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.253: install_library_bundle capture proof (background Symbol Editor shot, closed after; skipped when KiCad runs)
5 files changed
+57−14
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.252+0.9.253
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.252",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.252/adom-bridge-kicad-v0.9.252.zip",- "sha256": "599b975ada07114872d9c9138d59a14e5b9f4356eb98cb53982af1b8437749d5",- "size": 452444,+ "version": "0.9.253",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.253/adom-bridge-kicad-v0.9.253.zip",+ "sha256": "38149498618f100760933e128d83265afe79c90359a817f6d36b82037f4f58c0",+ "size": 453215, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-26T21:13:35Z",+ "released_at": "2026-08-26T21:24:52Z", "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.252",+ "version": "0.9.253", "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+45−6@@ -69,14 +69,43 @@ def _find_bundle_parts(root: Path): return sym, pretty, (models if models.is_dir() else None) -def _count_symbols(sym_path: Path) -> int:- """Top-level symbols only (sub-units are named NAME_<n>_<n>)."""+def _top_symbol_names(sym_path: Path) -> list:+ """Top-level symbol names only (sub-units are named NAME_<n>_<n>).""" try: text = sym_path.read_text(encoding="utf-8", errors="replace") except OSError:- return 0+ return [] names = re.findall(r'\(symbol\s+"([^"]+)"', text)- return sum(1 for n in names if not re.search(r"_\d+_\d+$", n))+ return [n for n in names if not re.search(r"_\d+_\d+$", n)]+++def _capture_proof(kicad_info: dict, args: dict, lib_name: str, first_symbol: str, result: dict):+ """Proof frame for the browser caller: open the Symbol Editor on the+ freshly installed library IN THE BACKGROUND (the show path's contract),+ grab the shot, then close the editor we opened. Best effort: any failure+ lands in proofSkipped, never in the install's success."""+ try:+ from handlers.show import handle_show_symbol+ show = handle_show_symbol(kicad_info, {+ "libraryName": lib_name,+ **({"symbolName": first_symbol} if first_symbol else {}),+ "capture": True,+ "captureMaxWidth": args.get("captureMaxWidth", 480),+ })+ if show.get("shot"):+ result["shot"] = show["shot"]+ else:+ result["proofSkipped"] = show.get("error") or show.get("shotUnavailable") or "no frame captured"+ # Close only an editor this call cold-opened; a reused window belongs+ # to whoever opened it.+ if not show.get("reused"):+ try:+ from handlers.close_windows import handle_close_symbol_editor+ handle_close_symbol_editor(kicad_info, {})+ except Exception:+ pass+ except Exception as e:+ result["proofSkipped"] = str(e) def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict:@@ -119,7 +148,8 @@ def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict: return {"success": False, "error": "No .kicad_sym found in the bundle", "_hint": "Expected an Adom wiki Cloud Library bundle: <Lib>.kicad_sym + <Lib>.pretty/ + 3dmodels/."} - report = {"libraryName": lib_name, "symbols": _count_symbols(sym),+ top_names = _top_symbol_names(sym)+ report = {"libraryName": lib_name, "symbols": len(top_names), "footprints": 0, "modelsInstalled": 0, "modelRefsRewritten": 0} sym_res = handle_install_library(kicad_info, {@@ -165,7 +195,7 @@ def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict: if kicad_running: summary += ". KiCad is running and reads library tables at startup, so restart KiCad to see it." - return {+ result = { "success": True, "output": summary, "data": report,@@ -177,5 +207,14 @@ def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict: "KiCad was not running; the library appears on next launch.") ), }+ # Proof frame (capture:true): only when KiCad was NOT already running.+ # A running instance predates the lib-table registration, so an editor+ # there cannot see the new library; the restart note is the message.+ if args.get("capture"):+ if kicad_running:+ result["proofSkipped"] = "kicad_running: the open KiCad predates the install; restart to see the library"+ else:+ _capture_proof(kicad_info, args, lib_name, top_names[0] if top_names else "", result)+ return result finally: shutil.rmtree(str(extract_dir), ignore_errors=True)
server.py+5−1@@ -359,7 +359,7 @@ _VERB_CATALOG = { "related": ["kicad_install_symbol", "kicad_install_footprint", "kicad_list_versions"], "pitfalls": ["each KiCad version has its OWN lib tables — install under every version the user needs", "restart/reload KiCad to see a newly-added library", "model-ref rewriting touches only the INSTALLED copy, never the source, and only refs whose basename matches an installed model (stock ${KICAD*_3DMODEL_DIR} refs stay)"]}, "install_library_bundle": {"summary": "Install an Adom wiki Cloud Library bundle zip (symbols + footprints + 3D models) in one call.", "long": False,- "hint": "Pass zipPath (staged on THIS machine via write_file / write_file_chunk) and optionally libraryName (defaults to the zip stem). Unpacks the bundle (<Lib>.kicad_sym + <Lib>.pretty/ + 3dmodels/), installs symbols and footprints, carries the 3D models with ref rewriting, and registers both lib tables. Returns {symbols, footprints, modelsInstalled, kicadRunning, restartNeeded}.",+ "hint": "Pass zipPath (staged on THIS machine via write_file / write_file_chunk) and optionally libraryName (defaults to the zip stem). Unpacks the bundle (<Lib>.kicad_sym + <Lib>.pretty/ + 3dmodels/), installs symbols and footprints, carries the 3D models with ref rewriting, and registers both lib tables. Returns {symbols, footprints, modelsInstalled, kicadRunning, restartNeeded}. capture:true additionally opens a BACKGROUND Symbol Editor on the new library, returns the `shot` frame (thumbnailDataUrl for browsers), and closes the editor it opened; skipped with proofSkipped when KiCad was already running (its tables predate the install).", "related": ["kicad_install_library", "kicad_lint_library", "kicad_open_symbol_editor"], "pitfalls": ["a RUNNING KiCad reads lib tables at startup only — when kicadRunning is true the user must restart KiCad themselves (never close it for them)", "zip entries with .. or absolute paths are refused"]}, "install_symbol": {"summary": "Install a .kicad_sym into the user's sym-lib-table.", "long": False,@@ -961,6 +961,9 @@ def dispatch_command(command: str, args: dict) -> dict: "open_footprint_editor", "open_3d_viewer", "place_footprint", "install_symbol", "install_footprint", "install_plugin", "upgrade",+ # capture:true opens a background Symbol Editor for the proof+ # shot, so the bundle install needs the full guard machinery.+ "install_library_bundle", # demo warms eeschema+pcbnew and opens editors/3D — without # membership here the capture/restore machinery skips it and # every window it spawns lands ON TOP of the user (verified@@ -973,6 +976,7 @@ def dispatch_command(command: str, args: dict) -> dict: "show_project", "show_symbol", "show_footprint", "show_3d_chip", "show_schematic", "show_2d_board", "show_3d_board", "show_library"} _SPAWNS_WINDOW_FOCUS = {"launch", "open_board", "open_schematic", "open_symbol_editor", "open_footprint_editor", "open_3d_viewer", "install_library",+ "install_library_bundle", "place_footprint", "install_symbol", "install_footprint", "demo", "export_molecule", "show_project", "show_symbol", "show_footprint", "show_3d_chip", "show_schematic", "show_2d_board", "show_3d_board", "show_library"}