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:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user