feat(find_model gui): toggle to overlay work plane as a violet polygon

The find_model response now returns plane_polygon_px (4 corners of a
30 cm square anchored at the work plane centroid, projected to RGB
pixels via the same K used for the SAM21 cloud). The GUI gains a
'Piano (viola)' checkbox that draws the polygon as a translucent
violet quad with an outline, helping the operator see whether the
plane sits where expected relative to the 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:53:16 +02:00
1 parent f1f9d8f638
commit b0a2d7141d
3 files changed
+46 -1

No files matched your search

+29
View File
@@ -316,6 +316,34 @@ def find_model(req: FindModelReq) -> dict[str, Any]:
# as a separate field so the GUI can color it independently.
sam21_px = project_camera_to_pixels(seg.points_cv_m, K, (W, H))
# 6. Project the work plane as a 30cm square anchored at its centroid,
# so the GUI can overlay it on the RGB image.
plane_polygon_px: list[list[int]] = []
wp = get_work_plane()
if wp is not None:
with sw.step("project_plane"):
n_vec = np.asarray(wp.normal, dtype=np.float64)
c_vec = np.asarray(wp.centroid, dtype=np.float64)
# Pick an arbitrary in-plane vector orthogonal to n.
ref = np.array([1.0, 0.0, 0.0]) if abs(n_vec[0]) < 0.9 else np.array([0.0, 1.0, 0.0])
u_vec = ref - (ref @ n_vec) * n_vec
u_vec /= np.linalg.norm(u_vec) or 1.0
v_vec = np.cross(n_vec, u_vec)
half = 0.15 # 30 cm square
corners = np.stack([
c_vec + half * u_vec + half * v_vec,
c_vec - half * u_vec + half * v_vec,
c_vec - half * u_vec - half * v_vec,
c_vec + half * u_vec - half * v_vec,
])
# Project each corner manually: project_camera_to_pixels drops
# off-frame points which would break the polygon, so allow them.
zs = corners[:, 2]
if (zs > 0).all():
proj = (K @ corners.T).T
uv = proj[:, :2] / proj[:, 2:3]
plane_polygon_px = np.round(uv).astype(int).tolist()
with sw.step("encode_rgb"):
rgb_b64 = base64.b64encode(seg.rgb_jpeg).decode("ascii")
@@ -333,6 +361,7 @@ def find_model(req: FindModelReq) -> dict[str, Any]:
"accepted_points_px": accepted_px.tolist(),
"excluded_points_px": excluded_px.tolist(),
"sam21_points_px": sam21_px.tolist(),
"plane_polygon_px": plane_polygon_px,
"n_accepted": int(accepted_m.shape[0]),
"n_excluded": int(excluded_m.shape[0]),
"n_sam21": int(seg.points_cv_m.shape[0]),