89 lines
2.5 KiB
Python
89 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Create a compact animation-only GLB from selected actions in a source GLB.
|
|
|
|
Usage:
|
|
blender --background --python tools/extract_animation_subset.py -- \
|
|
<source.glb> <output.glb> <clip> [<clip> ...]
|
|
|
|
The source skeleton is retained, all meshes and unrelated actions are removed,
|
|
and each requested action is exported as its own NLA track. This keeps external
|
|
authored animation libraries small enough to ship as retargeting sources.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
argv = sys.argv
|
|
argv = argv[argv.index("--") + 1:] if "--" in argv else []
|
|
if len(argv) < 3:
|
|
print(
|
|
"Usage: blender --background --python "
|
|
"tools/extract_animation_subset.py -- "
|
|
"<source.glb> <output.glb> <clip> [<clip> ...]"
|
|
)
|
|
sys.exit(1)
|
|
|
|
SOURCE = argv[0]
|
|
OUTPUT = argv[1]
|
|
REQUESTED = argv[2:]
|
|
|
|
|
|
def main():
|
|
bpy.ops.object.select_all(action="SELECT")
|
|
bpy.ops.object.delete()
|
|
bpy.ops.import_scene.gltf(filepath=SOURCE)
|
|
|
|
armatures = [obj for obj in bpy.data.objects if obj.type == "ARMATURE"]
|
|
if len(armatures) != 1:
|
|
print(f"ERROR: expected one armature, found {len(armatures)}")
|
|
sys.exit(1)
|
|
armature = armatures[0]
|
|
|
|
missing = [name for name in REQUESTED if bpy.data.actions.get(name) is None]
|
|
if missing:
|
|
print(f"ERROR: requested actions not found: {missing}")
|
|
sys.exit(1)
|
|
|
|
for obj in list(bpy.data.objects):
|
|
if obj != armature:
|
|
bpy.data.objects.remove(obj, do_unlink=True)
|
|
|
|
if not armature.animation_data:
|
|
armature.animation_data_create()
|
|
for track in list(armature.animation_data.nla_tracks):
|
|
armature.animation_data.nla_tracks.remove(track)
|
|
armature.animation_data.action = None
|
|
|
|
keep = set()
|
|
for name in REQUESTED:
|
|
action = bpy.data.actions[name]
|
|
action.use_fake_user = True
|
|
track = armature.animation_data.nla_tracks.new()
|
|
track.name = name
|
|
strip = track.strips.new(name, 0, action)
|
|
strip.name = name
|
|
track.mute = True
|
|
keep.add(action)
|
|
|
|
for action in list(bpy.data.actions):
|
|
if action not in keep:
|
|
bpy.data.actions.remove(action)
|
|
|
|
os.makedirs(os.path.dirname(os.path.abspath(OUTPUT)), exist_ok=True)
|
|
bpy.ops.export_scene.gltf(
|
|
filepath=OUTPUT,
|
|
export_format="GLB",
|
|
export_yup=True,
|
|
export_animations=True,
|
|
export_animation_mode="NLA_TRACKS",
|
|
export_skins=True,
|
|
export_bake_animation=True,
|
|
)
|
|
print(f"Exported {len(keep)} clips to {OUTPUT}")
|
|
|
|
|
|
main()
|