fix(work_plane): allow negative offset_mm + readable validation errors

- Drop the ge=0 constraint on FitReq.offset_mm (negative values are a
  documented usage: aggressive noise cull above the plane).
- HTML input min lowered to -200 to match.
- Render FastAPI 422 validation arrays as a human-readable string in the
  status chip (was showing "[object Object]").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal PastroandClaude Opus 4.7 committed 2026-05-13 11:03:36 +02:00
1 parent 92506d62c6
commit 76e0739620
3 files changed
+22 -4

No files matched your search

+4 -1
View File
@@ -31,7 +31,10 @@ class FitReq(BaseModel):
v_min: int = Field(..., ge=0) v_min: int = Field(..., ge=0)
u_max: int = Field(..., gt=0) u_max: int = Field(..., gt=0)
v_max: int = Field(..., gt=0) v_max: int = Field(..., gt=0)
offset_mm: float = Field(5.0, ge=0.0, le=200.0) # offset_mm può essere negativo: valore <0 esclude un buffer SOPRA il
# piano (toglie noise depth della superficie); valore >0 è permissivo
# (mantieni un buffer sotto il piano). Range esteso simmetricamente.
offset_mm: float = Field(5.0, ge=-200.0, le=200.0)
max_preview_points: int = Field(5000, ge=100, le=20000) max_preview_points: int = Field(5000, ge=100, le=20000)
+1 -1
View File
@@ -205,7 +205,7 @@
</div> </div>
<div class="kv compact"> <div class="kv compact">
<label>ROI <span id="wpRoiInfo" class="dim">— · clicca 2 angoli sull'RGB</span></label> <label>ROI <span id="wpRoiInfo" class="dim">— · clicca 2 angoli sull'RGB</span></label>
<label>offset (mm) <input type="number" id="wpOffset" value="5.0" step="0.5" min="0" max="200" /></label> <label>offset (mm) <input type="number" id="wpOffset" value="5.0" step="0.5" min="-200" max="200" /></label>
</div> </div>
<div class="actions"> <div class="actions">
<button id="wpFitBtn" class="primary" disabled>Calcola piano (preview)</button> <button id="wpFitBtn" class="primary" disabled>Calcola piano (preview)</button>
+17 -2
View File
@@ -246,6 +246,21 @@ function renderDetails(plane) {
$('wpDetails').textContent = JSON.stringify(plane, null, 2); $('wpDetails').textContent = JSON.stringify(plane, null, 2);
} }
// FastAPI 422 returns `detail` as an array of validation errors:
// [{loc, msg, type, ...}, ...]
// Plain HTTPException returns it as a string. Render either case usefully so
// the status chip never says "[object Object]".
function formatDetail(detail) {
if (typeof detail === 'string') return detail;
if (Array.isArray(detail)) {
return detail.map(e => {
const loc = Array.isArray(e.loc) ? e.loc.join('.') : e.loc;
return loc ? `${loc}: ${e.msg}` : (e.msg || JSON.stringify(e));
}).join('; ');
}
return JSON.stringify(detail);
}
async function doFit() { async function doFit() {
if (!roi) return; if (!roi) return;
setChip('fit…'); setChip('fit…');
@@ -259,7 +274,7 @@ async function doFit() {
method: 'POST', headers: { 'Content-Type': 'application/json' }, method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body), body: JSON.stringify(body),
}).then((r) => r.json()); }).then((r) => r.json());
if (r.detail) throw new Error(r.detail); if (r.detail) throw new Error(formatDetail(r.detail));
previewPlane = r.plane; previewPlane = r.plane;
previewPoints = r.points_camera; previewPoints = r.points_camera;
three.setPlane(previewPlane); three.setPlane(previewPlane);
@@ -282,7 +297,7 @@ async function doSave() {
method: 'POST', headers: { 'Content-Type': 'application/json' }, method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plane: previewPlane }), body: JSON.stringify({ plane: previewPlane }),
}).then((r) => r.json()); }).then((r) => r.json());
if (r.detail) throw new Error(r.detail); if (r.detail) throw new Error(formatDetail(r.detail));
savedPlane = r.plane; savedPlane = r.plane;
previewPlane = null; previewPlane = null;
$('wpStateBlock').innerHTML = `piano salvato · RMS ${savedPlane.fit_rms_mm.toFixed(1)} mm · n_inliers ${savedPlane.n_inliers} · offset ${savedPlane.offset_mm} mm`; $('wpStateBlock').innerHTML = `piano salvato · RMS ${savedPlane.fit_rms_mm.toFixed(1)} mm · n_inliers ${savedPlane.n_inliers} · offset ${savedPlane.offset_mm} mm`;