From c60bd1cb2df44735ed1ce719f88449928f8d4e4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:56:29 +0100 Subject: [PATCH 1/7] Fusion: Add Validate Instance in Context validator --- client/ayon_core/hosts/fusion/api/action.py | 52 ++++++++++++ .../publish/validate_instance_in_context.py | 80 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py diff --git a/client/ayon_core/hosts/fusion/api/action.py b/client/ayon_core/hosts/fusion/api/action.py index 1643f1ce03..a0c6aafcb5 100644 --- a/client/ayon_core/hosts/fusion/api/action.py +++ b/client/ayon_core/hosts/fusion/api/action.py @@ -58,3 +58,55 @@ class SelectInvalidAction(pyblish.api.Action): self.log.info( "Selecting invalid tools: %s" % ", ".join(sorted(names)) ) + + +class SelectToolAction(pyblish.api.Action): + """Select invalid output tool in Fusion when plug-in failed. + + """ + + label = "Select saver" + on = "failed" # This action is only available on a failed plug-in + icon = "search" # Icon from Awesome Icon + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context( + context, + plugin=plugin, + ) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes..") + tools = [] + for instance in errored_instances: + + tool = instance.data.get("tool") + if tool is not None: + tools.append(tool) + else: + self.log.warning( + "Plug-in returned to be invalid, " + f"but has no saver for instance {instance.name}." + ) + + if not tools: + # Assume relevant comp is current comp and clear selection + self.log.info("No invalid tools found.") + comp = get_current_comp() + flow = comp.CurrentFrame.FlowView + flow.Select() # No args equals clearing selection + return + + # Assume a single comp + first_tool = tools[0] + comp = first_tool.Comp() + flow = comp.CurrentFrame.FlowView + flow.Select() # No args equals clearing selection + names = set() + for tool in tools: + flow.Select(tool, True) + comp.SetActiveTool(tool) + names.add(tool.Name) + self.log.info( + "Selecting invalid tools: %s" % ", ".join(sorted(names)) + ) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py new file mode 100644 index 0000000000..3aa6fb452f --- /dev/null +++ b/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +"""Validate if instance context is the same as publish context.""" + +import pyblish.api +from ayon_core.hosts.fusion.api.action import SelectToolAction +from ayon_core.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin +) + + +class ValidateInstanceInContextFusion(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validator to check if instance context matches context of publish. + + When working in per-shot style you always publish data in context of + current asset (shot). This validator checks if this is so. It is optional + so it can be disabled when needed. + """ + # Similar to maya and houdini-equivalent `ValidateInstanceInContext` + + order = ValidateContentsOrder + label = "Instance in same Context" + optional = True + hosts = ["fusion"] + actions = [SelectToolAction, RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + + instance_context = self.get_context(instance.data) + context = self.get_context(instance.context.data) + if instance_context != context: + context_label = "{} > {}".format(*context) + instance_label = "{} > {}".format(*instance_context) + + raise PublishValidationError( + message=( + "Instance '{}' publishes to different asset than current " + "context: {}. Current context: {}".format( + instance.name, instance_label, context_label + ) + ), + description=( + "## Publishing to a different asset\n" + "There are publish instances present which are publishing " + "into a different asset than your current context.\n\n" + "Usually this is not what you want but there can be cases " + "where you might want to publish into another asset or " + "shot. If that's the case you can disable the validation " + "on the instance to ignore it." + ) + ) + + @classmethod + def repair(cls, instance): + + create_context = instance.context.data["create_context"] + instance_id = instance.data.get("instance_id") + created_instance = create_context.get_instance_by_id( + instance_id + ) + if created_instance is None: + raise RuntimeError( + f"No CreatedInstances found with id '{instance_id} " + f"in {create_context.instances_by_id}" + ) + + context_asset, context_task = cls.get_context(instance.context.data) + created_instance["folderPath"] = context_asset + created_instance["task"] = context_task + create_context.save_changes() + + @staticmethod + def get_context(data): + """Return asset, task from publishing context data""" + return data["folderPath"], data["task"] From 7d9ff383096a262bb03b66009802945c974dad8a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:57:02 +0100 Subject: [PATCH 2/7] Correctly preserve the instance's task instead of forcing current context task --- client/ayon_core/hosts/fusion/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index 36102d02cb..b1ecce728b 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -53,7 +53,7 @@ class CollectFusionRender( if product_type not in ["render", "image"]: continue - task_name = context.data["task"] + task_name = inst.data["task"] tool = inst.data["transientData"]["tool"] instance_families = inst.data.get("families", []) From 4c51f3a560109fa75c50f4104e609f93b4c8f28b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 17:44:31 +0100 Subject: [PATCH 3/7] Implement fix for losing instance id and disconnected logs in publisher UI --- .../fusion/plugins/publish/collect_render.py | 7 +++++++ .../publish/abstract_collect_render.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index b1ecce728b..3f5e2837bc 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -119,6 +119,13 @@ class CollectFusionRender( instances.append(instance) instances_to_remove.append(inst) + # TODO: Avoid this transfer instance id hack + # pass on the `id` of the original instance so any artist + # facing logs transfer as if they were made on the new instance + # instead, see `AbstractCollectRender.process()` + instance.id = inst.id + instance.instance_id = inst.data.get("instance_id") + for instance in instances_to_remove: context.remove(instance) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 745632ca0a..8b98cb678e 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -215,6 +215,25 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): render_instance_dict = attr.asdict(render_instance) instance = context.create_instance(render_instance.name) + + # TODO: Avoid this transfer instance id hack + # Transfer the id from another instance, e.g. when the render + # instance is intended to "replace" an existing instance like + # fusion does in `CollectRender`. Without matching the ids any + # logs produced for the instance prior to the "replacement" will + # not show artist-facing logs in reports + transfer_id = getattr(render_instance, "id") + if transfer_id: + instance._id = transfer_id + # The `instance_id` data may be overridden on the Creator + # to e.g. maybe make unique by node name instead of uuid, + # like in Maya, Fusion, Houdini integration. + # This transfers that unique (named) instance id. + # This transfer logic is currently (only?) used in Fusion. + transfer_instance_id = getattr(render_instance, "instance_id") + if transfer_instance_id: + instance.data["instance_id"] = transfer_instance_id + instance.data["label"] = render_instance.label instance.data.update(render_instance_dict) instance.data.update(data) From 4d737790de9d598f7a5b88e7e20b6ca6e738b444 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 May 2024 21:04:54 +0100 Subject: [PATCH 4/7] Working version --- client/ayon_core/hosts/nuke/api/plugin.py | 9 +++++++-- .../nuke/plugins/publish/extract_review_intermediates.py | 9 +++++++-- server_addon/nuke/package.py | 2 +- server_addon/nuke/server/settings/publish_plugins.py | 3 +++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/nuke/api/plugin.py b/client/ayon_core/hosts/nuke/api/plugin.py index fb56dec833..ec256ea303 100644 --- a/client/ayon_core/hosts/nuke/api/plugin.py +++ b/client/ayon_core/hosts/nuke/api/plugin.py @@ -834,7 +834,7 @@ class ExporterReviewMov(ExporterReview): self.log.info("Nodes exported...") return path - def generate_mov(self, farm=False, **kwargs): + def generate_mov(self, farm=False, delete=True, **kwargs): # colorspace data colorspace = None # get colorspace settings @@ -987,8 +987,13 @@ class ExporterReviewMov(ExporterReview): self.render(write_node.name()) # ---------- generate representation data + tags = ["review", "need_thumbnail"] + + if delete: + tags.append("delete") + self.get_representation_data( - tags=["review", "need_thumbnail", "delete"] + add_tags, + tags=tags + add_tags, custom_tags=add_custom_tags, range=True, colorspace=colorspace diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py b/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py index 8d7a3ec311..82c7b6e4c5 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py @@ -136,11 +136,16 @@ class ExtractReviewIntermediates(publish.Extractor): self, instance, o_name, o_data["extension"], multiple_presets) + o_data["add_custom_tags"].append("intermediate") + delete = not o_data.get("publish", False) + if instance.data.get("farm"): if "review" in instance.data["families"]: instance.data["families"].remove("review") - data = exporter.generate_mov(farm=True, **o_data) + data = exporter.generate_mov( + farm=True, delete=delete, **o_data + ) self.log.debug( "_ data: {}".format(data)) @@ -154,7 +159,7 @@ class ExtractReviewIntermediates(publish.Extractor): "bakeWriteNodeName": data.get("bakeWriteNodeName") }) else: - data = exporter.generate_mov(**o_data) + data = exporter.generate_mov(delete=delete, **o_data) # add representation generated by exporter generated_repres.extend(data["representations"]) diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py index bf03c4e7e7..e522b9fb5d 100644 --- a/server_addon/nuke/package.py +++ b/server_addon/nuke/package.py @@ -1,3 +1,3 @@ name = "nuke" title = "Nuke" -version = "0.1.11" +version = "0.1.12" diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index d5b05d8715..e67f7be24f 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -125,6 +125,7 @@ class ReformatNodesConfigModel(BaseSettingsModel): class IntermediateOutputModel(BaseSettingsModel): name: str = SettingsField(title="Output name") + publish: bool = SettingsField(title="Publish") filter: BakingStreamFilterModel = SettingsField( title="Filter", default_factory=BakingStreamFilterModel) read_raw: bool = SettingsField( @@ -346,6 +347,7 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "outputs": [ { "name": "baking", + "publish": False, "filter": { "task_types": [], "product_types": [], @@ -401,6 +403,7 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "outputs": [ { "name": "baking", + "publish": False, "filter": { "task_types": [], "product_types": [], From 340c07317f32bbfa94b865189cb554a36bb17465 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 May 2024 19:38:26 +0800 Subject: [PATCH 5/7] make validator animated reference being optional --- .../hosts/maya/plugins/publish/validate_animated_reference.py | 2 +- server_addon/maya/server/settings/publishers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py index 2ba2bff6fc..4e8261d42e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py @@ -18,7 +18,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, label = "Animated Reference Rig" accepted_controllers = ["transform", "locator"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] - optional = False + optional = True def process(self, instance): if not self.is_active(instance.data): diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 20523b2ca9..3e8dc704b7 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1448,8 +1448,8 @@ DEFAULT_PUBLISH_SETTINGS = { "active": True }, "ValidateAnimatedReferenceRig": { - "enabled": True, - "optional": False, + "enabled": False, + "optional": True, "active": True }, "ValidateAnimationContent": { From ce194b32febe009e2eec969c2fba77fb8238a319 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 May 2024 20:25:03 +0800 Subject: [PATCH 6/7] remove the validator --- .../publish/validate_animated_reference.py | 71 ------------------- .../maya/server/settings/publishers.py | 10 +-- 2 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py deleted file mode 100644 index 4e8261d42e..0000000000 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ /dev/null @@ -1,71 +0,0 @@ -import pyblish.api -import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import ( - PublishValidationError, - ValidateContentsOrder, - OptionalPyblishPluginMixin -) -from maya import cmds - - -class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validate all nodes in skeletonAnim_SET are referenced""" - - order = ValidateContentsOrder - hosts = ["maya"] - families = ["animation.fbx"] - label = "Animated Reference Rig" - accepted_controllers = ["transform", "locator"] - actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - animated_sets = instance.data.get("animated_skeleton", []) - if not animated_sets: - self.log.debug( - "No nodes found in skeletonAnim_SET. " - "Skipping validation of animated reference rig..." - ) - return - - for animated_reference in animated_sets: - is_referenced = cmds.referenceQuery( - animated_reference, isNodeReferenced=True) - if not bool(is_referenced): - raise PublishValidationError( - "All the content in skeletonAnim_SET" - " should be referenced nodes" - ) - invalid_controls = self.validate_controls(animated_sets) - if invalid_controls: - raise PublishValidationError( - "All the content in skeletonAnim_SET" - " should be transforms" - ) - - @classmethod - def validate_controls(self, set_members): - """Check if the controller set contains only accepted node types. - - Checks if all its set members are within the hierarchy of the root - Checks if the node types of the set members valid - - Args: - set_members: list of nodes of the skeleton_anim_set - hierarchy: list of nodes which reside under the root node - - Returns: - errors (list) - """ - - # Validate control types - invalid = [] - set_members = cmds.ls(set_members, long=True) - for node in set_members: - if cmds.nodeType(node) not in self.accepted_controllers: - invalid.append(node) - - return invalid diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3e8dc704b7..3ff57bab13 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -921,10 +921,7 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Animated Reference Rig", ) - ValidateAnimationContent: BasicValidateModel = SettingsField( - default_factory=BasicValidateModel, - title="Validate Animation Content", - ) + ValidateOutRelatedNodeIds: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animation Out Set Related Node Ids", @@ -1447,11 +1444,6 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "active": True }, - "ValidateAnimatedReferenceRig": { - "enabled": False, - "optional": True, - "active": True - }, "ValidateAnimationContent": { "enabled": True, "optional": False, From cd0bf07939a557744a2bc94d5f67479282b5099c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 May 2024 21:11:32 +0800 Subject: [PATCH 7/7] upversion --- server_addon/maya/package.py | 2 +- server_addon/maya/server/settings/publishers.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py index fe3e3039f5..5ab2fa217c 100644 --- a/server_addon/maya/package.py +++ b/server_addon/maya/package.py @@ -1,3 +1,3 @@ name = "maya" title = "Maya" -version = "0.1.18" +version = "0.1.19" diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3ff57bab13..01ac6f4acd 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -917,11 +917,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Rig Controllers", ) - ValidateAnimatedReferenceRig: BasicValidateModel = SettingsField( + ValidateAnimationContent: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, - title="Validate Animated Reference Rig", + title="Validate Animation Content", ) - ValidateOutRelatedNodeIds: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animation Out Set Related Node Ids",