← Commit history

0.9.305: Tour window targeting (John's laptop: fourteen KiCad windows from days of demos; the 2D beat showed an empty untitled PCB Editor and the 3D beat the old sample cube): every beat now targets the tour's own document titles (esc-g431 schematic and board, the Adom STM32 symbol, the LQFP footprint), reuses an existing window instead of opening a duplicate, and opens each 3D view from that exact editor (open_3d_viewer accepts pcbHwnd/fpHwnd). The clean slate at start also closes old adom-demo documents and an empty untitled PCB Editor.

John Lauer ·4cdefaf191 ·1mo ago ·parent 5ddab98
4 files changed +52−16
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.304\ No newline at end of file+0.9.305\ No newline at end of file
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.304",+  "version": "0.9.305",   "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+39−13
@@ -436,8 +436,8 @@ def _warm_kicad(kicad_info: dict, prep: dict) -> dict:     import time     try:         from .kicad_ui import _find_all_kicad_windows-        titles = [(w.get("title") or "") for w in _find_all_kicad_windows()]-        if any("Schematic Editor" in t for t in titles) and any("PCB Editor" in t for t in titles):+        tt = _tour_titles(prep)+        if _hwnd_for_title(tt["schematic"]) and _hwnd_for_title(tt["board"]):             return {"warmed": True, "eeschema": True, "pcbnew": True, "reused": True}     except Exception:         pass@@ -463,6 +463,22 @@ def _warm_kicad(kicad_info: dict, prep: dict) -> dict:             "raw": {"schematic": sch, "board": brd}}  +def _doc_stem(prep: dict) -> str:+    try:+        return Path(prep.get("board") or prep.get("schematic") or "esc-g431").stem+    except Exception:  # pylint: disable=broad-except+        return "esc-g431"+++def _tour_titles(prep: dict) -> dict:+    """The exact window titles this tour drives. Generic titles ('PCB Editor',+    '3D Viewer') matched the wrong window on John's laptop (an empty untitled+    PCB Editor, an old demo board); every beat targets these instead."""+    stem = _doc_stem(prep)+    return {"schematic": f"{stem} — Schematic Editor", "board": f"{stem} — PCB Editor",+            "symbol": f"Adom:{IC_SYM} — Symbol Editor", "footprint": f"Adom:{IC_FP} — Footprint Editor"}++ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:     """Run one tour step. Returns narration-ready info, never raises."""     has3d = bool((prep.get("models3d") or {}).get("soic8"))@@ -485,7 +501,8 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:                     _t.sleep(2)             except Exception:                 loaded = True   # can't verify — don't fail the beat on the checker-        return {"ok": bool(r.get("success")) and loaded, "window": "Symbol Editor",+        return {"ok": bool(r.get("success")) and loaded, "window": _tour_titles(prep)["symbol"],+                "hwnd": _hwnd_for_title(_tour_titles(prep)["symbol"]),                 "title": "1/6 · The MCU symbol",                 "say": ("This is the STM32G431, the microcontroller on the board you are about to see, "                         "in your own KiCad Symbol Editor. The bridge lifted it from the board's schematic "@@ -495,7 +512,8 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:                 "raw": r}     if step == "footprint":         r = handle_open_footprint_editor(kicad_info, {"libraryName": DEMO_LIB, "footprintName": IC_FP})-        return {"ok": bool(r.get("success")), "window": "Footprint Editor",+        _fh = _hwnd_for_title(_tour_titles(prep)["footprint"])+        return {"ok": bool(r.get("success")) and bool(_fh), "window": _tour_titles(prep)["footprint"], "hwnd": _fh,                 "title": "2/6 · Its footprint",                 "say": ("Same chip, now its land pattern: a 48 pin LQFP on half millimetre pitch, "                         "taken from the board itself. The symbol is the idea, the footprint is the copper."),@@ -509,7 +527,8 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:         # The viewer that APPEARS after this open is the chip's; remember it so         # the board beat can close it before opening its own.         before = set(_viewers_3d())-        r = handle_open_3d_viewer(kicad_info, {"editor": "footprint"})+        _fh = _hwnd_for_title(_tour_titles(prep)["footprint"])+        r = handle_open_3d_viewer(kicad_info, {"editor": "footprint", **({"fpHwnd": _fh} if _fh else {})})         v = _new_window(before, _viewers_3d, 8.0)         with _JOB_LOCK:             _JOB["chipViewer"] = v@@ -523,8 +542,11 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:                              if has3d else ["the bare pads (KiCad's 3D library is not installed here)"]),                 "raw": r}     if step == "schematic":-        r = handle_open_schematic(kicad_info, {"filePath": prep["schematic"]})-        return {"ok": bool(r.get("success")), "window": "Schematic Editor",+        _t = _tour_titles(prep)["schematic"]+        _sh = _hwnd_for_title(_t)+        r = {"success": True, "reused": True} if _sh else handle_open_schematic(kicad_info, {"filePath": prep["schematic"]})+        _sh = _sh or _new_window(set(), lambda: [h for h in [_hwnd_for_title(_t)] if h], 20.0)+        return {"ok": bool(r.get("success")) and bool(_sh), "window": _t, "hwnd": _sh,                 "title": "4/6 · The ESC schematic",                 "say": ("Now the real schematic: the Adom ESC G431, a brushless motor controller, "                         "straight from its wiki page. Over two hundred symbols: the STM32 you just saw, "@@ -533,8 +555,11 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:                              "the whole sheet came from wiki.adom.inc/adom/esc-g431"],                 "raw": r}     if step == "board":-        r = handle_open_board(kicad_info, {"filePath": prep["board"]})-        return {"ok": bool(r.get("success")), "window": "PCB Editor",+        _t = _tour_titles(prep)["board"]+        _bh = _hwnd_for_title(_t)+        r = {"success": True, "reused": True} if _bh else handle_open_board(kicad_info, {"filePath": prep["board"]})+        _bh = _bh or _new_window(set(), lambda: [h for h in [_hwnd_for_title(_t)] if h], 25.0)+        return {"ok": bool(r.get("success")) and bool(_bh), "window": _t, "hwnd": _bh,                 "title": "5/6 · The board in 2D",                 "say": ("The layout of that board: one hundred and forty nine footprints, the MOSFETs "                         "around the edge, the MCU in the middle, copper, silkscreen and courtyards all real."),@@ -549,8 +574,7 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:         try:             from .kicad_ui import _find_all_kicad_windows             for _ in range(15):-                if any("pcb editor" in (w.get("title") or "").lower()-                       for w in _find_all_kicad_windows()):+                if _hwnd_for_title(_tour_titles(prep)["board"]):                     break                 time.sleep(1)         except Exception:@@ -561,7 +585,8 @@ def _run_step(kicad_info: dict, step: str, prep: dict) -> dict:             _close_hwnd(chip)                       # the chip's viewer must not win the title match             time.sleep(1.5)         before = set(_viewers_3d())-        r = handle_open_3d_viewer(kicad_info, {"editor": "pcb"})+        _bh = _hwnd_for_title(_tour_titles(prep)["board"])+        r = handle_open_3d_viewer(kicad_info, {"editor": "pcb", **({"pcbHwnd": _bh} if _bh else {})})         v = _new_window(before, _viewers_3d, 12.0)         if not v:             vs = _viewers_3d()@@ -1168,7 +1193,8 @@ def _close_stale_tour_windows() -> None:         from handlers.kicad_ui import _find_all_kicad_windows         for w in _find_all_kicad_windows():             t = (w.get("title") or "").lower()-            if "3d viewer" in t or "footprint editor" in t:+            if ("3d viewer" in t or "footprint editor" in t or t.startswith("adom-demo")+                    or t.strip() == "pcb editor"):                 _close_hwnd(int(w["hwnd"]))         import time as _t         _t.sleep(1.5)
handlers/open_footprint_editor.py+11−1
@@ -1270,6 +1270,16 @@ def handle_open_3d_viewer(kicad_info: dict, args: dict) -> dict:      fp_hwnd = _find_footprint_editor()     pcb_hwnd = _find_pcb_editor()+    # Explicit source editors (tour, 2026-09-03): on a laptop with several PCB+    # Editors (an empty untitled one, an old demo board, the real board) the+    # first title match opened the wrong board's 3D view. A caller that knows+    # its editor's hwnd passes pcbHwnd / fpHwnd and we never guess.+    _pcb_override = args.get("pcbHwnd") or args.get("pcb_hwnd")+    _fp_override = args.get("fpHwnd") or args.get("fp_hwnd")+    if _pcb_override:+        pcb_hwnd = int(_pcb_override)+    if _fp_override:+        fp_hwnd = int(_fp_override)     if editor_choice in {"fp", "footprint"}:         if not fp_hwnd:             return {@@ -1386,7 +1396,7 @@ def handle_open_3d_viewer(kicad_info: dict, args: dict) -> dict:     # over the legacy Alt+3 keystroke. The bridge posts WM_COMMAND 20563     # (3D Viewer menu id from PCB Editor's View menu) which doesn't     # require foregrounding the window or stealing keyboard focus.-    pcb_hwnd = _find_pcb_editor()+    pcb_hwnd = int(_pcb_override) if _pcb_override else _find_pcb_editor()     if pcb_hwnd:         # Tier 0 (v0.9.99, #26): native Win32 menu-bar resolve + PostMessage.         try: