diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py new file mode 100644 index 0000000000..16b1dcf321 --- /dev/null +++ b/openpype/client/__init__.py @@ -0,0 +1,71 @@ +from .entities import ( + get_projects, + get_project, + + get_asset_by_id, + get_asset_by_name, + get_assets, + get_asset_ids_with_subsets, + + get_subset_by_id, + get_subset_by_name, + get_subsets, + get_subset_families, + + get_version_by_id, + get_version_by_name, + get_versions, + get_hero_version_by_id, + get_hero_version_by_subset_id, + get_hero_versions, + get_last_versions, + get_last_version_by_subset_id, + get_last_version_by_subset_name, + get_output_link_versions, + + get_representation_by_id, + get_representation_by_name, + get_representations, + get_representation_parents, + get_representations_parents, + + get_thumbnail, + get_thumbnails, + get_thumbnail_id_from_source, +) + +__all__ = ( + "get_projects", + "get_project", + + "get_asset_by_id", + "get_asset_by_name", + "get_assets", + "get_asset_ids_with_subsets", + + "get_subset_by_id", + "get_subset_by_name", + "get_subsets", + "get_subset_families", + + "get_version_by_id", + "get_version_by_name", + "get_versions", + "get_hero_version_by_id", + "get_hero_version_by_subset_id", + "get_hero_versions", + "get_last_versions", + "get_last_version_by_subset_id", + "get_last_version_by_subset_name", + "get_output_link_versions", + + "get_representation_by_id", + "get_representation_by_name", + "get_representations", + "get_representation_parents", + "get_representations_parents", + + "get_thumbnail", + "get_thumbnails", + "get_thumbnail_id_from_source", +) diff --git a/openpype/client/entities.py b/openpype/client/entities.py new file mode 100644 index 0000000000..a56288c1e8 --- /dev/null +++ b/openpype/client/entities.py @@ -0,0 +1,1663 @@ +"""Unclear if these will have public functions like these. + +Goal is that most of functions here are called on (or with) an object +that has project name as a context (e.g. on 'ProjectEntity'?). + ++ We will need more specific functions doing wery specific queires really fast. +""" + +import os +import collections + +import six +from bson.objectid import ObjectId + +from openpype.lib.mongo import OpenPypeMongoConnection + + +def _get_project_connection(project_name=None): + db_name = os.environ.get("AVALON_DB") or "avalon" + mongodb = OpenPypeMongoConnection.get_mongo_client()[db_name] + if project_name: + return mongodb[project_name] + return mongodb + + +def _prepare_fields(fields): + if not fields: + return None + + output = { + field: True + for field in fields + } + if "_id" not in output: + output["_id"] = True + return output + + +def _convert_id(in_id): + if isinstance(in_id, six.string_types): + return ObjectId(in_id) + return in_id + + +def _convert_ids(in_ids): + _output = set() + for in_id in in_ids: + if in_id is not None: + _output.add(_convert_id(in_id)) + return list(_output) + + +def get_projects(active=True, inactive=False, fields=None): + mongodb = _get_project_connection() + for project_name in mongodb.collection_names(): + if project_name in ("system.indexes",): + continue + project_doc = get_project( + project_name, active=active, inactive=inactive, fields=fields + ) + if project_doc is not None: + yield project_doc + + +def get_project(project_name, active=True, inactive=False, fields=None): + # Skip if both are disabled + if not active and not inactive: + return None + + query_filter = {"type": "project"} + # Keep query untouched if both should be available + if active and inactive: + pass + + # Add filter to keep only active + elif active: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": True}, + ] + + # Add filter to keep only inactive + elif inactive: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": False}, + ] + + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_asset_by_id(project_name, asset_id, fields=None): + """Receive asset data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_id (str|ObjectId): Asset's id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + dict: Asset entity data. + None: Asset was not found by id. + """ + + asset_id = _convert_id(asset_id) + if not asset_id: + return None + + query_filter = {"type": "asset", "_id": asset_id} + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_asset_by_name(project_name, asset_name, fields=None): + """Receive asset data by it's name. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_name (str): Asset's name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + dict: Asset entity data. + None: Asset was not found by name. + """ + + if not asset_name: + return None + + query_filter = {"type": "asset", "name": asset_name} + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_assets( + project_name, asset_ids=None, asset_names=None, archived=False, fields=None +): + """Assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + + asset_types = ["asset"] + if archived: + asset_types.append("archived_asset") + + if len(asset_types) == 1: + query_filter = {"type": asset_types[0]} + else: + query_filter = {"type": {"$in": asset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["_id"] = {"$in": asset_ids} + + if asset_names is not None: + if not asset_names: + return [] + query_filter["name"] = {"$in": list(asset_names)} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_asset_ids_with_subsets(project_name, asset_ids=None): + """Find out which assets have existing subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Look only for entered asset ids. + + Returns: + List[ObjectId]: Asset ids that have existing subsets. + """ + + subset_query = { + "type": "subset" + } + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + subset_query["parent"] = {"$in": asset_ids} + + conn = _get_project_connection(project_name) + result = conn.aggregate([ + { + "$match": subset_query + }, + { + "$group": { + "_id": "$parent", + "count": {"$sum": 1} + } + } + ]) + asset_ids_with_subsets = [] + for item in result: + asset_id = item["_id"] + count = item["count"] + if count > 0: + asset_ids_with_subsets.append(asset_id) + return asset_ids_with_subsets + + +def get_subset_by_id(project_name, subset_id, fields=None): + """Single subset entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Id of subset which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: + return None + + query_filters = {"type": "subset", "_id": subset_id} + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subset_by_name(project_name, subset_name, asset_id, fields=None): + """Single subset entity data by it's name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Id of parent asset. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + if not subset_name: + return None + + asset_id = _convert_id(asset_id) + if not asset_id: + return None + + query_filters = { + "type": "subset", + "name": subset_name, + "parent": asset_id + } + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subsets( + project_name, + subset_ids=None, + subset_names=None, + asset_ids=None, + archived=False, + fields=None +): + """Subset entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. + Filter ignored if 'None' is passed. + subset_names (list[str]): Subset names that should be queried. + Filter ignored if 'None' is passed. + asset_ids (list[str|ObjectId]): Asset ids under which should look for + the subsets. Filter ignored if 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching subsets. + """ + + subset_types = ["subset"] + if archived: + subset_types.append("archived_subset") + + if len(subset_types) == 1: + query_filter = {"type": subset_types[0]} + else: + query_filter = {"type": {"$in": subset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["parent"] = {"$in": asset_ids} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["_id"] = {"$in": subset_ids} + + if subset_names is not None: + if not subset_names: + return [] + query_filter["name"] = {"$in": list(subset_names)} + + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_subset_families(project_name, subset_ids=None): + """Set of main families of subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. + All subsets from project are used if 'None' is passed. + + Returns: + set[str]: Main families of matching subsets. + """ + + subset_filter = { + "type": "subset" + } + if subset_ids is not None: + if not subset_ids: + return set() + subset_filter["_id"] = {"$in": list(subset_ids)} + + conn = _get_project_connection(project_name) + result = list(conn.aggregate([ + {"$match": subset_filter}, + {"$project": { + "family": {"$arrayElemAt": ["$data.families", 0]} + }}, + {"$group": { + "_id": "family_group", + "families": {"$addToSet": "$family"} + }} + ])) + if result: + return set(result[0]["families"]) + return set() + + +def get_version_by_id(project_name, version_id, fields=None): + """Single version entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + + query_filter = { + "type": {"$in": ["version", "hero_version"]}, + "_id": version_id + } + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_version_by_name(project_name, version, subset_id, fields=None): + """Single version entity data by it's name and subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + version (int): name of version entity (it's version). + subset_id (str|ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: + return None + + conn = _get_project_connection(project_name) + query_filter = { + "type": "version", + "parent": subset_id, + "name": version + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def _get_versions( + project_name, + subset_ids=None, + version_ids=None, + standard=True, + hero=False, + fields=None +): + version_types = [] + if standard: + version_types.append("version") + + if hero: + version_types.append("hero_version") + + if not version_types: + return [] + elif len(version_types) == 1: + query_filter = {"type": version_types[0]} + else: + query_filter = {"type": {"$in": version_types}} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["parent"] = {"$in": subset_ids} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["_id"] = {"$in": version_ids} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_versions( + project_name, + version_ids=None, + subset_ids=None, + hero=False, + fields=None +): + """Version entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + version_ids (list[str|ObjectId]): Version ids that will be queried. + Filter ignored if 'None' is passed. + subset_ids (list[str]): Subset ids that will be queried. + Filter ignored if 'None' is passed. + hero (bool): Look also for hero versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching versions. + """ + + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=True, + hero=hero, + fields=fields + ) + + +def get_hero_version_by_subset_id(project_name, subset_id, fields=None): + """Hero version by subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Subset id under which is hero version. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version for passed subset id does not exists. + Dict: Hero version entity data. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: + return None + + versions = list(_get_versions( + project_name, + subset_ids=[subset_id], + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_version_by_id(project_name, version_id, fields=None): + """Hero version by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Hero version id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version with passed id was not found. + Dict: Hero version entity data. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + + versions = list(_get_versions( + project_name, + version_ids=[version_id], + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_versions( + project_name, + subset_ids=None, + version_ids=None, + fields=None +): + """Hero version entities data from one project filtered by entered filters. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str|ObjectId]): Subset ids for which should look for + hero versions. Filter ignored if 'None' is passed. + version_ids (list[str|ObjectId]): Hero version ids. Filter ignored if + 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable yielding hero versions matching passed filters. + """ + + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=False, + hero=True, + fields=fields + ) + + +def get_output_link_versions(project_name, version_id, fields=None): + """Versions where passed version was used as input. + + Question: + Not 100% sure about the usage of the function so the name and docstring + maybe does not match what it does? + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Version id which can be used as input link + for other versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable cursor yielding versions that are used as input + links for passed version. + """ + + version_id = _convert_id(version_id) + if not version_id: + return [] + + conn = _get_project_connection(project_name) + # Does make sense to look for hero versions? + query_filter = { + "type": "version", + "data.inputLinks.input": version_id + } + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_last_versions(project_name, subset_ids, fields=None): + """Latest versions for entered subset_ids. + + Args: + subset_ids (list): List of subset ids. + + Returns: + dict[ObjectId, int]: Key is subset id and value is last version name. + """ + + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return {} + + _pipeline = [ + # Find all versions of those subsets + {"$match": { + "type": "version", + "parent": {"$in": subset_ids} + }}, + # Sorting versions all together + {"$sort": {"name": 1}}, + # Group them by "parent", but only take the last + {"$group": { + "_id": "$parent", + "_version_id": {"$last": "$_id"} + }} + ] + + conn = _get_project_connection(project_name) + version_ids = [ + doc["_version_id"] + for doc in conn.aggregate(_pipeline) + ] + fields = _prepare_fields(fields) + if fields and "parent" not in fields: + fields.append("parent") + + version_docs = get_versions( + project_name, version_ids=version_ids, fields=fields + ) + + return { + version_doc["parent"]: version_doc + for version_doc in version_docs + } + + +def get_last_version_by_subset_id(project_name, subset_id, fields=None): + """Last version for passed subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: + return None + + last_versions = get_last_versions( + project_name, subset_ids=[subset_id], fields=fields + ) + return last_versions.get(subset_id) + + +def get_last_version_by_subset_name( + project_name, subset_name, asset_id, fields=None +): + """Last version for passed subset name under asset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Asset id which is parnt of passed subset name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_doc = get_subset_by_name( + project_name, subset_name, asset_id, fields=["_id"] + ) + if not subset_doc: + return None + return get_last_version_by_subset_id( + project_name, subset_doc["_id"], fields=fields + ) + + +def get_representation_by_id(project_name, representation_id, fields=None): + """Representation entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_id (str|ObjectId): Representation id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + + if not representation_id: + return None + + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types} + } + if representation_id is not None: + query_filter["_id"] = _convert_id(representation_id) + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representation_by_name( + project_name, representation_name, version_id, fields=None +): + """Representation entity data by it's name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_name (str): Representation name. + version_id (str|ObjectId): Id of parent version entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + + version_id = _convert_id(version_id) + if not version_id or not representation_name: + return None + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types}, + "name": representation_name, + "parent": version_id + } + + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + archived=False, + fields=None +): + """Representaion entities data from one project filtered by filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + archived (bool): Output will also contain archived representations. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + + repre_types = ["representation"] + if archived: + repre_types.append("archived_representations") + if len(repre_types) == 1: + query_filter = {"type": repre_types[0]} + else: + query_filter = {"type": {"$in": repre_types}} + + if representation_ids is not None: + representation_ids = _convert_ids(representation_ids) + if not representation_ids: + return [] + query_filter["_id"] = {"$in": representation_ids} + + if representation_names is not None: + if not representation_names: + return [] + query_filter["name"] = {"$in": list(representation_names)} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["parent"] = {"$in": version_ids} + + if extensions is not None: + if not extensions: + return [] + query_filter["context.ext"] = {"$in": list(extensions)} + + if names_by_version_ids is not None: + or_query = [] + for version_id, names in names_by_version_ids.items(): + if version_id and names: + or_query.append({ + "parent": _convert_id(version_id), + "name": {"$in": list(names)} + }) + if not or_query: + return [] + query_filter["$or"] = or_query + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_representations_parents(project_name, representations): + """Prepare parents of representation entities. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representations (list[dict]): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + + repres_by_version_id = collections.defaultdict(list) + versions_by_version_id = {} + versions_by_subset_id = collections.defaultdict(list) + subsets_by_subset_id = {} + subsets_by_asset_id = collections.defaultdict(list) + for representation in representations: + repre_id = representation["_id"] + version_id = representation["parent"] + repres_by_version_id[version_id].append(representation) + + versions = get_versions( + project_name, version_ids=repres_by_version_id.keys() + ) + for version in versions: + version_id = version["_id"] + subset_id = version["parent"] + versions_by_version_id[version_id] = version + versions_by_subset_id[subset_id].append(version) + + subsets = get_subsets( + project_name, subset_ids=versions_by_subset_id.keys() + ) + for subset in subsets: + subset_id = subset["_id"] + asset_id = subset["parent"] + subsets_by_subset_id[subset_id] = subset + subsets_by_asset_id[asset_id].append(subset) + + assets = get_assets(project_name, asset_ids=subsets_by_asset_id.keys()) + assets_by_id = { + asset["_id"]: asset + for asset in assets + } + + project = get_project(project_name) + + output = {} + for version_id, representations in repres_by_version_id.items(): + asset = None + subset = None + version = versions_by_version_id.get(version_id) + if version: + subset_id = version["parent"] + subset = subsets_by_subset_id.get(subset_id) + if subset: + asset_id = subset["parent"] + asset = assets_by_id.get(asset_id) + + for representation in representations: + repre_id = representation["_id"] + output[repre_id] = (version, subset, asset, project) + return output + + +def get_representation_parents(project_name, representation): + """Prepare parents of representation entity. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representation (dict): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + + if not representation: + return None + + repre_id = representation["_id"] + parents_by_repre_id = get_representations_parents( + project_name, [representation] + ) + return parents_by_repre_id.get(repre_id) + + +def get_thumbnail_id_from_source(project_name, src_type, src_id): + """Receive thumbnail id from source entity. + + Args: + project_name (str): Name of project where to look for queried entities. + src_type (str): Type of source entity ('asset', 'version'). + src_id (str|objectId): Id of source entity. + + Returns: + ObjectId: Thumbnail id assigned to entity. + None: If Source entity does not have any thumbnail id assigned. + """ + + if not src_type or not src_id: + return None + + query_filter = {"_id": _convert_id(src_id)} + + conn = _get_project_connection(project_name) + src_doc = conn.find_one(query_filter, {"data.thumbnail_id"}) + if src_doc: + return src_doc.get("data", {}).get("thumbnail_id") + return None + + +def get_thumbnails(project_name, thumbnail_ids, fields=None): + """Receive thumbnails entity data. + + Thumbnail entity can be used to receive binary content of thumbnail based + on it's content and ThumbnailResolvers. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_ids (list[str|ObjectId]): Ids of thumbnail entities. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + cursor: Cursor of queried documents. + """ + + if thumbnail_ids: + thumbnail_ids = _convert_ids(thumbnail_ids) + + if not thumbnail_ids: + return [] + query_filter = { + "type": "thumbnail", + "_id": {"$in": thumbnail_ids} + } + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_thumbnail(project_name, thumbnail_id, fields=None): + """Receive thumbnail entity data. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_id (str|ObjectId): Id of thumbnail entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If thumbnail with specified id was not found. + Dict: Thumbnail entity data which can be reduced to specified 'fields'. + """ + + if not thumbnail_id: + return None + query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +""" +## Custom data storage: +- Settings - OP settings overrides and local settings +- Logging - logs from PypeLogger +- Webpublisher - jobs +- Ftrack - events +- Maya - Shaders + - openpype/hosts/maya/api/shader_definition_editor.py + - openpype/hosts/maya/plugins/publish/validate_model_name.py + +## Global launch hooks +- openpype/hooks/pre_global_host_data.py + Query: + - project + - asset + +## Global load plugins +- openpype/plugins/load/delete_old_versions.py + Query: + - versions + - representations +- openpype/plugins/load/delivery.py + Query: + - representations + +## Global publish plugins +- openpype/plugins/publish/collect_avalon_entities.py + Query: + - asset + - project +- openpype/plugins/publish/collect_anatomy_instance_data.py + Query: + - assets + - subsets + - last version +- openpype/plugins/publish/collect_scene_loaded_versions.py + Query: + - representations +- openpype/plugins/publish/extract_hierarchy_avalon.py + Query: + - asset + - assets + - project + Create: + - asset + Update: + - asset +- openpype/plugins/publish/integrate_hero_version.py + Query: + - version + - hero version + - representations +- openpype/plugins/publish/integrate_new.py + Query: + - asset + - subset + - version + - representations +- openpype/plugins/publish/integrate_thumbnail.py + Query: + - version +- openpype/plugins/publish/validate_editorial_asset_name.py + Query: + - assets + +## Lib +- openpype/lib/applications.py + Query: + - project + - asset +- openpype/lib/avalon_context.py + Query: + - project + - asset + - linked assets (new function get_linked_assets?) + - subset + - subsets + - version + - versions + - last version + - representations + - linked representations (new function get_linked_ids_for_representations) + Update: + - workfile data +- openpype/lib/plugin_tools.py + Query: + - asset +- openpype/lib/project_backpack.py + Query: + - project + - everything from mongo + Update: + - project +- openpype/lib/usdlib.py + Query: + - project + - asset + +## Pipeline +- openpype/pipeline/load/utils.py + Query: + - project + - assets + - subsets + - version + - versions + - representation + - representations +- openpype/pipeline/mongodb.py + Query: + - project +- openpype/pipeline/thumbnail.py + Query: + - project + +## Hosts +### Aftereffects +- openpype/hosts/aftereffects/plugins/create/workfile_creator.py + Query: + - asset + +### Blender +- openpype/hosts/blender/api/pipeline.py + Query: + - asset +- openpype/hosts/blender/plugins/publish/extract_layout.py + Query: + - representation + +### Celaction +- openpype/hosts/celaction/plugins/publish/collect_audio.py + Query: + - subsets + - last versions + - representations + +### Fusion +- openpype/hosts/fusion/api/lib.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/fusion/plugins/load/load_sequence.py + Query: + - version +- openpype/hosts/fusion/scripts/fusion_switch_shot.py + Query: + - project + - asset + - versions +- openpype/hosts/fusion/utility_scripts/switch_ui.py + Query: + - assets + +### Harmony +- openpype/hosts/harmony/api/pipeline.py + Query: + - representation + +### Hiero +- openpype/hosts/hiero/api/lib.py + Query: + - project + - version + - versions + - representation +- openpype/hosts/hiero/api/tags.py + Query: + - task types + - assets +- openpype/hosts/hiero/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py + Query: + - assets + +### Houdini +- openpype/hosts/houdini/api/lib.py + Query: + - asset +- openpype/hosts/houdini/api/usd.py + Query: + - asset +- openpype/hosts/houdini/plugins/create/create_hda.py + Query: + - asset + - subsets +- openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py + Query: + - asset + - subset +- openpype/hosts/houdini/plugins/publish/extract_usd_layered.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py + Query: + - asset + - subset +- openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py + Query: + - project + - asset + +### Maya +- openpype/hosts/maya/api/action.py + Query: + - asset +- openpype/hosts/maya/api/commands.py + Query: + - asset + - project +- openpype/hosts/maya/api/lib.py + Query: + - project + - asset + - subset + - subsets + - version + - representation +- openpype/hosts/maya/api/setdress.py + Query: + - version + - representation +- openpype/hosts/maya/plugins/inventory/import_modelrender.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_audio.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_image_plane.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_look.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_vrayproxy.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_yeti_cache.py + Query: + - representation +- openpype/hosts/maya/plugins/publish/collect_review.py + Query: + - subsets +- openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py + Query: + - assets +- openpype/hosts/maya/plugins/publish/validate_node_ids_related.py + Query: + - asset +- openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py + Query: + - asset + - subset + +### Nuke +- openpype/hosts/nuke/api/command.py + Query: + - project + - asset +- openpype/hosts/nuke/api/lib.py + Query: + - project + - asset + - version + - versions + - representation +- openpype/hosts/nuke/plugins/load/load_backdrop.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_camera_abc.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_image.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_model.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_script_precomp.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/publish/collect_reads.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_instances.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_writes.py + Query: + - representation +- openpype/hosts/nuke/plugins/publish/validate_script.py + Query: + - asset + - project + +### Photoshop +- openpype/hosts/photoshop/plugins/create/workfile_creator.py + Query: + - asset + +### Resolve +- openpype/hosts/resolve/plugins/load/load_clip.py + Query: + - version + - versions + +### Standalone publisher +- openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py + Query: + - asset +- openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py + Query: + - assets +- openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py + Query: + - project + - asset +- openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py + Query: + - assets + +### TVPaint +- openpype/hosts/tvpaint/api/pipeline.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/load/load_workfile.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_instances.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_workfile.py + Query: + - asset + +### Unreal +- openpype/hosts/unreal/plugins/load/load_camera.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/load/load_layout.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/publish/extract_layout.py + Query: + - representation + +### Webpublisher +- openpype/hosts/webpublisher/webserver_service/webpublish_routes.py + Query: + - assets +- openpype/hosts/webpublisher/plugins/publish/collect_published_files.py + Query: + - last versions + +## Tools +openpype/tools/assetlinks/widgets.py +- SimpleLinkView + Query: + - get_versions + - get_subsets + - get_assets + - get_output_link_versions + +openpype/tools/creator/window.py +- CreatorWindow + Query: + - get_asset_by_name + - get_subsets + +openpype/tools/launcher/models.py +- LauncherModel + Query: + - get_project + - get_assets + +openpype/tools/libraryloader/app.py +- LibraryLoaderWindow + Query: + - get_project + +openpype/tools/loader/app.py +- LoaderWindow + Query: + - get_project +- show + Query: + - get_projects + +openpype/tools/loader/model.py +- SubsetsModel + Query: + - get_assets + - get_subsets + - get_last_versions + - get_versions + - get_hero_versions + - get_version_by_name +- RepresentationModel + Query: + - get_representations + - sync server specific queries (separated into multiple functions?) + - NOT REPLACED + +openpype/tools/loader/widgets.py +- FamilyModel + Query: + - get_subset_families +- VersionTextEdit + Query: + - get_subset_by_id + - get_version_by_id +- SubsetWidget + Query: + - get_subsets + - get_representations + Update: + - Subset groups (combination of asset id and subset names) +- RepresentationWidget + Query: + - get_subsets + - get_versions + - get_representations +- ThumbnailWidget + Query: + - get_thumbnail_id_from_source + - get_thumbnail + +openpype/tools/mayalookassigner/app.py +- MayaLookAssignerWindow + Query: + - get_last_version_by_subset_id + +openpype/tools/mayalookassigner/commands.py +- create_items_from_nodes + Query: + - get_asset_by_id + +openpype/tools/mayalookassigner/vray_proxies.py +- get_look_relationships + Query: + - get_representation_by_name +- load_look + Query: + - get_representation_by_name +- vrayproxy_assign_look + Query: + - get_last_version_by_subset_name + +openpype/tools/project_manager/project_manager/model.py +- HierarchyModel + Query: + - get_asset_ids_with_subsets + - get_project + - get_assets + +openpype/tools/project_manager/project_manager/view.py +- ProjectDocCache + Query: + - get_project + +openpype/tools/project_manager/project_manager/widgets.py +- CreateProjectDialog + Query: + - get_projects + +openpype/tools/publisher/widgets/create_dialog.py +- CreateDialog + Query: + - get_asset_by_name + - get_subsets + +openpype/tools/publisher/control.py +- AssetDocsCache + Query: + - get_assets + +openpype/tools/sceneinventory/model.py +- InventoryModel + Query: + - get_asset_by_id + - get_subset_by_id + - get_version_by_id + - get_last_version_by_subset_id + - get_representation + +openpype/tools/sceneinventory/switch_dialog.py +- SwitchAssetDialog + Query: + - get_asset_by_name + - get_assets + - get_subset_by_name + - get_subsets + - get_versions + - get_hero_versions + - get_last_versions + - get_representations + +openpype/tools/sceneinventory/view.py +- SceneInventoryView + Query: + - get_version_by_id + - get_versions + - get_hero_versions + - get_representation_by_id + - get_representations + +openpype/tools/standalonepublish/widgets/model_asset.py +- AssetModel + Query: + - get_assets + +openpype/tools/standalonepublish/widgets/widget_asset.py +- AssetWidget + Query: + - get_project + - get_asset_by_id + +openpype/tools/standalonepublish/widgets/widget_family.py +- FamilyWidget + Query: + - get_asset_by_name + - get_subset_by_name + - get_subsets + - get_last_version_by_subset_id + +openpype/tools/standalonepublish/app.py +- Window + Query: + - get_asset_by_id + +openpype/tools/texture_copy/app.py +- TextureCopy + Query: + - get_project + - get_asset_by_name + +openpype/tools/workfiles/files_widget.py +- FilesWidget + Query: + - get_asset_by_id + +openpype/tools/workfiles/model.py +- PublishFilesModel + Query: + - get_subsets + - get_versions + - get_representations + +openpype/tools/workfiles/save_as_dialog.py +- build_workfile_data + Query: + - get_project + - get_asset_by_name + +openpype/tools/workfiles/window.py +- Window + Query: + - get_asset_by_id + - get_asset_by_name + +openpype/tools/utils/assets_widget.py +- AssetModel + Query: + - get_project + - get_assets + +openpype/tools/utils/delegates.py +- VersionDelegate + Query: + - get_versions + - get_hero_versions + +openpype/tools/utils/lib.py +- GroupsConfig + Query: + - get_project +- FamilyConfigCache + Query: + - get_asset_by_name + +openpype/tools/utils/tasks_widget.py +- TasksModel + Query: + - get_project + - get_asset_by_id +""" diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 5b81764644..93d81145bc 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,6 +10,7 @@ from . import ops import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import ( schema, legacy_io, @@ -83,11 +84,9 @@ def uninstall(): def set_start_end_frames(): + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) scene = bpy.context.scene diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8ecc78a2c6..75d9cf440d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -1,13 +1,11 @@ import os import json -from bson.objectid import ObjectId - import bpy import bpy_extras import bpy_extras.anim_utils -from openpype.pipeline import legacy_io +from openpype.client import get_representation_by_name from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY import openpype.api @@ -131,43 +129,32 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 + project_name = instance.context.data["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) - parent = metadata["parent"] + version_id = metadata["parent"] family = metadata["family"] - self.log.debug("Parent: {}".format(parent)) + self.log.debug("Parent: {}".format(version_id)) # Get blend reference - blend = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "blend" - }, - projection={"_id": True}) + blend = get_representation_by_name( + project_name, "blend", version_id, fields=["_id"] + ) blend_id = None if blend: blend_id = blend["_id"] # Get fbx reference - fbx = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "fbx" - }, - projection={"_id": True}) + fbx = get_representation_by_name( + project_name, "fbx", version_id, fields=["_id"] + ) fbx_id = None if fbx: fbx_id = fbx["_id"] # Get abc reference - abc = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "abc" - }, - projection={"_id": True}) + abc = get_representation_by_name( + project_name, "abc", version_id, fields=["_id"] + ) abc_id = None if abc: abc_id = abc["_id"] diff --git a/openpype/hosts/hiero/api/otio/hiero_export.py b/openpype/hosts/hiero/api/otio/hiero_export.py index 1e4088d9c0..81cb43fa12 100644 --- a/openpype/hosts/hiero/api/otio/hiero_export.py +++ b/openpype/hosts/hiero/api/otio/hiero_export.py @@ -132,7 +132,7 @@ def create_time_effects(otio_clip, track_item): otio_effect = otio.schema.TimeEffect() otio_effect.name = name otio_effect.effect_name = effect_name - otio_effect.metadata = metadata + otio_effect.metadata.update(metadata) # add otio effect to clip effects otio_clip.effects.append(otio_effect) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index dc17ddc605..e8ada57f8f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -603,6 +603,18 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) + # renderman allows nodes to have filename attribute empty while + # you can have another incoming connection from different node. + pxr_nodes = set() + if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): + pxr_nodes = set( + cmds.pluginInfo("RenderMan_for_Maya", + query=True, + dependNode=True) + ) + if not source and cmds.nodeType(node) in pxr_nodes: + self.log.info("Renderman: source is empty, skipping...") + continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the # paths as the computed patterns diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2c5989309b..505eb19419 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -539,7 +539,9 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset): imageio_nodes = get_nuke_imageio_settings()["nodes"] required_nodes = imageio_nodes["requiredNodes"] - override_nodes = imageio_nodes["overrideNodes"] + + # HACK: for backward compatibility this needs to be optional + override_nodes = imageio_nodes.get("overrideNodes", []) imageio_node = None for node in required_nodes: diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 4007ccf51e..bb08e8c2c6 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -2,7 +2,20 @@ import nuke from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import ( - create_write_node, create_write_node_legacy) + create_write_node, + create_write_node_legacy, + get_created_node_imageio_setting_legacy +) + +# HACK: just to disable still image on projects which +# are not having anatomy imageio preset for CreateWriteStill +# TODO: remove this code as soon as it will be obsolete +imageio_writes = get_created_node_imageio_setting_legacy( + "Write", + "CreateWriteStill", + "stillMain" +) +print(imageio_writes["knobs"]) class CreateWriteStill(plugin.AbstractWriteRender): diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py new file mode 100644 index 0000000000..8a8e86e7b9 --- /dev/null +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -0,0 +1,288 @@ +import threading +import datetime +import copy +import collections + +import ftrack_api + +from openpype.lib import get_datetime_data +from openpype.api import get_project_settings +from openpype_modules.ftrack.lib import ServerAction + + +class CreateDailyReviewSessionServerAction(ServerAction): + """Create daily review session object per project. + + Action creates review sessions based on settings. Settings define if is + action enabled and what is a template for review session name. Logic works + in a way that if review session with the name already exists then skip + process. If review session for current day does not exist but yesterdays + review exists and is empty then yesterdays is renamed otherwise creates + new review session. + + Also contains cycle creation of dailies which is triggered each morning. + This option must be enabled in project settings. Cycle creation is also + checked on registration of action. + """ + + identifier = "create.daily.review.session" + #: Action label. + label = "OpenPype Admin" + variant = "- Create Daily Review Session (Server)" + #: Action description. + description = "Manually create daily review session" + role_list = {"Pypeclub", "Administrator", "Project Manager"} + + settings_key = "create_daily_review_session" + default_template = "{yy}{mm}{dd}" + + def __init__(self, *args, **kwargs): + super(CreateDailyReviewSessionServerAction, self).__init__( + *args, **kwargs + ) + + self._cycle_timer = None + self._last_cyle_time = None + self._day_delta = datetime.timedelta(days=1) + + def discover(self, session, entities, event): + """Show action only on AssetVersions.""" + + valid_selection = False + for ent in event["data"]["selection"]: + # Ignore entities that are not tasks or projects + if ent["entityType"].lower() in ( + "show", "task", "reviewsession", "assetversion" + ): + valid_selection = True + break + + if not valid_selection: + return False + return self.valid_roles(session, entities, event) + + def launch(self, session, entities, event): + project_entity = self.get_project_from_entity(entities[0], session) + project_name = project_entity["full_name"] + project_settings = self.get_project_settings_from_event( + event, project_name + ) + action_settings = self._extract_action_settings(project_settings) + project_name_by_id = { + project_entity["id"]: project_name + } + settings_by_project_id = { + project_entity["id"]: action_settings + } + self._process_review_session( + session, settings_by_project_id, project_name_by_id + ) + return True + + def register(self, *args, **kwargs): + """Override register to be able trigger """ + # Register server action as would be normally + super(CreateDailyReviewSessionServerAction, self).register( + *args, **kwargs + ) + + # Create threading timer which will trigger creation of report + # at the 00:00:01 of next day + # - callback will trigger another timer which will have 1 day offset + now = datetime.datetime.now() + # Create object of today morning + today_morning = datetime.datetime( + now.year, now.month, now.day, 0, 0, 1 + ) + # Add a day delta (to calculate next day date) + next_day_morning = today_morning + self._day_delta + # Calculate first delta in seconds for first threading timer + first_delta = (next_day_morning - now).total_seconds() + # Store cycle time which will be used to create next timer + self._last_cyle_time = next_day_morning + # Create timer thread + self._cycle_timer = threading.Timer(first_delta, self._timer_callback) + self._cycle_timer.start() + + self._check_review_session() + + def _timer_callback(self): + if ( + self._cycle_timer is not None + and self._last_cyle_time is not None + ): + now = datetime.datetime.now() + while self._last_cyle_time < now: + self._last_cyle_time = self._last_cyle_time + self._day_delta + + delay = (self._last_cyle_time - now).total_seconds() + + self._cycle_timer = threading.Timer(delay, self._timer_callback) + self._cycle_timer.start() + self._check_review_session() + + def _check_review_session(self): + session = ftrack_api.Session( + server_url=self.session.server_url, + api_key=self.session.api_key, + api_user=self.session.api_user, + auto_connect_event_hub=False + ) + project_entities = session.query( + "select id, full_name from Project" + ).all() + project_names_by_id = { + project_entity["id"]: project_entity["full_name"] + for project_entity in project_entities + } + + action_settings_by_project_id = self._get_action_settings( + project_names_by_id + ) + enabled_action_settings_by_project_id = {} + for item in action_settings_by_project_id.items(): + project_id, action_settings = item + if action_settings.get("cycle_enabled"): + enabled_action_settings_by_project_id[project_id] = ( + action_settings + ) + + if not enabled_action_settings_by_project_id: + self.log.info(( + "There are no projects that have enabled" + " cycle review sesison creation" + )) + + else: + self._process_review_session( + session, + enabled_action_settings_by_project_id, + project_names_by_id + ) + + session.close() + + def _process_review_session( + self, session, settings_by_project_id, project_names_by_id + ): + review_sessions = session.query(( + "select id, name, project_id" + " from ReviewSession where project_id in ({})" + ).format(self.join_query_keys(settings_by_project_id))).all() + + review_sessions_by_project_id = collections.defaultdict(list) + for review_session in review_sessions: + project_id = review_session["project_id"] + review_sessions_by_project_id[project_id].append(review_session) + + # Prepare fill data for today's review sesison and yesterdays + now = datetime.datetime.now() + today_obj = datetime.datetime( + now.year, now.month, now.day, 0, 0, 0 + ) + yesterday_obj = today_obj - self._day_delta + + today_fill_data = get_datetime_data(today_obj) + yesterday_fill_data = get_datetime_data(yesterday_obj) + + # Loop through projects and try to create daily reviews + for project_id, action_settings in settings_by_project_id.items(): + review_session_template = ( + action_settings["review_session_template"] + ).strip() or self.default_template + + today_project_fill_data = copy.deepcopy(today_fill_data) + yesterday_project_fill_data = copy.deepcopy(yesterday_fill_data) + project_name = project_names_by_id[project_id] + today_project_fill_data["project_name"] = project_name + yesterday_project_fill_data["project_name"] = project_name + + today_session_name = self._fill_review_template( + review_session_template, today_project_fill_data + ) + yesterday_session_name = self._fill_review_template( + review_session_template, yesterday_project_fill_data + ) + # Skip if today's session name could not be filled + if not today_session_name: + continue + + # Find matchin review session + project_review_sessions = review_sessions_by_project_id[project_id] + todays_session = None + yesterdays_session = None + for review_session in project_review_sessions: + session_name = review_session["name"] + if session_name == today_session_name: + todays_session = review_session + break + elif session_name == yesterday_session_name: + yesterdays_session = review_session + + # Skip if today's session already exist + if todays_session is not None: + self.log.debug(( + "Todays ReviewSession \"{}\"" + " in project \"{}\" already exists" + ).format(today_session_name, project_name)) + continue + + # Check if there is yesterday's session and is empty + # - in that case just rename it + if ( + yesterdays_session is not None + and len(yesterdays_session["review_session_objects"]) == 0 + ): + self.log.debug(( + "Renaming yesterdays empty review session \"{}\" to \"{}\"" + " in project \"{}\"" + ).format( + yesterday_session_name, today_session_name, project_name + )) + yesterdays_session["name"] = today_session_name + session.commit() + continue + + # Create new review session with new name + self.log.debug(( + "Creating new review session \"{}\" in project \"{}\"" + ).format(today_session_name, project_name)) + session.create("ReviewSession", { + "project_id": project_id, + "name": today_session_name + }) + session.commit() + + def _get_action_settings(self, project_names_by_id): + settings_by_project_id = {} + for project_id, project_name in project_names_by_id.items(): + project_settings = get_project_settings(project_name) + action_settings = self._extract_action_settings(project_settings) + settings_by_project_id[project_id] = action_settings + return settings_by_project_id + + def _extract_action_settings(self, project_settings): + return ( + project_settings + .get("ftrack", {}) + .get(self.settings_frack_subkey, {}) + .get(self.settings_key) + ) or {} + + def _fill_review_template(self, template, data): + output = None + try: + output = template.format(**data) + except Exception: + self.log.warning( + ( + "Failed to fill review session template {} with data {}" + ).format(template, data), + exc_info=True + ) + return output + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + CreateDailyReviewSessionServerAction(session).register() diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index c8e7e79600..9359e3057b 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -144,3 +144,12 @@ def parenthood(*args, **kwargs): @requires_install def bulk_write(*args, **kwargs): return _connection_object.bulk_write(*args, **kwargs) + + +@requires_install +def active_project(*args, **kwargs): + return _connection_object.active_project(*args, **kwargs) + + +def current_project(*args, **kwargs): + return Session.get("AVALON_PROJECT") diff --git a/openpype/pipeline/mongodb.py b/openpype/pipeline/mongodb.py index 565e26b966..dab5bb9e13 100644 --- a/openpype/pipeline/mongodb.py +++ b/openpype/pipeline/mongodb.py @@ -199,6 +199,10 @@ class AvalonMongoDB: """Return the name of the active project""" return self.Session["AVALON_PROJECT"] + def current_project(self): + """Currently set project in Session without triggering installation.""" + return self.Session.get("AVALON_PROJECT") + @requires_install @auto_reconnect def projects(self, projection=None, only_active=True): diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 91f6102501..2471105250 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -940,9 +940,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): families += current_families # create relative source path for DB - if "source" in instance.data: - source = instance.data["source"] - else: + source = instance.data.get("source") + if not source: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] source = self.get_rootless_path(anatomy, source) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 9d59deea3d..831c34835e 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -116,6 +116,15 @@ "Administrator", "Project manager" ] + }, + "create_daily_review_session": { + "enabled": true, + "role_list": [ + "Administrator", + "Project Manager" + ], + "cycle_enabled": false, + "review_session_template": "{yy}{mm}{dd}" } }, "user_handlers": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index 16cab49d5d..f8f9d5093d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -388,6 +388,44 @@ "object_type": "text" } ] + }, + { + "key": "create_daily_review_session", + "label": "Create daily review session", + "type": "dict", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text", + "use_label_wrap": true + }, + { + "type": "boolean", + "key": "cycle_enabled", + "label": "Create daily review session" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "review_session_template", + "label": "ReviewSession template", + "placeholder": "Default: {yy}{mm}{dd}" + }, + { + "type": "label", + "label": "Possible formatting keys in template:
- \"project_name\" - <Name of project>
- \"d\" - <Day of month number> in shortest possible way.
- \"dd\" - <Day of month number> with 2 digits.
- \"ddd\" - <Week day name> shortened week day. e.g.: `Mon`, ...
- \"dddd\" - <Week day name> full name of week day. e.g.: `Monday`, ...
- \"m\" - <Month number> in shortest possible way. e.g.: `1` if January
- \"mm\" - <Month number> with 2 digits.
- \"mmm\" - <Month name> shortened month name. e.g.: `Jan`, ...
- \"mmmm\" -<Month name> full month name. e.g.: `January`, ...
- \"yy\" - <Year number> shortened year. e.g.: `19`, `20`, ...
- \"yyyy\" - <Year number> full year. e.g.: `2019`, `2020`, ..." + } + ] } ] }, diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 22e8848a60..1b168e542c 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -1,10 +1,16 @@ +import collections +from openpype.client import ( + get_versions, + get_subsets, + get_assets, + get_output_link_versions, +) from Qt import QtWidgets class SimpleLinkView(QtWidgets.QWidget): - - def __init__(self, dbcon, parent=None): + def __init__(self, dbcon, parent): super(SimpleLinkView, self).__init__(parent=parent) self.dbcon = dbcon @@ -24,6 +30,11 @@ class SimpleLinkView(QtWidgets.QWidget): self._in_view = in_view self._out_view = out_view + self._version_doc_to_process = None + + @property + def project_name(self): + return self.dbcon.current_project() def clear(self): self._in_view.clear() @@ -31,60 +42,114 @@ class SimpleLinkView(QtWidgets.QWidget): def set_version(self, version_doc): self.clear() - if not version_doc or not self.isVisible(): - return + self._version_doc_to_process = version_doc + if version_doc and self.isVisible(): + self._fill_values() - # inputs - # + def showEvent(self, event): + super(SimpleLinkView, self).showEvent(event) + self._fill_values() + + def _fill_values(self): + if self._version_doc_to_process is None: + return + version_doc = self._version_doc_to_process + self._version_doc_to_process = None + self._fill_inputs(version_doc) + self._fill_outputs(version_doc) + + def _fill_inputs(self, version_doc): + version_ids = set() for link in version_doc["data"].get("inputLinks", []): # Backwards compatibility for "input" key used as "id" if "id" not in link: link_id = link["input"] else: link_id = link["id"] - version = self.dbcon.find_one( - {"_id": link_id, "type": "version"}, - projection={"name": 1, "parent": 1} - ) - if not version: - continue - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + version_ids.add(link_id) - self._in_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + version_docs = list(get_versions( + self.project_name, + version_ids=version_ids, + fields=["name", "parent"] + )) + + versions_by_subset_id = collections.defaultdict(list) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = [] + if versions_by_subset_id: + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] )) - # outputs - # - outputs = self.dbcon.find( - {"type": "version", "data.inputLinks.input": version_doc["_id"]}, - projection={"name": 1, "parent": 1} - ) - for version in outputs or []: - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) - self._out_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._in_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) + + def _fill_outputs(self, version_doc): + version_docs = list(get_output_link_versions( + self.project_name, + version_doc["_id"], + fields=["name", "parent"] + )) + versions_by_subset_id = collections.defaultdict(list) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = [] + if versions_by_subset_id: + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] + )) + + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) + + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] + )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._out_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index e0c329fb78..a3937d6a40 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,6 +4,7 @@ import re from Qt import QtWidgets, QtCore +from openpype.client import get_asset_by_name, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -215,12 +216,12 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return + project_name = legacy_io.active_project() asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = legacy_io.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin @@ -235,7 +236,6 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return - project_name = legacy_io.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = legacy_io.Session["AVALON_TASK"] @@ -269,14 +269,13 @@ class CreatorWindow(QtWidgets.QDialog): self._subset_name_input.setText(subset_name) # Get all subsets of the current asset - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } existing_subset_names_low = set( _name.lower() for _name in existing_subset_names diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 13567e7916..3f899cc05e 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -9,6 +9,10 @@ import appdirs from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.lib import JSONSettingRegistry from openpype.lib.applications import ( CUSTOM_LAUNCH_APP_GROUPS, @@ -81,13 +85,11 @@ class ActionModel(QtGui.QStandardItemModel): def get_application_actions(self): actions = [] - if not self.dbcon.Session.get("AVALON_PROJECT"): + if not self.dbcon.current_project(): return actions - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"config.apps": True} - ) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name, fields=["config.apps"]) if not project_doc: return actions @@ -448,7 +450,7 @@ class LauncherModel(QtCore.QObject): @property def project_name(self): """Current project name.""" - return self._dbcon.Session.get("AVALON_PROJECT") + return self._dbcon.current_project() @property def refreshing_assets(self): @@ -649,9 +651,8 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = list(self._dbcon.find( - {"type": "asset"}, - self._asset_projection + asset_docs = list(get_assets( + self._last_project_name, fields=self._asset_projection.keys() )) if not self._refreshing_assets: return diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 7fda6bd6f9..5f4d10d796 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -3,6 +3,7 @@ import sys from Qt import QtWidgets, QtCore, QtGui from openpype import style +from openpype.client import get_project from openpype.pipeline import AvalonMongoDB from openpype.tools.utils import lib as tools_lib from openpype.tools.loader.widgets import ( @@ -303,14 +304,26 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families = self._subsets_widget.get_subsets_families() self._families_filter_view.set_enabled_families(families) - def set_context(self, context, refresh=True): - self.echo("Setting context: {}".format(context)) - lib.schedule( - lambda: self._set_context(context, refresh=refresh), - 50, channel="mongo" - ) - # ------------------------------ + def set_context(self, context, refresh=True): + """Set the selection in the interface using a context. + The context must contain `asset` data by name. + + Args: + context (dict): The context to apply. + Returns: + None + """ + + asset_name = context.get("asset", None) + if asset_name is None: + return + + if refresh: + self._refresh_assets() + + self._assets_widget.select_asset_by_name(asset_name) + def _on_family_filter_change(self, families): self._subsets_widget.set_family_filters(families) @@ -323,10 +336,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): """Load assets from database""" if self.current_project is not None: # Ensure a project is loaded - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"type": 1} - ) + project_doc = get_project(self.current_project, fields=["_id"]) assert project_doc, "This is a bug" self._families_filter_view.set_enabled_families(set()) @@ -371,7 +381,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # Clear the version information on asset change self._version_info_widget.set_version(None) - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self.data["state"]["assetIds"] = asset_ids @@ -426,34 +436,17 @@ class LibraryLoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + src_type = "version" if not thumbnail_src_ids: + src_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(src_type, thumbnail_src_ids) version_ids = [doc["_id"] for doc in version_docs or []] if self._repres_widget: self._repres_widget.set_version_ids(version_ids) - def _set_context(self, context, refresh=True): - """Set the selection in the interface using a context. - The context must contain `asset` data by name. - - Args: - context (dict): The context to apply. - Returns: - None - """ - - asset_name = context.get("asset", None) - if asset_name is None: - return - - if refresh: - self._refresh_assets() - - self._assets_widget.select_asset_by_name(asset_name) - def _on_message_timeout(self): self._message_label.setText("") diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index bb589c199d..1917f23c60 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -3,6 +3,7 @@ import traceback from Qt import QtWidgets, QtCore +from openpype.client import get_projects, get_project from openpype import style from openpype.lib import register_event_callback from openpype.pipeline import ( @@ -39,7 +40,7 @@ class LoaderWindow(QtWidgets.QDialog): def __init__(self, parent=None): super(LoaderWindow, self).__init__(parent) title = "Asset Loader 2.1" - project_name = legacy_io.Session.get("AVALON_PROJECT") + project_name = legacy_io.active_project() if project_name: title += " - {}".format(project_name) self.setWindowTitle(title) @@ -274,8 +275,9 @@ class LoaderWindow(QtWidgets.QDialog): """Load assets from database""" # Ensure a project is loaded - project = legacy_io.find_one({"type": "project"}, {"type": 1}) - assert project, "Project was not found! This is a bug" + project_name = legacy_io.active_project() + project_doc = get_project(project_name, fields=["_id"]) + assert project_doc, "Project was not found! This is a bug" self._assets_widget.refresh() self._assets_widget.setFocus() @@ -314,7 +316,7 @@ class LoaderWindow(QtWidgets.QDialog): ) # Clear the version information on asset change - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self._version_info_widget.set_version(None) self.data["state"]["assetIds"] = asset_ids @@ -371,10 +373,12 @@ class LoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + source_type = "version" if not thumbnail_src_ids: + source_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(source_type, thumbnail_src_ids) if self._repres_widget is not None: version_ids = [doc["_id"] for doc in version_docs] @@ -576,8 +580,7 @@ def show(debug=False, parent=None, use_context=False): legacy_io.install() any_project = next( - project for project in legacy_io.projects() - if project.get("active", True) is not False + project for project in get_projects(fields=["name"]) ) legacy_io.Session["AVALON_PROJECT"] = any_project["name"] diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..b5dc16a680 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -7,6 +7,15 @@ from uuid import uuid4 from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, + get_versions, + get_hero_versions, + get_version_by_name, + get_representations +) from openpype.pipeline import ( HeroVersionType, schema, @@ -56,7 +65,7 @@ class BaseRepresentationModel(object): remote_site = remote_provider = None if not project_name: - project_name = self.dbcon.Session.get("AVALON_PROJECT") + project_name = self.dbcon.active_project() else: self.dbcon.Session["AVALON_PROJECT"] = project_name @@ -197,9 +206,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if subset_doc_projection: self.subset_doc_projection = subset_doc_projection - self.asset_doc_projection = asset_doc_projection - self.subset_doc_projection = subset_doc_projection - self.repre_icons = {} self.sync_server = None self.active_site = self.active_provider = None @@ -225,7 +231,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_fetching_stop = False self._doc_payload = {} - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self.refresh() @@ -244,7 +250,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): def set_grouping(self, state): self._grouping = state - self.on_doc_fetched() + self._on_doc_fetched() def get_subsets_families(self): return self._doc_payload.get("subset_families") or set() @@ -254,57 +260,61 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # because it also updates the information in other columns if index.column() == self.columns_index["version"]: item = index.internalPointer() - parent = item["_id"] + subset_id = item["_id"] if isinstance(value, HeroVersionType): - versions = list(self.dbcon.find({ - "type": {"$in": ["version", "hero_version"]}, - "parent": parent - }, sort=[("name", -1)])) - - version = None - last_version = None - for __version in versions: - if __version["type"] == "hero_version": - version = __version - elif last_version is None: - last_version = __version - - if version is not None and last_version is not None: - break - - _version = None - for __version in versions: - if __version["_id"] == version["version_id"]: - _version = __version - break - - version["data"] = _version["data"] - version["name"] = _version["name"] - version["is_from_latest"] = ( - last_version["_id"] == _version["_id"] - ) + version_doc = self._get_hero_version(subset_id) else: - version = self.dbcon.find_one({ - "name": value, - "type": "version", - "parent": parent - }) + project_name = self.dbcon.active_project() + version_doc = get_version_by_name( + project_name, value, subset_id + ) # update availability on active site when version changes - if self.sync_server.enabled and version: - query = self._repre_per_version_pipeline([version["_id"]], - self.active_site, - self.remote_site) + if self.sync_server.enabled and version_doc: + query = self._repre_per_version_pipeline( + [version_doc["_id"]], + self.active_site, + self.remote_site + ) docs = list(self.dbcon.aggregate(query)) if docs: repre = docs.pop() - version["data"].update(self._get_repre_dict(repre)) + version_doc["data"].update(self._get_repre_dict(repre)) - self.set_version(index, version) + self.set_version(index, version_doc) return super(SubsetsModel, self).setData(index, value, role) + def _get_hero_version(self, subset_id): + project_name = self.dbcon.active_project() + version_docs = get_versions( + project_name, subset_ids=[subset_id], hero=True + ) + standard_versions = [] + hero_version_doc = None + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version_doc = version_doc + continue + standard_versions.append(version_doc) + + src_version_id = hero_version_doc["version_id"] + src_version = None + is_from_latest = True + for version_doc in reversed(sorted( + standard_versions, key=lambda item: item["name"] + )): + if version_doc["_id"] == src_version_id: + src_version = version_doc + break + is_from_latest = False + + hero_version_doc["data"] = src_version["data"] + hero_version_doc["name"] = src_version["name"] + hero_version_doc["is_from_latest"] = is_from_latest + return hero_version_doc + def set_version(self, index, version): """Update the version data of the given index. @@ -391,26 +401,25 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): item["repre_info"] = repre_info def _fetch(self): - asset_docs = self.dbcon.find( - { - "type": "asset", - "_id": {"$in": self._asset_ids} - }, - self.asset_doc_projection + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, + asset_ids=self._asset_ids, + fields=self.asset_doc_projection.keys() ) + asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs } subset_docs_by_id = {} - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": {"$in": self._asset_ids} - }, - self.subset_doc_projection + subset_docs = get_subsets( + project_name, + asset_ids=self._asset_ids, + fields=self.subset_doc_projection.keys() ) + subset_families = set() for subset_doc in subset_docs: if self._doc_fetching_stop: @@ -423,37 +432,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): subset_docs_by_id[subset_doc["_id"]] = subset_doc subset_ids = list(subset_docs_by_id.keys()) - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "name": {"$last": "$name"}, - "type": {"$last": "$type"}, - "data": {"$last": "$data"}, - "locations": {"$last": "$locations"}, - "schema": {"$last": "$schema"} - }} - ] - last_versions_by_subset_id = dict() - for doc in self.dbcon.aggregate(_pipeline): - if self._doc_fetching_stop: - return - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc + last_versions_by_subset_id = get_last_versions( + project_name, + subset_ids, + fields=["_id", "parent", "name", "type", "data", "schema"] + ) - hero_versions = self.dbcon.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - }) + hero_versions = get_hero_versions(project_name, subset_ids=subset_ids) missing_versions = [] for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -462,10 +447,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): missing_versions_by_id = {} if missing_versions: - missing_version_docs = self.dbcon.find({ - "type": "version", - "_id": {"$in": missing_versions} - }) + missing_version_docs = get_versions( + project_name, version_ids=missing_versions + ) missing_versions_by_id = { missing_version_doc["_id"]: missing_version_doc for missing_version_doc in missing_version_docs @@ -488,23 +472,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version - self._doc_payload = { - "asset_docs_by_id": asset_docs_by_id, - "subset_docs_by_id": subset_docs_by_id, - "subset_families": subset_families, - "last_versions_by_subset_id": last_versions_by_subset_id - } - + repre_info = {} if self.sync_server.enabled: version_ids = set() for _subset_id, doc in last_versions_by_subset_id.items(): version_ids.add(doc["_id"]) - query = self._repre_per_version_pipeline(list(version_ids), - self.active_site, - self.remote_site) + query = self._repre_per_version_pipeline( + list(version_ids), self.active_site, self.remote_site + ) - repre_info = {} for doc in self.dbcon.aggregate(query): if self._doc_fetching_stop: return @@ -512,7 +489,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): doc["remote_provider"] = self.remote_provider repre_info[doc["_id"]] = doc - self._doc_payload["repre_info_by_version_id"] = repre_info + self._doc_payload = { + "asset_docs_by_id": asset_docs_by_id, + "subset_docs_by_id": subset_docs_by_id, + "subset_families": subset_families, + "last_versions_by_subset_id": last_versions_by_subset_id, + "repre_info_by_version_id": repre_info + } self.doc_fetched.emit() @@ -545,7 +528,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.fetch_subset_and_version() - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self._items_by_id = {} self.beginResetModel() @@ -1036,7 +1019,6 @@ class RepresentationSortProxyModel(GroupMemberFilterProxyModel): class RepresentationModel(TreeModel, BaseRepresentationModel): - doc_fetched = QtCore.Signal() refreshed = QtCore.Signal(bool) @@ -1062,33 +1044,43 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): "remote_site": "Remote" } - def __init__(self, dbcon, header, version_ids): + repre_projection = { + "_id": 1, + "name": 1, + "context.subset": 1, + "context.asset": 1, + "context.version": 1, + "context.representation": 1, + 'files.sites': 1 + } + + def __init__(self, dbcon, header): super(RepresentationModel, self).__init__() self.dbcon = dbcon self._data = [] self._header = header - self.version_ids = version_ids + self._version_ids = [] manager = ModulesManager() sync_server = active_site = remote_site = None active_provider = remote_provider = None - project = dbcon.Session["AVALON_PROJECT"] - if project: + project_name = dbcon.current_project() + if project_name: sync_server = manager.modules_by_name["sync_server"] - active_site = sync_server.get_active_site(project) - remote_site = sync_server.get_remote_site(project) + active_site = sync_server.get_active_site(project_name) + remote_site = sync_server.get_remote_site(project_name) # TODO refactor - active_provider = \ - sync_server.get_provider_for_site(project, - active_site) + active_provider = sync_server.get_provider_for_site( + project_name, active_site + ) if active_site == 'studio': active_provider = 'studio' - remote_provider = \ - sync_server.get_provider_for_site(project, - remote_site) + remote_provider = sync_server.get_provider_for_site( + project_name, remote_site + ) if remote_site == 'studio': remote_provider = 'studio' @@ -1099,7 +1091,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self.remote_site = remote_site self.remote_provider = remote_provider - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self._docs = {} self._icons = lib.get_repre_icons() @@ -1110,7 +1102,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self._items_by_id = {} def set_version_ids(self, version_ids): - self.version_ids = version_ids + self._version_ids = version_ids self.refresh() def data(self, index, role): @@ -1127,8 +1119,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if index.column() == self.Columns.index("name"): if item.get("isMerged"): return item["icon"] - else: - return self._icons["repre"] + return self._icons["repre"] active_index = self.Columns.index("active_site") remote_index = self.Columns.index("remote_site") @@ -1144,12 +1135,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): # site added, sync in progress progress_str = "not avail." if progress >= 0: - # progress == 0 for isMerged is unavailable if progress == 0 and item.get("isMerged"): progress_str = "not avail." else: - progress_str = "{}% {}".format(int(progress * 100), - label) + progress_str = "{}% {}".format( + int(progress * 100), label + ) return progress_str @@ -1179,7 +1170,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return super(RepresentationModel, self).data(index, role) - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self.beginResetModel() subsets = set() @@ -1189,10 +1180,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): group = None self._items_by_id = {} for doc in self._docs: - if len(self.version_ids) > 1: + if len(self._version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: - group_item = Item() item_id = str(uuid4()) group_item.update({ @@ -1213,9 +1203,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_groups_items[doc["name"]] = 0 group = group_item - progress = lib.get_progress_for_repre(doc, - self.active_site, - self.remote_site) + progress = lib.get_progress_for_repre( + doc, self.active_site, self.remote_site + ) active_site_icon = self._icons.get(self.active_provider) remote_site_icon = self._icons.get(self.remote_provider) @@ -1248,9 +1238,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'remote_site_progress': progress[self.remote_site] } if group: - group = self._sum_group_progress(doc["name"], group, - current_progress, - repre_groups_items) + group = self._sum_group_progress( + doc["name"], group, current_progress, repre_groups_items + ) self.add_child(item, group) @@ -1269,47 +1259,39 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return self._items_by_id.get(item_id) def refresh(self): - docs = [] - session_project = self.dbcon.Session['AVALON_PROJECT'] - if not session_project: + project_name = self.dbcon.current_project() + if not project_name: return - if self.version_ids: + repre_docs = [] + if self._version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python - docs = list(self.dbcon.find( - {"type": "representation", "parent": {"$in": self.version_ids}, - "files.sites.name": {"$exists": 1}}, self.projection())) - self._docs = docs + repre_docs = list(get_representations( + project_name, + version_ids=self._version_ids, + fields=self.repre_projection.keys() + )) + + self._docs = repre_docs self.doc_fetched.emit() - @classmethod - def projection(cls): - return { - "_id": 1, - "name": 1, - "context.subset": 1, - "context.asset": 1, - "context.version": 1, - "context.representation": 1, - 'files.sites': 1 - } + def _sum_group_progress( + self, repre_name, group, current_item_progress, repre_groups_items + ): + """Update final group progress - def _sum_group_progress(self, repre_name, group, current_item_progress, - repre_groups_items): - """ - Update final group progress - Called after every item in group is added + Called after every item in group is added - Args: - repre_name(string) - group(dict): info about group of selected items - current_item_progress(dict): {'active_site_progress': XX, - 'remote_site_progress': YY} - repre_groups_items(dict) - Returns: - (dict): updated group info + Args: + repre_name(string) + group(dict): info about group of selected items + current_item_progress(dict): {'active_site_progress': XX, + 'remote_site_progress': YY} + repre_groups_items(dict) + Returns: + (dict): updated group info """ repre_groups_items[repre_name] += 1 diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..0482bad642 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -7,6 +7,16 @@ import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.client import ( + get_subset_families, + get_subset_by_id, + get_subsets, + get_version_by_id, + get_versions, + get_representations, + get_thumbnail_id_from_source, + get_thumbnail, +) from openpype.api import Anatomy from openpype.pipeline import HeroVersionType from openpype.pipeline.thumbnail import get_thumbnail_binary @@ -237,8 +247,7 @@ class SubsetWidget(QtWidgets.QWidget): self.model = model self.view = view - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + self.on_project_change(dbcon.current_project()) view.customContextMenuRequested.connect(self.on_context_menu) @@ -302,33 +311,23 @@ class SubsetWidget(QtWidgets.QWidget): item["version_document"] ) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + project_name = self.dbcon.active_project() + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } version_ids = list(version_docs_by_id.keys()) - repre_docs = self.dbcon.find( - # Query all representations for selected versions at once - { - "type": "representation", - "parent": {"$in": version_ids} - }, - # Query only name and parent from representation - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=version_ids, + fields=["name", "parent"] ) + repre_docs_by_version_id = { version_id: [] for version_id in version_ids @@ -566,28 +565,42 @@ class SubsetWidget(QtWidgets.QWidget): # same representation available # Trigger - repre_ids = [] + project_name = self.dbcon.active_project() + subset_names_by_version_id = collections.defaultdict(set) for item in items: - representation = self.dbcon.find_one( - { - "type": "representation", - "name": representation_name, - "parent": item["version_document"]["_id"] - }, - {"_id": 1} - ) - if not representation: - self.echo("Subset '{}' has no representation '{}'".format( - item["subset"], representation_name - )) - continue - repre_ids.append(representation["_id"]) + version_id = item["version_document"]["_id"] + subset_names_by_version_id[version_id].add(item["subset"]) + + version_ids = set(subset_names_by_version_id.keys()) + repre_docs = get_representations( + project_name, + representation_names=[representation_name], + version_ids=version_ids, + fields=["_id", "parent"] + ) + + repre_ids = [] + for repre_doc in repre_docs: + repre_ids.append(repre_doc["_id"]) + + version_id = repre_doc["parent"] + if version_id not in version_ids: + version_ids.remove(version_id) + + for version_id in version_ids: + joined_subset_names = ", ".join([ + '"{}"'.format(subset) + for subset in subset_names_by_version_id[version_id] + ]) + self.echo("Subsets {} don't have representation '{}'".format( + joined_subset_names, representation_name + )) # get contexts only for selected menu option repre_contexts = get_repres_contexts(repre_ids, self.dbcon) - options = lib.get_options(action, loader, self, - list(repre_contexts.values())) - + options = lib.get_options( + action, loader, self, list(repre_contexts.values()) + ) error_info = _load_representations_by_loader( loader, repre_contexts, options=options ) @@ -661,27 +674,21 @@ class VersionTextEdit(QtWidgets.QTextEdit): print("Querying..") + project_name = self.dbcon.active_project() if not version_doc: - version_doc = self.dbcon.find_one({ - "_id": version_id, - "type": {"$in": ["version", "hero_version"]} - }) + version_doc = get_version_by_id(project_name, version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = self.dbcon.find_one({ - "_id": version_doc["version_id"], - "type": "version" - }) + _version_doc = get_version_by_id( + project_name, version_doc["version_id"] + ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( _version_doc["name"] ) - subset = self.dbcon.find_one({ - "_id": version_doc["parent"], - "type": "subset" - }) + subset = get_subset_by_id(project_name, version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp @@ -752,7 +759,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): if not source: return - project_name = self.dbcon.Session["AVALON_PROJECT"] + project_name = self.dbcon.current_project() if self._anatomy is None or self._anatomy.project_name != project_name: self._anatomy = Anatomy(project_name) @@ -833,24 +840,19 @@ class ThumbnailWidget(QtWidgets.QLabel): QtCore.Qt.SmoothTransformation ) - def set_thumbnail(self, doc_id=None): - if not doc_id: + def set_thumbnail(self, src_type, doc_ids): + if not doc_ids: self.set_pixmap() return - if isinstance(doc_id, (list, tuple)): - if len(doc_id) < 1: - self.set_pixmap() - return - doc_id = doc_id[0] + src_id = doc_ids[0] - doc = self.dbcon.find_one( - {"_id": doc_id}, - {"data.thumbnail_id"} + project_name = self.dbcon.active_project() + thumbnail_id = get_thumbnail_id_from_source( + project_name, + src_type, + src_id, ) - thumbnail_id = None - if doc: - thumbnail_id = doc.get("data", {}).get("thumbnail_id") if thumbnail_id == self.current_thumb_id: if self.current_thumbnail is None: self.set_pixmap() @@ -861,9 +863,7 @@ class ThumbnailWidget(QtWidgets.QLabel): self.set_pixmap() return - thumbnail_ent = self.dbcon.find_one( - {"type": "thumbnail", "_id": thumbnail_id} - ) + thumbnail_ent = get_thumbnail(project_name, thumbnail_id) if not thumbnail_ent: return @@ -917,21 +917,9 @@ class FamilyModel(QtGui.QStandardItemModel): def refresh(self): families = set() - if self.dbcon.Session.get("AVALON_PROJECT"): - result = list(self.dbcon.aggregate([ - {"$match": { - "type": "subset" - }}, - {"$project": { - "family": {"$arrayElemAt": ["$data.families", 0]} - }}, - {"$group": { - "_id": "family_group", - "families": {"$addToSet": "$family"} - }} - ])) - if result: - families = set(result[0]["families"]) + project_name = self.dbcon.current_project() + if project_name: + families = get_subset_families(project_name) root_item = self.invisibleRootItem() @@ -1176,7 +1164,7 @@ class RepresentationWidget(QtWidgets.QWidget): headers = [item[0] for item in self.default_widths] - model = RepresentationModel(self.dbcon, headers, []) + model = RepresentationModel(self.dbcon, headers) proxy_model = RepresentationSortProxyModel(self) proxy_model.setSourceModel(model) @@ -1213,8 +1201,8 @@ class RepresentationWidget(QtWidgets.QWidget): self.proxy_model = proxy_model self.sync_server_enabled = False - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + + self.on_project_change(dbcon.current_project()) self.model.refresh() @@ -1243,23 +1231,18 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - repre_docs = list(self.dbcon.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - { - "name": 1, - "parent": 1 - } + project_name = self.dbcon.active_project() + repre_docs = list(get_representations( + project_name, + representation_ids=repre_ids, + fields=["name", "parent"] )) + version_ids = [ repre_doc["parent"] for repre_doc in repre_docs ] - version_docs = self.dbcon.find({ - "_id": {"$in": version_ids} - }) + version_docs = get_versions(project_name, version_ids=version_ids) version_docs_by_id = {} version_docs_by_subset_id = collections.defaultdict(list) @@ -1269,15 +1252,10 @@ class RepresentationWidget(QtWidgets.QWidget): version_docs_by_id[version_id] = version_doc version_docs_by_subset_id[subset_id].append(version_doc) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc @@ -1446,13 +1424,12 @@ class RepresentationWidget(QtWidgets.QWidget): self._process_action(items, menu, point) def _process_action(self, items, menu, point): - """ - Show the context action menu and process selected + """Show the context action menu and process selected - Args: - items(dict): menu items - menu(OptionalMenu) - point(PointIndex) + Args: + items(dict): menu items + menu(OptionalMenu) + point(PointIndex) """ global_point = self.tree_view.mapToGlobal(point) action = menu.exec_(global_point) @@ -1468,21 +1445,23 @@ class RepresentationWidget(QtWidgets.QWidget): data_by_repre_id = {} selected_side = action_representation.get("selected_side") + is_sync_loader = tools_lib.is_sync_loader(loader) for item in items: - if tools_lib.is_sync_loader(loader): - site_name = "{}_site_name".format(selected_side) - data = { - "_id": item.get("_id"), - "site_name": item.get(site_name), - "project_name": self.dbcon.Session["AVALON_PROJECT"] - } + item_id = item.get("_id") + repre_ids.append(item_id) + if not is_sync_loader: + continue - if not data["site_name"]: - continue + site_name = "{}_site_name".format(selected_side) + data_site_name = item.get(site_name) + if not data_site_name: + continue - data_by_repre_id[data["_id"]] = data - - repre_ids.append(item.get("_id")) + data_by_repre_id[item_id] = { + "_id": item_id, + "site_name": data_site_name, + "project_name": self.dbcon.active_project() + } repre_contexts = get_repres_contexts(repre_ids, self.dbcon) options = lib.get_options(action, loader, self, diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 1b6cad77a8..5665acea42 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,6 +4,7 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import get_last_version_by_subset_id from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -211,6 +212,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): selection = self.assign_selected.isChecked() asset_nodes = self.asset_outliner.get_nodes(selection=selection) + project_name = legacy_io.active_project() start = time.time() for i, (asset, item) in enumerate(asset_nodes.items()): @@ -222,23 +224,20 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): assign_look = next((subset for subset in item["looks"] if subset["name"] in looks), None) if not assign_look: - self.echo("{} No matching selected " - "look for {}".format(prefix, asset)) + self.echo( + "{} No matching selected look for {}".format(prefix, asset) + ) continue # Get the latest version of this asset's look subset - version = legacy_io.find_one( - { - "type": "version", - "parent": assign_look["_id"] - }, - sort=[("name", -1)] + version = get_last_version_by_subset_id( + project_name, assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] - self.echo("{} Assigning {} to {}\t".format(prefix, - subset_name, - asset)) + self.echo("{} Assigning {} to {}\t".format( + prefix, subset_name, asset + )) nodes = item["nodes"] if cmds.pluginInfo('vrayformaya', query=True, loaded=True): diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index d41d8ca5a2..2e7a51efde 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -2,9 +2,9 @@ from collections import defaultdict import logging import os -from bson.objectid import ObjectId import maya.cmds as cmds +from openpype.client import get_asset_by_id from openpype.pipeline import ( legacy_io, remove_container, @@ -159,11 +159,9 @@ def create_items_from_nodes(nodes): log.warning("No id hashes") return asset_view_items + project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = legacy_io.find_one( - {"_id": ObjectId(_id)}, - projection={"name": True} - ) + asset = get_asset_by_id(project_name, _id, fields=["name"]) # Skip if asset id is not found if not asset: @@ -180,10 +178,12 @@ def create_items_from_nodes(nodes): namespace = get_namespace_from_node(node) namespaces.add(namespace) - asset_view_items.append({"label": asset["name"], - "asset": asset, - "looks": looks, - "namespaces": namespaces}) + asset_view_items.append({ + "label": asset["name"], + "asset": asset, + "looks": looks, + "namespaces": namespaces + }) return asset_view_items diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index 3523b24bf3..889396e555 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -6,11 +6,14 @@ import logging import json import six -from bson.objectid import ObjectId import alembic.Abc from maya import cmds +from openpype.client import ( + get_representation_by_name, + get_last_version_by_subset_name, +) from openpype.pipeline import ( legacy_io, load_container, @@ -155,13 +158,12 @@ def get_look_relationships(version_id): Returns: dict: Dictionary of relations. - """ - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "json" - }) + + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, representation_name="json", version_id=version_id + ) # Load relationships shader_relation = get_representation_path(json_representation) @@ -184,12 +186,12 @@ def load_look(version_id): list of shader nodes. """ + + project_name = legacy_io.active_project() # Get representations of shader file and relationships - look_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "ma" - }) + look_representation = get_representation_by_name( + project_name, representation_name="ma", version_id=version_id + ) # See if representation is already loaded, if so reuse it. host = registered_host() @@ -220,42 +222,6 @@ def load_look(version_id): return shader_nodes -def get_latest_version(asset_id, subset): - # type: (str, str) -> dict - """Get latest version of subset. - - Args: - asset_id (str): Asset ID - subset (str): Subset name. - - Returns: - Latest version - - Throws: - RuntimeError: When subset or version doesn't exist. - - """ - subset = legacy_io.find_one({ - "name": subset, - "parent": ObjectId(asset_id), - "type": "subset" - }) - if not subset: - raise RuntimeError("Subset does not exist: %s" % subset) - - version = legacy_io.find_one( - { - "type": "version", - "parent": subset["_id"] - }, - sort=[("name", -1)] - ) - if not version: - raise RuntimeError("Version does not exist.") - - return version - - def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): # type: (str, str) -> None """Assign look to vray proxy. @@ -281,13 +247,20 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): asset_id = node_id.split(":", 1)[0] node_ids_by_asset_id[asset_id].add(node_id) + project_name = legacy_io.active_project() for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - try: - version = get_latest_version(asset_id, subset=subset) - except RuntimeError as exc: - print(exc) + version = get_last_version_by_subset_name( + project_name, + subset_name=subset, + asset_id=asset_id, + fields=["_id"] + ) + if not version: + print("Didn't find last version for subset name {}".format( + subset + )) continue relationships = get_look_relationships(version["_id"]) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 223cfa629d..c5bde5aaec 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -7,6 +7,11 @@ from pymongo import UpdateOne, DeleteOne from Qt import QtCore, QtGui +from openpype.client import ( + get_project, + get_assets, + get_asset_ids_with_subsets, +) from openpype.lib import ( CURRENT_DOC_SCHEMAS, PypeLogger, @@ -255,10 +260,11 @@ class HierarchyModel(QtCore.QAbstractItemModel): return # Find project'd document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - ProjectItem.query_projection + project_doc = get_project( + project_name, + fields=list(ProjectItem.query_projection.keys()) ) + # Skip if project document does not exist # - this shouldn't happen using only UI elements if not project_doc: @@ -269,9 +275,8 @@ class HierarchyModel(QtCore.QAbstractItemModel): self.add_item(project_item) # Query all assets of the project - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - AssetItem.query_projection + asset_docs = get_assets( + project_name, fields=AssetItem.query_projection.keys() ) asset_docs_by_id = { asset_doc["_id"]: asset_doc @@ -282,31 +287,16 @@ class HierarchyModel(QtCore.QAbstractItemModel): # if asset item can be modified (name and hierarchy change) # - the same must be applied to all it's parents asset_ids = list(asset_docs_by_id.keys()) - result = [] + asset_ids_with_subsets = [] if asset_ids: - result = self.dbcon.database[project_name].aggregate([ - { - "$match": { - "type": "subset", - "parent": {"$in": asset_ids} - } - }, - { - "$group": { - "_id": "$parent", - "count": {"$sum": 1} - } - } - ]) + asset_ids_with_subsets = get_asset_ids_with_subsets( + project_name, asset_ids=asset_ids + ) asset_modifiable = { - asset_id: True + asset_id: asset_id not in asset_ids_with_subsets for asset_id in asset_docs_by_id.keys() } - for item in result: - asset_id = item["_id"] - count = item["count"] - asset_modifiable[asset_id] = count < 1 # Store assets by their visual parent to be able create their hierarchy asset_docs_by_parent_id = collections.defaultdict(list) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index a16def276d..cca892ef72 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -3,6 +3,7 @@ from queue import Queue from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_project from .delegates import ( NumberDelegate, NameDelegate, @@ -47,12 +48,8 @@ class ProjectDocCache: def set_project(self, project_name): self.project_doc = None - if not project_name: - return - - self.project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"} - ) + if project_name: + self.project_doc = get_project(project_name) class ToolsCache: diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index dc75b30bd7..371d1ba2ef 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,5 +1,6 @@ import re +from openpype.client import get_projects from .constants import ( NAME_ALLOWED_SYMBOLS, NAME_REGEX @@ -272,15 +273,9 @@ class CreateProjectDialog(QtWidgets.QDialog): def _get_existing_projects(self): project_names = set() project_codes = set() - for project_name in self.dbcon.database.collection_names(): - # Each collection will have exactly one project document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - {"name": 1, "data.code": 1} - ) - if not project_doc: - continue - + for project_doc in get_projects( + inactive=True, fields=["name", "data.code"] + ): project_name = project_doc.get("name") if not project_name: continue diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 2973d6a5bb..915fb7f32e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -13,6 +13,7 @@ except Exception: import pyblish.api +from openpype.client import get_assets from openpype.pipeline import ( PublishValidationError, registered_host, @@ -116,10 +117,10 @@ class AssetDocsCache: def _query(self): if self._asset_docs is None: - asset_docs = list(self.dbcon.find( - {"type": "asset"}, - self.projection - )) + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, fields=self.projection.keys() + ) task_names_by_asset_name = {} for asset_doc in asset_docs: asset_name = asset_doc["name"] diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 9e357f3a56..53bbef8b75 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -9,6 +9,8 @@ try: except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui + +from openpype.client import get_asset_by_name, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -647,21 +649,19 @@ class CreateDialog(QtWidgets.QDialog): if asset_name is None: return - asset_doc = self.dbcon.find_one({ - "type": "asset", - "name": asset_name - }) + project_name = self.dbcon.active_project() + asset_doc = get_asset_by_name(project_name, asset_name) self._asset_doc = asset_doc if asset_doc: - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"name": 1} + asset_id = asset_doc["_id"] + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - self._subset_names = set(subset_docs.distinct("name")) + self._subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } if not asset_doc: self.subset_name_input.setText("< Asset is not set >") diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8d72020c98..0bb9c4a658 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -5,8 +5,14 @@ from collections import defaultdict from Qt import QtCore, QtGui import qtawesome -from bson.objectid import ObjectId +from openpype.client import ( + get_asset_by_id, + get_subset_by_id, + get_version_by_id, + get_last_version_by_subset_id, + get_representation_by_id, +) from openpype.pipeline import ( legacy_io, schema, @@ -55,7 +61,7 @@ class InventoryModel(TreeModel): if not self.sync_enabled: return - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = legacy_io.current_project() active_site = sync_server.get_active_site(project_name) remote_site = sync_server.get_remote_site(project_name) @@ -192,12 +198,12 @@ class InventoryModel(TreeModel): self.clear() if self._hierarchy_view and selected: - if not hasattr(host.pipeline, "update_hierarchy"): # If host doesn't support hierarchical containers, then # cherry-pick only. self.add_items((item for item in items if item["objectName"] in selected)) + return # Update hierarchy info for all containers items_by_name = {item["objectName"]: item @@ -291,6 +297,9 @@ class InventoryModel(TreeModel): node.Item: root node which has children added based on the data """ + # NOTE: @iLLiCiTiT this need refactor + project_name = legacy_io.active_project() + self.beginResetModel() # Group by representation @@ -304,32 +313,36 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = legacy_io.find_one({"_id": ObjectId(repre_id)}) + representation = get_representation_by_id( + project_name, repre_id + ) if not representation: not_found["representation"].append(group_items) not_found_ids.append(repre_id) continue - version = legacy_io.find_one({"_id": representation["parent"]}) + version = get_version_by_id( + project_name, representation["parent"] + ) if not version: not_found["version"].append(group_items) not_found_ids.append(repre_id) continue elif version["type"] == "hero_version": - _version = legacy_io.find_one({ - "_id": version["version_id"] - }) + _version = get_version_by_id( + project_name, version["version_id"] + ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = legacy_io.find_one({"_id": version["parent"]}) + subset = get_subset_by_id(project_name, version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) continue - asset = legacy_io.find_one({"_id": subset["parent"]}) + asset = get_asset_by_id(project_name, subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) @@ -390,10 +403,9 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = legacy_io.find_one({ - "type": "version", - "parent": version["parent"] - }, sort=[("name", -1)]) + highest_version = get_last_version_by_subset_id( + project_name, version["parent"] + ) # create the group header group_node = Item() diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b2d770330f..1d1d5cbb91 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -4,6 +4,16 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_asset_by_name, + get_assets, + get_subset_by_name, + get_subsets, + get_versions, + get_hero_versions, + get_last_versions, + get_representations, +) from openpype.pipeline import legacy_io from openpype.pipeline.load import ( discover_loader_plugins, @@ -144,6 +154,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): self._prepare_content_data() self.refresh(True) + def active_project(self): + return legacy_io.active_project() + def _prepare_content_data(self): repre_ids = set() content_loaders = set() @@ -151,10 +164,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_ids.add(ObjectId(item["representation"])) content_loaders.add(item["loader"]) - repres = list(legacy_io.find({ - "type": {"$in": ["representation", "archived_representation"]}, - "_id": {"$in": list(repre_ids)} - })) + project_name = self.active_project() + repres = list(get_representations( + project_name, + representation_ids=repre_ids, + archived=True + )) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation @@ -179,10 +194,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_repres[repre_id] = repres_by_id[repre_id] version_ids.append(repre["parent"]) - versions = legacy_io.find({ - "type": {"$in": ["version", "hero_version"]}, - "_id": {"$in": list(set(version_ids))} - }) + versions = get_versions( + project_name, + version_ids=set(version_ids), + hero=True + ) content_versions = {} hero_version_ids = set() for version in versions: @@ -198,10 +214,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): else: subset_ids.append(content_versions[version_id]["parent"]) - subsets = legacy_io.find({ - "type": {"$in": ["subset", "archived_subset"]}, - "_id": {"$in": subset_ids} - }) + subsets = get_subsets( + project_name, subset_ids=subset_ids, archived=True + ) subsets_by_id = {sub["_id"]: sub for sub in subsets} asset_ids = [] @@ -220,10 +235,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_ids.append(subset["parent"]) content_subsets[subset_id] = subset - assets = legacy_io.find({ - "type": {"$in": ["asset", "archived_asset"]}, - "_id": {"$in": list(asset_ids)} - }) + assets = get_assets(project_name, asset_ids=asset_ids, archived=True) assets_by_id = {asset["_id"]: asset for asset in assets} missing_assets = [] @@ -472,9 +484,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": True} + asset_doc = get_asset_by_name( + self.active_project(), + selected_asset, + fields=["_id"] ) if not asset_doc: return [] @@ -523,38 +536,35 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxx( self, asset_doc, selected_subset, selected_repre ): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset_by_name( + project_name, + selected_subset, + asset_doc["_id"], + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) if not version_doc: return [] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"], - "name": selected_repre - }, - {"_id": True} + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset_by_name( + project_name, + selected_subset, + asset_doc["_id"], + fields=["_id"] ) if not subset_doc: return [] @@ -563,41 +573,51 @@ class SwitchAssetDialog(QtWidgets.QDialog): for repre_doc in self.content_repres.values(): repre_names.add(repre_doc["name"]) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": subset_doc["_id"], - "name": {"$in": list(repre_names)} - }, - {"_id": True} + # TODO where to take version ids? + version_ids = [] + repre_docs = get_representations( + project_name, + representation_names=repre_names, + version_ids=version_ids, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xox(self, asset_doc, selected_repre): - susbet_names = set() + subset_names = set() for subset_doc in self.content_subsets.values(): - susbet_names.add(subset_doc["name"]) + subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "name": {"$in": list(susbet_names)}, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id", "name"] ) - subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": subset_ids}, - "name": selected_repre - }, - {"_id": True} + subset_name_by_id = { + subset_doc["_id"]: subset_doc["name"] + for subset_doc in subset_docs + } + subset_ids = list(subset_name_by_id.keys()) + last_versions_by_subset_id = self.find_last_versions(subset_ids) + last_version_id_by_subset_name = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + subset_name = subset_name_by_id[subset_id] + last_version_id_by_subset_name[subset_name] = ( + last_version["_id"] + ) + + repre_docs = get_representations( + project_name, + version_ids=last_version_id_by_subset_name.values(), + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xoo(self, asset_doc): + project_name = self.active_project() repres_by_subset_name = collections.defaultdict(set) for repre_doc in self.content_repres.values(): repre_name = repre_doc["name"] @@ -606,13 +626,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_name = subset_doc["name"] repres_by_subset_name[subset_name].add(repre_name) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(repres_by_subset_name.keys())} - }, - {"_id": True, "name": True} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=repres_by_subset_name.keys(), + fields=["_id", "name"] )) subset_name_by_id = { subset_doc["_id"]: subset_doc["name"] @@ -627,60 +645,59 @@ class SwitchAssetDialog(QtWidgets.QDialog): last_version["_id"] ) - repre_or_query = [] + repre_names_by_version_id = {} for subset_name, repre_names in repres_by_subset_name.items(): version_id = last_version_id_by_subset_name.get(subset_name) # This should not happen but why to crash? - if version_id is None: - continue - repre_or_query.append({ - "parent": version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - {"$or": repre_or_query}, - {"_id": True} + if version_id is not None: + repre_names_by_version_id[version_id] = list(repre_names) + + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxx( self, selected_subset, selected_repre ): - subset_docs = list(legacy_io.find({ - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - })) + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] last_versions_by_subset_id = self.find_last_versions(subset_ids) last_version_ids = [ last_version["_id"] for last_version in last_versions_by_subset_id.values() ] - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": last_version_ids}, - "name": selected_repre - }) - + repre_docs = get_representations( + project_name, + version_ids=last_version_ids, + representation_names=[selected_repre], + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxo(self, selected_subset): - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": True, "parent": True} - )) - if not subset_docs: - return list() - + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] + ) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } + if not subset_docs: + return list() + last_versions_by_subset_id = self.find_last_versions( subset_docs_by_id.keys() ) @@ -702,56 +719,44 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_id = asset_doc["_id"] repre_names_by_asset_id[asset_id].add(repre_name) - repre_or_query = [] + repre_names_by_version_id = {} for last_version_id, subset_id in subset_id_by_version_id.items(): subset_doc = subset_docs_by_id[subset_id] asset_id = subset_doc["parent"] repre_names = repre_names_by_asset_id.get(asset_id) if not repre_names: continue - repre_or_query.append({ - "parent": last_version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - { - "type": "representation", - "$or": repre_or_query - }, - {"_id": True} - ) + repre_names_by_version_id[last_version_id] = repre_names + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oox(self, selected_repre): - repre_docs = legacy_io.find( - { - "name": selected_repre, - "parent": {"$in": list(self.content_versions.keys())} - }, - {"_id": True} + project_name = self.active_project() + repre_docs = get_representations( + project_name, + representation_names=[selected_repre], + version_ids=self.content_versions.keys(), + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_asset_box_values(self): - asset_docs = legacy_io.find( - {"type": "asset"}, - {"_id": 1, "name": 1} - ) + project_name = self.active_project() + asset_docs = get_assets(project_name, fields=["_id", "name"]) asset_names_by_id = { asset_doc["_id"]: asset_doc["name"] for asset_doc in asset_docs } - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(asset_names_by_id.keys())} - }, - { - "parent": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_names_by_id.keys(), + fields=["parent"] ) - filtered_assets = [] for subset in subsets: asset_name = asset_names_by_id[subset["parent"]] @@ -760,25 +765,20 @@ class SwitchAssetDialog(QtWidgets.QDialog): return sorted(filtered_assets) def _get_subset_box_values(self): + project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] + ) asset_ids = [asset_doc["_id"]] else: asset_ids = list(self.content_assets.keys()) - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": asset_ids} - }, - { - "parent": 1, - "name": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_ids, + fields=["parent", "name"] ) subset_names_by_parent_id = collections.defaultdict(set) @@ -800,6 +800,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _representations_box_values(self): # NOTE hero versions are not used because it is expected that # hero version has same representations as latests + project_name = self.active_project() selected_asset = self._assets_box.currentText() selected_subset = self._subsets_box.currentText() @@ -807,16 +808,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [ ] [?] if not selected_asset and not selected_subset: # Find all representations of selection's subsets - possible_repres = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(self.content_versions.keys())} - }, - { - "parent": 1, - "name": 1 - } - )) + possible_repres = get_representations( + project_name, + version_ids=self.content_versions.keys(), + fields=["parent", "name"] + ) possible_repres_by_parent = collections.defaultdict(set) for repre in possible_repres: @@ -836,29 +832,23 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": 1} + subset_doc = get_subset_by_name( + project_name, + selected_subset, + asset_doc["_id"], + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"] - }, - { - "name": 1 - } + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + fields=["name"] ) return [ repre_doc["name"] @@ -868,9 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -879,13 +868,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_names = set() for subset_doc in self.content_subsets.values(): subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(subset_names)} - }, - {"_id": 1} + + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id"] ) subset_ids = [ subset_doc["_id"] @@ -903,15 +891,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = list(get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] )) if not repre_docs: return list() @@ -933,13 +916,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): return list(available_repres) # [ ] [x] [?] - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "parent": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] )) if not subset_docs: return list() @@ -960,16 +941,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } - )) + repre_docs = list( + get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] + ) + ) if not repre_docs: return list() @@ -1016,14 +994,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [ ] [?] - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + project_name = self.active_project() + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) - subset_docs = legacy_io.find( - {"type": "subset", "parent": asset_doc["_id"]}, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_doc["_id"]], fields=["name"] ) + subset_names = set( subset_doc["name"] for subset_doc in subset_docs @@ -1035,27 +1013,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): break def find_last_versions(self, subset_ids): - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": list(subset_ids)} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "type": {"$last": "$type"} - }} - ] - last_versions_by_subset_id = dict() - for doc in legacy_io.aggregate(_pipeline): - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc - return last_versions_by_subset_id + project_name = self.active_project() + return get_last_versions( + project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "type"] + ) def _is_repre_ok(self, validation_state): selected_asset = self._assets_box.get_valid_value() @@ -1078,33 +1041,28 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [x] [ ] + project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": 1} + subset_doc = get_subset_by_name( + project_name, + selected_subset, + asset_doc["_id"], + fields=["_id"] ) - last_versions_by_subset_id = self.find_last_versions( - [subset_doc["_id"]] - ) - last_version = last_versions_by_subset_id.get(subset_doc["_id"]) + subset_id = subset_doc["_id"] + last_versions_by_subset_id = self.find_last_versions([subset_id]) + last_version = last_versions_by_subset_id.get(subset_id) if not last_version: validation_state.repre_ok = False return - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": last_version["_id"] - }, - {"name": 1} + repre_docs = get_representations( + project_name, + version_ids=[last_version["_id"]], + fields=["name"] ) repre_names = set( @@ -1119,16 +1077,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"_id": 1, "name": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "name"] )) subset_name_by_id = {} @@ -1145,15 +1100,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_subset_name = {} for repre_doc in repre_docs: @@ -1176,15 +1126,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [x] [ ] # Subset documents - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "name": 1, "parent": 1} + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "name", "parent"] ) - subset_docs_by_id = {} for subset_doc in subset_docs: subset_docs_by_id[subset_doc["_id"]] = subset_doc @@ -1197,15 +1144,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_asset_id = {} for repre_doc in repre_docs: @@ -1245,11 +1187,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): selected_subset = self._subsets_box.get_valid_value() selected_representation = self._representations_box.get_valid_value() + project_name = self.active_project() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset_by_name(project_name, selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets @@ -1259,16 +1199,15 @@ class SwitchAssetDialog(QtWidgets.QDialog): for asset_doc in asset_docs_by_id.values() } - asset_ids = list(asset_docs_by_id.keys()) - - subset_query = { - "type": "subset", - "parent": {"$in": asset_ids} - } + subset_names = None if selected_subset: - subset_query["name"] = selected_subset + subset_names = [selected_subset] - subset_docs = list(legacy_io.find(subset_query)) + subset_docs = list(get_subsets( + project_name, + subset_names=subset_names, + asset_ids=asset_docs_by_id.keys() + )) subset_ids = [] subset_docs_by_parent_and_name = collections.defaultdict(dict) for subset in subset_docs: @@ -1278,15 +1217,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_docs_by_parent_and_name[parent_id][name] = subset # versions - version_docs = list(legacy_io.find({ - "type": "version", - "parent": {"$in": subset_ids} - }, sort=[("name", -1)])) + _version_docs = get_versions(project_name, subset_ids=subset_ids) + version_docs = list(reversed( + sorted(_version_docs, key=lambda item: item["name"]) + )) - hero_version_docs = list(legacy_io.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - })) + hero_version_docs = list(get_hero_versions( + project_name, subset_ids=subset_ids + )) version_ids = list() @@ -1303,10 +1241,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): parent_id = hero_version_doc["parent"] hero_version_docs_by_parent_id[parent_id] = hero_version_doc - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": version_ids} - }) + repre_docs = get_representations(project_name, version_ids=version_ids) repre_docs_by_parent_id_by_name = collections.defaultdict(dict) for repre_doc in repre_docs: parent_id = repre_doc["parent"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 448e3f4e6f..63d181b2d6 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -6,6 +6,13 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_version_by_id, + get_versions, + get_hero_versions, + get_representation_by_id, + get_representations, +) from openpype import style from openpype.pipeline import ( legacy_io, @@ -83,12 +90,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, representation_ids=repre_ids, fields=["parent"] ) version_ids = [] @@ -97,10 +101,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if version_id not in version_ids: version_ids.append(version_id) - loaded_versions = legacy_io.find({ - "_id": {"$in": version_ids}, - "type": {"$in": ["version", "hero_version"]} - }) + loaded_versions = get_versions( + project_name, version_ids=version_ids, hero=True + ) loaded_hero_versions = [] versions_by_parent_id = collections.defaultdict(list) @@ -114,10 +117,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if parent_id not in version_parents: version_parents.append(parent_id) - all_versions = legacy_io.find({ - "type": {"$in": ["hero_version", "version"]}, - "parent": {"$in": version_parents} - }) + all_versions = get_versions( + project_name, subset_ids=version_parents, hero=True + ) hero_versions = [] versions = [] for version in all_versions: @@ -150,12 +152,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["parent"] ) version_ids = [] @@ -165,13 +165,13 @@ class SceneInventoryView(QtWidgets.QTreeView): version_id_by_repre_id[repre_doc["_id"]] = version_id if version_id not in version_ids: version_ids.append(version_id) - hero_versions = legacy_io.find( - { - "_id": {"$in": version_ids}, - "type": "hero_version" - }, - {"version_id": 1} + + hero_versions = get_hero_versions( + project_name, + version_ids=version_ids, + fields=["version_id"] ) + version_ids = set() for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -183,12 +183,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if current_version_id == hero_version_id: version_id_by_repre_id[_repre_id] = version_id - version_docs = legacy_io.find( - { - "_id": {"$in": list(version_ids)}, - "type": "version" - }, - {"name": 1} + version_docs = get_versions( + project_name, + version_ids=version_ids, + fields=["name"] ) version_name_by_id = {} for version_doc in version_docs: @@ -370,10 +368,9 @@ class SceneInventoryView(QtWidgets.QTreeView): active_site = self.sync_server.get_active_site(project_name) remote_site = self.sync_server.get_remote_site(project_name) - repre_docs = legacy_io.find({ - "type": "representation", - "_id": {"$in": repre_ids} - }) + repre_docs = get_representations( + project_name, representation_ids=repre_ids + ) repre_docs_by_id = { repre_doc["_id"]: repre_doc for repre_doc in repre_docs @@ -658,25 +655,35 @@ class SceneInventoryView(QtWidgets.QTreeView): active = items[-1] + project_name = legacy_io.active_project() # Get available versions for active representation - representation_id = ObjectId(active["representation"]) - representation = legacy_io.find_one({"_id": representation_id}) - version = legacy_io.find_one({ - "_id": representation["parent"] - }) + repre_doc = get_representation_by_id( + project_name, + active["representation"], + fields=["parent"] + ) - versions = list(legacy_io.find( - { - "parent": version["parent"], - "type": "version" - }, - sort=[("name", 1)] + repre_version_doc = get_version_by_id( + project_name, + repre_doc["parent"], + fields=["parent"] + ) + + version_docs = list(get_versions( + project_name, + subset_ids=[repre_version_doc["parent"]], + hero=True + )) + hero_version = None + standard_versions = [] + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version = version_doc + else: + standard_versions.append(version_doc) + versions = list(reversed( + sorted(standard_versions, key=lambda item: item["name"]) )) - - hero_version = legacy_io.find_one({ - "parent": version["parent"], - "type": "hero_version" - }) if hero_version: _version_id = hero_version["version_id"] for _version in versions: @@ -703,7 +710,7 @@ class SceneInventoryView(QtWidgets.QTreeView): all_versions = [] if hero_version: all_versions.append(hero_version) - all_versions.extend(reversed(versions)) + all_versions.extend(versions) if current_item: index = all_versions.index(current_item) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 44ec09b2ca..6def284a83 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -1,7 +1,9 @@ +import os import sys import json import traceback import functools +import datetime from Qt import QtWidgets, QtGui, QtCore @@ -10,7 +12,66 @@ from openpype.tools.settings import CHILD_OFFSET from .widgets import ExpandingWidget from .lib import create_deffered_value_change_timer -from .constants import DEFAULT_PROJECT_LABEL +from .constants import ( + DEFAULT_PROJECT_LABEL, + SETTINGS_PATH_KEY, + ROOT_KEY, + VALUE_KEY, + SAVE_TIME_KEY, + PROJECT_NAME_KEY, +) + +_MENU_SEPARATOR_REQ = object() + + +class ExtractHelper: + _last_save_dir = os.path.expanduser("~") + + @classmethod + def get_last_save_dir(cls): + return cls._last_save_dir + + @classmethod + def set_last_save_dir(cls, save_dir): + cls._last_save_dir = save_dir + + @classmethod + def ask_for_save_filepath(cls, parent): + dialog = QtWidgets.QFileDialog( + parent, + "Save settings values", + cls.get_last_save_dir(), + "Values (*.json)" + ) + # dialog.setOption(dialog.DontUseNativeDialog) + dialog.setAcceptMode(dialog.AcceptSave) + if dialog.exec() != dialog.Accepted: + return + + selected_urls = dialog.selectedUrls() + if not selected_urls: + return + + filepath = selected_urls[0].toLocalFile() + if not filepath: + return + + if not filepath.lower().endswith(".json"): + filepath += ".json" + return filepath + + @classmethod + def extract_settings_to_json(cls, filepath, settings_data, project_name): + now = datetime.datetime.now() + settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + if project_name != 0: + settings_data[PROJECT_NAME_KEY] = project_name + + with open(filepath, "w") as stream: + json.dump(settings_data, stream, indent=4) + + new_dir = os.path.dirname(filepath) + cls.set_last_save_dir(new_dir) class BaseWidget(QtWidgets.QWidget): @@ -190,24 +251,29 @@ class BaseWidget(QtWidgets.QWidget): actions_mapping[action] = remove_from_project_override menu.addAction(action) + def _get_mime_data_from_entity(self): + if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: + entity_path = None + else: + entity_path = "/".join( + [self.entity.root_key, self.entity.path] + ) + + value = self.entity.value + + # Copy for settings tool + return { + VALUE_KEY: value, + ROOT_KEY: self.entity.root_key, + SETTINGS_PATH_KEY: entity_path + } + def _copy_value_actions(self, menu): def copy_value(): mime_data = QtCore.QMimeData() - if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: - entity_path = None - else: - entity_path = "/".join( - [self.entity.root_key, self.entity.path] - ) - - value = self.entity.value # Copy for settings tool - settings_data = { - "root_key": self.entity.root_key, - "value": value, - "path": entity_path - } + settings_data = self._get_mime_data_from_entity() settings_encoded_data = QtCore.QByteArray() settings_stream = QtCore.QDataStream( settings_encoded_data, QtCore.QIODevice.WriteOnly @@ -218,6 +284,7 @@ class BaseWidget(QtWidgets.QWidget): ) # Copy as json + value = settings_data[VALUE_KEY] json_encoded_data = None if isinstance(value, (dict, list)): json_encoded_data = QtCore.QByteArray() @@ -241,25 +308,87 @@ class BaseWidget(QtWidgets.QWidget): action = QtWidgets.QAction("Copy", menu) return [(action, copy_value)] + def _extract_to_file(self): + filepath = ExtractHelper.ask_for_save_filepath(self) + if not filepath: + return + + settings_data = self._get_mime_data_from_entity() + project_name = 0 + if hasattr(self.category_widget, "project_name"): + project_name = self.category_widget.project_name + + ExtractHelper.extract_settings_to_json( + filepath, settings_data, project_name + ) + + def _extract_value_to_file_actions(self, menu): + extract_action = QtWidgets.QAction("Extract to file", menu) + return [ + _MENU_SEPARATOR_REQ, + (extract_action, self._extract_to_file) + ] + + def _parse_source_data_for_paste(self, data): + settings_path = None + root_key = None + if isinstance(data, dict): + data.pop(SAVE_TIME_KEY, None) + data.pop(PROJECT_NAME_KEY, None) + settings_path = data.pop(SETTINGS_PATH_KEY, settings_path) + root_key = data.pop(ROOT_KEY, root_key) + data = data.pop(VALUE_KEY, data) + + return { + VALUE_KEY: data, + SETTINGS_PATH_KEY: settings_path, + ROOT_KEY: root_key + } + + def _get_value_from_clipboard(self): + clipboard = QtWidgets.QApplication.clipboard() + mime_data = clipboard.mimeData() + app_value = mime_data.data("application/copy_settings_value") + if app_value: + settings_stream = QtCore.QDataStream( + app_value, QtCore.QIODevice.ReadOnly + ) + mime_data_value_str = settings_stream.readQString() + return json.loads(mime_data_value_str) + + if mime_data.hasUrls(): + for url in mime_data.urls(): + local_file = url.toLocalFile() + try: + with open(local_file, "r") as stream: + value = json.load(stream) + except Exception: + continue + if value: + return self._parse_source_data_for_paste(value) + + if mime_data.hasText(): + text = mime_data.text() + try: + value = json.loads(text) + except Exception: + try: + value = self.entity.convert_to_valid_type(text) + except Exception: + return None + return self._parse_source_data_for_paste(value) + def _paste_value_actions(self, menu): output = [] # Allow paste of value only if were copied from this UI - clipboard = QtWidgets.QApplication.clipboard() - mime_data = clipboard.mimeData() - mime_value = mime_data.data("application/copy_settings_value") + mime_data_value = self._get_value_from_clipboard() # Skip if there is nothing to do - if not mime_value: + if not mime_data_value: return output - settings_stream = QtCore.QDataStream( - mime_value, QtCore.QIODevice.ReadOnly - ) - mime_data_value_str = settings_stream.readQString() - mime_data_value = json.loads(mime_data_value_str) - - value = mime_data_value["value"] - path = mime_data_value["path"] - root_key = mime_data_value["root_key"] + value = mime_data_value[VALUE_KEY] + path = mime_data_value[SETTINGS_PATH_KEY] + root_key = mime_data_value[ROOT_KEY] # Try to find matching entity to be able paste values to same spot # - entity can't by dynamic or in dynamic item @@ -391,10 +520,19 @@ class BaseWidget(QtWidgets.QWidget): ui_actions.extend(self._copy_value_actions(menu)) ui_actions.extend(self._paste_value_actions(menu)) if ui_actions: - menu.addSeparator() - for action, callback in ui_actions: - menu.addAction(action) - actions_mapping[action] = callback + ui_actions.insert(0, _MENU_SEPARATOR_REQ) + + ui_actions.extend(self._extract_value_to_file_actions(menu)) + + for item in ui_actions: + if item is _MENU_SEPARATOR_REQ: + if len(menu.actions()) > 0: + menu.addSeparator() + continue + + action, callback = item + menu.addAction(action) + actions_mapping[action] = callback if not actions_mapping: action = QtWidgets.QAction("< No action >") diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index c8ade5fcdb..764f42f1a3 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -45,8 +45,15 @@ from .breadcrumbs_widget import ( SystemSettingsBreadcrumbs, ProjectSettingsBreadcrumbs ) - -from .base import GUIWidget +from .constants import ( + SETTINGS_PATH_KEY, + ROOT_KEY, + VALUE_KEY, +) +from .base import ( + ExtractHelper, + GUIWidget, +) from .list_item_widget import ListWidget from .list_strict_widget import ListStrictWidget from .dict_mutable_widget import DictMutableKeysWidget @@ -627,11 +634,35 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self._on_context_version_trigger ) submenu.addAction(action) + menu.addMenu(submenu) + extract_action = QtWidgets.QAction("Extract to file", menu) + extract_action.triggered.connect(self._on_extract_to_file) + + menu.addAction(extract_action) + def _on_context_version_trigger(self, version): self._on_source_version_change(version) + def _on_extract_to_file(self): + filepath = ExtractHelper.ask_for_save_filepath(self) + if not filepath: + return + + settings_data = { + SETTINGS_PATH_KEY: self.entity.root_key, + ROOT_KEY: self.entity.root_key, + VALUE_KEY: self.entity.value + } + project_name = 0 + if hasattr(self, "project_name"): + project_name = self.project_name + + ExtractHelper.extract_settings_to_json( + filepath, settings_data, project_name + ) + def _on_reset_crash(self): self.save_btn.setEnabled(False) diff --git a/openpype/tools/settings/settings/constants.py b/openpype/tools/settings/settings/constants.py index 9d6d7904d7..d98d18c8bf 100644 --- a/openpype/tools/settings/settings/constants.py +++ b/openpype/tools/settings/settings/constants.py @@ -7,6 +7,12 @@ PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2 PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3 PROJECT_VERSION_ROLE = QtCore.Qt.UserRole + 4 +# Save/Extract keys +SETTINGS_PATH_KEY = "__settings_path__" +ROOT_KEY = "__root_key__" +VALUE_KEY = "__value__" +SAVE_TIME_KEY = "__extracted__" +PROJECT_NAME_KEY = "__project_name__" __all__ = ( "DEFAULT_PROJECT_LABEL", @@ -15,4 +21,11 @@ __all__ = ( "PROJECT_IS_ACTIVE_ROLE", "PROJECT_IS_SELECTED_ROLE", "PROJECT_VERSION_ROLE", + + "SETTINGS_PATH_KEY", + "ROOT_KEY", + "SETTINGS_PATH_KEY", + "VALUE_KEY", + "SAVE_TIME_KEY", + "PROJECT_NAME_KEY", ) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 1ad5cd119e..3ceeb3ad48 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,6 +6,8 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_asset_by_id + from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget ) @@ -126,17 +128,6 @@ class Window(QtWidgets.QDialog): if event: super().resizeEvent(event) - def get_avalon_parent(self, entity): - ''' Avalon DB entities helper - get all parents (exclude project). - ''' - parent_id = entity['data']['visualParent'] - parents = [] - if parent_id is not None: - parent = self.db.find_one({'_id': parent_id}) - parents.extend(self.get_avalon_parent(parent)) - parents.append(parent['name']) - return parents - def on_project_change(self, project_name): self.widget_family.refresh() @@ -152,7 +143,10 @@ class Window(QtWidgets.QDialog): ] if len(selected) == 1: self.valid_parent = True - asset = self.db.find_one({"_id": selected[0], "type": "asset"}) + project_name = self.db.active_project() + asset = get_asset_by_id( + project_name, selected[0], fields=["name"] + ) self.widget_family.change_asset(asset['name']) else: self.valid_parent = False diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 02e9073555..abfc0a2145 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -4,6 +4,7 @@ import collections from Qt import QtCore, QtGui import qtawesome +from openpype.client import get_assets from openpype.style import ( get_default_entity_icon_color, get_deprecated_entity_font_color, @@ -104,17 +105,18 @@ class AssetModel(TreeModel): def refresh(self): """Refresh the data for the model.""" + project_name = self.dbcon.active_project() self.clear() - if ( - self.dbcon.active_project() is None or - self.dbcon.active_project() == '' - ): + if not project_name: return self.beginResetModel() # Get all assets in current project sorted by name - db_assets = self.dbcon.find({"type": "asset"}).sort("name", 1) + asset_docs = get_assets(project_name) + db_assets = list( + sorted(asset_docs, key=lambda item: item["name"]) + ) # Group the assets by their visual parent's id assets_by_parent = collections.defaultdict(list) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 8b43cd7cf8..73114f7960 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -2,6 +2,10 @@ import contextlib from Qt import QtWidgets, QtCore import qtawesome +from openpype.client import ( + get_project, + get_asset_by_id, +) from openpype.tools.utils import PlaceholderLineEdit from openpype.style import get_default_tools_icon_color @@ -218,7 +222,8 @@ class AssetWidget(QtWidgets.QWidget): self.view = view def collect_data(self): - project = self.dbcon.find_one({'type': 'project'}) + project_name = self.dbcon.active_project() + project = get_project(project_name, fields=["name"]) asset = self.get_active_asset() try: @@ -241,9 +246,16 @@ class AssetWidget(QtWidgets.QWidget): return ent_parents output = [] - if entity.get('data', {}).get('visualParent', None) is None: + parent_asset_id = entity.get('data', {}).get('visualParent', None) + if parent_asset_id is None: return output - parent = self.dbcon.find_one({'_id': entity['data']['visualParent']}) + + project_name = self.dbcon.active_project() + parent = get_asset_by_id( + project_name, + parent_asset_id, + fields=["name", "data.visualParent"] + ) output.append(parent['name']) output.extend(self.get_parents(parent)) return output @@ -349,9 +361,10 @@ class AssetWidget(QtWidgets.QWidget): tasks = [] selected = self.get_selected_assets() if len(selected) == 1: - asset = self.dbcon.find_one({ - "_id": selected[0], "type": "asset" - }) + project_name = self.dbcon.active_project() + asset = get_asset_by_id( + project_name, selected[0], fields=["data.tasks"] + ) if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) @@ -400,7 +413,7 @@ class AssetWidget(QtWidgets.QWidget): # Select mode = selection_model.Select | selection_model.Rows - for index in lib.iter_model_rows( + for index in _iter_model_rows( self.proxy, column=0, include_root=False ): # stop iteration if there are no assets to process diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 08cd45bbf2..1736be84ab 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -1,14 +1,21 @@ import re from Qt import QtWidgets, QtCore -from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole -from . import FamilyDescriptionWidget +from openpype.client import ( + get_asset_by_name, + get_subset_by_name, + get_subsets, + get_last_version_by_subset_id, +) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator from openpype.lib import TaskNotSetError from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole +from . import FamilyDescriptionWidget + class FamilyWidget(QtWidgets.QWidget): @@ -180,12 +187,9 @@ class FamilyWidget(QtWidgets.QWidget): asset_doc = None if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name - asset_doc = self.dbcon.find_one( - { - "type": "asset", - "name": asset_name - }, - {"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin and family @@ -200,14 +204,13 @@ class FamilyWidget(QtWidgets.QWidget): return # Get the asset from the database which match with the name - asset_doc = self.dbcon.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) if asset_doc and plugin: - project_name = self.dbcon.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = self.dbcon.Session["AVALON_TASK"] @@ -231,14 +234,14 @@ class FamilyWidget(QtWidgets.QWidget): self.input_result.setText("Select task please") # Get all subsets of the current asset - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } # Defaults to dropdown defaults = [] @@ -296,47 +299,37 @@ class FamilyWidget(QtWidgets.QWidget): if not auto_version: return + project_name = self.dbcon.active_project() asset_name = self.asset_name subset_name = self.input_result.text() version = 1 asset_doc = None subset_doc = None - versions = None if ( asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = self.dbcon.find_one( - { - 'type': 'asset', - 'name': asset_name - }, - {"_id": 1} + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) if asset_doc: - subset_doc = self.dbcon.find_one( - { - 'type': 'subset', - 'parent': asset_doc['_id'], - 'name': subset_name - }, - {"_id": 1} + subset_doc = get_subset_by_name( + project_name, + subset_name, + asset_doc['_id'], + fields=["_id"] ) if subset_doc: - versions = self.dbcon.find( - { - 'type': 'version', - 'parent': subset_doc['_id'] - }, - {"name": 1} - ).distinct("name") - - if versions: - versions = sorted(versions) - version = int(versions[-1]) + 1 + last_version = get_last_version_by_subset_id( + project_name, + subset_doc["_id"], + fields=["name"] + ) + if last_version: + version = last_version["name"] + 1 self.version_spinbox.setValue(version) diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index fd8d6dc02e..746a72b3ec 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,6 +4,7 @@ import click import speedcopy +from openpype.client import get_project, get_asset_by_name from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -29,20 +30,6 @@ class TextureCopy: if os.path.splitext(x)[1].lower() in texture_extensions) return textures - def _get_project(self, project_name): - project = legacy_io.find_one({ - 'type': 'project', - 'name': project_name - }) - return project - - def _get_asset(self, asset_name): - asset = legacy_io.find_one({ - 'type': 'asset', - 'name': asset_name - }) - return asset - def _get_destination_path(self, asset, project): project_name = project["name"] hierarchy = "" @@ -88,11 +75,12 @@ class TextureCopy: t.echo("!!! {}".format(e)) exit(1) - def process(self, asset, project, path): + def process(self, asset_name, project_name, path): """ Process all textures found in path and copy them to asset under project. """ + t.echo(">>> Looking for textures ...") textures = self._get_textures(path) if len(textures) < 1: @@ -101,14 +89,14 @@ class TextureCopy: else: t.echo(">>> Found {} textures ...".format(len(textures))) - project = self._get_project(project) + project = get_project(project_name) if not project: - t.echo("!!! Project name [ {} ] not found.".format(project)) + t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = self._get_asset(asset) - if not project: - t.echo("!!! Asset [ {} ] not found in project".format(asset)) + asset = get_asset_by_name(project_name, asset_name) + if not asset: + t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) t.echo((">>> Project [ {} ] and " "asset [ {} ] seems to be OK ...").format(project['name'], diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 82bdcd63a2..772946e9e1 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -5,6 +5,10 @@ import Qt from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.style import ( get_objected_colors, get_default_tools_icon_color, @@ -525,21 +529,18 @@ class AssetModel(QtGui.QStandardItemModel): self._doc_fetched.emit() def _fetch_asset_docs(self): - if not self.dbcon.Session.get("AVALON_PROJECT"): + project_name = self.dbcon.current_project() + if not project_name: return [] - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"_id": True} - ) + project_doc = get_project(project_name, fields=["_id"]) if not project_doc: return [] # Get all assets sorted by name - return list(self.dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + return list( + get_assets(project_name, fields=self._asset_projection.keys()) + ) def _stop_fetch_thread(self): self._refreshing = False diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 71f817a1d7..d6c2d69e76 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -6,15 +6,19 @@ import numbers import Qt from Qt import QtWidgets, QtGui, QtCore -from openpype.pipeline import HeroVersionType -from .models import TreeModel -from . import lib - if Qt.__binding__ == "PySide": from PySide.QtGui import QStyleOptionViewItemV4 elif Qt.__binding__ == "PyQt4": from PyQt4.QtGui import QStyleOptionViewItemV4 +from openpype.client import ( + get_versions, + get_hero_versions, +) +from openpype.pipeline import HeroVersionType +from .models import TreeModel +from . import lib + log = logging.getLogger(__name__) @@ -114,26 +118,24 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): "Version is not integer" ) + project_name = self.dbcon.active_project() # Add all available versions to the editor parent_id = item["version_document"]["parent"] - version_docs = list(self.dbcon.find( - { - "type": "version", - "parent": parent_id - }, - sort=[("name", 1)] + version_docs = list(sorted( + get_versions(project_name, subset_ids=[parent_id]), + key=lambda item: item["name"] )) - hero_version_doc = self.dbcon.find_one( - { - "type": "hero_version", - "parent": parent_id - }, { - "name": 1, - "data.tags": 1, - "version_id": 1 - } + hero_versions = list( + get_hero_versions( + project_name, + subset_ids=[parent_id], + fields=["name", "data.tags", "version_id"] + ) ) + hero_version_doc = None + if hero_versions: + hero_version_doc = hero_versions[0] doc_for_hero_version = None diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 20fea6600b..ea1362945f 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -6,6 +6,10 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset_by_name, +) from openpype.style import ( get_default_entity_icon_color, get_objected_colors, @@ -430,9 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = database[project_name].find_one( - {"type": "asset", "name": asset_name}, - {"data.tasks": True} + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") @@ -500,10 +503,7 @@ class GroupsConfig: project_name = self.dbcon.Session.get("AVALON_PROJECT") if project_name: # Get pre-defined group name and appearance from project config - project_doc = self.dbcon.find_one( - {"type": "project"}, - projection={"config.groups": True} - ) + project_doc = get_project(project_name, fields=["config.groups"]) if project_doc: group_configs = project_doc["config"].get("groups") or [] diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index eab183d5f3..0353f3dd2f 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -1,6 +1,10 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset_by_id, +) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -47,7 +51,8 @@ class TasksModel(QtGui.QStandardItemModel): # Get the project configured icons from database project_doc = {} if self._context_is_valid(): - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) self._loaded_project_name = self._get_current_project() self._project_doc = project_doc @@ -71,9 +76,9 @@ class TasksModel(QtGui.QStandardItemModel): def set_asset_id(self, asset_id): asset_doc = None if self._context_is_valid(): - asset_doc = self.dbcon.find_one( - {"_id": asset_id}, - {"data.tasks": True} + project_name = self._get_current_project() + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 977111b71b..68fe8301c9 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,6 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore +from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -384,7 +385,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - self._asset_doc = legacy_io.find_one({"_id": self._asset_id}) + project_name = legacy_io.active_project() + self._asset_doc = get_asset_by_id(project_name, self._asset_id) + return self._asset_doc def _get_session(self): diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index 8f9dd8c6ba..d5b7cef339 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -4,6 +4,11 @@ import logging from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_subsets, + get_versions, + get_representations, +) from openpype.style import ( get_default_entity_icon_color, get_disabled_entity_icon_color, @@ -215,6 +220,7 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._dbcon = dbcon self._anatomy = anatomy + self._file_extensions = extensions self._invalid_context_item = None @@ -234,6 +240,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._asset_id = None self._task_name = None + @property + def project_name(self): + return self._dbcon.Session["AVALON_PROJECT"] + def _set_item_invalid(self, item): item.setFlags(QtCore.Qt.NoItemFlags) item.setData(self._invalid_icon, QtCore.Qt.DecorationRole) @@ -285,15 +295,11 @@ class PublishFilesModel(QtGui.QStandardItemModel): def _get_workfie_representations(self): output = [] # Get subset docs of asset - subset_docs = self._dbcon.find( - { - "type": "subset", - "parent": self._asset_id - }, - { - "_id": True, - "name": True - } + subset_docs = get_subsets( + self.project_name, + asset_ids=[self._asset_id], + fields=["_id", "name"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] @@ -301,17 +307,12 @@ class PublishFilesModel(QtGui.QStandardItemModel): return output # Get version docs of subsets with their families - version_docs = self._dbcon.find( - { - "type": "version", - "parent": {"$in": subset_ids} - }, - { - "_id": True, - "data.families": True, - "parent": True - } + version_docs = get_versions( + self.project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "data.families"] ) + # Filter versions if they contain 'workfile' family filtered_versions = [] for version_doc in version_docs: @@ -327,13 +328,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): # Query representations of filtered versions and add filter for # extension extensions = [ext.replace(".", "") for ext in self._file_extensions] - repre_docs = self._dbcon.find( - { - "type": "representation", - "parent": {"$in": version_ids}, - "context.ext": {"$in": extensions} - } + repre_docs = get_representations( + self.project_name, version_ids, extensions ) + # Filter queried representations by task name if task is set filtered_repre_docs = [] for repre_doc in repre_docs: diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 3e97d6c938..b62fd2c889 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -5,6 +5,10 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import ( + get_project, + get_asset_by_name, +) from openpype.lib import ( get_last_workfile_with_version, get_workdir_data, @@ -22,29 +26,19 @@ def build_workfile_data(session): """Get the data required for workfile formatting from avalon `session`""" # Set work file data for template formatting + project_name = session["AVALON_PROJECT"] asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] host_name = session["AVALON_APP"] - project_doc = legacy_io.find_one( - {"type": "project"}, - { - "name": True, - "data.code": True, - "config.tasks": True, - } + project_doc = get_project( + project_name, fields=["name", "data.code", "config.tasks"] + ) + asset_doc = get_asset_by_name( + project_name, + asset_name, + fields=["name", "data.tasks", "data.parents"] ) - asset_doc = legacy_io.find_one( - { - "type": "asset", - "name": asset_name - }, - { - "name": True, - "data.tasks": True, - "data.parents": True - } - ) data = get_workdir_data(project_doc, asset_doc, task_name, host_name) data.update({ "version": 1, diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 02a22af26c..9f4cea2f8a 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,6 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets +from openpype.client import get_asset_by_id, get_asset_by_name from openpype import style from openpype.lib import ( get_workfile_doc, @@ -223,6 +224,10 @@ class Window(QtWidgets.QMainWindow): self._first_show = True self._context_to_set = None + @property + def project_name(self): + return legacy_io.Session["AVALON_PROJECT"] + def showEvent(self, event): super(Window, self).showEvent(event) if self._first_show: @@ -296,7 +301,8 @@ class Window(QtWidgets.QMainWindow): if not workfile_doc: workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() - asset_doc = legacy_io.find_one({"_id": asset_id}) + project_name = legacy_io.active_project() + asset_doc = get_asset_by_id(project_name, asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -322,14 +328,13 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = legacy_io.find_one( - { - "name": context["asset"], - "type": "asset" - }, - {"_id": 1} - ) or {} - asset_id = asset_doc.get("_id") + asset_doc = get_asset_by_name( + self.project_name, context["asset"], fields=["_id"] + ) + + asset_id = None + if asset_doc: + asset_id = asset_doc["_id"] # Select the asset self.assets_widget.select_asset(asset_id) self.tasks_widget.set_asset_id(asset_id)