project
Ghost Hand
Public Unreviewedby John Lauer
A small battery gadget that keeps an automatic sink faucet running while you use the sink: it learns the faucet's timeout, re-arms with the shortest blip, and stops after 10 minutes or when picked up.
main
John Lauer
Rev 2 hero 3D: an adult hand for scale beside the case (CC0, MakeHuman base mesh)
9852f34
1d ago
import json, numpy as np, trimesh
m=trimesh.load('hand_raw.ply')
V=[];
for l in open('base.obj'):
if l.startswith('v '): V.append([float(x) for x in l.split()[1:4]])
V=np.array(V)
w=json.load(open('default_weights.mhw'))['weights']
def pts(prefixes,th=0.5):
s=np.zeros(len(V))
for b in w:
if b.endswith('.R') and b.startswith(prefixes):
for i,x in w[b]: s[i]+=x
return V[s>th]
wrist=pts(('wrist',)).mean(0)
tip=pts(('finger3-3',),0.8) # middle fingertip segment
tipf=tip[np.argmax(np.linalg.norm(tip-wrist,axis=1))]
thumb=pts(('finger1-3',),0.8).mean(0)
pinky=pts(('finger5-3',),0.8).mean(0)
L=tipf-wrist; Lu=L/np.linalg.norm(L)
side=thumb-pinky; side-=side.dot(Lu)*Lu; Su=side/np.linalg.norm(side)
N=np.cross(Lu,Su) # normal; sign checked below
# palm faces the body (+x in MakeHuman A-pose); make Nd point to the palm side
if N[0]<0: N=-N
print('wrist',wrist.round(3),'len dm',np.linalg.norm(L).round(3))
# right hand palm down: fingers +Y, palm normal -Z, then thumb lands on -X (left side seen from above)
R=np.stack([np.cross(Lu,-N),Lu,-N]) # rows: new X,Y,Z axes expressed in old coords -> want Lu->Y, N->-Z
# new coords = R @ (p - wrist): X = (Lu x -N)·p, Y = Lu·p, Z = -N·p => palm (N side) goes to -Z
P=(m.vertices-wrist)@R.T
scale=0.180/(np.linalg.norm(L)) # 180 mm wrist-to-middle-tip, in metres
P*=scale
P[:,2]-=P[:,2].min() # rest on the counter
h=trimesh.Trimesh(P,m.faces,process=False)
if h.volume<0: h.invert()
h=h.subdivide_loop(1) if hasattr(h,'subdivide_loop') else h
h.visual=trimesh.visual.TextureVisuals(material=trimesh.visual.material.PBRMaterial(name='skin',baseColorFactor=[224,172,140,255],metallicFactor=0.0,roughnessFactor=0.65))
print('extents mm',(h.extents*1000).round(1),'faces',len(h.faces),'thumb x',((thumb-wrist)@R.T*scale*1000).round(1))
h.export('hand_ref.glb'); import os; print(os.path.getsize('hand_ref.glb')//1024,'KB')