KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
master
f158e1d
24d ago
Placement demo fixture: the Adom ESC G431, unplaced and unrouted
Two copies of the public Adom ESC G431 board (ESC: G431 rev 2.0.1, 149 footprints,
4 copper layers, 64 x 74 mm, KiCad 10 file format (version 20260206)), made by
tools/make_placement_fixture.py from the routed, placed original. They are the exercise
for the placement verbs (kicad_placement_state, kicad_move_footprint,
kicad_placement_validate, SKILL.md "Placement") and then for the routing verbs.
| File | What | Size |
|---|---|---|
esc-g431-unplaced.kicad_pcb |
Every footprint parked in a grid to the right of the outline, rotation 0, side kept; no copper; zones kept without fills | 779 KB |
esc-g431-unrouted.kicad_pcb |
The original placement; only the copper and the fills stripped | 780 KB |
esc-g431-placement-fixture.json |
The tool's summary for both copies |
The numbers
- Outline (Edge.Cuts box): x 104 to 168, y 58 to 132 mm.
- Parking grid (unplaced copy): x 173 to 244.87, y 58 to 140.83 mm; 149 footprints in rows sorted by reference, 2 mm apart, starting 5 mm right of the outline. Every one is at rotation 0 and outside the outline; none collides with another (the bridge's courtyard check and KiCad's DRC both say zero overlaps).
- Removed from both copies: 434 segments, 384 vias, 0 arcs, 57 zone fills
(
filled_polygon), 66 teardrop zones. Kept: the 49 user zones (pours on GND, +VBAT, +3V3, +5V, the phase nets), every footprint, every graphic, the setup block, the groups. - Everything else is byte for byte the source, CRLF line endings included: the tool parses
the file with a span-keeping s-expression tokenizer and rewrites only the nodes that
change. A parked footprint gets a new
(at X Y); because KiCad stores pad and text angles as absolute values, each child angle is rewritten relative to the footprint's old rotation so the part looks the same at rotation 0.
Teardrop zones are KiCad's generated copper around pads and vias. They belong to the copper
that was removed, so they go with it (pass --keep-teardrops to keep them).
What KiCad says about them
service-kicad pcb drc (KiCad 10.0.2) on both copies loads them without complaint:
| Copy | Errors | Unconnected | courtyards_overlap | Notes |
|---|---|---|---|---|
| unplaced | 13 | 271 | 0 | six malformed_courtyard (Q1 to Q6, the source library's open courtyard shapes) and seven clearance errors between pads inside U2's own footprint; both are in the source board too |
| unrouted | 14 | 271 | 1 | the same, plus the source board's own TP2 against C28 courtyard overlap |
271 unconnected items is the ratsnest to route once the parts are placed. The bridge's own
parser reads both copies back with 149 footprints, zero segments, zero vias and 49 zones
(rust/crates/kicad-core/tests/placement_fixture.rs).
Using it
- Copy
esc-g431-unplaced.kicad_pcbsomewhere the desktop's KiCad can open it (the project file is not needed; sibling.kicad_proand.kicad_drufiles are picked up by the DRC snapshot when present). - Open it in the PCB editor with the IPC API server on (
kicad_ipc_api {"enable":true}, relaunch), or start from the file: the read-only placement verbs answer from disk. kicad_placement_state {"filePath": ...}: 149 footprints, allparked, the ratsnest total around 4.2 m (the placed original measures about 1.5 m by the same estimate).- Place with
kicad_move_footprint, one part or cluster per call, following the recipe in SKILL.md. Each call is one native Undo step. kicad_placement_validateuntilplacedis true andparkedis empty, then route (kicad_autorouteengine ai,kicad_route_net) on what is now the unrouted board.
Rebuilding
python3 tools/make_placement_fixture.py --self-test
python3 tools/make_placement_fixture.py path/to/esc-g431.kicad_pcb --out demo/placement
Any placed .kicad_pcb with an Edge.Cuts outline works; the tool refuses a board without
one, since there is nowhere to park relative to.
# Placement demo fixture: the Adom ESC G431, unplaced and unrouted
Two copies of the public Adom ESC G431 board (`ESC: G431` rev 2.0.1, 149 footprints,
4 copper layers, 64 x 74 mm, KiCad 10 file format `(version 20260206)`), made by
`tools/make_placement_fixture.py` from the routed, placed original. They are the exercise
for the placement verbs (`kicad_placement_state`, `kicad_move_footprint`,
`kicad_placement_validate`, SKILL.md "Placement") and then for the routing verbs.
| File | What | Size |
|---|---|---|
| `esc-g431-unplaced.kicad_pcb` | Every footprint parked in a grid to the right of the outline, rotation 0, side kept; no copper; zones kept without fills | 779 KB |
| `esc-g431-unrouted.kicad_pcb` | The original placement; only the copper and the fills stripped | 780 KB |
| `esc-g431-placement-fixture.json` | The tool's summary for both copies | |
## The numbers
- Outline (Edge.Cuts box): x 104 to 168, y 58 to 132 mm.
- Parking grid (unplaced copy): x 173 to 244.87, y 58 to 140.83 mm; 149 footprints in rows
sorted by reference, 2 mm apart, starting 5 mm right of the outline. Every one is at
rotation 0 and outside the outline; none collides with another (the bridge's courtyard
check and KiCad's DRC both say zero overlaps).
- Removed from both copies: 434 segments, 384 vias, 0 arcs, 57 zone fills
(`filled_polygon`), 66 teardrop zones. Kept: the 49 user zones (pours on GND, +VBAT,
+3V3, +5V, the phase nets), every footprint, every graphic, the setup block, the groups.
- Everything else is byte for byte the source, CRLF line endings included: the tool parses
the file with a span-keeping s-expression tokenizer and rewrites only the nodes that
change. A parked footprint gets a new `(at X Y)`; because KiCad stores pad and text angles
as absolute values, each child angle is rewritten relative to the footprint's old
rotation so the part looks the same at rotation 0.
Teardrop zones are KiCad's generated copper around pads and vias. They belong to the copper
that was removed, so they go with it (pass `--keep-teardrops` to keep them).
## What KiCad says about them
`service-kicad pcb drc` (KiCad 10.0.2) on both copies loads them without complaint:
| Copy | Errors | Unconnected | courtyards_overlap | Notes |
|---|---|---|---|---|
| unplaced | 13 | 271 | 0 | six `malformed_courtyard` (Q1 to Q6, the source library's open courtyard shapes) and seven `clearance` errors between pads inside U2's own footprint; both are in the source board too |
| unrouted | 14 | 271 | 1 | the same, plus the source board's own TP2 against C28 courtyard overlap |
271 unconnected items is the ratsnest to route once the parts are placed. The bridge's own
parser reads both copies back with 149 footprints, zero segments, zero vias and 49 zones
(`rust/crates/kicad-core/tests/placement_fixture.rs`).
## Using it
1. Copy `esc-g431-unplaced.kicad_pcb` somewhere the desktop's KiCad can open it (the
project file is not needed; sibling `.kicad_pro` and `.kicad_dru` files are picked up by
the DRC snapshot when present).
2. Open it in the PCB editor with the IPC API server on (`kicad_ipc_api {"enable":true}`,
relaunch), or start from the file: the read-only placement verbs answer from disk.
3. `kicad_placement_state {"filePath": ...}`: 149 footprints, all `parked`, the ratsnest
total around 4.2 m (the placed original measures about 1.5 m by the same estimate).
4. Place with `kicad_move_footprint`, one part or cluster per call, following the recipe
in SKILL.md. Each call is one native Undo step.
5. `kicad_placement_validate` until `placed` is true and `parked` is empty, then route
(`kicad_autoroute` engine ai, `kicad_route_net`) on what is now the unrouted board.
## Rebuilding
```sh
python3 tools/make_placement_fixture.py --self-test
python3 tools/make_placement_fixture.py path/to/esc-g431.kicad_pcb --out demo/placement
```
Any placed `.kicad_pcb` with an Edge.Cuts outline works; the tool refuses a board without
one, since there is nowhere to park relative to.