From 9ab1b9eb270df4062ccd3dfde2e302bd3ac82b15 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 27 May 2025 23:01:51 -0400 Subject: [PATCH 1/8] Implement ValidateFolderCreationResolution. --- .../plugins/publish/validate_asset_docs.py | 140 +++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index b80b81b366..ac881bdeb9 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -1,5 +1,17 @@ import pyblish.api -from ayon_core.pipeline import PublishValidationError + +from ayon_api.entity_hub import EntityHub + +from ayon_core.lib import BoolDef +from ayon_core.pipeline.publish import ( + PublishValidationError, + RepairAction, + get_errored_instances_from_context, + AYONPyblishPluginMixin +) + + +_RESOLUTION_ATTRIBS = ("resolutionHeight", "resolutionWidth", "pixelAspect") class ValidateFolderEntities(pyblish.api.InstancePlugin): @@ -37,3 +49,129 @@ class ValidateFolderEntities(pyblish.api.InstancePlugin): "Instance \"{}\" doesn't have folder entity " "set which is needed for publishing." ).format(instance.data["name"])) + + +class RepairOverrideResolution(RepairAction): + """ Repair, force new resolution onto existing shot. + """ + label = "Force new shot resolution." + + def process(self, context, plugin): + entity_hub, entity, shot_data = ValidateFolderCreationResolution.get_shot_data( + context.data["hierarchyContext"] + ) + + for attrib in _RESOLUTION_ATTRIBS: + entity.attribs.set(attrib, shot_data[attrib]) + + entity_hub.commit_changes() + + +class RepairIgnoreResolution(RepairAction): + """ Repair, disable resolution update in problematic instance(s). + """ + label = "Do not update resolution." + + def process(self, context, plugin): + create_context = context.data["create_context"] + for inst in get_errored_instances_from_context(context, plugin=plugin): + instance_id = inst.data.get("instance_id") + created_instance = create_context.get_instance_by_id(instance_id) + attr_values = created_instance.data["publish_attributes"].get( + "ValidateFolderCreationResolution", {}) + attr_values["updateExistingFolderResolution"] = False + + create_context.save_changes() + + +class ValidateFolderCreationResolution(pyblish.api.InstancePlugin, AYONPyblishPluginMixin): + """ Validate resolution values before updating an existing folder. + """ + + label = "Validate new folder resolution" + order = pyblish.api.ValidatorOrder + families = ["shot"] + actions = [RepairIgnoreResolution, RepairOverrideResolution] + + @classmethod + def get_shot_data(self, hierarchy_context): + """ Retrieve matching entity and shot_data from hierarchy_context. + """ + project_name = tuple(hierarchy_context.keys())[0] + entity_hub = EntityHub(project_name) + entity_data = {project_name: entity_hub.project_entity} + entity_to_inspect = [(entity_data, hierarchy_context)] + + while entity_to_inspect: + entity_data, data = entity_to_inspect.pop(0) + + for name, value in data.items(): + entity = entity_data.get(name) + child = value.get("children", None) + + if entity and child: + entity_children = {child.name: child for child in entity.children} + entity_to_inspect.append((entity_children, child)) + + # Destination shot already exists return for validation. + elif value.get("folder_type") == "Shot" and entity and not child: + shot_data = value.get("attributes", {}) + return entity_hub, entity, shot_data + + return None + + def process(self, instance): + """ Validate existing shot resolution. + """ + try: + hierarchy_context = instance.context.data["hierarchyContext"] + + except KeyError: + self.log.info("No hierarchy context defined for shot instance.") + return + + validation_data = self.get_shot_data(hierarchy_context) + if not validation_data: + self.log.info("Destination shot does not exist yet, nothing to validate.") + return + + values = self.get_attr_values_from_data(instance.data) + _, entity, shot_data = validation_data + + # Validate existing shot resolution is matching new one + # ask for confirmation instead of blind update, this prevents mistakes. + if values.get("updateExistingFolderResolution", True): + for resolution_attrib in _RESOLUTION_ATTRIBS: + shot_value = shot_data.get(resolution_attrib) + entity_value = entity.attribs.get(resolution_attrib) + if shot_value and shot_value != entity_value: + raise PublishValidationError( + "Resolution mismatch for shot." + f"{resolution_attrib}={shot_value} but already existing " + f"shot is set to {entity_value}." + ) + + # If update existing shot is disabled, remove any resolution attribs. + else: + for resolution_attrib in _RESOLUTION_ATTRIBS: + shot_data.pop(resolution_attrib, None) + + self.log.info( + "Ignore existing shot resolution validation " + "(update is disabled)." + ) + + @classmethod + def get_attr_defs_for_instance( + cls, create_context, instance, + ): + if instance.product_type not in cls.families: + return [] + + return [ + BoolDef( + "updateExistingFolderResolution", + default=True, + label="Update existing shot resolution.", + ), + ] From eca282e76e821e74184a47e0793676455fa9c19e Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 27 May 2025 23:11:58 -0400 Subject: [PATCH 2/8] Fix lint. --- .../plugins/publish/validate_asset_docs.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index ac881bdeb9..571ffa5a64 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -57,9 +57,10 @@ class RepairOverrideResolution(RepairAction): label = "Force new shot resolution." def process(self, context, plugin): - entity_hub, entity, shot_data = ValidateFolderCreationResolution.get_shot_data( + values = ValidateFolderCreationResolution.get_shot_data( context.data["hierarchyContext"] ) + entity_hub, entity, shot_data = values for attrib in _RESOLUTION_ATTRIBS: entity.attribs.set(attrib, shot_data[attrib]) @@ -84,7 +85,10 @@ class RepairIgnoreResolution(RepairAction): create_context.save_changes() -class ValidateFolderCreationResolution(pyblish.api.InstancePlugin, AYONPyblishPluginMixin): +class ValidateFolderCreationResolution( + pyblish.api.InstancePlugin, + AYONPyblishPluginMixin + ): """ Validate resolution values before updating an existing folder. """ @@ -110,11 +114,17 @@ class ValidateFolderCreationResolution(pyblish.api.InstancePlugin, AYONPyblishPl child = value.get("children", None) if entity and child: - entity_children = {child.name: child for child in entity.children} + entity_children = { + child.name: child + for child in entity.children + } entity_to_inspect.append((entity_children, child)) # Destination shot already exists return for validation. - elif value.get("folder_type") == "Shot" and entity and not child: + elif ( + value.get("folder_type") == "Shot" + and entity and not child + ): shot_data = value.get("attributes", {}) return entity_hub, entity, shot_data @@ -132,7 +142,10 @@ class ValidateFolderCreationResolution(pyblish.api.InstancePlugin, AYONPyblishPl validation_data = self.get_shot_data(hierarchy_context) if not validation_data: - self.log.info("Destination shot does not exist yet, nothing to validate.") + self.log.info( + "Destination shot does not exist yet, " + "nothing to validate." + ) return values = self.get_attr_values_from_data(instance.data) @@ -147,8 +160,8 @@ class ValidateFolderCreationResolution(pyblish.api.InstancePlugin, AYONPyblishPl if shot_value and shot_value != entity_value: raise PublishValidationError( "Resolution mismatch for shot." - f"{resolution_attrib}={shot_value} but already existing " - f"shot is set to {entity_value}." + f"{resolution_attrib}={shot_value} but " + f"already existing shot is set to {entity_value}." ) # If update existing shot is disabled, remove any resolution attribs. From 3581a7e0bcfba7b5efb1af07255e51a19e1e1e22 Mon Sep 17 00:00:00 2001 From: Robin De Lillo Date: Wed, 28 May 2025 16:41:15 -0400 Subject: [PATCH 3/8] Apply suggestions from code review Co-authored-by: Roy Nieterau --- client/ayon_core/plugins/publish/validate_asset_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index 571ffa5a64..8f0ee2d981 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -185,6 +185,6 @@ class ValidateFolderCreationResolution( BoolDef( "updateExistingFolderResolution", default=True, - label="Update existing shot resolution.", + label="Update existing shot resolution", ), ] From 92457a9b5d3ff372b7eda34f3c09486847a6d3a7 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 28 May 2025 16:53:00 -0400 Subject: [PATCH 4/8] Ajust from PR feedback. --- .../ayon_core/plugins/publish/validate_asset_docs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index 8f0ee2d981..26f8c7bc19 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -115,8 +115,8 @@ class ValidateFolderCreationResolution( if entity and child: entity_children = { - child.name: child - for child in entity.children + chld.name: chld + for chld in entity.children } entity_to_inspect.append((entity_children, child)) @@ -137,12 +137,12 @@ class ValidateFolderCreationResolution( hierarchy_context = instance.context.data["hierarchyContext"] except KeyError: - self.log.info("No hierarchy context defined for shot instance.") + self.log.debug("No hierarchy context defined for shot instance.") return validation_data = self.get_shot_data(hierarchy_context) if not validation_data: - self.log.info( + self.log.debug( "Destination shot does not exist yet, " "nothing to validate." ) @@ -169,7 +169,7 @@ class ValidateFolderCreationResolution( for resolution_attrib in _RESOLUTION_ATTRIBS: shot_data.pop(resolution_attrib, None) - self.log.info( + self.log.debug( "Ignore existing shot resolution validation " "(update is disabled)." ) @@ -178,7 +178,7 @@ class ValidateFolderCreationResolution( def get_attr_defs_for_instance( cls, create_context, instance, ): - if instance.product_type not in cls.families: + if not cls.instance_matches_plugin_families(instance): return [] return [ From 5f1231e331ddfbc4e1212fd045893a4bf876def2 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 28 May 2025 17:34:02 -0400 Subject: [PATCH 5/8] Address feedback from PR. --- .../plugins/publish/validate_asset_docs.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index 26f8c7bc19..f3d7c056ac 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -154,15 +154,29 @@ class ValidateFolderCreationResolution( # Validate existing shot resolution is matching new one # ask for confirmation instead of blind update, this prevents mistakes. if values.get("updateExistingFolderResolution", True): + similar = True for resolution_attrib in _RESOLUTION_ATTRIBS: shot_value = shot_data.get(resolution_attrib) entity_value = entity.attribs.get(resolution_attrib) if shot_value and shot_value != entity_value: - raise PublishValidationError( + self.log.warning( "Resolution mismatch for shot." f"{resolution_attrib}={shot_value} but " f"already existing shot is set to {entity_value}." ) + similar = False + + if not similar: + resolution_data = { + key: value + for key, value in shot_data.items() + if key in _RESOLUTION_ATTRIBS + } + raise PublishValidationError( + "Resolution mismatch for " + f"shot: {resolution_data} does not " + "correspond to existing entity." + ) # If update existing shot is disabled, remove any resolution attribs. else: From 588371d1c4a7db973acc6994fc6f2627718c3ac5 Mon Sep 17 00:00:00 2001 From: Robin De Lillo Date: Mon, 2 Jun 2025 15:21:32 -0400 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: Roy Nieterau --- client/ayon_core/plugins/publish/validate_asset_docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index f3d7c056ac..75347b0544 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -160,9 +160,9 @@ class ValidateFolderCreationResolution( entity_value = entity.attribs.get(resolution_attrib) if shot_value and shot_value != entity_value: self.log.warning( - "Resolution mismatch for shot." + "Resolution mismatch for shot. " f"{resolution_attrib}={shot_value} but " - f"already existing shot is set to {entity_value}." + f" existing shot is set to {entity_value}." ) similar = False From 93f4e8992729635df71d86bc659d89dfae9f736e Mon Sep 17 00:00:00 2001 From: Robin De Lillo Date: Mon, 9 Jun 2025 11:25:41 -0400 Subject: [PATCH 7/8] Update client/ayon_core/plugins/publish/validate_asset_docs.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/validate_asset_docs.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index 75347b0544..89568f2fea 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -133,10 +133,8 @@ class ValidateFolderCreationResolution( def process(self, instance): """ Validate existing shot resolution. """ - try: - hierarchy_context = instance.context.data["hierarchyContext"] - - except KeyError: + hierarchy_context = instance.context.data.get("hierarchyContext") + if not hierarchy_context: self.log.debug("No hierarchy context defined for shot instance.") return From e2650ad403afc7e1ce1bd253310ccd63d22a1b31 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Mon, 9 Jun 2025 13:24:00 -0400 Subject: [PATCH 8/8] Adjust from PR feedback. --- .../plugins/publish/validate_asset_docs.py | 145 +++++++----------- 1 file changed, 56 insertions(+), 89 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_asset_docs.py b/client/ayon_core/plugins/publish/validate_asset_docs.py index 89568f2fea..25c9bf5fe9 100644 --- a/client/ayon_core/plugins/publish/validate_asset_docs.py +++ b/client/ayon_core/plugins/publish/validate_asset_docs.py @@ -51,27 +51,10 @@ class ValidateFolderEntities(pyblish.api.InstancePlugin): ).format(instance.data["name"])) -class RepairOverrideResolution(RepairAction): - """ Repair, force new resolution onto existing shot. - """ - label = "Force new shot resolution." - - def process(self, context, plugin): - values = ValidateFolderCreationResolution.get_shot_data( - context.data["hierarchyContext"] - ) - entity_hub, entity, shot_data = values - - for attrib in _RESOLUTION_ATTRIBS: - entity.attribs.set(attrib, shot_data[attrib]) - - entity_hub.commit_changes() - - class RepairIgnoreResolution(RepairAction): """ Repair, disable resolution update in problematic instance(s). """ - label = "Do not update resolution." + label = "Skip folder resolution validation." def process(self, context, plugin): create_context = context.data["create_context"] @@ -80,7 +63,7 @@ class RepairIgnoreResolution(RepairAction): created_instance = create_context.get_instance_by_id(instance_id) attr_values = created_instance.data["publish_attributes"].get( "ValidateFolderCreationResolution", {}) - attr_values["updateExistingFolderResolution"] = False + attr_values["validateExistingFolderResolution"] = False create_context.save_changes() @@ -95,11 +78,10 @@ class ValidateFolderCreationResolution( label = "Validate new folder resolution" order = pyblish.api.ValidatorOrder families = ["shot"] - actions = [RepairIgnoreResolution, RepairOverrideResolution] + actions = [RepairIgnoreResolution] - @classmethod - def get_shot_data(self, hierarchy_context): - """ Retrieve matching entity and shot_data from hierarchy_context. + def _validate_hierarchy_resolution(self, hierarchy_context): + """ Deep validation of hierarchy_context resolution. """ project_name = tuple(hierarchy_context.keys())[0] entity_hub = EntityHub(project_name) @@ -113,78 +95,63 @@ class ValidateFolderCreationResolution( entity = entity_data.get(name) child = value.get("children", None) - if entity and child: - entity_children = { - chld.name: chld - for chld in entity.children - } - entity_to_inspect.append((entity_children, child)) + # entity exists in AYON. + if entity: + folder_data = value.get("attributes", {}) + self._validate_folder_resolution( + folder_data, + entity, + ) - # Destination shot already exists return for validation. - elif ( - value.get("folder_type") == "Shot" - and entity and not child - ): - shot_data = value.get("attributes", {}) - return entity_hub, entity, shot_data + if child: + entity_children = { + chld.name: chld + for chld in entity.children + } + entity_to_inspect.append((entity_children, child)) - return None + def _validate_folder_resolution(self, folder_data, entity): + """ Validate folder resolution against existing data. + """ + similar = True + for resolution_attrib in _RESOLUTION_ATTRIBS: + folder_value = folder_data.get(resolution_attrib) + entity_value = entity.attribs.get(resolution_attrib) + if folder_value and folder_value != entity_value: + self.log.warning( + f"Resolution mismatch for folder {entity.name}. " + f"{resolution_attrib}={folder_value} but " + f" existing entity is set to {entity_value}." + ) + similar = False + + if not similar: + resolution_data = { + key: value + for key, value in folder_data.items() + if key in _RESOLUTION_ATTRIBS + } + raise PublishValidationError( + "Resolution mismatch for " + f"folder {entity.name} (type: {entity.folder_type}) " + f"{resolution_data} does not " + "correspond to existing entity." + ) def process(self, instance): - """ Validate existing shot resolution. + """ Validate existing folder resolution. """ + values = self.get_attr_values_from_data(instance.data) + if not values.get("validateExistingFolderResolution", False): + self.log.debug("Skip existing folder(s) resolution validation ") + return + hierarchy_context = instance.context.data.get("hierarchyContext") if not hierarchy_context: - self.log.debug("No hierarchy context defined for shot instance.") + self.log.debug("No hierarchy context defined for instance.") return - validation_data = self.get_shot_data(hierarchy_context) - if not validation_data: - self.log.debug( - "Destination shot does not exist yet, " - "nothing to validate." - ) - return - - values = self.get_attr_values_from_data(instance.data) - _, entity, shot_data = validation_data - - # Validate existing shot resolution is matching new one - # ask for confirmation instead of blind update, this prevents mistakes. - if values.get("updateExistingFolderResolution", True): - similar = True - for resolution_attrib in _RESOLUTION_ATTRIBS: - shot_value = shot_data.get(resolution_attrib) - entity_value = entity.attribs.get(resolution_attrib) - if shot_value and shot_value != entity_value: - self.log.warning( - "Resolution mismatch for shot. " - f"{resolution_attrib}={shot_value} but " - f" existing shot is set to {entity_value}." - ) - similar = False - - if not similar: - resolution_data = { - key: value - for key, value in shot_data.items() - if key in _RESOLUTION_ATTRIBS - } - raise PublishValidationError( - "Resolution mismatch for " - f"shot: {resolution_data} does not " - "correspond to existing entity." - ) - - # If update existing shot is disabled, remove any resolution attribs. - else: - for resolution_attrib in _RESOLUTION_ATTRIBS: - shot_data.pop(resolution_attrib, None) - - self.log.debug( - "Ignore existing shot resolution validation " - "(update is disabled)." - ) + self._validate_hierarchy_resolution(hierarchy_context) @classmethod def get_attr_defs_for_instance( @@ -195,8 +162,8 @@ class ValidateFolderCreationResolution( return [ BoolDef( - "updateExistingFolderResolution", - default=True, - label="Update existing shot resolution", + "validateExistingFolderResolution", + default=False, + label="Validate existing folders resolution", ), ]