Tracks and vias already on the board were ignored, so other nets could
be routed straight through them (the flight controller's 82 GND
stitching vias and its guard tracks). They are now copper of their net:
other nets keep a pad's distance from them in the grid, the exact check
and the room engine, and they add no connections to route.
- The exact engine verified copper per net and dropped a whole net on any
conflict: a 25-pin +3V3 net lost every connection to one bad segment.
Verify and accept per ratsnest connection instead.
- Pad and keepout cells were rounded outward, blocking cells up to half a
step clear of the copper; 62 of the 64 pins of a 0.5 mm pitch LQFP could
not start a route. Block only cells whose centre is inside the clearance
zone, and let a net walk along inside its own pad, which adds no copper.
- Routing was bounded by the outline's bounding box, so tracks cut across
rounded corners. Keep the DSN boundary as a polygon; the grid blocks cells
too close to it (scanline plus per-edge, precomputed for vias) and the
exact and room engines reject copper that is.
- --edge-clearance MM sets copper-to-edge clearance, e.g. KiCad's
min_copper_edge_clearance, which the DSN does not carry. Default 0.
On the TerraHawk rc4 power stage the exact engine now matches the
FreeRouting JAR: same open count, no DRC errors, with a 0.5 mm edge rule.
Measured against KiCad's own pads on the TerraHawk rc4 power stage:
- Back-side parts kept their pads on F.Cu (32 pads). Mirror the copper
layer for a back-side part.
- Pad outlines were copied unrotated (29 pads on rotated parts). Place the
outline through the same chain as the pin: pin rotation, back-side
mirror, part rotation. Oval pads, a path with a width, came out as a
zero-height sliver; pad them out by the aperture.
- Multi-shape padstacks used the first shape; use the largest.
- Footprint-level keepouts were parsed but never placed. KiCad exports a
mounting hole's plated ring that way, so the router could not see it.
- Vias were sized like a trace (0.25 mm square for a 0.6 mm via), and every
remaining short and clearance error was a via. Read the size from the
DSN's via padstack and use it in all three engines; the grid engine now
reserves the cells a via's copper reaches.
With these the exact and room engines route that board with no shorts,
clearance or other DRC errors. Pins now carry their component reference.
Greedy per-net room routing sends each net through a shared obstacle gap on its
own independently chosen path; the paths collide and some nets drop even when the
gap is physically wide enough for all of them. shove cannot fix this -- it
relocates blockers into existing space, it does not pack lanes.
Add an opt-in pre-pass (pack=True, independent of shove) that groups 2-pin nets
which must cross a common gap, assigns each a parallel lane across the gap's
usable width (pad-ordered so lanes never cross, spaced >= width + clearance) with
staggered fan-in trunks so the converge/diverge jogs never overlap, and realizes
each as an orthogonal trace verified exactly DRC-clean. Packing is all-or-nothing
per group, so it never leaves a channel worse than greedy. With pack=False the
pre-pass is skipped and the output is unchanged.
Scope: single-layer 2-pin nets through one axis-aligned gap, with enough fan
depth to stack the trunks. Threaded through route_dsn_board_rooms /
build_rooms_routing_result / route(engine="room").
_shove_recover_room clobbered a net's _Accepted with a fresh one whose
wire_owners held only the just-recovered wire, while its wires list (the
shared result.wires[net_no]) already held earlier committed connections.
That left wire_owners shorter than the parallel wires list, so a later
net's shove either removed the wrong wire's copper from the search tree
(a silent different-net short: copper stayed in the emitted SES but
vanished from the tree, and has_violation falsely certified it clean) or
raised IndexError and aborted the whole route.
Reuse the net's existing _Accepted on recovery so wire_owners stays
parallel to wires. Fixes both the silent DRC violation and the crash
sibling without touching the DRC-clean or determinism invariants.
Also correct the overstated "density win" framing of the shove_channel
fixture: its plain-routing net drop is an artifact of greedy net order,
not a density limit (the same board routes both nets with zero shoves
under a reorder or with the cosmetic seals removed). Reframed the test
and docstring; the genuine sub-cell win remains the wide_door occupancy
packing (shoves == 0).
Regression tests assert the invariant against the emitted output
directly (reconstructed copper tiles), not just the tree that the bug
fooled, plus determinism, no-crash, and the ordering-artifact bound.
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.