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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/source/api/lab/isaaclab.sim.spawners.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from_files
materials
wrappers
simready

.. rubric:: Classes

Expand Down Expand Up @@ -385,3 +386,25 @@ Wrappers
.. autoclass:: MultiUsdFileCfg
:members:
:exclude-members: __init__, func

SimReady Assets
---------------

.. automodule:: isaaclab.sim.spawners.simready

.. rubric:: Classes

.. autosummary::

SimReadyUsdFileCfg
SimReadyMultiUsdFileCfg

.. autoclass:: SimReadyUsdFileCfg
:members:
:show-inheritance:
:exclude-members: __init__, func

.. autoclass:: SimReadyMultiUsdFileCfg
:members:
:show-inheritance:
:exclude-members: __init__, func
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ teleop = [
"isaacteleop[retargeters,ui,cloudxr]~=1.3.0 ; platform_system == 'Linux' and platform_machine in 'x86_64 AMD64'",
"dex-retargeting==0.5.0 ; platform_system == 'Linux' and platform_machine in 'x86_64 AMD64'",
]
# SimReady USD-Search asset resolution (isaaclab.sim.spawners.simready).
simready = ["simready-search>=2026.4.2"]
# RLinf VLA post-training (externally contributed).
rlinf = [
"ray[default]>=2.47.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Added
^^^^^

* Added :func:`~isaaclab.utils.assets.search_simready_usd_paths` to resolve USD asset paths from a
SimReady USD-Search semantic query, and the :mod:`isaaclab.sim.spawners.simready` sub-module with
:class:`~isaaclab.sim.spawners.SimReadyUsdFileCfg` and :class:`~isaaclab.sim.spawners.SimReadyMultiUsdFileCfg`
to spawn the resolved assets instead of hardcoding USD file paths. The required ``simready-search``
package is available through the optional ``simready`` extra (``./isaaclab.sh -i simready``).
7 changes: 6 additions & 1 deletion source/isaaclab/isaaclab/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,11 +731,12 @@ def _install_isaacsim() -> None:
"newton",
"ov",
"rl",
"simready",
"visualizer",
}

# Extra features excluded from the automatic ``-i all`` / ``-i`` install.
MANUAL_EXTRA_FEATURES: set[str] = {"contrib", "ov"}
MANUAL_EXTRA_FEATURES: set[str] = {"contrib", "ov", "simready"}


def split_install_items(install_type: str) -> list[str]:
Expand Down Expand Up @@ -895,6 +896,10 @@ def _install_extra_feature(feature_name: str, selector: str = "") -> None:
_install_root_extra(backend)
elif feature_name == "ov":
_install_ov_extra_dependencies(selector)
elif feature_name == "simready":
if selector:
print_warning(f"'simready' does not support selectors (got '{selector}').")
_install_root_extra("simready")
else:
print_warning(
f"Unknown extra feature '{feature_name}'. "
Expand Down
2 changes: 2 additions & 0 deletions source/isaaclab/isaaclab/sim/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ from .spawners import (
RigidObjectSpawnerCfg,
SensorFrameCfg,
ShapeCfg,
SimReadyMultiUsdFileCfg,
SimReadyUsdFileCfg,
SpawnerCfg,
SphereCfg,
SphereLightCfg,
Expand Down
3 changes: 3 additions & 0 deletions source/isaaclab/isaaclab/sim/spawners/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ __all__ = [
"spawn_multi_usd_file",
"MultiAssetSpawnerCfg",
"MultiUsdFileCfg",
"SimReadyMultiUsdFileCfg",
"SimReadyUsdFileCfg",
]

from .spawner_cfg import SpawnerCfg, RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg
Expand Down Expand Up @@ -141,3 +143,4 @@ from .shapes import (
SphereCfg,
)
from .wrappers import spawn_multi_asset, spawn_multi_usd_file, MultiAssetSpawnerCfg, MultiUsdFileCfg
from .simready import SimReadyMultiUsdFileCfg, SimReadyUsdFileCfg
22 changes: 22 additions & 0 deletions source/isaaclab/isaaclab/sim/spawners/simready/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Sub-module for spawner configurations that resolve USD assets from SimReady search queries.

Instead of hardcoding USD file paths, these configurations query the SimReady USD-Search service
with a natural-language phrase (e.g. ``"food box"``) and spawn the top-ranked assets. The search
itself is performed by :func:`isaaclab.utils.assets.search_simready_usd_paths`. The service is
queried once, when the configuration is instantiated; no network calls are made during
simulation.

Using this sub-module requires the optional ``simready-search`` package. Install it with
``./isaaclab.sh -i simready`` or ``pip install simready-search``. The service requires HTTP Basic
authentication: set the ``USD_SEARCH_USERNAME`` and ``USD_SEARCH_PASSWORD`` environment variables
before the first search call.
"""

from isaaclab.utils.module import lazy_export

lazy_export()
11 changes: 11 additions & 0 deletions source/isaaclab/isaaclab/sim/spawners/simready/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

__all__ = [
"SimReadyMultiUsdFileCfg",
"SimReadyUsdFileCfg",
]

from .simready_cfg import SimReadyMultiUsdFileCfg, SimReadyUsdFileCfg
148 changes: 148 additions & 0 deletions source/isaaclab/isaaclab/sim/spawners/simready/simready_cfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from dataclasses import MISSING

from isaaclab.sim.spawners.from_files import UsdFileCfg
from isaaclab.sim.spawners.wrappers import MultiUsdFileCfg
from isaaclab.utils.assets import SIMREADY_SEARCH_SERVICE_ENDPOINT, search_simready_usd_paths
from isaaclab.utils.configclass import configclass


@configclass
class SimReadyUsdFileCfg(UsdFileCfg):
"""Configuration parameters for spawning the top-ranked SimReady asset for a search query.

This is a variant of :class:`~isaaclab.sim.spawners.from_files.UsdFileCfg` whose
:attr:`usd_path` is resolved from a SimReady semantic search query when the configuration is
instantiated. The top-ranked result is used. The service is queried exactly once: by the time
the spawner runs, :attr:`usd_path` is a plain string and no network calls are made during
simulation. When :attr:`usd_path` is already set (for instance, on a copy of a resolved
configuration), the search is skipped.

Please check :func:`~isaaclab.utils.assets.search_simready_usd_paths` for the required
optional dependency and the service credentials.

Example usage::

import isaaclab.sim as sim_utils
from isaaclab.assets import RigidObjectCfg

object_cfg = RigidObjectCfg(
prim_path="{ENV_REGEX_NS}/FoodBox",
spawn=sim_utils.SimReadyUsdFileCfg(query="food box"),
)
"""

query: str = MISSING
"""SimReady free-text search phrase."""

min_relevance: float = 0.0
"""Minimum relevance score for a match to be considered. Defaults to 0.0."""

filter_profiles: list[str] | None = None
"""SimReady profile names that the match must have. Defaults to None."""

filter_features: list[str] | None = None
"""SimReady feature names that the match must have. Defaults to None."""

service_endpoint: str = SIMREADY_SEARCH_SERVICE_ENDPOINT
"""URL of the SimReady USD-Search service."""

# note: redeclared so that __post_init__ can fill it from the search results.
usd_path: str = MISSING
"""Path to the USD file to spawn asset from. Resolved from :attr:`query` when not set."""

def __post_init__(self):
# skip when already resolved: copy() and replace() re-run __post_init__ with resolved values.
# note: configclass default factories deepcopy MISSING, so we match by type, not identity.
if not isinstance(self.usd_path, type(MISSING)):
return
if isinstance(self.query, type(MISSING)):
raise ValueError("Expected 'query' to be set when 'usd_path' is not provided.")
self.usd_path = search_simready_usd_paths(
self.query,
top_k=1,
min_relevance=self.min_relevance,
filter_profiles=self.filter_profiles,
filter_features=self.filter_features,
service_endpoint=self.service_endpoint,
)[0]


@configclass
class SimReadyMultiUsdFileCfg(MultiUsdFileCfg):
"""Configuration parameters for spawning multiple SimReady assets from a search query.

This is a variant of :class:`~isaaclab.sim.spawners.wrappers.MultiUsdFileCfg` whose
:attr:`usd_path` list is resolved from a SimReady semantic search query when the configuration
is instantiated. The service is queried exactly once: after that, :attr:`usd_path` contains a
frozen list of asset paths and the spawner behaves identically to a manually specified
:class:`~isaaclab.sim.spawners.wrappers.MultiUsdFileCfg`. No network calls are made during
simulation. When :attr:`usd_path` is already set (for instance, on a copy of a resolved
configuration), the search is skipped.

Since the resolved asset list depends on the state of the SimReady asset index, re-running a
script may resolve a different list. The resolved paths are recorded in the environment
configuration dump of a training run; pin them via :attr:`usd_path` to reproduce a run exactly.

Please check :func:`~isaaclab.utils.assets.search_simready_usd_paths` for the required
optional dependency and the service credentials.

Example usage::

import isaaclab.sim as sim_utils
from isaaclab.assets import RigidObjectCfg

clutter_cfg = RigidObjectCfg(
prim_path="{ENV_REGEX_NS}/Clutter_.*",
spawn=sim_utils.SimReadyMultiUsdFileCfg(
query="food box",
top_k=15,
mass_props=sim_utils.MassPropertiesCfg(mass=0.4),
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 0.5)),
)
"""

query: str = MISSING
"""SimReady free-text search phrase."""

top_k: int = 20
"""Maximum number of search results to include. Defaults to 20."""

min_relevance: float = 0.0
"""Minimum relevance score for a match to be included. Defaults to 0.0."""

filter_profiles: list[str] | None = None
"""SimReady profile names that every match must have. Defaults to None."""

filter_features: list[str] | None = None
"""SimReady feature names that every match must have. Defaults to None."""

service_endpoint: str = SIMREADY_SEARCH_SERVICE_ENDPOINT
"""URL of the SimReady USD-Search service."""

# note: redeclared so that __post_init__ can fill it from the search results.
usd_path: str | list[str] = MISSING
"""Paths to the USD files to spawn assets from. Resolved from :attr:`query` when not set."""

def __post_init__(self):
# skip when already resolved: copy() and replace() re-run __post_init__ with resolved values.
# note: configclass default factories deepcopy MISSING, so we match by type, not identity.
if not isinstance(self.usd_path, type(MISSING)):
return
if isinstance(self.query, type(MISSING)):
raise ValueError("Expected 'query' to be set when 'usd_path' is not provided.")
self.usd_path = search_simready_usd_paths(
self.query,
top_k=self.top_k,
min_relevance=self.min_relevance,
filter_profiles=self.filter_profiles,
filter_features=self.filter_features,
service_endpoint=self.service_endpoint,
)
90 changes: 90 additions & 0 deletions source/isaaclab/isaaclab/utils/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ def _parse_kit_asset_root() -> str:
GIT_ASSET_CACHE_DIR: str = os.path.join(tempfile.gettempdir(), "asset_cache")
"""Default local directory where git asset repositories are cached."""

SIMREADY_SEARCH_SERVICE_ENDPOINT = "https://search.dev.simready.omniverse.nvidia.com/"
"""Default SimReady USD-Search service endpoint.

The service requires HTTP Basic authentication. The credentials are read by the ``simready-search``
package from the ``USD_SEARCH_USERNAME`` and ``USD_SEARCH_PASSWORD`` environment variables, which
must be set before the first search call.
"""

_GIT_SSH_RE = re.compile(r"^[^@/:]+@[^:]+:.+")


Expand Down Expand Up @@ -387,6 +395,88 @@ def read_file(path: str) -> io.BytesIO:
raise FileNotFoundError(f"Unable to find the file: {path}")


def search_simready_usd_paths(
query: str | None = None,
top_k: int = 20,
min_relevance: float = 0.0,
filter_profiles: list[str] | None = None,
filter_features: list[str] | None = None,
service_endpoint: str = SIMREADY_SEARCH_SERVICE_ENDPOINT,
) -> list[str]:
"""Search the SimReady USD-Search service and return the top-ranked USD asset paths.

The results are ordered by descending relevance score. Ties are broken by the asset path so
that repeated calls (for instance, one per process in distributed training) resolve the same
ordering for identical service responses.

This function requires the optional ``simready-search`` package. Install it with
``./isaaclab.sh -i simready`` or ``pip install simready-search``. The service credentials are
read from the ``USD_SEARCH_USERNAME`` and ``USD_SEARCH_PASSWORD`` environment variables when
the package is first imported.

Args:
query: Free-text search phrase (e.g. ``"food box"``). At least one of :paramref:`query`,
:paramref:`filter_profiles`, or :paramref:`filter_features` must be provided.
top_k: Maximum number of asset paths to return.
min_relevance: Minimum relevance score for a match to be included.
filter_profiles: SimReady profile names that every match must have
(e.g. ``"Prop-Robotics-Isaac"``).
filter_features: SimReady feature names that every match must have
(e.g. ``"FET004_BASE_PHYSX"`` for rigid-body physics readiness).
service_endpoint: URL of the USD-Search service.

Returns:
USD asset paths ordered by descending relevance, at most :paramref:`top_k` entries.

Raises:
ImportError: If the optional ``simready-search`` package is not installed.
ValueError: If no search criterion is provided, ``top_k`` is not positive, or the search
returned no results.
"""
try:
from simready.search import ( # noqa: PLC0415
AssetLibrary,
SearchFilterFeature,
SearchFilterPhrase,
SearchFilterProfile,
SearchFilterRelevance,
)
except ImportError as ex:
raise ImportError(
"SimReady asset search requires the optional 'simready-search' package. Install it with"
" './isaaclab.sh -i simready' or 'pip install simready-search'."
) from ex

if top_k < 1:
raise ValueError(f"Expected 'top_k' to be positive, got: {top_k}.")
if query is None and not filter_profiles and not filter_features:
raise ValueError("At least one of 'query', 'filter_profiles', or 'filter_features' must be provided.")

# assemble the search filters. a match must pass all of them.
filters = []
if query is not None:
filters.append(SearchFilterPhrase(query))
if min_relevance > 0.0:
filters.append(SearchFilterRelevance(minimum=min_relevance))
for profile in filter_profiles or []:
filters.append(SearchFilterProfile(profile))
for feature in filter_features or []:
filters.append(SearchFilterFeature(feature))

# raise on network/auth failures instead of the library default of returning empty results
library = AssetLibrary(raise_on_network_error=True)
library.add_service_source(service_endpoint)
matches = library.search(include_all=filters, max_count=top_k)
if not matches:
raise ValueError(
f"SimReady search returned no results for query: {query!r}. Check the query phrase,"
" the filters, and the service endpoint."
)
# deterministic ordering: sort by descending relevance, breaking ties by asset path
matches = sorted(matches, key=lambda match: (-(match.relevance_score or 0.0), match.asset_path))
return [match.asset_path for match in matches]


def _find_asset_dependencies(local_asset_path: str) -> set[str]:
"""Collect external asset dependencies from a local asset file.

Expand Down
Loading
Loading