From ed482c3560ed22f347133e6b9c42d8c8e935920b Mon Sep 17 00:00:00 2001 From: Matteo Di Lena Date: Thu, 22 Aug 2024 17:35:49 +0200 Subject: [PATCH] Added proxy attributes support --- src/mutils/animation.py | 12 ++++++------ src/mutils/attribute.py | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/mutils/animation.py b/src/mutils/animation.py index ad6c8b80..d727bf45 100644 --- a/src/mutils/animation.py +++ b/src/mutils/animation.py @@ -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 [] @@ -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: diff --git a/src/mutils/attribute.py b/src/mutils/attribute.py index 27e4b0fa..2af04dbb 100644 --- a/src/mutils/attribute.py +++ b/src/mutils/attribute.py @@ -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): """ @@ -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()) @@ -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 @@ -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