Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019A9CSrh9rfM5WwqKtg8BSE
This commit is contained in:
@@ -20,9 +20,6 @@
|
||||
* Extra config: cameraAvailable, modo, checkUrl.
|
||||
*/
|
||||
function visionExecute() {
|
||||
// Overlay colour per role (VisionSuite roles: reference, roi, found).
|
||||
const ROLE_COLORS = { reference: '#2563eb', found: '#16a34a', roi: '#f59e0b' };
|
||||
|
||||
return {
|
||||
busy: false,
|
||||
grabbing: false, // frame requested from the station camera (#72)
|
||||
@@ -50,6 +47,14 @@ function visionExecute() {
|
||||
|
||||
// #97: start on a frame already acquired and analysed; hook visibility and exit.
|
||||
init() {
|
||||
// #120: «Rimisura»/nuovo ciclo (task-result-reset): il pezzo va registrato di nuovo, anche senza camera
|
||||
window.addEventListener('task-result-reset', () => {
|
||||
this.saved = false;
|
||||
this.preview = !!this.result;
|
||||
this.kick();
|
||||
});
|
||||
// #121: l'overlay SVG si ridisegna a ogni cambio di zoom (spessore e croci costanti a video)
|
||||
if (typeof this.$watch === 'function') this.$watch('z.zoom', () => this.drawOverlay());
|
||||
if (!this.hasCamera) return;
|
||||
document.addEventListener('visibilitychange', () => { if (!document.hidden) this.kick(); });
|
||||
const leave = () => { this.leaving = true; };
|
||||
@@ -150,6 +155,7 @@ function visionExecute() {
|
||||
if (!this.error && this.result && this.result.vision_result_id) {
|
||||
this.preview = false;
|
||||
this.saved = true;
|
||||
this._emitRecorded(); // #120: la pagina conta le quote registrate
|
||||
}
|
||||
} finally {
|
||||
this.registering = false;
|
||||
@@ -186,7 +192,9 @@ function visionExecute() {
|
||||
return;
|
||||
}
|
||||
this.result = body;
|
||||
// Draw once Alpine has rendered the canvas.
|
||||
// #120: senza camera il file scelto e' salvato subito (nessun «Registra»): stesso evento
|
||||
if (!this.hasCamera && body.vision_result_id) this._emitRecorded();
|
||||
// Draw once Alpine has rendered the svg.
|
||||
this.$nextTick(() => this.drawOverlay());
|
||||
} catch (e) {
|
||||
this.error = (cfg.labels && cfg.labels.requestFailed) || 'Error';
|
||||
@@ -195,6 +203,19 @@ function visionExecute() {
|
||||
}
|
||||
},
|
||||
|
||||
// #120: misure salvate (risposta di execute) alla pagina: conteggio quote e «Completato»
|
||||
_emitRecorded() {
|
||||
const ms = (this.result && this.result.measurements) || [];
|
||||
window.dispatchEvent(new CustomEvent('vision-recorded', { detail: { measurements: ms } }));
|
||||
},
|
||||
|
||||
// #119: «Salva» = frame grezzo PNG dell'immagine mostrata, senza disegni (save_image.js)
|
||||
saveImage() {
|
||||
const cfg = window.__visionCfg || {};
|
||||
if (!this.shownImage || typeof tmfSaveImage !== 'function') return Promise.resolve(false);
|
||||
return tmfSaveImage(this.shownImage, cfg.taskName || 'immagine');
|
||||
},
|
||||
|
||||
// Station camera (#72): the Flask proxy answers a PNG, which goes through
|
||||
// acquire() like a picked file. Errors show as the execute ones do.
|
||||
// #97: sends task_id (the task's camera wins over the station's); with a camera
|
||||
@@ -385,6 +406,11 @@ function visionExecute() {
|
||||
},
|
||||
zPanEnd() { this._zPan = null; this.zPanning = false; },
|
||||
|
||||
// #118: valore della cella: una quota non misurata (null) e' «NaN», mai Number(null) = 0
|
||||
fmtValue(v, unit) {
|
||||
return (v == null || !Number.isFinite(Number(v)) ? 'NaN' : Number(v).toFixed(3)) + ' ' + unit;
|
||||
},
|
||||
|
||||
// #117: tolleranza a 3 decimali, trattino se manca (come x-text="...?.toFixed(3) || '—'" dei calibri, ma 0 resta 0.000)
|
||||
fmtTol(v) { return v == null ? '\u2014' : Number(v).toFixed(3); },
|
||||
|
||||
@@ -420,32 +446,14 @@ function visionExecute() {
|
||||
return 'bg-[var(--bg-secondary)] text-[var(--text-muted)] border border-[var(--border-color)]';
|
||||
},
|
||||
|
||||
// Canvas is sized to the natural image, so overlay coordinates (image px)
|
||||
// are used as they are and CSS scales the whole thing to the displayed size.
|
||||
// #121: overlay SVG (VisionOverlay.svg) nel wrapper dello zoom: viewBox = dimensioni naturali, quindi
|
||||
// le coordinate (px immagine) valgono cosi' come sono; spessore e croci restano costanti a ogni zoom
|
||||
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);
|
||||
const lw = Math.max(2, cv.width / 400);
|
||||
g.lineWidth = lw;
|
||||
g.lineJoin = 'round';
|
||||
// #116: tutte le forme (rect2, segment, circle, arc...) le disegna vision_overlay.js; il ciclo sotto resta come ripiego
|
||||
if (typeof VisionOverlay !== 'undefined') {
|
||||
VisionOverlay.draw(g, (this.result && this.result.overlay) || [], lw);
|
||||
return;
|
||||
}
|
||||
for (const item of ((this.result && this.result.overlay) || [])) {
|
||||
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 img = this.$refs.img, sv = this.$refs.overlay;
|
||||
if (!sv) return;
|
||||
if (img && img.naturalWidth) sv.setAttribute('viewBox', '0 0 ' + img.naturalWidth + ' ' + img.naturalHeight);
|
||||
const ok = typeof VisionOverlay !== 'undefined' && typeof VisionOverlay.svg === 'function';
|
||||
sv.innerHTML = ok && this.result ? VisionOverlay.svg(this.result.overlay || [], { zoom: this.z.zoom }) : '';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user