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
Update 4 file(s)
4 files changed
+34−11
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.197+0.9.198
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.197",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.197/adom-bridge-kicad-v0.9.197.zip",- "sha256": "fc2b6091f00d477824d025f61cc072367fc8ea95ec396bc806ae2fb9dcbc79d5",- "size": 467545,+ "version": "0.9.198",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.198/adom-bridge-kicad-v0.9.198.zip",+ "sha256": "f29948616354a5a2caa53a9a7ad2e3df277e6cfea33ff2cd075137f08a39363d",+ "size": 468068, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-20T00:55:00Z",+ "released_at": "2026-08-20T01:05:00Z", "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.197",+ "version": "0.9.198", "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.",
server.py+27−4@@ -103,6 +103,12 @@ BRIDGE_VERSION = _read_bridge_version() # already knows the verb name and owns the clock, so it stamps the terminal # progress frame (percent 100 + the real elapsed) and feeds this machine's # rolling history, which is what makes later estimates "measured".+# A dialog only counts as BLOCKING once it has survived this long: KiCad throws+# transient progress windows ("Load PCB") that are gone before a human could+# read them, and calling those "waiting for you" is a lie that stops the bar.+_DIALOG_BLOCK_AFTER_SEC = 3.0+_DIALOG_FIRST_SEEN: dict = {}+ _PHASES = { "launch": "cold_start", "show_symbol": "show_symbol", "show_footprint": "show_footprint", "show_3d_chip": "show_3d_chip",@@ -564,14 +570,31 @@ def _handle_progress(kicad_info: dict, args: dict) -> dict: if frames: try: from handlers.close_windows import _win_scan_kicad_dialogs+ now = time.time()+ live_hwnds = set()+ blocker = None for d in (_win_scan_kicad_dialogs(kicad_info) or []): if not d.get("isDialog"): continue- what = (d.get("title") or "").strip() or "a KiCad dialog"+ h = d.get("hwnd")+ live_hwnds.add(h)+ first = _DIALOG_FIRST_SEEN.setdefault(h, now)+ # PERSISTENCE is what separates "you must act" from "KiCad is+ # busy". The first cut reported blockedBy for ANY dialog and+ # promptly claimed "waiting for you: Load PCB" - KiCad's own+ # transient progress window, which nobody had to touch. A false+ # blockedBy is worse than none: it stops the bar AND sends the+ # user hunting for a prompt that already closed itself. A real+ # prompt sits there until a human deals with it, so require the+ # SAME dialog to still be up a few seconds later.+ if now - first >= _DIALOG_BLOCK_AFTER_SEC and blocker is None:+ blocker = (d.get("title") or "").strip() or "a KiCad dialog"+ for stale in [h for h in _DIALOG_FIRST_SEEN if h not in live_hwnds]:+ _DIALOG_FIRST_SEEN.pop(stale, None)+ if blocker: for f in frames:- f["blockedBy"] = what- f["stepLabel"] = f"waiting for you: {what}"- break+ f["blockedBy"] = blocker+ f["stepLabel"] = f"waiting for you: {blocker}" except Exception: pass return {