From e0ecc72e49c0f3ed8860b05f30eeb3055de4c6ad Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 4 Jul 2017 15:22:56 +0200 Subject: [PATCH] refactored publish plugins --- colorbleed/action.py | 87 +++++++++++--- colorbleed/maya/lib.py | 2 +- .../plugins/maya/publish/collect_look.py | 106 +++++++++++++----- .../plugins/maya/publish/extract_look.py | 7 +- .../publish/validate_look_members_node_ids.py | 9 +- .../publish/validate_look_members_unique.py | 22 ++-- .../publish/validate_look_node_unique_ids.py | 18 +-- .../publish/validate_naming_convention.py | 33 ++++++ .../maya/publish/validate_unique_node_ids.py | 8 +- 9 files changed, 225 insertions(+), 67 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index fef737195d..382699035e 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -1,10 +1,13 @@ # absolute_import is needed to counter the `module has no cmds error` in Maya from __future__ import absolute_import -import pyblish.api +import os +import uuid from maya import cmds +import pyblish.api + def get_errored_instances_from_context(context): @@ -34,7 +37,7 @@ def get_errored_plugins_from_data(context): plugins = list() results = context.data.get("results", []) for result in results: - if result["success"] == True: + if result["success"] is True: continue plugins.append(result["plugin"]) @@ -150,8 +153,6 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): icon = "wrench" # Icon from Awesome Icon def process(self, context, plugin): - import cbra.lib - import cbra.utils.maya.node_uuid as id_utils self.log.info("Finding bad nodes..") @@ -182,15 +183,73 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): # Parse context from current file self.log.info("Parsing current context..") - try: - current_file = context.data['currentFile'] - context = cbra.lib.parse_context(current_file) - except RuntimeError, e: - self.log.error("Can't generate UUIDs because scene isn't " - "in new-style pipeline: ".format(current_file)) - raise e + print(">>> DEBUG CONTEXT :", context) + print(">>> DEBUG CONTEXT DATA:", context.data) - # Generate and add the ids to the nodes - ids = id_utils.generate_ids(context, invalid) - id_utils.add_ids(ids) + # # Generate and add the ids to the nodes + node_ids = self.generate_ids(context, invalid) + self.apply_ids(node_ids) self.log.info("Generated ids on nodes: {0}".format(invalid)) + + def get_context(self, instance=None): + + PROJECT = os.environ["AVALON_PROJECT"] + ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] + SILO = os.environ["AVALON_SILO"] + LOCATION = os.getenv("AVALON_LOCATION") + + return {"project": PROJECT, + "asset": ASSET, + "silo": SILO, + "location": LOCATION} + + def generate_ids(self, context, nodes): + """Generate cb UUIDs for nodes. + + The identifiers are formatted like: + assets:character/test:bluey:46D221D9-4150-8E49-6B17-43B04BFC26B6 + + This is a concatenation of: + - entity (shots or assets) + - folders (parent hierarchy) + - asset (the name of the asset) + - uuid (unique id for node in the scene) + + Raises: + RuntimeError: When context can't be parsed of the current asset + + Returns: + dict: node, uuid dictionary + + """ + + # Make a copy of the context + data = context.copy() + + # Define folders + + node_ids = dict() + for node in nodes: + # Generate a unique ID per node + data['uuid'] = uuid.uuid4() + unique_id = "{asset}:{item}:{uuid}".format(**data) + node_ids[node] = unique_id + + return node_ids + + def apply_ids(self, node_ids): + """Apply the created unique IDs to the node + Args: + node_ids (dict): each node with a unique id + + Returns: + None + """ + + attribute = "mbId" + for node, id in node_ids.items(): + # check if node has attribute + if not cmds.attributeQuery(attribute, node=node, exists=True): + cmds.addAttr(node, longName=attribute, dataType="string") + + cmds.setAttr("{}.{}".format(node, attribute), id) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c6314049ae..175d664f7c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -456,7 +456,7 @@ def extract_alembic(file, for key, value in options.items(): if isinstance(value, (list, tuple)): for entry in value: - job_args.append("-{0} {1}".format(key=key, value=entry)) + job_args.append("-{} {}".format(key, entry)) elif isinstance(value, bool): job_args.append("-{0}".format(key)) else: diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 7ee62af5fe..72946d8588 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -37,8 +37,8 @@ def get_look_attrs(node): valid = [attr for attr in attrs if attr in SHAPE_ATTRS] result.extend(valid) - if "cbId" in result: - result.remove("cbId") + if "mbID" in result: + result.remove("mbID") return result @@ -87,7 +87,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Discover related object sets self.log.info("Gathering sets..") - self.gather_sets(instance) + sets = self.gather_sets(instance) # Lookup with absolute names (from root namespace) instance_lookup = set([str(x) for x in cmds.ls(instance, @@ -95,9 +95,7 @@ class CollectLook(pyblish.api.InstancePlugin): absoluteName=True)]) self.log.info("Gathering set relations..") - sets = self.gather_sets(instance) for objset in sets: - self.log.debug("From %s.." % objset) content = cmds.sets(objset, query=True) objset_members = sets[objset]["members"] @@ -108,16 +106,14 @@ class CollectLook(pyblish.api.InstancePlugin): verbose) if not member_data: continue - sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end - sets = self.clean_sets(sets) - # Member attributes (shapes + transforms) + sets = self.remove_sets_without_members(sets) self.log.info("Gathering attribute changes to instance members..") - attributes = self.collect_attributes_changes(instance) + attributes = self.collect_attributes_changed(instance) looksets = cmds.ls(sets.keys(), absoluteName=True, long=True) # Store data on the instance @@ -133,41 +129,75 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Collected look for %s" % instance) def gather_sets(self, instance): + """Gather all objectSets which are of importance for publishing + + It checks if all nodes in the instance are related to any objectSet + which need to be + + Args: + instance (list): all nodes to be published + + Returns: + dict + """ # Get view sets (so we can ignore those sets later) sets = dict() view_sets = set() - model_panels = cmds.getPanel(type="modelPanel") - for panel in model_panels: + for panel in cmds.getPanel(type="modelPanel"): view_set = cmds.modelEditor(panel, query=True, viewObjects=True) if view_set: view_sets.add(view_set) for node in instance: - node_sets = self.filter_sets(node, view_sets) - if not node_sets: + related_sets = self.get_related_sets(node, view_sets) + if not related_sets: continue - for objset in node_sets: + for objset in related_sets: if objset in sets: continue + unique_id = cmds.getAttr("%s.mbID" % objset) sets[objset] = {"name": objset, - "uuid": id_utils.get_id(objset), + "uuid": unique_id, "members": list()} return sets - def filter_sets(self, node, view_sets): + def get_related_sets(self, node, view_sets): + """Get the sets which do not belong to any specific group - node_sets = cmds.listSets(object=node, extendToShape=False) or [] - if not node_sets: - return + Filters out based on: + - id attribute is NOT `pyblish.avalon.container` + - shapes and deformer shapes (alembic creates meshShapeDeformed) + - set name ends with any from a predefined list + - set in not in viewport set (isolate selected for example) + + Args: + node (str): name of the current not to check + """ + + ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] + + related_sets = cmds.listSets(object=node, extendToShape=False) + if not related_sets: + return [] + + # Ignore `avalon.container` + sets = [s for s in related_sets if + not cmds.attributeQuery("id", node=s, exists=True) or + not cmds.getAttr("%s.id" % s) in ignored] # Exclude deformer sets + # Autodesk documentation on listSets command: + # type(uint) : Returns all sets in the scene of the given + # >>> type: + # >>> 1 - all rendering sets + # >>> 2 - all deformer sets deformer_sets = cmds.listSets(object=node, extendToShape=False, type=2) or [] deformer_sets = set(deformer_sets) # optimize lookup - sets = [s for s in node_sets if s not in deformer_sets] + sets = [s for s in sets if s not in deformer_sets] # Ignore specifically named sets sets = [s for s in sets if not any(s.endswith(x) for x in self.IGNORE)] @@ -176,16 +206,24 @@ class CollectLook(pyblish.api.InstancePlugin): # viewports) sets = [s for s in sets if s not in view_sets] - self.log.info("Found sets {0} for {1}".format(node_sets, node)) + self.log.info("Found sets %s for %s" % (related_sets, node)) return sets - def clean_sets(self, sets): + def remove_sets_without_members(self, sets): + """Remove any set which does not have any members + + Args: + sets (dict): collection if sets with data as value + + Returns: + dict + """ for objset, data in sets.items(): if not data['members']: - self.log.debug("Removing redundant set " - "information: %s" % objset) + self.log.debug("Removing redundant set information: " + "%s" % objset) sets.pop(objset) return sets @@ -218,7 +256,8 @@ class CollectLook(pyblish.api.InstancePlugin): if verbose: self.log.debug("Such as %s.." % member) - member_data = {"name": node, "uuid": id_utils.get_id(node)} + member_data = {"name": node, + "uuid": cmds.getAttr("{}.mbID".format(node, ))} # Include components information when components are assigned if components: @@ -226,7 +265,22 @@ class CollectLook(pyblish.api.InstancePlugin): return member_data - def collect_attributes_changes(self, instance): + def collect_attributes_changed(self, instance): + """Collect all userDefined attributes which have changed + + Each node gets checked for user defined attributes which have been + altered during development. Each changes gets logged in a dictionary + + [{name: node, + uuid: uuid, + attributes: {attribute: value}}] + + Args: + instance (list): all nodes which will be published + + Returns: + list + """ attributes = [] for node in instance: diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index faf1e85876..97c572565a 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -37,7 +37,8 @@ class ExtractLook(colorbleed.api.Extractor): # Remove all members of the sets so they are not included in the # exported file by accident self.log.info("Extract sets (Maya ASCII)..") - sets = instance.data["lookSets"] + lookdata = instance.data["lookData"] + sets = lookdata["sets"] # Define the texture file node remapping resource_remap = dict() @@ -71,8 +72,8 @@ class ExtractLook(colorbleed.api.Extractor): # Write the JSON data self.log.info("Extract json..") - data = {"attributes": instance.data["lookAttributes"], - "sets": instance.data["lookSetRelations"]} + data = {"attributes": lookdata["attributes"], + "sets": lookdata["relationships"]} with open(json_path, "w") as f: json.dump(data, f) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index d601b22601..819f287bf6 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -1,8 +1,8 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api -import cbra.utils.maya.node_uuid as id_utils - class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): """Validate look members have colorbleed id attributes @@ -20,7 +20,8 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Members Id Attributes' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] @staticmethod def get_invalid(instance): @@ -40,7 +41,7 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): # Ensure all nodes have a cbId invalid = list() for node in members: - if not id_utils.has_id(node): + if not cmds.getAttr("{}.mbID".format(node)): invalid.append(node) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 18de8e73c4..48dce8b8d7 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -5,7 +5,14 @@ from maya import cmds import pyblish.api import colorbleed.api -import cbra.utils.maya.node_uuid as id_utils + +def get_unique_id(node): + attr = 'cbId' + unique_id = None + has_attribute = cmds.attributeQuery(attr, node=node, exists=True) + if has_attribute: + unique_id = cmds.getAttr("{}.{}".format(node, attr)) + return unique_id class ValidateLookMembersUnique(pyblish.api.InstancePlugin): @@ -25,15 +32,16 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Members Unique' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] @staticmethod def get_invalid(instance): # Get all members from the sets members = [] - relations = instance.data["lookData"]["sets"] - for sg in relations: + relationships = instance.data["lookData"]["relationships"] + for sg in relationships: sg_members = sg['members'] sg_members = [member['name'] for member in sg_members] members.extend(sg_members) @@ -45,10 +53,9 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): # Group members per id id_nodes = defaultdict(set) for node in members: - node_id = id_utils.get_id(node) + node_id = get_unique_id(node) if not node_id: continue - id_nodes[node_id].add(node) invalid = list() @@ -61,8 +68,9 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): def process(self, instance): """Process all meshes""" - invalid = self.get_invalid(instance) + print self.actions + invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Members found without " "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py index 738075fec5..653e8a347c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -1,9 +1,10 @@ from collections import defaultdict + +import maya.cmds as cmds + import pyblish.api import colorbleed.api -import cbra.utils.maya.node_uuid as id_utils - class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): """Validate look sets have unique colorbleed id attributes @@ -15,7 +16,7 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Look Id Unique Attributes' actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] + colorbleed.api.RepairAction] @staticmethod def get_invalid(instance): @@ -26,13 +27,15 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): id_sets = defaultdict(list) invalid = list() for node in nodes: - id = id_utils.get_id(node) - if not id: + unique_id = None + if cmds.attributeQuery("mbId", node=node, exists=True): + unique_id = cmds.getAttr("{}.mbId".format(node)) + if not unique_id: continue - id_sets[id].append(node) + id_sets[unique_id].append(node) - for id, nodes in id_sets.iteritems(): + for unique_id, nodes in id_sets.iteritems(): if len(nodes) > 1: invalid.extend(nodes) @@ -42,7 +45,6 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): """Process all meshes""" invalid = self.get_invalid(instance) - if invalid: raise RuntimeError("Nodes found without " "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_naming_convention.py b/colorbleed/plugins/maya/publish/validate_naming_convention.py index e69de29bb2..db4846b2bb 100644 --- a/colorbleed/plugins/maya/publish/validate_naming_convention.py +++ b/colorbleed/plugins/maya/publish/validate_naming_convention.py @@ -0,0 +1,33 @@ +import re + +import pyblish.api +import colorbleed.api + + +class ValidateNamingConvention(pyblish.api.InstancePlugin): + + label = "" + host = ["maya"] + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + invalid = [] + # todo: change pattern to company standard + pattern = re.compile("[a-zA-Z]+_[A-Z]{3}") + + nodes = list(instance) + for node in nodes: + match = pattern.match(node) + if not match: + invalid.append(node) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + self.log.error("Found invalid naming convention. Failed noted :\n" + "%s" % invalid) diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index 616cabfdc4..8e3ccb8a43 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -1,3 +1,7 @@ +from collections import defaultdict + +import maya.cmds as cmds + import pyblish.api import colorbleed.api @@ -16,12 +20,9 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): def get_invalid_dict(instance): """Return a dictionary mapping of id key to list of member nodes""" - import maya.cmds as cmds - uuid_attr = "cbId" # Collect each id with their members - from collections import defaultdict ids = defaultdict(list) for member in instance: has_attr = cmds.attributeQuery(uuid_attr, node=member, exists=True) @@ -60,4 +61,3 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Nodes found with non-unique " "asset IDs: {0}".format(invalid)) -