← Commit history

0.9.326: Tour: all editor and viewer windows at 95 percent of the work area, centered; 2D motion aims at the featured part (position parsed from the board and schematic) and checks the canvas after each gesture; 3D motion re-frames when the board is clipped or tiny

John Lauer ·c1b5cfcbf4 ·1mo ago ·parent d106f8f
1 file changed +46−10
handlers/demo_motion.py+46−10
@@ -146,7 +146,7 @@ def _menu_ids(hwnd: int) -> dict:     return ids  -def choreograph_3d_menu(hwnd: int, abort=None, log=None) -> dict | None:+def choreograph_3d_menu(hwnd: int, abort=None, log=None, measure=None) -> dict | None:     """Deterministic 3D motion through KiCad's own View menu (WM_COMMAND, 10     degrees per rotate, no mouse, no foreground needed): mouse drags over-rotated     the chip viewer and did nothing in the board viewer on 2026-09-03 because@@ -180,10 +180,26 @@ def choreograph_3d_menu(hwnd: int, abort=None, log=None) -> dict | None:         if log:             log(f"motion 3d(menu): {name}")         time.sleep(0.4)+        # Framing check (John, 2026-09-05): after every step look at the canvas.+        # Board clipped on two opposite edges -> one zoom-out; board tiny -> one+        # zoom-in. Recorded so the run says what it corrected.+        if measure and name not in ("extents",):+            try:+                m = measure() or {}+                t = set(m.get("touches") or [])+                if ({"left", "right"} <= t or {"top", "bottom"} <= t) and "zoom_out" in ids:+                    win_menu.invoke_menu_command(hwnd, ids["zoom_out"]); time.sleep(0.5)+                    rep.setdefault("framed", []).append(f"{name}: clipped {sorted(t)} -> zoom out")+                elif (m.get("coverage") or 0) < 0.10 and m.get("bbox") and "zoom_in" in ids:+                    win_menu.invoke_menu_command(hwnd, ids["zoom_in"]); time.sleep(0.5)+                    rep.setdefault("framed", []).append(f"{name}: coverage {m.get('coverage')} -> zoom in")+                rep["lastCoverage"] = m.get("coverage")+            except Exception:  # pylint: disable=broad-except+                pass     return rep  -def choreograph(hwnd: int, kind: str, abort=None, log=None, check=None) -> dict:+def choreograph(hwnd: int, kind: str, abort=None, log=None, check=None, measure=None, focus=None) -> dict:     """Run the beat's motion on `hwnd`. kind: '2d' | '3d'. Returns a report;     never raises. Stops the instant the window is not in front or abort()."""     rep = {"kind": kind, "gestures": 0, "aborted": None}@@ -207,7 +223,7 @@ def choreograph(hwnd: int, kind: str, abort=None, log=None, check=None) -> dict:     if kind == "3d":         try:             time.sleep(1.2)                    # let the STEP bodies load before the first turn-            menu_rep = choreograph_3d_menu(hwnd, abort=abort, log=log)+            menu_rep = choreograph_3d_menu(hwnd, abort=abort, log=log, measure=measure)             if menu_rep is not None:                 return menu_rep         except Exception as e:  # pylint: disable=broad-except@@ -236,13 +252,33 @@ def choreograph(hwnd: int, kind: str, abort=None, log=None, check=None) -> dict:             # smart about how close". Extents first, two wheel clicks in (about             # 1.7x), a short pan, back out, and END on extents so the frame the             # user is left with is the whole thing.+            # John, 2026-09-05: "be smart about your zooming/panning cuz you still+            # don't end up showing important stuff". Aim at the featured part: after+            # extents, measure where the drawing sits on the canvas and map the+            # part's fraction (from the design files) onto it; wheel-zoom AT that+            # point (KiCad zooms about the cursor), pan it to the centre, hold,+            # zoom back out, extents.+            aim = {"x": cx, "y": cy}+            def _aim():+                key(VK_HOME); time.sleep(0.7)+                if measure and focus:+                    try:+                        m = measure() or {}+                        bb = m.get("bbox")+                        if bb:+                            aim["x"] = int(bb[0] + float(focus[0]) * (bb[2] - bb[0]))+                            aim["y"] = int(bb[1] + float(focus[1]) * (bb[3] - bb[1]))+                            rep["aim"] = {"x": aim["x"], "y": aim["y"], "bbox": bb, "coverage": m.get("coverage")}+                    except Exception:  # pylint: disable=broad-except+                        pass             plan = [-                ("extents", lambda: key(VK_HOME)),-                ("zoom-in", lambda: wheel(2, cx, cy)),-                ("pan-right", lambda: drag("middle", cx, cy, cx - dx, cy, steps=16)),-                ("pan-down", lambda: drag("middle", cx, cy, cx, cy - dy, steps=16)),-                ("pan-back", lambda: drag("middle", cx, cy, cx + dx, cy + dy, steps=16)),-                ("zoom-out", lambda: wheel(-2, cx, cy)),+                ("extents", _aim),+                ("zoom-in", lambda: wheel(2, aim["x"], aim["y"])),+                ("centre", lambda: drag("middle", aim["x"], aim["y"], cx, cy, steps=18)),+                ("zoom-in-2", lambda: wheel(1, cx, cy)),+                ("pan-look", lambda: drag("middle", cx, cy, cx - dx, cy - dy, steps=14)),+                ("pan-back", lambda: drag("middle", cx - dx, cy - dy, cx, cy, steps=14)),+                ("zoom-out", lambda: wheel(-3, cx, cy)),                 ("extents-2", lambda: key(VK_HOME)),             ]         for name, fn in plan:@@ -257,7 +293,7 @@ def choreograph(hwnd: int, kind: str, abort=None, log=None, check=None) -> dict:             # that content goes off the screen ... analyze your motion". After             # every 2D gesture that can empty the view, look at the canvas; a             # blank one means we panned off the content: recenter on extents.-            if check and kind != "3d" and name.startswith(("pan", "zoom-in")):+            if check and kind != "3d" and name.startswith(("pan", "zoom-in", "centre")):                 try:                     if not check():                         key(VK_HOME); time.sleep(0.45)