README: engines, flags and zones as they now are, and measured results

Recommend the exact engine for KiCad boards; document --edge-clearance,
--grid-resolution, --heuristic-weight and the unrouted report; list what is
read from a KiCad DSN (pads, vias, keepout kinds, outline, zones, fine
pitch), each checked against KiCad; replace the scope section with the
TerraHawk measurements and what still trails the JAR.
This commit is contained in:
2026-09-27 13:04:23 -06:00
parent 3253e6fa43
commit 6f3db3e0e3
+88 -56
View File
@@ -1,6 +1,6 @@
# freeroute # freeroute
A native Python PCB autorouter — a Java-free reimplementation of the A native Python PCB autorouter: a Java-free reimplementation of the
[FreeRouting](https://github.com/freerouting/freerouting) engine. Specctra DSN [FreeRouting](https://github.com/freerouting/freerouting) engine. Specctra DSN
in, Specctra SES out, no JVM anywhere in the pipeline. in, Specctra SES out, no JVM anywhere in the pipeline.
@@ -8,8 +8,8 @@ in, Specctra SES out, no JVM anywhere in the pipeline.
Toolchains that autoroute (`kicad-mcp` among them) do it by shelling out to Toolchains that autoroute (`kicad-mcp` among them) do it by shelling out to
`freerouting.jar`, which drags a JVM into every install and container image. `freerouting.jar`, which drags a JVM into every install and container image.
`freeroute` reimplements the routing engine in pure Python — no dependencies at `freeroute` reimplements the routing engine in pure Python, with no dependencies
all, standard library only — so the JAR step becomes a `pip install`. beyond the standard library, so the JAR step becomes a `pip install`.
## The contract ## The contract
@@ -21,7 +21,7 @@ board.dsn ──▶ freeroute ──▶ board.ses
``` ```
`kicad-cli` already exports `.dsn` and imports `.ses`, so `freeroute` drops in `kicad-cli` already exports `.dsn` and imports `.ses`, so `freeroute` drops in
wherever `java -jar freerouting.jar` was invoked — nothing else in the pipeline wherever `java -jar freerouting.jar` was invoked, and nothing else in the pipeline
changes. The `-de` / `-do` / `-mp` flags are the JAR's, on purpose. changes. The `-de` / `-do` / `-mp` flags are the JAR's, on purpose.
## Install ## Install
@@ -33,27 +33,31 @@ uv tool install freeroute # or: pipx install freeroute
## Usage ## Usage
```bash ```bash
freeroute board.dsn -o board.ses # grid engine, the default # a KiCad board: the exact engine, with the board's copper-to-edge rule
freeroute -de board.dsn -do board.ses # the FreeRouting JAR spelling freeroute board.dsn --engine exact --shove --edge-clearance 0.5 -do board.ses
freeroute -de board.dsn -do board.ses # the FreeRouting JAR spelling (grid engine)
freeroute board.dsn --engine room --pack --shove -do board.ses freeroute board.dsn --engine room --pack --shove -do board.ses
freeroute board.dsn --engine exact --diagonal --shove -do board.ses
``` ```
With no `-do` / `-o`, the SES goes to stdout. With no `-do` / `-o`, the SES goes to stdout. Connections freeroute could not
make are listed on stderr; the SES is still written and the exit code stays 0,
as with the JAR. Connections whose two ends sit in one zone of their net are
listed separately, as left to that zone's fill.
### Engines ### Engines
Three routing tracks, selected with `--engine`: Three routing tracks, selected with `--engine`:
- **`grid`** (default) — multi-layer maze router over a uniform occupancy grid, - **`exact`** (recommended for real boards): orthogonal grid search whose every
with rip-up-and-retry and via search. Highest raw connectivity of the three; accepted connection is verified against exact integer geometry. It drops a
its geometry is grid-quantised, so it is the least precise about clearance. connection rather than emit a violation. Defaults to `--grid-resolution 2`.
- **`exact`** — orthogonal router whose output is verified against exact integer - **`grid`** (the default, for JAR-compatible invocation): the same multi-layer
geometry (tile/octagon clearance, not bounding boxes). Cleaner, DRC-clean maze router with rip-up and via search, without the exact check, so its
output where it succeeds; it will drop a net rather than emit a violation. geometry is only as precise as the grid.
- **`room`** — continuous expansion-room router: an exact decomposition of free - **`room`**: continuous expansion-room router, an exact decomposition of free
space, so it routes off-grid channels that the grid quantisation cannot see. space. It routes off-grid channels the grid cannot see, but it is much slower
on real boards and does not yet use zones.
### Flags ### Flags
@@ -62,6 +66,9 @@ Three routing tracks, selected with `--engine`:
| `--engine {grid,exact,room}` | routing track (default: `grid`) | | `--engine {grid,exact,room}` | routing track (default: `grid`) |
| `-de`, `--design` | input `.dsn` (FreeRouting-compatible alias) | | `-de`, `--design` | input `.dsn` (FreeRouting-compatible alias) |
| `-do`, `--output` | output `.ses` (default: stdout) | | `-do`, `--output` | output `.ses` (default: stdout) |
| `--edge-clearance MM` | copper to board-edge clearance, e.g. KiCad's `min_copper_edge_clearance`, which the DSN does not carry (default 0: copper only has to stay inside the outline) |
| `--grid-resolution K` | grid cells K times finer than trace + clearance (default 2 for exact, 1 for grid); finer reaches fine-pitch pins at a cost in time |
| `--heuristic-weight W` | exact: weighted A*, routes at most W times the shortest (default 1) |
| `-mp`, `--max-passes` | rip-up-and-retry pass bound (default 10) | | `-mp`, `--max-passes` | rip-up-and-retry pass bound (default 10) |
| `--no-rip-up` | single greedy pass, no rip-up | | `--no-rip-up` | single greedy pass, no rip-up |
| `--layers` | comma-separated signal layer indices to route on | | `--layers` | comma-separated signal layer indices to route on |
@@ -74,65 +81,90 @@ unsupported combination is a hard error (exit 2) rather than a silent no-op:
| | `--pack` | `--shove` | `--diagonal` | `--no-rip-up` | `-mp` | | | `--pack` | `--shove` | `--diagonal` | `--no-rip-up` | `-mp` |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| **grid** | — | — | — | yes | yes | | **grid** | no | no | no | yes | yes |
| **exact** | — | yes | yes | yes | yes | | **exact** | no | yes | yes | yes | yes |
| **room** | yes | yes | — | — | — | | **room** | yes | yes | no | no | no |
`--layers` works on all three. `--layers` and `--edge-clearance` work on all three; `--grid-resolution` on grid
and exact.
What the opt-in passes buy you: What the opt-in passes buy you:
- **`--pack`** (room) — when several 2-pin nets have to cross the same obstacle - **`--pack`** (room): when several 2-pin nets have to cross the same obstacle
gap, they are assigned consistent parallel lanes and routed together, so a gap, they are assigned consistent parallel lanes and routed together.
channel physically wide enough for all of them stops dropping nets to greedy - **`--shove`** (exact, room): a net that would be dropped nudges the blocking
per-net gate collisions.
- **`--shove`** (exact, room) — a net that would be dropped nudges the blocking
committed traces aside and re-routes, rolling every moved trace back if the committed traces aside and re-routes, rolling every moved trace back if the
retry fails. On the room engine it also turns on occupancy-aware gate retry fails. It recovers *ordering* drops; it cannot make space that is not
placement. It recovers *ordering* drops; it cannot manufacture space that is there.
not there. - **`--diagonal`** (exact): a still-dropped 2-pin net is retried as a 45° trace
- **`--diagonal`** (exact) — a still-dropped 2-pin net is retried as a 45° trace covered by an exact integer octagon.
whose diagonal copper is covered by an exact integer octagon, so it fits
corridors the orthogonal L cannot, at the ideal hypotenuse length. ## What freeroute reads from a KiCad DSN
These were each checked against KiCad itself, pad by pad or with its DRC:
- **Pads** placed as KiCad places them: back-side parts on the mirrored copper
layer, outlines rotated with the part, oval pads at their full width, the
largest shape of a multi-layer padstack.
- **Vias** at the size of the DSN's via padstack.
- **Keepouts**: `keepout` blocks tracks and vias, `wire_keepout` (KiCad's "no
tracks" rule area) tracks only, `via_keepout` vias only; a rule area with a
hole (a no-track ring along the edge) is split into convex pieces rather than
filled in; footprint-level keepouts (a mounting hole's plated ring) are placed
with their part; `place_keepout` does not affect routing.
- **The real board outline**, rounded corners included, with `--edge-clearance`.
- **Copper zones** (`plane`): a pad inside a zone connects through it, and a
route can end on it with a via. A zone is only counted on to connect when a
no-tracks rule area covers it; a board-wide pour can be split into islands by
other nets' tracks, so its pads are routed with tracks instead.
- **Fine-pitch pins**: a finer grid, and exact escape stubs for pins the grid
cannot reach.
SES coordinates are written in resolution units, exactly as the JAR writes them.
## Library use ## Library use
```python ```python
from freeroute.route import route from freeroute.route import route
ses_text = route(dsn_text, engine="room", shove=True, pack=True) ses_text = route(dsn_text, engine="exact", shove=True, edge_clearance_mm=0.5)
``` ```
## Status and honest scope ## Status and honest scope
Alpha, version 0.1.0. It parses real `kicad-cli` DSN output, routes, and writes Alpha. Measured on two real KiCad 10 boards (the TerraHawk charge node's power
SES that KiCad imports. What is actually implemented: stage and sense board, 2026-09-27), with every track stripped, then routed, then
checked with `kicad-cli pcb drc --refill-zones`:
- Specctra DSN parser (structure, library, placement, network, rules, keepouts) | board | freeroute exact | FreeRouting JAR | DRC errors from freeroute |
and SES writer. |---|---|---|---|
- Multi-layer routing with via search and rip-up-and-retry. | power stage (40 x 30 mm) | 7 unconnected, ~5 s | 12 | 0 |
- Exact integer geometry (tiles, octagons) with a spatial index, so the `exact` | sense board (55 x 25 mm, 0.5 mm pitch MCU) | 41 unconnected, ~2.5 min | 32 | 0 |
and `room` engines verify their own output is clearance-clean.
- Continuous expansion-room free-space decomposition. In the board owner's production flow (hand-routed power lanes kept, pours added
- Channel packing, shove, and 45° routing as described above. after routing), freeroute completed the power stage with no unconnected items.
What it is **not**, stated plainly: What it is **not**, stated plainly:
- **Not at FreeRouting/JAR density parity on dense commercial boards.** The JAR - **Not yet at JAR completion on a dense board.** The legality of what it routes
remains the better router when the board is hard. `freeroute` is honest about holds up; how much it routes does not match the JAR yet. The remaining gap is
drops: it leaves a net unrouted rather than emit a DRC violation, so on a congestion: routes committed one at a time block later ones. The JAR's
crowded board expect fewer completed nets than the JAR would manage. negotiated rip-up loop handles that better. Negotiated congestion routing is
- **`--diagonal` is a shortening/recovery pass, not diagonal-native search.** the planned next step.
The maze search is orthogonal; 45° geometry is applied to nets the orthogonal - **Slower than the JAR** on a dense board (minutes against seconds), being
pass dropped or to shorten a staircase. A true 45°-native expansion-room maze pure Python.
is not implemented. - **`--diagonal` is a recovery pass, not diagonal-native search.**
- No fanout pass, no post-route optimiser, no interactive/incremental routing, - The `room` engine does not use zones, the outline edge rule is only enforced
no net classes with per-class widths beyond the default rules, no plane/pour approximately there, and it is slow on real boards.
handling. - No fanout pass, no post-route optimiser, no incremental routing, no per-class
track widths beyond the default rules.
- Copper that KiCad draws as graphics (a net tie's polygon, a logo) is not in
the DSN at all, so no router sees it; mckicad adds footprint copper to the DSN
as keepouts.
If you need maximum completion on a dense board today, use the JAR. If you need For maximum completion on a dense board today, the JAR is still the stronger
routing without a JVM — CI, containers, an MCP server, a library call — this is router. For routing without a JVM (CI, containers, an MCP server, a library
that. call), freeroute routes legally and says what it could not do.
## Development ## Development
@@ -144,7 +176,7 @@ uv run ruff check src/ tests/
The `oracle`-marked tests run the real `freerouting.jar` as a differential The `oracle`-marked tests run the real `freerouting.jar` as a differential
reference and skip cleanly when Java or the JAR is absent. The JAR is never reference and skip cleanly when Java or the JAR is absent. The JAR is never
vendored, and neither is the upstream Java source — see vendored, and neither is the upstream Java source; see
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the port map and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the port map and
[`docs/PORTING_PLAN.md`](docs/PORTING_PLAN.md) for the roadmap. [`docs/PORTING_PLAN.md`](docs/PORTING_PLAN.md) for the roadmap.