wrote a Blender script to mirror-copy a pose range for walk cycle second halves, rotation math is half-working and i don't trust it

260 views 4 replies

The problem: I animate frames 1–12 of a 24-frame walk cycle, then want frames 13–24 to be the L/R-flipped version of what I just did. Doing it manually is paste-pose-flipped twelve times, then checking whether the heel strike lands right. Slow and I always miss something.

So I wrote a script. Bone name flipping works, location X-mirroring is fine. The rotation part is where I'm skeptical:

import bpy

def flip_bone_name(name):
    for a, b in [('.L', '.R'), ('.R', '.L'), ('_L', '_R'), ('_R', '_L')]:
        if name.endswith(a):
            return name[:-len(a)] + b
    return name

def mirror_range(obj, src_start, src_end):
    half = src_end - src_start + 1
    scene = bpy.context.scene

    for frame in range(src_start, src_end + 1):
        scene.frame_set(frame)
        target_frame = frame + half

        for bone in obj.pose.bones:
            mirror_name = flip_bone_name(bone.name)
            if mirror_name not in obj.pose.bones:
                continue
            dst = obj.pose.bones[mirror_name]

            loc = bone.location.copy()
            loc.x *= -1
            dst.location = loc
            dst.keyframe_insert('location', frame=target_frame)

            if bone.rotation_mode == 'QUATERNION':
                q = bone.rotation_quaternion
                dst.rotation_quaternion = q.__class__(q.w, -q.x, q.y, q.z)
                dst.keyframe_insert('rotation_quaternion', frame=target_frame)
            else:
                r = bone.rotation_euler.copy()
                r.x *= -1
                r.z *= -1
                dst.rotation_euler = r
                dst.keyframe_insert('rotation_euler', frame=target_frame)

obj = bpy.context.active_object
mirror_range(obj, src_start=1, src_end=12)

Most bones look correct. But a couple of my foot controllers come out slightly twisted. The heel contact looks right but there's a small rotation error on the toe pivot. I think the problem is that some bones have a rest orientation that doesn't align with world X, so negating local X isn't the same as mirroring across the character's actual symmetry plane.

Blender's own Paste Pose Flipped handles this correctly, it must use the bone's rest pose or the armature matrix as a reference. Does anyone know what it's actually doing internally? Or is there a cleaner Python approach that doesn't bake in orientation assumptions? Also curious if anyone's tackled scale mirroring. My rig uses squash-and-stretch on some IK chains and I haven't touched that part yet.

Replying to GlitchFox: euler order haunting the script is so predictable and yet here we are every time...

worth adding one caveat: this works cleanly for local-space rotations but breaks if any of your bones have non-standard local axes in the rest pose. hand rigs especially. When bones are oriented along joint direction rather than a global axis, the component negation can look correct in quat math but not actually read as a mirror visually. quick sanity check is to compare matrix_basis before and after the negate in the viewport before you commit to baking it across the whole frame range. caught me out once on a finger rig and i spent way too long wondering why the mirrored hand looked subtly wrong.

Replying to VertexWing: the rotation math being half-wrong is almost always euler order haunting you. if...

euler order haunting the script is so predictable and yet here we are every time. if you want to sidestep it entirely, do the mirror in quaternion space: convert to quat, negate the Y and Z components (for an X-axis mirror), convert back. order-agnostic and way more reliable. slightly awkward through Blender's pose bone API since you're typically reading/writing eulers anyway, but on a rig with mixed bone conventions it's worth the extra conversion steps.

spinning math equations nightmare

the rotation math being half-wrong is almost always euler order haunting you. if you're mirroring by negating certain rotation components, it only works reliably when every bone uses the same convention. blender is notorious for mixing these depending on how bones were created or imported. safer approach is to do the copy step in quaternion space: grab the quat, negate the appropriate components for your mirror axis, convert back. way more predictable and it stops being a "why does this work on half the bones" mystery.

haunted by euler angles
Replying to ShadowPulse: worth adding one caveat: this works cleanly for local-space rotations but breaks...

Non-standard rest pose axes are the thing almost every "negate components in quaternion space" solution quietly assumes away, and hand rigs are the worst for it since riggers orient finger bones along the chain in all kinds of directions. What's worked for me: compute a per-bone correction quaternion at script startup by comparing the actual rest rotation against the expected canonical forward axis. Apply the inverse correction before the flip, reapply after. A bit of per-rig setup but the mirror stops breaking whenever you swap rigs.

Moonjump
Forum Search Shader Sandbox
Sign In Register