diff --git a/scripts/environments/random_agent.py b/scripts/environments/random_agent.py index 46b6cdc1468..6ecce8de74d 100644 --- a/scripts/environments/random_agent.py +++ b/scripts/environments/random_agent.py @@ -18,7 +18,6 @@ ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -52,8 +51,8 @@ def main(): device=args_cli.device, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric, - newton_visualizer=args_cli.newton_visualizer, ) + # create environment env = gym.make(args_cli.task, cfg=env_cfg) diff --git a/scripts/environments/zero_agent.py b/scripts/environments/zero_agent.py index ab0301b8e7a..bdca64d515e 100644 --- a/scripts/environments/zero_agent.py +++ b/scripts/environments/zero_agent.py @@ -18,7 +18,6 @@ ) parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -52,8 +51,8 @@ def main(): device=args_cli.device, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric, - newton_visualizer=args_cli.newton_visualizer, ) + # create environment env = gym.make(args_cli.task, cfg=env_cfg) diff --git a/scripts/reinforcement_learning/rl_games/play.py b/scripts/reinforcement_learning/rl_games/play.py index ca807368406..a80329c2757 100644 --- a/scripts/reinforcement_learning/rl_games/play.py +++ b/scripts/reinforcement_learning/rl_games/play.py @@ -32,7 +32,6 @@ help="When no checkpoint provided, use the last saved model. Otherwise use the best saved model.", ) parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -80,12 +79,9 @@ def main(): task_name = args_cli.task.split(":")[-1] # parse env configuration env_cfg = parse_env_cfg( - args_cli.task, - device=args_cli.device, - num_envs=args_cli.num_envs, - use_fabric=not args_cli.disable_fabric, - newton_visualizer=args_cli.newton_visualizer, + args_cli.task, device=args_cli.device, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric ) + agent_cfg = load_cfg_from_registry(args_cli.task, "rl_games_cfg_entry_point") # specify directory for logging experiments diff --git a/scripts/reinforcement_learning/rl_games/train.py b/scripts/reinforcement_learning/rl_games/train.py index 9e0cfc67739..778114dcedb 100644 --- a/scripts/reinforcement_learning/rl_games/train.py +++ b/scripts/reinforcement_learning/rl_games/train.py @@ -38,7 +38,6 @@ const=True, help="if toggled, this experiment will be tracked with Weights and Biases", ) -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -90,7 +89,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg, agent_cfg: dict): # override configurations with non-hydra CLI arguments env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device - env_cfg.sim.enable_newton_rendering = args_cli.newton_visualizer # randomly sample a seed if seed = -1 if args_cli.seed == -1: diff --git a/scripts/reinforcement_learning/rsl_rl/play.py b/scripts/reinforcement_learning/rsl_rl/play.py index 9de5852b233..eea022507cf 100644 --- a/scripts/reinforcement_learning/rsl_rl/play.py +++ b/scripts/reinforcement_learning/rsl_rl/play.py @@ -34,7 +34,6 @@ help="Use the pre-trained checkpoint from Nucleus.", ) parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append RSL-RL cli arguments cli_args.add_rsl_rl_args(parser) # append AppLauncher cli args @@ -96,7 +95,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg, agent_cfg: RslRlBaseRun # note: certain randomizations occur in the environment initialization so we set the seed here env_cfg.seed = agent_cfg.seed env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device - env_cfg.sim.enable_newton_rendering = args_cli.newton_visualizer # specify directory for logging experiments log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name) @@ -117,19 +115,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg, agent_cfg: RslRlBaseRun # set the log directory for the environment (works for all environment types) env_cfg.log_dir = log_dir - # Set play mode for Newton viewer if using Newton visualizer - if args_cli.newton_visualizer: - # Set visualizer to play mode in Newton config - if hasattr(env_cfg.sim, "newton_cfg"): - env_cfg.sim.newton_cfg.visualizer_train_mode = False - else: - # Create newton_cfg if it doesn't exist - from isaaclab.sim._impl.newton_manager_cfg import NewtonCfg - - newton_cfg = NewtonCfg() - newton_cfg.visualizer_train_mode = False - env_cfg.sim.newton_cfg = newton_cfg - # create isaac environment env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None) diff --git a/scripts/reinforcement_learning/rsl_rl/train.py b/scripts/reinforcement_learning/rsl_rl/train.py index 56e712cfe21..b9a80a0ef1b 100644 --- a/scripts/reinforcement_learning/rsl_rl/train.py +++ b/scripts/reinforcement_learning/rsl_rl/train.py @@ -32,7 +32,6 @@ "--distributed", action="store_true", default=False, help="Run training with multiple GPUs or nodes." ) parser.add_argument("--export_io_descriptors", action="store_true", default=False, help="Export IO descriptors.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append RSL-RL cli arguments cli_args.add_rsl_rl_args(parser) # append AppLauncher cli args @@ -119,7 +118,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg, agent_cfg: RslRlBaseRun # note: certain randomizations occur in the environment initialization so we set the seed here env_cfg.seed = agent_cfg.seed env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device - env_cfg.sim.enable_newton_rendering = args_cli.newton_visualizer # multi-gpu training configuration if args_cli.distributed: diff --git a/scripts/reinforcement_learning/sb3/play.py b/scripts/reinforcement_learning/sb3/play.py index 613ec46d7d8..f9497e6ad91 100644 --- a/scripts/reinforcement_learning/sb3/play.py +++ b/scripts/reinforcement_learning/sb3/play.py @@ -39,7 +39,6 @@ default=False, help="Use a slower SB3 wrapper but keep all the extra training info.", ) -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -83,11 +82,7 @@ def main(): """Play with stable-baselines agent.""" # parse configuration env_cfg = parse_env_cfg( - args_cli.task, - device=args_cli.device, - num_envs=args_cli.num_envs, - use_fabric=not args_cli.disable_fabric, - newton_visualizer=args_cli.newton_visualizer, + args_cli.task, device=args_cli.device, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric ) task_name = args_cli.task.split(":")[-1] diff --git a/scripts/reinforcement_learning/sb3/train.py b/scripts/reinforcement_learning/sb3/train.py index fa34f951ffe..e86be128439 100644 --- a/scripts/reinforcement_learning/sb3/train.py +++ b/scripts/reinforcement_learning/sb3/train.py @@ -32,7 +32,6 @@ default=False, help="Use a slower SB3 wrapper but keep all the extra training info.", ) -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments @@ -113,7 +112,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg, agent_cfg: dict): # note: certain randomizations occur in the environment initialization so we set the seed here env_cfg.seed = agent_cfg["seed"] env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device - env_cfg.sim.enable_newton_rendering = args_cli.newton_visualizer # directory for logging into run_info = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") diff --git a/scripts/reinforcement_learning/skrl/play.py b/scripts/reinforcement_learning/skrl/play.py index 252b3b3bc91..b1a7202eb3d 100644 --- a/scripts/reinforcement_learning/skrl/play.py +++ b/scripts/reinforcement_learning/skrl/play.py @@ -46,7 +46,6 @@ help="The RL algorithm used for training the skrl agent.", ) parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) @@ -112,11 +111,7 @@ def main(): # parse configuration env_cfg = parse_env_cfg( - args_cli.task, - device=args_cli.device, - num_envs=args_cli.num_envs, - use_fabric=not args_cli.disable_fabric, - newton_visualizer=args_cli.newton_visualizer, + args_cli.task, device=args_cli.device, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric ) try: experiment_cfg = load_cfg_from_registry(task_name, f"skrl_{algorithm}_cfg_entry_point") diff --git a/scripts/reinforcement_learning/skrl/train.py b/scripts/reinforcement_learning/skrl/train.py index d59b2cc45d7..ff01055e344 100644 --- a/scripts/reinforcement_learning/skrl/train.py +++ b/scripts/reinforcement_learning/skrl/train.py @@ -44,7 +44,6 @@ choices=["AMP", "PPO", "IPPO", "MAPPO"], help="The RL algorithm used for training the skrl agent.", ) -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) @@ -113,7 +112,6 @@ def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg, agent_cfg: dict): # override configurations with non-hydra CLI arguments env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device - env_cfg.sim.enable_newton_rendering = args_cli.newton_visualizer # multi-gpu training config if args_cli.distributed: diff --git a/scripts/sim2sim_transfer/rsl_rl_transfer.py b/scripts/sim2sim_transfer/rsl_rl_transfer.py index d9b546ffd6f..4d6a7ae65c2 100644 --- a/scripts/sim2sim_transfer/rsl_rl_transfer.py +++ b/scripts/sim2sim_transfer/rsl_rl_transfer.py @@ -29,7 +29,6 @@ help="Use the pre-trained checkpoint from Nucleus.", ) parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.") -parser.add_argument("--newton_visualizer", action="store_true", default=False, help="Enable Newton rendering.") # Joint ordering arguments parser.add_argument( "--policy_transfer_file", @@ -147,8 +146,8 @@ def main(): device=args_cli.device, num_envs=args_cli.num_envs, use_fabric=not args_cli.disable_fabric, - newton_visualizer=args_cli.newton_visualizer, ) + agent_cfg: RslRlOnPolicyRunnerCfg = cli_args.parse_rsl_rl_cfg(task_name, args_cli) # specify directory for logging experiments diff --git a/source/isaaclab/isaaclab/envs/ui/base_env_window.py b/source/isaaclab/isaaclab/envs/ui/base_env_window.py index 6d10ab42c26..a122513e3ce 100644 --- a/source/isaaclab/isaaclab/envs/ui/base_env_window.py +++ b/source/isaaclab/isaaclab/envs/ui/base_env_window.py @@ -66,6 +66,9 @@ def __init__(self, env: ManagerBasedEnv, window_name: str = "IsaacLab"): # Listeners for environment selection changes self._ui_listeners: list[ManagerLiveVisualizer] = [] + # Check if any visualizer has live plots enabled + self._enable_live_plots = self._check_live_plots_enabled() + print("Creating window for environment.") # create window for UI self.ui_window = omni.ui.Window( @@ -102,6 +105,29 @@ def __del__(self): self.ui_window.destroy() self.ui_window = None + """ + Helper methods. + """ + + def _check_live_plots_enabled(self) -> bool: + """Check if any visualizer has live plots enabled. + + Returns: + True if any visualizer supports and has live plots enabled, False otherwise. + """ + # Check if simulation has visualizers + if not hasattr(self.env.sim, "_visualizers"): + return False + + # Check each visualizer + for visualizer in self.env.sim._visualizers: + # Check if visualizer supports live plots and has it enabled + if hasattr(visualizer, "cfg") and hasattr(visualizer.cfg, "enable_live_plots"): + if visualizer.supports_live_plots() and visualizer.cfg.enable_live_plots: + return True + + return False + """ Build sub-sections of the UI. """ @@ -421,6 +447,11 @@ def _create_debug_vis_ui_element(self, name: str, elem: object): is_checked = (hasattr(elem.cfg, "debug_vis") and elem.cfg.debug_vis) or ( hasattr(elem, "debug_vis") and elem.debug_vis ) + + # Auto-enable live plots for ManagerLiveVisualizer if visualizer has enable_live_plots=True + if isinstance(elem, ManagerLiveVisualizer) and self._enable_live_plots: + is_checked = True + self.ui_window_elements[f"{name}_cb"] = SimpleCheckBox( model=omni.ui.SimpleBoolModel(), enabled=elem.has_debug_vis_implementation, @@ -435,6 +466,9 @@ def _create_debug_vis_ui_element(self, name: str, elem: object): if not elem.set_vis_frame(self.ui_window_elements[f"{name}_panel"]): print(f"Frame failed to set for ManagerLiveVisualizer: {name}") + # Pass the enable_live_plots flag to the visualizer + elem._auto_expand_frames = self._enable_live_plots + # Add listener for environment selection changes if isinstance(elem, ManagerLiveVisualizer): self._ui_listeners.append(elem) diff --git a/source/isaaclab/isaaclab/sim/_impl/newton_manager.py b/source/isaaclab/isaaclab/sim/_impl/newton_manager.py index 248d8161901..6d7b42ccd2f 100644 --- a/source/isaaclab/isaaclab/sim/_impl/newton_manager.py +++ b/source/isaaclab/isaaclab/sim/_impl/newton_manager.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: BSD-3-Clause -import ctypes import numpy as np import re @@ -16,7 +15,6 @@ from newton.solvers import SolverBase, SolverFeatherstone, SolverMuJoCo, SolverXPBD from isaaclab.sim._impl.newton_manager_cfg import NewtonCfg -from isaaclab.sim._impl.newton_viewer import NewtonViewerGL from isaaclab.utils.timer import Timer @@ -35,18 +33,6 @@ def flipped_match(x: str, y: str) -> re.Match | None: return re.match(y, x) -@wp.kernel(enable_backward=False) -def set_vec3d_array( - fabric_vals: wp.fabricarray(dtype=wp.mat44d), - indices: wp.fabricarray(dtype=wp.uint32), - newton_vals: wp.array(ndim=1, dtype=wp.transformf), -): - i = int(wp.tid()) - idx = int(indices[i]) - new_val = newton_vals[idx] - fabric_vals[i] = wp.transpose(wp.mat44d(wp.math.transform_to_matrix(new_val))) - - class NewtonManager: _builder: ModelBuilder = None _model: Model = None @@ -66,7 +52,6 @@ class NewtonManager: _report_contacts: bool = False _graph = None _newton_stage_path = None - _renderer = None _sim_time = 0.0 _usdrt_stage = None _newton_index_attr = "newton:index" @@ -76,10 +61,6 @@ class NewtonManager: _gravity_vector: tuple[float, float, float] = (0.0, 0.0, -9.81) _up_axis: str = "Z" _num_envs: int = None - _visualizer_update_counter: int = 0 - _visualizer_update_frequency: int = 1 # Configurable frequency for all rendering updates - _visualizer_train_mode: bool = True # Whether visualizer is in training mode - _visualizer_disabled: bool = False # Whether visualizer has been disabled by user @classmethod def clear(cls): @@ -95,7 +76,6 @@ def clear(cls): NewtonManager._report_contacts = False NewtonManager._graph = None NewtonManager._newton_stage_path = None - NewtonManager._renderer = None NewtonManager._sim_time = 0.0 NewtonManager._on_init_callbacks = [] NewtonManager._on_start_callbacks = [] @@ -103,9 +83,6 @@ def clear(cls): NewtonManager._cfg = NewtonCfg() NewtonManager._up_axis = "Z" NewtonManager._first_call = True - NewtonManager._visualizer_update_counter = 0 - NewtonManager._visualizer_disabled = False - NewtonManager._visualizer_update_frequency = NewtonManager._cfg.newton_viewer_update_frequency @classmethod def set_builder(cls, builder): @@ -302,102 +279,6 @@ def set_simulation_dt(cls, dt: float) -> None: """ NewtonManager._dt = dt - @classmethod - def _render_call(cls, render_func) -> bool: - if NewtonManager._renderer is not None: - try: - if hasattr(NewtonManager._renderer, "renderer") and hasattr(NewtonManager._renderer.renderer, "window"): - if NewtonManager._renderer.renderer.window.has_exit: - NewtonManager._visualizer_disabled = True - NewtonManager._renderer = None - return False - except Exception as e: - print(f"[ERROR] Error in _render_call: {e}") - - try: - render_func() - return True - except (ctypes.ArgumentError, Exception) as e: - if "wrong type" in str(e) or "ArgumentError" in str(e): - NewtonManager._visualizer_disabled = True - if NewtonManager._renderer is not None: - try: - NewtonManager._renderer.close() - except Exception as e: - print(f"[ERROR] Error in _render_call: {e}") - NewtonManager._renderer = None - return False - else: - raise - - @classmethod - def render(cls) -> None: - if NewtonManager._visualizer_disabled: - return - - if NewtonManager._renderer is None: - NewtonManager._visualizer_train_mode = NewtonManager._cfg.visualizer_train_mode - NewtonManager._renderer = NewtonViewerGL( - width=1280, height=720, train_mode=NewtonManager._visualizer_train_mode - ) - NewtonManager._renderer.set_model(NewtonManager._model) - NewtonManager._renderer.camera.pos = wp.vec3(*NewtonManager._cfg.newton_viewer_camera_pos) - NewtonManager._renderer.up_axis = NewtonManager._up_axis - NewtonManager._renderer.scaling = 1.0 - NewtonManager._renderer._paused = False - else: - while NewtonManager._renderer is not None and NewtonManager._renderer.is_training_paused(): - - def render_frame(): - NewtonManager._renderer.begin_frame(NewtonManager._sim_time) - NewtonManager._renderer.log_state(NewtonManager._state_0) - NewtonManager._renderer.end_frame() - - if not NewtonManager._render_call(render_frame): - return - - NewtonManager._visualizer_update_counter += 1 - if ( - NewtonManager._renderer is not None - and NewtonManager._visualizer_update_counter >= NewtonManager._visualizer_update_frequency - ): - if not NewtonManager._renderer.is_paused(): - - def render_frame(): - NewtonManager._renderer.begin_frame(NewtonManager._sim_time) - NewtonManager._renderer.log_state(NewtonManager._state_0) - NewtonManager._renderer.end_frame() - - if not NewtonManager._render_call(render_frame): - return - else: - if not NewtonManager._render_call(lambda: NewtonManager._renderer._update()): - return - - NewtonManager._visualizer_update_counter = 0 - - @classmethod - def sync_fabric_transforms(cls) -> None: - """Syncs the fabric transforms with the Newton state. - - This function syncs the fabric transforms with the Newton state. - """ - selection = NewtonManager._usdrt_stage.SelectPrims( - require_attrs=[ - (usdrt.Sdf.ValueTypeNames.Matrix4d, "omni:fabric:worldMatrix", usdrt.Usd.Access.ReadWrite), - (usdrt.Sdf.ValueTypeNames.UInt, NewtonManager._newton_index_attr, usdrt.Usd.Access.Read), - ], - device="cuda:0", - ) - fabric_newton_indices = wp.fabricarray(selection, NewtonManager._newton_index_attr) - current_transforms = wp.fabricarray(selection, "omni:fabric:worldMatrix") - wp.launch( - set_vec3d_array, - dim=(fabric_newton_indices.shape[0]), - inputs=[current_transforms, fabric_newton_indices, NewtonManager._state_0.body_q], - device="cuda:0", - ) - @classmethod def get_model(cls): return NewtonManager._model diff --git a/source/isaaclab/isaaclab/sim/_impl/newton_manager_cfg.py b/source/isaaclab/isaaclab/sim/_impl/newton_manager_cfg.py index d827e28d645..c79a9036250 100644 --- a/source/isaaclab/isaaclab/sim/_impl/newton_manager_cfg.py +++ b/source/isaaclab/isaaclab/sim/_impl/newton_manager_cfg.py @@ -12,7 +12,7 @@ class NewtonCfg: """Configuration for Newton-related parameters. - These parameters are used to configure the Newton simulation. + These parameters are used to configure the Newton physics simulation. """ num_substeps: int = 1 @@ -27,13 +27,4 @@ class NewtonCfg: If set to False, the simulation performance will be severely degraded. """ - newton_viewer_update_frequency: int = 1 - """Frequency of updates to the Newton viewer.""" - - newton_viewer_camera_pos: tuple[float, float, float] = (10.0, 0.0, 3.0) - """Position of the camera in the Newton viewer.""" - - visualizer_train_mode: bool = True - """Whether the visualizer is in training mode (True) or play mode (False).""" - solver_cfg: NewtonSolverCfg = MJWarpSolverCfg() diff --git a/source/isaaclab/isaaclab/sim/_impl/newton_viewer.py b/source/isaaclab/isaaclab/sim/_impl/newton_viewer.py deleted file mode 100644 index 00f13dc9202..00000000000 --- a/source/isaaclab/isaaclab/sim/_impl/newton_viewer.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Training-aware viewer that adds a separate pause for simulation/training while keeping rendering at full rate. - -This class subclasses Newton's ViewerGL and introduces a second pause mode: -- Rendering pause: identical to the base viewer's pause (space key / Pause checkbox) -- Training pause: stops simulation/training steps but keeps rendering running - -The training pause can be toggled from the UI via a button and optionally via the 'T' key. -""" - -from __future__ import annotations - -import newton as nt -import warp as wp -from newton.viewer import ViewerGL - - -class NewtonViewerGL(ViewerGL): - def __init__(self, *args, train_mode: bool = True, **kwargs): - super().__init__(*args, **kwargs) - self._paused_training: bool = False - self._paused_rendering: bool = False - self._fallback_draw_controls: bool = False - self._is_train_mode: bool = train_mode # Convert train_mode to play_mode - - try: - self.register_ui_callback(self._render_training_controls, position="side") - except AttributeError: - self._fallback_draw_controls = True - - def is_training_paused(self) -> bool: - return self._paused_training - - def is_paused(self) -> bool: - return self._paused_rendering - - # UI callback rendered inside the "Example Options" panel of the left sidebar - def _render_training_controls(self, imgui): - imgui.separator() - - # Use simple flag to adjust labels - if self._is_train_mode: - imgui.text("IsaacLab Training Controls") - pause_label = "Resume Training" if self._paused_training else "Pause Training" - else: - imgui.text("IsaacLab Playback Controls") - pause_label = "Resume Playing" if self._paused_training else "Pause Playing" - - if imgui.button(pause_label): - self._paused_training = not self._paused_training - - # Only show rendering controls when in training mode - if self._is_train_mode: - rendering_label = "Resume Rendering" if self._paused_rendering else "Pause Rendering" - if imgui.button(rendering_label): - self._paused_rendering = not self._paused_rendering - - # Import NewtonManager locally to avoid circular imports - from .newton_manager import NewtonManager # noqa: PLC0415 - - imgui.text("Visualizer Update Frequency") - - current_frequency = NewtonManager._visualizer_update_frequency - changed, new_frequency = imgui.slider_int( - "##VisualizerUpdateFreq", current_frequency, 1, 20, f"Every {current_frequency} frames" - ) - if changed: - NewtonManager._visualizer_update_frequency = new_frequency - - if imgui.is_item_hovered(): - imgui.set_tooltip( - "Controls visualizer update frequency\nlower values-> more responsive visualizer but slower" - " training\nhigher values-> less responsive visualizer but faster training" - ) - - # Override only SPACE key to use rendering pause, preserve all other shortcuts - def on_key_press(self, symbol, modifiers): - if self.ui.is_capturing(): - return - - try: - import pyglet # noqa: PLC0415 - except Exception: - return - - if symbol == pyglet.window.key.SPACE: - # Override SPACE to pause rendering instead of base pause - self._paused_rendering = not self._paused_rendering - return - - # For all other keys, call base implementation to preserve functionality - super().on_key_press(symbol, modifiers) - - def _render_ui(self): - if not self._fallback_draw_controls: - return super()._render_ui() - - # Render base UI first - super()._render_ui() - - # Then render a small floating window with training controls - imgui = self.ui.imgui - # Place near left panel but offset - from contextlib import suppress - - with suppress(Exception): - imgui.set_next_window_pos(imgui.ImVec2(320, 10)) - - flags = 0 - if imgui.begin("Training Controls", flags=flags): - self._render_training_controls(imgui) - imgui.end() - return None - - def _render_left_panel(self): - """Override the left panel to remove the base pause checkbox.""" - imgui = self.ui.imgui - - # Use theme colors directly - nav_highlight_color = self.ui.get_theme_color(imgui.Col_.nav_cursor, (1.0, 1.0, 1.0, 1.0)) - - # Position the window on the left side - io = self.ui.io - imgui.set_next_window_pos(imgui.ImVec2(10, 10)) - imgui.set_next_window_size(imgui.ImVec2(300, io.display_size[1] - 20)) - - # Main control panel window - use safe flag values - flags = imgui.WindowFlags_.no_resize.value - - if imgui.begin(f"Newton Viewer v{nt.__version__}", flags=flags): - imgui.separator() - - header_flags = 0 - - imgui.set_next_item_open(True, imgui.Cond_.appearing) - if imgui.collapsing_header("IsaacLab Options"): - # Render UI callbacks for side panel - for callback in self._ui_callbacks["side"]: - callback(self.ui.imgui) - - # Model Information section - if self.model is not None: - imgui.set_next_item_open(True, imgui.Cond_.appearing) - if imgui.collapsing_header("Model Information", flags=header_flags): - imgui.separator() - imgui.text(f"Environments: {self.model.num_envs}") - axis_names = ["X", "Y", "Z"] - imgui.text(f"Up Axis: {axis_names[self.model.up_axis]}") - gravity = wp.to_torch(self.model.gravity)[0] - gravity_text = f"Gravity: ({gravity[0]:.2f}, {gravity[1]:.2f}, {gravity[2]:.2f})" - imgui.text(gravity_text) - - # Visualization Controls section - imgui.set_next_item_open(True, imgui.Cond_.appearing) - if imgui.collapsing_header("Visualization", flags=header_flags): - imgui.separator() - - # Joint visualization - show_joints = self.show_joints - changed, self.show_joints = imgui.checkbox("Show Joints", show_joints) - - # Contact visualization - show_contacts = self.show_contacts - changed, self.show_contacts = imgui.checkbox("Show Contacts", show_contacts) - - # Spring visualization - show_springs = self.show_springs - changed, self.show_springs = imgui.checkbox("Show Springs", show_springs) - - # Center of mass visualization - show_com = self.show_com - changed, self.show_com = imgui.checkbox("Show Center of Mass", show_com) - - # Rendering Options section - imgui.set_next_item_open(True, imgui.Cond_.appearing) - if imgui.collapsing_header("Rendering Options"): - imgui.separator() - - # Sky rendering - changed, self.renderer.draw_sky = imgui.checkbox("Sky", self.renderer.draw_sky) - - # Shadow rendering - changed, self.renderer.draw_shadows = imgui.checkbox("Shadows", self.renderer.draw_shadows) - - # Wireframe mode - changed, self.renderer.draw_wireframe = imgui.checkbox("Wireframe", self.renderer.draw_wireframe) - - # Light color - changed, self.renderer._light_color = imgui.color_edit3("Light Color", self.renderer._light_color) - # Sky color - changed, self.renderer.sky_upper = imgui.color_edit3("Sky Color", self.renderer.sky_upper) - # Ground color - changed, self.renderer.sky_lower = imgui.color_edit3("Ground Color", self.renderer.sky_lower) - - # Camera Information section - imgui.set_next_item_open(True, imgui.Cond_.appearing) - if imgui.collapsing_header("Camera"): - imgui.separator() - - pos = self.camera.pos - pos_text = f"Position: ({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f})" - imgui.text(pos_text) - imgui.text(f"FOV: {self.camera.fov:.1f}°") - imgui.text(f"Yaw: {self.camera.yaw:.1f}°") - imgui.text(f"Pitch: {self.camera.pitch:.1f}°") - - # Camera controls hint - update to reflect new controls - imgui.separator() - imgui.push_style_color(imgui.Col_.text, imgui.ImVec4(*nav_highlight_color)) - imgui.text("Controls:") - imgui.pop_style_color() - imgui.text("WASD - Move camera") - imgui.text("Left Click - Look around") - imgui.text("Right Click - Pick objects") - imgui.text("Scroll - Zoom") - imgui.text("Space - Pause/Resume Rendering") - imgui.text("H - Toggle UI") - imgui.text("ESC/Q - Exit") - - # NOTE: Removed selection API section for now. In the future, we can add single env control through this section. - # Selection API section - # self._render_selection_panel() - - imgui.end() - return diff --git a/source/isaaclab/isaaclab/sim/scene_data_provider.py b/source/isaaclab/isaaclab/sim/scene_data_provider.py new file mode 100644 index 00000000000..2114cd1766c --- /dev/null +++ b/source/isaaclab/isaaclab/sim/scene_data_provider.py @@ -0,0 +1,184 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Scene data provider for visualizers and renderers. + +This module provides a unified interface for accessing scene data from different physics backends +and synchronizing that data to rendering backends (USD Fabric, etc.). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from newton import Model, State + + +# Lazy import warp dependencies for OV visualizer support +def _get_warp_kernel(): + """Get the warp kernel for syncing fabric transforms. + + Returns the kernel only when warp is available, avoiding import errors + when warp is not installed. + """ + try: + import warp as wp + + # Define warp kernel for syncing transforms + @wp.kernel(enable_backward=False) + def set_vec3d_array( + fabric_vals: wp.fabricarray(dtype=wp.mat44d), + indices: wp.fabricarray(dtype=wp.uint32), + newton_vals: wp.array(ndim=1, dtype=wp.transformf), + ): + i = int(wp.tid()) + idx = int(indices[i]) + new_val = newton_vals[idx] + fabric_vals[i] = wp.transpose(wp.mat44d(wp.math.transform_to_matrix(new_val))) + + return set_vec3d_array + except ImportError: + return None + + +class SceneDataProvider: + """Unified scene data provider for all physics and rendering backends. + + This class provides: + - Access to physics state and model for visualizers (Newton OpenGL, Rerun) + - Synchronization of physics state to USD Fabric for OV visualizer + - Conditional updates based on active visualizers to avoid unnecessary work + + The provider handles both Newton-specific data (state, model) and OV-specific + synchronization (fabric transforms) within a single class to keep the abstraction + simple while supporting multiple backends. + """ + + def __init__(self, visualizer_cfgs: list[Any] | None = None): + """Initialize the scene data provider with visualizer configurations. + + Args: + visualizer_cfgs: List of visualizer configurations to determine which backends are active. + """ + self._has_newton_visualizer = False + self._has_rerun_visualizer = False + self._has_ov_visualizer = False + self._is_initialized = False + + # Determine which visualizers are enabled from configs + if visualizer_cfgs: + for cfg in visualizer_cfgs: + viz_type = getattr(cfg, "visualizer_type", None) + if viz_type == "newton": + self._has_newton_visualizer = True + elif viz_type == "rerun": + self._has_rerun_visualizer = True + elif viz_type == "omniverse": + self._has_ov_visualizer = True + + self._is_initialized = True + + def update(self) -> None: + """Update scene data for visualizers. + + This method: + - Syncs Newton transforms to USD Fabric (if OV visualizer is active) + - Can be extended for other backend-specific updates + + Note: + Only performs work if necessary visualizers are active to avoid overhead. + """ + if not self._is_initialized: + return + + # Sync fabric transforms only if OV visualizer is active + if self._has_ov_visualizer: + self._sync_fabric_transforms() + + def get_state(self) -> State | None: + """Get physics state for Newton-based visualizers. + + Returns: + Newton State object, or None if not available. + """ + # State is needed by Newton OpenGL and Rerun visualizers + if not (self._has_newton_visualizer or self._has_rerun_visualizer): + return None + + # Lazy import to avoid loading Newton if not needed + try: + from isaaclab.sim._impl.newton_manager import NewtonManager + + return NewtonManager._state_0 + except ImportError: + return None + + def get_model(self) -> Model | None: + """Get physics model for Newton-based visualizers. + + Returns: + Newton Model object, or None if not available. + """ + # Model is needed by Newton OpenGL and Rerun visualizers + if not (self._has_newton_visualizer or self._has_rerun_visualizer): + return None + + # Lazy import to avoid loading Newton if not needed + try: + from isaaclab.sim._impl.newton_manager import NewtonManager + + return NewtonManager._model + except ImportError: + return None + + def _sync_fabric_transforms(self) -> None: + """Sync Newton transforms to USD Fabric for OV visualizer. + + This method updates the USD Fabric with the latest physics transforms from Newton. + It uses a warp kernel to efficiently copy transform data on the GPU. + + The sync process: + 1. Selects USD prims that have both worldMatrix and newton index attributes + 2. Creates fabric arrays for transforms and indices + 3. Launches warp kernel to copy Newton body transforms to USD Fabric + + Note: + This is only called when OV visualizer is active to avoid unnecessary GPU work. + """ + # Get warp kernel (lazy loaded) + set_vec3d_array = _get_warp_kernel() + if set_vec3d_array is None: + return + + try: + import usdrt + import warp as wp + + from isaaclab.sim._impl.newton_manager import NewtonManager + + # Select all prims with required attributes + selection = NewtonManager._usdrt_stage.SelectPrims( + require_attrs=[ + (usdrt.Sdf.ValueTypeNames.Matrix4d, "omni:fabric:worldMatrix", usdrt.Usd.Access.ReadWrite), + (usdrt.Sdf.ValueTypeNames.UInt, NewtonManager._newton_index_attr, usdrt.Usd.Access.Read), + ], + device="cuda:0", + ) + + # Create fabric arrays for indices and transforms + fabric_newton_indices = wp.fabricarray(selection, NewtonManager._newton_index_attr) + current_transforms = wp.fabricarray(selection, "omni:fabric:worldMatrix") + + # Launch warp kernel to sync transforms + wp.launch( + set_vec3d_array, + dim=(fabric_newton_indices.shape[0]), + inputs=[current_transforms, fabric_newton_indices, NewtonManager._state_0.body_q], + device="cuda:0", + ) + except (ImportError, AttributeError): + # Silently fail if Newton isn't initialized or attributes are missing + pass diff --git a/source/isaaclab/isaaclab/sim/simulation_cfg.py b/source/isaaclab/isaaclab/sim/simulation_cfg.py index bea86d4150c..45dec51eee5 100644 --- a/source/isaaclab/isaaclab/sim/simulation_cfg.py +++ b/source/isaaclab/isaaclab/sim/simulation_cfg.py @@ -12,6 +12,7 @@ from typing import Literal from isaaclab.utils import configclass +from isaaclab.visualizers import NewtonVisualizerCfg, OVVisualizerCfg, RerunVisualizerCfg, VisualizerCfg from ._impl.newton_manager_cfg import NewtonCfg from .spawners.materials import RigidBodyMaterialCfg @@ -193,14 +194,41 @@ class SimulationCfg: The material is created at the path: ``{physics_prim_path}/defaultMaterial``. """ - render: RenderCfg = RenderCfg() + render_cfg: RenderCfg = RenderCfg() """Render settings. Default is RenderCfg().""" - enable_newton_rendering: bool = False - """Enable/disable rendering using Newton. Default is False. + visualizer_cfgs: list[VisualizerCfg] | VisualizerCfg | None = None + """Visualizer settings. Default is no visualizer. - When enabled, the Newton to renderer will be called every time the simulation is rendered. If Isaac Sim's - renderer is also enabled, both will be called. + Visualizers are separate from Renderers and intended for light-weight monitoring and debugging. + + This field can support multiple visualizer backends. It accepts: + - A single VisualizerCfg: One visualizer will be created + - A list of VisualizerCfg: Multiple visualizers will be created + - None or empty list: No visualizers will be created + + Supported visualizer backends: + - NewtonVisualizerCfg: Lightweight OpenGL-based visualizer + - OVVisualizerCfg: Omniverse-based high-fidelity visualizer + - RerunVisualizerCfg: Web-based Rerun visualizer with recording and replay + + Examples: + # Disable all visualizers + cfg.sim.visualizer_cfgs = [] + + # Use default visualizer (NewtonVisualizerCfg) + cfg = SimulationCfg() + + # Single custom visualizer + from isaaclab.visualizers import OVVisualizerCfg + cfg = SimulationCfg(visualizer_cfgs=OVVisualizerCfg()) + + # Multiple visualizers with custom configuration + from isaaclab.visualizers import NewtonVisualizerCfg, RerunVisualizerCfg + cfg = SimulationCfg(visualizer_cfgs=[ + NewtonVisualizerCfg(camera_position=(10.0, 0.0, 3.0)), + RerunVisualizerCfg(server_address="127.0.0.1:9876") + ]) """ create_stage_in_memory: bool = False diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 33c93873c1c..d716e482eea 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -31,7 +31,9 @@ from isaaclab.sim._impl.newton_manager import NewtonManager from isaaclab.sim.utils import create_new_stage_in_memory, use_stage +from isaaclab.visualizers import Visualizer +from .scene_data_provider import SceneDataProvider from .simulation_cfg import SimulationCfg from .spawners import DomeLightCfg, GroundPlaneCfg from .utils import bind_physics_material @@ -252,6 +254,10 @@ def __init__(self, cfg: SimulationCfg | None = None): self._app_control_on_stop_handle = None self._disable_app_control_on_stop_handle = False + # initialize visualizers and scene data provider + self._visualizers: list[Visualizer] = [] + self._visualizer_step_counter = 0 + self._scene_data_provider: SceneDataProvider | None = None # flag for skipping prim deletion callback # when stage in memory is attached self._skip_next_prim_deletion_callback_fn = False @@ -322,7 +328,7 @@ def _apply_render_settings_from_cfg(self): not_carb_settings = ["rendering_mode", "carb_settings", "antialiasing_mode"] # set preset settings (same behavior as the CLI arg --rendering_mode) - rendering_mode = self.cfg.render.rendering_mode + rendering_mode = self.cfg.render_cfg.rendering_mode if rendering_mode is not None: # check if preset is supported supported_rendering_modes = ["performance", "balanced", "quality"] @@ -344,7 +350,7 @@ def _apply_render_settings_from_cfg(self): set_carb_setting(self.carb_settings, key, value) # set user-friendly named settings - for key, value in vars(self.cfg.render).items(): + for key, value in vars(self.cfg.render_cfg).items(): if value is None or key in not_carb_settings: # skip unset settings and non-carb settings continue @@ -357,7 +363,7 @@ def _apply_render_settings_from_cfg(self): set_carb_setting(self.carb_settings, key, value) # set general carb settings - carb_settings = self.cfg.render.carb_settings + carb_settings = self.cfg.render_cfg.carb_settings if carb_settings is not None: for key, value in carb_settings.items(): if "_" in key: @@ -369,11 +375,11 @@ def _apply_render_settings_from_cfg(self): set_carb_setting(self.carb_settings, key, value) # set denoiser mode - if self.cfg.render.antialiasing_mode is not None: + if self.cfg.render_cfg.antialiasing_mode is not None: try: import omni.replicator.core as rep - rep.settings.set_render_rtx_realtime(antialiasing=self.cfg.render.antialiasing_mode) + rep.settings.set_render_rtx_realtime(antialiasing=self.cfg.render_cfg.antialiasing_mode) except Exception: pass @@ -534,9 +540,124 @@ def get_setting(self, name: str) -> Any: return self._settings.get(name) def forward(self) -> None: - """Updates articulation kinematics and fabric for rendering.""" + """Updates articulation kinematics and scene data for rendering.""" NewtonManager.forward_kinematics() - NewtonManager.sync_fabric_transforms() + # Update scene data provider (syncs fabric transforms if needed) + if self._scene_data_provider: + self._scene_data_provider.update() + + def initialize_visualizers(self) -> None: + """Initialize all configured visualizers. + + This method creates and initializes visualizers based on the configuration provided + in SimulationCfg.visualizer_cfgs. It supports: + - A single VisualizerCfg: Creates one visualizer + - A list of VisualizerCfg: Creates multiple visualizers + - None or empty list: No visualizers are created + + Note: + Visualizers are automatically skipped when running in headless mode. + """ + # Skip visualizers in headless mode + if not self._has_gui and not self._offscreen_render: + return + + # Handle different input formats + visualizer_cfgs = [] + if self.cfg.visualizer_cfgs is not None: + if isinstance(self.cfg.visualizer_cfgs, list): + visualizer_cfgs = self.cfg.visualizer_cfgs + else: + visualizer_cfgs = [self.cfg.visualizer_cfgs] + + # Create scene data provider with visualizer configs + # Provider will determine which backends are active + if visualizer_cfgs: + self._scene_data_provider = SceneDataProvider(visualizer_cfgs) + + # Create and initialize each visualizer + for viz_cfg in visualizer_cfgs: + try: + visualizer = viz_cfg.create_visualizer() + + # Build scene data dict with only what this visualizer needs + scene_data = {} + + # Newton and Rerun visualizers only need scene_data_provider + if viz_cfg.visualizer_type in ("newton", "rerun"): + scene_data["scene_data_provider"] = self._scene_data_provider + + # OV visualizer needs USD stage and simulation context + elif viz_cfg.visualizer_type == "omniverse": + scene_data["usd_stage"] = self.stage + scene_data["simulation_context"] = self + + # Initialize visualizer with minimal required data + visualizer.initialize(scene_data) + self._visualizers.append(visualizer) + omni.log.info(f"Initialized visualizer: {type(visualizer).__name__} (type: {viz_cfg.visualizer_type})") + + except Exception as e: + omni.log.error( + f"Failed to initialize visualizer '{viz_cfg.visualizer_type}' ({type(viz_cfg).__name__}): {e}" + ) + + def step_visualizers(self, dt: float) -> None: + """Update all active visualizers. + + This method steps all initialized visualizers and updates their state. + It also handles visualizer pause states and removes closed visualizers. + + Args: + dt: Time step in seconds. + """ + if not self._visualizers: + return + + self._visualizer_step_counter += 1 + + # Update visualizers and check if any should be removed + visualizers_to_remove = [] + + for visualizer in self._visualizers: + try: + # Check if visualizer is still running + if not visualizer.is_running(): + visualizers_to_remove.append(visualizer) + continue + + # Handle training pause - block until resumed + while visualizer.is_training_paused() and visualizer.is_running(): + # Visualizers fetch backend-specific state themselves + visualizer.step(0.0, state=None) + + # Always call step to process events, even if rendering is paused + # The visualizer's step() method handles pause state internally + visualizer.step(dt, state=None) + + except Exception as e: + omni.log.error(f"Error stepping visualizer '{type(visualizer).__name__}': {e}") + visualizers_to_remove.append(visualizer) + + # Remove closed visualizers + for visualizer in visualizers_to_remove: + try: + visualizer.close() + self._visualizers.remove(visualizer) + omni.log.info(f"Removed visualizer: {type(visualizer).__name__}") + except Exception as e: + omni.log.error(f"Error closing visualizer: {e}") + + def close_visualizers(self) -> None: + """Close all active visualizers and clean up resources.""" + for visualizer in self._visualizers: + try: + visualizer.close() + except Exception as e: + omni.log.error(f"Error closing visualizer '{type(visualizer).__name__}': {e}") + + self._visualizers.clear() + omni.log.info("All visualizers closed") def get_initial_stage(self) -> Usd.Stage: """Returns stage handle used during scene creation. @@ -577,6 +698,11 @@ def reset(self, soft: bool = False): if not soft: for _ in range(2): self.render() + + # Initialize visualizers after simulation is set up (only on first reset) + if not soft and not self._visualizers: + self.initialize_visualizers() + self._disable_app_control_on_stop_handle = False def step(self, render: bool = True): @@ -628,9 +754,8 @@ def step(self, render: bool = True): if self.is_playing(): NewtonManager.step() - # Use the NewtonManager to render the scene if enabled - if self.cfg.enable_newton_rendering: - NewtonManager.render() + # Update visualizers + self.step_visualizers(self.cfg.dt) # app.update() may be changing the cuda device in step, so we force it back to our desired device here if "cuda" in self.device: @@ -728,6 +853,9 @@ def clear_instance(cls): if cls._instance._app_control_on_stop_handle is not None: cls._instance._app_control_on_stop_handle.unsubscribe() cls._instance._app_control_on_stop_handle = None + # close all visualizers + if hasattr(cls._instance, "_visualizers"): + cls._instance.close_visualizers() # call parent to clear the instance super().clear_instance() NewtonManager.clear() diff --git a/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py b/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py index a7cc31c070b..9070e9a4990 100644 --- a/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py +++ b/source/isaaclab/isaaclab/ui/widgets/manager_live_visualizer.py @@ -68,6 +68,7 @@ def __init__(self, manager: ManagerBase, cfg: ManagerLiveVisualizerCfg = Manager self._viewer_env_idx = 0 self._vis_frame: omni.ui.Frame self._vis_window: omni.ui.Window + self._auto_expand_frames: bool = False # Set by BaseEnvWindow if visualizer has enable_live_plots=True # evaluate chosen terms if no terms provided use all available. self.term_names = [] @@ -231,7 +232,8 @@ def _set_debug_vis_impl(self, debug_vis: bool): f"ManagerLiveVisualizer: Term ({name}) is not a supported data type for" " visualization." ) - frame.collapsed = True + # Keep frames expanded if auto-expand is enabled (when visualizer has enable_live_plots=True) + frame.collapsed = not self._auto_expand_frames self._debug_vis = debug_vis diff --git a/source/isaaclab/isaaclab/visualizers/__init__.py b/source/isaaclab/isaaclab/visualizers/__init__.py new file mode 100644 index 00000000000..58f02399a78 --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/__init__.py @@ -0,0 +1,104 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Sub-package for visualizer configurations and implementations. + +This sub-package contains configuration classes and implementations for different +visualizer backends that can be used with Isaac Lab. The visualizers are used for +debug visualization and monitoring of the simulation, separate from rendering for sensors. + +Supported visualizers: +- Newton OpenGL Visualizer: Lightweight OpenGL-based visualizer +- Omniverse Visualizer: High-fidelity Omniverse-based visualizer using Isaac Sim viewport +- Rerun Visualizer: Web-based Rerun visualizer with recording and timeline scrubbing + +Visualizer Registry +------------------- +This module uses a registry pattern to decouple visualizer instantiation from specific types. +Visualizer implementations can register themselves using the `register_visualizer` decorator, +and configs can create visualizers via the `create_visualizer()` factory method. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +# Import config classes (no circular dependency) +from .newton_visualizer_cfg import NewtonVisualizerCfg +from .ov_visualizer_cfg import OVVisualizerCfg +from .rerun_visualizer_cfg import RerunVisualizerCfg + +# Import base classes first +from .visualizer import Visualizer +from .visualizer_cfg import VisualizerCfg + +if TYPE_CHECKING: + from typing import Type + + from .newton_visualizer import NewtonVisualizer + from .ov_visualizer import OVVisualizer + from .rerun_visualizer import RerunVisualizer + +# Global registry for visualizer types (lazy-loaded) +_VISUALIZER_REGISTRY: dict[str, Any] = {} + +__all__ = [ + "Visualizer", + "VisualizerCfg", + "NewtonVisualizerCfg", + "OVVisualizerCfg", + "RerunVisualizerCfg", + "get_visualizer_class", +] + + +# Register only selected visualizers to reduce unnecessary imports +def get_visualizer_class(name: str) -> type[Visualizer] | None: + """Get a visualizer class by name (lazy-loaded). + + Visualizer classes are imported only when requested to avoid loading + unnecessary dependencies. + + Args: + name: Visualizer type name (e.g., 'newton', 'rerun', 'omniverse', 'ov'). + + Returns: + Visualizer class if found, None otherwise. + + Example: + >>> visualizer_cls = get_visualizer_class('newton') + >>> if visualizer_cls: + >>> visualizer = visualizer_cls(cfg) + """ + # Check if already loaded + if name in _VISUALIZER_REGISTRY: + return _VISUALIZER_REGISTRY[name] + + # Lazy-load visualizer on first access + try: + if name == "newton": + from .newton_visualizer import NewtonVisualizer + + _VISUALIZER_REGISTRY["newton"] = NewtonVisualizer + return NewtonVisualizer + elif name in ("omniverse", "ov"): + from .ov_visualizer import OVVisualizer + + _VISUALIZER_REGISTRY["omniverse"] = OVVisualizer + _VISUALIZER_REGISTRY["ov"] = OVVisualizer # Alias + return OVVisualizer + elif name == "rerun": + from .rerun_visualizer import RerunVisualizer + + _VISUALIZER_REGISTRY["rerun"] = RerunVisualizer + return RerunVisualizer + else: + return None + except ImportError as e: + # Log import error but don't crash - visualizer just won't be available + import warnings + + warnings.warn(f"Failed to load visualizer '{name}': {e}", ImportWarning) + return None diff --git a/source/isaaclab/isaaclab/visualizers/newton_visualizer.py b/source/isaaclab/isaaclab/visualizers/newton_visualizer.py new file mode 100644 index 00000000000..84276d61cf0 --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/newton_visualizer.py @@ -0,0 +1,377 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Newton OpenGL Visualizer implementation.""" + +from __future__ import annotations + +import contextlib +from typing import Any + +import warp as wp +from newton.viewer import ViewerGL + +from .newton_visualizer_cfg import NewtonVisualizerCfg +from .visualizer import Visualizer + + +class NewtonViewerGL(ViewerGL): + """Wrapper around Newton's ViewerGL with training/rendering pause controls. + + Adds two pause modes: + - Training pause: Stops physics simulation, continues rendering + - Rendering pause: Stops rendering updates, continues physics (SPACE key) + """ + + def __init__(self, *args, metadata: dict | None = None, update_frequency: int = 1, **kwargs): + super().__init__(*args, **kwargs) + self._paused_training = False + self._paused_rendering = False + self._metadata = metadata or {} + self._fallback_draw_controls = False + self._update_frequency = update_frequency + + try: + self.register_ui_callback(self._render_training_controls, position="side") + except AttributeError: + self._fallback_draw_controls = True + + def is_training_paused(self) -> bool: + return self._paused_training + + def is_rendering_paused(self) -> bool: + return self._paused_rendering + + def _render_training_controls(self, imgui): + imgui.separator() + imgui.text("IsaacLab Controls") + + # Pause training/simulation button + pause_label = "Resume Training" if self._paused_training else "Pause Training" + if imgui.button(pause_label): + self._paused_training = not self._paused_training + + # Pause rendering button + rendering_label = "Resume Rendering" if self._paused_rendering else "Pause Rendering" + if imgui.button(rendering_label): + self._paused_rendering = not self._paused_rendering + self._paused = self._paused_rendering # Sync with parent class pause state + + # Visualizer update frequency control + imgui.text("Visualizer Update Frequency") + current_frequency = self._update_frequency + changed, new_frequency = imgui.slider_int( + "##VisualizerUpdateFreq", current_frequency, 1, 20, f"Every {current_frequency} frames" + ) + if changed: + self._update_frequency = new_frequency + + if imgui.is_item_hovered(): + imgui.set_tooltip( + "Controls visualizer update frequency\nlower values -> more responsive visualizer but slower" + " training\nhigher values -> less responsive visualizer but faster training" + ) + + def on_key_press(self, symbol, modifiers): + if self.ui.is_capturing(): + return + + try: + import pyglet # noqa: PLC0415 + except Exception: + return + + if symbol == pyglet.window.key.SPACE: + self._paused_rendering = not self._paused_rendering + self._paused = self._paused_rendering # Sync with parent class pause state + return + + super().on_key_press(symbol, modifiers) + + def _render_ui(self): + if not self._fallback_draw_controls: + return super()._render_ui() + + # Render base UI first + super()._render_ui() + + # Then render a small floating window with training controls + imgui = self.ui.imgui + # Place near left panel but offset + from contextlib import suppress + + with suppress(Exception): + imgui.set_next_window_pos(imgui.ImVec2(320, 10)) + + flags = 0 + if imgui.begin("Training Controls", flags=flags): + self._render_training_controls(imgui) + imgui.end() + return None + + def _render_left_panel(self): + """Override the left panel to remove the base pause checkbox.""" + import newton as nt + + imgui = self.ui.imgui + + # Use theme colors directly + nav_highlight_color = self.ui.get_theme_color(imgui.Col_.nav_cursor, (1.0, 1.0, 1.0, 1.0)) + + # Position the window on the left side + io = self.ui.io + imgui.set_next_window_pos(imgui.ImVec2(10, 10)) + imgui.set_next_window_size(imgui.ImVec2(300, io.display_size[1] - 20)) + + # Main control panel window - use safe flag values + flags = imgui.WindowFlags_.no_resize.value + + if imgui.begin(f"Newton Viewer v{nt.__version__}", flags=flags): + imgui.separator() + + header_flags = 0 + + imgui.set_next_item_open(True, imgui.Cond_.appearing) + if imgui.collapsing_header("IsaacLab Options"): + # Render UI callbacks for side panel + for callback in self._ui_callbacks["side"]: + callback(self.ui.imgui) + + # Model Information section + if self.model is not None: + imgui.set_next_item_open(True, imgui.Cond_.appearing) + if imgui.collapsing_header("Model Information", flags=header_flags): + imgui.separator() + num_envs = self._metadata.get("num_envs", 0) + imgui.text(f"Environments: {num_envs}") + axis_names = ["X", "Y", "Z"] + imgui.text(f"Up Axis: {axis_names[self.model.up_axis]}") + gravity = wp.to_torch(self.model.gravity)[0] + gravity_text = f"Gravity: ({gravity[0]:.2f}, {gravity[1]:.2f}, {gravity[2]:.2f})" + imgui.text(gravity_text) + + # Visualization Controls section + imgui.set_next_item_open(True, imgui.Cond_.appearing) + if imgui.collapsing_header("Visualization", flags=header_flags): + imgui.separator() + + # Joint visualization + show_joints = self.show_joints + changed, self.show_joints = imgui.checkbox("Show Joints", show_joints) + + # Contact visualization + show_contacts = self.show_contacts + changed, self.show_contacts = imgui.checkbox("Show Contacts", show_contacts) + + # Spring visualization + show_springs = self.show_springs + changed, self.show_springs = imgui.checkbox("Show Springs", show_springs) + + # Center of mass visualization + show_com = self.show_com + changed, self.show_com = imgui.checkbox("Show Center of Mass", show_com) + + # Rendering Options section + imgui.set_next_item_open(True, imgui.Cond_.appearing) + if imgui.collapsing_header("Rendering Options"): + imgui.separator() + + # Sky rendering + changed, self.renderer.draw_sky = imgui.checkbox("Sky", self.renderer.draw_sky) + + # Shadow rendering + changed, self.renderer.draw_shadows = imgui.checkbox("Shadows", self.renderer.draw_shadows) + + # Wireframe mode + changed, self.renderer.draw_wireframe = imgui.checkbox("Wireframe", self.renderer.draw_wireframe) + + # Light color + changed, self.renderer._light_color = imgui.color_edit3("Light Color", self.renderer._light_color) + # Sky color + changed, self.renderer.sky_upper = imgui.color_edit3("Sky Color", self.renderer.sky_upper) + # Ground color + changed, self.renderer.sky_lower = imgui.color_edit3("Ground Color", self.renderer.sky_lower) + + # Camera Information section + imgui.set_next_item_open(True, imgui.Cond_.appearing) + if imgui.collapsing_header("Camera"): + imgui.separator() + + pos = self.camera.pos + pos_text = f"Position: ({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f})" + imgui.text(pos_text) + imgui.text(f"FOV: {self.camera.fov:.1f}°") + imgui.text(f"Yaw: {self.camera.yaw:.1f}°") + imgui.text(f"Pitch: {self.camera.pitch:.1f}°") + + # Camera controls hint - update to reflect new controls + imgui.separator() + imgui.push_style_color(imgui.Col_.text, imgui.ImVec4(*nav_highlight_color)) + imgui.text("Controls:") + imgui.pop_style_color() + imgui.text("WASD - Move camera") + imgui.text("Left Click - Look around") + imgui.text("Right Click - Pick objects") + imgui.text("Scroll - Zoom") + imgui.text("Space - Pause/Resume Rendering") + imgui.text("H - Toggle UI") + imgui.text("ESC/Q - Exit") + + # NOTE: Removed selection API section for now. In the future, we can add single env control through this section. + # Selection API section + # self._render_selection_panel() + + imgui.end() + return + + +class NewtonVisualizer(Visualizer): + """Newton OpenGL visualizer for Isaac Lab. + + Lightweight OpenGL-based visualization with training/rendering pause controls. + """ + + def __init__(self, cfg: NewtonVisualizerCfg): + super().__init__(cfg) + self.cfg: NewtonVisualizerCfg = cfg + self._viewer: NewtonViewerGL | None = None + self._sim_time = 0.0 + self._step_counter = 0 + self._model = None + self._state = None + self._update_frequency = cfg.update_frequency + self._scene_data_provider = None + + def initialize(self, scene_data: dict[str, Any] | None = None) -> None: + """Initialize visualizer with scene data.""" + if self._is_initialized: + return + + # Import NewtonManager for metadata access + from isaaclab.sim._impl.newton_manager import NewtonManager + + # Store scene data provider for accessing physics state + if scene_data and "scene_data_provider" in scene_data: + self._scene_data_provider = scene_data["scene_data_provider"] + + # Get Newton-specific data from scene data provider + if self._scene_data_provider: + self._model = self._scene_data_provider.get_model() + self._state = self._scene_data_provider.get_state() + else: + # Fallback: direct access to NewtonManager (for backward compatibility) + self._model = NewtonManager._model + self._state = NewtonManager._state_0 + + if self._model is None: + raise RuntimeError("Newton visualizer requires Newton Model. Ensure Newton physics is initialized first.") + + # Build metadata from NewtonManager + metadata = { + "physics_backend": "newton", + "num_envs": NewtonManager._num_envs if NewtonManager._num_envs is not None else 0, + "gravity_vector": NewtonManager._gravity_vector, + "clone_physics_only": NewtonManager._clone_physics_only, + } + + # Create the viewer with metadata + self._viewer = NewtonViewerGL( + width=self.cfg.window_width, + height=self.cfg.window_height, + metadata=metadata, + update_frequency=self.cfg.update_frequency, + ) + + # Set the model + self._viewer.set_model(self._model) + + # Configure camera + self._viewer.camera.pos = wp.vec3(*self.cfg.camera_position) + self._viewer.up_axis = ["X", "Y", "Z"].index(self.cfg.up_axis) + self._viewer.scaling = 1.0 + self._viewer._paused = False + + # Configure visualization options + self._viewer.show_joints = self.cfg.show_joints + self._viewer.show_contacts = self.cfg.show_contacts + self._viewer.show_springs = self.cfg.show_springs + self._viewer.show_com = self.cfg.show_com + + # Configure rendering options + self._viewer.renderer.draw_shadows = self.cfg.enable_shadows + self._viewer.renderer.draw_sky = self.cfg.enable_sky + self._viewer.renderer.draw_wireframe = self.cfg.enable_wireframe + + # Configure colors + self._viewer.renderer.sky_upper = self.cfg.background_color + self._viewer.renderer.sky_lower = self.cfg.ground_color + self._viewer.renderer._light_color = self.cfg.light_color + + self._is_initialized = True + + def step(self, dt: float, state: Any | None = None) -> None: + """Update visualizer for one step.""" + if not self._is_initialized or self._is_closed or self._viewer is None: + return + + self._sim_time += dt + self._step_counter += 1 + + # Fetch updated state from scene data provider + if self._scene_data_provider: + self._state = self._scene_data_provider.get_state() + else: + # Fallback: direct access to NewtonManager + from isaaclab.sim._impl.newton_manager import NewtonManager + + self._state = NewtonManager._state_0 + + # Only update visualizer at the specified frequency + update_frequency = self._viewer._update_frequency if self._viewer else self._update_frequency + if self._step_counter % update_frequency != 0: + return + + with contextlib.suppress(Exception): + if not self._viewer.is_paused(): + self._viewer.begin_frame(self._sim_time) + if self._state is not None: + self._viewer.log_state(self._state) + self._viewer.end_frame() + else: + self._viewer._update() + + def close(self) -> None: + """Close visualizer and clean up resources.""" + if self._is_closed: + return + if self._viewer is not None: + self._viewer = None + self._is_closed = True + + def is_running(self) -> bool: + """Check if visualizer window is still open.""" + if not self._is_initialized or self._is_closed or self._viewer is None: + return False + return self._viewer.is_running() + + def supports_markers(self) -> bool: + """Newton visualizer does not have this feature yet.""" + return False + + def supports_live_plots(self) -> bool: + """Newton visualizer does not have this feature yet.""" + return False + + def is_training_paused(self) -> bool: + """Check if training is paused.""" + if not self._is_initialized or self._viewer is None: + return False + return self._viewer.is_training_paused() + + def is_rendering_paused(self) -> bool: + """Check if rendering is paused.""" + if not self._is_initialized or self._viewer is None: + return False + return self._viewer.is_rendering_paused() diff --git a/source/isaaclab/isaaclab/visualizers/newton_visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/newton_visualizer_cfg.py new file mode 100644 index 00000000000..a6533f4ba19 --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/newton_visualizer_cfg.py @@ -0,0 +1,74 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configuration for Newton OpenGL Visualizer.""" + +from typing import Literal + +from isaaclab.utils import configclass + +from .visualizer_cfg import VisualizerCfg + + +@configclass +class NewtonVisualizerCfg(VisualizerCfg): + """Configuration for Newton OpenGL visualizer. + + Lightweight OpenGL-based visualizer with real-time 3D rendering, interactive + camera controls, and debug visualization (contacts, joints, springs, COM). + + Requires: pyglet >= 2.1.6, imgui_bundle >= 1.92.0 + """ + + visualizer_type: str = "newton" + """Type identifier for Newton visualizer.""" + + window_width: int = 1920 + """Window width in pixels.""" + + window_height: int = 1080 + """Window height in pixels.""" + + camera_position: tuple[float, float, float] = (10.0, 0.0, 3.0) + """Initial camera position (x, y, z).""" + + camera_target: tuple[float, float, float] = (0.0, 0.0, 0.0) + """Initial camera target/look-at point (x, y, z).""" + + update_frequency: int = 1 + """Visualizer update frequency (updates every N frames). Lower = more responsive but slower training.""" + + up_axis: Literal["X", "Y", "Z"] = "Z" + """World up axis.""" + + show_joints: bool = False + """Show joint visualization.""" + + show_contacts: bool = False + """Show contact visualization.""" + + show_springs: bool = False + """Show spring visualization.""" + + show_com: bool = False + """Show center of mass visualization.""" + + enable_shadows: bool = True + """Enable shadow rendering.""" + + enable_sky: bool = True + """Enable sky rendering.""" + + enable_wireframe: bool = False + """Enable wireframe rendering.""" + + background_color: tuple[float, float, float] = (0.53, 0.81, 0.92) + """Background/sky color RGB [0,1].""" + + ground_color: tuple[float, float, float] = (0.18, 0.20, 0.25) + """Ground color RGB [0,1].""" + + light_color: tuple[float, float, float] = (1.0, 1.0, 1.0) + """Light color RGB [0,1].""" diff --git a/source/isaaclab/isaaclab/visualizers/ov_visualizer.py b/source/isaaclab/isaaclab/visualizers/ov_visualizer.py new file mode 100644 index 00000000000..e2c5da0cc4e --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/ov_visualizer.py @@ -0,0 +1,363 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# Copyright (c) 2022-2025, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Omniverse-based visualizer using Isaac Sim viewport.""" + +from __future__ import annotations + +import asyncio +from typing import Any + +import omni.log +from pxr import UsdGeom + +from .ov_visualizer_cfg import OVVisualizerCfg +from .visualizer import Visualizer + + +class OVVisualizer(Visualizer): + """Omniverse visualizer using Isaac Sim viewport. + + Renders USD stage with VisualizationMarkers and LivePlots. + Can attach to existing app or launch standalone. + """ + + def __init__(self, cfg: OVVisualizerCfg): + super().__init__(cfg) + self.cfg: OVVisualizerCfg = cfg + + self._simulation_app = None + self._viewport_window = None + self._viewport_api = None + self._is_initialized = False + self._sim_time = 0.0 + self._step_counter = 0 + + def initialize(self, scene_data: dict[str, Any] | None = None) -> None: + """Initialize OV visualizer.""" + if self._is_initialized: + omni.log.warn("[OVVisualizer] Already initialized.") + return + + usd_stage = None + simulation_context = None + if scene_data is not None: + usd_stage = scene_data.get("usd_stage") + simulation_context = scene_data.get("simulation_context") + + if usd_stage is None: + raise RuntimeError("OV visualizer requires a USD stage.") + + # Build metadata from simulation context if available + metadata = {} + if simulation_context is not None: + # Try to get num_envs from the simulation context's scene if available + num_envs = 0 + if hasattr(simulation_context, "scene") and simulation_context.scene is not None: + if hasattr(simulation_context.scene, "num_envs"): + num_envs = simulation_context.scene.num_envs + + # Detect physics backend (could be extended to check actual backend type) + physics_backend = "newton" # Default for now, could be made more sophisticated + + metadata = { + "num_envs": num_envs, + "physics_backend": physics_backend, + "env_prim_pattern": "/World/envs/env_{}", # Standard pattern + } + + self._ensure_simulation_app() + self._setup_viewport(usd_stage, metadata) + + num_envs = metadata.get("num_envs", 0) + physics_backend = metadata.get("physics_backend", "unknown") + omni.log.info(f"[OVVisualizer] Initialized ({num_envs} envs, {physics_backend} physics)") + + self._is_initialized = True + + def step(self, dt: float, state: Any | None = None) -> None: + """Update visualizer (no-op for OV - USD stage is auto-synced by Newton).""" + if not self._is_initialized: + return + self._sim_time += dt + self._step_counter += 1 + + def close(self) -> None: + """Clean up visualizer resources.""" + if not self._is_initialized: + return + + # Note: We don't close the SimulationApp here as it's managed by AppLauncher + self._simulation_app = None + self._viewport_window = None + self._viewport_api = None + self._is_initialized = False + + def is_running(self) -> bool: + """Check if visualizer is running.""" + if self._simulation_app is None: + return False + return self._simulation_app.is_running() + + def is_training_paused(self) -> bool: + """Check if training is paused (always False for OV).""" + return False + + def supports_markers(self) -> bool: + # Should we add marker configuration, or let the env itself handle it. + """Supports markers via USD prims.""" + return True + + def supports_live_plots(self) -> bool: + """Supports live plots via Isaac Lab UI. + + When enable_live_plots=True in OVVisualizerCfg: + - Automatically enables all manager visualizers (checkboxes checked) + - Keeps plot frames expanded by default + - Plots appear in the IsaacLab window docked to the right of the viewport + """ + return True + + # ------------------------------------------------------------------ + # Private methods + # ------------------------------------------------------------------ + + def _ensure_simulation_app(self) -> None: + """Ensure Isaac Sim app is running.""" + try: + # Check if omni.kit.app is available (indicates Isaac Sim is running) + import omni.kit.app + + # Get the running app instance + app = omni.kit.app.get_app() + if app is None or not app.is_running(): + raise RuntimeError( + "[OVVisualizer] No Isaac Sim app is running. " + "OV visualizer requires Isaac Sim to be launched via AppLauncher before initialization. " + "Ensure your script calls AppLauncher before creating the environment." + ) + + # Try to get SimulationApp instance for headless check + try: + from isaacsim import SimulationApp + + # Check various ways SimulationApp might store its instance + sim_app = None + if hasattr(SimulationApp, "_instance") and SimulationApp._instance is not None: + sim_app = SimulationApp._instance + elif hasattr(SimulationApp, "instance") and callable(SimulationApp.instance): + sim_app = SimulationApp.instance() + + if sim_app is not None: + self._simulation_app = sim_app + + # Check if running in headless mode + if self._simulation_app.config.get("headless", False): + omni.log.warn( + "[OVVisualizer] Running in headless mode. " + "OV visualizer requires GUI mode (launch with --headless=False) to create viewports." + ) + else: + omni.log.info("[OVVisualizer] Using existing Isaac Sim app instance.") + else: + # App is running but we couldn't get SimulationApp instance + # This is okay - we can still use omni APIs + omni.log.info("[OVVisualizer] Isaac Sim app is running (via omni.kit.app).") + + except ImportError: + # SimulationApp not available, but omni.kit.app is running + omni.log.info("[OVVisualizer] Using running Isaac Sim app (SimulationApp module not available).") + + except ImportError as e: + raise ImportError( + f"[OVVisualizer] Could not import omni.kit.app: {e}. Isaac Sim may not be installed or not running." + ) + + def _setup_viewport(self, usd_stage, metadata: dict) -> None: + """Setup viewport with camera and window size.""" + try: + import omni.kit.viewport.utility as vp_utils + from omni.ui import DockPosition + + # Create new viewport or use existing + if self.cfg.create_viewport and self.cfg.viewport_name: + # Map dock position string to enum + dock_position_map = { + "LEFT": DockPosition.LEFT, + "RIGHT": DockPosition.RIGHT, + "BOTTOM": DockPosition.BOTTOM, + "SAME": DockPosition.SAME, + } + dock_pos = dock_position_map.get(self.cfg.dock_position.upper(), DockPosition.SAME) + + # Create new viewport with proper API + self._viewport_window = vp_utils.create_viewport_window( + name=self.cfg.viewport_name, + width=self.cfg.window_width, + height=self.cfg.window_height, + position_x=50, + position_y=50, + docked=True, + ) + + omni.log.info(f"[OVVisualizer] Created viewport '{self.cfg.viewport_name}'") + + # Dock the viewport asynchronously (needs to wait for window creation) + asyncio.ensure_future(self._dock_viewport_async(self.cfg.viewport_name, dock_pos)) + + # Create dedicated camera for this viewport + if self._viewport_window: + self._create_and_assign_camera(usd_stage) + else: + # Use existing viewport by name, or fall back to active viewport + if self.cfg.viewport_name: + self._viewport_window = vp_utils.get_viewport_window_by_name(self.cfg.viewport_name) + + if self._viewport_window is None: + omni.log.warn( + f"[OVVisualizer] Viewport '{self.cfg.viewport_name}' not found. " + "Using active viewport instead." + ) + self._viewport_window = vp_utils.get_active_viewport_window() + else: + omni.log.info(f"[OVVisualizer] Using existing viewport '{self.cfg.viewport_name}'") + else: + self._viewport_window = vp_utils.get_active_viewport_window() + omni.log.info("[OVVisualizer] Using existing active viewport") + + if self._viewport_window is None: + omni.log.warn("[OVVisualizer] Could not get/create viewport.") + return + + # Get viewport API for camera control + self._viewport_api = self._viewport_window.viewport_api + + # Set camera pose (uses existing camera if not created above) + self._set_viewport_camera(self.cfg.camera_position, self.cfg.camera_target) + + omni.log.info( + f"[OVVisualizer] Viewport configured (size: {self.cfg.window_width}x{self.cfg.window_height})" + ) + + except ImportError as e: + omni.log.warn(f"[OVVisualizer] Viewport utilities unavailable: {e}") + except Exception as e: + omni.log.error(f"[OVVisualizer] Error setting up viewport: {e}") + + async def _dock_viewport_async(self, viewport_name: str, dock_position) -> None: + """Dock viewport window asynchronously after it's created. + + Args: + viewport_name: Name of the viewport window to dock. + dock_position: DockPosition enum value for where to dock. + """ + try: + import omni.kit.app + import omni.ui + + # Wait for the viewport window to be created in the workspace + viewport_window = None + for i in range(10): # Try up to 10 frames + viewport_window = omni.ui.Workspace.get_window(viewport_name) + if viewport_window: + omni.log.info(f"[OVVisualizer] Found viewport window '{viewport_name}' after {i} frames") + break + await omni.kit.app.get_app().next_update_async() + + if not viewport_window: + omni.log.warn( + f"[OVVisualizer] Could not find viewport window '{viewport_name}' in workspace for docking." + ) + return + + # Get the main viewport to dock relative to + main_viewport = omni.ui.Workspace.get_window("Viewport") + if not main_viewport: + # Try alternative viewport names + for alt_name in ["/OmniverseKit/Viewport", "Viewport Next"]: + main_viewport = omni.ui.Workspace.get_window(alt_name) + if main_viewport: + break + + if main_viewport and main_viewport != viewport_window: + # Dock the new viewport relative to the main viewport + viewport_window.dock_in(main_viewport, dock_position, 0.5) + + # Wait a frame for docking to complete + await omni.kit.app.get_app().next_update_async() + + # Make the new viewport the active/focused tab + # Try multiple methods to ensure it becomes active + viewport_window.focus() + viewport_window.visible = True + + # Wait another frame and focus again (sometimes needed for tabs) + await omni.kit.app.get_app().next_update_async() + viewport_window.focus() + + omni.log.info( + f"[OVVisualizer] Docked viewport '{viewport_name}' at position {self.cfg.dock_position} and set as" + " active" + ) + else: + omni.log.info( + f"[OVVisualizer] Could not find main viewport for docking. Viewport '{viewport_name}' will remain" + " floating." + ) + + except Exception as e: + omni.log.warn(f"[OVVisualizer] Error docking viewport: {e}") + + def _create_and_assign_camera(self, usd_stage) -> None: + """Create a dedicated camera for this viewport and assign it.""" + try: + # Create camera prim path based on viewport name + camera_path = f"/World/Cameras/{self.cfg.viewport_name}_Camera" + + # Check if camera already exists + camera_prim = usd_stage.GetPrimAtPath(camera_path) + if not camera_prim.IsValid(): + # Create camera prim + UsdGeom.Camera.Define(usd_stage, camera_path) + omni.log.info(f"[OVVisualizer] Created camera: {camera_path}") + else: + omni.log.info(f"[OVVisualizer] Using existing camera: {camera_path}") + + # Assign camera to viewport + if self._viewport_api: + self._viewport_api.set_active_camera(camera_path) + omni.log.info(f"[OVVisualizer] Assigned camera '{camera_path}' to viewport '{self.cfg.viewport_name}'") + + except Exception as e: + omni.log.warn(f"[OVVisualizer] Could not create/assign camera: {e}. Using default camera.") + + def _set_viewport_camera(self, position: tuple[float, float, float], target: tuple[float, float, float]) -> None: + """Set viewport camera position and target using Isaac Sim utilities.""" + if self._viewport_api is None: + return + + try: + # Import Isaac Sim viewport utilities + import isaacsim.core.utils.viewports as vp_utils + + # Get the camera prim path for this viewport + camera_path = self._viewport_api.get_active_camera() + if not camera_path: + camera_path = "/OmniverseKit_Persp" # Default camera + + # Use Isaac Sim utility to set camera view + vp_utils.set_camera_view( + eye=list(position), target=list(target), camera_prim_path=camera_path, viewport_api=self._viewport_api + ) + + omni.log.info(f"[OVVisualizer] Camera set: pos={position}, target={target}, camera={camera_path}") + + except Exception as e: + omni.log.warn(f"[OVVisualizer] Could not set camera: {e}") diff --git a/source/isaaclab/isaaclab/visualizers/ov_visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/ov_visualizer_cfg.py new file mode 100644 index 00000000000..c04ca0f31cf --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/ov_visualizer_cfg.py @@ -0,0 +1,48 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# Copyright (c) 2022-2025, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configuration for Omniverse-based visualizer.""" + +from isaaclab.utils import configclass + +from .visualizer_cfg import VisualizerCfg + + +@configclass +class OVVisualizerCfg(VisualizerCfg): + """Configuration for Omniverse visualizer using Isaac Sim viewport. + + Displays USD stage, VisualizationMarkers, and LivePlots. + Can attach to existing app or launch standalone. + """ + + visualizer_type: str = "omniverse" + """Type identifier for Omniverse visualizer.""" + + viewport_name: str | None = "Visualizer Viewport" + """Viewport name to use. If None, uses active viewport.""" + + create_viewport: bool = True + """Create new viewport with specified name and camera pose.""" + + dock_position: str = "SAME" + """Dock position for new viewport. Options: 'LEFT', 'RIGHT', 'BOTTOM', 'SAME' (tabs with existing).""" + + window_width: int = 1280 + """Viewport width in pixels.""" + + window_height: int = 720 + """Viewport height in pixels.""" + + camera_position: tuple[float, float, float] = (10.0, 10.0, 3.0) + """Initial camera position (x, y, z).""" + + camera_target: tuple[float, float, float] = (0.0, 0.0, 0.0) + """Initial camera target/look-at point (x, y, z).""" diff --git a/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py b/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py new file mode 100644 index 00000000000..9c87d27be77 --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py @@ -0,0 +1,268 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# Copyright (c) 2022-2025, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Rerun-based visualizer using rerun-sdk.""" + +from __future__ import annotations + +from typing import Any + +import omni.log + +from .rerun_visualizer_cfg import RerunVisualizerCfg +from .visualizer import Visualizer + +# Try to import rerun and Newton's ViewerRerun +try: + import rerun as rr + import rerun.blueprint as rrb + from newton.viewer import ViewerRerun + + _RERUN_AVAILABLE = True +except ImportError: + rr = None + rrb = None + ViewerRerun = None + _RERUN_AVAILABLE = False + + +class NewtonViewerRerun(ViewerRerun if _RERUN_AVAILABLE else object): + """Isaac Lab wrapper for Newton's ViewerRerun.""" + + def __init__( + self, + server: bool = True, + address: str = "127.0.0.1:9876", + launch_viewer: bool = True, + app_id: str | None = None, + keep_historical_data: bool = False, + keep_scalar_history: bool = True, + record_to_rrd: str | None = None, + metadata: dict | None = None, + ): + """Initialize Newton ViewerRerun wrapper.""" + # Call parent with only Newton parameters + super().__init__( + server=server, + address=address, + launch_viewer=launch_viewer, + app_id=app_id, + # Note: The current Newton version with IsaacLab does not support these recording flags. + # Support is available in the top of tree Newton version when we eventually upgrade. + # keep_historical_data=keep_historical_data, + # keep_scalar_history=keep_scalar_history, + # record_to_rrd=record_to_rrd, + ) + + # Isaac Lab state + self._metadata = metadata or {} + + # Log metadata on initialization + self._log_metadata() + + def _log_metadata(self) -> None: + """Log scene metadata to Rerun as text.""" + metadata_text = "# Isaac Lab Scene Metadata\n\n" + + # Physics info + physics_backend = self._metadata.get("physics_backend", "unknown") + metadata_text += f"**Physics Backend:** {physics_backend}\n" + + # Environment info + num_envs = self._metadata.get("num_envs", 0) + metadata_text += f"**Total Environments:** {num_envs}\n" + + # Additional metadata + for key, value in self._metadata.items(): + if key not in ["physics_backend", "num_envs"]: + metadata_text += f"**{key}:** {value}\n" + + # Log to Rerun + rr.log("metadata", rr.TextDocument(metadata_text, media_type=rr.MediaType.MARKDOWN)) + + +class RerunVisualizer(Visualizer): + """Rerun web-based visualizer with time scrubbing, recording, and data inspection. + + Requires Newton physics backend and rerun-sdk (pip install rerun-sdk).""" + + def __init__(self, cfg: RerunVisualizerCfg): + """Initialize Rerun visualizer.""" + super().__init__(cfg) + self.cfg: RerunVisualizerCfg = cfg + + if not _RERUN_AVAILABLE: + raise ImportError("Rerun visualizer requires rerun-sdk and Newton. Install: pip install rerun-sdk") + + self._viewer: NewtonViewerRerun | None = None + self._model = None + self._state = None + self._is_initialized = False + self._sim_time = 0.0 + self._scene_data_provider = None + + def initialize(self, scene_data: dict[str, Any] | None = None) -> None: + """Initialize visualizer with Newton Model and State.""" + if self._is_initialized: + omni.log.warn("[RerunVisualizer] Already initialized. Skipping re-initialization.") + return + + # Import NewtonManager for metadata access + from isaaclab.sim._impl.newton_manager import NewtonManager + + # Store scene data provider for accessing physics state + if scene_data and "scene_data_provider" in scene_data: + self._scene_data_provider = scene_data["scene_data_provider"] + + # Get Newton-specific data from scene data provider + if self._scene_data_provider: + self._model = self._scene_data_provider.get_model() + self._state = self._scene_data_provider.get_state() + else: + # Fallback: direct access to NewtonManager (for backward compatibility) + self._model = NewtonManager._model + self._state = NewtonManager._state_0 + + # Validate required Newton data + if self._model is None: + raise RuntimeError( + "Rerun visualizer requires a Newton Model. " + "Make sure Newton physics is initialized before creating the visualizer." + ) + + if self._state is None: + omni.log.warn("[RerunVisualizer] No Newton State available. Visualization may not work correctly.") + + # Build metadata from NewtonManager + metadata = { + "physics_backend": "newton", + "num_envs": NewtonManager._num_envs if NewtonManager._num_envs is not None else 0, + "gravity_vector": NewtonManager._gravity_vector, + "clone_physics_only": NewtonManager._clone_physics_only, + } + + # Create Newton ViewerRerun wrapper + try: + if self.cfg.record_to_rrd: + omni.log.info(f"[RerunVisualizer] Recording enabled to: {self.cfg.record_to_rrd}") + + self._viewer = NewtonViewerRerun( + server=self.cfg.server_mode, + address=self.cfg.server_address, + launch_viewer=self.cfg.launch_viewer, + app_id=self.cfg.app_id, + keep_historical_data=self.cfg.keep_historical_data, + keep_scalar_history=self.cfg.keep_scalar_history, + record_to_rrd=self.cfg.record_to_rrd, + metadata=metadata, + ) + + # Set the model + self._viewer.set_model(self._model) + + # Log initialization + num_envs = metadata.get("num_envs", 0) + physics_backend = metadata.get("physics_backend", "newton") + omni.log.info(f"[RerunVisualizer] Initialized with {num_envs} environments (physics: {physics_backend})") + + self._is_initialized = True + + except Exception as e: + omni.log.error(f"[RerunVisualizer] Failed to initialize viewer: {e}") + raise + + def step(self, dt: float, state: Any | None = None) -> None: + """Update visualizer each step. + + This method: + 1. Fetches updated state from NewtonManager + 2. Logs current state to Rerun (transforms, meshes) + 3. Actively logs markers (if enabled) + 4. Actively logs plot data (if enabled) + + Args: + dt: Time step in seconds. + state: Unused (deprecated parameter, kept for API compatibility). + """ + if not self._is_initialized or self._viewer is None: + omni.log.warn("[RerunVisualizer] Not initialized. Call initialize() first.") + return + + # Fetch updated state from scene data provider + if self._scene_data_provider: + self._state = self._scene_data_provider.get_state() + else: + # Fallback: direct access to NewtonManager + from isaaclab.sim._impl.newton_manager import NewtonManager + + self._state = NewtonManager._state_0 + + # Update internal time + self._sim_time += dt + + # Begin frame with current simulation time + self._viewer.begin_frame(self._sim_time) + + # Log state (transforms) - Newton's ViewerRerun handles this + if self._state is not None: + self._viewer.log_state(self._state) + + # End frame + self._viewer.end_frame() + + def close(self) -> None: + """Clean up Rerun visualizer resources and finalize recordings.""" + if not self._is_initialized or self._viewer is None: + return + + try: + if self.cfg.record_to_rrd: + omni.log.info(f"[RerunVisualizer] Finalizing recording to: {self.cfg.record_to_rrd}") + self._viewer.close() + omni.log.info("[RerunVisualizer] Closed successfully.") + if self.cfg.record_to_rrd: + import os + + if os.path.exists(self.cfg.record_to_rrd): + size = os.path.getsize(self.cfg.record_to_rrd) + omni.log.info(f"[RerunVisualizer] Recording saved: {self.cfg.record_to_rrd} ({size} bytes)") + else: + omni.log.warn(f"[RerunVisualizer] Recording file not found: {self.cfg.record_to_rrd}") + except Exception as e: + omni.log.warn(f"[RerunVisualizer] Error during close: {e}") + + self._viewer = None + self._is_initialized = False + + def is_running(self) -> bool: + """Check if visualizer is running. + + Returns: + True if viewer is initialized and running, False otherwise. + """ + if self._viewer is None: + return False + return self._viewer.is_running() + + def is_training_paused(self) -> bool: + """Check if training is paused. + + Note: + Rerun visualizer currently uses Rerun's built-in timeline controls for playback. + """ + return False + + def supports_markers(self) -> bool: + """Rerun visualizer does not have this feature yet.""" + return False + + def supports_live_plots(self) -> bool: + """Rerun visualizer does not have this feature yet.""" + return False diff --git a/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py new file mode 100644 index 00000000000..b91c71b8e9e --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py @@ -0,0 +1,50 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# Copyright (c) 2022-2025, The Isaac Lab Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Configuration for the Rerun visualizer.""" + +from __future__ import annotations + +from isaaclab.utils import configclass + +from .visualizer_cfg import VisualizerCfg + + +@configclass +class RerunVisualizerCfg(VisualizerCfg): + """Configuration for Rerun visualizer (web-based visualization). + + Provides time scrubbing, 3D navigation, data filtering, and .rrd recording. + Requires Newton physics backend and rerun-sdk: `pip install rerun-sdk` + """ + + visualizer_type: str = "rerun" + """Type identifier for Rerun visualizer.""" + + server_mode: bool = True + """Run Rerun in server mode (gRPC for web viewer).""" + + server_address: str = "127.0.0.1:9876" + """Server address and port for gRPC mode.""" + + launch_viewer: bool = True + """Auto-launch web viewer in browser.""" + + app_id: str = "isaaclab-simulation" + """Application identifier shown in viewer title.""" + + keep_historical_data: bool = False + """Keep transform history for time scrubbing (False = constant memory for training).""" + + keep_scalar_history: bool = True + """Keep scalar/plot history in timeline.""" + + record_to_rrd: str | None = None + """Path to save .rrd recording file. None = no recording.""" diff --git a/source/isaaclab/isaaclab/visualizers/visualizer.py b/source/isaaclab/isaaclab/visualizers/visualizer.py new file mode 100644 index 00000000000..1fef125d52c --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/visualizer.py @@ -0,0 +1,78 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Base class for visualizers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from .visualizer_cfg import VisualizerCfg + + +class Visualizer(ABC): + """Base class for all visualizer backends. + + Lifecycle: __init__() -> initialize() -> step() (repeated) -> close() + """ + + def __init__(self, cfg: VisualizerCfg): + """Initialize visualizer with config.""" + self.cfg = cfg + self._is_initialized = False + self._is_closed = False + + @abstractmethod + def initialize(self, scene_data: dict[str, Any] | None = None) -> None: + """Initialize visualizer with scene data (model, state, usd_stage, etc.).""" + pass + + @abstractmethod + def step(self, dt: float, state: Any | None = None) -> None: + """Update visualization for one step. + + Args: + dt: Time step in seconds. + state: Updated physics state (e.g., newton.State). + """ + pass + + @abstractmethod + def close(self) -> None: + """Clean up resources.""" + pass + + @abstractmethod + def is_running(self) -> bool: + """Check if visualizer is still running (e.g., window not closed).""" + pass + + def is_training_paused(self) -> bool: + """Check if training is paused by visualizer controls.""" + return False + + def is_rendering_paused(self) -> bool: + """Check if rendering is paused by visualizer controls.""" + return False + + @property + def is_initialized(self) -> bool: + """Check if initialize() has been called.""" + return self._is_initialized + + @property + def is_closed(self) -> bool: + """Check if close() has been called.""" + return self._is_closed + + def supports_markers(self) -> bool: + """Check if visualizer supports VisualizationMarkers.""" + return False + + def supports_live_plots(self) -> bool: + """Check if visualizer supports LivePlots.""" + return False diff --git a/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py new file mode 100644 index 00000000000..100411c3061 --- /dev/null +++ b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py @@ -0,0 +1,54 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Base configuration for visualizers.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from isaaclab.utils import configclass + +if TYPE_CHECKING: + from .visualizer import Visualizer + + +@configclass +class VisualizerCfg: + """Base configuration for all visualizer backends.""" + + visualizer_type: str = "base" + """Type identifier (e.g., 'newton', 'rerun', 'omniverse').""" + + # Note: Partial environment visualization will come later + # env_ids: list[Integer] = [] + + enable_markers: bool = True + """Enable visualization markers (debug drawing).""" + + enable_live_plots: bool = True + """Enable live plotting of data. + + When set to True for OVVisualizer: + - Automatically checks the checkboxes for all manager visualizers (Actions, Observations, Rewards, etc.) + - Keeps the plot frames expanded by default (not collapsed) + - Makes the live plots visible immediately in the IsaacLab window (docked to the right of the viewport) + + This provides a better out-of-the-box experience when you want to monitor training metrics. + """ + + def get_visualizer_type(self) -> str: + """Get the visualizer type identifier.""" + return self.visualizer_type + + def create_visualizer(self) -> Visualizer: + """Create visualizer instance from this config using factory pattern.""" + from . import get_visualizer_class + + visualizer_class = get_visualizer_class(self.visualizer_type) + if visualizer_class is None: + raise ValueError(f"Visualizer type '{self.visualizer_type}' is not registered.") + + return visualizer_class(self) diff --git a/source/isaaclab/setup.py b/source/isaaclab/setup.py index f487b388bde..bf12b70644d 100644 --- a/source/isaaclab/setup.py +++ b/source/isaaclab/setup.py @@ -53,9 +53,11 @@ "newton @ git+https://github.com/newton-physics/newton.git@15b9955bafa61f8fcb40c17dc00f0b552d3c65ca", "imgui-bundle==1.92.0", "PyOpenGL-accelerate==3.1.10", + # Note, this older version of rerun causes the view to flash dark & light + # newer versions of rerun, like 0.27, don't have this issue, but require numpy >=2 + "rerun-sdk==0.23", ] - # Additional dependencies that are only available on Linux platforms if platform.system() == "Linux": INSTALL_REQUIRES += [ diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py index 3842bda1471..a07befbd3f9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py @@ -119,7 +119,6 @@ def parse_env_cfg( device: str = "cuda:0", num_envs: int | None = None, use_fabric: bool | None = None, - newton_visualizer: bool | None = None, ) -> ManagerBasedRLEnvCfg | DirectRLEnvCfg: """Parse configuration for an environment and override based on inputs. @@ -130,7 +129,6 @@ def parse_env_cfg( use_fabric: Whether to enable/disable fabric interface. If false, all read/write operations go through USD. This slows down the simulation but allows seeing the changes in the USD through the USD stage. Defaults to None, in which case it is left unchanged. - newton_visualizer: Whether to enable/disable Newton rendering. Defaults to None, in which case it is left unchanged. Returns: The parsed configuration object. @@ -155,9 +153,6 @@ def parse_env_cfg( # number of environments if num_envs is not None: cfg.scene.num_envs = num_envs - # newton rendering - if newton_visualizer is not None: - cfg.sim.enable_newton_rendering = newton_visualizer return cfg