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:
2026-07-13 02:16:23 -06:00
parent b200ecf6f8
commit 0bee385e71
5 changed files with 356 additions and 11 deletions
+48 -9
View File
@@ -16,6 +16,7 @@ from freeroute.board import build_board
from freeroute.dsn import DsnBoard, parse_dsn
from freeroute.ses import RoutedVia, RoutedWire, RoutingResult, write_ses
from .exact_router import ExactRouteResult, route_board_exact
from .grid_router import RouteResult, route_board
__all__ = ["route", "route_dsn_board", "build_routing_result"]
@@ -69,12 +70,8 @@ def route_dsn_board(
return result, scale, [layer.name for layer in dsn.layers]
def build_routing_result(
dsn: DsnBoard, *, layers: list[int] | None = None, rip_up: bool = True
) -> RoutingResult:
"""Route ``dsn`` and convert the board-unit paths + vias to a DSN-unit
:class:`~freeroute.ses.RoutingResult` for SES emission."""
route, scale, layer_names = route_dsn_board(dsn, layers=layers, rip_up=rip_up)
def _to_routing_result(route, dsn: DsnBoard, scale: int, layer_names: list[str]) -> RoutingResult:
"""Convert a board-unit :class:`RouteResult` to a DSN-unit RoutingResult."""
width_dsn = _rule_width_dsn(dsn)
via_name = _via_padstack(dsn)
# net_number is assigned in DSN order by build_board, so index i -> number i+1
@@ -98,8 +95,50 @@ def build_routing_result(
return result
def route(dsn_text: str, *, layers: list[int] | None = None) -> str:
"""Route a Specctra DSN string and return the routed SES string."""
def build_routing_result(
dsn: DsnBoard, *, layers: list[int] | None = None, rip_up: bool = True
) -> RoutingResult:
"""Route ``dsn`` (grid track) and convert to a DSN-unit RoutingResult."""
route, scale, layer_names = route_dsn_board(dsn, layers=layers, rip_up=rip_up)
return _to_routing_result(route, dsn, scale, layer_names)
def route_dsn_board_exact(
dsn: DsnBoard, *, layers: list[int] | None = None, max_passes: int = 10
) -> tuple[ExactRouteResult, int, list[str]]:
"""Route a parsed :class:`DsnBoard` with the exact-geometry (orthogonal,
DRC-verified) track; return the exact result, the scale, and layer names."""
board = build_board(dsn)
scale = max(dsn.resolution.value, 1)
width_board = round(_rule_width_dsn(dsn) * scale)
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
exact = route_board_exact(
board,
trace_width=width_board,
clearance=clearance_board,
layers=layers,
max_passes=max_passes,
)
return exact, scale, [layer.name for layer in dsn.layers]
def build_exact_routing_result(dsn: DsnBoard, *, layers: list[int] | None = None) -> RoutingResult:
"""Route ``dsn`` (exact track) and convert to a DSN-unit RoutingResult."""
exact, scale, layer_names = route_dsn_board_exact(dsn, layers=layers)
return _to_routing_result(exact.result, dsn, scale, layer_names)
def route(dsn_text: str, *, layers: list[int] | None = None, engine: str = "grid") -> str:
"""Route a Specctra DSN string and return the routed SES string.
``engine`` selects the routing track: ``"grid"`` (default; the multi-layer
rip-up grid MVP — highest coverage, kept for backward compatibility) or
``"exact"`` (orthogonal, exact-geometry, DRC-verified — cleaner output where
it succeeds, but lower coverage on dense boards).
"""
dsn = parse_dsn(dsn_text)
result = build_routing_result(dsn, layers=layers)
if engine == "exact":
result = build_exact_routing_result(dsn, layers=layers)
else:
result = build_routing_result(dsn, layers=layers)
return write_ses(dsn, result)