improved validating look, improved save function, added filter_out_nodes

This commit is contained in:
aardschok 2017-08-08 15:07:14 +02:00
parent ea51913955
commit 7ef29e37f6
18 changed files with 271 additions and 210 deletions

View file

@ -8,6 +8,7 @@ from pyblish import api as pyblish
from maya import cmds
from . import menu
from . import lib
PARENT_DIR = os.path.dirname(__file__)
PACKAGE_DIR = os.path.dirname(PARENT_DIR)
@ -96,31 +97,19 @@ def on_save(_):
avalon.logger.info("Running callback on save..")
# establish set of nodes to ignore
ignore = set(["initialShadingGroup", "initialParticleSE"])
ignore |= set(cmds.ls(long=True, readOnly=True))
ignore |= set(cmds.ls(long=True, lockedNodes=True))
types = ["shadingEngine", "file", "mesh", "nurbsCurve"]
# the items which need to pass the id to their parent
nodes = set(cmds.ls(type=types, long=True))
# Add the collected transform to the nodes
transforms = cmds.listRelatives(list(nodes),
parent=True,
fullPath=True) or []
nodes |= set(transforms)
# Remove the ignored nodes
nodes -= ignore
types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"]
type_nodes = set(cmds.ls(type=types, long=True))
nodes = lib.filter_out_nodes(type_nodes,
defaults=True,
referenced_nodes=True)
# Lead with asset ID from the database
asset = os.environ["AVALON_ASSET"]
asset_id = io.find_one({"type": "asset", "name": asset})
asset_id = io.find_one({"type": "asset", "name": asset},
projection={"_id": True})
# generate the ids
for node in nodes:
_set_uuid(str(asset_id["_id"]), node)
for node in nodes:
print node
_set_uuid(str(asset_id["_id"]), node)

View file

