Documents the packages relevant to the port (io/specctra DSN/SES, board data model, geometry/planar primitives, autoroute core) with verified upstream paths and Python-translation notes. Corrects the seed plan's stale guesses: the Specctra code lives under io/specctra, not designforms/specctra.
12 KiB
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 <name>header with a literal three-token check ((,pcbscope keyword, name string), then callsKeyword.PCB_SCOPE.read_scope(par)to parse the body.readMetadata(stream)is a fast path that parses onlyparser/resolution/structureand stops. Returns a typedBoardReadResult(Success / OutlineMissing / ParseError / IoError). Defaults captured here: it flips the scanner toNAMEstate to read the pcb name cleanly.SesReader.java(392) /SesWriter.java(430) — session-file read/write.RulesReader.java/RulesWriter.java—.rulessidecar 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 singletonKeywordobjects;(→OPEN_BRACKET,)→CLOSED_BRACKET. - Non-keyword atoms are returned as
Integer,Double, orString. - Lexical states are the tricky part. Several keywords call
yybegin(NAME)(orLAYER_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 named0). 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_stringskips leading whitespace, handles a leading", and stops at whitespace /(/)(or a caller-supplied separator such as-forComp-Pinsplitting).
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_scopeloop: on(followed by a knownScopeKeyword, 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 <unit> <int>). Defaults: unitmil, resolution100(set inReadScopeParameter).Structure.java(1174) — the big one. Readslayer,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. Thencreate_boardturns it into geometry.layer:(layer <name> (type signal|power|jumper) (use_net …) (rule …)).boundary: a shape on layerpcbis the bounding box; shapes on layersignalare the outline.Shape.java(575) — shape grammar shared everywhere:(rect <layer> x1 y1 x2 y2),(circle <layer> dia [x y]),(polygon <layer> aperture x1 y1 …),(path <layer> width x1 y1 …).read_area_scopereads an optional name + a border shape +(window …)holes- optional
(clearance_class …).
- optional
Library.java(323) —(library (padstack …) (image …)).padstack: name, one or more(shape (<shape>)),(attach on|off),(absolute on|off).Package.java(391) reads(image <name> (side front|back) (pin <padstack> [ (rotate d) ] <pinname> x y) (outline …) (keepout …) …).Placement.java/Component.java(315) —(placement (component <libname> (place <refdes> x y front|back rot [ (lock_type position) ] [ (PN part) ] …))). Aplacewith no coords means "not yet placed".Network.java(1193) —(network (net <name> [subnet] (pins Comp-Pin …) (fromto …) (rule …)) (class <name> 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.javaextends it with the routing operations the autorouter drives.Item.java+ subclasses:Trace/PolylineTrace(a routed wire as aPolyline),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 fromPointbecause float math is inexact) for approximate work.RationalPoint/RationalVector— exact rational intersections of lines.Line/LineSegment/Polyline— aPolylineis a sequence of lines whose n lines define n−1 corners; traces are polylines.Direction,FortyfiveDegreeDirectionencode the angle grid.Shape(interface) →PolylineShape(straight-line borders) →TileShape(convex, half-plane intersection) →Simplex(general convex) andIntBox/IntOctagon(axis- and 45°-aligned boxes).ConvexShape,RegularTileShapeinterfaces.Circle,Ellipsefor round pads.Area/PolylineArea— a shape possibly with holes (border + hole shapes).split_to_convex()(decompose intoTileShape[]),convex_hull(),offset(),contains(),intersection()are the workhorse ops.Limits.java(CRIT_INToverflow 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.javais 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 theCompleteFreeSpaceExpansionRoomgraph.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/RouterSettingsholds 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)
- DSN parser — done this session (
src/freeroute/dsn/). - SES writer — next. Start with an empty/no-op session to prove the loop
end-to-end through
kicad-cli pcb import specctra-ses. SeeSesWriter.javaandSpecctraSesFileWriter.java:(session <name> (base_design <name>) (placement …) (was_is) (routes (resolution …) (network_out (net <name> (wire (path <layer> <width> x1 y1 …)) (via <padstack> x y))))). - Geometry primitives —
geometry/planarsubset needed by the board model. - Board model —
boarditems + spatial index. - Autorouter — maze/rip-up core, then optimize pass.
- CLI + kicad-mcp integration —
freeroute board.dsn -o board.ses.