mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Fix LKD-15: Ensure deformed shapes in look have ids from history (reuse logic from validate_rig_out_set_node_ids.py)
This commit is contained in:
parent
44f935b24b
commit
c4daf66e55
4 changed files with 109 additions and 181 deletions
|
|
@ -1247,3 +1247,44 @@ def remove_other_uv_sets(mesh):
|
||||||
for i in indices:
|
for i in indices:
|
||||||
attr = '{0}.uvSet[{1}]'.format(mesh, i)
|
attr = '{0}.uvSet[{1}]'.format(mesh, i)
|
||||||
cmds.removeMultiInstance(attr, b=True)
|
cmds.removeMultiInstance(attr, b=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_id_from_history(node):
|
||||||
|
"""Return first node id in the history chain that matches this node.
|
||||||
|
|
||||||
|
The nodes in history must be of the exact same node type and must be
|
||||||
|
parented under the same parent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node (str): node to retrieve the
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str or None: The id from the node in history or None when no id found
|
||||||
|
on any valid nodes in the history.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_parent(node):
|
||||||
|
"""Return full path name for parent of node"""
|
||||||
|
return cmds.listRelatives(node, parent=True, fullPath=True)
|
||||||
|
|
||||||
|
node = cmds.ls(node, long=True)[0]
|
||||||
|
|
||||||
|
# Find all similar nodes in history
|
||||||
|
history = cmds.listHistory(node)
|
||||||
|
node_type = cmds.nodeType(node)
|
||||||
|
similar_nodes = cmds.ls(history, exactType=node_type, long=True)
|
||||||
|
|
||||||
|
# Exclude itself
|
||||||
|
similar_nodes = [x for x in similar_nodes if x != node]
|
||||||
|
|
||||||
|
# The node *must be* under the same parent
|
||||||
|
parent = _get_parent(node)
|
||||||
|
similar_nodes = [i for i in similar_nodes if _get_parent(i) == parent]
|
||||||
|
|
||||||
|
# Check all of the remaining similar nodes and take the first one
|
||||||
|
# with an id and assume it's the original.
|
||||||
|
for similar_node in similar_nodes:
|
||||||
|
_id = get_id(similar_node)
|
||||||
|
if _id:
|
||||||
|
return _id
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
from maya import cmds
|
|
||||||
|
|
||||||
import pyblish.api
|
|
||||||
import colorbleed.api
|
|
||||||
import colorbleed.maya.lib as lib
|
|
||||||
|
|
||||||
# from cbra.utils.maya.node_uuid import add_ids
|
|
||||||
|
|
||||||
|
|
||||||
def get_deformed_history_id_mapping(shapes):
|
|
||||||
"""Return the id from history for nodes that are "Deformed".
|
|
||||||
|
|
||||||
When shapes are referenced and get deformed by a deformer
|
|
||||||
the shape is duplicated *without its attributes* as such
|
|
||||||
the new shape misses object ids. This method will try to
|
|
||||||
trace back in the history to find the first shape with
|
|
||||||
ids to identify the possible best match.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
shapes (list): The shapes that are deformed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Mapping of deformed shape to history shape.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
shapes = cmds.ls(shapes, shapes=True, long=True)
|
|
||||||
|
|
||||||
# Possibly deformed shapes
|
|
||||||
deformed_shapes = [x for x in shapes if "Deformed" in x.rsplit("|", 1)[-1]]
|
|
||||||
|
|
||||||
# The deformed shape should not be referenced
|
|
||||||
is_referenced = lambda n: cmds.referenceQuery(n, isNodeReferenced=True)
|
|
||||||
deformed_shapes = [x for x in deformed_shapes if not is_referenced(x)]
|
|
||||||
|
|
||||||
# Shapes without id
|
|
||||||
deformed_shapes_without_id = [x for x in deformed_shapes
|
|
||||||
if not lib.get_id(x)]
|
|
||||||
|
|
||||||
mapping = {}
|
|
||||||
for shape in deformed_shapes_without_id:
|
|
||||||
|
|
||||||
node_type = cmds.objectType(shape)
|
|
||||||
history = cmds.listHistory(shape)[1:] # history, skipping itself
|
|
||||||
history_shapes = cmds.ls(history, exactType=node_type, long=True)
|
|
||||||
if not history_shapes:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for history_shape in history_shapes:
|
|
||||||
id = lib.get_id(history_shape)
|
|
||||||
if not id:
|
|
||||||
continue
|
|
||||||
|
|
||||||
mapping[shape] = history_shape
|
|
||||||
break
|
|
||||||
|
|
||||||
return mapping
|
|
||||||
|
|
||||||
|
|
||||||
class CopyUUIDsFromHistory(pyblish.api.Action):
|
|
||||||
"""Repairs the action
|
|
||||||
|
|
||||||
To retrieve the invalid nodes this assumes a static `get_invalid(instance)`
|
|
||||||
method is available on the plugin.
|
|
||||||
|
|
||||||
"""
|
|
||||||
label = "Copy UUIDs from History"
|
|
||||||
on = "failed" # This action is only available on a failed plug-in
|
|
||||||
icon = "wrench" # Icon from Awesome Icon
|
|
||||||
|
|
||||||
def process(self, context, plugin):
|
|
||||||
|
|
||||||
# Get the errored instances
|
|
||||||
self.log.info("Finding failed instances..")
|
|
||||||
errored = colorbleed.api.get_errored_instances_from_context(context)
|
|
||||||
|
|
||||||
# Apply pyblish.logic to get the instances for the plug-in
|
|
||||||
instances = pyblish.api.instances_by_plugin(errored, plugin)
|
|
||||||
|
|
||||||
ids_map = dict()
|
|
||||||
for instance in instances:
|
|
||||||
invalid = plugin.get_invalid(instance)
|
|
||||||
mapping = get_deformed_history_id_mapping(invalid)
|
|
||||||
|
|
||||||
for destination, source in mapping.items():
|
|
||||||
ids_map[destination] = lib.get_id(source)
|
|
||||||
|
|
||||||
if not ids_map:
|
|
||||||
return
|
|
||||||
self.log.info(ids_map)
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateLookDeformedShapes(pyblish.api.InstancePlugin):
|
|
||||||
"""Validate look textures are set to ignore color space when set to RAW
|
|
||||||
|
|
||||||
Whenever the format is NOT set to sRGB for a file texture it must have
|
|
||||||
its ignore color space file rules checkbox enabled to avoid unwanted
|
|
||||||
reverting to sRGB settings upon file relinking.
|
|
||||||
|
|
||||||
To fix this use the select invalid action to find the invalid file nodes
|
|
||||||
and then check the "Ignore Color Space File Rules" checkbox under the
|
|
||||||
Color Space settings.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
order = colorbleed.api.ValidateContentsOrder
|
|
||||||
families = ['colorbleed.look']
|
|
||||||
hosts = ['maya']
|
|
||||||
label = 'Look deformed shapes'
|
|
||||||
actions = [colorbleed.api.SelectInvalidAction, CopyUUIDsFromHistory]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_invalid(cls, instance):
|
|
||||||
|
|
||||||
context = instance.context
|
|
||||||
nodes = context.data.get("instancePerItemNodesWithoutId", None)
|
|
||||||
if not nodes:
|
|
||||||
return list()
|
|
||||||
|
|
||||||
mapping = get_deformed_history_id_mapping(nodes)
|
|
||||||
return mapping.keys()
|
|
||||||
|
|
||||||
def process(self, instance):
|
|
||||||
"""Process all the nodes in the instance"""
|
|
||||||
|
|
||||||
invalid = self.get_invalid(instance)
|
|
||||||
|
|
||||||
if invalid:
|
|
||||||
raise RuntimeError("Shapes found that are considered 'Deformed'"
|
|
||||||
"without object ids: {0}".format(invalid))
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
from maya import cmds
|
||||||
|
|
||||||
|
import pyblish.api
|
||||||
|
import colorbleed.api
|
||||||
|
import colorbleed.maya.lib as lib
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin):
|
||||||
|
"""Validate if deformed shapes have related IDs to the original shapes.
|
||||||
|
|
||||||
|
When a deformer is applied in the scene on a referenced mesh that already
|
||||||
|
had deformers then Maya will create a new shape node for the mesh that
|
||||||
|
does not have the original id. This validator checks whether the ids are
|
||||||
|
valid on all the shape nodes in the instance.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
order = colorbleed.api.ValidateContentsOrder
|
||||||
|
families = ['colorbleed.look']
|
||||||
|
hosts = ['maya']
|
||||||
|
label = 'Deformed shape ids'
|
||||||
|
actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction]
|
||||||
|
|
||||||
|
def process(self, instance):
|
||||||
|
"""Process all the nodes in the instance"""
|
||||||
|
|
||||||
|
# Ensure all nodes have a cbId and a related ID to the original shapes
|
||||||
|
# if a deformer has been created on the shape
|
||||||
|
invalid = self.get_invalid(instance)
|
||||||
|
if invalid:
|
||||||
|
raise RuntimeError("Shapes found that are considered 'Deformed'"
|
||||||
|
"without object ids: {0}".format(invalid))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_invalid(cls, instance):
|
||||||
|
"""Get all nodes which do not match the criteria"""
|
||||||
|
|
||||||
|
shapes = cmds.ls(instance[:],
|
||||||
|
dag=True,
|
||||||
|
leaf=True,
|
||||||
|
shapes=True,
|
||||||
|
long=True,
|
||||||
|
noIntermediate=True)
|
||||||
|
|
||||||
|
invalid = []
|
||||||
|
for shape in shapes:
|
||||||
|
history_id = lib.get_id_from_history(shape)
|
||||||
|
if history_id:
|
||||||
|
current_id = lib.get_id(shape)
|
||||||
|
if current_id != history_id:
|
||||||
|
invalid.append(shape)
|
||||||
|
|
||||||
|
return invalid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def repair(cls, instance):
|
||||||
|
|
||||||
|
for node in cls.get_invalid(instance):
|
||||||
|
# Get the original id from history
|
||||||
|
history_id = lib.get_id_from_history(node)
|
||||||
|
if not history_id:
|
||||||
|
cls.log.error("Could not find ID in history for '%s'", node)
|
||||||
|
continue
|
||||||
|
|
||||||
|
lib.set_id(node, history_id, overwrite=True)
|
||||||
|
|
||||||
|
|
@ -5,55 +5,6 @@ import colorbleed.api
|
||||||
import colorbleed.maya.lib as lib
|
import colorbleed.maya.lib as lib
|
||||||
|
|
||||||
|
|
||||||
def get_id_from_history(node):
|
|
||||||
"""Return first node id in the history chain that matches this node.
|
|
||||||
|
|
||||||
The nodes in history must be of the exact same node type and must be
|
|
||||||
parented under the same parent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
node (str): node to retrieve the
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str or None: The id from the node in history or None when no id found
|
|
||||||
on any valid nodes in the history.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
node = cmds.ls(node, long=True)[0]
|
|
||||||
|
|
||||||
# Find all similar nodes in history
|
|
||||||
history = cmds.listHistory(node)
|
|
||||||
node_type = cmds.nodeType(node)
|
|
||||||
similar_nodes = cmds.ls(history, exactType=node_type, long=True)
|
|
||||||
|
|
||||||
# Exclude itself
|
|
||||||
similar_nodes = [x for x in similar_nodes if x != node]
|
|
||||||
|
|
||||||
# The node *must be* under the same parent
|
|
||||||
parent = get_parent(node)
|
|
||||||
similar_nodes = [i for i in similar_nodes if
|
|
||||||
get_parent(i) == parent]
|
|
||||||
|
|
||||||
# Check all of the remaining similar nodes and take the first one
|
|
||||||
# with an id and assume it's the original.
|
|
||||||
for similar_node in similar_nodes:
|
|
||||||
_id = lib.get_id(similar_node)
|
|
||||||
if _id:
|
|
||||||
return _id
|
|
||||||
|
|
||||||
|
|
||||||
def get_parent(node):
|
|
||||||
"""Get the parent node of the given node
|
|
||||||
Args:
|
|
||||||
node (str): full path of the node
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str, full path if parent node
|
|
||||||
"""
|
|
||||||
return cmds.listRelatives(node, parent=True, fullPath=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
||||||
"""Validate if deformed shapes have related IDs to the original shapes.
|
"""Validate if deformed shapes have related IDs to the original shapes.
|
||||||
|
|
||||||
|
|
@ -96,7 +47,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
||||||
noIntermediate=True)
|
noIntermediate=True)
|
||||||
|
|
||||||
for shape in shapes:
|
for shape in shapes:
|
||||||
history_id = get_id_from_history(shape)
|
history_id = lib.get_id_from_history(shape)
|
||||||
if history_id:
|
if history_id:
|
||||||
current_id = lib.get_id(shape)
|
current_id = lib.get_id(shape)
|
||||||
if current_id != history_id:
|
if current_id != history_id:
|
||||||
|
|
@ -109,7 +60,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
for node in cls.get_invalid(instance):
|
for node in cls.get_invalid(instance):
|
||||||
# Get the original id from history
|
# Get the original id from history
|
||||||
history_id = get_id_from_history(node)
|
history_id = lib.get_id_from_history(node)
|
||||||
if not history_id:
|
if not history_id:
|
||||||
cls.log.error("Could not find ID in history for '%s'", node)
|
cls.log.error("Could not find ID in history for '%s'", node)
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue