[maya] animLayer and the API

Has anyone successfully dealt with Maya’s animation layers with the API? I realize that animation layers have been implemented totally with scripts and there is no direct API functionality. However, my animation load/save app has been written directly accessing animCurves with the API. Now the animators have discovered they like animation layers and I need to write to them. If there isn’t already an existing curve on a layer when I’m going to apply keys, I’m having an issue creating a curve on the proper plug.

The animLayer -layeredPlug sounds like it should do what I want. But I can’t seem to give it what it likes as input. And grepping through the Maya others scripts, I don’t see anywhere that’s it’s actually being used so I can see an example.

Sorry to resurrect this old post, but did you ever figure out a solution? I am running into the exact same problem right now.

Well, the “animLayer” command is a clusterf… ahem, I mean, is poorly implemented. Here is what I have discovered so far:

If an attribute has keyframes on an animation layer, I can get the name of the animation curve. Then I can query and set the values on the anim curve directly.

# Note that you must use "edit" mode in order to query the value from "findCurveForPlug".
# Because the Python version of the command is broken.  In MEL either "query" mode or "edit"
# mode works.
cmds.animLayer('AnimLayer1', e=True, findCurveForPlug='pCube1.translateY')
# Result: [u'pCube1_translateY_AnimLayer1_inputB'] # 
cmds.keyframe('pCube1_translateY_AnimLayer1_inputB', q=True, time=(10, 10), eval=True, valueChange=True)
# Result: [2.0] # 

If an attribute does not have an animation curve on a specific layer, I can get the corresponding attribute on the blend node:

# mel.eval?  Oh, yeah.  The Python version of the command is broken.
mel.eval('animLayer -q -layeredPlug "pCube1.translateZ" "AnimLayer1"')
# Result: pCube1_translateZ_AnimLayer1.inputB # 
cmds.getAttr('pCube1_translateZ_AnimLayer1.inputB')
# Result: 3.5679999211430515 #

My remaining problem is that the “layeredPlug” flag does not return a result if the animation layer specified is the base animation layer. So I still don’t know how to get a static value from the base animation layer.

3 Likes

I think I figured out how to get the attribute when the animation layer is the base animation layer. Here it is all put together:

def getAttrPlugForLayer(nodeName, attribute, animLayer):
    """ Find the animBlendNode plug corresponding to the given node, attribute,
    and animation layer.
    
    """
    nodeAttr = '{0}.{1}'.format(nodeName, attribute)
    if not isObjectInAnimLayer(nodeAttr, animLayer):
        return None
    
    plug = None
    if animLayer == cmds.animLayer(q=True, root=True):
        # For the base animation layer, traverse the chain of animBlendNodes all
        # the way to the end.  The plug will be "inputA" on that last node.
        conns = cmds.listConnections(nodeAttr, type='animBlendNodeBase', s=True, d=False)
        blendNode = None
        while conns:
            blendNode = conns[0]
            conns = cmds.listConnections(blendNode, type='animBlendNodeBase', s=True, d=False)
        plug = '{0}.inputA'.format(blendNode)
    else:
        # For every layer other than the base animation layer, we can just use
        # the "animLayer" command.  Unfortunately the "layeredPlug" flag is
        # broken in Python in Maya 2016, so we have to use MEL.
        cmd = 'animLayer -q -layeredPlug "{0}" "{1}"'.format(nodeAttr, animLayer)
        plug = mel.eval(cmd)
    return plug

def isObjectInAnimLayer(obj, animLayer):
    """ Determine if the given object is in the given animation layer.
    
    Parameters:
    * obj - Can be either a node name, like "pCube1", or a node/attribute combo,
        like "pCube1.translateX".
    * animLayer - The name of an animation layer.
    
    """
    objAnimLayers = cmds.animLayer([obj], q=True, affectedLayers=True) or []
    if animLayer in objAnimLayers:
        return True
    return False