Add shove-and-retry to the exact router

Before a dropped net is abandoned, the exact router (with shove=True)
tries to make room by moving an existing trace aside instead: it forms a
straight orthogonal candidate for the dropped net, finds the axis-aligned
trace segments that cross it, and shoves each one perpendicular (via
shove_segment) far enough to restore clearance. A shove is accepted only
if the moved trace still connects its pads, clears every other item
exactly against the ShapeSearchTree, and stays inside the board outline,
so DRC-cleanliness is preserved by construction. Shoves are bounded
(max_shove_depth, per-trace shove_cap) and deterministic. The dropped set
now includes nets the grid failed to route, not only exact-clearance
rejections, so shove can recover them.

The pipeline exposes shove (and rip_up) through route_dsn_board_exact /
build_exact_routing_result / route(engine='exact', shove=True); default
off, so the grid track and the no-shove exact track are unchanged.
This commit is contained in:
2026-07-13 02:43:19 -06:00
parent dffa54a43a
commit f581f5b1e2
2 changed files with 345 additions and 40 deletions
+21 -7
View File
@@ -104,10 +104,17 @@ def build_routing_result(
def route_dsn_board_exact(
dsn: DsnBoard, *, layers: list[int] | None = None, max_passes: int = 10
dsn: DsnBoard,
*,
layers: list[int] | None = None,
max_passes: int = 10,
rip_up: bool = True,
shove: bool = False,
) -> 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."""
DRC-verified) track; return the exact result, the scale, and layer names.
``shove`` enables moving existing traces aside to recover dropped nets;
``rip_up`` toggles the underlying grid's rip-up-and-retry."""
board = build_board(dsn)
scale = max(dsn.resolution.value, 1)
width_board = round(_rule_width_dsn(dsn) * scale)
@@ -118,27 +125,34 @@ def route_dsn_board_exact(
clearance=clearance_board,
layers=layers,
max_passes=max_passes,
rip_up=rip_up,
shove=shove,
)
return exact, scale, [layer.name for layer in dsn.layers]
def build_exact_routing_result(dsn: DsnBoard, *, layers: list[int] | None = None) -> RoutingResult:
def build_exact_routing_result(
dsn: DsnBoard, *, layers: list[int] | None = None, shove: bool = False
) -> RoutingResult:
"""Route ``dsn`` (exact track) and convert to a DSN-unit RoutingResult."""
exact, scale, layer_names = route_dsn_board_exact(dsn, layers=layers)
exact, scale, layer_names = route_dsn_board_exact(dsn, layers=layers, shove=shove)
return _to_routing_result(exact.result, dsn, scale, layer_names)
def route(dsn_text: str, *, layers: list[int] | None = None, engine: str = "grid") -> str:
def route(
dsn_text: str, *, layers: list[int] | None = None, engine: str = "grid", shove: bool = False
) -> 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).
it succeeds). ``shove`` (exact engine only) tries moving existing traces
aside to recover dropped nets.
"""
dsn = parse_dsn(dsn_text)
if engine == "exact":
result = build_exact_routing_result(dsn, layers=layers)
result = build_exact_routing_result(dsn, layers=layers, shove=shove)
else:
result = build_routing_result(dsn, layers=layers)
return write_ses(dsn, result)