[3.0.0-beta2] Fix PhysX scene data for joint-labeled bodies#6148
Conversation
(cherry picked from commit 176ecd1)
Greptile SummaryThis PR cherry-picks a fix from
Confidence Score: 4/5The PhysX-side joint skip is safe and well-tested; the Newton-side resolver logic is correct, but the removal of the defensive body_label guard can crash update_visualization_state for any model where body_label is None. The newton_manager.py change introduced a regression: the old call used getattr(cls._model, 'body_label', None) or [] to handle a missing or None attribute, and the new list(cls._model.body_label) strips that safety. Any model variant where body_label is None will raise TypeError at runtime inside update_visualization_state, breaking Newton visualization sync for those models. source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py — specifically line 1838 where the body_label guard was weakened. Important Files Changed
Sequence DiagramsequenceDiagram
participant Consumer
participant NewtonManager
participant SceneDataProvider
participant PhysxSceneDataBackend
participant UsdStage
Consumer->>NewtonManager: update_visualization_state()
NewtonManager->>NewtonManager: _ensure_visualization_model()
alt _scene_data_mapping is None
NewtonManager->>NewtonManager: _resolve_scene_data_body_paths(body_label, stage)
loop For each body path that is a USD joint
NewtonManager->>UsdStage: GetPrimAtPath(body_path)
UsdStage-->>NewtonManager: joint prim
NewtonManager->>NewtonManager: _joint_body_path(prim)
NewtonManager->>UsdStage: GetBody1Rel / GetBody0Rel targets
UsdStage-->>NewtonManager: rigid-body target path
end
NewtonManager->>SceneDataProvider: create_mapping(resolved_body_paths)
SceneDataProvider-->>NewtonManager: _scene_data_mapping
end
NewtonManager->>SceneDataProvider: get_transforms(_scene_data, mapping)
SceneDataProvider->>PhysxSceneDataBackend: get_rigid_body_view()
alt _rigid_body_view is None
PhysxSceneDataBackend->>UsdStage: Traverse()
loop For each prim
PhysxSceneDataBackend->>PhysxSceneDataBackend: skip if prim.IsA(UsdPhysics.Joint)
PhysxSceneDataBackend->>PhysxSceneDataBackend: append if HasAPI(RigidBodyAPI)
end
PhysxSceneDataBackend->>PhysxSceneDataBackend: compact env paths to wildcards
PhysxSceneDataBackend->>PhysxSceneDataBackend: create_rigid_body_view(body_paths)
end
PhysxSceneDataBackend-->>SceneDataProvider: rigid body transforms
SceneDataProvider-->>NewtonManager: transforms written to _state_0.body_q
Reviews (1): Last reviewed commit: "Fix PhysX scene data for joint-labeled b..." | Re-trigger Greptile |
| cls._scene_data = SceneDataFormat.Transform() | ||
| if cls._scene_data_mapping is None: | ||
| body_paths = list(getattr(cls._model, "body_label", None) or []) | ||
| body_paths = cls._resolve_scene_data_body_paths(list(cls._model.body_label), scene_data_provider.usd_stage) |
There was a problem hiding this comment.
The original call used
getattr(cls._model, "body_label", None) or [] which safely handled both a missing attribute and a None value. The new code calls list(cls._model.body_label) directly — if body_label is None (which the original guard explicitly anticipated), this raises TypeError: 'NoneType' is not iterable. If the attribute is absent on some model variant, it raises AttributeError. Both crash update_visualization_state at runtime after the surrounding guard already verified only that cls._model is not None.
| body_paths = cls._resolve_scene_data_body_paths(list(cls._model.body_label), scene_data_provider.usd_stage) | |
| raw_labels = getattr(cls._model, "body_label", None) or [] | |
| body_paths = cls._resolve_scene_data_body_paths(list(raw_labels), scene_data_provider.usd_stage) |
| 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 isaaclab_newton.physics import NewtonManager | ||
|
|
||
| from pxr import Usd, UsdGeom, UsdPhysics | ||
|
|
||
| stage = Usd.Stage.CreateInMemory() | ||
| body_prim = UsdGeom.Xform.Define(stage, "/World/envs/env_0/Robot/robot0_forearm").GetPrim() | ||
| UsdPhysics.RigidBodyAPI.Apply(body_prim) | ||
| joint = UsdPhysics.FixedJoint.Define(stage, "/World/envs/env_0/Robot/joints/robot0_forearm") | ||
| joint.GetBody1Rel().SetTargets([body_prim.GetPath()]) | ||
|
|
||
| body_paths = ["/World/envs/env_0/Robot/joints/robot0_forearm"] | ||
| resolved_paths = NewtonManager._resolve_scene_data_body_paths(body_paths, stage) | ||
|
|
||
| assert resolved_paths == ["/World/envs/env_0/Robot/robot0_forearm"] |
There was a problem hiding this comment.
pytest.importorskip placed inside the test body rather than at module level is inconsistent with how test_physx_scene_data_backend.py handles the same dependency. Function-level placement works — pytest will skip the test correctly — but moving it to module level would align with the project pattern established in the companion test file.
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!
Summary
Validation