From e75c64114670f734cb049979d1b7cea041d0c746 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 26 Jun 2017 11:15:07 +0200 Subject: [PATCH 01/11] Integrated context repair class --- colorbleed/action.py | 46 +++++++++++++++++++ colorbleed/api.py | 14 +----- colorbleed/maya/lib.py | 2 +- colorbleed/plugins/maya/load/load_model.py | 1 + .../maya/publish/_validate_units_angular.py | 20 -------- .../maya/publish/_validate_units_fps.py | 28 ----------- .../maya/publish/_validate_units_linear.py | 20 -------- .../maya/publish/collect_current_file.py | 4 +- .../maya/publish/validate_maya_units.py | 26 ++++++++--- .../maya/publish/validate_rig_contents.py | 15 +++--- 10 files changed, 78 insertions(+), 98 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/_validate_units_angular.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_units_fps.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_units_linear.py diff --git a/colorbleed/action.py b/colorbleed/action.py index aad054044d..69562b1ef0 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -20,6 +20,27 @@ def get_errored_instances_from_context(context): return instances +def get_errored_plugins_from_data(context): + """Get all failed validation plugins + + Args: + context (object): + + Returns: + list of plugins which failed during validation + + """ + + plugins = list() + results = context.data.get("results", []) + for result in results: + if result["success"] == True: + continue + plugins.append(result["plugin"]) + + return plugins + + class RepairAction(pyblish.api.Action): """Repairs the action @@ -47,6 +68,31 @@ class RepairAction(pyblish.api.Action): plugin.repair(instance) +class RepairContextAction(pyblish.api.Action): + """Repairs the action + + To retrieve the invalid nodes this assumes a static `repair(instance)` + method is available on the plugin. + + """ + label = "Repair Context" + on = "failed" # This action is only available on a failed plug-in + + def process(self, context, plugin): + + if not hasattr(plugin, "repair"): + raise RuntimeError("Plug-in does not have repair method.") + + # Get the errored instances + self.log.info("Finding failed instances..") + errored_plugins = get_errored_plugins_from_data(context) + + # Apply pyblish.logic to get the instances for the plug-in + if plugin in errored_plugins: + self.log.info("Attempting fix ...") + plugin.repair() + + class SelectInvalidAction(pyblish.api.Action): """Select invalid nodes in Maya when plug-in failed. diff --git a/colorbleed/api.py b/colorbleed/api.py index 21ac9ba409..9de699fa62 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -14,20 +14,10 @@ from .plugin import ( from .action import ( SelectInvalidAction, GenerateUUIDsOnInvalidAction, - RepairAction + RepairAction, + RepairContextAction ) - -def merge(*args): - """Helper to merge OrderedDict instances""" - data = OrderedDict() - for arg in args: - for key, value in arg.items(): - data.pop(key, None) - data[key] = value - return data - - all = [ "Extractor", "ValidatePipelineOrder", diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index aa5345166c..6d4c9a272c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -241,4 +241,4 @@ def collect_animation_data(): def get_current_renderlayer(): - return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) \ No newline at end of file + return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 65dd3e2fd0..bfb0decaca 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,4 +1,5 @@ from maya import cmds + from avalon import api diff --git a/colorbleed/plugins/maya/publish/_validate_units_angular.py b/colorbleed/plugins/maya/publish/_validate_units_angular.py deleted file mode 100644 index 6ac560ee60..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_units_angular.py +++ /dev/null @@ -1,20 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateUnitsAngular(pyblish.api.ContextPlugin): - """Scene angular units must be in degrees""" - - order = colorbleed.api.ValidateSceneOrder - label = "Units (angular)" - families = ["colorbleed.rig", - "colorbleed.model", - "colorbleed.pointcache", - "colorbleed.curves"] - - def process(self, context): - units = context.data('angularUnits') - - self.log.info('Units (angular): {0}'.format(units)) - assert units and units == 'deg', ( - "Scene angular units must be degrees") diff --git a/colorbleed/plugins/maya/publish/_validate_units_fps.py b/colorbleed/plugins/maya/publish/_validate_units_fps.py deleted file mode 100644 index b28c86804e..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_units_fps.py +++ /dev/null @@ -1,28 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateUnitsFps(pyblish.api.ContextPlugin): - """Validate the scene linear, angular and time units.""" - - order = colorbleed.api.ValidateSceneOrder - label = "Units (fps)" - families = ["colorbleed.rig", - "colorbleed.pointcache", - "colorbleed.curves"] - actions = [colorbleed.api.RepairAction] - optional = True - - def process(self, context): - - fps = context.data['fps'] - - self.log.info('Units (time): {0} FPS'.format(fps)) - assert fps and fps == 25.0, "Scene must be 25 FPS" - - @classmethod - def repair(cls): - """Fix the current FPS setting of the scene, set to PAL(25.0 fps) - """ - import maya.cmds as cmds - cmds.currentUnit(time="pal") diff --git a/colorbleed/plugins/maya/publish/_validate_units_linear.py b/colorbleed/plugins/maya/publish/_validate_units_linear.py deleted file mode 100644 index 9230ae7626..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_units_linear.py +++ /dev/null @@ -1,20 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateUnitsLinear(pyblish.api.ContextPlugin): - """Scene must be in linear units""" - - order = colorbleed.api.ValidateSceneOrder - label = "Units (linear)" - families = ["colorbleed.rig", - "colorbleed.model", - "colorbleed.pointcache", - "colorbleed.curves"] - - def process(self, context): - units = context.data('linearUnits') - - self.log.info('Units (linear): {0}'.format(units)) - assert units and units == 'cm', ("Scene linear units must " - "be centimeters") diff --git a/colorbleed/plugins/maya/publish/collect_current_file.py b/colorbleed/plugins/maya/publish/collect_current_file.py index 305116fb2d..0b38ebcf3d 100644 --- a/colorbleed/plugins/maya/publish/collect_current_file.py +++ b/colorbleed/plugins/maya/publish/collect_current_file.py @@ -1,5 +1,3 @@ -import os - from maya import cmds import pyblish.api @@ -15,4 +13,4 @@ class CollectMayaCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" current_file = cmds.file(query=True, sceneName=True) - context.data['currentFile'] = os.path.normpath(current_file) + context.data['currentFile'] = current_file diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 538dec949c..383562cef2 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -1,3 +1,5 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api @@ -11,7 +13,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): "colorbleed.model", "colorbleed.pointcache", "colorbleed.curves"] - actions = [colorbleed.api.RepairAction] + actions = [colorbleed.api.RepairContextAction] def process(self, context): @@ -29,11 +31,23 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): assert angularunits and angularunits == 'deg', ("Scene angular units " "must be degrees") - - assert fps and fps == 25.0, "Scene must be 25 FP" + assert fps and fps == 25.0, "Scene must be 25 FPS" @classmethod def repair(cls): - """Fix the current FPS setting of the scene, set to PAL(25.0 fps) - """ - raise NotImplementedError() + """Fix the current FPS setting of the scene, set to PAL(25.0 fps)""" + + cls.log.info("Setting angular unit to 'degrees'") + cmds.currentUnit(angle="degree") + current_angle = cmds.currentUnit(query=True, angle=True) + cls.log.debug(current_angle) + + cls.log.info("Setting linear unit to 'centimeter'") + cmds.currentUnit(linear="centimeter") + current_linear = cmds.currentUnit(query=True, linear=True) + cls.log.debug(current_linear) + + cls.log.info("Setting time unit to 'PAL'") + cmds.currentUnit(time="pal") + current_time = cmds.currentUnit(query=True, time=True) + cls.log.debug(current_time) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 1b6beb8ad7..6ebf807762 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -1,3 +1,5 @@ +from maya import cmds + import pyblish.api import colorbleed.api @@ -19,17 +21,14 @@ class ValidateRigContents(pyblish.api.InstancePlugin): def process(self, instance): - from maya import cmds - - objsets = ("controls_SET", "out_SET") + objectsets = ("controls_SET", "out_SET") missing = list() - for objset in objsets: - if objset not in instance: - missing.append(objset) + for objectset in objectsets: + if objectset not in instance: + missing.append(objectset) - assert not missing, ("%s is missing %s" - % (instance, missing)) + assert not missing, ("%s is missing %s" % (instance, missing)) # Ensure there are at least some transforms or dag nodes # in the rig instance From e0b1bdb78099447246f22862f2c27b2234359111 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 26 Jun 2017 12:25:37 +0200 Subject: [PATCH 02/11] Added repair function for joints hidden --- colorbleed/action.py | 1 - .../plugins/maya/publish/validate_joints_hidden.py | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 69562b1ef0..fef737195d 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -63,7 +63,6 @@ class RepairAction(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - for instance in instances: plugin.repair(instance) diff --git a/colorbleed/plugins/maya/publish/validate_joints_hidden.py b/colorbleed/plugins/maya/publish/validate_joints_hidden.py index 77bfe9f5de..8378dbf8bc 100644 --- a/colorbleed/plugins/maya/publish/validate_joints_hidden.py +++ b/colorbleed/plugins/maya/publish/validate_joints_hidden.py @@ -81,7 +81,8 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): category = 'rig' version = (0, 1, 0) label = "Joints Hidden" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] @staticmethod def get_invalid(instance): @@ -93,5 +94,9 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Visible joints found: " - "{0}".format(invalid)) + raise ValueError("Visible joints found: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + import maya.mel as mel + mel.eval("HideJoints") From deb237113876cae51879e8f15222049eabcf06e7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:46:55 +0200 Subject: [PATCH 03/11] changed error catching, results in list of all errors in log --- .../maya/publish/validate_rig_contents.py | 94 ++++++++++++++----- .../maya/publish/validate_rig_controllers.py | 80 ++++++++++++++++ 2 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/validate_rig_controllers.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 6ebf807762..4445a48fca 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -16,17 +16,16 @@ class ValidateRigContents(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder label = "Rig Contents" hosts = ["maya"] - families = ["colorbleed.rig", "colorbleed.rigcontrols", - "colorbleed.rigpointcache"] + families = ["colorbleed.rig"] + + accepted_output = ["mesh", "transform"] + accepted_controllers = ["transform"] + ignore_nodes = [] def process(self, instance): objectsets = ("controls_SET", "out_SET") - - missing = list() - for objectset in objectsets: - if objectset not in instance: - missing.append(objectset) + missing = [obj for obj in objectsets if obj not in instance] assert not missing, ("%s is missing %s" % (instance, missing)) @@ -38,33 +37,78 @@ class ValidateRigContents(pyblish.api.InstancePlugin): "(Empty instance?)") self.log.info("Evaluating contents of object sets..") + not_meshes = list() + not_transforms = list() + invalid_hierachy = list() - # Ensure contents in sets - members = cmds.sets("out_SET", query=True) or [] - assert members, "Must have members in rig out_SET" + error = False - controls = cmds.sets("controls_SET", query=True) or [] - assert controls, "Must have controls in rig control_SET" + # Ensure contents in sets and retrieve long path for all objects + out_members = cmds.sets("out_SET", query=True) or [] + assert out_members, "Must have members in rig out_SET" + out_members = cmds.ls(out_members, long=True) + + controls_members = cmds.sets("controls_SET", query=True) or [] + controls_members = cmds.ls(controls_members, long=True) + assert controls_members, "Must have controls in rig control_SET" + + root_node = cmds.ls(set_members, assemblies=True) + root_content = cmds.listRelatives(root_node, + allDescendents=True, + fullPath=True) # Validate the contents further - shapes = cmds.listRelatives(members, + shapes = cmds.listRelatives(out_members, allDescendents=True, shapes=True, fullPath=True) or [] - for shape in shapes: - if cmds.nodeType(shape) != "mesh": + + # The user can add the shape node to the out_set, this will result + # in none when querying allDescendents + out_shapes = out_members + shapes + + # geometry + for shape in out_shapes: + nodetype = cmds.nodeType(shape) + if nodetype in self.ignore_nodes: + continue + + if nodetype not in self.accepted_output: not_meshes.append(shape) - not_transforms = list() - for node in cmds.sets("controls_SET", query=True) or []: - if cmds.nodeType(node) != "transform": - not_meshes.append(node) + # check if controllers are in the root group + if shape not in root_content: + invalid_hierachy.append(shape) - assert not_transforms == [], ( - "Only transforms can be part of the controls_SET: %s" - % not_transforms) + # curves + for node in controls_members: + nodetype = cmds.nodeType(node) + if nodetype in self.ignore_nodes: + continue - assert not_meshes == [], ( - "Only meshes can be part of the out_SET: %s" - % not_meshes) + if nodetype not in self.accepted_controllers: + not_transforms.append(node) + + # check if controllers are in the root group + if node not in root_content: + invalid_hierachy.append(node) + + if invalid_hierachy: + self.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierachy) + error = True + + if not_transforms: + self.log.error("Only transforms can be part of the controls_SET." + "\n%s" % not_transforms) + error = True + + if not_meshes: + self.log.error("Only meshes can be part of the out_SET\n%s" + % not_meshes) + error = True + + if error: + raise RuntimeError("Invalid rig content. See log for details.") diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py new file mode 100644 index 0000000000..dd9f77006c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -0,0 +1,80 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateRigControllers(pyblish.api.InstancePlugin): + """Check if the controllers have the transformation attributes set to + default values, locked vibisility attributes and are not keyed + """ + order = colorbleed.api.ValidateContentsOrder + 0.05 + label = "Rig Controllers" + hosts = ["maya"] + families = ["colorbleed.rig"] + + def process(self, instance): + + error = False + is_keyed = list() + not_locked = list() + is_offset = list() + + controls = cmds.sets("controls_SET", query=True) + assert controls, "Must have controls in rig control_SET" + + for control in controls: + valid_keyed = self.validate_keyed_state(control) + if not valid_keyed: + is_keyed.append(control) + + # check if visibility is locked + attribute = "{}.visibility".format(control) + locked = cmds.getAttr(attribute, lock=True) + if not locked: + not_locked.append(control) + + valid_transforms = self.validate_transforms(control) + if not valid_transforms: + is_offset.append(control) + + if is_keyed: + self.log.error("No controls can be keyes. Failed :\n" + "%s" % is_keyed) + + if is_offset: + self.log.error("All controls default transformation values. " + "Failed :\n%s" % is_offset) + + if not_locked: + self.log.error("All controls must have visibility " + "attribute locked. Failed :\n" + "%s" % not_locked) + + if error: + raise RuntimeError("Invalid rig controllers. See log for details.") + + def validate_transforms(self, control): + tolerance = 1e-30 + identity = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + + matrix = cmds.xform(control, query=True, matrix=True, objectSpace=True) + if not all(abs(x - y) < tolerance for x, y in zip(identity, matrix)): + return False + return True + + def validate_keyed_state(self, control): + """Check if the control has an animation curve attached + Args: + control: + + Returns: + + """ + animation_curves = cmds.keyframe(control, query=True, name=True) + if animation_curves: + return False + return True From c64040413db7bf18714a7dfd2a022e09a046cf81 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:49:01 +0200 Subject: [PATCH 04/11] removed redundant modules --- .../plugins/maya/publish/collect_metadata.py | 39 ------- .../maya/publish/validate_latest_versions.py | 108 ------------------ 2 files changed, 147 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/collect_metadata.py delete mode 100644 colorbleed/plugins/maya/publish/validate_latest_versions.py diff --git a/colorbleed/plugins/maya/publish/collect_metadata.py b/colorbleed/plugins/maya/publish/collect_metadata.py deleted file mode 100644 index b5924c25de..0000000000 --- a/colorbleed/plugins/maya/publish/collect_metadata.py +++ /dev/null @@ -1,39 +0,0 @@ -import pyblish.api -import copy - - -class CollectMetadata(pyblish.api.ContextPlugin): - """Transfer context metadata to the instance. - - This applies a copy of the `context.data['metadata']` to the - `instance.data['metadata']` for the following metadata: - - Provides: - { - "topic": "topic", - "author": "user", - "date": "date", - "filename": "currentFile" - } - - - """ - order = pyblish.api.CollectorOrder + 0.2 - label = "Metadata" - - mapping = {"topic": "topic", - "author": "user", - "date": "date", - "filename": "currentFile"} - - def process(self, context): - - metadata = {} - for key, source in self.mapping.iteritems(): - if source in context.data: - metadata[key] = context.data.get(source) - - for instance in context: - instance.data["metadata"] = copy.deepcopy(metadata) - - self.log.info("Collected {0}".format(metadata)) diff --git a/colorbleed/plugins/maya/publish/validate_latest_versions.py b/colorbleed/plugins/maya/publish/validate_latest_versions.py deleted file mode 100644 index 0b7d2dd826..0000000000 --- a/colorbleed/plugins/maya/publish/validate_latest_versions.py +++ /dev/null @@ -1,108 +0,0 @@ -import os - -from maya import cmds - -import pyblish.api -import colorbleed.api - -import cbra.lib -from cb.utils.python.decorators import memorize - - -def is_latest_version(path): - """Return whether path is the latest version. - - Args: - path (str): Full path to published file. - - Returns: - bool: Whether the path belongs to the latest version. - - """ - - ctx = cbra.lib.parse_context(path) - versions = cbra.lib.list_versions(ctx) - highest = cbra.lib.find_highest_version(versions) - - if ctx.get('version', None) != highest: - return False - else: - return True - - -@memorize -def is_latest_version_cached(path): - """Memorized cached wrapper to `is_latest_version`""" - return is_latest_version(path) - - -class ValidateLatestVersions(pyblish.api.InstancePlugin): - """Validates content included is using latest published versions. - - If published contents are out of date they can be easily updated to the - latest version using the scripts > pyblish > utilities > update_xxx for - the corresponding node type. - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.layout'] - label = "Latest Versions" - actions = [colorbleed.api.SelectInvalidAction] - optional = True - - # (node_type, attribute) that are non-referenced to check paths for - LOCAL_CHECKS = { - "gpuCache": "cacheFileName", - "VRayMesh": "fileName2" - } - - @classmethod - def get_invalid(cls, instance): - - all_nodes = instance[:] - invalid = list() - - # check non-referenced nodes - for node_type, attr in cls.LOCAL_CHECKS.iteritems(): - - nodes = cmds.ls(all_nodes, type=node_type, long=True) - referenced = cmds.ls(nodes, referencedNodes=True, long=True) - non_referenced = [n for n in nodes if n not in referenced] - - for node in non_referenced: - - path = cmds.getAttr("{0}.{1}".format(node, attr)) - path = os.path.normpath(path) - if not is_latest_version_cached(path): - invalid.append(node) - - # reference nodes related to this isntance - referenced = cmds.ls(all_nodes, long=True, referencedNodes=True) - referenced_nodes = set(cmds.referenceQuery(reference, referenceNode=True) - for reference in referenced) - - for reference in referenced_nodes: - path = cmds.referenceQuery(reference, - filename=True, - withoutCopyNumber=True) - path = os.path.normpath(path) - if not is_latest_version_cached(path): - invalid.append(reference) - - 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_latest_version_cached.cache.clear() - instance.context.data[key] = True - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Used Items are not updated to latest versions:" - "{0}".format(invalid)) \ No newline at end of file From 98dc353dcb96d600949b0f0a98af92c7b83b9207 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:53:28 +0200 Subject: [PATCH 05/11] refactoring complexity --- .../plugins/maya/publish/collect_look.py | 79 +++++++++++++------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 526eb0a2a8..81caa78c3a 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -69,6 +69,14 @@ class CollectLook(pyblish.api.InstancePlugin): # Ignore specifically named sets (check with endswith) IGNORE = ["out_SET", "controls_SET", "_INST"] + def process(self, instance): + """Collect the Look in the instance with the correct layer settings""" + + layer = instance.data.get("renderlayer", "defaultRenderLayer") + with context.renderlayer(layer): + self.log.info("Checking out layer: {0}".format(layer)) + self.collect(instance) + def collect(self, instance): # Whether to log information verbosely @@ -123,7 +131,7 @@ class CollectLook(pyblish.api.InstancePlugin): node_sets = [s for s in node_sets if s not in view_sets] if verbose: - self.log.debug("After filtering view sets {0}".format(node_sets)) + self.log.debug("After filtering view sets %s" % node_sets) self.log.info("Found sets {0} for {1}".format(node_sets, node)) @@ -140,30 +148,18 @@ class CollectLook(pyblish.api.InstancePlugin): 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): - - node, components = (member.rsplit(".", 1) + [None])[:2] - - # Only include valid members of the instance - if node not in instance_lookup: - if verbose: - self.log.info("Skipping member %s" % member) + member_data = self.collect_member_data(member, + objset_members, + instance_lookup, + verbose) + if not member_data: continue - if member in [m["name"] for m in sets[objset]["members"]]: - continue - - if verbose: - self.log.debug("Such as %s.." % member) - - member_data = {"name": node, "uuid": id_utils.get_id(node)} - - # Include components information when components are assigned - if components: - member_data["components"] = components - sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end @@ -212,10 +208,41 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Collected look for %s" % instance) - def process(self, instance): - """Collect the Look in the instance with the correct layer settings""" + 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": id_utils.get_id(node)} + + # Include components information when components are assigned + if components: + member_data["components"] = components + + return member_data + + - layer = instance.data.get("renderlayer", "defaultRenderLayer") - with context.renderlayer(layer): - self.log.info("Checking out layer: {0}".format(layer)) - self.collect(instance) From 70bab49ce3b7e965a206624b4477d57bcdb4f609 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:54:35 +0200 Subject: [PATCH 06/11] refactored error collecting and complexity --- .../maya/publish/collect_look_textures.py | 122 ++++++++++-------- .../maya/publish/validate_look_contents.py | 16 ++- 2 files changed, 83 insertions(+), 55 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py index 772deaeba1..586e264307 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -31,26 +31,11 @@ class SelectTextureNodesAction(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(instances, plugin) - def is_texture_resource(resource): - """Return whether the resource is a texture""" - - tags = resource.get("tags", []) - if not TAGS_LOOKUP.issubset(tags): - return False - - if resource.get("subfolder", None) != "textures": - return False - - if "node" not in resource: - return False - - return True - # Get the texture nodes from the instances nodes = [] for instance in instances: for resource in instance.data.get("resources", []): - if is_texture_resource(resource): + if self.is_texture_resource(resource): node = resource['node'] nodes.append(node) @@ -64,6 +49,21 @@ class SelectTextureNodesAction(pyblish.api.Action): self.log.info("No texture nodes found.") cmds.select(deselect=True) + def is_texture_resource(self, resource): + """Return whether the resource is a texture""" + + tags = resource.get("tags", []) + if not TAGS_LOOKUP.issubset(tags): + return False + + if resource.get("subfolder", None) != "textures": + return False + + if "node" not in resource: + return False + + return True + class CollectLookTextures(pyblish.api.InstancePlugin): """Collect look textures @@ -93,43 +93,61 @@ class CollectLookTextures(pyblish.api.InstancePlugin): resources = instance.data.get("resources", []) for node in files: - - attribute = "%s.fileTextureName" % 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 = "%s.computedFileTextureNamePattern" % 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 = shader.get_file_node_files(node) - if not files: - self.log.error("File node does not have a texture set: " - "{0}".format(node)) - - # Define the resource - resource = {"tags": TAGS[:], - "node": node, - "attribute": attribute, - "source": source, # required for resources - "files": files, # required for resources - "subfolder": "textures" # optional for resources - } - + resource = self.collect_resources(node, verbose) + if not resource: + continue resources.append(resource) # Store resources instance.data['resources'] = resources + + 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 = shader.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 + resource = {"tags": TAGS[:], + "node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files, # required for resources + "subfolder": "textures" # optional for resources + } + + return resource + + diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 6c91f83206..f117dad794 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -18,10 +18,20 @@ class ValidateLookContents(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" + error = False + + attributes = ["lookSets", + "lookSetRelations", + "lookAttributes"] + if not instance[:]: raise RuntimeError("Instance is empty") # Required look data - assert "lookSets" in instance.data - assert "lookSetRelations" in instance.data - assert "lookAttributes" in instance.data + for attr in attributes: + if attr not in instance.data: + self.log.error("No %s found in data" % attr) + error = True + + if error: + raise RuntimeError("Invalid look content. See log for details.") From 21aeb2edd04d72d470a7fcf7aac532f7c1d3ecdc Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:55:10 +0200 Subject: [PATCH 07/11] cosmetics --- colorbleed/plugins/maya/create/colorbleed_rig.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py index b29d5ae7e5..3fa718d756 100644 --- a/colorbleed/plugins/maya/create/colorbleed_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -1,6 +1,7 @@ -import avalon.maya from maya import cmds +import avalon.maya + class CreateRig(avalon.maya.Creator): """Skeleton and controls for manipulation of the geometry""" @@ -12,6 +13,8 @@ class CreateRig(avalon.maya.Creator): def process(self): instance = super(CreateRig, self).process() + self.log.info("Creating Rig instance set up ...") + controls = cmds.sets(name="controls_SET", empty=True) pointcache = cmds.sets(name="out_SET", empty=True) cmds.sets([controls, pointcache], forceElement=instance) From ef1836c18ba8f7b7fd22d33fab7dc6c8288dbd5d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:57:21 +0200 Subject: [PATCH 08/11] cosmetics --- colorbleed/plugins/maya/publish/collect_look.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 81caa78c3a..e72df4bd24 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -243,6 +243,3 @@ class CollectLook(pyblish.api.InstancePlugin): member_data["components"] = components return member_data - - - From 7c3fb0d0c5277df87be876de98a85353db2602e7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:57:50 +0200 Subject: [PATCH 09/11] cosmetics --- colorbleed/plugins/maya/load/load_model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index bfb0decaca..bdcdd56ca2 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,6 +1,7 @@ from maya import cmds from avalon import api +from avalon import maya class ModelLoader(api.Loader): @@ -14,7 +15,7 @@ class ModelLoader(api.Loader): representations = ["ma"] def process(self, name, namespace, context): - from avalon import maya + with maya.maintained_selection(): nodes = cmds.file( self.fname, @@ -22,7 +23,7 @@ class ModelLoader(api.Loader): reference=True, returnNewNodes=True, groupReference=True, - groupName=namespace + ":" + name + groupName="{}:{}".format(namespace, name) ) # Assign default shader to meshes From 13e56238bc33e9457542c15535800bea37ba7165 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 14:31:15 +0200 Subject: [PATCH 10/11] refactored complexitt --- .../plugins/maya/publish/collect_look.py | 174 +++++++++--------- 1 file changed, 91 insertions(+), 83 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index e72df4bd24..a51eac58a7 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -83,63 +83,11 @@ class CollectLook(pyblish.api.InstancePlugin): verbose = instance.data.get("verbose", False) self.log.info("Looking for look associations " - "for {0}..".format(instance.data['label'])) - - # Get view sets (so we can ignore those sets later) - model_panels = cmds.getPanel(type="modelPanel") - view_sets = set() - - for panel in model_panels: - view_set = cmds.modelEditor(panel, query=True, viewObjects=True) - if view_set: - view_sets.add(view_set) + "for %s" % instance.data['label']) # Discover related object sets self.log.info("Gathering sets..") - sets = dict() - for node in instance: - - node_sets = cmds.listSets(object=node, extendToShape=False) or [] - if verbose: - self.log.info("Found raw sets " - "{0} for {1}".format(node_sets, node)) - - if not node_sets: - continue - - # Exclude deformer sets - deformer_sets = cmds.listSets(object=node, - extendToShape=False, - type=2) or [] - deformer_sets = set(deformer_sets) # optimize lookup - node_sets = [s for s in node_sets if s not in deformer_sets] - - if verbose: - self.log.debug("After filtering deformer sets " - "{0}".format(node_sets)) - - # Ignore specifically named sets - node_sets = [s for s in node_sets if - not any(s.endswith(x) for x in self.IGNORE)] - - if verbose: - self.log.debug("After filtering ignored sets " - "{0}".format(node_sets)) - - # Ignore viewport filter view sets (from isolate select and - # viewports) - node_sets = [s for s in node_sets if s not in view_sets] - - if verbose: - self.log.debug("After filtering view sets %s" % node_sets) - - self.log.info("Found sets {0} for {1}".format(node_sets, node)) - - for objset in node_sets: - if objset not in sets: - sets[objset] = {"name": objset, - "uuid": id_utils.get_id(objset), - "members": list()} + self.gather_sets(instance) # Lookup with absolute names (from root namespace) instance_lookup = set([str(x) for x in cmds.ls(instance, @@ -147,6 +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) @@ -163,40 +112,15 @@ class CollectLook(pyblish.api.InstancePlugin): sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end - for objset, data in sets.items(): - if not data['members']: - self.log.debug("Removing redundant set " - "information: {0}".format(objset)) - sets.pop(objset) - + sets = self.clean_sets(sets) # Member attributes (shapes + transforms) self.log.info("Gathering attribute changes to instance members..") - attrs = [] - for node in instance: - # Collect changes to "custom" attributes - node_attrs = get_look_attrs(node) - - # Only include if there are any properties we care about - if not node_attrs: - continue - - attributes = {} - for attr in node_attrs: - attribute = "{}.{}".format(node, attr) - attributes[attr] = cmds.getAttr(attribute) - - # attributes = dict((attr, pm.getAttr("{}.{}".format(node, attr)) - # for attr in node_attrs)) - data = {"name": node, - "uuid": id_utils.get_id(node), - "attributes": attributes} - - attrs.append(data) + attributes = self.collect_attributes_changes(instance) # Store data on the instance - instance.data["lookAttributes"] = attrs + instance.data["lookAttributes"] = attributes instance.data["lookSetRelations"] = sets.values() instance.data["lookSets"] = cmds.ls(sets.keys(), absoluteName=True, @@ -204,10 +128,69 @@ class CollectLook(pyblish.api.InstancePlugin): # Log a warning when no relevant sets were retrieved for the look. if not instance.data['lookSets']: - self.log.warning("No sets found for the nodes in the instance: {0}".format(instance[:])) + self.log.warning("No sets found for the nodes in the instance: " + "%s" % instance[:]) self.log.info("Collected look for %s" % instance) + def gather_sets(self, instance): + + # 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: + 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: + continue + + for objset in node_sets: + if objset in sets: + continue + sets[objset] = {"name": objset, + "uuid": id_utils.get_id(objset), + "members": list()} + return sets + + def filter_sets(self, node, view_sets): + + node_sets = cmds.listSets(object=node, extendToShape=False) or [] + if not node_sets: + return + + # Exclude 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] + + # 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 {0} for {1}".format(node_sets, node)) + + return sets + + def clean_sets(self, sets): + + for objset, data in sets.items(): + if not data['members']: + self.log.debug("Removing redundant set " + "information: %s" % objset) + sets.pop(objset) + + return sets + def collect_member_data(self, member, objset_members, instance_members, verbose=False): """Get all information of the node @@ -243,3 +226,28 @@ class CollectLook(pyblish.api.InstancePlugin): member_data["components"] = components return member_data + + def collect_attributes_changes(self, instance): + + attributes = [] + for node in instance: + + # Collect changes to "custom" attributes + node_attrs = get_look_attrs(node) + + # Only include if there are any properties we care about + if not node_attrs: + continue + + node_attributes = {} + for attr in node_attrs: + attribute = "{}.{}".format(node, attr) + node_attributes[attr] = cmds.getAttr(attribute) + + data = {"name": node, + "uuid": id_utils.get_id(node), + "attributes": node_attributes} + + attributes.append(data) + + return attributes From 5e6b40c1708f2acfb2bf1678950d1450b8184776 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 14:31:41 +0200 Subject: [PATCH 11/11] cosmetics --- colorbleed/plugins/maya/publish/collect_look_textures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py index 586e264307..5cac73cb20 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -84,7 +84,8 @@ class CollectLookTextures(pyblish.api.InstancePlugin): # Get textures from sets sets = instance.data["lookSets"] if not sets: - raise RuntimeError("No look sets found for the nodes in the instance. {0}".format(sets)) + raise RuntimeError("No look sets found for the nodes in the " + "instance. %s" % sets) # Get the file nodes history = cmds.listHistory(sets) or []