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.302: Tour sync fixes (John's run on 0.9.301): stale panel presses from the previous show are drained before a tour starts (his earlier NEXT presses made the new tour jump to step 3 at once); the narration mute is reset from the call's muted flag at thread start and pushed to the panel on the first show; STOP from the remote sets stopping on progress immediately so the dashboard can close its own pill; progress carries stopping.
3 files changed
+36−3
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.301+0.9.302
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.301",+ "version": "0.9.302", "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+34−1@@ -986,6 +986,7 @@ def on_panel_event(ev: dict) -> dict: # poll _EXIT and bail at their next gesture. _EXIT["now"] = True _caption_clear()+ _job_update(stopping=True, stepLabel="Closing the demo tour") set_demo_control(cmd) _job_update(lastPress={"button": b, "at": at, "via": "callback"}) return {"queued": cmd}@@ -1032,6 +1033,27 @@ def _panel_poll() -> str | None: return None +def _drain_stale_presses() -> None:+ """Empty the panel's press queue before a tour starts. Presses queued+ against the previous show (same id) must not steer this one."""+ try:+ from handlers import ad_client+ for _ in range(3):+ r = ad_client.call("desktop_demo_panel", {"action": "poll", "id": _PANEL_ID}, timeout=6) or {}+ out = r.get("output") if isinstance(r, dict) else None+ if isinstance(out, str):+ try:+ import json as _j+ out = _j.loads(out)+ except Exception: # pylint: disable=broad-except+ out = {}+ d = ((out or {}).get("data") if isinstance(out, dict) else None) or out or {}+ if not d.get("events"):+ break+ except Exception: # pylint: disable=broad-except+ pass++ def _panel_hide() -> None: if not _PANEL["ok"]: return@@ -1126,6 +1148,15 @@ def _run_demo_background(kicad_info: dict) -> None: done_weight = 0.0 try: _EXIT["now"] = False+ _PANEL_SEEN.clear()+ _PANEL_SHOWN_ONCE["v"] = False+ _PANEL["scriptState"] = None+ # John, 2026-09-03: "no audio" and "jumped to step 3 instantly". The mute+ # flag outlived the previous tour, and ab keeps undrained presses for a+ # panel id across shows, so last run's NEXT presses fired at once. Reset+ # the mute from THIS call's args and swallow whatever the panel queued.+ _NARR["muted"] = bool(_JOB.get("mutedRequested"))+ _drain_stale_presses() _panel_show(0, "playing") _caption(_BEAT_CAPTIONS["prepare"], duration_ms=_BEAT_CAPTION_MS["prepare"]) _play_narration("prepare")@@ -1331,7 +1362,7 @@ def handle_demo(kicad_info: dict, args: dict) -> dict: out = {"success": True, **{k: snap.get(k) for k in ("done", "step", "stepLabel", "beatIndex", "beatCount", "percent", "elapsedSec", "estimatedSec", "error",- "panel", "muted", "muteSource", "lastPress", "paused")}}+ "panel", "muted", "muteSource", "lastPress", "paused", "stopping")}} # 0.9.199 (wiki issue #39, John): `active` used to be a BOOLEAN here and # an ARRAY in kicad_progress - the same key, two types, from one bridge. # A renderer written as `if (resp.active)` then treats a finished job as@@ -1376,6 +1407,8 @@ def handle_demo(kicad_info: dict, args: dict) -> dict: # ---- background start ---- if args.get("all") and args.get("background"): _NARR["muted"] = bool(args.get("muted"))+ with _JOB_LOCK:+ _JOB["mutedRequested"] = bool(args.get("muted")) # the thread re-applies this at start (sync rule) snap = _job_snapshot() if snap.get("active"): return {"success": False, "error": "a demo job is already running",