mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Embed all internal Colorbleed library (cb) code into config
This commit is contained in:
parent
a9f822757d
commit
128b285654
11 changed files with 689 additions and 57 deletions
|
|
@ -2,6 +2,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import importlib
|
import importlib
|
||||||
|
import itertools
|
||||||
|
|
||||||
from .vendor import pather
|
from .vendor import pather
|
||||||
from .vendor.pather.error import ParseError
|
from .vendor.pather.error import ParseError
|
||||||
|
|
@ -12,6 +13,24 @@ import avalon.api
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def pairwise(iterable):
|
||||||
|
"""s -> (s0,s1), (s2,s3), (s4, s5), ..."""
|
||||||
|
a = iter(iterable)
|
||||||
|
return itertools.izip(a, a)
|
||||||
|
|
||||||
|
|
||||||
|
def grouper(iterable, n, fillvalue=None):
|
||||||
|
"""Collect data into fixed-length chunks or blocks
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = [iter(iterable)] * n
|
||||||
|
return itertools.izip_longest(fillvalue=fillvalue, *args)
|
||||||
|
|
||||||
|
|
||||||
def is_latest(representation):
|
def is_latest(representation):
|
||||||
"""Return whether the representation is from latest version
|
"""Return whether the representation is from latest version
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,11 @@ INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000}
|
||||||
FLOAT_FPS = {23.976, 29.97, 47.952, 59.94}
|
FLOAT_FPS = {23.976, 29.97, 47.952, 59.94}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_mel_global(name):
|
||||||
|
"""Return the value of a mel global variable"""
|
||||||
|
return mel.eval("$%s = $%s;" % (name, name))
|
||||||
|
|
||||||
|
|
||||||
def matrix_equals(a, b, tolerance=1e-10):
|
def matrix_equals(a, b, tolerance=1e-10):
|
||||||
"""
|
"""
|
||||||
Compares two matrices with an imperfection tolerance
|
Compares two matrices with an imperfection tolerance
|
||||||
|
|
@ -306,6 +311,33 @@ def attribute_values(attr_values):
|
||||||
cmds.setAttr(attr, value)
|
cmds.setAttr(attr, value)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def keytangent_default(in_tangent_type='auto',
|
||||||
|
out_tangent_type='auto'):
|
||||||
|
"""Set the default keyTangent for new keys during this context"""
|
||||||
|
|
||||||
|
original_itt = cmds.keyTangent(query=True, g=True, itt=True)[0]
|
||||||
|
original_ott = cmds.keyTangent(query=True, g=True, ott=True)[0]
|
||||||
|
cmds.keyTangent(g=True, itt=in_tangent_type)
|
||||||
|
cmds.keyTangent(g=True, ott=out_tangent_type)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
cmds.keyTangent(g=True, itt=original_itt)
|
||||||
|
cmds.keyTangent(g=True, ott=original_ott)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def undo_chunk():
|
||||||
|
"""Open a undo chunk during context."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmds.undoInfo(openChunk=True)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
cmds.undoInfo(closeChunk=True)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def renderlayer(layer):
|
def renderlayer(layer):
|
||||||
"""Set the renderlayer during the context"""
|
"""Set the renderlayer during the context"""
|
||||||
|
|
@ -339,6 +371,126 @@ def evaluation(mode="off"):
|
||||||
cmds.evaluationManager(mode=original)
|
cmds.evaluationManager(mode=original)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def no_refresh():
|
||||||
|
"""Temporarily disables Maya's UI updates
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This only disabled the main pane and will sometimes still
|
||||||
|
trigger updates in torn off panels.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pane = _get_mel_global('gMainPane')
|
||||||
|
state = cmds.paneLayout(pane, query=True, manage=True)
|
||||||
|
cmds.paneLayout(pane, edit=True, manage=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
cmds.paneLayout(pane, edit=True, manage=state)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def empty_sets(sets, force=False):
|
||||||
|
"""Remove all members of the sets during the context"""
|
||||||
|
|
||||||
|
assert isinstance(sets, (list, tuple))
|
||||||
|
|
||||||
|
original = dict()
|
||||||
|
original_connections = []
|
||||||
|
|
||||||
|
# Store original state
|
||||||
|
for obj_set in sets:
|
||||||
|
members = cmds.sets(obj_set, query=True)
|
||||||
|
original[obj_set] = members
|
||||||
|
|
||||||
|
try:
|
||||||
|
for obj_set in sets:
|
||||||
|
cmds.sets(clear=obj_set)
|
||||||
|
if force:
|
||||||
|
# Break all connections if force is enabled, this way we
|
||||||
|
# prevent Maya from exporting any reference nodes which are
|
||||||
|
# connected with placeHolder[x] attributes
|
||||||
|
plug = "%s.dagSetMembers" % obj_set
|
||||||
|
connections = cmds.listConnections(plug,
|
||||||
|
source=True,
|
||||||
|
destination=False,
|
||||||
|
plugs=True,
|
||||||
|
connections=True) or []
|
||||||
|
original_connections.extend(connections)
|
||||||
|
for dest, src in pairwise(connections):
|
||||||
|
cmds.disconnectAttr(src, dest)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
|
||||||
|
for dest, src in pairwise(original_connections):
|
||||||
|
cmds.connectAttr(src, dest)
|
||||||
|
|
||||||
|
# Restore original members
|
||||||
|
for origin_set, members in original.iteritems():
|
||||||
|
cmds.sets(members, forceElement=origin_set)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def renderlayer(layer):
|
||||||
|
"""Set the renderlayer during the context
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
layer (str): Name of layer to switch to.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
original = cmds.editRenderLayerGlobals(query=True,
|
||||||
|
currentRenderLayer=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmds.editRenderLayerGlobals(currentRenderLayer=layer)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
cmds.editRenderLayerGlobals(currentRenderLayer=original)
|
||||||
|
|
||||||
|
|
||||||
|
class delete_after(object):
|
||||||
|
"""Context Manager that will delete collected nodes after exit.
|
||||||
|
|
||||||
|
This allows to ensure the nodes added to the context are deleted
|
||||||
|
afterwards. This is useful if you want to ensure nodes are deleted
|
||||||
|
even if an error is raised.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
with delete_after() as delete_bin:
|
||||||
|
cube = maya.cmds.polyCube()
|
||||||
|
delete_bin.extend(cube)
|
||||||
|
# cube exists
|
||||||
|
# cube deleted
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, nodes=None):
|
||||||
|
|
||||||
|
self._nodes = list()
|
||||||
|
|
||||||
|
if nodes:
|
||||||
|
self.extend(nodes)
|
||||||
|
|
||||||
|
def append(self, node):
|
||||||
|
self._nodes.append(node)
|
||||||
|
|
||||||
|
def extend(self, nodes):
|
||||||
|
self._nodes.extend(nodes)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._nodes)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
if self._nodes:
|
||||||
|
cmds.delete(self._nodes)
|
||||||
|
|
||||||
|
|
||||||
def get_renderer(layer):
|
def get_renderer(layer):
|
||||||
with renderlayer(layer):
|
with renderlayer(layer):
|
||||||
return cmds.getAttr("defaultRenderGlobals.currentRenderer")
|
return cmds.getAttr("defaultRenderGlobals.currentRenderer")
|
||||||
|
|
@ -367,6 +519,157 @@ def no_undo(flush=False):
|
||||||
cmds.undoInfo(**{keyword: original})
|
cmds.undoInfo(**{keyword: original})
|
||||||
|
|
||||||
|
|
||||||
|
def get_shader_assignments_from_shapes(shapes):
|
||||||
|
"""Return the shape assignment per related shading engines.
|
||||||
|
|
||||||
|
Returns a dictionary where the keys are shadingGroups and the values are
|
||||||
|
lists of assigned shapes or shape-components.
|
||||||
|
|
||||||
|
For the 'shapes' this will return a dictionary like:
|
||||||
|
{
|
||||||
|
"shadingEngineX": ["nodeX", "nodeY"],
|
||||||
|
"shadingEngineY": ["nodeA", "nodeB"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Args:
|
||||||
|
shapes (list): The shapes to collect the assignments for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The {shadingEngine: shapes} relationships
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
shapes = cmds.ls(shapes,
|
||||||
|
long=True,
|
||||||
|
selection=True,
|
||||||
|
shapes=True,
|
||||||
|
objectsOnly=True)
|
||||||
|
if not shapes:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Collect shading engines and their shapes
|
||||||
|
assignments = defaultdict(list)
|
||||||
|
for shape in shapes:
|
||||||
|
|
||||||
|
# Get unique shading groups for the shape
|
||||||
|
shading_groups = cmds.listConnections(shape,
|
||||||
|
type="shadingEngine") or []
|
||||||
|
shading_groups = list(set(shading_groups))
|
||||||
|
for shading_group in shading_groups:
|
||||||
|
assignments[shading_group].add(shape)
|
||||||
|
|
||||||
|
return dict(assignments)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def shader(nodes, shadingEngine="initialShadingGroup"):
|
||||||
|
"""Assign a shader to nodes during the context"""
|
||||||
|
|
||||||
|
shapes = cmds.ls(nodes, dag=1, o=1, shapes=1, long=1)
|
||||||
|
original = get_shader_assignments_from_shapes(shapes)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Assign override shader
|
||||||
|
if shapes:
|
||||||
|
cmds.sets(shapes, edit=True, forceElement=shadingEngine)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
|
||||||
|
# Assign original shaders
|
||||||
|
for sg, members in original.items():
|
||||||
|
if members:
|
||||||
|
cmds.sets(shapes, edit=True, forceElement=shadingEngine)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def displaySmoothness(nodes,
|
||||||
|
divisionsU=0,
|
||||||
|
divisionsV=0,
|
||||||
|
pointsWire=4,
|
||||||
|
pointsShaded=1,
|
||||||
|
polygonObject=1):
|
||||||
|
"""Set the displaySmoothness during the context"""
|
||||||
|
|
||||||
|
# Ensure only non-intermediate shapes
|
||||||
|
nodes = cmds.ls(nodes,
|
||||||
|
dag=1,
|
||||||
|
shapes=1,
|
||||||
|
long=1,
|
||||||
|
noIntermediate=True)
|
||||||
|
|
||||||
|
def parse(node):
|
||||||
|
"""Parse the current state of a node"""
|
||||||
|
state = {}
|
||||||
|
for key in ["divisionsU",
|
||||||
|
"divisionsV",
|
||||||
|
"pointsWire",
|
||||||
|
"pointsShaded",
|
||||||
|
"polygonObject"]:
|
||||||
|
value = cmds.displaySmoothness(node, query=1, **{key: True})
|
||||||
|
if value is not None:
|
||||||
|
state[key] = value[0]
|
||||||
|
return state
|
||||||
|
|
||||||
|
originals = dict((node, parse(node)) for node in nodes)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Apply current state
|
||||||
|
cmds.displaySmoothness(nodes,
|
||||||
|
divisionsU=divisionsU,
|
||||||
|
divisionsV=divisionsV,
|
||||||
|
pointsWire=pointsWire,
|
||||||
|
pointsShaded=pointsShaded,
|
||||||
|
polygonObject=polygonObject)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
# Revert state
|
||||||
|
for node, state in originals.iteritems():
|
||||||
|
if state:
|
||||||
|
cmds.displaySmoothness(node, **state)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def no_display_layers(nodes):
|
||||||
|
"""Ensure nodes are not in a displayLayer during context.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
nodes (list): The nodes to remove from any display layer.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Ensure long names
|
||||||
|
nodes = cmds.ls(nodes, long=True)
|
||||||
|
|
||||||
|
# Get the original state
|
||||||
|
lookup = set(nodes)
|
||||||
|
original = {}
|
||||||
|
for layer in cmds.ls(type='displayLayer'):
|
||||||
|
|
||||||
|
# Skip default layer
|
||||||
|
if layer == "defaultLayer":
|
||||||
|
continue
|
||||||
|
|
||||||
|
members = cmds.editDisplayLayerMembers(layer,
|
||||||
|
query=True,
|
||||||
|
fullNames=True)
|
||||||
|
if not members:
|
||||||
|
continue
|
||||||
|
members = set(members)
|
||||||
|
|
||||||
|
included = lookup.intersection(members)
|
||||||
|
if included:
|
||||||
|
original[layer] = list(included)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Add all nodes to default layer
|
||||||
|
cmds.editDisplayLayerMembers("defaultLayer", nodes, noRecurse=True)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
# Restore original members
|
||||||
|
for layer, members in original.iteritems():
|
||||||
|
cmds.editDisplayLayerMembers(layer, members, noRecurse=True)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def namespaced(namespace, new=True):
|
def namespaced(namespace, new=True):
|
||||||
"""Work inside namespace during context
|
"""Work inside namespace during context
|
||||||
|
|
@ -1534,3 +1837,193 @@ def validate_fps():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def bake(nodes,
|
||||||
|
frame_range=None,
|
||||||
|
step=1.0,
|
||||||
|
simulation=True,
|
||||||
|
preserve_outside_keys=False,
|
||||||
|
disable_implicit_control=True,
|
||||||
|
shape=True):
|
||||||
|
"""Bake the given nodes over the time range.
|
||||||
|
|
||||||
|
This will bake all attributes of the node, including custom attributes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodes (list): Names of transform nodes, eg. camera, light.
|
||||||
|
frame_range (tuple): frame range with start and end frame.
|
||||||
|
or if None then takes timeSliderRange
|
||||||
|
simulation (bool): Whether to perform a full simulation of the
|
||||||
|
attributes over time.
|
||||||
|
preserve_outside_keys (bool): Keep keys that are outside of the baked
|
||||||
|
range.
|
||||||
|
disable_implicit_control (bool): When True will disable any
|
||||||
|
constraints to the object.
|
||||||
|
shape (bool): When True also bake attributes on the children shapes.
|
||||||
|
step (float): The step size to sample by.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Parse inputs
|
||||||
|
if not nodes:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert isinstance(nodes, (list, tuple)), "Nodes must be a list or tuple"
|
||||||
|
|
||||||
|
# If frame range is None fall back to time slider range
|
||||||
|
if frame_range is None:
|
||||||
|
frame_range = getTimeSliderRange()
|
||||||
|
|
||||||
|
# If frame range is single frame bake one frame more,
|
||||||
|
# otherwise maya.cmds.bakeResults gets confused
|
||||||
|
if frame_range[1] == frame_range[0]:
|
||||||
|
frame_range[1] += 1
|
||||||
|
|
||||||
|
# Bake it
|
||||||
|
with keytangent_default(in_tangent_type='auto',
|
||||||
|
out_tangent_type='auto'):
|
||||||
|
cmds.bakeResults(nodes,
|
||||||
|
simulation=simulation,
|
||||||
|
preserveOutsideKeys=preserve_outside_keys,
|
||||||
|
disableImplicitControl=disable_implicit_control,
|
||||||
|
shape=shape,
|
||||||
|
sampleBy=step,
|
||||||
|
time=(frame_range[0], frame_range[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def bake_to_world_space(nodes,
|
||||||
|
frameRange=None,
|
||||||
|
simulation=True,
|
||||||
|
preserveOutsideKeys=False,
|
||||||
|
disableImplicitControl=True,
|
||||||
|
shape=True,
|
||||||
|
step=1.0):
|
||||||
|
"""Bake the nodes to world space transformation (incl. other attributes)
|
||||||
|
|
||||||
|
Bakes the transforms to world space (while maintaining all its animated
|
||||||
|
attributes and settings) by duplicating the node. Then parents it to world
|
||||||
|
and constrains to the original.
|
||||||
|
|
||||||
|
Other attributes are also baked by connecting all attributes directly.
|
||||||
|
Baking is then done using Maya's bakeResults command.
|
||||||
|
|
||||||
|
See `bake` for the argument documentation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: The newly created and baked node names.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_attrs(node):
|
||||||
|
"""Workaround for buggy shape attribute listing with listAttr"""
|
||||||
|
attrs = cmds.listAttr(node,
|
||||||
|
write=True,
|
||||||
|
scalar=True,
|
||||||
|
settable=True,
|
||||||
|
connectable=True,
|
||||||
|
keyable=True,
|
||||||
|
shortNames=True) or []
|
||||||
|
valid_attrs = []
|
||||||
|
for attr in attrs:
|
||||||
|
node_attr = '{0}.{1}'.format(node, attr)
|
||||||
|
|
||||||
|
# Sometimes Maya returns 'non-existent' attributes for shapes
|
||||||
|
# so we filter those out
|
||||||
|
if not cmds.attributeQuery(attr, node=node, exists=True):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We only need those that have a connection, just to be safe
|
||||||
|
# that it's actually keyable/connectable anyway.
|
||||||
|
if cmds.connectionInfo(node_attr,
|
||||||
|
isDestination=True):
|
||||||
|
valid_attrs.append(attr)
|
||||||
|
|
||||||
|
return valid_attrs
|
||||||
|
|
||||||
|
transform_attrs = set(["t", "r", "s",
|
||||||
|
"tx", "ty", "tz",
|
||||||
|
"rx", "ry", "rz",
|
||||||
|
"sx", "sy", "sz"])
|
||||||
|
|
||||||
|
world_space_nodes = []
|
||||||
|
with delete_after() as delete_bin:
|
||||||
|
|
||||||
|
# Create the duplicate nodes that are in world-space connected to
|
||||||
|
# the originals
|
||||||
|
for node in nodes:
|
||||||
|
|
||||||
|
# Duplicate the node
|
||||||
|
short_name = node.rsplit("|", 1)[-1]
|
||||||
|
new_name = "{0}_baked".format(short_name)
|
||||||
|
new_node = cmds.duplicate(node,
|
||||||
|
name=new_name,
|
||||||
|
renameChildren=True)[0]
|
||||||
|
|
||||||
|
# Connect all attributes on the node except for transform
|
||||||
|
# attributes
|
||||||
|
attrs = _get_attrs(node)
|
||||||
|
attrs = set(attrs) - transform_attrs if attrs else []
|
||||||
|
|
||||||
|
for attr in attrs:
|
||||||
|
orig_node_attr = '{0}.{1}'.format(node, attr)
|
||||||
|
new_node_attr = '{0}.{1}'.format(new_node, attr)
|
||||||
|
|
||||||
|
# unlock to avoid connection errors
|
||||||
|
cmds.setAttr(new_node_attr, lock=False)
|
||||||
|
|
||||||
|
cmds.connectAttr(orig_node_attr,
|
||||||
|
new_node_attr,
|
||||||
|
force=True)
|
||||||
|
|
||||||
|
# If shapes are also baked then connect those keyable attributes
|
||||||
|
if shape:
|
||||||
|
children_shapes = cmds.listRelatives(new_node,
|
||||||
|
children=True,
|
||||||
|
fullPath=True,
|
||||||
|
shapes=True)
|
||||||
|
if children_shapes:
|
||||||
|
orig_children_shapes = cmds.listRelatives(node,
|
||||||
|
children=True,
|
||||||
|
fullPath=True,
|
||||||
|
shapes=True)
|
||||||
|
for orig_shape, new_shape in zip(orig_children_shapes,
|
||||||
|
children_shapes):
|
||||||
|
attrs = _get_attrs(orig_shape)
|
||||||
|
for attr in attrs:
|
||||||
|
orig_node_attr = '{0}.{1}'.format(orig_shape, attr)
|
||||||
|
new_node_attr = '{0}.{1}'.format(new_shape, attr)
|
||||||
|
|
||||||
|
# unlock to avoid connection errors
|
||||||
|
cmds.setAttr(new_node_attr, lock=False)
|
||||||
|
|
||||||
|
cmds.connectAttr(orig_node_attr,
|
||||||
|
new_node_attr,
|
||||||
|
force=True)
|
||||||
|
|
||||||
|
# Parent to world
|
||||||
|
if cmds.listRelatives(new_node, parent=True):
|
||||||
|
new_node = cmds.parent(new_node, world=True)[0]
|
||||||
|
|
||||||
|
# Unlock transform attributes so constraint can be created
|
||||||
|
for attr in transform_attrs:
|
||||||
|
cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False)
|
||||||
|
|
||||||
|
# Constraints
|
||||||
|
delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False))
|
||||||
|
delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False))
|
||||||
|
|
||||||
|
world_space_nodes.append(new_node)
|
||||||
|
|
||||||
|
bake(world_space_nodes,
|
||||||
|
frame_range=frameRange,
|
||||||
|
step=step,
|
||||||
|
simulation=simulation,
|
||||||
|
preserve_outside_keys=preserveOutsideKeys,
|
||||||
|
disable_implicit_control=disableImplicitControl,
|
||||||
|
shape=shape)
|
||||||
|
|
||||||
|
return world_space_nodes
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,6 @@ class CreateCamera(avalon.maya.Creator):
|
||||||
|
|
||||||
# Bake to world space by default, when this is False it will also
|
# Bake to world space by default, when this is False it will also
|
||||||
# include the parent hierarchy in the baked results
|
# include the parent hierarchy in the baked results
|
||||||
data['bakeToWorldSpace'] = True
|
data['bake_to_world_space'] = True
|
||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
from maya import cmds
|
from maya import cmds
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import colorbleed.maya.lib as lib
|
import colorbleed.maya.lib as lib
|
||||||
from cb.utils.maya import context, shaders
|
|
||||||
|
|
||||||
SHAPE_ATTRS = ["castsShadows",
|
SHAPE_ATTRS = ["castsShadows",
|
||||||
"receiveShadows",
|
"receiveShadows",
|
||||||
|
|
@ -48,6 +51,139 @@ def get_look_attrs(node):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def node_uses_image_sequence(node):
|
||||||
|
"""Return whether file node uses an image sequence or single image.
|
||||||
|
|
||||||
|
Determine if a node uses an image sequence or just a single image,
|
||||||
|
not always obvious from its file path alone.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node (str): Name of the Maya node
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if node uses an image sequence
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# useFrameExtension indicates an explicit image sequence
|
||||||
|
node_path = get_file_node_path(node).lower()
|
||||||
|
|
||||||
|
# The following tokens imply a sequence
|
||||||
|
patterns = ["<udim>", "<tile>", "<uvtile>", "u<u>_v<v>", "<frame0"]
|
||||||
|
|
||||||
|
return (cmds.getAttr('%s.useFrameExtension' % node) or
|
||||||
|
any(pattern in node_path for pattern in patterns))
|
||||||
|
|
||||||
|
|
||||||
|
def seq_to_glob(path):
|
||||||
|
"""Takes an image sequence path and returns it in glob format,
|
||||||
|
with the frame number replaced by a '*'.
|
||||||
|
|
||||||
|
Image sequences may be numerical sequences, e.g. /path/to/file.1001.exr
|
||||||
|
will return as /path/to/file.*.exr.
|
||||||
|
|
||||||
|
Image sequences may also use tokens to denote sequences, e.g.
|
||||||
|
/path/to/texture.<UDIM>.tif will return as /path/to/texture.*.tif.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): the image sequence path
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Return glob string that matches the filename pattern.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# If any of the patterns, convert the pattern
|
||||||
|
patterns = {
|
||||||
|
"<udim>": "<udim>",
|
||||||
|
"<tile>": "<tile>",
|
||||||
|
"<uvtile>": "<uvtile>",
|
||||||
|
"#": "#",
|
||||||
|
"u<u>_v<v>": "<u>|<v>",
|
||||||
|
"<frame0": "<frame0\d+>",
|
||||||
|
"<f>": "<f>"
|
||||||
|
}
|
||||||
|
|
||||||
|
lower = path.lower()
|
||||||
|
has_pattern = False
|
||||||
|
for pattern, regex_pattern in patterns.items():
|
||||||
|
if pattern in lower:
|
||||||
|
path = re.sub(regex_pattern, "*", path, flags=re.IGNORECASE)
|
||||||
|
has_pattern = True
|
||||||
|
|
||||||
|
if has_pattern:
|
||||||
|
return path
|
||||||
|
|
||||||
|
base = os.path.basename(path)
|
||||||
|
matches = list(re.finditer(r'\d+', base))
|
||||||
|
if matches:
|
||||||
|
match = matches[-1]
|
||||||
|
new_base = '{0}*{1}'.format(base[:match.start()],
|
||||||
|
base[match.end():])
|
||||||
|
head = os.path.dirname(path)
|
||||||
|
return os.path.join(head, new_base)
|
||||||
|
else:
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_node_path(node):
|
||||||
|
"""Get the file path used by a Maya file node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node (str): Name of the Maya file node
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the file path in use
|
||||||
|
|
||||||
|
"""
|
||||||
|
# if the path appears to be sequence, use computedFileTextureNamePattern,
|
||||||
|
# this preserves the <> tag
|
||||||
|
if cmds.attributeQuery('computedFileTextureNamePattern',
|
||||||
|
node=node,
|
||||||
|
exists=True):
|
||||||
|
plug = '{0}.computedFileTextureNamePattern'.format(node)
|
||||||
|
texture_pattern = cmds.getAttr(plug)
|
||||||
|
|
||||||
|
patterns = ["<udim>",
|
||||||
|
"<tile>",
|
||||||
|
"u<u>_v<v>",
|
||||||
|
"<f>",
|
||||||
|
"<frame0",
|
||||||
|
"<uvtile>"]
|
||||||
|
lower = texture_pattern.lower()
|
||||||
|
if any(pattern in lower for pattern in patterns):
|
||||||
|
return texture_pattern
|
||||||
|
|
||||||
|
# otherwise use fileTextureName
|
||||||
|
return cmds.getAttr('{0}.fileTextureName'.format(node))
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_node_files(node):
|
||||||
|
"""Return the file paths related to the file node
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Will only return existing files. Returns an empty list
|
||||||
|
if not valid existing files are linked.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of full file paths.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
path = get_file_node_path(node)
|
||||||
|
path = cmds.workspace(expandName=path)
|
||||||
|
if node_uses_image_sequence(node):
|
||||||
|
glob_pattern = seq_to_glob(path)
|
||||||
|
return glob.glob(glob_pattern)
|
||||||
|
elif os.path.exists(path):
|
||||||
|
return [path]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class CollectLook(pyblish.api.InstancePlugin):
|
class CollectLook(pyblish.api.InstancePlugin):
|
||||||
"""Collect look data for instance.
|
"""Collect look data for instance.
|
||||||
|
|
||||||
|
|
@ -74,7 +210,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
||||||
def process(self, instance):
|
def process(self, instance):
|
||||||
"""Collect the Look in the instance with the correct layer settings"""
|
"""Collect the Look in the instance with the correct layer settings"""
|
||||||
|
|
||||||
with context.renderlayer(instance.data["renderlayer"]):
|
with lib.renderlayer(instance.data["renderlayer"]):
|
||||||
self.collect(instance)
|
self.collect(instance)
|
||||||
|
|
||||||
def collect(self, instance):
|
def collect(self, instance):
|
||||||
|
|
@ -268,7 +404,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
||||||
# paths as the computed patterns
|
# paths as the computed patterns
|
||||||
source = source.replace("\\", "/")
|
source = source.replace("\\", "/")
|
||||||
|
|
||||||
files = shaders.get_file_node_files(node)
|
files = get_file_node_files(node)
|
||||||
if len(files) == 0:
|
if len(files) == 0:
|
||||||
self.log.error("No valid files found from node `%s`" % node)
|
self.log.error("No valid files found from node `%s`" % node)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ from maya import cmds
|
||||||
import avalon.maya
|
import avalon.maya
|
||||||
import colorbleed.api
|
import colorbleed.api
|
||||||
|
|
||||||
import cb.utils.maya.context as context
|
import colorbleed.maya.lib as lib
|
||||||
|
|
||||||
|
|
||||||
class ExtractCameraAlembic(colorbleed.api.Extractor):
|
class ExtractCameraAlembic(colorbleed.api.Extractor):
|
||||||
"""Extract a Camera as Alembic.
|
"""Extract a Camera as Alembic.
|
||||||
|
|
||||||
The cameras gets baked to world space by default. Only when the instance's
|
The cameras gets baked to world space by default. Only when the instance's
|
||||||
`bakeToWorldSpace` is set to False it will include its full hierarchy.
|
`bake_to_world_space` is set to False it will include its full hierarchy.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor):
|
||||||
instance.data.get("endFrame", 1)]
|
instance.data.get("endFrame", 1)]
|
||||||
handles = instance.data.get("handles", 0)
|
handles = instance.data.get("handles", 0)
|
||||||
step = instance.data.get("step", 1.0)
|
step = instance.data.get("step", 1.0)
|
||||||
bake_to_worldspace = instance.data("bakeToWorldSpace", True)
|
bake_to_worldspace = instance.data("bake_to_world_space", True)
|
||||||
|
|
||||||
# get cameras
|
# get cameras
|
||||||
members = instance.data['setMembers']
|
members = instance.data['setMembers']
|
||||||
|
|
@ -66,8 +66,8 @@ class ExtractCameraAlembic(colorbleed.api.Extractor):
|
||||||
|
|
||||||
job_str += ' -file "{0}"'.format(path)
|
job_str += ' -file "{0}"'.format(path)
|
||||||
|
|
||||||
with context.evaluation("off"):
|
with lib.evaluation("off"):
|
||||||
with context.no_refresh():
|
with lib.no_refresh():
|
||||||
cmds.AbcExport(j=job_str, verbose=False)
|
cmds.AbcExport(j=job_str, verbose=False)
|
||||||
|
|
||||||
if "files" not in instance.data:
|
if "files" not in instance.data:
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import os
|
import os
|
||||||
from itertools import izip_longest
|
|
||||||
|
|
||||||
from maya import cmds
|
from maya import cmds
|
||||||
|
|
||||||
import avalon.maya
|
import avalon.maya
|
||||||
import colorbleed.api
|
import colorbleed.api
|
||||||
|
from colorbleed.lib import grouper
|
||||||
import cb.utils.maya.context as context
|
from colorbleed.maya import lib
|
||||||
from cb.utils.maya.animation import bakeToWorldSpace
|
|
||||||
|
|
||||||
|
|
||||||
def massage_ma_file(path):
|
def massage_ma_file(path):
|
||||||
|
|
@ -36,18 +34,6 @@ def massage_ma_file(path):
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def grouper(iterable, n, fillvalue=None):
|
|
||||||
"""Collect data into fixed-length chunks or blocks
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
args = [iter(iterable)] * n
|
|
||||||
return izip_longest(fillvalue=fillvalue, *args)
|
|
||||||
|
|
||||||
|
|
||||||
def unlock(plug):
|
def unlock(plug):
|
||||||
"""Unlocks attribute and disconnects inputs for a plug.
|
"""Unlocks attribute and disconnects inputs for a plug.
|
||||||
|
|
||||||
|
|
@ -87,7 +73,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor):
|
||||||
will be published.
|
will be published.
|
||||||
|
|
||||||
The cameras gets baked to world space by default. Only when the instance's
|
The cameras gets baked to world space by default. Only when the instance's
|
||||||
`bakeToWorldSpace` is set to False it will include its full hierarchy.
|
`bake_to_world_space` is set to False it will include its full hierarchy.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
The extracted Maya ascii file gets "massaged" removing the uuid values
|
The extracted Maya ascii file gets "massaged" removing the uuid values
|
||||||
|
|
@ -106,12 +92,12 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor):
|
||||||
instance.data.get("endFrame", 1)]
|
instance.data.get("endFrame", 1)]
|
||||||
handles = instance.data.get("handles", 0)
|
handles = instance.data.get("handles", 0)
|
||||||
step = instance.data.get("step", 1.0)
|
step = instance.data.get("step", 1.0)
|
||||||
bake_to_worldspace = instance.data("bakeToWorldSpace", True)
|
bake_to_worldspace = instance.data("bake_to_world_space", True)
|
||||||
|
|
||||||
# TODO: Implement a bake to non-world space
|
# TODO: Implement a bake to non-world space
|
||||||
# Currently it will always bake the resulting camera to world-space
|
# Currently it will always bake the resulting camera to world-space
|
||||||
# and it does not allow to include the parent hierarchy, even though
|
# and it does not allow to include the parent hierarchy, even though
|
||||||
# with `bakeToWorldSpace` set to False it should include its hierarchy
|
# with `bake_to_world_space` set to False it should include its hierarchy
|
||||||
# to be correct with the family implementation.
|
# to be correct with the family implementation.
|
||||||
if not bake_to_worldspace:
|
if not bake_to_worldspace:
|
||||||
self.log.warning("Camera (Maya Ascii) export only supports world"
|
self.log.warning("Camera (Maya Ascii) export only supports world"
|
||||||
|
|
@ -140,11 +126,13 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor):
|
||||||
# Perform extraction
|
# Perform extraction
|
||||||
self.log.info("Performing camera bakes for: {0}".format(transform))
|
self.log.info("Performing camera bakes for: {0}".format(transform))
|
||||||
with avalon.maya.maintained_selection():
|
with avalon.maya.maintained_selection():
|
||||||
with context.evaluation("off"):
|
with lib.evaluation("off"):
|
||||||
with context.no_refresh():
|
with lib.no_refresh():
|
||||||
baked = bakeToWorldSpace(transform,
|
baked = lib.bake_to_worldspace(
|
||||||
frameRange=range_with_handles,
|
transform,
|
||||||
step=step)
|
frameRange=range_with_handles,
|
||||||
|
step=step
|
||||||
|
)
|
||||||
baked_shapes = cmds.ls(baked,
|
baked_shapes = cmds.ls(baked,
|
||||||
type="camera",
|
type="camera",
|
||||||
dag=True,
|
dag=True,
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,9 @@ from maya import cmds
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import avalon.maya
|
import avalon.maya
|
||||||
import colorbleed.api
|
|
||||||
import colorbleed.maya.lib as maya
|
|
||||||
|
|
||||||
from cb.utils.maya import context
|
import colorbleed.api
|
||||||
|
import colorbleed.maya.lib as lib
|
||||||
|
|
||||||
|
|
||||||
class ExtractLook(colorbleed.api.Extractor):
|
class ExtractLook(colorbleed.api.Extractor):
|
||||||
|
|
@ -63,10 +62,10 @@ class ExtractLook(colorbleed.api.Extractor):
|
||||||
|
|
||||||
# Extract in correct render layer
|
# Extract in correct render layer
|
||||||
layer = instance.data.get("renderlayer", "defaultRenderLayer")
|
layer = instance.data.get("renderlayer", "defaultRenderLayer")
|
||||||
with context.renderlayer(layer):
|
with lib.renderlayer(layer):
|
||||||
# TODO: Ensure membership edits don't become renderlayer overrides
|
# TODO: Ensure membership edits don't become renderlayer overrides
|
||||||
with context.empty_sets(sets, force=True):
|
with lib.empty_sets(sets, force=True):
|
||||||
with maya.attribute_values(remap):
|
with lib.attribute_values(remap):
|
||||||
with avalon.maya.maintained_selection():
|
with avalon.maya.maintained_selection():
|
||||||
cmds.select(sets, noExpand=True)
|
cmds.select(sets, noExpand=True)
|
||||||
cmds.file(maya_path,
|
cmds.file(maya_path,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ from maya import cmds
|
||||||
|
|
||||||
import avalon.maya
|
import avalon.maya
|
||||||
import colorbleed.api
|
import colorbleed.api
|
||||||
|
import colorbleed.maya.lib as lib
|
||||||
from cb.utils.maya import context
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractModel(colorbleed.api.Extractor):
|
class ExtractModel(colorbleed.api.Extractor):
|
||||||
|
|
@ -47,15 +46,15 @@ class ExtractModel(colorbleed.api.Extractor):
|
||||||
noIntermediate=True,
|
noIntermediate=True,
|
||||||
long=True)
|
long=True)
|
||||||
|
|
||||||
with context.no_display_layers(instance):
|
with lib.no_display_layers(instance):
|
||||||
with context.displaySmoothness(members,
|
with lib.displaySmoothness(members,
|
||||||
divisionsU=0,
|
divisionsU=0,
|
||||||
divisionsV=0,
|
divisionsV=0,
|
||||||
pointsWire=4,
|
pointsWire=4,
|
||||||
pointsShaded=1,
|
pointsShaded=1,
|
||||||
polygonObject=1):
|
polygonObject=1):
|
||||||
with context.shader(members,
|
with lib.shader(members,
|
||||||
shadingEngine="initialShadingGroup"):
|
shadingEngine="initialShadingGroup"):
|
||||||
with avalon.maya.maintained_selection():
|
with avalon.maya.maintained_selection():
|
||||||
cmds.select(members, noExpand=True)
|
cmds.select(members, noExpand=True)
|
||||||
cmds.file(path,
|
cmds.file(path,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ from colorbleed.maya import lib
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import colorbleed.api
|
import colorbleed.api
|
||||||
|
|
||||||
from cb.utils.maya import context
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateLookSets(pyblish.api.InstancePlugin):
|
class ValidateLookSets(pyblish.api.InstancePlugin):
|
||||||
"""Validate if any sets are missing from the instance and look data
|
"""Validate if any sets are missing from the instance and look data
|
||||||
|
|
@ -57,7 +55,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin):
|
||||||
invalid = []
|
invalid = []
|
||||||
|
|
||||||
renderlayer = instance.data.get("renderlayer", "defaultRenderLayer")
|
renderlayer = instance.data.get("renderlayer", "defaultRenderLayer")
|
||||||
with context.renderlayer(renderlayer):
|
with lib.renderlayer(renderlayer):
|
||||||
for node in instance:
|
for node in instance:
|
||||||
# get the connected objectSets of the node
|
# get the connected objectSets of the node
|
||||||
sets = lib.get_related_sets(node)
|
sets = lib.get_related_sets(node)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from maya import cmds
|
from maya import cmds
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import colorbleed.api
|
|
||||||
from cb.utils.maya.context import undo_chunk
|
|
||||||
|
|
||||||
|
import colorbleed.api
|
||||||
import colorbleed.maya.action
|
import colorbleed.maya.action
|
||||||
|
from colorbleed.maya.lib import undo_chunk
|
||||||
|
|
||||||
|
|
||||||
class ValidateRigControllers(pyblish.api.InstancePlugin):
|
class ValidateRigControllers(pyblish.api.InstancePlugin):
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from maya import cmds
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import colorbleed.api
|
import colorbleed.api
|
||||||
from cb.utils.maya.context import undo_chunk
|
|
||||||
|
|
||||||
|
import colorbleed.maya.lib as lib
|
||||||
import colorbleed.maya.action
|
import colorbleed.maya.action
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin):
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
||||||
invalid = cls.get_invalid(instance)
|
invalid = cls.get_invalid(instance)
|
||||||
with undo_chunk():
|
with lib.undo_chunk():
|
||||||
for node in invalid:
|
for node in invalid:
|
||||||
for attribute in cls.attributes:
|
for attribute in cls.attributes:
|
||||||
if cmds.attributeQuery(attribute, node=node, exists=True):
|
if cmds.attributeQuery(attribute, node=node, exists=True):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue