AYON: Implement integrate links publish plugin (#4842)

* implemented get_output_link_versions

* implemented remainin entity link functions

* implemented ayon variant of links integration

* Changed class name and label

* fix data types iteration

* added links query fix from ayon api develop

* removed unnecessary import
This commit is contained in:
Jakub Trllo 2023-04-20 09:56:40 +02:00 committed by Jakub Trllo
parent 899f9965e4
commit ed9b25236e
6 changed files with 304 additions and 11 deletions

View file

@ -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):

View file

@ -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
]

View file

@ -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 = []

View file

@ -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"
)

View file

@ -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)

View file

@ -1,2 +1,2 @@
"""Package declaring Python API for Ayon server."""
__version__ = "0.1.17"
__version__ = "0.1.17-1"