diff --git a/genesis/ext/pyrender/viewer.py b/genesis/ext/pyrender/viewer.py index fbbf92bbb1..fa29be1551 100644 --- a/genesis/ext/pyrender/viewer.py +++ b/genesis/ext/pyrender/viewer.py @@ -19,16 +19,21 @@ # Importing tkinter and creating a first context before importing pyglet is necessary to avoid later segfault on MacOS. # Note that destroying the window will cause segfault at exit. +# On macOS 26+ (Darwin 25, Tahoe), system Tk 8.5 crashes on init due to a Darwin/product version mismatch. +# The pyglet segfault workaround is not needed on macOS 26+. root = None if sys.platform.startswith("darwin"): - try: - from tkinter import Tk + import platform as _platform + _mac_major = int(_platform.mac_ver()[0].split(".")[0]) if _platform.mac_ver()[0] else 0 + if _mac_major < 26: + try: + from tkinter import Tk - root = Tk() - root.withdraw() - except Exception: - # Some minimal Python install may not provide a working tkinter interface even if it is a standard library - pass + root = Tk() + root.withdraw() + except Exception: + # Some minimal Python install may not provide a working tkinter interface even if it is a standard library + pass import pyglet @@ -1212,6 +1217,16 @@ def start(self, auto_refresh=True): self.set_caption(self.viewer_flags["window_title"]) self.activate() + # On macOS, we need to explicitly activate the app to bring the window to focus + # when running from terminal (not as a bundled .app) + if sys.platform.startswith("darwin"): + try: + from AppKit import NSApp, NSApplication + NSApplication.sharedApplication() + NSApp.activateIgnoringOtherApps_(True) + except ImportError: + gs.logger.debug("PyObjC not available, window may not come to focus automatically") + # The viewer can be considered as fully initialized at this point if not self._initialized_event.is_set(): self._initialized_event.set() @@ -1240,6 +1255,18 @@ def run(self): elif threading.main_thread() != threading.current_thread(): raise RuntimeError("'Viewer.run' can only be called manually from main thread on MacOS.") + # On macOS, we need to explicitly activate the app to bring the window to focus + # when running from terminal (not as a bundled .app) + if sys.platform.startswith("darwin"): + try: + from AppKit import NSApp, NSApplication + NSApplication.sharedApplication() + NSApp.activateIgnoringOtherApps_(True) + # Also request the window to be frontmost + self.activate() + except ImportError: + gs.logger.debug("PyObjC not available, window may not come to focus automatically") + while self._is_active: try: self.refresh() diff --git a/genesis/vis/visualizer.py b/genesis/vis/visualizer.py index 51524abbf1..b539d866c5 100644 --- a/genesis/vis/visualizer.py +++ b/genesis/vis/visualizer.py @@ -66,9 +66,9 @@ def __init__(self, scene, show_viewer, vis_options, viewer_options, renderer_opt if viewer_options.run_in_thread is None: if sys.platform == "linux": viewer_options.run_in_thread = True - elif sys.platform == "darwin": + elif sys.platform == "darwin" or gs.platform == "macOS": viewer_options.run_in_thread = False - elif sys.platform == "win32": + elif sys.platform == "win32" or gs.platform == "Windows": viewer_options.run_in_thread = True if sys.platform == "darwin" and viewer_options.run_in_thread: gs.raise_exception("Running viewer in background thread is not supported on MacOS.") diff --git a/pyproject.toml b/pyproject.toml index 0f5ff142f5..682c9fb783 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,8 @@ dependencies = [ "fast_simplification>=0.1.12", # Surface reconstruction library for particle data from SPH simulations "pysplashsurf==0.14.*", + # Used on macOS to properly bring the viewer window to focus when running from terminal + "pyobjc-framework-Cocoa; sys_platform == 'darwin'", ] [project.optional-dependencies]