@@ -115,6 +115,9 @@ def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwa
115115 self ._livestream : Literal [0 , 1 , 2 ] # 0: Disabled, 1: WebRTC public, 2: WebRTC private
116116 self ._offscreen_render : bool # 0: Disabled, 1: Enabled
117117 self ._sim_experience_file : str # Experience file to load
118+ self ._visualizer : (
119+ list [str ] | None
120+ ) # Visualizer backends to use: None or list of ["rerun", "newton", "omniverse"]
118121
119122 # Exposed to train scripts
120123 self .device_id : int # device ID for GPU simulation (defaults to 0)
@@ -174,6 +177,16 @@ def app(self) -> SimulationApp:
174177 else :
175178 raise RuntimeError ("The `AppLauncher.app` member cannot be retrieved until the class is initialized." )
176179
180+ @property
181+ def visualizer (self ) -> list [str ] | None :
182+ """The visualizer backend(s) to use.
183+
184+ Returns:
185+ List of visualizer backend names (e.g., ["rerun", "newton"]) or None if no visualizers specified.
186+ Empty list means no visualizers should be initialized.
187+ """
188+ return self ._visualizer
189+
177190 """
178191 Operations.
179192 """
@@ -226,6 +239,21 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
226239 Arguments should be combined into a single string separated by space.
227240 Example usage: --kit_args "--ext-folder=/path/to/ext1 --ext-folder=/path/to/ext2"
228241
242+ * ``visualizer`` (list[str]): The visualizer backend(s) to use for the simulation.
243+ Valid options are:
244+
245+ - ``rerun``: Use Rerun visualizer.
246+ - ``newton``: Use Newton visualizer.
247+ - ``omniverse``: Use Omniverse visualizer.
248+ - Multiple visualizers can be specified: ``--visualizer rerun newton``
249+ - If not specified (default), NO visualizers will be initialized and headless mode is auto-enabled.
250+
251+ Note: If visualizer configs are not defined in the simulation config, default configs will be
252+ automatically created with all default parameters.
253+ If --headless is specified, it takes precedence and NO visualizers will be initialized.
254+ When omniverse visualizer is specified, the app will launch in non-headless mode automatically.
255+ When only non-GUI visualizers (rerun, newton) are specified, headless mode is auto-enabled.
256+
229257 Args:
230258 parser: An argument parser instance to be extended with the AppLauncher specific options.
231259 """
@@ -362,6 +390,19 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
362390 " exceeded, then the animation is not recorded."
363391 ),
364392 )
393+ arg_group .add_argument (
394+ "--visualizer" ,
395+ type = str ,
396+ nargs = "*" ,
397+ default = None ,
398+ help = (
399+ "The visualizer backend(s) to use for the simulation."
400+ ' Can be one or more of: "rerun", "newton", "omniverse".'
401+ " Multiple visualizers can be specified: --visualizer rerun newton."
402+ " If not specified (default), NO visualizers will be created and headless mode is auto-enabled."
403+ " If no visualizer configs are defined in sim config, default configs will be created automatically."
404+ ),
405+ )
365406 # special flag for backwards compatibility
366407
367408 # Corresponding to the beginning of the function,
@@ -382,6 +423,7 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
382423 "device" : ([str ], "cuda:0" ),
383424 "experience" : ([str ], "" ),
384425 "rendering_mode" : ([str ], "balanced" ),
426+ "visualizer" : ([list , type (None )], None ),
385427 }
386428 """A dictionary of arguments added manually by the :meth:`AppLauncher.add_app_launcher_args` method.
387429
@@ -479,6 +521,7 @@ def _config_resolution(self, launcher_args: dict):
479521 # Handle core settings
480522 livestream_arg , livestream_env = self ._resolve_livestream_settings (launcher_args )
481523 self ._resolve_headless_settings (launcher_args , livestream_arg , livestream_env )
524+ self ._resolve_visualizer_settings (launcher_args )
482525 self ._resolve_camera_settings (launcher_args )
483526 self ._resolve_xr_settings (launcher_args )
484527 self ._resolve_viewport_settings (launcher_args )
@@ -570,9 +613,16 @@ def _resolve_headless_settings(self, launcher_args: dict, livestream_arg: int, l
570613 raise ValueError (
571614 f"Invalid value for environment variable `HEADLESS`: { headless_env } . Expected: { headless_valid_vals } ."
572615 )
616+
617+ # Check if visualizers are requested and if omniverse visualizer is among them
618+ visualizers = launcher_args .get ("visualizer" , AppLauncher ._APPLAUNCHER_CFG_INFO ["visualizer" ][1 ])
619+ visualizers_requested = visualizers is not None and len (visualizers ) > 0
620+ omniverse_visualizer_requested = visualizers_requested and "omniverse" in visualizers
621+
573622 # We allow headless kwarg to supersede HEADLESS envvar if headless_arg does not have the default value
574623 # Note: Headless is always true when livestreaming
575624 if headless_arg is True :
625+ # User explicitly requested headless mode
576626 self ._headless = headless_arg
577627 elif self ._livestream in {1 , 2 }:
578628 # we are always headless on the host machine
@@ -588,12 +638,56 @@ def _resolve_headless_settings(self, launcher_args: dict, livestream_arg: int, l
588638 f"[INFO][AppLauncher]: Environment variable `LIVESTREAM={ self ._livestream } ` has implicitly"
589639 f" overridden the environment variable `HEADLESS={ headless_env } ` to True."
590640 )
641+ elif omniverse_visualizer_requested and headless_env == 0 :
642+ # Omniverse visualizer requires non-headless mode (needs Isaac Sim viewport)
643+ self ._headless = False
644+ print (
645+ "[INFO][AppLauncher]: Omniverse visualizer requested via --visualizer flag. "
646+ "Launching in non-headless mode to enable viewport visualization."
647+ )
648+ elif visualizers_requested and not omniverse_visualizer_requested and headless_env == 0 :
649+ # Newton and Rerun don't need Isaac Sim viewport (they create their own windows or are web-based)
650+ self ._headless = True
651+ print (
652+ f"[INFO][AppLauncher]: Visualizer(s) { visualizers } requested. "
653+ "Enabling headless mode (Isaac Sim viewport disabled)."
654+ )
655+ elif not visualizers_requested and headless_env == 0 :
656+ # No visualizers requested and headless not explicitly set -> enable headless mode
657+ self ._headless = True
658+ print (
659+ "[INFO][AppLauncher]: No visualizers specified via --visualizer flag. "
660+ "Automatically enabling headless mode. Use --visualizer <type> to enable GUI."
661+ )
591662 else :
592663 # Headless needs to be a bool to be ingested by SimulationApp
593664 self ._headless = bool (headless_env )
594665 # Headless needs to be passed to the SimulationApp so we keep it here
595666 launcher_args ["headless" ] = self ._headless
596667
668+ def _resolve_visualizer_settings (self , launcher_args : dict ):
669+ """Resolve visualizer related settings."""
670+ # Get visualizer setting from launcher_args
671+ visualizers = launcher_args .get ("visualizer" , AppLauncher ._APPLAUNCHER_CFG_INFO ["visualizer" ][1 ])
672+
673+ # Validate visualizer names
674+ valid_visualizers = ["rerun" , "newton" , "omniverse" ]
675+ if visualizers is not None and len (visualizers ) > 0 :
676+ invalid = [v for v in visualizers if v not in valid_visualizers ]
677+ if invalid :
678+ raise ValueError (f"Invalid visualizer(s) specified: { invalid } . Valid options are: { valid_visualizers } " )
679+
680+ # Store visualizer setting for later use
681+ # Convert empty list to None for consistency
682+ self ._visualizer = visualizers if visualizers and len (visualizers ) > 0 else None
683+
684+ # Check if both headless and visualizer are specified
685+ if self ._headless and self ._visualizer is not None :
686+ print (
687+ f"[WARN][AppLauncher]: Both headless mode and visualizer(s) { self ._visualizer } were specified."
688+ " Headless mode is enabled, so no visualizers will be initialized."
689+ )
690+
597691 def _resolve_camera_settings (self , launcher_args : dict ):
598692 """Resolve camera related settings."""
599693 enable_cameras_env = int (os .environ .get ("ENABLE_CAMERAS" , 0 ))
@@ -831,6 +925,14 @@ def _load_extensions(self):
831925 # for example: the `Camera` sensor class
832926 carb_settings_iface .set_bool ("/isaaclab/render/rtx_sensors" , False )
833927
928+ # set carb setting to store the visualizer backend(s) specified via command-line
929+ # this allows SimulationContext to filter visualizers based on the --visualizer flag
930+ # Store as comma-separated string for carb settings compatibility
931+ if self ._visualizer is not None :
932+ carb_settings_iface .set_string ("/isaaclab/visualizer" , "," .join (self ._visualizer ))
933+ else :
934+ carb_settings_iface .set_string ("/isaaclab/visualizer" , "" )
935+
834936 # set fabric update flag to disable updating transforms when rendering is disabled
835937 carb_settings_iface .set_bool ("/physics/fabricUpdateTransformations" , self ._rendering_enabled ())
836938
0 commit comments