Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Test-only: ran the Newton environment smoke tests kit-less, split the Kit-requiring tasks into

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning · Api — Public API change filed as skip fragment

The fragment uses the .skip tier, which the repository rules reserve for CI/docs/test-only changes and which yields no changelog entry and no version bump. This PR also changes public source: parse_env_cfg gains a presets parameter and new preset-resolution behavior. File an .rst fragment for isaaclab_tasks with an Added/Fixed entry describing parse_env_cfg(..., presets=...). The text also mentions a file split not present in this diff.

their own file, and limited OpenUSD's work-thread pool to work around the usd-core<26.5 physics
parser race that aborted the process during the Newton USD import.
17 changes: 13 additions & 4 deletions source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os
import re
import warnings
from collections.abc import Sequence
from typing import TYPE_CHECKING

import gymnasium as gym
Expand Down Expand Up @@ -144,7 +145,11 @@ def load_cfg_from_registry(task_name: str, entry_point_key: str) -> dict | objec


def parse_env_cfg(
task_name: str, device: str = "cuda:0", num_envs: int | None = None, use_fabric: bool | None = None
task_name: str,
device: str = "cuda:0",
num_envs: int | None = None,
use_fabric: bool | None = None,
presets: Sequence[str] = (),
) -> ManagerBasedRLEnvCfg | DirectRLEnvCfg:
"""Parse configuration for an environment and override based on inputs.

Expand All @@ -155,6 +160,10 @@ def parse_env_cfg(
use_fabric: Whether to enable/disable fabric interface. If false, all read/write operations go through USD.
This slows down the simulation but allows seeing the changes in the USD through the USD stage.
Defaults to None, in which case it is left unchanged.
presets: Preset names to select while resolving :class:`PresetCfg` wrappers, mirroring the
Hydra ``presets=`` CLI tokens. Defaults to an empty selection, which resolves to each
wrapper's default. A selection must be passed here rather than applied afterwards,
because the returned config no longer carries the wrappers to choose from.

Returns:
The parsed configuration object.
Expand All @@ -171,12 +180,12 @@ def parse_env_cfg(
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).
# Resolve any PresetCfg wrappers, honoring the requested presets and otherwise falling back to
# each wrapper's default 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)
cfg = resolve_presets(cfg, presets)

# simulation device
cfg.sim.device = device
Expand Down
22 changes: 15 additions & 7 deletions source/isaaclab_tasks/test/core/test_environments_newton.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
#
# SPDX-License-Identifier: BSD-3-Clause

"""Launch Isaac Sim Simulator first."""
"""Newton environment smoke tests.

from isaaclab.app import AppLauncher
The MJWarp preset is a kit-less backend, so this suite runs without ``AppLauncher``. Tasks carrying
Kit camera sensors still need the Kit runtime and are filtered out; ``test_environments.py`` already
smoke-tests each of them on its default backend.
"""

# launch the simulator
app_launcher = AppLauncher(headless=True, enable_cameras=True)
simulation_app = app_launcher.app
import os


"""Rest everything follows."""
# Limit OpenUSD's work-thread pool to one thread to avoid a race condition in usd-core<26.5, which
# corrupts native state while collecting collider descriptors and aborts the process during the
# Newton USD import. Set at module scope rather than in a fixture, unlike the same workaround in
# rendering_test_utils.py: kit-less, OpenUSD builds its task arena while tests are collected, and
# WorkSetConcurrencyLimit does not shrink an arena that already exists.
# TODO: Remove once usd-core>=26.5 is the minimum - that release fixes the race condition.
os.environ.setdefault("PXR_WORK_THREAD_LIMIT", "1")

import pytest

Expand All @@ -30,6 +36,8 @@
multi_agent=False,
newton_mjwarp_envs=True,
tier="core",
needs_kit=False,
physics_preset_name="newton_mjwarp",
),
)
@pytest.mark.newton_ci
Expand Down
47 changes: 47 additions & 0 deletions source/isaaclab_tasks/test/core/test_parse_env_cfg_presets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Tests that :func:`parse_env_cfg` honors a requested preset selection.

A selection has to be supplied while the ``PresetCfg`` wrappers are being resolved: the returned
config no longer carries the alternatives, so applying a preset afterwards silently keeps the
default. These cases use tasks whose default backend is *not* the requested one, so a dropped
selection fails instead of coincidentally matching.
"""

import pytest
from isaaclab_newton.physics import NewtonCfg
from isaaclab_physx.physics import PhysxCfg

import isaaclab_tasks # noqa: F401
from isaaclab_tasks.utils.parse_cfg import parse_env_cfg

pytestmark = pytest.mark.unit

# Defaults to PhysX and offers a newton_mjwarp preset, so the selection has to do real work.
_PHYSX_DEFAULT_TASK = "Isaac-Velocity-Flat-G1"


def test_parse_env_cfg_defaults_to_the_preset_default() -> None:
"""Without a selection, a wrapper still resolves to its default."""
env_cfg = parse_env_cfg(_PHYSX_DEFAULT_TASK)

assert isinstance(env_cfg.sim.physics, PhysxCfg)


def test_parse_env_cfg_applies_requested_physics_preset() -> None:
"""A requested preset must win over the task's default backend."""
env_cfg = parse_env_cfg(_PHYSX_DEFAULT_TASK, presets=("newton_mjwarp",))

