Speed up animation baking in Maya 2017

Hello all, I am trying to perform animation baking from one rig to another.
While my code works, it seems that the speed of the baking is somewhat slow.

I tried setting the viewport to use display Reference Editor, setting evaluation mode to off, and using cmds.refresh(suspend=True) to halt/ resume Maya’s handling of refresh events.
While using the above 3 mentioned methods works, it only seems to speed up the process very slightly - took about 20 seconds, across 120 frames for about 300 controllers (controllers with no animations are skipped, so it amounts to about 30 at most or lesser.), where it initially took about 23.45 seconds.

Wondering if anyone has any other tips or tricks on hand that I can play around with?
I am using Maya 2017, by the way.

Many thanks in advance for any replies :smiley:

For your baking, are you using bakeResults? And if so, what flags are you using?

This is the code that I am using in which I have used all the above 3 mentioned methods:

def bake_animation(selection, frame_range):
    cmds.select(selection)

    # Check the current evaluation mode used.
    current_eval_mode = cmds.evaluationManager(query=True, mode=True)[0]
    if not current_eval_mode == "off":
        # Set it to off.
        cmds.evaluationManager(mode="off")
    # Halts Maya's handling of refresh events.
    cmds.refresh(suspend=True)

    # Isolate to get the bake faster.    
    panelFocus = cmds.getPanel(withFocus=True)
    visPanels = cmds.getPanel(visiblePanels=True)
    modelPanels = cmds.getPanel(type="modelPanel")
    if modelPanels:
        for pan in modelPanels:
            finder = pan in visPanels
            if finder:
                activePanel = pan
        cmds.setFocus(activePanel)
        cmds.scriptedPanel(
            "referenceEditorPanel1",
            edit=True,
            replacePanel=activePanel
        )

    # Mostly looking at only Translate and Rotate channels.
    channels = ["translate", "rotate"]
    axis = ["X", "Y", "Z"]
    for frame in xrange(frame_range[0], frame_range[1]):
        cmds.currentTime(frame)
        for channel in channels:
            for axi in axis:
                temp_attr = "".join((channel, axi))
                cmds.setKeyframe(attribute=temp_attr, time=frame)

    # Back to the prespective
    cmds.modelPanel(
        activePanel,
        edit=True,
        replacePanel="referenceEditorPanel1"
    )
    cmds.setFocus(panelFocus)

    # Filter baked curve
    cmds.filterCurve(filter="euler")
    # Resumes Maya's handling of refresh events.
    cmds.refresh(suspend=False)
    # Set back the initial evaluation mode that it was set to.
    cmds.evaluationManager(mode=current_eval_mode)

The line that contains cmds.currentTime(frame), I tried commenting out, but it will results in no baking of animation and that is possibly the code portion that seems to make it slow…

If you’re really curious as to where your time is going, you can always profile your code:

from cProfile import Profile
p = Profile()
sel = cmds.ls(sl=True)
p.run('bake_animation(sel, (0, 250))').print_stats('cumtime')

This should print out what portion of your code is taking the most cumulative time to execute.
Which should give you a starting point as to identifying what might be the culprit.

Because I got curious myself, running your code against 440 random objects and baking 300 frames, I got the following results.

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   27.482   27.482 <string>:1(<module>)
        1    0.018    0.018   27.482   27.482 <maya console>:5(bake_animation)
     1800   26.267    0.015   26.267    0.015 {built-in method setKeyframe}
        1    0.979    0.979    0.979    0.979 {built-in method filterCurve}
      300    0.154    0.001    0.154    0.001 {built-in method currentTime}
        1    0.024    0.024    0.024    0.024 {built-in method modelPanel}
        1    0.020    0.020    0.020    0.020 {built-in method scriptedPanel}
        1    0.014    0.014    0.014    0.014 {built-in method select}
        2    0.003    0.001    0.003    0.001 {built-in method setFocus}
     1800    0.002    0.000    0.002    0.000 {method 'join' of 'str' objects}
        3    0.001    0.000    0.001    0.000 {built-in method evaluationManager}
        3    0.000    0.000    0.000    0.000 {built-in method getPanel}
        2    0.000    0.000    0.000    0.000 {built-in method refresh}
        2    0.000    0.000    0.000    0.000 {built-in method undoInfo}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Which means that setKeyframe is where all your time is going.

One alteration you could make.

    channels = ["translate", "rotate"]
    axis = ["X", "Y", "Z"]
    attributes = [''.join(c, x) for c in channels for x in axis]
    for frame in xrange(frame_range[0], frame_range[1]):
        cmds.currentTime(frame)
        cmds.setKeyframe(attribute=attributes, time=frame)

This should reduce the number of calls you’ve got to setKeyframe, and in turn should get you some time back. it shaved almost 10 seconds off of my trial runs.

Do you need to call cmds.currentTime() before each cmds.setKeyframe() if you’re telling setKeyframe which frame to set anyway?

I was wondering the same thing, but the currentTime call is basically acting as a get.
So it jumps to that time, and then set is called on all the attributes for that time value.

Probably the redundant part is actually in the setKeyframe call.
Otherwise you’d need a getAttr call for each attribute you want to bake. Which would definitely add some overhead.

Hi all, initially I am not using cmds.currentTime(). Things seems to work only at the first frame, it was then I realized that it did not actually bake in any animation.

Also, cool to know about the profile code. Was using timerX to iterate my code as a whole…

One of the big performance wins with maya, is batching as much as possible into each call to a command.

Not sure when they started adding it, but at least in the 2018 docs, they’ve got a M icon on any attribute that can take a collection in addition to single arguments.

Use those when you can.

Can you kindly elaborate more on the batching that you have mentioned? I am using Maya version 2017 though, don’t think it has the M icon that you have mentioned.

For example:

attributes= ['tx', 'rx', 'ty', 'ry', 'tz', 'rz']
sel = cmds.ls(sl=True)
# Best case, we're batching the objects, AND the attributes
cmds.setKeyframe(sel, attribute=attributes, time=frame)
# Better case, we're batching the objects
for attr in attributes:
    cmds.setKeyframe(sel, attribute=attr, time=frame)
# Worst case, no batching at all.
for obj in sel:
    for attr in attributes:
        cmds.setKeyframe(obj, atttribute=attr, time=frame)

In the first case, I’m calling cmds.setKeyframe once, and it batches the operation across all the selected objects, with all of the specified attributes.
In the second case, which is what your original version is doing, we are batching all of the objects together, but we’re still calling cmds.setKeyframe 6 times.

In the worst case, we’d be calling cmds.setKeframe 6 * num_of_selected_objects.