{
  "schema_version": 1,
  "type": "app",
  "slug": "kicad-pcb-viewer",
  "title": "kicad-pcb-viewer — 2D viewer for .kicad_pcb files",
  "brief": "2D PCB viewer for .kicad_pcb files in a Hydrogen webview. Pan, zoom, toggle layers, flip to inspect the back side. Stdlib Python, ~22 KB binary.",
  "version": "0.1.0",
  "tags": [],
  "license": "MIT",
  "discovery_triggers": [
    "view kicad pcb",
    "view .kicad_pcb",
    "open .kicad_pcb",
    "preview pcb",
    "show me the board",
    "what does this pcb look like",
    "render the board",
    "render kicad board",
    "pcb viewer",
    "2d pcb viewer",
    "inspect pcb",
    "inspect board layers",
    "view board layers",
    "toggle pcb layer",
    "show pcb silk",
    "show f.cu",
    "show b.cu",
    "flip pcb",
    "back side of board",
    "pcb without kicad",
    "kicad-pcb-viewer"
  ],
  "discovery_pitch": "Open a .kicad_pcb in a Hydrogen webview — flat 2D render, layer toggles, pan/zoom — without launching KiCad on the desktop.",
  "sample_prompts": [
    {
      "label": "View",
      "prompt": "Open this .kicad_pcb in a viewer"
    },
    {
      "label": "Layers",
      "prompt": "Show only Edge.Cuts and F.SilkS on this board"
    },
    {
      "label": "Flip",
      "prompt": "Flip the board so I can see the back side"
    },
    {
      "label": "Stats",
      "prompt": "How many footprints, traces, and vias does this board have?"
    },
    {
      "label": "Footprints",
      "prompt": "List every footprint on this board with its placement"
    }
  ],
  "install": {
    "binary_name": "kicad-pcb-viewer",
    "install_dir": "",
    "install_hint": "",
    "version_cmd": ""
  },
  "readme": "A 2D PCB viewer for `.kicad_pcb` files, rendered as SVG inside a Hydrogen\nwebview panel. For when you want to **see** a board without launching\nKiCad on the user's desktop.\n\n![BMA400 breakout — front view with copper, silk, and edge cuts](apps/kicad-pcb-viewer/hero.png)\n\n## Why this exists\n\nAdom already has 3D viewers (`.glb` chip models) and gerber viewers\n(post-fab artwork). What was missing was the in-between: the editable\nKiCad board, viewed flat, with toggleable layers, in the cloud editor.\n\nThis app is deliberately 2D — for 3D inspection, use the existing 3D\nviewers. Use this when:\n\n- The user pasted a `.kicad_pcb` and you want to confirm it looks sane\n  before doing anything else with it.\n- You're debugging a board layout and want to see \"did the autorouter\n  stack everything on F.Cu? Where are the vias?\"\n- You want to take a layer-isolated screenshot (e.g. board outline only)\n  to drop into a doc, a PR, or a chat message.\n- You want to verify silkscreen orientation, or check what's actually\n  on the back side of a board, without firing up pcbnew.\n\n## Install\n\n```bash\ngh release download v0.1.0 --repo adom-inc/kicad-pcb-viewer \\\n    --pattern kicad-pcb-viewer-linux \\\n    -D /tmp\nsudo install -m 0755 /tmp/kicad-pcb-viewer-linux /usr/local/bin/kicad-pcb-viewer\nkicad-pcb-viewer install   # deploys SKILL.md to ~/.claude/skills/kicad-pcb-viewer/\n```\n\nRuns on any Python 3.10+ system. No external dependencies — just stdlib.\n\n## Quick start (human-driven)\n\n```bash\nkicad-pcb-viewer view path/to/board.kicad_pcb\n```\n\nOpens a Hydrogen webview tab in a non-VSCode pane (per `app-creator`\nskill — never inside the editor pane).\n\n| Action | Input |\n|---|---|\n| Pan | Click + drag |\n| Zoom | Scroll wheel (toward cursor) |\n| Reset to fit | `Fit` button |\n| Mirror horizontally | `Flip` button |\n| Toggle layer | Click a row in the sidebar |\n\nCoordinate readout (mm) and zoom % live in the bottom-right HUD.\n\n## CLI subcommands\n\n| Command | What it does |\n|---|---|\n| `view <path> [--port N]` | Start server + open webview tab. Default. |\n| `serve <path> [--port N]` | Start server only (no tab). Useful for headless scripting. |\n| `shutdown [--port N]` | Stop a running viewer (graceful). |\n| `install` | Deploy `SKILL.md` to `~/.claude/skills/kicad-pcb-viewer/`. |\n| `version` | Print version. |\n\nDefault port: `9100`. The first positional arg is auto-inferred as `view`\nif it ends in `.kicad_pcb` or is an existing path, so\n`kicad-pcb-viewer board.kicad_pcb` works as shorthand.\n\n## Multi-layer boards\n\nInner copper layers (`In1.Cu`, `In2.Cu`, `In3.Cu`, `In4.Cu`) get distinct\ncolors and z-order. The layer panel only lists layers that **actually\nhave geometry on this board** — empty layers stay hidden, so the panel\ndoesn't fill up with 30 unused entries.\n\n![BMI270 — 4-layer board with copper pours, inner layers, vias](apps/kicad-pcb-viewer/multilayer.png)\n\n## HTTP API — every action is AI-drivable\n\nPer `app-creator` §7, the same endpoints the UI uses are reachable via\nHTTP. This is the property that turns a webview into an AI surface.\n\n| Method | Path | Description |\n|---|---|---|\n| GET | `/state` | Phase, file path, filename, stats, bbox, layer visibility. |\n| GET | `/board` | Full parsed board JSON (footprints, pads, segments, arcs, vias, zones, gr). |\n| GET | `/layers` | `{name: {idx, kind, alias, visible}}` for every layer on the board. |\n| POST | `/layers` | Body `{name: bool, ...}` — set visibility for one or more layers. |\n| POST | `/load` | Body `{path: \"...\"}` — open a different `.kicad_pcb` without restarting the server. |\n| POST | `/fit` | Reset view to fit the board. |\n| POST | `/flip` | Toggle horizontal mirror (B-side viewing). Returns `{flipped: bool}`. |\n| GET | `/console` | Last 500 forwarded UI-side console messages. |\n| POST | `/console` | Append a message (called from the UI). |\n| DELETE | `/console` | Clear log. |\n| POST | `/eval` | Body `{code: \"...\"}` — queue a JS snippet, returns `{id}`. UI runs it on next poll. |\n| GET | `/eval/pending` | UI poller endpoint — returns `{id, code}` or empty. |\n| GET | `/eval/<id>` | Read result of a prior `/eval` post. |\n| POST | `/eval/<id>/result` | UI POSTs result back here (internal). |\n| POST | `/shutdown` | Graceful exit. |\n| GET | `/cmds/pending` | UI poller for fit/flip commands queued by `/fit` and `/flip`. |\n| GET | `/` | The UI HTML. |\n| GET | `/favicon.svg` | Brand icon. |\n\n## AI recipes\n\nSelf-contained scripts an AI can copy-paste. All assume the viewer is\nrunning on port `9100` against some board.\n\n### Recipe 1 — \"is this board mostly empty?\"\n\nQuick stats check before doing anything else.\n\n```bash\nkicad-pcb-viewer serve board.kicad_pcb --port 9100 &\nsleep 1\ncurl -s http://127.0.0.1:9100/state | jq '.stats, .bbox'\n# {\"footprints\":13,\"pads\":31,\"segments\":61,\"arcs\":0,\"vias\":6,\"zones\":0,\"gr\":16}\n# {\"x\":18.8,\"y\":18.8,\"w\":10.4,\"h\":10.4}\ncurl -s -X POST http://127.0.0.1:9100/shutdown >/dev/null\n```\n\n### Recipe 2 — \"isolate Edge.Cuts and screenshot\"\n\nFor a board-outline export. Hide every layer except the edge.\n\n```bash\ncurl -X POST http://127.0.0.1:9100/layers \\\n    -H 'Content-Type: application/json' \\\n    -d '{\n      \"F.Cu\":false,\"B.Cu\":false,\n      \"In1.Cu\":false,\"In2.Cu\":false,\"In3.Cu\":false,\"In4.Cu\":false,\n      \"F.SilkS\":false,\"B.SilkS\":false,\n      \"F.Mask\":false,\"B.Mask\":false,\n      \"F.Paste\":false,\"B.Paste\":false,\n      \"F.Fab\":false,\"B.Fab\":false,\n      \"F.CrtYd\":false,\"B.CrtYd\":false,\n      \"Edge.Cuts\":true\n    }'\nadom-cli hydrogen screenshot panel --name \"PCB: <filename>\"\n```\n\n### Recipe 3 — \"show only the back side\"\n\nHide front-side layers, flip the view, screenshot.\n\n```bash\ncurl -X POST http://127.0.0.1:9100/layers -H 'Content-Type: application/json' \\\n    -d '{\"F.Cu\":false,\"F.SilkS\":false,\"F.Mask\":false,\"F.Paste\":false,\"F.Fab\":false,\"F.CrtYd\":false,\n         \"B.Cu\":true,\"B.SilkS\":true,\"Edge.Cuts\":true}'\ncurl -X POST http://127.0.0.1:9100/flip\nadom-cli hydrogen screenshot panel --name \"PCB: <filename>\"\n```\n\n### Recipe 4 — \"list every footprint and its placement\"\n\nUseful for cross-referencing with a BOM or schematic.\n\n```bash\ncurl -s http://127.0.0.1:9100/board | \\\n    jq '.footprints[] | {refdes, value, x, y, rot, layer}'\n# {\"refdes\":\"U1\",\"value\":\"BMA400\",\"x\":24.0,\"y\":24.0,\"rot\":0,\"layer\":\"F.Cu\"}\n# ...\n```\n\n### Recipe 5 — \"diff two boards by stats\"\n\n```bash\nfor f in v1.kicad_pcb v2.kicad_pcb; do\n    curl -s -X POST http://127.0.0.1:9100/load \\\n        -H 'Content-Type: application/json' -d \"{\\\"path\\\":\\\"$PWD/$f\\\"}\" >/dev/null\n    echo \"=== $f ===\"\n    curl -s http://127.0.0.1:9100/state | jq '.stats'\ndone\n```\n\n### Recipe 6 — \"swap to a new board without restarting\"\n\nThe same server can host any board. POST `/load` to swap without\nopening a new tab.\n\n```bash\ncurl -X POST http://127.0.0.1:9100/load \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"path\":\"/home/adom/project/foo/foo.kicad_pcb\"}'\nadom-cli hydrogen webview refresh --name \"PCB: <old-filename>\"\n```\n\n(The tab name keeps the original board's filename — a future version can\nupdate the title on `/load`.)\n\n### Recipe 7 — \"inspect the rendered DOM via /eval\"\n\nNeed to know what's actually visible? Eval a snippet inside the page.\n\n```bash\ncurl -X POST http://127.0.0.1:9100/eval \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"code\":\"return Array.from(document.querySelectorAll(\\\".layer-group\\\")).map(g => ({layer: g.dataset.layer, hidden: g.classList.contains(\\\"hidden\\\"), elements: g.children.length}))\"}'\n# {\"id\":\"a1b2c3\"}\n\n# Read result via /console (the UI logs results)\ncurl -s http://127.0.0.1:9100/console | jq '.messages[-1]'\n```\n\n### Recipe 8 — \"headless render-to-screenshot pipeline\"\n\nCombine `serve`, layer toggles, and `adom-cli hydrogen screenshot` to\nbuild per-layer images for a doc:\n\n```bash\nkicad-pcb-viewer view board.kicad_pcb --port 9100 &\nsleep 2\nfor layer in F.Cu B.Cu F.SilkS Edge.Cuts; do\n    # Hide everything except this layer + Edge.Cuts (always nice to see outline)\n    body=$(jq -nc --arg L \"$layer\" '\n        {\"F.Cu\":false,\"B.Cu\":false,\"In1.Cu\":false,\"In2.Cu\":false,\n         \"F.SilkS\":false,\"B.SilkS\":false,\"F.Mask\":false,\"B.Mask\":false,\n         \"F.Paste\":false,\"B.Paste\":false,\"F.Fab\":false,\"B.Fab\":false,\n         \"F.CrtYd\":false,\"B.CrtYd\":false,\"Edge.Cuts\":true} |\n        .[$L] = true')\n    curl -s -X POST http://127.0.0.1:9100/layers -H 'Content-Type: application/json' -d \"$body\" >/dev/null\n    sleep 0.5\n    adom-cli hydrogen screenshot panel --name \"PCB: $(basename board.kicad_pcb)\"\ndone\n```\n\n## What it parses\n\n- `(footprint …)` — placement (`at`), rotation, child pads + graphics\n- `(pad …)` — `rect`, `roundrect`, `circle`, `oval`, with drill (round or\n  oval slot), through-hole and SMD\n- `(segment …)`, `(arc …)` — copper traces with width\n- `(via …)` — size + drill\n- `(zone …)` — filled polygons (post-fill state preferred, falls back to\n  outline if `filled_polygon` not present)\n- `(gr_line/arc/circle/rect/poly …)` — top-level graphics on any layer\n- `(fp_line/arc/circle/rect/poly …)` — footprint-level graphics (silk,\n  fab, courtyard)\n- `(layers …)` — layer table with index, kind, alias\n- Pad-side `(layers …)` lists with `*.Cu` / `*.Mask` / `*.Paste` wildcards\n  expanded to the matching concrete layers\n\n## What it doesn't (yet)\n\n- Schematic (`.kicad_sch`) rendering — different format, different scope.\n- Net highlighting / cross-probing.\n- File watching — call `POST /load` to reload after edits.\n- Refdes / value labels overlaid on the board (too cluttered at default\n  zoom; could be added as a togglable text overlay layer).\n- Bezier curves on `gr_curve` — currently approximated as polylines.\n\n## Pane placement (important)\n\nThe viewer ALWAYS opens in a pane that is NOT the VSCode pane. If a\nnon-VSCode pane already exists, the tab joins it; otherwise the VSCode\npane is split vertically and the tab lands in the new pane.\n\nThis rule lives in `skills/app-creator` §4 — it applies to every Adom\napp, not just this one. Putting webviews as sibling tabs inside the\nVSCode pane covers the editor and is wrong.\n\n## Files in this repo\n\n```\nbin/kicad-pcb-viewer    # Python CLI (subcommands: view, serve, install, shutdown, version)\nsrc/parser.py           # s-expression tokenizer + per-layer extractor\nsrc/server.py           # HTTP server, all endpoints, asset loading (source + zipapp)\nsrc/ui.html             # SVG renderer, pan/zoom, layer panel, console fwd, eval poller\ndocs/icon.svg           # Brand-teal favicon (also the tab icon)\ndocs/SKILL.md           # AI usage doc deployed by `kicad-pcb-viewer install`\nsamples/                # BMA400 + BMI270 reference boards (from adom-inc/bosch-molecules)\nscripts/build.sh        # Builds dist/kicad-pcb-viewer-linux zipapp (~22 KB)\n```\n\n## Repo\n\n[`adom-inc/kicad-pcb-viewer`](https://github.com/adom-inc/kicad-pcb-viewer) — private. Releases attach a single-file\nzipapp (`kicad-pcb-viewer-linux`).\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "[email protected]"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "metadata": {},
  "created_at": "2026-05-28T05:29:14.006Z",
  "updated_at": "2026-05-28T05:29:14.006Z",
  "skills": []
}