From 53dac8b0a8e1893afc9c1720d6c17ca9a65ec911 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:12:34 +0800 Subject: [PATCH 01/20] maxscript's conversion of bool to python --- openpype/hosts/max/api/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 71a0b94e1f..69a495f5ae 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -172,7 +172,7 @@ class MaxCreator(Creator, MaxCreatorBase): # Setting the property rt.setProperty( instance_node.openPypeData, "all_handles", node_list) - + self.log.debug(f"{instance}") self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) @@ -184,6 +184,7 @@ class MaxCreator(Creator, MaxCreatorBase): created_instance = CreatedInstance.from_existing( read(rt.GetNodeByName(instance)), self ) + self.log.debug(f"{created_instance}") self._add_instance_to_context(created_instance) def update_instances(self, update_list): From 5a228d4d5192825081225cdaef23d9cbf20397bb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:21:37 +0800 Subject: [PATCH 02/20] maxscript's conversion of bool to python --- openpype/hosts/max/api/lib.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 1d53802ecf..c1e67409a2 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -78,7 +78,13 @@ def read(container) -> dict: value.startswith(JSON_PREFIX): with contextlib.suppress(json.JSONDecodeError): value = json.loads(value[len(JSON_PREFIX):]) - data[key.strip()] = value + if key.strip() == "active": + if value == "true": + data[key.strip()] = True + else: + data[key.strip()] = False + else: + data[key.strip()] = value data["instance_node"] = container.Name From eb63b4bae1291006071b3689735abe5e4b1d7829 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:22:35 +0800 Subject: [PATCH 03/20] remove unnecessary debug check --- openpype/hosts/max/api/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 69a495f5ae..08e41df554 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -172,7 +172,6 @@ class MaxCreator(Creator, MaxCreatorBase): # Setting the property rt.setProperty( instance_node.openPypeData, "all_handles", node_list) - self.log.debug(f"{instance}") self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) @@ -184,7 +183,6 @@ class MaxCreator(Creator, MaxCreatorBase): created_instance = CreatedInstance.from_existing( read(rt.GetNodeByName(instance)), self ) - self.log.debug(f"{created_instance}") self._add_instance_to_context(created_instance) def update_instances(self, update_list): From 3c4c922b4f5e66874ccc886ecddff0d02528afce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:23:22 +0800 Subject: [PATCH 04/20] restore the plugin.py --- openpype/hosts/max/api/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 08e41df554..71a0b94e1f 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -172,6 +172,7 @@ class MaxCreator(Creator, MaxCreatorBase): # Setting the property rt.setProperty( instance_node.openPypeData, "all_handles", node_list) + self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) From 04ec40134329694e72d7421e13f60744f824af1a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 15:52:37 +0800 Subject: [PATCH 05/20] roy's comment --- openpype/hosts/max/api/lib.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index c1e67409a2..879f0abfa4 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -78,13 +78,15 @@ def read(container) -> dict: value.startswith(JSON_PREFIX): with contextlib.suppress(json.JSONDecodeError): value = json.loads(value[len(JSON_PREFIX):]) - if key.strip() == "active": - if value == "true": - data[key.strip()] = True - else: - data[key.strip()] = False - else: - data[key.strip()] = value + + # default value behavior + # convert maxscript boolean values + if value == "true": + value = True + elif value == "false": + value = False + + data[key.strip()] = value data["instance_node"] = container.Name From a0fb1d49cad2da140e3b3ff9a791b37bdabaa199 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 3 Jul 2023 17:36:48 +0300 Subject: [PATCH 06/20] add select invalid action --- .../publish/validate_sop_output_node.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index ed7f438729..74f45c0925 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -1,7 +1,13 @@ # -*- coding: utf-8 -*- import pyblish.api from openpype.pipeline import PublishValidationError +from openpype.pipeline.publish import RepairAction +import hou + +class SelectInvalidAction(RepairAction): + label = "Select Invalid ROP" + icon = "mdi.cursor-default-click" class ValidateSopOutputNode(pyblish.api.InstancePlugin): """Validate the instance SOP Output Node. @@ -19,6 +25,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" + actions = [SelectInvalidAction] def process(self, instance): @@ -31,9 +38,6 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - - import hou - output_node = instance.data.get("output_node") if output_node is None: @@ -81,3 +85,19 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): "Output node `%s` has no geometry data." % output_node.path() ) return [output_node.path()] + + @classmethod + def repair(cls, instance): + """Select Invalid ROP. + + It's used to select invalid ROP which tells the + artist which ROP node need to be fixed! + """ + + rop_node = hou.node(instance.data["instance_node"]) + rop_node.setSelected(True, clear_all_selected=True) + + cls.log.debug( + '%s has been selected' + % rop_node.path() + ) From 6983503468b03490e53089e71a45d86780dfc5a6 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 3 Jul 2023 18:16:23 +0300 Subject: [PATCH 07/20] fix lint problem --- .../hosts/houdini/plugins/publish/validate_sop_output_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index 74f45c0925..e8fb11a51c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -5,6 +5,7 @@ from openpype.pipeline.publish import RepairAction import hou + class SelectInvalidAction(RepairAction): label = "Select Invalid ROP" icon = "mdi.cursor-default-click" From 73ecfe88e8773f5178d72785ac1771b83dde3b7f Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 13:27:31 +0300 Subject: [PATCH 08/20] change action name to Select ROP --- .../plugins/publish/validate_sop_output_node.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index e8fb11a51c..0d2aa64df6 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -6,8 +6,8 @@ from openpype.pipeline.publish import RepairAction import hou -class SelectInvalidAction(RepairAction): - label = "Select Invalid ROP" +class SelectROPAction(RepairAction): + label = "Select ROP" icon = "mdi.cursor-default-click" class ValidateSopOutputNode(pyblish.api.InstancePlugin): @@ -26,7 +26,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" - actions = [SelectInvalidAction] + actions = [SelectROPAction] def process(self, instance): @@ -89,10 +89,10 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - """Select Invalid ROP. + """Select ROP. - It's used to select invalid ROP which tells the - artist which ROP node need to be fixed! + It's used to select the associated ROP for the selected instance + which tells the artist which ROP node need to be fixed! """ rop_node = hou.node(instance.data["instance_node"]) From 379cf0f76976843d966e4739d1fa6446d0524774 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 13:29:24 +0300 Subject: [PATCH 09/20] add SelectInvalidAction --- .../plugins/publish/validate_sop_output_node.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index 0d2aa64df6..834bc39a24 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -2,6 +2,7 @@ import pyblish.api from openpype.pipeline import PublishValidationError from openpype.pipeline.publish import RepairAction +from openpype.hosts.houdini.api.action import SelectInvalidAction import hou @@ -26,7 +27,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" - actions = [SelectROPAction] + actions = [SelectROPAction, SelectInvalidAction] def process(self, instance): @@ -48,7 +49,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): "Ensure a valid SOP output path is set." % node.path() ) - return [node.path()] + return [node] # Output node must be a Sop node. if not isinstance(output_node, hou.SopNode): @@ -58,7 +59,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): "instead found category type: %s" % (output_node.path(), output_node.type().category().name()) ) - return [output_node.path()] + return [output_node] # For the sake of completeness also assert the category type # is Sop to avoid potential edge case scenarios even though @@ -78,14 +79,14 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): except hou.Error as exc: cls.log.error("Cook failed: %s" % exc) cls.log.error(output_node.errors()[0]) - return [output_node.path()] + return [output_node] # Ensure the output node has at least Geometry data if not output_node.geometry(): cls.log.error( "Output node `%s` has no geometry data." % output_node.path() ) - return [output_node.path()] + return [output_node] @classmethod def repair(cls, instance): From 612b85a703f05a460ecda0285ab441ef72334c39 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 17:08:14 +0300 Subject: [PATCH 10/20] move SelectRopAction to api.actions --- openpype/hosts/houdini/api/action.py | 43 +++++++++++++++++++ .../publish/validate_sop_output_node.py | 26 ++--------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index 27e8ce55bb..b6879bb276 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -44,3 +44,46 @@ class SelectInvalidAction(pyblish.api.Action): node.setCurrent(True) else: self.log.info("No invalid nodes found.") + + +class SelectROPAction(pyblish.api.Action): + """Select ROP. + + It's used to select the associated ROPs with all errored instances + not necessarily the ones that errored on the plugin we're running the action on. + """ + + label = "Select ROP" + on = "failed" # This action is only available on a failed plug-in + icon = "mdi.cursor-default-click" + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding ROP nodes..") + rop_nodes = list() + for instance in instances: + node_path = instance.data.get("instance_node") + if not node_path: + continue + + node = hou.node(node_path) + if not node: + continue + + rop_nodes.append(node) + + hou.clearAllSelected() + if rop_nodes: + self.log.info("Selecting ROP nodes: {}".format( + ", ".join(node.path() for node in rop_nodes) + )) + for node in rop_nodes: + node.setSelected(True) + node.setCurrent(True) + else: + self.log.info("No ROP nodes found.") diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index 834bc39a24..d9dee38680 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -1,16 +1,14 @@ # -*- coding: utf-8 -*- import pyblish.api from openpype.pipeline import PublishValidationError -from openpype.pipeline.publish import RepairAction -from openpype.hosts.houdini.api.action import SelectInvalidAction +from openpype.hosts.houdini.api.action import ( + SelectInvalidAction, + SelectROPAction, +) import hou -class SelectROPAction(RepairAction): - label = "Select ROP" - icon = "mdi.cursor-default-click" - class ValidateSopOutputNode(pyblish.api.InstancePlugin): """Validate the instance SOP Output Node. @@ -87,19 +85,3 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): "Output node `%s` has no geometry data." % output_node.path() ) return [output_node] - - @classmethod - def repair(cls, instance): - """Select ROP. - - It's used to select the associated ROP for the selected instance - which tells the artist which ROP node need to be fixed! - """ - - rop_node = hou.node(instance.data["instance_node"]) - rop_node.setSelected(True, clear_all_selected=True) - - cls.log.debug( - '%s has been selected' - % rop_node.path() - ) From d61bd762049acba4e6fbc0445cb031dde5f452e8 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 17:11:21 +0300 Subject: [PATCH 11/20] fix lint problems --- openpype/hosts/houdini/api/action.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index b6879bb276..a9afe38931 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -49,8 +49,7 @@ class SelectInvalidAction(pyblish.api.Action): class SelectROPAction(pyblish.api.Action): """Select ROP. - It's used to select the associated ROPs with all errored instances - not necessarily the ones that errored on the plugin we're running the action on. + It's used to select the associated ROPs with the errored instances. """ label = "Select ROP" From ba877956b94b1663ce6d38168eea48da8dd6bfca Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Jul 2023 09:17:53 +0100 Subject: [PATCH 12/20] Fix collecting arnold prefix when none --- openpype/hosts/maya/api/lib_renderproducts.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a6bcd003a5..4f52372f06 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -528,6 +528,9 @@ class RenderProductsArnold(ARenderProducts): def get_renderer_prefix(self): prefix = super(RenderProductsArnold, self).get_renderer_prefix() + if prefix is None: + return "" + merge_aovs = self._get_attr("defaultArnoldDriver.mergeAOVs") if not merge_aovs and "" not in prefix.lower(): # When Merge AOVs is disabled and token not present From 9dbba9394b97562025a83cbc5075e17f22dedf73 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 5 Jul 2023 14:06:58 +0300 Subject: [PATCH 13/20] update action with roy's PR --- openpype/hosts/houdini/api/action.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index a9afe38931..eeb9cfda62 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -57,15 +57,12 @@ class SelectROPAction(pyblish.api.Action): icon = "mdi.cursor-default-click" def process(self, context, plugin): - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + errored_instances = get_errored_instances_from_context(context, plugin) # Get the invalid nodes for the plug-ins self.log.info("Finding ROP nodes..") rop_nodes = list() - for instance in instances: + for instance in errored_instances: node_path = instance.data.get("instance_node") if not node_path: continue From 974d70869300015c9044a1a4b043b50c247ed670 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Jul 2023 12:41:26 +0100 Subject: [PATCH 14/20] Revert "Fix collecting arnold prefix when none" This reverts commit ba877956b94b1663ce6d38168eea48da8dd6bfca. --- openpype/hosts/maya/api/lib_renderproducts.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 4f52372f06..a6bcd003a5 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -528,9 +528,6 @@ class RenderProductsArnold(ARenderProducts): def get_renderer_prefix(self): prefix = super(RenderProductsArnold, self).get_renderer_prefix() - if prefix is None: - return "" - merge_aovs = self._get_attr("defaultArnoldDriver.mergeAOVs") if not merge_aovs and "" not in prefix.lower(): # When Merge AOVs is disabled and token not present From 558cd4fe6818cb708c46d2b3f02f6a4125e8b355 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Jul 2023 12:42:16 +0100 Subject: [PATCH 15/20] Use BigRoy soluiton --- openpype/hosts/maya/api/lib_renderproducts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a6bcd003a5..7bfb53d500 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -274,12 +274,14 @@ class ARenderProducts: "Unsupported renderer {}".format(self.renderer) ) + # Note: When this attribute is never set (e.g. on maya launch) then + # this can return None even though it is a string attribute prefix = self._get_attr(prefix_attr) if not prefix: # Fall back to scene name by default - log.debug("Image prefix not set, using ") - file_prefix = "" + log.warning("Image prefix not set, using ") + prefix = "" return prefix From 2b23b42da65b6943a6b46253816422f71c7ac8c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Jul 2023 14:11:20 +0200 Subject: [PATCH 16/20] RepairAction and SelectInvalidAction actually filter to instances that failed on the exact plugin - not on "any failure" (#5240) --- openpype/action.py | 12 +++++------- openpype/hosts/blender/api/action.py | 6 +++--- openpype/hosts/fusion/api/action.py | 8 +++----- openpype/hosts/houdini/api/action.py | 8 +++----- openpype/hosts/maya/api/action.py | 8 +++----- openpype/hosts/nuke/api/actions.py | 8 +++----- .../nuke/plugins/publish/validate_rendered_frames.py | 10 ++-------- openpype/hosts/resolve/api/action.py | 8 +++----- openpype/pipeline/publish/lib.py | 7 ++++++- openpype/pipeline/publish/publish_plugins.py | 8 +++----- 10 files changed, 34 insertions(+), 49 deletions(-) diff --git a/openpype/action.py b/openpype/action.py index 15c96404b6..6114c65fd4 100644 --- a/openpype/action.py +++ b/openpype/action.py @@ -49,7 +49,7 @@ def deprecated(new_destination): @deprecated("openpype.pipeline.publish.get_errored_instances_from_context") -def get_errored_instances_from_context(context): +def get_errored_instances_from_context(context, plugin=None): """ Deprecated: Since 3.14.* will be removed in 3.16.* or later. @@ -57,7 +57,7 @@ def get_errored_instances_from_context(context): from openpype.pipeline.publish import get_errored_instances_from_context - return get_errored_instances_from_context(context) + return get_errored_instances_from_context(context, plugin=plugin) @deprecated("openpype.pipeline.publish.get_errored_plugins_from_context") @@ -97,11 +97,9 @@ class RepairAction(pyblish.api.Action): # Get the errored instances self.log.info("Finding failed instances..") - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - for instance in instances: + errored_instances = get_errored_instances_from_context(context, + plugin=plugin) + for instance in errored_instances: plugin.repair(instance) diff --git a/openpype/hosts/blender/api/action.py b/openpype/hosts/blender/api/action.py index fe0833e39f..dc49d6d9ae 100644 --- a/openpype/hosts/blender/api/action.py +++ b/openpype/hosts/blender/api/action.py @@ -12,13 +12,13 @@ class SelectInvalidAction(pyblish.api.Action): icon = "search" def process(self, context, plugin): - errored_instances = get_errored_instances_from_context(context) - instances = pyblish.api.instances_by_plugin(errored_instances, 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...") invalid = list() - for instance in instances: + for instance in errored_instances: invalid_nodes = plugin.get_invalid(instance) if invalid_nodes: if isinstance(invalid_nodes, (list, tuple)): diff --git a/openpype/hosts/fusion/api/action.py b/openpype/hosts/fusion/api/action.py index ff5dd14caa..347d552108 100644 --- a/openpype/hosts/fusion/api/action.py +++ b/openpype/hosts/fusion/api/action.py @@ -18,15 +18,13 @@ class SelectInvalidAction(pyblish.api.Action): icon = "search" # Icon from Awesome Icon def process(self, context, plugin): - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, 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..") invalid = list() - for instance in instances: + for instance in errored_instances: invalid_nodes = plugin.get_invalid(instance) if invalid_nodes: if isinstance(invalid_nodes, (list, tuple)): diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index 27e8ce55bb..b1519ddd1d 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -17,15 +17,13 @@ class SelectInvalidAction(pyblish.api.Action): def process(self, context, plugin): - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, 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..") invalid = list() - for instance in instances: + for instance in errored_instances: invalid_nodes = plugin.get_invalid(instance) if invalid_nodes: if isinstance(invalid_nodes, (list, tuple)): diff --git a/openpype/hosts/maya/api/action.py b/openpype/hosts/maya/api/action.py index 065fdf3691..3b8e2c1848 100644 --- a/openpype/hosts/maya/api/action.py +++ b/openpype/hosts/maya/api/action.py @@ -111,15 +111,13 @@ class SelectInvalidAction(pyblish.api.Action): except ImportError: raise ImportError("Current host is not Maya") - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, 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..") invalid = list() - for instance in instances: + for instance in errored_instances: invalid_nodes = plugin.get_invalid(instance) if invalid_nodes: if isinstance(invalid_nodes, (list, tuple)): diff --git a/openpype/hosts/nuke/api/actions.py b/openpype/hosts/nuke/api/actions.py index 92b83560da..c955a85acc 100644 --- a/openpype/hosts/nuke/api/actions.py +++ b/openpype/hosts/nuke/api/actions.py @@ -25,15 +25,13 @@ class SelectInvalidAction(pyblish.api.Action): except ImportError: raise ImportError("Current host is not Nuke") - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, 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..") invalid = list() - for instance in instances: + for instance in errored_instances: invalid_nodes = plugin.get_invalid(instance) if invalid_nodes: diff --git a/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py index 1c22c5b9d0..45c20412c8 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py +++ b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py @@ -2,6 +2,7 @@ import os import pyblish.api import clique from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.publish import get_errored_instances_from_context class RepairActionBase(pyblish.api.Action): @@ -11,14 +12,7 @@ class RepairActionBase(pyblish.api.Action): @staticmethod def get_instance(context, plugin): # Get the errored instances - failed = [] - for result in context.data["results"]: - if (result["error"] is not None and result["instance"] is not None - and result["instance"] not in failed): - failed.append(result["instance"]) - - # Apply pyblish.logic to get the instances for the plug-in - return pyblish.api.instances_by_plugin(failed, plugin) + return get_errored_instances_from_context(context, plugin=plugin) def repair_knob(self, instances, state): for instance in instances: diff --git a/openpype/hosts/resolve/api/action.py b/openpype/hosts/resolve/api/action.py index ceedc2cc54..d1dffca7cc 100644 --- a/openpype/hosts/resolve/api/action.py +++ b/openpype/hosts/resolve/api/action.py @@ -27,15 +27,13 @@ class SelectInvalidAction(pyblish.api.Action): except ImportError: raise ImportError("Current host is not Resolve") - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + errored_instances = get_errored_instances_from_context(context, + plugin=plugin) # Get the invalid nodes for the plug-ins self.log.info("Finding invalid clips..") invalid = list() - for instance in instances: + for instance in errored_instances: invalid_nodes = plugin.get_invalid(instance) if invalid_nodes: if isinstance(invalid_nodes, (list, tuple)): diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 471be5ddb8..0961d79234 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -577,12 +577,14 @@ def remote_publish(log, close_plugin_name=None, raise_error=False): raise RuntimeError(error_message) -def get_errored_instances_from_context(context): +def get_errored_instances_from_context(context, plugin=None): """Collect failed instances from pyblish context. Args: context (pyblish.api.Context): Publish context where we're looking for failed instances. + plugin (pyblish.api.Plugin): If provided then only consider errors + related to that plug-in. Returns: List[pyblish.lib.Instance]: Instances which failed during processing. @@ -594,6 +596,9 @@ def get_errored_instances_from_context(context): # When instance is None we are on the "context" result continue + if plugin is not None and result.get("plugin") != plugin: + continue + if result["error"]: instances.append(result["instance"]) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 1eec0760a1..ba3be6397e 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -234,11 +234,9 @@ class RepairAction(pyblish.api.Action): # Get the errored instances self.log.debug("Finding failed instances..") - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - for instance in instances: + errored_instances = get_errored_instances_from_context(context, + plugin=plugin) + for instance in errored_instances: self.log.debug( "Attempting repair for instance: {} ...".format(instance) ) From 587b98d65eb9b3f858a519f9760b8fbcf0c92522 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Wed, 5 Jul 2023 23:14:58 +0800 Subject: [PATCH 17/20] General: add the os library before os.environ.get (#5249) * add the os library before os.environ.get * move os import into the top --- openpype/pipeline/create/creator_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index fbb459ab12..947a90ef08 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -1,3 +1,4 @@ +import os import copy import collections From 4d87046f6a4373fa2587de7c9b2537258101461c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 6 Jul 2023 16:42:05 +0100 Subject: [PATCH 18/20] Fix set_attribute for enum attributes --- openpype/hosts/maya/api/lib.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 8569bbd38f..fca4410ede 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1522,7 +1522,15 @@ def set_attribute(attribute, value, node): cmds.addAttr(node, longName=attribute, **kwargs) node_attr = "{}.{}".format(node, attribute) - if "dataType" in kwargs: + enum_type = cmds.attributeQuery(attribute, node=node, enum=True) + if enum_type and value_type == "str": + enum_string_values = cmds.attributeQuery( + attribute, node=node, listEnum=True + )[0].split(":") + cmds.setAttr( + "{}.{}".format(node, attribute), enum_string_values.index(value) + ) + elif "dataType" in kwargs: attr_type = kwargs["dataType"] cmds.setAttr(node_attr, value, type=attr_type) else: From 99efc0e735bb3951b127a467e575532e331e25ae Mon Sep 17 00:00:00 2001 From: Alexey Bogomolov <11698866+movalex@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:38:35 +0300 Subject: [PATCH 19/20] rstrip the template string (#5235) --- openpype/pipeline/delivery.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py index 500f54040a..ddde45d4da 100644 --- a/openpype/pipeline/delivery.py +++ b/openpype/pipeline/delivery.py @@ -157,6 +157,8 @@ def deliver_single_file( delivery_path = delivery_path.replace("..", ".") # Make sure path is valid for all platforms delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) + # Remove newlines from the end of the string to avoid OSError during copy + delivery_path = delivery_path.rstrip() delivery_folder = os.path.dirname(delivery_path) if not os.path.exists(delivery_folder): From ed91fdde03cabae62acc9afee6582f413ef7a4b2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 7 Jul 2023 13:51:35 +0200 Subject: [PATCH 20/20] Update scene inventory even if any errors occurred during update (#5252) * Update scene inventory even if any errors occurred during update + re-use logic * Fix code --- openpype/tools/sceneinventory/view.py | 101 ++++++++++++++------------ 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 73d33392b9..57e6e24411 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -1,5 +1,6 @@ import collections import logging +import itertools from functools import partial from qtpy import QtWidgets, QtCore @@ -195,20 +196,17 @@ class SceneInventoryView(QtWidgets.QTreeView): version_name_by_id[version_doc["_id"]] = \ version_doc["name"] + # Specify version per item to update to + update_items = [] + update_versions = [] for item in items: repre_id = item["representation"] version_id = version_id_by_repre_id.get(repre_id) version_name = version_name_by_id.get(version_id) if version_name is not None: - try: - update_container(item, version_name) - except AssertionError: - self._show_version_error_dialog( - version_name, [item] - ) - log.warning("Update failed", exc_info=True) - - self.data_changed.emit() + update_items.append(item) + update_versions.append(version_name) + self._update_containers(update_items, update_versions) update_icon = qtawesome.icon( "fa.asterisk", @@ -225,16 +223,6 @@ class SceneInventoryView(QtWidgets.QTreeView): update_to_latest_action = None if has_outdated or has_loaded_hero_versions: - # update to latest version - def _on_update_to_latest(items): - for item in items: - try: - update_container(item, -1) - except AssertionError: - self._show_version_error_dialog(None, [item]) - log.warning("Update failed", exc_info=True) - self.data_changed.emit() - update_icon = qtawesome.icon( "fa.angle-double-up", color=DEFAULT_COLOR @@ -245,21 +233,11 @@ class SceneInventoryView(QtWidgets.QTreeView): menu ) update_to_latest_action.triggered.connect( - lambda: _on_update_to_latest(items) + lambda: self._update_containers(items, version=-1) ) change_to_hero = None if has_available_hero_version: - # change to hero version - def _on_update_to_hero(items): - for item in items: - try: - update_container(item, HeroVersionType(-1)) - except AssertionError: - self._show_version_error_dialog('hero', [item]) - log.warning("Update failed", exc_info=True) - self.data_changed.emit() - # TODO change icon change_icon = qtawesome.icon( "fa.asterisk", @@ -271,7 +249,8 @@ class SceneInventoryView(QtWidgets.QTreeView): menu ) change_to_hero.triggered.connect( - lambda: _on_update_to_hero(items) + lambda: self._update_containers(items, + version=HeroVersionType(-1)) ) # set version @@ -740,14 +719,7 @@ class SceneInventoryView(QtWidgets.QTreeView): if label: version = versions_by_label[label] - for item in items: - try: - update_container(item, version) - except AssertionError: - self._show_version_error_dialog(version, [item]) - log.warning("Update failed", exc_info=True) - # refresh model when done - self.data_changed.emit() + self._update_containers(items, version) def _show_switch_dialog(self, items): """Display Switch dialog""" @@ -782,9 +754,9 @@ class SceneInventoryView(QtWidgets.QTreeView): Args: version: str or int or None """ - if not version: + if version == -1: version_str = "latest" - elif version == "hero": + elif isinstance(version, HeroVersionType): version_str = "hero" elif isinstance(version, int): version_str = "v{:03d}".format(version) @@ -841,10 +813,43 @@ class SceneInventoryView(QtWidgets.QTreeView): return # Trigger update to latest - for item in outdated_items: - try: - update_container(item, -1) - except AssertionError: - self._show_version_error_dialog(None, [item]) - log.warning("Update failed", exc_info=True) - self.data_changed.emit() + self._update_containers(outdated_items, version=-1) + + def _update_containers(self, items, version): + """Helper to update items to given version (or version per item) + + If at least one item is specified this will always try to refresh + the inventory even if errors occurred on any of the items. + + Arguments: + items (list): Items to update + version (int or list): Version to set to. + This can be a list specifying a version for each item. + Like `update_container` version -1 sets the latest version + and HeroTypeVersion instances set the hero version. + + """ + + if isinstance(version, (list, tuple)): + # We allow a unique version to be specified per item. In that case + # the length must match with the items + assert len(items) == len(version), ( + "Number of items mismatches number of versions: " + "{} items - {} versions".format(len(items), len(version)) + ) + versions = version + else: + # Repeat the same version infinitely + versions = itertools.repeat(version) + + # Trigger update to latest + try: + for item, item_version in zip(items, versions): + try: + update_container(item, item_version) + except AssertionError: + self._show_version_error_dialog(item_version, [item]) + log.warning("Update failed", exc_info=True) + finally: + # Always update the scene inventory view, even if errors occurred + self.data_changed.emit()