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.89: footprint editor gets the plugin-free win32 background load tier + honest failures (#21 remainder); suite fp test is now a real e2e stock-footprint load
6 files changed
+112−15
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.88+0.9.89
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.88",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.88/adom-bridge-kicad-v0.9.88.zip",- "sha256": "061ac097595f6b62b72faa9591a022bd0a1b80baef20338abe02af0ec42b887a",- "size": 383168,+ "version": "0.9.89",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.89/adom-bridge-kicad-v0.9.89.zip",+ "sha256": "b87fcfd194b84814d8530441d0cf827bc13741275e98207159558419c5044f70",+ "size": 384185, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-14T18:14:16Z",+ "released_at": "2026-08-14T18:35:39Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.89.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.88",+ "version": "0.9.89", "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+101−7@@ -267,6 +267,84 @@ def _send_ctrl_shift_e(hwnd: int): user32.keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0) +def _get_footprint_editor_title() -> str:+ """Current Footprint Editor title ('' if not found) — load ground truth."""+ try:+ hwnd = _find_footprint_editor()+ if not hwnd:+ return ""+ buf = ctypes.create_unicode_buffer(512)+ user32.GetWindowTextW(hwnd, buf, 512)+ return buf.value or ""+ except Exception: # pylint: disable=broad-except+ return ""+++def _background_navigate_win32_fp(fp_hwnd: int, filter_text: str, verify: str) -> bool:+ """v0.9.89 (#21): plugin-free BACKGROUND footprint load — the same tier that+ fixed the Symbol Editor (0.9.86). WM_SETTEXT into the LIB_TREE filter, Down+ through the filter box to select the match, Enter posted to the tree+ candidates + PostMessage dblclick fallback. Zero focus, no plugin needed."""+ edit = _find_child_by_class_and_parent(fp_hwnd, "Edit", "searchCtrl")+ if not edit:+ return False+ WM_SETTEXT = 0x000C+ WM_KEYDOWN, WM_KEYUP, VK_RETURN, VK_DOWN = 0x0100, 0x0101, 0x0D, 0x28+ try:+ user32.SendMessageW(edit, WM_SETTEXT, 0, ctypes.create_unicode_buffer(filter_text))+ except Exception:+ return False+ time.sleep(1.4)+ user32.PostMessageW(edit, WM_KEYDOWN, VK_DOWN, 0)+ user32.PostMessageW(edit, WM_KEYUP, VK_DOWN, 0)+ time.sleep(0.5)++ def _tree_candidates():+ cands = []+ try:+ pane = user32.GetParent(user32.GetParent(edit)) or user32.GetParent(edit)+ WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND,+ ctypes.wintypes.LPARAM)+ def cb(hwnd, _l):+ r = ctypes.wintypes.RECT()+ user32.GetWindowRect(hwnd, ctypes.byref(r))+ if (r.bottom - r.top) > 200 and hwnd != edit:+ cands.append((hwnd, r))+ return True+ cbp = WNDENUMPROC(cb)+ _callbacks.append(cbp)+ user32.EnumChildWindows(pane, cbp, 0)+ except Exception:+ pass+ return cands++ def _title_ok():+ t = (_get_footprint_editor_title() or "").lower()+ return verify in t and "no footprint loaded" not in t++ for hwnd, _r in _tree_candidates():+ user32.PostMessageW(hwnd, WM_KEYDOWN, VK_RETURN, 0)+ user32.PostMessageW(hwnd, WM_KEYUP, VK_RETURN, 0)+ for _ in range(6):+ time.sleep(0.7)+ if _title_ok():+ return True+ WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, MK_LBUTTON = 0x0201, 0x0202, 0x0203, 0x0001+ for hwnd, r in _tree_candidates():+ w = r.right - r.left+ x, y = max(10, w // 3), 58+ lp = (y << 16) | (x & 0xFFFF)+ user32.PostMessageW(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, lp)+ user32.PostMessageW(hwnd, WM_LBUTTONUP, 0, lp)+ user32.PostMessageW(hwnd, WM_LBUTTONDBLCLK, MK_LBUTTON, lp)+ user32.PostMessageW(hwnd, WM_LBUTTONUP, 0, lp)+ for _ in range(8):+ time.sleep(0.7)+ if _title_ok():+ return True+ return False++ def _navigate_to_footprint(fp_hwnd: int, footprint_name: str, load_after: bool = True) -> dict: """Call the PowerShell script to navigate to a specific footprint. @@ -581,6 +659,16 @@ def handle_open_footprint_editor(kicad_info: dict, args: dict) -> dict: return {"success": True, "output": "Footprint Editor opened successfully."} search_text = footprint_name or library_name+ _verify = (footprint_name or search_text).lower()++ # v0.9.89: plugin-free background load first (the 0.9.86 symbol-editor tier).+ _filter = f"{library_name} {footprint_name}".strip() if library_name else search_text+ if _background_navigate_win32_fp(fp_hwnd, _filter, _verify):+ return {+ "success": True,+ "output": f"Footprint '{search_text}' loaded (background win32 path): {_get_footprint_editor_title()}",+ "via": "win32-background",+ } nav_result = _navigate_to_footprint(fp_hwnd, search_text) @@ -609,16 +697,22 @@ def handle_open_footprint_editor(kicad_info: dict, args: dict) -> dict: } else: return {- "success": True,- "output": f"Footprint Editor opened and searched for '{search_text}'. "- f"Use Ctrl+Shift+E to load the footprint if it didn't auto-load.",+ "success": False,+ "editorOpened": True,+ "footprintLoaded": False,+ "error": f"Footprint Editor opened but '{search_text}' did not load "+ f"(title: {_get_footprint_editor_title() or 'unknown'})",+ "_hint": ("All load paths ran without confirming the footprint. Verify the "+ "library/footprint pair exists (fp-lib-table row + .pretty content); "+ "a retry usually lands if pcbnew just cold-started."), } else:- _force_foreground(fp_hwnd) return {- "success": True,- "output": f"Footprint Editor opened but navigation failed: {nav_result.get('error', 'unknown')}. "- f"Search for '{search_text}' manually.",+ "success": False,+ "editorOpened": True,+ "footprintLoaded": False,+ "error": f"Footprint Editor opened but navigation errored: {nav_result.get('error', 'unknown')}",+ "_hint": f"Retry once; kicad_state shows the editor. Requested: '{search_text}'.", }
skills/kicad-bridge-test/run_verb_tests.py+4−1@@ -124,7 +124,10 @@ def matrix(w: dict) -> dict: "open_symbol_editor": dict(phase=3, args={"symbolName": "R", "libraryName": "Device"}, check="symbol_verified", timeout=160, note="the #20 acceptance test: verified Device:R load"),- "open_footprint_editor": dict(phase=3, args={}, timeout=160),+ "open_footprint_editor": dict(phase=3,+ args={"footprintName": "R_0603_1608Metric", "library": "Resistor_SMD"},+ timeout=160,+ note="e2e load of a stock footprint (needs the 0.9.84 lib self-heal)"), "open_3d_viewer": dict(phase=3, args={"reason": "daily verb test"}, timeout=200), "open_editors": dict(phase=3, args={}, check="editors_listed"), "state": dict(phase=3, args={"maxWidth": 640}, check="state_shots", timeout=160,