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.256: reveal_library plugin method (0.9.2): left-open editor shows the library's FULL list via tree-node select, no text filter (generic names like UI lost the fuzzy match)
5 files changed
+83−12
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.255+0.9.256
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.255",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.255/adom-bridge-kicad-v0.9.255.zip",- "sha256": "e89d3bd81e3dbefe12826b0a1c8b0881bd279eb48af1a5a950526ae4826e824f",- "size": 453653,+ "version": "0.9.256",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.256/adom-bridge-kicad-v0.9.256.zip",+ "sha256": "c9b5d8ee2c4433a81d8e412b276d561232d2383e829866d1388d9add71a0884f",+ "size": 454370, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-27T21:14:23Z",+ "released_at": "2026-08-27T21:16:40Z", "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.255",+ "version": "0.9.256", "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_library_bundle.py+6−4@@ -109,14 +109,16 @@ def _capture_proof(kicad_info: dict, args: dict, lib_name: str, first_symbol: st pass else: result["editorLeftOpen"] = True- # Loosen the tree filter to just the LIBRARY name so the user sees- # the whole list, not only the photographed part.+ # Show the FULL list: clear the tree filter and select/expand the+ # library node by name (plugin 0.9.2 reveal_library; a text filter+ # made generic names like 'UI' lose a fuzzy match to other libs). try: from handlers.bridge_client import _resolve_plugin, call as _plugin_call info, err = _resolve_plugin("eeschema") if info and err is None:- _plugin_call(info["port"], "select_and_load_symbol",- {"symbolName": lib_name, "libraryName": lib_name}, timeout=15.0)+ rev = _plugin_call(info["port"], "reveal_library",+ {"libraryName": lib_name}, timeout=20.0)+ result["libraryRevealed"] = bool((rev or {}).get("found")) except Exception: pass except Exception as e:
plugin_payload/adom_bridge.py+70−1@@ -60,7 +60,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from typing import Any, Callable, Optional -__version__ = "0.9.1"+__version__ = "0.9.2" # Exe-name -> port offset. The KiCad bridge in adom-desktop knows this # table too. New exes get appended; never renumber existing entries or@@ -1948,6 +1948,62 @@ def _ui_navigate_symbol(symbol_name: str) -> dict: return _ui_navigate_in_editor("symbol", symbol_name) +def _ui_reveal_library(editor_kind: str, library_name: str) -> dict:+ """Select + expand the LIBRARY node by name with NO text filter. A filter+ string competes globally (a generic library name like 'UI' loses a fuzzy+ match to Arduino_* items), so the reveal works on the tree model instead:+ walk the top-level (library) items, match the label, expand and select."""+ try:+ import wx # type: ignore[import-not-found]+ import wx.dataview # type: ignore[import-not-found]+ except ImportError:+ return {"found": False, "error": "wx not loaded"}+ needle = f"{editor_kind} editor".lower()+ target = None+ for win in wx.GetTopLevelWindows():+ try:+ title = win.GetTitle() if hasattr(win, "GetTitle") else ""+ except Exception: # pylint: disable=broad-except+ title = ""+ if needle in title.lower():+ target = win+ break+ if target is None:+ return {"found": False, "error": f"no '{editor_kind} editor' frame is open"}+ dvc = _find_dataview_ctrl_recursive(target)+ if dvc is None:+ return {"found": False, "error": "no library tree control found"}+ try:+ model = dvc.GetModel()+ root = wx.dataview.DataViewItem()+ kids = wx.dataview.DataViewItemArray()+ model.GetChildren(root, kids)+ except Exception as e: # pylint: disable=broad-except+ return {"found": False, "error": f"tree walk failed: {e}"}+ want = (library_name or "").strip().lower()+ hit = None+ labels = []+ for i in range(len(kids)):+ try:+ label = str(model.GetValue(kids[i], 0) or "")+ except Exception: # pylint: disable=broad-except+ label = ""+ labels.append(label)+ if label.strip().lower() == want or label.split(" ")[0].strip().lower() == want:+ hit = kids[i]+ break+ if hit is None:+ return {"found": False, "error": "library not in tree", "libraries": labels[:40]}+ try:+ dvc.UnselectAll()+ dvc.Expand(hit)+ dvc.Select(hit)+ dvc.EnsureVisible(hit)+ except Exception: # pylint: disable=broad-except+ pass+ return {"found": True, "revealed": library_name, "frameTitle": target.GetTitle()}++ def _ui_navigate_footprint(footprint_name: str) -> dict: return _ui_navigate_in_editor("footprint", footprint_name) @@ -3050,6 +3106,19 @@ def _dispatch(method: str, params: dict) -> Any: # useSendInput:true escalates from wx.PostEvent (safe, may not # propagate) to real Win32 SendInput (reliable, briefly steals # foreground). Always pair with verify_loaded for confirmation.+ if method == "reveal_library":+ # Show the user their new library's FULL list: clear the tree filter+ # (hop 1; the repopulate debounces on the UI thread), then select and+ # expand the library node by NAME on the model (hop 2) - no text+ # filter, so generic names never lose a fuzzy match to other libs.+ _lib = params.get("libraryName") or params.get("name") or ""+ if not _lib:+ raise ValueError("reveal_library requires params.libraryName")+ _editor = params.get("editor") or "symbol"+ import time as _t+ _call_on_ui_thread(lambda: _ui_navigate_in_editor(_editor, ""))+ _t.sleep(0.8)+ return _call_on_ui_thread(lambda: _ui_reveal_library(_editor, _lib)) if method == "select_and_load_symbol": name = params.get("symbolName") or params.get("name") or "" use_si = bool(params.get("useSendInput"))