fix(gui): two-click ROI on Calibrazione Piano (was shift+drag)

User asked for "2 punti" — replace the drag-to-rectangle interaction with
two discrete clicks: first click places corner A (cross marker), second
click defines the rectangle, third click starts a new selection.
Rectangles smaller than 10×10 px are rejected. Status chip guides the
flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal PastroandClaude Opus 4.7 committed 2026-05-11 17:38:43 +02:00
1 parent b4db712cd0
commit 652fa4b912
2 files changed
+55 -22

No files matched your search

+6 -5
View File
@@ -187,7 +187,7 @@
<canvas id="wpOverlay" style="position:absolute;left:0;top:0;pointer-events:none"></canvas>
</div>
</div>
<div class="marker-info">RGB live · shift+drag = ROI</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>
@@ -198,12 +198,13 @@
<h2>Calibrazione</h2>
<div class="ctrl-block">
<div class="info-box">
Disegna con shift+drag un rettangolo sul tavolo (senza oggetti), 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>.
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">— · disegna con shift+drag</span></label>
<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="0" max="200" /></label>
</div>
<div class="actions">
+49 -17
View File
@@ -21,7 +21,7 @@ function setChip(text, level) {
function setRoiInfo() {
const el = $('wpRoiInfo');
if (!roi) { el.textContent = '— · disegna con shift+drag'; el.classList.add('dim'); return; }
if (!roi) { el.textContent = "— · clicca 2 angoli sull'RGB"; el.classList.add('dim'); return; }
el.classList.remove('dim');
el.textContent = `u=${roi.u0}…${roi.u1} v=${roi.v0}…${roi.v1} (${roi.u1 - roi.u0}×${roi.v1 - roi.v0} px)`;
}
@@ -68,6 +68,19 @@ function startRgbLoop() {
}
img.addEventListener('load', syncOverlaySize);
// Two-click ROI: 1st click = corner A, 2nd click = corner B (rectangle
// defined). 3rd click starts a new selection.
let firstCorner = null;
function drawCross(ctx, u, v) {
ctx.strokeStyle = '#9b6dff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(u - 8, v); ctx.lineTo(u + 8, v);
ctx.moveTo(u, v - 8); ctx.lineTo(u, v + 8);
ctx.stroke();
}
function drawOverlay() {
const ctx = overlay.getContext('2d');
ctx.clearRect(0, 0, overlay.width, overlay.height);
@@ -75,32 +88,51 @@ function startRgbLoop() {
ctx.strokeStyle = '#9b6dff';
ctx.lineWidth = 2;
ctx.strokeRect(roi.u0, roi.v0, roi.u1 - roi.u0, roi.v1 - roi.v0);
drawCross(ctx, roi.u0, roi.v0);
drawCross(ctx, roi.u1, roi.v1);
} else if (firstCorner) {
drawCross(ctx, firstCorner.u, firstCorner.v);
}
}
viewport.addEventListener('mousedown', (e) => {
if (!e.shiftKey) return; // shift+drag = ROI; plain drag = pan (no-op here)
viewport.addEventListener('click', (e) => {
const p = imgPixelFromEvent(e);
drawing = { u0: p.u, v0: p.v, u1: p.u, v1: p.v };
e.preventDefault();
});
viewport.addEventListener('mousemove', (e) => {
if (!drawing) return;
const p = imgPixelFromEvent(e);
drawing.u1 = p.u;
drawing.v1 = p.v;
if (firstCorner === null && roi === null) {
// First click: place corner A.
firstCorner = p;
drawOverlay();
setChip('punto 1/2 piazzato — clicca il secondo angolo');
return;
}
if (firstCorner !== null) {
// Second click: complete the rectangle.
roi = {
u0: Math.min(drawing.u0, drawing.u1),
v0: Math.min(drawing.v0, drawing.v1),
u1: Math.max(drawing.u0, drawing.u1),
v1: Math.max(drawing.v0, drawing.v1),
u0: Math.min(firstCorner.u, p.u),
v0: Math.min(firstCorner.v, p.v),
u1: Math.max(firstCorner.u, p.u),
v1: Math.max(firstCorner.v, p.v),
};
firstCorner = null;
// Reject degenerate rectangles (<10 px on either side).
if (roi.u1 - roi.u0 < 10 || roi.v1 - roi.v0 < 10) {
roi = null;
setChip('rettangolo troppo piccolo, riprova', 'err');
} else {
setChip('ROI pronto — clicca "Calcola piano"', 'ok');
}
drawOverlay();
setRoiInfo();
updateButtonStates();
return;
}
// ROI already set: start a new selection on the next click.
roi = null;
firstCorner = p;
drawOverlay();
setRoiInfo();
updateButtonStates();
setChip('punto 1/2 piazzato — clicca il secondo angolo');
});
viewport.addEventListener('mouseup', () => { drawing = null; });
viewport.addEventListener('mouseleave', () => { drawing = null; });
})();
// ---------------- Three.js 3D viewer ----------------