Unlock attributes only during the bake context, make sure attributes are relocked after to preserve the lock state of the original node being baked

This commit is contained in:
Roy Nieterau 2023-12-22 11:09:46 +01:00
parent a75ff0f71a
commit 1a39a5cc99

View file

@ -2566,68 +2566,36 @@ def bake_to_world_space(nodes,
"""
@contextlib.contextmanager
def _revert_lock_attributes(node, new_node):
# Connect all attributes on the node except for transform
# attributes
attrs = _get_attrs(node)
attrs = set(attrs) - transform_attrs if attrs else []
original_attrs_lock = {}
def _unlock_attr(attr):
"""Unlock attribute during context if it is locked"""
if not cmds.getAttr(attr, lock=True):
# If not locked, do nothing
yield
return
try:
for attr in attrs:
orig_node_attr = '{0}.{1}'.format(node, attr)
new_node_attr = '{0}.{1}'.format(new_node, attr)
original_attrs_lock[new_node_attr] = (
cmds.getAttr(new_node_attr, lock=True)
)
# unlock to avoid connection errors
cmds.setAttr(new_node_attr, lock=False)
cmds.connectAttr(orig_node_attr,
new_node_attr,
force=True)
cmds.setAttr(attr, lock=False)
yield
finally:
for attr, lock_state in original_attrs_lock.items():
cmds.setAttr(attr, lock=lock_state)
@contextlib.contextmanager
def _revert_lock_shape_attributes(node, new_node, shape):
# 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)
original_shape_lock_attrs = {}
try:
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)
original_shape_lock_attrs[new_node_attr] = (
cmds.getAttr(new_node_attr, lock=True)
)
# unlock to avoid connection errors
cmds.setAttr(new_node_attr, lock=False)
cmds.connectAttr(orig_node_attr,
new_node_attr,
force=True)
yield
finally:
for attr, lock_state in original_shape_lock_attrs.items():
cmds.setAttr(attr, lock=lock_state)
cmds.setAttr(attr, lock=True)
def _get_attrs(node):
"""Workaround for buggy shape attribute listing with listAttr"""
"""Workaround for buggy shape attribute listing with listAttr
This will only return keyable settable attributes that have an
incoming connections (those that have a reason to be baked).
Technically this *may* fail to return attributes driven by complex
expressions for which maya makes no connections, e.g. doing actual
`setAttr` calls in expressions.
Arguments:
node (str): The node to list attributes for.
Returns:
list: Keyable attributes with incoming connections.
The attribute may be locked.
"""
attrs = cmds.listAttr(node,
write=True,
scalar=True,
@ -2652,13 +2620,14 @@ def bake_to_world_space(nodes,
return valid_attrs
transform_attrs = set(["t", "r", "s",
"tx", "ty", "tz",
"rx", "ry", "rz",
"sx", "sy", "sz"])
transform_attrs = {"t", "r", "s",
"tx", "ty", "tz",
"rx", "ry", "rz",
"sx", "sy", "sz"}
world_space_nodes = []
with delete_after() as delete_bin:
with contextlib.ExitStack() as stack:
delete_bin = stack.enter_context(delete_after())
# Create the duplicate nodes that are in world-space connected to
# the originals
for node in nodes:
@ -2669,32 +2638,66 @@ def bake_to_world_space(nodes,
new_node = cmds.duplicate(node,
name=new_name,
renameChildren=True)[0] # noqa
with _revert_lock_attributes(node, new_node):
with _revert_lock_shape_attributes(node, new_node):
# 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)
# Parent new node to world
if cmds.listRelatives(new_node, parent=True):
new_node = cmds.parent(new_node, world=True)[0]
# Constraints
delete_bin.extend(
cmds.parentConstraint(node, new_node, mo=False))
delete_bin.extend(
cmds.scaleConstraint(node, new_node, mo=False))
# Temporarily unlock and passthrough connect all attributes
# so we can bake them over time
# Skip transform attributes because we will constrain them later
attrs = set(_get_attrs(node)) - transform_attrs
for attr in attrs:
orig_node_attr = "{}.{}".format(node, attr)
new_node_attr = "{}.{}".format(new_node, attr)
world_space_nodes.append(new_node)
# unlock during context to avoid connection errors
stack.enter_context(_unlock_attr(new_node_attr))
cmds.connectAttr(orig_node_attr,
new_node_attr,
force=True)
bake(world_space_nodes,
frame_range=frame_range,
step=step,
simulation=simulation,
preserve_outside_keys=preserve_outside_keys,
disable_implicit_control=disable_implicit_control,
shape=shape)
# If shapes are also baked then also temporarily unlock and
# passthrough connect all shape attributes for baking
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 = "{}.{}".format(orig_shape, attr)
new_node_attr = "{}.{}".format(new_shape, attr)
# unlock during context to avoid connection errors
stack.enter_context(_unlock_attr(new_node_attr))
cmds.connectAttr(orig_node_attr,
new_node_attr,
force=True)
# Constraint transforms
for attr in transform_attrs:
transform_attr = "{}.{}".format(new_node, attr)
stack.enter_context(_unlock_attr(transform_attr))
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=frame_range,
step=step,
simulation=simulation,
preserve_outside_keys=preserve_outside_keys,
disable_implicit_control=disable_implicit_control,
shape=shape)
return world_space_nodes