@@ -49,57 +49,6 @@ def __call__(self, parser, namespace, values, option_string=None):
4949 setattr (namespace , f"{ self .dest } _explicit" , True )
5050
5151
52- def _parse_visualizer_csv (value : str ) -> list [str ]:
53- """Parse visualizer list from a single comma-delimited CLI token."""
54- valid = {"kit" , "newton" , "rerun" , "viser" , "none" }
55- token = (value or "" ).strip ()
56- if not token :
57- raise argparse .ArgumentTypeError (
58- "Invalid --visualizer value: empty string. Use a comma-separated list, e.g. --viz kit,newton."
59- )
60- if " " in token :
61- raise argparse .ArgumentTypeError (
62- "Invalid --visualizer value: spaces are not allowed. "
63- "Use a comma-separated list without spaces, e.g. --viz kit,newton,rerun,viser."
64- )
65-
66- names = [item .strip ().lower () for item in token .split ("," )]
67- if any (not name for name in names ):
68- raise argparse .ArgumentTypeError (
69- "Invalid --visualizer value: empty visualizer entry detected. "
70- "Use a comma-separated list without empty items."
71- )
72- invalid = [name for name in names if name not in valid ]
73- if invalid :
74- raise argparse .ArgumentTypeError (
75- f"Invalid --visualizer value(s): { ', ' .join (invalid )} . Valid options: { ', ' .join (sorted (valid ))} ."
76- )
77- # De-duplicate while preserving order.
78- return list (dict .fromkeys (names ))
79-
80-
81- def _normalize_visualizer_intent (intent : Any ) -> tuple [bool , bool ]:
82- """Normalize and validate upstream config visualizer intent payload.
83-
84- The expected schema is:
85- ``{"has_any_visualizers": bool, "has_kit_visualizer": bool}``.
86- """
87- if intent is None :
88- return False , False
89- if not isinstance (intent , dict ):
90- raise ValueError ("Invalid value for `visualizer_intent`: expected dict or None." )
91-
92- has_any = intent .get ("has_any_visualizers" , False )
93- has_kit = intent .get ("has_kit_visualizer" , False )
94- if not isinstance (has_any , bool ) or not isinstance (has_kit , bool ):
95- raise ValueError (
96- "Invalid `visualizer_intent` values: expected booleans for `has_any_visualizers` and `has_kit_visualizer`."
97- )
98- if has_kit and not has_any :
99- raise ValueError ("Invalid `visualizer_intent`: `has_kit_visualizer=True` requires `has_any_visualizers=True`." )
100- return has_any , has_kit
101-
102-
10352class ExplicitTrueAction (argparse .Action ):
10453 """Custom action to track explicit use of boolean flags."""
10554
@@ -133,6 +82,96 @@ class AppLauncher:
13382
13483 """
13584
85+ @staticmethod
86+ def sync_visualizer_cli_settings_to_carb (launcher_args : dict ) -> None :
87+ """Write visualizer CLI selection and ``--max_visible_envs`` to carb settings.
88+
89+ Callers may set ``visualizer_explicit`` / ``visualizer_disable_all`` when those values
90+ were resolved elsewhere (e.g. :class:`AppLauncher` strips flags from *launcher_args*).
91+ Otherwise ``disable_all`` is inferred from ``"none"`` in ``visualizer``.
92+
93+ Also used when Kit is skipped (see :mod:`isaaclab_tasks.utils.sim_launcher`).
94+ """
95+ visualizers = launcher_args .get ("visualizer" )
96+
97+ if "max_visible_envs" in launcher_args :
98+ v = launcher_args ["max_visible_envs" ]
99+ if v is not None and int (v ) < 0 :
100+ raise ValueError (f"Invalid value for --max_visible_envs: { v } . Expected non-negative int." )
101+
102+ cli_explicit = bool (launcher_args .get ("visualizer_explicit" , False ))
103+ if "visualizer_disable_all" in launcher_args :
104+ cli_disable_all = bool (launcher_args ["visualizer_disable_all" ])
105+ else :
106+ cli_disable_all = bool (cli_explicit ) and visualizers is not None and "none" in visualizers
107+
108+ with contextlib .suppress (Exception ):
109+ visualizer_str = " " .join (visualizers ) if visualizers else ""
110+ settings = get_settings_manager ()
111+ settings .set_string ("/isaaclab/visualizer/types" , visualizer_str )
112+ settings .set_bool ("/isaaclab/visualizer/explicit" , cli_explicit )
113+ settings .set_bool ("/isaaclab/visualizer/disable_all" , cli_disable_all )
114+
115+ # Sentinel: ``-1`` means ``--max_visible_envs`` was not passed (see ``SimulationContext``).
116+ if "max_visible_envs" in launcher_args :
117+ settings .set_int ("/isaaclab/visualizer/max_visible_envs" , int (launcher_args ["max_visible_envs" ]))
118+ else :
119+ settings .set_int ("/isaaclab/visualizer/max_visible_envs" , - 1 )
120+
121+ @staticmethod
122+ def _parse_visualizer_csv (value : str ) -> list [str ]:
123+ """Parse visualizer list from a single comma-delimited CLI token."""
124+ valid = {"kit" , "newton" , "rerun" , "viser" , "none" }
125+ token = (value or "" ).strip ()
126+ if not token :
127+ raise argparse .ArgumentTypeError (
128+ "Invalid --visualizer value: empty string. Use a comma-separated list, e.g. --viz kit,newton."
129+ )
130+ if " " in token :
131+ raise argparse .ArgumentTypeError (
132+ "Invalid --visualizer value: spaces are not allowed. "
133+ "Use a comma-separated list without spaces, e.g. --viz kit,newton,rerun,viser."
134+ )
135+
136+ names = [item .strip ().lower () for item in token .split ("," )]
137+ if any (not name for name in names ):
138+ raise argparse .ArgumentTypeError (
139+ "Invalid --visualizer value: empty visualizer entry detected. "
140+ "Use a comma-separated list without empty items."
141+ )
142+ invalid = [name for name in names if name not in valid ]
143+ if invalid :
144+ raise argparse .ArgumentTypeError (
145+ f"Invalid --visualizer value(s): { ', ' .join (invalid )} . Valid options: { ', ' .join (sorted (valid ))} ."
146+ )
147+ # De-duplicate while preserving order.
148+ return list (dict .fromkeys (names ))
149+
150+ @staticmethod
151+ def _normalize_visualizer_intent (intent : Any ) -> tuple [bool , bool ]:
152+ """Normalize and validate upstream config visualizer intent payload.
153+
154+ The expected schema is:
155+ ``{"has_any_visualizers": bool, "has_kit_visualizer": bool}``.
156+ """
157+ if intent is None :
158+ return False , False
159+ if not isinstance (intent , dict ):
160+ raise ValueError ("Invalid value for `visualizer_intent`: expected dict or None." )
161+
162+ has_any = intent .get ("has_any_visualizers" , False )
163+ has_kit = intent .get ("has_kit_visualizer" , False )
164+ if not isinstance (has_any , bool ) or not isinstance (has_kit , bool ):
165+ raise ValueError (
166+ "Invalid `visualizer_intent` values: expected booleans for `has_any_visualizers` and "
167+ "`has_kit_visualizer`."
168+ )
169+ if has_kit and not has_any :
170+ raise ValueError (
171+ "Invalid `visualizer_intent`: `has_kit_visualizer=True` requires `has_any_visualizers=True`."
172+ )
173+ return has_any , has_kit
174+
136175 def __init__ (self , launcher_args : argparse .Namespace | dict | None = None , ** kwargs ):
137176 """Create a `SimulationApp`_ instance based on the input settings.
138177
@@ -188,7 +227,6 @@ def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwa
188227 self ._livestream : Literal [0 , 1 , 2 ] # 0: Disabled, 1: WebRTC public, 2: WebRTC private
189228 self ._offscreen_render : bool # 0: Disabled, 1: Enabled
190229 self ._sim_experience_file : str # Experience file to load
191- self ._visualizer_max_worlds : int | None # Optional max worlds override for Newton-based visualizers
192230 self ._video_enabled : bool # Whether --video recording is enabled
193231
194232 # Exposed to train scripts
@@ -330,10 +368,9 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
330368 - Multiple visualizers can be specified as a comma-delimited list:
331369 ``--viz rerun,newton,viser``.
332370
333- * ``visualizer_max_worlds`` (int | None): Optional global override for the maximum number of worlds
334- rendered in Newton-based visualizers (newton, rerun, viser). If omitted, each visualizer uses its
335- config default.
336-
371+ * ``max_visible_envs`` (int | None): Optional global override for partial visualization by capping
372+ how many environments are shown in the visualizers.
373+ More partial visualization configuration fields are available in the ``VisualizerCfg`` class.
337374
338375 .. _`WebRTC`: https://docs.isaacsim.omniverse.nvidia.com/latest/installation/manual_livestream_clients.html#isaac-sim-short-webrtc-streaming-client
339376
@@ -414,7 +451,7 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
414451 arg_group .add_argument (
415452 "--visualizer" ,
416453 "--viz" ,
417- type = _parse_visualizer_csv ,
454+ type = AppLauncher . _parse_visualizer_csv ,
418455 action = ExplicitAction ,
419456 default = None ,
420457 help = "Visualizer backends to enable as CSV (e.g., kit,newton,rerun,viser)." ,
@@ -485,13 +522,10 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
485522 ),
486523 )
487524 arg_group .add_argument (
488- "--visualizer_max_worlds " ,
525+ "--max_visible_envs " ,
489526 type = int ,
490- default = AppLauncher ._APPLAUNCHER_CFG_INFO ["visualizer_max_worlds" ][1 ],
491- help = (
492- "Optional global max worlds override for Newton-based visualizers (newton/rerun/viser). "
493- "If omitted, visualizer config defaults are used."
494- ),
527+ default = argparse .SUPPRESS ,
528+ help = ("When set, caps the nums of envs shown in the launched visualizers." ),
495529 )
496530 # special flag for backwards compatibility
497531
@@ -513,7 +547,7 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None:
513547 "device" : ([str ], "cuda:0" ),
514548 "experience" : ([str ], "" ),
515549 "rendering_mode" : ([str ], "balanced" ),
516- "visualizer_max_worlds " : ([int , type (None )], None ),
550+ "max_visible_envs " : ([int , type (None )], None ),
517551 }
518552 """A dictionary of arguments added manually by the :meth:`AppLauncher.add_app_launcher_args` method.
519553
@@ -783,7 +817,9 @@ def _resolve_headless_settings(self, launcher_args: dict, livestream_arg: int, l
783817 def _resolve_visualizer_settings (self , launcher_args : dict ) -> None :
784818 """Resolve visualizer CLI semantics and normalize selection."""
785819 raw_visualizers = launcher_args .get ("visualizer" )
786- cfg_has_any , cfg_has_kit = _normalize_visualizer_intent (launcher_args .pop ("visualizer_intent" , None ))
820+ cfg_has_any , cfg_has_kit = AppLauncher ._normalize_visualizer_intent (
821+ launcher_args .pop ("visualizer_intent" , None )
822+ )
787823 self ._cfg_has_any_visualizers = cfg_has_any
788824 self ._cfg_has_kit_visualizer = cfg_has_kit
789825 visualizer_explicit = bool (launcher_args .pop ("visualizer_explicit" , False ))
@@ -793,7 +829,7 @@ def _resolve_visualizer_settings(self, launcher_args: dict) -> None:
793829 visualizer_types : list [str ] = []
794830 if raw_visualizers is not None :
795831 if isinstance (raw_visualizers , str ):
796- visualizer_types = _parse_visualizer_csv (raw_visualizers )
832+ visualizer_types = AppLauncher . _parse_visualizer_csv (raw_visualizers )
797833 else :
798834 visualizer_types = [str (v ).strip ().lower () for v in raw_visualizers if str (v ).strip ()]
799835
@@ -1152,28 +1188,14 @@ def _set_animation_recording_settings(self, launcher_args: dict) -> None:
11521188 settings .set_float ("/isaaclab/anim_recording/stop_time" , stop_time )
11531189
11541190 def _set_visualizer_settings (self , launcher_args : dict ) -> None :
1155- """Store visualizer selection and max-worlds override in settings."""
1156- visualizers = launcher_args .get ("visualizer" )
1157- visualizer_max_worlds = launcher_args .get ("visualizer_max_worlds" )
1158-
1159- if visualizer_max_worlds is not None and visualizer_max_worlds < 0 :
1160- raise ValueError (
1161- f"Invalid value for --visualizer_max_worlds: { visualizer_max_worlds } . Expected non-negative int."
1162- )
1163-
1164- with contextlib .suppress (Exception ):
1165- visualizer_str = " " .join (visualizers ) if visualizers else ""
1166- settings = get_settings_manager ()
1167- cli_visualizer_explicit = getattr (self , "_cli_visualizer_explicit" , False )
1168- cli_visualizer_disable_all = getattr (self , "_cli_visualizer_disable_all" , False )
1169- settings .set_string ("/isaaclab/visualizer/types" , visualizer_str )
1170- settings .set_bool ("/isaaclab/visualizer/explicit" , cli_visualizer_explicit )
1171- settings .set_bool ("/isaaclab/visualizer/disable_all" , cli_visualizer_disable_all )
1172- # Store as int setting where -1 means "use per-visualizer defaults".
1173- if visualizer_max_worlds is None :
1174- settings .set_int ("/isaaclab/visualizer/max_worlds" , - 1 )
1175- else :
1176- settings .set_int ("/isaaclab/visualizer/max_worlds" , int (visualizer_max_worlds ))
1191+ """Persist visualizer CLI flags and ``max_visible_envs`` override for :class:`SimulationContext`."""
1192+ AppLauncher .sync_visualizer_cli_settings_to_carb (
1193+ {
1194+ ** launcher_args ,
1195+ "visualizer_explicit" : getattr (self , "_cli_visualizer_explicit" , False ),
1196+ "visualizer_disable_all" : getattr (self , "_cli_visualizer_disable_all" , False ),
1197+ }
1198+ )
11771199
11781200 def _interrupt_signal_handle_callback (self , signal , frame ):
11791201 """Handle the interrupt signal from the keyboard."""
0 commit comments