Pannello visione operatore: zoom, overlay e Salva passano a <tmf-viewer>

Refs #122

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 21:58:18 +01:00
parent 43ef22079d
commit 815d49d699
7 changed files with 84 additions and 348 deletions
@@ -93,46 +93,28 @@ 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);
check('#116 senza VisionOverlay: nessuna etichetta', c3.viewerLabels.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;
const ls = c3.viewerLabels;
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);
// #122: posizione in px logici dell'immagine (la percentuale la calcola <tmf-viewer>), niente style
check('#122 posizione in px logici', ls[0].x === 500 && ls[0].y === 250 && ls[0].style === undefined, JSON.stringify(ls[0]));
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 senza risultato: nessuna etichetta', c3.viewerLabels.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');
// #121: drawOverlay imposta il viewBox naturale e riempie l'svg con VisionOverlay.svg(overlay, {zoom})
let svgArgs = null;
ctx.VisionOverlay = { svg(overlay, opts) { svgArgs = { overlay, opts }; return '<circle cx="1" cy="2" r="3"/>'; } };
const attrs = {};
const sv = { innerHTML: 'vecchio', setAttribute(k, v) { attrs[k] = v; } };
c3.$refs = { img: { naturalWidth: 2000, naturalHeight: 1000 }, overlay: sv };
c3.result = { overlay: [{ type: 'circle' }] };
c3.z.zoom = 0.5;
c3.drawOverlay();
check('#121 viewBox = dimensioni naturali', attrs.viewBox === '0 0 2000 1000', attrs.viewBox);
check('#121 innerHTML = VisionOverlay.svg', sv.innerHTML === '<circle cx="1" cy="2" r="3"/>', sv.innerHTML);
check('#121 svg riceve overlay e zoom', svgArgs.overlay.length === 1 && svgArgs.opts.zoom === 0.5, JSON.stringify(svgArgs.opts));
c3.result = null;
c3.drawOverlay();
check('#121 senza risultato: svg vuoto', sv.innerHTML === '');
ctx.VisionOverlay = {};
c3.result = { overlay: [{ type: 'circle' }] };
c3.drawOverlay();
check('#121 senza VisionOverlay.svg: svg vuoto, nessun errore', sv.innerHTML === '');
check('#121 niente codice canvas', !/getContext|clearRect|\.width\s*=/.test(src.slice(src.indexOf('drawOverlay() {'))));
// #122: zoom, overlay SVG e Salva sono di <tmf-viewer>: il componente non li ha piu'
check('#122 niente drawOverlay/saveImage/overlayLabels/z', c3.drawOverlay === undefined && c3.saveImage === undefined && c3.overlayLabels === undefined && c3.z === undefined);
check('#122 niente codice canvas', !/getContext|clearRect/.test(src));
// #118: quota non misurata (value null) = NaN, mai Number(null) = 0
check('#118 fmtValue(null) = NaN', c3.fmtValue(null, 'mm') === 'NaN mm', c3.fmtValue(null, 'mm'));
@@ -193,17 +175,11 @@ c5.acquire = async function () { this.result = { vision_result_id: 7, measuremen
h();
check('#120 task-result-reset: saved = false', c5.saved === false);
// #119: saveImage -> tmfSaveImage(src dell'immagine mostrata, nome del task)
let saveArgs = null;
ctx.tmfSaveImage = (src, name) => { saveArgs = { src, name }; return Promise.resolve(true); };
// #119 #122: saveName = titolo del task (nome proposto da <tmf-viewer save-name>), 'immagine' se manca
win.__visionCfg = { taskName: 'Roto3', subtasks: [] };
const c9 = ctx.visionExecute();
c9.imageSrc = 'blob:abc';
const r9 = await c9.saveImage();
check('#119 saveImage: tmfSaveImage(src, nome task)', r9 === true && saveArgs.src === 'blob:abc' && saveArgs.name === 'Roto3', JSON.stringify(saveArgs));
c9.imageSrc = '';
saveArgs = null;
check('#119 saveImage senza immagine: niente', (await c9.saveImage()) === false && saveArgs === null);
check('#122 saveName = nome del task', ctx.visionExecute().saveName === 'Roto3');
win.__visionCfg = { subtasks: [] };
check('#122 saveName di riserva', ctx.visionExecute().saveName === 'immagine');
process.exit(failures ? 1 : 0);
})();