wrote a Blender script to reverse an action in-place — the handle math is what trips you up

303 views 6 replies

Kept needing reversed versions of animations, like a put-down from a pick-up, a reversed reload for an "undo" interaction, that kind of thing. Blender doesn't have a built-in reverse action operator that actually works cleanly, and the manual approach (scale by -1 in the graph editor) messes up your frame range and leaves you with negative frame numbers.

Wrote a script that does it properly. The core idea: remap each keyframe's time from [start, end] to [end, start]. That part is obvious. What's less obvious is that you also have to swap and negate the Bezier handle offsets, otherwise your easing curves end up mirrored wrong and slow-outs become slow-ins.

import bpy

def reverse_action(action, start_frame=None, end_frame=None):
    if not action or not action.fcurves:
        return

    all_times = [kp.co.x for fc in action.fcurves for kp in fc.keyframe_points]
    if not all_times:
        return

    t_start = start_frame if start_frame is not None else min(all_times)
    t_end   = end_frame   if end_frame   is not None else max(all_times)
    t_range = t_end - t_start

    for fcurve in action.fcurves:
        for kp in fcurve.keyframe_points:
            orig_t = kp.co.x
            new_t  = t_start + (t_range - (orig_t - t_start))

            lh_offset = kp.handle_left.x  - orig_t
            rh_offset = kp.handle_right.x - orig_t

            kp.co.x = new_t
            kp.handle_left.x  = new_t - rh_offset
            kp.handle_right.x = new_t - lh_offset

        fcurve.keyframe_points.sort()
        fcurve.update()

# Usage
action = bpy.data.actions.get("MyAction")
if action:
    reverse_action(action)

A few caveats: modifies the action in-place, so duplicate it first if you want to keep the original. It's purely reversing time, not values, which is almost always what you want. Quaternion rotation channels reverse cleanly with this. Euler channels can develop flips if the original already had discontinuities, but that's a pre-existing problem, not something the script introduces.

Works well enough that I've wired it into my sidebar panel as a one-click button. Curious if anyone's hit edge cases, particularly on rigs that mix Euler and quaternion bones in the same action, which I haven't fully stress-tested.

Replying to CrystalByte: revive is the strongest argument for this, fully agree. one thing that bites me ...

The easing flip problem sounds like it should have a clean mathematical solution but never really does in practice. Ease-in becomes ease-out in theory, but the feel is wrong because the physical context is different: dying has gravity assisting the motion, reviving is fighting it. The curves look like mirrors of each other but they don't read that way to a viewer.

What I've landed on: reverse the timing, preserve maybe a third of the original easing character, then do a dedicated pass on whatever reads heavy or floaty specifically in the revive context. It's still a starting-point workflow. Saves real time, but it doesn't eliminate the polish pass. Just moves you forward faster.

Replying to CrystalByte: revive is the strongest argument for this, fully agree. one thing that bites me ...

The fix I've landed on for the inherited easing problem: flatten all handles to linear before running the reverse, then re-add easing manually on the revive pass. It's an extra step, but the alternative is fighting curves you never intended to keep. A revive should feel like a revive: deliberately timed, not the death animation run backwards with its settle still baked in.

Adds maybe 10–15 minutes per clip, but the result is intentional instead of accidental, and that difference shows up in feel pretty clearly.

Replying to ShadowDrift: The use case that jumped out at me: revive animations. If you have a character d...

tbh the first time i needed a reversed interact animation i manually flipped every keyframe by hand before someone mentioned there were tools for this. felt pretty dumb. but the "starting point" framing is exactly right. Reversed motion almost always needs easing rework even when the poses look correct. things that ease in naturally going forward tend to feel weirdly floaty on the way back. the poses are free, the timing still costs you something.

embarrassed looking away slowly

The use case that jumped out at me: revive animations. If you have a character death sequence, reversing it gives you a solid starting point for the revive. Hands push up, spine uncurls, head lifts. The reversed version won't be final, but it saves the entire blocking pass and the weight timing usually reads correctly because it's literally the same performance in reverse. Same logic applies to any interact-and-undo pair: equip/unequip, open/close, draw/sheath.

One question on the implementation: does it handle the action's frame range, or does it flip key positions and leave the range as-is? Asking because if the original has a few held frames at the end, reversing puts those held frames at the beginning, which is almost never what you want for something like a revive or an unequip.

Replying to ShadowDrift: The use case that jumped out at me: revive animations. If you have a character d...

revive is the strongest argument for this, fully agree. one thing that bites me every time though: if the death anim has any easing baked in (slow settle at the end, a little anticipation at the start), it reads completely wrong reversed. a slow peaceful collapse becomes a sudden violent lurch back upright. less "rising hero" and more "corpse has regrets."

i've started flattening the handles on the source action before reversing when i know i'll need both directions. defeats the one-click appeal a bit, but it beats manually cleaning up handles on the reversed result after the fact.

zombie suddenly standing up

unexpected use case i found for this: idle variations. i have a base idle and wanted a second version for visual variety so players aren't staring at the exact same loop forever. used to manually re-pose and re-key the whole thing by hand. now i reverse the original, tweak a few frames to account for asymmetry, done. not a perfect mirror obviously but good enough that nobody's ever mentioned it.

sneaky shortcut works perfectly

Moonjump
Forum Search Shader Sandbox
Sign In Register