@ -613,7 +613,43 @@ def remap_resource_nodes(resources, folder=None):
cmds.file(save=True, type="mayaAscii")
def _get_id(node):
def filter_out_nodes(nodes, defaults=False, referenced_nodes=False):
"""Filter out any node which are locked (reference) or readOnly
Args:
nodes (set): nodes to filter
locked (bool): set True to filter out lockedNodes
readonly (bool): set True to filter out readOnly
Returns:
nodes (list): list of filtered nodes
"""
# establish set of nodes to ignore
# `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove
# default nodes and reference nodes
ignore = set()
if referenced_nodes:
ignore |= set(cmds.ls(long=True, referencedNodes=referenced_nodes))
if defaults:
ignore |= set(cmds.ls(long=True, defaultNodes=defaults))
# The items which need to pass the id to their parent
# Add the collected transform to the nodes
dag = cmds.ls(list(nodes),
type="dagNode",
long=True) # query only dag nodes
transforms = cmds.listRelatives(dag,
parent=True,
fullPath=True) or []
nodes |= set(transforms)
nodes -= ignore # Remove the ignored nodes
return nodes
def get_id(node):
"""
Get the `cbId` attribute of the given node
Args:
@ -817,7 +853,7 @@ def assign_look(nodes, subset="lookDefault"):
# Group all nodes per asset id
grouped = defaultdict(list)
for node in nodes:
colorbleed_id = _get_id(node)
colorbleed_id = get_id(node)
if not colorbleed_id:
continue
@ -883,11 +919,11 @@ def apply_shaders(relationships, shadernodes, nodes):
# region compute lookup
ns_nodes_by_id = defaultdict(list)
for node in nodes:
ns_nodes_by_id[_get_id(node)].append(node)
ns_nodes_by_id[get_id(node)].append(node)
shading_engines_by_id = defaultdict(list)
for shad in shading_engines:
shading_engines_by_id[_get_id(shad)].append(shad)
shading_engines_by_id[get_id(shad)].append(shad)
# endregion
# region assign

View file

@ -1,46 +0,0 @@
import pyblish.api
import colorbleed.api
class ValidateNodeIds(pyblish.api.InstancePlugin):
"""Validate nodes have colorbleed id attributes
All look sets should have id attributes.
"""
label = 'Node Id Attributes'
families = ['colorbleed.look', 'colorbleed.model']
hosts = ['maya']
order = colorbleed.api.ValidatePipelineOrder
actions = [colorbleed.api.SelectInvalidAction,
colorbleed.api.GenerateUUIDsOnInvalidAction]
@staticmethod
def get_invalid(instance):
import maya.cmds as cmds
nodes = instance.data["setMembers"]
# Ensure all nodes have a cbId
data_id = {}
invalid = []
for node in nodes:
try:
uuid = cmds.getAttr("{}.cbId".format(node))
data_id[uuid] = node
if uuid in data_id:
invalid.append(node)
except RuntimeError:
pass
return invalid
def process(self, instance):
"""Process all meshes"""
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Nodes found with invalid"
"asset IDs: {0}".format(invalid))

View file

@ -1,8 +1,8 @@
from maya import cmds
import pyblish.api
import colorbleed.maya.lib as lib
from cb.utils.maya import context, shaders
import cbra.utils.maya.node_uuid as id_utils
SHAPE_ATTRS = ["castsShadows",
"receiveShadows",
@ -13,6 +13,7 @@ SHAPE_ATTRS = ["castsShadows",
"visibleInRefractions",
"doubleSided",
"opposite"]
SHAPE_ATTRS = set(SHAPE_ATTRS)
@ -167,10 +168,10 @@ class CollectLook(pyblish.api.InstancePlugin):
if objset in sets:
continue
unique_id = cmds.getAttr("%s.cbId" % objset)
sets[objset] = {"name": objset,
"uuid": unique_id,
"uuid": lib.get_id(objset),
"members": list()}
return sets
def get_related_sets(self, node, view_sets):
@ -185,8 +186,12 @@ class CollectLook(pyblish.api.InstancePlugin):
Args:
node (str): name of the current not to check
"""
defaults = ["initialShadingGroup",
"defaultLightSet",
"defaultObjectSet"]
ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"]
ignored = ["pyblish.avalon.instance",
"pyblish.avalon.container"]
related_sets = cmds.listSets(object=node, extendToShape=False)
if not related_sets:
@ -206,6 +211,7 @@ class CollectLook(pyblish.api.InstancePlugin):
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]
@ -215,8 +221,9 @@ class CollectLook(pyblish.api.InstancePlugin):
# Ignore viewport filter view sets (from isolate select and
# viewports)
sets = [s for s in sets if s not in view_sets]
sets = [s for s in sets if s not in defaults]
self.log.info("Found sets %s for %s" % (related_sets, node))
self.log.info("Found sets %s for %s" % (sets, node))
return sets
@ -263,21 +270,14 @@ 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
# check node type, if mesh get parent!
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
# A mesh will always have a transform node in Maya logic
node = cmds.listRelatives(node, parent=True, fullPath=True)[0]
if verbose:
self.log.debug("Such as %s.." % member)
if not cmds.attributeQuery("cbId", node=node, exists=True):
self.log.error("Node '{}' has no attribute 'cbId'".format(node))
return
member_data = {"name": node,
"uuid": cmds.getAttr("{}.cbId".format(node))}
@ -321,7 +321,7 @@ class CollectLook(pyblish.api.InstancePlugin):
node_attributes[attr] = cmds.getAttr(attribute)
attributes.append({"name": node,
"uuid": id_utils.get_id(node),
"uuid": cmds.getAttr("{}.cbId".format(node)),
"attributes": node_attributes})
return attributes

View file

