diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py index b49c8dd505..e9bb2287a0 100644 --- a/openpype/client/server/entities.py +++ b/openpype/client/server/entities.py @@ -423,8 +423,23 @@ def get_last_version_by_subset_name( ) -def get_output_link_versions(*args, **kwargs): - raise NotImplementedError("'get_output_link_versions' not implemented") +def get_output_link_versions(project_name, version_id, fields=None): + if not version_id: + return [] + + con = get_server_api_connection() + version_links = con.get_version_links( + project_name, version_id, link_direction="out") + + version_ids = { + link["entityId"] + for link in version_links + if link["entityType"] == "version" + } + if not version_ids: + return [] + + return get_versions(project_name, version_ids=version_ids, fields=fields) def version_is_latest(project_name, version_id): diff --git a/openpype/client/server/entity_links.py b/openpype/client/server/entity_links.py index f61b461f38..d8395aabe7 100644 --- a/openpype/client/server/entity_links.py +++ b/openpype/client/server/entity_links.py @@ -1,3 +1,9 @@ +import ayon_api +from ayon_api import get_folder_links, get_versions_links + +from .entities import get_assets, get_representation_by_id + + def get_linked_asset_ids(project_name, asset_doc=None, asset_id=None): """Extract linked asset ids from asset document. @@ -15,7 +21,19 @@ def get_linked_asset_ids(project_name, asset_doc=None, asset_id=None): List[Union[ObjectId, str]]: Asset ids of input links. """ - return [] + output = [] + if not asset_doc and not asset_id: + return output + + if not asset_id: + asset_id = asset_doc["_id"] + + links = get_folder_links(project_name, asset_id, link_direction="in") + return [ + link["entityId"] + for link in links + if link["entityType"] == "folder" + ] def get_linked_assets( @@ -38,7 +56,11 @@ def get_linked_assets( asset doc. """ - return [] + link_ids = get_linked_asset_ids(project_name, asset_doc, asset_id) + if not link_ids: + return [] + return list(get_assets(project_name, asset_ids=link_ids, fields=fields)) + def get_linked_representation_id( @@ -51,6 +73,10 @@ def get_linked_representation_id( Representation links now works only from representation through version back to representations. + Todos: + Missing depth query. Not sure how it did find more representations in + depth, probably links to version? + Args: project_name (str): Name of project where look for links. repre_doc (Dict[str, Any]): Representation document. @@ -62,4 +88,69 @@ def get_linked_representation_id( List[ObjectId] Linked representation ids. """ - return [] + if repre_doc: + repre_id = repre_doc["_id"] + + if not repre_id and not repre_doc: + return [] + + version_id = None + if repre_doc: + version_id = repre_doc.get("parent") + + if not version_id: + repre_doc = get_representation_by_id( + project_name, repre_id, fields=["parent"] + ) + if repre_doc: + version_id = repre_doc["parent"] + + if not version_id: + return [] + + if max_depth is None or max_depth == 0: + max_depth = 1 + + link_types = None + if link_type: + link_types = [link_type] + + # Store already found version ids to avoid recursion, and also to store + # output -> Don't forget to remove 'version_id' at the end!!! + linked_version_ids = {version_id} + # Each loop of depth will reset this variable + versions_to_check = {version_id} + for _ in range(max_depth): + if not versions_to_check: + break + + links = get_versions_links( + project_name, + versions_to_check, + link_types=link_types, + link_direction="out") + + versions_to_check = set() + for link in links: + # Care only about version links + if link["entityType"] != "version": + continue + entity_id = link["entityId"] + # Skip already found linked version ids + if entity_id in linked_version_ids: + continue + linked_version_ids.add(entity_id) + versions_to_check.add(entity_id) + + linked_version_ids.remove(version_id) + if not linked_version_ids: + return [] + + representations = ayon_api.get_representations( + project_name, + version_ids=linked_version_ids, + fields=["id"]) + return [ + repre["id"] + for repre in representations + ] diff --git a/openpype/plugins/publish/integrate_inputlinks.py b/openpype/plugins/publish/integrate_inputlinks.py index 6964f2d938..c639bbf994 100644 --- a/openpype/plugins/publish/integrate_inputlinks.py +++ b/openpype/plugins/publish/integrate_inputlinks.py @@ -3,6 +3,7 @@ from collections import OrderedDict from bson.objectid import ObjectId import pyblish.api +from openpype import AYON_SERVER_ENABLED from openpype.pipeline import legacy_io @@ -34,6 +35,11 @@ class IntegrateInputLinks(pyblish.api.ContextPlugin): plugin. """ + + if AYON_SERVER_ENABLED: + self.log.info("Skipping, in AYON mode") + return + workfile = None publishing = [] diff --git a/openpype/plugins/publish/integrate_inputlinks_ayon.py b/openpype/plugins/publish/integrate_inputlinks_ayon.py new file mode 100644 index 0000000000..8ab5c923c4 --- /dev/null +++ b/openpype/plugins/publish/integrate_inputlinks_ayon.py @@ -0,0 +1,160 @@ +import collections + +import pyblish.api +from ayon_api import create_link, make_sure_link_type_exists + +from openpype import AYON_SERVER_ENABLED + + +class IntegrateInputLinksAYON(pyblish.api.ContextPlugin): + """Connecting version level dependency links""" + + order = pyblish.api.IntegratorOrder + 0.2 + label = "Connect Dependency InputLinks AYON" + + def process(self, context): + """Connect dependency links for all instances, globally + + Code steps: + - filter instances that integrated version + - have "versionEntity" entry in data + - separate workfile instance within filtered instances + - when workfile instance is available: + - link all `loadedVersions` as input of the workfile + - link workfile as input of all other integrated versions + - link version's inputs if it's instance have "inputVersions" entry + - + + inputVersions: + The "inputVersions" in instance.data should be a list of + version ids (str), which are the dependencies of the publishing + instance that should be extracted from working scene by the DCC + specific publish plugin. + """ + + if not AYON_SERVER_ENABLED: + self.log.info("Skipping, not in AYON mode") + return + + workfile_instance, other_instances = self.split_instances(context) + + # Variable where links are stored in submethods + new_links_by_type = collections.defaultdict(list) + + self.create_workfile_links( + workfile_instance, other_instances, new_links_by_type) + + self.create_generative_links(other_instances, new_links_by_type) + + self.create_links_on_server(context, new_links_by_type) + + def split_instances(self, context): + workfile_instance = None + other_instances = [] + + for instance in context: + # Skip inactive instances + if not instance.data.get("publish", True): + continue + + version_doc = instance.data.get("versionEntity") + if not version_doc: + self.log.debug( + "Instance {} doesn't have version.".format(instance)) + continue + + family = instance.data.get("family") + if family == "workfile": + workfile_instance = instance + else: + other_instances.append(instance) + return workfile_instance, other_instances + + def add_link(self, new_links_by_type, link_type, input_id, output_id): + """Add dependency link data into temporary variable. + + Args: + new_links_by_type (dict[str, list[dict[str, Any]]]): Object where + output is stored. + link_type (str): Type of link, one of 'reference' or 'generative' + input_id (str): Input version id. + output_id (str): Output version id. + """ + + new_links_by_type[link_type].append((input_id, output_id)) + + def create_workfile_links( + self, workfile_instance, other_instances, new_links_by_type + ): + if workfile_instance is None: + self.log.warn("No workfile in this publish session.") + return + + workfile_version_id = workfile_instance.data["versionEntity"]["_id"] + # link workfile to all publishing versions + for instance in other_instances: + self.add_link( + new_links_by_type, + "generative", + workfile_version_id, + instance.data["versionEntity"]["_id"], + ) + + loaded_versions = workfile_instance.context.get("loadedVersions") + if not loaded_versions: + return + + # link all loaded versions in scene into workfile + for version in loaded_versions: + self.add_link( + new_links_by_type, + "reference", + version["version"], + workfile_version_id, + ) + + def create_generative_links(self, other_instances, new_links_by_type): + for instance in other_instances: + input_versions = instance.data.get("inputVersions") + if not input_versions: + continue + + version_entity = instance.data["versionEntity"] + for input_version in input_versions: + self.add_link( + new_links_by_type, + "generative", + input_version, + version_entity["_id"], + ) + + def create_links_on_server(self, context, new_links): + """Create new links on server. + + Args: + dict[str, list[tuple[str, str]]]: Version links by link type. + """ + + if not new_links: + return + + project_name = context.data["projectName"] + + # Make sure link types are available on server + for link_type in new_links.keys(): + make_sure_link_type_exists( + project_name, link_type, "version", "version" + ) + + # Create link themselves + for link_type, items in new_links.items(): + for item in items: + input_id, output_id = item + create_link( + project_name, + link_type, + input_id, + "version", + output_id, + "version" + ) diff --git a/openpype/vendor/python/common/ayon_api/server_api.py b/openpype/vendor/python/common/ayon_api/server_api.py index 8d52b484d8..796ec13d41 100644 --- a/openpype/vendor/python/common/ayon_api/server_api.py +++ b/openpype/vendor/python/common/ayon_api/server_api.py @@ -4514,13 +4514,34 @@ class ServerAPI(object): dict[str, list[dict[str, Any]]]: Link info by entity ids. """ - mapped_type = self._entity_types_link_mapping.get(entity_type) - if not mapped_type: + if entity_type == "folder": + query_func = folders_graphql_query + id_filter_key = "folderIds" + project_sub_key = "folders" + elif entity_type == "task": + query_func = tasks_graphql_query + id_filter_key = "taskIds" + project_sub_key = "tasks" + elif entity_type == "subset": + query_func = subsets_graphql_query + id_filter_key = "subsetIds" + project_sub_key = "subsets" + elif entity_type == "version": + query_func = versions_graphql_query + id_filter_key = "versionIds" + project_sub_key = "versions" + elif entity_type == "representation": + query_func = representations_graphql_query + id_filter_key = "representationIds" + project_sub_key = "representations" + else: raise ValueError("Unknown type \"{}\". Expected {}".format( - entity_type, ", ".join(self._entity_types_link_mapping.keys()) + entity_type, + ", ".join( + ("folder", "task", "subset", "version", "representation") + ) )) - id_filter_key, project_sub_key = mapped_type output = collections.defaultdict(list) filters = { "projectName": project_name @@ -4534,7 +4555,7 @@ class ServerAPI(object): if not self._prepare_link_filters(filters, link_types, link_direction): return output - query = folders_graphql_query({"id", "links"}) + query = query_func({"id", "links"}) for attr, filter_value in filters.items(): query.set_variable_value(attr, filter_value) diff --git a/openpype/vendor/python/common/ayon_api/version.py b/openpype/vendor/python/common/ayon_api/version.py index c8ddddd97e..3120942636 100644 --- a/openpype/vendor/python/common/ayon_api/version.py +++ b/openpype/vendor/python/common/ayon_api/version.py @@ -1,2 +1,2 @@ """Package declaring Python API for Ayon server.""" -__version__ = "0.1.17" +__version__ = "0.1.17-1"