From b852976a4a7543b304331bfeaea9a3dcd341d0f6 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Thu, 21 May 2026 17:01:03 +0200 Subject: [PATCH] feat(pose_3d): readout XYZ/RPY editabili (input number, drive il ghost) --- web/index.html | 12 +++---- web/pose_3d.css | 20 +++++++++++- web/pose_3d.js | 83 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 14 deletions(-) diff --git a/web/index.html b/web/index.html index 572a6f7..5fe1fe2 100644 --- a/web/index.html +++ b/web/index.html @@ -500,12 +500,12 @@

Posa target

-
X — mm
-
Y — mm
-
Z — mm
-
R —°
-
P —°
-
W —°
+ + + + + +
— polso fermo
diff --git a/web/pose_3d.css b/web/pose_3d.css index e88e22d..4fef9ef 100644 --- a/web/pose_3d.css +++ b/web/pose_3d.css @@ -80,7 +80,25 @@ color: #ddd; margin-bottom: 8px; } -#tab-pose-3d .p3-readout span { color: #4da3ff; } +#tab-pose-3d .p3-readout label { + display: flex; + align-items: center; + gap: 4px; +} +#tab-pose-3d .p3-readout input { + width: 60px; + background: #0d1014; + border: 1px solid #2a2f36; + color: #4da3ff; + padding: 2px 4px; + border-radius: 3px; + font-size: 12px; + font-family: monospace; +} +#tab-pose-3d .p3-readout input:focus { + outline: 1px solid #4da3ff; + border-color: #4da3ff; +} #tab-pose-3d .p3-status { font-size: 12px; color: #888; margin-bottom: 8px; } #tab-pose-3d .p3-status.modified { color: #f0ad4e; } diff --git a/web/pose_3d.js b/web/pose_3d.js index 90799e0..4304a49 100644 --- a/web/pose_3d.js +++ b/web/pose_3d.js @@ -251,6 +251,21 @@ function quatToRPYdeg(q) { return [roll * 180 / Math.PI, pitch * 180 / Math.PI, yaw * 180 / Math.PI]; } +function rpyDegToQuat(roll, pitch, yaw) { + const r = roll * Math.PI / 180; + const p = pitch * Math.PI / 180; + const y = yaw * Math.PI / 180; + const cr = Math.cos(r / 2), sr = Math.sin(r / 2); + const cp = Math.cos(p / 2), sp = Math.sin(p / 2); + const cy = Math.cos(y / 2), sy = Math.sin(y / 2); + // ZYX intrinsic (matches quatToRPYdeg) + const qx = sr * cp * cy - cr * sp * sy; + const qy = cr * sp * cy + sr * cp * sy; + const qz = cr * cp * sy - sr * sp * cy; + const qw = cr * cp * cy + sr * sp * sy; + return [qx, qy, qz, qw]; +} + // ----- state apply to scene ----- function updateRealWrists() { for (const side of ['right', 'left']) { @@ -294,21 +309,33 @@ function syncGhostsToTargetOrReal() { } } +function setInputIfNotFocused(id, value) { + const el = $(id); + if (!el) return; + if (document.activeElement === el) return; // user is typing, don't stomp + el.value = value; +} + function updateReadout() { const side = state.active_side; const pose = state.target[side] || state.current[side]; if (!pose) { - $('p3X').textContent = '—'; + setInputIfNotFocused('p3X', ''); + setInputIfNotFocused('p3Y', ''); + setInputIfNotFocused('p3Z', ''); + setInputIfNotFocused('p3R', ''); + setInputIfNotFocused('p3P', ''); + setInputIfNotFocused('p3W', ''); return; } const p = pose.position, q = pose.quaternion; - $('p3X').textContent = (p[0] * 1000).toFixed(1); - $('p3Y').textContent = (p[1] * 1000).toFixed(1); - $('p3Z').textContent = (p[2] * 1000).toFixed(1); + setInputIfNotFocused('p3X', (p[0] * 1000).toFixed(1)); + setInputIfNotFocused('p3Y', (p[1] * 1000).toFixed(1)); + setInputIfNotFocused('p3Z', (p[2] * 1000).toFixed(1)); const rpy = quatToRPYdeg(q); - $('p3R').textContent = rpy[0].toFixed(1); - $('p3P').textContent = rpy[1].toFixed(1); - $('p3W').textContent = rpy[2].toFixed(1); + setInputIfNotFocused('p3R', rpy[0].toFixed(1)); + setInputIfNotFocused('p3P', rpy[1].toFixed(1)); + setInputIfNotFocused('p3W', rpy[2].toFixed(1)); $('p3Status').textContent = state.target[side] ? '● modificata' : '— polso fermo'; $('p3Status').className = 'p3-status' + (state.target[side] ? ' modified' : ''); } @@ -447,6 +474,47 @@ function bindGizmoEvents() { }); } +function readReadoutInputs() { + const num = (id, dflt) => { + const v = parseFloat($(id).value); + return Number.isFinite(v) ? v : dflt; + }; + const side = state.active_side; + if (!side) return; + // fallback values: current pose if any, else SHOULDER position via arm + const fallback = state.current[side] || (state.arms[side] ? { + position: [state.arms[side].position.x, state.arms[side].position.y, state.arms[side].position.z], + quaternion: [0, 0, 0, 1], + } : { position: [0, 0, 0], quaternion: [0, 0, 0, 1] }); + const x_mm = num('p3X', fallback.position[0] * 1000); + const y_mm = num('p3Y', fallback.position[1] * 1000); + const z_mm = num('p3Z', fallback.position[2] * 1000); + const r_deg = num('p3R', 0); + const p_deg = num('p3P', 0); + const w_deg = num('p3W', 0); + const position = [x_mm / 1000, y_mm / 1000, z_mm / 1000]; + const quaternion = rpyDegToQuat(r_deg, p_deg, w_deg); + state.target[side] = { position, quaternion }; + // Aggiorna ghost in scena + const ghost = state.ghosts[side]; + if (ghost) { + ghost.position.set(position[0], position[1], position[2]); + ghost.quaternion.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]); + ghost.visible = true; + } + // Status + $('p3Status').textContent = '● modificata (da input)'; + $('p3Status').className = 'p3-status modified'; +} + +function bindReadoutInputs() { + for (const id of ['p3X', 'p3Y', 'p3Z', 'p3R', 'p3P', 'p3W']) { + const el = $(id); + if (!el) continue; + el.addEventListener('change', readReadoutInputs); // fire on blur / Enter + } +} + // ----- side change listener ----- function bindSideControls() { for (const r of document.querySelectorAll('input[name=p3Side]')) { @@ -690,6 +758,7 @@ function ensureInit() { bindJogAutoApply(); bindPoseButtons(); bindUnlockButton(); + bindReadoutInputs(); pollState(); pollTimer = setInterval(pollState, POLL_MS); }