feat(pose_3d): readout XYZ/RPY editabili (input number, drive il ghost)

This commit is contained in:
Adriano Dal Pastro committed 2026-05-21 17:01:03 +02:00
1 parent 8b722ca729
commit b852976a4a
3 files changed
+101 -14

No files matched your search

+6 -6
View File
@@ -500,12 +500,12 @@
<div class="p3-section">
<h3>Posa target</h3>
<div class="p3-readout">
<div>X <span id="p3X">—</span> mm</div>
<div>Y <span id="p3Y">—</span> mm</div>
<div>Z <span id="p3Z">—</span> mm</div>
<div>R <span id="p3R">—</span>°</div>
<div>P <span id="p3P">—</span>°</div>
<div>W <span id="p3W">—</span>°</div>
<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 class="p3-status" id="p3Status">— polso fermo</div>
<div class="p3-row" id="p3EditorActions">
+19 -1
View File
@@ -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; }
+76 -7
View File
@@ -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);
}