← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.211\ No newline at end of file+0.9.212\ No newline at end of file
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":