← Commit history

Publish 1.0.19

John Lauer ·ec773d5ef6 ·2mo ago ·parent 8a9a5bb
4 files changed +224−258
README.md+35−63
@@ -1,14 +1,28 @@-# Adom Bridge — KiCad Bridge+# Adom Desktop — KiCad Bridge -A **reverse bridge** that lets Adom Bridge (ab) — and the AI driving it — control the user's own+A **reverse bridge** that lets Adom Desktop (AD) — and the AI driving it — control the user's own KiCad on Windows: open and drive every editor, install symbol/footprint libraries, place parts, run DRC/ERC, export manufacturing files, and screenshot any window back to the AI. +> ### Two artifacts on this page — which one do you want?+>+> | | **Release ZIP** (`adom-bridge-kicad-v*.zip`) | **Package** (`adom-wiki pkg install adom/adom-desktop-kicad-bridge`) |+> |---|---|---|+> | What it is | The **bridge runtime** — the actual Python server that drives KiCad | **Skills + docs only**, for a container/agent |+> | Who loads it | **Adom Desktop** downloads it automatically via `adom-bridge-kicad-manifest.json` | You, into a container, to teach an agent the `kicad_*` verbs |+> | Contains | code, certs, UIA scripts, board template | `SKILL.md` files (no binaries, no images) |+> | You install it manually? | **No** — AD handles it | Yes |+>+> **The package is not the bridge.** Installing the package does not give you a working+> bridge; AD loading the release zip does. Both are kept deliberately small — the zip is+> auto-bundled into AD, so it ships only what the runtime needs.++ KiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if none is present, *installs* one for them — it never asks the user to download or click through anything by hand. -- **Page / docs:** https://wiki.adom.inc/adom/kicad-bridge-- **Bridge SDK:** https://wiki.adom.inc/adom/adom-bridge-sdk+- **Page / docs:** https://wiki.adom.inc/adom/adom-desktop-kicad-bridge+- **Bridge SDK:** https://wiki.adom.inc/adom/adom-desktop-bridges - **Verb namespace:** `kicad_*` &nbsp;·&nbsp; **Status verb:** `kicad_bridge_status` &nbsp;·&nbsp; **Health:** `GET /status`  > Every screenshot in this README was captured on **ADOMBASELINE**, a stock Hyper-V VM with **no@@ -19,63 +33,21 @@ KiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if  ## Demo — narrated live editor tour (real screen recording) -[![KiCad bridge live demo](https://wiki.adom.inc/blob/app/kicad-bridge/kicad-bridge-demo-poster.png)](https://wiki.adom.inc/blob/app/kicad-bridge/kicad-bridge-demo.mp4)--**▶ [Watch the demo](https://wiki.adom.inc/blob/app/kicad-bridge/kicad-bridge-demo.mp4)** (48s, 🔊 narrated) — the bridge driving real KiCad on a Windows VM: **select** a footprint and **zoom** the copper in the PCB editor, **rotate** the assembled board in 3D, then tour the schematic, symbol, and footprint editors. Genuine screen recording of the live windows (zoom / select / 3D rotation), narrated section by section — not animated stills.+[![KiCad bridge live demo](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo-poster.png)](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4) +**▶ [Watch the demo](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)** (48s, 🔊 narrated) — the bridge driving real KiCad on a Windows VM: **select** a footprint and **zoom** the copper in the PCB editor, **rotate** the assembled board in 3D, then tour the schematic, symbol, and footprint editors. Genuine screen recording of the live windows (zoom / select / 3D rotation), narrated section by section — not animated stills. ------## The demo verb — `kicad_demo`--One verb that shows the whole bridge off, built for the AI that demos Adom Bridge-during Hydrogen's install. Six beats, in the order a hardware person thinks:--| Beat | Shows |-|---|---|-| 1 | the **schematic symbol** in the user's own Symbol Editor |-| 2 | the **footprint** for that same part |-| 3 | that part in **3D** |-| 4 | a **schematic** using it (U1 + two 10k resistors) |-| 5 | the **2D board layout** |-| 6 | the finished **board in 3D** |--```bash-adom-desktop kicad_demo '{}'                      # start: prepares, warms KiCad, opens beat 1-adom-desktop kicad_demo '{"step":"footprint"}'    # ...then follow data.nextCall each time-adom-desktop kicad_demo '{"all":true}'            # bulk (non-interactive; slower than one request budget)-adom-desktop kicad_close '{"force":true}'         # tear down-```--**It narrates itself.** Every beat returns `say` (a line to speak), `pointOut`-(what's actually on screen), `window` (match it in `kicad_screenshot_all`) and-`nextCall`. One beat per call is the default precisely so the AI can talk, screenshot,-then move on.--**It ships nothing.** The two demo parts, the schematic and the board are all-generated in code, and the 3D bodies come from KiCad's *own* bundled-`3dmodels/*.3dshapes` — so the runtime zip stays small and the 3D views are real.-Parts install into the user's real `Adom` library; the project lands in-`Documents/adom-kicad-demo`.--**No KiCad? It offers to install it.** `kicad_demo` returns an offer instead of an-error; `kicad_demo '{"installKiCad": true}'` silently installs the official build-(per-user, no UAC) and then runs the tour. Watching the AI install your EDA tool is-itself a good demo beat.--**It never takes your screen.** Every window opens in the background and all input is-window-targeted (UIA / `PostMessage`), so you can keep working while it runs.  ---  ## How it fits together -The bridge is a `spawn.kind: python` process Bridge launches on the user's machine (`entrypoint:-server.py`, `port: 0` — Bridge picks the port and passes `ADOM_BIND_HOST`). It speaks HTTP+The bridge is a `spawn.kind: python` process AD launches on the user's machine (`entrypoint:+server.py`, `port: 0` — AD picks the port and passes `ADOM_BIND_HOST`). It speaks HTTP (`POST /command`, `GET /status`) and reaches KiCad through **four control surfaces**, picking the lightest one that can do the job. -![Control surfaces](https://wiki.adom.inc/blob/app/kicad-bridge/control-surfaces.svg)+![Control surfaces](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/control-surfaces.svg)  | # | Surface | Used for | Where | |---|---------|----------|-------|@@ -95,33 +67,33 @@ generated by `tour-pack-rp2040/` (an `AdomRP2040` symbol + `QFN-56_AdomRP2040` f  The RP2040 breakout: the MCU (U1), USB-C, a 12 MHz crystal, decoupling, and mounting pins. -![Schematic editor](https://wiki.adom.inc/blob/app/kicad-bridge/schematic-editor.png)+![Schematic editor](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/schematic-editor.png)  ### Symbol editor — `kicad_open_symbol_editor`  Opens straight to a symbol (`kicad_install_symbol` puts it in a user library first). -![Symbol editor](https://wiki.adom.inc/blob/app/kicad-bridge/symbol-editor.png)+![Symbol editor](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/symbol-editor.png)  ### Footprint editor — `kicad_open_footprint_editor`  `{"footprintName":"QFN-56_AdomRP2040","library":"AdomRP2040"}` loads the part directly — 56 pins + thermal pad, 7×7 mm, 0.4 mm pitch, courtyard and silkscreen. -![Footprint editor](https://wiki.adom.inc/blob/app/kicad-bridge/footprint-editor.png)+![Footprint editor](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/footprint-editor.png)  ### PCB editor (2D) — `kicad_open_board`  The routed breakout — copper, silkscreen, the QFN-56 land pattern, mounting holes. -![PCB editor 2D](https://wiki.adom.inc/blob/app/kicad-bridge/pcb-editor-2d.png)+![PCB editor 2D](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/pcb-editor-2d.png)  ### 3D viewer — `kicad_open_3d_viewer {"editor":"pcb"}`  The full board in 3D — board body, the USB-C connector's 3D model, the QFN chip body, SMD parts, plated through-holes. **Rendered on the CPU via the software-OpenGL fallback (below); reload 1.7 s.** -![3D viewer](https://wiki.adom.inc/blob/app/kicad-bridge/3d-viewer.png)+![3D viewer](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/3d-viewer.png)  --- @@ -130,14 +102,14 @@ plated through-holes. **Rendered on the CPU via the software-OpenGL fallback (be The bridge never tells the user to go download KiCad. `kicad_upgrade` fetches the official installer and runs it silently, picking the scope automatically: -- **Elevated Bridge** → `/allusers /S` (system-wide, `%ProgramFiles%\KiCad`).-- **Non-elevated Bridge** → `/currentuser /S` (`%LocalAppData%\Programs\KiCad`, **no UAC prompt**).+- **Elevated AD** → `/allusers /S` (system-wide, `%ProgramFiles%\KiCad`).+- **Non-elevated AD** → `/currentuser /S` (`%LocalAppData%\Programs\KiCad`, **no UAC prompt**).  `kicad_readiness` reports whether KiCad is installed and ready without side effects; the AI routes on it before offering to install. Hard-won install details (all handled for you):  - KiCad 10's NsisMultiUser installer **requires** a scope flag — bare `/S` errors `rc=666660`.-- Bridge's portable Python has **no CA bundle**; the bridge ships `certs/cacert.pem` and uses it for TLS.+- AD's portable Python has **no CA bundle**; the bridge ships `certs/cacert.pem` and uses it for TLS. - Downloads are size-checked against `Content-Length` (a truncated installer otherwise fails at NSIS). - `%APPDATA%/kicad/<ver>/` lib tables don't exist until first launch — the bridge **bootstraps** them   so `install_library`/`install_footprint` work on a never-opened KiCad.@@ -157,7 +129,7 @@ Auto-expired benign dialogs include: - **"Welcome to KiCad — starting for the first time"** first-run wizard (dismissing accepts defaults). - **"This file was created by an older version of KiCad"** conversion notice. -![First-run wizard, auto-dismissed](https://wiki.adom.inc/blob/app/kicad-bridge/dialog-firstrun-wizard.png)+![First-run wizard, auto-dismissed](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/dialog-firstrun-wizard.png)  Verbs: @@ -204,13 +176,13 @@ related verbs, and pitfalls.  ## Running / dependencies -Bridge provisions everything; there is nothing to install by hand.+AD provisions everything; there is nothing to install by hand. -- **Python** ≥ 3.11 (Bridge provisions it) — stdlib only, plus the bundled `certs/cacert.pem`.+- **Python** ≥ 3.11 (AD provisions it) — stdlib only, plus the bundled `certs/cacert.pem`. - **KiCad** ≥ 7.0 (host app; `kicad_upgrade` installs it if absent). - **OS:** Windows (macOS/Linux detection stubs exist; the GUI surfaces are Windows-first). -The bridge binds `ADOM_BIND_HOST` on a Bridge-assigned port — never `0.0.0.0`. Auto-updates via+The bridge binds `ADOM_BIND_HOST` on an AD-assigned port — never `0.0.0.0`. Auto-updates via `updateManifestUrl` in `bridge.json`.  ---
package.json+3−8
@@ -1,6 +1,6 @@ {   "slug": "kicad-bridge",-  "version": "1.0.18",+  "version": "1.0.19",   "type": "app",   "description": "Skills for your container so your AI knows how to drive the KiCad bridge. The bridge runtime itself is the release zip; Adom Bridge loads that.",   "tags": [@@ -28,11 +28,6 @@     "skills/kicad-interaction/SKILL.md",     "skills/kicad-3d-models/SKILL.md",     "skills/kicad-tour/SKILL.md",-    "skills/kicad-tour/tour_runner.py",-    "adom-desktop-kicad-bridge-hero-v4.png"-  ],-  "hero": {-    "path": "adom-desktop-kicad-bridge-hero-v4.png"-  },-  "name": "kicad-bridge"+    "skills/kicad-tour/tour_runner.py"+  ] }\ No newline at end of file
page.json+2−3
@@ -4,7 +4,7 @@   "slug": "kicad-bridge",   "title": "KiCad - the KiCad Bridge",   "brief": "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.",-  "version": "1.0.18",+  "version": "1.0.19",   "tags": [     "kicad",     "pcb",@@ -120,8 +120,7 @@     "skills/kicad-interaction/SKILL.md",     "skills/kicad-3d-models/SKILL.md",     "skills/kicad-tour/SKILL.md",-    "skills/kicad-tour/tour_runner.py",-    "adom-desktop-kicad-bridge-hero-v4.png"+    "skills/kicad-tour/tour_runner.py"   ],   "org": "adom",   "name": "kicad-bridge"
skills/kicad-3d-models/SKILL.md+184−184
@@ -1,184 +1,184 @@----
-name: kicad-3d-models
-description: Add standard KiCad 3D models to a .kicad_pcb file (especially tscircuit exports that ship without model references), open the 3D viewer in KiCad on the user's desktop, and screenshot to verify. Trigger words — 3d models in kicad, kicad 3d viewer, alt+3, no 3d chips, missing 3d models, add 3d models, kicad 3d screenshot, show me 3d in kicad, prove 3d works, kicad models missing.
----
-
-# kicad-3d-models
-
-**Prerequisite: Read `kicad-interaction` skill first.** It covers state checks, window management, and dialog handling that this skill depends on.
-
-Add KiCad-standard 3D model references to `.kicad_pcb` files, push to the user's desktop, open the 3D viewer, and screenshot to prove it worked.
-
-## When to use
-
-- User opens a tscircuit-exported `.kicad_pcb` in KiCad and sees no 3D models (Alt+3 shows bare board)
-- User asks to add 3D models to a KiCad PCB
-- User wants to see/screenshot the KiCad 3D viewer
-
-## Why tscircuit exports lack 3D models
-
-`tsci export -f kicad_pcb` produces valid PCB layouts (pads, traces, silkscreen, board outline) but does **not** inject `(model ...)` entries into footprint blocks. KiCad's 3D viewer needs those entries to know which `.step`/`.wrl` file to render for each component.
-
-## Step 1 — Discover the KiCad 3D model environment variable
-
-KiCad versions use version-specific env vars. Query `service-kicad` to get the correct one:
-
-```bash
-service-kicad fp fetch Resistor_SMD R_0402_1005Metric 2>&1 | grep "model"
-```
-
-This returns something like:
-```
-(model "${KICAD10_3DMODEL_DIR}/Resistor_SMD.3dshapes/R_0402_1005Metric.step"
-```
-
-The `${KICAD10_3DMODEL_DIR}` prefix is what you need. Adjust for the KiCad version on the user's desktop.
-
-## Step 2 — Map footprint types to 3D models
-
-List the unique footprint types in the `.kicad_pcb`:
-
-```bash
-grep -A1 "(footprint" board.kicad_pcb | grep '"tscircuit:' | sort -u
-```
-
-Then build a mapping. Common tscircuit footprint → KiCad 3D model mappings:
-
-| tscircuit footprint | KiCad 3D model path |
-|---|---|
-| `tscircuit:resistor_0402` | `Resistor_SMD.3dshapes/R_0402_1005Metric.step` |
-| `tscircuit:resistor_0603` | `Resistor_SMD.3dshapes/R_0603_1608Metric.step` |
-| `tscircuit:resistor_0805` | `Resistor_SMD.3dshapes/R_0805_2012Metric.step` |
-| `tscircuit:capacitor_0402` | `Capacitor_SMD.3dshapes/C_0402_1005Metric.step` |
-| `tscircuit:capacitor_0603` | `Capacitor_SMD.3dshapes/C_0603_1608Metric.step` |
-| `tscircuit:capacitor_0805` | `Capacitor_SMD.3dshapes/C_0805_2012Metric.step` |
-| `tscircuit:led_0603_color(...)` | `LED_SMD.3dshapes/LED_0603_1608Metric.step` |
-| `tscircuit:TYPE-C-31-M-12` | `Connector_USB.3dshapes/USB_C_Receptacle_HRO_TYPE-C-31-M-12.step` |
-| `tscircuit:chip` (machine pins) | Skip or use a small placeholder |
-
-For non-standard footprints, use `service-kicad fp fetch <library> <name>` to look up the model path. Common KiCad 3D model libraries:
-
-- `Resistor_SMD`, `Resistor_THT`
-- `Capacitor_SMD`, `Capacitor_THT`
-- `LED_SMD`, `LED_THT`
-- `Connector_USB`, `Connector_PinHeader`
-- `Package_SO`, `Package_QFP`, `Package_BGA`, `Package_DFN_QFN`
-
-## Step 3 — Inject model entries
-
-Write a Python script that:
-1. Reads the `.kicad_pcb` file
-2. For each `(footprint ...)` block, identifies the footprint type from the line after `(footprint`
-3. Finds the closing `)` of the footprint block (by tracking paren depth)
-4. Inserts a `(model ...)` block before the closing paren
-
-Model block format:
-```
-    (model "${KICAD10_3DMODEL_DIR}/Library.3dshapes/ModelName.step"
-      (offset (xyz 0 0 0))
-      (scale (xyz 1 1 1))
-      (rotate (xyz 0 0 0))
-    )
-```
-
-Verify the result:
-```bash
-grep "KICAD.*3DMODEL" board.kicad_pcb | sort | uniq -c
-```
-
-## Step 4 — Send to desktop and open in KiCad
-
-**CRITICAL: Close all existing KiCad windows BEFORE opening the updated file.** Opening the same file twice creates "File Open Warning" dialogs and leaves stale editors around.
-
-```bash
-# Always close first
-adom-desktop kicad_close '{}'
-
-# Send files (send PCB and any sidecar STEP/WRL separately if >1MB total to avoid 413)
-adom-desktop send_files '{"filePaths":["/path/to/board.kicad_pcb"],"category":"kicad"}'
-adom-desktop send_files '{"filePaths":["/path/to/USB_C_model.wrl"],"category":"kicad"}'
-
-# Open fresh
-adom-desktop kicad_open_board '{"filePath":"C:/Users/john/Downloads/board.kicad_pcb"}'
-```
-
-Wait for the PCB editor to load, then check for dialogs:
-```bash
-adom-desktop kicad_window_info '{}'
-```
-
-## Step 5 — Open the 3D viewer and screenshot
-
-```bash
-# Open the 3D viewer (equivalent to Alt+3)
-adom-desktop kicad_open_3d_viewer '{"hwnd": <pcb_editor_hwnd>}'
-
-# Wait for rendering, then check the viewer window appeared
-sleep 5
-adom-desktop kicad_window_info '{}'
-
-# Screenshot the 3D viewer window
-adom-desktop desktop_screenshot_window '{"hwnd": <3d_viewer_hwnd>}'
-```
-
-The screenshot saves to `/tmp/adom-desktop-screenshots/`. The file is already on Docker — read it with the Read tool to verify the 3D models rendered correctly.
-
-## Handling missing models (e.g. manufacturer-specific USB-C connectors)
-
-Some footprints reference 3D models that aren't in KiCad's standard `packages3D` distribution. Common case: `USB_C_Receptacle_HRO_TYPE-C-31-M-12` ships in the KiCad footprint library but has no matching STEP in packages3D.
-
-**Fix: relative path + ship the STEP alongside the PCB.**
-
-1. Find a visually similar model that IS available:
-   ```bash
-   service-kicad model fetch "Connector_USB.3dshapes/USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal.step" --out /tmp/usbc-standin.step
-   ```
-2. Rename it to match the expected filename and place it next to the `.kicad_pcb`
-3. Change the model reference in the PCB to a relative path:
-   ```
-   (model "./USB_C_Receptacle_HRO_TYPE-C-31-M-12.step" ...)
-   ```
-4. Send BOTH the `.kicad_pcb` and the `.step` file to the desktop (send separately if the STEP triggers a 413 payload-too-large error)
-
-Test with `service-kicad model fetch` first — if it 404s, the model isn't in packages3D and needs this workaround.
-
-**CRITICAL: WRL unit conversion.** KiCad interprets WRL/VRML coordinates as **inches**. If you convert an OBJ/GLB/STL (which are typically in mm) to WRL, you MUST divide all vertex coordinates by 25.4. Without this, the model renders 25.4x too large. STEP files don't have this problem — KiCad reads STEP as mm natively.
-
-## Up-axis rotation convention
-
-STEP files are authored with different up-axis conventions. KiCad uses Z-up. When injecting `(model ...)` references, read `chosen_up_axis` from chip-fetcher's `info.json` to determine the correct rotation:
-
-| `chosen_up_axis` | Source convention | KiCad rotation needed |
-|---|---|---|
-| `z` | Z-up (KiCad native) | `(rotate (xyz 0 0 0))` |
-| `y` | Y-up (Fusion 360, many vendors) | `(rotate (xyz -90 0 0))` |
-
-If `info.json` says `chosen_up_axis: "y"` and you use `(rotate (xyz 0 0 0))`, the model will render sideways or upside-down in KiCad. chip-fetcher's dashboard shows the orientation visually (Z-up/Y-up thumbnail icons) -- check it BEFORE writing the model reference.
-
-See the [`board-building-pipeline`](../board-building-pipeline/SKILL.md) skill for the full up-axis table and how `chosen_up_axis` flows through the entire pipeline (chip-fetcher -> step2glb -> chiplinter -> chipfit -> KiCad footprint writer).
-
-## Footprint source matters (CRITICAL)
-
-When injecting a model reference for a non-standard component, the STEP model and the footprint MUST come from the same source:
-
-- The STEP model from chip-fetcher was designed to match the chip-fetcher footprint (.kicad_mod), **NOT** the tscircuit-exported footprint
-- If you source a STEP from SnapMagic/Mouser/manufacturer, you must ALSO use the footprint from that same source
-- Replace the entire footprint block in the .kicad_pcb, preserving only the board placement coordinates `(at X Y rot)`
-
-**Why this matters:** A tscircuit footprint and a SnapMagic footprint for the same component (e.g. USB-C HRO TYPE-C-31-M-12) have completely different pad positions and mounting hole locations. Bolting the SnapMagic STEP onto the tscircuit footprint causes pads to be misaligned even though the STEP renders in the 3D viewer -- the model just floats above the wrong pad locations.
-
-**How to replace a footprint block:**
-1. Open the chip-fetcher .kicad_mod file for the component
-2. Find the matching `(footprint ...)` block in the .kicad_pcb
-3. Note the `(at X Y rot)` line from the original block (board placement)
-4. Replace the entire footprint block with the chip-fetcher version
-5. Update the `(at ...)` line to use the original board placement coordinates
-6. Ensure the `(model ...)` reference points to the STEP from the same source
-
-## Checklist before reporting success
-
-- [ ] Model count matches expected component count
-- [ ] Screenshot shows 3D component packages (not a bare green board)
-- [ ] USB-C connector visible if present
-- [ ] LED packages visible and arranged in expected pattern
-- [ ] No error dialogs in KiCad
+---+name: kicad-3d-models+description: Add standard KiCad 3D models to a .kicad_pcb file (especially tscircuit exports that ship without model references), open the 3D viewer in KiCad on the user's desktop, and screenshot to verify. Trigger words — 3d models in kicad, kicad 3d viewer, alt+3, no 3d chips, missing 3d models, add 3d models, kicad 3d screenshot, show me 3d in kicad, prove 3d works, kicad models missing.+---++# kicad-3d-models++**Prerequisite: Read `kicad-interaction` skill first.** It covers state checks, window management, and dialog handling that this skill depends on.++Add KiCad-standard 3D model references to `.kicad_pcb` files, push to the user's desktop, open the 3D viewer, and screenshot to prove it worked.++## When to use++- User opens a tscircuit-exported `.kicad_pcb` in KiCad and sees no 3D models (Alt+3 shows bare board)+- User asks to add 3D models to a KiCad PCB+- User wants to see/screenshot the KiCad 3D viewer++## Why tscircuit exports lack 3D models++`tsci export -f kicad_pcb` produces valid PCB layouts (pads, traces, silkscreen, board outline) but does **not** inject `(model ...)` entries into footprint blocks. KiCad's 3D viewer needs those entries to know which `.step`/`.wrl` file to render for each component.++## Step 1 — Discover the KiCad 3D model environment variable++KiCad versions use version-specific env vars. Query `service-kicad` to get the correct one:++```bash+service-kicad fp fetch Resistor_SMD R_0402_1005Metric 2>&1 | grep "model"+```++This returns something like:+```+(model "${KICAD10_3DMODEL_DIR}/Resistor_SMD.3dshapes/R_0402_1005Metric.step"+```++The `${KICAD10_3DMODEL_DIR}` prefix is what you need. Adjust for the KiCad version on the user's desktop.++## Step 2 — Map footprint types to 3D models++List the unique footprint types in the `.kicad_pcb`:++```bash+grep -A1 "(footprint" board.kicad_pcb | grep '"tscircuit:' | sort -u+```++Then build a mapping. Common tscircuit footprint → KiCad 3D model mappings:++| tscircuit footprint | KiCad 3D model path |+|---|---|+| `tscircuit:resistor_0402` | `Resistor_SMD.3dshapes/R_0402_1005Metric.step` |+| `tscircuit:resistor_0603` | `Resistor_SMD.3dshapes/R_0603_1608Metric.step` |+| `tscircuit:resistor_0805` | `Resistor_SMD.3dshapes/R_0805_2012Metric.step` |+| `tscircuit:capacitor_0402` | `Capacitor_SMD.3dshapes/C_0402_1005Metric.step` |+| `tscircuit:capacitor_0603` | `Capacitor_SMD.3dshapes/C_0603_1608Metric.step` |+| `tscircuit:capacitor_0805` | `Capacitor_SMD.3dshapes/C_0805_2012Metric.step` |+| `tscircuit:led_0603_color(...)` | `LED_SMD.3dshapes/LED_0603_1608Metric.step` |+| `tscircuit:TYPE-C-31-M-12` | `Connector_USB.3dshapes/USB_C_Receptacle_HRO_TYPE-C-31-M-12.step` |+| `tscircuit:chip` (machine pins) | Skip or use a small placeholder |++For non-standard footprints, use `service-kicad fp fetch <library> <name>` to look up the model path. Common KiCad 3D model libraries:++- `Resistor_SMD`, `Resistor_THT`+- `Capacitor_SMD`, `Capacitor_THT`+- `LED_SMD`, `LED_THT`+- `Connector_USB`, `Connector_PinHeader`+- `Package_SO`, `Package_QFP`, `Package_BGA`, `Package_DFN_QFN`++## Step 3 — Inject model entries++Write a Python script that:+1. Reads the `.kicad_pcb` file+2. For each `(footprint ...)` block, identifies the footprint type from the line after `(footprint`+3. Finds the closing `)` of the footprint block (by tracking paren depth)+4. Inserts a `(model ...)` block before the closing paren++Model block format:+```+    (model "${KICAD10_3DMODEL_DIR}/Library.3dshapes/ModelName.step"+      (offset (xyz 0 0 0))+      (scale (xyz 1 1 1))+      (rotate (xyz 0 0 0))+    )+```++Verify the result:+```bash+grep "KICAD.*3DMODEL" board.kicad_pcb | sort | uniq -c+```++## Step 4 — Send to desktop and open in KiCad++**CRITICAL: Close all existing KiCad windows BEFORE opening the updated file.** Opening the same file twice creates "File Open Warning" dialogs and leaves stale editors around.++```bash+# Always close first+adom-desktop kicad_close '{}'++# Send files (send PCB and any sidecar STEP/WRL separately if >1MB total to avoid 413)+adom-desktop send_files '{"filePaths":["/path/to/board.kicad_pcb"],"category":"kicad"}'+adom-desktop send_files '{"filePaths":["/path/to/USB_C_model.wrl"],"category":"kicad"}'++# Open fresh+adom-desktop kicad_open_board '{"filePath":"C:/Users/john/Downloads/board.kicad_pcb"}'+```++Wait for the PCB editor to load, then check for dialogs:+```bash+adom-desktop kicad_window_info '{}'+```++## Step 5 — Open the 3D viewer and screenshot++```bash+# Open the 3D viewer (equivalent to Alt+3)+adom-desktop kicad_open_3d_viewer '{"hwnd": <pcb_editor_hwnd>}'++# Wait for rendering, then check the viewer window appeared+sleep 5+adom-desktop kicad_window_info '{}'++# Screenshot the 3D viewer window+adom-desktop desktop_screenshot_window '{"hwnd": <3d_viewer_hwnd>}'+```++The screenshot saves to `/tmp/adom-desktop-screenshots/`. The file is already on Docker — read it with the Read tool to verify the 3D models rendered correctly.++## Handling missing models (e.g. manufacturer-specific USB-C connectors)++Some footprints reference 3D models that aren't in KiCad's standard `packages3D` distribution. Common case: `USB_C_Receptacle_HRO_TYPE-C-31-M-12` ships in the KiCad footprint library but has no matching STEP in packages3D.++**Fix: relative path + ship the STEP alongside the PCB.**++1. Find a visually similar model that IS available:+   ```bash+   service-kicad model fetch "Connector_USB.3dshapes/USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal.step" --out /tmp/usbc-standin.step+   ```+2. Rename it to match the expected filename and place it next to the `.kicad_pcb`+3. Change the model reference in the PCB to a relative path:+   ```+   (model "./USB_C_Receptacle_HRO_TYPE-C-31-M-12.step" ...)+   ```+4. Send BOTH the `.kicad_pcb` and the `.step` file to the desktop (send separately if the STEP triggers a 413 payload-too-large error)++Test with `service-kicad model fetch` first — if it 404s, the model isn't in packages3D and needs this workaround.++**CRITICAL: WRL unit conversion.** KiCad interprets WRL/VRML coordinates as **inches**. If you convert an OBJ/GLB/STL (which are typically in mm) to WRL, you MUST divide all vertex coordinates by 25.4. Without this, the model renders 25.4x too large. STEP files don't have this problem — KiCad reads STEP as mm natively.++## Up-axis rotation convention++STEP files are authored with different up-axis conventions. KiCad uses Z-up. When injecting `(model ...)` references, read `chosen_up_axis` from chip-fetcher's `info.json` to determine the correct rotation:++| `chosen_up_axis` | Source convention | KiCad rotation needed |+|---|---|---|+| `z` | Z-up (KiCad native) | `(rotate (xyz 0 0 0))` |+| `y` | Y-up (Fusion 360, many vendors) | `(rotate (xyz -90 0 0))` |++If `info.json` says `chosen_up_axis: "y"` and you use `(rotate (xyz 0 0 0))`, the model will render sideways or upside-down in KiCad. chip-fetcher's dashboard shows the orientation visually (Z-up/Y-up thumbnail icons) -- check it BEFORE writing the model reference.++See the [`board-building-pipeline`](../board-building-pipeline/SKILL.md) skill for the full up-axis table and how `chosen_up_axis` flows through the entire pipeline (chip-fetcher -> step2glb -> chiplinter -> chipfit -> KiCad footprint writer).++## Footprint source matters (CRITICAL)++When injecting a model reference for a non-standard component, the STEP model and the footprint MUST come from the same source:++- The STEP model from chip-fetcher was designed to match the chip-fetcher footprint (.kicad_mod), **NOT** the tscircuit-exported footprint+- If you source a STEP from SnapMagic/Mouser/manufacturer, you must ALSO use the footprint from that same source+- Replace the entire footprint block in the .kicad_pcb, preserving only the board placement coordinates `(at X Y rot)`++**Why this matters:** A tscircuit footprint and a SnapMagic footprint for the same component (e.g. USB-C HRO TYPE-C-31-M-12) have completely different pad positions and mounting hole locations. Bolting the SnapMagic STEP onto the tscircuit footprint causes pads to be misaligned even though the STEP renders in the 3D viewer -- the model just floats above the wrong pad locations.++**How to replace a footprint block:**+1. Open the chip-fetcher .kicad_mod file for the component+2. Find the matching `(footprint ...)` block in the .kicad_pcb+3. Note the `(at X Y rot)` line from the original block (board placement)+4. Replace the entire footprint block with the chip-fetcher version+5. Update the `(at ...)` line to use the original board placement coordinates+6. Ensure the `(model ...)` reference points to the STEP from the same source++## Checklist before reporting success++- [ ] Model count matches expected component count+- [ ] Screenshot shows 3D component packages (not a bare green board)+- [ ] USB-C connector visible if present+- [ ] LED packages visible and arranged in expected pattern+- [ ] No error dialogs in KiCad