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 5056ee7..5a5a945 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,7 @@ function dxfRecipeWizard(taskId, csrf, msgs) { imageName: '', imageSrc: '', dxfFile: null, + mmPx: '', // #98: mm/px a mano (immagine da file), stringa con virgola o punto bandMm: 0.1, minPointsPct: 95, dark: true, // spuntato = pezzo scuro su fondo chiaro (controluce) @@ -59,8 +60,19 @@ function dxfRecipeWizard(taskId, csrf, msgs) { }, get calibrated() { return this.calState === 'calibrated'; }, + // #98: mm/px inserito a mano -> px/mm; null se vuoto o fuori da 0.00001..100 (limiti di /camera/calibrazioni/manuale, #95) + get manualPxMm() { + const raw = String(this.mmPx || '').trim().replace(',', '.'); + if (!raw) return null; + const v = Number(raw); + if (!Number.isFinite(v) || v < 0.00001 || v > 100) return null; + return 1 / v; + }, + // #98: compilato ma non valido -> errore in pagina + get mmPxInvalid() { return String(this.mmPx || '').trim() !== '' && this.manualPxMm === null; }, get canRun() { - return !!(this.stationId && this.calibrated && this.imageFile && this.dxfFile) && !this.busy; + // #98: la scala a mano sostituisce la calibrazione della stazione + return !!(this.stationId && (this.calibrated || this.manualPxMm !== null) && this.imageFile && this.dxfFile) && !this.busy; }, _setImage(file) { @@ -120,6 +132,8 @@ function dxfRecipeWizard(taskId, csrf, msgs) { fd.append('image', this.imageFile, this.imageFile.name || 'capture.png'); fd.append('dxf', this.dxfFile, this.dxfFile.name); fd.append('station_id', this.stationId); + // #98: scala a mano; il backend la preferisce alla calibrazione della stazione + if (this.manualPxMm !== null) fd.append('px_per_mm', String(this.manualPxMm)); fd.append('band_mm', String(this.bandMm)); fd.append('min_points_pct', String(this.minPointsPct)); fd.append('polarity', this.dark ? 'dark' : 'light'); diff --git a/src/frontend/flask_app/templates/maker/task_editor.html b/src/frontend/flask_app/templates/maker/task_editor.html index a06fed4..5a7f4c6 100644 --- a/src/frontend/flask_app/templates/maker/task_editor.html +++ b/src/frontend/flask_app/templates/maker/task_editor.html @@ -680,13 +680,23 @@

{{ _('Calibrazione') }}:

-

+

{# #98: nascosto con mm/px valido #} {{ _('Calibra') }} ({{ _('oppure vedi la pagina Stazioni') }})

+ +
+ + +

+ = px/mm +

+

{{ _('mm/px non valido') }}

+

{{ _('Se compilato sostituisce la calibrazione della stazione; la scala resta salvata nella ricetta.') }}

+
diff --git a/src/frontend/flask_app/tests/test_dxf_recipe_routes.py b/src/frontend/flask_app/tests/test_dxf_recipe_routes.py index a1cebe8..c9b85d3 100644 --- a/src/frontend/flask_app/tests/test_dxf_recipe_routes.py +++ b/src/frontend/flask_app/tests/test_dxf_recipe_routes.py @@ -178,6 +178,12 @@ class TestDxfCompareProxy: assert set(kwargs["files"]) == {"image", "dxf"} assert kwargs["files"]["image"][1] == PNG + def test_manual_px_per_mm_forwarded(self, logged_in_client, mock_api_client): + # #98: scala a mano dal wizard (immagine da file): inoltrata al backend + mock_api_client.post.return_value = self.OK + self._post(logged_in_client, station_id="4", px_per_mm="71.1237") + assert mock_api_client.post.call_args.kwargs["data"]["px_per_mm"] == "71.1237" + def test_save_true_forwarded(self, logged_in_client, mock_api_client): mock_api_client.post.return_value = {**self.OK, "saved": True, "recipe_dir": "vision/recipes/7/x"} resp = self._post(logged_in_client, save="true") @@ -229,4 +235,5 @@ class TestPages: assert "js/dxf_recipe_wizard.js" in html assert "dxfRecipeWizard(" in html assert "@dxf-recipe-saved" in html + assert 'x-model="mmPx"' in html # #98: campo mm/px a mano assert "Pezzo scuro su fondo chiaro" in html or "Dark part on light" in html