Graeme Hill's Dev Blog

Exporting armature animation with the Blender 2.5 Python API

Star date: 2011.300

It took me forever to get this right so I thought I'd post my solution. Most of the examples I found were for previous versions of Blender (the API changed a lot in Blender 2.5). The sample scripts I did find never seemed to work and the official documentation seemed to be somewhat insufficient.

The first thing you need to understand is that there are bones, and then there are pose bones. They are completely different objects:

# Get the armature (this technique assumes it is selected)
armature = bpy.context.active_object

# Get bone and pose bone data
bones = armature.data.bones
pose_bones = armature.pose.bones

Pose bones contain pose data for the current frame, while regular bone objects store the default state of the bone (ie: as it appears in edit mode). In order to get a pose at a specific frame you actually need to set the frame:

scene = bpy.context.scene
armature = bpy.context.active_object

# Loop through every frame in the scene and get bone pose at each one
for frame in range(scene.frame_end + 1):
    scene.frame_set(frame)
    for pose_bone in armature.pose.bones:
        do_something_with_pose_bone(pose_bone)

We can now get each pose bone object at each frame. All that's left is figuring out how to extract the bone rotation and position info. In my case I assumed that the bone does not move, it only rotates around its pivot point, which is called the "head" of the bone in Blender. Here's how we get the head location of the bone:

bone = armature.data.bones[0]
head_position = armature.matrix_world * bone.head_local

That's pretty easy, you just take the local position of the bone and translate it to world coordinates. Rotation gets a little more complicated. Since I set my rotations with quaternions in blender I thought it made sense to just use pose_bone.rotation_quaternion. That expression yielded the exact value that is displayed in the editor while in pose mode so I figured I was good to go. However, on closer inspection, the value returned by rotation_quaternion most definitely does not represent the rotation of that bone relative to its parent. In order to get that value I eventually found this technique:

def get_pose_bone_matrix(pose_bone):
    local_matrix = pose_bone.matrix_channel.to_3x3()
    if pose_bone.parent is None:
        return local_matrix
    else:
        return pose_bone.parent.matrix_channel.to_3x3().inverted() * local_matrix

Note that you can easily convert between matrices and quaternions using the to_quaternion() and to_matrix() methods. The idea here is to get the local matrix and then basically undue the rotation from the parent. Originally I thought I would have to keep going up the tree to the root bone, but apparently not. Going one level up seems to be enough.

I hope this saves someone from ripping their hair out like I did!