diff --git a/src/presa_mano/api/work_plane_routes.py b/src/presa_mano/api/work_plane_routes.py
index 111a267..99f3382 100644
--- a/src/presa_mano/api/work_plane_routes.py
+++ b/src/presa_mano/api/work_plane_routes.py
@@ -31,7 +31,10 @@ class FitReq(BaseModel):
v_min: int = Field(..., ge=0)
u_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)
diff --git a/web/index.html b/web/index.html
index 3f92714..dde0087 100644
--- a/web/index.html
+++ b/web/index.html
@@ -205,7 +205,7 @@
-
+
diff --git a/web/work_plane.js b/web/work_plane.js
index a224ccc..3325b65 100644
--- a/web/work_plane.js
+++ b/web/work_plane.js
@@ -246,6 +246,21 @@ function renderDetails(plane) {
$('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() {
if (!roi) return;
setChip('fit…');
@@ -259,7 +274,7 @@ async function doFit() {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then((r) => r.json());
- if (r.detail) throw new Error(r.detail);
+ if (r.detail) throw new Error(formatDetail(r.detail));
previewPlane = r.plane;
previewPoints = r.points_camera;
three.setPlane(previewPlane);
@@ -282,7 +297,7 @@ async function doSave() {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plane: previewPlane }),
}).then((r) => r.json());
- if (r.detail) throw new Error(r.detail);
+ if (r.detail) throw new Error(formatDetail(r.detail));
savedPlane = r.plane;
previewPlane = null;
$('wpStateBlock').innerHTML = `piano salvato · RMS ${savedPlane.fit_rms_mm.toFixed(1)} mm · n_inliers ${savedPlane.n_inliers} · offset ${savedPlane.offset_mm} mm`;