Wizard DXF e riepilogo: overlay SVG, Salva immagine, NaN per quota non misurata

Refs #118 #119 #121

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 21:20:26 +01:00
parent 4485803122
commit ea64f3c524
8 changed files with 161 additions and 26 deletions
@@ -84,16 +84,18 @@ function csvExport() {
const toleranceMinus = this.hasNumber(nominal) && this.hasNumber(subtask.ltl)
? nominal - subtask.ltl : null;
// #118: quota non misurata (value null) -> NaN nel valore e nella deviazione; le tolleranze nulle restano vuote
const notMeasured = m.value === null || m.value === undefined;
const row = [
m.id || '',
m.subtask_id || '',
this.escapeCsvValue(subtask.description || ''),
this.formatNumber(m.value),
notMeasured ? 'NaN' : this.formatNumber(m.value),
this.escapeCsvValue(subtask.unit || 'mm'),
this.formatNumber(nominal),
this.formatNumber(tolerancePlus),
this.formatNumber(toleranceMinus),
this.formatNumber(m.deviation),
notMeasured ? 'NaN' : this.formatNumber(m.deviation),
m.pass_fail || '',
this.escapeCsvValue(m.lot_number || ''),
this.escapeCsvValue(m.serial_number || ''),
@@ -4,8 +4,7 @@
// la scrive e avvisa il componente visionRecipe con l'evento 'dxf-recipe-saved'.
// Uso: x-data="dxfRecipeWizard(task.id, csrfFn, msgs)". Dipende da Alpine.js e station_calibration.js.
function dxfRecipeWizard(taskId, csrf, msgs) {
// Colori overlay per ruolo, uguali a vision_execute.js (reference, roi, found)
const ROLE_COLORS = { reference: '#2563eb', found: '#16a34a', roi: '#f59e0b' };
// #121: i colori per ruolo stanno in VisionOverlay (vision_overlay.js)
const VALUE_UNITS = { 'dxf.points_in_pct': '%', 'dxf.distance_avg': 'mm', 'dxf.distance_min': 'mm', 'dxf.distance_max': 'mm' }; // #104
const VALUE_KEYS = ['dxf.points_in_pct', 'dxf.distance_avg', 'dxf.distance_min', 'dxf.distance_max'];
@@ -466,6 +465,7 @@ function dxfRecipeWizard(taskId, csrf, msgs) {
let cx = 0, cy = 0;
if (vp) { cx = (vp.scrollLeft + vp.clientWidth / 2) / old; cy = (vp.scrollTop + vp.clientHeight / 2) / old; }
this.zoom = z;
this.drawOverlay(); // #121: ridisegno al nuovo zoom
if (vp) this.$nextTick(() => { vp.scrollLeft = cx * z - vp.clientWidth / 2; vp.scrollTop = cy * z - vp.clientHeight / 2; });
},
// #104: Fit = tutta l'immagine nel viewport (altezza massima, non quella corrente che segue l'immagine)
@@ -475,6 +475,7 @@ function dxfRecipeWizard(taskId, csrf, msgs) {
const z = Math.min(8, Math.max(0.05, Math.min(vp.clientWidth / img.naturalWidth, this._maxVpH() / img.naturalHeight)));
this.zoom = z;
this.fitZoom = z;
this.drawOverlay(); // #121: ridisegno al nuovo zoom
vp.scrollLeft = 0; vp.scrollTop = 0;
},
// #113: zoom per chiave ('img' | 'dxf'), stessa logica di #104 ma con stato in views[k]. Viewport fisso di 240px.
@@ -581,26 +582,15 @@ function dxfRecipeWizard(taskId, csrf, msgs) {
{ maximumFractionDigits: 3 }) : '-';
},
// Come vision_execute.js: canvas della dimensione dell'immagine, coordinate in pixel immagine
// #121: overlay SVG in pixel immagine (viewBox = dimensioni naturali); tratto a spessore costante a ogni zoom.
// Senza VisionOverlay.svg resta vuoto, senza errori.
drawOverlay() {
const img = this.$refs.img, cv = this.$refs.overlay;
if (!img || !cv || !img.naturalWidth) return;
cv.width = img.naturalWidth;
cv.height = img.naturalHeight;
const g = cv.getContext('2d');
g.clearRect(0, 0, cv.width, cv.height);
g.lineWidth = Math.max(2, cv.width / 400);
g.lineJoin = 'round';
const img = this.$refs.img, sv = this.$refs.overlay;
if (!img || !sv || !img.naturalWidth) return;
sv.setAttribute('viewBox', '0 0 ' + img.naturalWidth + ' ' + img.naturalHeight);
const shapes = (this.result && this.result.result && this.result.result.overlay) || [];
for (const item of shapes) {
const rows = item.rows || [], cols = item.cols || [];
if (!rows.length || rows.length !== cols.length) continue;
g.strokeStyle = ROLE_COLORS[item.role] || '#ef4444';
g.beginPath();
g.moveTo(cols[0], rows[0]);
for (let i = 1; i < rows.length; i++) g.lineTo(cols[i], rows[i]);
g.stroke();
}
const VO = (typeof window !== 'undefined' && window.VisionOverlay) || null;
sv.innerHTML = (VO && typeof VO.svg === 'function') ? VO.svg(shapes, { zoom: this.zoom }) : '';
},
};
}