From c4daf66e55596c1cc7963673e9620665aca0c5ce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Feb 2018 14:58:05 +0100 Subject: [PATCH] Fix LKD-15: Ensure deformed shapes in look have ids from history (reuse logic from validate_rig_out_set_node_ids.py) --- colorbleed/maya/lib.py | 41 ++++++ .../publish/validate_look_deformed_shapes.py | 130 ------------------ .../validate_node_ids_deformed_shapes.py | 66 +++++++++ .../publish/validate_rig_out_set_node_ids.py | 53 +------ 4 files changed, 109 insertions(+), 181 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py create mode 100644 colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 729cc1f60c..d6df4fae52 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1247,3 +1247,44 @@ def remove_other_uv_sets(mesh): for i in indices: attr = '{0}.uvSet[{1}]'.format(mesh, i) 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 diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py deleted file mode 100644 index 1e458b557e..0000000000 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ /dev/null @@ -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)) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py new file mode 100644 index 0000000000..4513acb6e1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py @@ -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) + diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py index a109c39171..6b8fc9d28c 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -5,55 +5,6 @@ import colorbleed.api 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): """Validate if deformed shapes have related IDs to the original shapes. @@ -96,7 +47,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): noIntermediate=True) for shape in shapes: - history_id = get_id_from_history(shape) + history_id = lib.get_id_from_history(shape) if history_id: current_id = lib.get_id(shape) if current_id != history_id: @@ -109,7 +60,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): for node in cls.get_invalid(instance): # 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: cls.log.error("Could not find ID in history for '%s'", node) continue