Files
Presa-Mano/web/index.html
T
AdrianoandClaude Opus 4.8 cf726b479e feat: badge versione build in GUI + build.sh con auto-bump
- VERSION (1.0.0) + build.sh: bump patch/minor/major e deploy via docker compose
- Dockerfile: ARG/ENV APP_VERSION + BUILD_TIME (iniettati come build-arg)
- version.build_info(): env in Docker, fallback file VERSION + '(local)' in dev
- GET /api/version; badge #appVersionPill nell'header (tooltip = build time)
- test_version.py: env vs fallback

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:40:52 +02:00

613 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>presa-mano</title>
<link rel="stylesheet" href="/static/style.css" />
<link rel="stylesheet" href="/static/pose_3d.css">
</head>
<body>
<header>
<h1>presa-mano</h1>
<nav class="tabs">
<button class="tab active" data-tab="home">Home</button>
<button class="tab" data-tab="workplane">Calibrazione Piano</button>
<button class="tab" data-tab="grasp">Cerca modello</button>
<button class="tab" data-tab="right-arm">Braccio DX</button>
<button class="tab" data-tab="pose-3d">Pose 3D</button>
<button class="tab" data-tab="ee">Movimento EE</button>
</nav>
<div class="status-strip" id="statusStrip"></div>
<span class="active-robot-pill" id="activeRobotPill" title="Robot attualmente collegato">robot: —</span>
<span class="active-robot-pill" id="appVersionPill" title="versione build">V—</span>
</header>
<main>
<!-- ============================== HOME ============================== -->
<section class="panel active" id="tab-home">
<div class="posture-toolbar">
<label class="inline">Robot attivo
<select id="homeRobotSelect">
<option value="g1">G1</option>
<option value="h1">H1</option>
</select>
</label>
<button id="homeApplyRobotBtn" class="primary">Applica</button>
<label class="inline"><input type="checkbox" id="homeAutoPoll" checked /> Auto-poll</label>
<label class="inline">ogni
<input type="number" id="homePollMs" value="2000" min="500" step="500" style="width:80px"> ms
</label>
<button id="homeRefreshBtn">Aggiorna ora</button>
</div>
<div class="info-box">
Stato di comunicazione con i microservizi upstream. Il selettore sopra cambia il robot attivo (G1 o H1) e aggiorna <code>config.yaml</code> a runtime; le chiamate <code>/api/wrist</code>, <code>/api/posture</code>, <code>/api/robot</code> useranno il robot selezionato.
</div>
<h2>Stato dispositivi</h2>
<table class="device-status-table" id="homeDeviceTable">
<thead>
<tr>
<th>Servizio</th>
<th>URL</th>
<th>Stato</th>
<th>Latenza</th>
<th>Dettagli</th>
</tr>
</thead>
<tbody>
<tr><td colspan="5" class="dim">caricamento…</td></tr>
</tbody>
</table>
<h3>Ultimo esito</h3>
<pre id="homeLog" class="log small">—</pre>
</section>
<!-- ============================== CALIBRAZIONE PIANO ============================== -->
<section class="panel" id="tab-workplane">
<div class="diag3d-toolbar">
<span class="post-health" id="wpStatusChip">—</span>
</div>
<div class="grid-2 diag3d-layout">
<div>
<div id="wpViewport" style="position:relative;width:min(100%,720px);height:440px;overflow:hidden;background:#000;border:1px solid var(--border);border-radius:4px;cursor:grab;user-select:none">
<div id="wpPanZoom" style="position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform">
<img id="wpImg" alt="Nuwa RGB" style="display:block;max-width:none;pointer-events:none" draggable="false" />
<canvas id="wpOverlay" style="position:absolute;left:0;top:0;pointer-events:none"></canvas>
</div>
</div>
<div class="marker-info">RGB live · 2 click = angoli del rettangolo ROI · 3° click ricomincia</div>
<h3 style="margin-top:14px">Vista 3D (frame camera)</h3>
<div class="canvas-wrap" style="width:min(100%,720px)">
<canvas id="wp3dCanvas" width="720" height="360" style="display:block;background:#0a0d11;border:1px solid var(--border);border-radius:4px"></canvas>
</div>
<div class="marker-info">vista camera · blu = punti usati per il fit · ciano = scena tenuta · rosso = scena esclusa (sotto piano−offset) · drag = ruota · wheel = zoom</div>
</div>
<div>
<h2>Calibrazione</h2>
<div class="ctrl-block">
<div class="info-box">
Clicca due angoli opposti sul tavolo (senza oggetti) per definire il
rettangolo ROI, poi clicca <strong>Calcola piano</strong>. Regola
l'offset finché i punti gialli rappresentano l'oggetto e i rossi
spariscono. Poi <strong>Salva piano</strong>.
</div>
<div class="kv compact">
<label>ROI <span id="wpRoiInfo" class="dim">— · clicca 2 angoli sull'RGB</span></label>
<label>offset (mm) <input type="number" id="wpOffset" value="5.0" step="0.5" min="-200" max="200" /></label>
</div>
<div class="actions">
<button id="wpFitBtn" class="primary" disabled>Calcola piano (preview)</button>
<button id="wpSaveBtn" disabled>Salva piano</button>
<button id="wpClearBtn" class="danger" disabled>Rimuovi piano salvato</button>
</div>
</div>
<h2>Stato</h2>
<div id="wpStateBlock" class="ctrl-block">
<div class="dim">nessun piano</div>
</div>
<h2>Dettagli piano</h2>
<pre id="wpDetails" class="log small">—</pre>
</div>
</div>
</section>
<!-- ============================== TROVA UTENSILE ============================== -->
<!-- ============================== CERCA MODELLO ============================== -->
<section class="panel" id="tab-grasp">
<div class="grid-2">
<div>
<h2>RGB + detection — click per misurare</h2>
<div class="canvas-wrap">
<canvas id="graspCanvas" width="640" height="480"></canvas>
</div>
<div class="measure-strip" id="measureInfo">click sull'immagine per la distanza</div>
<div class="kv">
<label>Nuvola di <select id="graspObjectPick"><option value="">(top overall)</option></select></label>
<label>Confidenza <input type="number" id="graspConf" value="0.5" step="0.05" min="0" max="1" /></label>
</div>
<div id="graspLegend" class="grasp-legend"></div>
<div class="actions">
<button id="detectBtn">Rileva oggetto</button>
<button id="loopBtn">▶ Loop riconoscimento</button>
<label class="inline" style="margin-left:8px"><input type="number" id="loopRateMs" value="500" min="100" step="50" style="width:80px"> ms</label>
</div>
</div>
<div>
<h2>Fit modello 3D</h2>
<div class="kv">
<label>Modello
<select id="findModelPick"><option value="">— carico ricette… —</option></select>
</label>
<button id="findRecipesRefreshBtn" class="ghost" title="Ricarica ricette">↻</button>
</div>
<div class="actions">
<button id="findBtn" class="primary" disabled>Trova</button>
</div>
<div id="findStatus" class="status-line">—</div>
<div class="canvas-wrap">
<canvas id="findResultCanvas" width="640" height="480"></canvas>
</div>
<div class="kv">
<label class="inline"><input type="checkbox" id="findShowModel" checked> Punti modello (blu)</label>
<label class="inline"><input type="checkbox" id="findShowAccepted"> Punti accettati (verdi)</label>
<label class="inline"><input type="checkbox" id="findShowExcluded"> Punti esclusi (rossi)</label>
<label class="inline"><input type="checkbox" id="findShowSam21"> Punti SAM21 (gialli)</label>
<label class="inline"><input type="checkbox" id="findShowPlane"> Piano (viola)</label>
</div>
<pre id="findDetails" class="log small">—</pre>
<!-- DOM hooks dormienti consumati da app.js residuo. Tenuti per evitare null deref. -->
<div style="display:none">
<span id="statsWindow">5</span>
<table id="statsTable"><tbody>
<tr><td>x</td><td>—</td><td>—</td><td>—</td><td>—</td><td>—</td></tr>
<tr><td>y</td><td>—</td><td>—</td><td>—</td><td>—</td><td>—</td></tr>
<tr><td>z</td><td>—</td><td>—</td><td>—</td><td>—</td><td>—</td></tr>
</tbody></table>
<div id="statsMeta"></div>
<pre id="graspOut"></pre>
</div>
</div>
</div>
</section>
<!-- ============================== BRACCIO DX ============================== -->
<section class="panel" id="tab-right-arm">
<div class="ra-grid">
<!-- FRONTE -->
<div class="ra-card">
<h3>Fronte</h3>
<div class="ra-imgwrap">
<img id="raFrontImg" alt="front camera" />
<canvas id="raFrontCanvas"></canvas>
</div>
<div class="ra-row">
<label>Classe <select id="raFrontClass"></select></label>
<label>Conf <input id="raFrontConf" type="number" step="0.05" min="0" max="1" value="0.4" /></label>
</div>
<div class="ra-row">
<button id="raFrontDetectBtn" type="button">Cerca su fronte</button>
<label><input id="raFrontAutoRefresh" type="checkbox" checked /> auto-refresh 1s</label>
</div>
<div id="raFrontResult" class="ra-result dim">— nessuna detection</div>
</div>
<!-- POLSO -->
<div class="ra-card">
<h3>Polso (Nuwa)</h3>
<div class="ra-imgwrap">
<img id="raWristImg" alt="wrist camera" />
<canvas id="raWristCanvas"></canvas>
</div>
<div class="ra-row">
<label>Classe <select id="raWristClass"></select></label>
<label>Conf <input id="raWristConf" type="number" step="0.05" min="0" max="1" value="0.4" /></label>
</div>
<div class="ra-row">
<button id="raWristDetectBtn" type="button">Cerca su polso</button>
<label><input id="raWristAutoRefresh" type="checkbox" checked /> auto-refresh 1s</label>
</div>
<div id="raWristResult" class="ra-result dim">— nessuna detection</div>
</div>
<!-- POSA POLSO -->
<div class="ra-card">
<h3>Posa polso DX</h3>
<table class="ra-pose-table" id="raPoseTable">
<tr><th>X</th><td id="raPoseX">—</td><td>m</td></tr>
<tr><th>Y</th><td id="raPoseY">—</td><td>m</td></tr>
<tr><th>Z</th><td id="raPoseZ">—</td><td>m</td></tr>
<tr><th>Roll</th><td id="raPoseR">—</td><td>°</td></tr>
<tr><th>Pitch</th><td id="raPoseP">—</td><td>°</td></tr>
<tr><th>Yaw</th><td id="raPoseW">—</td><td>°</td></tr>
</table>
<button id="raPoseRefreshBtn" type="button">Refresh</button>
</div>
</div>
<!-- OFFSET + MOVE -->
<div class="ra-card ra-wide">
<h3>Offset presa rispetto a camera (mm)</h3>
<div class="ra-row">
<label>X <input id="raOffX" type="number" step="1" value="0" /></label>
<label>Y <input id="raOffY" type="number" step="1" value="0" /></label>
<label>Z <input id="raOffZ" type="number" step="1" value="100" /></label>
<span class="ra-sep">|</span>
<label>Sign X <input id="raSgnX" type="number" step="2" min="-1" max="1" value="1" /></label>
<label>Sign Y <input id="raSgnY" type="number" step="2" min="-1" max="1" value="-1" /></label>
<label>Sign Z <input id="raSgnZ" type="number" step="2" min="-1" max="1" value="-1" /></label>
</div>
<div class="ra-row" title="Traslazione costante dall'origine polso all'origine camera, nel frame polso">
<span class="ra-sep">Camera rispetto a polso (mm):</span>
<label>X <input id="raCamOffX" type="number" step="1" value="0" /></label>
<label>Y <input id="raCamOffY" type="number" step="1" value="0" /></label>
<label>Z <input id="raCamOffZ" type="number" step="1" value="0" /></label>
</div>
<div class="ra-row">
<button id="raSaveOffsetBtn" type="button">Salva offset</button>
<label><input id="raMoveDryRun" type="checkbox" checked /> dry-run</label>
<label title="Bypassa il limite max_step_m"><input id="raMoveForce" type="checkbox" /> force</label>
<button id="raMoveBtn" type="button" class="ra-danger">Muovi verso pezzo</button>
</div>
<div id="raMoveResult" class="ra-result dim">— nessun move</div>
</div>
<!-- VERIFICA TAG (relativa) -->
<div class="ra-card ra-wide">
<h3>Verifica tag id=1 (relativa, no calibrazione)</h3>
<p class="ra-help">
Premi <em>Snapshot</em> con il polso fermo, muovi il polso, premi ancora.
Per ogni coppia consecutiva confronto ‖Δpolso‖ (FK) con ‖Δtag‖ (front camera).
Se combaciano (errore &lt; ~10%) FK e detection sono coerenti.
</p>
<div class="ra-row">
<button id="raVerifySnapBtn" type="button">Snapshot</button>
<button id="raVerifyResetBtn" type="button">Reset</button>
<span id="raVerifyCount" class="dim">0 snapshot</span>
</div>
<div id="raVerifyResult" class="ra-result dim">— nessuno snapshot</div>
</div>
<!-- HOME + CUSTOM -->
<div class="ra-row ra-wide-grid">
<div class="ra-card">
<h3>Posa Home (preset)</h3>
<div class="ra-pose-form">
<label>X <input id="raHomeX" type="number" step="0.01" /></label>
<label>Y <input id="raHomeY" type="number" step="0.01" /></label>
<label>Z <input id="raHomeZ" type="number" step="0.01" /></label>
<label>Roll <input id="raHomeR" type="number" step="1" /></label>
<label>Pitch <input id="raHomeP" type="number" step="1" /></label>
<label>Yaw <input id="raHomeW" type="number" step="1" /></label>
</div>
<button id="raHomeBtn" type="button">Vai a Home</button>
</div>
<div class="ra-card">
<h3>Posa Custom (editabile)</h3>
<div class="ra-pose-form">
<label>X <input id="raCustX" type="number" step="0.01" /></label>
<label>Y <input id="raCustY" type="number" step="0.01" /></label>
<label>Z <input id="raCustZ" type="number" step="0.01" /></label>
<label>Roll <input id="raCustR" type="number" step="1" /></label>
<label>Pitch <input id="raCustP" type="number" step="1" /></label>
<label>Yaw <input id="raCustW" type="number" step="1" /></label>
</div>
<div class="ra-row">
<button id="raCustomGoBtn" type="button">Vai a Custom</button>
<button id="raCustomSaveBtn" type="button">Salva custom</button>
<button id="raCustomUseCurrentBtn" type="button">Usa posa corrente</button>
</div>
</div>
</div>
</section>
<!-- ============================== POSE 3D ============================== -->
<section class="panel" id="tab-pose-3d">
<div class="p3-layout">
<div class="p3-viewport">
<canvas id="p3Canvas"></canvas>
<div class="p3-mode-badge" id="p3ModeBadge">Editor</div>
<div class="p3-help">click polso = seleziona · gizmo: T sposta / R ruota · drag = muovi · orbit = wheel/drag fondo · pan = shift+drag</div>
</div>
<aside class="p3-sidebar">
<div class="p3-group" data-group="pose">
<div class="p3-section">
<h3>Modalità</h3>
<div class="p3-row">
<label><input type="radio" name="p3Mode" value="editor" checked> Editor (simulato)</label>
<label><input type="radio" name="p3Mode" value="jog"> Jog (move reale)</label>
</div>
<label class="p3-check"><input type="checkbox" id="p3Precise" checked> Precise (controllo accurato)</label>
</div>
<div class="p3-section">
<h3>Polso attivo</h3>
<div class="p3-row">
<label><input type="radio" name="p3Side" value="right" checked> Destro</label>
<label><input type="radio" name="p3Side" value="left"> Sinistro</label>
</div>
<div class="p3-row">
<label><input type="radio" name="p3Gizmo" value="translate" checked> T sposta</label>
<label><input type="radio" name="p3Gizmo" value="rotate"> R ruota</label>
</div>
</div>
<div class="p3-section">
<h3>Posa target</h3>
<div class="p3-readout">
<label>X <input id="p3X" type="number" step="1" /> mm</label>
<label>Y <input id="p3Y" type="number" step="1" /> mm</label>
<label>Z <input id="p3Z" type="number" step="1" /> mm</label>
<label>R <input id="p3R" type="number" step="1" /> °</label>
<label>P <input id="p3P" type="number" step="1" /> °</label>
<label>W <input id="p3W" type="number" step="1" /> °</label>
</div>
<div id="p3Feasibility" class="p3-feasibility"></div>
<div class="p3-status" id="p3Status">— polso fermo</div>
<div class="p3-row" id="p3EditorActions">
<button id="p3ApplyBtn">Applica (Editor)</button>
<button id="p3CancelBtn">Annulla modifica</button>
</div>
<div class="p3-row">
<button id="p3SyncBtn" title="Ricopia la posa attuale del robot negli input">⟳ Sincronizza con robot</button>
</div>
</div>
<div class="p3-section">
<h3>Pose salvate — DX</h3>
<div class="p3-row">
<select id="p3PoseSelectR"><option value="">(seleziona)</option></select>
</div>
<div class="p3-row">
<button id="p3PoseGoBtnR">Vai</button>
<input type="text" id="p3PoseNameR" placeholder="nome" maxlength="40">
<button id="p3PoseSaveBtnR">Salva</button>
<button id="p3PoseDelBtnR">✕</button>
</div>
</div>
<div class="p3-section">
<h3>Pose salvate — SX</h3>
<div class="p3-row">
<select id="p3PoseSelectL"><option value="">(seleziona)</option></select>
</div>
<div class="p3-row">
<button id="p3PoseGoBtnL">Vai</button>
<input type="text" id="p3PoseNameL" placeholder="nome" maxlength="40">
<button id="p3PoseSaveBtnL">Salva</button>
<button id="p3PoseDelBtnL">✕</button>
</div>
</div>
</div><!-- /p3-group pose -->
<div class="p3-group" data-group="cameras" hidden>
<div class="p3-section">
<h3>Camera Nuwa (polso DX)</h3>
<div class="p3-row">
<label><input type="checkbox" id="p3NuwaEnabled"> Abilitata</label>
</div>
<div class="p3-readout">
<label>dX <input id="p3NuwaX" type="number" step="1" /> mm</label>
<label>dY <input id="p3NuwaY" type="number" step="1" /> mm</label>
<label>dZ <input id="p3NuwaZ" type="number" step="1" /> mm</label>
<label>R <input id="p3NuwaR" type="number" step="1" /> °</label>
<label>P <input id="p3NuwaP" type="number" step="1" /> °</label>
<label>W <input id="p3NuwaW" type="number" step="1" /> °</label>
</div>
<div class="p3-row">
<button id="p3NuwaApplyBtn">Applica</button>
<button id="p3NuwaResetBtn">Default</button>
</div>
</div>
<div class="p3-section">
<h3>Camera D435i (offset URDF)</h3>
<div class="p3-readout">
<label>dX <input id="p3D435X" type="number" step="1" /> mm</label>
<label>dY <input id="p3D435Y" type="number" step="1" /> mm</label>
<label>dZ <input id="p3D435Z" type="number" step="1" /> mm</label>
<label>R <input id="p3D435R" type="number" step="1" /> °</label>
<label>P <input id="p3D435P" type="number" step="1" /> °</label>
<label>W <input id="p3D435W" type="number" step="1" /> °</label>
</div>
<div class="p3-row">
<button id="p3D435ApplyBtn">Applica</button>
<button id="p3D435ResetBtn">Default</button>
</div>
</div>
<div class="p3-section">
<h3>Cloud cross-camera</h3>
<div class="p3-row">
<label><input type="checkbox" id="p3ShowNuwaCloud"> Nuwa</label>
<label><input type="checkbox" id="p3FlipNuwaX">↔X</label>
<label><input type="checkbox" id="p3FlipNuwaY">↔Y</label>
<label><input type="checkbox" id="p3FlipNuwaZ">↔Z</label>
</div>
<div class="p3-row">
<label><input type="checkbox" id="p3ShowD435Cloud"> D435i</label>
<label><input type="checkbox" id="p3FlipD435X">↔X</label>
<label><input type="checkbox" id="p3FlipD435Y">↔Y</label>
<label><input type="checkbox" id="p3FlipD435Z">↔Z</label>
</div>
<div class="p3-row">
<label><input type="checkbox" id="p3D435UseHandeye"> D435i: usa hand-eye (vs URDF)</label>
</div>
<div class="p3-row" title="Workaround per bug intrinsics-mismatch del servizio D435i (fx_ir/fx_rgb ≈ 0.65). Scala solo X e Y del cloud.">
<label>D435i scale XY
<input id="p3D435ScaleXY" type="number" min="0.1" max="3" step="0.01" value="0.65" style="width:60px">
</label>
</div>
<div class="p3-row" title="Calibrazione persistente Nuwa→frontale via tag condiviso (Umeyama). Premi Allinea con il tag visto da entrambe le camere.">
<label><input type="checkbox" id="p3AlignEnabled"> Allinea Nuwa→frontale</label>
<label>tag id <input id="p3AlignTagId" type="number" min="0" max="999" value="0" style="width:55px"></label>
<button id="p3AlignBtn">Allinea</button>
</div>
<div class="marker-info" id="p3AlignStatus">—</div>
<div class="p3-row">
<label>step <input id="p3CloudStep" type="number" min="1" max="32" value="8" style="width:50px"></label>
<label>max <input id="p3CloudMaxDepth" type="number" min="100" max="5000" step="100" value="2000" style="width:70px"> mm</label>
</div>
<div class="p3-row">
<label>size Nuwa <input id="p3NuwaCloudSize" type="number" min="0.5" max="100" step="0.5" value="5" style="width:55px"> mm</label>
<label>size D435i <input id="p3D435CloudSize" type="number" min="0.5" max="100" step="0.5" value="5" style="width:55px"> mm</label>
</div>
<div class="p3-row">
<button id="p3CloudAcquireBtn">Acquisisci</button>
<button id="p3CloudClearBtn">✕</button>
</div>
<div class="marker-info" id="p3CloudStatus">—</div>
</div>
</div><!-- /p3-group cameras -->
<div class="p3-group" data-group="misc" hidden>
<div class="p3-section">
<h3>Tag locator</h3>
<div class="p3-row">
<label><input type="checkbox" id="p3TagPlaceMode"> <b>Modalità tag click</b> (click sul modello = piazza crocetta)</label>
</div>
<div class="p3-row">
<label>Tag
<select id="p3TagSelect">
<option value="10:40:wrist_sx">wrist sx (id=10, 40mm)</option>
<option value="1:20:nuwa_dx">nuwa dx (id=1, 20mm)</option>
</select>
</label>
</div>
<div class="p3-row">
<label>x <input id="p3TagPosX" type="number" readonly style="width:70px"> mm</label>
<label>y <input id="p3TagPosY" type="number" readonly style="width:70px"> mm</label>
<label>z <input id="p3TagPosZ" type="number" readonly style="width:70px"> mm</label>
</div>
<div class="p3-row">
<button id="p3TagSaveBtn" disabled>Salva</button>
<button id="p3TagClearBtn" disabled>✕ rimuovi</button>
</div>
<div class="marker-info" id="p3TagStatus">attiva la modalità tag click sopra, poi clicca sul modello 3D</div>
</div>
<div class="p3-section">
<h3>Debug</h3>
<div class="p3-row">
<label><input type="checkbox" id="p3ShowJointUCS"> Mostra UCS giunti</label>
</div>
<div class="p3-row">
<label>Scala
<input id="p3JointUCSSize" type="number" min="10" max="200" step="5" value="40" style="width:60px"> mm
</label>
</div>
<div class="p3-row">
<label><input type="checkbox" id="p3ShowD435CalibUCS" checked> UCS D435i hand-eye (arancio)</label>
</div>
<div class="p3-row">
<label><input type="checkbox" id="p3ShowRobotBody" checked> Mostra robot (mesh)</label>
</div>
</div>
<div class="p3-section">
<h3>Tags</h3>
<div class="p3-row">
<label><input type="checkbox" id="p3ShowFrontTags"> Mostra tag visibili (quad + dim + dist)</label>
</div>
<div class="p3-row">
<label>IDs <input id="p3FrontTagIds" type="text" value="0-49" style="width:90px"></label>
</div>
<div class="marker-info" id="p3FrontTagStatus">—</div>
</div>
<div class="p3-section">
<button id="p3UnlockBtn">Sblocca braccia</button>
</div>
<div class="p3-section" id="p3ErrorBox" style="display:none">
<div class="p3-error" id="p3ErrorText"></div>
</div>
</div><!-- /p3-group misc -->
</aside>
<nav class="p3-tabrail" aria-label="Pose 3D groups">
<button type="button" class="p3-tab active" data-target="pose">POSE</button>
<button type="button" class="p3-tab" data-target="cameras">CAMERAS</button>
<button type="button" class="p3-tab" data-target="misc">MISC</button>
</nav>
</div>
</section>
<!-- ============================== EE ============================== -->
<section class="panel" id="tab-ee">
<div class="grid-2">
<div>
<h2>Polso corrente</h2>
<pre id="wristCurrent" class="log">—</pre>
<button id="refreshWristBtn">Aggiorna pose</button>
<h3>Posa assoluta</h3>
<div class="kv">
<label>Side <select id="eeSide"><option value="right">right</option><option value="left">left</option></select></label>
<label>Px <input type="number" id="absPx" step="0.01" value="0.30"></label>
<label>Py <input type="number" id="absPy" step="0.01" value="0.10"></label>
<label>Pz <input type="number" id="absPz" step="0.01" value="0.20"></label>
<label>Qx <input type="number" id="absQx" step="0.01" value="0"></label>
<label>Qy <input type="number" id="absQy" step="0.01" value="0"></label>
<label>Qz <input type="number" id="absQz" step="0.01" value="0"></label>
<label>Qw <input type="number" id="absQw" step="0.01" value="1"></label>
<label class="inline"><input type="checkbox" id="eeDryRun" /> Dry-run</label>
</div>
<button id="absSendBtn" class="primary">Invia assoluta</button>
<h3>Delta relativo</h3>
<div class="kv">
<label>ΔX <input type="number" id="relDx" step="0.01" value="0"></label>
<label>ΔY <input type="number" id="relDy" step="0.01" value="0"></label>
<label>ΔZ <input type="number" id="relDz" step="0.01" value="0.05"></label>
</div>
<button id="relSendBtn" class="primary">Invia relativa</button>
</div>
<div>
<h2>Mano + sicurezza</h2>
<div class="actions">
<button id="handOpenBtn">RIGHT_OPEN</button>
<button id="handCloseBtn">RIGHT_CLOSE</button>
<button id="handOpenLBtn">LEFT_OPEN</button>
<button id="handCloseLBtn">LEFT_CLOSE</button>
</div>
<div class="actions">
<button id="estopBtn" class="danger">EMERGENCY STOP</button>
<button id="unlockBtn">Unlock</button>
</div>
<h3>Ultimo esito</h3>
<pre id="eeLog" class="log">—</pre>
</div>
</div>
</section>
</main>
<div id="toastHost" class="toast-host" aria-live="polite"></div>
<script type="importmap">
{
"imports": {
"three": "/static/vendor/three/build/three.module.js",
"three/addons/": "/static/vendor/three/addons/"
}
}
</script>
<script src="/static/app.js"></script>
<script type="module" src="/static/work_plane.js"></script>
<script type="module" src="/static/right_arm.js"></script>
<script type="module" src="/static/pose_3d.js"></script>
</body>
</html>