Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Fixed
^^^^^

* Removed the unconditional ``from omni.physx.scripts import utils`` in
:func:`isaaclab.sim.schemas.modify_articulation_root_properties` by inlining
the single-selection ``Fixed`` joint creation via :mod:`pxr.UsdPhysics`
directly. The previous code path broke any kitless newton run that needed
to anchor a fixed-base articulation to the world.
68 changes: 63 additions & 5 deletions source/isaaclab/isaaclab/sim/schemas/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import numpy as np
import warp as wp

from pxr import Sdf, Usd, UsdGeom, UsdPhysics
from pxr import Gf, Sdf, Usd, UsdGeom, UsdPhysics

from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.utils.string import to_camel_case
Expand Down Expand Up @@ -134,6 +134,63 @@ def _get_field_declaring_class(cfg_class: type, field_name: str) -> type | None:
return None


def _create_fixed_joint_to_world(stage: Usd.Stage, articulation_prim: Usd.Prim) -> UsdPhysics.FixedJoint:
"""Create a ``UsdPhysics.FixedJoint`` pinning ``articulation_prim`` to the world.

Kitless equivalent of ``omni.physx.scripts.utils.createJoint(stage, "Fixed",
from_prim=None, to_prim=articulation_prim)``: the omni helper is just a
thin wrapper around :mod:`pxr.UsdPhysics` ops, so inlining the
single-selection Fixed case lets this code path work both with and without
Kit loaded (newton backend in kitless mode hits this too).

Args:
stage: USD stage to author on.
articulation_prim: The articulation root prim to anchor to the world.

Returns:
The created :class:`pxr.UsdPhysics.FixedJoint`.
"""
to_path = articulation_prim.GetPath().pathString
# Mirror omni.physx createJoint's "find first writable ancestor" walk:
# instanced / prototype prims can't host the joint, so climb to a writable
# parent before authoring.
base_prim = articulation_prim
pseudo_root = stage.GetPseudoRoot()
while base_prim != pseudo_root:
if base_prim.IsInPrototype() or base_prim.IsInstanceProxy() or base_prim.IsInstanceable():
base_prim = base_prim.GetParent()
else:
break
joint_base_path = str(base_prim.GetPrimPath())
if joint_base_path == "/":
joint_base_path = ""

# Pick a unique sibling name "FixedJoint", "FixedJoint_01", etc.
joint_name = "FixedJoint"
idx = 1
while stage.GetPrimAtPath(f"{joint_base_path}/{joint_name}").IsValid():
joint_name = f"FixedJoint_{idx:02d}"
idx += 1
joint_path = f"{joint_base_path}/{joint_name}"

component = UsdPhysics.FixedJoint.Define(stage, joint_path)

# Single-selection placement: joint sits at to_prim's world pose; body0
# rel is left empty (world), body1 rel points to to_prim. Matches the
# omni single-selection branch.
xf_cache = UsdGeom.XformCache()
to_pose = xf_cache.GetLocalToWorldTransform(articulation_prim).RemoveScaleShear()
pos = Gf.Vec3f(to_pose.ExtractTranslation())
rot = Gf.Quatf(to_pose.ExtractRotationQuat())

component.CreateBody1Rel().SetTargets([Sdf.Path(to_path)])
component.CreateLocalPos0Attr().Set(pos)
component.CreateLocalRot0Attr().Set(rot)
component.CreateLocalPos1Attr().Set(Gf.Vec3f(0.0))
component.CreateLocalRot1Attr().Set(Gf.Quatf(1.0))
return component


def _apply_namespaced_schemas(prim, cfg, cfg_dict: dict) -> None:
"""Route every cfg field to its declaring class's namespace and apply schemas.

Expand Down Expand Up @@ -339,10 +396,11 @@ def modify_articulation_root_properties(
" the articulation tree. However, this is not implemented yet."
)

# create a fixed joint between the root link and the world frame
from omni.physx.scripts import utils as physx_utils

physx_utils.createJoint(stage=stage, joint_type="Fixed", from_prim=None, to_prim=articulation_prim)
# Create a fixed joint between the root link and the world frame.
# Use pxr.UsdPhysics directly so this code path also works in
# kitless mode (newton backend without Kit). The equivalent
# ``omni.physx.scripts.utils.createJoint`` is a thin pxr.Usd wrapper.
_create_fixed_joint_to_world(stage, articulation_prim)

# Having a fixed joint on a rigid body is not treated as "fixed base articulation".
# instead, it is treated as a part of the maximal coordinate tree.
Expand Down
Empty file.
Empty file.
25 changes: 14 additions & 11 deletions source/isaaclab_newton/test/assets/test_articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
# ignore private usage of variables warning
# pyright: reportPrivateUsage=none

"""Launch Isaac Sim Simulator first."""
"""Kitless newton tests: run the newton physics backend without booting Kit.

from isaaclab.app import AppLauncher

HEADLESS = True

# launch omniverse app
simulation_app = AppLauncher(headless=True).app

"""Rest everything follows."""
``SimulationContext`` and :func:`~isaaclab.sim.build_simulation_context` gate
all Kit-specific paths on :func:`~isaaclab.utils.version.has_kit`, so omitting
the module-level ``AppLauncher(headless=True).app`` boot is sufficient — newton
tests run in pure-python + warp without Isaac Sim's Kit runtime. This avoids
the Kit/Isaac-Sim concurrency lifecycle bug (SIGHUP / shutdown-hang at >=3
concurrent Kit instances on test_articulation under multi-GPU CI) and shaves
~30s off per-file boot.
"""

import sys
from copy import deepcopy
Expand Down Expand Up @@ -354,9 +354,12 @@ def generate_articulation(
# Fix reversed joints for known-broken USD assets (body0/body1 swapped)
usd_path = getattr(articulation_cfg.spawn, "usd_path", "")
if any(name in usd_path for name in _REVERSED_JOINT_USD_FILES):
import omni.usd
# Kitless: use IsaacLab's stage helper instead of ``omni.usd.get_context()``.
# ``get_current_stage`` falls back to the in-memory pxr.Usd stage when Kit
# isn't loaded.
from isaaclab.sim.utils.stage import get_current_stage

fix_reversed_joints(omni.usd.get_context().get_stage())
fix_reversed_joints(get_current_stage())

return articulation, translations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@
# pyright: reportPrivateUsage=none


"""Launch Isaac Sim Simulator first."""

from isaaclab.app import AppLauncher

# launch omniverse app
simulation_app = AppLauncher(headless=True).app

"""Rest everything follows."""
"""Kitless newton-only test: no AppLauncher boot. See test_articulation.py
for rationale."""

import sys

Expand Down
Loading