wrote a blender script to convert held pose keyframes to vector handles automatically — sick of bezier overshoots on things that should just hold

383 views 0 replies

The problem: Blender defaults everything to Bezier interpolation, which is usually what you want. But when you have a held pose (two adjacent keyframes with the same value), the Bezier handles can still introduce a tiny overshoot into what should be a completely flat hold. Depending on surrounding curve shape, you get this subtle float at the edge of the pose. On a looping idle it compounds every cycle. On a facial hold it's the thing making a character look vaguely restless when they should be completely still.

I've been manually hunting these down in the F-curve editor for years. Finally got fed up enough to automate it.

import bpy

def flatten_held_handles(threshold=0.001):
    obj = bpy.context.object
    if not obj or not obj.animation_data or not obj.animation_data.action:
        print("No active object with animation data.")
        return

    action = obj.animation_data.action
    changed = 0

    for fcurve in action.fcurves:
        kps = fcurve.keyframe_points
        for i in range(len(kps)):
            kf = kps[i]
            if i < len(kps) - 1:
                nxt = kps[i + 1]
                if abs(kf.co.y - nxt.co.y) <= threshold:
                    kf.handle_right_type = 'VECTOR'
                    nxt.handle_left_type = 'VECTOR'
                    changed += 1
        fcurve.update()

    print(f"Done. Converted {changed} held-pose handle pairs.")

flatten_held_handles()

How it works: iterates over every F-curve in the active action, finds adjacent keyframe pairs where values are within a threshold, and flips both handles to VECTOR. Flat approach, flat departure. No overshoot.

A few things worth knowing before you run it:

  • Only runs on the active object's current action. If you want to batch across multiple objects you'd need to loop over all scene objects and their animation data.
  • The threshold is the main thing to tune. 0.001 works fine for location and scale channels, but Euler rotation data is noisier, so bumping to 0.01 or higher is usually safer there, especially on anything that came through mocap retargeting.
  • VECTOR handles produce a genuinely flat hold: hard arrival, hard departure. If you actually want an ease into the hold, you'd want ALIGNED handles instead. Different problem, different fix.

The thing I haven't sorted out: automatically detecting which channels need a tighter vs. looser threshold. Right now I'm running it twice with different values and eyeballing, which is embarrassing. Anyone solved this more cleanly? Also curious if it's worth checking whether a keyframe pair is already using constant interpolation and skipping those in the output, because technically they don't have the problem but they still get counted in the changed total right now.

Moonjump
Forum Search Shader Sandbox
Sign In Register