# Upstream FreeRouting architecture — a port map This maps the parts of the FreeRouting Java source (cloned into the gitignored `reference/freerouting/`) that matter for a Java-free Python port. It was written by reading the actual source, not the Specctra spec. Package paths here are verified against the clone (commit fetched via `--depth 1` on 2026-07-11) and **differ from the seed plan's guesses** — the DSN/SES code lives under `io/specctra/`, not `designforms/specctra/`. Java root in the clone: `src/main/java/app/freerouting/`. ## 1. Specctra DSN / SES I/O — `io/specctra/` This is the I/O boundary the port must match: `kicad-cli` produces the `.dsn` input and consumes the `.ses` output. ### Entry points (`io/specctra/`) - `DsnReader.java` (294 lines) — the modern read entry point. `readBoard(stream,…)` validates the `(pcb ` header with a literal three-token check (`(` , `pcb` scope keyword, name string), then calls `Keyword.PCB_SCOPE.read_scope(par)` to parse the body. `readMetadata(stream)` is a fast path that parses only `parser`/`resolution`/`structure` and stops. Returns a typed `BoardReadResult` (Success / OutlineMissing / ParseError / IoError). Defaults captured here: it flips the scanner to `NAME` state to read the pcb name cleanly. - `SesReader.java` (392) / `SesWriter.java` (430) — session-file read/write. - `RulesReader.java` / `RulesWriter.java` — `.rules` sidecar files. - `DsnWriter.java` (97) — DSN writer entry. ### Tokenizer + scope readers (`io/specctra/parser/`) The lexer is **JFlex-generated** — `SpecctraDsnStreamReader.java` (1467 lines) is a packed DFA; do **not** port the DFA. The human-readable grammar is in `SpecctraFileDescription.flex` (312 lines) and that is what to reimplement. Key lexical facts extracted from the flex file: - Whitespace: `\r \n \f \t space`. Comments: `#…` to EOL, and `/* … */`. - Two quote chars: `"` (STRING1) and `'` (STRING2). No escaping — inside a quoted string `\` is a literal backslash; the matching quote ends it. - Keywords are matched case-insensitively (`%ignorecase`) and returned as singleton `Keyword` objects; `(` → `OPEN_BRACKET`, `)` → `CLOSED_BRACKET`. - Non-keyword atoms are returned as `Integer`, `Double`, or `String`. - **Lexical states are the tricky part.** Several keywords call `yybegin(NAME)` (or `LAYER_NAME`, `COMPONENT_NAME`, `SPEC_CHAR`, `IGNORE_QUOTE`) so the *next* token is forced to be read as a string even when it looks like a number (a net or layer literally named `0`). The Python port reconciles this by keeping every unquoted atom as a token that preserves both its raw text and a best-effort numeric value, and letting each scope reader decide which to use — exactly the decision the lexical state encodes. - High-level scanner helpers (bottom of `SpecctraDsnStreamReader.java`): `next_string(ignoreNewline, leadingSep)`, `next_string_list(sep)`, `next_double()`, `next_closing_bracket()`. `next_string` skips leading whitespace, handles a leading `"`, and stops at whitespace / `(` / `)` (or a caller-supplied separator such as `-` for `Comp-Pin` splitting). Scope-reader classes (each a recursive-descent reader over the token stream — this is the structure the Python `reader.py` mirrors): - `ScopeKeyword.java` — base class. `read_scope` loop: on `(` followed by a known `ScopeKeyword`, recurse; unknown scope → `skip_scope` (bracket counting). This tolerant skip is the backbone of the whole reader. - `Parser.java` — `(parser (string_quote ") (host_cad …) (host_version …) (constant …) (write_resolution …) (generated_by_freerouting))`. - `Resolution.java` — `(resolution )`. Defaults: unit `mil`, resolution `100` (set in `ReadScopeParameter`). - `Structure.java` (1174) — the big one. Reads `layer`, `boundary`, `via` (routing via padstack names, incl. `(spare …)`), `rule` (default width/ clearance), `keepout` / `via_keepout` / `place_keepout`, `plane`, `control (via_at_smd on|off)`, `snap_angle`, `autoroute_settings`, `flip_style`. Then `create_board` turns it into geometry. `layer`: `(layer (type signal|power|jumper) (use_net …) (rule …))`. `boundary`: a shape on layer `pcb` is the bounding box; shapes on layer `signal` are the outline. - `Shape.java` (575) — shape grammar shared everywhere: `(rect x1 y1 x2 y2)`, `(circle dia [x y])`, `(polygon aperture x1 y1 …)`, `(path width x1 y1 …)`. `read_area_scope` reads an optional name + a border shape + `(window …)` holes + optional `(clearance_class …)`. - `Library.java` (323) — `(library (padstack …) (image …))`. `padstack`: name, one or more `(shape ())`, `(attach on|off)`, `(absolute on|off)`. `Package.java` (391) reads `(image (side front|back) (pin [ (rotate d) ] x y) (outline …) (keepout …) …)`. - `Placement.java` / `Component.java` (315) — `(placement (component (place x y front|back rot [ (lock_type position) ] [ (PN part) ] …)))`. A `place` with no coords means "not yet placed". - `Network.java` (1193) — `(network (net [subnet] (pins Comp-Pin …) (fromto …) (rule …)) (class net… (circuit (use_via …)) (rule …)) (class_class …))`. Pin refs split component/pin on the first `-`. - `NetClass.java`, `Net.java`, `Rule.java` (width/clearance rules), `AutorouteSettings.java` — supporting readers. - Data holders: `Layer`, `Padstack`(core), `PinInfo`, `ComponentPlacement`, `Circle/Rectangle/Polygon/PolygonPath/PolylinePath`. **Python port status:** items 1 (tokenizer), the shape grammar, and every structure/library/network/placement/parser/resolution scope above are implemented in `src/freeroute/dsn/` this session (see below). ## 2. Board data model — `board/` Constructed by `Structure.create_board`. Central classes: - `BasicBoard.java` — geometric item container: insert/delete/pick items, layer structure, bounding box, `insert_obstacle`, `insert_via_obstacle`, `insert_conduction_area`, `insert_component_obstacle`. `RoutingBoard.java` extends it with the routing operations the autorouter drives. - `Item.java` + subclasses: `Trace`/`PolylineTrace` (a routed wire as a `Polyline`), `Via`, `DrillItem`, `Pin`, `ObstacleArea`/`ConductionArea`/ `ComponentObstacleArea`/`ViaObstacleArea` (keepouts & planes), `BoardOutline`. - `Component.java` / `Components.java` — placed component instances. - `Layer.java` / `LayerStructure.java` — signal/power layers, index 0 = top. - `SearchTreeManager.java` + `ShapeSearchTree*.java` — the spatial index (MinAreaTree R-tree variants, one per angle restriction) used for fast obstacle queries during routing. - Shoving/tightening: `ShoveTraceAlgo`, `ForcedPadAlgo`, `ForcedViaAlgo`, `PullTightAlgo{,45,90,AnyAngle}`. - `Unit.java` (mil/inch/mm/um), `CoordinateTransform` (dsn↔board scaling), `AngleRestriction` (NINETY / FORTYFIVE / NONE). **Python translation:** a `board` package of dataclasses + a spatial index. `Item` hierarchy → dataclasses with a shared base; the search tree → an rtree/ STRtree (shapely) or a hand-rolled bbox tree. Not started this session. ## 3. Planar geometry — `geometry/planar/` (34 files) The math the router stands on. Key types and their Python analogues: - `IntPoint` / `IntVector` — integer point/vector (board coords). `FloatPoint` (double tuple, not derived from `Point` because float math is inexact) for approximate work. `RationalPoint`/`RationalVector` — exact rational intersections of lines. - `Line` / `LineSegment` / `Polyline` — a `Polyline` is a sequence of lines whose n lines define n−1 corners; traces are polylines. `Direction`, `FortyfiveDegreeDirection` encode the angle grid. - `Shape` (interface) → `PolylineShape` (straight-line borders) → `TileShape` (convex, half-plane intersection) → `Simplex` (general convex) and `IntBox`/`IntOctagon` (axis- and 45°-aligned boxes). `ConvexShape`, `RegularTileShape` interfaces. `Circle`, `Ellipse` for round pads. - `Area` / `PolylineArea` — a shape possibly with holes (border + hole shapes). - `split_to_convex()` (decompose into `TileShape[]`), `convex_hull()`, `offset()`, `contains()`, `intersection()` are the workhorse ops. - `Limits.java` (`CRIT_INT` overflow guard used when picking the coord scale). **Python translation:** this is the highest-risk port (exact rational geometry, convex decomposition). Options: lean on `shapely` for polygon ops and add a thin 45°/octagon layer, or port `Simplex`/`TileShape` directly for bit-exact parity with the JAR oracle. Decide when the board model lands. Not started this session. ## 4. Autorouter core — `autoroute/` - `BatchAutorouter.java` / `BatchAutorouterThread.java` — the top loop: pick the next unrouted connection, route it, rip up and retry on failure, repeat over passes. `BatchAutorouterV19.java` is a newer variant; `BatchFanout`, `BatchOptimizer{,MultiThreaded}` are the fanout/optimize passes. - `AutorouteEngine.java` — per-board routing state (expansion rooms, drill pages, the maze search driver). Holds the `CompleteFreeSpaceExpansionRoom` graph. - `MazeSearchAlgo.java` — the A*/Dijkstra maze search over expansion rooms ("route an incomplete connection via a maze search"). `MazeSearchElement`, `MazeListElement`, `MazeShoveTraceAlgo` (shove obstacles while searching). - `LocateFoundConnectionAlgo{,45Degree,AnyAngle}.java` — turn a found maze path back into concrete trace geometry at the right angle restriction. - `ExpansionRoom` / `FreeSpaceExpansionRoom` / `ObstacleExpansionRoom` / `ExpansionDoor` / `ExpansionDrill` — the free-space decomposition the maze search walks. `SortedRoomNeighbours{,45Degree,Orthogonal}` order neighbours. - `AutorouteControl.java` — cost function parameters (via costs, preferred- direction trace costs, ripup costs, start pass number). - `InsertFoundConnectionAlgo.java` — commit the located connection to the board. **Python translation:** the last and largest piece. An MVP can start grid/ Manhattan-45° maze routing decoupled from the exact expansion-room model, then converge toward FreeRouting's free-space rooms. Not started this session. ## 5. Supporting packages (context) - `rules/` — `BoardRules`, `ClearanceMatrix`, `NetClass`, `Net`, `ViaInfo`, `DefaultItemClearanceClasses`. The clearance matrix is class×class×layer. - `core/` — `Padstack`, `Padstacks`, `Package`, `Packages`, `BoardLibrary`, `RoutingJob`. `settings/RouterSettings` holds autoroute config. - `datastructures/` — `UndoableObjects`, `IndentFileWriter` (the DSN/SES pretty printer), `IdentifierType` (quoting on write), identification-number generators. - `drc/` — design-rule checks. `logger/FRLogger` — logging (warnings collected into the read result). ## Port order (this file drives the roadmap) 1. **DSN parser** — done this session (`src/freeroute/dsn/`). 2. **SES writer** — next. Start with an empty/no-op session to prove the loop end-to-end through `kicad-cli pcb import specctra-ses`. See `SesWriter.java` and `SpecctraSesFileWriter.java`: `(session (base_design ) (placement …) (was_is) (routes (resolution …) (network_out (net (wire (path x1 y1 …)) (via x y)))))`. 3. **Geometry primitives** — `geometry/planar` subset needed by the board model. 4. **Board model** — `board` items + spatial index. 5. **Autorouter** — maze/rip-up core, then optimize pass. 6. **CLI + kicad-mcp integration** — `freeroute board.dsn -o board.ses`.