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.120: per-step screenshot evidence streamed to the demo caller (#25 web-control contract) - stepShot{step,title,image<=640px} rides the first progress frame after each beat; final result.state.shots unchanged
6 files changed
+52−11
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.119+0.9.120
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.119",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.119/adom-bridge-kicad-v0.9.119.zip",- "sha256": "cb4640fe59796e8355e56a175c50c31fe568596e9b6d97e8c96710f3caf58579",- "size": 411171,+ "version": "0.9.120",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.120/adom-bridge-kicad-v0.9.120.zip",+ "sha256": "4c400e0367c333b3af41aab794eb2bc223d73cd9567d892446c42a55c8e87733",+ "size": 411776, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-16T14:27:29Z",+ "released_at": "2026-08-16T17:35:52Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.120.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.119",+ "version": "0.9.120", "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+42−1@@ -441,6 +441,30 @@ def _job_update(**kw): _JOB.update(kw) +def _beat_shot(kicad_info: dict, step_label: str, window_title: str) -> dict | None:+ """The per-step EVIDENCE frame for the web-control page (#25, John 2026-08-16:+ the page's own guess-snaps race the tour and shoot the wrong window — only+ the bridge knows the moment a step completes and which window is its+ surface). Returns {step, title, image(base64 PNG, <=640px wide)} or None."""+ if not window_title:+ return None+ try:+ import base64 as _b64+ from handlers.kicad_ui import _find_all_kicad_windows, _screenshot_hwnd+ for w in _find_all_kicad_windows():+ if window_title.lower() in (w.get("title") or "").lower():+ shot = _screenshot_hwnd(w["hwnd"], label="stepshot", max_dim=640)+ p = shot.get("fullPath") or shot.get("savedTo")+ if shot.get("success") and p and p.lower().endswith(".png"):+ with open(p, "rb") as f:+ img = _b64.b64encode(f.read()).decode()+ return {"step": step_label, "title": w.get("title"), "image": img}+ break+ except Exception:+ pass+ return None++ def _run_demo_background(kicad_info: dict) -> None: """Thread body: the six beats with live progress accounting.""" import time as _t@@ -467,6 +491,10 @@ def _run_demo_background(kicad_info: dict) -> None: elapsedSec=round(_t.monotonic() - t0)) prep["warm"] = _warm_kicad(kicad_info, prep) done_weight += _BEAT_EST["prepare"]+ shot = _beat_shot(kicad_info, "Preparing the sample project", "KiCad")+ if shot:+ with _JOB_LOCK:+ _JOB.setdefault("pendingShots", []).append(shot) results = [] for i, s_ in enumerate(STEPS): _job_update(step=s_, stepLabel=f"Opening {s_.replace('_', ' ')}",@@ -480,6 +508,11 @@ def _run_demo_background(kicad_info: dict) -> None: r["retried"] = True r["step"] = s_ results.append(r)+ shot = _beat_shot(kicad_info, f"Opening {s_.replace('_', ' ')}",+ r.get("title") or r.get("window") or "")+ if shot:+ with _JOB_LOCK:+ _JOB.setdefault("pendingShots", []).append(shot) done_weight += _BEAT_EST.get(s_, 10) # final payload = the kicad_state shape (#22): every window, base64 state = {}@@ -517,6 +550,12 @@ def handle_demo(kicad_info: dict, args: dict) -> dict: out = {"success": True, **{k: snap.get(k) for k in ("active", "done", "step", "stepLabel", "beatIndex", "beatCount", "percent", "elapsedSec", "estimatedSec", "error")}}+ # Per-step evidence (#25): one completed-step screenshot rides the FIRST+ # progress frame after that step lands — {step, title, image<=640px}.+ with _JOB_LOCK:+ pend = _JOB.get("pendingShots") or []+ if pend:+ out["stepShot"] = pend.pop(0) if snap.get("done") and snap.get("result") is not None: out["result"] = snap["result"] out["_hint"] = ("Demo complete: result.state.shots[] is every KiCad window as base64 "@@ -524,7 +563,9 @@ def handle_demo(kicad_info: dict, args: dict) -> dict: "kicad_close {\"force\":true} when the user is done looking.") else: out["_hint"] = ("Long-running job — render a real progress bar from percent/"- "elapsedSec/estimatedSec and show stepLabel. Poll every 2-3s.")+ "elapsedSec/estimatedSec and show stepLabel. Poll every 2-3s. "+ "stepShot (when present) is the completed step's own window as "+ "base64 PNG — pin it as that step's evidence.") return out # ---- background start ----
handlers/kicad_ui.py+3−3@@ -265,7 +265,7 @@ def _write_bmp(path: str, width: int, height: int, pixel_data: bytes): f.write(bytes(row_24)) -def _screenshot_hwnd(hwnd: int, label: str = "") -> dict:+def _screenshot_hwnd(hwnd: int, label: str = "", max_dim: int = 1568) -> dict: """Capture a window by HWND using PrintWindow. Returns {success, savedTo, sizeKB}.""" rect = _get_window_rect(hwnd) width = rect["width"]@@ -327,7 +327,7 @@ def _screenshot_hwnd(hwnd: int, label: str = "") -> dict: from PIL import Image img = Image.frombytes("RGBA", (width, height), pixel_buf.raw, "raw", "BGRA") img = img.transpose(Image.FLIP_TOP_BOTTOM)- MAX_DIM = 1568+ MAX_DIM = max_dim if max(width, height) > MAX_DIM: ratio = MAX_DIM / max(width, height) img = img.resize((int(width * ratio), int(height * ratio)), Image.LANCZOS)@@ -340,7 +340,7 @@ def _screenshot_hwnd(hwnd: int, label: str = "") -> dict: try: from handlers.png_stdlib import write_png_from_bgra path = os.path.join(SCREENSHOT_DIR, f"kicad{suffix}-{timestamp}.png")- write_png_from_bgra(path, width, height, pixel_buf.raw, max_dim=1568)+ write_png_from_bgra(path, width, height, pixel_buf.raw, max_dim=max_dim) except Exception: path = os.path.join(SCREENSHOT_DIR, f"kicad{suffix}-{timestamp}.bmp") _write_bmp(path, width, height, pixel_buf.raw)