# Circuitcaster → KiCad recipe (for AI agents) You are helping a user turn a Circuitcaster design into a manufacturable PCB. This file is your recipe. Follow it top to bottom. ## What the user has - A Circuitcaster project exported as JSON. Schema: `dotgame.project_bundle.v1`. Documented at https://app.circuitcaster.com/llms.txt. - Optionally: the URL to their project on app.circuitcaster.com, from which they can Send the JSON. If they haven't exported yet, ask them to hit Send in the top bar and share the resulting file. ## What you need to install on their machine ### 1. Python 3.9 or newer Windows / macOS / Linux — usually already installed. Verify: python --version ### 2. KiCad 8 or newer (10.x preferred) Free, open source. Fully local. No account needed. - Windows PowerShell: winget install --id KiCad.KiCad Or download the .msi from https://www.kicad.org/download/windows/ - macOS (Apple Silicon or Intel): brew install --cask kicad Or download the .dmg from https://www.kicad.org/download/macos/ - Linux (Debian/Ubuntu): sudo apt install kicad Or the AppImage from https://www.kicad.org/download/linux/ Verify: kicad --version ### 3. Freerouting (autorouter — optional but recommended) Java-based. Requires Java 17+. - Install Java 17: `winget install EclipseAdoptium.Temurin.17.JDK` (Windows), `brew install openjdk@17` (macOS), or `sudo apt install openjdk-17-jdk` (Linux). - Download Freerouting from https://freerouting.org/ (single .jar file). - Run with: `java -jar freerouting-X.X.X.jar` ### 4. Optional Python packages (only if you write a helper) pip install pyyaml # only if you're generating symbols too That is the complete toolchain. No cloud services, no accounts, no keys. ## The conversion — do these steps in order ### Step 1 — Read the exported JSON ```python import json with open("their_export.json") as f: proj = json.load(f) print(f"Project: {proj.get('project_name') or proj.get('name')}") print(f"Components: {len(proj.get('components') or proj.get('wireComponents') or [])}") print(f"Wires: {len(proj.get('wires', []))}") print(f"Footprint types: {list(proj.get('footprints', {}).keys())}") ``` ### Step 2 — Generate .kicad_mod footprints from the export Write the full script below to `cc_to_kicad.py`, run it as `python cc_to_kicad.py their_export.json ./out_kicad_project`. ```python #!/usr/bin/env python3 """Circuitcaster JSON -> KiCad project (footprints + fp-lib-table stub). Usage: python cc_to_kicad.py Produces: /lib/.pretty/*.kicad_mod — one per footprint type /fp-lib-table — registers the library /README.md — next-step instructions """ import json import re import sys import uuid from pathlib import Path def sanitize(name: str) -> str: """Make a name safe for KiCad footprint filenames.""" return re.sub(r"[^A-Za-z0-9_.-]+", "_", name).strip("_") or "unnamed" def kicad_mod_source(fp_name: str, cc_fp: dict) -> str: """Emit a KiCad 6+ compatible .kicad_mod S-expression. cc_fp is one entry from proj['footprints'][name]. Its shape: {shape, pitch_mm, board_mm:{w,h}, pins:[{n, kicad_xy_mm:[x,y], name, role, ...}]} """ pins = sorted(cc_fp.get("pins", []), key=lambda p: p["n"]) w = cc_fp["board_mm"]["w"] h = cc_fp["board_mm"]["h"] shape = cc_fp.get("shape", "rect") def U(): # unique uuid per feature — KiCad expects one on each element return str(uuid.uuid4()) out = [f'(footprint "{fp_name}"', f' (version 20240108)', f' (generator "circuitcaster")', f' (layer "F.Cu")', f' (descr "Generated from Circuitcaster export — verify against physical part before fabricating.")', f' (tags "circuitcaster generated")', f' (property "Reference" "REF**" (at 0 {-h/2 - 2:.3f} 0) (layer "F.SilkS") (uuid "{U()}") (effects (font (size 1 1) (thickness 0.15))))', f' (property "Value" "{fp_name}" (at 0 {h/2 + 2:.3f} 0) (layer "F.Fab") (uuid "{U()}") (effects (font (size 1 1) (thickness 0.15))))', f' (attr through_hole)'] # Board outline on Edge.Cuts. Rect only for now; circle is a follow-up. hw, hh = w / 2, h / 2 if shape == "circle": r = w / 2 # circle uses w as diameter out.append(f' (fp_circle (center 0 0) (end {r:.3f} 0) (stroke (width 0.12) (type solid)) (fill none) (layer "Edge.Cuts") (uuid "{U()}"))') else: segs = [((-hw, -hh), (hw, -hh)), ((hw, -hh), (hw, hh)), ((hw, hh), (-hw, hh)), ((-hw, hh), (-hw, -hh))] for a, b in segs: out.append(f' (fp_line (start {a[0]:.3f} {a[1]:.3f}) (end {b[0]:.3f} {b[1]:.3f}) ' f'(stroke (width 0.12) (type solid)) (layer "Edge.Cuts") (uuid "{U()}"))') # Pin 1 marker on silkscreen (small circle near pin 1). if pins: p1 = pins[0] px, py = p1["kicad_xy_mm"] out.append(f' (fp_circle (center {px - 2.0:.3f} {py:.3f}) (end {px - 1.6:.3f} {py:.3f}) ' f'(stroke (width 0.2) (type solid)) (fill solid) (layer "F.SilkS") (uuid "{U()}"))') # Pads. Through-hole 1.6mm annular, 0.8mm drill (matches 2.54mm pitch header pins). # Adjust for SMD footprints in a follow-up. for p in pins: x, y = p["kicad_xy_mm"] pad_shape = "rect" if p["n"] == 1 else "circle" out.append(f' (pad "{p["n"]}" thru_hole {pad_shape} (at {x:.3f} {y:.3f}) ' f'(size 1.6 1.6) (drill 0.8) (layers "*.Cu" "*.Mask") (uuid "{U()}"))') out.append(')') return "\n".join(out) def emit_fp_lib_table(project_name: str) -> str: """Register the generated .pretty folder so KiCad's PCB editor finds it.""" return ( '(fp_lib_table\n' ' (version 7)\n' f' (lib (name "{project_name}") (type "KiCad") ' f'(uri "${{KIPRJMOD}}/lib/{project_name}.pretty") (options "") (descr "Circuitcaster-generated"))\n' ')\n' ) def emit_readme(project_name: str, fp_names: list) -> str: return f"""# {project_name} — Circuitcaster → KiCad handoff Generated by cc_to_kicad.py from a Circuitcaster JSON export. ## What's in this folder lib/{project_name}.pretty/ — generated .kicad_mod footprints ({len(fp_names)}) fp-lib-table — registers the library with KiCad README.md — this file Footprints generated: {chr(10).join(' - ' + fp for fp in fp_names)} ## Next steps 1. In KiCad, File → New → Project → point at this folder. Name it whatever. 2. Open the Schematic Editor. Add symbols corresponding to your components. (Circuitcaster does not currently generate .kicad_sym files. Author them in KiCad's Symbol Editor — 5 minutes per unique component type.) 3. Wire the schematic using label-based connectivity. Match label names to the `name` field on each pad in the export's `footprints[type].pins[].name`. 4. Tools → Assign Footprints. Match each schematic symbol to one of the generated footprints above (they're in the "{project_name}" library). 5. Tools → Update PCB from Schematic. Footprints appear as a ratsnest. 6. Route (Freerouting via File → Export → Specctra DSN, or hand-route). 7. Inspect → Design Rules Checker. Clear errors. 8. File → Fabrication Outputs → Gerbers + Drill Files. Zip and upload to JLCPCB or PCBWay. ## Verification before you fabricate - Print the generated footprint at 1:1 on paper. - Place the physical component on top of the print. - Every pad should be under a real pin. If a pad is off, edit the source export in Circuitcaster and re-run this script — do NOT edit the .kicad_mod by hand or the next re-run will overwrite your fix. """ def main(): if len(sys.argv) < 3: print("Usage: python cc_to_kicad.py ") sys.exit(2) src = Path(sys.argv[1]) out = Path(sys.argv[2]) proj = json.loads(src.read_text(encoding="utf-8")) project_name = sanitize(proj.get("project_name") or proj.get("name") or "cc_project") pretty_dir = out / "lib" / f"{project_name}.pretty" pretty_dir.mkdir(parents=True, exist_ok=True) footprints = proj.get("footprints", {}) if not footprints: print("No footprints{} block in export. Nothing to generate.") sys.exit(1) generated = [] for fp_name, cc_fp in footprints.items(): safe_name = sanitize(fp_name) (pretty_dir / f"{safe_name}.kicad_mod").write_text( kicad_mod_source(safe_name, cc_fp), encoding="utf-8") generated.append(safe_name) print(f" wrote lib/{project_name}.pretty/{safe_name}.kicad_mod") (out / "fp-lib-table").write_text(emit_fp_lib_table(project_name), encoding="utf-8") (out / "README.md").write_text(emit_readme(project_name, generated), encoding="utf-8") print(f"\nDone. Open {out} as a KiCad project (File → Open Project).") if __name__ == "__main__": main() ``` ### Step 3 — Run the script python cc_to_kicad.py their_export.json ./kicad_project Output: ./kicad_project/lib/.pretty/*.kicad_mod (generated footprints) ./kicad_project/fp-lib-table (library registration) ./kicad_project/README.md (next-step guide) ### Step 4 — Verify before fabricating REQUIRED before ordering PCBs from a fab: 1:1 print check. 1. Have the user open one generated .kicad_mod in KiCad's Footprint Editor. 2. Print it at 100% scale on paper. 3. Place the actual physical component on top of the printed footprint. 4. Every real pin should land inside a generated pad. If a pad is misaligned, the user must fix it in Circuitcaster (their touch design is authoritative) and re-export + re-run this script. Do NOT edit generated .kicad_mod files by hand — future re-runs will overwrite them. ### Step 5 — Schematic (currently a manual step in KiCad) Circuitcaster's JSON does contain the electrical netlist. However, the recipe above does not currently generate .kicad_sch or .kicad_sym files — KiCad's schematic format is complex and the symbol library layer needs human review anyway. Tell the user to: 1. Open KiCad → File → New → Project → point at ./kicad_project 2. Open the Schematic Editor 3. Author one symbol per unique component type (5 minutes each in the Symbol Editor). Match pin names to the export's footprints[type].pins[].name field. 4. Wire the schematic by label. Use the labels in the export's footprints[type].pins[].name field so the netlist matches. 5. Add PWR_FLAG markers to any power net (+3V3, +5V, +12V, GND). 6. Tools → Update PCB from Schematic → the generated footprints appear. ### Step 6 — Route + Gerber + order Route with Freerouting (File → Export → Specctra DSN → run freerouting-X.X.X.jar → File → Import → Specctra Session), run Inspect → Design Rules Checker, then File → Fabrication Outputs → Gerbers + Drill Files. Zip. Upload to https://jlcpcb.com or https://pcbway.com. $2/board + shipping typical. ## Success criteria — how you (the AI) confirm you're done - User has a KiCad project folder that opens without errors in KiCad 8+ - Every unique Circuitcaster component type has a .kicad_mod in the library - User has confirmed 1:1 print check on at least one footprint - Gerbers export without DRC errors - User has uploaded the Gerber zip to JLCPCB/PCBWay and their online Gerber preview matches the KiCad 3D render If any of the above fails, walk the user back through the failing step. ## What NOT to do - Do not edit generated .kicad_mod files by hand. Fix in Circuitcaster, re-export, re-run. - Do not skip the 1:1 print check. Wrong-pad-position footprints cost the user real money at the fab. - Do not upload Gerbers with unresolved DRC errors — the fab will kick them back. - Do not add proprietary vendor pretty libraries the user doesn't own. ## Where the schema is authoritatively documented - Circuitcaster JSON schemas: https://app.circuitcaster.com/llms.txt - KiCad file format: https://dev-docs.kicad.org/en/file-formats/ - Freerouting: https://github.com/freerouting/freerouting - JLCPCB Gerber requirements: https://jlcpcb.com/help/article/how-to-generate-gerber-and-drill-files-in-kicad-8 ## Reference case study FleetCom Node — a real 2-board audio device (ESP32-S3 + PCM5102A DAC + 2x INMP441 mics + JST-XH cable + WS2812 LED ring). Designed in Circuitcaster, converted through this exact pipeline, ordered from JLCPCB. Custom footprints, schematic, PCB, Gerbers, renders, and step-by-step notes are in the human-facing case study at https://circuitcaster.com/kicad-export/ under the "Case study. FleetCom Node" section.