diff --git a/src/frontend/flask_app/blueprints/maker.py b/src/frontend/flask_app/blueprints/maker.py index 5955fe2..da49040 100644 --- a/src/frontend/flask_app/blueprints/maker.py +++ b/src/frontend/flask_app/blueprints/maker.py @@ -701,6 +701,25 @@ def api_dxf_compare_recipe(task_id: int): return jsonify(resp), 200 +# #101: anteprima del DXF appena scelto nel wizard (contorni letti dal vision worker) +@maker_bp.route("/api/vision/dxf-preview", methods=["POST"]) +@login_required +@role_required("Maker") +def api_dxf_preview(): + """Proxy: anteprima del DXF (contorni, bbox, L x H) prima di creare la ricetta (#101).""" + dxf = request.files.get("dxf") + if dxf is None or not dxf.filename: + return jsonify({"error": True, "detail": _("Serve il file DXF")}), 400 + + resp = api_client.post( + "/api/vision/dxf-preview", + files={"dxf": (dxf.filename, dxf.read(), dxf.mimetype or "application/octet-stream")}, + ) + if resp.get("error"): + return jsonify(resp), resp.get("status_code", 500) + return jsonify(resp), 200 + + @maker_bp.route("/api/files/", methods=["GET"]) @login_required def api_get_file(file_path: str): diff --git a/src/frontend/flask_app/static/js/dxf_recipe_wizard.js b/src/frontend/flask_app/static/js/dxf_recipe_wizard.js index 5a5a945..adbcd5c 100644 --- a/src/frontend/flask_app/static/js/dxf_recipe_wizard.js +++ b/src/frontend/flask_app/static/js/dxf_recipe_wizard.js @@ -21,6 +21,10 @@ function dxfRecipeWizard(taskId, csrf, msgs) { imageName: '', imageSrc: '', dxfFile: null, + dxfPreview: null, // #101: risposta di /dxf-preview {contorni, bbox, larghezza_mm, altezza_mm, entita, punti} + dxfPreviewBusy: false, + dxfPreviewError: '', + _dxfPreviewSeq: 0, // #101: una risposta arrivata dopo un cambio di file si scarta mmPx: '', // #98: mm/px a mano (immagine da file), stringa con virgola o punto bandMm: 0.1, minPointsPct: 95, @@ -87,7 +91,60 @@ function dxfRecipeWizard(taskId, csrf, msgs) { const f = ev.target.files && ev.target.files[0]; if (f) this._setImage(f); }, - onDxf(ev) { this.dxfFile = (ev.target.files && ev.target.files[0]) || null; this.result = null; }, + onDxf(ev) { + this.dxfFile = (ev.target.files && ev.target.files[0]) || null; + this.result = null; + this.loadDxfPreview(); // #101: anteprima appena si sceglie il file + }, + + // #101: legge il DXF sul vision worker (via Flask e backend) e ne mostra i contorni + async loadDxfPreview() { + const seq = ++this._dxfPreviewSeq; + this.dxfPreview = null; + this.dxfPreviewError = ''; + this.dxfPreviewBusy = false; + if (!this.dxfFile) return; // file tolto: tutto azzerato + this.dxfPreviewBusy = true; + try { + const fd = new FormData(); + fd.append('dxf', this.dxfFile, this.dxfFile.name); + const resp = await fetch('/maker/api/vision/dxf-preview', { + method: 'POST', headers: { 'X-CSRFToken': this._csrf() }, body: fd, + }); + const body = await resp.json().catch(() => ({})); + if (seq !== this._dxfPreviewSeq) return; // file cambiato nel frattempo + if (!resp.ok || body.error) this.dxfPreviewError = this._errorText(body); + else this.dxfPreview = body; + } catch (e) { + if (seq !== this._dxfPreviewSeq) return; + this.dxfPreviewError = this.msgs.requestFailed || String(e); + } + if (seq === this._dxfPreviewSeq) this.dxfPreviewBusy = false; + }, + + // #101: una stringa `points` per ogni contorno, per + dxfPreviewPaths() { + const contorni = (this.dxfPreview && this.dxfPreview.contorni) || []; + return contorni.filter(c => c && c.length > 1).map(c => c.map(p => p[0] + ',' + p[1]).join(' ')); + }, + + // #101: viewBox sul bbox con margine 5% (minimo 0.5 mm per lato) + dxfPreviewViewBox() { + const b = this.dxfPreview && this.dxfPreview.bbox; + if (!b) return '0 0 1 1'; + const w = b.xmax - b.xmin, h = b.ymax - b.ymin; + const m = Math.max(0.5, 0.05 * Math.max(w, h)); + return [b.xmin - m, b.ymin - m, w + 2 * m, h + 2 * m].map(v => +v.toFixed(3)).join(' '); + }, + + // #101: «L × H mm · N entità», decimale con virgola (2 decimali) secondo la lingua pagina + get dxfPreviewSummary() { + const d = this.dxfPreview; + if (!d) return ''; + const f = v => Number(v).toLocaleString(document.documentElement.lang || 'it', + { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + return `${f(d.larghezza_mm)} × ${f(d.altezza_mm)} mm · ${d.entita} ${this.msgs.dxfEntities || 'entities'}`; + }, // Frame dalla camera della stazione scelta async grab() { diff --git a/src/frontend/flask_app/templates/maker/task_editor.html b/src/frontend/flask_app/templates/maker/task_editor.html index 5a7f4c6..c3b6fed 100644 --- a/src/frontend/flask_app/templates/maker/task_editor.html +++ b/src/frontend/flask_app/templates/maker/task_editor.html @@ -662,7 +662,7 @@