@ -15,9 +15,10 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
"""
label = "Validate Frame Range"
order = colorbleed.api.ValidateContentsOrder
label = "Frame Range"
families = ["colorbleed.animation",
"colorbleed.render"]
def process(self, instance):

View file

@ -11,28 +11,72 @@ class ValidateLookContents(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder
families = ['colorbleed.look']
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look Contents'
actions = [colorbleed.api.SelectInvalidAction]
invalid = []
errors = []
def process(self, instance):
"""Process all the nodes in the instance"""
error = False
attributes = ["sets",
"relationships",
"attributes"]
if not instance[:]:
raise RuntimeError("Instance is empty")
# Required look data
self.get_invalid(instance)
if self.errors:
error_string = "\n".join(self.errors)
raise RuntimeError("Invalid look content. "
"Errors : {}".format(error_string))
@classmethod
def get_invalid(cls, instance):
invalid_attr = list(cls.validate_lookdata_attributes(instance))
invalid_rels = list(cls.validate_relationships(instance))
invalid = invalid_attr + invalid_rels
return invalid
@classmethod
def validate_lookdata_attributes(cls, instance):
"""Check if the lookData has the required attributes
Args:
instance
"""
invalid = set()
attributes = ["sets", "relationships", "attributes"]
lookdata = instance.data["lookData"]
for attr in attributes:
if attr not in lookdata:
self.log.error("No %s found in data" % attr)
error = True
cls.errors.append("Look Data has no attribute "
"'{}'".format(attr))
invalid.add(instance.name)
if error:
raise RuntimeError("Invalid look content. See log for details.")
return invalid
@classmethod
def validate_relationships(cls, instance):
"""Validate and update lookData relationships"""
invalid = set()
relationships = instance.data["lookData"]["relationships"]
for relationship in relationships:
look_name = relationship["name"]
for key, value in relationship.items():
if value is None:
cls.errors.append("{} has invalid attribite "
"'{}'".format(look_name, key))
invalid.add(look_name)
return invalid

View file

@ -15,7 +15,7 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder
families = ['colorbleed.look']
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look Default Shader Connections'

View file

@ -102,7 +102,7 @@ class ValidateLookDeformedShapes(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder
families = ['colorbleed.look']
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look deformed shapes'
actions = [colorbleed.api.SelectInvalidAction, CopyUUIDsFromHistory]

View file

@ -18,7 +18,7 @@ class ValidateLookIgnoreColorSpace(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder
families = ['colorbleed.look']
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look RAW Ignore color space'
actions = [colorbleed.api.SelectInvalidAction]

View file

@ -1,14 +1,13 @@
import maya.cmds as cmds
import pyblish.api
import colorbleed.api
import colorbleed.maya.lib as lib
class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin):
class ValidateLookMembersHaveId(pyblish.api.InstancePlugin):
"""Validate look members have colorbleed id attributes
Looks up the contents of the look to see if all its members have
colorbleed id attributes so they can be connected correctly.
Colorbleed Id attributes so they can be connected correctly.
When invalid it's very likely related to the model not having the id
attributes that it should have. These should have been generated in the
@ -17,35 +16,12 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidatePipelineOrder
families = ['colorbleed.look']
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look Members Id Attributes'
label = 'Look Members Have ID Attribute'
actions = [colorbleed.api.SelectInvalidAction,
colorbleed.api.GenerateUUIDsOnInvalidAction]
@staticmethod
def get_invalid(instance):
# Get all members from the sets
members = []
relations = instance.data["lookData"]["relationships"]
for sg in relations:
sg_members = sg['members']
sg_members = [member['name'] for member in sg_members]
members.extend(sg_members)
# Get all sets
members = list(set(members))
# Ensure all nodes have a cbId
invalid = list()
for node in members:
if not cmds.getAttr("{}.cbId".format(node)):
invalid.append(node)
return invalid
def process(self, instance):
"""Process all meshes"""
@ -53,3 +29,24 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin):
if invalid:
raise RuntimeError("Members found without "
"asset IDs: {0}".format(invalid))
@classmethod
def get_invalid(cls, instance):
# Get all members from the sets
members = []
relations = instance.data["lookData"]["relationships"]
for relation in relations:
members = [member['name'] for member in relation['members']]
members.extend(members)
# Get all sets
members = list(set(members))
# Ensure all nodes have a cbId
invalid = list()
for node in members:
if not lib.get_id(node):
invalid.append(node)
return invalid

View file

@ -15,8 +15,9 @@ def get_unique_id(node):
return unique_id
class ValidateLookMembersUnique(pyblish.api.InstancePlugin):
"""Validate members of look are unique.
class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin):
"""Validate the relational nodes of the look data to ensure every node is
unique.
This ensures the same id is not present as more than one node in the look.
@ -29,9 +30,10 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidatePipelineOrder
families = ['colorbleed.look']
label = 'Non Duplicate Relationship Members (ID)'
hosts = ['maya']
label = 'Look Members Unique'
families = ['colorbleed.lookdev']
actions = [colorbleed.api.SelectInvalidAction,
colorbleed.api.GenerateUUIDsOnInvalidAction]
@ -42,8 +44,7 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin):
members = []
relationships = instance.data["lookData"]["relationships"]
for sg in relationships:
sg_members = sg['members']
sg_members = [member['name'] for member in sg_members]
sg_members = [member['name'] for member in sg['members']]
members.extend(sg_members)
# Ensure we don't have components but the objects
@ -68,9 +69,7 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin):
def process(self, instance):
"""Process all meshes"""
print self.actions
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Members found without "
"asset IDs: {0}".format(invalid))
raise RuntimeError("Members found without asset IDs: "
"{0}".format(invalid))

