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.315: Tour remote: prev/next/pause cut the beat in flight at the next gesture; back goes to the beat before the one on screen; presses and acks recorded
3 files changed
+35−13
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.314+0.9.315
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.314",+ "version": "0.9.315", "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+33−11@@ -819,6 +819,8 @@ def _verify_beat(hwnd: int | None, expected_title: str, tries: int = 2) -> dict: prev = _digest(); v["stable"] = False; v["settleMs"] = 0 t0 = _t.monotonic() while _t.monotonic() - t0 < 8.0:+ if _pending_interrupt():+ v["interrupted"] = True; break _t.sleep(0.8); cur = _digest() if prev and cur and prev == cur: v["stable"] = True; break@@ -1238,9 +1240,12 @@ def on_panel_event(ev: dict) -> dict: _PANEL_SEEN.add(str(at)) b = str(ev.get("button") or "").lower() _ACK = {"mute": "Remote: muted", "unmute": "Remote: narration back on",- "pause": "Remote: pausing after this beat", "play": "Remote: playing",- "next": "Remote: next, after this beat", "back": "Remote: back, after this beat",+ "pause": "Remote: pausing", "play": "Remote: playing",+ "next": "Remote: next", "back": "Remote: back", "stop": "Remote: stopping the tour"}+ with _JOB_LOCK:+ _JOB.setdefault("presses", []).append({"button": b, "at": at, "via": "callback",+ "beat": _JOB.get("beatIndex"), "step": _JOB.get("step")}) if b in _ACK: _caption_ack(_ACK[b]) if b in ("mute", "unmute"):@@ -1252,7 +1257,7 @@ def on_panel_event(ev: dict) -> dict: cmd = mapping.get(b) if not cmd: return {"ignored": b}- if cmd in ("pause", "exit"):+ if cmd in ("pause", "exit", "next", "prev"): _stop_narration() # the user's hand: silence within the beat if cmd == "exit": # John, 2026-09-03: on the panel's X "exit immediately, remove the@@ -1412,13 +1417,27 @@ def _caption_ack(text: str) -> None: indicating you got my signal').""" try: from handlers import ad_client- ad_client.call("desktop_caption", {"id": "kicad-demo-ack", "text": text, "duration": 3500,- "size": "medium", "position": "top",- "reason": "Acknowledge a press on the KiCad tour remote the instant it arrives"}, timeout=6)+ r = ad_client.call("desktop_caption", {"id": "kicad-demo-ack", "text": text, "duration": 3500,+ "size": "medium", "position": "top",+ "reason": "Acknowledge a press on the KiCad tour remote the instant it arrives"}, timeout=6) or {}+ with _JOB_LOCK:+ _JOB.setdefault("captions", []).append({"text": text[:60], "ack": True,+ "ok": bool(r.get("success")) and not r.get("error"),+ "error": (r.get("error") or None)}) except Exception: # pylint: disable=broad-except pass +def _pending_interrupt() -> bool:+ """A transport press that must cut the beat in flight (John, 2026-09-04:+ "i would expect your prev/next to be instant, not wait for next beat").+ Motion polls this between gestures; the narration wait polls it too."""+ if _EXIT["now"]:+ return True+ with _CTRL_LOCK:+ return _CTRL["cmd"] in ("prev", "next", "pause", "exit")++ def _adopt_tour_identity() -> None: """Re-apply the identity of the request that STARTED the tour on this thread. @@ -1513,7 +1532,7 @@ def _run_demo_background(kicad_info: dict) -> None: cmd = _drain_ctrl() if cmd == "pause": _stop_narration()- _panel_show(i + 1, "paused")+ _panel_show(max(1, i), "paused") # the beat on screen, not the next _caption("KiCad tour paused" + ("" if _PANEL["ok"] else " - use the toast buttons or the dashboard to continue"), duration_ms=300000)@@ -1531,9 +1550,12 @@ def _run_demo_background(kicad_info: dict) -> None: exited = True break if cmd == "prev":- i = max(0, i - 1)- if results:- results.pop()+ # i already points past the beat the user was watching (it+ # advanced at the bottom of the loop), so "back" is two steps:+ # measured on John's laptop 2026-09-04, i - 1 replayed the same+ # footprint beat twice.+ i = max(0, i - 2)+ del results[i:] # ("next" simply proceeds: we are already between beats, so moving # on IS skipping the wait; a next during a beat applies here.) s_ = STEPS[i]@@ -1591,7 +1613,7 @@ def _run_demo_background(kicad_info: dict) -> None: try: from handlers import demo_motion kind = "3d" if s_ in ("footprint3d", "board3d") else "2d"- r["motion"] = demo_motion.choreograph(target, kind, abort=lambda: _EXIT["now"])+ r["motion"] = demo_motion.choreograph(target, kind, abort=_pending_interrupt) except Exception as e: # pylint: disable=broad-except r["motion"] = {"error": f"{type(e).__name__}: {e}"} if _EXIT["now"]: