mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #219 from BigRoy/houdini_fixes
Houdini cosmetics and fix Alembic Camera publishing
This commit is contained in:
commit
a8078e7706
11 changed files with 176 additions and 22 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin):
|
|||
|
||||
The connected node cannot be of the following types for Alembic:
|
||||
- VDB
|
||||
- Volumne
|
||||
- Volume
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
34
colorbleed/plugins/houdini/publish/validate_bypass.py
Normal file
34
colorbleed/plugins/houdini/publish/validate_bypass.py
Normal 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]
|
||||
41
colorbleed/plugins/houdini/publish/validate_camera_rop.py
Normal file
41
colorbleed/plugins/houdini/publish/validate_camera_rop.py
Normal 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()))
|
||||
|
||||
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue