"""PROCITY mixamo-clip converter — a Mixamo `.fbx` animation → a mesh-free house clip GLB, the exact shape of the shipped `web/models/peds/walk.glb` / `idle.glb`: armature + one animation, NO mesh, but WITH the (now dangling) skin that Blender's glTF I/O leaves behind — 65 `mixamorig` joints + inverseBindMatrices, referenced by zero nodes. That vestigial skin is what makes glTF tooling (and Blender re-import) reconstruct the file as an armature; three.js binds the clip by track name via Lane D's `rigs.js` `_canon` regardless, so no bone rename is needed. Runs headless on this box's Blender: BL=/Applications/Blender.app/Contents/MacOS/Blender "$BL" --background --python fbx_to_clip.py -- IN.fbx OUT.glb [ClipName] Two stages, one command: (1) Blender imports the FBX (mesh+armature+anim) and exports a SKINNED GLB (the skin needs the mesh to be emitted); (2) a pure-python glTF strip removes the mesh + its geometry accessors, re-indexing/compacting so only the skeleton + dangling skin + animation remain (≈100 KB). """ import bpy, sys, os, json, struct, tempfile ARGV = sys.argv[sys.argv.index("--") + 1:] FBX, OUT = ARGV[0], ARGV[1] CLIP = ARGV[2] if len(ARGV) > 2 else "Clip" os.makedirs(os.path.dirname(os.path.abspath(OUT)), exist_ok=True) def wipe(): for o in list(bpy.data.objects): bpy.data.objects.remove(o, do_unlink=True) for coll in (bpy.data.meshes, bpy.data.materials, bpy.data.images, bpy.data.armatures, bpy.data.actions): for d in list(coll): if d.users == 0: coll.remove(d) # ── stage 2: pure-python glTF mesh-strip (keep skeleton + dangling skin + animation) ────────────── def read_glb(p): d = open(p, "rb").read() off = 12; js = None; binblob = b"" while off < len(d): clen, ctype = struct.unpack("