Add CharacterVisualDescriptor (11-variant BodyType enum, wire format encode/decode, body_type_key mapping) and CharacterVisual compositor (runtime 3D character assembler with slot architecture, BoneAttachment3D, clothing coverage, recolor mask loading, skin tone tinting, bone validation). Includes 64 unit tests and Blender utility scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
59 lines
1.6 KiB
Python
59 lines
1.6 KiB
Python
"""
|
|
blender_compare_bones.py
|
|
Usage: tooling/blender --background --python tooling/blender_compare_bones.py -- <file_a.glb> <file_b.glb>
|
|
|
|
Compares bone names between two GLB/GLTF files. Reports missing or extra bones.
|
|
"""
|
|
|
|
import sys
|
|
import bpy
|
|
|
|
|
|
def get_bones(path: str) -> set:
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
bpy.ops.import_scene.gltf(filepath=path)
|
|
for obj in bpy.context.scene.objects:
|
|
if obj.type == 'ARMATURE':
|
|
return {b.name for b in obj.data.bones}
|
|
return set()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
argv = sys.argv
|
|
if "--" not in argv:
|
|
print("Usage: tooling/blender --background --python tooling/blender_compare_bones.py -- <a.glb> <b.glb>")
|
|
sys.exit(1)
|
|
|
|
args = argv[argv.index("--") + 1:]
|
|
if len(args) < 2:
|
|
print("ERROR: Need two GLB/GLTF paths.")
|
|
sys.exit(1)
|
|
|
|
path_a, path_b = args[0], args[1]
|
|
|
|
print(f"Loading A: {path_a}")
|
|
bones_a = get_bones(path_a)
|
|
print(f"A has {len(bones_a)} bones")
|
|
|
|
print(f"Loading B: {path_b}")
|
|
bones_b = get_bones(path_b)
|
|
print(f"B has {len(bones_b)} bones")
|
|
|
|
only_in_a = bones_a - bones_b
|
|
only_in_b = bones_b - bones_a
|
|
shared = bones_a & bones_b
|
|
|
|
print(f"Shared: {len(shared)}")
|
|
if only_in_a:
|
|
print(f"Only in A ({len(only_in_a)}):")
|
|
for name in sorted(only_in_a):
|
|
print(f" - {name}")
|
|
if only_in_b:
|
|
print(f"Only in B ({len(only_in_b)}):")
|
|
for name in sorted(only_in_b):
|
|
print(f" + {name}")
|
|
if not only_in_a and not only_in_b:
|
|
print("BONE_MATCH=OK")
|
|
else:
|
|
print("BONE_MATCH=MISMATCH")
|