assert isinstance(env_cfg.sim.physics, NewtonCfg)


def test_parse_env_cfg_applies_preset_alongside_other_overrides() -> None:
"""Selecting a preset must not discard the device and num_envs overrides."""
env_cfg = parse_env_cfg(_PHYSX_DEFAULT_TASK, device="cuda:0", num_envs=3, presets=("newton_mjwarp",))

assert isinstance(env_cfg.sim.physics, NewtonCfg)
assert env_cfg.sim.device == "cuda:0"
assert env_cfg.scene.num_envs == 3
61 changes: 44 additions & 17 deletions source/isaaclab_tasks/test/env_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from isaaclab.sim import SimulationContext
from isaaclab.utils.version import get_isaac_sim_version

from isaaclab_tasks.utils.hydra import apply_overrides, collect_presets
from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry, parse_env_cfg

# Map of task IDs to the reason for marking the corresponding parametrized
Expand Down Expand Up @@ -103,6 +102,26 @@ def _has_physics_preset(raw_cfg, preset_name: str) -> bool:
return physics is not None and hasattr(physics, preset_name)


def _task_needs_kit(task_id: str, physics_preset_name: str | None = None) -> bool:
"""Check whether a task's resolved config requires the Isaac Sim Kit runtime.

Derived from the same scan :func:`~isaaclab.app.launch_simulation` performs, so the
kit-less split follows the launcher instead of a hand-maintained task list.

Args:
task_id: Registered task ID.
physics_preset_name: Physics preset applied before scanning, since the backend
selection decides whether Kit is needed.

Returns:
True if the task needs the Kit runtime.
"""
from isaaclab.app import sim_launcher

env_cfg = parse_env_cfg(task_id, presets=(physics_preset_name,) if physics_preset_name else ())
return bool(sim_launcher.scan(env_cfg, {}).needs_kit)


def setup_environment(
include_play: bool = False,
factory_envs: bool | None = None,
Expand All @@ -112,6 +131,8 @@ def setup_environment(
pickplace_stack_envs: bool | None = None,
newton_mjwarp_envs: bool | None = None,
tier: str | None = None,
needs_kit: bool | None = None,
physics_preset_name: str | None = None,
) -> list[str]:
"""
Acquire all registered Isaac environment task IDs with optional filters.
Expand Down Expand Up @@ -146,6 +167,12 @@ def setup_environment(
- "core": include only core environments (registered under ``isaaclab_tasks.core``).
- "contrib": include only contributed environments (registered under ``isaaclab_tasks.contrib``).
- None: include all environments regardless of tier.
needs_kit:
- True: include only environments whose resolved config needs the Kit runtime.
- False: include only environments that run kit-less.
- None: include all environments regardless of Kit requirement.
physics_preset_name: Physics preset applied before deciding the Kit requirement, since the
backend selection determines whether Kit is needed. Only used with ``needs_kit``.

Returns:
A sorted list of task IDs matching the selected filters.
Expand Down Expand Up @@ -222,6 +249,11 @@ def setup_environment(
continue
# if None: no filter

# apply Kit-runtime filter
if needs_kit is not None and _task_needs_kit(task_spec.id, physics_preset_name) != needs_kit:
continue
# if None: no filter

registered_tasks.append(task_spec.id)

# sort environments alphabetically
Expand Down Expand Up @@ -290,8 +322,10 @@ def _run_environments(
If None, uses the environment's default physics.
"""

# skip test if stage in memory is not supported
if get_isaac_sim_version().major < 5 and create_stage_in_memory:
# skip test if stage in memory is not supported. Only query the Isaac Sim version when the
# answer can matter: get_isaac_sim_version() imports isaacsim, which a kit-less caller does
# not have.
if create_stage_in_memory and get_isaac_sim_version().major < 5:
pytest.skip("Stage in memory is not supported in this version of Isaac Sim")

# skip suction gripper environments as they require CPU simulation and cannot be run with GPU simulation
Expand Down Expand Up @@ -370,20 +404,13 @@ def _check_random_actions(
get_settings_manager().set_bool("/isaaclab/render/rtx_sensors", False)
env = None
try:
# parse config
env_cfg = parse_env_cfg(task_name, device=device, num_envs=num_envs)
# apply physics preset override before creating the environment
if physics_preset_name is not None:
# parse_env_cfg already resolved PresetCfg wrappers to their default,
# so we load the raw config to retrieve preset alternatives.
raw_cfg = load_cfg_from_registry(task_name, "env_cfg_entry_point")
presets = {"env": collect_presets(raw_cfg), "agent": {}}
hydra_cfg = {"env": env_cfg.to_dict(), "agent": None}
apply_overrides(env_cfg, None, hydra_cfg, [physics_preset_name], [], [], presets)
# Re-apply num_envs since apply_overrides may have replaced
# the scene config with the preset's default num_envs.
if num_envs is not None:
env_cfg.scene.num_envs = num_envs
# parse config, selecting the requested physics preset as it is resolved
env_cfg = parse_env_cfg(
task_name,
device=device,
num_envs=num_envs,
presets=(physics_preset_name,) if physics_preset_name else (),
)
# set config args
env_cfg.sim.create_stage_in_memory = create_stage_in_memory
if disable_clone_in_fabric:
Expand Down
Loading