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.157: session sanitize - bridge projects are scrubbed from KiCad restore-on-launch (kicad.json system.open_projects) so a user launch never auto-opens OUR demo and hits the project lock warning; project paths recorded in the ownership ledger
8 files changed
+112−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.156+0.9.157
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.156",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.156/adom-bridge-kicad-v0.9.156.zip",- "sha256": "504ef46dd51e4e7addcc45f4de7cb99a58bae307c827f94640db98d05ac9ed5a",- "size": 437802,+ "version": "0.9.157",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.157/adom-bridge-kicad-v0.9.157.zip",+ "sha256": "4ad644794c75f42deb992d2e34bd938b27d6cf315c1a002130fe5c4155a13a6e",+ "size": 438915, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-17T21:17:20Z",+ "released_at": "2026-08-17T21:29:36Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.157.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.156",+ "version": "0.9.157", "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+5@@ -300,6 +300,11 @@ def _prepare(kicad_info: dict) -> dict: (d / "adom-demo.kicad_pro").write_text('{"board":{},"meta":{"filename":"adom-demo.kicad_pro","version":1},"schematic":{},"sheets":[]}', encoding="utf-8") out["projectDir"] = str(d) out["project"] = str(d / "adom-demo.kicad_pro")+ try:+ from handlers.win_focus import register_owned_project+ register_owned_project(out["project"])+ except Exception:+ pass out["schematic"] = str(d / "adom-demo.kicad_sch") out["board"] = str(d / "adom-demo.kicad_pcb") return out
handlers/make_part_project.py+6@@ -209,6 +209,12 @@ def handle_make_part_project(kicad_info: dict, args: dict) -> dict: p = out_dir / name p.write_text(content, encoding="utf-8") files[name.rsplit(".", 1)[-1]] = str(p)+ if name.endswith(".kicad_pro"):+ try:+ from handlers.win_focus import register_owned_project+ register_owned_project(str(p))+ except Exception:+ pass # stale single-instance locks from a previous open of the same project for lck in out_dir.glob("*.lck"): try:
handlers/win_focus.py+88−1@@ -439,7 +439,8 @@ def _save_ledger() -> None: wins.append({"hwnd": int(h), "pid": _pid_of_hwnd(h)}) except Exception: pass- _ledger_path().write_text(_json.dumps({"pids": sorted(live), "windows": wins}))+ _ledger_path().write_text(_json.dumps({"pids": sorted(live), "windows": wins,+ "projects": sorted(_owned_projects)})) except Exception: pass @@ -468,6 +469,11 @@ def load_owned_ledger() -> None: _owned_pids.add(int(p)) except Exception: pass+ for p in data.get("projects", []):+ try:+ _owned_projects.add(str(p))+ except Exception:+ pass def register_owned_pid(pid) -> None:@@ -549,6 +555,82 @@ def is_bridge_window(hwnd) -> bool: return False +_owned_projects: set = set()+_last_sanitize = 0.0+++def register_owned_project(path) -> None:+ """Project-creating verbs (make_part_project, demo prep) record their+ .kicad_pro here so session sanitizing knows exactly what is OURS."""+ try:+ if path:+ _owned_projects.add(str(path))+ _save_ledger()+ except Exception:+ pass+++def _any_kicad_process_running() -> bool:+ try:+ pm = _parent_pid_map()+ for pid in pm:+ if _pid_is_kicad(pid):+ return True+ except Exception:+ pass+ return False+++def _is_bridge_project_path(p: str) -> bool:+ q = (p or "").replace("/", "\\").lower()+ if any(q == str(o).replace("/", "\\").lower() for o in _owned_projects):+ return True+ # legacy pollution predating the project ledger: the bridge's own demo+ # naming (adom-* and in-<part>-demo under Documents)+ import re as _re+ return bool(_re.search(r"\\(adom-[a-z0-9-]+|in-[a-z0-9]+-demo)\\", q))+++def sanitize_kicad_session() -> int:+ """Remove BRIDGE projects from KiCad's restore-on-launch list (John,+ 2026-08-17: every launch of HIS KiCad auto-opened OUR adom-demo and hit the+ project lock warning). kicad.json system.open_projects is what the PM+ restores at startup; KiCad rewrites it at exit, so edits only stick while+ NO KiCad runs — the sweep retries until that window opens. file_history is+ left alone (honest recents). Returns entries removed."""+ global _last_sanitize+ import time as _tm+ if not IS_WINDOWS:+ return 0+ _last_sanitize = _tm.monotonic()+ if _any_kicad_process_running():+ return 0+ removed = 0+ try:+ import glob as _glob+ import json as _json+ import os as _os+ appdata = _os.environ.get("APPDATA") or ""+ for cfg in _glob.glob(_os.path.join(appdata, "kicad", "*", "kicad.json")):+ try:+ data = _json.loads(open(cfg, encoding="utf-8").read())+ sysd = data.get("system") or {}+ op = sysd.get("open_projects") or []+ keep = [p for p in op if not _is_bridge_project_path(p)]+ if len(keep) != len(op):+ sysd["open_projects"] = keep+ data["system"] = sysd+ open(cfg, "w", encoding="utf-8").write(_json.dumps(data, indent=2))+ removed += len(op) - len(keep)+ print(f"[session] scrubbed {len(op)-len(keep)} bridge project(s) "+ f"from {cfg}", file=sys.stderr)+ except Exception:+ pass+ except Exception:+ pass+ return removed++ def heal_offscreen_windows() -> int: """Boot repair (John, 2026-08-17: "when the user goes to open kicad normally ... it starts way offscreen and kicad doesn't fix itself"): if a parked@@ -663,6 +745,11 @@ def _start_park_sweep() -> None: continue _t.sleep(0.1) else:+ if _t.monotonic() - _last_sanitize > 60:+ try:+ sanitize_kicad_session()+ except Exception:+ pass _t.sleep(0.25) except Exception: try:
server.py+6@@ -1110,6 +1110,12 @@ def main(): atexit.register(win_focus.unpark_all) except Exception: pass+ # 0.9.157: scrub bridge projects from KiCad's restore-on-launch list (only+ # sticks while no KiCad runs; the sweep retries until that window opens).+ try:+ win_focus.sanitize_kicad_session()+ except Exception:+ pass try: from handlers.win_taskbar import badge_existing_on_boot badge_existing_on_boot()