mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #4819 from BigRoy/enhancement/houdini_validate_vdb_output_node_speed
This commit is contained in:
commit
254c976d07
5 changed files with 210 additions and 109 deletions
46
openpype/hosts/houdini/api/action.py
Normal file
46
openpype/hosts/houdini/api/action.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import pyblish.api
|
||||
import hou
|
||||
|
||||
from openpype.pipeline.publish import get_errored_instances_from_context
|
||||
|
||||
|
||||
class SelectInvalidAction(pyblish.api.Action):
|
||||
"""Select invalid nodes in Maya when plug-in failed.
|
||||
|
||||
To retrieve the invalid nodes this assumes a static `get_invalid()`
|
||||
method is available on the plugin.
|
||||
|
||||
"""
|
||||
label = "Select invalid"
|
||||
on = "failed" # This action is only available on a failed plug-in
|
||||
icon = "search" # Icon from Awesome Icon
|
||||
|
||||
def process(self, context, plugin):
|
||||
|
||||
errored_instances = get_errored_instances_from_context(context)
|
||||
|
||||
# Apply pyblish.logic to get the instances for the plug-in
|
||||
instances = pyblish.api.instances_by_plugin(errored_instances, plugin)
|
||||
|
||||
# Get the invalid nodes for the plug-ins
|
||||
self.log.info("Finding invalid nodes..")
|
||||
invalid = list()
|
||||
for instance in instances:
|
||||
invalid_nodes = plugin.get_invalid(instance)
|
||||
if invalid_nodes:
|
||||
if isinstance(invalid_nodes, (list, tuple)):
|
||||
invalid.extend(invalid_nodes)
|
||||
else:
|
||||
self.log.warning("Plug-in returned to be invalid, "
|
||||
"but has no selectable nodes.")
|
||||
|
||||
hou.clearAllSelected()
|
||||
if invalid:
|
||||
self.log.info("Selecting invalid nodes: {}".format(
|
||||
", ".join(node.path() for node in invalid)
|
||||
))
|
||||
for node in invalid:
|
||||
node.setSelected(True)
|
||||
node.setCurrent(True)
|
||||
else:
|
||||
self.log.info("No invalid nodes found.")
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Scene setting</title>
|
||||
<description>
|
||||
## Invalid input node
|
||||
|
||||
VDB input must have the same number of VDBs, points, primitives and vertices as output.
|
||||
|
||||
</description>
|
||||
<detail>
|
||||
### __Detailed Info__ (optional)
|
||||
|
||||
A VDB is an inherited type of Prim, holds the following data:
|
||||
- Primitives: 1
|
||||
- Points: 1
|
||||
- Vertices: 1
|
||||
- VDBs: 1
|
||||
</detail>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Invalid VDB</title>
|
||||
<description>
|
||||
## Invalid VDB output
|
||||
|
||||
All primitives of the output geometry must be VDBs, no other primitive
|
||||
types are allowed. That means that regardless of the amount of VDBs in the
|
||||
geometry it will have an equal amount of VDBs, points, primitives and
|
||||
vertices since each VDB primitive is one point, one vertex and one VDB.
|
||||
|
||||
This validation only checks the geometry on the first frame of the export
|
||||
frame range.
|
||||
|
||||
|
||||
|
||||
</description>
|
||||
<detail>
|
||||
### Detailed Info
|
||||
|
||||
ROP node `{rop_path}` is set to export SOP path `{sop_path}`.
|
||||
|
||||
{message}
|
||||
|
||||
</detail>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pyblish.api
|
||||
from openpype.pipeline import (
|
||||
PublishValidationError
|
||||
)
|
||||
|
||||
|
||||
class ValidateVDBInputNode(pyblish.api.InstancePlugin):
|
||||
"""Validate that the node connected to the output node is of type VDB.
|
||||
|
||||
Regardless of the amount of VDBs create the output will need to have an
|
||||
equal amount of VDBs, points, primitives and vertices
|
||||
|
||||
A VDB is an inherited type of Prim, holds the following data:
|
||||
- Primitives: 1
|
||||
- Points: 1
|
||||
- Vertices: 1
|
||||
- VDBs: 1
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder + 0.1
|
||||
families = ["vdbcache"]
|
||||
hosts = ["houdini"]
|
||||
label = "Validate Input Node (VDB)"
|
||||
|
||||
def process(self, instance):
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
self,
|
||||
"Node connected to the output node is not of type VDB",
|
||||
title=self.label
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
node = instance.data["output_node"]
|
||||
|
||||
prims = node.geometry().prims()
|
||||
nr_of_prims = len(prims)
|
||||
|
||||
nr_of_points = len(node.geometry().points())
|
||||
if nr_of_points != nr_of_prims:
|
||||
cls.log.error("The number of primitives and points do not match")
|
||||
return [instance]
|
||||
|
||||
for prim in prims:
|
||||
if prim.numVertices() != 1:
|
||||
cls.log.error("Found primitive with more than 1 vertex!")
|
||||
return [instance]
|
||||
|
|
@ -1,14 +1,73 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import contextlib
|
||||
|
||||
import pyblish.api
|
||||
import hou
|
||||
from openpype.pipeline import PublishValidationError
|
||||
|
||||
from openpype.pipeline import PublishXmlValidationError
|
||||
from openpype.hosts.houdini.api.action import SelectInvalidAction
|
||||
|
||||
|
||||
def group_consecutive_numbers(nums):
|
||||
"""
|
||||
Args:
|
||||
nums (list): List of sorted integer numbers.
|
||||
|
||||
Yields:
|
||||
str: Group ranges as {start}-{end} if more than one number in the range
|
||||
else it yields {end}
|
||||
|
||||
"""
|
||||
start = None
|
||||
end = None
|
||||
|
||||
def _result(a, b):
|
||||
if a == b:
|
||||
return "{}".format(a)
|
||||
else:
|
||||
return "{}-{}".format(a, b)
|
||||
|
||||
for num in nums:
|
||||
if start is None:
|
||||
start = num
|
||||
end = num
|
||||
elif num == end + 1:
|
||||
end = num
|
||||
else:
|
||||
yield _result(start, end)
|
||||
start = num
|
||||
end = num
|
||||
if start is not None:
|
||||
yield _result(start, end)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def update_mode_context(mode):
|
||||
original = hou.updateModeSetting()
|
||||
try:
|
||||
hou.setUpdateMode(mode)
|
||||
yield
|
||||
finally:
|
||||
hou.setUpdateMode(original)
|
||||
|
||||
|
||||
def get_geometry_at_frame(sop_node, frame, force=True):
|
||||
"""Return geometry at frame but force a cooked value."""
|
||||
with update_mode_context(hou.updateMode.AutoUpdate):
|
||||
sop_node.cook(force=force, frame_range=(frame, frame))
|
||||
return sop_node.geometryAtFrame(frame)
|
||||
|
||||
|
||||
class ValidateVDBOutputNode(pyblish.api.InstancePlugin):
|
||||
"""Validate that the node connected to the output node is of type VDB.
|
||||
|
||||
Regardless of the amount of VDBs create the output will need to have an
|
||||
equal amount of VDBs, points, primitives and vertices
|
||||
All primitives of the output geometry must be VDBs, no other primitive
|
||||
types are allowed. That means that regardless of the amount of VDBs in the
|
||||
geometry it will have an equal amount of VDBs, points, primitives and
|
||||
vertices since each VDB primitive is one point, one vertex and one VDB.
|
||||
|
||||
This validation only checks the geometry on the first frame of the export
|
||||
frame range for optimization purposes.
|
||||
|
||||
A VDB is an inherited type of Prim, holds the following data:
|
||||
- Primitives: 1
|
||||
|
|
@ -22,54 +81,95 @@ class ValidateVDBOutputNode(pyblish.api.InstancePlugin):
|
|||
families = ["vdbcache"]
|
||||
hosts = ["houdini"]
|
||||
label = "Validate Output Node (VDB)"
|
||||
actions = [SelectInvalidAction]
|
||||
|
||||
def process(self, instance):
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
"Node connected to the output node is not" " of type VDB!",
|
||||
title=self.label
|
||||
invalid_nodes, message = self.get_invalid_with_message(instance)
|
||||
if invalid_nodes:
|
||||
|
||||
# instance_node is str, but output_node is hou.Node so we convert
|
||||
output = instance.data.get("output_node")
|
||||
output_path = output.path() if output else None
|
||||
|
||||
raise PublishXmlValidationError(
|
||||
self,
|
||||
"Invalid VDB content: {}".format(message),
|
||||
formatting_data={
|
||||
"message": message,
|
||||
"rop_path": instance.data.get("instance_node"),
|
||||
"sop_path": output_path
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
def get_invalid_with_message(cls, instance):
|
||||
|
||||
node = instance.data["output_node"]
|
||||
node = instance.data.get("output_node")
|
||||
if node is None:
|
||||
cls.log.error(
|
||||
instance_node = instance.data.get("instance_node")
|
||||
error = (
|
||||
"SOP path is not correctly set on "
|
||||
"ROP node '%s'." % instance.data.get("instance_node")
|
||||
"ROP node `{}`.".format(instance_node)
|
||||
)
|
||||
return [instance]
|
||||
return [hou.node(instance_node), error]
|
||||
|
||||
frame = instance.data.get("frameStart", 0)
|
||||
geometry = node.geometryAtFrame(frame)
|
||||
geometry = get_geometry_at_frame(node, frame)
|
||||
if geometry is None:
|
||||
# No geometry data on this node, maybe the node hasn't cooked?
|
||||
cls.log.error(
|
||||
"SOP node has no geometry data. "
|
||||
"Is it cooked? %s" % node.path()
|
||||
error = (
|
||||
"SOP node `{}` has no geometry data. "
|
||||
"Was it unable to cook?".format(node.path())
|
||||
)
|
||||
return [node]
|
||||
return [node, error]
|
||||
|
||||
prims = geometry.prims()
|
||||
nr_of_prims = len(prims)
|
||||
num_prims = geometry.intrinsicValue("primitivecount")
|
||||
num_points = geometry.intrinsicValue("pointcount")
|
||||
if num_prims == 0 and num_points == 0:
|
||||
# Since we are only checking the first frame it doesn't mean there
|
||||
# won't be VDB prims in a few frames. As such we'll assume for now
|
||||
# the user knows what he or she is doing
|
||||
cls.log.warning(
|
||||
"SOP node `{}` has no primitives on start frame {}. "
|
||||
"Validation is skipped and it is assumed elsewhere in the "
|
||||
"frame range VDB prims and only VDB prims will exist."
|
||||
"".format(node.path(), int(frame))
|
||||
)
|
||||
return [None, None]
|
||||
|
||||
# All primitives must be hou.VDB
|
||||
invalid_prim = False
|
||||
for prim in prims:
|
||||
if not isinstance(prim, hou.VDB):
|
||||
cls.log.error("Found non-VDB primitive: %s" % prim)
|
||||
invalid_prim = True
|
||||
if invalid_prim:
|
||||
return [instance]
|
||||
num_vdb_prims = geometry.countPrimType(hou.primType.VDB)
|
||||
cls.log.debug("Detected {} VDB primitives".format(num_vdb_prims))
|
||||
if num_prims != num_vdb_prims:
|
||||
# There's at least one primitive that is not a VDB.
|
||||
# Search them and report them to the artist.
|
||||
prims = geometry.prims()
|
||||
invalid_prims = [prim for prim in prims
|
||||
if not isinstance(prim, hou.VDB)]
|
||||
if invalid_prims:
|
||||
# Log prim numbers as consecutive ranges so logging isn't very
|
||||
# slow for large number of primitives
|
||||
error = (
|
||||
"Found non-VDB primitives for `{}`. "
|
||||
"Primitive indices {} are not VDB primitives.".format(
|
||||
node.path(),
|
||||
", ".join(group_consecutive_numbers(
|
||||
prim.number() for prim in invalid_prims
|
||||
))
|
||||
)
|
||||
)
|
||||
return [node, error]
|
||||
|
||||
nr_of_points = len(geometry.points())
|
||||
if nr_of_points != nr_of_prims:
|
||||
cls.log.error("The number of primitives and points do not match")
|
||||
return [instance]
|
||||
if num_points != num_vdb_prims:
|
||||
# We have points unrelated to the VDB primitives.
|
||||
error = (
|
||||
"The number of primitives and points do not match in '{}'. "
|
||||
"This likely means you have unconnected points, which we do "
|
||||
"not allow in the VDB output.".format(node.path()))
|
||||
return [node, error]
|
||||
|
||||
for prim in prims:
|
||||
if prim.numVertices() != 1:
|
||||
cls.log.error("Found primitive with more than 1 vertex!")
|
||||
return [instance]
|
||||
return [None, None]
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
nodes, _ = cls.get_invalid_with_message(instance)
|
||||
return nodes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue