Repository navigation
Conversation
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
[Medium impact] The changes affect the optional environment-generation search and use of SimReady assets. Fix the candidate-name collisions and update the dependency lock before merging. FindingsSummaryAdds SimReady Central search, pinned asset downloads, and combined searches across selected backends.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Missing object phrases] --> B[Selected search backends]
B --> C[Existing SimReady libraries]
B --> D[SimReady Central]
D --> E[Download pinned package]
E --> F[Validate body and write wrapper]
C --> G[Merge distinct USD paths per phrase]
F --> G
G --> H[Register catalogue choices]
H --> I[Generate scene spec]
G --> J[Asset report]
I --> J
Reviews (1) · Last reviewed commit: "Name SimReady helpers by their operation..." · Reviewed by Greptile |
| for candidate in result.candidates: | ||
| key = (candidate.search_phrase, candidate.usd_path) | ||
| if key not in seen: | ||
| seen.add(key) | ||
| candidates.append(candidate) |
There was a problem hiding this comment.
Returned assets share one name
With --simready_sources isaac-sim-ga simready-central, both backends can return different assets for the same phrase, even at the default result limit. This merge keeps both, but register_searched_simready_object gives them the same catalogue name. The agent then overwrites _simready_usd_paths under that name, so the model cannot choose between the assets and the later backend silently wins.
Give each distinct candidate its own catalogue name and keep that name tied to its USD path.
| # its own: it adds one wheel, since Isaac Sim already brings everything it depends on, and an | ||
| # extra only bought a way to install the review GUI with its asset search missing. | ||
| "simready-search>=2026.4.2", | ||
| "huggingface-hub>=0.36", |
There was a problem hiding this comment.
Adding huggingface-hub to pyproject.toml without updating uv.lock breaks CI. The lock still omits this direct dependency from both the dev list and requires-dist. CI runs uv lock --check --no-python-downloads and later uv sync --locked --extra dev, so it rejects the stale lock before running tests.
Regenerate and commit uv.lock with this dependency change.
| except Exception: | ||
| return False, traceback.format_exc() | ||
|
|
||
| st.session_state["simready_asset_report"] = agent.simready_asset_report(spec) |
There was a problem hiding this comment.
Asset sources vanish on reopen
The GUI keeps the asset report only in st.session_state, while automatically saving the generated YAML. On reopening that YAML in a new session, initialize_state looks for a sibling .assets.json file that the GUI never writes. Central specs contain local wrapper paths, so users also lose the original source URLs unless they downloaded the report separately.
Save the report beside the YAML using the same naming convention as the CLI.
| _logger.warning("simready backend %s failed: %s", source, exc) | ||
| continue | ||
| for candidate in result.candidates: | ||
| key = (candidate.search_phrase, candidate.usd_path) |
There was a problem hiding this comment.
🟡 Two hits for one object collapse into one
With two sources selected, "egg" can get a hit from the GA library and one from Central, and both survive this merge. In _extend_catalogue_with_simready, register_searched_simready_object names the asset after the phrase only, so the second call gets the first class back.
As a result:
- the LLM sees two identical
simready_eggentries; _simready_usd_pathskeeps only the last path;- the spec always gets the asset from the source listed last, so the docs' "up to two candidates per object" never gives the model a choice.
max_results_per_object > 1 already had this problem, but with two sources it now happens at the default setting.
Could we keep only the first candidate per phrase here, in source order? Or give each candidate its own registry name, such as simready_egg_2? An agent-level test with two candidates for one phrase would catch this.
There was a problem hiding this comment.
+1. keeping only one is ok, but we should make the choice explicit/documented.
| ] | ||
| if not counts or any(float(count) != 1 for count in counts): | ||
| continue | ||
| usd_path = _download_and_wrap_central_asset(source["path"]) |
There was a problem hiding this comment.
🟡 Generated specs point at a file in this machine's cache
For Central hits, usd_path is the wrapper under ~/.cache/isaaclab_arena/simready-central/, which in turn points at an absolute Hugging Face cache path. That path goes into the spec, so the spec only loads for the same user on the same machine. GA hits put a remote URL in the spec instead.
SimReadyUsdObject already accepts the pinned URL and builds the wrapper itself, and source_url is that URL. Could the candidate keep the pinned URL as usd_path and still run the checks here? Generated specs would then work anywhere, like the egg-tray YAML. source_url and the cache-path part of the asset report would also no longer be needed.
There was a problem hiding this comment.
+1. I think the usd_path can store the pinned remote URL for simready central already, and we can just remove the added source_url param
source_url = source["path"].replace("hf://", "https://", 1)
_download_and_wrap_central_asset(source["path"]) # validate and populate the cache
candidates.append(
SimReadyObjectCandidate(
search_phrase=phrase,
usd_path=source_url,
# ...
)
)
| ): | ||
| assert usd_path, "simready_usd_object requires params.usd_path" | ||
| if usd_path.startswith(("https://huggingface.co/datasets/", "hf://huggingface.co/datasets/")): | ||
| from isaaclab_arena.agentic_environment_generation.simready_central import _download_and_wrap_central_asset |
There was a problem hiding this comment.
🟡 Asset class reaches into the env-gen package
SimReadyUsdObject is a core asset used by any spec, but it now imports the private _download_and_wrap_central_asset from agentic_environment_generation. The import is lazy, but asset construction still depends on the agent package. Downloading, checking and wrapping a Central package is asset loading, not agent work.
Could the URL parsing, download and wrap helpers move to an asset-side module, such as isaaclab_arena/assets/simready_central_asset.py, under public names? The search and this class would then both import from there. The prefix check here could also reuse that module's URL parser instead of repeating the prefixes.
| source_url=source["path"].replace("hf://", "https://", 1), | ||
| ) | ||
| ) | ||
| except Exception as exc: |
There was a problem hiding this comment.
🟡 A broken setup looks like "no asset found"
This catch treats an expected rejection (articulated asset, several bodies, disabled body) the same as a real failure:
snapshot_downloadfailing on network, auth or rate limits;huggingface_hubnot being installed;- a disk error while writing the wrapper.
All of these are logged at INFO for each hit, and the phrase ends up unmatched. Both the CLI and the GUI then tell the user to rephrase the prompt.
Could the checks in _parse_central_asset_url and _download_and_wrap_central_asset raise ValueError instead of using assert, and this block catch only ValueError? Those checks validate downloaded files and URLs that come from the search or from a user's YAML. That is external input, so a real exception fits better, and the checks would still run under -O. Other errors would then reach the WARNING in _search_simready_source.
| # its own: it adds one wheel, since Isaac Sim already brings everything it depends on, and an | ||
| # extra only bought a way to install the review GUI with its asset search missing. | ||
| "simready-search>=2026.4.2", | ||
| "huggingface-hub>=0.36", |
There was a problem hiding this comment.
🟡 uv.lock needs updating
Adding huggingface-hub to the dev extra changes the project's requires-dist. However, uv.lock isn't part of this PR, and its isaaclab-arena metadata has no huggingface-hub entry. The native uv job runs uv lock --check before syncing, so it should fail on this. Running uv lock and committing the result should fix it.
| phrases: list[str], config: SimReadySearchConfig, source: SimReadySourceKind | ||
| ) -> SimReadyCandidateCatalogue: | ||
| """Search one source, retaining the configured number of hits per phrase.""" | ||
| library = ( |
There was a problem hiding this comment.
🔵 Choose the per-phrase search once
This function checks source == SimReadySourceKind.CENTRAL three times and re-runs the import for every phrase. The unmatched_phrases it returns is never read, because search_simready_objects works it out again. Could it choose the search function once and return just the candidates?
if source == SimReadySourceKind.CENTRAL:
# Deferred: simready_central imports this module.
from isaaclab_arena.agentic_environment_generation.simready_central import search_central_objects
search_phrase = search_central_objects
else:
library = _create_asset_library_for_source(source, s3_url=config.s3_url, service_url=config.service_url)
if library is None:
return []
search_phrase = functools.partial(_search_library_rigid_body_candidates, library)
# ... then, per phrase:
hits = search_phrase(phrase, max_results=config.max_results_per_object)| assert max_results > 0, "max_results must be positive" | ||
| response = requests.post( | ||
| CENTRAL_SEARCH_URL, | ||
| headers={"x-usdsearch-storage-backend": "usdsearch-dev"}, |
There was a problem hiding this comment.
🔵 Is usdsearch-dev the backend to ship against?
The name suggests a development deployment. If it is renamed or removed, every Central search fails. If it's intended, could it be a named constant next to CENTRAL_SEARCH_URL, with a short note on why this backend is used?
| ) | ||
| response.raise_for_status() | ||
| candidates = [] | ||
| for hit in response.json()["hits"]: |
There was a problem hiding this comment.
🔵 No check that a hit is the object asked for
The library path keeps only hits whose path names the object (_filter_and_rank_path_matches), which guards against a "grey bin" search returning a cabinet. Here, the first single-body hit is accepted whatever it is. Hybrid search usually still returns its top hits when nothing really matches. With two sources, such a hit can also replace a correct GA hit (see the merge comment). Is there a score threshold or a name property we could check?
| - Sets the number of zero-action simulation steps. Default: ``20``. | ||
| - Evaluation smoke test | ||
|
|
||
| With ``--enable_simready_search --simready_source simready-central``, search |
There was a problem hiding this comment.
🔵 Old flag name
This paragraph still uses --simready_source, while the rest of the page uses --simready_sources. The alias still works, but one name is easier to follow.
| With ``--enable_simready_search --simready_source simready-central``, search | |
| With ``--enable_simready_search --simready_sources simready-central``, search |
🤖 Isaac Lab-Arena Review BotSummaryThis PR adds SimReady Central (Hugging Face) as a search backend for agentic environment generation, and lets the runner and GUI search several backends in one run. It also:
The Central download and checking code is careful. The main problems are how results from several backends reach the spec, and where the Central loading code lives. Design, Boundaries & Scope
Findings🟡 Warning: simready_asset_search.py:102: two hits for one phrase collapse into one registry entry, and the spec always gets the last backend's path. Test Coverage and CI Cost
VerdictSignificant concerns |
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
qianl-nv
left a comment
There was a problem hiding this comment.
Thanks for initiating this!
It seems we are creating enough special handling logic for simready central that it doesn't fit with the existing AssetLibrary based simready search. Made a proposal to split the two in one of my comments, wdyt?
| ] | ||
| if not counts or any(float(count) != 1 for count in counts): | ||
| continue | ||
| usd_path = _download_and_wrap_central_asset(source["path"]) |
There was a problem hiding this comment.
+1. I think the usd_path can store the pinned remote URL for simready central already, and we can just remove the added source_url param
source_url = source["path"].replace("hf://", "https://", 1)
_download_and_wrap_central_asset(source["path"]) # validate and populate the cache
candidates.append(
SimReadyObjectCandidate(
search_phrase=phrase,
usd_path=source_url,
# ...
)
)
| @@ -0,0 +1,82 @@ | |||
| # Copyright (c) 2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | |||
There was a problem hiding this comment.
while the commit message mentions the value of simready-central is "OEM contributed & validated industrial assets", here we are adding another "tabletop/kitchen" example. what's your plan with this example? should we replace with some "industrial looking task" with assets from simready-central (even if it's still pnp)?
| _logger.warning("simready backend %s failed: %s", source, exc) | ||
| continue | ||
| for candidate in result.candidates: | ||
| key = (candidate.search_phrase, candidate.usd_path) |
There was a problem hiding this comment.
+1. keeping only one is ok, but we should make the choice explicit/documented.
| @@ -51,6 +51,7 @@ dev = [ | |||
| # its own: it adds one wheel, since Isaac Sim already brings everything it depends on, and an | |||
| # extra only bought a way to install the review GUI with its asset search missing. | |||
| "simready-search>=2026.4.2", | |||
There was a problem hiding this comment.
which simready version are your local docker using? please make sure if you tested on a newer version, whether this old version is still compatible; if not let's bump the min version here.
| _logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def search_central_objects(phrase: str, *, max_results: int) -> list[SimReadyObjectCandidate]: |
There was a problem hiding this comment.
As I understand we need this special handling for simready-central because the simready search api still do not support the filter_by_properties_numeric. Let's leave a TODO here explaining the reason?
There was a problem hiding this comment.
Alternatively, Central differs enough to justify a backend strategy, separately from GA/S3 ect supported through AssetLibrary.
class SimReadySearchBackend(Protocol):
def search(
self, phrases: list[str], max_results: int
) -> list[SimReadyObjectCandidate]: ...
class AssetLibrarySearchBackend:
"""Search GA, S3, or generic service sources through AssetLibrary."""
class CentralSearchBackend:
"""Search Central through its specialized REST API and validate packages."""
Then search_simready_objects() only orchestrates:
- Construct selected backends.
- Ask each backend for candidates.
- Merge/deduplicate candidates.
- Compute unmatched phrases.
Separately, move URL parsing, downloading, and wrapper creation into an asset-side module because both Central search and SimReadyUsdObject need it.
|
|
||
| def _search_simready_source( | ||
| phrases: list[str], config: SimReadySearchConfig, source: SimReadySourceKind | ||
| ) -> SimReadyCandidateCatalogue: |
There was a problem hiding this comment.
we can return list[SimReadyObjectCandidate]. we don't need a per-source unmathced list, the unmatched phrases is recomputed on line 109 anyway
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Add SimReady Central to agentic environment generation and support searching multiple asset sources. https://huggingface.co/spaces/nvidia/simready-central as a endpoint, coupled with USD search.
The value of this library is a community/OEM contributed & validated industrial assets. See release notes here
Why this change?
Environment generation can have more USDs by accessing to Central’s Hugging Face assets alongside the existing SimReady library. Users should be able to search by object name in env gen.
What changes?
simready_ga_assets.py, Central REST search insimready_central_assets.py, and shared types and the backend protocol insimready_search_interface.py.tabletop_pick_place_screwdriver_bearing_wrench.yaml: Franka picks and places a ForgeCAD screwdriver on the maple table, alongside an NVIDIA ball bearing and combination wrench. All three props come from Central.Usage example
Before:
After:
Validation
Example
Franka picks up a spray gun from the maple table and places it on its bench stand. An orange paint jar, paintbrush, paint tube, ball bearing, and combination wrench serve as distractors. All seven objects come from NVIDIA’s Central dataset.
Success requires lifting the gun, then maintaining supported placement on the stand for 10 consecutive steps.
