Fix PhysX scene data for joint-labeled bodies#6041
Conversation
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot — PR #6041
Fix PhysX scene data for joint-labeled bodies by @ooctipus
Summary
This PR fixes two related issues when USD assets have rigid-body-annotated joint prims (e.g., ShadowHand-style robots where joints share leaf names with bodies):
- PhysX scene-data backend (
physx_manager.py): Excludes joint prims from the global rigid-body view even if they haveRigidBodyAPIapplied. - Newton visualization sync (
newton_manager.py): Resolves body labels that point at joint prims to the actual rigid-body target via the joint'sBody1Rel/Body0Relrelationships.
✅ Findings
| # | Category | Finding | Severity |
|---|---|---|---|
| 1 | Correctness | The prim.IsA(UsdPhysics.Joint) check in physx_manager.py correctly filters joints that erroneously have RigidBodyAPI. This is the right USD predicate — it covers FixedJoint, RevoluteJoint, PrismaticJoint, etc. |
✅ Good |
| 2 | Correctness | The _resolve_scene_data_body_paths method correctly uses a for/else pattern to break out of nested loops when the first valid rigid-body target is found. |
✅ Good |
| 3 | Correctness | The resolution order (Body1 first, then Body0) is correct — Body1 is conventionally the child body in USD physics joints, which is the body that moves. | ✅ Good |
| 4 | Edge cases | None entries in body_paths are properly skipped. Invalid prims and non-joint prims pass through unchanged. stage is None early-returns the original list. |
✅ Good |
| 5 | Test coverage | Both fixes have focused regression tests using in-memory USD stages. Tests verify the exact filtering/resolution behavior. | ✅ Good |
| 6 | Documentation | CHANGELOGs updated for both isaaclab_newton and isaaclab_physx. |
✅ Good |
⚠️ Observations (non-blocking)
-
CI Status:
Check changelog fragmentsandpre-commitchecks are currently failing. These should be resolved before merge. The changelog fragment check may require a specific file naming convention — please verify the entries match the expected format. -
Import location: In
_resolve_scene_data_body_paths,from pxr import UsdPhysicsis imported inside the method body. This is fine for lazy-loading (avoids import errors whenpxrisn't available), but consider noting this pattern if it deviates from the module's conventions. -
pathStringattribute: Lineresolved_paths[i] = target_path.pathString— this assumestarget_pathis anSdf.Pathobject (which it is when coming fromGetTargets()). Correct usage. -
Performance: The resolution iterates over all body paths and does USD stage lookups for each. For robots with hundreds of bodies, this runs once at mapping creation time, so the cost is acceptable.
Verdict
✅ LGTM — The logic is correct, well-tested, and properly scoped. The fix addresses a real issue where joint prims with RigidBodyAPI corrupt the PhysX rigid-body view tensor shape. The only action items are fixing the failing CI checks (changelog fragments + pre-commit).
Update (176ecd1): Reviewed incremental changes. New commits add env destructor cleanup, MDL sibling-module import resolution, lift task consolidation (removing -v0 suffixes), AutoMate SoftDTW Numba→Torch rewrite, skrl JAX import fix, and optional wheeled-robot extensions. No new issues found. Previous P2 inline comments (test style, Body0Rel fallback coverage) remain unaddressed but are non-blocking.
Update (a6714f2): Reviewed the incremental diff (176ecd1→a6714f26). The new commits are a large-scale task-renaming and package-consolidation refactor (e.g. Isaac-Repose-* → Isaac-Reorient-*, inhand → reorient, shadow_hand_over → handover, cabinet direct-workflow extraction, device_split CI infrastructure). These changes are unrelated to the PhysX scene-data fix. My previous P2 inline comments (test pytest.importorskip placement, untested Body0Rel fallback path) were not addressed — the affected files were not modified in this push. No new issues found in the incremental changes relevant to this PR's scope.
Greptile SummaryThis PR fixes two related PhysX/Newton interop bugs that appear with ShadowHand-style USD assets where a joint prim has the same leaf name as the child rigid body. The physx_manager traversal now skips joint prims (even when
Confidence Score: 4/5Both fixes are small, well-scoped, and safe to merge; the only open questions are test coverage gaps for the Body0Rel fallback path. The core logic changes are a single guard added to a traversal loop and a new static resolution method — both are straightforward. The for-else labeled-break in
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[stage.Traverse prim] --> B{prim.HasAPI RigidBodyAPI?}
B -- No --> C{matches env_\\d+ path?}
B -- Yes --> D{prim.IsA Joint?}
D -- No --> E[Add to rigid_body_paths]
D -- Yes --> C
C -- Yes --> F[Add leaf name to non_rigid_body_names]
C -- No --> G[Skip]
E --> H{leaf name in non_rigid_body_names?}
H -- Yes --> I[Add to exact_paths]
H -- No --> J[Add to wildcard patterns]
I --> K[create_rigid_body_view]
J --> K
subgraph Newton resolve
L[body_paths from model.body_label] --> M{prim.IsA Joint?}
M -- No --> N[Keep original path]
M -- Yes --> O{Body1Rel has RigidBodyAPI target?}
O -- Yes --> P[Use Body1Rel target path]
O -- No --> Q{Body0Rel has RigidBodyAPI target?}
Q -- Yes --> R[Use Body0Rel target path]
Q -- No --> N
end
Reviews (1): Last reviewed commit: "Fix PhysX scene data for joint-labeled b..." | Re-trigger Greptile |
| def test_resolve_scene_data_body_paths_uses_joint_body_targets(): | ||
| """PhysX visualization sync maps Newton joint labels to the actual body prim path.""" | ||
| import pytest | ||
|
|
||
| pytest.importorskip("pxr") | ||
| from pxr import Usd, UsdGeom, UsdPhysics |
There was a problem hiding this comment.
The
import pytest / pytest.importorskip("pxr") placed inside the test function body is inconsistent with the companion test file test_physx_scene_data_backend.py, which calls pytest.importorskip at module level (line 10) so the entire module is skipped when pxr is absent. Embedding it inside the function still works, but it means the rest of the module is imported and any top-level side effects run before the skip fires.
| def test_resolve_scene_data_body_paths_uses_joint_body_targets(): | |
| """PhysX visualization sync maps Newton joint labels to the actual body prim path.""" | |
| import pytest | |
| pytest.importorskip("pxr") | |
| from pxr import Usd, UsdGeom, UsdPhysics | |
| def test_resolve_scene_data_body_paths_uses_joint_body_targets(): | |
| """PhysX visualization sync maps Newton joint labels to the actual body prim path.""" | |
| pxr = pytest.importorskip("pxr") | |
| Usd = pxr.Usd | |
| UsdGeom = pxr.UsdGeom | |
| UsdPhysics = pxr.UsdPhysics |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| for rel in (joint.GetBody1Rel(), joint.GetBody0Rel()): | ||
| for target_path in rel.GetTargets(): | ||
| target_prim = stage.GetPrimAtPath(target_path) | ||
| if target_prim.IsValid() and target_prim.HasAPI(UsdPhysics.RigidBodyAPI): | ||
| resolved_paths[i] = target_path.pathString | ||
| break | ||
| else: | ||
| continue | ||
| break |
There was a problem hiding this comment.
Body0Rel fallback path is untested
The Body1Rel→Body0Rel priority logic (the for-else labeled-break) is the only non-trivial control flow in _resolve_scene_data_body_paths, but the new test only exercises Body1Rel. A joint whose Body1Rel is empty (or has no RigidBodyAPI target) would fall through to Body0Rel; without a test, a future refactor of the nested for-else could silently break that fallback. Similarly, the "no valid target on either rel → path unchanged" case is uncovered.
e99ec0e to
ec500e8
Compare
ec500e8 to
176ecd1
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot — PR #6041 (Update)
Reviewed incremental changes: c00ce4a → a6714f2
Changes Since Last Review
The PR-relevant files were modified in this push. Here's what's new:
Newton Manager Refactoring (newton_manager.py)
| # | Change | Assessment |
|---|---|---|
| 1 | Extracted _initialize_fabric_body_prims() as a static method |
✅ Good refactor — improves testability and separation of concerns |
| 2 | Added _cl_fabric_body_bindings class variable for body-index mapping |
✅ Cleaner than inline construction |
| 3 | clear() now resets _cl_fabric_body_bindings = None |
✅ Proper cleanup |
| 4 | Uses fabric_hierarchy.update_world_xforms() instead of per-prim SetWorldXformFromUsd() |
✅ More efficient batch update |
Core Fix (unchanged logic, now in final position)
_resolve_scene_data_body_paths(): The joint-to-body resolution logic is intact and correctly placed.physx_manager.py: Theprim.IsA(UsdPhysics.Joint)skip is present at line 194.
Tests
Both test files are new additions that verify the fix:
test_newton_manager_visualization_state.py: Tests joint→body path resolutiontest_physx_scene_data_backend.py: Tests joint prim exclusion from rigid body views
💭 Observations
-
Test placement of
pytest.importorskip: Intest_newton_manager_visualization_state.py, theimportorskipis inside the test function (line 166). This works but means the skip message will show the test name rather than skipping at module load. The physx test file places it at module level (line 10-11), which is the more conventional pattern. Non-blocking style note. -
Body0Rel fallback path: The
_joint_body_pathhelper iteratesBody1RelthenBody0Rel, returning on first valid target. If Body1 targets an invalid prim but Body0 is valid, it falls through correctly. However, there's no test for the Body0Rel fallback path. Consider adding one for completeness. Non-blocking. -
Fabric body bindings: The new
_cl_fabric_body_bindingsis populated externally (byNewtonReplicateContextpresumably). If it'sNone, the code constructs bindings frombody_labeldirectly. This dual-path is sensible for supporting both replicated and non-replicated scenes.
Verdict
✅ LGTM — The core fix is correct and well-tested. The refactoring of start_simulation is a clean improvement. Previous observations about CI failures (changelog fragments, pre-commit) still apply if not yet resolved.
Reviewed commit: a6714f2
## Summary - Cherry-picks #6041 onto release/3.0.0-beta2. - Skips USD joint prims when building the global PhysX scene-data rigid-body view. - Resolves Newton visualization body labels that point at USD joints through their rigid-body targets. - Adds regression coverage for joint/body leaf-name collisions. ## Validation - python3 -m py_compile source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py source/isaaclab/test/sim/test_newton_manager_visualization_state.py source/isaaclab_physx/test/sim/test_physx_scene_data_backend.py - git diff --check upstream/release/3.0.0-beta2...HEAD - python3 tools/changelog/cli.py check release/3.0.0-beta2 - pre-commit run --files source/isaaclab/changelog.d/zhengyuz-physx-scene-data-shadowhand.skip source/isaaclab/test/sim/test_newton_manager_visualization_state.py source/isaaclab_newton/changelog.d/zhengyuz-physx-scene-data-shadowhand.rst source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py source/isaaclab_physx/changelog.d/zhengyuz-physx-scene-data-shadowhand.rst source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py source/isaaclab_physx/test/sim/test_physx_scene_data_backend.py - Focused pytest attempted with python3 and ./isaaclab.sh -p; this local checkout did not have the needed runtime package setup, so the tests stopped at missing isaaclab_newton/toml imports before exercising the change.
Summary
Validation
lazy_loader) and USD bindings (pxr) installed