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
v0.9.77: open_symbol_editor honors symbolName/libraryName (#20) — waits for cold-start plugin, library-qualified filter, verified load with honest success:false
6 files changed
+54−23
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.76+0.9.77
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.76",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.76/adom-bridge-kicad-v0.9.76.zip",- "sha256": "cb2d1a1388017d5b6f5bf14c9720fc079af175c1480f7080e648c19efe2bd460",- "size": 373323,+ "version": "0.9.77",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.77/adom-bridge-kicad-v0.9.77.zip",+ "sha256": "b0a50d1262eb84074fba0ed4d3378668f2d8568f05af455fa57d20f91b3c68c4",+ "size": 374204, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-13T23:46:06Z",+ "released_at": "2026-08-14T01:28:56Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.77.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.76",+ "version": "0.9.77", "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_symbol_editor.py+45−14@@ -152,21 +152,36 @@ def _get_symbol_editor_title() -> str: return "" -def _plugin_background_load(symbol_name: str) -> dict:+def _plugin_background_load(symbol_name: str, library_name: str = "",+ wait_plugin_s: float = 0.0) -> dict: """v0.9.74: background canvas-load via the in-process plugin (payload 0.9.1+). Two-hop filter->settle->activate inside the plugin, zero focus/keyboard theft (John's no-foreground mandate). Uses bridge_client's discovery to find the live eeschema instance. {'loaded': False, ...} on any failure so the caller can fall through to the legacy paths.++ v0.9.77 (#20): `wait_plugin_s` polls for the plugin to COME ALIVE — on a+ cold-started eeschema the plugin needs a few seconds to bind, and giving up+ on the first probe was the main reason a requested symbol never loaded.+ `library_name` narrows the LIB_TREE filter ("Device R" instead of just "R")+ so a short symbol name doesn't strand the selection on the wrong match. """ try: from .bridge_client import _resolve_plugin, call as _plugin_call- info, err = _resolve_plugin("eeschema")- if err is not None or not info:- return {"loaded": False, "_error": "no live eeschema plugin"}+ deadline = time.monotonic() + max(0.0, wait_plugin_s)+ info = None+ while True:+ info, err = _resolve_plugin("eeschema")+ if err is None and info:+ break+ if time.monotonic() >= deadline:+ return {"loaded": False, "_error": "no live eeschema plugin"}+ time.sleep(1.0)+ filter_text = f"{library_name} {symbol_name}".strip() if library_name else symbol_name return _plugin_call(info["port"], "select_and_load_symbol",- {"symbolName": symbol_name}, timeout=15.0) or {"loaded": False}+ {"symbolName": filter_text, "libraryName": library_name},+ timeout=15.0) or {"loaded": False} except Exception as e: # pylint: disable=broad-except return {"loaded": False, "_error": f"{type(e).__name__}: {e}"} @@ -444,11 +459,14 @@ def handle_open_symbol_editor(kicad_info: dict, args: dict) -> dict: # v0.9.74: FIRST try the plugin's background canvas-load (payload 0.9.1's # two-hop filter->settle->activate) — zero focus/keyboard theft. The PS # navigate below is the fallback when no plugin instance is alive.- _pl = _plugin_background_load(search_text)+ # v0.9.77 (#20): wait up to 12s for the plugin on a cold-started eeschema —+ # first-probe give-up was why symbolName silently produced an empty editor.+ _verify = (symbol_name or search_text).lower()+ _pl = _plugin_background_load(search_text, library_name, wait_plugin_s=12.0) if str(_pl.get("loaded")) in ("True", "unverified"): time.sleep(0.6) _title = _get_symbol_editor_title() if "_get_symbol_editor_title" in globals() else ""- if _pl.get("loaded") is True or (search_text.lower() in (_title or "").lower()):+ if _pl.get("loaded") is True or (_verify in (_title or "").lower()): return { "success": True, "output": f"Symbol '{search_text}' loaded (background plugin path): {_title or _pl.get('frameTitle','')}",@@ -485,16 +503,29 @@ def handle_open_symbol_editor(kicad_info: dict, args: dict) -> dict: "output": f"Symbol Editor loaded: {nav_result2['title']}", } else:+ # #20: the caller asked for a SPECIFIC symbol and it is not loaded —+ # that is a failure of the request, not a success. The editor itself+ # is open (editorOpened) so the caller can decide to retry or hand off. return {- "success": True,- "output": f"Symbol Editor opened and searched for '{search_text}'. "- f"Use Ctrl+Shift+E to load the symbol if it didn't auto-load.",+ "success": False,+ "editorOpened": True,+ "symbolLoaded": False,+ "error": f"Symbol Editor opened but '{search_text}' did not load "+ f"(title: {_get_symbol_editor_title() or 'unknown'})",+ "pathwaysTried": pathways_tried + ["plugin-background", "ps-navigate", "ps-navigate-retry"],+ "_hint": ("Both load paths ran without confirming the symbol. If eeschema just "+ "cold-started, its plugin may still be binding — retry this same call "+ "(the plugin path is waited on and usually succeeds second time). Verify "+ "the symbol exists: the title staying '[no symbol loaded]' with a valid "+ "name usually means the library/symbol pair doesn't match."), } else: # Error from the script- _force_foreground(sym_hwnd) return {- "success": True,- "output": f"Symbol Editor opened but navigation failed: {nav_result.get('error', 'unknown')}. "- f"Search for '{search_text}' manually.",+ "success": False,+ "editorOpened": True,+ "symbolLoaded": False,+ "error": f"Symbol Editor opened but navigation errored: {nav_result.get('error', 'unknown')}",+ "pathwaysTried": pathways_tried + ["plugin-background", "ps-navigate:error"],+ "_hint": f"Retry once; if it persists, kicad_screenshot_all shows the editor state. Requested: '{search_text}'.", }
server.py+2−2@@ -207,9 +207,9 @@ _VERB_CATALOG = { "related": ["kicad_run_erc", "kicad_export_bom_csv", "kicad_screenshot_all", "kicad_close"], "pitfalls": ["filePath must be ABSOLUTE", "open the top-level .kicad_sch, not a sub-sheet, to load the whole design"]}, "open_symbol_editor": {"summary": "Open the Symbol Editor (optionally at a symbol).", "long": True,- "hint": "Opens the Symbol Editor; pass library/symbol to jump straight to one.",+ "hint": "Opens the Symbol Editor; pass symbolName (+ libraryName to disambiguate short names) to load that symbol into the canvas. v0.9.77: the load is VERIFIED — if the requested symbol didn't load you get success:false with editorOpened:true, not a silent empty editor.", "related": ["kicad_install_symbol", "kicad_close_symbol_editor", "kicad_screenshot_all"],- "pitfalls": ["the symbol must already be in an installed sym-lib-table library (install_symbol first)"]},+ "pitfalls": ["the symbol must already be in an installed sym-lib-table library (install_symbol first)", "cold-started eeschema needs a few seconds for its plugin to bind — the verb waits, but a retry after success:false usually lands"]}, "open_footprint_editor": {"summary": "Open the Footprint Editor (optionally at a footprint).", "long": True, "hint": "Opens the Footprint Editor; pass library/footprint to jump to one.", "related": ["kicad_install_footprint", "kicad_close_footprint_editor", "kicad_screenshot_all"],