From ed730c02e64a7e5189b09598b89dec545e245270 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 4 Jul 2017 15:20:06 +0200 Subject: [PATCH 01/31] implemented scripts menu to launcher --- colorbleed/__init__.py | 2 +- colorbleed/maya/menu.json | 61 ++++++++++++ colorbleed/maya/menu.py | 98 +++++++++++-------- ...ode_ids.py => validate_unique_node_ids.py} | 2 +- 4 files changed, 121 insertions(+), 42 deletions(-) create mode 100644 colorbleed/maya/menu.json rename colorbleed/plugins/maya/publish/{_validate_unique_node_ids.py => validate_unique_node_ids.py} (98%) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index a6d0890f25..a158fcbee9 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -14,4 +14,4 @@ def install(): def uninstall(): - pyblish.deregister_plugin_path(PUBLISH_PATH) \ No newline at end of file + pyblish.deregister_plugin_path(PUBLISH_PATH) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json new file mode 100644 index 0000000000..1918163f97 --- /dev/null +++ b/colorbleed/maya/menu.json @@ -0,0 +1,61 @@ +{"Animation": [{"title" : "Script A", + "tooltip": "Script A", + "command": "$SCRIPTMENU/script_a.py", + "sourcetype": "file", + "tags": ["test", "script", "cluster"], + "icon": "$SCRIPTMENU/resources/script_a.png", + "label": "SCR A"}, + {"title" : "Script B", + "tooltip": "Run script B", + "command": "$SCRIPTMENU/script_b.py", + "sourcetype": "file", + "tags": ["test", "script", "curves"]}, + {"title" : "Script C", + "tooltip": "Run script C", + "command": "$SCRIPTMENU/script_c.py", + "sourcetype": "file", + "tags": ["test", "script", "joints"], + "icon": ""} + ], + +"Modeling": [{"title" : "Script A", + "tooltip": "Run script A", + "command": "$SCRIPTMENU/script_a.py", + "sourcetype": "file", + "tags": ["test", "script", "model", "blendshapes"]}, + {"title" : "Script B", + "tooltip": "Run script B", + "command": "$SCRIPTMENU/script_b.py", + "sourcetype": "file", + "tags": ["test", "script", "normals", "model"]}, + {"title" : "Script C", + "tooltip": "Run script C", + "command": "$SCRIPTMENU/script_c.py", + "sourcetype": "file", + "tags": ["math", "power", "sum"]} + ], + +"Rigging": [{"title" : "Script A", + "tooltip": "Run script A", + "command": "$SCRIPTMENU/script_a.py", + "sourcetype": "file", + "tags": ["test", "tool", "rig", "skeleton"]}, + {"title" : "Script B", + "tooltip": "Run script A", + "command": "$SCRIPTMENU/script_b.py", + "sourcetype": "file", + "tags": ["test", "script", "cloth", "rig", "setup"]}, + {"title" : "Script C", + "tooltip": "Run script C", + "command": "$SCRIPTMENU/script_c.py", + "sourcetype": "file", + "tags": ["test", "script", "approval"]} + ], + +"MEL": [{"title" : "Create cube", + "tooltip": "Launch character rigging tool", + "command": "polyCube -w 1 -h 1 -d 1;", + "sourcetype": "mel", + "tags": ["test", "script", "mel"]} + ] +} \ No newline at end of file diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 07a9ba81ce..f72dc07ed9 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -1,62 +1,80 @@ import sys -from maya import cmds + from avalon.vendor.Qt import QtWidgets, QtCore self = sys.modules[__name__] self._menu = "colorbleed" -self._parent = { - widget.objectName(): widget - for widget in QtWidgets.QApplication.topLevelWidgets() -}.get("MayaWindow") +self._parent = {widget.objectName(): widget for widget in + QtWidgets.QApplication.topLevelWidgets()}.get("MayaWindow") def install(): - from . import interactive + # from . import interactive uninstall() def deferred(): - cmds.menu(self._menu, - label="Colorbleed", - tearOff=True, - parent="MayaWindow") - # Modeling sub-menu - cmds.menuItem("Modeling", - label="Modeling", - tearOff=True, - subMenu=True, - parent=self._menu) + import site + import os - cmds.menuItem("Combine", command=interactive.combine) + # todo: replace path with server / library path + site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") - # Rigging sub-menu - cmds.menuItem("Rigging", - label="Rigging", - tearOff=True, - subMenu=True, - parent=self._menu) + from scriptsmenu import launchformaya + import scriptsmenu.scriptsmenu as menu - cmds.menuItem("Auto Connect", command=interactive.auto_connect) - cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) - cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) - cmds.menuItem("Clone (Special)", command=interactive.clone_special) - cmds.menuItem("Create Follicle", command=interactive.follicle) + # load configuration of custon menu + config_path = os.path.join(os.path.dirname(__file__), "menu.json") + config = menu.load_configuration(config_path) - # Animation sub-menu - cmds.menuItem("Animation", - label="Animation", - tearOff=True, - subMenu=True, - parent=self._menu) + # create menu in appliction + cb_menu = launchformaya.main(title=self._menu, parent=self._parent) - cmds.menuItem("Set Defaults", command=interactive.set_defaults) + # apply configuration + menu.load_from_configuration(cb_menu, config) - cmds.setParent("..", menu=True) - - cmds.menuItem(divider=True) - - cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) + # cmds.menu(self._menu, + # label=self._menu.capitalize(), + # tearOff=True, + # parent="MayaWindow") + # + # # Modeling sub-menu + # cmds.menuItem("Modeling", + # label="Modeling", + # tearOff=True, + # subMenu=True, + # parent=self._menu) + # + # cmds.menuItem("Combine", command=interactive.combine) + # + # # Rigging sub-menu + # cmds.menuItem("Rigging", + # label="Rigging", + # tearOff=True, + # subMenu=True, + # parent=self._menu) + # + # cmds.menuItem("Auto Connect", command=interactive.auto_connect) + # cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) + # cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) + # cmds.menuItem("Clone (Special)", command=interactive.clone_special) + # cmds.menuItem("Create Follicle", command=interactive.follicle) + # + # # Animation sub-menu + # cmds.menuItem("Animation", + # label="Animation", + # tearOff=True, + # subMenu=True, + # parent=self._menu) + # + # cmds.menuItem("Set Defaults", command=interactive.set_defaults) + # + # cmds.setParent("..", menu=True) + # + # cmds.menuItem(divider=True) + # + # cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) # Allow time for uninstallation to finish. QtCore.QTimer.singleShot(100, deferred) diff --git a/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py similarity index 98% rename from colorbleed/plugins/maya/publish/_validate_unique_node_ids.py rename to colorbleed/plugins/maya/publish/validate_unique_node_ids.py index d7453a3a4c..616cabfdc4 100644 --- a/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -18,7 +18,7 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): import maya.cmds as cmds - uuid_attr = "mbId" + uuid_attr = "cbId" # Collect each id with their members from collections import defaultdict From e0ecc72e49c0f3ed8860b05f30eeb3055de4c6ad Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 4 Jul 2017 15:22:56 +0200 Subject: [PATCH 02/31] 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)) - From 92be4387aeb5c5429f5dc78fcfc0602c6dd6d0cb Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 10 Jul 2017 11:56:10 +0200 Subject: [PATCH 03/31] incorpirated the dynamic menu, wip --- colorbleed/maya/menu.json | 1751 ++++++++++++++++- colorbleed/maya/menu.py | 108 +- .../publish/__collect_instance_per_item.py | 177 -- .../maya/publish/__collect_yeti_caches.py | 156 -- .../plugins/maya/publish/__extract_layout.py | 81 - .../maya/publish/__validate_layout_nodes.py | 91 - .../publish/__validate_related_node_ids.py | 83 - .../publish/__validate_unique_ids_in_item.py | 167 -- .../maya/publish/_integrate_cb_asset.py | 85 - 9 files changed, 1734 insertions(+), 965 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/__collect_instance_per_item.py delete mode 100644 colorbleed/plugins/maya/publish/__collect_yeti_caches.py delete mode 100644 colorbleed/plugins/maya/publish/__extract_layout.py delete mode 100644 colorbleed/plugins/maya/publish/__validate_layout_nodes.py delete mode 100644 colorbleed/plugins/maya/publish/__validate_related_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py delete mode 100644 colorbleed/plugins/maya/publish/_integrate_cb_asset.py diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 1918163f97..9c3cc40bba 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,61 +1,1696 @@ -{"Animation": [{"title" : "Script A", - "tooltip": "Script A", - "command": "$SCRIPTMENU/script_a.py", - "sourcetype": "file", - "tags": ["test", "script", "cluster"], - "icon": "$SCRIPTMENU/resources/script_a.png", - "label": "SCR A"}, - {"title" : "Script B", - "tooltip": "Run script B", - "command": "$SCRIPTMENU/script_b.py", - "sourcetype": "file", - "tags": ["test", "script", "curves"]}, - {"title" : "Script C", - "tooltip": "Run script C", - "command": "$SCRIPTMENU/script_c.py", - "sourcetype": "file", - "tags": ["test", "script", "joints"], - "icon": ""} - ], +{ + "order": [ + "Modeling", + "Rigging", + "Shading", + "Animation", + "Layout", + "Particles", + "Cleanup", + "Projects", + "Pyblish", + "Others" + ], + "Modeling": [ + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", + "sourcetype": "file", + "tags": ["modeling", "duplicate", "normalized"], + "title": "Duplicate Normalized", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", + "sourcetype": "file", + "tags": ["modeling", "transfer", "uv"], + "title": "Transfer UVs", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", + "sourcetype": "file", + "tags": ["modeling", "mirror", "symmetry"], + "title": "Mirror Symmetry", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI", + "sourcetype": "file", + "tags": ["modeling", "select", "outline", "ui"], + "title": "Select Outline UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", + "sourcetype": "file", + "tags": ["modeling", "polygon", "uvset", "delete"], + "title": "polyDeleteOtherUVSets", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", + "sourcetype": "file", + "tags": ["modeling", "combine", "polygon", "quick"], + "title": "Polygon Combine Quick", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "modeling", + "separateMeshPerShader" + ], + "title": "Separate Mesh Per Shader", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", + "sourcetype": "file", + "tags": [ + "modeling", + "polyDetachSeparate" + ], + "title": "Polygon Detach and Separate", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", + "sourcetype": "file", + "tags": ["modeling", "relax", "verts"], + "title": "Polygon Relax Vertices", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", + "sourcetype": "file", + "tags": [ + "modeling", + "polySelectEveryNthEdgeUI" + ], + "title": "Select Every Nth Edge" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\djPFXUVs.py", + "sourcetype": "file", + "tags": [ + "modeling", + "djPFXUVs" + ], + "title": "dj PFX UVs", + "tooltip": "" + } + ], -"Modeling": [{"title" : "Script A", - "tooltip": "Run script A", - "command": "$SCRIPTMENU/script_a.py", - "sourcetype": "file", - "tags": ["test", "script", "model", "blendshapes"]}, - {"title" : "Script B", - "tooltip": "Run script B", - "command": "$SCRIPTMENU/script_b.py", - "sourcetype": "file", - "tags": ["test", "script", "normals", "model"]}, - {"title" : "Script C", - "tooltip": "Run script C", - "command": "$SCRIPTMENU/script_c.py", - "sourcetype": "file", - "tags": ["math", "power", "sum"]} - ], + "Animation": [ + { + "command": "", + "sourcetype": "", + "tags": ["animation", "attributes"], + "title": "Attributes", + "tooltip": "", + "items": [ + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes"], + "title": "Copy Values", + "tooltip": "Copy attribute values" + },{ + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "connections", "incoming"], + "title": "Copy In Connections", + "tooltip": "Copy incoming connections" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "connections", "out"], + "title": "Copy Out Connections", + "tooltip": "Copy outcoming connections" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "local"], + "title": "Copy Local Transfroms", + "tooltip": "Copy local transfroms" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "matrix"], + "title": "Copy Matrix Transfroms", + "tooltip": "Copy Matrix transfroms" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "UI"], + "title": "Copy Transforms UI", + "tooltip": "Open the Copy Transforms UI" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "UI", "simple"], + "title": "Simple Copy UI", + "tooltip": "Open the simple Copy Transforms UI" + } + ] + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "animation", + "optimize" + ], + "title": "Optimize", + "tooltip": "Optimization scripts", + "items":[ + { + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", + "sourcetype": "file", + "tags": ["animation", "hierarchy", "toggle", "freeze"], + "title": "Toggle Freeze Hierarchy", + "tooltip": "Freeze and unfreeze hierarchy" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", + "sourcetype": "file", + "tags": ["animation", "nucleus", "toggle", "parallel"], + "title": "Toggle Parallel Nucleus", + "tooltip": "Toggle parallel nucleus" + } + ] + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", + "sourcetype": "file", + "tags": [ + "animation", + "zvParentMaster" + ], + "title": "ZV Parent Master", + "tooltip": "Open ZV Parent Master UI" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", + "sourcetype": "file", + "tags": ["animation", "path"], + "title": "Path Animation", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", + "sourcetype": "file", + "tags": [ + "animation", + "timeStepper" + ], + "title": "TimeStepper", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", + "sourcetype": "file", + "tags": ["animation", "world space", "bake"], + "title": "Bake To World Space", + "tooltip": "Select the item which need to be baked to World Space" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", + "sourcetype": "file", + "tags": ["animation", "gui", "simple", "capture"], + "title": "Capture GUI - simple", + "tooltip": "Simplified version of the Capture GUI" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", + "sourcetype": "file", + "tags": ["animation", "optimizer", "scene"], + "title": "Animation Scene Optimizer", + "tooltip": "Optimize animation in a scene" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", + "sourcetype": "file", + "tags": [ + "animation", + "poseLibrary" + ], + "title": "Pose Library", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "sourcetype": "file", + "tags": ["animation", "capture", "screenshot", "movie"], + "title": "Capture GUI", + "tooltip": "Render current camera to an image or movie" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", + "sourcetype": "file", + "tags": [ + "animation", + "key_amplifier_ui" + ], + "title": "Key Amplifier UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "sourcetype": "file", + "tags": ["animation", "curves", "scene"], + "title": "Select All Animation Curves", + "tooltip": "Select all animation curves in the scene" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", + "sourcetype": "file", + "tags": [ + "animation", + "offsetSelectedObjectsUI" + ], + "title": "Offset Selected Object UI", + "tooltip": "Offset selected objects" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", + "sourcetype": "file", + "tags": [ + "animation", + "tweenMachineUI" + ], + "title": "Tween Machine UI", + "tooltip": "" + } + ], -"Rigging": [{"title" : "Script A", - "tooltip": "Run script A", - "command": "$SCRIPTMENU/script_a.py", - "sourcetype": "file", - "tags": ["test", "tool", "rig", "skeleton"]}, - {"title" : "Script B", - "tooltip": "Run script A", - "command": "$SCRIPTMENU/script_b.py", - "sourcetype": "file", - "tags": ["test", "script", "cloth", "rig", "setup"]}, - {"title" : "Script C", - "tooltip": "Run script C", - "command": "$SCRIPTMENU/script_c.py", - "sourcetype": "file", - "tags": ["test", "script", "approval"]} - ], + "Rigging": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "removeRotationAxis" + ], + "title": "removeRotationAxis", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "mirrorCurveShape" + ], + "title": "mirrorCurveShape", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedMesh" + ], + "title": "selectSkinclusterJointsFromSelectedMesh", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "channelBoxManagerUI" + ], + "title": "channelBoxManagerUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setJointOrientationFromCurrentRotation" + ], + "title": "setJointOrientationFromCurrentRotation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "regenerate_blendshape_targets" + ], + "title": "regenerate_blendshape_targets", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "freezeTransformToGroup" + ], + "title": "freezeTransformToGroup", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedComponents" + ], + "title": "selectSkinclusterJointsFromSelectedComponents", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "superRelativeCluster" + ], + "title": "superRelativeCluster", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "special" + ], + "title": "special", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "averageSkinWeights" + ], + "title": "averageSkinWeights", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "connectUI" + ], + "title": "connectUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "ikHandlePoleVectorLocator" + ], + "title": "ikHandlePoleVectorLocator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleSegmentScaleCompensate" + ], + "title": "toggleSegmentScaleCompensate", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "addCurveBetween" + ], + "title": "addCurveBetween", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "resetBindSelectedMeshes" + ], + "title": "resetBindSelectedMeshes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "rapidRig" + ], + "title": "rapidRig", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "paintItNowUI" + ], + "title": "paintItNowUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleSkinclusterDeformNormals" + ], + "title": "toggleSkinclusterDeformNormals", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setSelectedJointsOrientationZero" + ], + "title": "setSelectedJointsOrientationZero", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "groupSelected" + ], + "title": "groupSelected", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "simpleControllerOnSelection" + ], + "title": "simpleControllerOnSelection", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "resetBindSelectedSkinJoints" + ], + "title": "resetBindSelectedSkinJoints", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "quickSetWeightsUI" + ], + "title": "quickSetWeightsUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "simpleControllerOnSelectionHierarchy" + ], + "title": "simpleControllerOnSelectionHierarchy", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setRotationOrderUI" + ], + "title": "setRotationOrderUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "jointOrientUI" + ], + "title": "jointOrientUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "copySkinWeightsLocal" + ], + "title": "copySkinWeightsLocal", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "tfSmoothSkinWeight" + ], + "title": "tfSmoothSkinWeight", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "characterAutorigger" + ], + "title": "characterAutorigger", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "jointsOnCurve" + ], + "title": "jointsOnCurve", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "parentScaleConstraint" + ], + "title": "parentScaleConstraint", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "rigRoss" + ], + "title": "rigRoss", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "createCenterLocator" + ], + "title": "createCenterLocator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setJointLabels" + ], + "title": "setJointLabels", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "cbSmoothSkinWeightUI" + ], + "title": "cbSmoothSkinWeightUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleIntermediates" + ], + "title": "toggleIntermediates", + "tooltip": "" + } + ], -"MEL": [{"title" : "Create cube", - "tooltip": "Launch character rigging tool", - "command": "polyCube -w 1 -h 1 -d 1;", - "sourcetype": "mel", - "tags": ["test", "script", "mel"]} - ] -} \ No newline at end of file + "Shading": [ + { + "command": "", + "sourcetype": "file", + "tags": ["shading", "vray"], + "title": "VRay", + "tooltip": "", + "items": [ + { + "title": "Import Proxies", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", + "sourcetype": "file", + "tags": ["shading", "vray", "import","proxies"], + "tooltip": "" + }, + {"title": "separator"}, + { + "title": "Select All GES", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selectAllGES.py"] + }, + { + "title": "Select All GES Under Selection", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selection","all", "GES"] + }, + {"title": "separator"}, + { + "title": "Selection To VRay Mesh", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selection","vraymesh"] + }, + { + "title": "Add VRay Round Edges Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "round edges", "attribute"] + }, + { + "title": "Add Gamma", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add gamma"] + }, + {"title": "separator"}, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", + "sourcetype": "file", + "title": "Select Unconnected Shader Materials", + "tags": ["shading", "vray", "select", "vraymesh", "materials", "unconnected shader slots"], + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", + "sourcetype": "file", + "title": "Merge Similar VRay Mesh Materials", + "tags": ["shading", "vray", "Merge","VRayMesh", "Materials"], + "tooltip": "" + }, + { + "title": "Create Two Sided Material", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", + "sourcetype": "file", + "tooltip": "Creates two sided material for selected material and renames it", + "tags": ["shading", "vray", "two sided", "material"] + }, + { + "title": "Create Two Sided Material For Selected", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", + "sourcetype": "file", + "tooltip": "Select material to create a two sided version from it", + "tags": ["shading", "vray", "Create2SidedMtlForSelectedMtl.py"] + }, + {"title": "separator"}, + { + "title": "Add OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add", "open subdiv", "attribute"] + }, + { + "title": "Remove OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "remove","opensubdiv","attributee"] + }, + {"title": "separator"}, + { + "title": "Add Subdivision Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "addVraySubdivisionAttribute"] + }, + { + "title": "Remove Subdivision Attribute.py", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "remove","subdivision", "attribute"] + }, + {"title": "separator"}, + { + "title": "Add Vray Object Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add", "object id"] + }, + { + "title": "Add Vray Material Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "addVrayMaterialIds.py" + ] + }, + {"title": "separator"}, + { + "title": "Set Physical DOF Depth", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "physical","DOF ","Depth"] + }, + { + "title": "Magic Vray Proxy UI", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "magicVrayProxyUI"] + } + ] + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUI.py", + "sourcetype": "file", + "tags": [ + "shading", + "LightLinkUI" + ], + "title": "Light Link UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "sourcetype": "file", + "tags": ["shading", "CLRImage", "textures", "preview"], + "title": "Set Texture Preview To CLRImage", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior", + "sourcetype": "file", + "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], + "title": "fixDefaultShaderSetBehavior", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", + "sourcetype": "file", + "tags": [ + "shading", + "fixSelectedShapesReferenceAssignments" + ], + "title": "Fix Shapes Reference Assignments", + "tooltip": "Select shapes to fix the reference assignments" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\selectLambert1Members.py", + "sourcetype": "file", + "tags": [ + "shading", + "selectLambert1Members" + ], + "title": "Select Lambert1 Members", + "tooltip": "Selects all objects which have the Lambert1 shader assigned" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\selectShapesWithoutShader.py", + "sourcetype": "file", + "tags": [ + "shading", + "selectShapesWithoutShader" + ], + "title": "Select Shapes Without Shader", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", + "sourcetype": "file", + "tags": [ + "shading", + "fixRenderLayerOutAdjustmentErrors" + ], + "title": "Fix RenderLayerOut Adjustment Errors", + "tooltip": "" + } + ], + + "Layout": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "alignDistributeUI" + ], + "title": "alignDistributeUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "alignSimpleUI" + ], + "title": "alignSimpleUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "center_locator" + ], + "title": "center_locator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "average_locator" + ], + "title": "average_locator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "selectWithinProximityUI" + ], + "title": "selectWithinProximityUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "dupCurveUI" + ], + "title": "dupCurveUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "randomDeselectUI" + ], + "title": "randomDeselectUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "multiReferencerUI" + ], + "title": "multiReferencerUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "duplicateOffsetUI" + ], + "title": "duplicateOffsetUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "spPaint3d" + ], + "title": "spPaint3d", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "randomizeUI" + ], + "title": "randomizeUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "distributeWithinObjectUI" + ], + "title": "distributeWithinObjectUI", + "tooltip": "" + } + ], + + "Particles": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjects" + ], + "title": "instancerToObjects", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsInstances" + ], + "title": "instancerToObjectsInstances", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticlesAndInstancerCleanSource" + ], + "title": "objectsToParticlesAndInstancerCleanSource", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "particleComponentsToLocators" + ], + "title": "particleComponentsToLocators", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticlesAndInstancer" + ], + "title": "objectsToParticlesAndInstancer", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "spawnParticlesOnMesh" + ], + "title": "spawnParticlesOnMesh", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsInstancesWithAnimation" + ], + "title": "instancerToObjectsInstancesWithAnimation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticles" + ], + "title": "objectsToParticles", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "add_particle_cacheFile_attrs" + ], + "title": "add_particle_cacheFile_attrs", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "mergeParticleSystems" + ], + "title": "mergeParticleSystems", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "particlesToLocators" + ], + "title": "particlesToLocators", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsWithAnimation" + ], + "title": "instancerToObjectsWithAnimation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "killSelectedParticles" + ], + "title": "killSelectedParticles", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "clearInitialState" + ], + "title": "clearInitialState", + "tooltip": "" + } + ], + + "Cleanup": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectIntermediateObjects" + ], + "title": "selectIntermediateObjects", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "resetViewportCache" + ], + "title": "resetViewportCache", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectNonUniqueNames" + ], + "title": "selectNonUniqueNames", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "uniqifyNodeNames" + ], + "title": "uniqifyNodeNames", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectByType" + ], + "title": "selectByType", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeNamespaces" + ], + "title": "removeNamespaces", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "autoRenameFileNodes" + ], + "title": "autoRenameFileNodes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "remove_user_defined_attributes" + ], + "title": "remove_user_defined_attributes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnknownNodes" + ], + "title": "removeUnknownNodes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "colorbleedRename" + ], + "title": "colorbleedRename", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnloadedReferences" + ], + "title": "removeUnloadedReferences", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "referenceEditsUI" + ], + "title": "referenceEditsUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "renameShapesToTransform" + ], + "title": "renameShapesToTransform", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeReferencesFailedEdits" + ], + "title": "removeReferencesFailedEdits", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "reorderUI" + ], + "title": "reorderUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "pastedCleaner" + ], + "title": "pastedCleaner", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "deleteGhostIntermediateObjects" + ], + "title": "deleteGhostIntermediateObjects", + "tooltip": "" + } + ], + + "Projects": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "fifa" + ], + "title": "fifa", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "beakbuds" + ], + "title": "beakbuds", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "redeemer" + ], + "title": "redeemer", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "bjorn" + ], + "title": "bjorn", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "aldi" + ], + "title": "aldi", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "eneco" + ], + "title": "eneco", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "duurzame_verpakking" + ], + "title": "duurzame_verpakking", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "bunch" + ], + "title": "bunch", + "tooltip": "" + } + ], + + "Pyblish": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "layout" + ], + "title": "layout", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "look" + ], + "title": "look", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "submit_to_deadline" + ], + "title": "submit_to_deadline", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "instance_creator" + ], + "title": "instance_creator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "utilities" + ], + "title": "utilities", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "loader" + ], + "title": "loader", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "animation" + ], + "title": "animation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "lighting" + ], + "title": "lighting", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "inventory_ui" + ], + "title": "inventory_ui", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "explorer" + ], + "title": "explorer", + "tooltip": "" + } + ], + + "Others": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "instanceSmartTransform" + ], + "title": "instanceSmartTransform", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "archiveSceneUI" + ], + "title": "archiveSceneUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "getSimilarMeshes" + ], + "title": "getSimilarMeshes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "createBoundingBoxEachSelected" + ], + "title": "createBoundingBoxEachSelected", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "curveFromPositionEveryFrame" + ], + "title": "curveFromPositionEveryFrame", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "hair" + ], + "title": "hair", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "selectSoftSelection" + ], + "title": "selectSoftSelection", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "instanceLeafSmartTransform" + ], + "title": "instanceLeafSmartTransform", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "randomizeUVShellsSelectedObjects" + ], + "title": "randomizeUVShellsSelectedObjects", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "centerPivotGroup" + ], + "title": "centerPivotGroup", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "locatorsOnSelectedFaces" + ], + "title": "locatorsOnSelectedFaces", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "display" + ], + "title": "display", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "copyDeformers" + ], + "title": "copyDeformers", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "locatorsOnEdgeSelectionPrompt" + ], + "title": "locatorsOnEdgeSelectionPrompt", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "yeti" + ], + "title": "yeti", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "selectInReferenceEditor" + ], + "title": "selectInReferenceEditor", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "selectConstrainingObject" + ], + "title": "selectConstrainingObject", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "deformerSetRelationsUI" + ], + "title": "deformerSetRelationsUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "recreateBaseNodesForAllLatticeNodes" + ], + "title": "recreateBaseNodesForAllLatticeNodes", + "tooltip": "" + } + ] +} diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index f72dc07ed9..5349284e63 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -1,90 +1,64 @@ import sys +import os +import logging +import site from avalon.vendor.Qt import QtWidgets, QtCore +import maya.cmds as cmds + self = sys.modules[__name__] self._menu = "colorbleed" -self._parent = {widget.objectName(): widget for widget in - QtWidgets.QApplication.topLevelWidgets()}.get("MayaWindow") + +# set colorbleed scripts path in environment keys +os.environ["COLORBLEED_SCRIPTS"] = r"P:\pipeline\dev\git\cbMayaScripts\cbMayaScripts" + +log = logging.getLogger(__name__) -def install(): - # from . import interactive +def deferred(): - uninstall() + # todo: replace path with server / library path + site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") - def deferred(): + from scriptsmenu import launchformaya + import scriptsmenu.scriptsmenu as menu - import site - import os + log.info("Attempting to install ...") - # todo: replace path with server / library path - site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") + # load configuration of custom menu + config_path = os.path.join(os.path.dirname(__file__), "menu.json") + config = menu.load_configuration(config_path) - from scriptsmenu import launchformaya - import scriptsmenu.scriptsmenu as menu + # hack? + parent = launchformaya._maya_main_menubar() + cb_menu = menu.ScriptsMenu(title=self._menu.title(), parent=parent) - # load configuration of custon menu - config_path = os.path.join(os.path.dirname(__file__), "menu.json") - config = menu.load_configuration(config_path) + # register modifiers + modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier + cb_menu.register_callback(modifiers, launchformaya.to_shelf) - # create menu in appliction - cb_menu = launchformaya.main(title=self._menu, parent=self._parent) - - # apply configuration - menu.load_from_configuration(cb_menu, config) - - # cmds.menu(self._menu, - # label=self._menu.capitalize(), - # tearOff=True, - # parent="MayaWindow") - # - # # Modeling sub-menu - # cmds.menuItem("Modeling", - # label="Modeling", - # tearOff=True, - # subMenu=True, - # parent=self._menu) - # - # cmds.menuItem("Combine", command=interactive.combine) - # - # # Rigging sub-menu - # cmds.menuItem("Rigging", - # label="Rigging", - # tearOff=True, - # subMenu=True, - # parent=self._menu) - # - # cmds.menuItem("Auto Connect", command=interactive.auto_connect) - # cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) - # cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) - # cmds.menuItem("Clone (Special)", command=interactive.clone_special) - # cmds.menuItem("Create Follicle", command=interactive.follicle) - # - # # Animation sub-menu - # cmds.menuItem("Animation", - # label="Animation", - # tearOff=True, - # subMenu=True, - # parent=self._menu) - # - # cmds.menuItem("Set Defaults", command=interactive.set_defaults) - # - # cmds.setParent("..", menu=True) - # - # cmds.menuItem(divider=True) - # - # cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) - - # Allow time for uninstallation to finish. - QtCore.QTimer.singleShot(100, deferred) + # apply configuration + menu.load_from_configuration(cb_menu, config) def uninstall(): + + log.info("Attempting to uninstall ..") app = QtWidgets.QApplication.instance() widgets = dict((w.objectName(), w) for w in app.allWidgets()) menu = widgets.get(self._menu) if menu: - menu.deleteLater() - del(menu) + try: + menu.deleteLater() + del menu + except Exception as e: + log.error(e) + + +def install(): + + uninstall() + # Allow time for uninstallation to finish. + cmds.evalDeferred(deferred) diff --git a/colorbleed/plugins/maya/publish/__collect_instance_per_item.py b/colorbleed/plugins/maya/publish/__collect_instance_per_item.py deleted file mode 100644 index 7dd70d444a..0000000000 --- a/colorbleed/plugins/maya/publish/__collect_instance_per_item.py +++ /dev/null @@ -1,177 +0,0 @@ -from collections import defaultdict - -from maya import cmds - -import cbra.utils.maya.node_uuid as node_uuid -import cbra.lib - -import pyblish.api - - -class CollectInstancePerItem(pyblish.api.ContextPlugin): - """Collect instances from the Maya scene and breaks them down per item id - - An instance is identified by having an _INST suffix - and a .family user-defined attribute. - - All other user-defined attributes of the object set - is accessible within each instance's data. - - This collector breaks the instances down to each Item member it contains, - by using the IDs on the nodes in the instance it will split up the instance - into separate instances for each unique "item" id it finds. - - Note: - - Only breaks down based on children members and ignores parent members. - - Discards members without IDs. - - """ - - order = pyblish.api.CollectorOrder + 0.1 - hosts = ["maya"] - label = "Instance per Item" - - _include_families = ["colorbleed.look"] - - def process(self, context): - - invalid = list() - - for objset in cmds.ls("*_SET", - objectsOnly=True, - type='objectSet', - long=True, - recursive=True): # Include namespace - - try: - family = cmds.getAttr("{}.family".format(objset)) - except ValueError: - self.log.error("Found: %s found, but no family." % objset) - continue - - if family not in self._include_families: - continue - - # ignore referenced sets - if cmds.referenceQuery(objset, isNodeReferenced=True): - continue - - instances = self.build_instances(context, objset) - if not instances: - - # Log special error messages when objectSet is completely - # empty (has no members) to clarify to artists the root of - # their problem. - if not cmds.sets(objset, query=True): - self.log.error("Instance objectSet has no members: " - "{}".format(objset)) - - self.log.error("No instances retrieved from objectSet: " - "{}".format(objset)) - invalid.append(objset) - - if invalid: - raise RuntimeError("Invalid instances: {}".format(invalid)) - - # Sort context based on family - context[:] = sorted( - context, key=lambda instance: instance.data("family")) - - def build_instances(self, context, objset): - """Build the instances for a single instance objectSet - - Returns: - list: The constructed instances from the objectSet. - - """ - - self.log.info("Collecting: %s" % objset) - - short_name = objset.rsplit("|", 1)[-1].rsplit(":", 1)[-1] - - # Default data - default_data = {"name": short_name[:-5], - "subset": "default"} - - # Get user data from user defined attributes - user_data = dict() - for attr in cmds.listAttr(objset, userDefined=True): - try: - value = cmds.getAttr("{}.{}".format(objset, attr)) - user_data[attr] = value - except RuntimeError: - continue - - # Maintain nested object sets - members = cmds.sets(objset, query=True) - members = cmds.ls(members, long=True) - - children = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] - - # Exclude intermediate objects - children = cmds.ls(children, noIntermediate=True, long=True) - - nodes = members + children - nodes = list(set(nodes)) - - # Group nodes using ids to an Item - nodes_id = node_uuid.build_cache(nodes, include_without_ids=True) - - # Log warning for nodes without ids - if None in nodes_id: - self.log.warning("Skipping nodes without ids: " - "{}".format(nodes_id[None])) - - # ignore nodes without ids - context.data["instancePerItemNodesWithoutId"] = nodes_id.pop(None, - None) - - item_groups = defaultdict(list) - - for id, nodes in nodes_id.iteritems(): - item_id = id.rsplit(":", 1)[0] - item_groups[item_id].extend(nodes) - - instances = list() - for item_id, item_nodes in item_groups.iteritems(): - - ctx = node_uuid.parse_id("{}:fake_node_uuid".format(item_id)) - - # Use itemPath to parse full blown context using official lib - ctx = cbra.lib.parse_context(ctx['itemPath']) - - item = ctx.get('item', None) - if item is None: - self.log.info("Unparsed item id: {}".format(item_id)) - self.log.error("Item can't be parsed and seems to be " - "non-existent. Was an asset renamed? Or your" - "project set incorrectly?") - raise RuntimeError("Item not parsed. See log for description.") - - instance = context.create_instance(objset) - - # Set the related members - instance[:] = item_nodes - instance.data['setMembers'] = item_nodes - - # Set defaults and user data - instance.data.update(default_data.copy()) - instance.data.update(user_data.copy()) - - # Override the label to be clear - name = instance.data['name'] - instance.data['label'] = "{0} ({1})".format(name, item) - - # Store that the instance is collected per item - instance.data['_instancePerItem'] = True - instance.data['_itemContext'] = ctx - - assert "family" in instance.data, "No family data in instance" - assert "name" in instance.data, ("No objectSet name data " - "in instance") - - instances.append(instance) - - return instances diff --git a/colorbleed/plugins/maya/publish/__collect_yeti_caches.py b/colorbleed/plugins/maya/publish/__collect_yeti_caches.py deleted file mode 100644 index a236836cdf..0000000000 --- a/colorbleed/plugins/maya/publish/__collect_yeti_caches.py +++ /dev/null @@ -1,156 +0,0 @@ -import os -import re -import pyseq -import glob - -import pyblish.api - -from maya import cmds - - -class SeletYetiCachesAction(pyblish.api.Action): - """Select the nodes related to the collected file textures""" - - label = "Select yeti nodes" - on = "succeeded" # This action is only available on a failed plug-in - icon = "search" # Icon from Awesome Icon - - def process(self, context, plugin): - - self.log.info("Finding textures..") - - # Get the errored instances - instances = [] - for result in context.data["results"]: - instance = result["instance"] - if instance is None: - continue - - instances.append(instance) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(instances, plugin) - - # Get the texture nodes from the instances - nodes = [] - for instance in instances: - texture_nodes = instance.data['yetiCaches'].keys() - nodes.extend(texture_nodes) - - # Ensure unique - nodes = list(set(nodes)) - - if nodes: - self.log.info("Selecting nodes: %s" % ", ".join(nodes)) - cmds.select(nodes, r=True, noExpand=True) - else: - self.log.info("No nodes found.") - cmds.select(deselect=True) - - -def get_sequence(filename, pattern="%04d"): - """Get pyseq sequence from filename - - Supports negative frame ranges like (-001, 0000, 0001 and -0001, 0000, 0001). - - Arguments: - filename (str): The full path to filename containing the given pattern. - pattern (str): The pattern to swap with the variable frame number. - - Returns: - pyseq.Sequence: file sequence. - - """ - - glob_pattern = filename.replace(pattern, "*") - - escaped = re.escape(filename) - re_pattern = escaped.replace(pattern, "-?[0-9]+") - - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] - - return pyseq.get_sequences(files) - - -class CollectYetiCaches(pyblish.api.InstancePlugin): - """Collect used yeti caches. - - Collects the file sequences from pgYetiMaya.cacheFileName - - """ - - order = pyblish.api.CollectorOrder + 0.495 - label = 'Yeti Caches' - families = ["colorbleed.groom"] - actions = [SeletYetiCachesAction] - - TYPES = {"pgYetiMaya": "cacheFileName"} - - def process(self, instance): - - # Get textures from sets - members = instance.data("setMembers") - members = cmds.ls(members, dag=True, shapes=True, type="pgYetiMaya", - noIntermediate=True, long=True) - if not members: - raise RuntimeError("Instance appears to be empty (no members)") - - # Collect only those cache frames that are required - # If handles are required it is assumed to already be included - # in the start frame and end frames. - # (e.g. using frame handle collector) - start_frame = instance.data("startFrame") - end_frame = instance.data("endFrame") - required = set(range(int(start_frame), int(end_frame) + 1)) - - history = cmds.listHistory(members) or [] - - resources = instance.data.get("resources", []) - yeti_caches = dict() - - for node_type, attr in self.TYPES.iteritems(): - for node in cmds.ls(history, type=node_type, long=True): - - attribute = "{0}.{1}".format(node, attr) - - # Source - source = cmds.getAttr(attribute) - if not source: - self.log.error("Node does not have a file set: " - "{0}".format(node)) - - # Collect the source as expanded path because that's also - # how the attribute must be 'set' for yeti nodes. - source = os.path.realpath(cmds.workspace(expandName=source)) - - # Collect the frames we need from the sequence - sequences = get_sequence(source) - files = list() - for sequence in sequences: - for index, frame in enumerate(sequence.frames()): - if frame not in required: - continue - - item = sequence[index] - files.append(item.path) - - # Define the resource - resource = {"tags": ["maya", "yeti", "attribute"], - "node": node, - "attribute": attribute, - "source": source, # required for resources - "files": files, # required for resources - "subfolder": "caches" # optional for resources - } - - resources.append(resource) - - # For validations - yeti_caches[node] = {"attribute": attribute, - "source": source, - "sequences": sequences} - - # Store data on instance - instance.data['yetiCaches'] = yeti_caches - instance.data['resources'] = resources diff --git a/colorbleed/plugins/maya/publish/__extract_layout.py b/colorbleed/plugins/maya/publish/__extract_layout.py deleted file mode 100644 index 823337b2e1..0000000000 --- a/colorbleed/plugins/maya/publish/__extract_layout.py +++ /dev/null @@ -1,81 +0,0 @@ -import json - -from maya import cmds - -import avalon.maya -import colorbleed.api - -import cb.utils.maya.context as context -import cbra.utils.maya.layout as layout - - -def get_upstream_hierarchy_fast(nodes): - """Passed in nodes must be long names!""" - - matched = set() - parents = [] - - for node in nodes: - hierarchy = node.split("|") - num = len(hierarchy) - for x in range(1, num-1): - parent = "|".join(hierarchy[:num-x]) - if parent in parents: - break - else: - parents.append(parent) - matched.add(parent) - - return parents - - -class ExtractLayout(colorbleed.api.Extractor): - """Extract Layout as both gpuCache and Alembic""" - - label = "Layout (gpuCache & alembic)" - hosts = ["maya"] - families = ["colorbleed.layout"] - - def process(self, instance): - - # Define extract output file path - dir_path = self.staging_dir(instance) - - start = instance.data.get("startFrame", 1) - end = instance.data.get("endFrame", 1) - step = instance.data.get("step", 1.0) - placeholder = instance.data.get("placeholder", False) - write_color_sets = instance.data.get("writeColorSets", False) - renderable_only = instance.data.get("renderableOnly", False) - visible_only = instance.data.get("visibleOnly", False) - - layers = instance.data.get("animLayersActive", None) - if layers: - layers = json.loads(layers) - self.log.info("Publishing with animLayers active: " - "{0}".format(layers)) - - # Perform extraction - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - - # Get children hierarchy - nodes = instance.data['setMembers'] - cmds.select(nodes, r=True, hierarchy=True) - hierarchy = cmds.ls(selection=True, long=True) - - with context.evaluation("off"): - with context.no_refresh(): - with context.active_anim_layers(layers): - layout.extract_layout(hierarchy, - dir_path, - start=start, - end=end, - step=step, - placeholder=placeholder, - write_color_sets=write_color_sets, - renderable_only=renderable_only, - visible_only=visible_only) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, dir_path)) diff --git a/colorbleed/plugins/maya/publish/__validate_layout_nodes.py b/colorbleed/plugins/maya/publish/__validate_layout_nodes.py deleted file mode 100644 index fda17e4433..0000000000 --- a/colorbleed/plugins/maya/publish/__validate_layout_nodes.py +++ /dev/null @@ -1,91 +0,0 @@ -import os - -import maya.cmds as cmds - -import pyblish.api -import colorbleed.api - -import cbra.lib -from cb.utils.python.decorators import memorize - - -def isclose(a, b, rel_tol=1e-9, abs_tol=0.0): - return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) - - -@memorize -def is_published_path(path): - """Return whether path is from a published file""" - - # Quick check (optimization) without going through the folder - # structure - if cbra.lib.DIR_PUBLISH.lower() not in path.lower(): - return False - - try: - context = cbra.lib.parse_context(path) - except RuntimeError: - context = dict() - - return all([context.get("family", None), - context.get("subset", None), - context.get("version", None)]) - - -class ValidateLayoutNodes(pyblish.api.InstancePlugin): - """Validates that layout nodes behave to certain rules - - Gpu caches in a layout may not have sub-frame offsets, like offsets with a - value after the decimal point. (e.g. 1.45) - - Gpu caches loaded in a layout MUST come from a published source that has - family and version. - - """ - - order = colorbleed.api.ValidateContentsOrder - label = 'Layout Nodes' - families = ['colorbleed.layout'] - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - - caches = cmds.ls(instance, type="gpuCache", long=True) - - # Validate sub-frame offsets - invalid_offsets = list() - for cache in caches: - - offset = cmds.getAttr("{}.animOffset".format(cache)) - if not isclose(offset, round(offset)): - cls.log.warning("Invalid sub-frame offset on: %s" % cache) - invalid_offsets.append(cache) - - # Validate gpuCache paths are from published files - invalid_paths = list() - for cache in caches: - path = cmds.getAttr("{}.cacheFileName".format(cache)) - path = os.path.normpath(path) - if not is_published_path(path): - cls.log.warning("GpuCache path not from published file: " - "{0} -> {1}".format(cache, path)) - invalid_paths.append(cache) - - invalid = invalid_offsets + invalid_paths - - return invalid - - def process(self, instance): - - # Clear cache only once per publish. So we store a value on - # the context on the first instance so we clear only once. - name = self.__class__.__name__ - key = "_plugin_{0}_processed".format(name) - if not instance.context.data.get(key, False): - is_published_path.cache.clear() - instance.context.data[key] = True - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Invalid nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/__validate_related_node_ids.py b/colorbleed/plugins/maya/publish/__validate_related_node_ids.py deleted file mode 100644 index a79138168d..0000000000 --- a/colorbleed/plugins/maya/publish/__validate_related_node_ids.py +++ /dev/null @@ -1,83 +0,0 @@ -import pyblish.api -import colorbleed.api - -import cbra.utils.maya.node_uuid as id_utils -import cbra.lib - - -class ValidateRelatedNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have related colorbleed ids. - - An ID is 'related' if its built in the current Item. - - Note that this doesn't ensure it's from the current Task. An ID created - from `lookdev` has the same relation to the Item as one coming from others, - like `rigging` or `modeling`. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.model'] - hosts = ['maya'] - label = 'Related Id Attributes' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - @classmethod - def get_invalid(cls, instance): - """Return the member nodes that are invalid""" - - context = instance.context - current_file = context.data.get('currentFile', None) - if not current_file: - raise RuntimeError("No current file information: " - "{0}".format(current_file)) - - try: - context = cbra.lib.parse_context(current_file) - except RuntimeError, e: - cls.log.error("Can't generate UUIDs because scene isn't " - "in new-style pipeline: ".format(current_file)) - raise e - - def to_item(id): - """Split the item id part from a node id""" - return id.rsplit(":", 1)[0] - - # Generate a fake id in the current context to retrieve the item - # id prefix that should match with ids on the nodes - fake_node = "__node__" - ids = id_utils.generate_ids(context, [fake_node]) - id = ids[fake_node] - item_prefix = to_item(id) - - # Take only the ids with more than one member - invalid = list() - invalid_items = set() - for member in instance: - member_id = id_utils.get_id(member) - - # skip nodes without ids - if not member_id: - continue - - if not member_id.startswith(item_prefix): - invalid.append(member) - invalid_items.add(to_item(member_id)) - - # 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 - - def process(self, instance): - """Process all meshes""" - - # Ensure all nodes have a cbId - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Nodes found with non-related " - "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py b/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py deleted file mode 100644 index 55d3a14377..0000000000 --- a/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py +++ /dev/null @@ -1,167 +0,0 @@ -import os -from collections import defaultdict - -import pyblish.api -import colorbleed.api - -import cbra.lib -from cbra.utils.maya.abc import get_alembic_ids -from cbra.utils.maya.node_uuid import get_id - - -def get_subset_path(context): - return os.path.join(context['itemPath'], - cbra.lib.DIR_PUBLISH, - context['family'], - context['subset']) - - -class ValidateUniqueIdsInItem(pyblish.api.InstancePlugin): - """Checks whether IDs are unique across other subsets - - This ensures a model to be published can't have ids - which are already present in another subset. For example - the "default" model can't have ids present in the "high" - subset. - - Note: - This will also invalidate the instance if it contains - nodes that are present in another instance in the scene. - So ensure the instance you're publishing actually has - the correct set members. - - """ - - order = colorbleed.api.ValidateMeshOrder - families = ['colorbleed.model'] - hosts = ['maya'] - label = 'Unique Ids in Item' - actions = [colorbleed.api.SelectInvalidAction] - optional = True - - @classmethod - def iter_invalid(cls, instance): - - verbose = instance.data.get("verbose", False) - - def _get_instance_ids(instance): - """Collect ids in an instance""" - nodes_per_id = defaultdict(list) - for node in instance: - node_id = get_id(node) - if node_id: - nodes_per_id[node_id].append(node) - return nodes_per_id - - nodes_per_id = _get_instance_ids(instance) - if not nodes_per_id: - return - - ids_lookup = set(nodes_per_id.keys()) - - instance_context = instance.data["instanceContext"] - instance_subset = instance.data['subset'] - - assert instance_context, "Instance must have 'instanceContext' data" - assert instance_subset, "Instance must have 'subset' data" - - subsets_checked = set() - subsets_checked.add(instance_subset) # we can skip this subset - - # Compare with all other *currently publishing instances* - # of family 'model' for this item - for other_instance in instance.context: - if other_instance is instance: - continue - - if other_instance.data['subset'] == instance_subset: - cls.log.error("Another instance has the same subset? " - "This should never happen.") - - if other_instance.data['family'] != "model": - continue - - if other_instance.data['instanceContext']['item'] != \ - instance_context['item']: - cls.log.error("Also publishing model for other item? " - "This should never happen.") - continue - other_ids = _get_instance_ids(other_instance).keys() - - # Perform comparison - intersection = ids_lookup.intersection(other_ids) - if intersection: - for node_id in intersection: - nodes = nodes_per_id[node_id] - for node in nodes: - yield node - - # Those that are invalid don't need to be checked again - ids_lookup.difference_update(other_ids) - - if not ids_lookup: - # Once we have no ids to check for anymore we can already - # return - return - - subsets_checked.add(other_instance.data['subset']) - - # Compare with all previously *published instances* - # of family 'model' for this item - ctx = instance_context.copy() - ctx['family'] = "model" - - published_subsets = cbra.lib.list_subsets(ctx) - published_subsets = set(x for x in published_subsets if - x != instance_subset) - - for published_subset in published_subsets: - ctx['subset'] = published_subset - ctx['subsetPath'] = get_subset_path(ctx) - - versions = cbra.lib.list_versions(ctx) - version = cbra.lib.find_highest_version(versions) - if not version: - cls.log.debug("No published version for " - "'model': {0}".format(published_subset)) - continue - - ctx['currentVersion'] = version - publish_abc = cbra.lib.get_filepath(ctx) + ".abc" - - if not os.path.exists(publish_abc): - cls.log.error("Published file to compare with does not exist: " - "{0}".format(publish_abc)) - continue - - if verbose: - cls.log.debug("Comparing with: {0}".format(publish_abc)) - - abc_ids = set(get_alembic_ids(publish_abc).values()) - - # Perform comparison - intersection = ids_lookup.intersection(abc_ids) - if intersection: - for node_id in intersection: - nodes = nodes_per_id[node_id] - for node in nodes: - yield node - - # Those that are invalid don't need to be checked again - ids_lookup.difference_update(abc_ids) - - if not ids_lookup: - # Once we have no ids to check for anymore we can already - # return - return - - return - - @classmethod - def get_invalid(cls, instance): - return list(cls.iter_invalid(instance)) - - def process(self, instance): - """Process all meshes""" - if any(self.iter_invalid(instance)): - raise RuntimeError("Invalid nodes found in {0}".format(instance)) diff --git a/colorbleed/plugins/maya/publish/_integrate_cb_asset.py b/colorbleed/plugins/maya/publish/_integrate_cb_asset.py deleted file mode 100644 index 61535e58db..0000000000 --- a/colorbleed/plugins/maya/publish/_integrate_cb_asset.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -import shutil - -import pyblish_cb.lib -import colorbleed.api - - -class IntegrateColorbleedAssets(colorbleed.api.Integrator): - """Name and position instances on disk for instances. - - The files are transferred from the `extractDir` to the - computed `integrationDir` and are renamed as: - - "{item}_{family}_{subsetName}_{version}.{ext}" - - Assumptions: - - Each extracted instance is 1 file (no directories) - - """ - - label = "Asset" - families = ["colorbleed.model", "colorbleed.rig", "colorbleed.pointcache", - "colorbleed.proxy", "colorbleed.layout", "colorbleed.look", - "colorbleed.vrmeshReplace", "colorbleed.review", - "colorbleed.instancer", "colorbleed.camera", - "colorbleed.mayaAscii", - "colorbleed.furYeti"] - - def process(self, instance): - super(IntegrateColorbleedAssets, self).process(instance) - - self.log.info("Integrating {0}..".format(instance)) - - integration = pyblish_cb.lib.compute_integration(instance) - - # Store reference for upcoming plug-ins - instance.data["integrationDir"] = integration['path'] - instance.data["integrationVersion"] = integration['versionNum'] - - path = integration['path'] - data = integration.copy() - - try: - if not os.path.exists(path): - os.makedirs(path) - - self.log.info("Moving files to %s" % path) - - tmp = instance.data["extractDir"] - for src in (os.path.join(tmp, f) for f in os.listdir(tmp)): - - self.log.debug("Integrating %s" % src) - - # Source must be a file - if not os.path.isfile(src): - self.log.error("Source is not a file: {0}".format(src)) - continue - - # TODO(marcus): Consider files without extension - data["ext"] = src.split(".", 1)[-1] - dst = os.path.join(path, "{item}_" - "{family}_" - "{subsetName}_" - "{version}.{ext}".format( - **data)) - - # Copy - self.log.info("\"%s\" -> \"%s\"" % (src, dst)) - shutil.copyfile(src, dst) - - self.log.debug("Tagged %s with .Version" % path) - - try: - subset_path = os.path.dirname(path) - cquery.tag(subset_path, ".Subset") - self.log.debug("Tagged %s with .Subset" % subset_path) - except cquery.TagExists: - pass - - except OSError as e: - # If, for whatever reason, this instance did not get written. - instance.data.pop("integrationDir") - raise e - - except Exception as e: - raise Exception("An unknown error occured: %s" % e) From 9dfc7f97e83d3665ff128e4041d7bde770770fab Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 10 Jul 2017 11:57:28 +0200 Subject: [PATCH 04/31] added naming convention validator --- colorbleed.bat | 4 ---- colorbleed/plugins/maya/publish/validate_naming_convention.py | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 colorbleed.bat diff --git a/colorbleed.bat b/colorbleed.bat deleted file mode 100644 index 3bf3119e6a..0000000000 --- a/colorbleed.bat +++ /dev/null @@ -1,4 +0,0 @@ -:: Set paths to ensure plugins have access to the inhouse tools - -set PYTHONPATH=%PYTHONPATH%;P:\pipeline\dev\git\cb; -set PYTHONPATH=%PYTHONPATH%;P:\pipeline\dev\git\cbra; diff --git a/colorbleed/plugins/maya/publish/validate_naming_convention.py b/colorbleed/plugins/maya/publish/validate_naming_convention.py index db4846b2bb..7dbf9ad3f7 100644 --- a/colorbleed/plugins/maya/publish/validate_naming_convention.py +++ b/colorbleed/plugins/maya/publish/validate_naming_convention.py @@ -7,6 +7,7 @@ import colorbleed.api class ValidateNamingConvention(pyblish.api.InstancePlugin): label = "" + families = ["colorbleed.model"] host = ["maya"] actions = [colorbleed.api.SelectInvalidAction] From b4b9960d01400dbe714bf5109aa69617537e8368 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:40:11 +0200 Subject: [PATCH 05/31] added list of accepted and ignored file types --- colorbleed/filetypes.py | 2 + .../plugins/maya/create/colorbleed_texture.py | 0 colorbleed/plugins/maya/publish/_debug.py | 4 -- ...t_look_textures.py => collect_textures.py} | 9 +-- .../plugins/maya/publish/extract_textures.py | 0 colorbleed/plugins/publish/pre_integrate.py | 68 +++++++++++++++++++ 6 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 colorbleed/filetypes.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_texture.py rename colorbleed/plugins/maya/publish/{collect_look_textures.py => collect_textures.py} (94%) create mode 100644 colorbleed/plugins/maya/publish/extract_textures.py create mode 100644 colorbleed/plugins/publish/pre_integrate.py diff --git a/colorbleed/filetypes.py b/colorbleed/filetypes.py new file mode 100644 index 0000000000..fbefeff9be --- /dev/null +++ b/colorbleed/filetypes.py @@ -0,0 +1,2 @@ +accepted_images_types = [".png", ".jpg", ".tga", ".tiff"] +ignored_images_types = [".pds"] diff --git a/colorbleed/plugins/maya/create/colorbleed_texture.py b/colorbleed/plugins/maya/create/colorbleed_texture.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/_debug.py b/colorbleed/plugins/maya/publish/_debug.py index 7919b1fda1..ef40126be1 100644 --- a/colorbleed/plugins/maya/publish/_debug.py +++ b/colorbleed/plugins/maya/publish/_debug.py @@ -13,7 +13,3 @@ class DebugPlugin(pyblish.api.InstancePlugin): self.log("\n\n----------------------") self.log("Instance") pprint.pprint(instance) - - self.log("\n\n----------------------") - self.log("Instance.data") - pprint.pprint(instance.data) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_textures.py similarity index 94% rename from colorbleed/plugins/maya/publish/collect_look_textures.py rename to colorbleed/plugins/maya/publish/collect_textures.py index a6636b24a2..a55e274da0 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_textures.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -import cb.utils.maya.shaders as shader +import cb.utils.maya.shaders as shaders TAGS = ["maya", "attribute", "look"] TAGS_LOOKUP = set(TAGS) @@ -74,7 +74,7 @@ class CollectLookTextures(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.498 label = 'Textures' - families = ["colorbleed.look"] + families = ["colorbleed.texture"] actions = [SelectTextureNodesAction] def process(self, instance): @@ -134,19 +134,20 @@ class CollectLookTextures(pyblish.api.InstancePlugin): # paths as the computed patterns source = computed_source.replace("\\", "/") - files = shader.get_file_node_files(node) + files = shaders.get_file_node_files(node) if not files: self.log.error("File node does not have a texture set: " "{0}".format(node)) return # Define the resource + # todo: find a way to generate the destination for the publisher resource = {"tags": TAGS[:], "node": node, "attribute": attribute, "source": source, # required for resources "files": files, # required for resources - "subfolder": "textures" # optional for resources + "subfolder": "textures", # optional for resources } return resource diff --git a/colorbleed/plugins/maya/publish/extract_textures.py b/colorbleed/plugins/maya/publish/extract_textures.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/publish/pre_integrate.py b/colorbleed/plugins/publish/pre_integrate.py new file mode 100644 index 0000000000..cb5eaa0656 --- /dev/null +++ b/colorbleed/plugins/publish/pre_integrate.py @@ -0,0 +1,68 @@ +import os +import logging +import shutil + +import maya.cmds as cmds + +import pyblish.api + +log = logging.getLogger(__name__) + + +class PostIntegrateAsset(pyblish.api.InstancePlugin): + """Resolve any dependency issies + + This plug-in resolves any paths which, if not updated might break + the published file. + + The order of families is important, when working with lookdev you want to + first publish the texture, update the texture paths in the nodes and then + publish the shading network. Same goes for file dependent assets. + """ + + label = "Post Intergrate Asset" + order = pyblish.api.IntegratorOrder + 0.1 + families = ["colorbleed.lookdev", "colorbleed.texture"] + + def process(self, instance): + + # get needed variables + version_folder = instance.data["versionFolder"] + family = instance.data["family"] + resources = instance.data("resources", []) + + self.log.info("Running post process for {}".format(instance.name)) + + if family == "colorbleed.texture": + texture_folder = os.path.join(version_folder, "textures") + self.remap_resource_nodes(resources, folder=texture_folder) + + elif family == "colorbleed.lookdev": + self.remap_resource_nodes(resources) + + # self.log.info("Removing temporary files and folders ...") + # if passed: + # stagingdir = instance.data["stagingDir"] + # shutil.rmtree(stagingdir) + + def remap_resource_nodes(self, resources, folder=None): + + self.log.info("Updating resource nodes ...") + for resource in resources: + source = resource["source"] + if folder: + fname = os.path.basename(source) + fpath = os.path.join(folder, fname) + else: + fpath = source + + node_attr = resource["attribute"] + print("UPDATING {} -> {}".format(node_attr, fpath)) + cmds.setAttr(node_attr, fpath, type="string") + + self.log.info("Saving file ...") + + cmds.file(save=True, type="mayaAscii") + + def remap_yeti_resource_nodes(self, node,): + pass From dcf6c21bfaacc9e44228099656f55541e884bbf3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:41:23 +0200 Subject: [PATCH 06/31] fixed bug in save callback --- colorbleed/maya/__init__.py | 80 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 7e1da1a190..6e74cd0980 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,9 +1,12 @@ import os import site +import uuid -from avalon import api as avalon +from avalon import maya, api as avalon from pyblish import api as pyblish +from maya import cmds + from . import menu PARENT_DIR = os.path.dirname(__file__) @@ -28,6 +31,11 @@ def install(): menu.install() + print("Installing callbacks ... ") + avalon.on("init", on_init) + avalon.on("new", on_new) + avalon.on("save", on_save) + def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) @@ -35,3 +43,73 @@ def uninstall(): avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) menu.uninstall() + + +def _set_uuid(node): + """Add cbId to `node` + Unless one already exists. + """ + + asset = os.environ["AVALON_ASSET"] + attr = "{0}.cbId".format(node) + + if not cmds.objExists(attr): + cmds.addAttr(node, longName="cbId", dataType="string") + _, uid = str(uuid.uuid4()).rsplit("-", 1) + cb_uid = "{}:{}:{}".format(asset, node, uid) + + cmds.setAttr(attr, cb_uid, type="string") + + +def on_init(): + avalon.logger.info("Running callback on init..") + + maya.commands.reset_frame_range() + maya.commands.reset_resolution() + + +def on_new(): + avalon.logger.info("Running callback on new..") + + # Load dependencies + cmds.loadPlugin("AbcExport.mll", quiet=True) + cmds.loadPlugin("AbcImport.mll", quiet=True) + + maya.commands.reset_frame_range() + maya.commands.reset_resolution() + + +def on_save(): + """Automatically add IDs to new nodes + Any transform of a mesh, without an existing ID, + is given one automatically on file save. + """ + + avalon.logger.info("Running callback on save..") + + defaults = ["initialShadingGroup", "initialParticleSE"] + + # the default items which always want to have an ID + types = ["mesh", "shadingEngine", "file"] + + # the items which need to pass the id to their parent + subtypes = ["nurbsCurve"] + + nodes = (set(cmds.ls(type=types, long=True)) - + set(cmds.ls(long=True, readOnly=True)) - + set(cmds.ls(long=True, lockedNodes=True))) + + transforms = set() + for n in cmds.ls(type=subtypes, long=True): + for r in cmds.listRelatives(n, parent=True, fullPath=True): + transforms.add(r) + + # merge transforms and nodes in one set to make sure every item + # is unique + nodes |= transforms + + # remove any default nodes + for node in nodes: + if node in defaults: + continue + _set_uuid(node) From 535ce23a75936cedd6e24dc1adf4297cd0fc8fd0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:44:34 +0200 Subject: [PATCH 07/31] cosmetics --- colorbleed/__init__.py | 3 ++- colorbleed/api.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index a158fcbee9..f29597a297 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -8,10 +8,11 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") def install(): publish_path = os.path.join(PLUGINS_DIR, "publish") - print("Registering global plug-ins..") + pyblish.register_plugin_path(publish_path) def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) + diff --git a/colorbleed/api.py b/colorbleed/api.py index 33924cb02b..531a63a50d 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -21,7 +21,9 @@ from .action import ( ) all = [ + # plugin classes "Extractor", + # ordering "ValidatePipelineOrder", "ValidateContentsOrder", "ValidateSceneOrder", From 91c3e6fcbfb37d719e5488690e1108ccad686b80 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:45:40 +0200 Subject: [PATCH 08/31] added a extra functions --- colorbleed/maya/lib.py | 106 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 175d664f7c..0ae2d442c8 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -7,9 +7,10 @@ import logging import os import json -log = logging.getLogger(__name__) +from maya import cmds, mel -from maya import cmds + +log = logging.getLogger(__name__) def maintained_selection(arg=None): @@ -249,6 +250,78 @@ def get_current_renderlayer(): return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) +@contextlib.contextmanager +def no_undo(flush=False): + """Disable the undo queue during the context + + Arguments: + flush (bool): When True the undo queue will be emptied when returning + from the context losing all undo history. Defaults to False. + + """ + original = cmds.undoInfo(query=True, state=True) + keyword = 'state' if flush else 'stateWithoutFlush' + + try: + cmds.undoInfo(**{keyword: False}) + yield + finally: + cmds.undoInfo(**{keyword: original}) + + +def polyConstraint(components, *args, **kwargs): + """Return the list of *components* with the constraints applied. + + A wrapper around Maya's `polySelectConstraint` to retrieve its results as + a list without altering selections. For a list of possible constraints + see `maya.cmds.polySelectConstraint` documentation. + + Arguments: + components (list): List of components of polygon meshes + + Returns: + list: The list of components filtered by the given constraints. + + """ + + kwargs.pop('mode', None) + + with no_undo(flush=False): + print("la") + with maintained_selection(): + print("po") + # Apply constraint using mode=2 (current and next) so + # it applies to the selection made before it; because just + # a `maya.cmds.select()` call will not trigger the constraint. + with reset_polySelectConstraint(): + print("do") + cmds.select(components, r=1) + cmds.polySelectConstraint(*args, mode=2, **kwargs) + result = cmds.ls(selection=True) + + return result + + +@contextlib.contextmanager +def reset_polySelectConstraint(reset=True): + """Context during which the given polyConstraint settings are disabled. + + The original settings are restored after the context. + + """ + + original = cmds.polySelectConstraint(query=True, stateString=True) + + try: + if reset: + # Reset all parameters + mel.eval("resetPolySelectConstraint;") + cmds.polySelectConstraint(disable=True) + yield + finally: + mel.eval(original) + + def is_visible(node, displayLayer=True, intermediateObject=True, @@ -343,7 +416,7 @@ _alembic_options = { def extract_alembic(file, startFrame=None, endFrame=None, - selection= True, + selection=True, uvWrite= True, eulerFilter= True, dataFormat="ogawa", @@ -482,3 +555,30 @@ def extract_alembic(file, log.debug("Extracted Alembic to: %s", file) return file + + +def maya_temp_folder(): + scene_dir = os.path.dirname(cmds.file(query=True, sceneName=True)) + tmp_dir = os.path.abspath(os.path.join(scene_dir, "..", "tmp")) + if not os.path.isdir(tmp_dir): + os.makedirs(tmp_dir) + + return tmp_dir + + +def remap_resource_nodes(resources, folder=None): + + log.info("Updating resource nodes ...") + for resource in resources: + source = resource["source"] + if folder: + fname = os.path.basename(source) + fpath = os.path.join(folder, fname) + else: + fpath = source + + node_attr = resource["attribute"] + cmds.setAttr(node_attr, fpath, type="string") + + log.info("Saving file ...") + cmds.file(save=True, type="mayaAscii") From dfe134ebbc6cf084f4a6111bd9ee369b465519ca Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:46:34 +0200 Subject: [PATCH 09/31] Integrated script menu at launch, menu config is wip --- colorbleed/maya/menu.json | 727 ++++++++++++++++++++++++-------------- colorbleed/maya/menu.py | 6 +- 2 files changed, 475 insertions(+), 258 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9c3cc40bba..9258617399 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,5 +1,6 @@ { "order": [ + "main", "Modeling", "Rigging", "Shading", @@ -11,46 +12,90 @@ "Pyblish", "Others" ], + "main": [ + { + "command": "$COLORBLEED_SCRIPTS\\others\\save_scene_incremental.py", + "sourcetype": "file", + "title": "Version Up", + "tooltip": "Incremental save with a specific format" + }, + { + "command": "", + "sourcetype": "file", + "title": "Save scene", + "tooltip": "Save the scene based on a specific format" + }, + { + "title": "separator" + } + ], "Modeling": [ { "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", "sourcetype": "file", - "tags": ["modeling", "duplicate", "normalized"], + "tags": [ + "modeling", + "duplicate", + "normalized" + ], "title": "Duplicate Normalized", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", "sourcetype": "file", - "tags": ["modeling", "transfer", "uv"], + "tags": [ + "modeling", + "transfer", + "uv" + ], "title": "Transfer UVs", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", "sourcetype": "file", - "tags": ["modeling", "mirror", "symmetry"], + "tags": [ + "modeling", + "mirror", + "symmetry" + ], "title": "Mirror Symmetry", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI", "sourcetype": "file", - "tags": ["modeling", "select", "outline", "ui"], + "tags": [ + "modeling", + "select", + "outline", + "ui" + ], "title": "Select Outline UI", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", "sourcetype": "file", - "tags": ["modeling", "polygon", "uvset", "delete"], + "tags": [ + "modeling", + "polygon", + "uvset", + "delete" + ], "title": "polyDeleteOtherUVSets", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", "sourcetype": "file", - "tags": ["modeling", "combine", "polygon", "quick"], + "tags": [ + "modeling", + "combine", + "polygon", + "quick" + ], "title": "Polygon Combine Quick", "tooltip": "" }, @@ -77,7 +122,11 @@ { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", "sourcetype": "file", - "tags": ["modeling", "relax", "verts"], + "tags": [ + "modeling", + "relax", + "verts" + ], "title": "Polygon Relax Vertices", "tooltip": "" }, @@ -101,60 +150,104 @@ "tooltip": "" } ], - "Animation": [ { "command": "", "sourcetype": "", - "tags": ["animation", "attributes"], + "tags": [ + "animation", + "attributes" + ], "title": "Attributes", "tooltip": "", "items": [ { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes"], + "tags": [ + "animation", + "copy", + "attributes" + ], "title": "Copy Values", "tooltip": "Copy attribute values" - },{ + }, + { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "connections", "incoming"], + "tags": [ + "animation", + "copy", + "attributes", + "connections", + "incoming" + ], "title": "Copy In Connections", "tooltip": "Copy incoming connections" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "connections", "out"], + "tags": [ + "animation", + "copy", + "attributes", + "connections", + "out" + ], "title": "Copy Out Connections", "tooltip": "Copy outcoming connections" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "local"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "local" + ], "title": "Copy Local Transfroms", "tooltip": "Copy local transfroms" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "matrix"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "matrix" + ], "title": "Copy Matrix Transfroms", "tooltip": "Copy Matrix transfroms" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "UI"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "UI" + ], "title": "Copy Transforms UI", "tooltip": "Open the Copy Transforms UI" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "UI", "simple"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "UI", + "simple" + ], "title": "Simple Copy UI", "tooltip": "Open the simple Copy Transforms UI" } @@ -169,18 +262,28 @@ ], "title": "Optimize", "tooltip": "Optimization scripts", - "items":[ + "items": [ { "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", "sourcetype": "file", - "tags": ["animation", "hierarchy", "toggle", "freeze"], + "tags": [ + "animation", + "hierarchy", + "toggle", + "freeze" + ], "title": "Toggle Freeze Hierarchy", "tooltip": "Freeze and unfreeze hierarchy" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", "sourcetype": "file", - "tags": ["animation", "nucleus", "toggle", "parallel"], + "tags": [ + "animation", + "nucleus", + "toggle", + "parallel" + ], "title": "Toggle Parallel Nucleus", "tooltip": "Toggle parallel nucleus" } @@ -199,7 +302,10 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", "sourcetype": "file", - "tags": ["animation", "path"], + "tags": [ + "animation", + "path" + ], "title": "Path Animation", "tooltip": "" }, @@ -216,21 +322,34 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "sourcetype": "file", - "tags": ["animation", "world space", "bake"], + "tags": [ + "animation", + "world space", + "bake" + ], "title": "Bake To World Space", "tooltip": "Select the item which need to be baked to World Space" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", "sourcetype": "file", - "tags": ["animation", "gui", "simple", "capture"], + "tags": [ + "animation", + "gui", + "simple", + "capture" + ], "title": "Capture GUI - simple", "tooltip": "Simplified version of the Capture GUI" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", "sourcetype": "file", - "tags": ["animation", "optimizer", "scene"], + "tags": [ + "animation", + "optimizer", + "scene" + ], "title": "Animation Scene Optimizer", "tooltip": "Optimize animation in a scene" }, @@ -247,7 +366,12 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", "sourcetype": "file", - "tags": ["animation", "capture", "screenshot", "movie"], + "tags": [ + "animation", + "capture", + "screenshot", + "movie" + ], "title": "Capture GUI", "tooltip": "Render current camera to an image or movie" }, @@ -264,7 +388,11 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", "sourcetype": "file", - "tags": ["animation", "curves", "scene"], + "tags": [ + "animation", + "curves", + "scene" + ], "title": "Select All Animation Curves", "tooltip": "Select all animation curves in the scene" }, @@ -289,385 +417,366 @@ "tooltip": "" } ], - "Rigging": [ { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", + "sourcetype": "python", "tags": [ "rigging", - "removeRotationAxis" + "addCurveBetween", + "python" ], - "title": "removeRotationAxis", - "tooltip": "" + "title": "Add Curve Between" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", + "sourcetype": "python", "tags": [ "rigging", - "mirrorCurveShape" + "averageSkinWeights", + "python" ], - "title": "mirrorCurveShape", - "tooltip": "" + "title": "Average Skin Weights" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", + "sourcetype": "python", "tags": [ "rigging", - "selectSkinclusterJointsFromSelectedMesh" + "cbSmoothSkinWeightUI", + "python" ], - "title": "selectSkinclusterJointsFromSelectedMesh", - "tooltip": "" + "title": "CB Smooth Skin Weight UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", + "sourcetype": "python", "tags": [ "rigging", - "channelBoxManagerUI" + "channelBoxManagerUI", + "python" ], - "title": "channelBoxManagerUI", - "tooltip": "" + "title": "Channel Box Manager UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", + "sourcetype": "python", "tags": [ "rigging", - "setJointOrientationFromCurrentRotation" + "characterAutorigger", + "python" ], - "title": "setJointOrientationFromCurrentRotation", - "tooltip": "" + "title": "Character Auto Rigger" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", + "sourcetype": "python", "tags": [ "rigging", - "regenerate_blendshape_targets" + "connectUI", + "python" ], - "title": "regenerate_blendshape_targets", - "tooltip": "" + "title": "Connect UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", + "sourcetype": "python", "tags": [ "rigging", - "freezeTransformToGroup" + "copySkinWeightsLocal", + "python" ], - "title": "freezeTransformToGroup", - "tooltip": "" + "title": "Copy Skin Weights Local" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", + "sourcetype": "python", "tags": [ "rigging", - "selectSkinclusterJointsFromSelectedComponents" + "createCenterLocator", + "python" ], - "title": "selectSkinclusterJointsFromSelectedComponents", - "tooltip": "" + "title": "Create Center Locator" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", + "sourcetype": "python", "tags": [ "rigging", - "superRelativeCluster" + "freezeTransformToGroup", + "python" ], - "title": "superRelativeCluster", - "tooltip": "" + "title": "Freeze Transform To Group" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", + "sourcetype": "python", "tags": [ "rigging", - "special" + "groupSelected", + "python" ], - "title": "special", - "tooltip": "" + "title": "Group Selected" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", + "sourcetype": "python", "tags": [ "rigging", - "averageSkinWeights" + "ikHandlePoleVectorLocator", + "python" ], - "title": "averageSkinWeights", - "tooltip": "" + "title": "IK Handle Pole Vector Locator" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", + "sourcetype": "python", "tags": [ "rigging", - "connectUI" + "jointOrientUI", + "python" ], - "title": "connectUI", - "tooltip": "" + "title": "Joint Orient UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", + "sourcetype": "python", "tags": [ "rigging", - "ikHandlePoleVectorLocator" + "jointsOnCurve", + "python" ], - "title": "ikHandlePoleVectorLocator", - "tooltip": "" + "title": "Joints On Curve" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", + "sourcetype": "python", "tags": [ "rigging", - "toggleSegmentScaleCompensate" + "mirrorCurveShape", + "python" ], - "title": "toggleSegmentScaleCompensate", - "tooltip": "" + "title": "Mirror Curve Shape" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", + "sourcetype": "python", "tags": [ "rigging", - "addCurveBetween" + "paintItNowUI", + "python" ], - "title": "addCurveBetween", - "tooltip": "" + "title": "Paint It Now UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", + "sourcetype": "python", "tags": [ "rigging", - "resetBindSelectedMeshes" + "parentScaleConstraint", + "python" ], - "title": "resetBindSelectedMeshes", - "tooltip": "" + "title": "Parent Scale Constraint" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", + "sourcetype": "python", "tags": [ "rigging", - "rapidRig" + "quickSetWeightsUI", + "python" ], - "title": "rapidRig", - "tooltip": "" + "title": "Quick Set Weights UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", + "sourcetype": "python", "tags": [ "rigging", - "paintItNowUI" + "rapidRig", + "python" ], - "title": "paintItNowUI", - "tooltip": "" + "title": "rapidRig" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", + "sourcetype": "python", "tags": [ "rigging", - "toggleSkinclusterDeformNormals" + "regenerate_blendshape_targets", + "python" ], - "title": "toggleSkinclusterDeformNormals", - "tooltip": "" + "title": "Regenerate Blendshape Targets" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", + "sourcetype": "python", "tags": [ "rigging", - "setSelectedJointsOrientationZero" + "removeRotationAxis", + "python" ], - "title": "setSelectedJointsOrientationZero", - "tooltip": "" + "title": "Remove Rotation Axis" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", + "sourcetype": "python", "tags": [ "rigging", - "groupSelected" + "resetBindSelectedMeshes", + "python" ], - "title": "groupSelected", - "tooltip": "" + "title": "Reset Bind Selected Meshes" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "sourcetype": "python", "tags": [ "rigging", - "simpleControllerOnSelection" + "resetBindSelectedSkinJoints", + "python" ], - "title": "simpleControllerOnSelection", - "tooltip": "" + "title": "Reset Bind Selected Skin Joints" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "sourcetype": "python", "tags": [ "rigging", - "resetBindSelectedSkinJoints" + "selectSkinclusterJointsFromSelectedComponents", + "python" ], - "title": "resetBindSelectedSkinJoints", - "tooltip": "" + "title": "Select Skincluster Joints From Selected Components" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "sourcetype": "python", "tags": [ "rigging", - "quickSetWeightsUI" + "selectSkinclusterJointsFromSelectedMesh", + "python" ], - "title": "quickSetWeightsUI", - "tooltip": "" + "title": "Select Skincluster Joints From Selected Mesh" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", + "sourcetype": "python", "tags": [ "rigging", - "simpleControllerOnSelectionHierarchy" + "setJointLabels", + "python" ], - "title": "simpleControllerOnSelectionHierarchy", - "tooltip": "" + "title": "setJointLabels" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "sourcetype": "python", "tags": [ "rigging", - "setRotationOrderUI" + "setJointOrientationFromCurrentRotation", + "python" ], - "title": "setRotationOrderUI", - "tooltip": "" + "title": "Set Joint Orientation From Current Rotation" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", + "sourcetype": "python", "tags": [ "rigging", - "jointOrientUI" + "setRotationOrderUI", + "python" ], - "title": "jointOrientUI", - "tooltip": "" + "title": "Set Rotation Order UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "sourcetype": "python", "tags": [ "rigging", - "copySkinWeightsLocal" + "setSelectedJointsOrientationZero", + "python" ], - "title": "copySkinWeightsLocal", - "tooltip": "" + "title": "Set Selected Joints Orientation Zero" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", + "sourcetype": "python", "tags": [ "rigging", - "tfSmoothSkinWeight" + "simpleControllerOnSelection", + "python" ], - "title": "tfSmoothSkinWeight", - "tooltip": "" + "title": "Simple Controller On Selection" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", + "sourcetype": "python", "tags": [ "rigging", - "characterAutorigger" + "simpleControllerOnSelectionHierarchy", + "python" ], - "title": "characterAutorigger", - "tooltip": "" + "title": "Simple Controller On Selection Hierarchy" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", + "sourcetype": "python", "tags": [ "rigging", - "jointsOnCurve" + "superRelativeCluster", + "python" ], - "title": "jointsOnCurve", - "tooltip": "" + "title": "Super Relative Cluster" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", + "sourcetype": "python", "tags": [ "rigging", - "parentScaleConstraint" + "tfSmoothSkinWeight", + "python" ], - "title": "parentScaleConstraint", - "tooltip": "" + "title": "TF Smooth Skin Weight" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", + "sourcetype": "python", "tags": [ "rigging", - "rigRoss" + "toggleIntermediates", + "python" ], - "title": "rigRoss", - "tooltip": "" + "title": "Toggle Intermediates" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", + "sourcetype": "python", "tags": [ "rigging", - "createCenterLocator" + "toggleSegmentScaleCompensate", + "python" ], - "title": "createCenterLocator", - "tooltip": "" + "title": "Toggle Segment Scale Compensate" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", + "sourcetype": "python", "tags": [ "rigging", - "setJointLabels" + "toggleSkinclusterDeformNormals", + "python" ], - "title": "setJointLabels", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "rigging", - "cbSmoothSkinWeightUI" - ], - "title": "cbSmoothSkinWeightUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleIntermediates" - ], - "title": "toggleIntermediates", - "tooltip": "" + "title": "Toggle Skincluster Deform Normals" } ], - "Shading": [ { "command": "", "sourcetype": "file", - "tags": ["shading", "vray"], + "tags": [ + "shading", + "vray" + ], "title": "VRay", "tooltip": "", "items": [ @@ -675,59 +784,107 @@ "title": "Import Proxies", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", "sourcetype": "file", - "tags": ["shading", "vray", "import","proxies"], + "tags": [ + "shading", + "vray", + "import", + "proxies" + ], "tooltip": "" }, - {"title": "separator"}, + { + "title": "separator" + }, { "title": "Select All GES", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "selectAllGES.py"] + "tags": [ + "shading", + "vray", + "selectAllGES.py" + ] }, { "title": "Select All GES Under Selection", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "selection","all", "GES"] + "tags": [ + "shading", + "vray", + "selection", + "all", + "GES" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Selection To VRay Mesh", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "selection","vraymesh"] + "tags": [ + "shading", + "vray", + "selection", + "vraymesh" + ] }, { "title": "Add VRay Round Edges Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "round edges", "attribute"] + "tags": [ + "shading", + "vray", + "round edges", + "attribute" + ] }, { "title": "Add Gamma", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "add gamma"] + "tags": [ + "shading", + "vray", + "add gamma" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", "sourcetype": "file", "title": "Select Unconnected Shader Materials", - "tags": ["shading", "vray", "select", "vraymesh", "materials", "unconnected shader slots"], + "tags": [ + "shading", + "vray", + "select", + "vraymesh", + "materials", + "unconnected shader slots" + ], "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", "sourcetype": "file", "title": "Merge Similar VRay Mesh Materials", - "tags": ["shading", "vray", "Merge","VRayMesh", "Materials"], + "tags": [ + "shading", + "vray", + "Merge", + "VRayMesh", + "Materials" + ], "tooltip": "" }, { @@ -735,52 +892,94 @@ "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", "sourcetype": "file", "tooltip": "Creates two sided material for selected material and renames it", - "tags": ["shading", "vray", "two sided", "material"] + "tags": [ + "shading", + "vray", + "two sided", + "material" + ] }, { "title": "Create Two Sided Material For Selected", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", "sourcetype": "file", "tooltip": "Select material to create a two sided version from it", - "tags": ["shading", "vray", "Create2SidedMtlForSelectedMtl.py"] + "tags": [ + "shading", + "vray", + "Create2SidedMtlForSelectedMtl.py" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Add OpenSubdiv Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "add", "open subdiv", "attribute"] + "tags": [ + "shading", + "vray", + "add", + "open subdiv", + "attribute" + ] }, { "title": "Remove OpenSubdiv Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "remove","opensubdiv","attributee"] + "tags": [ + "shading", + "vray", + "remove", + "opensubdiv", + "attributee" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Add Subdivision Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "addVraySubdivisionAttribute"] + "tags": [ + "shading", + "vray", + "addVraySubdivisionAttribute" + ] }, { "title": "Remove Subdivision Attribute.py", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "remove","subdivision", "attribute"] + "tags": [ + "shading", + "vray", + "remove", + "subdivision", + "attribute" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Add Vray Object Ids", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "add", "object id"] + "tags": [ + "shading", + "vray", + "add", + "object id" + ] }, { "title": "Add Vray Material Ids", @@ -793,20 +992,32 @@ "addVrayMaterialIds.py" ] }, - {"title": "separator"}, + { + "title": "separator" + }, { "title": "Set Physical DOF Depth", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "physical","DOF ","Depth"] + "tags": [ + "shading", + "vray", + "physical", + "DOF ", + "Depth" + ] }, { "title": "Magic Vray Proxy UI", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "magicVrayProxyUI"] + "tags": [ + "shading", + "vray", + "magicVrayProxyUI" + ] } ] }, @@ -823,14 +1034,24 @@ { "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", "sourcetype": "file", - "tags": ["shading", "CLRImage", "textures", "preview"], + "tags": [ + "shading", + "CLRImage", + "textures", + "preview" + ], "title": "Set Texture Preview To CLRImage", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior", "sourcetype": "file", - "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], + "tags": [ + "shading", + "fix", + "DefaultShaderSet", + "Behavior" + ], "title": "fixDefaultShaderSetBehavior", "tooltip": "" }, @@ -875,7 +1096,6 @@ "tooltip": "" } ], - "Layout": [ { "command": "", @@ -998,7 +1218,6 @@ "tooltip": "" } ], - "Particles": [ { "command": "", @@ -1141,7 +1360,6 @@ "tooltip": "" } ], - "Cleanup": [ { "command": "", @@ -1314,7 +1532,6 @@ "tooltip": "" } ], - "Projects": [ { "command": "", @@ -1397,7 +1614,6 @@ "tooltip": "" } ], - "Pyblish": [ { "command": "", @@ -1500,7 +1716,6 @@ "tooltip": "" } ], - "Others": [ { "command": "", diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 5349284e63..4b09f87f1b 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -30,9 +30,11 @@ def deferred(): config_path = os.path.join(os.path.dirname(__file__), "menu.json") config = menu.load_configuration(config_path) - # hack? + # get Maya menubar parent = launchformaya._maya_main_menubar() - cb_menu = menu.ScriptsMenu(title=self._menu.title(), parent=parent) + cb_menu = menu.ScriptsMenu(objectName=self._menu, + title=self._menu.title(), + parent=parent) # register modifiers modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier From 7e243228f4abbf6a5b3cc5ac4c8938d2ff02e2b9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:53:38 +0200 Subject: [PATCH 10/31] changed order numder --- colorbleed/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 5adf7dbe24..63a8b0e278 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -1,6 +1,7 @@ import tempfile import pyblish.api + ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 @@ -17,7 +18,7 @@ class Extractor(pyblish.api.InstancePlugin): """ - order = pyblish.api.ExtractorOrder + order = 2.0 def staging_dir(self, instance): """Provide a temporary directory in which to store extracted files From bd240bb7267db8c54f1ae55727b2e9f20538f8c1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:54:55 +0200 Subject: [PATCH 11/31] added texture instances, look no longer has suffix INST --- colorbleed/plugins/maya/create/colorbleed_look.py | 2 +- colorbleed/plugins/maya/create/colorbleed_texture.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index b93673b523..3cfb7423c3 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -7,7 +7,7 @@ class CreateLook(avalon.maya.Creator): name = "lookDefault" label = "Look Dev" - family = "colorbleed.look" + family = "colorbleed.lookdev" def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_texture.py b/colorbleed/plugins/maya/create/colorbleed_texture.py index e69de29bb2..1080f1c6a1 100644 --- a/colorbleed/plugins/maya/create/colorbleed_texture.py +++ b/colorbleed/plugins/maya/create/colorbleed_texture.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateTexture(avalon.maya.Creator): + """Polygonal geometry for animation""" + + name = "texturesDefault" + label = "Textures" + family = "colorbleed.texture" \ No newline at end of file From 22ffd263fe4fab69971442a381bfdfcf1a8b256a Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 15:51:02 +0200 Subject: [PATCH 12/31] shot name is now set as asset in instance when rig is loaded in animation --- colorbleed/plugins/maya/load/load_rig.py | 36 ++++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 1332e63f4f..0138f56b76 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -1,4 +1,7 @@ +import os + from maya import cmds + from avalon import api @@ -18,12 +21,13 @@ class RigLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): + print("debug : {}\n{}\n".format(name, namespace)) nodes = cmds.file(self.fname, namespace=namespace, reference=True, returnNewNodes=True, groupReference=True, - groupName=namespace + ":" + name) + groupName="{}:{}".format(namespace, name)) # Store for post-process self[:] = nodes @@ -38,6 +42,7 @@ class RigLoader(api.Loader): # Better register this keyword, so that it can be used # elsewhere, such as in the Integrator plug-in, # without duplication. + output = next( (node for node in self if node.endswith("out_SET")), None) @@ -48,18 +53,25 @@ class RigLoader(api.Loader): assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." + # To ensure the asset under which is published is actually the shot + # not the asset to which the rig belongs to. + current_task = os.environ["AVALON_TASK"] + asset_name = context["asset"]["name"] + if current_task == "animate": + asset = "{}".format(os.environ["AVALON_ASSET"]) + else: + asset = "{}".format(asset_name) + with maya.maintained_selection(): cmds.select([output, controls], noExpand=True) - dependencies = [context["representation"]["_id"]] - asset = context["asset"]["name"] + "_" - # TODO(marcus): Hardcoding the family here, better separate this. - maya.create( - name=maya.unique_name(asset, suffix="_SET"), - asset=context["asset"]["name"], - family="colorbleed.animation", - options={"useSelection": True}, - data={ - "dependencies": " ".join(str(d) for d in dependencies) - }) + dependencies = [context["representation"]["_id"]] + dependencies = " ".join(str(d) for d in dependencies) + unique_name = maya.unique_name(asset_name, suffix="_SET") + + maya.create(name=unique_name, + asset=asset, + family="colorbleed.animation", + options={"useSelection": True}, + data={"dependencies": dependencies}) From bf8edf8d6e671c4b78cba3bf9b79be067a884522 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 21 Jul 2017 11:08:22 +0200 Subject: [PATCH 13/31] added cbId to attribute argumen --- ..._zero_edge.py => _validate_mesh_non_zero_edge.py} | 12 +++++------- colorbleed/plugins/maya/publish/extract_alembic.py | 9 ++++++++- 2 files changed, 13 insertions(+), 8 deletions(-) rename colorbleed/plugins/maya/publish/{validate_mesh_non_zero_edge.py => _validate_mesh_non_zero_edge.py} (83%) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py b/colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py similarity index 83% rename from colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py rename to colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py index b706913592..02019da523 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py +++ b/colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py @@ -2,8 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api - -from pyblish_cb.tmp_utils import polyConstraint +import colorbleed.maya.lib as lib class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @@ -41,17 +40,16 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): edges = ['{0}.e[*]'.format(node) for node in meshes] # Filter by constraint on edge length - invalid = polyConstraint(edges, - t=0x8000, # type=edge - length=1, - lengthbound=(0, cls.__tolerance)) + invalid = lib.polyConstraint(edges, + t=0x8000, # type=edge + length=1, + lengthbound=(0, cls.__tolerance)) return invalid def process(self, instance): """Process all meshes""" invalid = self.get_invalid(instance) - if invalid: raise RuntimeError("Meshes found with zero " "edge length: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 2bde0bfe9a..83a87e7a6b 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -1,6 +1,8 @@ import os import copy +import maya.cmds as cmds + import avalon.maya import colorbleed.api from colorbleed.maya.lib import extract_alembic @@ -26,9 +28,14 @@ class ExtractAlembic(colorbleed.api.Extractor): path = os.path.join(parent_dir, filename) options = copy.deepcopy(instance.data) - options['selection'] = True + options["attr"] = ["cbId"] + # force elect items to ensure all items get exported by Alembic + members = instance.data("setMembers") + cmds.select(members) with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): extract_alembic(file=path, **options) + + cmds.select(clear=True) From c8cfc53f11e52324072c7cd5b4566dffbd8a3d7d Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 21 Jul 2017 14:20:26 +0200 Subject: [PATCH 14/31] removed unsupported flag from extract alembic call, added cbId to attr flag --- .../plugins/maya/publish/extract_alembic.py | 2 ++ .../plugins/maya/publish/extract_animation.py | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 83a87e7a6b..c3abe59244 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -33,6 +33,8 @@ class ExtractAlembic(colorbleed.api.Extractor): # force elect items to ensure all items get exported by Alembic members = instance.data("setMembers") + print members + cmds.select(members) with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index 48cb4711dc..f0241a79b4 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -1,4 +1,10 @@ +import os + +from maya import cmds + +import avalon.maya import colorbleed.api +from colorbleed.maya.lib import extract_alembic class ExtractColorbleedAnimation(colorbleed.api.Extractor): @@ -14,10 +20,6 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): families = ["colorbleed.animation"] def process(self, instance): - import os - from maya import cmds - import avalon.maya - from colorbleed.maya.lib import extract_alembic # Collect the out set nodes out_sets = [node for node in instance if node.endswith("out_SET")] @@ -27,11 +29,13 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): out_set = out_sets[0] nodes = cmds.sets(out_set, query=True) - # Include all descendents + # Include all descendants nodes += cmds.listRelatives(nodes, allDescendents=True, fullPath=True) or [] + print("Exporting {} as alembic".format(nodes)) + # Collect the start and end including handles start = instance.data["startFrame"] end = instance.data["endFrame"] @@ -52,13 +56,10 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): cmds.select(nodes, noExpand=True) - extract_alembic(file=path, **{ - "selection": True, - "frameRange": (start, end), - "writeVisibility": True, - "writeUV": True, - "step": instance.data.get("step", 1.0), - "attributePrefix": ("mb",) - }) + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **{"step": instance.data.get("step", 1.0), + "attr": ["cbId"]}) self.log.info("Extracted {} to {}".format(instance, dirname)) From 6a6335c7137992c79187c71f70172c7224e3ef59 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:27:53 +0200 Subject: [PATCH 15/31] added assign_look logic --- colorbleed/maya/lib.py | 254 +++++++++++++++++- ...bic_attrs.py => _collect_alembic_attrs.py} | 0 2 files changed, 250 insertions(+), 4 deletions(-) rename colorbleed/plugins/maya/publish/{collect_alembic_attrs.py => _collect_alembic_attrs.py} (100%) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 0ae2d442c8..b4e4dec544 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1,17 +1,26 @@ """Standalone helper functions""" import re -import contextlib -from collections import OrderedDict -import logging import os +import bson import json +import logging +import contextlib +from collections import OrderedDict, defaultdict + +from avalon import maya, io from maya import cmds, mel log = logging.getLogger(__name__) +project = io.find_one({"type": "project", + "name": os.environ["AVALON_PROJECT"]}, + projection={"config.template.publish": True, + "_id": False}) +TEMPLATE = project["config"]["template"]["publish"] + def maintained_selection(arg=None): if arg is not None: @@ -522,7 +531,7 @@ def extract_alembic(file, valid_types = _alembic_options[key] if not isinstance(value, valid_types): raise TypeError("Alembic option unsupported type: " - "{0} (expected {1}}".format(value, valid_types)) + "{0} (expected {1})".format(value, valid_types)) # Format the job string from options job_args = list() @@ -549,6 +558,8 @@ def extract_alembic(file, log.debug("Extracting Alembic with job arguments: %s", job_str) # Perform extraction + print("Alembic Job Arguments : {}".format(job_str)) + cmds.AbcExport(j=job_str, verbose=verbose) if verbose: @@ -582,3 +593,238 @@ def remap_resource_nodes(resources, folder=None): log.info("Saving file ...") cmds.file(save=True, type="mayaAscii") + + +def _get_id(node): + """ + Get the `cbId` attribute of the given node + Args: + node (str): the name of the node to retrieve the attribute from + + Returns: + str + + """ + + if node is None: + return + + try: + attr = "{}.cbId".format(node) + attribute_value = cmds.getAttr(attr) + except Exception as e: + log.warning(e) + return + + return attribute_value + + +def filter_by_id(nodes, uuids): + """Filter all nodes which match the UUIDs + + Args: + nodes (list): collection of nodes to check + uuids (list): a list of UUIDs which are linked to the shader + + Returns: + list: matching nodes + """ + + filtered_nodes = [] + for node in nodes: + if node is None: + continue + + if not cmds.attributeQuery("cbId", node=node, exists=True): + continue + + # Deformed shaped + attr = "{}.cbId".format(node) + attribute_value = cmds.getAttr(attr) + if attribute_value not in uuids: + continue + + filtered_nodes.append(node) + + return filtered_nodes + + +def get_representation_file(representation, template=TEMPLATE): + """ + Rebuild the filepath of the representation's context + Args: + representation (dict): data of the registered in the database + template (str): the template to fill + + Returns: + str + + """ + context = representation["context"].copy() + context["root"] = os.environ["AVALON_ROOT"] + return template.format(**context) + + +def list_looks(asset_id): + """Return all look subsets for the given asset + + This assumes all look subsets start with "look*" in their names. + """ + + # # get all subsets with look leading in + # the name associated with the asset + subset = io.find({"parent": asset_id, + "type": "subset", + "name": {"$regex": "look*"}}) + + return list(subset) + + +def assign_look_by_version(nodes, version_id): + """Assign nodes a specific published look version by id. + + This assumes the nodes correspond with the asset. + + Args: + nodes(list): nodes to assign look to + version_id (bson.ObjectId) + + Returns: + None + """ + + # get representations of shader file and relationships + shader_file = io.find_one({"type": "representation", + "parent": version_id, + "name": "ma"}) + + shader_relations = io.find_one({"type": "representation", + "parent": version_id, + "name": "json"}) + + # Load file + shader_filepath = get_representation_file(shader_file) + shader_relation = get_representation_file(shader_relations) + + try: + existing_reference = cmds.file(shader_filepath, + query=True, + referenceNode=True) + except RuntimeError as e: + if e.message.rstrip() != "Cannot find the scene file.": + raise + + log.info("Loading lookdev for the first time..") + + # Define namespace + assetname = shader_file['context']['asset'] + ns_assetname = "{}_".format(assetname) + namespace = maya.unique_namespace(ns_assetname, + format="%03d", + suffix="_look") + + # Reference the look file + with maya.maintained_selection(): + shader_nodes = cmds.file(shader_filepath, + namespace=namespace, + reference=True, + returnNewNodes=True) + else: + log.info("Reusing existing lookdev..") + shader_nodes = cmds.referenceQuery(existing_reference, nodes=True) + + # Assign relationships + with open(shader_relation, "r") as f: + relationships = json.load(f) + + apply_shaders(relationships, shader_nodes, nodes) + + +def assign_look(nodes, subset="lookDefault"): + """Assigns a look to a node. + + Optimizes the nodes by grouping by asset id and finding + related subset by name. + + Args: + nodes (list): all nodes to assign the look to + subset (str): name of the subset to find + """ + + # Group all nodes per asset id + grouped = defaultdict(list) + for node in nodes: + colorbleed_id = cmds.getAttr("{}.cbId".format(node)) + asset_id = colorbleed_id.split(":")[0] + grouped[asset_id].append(node) + + for asset_id, asset_nodes in grouped.items(): + # create objectId for database + asset_id = bson.ObjectId(asset_id) + subset = io.find_one({"type": "subset", + "name": subset, + "parent": asset_id}) + + assert subset, "No subset found for {}".format(asset_id) + + # get last version + version = io.find_one({"parent": subset['_id'], + "type": "version", + "data.families": + {"$in":["colorbleed.lookdev"]} + }, + sort=[("name", -1)], + projection={"_id": True}) + + log.debug("Assigning look '{}' <{}> to nodes: {}".format(subset, + version, + asset_nodes)) + + assign_look_by_version(asset_nodes, version['_id']) + + +def apply_shaders(relationships, shader_nodes, nodes): + """Apply all shaders to the nodes based on the relationship data + + Args: + relationships (dict): shader to node relationships + shader_nodes (list): shader network nodes + nodes (list): nodes to assign to + + Returns: + None + """ + + shader_sets = relationships.get("sets", []) + shading_engines = cmds.ls(shader_nodes, type="shadingEngine", long=True) + assert len(shading_engines) > 0, ("Error in retrieving shading engine " + "from reference") + + # Pre-filter nodes and shader nodes + nodes_by_id = defaultdict(list) + shader_nodes_by_id = defaultdict(list) + for node in nodes: + _id = _get_id(node) + nodes_by_id[_id].append(node) + + for shader_node in shader_nodes: + _id = _get_id(shader_node) + shader_nodes_by_id[_id].append(shader_node) + + # get all nodes which we need to link per shader + for shader_set in shader_sets: + # collect shading engine + uuid = shader_set["uuid"] + shading_engine = shader_nodes_by_id.get(uuid, []) + assert len(shading_engine) == 1, ("Could not find the correct " + "shading engine with cbId " + "'{}'".format(uuid)) + + # collect members + filtered_nodes = list() + for member in shader_set["members"]: + member_uuid = member["uuid"] + members = nodes_by_id.get(member_uuid, []) + filtered_nodes.extend(members) + + cmds.sets(filtered_nodes, forceElement=shading_engine[0]) diff --git a/colorbleed/plugins/maya/publish/collect_alembic_attrs.py b/colorbleed/plugins/maya/publish/_collect_alembic_attrs.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_alembic_attrs.py rename to colorbleed/plugins/maya/publish/_collect_alembic_attrs.py From e0fa549f0b3ab5797a9a12760ec8dea12ea97f83 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:32:56 +0200 Subject: [PATCH 16/31] separated database logic from move logic --- colorbleed/plugins/publish/integrate_asset.py | 258 +++--------------- colorbleed/plugins/publish/pre_integrate.py | 257 ++++++++++++++--- 2 files changed, 264 insertions(+), 251 deletions(-) diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py index 457f0cfa67..7db26fdaab 100644 --- a/colorbleed/plugins/publish/integrate_asset.py +++ b/colorbleed/plugins/publish/integrate_asset.py @@ -1,13 +1,15 @@ +import json import os import errno import shutil -from pprint import pformat + +import colorbleed.maya.lib as lib import pyblish.api -from avalon import api, io +from avalon import io -class IntegrateMindbenderAsset(pyblish.api.InstancePlugin): +class IntegrateAsset(pyblish.api.InstancePlugin): """Write to files and metadata This plug-in exposes your data to others by encapsulating it @@ -30,225 +32,53 @@ class IntegrateMindbenderAsset(pyblish.api.InstancePlugin): """ label = "Integrate Asset" - order = pyblish.api.IntegratorOrder - families = [ - "colorbleed.model", - "colorbleed.rig", - "colorbleed.animation", - "colorbleed.camera", - "colorbleed.lookdev", - "colorbleed.historyLookdev", - "colorbleed.group" - ] + order = pyblish.api.IntegratorOrder + 0.1 + families = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.lookdev", + "colorbleed.texture", + "colorbleed.historyLookdev", + "colorbleed.group"] def process(self, instance): - # Required environment variables - 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") - # todo(marcus): avoid hardcoding labels in the integrator - representation_labels = {".ma": "Maya Ascii", - ".source": "Original source file", - ".abc": "Alembic"} + # get needed data + traffic = instance.data["traffic"] + representations = instance.data["representations"] + version_folder = instance.data["versionFolder"] + family = instance.data["family"] + resources = instance.data("resources", []) - context = instance.context - # Atomicity - # - # Guarantee atomic publishes - each asset contains - # an identical set of members. - # __ - # / o - # / \ - # | o | - # \ / - # o __/ - # - assert all(result["success"] for result in context.data["results"]), ( - "Atomicity not held, aborting.") + self.log.info("Registering {} items".format(len(representations))) + io.insert_many(representations) - # Assemble - # - # | - # v - # ---> <---- - # ^ - # | - # - stagingdir = instance.data.get("stagingDir") - assert stagingdir, ("Incomplete instance \"%s\": " - "Missing reference to staging area." % instance) + # moving files + for src_dest in traffic: + src, dest = src_dest + self.log.info("Copying file .. {} -> {}".format(src, dest)) + self.copy_file(src, dest) - self.log.debug("Establishing staging directory @ %s" % stagingdir) + if family == "colorbleed.texture": + try: + lib.remap_resource_nodes(resources, folder=version_folder) + except Exception as e: + self.log.error(e) - project = io.find_one({"type": "project"}) - asset = io.find_one({"name": ASSET}) + if family == "colorbleed.lookdev": + try: + tmp_dir = lib.maya_temp_folder() + resource_file = os.path.join(tmp_dir, "resources.json") + with open(resource_file, "r") as f: + resources = json.load(f) + lib.remap_resource_nodes(resources) + except Exception as e: + self.log.error(e) - assert all([project, asset]), ("Could not find current project or " - "asset '%s'" % ASSET) - - subset = self.get_subset(asset, instance) - - # get next version - latest_version = io.find_one({"type": "version", - "parent": subset["_id"]}, - {"name": True}, - sort=[("name", -1)]) - - next_version = 1 - if latest_version is not None: - next_version += latest_version["name"] - - self.log.debug("Next version: %i" % next_version) - - version_data = self.create_version_data(context, instance) - version = self.create_version(subset=subset, - version_number=next_version, - locations=[LOCATION], - data=version_data) - - self.log.debug("Creating version: %s" % pformat(version)) - version_id = io.insert_one(version).inserted_id - - # Write to disk - # _ - # | | - # _| |_ - # ____\ / - # |\ \ / \ - # \ \ v \ - # \ \________. - # \|________| - # - template_data = { - "root": api.registered_root(), - "project": PROJECT, - "silo": SILO, - "asset": ASSET, - "subset": subset["name"], - "version": version["name"], - } - - template_publish = project["config"]["template"]["publish"] - - for fname in os.listdir(stagingdir): - name, ext = os.path.splitext(fname) - template_data["representation"] = ext[1:] - - src = os.path.join(stagingdir, fname) - dst = template_publish.format(**template_data) - - # Backwards compatibility - if fname == ".metadata.json": - dirname = os.path.dirname(dst) - dst = os.path.join(dirname, ".metadata.json") - - self.log.info("Copying %s -> %s" % (src, dst)) - - # copy source to destination (library) - self.copy_file(src, dst) - - representation = { - "schema": "avalon-core:representation-2.0", - "type": "representation", - "parent": version_id, - "name": ext[1:], - "data": {"label": representation_labels.get(ext)}, - "dependencies": instance.data.get("dependencies", "").split(), - - # Imprint shortcut to context - # for performance reasons. - "context": { - "project": PROJECT, - "asset": ASSET, - "silo": SILO, - "subset": subset["name"], - "version": version["name"], - "representation": ext[1:] - } - } - - io.insert_one(representation) - - self.log.info("Successfully integrated \"%s\" to \"%s\"" % ( - instance, dst)) - - def get_subset(self, asset, instance): - - subset = io.find_one({"type": "subset", - "parent": asset["_id"], - "name": instance.data["subset"]}) - - if subset is None: - subset_name = instance.data["subset"] - self.log.info("Subset '%s' not found, creating.." % subset_name) - - _id = io.insert_one({ - "schema": "avalon-core:subset-2.0", - "type": "subset", - "name": subset_name, - "data": {}, - "parent": asset["_id"] - }).inserted_id - - subset = io.find_one({"_id": _id}) - - return subset - - def create_representation(self): - pass - - def create_version(self, subset, version_number, locations, data=None): - """ Copy given source to destination - - Arguments: - subset (dict): the registered subset of the asset - version_number (int): the version number - locations (list): the currently registered locations - """ - # Imprint currently registered location - version_locations = [location for location in locations if - location is not None] - - return {"schema": "avalon-core:version-2.0", - "type": "version", - "parent": subset["_id"], - "name": version_number, - "locations": version_locations, - "data": data} - - def create_version_data(self, context, instance): - """ - Create the data collection for th version - Args: - context (object): the current context - instance(object): the current instance being published - - Returns: - dict: the required information with instance.data as key - """ - - families = [] - current_families = instance.data.get("families", list()) - instance_family = instance.data.get("family", None) - - families += current_families - if instance_family is not None: - families.append(instance_family) - - # create relative source path for DB - relative_path = os.path.relpath(context.data["currentFile"], - api.registered_root()) - source = os.path.join("{root}", relative_path).replace("\\", "/") - - version_data = {"families": families, - "time": context.data["time"], - "author": context.data["user"], - "source": source, - "comment": context.data.get("comment")} - - return dict(instance.data, **version_data) + self.log.info("Removing temporary files and folders ...") + stagingdir = instance.data["stagingDir"] + shutil.rmtree(stagingdir) def copy_file(self, src, dst): """ Copy given source to destination diff --git a/colorbleed/plugins/publish/pre_integrate.py b/colorbleed/plugins/publish/pre_integrate.py index cb5eaa0656..a6c798914d 100644 --- a/colorbleed/plugins/publish/pre_integrate.py +++ b/colorbleed/plugins/publish/pre_integrate.py @@ -1,15 +1,15 @@ import os import logging -import shutil - -import maya.cmds as cmds import pyblish.api +from avalon import api, io +import colorbleed.filetypes as filetypes + log = logging.getLogger(__name__) -class PostIntegrateAsset(pyblish.api.InstancePlugin): +class PreIntegrateAsset(pyblish.api.InstancePlugin): """Resolve any dependency issies This plug-in resolves any paths which, if not updated might break @@ -20,49 +20,232 @@ class PostIntegrateAsset(pyblish.api.InstancePlugin): publish the shading network. Same goes for file dependent assets. """ - label = "Post Intergrate Asset" - order = pyblish.api.IntegratorOrder + 0.1 - families = ["colorbleed.lookdev", "colorbleed.texture"] + label = "Pre Intergrate Asset" + order = pyblish.api.IntegratorOrder + families = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.lookdev", + "colorbleed.texture", + "colorbleed.historyLookdev", + "colorbleed.group"] def process(self, instance): - # get needed variables - version_folder = instance.data["versionFolder"] - family = instance.data["family"] - resources = instance.data("resources", []) + # Required environment variables + 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") - self.log.info("Running post process for {}".format(instance.name)) + # todo(marcus): avoid hardcoding labels in the integrator + representation_labels = {".ma": "Maya Ascii", + ".source": "Original source file", + ".abc": "Alembic"} - if family == "colorbleed.texture": - texture_folder = os.path.join(version_folder, "textures") - self.remap_resource_nodes(resources, folder=texture_folder) + context = instance.context + # Atomicity + # + # Guarantee atomic publishes - each asset contains + # an identical set of members. + # __ + # / o + # / \ + # | o | + # \ / + # o __/ + # + assert all(result["success"] for result in context.data["results"]), ( + "Atomicity not held, aborting.") - elif family == "colorbleed.lookdev": - self.remap_resource_nodes(resources) + # Assemble + # + # | + # v + # ---> <---- + # ^ + # | + # + stagingdir = instance.data.get("stagingDir") + assert stagingdir, ("Incomplete instance \"%s\": " + "Missing reference to staging area." % instance) - # self.log.info("Removing temporary files and folders ...") - # if passed: - # stagingdir = instance.data["stagingDir"] - # shutil.rmtree(stagingdir) + # extra check if stagingDir actually exists and is available - def remap_resource_nodes(self, resources, folder=None): + self.log.debug("Establishing staging directory @ %s" % stagingdir) - self.log.info("Updating resource nodes ...") - for resource in resources: - source = resource["source"] - if folder: - fname = os.path.basename(source) - fpath = os.path.join(folder, fname) - else: - fpath = source + project = io.find_one({"type": "project"}) + asset = io.find_one({"name": ASSET}) - node_attr = resource["attribute"] - print("UPDATING {} -> {}".format(node_attr, fpath)) - cmds.setAttr(node_attr, fpath, type="string") + assert all([project, asset]), ("Could not find current project or " + "asset '%s'" % ASSET) - self.log.info("Saving file ...") + subset = self.get_subset(asset, instance) - cmds.file(save=True, type="mayaAscii") + # get next version + latest_version = io.find_one({"type": "version", + "parent": subset["_id"]}, + {"name": True}, + sort=[("name", -1)]) - def remap_yeti_resource_nodes(self, node,): - pass + next_version = 1 + if latest_version is not None: + next_version += latest_version["name"] + + self.log.debug("Next version: %i" % next_version) + + version_data = self.create_version_data(context, instance) + version = self.create_version(subset=subset, + version_number=next_version, + locations=[LOCATION], + data=version_data) + + self.log.debug("Creating version ...") + version_id = io.insert_one(version).inserted_id + + # Write to disk + # _ + # | | + # _| |_ + # ____\ / + # |\ \ / \ + # \ \ v \ + # \ \________. + # \|________| + # + root = api.registered_root() + template_data = {"root": root, + "project": PROJECT, + "silo": SILO, + "asset": ASSET, + "subset": subset["name"], + "version": version["name"]} + + template_publish = project["config"]["template"]["publish"] + + representations = [] + traffic = [] + staging_content = os.listdir(stagingdir) + for v, fname in enumerate(staging_content): + + name, ext = os.path.splitext(fname) + template_data["representation"] = ext[1:] + + src = os.path.join(stagingdir, fname) + dst = template_publish.format(**template_data) + if v == 0: + instance.data["versionFolder"] = os.path.dirname(dst) + + # Files to copy as if or to specific folder + if ext in filetypes.accepted_images_types: + dirname = os.path.dirname(dst) + dst = os.path.join(dirname, fname) + + # Backwards compatibility + if fname == ".metadata.json": + dirname = os.path.dirname(dst) + dst = os.path.join(dirname, fname) + + # copy source to destination (library) + traffic.append([src, dst]) + + representation = { + "schema": "avalon-core:representation-2.0", + "type": "representation", + "parent": version_id, + "name": ext[1:], + "data": {"label": representation_labels.get(ext)}, + "dependencies": instance.data.get("dependencies", "").split(), + + # Imprint shortcut to context + # for performance reasons. + "context": { + "project": PROJECT, + "asset": ASSET, + "silo": SILO, + "subset": subset["name"], + "version": version["name"], + "representation": ext[1:] + } + } + representations.append(representation) + + # store data for database and source / destinations + instance.data["representations"] = representations + instance.data["traffic"] = traffic + + return representations + + def get_subset(self, asset, instance): + + subset = io.find_one({"type": "subset", + "parent": asset["_id"], + "name": instance.data["subset"]}) + + if subset is None: + subset_name = instance.data["subset"] + self.log.info("Subset '%s' not found, creating.." % subset_name) + + _id = io.insert_one({ + "schema": "avalon-core:subset-2.0", + "type": "subset", + "name": subset_name, + "data": {}, + "parent": asset["_id"] + }).inserted_id + + subset = io.find_one({"_id": _id}) + + return subset + + def create_version(self, subset, version_number, locations, data=None): + """ Copy given source to destination + + Arguments: + subset (dict): the registered subset of the asset + version_number (int): the version number + locations (list): the currently registered locations + """ + # Imprint currently registered location + version_locations = [location for location in locations if + location is not None] + + return {"schema": "avalon-core:version-2.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "locations": version_locations, + "data": data} + + def create_version_data(self, context, instance): + """Create the data collection for th version + + Args: + context: the current context + instance: the current instance being published + + Returns: + dict: the required information with instance.data as key + """ + + families = [] + current_families = instance.data.get("families", list()) + instance_family = instance.data.get("family", None) + + families += current_families + if instance_family is not None: + families.append(instance_family) + + # create relative source path for DB + relative_path = os.path.relpath(context.data["currentFile"], + api.registered_root()) + source = os.path.join("{root}", relative_path).replace("\\", "/") + + version_data = {"families": families, + "time": context.data["time"], + "author": context.data["user"], + "source": source, + "comment": context.data.get("comment")} + + return dict(instance.data, **version_data) From 9a70383051acfb7251d69e3c4de721b42dc11324 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:37:03 +0200 Subject: [PATCH 17/31] improved collectors --- .../plugins/maya/publish/collect_instances.py | 27 ++- .../plugins/maya/publish/collect_look.py | 74 ++++++++- .../plugins/maya/publish/collect_textures.py | 157 ++++++++++++++++-- 3 files changed, 233 insertions(+), 25 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 498b4fb30c..bbcd8a56cb 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -3,7 +3,7 @@ from maya import cmds import pyblish.api -class CollectMindbenderInstances(pyblish.api.ContextPlugin): +class CollectInstances(pyblish.api.ContextPlugin): """Gather instances by objectSet and pre-defined attribute This collector takes into account assets that are associated with @@ -38,14 +38,24 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): """ - label = "Collect Mindbender Instances" + label = "Collect Instances" order = pyblish.api.CollectorOrder hosts = ["maya"] + isntance_order = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.texture", + "colorbleed.lookdev", + "colorbleed.historyLookdev", + "colorbleed.group"] def process(self, context): + objectset = cmds.ls("*.id", long=True, type="objectSet", recursive=True, objectsOnly=True) for objset in objectset: + self.log.info("Creating instance for {}".format(objset)) members = cmds.sets(objset, query=True) if members is None: @@ -61,7 +71,8 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # The developer is responsible for specifying # the family of each instance. - has_family = cmds.attributeQuery("family", node=objset, + has_family = cmds.attributeQuery("family", + node=objset, exists=True) assert has_family, "\"%s\" was missing a family" % objset @@ -70,7 +81,7 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # Apply each user defined attribute as data for attr in cmds.listAttr(objset, userDefined=True) or list(): try: - value = cmds.getAttr("{}.{}".format(objset, attr)) + value = cmds.getAttr("%s.%s" % (objset, attr)) except Exception: # Some attributes cannot be read directly, # such as mesh and color attributes. These @@ -82,9 +93,10 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # Collect members members = cmds.ls(members, long=True) or [] + children = cmds.listRelatives(members, allDescendents=True, - fullPath=True) + fullPath=True) or [] parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) @@ -99,6 +111,10 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # user interface interested in visualising it. self.log.info("Found: \"%s\" " % instance.data["name"]) + context[:] = sorted(context) + + return context + def get_all_parents(self, nodes): """Get all parents by using string operations (optimization) @@ -108,6 +124,7 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): Returns: list """ + parents = [] for node in nodes: splitted = node.split("|") diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 72946d8588..eff9c29f55 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,9 +1,9 @@ from maya import cmds -from cb.utils.maya import context -import cbra.utils.maya.node_uuid as id_utils import pyblish.api +from cb.utils.maya import context, shaders +import cbra.utils.maya.node_uuid as id_utils SHAPE_ATTRS = ["castsShadows", "receiveShadows", @@ -37,8 +37,8 @@ def get_look_attrs(node): valid = [attr for attr in attrs if attr in SHAPE_ATTRS] result.extend(valid) - if "mbID" in result: - result.remove("mbID") + if "cbId" in result: + result.remove("cbId") return result @@ -62,7 +62,7 @@ class CollectLook(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.look"] + families = ["colorbleed.lookdev"] label = "Collect Look" hosts = ["maya"] @@ -120,6 +120,9 @@ class CollectLook(pyblish.api.InstancePlugin): instance.data["lookData"] = {"attributes": attributes, "relationships": sets.values(), "sets": looksets} + # Collect textures + resources = [self.collect_resources(n) for n in cmds.ls(type="file")] + instance.data["resources"] = resources # Log a warning when no relevant sets were retrieved for the look. if not instance.data["lookData"]["sets"]: @@ -157,7 +160,8 @@ class CollectLook(pyblish.api.InstancePlugin): for objset in related_sets: if objset in sets: continue - unique_id = cmds.getAttr("%s.mbID" % objset) + + unique_id = cmds.getAttr("%s.cbId" % objset) sets[objset] = {"name": objset, "uuid": unique_id, "members": list()} @@ -253,11 +257,24 @@ class CollectLook(pyblish.api.InstancePlugin): if member in [m["name"] for m in objset_members]: return + # check node type, if mesh get parent! makes assigning shaders easier + if cmds.nodeType(node) == "mesh": + parent = cmds.listRelatives(node, parent=True, fullPath=True) + # a mesh NEEDS to have a parent in Maya logic, no reason for + # assertions or extra checking + parent = parent[0] + if cmds.attributeQuery("cbId", node=parent, exists=True): + node = parent + else: + self.log.error("Transform group of mesh '{}' has no attribute " + "'cbId', this is manditory") + return + if verbose: self.log.debug("Such as %s.." % member) member_data = {"name": node, - "uuid": cmds.getAttr("{}.mbID".format(node, ))} + "uuid": cmds.getAttr("{}.cbId".format(node))} # Include components information when components are assigned if components: @@ -304,3 +321,46 @@ class CollectLook(pyblish.api.InstancePlugin): attributes.append(data) return attributes + + def collect_resources(self, node, verbose=False): + """Collect the link to the file(s) used (resource) + Args: + node (str): name of the node + verbose (bool): enable debug information + + Returns: + dict + """ + + attribute = "{}.fileTextureName".format(node) + source = cmds.getAttr(attribute) + + # Get the computed file path (e.g. the one with the pattern + # in it) So we can reassign it this computed file path whenever + # we need to. + computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_source = cmds.getAttr(computed_attribute) + if source != computed_source: + if verbose: + self.log.debug("File node computed pattern differs from " + "original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) + + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = computed_source.replace("\\", "/") + + files = shaders.get_file_node_files(node) + if not files: + self.log.error("File node does not have a texture set: " + "{0}".format(node)) + return + + # Define the resource + return {"node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files} # required for resources diff --git a/colorbleed/plugins/maya/publish/collect_textures.py b/colorbleed/plugins/maya/publish/collect_textures.py index a55e274da0..bc1e1ede0f 100644 --- a/colorbleed/plugins/maya/publish/collect_textures.py +++ b/colorbleed/plugins/maya/publish/collect_textures.py @@ -1,7 +1,6 @@ from maya import cmds import pyblish.api - import cb.utils.maya.shaders as shaders TAGS = ["maya", "attribute", "look"] @@ -72,23 +71,37 @@ class CollectLookTextures(pyblish.api.InstancePlugin): """ - order = pyblish.api.CollectorOrder + 0.498 - label = 'Textures' + order = pyblish.api.CollectorOrder + 0.35 + label = 'Collect Look Textures' families = ["colorbleed.texture"] actions = [SelectTextureNodesAction] + IGNORE = ["out_SET", "controls_SET", "_INST"] + def process(self, instance): verbose = instance.data.get("verbose", False) - # Get textures from sets - sets = instance.data["lookData"]["sets"] - if not sets: - raise RuntimeError("No look sets found for the nodes in the " - "instance. %s" % sets) + # Get all texture nodes from the shader networks + sets = self.gather_sets(instance) + instance_members = {str(i) for i in cmds.ls(instance, long=True, + absoluteName=True)} + + self.log.info("Gathering set relations..") + for objset in sets: + self.log.debug("From %s.." % objset) + content = cmds.sets(objset, query=True) + objset_members = sets[objset]["members"] + for member in cmds.ls(content, long=True, absoluteName=True): + member_data = self.collect_member_data(member, + objset_members, + instance_members, + verbose) + if not member_data: + continue # Get the file nodes - history = cmds.listHistory(sets) or [] + history = cmds.listHistory(sets.keys()) or [] files = cmds.ls(history, type="file") files = list(set(files)) @@ -99,9 +112,44 @@ class CollectLookTextures(pyblish.api.InstancePlugin): continue resources.append(resource) - # Store resources instance.data['resources'] = resources + 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() + 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: + related_sets = self.get_related_sets(node, view_sets) + if not related_sets: + continue + + for objset in related_sets: + if objset in sets: + continue + unique_id = cmds.getAttr("%s.cbId" % objset) + sets[objset] = {"name": objset, + "uuid": unique_id, + "members": list()} + return sets + def collect_resources(self, node, verbose=False): """Collect the link to the file(s) used (resource) Args: @@ -112,6 +160,8 @@ class CollectLookTextures(pyblish.api.InstancePlugin): dict """ + # assure node includes full path + node = cmds.ls(node, long=True)[0] attribute = "{}.fileTextureName".format(node) source = cmds.getAttr(attribute) @@ -146,8 +196,89 @@ class CollectLookTextures(pyblish.api.InstancePlugin): "node": node, "attribute": attribute, "source": source, # required for resources - "files": files, # required for resources - "subfolder": "textures", # optional for resources - } + "files": files} # required for resources return resource + + def collect_member_data(self, member, objset_members, instance_members, + verbose=False): + """Get all information of the node + Args: + member (str): the name of the node to check + objset_members (list): the objectSet members + instance_members (set): the collected instance members + verbose (bool): get debug information + + Returns: + dict + + """ + + node, components = (member.rsplit(".", 1) + [None])[:2] + + # Only include valid members of the instance + if node not in instance_members: + if verbose: + self.log.info("Skipping member %s" % member) + return + + if member in [m["name"] for m in objset_members]: + return + + if verbose: + self.log.debug("Such as %s.." % member) + + member_data = {"name": node, + "uuid": cmds.getAttr("{}.cbId".format(node, ))} + + # Include components information when components are assigned + if components: + member_data["components"] = components + + return member_data + + def get_related_sets(self, node, view_sets): + """Get the sets which do not belong to any specific group + + 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 containers + 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 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)] + + # Ignore viewport filter view sets (from isolate select and + # viewports) + sets = [s for s in sets if s not in view_sets] + + self.log.info("Found sets %s for %s" % (related_sets, node)) + + return sets \ No newline at end of file From 115a85f3914febd5825267e7dd1bd97139fa6f3d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:39:05 +0200 Subject: [PATCH 18/31] extended load_look, improved load_animation namespace --- colorbleed/plugins/maya/load/load_look.py | 160 ++++++++++++++++++++-- colorbleed/plugins/maya/load/load_rig.py | 22 ++- 2 files changed, 159 insertions(+), 23 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 411f2fbed5..c73e2b73a8 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -2,7 +2,7 @@ import os import json from maya import cmds -from avalon import api +from avalon import api, maya class LookLoader(api.Loader): @@ -17,7 +17,25 @@ class LookLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): - from avalon import maya + """ + Load and try to ssign Lookdev to nodes based on relationship data + Args: + name: + namespace: + context: + data: + + Returns: + + """ + + # improve readability of the namespace + assetname = context["asset"]["name"] + ns_assetname = "{}_".format(assetname) + + namespace = maya.unique_namespace(ns_assetname, + format="%03d", + suffix="_look") try: existing_reference = cmds.file(self.fname, query=True, @@ -28,20 +46,16 @@ class LookLoader(api.Loader): self.log.info("Loading lookdev for the first time..") with maya.maintained_selection(): - nodes = cmds.file( - self.fname, - namespace=namespace, - reference=True, - returnNewNodes=True - ) + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True) else: self.log.info("Reusing existing lookdev..") nodes = cmds.referenceQuery(existing_reference, nodes=True) - namespace = nodes[0].split(":", 1)[0] # Assign shaders self.fname = self.fname.rsplit(".", 1)[0] + ".json" - if not os.path.isfile(self.fname): self.log.warning("Look development asset " "has no relationship data.") @@ -50,6 +64,130 @@ class LookLoader(api.Loader): with open(self.fname) as f: relationships = json.load(f) - maya.apply_shaders(relationships, namespace) + # Get all nodes which belong to a matching name space + # Currently this is the safest way to get all the nodes + namespace_nodes = self.get_namespace_nodes(assetname) + self.apply_shaders(relationships, nodes, namespace_nodes) self[:] = nodes + + def apply_shaders(self, relationships, nodes, namespace_nodes): + """Apply all shaders to the nodes based on the relationship data + + Args: + relationships (dict): shader to node relationships + nodes (list): shader network nodes + namespace_nodes (list): nodes from linked to namespace + + Returns: + None + """ + + # attributes = relationships.get("attributes", []) + sets = relationships.get("sets", []) + + shading_engines = cmds.ls(nodes, type="shadingEngine", long=True) + assert len(shading_engines) > 0, ("Error in retrieving shading engine " + "from reference") + + # get all nodes which we need to link + for set in sets: + # collect all unique IDs of the set members + uuid = set["uuid"] + member_uuids = [member["uuid"] for member in set["members"]] + filtered_nodes = self.get_matching_nodes(namespace_nodes, + member_uuids) + shading_engine = self.get_matching_nodes(shading_engines, + [uuid]) + + assert len(shading_engine) == 1, ("Could not find the correct " + "shading engine with cbId " + "'{}'".format(uuid)) + + cmds.sets(filtered_nodes, forceElement=shading_engine[0]) + + def get_matching_nodes(self, nodes, uuids): + """Filter all nodes which match the UUIDs + + Args: + nodes (list): collection of nodes to check + uuids (list): a list of UUIDs which are linked to the shader + + Returns: + list: matching nodes + """ + + filtered_nodes = [] + for node in nodes: + if node is None: + continue + + if not cmds.attributeQuery("cbId", node=node, exists=True): + continue + + # Deformed shaped + attr = "{}.cbId".format(node) + attribute_value = cmds.getAttr(attr) + + if attribute_value not in uuids: + continue + + filtered_nodes.append(node) + + return filtered_nodes + + def get_namespace_nodes(self, assetname): + """ + Get all nodes of namespace `asset_*` and check if they have a shader + assigned, if not add to list + Args: + context (dict): current context of asset + + Returns: + list + + """ + + # types = ["transform", "mesh"] + list_nodes = [] + + namespaces = cmds.namespaceInfo(listOnlyNamespaces=True) + + # remove basic namespaces + namespaces.remove("UI") + namespaces.remove("shared") + + for ns in namespaces: + if not ns.startswith(assetname): + continue + # get reference nodes + ns_nodes = cmds.namespaceInfo(ns, listOnlyDependencyNodes=True) + # TODO: might need to extend the types + # check if any nodes are connected to something else than lambert1 + list_nodes = cmds.ls(ns_nodes, long=True) + unassigned_nodes = [self.has_default_shader(n) for n in list_nodes] + nodes = [n for n in unassigned_nodes if n is not None] + + list_nodes.extend(nodes) + + return set(list_nodes) + + def has_default_shader(self, node): + """Check if the nodes have `initialShadingGroup` shader assigned + + Args: + node (str): node to check + + Returns: + str + """ + + shaders = cmds.listConnections(node, type="shadingEngine") or [] + if "initialShadingGroup" in shaders: + # return transform node + transform = cmds.listRelatives(node, parent=True, type="transform", + fullPath=True) + if not transform: + return [] + + return transform[0] diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 0138f56b76..5604ec22c5 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -2,7 +2,7 @@ import os from maya import cmds -from avalon import api +from avalon import api, maya class RigLoader(api.Loader): @@ -21,7 +21,9 @@ class RigLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): - print("debug : {}\n{}\n".format(name, namespace)) + + assetname = "{}_".format(context["asset"]["name"]) + unique_namespace = maya.unique_namespace(assetname, format="%03d") nodes = cmds.file(self.fname, namespace=namespace, reference=True, @@ -31,9 +33,8 @@ class RigLoader(api.Loader): # Store for post-process self[:] = nodes - if data.get("post_process", True): - self._post_process(name, namespace, context, data) + self._post_process(name, unique_namespace, context, data) def _post_process(self, name, namespace, context, data): from avalon import maya @@ -43,12 +44,10 @@ class RigLoader(api.Loader): # elsewhere, such as in the Integrator plug-in, # without duplication. - output = next( - (node for node in self - if node.endswith("out_SET")), None) - controls = next( - (node for node in self - if node.endswith("controls_SET")), None) + output = next((node for node in self if + node.endswith("out_SET")), None) + controls = next((node for node in self if + node.endswith("controls_SET")), None) assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." @@ -68,9 +67,8 @@ class RigLoader(api.Loader): # TODO(marcus): Hardcoding the family here, better separate this. dependencies = [context["representation"]["_id"]] dependencies = " ".join(str(d) for d in dependencies) - unique_name = maya.unique_name(asset_name, suffix="_SET") - maya.create(name=unique_name, + maya.create(name=namespace, asset=asset, family="colorbleed.animation", options={"useSelection": True}, From c4cf4b3917be6dcfe709cc90b56023c02417faba Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:40:46 +0200 Subject: [PATCH 19/31] improved loaders --- .../plugins/maya/load/load_animation.py | 60 +++++++++++-------- colorbleed/plugins/maya/load/load_model.py | 2 - 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index ed65b2f042..b29bbecece 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -1,4 +1,8 @@ -from avalon import api +import os + +from maya import cmds + +from avalon import api, maya class AbcLoader(api.Loader): @@ -13,12 +17,17 @@ class AbcLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): - from maya import cmds cmds.loadPlugin("AbcImport.mll", quiet=True) # Prevent identical alembic nodes from being shared # Create unique namespace for the cameras + # Get name from asset being loaded + assert "_" in name, "Naming convention not followed" + assetname = "{}_".format(name.split("_")[0]) + namespace = maya.unique_namespace(assetname, + format="%03d", + suffix="_abc") nodes = cmds.file(self.fname, namespace=namespace, sharedReferenceFile=False, @@ -27,8 +36,13 @@ class AbcLoader(api.Loader): reference=True, returnNewNodes=True) + # load colorbleed ID attribute + self.lock_id_attr(nodes) + self[:] = nodes + def lock_id_attr(self): + pass class CurvesLoader(api.Loader): """Specific loader of Curves for the avalon.animation family""" @@ -77,24 +91,19 @@ class CurvesLoader(api.Loader): ]) with maya.maintained_selection(): - cmds.select( - control_set, - replace=True, + cmds.select(control_set, + replace=True, + # Support controllers being embedded in + # additional selection sets. + noExpand=False) - # Support controllers being embedded in - # additional selection sets. - noExpand=False - ) - - nodes = cmds.file( - self.fname, - i=True, - type="atomImport", - renameAll=True, - namespace=namespace, - options=options, - returnNewNodes=True, - ) + nodes = cmds.file(self.fname, + i=True, + type="atomImport", + renameAll=True, + namespace=namespace, + options=options, + returnNewNodes=True) self[:] = nodes + cmds.sets(container, query=True) + [container] @@ -102,8 +111,7 @@ class CurvesLoader(api.Loader): self._post_process(name, namespace, context, data) def _post_process(self, name, namespace, context, data): - import os - from maya import cmds + from avalon import maya, io # Task-dependent post-process @@ -137,6 +145,7 @@ class CurvesLoader(api.Loader): cmds.select([output, controls], noExpand=True) dependencies = [context["representation"]["_id"]] + dependencies = " ".join(str(d) for d in dependencies) name = "anim{}_".format(dependency["name"].title()) # TODO(marcus): Hardcoding the family here, better separate this. @@ -144,8 +153,7 @@ class CurvesLoader(api.Loader): assert len(family) == 1, ("None or multiple animation " "families found") family = family[0] - maya.create( - name=maya.unique_name(name, suffix="_SET"), - family=family, - options={"useSelection": True}, - data={"dependencies": " ".join(str(d) for d in dependencies)}) + maya.create(name=maya.unique_name(name, suffix="_SET"), + family=family, + options={"useSelection": True}, + data={"dependencies": dependencies}) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 313048b1da..57d1676189 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -46,8 +46,6 @@ class ModelGPUCacheLoader(api.Loader): def process(self, name, namespace, context, data): - from maya import cmds - # todo: This will likely not be entirely safe with "containerize" # also this cannot work in the manager because it only works # on references at the moment! From eb61b4b2f05b482d7e98aa095b0f861cbe70be6b Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:42:30 +0200 Subject: [PATCH 20/31] improved extract to match integrator --- .../plugins/maya/publish/extract_alembic.py | 20 ++++++-- .../plugins/maya/publish/extract_look.py | 47 ++++++++----------- .../plugins/maya/publish/extract_textures.py | 41 ++++++++++++++++ 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index c3abe59244..b15380e1d5 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -16,9 +16,7 @@ class ExtractAlembic(colorbleed.api.Extractor): """ label = "Alembic" - families = ["colorbleed.model", - "colorbleed.pointcache", - "colorbleed.proxy"] + families = ["colorbleed.model", "colorbleed.pointcache"] optional = True def process(self, instance): @@ -27,13 +25,27 @@ class ExtractAlembic(colorbleed.api.Extractor): filename = "%s.abc" % instance.name path = os.path.join(parent_dir, filename) + attrPrefix = instance.data.get("attrPrefix", []) + attrPrefix.append("cb") + options = copy.deepcopy(instance.data) + options['attrPrefix'] = attrPrefix + + # Ensure visibility keys are written + options['writeVisibility'] = True + + # Write creases + options['writeCreases'] = True + + # Ensure UVs are written + options['uvWrite'] = True + options['selection'] = True options["attr"] = ["cbId"] # force elect items to ensure all items get exported by Alembic members = instance.data("setMembers") - print members + print "Members : {}".format(members) cmds.select(members) with avalon.maya.suspended_refresh(): diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 97c572565a..04c632e2d9 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -3,10 +3,11 @@ import json from maya import cmds +import pyblish.api import avalon.maya import colorbleed.api -import cb.utils.maya.context as context +from cb.utils.maya import context class ExtractLook(colorbleed.api.Extractor): @@ -18,9 +19,10 @@ class ExtractLook(colorbleed.api.Extractor): """ - label = "Look (Maya ASCII + JSON)" + label = "Extract Look (Maya ASCII + JSON)" hosts = ["maya"] - families = ["colorbleed.look"] + families = ["colorbleed.lookdev"] + order = pyblish.api.ExtractorOrder + 0.2 def process(self, instance): @@ -29,6 +31,7 @@ class ExtractLook(colorbleed.api.Extractor): maya_fname = "{0}.ma".format(instance.name) json_fname = "{0}.json".format(instance.name) + # Make texture dump folder maya_path = os.path.join(dir_path, maya_fname) json_path = os.path.join(dir_path, json_fname) @@ -40,40 +43,28 @@ class ExtractLook(colorbleed.api.Extractor): lookdata = instance.data["lookData"] sets = lookdata["sets"] - # Define the texture file node remapping - resource_remap = dict() - # required tags to be a look resource - required_tags = ["maya", "attribute", "look"] - resources = instance.data.get("resources", []) - for resource in resources: - resource_tags = resource.get("tags", []) - if all(tag in resource_tags for tag in required_tags): - node = resource['node'] - destination = resource['destination'] - resource_remap["{}.fileTextureName".format(node)] = destination - - # Extract in corect render layer + # Extract in correct render layer layer = instance.data.get("renderlayer", "defaultRenderLayer") with context.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides with context.empty_sets(sets): - with context.attribute_values(resource_remap): - with avalon.maya.maintained_selection(): - cmds.select(sets, noExpand=True) - cmds.file(maya_path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=True, - constraints=True, - expressions=True, - constructionHistory=True) + with avalon.maya.maintained_selection(): + cmds.select(sets, noExpand=True) + cmds.file(maya_path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) # Write the JSON data self.log.info("Extract json..") 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/extract_textures.py b/colorbleed/plugins/maya/publish/extract_textures.py index e69de29bb2..86380d4295 100644 --- a/colorbleed/plugins/maya/publish/extract_textures.py +++ b/colorbleed/plugins/maya/publish/extract_textures.py @@ -0,0 +1,41 @@ +import json +import os +import shutil + +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + + +class ExtractTextures(colorbleed.api.Extractor): + + label = "Extract Textures" + hosts = ["maya"] + families = ["colorbleed.texture"] + order = pyblish.api.ExtractorOrder + 0.1 + + def process(self, instance): + + self.log.info("Extracting textures ...") + + dir_path = self.staging_dir(instance) + resources = instance.data["resources"] + for resource in resources: + self.copy_files(dir_path, resource["files"]) + + self.log.info("Storing cross instance information ...") + self.store_data(resources) + + def store_data(self, data): + tmp_dir = lib.maya_temp_folder() + tmp_file = os.path.join(tmp_dir, "resources.json") + with open(tmp_file, "w") as f: + json.dump(data, fp=f, + separators=[",", ":"], + ensure_ascii=False) + + def copy_files(self, dest, files): + for f in files: + fname = os.path.basename(f) + dest_file = os.path.join(dest, fname) + shutil.copy(f, dest_file) From 24e11ac0475a624b9d128dfabb8e58dbf6569ce9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:43:38 +0200 Subject: [PATCH 21/31] improved validators, added support for colorbleed IDs --- .../publish/validate_look_members_node_ids.py | 2 +- .../maya/publish/validate_resources.py | 6 ++--- .../maya/publish/validate_rig_contents.py | 23 +++++++++++------- .../maya/publish/validate_rig_controllers.py | 24 ++++++++++++++++++- 4 files changed, 40 insertions(+), 15 deletions(-) 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 819f287bf6..4e851e0b21 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -41,7 +41,7 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): # Ensure all nodes have a cbId invalid = list() for node in members: - if not cmds.getAttr("{}.mbID".format(node)): + if not cmds.getAttr("{}.cbId".format(node)): invalid.append(node) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_resources.py b/colorbleed/plugins/maya/publish/validate_resources.py index 57a9239905..2dc6f8c313 100644 --- a/colorbleed/plugins/maya/publish/validate_resources.py +++ b/colorbleed/plugins/maya/publish/validate_resources.py @@ -23,9 +23,7 @@ class ValidateResources(pyblish.api.InstancePlugin): def process(self, instance): for resource in instance.data.get('resources', []): - # Required data - assert "source" in resource - assert "destination" in resource - assert "files" in resource + assert "source" in resource, "No source found" + assert "files" in resource, "No files from source" assert all(os.path.exists(f) for f in resource['files']) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 5744806005..3ef1220e56 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -44,7 +44,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): output_content = cmds.sets("out_SET", query=True) or [] assert output_content, "Must have members in rig out_SET" - controls_content = cmds.set("controls_SET", query=True) or [] + controls_content = cmds.sets("controls_SET", query=True) or [] assert controls_content, "Must have members in rig controls_SET" root_node = cmds.ls(set_members, assemblies=True) @@ -56,15 +56,15 @@ class ValidateRigContents(pyblish.api.InstancePlugin): self.invalid_controls = self.validate_controls(controls_content, hierarchy) - if self.invalid_hierachy: + if self.invalid_hierarchy: self.log.error("Found nodes which reside outside of root group " "while they are set up for publishing." - "\n%s" % self.invalid_hierachy) + "\n%s" % self.invalid_hierarchy) error = True - if self.not_transforms: + if self.invalid_controls: self.log.error("Only transforms can be part of the controls_SET." - "\n%s" % self.not_transforms) + "\n%s" % self.invalid_controls) error = True if self.invalid_geometry: @@ -100,6 +100,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ errors = [] for node in nodes: + print node if node not in hierarchy: errors.append(node) return errors @@ -128,10 +129,12 @@ class ValidateRigContents(pyblish.api.InstancePlugin): # The user can add the shape node to the out_set, this will result # in none when querying allDescendents all_shapes = set_members + shapes + all_long_names = [cmds.ls(i, long=True)[0] for i in all_shapes] # geometry - invalid_shapes = self.validate_hierarchy(hierarchy, all_shapes) - self.invalid_hierachy.extend(invalid_shapes) + invalid_shapes = self.validate_hierarchy(hierarchy, + all_long_names) + self.invalid_hierarchy.extend(invalid_shapes) for shape in all_shapes: nodetype = cmds.nodeType(shape) if nodetype in self.ignore_nodes: @@ -157,8 +160,10 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ errors = [] - invalid_controllers = self.validate_hierarchy(hierarchy, set_members) - self.invalid_hierachy.extend(invalid_controllers) + all_long_names = [cmds.ls(i, long=True)[0] for i in set_members] + invalid_controllers = self.validate_hierarchy(hierarchy, + all_long_names) + self.invalid_hierarchy.extend(invalid_controllers) for node in set_members: nodetype = cmds.nodeType(node) if nodetype in self.ignore_nodes: diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index dd9f77006c..9b066f6351 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -1,8 +1,12 @@ +import logging + from maya import cmds import pyblish.api import colorbleed.api +log = logging.getLogger("Rig Controllers") + class ValidateRigControllers(pyblish.api.InstancePlugin): """Check if the controllers have the transformation attributes set to @@ -12,6 +16,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): label = "Rig Controllers" hosts = ["maya"] families = ["colorbleed.rig"] + actions = [colorbleed.api.RepairAction] def process(self, instance): @@ -21,7 +26,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): is_offset = list() controls = cmds.sets("controls_SET", query=True) - assert controls, "Must have controls in rig control_SET" + assert controls, "Must have controls in rig controls_SET" for control in controls: valid_keyed = self.validate_keyed_state(control) @@ -41,15 +46,18 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if is_keyed: self.log.error("No controls can be keyes. Failed :\n" "%s" % is_keyed) + error = True if is_offset: self.log.error("All controls default transformation values. " "Failed :\n%s" % is_offset) + error = True if not_locked: self.log.error("All controls must have visibility " "attribute locked. Failed :\n" "%s" % not_locked) + error = True if error: raise RuntimeError("Invalid rig controllers. See log for details.") @@ -78,3 +86,17 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if animation_curves: return False return True + + @classmethod + def repair(cls, instance): + + # lock all controllers in controls_SET + controls = cmds.sets("controls_SET", query=True) + for control in controls: + attr = "{}.visibility".format(control) + locked = cmds.getAttr(attr, lock=True) + if not locked: + print("Locking visibility for %s" % control) + cmds.setAttr(attr, lock=True) + + continue From d4e846946ecfd34345d0b6d90a0758e8c96839f3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:44:50 +0200 Subject: [PATCH 22/31] improved set_uuid on maya save --- colorbleed/maya/__init__.py | 39 +++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 6e74cd0980..3097683464 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -2,7 +2,7 @@ import os import site import uuid -from avalon import maya, api as avalon +from avalon import maya, io, api as avalon from pyblish import api as pyblish from maya import cmds @@ -45,22 +45,31 @@ def uninstall(): menu.uninstall() -def _set_uuid(node): +def _set_uuid(asset_id, node): """Add cbId to `node` Unless one already exists. """ - asset = os.environ["AVALON_ASSET"] attr = "{0}.cbId".format(node) - - if not cmds.objExists(attr): + if not cmds.attributeQuery("cbId", node=node, exists=True): cmds.addAttr(node, longName="cbId", dataType="string") _, uid = str(uuid.uuid4()).rsplit("-", 1) - cb_uid = "{}:{}:{}".format(asset, node, uid) + cb_uid = "{}:{}".format(asset_id, uid) cmds.setAttr(attr, cb_uid, type="string") +def _copy_uuid(source, target): + + source_attr = "{0}.cbId".format(source) + target_attr = "{0}.cbId".format(target) + if not cmds.attributeQuery("cbId", node=target, exists=True): + cmds.addAttr(target, longName="cbId", dataType="string") + + attribute_value = cmds.getAttr(source_attr) + cmds.setAttr(target_attr, attribute_value, type="string") + + def on_init(): avalon.logger.info("Running callback on init..") @@ -90,17 +99,20 @@ def on_save(): defaults = ["initialShadingGroup", "initialParticleSE"] # the default items which always want to have an ID - types = ["mesh", "shadingEngine", "file"] + types = ["mesh", "shadingEngine", "file", "nurbsCurve"] # the items which need to pass the id to their parent - subtypes = ["nurbsCurve"] - nodes = (set(cmds.ls(type=types, long=True)) - set(cmds.ls(long=True, readOnly=True)) - set(cmds.ls(long=True, lockedNodes=True))) transforms = set() - for n in cmds.ls(type=subtypes, long=True): + for n in cmds.ls(type=types, long=True): + # pass id to parent of node if in subtypes + relatives = cmds.listRelatives(n, parent=True, fullPath=True) + if not relatives: + continue + for r in cmds.listRelatives(n, parent=True, fullPath=True): transforms.add(r) @@ -108,8 +120,11 @@ def on_save(): # is unique nodes |= transforms - # remove any default nodes + # Lead with asset ID from the database + asset = os.environ["AVALON_ASSET"] + asset_id = io.find_one({"type": "asset", "name": asset}) + for node in nodes: if node in defaults: continue - _set_uuid(node) + _set_uuid(str(asset_id["_id"]), node) From beb06718b2d54a9102f155249c2087357bfc308f Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:47:43 +0200 Subject: [PATCH 23/31] changed pipeline id name to cbId --- colorbleed/maya/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/commands.py b/colorbleed/maya/commands.py index b31e4abe64..c2943128a3 100644 --- a/colorbleed/maya/commands.py +++ b/colorbleed/maya/commands.py @@ -612,10 +612,10 @@ def auto_connect_assets(src, dst): break for input_transform in cmds.sets(in_set, query=True): - mbid = cmds.getAttr(input_transform + ".mbID") + mbid = cmds.getAttr(input_transform + ".cbId") input_shape = cmds.listRelatives(input_transform, shapes=True)[0] - for output_transform in lib.lsattr("mbID", value=mbid): + for output_transform in lib.lsattr("cbId", value=mbid): ref = cmds.referenceQuery(output_transform, referenceNode=True) if ref != src: From ec946f8c5b7739eec4996b370e927a08d75749f1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 15:35:02 +0200 Subject: [PATCH 24/31] changed warning to debug logger for `get_id` --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b4e4dec544..f1530fd109 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -613,7 +613,7 @@ def _get_id(node): attr = "{}.cbId".format(node) attribute_value = cmds.getAttr(attr) except Exception as e: - log.warning(e) + log.debug(e) return return attribute_value From 4c0160757b8228a0cd0b70d22ef64f0df0bbf86c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 15:35:31 +0200 Subject: [PATCH 25/31] added lib.assign_look to the loader --- colorbleed/plugins/maya/load/load_look.py | 68 +---------------------- 1 file changed, 2 insertions(+), 66 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index c73e2b73a8..1742983e84 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -3,6 +3,7 @@ import json from maya import cmds from avalon import api, maya +import colorbleed.maya.lib as lib class LookLoader(api.Loader): @@ -67,75 +68,10 @@ class LookLoader(api.Loader): # Get all nodes which belong to a matching name space # Currently this is the safest way to get all the nodes namespace_nodes = self.get_namespace_nodes(assetname) - self.apply_shaders(relationships, nodes, namespace_nodes) + lib.apply_shaders(relationships, nodes, namespace_nodes) self[:] = nodes - def apply_shaders(self, relationships, nodes, namespace_nodes): - """Apply all shaders to the nodes based on the relationship data - - Args: - relationships (dict): shader to node relationships - nodes (list): shader network nodes - namespace_nodes (list): nodes from linked to namespace - - Returns: - None - """ - - # attributes = relationships.get("attributes", []) - sets = relationships.get("sets", []) - - shading_engines = cmds.ls(nodes, type="shadingEngine", long=True) - assert len(shading_engines) > 0, ("Error in retrieving shading engine " - "from reference") - - # get all nodes which we need to link - for set in sets: - # collect all unique IDs of the set members - uuid = set["uuid"] - member_uuids = [member["uuid"] for member in set["members"]] - filtered_nodes = self.get_matching_nodes(namespace_nodes, - member_uuids) - shading_engine = self.get_matching_nodes(shading_engines, - [uuid]) - - assert len(shading_engine) == 1, ("Could not find the correct " - "shading engine with cbId " - "'{}'".format(uuid)) - - cmds.sets(filtered_nodes, forceElement=shading_engine[0]) - - def get_matching_nodes(self, nodes, uuids): - """Filter all nodes which match the UUIDs - - Args: - nodes (list): collection of nodes to check - uuids (list): a list of UUIDs which are linked to the shader - - Returns: - list: matching nodes - """ - - filtered_nodes = [] - for node in nodes: - if node is None: - continue - - if not cmds.attributeQuery("cbId", node=node, exists=True): - continue - - # Deformed shaped - attr = "{}.cbId".format(node) - attribute_value = cmds.getAttr(attr) - - if attribute_value not in uuids: - continue - - filtered_nodes.append(node) - - return filtered_nodes - def get_namespace_nodes(self, assetname): """ Get all nodes of namespace `asset_*` and check if they have a shader From e52310e63825be5bcc85bb943ad71b9420a1c3bc Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 17:50:48 +0200 Subject: [PATCH 26/31] added get_reference_node function --- colorbleed/maya/lib.py | 27 ++++++++++++------- .../plugins/publish/post_look_integrate.py | 0 2 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 colorbleed/plugins/publish/post_look_integrate.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index f1530fd109..686a08c6fe 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -665,6 +665,21 @@ def get_representation_file(representation, template=TEMPLATE): return template.format(**context) +def get_reference_node(path): + """ + Get the reference node when the path is found being used in a reference + Args: + path (str): + + Returns: + + """ + node = cmds.file(path, query=True, referenceNode=True) + reference_path = cmds.referenceQuery(path, filename=True) + if os.path.normpath(path) == os.path.normpath(reference_path): + return node + + def list_looks(asset_id): """Return all look subsets for the given asset @@ -706,14 +721,8 @@ def assign_look_by_version(nodes, version_id): shader_filepath = get_representation_file(shader_file) shader_relation = get_representation_file(shader_relations) - try: - existing_reference = cmds.file(shader_filepath, - query=True, - referenceNode=True) - except RuntimeError as e: - if e.message.rstrip() != "Cannot find the scene file.": - raise - + reference_node = get_reference_node(shader_filepath) + if reference_node is None: log.info("Loading lookdev for the first time..") # Define namespace @@ -731,7 +740,7 @@ def assign_look_by_version(nodes, version_id): returnNewNodes=True) else: log.info("Reusing existing lookdev..") - shader_nodes = cmds.referenceQuery(existing_reference, nodes=True) + shader_nodes = cmds.referenceQuery(reference_node, nodes=True) # Assign relationships with open(shader_relation, "r") as f: diff --git a/colorbleed/plugins/publish/post_look_integrate.py b/colorbleed/plugins/publish/post_look_integrate.py new file mode 100644 index 0000000000..e69de29bb2 From a168dd644d97100cc0548a0adcf1b6787c5a099d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 17:52:39 +0200 Subject: [PATCH 27/31] implemented get_reference_node, improved referencing multiple files with similar filename --- colorbleed/plugins/maya/load/load_look.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 1742983e84..9dffb3c550 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -30,6 +30,8 @@ class LookLoader(api.Loader): """ + reference_node = None + # improve readability of the namespace assetname = context["asset"]["name"] ns_assetname = "{}_".format(assetname) @@ -37,23 +39,22 @@ class LookLoader(api.Loader): namespace = maya.unique_namespace(ns_assetname, format="%03d", suffix="_look") - try: - existing_reference = cmds.file(self.fname, - query=True, - referenceNode=True) - except RuntimeError as e: - if e.message.rstrip() != "Cannot find the scene file.": - raise - self.log.info("Loading lookdev for the first time..") + try: + reference_node = lib.get_reference_node(self.fname) + except: + pass + + if reference_node is None: + self.log.info("Loading lookdev for the first time ...") with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, reference=True, returnNewNodes=True) else: - self.log.info("Reusing existing lookdev..") - nodes = cmds.referenceQuery(existing_reference, nodes=True) + self.log.info("Reusing existing lookdev ...") + nodes = cmds.referenceQuery(reference_node, nodes=True) # Assign shaders self.fname = self.fname.rsplit(".", 1)[0] + ".json" From 73d93b1bd1bee3b55143cdbcea14639c0ef7078e Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:50:35 +0200 Subject: [PATCH 28/31] added more items to menu --- colorbleed/maya/menu.json | 364 ++++++++++++++++---------------------- 1 file changed, 157 insertions(+), 207 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9258617399..4931063ee7 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -19,12 +19,6 @@ "title": "Version Up", "tooltip": "Incremental save with a specific format" }, - { - "command": "", - "sourcetype": "file", - "title": "Save scene", - "tooltip": "Save the scene based on a specific format" - }, { "title": "separator" } @@ -84,7 +78,7 @@ "uvset", "delete" ], - "title": "polyDeleteOtherUVSets", + "title": "Polygon Delete Other UV Sets", "tooltip": "" }, { @@ -112,10 +106,7 @@ { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", "sourcetype": "file", - "tags": [ - "modeling", - "polyDetachSeparate" - ], + "tags": ["modeling", "poly","detach","separate"], "title": "Polygon Detach and Separate", "tooltip": "" }, @@ -133,10 +124,7 @@ { "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", "sourcetype": "file", - "tags": [ - "modeling", - "polySelectEveryNthEdgeUI" - ], + "tags": ["modeling", "select","nth", "edge", "ui"], "title": "Select Every Nth Edge" }, { @@ -144,7 +132,7 @@ "sourcetype": "file", "tags": [ "modeling", - "djPFXUVs" + "djPFX", "UVs" ], "title": "dj PFX UVs", "tooltip": "" @@ -256,10 +244,7 @@ { "command": "", "sourcetype": "file", - "tags": [ - "animation", - "optimize" - ], + "tags": [], "title": "Optimize", "tooltip": "Optimization scripts", "items": [ @@ -297,7 +282,7 @@ "zvParentMaster" ], "title": "ZV Parent Master", - "tooltip": "Open ZV Parent Master UI" + "tooltip": "Open ZV Parent M UI" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", @@ -342,6 +327,18 @@ "title": "Capture GUI - simple", "tooltip": "Simplified version of the Capture GUI" }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "sourcetype": "file", + "tags": [ + "animation", + "capture", + "screenshot", + "movie" + ], + "title": "Capture GUI", + "tooltip": "Render current camera to an image or movie" + }, { "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", "sourcetype": "file", @@ -363,18 +360,6 @@ "title": "Pose Library", "tooltip": "" }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", - "sourcetype": "file", - "tags": [ - "animation", - "capture", - "screenshot", - "movie" - ], - "title": "Capture GUI", - "tooltip": "Render current camera to an image or movie" - }, { "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", "sourcetype": "file", @@ -548,6 +533,66 @@ ], "title": "Joints On Curve" }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "sourcetype": "python", + "tags": [ + "rigging", + "resetBindSelectedSkinJoints", + "python" + ], + "title": "Reset Bind Selected Skin Joints" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "sourcetype": "python", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedComponents", + "python" + ], + "title": "Select Skincluster Joints From Selected Components" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "sourcetype": "python", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedMesh", + "python" + ], + "title": "Select Skincluster Joints From Selected Mesh" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setJointLabels", + "python" + ], + "title": "Set Joint Labels" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setJointOrientationFromCurrentRotation", + "python" + ], + "title": "Set Joint Orientation From Current Rotation" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setSelectedJointsOrientationZero", + "python" + ], + "title": "Set Selected Joints Orientation Zero" + }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", "sourcetype": "python", @@ -558,6 +603,16 @@ ], "title": "Mirror Curve Shape" }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setRotationOrderUI", + "python" + ], + "title": "Set Rotation Order UI" + }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", "sourcetype": "python", @@ -596,7 +651,7 @@ "rapidRig", "python" ], - "title": "rapidRig" + "title": "Rapid Rig" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", @@ -628,76 +683,6 @@ ], "title": "Reset Bind Selected Meshes" }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", - "sourcetype": "python", - "tags": [ - "rigging", - "resetBindSelectedSkinJoints", - "python" - ], - "title": "Reset Bind Selected Skin Joints" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", - "sourcetype": "python", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedComponents", - "python" - ], - "title": "Select Skincluster Joints From Selected Components" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", - "sourcetype": "python", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedMesh", - "python" - ], - "title": "Select Skincluster Joints From Selected Mesh" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setJointLabels", - "python" - ], - "title": "setJointLabels" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setJointOrientationFromCurrentRotation", - "python" - ], - "title": "Set Joint Orientation From Current Rotation" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setRotationOrderUI", - "python" - ], - "title": "Set Rotation Order UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setSelectedJointsOrientationZero", - "python" - ], - "title": "Set Selected Joints Orientation Zero" - }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", "sourcetype": "python", @@ -770,7 +755,7 @@ } ], "Shading": [ - { + { "command": "", "sourcetype": "file", "tags": [ @@ -1098,123 +1083,88 @@ ], "Layout": [ { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\alignDistributeUI.py", "sourcetype": "file", - "tags": [ - "layout", - "alignDistributeUI" + "tags": ["layout", "align", "Distribute", "UI"], + "title": "Align Distribute UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\alignSimpleUI.py", + "sourcetype": "file", + "tags": ["layout", "align", "UI", "Simple"], + "title": "Align Simple UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\center_locator.py", + "sourcetype": "file", + "tags": ["layout", "center", "locator"], + "title": "Center Locator", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\average_locator.py", + "sourcetype": "file", + "tags": ["layout", "average", "locator"], + "title": "Average Locator", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\selectWithinProximityUI.py", + "sourcetype": "file", + "tags": ["layout", "select", "proximity", "ui" ], - "title": "alignDistributeUI", + "title": "Select Within Proximity UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\dupCurveUI.py", + "sourcetype": "file", + "tags": ["layout", "Duplicate", "Curve", "UI"], + "title": "Duplicate Curve UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\randomDeselectUI.py", + "sourcetype": "file", + "tags": ["layout", "random", "Deselect", "UI"], + "title": "Random Deselect UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\multiReferencerUI.py", + "sourcetype": "file", + "tags": ["layout", "multi", "reference"], + "title": "Multi Referencer UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\duplicateOffsetUI.py", + "sourcetype": "file", + "tags": ["layout", "duplicate", "offset", "UI"], + "title": "Duplicate Offset UI", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "layout", - "alignSimpleUI" - ], - "title": "alignSimpleUI", + "tags": ["layout", "spPaint3d", "paint", "tool"], + "title": "SP Paint 3d", "tooltip": "" }, { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\randomizeUI.py", "sourcetype": "file", - "tags": [ - "layout", - "center_locator" - ], - "title": "center_locator", + "tags": ["layout", "randomize","UI"], + "title": "Randomize UI", "tooltip": "" }, { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\distributeWithinObjectUI.py", "sourcetype": "file", - "tags": [ - "layout", - "average_locator" - ], - "title": "average_locator", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "selectWithinProximityUI" - ], - "title": "selectWithinProximityUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "dupCurveUI" - ], - "title": "dupCurveUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "randomDeselectUI" - ], - "title": "randomDeselectUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "multiReferencerUI" - ], - "title": "multiReferencerUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "duplicateOffsetUI" - ], - "title": "duplicateOffsetUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "spPaint3d" - ], - "title": "spPaint3d", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "randomizeUI" - ], - "title": "randomizeUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "distributeWithinObjectUI" - ], - "title": "distributeWithinObjectUI", + "tags": ["layout", "distribute", "ObjectUI", "within"], + "title": "Distribute Within Object UI", "tooltip": "" } ], From 5eaf66c55cba67638571079f65ce2acd555cb8b1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:51:28 +0200 Subject: [PATCH 29/31] added post-integrate plugin - temp --- colorbleed/plugins/publish/integrate_asset.py | 24 +---------- .../plugins/publish/post_look_integrate.py | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py index 7db26fdaab..83678f7242 100644 --- a/colorbleed/plugins/publish/integrate_asset.py +++ b/colorbleed/plugins/publish/integrate_asset.py @@ -47,35 +47,15 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # get needed data traffic = instance.data["traffic"] representations = instance.data["representations"] - version_folder = instance.data["versionFolder"] - family = instance.data["family"] - resources = instance.data("resources", []) self.log.info("Registering {} items".format(len(representations))) io.insert_many(representations) # moving files - for src_dest in traffic: - src, dest = src_dest + for src, dest in traffic: self.log.info("Copying file .. {} -> {}".format(src, dest)) self.copy_file(src, dest) - if family == "colorbleed.texture": - try: - lib.remap_resource_nodes(resources, folder=version_folder) - except Exception as e: - self.log.error(e) - - if family == "colorbleed.lookdev": - try: - tmp_dir = lib.maya_temp_folder() - resource_file = os.path.join(tmp_dir, "resources.json") - with open(resource_file, "r") as f: - resources = json.load(f) - lib.remap_resource_nodes(resources) - except Exception as e: - self.log.error(e) - self.log.info("Removing temporary files and folders ...") stagingdir = instance.data["stagingDir"] shutil.rmtree(stagingdir) @@ -100,4 +80,4 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.critical("An unexpected error occurred.") raise - shutil.copy(src, dst) \ No newline at end of file + shutil.copy(src, dst) diff --git a/colorbleed/plugins/publish/post_look_integrate.py b/colorbleed/plugins/publish/post_look_integrate.py index e69de29bb2..e66828b3c8 100644 --- a/colorbleed/plugins/publish/post_look_integrate.py +++ b/colorbleed/plugins/publish/post_look_integrate.py @@ -0,0 +1,40 @@ +import json +import os + + +import colorbleed.maya.lib as lib + +import pyblish.api + + +class IntegrateAsset(pyblish.api.InstancePlugin): + """Remap source paths for lookdev and textures + + """ + + label = "Remap source paths" + order = pyblish.api.IntegratorOrder + 0.15 + families = ["colorbleed.lookdev", + "colorbleed.texture"] + + def process(self, instance): + + family = instance.data['family'] + resources = instance.data['resources'] + version_folder = instance.data['versionFolder'] + + if family == "colorbleed.texture": + try: + lib.remap_resource_nodes(resources, folder=version_folder) + except Exception as e: + self.log.error(e) + + if family == "colorbleed.lookdev": + try: + tmp_dir = lib.maya_temp_folder() + resource_file = os.path.join(tmp_dir, "resources.json") + with open(resource_file, "r") as f: + resources = json.load(f) + lib.remap_resource_nodes(resources) + except Exception as e: + self.log.error(e) From a3e704e5bbe7c920e1c2cad2fe9676f813529485 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:52:32 +0200 Subject: [PATCH 30/31] removed lock_id_attr function --- colorbleed/plugins/maya/load/load_animation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index b29bbecece..9248b49287 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -37,12 +37,9 @@ class AbcLoader(api.Loader): returnNewNodes=True) # load colorbleed ID attribute - self.lock_id_attr(nodes) self[:] = nodes - def lock_id_attr(self): - pass class CurvesLoader(api.Loader): """Specific loader of Curves for the avalon.animation family""" From e9cf2e964b90b3d875744cf49c6d6c738c4fd8a1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:54:17 +0200 Subject: [PATCH 31/31] docstrings and cosmetics --- colorbleed/maya/lib.py | 4 ++-- colorbleed/maya/menu.py | 3 +++ colorbleed/plugins/maya/load/load_look.py | 5 ++++- colorbleed/plugins/maya/publish/collect_look.py | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 686a08c6fe..a2715863ee 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -669,10 +669,10 @@ def get_reference_node(path): """ Get the reference node when the path is found being used in a reference Args: - path (str): + path (str): the file path to check Returns: - + node (str): name of the reference node in question """ node = cmds.file(path, query=True, referenceNode=True) reference_path = cmds.referenceQuery(path, filename=True) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 4b09f87f1b..003c303097 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -24,6 +24,9 @@ def deferred(): from scriptsmenu import launchformaya import scriptsmenu.scriptsmenu as menu + reload(launchformaya) + reload(menu) + log.info("Attempting to install ...") # load configuration of custom menu diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 9dffb3c550..557d18a3c2 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -30,7 +30,7 @@ class LookLoader(api.Loader): """ - reference_node = None + # improve readability of the namespace assetname = context["asset"]["name"] @@ -40,6 +40,9 @@ class LookLoader(api.Loader): format="%03d", suffix="_look") + # try / except here is to ensure that the get_reference_node + # does not fail when the file doesn't exist yet + reference_node = None try: reference_node = lib.get_reference_node(self.fname) except: diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index eff9c29f55..8511914313 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -121,7 +121,7 @@ class CollectLook(pyblish.api.InstancePlugin): "relationships": sets.values(), "sets": looksets} # Collect textures - resources = [self.collect_resources(n) for n in cmds.ls(type="file")] + resources = [self.collect_resource(n) for n in cmds.ls(type="file")] instance.data["resources"] = resources # Log a warning when no relevant sets were retrieved for the look. @@ -322,7 +322,7 @@ class CollectLook(pyblish.api.InstancePlugin): return attributes - def collect_resources(self, node, verbose=False): + def collect_resource(self, node, verbose=False): """Collect the link to the file(s) used (resource) Args: node (str): name of the node