wrote a Blender script to audit orphaned animation actions — didn't realize how many were rotting in my .blend files

260 views 10 replies

Was trying to figure out why my character project .blend had ballooned past 800MB — definitely not 800MB worth of meshes. Turned out I had accumulated nearly 300 animation actions across months of work: retakes, variants, test poses, replaced mocap imports, old facial rigs. All sitting there with fake user flags keeping them alive.

Blender's built-in orphan purge (File → Clean Up → Purge All) only removes data with zero users and no fake user flag. Most of my dead actions had fake_user set from old imports or accidental F-key presses, so the purge did exactly nothing.

Wrote a script to actually surface what's referenced vs what's just floating:

import bpy

def get_referenced_actions():
    referenced = set()
    for obj in bpy.data.objects:
        ad = obj.animation_data
        if not ad:
            continue
        if ad.action:
            referenced.add(ad.action.name)
        for track in ad.nla_tracks:
            for strip in track.strips:
                if strip.action:
                    referenced.add(strip.action.name)
    return referenced

def audit_orphaned_actions(purge=False):
    referenced = get_referenced_actions()
    orphans = [a for a in bpy.data.actions if a.name not in referenced]

    print(f"Referenced: {len(referenced)},  Orphaned: {len(orphans)}")
    for action in orphans:
        print(f"  {action.name!r}  fake_user={action.use_fake_user}  users={action.users}")
        if purge:
            action.use_fake_user = False

    if purge:
        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=False, do_recursive=True)
        print("Purge complete.")

# Run audit first to see what you've got
audit_orphaned_actions(purge=False)

# Uncomment when ready to actually purge
# audit_orphaned_actions(purge=True)

Run it from the Scripting editor, check the output, then flip purge=True when you're confident. Save a backup first. Once those actions are gone they're gone — Blender won't ask twice.

Dropped my file from 840MB to 390MB. The remaining 390MB is apparently actual geometry so I can only blame myself for that one.

One gap I haven't closed: this doesn't catch shape key actions or grease pencil animation data, which live in different datablock types. If anyone knows a cleaner way to walk the full bpy.data animation reference graph comprehensively, I'd love to see it.

Replying to NimbusPike: The Walk.001 spiral is so real. I wrote a small cleanup script just to detect th...

Combining the suffix check with action.users is really the move. Suffix + zero users is almost certainly a duplicate that was never cleaned up. Suffix + active users at least warrants a look, but is probably also junk.

import bpy, re

SUFFIX_PATTERN = re.compile(r'\.\d{3}$')

for action in bpy.data.actions:
    has_suffix = bool(SUFFIX_PATTERN.search(action.name))
    is_orphaned = action.users == 0
    if has_suffix or is_orphaned:
        auto_remove = has_suffix and is_orphaned
        status = 'AUTO-REMOVE' if auto_remove else 'REVIEW'
        print(f'[{status}] {action.name} ({action.users} users)')

First time I ran something like this I dropped my action count by about 60%. The REVIEW ones were almost all duplicates too, just named in a way the script couldn't prove automatically without a human sanity check.

Replying to EmberArc: the prefix convention is the only thing keeping me sane on multi-character proje...

the armature-based auto-prefix is a great idea until you rename the armature object (even just fixing a typo), and suddenly all your existing actions have the old name baked in. ended up with a bunch of actions flagged as orphaned that were actively referenced in NLA strips, just with a stale prefix.  this is fine office fire

fix was adding a secondary pass that ignores the prefix entirely and checks whether the action appears in any NLA strip directly. if it's in a strip, it's not orphaned regardless of what the name says. catches those false positives without much extra complexity.

Replying to BytePulse: lmao the .001.001.001 chain is a rite of passage. mine hit .004 before i noticed...

the action picker in the NLA editor is its own punishment. didn't hit .004 but .001.001 was enough, especially because in the picker they all look basically identical unless you zoom the name column. spent an embarrassing amount of time selecting a stale dupe, animating on it, and only noticing when i went to link the action somewhere and it had zero users.

at some point i just started prefixing with dates. 2025_02_walk_cycle instead of Walk. ugly but at least you can tell them apart at a glance in the picker. shouldn't have to do this but here we are.

scrolling through identical options forever
Replying to AstralDusk: lmao 800MB is so relatable. mine ballooned similarly and it turned out I'd been ...

The Walk.001 spiral is so real. I wrote a small cleanup script just to detect that pattern. Anything with a .001 suffix gets flagged and listed for review. Turned out most of mine came from Ctrl+D-ing the armature instead of properly linking a shared action through the NLA. Once I understood the actual cause it was easy to stop doing it, but the cleanup pass for 200+ actions was still brutal.

Your audit approach sounds like exactly what I needed three months ago.

Replying to AstralDusk: lmao 800MB is so relatable. mine ballooned similarly and it turned out I'd been ...

lmao the .001.001.001 chain is a rite of passage. mine hit .004 before i noticed. what made it worse: browsing actions in the animation tree node picker and having to scroll through eight near-identical names trying to remember which "Walk" is the one actually in use versus the three ghosts of experiments past.

scrolling through endless identical options
Replying to DriftReef: lol the action picker really is its own punishment. i started prefixing everythi...

the prefix convention is the only thing keeping me sane on multi-character projects. i extended it a bit: my import pipeline auto-prefixes based on which armature the action was baked onto, so it's never a manual step. the moment you forget once and end up with a stray Walk in a project that's otherwise all PC_Walk, it immediately stands out instead of hiding in plain sight.

the grouping in the NLA editor track headers alone is worth it. feels like a small thing until you're trying to find one specific action across 40 tracks at 11pm.

scrolling through very long list desperately
Replying to ByteStone: the armature-based auto-prefix is a great idea until you rename the armature obj...

The rename problem is real, but the fix is separating the prefix definition from the armature object name entirely. I keep a custom property directly on the armature object. In Blender you can do arm["action_prefix"] = "PC_" and it survives renames, file merges, and appends. The import script reads that property instead of deriving anything from the object name.

This means you set the prefix once when you create the armature, and renaming the object later is completely irrelevant. If the property is missing, the script logs a warning and skips auto-prefixing rather than silently baking in the wrong name. A bit more setup upfront, but it hasn't surprised me once since switching to it.

Replying to RogueStone: The rename problem is real, but the fix is separating the prefix definition from...

oh this is smart. i’ve been reading the prefix from the armature object name and hit the rename problem more than once. using a custom property as the canonical source is obviously the right answer in retrospect.

does your import pipeline read that property automatically or do you set it manually when first setting up a new character rig?

obvious solution facepalm

lmao 800MB is so relatable. mine ballooned similarly and it turned out I'd been duplicating actions instead of properly renaming them — ended up with "Walk", "Walk.001", "Walk.001.001" and none had fake users so blender just sat on them silently. an audit pass like this should honestly be mandatory before any project handoff

opening closet everything falls out
Replying to BinarySpark: the action picker in the NLA editor is its own punishment. didn't hit .004 but ....

lol the action picker really is its own punishment. i started prefixing everything with a short character/rig code (PC_, EN_, BOSS_ etc.) so the list at least groups in a way that's navigable. doesn't fix the clutter but at least you're not squinting at eight near-identical Walk.001 entries trying to remember which one actually has keyframes on it.

squinting at screen trying to read tiny text
Moonjump
Forum Search Shader Sandbox
Sign In Register