So, been fighting this one for a while now. There’s this nasty little bug in Maya 2017 (and I believe it exists in previous versions as well) where animation files that have a referenced rig AND animation layers will randomly save invalid connectAttr calls that cause animation layer nodes to connect to themselves (output connects to one if its own input attributes rather than the proper control). This creates cycles, Maya screams at us, and everyone is sad. Worse yet, this bug doesn’t demonstrate itself until someone re-opens the file and the scene is reconstructed and the faulty connectAttr call reconnects the node to itself, removing the proper animCurve connection.

Fortunately, this seems to be a rare occurrence and everything is great. Unfortunately, this seems to be a rare occurrence and it bites us in the ass when an animator opens a file the next morning, we go to do a batch export, or someone re-visits an animation from days/weeks/months ago. Some times, it’s a quick fix of reverting to the previous unbroken file and updating the animation again (small tweak in a layer). Other times, considerable work is lost, and losing work sucks. But alas, the work is not truly lost as all the animation curves are still retained in the scene. So I finally took some time this morning to properly work through and understand the problem and wrote some code to help alleviate the issue:

import pymel.core as pmc
def fixupCyclicAnimLayers():
"""
This function finds animation layer nodes which are connected to themselves, disconnects them, and HOPEFULLY finds
the remaining unconnected animation curve, and plugs it back into the appropriate attribute.
:return: None
"""
# Get all our initial layer/curve data
animLayers = [layer for layer in pmc.ls(type=['animLayer']) if 'BaseAnimation' not in layer.name()]
animBlendNodes = pmc.ls(type=['animBlendNodeBase'])
animCurveNodes = pmc.ls(type=['animCurve'])
# Want to work on one layer at a time
for animLayer in animLayers:
for animBlend in [blendNode for blendNode in animBlendNodes if animLayer.name() in blendNode.name()]:
blendNodeShortName = animBlend.shortName().split(':')[-1].split('_{layerName}'.format(layerName=animLayer.name()))[0]
matchingCurves = [animCurve for animCurve in animCurveNodes if blendNodeShortName in animCurve.shortName()]
# Find layer nodes that are connected to layer nodes, which... shouldn't be happening.
connectedNodeAttrs = pmc.listConnections(animBlend, type='animBlendNodeBase',
plugs=True, d=True, s=False, c=True)
animBlendAttrs = []
for nodeAttr in connectedNodeAttrs:
# GTFO cyclic connections!
pmc.disconnectAttr(nodeAttr[0], nodeAttr[1])
# Need to track what we're connecting back to
animBlendAttrs.append(nodeAttr[1])
i = 0
for animCurve in matchingCurves:
connectedAttrs = pmc.listConnections(animCurve)
if len(connectedAttrs) == 0 or connectedAttrs is None:
try:
pmc.connectAttr(animCurve.output, animBlendAttrs[i])
except:
pass
i += 1

Ultimately what’s happening here is we’re looking for animation layer nodes that connect to other animation layer nodes (themselves). We break those connections while tracking what inputs to reconnect. We then look for orphaned animCurve nodes that contain the name of the layer node and hook everything back up.

It’s still an early pass, but in initial testing, it has corrected all the cycle warnings and got the animation back into the proper working state. It works with multiple layers regardless of their name. Seems like a useful snippet of code, so wanted to share it out for anyone else that might be encountering this fun little gem.

Tags [ Maya, Python, Code, Animation ]