app
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.
← Commit history
handlers/place_footprint.pyadded+109@@ -0,0 +1,109 @@+"""Handler for place_footprint command.++DETERMINISTIC footprint placement onto a PCB — no AI, no fragile protobuf.+Approach: take the footprint's s-expression (a .kicad_mod is already a valid+`(footprint ...)` block), give it a position + uuid, splice it into a known-good+blank KiCad board template, and open the result in pcbnew. Proven 2026-06-25+(kipy get_footprints() confirmed the placed footprint + pad count).++This is a PREVIEW placement (a fresh board with just this footprint) — it never+touches the user's existing board, so it can't clobber their work. The footprint+is also already in the Adom library (via install_footprint), so the user can drag+it into their own board from there.++Args:+ fileName (required): e.g. "BMI423.kicad_mod"+ fileContent (required): base64-encoded .kicad_mod content+ footprintName (opt): parsed from the content if omitted+ x, y (opt): placement in mm (default board-center-ish 148,105)+ open (opt): open the board in pcbnew (default True)++Returns: {success, boardPath, footprintName, placedAt, opened, output}+"""++import base64+import re+import subprocess+import sys+import uuid+from pathlib import Path++sys.path.insert(0, str(Path(__file__).parent.parent))+from proc import run as _safe_run # noqa: E402++_TEMPLATE = Path(__file__).resolve().parent.parent / "templates" / "blank-board.kicad_pcb"+++def handle_place_footprint(kicad_info: dict, args: dict) -> dict:+ if not kicad_info.get("installed"):+ return {"success": False, "error": "KiCad not installed",+ "_hint": "Install KiCad from https://www.kicad.org/download/ then retry."}++ file_name = args.get("fileName", "")+ file_content = args.get("fileContent", "")+ if not file_content:+ return {"success": False, "error": "No fileContent (base64 .kicad_mod) specified"}++ try:+ fp_text = base64.b64decode(file_content).decode("utf-8", "replace").strip()+ except Exception as e:+ return {"success": False, "error": f"could not decode fileContent: {e}"}+ if not fp_text.startswith("(footprint"):+ return {"success": False, "error": "fileContent is not a .kicad_mod (must start with '(footprint')"}++ fp_name = args.get("footprintName") or ""+ if not fp_name:+ m = re.search(r'\(footprint\s+"([^"]+)"', fp_text)+ fp_name = m.group(1) if m else Path(file_name).stem or "footprint"++ x = args.get("x", 148.0)+ y = args.get("y", 105.0)++ if not _TEMPLATE.exists():+ return {"success": False, "error": f"board template missing at {_TEMPLATE}"}+ skel = _TEMPLATE.read_text(encoding="utf-8")++ # Make the footprint a board item: inject a uuid + position right after its+ # first (layer "...") token, then splice before the board's final paren.+ uid = str(uuid.uuid4())+ fp_board = re.sub(+ r'(\(layer\s+"[^"]+"\))',+ r'\1\n\t\t(uuid "%s")\n\t\t(at %s %s)' % (uid, x, y),+ fp_text, count=1,+ )+ idx = skel.rstrip().rfind(")")+ combined = skel[:idx].rstrip() + "\n" + fp_board + "\n)\n"++ user_dir = kicad_info.get("user_dir") or str(Path.home())+ out_path = Path(user_dir) / f"adom-place-{fp_name}.kicad_pcb"+ try:+ out_path.write_text(combined, encoding="utf-8")+ except OSError as e:+ return {"success": False, "error": f"could not write board: {e}"}++ opened = False+ open_it = args.get("open", True)+ if open_it:+ pcbnew_exe = kicad_info.get("pcbnew_exe")+ if pcbnew_exe and Path(pcbnew_exe).exists():+ try:+ # detached launch so the bridge doesn't block on the GUI+ _safe_run([pcbnew_exe, str(out_path)], timeout=8)+ opened = True+ except subprocess.TimeoutExpired:+ opened = True # launched; it just doesn't return (GUI stays up)+ except OSError as e:+ return {"success": True, "boardPath": str(out_path), "footprintName": fp_name,+ "placedAt": [x, y], "opened": False,+ "output": f"Board written but pcbnew launch failed: {e}",+ "_hint": f"Open it manually: {out_path}"}++ return {+ "success": True,+ "boardPath": str(out_path),+ "footprintName": fp_name,+ "placedAt": [x, y],+ "opened": opened,+ "output": f"Placed footprint '{fp_name}' at ({x}, {y}) mm on a preview board"+ + (" and opened it in pcbnew" if opened else ""),+ }