-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Author fixed-root-link world joint with pure USD #6536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
b50b40e
f5a57a7
beac440
796e94e
e077ecc
c8f7ab5
d326c5c
e531e0c
c552144
e7e049e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| Changed | ||
| ^^^^^^^ | ||
|
|
||
| * Changed :meth:`~isaaclab.sim.schemas.modify_articulation_root_properties` to author | ||
| the fixed-root-link world joint directly with USD on every backend instead of calling | ||
| ``omni.physx.scripts.utils.createJoint``. This removes the ``omni.physx`` dependency | ||
| from the spawn path, which previously raised ``ModuleNotFoundError`` when spawning | ||
| fixed-base articulations on kitless backends (e.g. Newton). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| # Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). | ||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| """Regression tests for authoring the world fixed joint with pure USD. | ||
|
|
||
| These tests run on an in-memory USD stage and intentionally do NOT launch Isaac Sim / | ||
| Kit. :func:`create_world_fixed_joint` is the single fixed-root-link authoring path for | ||
| every backend, so passing here also covers kitless backends (e.g. Newton) where | ||
| ``omni.physx`` is unavailable. | ||
| """ | ||
|
|
||
| import math | ||
|
|
||
| from pxr import Gf, Usd, UsdGeom, UsdPhysics | ||
|
|
||
| from isaaclab.sim.schemas.schemas import create_world_fixed_joint | ||
|
|
||
|
|
||
| def _make_root_prim(stage: Usd.Stage, path: str, translation: tuple[float, float, float]) -> Usd.Prim: | ||
| """Create an xform articulation-root prim with a rigid body and a world translation.""" | ||
| xform = UsdGeom.Xform.Define(stage, path) | ||
| xform.AddTranslateOp().Set(Gf.Vec3d(*translation)) | ||
| prim = xform.GetPrim() | ||
| UsdPhysics.ArticulationRootAPI.Apply(prim) | ||
| UsdPhysics.RigidBodyAPI.Apply(prim) | ||
| return prim | ||
|
|
||
|
|
||
| def _find_fixed_joint(stage: Usd.Stage) -> UsdPhysics.FixedJoint | None: | ||
| """Return the first ``UsdPhysics.FixedJoint`` authored on the stage.""" | ||
| for prim in stage.Traverse(): | ||
| if prim.IsA(UsdPhysics.FixedJoint): | ||
| return UsdPhysics.FixedJoint(prim) | ||
| return None | ||
|
|
||
|
|
||
| def test_create_world_fixed_joint_authors_world_anchored_joint(): | ||
| """Authoring must materialize a ``UsdPhysics.FixedJoint`` anchored to the world. | ||
|
|
||
| A world-attached fixed joint targets only ``body1`` (the root link), leaving ``body0`` | ||
| empty, and anchors ``localPos0`` at the root link's world translation. | ||
| """ | ||
| stage = Usd.Stage.CreateInMemory() | ||
| prim = _make_root_prim(stage, "/World/Robot", translation=(1.0, 2.0, 3.0)) | ||
|
|
||
| create_world_fixed_joint(prim, stage) | ||
|
|
||
| joint = _find_fixed_joint(stage) | ||
| assert joint is not None, "no UsdPhysics.FixedJoint was authored" | ||
| # world-attached joint: body0 empty, body1 -> root link | ||
| assert joint.GetBody0Rel().GetTargets() == [] | ||
| assert joint.GetBody1Rel().GetTargets() == [prim.GetPath()] | ||
| # localPos0 anchors the joint at the root link's world pose | ||
| local_pos0 = joint.GetLocalPos0Attr().Get() | ||
| assert math.isclose(local_pos0[0], 1.0) and math.isclose(local_pos0[1], 2.0) and math.isclose(local_pos0[2], 3.0) | ||
| # the joint must be effectively unbreakable | ||
| assert joint.GetBreakForceAttr().Get() > 1e37 | ||
| assert joint.GetBreakTorqueAttr().Get() > 1e37 | ||
|
|
||
|
|
||
| def test_create_world_fixed_joint_skips_instanceable_root(): | ||
| """The joint must be authored on a writable ancestor when the root prim is instanceable. | ||
|
|
||
| Instanceable/instance-proxy/prototype prims are not authorable, so the joint prim must | ||
| be created under the first writable ancestor rather than under the instanceable root. | ||
| """ | ||
| stage = Usd.Stage.CreateInMemory() | ||
| UsdGeom.Xform.Define(stage, "/World") | ||
| prim = _make_root_prim(stage, "/World/Robot", translation=(0.0, 0.0, 0.0)) | ||
| prim.SetInstanceable(True) | ||
| assert prim.IsInstanceable() | ||
|
|
||
| create_world_fixed_joint(prim, stage) | ||
|
|
||
| joint = _find_fixed_joint(stage) | ||
| assert joint is not None, "no UsdPhysics.FixedJoint was authored" | ||
| # the joint prim must not be parented under the instanceable root | ||
| joint_path = joint.GetPrim().GetPath().pathString | ||
| assert not joint_path.startswith("/World/Robot/"), f"joint authored under instanceable root: {joint_path}" | ||
| # it still targets the root link | ||
| assert joint.GetBody1Rel().GetTargets() == [prim.GetPath()] | ||
|
Comment on lines
+63
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name-deduplication loop inside 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! |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
localRot0code path is untestedBoth test functions use a prim with a zero or identity rotation, so
joint.GetLocalRot0Attr().Get()is always the identity quaternion and theGf.Quatf(world_pose.ExtractRotationQuat())branch is never exercised. If the rotation-extraction logic regresses (e.g. wrong matrix decomposition order, inadvertentRemoveScaleShearstripping the rotation for scaled parents), no test would catch it. Adding a third test that translates and rotates the prim, then asserts the authoredlocalRot0matches the expected quaternion, would close this gap.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!