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.299: Tour board-in-3D fix: KiCad's 3D viewers are unowned top-level windows, so picking 'the viewer owned by the PCB Editor' never matched and the chip's viewer was never closed (0.9.298 test-box run: board3d failed with the viewer actually open). The beats now take the viewer that APPEARS after their open, the chip's viewer is remembered and closed by handle before the board's opens, with the topmost viewer as fallback.
3 files changed
+42−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.298+0.9.299
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.298",+ "version": "0.9.299", "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/demo.py+40−6@@ -504,10 +504,17 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict: "raw": r} if step == "footprint3d": import time; time.sleep(2)+ # KiCad's 3D viewers are UNOWNED top-level windows (measured 2026-09-03:+ # GW_OWNER 0 on both), so "owned by the Footprint Editor" never matches.+ # The viewer that APPEARS after this open is the chip's; remember it so+ # the board beat can close it before opening its own.+ before = set(_viewers_3d()) r = handle_open_3d_viewer(kicad_info, {"editor": "footprint"})- time.sleep(2)+ v = _new_window(before, _viewers_3d, 8.0)+ with _JOB_LOCK:+ _JOB["chipViewer"] = v return {"ok": bool(r.get("success")), "window": "3D Viewer",- "hwnd": _viewer_3d_owned_by("Footprint Editor"),+ "hwnd": v, "title": "3/6 · The chip in 3D", "say": ("And the same chip in three dimensions" + (", the LQFP-48 body from KiCad's own 3D library sitting on its pads."@@ -549,11 +556,16 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict: except Exception: pass time.sleep(2)- _close_hwnd(_viewer_3d_owned_by("Footprint Editor")) # the chip's viewer must not win the title match- time.sleep(1)+ chip = _JOB.get("chipViewer")+ if chip:+ _close_hwnd(chip) # the chip's viewer must not win the title match+ time.sleep(1.5)+ before = set(_viewers_3d()) r = handle_open_3d_viewer(kicad_info, {"editor": "pcb"})- time.sleep(3)- v = _viewer_3d_owned_by("PCB Editor")+ v = _new_window(before, _viewers_3d, 12.0)+ if not v:+ vs = _viewers_3d()+ v = vs[0] if vs else None # fallback: the topmost viewer (the one KiCad just raised) return {"ok": bool(r.get("success")) and bool(v), "window": "3D Viewer", "hwnd": v, "title": "6/6 · The board in 3D", "say": ("And the finished ESC in 3D. This is the payoff: from one symbol to a rendered "@@ -656,6 +668,28 @@ def _hwnd_for_title(window_title: str) -> int | None: return None +def _viewers_3d() -> list:+ """hwnds of every '3D Viewer' window, top of the z-order first."""+ try:+ from handlers.kicad_ui import _find_all_kicad_windows+ rows = [w for w in _find_all_kicad_windows() if "3d viewer" in (w.get("title") or "").lower()]+ return [int(w["hwnd"]) for w in rows]+ except Exception: # pylint: disable=broad-except+ return []+++def _new_window(before: set, getter, wait_s: float = 8.0) -> int | None:+ """The hwnd that appears in getter() and was not in `before`, within wait_s."""+ import time as _t+ t0 = _t.monotonic()+ while _t.monotonic() - t0 < wait_s:+ for h in getter():+ if h not in before:+ return h+ _t.sleep(0.5)+ return None++ def _viewer_3d_owned_by(editor_title: str) -> int | None: """The '3D Viewer' whose OWNER is the given editor frame (Footprint Editor vs PCB Editor). John, 2026-09-03: the last beat showed the chip's