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
0.9.343: Footprint loads with two pcbnew processes; list_footprints reads the fp-lib-table (#81)
5 files changed
+63−19
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.342+0.9.343
SKILL.md+1@@ -383,3 +383,4 @@ Rules live on the wiki page adom/pcb-design-rules and are fetched at call time, - **Adom boards with a schematic and a PCB**: `adom/drone-motor-carrier`, `adom/esc-g431`, `adom/carrier-fc-rp2350a-mini`, `adom/rp2350a-ads1220` (each carries `<slug>.kicad_sch`, `<slug>.kicad_pcb`, `.step`, `.glb`). Pull the `.kicad_sch` + `.kicad_pcb`, push them to the desktop, then `kicad_open_board` / `kicad_show_2d_board` / `kicad_show_3d_board`, `kicad_lint_board`, `kicad_set_design_rules {"fab":"adom-instapcb"}`, `kicad_export_gerbers`. - **The Adom PCM libraries** (symbols, footprints and 3D for every part on the Adom line): `kicad_pcm_install {"repositoryUrl":"https://wiki.adom.inc/api/v1/pages/kicad-pcm/files/repository.json","identifier":"inc.adom.kicad.basic-parts"}` (also `inc.adom.kicad.rp2040`). KiCad registers the libraries on its next start. - Never quote a wiki page you have not fetched: `aravk/in-s42ata`, an older example, no longer exists.+
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.342",+ "version": "0.9.343", "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
handlers/install_footprint.py+37−13@@ -306,25 +306,49 @@ def handle_list_footprints(kicad_info: dict, args: dict) -> dict: return {"success": False, "error": "KiCad user directory not found", "_hint": "Launch KiCad once so it creates Documents/KiCad/<ver>/."} - roots = [_P(user_dir) / "footprints"] libraries, total = {}, 0- for root in roots:- if not root.is_dir():- continue++ def _add(name: str, pretty) -> None:+ nonlocal total+ if name in libraries or not pretty.is_dir():+ return+ if want and name.lower() != want.lower():+ return+ names = sorted(p.stem for p in pretty.glob("*.kicad_mod"))+ libraries[name] = {"path": str(pretty).replace("\\", "/"),+ "count": len(names), "footprints": names}+ total += len(names)++ # 1. Every library KiCad itself knows: the user's fp-lib-table, URIs expanded+ # the way KiCad expands them (stock ${KICAD<n>_FOOTPRINT_DIR} libraries+ # included). Issue #81: "No library named 'Resistor_SMD' on this machine"+ # was this verb only looking at Documents/KiCad/<ver>/footprints.+ try:+ from lib_table import LibTable+ from adom_library import _expand_kicad_uri+ config_dir = kicad_info.get("config_dir")+ if not config_dir:+ import kicad_detect+ config_dir = kicad_detect._win_find_config_dir(kicad_info.get("version") or "")+ table = _P(config_dir) / "fp-lib-table" if config_dir else None+ if table and table.is_file():+ for e in LibTable.parse_file(str(table)).entries:+ resolved = _expand_kicad_uri(e.uri, kicad_info)+ if resolved:+ _add(e.name, _P(resolved))+ except Exception: # pylint: disable=broad-except+ pass+ # 2. Unregistered .pretty folders in the user dir (installed but not yet in the table).+ root = _P(user_dir) / "footprints"+ if root.is_dir(): for pretty in sorted(root.glob("*.pretty")):- name = pretty.stem- if want and name.lower() != want.lower():- continue- names = sorted(p.stem for p in pretty.glob("*.kicad_mod"))- libraries[name] = {"path": str(pretty).replace("\\", "/"),- "count": len(names), "footprints": names}- total += len(names)+ _add(pretty.stem, pretty) out = {"success": True, "libraryCount": len(libraries), "footprintCount": total, "libraries": libraries} if want and not libraries:- out["_hint"] = (f"No library named '{want}' on this machine. Libraries present: "- f"{', '.join(sorted(libraries)) or 'none'}. Install one with "+ out["_hint"] = (f"No library named '{want}' in this KiCad's fp-lib-table or user footprints dir. "+ f"Install one with " f"kicad_install_footprint, which creates Adom.pretty on first use.") else: out["_hint"] = ("The footprint NAME is the .kicad_mod file stem, which is what "
handlers/open_footprint_editor.py+23−4@@ -606,7 +606,7 @@ def _ensure_tree_search(fp_hwnd: int): return edit import time as _t now = _t.monotonic()- if now - _TREE_TOGGLED.get(int(fp_hwnd), 0) > 60 and _show_library_tree_pane():+ if now - _TREE_TOGGLED.get(int(fp_hwnd), 0) > 60 and _show_library_tree_pane(fp_hwnd): _TREE_TOGGLED[int(fp_hwnd)] = now for _ in range(16): edit = _find_child_by_class_and_parent(fp_hwnd, "Edit", "searchCtrl")@@ -616,12 +616,31 @@ def _ensure_tree_search(fp_hwnd: int): return edit if _visible(edit) else None -def _show_library_tree_pane() -> bool:+def _window_pid(hwnd: int) -> int | None:+ try:+ pid = ctypes.wintypes.DWORD(0)+ user32.GetWindowThreadProcessId(int(hwnd), ctypes.byref(pid))+ return int(pid.value) or None+ except Exception: # pylint: disable=broad-except+ return None+++def _show_library_tree_pane(fp_hwnd: int = 0) -> bool: """Tick View > Panels > Library Tree on the Footprint Editor frame via the- pcbnew plugin (menu id resolved live; the FP editor is not frame 0)."""+ pcbnew plugin (menu id resolved live; the FP editor is not frame 0).++ Ask the pcbnew that OWNS the Footprint Editor window (#81, winvm 2026-09-07):+ with two pcbnew processes alive the resolver's default "newest instance" was+ a stray PCB Editor with no Footprint Editor frame, the toggle went nowhere,+ the tree stayed hidden and every background load typed into nothing.""" try: from handlers.bridge_client import _resolve_plugin, call as _rpc- meta, err = _resolve_plugin("pcbnew")+ meta, err = None, None+ owner = _window_pid(fp_hwnd) if fp_hwnd else None+ if owner:+ meta, err = _resolve_plugin("pcbnew", owner)+ if err or not meta:+ meta, err = _resolve_plugin("pcbnew") if err or not meta: return False rpc = _rpc(meta["port"], "get_menu_ids", {}, timeout=8.0)