feat(right_arm): perm assi cam->polso, precise=true default, verify_tag UI

Lavoro accumulato nella sessione 2026-05-21 prima del brainstorming
della tab Pose 3D:

- compute_delta_wrist generalizzato con cam_to_wrist_perm: la mappa
  cam->polso non e' solo sign-flip ma una permutazione (default
  identita' per backward compat). DeltaOversize ora espone X/Y/Z
  nella stringa d'errore. Test aggiornati.

- G1Client.wrist_absolute / wrist_relative: precise=True di default
  (era False). Stessa cosa per wrist_routes.AbsoluteReq Pydantic
  e per agent.tools move_wrist_*. Movimenti reali ora chiedono
  controllo accurato salvo override esplicito.

- Tab Braccio DX:
  - /verify_tag endpoint: snapshot wrist FK + tag id=1 dalla front
    camera. Per verifica relativa moveto vs marker (no calibrazione).
  - GUI: pannello "Verifica tag id=1" + campi camera_offset_wrist_mm.
  - markUserTouchedOnInput esteso a raOff/raSgn/raCamOff.

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-21 15:44:48 +02:00
1 parent 8d40ebaf89
commit 57954b5c8d
8 files changed
+238 -23

No files matched your search

+53
View File
@@ -39,6 +39,40 @@ def test_signs_applied_componentwise():
assert np.allclose(delta, [0.05, -0.10, -0.20])
def test_permutation_maps_cam_axes_to_wrist_axes():
# Mounting: cam guarda avanti = wrist_X. perm=(2,0,1): wrist_X<-cam_Z,
# wrist_Y<-cam_X, wrist_Z<-cam_Y. signs=(+1,-1,-1).
# centroid=(0.10, 0.05, 0.30) → wrist=(+0.30, -0.10, -0.05) prima offset/t_wc
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.10, 0.05, 0.30]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, -1, -1),
cam_to_wrist_perm=(2, 0, 1),
)
assert np.allclose(delta, [0.30, -0.10, -0.05])
def test_permutation_default_is_identity_back_compat():
# Senza perm esplicito, comportamento = sign-only (modello vecchio).
delta_new = compute_delta_wrist(
centroid_camera_m=np.array([0.10, 0.20, 0.30]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, -1, -1),
)
assert np.allclose(delta_new, [0.10, -0.20, -0.30])
def test_invalid_permutation_raises():
import pytest as _pt
with _pt.raises(ValueError, match="permutation"):
compute_delta_wrist(
centroid_camera_m=np.zeros(3),
offset_camera_m=np.zeros(3),
axis_signs=(1, 1, 1),
cam_to_wrist_perm=(0, 0, 1),
)
def test_camera_offset_wrist_added_after_sign_flip():
# centroid=(0,0,1) offset=(0,0,0) sign=(1,1,1) t_wc=(0.05,-0.02,0.08)
# → sign*(centroid-offset) = (0,0,1)
@@ -73,6 +107,25 @@ def test_oversize_raises_when_not_forced():
)
def test_oversize_message_includes_per_axis_breakdown():
# delta = (1,-1,-1) * ((0.10, -0.02, 0.50) - 0) = (0.10, 0.02, -0.50)
# ||delta|| = sqrt(0.01+0.0004+0.25) = ~0.510m, max=0.15
with pytest.raises(DeltaOversize) as exc_info:
compute_delta_wrist(
centroid_camera_m=np.array([0.10, -0.02, 0.50]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, -1, -1),
max_step_m=0.15,
force=False,
)
msg = str(exc_info.value)
assert "X=100.0" in msg
assert "Y=20.0" in msg
assert "Z=-500.0" in msg
# E l'oggetto espone il vettore strutturato
assert np.allclose(exc_info.value.delta_m, [0.10, 0.02, -0.50])
def test_oversize_passes_when_forced():
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.0, 0.0, 0.5]),