Animation Transfer Maya Python Script

Hey Everyone,

I created an animation transfer maya python script with the intention of using it to transfer specific animation data between similar riggs with different meshes. Transfer animation between maya scenes of similar characters. I’ve posted the python file on my blog: http://www.erkmuss.com/blog/2012/3/26/passadata-version-10.html. If you’re confused about what it does you can view a video on my portfolio that shows it off.

It currently works how I intended it but it has limitations and probably could do some things better. I’m asking for feedback on it to improve it. Specific things I’d like to improve are:
-Passes information via naming conventions - will switch to a hierachy based system in future. Joints have to be same name!

-The scale of the scene affects the animation transfer when applied. -Whatever the scale is in the animation your exporting from has to be the same as the one your applying to.

-Want to add functionality to let you choose which data to export and import.

-Animation keyframes need clean-up because currently every frame of animation is keyed when applied.

If you have any insight on how to achieve any of these please let me know or give me a direction to go in. Also feel free to use the script or modify it for whatever you need.

Thanks,
Eric

Ok nobody has to download it to give feedback - look at the code and tell me if it needs improvement anywhere or if I should be implementing something differently. Or if you have suggestions for how to improve the functionality. Ignore anything mentioning XML, I was using it as my data transfer language but switched to json.

import json
import maya.cmds as cmds #import maya commands

#opens window that prompts user to select a file
def selectFilePath(*args):
    
    #pops up dialog that allows user to select a place to load documents
    filePath = cmds.fileDialog2(fm=0, dir="C:\\Users\\Eric\\Desktop\	est.txt", cap="Select File To Load:", ff="*.txt")
    cmds.textField("dataTF", edit=True, text=filePath[0]);
    
#apply data
def Apply(*args):
    
    #take in filePath selected from textField
    filePath = cmds.textField("dataTF", q=True, tx=True)
    rootNode = cmds.textField("rootTF", q=True, tx=True)
    
    #need both filePath and rootNode for it to work
    if filePath == "-":
        print "No filePath Specified"
    elif rootNode == "-":
        print "No root node selected"
    else:
        f=open(filePath, 'r')
        j_data= json.loads(f.readline())
        f.close()
        
        #list of keys(objects)
        objectList = j_data.keys()
        for object in objectList:
            print object
            keyframes = j_data[str(object)]
            keyframesList = j_data[str(object)].keys()
            
            for key in keyframesList:
                attributes = keyframes[str(key)] #attributes of specific key
                attributesList = keyframes[str(key)].keys()
                
                cmds.currentTime(key)
                cmds.select(object)
                for attr in attributesList:
                    #skip if locked                    
                    if cmds.getAttr(object+'.'+attr,lock=True) == True:
                        continue
                    else:
                        print attributes[attr]
                        cmds.setAttr(object+'.'+attr, float(attributes[attr]), clamp=True)
                        cmds.setKeyframe(v=float(attributes[attr]), at=attr)

#PURPOSE: To transfer animation data from one rigged mesh to
#another mesh of the same rig. Specifically to speed up iteration
#of animations on different characters using the same rig by creating
#seperate .mb files within Unity so that each animation can be edited 
#independently using the @ naming convention method of animating.

#WRITTEN BY ERIC MUSSER - ERKMUSS.SQUARESPACE.COM - ENJOY :D

#NOTES BEFORE USING
#Scenes must have same naming convention - it looks for names between rigs.
#Scenes must have same standard unit (grid size) otherwise units won't convert

import json
import maya.cmds as cmds #import maya commands

#opens window that prompts user to select a xml file
def selectFilePath(*args):
    
    #pops up dialog that allows user to select a place to load documents
    filePath = cmds.fileDialog2(fm=0, dir="C:\\Users\\Eric\\Desktop\	est.txt", cap="Select File To Load:", ff="*.txt")
    cmds.textField("dataTF", edit=True, text=filePath[0]);
    
#apply data
def Apply(*args):
    
    #take in filePath selected from textField
    filePath = cmds.textField("dataTF", q=True, tx=True)
    rootNode = cmds.textField("rootTF", q=True, tx=True)
    
    #need both filePath and rootNode for it to work
    if filePath == "-":
        print "No filePath Specified"
    elif rootNode == "-":
        print "No root node selected"
    else:
        f=open(filePath, 'r')
        j_data= json.loads(f.readline())
        f.close()
        
        #list of keys(objects)
        objectList = j_data.keys()
        for object in objectList:
            print object
            keyframes = j_data[str(object)]
            keyframesList = j_data[str(object)].keys()
            
            for key in keyframesList:
                attributes = keyframes[str(key)] #attributes of specific key
                attributesList = keyframes[str(key)].keys()
                
                cmds.currentTime(key)
                cmds.select(object)
                for attr in attributesList:
                    #skip if locked                    
                    if cmds.getAttr(object+'.'+attr,lock=True) == True:
                        continue
                    else:
                        print attributes[attr]
                        cmds.setAttr(object+'.'+attr, float(attributes[attr]), clamp=True)
                        cmds.setKeyframe(v=float(attributes[attr]), at=attr)

def setRoot(*args):
    
    #select heirarchy of root
    selection = cmds.ls(sl= True)
    if selection == []:
        print "Nothing Selected"
    else:
        #change textField text to root name
        cmds.textField("rootTF", edit=True, text=selection[0]);
            
       
