Greedy per-net room routing sends each net through a shared obstacle gap on its own independently chosen path; the paths collide and some nets drop even when the gap is physically wide enough for all of them. shove cannot fix this -- it relocates blockers into existing space, it does not pack lanes. Add an opt-in pre-pass (pack=True, independent of shove) that groups 2-pin nets which must cross a common gap, assigns each a parallel lane across the gap's usable width (pad-ordered so lanes never cross, spaced >= width + clearance) with staggered fan-in trunks so the converge/diverge jogs never overlap, and realizes each as an orthogonal trace verified exactly DRC-clean. Packing is all-or-nothing per group, so it never leaves a channel worse than greedy. With pack=False the pre-pass is skipped and the output is unchanged. Scope: single-layer 2-pin nets through one axis-aligned gap, with enough fan depth to stack the trunks. Threaded through route_dsn_board_rooms / build_rooms_routing_result / route(engine="room").
freeroute
A native Python PCB autorouter — a Java-free port of the FreeRouting engine.
Why
kicad-mcp currently autoroutes by shelling out to freerouting.jar, which
drags in a JVM dependency. freeroute reimplements the routing engine in
pure Python so the toolchain needs no Java at all.
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 is a
drop-in replacement for the JAR step — nothing else in the pipeline changes.
Status
Pre-alpha. Under active development. See docs/PORTING_PLAN.md
for the architecture and porting roadmap.
Usage (target)
freeroute board.dsn -o board.ses
Development
uv sync
uv run pytest
uv run ruff check src/ tests/
License
GPL-3.0-or-later, matching the FreeRouting source it is ported from.
Languages
Python
100%