Pannello visione: integrazione overlay ed etichette quota (rows, drawOverlay, overlayLabels)

Refs #116

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 17:00:48 +01:00
parent 88b3f202b1
commit acd94a9d33
4 changed files with 101 additions and 1 deletions
@@ -71,4 +71,52 @@ check('#117 fmtTol(undefined) = trattino', c2.fmtTol(undefined) === '\u2014');
check('#117 fmtTol(0.05) = 0.050', c2.fmtTol(0.05) === '0.050', c2.fmtTol(0.05));
check('#117 fmtTol(0) = 0.000', c2.fmtTol(0) === '0.000', c2.fmtTol(0));
// #116: righe con key/name/vision_output, etichette in % con un VisionOverlay finto, labelClass
win.__visionCfg = {
subtasks: [
{ id: 30, marker_number: 1, description: 'Larghezza', unit: 'mm', vision_output: 'larg.value' },
{ id: 31, description: '', measurement_type: 'Diametro', unit: 'mm' },
],
};
const c3 = ctx.visionExecute();
c3.result = { overlay: [{ type: 'circle', tool_id: 'larg' }], measurements: [
{ subtask_id: 30, value: 1.5, pass_fail: 'pass' },
{ subtask_id: 31, value: 2, pass_fail: 'fail' },
] };
const r3 = c3.rows;
check('#116 riga: key = subtask_id', r3[0].key === 30 && r3[1].key === 31);
check('#116 riga: name senza marker', r3[0].name === 'Larghezza' && r3[1].name === 'Diametro', r3[0].name + '/' + r3[1].name);
check('#116 riga: vision_output', r3[0].vision_output === 'larg.value' && r3[1].vision_output == null);
check('#116 label invariato (#114)', r3[0].label === '1. Larghezza', r3[0].label);
check('#116 senza VisionOverlay: nessuna etichetta', c3.overlayLabels.length === 0);
let seen = null;
ctx.VisionOverlay = {
labels(rows, overlay) { seen = { rows, overlay }; return [{ key: 30, x: 500, y: 250, text: 'Larghezza 1.500 mm', status: 'pass' }]; },
};
check('#116 senza immagine: nessuna etichetta', c3.overlayLabels.length === 0);
c3.z.w = 2000; c3.z.h = 1000;
const ls = c3.overlayLabels;
check('#116 una etichetta', ls.length === 1 && ls[0].key === 30 && ls[0].text === 'Larghezza 1.500 mm', JSON.stringify(ls));
check('#116 posizione in percentuale', ls[0].style === 'left:25%;top:25%;transform:translate(8px,-50%)', ls[0].style);
check('#116 classe da esito', ls[0].cls === 'bg-green-600 text-white', ls[0].cls);
check('#116 labels riceve rows e overlay', seen.rows.length === 2 && seen.overlay.length === 1);
c3.result = null;
check('#116 senza risultato: nessuna etichetta', c3.overlayLabels.length === 0);
check('#116 labelClass pass', c3.labelClass('pass') === 'bg-green-600 text-white');
check('#116 labelClass warning', c3.labelClass('warning') === 'bg-amber-400 text-black');
check('#116 labelClass fail', c3.labelClass('fail') === 'bg-red-600 text-white');
check('#116 labelClass altro', c3.labelClass('boh') === 'bg-gray-700 text-white' && c3.labelClass(undefined) === 'bg-gray-700 text-white');
// drawOverlay usa VisionOverlay.draw con lw = max(2, w/400)
let drawn = null;
ctx.VisionOverlay = { draw(g, overlay, lw) { drawn = { g, overlay, lw }; return { drawn: 1, skipped: 0 }; } };
const gfake = { clearRect() {}, set lineWidth(v) {}, set lineJoin(v) {} };
const cv = { width: 0, height: 0, getContext() { return gfake; } };
c3.$refs = { img: { naturalWidth: 2000, naturalHeight: 1000 }, overlay: cv };
c3.result = { overlay: [{ type: 'circle' }] };
c3.drawOverlay();
check('#116 drawOverlay chiama VisionOverlay.draw', drawn && drawn.g === gfake && drawn.overlay.length === 1 && drawn.lw === 5, JSON.stringify(drawn && drawn.lw));
check('#116 canvas alla risoluzione naturale', cv.width === 2000 && cv.height === 1000);
process.exit(failures ? 1 : 0);
@@ -71,3 +71,14 @@ def test_quote_table_shows_nominal_and_tolerances():
for marker in ("fmtTol(r.nominal)", "fmtTol(r.ltl)", "fmtTol(r.utl)", "fmtTol(r.lwl)", "fmtTol(r.uwl)"):
assert marker in panel, marker
assert "text-measure-fail" in panel and "text-measure-warning" in panel
def test_overlay_script_loaded_before_vision_execute_and_labels_layer():
# #116: vision_overlay.js prima di vision_execute.js; strato etichette dentro il wrapper dello zoom, dopo il canvas
src = TEMPLATE.read_text(encoding="utf-8")
assert src.index("js/vision_overlay.js") < src.index("js/vision_execute.js")
panel = _panel()
assert panel.index('x-ref="overlay"') < panel.index('x-for="l in overlayLabels"')
label = _tag_with(panel, 'x-text="l.text"')
assert ':style="l.style"' in label and "pointer-events-none" in label
assert 'style="' not in label.replace(':style="', "") # niente style statico accanto a :style