0
$\begingroup$

I was wondering how Blender does some of its internal animation work and how it propagates changes between bones of the same armature/bone hierarchy.

The problem is as follows: There is an input human rig definition together with bone poses (bone head and quaternion) for animating a character, all in armature space. I import the rig definition and create a character from that looks like: armature definition in JSON file The rig looks accordingly like this (rest pose): correct rest pose armature in Blender

When I then try to animate the pose bones I get very strange results when I toggle the object with the armature to pose mode initially, set all poses and then go back to object mode:

    # animate the bones
    bpy.context.view_layer.objects.active = skeleton_obj
    bpy.ops.object.mode_set(mode="POSE")
    bone_qis_RS = {} # rotation of each bone at frame i
    bone_his_RS = {} # head of each bone at frame i

    for frame in range(frame_count):
        bpy.context.scene.frame_current = frame

        # debug log output
        if verbose:
            name = skeleton_obj.name
            m = f"Animating frame {frame} of skeleton {name}."
            print(m, flush=True)

        # for each bone bone: get start point & orientation
        for bone_name in bone_names:
            # read head
            ... skipped ...
            hi_RS = Vector([x, y, z])
            bone_his_RS[bone_name] = hi_RS

            # read rotation
            ... skipped ...
            bone_qis_RS[bone_name] = q_i_RS

        for name in bone_names:
            if name == "b_root":
                continue

            # get pose bone, zero pose & current pose
            pose_bone = pose_bones[name]
            bone_q_i_RS = bone_qis_RS[name]
            bone_head_i_RS = bone_his_RS[name]

            pose_bone.matrix = Matrix.LocRotScale(bone_head_i_RS, bone_q_i_RS, None)
            pose_bone.keyframe_insert(data_path="rotation_quaternion", frame=frame)
            pose_bone.keyframe_insert(data_path="location", frame=frame)
    bpy.ops.object.mode_set(mode="OBJECT")

The result looks like: messed up pose bones in Blender Only the first pose, the rest pose, looks okay. Everything with frame > 0 is a mess.

However, when I toggle between pose and object mode after each bone update, I get the correct result:

    # animate the bones
    bone_qis_RS = {} # rotation of each bone at frame i
    bone_his_RS = {} # head of each bone at frame i

    for frame in range(frame_count):
        bpy.context.scene.frame_current = frame

        # debug log output
        if verbose:
            name = skeleton_obj.name
            m = f"Animating frame {frame} of skeleton {name}."
            print(m, flush=True)

        # for each bone bone: get start point & orientation
        for bone_name in bone_names:
            # read head
            ... skipped ...
            hi_RS = Vector([x, y, z])
            bone_his_RS[bone_name] = hi_RS

            # read rotation
            ... skipped ...
            bone_qis_RS[bone_name] = q_i_RS

        for name in bone_names:
            if name == "b_root":
                continue

            # get pose bone, zero pose & current pose
            pose_bone = pose_bones[name]
            bone_q_i_RS = bone_qis_RS[name]
            bone_head_i_RS = bone_his_RS[name]
            
            bpy.context.view_layer.objects.active = skeleton_obj
            bpy.ops.object.mode_set(mode="POSE")
            pose_bone.matrix = Matrix.LocRotScale(bone_head_i_RS, bone_q_i_RS, None)
            pose_bone.keyframe_insert(data_path="rotation_quaternion", frame=frame)
            pose_bone.keyframe_insert(data_path="location", frame=frame)
            bpy.ops.object.mode_set(mode="OBJECT")

The character walks properly: correct walking pose in Blender

Though, importing a motion is now considerably slower and takes quite some time. I was wondering what internal updates Blender does and how this can be done more efficiently, but without the mess shown above? Intuitively, importing a motion sequence should be very fast as all the bone poses are already computed and only need to be forwarded to Blender.

How are the bone coordinate systems propagated from parents to their children? Is there an update function or so I can directly call on the bone I changed? When is pose_bone.matrix updated by Blender due to changing the rotation quaternion of a parent?

Any insights into how the bones are updated internally and how to best set their poses are appreciated!

$\endgroup$
2
  • $\begingroup$ It might be the case that you need to get a reference to the armature with its updated depsgraph at the beginning of each frame before you do any further movement with Python. Toggling Pose mode probably forces that to happen which is why it works as expected when you do it that way. I'm not totally sure cause I don't do a ton of animation with Python, but that is my hunch for what is the issue. $\endgroup$ Commented Nov 15, 2024 at 17:03
  • $\begingroup$ Here's a page in the docs that refers to what I'm talking about. $\endgroup$ Commented Nov 15, 2024 at 17:03

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.