Keyframing a pose asset in Python

Hello, I want to apply and keyframe a pose asset. I managed to apply the pose asset successfully. However, I was not able to keyframe it.

This is my code for applying the pose:

def apply_pose(frame, pose):
    win = bpy.context.window
    scr = win.screen
    area = scr.areas[0]
    area.type = "DOPESHEET_EDITOR"e

    with bpy.context.temp_override(area=area):
        bpy.ops.object.mode_set(mode='POSE')
        bpy.context.scene.frame_set(frame)
        bpy.data.objects['DeOne'].animation_data.action = bpy.data.actions[pose]
        for i in bpy.context.selected_objects[0].data.bones:
             i.select = True

        bpy.ops.anim.keyframe_insert_by_name(type="WholeCharacter")

I also tried bpy.ops.action.keyframe_insert() and bpy.ops.anim.keyframe_insert_by_name(type="BUILTIN_KSI_BendyBones") none of which inserted the right keyframe.

Thank you for your help.

(I posed a similar question on the Blender Stackexchange a few days ago without any answer)

:thinking: It’s generally bad practice to use bpy.ops in a script if there’s a way to do it through data manipulation. All that stuff with the area and the override is because of ops. I don’t even know what the with statement is doing, here!
So anyways; you don’t have to switch to pose mode to modify pose properties, but you should make sure you aren’t in Edit Mode.
if object.mode == 'EDIT: return None'
Here is the API reference for the right way to add a keyframe from Python. As you can see, you can set the frame and value from there.
Now, for the actions: you can’t keyframe the action that an object is using because the Action is what holds the keyframes. So instead, you keep the action on the current object and transfer the pose you want into the object’s action.
Something like this:

fcurve_value={}
for frame in range(length_of_animation):
    for fcurve in pose_action.fcurves:
        fcurve_value[(fcurve.data_path, fcurve.array_index)]=fcurve.evaluate(frame)
    for (data_path, index), value in fcurve_value.items():
        object.keyframe_insert(data_path, index, frame)

Please note that I haven’t tried the above script and it likely contains some syntax errors.
But anyhow, if you ever find yourself setting selection or doing context overrides in Blender’s Python API, there’s a 95% chance you’re making things harder than they should be, and perform worse than they should. bpy.ops commands usually have a data-modification alternate that is more Pythonic and better for scripting.
The .ops commands exist because they are useful for interacting with Blender on the level of an ordinary user. You can make your own operators very easily with the built-in Simple Operator template, too, and I encourage you to do this whenever you make tools! It becomes much easier to reuse them when you do.

I left out a fair bit of stuff here in the hopes that you can figure it out on your own, since I’m in the middle of something right now. Let me know if you get it and I will try and follow up if you need more pointers :slight_smile:

1 Like

Thank you so much! That was the hint I needed. After some tinkering, I got the following working solution:

def apply_pose(frame, pose):
    bpy.context.scene.frame_set(frame)
    fcurve_value = {}
    for fcurve in bpy.data.actions[pose].fcurves:
        fcurve_value[(fcurve.data_path, fcurve.array_index)] = fcurve.evaluate(frame)

    for (data_path, index), value in fcurve_value.items():
        dimensions = {0: ".x", 1: ".y", 2: ".z"}

        set_value(bpy.context.object, str(data_path) + dimensions[index], value)
        bpy.context.object.keyframe_insert(data_path, index=index, frame=frame)


def set_value(obj, path, value):
    if "." in path:
        path_prop, path_attr = path.rsplit(".", 1)
        prop = obj.path_resolve(path_prop)
    else:
        prop = obj
        path_attr = path
    setattr(prop, path_attr, value)