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
Update 5 file(s)
5 files changed
+113−9
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.211+0.9.212
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.211",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.211/adom-bridge-kicad-v0.9.211.zip",- "sha256": "87da9060b22146a3d1981502bfbf410f872091b6732d696e41ac7865978aa312",- "size": 480064,+ "version": "0.9.212",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.212/adom-bridge-kicad-v0.9.212.zip",+ "sha256": "5f73136782c85ddbbf12e5bab254b6c345f663d9b40b2d1733efc282baaf1b16",+ "size": 481679, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-20T15:20:00Z",+ "released_at": "2026-08-20T15:45:00Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.211",+ "version": "0.9.212", "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/open_footprint_editor.py+29−2@@ -329,8 +329,35 @@ def _library_says(kicad_info: dict, library: str, wanted: str) -> dict: if not near: import difflib near = difflib.get_close_matches(wanted, names, n=3, cutoff=0.6)- return {"existsInLibrary": exact, "libraryCount": len(names),- "closestMatches": near[:3]}+ out = {"existsInLibrary": exact, "libraryCount": len(names),+ "closestMatches": near[:3]}++ # 0.9.212 (#44, John): "existsInLibrary is a filename check ... it+ # answers 'is there a file called this' rather than 'can KiCad load+ # this'. Those diverge in exactly this failure." Correct - so ask the+ # only thing that can answer the second question: code running INSIDE+ # pcbnew, via the reverse-bridge plugin. It reports whether KiCad has+ # the library registered, whether ITS enumeration contains the name,+ # and whether the footprint actually parses - returning KiCad's own+ # error when it does not.+ try:+ from handlers.bridge_client import handle_bridge_call+ probe = handle_bridge_call(kicad_info, {+ "exeName": "pcbnew", "method": "probe_footprint",+ "params": {"library": library or "Adom", "footprint": wanted},+ "timeout": 12,+ })+ res = ((probe or {}).get("result") or {}) if isinstance(probe, dict) else {}+ if res:+ out["kicad"] = {k: res.get(k) for k in+ ("libraryRegistered", "inKicadList", "kicadListCount",+ "loads", "loadError", "kicadNearby", "verdict")+ if res.get(k) is not None}+ elif isinstance(probe, dict) and probe.get("error"):+ out["kicadProbe"] = f"unavailable: {str(probe.get('error'))[:90]}"+ except Exception as e:+ out["kicadProbe"] = f"unavailable: {type(e).__name__}"+ return out except Exception: return {}
plugin_payload/adom_bridge.py+77@@ -2807,6 +2807,80 @@ def _ui_wm_command(frame_index: int, menu_id: int) -> dict: # --- request dispatcher ---------------------------------------------------- ++def _ui_probe_footprint(library: str, footprint: str) -> dict:+ """Ask KICAD ITSELF whether it can see and load this footprint.++ Wiki issue #44 (John): "existsInLibrary is a filename check, so it answers+ 'is there a file called this' rather than 'can KiCad load this'. Those+ diverge in exactly this failure." He is right, and only code running INSIDE+ pcbnew can answer the second question - which this is.++ Three distinct answers, where the directory listing could only give one:+ * the library is not registered with KiCad at all (fp-lib-table)+ * KiCad enumerates the library but the name is not in ITS list+ * the name is listed but LOADING it raises - and then we return KiCad's+ own parse error, which is the thing nobody outside the process can see.+ """+ import pcbnew # available only inside a KiCad process++ out = {"library": library, "footprint": footprint}+ try:+ tbl = pcbnew.GetGlobalFootprintTable()+ except Exception as e:+ out["error"] = f"no footprint table: {type(e).__name__}: {e}"+ return out++ try:+ nicks = [str(n) for n in tbl.GetLogicalLibs()]+ except Exception as e:+ nicks = []+ out["libListError"] = f"{type(e).__name__}: {e}"+ out["registeredLibraries"] = nicks+ out["libraryRegistered"] = library in nicks+ if not out["libraryRegistered"]:+ out["verdict"] = ("LIBRARY NOT REGISTERED: KiCad has no fp-lib-table row for "+ f"'{library}', so nothing in it can load regardless of the files.")+ return out++ try:+ listed = [str(n) for n in tbl.FootprintEnumerate(library, False)]+ except Exception as e:+ out["enumerateError"] = f"{type(e).__name__}: {e}"+ listed = []+ out["kicadListCount"] = len(listed)+ out["inKicadList"] = footprint in listed+ if listed and not out["inKicadList"]:+ near = [n for n in listed if footprint.lower() in n.lower()+ or n.lower() in footprint.lower()][:5]+ out["kicadNearby"] = near+ out["verdict"] = ("ON DISK BUT NOT IN KICAD'S LIST: the file exists, and KiCad's own "+ "enumeration of this library does not contain that name. KiCad is "+ "reading a different set of files than the directory listing shows.")+ return out++ # The decisive step: make KiCad actually parse it.+ try:+ mod = tbl.FootprintLoad(library, footprint)+ if mod is None:+ out["loads"] = False+ out["verdict"] = ("LISTED BUT LOADS AS NULL: KiCad enumerates the name but returns "+ "no footprint for it.")+ else:+ out["loads"] = True+ try:+ out["padCount"] = int(mod.GetPadCount())+ except Exception:+ pass+ out["verdict"] = ("LOADS FINE: KiCad can enumerate AND parse this footprint, so any "+ "failure to show it is in the editor navigation, not the library.")+ except Exception as e:+ out["loads"] = False+ out["loadError"] = f"{type(e).__name__}: {e}"+ out["verdict"] = (f"KICAD REFUSES TO LOAD IT: {type(e).__name__}: {e}. This is KiCad's "+ "own parser talking - the file is present but it will not open.")+ return out+ def _dispatch(method: str, params: dict) -> Any: if method == "ping": return {@@ -2830,6 +2904,9 @@ def _dispatch(method: str, params: dict) -> Any: return _call_on_ui_thread(_ui_get_schematic_info) if method == "get_open_editors": return _call_on_ui_thread(_ui_get_open_editors)+ if method == "probe_footprint":+ return _call_on_ui_thread(lambda: _ui_probe_footprint(+ str(params.get("library") or ""), str(params.get("footprint") or ""))) if method == "get_pcb_footprints": return _call_on_ui_thread(_ui_get_pcb_footprints) if method == "get_pcb_layers":