test: pezzo a L (sample.dxf e il rettangolo con foro non distinguono il capovolgimento), refs #127

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
This commit is contained in:
2026-11-06 14:13:46 +01:00
parent d8bca535ac
commit 1bd6c6b011
+24 -14
View File
@@ -37,7 +37,7 @@ PX_PER_MM = 4.0
def a_scene(angle_deg: float = 0.0, shift_px: tuple[float, float] = (0.0, 0.0), def a_scene(angle_deg: float = 0.0, shift_px: tuple[float, float] = (0.0, 0.0),
margin_mm: float = 60.0, hole_dx_mm: float = 0.0) -> np.ndarray: margin_mm: float = 60.0) -> np.ndarray:
"""Il pezzo del sample.dxf (scuro su bianco) a 4 px/mm, ruotato e traslato.""" """Il pezzo del sample.dxf (scuro su bianco) a 4 px/mm, ruotato e traslato."""
w = int((DXF_WIDTH_MM + 2 * margin_mm) * PX_PER_MM) w = int((DXF_WIDTH_MM + 2 * margin_mm) * PX_PER_MM)
h = int((DXF_HEIGHT_MM + 2 * margin_mm) * PX_PER_MM) h = int((DXF_HEIGHT_MM + 2 * margin_mm) * PX_PER_MM)
@@ -45,8 +45,7 @@ def a_scene(angle_deg: float = 0.0, shift_px: tuple[float, float] = (0.0, 0.0),
cx, cy = w // 2, h // 2 cx, cy = w // 2, h // 2
hw, hh = int(DXF_WIDTH_MM * PX_PER_MM / 2), int(DXF_HEIGHT_MM * PX_PER_MM / 2) hw, hh = int(DXF_WIDTH_MM * PX_PER_MM / 2), int(DXF_HEIGHT_MM * PX_PER_MM / 2)
cv2.rectangle(img, (cx - hw, cy - hh), (cx + hw, cy + hh), 0, -1) cv2.rectangle(img, (cx - hw, cy - hh), (cx + hw, cy + hh), 0, -1)
# #127: hole_dx_mm sposta il foro fuori centro (pezzo asimmetrico a 180 gradi) cv2.circle(img, (cx, cy), int(DXF_HOLE_R_MM * PX_PER_MM), 255, -1)
cv2.circle(img, (cx + int(hole_dx_mm * PX_PER_MM), cy), int(DXF_HOLE_R_MM * PX_PER_MM), 255, -1)
if angle_deg or any(shift_px): if angle_deg or any(shift_px):
m = cv2.getRotationMatrix2D((cx, cy), angle_deg, 1.0) # >0 = antiorario a video m = cv2.getRotationMatrix2D((cx, cy), angle_deg, 1.0) # >0 = antiorario a video
m[0, 2] += shift_px[0] m[0, 2] += shift_px[0]
@@ -96,37 +95,48 @@ def test_ricetta_dxf_con_riferimento_gia_ruotato(tmp_path):
assert risultato["outputs"]["dxf.points_in_pct"] > 95.0 assert risultato["outputs"]["dxf.points_in_pct"] > 95.0
# #127: DXF asimmetrico a 180 gradi (sample.dxf e' simmetrico, non distingue il capovolgimento) # #127: pezzo a L (asimmetrico a 180 gradi: sample.dxf e' simmetrico e non distingue il
HOLE_DX_MM = 60.0 # capovolgimento). Un rettangolo con foro fuori centro NON basta: l'ICP di Core non lo risolve.
L_PROFILE_MM = [(-100, -50), (100, -50), (100, 50), (-20, 50), (-20, 10), (-100, 10)]
def _asymmetric_dxf(path: Path) -> bytes: def _l_dxf(path: Path) -> bytes:
"""Rettangolo 200x100 mm centrato nell'origine + foro r=10 mm a x=+60 mm."""
import ezdxf import ezdxf
doc = ezdxf.new() doc = ezdxf.new()
msp = doc.modelspace() doc.modelspace().add_lwpolyline(L_PROFILE_MM, close=True)
msp.add_lwpolyline([(-100, -50), (100, -50), (100, 50), (-100, 50)], close=True)
msp.add_circle((HOLE_DX_MM, 0), DXF_HOLE_R_MM)
doc.saveas(str(path)) doc.saveas(str(path))
return path.read_bytes() return path.read_bytes()
def _l_scene(angle_deg: float = 0.0) -> np.ndarray:
"""Il pezzo a L (scuro su bianco) a 4 px/mm; l'asse y del DXF e' verso l'alto."""
w, h = int(320 * PX_PER_MM), int(220 * PX_PER_MM)
cx, cy = w // 2, h // 2
img = np.full((h, w), 255, np.uint8)
pts = np.array([(cx + x * PX_PER_MM, cy - y * PX_PER_MM) for x, y in L_PROFILE_MM], np.int32)
cv2.fillPoly(img, [pts], 0)
if angle_deg:
m = cv2.getRotationMatrix2D((cx, cy), angle_deg, 1.0)
img = cv2.warpAffine(img, m, (w, h), flags=cv2.INTER_LINEAR, borderValue=255)
return img
def _pct_a_180(tmp_path, togli_quarter: bool) -> tuple[float, float]: def _pct_a_180(tmp_path, togli_quarter: bool) -> tuple[float, float]:
"""points_in_pct della ricetta costruita a 0 gradi: (sulla scena dritta, a 180 gradi).""" """points_in_pct della ricetta costruita a 0 gradi: (sulla scena dritta, a 180 gradi)."""
dxf = _asymmetric_dxf(tmp_path / "asimmetrico.dxf") risultato = build_dxf_compare(_l_scene(0.0), _l_dxf(tmp_path / "l.dxf"), PX_PER_MM, band_mm=2.0)
risultato = build_dxf_compare(a_scene(0.0, hole_dx_mm=HOLE_DX_MM), dxf, PX_PER_MM, band_mm=2.0)
graph = risultato["graph"] graph = risultato["graph"]
if togli_quarter: # misura di controllo: senza pose_search il pezzo a 180 va capovolto if togli_quarter: # misura di controllo: senza pose_search il pezzo a 180 va capovolto
for tool in graph["tools"]: for tool in graph["tools"]:
tool["values"].pop("pose_search", None) tool["values"].pop("pose_search", None)
for nome, b64 in risultato["files"].items(): for nome, b64 in risultato["files"].items():
(tmp_path / nome).write_bytes(base64.b64decode(b64)) (tmp_path / nome).write_bytes(base64.b64decode(b64))
def pct(img): def pct(img):
esito = run_graph(img, graph, calibration={"px_per_mm": PX_PER_MM}, recipe_dir=tmp_path) esito = run_graph(img, graph, calibration={"px_per_mm": PX_PER_MM}, recipe_dir=tmp_path)
assert not esito.failures, esito.failures assert not esito.failures, esito.failures
return esito.outputs["dxf.points_in_pct"] return esito.outputs["dxf.points_in_pct"]
return pct(a_scene(0.0, hole_dx_mm=HOLE_DX_MM)), pct(a_scene(180.0, hole_dx_mm=HOLE_DX_MM)) return pct(_l_scene(0.0)), pct(_l_scene(180.0))
def test_ricetta_dxf_su_pezzo_capovolto_di_180(tmp_path): def test_ricetta_dxf_su_pezzo_capovolto_di_180(tmp_path):
@@ -139,7 +149,7 @@ def test_ricetta_dxf_su_pezzo_capovolto_di_180(tmp_path):
def test_ricetta_dxf_senza_quarter_il_pezzo_a_180_e_capovolto(tmp_path): def test_ricetta_dxf_senza_quarter_il_pezzo_a_180_e_capovolto(tmp_path):
# #127: controllo del test sopra - senza pose_search il disegno finisce capovolto # #127: controllo del test sopra - senza pose_search il disegno finisce capovolto
_, pct_180 = _pct_a_180(tmp_path, togli_quarter=True) _, pct_180 = _pct_a_180(tmp_path, togli_quarter=True)
assert pct_180 < 80.0 # misurato 67,2 (il rettangolo si sovrappone lo stesso, il foro no) assert pct_180 < 20.0 # misurato ~2,8% (Core#343 riporta 3,3% su PR165)
def test_graph_scrive_pose_search_quarter_nel_compare_dxf(): def test_graph_scrive_pose_search_quarter_nel_compare_dxf():