diff --git a/embodichain/lab/sim/sim_manager.py b/embodichain/lab/sim/sim_manager.py index c8d1cf875..5f4fc6977 100644 --- a/embodichain/lab/sim/sim_manager.py +++ b/embodichain/lab/sim/sim_manager.py @@ -255,6 +255,7 @@ def __new__(cls, sim_config: SimulationManagerCfg = SimulationManagerCfg()): instance = super(SimulationManager, cls).__new__(cls) # Store sim_config in the instance for use in __init__ or elsewhere instance.sim_config = sim_config + instance._is_constructed = False cls._instances[n_instance] = instance return instance @@ -378,6 +379,8 @@ def __init__( if sim_config.headless is False: self._window = self._world.get_windows() + self._is_constructed = True + @classmethod def get_instance(cls, instance_id: int = 0) -> SimulationManager: """Get the instance of SimulationManager by id. @@ -3117,6 +3120,12 @@ def _deferred_destroy(self) -> None: self.stop_window_record() self.wait_window_record_saves() + # Stop the render loop before releasing scene resources. Vulkan window + # presentation may otherwise continue acquiring swapchain images while + # Env::Clean tears down render objects used by the in-flight frame. + if getattr(self, "is_window_opened", False): + self.close_window() + import sys, gc self.clean_materials() diff --git a/scripts/tutorials/atomic_action/tutorial_utils.py b/scripts/tutorials/atomic_action/tutorial_utils.py index f90c1b127..8165f8ff3 100644 --- a/scripts/tutorials/atomic_action/tutorial_utils.py +++ b/scripts/tutorials/atomic_action/tutorial_utils.py @@ -230,16 +230,30 @@ def run_tutorial(main: Callable[[], None]) -> None: Args: main: Zero-argument tutorial entry point. """ + interrupted = False try: - main() + try: + main() + except KeyboardInterrupt: + # Handle Ctrl+C before native cleanup. An active traceback keeps + # main() locals (including borrowed C++ material wrappers) alive; + # destroying World first would make their later destructors unsafe. + interrupted = True + logger.log_info("Tutorial interrupted; shutting down cleanly.") finally: if SimulationManager.is_instantiated(): sim = SimulationManager.get_instance() - if sim.is_window_recording(): - sim.stop_window_record() - sim.wait_window_record_saves() - sim.destroy(exit_process=False) - SimulationManager.flush_cleanup_queue() + if not getattr(sim, "_is_constructed", False): + SimulationManager.reset(getattr(sim, "instance_id", 0)) + else: + if sim.is_window_recording(): + sim.stop_window_record() + sim.wait_window_record_saves() + sim.destroy(exit_process=False) + SimulationManager.flush_cleanup_queue() + + if interrupted: + raise SystemExit(130) def add_ur5_gripper_robot(