fix: IS 4.5.0 -> 5.0.0 migration — USD metadata, DomeLight, scene reuse
- Fix USD metersPerUnit/upAxis for IS 5.0.0 (no longer auto-compensated) - Batch fix all Aligned_obj.usd, table, and art USD files with backups - Fix DomeLight rotation to Z-axis only (prevent tilted environment map) - Fix scene reuse across episodes (arena_file caching, task clearing, prim guard) - Add migration tools: scan_usd_metadata.py, fix_usd_metadata.py - Add migration guide: migerate/migerate.md - Add nvidia-curobo to .gitignore - Fix sort_the_rubbish config: obj_0 -> obj_1 (obj_0 does not exist) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
159
migerate/fix_usd_metadata.py
Normal file
159
migerate/fix_usd_metadata.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Batch fix USD metadata for IS 5.0.0 compatibility.
|
||||
|
||||
IS 4.5.0 auto-added 'Rotate:unitsResolve' and 'Scale:unitsResolve' xformOps
|
||||
to compensate for metersPerUnit/upAxis differences. IS 5.0.0 no longer does this,
|
||||
causing RigidObject physics to break (objects fly away).
|
||||
|
||||
Fix: Change metersPerUnit to 1.0 and upAxis to Z in USD metadata.
|
||||
Vertex data stays unchanged (already in correct scale for the scene).
|
||||
|
||||
Usage:
|
||||
# Scan only (dry run):
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --dry-run
|
||||
|
||||
# Fix all:
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets
|
||||
|
||||
# Fix specific pattern:
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --pattern "Aligned_obj.usd"
|
||||
|
||||
# Skip specific directories (e.g. robot models, curobo assets):
|
||||
python migerate/fix_usd_metadata.py --root . --skip curobo,robot
|
||||
|
||||
# Restore from backups:
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --restore
|
||||
"""
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from pxr import Usd, UsdGeom
|
||||
|
||||
|
||||
def fix_usd_files(root: str, pattern: str = "*.usd", target_mpu: float = 1.0,
|
||||
target_up: str = "Z", dry_run: bool = False, skip: list = None):
|
||||
usd_files = glob.glob(os.path.join(root, "**", pattern), recursive=True)
|
||||
usd_files = [f for f in usd_files if not f.endswith(".bak")]
|
||||
usd_files.sort()
|
||||
|
||||
skip = skip or []
|
||||
|
||||
print(f"Scanning {len(usd_files)} USD files under: {root}")
|
||||
if dry_run:
|
||||
print("[DRY RUN] No files will be modified.\n")
|
||||
print()
|
||||
|
||||
fixed = 0
|
||||
skipped = 0
|
||||
skipped_dir = 0
|
||||
errors = 0
|
||||
|
||||
for fpath in usd_files:
|
||||
rel = os.path.relpath(fpath, root)
|
||||
|
||||
# Check skip patterns
|
||||
if any(s in rel for s in skip):
|
||||
print(f" SKIP (dir filter): {rel}")
|
||||
skipped_dir += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
stage = Usd.Stage.Open(fpath)
|
||||
if stage is None:
|
||||
print(f" ERROR: Cannot open {rel}")
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
mpu = UsdGeom.GetStageMetersPerUnit(stage)
|
||||
up = UsdGeom.GetStageUpAxis(stage)
|
||||
|
||||
needs_fix = (mpu != target_mpu) or (up != target_up)
|
||||
if not needs_fix:
|
||||
print(f" OK: {rel} (mpu={mpu}, up={up})")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if dry_run:
|
||||
print(f" WOULD FIX: {rel} (mpu={mpu}, up={up} -> mpu={target_mpu}, up={target_up})")
|
||||
fixed += 1
|
||||
continue
|
||||
|
||||
# Backup
|
||||
bak = fpath + ".bak"
|
||||
if not os.path.exists(bak):
|
||||
shutil.copy2(fpath, bak)
|
||||
|
||||
# Fix
|
||||
UsdGeom.SetStageMetersPerUnit(stage, target_mpu)
|
||||
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z if target_up == "Z" else UsdGeom.Tokens.y)
|
||||
stage.GetRootLayer().Save()
|
||||
|
||||
print(f" FIXED: {rel} (mpu={mpu}/{up} -> {target_mpu}/{target_up})")
|
||||
fixed += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" ERROR: {rel} -> {e}")
|
||||
errors += 1
|
||||
|
||||
print(f"\nDone: {fixed} {'would fix' if dry_run else 'fixed'}, {skipped} already OK, "
|
||||
f"{skipped_dir} skipped (dir filter), {errors} errors.")
|
||||
|
||||
if not dry_run and fixed > 0:
|
||||
print("Backups saved as *.bak.")
|
||||
print("To restore: python migerate/fix_usd_metadata.py --root <DIR> --restore")
|
||||
|
||||
return fixed
|
||||
|
||||
|
||||
def restore_backups(root: str, pattern: str = "*.usd"):
|
||||
bak_files = glob.glob(os.path.join(root, "**", pattern + ".bak"), recursive=True)
|
||||
bak_files.sort()
|
||||
|
||||
if not bak_files:
|
||||
print(f"No .bak files found under: {root}")
|
||||
return
|
||||
|
||||
print(f"Found {len(bak_files)} backup files. Restoring...")
|
||||
for bak in bak_files:
|
||||
original = bak[:-4] # remove .bak
|
||||
shutil.copy2(bak, original)
|
||||
print(f" Restored: {os.path.relpath(original, root)}")
|
||||
print(f"\nDone: {len(bak_files)} files restored.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Batch fix USD metersPerUnit/upAxis metadata for IS 5.0.0 compatibility",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Dry run on example assets:
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --dry-run
|
||||
|
||||
# Fix all Aligned_obj.usd in example assets:
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --pattern "Aligned_obj.usd"
|
||||
|
||||
# Fix everything except robot/curobo dirs:
|
||||
python migerate/fix_usd_metadata.py --root . --skip curobo,robot
|
||||
|
||||
# Restore all backups:
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --restore
|
||||
""")
|
||||
parser.add_argument("--root", default="workflows/simbox/example_assets",
|
||||
help="Root directory to scan (default: workflows/simbox/example_assets)")
|
||||
parser.add_argument("--pattern", default="*.usd", help="Filename glob pattern (default: *.usd)")
|
||||
parser.add_argument("--target-mpu", type=float, default=1.0, help="Target metersPerUnit (default: 1.0)")
|
||||
parser.add_argument("--target-up", choices=["Y", "Z"], default="Z", help="Target upAxis (default: Z)")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Scan and report only, do not modify files")
|
||||
parser.add_argument("--skip", default="", help="Comma-separated dir substrings to skip (e.g. 'curobo,robot')")
|
||||
parser.add_argument("--restore", action="store_true", help="Restore all .bak files to originals")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.restore:
|
||||
restore_backups(args.root, args.pattern)
|
||||
else:
|
||||
skip_list = [s.strip() for s in args.skip.split(",") if s.strip()]
|
||||
fix_usd_files(args.root, args.pattern, args.target_mpu, args.target_up, args.dry_run, skip_list)
|
||||
199
migerate/migerate.md
Normal file
199
migerate/migerate.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# Isaac Sim 4.5.0 -> 5.0.0 Migration Guide
|
||||
|
||||
## Background
|
||||
|
||||
- GPU: NVIDIA RTX PRO 6000 Blackwell (SM_120, compute capability 12.0)
|
||||
- IS 4.5.0 = Kit 106.5.0, DLSS Ray Reconstruction not supported on Blackwell (needs Kit >= 106.5.3), rendering has noise
|
||||
- IS 5.0.0 = Kit 107.x, fixes Blackwell rendering noise
|
||||
- IS 5.1.0 headless camera rendering completely broken (known bug GitHub #3250), abandoned
|
||||
|
||||
## Issues & Solutions
|
||||
|
||||
### 1. SimulationApp import path changed
|
||||
|
||||
**Error:** `ModuleNotFoundError: No module named 'omni.isaac.kit'`
|
||||
|
||||
**Cause:** IS 5.x changed the import path.
|
||||
|
||||
**Fix** (`nimbus_extension/components/load/env_loader.py`):
|
||||
```python
|
||||
try:
|
||||
from isaacsim import SimulationApp # IS 5.x
|
||||
except ImportError:
|
||||
from omni.isaac.kit import SimulationApp # IS 4.x
|
||||
```
|
||||
|
||||
### 2. USD metersPerUnit / upAxis not auto-compensated
|
||||
|
||||
**Error:** RigidObject Z coordinate flying to 109.75+ after `world.reset()`, curobo `plan_single()` fails with "Plan did not converge" (goal_pos Z = 25261 meters).
|
||||
|
||||
**Cause:** IS 4.5.0 auto-added `Rotate:unitsResolve` (X=90) and `Scale:unitsResolve` (0.01, 0.01, 0.01) xformOps to compensate for USD files with `metersPerUnit=0.01, upAxis=Y`. IS 5.0.0 no longer does this, causing PhysX to misinterpret RigidObject positions.
|
||||
|
||||
GeometryObjects (no physics) were unaffected — only RigidObjects with PhysX simulation had the issue.
|
||||
|
||||
**Fix:** Change USD metadata directly using pxr scripting. Run `fix_usd_metadata.py`:
|
||||
```python
|
||||
from pxr import Usd, UsdGeom
|
||||
stage = Usd.Stage.Open(usd_path)
|
||||
UsdGeom.SetStageMetersPerUnit(stage, 1.0) # was 0.01
|
||||
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z) # was Y
|
||||
stage.GetRootLayer().Save()
|
||||
```
|
||||
|
||||
All 14 `Aligned_obj.usd` files under `workflows/simbox/example_assets` have been batch-fixed. Backups saved as `.bak`.
|
||||
|
||||
**Verification:** After fix, pick_object_right Z went from 109.75 to 0.7968 (normal).
|
||||
|
||||
### 3. scipy `scalar_first` parameter not supported
|
||||
|
||||
**Error:** `Rotation.as_quat() takes no keyword arguments`
|
||||
|
||||
**Cause:** IS 5.0.0 ships scipy < 1.11 which doesn't support `scalar_first` parameter in `Rotation.from_quat()` / `Rotation.as_quat()`. Dozens of files use this parameter.
|
||||
|
||||
**Fix** (`launcher.py`): Monkey-patch via subclass at startup:
|
||||
```python
|
||||
try:
|
||||
from scipy.spatial.transform import Rotation as _R
|
||||
_R.from_quat([1, 0, 0, 0], scalar_first=True)
|
||||
except TypeError:
|
||||
# Install Rotation subclass that handles scalar_first
|
||||
# See launcher.py for full implementation
|
||||
pass
|
||||
```
|
||||
|
||||
### 4. `arena_file_path` is None on second reset
|
||||
|
||||
**Error:** `arena_file_path` becomes None after first episode.
|
||||
|
||||
**Cause:** `self.task_cfg.pop("arena_file", None)` on line 107 of `simbox_dual_workflow.py` deletes the key after first use.
|
||||
|
||||
**Fix** (`workflows/simbox_dual_workflow.py`): Cache the value:
|
||||
```python
|
||||
self._arena_file_path = None # in __init__
|
||||
|
||||
# in reset():
|
||||
arena_file_path = self.task_cfg.get("arena_file", None) or self._arena_file_path
|
||||
if arena_file_path:
|
||||
self._arena_file_path = arena_file_path
|
||||
```
|
||||
|
||||
### 5. "Task name should be unique in the world"
|
||||
|
||||
**Error:** `AssertionError` when calling `world.add_task()` on second episode.
|
||||
|
||||
**Cause:** World retains tasks from previous episode.
|
||||
|
||||
**Fix** (`workflows/simbox_dual_workflow.py`):
|
||||
```python
|
||||
self.world._current_tasks.clear()
|
||||
self.world.add_task(self.task)
|
||||
```
|
||||
|
||||
### 6. "A prim already exists at prim path"
|
||||
|
||||
**Error:** Scene setup tries to re-create existing prims on repeated `world.reset()` calls.
|
||||
|
||||
**Fix** (`workflows/simbox/core/tasks/banana.py`): Add `_scene_initialized` guard:
|
||||
```python
|
||||
def set_up_scene(self, scene):
|
||||
if getattr(self, '_scene_initialized', False):
|
||||
self._task_objects = {}
|
||||
self._task_objects |= (
|
||||
getattr(self, 'fixtures', {}) |
|
||||
getattr(self, 'objects', {}) |
|
||||
getattr(self, 'robots', {}) |
|
||||
getattr(self, 'cameras', {})
|
||||
)
|
||||
return
|
||||
self._scene_initialized = True
|
||||
super().set_up_scene(scene)
|
||||
```
|
||||
|
||||
### 7. Collision group prim already exists
|
||||
|
||||
**Error:** Collision group prims persist across resets.
|
||||
|
||||
**Fix** (`workflows/simbox/core/utils/collision_utils.py`): Remove existing collision prims before re-creating:
|
||||
```python
|
||||
collision_prim = stage.GetPrimAtPath(collision_root_path)
|
||||
if collision_prim.IsValid():
|
||||
stage.RemovePrim(collision_root_path)
|
||||
```
|
||||
|
||||
### 8. DomeLight environment map tilted
|
||||
|
||||
**Error:** HDR environment map appears rotated/tilted in viewport and rendered output.
|
||||
|
||||
**Cause:** DomeLight rotation was randomized on all 3 axes (X, Y, Z). Rotating X/Y tilts the environment.
|
||||
|
||||
**Fix** (`workflows/simbox/core/tasks/banana.py`): Only rotate Z axis:
|
||||
```python
|
||||
# Before:
|
||||
rotation = [random.uniform(r[0], r[1]) for _ in range(3)]
|
||||
# After:
|
||||
rotation = [0.0, 0.0, random.uniform(r[0], r[1])]
|
||||
```
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `nimbus_extension/components/load/env_loader.py` | SimulationApp import compatibility |
|
||||
| `launcher.py` | scipy Rotation monkey-patch |
|
||||
| `workflows/simbox_dual_workflow.py` | arena_file caching, task clearing, task reuse |
|
||||
| `workflows/simbox/core/tasks/banana.py` | Scene re-init guard, DomeLight rotation fix |
|
||||
| `workflows/simbox/core/utils/collision_utils.py` | Collision group cleanup |
|
||||
| `workflows/simbox/example_assets/**/Aligned_obj.usd` | metersPerUnit=1.0, upAxis=Z |
|
||||
| `fix_usd_metadata.py` | Batch USD metadata fix script |
|
||||
|
||||
## Tools
|
||||
|
||||
Migration tools are located in the `migerate/` directory.
|
||||
|
||||
### scan_usd_metadata.py — Scan USD metadata
|
||||
|
||||
Scan all USD files and report their `metersPerUnit` / `upAxis`:
|
||||
|
||||
```bash
|
||||
conda activate banana500
|
||||
|
||||
# Scan entire project
|
||||
python migerate/scan_usd_metadata.py --root .
|
||||
|
||||
# Scan specific directory
|
||||
python migerate/scan_usd_metadata.py --root workflows/simbox/example_assets
|
||||
```
|
||||
|
||||
Exit code: 0 = all OK, 1 = found files with non-standard metadata.
|
||||
建议扫描后丢给大模型分析一下然后再做下一步
|
||||
### fix_usd_metadata.py — Batch fix USD metadata
|
||||
|
||||
Fix `metersPerUnit` and `upAxis` in USD files, with backup/restore support:
|
||||
|
||||
```bash
|
||||
conda activate banana500
|
||||
|
||||
# Dry run — see what would be fixed, no changes made
|
||||
python migerate/fix_usd_metadata.py --root . --dry-run --skip curobo,robot
|
||||
|
||||
# Fix example assets only (default)
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets
|
||||
|
||||
# Fix all USD in project, skip robot/curobo models
|
||||
python migerate/fix_usd_metadata.py --root . --skip curobo,robot
|
||||
|
||||
# Fix only Aligned_obj.usd files
|
||||
python migerate/fix_usd_metadata.py --root . --pattern "Aligned_obj.usd"
|
||||
|
||||
# Restore from backups (undo all fixes)
|
||||
python migerate/fix_usd_metadata.py --root workflows/simbox/example_assets --restore
|
||||
```
|
||||
|
||||
**Important:** Do NOT fix robot USD (curobo, split_aloha) — their metersPerUnit/upAxis are intentionally set for their own coordinate systems. Use `--skip` to exclude them.
|
||||
|
||||
## Notes
|
||||
|
||||
- `plan_single()` occasionally returns None for edge-case target poses (workspace boundary). This is normal and happens in IS 4.5.0 too.
|
||||
- Example HDR is only 1k resolution — production should use 4k/8k for better background quality.
|
||||
- Example `envmap_lib` only has 1 HDR file — add more for randomization diversity.
|
||||
- `non_recyclable_garbage/obj_0` does not exist in example assets (only obj_1~obj_11). Config changed to use `obj_1`.
|
||||
70
migerate/scan_usd_metadata.py
Normal file
70
migerate/scan_usd_metadata.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Scan all USD files in the project and report their metersPerUnit / upAxis metadata.
|
||||
|
||||
Usage:
|
||||
python migerate/scan_usd_metadata.py [--root DIR]
|
||||
|
||||
Default root: current working directory (project root).
|
||||
"""
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pxr import Usd, UsdGeom
|
||||
|
||||
|
||||
def scan(root: str, target_mpu: float = 1.0, target_up: str = "Z"):
|
||||
usd_files = glob.glob(os.path.join(root, "**", "*.usd"), recursive=True)
|
||||
usd_files = [f for f in usd_files if not f.endswith(".bak")]
|
||||
usd_files.sort()
|
||||
|
||||
ok_files = []
|
||||
diff_files = []
|
||||
error_files = []
|
||||
|
||||
for fpath in usd_files:
|
||||
try:
|
||||
stage = Usd.Stage.Open(fpath)
|
||||
if stage is None:
|
||||
error_files.append((fpath, "Cannot open stage"))
|
||||
continue
|
||||
mpu = UsdGeom.GetStageMetersPerUnit(stage)
|
||||
up = UsdGeom.GetStageUpAxis(stage)
|
||||
rel = os.path.relpath(fpath, root)
|
||||
if mpu == target_mpu and up == target_up:
|
||||
ok_files.append((rel, mpu, up))
|
||||
else:
|
||||
diff_files.append((rel, mpu, up))
|
||||
except Exception as e:
|
||||
error_files.append((fpath, str(e)))
|
||||
|
||||
# Report
|
||||
print(f"Scanned {len(ok_files) + len(diff_files) + len(error_files)} USD files under: {root}")
|
||||
print(f" OK (mpu={target_mpu}, up={target_up}): {len(ok_files)}")
|
||||
print(f" Different: {len(diff_files)}")
|
||||
print(f" Errors: {len(error_files)}")
|
||||
|
||||
if diff_files:
|
||||
print(f"\n{'='*80}")
|
||||
print(f"Files with non-standard metadata (mpu != {target_mpu} or up != {target_up}):")
|
||||
print(f"{'='*80}")
|
||||
for rel, mpu, up in diff_files:
|
||||
print(f" mpu={mpu:<8} up={up:<4} {rel}")
|
||||
|
||||
if error_files:
|
||||
print(f"\n{'='*80}")
|
||||
print("Files with errors:")
|
||||
print(f"{'='*80}")
|
||||
for fpath, err in error_files:
|
||||
print(f" ERROR: {fpath} -> {err}")
|
||||
|
||||
return diff_files
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Scan USD files for metersPerUnit/upAxis metadata")
|
||||
parser.add_argument("--root", default=os.getcwd(), help="Root directory to scan (default: cwd)")
|
||||
args = parser.parse_args()
|
||||
diff = scan(args.root)
|
||||
sys.exit(1 if diff else 0)
|
||||
Reference in New Issue
Block a user