99
1010import argparse
1111import logging
12- from collections .abc import Callable , Generator
12+ from collections .abc import Callable , Generator , Iterable , Mapping
1313from contextlib import contextmanager
1414from typing import Any
1515
16+ from isaaclab .app .settings_manager import get_settings_manager
1617from isaaclab .physics .physics_manager_cfg import PhysicsCfg
1718from isaaclab .renderers .renderer_cfg import RendererCfg
1819from isaaclab .sensors .camera .camera_cfg import CameraCfg
@@ -31,6 +32,17 @@ def add_launcher_args(parser: argparse.ArgumentParser) -> None:
3132 AppLauncher .add_app_launcher_args (parser )
3233
3334
35+ def _iter_config_children (node ) -> Iterable [Any ]:
36+ if isinstance (node , Mapping ):
37+ return node .values ()
38+ if isinstance (node , (list , tuple )):
39+ return node
40+ try :
41+ return vars (node ).values ()
42+ except TypeError :
43+ return ()
44+
45+
3446def _scan_config (cfg , predicates : list [Callable [[Any ], bool ]]) -> list [bool ]:
3547 """Recursively walk *cfg* and evaluate each predicate on every node.
3648
@@ -53,11 +65,7 @@ def _visit(node):
5365 if not results [i ] and pred (node ):
5466 results [i ] = True
5567
56- try :
57- children = vars (node )
58- except TypeError :
59- return
60- for child in children .values ():
68+ for child in _iter_config_children (node ):
6169 if child is None or isinstance (child , (int , float , str , bool )):
6270 continue
6371 _visit (child )
@@ -66,6 +74,64 @@ def _visit(node):
6674 return results
6775
6876
77+ def _collect_renderer_types (cfg ) -> set [str ]:
78+ """Return renderer type names declared in the resolved runtime config."""
79+ renderer_types : set [str ] = set ()
80+ visited : set [int ] = set ()
81+
82+ def _visit (node ):
83+ node_id = id (node )
84+ if node_id in visited :
85+ return
86+ visited .add (node_id )
87+
88+ if isinstance (node , RendererCfg ):
89+ renderer_types .add (getattr (node , "renderer_type" , "default" ))
90+ return
91+
92+ for child in _iter_config_children (node ):
93+ if child is None or isinstance (child , (int , float , str , bool )):
94+ continue
95+ _visit (child )
96+
97+ _visit (cfg )
98+ return renderer_types
99+
100+
101+ def _compute_runtime_intent (env_cfg , launcher_args : argparse .Namespace | dict | None = None ) -> dict [str , Any ]:
102+ """Compute backend, renderer, and visualizer intent from the resolved env config."""
103+ is_kitless , has_kit_cameras , has_kit_physics , has_ovrtx_renderer , has_ovphysx_physics = _scan_config (
104+ env_cfg , [_is_kitless_physics , _is_kit_camera , _is_kit_physics , _is_ovrtx_renderer , _is_ovphysx_physics ]
105+ )
106+ renderer_types = _collect_renderer_types (env_cfg )
107+ visualizer_types = _get_visualizer_types (launcher_args )
108+ visualizer_intent = _compute_visualizer_intent (env_cfg )
109+ has_kit_visualizer = "kit" in visualizer_types or visualizer_intent .get ("has_kit_visualizer" , False )
110+ needs_kit = has_kit_cameras or not is_kitless or "kit" in visualizer_types
111+ return {
112+ "needs_kit" : needs_kit ,
113+ "has_kit_cameras" : has_kit_cameras ,
114+ "has_kit_physics" : has_kit_physics ,
115+ "has_kit_visualizer" : has_kit_visualizer ,
116+ "has_ovrtx_renderer" : has_ovrtx_renderer or "ovrtx" in renderer_types ,
117+ "has_ovphysx_physics" : has_ovphysx_physics ,
118+ "renderer_types" : renderer_types ,
119+ "visualizer_types" : visualizer_types ,
120+ "visualizer_intent" : visualizer_intent ,
121+ }
122+
123+
124+ def _sync_runtime_intent_settings (runtime_intent : dict [str , Any ]) -> None :
125+ """Persist launcher runtime intent for SimulationContext readers."""
126+ settings = get_settings_manager ()
127+ renderer_types = sorted (str (v ) for v in runtime_intent .get ("renderer_types" , set ()) if str (v ))
128+ settings .set_bool ("/isaaclab/runtime/needs_kit" , bool (runtime_intent .get ("needs_kit" , False )))
129+ settings .set_bool ("/isaaclab/runtime/has_kit_cameras" , bool (runtime_intent .get ("has_kit_cameras" , False )))
130+ settings .set_bool ("/isaaclab/runtime/has_kit_visualizer" , bool (runtime_intent .get ("has_kit_visualizer" , False )))
131+ settings .set_bool ("/isaaclab/runtime/has_ovrtx_renderer" , bool (runtime_intent .get ("has_ovrtx_renderer" , False )))
132+ settings .set_string ("/isaaclab/runtime/renderer_types" , " " .join (renderer_types ))
133+
134+
69135def _is_kitless_physics (node ) -> bool :
70136 """True when the node is a kitless physics config (Newton or OvPhysX)."""
71137 return isinstance (node , PhysicsCfg ) and type (node ).__name__ in ("NewtonCfg" , "OvPhysxCfg" )
@@ -165,12 +231,12 @@ def compute_kit_requirements(
165231 Returns:
166232 (needs_kit, has_kit_cameras, visualizer_types)
167233 """
168- is_kitless , has_kit_cameras = _scan_config (env_cfg , [ _is_kitless_physics , _is_kit_camera ] )
169- needs_kit = has_kit_cameras or not is_kitless
170- visualizer_types = _get_visualizer_types ( launcher_args )
171- if "kit" in visualizer_types :
172- needs_kit = True
173- return needs_kit , has_kit_cameras , visualizer_types
234+ runtime_intent = _compute_runtime_intent (env_cfg , launcher_args )
235+ return (
236+ bool ( runtime_intent [ "needs_kit" ]),
237+ bool ( runtime_intent [ "has_kit_cameras" ]),
238+ set ( runtime_intent [ "visualizer_types" ]),
239+ )
174240
175241
176242def validate_runtime_compatibility (
@@ -194,12 +260,14 @@ def validate_runtime_compatibility(
194260 ValueError: If the OVRTX renderer is combined with Kit-based physics or the
195261 Kit visualizer, or if OvPhysX physics is combined with the Kit visualizer.
196262 """
197- has_kit_physics , has_ovrtx_renderer , has_ovphysx_physics = _scan_config (
198- env_cfg , [_is_kit_physics , _is_ovrtx_renderer , _is_ovphysx_physics ]
199- )
200- visualizer_intent = _compute_visualizer_intent (env_cfg )
201- visualizer_types = _get_visualizer_types (launcher_args )
202- has_kit_visualizer = "kit" in visualizer_types or visualizer_intent .get ("has_kit_visualizer" , False )
263+ _validate_runtime_compatibility_from_intent (_compute_runtime_intent (env_cfg , launcher_args ))
264+
265+
266+ def _validate_runtime_compatibility_from_intent (runtime_intent : dict [str , Any ]) -> None :
267+ has_kit_physics = bool (runtime_intent ["has_kit_physics" ])
268+ has_ovrtx_renderer = bool (runtime_intent ["has_ovrtx_renderer" ])
269+ has_ovphysx_physics = bool (runtime_intent ["has_ovphysx_physics" ])
270+ has_kit_visualizer = bool (runtime_intent ["has_kit_visualizer" ])
203271
204272 if has_ovphysx_physics and has_kit_visualizer :
205273 raise ValueError (
@@ -319,23 +387,26 @@ def launch_simulation(
319387 # ovrtx and Kit ship the same RTX hydra libraries under conflicting USD namespaces;
320388 # loading both in the same process causes a dynamic-linker crash. Use
321389 # --visualizer newton instead, which is compatible with ovrtx presets.
322- early_visualizer_types = _get_visualizer_types (launcher_args )
323- if "kit" in early_visualizer_types :
324- has_ovrtx = _scan_config (
325- env_cfg , [lambda node : isinstance (node , RendererCfg ) and getattr (node , "renderer_type" , None ) == "ovrtx" ]
326- )[0 ]
327- if has_ovrtx :
328- raise ValueError (
329- "[launch_simulation] '--visualizer kit' is incompatible with 'ovrtx_renderer'. "
330- "Both Kit (Isaac Sim) and ovrtx ship conflicting RTX hydra libraries "
331- "(librtx.hydra.so, liblegacy.hydra.so) compiled against different USD namespaces, "
332- "which causes a dynamic-linker crash when loaded into the same process. "
333- "Use '--visualizer newton' instead, which is fully compatible with ovrtx presets."
334- )
390+ runtime_intent = _compute_runtime_intent (env_cfg , launcher_args )
391+ early_visualizer_types = set (runtime_intent ["visualizer_types" ])
392+ if "kit" in early_visualizer_types and runtime_intent ["has_ovrtx_renderer" ]:
393+ raise ValueError (
394+ "[launch_simulation] '--visualizer kit' is incompatible with 'ovrtx_renderer'. "
395+ "Both Kit (Isaac Sim) and ovrtx ship conflicting RTX hydra libraries "
396+ "(librtx.hydra.so, liblegacy.hydra.so) compiled against different USD namespaces, "
397+ "which causes a dynamic-linker crash when loaded into the same process. "
398+ "Use '--visualizer newton' instead, which is fully compatible with ovrtx presets."
399+ )
335400
336- validate_runtime_compatibility ( env_cfg , launcher_args )
401+ _validate_runtime_compatibility_from_intent ( runtime_intent )
337402 needs_kit , has_kit_cameras , visualizer_types = compute_kit_requirements (env_cfg , launcher_args )
338- visualizer_intent = _compute_visualizer_intent (env_cfg )
403+ visualizer_intent = runtime_intent ["visualizer_intent" ]
404+ runtime_intent ["needs_kit" ] = needs_kit
405+ runtime_intent ["has_kit_cameras" ] = has_kit_cameras
406+ runtime_intent ["has_kit_visualizer" ] = "kit" in visualizer_types or visualizer_intent .get (
407+ "has_kit_visualizer" , False
408+ )
409+ runtime_intent ["visualizer_types" ] = visualizer_types
339410 _set_visualizer_intent_on_launcher_args (launcher_args , visualizer_intent )
340411
341412 if needs_kit and has_kit_cameras :
@@ -421,7 +492,11 @@ def launch_simulation(
421492 if sim_cfg is not None and hasattr (app_launcher , "device" ):
422493 sim_cfg .device = app_launcher .device
423494 close_fn = app_launcher .app .close
424- elif visualizer_types or visualizer_explicit_none :
495+ _sync_runtime_intent_settings (runtime_intent )
496+ else :
497+ _sync_runtime_intent_settings (runtime_intent )
498+
499+ if not needs_kit and (visualizer_types or visualizer_explicit_none ):
425500 # Newton path without Kit: AppLauncher is skipped, so manually store the visualizer
426501 # selection in SettingsManager (works in standalone mode via plain dict) so that
427502 # SimulationContext._get_cli_visualizer_types() can find it.
0 commit comments