diff --git a/.github/actions/validate-kitless-image/action.yml b/.github/actions/validate-kitless-image/action.yml
index 644fc7b99d9c..f45dbd6b3707 100644
--- a/.github/actions/validate-kitless-image/action.yml
+++ b/.github/actions/validate-kitless-image/action.yml
@@ -54,10 +54,8 @@ runs:
print("ovrtx renderer initialized")
'
- # The Newton training probe is the shared one architecture CI also runs, so
- # the command and its assertions live in one place. pytest goes only into
- # this disposable layer; reinstalling the workspace would fail against the
- # read-only source mount.
+ # Discover files by grep, not by pointing pytest at a directory: this image has no Isaac
+ # Sim, and collecting a directory imports every module to read its markers.
docker run --rm --gpus all --network host \
"${mount_args[@]}" \
"$IMAGE_TAG" \
@@ -67,6 +65,8 @@ runs:
python -m pytest \
source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py::test_train_cartpole_state_completes \
source/isaaclab/test/benchmark/test_asset_suite_runtime_semantics.py \
+ source/isaaclab/test/sim/test_urdf_converter.py \
+ source/isaaclab/test/sim/test_mjcf_converter.py \
-q -p no:cacheprovider
'
diff --git a/docker/Dockerfile.kitless b/docker/Dockerfile.kitless
index 403cbfe67d7c..08ec1f1d84ec 100644
--- a/docker/Dockerfile.kitless
+++ b/docker/Dockerfile.kitless
@@ -46,12 +46,13 @@ COPY source/ source/
COPY isaaclab.sh ./
# Same entry point as Dockerfile.base. The selectors are explicit because a bare
-# --install excludes `ov` and `visualizer`; both take `[all]` here. The venv pins
+# --install excludes `ov` and `visualizer`; both take `[all]` here. `importers` carries
+# the standalone URDF/MJCF importers that replace the Isaac Sim ones. The venv pins
# python3.12 to match the runtime stage's libpython3.12, and isaaclab.sh resolves
# VIRTUAL_ENV first.
RUN uv venv --python /usr/bin/python3.12 --seed --no-managed-python "${VIRTUAL_ENV}" \
&& chmod +x "${ISAACLAB_PATH}/isaaclab.sh" \
- && "${ISAACLAB_PATH}/isaaclab.sh" --install newton,rl[all],ov[all],visualizer[all] \
+ && "${ISAACLAB_PATH}/isaaclab.sh" --install newton,rl[all],ov[all],visualizer[all],importers \
&& python -c "import importlib.metadata as m; \
names = {d.metadata['Name'].lower() for d in m.distributions()}; \
assert 'isaacsim' not in names; \
diff --git a/docs/source/api/lab/isaaclab.sim.converters.rst b/docs/source/api/lab/isaaclab.sim.converters.rst
index 6fd5155c4e53..db3d8add909f 100644
--- a/docs/source/api/lab/isaaclab.sim.converters.rst
+++ b/docs/source/api/lab/isaaclab.sim.converters.rst
@@ -38,7 +38,7 @@ Mesh Converter
:members:
:inherited-members:
:show-inheritance:
- :exclude-members: __init__
+ :exclude-members: __init__, PhysicsVariant
URDF Converter
@@ -53,7 +53,7 @@ URDF Converter
:members:
:inherited-members:
:show-inheritance:
- :exclude-members: __init__
+ :exclude-members: __init__, PhysicsVariant
MJCF Converter
--------------
@@ -67,4 +67,4 @@ MJCF Converter
:members:
:inherited-members:
:show-inheritance:
- :exclude-members: __init__
+ :exclude-members: __init__, PhysicsVariant
diff --git a/docs/source/how-to/import_new_asset.rst b/docs/source/how-to/import_new_asset.rst
index 3a5f47d98e7b..111497dade1a 100644
--- a/docs/source/how-to/import_new_asset.rst
+++ b/docs/source/how-to/import_new_asset.rst
@@ -25,8 +25,8 @@ use the Omniverse Kit to edit the asset and export it to other file formats. Isa
these importers by default. They can also be enabled manually in Omniverse Kit.
Isaac Lab's URDF and MJCF converter utilities first use the importer APIs from
-Isaac Sim when the full runtime is installed. In kit-less environments, install
-the standalone importer wheel as described in
+Isaac Sim when the full runtime is installed. In kit-less environments they fall back
+to the standalone importers described in
:ref:`installation-standalone-importers` below.
The Kit visualizer and GUI import dialogs still require an Omniverse Kit runtime.
@@ -43,25 +43,17 @@ For more details on instanceable assets, please check the Isaac Sim `documentati
Standalone URDF/MJCF importers
------------------------------
-The URDF and MJCF converter scripts can run without Isaac Sim when the standalone
-``isaacsim-asset-isolated`` wheel is installed in the active environment. The wheel is not
-published on PyPI, so replace ``PACKAGE_INDEX_URL`` with the package index that hosts it:
+The URDF and MJCF converter scripts run without Isaac Sim. The standalone
+``isaacsim-asset-isolated`` wheel is a base dependency, so no extra install step is needed.
+Optionally pass ``--viz newton`` (or ``rerun`` / ``viser``) to preview the converted asset in a
+kit-less Isaac Lab visualizer:
.. code-block:: bash
- uv pip install "isaacsim-asset-isolated>=6.0,<6.1" \
- --extra-index-url "PACKAGE_INDEX_URL"
-
-After installing the wheel, run conversion in the kit-less environment. Optionally pass
-``--viz newton`` (or ``rerun`` / ``viser``) to preview the converted asset in a kit-less
-Isaac Lab visualizer:
-
-.. code-block:: bash
-
- python scripts/tools/convert_urdf.py \
+ uv run python scripts/tools/convert_urdf.py \
path/to/robot.urdf path/to/output_dir --merge_joints
- python scripts/tools/convert_mjcf.py \
+ uv run python scripts/tools/convert_mjcf.py \
path/to/model.xml path/to/output.usd --merge_mesh
If Isaac Sim is installed in the same environment, Isaac Lab uses the Isaac Sim importer
diff --git a/docs/source/setup/installation/index.rst b/docs/source/setup/installation/index.rst
index a891022ab881..1a7af0195b39 100644
--- a/docs/source/setup/installation/index.rst
+++ b/docs/source/setup/installation/index.rst
@@ -304,8 +304,8 @@ Install `uv `__ or
``-i`` always installs the core source packages. With no value, it also installs the optional
``mimic`` and ``teleop`` submodules plus the default Newton, RL, and visualizer dependencies.
-It does not install ``tetrahedralization``, ``contrib``, ``ov``, or Isaac Sim; request those
-explicitly when needed.
+It does not install ``tetrahedralization``, ``contrib``, ``ov``, or Isaac Sim;
+request those explicitly when needed.
Use ``-i core`` for core packages only. Otherwise, pass a comma-separated list of selectors:
@@ -603,8 +603,8 @@ Extras can be combined freely: none of them conflict, so any set of extras -- in
the Isaac Sim and OV backend stacks together -- resolves into a single environment.
Use ``all`` to get every backend, RL library, and visualizer in one flag. The
specialized extras (``rlinf``, ``mimic``, ``teleop``, ``tetrahedralization``, ``video``,
-``leapp``) and the developer ``test`` tooling are not part of ``all``; request them by
-name.
+``leapp``) and the developer ``test`` tooling are not part of ``all``;
+request them by name.
.. isaaclab-uv-wheel-install::
diff --git a/pyproject.toml b/pyproject.toml
index a96402974ded..4b56d10a4556 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,7 +47,7 @@ dependencies = [
"protobuf>=4.25.8,!=5.26.0",
"hidapi>=0.14.0",
"gymnasium>=1.2.0",
- "trimesh",
+ "trimesh>=4.6.8", # lower bound inherited from newton[importers]
"pyglet>=2.1.6,<3",
"transformers==4.57.6",
"einops",
@@ -66,11 +66,15 @@ dependencies = [
"pin ; platform_system == 'Linux' and (platform_machine == 'x86_64' or platform_machine == 'aarch64')",
"pin-pink==3.3.0 ; platform_system == 'Linux' and (platform_machine == 'x86_64' or platform_machine == 'aarch64')",
"daqp==0.8.5 ; platform_system == 'Linux' and (platform_machine == 'x86_64' or platform_machine == 'aarch64')",
- # OpenUSD (kit-less mode). Both packages vendor a complete ``pxr`` runtime, so
- # they must never co-install: usd-core supplies pxr where it has wheels (x86_64),
- # usd-exchange supplies it on aarch64 (where usd-core has no wheel).
- "usd-core>=25.11,<26.0 ; platform_machine == 'x86_64' or platform_machine == 'AMD64'",
- "usd-exchange>=2.2 ; platform_machine == 'aarch64'",
+ # OpenUSD (kit-less mode). Use the same provider required by the URDF and MJCF importer
+ # packages so another complete ``pxr`` runtime cannot co-install with it. Two providers in
+ # one environment overwrite each other's files, and removing either then breaks ``pxr``.
+ # usd-exchange 2.3.0 vendors USD 25.5, matching the Isaac Sim 6.0 wheel stack.
+ "usd-exchange==2.3.0",
+ # Standalone URDF/MJCF importers, so conversion works without Isaac Sim. They install
+ # alongside it without displacing it: Kit serves ``isaacsim.asset`` from its extension
+ # roots when the runtime is present, and the standalone packages serve it otherwise.
+ "isaacsim-asset-isolated>=6.0,<6.1",
# avoid broken hf-xet pre-release cached on NVIDIA Artifactory
"hf-xet>=1.4.1,<2.0.0 ; platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64'",
# ----- tasks -----
@@ -87,9 +91,19 @@ dependencies = [
# ----- newton (default physics engine) -----
# Loose bound so the wheel co-resolves with isaacsim's newton[sim]==1.2.0 pin; the
# exact git commit is forced via [tool.uv].override-dependencies (uv sync only).
- # The importers extra carries the mesh-processing deps (coacd, fast-simplification,
- # ...) that honoring USD-authored ``physics:approximation`` requires.
- "newton[sim,importers]>=1.2.0",
+ "newton[sim]>=1.2.0",
+ # Import and mesh-processing packages used by Newton, including the ones that honoring
+ # USD-authored ``physics:approximation`` requires. Keep these explicit instead of selecting
+ # newton[importers], whose standalone USD dependency would overlap with usd-exchange.
+ "requests>=2.25.0",
+ "scipy>=1.11.0",
+ "coacd>=1.0.7",
+ "fast-simplification>=0.1.11",
+ "alphashape>=1.3.1",
+ "meshio>=5.3.5",
+ "pycollada>=0.9",
+ "resolve-robotics-uri-py>=0.4.0",
+ "open3d>=0.19.0 ; python_version < '3.13' and (sys_platform != 'linux' or platform_machine != 'aarch64')",
# Loose bound co-resolves with isaacsim's ==0.2.0; the override below forces >=0.4.1.
"newton-usd-schemas>=0.2.0",
"PyOpenGL-accelerate>=3.1.0",
@@ -146,6 +160,7 @@ rerun = [
]
isaacsim = ["isaacsim[all,extscache]==6.0.1.0"]
+
ov = ["ovphysx==0.5.9", "ovrtx>=0.4.0,<0.5.0", "ovstage==0.1.0.346039"]
ovphysx = ["ovphysx==0.5.9", "ovstage==0.1.0.346039"]
ovrtx = ["ovrtx>=0.4.0,<0.5.0", "ovstage==0.1.0.346039"]
@@ -179,9 +194,9 @@ leapp = [
"leapp>=0.5.2",
]
# Every backend, RL library, and visualizer in one flag. No extra is forked in
-# [tool.uv].conflicts, so Isaac Sim and both OV backends resolve into a single
-# environment. The specialized extras (rlinf, mimic, teleop, tetrahedralization,
-# video, leapp) and the developer ``test`` tooling stay opt-in by name.
+# [tool.uv].conflicts, so any combination resolves into a single environment. The
+# specialized extras (rlinf, mimic, teleop, tetrahedralization, video, leapp)
+# and the developer ``test`` tooling stay opt-in by name.
all = [
"isaaclab-dev[sb3,skrl,rl-games,rsl-rl,viser,rerun,isaacsim,ov]",
]
@@ -192,6 +207,7 @@ all = [
# ``test_version_single_source`` fails CI if they drift.
[tool.isaaclab.versions]
isaacsim = "6.0.1.0"
+usd_exchange = "2.3.0"
torch = "2.11.0"
torchvision = "0.26.0"
torchaudio = "2.11.0"
@@ -335,6 +351,7 @@ markers = [
"benchmark: test covers the Isaac Lab benchmark framework and infrastructure",
"rendering: test exercises the rendering / camera / visualizer pipeline",
"smoke: tests for core installation, task, and RL functionality",
+ "kitless: test must pass inside the Kit-less container, which has no Isaac Sim runtime",
]
# Add pypi.nvidia.com so that `uv pip install isaaclab[isaacsim]` works without --extra-index-url.
@@ -380,7 +397,7 @@ override-dependencies = [
"numpy>=2",
"mujoco~=3.11.0",
"mujoco-warp~=3.11.0",
- "newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@release-1.5",
+ "newton[sim] @ git+https://github.com/newton-physics/newton.git@release-1.5",
# Force the Newton-matched schemas over isaacsim's ==0.2.0 pin.
"newton-usd-schemas>=0.4.1",
"torch==2.11.0",
@@ -393,10 +410,6 @@ override-dependencies = [
# ovphysx caps packaging at <24 and isaacsim-core pins ==26.0; both caps are
# stricter than the code needs, so widening lets them resolve together at 26.0.
"packaging>=20,<27",
- # usd-exchange and usd-core each vendor a full ``pxr`` built against a different
- # USD version, and newton[importers] pulls usd-exchange unmarked. Re-impose the
- # platform split from [project.dependencies] so only one lands per platform.
- "usd-exchange>=2.2 ; platform_machine == 'aarch64'",
]
python-preference = "only-managed"
package = false
diff --git a/scripts/tools/convert_mjcf.py b/scripts/tools/convert_mjcf.py
index 803d53347168..8243031c9319 100644
--- a/scripts/tools/convert_mjcf.py
+++ b/scripts/tools/convert_mjcf.py
@@ -36,9 +36,9 @@
"""Parse CLI first so we can decide whether to launch Isaac Sim Kit."""
import argparse
-from importlib import metadata
from isaaclab.app import AppLauncher, add_launcher_args, launch_simulation
+from isaaclab.utils.version import standalone_importers_available
parser = argparse.ArgumentParser(description="Utility to convert a MJCF into USD format.")
parser.add_argument("input", type=str, help="The path to the input MJCF file.")
@@ -82,14 +82,12 @@
add_launcher_args(parser)
args_cli = parser.parse_args()
-# The MJCF importer ships as a Kit extension unless the standalone importer wheel is installed, so
-# Kit is only required when the wheel is absent. With the wheel present the conversion runs kitlessly
-# and the kitless visualizers can host the preview.
-try:
- metadata.distribution("isaacsim-asset-isolated")
- args_cli.require_kit = False
-except metadata.PackageNotFoundError:
- args_cli.require_kit = True
+# Prefer kit-less: it skips Kit startup and the kitless visualizers can host the preview.
+args_cli.require_kit = not standalone_importers_available()
+
+# ``launch_simulation`` receives a bare ``PhysicsCfg()`` placeholder, so name the backend the
+# runtime provides; without it the preview builds a simulation with no physics manager.
+args_cli.physics = "isaacsim_physx" if args_cli.require_kit else "newton_mjwarp"
# Report the missing importer before converting anything. Without this the launcher reports only
# that Isaac Sim is absent, which does not mention the wheel that would make this run kitlessly.
diff --git a/scripts/tools/convert_urdf.py b/scripts/tools/convert_urdf.py
index 8390704404b7..803dd4979bae 100644
--- a/scripts/tools/convert_urdf.py
+++ b/scripts/tools/convert_urdf.py
@@ -36,9 +36,9 @@
"""Parse CLI first so we can decide whether to launch Isaac Sim Kit."""
import argparse
-from importlib import metadata
from isaaclab.app import AppLauncher, add_launcher_args, launch_simulation
+from isaaclab.utils.version import standalone_importers_available
parser = argparse.ArgumentParser(description="Utility to convert a URDF into USD format.")
parser.add_argument("input", type=str, help="The path to the input URDF file.")
@@ -78,14 +78,12 @@
add_launcher_args(parser)
args_cli = parser.parse_args()
-# The URDF importer ships as a Kit extension unless the standalone importer wheel is installed, so
-# Kit is only required when the wheel is absent. With the wheel present the conversion runs kitlessly
-# and the kitless visualizers can host the preview.
-try:
- metadata.distribution("isaacsim-asset-isolated")
- args_cli.require_kit = False
-except metadata.PackageNotFoundError:
- args_cli.require_kit = True
+# Prefer kit-less: it skips Kit startup and the kitless visualizers can host the preview.
+args_cli.require_kit = not standalone_importers_available()
+
+# ``launch_simulation`` receives a bare ``PhysicsCfg()`` placeholder, so name the backend the
+# runtime provides; without it the preview builds a simulation with no physics manager.
+args_cli.physics = "isaacsim_physx" if args_cli.require_kit else "newton_mjwarp"
# Report the missing importer before converting anything. Without this the launcher reports only
# that Isaac Sim is absent, which does not mention the wheel that would make this run kitlessly.
diff --git a/source/isaaclab/changelog.d/jichuanh-standalone-importers.minor.rst b/source/isaaclab/changelog.d/jichuanh-standalone-importers.minor.rst
new file mode 100644
index 000000000000..ef6006b0766a
--- /dev/null
+++ b/source/isaaclab/changelog.d/jichuanh-standalone-importers.minor.rst
@@ -0,0 +1,37 @@
+Added
+^^^^^
+
+* Added the standalone URDF and MJCF importers as base dependencies, so conversion works without
+ Isaac Sim and without an extra install step.
+
+* Added :attr:`~isaaclab.sim.converters.AssetConverterBaseCfg.physics_variant` to choose which
+ ``"Physics"`` variant the URDF and MJCF converters select.
+
+Fixed
+^^^^^
+
+* Fixed ``scripts/tools/convert_urdf.py`` and ``scripts/tools/convert_mjcf.py`` crashing when the
+ converted asset was previewed with a kitless visualizer (``--viz newton``, ``--viz rerun``, or
+ ``--viz viser``), by selecting the physics backend that matches the runtime.
+
+* Fixed URDF and MJCF conversion producing assets with no joints, articulation roots, or mass
+ properties.
+
+* Fixed MJCF conversion failing with ``Cannot find a valid schema for 'MjcSceneAPI'``.
+
+* Fixed installation failures caused by overlapping standalone USD providers by using
+ ``usd-exchange`` on all supported platforms and installing required Newton mesh-processing
+ packages directly.
+
+* Fixed :meth:`~isaaclab.utils.dict.class_to_dict` expanding enum values into their internal
+ members.
+
+Changed
+^^^^^^^
+
+* Changed :meth:`~isaaclab.sim.utils.select_usd_variants` to raise when a variant set exists on the
+ prim but does not offer the requested variant, which includes
+ :attr:`~isaaclab.sim.UsdFileCfg.variants` at spawn time. USD accepts such a selection and composes
+ the prim as if nothing were selected, so the asset used to spawn silently without what the variant
+ carries. A variant set the prim does not have is still skipped with a warning. Set a variant the
+ asset offers, or drop the entry.
diff --git a/source/isaaclab/isaaclab/__init__.py b/source/isaaclab/isaaclab/__init__.py
index 4af707f4a6a7..48b9f61afaa5 100644
--- a/source/isaaclab/isaaclab/__init__.py
+++ b/source/isaaclab/isaaclab/__init__.py
@@ -6,6 +6,7 @@
"""Package containing the core framework."""
import importlib.metadata
+import importlib.util
import os
import sys
@@ -79,7 +80,33 @@ def _should_demote(path: str) -> bool:
os.environ["PYTHONPATH"] = os.pathsep.join(env_clean + env_demoted)
+def _expose_mujoco_usd_schemas():
+ """Put the MuJoCo USD schemas on OpenUSD's plugin search path.
+
+ ``mujoco-usd-converter`` ships them as a codeless schema plugin that it registers when the
+ package is imported. OpenUSD builds its schema registry once, on the first schema query, and
+ ignores plugins registered after that -- so MJCF conversion fails with "Cannot find a valid
+ schema for ``MjcSceneAPI``" whenever anything touched a schema first. Adding the plugin to the
+ search path lets OpenUSD find it while building the registry, so importing the converter late
+ no longer matters.
+
+ OpenUSD reads the search path while building the registry, so this only helps while the
+ registry is still unbuilt. A host that queries a schema before importing Isaac Lab has to put
+ the plugin directory on ``PXR_PLUGINPATH_NAME`` itself.
+ """
+ spec = importlib.util.find_spec("mujoco_usd_converter")
+ if spec is None or spec.origin is None:
+ return
+ plugins = os.path.join(os.path.dirname(spec.origin), "plugins")
+ if not os.path.isdir(plugins):
+ return
+ search_path = os.environ.get("PXR_PLUGINPATH_NAME", "")
+ if plugins not in search_path.split(os.pathsep):
+ os.environ["PXR_PLUGINPATH_NAME"] = os.pathsep.join(filter(None, (search_path, plugins)))
+
+
_deprioritize_prebundle_paths()
+_expose_mujoco_usd_schemas()
try:
diff --git a/source/isaaclab/isaaclab/benchmark/capture.py b/source/isaaclab/isaaclab/benchmark/capture.py
index d885a25da459..ae9b1cb4d0b0 100644
--- a/source/isaaclab/isaaclab/benchmark/capture.py
+++ b/source/isaaclab/isaaclab/benchmark/capture.py
@@ -171,6 +171,7 @@ def capture_versions(bm: Any) -> Versions:
mujoco=md.get("mujoco_version"),
cuda_bindings=md.get("cuda_bindings_version"),
usd_core=md.get("usd_core_version"),
+ usd_exchange=md.get("usd_exchange_version"),
isaaclab_release=md.get("isaaclab_release_version"),
)
diff --git a/source/isaaclab/isaaclab/benchmark/recorders/record_version_info.py b/source/isaaclab/isaaclab/benchmark/recorders/record_version_info.py
index 457953b233af..f5cc62d1e860 100644
--- a/source/isaaclab/isaaclab/benchmark/recorders/record_version_info.py
+++ b/source/isaaclab/isaaclab/benchmark/recorders/record_version_info.py
@@ -131,7 +131,10 @@ def _get_version_info(self) -> None:
# Key dependencies
self._record("gymnasium", self._get_pkg_version("gymnasium"))
self._record("cuda_bindings", self._get_pkg_version("cuda-bindings"))
+ # usd-exchange is the standalone USD provider; usd-core only appears in environments
+ # that predate the switch, so record whichever one is installed.
self._record("usd_core", self._get_pkg_version("usd-core"))
+ self._record("usd_exchange", self._get_pkg_version("usd-exchange"))
# Release version from root VERSION file
version_file = os.path.join(_REPO_ROOT, "VERSION")
diff --git a/source/isaaclab/isaaclab/benchmark/schema.py b/source/isaaclab/isaaclab/benchmark/schema.py
index 4adbdbaf33f7..23652ab14898 100644
--- a/source/isaaclab/isaaclab/benchmark/schema.py
+++ b/source/isaaclab/isaaclab/benchmark/schema.py
@@ -129,6 +129,7 @@ class Versions:
mujoco: str | None = None
cuda_bindings: str | None = None
usd_core: str | None = None
+ usd_exchange: str | None = None
isaaclab_release: str | None = None
diff --git a/source/isaaclab/isaaclab/cli/commands/install.py b/source/isaaclab/isaaclab/cli/commands/install.py
index 0608e73a4034..ff86c29825eb 100644
--- a/source/isaaclab/isaaclab/cli/commands/install.py
+++ b/source/isaaclab/isaaclab/cli/commands/install.py
@@ -1268,10 +1268,6 @@ def append_submodules_once(package_dirs: tuple[str, ...]) -> None:
# root pyproject. torch is excluded — it is handled by _ensure_cuda_torch.
_install_centralized_dependencies(pip_cmd, requested_optional_submodules)
- # Isaac Sim's bundled newton==1.2.0 satisfies the loose core bound, so force the
- # pinned Newton git build (the default physics engine) over it.
- _ensure_newton()
-
# Install requested optional submodule dependency extras.
if optional_submodule_extra_dependencies:
print_info("Installing optional submodule dependencies...")
@@ -1284,6 +1280,13 @@ def append_submodules_once(package_dirs: tuple[str, ...]) -> None:
for feature_name, selector in extra_features:
_install_extra_feature(feature_name, selector)
+ # Isaac Sim's bundled newton==1.2.0 satisfies the loose core bound, so force the
+ # pinned Newton git build (the default physics engine) over it. This runs after every
+ # install pass because they go through pip, which does not see
+ # [tool.uv].override-dependencies: isaacsim-asset-isolated's exact mujoco and
+ # newton-usd-schemas pins would otherwise stand.
+ _ensure_newton()
+
# In some rare cases, torch might not be installed properly by pyproject.toml, add one more check here.
# Can prevent that from happening.
_ensure_cuda_torch()
diff --git a/source/isaaclab/isaaclab/sim/converters/asset_converter_base.py b/source/isaaclab/isaaclab/sim/converters/asset_converter_base.py
index 38cc58161fb3..90ea06009595 100644
--- a/source/isaaclab/isaaclab/sim/converters/asset_converter_base.py
+++ b/source/isaaclab/isaaclab/sim/converters/asset_converter_base.py
@@ -6,6 +6,7 @@
import abc
import hashlib
import json
+import logging
import os
import pathlib
import random
@@ -16,6 +17,8 @@
from isaaclab.utils.assets import check_file_path
from isaaclab.utils.io import dump_yaml
+logger = logging.getLogger(__name__)
+
class AssetConverterBase(abc.ABC):
"""Base class for converting an asset file from different formats into USD format.
@@ -101,14 +104,15 @@ def __init__(self, cfg: AssetConverterBaseCfg):
# convert the asset to USD if the hash is different or USD file does not exist
if cfg.force_usd_conversion or not self._usd_file_exists or not self._is_same_asset:
- # write the updated hash
- with open(self._dest_hash_path, "w") as f:
- f.write(self._asset_hash)
# convert the asset to USD
self._convert_asset(cfg)
- # the importers emit the physics payloads behind a "Physics" variant set but leave it
- # unselected, which composes the asset with geometry only
- self._select_physics_variant()
+ # importers put the physics payloads behind a "Physics" variant set and disagree on
+ # which variant to select, so settle it here
+ self._select_physics_variant(cfg.physics_variant)
+ # record the hash only now: writing it earlier would let a conversion that raised
+ # still count as cached, so an identical retry would skip it and return the asset
+ with open(self._dest_hash_path, "w") as f:
+ f.write(self._asset_hash)
# dump the configuration to a file
dump_yaml(os.path.join(self.usd_dir, "config.yaml"), cfg.to_dict())
# add comment to top of the saved config file with information about the converter
@@ -166,20 +170,25 @@ def _convert_asset(self, cfg: AssetConverterBaseCfg):
Private helpers.
"""
- def _select_physics_variant(self, variant: str = "physx"):
+ def _select_physics_variant(self, variant: str):
"""Author a selection for the ``"Physics"`` variant set on the converted asset.
- The URDF and MJCF importers write the physics description into a ``"Physics"`` variant set
- without selecting a variant, so opening the asset composes it without joints, articulation
- roots, or mass properties. Selecting a variant here restores physics for every consumer. The
- selection is authored on the asset, so a spawner that selects a different variant on the
- referencing prim still wins.
+ Importers put the physics description behind a ``"Physics"`` variant set, and which variant
+ they select is not consistent: the Isaac Sim importer extensions leave the set unselected,
+ which composes the asset without joints, articulation roots, or mass properties, while the
+ standalone importer wheel selects one of its own. Authoring the configured variant here makes
+ the outcome the same either way. The selection is authored on the asset, so a spawner that
+ selects a different variant on the referencing prim still wins.
- Does nothing when the asset has no such variant set or the importer already authored a
- selection.
+ Does nothing when the asset has no such variant set.
Args:
variant: The variant to select.
+
+ Raises:
+ ValueError: When the asset offers a ``"Physics"`` variant set without the requested
+ variant. Substituting another one would silently hand back an asset configured for a
+ different backend than the caller asked for.
"""
from pxr import Usd
@@ -188,7 +197,13 @@ def _select_physics_variant(self, variant: str = "physx"):
if not prim or "Physics" not in prim.GetVariantSets().GetNames():
return
variant_set = prim.GetVariantSets().GetVariantSet("Physics")
- if variant_set.GetVariantSelection() or variant not in variant_set.GetVariantNames():
+ available = variant_set.GetVariantNames()
+ if variant not in available:
+ raise ValueError(
+ f"The converted asset has no '{variant}' physics variant. Set"
+ f" {type(self.cfg).__name__}.physics_variant to one of: {available}."
+ )
+ if variant == variant_set.GetVariantSelection():
return
variant_set.SetVariantSelection(variant)
stage.GetRootLayer().Save()
diff --git a/source/isaaclab/isaaclab/sim/converters/asset_converter_base_cfg.py b/source/isaaclab/isaaclab/sim/converters/asset_converter_base_cfg.py
index 706927fd350e..b34ce4f4f24b 100644
--- a/source/isaaclab/isaaclab/sim/converters/asset_converter_base_cfg.py
+++ b/source/isaaclab/isaaclab/sim/converters/asset_converter_base_cfg.py
@@ -6,6 +6,7 @@
from __future__ import annotations
from dataclasses import MISSING
+from enum import StrEnum
from isaaclab.utils.configclass import configclass
@@ -14,6 +15,21 @@
class AssetConverterBaseCfg:
"""The base configuration class for asset converters."""
+ class PhysicsVariant(StrEnum):
+ """Variants offered by the ``"Physics"`` variant set that the URDF and MJCF importers author."""
+
+ PHYSICS = "physics"
+ """Backend-portable physics: joints, articulation roots, mass, and the Newton schemas."""
+
+ PHYSX = "physx"
+ """:attr:`PHYSICS` plus PhysX-specific tuning."""
+
+ MUJOCO = "mujoco"
+ """:attr:`PHYSICS` plus MuJoCo-specific tuning."""
+
+ NONE = "none"
+ """No physics at all."""
+
asset_path: str = MISSING
"""The absolute path to the asset file to convert into USD."""
@@ -48,3 +64,26 @@ class AssetConverterBaseCfg:
used in the scene. For more information, please check the USD documentation on
`scene-graph instancing `_.
"""
+
+ physics_variant: PhysicsVariant | str = PhysicsVariant.PHYSICS
+ """The ``"Physics"`` variant to select on the generated USD file. Defaults to
+ :attr:`PhysicsVariant.PHYSICS`.
+
+ The URDF and MJCF importers emit physics as payloads behind a ``"Physics"`` variant set, and
+ disagree on which variant to select: the Isaac Sim importer extensions leave the set unselected,
+ which composes the asset without joints, articulation roots, or mass properties, while the
+ standalone importer wheel picks one of its own. The converter authors this selection so that the
+ asset carries the same physics either way.
+
+ The default holds the backend-portable description, and :attr:`PhysicsVariant.PHYSX` /
+ :attr:`PhysicsVariant.MUJOCO` sublayer it, so select one of those to author an asset for a
+ specific backend. When the asset offers the variant set but not the requested variant,
+ conversion raises rather than substituting one authored for a different backend.
+
+ This applies only to assets that carry a ``"Physics"`` variant set. Conversions that emit a
+ flat asset instead, such as the URDF and MJCF converters with ``run_asset_transformer`` set to
+ False, keep whatever physics the importer wrote and ignore this field.
+
+ Every variant stays in the generated USD file, so this only decides what composes by default:
+ :attr:`~isaaclab.sim.UsdFileCfg.variants` overrides the selection at spawn time.
+ """
diff --git a/source/isaaclab/isaaclab/sim/utils/prims.py b/source/isaaclab/isaaclab/sim/utils/prims.py
index a9b3a439b113..417daa1bb086 100644
--- a/source/isaaclab/isaaclab/sim/utils/prims.py
+++ b/source/isaaclab/isaaclab/sim/utils/prims.py
@@ -1038,8 +1038,14 @@ class TableVariants:
variants: A dictionary or config class mapping variant set names to variant selections.
stage: The USD stage. Defaults to None, in which case, the current stage is used.
+ A variant set the prim does not have is skipped with a warning, so one configuration can spawn
+ assets that expose different options. A set that exists but does not offer the requested variant
+ is an error: USD accepts the selection and composes the prim as if nothing were selected, so the
+ asset would silently spawn without the description the variant carries.
+
Raises:
- ValueError: If the prim at the specified path is not valid.
+ ValueError: If the prim at the specified path is not valid, or if a variant set on the prim
+ does not offer the requested variant.
.. _USD Variants: https://graphics.pixar.com/usd/docs/USD-Glossary.html#USDGlossary-Variant
"""
@@ -1063,6 +1069,15 @@ class TableVariants:
continue
variant_set = existing_variant_sets.GetVariantSet(variant_set_name)
+ # USD accepts a selection naming a variant the set does not offer, and the prim then
+ # composes as if nothing were selected, so reject it instead of spawning a silently
+ # incomplete asset.
+ available = variant_set.GetVariantNames()
+ if variant_selection not in available:
+ raise ValueError(
+ f"Variant set '{variant_set_name}' on prim '{prim_path}' does not offer variant"
+ f" '{variant_selection}'. Available variants: {available}."
+ )
# Only set the variant selection if it is different from the current selection.
if variant_set.GetVariantSelection() != variant_selection:
variant_set.SetVariantSelection(variant_selection)
diff --git a/source/isaaclab/isaaclab/utils/dict.py b/source/isaaclab/isaaclab/utils/dict.py
index 88cf63968def..584f4ac2da62 100644
--- a/source/isaaclab/isaaclab/utils/dict.py
+++ b/source/isaaclab/isaaclab/utils/dict.py
@@ -9,6 +9,7 @@
import hashlib
import json
from collections.abc import Iterable, Mapping, Sized
+from enum import Enum
from typing import Any
import torch
@@ -42,6 +43,9 @@ def class_to_dict(obj: object) -> dict[str, Any]:
# ResolvableString is a str subclass — serialize as plain str so OmegaConf accepts it.
if isinstance(obj, ResolvableString):
return str(obj)
+ # Enum members carry a ``__dict__`` of internals, so serialize the value they stand for.
+ if isinstance(obj, Enum):
+ return obj.value
# convert object to dictionary
if isinstance(obj, dict):
obj_dict = obj
@@ -113,6 +117,13 @@ def update_class_from_dict(obj, data: dict[str, Any], _ns: str = "") -> None:
if isinstance(value, Iterable) and not isinstance(value, str):
# ---- 2a) flat iterable → replace wholesale ----------
if all(not isinstance(el, Mapping) for el in value):
+ # class_to_dict serialized enum members as their values, so rebuild them
+ # from the member type the existing container holds.
+ if isinstance(obj_mem, Iterable) and not isinstance(obj_mem, str):
+ enum_types = {type(el) for el in obj_mem if isinstance(el, Enum)}
+ if len(enum_types) == 1:
+ enum_type = enum_types.pop()
+ value = [el if isinstance(el, Enum) else enum_type(el) for el in value]
out_val = tuple(value) if isinstance(obj_mem, tuple) else value
if isinstance(obj, dict):
obj[key] = out_val
@@ -158,6 +169,11 @@ def update_class_from_dict(obj, data: dict[str, Any], _ns: str = "") -> None:
f" Expected callable or callable-string, Received: {type(value)}."
)
+ # -- 3b) enum attribute → rebuild the member from its value ------------
+ elif isinstance(obj_mem, Enum) and value is not None:
+ # class_to_dict serializes members as their value, so restore the member here
+ value = type(obj_mem)(value)
+
# -- 4) simple scalar / explicit None ---------------------
elif value is None or isinstance(value, type(obj_mem)):
pass
diff --git a/source/isaaclab/isaaclab/utils/version.py b/source/isaaclab/isaaclab/utils/version.py
index 7627c89c7947..cc2a8ce2ae67 100644
--- a/source/isaaclab/isaaclab/utils/version.py
+++ b/source/isaaclab/isaaclab/utils/version.py
@@ -38,6 +38,29 @@ def has_kit() -> bool:
return False
+def standalone_importers_available() -> bool:
+ """Check whether the standalone URDF/MJCF importers can be imported in this environment.
+
+ Being installed is not enough: the ``isaacsim-asset-isolated`` wheel contributes
+ ``isaacsim.asset`` as a PEP 420 namespace portion, and the Isaac Sim runtime ships
+ ``isaacsim`` as a regular package, which discards every portion for that name. Resolving the
+ spec detects that without importing ``isaacsim``, so this stays safe before a launch decision.
+
+ Returns:
+ Whether the standalone importers are installed and reachable.
+ """
+ import importlib.machinery
+ from importlib import metadata
+
+ try:
+ metadata.distribution("isaacsim-asset-isolated")
+ except metadata.PackageNotFoundError:
+ return False
+ found = importlib.machinery.PathFinder().find_spec("isaacsim")
+ # ``origin`` is None only for a namespace package; a regular package reports its __init__.py
+ return found is not None and found.origin is None
+
+
@functools.lru_cache(maxsize=1)
def get_isaac_sim_version() -> Version:
"""Get the Isaac Sim version as a Version object, cached for performance.
diff --git a/source/isaaclab/test/app/standalone_script_cases.py b/source/isaaclab/test/app/standalone_script_cases.py
index 829ec5836ada..073d4e0724c2 100644
--- a/source/isaaclab/test/app/standalone_script_cases.py
+++ b/source/isaaclab/test/app/standalone_script_cases.py
@@ -14,12 +14,19 @@
import selectors
import signal
import subprocess
+import tempfile
import time
from dataclasses import dataclass, field
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
SCRIPT_ROOTS = (ROOT / "scripts" / "demos", ROOT / "scripts" / "tutorials")
+# ``scripts/tools`` is not a root because most of its scripts are not simulator launches. The asset
+# converters are: they build a SimulationContext to preview the converted asset.
+EXTRA_SCRIPTS = (
+ ROOT / "scripts" / "tools" / "convert_urdf.py",
+ ROOT / "scripts" / "tools" / "convert_mjcf.py",
+)
VISUALIZERS = ("none", "kit", "newton", "rerun", "viser")
DEFAULT_READINESS_PATTERN = r"Setup complete"
MAX_OUTPUT_BYTES = 4 * 1024 * 1024
@@ -134,6 +141,9 @@ class SmokeResult:
fatal_patterns: tuple[str, ...] = ()
+# newton ships the same NVIDIA Ant model the Kit importer bundles, as in test_mjcf_converter.py.
+_NEWTON_MJCF = str(Path(importlib.util.find_spec("newton").origin).parent / "examples" / "assets" / "nv_ant.xml")
+
OVERRIDES = {
"scripts/demos/arl_robot_1.py": ScriptOverride(readiness_pattern=r"Starting demo with Lee Position Controller"),
"scripts/demos/h1_locomotion.py": ScriptOverride(
@@ -210,6 +220,19 @@ class SmokeResult:
visualizers=("none",),
required_modules=("ovrtx",),
),
+ # Readiness fires once conversion succeeds, so the preview runs inside the soak.
+ "scripts/tools/convert_urdf.py": ScriptOverride(
+ args=(
+ str(ROOT / "source" / "isaaclab" / "test" / "sim" / "urdfs" / "test_merge_joints.urdf"),
+ str(Path(tempfile.gettempdir()) / "isaaclab_converter_smoke" / "urdf"),
+ "--merge_joints",
+ ),
+ readiness_pattern=r"Generated USD file:",
+ ),
+ "scripts/tools/convert_mjcf.py": ScriptOverride(
+ args=(_NEWTON_MJCF, str(Path(tempfile.gettempdir()) / "isaaclab_converter_smoke" / "mjcf")),
+ readiness_pattern=r"Generated USD file:",
+ ),
"scripts/tutorials/00_sim/create_empty.py": ScriptOverride(visualizers=("none", "kit")),
"scripts/tutorials/00_sim/launch_app.py": ScriptOverride(visualizers=("none", "kit")),
"scripts/tutorials/00_sim/log_time.py": ScriptOverride(visualizers=("none", "kit")),
@@ -242,10 +265,10 @@ class SmokeResult:
def discover_specs() -> list[ScriptSpec]:
- """Discover executable demo/tutorial scripts and their literal CLI choices."""
+ """Discover the executable demo, tutorial, and tool scripts and their literal CLI choices."""
specs = []
- for root in SCRIPT_ROOTS:
- for path in sorted(root.rglob("*.py")):
+ for group in (*(sorted(root.rglob("*.py")) for root in SCRIPT_ROOTS), EXTRA_SCRIPTS):
+ for path in group:
source = path.read_text(encoding="utf-8")
tree = ast.parse(source, filename=str(path))
if not _has_main_guard(tree):
diff --git a/source/isaaclab/test/cli/test_install.py b/source/isaaclab/test/cli/test_install.py
index 28809f11fe71..511a709d0744 100644
--- a/source/isaaclab/test/cli/test_install.py
+++ b/source/isaaclab/test/cli/test_install.py
@@ -264,7 +264,7 @@ def fake_run(cmd, *args, **kwargs):
install_cmds = [cmd for cmd in calls if "install" in cmd]
assert install_cmds, "expected a pip install call"
install_args = install_cmds[-1]
- assert any(arg.startswith("newton[sim,importers]") and arg.endswith(commit) for arg in install_args)
+ assert any(arg.startswith("newton[sim]") and arg.endswith(commit) for arg in install_args)
assert any(arg.startswith("newton-usd-schemas") for arg in install_args), "schemas must be forced too"
def test_skips_when_commit_already_installed(self):
diff --git a/source/isaaclab/test/cli/test_source_package_metadata.py b/source/isaaclab/test/cli/test_source_package_metadata.py
index 46accda6fbe0..29c6844730c4 100644
--- a/source/isaaclab/test/cli/test_source_package_metadata.py
+++ b/source/isaaclab/test/cli/test_source_package_metadata.py
@@ -23,27 +23,46 @@ def _repo_root() -> Path:
raise RuntimeError("Could not find Isaac Lab repository root.")
-def test_isaaclab_usd_core_pin_stays_on_isaacsim_compatible_usd25_abi():
- """The kit-less USD package must stay on the Isaac Sim compatible USD 25 ABI."""
+def test_isaaclab_uses_one_standalone_usd_provider():
+ """Isaac Lab must install only the USD provider shared with its importer dependencies.
+
+ ``usd-core`` and ``usd-exchange`` each install a complete ``pxr`` into the same directory, so
+ a second provider silently overwrites the first and removing either one leaves ``pxr`` broken.
+ Nothing detects that, because the two are separate distributions.
+ """
with (_repo_root() / "pyproject.toml").open("rb") as f:
pyproject = tomllib.load(f)
- usd_core_dependencies = [
- dependency for dependency in pyproject["project"]["dependencies"] if dependency.startswith("usd-core")
+ dependencies = pyproject["project"]["dependencies"]
+ usd_providers = [
+ dependency
+ for dependency in dependencies
+ if dependency.startswith("usd-core") or dependency.startswith("usd-exchange")
]
- assert usd_core_dependencies == [
- "usd-core>=25.11,<26.0 ; platform_machine == 'x86_64' or platform_machine == 'AMD64'"
- ]
+ assert usd_providers == ["usd-exchange==2.3.0"]
+
+
+def test_resolved_environment_has_no_second_usd_provider():
+ """No dependency may pull ``usd-core`` back in behind an extra.
+
+ ``newton[importers]``, ``mujoco[usd]`` and ``warp-lang[examples]`` all require it, so selecting
+ any of them would reinstate the overlap that the direct dependencies avoid. Checking the lock
+ catches that, where checking ``pyproject.toml`` alone would not.
+ """
+ with (_repo_root() / "uv.lock").open("rb") as f:
+ lock = tomllib.load(f)
+ locked = {package["name"] for package in lock["package"]}
-def test_isaaclab_standalone_usd_providers_are_platform_disjoint():
- """Standalone USD packages must not overlap on platforms where both ship ``pxr``."""
+ assert "usd-core" not in locked
+ assert "usd-exchange" in locked
+
+
+def test_standalone_importers_ship_as_base_dependencies():
+ """The standalone URDF/MJCF importers install by default, so conversion works without Isaac Sim."""
with (_repo_root() / "pyproject.toml").open("rb") as f:
pyproject = tomllib.load(f)
- usd_exchange_dependencies = [
- dependency for dependency in pyproject["project"]["dependencies"] if dependency.startswith("usd-exchange")
- ]
-
- assert usd_exchange_dependencies == ["usd-exchange>=2.2 ; platform_machine == 'aarch64'"]
+ assert "isaacsim-asset-isolated>=6.0,<6.1" in pyproject["project"]["dependencies"]
+ assert "importers" not in pyproject["project"]["optional-dependencies"]
diff --git a/source/isaaclab/test/cli/test_uv_run_pyproject.py b/source/isaaclab/test/cli/test_uv_run_pyproject.py
index 57b39fd3a2fe..007e26281e80 100644
--- a/source/isaaclab/test/cli/test_uv_run_pyproject.py
+++ b/source/isaaclab/test/cli/test_uv_run_pyproject.py
@@ -85,9 +85,9 @@ def test_uv_run_exposes_centralized_feature_extras():
def test_all_extra_aggregates_backends_rl_libraries_and_visualizers():
"""``all`` is the single flag for every backend, RL library, and visualizer.
- Nothing is forked in ``[tool.uv].conflicts`` any more, so Isaac Sim and both OV
- backends fit in one environment alongside every RL library and visualizer. The
- specialized workflows stay opt-in by name -- they are large, narrowly used, or both.
+ Nothing is forked in ``[tool.uv].conflicts``, so Isaac Sim and both OV backends fit in
+ one environment alongside every RL library and visualizer. The specialized workflows stay
+ opt-in by name -- they are large, narrowly used, or both.
"""
optional = _root_pyproject()["project"]["optional-dependencies"]
@@ -202,10 +202,11 @@ def test_public_ov_packages_use_public_pypi_index():
def test_uv_run_declares_no_extra_conflicts():
"""No extra is forked: every combination resolves into a single environment.
- ``[tool.uv].conflicts`` used to fork ``isaacsim`` / ``teleop`` away from the OV
- runtimes. The overrides below reconcile the last of those pins -- ``packaging`` for
- ovphysx, WebSockets for Viser, coverage for ``test`` -- and ovrtx declares no Python
- dependencies at all, so the table is now empty and can stay that way.
+ ``[tool.uv].conflicts`` used to fork ``isaacsim`` / ``teleop`` away from the OV runtimes,
+ and briefly away from the standalone importers. The overrides below reconcile the last of
+ those pins -- ``packaging`` for ovphysx, WebSockets for Viser, coverage for ``test``. The
+ importers install beside Isaac Sim without displacing it: the two distributions share no
+ files, and Kit serves ``isaacsim.asset`` from its extension roots either way.
"""
tool_uv = _root_pyproject()["tool"]["uv"]
@@ -255,9 +256,8 @@ def test_uv_run_base_dependencies_cover_newton_rsl_rl_training():
dependencies = _root_pyproject()["project"]["dependencies"]
# Newton is the default physics engine and RSL-RL the default training library,
- # so both ship as core third-party requirements (not opt-in extras). The importers
- # extra carries the mesh-processing deps that authored collision approximations need.
- assert any(dep.startswith("newton[sim,importers]") for dep in dependencies)
+ # so both ship as core third-party requirements (not opt-in extras).
+ assert any(dep.startswith("newton[sim]") for dep in dependencies)
assert any(dep.startswith("rsl-rl-lib") for dep in dependencies)
diff --git a/source/isaaclab/test/install_ci/cli/test_cli_install_core_in_uvenv_correctness.py b/source/isaaclab/test/install_ci/cli/test_cli_install_core_in_uvenv_correctness.py
index dec3e054c44a..0deffe160342 100644
--- a/source/isaaclab/test/install_ci/cli/test_cli_install_core_in_uvenv_correctness.py
+++ b/source/isaaclab/test/install_ci/cli/test_cli_install_core_in_uvenv_correctness.py
@@ -101,7 +101,7 @@ def test_install_core_omits_optional_submodules(self, isaaclab_root):
@pytest.mark.slow
@pytest.mark.timeout(1800)
def test_install_core_provides_newton_importer_dependencies(self, isaaclab_root):
- """Newton's mesh-processing deps ship with -i core (via newton[sim,importers]).
+ """Newton's mesh-processing deps ship with -i core (declared directly, not via a Newton extra).
Without them, Newton warns and falls back to a single convex hull instead of
honoring an authored ``physics:approximation`` (behavior is unit-tested in
diff --git a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py
index 7b0665c64709..af8fd4a7a8b0 100644
--- a/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py
+++ b/source/isaaclab/test/install_ci/uv_pip/test_uv_pip_install_isaaclab_rl_tasks_imports_rl_tasks.py
@@ -85,6 +85,13 @@ def test_install_rl_tasks_makes_isaaclab_tasks_importable(self):
@pytest.mark.slow
@pytest.mark.timeout(1200)
def test_install_rl_tasks_omits_isaacsim(self):
- """``import isaacsim`` fails after installing the RL extras (isaacsim extra not requested)."""
- result = self.run_in_uv_env(["python", "-c", "import isaacsim"])
+ """The Isaac Sim runtime is absent after installing the RL extras (isaacsim extra not requested).
+
+ ``import isaacsim`` is not the check: the standalone importers ship in the base
+ dependencies and contribute an ``isaacsim.asset`` portion, so the namespace package
+ resolves without the runtime. Ask the distribution instead.
+ """
+ result = self.run_in_uv_env(
+ ["python", "-c", "import importlib.metadata as m; m.version('isaacsim')"],
+ )
assert result.returncode != 0, f"isaacsim should not be installed by the {self._extras} extras"
diff --git a/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt b/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt
index 1c6210eb4fc7..578541211541 100644
--- a/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt
+++ b/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt
@@ -1,7 +1,7 @@
numpy>=2
mujoco~=3.11.0
mujoco-warp~=3.11.0
-newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@release-1.5
+newton[sim] @ git+https://github.com/newton-physics/newton.git@release-1.5
newton-usd-schemas>=0.4.1
torch==2.11.0
torchvision==0.26.0
@@ -10,4 +10,3 @@ typing-extensions>=4.15.0
websockets>=14.0,<17.0.0
coverage>=7.6.1
packaging>=20,<27
-usd-exchange>=2.2 ; platform_machine == 'aarch64'
diff --git a/source/isaaclab/test/sim/test_mjcf_converter.py b/source/isaaclab/test/sim/test_mjcf_converter.py
index 60edf227069b..a1ecec3a0a8b 100644
--- a/source/isaaclab/test/sim/test_mjcf_converter.py
+++ b/source/isaaclab/test/sim/test_mjcf_converter.py
@@ -5,19 +5,11 @@
"""Run the converter from the standalone importer wheel when installed; otherwise launch Isaac Sim."""
-from importlib import metadata
-
from isaaclab.app import AppLauncher
+from isaaclab.utils.version import standalone_importers_available
-# Prefer the standalone importer wheel: when it is installed the MJCF converter resolves its
-# importer from it and these tests run kitlessly (no Kit app, no extension manager). Fall back to
-# the full Isaac Sim runtime only when the wheel is absent. Distribution metadata is used so that
-# no Isaac Lab simulation module is imported before the launch decision.
-try:
- metadata.distribution("isaacsim-asset-isolated")
- _USE_KIT = False
-except metadata.PackageNotFoundError:
- _USE_KIT = AppLauncher.is_available()
+# Prefer kit-less; fall back to Kit when the standalone importers are not usable.
+_USE_KIT = not standalone_importers_available() and AppLauncher.is_available()
simulation_app = AppLauncher(headless=True).app if _USE_KIT else None
"""Rest everything follows."""
@@ -37,7 +29,9 @@
from isaaclab.sim import SimulationCfg, SimulationContext
from isaaclab.sim.converters import MjcfConverter, MjcfConverterCfg
-pytestmark = [pytest.mark.integration, pytest.mark.isaacsim_ci]
+# The Kit-less container mounts the checkout read-only, so ``usd_dir`` goes under ``tmp_path``.
+pytestmark = [pytest.mark.integration, pytest.mark.isaacsim_ci, pytest.mark.kitless]
+
_MJCF_IMPORTER_EXTENSION = "isaacsim.asset.importer.mjcf"
@@ -167,7 +161,7 @@ def test_create_prim_from_usd(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_self_collision(test_setup_teardown):
+def test_self_collision(test_setup_teardown, tmp_path):
"""Verify that ``self_collision=True`` enables self-collisions on the Newton articulation root.
The Isaac Sim importer's ``enable_self_collision`` writes the ``newton:selfCollisionEnabled``
@@ -175,8 +169,7 @@ def test_self_collision(test_setup_teardown):
``PhysicsArticulationRootAPI``, or ``NewtonArticulationRootAPI``).
"""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_self_collision")
+ output_dir = os.path.join(str(tmp_path), "mjcf_self_collision")
os.makedirs(output_dir, exist_ok=True)
config.self_collision = True
@@ -208,11 +201,10 @@ def test_self_collision(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_collision_from_visuals(test_setup_teardown):
+def test_collision_from_visuals(test_setup_teardown, tmp_path):
"""Verify that ``collision_from_visuals=True`` runs successfully and produces a spawnable USD."""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_collision_visuals")
+ output_dir = os.path.join(str(tmp_path), "mjcf_collision_visuals")
os.makedirs(output_dir, exist_ok=True)
config.collision_from_visuals = True
@@ -228,11 +220,10 @@ def test_collision_from_visuals(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_collision_type_convex_decomposition(test_setup_teardown):
+def test_collision_type_convex_decomposition(test_setup_teardown, tmp_path):
"""Verify that ``collision_type='Convex Decomposition'`` runs without error."""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_convex_decomp")
+ output_dir = os.path.join(str(tmp_path), "mjcf_convex_decomp")
os.makedirs(output_dir, exist_ok=True)
config.collision_from_visuals = True
@@ -249,15 +240,14 @@ def test_collision_type_convex_decomposition(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_link_density(test_setup_teardown):
+def test_link_density(test_setup_teardown, tmp_path):
"""Verify that ``link_density`` applies density without errors.
``nv_ant.xml`` has explicit inertial data on most bodies, so density is only applied where
mass is unspecified. This test ensures the pipeline runs and the output is spawnable.
"""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_link_density")
+ output_dir = os.path.join(str(tmp_path), "mjcf_link_density")
os.makedirs(output_dir, exist_ok=True)
config.link_density = 500.0
@@ -277,11 +267,10 @@ def test_link_density(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_merge_mesh(test_setup_teardown):
+def test_merge_mesh(test_setup_teardown, tmp_path):
"""Verify that ``merge_mesh=True`` runs successfully and still produces a spawnable USD."""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_merge_mesh")
+ output_dir = os.path.join(str(tmp_path), "mjcf_merge_mesh")
os.makedirs(output_dir, exist_ok=True)
config.merge_mesh = True
@@ -297,11 +286,10 @@ def test_merge_mesh(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_import_physics_scene(test_setup_teardown):
+def test_import_physics_scene(test_setup_teardown, tmp_path):
"""Verify that ``import_physics_scene=True`` still produces a spawnable USD."""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_physics_scene")
+ output_dir = os.path.join(str(tmp_path), "mjcf_physics_scene")
os.makedirs(output_dir, exist_ok=True)
config.import_physics_scene = True
@@ -313,11 +301,10 @@ def test_import_physics_scene(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_run_asset_transformer_disabled(test_setup_teardown):
+def test_run_asset_transformer_disabled(test_setup_teardown, tmp_path):
"""Verify that ``run_asset_transformer=False`` produces a flat USD that is still spawnable."""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_no_transformer")
+ output_dir = os.path.join(str(tmp_path), "mjcf_no_transformer")
os.makedirs(output_dir, exist_ok=True)
config.run_asset_transformer = False
@@ -333,7 +320,7 @@ def test_run_asset_transformer_disabled(test_setup_teardown):
@pytest.mark.isaacsim_ci
-def test_override_actuator_gains(test_setup_teardown):
+def test_override_actuator_gains(test_setup_teardown, tmp_path):
"""Verify that actuator gain overrides are written to ``MjcActuator`` prims.
``nv_ant.xml`` defines ``MjcActuator`` prims, so setting ``override_gain_type``,
@@ -341,8 +328,7 @@ def test_override_actuator_gains(test_setup_teardown):
corresponding ``mjc:*`` attributes on every actuator.
"""
sim, config = test_setup_teardown
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "mjcf_actuator_gains")
+ output_dir = os.path.join(str(tmp_path), "mjcf_actuator_gains")
os.makedirs(output_dir, exist_ok=True)
kp = 50.0
diff --git a/source/isaaclab/test/sim/test_urdf_converter.py b/source/isaaclab/test/sim/test_urdf_converter.py
index 0244e54f5651..0be2819bd33b 100644
--- a/source/isaaclab/test/sim/test_urdf_converter.py
+++ b/source/isaaclab/test/sim/test_urdf_converter.py
@@ -5,19 +5,11 @@
"""Run the converter from the standalone importer wheel when installed; otherwise launch Isaac Sim."""
-from importlib import metadata
-
from isaaclab.app import AppLauncher
+from isaaclab.utils.version import standalone_importers_available
-# Prefer the standalone importer wheel: when it is installed the URDF converter resolves its
-# importer from it and these tests run kitlessly (no Kit app, no extension manager). Fall back to
-# the full Isaac Sim runtime only when the wheel is absent. Distribution metadata is used so that
-# no Isaac Lab simulation module is imported before the launch decision.
-try:
- metadata.distribution("isaacsim-asset-isolated")
- _USE_KIT = False
-except metadata.PackageNotFoundError:
- _USE_KIT = AppLauncher.is_available()
+# Prefer kit-less; fall back to Kit when the standalone importers are not usable.
+_USE_KIT = not standalone_importers_available() and AppLauncher.is_available()
simulation_app = AppLauncher(headless=True).app if _USE_KIT else None
"""Rest everything follows."""
@@ -39,7 +31,9 @@
from isaaclab.sim import SimulationCfg, SimulationContext
from isaaclab.sim.converters import UrdfConverter, UrdfConverterCfg
-pytestmark = pytest.mark.integration
+# The Kit-less container mounts the checkout read-only, so ``usd_dir`` goes under ``tmp_path``.
+pytestmark = [pytest.mark.integration, pytest.mark.kitless]
+
# Portable Franka URDF for the kitless path (the Kit path uses the importer extension's bundled
# ``panda_arm_hand.urdf``). Both expose the same 7 revolute + 2 prismatic joint structure.
@@ -51,6 +45,10 @@
# Kept beside the tests so they are hermetic and run on either importer backend.
_MERGE_JOINTS_URDF = os.path.join(os.path.dirname(os.path.abspath(__file__)), "urdfs", "test_merge_joints.urdf")
+# Fixed-joint-only fixture: the importer writes no PhysX data for it, so its "Physics" variant set
+# offers no "physx" variant, so requesting it must fail.
+_FIXED_ONLY_URDF = os.path.join(os.path.dirname(os.path.abspath(__file__)), "urdfs", "test_fixed_only.urdf")
+
# Create a fixture for setup and teardown
@pytest.fixture
@@ -106,12 +104,11 @@ def test_no_change(sim_config):
@pytest.mark.isaacsim_ci
-def test_config_change(sim_config):
+def test_config_change(sim_config, tmp_path):
"""Call conversion twice but change the config in the second call. This should generate a new USD file."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_config_change")
+ output_dir = os.path.join(str(tmp_path), "urdf_config_change")
os.makedirs(output_dir, exist_ok=True)
config.usd_dir = output_dir
@@ -143,7 +140,7 @@ def test_create_prim_from_usd(sim_config):
@pytest.mark.isaacsim_ci
-def test_config_drive_type(sim_config):
+def test_config_drive_type(sim_config, tmp_path):
"""Verify that ``target_type='position'`` plus uniform PD gains are written into every joint's DriveAPI.
Reads the converter's USD output directly via :class:`pxr.UsdPhysics.DriveAPI` so the assertion does
@@ -151,8 +148,7 @@ def test_config_drive_type(sim_config):
convention) and prismatic joints in N/m.
"""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_converter")
+ output_dir = os.path.join(str(tmp_path), "urdf_converter")
os.makedirs(output_dir, exist_ok=True)
stiffness = 42.0
@@ -268,12 +264,11 @@ def test_merge_fixed_joints_xml():
@pytest.mark.isaacsim_ci
-def test_merge_fixed_joints_converter(sim_config):
+def test_merge_fixed_joints_converter(sim_config, tmp_path):
"""Test the full URDF converter pipeline with merge_fixed_joints enabled."""
sim, config = sim_config
# Create directory to dump results
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_converter_merge")
+ output_dir = os.path.join(str(tmp_path), "urdf_converter_merge")
os.makedirs(output_dir, exist_ok=True)
# use a URDF that has fixed joints
@@ -294,11 +289,10 @@ def test_merge_fixed_joints_converter(sim_config):
@pytest.mark.isaacsim_ci
-def test_fix_base_creates_fixed_joint(sim_config):
+def test_fix_base_creates_fixed_joint(sim_config, tmp_path):
"""Verify that fix_base=True creates a FixedJoint in the output USD."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_fix_base")
+ output_dir = os.path.join(str(tmp_path), "urdf_fix_base")
os.makedirs(output_dir, exist_ok=True)
config.fix_base = True
@@ -321,11 +315,10 @@ def test_fix_base_creates_fixed_joint(sim_config):
@pytest.mark.isaacsim_ci
-def test_no_fix_base(sim_config):
+def test_no_fix_base(sim_config, tmp_path):
"""Verify that fix_base=False does not create a fix_base_joint."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_no_fix_base")
+ output_dir = os.path.join(str(tmp_path), "urdf_no_fix_base")
os.makedirs(output_dir, exist_ok=True)
config.fix_base = False
@@ -343,7 +336,7 @@ def test_no_fix_base(sim_config):
@pytest.mark.isaacsim_ci
-def test_collision_from_visuals(sim_config):
+def test_collision_from_visuals(sim_config, tmp_path):
"""Verify that collision_from_visuals runs without error and produces valid output.
Note: CollisionAPI is applied on the intermediate stage before the asset transformer
@@ -352,8 +345,7 @@ def test_collision_from_visuals(sim_config):
inspecting the final USD for CollisionAPI schemas.
"""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_collision_visuals")
+ output_dir = os.path.join(str(tmp_path), "urdf_collision_visuals")
os.makedirs(output_dir, exist_ok=True)
config.collision_from_visuals = True
@@ -369,11 +361,10 @@ def test_collision_from_visuals(sim_config):
@pytest.mark.isaacsim_ci
-def test_no_collision_from_visuals(sim_config):
+def test_no_collision_from_visuals(sim_config, tmp_path):
"""Verify that conversion succeeds when collision_from_visuals is disabled."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_no_collision_visuals")
+ output_dir = os.path.join(str(tmp_path), "urdf_no_collision_visuals")
os.makedirs(output_dir, exist_ok=True)
config.collision_from_visuals = False
@@ -389,7 +380,7 @@ def test_no_collision_from_visuals(sim_config):
@pytest.mark.isaacsim_ci
-def test_self_collision(sim_config):
+def test_self_collision(sim_config, tmp_path):
"""Verify that ``self_collision=True`` enables self-collision on the Newton articulation root.
The Isaac Sim importer's ``enable_self_collision`` writes the ``newton:selfCollisionEnabled``
@@ -397,8 +388,7 @@ def test_self_collision(sim_config):
``PhysicsArticulationRootAPI``, or ``NewtonArticulationRootAPI``).
"""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_self_collision")
+ output_dir = os.path.join(str(tmp_path), "urdf_self_collision")
os.makedirs(output_dir, exist_ok=True)
config.self_collision = True
@@ -432,11 +422,10 @@ def test_self_collision(sim_config):
@pytest.mark.isaacsim_ci
-def test_drive_type_acceleration(sim_config):
+def test_drive_type_acceleration(sim_config, tmp_path):
"""Verify that drive_type='acceleration' is applied to all joints."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_drive_accel")
+ output_dir = os.path.join(str(tmp_path), "urdf_drive_accel")
os.makedirs(output_dir, exist_ok=True)
config.force_usd_conversion = True
@@ -466,11 +455,10 @@ def test_drive_type_acceleration(sim_config):
@pytest.mark.isaacsim_ci
-def test_target_type_none_zeros_gains(sim_config):
+def test_target_type_none_zeros_gains(sim_config, tmp_path):
"""Verify that ``target_type='none'`` zeros the DriveAPI stiffness and damping on every joint."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_target_none")
+ output_dir = os.path.join(str(tmp_path), "urdf_target_none")
os.makedirs(output_dir, exist_ok=True)
config.force_usd_conversion = True
@@ -502,11 +490,10 @@ def test_target_type_none_zeros_gains(sim_config):
@pytest.mark.isaacsim_ci
-def test_per_joint_dict_gains(sim_config):
+def test_per_joint_dict_gains(sim_config, tmp_path):
"""Verify that per-joint dict-based gains are applied correctly."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_dict_gains")
+ output_dir = os.path.join(str(tmp_path), "urdf_dict_gains")
os.makedirs(output_dir, exist_ok=True)
arm_stiffness = 100.0
@@ -572,11 +559,10 @@ def test_per_joint_dict_gains(sim_config):
@pytest.mark.isaacsim_ci
-def test_per_joint_dict_drive_type(sim_config):
+def test_per_joint_dict_drive_type(sim_config, tmp_path):
"""Verify that per-joint dict-based drive type is applied correctly."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_dict_drive_type")
+ output_dir = os.path.join(str(tmp_path), "urdf_dict_drive_type")
os.makedirs(output_dir, exist_ok=True)
config.force_usd_conversion = True
@@ -610,11 +596,10 @@ def test_per_joint_dict_drive_type(sim_config):
@pytest.mark.isaacsim_ci
-def test_natural_frequency_gains_deprecation(sim_config):
+def test_natural_frequency_gains_deprecation(sim_config, tmp_path):
"""Verify that NaturalFrequencyGainsCfg emits a DeprecationWarning and conversion still succeeds."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_nat_freq")
+ output_dir = os.path.join(str(tmp_path), "urdf_nat_freq")
os.makedirs(output_dir, exist_ok=True)
config.force_usd_conversion = True
@@ -640,11 +625,10 @@ def test_natural_frequency_gains_deprecation(sim_config):
@pytest.mark.isaacsim_ci
-def test_usd_structure_has_joints_and_links(sim_config):
+def test_usd_structure_has_joints_and_links(sim_config, tmp_path):
"""Validate that the output USD contains the expected joint and link prims for Franka Panda."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_structure")
+ output_dir = os.path.join(str(tmp_path), "urdf_structure")
os.makedirs(output_dir, exist_ok=True)
config.merge_fixed_joints = False
@@ -677,7 +661,7 @@ def test_usd_structure_has_joints_and_links(sim_config):
@pytest.mark.isaacsim_ci
-def test_link_density(sim_config):
+def test_link_density(sim_config, tmp_path):
"""Verify that link_density applies density to rigid body links.
Note: The Franka Panda URDF has explicit mass on all links, so the importer's
@@ -685,8 +669,7 @@ def test_link_density(sim_config):
This test verifies the pipeline runs without errors when ``link_density`` is set.
"""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_link_density")
+ output_dir = os.path.join(str(tmp_path), "urdf_link_density")
os.makedirs(output_dir, exist_ok=True)
config.link_density = 500.0
@@ -709,7 +692,7 @@ def test_link_density(sim_config):
@pytest.mark.isaacsim_ci
-def test_collision_type_convex_decomposition(sim_config):
+def test_collision_type_convex_decomposition(sim_config, tmp_path):
"""Verify that ``collision_type='Convex Decomposition'`` runs without error and produces valid output.
Note: MeshCollisionAPI is applied on the intermediate stage before the asset transformer.
@@ -717,8 +700,7 @@ def test_collision_type_convex_decomposition(sim_config):
verifies the pipeline executes successfully and produces a spawnable USD.
"""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_convex_decomp")
+ output_dir = os.path.join(str(tmp_path), "urdf_convex_decomp")
os.makedirs(output_dir, exist_ok=True)
config.collision_from_visuals = True
@@ -735,11 +717,10 @@ def test_collision_type_convex_decomposition(sim_config):
@pytest.mark.isaacsim_ci
-def test_unsupported_features_warn(sim_config):
+def test_unsupported_features_warn(sim_config, tmp_path):
"""Verify that deprecated config options emit warnings without failing."""
sim, config = sim_config
- test_dir = os.path.dirname(os.path.abspath(__file__))
- output_dir = os.path.join(test_dir, "output", "urdf_deprecated_warn")
+ output_dir = os.path.join(str(tmp_path), "urdf_deprecated_warn")
os.makedirs(output_dir, exist_ok=True)
config.convert_mimic_joints_to_normal_joints = True
@@ -751,3 +732,94 @@ def test_unsupported_features_warn(sim_config):
# conversion should succeed despite deprecated options
urdf_converter = UrdfConverter(config)
assert os.path.exists(urdf_converter.usd_path), "USD file should be created despite deprecated options"
+
+
+def _physics_variant(usd_path: str) -> tuple[str, list[str]]:
+ """Return the authored ``"Physics"`` variant selection and the available variants.
+
+ Both are read while the stage is still referenced, since USD objects do not keep it alive.
+ """
+ from pxr import Usd
+
+ stage = Usd.Stage.Open(usd_path)
+ variant_set = stage.GetDefaultPrim().GetVariantSets().GetVariantSet("Physics")
+ return variant_set.GetVariantSelection(), variant_set.GetVariantNames()
+
+
+def _count_physics(usd_path: str) -> tuple[int, int]:
+ """Return the number of joints and articulation roots composed from the USD file at ``usd_path``.
+
+ The stage is held in a local for the whole traversal, since prims do not keep it alive.
+ """
+ from pxr import Usd, UsdPhysics
+
+ stage = Usd.Stage.Open(usd_path)
+ prims = list(stage.Traverse())
+ joints = sum(1 for prim in prims if prim.IsA(UsdPhysics.Joint))
+ roots = sum(1 for prim in prims if prim.HasAPI(UsdPhysics.ArticulationRootAPI))
+ return joints, roots
+
+
+@pytest.mark.isaacsim_ci
+def test_physics_variant_selected_by_default(sim_config, tmp_path):
+ """Verify that the converter selects the backend-portable ``"physics"`` variant by default.
+
+ The importer leaves its ``"Physics"`` variant set unselected, which composes the asset without
+ joints, articulation roots, or mass properties. Without a selection this asserts 0 joints.
+ """
+ sim, config = sim_config
+ output_dir = os.path.join(str(tmp_path), "urdf_physics_variant_default")
+ os.makedirs(output_dir, exist_ok=True)
+
+ config.force_usd_conversion = True
+ config.usd_dir = output_dir
+ urdf_converter = UrdfConverter(config)
+
+ selection, _ = _physics_variant(urdf_converter.usd_path)
+ assert selection == "physics"
+
+ joints, roots = _count_physics(urdf_converter.usd_path)
+ assert joints > 0, "Expected the converted USD to compose joints"
+ assert roots > 0, "Expected the converted USD to compose an articulation root"
+
+
+@pytest.mark.isaacsim_ci
+def test_physics_variant_override(sim_config, tmp_path):
+ """Verify that ``physics_variant`` selects the requested variant instead of the default."""
+ sim, config = sim_config
+ output_dir = os.path.join(str(tmp_path), "urdf_physics_variant_override")
+ os.makedirs(output_dir, exist_ok=True)
+
+ config.force_usd_conversion = True
+ config.usd_dir = output_dir
+ config.physics_variant = "mujoco"
+ urdf_converter = UrdfConverter(config)
+
+ selection, _ = _physics_variant(urdf_converter.usd_path)
+ assert selection == "mujoco"
+
+ joints, roots = _count_physics(urdf_converter.usd_path)
+ assert joints > 0, "Expected the converted USD to compose joints"
+ assert roots > 0, "Expected the converted USD to compose an articulation root"
+
+
+@pytest.mark.isaacsim_ci
+def test_physics_variant_raises_again_on_retry(tmp_path):
+ """Verify that a conversion which failed on the variant does not count as cached.
+
+ The converter skips conversion when the asset hash matches, so recording the hash before the
+ variant is settled would make an identical retry return the asset the importer selected.
+ """
+ output_dir = os.path.join(str(tmp_path), "urdf_physics_variant_missing_retry")
+ os.makedirs(output_dir, exist_ok=True)
+
+ config = UrdfConverterCfg(
+ asset_path=_FIXED_ONLY_URDF,
+ fix_base=True,
+ usd_dir=output_dir,
+ physics_variant="physx",
+ )
+
+ for _ in range(2):
+ with pytest.raises(ValueError, match="no 'physx' physics variant"):
+ UrdfConverter(config)
diff --git a/source/isaaclab/test/sim/test_utils_prims.py b/source/isaaclab/test/sim/test_utils_prims.py
index 117aaced1608..52b570300070 100644
--- a/source/isaaclab/test/sim/test_utils_prims.py
+++ b/source/isaaclab/test/sim/test_utils_prims.py
@@ -395,6 +395,16 @@ def test_select_usd_variants():
# Check if the variant selection is correct
assert variant_set.GetVariantSelection() == "red"
+ # A variant the set does not offer must raise: USD would accept the selection and compose the
+ # prim as if nothing were selected, spawning the asset without what the variant carries.
+ with pytest.raises(ValueError, match="does not offer variant"):
+ sim_utils.utils.select_usd_variants("/World", {"colors": "chartreuse"}, stage)
+ assert variant_set.GetVariantSelection() == "red"
+
+ # A variant set the prim does not have stays a warning, so one configuration can spawn assets
+ # that expose different options.
+ sim_utils.utils.select_usd_variants("/World", {"absent_set": "anything"}, stage)
+
def test_select_usd_variants_in_usd_file():
"""Test select_usd_variants() function in USD file."""
diff --git a/source/isaaclab/test/sim/urdfs/test_fixed_only.urdf b/source/isaaclab/test/sim/urdfs/test_fixed_only.urdf
new file mode 100644
index 000000000000..c209868c24be
--- /dev/null
+++ b/source/isaaclab/test/sim/urdfs/test_fixed_only.urdf
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/isaaclab/test/utils/test_dict.py b/source/isaaclab/test/utils/test_dict.py
index 7546736497f6..6b5923f01c1e 100644
--- a/source/isaaclab/test/utils/test_dict.py
+++ b/source/isaaclab/test/utils/test_dict.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+import enum
import random
import pytest
@@ -116,3 +117,58 @@ def test_update_class_from_dict_does_not_rewrap_resolvable_string():
dict_utils.update_class_from_dict(cfg, {"class_type": existing})
assert cfg.class_type is existing
+
+
+class _Flavor(enum.StrEnum):
+ VANILLA = "vanilla"
+
+
+class _Level(enum.IntEnum):
+ LOW = 1
+
+
+class _Flavors(enum.StrEnum):
+ CHOCOLATE = "chocolate"
+ STRAWBERRY = "strawberry"
+
+
+class _EnumCfg:
+ """Config holding enum members, which carry a ``__dict__`` of enum internals."""
+
+ flavor: _Flavor = _Flavor.VANILLA
+ level: _Level = _Level.LOW
+ scoops: list[_Flavors] = [_Flavors.CHOCOLATE]
+ cone: tuple[_Flavors, ...] = (_Flavors.STRAWBERRY,)
+
+ def __init__(self):
+ self.flavor = _Flavor.VANILLA
+ self.level = _Level.LOW
+ self.scoops = [_Flavors.CHOCOLATE]
+ self.cone = (_Flavors.STRAWBERRY,)
+
+
+def test_class_to_dict_serializes_enums_as_values():
+ """Enum members should serialize to the value they stand for, not to their internals."""
+ data = dict_utils.class_to_dict(_EnumCfg())
+
+ assert data["flavor"] == "vanilla"
+ assert data["level"] == 1
+ # the enum internals must not leak into the output
+ assert not isinstance(data["flavor"], dict)
+ assert not isinstance(data["level"], dict)
+
+
+def test_update_class_from_dict_restores_enums_from_values():
+ """Serializing and reloading a config should hand back enum members, not raw scalars."""
+ cfg = _EnumCfg()
+
+ dict_utils.update_class_from_dict(cfg, dict_utils.class_to_dict(_EnumCfg()))
+
+ assert cfg.flavor is _Flavor.VANILLA
+ assert cfg.level is _Level.LOW
+ # members inside a list or tuple survive the round trip too: the flat-iterable path
+ # replaces the container wholesale, so it has to rebuild them itself
+ assert cfg.scoops == [_Flavors.CHOCOLATE]
+ assert all(isinstance(el, _Flavors) for el in cfg.scoops)
+ assert cfg.cone == (_Flavors.STRAWBERRY,)
+ assert all(isinstance(el, _Flavors) for el in cfg.cone)
diff --git a/source/isaaclab_newton/changelog.d/jichuanh-standalone-importers.skip b/source/isaaclab_newton/changelog.d/jichuanh-standalone-importers.skip
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/tools/wheel_builder/uv-overrides.txt b/tools/wheel_builder/uv-overrides.txt
index 1c6210eb4fc7..578541211541 100644
--- a/tools/wheel_builder/uv-overrides.txt
+++ b/tools/wheel_builder/uv-overrides.txt
@@ -1,7 +1,7 @@
numpy>=2
mujoco~=3.11.0
mujoco-warp~=3.11.0
-newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@release-1.5
+newton[sim] @ git+https://github.com/newton-physics/newton.git@release-1.5
newton-usd-schemas>=0.4.1
torch==2.11.0
torchvision==0.26.0
@@ -10,4 +10,3 @@ typing-extensions>=4.15.0
websockets>=14.0,<17.0.0
coverage>=7.6.1
packaging>=20,<27
-usd-exchange>=2.2 ; platform_machine == 'aarch64'
diff --git a/uv.lock b/uv.lock
index 8a75ab81469c..812d7446efdb 100644
--- a/uv.lock
+++ b/uv.lock
@@ -20,7 +20,7 @@ overrides = [
{ name = "coverage", specifier = ">=7.6.1" },
{ name = "mujoco", specifier = "~=3.11.0" },
{ name = "mujoco-warp", specifier = "~=3.11.0" },
- { name = "newton", extras = ["sim", "importers"], git = "https://github.com/newton-physics/newton.git?rev=release-1.5" },
+ { name = "newton", extras = ["sim"], git = "https://github.com/newton-physics/newton.git?rev=release-1.5" },
{ name = "newton-usd-schemas", specifier = ">=0.4.1" },
{ name = "numpy", specifier = ">=2" },
{ name = "packaging", specifier = ">=20,<27" },
@@ -37,7 +37,6 @@ overrides = [
{ name = "torchvision", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'", specifier = "==0.26.0", index = "https://download.pytorch.org/whl/cu128" },
{ name = "torchvision", marker = "sys_platform == 'win32'", specifier = "==0.26.0", index = "https://download.pytorch.org/whl/cu128" },
{ name = "typing-extensions", specifier = ">=4.15.0" },
- { name = "usd-exchange", marker = "platform_machine == 'aarch64'", specifier = ">=2.2" },
{ name = "websockets", specifier = ">=14.0,<17.0.0" },
]
@@ -1765,7 +1764,7 @@ wheels = [
[[package]]
name = "isaaclab"
-version = "16.0.0"
+version = "16.0.1"
source = { editable = "source/isaaclab" }
[[package]]
@@ -1799,9 +1798,12 @@ name = "isaaclab-dev"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
+ { name = "alphashape", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "botocore", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "coacd", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "daqp", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
{ name = "einops", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "fast-simplification", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "filelock", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "gymnasium", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "h5py", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -1821,15 +1823,18 @@ dependencies = [
{ name = "isaaclab-tasks", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "isaaclab-tasks-experimental", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "isaaclab-visualizers", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "isaacsim-asset-isolated", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "lazy-loader", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "matplotlib", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "newton", extra = ["importers", "sim"], marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "meshio", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "newton", extra = ["sim"], marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "newton-usd-schemas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "numba", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "omniverseclient", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "onnx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "onnxscript", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "open3d", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "packaging", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "pillow", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "pin", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
@@ -1837,10 +1842,14 @@ dependencies = [
{ name = "prettytable", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "protobuf", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "psutil", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "pycollada", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "pyglet", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "pyopengl-accelerate", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "requests", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "resolve-robotics-uri-py", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "rich", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "rsl-rl-lib", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "scipy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "starlette", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "tensorboard", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -1853,8 +1862,7 @@ dependencies = [
{ name = "transformers", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "trimesh", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "typing-extensions", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "usd-core", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "usd-exchange", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" },
+ { name = "usd-exchange", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "warp-lang", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
@@ -1976,9 +1984,11 @@ viser = [
requires-dist = [
{ name = "aiohttp", marker = "extra == 'rl-games'", specifier = ">=3.12.14" },
{ name = "albumentations", marker = "extra == 'rlinf'", specifier = ">=1.4.18" },
+ { name = "alphashape", specifier = ">=1.3.1" },
{ name = "autodocsumm", marker = "extra == 'test'" },
{ name = "av", marker = "extra == 'rlinf'", specifier = ">=12.3.0" },
{ name = "botocore" },
+ { name = "coacd", specifier = ">=1.0.7" },
{ name = "coverage", marker = "extra == 'test'", specifier = ">=7.6.1" },
{ name = "daqp", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", specifier = "==0.8.5" },
{ name = "decord2", marker = "extra == 'rlinf'" },
@@ -1986,6 +1996,7 @@ requires-dist = [
{ name = "diffusers", marker = "extra == 'rlinf'", specifier = ">=0.35.0" },
{ name = "dm-tree", marker = "extra == 'rlinf'", specifier = ">=0.1.8" },
{ name = "einops" },
+ { name = "fast-simplification", specifier = ">=0.1.11" },
{ name = "filelock" },
{ name = "flaky", marker = "extra == 'test'" },
{ name = "gym", marker = "extra == 'rl-games'" },
@@ -2014,14 +2025,16 @@ requires-dist = [
{ name = "isaaclab-visualizers", editable = "source/isaaclab_visualizers" },
{ name = "isaacsim", extras = ["all", "extscache"], marker = "extra == 'isaacsim'", specifier = "==6.0.1.0" },
{ name = "isaacsim", extras = ["all", "extscache"], marker = "extra == 'teleop'", specifier = "==6.0.1.0" },
+ { name = "isaacsim-asset-isolated", specifier = ">=6.0,<6.1" },
{ name = "isaacteleop", extras = ["retargeters", "ui", "cloudxr"], marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'teleop'", specifier = "~=1.4.0" },
{ name = "junitparser", marker = "extra == 'test'" },
{ name = "lazy-loader", specifier = ">=0.4" },
{ name = "leapp", marker = "extra == 'leapp'", specifier = ">=0.5.2" },
{ name = "matplotlib", specifier = ">=3.10.3" },
+ { name = "meshio", specifier = ">=5.3.5" },
{ name = "moviepy", marker = "extra == 'video'", specifier = ">=1.0.3,<2.0.0.dev0" },
{ name = "myst-parser", marker = "extra == 'test'" },
- { name = "newton", extras = ["sim", "importers"], specifier = ">=1.2.0" },
+ { name = "newton", extras = ["sim"], specifier = ">=1.2.0" },
{ name = "newton-usd-schemas", specifier = ">=0.2.0" },
{ name = "numba", specifier = ">=0.63.1" },
{ name = "numpy", specifier = ">=2" },
@@ -2030,6 +2043,7 @@ requires-dist = [
{ name = "onnx", specifier = ">=1.18.0" },
{ name = "onnxscript", specifier = ">=0.5" },
{ name = "onnxscript", marker = "extra == 'rsl-rl'", specifier = ">=0.5" },
+ { name = "open3d", marker = "(python_full_version < '3.13' and platform_machine != 'aarch64') or (python_full_version < '3.13' and sys_platform != 'linux')", specifier = ">=0.19.0" },
{ name = "ovphysx", marker = "extra == 'ov'", specifier = "==0.5.9", index = "https://pypi.org/simple" },
{ name = "ovphysx", marker = "extra == 'ovphysx'", specifier = "==0.5.9", index = "https://pypi.org/simple" },
{ name = "ovrtx", marker = "extra == 'ov'", specifier = ">=0.4.0,<0.5.0" },
@@ -2047,19 +2061,23 @@ requires-dist = [
{ name = "protobuf", specifier = ">=4.25.8,!=5.26.0" },
{ name = "psutil" },
{ name = "pyarrow", marker = "extra == 'rerun'", specifier = "==22.0.0" },
+ { name = "pycollada", specifier = ">=0.9" },
{ name = "pyglet", specifier = ">=2.1.6,<3" },
{ name = "pyopengl-accelerate", specifier = ">=3.1.0" },
{ name = "pytest", marker = "extra == 'test'" },
{ name = "pytest-mock", marker = "extra == 'test'" },
{ name = "pytetwild", extras = ["all"], marker = "extra == 'tetrahedralization'", specifier = ">=0.3.0,<0.4" },
{ name = "ray", extras = ["default"], marker = "extra == 'rlinf'", specifier = ">=2.47.0" },
+ { name = "requests", specifier = ">=2.25.0" },
{ name = "rerun-sdk", marker = "extra == 'rerun'", specifier = ">=0.29.0" },
+ { name = "resolve-robotics-uri-py", specifier = ">=0.4.0" },
{ name = "rich" },
{ name = "rich", marker = "extra == 'sb3'" },
{ name = "rl-games", marker = "extra == 'rl-games'", git = "https://github.com/isaac-sim/rl_games.git?rev=python3.11" },
{ name = "robomimic", marker = "sys_platform == 'linux' and extra == 'mimic'", git = "https://github.com/ARISE-Initiative/robomimic.git?rev=v0.4.0" },
{ name = "rsl-rl-lib", specifier = "==5.4.1" },
{ name = "rsl-rl-lib", marker = "extra == 'rsl-rl'", specifier = "==5.4.1" },
+ { name = "scipy", specifier = ">=1.11.0" },
{ name = "skrl", marker = "extra == 'skrl'", specifier = ">=2.1.0" },
{ name = "sphinx", marker = "extra == 'test'", specifier = ">=7.0,<9" },
{ name = "sphinx-book-theme", marker = "extra == 'test'", specifier = ">=1.1" },
@@ -2091,10 +2109,9 @@ requires-dist = [
{ name = "tqdm", specifier = ">=4.67.1" },
{ name = "tqdm", marker = "extra == 'sb3'" },
{ name = "transformers", specifier = "==4.57.6" },
- { name = "trimesh" },
+ { name = "trimesh", specifier = ">=4.6.8" },
{ name = "typing-extensions", specifier = ">=4.15.0" },
- { name = "usd-core", marker = "platform_machine == 'AMD64' or platform_machine == 'x86_64'", specifier = ">=25.11,<26.0" },
- { name = "usd-exchange", marker = "platform_machine == 'aarch64'", specifier = ">=2.2" },
+ { name = "usd-exchange", specifier = "==2.3.0" },
{ name = "viser", marker = "extra == 'viser'", specifier = ">=1.0.16" },
{ name = "warp-lang", specifier = "==1.16.0" },
]
@@ -2141,7 +2158,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }]
[[package]]
name = "isaaclab-ov"
-version = "1.0.0"
+version = "2.0.0"
source = { editable = "source/isaaclab_ov" }
dependencies = [
{ name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -2178,7 +2195,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }]
[[package]]
name = "isaaclab-rl"
-version = "0.14.0"
+version = "0.14.1"
source = { editable = "source/isaaclab_rl" }
dependencies = [
{ name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -2195,7 +2212,7 @@ requires-dist = [
[[package]]
name = "isaaclab-tasks"
-version = "15.0.1"
+version = "16.0.0"
source = { editable = "source/isaaclab_tasks" }
dependencies = [
{ name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -2314,6 +2331,22 @@ wheels = [
{ url = "https://pypi.nvidia.com/isaacsim-asset/isaacsim_asset-6.0.1.0-cp312-none-win_amd64.whl", hash = "sha256:fcca96376a4151fca0be2f8596d7c644cae71ba8ac56d735d3643052e0369596" },
]
+[[package]]
+name = "isaacsim-asset-isolated"
+version = "6.0.1.0"
+source = { registry = "https://pypi.nvidia.com/" }
+dependencies = [
+ { name = "isaacsim-robot-schema", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "mujoco", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "mujoco-usd-converter", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "newton-usd-schemas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "urdf-usd-converter", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "usd-exchange", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+]
+wheels = [
+ { url = "https://pypi.nvidia.com/isaacsim-asset-isolated/isaacsim_asset_isolated-6.0.1.0-py3-none-any.whl", hash = "sha256:2861f2cb9459c3f82fdf730342b372f1b3aee6eac16fd022230322652c594d56" },
+]
+
[[package]]
name = "isaacsim-benchmark"
version = "6.0.1.0"
@@ -2379,7 +2412,7 @@ dependencies = [
{ name = "mujoco-warp", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nest-asyncio", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "networkx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "newton", extra = ["importers", "sim"], marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+ { name = "newton", extra = ["sim"], marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "newton-actuators", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "newton-usd-schemas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "numpy-stl", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -2594,6 +2627,17 @@ wheels = [
{ url = "https://pypi.nvidia.com/isaacsim-robot-motion/isaacsim_robot_motion-6.0.1.0-cp312-none-win_amd64.whl", hash = "sha256:4ce18b32cbcca44fc2163346076fd99d2ea1b6b19fff9e9a11cb69c134e0c16f" },
]
+[[package]]
+name = "isaacsim-robot-schema"
+version = "6.0.1.0"
+source = { registry = "https://pypi.nvidia.com/" }
+dependencies = [
+ { name = "usd-exchange", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
+]
+wheels = [
+ { url = "https://pypi.nvidia.com/isaacsim-robot-schema/isaacsim_robot_schema-6.0.1.0-py3-none-any.whl", hash = "sha256:1a712f30202ec2286ac1dea521ec35b6d8dca1febfcf617c373b485547733884" },
+]
+
[[package]]
name = "isaacsim-robot-setup"
version = "6.0.1.0"
@@ -3198,7 +3242,7 @@ dependencies = [
{ name = "newton-usd-schemas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "numpy-stl", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "tinyobjloader", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "usd-exchange", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" },
+ { name = "usd-exchange", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/33/fc9c68f45aa6a6bc4d7012cfc5568b583577628d9c4f011c2816f2e3b0d2/mujoco_usd_converter-0.2.0.tar.gz", hash = "sha256:7aebb06590573583d642616fbaa7523cf098a7b31621023ef48d50538eef3e8a", size = 301687, upload-time = "2026-04-29T23:02:32.558Z" }
wheels = [
@@ -3306,21 +3350,6 @@ dependencies = [
]
[package.optional-dependencies]
-importers = [
- { name = "alphashape", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "coacd", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "fast-simplification", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "meshio", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "newton-usd-schemas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "open3d", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "pycollada", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "requests", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "resolve-robotics-uri-py", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "scipy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "trimesh", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "usd-core", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "usd-exchange", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" },
-]
sim = [
{ name = "mujoco", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "mujoco-warp", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
@@ -5801,7 +5830,7 @@ dependencies = [
{ name = "numpy-stl", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "pycollada", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "tinyobjloader", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
- { name = "usd-exchange", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" },
+ { name = "usd-exchange", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/73/de/1ba6cd4d985e617024cd7a50d9e269690c39ab9a4e08d3ba5023e63ab0ec/urdf_usd_converter-0.1.3.tar.gz", hash = "sha256:954f15a5c7f5a772b2df6eb206749a1af712de840cafb8e8a5d92349b9d32c86", size = 213029, upload-time = "2026-05-22T20:11:07.017Z" }
wheels = [
@@ -5817,21 +5846,14 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
]
-[[package]]
-name = "usd-core"
-version = "25.11"
-source = { registry = "https://pypi.org/simple" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ed/f1/dbf18bee5b569a06119f0cde798daeef4837887594a0caf415eb1d75112a/usd_core-25.11-cp312-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c05c05150f6c4598e0738677f0b7c8f82ff9fc492d040ed78147eb1ad5ce8545", size = 27393145, upload-time = "2025-10-24T19:01:27.841Z" },
- { url = "https://files.pythonhosted.org/packages/48/34/dd9883f27526288de19741100cb5b75f2725423367a999c055a677d1d88a/usd_core-25.11-cp312-none-win_amd64.whl", hash = "sha256:7bc5aaad20287bfc821e73e2ced99123e448e40ffd018acf99b09975eab4012d", size = 13061673, upload-time = "2025-10-24T19:01:30.396Z" },
-]
-
[[package]]
name = "usd-exchange"
version = "2.3.0"
source = { registry = "https://pypi.nvidia.com/" }
wheels = [
{ url = "https://pypi.nvidia.com/usd-exchange/usd_exchange-2.3.0-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:3beddb68abb89b1f3be207a35275604a6eb89b179ba052ca3eada5cd7202e534" },
+ { url = "https://pypi.nvidia.com/usd-exchange/usd_exchange-2.3.0-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:358c858fafebc0ca37a0e62608e595d894ff732eb723ed871292755f5882e8b4" },
+ { url = "https://pypi.nvidia.com/usd-exchange/usd_exchange-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:f5ab1b54ce670d8e36786c40750f86396463f2185cfaa5f5e813d6c5207e35ae" },
]
[[package]]