- 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.