57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
|
|
"""Compare finger-bone REST rotations between GLB skeletons.
|
||
|
|
First GLB is the reference; each other is reported as per-bone angle deviation (degrees).
|
||
|
|
usage: rest_deviation.py canonical.glb other.glb [more.glb ...]
|
||
|
|
"""
|
||
|
|
import json, struct, sys, math
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
FING = ("thumb", "index", "middle", "ring", "pinky")
|
||
|
|
|
||
|
|
|
||
|
|
def read_gltf(path):
|
||
|
|
d = Path(path).read_bytes()
|
||
|
|
jlen = struct.unpack_from("<I", d, 12)[0]
|
||
|
|
return json.loads(d[20:20 + jlen].decode("utf-8"))
|
||
|
|
|
||
|
|
|
||
|
|
def rests(path):
|
||
|
|
g = read_gltf(path)
|
||
|
|
out = {}
|
||
|
|
for nd in g["nodes"]:
|
||
|
|
n = nd.get("name", "")
|
||
|
|
if any(t in n.lower() for t in FING):
|
||
|
|
out[n] = nd.get("rotation", [0, 0, 0, 1])
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def angle_between(a, b):
|
||
|
|
"""Geodesic angle (deg) between two unit quaternions, sign-insensitive."""
|
||
|
|
d = abs(sum(x * y for x, y in zip(a, b)))
|
||
|
|
d = max(-1.0, min(1.0, d))
|
||
|
|
return math.degrees(2 * math.acos(d))
|
||
|
|
|
||
|
|
|
||
|
|
ref_path = sys.argv[1]
|
||
|
|
ref = rests(ref_path)
|
||
|
|
print(f"reference: {Path(ref_path).name} ({len(ref)} finger bones)")
|
||
|
|
|
||
|
|
for p in sys.argv[2:]:
|
||
|
|
other = rests(p)
|
||
|
|
print(f"\n== {Path(p).name} == {len(other)} finger bones")
|
||
|
|
missing = sorted(set(ref) - set(other))
|
||
|
|
extra = sorted(set(other) - set(ref))
|
||
|
|
if missing:
|
||
|
|
print(f" MISSING vs ref ({len(missing)}): {', '.join(missing)}")
|
||
|
|
if extra:
|
||
|
|
print(f" EXTRA vs ref ({len(extra)}): {', '.join(extra)}")
|
||
|
|
devs = []
|
||
|
|
for n in sorted(set(ref) & set(other)):
|
||
|
|
devs.append((angle_between(ref[n], other[n]), n))
|
||
|
|
devs.sort(reverse=True)
|
||
|
|
if not devs:
|
||
|
|
continue
|
||
|
|
over = [d for d in devs if d[0] > 1.0]
|
||
|
|
print(f" shared {len(devs)} | deviating >1deg: {len(over)} | max {devs[0][0]:.1f}deg ({devs[0][1]})")
|
||
|
|
for d, n in devs[:12]:
|
||
|
|
print(f" {n:24s} {d:6.1f}deg")
|