def exportData(*args):
    #takes value from text field to use in creating xml file
    root = cmds.textField("rootTF", q=True, tx=True)
        
    #make root readable and check for no selection    
    if root == '-':
        print "Nothing selected for root node."
    else:
        #select hiearchy of root, then add those selections to a variable
        cmds.select(root, hi=True)
        selection = cmds.ls(sl=True);
        
        #empty dictionary for storing objects
        objectDict = {}
        
        #go through each object in selected heirarchy
        for object in selection:
            cmds.select(object)
            #get first and last frame of current object in heirarchy
            firstKey = cmds.findKeyframe(time=(0, 1000), which='first') 
            lastKey = cmds.findKeyframe(time=(0, 1000), which='last')
            
            
            #to deal with objects that aren't keyed
            if int(firstKey) == int(lastKey):
                continue
            else:
                #create empty dict for storing keyframes
                keyframeDict = {}
                
                #go through each frame of animation
                for frame in range(int(firstKey),int(lastKey)):
                    
                    #make sure on the right frame
                    cmds.currentTime(frame)
                    
                    #create empty dict for storing attributes
                    attributeDict = {}
                    
                    #get the translation
                    object_translation = cmds.getAttr(object + ".translate")
                    attributeDict["translateX"] = str(object_translation[0][0])
                    attributeDict["translateY"] = str(object_translation[0][1])
                    attributeDict["translateZ"] = str(object_translation[0][2])
                    
                    #get the rotation
                    object_rotation = cmds.getAttr(object + ".rotate")
                    attributeDict["rotateX"] = str(object_rotation[0][0])
                    attributeDict["rotateY"] = str(object_rotation[0][1])
                    attributeDict["rotateZ"] = str(object_rotation[0][2])
                    
                    #add attribute data to keyframeDict
                    keyframeDict[str(frame)] = attributeDict
                        
                #add keyframe data to object    
                objectDict[str(object)] = keyframeDict

        #prompt user for a location to save their file
        filePath = cmds.fileDialog2(fm=0, dir="C:\\Users\\Eric\\Desktop\	est.txt", cap="Select Location To Save File:", ff="*.txt")
        f=open(filePath[0], 'w')
        f.write(json.dumps(objectDict,skipkeys = True))
        f.close()
        
        print "Sent to " + filePath[0]    

#delete window if it exists
if cmds.window("passData", q=True, exists=True):
    cmds.deleteUI("passData", wnd=True);
    
#create user interface
cmds.window("passData", t="Pass Animation Data", rtf=True, s=False);
cmds.rowColumnLayout( numberOfColumns=3, columnWidth=[(1, 100), (2, 200), (3, 100)]);

#first row
cmds.text("Root_Node");
cmds.textField("rootTF", text="-", ed=False);
cmds.button("Set Root", c=setRoot);

#second row
cmds.text("Data_File");
cmds.textField("dataTF", text="-", ed=False);
cmds.button("Select File Path", c=selectFilePath);

#third row
cmds.text("");
cmds.button("Export Current Data...", c=exportData);
cmds.button("Apply Data", c=Apply);

cmds.showWindow("passData");

the actual saving and loading looks about what mine does (not saying mine is good at all) but just a couple of suggestions that might be personal preference but I think is good advice for having people look over your code in the future:

  1. use block comments to describe your methods like so:


def myDef(arg_1, arg_2, *args):
    """
    myDef is a function that takes in two args and does something to them

    @type arg_1: str
    @param arg_1: this is the first arg, it is the name of something

    @type arg_2: list
    @param arg_2: this is a list of objects to iterate over

    @rtype: list
    @returns: sends back the modified list of arg_2
    """
    blah, blah blah
    pass
    return arg_2


this way you can also remember what the heck you were doing and know exactly what arguments are doing months down the road if you have to abandon the script for a while.
2. Make the tool its own class

A general question, more about game engines than Maya:
How do hierarchy based animation tools/players work (as opposed to name based)? If a parent_node has two children, how do you ensure you’re applying a child animation to the correct child? Some engines require the node names to be exactly the same, while others will play animation on any rigs that share the same hierarchical structure… Do those engines use naming as a secondary check, so thigh_left doesn’t play thigh_right’s anims?

[QUOTE=mattanimation;15407]the actual saving and loading looks about what mine does (not saying mine is good at all) but just a couple of suggestions that might be personal preference but I think is good advice for having people look over your code in the future:

  1. use block comments to describe your methods like so:


def myDef(arg_1, arg_2, *args):
    """
    myDef is a function that takes in two args and does something to them

    @type arg_1: str
    @param arg_1: this is the first arg, it is the name of something

    @type arg_2: list
    @param arg_2: this is a list of objects to iterate over

    @rtype: list
    @returns: sends back the modified list of arg_2
    """
    blah, blah blah
    pass
    return arg_2


this way you can also remember what the heck you were doing and know exactly what arguments are doing months down the road if you have to abandon the script for a while.
2. Make the tool its own class[/QUOTE]

Yeah I actually based a lot of it off of your blog post, really helped me out - thanks! I’ll make the comments easier to read, I know I got a little lazy near the end. I hadn’t thought making it a class would be necessary but will definitely make it when I add functionality later.

[QUOTE=patconnole;15408]A general question, more about game engines than Maya:
How do hierarchy based animation tools/players work (as opposed to name based)? If a parent_node has two children, how do you ensure you’re applying a child animation to the correct child? Some engines require the node names to be exactly the same, while others will play animation on any rigs that share the same hierarchical structure… Do those engines use naming as a secondary check, so thigh_left doesn’t play thigh_right’s anims?[/QUOTE]

I think it would be dependent on the engine. If you wanted to find out for sure in whatever engine you could probably run some tests to see what works and what doesn’t. That’s usually what I do.

*Edit Maybe a better question to be posed to the specific developer of the engine if you’re looking to find out the behind the scenes information.