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