From 046c74a295bf69cf3f074bf0dd82954cceb87108 Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sat, 22 Aug 2026 16:14:48 -0700 Subject: [PATCH 1/6] Remove post-Hydra preset resolution --- scripts/benchmarks/benchmark_cameras.py | 6 +- scripts/demos/h1_locomotion.py | 6 +- scripts/demos/heterogeneous_scene.py | 6 +- .../03_envs/policy_inference_in_usd.py | 6 +- .../tutorials/03_envs/run_cartpole_rl_env.py | 6 +- .../07_visualizers/run_video_recording.py | 13 +- ...ove-post-hydra-preset-resolution.major.rst | 10 ++ source/isaaclab/isaaclab/app/sim_launcher.py | 24 ++-- source/isaaclab/isaaclab/benchmark/capture.py | 130 +++--------------- .../rl_games/benchmark_play_rl_games.py | 3 +- .../rl_games/benchmark_train_rl_games.py | 3 +- .../backends/rsl_rl/benchmark_play_rsl_rl.py | 3 +- .../backends/rsl_rl/benchmark_train_rsl_rl.py | 3 +- .../backends/sb3/benchmark_play_sb3.py | 3 +- .../backends/sb3/benchmark_train_sb3.py | 3 +- .../backends/skrl/benchmark_play_skrl.py | 3 +- .../backends/skrl/benchmark_train_skrl.py | 3 +- .../isaaclab/benchmark/entrypoints/runtime.py | 3 +- .../isaaclab/benchmark/entrypoints/startup.py | 3 +- .../isaaclab/isaaclab/envs/direct_marl_env.py | 4 - .../isaaclab/isaaclab/envs/direct_rl_env.py | 4 - .../isaaclab/envs/leapp_deployment_env.py | 2 - .../isaaclab/envs/manager_based_env.py | 4 - .../isaaclab/sim/simulation_context.py | 7 +- source/isaaclab/isaaclab/utils/__init__.pyi | 3 +- source/isaaclab/isaaclab/utils/configclass.py | 43 ------ .../isaaclab/test/benchmark/test_capture.py | 39 ++---- ...ased_rl_env_obs_spaces_task_integration.py | 21 ++- .../misc/cartpole_training_smoke.py | 7 +- .../remove-post-hydra-preset-resolution.skip | 0 .../isaaclab_contrib/rl/rlinf/extension.py | 5 +- .../test/custom_coupling/test_env_cfg.py | 28 ++-- .../remove-post-hydra-preset-resolution.skip | 0 .../isaaclab_experimental/envs/frontend.py | 18 +-- .../test/envs/test_frontend_cfg_conversion.py | 27 ++-- .../remove-post-hydra-preset-resolution.skip | 0 .../test/test_curobo_planner_cube_stack.py | 7 +- .../test/test_curobo_planner_franka.py | 6 +- .../remove-post-hydra-preset-resolution.rst | 5 + .../isaaclab_rl/entrypoints/common.py | 112 ++++----------- .../test/test_entrypoints_common.py | 46 ++----- ...ove-post-hydra-preset-resolution.major.rst | 9 ++ .../cartpole/cartpole_direct_camera_env.py | 9 +- .../shadow_hand_direct_camera_env_cfg.py | 17 ++- .../isaaclab_tasks/utils/hydra.py | 26 +++- .../isaaclab_tasks/utils/parse_cfg.py | 13 +- .../core/test_shadow_hand_camera_presets.py | 6 +- .../test/core/test_video_recording.py | 14 +- .../remove-post-hydra-preset-resolution.skip | 0 .../test/visualizer_integration_utils.py | 50 ++----- tools/environ_docs.py | 6 +- 51 files changed, 232 insertions(+), 543 deletions(-) create mode 100644 source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst create mode 100644 source/isaaclab_contrib/changelog.d/remove-post-hydra-preset-resolution.skip create mode 100644 source/isaaclab_experimental/changelog.d/remove-post-hydra-preset-resolution.skip create mode 100644 source/isaaclab_mimic/changelog.d/remove-post-hydra-preset-resolution.skip create mode 100644 source/isaaclab_rl/changelog.d/remove-post-hydra-preset-resolution.rst create mode 100644 source/isaaclab_tasks/changelog.d/remove-post-hydra-preset-resolution.major.rst create mode 100644 source/isaaclab_visualizers/changelog.d/remove-post-hydra-preset-resolution.skip diff --git a/scripts/benchmarks/benchmark_cameras.py b/scripts/benchmarks/benchmark_cameras.py index 29138ff08b16..c0c54310c420 100644 --- a/scripts/benchmarks/benchmark_cameras.py +++ b/scripts/benchmarks/benchmark_cameras.py @@ -271,7 +271,7 @@ ) from isaaclab.utils.math import orthogonalize_perspective_depth, unproject_depth -from isaaclab_tasks.utils import load_cfg_from_registry +from isaaclab_tasks.utils import parse_env_cfg """ Camera Creation @@ -527,9 +527,7 @@ def inject_cameras_into_task( num_cameras_per_env: int = 1, ) -> gym.Env: """Loads the task, sticks cameras into the config, and creates the environment.""" - cfg = load_cfg_from_registry(task, "env_cfg_entry_point") - cfg.sim.device = args_cli.device - cfg.sim.use_fabric = args_cli.use_fabric + cfg = parse_env_cfg(task, device=args_cli.device, use_fabric=args_cli.use_fabric) scene_cfg = cfg.scene num_envs = int(num_cams / num_cameras_per_env) diff --git a/scripts/demos/h1_locomotion.py b/scripts/demos/h1_locomotion.py index cdd6e2684a70..d7c235a15f9c 100644 --- a/scripts/demos/h1_locomotion.py +++ b/scripts/demos/h1_locomotion.py @@ -65,8 +65,7 @@ get_published_pretrained_checkpoint, ) -from isaaclab_tasks.core.velocity.config.h1.rough_env_cfg import H1RoughEnvCfg -from isaaclab_tasks.utils import resolve_presets +from isaaclab_tasks.utils import resolve_task_config TASK = "Isaac-Velocity-Rough-H1" RL_LIBRARY = "rsl_rl" @@ -93,8 +92,7 @@ def __init__(self): agent_cfg: RslRlOnPolicyRunnerCfg = cli_args.parse_rsl_rl_cfg(TASK, args_cli) agent_cfg = handle_deprecated_rsl_rl_cfg(agent_cfg, metadata.version("rsl-rl-lib")) # create envionrment - env_cfg = resolve_presets(H1RoughEnvCfg(), selected=(args_cli.physics,)) - env_cfg.play_mode() + env_cfg, _ = resolve_task_config(TASK, "", play_mode=True, overrides=(f"physics={args_cli.physics}",)) env_cfg.scene.num_envs = 25 env_cfg.episode_length_s = 1000000 env_cfg.curriculum = None diff --git a/scripts/demos/heterogeneous_scene.py b/scripts/demos/heterogeneous_scene.py index 39ef207d846b..7db27a73d6a5 100644 --- a/scripts/demos/heterogeneous_scene.py +++ b/scripts/demos/heterogeneous_scene.py @@ -57,8 +57,7 @@ from isaaclab.scene import InteractiveSceneCfg from isaaclab.scene import add as scene_add -from isaaclab_tasks.utils.hydra import resolve_presets -from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry +from isaaclab_tasks.utils import resolve_task_config # Tasks composed by default. The selection criterion is simple: every listed # scene is a PhysX task whose floor is a single flat plane at height zero, so @@ -103,8 +102,7 @@ def _load_task_scenes() -> tuple[list[str], list[InteractiveSceneCfg]]: raise ValueError("Select at least two task scenes.") scene_cfgs = [] for task_id in task_ids: - # resolve preset placeholders (e.g. object-set choices) to their defaults - env_cfg = resolve_presets(load_cfg_from_registry(task_id, "env_cfg_entry_point")) + env_cfg, _ = resolve_task_config(task_id, "", overrides=(f"physics={args_cli.physics}", *hydra_args)) scene_cfgs.append(env_cfg.scene) return task_ids, scene_cfgs diff --git a/scripts/tutorials/03_envs/policy_inference_in_usd.py b/scripts/tutorials/03_envs/policy_inference_in_usd.py index 337959bf5005..b6bf303abfbd 100644 --- a/scripts/tutorials/03_envs/policy_inference_in_usd.py +++ b/scripts/tutorials/03_envs/policy_inference_in_usd.py @@ -45,7 +45,7 @@ from isaaclab.terrains import TerrainImporterCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, read_file -from isaaclab_tasks.core.velocity.config.h1.rough_env_cfg import H1RoughEnvCfg +from isaaclab_tasks.utils import parse_env_cfg def main(): @@ -56,16 +56,14 @@ def main(): policy = torch.jit.load(file, map_location=args_cli.device) # setup environment - env_cfg = H1RoughEnvCfg() + env_cfg = parse_env_cfg("Isaac-Velocity-Rough-H1", device=args_cli.device, num_envs=1) env_cfg.play_mode() - env_cfg.scene.num_envs = 1 env_cfg.curriculum = None env_cfg.scene.terrain = TerrainImporterCfg( prim_path="/World/ground", terrain_type="usd", usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Simple_Warehouse/warehouse.usd", ) - env_cfg.sim.device = args_cli.device if args_cli.device == "cpu": env_cfg.sim.use_fabric = False diff --git a/scripts/tutorials/03_envs/run_cartpole_rl_env.py b/scripts/tutorials/03_envs/run_cartpole_rl_env.py index 7d2869a9f65c..a36fe2452a73 100644 --- a/scripts/tutorials/03_envs/run_cartpole_rl_env.py +++ b/scripts/tutorials/03_envs/run_cartpole_rl_env.py @@ -39,15 +39,13 @@ from isaaclab.envs import ManagerBasedRLEnv -from isaaclab_tasks.core.cartpole.cartpole_manager_env_cfg import CartpoleEnvCfg +from isaaclab_tasks.utils import parse_env_cfg def main(): """Main function.""" # create environment configuration - env_cfg = CartpoleEnvCfg() - env_cfg.scene.num_envs = args_cli.num_envs - env_cfg.sim.device = args_cli.device + env_cfg = parse_env_cfg("Isaac-Cartpole", device=args_cli.device, num_envs=args_cli.num_envs) # setup RL environment env = ManagerBasedRLEnv(cfg=env_cfg) diff --git a/scripts/tutorials/07_visualizers/run_video_recording.py b/scripts/tutorials/07_visualizers/run_video_recording.py index dfacdd71dbf1..dafde9683de9 100644 --- a/scripts/tutorials/07_visualizers/run_video_recording.py +++ b/scripts/tutorials/07_visualizers/run_video_recording.py @@ -56,7 +56,7 @@ from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.envs.utils.video_recorder_cfg import VideoRecorderCfg -from isaaclab_tasks.utils import setup_preset_cli +from isaaclab_tasks.utils import resolve_task_config, setup_preset_cli # --------------------------------------------------------------------------- # Constants @@ -87,11 +87,7 @@ def _output_dir(example: int) -> str: def _shadow_env_cfg(num_envs: int, env_spacing: float = _SHADOW_ENV_SPACING): """Build a base Shadow Hand camera env cfg shared by all examples.""" - from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_camera_env_cfg import ShadowHandCameraEnvCfg - - env_cfg = ShadowHandCameraEnvCfg() - env_cfg.tiled_camera = env_cfg.tiled_camera.rgb - env_cfg.tiled_camera.renderer_cfg = env_cfg.tiled_camera.renderer_cfg.default + env_cfg, _ = resolve_task_config(_TASK_SHADOW, "", overrides=(*sys.argv[1:], "env.tiled_camera=rgb")) env_cfg.tiled_camera.height = 256 env_cfg.tiled_camera.width = 256 env_cfg.scene.num_envs = num_envs @@ -109,8 +105,6 @@ def _build_env_cfg_example_1(num_envs: int): from isaaclab_visualizers.kit import KitVisualizerCfg env_cfg = _shadow_env_cfg(num_envs) - env_cfg.sim.physics = env_cfg.sim.physics.default - env_cfg.sim.visualizer_cfgs = [KitVisualizerCfg(eye=_SHADOW_EYE, lookat=_SHADOW_LOOKAT)] out = _output_dir(1) @@ -130,7 +124,6 @@ def _build_env_cfg_example_1(num_envs: int): def _build_env_cfg_example_2(num_envs: int): """Shadow Hand + headless: scene tiled-camera sensor clip only.""" env_cfg = _shadow_env_cfg(num_envs, env_spacing=2.0) - env_cfg.sim.physics = env_cfg.sim.physics.default env_cfg.sim.visualizer_cfgs = [] # no interactive visualizer out = _output_dir(2) @@ -158,8 +151,6 @@ def _build_env_cfg_example_3(num_envs: int): from isaaclab_visualizers.newton import NewtonGLVisualizerCfg env_cfg = _shadow_env_cfg(num_envs) - env_cfg.sim.physics = env_cfg.sim.physics.default - kit_cfg = KitVisualizerCfg( eye=_SHADOW_EYE, lookat=_SHADOW_LOOKAT, diff --git a/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst b/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst new file mode 100644 index 000000000000..93d399c93269 --- /dev/null +++ b/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst @@ -0,0 +1,10 @@ +Removed +^^^^^^^ + +* **Breaking:** Removed late task-preset resolution from environment construction and the + :func:`isaaclab.utils.resolve_cfg_presets` helper. Compose registered tasks with + :func:`isaaclab_tasks.utils.resolve_task_config` or :func:`isaaclab_tasks.utils.parse_env_cfg` + before constructing an environment. +* **Breaking:** Replaced ``run_config_from_presets`` with ``run_config_from_env_cfg`` in benchmark + capture. Pass the concrete composed environment configuration instead of inferring backends from + selector strings. diff --git a/source/isaaclab/isaaclab/app/sim_launcher.py b/source/isaaclab/isaaclab/app/sim_launcher.py index 8f0c3fbd0096..89d3a1dc2754 100644 --- a/source/isaaclab/isaaclab/app/sim_launcher.py +++ b/source/isaaclab/isaaclab/app/sim_launcher.py @@ -114,14 +114,11 @@ def _is_kit_camera(node) -> bool: # ``auto_rtx`` is resolved after the initial scan once physics and # visualizer intent are known; ie. it may become OVRTX for a kitless run. return False - if isinstance(renderer_cfg, RendererCfg): - return renderer_cfg.renderer_type in ("default", "isaac_rtx") - # PresetCfg renderers (e.g. MultiBackendRendererCfg) are resolved during - # environment construction once the physics backend is known; assume they - # match the backend, so not necessarily Kit. - from isaaclab_tasks.utils import PresetCfg - - return not isinstance(renderer_cfg, PresetCfg) + if not isinstance(renderer_cfg, RendererCfg): + raise TypeError( + f"CameraCfg.renderer_cfg must be a concrete RendererCfg or None, got {type(renderer_cfg).__name__}." + ) + return renderer_cfg.renderer_type in ("default", "isaac_rtx") """ @@ -218,7 +215,7 @@ class Scan: """Signals gathered from one walk of the config tree (see :func:`scan`). Every field starts as a plain snapshot computed during that single walk. - Automatic PhysX preset selections and RTX placeholders are also recorded so + Automatic PhysX configurations and RTX placeholders are also recorded so launch-time resolution can update the physics- and renderer-related fields without traversing the config tree again. ``needs_kit`` is the headline launch decision after automatic selections are resolved: a Kit-renderer camera or Isaac @@ -424,11 +421,11 @@ def _validate_runtime(scan: Scan, kit_sources: tuple[str, ...]) -> None: "\n" "To fix this, pick one of the following supported combinations:\n" " * Keep OvPhysX physics and switch to a kitless renderer/visualizer:\n" - " presets=ovphysx,ovrtx\n" + " use `OvPhysxCfg` with `OVRTXRendererCfg`\n" " (and use `--visualizer newton`, `--visualizer rerun`, or `--visualizer viser`, or omit\n" " the visualizer argument for headless execution.)\n" " * Keep Isaac Sim / Kit and switch to a Kit-compatible physics backend:\n" - " presets=isaacsim_physx,isaacsim_rtx\n" + " use `PhysxCfg` with `IsaacRtxRendererCfg`\n" ) if not scan.has_ovrtx or not kit_sources: @@ -441,11 +438,10 @@ def _validate_runtime(scan: Scan, kit_sources: tuple[str, ...]) -> None: "\n" "To fix this, pick one of the following supported combinations:\n" " * Keep Isaac Sim / Kit and switch the renderer:\n" - " presets=isaacsim_rtx\n" - " (uses `IsaacRtxRendererCfg`, the Kit-compatible renderer.)\n" + " use `IsaacRtxRendererCfg`, the Kit-compatible renderer\n" " * Keep the OVRTX renderer and switch to a kitless physics backend\n" " (and avoid `--visualizer kit`):\n" - " presets=newton_mjwarp,ovrtx\n" + " use `NewtonCfg` or `OvPhysxCfg` with `OVRTXRendererCfg`\n" ) diff --git a/source/isaaclab/isaaclab/benchmark/capture.py b/source/isaaclab/isaaclab/benchmark/capture.py index ae9b1cb4d0b0..2c9781141e72 100644 --- a/source/isaaclab/isaaclab/benchmark/capture.py +++ b/source/isaaclab/isaaclab/benchmark/capture.py @@ -12,24 +12,21 @@ :class:`~isaaclab.benchmark.schema.Resources`. Import-time dependencies stay light: no torch, isaacsim, or RL-library -imports. Preset metadata is imported lazily when run_config_from_presets() -needs it. The benchmark object is accepted at call time; its recorder classes +imports. The benchmark object is accepted at call time; its recorder classes are never imported here. """ from __future__ import annotations import socket -from collections.abc import Sequence from datetime import datetime, timezone -from typing import Any, get_args +from typing import Any from isaaclab.benchmark.schema import ( GpuDeviceInfo, GpuResources, Hardware, MeanStd, - PhysicsBackend, Resources, RunConfig, Versions, @@ -97,7 +94,7 @@ def synth_run_id( Args: framework: RL framework name, or ``None`` for non-learning runs (substituted with ``"runtime"``). - physics_backend: Physics backend preset string. + physics_backend: Physics backend name. task: Gym task id. seed: Environment/agent seed. stamp: Timestamp string (e.g. ``"20260612-150000"``). @@ -231,38 +228,8 @@ def capture_hardware(bm: Any) -> Hardware: ) -def _preset_target_metadata() -> tuple[str, str, str, dict[str, str]]: - """Return preset selector labels and physics aliases from the preset CLI layer.""" - from isaaclab_tasks.utils.preset_target import PresetTarget - - return ( - PresetTarget.PHYSICS.value, - PresetTarget.RENDERER.value, - PresetTarget.DOMAIN.value, - dict(PresetTarget.PHYSICS.legacy_aliases), - ) - - -def _physics_backend_names() -> set[str]: - """Return physics backend names accepted by the benchmark schema.""" - return set(get_args(PhysicsBackend)) - - -def _rendering_backend_by_preset() -> dict[str, str]: - """Return renderer preset names mapped to benchmark rendering backend names.""" - try: - from isaaclab_tasks.utils.hydra import _preset_fields - from isaaclab_tasks.utils.presets import MultiBackendRendererCfg - except ImportError: - return {} - - return { - name: name.removesuffix("_renderer") for name in _preset_fields(MultiBackendRendererCfg()) if name != "default" - } - - -def _backend_defaults_from_env_cfg(env_cfg: object) -> tuple[str | None, str | None]: - """Return active backend names from a resolved environment configuration.""" +def _backends_from_env_cfg(env_cfg: object) -> tuple[str | None, str | None]: + """Return active backend names from a concrete environment configuration.""" physics_cfg = getattr(getattr(env_cfg, "sim", None), "physics", None) physics_descriptor = ( f"{type(physics_cfg).__module__}.{type(physics_cfg).__name__} {getattr(physics_cfg, 'class_type', '')}" @@ -308,91 +275,26 @@ def _backend_defaults_from_env_cfg(env_cfg: object) -> tuple[str | None, str | N return physics, rendering -def _expand_preset_tokens(tokens: Sequence[str]) -> list[tuple[str | None, str]]: - """Expand Hydra-style preset tokens into ``(selector, value)`` pairs.""" - physics_label, renderer_label, domain_label, _ = _preset_target_metadata() - expanded: list[tuple[str | None, str]] = [] - for token in tokens: - selector, has_value, raw_value = token.partition("=") - if not has_value: - value = token.strip() - if value: - expanded.append((None, value)) - continue - - selector = selector.strip() - if selector == domain_label: - expanded.extend((selector, value) for value in (v.strip() for v in raw_value.split(",")) if value) - elif selector in (physics_label, renderer_label): - value = raw_value.strip() - if value: - expanded.append((selector, value)) - else: - value = token.strip() - if value: - expanded.append((None, value)) - return expanded - - -def run_config_from_presets(tokens: Sequence[str], *, env_cfg: object | None = None) -> RunConfig: - """Build a :class:`~isaaclab.benchmark.RunConfig` from presets and resolved task config. - - Picks backend defaults from ``env_cfg`` when provided, then applies recognised - preset tokens. Without a resolved config, physics defaults to ``"physx"`` and - rendering to ``"none"``. Accepts bare preset names as well as Hydra-style - ``physics=...``, ``renderer=...``, and ``presets=...`` tokens. +def run_config_from_env_cfg(env_cfg: object) -> RunConfig: + """Build a :class:`~isaaclab.benchmark.RunConfig` from a concrete task config. Args: - tokens: Active preset tokens (e.g. ``["newton_mjwarp", "rgb"]``). - env_cfg: Optional resolved task environment configuration. Its active physics - and renderer configurations take precedence over token inference. + env_cfg: Concrete task environment configuration. Returns: Populated :class:`~isaaclab.benchmark.RunConfig`. - """ - if not tokens and env_cfg is None: - return RunConfig(physics_backend="physx", rendering_backend="none", presets=[]) - - physics = "physx" - rendering = "none" - expanded_tokens = _expand_preset_tokens(tokens) - physics_label, renderer_label, _, physics_aliases = _preset_target_metadata() - physics_backends = _physics_backend_names() - rendering_backends: dict[str, str] | None = None - - def rendering_backend_for(preset_name: str) -> str | None: - nonlocal rendering_backends - if rendering_backends is None: - rendering_backends = _rendering_backend_by_preset() - if preset_name in rendering_backends: - return rendering_backends[preset_name] - if preset_name.endswith("_renderer"): - return preset_name.removesuffix("_renderer") - return None - for selector, token in expanded_tokens: - if selector == physics_label: - physics = physics_aliases.get(token, token) - elif selector == renderer_label: - rendering = rendering_backend_for(token) or token - else: - canonical = physics_aliases.get(token, token) - if canonical in physics_backends: - physics = canonical - elif token.endswith("_renderer"): - renderer = rendering_backend_for(token) - if renderer is not None: - rendering = renderer - - if env_cfg is not None: - active_physics, active_rendering = _backend_defaults_from_env_cfg(env_cfg) - physics = active_physics or physics - rendering = active_rendering or rendering + Raises: + ValueError: If the config does not contain a supported concrete physics backend. + """ + physics, rendering = _backends_from_env_cfg(env_cfg) + if physics is None: + physics_cfg = getattr(getattr(env_cfg, "sim", None), "physics", None) + raise ValueError(f"Unsupported concrete physics config: {type(physics_cfg).__name__}.") return RunConfig( physics_backend=physics, - rendering_backend=rendering, - presets=[token for _, token in expanded_tokens], + rendering_backend=rendering or "none", ) diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_play_rl_games.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_play_rl_games.py index d4e7e1cfb425..e8c295b61bcf 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_play_rl_games.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_play_rl_games.py @@ -160,7 +160,7 @@ def run(argv: list[str]) -> BenchmarkResult: else: resume_path = _common.resolve_play_checkpoint(args_cli.checkpoint, "rl_games", args_cli.task, env_cfg) - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -181,7 +181,6 @@ def run(argv: list[str]) -> BenchmarkResult: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, ] }, ) diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_train_rl_games.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_train_rl_games.py index 7e7cfbb22e09..b6dd72dd4ad0 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_train_rl_games.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rl_games/benchmark_train_rl_games.py @@ -209,7 +209,7 @@ def run(argv: list[str]) -> BenchmarkResult | None: horizon_length = agent_cfg["params"]["config"].get("horizon_length", 16) reported_num_envs, _ = distributed.global_work(env_cfg.scene.num_envs, horizon_length) - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -231,7 +231,6 @@ def run(argv: list[str]) -> BenchmarkResult | None: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, {"name": "world_size", "data": distributed.world_size}, ] }, diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_play_rsl_rl.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_play_rsl_rl.py index ae13a5c9e616..52e64262abb4 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_play_rsl_rl.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_play_rsl_rl.py @@ -157,7 +157,7 @@ def run(argv: list[str]) -> BenchmarkResult: else: resume_path = _common.resolve_play_checkpoint(args.checkpoint, "rsl_rl", args.task, env_cfg) - cfg = capture.run_config_from_presets(remaining, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -178,7 +178,6 @@ def run(argv: list[str]) -> BenchmarkResult: "data": ("serialized_synchronized" if args.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, ] }, ) diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_train_rsl_rl.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_train_rsl_rl.py index f7c79099095d..cec0ce323d98 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_train_rsl_rl.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/rsl_rl/benchmark_train_rsl_rl.py @@ -197,7 +197,7 @@ def run(argv: list[str]) -> BenchmarkResult | None: env_cfg.seed = agent_cfg.seed reported_num_envs, _ = distributed.global_work(env_cfg.scene.num_envs, agent_cfg.num_steps_per_env) - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -219,7 +219,6 @@ def run(argv: list[str]) -> BenchmarkResult | None: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, {"name": "world_size", "data": distributed.world_size}, ] }, diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_play_sb3.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_play_sb3.py index 7c83a2a05fb4..7f3aa1e6f171 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_play_sb3.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_play_sb3.py @@ -160,7 +160,7 @@ def run(argv: list[str]) -> BenchmarkResult: else: resume_path = _common.resolve_play_checkpoint(args_cli.checkpoint, "sb3", args_cli.task, env_cfg) - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -181,7 +181,6 @@ def run(argv: list[str]) -> BenchmarkResult: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, ] }, ) diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_train_sb3.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_train_sb3.py index d5c76396e826..a1a1a80d14cb 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_train_sb3.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/sb3/benchmark_train_sb3.py @@ -246,7 +246,7 @@ def run(argv: list[str]) -> BenchmarkResult: steps_per_iteration = env_cfg.scene.num_envs * n_steps_cfg resolved_max_iterations = (int(agent_cfg["n_timesteps"]) + steps_per_iteration - 1) // steps_per_iteration - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -268,7 +268,6 @@ def run(argv: list[str]) -> BenchmarkResult: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, ] }, ) diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_play_skrl.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_play_skrl.py index 9ffcfcd06670..697f5e4a5743 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_play_skrl.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_play_skrl.py @@ -188,7 +188,7 @@ def run(argv: list[str]) -> BenchmarkResult: else: resume_path = _common.resolve_play_checkpoint(args_cli.checkpoint, "skrl", args_cli.task, env_cfg) - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -210,7 +210,6 @@ def run(argv: list[str]) -> BenchmarkResult: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, ] }, ) diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_train_skrl.py b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_train_skrl.py index 00b5e695c118..a6c09f1dbe39 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_train_skrl.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/backends/skrl/benchmark_train_skrl.py @@ -328,7 +328,7 @@ def run(argv: list[str]) -> BenchmarkResult | None: }, ) - cfg = capture.run_config_from_presets(remaining_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) formatter_types = [value.strip() for value in args_cli.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] @@ -351,7 +351,6 @@ def run(argv: list[str]) -> BenchmarkResult | None: "data": ("serialized_synchronized" if args_cli.measure_sync_step else "host_return"), }, {"name": "environment_step_warmup_steps", "data": args_cli.warmup_steps}, - {"name": "presets", "data": ",".join(cfg.presets)}, {"name": "world_size", "data": distributed.world_size}, ] }, diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/runtime.py b/source/isaaclab/isaaclab/benchmark/entrypoints/runtime.py index 4de2b3138d5c..840b3b95317b 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/runtime.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/runtime.py @@ -149,7 +149,7 @@ def run(argv: list[str]) -> BenchmarkResult | None: formatter_types = [value.strip() for value in args.benchmark_formatter.split(",") if value.strip()] formatter_types = formatter_types or ["omniperf"] - cfg = capture.run_config_from_presets(remaining, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) benchmark = BaseIsaacLabBenchmark( benchmark_name="benchmark_runtime", @@ -168,7 +168,6 @@ def run(argv: list[str]) -> BenchmarkResult | None: "name": "environment_step_measurement_mode", "data": ("serialized_synchronized" if args.measure_sync_step else "host_return"), }, - {"name": "presets", "data": ",".join(cfg.presets)}, {"name": "world_size", "data": distributed.world_size}, ] }, diff --git a/source/isaaclab/isaaclab/benchmark/entrypoints/startup.py b/source/isaaclab/isaaclab/benchmark/entrypoints/startup.py index a0c30db1fae8..49a611afe511 100644 --- a/source/isaaclab/isaaclab/benchmark/entrypoints/startup.py +++ b/source/isaaclab/isaaclab/benchmark/entrypoints/startup.py @@ -328,7 +328,7 @@ def run(argv: list[str]) -> BenchmarkResult | None: ], ) - cfg = capture.run_config_from_presets(hydra_args, env_cfg=env_cfg) + cfg = capture.run_config_from_env_cfg(env_cfg) stamp = end_utc.translate(str.maketrans("", "", ":-"))[:15] seed = args.seed if args.seed is not None else 0 run_id = capture.synth_run_id(None, cfg.physics_backend, args.task, seed, stamp) @@ -356,7 +356,6 @@ def run(argv: list[str]) -> BenchmarkResult | None: {"name": "seed", "data": args.seed}, {"name": "num_envs", "data": args.num_envs}, {"name": "top_n", "data": args.top_n}, - {"name": "presets", "data": ",".join(cfg.presets)}, {"name": "world_size", "data": distributed.world_size}, ] }, diff --git a/source/isaaclab/isaaclab/envs/direct_marl_env.py b/source/isaaclab/isaaclab/envs/direct_marl_env.py index 1523591d1e8d..c4cd46cf423c 100644 --- a/source/isaaclab/isaaclab/envs/direct_marl_env.py +++ b/source/isaaclab/isaaclab/envs/direct_marl_env.py @@ -22,7 +22,6 @@ from isaaclab.scene import InteractiveScene from isaaclab.sim import SimulationContext from isaaclab.sim.utils.stage import use_stage -from isaaclab.utils.configclass import resolve_cfg_presets from isaaclab.utils.noise import NoiseModel from isaaclab.utils.seed import configure_seed from isaaclab.utils.timer import Timer @@ -81,9 +80,6 @@ def __init__(self, cfg: DirectMARLEnvCfg, render_mode: str | None = None, **kwar # check that the config is valid cfg.validate() - # Resolve any preset-wrapper fields (PresetCfg subclasses or old-style ``presets`` dicts) - # to their default variant so that managers and scene builders see concrete cfg objects. - resolve_cfg_presets(cfg) # store inputs to class self.cfg = cfg # store the render mode diff --git a/source/isaaclab/isaaclab/envs/direct_rl_env.py b/source/isaaclab/isaaclab/envs/direct_rl_env.py index f139522b5eb1..92277efedb9c 100644 --- a/source/isaaclab/isaaclab/envs/direct_rl_env.py +++ b/source/isaaclab/isaaclab/envs/direct_rl_env.py @@ -23,7 +23,6 @@ from isaaclab.scene import InteractiveScene from isaaclab.sim import SimulationContext from isaaclab.sim.utils.stage import use_stage -from isaaclab.utils.configclass import resolve_cfg_presets from isaaclab.utils.noise import NoiseModel from isaaclab.utils.seed import configure_seed from isaaclab.utils.timer import Timer @@ -87,9 +86,6 @@ def __init__(self, cfg: DirectRLEnvCfg, render_mode: str | None = None, **kwargs # check that the config is valid cfg.validate() - # Resolve any preset-wrapper fields to their default variant so that downstream - # scene/physics setup receives concrete cfg objects rather than multi-backend selectors. - resolve_cfg_presets(cfg) # store inputs to class self.cfg = cfg # store the render mode diff --git a/source/isaaclab/isaaclab/envs/leapp_deployment_env.py b/source/isaaclab/isaaclab/envs/leapp_deployment_env.py index e63e4d0704d8..66d6eb2ae82d 100644 --- a/source/isaaclab/isaaclab/envs/leapp_deployment_env.py +++ b/source/isaaclab/isaaclab/envs/leapp_deployment_env.py @@ -31,7 +31,6 @@ from isaaclab.scene import InteractiveScene from isaaclab.sim import SimulationContext from isaaclab.sim.utils.stage import use_stage -from isaaclab.utils.configclass import resolve_cfg_presets logger = logging.getLogger(__name__) @@ -167,7 +166,6 @@ def __init__(self, cfg: Any, leapp_yaml_path: str): cfg.scene.num_envs = 1 cfg.validate() - resolve_cfg_presets(cfg) self.cfg = cfg self._is_closed = False self._leapp_yaml_path = leapp_yaml_path diff --git a/source/isaaclab/isaaclab/envs/manager_based_env.py b/source/isaaclab/isaaclab/envs/manager_based_env.py index 6c475adee681..e427b7d374be 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_env.py @@ -19,7 +19,6 @@ from isaaclab.scene import InteractiveScene from isaaclab.sim import SimulationContext from isaaclab.sim.utils.stage import use_stage -from isaaclab.utils.configclass import resolve_cfg_presets from isaaclab.utils.seed import configure_seed from isaaclab.utils.timer import Timer @@ -89,9 +88,6 @@ def __init__(self, cfg: ManagerBasedEnvCfg): # check that the config is valid cfg.validate() - # Resolve any preset-wrapper fields (PresetCfg subclasses or old-style ``presets`` dicts) - # to their default variant so that managers and scene builders see concrete cfg objects. - resolve_cfg_presets(cfg) # store inputs to class self.cfg = cfg # initialize internal variables diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 1aa7a3d8254d..333f6dd2caf4 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -46,15 +46,14 @@ _VISUALIZER_ALIASES = {"newton": "newton_gl"} -def _resolve_physics_cfg(physics_cfg: Any, use_isaac_sim: bool) -> PhysicsCfg: +def _resolve_physics_cfg(physics_cfg: PhysicsCfg | None, use_isaac_sim: bool) -> PhysicsCfg: """Resolve a simulation physics config to a concrete backend.""" if physics_cfg is None: from isaaclab_physx.physics import PhysxCfg physics_cfg = PhysxCfg() - - if not hasattr(physics_cfg, "class_type") and hasattr(physics_cfg, "default"): - physics_cfg = physics_cfg.default + elif not isinstance(physics_cfg, PhysicsCfg): + raise TypeError(f"SimulationCfg.physics must be a concrete PhysicsCfg, got {type(physics_cfg).__name__}.") return _resolve_physx_auto_cfg(physics_cfg, use_isaac_sim=use_isaac_sim) diff --git a/source/isaaclab/isaaclab/utils/__init__.pyi b/source/isaaclab/isaaclab/utils/__init__.pyi index e9f5e201ca21..4c9a6aea746b 100644 --- a/source/isaaclab/isaaclab/utils/__init__.pyi +++ b/source/isaaclab/isaaclab/utils/__init__.pyi @@ -55,7 +55,6 @@ __all__ = [ "get_isaac_sim_version", "compare_versions", "configclass", - "resolve_cfg_presets", "checked_apply", ] @@ -107,4 +106,4 @@ from .string import ( ) from .types import ArticulationActions from .version import has_kit, get_isaac_sim_version, compare_versions -from .configclass import checked_apply, configclass, resolve_cfg_presets +from .configclass import checked_apply, configclass diff --git a/source/isaaclab/isaaclab/utils/configclass.py b/source/isaaclab/isaaclab/utils/configclass.py index 0de67a21c137..373c0b6cd638 100644 --- a/source/isaaclab/isaaclab/utils/configclass.py +++ b/source/isaaclab/isaaclab/utils/configclass.py @@ -606,49 +606,6 @@ def _wrap(): return _wrap -def resolve_cfg_presets(cfg: object) -> object: - """Recursively replace preset-wrapper fields with their *default* preset. - - Task configs may use two preset-selector patterns to support multiple physics backends - (PhysX / Newton) or observation modes. Both patterns produce wrapper objects that are - **not** valid as the concrete cfg that downstream managers / scene builders expect. - This function resolves them in-place so the config can be used without a Hydra CLI - override (e.g. in unit tests or when creating environments directly). - - Supported patterns: - - * **New style** (``PresetCfg`` subclass): a configclass whose MRO contains a class named - ``PresetCfg``. The active variant is stored in the ``default`` attribute. - * **Old style** (``presets`` dict): a configclass that has a ``presets: dict[str, Cfg]`` - attribute with a ``"default"`` key. - - Args: - cfg: Any configclass instance (or any object; non-configclasses are returned as-is). - - Returns: - The same ``cfg`` object, modified in-place with preset wrappers replaced. - """ - if not hasattr(cfg, "__dataclass_fields__"): - return cfg - for field_name in list(cfg.__dataclass_fields__): - value = getattr(cfg, field_name, None) - if value is None or not hasattr(value, "__dataclass_fields__"): - continue - # New-style PresetCfg: class hierarchy contains a class named "PresetCfg". - if any(cls.__name__ == "PresetCfg" for cls in type(value).__mro__): - resolved = value.default - setattr(cfg, field_name, resolved) - resolve_cfg_presets(resolved) - # Old-style preset: configclass with a ``presets`` dict that has a ``"default"`` key. - elif isinstance(getattr(value, "presets", None), dict) and "default" in value.presets: - resolved = value.presets["default"] - setattr(cfg, field_name, resolved) - resolve_cfg_presets(resolved) - else: - resolve_cfg_presets(value) - return cfg - - def checked_apply(src: Any, target: Any) -> None: """Forward every declared field on ``src`` (a dataclass) onto ``target``. diff --git a/source/isaaclab/test/benchmark/test_capture.py b/source/isaaclab/test/benchmark/test_capture.py index 3a179e0dd8c9..8e200f916a78 100644 --- a/source/isaaclab/test/benchmark/test_capture.py +++ b/source/isaaclab/test/benchmark/test_capture.py @@ -6,16 +6,14 @@ """Tests for benchmark capture helpers (Isaac-Sim-free, fake recorders).""" from types import SimpleNamespace -from typing import Literal import pytest -import isaaclab.benchmark.capture as capture from isaaclab.benchmark.capture import ( capture_hardware, capture_resources, capture_versions, - run_config_from_presets, + run_config_from_env_cfg, synth_run_id, ) from isaaclab.benchmark.interfaces import MeasurementData @@ -209,44 +207,23 @@ def test_synth_run_id(): assert "rsl_rl" in rid and "physx" in rid and "42" in rid -def test_run_config_from_presets_resolves_backend_configuration(monkeypatch): - cases = [ - ([], "physx", "none", []), - ( - ["newton_mjwarp", "ovrtx_renderer", "rgb"], - "newton_mjwarp", - "ovrtx", - ["newton_mjwarp", "ovrtx_renderer", "rgb"], - ), - (["newton"], "newton_mjwarp", "none", ["newton"]), - ( - ["physics=newton_mjwarp", "renderer=ovrtx_renderer", "presets=rgb,depth"], - "newton_mjwarp", - "ovrtx", - ["newton_mjwarp", "ovrtx_renderer", "rgb", "depth"], - ), - ] - for tokens, physics, rendering, presets in cases: - cfg = run_config_from_presets(tokens) - assert cfg.physics_backend == physics - assert cfg.rendering_backend == rendering - assert cfg.presets == presets - - monkeypatch.setattr(capture, "PhysicsBackend", Literal["physx", "newton_mjwarp_vbd"], raising=False) - assert run_config_from_presets(["newton_mjwarp_vbd"]).physics_backend == "newton_mjwarp_vbd" - +def test_run_config_uses_concrete_backend_configuration(): env_cfg = SimpleNamespace( sim=SimpleNamespace(physics=SimpleNamespace(class_type="isaaclab_newton.physics:NewtonMJWarpManager")), camera=SimpleNamespace(renderer_cfg=SimpleNamespace(renderer_type="isaac_rtx")), ) - cfg = run_config_from_presets([], env_cfg=env_cfg) + cfg = run_config_from_env_cfg(env_cfg) assert cfg.physics_backend == "newton_mjwarp" assert cfg.rendering_backend == "isaacsim_rtx" + assert cfg.presets == [] physx_env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=SimpleNamespace(class_type="PhysXManager"))) - cfg = run_config_from_presets(["isaacsim_physx"], env_cfg=physx_env_cfg) + cfg = run_config_from_env_cfg(physx_env_cfg) assert cfg.physics_backend == "physx" + with pytest.raises(ValueError, match="Unsupported concrete physics config"): + run_config_from_env_cfg(SimpleNamespace(sim=SimpleNamespace(physics=object()))) + def test_capture_resources_peak_clamped_to_mean_when_peak_row_absent(): # Build a recorder that has mean/std rows but no peak rows. diff --git a/source/isaaclab/test/envs/test_manager_based_rl_env_obs_spaces_task_integration.py b/source/isaaclab/test/envs/test_manager_based_rl_env_obs_spaces_task_integration.py index 88d65ea3f408..30780a28d20d 100644 --- a/source/isaaclab/test/envs/test_manager_based_rl_env_obs_spaces_task_integration.py +++ b/source/isaaclab/test/envs/test_manager_based_rl_env_obs_spaces_task_integration.py @@ -23,9 +23,7 @@ from isaaclab.envs import ManagerBasedRLEnv from isaaclab.managers import ObservationGroupCfg -from isaaclab_tasks.contrib.velocity.config.anymal_c.rough_env_cfg import AnymalCRoughEnvCfg -from isaaclab_tasks.core.cartpole.cartpole_manager_camera_env_cfg import CartpoleCameraEnvCfg -from isaaclab_tasks.core.cartpole.cartpole_manager_env_cfg import CartpoleEnvCfg +from isaaclab_tasks.utils import resolve_task_config pytestmark = pytest.mark.integration @@ -41,7 +39,7 @@ def test_non_concatenated_obs_groups_contain_all_terms(device): sim_utils.create_new_stage() # configure the policy group to return its terms separately - env_cfg = CartpoleEnvCfg() + env_cfg, _ = resolve_task_config("Isaac-Cartpole", "", overrides=()) env_cfg.scene.num_envs = 2 # keep num_envs small for testing env_cfg.observations.policy.concatenate_terms = False env_cfg.sim.device = device @@ -68,24 +66,21 @@ def test_non_concatenated_obs_groups_contain_all_terms(device): @pytest.mark.parametrize( - ("env_cfg_cls", "presets"), + ("task_name", "overrides"), [ - (CartpoleCameraEnvCfg, ("rgb",)), - (CartpoleCameraEnvCfg, ("depth",)), - (AnymalCRoughEnvCfg, ()), + ("Isaac-Cartpole-Camera", ("presets=rgb",)), + ("Isaac-Cartpole-Camera", ("presets=depth",)), + ("IsaacContrib-Velocity-Rough-AnymalC", ()), ], ids=["RGB", "Depth", "RayCaster"], ) @pytest.mark.parametrize("device", ["cpu", "cuda"]) -def test_obs_space_follows_clip_constraint(env_cfg_cls, presets, device): +def test_obs_space_follows_clip_constraint(task_name, overrides, device): """Ensure observation space bounds reflect the clip constraint on each term.""" # new USD stage sim_utils.create_new_stage() - # configure the env -- resolve Hydra presets so _Preset fields become plain values - from isaaclab_tasks.utils.hydra import resolve_presets - - env_cfg = resolve_presets(env_cfg_cls(), presets) + env_cfg, _ = resolve_task_config(task_name, "", overrides=overrides) env_cfg.scene.num_envs = 2 # keep num_envs small for testing for group_cfg in vars(env_cfg.observations).values(): if isinstance(group_cfg, ObservationGroupCfg): diff --git a/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py b/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py index 469f7dc9ab9b..7c657cd63b1f 100644 --- a/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py +++ b/source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py @@ -84,10 +84,11 @@ def test_render_cartpole_camera_produces_valid_observation_and_reward() -> None: import torch from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env import CartpoleCameraEnv - from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env_cfg import CartpoleCameraEnvCfg - from isaaclab_tasks.utils.hydra import resolve_presets + from isaaclab_tasks.utils import resolve_task_config - env_cfg = resolve_presets(CartpoleCameraEnvCfg(), selected={"newton_mjwarp", "newton_renderer"}) + env_cfg, _ = resolve_task_config( + "Isaac-Cartpole-Camera-Direct", "", overrides=("physics=newton_mjwarp", "renderer=newton_renderer") + ) env_cfg.scene.num_envs = 2 env_cfg.frame_stack = 1 env = None diff --git a/source/isaaclab_contrib/changelog.d/remove-post-hydra-preset-resolution.skip b/source/isaaclab_contrib/changelog.d/remove-post-hydra-preset-resolution.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_contrib/isaaclab_contrib/rl/rlinf/extension.py b/source/isaaclab_contrib/isaaclab_contrib/rl/rlinf/extension.py index d38e577811c0..7c65d8efc363 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/rl/rlinf/extension.py +++ b/source/isaaclab_contrib/isaaclab_contrib/rl/rlinf/extension.py @@ -490,10 +490,9 @@ def make_env_isaaclab() -> tuple: sim_app = AppLauncher(headless=True, enable_cameras=True).app import gymnasium as gym - from isaaclab_tasks.utils import load_cfg_from_registry + from isaaclab_tasks.utils import parse_env_cfg - isaac_env_cfg = load_cfg_from_registry(self.isaaclab_env_id, "env_cfg_entry_point") - isaac_env_cfg.scene.num_envs = self.cfg.init_params.num_envs + isaac_env_cfg = parse_env_cfg(self.isaaclab_env_id, num_envs=self.cfg.init_params.num_envs) env = gym.make(self.isaaclab_env_id, cfg=isaac_env_cfg, render_mode="rgb_array").unwrapped diff --git a/source/isaaclab_contrib/test/custom_coupling/test_env_cfg.py b/source/isaaclab_contrib/test/custom_coupling/test_env_cfg.py index 8a13a2f96905..2d3c5eec7699 100644 --- a/source/isaaclab_contrib/test/custom_coupling/test_env_cfg.py +++ b/source/isaaclab_contrib/test/custom_coupling/test_env_cfg.py @@ -5,15 +5,13 @@ """Unit tests for the custom coupling environment configuration.""" -import sys - import pytest -from isaaclab_contrib.custom_coupling.franka_soft_env_cfg import FrankaSoftCustomCouplingEnvCfg, PhysicsCfg +import isaaclab_contrib.custom_coupling.tasks # noqa: F401 +from isaaclab_contrib.custom_coupling.franka_soft_env_cfg import PhysicsCfg -from isaaclab_tasks.core.lift.config.franka_soft.franka_cloth_env_cfg import FrankaClothCameraEnvCfg from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import PhysicsCfg as CorePhysicsCfg -from isaaclab_tasks.utils.hydra import register_task, resolve_presets +from isaaclab_tasks.utils import resolve_task_config MANUAL_MANAGER = "isaaclab_contrib.custom_coupling.coupled_mjwarp_vbd_manager:NewtonCoupledMJWarpVBDManager" PROXY_MANAGER = "isaaclab_contrib.coupling.coupler:NewtonCouplerManager" @@ -21,7 +19,7 @@ def test_example_default_preset_uses_the_manual_coupler() -> None: """Importing the example must select its own manual coupling preset.""" - env_cfg = resolve_presets(FrankaSoftCustomCouplingEnvCfg(), selected=()) + env_cfg, _ = resolve_task_config("IsaacContrib-Lift-Soft-Franka-Custom-Coupling", "", overrides=()) assert env_cfg.sim.physics.class_type == MANUAL_MANAGER @@ -36,23 +34,25 @@ def test_core_declares_only_the_proxy_preset() -> None: assert "newton_mjwarp_vbd" in contrib_variants -def test_core_task_rejects_the_removed_preset_name(monkeypatch: pytest.MonkeyPatch) -> None: +def test_core_task_rejects_the_removed_preset_name() -> None: """Selecting the moved name on a core task must fail loudly, not fall back.""" - monkeypatch.setattr(sys, "argv", ["prog", "presets=newton_mjwarp_vbd"]) - with pytest.raises(ValueError, match="newton_mjwarp_vbd"): - register_task("Isaac-Lift-Soft-Franka", "rsl_rl_cfg_entry_point") + resolve_task_config( + "Isaac-Lift-Soft-Franka", "rsl_rl_cfg_entry_point", overrides=("physics=newton_mjwarp_vbd",) + ) def test_proxy_preset_selectable_on_example() -> None: """The example still resolves the inherited core proxy preset.""" - env_cfg = resolve_presets(FrankaSoftCustomCouplingEnvCfg(), selected=("newton_mjwarp_vbd_proxy",)) + env_cfg, _ = resolve_task_config( + "IsaacContrib-Lift-Soft-Franka-Custom-Coupling", "", overrides=("physics=newton_mjwarp_vbd_proxy",) + ) assert env_cfg.sim.physics.class_type == PROXY_MANAGER -def test_cloth_camera_task_always_uses_proxy_coupling() -> None: - """The cloth camera task only supports proxy coupling, including for the contrib preset name.""" - env_cfg = resolve_presets(FrankaClothCameraEnvCfg(), selected=("newton_mjwarp_vbd",)) +def test_cloth_camera_task_uses_proxy_coupling() -> None: + """The cloth camera task uses the core proxy coupling by default.""" + env_cfg, _ = resolve_task_config("Isaac-Lift-Soft-Franka-Camera", "", overrides=()) assert env_cfg.sim.physics.class_type == PROXY_MANAGER diff --git a/source/isaaclab_experimental/changelog.d/remove-post-hydra-preset-resolution.skip b/source/isaaclab_experimental/changelog.d/remove-post-hydra-preset-resolution.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py index 9da98ad24cba..b3523265662a 100644 --- a/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py +++ b/source/isaaclab_experimental/isaaclab_experimental/envs/frontend.py @@ -174,9 +174,7 @@ def adapt_cfg(cls, cfg: Any) -> None: Three steps, each independently testable: 1. :meth:`_require_newton_physics` — hard check that ``cfg.sim.physics`` - is :class:`~isaaclab_newton.physics.NewtonCfg`. The user is - responsible for selecting the Newton variant of the task's - :class:`PresetCfg` via ``presets=newton_mjwarp``; we don't auto-inject. + is :class:`~isaaclab_newton.physics.NewtonCfg`. 2. :meth:`_promote_scene_entity_cfgs` — replace stable :class:`~isaaclab.managers.SceneEntityCfg` instances under each term's ``params`` with the warp variant (which adds warp-cached @@ -204,8 +202,7 @@ def check_compatibility(cls, cfg: Any) -> str | None: cannot drift from the real code path. Args: - cfg: A stable manager-based env cfg with its physics preset already resolved - (``presets=newton_mjwarp``); an unresolved preset is reported as incompatible. + cfg: A stable manager-based env cfg with a concrete Newton physics configuration. Returns: ``None`` when the cfg adapts, otherwise the reason, listing every missing twin. @@ -224,11 +221,8 @@ def check_compatibility(cls, cfg: Any) -> str | None: def _require_newton_physics(cfg: Any, label: str) -> None: """Block unless ``cfg.sim.physics`` is :class:`NewtonCfg`. - The warp managers' assets read state through :class:`NewtonManager`; - a :class:`PhysxCfg` (or unresolved :class:`PresetCfg`) is a hard - incompatibility. The fix is to pass ``presets=newton_mjwarp`` on the CLI - so Hydra resolves the task's :class:`PresetCfg` wrapper to the Newton - field before construction. + The warp managers' assets read state through :class:`NewtonManager`, so + every other physics configuration is incompatible. """ from isaaclab_newton.physics import NewtonCfg @@ -237,8 +231,8 @@ def _require_newton_physics(cfg: Any, label: str) -> None: return raise FrontendIncompatibleError( f"warp env {label!r}: expected cfg.sim.physics to be NewtonCfg," - f" got {type(physics).__name__!r}. Pass `presets=newton_mjwarp` on the CLI so" - f" Hydra resolves the task's PresetCfg wrapper to the Newton variant." + f" got {type(physics).__name__!r}. Select Newton while composing the task configuration" + " before constructing the environment." ) @classmethod diff --git a/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py b/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py index c6d345c54934..a05760fb5c4e 100644 --- a/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py +++ b/source/isaaclab_experimental/test/envs/test_frontend_cfg_conversion.py @@ -33,8 +33,7 @@ # Registering the task packages is the whole point — import for side effects. import isaaclab_tasks # noqa: F401 -from isaaclab_tasks.utils.hydra import resolve_presets -from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry +from isaaclab_tasks.utils import resolve_task_config # Stable manager-based tasks resolve to this env class; direct tasks provide their own and # take the :meth:`WarpFrontend._resolve_direct_warp_class` path instead of cfg adaptation. @@ -83,12 +82,10 @@ def _manager_warp_tasks() -> list[tuple[str, str]]: return sorted(tasks) -def _load_adapted_cfg(cfg_entry_point: str): - """Instantiate an env cfg, resolve the Newton preset, and adapt it for warp.""" - module_path, class_name = cfg_entry_point.split(":") - cfg = getattr(importlib.import_module(module_path), class_name)() - cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) - assert isinstance(cfg.sim.physics, NewtonCfg), "task does not provide a newton_mjwarp physics preset" +def _load_adapted_cfg(task_id: str): + """Compose a task for Newton and adapt it for warp.""" + cfg, _ = resolve_task_config(task_id, "", overrides=("physics=newton_mjwarp",)) + assert isinstance(cfg.sim.physics, NewtonCfg), "task does not provide Newton MJWarp physics" # Raises FrontendIncompatibleError if any warp-managed term lacks a warp twin. WarpFrontend.adapt_cfg(cfg) return cfg @@ -116,8 +113,7 @@ def _sweep_warp_support() -> tuple[frozenset[str], dict[str, str], dict[str, str try: # the canonical loader, so every registry form the runtime accepts is surveyed; # matching only ``str`` entry points here would skip callable ones silently - cfg = load_cfg_from_registry(task_id, "env_cfg_entry_point") - cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) + cfg, _ = resolve_task_config(task_id, "", overrides=("physics=newton_mjwarp",)) except Exception as exc: # noqa: BLE001 - any cfg load failure means "cannot judge" unimportable[task_id] = f"{type(exc).__name__}: {exc}" continue @@ -184,7 +180,7 @@ def test_no_warp_task_registrations_remain(): @pytest.mark.parametrize("task_id", sorted(_WARP_SUPPORTED_TASKS), ids=sorted(_WARP_SUPPORTED_TASKS)) def test_stable_task_cfg_adapts_to_warp(task_id: str): """Each covered stable task adapts without a missing twin (the --frontend warp path).""" - cfg = _load_adapted_cfg(_cfg_entry_point(task_id)) + cfg = _load_adapted_cfg(task_id) # Action terms carry a ``class_type`` (not a ``func``) and live on a base that # is not a ManagerTermBaseCfg; guard that the adapter still swaps them to the @@ -225,7 +221,7 @@ def test_stable_cartpole_cfg_adapts_to_current_warp_module_layout(): """The stable Cartpole cfg resolves task-specific twins in the current package layout.""" from isaaclab_experimental.managers.action_manager import ActionTerm - cfg = _load_adapted_cfg(_cfg_entry_point("Isaac-Cartpole")) + cfg = _load_adapted_cfg("Isaac-Cartpole") assert cfg.rewards.pole_pos.func.__module__.startswith("isaaclab_tasks_experimental.core.cartpole.mdp") assert cfg.rewards.success_rate.func.__module__.startswith("isaaclab_tasks_experimental.core.cartpole.mdp") @@ -258,7 +254,7 @@ def test_stable_observation_noise_converts_to_warp_twins(): } assert stable_params, "expected uniform noise on the stable velocity observations" - cfg = _load_adapted_cfg(entry) + cfg = _load_adapted_cfg("Isaac-Velocity-Flat-UnitreeGo2") converted = dict(_iter_obs_terms(cfg)) for name, (n_min, n_max) in stable_params.items(): twin = converted[name].noise @@ -272,10 +268,7 @@ def test_noise_cfg_without_warp_twin_is_a_hard_error(): from isaaclab.utils.noise import NoiseModelCfg, UniformNoiseCfg - entry = _cfg_entry_point("Isaac-Velocity-Flat-UnitreeGo2") - module_path, class_name = entry.split(":") - cfg = getattr(importlib.import_module(module_path), class_name)() - cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) + cfg, _ = resolve_task_config("Isaac-Velocity-Flat-UnitreeGo2", "", overrides=("physics=newton_mjwarp",)) name, term = next(iter(_iter_obs_terms(cfg))) term.noise = NoiseModelCfg(noise_cfg=UniformNoiseCfg()) with pytest.raises(FrontendIncompatibleError, match="noise"): diff --git a/source/isaaclab_mimic/changelog.d/remove-post-hydra-preset-resolution.skip b/source/isaaclab_mimic/changelog.d/remove-post-hydra-preset-resolution.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_mimic/test/test_curobo_planner_cube_stack.py b/source/isaaclab_mimic/test/test_curobo_planner_cube_stack.py index 8dea1cb14f74..6cf1bc3f86a0 100644 --- a/source/isaaclab_mimic/test/test_curobo_planner_cube_stack.py +++ b/source/isaaclab_mimic/test/test_curobo_planner_cube_stack.py @@ -33,10 +33,12 @@ from isaaclab.envs.manager_based_env import ManagerBasedEnv from isaaclab.markers import FRAME_MARKER_CFG, VisualizationMarkers -from isaaclab_mimic.envs.franka_stack_ik_rel_mimic_env_cfg import FrankaCubeStackIKRelMimicEnvCfg +import isaaclab_mimic.envs # noqa: F401 from isaaclab_mimic.motion_planners.curobo.curobo_planner import CuroboPlanner from isaaclab_mimic.motion_planners.curobo.curobo_planner_cfg import CuroboPlannerCfg +from isaaclab_tasks.utils import parse_env_cfg + GRIPPER_OPEN_CMD: float = 1.0 GRIPPER_CLOSE_CMD: float = -1.0 @@ -93,8 +95,7 @@ def cube_stack_test_env() -> Generator[dict[str, Any], None, None]: random.seed(SEED) torch.manual_seed(SEED) - env_cfg = FrankaCubeStackIKRelMimicEnvCfg() - env_cfg.scene.num_envs = 1 + env_cfg = parse_env_cfg("Isaac-Stack-Cube-Franka-IK-Rel-Mimic-v0", num_envs=1) for frame in env_cfg.scene.ee_frame.target_frames: if frame.name == "end_effector": print(f"Setting end effector offset from {frame.offset.pos} to (0.0, 0.0, 0.0) for SkillGen parity") diff --git a/source/isaaclab_mimic/test/test_curobo_planner_franka.py b/source/isaaclab_mimic/test/test_curobo_planner_franka.py index fda747ca8657..fe90db4b530b 100644 --- a/source/isaaclab_mimic/test/test_curobo_planner_franka.py +++ b/source/isaaclab_mimic/test/test_curobo_planner_franka.py @@ -35,7 +35,8 @@ from isaaclab_mimic.motion_planners.curobo.curobo_planner import CuroboPlanner from isaaclab_mimic.motion_planners.curobo.curobo_planner_cfg import CuroboPlannerCfg -from isaaclab_tasks.contrib.stack.config.franka.stack_joint_pos_env_cfg import FrankaCubeStackEnvCfg +import isaaclab_tasks # noqa: F401 +from isaaclab_tasks.utils import parse_env_cfg # Predefined EE goals for the test # Each entry is a tuple of: (goal specification, goal ID) @@ -54,8 +55,7 @@ def curobo_test_env() -> Generator[dict[str, Any], None, None]: random.seed(SEED) torch.manual_seed(SEED) - env_cfg = FrankaCubeStackEnvCfg() - env_cfg.scene.num_envs = 1 + env_cfg = parse_env_cfg("IsaacContrib-Stack-Cube-Franka", num_envs=1) # Add a static wall for the robot to avoid wall_props = RigidBodyPropertiesCfg(kinematic_enabled=True, disable_gravity=True) diff --git a/source/isaaclab_rl/changelog.d/remove-post-hydra-preset-resolution.rst b/source/isaaclab_rl/changelog.d/remove-post-hydra-preset-resolution.rst new file mode 100644 index 000000000000..711a92ca1583 --- /dev/null +++ b/source/isaaclab_rl/changelog.d/remove-post-hydra-preset-resolution.rst @@ -0,0 +1,5 @@ +Changed +^^^^^^^ + +* Changed run summaries to report the concrete physics and renderer backends directly, without + reparsing preset selectors after task composition. diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py index 3da930619bfd..bb3e98a1ae3f 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/common.py @@ -16,7 +16,7 @@ import runpy import sys import warnings -from collections.abc import Callable, Container, Iterator +from collections.abc import Callable, Iterator from contextlib import ExitStack, contextmanager from datetime import datetime, timezone from pathlib import Path @@ -34,11 +34,8 @@ from isaaclab.utils.images import make_camera_output_grid, normalize_camera_output_for_display from isaaclab.utils.io import dump_yaml -# Preset selectors whose values name a preset, and the preset names that resolved -# configs map back to when their class or ``renderer_type`` differs from the name. -_PRESET_SELECTORS = frozenset({"presets", "physics", "renderer"}) -_PHYSICS_PRESET_NAMES = {"PhysxCfg": "isaacsim_physx", "OvPhysxCfg": "ovphysx", "PhysxAutoCfg": "physx"} -_RENDERER_PRESET_NAMES = {"isaac_rtx": "isaacsim_rtx", "newton_warp": "newton_renderer", "auto_rtx": "rtx"} +_PHYSICS_BACKEND_NAMES = {"PhysxCfg": "isaacsim_physx", "OvPhysxCfg": "ovphysx", "PhysxAutoCfg": "physx"} +_RENDERER_BACKEND_NAMES = {"isaac_rtx": "isaacsim_rtx", "newton_warp": "newton_renderer", "auto_rtx": "rtx"} RUN_MANIFEST_FILENAME = "run.json" RUN_MANIFEST_VERSION = 1 @@ -355,7 +352,7 @@ def add_frontend_args(parser: argparse.ArgumentParser) -> None: "Runtime that constructs the environment. 'torch' uses the registered stable environment via" " gym.make. 'warp' (experimental) adapts a manager-based task config onto the Warp runtime, or" " dispatches a direct task to its registered Warp environment; requires isaaclab_experimental" - " and `presets=newton_mjwarp`." + " and `physics=newton_mjwarp`." ), ) @@ -555,33 +552,29 @@ def show_run_summary( ) -> None: """Print a summary of the backends and scale a run is about to use. - Every row names the backend that will run, alongside the choice the run stopped at. - A backend reached through a family the command line named -- ``physics=physx`` and - ``renderer=rtx`` name a family that launch resolves -- is shown as - `` ()``, and a backend the run named neither directly nor by - family is shown as ``default ()``. + Every row names the backend that will run. An automatic launcher choice is + shown as `` ()``. - Resolving those selectors mutates *env_cfg* in place, exactly as the following - :func:`~isaaclab.app.launch_simulation` call would; call this after every other - pre-launch config change, in particular :func:`pre_launch_video_config`. + Resolving automatic backend configurations mutates *env_cfg* in place, exactly + as the following :func:`~isaaclab.app.launch_simulation` call would; call this + after every other pre-launch config change, in particular :func:`pre_launch_video_config`. Args: screen: Loading screen that owns the console. args_cli: Parsed command-line arguments. - env_cfg: Isaac Lab environment config, with its presets already resolved. + env_cfg: Concrete Isaac Lab environment config. library: Reinforcement learning library running the workflow. action: Workflow name, either ``"train"`` or ``"play"``. """ - selected = _selected_preset_names() device = getattr(args_cli, "device", None) or env_cfg.sim.device num_envs = getattr(args_cli, "num_envs", None) or env_cfg.scene.num_envs # Names read before the scan resolves the automatic selectors, so a row can report # the family the run asked for next to the backend that family resolved to - requested_physics = _physics_name(env_cfg.sim.physics) + requested_physics = _physics_backend_name(env_cfg.sim.physics) requested_renderer = _renderer_name(env_cfg) scan(env_cfg, args_cli) - physics = _physics_name(env_cfg.sim.physics) + physics = _physics_backend_name(env_cfg.sim.physics) renderer = _renderer_name(env_cfg) screen.summary( @@ -590,13 +583,12 @@ def show_run_summary( "Task": args_cli.task, "Workflow": _workflow_name(env_cfg), "RL library": library, - "Physics": _label(requested_physics, physics, selected=selected), + "Physics": _backend_label(requested_physics, physics), "Renderer": ( "n/a (no camera sensors)" if renderer is None - else _label(requested_renderer or renderer, renderer, selected=selected) + else _backend_label(requested_renderer or renderer, renderer) ), - "Presets": _additional_preset_names({requested_physics, physics, requested_renderer, renderer}), "Visualizer": _visualizer_name(args_cli, env_cfg), "Device": str(device), "Environments": str(num_envs), @@ -604,68 +596,18 @@ def show_run_summary( ) -def _label(requested: str, resolved: str, *, selected: set[str] = frozenset()) -> str: - """Name the backend a row reports, and where the run stopped choosing it. +def _backend_label(requested: str, concrete: str) -> str: + """Name a concrete backend and its automatic selector when they differ. Args: - requested: Preset name the config carried before launch resolved its automatic - selectors, which names a backend family when it differs from *resolved*. - resolved: Preset name of the backend that will run. - selected: Preset names the command line asked for. + requested: Backend name before launcher-owned automatic selection. + concrete: Concrete backend that will run. Returns: - The resolved name when the run asked for that backend, `` ()`` - when it asked for the family the backend was picked from, and - ``default ()`` when it asked for neither. + The concrete name, or `` ()`` when the launcher + selected a concrete backend from an automatic configuration. """ - if resolved in selected: - return resolved - if requested in selected: - return f"{requested} ({resolved})" - return f"default ({resolved})" - - -def _selected_preset_names() -> set[str]: - """Return the preset names the command line asked for. - - Reads the same ``sys.argv`` the preset resolver consumes (see - :func:`~isaaclab_tasks.utils.hydra.register_task`), so the summary marks a - backend as chosen exactly when a ``physics=`` / ``renderer=`` / ``presets=`` - token or a Hydra path override named it. - """ - names: set[str] = set() - for token in sys.argv[1:]: - key, separator, value = token.partition("=") - if separator and (key.lstrip("-") in _PRESET_SELECTORS or key.startswith(("env.", "agent."))): - names.update(part.strip() for part in value.split(",") if part.strip()) - return names - - -def _additional_preset_names(shown: Container[str | None]) -> str: - """Return the presets the command line asked for that no other row names. - - Domain presets such as ``presets=cube`` do not surface anywhere else in the - summary, so they are listed here. A preset a row already reports is left out, - whether it names the backend that will run or the family the row resolved it - from -- ``renderer=rtx`` is reported by an ``rtx (ovrtx)`` renderer row. - - Args: - shown: Preset names reported by the physics and renderer rows, including - the families those rows resolved from. - - Returns: - The remaining preset names in command-line order, comma separated, or - ``"none"`` when the run named no other preset. - """ - names: list[str] = [] - for token in sys.argv[1:]: - key, separator, value = token.partition("=") - if not separator or key.lstrip("-") not in _PRESET_SELECTORS: - continue - for part in (part.strip() for part in value.split(",")): - if part and part not in shown and part not in names: - names.append(part) - return ", ".join(names) if names else "none" + return concrete if requested == concrete else f"{requested} ({concrete})" def _workflow_name(env_cfg: Any) -> str: @@ -675,11 +617,11 @@ def _workflow_name(env_cfg: Any) -> str: return "direct (multi-agent)" if isinstance(env_cfg, DirectMARLEnvCfg) else "direct" -def _physics_name(physics_cfg: Any) -> str: - """Return the preset name of a resolved physics config.""" +def _physics_backend_name(physics_cfg: Any) -> str: + """Return the backend name of a concrete physics config.""" class_name = type(physics_cfg).__name__ - if class_name in _PHYSICS_PRESET_NAMES: - return _PHYSICS_PRESET_NAMES[class_name] + if class_name in _PHYSICS_BACKEND_NAMES: + return _PHYSICS_BACKEND_NAMES[class_name] solver_cfg = getattr(physics_cfg, "solver_cfg", None) backend = class_name.removesuffix("Cfg").lower() if solver_cfg is None: @@ -688,7 +630,7 @@ def _physics_name(physics_cfg: Any) -> str: def _renderer_name(env_cfg: Any) -> str | None: - """Return the preset name of the renderer used by the first camera sensor of *env_cfg*. + """Return the backend name of the renderer used by the first camera sensor of *env_cfg*. Only configs whose class declares ``renderer_cfg`` are read. Probing every attribute with :func:`getattr` instead would resolve lazily evaluated config @@ -703,7 +645,7 @@ def _renderer_name(env_cfg: Any) -> str | None: renderer_cfg = value.renderer_cfg if isinstance(renderer_cfg, RendererCfg): renderer_type = renderer_cfg.renderer_type - return _RENDERER_PRESET_NAMES.get(renderer_type, renderer_type) + return _RENDERER_BACKEND_NAMES.get(renderer_type, renderer_type) return None diff --git a/source/isaaclab_rl/test/test_entrypoints_common.py b/source/isaaclab_rl/test/test_entrypoints_common.py index c3594644bc98..53de691e026e 100644 --- a/source/isaaclab_rl/test/test_entrypoints_common.py +++ b/source/isaaclab_rl/test/test_entrypoints_common.py @@ -338,26 +338,6 @@ def test_resolve_play_task_name_keeps_registered_and_unknown_tasks() -> None: assert resolve_play_task_name(None) is None -@pytest.mark.parametrize( - "argv, expected", - [ - ([], "none"), - (["presets=cube"], "cube"), - (["presets=newton_mjwarp,cube,tiled"], "cube, tiled"), - (["physics=newton_mjwarp", "presets=cube"], "cube"), - (["renderer=rtx"], "none"), - # duplicates collapse, and non-preset overrides are ignored - (["presets=cube", "physics=cube", "env.scene.num_envs=64"], "cube"), - ], -) -def test_additional_preset_names_lists_presets_without_a_row_of_their_own( - argv: list[str], expected: str, monkeypatch: pytest.MonkeyPatch -) -> None: - """The presets row names the chosen presets that physics and renderer do not already report.""" - monkeypatch.setattr(_rl_common.sys, "argv", ["train.py"] + argv) - assert _rl_common._additional_preset_names({"newton_mjwarp", "rtx"}) == expected - - class _RecordingScreen: """Loading screen stand-in that keeps the summary fields instead of drawing them.""" @@ -369,31 +349,23 @@ def summary(self, title: str, fields: dict[str, str]) -> None: @pytest.mark.parametrize( - "selectors, expected_physics, expected_renderer, expected_presets", + "selectors, expected_physics, expected_renderer", [ - (["physics=ovphysx", "renderer=rtx"], "ovphysx", "rtx (ovrtx)", "none"), - (["physics=isaacsim_physx", "renderer=rtx"], "isaacsim_physx", "rtx (isaacsim_rtx)", "none"), + (["physics=ovphysx", "renderer=rtx"], "ovphysx", "rtx (ovrtx)"), + (["physics=isaacsim_physx", "renderer=rtx"], "isaacsim_physx", "rtx (isaacsim_rtx)"), # ``physx`` reaches the physics backend the same way ``rtx`` reaches the renderer - (["physics=physx", "renderer=rtx"], "physx (ovphysx)", "rtx (ovrtx)", "none"), - # a run that names no backend reports the ones the task pinned as defaults - ([], "default (newton_mjwarp)", "default (newton_renderer)", "none"), - # a domain preset has no row of its own - (["physics=physx", "presets=depth"], "physx (ovphysx)", "default (newton_renderer)", "depth"), + (["physics=physx", "renderer=rtx"], "physx (ovphysx)", "rtx (ovrtx)"), + ([], "newton_mjwarp", "newton_renderer"), + (["physics=physx", "presets=depth"], "physx (ovphysx)", "newton_renderer"), ], ) -def test_run_summary_reports_the_backends_the_run_resolves_to( +def test_run_summary_reports_concrete_backends( selectors: list[str], expected_physics: str, expected_renderer: str, - expected_presets: str, monkeypatch: pytest.MonkeyPatch, ) -> None: - """``physics=physx`` and ``renderer=rtx`` name a family; the summary names what will run. - - A row reached through such a selector names the resolved backend on its own, since - the run never asked for a different one; the selector is listed under presets. Only - a backend the task pinned and the run never named is marked a default. - """ + """The summary reports concrete backends and launcher-owned automatic choices.""" import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils import resolve_task_config @@ -407,4 +379,4 @@ def test_run_summary_reports_the_backends_the_run_resolves_to( assert screen.fields["Physics"] == expected_physics assert screen.fields["Renderer"] == expected_renderer - assert screen.fields["Presets"] == expected_presets + assert "Presets" not in screen.fields diff --git a/source/isaaclab_tasks/changelog.d/remove-post-hydra-preset-resolution.major.rst b/source/isaaclab_tasks/changelog.d/remove-post-hydra-preset-resolution.major.rst new file mode 100644 index 000000000000..db153202969f --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/remove-post-hydra-preset-resolution.major.rst @@ -0,0 +1,9 @@ +Changed +^^^^^^^ + +* **Breaking:** Made task composition the sole owner of preset replacement. Runtime task code now + requires concrete camera, renderer, and physics configurations; use + :func:`isaaclab_tasks.utils.resolve_task_config` or :func:`isaaclab_tasks.utils.parse_env_cfg` + instead of passing a raw registered configuration class to an environment. +* Added explicit programmatic ``overrides`` to :func:`isaaclab_tasks.utils.resolve_task_config` so + tools and tests can use the same composition path without modifying :data:`sys.argv`. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py index c8867d3175f0..a475e5df08b6 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cartpole/cartpole_direct_camera_env.py @@ -16,7 +16,6 @@ from isaaclab.utils.images import is_rgb_like, normalize_camera_image from isaaclab_tasks.core.cartpole.cartpole_direct_env import CartpoleEnv -from isaaclab_tasks.utils import PresetCfg if TYPE_CHECKING: from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env_cfg import CartpoleCameraEnvCfg @@ -28,16 +27,12 @@ class CartpoleCameraEnv(CartpoleEnv): cfg: CartpoleCameraEnvCfg def __init__(self, cfg: CartpoleCameraEnvCfg, render_mode: str | None = None, **kwargs): - # DirectRLEnv resolves presets after this subclass derives its Gym - # observation space. Use the default camera preset only for that - # derivation; leave full config resolution to DirectRLEnv. - camera_cfg = cfg.tiled_camera.default if isinstance(cfg.tiled_camera, PresetCfg) else cfg.tiled_camera cfg.frame_stack = max(1, cfg.frame_stack) if isinstance(cfg.observation_space, list): cfg.observation_space = [ int(cfg.observation_space[0]) * cfg.frame_stack, - int(camera_cfg.height), - int(camera_cfg.width), + int(cfg.tiled_camera.height), + int(cfg.tiled_camera.width), ] super().__init__(cfg, render_mode, **kwargs) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py index 3f73a3f4ee41..fa321188be0b 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py @@ -6,6 +6,7 @@ from __future__ import annotations import isaaclab.sim as sim_utils +from isaaclab.renderers import RendererCfg from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import CameraCfg from isaaclab.utils.configclass import configclass @@ -17,15 +18,19 @@ def validate_shadow_hand_camera_settings( - tiled_camera: CameraCfg | ShadowHandTiledCameraCfg, + tiled_camera: CameraCfg, feature_extractor: FeatureExtractorCfg, ) -> None: - """Validate one resolved or defaulted Shadow Hand camera pipeline.""" - while isinstance(tiled_camera, PresetCfg): - tiled_camera = tiled_camera.default + """Validate one concrete Shadow Hand camera pipeline.""" + if not isinstance(tiled_camera, CameraCfg): + raise TypeError( + f"Shadow Hand camera validation requires a concrete CameraCfg, got {type(tiled_camera).__name__}." + ) renderer_cfg = tiled_camera.renderer_cfg - while isinstance(renderer_cfg, PresetCfg): - renderer_cfg = renderer_cfg.default + if renderer_cfg is not None and not isinstance(renderer_cfg, RendererCfg): + raise TypeError( + f"Shadow Hand camera validation requires a concrete RendererCfg or None, got {type(renderer_cfg).__name__}." + ) renderer_type = getattr(renderer_cfg, "renderer_type", None) warp_supported = { diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py b/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py index cfe353b3dc43..af437defabb1 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py @@ -31,7 +31,7 @@ import sys import warnings from collections import deque -from collections.abc import Callable, Mapping +from collections.abc import Callable, Mapping, Sequence import hydra from hydra.core.config_store import ConfigStore @@ -464,7 +464,12 @@ def hydra_main(hydra_cfg, env_cfg=env_cfg, agent_cfg=agent_cfg): sys.argv = original_argv -def resolve_task_config(task_name: str, agent_cfg_entry_point: str, play_mode: bool = False): +def resolve_task_config( + task_name: str, + agent_cfg_entry_point: str | None, + play_mode: bool = False, + overrides: Sequence[str] | None = None, +): """Resolve env and agent configs with Hydra overrides, presets, and scalars fully applied. Safe to call before Kit is launched -- callable config values are stored as @@ -476,12 +481,17 @@ def resolve_task_config(task_name: str, agent_cfg_entry_point: str, play_mode: b agent_cfg_entry_point: Agent config entry point key (e.g., "rsl_rl_cfg_entry_point"). play_mode: Whether to apply the play-mode overrides defined by the environment configuration's ``play_mode`` method after loading. Defaults to False. + overrides: Optional Hydra arguments to use instead of reading them from + :data:`sys.argv`. This keeps programmatic task composition on the same + path as command-line composition. Defaults to None. Returns: Tuple of (env_cfg, agent_cfg) fully resolved. """ task = task_name.split(":")[-1] - env_cfg, agent_cfg, hydra_args = register_task(task, agent_cfg_entry_point, play_mode=play_mode) + env_cfg, agent_cfg, hydra_args = register_task( + task, agent_cfg_entry_point, play_mode=play_mode, overrides=overrides + ) resolved = {} _run_hydra(task, env_cfg, agent_cfg, hydra_args, lambda e, a: resolved.update(env_cfg=e, agent_cfg=a)) return resolved["env_cfg"], resolved["agent_cfg"] @@ -580,7 +590,12 @@ def _validate_typed_presets( ) -def register_task(task_name: str, agent_entry: str, play_mode: bool = False) -> tuple: +def register_task( + task_name: str, + agent_entry: str | None, + play_mode: bool = False, + overrides: Sequence[str] | None = None, +) -> tuple: """Load configs, collect presets recursively, register base config to Hydra. Presets are collected from nested configclasses and stored separately - @@ -591,6 +606,7 @@ def register_task(task_name: str, agent_entry: str, play_mode: bool = False) -> agent_entry: Agent config entry point key. play_mode: Whether to apply the play-mode overrides defined by the environment configuration's ``play_mode`` method after loading. Defaults to False. + overrides: Optional Hydra arguments to compose instead of :data:`sys.argv`. Returns: Tuple of ``(env_cfg, agent_cfg, hydra_args)`` where presets have been @@ -610,7 +626,7 @@ def register_task(task_name: str, agent_entry: str, play_mode: bool = False) -> requested_targets: dict[PresetTarget, set[str]] = {} override_items: list[tuple[str, str, str]] = [] hydra_args: list[str] = [] - for arg in sys.argv[1:]: + for arg in sys.argv[1:] if overrides is None else overrides: if "=" not in arg: hydra_args.append(arg) continue diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py index 3d364c9f65e4..4377adf93002 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py @@ -18,7 +18,7 @@ import gymnasium as gym import yaml -from isaaclab_tasks.utils.hydra import _user_stacklevel, resolve_presets +from isaaclab_tasks.utils.hydra import _user_stacklevel, resolve_task_config if TYPE_CHECKING: from isaaclab.envs import DirectRLEnvCfg, ManagerBasedRLEnvCfg @@ -163,21 +163,14 @@ def parse_env_cfg( RuntimeError: If the configuration for the task is not a class. We assume users always use a class for the environment configuration. """ - # load the default configuration - cfg = load_cfg_from_registry(task_name.split(":")[-1], "env_cfg_entry_point") + # Compose the registered task through the same boundary used by Hydra entry points. + cfg, _ = resolve_task_config(task_name, None, overrides=()) # check that it is not a dict # we assume users always use a class for the configuration if isinstance(cfg, dict): raise RuntimeError(f"Configuration for the task: '{task_name}' is not a class. Please provide a class.") - # Resolve any PresetCfg wrappers to their default preset so the config - # is usable without a Hydra CLI override (e.g. in tests). - # Must happen BEFORE attribute overrides, otherwise overrides on PresetCfg wrapper - # fields (e.g. cfg.scene when scene is a PresetCfg) get discarded when the wrapper - # is replaced by its .default. - cfg = resolve_presets(cfg) - # simulation device cfg.sim.device = device # disable fabric to read/write through USD diff --git a/source/isaaclab_tasks/test/core/test_shadow_hand_camera_presets.py b/source/isaaclab_tasks/test/core/test_shadow_hand_camera_presets.py index 7f2d0f20b9fb..04443abf85fb 100644 --- a/source/isaaclab_tasks/test/core/test_shadow_hand_camera_presets.py +++ b/source/isaaclab_tasks/test/core/test_shadow_hand_camera_presets.py @@ -24,6 +24,7 @@ from isaaclab_physx.renderers import IsaacRtxRendererCfg from isaaclab.renderers import RendererCfg +from isaaclab.sensors import CameraCfg from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_camera_env_cfg import ( ShadowHandCameraEnvCfg, @@ -41,8 +42,9 @@ def _make_cfg(renderer_type: str | None, data_types: list[str], feature_extracto The mock reuses the real validation logic from :class:`ShadowHandCameraEnvCfg`. """ cfg = types.SimpleNamespace() - cfg.tiled_camera = types.SimpleNamespace( - renderer_cfg=types.SimpleNamespace(renderer_type=renderer_type), + cfg.tiled_camera = CameraCfg( + prim_path="/Camera", + renderer_cfg=RendererCfg(renderer_type=renderer_type) if renderer_type is not None else None, data_types=data_types, ) cfg.feature_extractor = types.SimpleNamespace(enabled=feature_extractor_enabled) diff --git a/source/isaaclab_tasks/test/core/test_video_recording.py b/source/isaaclab_tasks/test/core/test_video_recording.py index ba5744216b43..cb9f189bd88a 100644 --- a/source/isaaclab_tasks/test/core/test_video_recording.py +++ b/source/isaaclab_tasks/test/core/test_video_recording.py @@ -102,17 +102,15 @@ def _cartpole_cfg_newton(*, num_envs: int = 1): def _cartpole_camera_cfg_physx(*, num_envs: int = 1): - from isaaclab_physx.physics import PhysxCfg - from isaaclab_physx.renderers import IsaacRtxRendererCfg - - from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env_cfg import CartpoleCameraEnvCfg + from isaaclab_tasks.utils import resolve_task_config - cfg = CartpoleCameraEnvCfg() - cfg = cfg.default + cfg, _ = resolve_task_config( + "Isaac-Cartpole-Camera-Direct", + "", + overrides=("physics=isaacsim_physx", "renderer=isaacsim_rtx"), + ) cfg.seed = _SEED cfg.scene.num_envs = num_envs - cfg.sim.physics = PhysxCfg() - cfg.tiled_camera.default.renderer_cfg.default = IsaacRtxRendererCfg() return cfg diff --git a/source/isaaclab_visualizers/changelog.d/remove-post-hydra-preset-resolution.skip b/source/isaaclab_visualizers/changelog.d/remove-post-hydra-preset-resolution.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_visualizers/test/visualizer_integration_utils.py b/source/isaaclab_visualizers/test/visualizer_integration_utils.py index ebd0f9f03379..c3dc7cd086a4 100644 --- a/source/isaaclab_visualizers/test/visualizer_integration_utils.py +++ b/source/isaaclab_visualizers/test/visualizer_integration_utils.py @@ -22,7 +22,6 @@ from __future__ import annotations import contextlib -import copy import gc import logging import math @@ -44,12 +43,9 @@ from isaaclab.sim import SimulationContext from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env import CartpoleCameraEnv -from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env_cfg import CartpoleCameraEnvCfg from isaaclab_tasks.core.cartpole.cartpole_manager_env_cfg import CartpolePhysicsCfg -from isaaclab_tasks.core.lift.config.franka_soft.franka_cloth_env_cfg import FrankaClothEnvCfg -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_env_cfg import ShadowHandEnvCfg from isaaclab_tasks.core.reorient.reorient_direct_env import ReorientDirectEnv -from isaaclab_tasks.core.velocity.config.anymal_d.flat_env_cfg import AnymalDFlatEnvCfg +from isaaclab_tasks.utils import resolve_task_config # Debugging mode configs. @@ -1542,8 +1538,8 @@ def _make_shadow_hand_env( visualizer_kind: str | tuple[str, ...], backend_kind: str, *, tiled_camera: bool = False ) -> ReorientDirectEnv: """Create a shadow hand env configured with selected visualizer and physics backend.""" - env_cfg = copy.deepcopy(ShadowHandEnvCfg()) - env_cfg = _apply_env_cfg_preset(env_cfg, "newton_mjwarp" if backend_kind == "newton" else "physx") + physics = "newton_mjwarp" if backend_kind == "newton" else "physx" + env_cfg = _compose_task_cfg("Isaac-Reorient-Cube-Shadow-Direct", physics) env_cfg.scene.num_envs = ( _SHADOW_HAND_TILED_CAMERA_INTEGRATION_NUM_ENVS if tiled_camera else _SHADOW_HAND_INTEGRATION_NUM_ENVS ) @@ -1601,8 +1597,8 @@ def _make_anymal_d_env(visualizer_kind: str | tuple[str, ...], backend_kind: str """ from isaaclab.envs import ManagerBasedRLEnv - env_cfg = copy.deepcopy(AnymalDFlatEnvCfg()) - env_cfg = _apply_env_cfg_preset(env_cfg, "newton_mjwarp" if backend_kind == "newton" else "physx") + physics = "newton_mjwarp" if backend_kind == "newton" else "physx" + env_cfg = _compose_task_cfg("Isaac-Velocity-Flat-AnymalD", physics) env_cfg.scene.num_envs = ( _ANYMAL_D_TILED_CAMERA_INTEGRATION_NUM_ENVS if tiled_camera else _ANYMAL_D_INTEGRATION_NUM_ENVS ) @@ -1744,32 +1740,20 @@ def _resolve_nucleus_url_to_local(url: str) -> str: return url -def _apply_env_cfg_preset(env_cfg, preset_name: str): - """Apply a named preset to all :class:`~isaaclab_tasks.utils.PresetCfg` fields in *env_cfg*. - - Uses the same Hydra resolver path as :func:`rendering_test_utils._apply_overrides_to_env_cfg` - so nested presets (e.g. ``scene.deformable``) are resolved correctly. - """ - from isaaclab_tasks.utils.hydra import apply_overrides, collect_presets, parse_overrides - - presets = {"env": collect_presets(env_cfg)} - global_presets, preset_sel, preset_scalar, _ = parse_overrides([f"presets={preset_name}"], presets) - hydra_cfg = {"env": env_cfg.to_dict()} - env_cfg, _ = apply_overrides(env_cfg, None, hydra_cfg, global_presets, preset_sel, preset_scalar, presets) +def _compose_task_cfg(task_id: str, physics: str, *overrides: str): + """Compose a registered task with concrete physics and optional Hydra overrides.""" + env_cfg, _ = resolve_task_config(task_id, "", overrides=(f"physics={physics}", *overrides)) return env_cfg def _make_franka_cloth_env(visualizer_kind: str | tuple[str, ...], *, tiled_camera: bool = False): """Create a franka cloth env configured with the selected visualizer on the Newton backend. - Franka cloth uses Newton VBD cloth physics exclusively; there is no PhysX preset. - All nested :class:`~isaaclab_tasks.utils.PresetCfg` fields (including - ``scene.deformable``) are resolved via the Hydra preset resolver. + Franka cloth uses Newton VBD cloth physics exclusively. """ from isaaclab.envs import ManagerBasedRLEnv - env_cfg = copy.deepcopy(FrankaClothEnvCfg()) - env_cfg = _apply_env_cfg_preset(env_cfg, "newton_mjwarp_vbd_proxy") + env_cfg = _compose_task_cfg("Isaac-Lift-Soft-Franka", "newton_mjwarp_vbd_proxy") # Remap nucleus S3 URLs to local /tmp cache so the test works offline when the # omni.client hash cache is cold (shadow hand / AnymalD are warm from prior runs). env_cfg.scene.robot.spawn.usd_path = _resolve_nucleus_url_to_local(env_cfg.scene.robot.spawn.usd_path) @@ -1834,16 +1818,8 @@ def _make_cartpole_camera_env( visualizer_kind: str | tuple[str, ...], backend_kind: str, *, tiled_camera: bool = False ) -> CartpoleCameraEnv: """Create cartpole camera env configured with selected visualizer and physics backend.""" - env_cfg_root = CartpoleCameraEnvCfg() - env_cfg = getattr(env_cfg_root, "default", None) - if env_cfg is None: - env_cfg = getattr(type(env_cfg_root), "default", None) - if env_cfg is None: - raise RuntimeError( - "CartpoleCameraEnvCfg does not expose a 'default' preset config. " - f"Available attributes: {sorted(vars(env_cfg_root).keys())}" - ) - env_cfg = copy.deepcopy(env_cfg) + physics = "newton_mjwarp" if backend_kind == "newton" else "physx" + env_cfg = _compose_task_cfg("Isaac-Cartpole-Camera-Direct", physics, "renderer=isaacsim_rtx") env_cfg.scene.num_envs = ( _CARTPOLE_TILED_CAMERA_INTEGRATION_NUM_ENVS if tiled_camera else _CARTPOLE_INTEGRATION_NUM_ENVS ) @@ -1855,8 +1831,6 @@ def _make_cartpole_camera_env( if isinstance(env_cfg.observation_space, list) and len(env_cfg.observation_space) >= 3: env_cfg.observation_space = [th, tw, env_cfg.observation_space[2]] env_cfg.seed = None - env_cfg.sim.physics, _ = _get_physics_cfg(backend_kind) - env_cfg.tiled_camera.default.renderer_cfg = env_cfg.tiled_camera.default.renderer_cfg.isaacsim_rtx visualizer_kinds = (visualizer_kind,) if isinstance(visualizer_kind, str) else tuple(visualizer_kind) visualizer_cfgs = [_get_visualizer_cfg(kind, tiled_camera=tiled_camera)[0] for kind in visualizer_kinds] env_cfg.sim.visualizer_cfgs = visualizer_cfgs[0] if len(visualizer_cfgs) == 1 else visualizer_cfgs diff --git a/tools/environ_docs.py b/tools/environ_docs.py index c3f9edd9a038..a0ff56134939 100644 --- a/tools/environ_docs.py +++ b/tools/environ_docs.py @@ -103,11 +103,9 @@ def _supports_warp_frontend(task_name: str, workflow: str, presets: dict[PresetT try: from isaaclab_experimental.envs.frontend import FrontendIncompatibleError, WarpFrontend - from isaaclab_tasks.utils.hydra import resolve_presets - from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry + from isaaclab_tasks.utils import resolve_task_config - cfg = load_cfg_from_registry(task_name, "env_cfg_entry_point") - cfg = resolve_presets(cfg, selected=("newton_mjwarp",)) + cfg, _ = resolve_task_config(task_name, "", overrides=("physics=newton_mjwarp",)) if workflow == "Direct": try: return WarpFrontend._resolve_direct_warp_class(task_name, cfg) is not None From ac557d7a552d53182fa5df9def27b835f10d9113 Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sat, 22 Aug 2026 17:07:19 -0700 Subject: [PATCH 2/6] Preserve default physics benchmark behavior --- source/isaaclab/isaaclab/benchmark/capture.py | 7 +++++-- .../isaaclab/test/benchmark/test_capture.py | 3 +++ .../test/core/test_preset_kit_decision.py | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/benchmark/capture.py b/source/isaaclab/isaaclab/benchmark/capture.py index 2c9781141e72..d93c47821039 100644 --- a/source/isaaclab/isaaclab/benchmark/capture.py +++ b/source/isaaclab/isaaclab/benchmark/capture.py @@ -231,9 +231,12 @@ def capture_hardware(bm: Any) -> Hardware: def _backends_from_env_cfg(env_cfg: object) -> tuple[str | None, str | None]: """Return active backend names from a concrete environment configuration.""" physics_cfg = getattr(getattr(env_cfg, "sim", None), "physics", None) + physics_type = type(physics_cfg) physics_descriptor = ( - f"{type(physics_cfg).__module__}.{type(physics_cfg).__name__} {getattr(physics_cfg, 'class_type', '')}" - ).lower() + "physx" + if physics_cfg is None + else f"{physics_type.__module__}.{physics_type.__name__} {getattr(physics_cfg, 'class_type', '')}".lower() + ) physics = next( ( name diff --git a/source/isaaclab/test/benchmark/test_capture.py b/source/isaaclab/test/benchmark/test_capture.py index 8e200f916a78..34d903149649 100644 --- a/source/isaaclab/test/benchmark/test_capture.py +++ b/source/isaaclab/test/benchmark/test_capture.py @@ -221,6 +221,9 @@ def test_run_config_uses_concrete_backend_configuration(): cfg = run_config_from_env_cfg(physx_env_cfg) assert cfg.physics_backend == "physx" + default_env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=None)) + assert run_config_from_env_cfg(default_env_cfg).physics_backend == "physx" + with pytest.raises(ValueError, match="Unsupported concrete physics config"): run_config_from_env_cfg(SimpleNamespace(sim=SimpleNamespace(physics=object()))) diff --git a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py index 0f3a9860372c..bb97a9d167d9 100644 --- a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py +++ b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py @@ -60,6 +60,26 @@ def test_resolve_task_config_applies_plain_scalar_override(): assert env_cfg.scene.num_envs == 123 +def test_camera_cli_size_overrides_update_observation_space(monkeypatch: pytest.MonkeyPatch): + """A composed camera config exposes CLI dimensions before the env derives its Gym space.""" + from isaaclab_tasks.core.cartpole.cartpole_direct_camera_env import CartpoleCameraEnv, CartpoleEnv + + env_cfg = _resolve_with_args( + "env.tiled_camera.height=45", + "env.tiled_camera.width=80", + "env.frame_stack=1", + ) + + def fake_parent_init(self, cfg, *_args, **_kwargs): + self.cfg = cfg + self._is_closed = True + + monkeypatch.setattr(CartpoleEnv, "__init__", fake_parent_init) + env = CartpoleCameraEnv(env_cfg) + + assert env.cfg.observation_space == [3, 45, 80] + + def test_rtx_is_renderer_selector(): """The automatic RTX selector is exposed as ``renderer=rtx``.""" preset_map = enumerate_task_presets(_CAMERA_PRESETS_TASK) From 8a501ac07fe6b8667208af909d7bf0c130eba9cc Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sat, 22 Aug 2026 17:14:44 -0700 Subject: [PATCH 3/6] Default missing physics config to Newton --- .../remove-post-hydra-preset-resolution.major.rst | 6 ++++++ source/isaaclab/isaaclab/benchmark/capture.py | 2 +- source/isaaclab/isaaclab/sim/simulation_cfg.py | 4 ++-- source/isaaclab/isaaclab/sim/simulation_context.py | 4 ++-- source/isaaclab/test/benchmark/test_capture.py | 2 +- source/isaaclab/test/sim/test_simulation_context.py | 10 ++++++++++ 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst b/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst index 93d399c93269..89bc6c691bd4 100644 --- a/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst +++ b/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst @@ -8,3 +8,9 @@ Removed * **Breaking:** Replaced ``run_config_from_presets`` with ``run_config_from_env_cfg`` in benchmark capture. Pass the concrete composed environment configuration instead of inferring backends from selector strings. + +Changed +^^^^^^^ + +* **Breaking:** Changed an omitted :attr:`isaaclab.sim.SimulationCfg.physics` configuration to use + Newton MJWarp by default. Pass an explicit ``PhysxCfg`` to retain Isaac Sim PhysX. diff --git a/source/isaaclab/isaaclab/benchmark/capture.py b/source/isaaclab/isaaclab/benchmark/capture.py index d93c47821039..bbfc00e56f5a 100644 --- a/source/isaaclab/isaaclab/benchmark/capture.py +++ b/source/isaaclab/isaaclab/benchmark/capture.py @@ -233,7 +233,7 @@ def _backends_from_env_cfg(env_cfg: object) -> tuple[str | None, str | None]: physics_cfg = getattr(getattr(env_cfg, "sim", None), "physics", None) physics_type = type(physics_cfg) physics_descriptor = ( - "physx" + "newton_mjwarp" if physics_cfg is None else f"{physics_type.__module__}.{physics_type.__name__} {getattr(physics_cfg, 'class_type', '')}".lower() ) diff --git a/source/isaaclab/isaaclab/sim/simulation_cfg.py b/source/isaaclab/isaaclab/sim/simulation_cfg.py index d3105f8011a6..c4cec931f12e 100644 --- a/source/isaaclab/isaaclab/sim/simulation_cfg.py +++ b/source/isaaclab/isaaclab/sim/simulation_cfg.py @@ -98,10 +98,10 @@ class SimulationCfg: """ physics: PhysicsCfg | None = None - """Physics manager configuration. Default is None (uses PhysxCfg()). + """Physics manager configuration. Default is None (uses NewtonCfg with MJWarp). This configuration determines which physics manager to use. Override with - a different config (e.g., NewtonManagerCfg) to use a different physics backend. + a different concrete config to use another physics backend or Newton solver. """ create_stage_in_memory: bool = False diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 333f6dd2caf4..69477c0a87a4 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -49,9 +49,9 @@ def _resolve_physics_cfg(physics_cfg: PhysicsCfg | None, use_isaac_sim: bool) -> PhysicsCfg: """Resolve a simulation physics config to a concrete backend.""" if physics_cfg is None: - from isaaclab_physx.physics import PhysxCfg + from isaaclab_newton.physics import NewtonCfg - physics_cfg = PhysxCfg() + physics_cfg = NewtonCfg() elif not isinstance(physics_cfg, PhysicsCfg): raise TypeError(f"SimulationCfg.physics must be a concrete PhysicsCfg, got {type(physics_cfg).__name__}.") diff --git a/source/isaaclab/test/benchmark/test_capture.py b/source/isaaclab/test/benchmark/test_capture.py index 34d903149649..b38895d2b094 100644 --- a/source/isaaclab/test/benchmark/test_capture.py +++ b/source/isaaclab/test/benchmark/test_capture.py @@ -222,7 +222,7 @@ def test_run_config_uses_concrete_backend_configuration(): assert cfg.physics_backend == "physx" default_env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=None)) - assert run_config_from_env_cfg(default_env_cfg).physics_backend == "physx" + assert run_config_from_env_cfg(default_env_cfg).physics_backend == "newton_mjwarp" with pytest.raises(ValueError, match="Unsupported concrete physics config"): run_config_from_env_cfg(SimpleNamespace(sim=SimpleNamespace(physics=object()))) diff --git a/source/isaaclab/test/sim/test_simulation_context.py b/source/isaaclab/test/sim/test_simulation_context.py index 325ec6e08176..501fe35c4436 100644 --- a/source/isaaclab/test/sim/test_simulation_context.py +++ b/source/isaaclab/test/sim/test_simulation_context.py @@ -59,6 +59,7 @@ def test_init(device): gravity=(0.0, -0.5, -0.5), physics_material=RigidBodyMaterialCfg(), render_interval=5, + physics=PhysxCfg(), ) # sim = SimulationContext(cfg) # TODO: Figure out why keyword argument doesn't work. @@ -94,6 +95,15 @@ def test_init(device): np.testing.assert_almost_equal(gravity, cfg.gravity) +@pytest.mark.isaacsim_ci +def test_default_physics_is_newton_mjwarp(): + """An omitted physics config resolves to the Newton MJWarp default.""" + sim = SimulationContext(SimulationCfg()) + + assert isinstance(sim.cfg.physics, NewtonCfg) + assert isinstance(sim.cfg.physics.solver_cfg, MJWarpSolverCfg) + + @pytest.mark.isaacsim_ci @pytest.mark.parametrize( "physics_cfg", From b509fb4d3f46a4736d56cd9894e8f7b374d4a896 Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sun, 23 Aug 2026 16:28:50 -0700 Subject: [PATCH 4/6] Restore PhysX default after preset cleanup --- .../remove-post-hydra-preset-resolution.major.rst | 6 ------ source/isaaclab/isaaclab/benchmark/capture.py | 2 +- source/isaaclab/isaaclab/sim/simulation_cfg.py | 4 ++-- source/isaaclab/isaaclab/sim/simulation_context.py | 4 ++-- source/isaaclab/test/benchmark/test_capture.py | 2 +- source/isaaclab/test/sim/test_simulation_context.py | 10 ---------- .../isaaclab_experimental/test/envs/test_frontend.py | 5 +++-- .../test/core/test_runtime_compatibility.py | 9 ++++----- 8 files changed, 13 insertions(+), 29 deletions(-) diff --git a/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst b/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst index 89bc6c691bd4..93d399c93269 100644 --- a/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst +++ b/source/isaaclab/changelog.d/remove-post-hydra-preset-resolution.major.rst @@ -8,9 +8,3 @@ Removed * **Breaking:** Replaced ``run_config_from_presets`` with ``run_config_from_env_cfg`` in benchmark capture. Pass the concrete composed environment configuration instead of inferring backends from selector strings. - -Changed -^^^^^^^ - -* **Breaking:** Changed an omitted :attr:`isaaclab.sim.SimulationCfg.physics` configuration to use - Newton MJWarp by default. Pass an explicit ``PhysxCfg`` to retain Isaac Sim PhysX. diff --git a/source/isaaclab/isaaclab/benchmark/capture.py b/source/isaaclab/isaaclab/benchmark/capture.py index bbfc00e56f5a..d93c47821039 100644 --- a/source/isaaclab/isaaclab/benchmark/capture.py +++ b/source/isaaclab/isaaclab/benchmark/capture.py @@ -233,7 +233,7 @@ def _backends_from_env_cfg(env_cfg: object) -> tuple[str | None, str | None]: physics_cfg = getattr(getattr(env_cfg, "sim", None), "physics", None) physics_type = type(physics_cfg) physics_descriptor = ( - "newton_mjwarp" + "physx" if physics_cfg is None else f"{physics_type.__module__}.{physics_type.__name__} {getattr(physics_cfg, 'class_type', '')}".lower() ) diff --git a/source/isaaclab/isaaclab/sim/simulation_cfg.py b/source/isaaclab/isaaclab/sim/simulation_cfg.py index c4cec931f12e..d3105f8011a6 100644 --- a/source/isaaclab/isaaclab/sim/simulation_cfg.py +++ b/source/isaaclab/isaaclab/sim/simulation_cfg.py @@ -98,10 +98,10 @@ class SimulationCfg: """ physics: PhysicsCfg | None = None - """Physics manager configuration. Default is None (uses NewtonCfg with MJWarp). + """Physics manager configuration. Default is None (uses PhysxCfg()). This configuration determines which physics manager to use. Override with - a different concrete config to use another physics backend or Newton solver. + a different config (e.g., NewtonManagerCfg) to use a different physics backend. """ create_stage_in_memory: bool = False diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 69477c0a87a4..333f6dd2caf4 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -49,9 +49,9 @@ def _resolve_physics_cfg(physics_cfg: PhysicsCfg | None, use_isaac_sim: bool) -> PhysicsCfg: """Resolve a simulation physics config to a concrete backend.""" if physics_cfg is None: - from isaaclab_newton.physics import NewtonCfg + from isaaclab_physx.physics import PhysxCfg - physics_cfg = NewtonCfg() + physics_cfg = PhysxCfg() elif not isinstance(physics_cfg, PhysicsCfg): raise TypeError(f"SimulationCfg.physics must be a concrete PhysicsCfg, got {type(physics_cfg).__name__}.") diff --git a/source/isaaclab/test/benchmark/test_capture.py b/source/isaaclab/test/benchmark/test_capture.py index b38895d2b094..34d903149649 100644 --- a/source/isaaclab/test/benchmark/test_capture.py +++ b/source/isaaclab/test/benchmark/test_capture.py @@ -222,7 +222,7 @@ def test_run_config_uses_concrete_backend_configuration(): assert cfg.physics_backend == "physx" default_env_cfg = SimpleNamespace(sim=SimpleNamespace(physics=None)) - assert run_config_from_env_cfg(default_env_cfg).physics_backend == "newton_mjwarp" + assert run_config_from_env_cfg(default_env_cfg).physics_backend == "physx" with pytest.raises(ValueError, match="Unsupported concrete physics config"): run_config_from_env_cfg(SimpleNamespace(sim=SimpleNamespace(physics=object()))) diff --git a/source/isaaclab/test/sim/test_simulation_context.py b/source/isaaclab/test/sim/test_simulation_context.py index 501fe35c4436..325ec6e08176 100644 --- a/source/isaaclab/test/sim/test_simulation_context.py +++ b/source/isaaclab/test/sim/test_simulation_context.py @@ -59,7 +59,6 @@ def test_init(device): gravity=(0.0, -0.5, -0.5), physics_material=RigidBodyMaterialCfg(), render_interval=5, - physics=PhysxCfg(), ) # sim = SimulationContext(cfg) # TODO: Figure out why keyword argument doesn't work. @@ -95,15 +94,6 @@ def test_init(device): np.testing.assert_almost_equal(gravity, cfg.gravity) -@pytest.mark.isaacsim_ci -def test_default_physics_is_newton_mjwarp(): - """An omitted physics config resolves to the Newton MJWarp default.""" - sim = SimulationContext(SimulationCfg()) - - assert isinstance(sim.cfg.physics, NewtonCfg) - assert isinstance(sim.cfg.physics.solver_cfg, MJWarpSolverCfg) - - @pytest.mark.isaacsim_ci @pytest.mark.parametrize( "physics_cfg", diff --git a/source/isaaclab_experimental/test/envs/test_frontend.py b/source/isaaclab_experimental/test/envs/test_frontend.py index a56b7f368156..31b59bf952e6 100644 --- a/source/isaaclab_experimental/test/envs/test_frontend.py +++ b/source/isaaclab_experimental/test/envs/test_frontend.py @@ -228,8 +228,9 @@ def test_require_newton_passes_for_newton(): def test_require_newton_rejects_physx(): with pytest.raises(FrontendIncompatibleError) as exc: fe.WarpFrontend._require_newton_physics(_cfg_with_physics(PhysxCfg()), "Isaac-Test-v0") - assert "presets=newton_mjwarp" in str(exc.value) - assert "PhysxCfg" in str(exc.value) + message = str(exc.value) + assert "Select Newton while composing the task configuration" in message + assert "PhysxCfg" in message def test_require_newton_rejects_none(): diff --git a/source/isaaclab_tasks/test/core/test_runtime_compatibility.py b/source/isaaclab_tasks/test/core/test_runtime_compatibility.py index fce37d4d4c01..12c347a09c9e 100644 --- a/source/isaaclab_tasks/test/core/test_runtime_compatibility.py +++ b/source/isaaclab_tasks/test/core/test_runtime_compatibility.py @@ -7,9 +7,8 @@ The OVRTX renderer is kitless and cannot run together with Isaac Sim / Kit runtimes (``PhysxCfg`` physics or the Kit visualizer). These tests verify that -invalid combinations selected via ``presets=...`` (or ``--visualizer kit``) raise -a clear error pointing the user at the correct ``isaacsim_rtx`` preset. -No Kit/GPU required — safe for CI and beginners. +invalid composed configurations raise a clear error identifying compatible +concrete renderer configurations. No Kit/GPU required. """ import argparse @@ -70,7 +69,7 @@ def test_isaacsim_physx_plus_ovrtx_raises(): validate_runtime_compatibility(env_cfg) msg = str(excinfo.value) assert "PhysxCfg" in msg - assert "isaacsim_rtx" in msg + assert "IsaacRtxRendererCfg" in msg def test_kit_visualizer_plus_ovrtx_raises(): @@ -85,7 +84,7 @@ def test_kit_visualizer_plus_ovrtx_raises(): validate_runtime_compatibility(env_cfg, launcher_args) msg = str(excinfo.value) assert "Kit visualizer" in msg - assert "isaacsim_rtx" in msg + assert "IsaacRtxRendererCfg" in msg def test_kit_visualizer_dict_args_plus_ovrtx_raises(): From daca1942c8dbd2cbeca8ac37084dda4bf945d624 Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sun, 23 Aug 2026 16:32:19 -0700 Subject: [PATCH 5/6] Verify camera CLI overrides reach Gym space --- source/isaaclab_tasks/test/core/test_preset_kit_decision.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py index bb97a9d167d9..0c89877854ec 100644 --- a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py +++ b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py @@ -11,6 +11,7 @@ """ import sys +from types import SimpleNamespace import gymnasium as gym import pytest @@ -72,12 +73,17 @@ def test_camera_cli_size_overrides_update_observation_space(monkeypatch: pytest. def fake_parent_init(self, cfg, *_args, **_kwargs): self.cfg = cfg + self.scene = SimpleNamespace(num_envs=2) + self.sim = SimpleNamespace(device="cpu") + self._configure_gym_env_spaces() self._is_closed = True monkeypatch.setattr(CartpoleEnv, "__init__", fake_parent_init) env = CartpoleCameraEnv(env_cfg) assert env.cfg.observation_space == [3, 45, 80] + assert env.single_observation_space["policy"].shape == (3, 45, 80) + assert env.observation_space.shape == (2, 3, 45, 80) def test_rtx_is_renderer_selector(): From f9a431cfb4371891428df4b7e6c6f1c24d0f1b9e Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sun, 23 Aug 2026 22:28:39 -0700 Subject: [PATCH 6/6] Keep heterogeneous task composition backend-neutral --- scripts/demos/heterogeneous_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/demos/heterogeneous_scene.py b/scripts/demos/heterogeneous_scene.py index 7db27a73d6a5..31bc8a133865 100644 --- a/scripts/demos/heterogeneous_scene.py +++ b/scripts/demos/heterogeneous_scene.py @@ -102,7 +102,7 @@ def _load_task_scenes() -> tuple[list[str], list[InteractiveSceneCfg]]: raise ValueError("Select at least two task scenes.") scene_cfgs = [] for task_id in task_ids: - env_cfg, _ = resolve_task_config(task_id, "", overrides=(f"physics={args_cli.physics}", *hydra_args)) + env_cfg, _ = resolve_task_config(task_id, "", overrides=hydra_args) scene_cfgs.append(env_cfg.scene) return task_ids, scene_cfgs