Files
animation/tools/skin_weight_audit.py
T

62 lines
2.6 KiB
Python
Raw Normal View History

"""Sum skin weights per bone group (finger vs hand vs rest) for each GLB. usage: weight_audit.py *.glb"""
import json, struct, sys
from pathlib import Path
FING = ("thumb", "index", "middle", "ring", "pinky")
def read_glb(path):
data = Path(path).read_bytes()
length = struct.unpack_from("<I", data, 8)[0]
off = 12; gltf = None; binc = None
while off < length:
clen, ctype = struct.unpack_from("<II", data, off); off += 8
if ctype == 0x4E4F534A: gltf = json.loads(data[off:off+clen])
elif ctype == 0x004E4942: binc = data[off:off+clen]
off += clen
return gltf, binc
def acc(gltf, binc, idx):
a = gltf["accessors"][idx]
bv = gltf["bufferViews"][a["bufferView"]]
ncomp = {"SCALAR":1, "VEC2":2, "VEC3":3, "VEC4":4}[a["type"]]
fmt = {5121:"B", 5123:"H", 5125:"I", 5126:"f"}[a["componentType"]]
stride = bv.get("byteStride")
size = struct.calcsize(fmt)*ncomp
off = bv.get("byteOffset",0) + a.get("byteOffset",0)
out = []
for i in range(a["count"]):
o = off + i*(stride or size)
out.append(struct.unpack_from("<%d%s" % (ncomp, fmt), binc, o))
return out, a["componentType"]
for path in sys.argv[1:]:
gltf, binc = read_glb(path)
names = [nd.get("name", "") for nd in gltf["nodes"]]
print(f"\n== {Path(path).name} ==")
for mi, mesh in enumerate(gltf.get("meshes", [])):
for pi, prim in enumerate(mesh.get("primitives", [])):
att = prim["attributes"]
if "JOINTS_0" not in att: continue
# which skin uses this mesh
skin_idx = next((nd.get("skin") for nd in gltf["nodes"]
if nd.get("mesh") == mi and "skin" in nd), None)
if skin_idx is None: continue
joints = gltf["skins"][skin_idx]["joints"]
jn = [names[j] for j in joints]
J, _ = acc(gltf, binc, att["JOINTS_0"])
W, wt = acc(gltf, binc, att["WEIGHTS_0"])
wsc = 1.0 if wt == 5126 else (1/255 if wt == 5121 else 1/65535)
fing_w = hand_w = 0.0
fing_verts = 0
for jrow, wrow in zip(J, W):
fv = 0
for j, w in zip(jrow, wrow):
w *= wsc
if w <= 0: continue
n = jn[j].lower()
if any(t in n for t in FING): fing_w += w; fv = 1
elif n.startswith("hand"): hand_w += w
fing_verts += fv
print(f" mesh[{mi}] '{mesh.get('name','')}' prim{pi}: {len(J)} verts | "
f"finger-weighted verts: {fing_verts} | total finger W: {fing_w:.0f} | hand W: {hand_w:.0f}")