main
Colby Knox 0.31.7: the board model names tracks[].id, the handle edit_tracks takes ab9c6ac 8d ago

The board model

One PCB, the same shape from every EDA tool. Every check in the standard reads it; every bridge returns it from <prefix>_routing_state. The JSON Schema is schema/board.schema.json; the companion models (stack-up, poured copper, 3D bodies, sheets, netlist) are schema/companions.schema.json.

The reference is the Altium bridge. AI Flow reads the same shape from KiCad and Fusion, which is why it uses KiCad's layer names.

In one picture

{
  "units": "mm", "boardId": "C:/proj/Board.PcbDoc", "revision": "476053b2ec164071",
  "nets": ["GND", "N1"], "unconnected": 0,
  "tracks": [{"start": {"x": 12.7, "y": 11.905}, "end": {"x": 26.3, "y": 11.905}, "width": 0.3, "layer": "F.Cu", "net": "N1"}],
  "arcs": [{"center": {"x": 5, "y": 5}, "radius": 1, "startAngle": 0, "endAngle": 90, "width": 0.25, "layer": "F.Cu", "net": "N1"}],
  "vias": [{"position": {"x": 7.5, "y": 2.5}, "size": 0.6, "drill": 0.3, "layers": ["F.Cu", "B.Cu"], "net": "GND"}],
  "pads": [{"ref": "U1", "name": "8", "x": 3.3, "y": 11.905, "sizeX": 1.5, "sizeY": 0.6, "rotation": 0, "layer": "F.Cu", "drill": 0, "net": "N1", "shape": "rect"}],
  "components": [{"ref": "U1", "x": 6, "y": 10, "rotation": 0, "side": "top", "pattern": "SOIC8", "has3d": true}],
  "polygons": [{"net": "GND", "layer": "B.Cu", "points": [[0.5, 0.5], [20, 0.5], [20, 19.5], [0.5, 19.5]]}],
  "outline": [[1, 0], [39, 0], [40, 1], [40, 19], [39, 20], [1, 20], [0, 19], [0, 1]],
  "outlineEdges": [{"kind": "line", "start": [1, 0], "end": [39, 0]}, {"kind": "arc", "start": [39, 0], "end": [40, 1], "center": [39, 1], "radius": 1, "angle1": 270, "angle2": 360}]
}

Rules a bridge must keep

  • Millimetres, absolute, in the standard frame: the tool's origin and x axis, y up. A tool whose y points down (KiCad) negates y and nothing else: same origin, same x, and angles stay counterclockwise as seen on screen, so the numbers need no change. Altium and Fusion are y up already. No offset from the board origin: the checks compare against the outline the same reply gives. A bridge whose existing callers read its native frame may keep that as its default and answer the standard model on request (KiCad: model: "standard"), as long as its edit verbs take the standard model's coordinates back (KiCad: frame: "standard").
  • Copper layers by KiCad's names (F.Cu, In<n>.Cu, B.Cu, *.Cu). Non-copper layers may use the tool's own name or id (Altium sends L57 for Mechanical 1).
  • Pad shapes: round (a circle when sizeX = sizeY, otherwise a stadium with semicircle ends on the long axis; a tool's oval or obround is round), circle, rect, roundrect, octagonal, rotated-rect, other.
  • A custom-shape pad is its bounding rectangle (shape: "rect", rotation 0), with its real outline in copperPoints when the bridge can read it (Fusion can). Checks use copperPoints when present. Altium's API gives such a pad no size; reported as 0 x 0, every clearance check walked straight through the Bluetooth Sentinel's 5 mm coin cell contacts.
  • Net-less copper is reported with an empty net, not dropped: a hatched pour's pieces arrive that way from Altium, and the copper checks count them.
  • unconnected is computed from the copper, not taken from the tool's ratsnest (Altium's ratsnest skips parts outside the board; Fusion's draws airwires to track ends 5 to 7.5 um off a pad's anchor but over its copper). Per net, pads, tracks, vias and poured copper join where they touch on a shared layer; every group beyond the first is a connection still to make. The tool's own count may ride along under its own key (altiumRatsnest, fusionAirwires).
  • Poured copper that reaches nothing else of its net is isolatedCopper, listed and not counted: an island a pour left behind, or a pour that touches no pad, track or via of its net. KiCad's DRC treats it the same way (a warning, not an unconnected item). A stray track or via still counts: copper that reaches nothing is a connection not made.
  • tracks[].id is the handle edit_tracks takes. It's optional in the model, and a bridge that answers edit_tracks gives one. It can be the tool's own id or one built from the track's position (Fusion's tracks have no id), so it need only hold until the board changes. It never counts toward revision.
  • revision changes when copper, parts or the outline change, and only then: it is the edit verbs' guard. It must be stable for an unchanged board; independent of item order is better (KiCad's is).
  • The outline as edges, arcs kept. Measuring against a curved edge needs the arc; sample it every 2 degrees (16 points per arc put copper 0.04 mm outside the Sentinel's round edge).
  • polygons is the pour as drawn, kind: "zone" (the default) or "graphic" for a filled copper shape. The copper actually poured is the pours model (pour_state): checks that need real copper read that.

The companion models

Model Verb What reads it
Stack-up layer_stack impedance, current capacity, fab check (copper weight, thickness), copper balance, return paths (planes). Each copper layer carries its board model name in layer (F.Cu, In<n>.Cu, B.Cu); an internal plane is In<n>.Cu by its place in the copper stack, with kind: "plane", and counts as copper everywhere but its splits and antipads
Poured copper pour_state copper balance, return paths and their fix plans. Island by island, outline and holes, filled by the even-odd rule: a fractured fill (KiCad: holes joined to the outline by zero-width cuts, holes: [], fractured: true) is the same copper and needs no separating
3D bodies bodies (Altium: an agent call behind height_check) height check, placement check. One box per body (Altium) or per part (Fusion); z from the surface the part sits on
Sheet parts with pin types read_sheet_file (Altium reads the .SchDoc file itself) schematic review, pin-to-pad check. Pin types by name (input, io, output, power, supply, not_connected...); Altium's codes are accepted too
Netlist netlist schematic review
Silkscreen silk_state designator placement, fab check (text height, line width)

Settled questions

Question Answer Where
What does "y up" mean on a y-down tool? Negate y; keep the origin, x and the angles #1
Do pour islands count as unconnected? No: isolatedCopper, listed, not counted. Strays still count #1, #2
Must fractured fills be split into outline and holes? No: even-odd filling gives the same copper #1
Custom pads? Bounding rectangle, plus copperPoints when known #2
Units in replies? mm, and a unit-named field says mm (xMm); see conventions #2
What is an internal plane called? In<n>.Cu by its place in the copper stack, kind: "plane" in the stack-up; a tool's own id may ride along (layerId), never in its place #8