Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/mutils/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,12 @@ def save(
if not FIX_SAVE_ANIM_REFERENCE_LOCKED_ERROR:
mutils.disconnectAll(dstNode)

# Make sure we delete all proxy attributes, otherwise pasteKey will duplicate keys
mutils.Attribute.deleteProxyAttrs(dstNode)
# Collect all the proxy attributes and un-proxy them before export. This way they will be
# saved as standard attributes and all their values will be pasted correctly when loaded back.
proxyAttrs = mutils.Attribute.collectProxyAttrs(dstNode)
for proxy in proxyAttrs:
logger.debug("proxy attribute: %s", proxy)
proxy.unProxy()
maya.cmds.pasteKey(dstNode)

attrs = maya.cmds.listAttr(dstNode, unlocked=True, keyable=True) or []
Expand Down Expand Up @@ -811,10 +815,6 @@ def load(
logger.debug('Skipping attribute: The destination attribute "%s" does not exist!' % dstAttr.fullname())
continue

if dstAttr.isProxy():
logger.debug('Skipping attribute: The destination attribute "%s" is a proxy attribute!', dstAttr.fullname())
continue

srcCurve = self.animCurve(srcNode.name(), attr, withNamespace=True)

if srcCurve:
Expand Down
39 changes: 31 additions & 8 deletions src/mutils/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,11 @@ def listAttr(cls, name, **kwargs):
return [cls(name, attr) for attr in attrs]

@classmethod
def deleteProxyAttrs(cls, name):
"""Delete all the proxy attributes for the given object name."""
def collectProxyAttrs(cls, name):
attrs = cls.listAttr(name, unlocked=True, keyable=True) or []
for attr in attrs:
if attr.isProxy():
attr.delete()
yield attr

def __init__(self, name, attr=None, value=None, type=None, cache=True):
"""
Expand Down Expand Up @@ -181,6 +180,28 @@ def isProxy(self):

return maya.cmds.addAttr(self.fullname(), query=True, usedAsProxy=True)

def unProxy(self):
"""
Converts the attribute from proxy back to dynamic.

:rtype: None
"""
# Collect all the data before deleting the attribute, as that will require it to
# still be alive.
name = self.attr()
type_ = self.type()
value = self.value()
if self.isProxy():
self.delete()
# Re-create the attribute in non-proxy mode.
maya.cmds.addAttr(
self.name(),
longName=name,
attributeType=type_,
defaultValue=value,
keyable=True,
)

def delete(self):
"""Delete the attribute"""
maya.cmds.deleteAttr(self.fullname())
Expand Down Expand Up @@ -421,10 +442,6 @@ def setAnimCurve(self, curve, time, option, source=None, connect=False):
fullname = self.fullname()
startTime, endTime = time

if self.isProxy():
logger.debug("Cannot set anim curve for proxy attribute")
return

if not self.exists():
logger.debug("Attr does not exists")
return
Expand Down Expand Up @@ -492,7 +509,13 @@ def animCurve(self):

if self.exists():

n = self.listConnections(plugs=True, destination=False)
# Find the keys connected to the current attribute. If the attribute is a proxy, the anim keys will
# actually be connected to the node where the source of the proxy attribute comes from, so traverse
# that to find the keys.
animSource = self.fullname()
if self.isProxy():
animSource = self.listConnections(plugs=True, destination=False)
n = maya.cmds.listConnections(animSource, plugs=True, destination=False)

if n and "animCurve" in maya.cmds.nodeType(n):
result = n
Expand Down