mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 13:52:15 +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 logging
|
||||
import importlib
|
||||
import itertools
|
||||
|
||||
from .vendor import pather
|
||||
from .vendor.pather.error import ParseError
|
||||
|
|
@ -12,6 +13,24 @@ import avalon.api
|
|||
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):
|
||||
"""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}
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Compares two matrices with an imperfection tolerance
|
||||
|
|
@ -306,6 +311,33 @@ def attribute_values(attr_values):
|
|||
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
|
||||
def renderlayer(layer):
|
||||
"""Set the renderlayer during the context"""
|
||||
|
|
@ -339,6 +371,126 @@ def evaluation(mode="off"):
|
|||
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):
|
||||
with renderlayer(layer):
|
||||
return cmds.getAttr("defaultRenderGlobals.currentRenderer")
|
||||
|
|
@ -367,6 +519,157 @@ def no_undo(flush=False):
|
|||
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
|
||||
def namespaced(namespace, new=True):
|
||||
"""Work inside namespace during context
|
||||
|
|
@ -1534,3 +1837,193 @@ def validate_fps():
|
|||
return False
|
||||
|
||||
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
|
||||
# include the parent hierarchy in the baked results
|
||||
data['bakeToWorldSpace'] = True
|
||||
data['bake_to_world_space'] = True
|
||||
|
||||
self.data = data
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import re
|
||||
import os
|
||||
import glob
|
||||
|
||||
from maya import cmds
|
||||
import pyblish.api
|
||||
import colorbleed.maya.lib as lib
|
||||
from cb.utils.maya import context, shaders
|
||||
|
||||
SHAPE_ATTRS = ["castsShadows",
|
||||
"receiveShadows",
|
||||
|
|
@ -48,6 +51,139 @@ def get_look_attrs(node):
|
|||
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):
|
||||
"""Collect look data for instance.
|
||||
|
||||
|
|
@ -74,7 +210,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
"""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)
|
||||
|
||||
def collect(self, instance):
|
||||
|
|
@ -268,7 +404,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
# paths as the computed patterns
|
||||
source = source.replace("\\", "/")
|
||||
|
||||
files = shaders.get_file_node_files(node)
|
||||
files = get_file_node_files(node)
|
||||
if len(files) == 0:
|
||||
self.log.error("No valid files found from node `%s`" % node)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ from maya import cmds
|
|||
import avalon.maya
|
||||
import colorbleed.api
|
||||
|
||||
import cb.utils.maya.context as context
|
||||
import colorbleed.maya.lib as lib
|
||||
|
||||
|
||||
class ExtractCameraAlembic(colorbleed.api.Extractor):
|
||||
"""Extract a Camera as Alembic.
|
||||
|
||||
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)]
|
||||
handles = instance.data.get("handles", 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
|
||||
members = instance.data['setMembers']
|
||||
|
|
@ -66,8 +66,8 @@ class ExtractCameraAlembic(colorbleed.api.Extractor):
|
|||
|
||||
job_str += ' -file "{0}"'.format(path)
|
||||
|
||||
with context.evaluation("off"):
|
||||
with context.no_refresh():
|
||||
with lib.evaluation("off"):
|
||||
with lib.no_refresh():
|
||||
cmds.AbcExport(j=job_str, verbose=False)
|
||||
|
||||
if "files" not in instance.data:
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import os
|
||||
from itertools import izip_longest
|
||||
|
||||
from maya import cmds
|
||||
|
||||
import avalon.maya
|
||||
import colorbleed.api
|
||||
|
||||
import cb.utils.maya.context as context
|
||||
from cb.utils.maya.animation import bakeToWorldSpace
|
||||
from colorbleed.lib import grouper
|
||||
from colorbleed.maya import lib
|
||||
|
||||
|
||||
def massage_ma_file(path):
|
||||
|
|
@ -36,18 +34,6 @@ def massage_ma_file(path):
|
|||
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):
|
||||
"""Unlocks attribute and disconnects inputs for a plug.
|
||||
|
||||
|
|
@ -87,7 +73,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor):
|
|||
will be published.
|
||||
|
||||
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:
|
||||
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)]
|
||||
handles = instance.data.get("handles", 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
|
||||
# Currently it will always bake the resulting camera to world-space
|
||||
# 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.
|
||||
if not bake_to_worldspace:
|
||||
self.log.warning("Camera (Maya Ascii) export only supports world"
|
||||
|
|
@ -140,11 +126,13 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor):
|
|||
# Perform extraction
|
||||
self.log.info("Performing camera bakes for: {0}".format(transform))
|
||||
with avalon.maya.maintained_selection():
|
||||
with context.evaluation("off"):
|
||||
with context.no_refresh():
|
||||
baked = bakeToWorldSpace(transform,
|
||||
frameRange=range_with_handles,
|
||||
step=step)
|
||||
with lib.evaluation("off"):
|
||||
with lib.no_refresh():
|
||||
baked = lib.bake_to_worldspace(
|
||||
transform,
|
||||
frameRange=range_with_handles,
|
||||
step=step
|
||||
)
|
||||
baked_shapes = cmds.ls(baked,
|
||||
type="camera",
|
||||
dag=True,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ from maya import cmds
|
|||
|
||||
import pyblish.api
|
||||
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):
|
||||
|
|
@ -63,10 +62,10 @@ class ExtractLook(colorbleed.api.Extractor):
|
|||
|
||||
# Extract in correct render layer
|
||||
layer = instance.data.get("renderlayer", "defaultRenderLayer")
|
||||
with context.renderlayer(layer):
|
||||
with lib.renderlayer(layer):
|
||||
# TODO: Ensure membership edits don't become renderlayer overrides
|
||||
with context.empty_sets(sets, force=True):
|
||||
with maya.attribute_values(remap):
|
||||
with lib.empty_sets(sets, force=True):
|
||||
with lib.attribute_values(remap):
|
||||
with avalon.maya.maintained_selection():
|
||||
cmds.select(sets, noExpand=True)
|
||||
cmds.file(maya_path,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ from maya import cmds
|
|||
|
||||
import avalon.maya
|
||||
import colorbleed.api
|
||||
|
||||
from cb.utils.maya import context
|
||||
import colorbleed.maya.lib as lib
|
||||
|
||||
|
||||
class ExtractModel(colorbleed.api.Extractor):
|
||||
|
|
@ -47,15 +46,15 @@ class ExtractModel(colorbleed.api.Extractor):
|
|||
noIntermediate=True,
|
||||
long=True)
|
||||
|
||||
with context.no_display_layers(instance):
|
||||
with context.displaySmoothness(members,
|
||||
divisionsU=0,
|
||||
divisionsV=0,
|
||||
pointsWire=4,
|
||||
pointsShaded=1,
|
||||
polygonObject=1):
|
||||
with context.shader(members,
|
||||
shadingEngine="initialShadingGroup"):
|
||||
with lib.no_display_layers(instance):
|
||||
with lib.displaySmoothness(members,
|
||||
divisionsU=0,
|
||||
divisionsV=0,
|
||||
pointsWire=4,
|
||||
pointsShaded=1,
|
||||
polygonObject=1):
|
||||
with lib.shader(members,
|
||||
shadingEngine="initialShadingGroup"):
|
||||
with avalon.maya.maintained_selection():
|
||||
cmds.select(members, noExpand=True)
|
||||
cmds.file(path,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ from colorbleed.maya import lib
|
|||
import pyblish.api
|
||||
import colorbleed.api
|
||||
|
||||
from cb.utils.maya import context
|
||||
|
||||
|
||||
class ValidateLookSets(pyblish.api.InstancePlugin):
|
||||
"""Validate if any sets are missing from the instance and look data
|
||||
|
|
@ -57,7 +55,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin):
|
|||
invalid = []
|
||||
|
||||
renderlayer = instance.data.get("renderlayer", "defaultRenderLayer")
|
||||
with context.renderlayer(renderlayer):
|
||||
with lib.renderlayer(renderlayer):
|
||||
for node in instance:
|
||||
# get the connected objectSets of the node
|
||||
sets = lib.get_related_sets(node)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
import colorbleed.api
|
||||
from cb.utils.maya.context import undo_chunk
|
||||
|
||||
import colorbleed.api
|
||||
import colorbleed.maya.action
|
||||
from colorbleed.maya.lib import undo_chunk
|
||||
|
||||
|
||||
class ValidateRigControllers(pyblish.api.InstancePlugin):
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from maya import cmds
|
|||
|
||||
import pyblish.api
|
||||
import colorbleed.api
|
||||
from cb.utils.maya.context import undo_chunk
|
||||
|
||||
import colorbleed.maya.lib as lib
|
||||
import colorbleed.maya.action
|
||||
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin):
|
|||
def repair(cls, instance):
|
||||
|
||||
invalid = cls.get_invalid(instance)
|
||||
with undo_chunk():
|
||||
with lib.undo_chunk():
|
||||
for node in invalid:
|
||||
for attribute in cls.attributes:
|
||||
if cmds.attributeQuery(attribute, node=node, exists=True):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue