From 0dc3e17273ae5a4f8645f4cfdd3392fd720c9ce7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Feb 2025 02:46:30 +0100 Subject: [PATCH 01/11] Implement draft for AttachReviewables integrator --- .../publish/integrate_attach_reviewable.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 client/ayon_core/plugins/publish/integrate_attach_reviewable.py diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py new file mode 100644 index 0000000000..0457e2ad1b --- /dev/null +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -0,0 +1,95 @@ +import copy +import pyblish.api +from typing import List + +from ayon_core.lib import EnumDef +from ayon_core.pipeline import OptionalPyblishPluginMixin + + +class AttachReviewables(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Attach reviewable to other instances""" + + families = ["render", "review"] + order = pyblish.api.IntegratorOrder - 0.499 + label = "Attach reviewables" + + def process(self, instance): + # TODO: Support farm. + # If instance is being submitted to the farm we should pass through + # the 'attached reviewables' metadata to the farm job + # TODO: Reviewable frame range and resolutions + # Because we are attaching the data to another instance, how do we + # correctly propagate the resolution + frame rate to the other + # instance? Do we even need to? + # TODO: If this were to attach 'renders' to another instance that would + # mean there wouldn't necessarily be a render publish separate as a + # result. Is that correct expected behavior? + attr_values = self.get_attr_values_from_data(instance.data) + attach_to = attr_values.get("attach", []) + if not attach_to: + self.log.debug( + "Reviewable is not set to attach to another instance.") + return + + attach_instances: List[pyblish.api.Instance] = [] + for attach_instance_id in attach_to: + # Find the `pyblish.api.Instance` matching the `CreatedInstance.id` + # in the `attach_to` list + attach_instance = next(( + _inst for _inst in instance.context + if _inst.data.get("instance_id") == attach_instance_id + ), None) + if not attach_instance: + continue + + # Skip inactive instances + if not attach_instance.data.get("active", True): + continue + + attach_instances.append(attach_instance) + + self.log.debug( + f"Attaching reviewable to other instances: {attach_instances}") + + # Copy the representations of this reviewable instance to the other + # instance + representations = instance.data.get("representations", []) + for attach_instance in attach_instances: + self.log.info(f"Attaching to {attach_instance.name}") + attach_instance.data.setdefault("representations", []).extend( + copy.deepcopy(representations) + ) + + # Delete representations on the reviewable instance itself + for repre in representations: + self.log.debug( + "Marking representation as deleted because it was " + f"attached to other instances instead: {repre}") + repre.setdefault("tags", []).append("delete") + + @classmethod + def get_attr_defs_for_instance(cls, create_context, instance): + # TODO: Check if instance is actually a 'reviewable' + # Filtering of instance, if needed, can be customized + if not cls.instance_matches_plugin_families(instance): + return [] + + items = [] + for other_instance in create_context.instances: + if other_instance == instance: + continue + items.append({ + "label": other_instance.label, + "value": str(other_instance.id) + }) + + return [ + EnumDef( + "attach", + label="Attach reviewable", + multiselection=True, + items=items, + tooltip="Attach this reviewable to another instance", + ) + ] \ No newline at end of file From 0659bead0d2d65e254792e0aafc8e462f95f81b3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Feb 2025 08:52:23 +0100 Subject: [PATCH 02/11] Cosmetics --- client/ayon_core/plugins/publish/integrate_attach_reviewable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 0457e2ad1b..f3e7f38493 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -92,4 +92,4 @@ class AttachReviewables(pyblish.api.InstancePlugin, items=items, tooltip="Attach this reviewable to another instance", ) - ] \ No newline at end of file + ] From 72fdcf9c660044e4ee0e8b991e60d55d4ca32647 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Feb 2025 08:54:55 +0100 Subject: [PATCH 03/11] Improve docstring --- .../plugins/publish/integrate_attach_reviewable.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index f3e7f38493..8142b4f947 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -8,7 +8,18 @@ from ayon_core.pipeline import OptionalPyblishPluginMixin class AttachReviewables(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Attach reviewable to other instances""" + """Attach reviewable to other instances + + This pre-integrator plugin allows instances to be 'attached to' other + instances by moving all its representations over to the other instance. + Even though this technically could work for any representation the current + intent is to use for reviewables only, like e.g. `review` or `render` + product type. + + When the reviewable is attached to another instance, the instance itself + will not be published as a separate entity. Instead, the representations + will be copied/moved to the instances it is attached to. + """ families = ["render", "review"] order = pyblish.api.IntegratorOrder - 0.499 From bfd4a986fb51bf9437c537dc51e0bb1e1b0ef391 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Feb 2025 09:05:23 +0100 Subject: [PATCH 04/11] Reformat using ruff --- .../publish/integrate_attach_reviewable.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 8142b4f947..4c4bc3d987 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -6,8 +6,9 @@ from ayon_core.lib import EnumDef from ayon_core.pipeline import OptionalPyblishPluginMixin -class AttachReviewables(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): +class AttachReviewables( + pyblish.api.InstancePlugin, OptionalPyblishPluginMixin +): """Attach reviewable to other instances This pre-integrator plugin allows instances to be 'attached to' other @@ -40,17 +41,22 @@ class AttachReviewables(pyblish.api.InstancePlugin, attach_to = attr_values.get("attach", []) if not attach_to: self.log.debug( - "Reviewable is not set to attach to another instance.") + "Reviewable is not set to attach to another instance." + ) return attach_instances: List[pyblish.api.Instance] = [] for attach_instance_id in attach_to: # Find the `pyblish.api.Instance` matching the `CreatedInstance.id` # in the `attach_to` list - attach_instance = next(( - _inst for _inst in instance.context - if _inst.data.get("instance_id") == attach_instance_id - ), None) + attach_instance = next( + ( + _inst + for _inst in instance.context + if _inst.data.get("instance_id") == attach_instance_id + ), + None, + ) if not attach_instance: continue @@ -61,7 +67,8 @@ class AttachReviewables(pyblish.api.InstancePlugin, attach_instances.append(attach_instance) self.log.debug( - f"Attaching reviewable to other instances: {attach_instances}") + f"Attaching reviewable to other instances: {attach_instances}" + ) # Copy the representations of this reviewable instance to the other # instance @@ -76,7 +83,8 @@ class AttachReviewables(pyblish.api.InstancePlugin, for repre in representations: self.log.debug( "Marking representation as deleted because it was " - f"attached to other instances instead: {repre}") + f"attached to other instances instead: {repre}" + ) repre.setdefault("tags", []).append("delete") @classmethod @@ -90,10 +98,12 @@ class AttachReviewables(pyblish.api.InstancePlugin, for other_instance in create_context.instances: if other_instance == instance: continue - items.append({ - "label": other_instance.label, - "value": str(other_instance.id) - }) + items.append( + { + "label": other_instance.label, + "value": str(other_instance.id), + } + ) return [ EnumDef( From 5bb09d525657f7c03f034fb0dc060f87d9450a4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Mar 2025 17:39:19 +0100 Subject: [PATCH 05/11] Fix check whether instance was found (it may be falsey if empty instance list) --- client/ayon_core/plugins/publish/integrate_attach_reviewable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 4c4bc3d987..92ff1c6cfd 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -57,7 +57,7 @@ class AttachReviewables( ), None, ) - if not attach_instance: + if attach_instance is None: continue # Skip inactive instances From d887a21bf9d6906f202cb86d14d22718268a591c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Mar 2025 17:39:41 +0100 Subject: [PATCH 06/11] Disallow attaching reviewables to reviewables for now --- .../ayon_core/plugins/publish/integrate_attach_reviewable.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 92ff1c6cfd..1127b45ba2 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -98,6 +98,11 @@ class AttachReviewables( for other_instance in create_context.instances: if other_instance == instance: continue + + # Do not allow attaching to other reviewable instances + if other_instance.data["productType"] in cls.families: + continue + items.append( { "label": other_instance.label, From cb2c1b0329e100208db0b3249c5170808e4d9137 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Mar 2025 17:40:07 +0100 Subject: [PATCH 07/11] Set `integrate` to false on the representation to avoid warnings logged by the Integrator --- .../ayon_core/plugins/publish/integrate_attach_reviewable.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 1127b45ba2..dc8b87fa99 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -87,6 +87,10 @@ class AttachReviewables( ) repre.setdefault("tags", []).append("delete") + # Stop integrator from trying to integrate this instance + if attach_to: + instance.data["integrate"] = False + @classmethod def get_attr_defs_for_instance(cls, create_context, instance): # TODO: Check if instance is actually a 'reviewable' From e388b42ad8f3b4055afd0a3ba9ca9c701cd4917d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Mar 2025 17:40:28 +0100 Subject: [PATCH 08/11] Turn log to info because it makes it clearer what is going on --- client/ayon_core/plugins/publish/integrate_attach_reviewable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index dc8b87fa99..41a510a2d3 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -66,7 +66,7 @@ class AttachReviewables( attach_instances.append(attach_instance) - self.log.debug( + self.log.info( f"Attaching reviewable to other instances: {attach_instances}" ) From 2a3f9b743b4efb89870a7ea18e7e0042a84792d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Mar 2025 17:55:22 +0100 Subject: [PATCH 09/11] Log instance names instead of the objects --- .../ayon_core/plugins/publish/integrate_attach_reviewable.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 41a510a2d3..63a7699cb0 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -66,8 +66,11 @@ class AttachReviewables( attach_instances.append(attach_instance) + instances_names = ", ".join( + instance.name for instance in attach_instances + ) self.log.info( - f"Attaching reviewable to other instances: {attach_instances}" + f"Attaching reviewable to other instances: {instances_names}" ) # Copy the representations of this reviewable instance to the other From 597b91c1356ee718df603317c08686ce34e65b07 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Mar 2025 17:57:51 +0100 Subject: [PATCH 10/11] For now disallow attaching to 'farm' instances --- .../plugins/publish/integrate_attach_reviewable.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 63a7699cb0..74a8cd3ee9 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -64,6 +64,14 @@ class AttachReviewables( if not attach_instance.data.get("active", True): continue + # For now do not support attaching to 'farm' instances until we + # can pass the 'attaching' on to the farm jobs. + if attach_instance.data.get("farm"): + self.log.warning( + "Attaching to farm instances is not supported yet." + ) + continue + attach_instances.append(attach_instance) instances_names = ", ".join( From ce9970636f71601835459af5f1990b5d69fcca03 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 26 Mar 2025 17:32:15 +0100 Subject: [PATCH 11/11] Allow enabling/disabling Attach Reviewables feature --- .../publish/integrate_attach_reviewable.py | 2 ++ server/settings/publish_plugins.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py index 74a8cd3ee9..b98d8d28fe 100644 --- a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -26,6 +26,8 @@ class AttachReviewables( order = pyblish.api.IntegratorOrder - 0.499 label = "Attach reviewables" + settings_category = "core" + def process(self, instance): # TODO: Support farm. # If instance is being submitted to the farm we should pass through diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index c9c66e65d9..39a9c028f9 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -12,6 +12,10 @@ from ayon_server.settings import ( from ayon_server.types import ColorRGBA_uint8 +class EnabledModel(BaseSettingsModel): + enabled: bool = SettingsField(True) + + class ValidateBaseModel(BaseSettingsModel): _isGroup = True enabled: bool = SettingsField(True) @@ -1026,6 +1030,17 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=IntegrateHeroVersionModel, title="Integrate Hero Version" ) + AttachReviewables: EnabledModel = SettingsField( + default_factory=EnabledModel, + title="Attach Reviewables", + description=( + "When enabled, expose an 'Attach Reviewables' attribute on review" + " and render instances in the publisher to allow including the" + " media to be attached to another instance.\n\n" + "If a reviewable is attached to another instance it will not be " + "published as a render/review product of its own." + ) + ) CleanUp: CleanUpModel = SettingsField( default_factory=CleanUpModel, title="Clean Up" @@ -1410,6 +1425,9 @@ DEFAULT_PUBLISH_VALUES = { ], "use_hardlinks": False }, + "AttachReviewables": { + "enabled": True, + }, "CleanUp": { "paterns": [], # codespell:ignore paterns "remove_temp_renders": False