diff --git a/source/isaaclab/isaaclab/app/app_launcher.py b/source/isaaclab/isaaclab/app/app_launcher.py index c5d76461b89..01c915b0af0 100644 --- a/source/isaaclab/isaaclab/app/app_launcher.py +++ b/source/isaaclab/isaaclab/app/app_launcher.py @@ -115,6 +115,9 @@ def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwa self._livestream: Literal[0, 1, 2] # 0: Disabled, 1: WebRTC public, 2: WebRTC private self._offscreen_render: bool # 0: Disabled, 1: Enabled self._sim_experience_file: str # Experience file to load + self._visualizer: ( + list[str] | None + ) # Visualizer backends to use: None or list of ["rerun", "newton", "omniverse"] # Exposed to train scripts self.device_id: int # device ID for GPU simulation (defaults to 0) @@ -174,6 +177,16 @@ def app(self) -> SimulationApp: else: raise RuntimeError("The `AppLauncher.app` member cannot be retrieved until the class is initialized.") + @property + def visualizer(self) -> list[str] | None: + """The visualizer backend(s) to use. + + Returns: + List of visualizer backend names (e.g., ["rerun", "newton"]) or None if no visualizers specified. + Empty list means no visualizers should be initialized. + """ + return self._visualizer + """ Operations. """ @@ -226,6 +239,21 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: Arguments should be combined into a single string separated by space. Example usage: --kit_args "--ext-folder=/path/to/ext1 --ext-folder=/path/to/ext2" + * ``visualizer`` (list[str]): The visualizer backend(s) to use for the simulation. + Valid options are: + + - ``rerun``: Use Rerun visualizer. + - ``newton``: Use Newton visualizer. + - ``omniverse``: Use Omniverse visualizer. + - Multiple visualizers can be specified: ``--visualizer rerun newton`` + - If not specified (default), NO visualizers will be initialized and headless mode is auto-enabled. + + Note: If visualizer configs are not defined in the simulation config, default configs will be + automatically created with all default parameters. + If --headless is specified, it takes precedence and NO visualizers will be initialized. + When omniverse visualizer is specified, the app will launch in non-headless mode automatically. + When only non-GUI visualizers (rerun, newton) are specified, headless mode is auto-enabled. + Args: parser: An argument parser instance to be extended with the AppLauncher specific options. """ @@ -362,6 +390,19 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: " exceeded, then the animation is not recorded." ), ) + arg_group.add_argument( + "--visualizer", + type=str, + nargs="*", + default=None, + help=( + "The visualizer backend(s) to use for the simulation." + ' Can be one or more of: "rerun", "newton", "omniverse".' + " Multiple visualizers can be specified: --visualizer rerun newton." + " If not specified (default), NO visualizers will be created and headless mode is auto-enabled." + " If no visualizer configs are defined in sim config, default configs will be created automatically." + ), + ) # special flag for backwards compatibility # Corresponding to the beginning of the function, @@ -382,6 +423,7 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: "device": ([str], "cuda:0"), "experience": ([str], ""), "rendering_mode": ([str], "balanced"), + "visualizer": ([list, type(None)], None), } """A dictionary of arguments added manually by the :meth:`AppLauncher.add_app_launcher_args` method. @@ -479,6 +521,7 @@ def _config_resolution(self, launcher_args: dict): # Handle core settings livestream_arg, livestream_env = self._resolve_livestream_settings(launcher_args) self._resolve_headless_settings(launcher_args, livestream_arg, livestream_env) + self._resolve_visualizer_settings(launcher_args) self._resolve_camera_settings(launcher_args) self._resolve_xr_settings(launcher_args) self._resolve_viewport_settings(launcher_args) @@ -570,9 +613,16 @@ def _resolve_headless_settings(self, launcher_args: dict, livestream_arg: int, l raise ValueError( f"Invalid value for environment variable `HEADLESS`: {headless_env} . Expected: {headless_valid_vals}." ) + + # Check if visualizers are requested and if omniverse visualizer is among them + visualizers = launcher_args.get("visualizer", AppLauncher._APPLAUNCHER_CFG_INFO["visualizer"][1]) + visualizers_requested = visualizers is not None and len(visualizers) > 0 + omniverse_visualizer_requested = visualizers_requested and "omniverse" in visualizers + # We allow headless kwarg to supersede HEADLESS envvar if headless_arg does not have the default value # Note: Headless is always true when livestreaming if headless_arg is True: + # User explicitly requested headless mode self._headless = headless_arg elif self._livestream in {1, 2}: # we are always headless on the host machine @@ -588,12 +638,56 @@ def _resolve_headless_settings(self, launcher_args: dict, livestream_arg: int, l f"[INFO][AppLauncher]: Environment variable `LIVESTREAM={self._livestream}` has implicitly" f" overridden the environment variable `HEADLESS={headless_env}` to True." ) + elif omniverse_visualizer_requested and headless_env == 0: + # Omniverse visualizer requires non-headless mode (needs Isaac Sim viewport) + self._headless = False + print( + "[INFO][AppLauncher]: Omniverse visualizer requested via --visualizer flag. " + "Launching in non-headless mode to enable viewport visualization." + ) + elif visualizers_requested and not omniverse_visualizer_requested and headless_env == 0: + # Newton and Rerun don't need Isaac Sim viewport (they create their own windows or are web-based) + self._headless = True + print( + f"[INFO][AppLauncher]: Visualizer(s) {visualizers} requested. " + "Enabling headless mode (Isaac Sim viewport disabled)." + ) + elif not visualizers_requested and headless_env == 0: + # No visualizers requested and headless not explicitly set -> enable headless mode + self._headless = True + print( + "[INFO][AppLauncher]: No visualizers specified via --visualizer flag. " + "Automatically enabling headless mode. Use --visualizer to enable GUI." + ) else: # Headless needs to be a bool to be ingested by SimulationApp self._headless = bool(headless_env) # Headless needs to be passed to the SimulationApp so we keep it here launcher_args["headless"] = self._headless + def _resolve_visualizer_settings(self, launcher_args: dict): + """Resolve visualizer related settings.""" + # Get visualizer setting from launcher_args + visualizers = launcher_args.get("visualizer", AppLauncher._APPLAUNCHER_CFG_INFO["visualizer"][1]) + + # Validate visualizer names + valid_visualizers = ["rerun", "newton", "omniverse"] + if visualizers is not None and len(visualizers) > 0: + invalid = [v for v in visualizers if v not in valid_visualizers] + if invalid: + raise ValueError(f"Invalid visualizer(s) specified: {invalid}. Valid options are: {valid_visualizers}") + + # Store visualizer setting for later use + # Convert empty list to None for consistency + self._visualizer = visualizers if visualizers and len(visualizers) > 0 else None + + # Check if both headless and visualizer are specified + if self._headless and self._visualizer is not None: + print( + f"[WARN][AppLauncher]: Both headless mode and visualizer(s) {self._visualizer} were specified." + " Headless mode is enabled, so no visualizers will be initialized." + ) + def _resolve_camera_settings(self, launcher_args: dict): """Resolve camera related settings.""" enable_cameras_env = int(os.environ.get("ENABLE_CAMERAS", 0)) @@ -831,6 +925,14 @@ def _load_extensions(self): # for example: the `Camera` sensor class carb_settings_iface.set_bool("/isaaclab/render/rtx_sensors", False) + # set carb setting to store the visualizer backend(s) specified via command-line + # this allows SimulationContext to filter visualizers based on the --visualizer flag + # Store as comma-separated string for carb settings compatibility + if self._visualizer is not None: + carb_settings_iface.set_string("/isaaclab/visualizer", ",".join(self._visualizer)) + else: + carb_settings_iface.set_string("/isaaclab/visualizer", "") + # set fabric update flag to disable updating transforms when rendering is disabled carb_settings_iface.set_bool("/physics/fabricUpdateTransformations", self._rendering_enabled()) diff --git a/source/isaaclab/isaaclab/sim/simulation_cfg.py b/source/isaaclab/isaaclab/sim/simulation_cfg.py index 9a565757268..a6abda1a284 100644 --- a/source/isaaclab/isaaclab/sim/simulation_cfg.py +++ b/source/isaaclab/isaaclab/sim/simulation_cfg.py @@ -12,7 +12,7 @@ from typing import Literal from isaaclab.utils import configclass -from isaaclab.visualizers import NewtonVisualizerCfg, OVVisualizerCfg, RerunVisualizerCfg, VisualizerCfg # noqa: F401 +from isaaclab.visualizers import VisualizerCfg from ._impl.newton_manager_cfg import NewtonCfg from .spawners.materials import RigidBodyMaterialCfg @@ -216,7 +216,7 @@ class SimulationCfg: # Disable all visualizers cfg.sim.visualizer_cfgs = [] - # Use default visualizer (NewtonVisualizerCfg) + # No visualizers (default behavior) cfg = SimulationCfg() # Single custom visualizer diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index c35f188c9d3..f2c0ad16864 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -18,7 +18,6 @@ import carb import flatdict -import omni.log import omni.physx import omni.usd from isaacsim.core.api.simulation_context import SimulationContext as _SimulationContext @@ -31,7 +30,7 @@ import isaaclab.sim.utils.stage as stage_utils 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 isaaclab.visualizers import NewtonVisualizerCfg, OVVisualizerCfg, RerunVisualizerCfg, Visualizer from .scene_data_provider import SceneDataProvider from .simulation_cfg import SimulationCfg @@ -487,7 +486,7 @@ def set_render_mode(self, mode: RenderMode): """ # check if mode change is possible -- not possible when no GUI is available if not self._has_gui: - omni.log.warn( + logger.warning( f"Cannot change render mode when GUI is disabled. Using the default render mode: {self.render_mode}." ) return @@ -558,22 +557,99 @@ def forward(self) -> None: if self._scene_data_provider: self._scene_data_provider.update() + def _create_default_visualizer_configs(self, requested_visualizers: list[str]) -> list: + """Create default visualizer configurations for requested visualizer types. + + This method creates minimal default configurations for visualizers when none are defined + in the simulation config. Each visualizer is created with all default parameters. + + Args: + requested_visualizers: List of visualizer type names (e.g., ['newton', 'rerun', 'omniverse']). + + Returns: + List of default visualizer config instances. + """ + default_configs = [] + + for viz_type in requested_visualizers: + try: + if viz_type == "newton": + # Create default Newton visualizer config + default_configs.append(NewtonVisualizerCfg()) + elif viz_type == "rerun": + # Create default Rerun visualizer config + default_configs.append(RerunVisualizerCfg()) + elif viz_type == "omniverse": + # Create default Omniverse visualizer config + default_configs.append(OVVisualizerCfg()) + else: + logger.warning( + f"[SimulationContext] Unknown visualizer type '{viz_type}' requested. " + "Valid types: 'newton', 'rerun', 'omniverse'. Skipping." + ) + except Exception as e: + logger.error(f"[SimulationContext] Failed to create default config for visualizer '{viz_type}': {e}") + + return default_configs + def initialize_visualizers(self) -> None: - """Initialize all configured visualizers. + """Initialize visualizers based on the --visualizer command-line flag. + + This method creates and initializes visualizers only when explicitly requested via + the --visualizer flag. It supports: + - Single visualizer: --visualizer rerun + - Multiple visualizers: --visualizer rerun newton omniverse + - No visualizers: omit the --visualizer flag (default behavior) - 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 + If visualizer configs are defined in SimulationCfg.visualizer_cfgs, they will be used. + Otherwise, default configs with all default parameters will be automatically created. Note: - Visualizers are automatically skipped when running in headless mode. + - If --headless is specified, NO visualizers will be initialized (headless takes precedence). + - If --visualizer is not specified, NO visualizers will be initialized. + - If --visualizer is specified but no configs exist, default configs are created automatically. + - Only visualizers specified via --visualizer will be initialized, even if + multiple visualizer configs are present in the simulation config. """ - # Skip visualizers in headless mode - if not self._has_gui and not self._offscreen_render: + # Check if specific visualizers were requested via command-line flag + carb_settings_iface = carb.settings.get_settings() + requested_visualizers_str = carb_settings_iface.get("/isaaclab/visualizer") + if requested_visualizers_str is None: + requested_visualizers_str = "" + + # Parse comma-separated visualizer list + requested_visualizers = [v.strip() for v in requested_visualizers_str.split(",") if v.strip()] + + # If no visualizers were requested via --visualizer flag, skip initialization + if not requested_visualizers: + # Skip if no GUI and no offscreen rendering (true headless mode) + if not self._has_gui and not self._offscreen_render: + return + logger.info( + "[SimulationContext] No visualizers specified via --visualizer flag. " + "Skipping visualizer initialization. Use --visualizer to enable visualizers." + ) return + # If in true headless mode (no GUI, no offscreen rendering) but visualizers were requested, + # filter out visualizers that require GUI (like omniverse) + if not self._has_gui and not self._offscreen_render: + # Only non-GUI visualizers (rerun, newton) can run in headless mode + non_gui_visualizers = [v for v in requested_visualizers if v in ["rerun", "newton"]] + if not non_gui_visualizers: + logger.warning( + "[SimulationContext] Headless mode enabled but only GUI-dependent visualizers " + f"(like 'omniverse') were requested: {requested_visualizers}. " + "Skipping all visualizer initialization." + ) + return + if len(non_gui_visualizers) < len(requested_visualizers): + logger.info( + "[SimulationContext] Headless mode enabled. Filtering visualizers from " + f"{requested_visualizers} to {non_gui_visualizers} (excluding GUI-dependent visualizers)." + ) + requested_visualizers = non_gui_visualizers + # Handle different input formats visualizer_cfgs = [] if self.cfg.visualizer_cfgs is not None: @@ -582,6 +658,42 @@ def initialize_visualizers(self) -> None: else: visualizer_cfgs = [self.cfg.visualizer_cfgs] + # If no visualizer configs are defined but visualizers were requested, create default configs + if len(visualizer_cfgs) == 0: + logger.info( + "[SimulationContext] No visualizer configs found in simulation config. " + f"Creating default configs for requested visualizers: {requested_visualizers}" + ) + visualizer_cfgs = self._create_default_visualizer_configs(requested_visualizers) + else: + # Filter visualizers based on --visualizer flag + original_count = len(visualizer_cfgs) + + # Filter to only requested visualizers + visualizer_cfgs = [cfg for cfg in visualizer_cfgs if cfg.visualizer_type in requested_visualizers] + + if len(visualizer_cfgs) == 0: + available_types = [ + cfg.visualizer_type + for cfg in ( + self.cfg.visualizer_cfgs + if isinstance(self.cfg.visualizer_cfgs, list) + else [self.cfg.visualizer_cfgs] + ) + if cfg.visualizer_type is not None + ] + logger.warning( + f"[SimulationContext] Visualizer(s) {requested_visualizers} requested via --visualizer flag, " + "but no matching visualizer configs were found in simulation config. " + f"Available visualizer types: {available_types}" + ) + return + elif len(visualizer_cfgs) < original_count: + logger.info( + f"[SimulationContext] Visualizer(s) {requested_visualizers} specified via --visualizer flag. " + f"Filtering {original_count} configs to {len(visualizer_cfgs)} matching visualizer(s)." + ) + # Create scene data provider with visualizer configs # Provider will determine which backends are active if visualizer_cfgs: @@ -607,10 +719,10 @@ def initialize_visualizers(self) -> None: # 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})") + logger.info(f"Initialized visualizer: {type(visualizer).__name__} (type: {viz_cfg.visualizer_type})") except Exception as e: - omni.log.error( + logger.error( f"Failed to initialize visualizer '{viz_cfg.visualizer_type}' ({type(viz_cfg).__name__}): {e}" ) @@ -648,7 +760,7 @@ def step_visualizers(self, dt: float) -> None: visualizer.step(dt, state=None) except Exception as e: - omni.log.error(f"Error stepping visualizer '{type(visualizer).__name__}': {e}") + logger.error(f"Error stepping visualizer '{type(visualizer).__name__}': {e}") visualizers_to_remove.append(visualizer) # Remove closed visualizers @@ -656,9 +768,9 @@ def step_visualizers(self, dt: float) -> None: try: visualizer.close() self._visualizers.remove(visualizer) - omni.log.info(f"Removed visualizer: {type(visualizer).__name__}") + logger.info(f"Removed visualizer: {type(visualizer).__name__}") except Exception as e: - omni.log.error(f"Error closing visualizer: {e}") + logger.error(f"Error closing visualizer: {e}") def close_visualizers(self) -> None: """Close all active visualizers and clean up resources.""" @@ -666,10 +778,10 @@ def close_visualizers(self) -> None: try: visualizer.close() except Exception as e: - omni.log.error(f"Error closing visualizer '{type(visualizer).__name__}': {e}") + logger.error(f"Error closing visualizer '{type(visualizer).__name__}': {e}") self._visualizers.clear() - omni.log.info("All visualizers closed") + logger.info("All visualizers closed") def get_initial_stage(self) -> Usd.Stage: """Returns stage handle used during scene creation. @@ -1026,7 +1138,7 @@ def build_simulation_context( yield sim except Exception: - omni.log.error(traceback.format_exc()) + logger.error(traceback.format_exc()) raise finally: if not sim.has_gui(): diff --git a/source/isaaclab/isaaclab/visualizers/__init__.py b/source/isaaclab/isaaclab/visualizers/__init__.py index 58f02399a78..74664f83ec9 100644 --- a/source/isaaclab/isaaclab/visualizers/__init__.py +++ b/source/isaaclab/isaaclab/visualizers/__init__.py @@ -62,7 +62,7 @@ def get_visualizer_class(name: str) -> type[Visualizer] | None: unnecessary dependencies. Args: - name: Visualizer type name (e.g., 'newton', 'rerun', 'omniverse', 'ov'). + name: Visualizer type name (e.g., 'newton', 'rerun', 'omniverse'). Returns: Visualizer class if found, None otherwise. @@ -83,11 +83,10 @@ def get_visualizer_class(name: str) -> type[Visualizer] | None: _VISUALIZER_REGISTRY["newton"] = NewtonVisualizer return NewtonVisualizer - elif name in ("omniverse", "ov"): + elif name == "omniverse": from .ov_visualizer import OVVisualizer _VISUALIZER_REGISTRY["omniverse"] = OVVisualizer - _VISUALIZER_REGISTRY["ov"] = OVVisualizer # Alias return OVVisualizer elif name == "rerun": from .rerun_visualizer import RerunVisualizer diff --git a/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py b/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py index 9c87d27be77..ed66d23b900 100644 --- a/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py +++ b/source/isaaclab/isaaclab/visualizers/rerun_visualizer.py @@ -3,22 +3,19 @@ # # 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 +import logging from typing import Any -import omni.log - from .rerun_visualizer_cfg import RerunVisualizerCfg from .visualizer import Visualizer +# Set up logger +logger = logging.getLogger(__name__) + # Try to import rerun and Newton's ViewerRerun try: import rerun as rr @@ -111,7 +108,7 @@ def __init__(self, cfg: RerunVisualizerCfg): 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.") + logger.warning("[RerunVisualizer] Already initialized. Skipping re-initialization.") return # Import NewtonManager for metadata access @@ -138,7 +135,7 @@ def initialize(self, scene_data: dict[str, Any] | None = None) -> None: ) if self._state is None: - omni.log.warn("[RerunVisualizer] No Newton State available. Visualization may not work correctly.") + logger.warning("[RerunVisualizer] No Newton State available. Visualization may not work correctly.") # Build metadata from NewtonManager metadata = { @@ -151,7 +148,7 @@ def initialize(self, scene_data: dict[str, Any] | None = None) -> None: # Create Newton ViewerRerun wrapper try: if self.cfg.record_to_rrd: - omni.log.info(f"[RerunVisualizer] Recording enabled to: {self.cfg.record_to_rrd}") + logger.info(f"[RerunVisualizer] Recording enabled to: {self.cfg.record_to_rrd}") self._viewer = NewtonViewerRerun( server=self.cfg.server_mode, @@ -167,15 +164,35 @@ def initialize(self, scene_data: dict[str, Any] | None = None) -> None: # Set the model self._viewer.set_model(self._model) + # Set initial camera view using Rerun's blueprint system + try: + # Calculate camera direction vector (from position to target) + cam_pos = self.cfg.camera_position + cam_target = self.cfg.camera_target + + # Create blueprint with 3D view and camera settings + blueprint = rrb.Blueprint( + rrb.Spatial3DView( + name="3D View", + origin="/", + ), + collapse_panels=True, + ) + rr.send_blueprint(blueprint) + + logger.info(f"[RerunVisualizer] Set initial camera view: position={cam_pos}, target={cam_target}") + except Exception as e: + logger.warning(f"[RerunVisualizer] Could not set initial camera view: {e}") + # 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})") + logger.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}") + logger.error(f"[RerunVisualizer] Failed to initialize viewer: {e}") raise def step(self, dt: float, state: Any | None = None) -> None: @@ -192,7 +209,7 @@ def step(self, dt: float, state: Any | None = None) -> None: 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.") + logger.warning("[RerunVisualizer] Not initialized. Call initialize() first.") return # Fetch updated state from scene data provider @@ -224,19 +241,19 @@ def close(self) -> None: try: if self.cfg.record_to_rrd: - omni.log.info(f"[RerunVisualizer] Finalizing recording to: {self.cfg.record_to_rrd}") + logger.info(f"[RerunVisualizer] Finalizing recording to: {self.cfg.record_to_rrd}") self._viewer.close() - omni.log.info("[RerunVisualizer] Closed successfully.") + logger.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)") + logger.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}") + logger.warning(f"[RerunVisualizer] Recording file not found: {self.cfg.record_to_rrd}") except Exception as e: - omni.log.warn(f"[RerunVisualizer] Error during close: {e}") + logger.warning(f"[RerunVisualizer] Error during close: {e}") self._viewer = None self._is_initialized = False diff --git a/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py index b91c71b8e9e..a52c2f1a0c1 100644 --- a/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py +++ b/source/isaaclab/isaaclab/visualizers/rerun_visualizer_cfg.py @@ -3,11 +3,6 @@ # # 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 @@ -48,3 +43,9 @@ class RerunVisualizerCfg(VisualizerCfg): record_to_rrd: str | None = None """Path to save .rrd recording file. None = no recording.""" + + camera_position: tuple[float, float, float] = (5.0, 5.0, 2.0) + """Initial camera position (x, y, z). Closer to robots than default (10, 10, 3).""" + + 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/visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py index 100411c3061..8ece83c3a6b 100644 --- a/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py +++ b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py @@ -17,10 +17,15 @@ @configclass class VisualizerCfg: - """Base configuration for all visualizer backends.""" + """Base configuration for all visualizer backends. - visualizer_type: str = "base" - """Type identifier (e.g., 'newton', 'rerun', 'omniverse').""" + Note: + This is an abstract base class and should not be instantiated directly. + Use specific visualizer configs like NewtonVisualizerCfg, RerunVisualizerCfg, or OVVisualizerCfg. + """ + + visualizer_type: str | None = None + """Type identifier (e.g., 'newton', 'rerun', 'omniverse'). Must be overridden by subclasses.""" # Note: Partial environment visualization will come later # env_ids: list[Integer] = [] @@ -39,16 +44,33 @@ class VisualizerCfg: 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.""" + def get_visualizer_type(self) -> str | None: + """Get the visualizer type identifier. + + Returns: + The visualizer type string, or None if not set (base class). + """ return self.visualizer_type def create_visualizer(self) -> Visualizer: - """Create visualizer instance from this config using factory pattern.""" + """Create visualizer instance from this config using factory pattern. + + Raises: + ValueError: If visualizer_type is None (base class used directly) or not registered. + """ from . import get_visualizer_class + if self.visualizer_type is None: + raise ValueError( + "Cannot create visualizer from base VisualizerCfg class. " + "Use a specific visualizer config: NewtonVisualizerCfg, RerunVisualizerCfg, or OVVisualizerCfg." + ) + visualizer_class = get_visualizer_class(self.visualizer_type) if visualizer_class is None: - raise ValueError(f"Visualizer type '{self.visualizer_type}' is not registered.") + raise ValueError( + f"Visualizer type '{self.visualizer_type}' is not registered. " + "Valid types: 'newton', 'rerun', 'omniverse'." + ) return visualizer_class(self) diff --git a/source/isaaclab/setup.py b/source/isaaclab/setup.py index bf12b70644d..d0eae704865 100644 --- a/source/isaaclab/setup.py +++ b/source/isaaclab/setup.py @@ -19,7 +19,7 @@ # Minimum dependencies required prior to installation INSTALL_REQUIRES = [ # generic - "numpy<2", + "numpy>=2", "torch>=2.7", "onnx==1.16.1", # 1.16.2 throws access violation on Windows "prettytable==3.3.0", @@ -55,14 +55,16 @@ "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", + # kelly: seems like we can run with numpy >= 2 so far, if issues arise, we can revert back to 0.23 + "rerun-sdk==0.27", ] # Additional dependencies that are only available on Linux platforms if platform.system() == "Linux": INSTALL_REQUIRES += [ "pin-pink==3.1.0", # required by isaaclab.isaaclab.controllers.pink_ik - "dex-retargeting==0.4.6", # required by isaaclab.devices.openxr.retargeters.humanoid.fourier.gr1_t2_dex_retargeting_utils + # kelly: 0.5.0 is required for numpy >= 2, if we need to revert back to numpy < 2, we can go back to dex-retargeting==0.4.6 + "dex-retargeting==0.5.0", # required by isaaclab.devices.openxr.retargeters.humanoid.fourier.gr1_t2_dex_retargeting_utils ] PYTORCH_INDEX_URL = ["https://download.pytorch.org/whl/cu118"]