From d8bcf279c2294f55c04cfa26cb82d09e290a8180 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:22:38 +0100 Subject: [PATCH 01/11] Add docstring to Houdini Creators --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 1 + colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- colorbleed/plugins/houdini/create/create_vbd_cache.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 8da8c4d2b5..c548a6309a 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -2,6 +2,7 @@ from avalon import houdini class CreateAlembicCamera(houdini.Creator): + """Single baked camera from Alembic ROP""" name = "camera" label = "Camera (Abc)" diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index c54a6c91a6..4eab69fd98 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -2,7 +2,7 @@ from avalon import houdini class CreatePointCache(houdini.Creator): - """Alembic pointcache for animated data""" + """Alembic ROP to pointcache""" name = "pointcache" label = "Point Cache" diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 617975a711..ebdb1271ce 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -2,7 +2,7 @@ from avalon import houdini class CreateVDBCache(houdini.Creator): - """Alembic pointcache for animated data""" + """OpenVDB from Geometry ROP""" name = "vbdcache" label = "VDB Cache" From 7098df26e7fc511d2c540784cfea4467adefd1a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:24:44 +0100 Subject: [PATCH 02/11] Cosmetics typo --- colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 4eab69fd98..85993678c2 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -22,7 +22,7 @@ class CreatePointCache(houdini.Creator): parms = {"use_sop_path": True, # Export single node from SOP Path "build_from_path": True, # Direct path of primitive in output - "path_attrib": "path", # Pass path attribute for output\ + "path_attrib": "path", # Pass path attribute for output "prim_to_detail_pattern": "cbId", "format": 2, # Set format to Ogawa "filename": "$HIP/pyblish/%s.abc" % self.name} From c33e1274f35c4ada86ab21e7ad5e8bae023be3e6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:32:48 +0100 Subject: [PATCH 03/11] Fix docstring typo --- .../plugins/houdini/publish/validate_alembic_input_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py index 91f9e9f97e..663b1198eb 100644 --- a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py +++ b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py @@ -7,7 +7,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): The connected node cannot be of the following types for Alembic: - VDB - - Volumne + - Volume """ From 3e2b506cf302d1ac27e134ef14d30f725231fd84 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 23:25:15 +0100 Subject: [PATCH 04/11] Implement Alembic Camera extraction in Houdini - the right way --- .../houdini/create/create_alembic_camera.py | 20 +++++++-- .../houdini/publish/collect_output_node.py | 3 +- .../houdini/publish/validate_camera_rop.py | 41 +++++++++++++++++++ .../publish/validate_mkpaths_toggled.py | 3 +- .../houdini/publish/validate_output_node.py | 10 ++--- 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 colorbleed/plugins/houdini/publish/validate_camera_rop.py diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index c548a6309a..96449fb3db 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -21,11 +21,25 @@ class CreateAlembicCamera(houdini.Creator): def process(self): instance = super(CreateAlembicCamera, self).process() - parms = {"use_sop_path": True, - "filename": "$HIP/pyblish/%s.abc" % self.name} + parms = { + "filename": "$HIP/pyblish/%s.abc" % self.name, + "use_sop_path": False + } if self.nodes: node = self.nodes[0] - parms.update({"sop_path": node.path()}) + path = node.path() + + # Split the node path into the first root and the remainder + # So we can set the root and objects parameters correctly + _, root, remainder = path.split("/", 2) + parms.update({ + "root": "/" + root, + "objects": remainder + }) instance.setParms(parms) + + # Lock the Use Sop Path setting so the + # user doesn't accidentally enable it. + instance.parm("use_sop_path").lock(True) diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/colorbleed/plugins/houdini/publish/collect_output_node.py index a3f49761b9..d90898944f 100644 --- a/colorbleed/plugins/houdini/publish/collect_output_node.py +++ b/colorbleed/plugins/houdini/publish/collect_output_node.py @@ -5,7 +5,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): """Collect the out node's SOP Path value.""" order = pyblish.api.CollectorOrder - families = ["*"] + families = ["colorbleed.pointcache", + "colorbleed.vdbcache"] hosts = ["houdini"] label = "Collect Output SOP Path" diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/colorbleed/plugins/houdini/publish/validate_camera_rop.py new file mode 100644 index 0000000000..bf09b26df4 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_camera_rop.py @@ -0,0 +1,41 @@ +import pyblish.api +import colorbleed.api + + +class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): + """Validate Camera ROP settings.""" + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.camera'] + hosts = ['houdini'] + label = 'Camera ROP' + + def process(self, instance): + + import hou + + node = instance[0] + if node.parm("use_sop_path").eval(): + raise RuntimeError("Alembic ROP for Camera export should not be " + "set to 'Use Sop Path'. Please disable.") + + # Get the root and objects parameter of the Alembic ROP node + root = node.parm("root").eval() + objects = node.parm("objects").eval() + assert root, "Root parameter must be set on Alembic ROP" + assert root.startswith("/"), "Root parameter must start with slash /" + assert objects, "Objects parameter must be set on Alembic ROP" + assert len(objects.split(" ")) == 1, "Must have only a single object." + + # Check if the object exists and is a camera + path = root + "/" + objects + camera = hou.node(path) + + if not camera: + raise ValueError("Camera path does not exist: %s" % path) + + if not camera.type().name() == "cam": + raise ValueError("Object set in Alembic ROP is not a camera: " + "%s (type: %s)" % (camera, camera.type().name())) + + diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index 1a83565d11..a66c6754be 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -6,7 +6,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate Create Intermediate Directories is enabled on ROP node.""" order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.pointcache'] + families = ['colorbleed.pointcache', + 'colorbleed.camera'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index bb9eec508e..8a088afec9 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -13,7 +13,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["*"] + families = ["colorbleed.pointcache", + "colorbleed.vdbcache"] hosts = ["houdini"] label = "Validate Output Node" @@ -39,10 +40,9 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): # Check if type is correct type_name = output_node.type().name() - if type_name not in ["output", "cam"]: - cls.log.error("Output node `%s` is not an accepted type." - "Expected types: `output` or `camera`" % - output_node.path()) + if type_name != "output": + cls.log.error("Output node `%s` is not an `output` type node." + % output_node.path()) return [output_node.path()] # Check if output node has incoming connections From fa11bb55cdaf539d8faeefa09191c08e505963a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 23:29:49 +0100 Subject: [PATCH 05/11] Refactor class name (fix typo) --- colorbleed/plugins/houdini/publish/validate_camera_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/colorbleed/plugins/houdini/publish/validate_camera_rop.py index bf09b26df4..83f4dd5a57 100644 --- a/colorbleed/plugins/houdini/publish/validate_camera_rop.py +++ b/colorbleed/plugins/houdini/publish/validate_camera_rop.py @@ -2,7 +2,7 @@ import pyblish.api import colorbleed.api -class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): +class ValidateCameraROP(pyblish.api.InstancePlugin): """Validate Camera ROP settings.""" order = colorbleed.api.ValidateContentsOrder From 9b832b486e5ee469208e5a8f6b28c24aadcd99cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 14 Jan 2019 18:24:57 +0100 Subject: [PATCH 06/11] Don't force output node, but allow any SOP node. - Also validate there's actual .geometry() data in the node. --- .../houdini/publish/validate_output_node.py | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 8a088afec9..5f55e1507b 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -7,8 +7,9 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): This will ensure: - The SOP Path is set. - The SOP Path refers to an existing object. - - The SOP Path node is of type 'output' or 'camera' + - The SOP Path node is a SOP node. - The SOP Path node has at least one input connection (has an input) + - The SOP Path has geometry data. """ @@ -28,6 +29,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): + import hou + output_node = instance.data["output_node"] if output_node is None: @@ -38,15 +41,33 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): return node.path() - # Check if type is correct - type_name = output_node.type().name() - if type_name != "output": - cls.log.error("Output node `%s` is not an `output` type node." - % output_node.path()) - return [output_node.path()] + # Output node must be a Sop node. + if not isinstance(output_node, hou.SopNode): + cls.log.error("Output node %s is not a SOP node. " + "SOP Path must point to a SOP node, " + "instead found category type: %s" % ( + output_node.path(), + output_node.type().category().name() + ) + ) + return [output_node] + + # For the sake of completeness also assert the category type + # is Sop to avoid potential edge case scenarios even though + # the isinstance check above should be stricter than this category + assert output_node.type().category().name() == "Sop", ( + "Output node %s is not of category Sop. This is a bug.." % + output_node.path() + ) # Check if output node has incoming connections - if type_name == "output" and not output_node.inputConnections(): + if output_node.inputConnections(): cls.log.error("Output node `%s` has no incoming connections" % output_node.path()) return [output_node.path()] + + # Ensure the output node has at least Geometry data + if not output_node.geometry(): + cls.log.error("Output node `%s` has no geometry data." + % output_node.path()) + return [output_node.path()] From 9d8133ef82cd1d23c7fb280dda958d330b4d8290 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 09:27:26 +0100 Subject: [PATCH 07/11] Improve Houdini saved filename collection, ignore default "untitled.hip" --- .../houdini/publish/collect_current_file.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collect_current_file.py b/colorbleed/plugins/houdini/publish/collect_current_file.py index 7852943b34..ea954c4791 100644 --- a/colorbleed/plugins/houdini/publish/collect_current_file.py +++ b/colorbleed/plugins/houdini/publish/collect_current_file.py @@ -1,3 +1,4 @@ +import os import hou import pyblish.api @@ -12,4 +13,24 @@ class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" - context.data['currentFile'] = hou.hipFile.path() + + filepath = hou.hipFile.path() + if not os.path.exists(filepath): + # By default Houdini will even point a new scene to a path. + # However if the file is not saved at all and does not exist, + # we assume the user never set it. + filepath = "" + + elif os.path.basename(filepath) == "untitled.hip": + # Due to even a new file being called 'untitled.hip' we are unable + # to confirm the current scene was ever saved because the file + # could have existed already. We will allow it if the file exists, + # but show a warning for this edge case to clarify the potential + # false positive. + self.log.warning("Current file is 'untitled.hip' and we are " + "unable to detect whether the current scene is " + "saved correctly.") + + context.data['currentFile'] = filepath + + From f95ef7dd1ca922864b2071df11f78266b95381d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 09:46:07 +0100 Subject: [PATCH 08/11] Houdini correctly set and validate ROP bypass state on instance toggle --- colorbleed/houdini/__init__.py | 19 +++++++++++ .../houdini/publish/validate_bypass.py | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_bypass.py diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index e4640e2857..05d5195605 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -39,6 +39,8 @@ def install(): avalon.on("save", on_save) avalon.on("open", on_open) + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + log.info("Setting default family states for loader..") avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] @@ -91,3 +93,20 @@ def on_open(*args): "your Maya scene.") dialog.on_show.connect(_on_show_inventory) dialog.show() + + +def on_pyblish_instance_toggled(instance, new_value, old_value): + """Toggle saver tool passthrough states on instance toggles.""" + + nodes = instance[:] + if not nodes: + return + + # Assume instance node is first node + instance_node = nodes[0] + + if instance_node.isBypassed() != (not old_value): + print("%s old bypass state didn't match old instance state, " + "updating anyway.." % instance_node.path()) + + instance_node.bypass(not new_value) diff --git a/colorbleed/plugins/houdini/publish/validate_bypass.py b/colorbleed/plugins/houdini/publish/validate_bypass.py new file mode 100644 index 0000000000..9af8a2b9ae --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_bypass.py @@ -0,0 +1,34 @@ +import pyblish.api +import colorbleed.api + + +class ValidateBypassed(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 Alembic + ROP node whenever Build Hierarchy from Attribute is enabled. + + """ + + order = colorbleed.api.ValidateContentsOrder - 0.1 + families = ["*"] + hosts = ["houdini"] + label = "Validate ROP Bypass" + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + rop = invalid[0] + raise RuntimeError( + "ROP node %s is set to bypass, publishing cannot continue.." % + rop.path() + ) + + @classmethod + def get_invalid(cls, instance): + + rop = instance[0] + if rop.isBypassed(): + return [rop] From 5c320088de197080093367d6ada017ae186a43ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:12:59 +0100 Subject: [PATCH 09/11] Improve error readability --- colorbleed/plugins/houdini/publish/validate_output_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 5f55e1507b..81b4e1e3d5 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -23,6 +23,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: + invalid = [node.path() for node in invalid] raise RuntimeError("Output node(s) `%s` are incorrect. " "See plug-in log for details." % invalid) From b5424e2954147eb2aaf4f6824f26e0641b74e48b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:13:36 +0100 Subject: [PATCH 10/11] Include colorbleed.vdbcache family, and report node path in error --- .../plugins/houdini/publish/validate_mkpaths_toggled.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index a66c6754be..826dedf933 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -7,7 +7,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.pointcache', - 'colorbleed.camera'] + 'colorbleed.camera', + 'colorbleed.vdbcache'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' @@ -15,8 +16,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found ROP nodes with Create Intermediate " - "Directories turned off") + raise RuntimeError("Found ROP node with Create Intermediate " + "Directories turned off: %s" % invalid) @classmethod def get_invalid(cls, instance): From f7d755d9fe80959f222c720c4ee7a5d7fc425dc2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:20:47 +0100 Subject: [PATCH 11/11] Correctly pass on node paths for error message readability --- colorbleed/plugins/houdini/publish/validate_output_node.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 81b4e1e3d5..eb84ea721b 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -23,7 +23,6 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - invalid = [node.path() for node in invalid] raise RuntimeError("Output node(s) `%s` are incorrect. " "See plug-in log for details." % invalid) @@ -40,7 +39,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): "Ensure a valid SOP output path is set." % node.path()) - return node.path() + return [node.path()] # Output node must be a Sop node. if not isinstance(output_node, hou.SopNode): @@ -51,7 +50,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): output_node.type().category().name() ) ) - return [output_node] + return [output_node.path()] # For the sake of completeness also assert the category type # is Sop to avoid potential edge case scenarios even though