View file

@ -22,65 +22,51 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidateContentsOrder
families = ['colorbleed.look']
order = colorbleed.api.ValidateContentsOrder + 0.01
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look No Default Shaders'
actions = [colorbleed.api.SelectInvalidAction]
@classmethod
def get_invalid_sets(cls, instance):
def get_invalid(cls, instance):
invalid = []
disallowed = ["lambert1",
"initialShadingGroup",
"initialParticleSE",
"particleCloud1"]
disallowed = set(disallowed)
# Check among the sets
lookdata = instance.data["lookData"]
sets = lookdata['sets']
lookup = set(sets)
intersect = lookup.intersection(disallowed)
if intersect:
cls.log.error("Default shaders found in the "
"look: {0}".format(list(intersect)))
return list(intersect)
setmembers = instance.data["setMembers"]
members = cmds.listRelatives(setmembers,
allDescendents=True,
type="shape")
# Check among history/inputs of the sets
history = cmds.listHistory(sets) or []
lookup = set(history)
for member in members:
intersect = lookup.intersection(disallowed)
if intersect:
cls.log.error("Default shaders found in the history of the "
"look: {0}".format(list(intersect)))
return list(intersect)
# get connection
# listConnections returns a list or None
shading_engine = cmds.listConnections(member, type="shadingEngine")
if not shading_engine:
cls.log.error("Detected shape without shading engine : "
"'{}'".format(member))
invalid.append(member)
continue
return list()
@classmethod
def get_invalid(cls, instance):
shaders = cls.get_invalid_sets(instance)
nodes = instance[:]
# Get members of the shaders
all = set()
for shader in shaders:
members = cmds.sets(shader, query=True) or []
members = cmds.ls(members, long=True)
all.update(members)
# Get the instance nodes among the shader members
invalid = all.intersection(nodes)
invalid = list(invalid)
# retrieve the shading engine out of the list
shading_engine = shading_engine[0]
if shading_engine in disallowed:
cls.log.error("Member connected to a disallows objectSet: "
"'{}'".format(member))
else:
continue
return invalid
def process(self, instance):
"""Process all the nodes in the instance"""
sets = self.get_invalid_sets(instance)
# sets = self.get_invalid_sets(instance)
sets = self.get_invalid(instance)
if sets:
raise RuntimeError("Invalid shaders found: {0}".format(sets))

View file

