Files
animation/tools/edge_stretch_cmp.py
T

78 lines
2.7 KiB
Python
Raw Normal View History

"""Edge-stretch comparison across OBJ dirs, with a rest-length split.
usage: stretch_cmp.py <rest_dir> <label>=<dir> [<label>=<dir> ...]
Rest OBJs (rest_hand_l/r.obj) come from <rest_dir>; every compared dir must share the
body's vertex order. Reports, per pose and hand: max / p99.9 / >2x / >5x over ALL edges,
then the subset that is real geometry (>=1mm rest length) and the subset that is
VISIBLE (posed length >=1cm) — the count that decides whether a render shows a needle.
"""
import math
import os
import sys
def load_obj(path):
vs, faces = [], []
with open(path) as f:
for line in f:
if line.startswith("v "):
p = line.split()
vs.append((float(p[1]), float(p[2]), float(p[3])))
elif line.startswith("f "):
idx = [int(t.split("/")[0]) - 1 for t in line.split()[1:]]
for i in range(1, len(idx) - 1):
faces.append((idx[0], idx[i], idx[i + 1]))
return vs, faces
def edge_set(faces):
es = set()
for a, b, c in faces:
for u, v in ((a, b), (b, c), (c, a)):
es.add((u, v) if u < v else (v, u))
return sorted(es)
def dist(p, q):
return math.sqrt(sum((p[i] - q[i]) ** 2 for i in range(3)))
rest_dir = sys.argv[1]
cols = [a.split("=", 1) for a in sys.argv[2:]]
for hand in ("l", "r"):
rest_v, rest_f = load_obj(os.path.join(rest_dir, f"rest_hand_{hand}.obj"))
edges = edge_set(rest_f)
rest_len = [dist(rest_v[a], rest_v[b]) for a, b in edges]
print(f"\n=== hand_{hand} ({len(rest_v)} verts, {len(edges)} edges) ===")
print(f"{'pose / build':22s} {'max':>8s} {'p99.9':>7s} {'>2x':>6s} {'>5x':>6s}"
f" {'>5x real':>9s} {'>=1cm':>7s}")
for pose in ("flat", "fist", "grip"):
for label, d in cols:
f = os.path.join(d, f"{pose}_hand_{hand}.obj")
if not os.path.exists(f):
continue
v, _ = load_obj(f)
if len(v) != len(rest_v):
print(f"{pose+' '+label:22s} VERT COUNT MISMATCH {len(v)} vs {len(rest_v)}")
continue
rs = []
gt5 = gt2 = real5 = vis = 0
for (a, b), rl in zip(edges, rest_len):
if rl <= 1e-9:
continue
lq = dist(v[a], v[b])
r = lq / rl
rs.append(r)
if r > 2:
gt2 += 1
if r > 5:
gt5 += 1
if rl >= 0.001:
real5 += 1
if lq >= 0.01:
vis += 1
rs.sort()
print(f"{pose+' '+label:22s} {rs[-1]:7.1f}x {rs[int(len(rs)*0.999)]:6.2f}x"
f" {gt2:6d} {gt5:6d} {real5:9d} {vis:7d}")