Harden packaging for a public release
Add the GPLv3 text (the package declared the licence but never shipped it), a py.typed marker, and an sdist include/exclude allowlist so the distribution carries only src/, docs/, README, LICENSE and pyproject. reference/ is a clone of the GPL FreeRouting Java tree kept purely as a porting reference and must never be redistributed inside this package; tests, caches and build output are excluded too. Switch to the PEP 639 SPDX licence expression with license-files, widen the classifiers, and add an -o alias for -do. Rewrite the README around the engine matrix: what each track and pass actually does, and what this is not (no FreeRouting density parity on dense boards, and --diagonal is a recovery and shortening pass, not a diagonal-native search).
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
# freeroute
|
||||
|
||||
A native Python PCB autorouter — a Java-free port of the
|
||||
[FreeRouting](https://github.com/freerouting/freerouting) engine.
|
||||
A native Python PCB autorouter — a Java-free reimplementation of the
|
||||
[FreeRouting](https://github.com/freerouting/freerouting) engine. Specctra DSN
|
||||
in, Specctra SES out, no JVM anywhere in the pipeline.
|
||||
|
||||
## 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.
|
||||
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
|
||||
|
||||
@@ -18,28 +20,136 @@ 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.
|
||||
`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.
|
||||
|
||||
## Status
|
||||
|
||||
Pre-alpha. Under active development. See [`docs/PORTING_PLAN.md`](docs/PORTING_PLAN.md)
|
||||
for the architecture and porting roadmap.
|
||||
|
||||
## Usage (target)
|
||||
## Install
|
||||
|
||||
```bash
|
||||
freeroute board.dsn -o board.ses
|
||||
uv tool install freeroute # or: pipx install freeroute
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run pytest
|
||||
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`](docs/ARCHITECTURE.md) for the port map and
|
||||
[`docs/PORTING_PLAN.md`](docs/PORTING_PLAN.md) for the roadmap.
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0-or-later, matching the FreeRouting source it is ported from.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user