rsp2k 83ad0f8ef7 Keep a route's own vias apart while it is searched
Committed vias were checked for hole spacing, but a route's own vias are
not committed until it is done, so a route hopping layers (more of them
on 6 layers) could put two of its vias 0.41 mm apart, as th-eda's
6-layer flight controller showed. A via is now also checked against
layer changes earlier on its own route.
2026-09-29 03:10:52 -06:00
2026-07-13 18:17:13 -06:00

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, with no dependencies beyond the standard library, 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, and 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

# a KiCad board: the exact engine, with the board's copper-to-edge rule
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

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

Three routing tracks, selected with --engine:

  • exact (recommended for real boards): orthogonal grid search whose every accepted connection is verified against exact integer geometry. It drops a connection rather than emit a violation. Defaults to --grid-resolution 2.
  • grid (the default, for JAR-compatible invocation): the same multi-layer maze router with rip-up and via search, without the exact check, so its geometry is only as precise as the grid.
  • room: continuous expansion-room router, an exact decomposition of free 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

flag effect
--engine {grid,exact,room} routing track (default: grid)
-de, --design input .dsn (FreeRouting-compatible alias)
-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)
--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 no no no yes yes
exact no yes yes yes yes
room yes yes no no no

--layers and --edge-clearance work on all three; --grid-resolution on grid and exact.

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.
  • --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. It recovers ordering drops; it cannot make space that is not there.
  • --diagonal (exact): a still-dropped 2-pin net is retried as a 45° trace covered by an exact integer octagon.

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

from freeroute.route import route

ses_text = route(dsn_text, engine="exact", shove=True, edge_clearance_mm=0.5)

Status and honest scope

Alpha. Measured on two real KiCad 10 boards (the TerraHawk charge node's power stage and sense board, 2026-09-27), with every track stripped, then routed, then checked with kicad-cli pcb drc --refill-zones:

board freeroute exact FreeRouting JAR DRC errors from freeroute
power stage (40 x 30 mm) 7 unconnected, ~5 s 12 0
sense board (55 x 25 mm, 0.5 mm pitch MCU) 41 unconnected, ~2.5 min 32 0

In the board owner's production flow (hand-routed power lanes kept, pours added after routing), freeroute completed the power stage with no unconnected items.

What it is not, stated plainly:

  • Not yet at JAR completion on a dense board. The legality of what it routes holds up; how much it routes does not match the JAR yet. The remaining gap is congestion: routes committed one at a time block later ones. The JAR's negotiated rip-up loop handles that better. Negotiated congestion routing is the planned next step.
  • Slower than the JAR on a dense board (minutes against seconds), being pure Python.
  • --diagonal is a recovery pass, not diagonal-native search.
  • The room engine does not use zones, the outline edge rule is only enforced approximately there, and it is slow on real boards.
  • 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.

For maximum completion on a dense board today, the JAR is still the stronger router. For routing without a JVM (CI, containers, an MCP server, a library call), freeroute routes legally and says what it could not do.

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.

S
Description
Native Python PCB autorouter: negotiated congestion, tree routing, exact checks (Specctra DSN in, SES out). Formerly freeroute.
Readme GPL-3.0
886 KiB
Languages
Python 99.6%
Shell 0.4%