diff --git a/src/woodshop/viewer.py b/src/woodshop/viewer.py index e132e43..3c9921d 100644 --- a/src/woodshop/viewer.py +++ b/src/woodshop/viewer.py @@ -102,6 +102,16 @@ def run(scene_path: Path | None = None, poll_s: float = 0.3) -> None: plotter.set_background("#2b2b2b") plotter.enable_parallel_projection() + # Let closing the window (X button) or pressing q/Escape end the loop. + closed = {"flag": False} + def _on_close(): + closed["flag"] = True + for key in ("q", "Escape"): + try: + plotter.add_key_event(key, _on_close) + except Exception: + pass + last_mtime = -1.0 scene = Scene.load(scene_path) if scene_path.exists() else Scene() _render(plotter, scene) @@ -109,6 +119,9 @@ def run(scene_path: Path | None = None, poll_s: float = 0.3) -> None: plotter.show(interactive_update=True, auto_close=False) while True: + # Stop if the render window has been closed by any means. + if closed["flag"] or getattr(plotter, "_closed", False) or plotter.render_window is None: + break try: mtime = scene_path.stat().st_mtime if scene_path.exists() else 0.0 if mtime != last_mtime: @@ -119,9 +132,14 @@ def run(scene_path: Path | None = None, poll_s: float = 0.3) -> None: time.sleep(poll_s) except KeyboardInterrupt: break - except Exception: # window closed + except Exception: # window destroyed mid-update break + try: + plotter.close() + except Exception: + pass + def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser(prog="woodshop-view", description="Live WoodShop 3D viewport.")