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
A native Python PCB autorouter — a Java-free reimplementation of the FreeRouting engine. Specctra DSN in, Specctra SES out, no JVM anywhere in the pipeline.
Why
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.
freeroute reimplements the routing engine in pure Python — no dependencies at
all, standard library only — so the JAR step becomes a pip install.
The contract
The entire integration surface is two Specctra files:
board.dsn ──▶ freeroute ──▶ board.ses
(unrouted) (routed session: traces + vias)
kicad-cli already exports .dsn and imports .ses, so freeroute drops in
wherever java -jar freerouting.jar was invoked — nothing else in the pipeline
changes. The -de / -do / -mp flags are the JAR's, on purpose.
Install
uv tool install freeroute # or: pipx install freeroute
Usage
freeroute board.dsn -o board.ses # grid engine, the default
freeroute -de board.dsn -do board.ses # the FreeRouting JAR spelling
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.
Engines
Three routing tracks, selected with --engine:
grid(default) — multi-layer maze router over a uniform occupancy grid, with rip-up-and-retry and via search. Highest raw connectivity of the three; its geometry is grid-quantised, so it is the least precise about clearance.exact— orthogonal router whose output is verified against exact integer geometry (tile/octagon clearance, not bounding boxes). Cleaner, DRC-clean output where it succeeds; it will drop a net rather than emit a violation.room— continuous expansion-room router: an exact decomposition of free space, so it routes off-grid channels that the grid quantisation cannot see.
Flags
| flag | effect |
|---|---|
--engine {grid,exact,room} |
routing track (default: grid) |
-de, --design |
input .dsn (FreeRouting-compatible alias) |
-do, --output |
output .ses (default: stdout) |
-mp, --max-passes |
rip-up-and-retry pass bound (default 10) |
--no-rip-up |
single greedy pass, no rip-up |
--layers |
comma-separated signal layer indices to route on |
--pack |
coordinated multi-trace channel packing |
--shove |
move committed traces aside to recover dropped nets |
--diagonal |
45° recovery pass for dropped 2-pin nets |
Not every engine implements every pass. This is the supported matrix, and an unsupported combination is a hard error (exit 2) rather than a silent no-op:
--pack |
--shove |
--diagonal |
--no-rip-up |
-mp |
|
|---|---|---|---|---|---|
| grid | — | — | — | yes | yes |
| exact | — | yes | yes | yes | yes |
| room | yes | yes | — | — | — |
--layers works on all three.
What the opt-in passes buy you:
--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 channel physically wide enough for all of them stops dropping nets to greedy 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 retry fails. On the room engine it also turns on occupancy-aware gate placement. It recovers ordering drops; it cannot manufacture space that is not there.--diagonal(exact) — a still-dropped 2-pin net is retried as a 45° trace whose diagonal copper is covered by an exact integer octagon, so it fits corridors the orthogonal L cannot, at the ideal hypotenuse length.
Library use
from freeroute.route import route
ses_text = route(dsn_text, engine="room", shove=True, pack=True)
Status and honest scope
Alpha, version 0.1.0. It parses real kicad-cli DSN output, routes, and writes
SES that KiCad imports. What is actually implemented:
- Specctra DSN parser (structure, library, placement, network, rules, keepouts) and SES writer.
- Multi-layer routing with via search and rip-up-and-retry.
- Exact integer geometry (tiles, octagons) with a spatial index, so the
exactandroomengines verify their own output is clearance-clean. - Continuous expansion-room free-space decomposition.
- Channel packing, shove, and 45° routing as described above.
What it is not, stated plainly:
- Not at FreeRouting/JAR density parity on dense commercial boards. The JAR
remains the better router when the board is hard.
freerouteis honest about drops: it leaves a net unrouted rather than emit a DRC violation, so on a crowded board expect fewer completed nets than the JAR would manage. --diagonalis a shortening/recovery pass, not diagonal-native search. The maze search is orthogonal; 45° geometry is applied to nets the orthogonal pass dropped or to shorten a staircase. A true 45°-native expansion-room maze is not implemented.- No fanout pass, no post-route optimiser, no interactive/incremental routing, no net classes with per-class widths beyond the default rules, no plane/pour handling.
If you need maximum completion on a dense board today, use the JAR. If you need routing without a JVM — CI, containers, an MCP server, a library call — this is that.
Development
uv sync
uv run pytest -m "not oracle" # the suite; oracle tests need Java + the JAR
uv run ruff check src/ tests/
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
vendored, and neither is the upstream Java source — see
docs/ARCHITECTURE.md for the port map and
docs/PORTING_PLAN.md for the roadmap.
License
GPL-3.0-or-later, matching the FreeRouting source it is ported from. No upstream Java code is distributed with this package; the reference clone used during porting is git-ignored and excluded from the sdist.