wrote a Blender script to build a visual action catalog — 40+ actions in my file and I had no idea which was which

283 views 0 replies

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:

  • Skips actions starting with . — Blender stashes orphaned and internal actions with that prefix, you don't want those in your catalog
  • frame_offset=0.5 samples the midpoint of the clip. Pass 0.0 for the first frame if you prefer a consistent reference pose across everything
  • Uses bpy.ops.render.opengl, which captures whatever your active 3D viewport looks like — worth setting a sensible camera angle and shading mode before you run it
  • Restores the original action and frame when done, so it's safe to run mid-session without nuking your state

Main 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.

Moonjump
Forum Search Shader Sandbox
Sign In Register