feat(pose_3d): overlay tag su entrambe le camere (Nuwa rosso, D435i ciano)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AdrianoandClaude Sonnet 4.6 committed 2026-06-03 12:31:16 +02:00
1 parent 79f587567e
commit edc4430c67
1 file changed
+33 -40
+33 -40
View File
@@ -227,8 +227,9 @@ const state = {
position_m: null, // [x,y,z] in pelvis frame della crocetta corrente position_m: null, // [x,y,z] in pelvis frame della crocetta corrente
joints_at_place: null, // snapshot giunti al click (può essere null se G1 offline) joints_at_place: null, // snapshot giunti al click (può essere null se G1 offline)
}, },
front_tags: { // overlay tag visibili dalla camera D435i frontale tags: { // overlay tag per camera (gruppi THREE child del cloud node)
group: null, // THREE.Group child del d435 parent (cloud-like transform) nuwa: { group: null },
d435: { group: null },
}, },
}; };
@@ -1767,7 +1768,7 @@ async function acquireClouds() {
saveCloudParamsToConfig(); saveCloudParamsToConfig();
// Nuova immagine acquisita: se l'overlay tag e' attivo ri-esegui la // Nuova immagine acquisita: se l'overlay tag e' attivo ri-esegui la
// detection una volta sola, sul frame appena acquisito. // detection una volta sola, sul frame appena acquisito.
if ($('p3ShowFrontTags')?.checked) pollFrontTags(); if ($('p3ShowFrontTags')?.checked) pollTags();
} catch (e) { } catch (e) {
$('p3CloudStatus').textContent = `errore: ${e.message}`; $('p3CloudStatus').textContent = `errore: ${e.message}`;
$('p3CloudStatus').style.color = 'var(--danger)'; $('p3CloudStatus').style.color = 'var(--danger)';
@@ -1795,6 +1796,8 @@ function clearClouds() {
// camera in modalita' URDF link e in modalita' hand-eye. // camera in modalita' URDF link e in modalita' hand-eye.
const FRONT_TAG_COLOR = 0x4dd0e1; const FRONT_TAG_COLOR = 0x4dd0e1;
const NUWA_TAG_COLOR = 0xff3b30; // rosso: tag visti dalla camera Nuwa (polso)
const TAG_COLORS = { nuwa: NUWA_TAG_COLOR, d435: FRONT_TAG_COLOR };
const FRONT_TAG_LABEL_COLOR = '#4dd0e1'; const FRONT_TAG_LABEL_COLOR = '#4dd0e1';
// Label compatta auto-sized: canvas largo quanto il testo, font piccolo, // Label compatta auto-sized: canvas largo quanto il testo, font piccolo,
@@ -1854,8 +1857,8 @@ function applyCloudLikeTransform(obj, camKey) {
obj.scale.set(sv[0], sv[1], sv[2]); obj.scale.set(sv[0], sv[1], sv[2]);
} }
function clearFrontTagsGroup() { function clearTagsGroup(camKey) {
const grp = state.front_tags.group; const grp = state.tags[camKey].group;
if (!grp) return; if (!grp) return;
if (grp.parent) grp.parent.remove(grp); if (grp.parent) grp.parent.remove(grp);
grp.traverse(o => { grp.traverse(o => {
@@ -1865,64 +1868,54 @@ function clearFrontTagsGroup() {
else o.material.dispose(); else o.material.dispose();
} }
}); });
state.front_tags.group = null; state.tags[camKey].group = null;
} }
function addOneFrontTag(grp, t) { function addOneTag(grp, t, color) {
const c = t.corners_3d_optical_m; const c = t.corners_3d_optical_m;
if (!Array.isArray(c) || c.length !== 4) return; if (!Array.isArray(c) || c.length !== 4) return;
if (c.some(p => !p || p.some(v => !Number.isFinite(v)))) return; if (c.some(p => !p || p.some(v => !Number.isFinite(v)))) return;
// LineLoop (5 verts = close)
const v = new Float32Array(5 * 3); const v = new Float32Array(5 * 3);
for (let i = 0; i < 4; i++) { v[3*i]=c[i][0]; v[3*i+1]=c[i][1]; v[3*i+2]=c[i][2]; } for (let i = 0; i < 4; i++) { v[3*i]=c[i][0]; v[3*i+1]=c[i][1]; v[3*i+2]=c[i][2]; }
v[12] = c[0][0]; v[13] = c[0][1]; v[14] = c[0][2]; v[12] = c[0][0]; v[13] = c[0][1]; v[14] = c[0][2];
const geom = new THREE.BufferGeometry(); const geom = new THREE.BufferGeometry();
geom.setAttribute('position', new THREE.BufferAttribute(v, 3)); geom.setAttribute('position', new THREE.BufferAttribute(v, 3));
const mat = new THREE.LineBasicMaterial({ color: FRONT_TAG_COLOR }); grp.add(new THREE.Line(geom, new THREE.LineBasicMaterial({ color })));
grp.add(new THREE.Line(geom, mat));
// Label "id=X · NNmm · d=DDDmm" leggermente offset dal centro
const edge_s = Number.isFinite(t.edge_mm) ? `${t.edge_mm.toFixed(1)}mm` : '?'; const edge_s = Number.isFinite(t.edge_mm) ? `${t.edge_mm.toFixed(1)}mm` : '?';
const dist_s = Number.isFinite(t.distance_m) ? `${(t.distance_m * 1000).toFixed(0)}mm` : '?'; const dist_s = Number.isFinite(t.distance_m) ? `${(t.distance_m * 1000).toFixed(0)}mm` : '?';
const sprite = makeCompactLabel(`id=${t.id} · ${edge_s} · d=${dist_s}`); const sprite = makeCompactLabel(`id=${t.id} · ${edge_s} · d=${dist_s}`);
const ctr = t.center_3d_optical_m; const ctr = t.center_3d_optical_m;
if (ctr && ctr.every(Number.isFinite)) { if (ctr && ctr.every(Number.isFinite)) sprite.position.set(ctr[0], ctr[1] - 0.04, ctr[2]);
// offset in -Y ottico = sopra (Y down nella convenzione OpenCV)
sprite.position.set(ctr[0], ctr[1] - 0.04, ctr[2]);
}
grp.add(sprite); grp.add(sprite);
} }
function renderFrontTags(tags) { function renderTags(camKey, tags, color) {
clearFrontTagsGroup(); clearTagsGroup(camKey);
if (!tags || tags.length === 0) return; if (!tags || tags.length === 0) return;
const parent = getCloudParent('d435'); const parent = getCloudParent(camKey);
if (!parent) { if (!parent) { console.warn(`[pose_3d] tags: parent ${camKey} non disponibile`); return; }
console.warn('[pose_3d] front_tags: parent d435 non disponibile');
return;
}
const grp = new THREE.Group(); const grp = new THREE.Group();
grp.name = 'front_tags_group'; grp.name = `${camKey}_tags_group`;
applyCloudLikeTransform(grp, 'd435'); applyCloudLikeTransform(grp, camKey);
parent.add(grp); parent.add(grp);
state.front_tags.group = grp; state.tags[camKey].group = grp;
for (const t of tags) addOneFrontTag(grp, t); for (const t of tags) addOneTag(grp, t, color);
} }
async function pollFrontTags() { async function pollTags() {
const ids = ($('p3FrontTagIds')?.value || '0-49').trim(); const ids = ($('p3FrontTagIds')?.value || '0-49').trim();
const status = $('p3FrontTagStatus'); const status = $('p3FrontTagStatus');
try { try {
const r = await jsonFetch(`/api/pose_3d/front_tags?ids=${encodeURIComponent(ids)}`); const r = await jsonFetch(`/api/pose_3d/tags?ids=${encodeURIComponent(ids)}`);
const tags = r.tags || []; const cams = r.cameras || {};
renderFrontTags(tags); const parts = [];
if (status) { for (const camKey of ['nuwa', 'd435']) {
const ms = r.timings_ms?.total; const c = cams[camKey] || {};
status.textContent = tags.length renderTags(camKey, c.tags || [], TAG_COLORS[camKey]);
? `${tags.length} tag · ids: ${tags.map(t => t.id).join(',')}${ms != null ? ' · ' + ms.toFixed(0) + 'ms' : ''}` if (c.error) parts.push(`${camKey}: err`);
: 'nessun tag visibile'; else parts.push(`${camKey}: ${(c.tags || []).length}`);
} }
if (status) status.textContent = parts.join(' · ');
} catch (e) { } catch (e) {
if (status) status.textContent = `errore: ${e?.message || e}`; if (status) status.textContent = `errore: ${e?.message || e}`;
} }
@@ -1933,10 +1926,10 @@ function bindFrontTagFinder() {
if (!cb) return; if (!cb) return;
cb.addEventListener('change', () => { cb.addEventListener('change', () => {
if (cb.checked) { if (cb.checked) {
// Refresh immediato senza aspettare il prossimo tick di pollState pollTags();
pollFrontTags();
} else { } else {
clearFrontTagsGroup(); clearTagsGroup('nuwa');
clearTagsGroup('d435');
const s = $('p3FrontTagStatus'); const s = $('p3FrontTagStatus');
if (s) s.textContent = '—'; if (s) s.textContent = '—';
} }