Both upgrades sit behind an opt-in shove=False flag, so the default code
path is byte-for-byte the foundation router (existing room tests are
unchanged by construction).
With shove=True:
- Gate placement becomes occupancy-aware: each edge gate is projected
into the largest other-net-free sub-span of the door, with the reserved
intervals read straight from the search tree. With margin = clearance +
half_width the room decomposition already keeps doors clearance-clear,
so this reduces exactly to the old midpoint clamp where no other-net
copper crosses the door.
- When a net would be dropped, a transactional shove-in-rooms recovery
nudges the committed trace(s) blocking it aside with the exact shove
primitive and re-verifies against the clearance oracle, so both nets fit
through one channel. Every moved trace is journaled and rolled back to
its exact original tiles and wires on any failure -- the room engine
mutates already-committed nets, so the rollback is real rather than the
exact track's no-op.
The board-wide has_violation(clearance) gate stays the final assertion.
Thread the flag through pipeline.route_dsn_board_rooms /
build_rooms_routing_result / route(engine='room', shove=...).
A third engine (engine='room') that replaces the fixed routing grid with
FreeRouting's free-space rooms. Per layer, the space not occupied by items
(inflated by the routing clearance) is decomposed into exact rectangles via
coordinate compression (the orthogonal analogue of
CompleteFreeSpaceExpansionRoom); adjacent rooms share an edge door, and a via
door joins overlapping rooms on two layers. The maze search is an A* over
rooms-through-doors (cost = geometric distance + via cost), so a path may
cross a door anywhere along its width rather than at a quantized cell. The
found room sequence is realized into an exact orthogonal Polyline through the
door mid-gates and verified DRC-clean against the ShapeSearchTree, dropping
the net if it cannot be made exactly clean.
Maps to autoroute/: ExpansionRoom / CompleteFreeSpaceExpansionRoom (rooms),
ExpansionDoor / TargetItemExpansionDoor (doors), MazeSearchAlgo /
AutorouteEngine (search), LocateFoundConnectionAlgo (realization).
Foundation scope: rooms are axis-aligned rectangles (not general Simplex
tiles); realization uses an L-connector through door mid-gates. Any-angle
rooms, optimal gate placement (true sub-cell shove), and optimization passes
are follow-ups. The grid and exact tracks are unchanged; this is opt-in.
Routes simple/crossing (with a via) and all four multi-pin nets of the real
KiCad board, DRC-clean.
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.
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'.
Restructures the router so occupancy is tracked per connection, then adds
a rip-up-and-retry loop that recovers from bad greedy net orderings.
When a connection cannot reach its target through free space, a rip-up
search may pass through other nets' traces at an escalating penalty; the
router rips up the connections that path crosses (fully removing their
occupancy), routes the failing connection, and re-queues the ripped
connections. Passes iterate up to max_passes, keeping the best
(fewest-unrouted) result and stopping on full success or when a pass
changes nothing.
Thrash prevention: each connection may be ripped at most rip_cap times,
and the rip penalty escalates with a connection's rip count, so
repeatedly-ripped connections harden into walls. Fully deterministic
(sorted rip sets, connection-creation order, tuple-keyed A* — no RNG).
route()/route_dsn_board()/route_board() take rip_up and max_passes;
rip_up=False reproduces the previous greedy single-pass behaviour for
comparison. RouteResult now reports the unrouted connection count.
Extends the grid router with a layer axis: A* nodes are (col, row, layer),
in-plane moves stay on a layer, and a via move transitions between layers
at a cell for a configurable via_cost (so the router prefers one layer but
changes layers to get through). Occupancy is tracked per (cell, layer);
a through via must be clear on every signal layer and then blocks all of
them for other nets.
RouteResult now carries per-layer wire segments and per-net via locations.
The pipeline emits each segment on its layer and each via as
(via <padstack> x y), using the DSN's via padstack; wire and via
coordinates are converted from board units back to DSN units. route() and
route_dsn_board() take an optional layers= to restrict routing (e.g. a
single layer) for comparison.
Implements a working autorouter and the Java-free replacement for the
freerouting.jar step: dsn_text -> parse_dsn -> build_board -> route ->
write_ses.
GridRouter is a single-layer A* maze search over a uniform occupancy
grid: a cell is blocked by another net's pad (inflated by clearance +
half trace width) or a keepout; each net's ratsnest is connected pin to
pin; a routed trace then blocks other nets. The cell path becomes a
trace polyline whose endpoints are the exact pin locations. Board-unit
paths are converted back to DSN units for the SES (wire (path ...))
scopes.
This is an MVP, not a port of FreeRouting's expansion-room maze:
free-space rooms, rip-up-and-retry, multi-layer via search, and shove
are deferred (they raise quality/coverage, not the connectivity
milestone). It reaches connectivity on boards whose nets route on one
layer without crossing.
Adds tests/dsn/fixtures/simple_2net.dsn (a guaranteed-routable 2-net
board). Invariant tests: both nets route, the emitted SES parses, every
trace stays on a valid layer and within the board outline, and every
routed net's trace endpoints sit exactly on its two pads. An oracle-gated
test asserts connectivity parity with the reference FreeRouting JAR on
the simple board.