This commit is contained in:
Roy Nieterau 2017-06-27 16:01:28 +02:00
commit 2da2f00ffc
18 changed files with 461 additions and 435 deletions

View file

@ -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
@ -42,11 +63,35 @@ 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)
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.

View file

@ -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",

View file

@ -241,4 +241,4 @@ def collect_animation_data():
def get_current_renderlayer():
return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True)
return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True)

View file

@ -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)

View file

@ -1,5 +1,7 @@
from maya import cmds
from avalon import api
from avalon import maya
class ModelLoader(api.Loader):
@ -13,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,
@ -21,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

View file

@ -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")

View file

@ -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")

View file

@ -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")

View file

@ -1,5 +1,3 @@
import os
from maya import cmds
import pyblish.api

View file

@ -69,69 +69,25 @@ 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
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 {0}".format(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,
@ -139,44 +95,141 @@ 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"]
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
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 = []
attributes = self.collect_attributes_changes(instance)
# Store data on the instance
instance.data["lookAttributes"] = attributes
instance.data["lookSetRelations"] = sets.values()
instance.data["lookSets"] = cmds.ls(sets.keys(),
absoluteName=True,
long=True)
# 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: "
"%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
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
def collect_attributes_changes(self, instance):
attributes = []
for node in instance:
# Collect changes to "custom" attributes
@ -186,36 +239,15 @@ class CollectLook(pyblish.api.InstancePlugin):
if not node_attrs:
continue
attributes = {}
node_attributes = {}
for attr in node_attrs:
attribute = "{}.{}".format(node, attr)
attributes[attr] = cmds.getAttr(attribute)
node_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}
"attributes": node_attributes}
attrs.append(data)
attributes.append(data)
# Store data on the instance
instance.data["lookAttributes"] = attrs
instance.data["lookSetRelations"] = sets.values()
instance.data["lookSets"] = cmds.ls(sets.keys(),
absoluteName=True,
long=True)
# 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.info("Collected look for %s" % instance)
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)
return attributes

View file

@ -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
@ -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 []
@ -93,43 +94,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 <UDIM> 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 <UDIM> 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

View file

@ -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))

View file

@ -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")

View file

@ -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))

View file

@ -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.")

View file

@ -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)

View file

@ -1,3 +1,5 @@
from maya import cmds
import pyblish.api
import colorbleed.api
@ -14,22 +16,18 @@ 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):
from maya import cmds
objectsets = ("controls_SET", "out_SET")
missing = [obj for obj in objectsets if obj not in instance]
objsets = ("controls_SET", "out_SET")
missing = list()
for objset in objsets:
if objset not in instance:
missing.append(objset)
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
@ -39,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.")

View file

@ -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