maker: mm/px a mano nel wizard DXF per immagini da file (Refs #98)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 15:37:40 +01:00
parent 4b1b23cfb6
commit 27d7c1e832
3 changed files with 33 additions and 2 deletions
@@ -21,6 +21,7 @@ function dxfRecipeWizard(taskId, csrf, msgs) {
imageName: '', imageName: '',
imageSrc: '', imageSrc: '',
dxfFile: null, dxfFile: null,
mmPx: '', // #98: mm/px a mano (immagine da file), stringa con virgola o punto
bandMm: 0.1, bandMm: 0.1,
minPointsPct: 95, minPointsPct: 95,
dark: true, // spuntato = pezzo scuro su fondo chiaro (controluce) dark: true, // spuntato = pezzo scuro su fondo chiaro (controluce)
@@ -59,8 +60,19 @@ function dxfRecipeWizard(taskId, csrf, msgs) {
}, },
get calibrated() { return this.calState === 'calibrated'; }, 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() { 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) { _setImage(file) {
@@ -120,6 +132,8 @@ function dxfRecipeWizard(taskId, csrf, msgs) {
fd.append('image', this.imageFile, this.imageFile.name || 'capture.png'); fd.append('image', this.imageFile, this.imageFile.name || 'capture.png');
fd.append('dxf', this.dxfFile, this.dxfFile.name); fd.append('dxf', this.dxfFile, this.dxfFile.name);
fd.append('station_id', this.stationId); 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('band_mm', String(this.bandMm));
fd.append('min_points_pct', String(this.minPointsPct)); fd.append('min_points_pct', String(this.minPointsPct));
fd.append('polarity', this.dark ? 'dark' : 'light'); fd.append('polarity', this.dark ? 'dark' : 'light');
@@ -680,13 +680,23 @@
<p x-show="stationId && calibrated" class="mt-1 text-xs text-emerald-600"> <p x-show="stationId && calibrated" class="mt-1 text-xs text-emerald-600">
{{ _('Calibrazione') }}: <span x-text="calLabel"></span> {{ _('Calibrazione') }}: <span x-text="calLabel"></span>
</p> </p>
<p x-show="stationId && calState && !calibrated" class="mt-1 text-xs text-amber-600"> <p x-show="stationId && calState && !calibrated && manualPxMm === null" class="mt-1 text-xs text-amber-600">{# #98: nascosto con mm/px valido #}
<span x-text="calState === 'uncalibrated' ? {{ _('Stazione non calibrata: calibrala prima di creare la ricetta.')|tojson_attr }} : {{ _('Acquisitore non raggiungibile: impossibile leggere la calibrazione.')|tojson_attr }}"></span> <span x-text="calState === 'uncalibrated' ? {{ _('Stazione non calibrata: calibrala prima di creare la ricetta.')|tojson_attr }} : {{ _('Acquisitore non raggiungibile: impossibile leggere la calibrazione.')|tojson_attr }}"></span>
<a :href="calUrl" target="_blank" rel="noopener" x-show="calUrl" <a :href="calUrl" target="_blank" rel="noopener" x-show="calUrl"
class="font-medium text-primary hover:underline">{{ _('Calibra') }}</a> class="font-medium text-primary hover:underline">{{ _('Calibra') }}</a>
<span class="text-[var(--text-muted)]">({{ _('oppure vedi la pagina Stazioni') }})</span> <span class="text-[var(--text-muted)]">({{ _('oppure vedi la pagina Stazioni') }})</span>
</p> </p>
</div> </div>
<!-- #98: mm/px a mano per immagini da file, sostituisce la calibrazione della stazione -->
<div>
<label class="tmf-label text-xs">{{ _('mm/px (immagine da file)') }}</label>
<input type="text" inputmode="decimal" class="tmf-input text-xs" x-model="mmPx" placeholder="0,01406">
<p x-show="manualPxMm !== null" class="mt-1 text-xs text-emerald-600">
= <span x-text="fmt(manualPxMm)"></span> px/mm
</p>
<p x-show="mmPxInvalid" class="mt-1 text-xs text-amber-600">{{ _('mm/px non valido') }}</p>
<p class="mt-1 text-xs text-[var(--text-muted)]">{{ _('Se compilato sostituisce la calibrazione della stazione; la scala resta salvata nella ricetta.') }}</p>
</div>
<!-- Immagine: camera o file --> <!-- Immagine: camera o file -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div> <div>
@@ -178,6 +178,12 @@ class TestDxfCompareProxy:
assert set(kwargs["files"]) == {"image", "dxf"} assert set(kwargs["files"]) == {"image", "dxf"}
assert kwargs["files"]["image"][1] == PNG 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): 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"} mock_api_client.post.return_value = {**self.OK, "saved": True, "recipe_dir": "vision/recipes/7/x"}
resp = self._post(logged_in_client, save="true") resp = self._post(logged_in_client, save="true")
@@ -229,4 +235,5 @@ class TestPages:
assert "js/dxf_recipe_wizard.js" in html assert "js/dxf_recipe_wizard.js" in html
assert "dxfRecipeWizard(" in html assert "dxfRecipeWizard(" in html
assert "@dxf-recipe-saved" 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 assert "Pezzo scuro su fondo chiaro" in html or "Dark part on light" in html