Ryan Malloy fd3bee1085 Add CLAUDE.md: invariants, architecture, and hard-won lessons
Captures the three non-negotiable invariants (DRC-clean output, exact integer
geometry, determinism), the engine matrix, the reference/ GPL rule, the JAR
oracle strategy, and the lessons that cost real debugging: KiCad's /*52 net
names, exact clearance being load-bearing, shove not being a densifier, and the
falsifiable-fixture bar for any density claim.

Force-added past the global ignore: this is a private repo and the file is
excluded from the sdist, so it never reaches PyPI.
2026-07-14 07:28:46 -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 — 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 exact and room engines 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. freeroute is 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.
  • --diagonal is 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.

Description
Native Python PCB autorouter — a Java-free port of the FreeRouting engine (Specctra DSN in, SES out)
Readme GPL-3.0 391 KiB
Languages
Python 100%