Add exact-geometry (orthogonal, DRC-clean) routing track
Adds route/exact_router.py: routes with the grid track's rip-up/multi-layer/ via topology search in orthogonal mode (axis-aligned segments -> exact IntBox copper), then verifies clearance exactly against a ShapeSearchTree. A net whose copper would come closer than the clearance to any accepted item is dropped, so the emitted geometry is DRC-clean by construction; endpoints stay exactly on pads and vias at real layer transitions. GridRouter gains a non-breaking orthogonal option (4-connected). The pipeline gains an engine= selector: 'grid' (default, unchanged, highest coverage) or 'exact' (DRC-verified). The grid MVP stays the fallback. On the designed boards the exact track routes every net DRC-clean; on the real KiCad board it matches the grid's connectivity (4/4 multi-pin nets) and is DRC-clean where the grid MVP's widened centrelines are not. Deferred: shove, and 45-degree/IntOctagon trace caps. Tests: the exact router's output has no clearance violation (exact IntBox check) on every fixture; endpoints on pads; traces orthogonal; crossing uses a via; and a direct comparison showing the exact track is clean where the grid MVP violates on the real board. Oracle-gated parity for engine='exact'.
This commit is contained in:
@@ -128,6 +128,7 @@ class GridRouter:
|
||||
max_passes: int = 10,
|
||||
rip_cap: int = 4,
|
||||
rip_penalty: float = 30.0,
|
||||
orthogonal: bool = False,
|
||||
) -> None:
|
||||
self.board = board
|
||||
self.trace_width = max(trace_width, 1)
|
||||
@@ -138,6 +139,9 @@ class GridRouter:
|
||||
self.max_passes = max(max_passes, 1)
|
||||
self.rip_cap = max(rip_cap, 0)
|
||||
self.rip_penalty = rip_penalty
|
||||
# orthogonal routing (axis-aligned segments only) yields exact IntBox
|
||||
# trace copper; 8-connected also produces 45-degree segments.
|
||||
self._neighbours = _NEIGHBOURS[:4] if orthogonal else _NEIGHBOURS
|
||||
# cell size must fit a trace plus its clearance to a neighbouring trace
|
||||
self.step = max(self.trace_width + self.clearance, 1)
|
||||
|
||||
@@ -272,7 +276,7 @@ class GridRouter:
|
||||
return _reconstruct(came_from, current)
|
||||
cx, cy, cl = current
|
||||
base = g_score[current]
|
||||
for dx, dy in _NEIGHBOURS:
|
||||
for dx, dy in self._neighbours:
|
||||
cell = (cx + dx, cy + dy)
|
||||
if not (0 <= cell[0] < self.cols and 0 <= cell[1] < self.rows):
|
||||
continue
|
||||
@@ -485,6 +489,7 @@ def route_board(
|
||||
via_cost: float = 10.0,
|
||||
rip_up: bool = True,
|
||||
max_passes: int = 10,
|
||||
orthogonal: bool = False,
|
||||
) -> RouteResult:
|
||||
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
|
||||
router = GridRouter(
|
||||
@@ -495,5 +500,6 @@ def route_board(
|
||||
via_cost=via_cost,
|
||||
rip_up=rip_up,
|
||||
max_passes=max_passes,
|
||||
orthogonal=orthogonal,
|
||||
)
|
||||
return router.route()
|
||||
|
||||
Reference in New Issue
Block a user