Files
hand-motion-pipeline/scripts/build_box_collision_v2.py
liyang ae28d55f81 Update to 2026-09-17 pipeline snapshot; add weights, L20 assets and recording via Git LFS
Source: RGB-D -> Dyn-HaMR -> L20 retargeting -> FoundationPose -> reference repair -> SPIDER,
documented in docs/PIPELINE_LATEST.md and docs/SETUP_AND_WEIGHTS.md. Adds FoundationPose and
nvdiffrast upstream snapshots, requirements/pipeline_venv.txt and the FoundationPose weight
manifest/downloader.

Assets (Git LFS): weights/ (WiLoR detector, HandFlow denoiser, UniDepth-L), FoundationPose
checkpoints, HaMeR checkpoint, Dyn-HaMR HMP model and BMC constraints, L20 URDF/meshes, the
20260915_171525 D405 recording and the two box CADs. MANO models are not redistributed
(third_party/hamer/_DATA/data/mano/README.txt). Environments, caches and run outputs excluded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:43:37 +08:00

25 lines
2.3 KiB
Python

"""Repair CAD T junctions without geometry changes, then decompose without a 20-hull cap."""
from pathlib import Path
import numpy as np,trimesh,json,coacd
ROOT=Path(__file__).resolve().parents[1];O=ROOT/'output/collision_fix_20260915';C=O/'collision_v2';C.mkdir(parents=True,exist_ok=True)
report={};coacd.set_log_level('warn')
for name,file in [('upper','上半.stl'),('lower','下半.stl')]:
old=trimesh.load(ROOT/'docs'/file);m=old.copy();splits=0
for iteration in range(100):
count=np.bincount(m.edges_unique_inverse);edges=m.edges_unique[count==1];changed=False;v=m.vertices
for a,b in edges:
ab=v[b]-v[a];t=(v-v[a])@ab/(ab@ab);distance=np.linalg.norm(v-(v[a]+t[:,None]*ab),axis=1);inside=np.flatnonzero((t>1e-7)&(t<1-1e-7)&(distance<1e-8))
if len(inside)==0:continue
faces=m.faces.tolist();hit=[i for i,f in enumerate(faces) if a in f and b in f]
if len(hit)!=1:continue
face=faces.pop(hit[0]);k=next(k for k in range(3) if face[k] in [a,b] and face[(k+1)%3] in [a,b]);a,b,third=face[k],face[(k+1)%3],face[(k+2)%3];inside=sorted(inside,key=lambda x:np.linalg.norm(v[x]-v[a]));chain=[a,*inside,b];faces.extend([[int(x),int(y),int(third)] for x,y in zip(chain[:-1],chain[1:])]);m=trimesh.Trimesh(v.copy(),faces,process=False);splits+=1;changed=True;break
if not changed:break
assert np.array_equal(old.vertices,m.vertices) and abs(old.area-m.area)<1e-8 and abs(old.volume-m.volume)<1e-8
print(name,'closed',m.is_watertight,'splits',splits,'boundary',int(np.sum(np.bincount(m.edges_unique_inverse)==1)),flush=True)
assert m.is_watertight and m.is_winding_consistent
m.export(C/(name+'_closed.ply'))
parts=coacd.run_coacd(coacd.Mesh(m.vertices,m.faces),threshold=.03,max_convex_hull=96,preprocess_mode='off',resolution=1500,mcts_nodes=10,mcts_iterations=60,mcts_max_depth=3,merge=True,seed=0)
for i,(v,f) in enumerate(parts):trimesh.Trimesh(v,f,process=False).export(C/f'{name}_{i:03d}.obj')
report[name]={'parts':len(parts),'watertight':bool(m.is_watertight),'t_junction_splits':splits,'surface_area_change':float(m.area-old.area),'volume_change':float(m.volume-old.volume),'cad_volume':float(m.volume),'convex_sum_volume':float(sum(trimesh.Trimesh(v,f,process=False).volume for v,f in parts)),'normalized_concavity_threshold':.03,'part_cap':96}
(C/'manifest.json').write_text(json.dumps(report,indent=2));print(name,report[name],flush=True)