mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
validate mesh name
This commit is contained in:
parent
d1e82ceb7e
commit
e6b9a7d038
3 changed files with 102 additions and 364 deletions
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue