Repository navigation
Fix lazy conversion loading a stale MJCF or URDF conversion - #8163
Conversation
The Isaac Sim importers do not overwrite an earlier conversion in a reused usd_dir; they write the new one to a <name>_1 folder. The converters still looked for <name>/<name>.usda, so the next lazy conversion with a matching asset hash returned the first output and reverted a changed asset file or configuration. Record the requested and the generated USD file with the asset hash, and load the generated file only when both the hash and the requested file match. Records of earlier versions hold only the hash, so each existing usd_dir converts once more.
|
There was a problem hiding this comment.
Isaac Lab Review Bot
The cache-record expansion fixes stale lazy reuse for numbered URDF and MJCF outputs, with aligned documentation, changelog, and regression coverage. One implementation issue remains: a recorded generated path is adopted before deciding whether conversion is required, so forced and recovery conversions can use that generated path as their new request.
- Design and architecture: Keeping cache ownership in
AssetConverterBaseis the appropriate shared fix for both importers and avoids deleting outputs that may still be in use. However, loadingrecord[2]into_usd_file_namebefore the conversion decision conflates the previously generated output with the requested destination; those states should remain separate until the code confirms a cache hit will be reused. - API: The checked public surface—
usd_dir,usd_file_name,usd_path, and the existing converter configuration fields—has no signature, symbol, or default changes. Lazy cache hits now return the previously generated numbered output as intended, but forced or missing-file reconversions can expose unexpectedly nested numbered paths because the prior generated name becomes the next requested name. - Compatibility and deprecation: Breaking changes: none identified. Legacy single-line
.asset_hashrecords are internal cache artifacts and cause the documented one-time reconversion. The implementation issue affects output naming during forced or recovery conversion rather than removing or renaming a public contract. - Implementation: The record comparison and post-conversion write ordering preserve failed-variant retry behavior, but line 101 assigns
record[2]before evaluatingforce_usd_conversionor a missing generated file. Consequently,_convert_assetcan receive the previous generated path throughusd_pathand deduplicate from that path, producing nested names such asrobot_1_1rather than the documented sibling sequence. Apply the recorded generated name only when the conversion is actually skipped. - Style consistency: The docstring, comments, encoding arguments, changelog fragment, and how-to notes otherwise follow adjacent conventions and repository guidance. No import, naming, typing, or member-order issue was identified; the required correction is behavioral rather than stylistic.
- Test quality: The changed MJCF and URDF
test_config_changecases validly extend existing owner-boundary tests with a third lazy conversion and assert both generated-path reuse and unchanged modification time. They cover distinct importer paths and do not duplicate the mesh converter test. Additional focused coverage should exercise a cache hit followed by forced conversion or deletion of the recorded generated file, which would expose the accepted reconversion-path defect.
Minor fixes needed. Posted 1 actionable finding inline.
Automated review; human maintainers own approval decisions.
Keep the requested file name until the converter decides to reuse the recorded conversion, so that every conversion starts from the requested file. Test that a record of an earlier version and a missing generated file each convert again, into a folder next to the earlier ones.
The first and second conversions write different files, and on a file system with 1-second modification times both can share the same time. The importer writes the second conversion to a new folder, so compare the paths.
Check that a lazy conversion converts again when the record matches the hash but names another requested file, and that a missing generated file is not replaced by the first output. Note in the how-to that the conversion scripts force the conversion, so later lazy conversions do not reuse their output, and that only URDF and MJCF reconvert into a new numbered folder.
|
Thanks @NeoZng for raising this! Could we add an opt-in to allow overwriting previous conversions? My main concern with this PR is that nothing is cleaned-up. If you switch between config A and config B, it doesn't reuse robot/ for A. Each switch converts again: robot, robot_1, robot_2, … The migration also forces one extra numbered folder for every existing cache. |
|
Thanks! Some context: since #5394, every conversion into a reused Should we keep this, or delete the previous output and convert again in place? Deleting has caveats: it can remove a source folder named like the robot, it breaks processes that are reading that folder, and a process that has already opened the layer does not see a rewrite at the same path (on file systems with 1-second modification times, not even after |
|
@AntoineRichard I converted the changelog fragment to the towncrier format from #8118 (ec7f31e) and merged |
|
Thanks @NeoZng, and sorry for the slow answer. Let's keep the numbered folders and not delete anything by default. Your caveats about deleting are fair. Since this PR already changes the 1. Record every conversion, not just the last oneKeep the file name {
"version": 1,
"entries": [
{"hash": "<md5>", "requested": "robot/robot.usda", "generated": "robot/robot.usda"},
{"hash": "<md5>", "requested": "robot/robot.usda", "generated": "robot_1/robot_1.usda"}
]
}
Lookup (skipped when Write after a successful conversion (still after
With this, URDF/MJCF A → B → A reuses 2. Drop
|
Record every conversion in .asset_hash, a JSON document with one entry per conversion that holds the hash of the asset file and configuration, the requested USD file and the generated one. A lazy conversion reuses the generated file of a matching entry while it exists, so switching from configuration A to B and back to A reuses the first output. A new entry replaces entries whose file is gone, the entry of the same hash and requested file, and the entries in the folder it wrote to, since outputs in one folder can share files such as the mesh converter's Props/instanceable_meshes.usd. The record is replaced in one step. Leave force_usd_conversion out of the hash, so lazy conversions reuse a forced output. Adopt a record of an earlier version when it matches the hash that version recorded for a lazy conversion, the requested file exists and no numbered folder sits next to it.
|
@AntoineRichard Thanks for the detailed plan. Implemented in 0241117, with two small deviations:
Instead of reading the asset once for both hashes, the legacy hash is only computed when an old record may be adopted. All tests from your list are in. Happy to follow up with the cleanup flag on |
|
run-ci |
## Description `MjcfConverter` and `UrdfConverter` can return an earlier USD from a reused `usd_dir`. Their importers write a changed conversion to a numbered folder, but `AssetConverterBase` records only a hash and checks the original requested file on the next lazy call. For example, A writes `robot/`, B writes `robot_1/`, and a second request for B could return A from `robot/`. The cache now stores one record in `.asset_hash`: the asset/configuration hash, requested USD file, and actual generated USD file. It reuses that file only when the hash and request match and the generated file still exists. A legacy hash-only record is a cache miss, since it cannot identify which output the importer wrote. The record is written only after conversion succeeds. The cache retains only the most recent conversion. A → B → A converts again to a new numbered folder; existing outputs are not deleted. Multi-version reuse and cleanup are separate concerns. `force_usd_conversion` keeps its existing behavior. ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Release backport - [x] <!-- backport-active-release --> Backport this pull request to the active release branch after it merges into `develop` ## Validation - The extended URDF `test_config_change` fails on the old cache at the stale-output assertion and passes with the fix. - `source/isaaclab/test/sim/test_urdf_converter.py`: 17 passed. - `source/isaaclab/test/sim/test_mjcf_converter.py`: 11 passed. - `source/isaaclab/test/sim/test_mesh_converter.py -k 'no_change or config_change'`: 2 passed. - `uv run --no-sync isaaclab -f`: passed with the PR base selected for changelog validation. ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the pre-commit checks - [x] I have validated the changed behavior with focused coverage and applied the test-audit criteria - [x] I have added a changelog fragment under `source/isaaclab/changelog.d/` - [x] My name already exists in `CONTRIBUTORS.md` --------- Co-authored-by: Octi Zhang <zhengyuz@nvidia.com> (cherry picked from commit c31fe15)
|
Backported to |
Description
MjcfConverterandUrdfConvertercan return an earlier USD from a reusedusd_dir. Their importers write a changed conversion to a numbered folder, butAssetConverterBaserecords only a hash and checks the original requested file on the next lazy call. For example, A writesrobot/, B writesrobot_1/, and a second request for B could return A fromrobot/.The cache now stores one record in
.asset_hash: the asset/configuration hash, requested USD file, and actual generated USD file. It reuses that file only when the hash and request match and the generated file still exists. A legacy hash-only record is a cache miss, since it cannot identify which output the importer wrote. The record is written only after conversion succeeds.The cache retains only the most recent conversion. A → B → A converts again to a new numbered folder; existing outputs are not deleted. Multi-version reuse and cleanup are separate concerns.
force_usd_conversionkeeps its existing behavior.Type of change
Release backport
developValidation
test_config_changefails on the old cache at the stale-output assertion and passes with the fix.source/isaaclab/test/sim/test_urdf_converter.py: 17 passed.source/isaaclab/test/sim/test_mjcf_converter.py: 11 passed.source/isaaclab/test/sim/test_mesh_converter.py -k 'no_change or config_change': 2 passed.uv run --no-sync isaaclab -f: passed with the PR base selected for changelog validation.Checklist
source/isaaclab/changelog.d/CONTRIBUTORS.md