A route may not leave a cell inside a wire keepout as a wire, nor reach
a pin there by one: it changes layer instead, and the via goes in the
pad. A pin whose every stub enters a keepout is reached by a via only.
A structure keepout named rule_area... (mckicad names KiCad rule areas
so) is checked for overlap only, as KiCad's DRC does, with the free ends
of each wire tested for their round caps.
--access-cost C makes a route pay C to pass where it would close another
pin's way out (its end cells, the via it may need there, and the
clearance around both) while that pin still has connections to route.
A pin inside a wire keepout can only leave by a via; its stub to the
cell centre was track inside the keepout, which the exact check refused.
The via now goes on the pin itself. Pin stubs are also checked exactly
against wire keepouts, as the exact engine will check them.
bench/diagnose.py reports what each refused connection collided with.
Each round routes every connection that has failed so far ahead of the
rest, which keep their order. The promoted set only grows, so two groups
cannot take turns failing; the best round is kept.
The router is deterministic, but its result depends heavily on which
connection routes first. A seed shuffles that order reproducibly, so a
change can be judged over several orders instead of the one it happened
to get.
freeroute exited 0 and said nothing when it dropped connections, which
cost real diagnosis time: on rc4 two simple two-pin nets came back with no
copper and no explanation. The grid now records each connection it could
not route, the exact check adds each it rejects, and the CLI lists them on
stderr (the SES is still written; the exit code stays 0, as the JAR's).
A connection whose ends both sit in one zone of its net is listed apart,
as left to the zone fill: freeroute does not route through a zone it
cannot trust to be solid, but KiCad's fill often joins those pads anyway.
freeroute ignored the DSN's planes (KiCad's zones), so it never knew that
pads in a pour are joined by it. On the rc4 power stage those pads sit
inside "keep router off" areas as well, and every connection to them was
dropped without a word.
- Read (plane <net> <shape>) and put each zone on the board as a
ConductionArea of its net. Zones are not obstacles: KiCad refills them
around other nets' copper.
- The grid groups each net's pads and zone pieces that copper already
joins (a pad in a zone, two zones sharing a pad) and routes only between
groups. A connection may end on a zone by a via inside its copper, or by
a track where tracks are allowed.
- wire_keepout now blocks traces only, as KiCad's "no tracks" rule area
does; via_keepout only vias; keepout both, in the grid and in the exact
verifier.
rc4 power stage, exact engine, resolution 2: 2 unconnected, down from 8
(FreeRouting JAR: 12), no DRC errors.
The grid search estimated the remaining distance as a straight line even
when only orthogonal steps exist; Manhattan is the tighter estimate those
moves admit and still never overestimates. --heuristic-weight W (default
1) makes it weighted A*: routes at most W times the shortest (tested).
A cell was exactly one trace plus its clearance, so a trace owning its cell
was the whole clearance model, and the 0.4 mm grid could not reach most
pins of a 0.5 mm pitch package: between two 0.3 mm pads a trace's centre
has a legal band only 0.15 mm wide. Resolution K makes cells K times
finer and has every trace claim a halo, the cells whose centre is nearer
than trace + clearance to one of its own, released again on rip-up.
K = 1 has an empty halo and is the old grid exactly (tested). On a 0.5 mm
row, K = 4 gets every pin out where K = 1 gets two. On the rc4 boards, K = 2
cuts the sense board's opens from 66 to 41 (JAR: 32) and the power board's
to 7 (JAR: 12), still with no DRC errors from freeroute's own copper.
- 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.
Add diagonal=True to the exact router: after orthogonal routing, each 2-pin net
is retried as the shortest exactly-clean octilinear (0/45/90/135-degree) trace.
A dropped net is recovered; an orthogonal route is replaced only when the
diagonal is strictly shorter. Candidates are the direct 45-degree segment or the
two diagonal-plus-axis two-benders; each segment's diagonal copper is covered by
the exact octagon and its clearance checked with clearance_conflict_shape, so a
trace fits a diagonal corridor the bounding-box cover would reject.
Endpoints stay on the pads; diagonal copper is stored with its exact octagon so
later nets clear it precisely; same-net copper is ignored during the check so the
candidate is verified against every other net before the orthogonal copper is
ripped. With diagonal=False the pass is skipped and output is byte-for-byte the
orthogonal router. Threaded through route_dsn_board_exact /
build_exact_routing_result / route(engine="exact").
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'.