wrote a Blender script to split actions by timeline markers — why is this not built in

500 views 0 replies

My workflow for character animation in Blender is to dump everything into one big action. Idle on frames 0–40, walk cycle on 60–100, attack on 120–160. Markers to label the ranges. Works great while you're actively animating: scrub between clips, compare timing, everything stays in context.

Then comes export time. Each clip needs to be its own named action, offset so it starts at frame 0. Ten clips means roughly twenty minutes of copy-paste-offset-rename before I can even open the FBX exporter. I did this by hand for an embarrassingly long time.

Finally wrote a script. Place markers at the start of each clip (the marker name becomes the action name), put a final marker at the end of the last clip, run the script. Done.

import bpy

def split_action_by_markers():
    obj = bpy.context.active_object
    if not obj or not obj.animation_data or not obj.animation_data.action:
        print("No animated object selected")
        return

    src = obj.animation_data.action
    markers = sorted(bpy.context.scene.timeline_markers, key=lambda m: m.frame)

    if len(markers) < 2:
        print("Need at least 2 markers")
        return

    for i in range(len(markers) - 1):
        start_m, end_m = markers[i], markers[i + 1]
        clip_name = start_m.name
        f_start, f_end = start_m.frame, end_m.frame

        action = bpy.data.actions.new(name=clip_name)
        action.use_fake_user = True

        for fc in src.fcurves:
            kps = [kp for kp in fc.keyframe_points if f_start <= kp.co.x <= f_end]
            if not kps:
                continue

            group_name = fc.group.name if fc.group else ""
            new_fc = action.fcurves.new(fc.data_path, index=fc.array_index, action_group=group_name)
            new_fc.keyframe_points.add(len(kps))

            for j, kp in enumerate(kps):
                nkp = new_fc.keyframe_points[j]
                nkp.co = (kp.co.x - f_start, kp.co.y)
                nkp.handle_left = (kp.handle_left.x - f_start, kp.handle_left.y)
                nkp.handle_right = (kp.handle_right.x - f_start, kp.handle_right.y)
                nkp.interpolation = kp.interpolation
                nkp.handle_left_type = kp.handle_left_type
                nkp.handle_right_type = kp.handle_right_type

            new_fc.update()

        print(f"Created '{clip_name}': {f_end - f_start} frames")

split_action_by_markers()

Few caveats:

  • Handles are copied manually. Mostly fine, but gets weird when a keyframe sits right at a marker boundary with handles that extend past it — you'll see slight curve shape differences at the clip edges.
  • Doesn't set the action's manual frame range (use_frame_range), so some exporters calculate range from the keyframes themselves. Easy to add if your pipeline cares about it.
  • Doesn't touch the NLA editor at all. My workflow uses "All Actions" in the FBX exporter so NLA is irrelevant to me — if you work NLA-first, this won't help much.

Curious whether anyone manages multi-clip sessions working NLA-first from the start. I always end up with markers because they feel more natural mid-session, but I wonder if I'm just creating more work for myself at export time.

Moonjump
Forum Search Shader Sandbox
Sign In Register