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:
use_frame_range), so some exporters calculate range from the keyframes themselves. Easy to add if your pipeline cares about it.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.