Problem I kept hitting: I had ~45 actions accumulated in a single .blend — hand-keyed clips, retargeted mocap, some WIP stuff I forgot to clean out. The only way to figure out what "Action.023" actually contained was to manually assign it to the armature and scrub through it. Multiply that by every time I reopened the file after a few weeks away.
So I wrote a script that assigns each action to an armature, seeks to a configurable point in the clip, fires an OpenGL viewport render, and saves a PNG named after the action. Run it once and you've got a folder of thumbnails you can sort by filename and actually browse visually.
import bpy
import os
def render_action_thumbnails(armature_name, output_dir, frame_offset=0.5):
ob = bpy.data.objects.get(armature_name)
if ob is None or ob.type != 'ARMATURE':
raise ValueError(f"No armature named '{armature_name}'")
if ob.animation_data is None:
ob.animation_data_create()
original_action = ob.animation_data.action
original_frame = bpy.context.scene.frame_current
original_filepath = bpy.context.scene.render.filepath
os.makedirs(output_dir, exist_ok=True)
bpy.context.scene.render.image_settings.file_format = 'PNG'
for action in bpy.data.actions:
if action.name.startswith('.'):
continue # skip internally stashed actions
ob.animation_data.action = action
start, end = action.frame_range
mid = int(start + (end - start) * frame_offset)
bpy.context.scene.frame_set(mid)
bpy.context.view_layer.update()
safe_name = action.name.replace('/', '_')
bpy.context.scene.render.filepath = os.path.join(output_dir, safe_name)
bpy.ops.render.opengl(write_still=True)
print(f"[catalog] {action.name}")
ob.animation_data.action = original_action
bpy.context.scene.frame_set(original_frame)
bpy.context.scene.render.filepath = original_filepath
# run from the Blender text editor:
render_action_thumbnails("Armature", "/tmp/action_catalog/")
Few things to know:
. — Blender stashes orphaned and internal actions with that prefix, you don't want those in your catalogframe_offset=0.5 samples the midpoint of the clip. Pass 0.0 for the first frame if you prefer a consistent reference pose across everythingbpy.ops.render.opengl, which captures whatever your active 3D viewport looks like — worth setting a sensible camera angle and shading mode before you run itMain limitation right now: it assumes all actions belong to the same armature. If your file has multiple characters you'd need to add logic to route each action to the right target — totally doable, just haven't needed it yet.
Been thinking about adding a step to tile the thumbnails into a grid contact sheet, either with PIL or using Blender's compositor. For now, dumping named PNGs to a folder and sorting by filename is working fine. Anyone taken this further? Curious whether doing the grid compositing from inside Blender without reaching for an external library is even worth attempting, or if PIL is just the obvious answer.