vision: il DXF di confronto unisce le entita' aperte in contorni chiusi (Refs #103)
chiudi_contorni unisce LINE/ARC aperti in LWPOLYLINE chiuse prima di costruire la ricetta e per l'anteprima: con banda larga cut_open_ends non lasciava piu' punti in banda. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -184,6 +184,66 @@ def _candidate_anchors(gray: np.ndarray, dxf_path: Path, px_per_mm: float,
|
||||
return anchors
|
||||
|
||||
|
||||
def chiudi_contorni(dxf_bytes: bytes) -> tuple[bytes, dict]:
|
||||
"""#103: unisce le entita' aperte (LINE/ARC/polilinee aperte in fila) in contorni chiusi.
|
||||
|
||||
CompareDxf taglia il contorno misurato agli estremi di ogni entita' aperta
|
||||
del disegno (cut_open_ends): con un profilo spezzato in LINE+ARC e banda
|
||||
larga non resta nessun punto in banda e il confronto fallisce. Qui le catene
|
||||
chiuse diventano una LWPOLYLINE chiusa (con bulge per gli archi).
|
||||
|
||||
Pura e senza eccezioni nuove: se non c'e' nulla da unire, o ezdxf non legge
|
||||
il file, ritorna i byte originali (sha invariato, ricette esistenti stabili).
|
||||
Ritorna (bytes, {"anelli", "entita_unite", "entita_libere"}).
|
||||
"""
|
||||
nulla = {"anelli": 0, "entita_unite": 0, "entita_libere": 0}
|
||||
try:
|
||||
import ezdxf
|
||||
from ezdxf import edgeminer as em, edgesmith as es
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
src = Path(tmp) / "in.dxf"
|
||||
src.write_bytes(dxf_bytes)
|
||||
doc = ezdxf.readfile(str(src))
|
||||
msp = doc.modelspace()
|
||||
# edges_from_entities_2d salta le entita' gia' chiuse (restano dove sono)
|
||||
edges = list(es.edges_from_entities_2d(msp))
|
||||
if not edges:
|
||||
return dxf_bytes, nulla
|
||||
loops = em.find_all_loops(em.Deposit(edges, gap_tol=0.01)) # 0.01 mm: giunzioni CAD
|
||||
usati: set = set()
|
||||
unite: list = [] # (polilinea, entita' di partenza)
|
||||
# loop piu' lungo per primo, ogni edge usato una sola volta
|
||||
for loop in sorted(loops, key=lambda lp: -sum(e.length for e in lp)):
|
||||
ids = {e.id for e in loop}
|
||||
if ids & usati:
|
||||
continue
|
||||
usati |= ids
|
||||
unite.append((loop, es.lwpolyline_from_chain(loop)))
|
||||
if not unite:
|
||||
return dxf_bytes, {**nulla, "entita_libere": len(edges)}
|
||||
entita_unite = 0
|
||||
for loop, pl in unite:
|
||||
pl.closed = True
|
||||
payload = getattr(loop[0], "payload", None)
|
||||
layer = getattr(getattr(payload, "dxf", None), "layer", None)
|
||||
if layer:
|
||||
pl.dxf.layer = layer # stesso layer dell'entita' di partenza
|
||||
for e in loop:
|
||||
ent = getattr(e, "payload", None)
|
||||
if ent is not None and ent.is_alive:
|
||||
msp.delete_entity(ent) # sostituita dalla polilinea chiusa
|
||||
msp.add_foreign_entity(pl)
|
||||
entita_unite += len(loop)
|
||||
dst = Path(tmp) / "out.dxf"
|
||||
doc.saveas(str(dst))
|
||||
info = {"anelli": len(unite), "entita_unite": entita_unite,
|
||||
"entita_libere": len(edges) - entita_unite}
|
||||
return dst.read_bytes(), info
|
||||
except Exception: # noqa: BLE001 - DXF illeggibile: l'errore resta quello a valle
|
||||
return dxf_bytes, nulla
|
||||
|
||||
|
||||
def _run(gray, graph, px_per_mm, folder) -> RunOutcome:
|
||||
return run_graph(gray, graph, calibration={"px_per_mm": px_per_mm}, recipe_dir=folder)
|
||||
|
||||
@@ -211,6 +271,9 @@ def build_dxf_compare(
|
||||
raise CostruttoreError(f"polarity deve essere una di {list(_POLARITIES)}")
|
||||
if not dxf_bytes:
|
||||
raise CostruttoreError("DXF vuoto")
|
||||
# #103: entita' aperte unite in contorni chiusi PRIMA dello sha: disegno.dxf,
|
||||
# grafo e runtime usano tutti il DXF chiuso
|
||||
dxf_bytes, _info_chiusura = chiudi_contorni(dxf_bytes)
|
||||
|
||||
frame = _gray_u8(gray)
|
||||
calibration_bytes = json.dumps({"px_per_mm": px_per_mm}).encode("utf-8")
|
||||
|
||||
Reference in New Issue
Block a user