Merge pull request #219 from BigRoy/houdini_fixes

Houdini cosmetics and fix Alembic Camera publishing
This commit is contained in:
Roy Nieterau 2019-01-15 10:21:33 +01:00 committed by GitHub
commit a8078e7706
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 176 additions and 22 deletions

View file

@ -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)

View file

@ -2,6 +2,7 @@ from avalon import houdini
class CreateAlembicCamera(houdini.Creator):
"""Single baked camera from Alembic ROP"""
name = "camera"
label = "Camera (Abc)"
@ -20,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)

View file

@ -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"
@ -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}

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -7,7 +7,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin):
The connected node cannot be of the following types for Alembic:
- VDB
- Volumne
- Volume
"""

View file

@ -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]

View file

@ -0,0 +1,41 @@
import pyblish.api
import colorbleed.api
class ValidateCameraROP(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()))

View file

@ -6,7 +6,9 @@ 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',
'colorbleed.vdbcache']
hosts = ['houdini']
label = 'Create Intermediate Directories Checked'
@ -14,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):

View file

@ -7,13 +7,15 @@ 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.
"""
order = pyblish.api.ValidatorOrder
families = ["*"]
families = ["colorbleed.pointcache",
"colorbleed.vdbcache"]
hosts = ["houdini"]
label = "Validate Output Node"
@ -27,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:
@ -35,18 +39,35 @@ class ValidateOutputNode(pyblish.api.InstancePlugin):
"Ensure a valid SOP output path is set."
% node.path())
return node.path()
return [node.path()]
# 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())
# 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.path()]
# 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()]