From a2e18323ff1103861891cf84dd9e2ac3b6efab53 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 27 Jul 2023 22:32:43 +0300 Subject: [PATCH 01/63] update fbx creator --- .../houdini/plugins/create/create_fbx.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/create/create_fbx.py diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py new file mode 100644 index 0000000000..65f613bdea --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating fbx.""" +from openpype.hosts.houdini.api import plugin + +import hou + + +class CreateFilmboxFBX(plugin.HoudiniCreator): + """Filmbox FBX Driver""" + identifier = "io.openpype.creators.houdini.filmboxfbx" + label = "Filmbox FBX" + family = "filmboxfbx" + icon = "fa5s.cubes" + + def create(self, subset_name, instance_data, pre_create_data): + instance_data.pop("active", None) + instance_data.update({"node_type": "filmboxfbx"}) + + instance = super(CreateFilmboxFBX, self).create( + subset_name, + instance_data, + pre_create_data) + + instance_node = hou.node(instance.get("instance_node")) + output_path = hou.text.expandString( + "$HIP/pyblish/{}.fbx".format(subset_name)) + + parms = { + "sopoutput": output_path + } + + if self.selected_nodes: + selected_node = self.selected_nodes[0] + + # Although Houdini allows ObjNode path on `startnode` for the + # the ROP node we prefer it set to the SopNode path explicitly + + # Allow sop level paths (e.g. /obj/geo1/box1) + if isinstance(selected_node, hou.SopNode): + parms["startnode"] = selected_node.path() + self.log.debug( + "Valid SopNode selection, 'Export' in filmboxfbx" + " will be set to '%s'." + % selected_node + ) + + # Allow object level paths to Geometry nodes (e.g. /obj/geo1) + # but do not allow other object level nodes types like cameras, etc. + elif isinstance(selected_node, hou.ObjNode) and \ + selected_node.type().name() in ["geo"]: + + # get the output node with the minimum + # 'outputidx' or the node with display flag + sop_path = self.get_obj_output(selected_node) + + if sop_path: + parms["startnode"] = sop_path.path() + self.log.debug( + "Valid ObjNode selection, 'Export' in filmboxfbx " + "will be set to the child path '%s'." + % sop_path + ) + + if not parms.get("startnode", None): + self.log.debug( + "Selection isn't valid. 'Export' in filmboxfbx will be empty." + ) + else: + self.log.debug( + "No Selection. 'Export' in filmboxfbx will be empty." + ) + + instance_node.setParms(parms) + + # Lock any parameters in this list + to_lock = [] + self.lock_parameters(instance_node, to_lock) + + def get_network_categories(self): + return [ + hou.ropNodeTypeCategory(), + hou.sopNodeTypeCategory() + ] + + def get_obj_output(self, obj_node): + """Find output node with the smallest 'outputidx'.""" + + outputs = obj_node.subnetOutputs() + + # if obj_node is empty + if not outputs: + return + + # if obj_node has one output child whether its + # sop output node or a node with the render flag + elif len(outputs) == 1: + return outputs[0] + + # if there are more than one, then it have multiple ouput nodes + # return the one with the minimum 'outputidx' + else: + return min(outputs, + key=lambda node: node.evalParm('outputidx')) From 5a9281823d3d18cc9857875b796f7e65d9c747f1 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 28 Jul 2023 00:06:55 +0300 Subject: [PATCH 02/63] update create_fbx --- .../houdini/plugins/create/create_fbx.py | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 65f613bdea..b59a8ccfc6 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -12,23 +12,61 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): family = "filmboxfbx" icon = "fa5s.cubes" + # Overrides HoudiniCreator.create() def create(self, subset_name, instance_data, pre_create_data): - instance_data.pop("active", None) + # instance_data.pop("active", None) + + # set node type instance_data.update({"node_type": "filmboxfbx"}) + # set essential extra parameters instance = super(CreateFilmboxFBX, self).create( subset_name, instance_data, pre_create_data) + # get the created node instance_node = hou.node(instance.get("instance_node")) + + # get output path output_path = hou.text.expandString( "$HIP/pyblish/{}.fbx".format(subset_name)) + # get selection + selection = self.get_selection() + + # parms dictionary parms = { + "startnode" : selection, "sopoutput": output_path } + # set parms + instance_node.setParms(parms) + + # Lock any parameters in this list + to_lock = [] + self.lock_parameters(instance_node, to_lock) + + # Overrides HoudiniCreator.get_network_categories() + def get_network_categories(self): + return [ + hou.ropNodeTypeCategory(), + hou.sopNodeTypeCategory() + ] + + def get_selection(self): + """Selection Logic. + + how self.selected_nodes should be processed to get + the desirable node from selection. + + Returns: + str : node path + """ + + selection = "" + if self.selected_nodes: selected_node = self.selected_nodes[0] @@ -37,7 +75,7 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # Allow sop level paths (e.g. /obj/geo1/box1) if isinstance(selected_node, hou.SopNode): - parms["startnode"] = selected_node.path() + selection = selected_node.path() self.log.debug( "Valid SopNode selection, 'Export' in filmboxfbx" " will be set to '%s'." @@ -54,14 +92,14 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): sop_path = self.get_obj_output(selected_node) if sop_path: - parms["startnode"] = sop_path.path() + selection = sop_path.path() self.log.debug( "Valid ObjNode selection, 'Export' in filmboxfbx " "will be set to the child path '%s'." % sop_path ) - if not parms.get("startnode", None): + if not selection: self.log.debug( "Selection isn't valid. 'Export' in filmboxfbx will be empty." ) @@ -70,20 +108,12 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): "No Selection. 'Export' in filmboxfbx will be empty." ) - instance_node.setParms(parms) - - # Lock any parameters in this list - to_lock = [] - self.lock_parameters(instance_node, to_lock) - - def get_network_categories(self): - return [ - hou.ropNodeTypeCategory(), - hou.sopNodeTypeCategory() - ] + return selection def get_obj_output(self, obj_node): - """Find output node with the smallest 'outputidx'.""" + """Find output node with the smallest 'outputidx' + or return tje node with the render flag instead. + """ outputs = obj_node.subnetOutputs() From 9724ea4c84f51e68785cbd54c1d3eef87cdd4a3f Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 28 Jul 2023 00:08:23 +0300 Subject: [PATCH 03/63] update create_fbx --- openpype/hosts/houdini/plugins/create/create_fbx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index b59a8ccfc6..d4594edcbe 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- -"""Creator plugin for creating fbx.""" +"""Creator plugin for creating fbx. + +It was made to pratice publish process. +""" from openpype.hosts.houdini.api import plugin import hou From 8a431f2d44c967daab56dce3ed06fdfd7bcae0c5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 28 Jul 2023 00:18:47 +0300 Subject: [PATCH 04/63] update create_fbx --- openpype/hosts/houdini/plugins/create/create_fbx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index d4594edcbe..30cc98ddb9 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -31,11 +31,12 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # get the created node instance_node = hou.node(instance.get("instance_node")) - # get output path + # Get this node specific parms + # 1. get output path output_path = hou.text.expandString( "$HIP/pyblish/{}.fbx".format(subset_name)) - # get selection + # 2. get selection selection = self.get_selection() # parms dictionary From e4c9275ac36e1900d48a3605f2693a189ab92158 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 28 Jul 2023 20:08:20 +0300 Subject: [PATCH 05/63] update default_variants --- openpype/hosts/houdini/plugins/create/create_fbx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 30cc98ddb9..18fd879ac7 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -15,6 +15,9 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): family = "filmboxfbx" icon = "fa5s.cubes" + default_variant = "FBX" + default_variants = ["FBX", "Main", "Test"] + # Overrides HoudiniCreator.create() def create(self, subset_name, instance_data, pre_create_data): # instance_data.pop("active", None) From 919247b3d4675b5febbb3ebebdc2b92075312399 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 28 Jul 2023 21:17:38 +0300 Subject: [PATCH 06/63] update creator --- .../houdini/plugins/create/create_fbx.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 18fd879ac7..fed1ad0562 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -4,6 +4,7 @@ It was made to pratice publish process. """ from openpype.hosts.houdini.api import plugin +from openpype.lib import EnumDef import hou @@ -42,10 +43,14 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # 2. get selection selection = self.get_selection() + # 3. get Vertex Cache Format + vcformat = pre_create_data.get("vcformat") + # parms dictionary parms = { "startnode" : selection, - "sopoutput": output_path + "sopoutput": output_path, + "vcformat" : vcformat } # set parms @@ -62,6 +67,19 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): hou.sopNodeTypeCategory() ] + # Overrides HoudiniCreator.get_pre_create_attr_defs() + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + vcformat = EnumDef("vcformat", + items={ + 0: "Maya Compatible (MC)", + 1: "3DS MAX Compatible (PC2)" + }, + default=0, + label="Vertex Cache Format") + + return attrs + [vcformat] + def get_selection(self): """Selection Logic. From 7918668e72ec743c63d966cbf1db3f60159292ef Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 28 Jul 2023 22:15:59 +0300 Subject: [PATCH 07/63] add Valid Frame Range --- .../hosts/houdini/plugins/create/create_fbx.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index fed1ad0562..0345e07ea6 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -46,11 +46,15 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # 3. get Vertex Cache Format vcformat = pre_create_data.get("vcformat") + # 3. get Valid Frame Range + trange = pre_create_data.get("trange") + # parms dictionary parms = { "startnode" : selection, "sopoutput": output_path, - "vcformat" : vcformat + "vcformat" : vcformat, + "trange" : trange } # set parms @@ -77,8 +81,15 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): }, default=0, label="Vertex Cache Format") + trange = EnumDef("trange", + items={ + 0: "Render Current Frame", + 1: "Render Frame Range" + }, + default=0, + label="Valid Frame Range") - return attrs + [vcformat] + return attrs + [vcformat, trange] def get_selection(self): """Selection Logic. From ed2a48e2cd9af28fdd45237cecd9d1d9c17eab7e Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Sat, 29 Jul 2023 02:28:02 +0300 Subject: [PATCH 08/63] add fbx collector --- .../plugins/publish/collect_fbx_type.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/collect_fbx_type.py diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py new file mode 100644 index 0000000000..f25dbf3a5b --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -0,0 +1,56 @@ +"""Collector for filmboxfbx types. + +A Collector can act as a preprocessor for the validation stage. +It is used mainly to update instance.data + +P.S. + There are some collectors that run by default for all types. +""" +import pyblish.api + + +class CollectFilmboxfbxType(pyblish.api.InstancePlugin): + """Collect data type for filmboxfbx instance.""" + + order = pyblish.api.CollectorOrder + hosts = ["houdini"] + families = ["filmboxfbx"] + label = "Collect type of filmboxfbx" + + def process(self, instance): + + if instance.data["creator_identifier"] == "io.openpype.creators.houdini.filmboxfbx": # noqa: E501 + # such a condition can be used to differentiate between + # instances by identifier even if they have the same type. + pass + + # Update instance.data with ouptut_node + out_node = self.get_output_node(instance) + + if out_node: + instance.data["output_node"] = out_node + + # Disclaimer : As a convntin we use collect_output_node.py + # to Update instance.data with ouptut_node of different types + # however, we use this collector instead for demonstration + + + def get_output_node(self, instance): + """Getting output_node Logic. + + It's moved here so that it become easier to focus on + process method. + """ + + import hou + + # get output node + node = hou.node(instance.data["instance_node"]) + out_node = node.parm("startnode").evalAsNode() + + if not out_node: + self.log.warning("No output node collected.") + return + + self.log.debug("Output node: %s" % out_node.path()) + return out_node From 051f03f4fa4e4718c6be56878ae2f8b07b5a3a11 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 31 Jul 2023 21:00:16 +0300 Subject: [PATCH 09/63] add export validator --- .../publish/validate_fbx_export_node.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py new file mode 100644 index 0000000000..f9f2461415 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +"""Validator plugin for Export node in filmbox instance.""" +import pyblish.api +from openpype.pipeline import PublishValidationError + + +class ValidateNoExportPath(pyblish.api.InstancePlugin): + """Validate if Export node in filmboxfbx instance exists.""" + + order = pyblish.api.ValidatorOrder + families = ["filmboxfbx"] + label = "Validate Filmbox Export Node" + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + "Export node is incorrect", + title="Invalid Export Node" + ) + + @classmethod + def get_invalid(cls, instance): + + import hou + + fbx_rop = hou.node(instance.data.get("instance_node")) + export_node = fbx_rop.parm("startnode").evalAsNode() + + if not export_node: + cls.log.error( + ("Empty Export ('Export' parameter) found in " + "the filmbox instance - {}".format(fbx_rop.path())) + ) + return [fbx_rop] + + if not isinstance(export_node, hou.SopNode): + cls.log.error( + "Export node '{}' is not pointing to valid SOP" + " node".format(export_node.path()) + ) + return [export_node] From ecc583f8f2eec059e8589e6c45934540085aad34 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 1 Aug 2023 22:04:04 +0300 Subject: [PATCH 10/63] update create collect validate --- .../houdini/plugins/create/create_fbx.py | 11 +- .../plugins/publish/collect_fbx_type.py | 1 + .../publish/validate_fbx_export_node.py | 43 ---- .../publish/validate_fbx_hierarchy_path.py | 220 ++++++++++++++++++ .../publish/validate_sop_output_node.py | 2 +- 5 files changed, 231 insertions(+), 46 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py create mode 100644 openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 0345e07ea6..ee2fdcd73f 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -2,7 +2,14 @@ """Creator plugin for creating fbx. It was made to pratice publish process. + +Filmbox by default expects an ObjNode +it's by default selects the output sop with mimimum idx +or the node with render flag isntead. + +to eleminate any confusion, we set the sop node explictly. """ + from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef @@ -16,8 +23,8 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): family = "filmboxfbx" icon = "fa5s.cubes" - default_variant = "FBX" - default_variants = ["FBX", "Main", "Test"] + default_variant = "Main" + default_variants = ["Main", "Test"] # Overrides HoudiniCreator.create() def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index f25dbf3a5b..05a0af659f 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -17,6 +17,7 @@ class CollectFilmboxfbxType(pyblish.api.InstancePlugin): families = ["filmboxfbx"] label = "Collect type of filmboxfbx" + # overrides InstancePlugin.process() def process(self, instance): if instance.data["creator_identifier"] == "io.openpype.creators.houdini.filmboxfbx": # noqa: E501 diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py deleted file mode 100644 index f9f2461415..0000000000 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_export_node.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validator plugin for Export node in filmbox instance.""" -import pyblish.api -from openpype.pipeline import PublishValidationError - - -class ValidateNoExportPath(pyblish.api.InstancePlugin): - """Validate if Export node in filmboxfbx instance exists.""" - - order = pyblish.api.ValidatorOrder - families = ["filmboxfbx"] - label = "Validate Filmbox Export Node" - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError( - "Export node is incorrect", - title="Invalid Export Node" - ) - - @classmethod - def get_invalid(cls, instance): - - import hou - - fbx_rop = hou.node(instance.data.get("instance_node")) - export_node = fbx_rop.parm("startnode").evalAsNode() - - if not export_node: - cls.log.error( - ("Empty Export ('Export' parameter) found in " - "the filmbox instance - {}".format(fbx_rop.path())) - ) - return [fbx_rop] - - if not isinstance(export_node, hou.SopNode): - cls.log.error( - "Export node '{}' is not pointing to valid SOP" - " node".format(export_node.path()) - ) - return [export_node] diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py new file mode 100644 index 0000000000..24f88b384f --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +"""It's almost the same as +'validate_primitive_hierarchy_paths.py' +however this one includes more comments for demonstration. + +FYI, path for fbx behaves a little differently. +In maya terms: +in Filmbox FBX: it sets the name of the object +in Alembic ROP: it sets the name of the shape +""" + +import pyblish.api +from openpype.pipeline import PublishValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) +from openpype.hosts.houdini.api.action import ( + SelectInvalidAction, + SelectROPAction, +) + +import hou + +# Each validation can have a single repair action +# which calls the repair method +class AddDefaultPathAction(RepairAction): + label = "Add a default path" + icon = "mdi.pencil-plus-outline" + + +class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): + """Validate all primitives build hierarchy from attribute + when enabled. + + The name of the attribute must exist on the prims and have the + same name as Build Hierarchy from Attribute's `Path Attribute` + value on the FilmBox node. + This validation enables 'Build Hierarchy from Attribute' + by default. + """ + + # Usually you will this value by default + order = ValidateContentsOrder + 0.1 + families = ["filmboxfbx"] + hosts = ["houdini"] + label = "Validate FBX Hierarchy Path" + + # Validation can have as many actions as you want + # all of these actions are defined in a seperate place + # unlike the repair action + actions = [SelectInvalidAction, AddDefaultPathAction, + SelectROPAction] + + # overrides InstancePlugin.process() + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + nodes = [n.name() for n in invalid] + raise PublishValidationError( + "See log for details. " + "Invalid nodes: {0}".format(nodes), + title=self.label + ) + + # This method was named get_invalid as a convention + # it's also used by SelectInvalidAction to select + # the returned node + @classmethod + def get_invalid(cls, instance): + + output_node = instance.data.get("output_node") + rop_node = hou.node(instance.data["instance_node"]) + + if output_node is None: + cls.log.error( + "SOP Output node in '%s' does not exist. " + "Ensure a valid SOP output path is set.", + rop_node.path() + ) + + return [rop_node] + + build_from_path = rop_node.parm("buildfrompath").eval() + if not build_from_path: + cls.log.debug( + "Filmbox FBX has 'Build from Path' disabled. " + "Enbaling it as default." + ) + rop_node.parm("buildfrompath").set(1) + + path_attr = rop_node.parm("pathattrib").eval() + if not path_attr: + cls.log.debug( + "Filmbox FBX node has no Path Attribute" + "value set, setting it to 'path' as default." + ) + rop_node.parm("pathattrib").set("path") + + cls.log.debug("Checking for attribute: %s", path_attr) + + if not hasattr(output_node, "geometry"): + # In the case someone has explicitly set an Object + # node instead of a SOP node in Geometry context + # then for now we ignore - this allows us to also + # export object transforms. + cls.log.warning("No geometry output node found," + " skipping check..") + return + + # Check if the primitive attribute exists + frame = instance.data.get("frameStart", 0) + geo = output_node.geometryAtFrame(frame) + + # If there are no primitives on the current frame then + # we can't check whether the path names are correct. + # So we'll just issue a warning that the check can't + # be done consistently and skip validation. + + if len(geo.iterPrims()) == 0: + cls.log.warning( + "No primitives found on current frame." + " Validation for primitive hierarchy" + " paths will be skipped," + " thus can't be validated." + ) + return + + # Check if there are any values for the primitives + attrib = geo.findPrimAttrib(path_attr) + if not attrib: + cls.log.info( + "Geometry Primitives are missing " + "path attribute: `%s`", path_attr + ) + return [output_node] + + # Ensure at least a single string value is present + if not attrib.strings(): + cls.log.info( + "Primitive path attribute has no " + "string values: %s", path_attr + ) + return [output_node] + + paths = geo.primStringAttribValues(path_attr) + # Ensure all primitives are set to a valid path + # Collect all invalid primitive numbers + invalid_prims = [i for i, path in enumerate(paths) if not path] + if invalid_prims: + num_prims = len(geo.iterPrims()) # faster than len(geo.prims()) + cls.log.info( + "Prims have no value for attribute `%s` " + "(%s of %s prims)", + path_attr, len(invalid_prims), num_prims + ) + return [output_node] + + # what repair action expects to find and call + @classmethod + def repair(cls, instance): + """Add a default path attribute Action. + + It is a helper action more than a repair action, + used to add a default single value for the path. + """ + + rop_node = hou.node(instance.data["instance_node"]) + # I'm doing so because an artist may change output node + # before clicking the button. + output_node = rop_node.parm("startnode").evalAsNode() + + if not output_node: + cls.log.debug( + "Action isn't performed, invalid SOP Path on %s", + rop_node + ) + return + + # This check to prevent the action from running multiple times. + # git_invalid only returns [output_node] when + # path attribute is the problem + if cls.get_invalid(instance) != [output_node]: + return + + path_attr = rop_node.parm("pathattrib").eval() + + path_node = output_node.parent().createNode("name", + "AUTO_PATH") + path_node.parm("attribname").set(path_attr) + path_node.parm("name1").set('`opname("..")`_GEO') + + cls.log.debug( + "'%s' was created. It adds '%s' with a default" + " single value", path_node, path_attr + ) + + path_node.setGenericFlag(hou.nodeFlag.DisplayComment, True) + path_node.setComment( + 'Auto path node was created automatically by ' + '"Add a default path attribute"' + '\nFeel free to modify or replace it.' + ) + + if output_node.type().name() in ["null", "output"]: + # Connect before + path_node.setFirstInput(output_node.input(0)) + path_node.moveToGoodPosition() + output_node.setFirstInput(path_node) + output_node.moveToGoodPosition() + else: + # Connect after + path_node.setFirstInput(output_node) + rop_node.parm("startnode").set(path_node.path()) + path_node.moveToGoodPosition() + + cls.log.debug( + "SOP path on '%s' updated to new output node '%s'", + rop_node, path_node + ) 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 d9dee38680..da9752505a 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["pointcache", "vdbcache"] + families = ["pointcache", "vdbcache", "filmboxfbx"] hosts = ["houdini"] label = "Validate Output Node" actions = [SelectROPAction, SelectInvalidAction] From 0a7d6aa31845a172dea4899f98a99bac66d806d8 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 1 Aug 2023 22:09:01 +0300 Subject: [PATCH 11/63] update validate --- .../houdini/plugins/publish/validate_fbx_hierarchy_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index 24f88b384f..7e890db58e 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -56,7 +56,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - nodes = [n.name() for n in invalid] + nodes = [n.path() for n in invalid] raise PublishValidationError( "See log for details. " "Invalid nodes: {0}".format(nodes), From 39225ee0479c0bdf257d02fcfff236d105806fd6 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 1 Aug 2023 22:54:05 +0300 Subject: [PATCH 12/63] add fbx extractor --- .../houdini/plugins/publish/extract_fbx.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/extract_fbx.py diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py new file mode 100644 index 0000000000..70bf3e1017 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -0,0 +1,50 @@ +import os + +import pyblish.api + +from openpype.pipeline import publish +from openpype.hosts.houdini.api.lib import render_rop + +import hou + + +class ExtractRedshiftProxy(publish.Extractor): + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Extract FilmBox FBX" + families = ["filmboxfbx"] + hosts = ["houdini"] + + # overrides InstancePlugin.process() + def process(self, instance): + + ropnode = hou.node(instance.data.get("instance_node")) + + # Get the filename from the filename parameter + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("sopoutput") + staging_dir = os.path.normpath(os.path.dirname(output)) + instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(output) + + self.log.info("Writing FBX '%s' to '%s'" % (file_name, + staging_dir)) + + render_rop(ropnode) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "fbx", + "ext": "fbx", + "files": file_name, + "stagingDir": staging_dir, + } + + # A single frame may also be rendered without start/end frame. + if "frameStart" in instance.data and "frameEnd" in instance.data: + representation["frameStart"] = instance.data["frameStart"] + representation["frameEnd"] = instance.data["frameEnd"] + + instance.data["representations"].append(representation) From b507c2d52b2b2ad23ec1d300e8f5d8cbbd97fb58 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 1 Aug 2023 23:03:55 +0300 Subject: [PATCH 13/63] register filmbox family in integrate.py --- openpype/plugins/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index ffb9acf4a7..05ffe0bd3d 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -138,7 +138,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "simpleUnrealTexture", "online", "uasset", - "blendScene" + "blendScene", + "filmboxfbx" ] default_template_name = "publish" From 686ba073cef895b17bc6393e4a4dd9bbb4bf73f5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 1 Aug 2023 23:06:40 +0300 Subject: [PATCH 14/63] update comments --- openpype/hosts/houdini/plugins/publish/collect_fbx_type.py | 1 + openpype/hosts/houdini/plugins/publish/extract_fbx.py | 3 ++- .../houdini/plugins/publish/validate_fbx_hierarchy_path.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index 05a0af659f..c665852bb6 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -12,6 +12,7 @@ import pyblish.api class CollectFilmboxfbxType(pyblish.api.InstancePlugin): """Collect data type for filmboxfbx instance.""" + # Usually you will use this value as default order = pyblish.api.CollectorOrder hosts = ["houdini"] families = ["filmboxfbx"] diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index 70bf3e1017..6a4b541c33 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -10,12 +10,13 @@ import hou class ExtractRedshiftProxy(publish.Extractor): + # Usually you will use this value as default order = pyblish.api.ExtractorOrder + 0.1 label = "Extract FilmBox FBX" families = ["filmboxfbx"] hosts = ["houdini"] - # overrides InstancePlugin.process() + # overrides Extractor.process() def process(self, instance): ropnode = hou.node(instance.data.get("instance_node")) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index 7e890db58e..871b347155 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -40,7 +40,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): by default. """ - # Usually you will this value by default + # Usually you will use this value as default order = ValidateContentsOrder + 0.1 families = ["filmboxfbx"] hosts = ["houdini"] From 7a74608b11bfd891d52b92ae2bdf78ebf3dcd468 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 3 Aug 2023 21:42:26 +0300 Subject: [PATCH 15/63] add loader --- .../hosts/houdini/plugins/load/load_fbx.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/load/load_fbx.py diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py new file mode 100644 index 0000000000..e69bdbb10a --- /dev/null +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +"""Fbx Loader for houdini. + +It's an exact copy of +'load_bgeo.py' +however this one includes extra comments for demonstration. + +This plugin is part of publish process guide. +""" +import os +import re + +from openpype.pipeline import ( + load, + get_representation_path, +) +from openpype.hosts.houdini.api import pipeline + + +class FbxLoader(load.LoaderPlugin): + """Load fbx files to Houdini.""" + + label = "Load FBX" + families = ["filmboxfbx", "fbx"] + representations = ["fbx"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + import hou + + # Get the root node + obj = hou.node("/obj") + + # Define node name + namespace = namespace if namespace else context["asset"]["name"] + node_name = "{}_{}".format(namespace, name) if namespace else name + + # Create a new geo node + container = obj.createNode("geo", node_name=node_name) + is_sequence = bool(context["representation"]["context"].get("frame")) + + # Remove the file node, it only loads static meshes + # Houdini 17 has removed the file node from the geo node + file_node = container.node("file1") + if file_node: + file_node.destroy() + + # Explicitly create a file node + path = self.filepath_from_context(context) + file_node = container.createNode("file", node_name=node_name) + file_node.setParms( + {"file": self.format_path(path, context["representation"])}) + + # Set display on last node + file_node.setDisplayFlag(True) + + nodes = [container, file_node] + self[:] = nodes + + return pipeline.containerise( + node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="", + ) + + @staticmethod + def format_path(path, representation): + """Format file path correctly for single bgeo or bgeo sequence.""" + if not os.path.exists(path): + raise RuntimeError("Path does not exist: %s" % path) + + is_sequence = bool(representation["context"].get("frame")) + # The path is either a single file or sequence in a folder. + if not is_sequence: + filename = path + else: + filename = re.sub(r"(.*)\.(\d+)\.(bgeo.*)", "\\1.$F4.\\3", path) + + filename = os.path.join(path, filename) + + filename = os.path.normpath(filename) + filename = filename.replace("\\", "/") + + return filename + + def update(self, container, representation): + + node = container["node"] + try: + file_node = next( + n for n in node.children() if n.type().name() == "file" + ) + except StopIteration: + self.log.error("Could not find node of type `file`") + return + + # Update the file path + file_path = get_representation_path(representation) + file_path = self.format_path(file_path, representation) + + file_node.setParms({"file": file_path}) + + # Update attribute + node.setParms({"representation": str(representation["_id"])}) + + def remove(self, container): + + node = container["node"] + node.destroy() + + def switch(self, container, representation): + self.update(container, representation) From a1b0c409aca7af1f9d44563bc4c797d884b67b5a Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 3 Aug 2023 21:43:50 +0300 Subject: [PATCH 16/63] update doc strings --- openpype/hosts/houdini/plugins/create/create_fbx.py | 2 ++ .../hosts/houdini/plugins/publish/collect_fbx_type.py | 2 ++ .../plugins/publish/validate_fbx_hierarchy_path.py | 8 ++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index ee2fdcd73f..ac76dc0441 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -8,6 +8,8 @@ it's by default selects the output sop with mimimum idx or the node with render flag isntead. to eleminate any confusion, we set the sop node explictly. + +This plugin is part of publish process guide. """ from openpype.hosts.houdini.api import plugin diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index c665852bb6..f1665b27f0 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -5,6 +5,8 @@ It is used mainly to update instance.data P.S. There are some collectors that run by default for all types. + +This plugin is part of publish process guide. """ import pyblish.api diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index 871b347155..6a2bda1bca 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- -"""It's almost the same as +"""Validate path attribute for all primitives. + +It's almost the same as 'validate_primitive_hierarchy_paths.py' -however this one includes more comments for demonstration. +however this one includes extra comments for demonstration. FYI, path for fbx behaves a little differently. In maya terms: in Filmbox FBX: it sets the name of the object in Alembic ROP: it sets the name of the shape + +This plugin is part of publish process guide. """ import pyblish.api From 08fdbf1283151e2109f1649321700ea8697c261d Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 3 Aug 2023 23:34:32 +0300 Subject: [PATCH 17/63] make loader look prettier --- .../hosts/houdini/plugins/load/load_fbx.py | 151 +++++++++++------- 1 file changed, 96 insertions(+), 55 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index e69bdbb10a..5afd39ea99 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -23,71 +23,25 @@ class FbxLoader(load.LoaderPlugin): label = "Load FBX" families = ["filmboxfbx", "fbx"] representations = ["fbx"] - order = -10 - icon = "code-fork" - color = "orange" + order = -10 # you can use this by default. + icon = "code-fork" # you can use this by default. + color = "orange" # you can use this by default. def load(self, context, name=None, namespace=None, data=None): - import hou + file_path = self.get_file_path(context) - # Get the root node - obj = hou.node("/obj") + namespace, node_name = self.get_node_name(context, name, namespace) - # Define node name - namespace = namespace if namespace else context["asset"]["name"] - node_name = "{}_{}".format(namespace, name) if namespace else name + nodes = self.create_load_node_tree(file_path, node_name, name) - # Create a new geo node - container = obj.createNode("geo", node_name=node_name) - is_sequence = bool(context["representation"]["context"].get("frame")) - - # Remove the file node, it only loads static meshes - # Houdini 17 has removed the file node from the geo node - file_node = container.node("file1") - if file_node: - file_node.destroy() - - # Explicitly create a file node - path = self.filepath_from_context(context) - file_node = container.createNode("file", node_name=node_name) - file_node.setParms( - {"file": self.format_path(path, context["representation"])}) - - # Set display on last node - file_node.setDisplayFlag(True) - - nodes = [container, file_node] self[:] = nodes - return pipeline.containerise( - node_name, - namespace, - nodes, - context, - self.__class__.__name__, - suffix="", + containerised_nodes = self.get_containerised_nodes( + nodes, context, node_name, namespace ) - @staticmethod - def format_path(path, representation): - """Format file path correctly for single bgeo or bgeo sequence.""" - if not os.path.exists(path): - raise RuntimeError("Path does not exist: %s" % path) - - is_sequence = bool(representation["context"].get("frame")) - # The path is either a single file or sequence in a folder. - if not is_sequence: - filename = path - else: - filename = re.sub(r"(.*)\.(\d+)\.(bgeo.*)", "\\1.$F4.\\3", path) - - filename = os.path.join(path, filename) - - filename = os.path.normpath(filename) - filename = filename.replace("\\", "/") - - return filename + return containerised_nodes def update(self, container, representation): @@ -116,3 +70,90 @@ class FbxLoader(load.LoaderPlugin): def switch(self, container, representation): self.update(container, representation) + + def get_file_path(self, context): + """Return formatted file path.""" + + # Format file name, Houdini only wants forward slashes + file_path = self.filepath_from_context(context) + file_path = os.path.normpath(file_path) + file_path = file_path.replace("\\", "/") + + return file_path + + def get_node_name(self, context, name=None, namespace=None): + """Define node name.""" + + if not namespace: + namespace = context["asset"]["name"] + + if namespace: + node_name = "{}_{}".format(namespace, name) + else: + node_name = name + + return namespace, node_name + + def create_load_node_tree(self, file_path, node_name, subset_name): + """Create Load node network. + + you can start building your tree at any obj level. + it'll be much easier to build it in the root obj level. + + Afterwards, your tree will be automatically moved to + '/obj/AVALON_CONTAINERS' subnetwork. + """ + import hou + + # Get the root obj level + obj = hou.node("/obj") + + # Create a new obj geo node + parent_node = obj.createNode("geo", node_name=node_name) + + # In older houdini, + # when reating a new obj geo node, a default file node will be + # automatically created. + # so, we will delete it if exists. + file_node = parent_node.node("file1") + if file_node: + file_node.destroy() + + # Create a new file node + file_node = parent_node.createNode("file", node_name= node_name) + file_node.setParms({"file":file_path}) + + # Create attribute delete + attribdelete_name = "attribdelete_{}".format(subset_name) + attribdelete = parent_node.createNode("attribdelete", + node_name= attribdelete_name) + attribdelete.setParms({"ptdel":"fbx_*"}) + attribdelete.setInput(0, file_node) + + # Create a Null node + null_name = "OUT_{}".format(subset_name) + null = parent_node.createNode("null", node_name= null_name) + null.setInput(0, attribdelete) + + # Ensure display flag is on the file_node input node and not on the OUT + # node to optimize "debug" displaying in the viewport. + file_node.setDisplayFlag(True) + + # Set new position for unpack node else it gets cluttered + nodes = [parent_node, file_node, attribdelete, null] + for nr, node in enumerate(nodes): + node.setPosition([0, (0 - nr)]) + + return nodes + + def get_containerised_nodes(self, nodes, context, node_name, namespace): + containerised_nodes = pipeline.containerise( + node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="", + ) + + return containerised_nodes From de56b53ac6c48f0ca73b9111ce0edbf73d02d950 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 3 Aug 2023 23:35:28 +0300 Subject: [PATCH 18/63] update doc string --- openpype/hosts/houdini/plugins/load/load_fbx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 5afd39ea99..6bac7a7cec 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """Fbx Loader for houdini. -It's an exact copy of -'load_bgeo.py' +It's almost a copy of +'load_bgeo.py'and 'load_alembic.py' however this one includes extra comments for demonstration. This plugin is part of publish process guide. From ba5f7eb417ccf86edccb4a3b1ef2b6dfe64fecca Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 3 Aug 2023 23:36:33 +0300 Subject: [PATCH 19/63] remove un-necessary import --- openpype/hosts/houdini/plugins/load/load_fbx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 6bac7a7cec..3f66c8e88f 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -8,7 +8,6 @@ however this one includes extra comments for demonstration. This plugin is part of publish process guide. """ import os -import re from openpype.pipeline import ( load, From 31f8c4cd13ef4240566f8dbf98af86f2488d3936 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 7 Aug 2023 18:59:04 +0300 Subject: [PATCH 20/63] update fbx creator --- .../houdini/plugins/create/create_fbx.py | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index ac76dc0441..9e1dbea2f4 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -4,10 +4,12 @@ It was made to pratice publish process. Filmbox by default expects an ObjNode -it's by default selects the output sop with mimimum idx -or the node with render flag isntead. +however, we set the sop node explictly +to eleminate any confusion. -to eleminate any confusion, we set the sop node explictly. +This creator by default will select +the output sop with mimimum idx +or the node with render flag isntead. This plugin is part of publish process guide. """ @@ -30,12 +32,11 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # Overrides HoudiniCreator.create() def create(self, subset_name, instance_data, pre_create_data): - # instance_data.pop("active", None) # set node type instance_data.update({"node_type": "filmboxfbx"}) - # set essential extra parameters + # create instance (calls super create method) instance = super(CreateFilmboxFBX, self).create( subset_name, instance_data, @@ -44,33 +45,14 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # get the created node instance_node = hou.node(instance.get("instance_node")) - # Get this node specific parms - # 1. get output path - output_path = hou.text.expandString( - "$HIP/pyblish/{}.fbx".format(subset_name)) - - # 2. get selection - selection = self.get_selection() - - # 3. get Vertex Cache Format - vcformat = pre_create_data.get("vcformat") - - # 3. get Valid Frame Range - trange = pre_create_data.get("trange") - - # parms dictionary - parms = { - "startnode" : selection, - "sopoutput": output_path, - "vcformat" : vcformat, - "trange" : trange - } + # get parms + parms = self.get_parms(subset_name, pre_create_data) # set parms instance_node.setParms(parms) # Lock any parameters in this list - to_lock = [] + to_lock = ["family", "id"] self.lock_parameters(instance_node, to_lock) # Overrides HoudiniCreator.get_network_categories() @@ -100,6 +82,32 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): return attrs + [vcformat, trange] + def get_parms(self, subset_name, pre_create_data): + """Get parameters values for this specific node.""" + + # 1. get output path + output_path = hou.text.expandString( + "$HIP/pyblish/{}.fbx".format(subset_name)) + + # 2. get selection + selection = self.get_selection() + + # 3. get Vertex Cache Format + vcformat = pre_create_data.get("vcformat") + + # 4. get Valid Frame Range + trange = pre_create_data.get("trange") + + # parms dictionary + parms = { + "startnode" : selection, + "sopoutput": output_path, + "vcformat" : vcformat, + "trange" : trange + } + + return parms + def get_selection(self): """Selection Logic. From b1ac707a68335990970481885f7b3ac6bcbb87c5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 7 Aug 2023 22:11:01 +0300 Subject: [PATCH 21/63] update plugins for better demonstration --- .../houdini/plugins/create/create_fbx.py | 7 ++- .../hosts/houdini/plugins/load/load_fbx.py | 8 ++-- .../plugins/publish/collect_fbx_type.py | 22 ++++----- .../houdini/plugins/publish/extract_fbx.py | 48 +++++++++++++++---- .../publish/validate_fbx_hierarchy_path.py | 10 ++-- 5 files changed, 66 insertions(+), 29 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 9e1dbea2f4..2bfdb3e729 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -21,12 +21,15 @@ import hou class CreateFilmboxFBX(plugin.HoudiniCreator): - """Filmbox FBX Driver""" + """Filmbox FBX Driver.""" + + # you should set identifier = "io.openpype.creators.houdini.filmboxfbx" label = "Filmbox FBX" family = "filmboxfbx" icon = "fa5s.cubes" + # optional to set default_variant = "Main" default_variants = ["Main", "Test"] @@ -36,7 +39,7 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # set node type instance_data.update({"node_type": "filmboxfbx"}) - # create instance (calls super create method) + # create instance (calls HoudiniCreator.create()) instance = super(CreateFilmboxFBX, self).create( subset_name, instance_data, diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 3f66c8e88f..993b57ad21 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -22,9 +22,11 @@ class FbxLoader(load.LoaderPlugin): label = "Load FBX" families = ["filmboxfbx", "fbx"] representations = ["fbx"] - order = -10 # you can use this by default. - icon = "code-fork" # you can use this by default. - color = "orange" # you can use this by default. + + # Usually you will use these value as default + order = -10 + icon = "code-fork" + color = "orange" def load(self, context, name=None, namespace=None, data=None): diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index f1665b27f0..794d8bd6e7 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -1,10 +1,11 @@ """Collector for filmboxfbx types. -A Collector can act as a preprocessor for the validation stage. +Collectors act as a pre process for the validation stage. It is used mainly to update instance.data P.S. - There are some collectors that run by default for all types. + There are some collectors that run by default + for all types. This plugin is part of publish process guide. """ @@ -14,18 +15,21 @@ import pyblish.api class CollectFilmboxfbxType(pyblish.api.InstancePlugin): """Collect data type for filmboxfbx instance.""" - # Usually you will use this value as default - order = pyblish.api.CollectorOrder hosts = ["houdini"] families = ["filmboxfbx"] label = "Collect type of filmboxfbx" + # Usually you will use this value as default + order = pyblish.api.CollectorOrder + # overrides InstancePlugin.process() def process(self, instance): if instance.data["creator_identifier"] == "io.openpype.creators.houdini.filmboxfbx": # noqa: E501 # such a condition can be used to differentiate between - # instances by identifier even if they have the same type. + # instances by identifier becuase sometimes instances + # may have the same family but different identifier + # e.g. bgeo and alembic pass # Update instance.data with ouptut_node @@ -36,15 +40,11 @@ class CollectFilmboxfbxType(pyblish.api.InstancePlugin): # Disclaimer : As a convntin we use collect_output_node.py # to Update instance.data with ouptut_node of different types - # however, we use this collector instead for demonstration + # however, this collector is used for demonstration def get_output_node(self, instance): - """Getting output_node Logic. - - It's moved here so that it become easier to focus on - process method. - """ + """Getting output_node Logic.""" import hou diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index 6a4b541c33..102b075838 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -1,3 +1,11 @@ +"""Extract FilmBox FBX. + +Extractors are used to generate output and +update representation dictionary. + +This plugin is part of publish process guide. +""" + import os import pyblish.api @@ -10,31 +18,51 @@ import hou class ExtractRedshiftProxy(publish.Extractor): - # Usually you will use this value as default - order = pyblish.api.ExtractorOrder + 0.1 label = "Extract FilmBox FBX" families = ["filmboxfbx"] hosts = ["houdini"] + # Usually you will use this value as default + order = pyblish.api.ExtractorOrder + 0.1 + # overrides Extractor.process() def process(self, instance): + # get rop node ropnode = hou.node(instance.data.get("instance_node")) - # Get the filename from the filename parameter - # `.evalParm(parameter)` will make sure all tokens are resolved - output = ropnode.evalParm("sopoutput") - staging_dir = os.path.normpath(os.path.dirname(output)) + # render rop + render_rop(ropnode) + + # get required data + file_name, staging_dir = self.get_paths_data(ropnode) + representation = self.get_representation(instance, + file_name, + staging_dir) + + # set value type for 'representations' key to list + if "representations" not in instance.data: + instance.data["representations"] = [] + + # update instance data instance.data["stagingDir"] = staging_dir + instance.data["representations"].append(representation) + + def get_paths_data(self, ropnode): + # Get the filename from the filename parameter + output = ropnode.evalParm("sopoutput") + + staging_dir = os.path.normpath(os.path.dirname(output)) + file_name = os.path.basename(output) self.log.info("Writing FBX '%s' to '%s'" % (file_name, staging_dir)) - render_rop(ropnode) + return file_name, staging_dir - if "representations" not in instance.data: - instance.data["representations"] = [] + def get_representation(self, instance, + file_name, staging_dir): representation = { "name": "fbx", @@ -48,4 +76,4 @@ class ExtractRedshiftProxy(publish.Extractor): representation["frameStart"] = instance.data["frameStart"] representation["frameEnd"] = instance.data["frameEnd"] - instance.data["representations"].append(representation) + return representation diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index 6a2bda1bca..e98e562fe8 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- """Validate path attribute for all primitives. +Validators are used to verify the work of artists, +by running some checks which automates the approval process. + It's almost the same as 'validate_primitive_hierarchy_paths.py' however this one includes extra comments for demonstration. @@ -44,12 +47,13 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): by default. """ - # Usually you will use this value as default - order = ValidateContentsOrder + 0.1 families = ["filmboxfbx"] hosts = ["houdini"] label = "Validate FBX Hierarchy Path" + # Usually you will use this value as default + order = ValidateContentsOrder + 0.1 + # Validation can have as many actions as you want # all of these actions are defined in a seperate place # unlike the repair action @@ -69,7 +73,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): # This method was named get_invalid as a convention # it's also used by SelectInvalidAction to select - # the returned node + # the returned nodes @classmethod def get_invalid(cls, instance): From f41ad17b7bbf962baf209adcce243372ab7948bd Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 7 Aug 2023 22:22:47 +0300 Subject: [PATCH 22/63] update comments --- openpype/hosts/houdini/plugins/load/load_fbx.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 993b57ad21..5294f5248d 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -30,14 +30,19 @@ class FbxLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): + # get file path file_path = self.get_file_path(context) + # get necessary data namespace, node_name = self.get_node_name(context, name, namespace) + # create load tree nodes = self.create_load_node_tree(file_path, node_name, name) self[:] = nodes + # Call containerise function which does some + # automations for you containerised_nodes = self.get_containerised_nodes( nodes, context, node_name, namespace ) @@ -96,7 +101,7 @@ class FbxLoader(load.LoaderPlugin): return namespace, node_name def create_load_node_tree(self, file_path, node_name, subset_name): - """Create Load node network. + """Create Load network. you can start building your tree at any obj level. it'll be much easier to build it in the root obj level. @@ -148,6 +153,12 @@ class FbxLoader(load.LoaderPlugin): return nodes def get_containerised_nodes(self, nodes, context, node_name, namespace): + """Call containerise function. + + It does some automations that you don't have to worry about, e.g. + 1. It moves created nodes to the AVALON_CONTAINERS subnetwork + 2. Add extra parameters + """ containerised_nodes = pipeline.containerise( node_name, namespace, From 77d18d0b060d009a80102f00a3f76b31f488bd03 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 22 Aug 2023 23:20:33 +0300 Subject: [PATCH 23/63] make hound happy --- .../houdini/plugins/create/create_fbx.py | 32 +++++++++---------- .../hosts/houdini/plugins/load/load_fbx.py | 10 +++--- .../plugins/publish/collect_fbx_type.py | 1 - .../publish/validate_fbx_hierarchy_path.py | 1 + 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 2bfdb3e729..1ff63ab2c4 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -69,19 +69,19 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): def get_pre_create_attr_defs(self): attrs = super().get_pre_create_attr_defs() vcformat = EnumDef("vcformat", - items={ - 0: "Maya Compatible (MC)", - 1: "3DS MAX Compatible (PC2)" - }, - default=0, - label="Vertex Cache Format") + items={ + 0: "Maya Compatible (MC)", + 1: "3DS MAX Compatible (PC2)" + }, + default=0, + label="Vertex Cache Format") trange = EnumDef("trange", - items={ - 0: "Render Current Frame", - 1: "Render Frame Range" - }, - default=0, - label="Valid Frame Range") + items={ + 0: "Render Current Frame", + 1: "Render Frame Range" + }, + default=0, + label="Valid Frame Range") return attrs + [vcformat, trange] @@ -103,10 +103,10 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # parms dictionary parms = { - "startnode" : selection, + "startnode": selection, "sopoutput": output_path, - "vcformat" : vcformat, - "trange" : trange + "vcformat": vcformat, + "trange": trange } return parms @@ -139,7 +139,7 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): ) # Allow object level paths to Geometry nodes (e.g. /obj/geo1) - # but do not allow other object level nodes types like cameras, etc. + # but do not allow other object level nodes types like cameras. elif isinstance(selected_node, hou.ObjNode) and \ selected_node.type().name() in ["geo"]: diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 5294f5248d..34f75e1485 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -126,19 +126,19 @@ class FbxLoader(load.LoaderPlugin): file_node.destroy() # Create a new file node - file_node = parent_node.createNode("file", node_name= node_name) - file_node.setParms({"file":file_path}) + file_node = parent_node.createNode("file", node_name=node_name) + file_node.setParms({"file": file_path}) # Create attribute delete attribdelete_name = "attribdelete_{}".format(subset_name) attribdelete = parent_node.createNode("attribdelete", - node_name= attribdelete_name) - attribdelete.setParms({"ptdel":"fbx_*"}) + node_name=attribdelete_name) + attribdelete.setParms({"ptdel": "fbx_*"}) attribdelete.setInput(0, file_node) # Create a Null node null_name = "OUT_{}".format(subset_name) - null = parent_node.createNode("null", node_name= null_name) + null = parent_node.createNode("null", node_name=null_name) null.setInput(0, attribdelete) # Ensure display flag is on the file_node input node and not on the OUT diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index 794d8bd6e7..3ee2541f72 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -42,7 +42,6 @@ class CollectFilmboxfbxType(pyblish.api.InstancePlugin): # to Update instance.data with ouptut_node of different types # however, this collector is used for demonstration - def get_output_node(self, instance): """Getting output_node Logic.""" diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index e98e562fe8..9208a16bd1 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -29,6 +29,7 @@ from openpype.hosts.houdini.api.action import ( import hou + # Each validation can have a single repair action # which calls the repair method class AddDefaultPathAction(RepairAction): From 0fe908a7cc4e2130e36f52820f9064ba48621d59 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 22 Aug 2023 23:23:56 +0300 Subject: [PATCH 24/63] make hound happy again --- openpype/hosts/houdini/plugins/create/create_fbx.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index 1ff63ab2c4..a92a4a5a24 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -70,11 +70,11 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): attrs = super().get_pre_create_attr_defs() vcformat = EnumDef("vcformat", items={ - 0: "Maya Compatible (MC)", - 1: "3DS MAX Compatible (PC2)" - }, - default=0, - label="Vertex Cache Format") + 0: "Maya Compatible (MC)", + 1: "3DS MAX Compatible (PC2)" + }, + default=0, + label="Vertex Cache Format") trange = EnumDef("trange", items={ 0: "Render Current Frame", From 4bfde17a420cc3686734d049728e7eaf66b51538 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 22 Aug 2023 23:26:21 +0300 Subject: [PATCH 25/63] make hound happy --- openpype/hosts/houdini/plugins/create/create_fbx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index a92a4a5a24..e26fd660ba 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -70,8 +70,8 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): attrs = super().get_pre_create_attr_defs() vcformat = EnumDef("vcformat", items={ - 0: "Maya Compatible (MC)", - 1: "3DS MAX Compatible (PC2)" + 0: "Maya Compatible (MC)", + 1: "3DS MAX Compatible (PC2)" }, default=0, label="Vertex Cache Format") From 433160e75ae70c2a17d5460c6815588bb17c58b0 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 22 Aug 2023 23:29:02 +0300 Subject: [PATCH 26/63] make hound happy again --- openpype/hosts/houdini/plugins/create/create_fbx.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index e26fd660ba..ed95daafca 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -72,16 +72,16 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): items={ 0: "Maya Compatible (MC)", 1: "3DS MAX Compatible (PC2)" - }, + }, default=0, label="Vertex Cache Format") trange = EnumDef("trange", items={ 0: "Render Current Frame", 1: "Render Frame Range" - }, - default=0, - label="Valid Frame Range") + }, + default=0, + label="Valid Frame Range") return attrs + [vcformat, trange] From 58408817999f053118a5c14cff8058c909a512cf Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 22 Aug 2023 23:30:00 +0300 Subject: [PATCH 27/63] make hound happy --- openpype/hosts/houdini/plugins/create/create_fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index ed95daafca..cac90f1e87 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -72,7 +72,7 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): items={ 0: "Maya Compatible (MC)", 1: "3DS MAX Compatible (PC2)" - }, + }, default=0, label="Vertex Cache Format") trange = EnumDef("trange", From ee8a2b1aa37567ccfc77fe3f31562eca0622f294 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 23 Aug 2023 19:10:40 +0300 Subject: [PATCH 28/63] update geo validation --- .../publish/validate_fbx_hierarchy_path.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index 9208a16bd1..e060756801 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -17,7 +17,10 @@ This plugin is part of publish process guide. """ import pyblish.api -from openpype.pipeline import PublishValidationError +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) from openpype.pipeline.publish import ( ValidateContentsOrder, RepairAction, @@ -37,7 +40,8 @@ class AddDefaultPathAction(RepairAction): icon = "mdi.pencil-plus-outline" -class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): +class ValidateFBXPrimitiveHierarchyPaths(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validate all primitives build hierarchy from attribute when enabled. @@ -50,7 +54,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): families = ["filmboxfbx"] hosts = ["houdini"] - label = "Validate FBX Hierarchy Path" + label = "Validate Prims Hierarchy Path (FBX)" # Usually you will use this value as default order = ValidateContentsOrder + 0.1 @@ -61,6 +65,10 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): actions = [SelectInvalidAction, AddDefaultPathAction, SelectROPAction] + # 'OptionalPyblishPluginMixin' where logic for 'optional' is implemented. + # It requires updating project settings + optional = True + # overrides InstancePlugin.process() def process(self, instance): invalid = self.get_invalid(instance) @@ -108,19 +116,21 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): cls.log.debug("Checking for attribute: %s", path_attr) - if not hasattr(output_node, "geometry"): - # In the case someone has explicitly set an Object - # node instead of a SOP node in Geometry context - # then for now we ignore - this allows us to also - # export object transforms. - cls.log.warning("No geometry output node found," + # Get frame + frame = hou.intFrame() + trange = rop_node.parm("trange").eval() + if trange: + frame = int(hou.playbar.frameRange()[0]) + + frame = instance.data.get("frameStart", frame) + + # Get Geo at that frame + geo = output_node.geometryAtFrame(frame) + if not geo: + cls.log.warning("No geometry found," " skipping check..") return - # Check if the primitive attribute exists - frame = instance.data.get("frameStart", 0) - geo = output_node.geometryAtFrame(frame) - # If there are no primitives on the current frame then # we can't check whether the path names are correct. # So we'll just issue a warning that the check can't From 1d0c78f044a4f0d5dd6092968ff543f09d5c5987 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 23 Aug 2023 19:11:04 +0300 Subject: [PATCH 29/63] update label --- .../plugins/publish/validate_primitive_hierarchy_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py index 471fa5b6d1..930978ef16 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py @@ -26,7 +26,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): order = ValidateContentsOrder + 0.1 families = ["abc"] hosts = ["houdini"] - label = "Validate Prims Hierarchy Path" + label = "Validate Prims Hierarchy Path (ABC)" actions = [AddDefaultPathAction] def process(self, instance): From 4ea1f2b586835f6a6cad97633d5149fc614ce991 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 23 Aug 2023 19:11:47 +0300 Subject: [PATCH 30/63] include fbx validator in settings --- openpype/settings/defaults/project_settings/houdini.json | 5 +++++ .../projects_schema/schemas/schema_houdini_publish.json | 6 +++++- server_addon/houdini/server/settings/publish_plugins.py | 8 ++++++++ server_addon/houdini/server/version.py | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 9d047c28bd..2295422202 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -97,6 +97,11 @@ "enabled": true, "optional": true, "active": true + }, + "ValidateFBXPrimitiveHierarchyPaths": { + "enabled": true, + "optional": true, + "active": true } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json index aa6eaf5164..d58b36eff1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json @@ -43,8 +43,12 @@ { "key": "ValidateContainers", "label": "ValidateContainers" + }, + { + "key": "ValidateFBXPrimitiveHierarchyPaths", + "label": "Validate Path Attribute for FBX" } ] } ] -} \ No newline at end of file +} diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 7d35d7e634..44ff00c318 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -133,6 +133,9 @@ class PublishPluginsModel(BaseSettingsModel): ValidateContainers: ValidateContainersModel = Field( default_factory=ValidateContainersModel, title="Validate Latest Containers.") + ValidateFBXPrimitiveHierarchyPaths: ValidateContainersModel = Field( + default_factory=ValidateContainersModel, + title="Validate Path Attribute for FBX.") DEFAULT_HOUDINI_PUBLISH_SETTINGS = { @@ -152,5 +155,10 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "enabled": True, "optional": True, "active": True + }, + "ValidateFBXPrimitiveHierarchyPaths": { + "enabled": True, + "optional": True, + "active": True } } diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 485f44ac21..b3f4756216 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" From 96893f6ab0a56db976aa14709f2fbbd734b70f88 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 23 Aug 2023 19:56:29 +0300 Subject: [PATCH 31/63] update doc string --- openpype/hosts/houdini/plugins/create/create_fbx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_fbx.py index cac90f1e87..b45aef8fdf 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_fbx.py @@ -67,6 +67,8 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # Overrides HoudiniCreator.get_pre_create_attr_defs() def get_pre_create_attr_defs(self): + """Add settings for users. """ + attrs = super().get_pre_create_attr_defs() vcformat = EnumDef("vcformat", items={ From e181eeab93db55b7804f3b6f5055369e1a30ede7 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 24 Aug 2023 16:02:18 +0300 Subject: [PATCH 32/63] convert filmboxfbx to UE static mesh --- ...ate_fbx.py => create_unreal_staticmesh.py} | 25 ++++++--------- .../hosts/houdini/plugins/load/load_fbx.py | 2 +- .../plugins/publish/collect_fbx_type.py | 10 +++--- .../houdini/plugins/publish/extract_fbx.py | 4 +-- .../publish/validate_fbx_hierarchy_path.py | 2 +- .../publish/validate_sop_output_node.py | 2 +- openpype/plugins/publish/integrate.py | 3 +- .../defaults/project_settings/houdini.json | 13 ++++++++ .../schemas/schema_houdini_create.json | 31 +++++++++++++++++++ .../server/settings/publish_plugins.py | 29 +++++++++++++++++ 10 files changed, 94 insertions(+), 27 deletions(-) rename openpype/hosts/houdini/plugins/create/{create_fbx.py => create_unreal_staticmesh.py} (89%) diff --git a/openpype/hosts/houdini/plugins/create/create_fbx.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py similarity index 89% rename from openpype/hosts/houdini/plugins/create/create_fbx.py rename to openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index b45aef8fdf..4543f14934 100644 --- a/openpype/hosts/houdini/plugins/create/create_fbx.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -20,17 +20,18 @@ from openpype.lib import EnumDef import hou -class CreateFilmboxFBX(plugin.HoudiniCreator): +class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): """Filmbox FBX Driver.""" # you should set - identifier = "io.openpype.creators.houdini.filmboxfbx" - label = "Filmbox FBX" - family = "filmboxfbx" + identifier = "io.openpype.creators.houdini.unrealstaticmesh" + label = "Unreal - Static Mesh" + family = "staticMesh" icon = "fa5s.cubes" # optional to set default_variant = "Main" + # 'default_variants' will be overriden by settings. default_variants = ["Main", "Test"] # Overrides HoudiniCreator.create() @@ -40,7 +41,7 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): instance_data.update({"node_type": "filmboxfbx"}) # create instance (calls HoudiniCreator.create()) - instance = super(CreateFilmboxFBX, self).create( + instance = super(HouCreateUnrealStaticMesh, self).create( subset_name, instance_data, pre_create_data) @@ -77,15 +78,8 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): }, default=0, label="Vertex Cache Format") - trange = EnumDef("trange", - items={ - 0: "Render Current Frame", - 1: "Render Frame Range" - }, - default=0, - label="Valid Frame Range") - return attrs + [vcformat, trange] + return attrs + [vcformat] def get_parms(self, subset_name, pre_create_data): """Get parameters values for this specific node.""" @@ -100,8 +94,9 @@ class CreateFilmboxFBX(plugin.HoudiniCreator): # 3. get Vertex Cache Format vcformat = pre_create_data.get("vcformat") - # 4. get Valid Frame Range - trange = pre_create_data.get("trange") + # 4. Valid Frame Range + # It should publish the current frame. + trange = 0 # parms dictionary parms = { diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 34f75e1485..d661f84eeb 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -20,7 +20,7 @@ class FbxLoader(load.LoaderPlugin): """Load fbx files to Houdini.""" label = "Load FBX" - families = ["filmboxfbx", "fbx"] + families = ["staticMesh", "fbx"] representations = ["fbx"] # Usually you will use these value as default diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index 3ee2541f72..4c83829c67 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -13,11 +13,11 @@ import pyblish.api class CollectFilmboxfbxType(pyblish.api.InstancePlugin): - """Collect data type for filmboxfbx instance.""" + """Collect data type for fbx instance.""" hosts = ["houdini"] - families = ["filmboxfbx"] - label = "Collect type of filmboxfbx" + families = ["fbx", "staticMesh"] + label = "Collect type of fbx" # Usually you will use this value as default order = pyblish.api.CollectorOrder @@ -25,12 +25,12 @@ class CollectFilmboxfbxType(pyblish.api.InstancePlugin): # overrides InstancePlugin.process() def process(self, instance): - if instance.data["creator_identifier"] == "io.openpype.creators.houdini.filmboxfbx": # noqa: E501 + if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh": # noqa: E501 # such a condition can be used to differentiate between # instances by identifier becuase sometimes instances # may have the same family but different identifier # e.g. bgeo and alembic - pass + instance.data["families"] += ["fbx"] # Update instance.data with ouptut_node out_node = self.get_output_node(instance) diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index 102b075838..8e45a554c0 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -18,8 +18,8 @@ import hou class ExtractRedshiftProxy(publish.Extractor): - label = "Extract FilmBox FBX" - families = ["filmboxfbx"] + label = "Extract FBX" + families = ["fbx"] hosts = ["houdini"] # Usually you will use this value as default diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index e060756801..01cb01f497 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -52,7 +52,7 @@ class ValidateFBXPrimitiveHierarchyPaths(pyblish.api.InstancePlugin, by default. """ - families = ["filmboxfbx"] + families = ["fbx"] hosts = ["houdini"] label = "Validate Prims Hierarchy Path (FBX)" 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 da9752505a..2b426d96dd 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["pointcache", "vdbcache", "filmboxfbx"] + families = ["pointcache", "vdbcache", "fbx"] hosts = ["houdini"] label = "Validate Output Node" actions = [SelectROPAction, SelectInvalidAction] diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index ee4af1a0e0..be07cffe72 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -139,8 +139,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "simpleUnrealTexture", "online", "uasset", - "blendScene", - "filmboxfbx" + "blendScene" ] default_template_name = "publish" diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 2295422202..e19e71de17 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -19,6 +19,19 @@ ], "ext": ".ass" }, + "HouCreateUnrealStaticMesh": { + "enabled": true, + "default_variants": [ + "Main" + ], + "static_mesh_prefix": "S", + "collision_prefixes": [ + "UBX", + "UCP", + "USP", + "UCX" + ] + }, "CreateAlembicCamera": { "enabled": true, "default_variants": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json index 799bc0e81a..3d55bd834f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json @@ -39,6 +39,37 @@ ] }, + { + "type": "dict", + "collapsible": true, + "key": "HouCreateUnrealStaticMesh", + "label": "Create Unreal - Static Mesh", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default Variants", + "object_type": "text" + }, + { + "type": "text", + "key": "static_mesh_prefix", + "label": "Static Mesh Prefix" + }, + { + "type": "list", + "key": "collision_prefixes", + "label": "Collision Mesh Prefixes", + "object_type": "text" + } + ] + }, { "type": "schema_template", "name": "template_create_plugin", diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 44ff00c318..5ddfa07bc4 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -20,11 +20,27 @@ class CreateArnoldAssModel(BaseSettingsModel): ) ext: str = Field(Title="Extension") +class HouCreateUnrealStaticMeshModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + default_variants: list[str] = Field( + default_factory=list, + title="Default Products" + ) + static_mesh_prefixes: str = Field("S", title="Static Mesh Prefix") + collision_prefixes: list[str] = Field( + default_factory=list, + title="Collision Prefixes" + ) class CreatePluginsModel(BaseSettingsModel): CreateArnoldAss: CreateArnoldAssModel = Field( default_factory=CreateArnoldAssModel, title="Create Alembic Camera") + # "-" is not compatible in the new model + HouCreateUnrealStaticMesh: HouCreateUnrealStaticMeshModel = Field( + default_factory=HouCreateUnrealStaticMeshModel, + title="Create Unreal_Static Mesh" + ) CreateAlembicCamera: CreatorModel = Field( default_factory=CreatorModel, title="Create Alembic Camera") @@ -63,6 +79,19 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = { "default_variants": ["Main"], "ext": ".ass" }, + "HouCreateUnrealStaticMesh": { + "enabled": True, + "default_variants": [ + "Main" + ], + "static_mesh_prefix": "S", + "collision_prefixes": [ + "UBX", + "UCP", + "USP", + "UCX" + ] + }, "CreateAlembicCamera": { "enabled": True, "default_variants": ["Main"] From b6a81bd64b74c60e2b0e8e271f7f2704b6b80bf7 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Thu, 24 Aug 2023 16:06:21 +0300 Subject: [PATCH 33/63] resolve hound conversations --- .../hosts/houdini/plugins/create/create_unreal_staticmesh.py | 3 ++- server_addon/houdini/server/settings/publish_plugins.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index 4543f14934..2a284f7979 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -154,7 +154,8 @@ class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): if not selection: self.log.debug( - "Selection isn't valid. 'Export' in filmboxfbx will be empty." + "Selection isn't valid. 'Export' in " + "filmboxfbx will be empty." ) else: self.log.debug( diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 5ddfa07bc4..aaa1d0ba1d 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -20,6 +20,7 @@ class CreateArnoldAssModel(BaseSettingsModel): ) ext: str = Field(Title="Extension") + class HouCreateUnrealStaticMeshModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") default_variants: list[str] = Field( @@ -32,6 +33,7 @@ class HouCreateUnrealStaticMeshModel(BaseSettingsModel): title="Collision Prefixes" ) + class CreatePluginsModel(BaseSettingsModel): CreateArnoldAss: CreateArnoldAssModel = Field( default_factory=CreateArnoldAssModel, From e1f2a77089f406dfa7ebb8bfc22102155f1a3f1b Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 25 Aug 2023 22:36:38 +0300 Subject: [PATCH 34/63] Introduce houdini unreal static mesh --- .../create/create_unreal_staticmesh.py | 36 +++-- .../houdini/plugins/publish/extract_fbx.py | 2 +- .../publish/validate_fbx_hierarchy_path.py | 14 +- .../publish/validate_mesh_is_static.py | 133 ++++++++++++++++++ .../defaults/project_settings/houdini.json | 2 +- .../schemas/schema_houdini_create.json | 2 +- .../server/settings/publish_plugins.py | 8 +- 7 files changed, 169 insertions(+), 28 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index 2a284f7979..479392f231 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- -"""Creator plugin for creating fbx. +"""Creator plugin for creating Unreal Static Meshes. -It was made to pratice publish process. +Unreal Static Meshes will be published as FBX. Filmbox by default expects an ObjNode however, we set the sop node explictly to eleminate any confusion. -This creator by default will select +This will make Filmbox to ignore any object transformations! + +get_obj_output selects the output sop with mimimum idx or the node with render flag isntead. @@ -15,13 +17,13 @@ This plugin is part of publish process guide. """ from openpype.hosts.houdini.api import plugin -from openpype.lib import EnumDef +from openpype.lib import BoolDef, EnumDef import hou -class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): - """Filmbox FBX Driver.""" +class CreateUnrealStaticMesh(plugin.HoudiniCreator): + """Unreal Static Meshes with collisions. """ # you should set identifier = "io.openpype.creators.houdini.unrealstaticmesh" @@ -41,7 +43,7 @@ class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): instance_data.update({"node_type": "filmboxfbx"}) # create instance (calls HoudiniCreator.create()) - instance = super(HouCreateUnrealStaticMesh, self).create( + instance = super(CreateUnrealStaticMesh, self).create( subset_name, instance_data, pre_create_data) @@ -78,8 +80,15 @@ class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): }, default=0, label="Vertex Cache Format") + convert_units = BoolDef("convertunits", + tooltip="When on, the FBX is converted" + "from the current Houdini " + "system units to the native " + "FBX unit of centimeters.", + default=False, + label="Convert Units") - return attrs + [vcformat] + return attrs + [vcformat, convert_units] def get_parms(self, subset_name, pre_create_data): """Get parameters values for this specific node.""" @@ -94,15 +103,18 @@ class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): # 3. get Vertex Cache Format vcformat = pre_create_data.get("vcformat") - # 4. Valid Frame Range - # It should publish the current frame. - trange = 0 + # 4. get convert_units + convertunits = pre_create_data.get("convertunits") + + # 5. get Valid Frame Range + trange = 1 # parms dictionary parms = { "startnode": selection, "sopoutput": output_path, "vcformat": vcformat, + "convertunits": convertunits, "trange": trange } @@ -166,7 +178,7 @@ class HouCreateUnrealStaticMesh(plugin.HoudiniCreator): def get_obj_output(self, obj_node): """Find output node with the smallest 'outputidx' - or return tje node with the render flag instead. + or return the node with the render flag instead. """ outputs = obj_node.subnetOutputs() diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index 8e45a554c0..c0e84c00c8 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -16,7 +16,7 @@ from openpype.hosts.houdini.api.lib import render_rop import hou -class ExtractRedshiftProxy(publish.Extractor): +class ExtractFBX(publish.Extractor): label = "Extract FBX" families = ["fbx"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py index 01cb01f497..be73ccd223 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py @@ -65,8 +65,8 @@ class ValidateFBXPrimitiveHierarchyPaths(pyblish.api.InstancePlugin, actions = [SelectInvalidAction, AddDefaultPathAction, SelectROPAction] - # 'OptionalPyblishPluginMixin' where logic for 'optional' is implemented. - # It requires updating project settings + # 'OptionalPyblishPluginMixin' adds the functionality to + # enable/disable plugins, It requires adding new settings. optional = True # overrides InstancePlugin.process() @@ -116,13 +116,9 @@ class ValidateFBXPrimitiveHierarchyPaths(pyblish.api.InstancePlugin, cls.log.debug("Checking for attribute: %s", path_attr) - # Get frame - frame = hou.intFrame() - trange = rop_node.parm("trange").eval() - if trange: - frame = int(hou.playbar.frameRange()[0]) - - frame = instance.data.get("frameStart", frame) + # Use current frame if "frameStart" doesn't exist + # This only happens when ""trange" is 0 + frame = instance.data.get("frameStart", hou.intFrame()) # Get Geo at that frame geo = output_node.geometryAtFrame(frame) diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py new file mode 100644 index 0000000000..fa6442d0d4 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +"""Validate mesh is static. + +This plugin is part of publish process guide. +""" + +import pyblish.api +from openpype.pipeline import PublishValidationError +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) +from openpype.hosts.houdini.api.action import ( + SelectInvalidAction, + SelectROPAction, +) + +import hou + + +# Each validation can have a single repair action +# which calls the repair method +class FreezeTimeAction(RepairAction): + label = "Freeze Time" + icon = "ei.pause-alt" + + +class ValidateMeshIsStatic(pyblish.api.InstancePlugin): + """Validate mesh is static. + + It checks if node is time dependant. + """ + + families = ["staticMesh"] + hosts = ["houdini"] + label = "Validate mesh is static" + + # Usually you will use this value as default + order = ValidateContentsOrder + 0.1 + + # Validation can have as many actions as you want + # all of these actions are defined in a seperate place + # unlike the repair action + actions = [FreezeTimeAction, SelectInvalidAction, + SelectROPAction] + + # overrides InstancePlugin.process() + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + nodes = [n.path() for n in invalid] + raise PublishValidationError( + "See log for details. " + "Invalid nodes: {0}".format(nodes), + title=self.label + ) + + # This method was named get_invalid as a convention + # it's also used by SelectInvalidAction to select + # the returned nodes + @classmethod + def get_invalid(cls, instance): + + output_node = instance.data.get("output_node") + if output_node.isTimeDependent(): + cls.log.info("Mesh is not static!") + return [output_node] + + + + # what repair action expects to find and call + @classmethod + def repair(cls, instance): + """Adds a time shift node. + + It should kill time dependency. + """ + + rop_node = hou.node(instance.data["instance_node"]) + # I'm doing so because an artist may change output node + # before clicking the button. + output_node = rop_node.parm("startnode").evalAsNode() + + if not output_node: + cls.log.debug( + "Action isn't performed, invalid SOP Path on %s", + rop_node + ) + return + + # This check to prevent the action from running multiple times. + # git_invalid only returns [output_node] when + # path attribute is the problem + if cls.get_invalid(instance) != [output_node]: + return + + + + time_shift = output_node.parent().createNode("timeshift", + "freeze_time") + time_shift.parm("frame").deleteAllKeyframes() + + frame = instance.data.get("frameStart", hou.intFrame()) + time_shift.parm("frame").set(frame) + + cls.log.debug( + "'%s' was created. It will kill time dependency." + , time_shift + ) + + time_shift.setGenericFlag(hou.nodeFlag.DisplayComment, True) + time_shift.setComment( + 'This node was created automatically by ' + '"Freeze Time" Action' + '\nFeel free to modify or replace it.' + ) + + if output_node.type().name() in ["null", "output"]: + # Connect before + time_shift.setFirstInput(output_node.input(0)) + time_shift.moveToGoodPosition() + output_node.setFirstInput(time_shift) + output_node.moveToGoodPosition() + else: + # Connect after + time_shift.setFirstInput(output_node) + rop_node.parm("startnode").set(time_shift.path()) + time_shift.moveToGoodPosition() + + cls.log.debug( + "SOP path on '%s' updated to new output node '%s'", + rop_node, time_shift + ) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index e19e71de17..c39eb717fd 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -19,7 +19,7 @@ ], "ext": ".ass" }, - "HouCreateUnrealStaticMesh": { + "CreateUnrealStaticMesh": { "enabled": true, "default_variants": [ "Main" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json index 3d55bd834f..b19761df91 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json @@ -42,7 +42,7 @@ { "type": "dict", "collapsible": true, - "key": "HouCreateUnrealStaticMesh", + "key": "CreateUnrealStaticMesh", "label": "Create Unreal - Static Mesh", "checkbox_key": "enabled", "children": [ diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index aaa1d0ba1d..1e5cd7f551 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -21,7 +21,7 @@ class CreateArnoldAssModel(BaseSettingsModel): ext: str = Field(Title="Extension") -class HouCreateUnrealStaticMeshModel(BaseSettingsModel): +class CreateUnrealStaticMeshModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") default_variants: list[str] = Field( default_factory=list, @@ -39,8 +39,8 @@ class CreatePluginsModel(BaseSettingsModel): default_factory=CreateArnoldAssModel, title="Create Alembic Camera") # "-" is not compatible in the new model - HouCreateUnrealStaticMesh: HouCreateUnrealStaticMeshModel = Field( - default_factory=HouCreateUnrealStaticMeshModel, + CreateUnrealStaticMesh: CreateUnrealStaticMeshModel = Field( + default_factory=CreateUnrealStaticMeshModel, title="Create Unreal_Static Mesh" ) CreateAlembicCamera: CreatorModel = Field( @@ -81,7 +81,7 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = { "default_variants": ["Main"], "ext": ".ass" }, - "HouCreateUnrealStaticMesh": { + "CreateUnrealStaticMesh": { "enabled": True, "default_variants": [ "Main" From 3d658bb3f23aa9bf50f0a46a62f810b01bc06139 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 25 Aug 2023 22:39:10 +0300 Subject: [PATCH 35/63] resolve hound conversations --- .../houdini/plugins/publish/validate_mesh_is_static.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py index fa6442d0d4..ac80fda537 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -66,8 +66,6 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin): cls.log.info("Mesh is not static!") return [output_node] - - # what repair action expects to find and call @classmethod def repair(cls, instance): @@ -94,8 +92,6 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin): if cls.get_invalid(instance) != [output_node]: return - - time_shift = output_node.parent().createNode("timeshift", "freeze_time") time_shift.parm("frame").deleteAllKeyframes() @@ -104,8 +100,8 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin): time_shift.parm("frame").set(frame) cls.log.debug( - "'%s' was created. It will kill time dependency." - , time_shift + "'%s' was created. It will kill time dependency.", + time_shift ) time_shift.setGenericFlag(hou.nodeFlag.DisplayComment, True) From c610a850b9e12023b2a6e5f92769b2a971b7b2d1 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Sat, 26 Aug 2023 01:23:20 +0300 Subject: [PATCH 36/63] dynamic subset name --- .../plugins/create/create_unreal_staticmesh.py | 14 ++++++++++++++ .../settings/defaults/project_settings/global.json | 3 ++- server_addon/core/server/settings/tools.py | 3 ++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index 479392f231..179c81510a 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -90,6 +90,20 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): return attrs + [vcformat, convert_units] + # Overrides BaseCreator.get_dynamic_data() + def get_dynamic_data( + self, variant, task_name, asset_doc, project_name, host_name, instance + ): + """ + The default subset name templates for Unreal include {asset} and thus + we should pass that along as dynamic data. + """ + dynamic_data = super(CreateUnrealStaticMesh, self).get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name, instance + ) + dynamic_data["asset"] = asset_doc["name"] + return dynamic_data + def get_parms(self, subset_name, pre_create_data): """Get parameters values for this specific node.""" diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 06a595d1c5..52ac745f6d 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -429,7 +429,8 @@ "staticMesh" ], "hosts": [ - "maya" + "maya", + "houdini" ], "task_types": [], "tasks": [], diff --git a/server_addon/core/server/settings/tools.py b/server_addon/core/server/settings/tools.py index 7befc795e4..5dbe6ab215 100644 --- a/server_addon/core/server/settings/tools.py +++ b/server_addon/core/server/settings/tools.py @@ -370,7 +370,8 @@ DEFAULT_TOOLS_VALUES = { "staticMesh" ], "hosts": [ - "maya" + "maya", + "houdini" ], "task_types": [], "tasks": [], From 3a43806a5b30496c44b0adb469f1b1a52355b66d Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Sat, 26 Aug 2023 01:23:52 +0300 Subject: [PATCH 37/63] dynamic subset name --- server_addon/core/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py index 485f44ac21..b3f4756216 100644 --- a/server_addon/core/server/version.py +++ b/server_addon/core/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" From e4819fc952a8711e013c5e473933559219014483 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 01:02:32 +0300 Subject: [PATCH 38/63] Kayla's and BigRoy's comments --- .../create/create_unreal_staticmesh.py | 4 +- .../hosts/houdini/plugins/load/load_fbx.py | 58 ++++++++----------- .../plugins/publish/collect_fbx_type.py | 32 +--------- .../plugins/publish/collect_output_node.py | 7 ++- 4 files changed, 34 insertions(+), 67 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index 179c81510a..a048965364 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -26,8 +26,8 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): """Unreal Static Meshes with collisions. """ # you should set - identifier = "io.openpype.creators.houdini.unrealstaticmesh" - label = "Unreal - Static Mesh" + identifier = "io.openpype.creators.houdini.unrealstaticmesh.fbx" + label = "Unreal - Static Mesh (FBX)" family = "staticMesh" icon = "fa5s.cubes" diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index d661f84eeb..2e4dafc2d8 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -31,7 +31,7 @@ class FbxLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): # get file path - file_path = self.get_file_path(context) + file_path = self.get_file_path(context=context) # get necessary data namespace, node_name = self.get_node_name(context, name, namespace) @@ -41,10 +41,15 @@ class FbxLoader(load.LoaderPlugin): self[:] = nodes - # Call containerise function which does some - # automations for you - containerised_nodes = self.get_containerised_nodes( - nodes, context, node_name, namespace + # Call containerise function which does some automations for you + # like moving created nodes to the AVALON_CONTAINERS subnetwork + containerised_nodes = pipeline.containerise( + node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="", ) return containerised_nodes @@ -61,8 +66,7 @@ class FbxLoader(load.LoaderPlugin): return # Update the file path - file_path = get_representation_path(representation) - file_path = self.format_path(file_path, representation) + file_path = self.get_file_path(representation=representation) file_node.setParms({"file": file_path}) @@ -77,15 +81,18 @@ class FbxLoader(load.LoaderPlugin): def switch(self, container, representation): self.update(container, representation) - def get_file_path(self, context): + def get_file_path(self, context=None, representation=None): """Return formatted file path.""" # Format file name, Houdini only wants forward slashes - file_path = self.filepath_from_context(context) - file_path = os.path.normpath(file_path) - file_path = file_path.replace("\\", "/") + if context: + file_path = self.filepath_from_context(context) + elif representation: + file_path = get_representation_path(representation) + else: + return "" - return file_path + return file_path.replace("\\", "/") def get_node_name(self, context, name=None, namespace=None): """Define node name.""" @@ -145,27 +152,8 @@ class FbxLoader(load.LoaderPlugin): # node to optimize "debug" displaying in the viewport. file_node.setDisplayFlag(True) - # Set new position for unpack node else it gets cluttered - nodes = [parent_node, file_node, attribdelete, null] - for nr, node in enumerate(nodes): - node.setPosition([0, (0 - nr)]) + # Set new position for children nodes + parent_node.layoutChildren() - return nodes - - def get_containerised_nodes(self, nodes, context, node_name, namespace): - """Call containerise function. - - It does some automations that you don't have to worry about, e.g. - 1. It moves created nodes to the AVALON_CONTAINERS subnetwork - 2. Add extra parameters - """ - containerised_nodes = pipeline.containerise( - node_name, - namespace, - nodes, - context, - self.__class__.__name__, - suffix="", - ) - - return containerised_nodes + # Retrun all the nodes + return [parent_node, file_node, attribdelete, null] diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py index 4c83829c67..6ac40a4f50 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py @@ -16,7 +16,7 @@ class CollectFilmboxfbxType(pyblish.api.InstancePlugin): """Collect data type for fbx instance.""" hosts = ["houdini"] - families = ["fbx", "staticMesh"] + families = ["staticMesh"] label = "Collect type of fbx" # Usually you will use this value as default @@ -25,35 +25,9 @@ class CollectFilmboxfbxType(pyblish.api.InstancePlugin): # overrides InstancePlugin.process() def process(self, instance): - if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh": # noqa: E501 + if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh.fbx": # noqa: E501 # such a condition can be used to differentiate between - # instances by identifier becuase sometimes instances + # instances by identifier because sometimes instances # may have the same family but different identifier # e.g. bgeo and alembic instance.data["families"] += ["fbx"] - - # Update instance.data with ouptut_node - out_node = self.get_output_node(instance) - - if out_node: - instance.data["output_node"] = out_node - - # Disclaimer : As a convntin we use collect_output_node.py - # to Update instance.data with ouptut_node of different types - # however, this collector is used for demonstration - - def get_output_node(self, instance): - """Getting output_node Logic.""" - - import hou - - # get output node - node = hou.node(instance.data["instance_node"]) - out_node = node.parm("startnode").evalAsNode() - - if not out_node: - self.log.warning("No output node collected.") - return - - self.log.debug("Output node: %s" % out_node.path()) - return out_node diff --git a/openpype/hosts/houdini/plugins/publish/collect_output_node.py b/openpype/hosts/houdini/plugins/publish/collect_output_node.py index 601ed17b39..91bd5fdb15 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/collect_output_node.py @@ -12,7 +12,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): "imagesequence", "usd", "usdrender", - "redshiftproxy" + "redshiftproxy", + "staticMesh" ] hosts = ["houdini"] @@ -57,6 +58,10 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): elif node_type == "Redshift_Proxy_Output": out_node = node.parm("RS_archive_sopPath").evalAsNode() + + elif node_type == "filmboxfbx": + out_node = node.parm("startnode").evalAsNode() + else: raise ValueError( "ROP node type '%s' is" " not supported." % node_type From 127c63eb94b98af297287833e81f0ec9b2a8c5e7 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 01:04:11 +0300 Subject: [PATCH 39/63] hound comments --- openpype/hosts/houdini/plugins/load/load_fbx.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 2e4dafc2d8..681837e046 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -7,8 +7,6 @@ however this one includes extra comments for demonstration. This plugin is part of publish process guide. """ -import os - from openpype.pipeline import ( load, get_representation_path, From d1e82ceb7ed7e0681a9856b6376cc8831f1c2d2a Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 15:00:45 +0300 Subject: [PATCH 40/63] allow publishing sop and obj nodes --- .../create/create_unreal_staticmesh.py | 70 ++++++------------- .../publish/validate_sop_output_node.py | 2 +- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index a048965364..0b5b313d9e 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -73,6 +73,13 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): """Add settings for users. """ attrs = super().get_pre_create_attr_defs() + createsubnetroot = BoolDef("createsubnetroot", + tooltip="Create an extra root for the Export node " + "when it’s a subnetwork. This causes the " + "exporting subnetwork node to be " + "represented in the FBX file.", + default=False, + label="Create Root for Subnet") vcformat = EnumDef("vcformat", items={ 0: "Maya Compatible (MC)", @@ -88,7 +95,7 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): default=False, label="Convert Units") - return attrs + [vcformat, convert_units] + return attrs + [createsubnetroot, vcformat, convert_units] # Overrides BaseCreator.get_dynamic_data() def get_dynamic_data( @@ -123,13 +130,17 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): # 5. get Valid Frame Range trange = 1 + # 6. get createsubnetroot + createsubnetroot = pre_create_data.get("createsubnetroot") + # parms dictionary parms = { "startnode": selection, "sopoutput": output_path, "vcformat": vcformat, "convertunits": convertunits, - "trange": trange + "trange": trange, + "createsubnetroot": createsubnetroot } return parms @@ -149,36 +160,23 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): if self.selected_nodes: selected_node = self.selected_nodes[0] - # Although Houdini allows ObjNode path on `startnode` for the - # the ROP node we prefer it set to the SopNode path explicitly - - # Allow sop level paths (e.g. /obj/geo1/box1) + # Accept sop level nodes (e.g. /obj/geo1/box1) if isinstance(selected_node, hou.SopNode): selection = selected_node.path() self.log.debug( "Valid SopNode selection, 'Export' in filmboxfbx" - " will be set to '%s'." - % selected_node + " will be set to '%s'.", selected_node ) - # Allow object level paths to Geometry nodes (e.g. /obj/geo1) - # but do not allow other object level nodes types like cameras. - elif isinstance(selected_node, hou.ObjNode) and \ - selected_node.type().name() in ["geo"]: + # Accept object level nodes (e.g. /obj/geo1) + elif isinstance(selected_node, hou.ObjNode): + selection = selected_node.path() + self.log.debug( + "Valid ObjNode selection, 'Export' in filmboxfbx " + "will be set to the child path '%s'.", selection + ) - # get the output node with the minimum - # 'outputidx' or the node with display flag - sop_path = self.get_obj_output(selected_node) - - if sop_path: - selection = sop_path.path() - self.log.debug( - "Valid ObjNode selection, 'Export' in filmboxfbx " - "will be set to the child path '%s'." - % sop_path - ) - - if not selection: + else: self.log.debug( "Selection isn't valid. 'Export' in " "filmboxfbx will be empty." @@ -189,25 +187,3 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): ) return selection - - def get_obj_output(self, obj_node): - """Find output node with the smallest 'outputidx' - or return the node with the render flag instead. - """ - - outputs = obj_node.subnetOutputs() - - # if obj_node is empty - if not outputs: - return - - # if obj_node has one output child whether its - # sop output node or a node with the render flag - elif len(outputs) == 1: - return outputs[0] - - # if there are more than one, then it have multiple ouput nodes - # return the one with the minimum 'outputidx' - else: - return min(outputs, - key=lambda node: node.evalParm('outputidx')) 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 2b426d96dd..d9dee38680 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["pointcache", "vdbcache", "fbx"] + families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" actions = [SelectROPAction, SelectInvalidAction] From e6b9a7d0381a6325100c65537dd744d0796c3b31 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 17:04:22 +0300 Subject: [PATCH 41/63] validate mesh name --- .../publish/validate_fbx_hierarchy_path.py | 235 ------------------ .../publish/validate_mesh_is_static.py | 129 ---------- .../validate_unreal_staticmesh_naming.py | 102 ++++++++ 3 files changed, 102 insertions(+), 364 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py delete mode 100644 openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py create mode 100644 openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py deleted file mode 100644 index be73ccd223..0000000000 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_hierarchy_path.py +++ /dev/null @@ -1,235 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validate path attribute for all primitives. - -Validators are used to verify the work of artists, -by running some checks which automates the approval process. - -It's almost the same as -'validate_primitive_hierarchy_paths.py' -however this one includes extra comments for demonstration. - -FYI, path for fbx behaves a little differently. -In maya terms: -in Filmbox FBX: it sets the name of the object -in Alembic ROP: it sets the name of the shape - -This plugin is part of publish process guide. -""" - -import pyblish.api -from openpype.pipeline import ( - PublishValidationError, - OptionalPyblishPluginMixin -) -from openpype.pipeline.publish import ( - ValidateContentsOrder, - RepairAction, -) -from openpype.hosts.houdini.api.action import ( - SelectInvalidAction, - SelectROPAction, -) - -import hou - - -# Each validation can have a single repair action -# which calls the repair method -class AddDefaultPathAction(RepairAction): - label = "Add a default path" - icon = "mdi.pencil-plus-outline" - - -class ValidateFBXPrimitiveHierarchyPaths(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validate all primitives build hierarchy from attribute - when enabled. - - The name of the attribute must exist on the prims and have the - same name as Build Hierarchy from Attribute's `Path Attribute` - value on the FilmBox node. - This validation enables 'Build Hierarchy from Attribute' - by default. - """ - - families = ["fbx"] - hosts = ["houdini"] - label = "Validate Prims Hierarchy Path (FBX)" - - # Usually you will use this value as default - order = ValidateContentsOrder + 0.1 - - # Validation can have as many actions as you want - # all of these actions are defined in a seperate place - # unlike the repair action - actions = [SelectInvalidAction, AddDefaultPathAction, - SelectROPAction] - - # 'OptionalPyblishPluginMixin' adds the functionality to - # enable/disable plugins, It requires adding new settings. - optional = True - - # overrides InstancePlugin.process() - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - nodes = [n.path() for n in invalid] - raise PublishValidationError( - "See log for details. " - "Invalid nodes: {0}".format(nodes), - title=self.label - ) - - # This method was named get_invalid as a convention - # it's also used by SelectInvalidAction to select - # the returned nodes - @classmethod - def get_invalid(cls, instance): - - output_node = instance.data.get("output_node") - rop_node = hou.node(instance.data["instance_node"]) - - if output_node is None: - cls.log.error( - "SOP Output node in '%s' does not exist. " - "Ensure a valid SOP output path is set.", - rop_node.path() - ) - - return [rop_node] - - build_from_path = rop_node.parm("buildfrompath").eval() - if not build_from_path: - cls.log.debug( - "Filmbox FBX has 'Build from Path' disabled. " - "Enbaling it as default." - ) - rop_node.parm("buildfrompath").set(1) - - path_attr = rop_node.parm("pathattrib").eval() - if not path_attr: - cls.log.debug( - "Filmbox FBX node has no Path Attribute" - "value set, setting it to 'path' as default." - ) - rop_node.parm("pathattrib").set("path") - - cls.log.debug("Checking for attribute: %s", path_attr) - - # Use current frame if "frameStart" doesn't exist - # This only happens when ""trange" is 0 - frame = instance.data.get("frameStart", hou.intFrame()) - - # Get Geo at that frame - geo = output_node.geometryAtFrame(frame) - if not geo: - cls.log.warning("No geometry found," - " skipping check..") - return - - # If there are no primitives on the current frame then - # we can't check whether the path names are correct. - # So we'll just issue a warning that the check can't - # be done consistently and skip validation. - - if len(geo.iterPrims()) == 0: - cls.log.warning( - "No primitives found on current frame." - " Validation for primitive hierarchy" - " paths will be skipped," - " thus can't be validated." - ) - return - - # Check if there are any values for the primitives - attrib = geo.findPrimAttrib(path_attr) - if not attrib: - cls.log.info( - "Geometry Primitives are missing " - "path attribute: `%s`", path_attr - ) - return [output_node] - - # Ensure at least a single string value is present - if not attrib.strings(): - cls.log.info( - "Primitive path attribute has no " - "string values: %s", path_attr - ) - return [output_node] - - paths = geo.primStringAttribValues(path_attr) - # Ensure all primitives are set to a valid path - # Collect all invalid primitive numbers - invalid_prims = [i for i, path in enumerate(paths) if not path] - if invalid_prims: - num_prims = len(geo.iterPrims()) # faster than len(geo.prims()) - cls.log.info( - "Prims have no value for attribute `%s` " - "(%s of %s prims)", - path_attr, len(invalid_prims), num_prims - ) - return [output_node] - - # what repair action expects to find and call - @classmethod - def repair(cls, instance): - """Add a default path attribute Action. - - It is a helper action more than a repair action, - used to add a default single value for the path. - """ - - rop_node = hou.node(instance.data["instance_node"]) - # I'm doing so because an artist may change output node - # before clicking the button. - output_node = rop_node.parm("startnode").evalAsNode() - - if not output_node: - cls.log.debug( - "Action isn't performed, invalid SOP Path on %s", - rop_node - ) - return - - # This check to prevent the action from running multiple times. - # git_invalid only returns [output_node] when - # path attribute is the problem - if cls.get_invalid(instance) != [output_node]: - return - - path_attr = rop_node.parm("pathattrib").eval() - - path_node = output_node.parent().createNode("name", - "AUTO_PATH") - path_node.parm("attribname").set(path_attr) - path_node.parm("name1").set('`opname("..")`_GEO') - - cls.log.debug( - "'%s' was created. It adds '%s' with a default" - " single value", path_node, path_attr - ) - - path_node.setGenericFlag(hou.nodeFlag.DisplayComment, True) - path_node.setComment( - 'Auto path node was created automatically by ' - '"Add a default path attribute"' - '\nFeel free to modify or replace it.' - ) - - if output_node.type().name() in ["null", "output"]: - # Connect before - path_node.setFirstInput(output_node.input(0)) - path_node.moveToGoodPosition() - output_node.setFirstInput(path_node) - output_node.moveToGoodPosition() - else: - # Connect after - path_node.setFirstInput(output_node) - rop_node.parm("startnode").set(path_node.path()) - path_node.moveToGoodPosition() - - cls.log.debug( - "SOP path on '%s' updated to new output node '%s'", - rop_node, path_node - ) diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py deleted file mode 100644 index ac80fda537..0000000000 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validate mesh is static. - -This plugin is part of publish process guide. -""" - -import pyblish.api -from openpype.pipeline import PublishValidationError -from openpype.pipeline.publish import ( - ValidateContentsOrder, - RepairAction, -) -from openpype.hosts.houdini.api.action import ( - SelectInvalidAction, - SelectROPAction, -) - -import hou - - -# Each validation can have a single repair action -# which calls the repair method -class FreezeTimeAction(RepairAction): - label = "Freeze Time" - icon = "ei.pause-alt" - - -class ValidateMeshIsStatic(pyblish.api.InstancePlugin): - """Validate mesh is static. - - It checks if node is time dependant. - """ - - families = ["staticMesh"] - hosts = ["houdini"] - label = "Validate mesh is static" - - # Usually you will use this value as default - order = ValidateContentsOrder + 0.1 - - # Validation can have as many actions as you want - # all of these actions are defined in a seperate place - # unlike the repair action - actions = [FreezeTimeAction, SelectInvalidAction, - SelectROPAction] - - # overrides InstancePlugin.process() - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - nodes = [n.path() for n in invalid] - raise PublishValidationError( - "See log for details. " - "Invalid nodes: {0}".format(nodes), - title=self.label - ) - - # This method was named get_invalid as a convention - # it's also used by SelectInvalidAction to select - # the returned nodes - @classmethod - def get_invalid(cls, instance): - - output_node = instance.data.get("output_node") - if output_node.isTimeDependent(): - cls.log.info("Mesh is not static!") - return [output_node] - - # what repair action expects to find and call - @classmethod - def repair(cls, instance): - """Adds a time shift node. - - It should kill time dependency. - """ - - rop_node = hou.node(instance.data["instance_node"]) - # I'm doing so because an artist may change output node - # before clicking the button. - output_node = rop_node.parm("startnode").evalAsNode() - - if not output_node: - cls.log.debug( - "Action isn't performed, invalid SOP Path on %s", - rop_node - ) - return - - # This check to prevent the action from running multiple times. - # git_invalid only returns [output_node] when - # path attribute is the problem - if cls.get_invalid(instance) != [output_node]: - return - - time_shift = output_node.parent().createNode("timeshift", - "freeze_time") - time_shift.parm("frame").deleteAllKeyframes() - - frame = instance.data.get("frameStart", hou.intFrame()) - time_shift.parm("frame").set(frame) - - cls.log.debug( - "'%s' was created. It will kill time dependency.", - time_shift - ) - - time_shift.setGenericFlag(hou.nodeFlag.DisplayComment, True) - time_shift.setComment( - 'This node was created automatically by ' - '"Freeze Time" Action' - '\nFeel free to modify or replace it.' - ) - - if output_node.type().name() in ["null", "output"]: - # Connect before - time_shift.setFirstInput(output_node.input(0)) - time_shift.moveToGoodPosition() - output_node.setFirstInput(time_shift) - output_node.moveToGoodPosition() - else: - # Connect after - time_shift.setFirstInput(output_node) - rop_node.parm("startnode").set(time_shift.path()) - time_shift.moveToGoodPosition() - - cls.log.debug( - "SOP path on '%s' updated to new output node '%s'", - rop_node, time_shift - ) diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py new file mode 100644 index 0000000000..be450a0410 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +"""Validator for correct naming of Static Meshes.""" +import pyblish.api +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from openpype.pipeline.publish import ValidateContentsOrder + +from openpype.hosts.houdini.api.action import SelectInvalidAction + +import hou + + +class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate name of Unreal Static Mesh + + This validator checks if output node name has a collision prefix: + - UBX + - UCP + - USP + - UCX + + This validator also checks if subset name is correct + - {static mesh prefix}_{Asset-Name}{Variant}. + + """ + + families = ["staticMesh"] + hosts = ["houdini"] + label = "Unreal Static Mesh Name (FBX)" + order = ValidateContentsOrder + 0.1 + actions = [SelectInvalidAction] + + optional = True + + @classmethod + def apply_settings(cls, project_settings, system_settings): + settings = ( + project_settings["houdini"]["create"]["CreateUnrealStaticMesh"] + ) + cls.collision_prefixes = settings["collision_prefixes"] + cls.static_mesh_prefix = settings["static_mesh_prefix"] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + nodes = [n.path() for n in invalid if isinstance(n, hou.Node)] + raise PublishValidationError( + "See log for details. " + "Invalid nodes: {0}".format(nodes), + title=self.label + ) + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + + rop_node = hou.node(instance.data["instance_node"]) + output_node = instance.data.get("output_node") + cls.log.debug(cls.collision_prefixes) + + # Check nodes names + if output_node.childTypeCategory() == hou.objNodeTypeCategory(): + for child in output_node.children(): + for prefix in cls.collision_prefixes: + if child.name().startswith(prefix): + invalid.append(child) + cls.log.error( + "Invalid name: Child node '%s' in '%s' " + "has a collision prefix '%s'" + , child.name(), output_node.path(), prefix + ) + break + else: + cls.log.debug(output_node.name()) + for prefix in cls.collision_prefixes: + if output_node.name().startswith(prefix): + invalid.append(output_node) + cls.log.error( + "Invalid name: output node '%s' " + "has a collision prefix '%s'" + , output_node.name(), prefix + ) + + # Check subset name + subset_name = "{}_{}{}".format( + cls.static_mesh_prefix, + instance.data["asset"], + instance.data.get("variant", "") + ) + + if instance.data.get("subset") != subset_name: + invalid.append(rop_node) + cls.log.error( + "Invalid subset name on rop node '%s' should be '%s'." + , rop_node.path(), subset_name + ) + + return invalid From 128939068084b3b5af50db7812d1a9b8021a471c Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 17:23:53 +0300 Subject: [PATCH 42/63] update settings --- .../plugins/publish/validate_unreal_staticmesh_naming.py | 6 +++++- openpype/settings/defaults/project_settings/houdini.json | 2 +- .../projects_schema/schemas/schema_houdini_publish.json | 4 ++-- server_addon/houdini/server/settings/publish_plugins.py | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index be450a0410..24ef304185 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -14,7 +14,7 @@ import hou class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Validate name of Unreal Static Mesh + """Validate name of Unreal Static Mesh. This validator checks if output node name has a collision prefix: - UBX @@ -44,6 +44,10 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, cls.static_mesh_prefix = settings["static_mesh_prefix"] def process(self, instance): + + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: nodes = [n.path() for n in invalid if isinstance(n, hou.Node)] diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index c39eb717fd..65f13fa1ab 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -111,7 +111,7 @@ "optional": true, "active": true }, - "ValidateFBXPrimitiveHierarchyPaths": { + "ValidateUnrealStaticMeshName": { "enabled": true, "optional": true, "active": true diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json index d58b36eff1..4339f86db6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json @@ -45,8 +45,8 @@ "label": "ValidateContainers" }, { - "key": "ValidateFBXPrimitiveHierarchyPaths", - "label": "Validate Path Attribute for FBX" + "key": "ValidateUnrealStaticMeshName", + "label": "Validate Unreal Static Mesh Name" } ] } diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 1e5cd7f551..335751e5f9 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -164,9 +164,9 @@ class PublishPluginsModel(BaseSettingsModel): ValidateContainers: ValidateContainersModel = Field( default_factory=ValidateContainersModel, title="Validate Latest Containers.") - ValidateFBXPrimitiveHierarchyPaths: ValidateContainersModel = Field( + ValidateUnrealStaticMeshName: ValidateContainersModel = Field( default_factory=ValidateContainersModel, - title="Validate Path Attribute for FBX.") + title="Validate Unreal Static Mesh Name.") DEFAULT_HOUDINI_PUBLISH_SETTINGS = { @@ -187,7 +187,7 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "optional": True, "active": True }, - "ValidateFBXPrimitiveHierarchyPaths": { + "ValidateUnrealStaticMeshName": { "enabled": True, "optional": True, "active": True From c73d76ef15289f868d016c689ecaa880c3144262 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 17:33:54 +0300 Subject: [PATCH 43/63] resolve houn --- .../plugins/create/create_unreal_staticmesh.py | 13 +++++++------ .../publish/validate_unreal_staticmesh_naming.py | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index 0b5b313d9e..6002f7b1d7 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -74,12 +74,13 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): attrs = super().get_pre_create_attr_defs() createsubnetroot = BoolDef("createsubnetroot", - tooltip="Create an extra root for the Export node " - "when it’s a subnetwork. This causes the " - "exporting subnetwork node to be " - "represented in the FBX file.", - default=False, - label="Create Root for Subnet") + tooltip="Create an extra root for the " + "Export node when it’s a " + "subnetwork. This causes the " + "exporting subnetwork node to be " + "represented in the FBX file.", + default=False, + label="Create Root for Subnet") vcformat = EnumDef("vcformat", items={ 0: "Maya Compatible (MC)", diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index 24ef304185..a3426d2f19 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -74,8 +74,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, invalid.append(child) cls.log.error( "Invalid name: Child node '%s' in '%s' " - "has a collision prefix '%s'" - , child.name(), output_node.path(), prefix + "has a collision prefix '%s'", + child.name(), output_node.path(), prefix ) break else: @@ -85,8 +85,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, invalid.append(output_node) cls.log.error( "Invalid name: output node '%s' " - "has a collision prefix '%s'" - , output_node.name(), prefix + "has a collision prefix '%s'", + output_node.name(), prefix ) # Check subset name @@ -99,8 +99,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, if instance.data.get("subset") != subset_name: invalid.append(rop_node) cls.log.error( - "Invalid subset name on rop node '%s' should be '%s'." - , rop_node.path(), subset_name + "Invalid subset name on rop node '%s' should be '%s'.", + rop_node.path(), subset_name ) return invalid From 1f452510d8e4fb38deaebe59eb5f4d970d2b5aa1 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 1 Sep 2023 17:35:57 +0300 Subject: [PATCH 44/63] resolve hound --- .../plugins/publish/validate_unreal_staticmesh_naming.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index a3426d2f19..7820be4009 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -99,8 +99,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, if instance.data.get("subset") != subset_name: invalid.append(rop_node) cls.log.error( - "Invalid subset name on rop node '%s' should be '%s'.", - rop_node.path(), subset_name + "Invalid subset name on rop node '%s' should be '%s'.", + rop_node.path(), subset_name ) return invalid From 938dc72d9179ee8938a98f0ce326c4dfadda5657 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 4 Sep 2023 11:56:10 +0300 Subject: [PATCH 45/63] revise creator and collector --- .../create/create_unreal_staticmesh.py | 36 +++---------------- .../plugins/publish/collect_fbx_type.py | 33 ----------------- .../publish/collect_staticmesh_type.py | 20 +++++++++++ .../validate_primitive_hierarchy_paths.py | 2 +- 4 files changed, 26 insertions(+), 65 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/collect_fbx_type.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index 6002f7b1d7..ca5e2e8fb4 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -1,21 +1,5 @@ # -*- coding: utf-8 -*- -"""Creator plugin for creating Unreal Static Meshes. - -Unreal Static Meshes will be published as FBX. - -Filmbox by default expects an ObjNode -however, we set the sop node explictly -to eleminate any confusion. - -This will make Filmbox to ignore any object transformations! - -get_obj_output selects -the output sop with mimimum idx -or the node with render flag isntead. - -This plugin is part of publish process guide. -""" - +"""Creator for Unreal Static Meshes.""" from openpype.hosts.houdini.api import plugin from openpype.lib import BoolDef, EnumDef @@ -25,30 +9,23 @@ import hou class CreateUnrealStaticMesh(plugin.HoudiniCreator): """Unreal Static Meshes with collisions. """ - # you should set identifier = "io.openpype.creators.houdini.unrealstaticmesh.fbx" label = "Unreal - Static Mesh (FBX)" family = "staticMesh" icon = "fa5s.cubes" - # optional to set - default_variant = "Main" - # 'default_variants' will be overriden by settings. - default_variants = ["Main", "Test"] + default_variants = ["Main"] - # Overrides HoudiniCreator.create() def create(self, subset_name, instance_data, pre_create_data): - # set node type instance_data.update({"node_type": "filmboxfbx"}) - # create instance (calls HoudiniCreator.create()) instance = super(CreateUnrealStaticMesh, self).create( subset_name, instance_data, pre_create_data) - # get the created node + # get the created rop node instance_node = hou.node(instance.get("instance_node")) # get parms @@ -61,21 +38,19 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): to_lock = ["family", "id"] self.lock_parameters(instance_node, to_lock) - # Overrides HoudiniCreator.get_network_categories() def get_network_categories(self): return [ hou.ropNodeTypeCategory(), hou.sopNodeTypeCategory() ] - # Overrides HoudiniCreator.get_pre_create_attr_defs() def get_pre_create_attr_defs(self): """Add settings for users. """ attrs = super().get_pre_create_attr_defs() createsubnetroot = BoolDef("createsubnetroot", tooltip="Create an extra root for the " - "Export node when it’s a " + "Export node when it's a " "subnetwork. This causes the " "exporting subnetwork node to be " "represented in the FBX file.", @@ -98,7 +73,6 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): return attrs + [createsubnetroot, vcformat, convert_units] - # Overrides BaseCreator.get_dynamic_data() def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name, instance ): @@ -113,7 +87,7 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): return dynamic_data def get_parms(self, subset_name, pre_create_data): - """Get parameters values for this specific node.""" + """Get parameters values. """ # 1. get output path output_path = hou.text.expandString( diff --git a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py b/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py deleted file mode 100644 index 6ac40a4f50..0000000000 --- a/openpype/hosts/houdini/plugins/publish/collect_fbx_type.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Collector for filmboxfbx types. - -Collectors act as a pre process for the validation stage. -It is used mainly to update instance.data - -P.S. - There are some collectors that run by default - for all types. - -This plugin is part of publish process guide. -""" -import pyblish.api - - -class CollectFilmboxfbxType(pyblish.api.InstancePlugin): - """Collect data type for fbx instance.""" - - hosts = ["houdini"] - families = ["staticMesh"] - label = "Collect type of fbx" - - # Usually you will use this value as default - order = pyblish.api.CollectorOrder - - # overrides InstancePlugin.process() - def process(self, instance): - - if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh.fbx": # noqa: E501 - # such a condition can be used to differentiate between - # instances by identifier because sometimes instances - # may have the same family but different identifier - # e.g. bgeo and alembic - instance.data["families"] += ["fbx"] diff --git a/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py b/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py new file mode 100644 index 0000000000..8fb07c1c5c --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +"""Collector for staticMesh types. """ + +import pyblish.api + + +class CollectStaticMeshType(pyblish.api.InstancePlugin): + """Collect data type for fbx instance.""" + + hosts = ["houdini"] + families = ["staticMesh"] + label = "Collect type of staticMesh" + + order = pyblish.api.CollectorOrder + + def process(self, instance): + + if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh.fbx": # noqa: E501 + # Marking this instance as FBX which triggers the FBX extractor. + instance.data["families"] += ["fbx"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py index 930978ef16..471fa5b6d1 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py @@ -26,7 +26,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): order = ValidateContentsOrder + 0.1 families = ["abc"] hosts = ["houdini"] - label = "Validate Prims Hierarchy Path (ABC)" + label = "Validate Prims Hierarchy Path" actions = [AddDefaultPathAction] def process(self, instance): From ad62e1fd469fe122b9b7490a5d68b49db26741b2 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 4 Sep 2023 15:06:42 +0300 Subject: [PATCH 46/63] revise collect-validators-extract --- .../hosts/houdini/plugins/load/load_fbx.py | 21 ++---- .../publish/collect_staticmesh_type.py | 2 +- .../houdini/plugins/publish/extract_fbx.py | 13 +--- .../publish/validate_mesh_is_static.py | 70 +++++++++++++++++++ .../plugins/publish/validate_output_node.py | 55 +++++++++++++++ .../publish/validate_sop_output_node.py | 2 +- .../validate_unreal_staticmesh_naming.py | 9 ++- 7 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py create mode 100644 openpype/hosts/houdini/plugins/publish/validate_output_node.py diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 681837e046..9c7dbf578e 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -1,12 +1,5 @@ # -*- coding: utf-8 -*- -"""Fbx Loader for houdini. - -It's almost a copy of -'load_bgeo.py'and 'load_alembic.py' -however this one includes extra comments for demonstration. - -This plugin is part of publish process guide. -""" +"""Fbx Loader for houdini. """ from openpype.pipeline import ( load, get_representation_path, @@ -15,17 +8,17 @@ from openpype.hosts.houdini.api import pipeline class FbxLoader(load.LoaderPlugin): - """Load fbx files to Houdini.""" + """Load fbx files. """ label = "Load FBX" - families = ["staticMesh", "fbx"] - representations = ["fbx"] - - # Usually you will use these value as default - order = -10 icon = "code-fork" color = "orange" + order = -10 + + families = ["staticMesh", "fbx"] + representations = ["fbx"] + def load(self, context, name=None, namespace=None, data=None): # get file path diff --git a/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py b/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py index 8fb07c1c5c..263d7c1001 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py @@ -16,5 +16,5 @@ class CollectStaticMeshType(pyblish.api.InstancePlugin): def process(self, instance): if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh.fbx": # noqa: E501 - # Marking this instance as FBX which triggers the FBX extractor. + # Marking this instance as FBX triggers the FBX extractor. instance.data["families"] += ["fbx"] diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index c0e84c00c8..2a95734ece 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -1,15 +1,8 @@ -"""Extract FilmBox FBX. - -Extractors are used to generate output and -update representation dictionary. - -This plugin is part of publish process guide. -""" +# -*- coding: utf-8 -*- +"""Fbx Extractor for houdini. """ import os - import pyblish.api - from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop @@ -22,10 +15,8 @@ class ExtractFBX(publish.Extractor): families = ["fbx"] hosts = ["houdini"] - # Usually you will use this value as default order = pyblish.api.ExtractorOrder + 0.1 - # overrides Extractor.process() def process(self, instance): # get rop node diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py new file mode 100644 index 0000000000..90985b4239 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +"""Validator for correct naming of Static Meshes.""" +import pyblish.api +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from openpype.pipeline.publish import ValidateContentsOrder + +from openpype.hosts.houdini.api.action import SelectInvalidAction + +import hou + + +class ValidateMeshIsStatic(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate mesh is static. + + It checks if output node is time dependant. + """ + + families = ["staticMesh"] + hosts = ["houdini"] + label = "Validate Mesh is Static" + order = ValidateContentsOrder + 0.1 + actions = [SelectInvalidAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + nodes = [n.path() for n in invalid if isinstance(n, hou.Node)] + raise PublishValidationError( + "See log for details. " + "Invalid nodes: {0}".format(nodes) + ) + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + + output_node = instance.data.get("output_node") + if output_node is None: + cls.log.debug( + "No Output Node, skipping check.." + ) + return + + + + if output_node.name().isTimeDependent(): + invalid.append(output_node) + cls.log.error( + "Output node '%s' is time dependent.", + output_node.name() + ) + + if output_node.childTypeCategory() == hou.objNodeTypeCategory(): + for child in output_node.children(): + if output_node.name().isTimeDependent(): + invalid.append(child) + cls.log.error( + "Child node '%s' in '%s' " + "his time dependent.", + child.name(), output_node.path() + ) + break + + return invalid diff --git a/openpype/hosts/houdini/plugins/publish/validate_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_output_node.py new file mode 100644 index 0000000000..99a6cda077 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_output_node.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from openpype.pipeline import PublishValidationError +from openpype.hosts.houdini.api.action import ( + SelectInvalidAction, + SelectROPAction, +) + +import hou + + +class ValidateOutputNode(pyblish.api.InstancePlugin): + """Validate the instance Output Node. + + This will ensure: + - The Output Node Path is set. + - The Output Node Path refers to an existing object. + """ + + order = pyblish.api.ValidatorOrder + families = ["fbx"] + hosts = ["houdini"] + label = "Validate Output Node" + actions = [SelectROPAction, SelectInvalidAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + "Output node(s) are incorrect", + title="Invalid output node(s)" + ) + + @classmethod + def get_invalid(cls, instance): + output_node = instance.data.get("output_node") + + if output_node is None: + rop_node = hou.node(instance.data["instance_node"]) + cls.log.error( + "Output node in '%s' does not exist. " + "Ensure a valid output path is set.", rop_node.path() + ) + + return [rop_node] + + if output_node.type().category().name() not in ["Sop", "Object"]: + cls.log.error( + "Output node %s is not a SOP or OBJ node. " + "It must point to a SOP or OBJ node, " + "instead found category type: %s" + % (output_node.path(), output_node.type().category().name()) + ) + return [output_node] 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 d9dee38680..9590e37d26 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -24,7 +24,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder families = ["pointcache", "vdbcache"] hosts = ["houdini"] - label = "Validate Output Node" + label = "Validate Output Node (SOP)" actions = [SelectROPAction, SelectInvalidAction] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index 7820be4009..f1ea9b3844 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -53,8 +53,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, nodes = [n.path() for n in invalid if isinstance(n, hou.Node)] raise PublishValidationError( "See log for details. " - "Invalid nodes: {0}".format(nodes), - title=self.label + "Invalid nodes: {0}".format(nodes) ) @classmethod @@ -64,7 +63,11 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, rop_node = hou.node(instance.data["instance_node"]) output_node = instance.data.get("output_node") - cls.log.debug(cls.collision_prefixes) + if output_node is None: + cls.log.debug( + "No Output Node, skipping check.." + ) + return # Check nodes names if output_node.childTypeCategory() == hou.objNodeTypeCategory(): From 1678bd56032e8975301651c4bcdfb6ae1290dce6 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 4 Sep 2023 19:48:15 +0300 Subject: [PATCH 47/63] update fetch output --- .../validate_unreal_staticmesh_naming.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index f1ea9b3844..3c13f081a9 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -107,3 +107,30 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, ) return invalid + + def get_outputs(self, output_node): + + if output_node.childTypeCategory() == hou.objNodeTypeCategory(): + out_list = [output_node] + for child in output_node.children(): + out_list += self.get_outputs(child) + + return out_list + + elif output_node.childTypeCategory() == hou.sopNodeTypeCategory(): + return [output_node, self.get_obj_output(output_node)] + + def get_obj_output(self, obj_node): + """Find sop output node, """ + + outputs = obj_node.subnetOutputs() + + if not outputs: + return + + elif len(outputs) == 1: + return outputs[0] + + else: + return min(outputs, + key=lambda node: node.evalParm('outputidx')) From 79aab2534ea53931980b4bb44e52713047898c65 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 4 Sep 2023 21:59:04 +0300 Subject: [PATCH 48/63] update retrieving output nodes --- openpype/hosts/houdini/api/lib.py | 39 ++++++++++++ .../publish/validate_mesh_is_static.py | 27 +++----- .../validate_unreal_staticmesh_naming.py | 62 +++++-------------- 3 files changed, 65 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 75c7ff9fee..0f1cfe0717 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -649,3 +649,42 @@ def get_color_management_preferences(): "display": hou.Color.ocio_defaultDisplay(), "view": hou.Color.ocio_defaultView() } + + +def get_obj_node_output(obj_node): + """Find output node. + + get the output node with the minimum 'outputidx' + or the node with display flag. + """ + + outputs = obj_node.subnetOutputs() + if not outputs: + return + + elif len(outputs) == 1: + return outputs[0] + + else: + return min(outputs, + key=lambda node: node.evalParm('outputidx')) + + +def get_output_children(output_node, include_sops=True): + """Recursively return a list of all output nodes + contained in this node including this node. + + It works in a similar manner to output_node.allNodes(). + """ + out_list = [output_node] + + if output_node.childTypeCategory() == hou.objNodeTypeCategory(): + for child in output_node.children(): + out_list += get_output_children(child, include_sops=include_sops) + + elif include_sops and output_node.childTypeCategory() == hou.sopNodeTypeCategory(): + out = get_obj_node_output(output_node) + if out: + out_list += [out] + + return out_list diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py index 90985b4239..36c8ef6d63 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( from openpype.pipeline.publish import ValidateContentsOrder from openpype.hosts.houdini.api.action import SelectInvalidAction +from openpype.hosts.houdini.api.lib import get_output_children import hou @@ -47,24 +48,14 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin, ) return + all_outputs = get_output_children(output_node) - - if output_node.name().isTimeDependent(): - invalid.append(output_node) - cls.log.error( - "Output node '%s' is time dependent.", - output_node.name() - ) - - if output_node.childTypeCategory() == hou.objNodeTypeCategory(): - for child in output_node.children(): - if output_node.name().isTimeDependent(): - invalid.append(child) - cls.log.error( - "Child node '%s' in '%s' " - "his time dependent.", - child.name(), output_node.path() - ) - break + for output in all_outputs: + if output.isTimeDependent(): + invalid.append(output) + cls.log.error( + "Output node '%s' is time dependent.", + output.path() + ) return invalid diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index 3c13f081a9..5558b43258 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( from openpype.pipeline.publish import ValidateContentsOrder from openpype.hosts.houdini.api.action import SelectInvalidAction +from openpype.hosts.houdini.api.lib import get_output_children import hou @@ -69,28 +70,26 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, ) return + if not rop_node.evalParm('buildfrompath'): + # This validator doesn't support naming check if + # building hierarchy from path' is used + cls.log.info( + "Using 'Build Hierarchy from Path Attribute', skipping check.." + ) + return + # Check nodes names - if output_node.childTypeCategory() == hou.objNodeTypeCategory(): - for child in output_node.children(): - for prefix in cls.collision_prefixes: - if child.name().startswith(prefix): - invalid.append(child) - cls.log.error( - "Invalid name: Child node '%s' in '%s' " - "has a collision prefix '%s'", - child.name(), output_node.path(), prefix - ) - break - else: - cls.log.debug(output_node.name()) + all_outputs = get_output_children(output_node, include_sops=False) + for output in all_outputs: for prefix in cls.collision_prefixes: - if output_node.name().startswith(prefix): - invalid.append(output_node) + if output.name().startswith(prefix): + invalid.append(output) cls.log.error( - "Invalid name: output node '%s' " - "has a collision prefix '%s'", - output_node.name(), prefix + "Invalid node name: Node '%s' " + "includes a collision prefix '%s'", + output.path(), prefix ) + break # Check subset name subset_name = "{}_{}{}".format( @@ -107,30 +106,3 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, ) return invalid - - def get_outputs(self, output_node): - - if output_node.childTypeCategory() == hou.objNodeTypeCategory(): - out_list = [output_node] - for child in output_node.children(): - out_list += self.get_outputs(child) - - return out_list - - elif output_node.childTypeCategory() == hou.sopNodeTypeCategory(): - return [output_node, self.get_obj_output(output_node)] - - def get_obj_output(self, obj_node): - """Find sop output node, """ - - outputs = obj_node.subnetOutputs() - - if not outputs: - return - - elif len(outputs) == 1: - return outputs[0] - - else: - return min(outputs, - key=lambda node: node.evalParm('outputidx')) From 9ea11e7f2d90faf25487ebe2f8f7db4d461da018 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 4 Sep 2023 22:02:23 +0300 Subject: [PATCH 49/63] resolve hound --- openpype/hosts/houdini/api/lib.py | 7 ++++--- .../houdini/plugins/publish/validate_mesh_is_static.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 0f1cfe0717..3e51912c26 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -667,7 +667,7 @@ def get_obj_node_output(obj_node): else: return min(outputs, - key=lambda node: node.evalParm('outputidx')) + key=lambda node: node.evalParm('outputidx')) def get_output_children(output_node, include_sops=True): @@ -682,8 +682,9 @@ def get_output_children(output_node, include_sops=True): for child in output_node.children(): out_list += get_output_children(child, include_sops=include_sops) - elif include_sops and output_node.childTypeCategory() == hou.sopNodeTypeCategory(): - out = get_obj_node_output(output_node) + elif include_sops and \ + output_node.childTypeCategory() == hou.sopNodeTypeCategory(): + out = get_obj_node_output(output_node) if out: out_list += [out] diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py index 36c8ef6d63..25ab362a88 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -14,7 +14,7 @@ import hou class ValidateMeshIsStatic(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): + OptionalPyblishPluginMixin): """Validate mesh is static. It checks if output node is time dependant. From d54111bd75843c4713cac3409c1770594ce634e2 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Tue, 5 Sep 2023 11:59:12 +0300 Subject: [PATCH 50/63] Update docstring Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/api/lib.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 3e51912c26..b108d0d881 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -654,8 +654,19 @@ def get_color_management_preferences(): def get_obj_node_output(obj_node): """Find output node. - get the output node with the minimum 'outputidx' - or the node with display flag. + If the node has any output node return the + output node with the minimum `outputidx`. + When no output is present return the node + with the display flag set. If no output node is + detected then None is returned. + + Arguments: + node (hou.Node): The node to retrieve a single + the output node for. + + Returns: + Optional[hou.Node]: The child output node. + """ outputs = obj_node.subnetOutputs() From 45f86749e165f3825ccdff4dbe8200180f4369b8 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 5 Sep 2023 19:52:13 +0300 Subject: [PATCH 51/63] resolve some comments --- .../create/create_unreal_staticmesh.py | 50 +++------- .../hosts/houdini/plugins/load/load_fbx.py | 2 +- .../houdini/plugins/publish/extract_fbx.py | 45 +++------ ...ut_node.py => validate_fbx_output_node.py} | 3 +- .../publish/validate_mesh_is_static.py | 2 +- .../plugins/publish/validate_subset_name.py | 94 +++++++++++++++++++ .../validate_unreal_staticmesh_naming.py | 21 +---- .../defaults/project_settings/houdini.json | 10 ++ .../schemas/schema_houdini_publish.json | 8 ++ .../server/settings/publish_plugins.py | 16 ++++ 10 files changed, 164 insertions(+), 87 deletions(-) rename openpype/hosts/houdini/plugins/publish/{validate_output_node.py => validate_fbx_output_node.py} (93%) create mode 100644 openpype/hosts/houdini/plugins/publish/validate_subset_name.py diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index ca5e2e8fb4..fc3783c0d1 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -28,8 +28,18 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): # get the created rop node instance_node = hou.node(instance.get("instance_node")) - # get parms - parms = self.get_parms(subset_name, pre_create_data) + # prepare parms + output_path = hou.text.expandString("$HIP/pyblish/{}.fbx".format(subset_name)) + parms = { + "startnode": self.get_selection(), + "sopoutput": output_path, + # vertex cache format + "vcformat": pre_create_data.get("vcformat"), + "convertunits": pre_create_data.get("convertunits"), + # set render range to use frame range start-end frame + "trange": 1, + "createsubnetroot": pre_create_data.get("createsubnetroot") + } # set parms instance_node.setParms(parms) @@ -47,7 +57,7 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): def get_pre_create_attr_defs(self): """Add settings for users. """ - attrs = super().get_pre_create_attr_defs() + attrs = super(CreateUnrealStaticMesh, self).get_pre_create_attr_defs() createsubnetroot = BoolDef("createsubnetroot", tooltip="Create an extra root for the " "Export node when it's a " @@ -86,40 +96,6 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): dynamic_data["asset"] = asset_doc["name"] return dynamic_data - def get_parms(self, subset_name, pre_create_data): - """Get parameters values. """ - - # 1. get output path - output_path = hou.text.expandString( - "$HIP/pyblish/{}.fbx".format(subset_name)) - - # 2. get selection - selection = self.get_selection() - - # 3. get Vertex Cache Format - vcformat = pre_create_data.get("vcformat") - - # 4. get convert_units - convertunits = pre_create_data.get("convertunits") - - # 5. get Valid Frame Range - trange = 1 - - # 6. get createsubnetroot - createsubnetroot = pre_create_data.get("createsubnetroot") - - # parms dictionary - parms = { - "startnode": selection, - "sopoutput": output_path, - "vcformat": vcformat, - "convertunits": convertunits, - "trange": trange, - "createsubnetroot": createsubnetroot - } - - return parms - def get_selection(self): """Selection Logic. diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 9c7dbf578e..7e7f0c04e5 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -146,5 +146,5 @@ class FbxLoader(load.LoaderPlugin): # Set new position for children nodes parent_node.layoutChildren() - # Retrun all the nodes + # Return all the nodes return [parent_node, file_node, attribdelete, null] diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index 2a95734ece..e8cd207818 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -21,40 +21,17 @@ class ExtractFBX(publish.Extractor): # get rop node ropnode = hou.node(instance.data.get("instance_node")) + output_node = ropnode.evalParm("sopoutput") + + # get staging_dir and file_name + staging_dir = os.path.normpath(os.path.dirname(output_node)) + file_name = os.path.basename(output_node) # render rop + self.log.debug("Writing FBX '%s' to '%s'",file_name, staging_dir) render_rop(ropnode) - # get required data - file_name, staging_dir = self.get_paths_data(ropnode) - representation = self.get_representation(instance, - file_name, - staging_dir) - - # set value type for 'representations' key to list - if "representations" not in instance.data: - instance.data["representations"] = [] - - # update instance data - instance.data["stagingDir"] = staging_dir - instance.data["representations"].append(representation) - - def get_paths_data(self, ropnode): - # Get the filename from the filename parameter - output = ropnode.evalParm("sopoutput") - - staging_dir = os.path.normpath(os.path.dirname(output)) - - file_name = os.path.basename(output) - - self.log.info("Writing FBX '%s' to '%s'" % (file_name, - staging_dir)) - - return file_name, staging_dir - - def get_representation(self, instance, - file_name, staging_dir): - + # prepare representation representation = { "name": "fbx", "ext": "fbx", @@ -67,4 +44,10 @@ class ExtractFBX(publish.Extractor): representation["frameStart"] = instance.data["frameStart"] representation["frameEnd"] = instance.data["frameEnd"] - return representation + # set value type for 'representations' key to list + if "representations" not in instance.data: + instance.data["representations"] = [] + + # update instance data + instance.data["stagingDir"] = staging_dir + instance.data["representations"].append(representation) diff --git a/openpype/hosts/houdini/plugins/publish/validate_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py similarity index 93% rename from openpype/hosts/houdini/plugins/publish/validate_output_node.py rename to openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py index 99a6cda077..503a3bb3c1 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py @@ -9,12 +9,13 @@ from openpype.hosts.houdini.api.action import ( import hou -class ValidateOutputNode(pyblish.api.InstancePlugin): +class ValidateFBXOutputNode(pyblish.api.InstancePlugin): """Validate the instance Output Node. This will ensure: - The Output Node Path is set. - The Output Node Path refers to an existing object. + - The Output Node is a Sop or Obj node. """ order = pyblish.api.ValidatorOrder diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py index 25ab362a88..4d0904eb53 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -17,7 +17,7 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validate mesh is static. - It checks if output node is time dependant. + It checks if output node is time dependent. """ families = ["staticMesh"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_subset_name.py b/openpype/hosts/houdini/plugins/publish/validate_subset_name.py new file mode 100644 index 0000000000..299729a6e8 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_subset_name.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +"""Validator for correct naming of Static Meshes.""" +import pyblish.api +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, +) +from openpype.hosts.houdini.api.action import SelectInvalidAction +from openpype.pipeline.create import get_subset_name + +import hou + + +class FixSubsetNameAction(RepairAction): + label = "Fix Subset Name" + + +class ValidateSubsetName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate Subset name. + + """ + + families = ["staticMesh"] + hosts = ["houdini"] + label = "Validate Subset Name" + order = ValidateContentsOrder + 0.1 + actions = [FixSubsetNameAction, SelectInvalidAction] + + optional = True + + + def process(self, instance): + + if not self.is_active(instance.data): + return + + invalid = self.get_invalid(instance) + if invalid: + nodes = [n.path() for n in invalid] + raise PublishValidationError( + "See log for details. " + "Invalid nodes: {0}".format(nodes) + ) + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + + rop_node = hou.node(instance.data["instance_node"]) + + # Check subset name + subset_name = get_subset_name( + family=instance.data["family"], + variant=instance.data["variant"], + task_name=instance.data["task"], + asset_doc=instance.data["assetEntity"], + dynamic_data={"asset":instance.data["asset"]} + ) + + if instance.data.get("subset") != subset_name: + invalid.append(rop_node) + cls.log.error( + "Invalid subset name on rop node '%s' should be '%s'.", + rop_node.path(), subset_name + ) + + return invalid + + @classmethod + def repair(cls, instance): + rop_node = hou.node(instance.data["instance_node"]) + + # Check subset name + subset_name = get_subset_name( + family=instance.data["family"], + variant=instance.data["variant"], + task_name=instance.data["task"], + asset_doc=instance.data["assetEntity"], + dynamic_data={"asset":instance.data["asset"]} + ) + + instance.data["subset"] = subset_name + rop_node.parm("subset").set(subset_name) + + cls.log.debug( + "Subset name on rop node '%s' has been set to '%s'.", + rop_node.path(), subset_name + ) diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index 5558b43258..791db8198f 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -35,9 +35,12 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, actions = [SelectInvalidAction] optional = True + collision_prefixes = [] + static_mesh_prefix = "" @classmethod def apply_settings(cls, project_settings, system_settings): + settings = ( project_settings["houdini"]["create"]["CreateUnrealStaticMesh"] ) @@ -51,7 +54,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: - nodes = [n.path() for n in invalid if isinstance(n, hou.Node)] + nodes = [n.path() for n in invalid] raise PublishValidationError( "See log for details. " "Invalid nodes: {0}".format(nodes) @@ -70,7 +73,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, ) return - if not rop_node.evalParm('buildfrompath'): + if rop_node.evalParm("buildfrompath"): # This validator doesn't support naming check if # building hierarchy from path' is used cls.log.info( @@ -91,18 +94,4 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, ) break - # Check subset name - subset_name = "{}_{}{}".format( - cls.static_mesh_prefix, - instance.data["asset"], - instance.data.get("variant", "") - ) - - if instance.data.get("subset") != subset_name: - invalid.append(rop_node) - cls.log.error( - "Invalid subset name on rop node '%s' should be '%s'.", - rop_node.path(), subset_name - ) - return invalid diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 65f13fa1ab..7673725831 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -111,6 +111,16 @@ "optional": true, "active": true }, + "ValidateSubsetName": { + "enabled": true, + "optional": true, + "active": true + }, + "ValidateMeshIsStatic": { + "enabled": true, + "optional": true, + "active": true + }, "ValidateUnrealStaticMeshName": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json index 4339f86db6..670b1a0bc2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json @@ -44,6 +44,14 @@ "key": "ValidateContainers", "label": "ValidateContainers" }, + { + "key": "ValidateSubsetName", + "label": "Validate Subset Name" + }, + { + "key": "ValidateMeshIsStatic", + "label": "Validate Mesh is Static" + }, { "key": "ValidateUnrealStaticMeshName", "label": "Validate Unreal Static Mesh Name" diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 335751e5f9..b3e47d6948 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -164,6 +164,12 @@ class PublishPluginsModel(BaseSettingsModel): ValidateContainers: ValidateContainersModel = Field( default_factory=ValidateContainersModel, title="Validate Latest Containers.") + ValidateSubsetName: ValidateContainersModel = Field( + default_factory=ValidateContainersModel, + title="Validate Subset Name.") + ValidateMeshIsStatic: ValidateContainersModel = Field( + default_factory=ValidateContainersModel, + title="Validate Mesh is Static.") ValidateUnrealStaticMeshName: ValidateContainersModel = Field( default_factory=ValidateContainersModel, title="Validate Unreal Static Mesh Name.") @@ -187,6 +193,16 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "optional": True, "active": True }, + "ValidateSubsetName": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshIsStatic": { + "enabled": True, + "optional": True, + "active": True + }, "ValidateUnrealStaticMeshName": { "enabled": True, "optional": True, From ebae3cf03ef443b51296eb160a06d26e3c4ba637 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 5 Sep 2023 19:54:31 +0300 Subject: [PATCH 52/63] remove white spaces --- openpype/hosts/houdini/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index b108d0d881..7d3edbc707 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -654,19 +654,19 @@ def get_color_management_preferences(): def get_obj_node_output(obj_node): """Find output node. - If the node has any output node return the + If the node has any output node return the output node with the minimum `outputidx`. When no output is present return the node with the display flag set. If no output node is detected then None is returned. - + Arguments: node (hou.Node): The node to retrieve a single the output node for. - + Returns: Optional[hou.Node]: The child output node. - + """ outputs = obj_node.subnetOutputs() From f00d76c0330f37eff8956cfd633484e1cd607ec5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 5 Sep 2023 19:58:13 +0300 Subject: [PATCH 53/63] resolve hound --- .../hosts/houdini/plugins/create/create_unreal_staticmesh.py | 5 ++++- openpype/hosts/houdini/plugins/publish/extract_fbx.py | 4 ++-- .../hosts/houdini/plugins/publish/validate_subset_name.py | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py index fc3783c0d1..2f92def54a 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py @@ -29,7 +29,10 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): instance_node = hou.node(instance.get("instance_node")) # prepare parms - output_path = hou.text.expandString("$HIP/pyblish/{}.fbx".format(subset_name)) + output_path = hou.text.expandString( + "$HIP/pyblish/{}.fbx".format(subset_name) + ) + parms = { "startnode": self.get_selection(), "sopoutput": output_path, diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index e8cd207818..dd61e68f3b 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -28,7 +28,7 @@ class ExtractFBX(publish.Extractor): file_name = os.path.basename(output_node) # render rop - self.log.debug("Writing FBX '%s' to '%s'",file_name, staging_dir) + self.log.debug("Writing FBX '%s' to '%s'", file_name, staging_dir) render_rop(ropnode) # prepare representation @@ -36,7 +36,7 @@ class ExtractFBX(publish.Extractor): "name": "fbx", "ext": "fbx", "files": file_name, - "stagingDir": staging_dir, + "stagingDir": staging_dir } # A single frame may also be rendered without start/end frame. diff --git a/openpype/hosts/houdini/plugins/publish/validate_subset_name.py b/openpype/hosts/houdini/plugins/publish/validate_subset_name.py index 299729a6e8..bb3648f361 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_subset_name.py +++ b/openpype/hosts/houdini/plugins/publish/validate_subset_name.py @@ -33,7 +33,6 @@ class ValidateSubsetName(pyblish.api.InstancePlugin, optional = True - def process(self, instance): if not self.is_active(instance.data): @@ -60,7 +59,7 @@ class ValidateSubsetName(pyblish.api.InstancePlugin, variant=instance.data["variant"], task_name=instance.data["task"], asset_doc=instance.data["assetEntity"], - dynamic_data={"asset":instance.data["asset"]} + dynamic_data={"asset": instance.data["asset"]} ) if instance.data.get("subset") != subset_name: @@ -82,7 +81,7 @@ class ValidateSubsetName(pyblish.api.InstancePlugin, variant=instance.data["variant"], task_name=instance.data["task"], asset_doc=instance.data["assetEntity"], - dynamic_data={"asset":instance.data["asset"]} + dynamic_data={"asset": instance.data["asset"]} ) instance.data["subset"] = subset_name From 572d6e3ab52d5d398a9a25fd4405cad4fecea796 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Fri, 8 Sep 2023 12:44:14 +0300 Subject: [PATCH 54/63] update ayon settings --- .../houdini/server/settings/publish_plugins.py | 12 ++++++------ server_addon/houdini/server/version.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 528e847fce..6ceff028a5 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -167,14 +167,14 @@ class PublishPluginsModel(BaseSettingsModel): ValidateContainers: BasicValidateModel = Field( default_factory=BasicValidateModel, title="Validate Latest Containers.") - ValidateSubsetName: ValidateContainersModel = Field( - default_factory=ValidateContainersModel, + ValidateSubsetName: BasicValidateModel = Field( + default_factory=BasicValidateModel, title="Validate Subset Name.") - ValidateMeshIsStatic: ValidateContainersModel = Field( - default_factory=ValidateContainersModel, + ValidateMeshIsStatic: BasicValidateModel = Field( + default_factory=BasicValidateModel, title="Validate Mesh is Static.") - ValidateUnrealStaticMeshName: ValidateContainersModel = Field( - default_factory=ValidateContainersModel, + ValidateUnrealStaticMeshName: BasicValidateModel = Field( + default_factory=BasicValidateModel, title="Validate Unreal Static Mesh Name.") diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index b3f4756216..ae7362549b 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" From 2d255f15bedc2aca61177d80cad987b7108b5a26 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 11 Sep 2023 14:05:24 +0300 Subject: [PATCH 55/63] validate empty nodes and invalid prims --- .../publish/validate_fbx_output_node.py | 109 ++++++++++++++++-- 1 file changed, 100 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py index 503a3bb3c1..9f6a1b8767 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py @@ -5,7 +5,8 @@ from openpype.hosts.houdini.api.action import ( SelectInvalidAction, SelectROPAction, ) - +from openpype.hosts.houdini.api.lib import get_obj_node_output +from collections import defaultdict import hou @@ -16,27 +17,38 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): - The Output Node Path is set. - The Output Node Path refers to an existing object. - The Output Node is a Sop or Obj node. + - The Output Node has geometry data. """ order = pyblish.api.ValidatorOrder families = ["fbx"] hosts = ["houdini"] - label = "Validate Output Node" + label = "Validate FBX Output Node" actions = [SelectROPAction, SelectInvalidAction] def process(self, instance): - invalid = self.get_invalid(instance) + invalid = self.get_invalid_categorized(instance) if invalid: raise PublishValidationError( "Output node(s) are incorrect", title="Invalid output node(s)" ) - @classmethod def get_invalid(cls, instance): + out = cls.get_invalid_categorized(instance).values() + invalid = [] + for row in out: + invalid += row + return invalid + + + @classmethod + def get_invalid_categorized(cls, instance): output_node = instance.data.get("output_node") + # Check if The Output Node Path is set and + # refers to an existing object. if output_node is None: rop_node = hou.node(instance.data["instance_node"]) cls.log.error( @@ -46,11 +58,90 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): return [rop_node] - if output_node.type().category().name() not in ["Sop", "Object"]: + # Check if the Output Node is a Sop or Obj node + # also, make a dictionary of all geo obj nodes + # and their sop output node. + all_outputs = {} + # if user selects an ObjSubnet or an ObjNetwork + if output_node.childTypeCategory() == hou.objNodeTypeCategory(): + all_outputs.update({output_node : {}}) + for node in output_node.allSubChildren(): + if node.type().name() == "geo": + out = get_obj_node_output(node) + all_outputs[output_node].update({node: out}) + + # elif user selects a geometry ObjNode + elif output_node.type().name() == "geo": + out = get_obj_node_output(output_node) + all_outputs.update({output_node: out}) + + # elif user selects a SopNode + elif output_node.type().category().name() == "Sop": + # expetional case because output_node is not an obj node + all_outputs.update({output_node: output_node}) + + # Then it's wrong node type + else: cls.log.error( - "Output node %s is not a SOP or OBJ node. " - "It must point to a SOP or OBJ node, " - "instead found category type: %s" - % (output_node.path(), output_node.type().category().name()) + "Output node %s is not a SOP or OBJ Geo or OBJ SubNet node. " + "Instead found category type: %s %s" + , output_node.path(), output_node.type().category().name() + , output_node.type().name() ) return [output_node] + + # Check if geo obj node have geometry. + # return geo obj node if their sop output node + valid = {} + invalid = defaultdict(list) + cls.filter_inner_dict(all_outputs, valid, invalid) + + invalid_prim_types = ["VDB", "Volume"] + for obj_node, sop_node in valid.items(): + # Empty Geometry test + if not hasattr(sop_node, "geometry"): + invalid["empty_geometry"].append(sop_node) + cls.log.error( + "Sop node '%s' includes no geometry." + , sop_node.path() + ) + continue + + frame = instance.data.get("frameStart", 0) + geo = sop_node.geometryAtFrame(frame) + if len(geo.iterPrims()) == 0: + invalid["empty_geometry"].append(sop_node) + cls.log.error( + "Sop node '%s' includes no geometry." + , sop_node.path() + ) + continue + + # Invalid Prims test + for prim_type in invalid_prim_types: + if geo.countPrimType(prim_type) > 0: + invalid["invalid_prims"].append(sop_node) + cls.log.error( + "Sop node '%s' includes invliad prims of type '%s'." + , sop_node.path(), prim_type + ) + + if invalid: + return invalid + + @classmethod + def filter_inner_dict(cls, d: dict, valid: dict, invalid: dict): + """Parse the dictionary and filter items to valid and invalid. + + Invalid items have empty values like {}, None + Valid dictionary is a flattened dictionary that includes + the valid inner items. + """ + + for k, v in d.items(): + if not v: + invalid["empty_objs"].append(k) + elif isinstance(v, dict): + cls.filter_inner_dict(v, valid, invalid) + else: + valid.update({k:v}) From ad0c6245cdd347fed47b075e1ecb13e5dfb8c359 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 11 Sep 2023 15:52:11 +0300 Subject: [PATCH 56/63] remove unnecessary logic --- .../publish/validate_fbx_output_node.py | 89 +++++++++---------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py index 9f6a1b8767..d06ef593d3 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py @@ -28,23 +28,15 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): def process(self, instance): - invalid = self.get_invalid_categorized(instance) + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( "Output node(s) are incorrect", title="Invalid output node(s)" ) + @classmethod def get_invalid(cls, instance): - out = cls.get_invalid_categorized(instance).values() - invalid = [] - for row in out: - invalid += row - return invalid - - - @classmethod - def get_invalid_categorized(cls, instance): output_node = instance.data.get("output_node") # Check if The Output Node Path is set and @@ -58,27 +50,47 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): return [rop_node] - # Check if the Output Node is a Sop or Obj node - # also, make a dictionary of all geo obj nodes - # and their sop output node. - all_outputs = {} - # if user selects an ObjSubnet or an ObjNetwork + # Check if the Output Node is a Sop or an Obj node + # also, list all sop output nodes inside as well as + # invalid empty nodes. + all_out_sops = [] + invalid = defaultdict(list) + + # if output_node is an ObjSubnet or an ObjNetwork if output_node.childTypeCategory() == hou.objNodeTypeCategory(): - all_outputs.update({output_node : {}}) for node in output_node.allSubChildren(): if node.type().name() == "geo": out = get_obj_node_output(node) - all_outputs[output_node].update({node: out}) + if out: + all_out_sops.append(out) + else: + invalid["empty_objs"].append(node) + cls.log.error( + "Geo Obj Node '%s' is empty!" + , node.path() + ) + if not all_out_sops: + invalid["empty_objs"].append(output_node) + cls.log.error( + "Output Node '%s' is empty!" + , node.path() + ) - # elif user selects a geometry ObjNode + # elif output_node is an ObjNode elif output_node.type().name() == "geo": out = get_obj_node_output(output_node) - all_outputs.update({output_node: out}) + if out: + all_out_sops.append(out) + else: + invalid["empty_objs"].append(node) + cls.log.error( + "Output Node '%s' is empty!" + , node.path() + ) - # elif user selects a SopNode + # elif output_node is a SopNode elif output_node.type().category().name() == "Sop": - # expetional case because output_node is not an obj node - all_outputs.update({output_node: output_node}) + all_out_sops.append(output_node) # Then it's wrong node type else: @@ -90,19 +102,15 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): ) return [output_node] - # Check if geo obj node have geometry. - # return geo obj node if their sop output node - valid = {} - invalid = defaultdict(list) - cls.filter_inner_dict(all_outputs, valid, invalid) - + # Check if all output sop nodes have geometry + # and don't contain invalid prims invalid_prim_types = ["VDB", "Volume"] - for obj_node, sop_node in valid.items(): + for sop_node in all_out_sops: # Empty Geometry test if not hasattr(sop_node, "geometry"): invalid["empty_geometry"].append(sop_node) cls.log.error( - "Sop node '%s' includes no geometry." + "Sop node '%s' doesn't include any prims." , sop_node.path() ) continue @@ -112,7 +120,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): if len(geo.iterPrims()) == 0: invalid["empty_geometry"].append(sop_node) cls.log.error( - "Sop node '%s' includes no geometry." + "Sop node '%s' doesn't include any prims." , sop_node.path() ) continue @@ -127,21 +135,4 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): ) if invalid: - return invalid - - @classmethod - def filter_inner_dict(cls, d: dict, valid: dict, invalid: dict): - """Parse the dictionary and filter items to valid and invalid. - - Invalid items have empty values like {}, None - Valid dictionary is a flattened dictionary that includes - the valid inner items. - """ - - for k, v in d.items(): - if not v: - invalid["empty_objs"].append(k) - elif isinstance(v, dict): - cls.filter_inner_dict(v, valid, invalid) - else: - valid.update({k:v}) + return [output_node] From 20f4b62213530118829dbbe027d25c982f80675b Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 11 Sep 2023 16:33:28 +0300 Subject: [PATCH 57/63] Remove unnecessary line --- .../hosts/houdini/plugins/publish/validate_mesh_is_static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py index 4d0904eb53..6bf94f7536 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -30,7 +30,7 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: - nodes = [n.path() for n in invalid if isinstance(n, hou.Node)] + nodes = [n.path() for n in invalid] raise PublishValidationError( "See log for details. " "Invalid nodes: {0}".format(nodes) From 21c174c47bd761392ff6856346bdedf5ece732d9 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 11 Sep 2023 16:34:05 +0300 Subject: [PATCH 58/63] update error message --- .../publish/validate_fbx_output_node.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py index d06ef593d3..d493092755 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py @@ -6,7 +6,6 @@ from openpype.hosts.houdini.api.action import ( SelectROPAction, ) from openpype.hosts.houdini.api.lib import get_obj_node_output -from collections import defaultdict import hou @@ -30,8 +29,10 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: + nodes = [n.path() for n in invalid] raise PublishValidationError( - "Output node(s) are incorrect", + "See log for details. " + "Invalid nodes: {0}".format(nodes), title="Invalid output node(s)" ) @@ -54,7 +55,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): # also, list all sop output nodes inside as well as # invalid empty nodes. all_out_sops = [] - invalid = defaultdict(list) + invalid = [] # if output_node is an ObjSubnet or an ObjNetwork if output_node.childTypeCategory() == hou.objNodeTypeCategory(): @@ -64,13 +65,13 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): if out: all_out_sops.append(out) else: - invalid["empty_objs"].append(node) + invalid.append(node) # empty_objs cls.log.error( "Geo Obj Node '%s' is empty!" , node.path() ) if not all_out_sops: - invalid["empty_objs"].append(output_node) + invalid.append(output_node) # empty_objs cls.log.error( "Output Node '%s' is empty!" , node.path() @@ -82,7 +83,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): if out: all_out_sops.append(out) else: - invalid["empty_objs"].append(node) + invalid.append(node) # empty_objs cls.log.error( "Output Node '%s' is empty!" , node.path() @@ -92,7 +93,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): elif output_node.type().category().name() == "Sop": all_out_sops.append(output_node) - # Then it's wrong node type + # Then it's a wrong node type else: cls.log.error( "Output node %s is not a SOP or OBJ Geo or OBJ SubNet node. " @@ -108,7 +109,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): for sop_node in all_out_sops: # Empty Geometry test if not hasattr(sop_node, "geometry"): - invalid["empty_geometry"].append(sop_node) + invalid.append(sop_node) # empty_geometry cls.log.error( "Sop node '%s' doesn't include any prims." , sop_node.path() @@ -118,7 +119,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): frame = instance.data.get("frameStart", 0) geo = sop_node.geometryAtFrame(frame) if len(geo.iterPrims()) == 0: - invalid["empty_geometry"].append(sop_node) + invalid.append(sop_node) # empty_geometry cls.log.error( "Sop node '%s' doesn't include any prims." , sop_node.path() @@ -128,11 +129,11 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): # Invalid Prims test for prim_type in invalid_prim_types: if geo.countPrimType(prim_type) > 0: - invalid["invalid_prims"].append(sop_node) + invalid.append(sop_node) # invalid_prims cls.log.error( - "Sop node '%s' includes invliad prims of type '%s'." + "Sop node '%s' includes invalid prims of type '%s'." , sop_node.path(), prim_type ) if invalid: - return [output_node] + return invalid From 9105e74b4326686510536cc9e2f8fd37f44be563 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 11 Sep 2023 16:43:26 +0300 Subject: [PATCH 59/63] resolve hound --- .../publish/validate_fbx_output_node.py | 32 +++++++++---------- .../publish/validate_mesh_is_static.py | 2 -- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py index d493092755..ea13d25122 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py @@ -67,14 +67,14 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): else: invalid.append(node) # empty_objs cls.log.error( - "Geo Obj Node '%s' is empty!" - , node.path() + "Geo Obj Node '%s' is empty!", + node.path() ) if not all_out_sops: invalid.append(output_node) # empty_objs cls.log.error( - "Output Node '%s' is empty!" - , node.path() + "Output Node '%s' is empty!", + node.path() ) # elif output_node is an ObjNode @@ -85,21 +85,21 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): else: invalid.append(node) # empty_objs cls.log.error( - "Output Node '%s' is empty!" - , node.path() + "Output Node '%s' is empty!", + node.path() ) # elif output_node is a SopNode elif output_node.type().category().name() == "Sop": - all_out_sops.append(output_node) + all_out_sops.append(output_node) # Then it's a wrong node type else: cls.log.error( "Output node %s is not a SOP or OBJ Geo or OBJ SubNet node. " - "Instead found category type: %s %s" - , output_node.path(), output_node.type().category().name() - , output_node.type().name() + "Instead found category type: %s %s", + output_node.path(), output_node.type().category().name(), + output_node.type().name() ) return [output_node] @@ -111,8 +111,8 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): if not hasattr(sop_node, "geometry"): invalid.append(sop_node) # empty_geometry cls.log.error( - "Sop node '%s' doesn't include any prims." - , sop_node.path() + "Sop node '%s' doesn't include any prims.", + sop_node.path() ) continue @@ -121,8 +121,8 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): if len(geo.iterPrims()) == 0: invalid.append(sop_node) # empty_geometry cls.log.error( - "Sop node '%s' doesn't include any prims." - , sop_node.path() + "Sop node '%s' doesn't include any prims.", + sop_node.path() ) continue @@ -131,8 +131,8 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): if geo.countPrimType(prim_type) > 0: invalid.append(sop_node) # invalid_prims cls.log.error( - "Sop node '%s' includes invalid prims of type '%s'." - , sop_node.path(), prim_type + "Sop node '%s' includes invalid prims of type '%s'.", + sop_node.path(), prim_type ) if invalid: diff --git a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py index 6bf94f7536..b499682e0b 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mesh_is_static.py @@ -10,8 +10,6 @@ from openpype.pipeline.publish import ValidateContentsOrder from openpype.hosts.houdini.api.action import SelectInvalidAction from openpype.hosts.houdini.api.lib import get_output_children -import hou - class ValidateMeshIsStatic(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): From 583cfba38c2f4ffbf0c72795ac4adb48d98715e5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 11 Sep 2023 16:48:38 +0300 Subject: [PATCH 60/63] update doc string --- .../hosts/houdini/plugins/publish/validate_fbx_output_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py index ea13d25122..894dad7d72 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_fbx_output_node.py @@ -17,6 +17,7 @@ class ValidateFBXOutputNode(pyblish.api.InstancePlugin): - The Output Node Path refers to an existing object. - The Output Node is a Sop or Obj node. - The Output Node has geometry data. + - The Output Node doesn't include invalid primitive types. """ order = pyblish.api.ValidatorOrder From 2372e552d39137d0fecb3a93bf03dda1581c0361 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 13 Sep 2023 10:46:48 +0300 Subject: [PATCH 61/63] use more decriptive variable name --- openpype/hosts/houdini/plugins/publish/extract_fbx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_fbx.py b/openpype/hosts/houdini/plugins/publish/extract_fbx.py index dd61e68f3b..7993b3352f 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_fbx.py +++ b/openpype/hosts/houdini/plugins/publish/extract_fbx.py @@ -21,11 +21,11 @@ class ExtractFBX(publish.Extractor): # get rop node ropnode = hou.node(instance.data.get("instance_node")) - output_node = ropnode.evalParm("sopoutput") + output_file = ropnode.evalParm("sopoutput") # get staging_dir and file_name - staging_dir = os.path.normpath(os.path.dirname(output_node)) - file_name = os.path.basename(output_node) + staging_dir = os.path.normpath(os.path.dirname(output_file)) + file_name = os.path.basename(output_file) # render rop self.log.debug("Writing FBX '%s' to '%s'", file_name, staging_dir) From 6c70df2a2e482cc1ad21d9f62b05a30e4282865a Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 18 Sep 2023 21:02:58 +0300 Subject: [PATCH 62/63] remove unnecessary logic --- .../hosts/houdini/plugins/load/load_fbx.py | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 7e7f0c04e5..cac22d62d4 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -21,8 +21,9 @@ class FbxLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): - # get file path - file_path = self.get_file_path(context=context) + # get file path from context + file_path = self.filepath_from_context(context) + file_path = file_path.replace("\\", "/") # get necessary data namespace, node_name = self.get_node_name(context, name, namespace) @@ -56,8 +57,9 @@ class FbxLoader(load.LoaderPlugin): self.log.error("Could not find node of type `file`") return - # Update the file path - file_path = self.get_file_path(representation=representation) + # Update the file path from representation + file_path = get_representation_path(representation) + file_path = file_path.replace("\\", "/") file_node.setParms({"file": file_path}) @@ -72,19 +74,6 @@ class FbxLoader(load.LoaderPlugin): def switch(self, container, representation): self.update(container, representation) - def get_file_path(self, context=None, representation=None): - """Return formatted file path.""" - - # Format file name, Houdini only wants forward slashes - if context: - file_path = self.filepath_from_context(context) - elif representation: - file_path = get_representation_path(representation) - else: - return "" - - return file_path.replace("\\", "/") - def get_node_name(self, context, name=None, namespace=None): """Define node name.""" From d91f54f2f74edac0a729513639f40ed2558e5ff5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 20 Sep 2023 19:18:33 +0300 Subject: [PATCH 63/63] rename files and make validator optional --- ...e_unreal_staticmesh.py => create_staticmesh.py} | 14 +++++++------- .../plugins/publish/collect_staticmesh_type.py | 2 +- .../publish/validate_unreal_staticmesh_naming.py | 2 +- .../settings/defaults/project_settings/global.json | 3 +-- .../defaults/project_settings/houdini.json | 4 ++-- .../schemas/schema_houdini_create.json | 4 ++-- server_addon/core/server/settings/tools.py | 3 +-- .../houdini/server/settings/publish_plugins.py | 12 ++++++------ 8 files changed, 21 insertions(+), 23 deletions(-) rename openpype/hosts/houdini/plugins/create/{create_unreal_staticmesh.py => create_staticmesh.py} (91%) diff --git a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/houdini/plugins/create/create_staticmesh.py similarity index 91% rename from openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py rename to openpype/hosts/houdini/plugins/create/create_staticmesh.py index 2f92def54a..ea0b36f03f 100644 --- a/openpype/hosts/houdini/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/houdini/plugins/create/create_staticmesh.py @@ -6,11 +6,11 @@ from openpype.lib import BoolDef, EnumDef import hou -class CreateUnrealStaticMesh(plugin.HoudiniCreator): - """Unreal Static Meshes with collisions. """ +class CreateStaticMesh(plugin.HoudiniCreator): + """Static Meshes as FBX. """ - identifier = "io.openpype.creators.houdini.unrealstaticmesh.fbx" - label = "Unreal - Static Mesh (FBX)" + identifier = "io.openpype.creators.houdini.staticmesh.fbx" + label = "Static Mesh (FBX)" family = "staticMesh" icon = "fa5s.cubes" @@ -20,7 +20,7 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): instance_data.update({"node_type": "filmboxfbx"}) - instance = super(CreateUnrealStaticMesh, self).create( + instance = super(CreateStaticMesh, self).create( subset_name, instance_data, pre_create_data) @@ -60,7 +60,7 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): def get_pre_create_attr_defs(self): """Add settings for users. """ - attrs = super(CreateUnrealStaticMesh, self).get_pre_create_attr_defs() + attrs = super(CreateStaticMesh, self).get_pre_create_attr_defs() createsubnetroot = BoolDef("createsubnetroot", tooltip="Create an extra root for the " "Export node when it's a " @@ -93,7 +93,7 @@ class CreateUnrealStaticMesh(plugin.HoudiniCreator): The default subset name templates for Unreal include {asset} and thus we should pass that along as dynamic data. """ - dynamic_data = super(CreateUnrealStaticMesh, self).get_dynamic_data( + dynamic_data = super(CreateStaticMesh, self).get_dynamic_data( variant, task_name, asset_doc, project_name, host_name, instance ) dynamic_data["asset"] = asset_doc["name"] diff --git a/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py b/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py index 263d7c1001..db9efec7a1 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py +++ b/openpype/hosts/houdini/plugins/publish/collect_staticmesh_type.py @@ -15,6 +15,6 @@ class CollectStaticMeshType(pyblish.api.InstancePlugin): def process(self, instance): - if instance.data["creator_identifier"] == "io.openpype.creators.houdini.unrealstaticmesh.fbx": # noqa: E501 + if instance.data["creator_identifier"] == "io.openpype.creators.houdini.staticmesh.fbx": # noqa: E501 # Marking this instance as FBX triggers the FBX extractor. instance.data["families"] += ["fbx"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py index 791db8198f..ae3c7e5602 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/houdini/plugins/publish/validate_unreal_staticmesh_naming.py @@ -42,7 +42,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin, def apply_settings(cls, project_settings, system_settings): settings = ( - project_settings["houdini"]["create"]["CreateUnrealStaticMesh"] + project_settings["houdini"]["create"]["CreateStaticMesh"] ) cls.collision_prefixes = settings["collision_prefixes"] cls.static_mesh_prefix = settings["static_mesh_prefix"] diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 52ac745f6d..06a595d1c5 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -429,8 +429,7 @@ "staticMesh" ], "hosts": [ - "maya", - "houdini" + "maya" ], "task_types": [], "tasks": [], diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 6964db0013..5392fc34dd 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -19,7 +19,7 @@ ], "ext": ".ass" }, - "CreateUnrealStaticMesh": { + "CreateStaticMesh": { "enabled": true, "default_variants": [ "Main" @@ -127,7 +127,7 @@ "active": true }, "ValidateUnrealStaticMeshName": { - "enabled": true, + "enabled": false, "optional": true, "active": true } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json index b19761df91..cd8c260124 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json @@ -42,8 +42,8 @@ { "type": "dict", "collapsible": true, - "key": "CreateUnrealStaticMesh", - "label": "Create Unreal - Static Mesh", + "key": "CreateStaticMesh", + "label": "Create Static Mesh", "checkbox_key": "enabled", "children": [ { diff --git a/server_addon/core/server/settings/tools.py b/server_addon/core/server/settings/tools.py index 5dbe6ab215..7befc795e4 100644 --- a/server_addon/core/server/settings/tools.py +++ b/server_addon/core/server/settings/tools.py @@ -370,8 +370,7 @@ DEFAULT_TOOLS_VALUES = { "staticMesh" ], "hosts": [ - "maya", - "houdini" + "maya" ], "task_types": [], "tasks": [], diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py index 6ceff028a5..58240b0205 100644 --- a/server_addon/houdini/server/settings/publish_plugins.py +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -21,7 +21,7 @@ class CreateArnoldAssModel(BaseSettingsModel): ext: str = Field(Title="Extension") -class CreateUnrealStaticMeshModel(BaseSettingsModel): +class CreateStaticMeshModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") default_variants: list[str] = Field( default_factory=list, @@ -39,9 +39,9 @@ class CreatePluginsModel(BaseSettingsModel): default_factory=CreateArnoldAssModel, title="Create Alembic Camera") # "-" is not compatible in the new model - CreateUnrealStaticMesh: CreateUnrealStaticMeshModel = Field( - default_factory=CreateUnrealStaticMeshModel, - title="Create Unreal_Static Mesh" + CreateStaticMesh: CreateStaticMeshModel = Field( + default_factory=CreateStaticMeshModel, + title="Create Static Mesh" ) CreateAlembicCamera: CreatorModel = Field( default_factory=CreatorModel, @@ -81,7 +81,7 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = { "default_variants": ["Main"], "ext": ".ass" }, - "CreateUnrealStaticMesh": { + "CreateStaticMesh": { "enabled": True, "default_variants": [ "Main" @@ -212,7 +212,7 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "active": True }, "ValidateUnrealStaticMeshName": { - "enabled": True, + "enabled": False, "optional": True, "active": True }