# Design — Tab "Pose 3D" (editor visuale + jog dei polsi) **Data:** 2026-05-21 **Stato:** approvato per implementazione ## Obiettivo Aggiungere una tab "3D" alla GUI di `presa-mano` che permetta di: 1. Visualizzare la posa corrente dei polsi destro e sinistro del robot attivo in un viewport 3D. 2. Trascinare in 3D (gizmo translate+rotate) un polso alla volta per definire una nuova posa. 3. Validare la posa via il bridge G1 e (se in modalità Jog) muovere il robot reale. 4. Salvare/caricare pose con nome per riuso. Sostituisce il workflow attuale che richiede di editare manualmente campi numerici X/Y/Z + RPY nella tab Braccio DX, e fornisce una rappresentazione visuale per le 3 UCS di riferimento (base, polso DX, polso SX) finora invisibili. ## Modalità di lavoro Toggle in header tab, due valori mutuamente esclusivi: - **Editor**: il drag aggiorna SOLO la posa ghost locale (preview). Il bottone "Applica" abilita un commit *simulato* (il ghost diventa "current" lato client, robot non si muove). Utile per pianificare/visualizzare senza rischio. - **Jog**: il drag aggiorna il ghost; al rilascio del mouse (`mouseup`) parte automaticamente un comando reale `wrist_absolute` con `dry_run=false`. Nessun bottone Apply. 1 drag = 1 move. Safety guard in modalità Jog: prima dell'auto-apply, calcolo `||ghost_target − current_wrist||`. Se supera `pose_3d.live_max_step_m` (default 0.15 m), mostro un dialog di conferma "Movimento richiesto X mm. Procedi?" prima di inviare al robot. **Flag `precise`**: tutti i comandi reali (Editor Apply non incluso — è sim locale) hanno per default `precise=true` (controllo accurato del bridge). Il pannello espone un checkbox **"Precise"** abilitato di default; l'utente può disattivarlo se sa che il movimento è approssimativo / fuori workspace nominale (evita `arms: error_timeout` su movimenti grandi). Il toggle è di sessione, non persistito. ## Architettura ``` ┌─────────────────────────────────────────────────────────────────┐ │ BROWSER │ │ web/pose_3d.html (markup) + web/pose_3d.js (Three.js + UI) │ │ web/pose_3d.css │ │ - Scene Three.js: torso, 2 link, 2 polso-sphere, 3 UCS │ │ - TransformControls: gizmo translate+rotate sul polso attivo │ │ - Sidebar: side selector, mode toggle, gizmo toggle, readout, │ │ bottoni Apply (Editor)/Sblocca, save/load pose │ │ - Polling GET /api/pose_3d/state ogni 1s per sync con robot │ └──────────────────────────┬──────────────────────────────────────┘ │ HTTP /api/pose_3d/* ▼ ┌─────────────────────────────────────────────────────────────────┐ │ ORCHESTRATOR (FastAPI, presa-mano) │ │ src/presa_mano/api/pose_3d_routes.py │ │ - GET /state -> {left_wrist, right_wrist, poses}│ │ - POST /apply -> wrist_absolute(side, pos, quat) │ │ - GET /poses -> dict {nome: NamedPose} │ │ - PUT /poses/{name} -> salva/aggiorna posa │ │ - DELETE /poses/{name} -> rimuove │ │ - POST /poses/{name}/go -> esegue posa salvata │ │ - POST /unlock -> proxy POST :8765/unlock │ │ config.yaml > pose_3d.{named_poses, live_max_step_m} │ └──────────────────────────┬──────────────────────────────────────┘ │ chiama _deps.g1() (robot attivo h1|g1) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ G1_API_Server (port 8765, esistente, NON modificato) │ │ - GET /wrist -> {left_wrist, right_wrist} │ │ - POST /wrist/absolute -> IK + motion │ │ - POST /unlock -> sblocco arms (recovery error_timeout)│ └─────────────────────────────────────────────────────────────────┘ ``` Dipendenze frontend (CDN, no build step): - `three@0.160` (~600 KB minified) - `three/examples/jsm/controls/OrbitControls` - `three/examples/jsm/controls/TransformControls` Caricate via `