board.json
One flat structure with everything the three views need, so the viewer never recomputes placement maths that was already done. Written by export.board_json() into every version; served at /api/projects/<slug>/versions/<vid>/board.
{
"name": "ne555-led-blinker", "width": 28, "height": 22,
"components": [ { "ref": "U1", "kind": "ic", "value": "NE555", "purpose": "timer",
"package": "SOIC-8", "lcsc": "C7593", "mfr": "NE555DR",
"price": 0.1263, "basic": true, "stock": 209000,
"symbol": "NE555D", "sym": "Timer:NE555D",
"x": 15.35, "y": 7.17, "rot": 90, "w": 6.4, "h": 5.2,
"body_w": 4.9, "body_h": 3.9, "body_z": 1.75, "lead": "gullwing", "standoff": 0.15,
"pads": [ { "name": "1", "x": 12.6, "y": 5.3, "w": 0.6, "h": 1.5, "shape": "rect", "th": false, "drill": 0 } ],
"silk": [ { "pts": [[12.15, 4.1], [12.15, 10.2]], "w": 0.15 } ],
"body": [[12.5, 4.7], [18.2, 4.7], [18.2, 9.6], [12.5, 9.6]],
"court": [12.15, 18.55, 4.1, 10.2] } ],
"symbols": { "Timer:NE555D": { "rects": [], "polys": [], "arcs": [], "texts": [], "pins": [], "fields": {} } },
"nets": [ { "name": "GND", "kind": "ground", "voltage_v": 0, "current_a": null, "width_mm": 0.25,
"pins": [ { "ref": "U1", "pad": "1", "fn": "GND" } ] } ],
"wires": [ { "net": "OUT", "layer": "F.Cu", "width": 0.25, "pts": [[15.3, 7.2], [18.0, 7.2]] } ],
"vias": [ { "net": "GND", "x": 10.1, "y": 4.4, "d": 0.6, "drill": 0.3 } ],
"insignia": { "fills": [ [[10.4, 1.0], [11.9, 1.0], [11.9, 2.5], [10.4, 2.5]] ] },
"erc": [ { "severity": "warning", "rule": "floating-pin", "message": "U1 pin(s) ['5'] unconnected" } ],
"gaps": [], "notes": [],
"bom": { "parts": 0.36, "setup": 9.0 },
"inventory": { "quantities": [1, 10, 25, 100], "reports": { "1": { "qty": 1, "buildable": true, "parts_cost": 0.36, "setup_cost": 9.0, "total": 9.36, "per_board": 9.36, "checked_live": true, "lines": [ { "lcsc": "C7593", "refs": ["U1"], "needed": 2, "stock": 209000, "headroom": 104500.0, "risk": "ok", "note": "" } ] } } },
"generation": { "prompt": "A 555 timer LED blinker running from 5V", "backend": "gemini", "model": "gemini-3.8-flash",
"summary": "…", "assumptions": [], "unsupported": [], "decisions": [],
"input_tokens": 4210, "output_tokens": 1180, "cost_usd": null }
}| Key | Contents |
|---|---|
name, width, height | board name and size in mm |
components[] | resolved parts with absolute, already-rotated pad positions (x, y in mm, board origin bottom-left, y up), the 3D profile (body_z, lead, standoff), price, stock and basic/extended, the symbol name and sym key into the symbol table |
components[].pads[].shape | what the fab flashes: rect, or circle — a round pad longer than it is wide is the oval the Gerber and the .kicad_pcb draw. Since 15 Sep 2026; the 2D view drew every surface pad as a rounded rectangle and every through-hole pad as a circle before it, and still does on a board without the key |
components[].silk | the part's silkscreen in board mm, stroke by stroke — pts and the pen width w, or fill: true for a solid shape — the same strokes gerber._silk inks and the .kicad_pcb carries (silk.placed()): the vendor's own drawing where the footprint is the vendor's, and one drawn clear of the pads from the body and pins otherwise. Absent on a board built before 15 Sep 2026, which draws its courtyard box as it always did |
components[].body, components[].court | the body's four corners (F.Fab), turned and mirrored with the part, and the courtyard box [x0, x1, y0, y1] the placer kept clear (F.CrtYd) — where the placer put it, which for a module is not centred on the origin the part turns about |
vias[].d, vias[].drill | the via's pad and hole diameter, so the view draws the size the copper has rather than a size it remembers |
symbols | the schematic drawings, one per distinct symbol, in KiCad mils with y up; a part with no library drawing gets a box under generic:<ref> |
sheet | the schematic laid out (sheet.py): symbols (ref → x, y, rot, mir, units, role, pinned, box), wires (polylines, stub for a pin's label stub, trunk for a lane), junctions, labels (net, x, y, rot), ports (net, kind, x, y, dir), nc, texts (ref/value placements), frame (sheet size and where the drawing sits on it), undrawn (pads with no pin in their drawing) and stats — all in mils, y down. Rebuilt on read for boards saved before it existed |
nets[] | name, kind (ground, power, signal), pins with the function name each pin was resolved from, and the electrical facts below |
nets[].voltage_v | nominal volts against ground, or null — what makes Ohm's law checkable (ERC) |
nets[].current_a | steady amps, or null — what the copper is sized from |
nets[].width_mm | the width the net was actually routed at: IPC-2221 for its current, or the 0.25 mm median when it declared none |
wires[], vias[] | routed copper by layer (F.Cu, B.Cu) and width |
routing | the router's account: predicted (the model's rating of the placement it saw first, and which model), candidates[] (every placement annealed: seed, wirelength cost, p_clean), attempts[] (each placement routed: the router's unrouted and violations counts, how many nets were incomplete, seconds, clean), chosen (index into attempts), router (the kept attempt's counts), clearance_mm (the rule the board was routed at: 0.2 unless its parts pulled it lower, see dsn.rules), and incomplete — net name to the pins its copper never reaches. null on a board built before this was recorded; the workbench then falls back to asking which nets have any wire at all |
insignia | the maker's mark as the filled polygons the silkscreen Gerber fills as regions, in board mm — null on a board with no room for it and on versions built before it existed. The views fill these rather than redrawing the logo, so what is on screen is the ink on the board |
erc[] | every finding: severity, rule, message |
gaps[], notes[] | what could not be resolved, and what was resolved but worth saying |
bom | parts cost at qty 1 and the extended-part setup fee |
inventory | the stock sweep — see Stock and pricing |
generation | provenance: prompt, backend, model, tokens, cost, and the model's summary, assumptions, decisions and unsupported |
Boards saved before the symbol table existed get it rebuilt on read (export.attach_symbols), so a project's whole history draws with real symbols.