chore(mako): handfix keepers — clip-curl pose recipe, repair masks, fin_locate

pose_clipcurl_idle.json is the recipe for extracting a clip's pinned curl;
the .txt files are the crosshand-repair vertex masks. The 230 MB of A/B bake
GLBs and render dirs stay out as regenerable scratch (now gitignored);
review/ evidence PNGs were already in the ignored tier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 12:08:41 -07:00
parent fb6cf9a2cb
commit e2403d4b3e
71 changed files with 58565 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
"""Cluster the worst-stretched edges by REST position to name the digit region."""
import sys, os, math
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(tok.split('/')[0]) - 1 for tok 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)))
d = sys.argv[1]
for hand in ('l', 'r'):
rest_v, rest_f = load_obj(os.path.join(d, f'rest_hand_{hand}.obj'))
edges = edge_set(rest_f)
xs = sorted(v[0] for v in rest_v)
print(f'hand_{hand}: rest x range {xs[0]*100:.1f}..{xs[-1]*100:.1f} cm, '
f'y range {min(v[1] for v in rest_v)*100:.1f}..{max(v[1] for v in rest_v)*100:.1f}, '
f'z range {min(v[2] for v in rest_v)*100:.1f}..{max(v[2] for v in rest_v)*100:.1f}')
for pose in ('fist', 'grip'):
v, _ = load_obj(os.path.join(d, f'{pose}_hand_{hand}.obj'))
bad = []
for a, b in edges:
rl = dist(rest_v[a], rest_v[b])
if rl <= 1e-9:
continue
r = dist(v[a], v[b]) / rl
if r > 5:
bad.append((r, a))
# bounding box of bad verts in rest space
pts = [rest_v[a] for _, a in bad]
if not pts:
print(f' {pose}: no >5x edges')
continue
bx = (min(p[0] for p in pts)*100, max(p[0] for p in pts)*100)
by = (min(p[1] for p in pts)*100, max(p[1] for p in pts)*100)
bz = (min(p[2] for p in pts)*100, max(p[2] for p in pts)*100)
cx = sum(p[0] for p in pts)/len(pts)*100
cy = sum(p[1] for p in pts)/len(pts)*100
cz = sum(p[2] for p in pts)/len(pts)*100
print(f' {pose}: {len(bad)} edges>5x rest-bbox x[{bx[0]:.1f},{bx[1]:.1f}] '
f'y[{by[0]:.1f},{by[1]:.1f}] z[{bz[0]:.1f},{bz[1]:.1f}] centroid ({cx:.1f},{cy:.1f},{cz:.1f}) cm')
# z-histogram (palm axis?) to see if it's one digit or spread
zs = sorted(p[2]*100 for p in pts)
q = lambda f: zs[int(f*(len(zs)-1))]
print(f' rest z quartiles: {q(0):.1f} / {q(0.25):.1f} / {q(0.5):.1f} / {q(0.75):.1f} / {q(1):.1f}')