""" blender_extract_armature.py Usage: tooling/blender --background --python tooling/blender_extract_armature.py -- Loads a Quaternius GLTF file, strips all mesh objects and animation data, and exports only the armature as a GLB skeleton file. Requirements: - 65-bone hierarchy preserved - No mesh geometry - No animation data - Y-up, -Z forward (glTF default) - 1 unit = 1 meter (Quaternius convention) """ import sys import bpy def extract_armature(input_path: str, output_path: str) -> None: # Clear the default scene bpy.ops.wm.read_factory_settings(use_empty=True) # Import the GLTF/GLB print(f"Loading: {input_path}") bpy.ops.import_scene.gltf(filepath=input_path) # Report what was imported all_objects = list(bpy.context.scene.objects) print(f"Imported {len(all_objects)} objects:") for obj in all_objects: print(f" {obj.name} ({obj.type})") # Find armature objects armatures = [obj for obj in all_objects if obj.type == 'ARMATURE'] if not armatures: print("ERROR: No armature found in the imported file.") sys.exit(1) armature = armatures[0] print(f"Armature: {armature.name} — {len(armature.data.bones)} bones") if len(armature.data.bones) < 60: print(f"WARNING: Expected ~65 bones, found {len(armature.data.bones)}. Check compatibility.") # Remove all non-armature objects (meshes, lights, cameras, empties) bpy.ops.object.select_all(action='DESELECT') for obj in all_objects: if obj.type != 'ARMATURE': obj.select_set(True) bpy.ops.object.delete() # Remove all animation data (skeleton only, no poses) if armature.animation_data: armature.animation_data_clear() for action in list(bpy.data.actions): bpy.data.actions.remove(action) # Select only the armature for export bpy.ops.object.select_all(action='DESELECT') armature.select_set(True) bpy.context.view_layer.objects.active = armature # Export as GLB — skeleton only, no animations, no meshes print(f"Exporting to: {output_path}") bpy.ops.export_scene.gltf( filepath=output_path, use_selection=True, export_format='GLB', export_animations=False, export_skins=True, export_yup=True, ) # Verify: report bone count and names bones = sorted(armature.data.bones, key=lambda b: b.name) print(f"Export complete. {len(bones)} bones:") for bone in bones: print(f" {bone.name}") if __name__ == "__main__": argv = sys.argv if "--" not in argv: print("Usage: tooling/blender --background --python tooling/blender_extract_armature.py -- ") sys.exit(1) args = argv[argv.index("--") + 1:] if len(args) < 2: print("ERROR: Provide input GLTF/GLB path and output GLB path.") sys.exit(1) extract_armature(args[0], args[1])