From dee1d504bf77bca344234faccc94a2476ab645c5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 31 Oct 2017 16:42:57 +0100 Subject: [PATCH] improved check to validate if IDs are related to original use shape --- ...date_animation_out_set_related_node_ids.py | 129 ++++++++++++------ 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 93cde23062..b588d5b375 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -3,11 +3,10 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api import colorbleed.maya.lib as lib -import avalon.io as io class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): - """Validate rig out_SET nodes have related ids to current context + """Validate if nodes have related IDs to the source (original shapes) An ID is 'related' if its built in the current Item. @@ -18,10 +17,10 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.animation'] + families = ['colorbleed.animation', "colorbleed.pointcache"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] optional = True ignore_types = ("constraint",) @@ -29,61 +28,105 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): def process(self, instance): """Process all meshes""" - # Ensure all nodes have a cbId + # 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("Nodes found with non-related " "asset IDs: {0}".format(invalid)) - @classmethod - def get_pointcache_nodes(cls, instance): - - # Get out_SET - sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'out_SET'] - - nodes = list() - for s in pointcache_sets: - # Ensure long names - members = cmds.ls(cmds.sets(s, query=True), long=True) - descendants = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] - descendants = cmds.ls(descendants, - noIntermediate=True, - long=True) - hierarchy = members + descendants - nodes.extend(hierarchy) - - # ignore certain node types (e.g. constraints) - ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) - if ignore: - ignore = set(ignore) - nodes = [node for node in nodes if node not in ignore] - - return nodes - @classmethod def get_invalid(cls, instance): - invalid_items = [] + """Get all nodes which do not match the criteria""" + invalid = [] # get asset id - nodes = cls.get_pointcache_nodes(instance) + nodes = instance.data["pointcache_data"] for node in nodes: + node_type = cmds.nodeType(node) node_id = lib.get_id(node) + + if node_type == "mesh" and not node_id: + invalid.append(node) + continue + if not node_id: - invalid_items.append(node) + continue - # TODO: Should we check whether the ids are related to the rig's asset? + root_id = cls.get_history_root_id(node=node) + if root_id is not None: + asset_id = cls.to_item(node_id) + if root_id != asset_id: + invalid.append(node) - # Log invalid item ids - if invalid_items: - for item_id in sorted(invalid_items): - cls.log.warning("Found invalid item id: {0}".format(item_id)) + return invalid - return invalid_items + @classmethod + def get_history_root_id(cls, node): + """ + + Get the original node ID when a node has been deformed + Args: + node (str): node to retrieve the + + Returns: + str: the asset ID as found in the database + """ + + asset_id = None + node = cmds.ls(node, long=True)[0] + + # We only check when the node is *not* referenced + if cmds.referenceQuery(node, isNodeReferenced=True): + return + + # 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 = cls.get_parent(node) + similar_nodes = [i for i in similar_nodes if + cls.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 not _id: + continue + + asset_id = cls.to_item(_id) + break + + return asset_id + + @classmethod + def repair(cls, instance): + + for node in cls.get_invalid(instance): + # Get node ID and the asset ID part + node_id = lib.get_id(node) + asset_id = cls.to_item(node_id) + + # Get root asset ID + root_id = cls.get_history_root_id(node=node) + + # Replace errored ID with good ID + new_id = node_id.replace(asset_id, root_id) + + cmds.setAttr("%s.cbId" % node, new_id, type="string") @staticmethod def to_item(_id): """Split the item id part from a node id""" - return _id.rsplit(":", 1)[0] + return _id.split(":", 1)[0] + + @staticmethod + def get_parent(node): + return cmds.listRelatives(node, parent=True, fullPath=True) \ No newline at end of file