@ -12,7 +12,7 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin):
"""
order = colorbleed.api.ValidatePipelineOrder
families = ['colorbleed.look']
families = ['colorbleed.lookdev']
hosts = ['maya']
label = 'Look Id Unique Attributes'
actions = [colorbleed.api.SelectInvalidAction,
@ -28,8 +28,8 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin):
invalid = list()
for node in nodes:
unique_id = None
if cmds.attributeQuery("mbId", node=node, exists=True):
unique_id = cmds.getAttr("{}.mbId".format(node))
if cmds.attributeQuery("cbId", node=node, exists=True):
unique_id = cmds.getAttr("{}.cbId".format(node))
if not unique_id:
continue

View file

@ -24,8 +24,6 @@ class ValidateModelContent(pyblish.api.InstancePlugin):
@classmethod
def get_invalid(cls, instance):
pprint.pprint(instance.data)
content_instance = instance.data.get("setMembers", None)
if not content_instance:
cls.log.error("Instance has no nodes!")

View file

@ -0,0 +1,51 @@
import maya.cmds as cmds
import pyblish.api
import colorbleed.api
from colorbleed.maya import lib
class ValidateNodeIDs(pyblish.api.InstancePlugin):
"""Validate nodes have a Colorbleed Id
"""
order = colorbleed.api.ValidatePipelineOrder
label = 'Node Ids (ID)'
hosts = ['maya']
families = ["colorbleed.model",
"colorbleed.lookdev",
"colorbleed.rig"]
actions = [colorbleed.api.SelectInvalidAction,
colorbleed.api.GenerateUUIDsOnInvalidAction]
def process(self, instance):
"""Process all meshes"""
# Ensure all nodes have a cbId
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Nodes found without "
"IDs: {0}".format(invalid))
@classmethod
def get_invalid(cls, instance):
"""Return the member nodes that are invalid"""
invalid = list()
# TODO: Implement check on only nodes like on_save callback.
instance_shape = cmds.ls(instance, type="shape")
# We do want to check the referenced nodes as we it might be
# part of the end product
nodes = lib.filter_out_nodes(set(instance_shape), defaults=True)
for node in nodes:
if not lib.get_id(node):
invalid.append(node)
return invalid

View file

@ -1,20 +1,22 @@
from collections import defaultdict
import maya.cmds as cmds
import pyblish.api
import colorbleed.api
import colorbleed.maya.lib as lib
class ValidateUniqueNodeIds(pyblish.api.InstancePlugin):
"""Validate nodes have colorbleed id attributes"""
class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin):
"""Validate the nodes in the instance have a unique Colorbleed Id
Here we ensure that what has been added to the instance is unique
"""
order = colorbleed.api.ValidatePipelineOrder
label = 'Unique Id Attributes'
label = 'Non Duplicate Instance Members (ID)'
hosts = ['maya']
families = ['colorbleed.model',
'colorbleed.lookdev',
'colorbleed.rig']
families = ["colorbleed.model",
"colorbleed.lookdev",
"colorbleed.rig"]
actions = [colorbleed.api.SelectInvalidAction,
colorbleed.api.GenerateUUIDsOnInvalidAction]
@ -23,15 +25,12 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin):
def get_invalid_dict(cls, instance):
"""Return a dictionary mapping of id key to list of member nodes"""
uuid_attr = "cbId"
# Collect each id with their members
ids = defaultdict(list)
for member in instance:
if not cmds.attributeQuery(uuid_attr, node=member, exists=True):
object_id = lib.get_id(member)
if not object_id:
continue
object_id = cmds.getAttr("{}.{}".format(member, uuid_attr))
ids[object_id].append(member)
# Skip those without IDs (if everything should have an ID that should

View file

@ -4,10 +4,10 @@ import os
import avalon.io as io
class CollectResourceDestination(pyblish.api.InstancePlugin):
class CollectAssumedDestination(pyblish.api.InstancePlugin):
"""This plug-ins displays the comment dialog box per default"""
label = "Collect Resource Destination"
label = "Collect Assumed Destination"
order = pyblish.api.CollectorOrder + 0.499
def process(self, instance):
@ -63,15 +63,22 @@ class CollectResourceDestination(pyblish.api.InstancePlugin):
# get all the stuff from the database
subset_name = instance.data["subset"]
asset_name = instance.data["asset"]
project_name = os.environ["AVALON_PROJECT"]
project = io.find_one({"type": "project",
"name": project_name},
projection={"config": True})
template = project["config"]["template"]["publish"]
asset = io.find_one({"type": "asset",
"name": asset_name,
"parent": project["_id"]})
subset = io.find_one({"type": "subset",
"name": subset_name})
"name": subset_name,
"parent": asset["_id"]})
# assume there is no version yet, we start at `1`
version_number = 1
@ -85,7 +92,7 @@ class CollectResourceDestination(pyblish.api.InstancePlugin):
template_data = {"root": os.environ["AVALON_ROOT"],
"project": project_name,
"silo": os.environ["AVALON_SILO"],
"asset": instance.data["asset"],
"asset": asset_name,
"subset": subset_name,
"version": version_number,
"representation": "TEMP"}

View file

@ -4,7 +4,7 @@ import pyblish.api
class CollectMindbenderComment(pyblish.api.ContextPlugin):
"""This plug-ins displays the comment dialog box per default"""
label = "Collect Mindbender Time"
label = "Collect Comment"
order = pyblish.api.CollectorOrder
def process(self, context):