#!/usr/bin/env python3 """V10 constrained zero refinement, with exact active-pinch pose anchors. The photographs suggest straight, nearly parallel fingers; zero-degree targets are a geometric prior, not calibrated image measurements. Sweep bounded changes and reject any contact, endpoint or measured-pose regression beyond stated gates. """ import argparse,json,os os.environ.setdefault('MUJOCO_GL','egl') from pathlib import Path import xml.etree.ElementTree as ET import numpy as np from scipy.spatial.transform import Rotation import fit_middle as v9 base=v9.base OUTPUT=base.MODEL_DIR/'linkerhand_o30_right_zero_v10_calibrated.urdf' PARAMETERS=base.MODEL_DIR/'o30_right_zero_v10_calibration.json' REPORT=base.ROOT/'outputs/reports/O30/o30_right_zero_v10_calibration' ZERO=np.array([0,0,255,197,110,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0],dtype=float) SELECTED={2:0,3:1,4:2,5:3,7:0,11:0} GROUPS=[[0,1,6,15],[2,7,11,16],[3,8,12,17],[4,9,13,18],[5,10,14,19]] PROTECTED_ENDPOINTS=[8,9,10,12,14,15,17,18,19] def setup(): fit,previous=v9.setup();_,old=v9.load(previous) return fit,old def anchored_candidate(c,old,fraction): q=old.copy();oldzero=c.positions(old,ZERO) for i,k in SELECTED.items(): u=c.commands[k][i]/255;z=ZERO[i]/255 anchor=c.positions(old,c.commands[k])[i];target=oldzero[i]*(1-fraction) span=(anchor-target)/(u-z) q[i]=target-z*span-c.lo[i];q[20+i]=span/c.span[i]-1 return q def score(fit,old,q): c=fit.cal;contacts=fit.contacts(q) for row in contacts: assert abs(row['mesh_gap_mm'])<.2 and row['penetration_mm']<.2 for k,cmd in enumerate(c.commands): indices=GROUPS[0]+GROUPS[k+1] assert np.max(abs(c.positions(q,cmd)[indices]-c.positions(old,cmd)[indices]))<1e-12 ep=np.full(20,255.) assert np.array_equal(c.positions(q,ep)[PROTECTED_ENDPOINTS],c.positions(old,ep)[PROTECTED_ENDPOINTS]) c.set_pose(q,ZERO);penetration=max([0.]+[-ct.dist*1000 for ct in c.data.contact]);assert penetration<.2 measured=[] for cmd,finger in zip(v9.v7.review.COMMANDS,['middle','thumb']): points=[] for parameters in [old,q]: c.set_pose(parameters,cmd) ids=[b for b in range(c.model.nbody) if c.model.body(b).name.startswith(finger+'_')] points.append(c.data.xpos[ids].copy()) change=float(np.max(np.linalg.norm(points[1]-points[0],axis=1))*1000) assert change<.5 measured.append(dict(finger=finger,max_body_origin_change_mm=change)) angles=np.rad2deg(c.positions(q,ZERO)) return dict(contacts=contacts,zero_penetration_mm=penetration, zero_selected_rms_deg=float(np.sqrt(np.mean(angles[list(SELECTED)]**2))), zero_angles_deg=dict(zip(base.NAMES,angles.tolist())),measured_single_joint_pose_changes=measured, protected_endpoints_deg={base.NAMES[i]:float(np.rad2deg(c.positions(q,ep)[i])) for i in PROTECTED_ENDPOINTS}) def fit_zero(fit,old): history=[];best=None for fraction in np.linspace(0,1,21): q=anchored_candidate(fit.cal,old,fraction) try: metrics=score(fit,old,q) history.append(dict(fraction=float(fraction),accepted=True,rms_deg=metrics['zero_selected_rms_deg'])) if best is None or metrics['zero_selected_rms_deg']