Skip to content
168 changes: 133 additions & 35 deletions client/ayon_core/plugins/publish/collect_hierarchy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import pyblish.api
from ayon_core.lib import BoolDef
import ayon_api
from ayon_core.pipeline import publish
Comment on lines +2 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from ayon_core.lib import BoolDef
import ayon_api
from ayon_core.pipeline import publish
import ayon_api
from ayon_core.lib import BoolDef
from ayon_core.pipeline import publish



class CollectHierarchy(pyblish.api.ContextPlugin):
class CollectHierarchy(
pyblish.api.ContextPlugin,
publish.AYONPyblishPluginMixin,
):
"""Collecting hierarchy from `parents`.

present in `clip` family instances coming from the request json data file
Expand All @@ -13,20 +19,40 @@ class CollectHierarchy(pyblish.api.ContextPlugin):

label = "Collect Hierarchy"
order = pyblish.api.CollectorOrder - 0.076
hosts = ["resolve", "hiero", "flame", "traypublisher"]
settings_category = "core"

def process(self, context):
project_name = context.data["projectName"]
final_context = {
project_name: {
"entity_type": "project",
"children": {}
},
}
temp_context = {}
for instance in context:
self.log.debug("Processing instance: `{}` ...".format(instance))
ignore_shot_attributes_on_update = False

@classmethod
def get_attr_defs_for_context(cls, create_context):
return [
BoolDef(
"ignore_shot_attributes_on_update",
label="Ignore shot attributes on update",
default=cls.ignore_shot_attributes_on_update
)
]

@classmethod
def apply_settings(cls, project_settings):
cls.ignore_shot_attributes_on_update = (
project_settings
["core"]
["CollectHierarchy"]
["ignore_shot_attributes_on_update"]
)

def _get_shot_instances(self, context):
"""Get shot instances from context.

Args:
context (pyblish.api.Context): Context is a list of instances.

Returns:
list[pyblish.api.Instance]: A list of shot instances.
"""
shot_instances = []
for instance in context:
# shot data dict
product_type = instance.data["productType"]
families = instance.data["families"]
Expand All @@ -41,6 +67,67 @@ def process(self, context):
self.log.debug("Skipping not a shot from hero track")
continue

shot_instances.append(instance)

return shot_instances

def get_existing_folder_entities(self, project_name, shot_instances):
"""Get existing folder entities for given shot instances.

Args:
project_name (str): The name of the project.
shot_instances (list[pyblish.api.Instance]): A list of shot
instances.

Returns:
dict[str, dict]: A dictionary mapping folder paths to existing
folder entities.
"""
# first we need to get all folder paths from shot instances
folder_paths = {
instance.data["folderPath"]
for instance in shot_instances
}
# then we get all existing folder entities with one request
existing_entities = {
folder_entity["path"]: folder_entity
for folder_entity in ayon_api.get_folders(
project_name, folder_paths=folder_paths, fields={"path"})
}
for folder_path in folder_paths:
# add None value to non-existing folder entities
existing_entities.setdefault(folder_path, None)

return existing_entities

def process(self, context):
# get only shot instances from context
shot_instances = self._get_shot_instances(context)

if not shot_instances:
return

# get user input
values = self.get_attr_values_from_data(context.data)
ignore_shot_attributes_on_update = values.get(
"ignore_shot_attributes_on_update", None)

project_name = context.data["projectName"]
final_context = {
project_name: {
"entity_type": "project",
"children": {}
},
}
temp_context = {}
existing_entities = self.get_existing_folder_entities(
project_name, shot_instances)

for instance in shot_instances:
folder_path = instance.data["folderPath"]
self.log.debug(
f"Processing instance: `{folder_path} {instance}` ...")

shot_data = {
"entity_type": "folder",
# WARNING unless overwritten, default folder type is hardcoded
Expand All @@ -51,32 +138,43 @@ def process(self, context):
}

shot_data["attributes"] = {}
SHOT_ATTRS = (
"handleStart",
"handleEnd",
"frameStart",
"frameEnd",
"clipIn",
"clipOut",
"fps",
"resolutionWidth",
"resolutionHeight",
"pixelAspect",
)
for shot_attr in SHOT_ATTRS:
attr_value = instance.data.get(shot_attr)
if attr_value is None:
# Shot attribute might not be defined (e.g. CSV ingest)
# we need to check if the shot entity already exists
# and if not the attributes needs to be added in case the option
# is disabled by settings
if (
existing_entities.get(folder_path)
and not ignore_shot_attributes_on_update
):
SHOT_ATTRS = (
"handleStart",
"handleEnd",
"frameStart",
"frameEnd",
"clipIn",
"clipOut",
"fps",
"resolutionWidth",
"resolutionHeight",
"pixelAspect",
)
Comment on lines +148 to +159
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this can be at the top of the files as constant.

for shot_attr in SHOT_ATTRS:
attr_value = instance.data.get(shot_attr)
if attr_value is None:
# Shot attribute might not be defined (e.g. CSV ingest)
self.log.debug(
"%s shot attribute is not defined for instance.",
shot_attr
)
continue

shot_data["attributes"][shot_attr] = attr_value
else:
self.log.debug(
"%s shot attribute is not defined for instance.",
shot_attr
"Shot attributes will not be updated."
)
continue

shot_data["attributes"][shot_attr] = attr_value

# Split by '/' for AYON where asset is a path
name = instance.data["folderPath"].split("/")[-1]
name = folder_path.split("/")[-1]
actual = {name: shot_data}

for parent in reversed(instance.data["parents"]):
Expand Down
15 changes: 15 additions & 0 deletions server/settings/publish_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ class CollectAudioModel(BaseSettingsModel):
)


class CollectHierarchyModel(BaseSettingsModel):
_isGroup = True
ignore_shot_attributes_on_update: bool = SettingsField(
False,
title="Ignore shot attributes on update"
)


class CollectSceneVersionModel(BaseSettingsModel):
_isGroup = True
hosts: list[str] = SettingsField(
Expand Down Expand Up @@ -1094,6 +1102,10 @@ class PublishPuginsModel(BaseSettingsModel):
default_factory=CollectExplicitResolutionModel,
title="Collect Explicit Resolution"
)
CollectHierarchy: CollectHierarchyModel = SettingsField(
default_factory=CollectHierarchyModel,
title="Collect Hierarchy"
)
ValidateEditorialAssetName: ValidateBaseModel = SettingsField(
default_factory=ValidateBaseModel,
title="Validate Editorial Asset Name"
Expand Down Expand Up @@ -1275,6 +1287,9 @@ class PublishPuginsModel(BaseSettingsModel):
],
"options": []
},
"CollectHierarchy": {
"ignore_shot_attributes_on_update": False,
},
"ValidateEditorialAssetName": {
"enabled": True,
"optional": False,
Expand Down
Loading