# Blender headless: convert with-skin Mixamo FBX clips to anim-only FBX # (deletes meshes, keeps armature+action). MIRPAMO's retarget mapper needs # anim-only sources. # # Usage: Blender -b --python pipeline/strip_skin.py -- --clips "a.fbx,b.fbx" --out dir import argparse import sys from pathlib import Path import bpy def parse_args(): argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] ap = argparse.ArgumentParser() ap.add_argument("--clips", required=True) ap.add_argument("--out", required=True) return ap.parse_args(argv) args = parse_args() out = Path(args.out).expanduser() out.mkdir(parents=True, exist_ok=True) for clip in args.clips.split(","): clip = Path(clip).expanduser() # GLB out: Blender 5's FBX exporter flattens slotted actions to identity; # the glTF exporter is slot-native and MIRPAMO imports glb clips fine. dest = (out / clip.name).with_suffix(".glb") if dest.exists(): print("SKIP", clip.name) continue bpy.ops.wm.read_factory_settings(use_empty=True) bpy.ops.import_scene.fbx(filepath=str(clip), use_anim=True) for o in list(bpy.data.objects): if o.type == "MESH": bpy.data.objects.remove(o, do_unlink=True) # normalise numbered Mixamo namespaces (mixamorig7: -> mixamorig:) so # downstream mappers (MIRPAMO retarget) match by exact name. # NOTE: bone rename does NOT fix up slotted-action fcurve paths in # Blender 5 — rewrite them explicitly or the action drives nothing. import re def all_fcurves(act): if hasattr(act, "fcurves") and len(getattr(act, "fcurves", [])): return list(act.fcurves) out = [] for layer in act.layers: for strip in layer.strips: for cb in strip.channelbags: out.extend(cb.fcurves) return out for o in bpy.data.objects: if o.type == "ARMATURE": for b in o.data.bones: b.name = re.sub(r"^mixamorig\d+:", "mixamorig:", b.name) for act in bpy.data.actions: for fc in all_fcurves(act): if '"' in fc.data_path: bone = fc.data_path.split('"')[1] fixed = re.sub(r"^mixamorig\d+:", "mixamorig:", bone) if fixed != bone: fc.data_path = fc.data_path.replace('"%s"' % bone, '"%s"' % fixed) bpy.ops.export_scene.gltf(filepath=str(dest), export_format="GLB", export_animations=True) print("STRIPPED", clip.name) print("DONE")