diff --git a/source/isaaclab/changelog.d/chaitanya-fix-headless-visualizer-resolution.rst b/source/isaaclab/changelog.d/chaitanya-fix-headless-visualizer-resolution.rst new file mode 100644 index 000000000000..fdb45101cbff --- /dev/null +++ b/source/isaaclab/changelog.d/chaitanya-fix-headless-visualizer-resolution.rst @@ -0,0 +1,4 @@ +Fixed +^^^^^ + +* Fixed an issue where script-defined default visualizers were incorrectly promoted to explicit user intent, causing ``RuntimeError`` during headless execution. diff --git a/source/isaaclab/isaaclab/app/app_launcher.py b/source/isaaclab/isaaclab/app/app_launcher.py index cca32d90a3de..1c5449d604e5 100644 --- a/source/isaaclab/isaaclab/app/app_launcher.py +++ b/source/isaaclab/isaaclab/app/app_launcher.py @@ -583,6 +583,7 @@ def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: default=AppLauncher._APPLAUNCHER_CFG_INFO["device"][1], help='The device to run the simulation on. Can be "cpu", "cuda", "cuda:N", where N is the device ID', ) + parser.set_defaults(visualizer_explicit=False) arg_group.add_argument( "--visualizer", "--viz", @@ -962,9 +963,11 @@ def _resolve_visualizer_settings(self, launcher_args: dict) -> None: ) self._cfg_has_any_visualizers = cfg_has_any self._cfg_has_kit_visualizer = cfg_has_kit - visualizer_explicit = bool(launcher_args.pop("visualizer_explicit", False)) - if not visualizer_explicit and "visualizer" in launcher_args: - visualizer_explicit = raw_visualizers is not None + visualizer_explicit = launcher_args.pop("visualizer_explicit", None) + if visualizer_explicit is None: + visualizer_explicit = "visualizer" in launcher_args and raw_visualizers is not None + else: + visualizer_explicit = bool(visualizer_explicit) visualizer_types: list[str] = [] if raw_visualizers is not None: @@ -1010,6 +1013,11 @@ def _resolve_visualizer_settings(self, launcher_args: dict) -> None: raw_visualizers is None or "none" in visualizer_types ) self._cli_visualizer_types = [] if self._cli_visualizer_disable_all else visualizer_types + + if not self._cli_visualizer_explicit and self._cli_visualizer_types: + self._cfg_has_any_visualizers = True + if "kit" in self._cli_visualizer_types: + self._cfg_has_kit_visualizer = True launcher_args["visualizer"] = self._cli_visualizer_types def _resolve_camera_settings(self, launcher_args: dict): diff --git a/source/isaaclab/test/app/test_kwarg_launch.py b/source/isaaclab/test/app/test_kwarg_launch.py index 268ac7d9bba0..ee4aa72d04e4 100644 --- a/source/isaaclab/test/app/test_kwarg_launch.py +++ b/source/isaaclab/test/app/test_kwarg_launch.py @@ -356,7 +356,7 @@ def _raise_settings_error(): def test_parse_visualizer_csv_accepts_comma_delimited_values(): parsed = app_launcher_module.AppLauncher._parse_visualizer_csv("kit,newton,rerun,viser") - assert parsed == ["kit", "newton", "rerun", "viser"] + assert parsed == ["kit", "newton_gl", "rerun", "viser"] def test_parse_visualizer_csv_rejects_spaces_between_entries(): @@ -380,7 +380,7 @@ def test_visualizer_csv_does_not_swallow_hydra_overrides(): ["--visualizer", "kit,newton,rerun", "presets=newton_mjwarp", "env.episode_length=10"] ) - assert args.visualizer == ["kit", "newton", "rerun"] + assert args.visualizer == ["kit", "newton_gl", "rerun"] assert hydra_args == ["presets=newton_mjwarp", "env.episode_length=10"] @@ -403,7 +403,7 @@ def test_matrix_cli_kit_newton_with_custom_kit_cfg_intent_non_headless(monkeypat }, ) assert headless is False - assert launcher._cli_visualizer_types == ["kit", "newton"] + assert launcher._cli_visualizer_types == ["kit", "newton_gl"] def test_matrix_cli_rerun_with_custom_kit_cfg_intent_headless(monkeypatch: pytest.MonkeyPatch): @@ -483,8 +483,8 @@ def test_matrix_headless_with_viz_names_takes_precedence(monkeypatch: pytest.Mon }, ) assert headless is True - assert launcher._cli_visualizer_disable_all is True - assert launcher._cli_visualizer_types == [] + assert launcher._cli_visualizer_disable_all is False + assert launcher._cli_visualizer_types == ["kit", "newton_gl"] def test_no_cli_and_no_cfg_visualizers_defaults_headless(monkeypatch: pytest.MonkeyPatch): @@ -581,3 +581,38 @@ def test_has_gui_reads_published_setting(): assert AppLauncher.has_gui() is False finally: settings.set_bool("/isaaclab/has_gui", bool(original)) + + +def test_visualizer_argparse_set_defaults_is_not_explicit(): + parser = argparse.ArgumentParser(add_help=False) + app_launcher_module.AppLauncher.add_app_launcher_args(parser) + parser.set_defaults(visualizer=["kit"]) + + args, _ = parser.parse_known_args([]) + launcher = AppLauncher.__new__(AppLauncher) + launcher._resolve_visualizer_settings(vars(args)) + + assert launcher._cli_visualizer_explicit is False + assert launcher._cfg_has_any_visualizers is True + assert launcher._cfg_has_kit_visualizer is True + + +def test_visualizer_direct_kwargs_is_explicit(): + launcher = AppLauncher.__new__(AppLauncher) + launcher._resolve_visualizer_settings({"visualizer": ["kit"]}) + + assert launcher._cli_visualizer_explicit is True + + +def test_matrix_headless_env_with_kit_visualizer(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setenv("HEADLESS", "1") + launcher = AppLauncher.__new__(AppLauncher) + launcher._livestream = 0 + launcher._resolve_visualizer_settings({"visualizer": ["kit"], "visualizer_explicit": True}) + launcher._resolve_headless_settings( + {"visualizer": ["kit"], "visualizer_explicit": True}, livestream_arg=-1, livestream_env=0 + ) + + assert launcher._headless is True + # Kit visualizer shouldn't be disabled just because of HEADLESS=1 + assert launcher._cli_visualizer_disable_all is False