From 7b333dae1feb46decc92f9cde555318ae5d26ec0 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Sat, 30 May 2026 22:39:58 +0000 Subject: [PATCH 1/4] Make newton test_articulation kitless Drop the module-level ``AppLauncher(headless=True).app`` boot in ``source/isaaclab_newton/test/assets/test_articulation.py``. The existing ``has_kit()`` gate in :class:`~isaaclab.sim.SimulationContext` carries the test through the kitless path: newton tests run in pure-python + warp, no Kit runtime, no Isaac Sim app process. This sidesteps the Kit/Isaac-Sim concurrency lifecycle bug (newton SIGHUP + physx shutdown-hang on test_articulation under sustained 3+ concurrent Kit instances on multi-GPU CI) by removing the Kit dependency entirely. Also shaves ~30 s off per-file boot. The kitless path exposed one unguarded ``omni.physx`` import in ``isaaclab/sim/schemas/schemas.py``: the fixed-base articulation root joint creation called ``omni.physx.scripts.utils.createJoint``. Inline it as ``_create_fixed_joint_to_world`` using :mod:`pxr.UsdPhysics` ops directly so the same code path works with or without Kit loaded. Scope intentionally limited to ``test_articulation.py`` + the one schemas import. The other four newton test files (``test_rigid_object.py``, ``test_rigid_object_collection.py``, ``test_newton_actuators_newton.py``, ``test_newton_schemas.py``) follow the same pattern and can be converted in a follow-up once this one is validated on CI. --- .../jichuanh-kitless-newton-tests.rst | 17 +++++ .../isaaclab/isaaclab/sim/schemas/schemas.py | 68 +++++++++++++++++-- .../test/assets/test_articulation.py | 20 +++--- 3 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst diff --git a/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst b/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst new file mode 100644 index 000000000000..e282286d686c --- /dev/null +++ b/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst @@ -0,0 +1,17 @@ +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. + +Changed +^^^^^^^ + +* The ``test_articulation.py`` newton integration tests no longer boot Kit at + module level: :func:`AppLauncher` is dropped and the existing kitless + branch in :class:`~isaaclab.sim.SimulationContext` (already gated by + :func:`~isaaclab.utils.version.has_kit`) carries the test. Avoids the Kit + lifecycle ``SIGHUP`` / shutdown-hang under concurrent multi-GPU CI. diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 23f53b105cb3..05e30747be89 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -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 @@ -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. @@ -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. diff --git a/source/isaaclab_newton/test/assets/test_articulation.py b/source/isaaclab_newton/test/assets/test_articulation.py index 4b27988f65ed..e2103d31c18e 100644 --- a/source/isaaclab_newton/test/assets/test_articulation.py +++ b/source/isaaclab_newton/test/assets/test_articulation.py @@ -6,16 +6,16 @@ # ignore private usage of variables warning # pyright: reportPrivateUsage=none -"""Launch Isaac Sim Simulator first.""" - -from isaaclab.app import AppLauncher - -HEADLESS = True - -# launch omniverse app -simulation_app = AppLauncher(headless=True).app - -"""Rest everything follows.""" +"""Kitless newton tests: run the newton physics backend without booting Kit. + +``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 From 0e1921391c792d9f898304f2fb9b8036d1cc2fd1 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Sat, 30 May 2026 23:21:09 +0000 Subject: [PATCH 2/4] Convert remaining newton-only tests to kitless Mirror test_articulation's pattern across five sibling tests that don't need Kit: remove the AppLauncher boot at module top, replace docstring with a rationale comment pointing back to test_articulation. Files: test_visualization_markers, test_deformable_object, test_rigid_deformable_coupling, test_rigid_object_collection, test_newton_schemas. SimulationContext's existing has_kit() gate already handles the kitless branch. Also: re-targets the changelog fragment to schemas.py only (the only user-visible change), demoting per-test changes to .skip in the newton and contrib packages. --- .../changelog.d/jichuanh-kitless-newton-tests.rst | 9 --------- .../test/markers/test_visualization_markers.py | 11 +++-------- .../changelog.d/jichuanh-kitless-newton-tests.skip | 0 .../test/deformable/test_deformable_object.py | 10 ++-------- .../test/deformable/test_rigid_deformable_coupling.py | 10 ++-------- .../changelog.d/jichuanh-kitless-newton-tests.skip | 0 .../isaaclab_newton/test/assets/test_articulation.py | 7 +++++-- .../test/assets/test_rigid_object_collection.py | 10 ++-------- .../isaaclab_newton/test/sim/test_newton_schemas.py | 11 ++++------- 9 files changed, 18 insertions(+), 50 deletions(-) create mode 100644 source/isaaclab_contrib/changelog.d/jichuanh-kitless-newton-tests.skip create mode 100644 source/isaaclab_newton/changelog.d/jichuanh-kitless-newton-tests.skip diff --git a/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst b/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst index e282286d686c..a90832ac0fb4 100644 --- a/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst +++ b/source/isaaclab/changelog.d/jichuanh-kitless-newton-tests.rst @@ -6,12 +6,3 @@ Fixed 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. - -Changed -^^^^^^^ - -* The ``test_articulation.py`` newton integration tests no longer boot Kit at - module level: :func:`AppLauncher` is dropped and the existing kitless - branch in :class:`~isaaclab.sim.SimulationContext` (already gated by - :func:`~isaaclab.utils.version.has_kit`) carries the test. Avoids the Kit - lifecycle ``SIGHUP`` / shutdown-hang under concurrent multi-GPU CI. diff --git a/source/isaaclab/test/markers/test_visualization_markers.py b/source/isaaclab/test/markers/test_visualization_markers.py index b9ae8387cf0f..a325a1a93e4f 100644 --- a/source/isaaclab/test/markers/test_visualization_markers.py +++ b/source/isaaclab/test/markers/test_visualization_markers.py @@ -3,14 +3,9 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""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 (avoid Kit lifecycle SIGHUP/shutdown-hang under concurrent +multi-GPU CI; SimulationContext's existing has_kit() gate carries the test).""" import isaaclab_visualizers.newton.newton_visualization_markers as newton_markers import isaaclab_visualizers.newton.newton_visualizer as newton_visualizer diff --git a/source/isaaclab_contrib/changelog.d/jichuanh-kitless-newton-tests.skip b/source/isaaclab_contrib/changelog.d/jichuanh-kitless-newton-tests.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_contrib/test/deformable/test_deformable_object.py b/source/isaaclab_contrib/test/deformable/test_deformable_object.py index d64cc39331bd..4f71157f8bb1 100644 --- a/source/isaaclab_contrib/test/deformable/test_deformable_object.py +++ b/source/isaaclab_contrib/test/deformable/test_deformable_object.py @@ -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 +isaaclab_newton/test/assets/test_articulation.py for rationale.""" import pytest import torch diff --git a/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py b/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py index 4b00c0d2e371..86da5749a524 100644 --- a/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py +++ b/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py @@ -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 +isaaclab_newton/test/assets/test_articulation.py for rationale.""" import pytest import torch diff --git a/source/isaaclab_newton/changelog.d/jichuanh-kitless-newton-tests.skip b/source/isaaclab_newton/changelog.d/jichuanh-kitless-newton-tests.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_newton/test/assets/test_articulation.py b/source/isaaclab_newton/test/assets/test_articulation.py index e2103d31c18e..284218a5dc86 100644 --- a/source/isaaclab_newton/test/assets/test_articulation.py +++ b/source/isaaclab_newton/test/assets/test_articulation.py @@ -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 diff --git a/source/isaaclab_newton/test/assets/test_rigid_object_collection.py b/source/isaaclab_newton/test/assets/test_rigid_object_collection.py index 18928bedb5d8..c200311a88f3 100644 --- a/source/isaaclab_newton/test/assets/test_rigid_object_collection.py +++ b/source/isaaclab_newton/test/assets/test_rigid_object_collection.py @@ -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 diff --git a/source/isaaclab_newton/test/sim/test_newton_schemas.py b/source/isaaclab_newton/test/sim/test_newton_schemas.py index 67ee6265d82c..d543c78d91e7 100644 --- a/source/isaaclab_newton/test/sim/test_newton_schemas.py +++ b/source/isaaclab_newton/test/sim/test_newton_schemas.py @@ -3,14 +3,11 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Tests for Newton and MuJoCo schema cfg classes in isaaclab_newton.""" +"""Tests for Newton and MuJoCo schema cfg classes in isaaclab_newton. -from isaaclab.app import AppLauncher - -# launch omniverse app -simulation_app = AppLauncher(headless=True).app - -"""Rest everything follows.""" +Kitless: no AppLauncher boot. See isaaclab_newton/test/assets/test_articulation.py +for rationale. +""" import pytest from isaaclab_newton.sim.schemas import ( From d887cba5aab32d723431697cd2962fdc73716de8 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Sat, 30 May 2026 23:32:43 +0000 Subject: [PATCH 3/4] Revert kitless conversion on three tests that need Kit Three of the six newton-only tests instantiate SimulationContext with the default SimulationCfg, which resolves the physics backend through ``string_to_callable`` and binds to ``omni.physics``. Without Kit booted, setup raises ``ModuleNotFoundError: No module named 'omni.physics'``. Reverting test_newton_schemas, test_deformable_object, and test_rigid_deformable_coupling to keep their AppLauncher boot. The three files that work with the kitless path (test_articulation, test_rigid_object_collection, test_visualization_markers) remain converted. Making the failing three kitless requires switching them to the newton-aware cfg pattern test_articulation uses, which is out of scope here. --- .../test/deformable/test_deformable_object.py | 10 ++++++++-- .../test/deformable/test_rigid_deformable_coupling.py | 10 ++++++++-- .../isaaclab_newton/test/sim/test_newton_schemas.py | 11 +++++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/source/isaaclab_contrib/test/deformable/test_deformable_object.py b/source/isaaclab_contrib/test/deformable/test_deformable_object.py index 4f71157f8bb1..d64cc39331bd 100644 --- a/source/isaaclab_contrib/test/deformable/test_deformable_object.py +++ b/source/isaaclab_contrib/test/deformable/test_deformable_object.py @@ -7,8 +7,14 @@ # pyright: reportPrivateUsage=none -"""Kitless newton-only test: no AppLauncher boot. See -isaaclab_newton/test/assets/test_articulation.py for rationale.""" +"""Launch Isaac Sim Simulator first.""" + +from isaaclab.app import AppLauncher + +# launch omniverse app +simulation_app = AppLauncher(headless=True).app + +"""Rest everything follows.""" import pytest import torch diff --git a/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py b/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py index 86da5749a524..4b00c0d2e371 100644 --- a/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py +++ b/source/isaaclab_contrib/test/deformable/test_rigid_deformable_coupling.py @@ -7,8 +7,14 @@ # pyright: reportPrivateUsage=none -"""Kitless newton-only test: no AppLauncher boot. See -isaaclab_newton/test/assets/test_articulation.py for rationale.""" +"""Launch Isaac Sim Simulator first.""" + +from isaaclab.app import AppLauncher + +# launch omniverse app +simulation_app = AppLauncher(headless=True).app + +"""Rest everything follows.""" import pytest import torch diff --git a/source/isaaclab_newton/test/sim/test_newton_schemas.py b/source/isaaclab_newton/test/sim/test_newton_schemas.py index d543c78d91e7..67ee6265d82c 100644 --- a/source/isaaclab_newton/test/sim/test_newton_schemas.py +++ b/source/isaaclab_newton/test/sim/test_newton_schemas.py @@ -3,11 +3,14 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Tests for Newton and MuJoCo schema cfg classes in isaaclab_newton. +"""Tests for Newton and MuJoCo schema cfg classes in isaaclab_newton.""" -Kitless: no AppLauncher boot. See isaaclab_newton/test/assets/test_articulation.py -for rationale. -""" +from isaaclab.app import AppLauncher + +# launch omniverse app +simulation_app = AppLauncher(headless=True).app + +"""Rest everything follows.""" import pytest from isaaclab_newton.sim.schemas import ( From c2495f3007fae80d41e1e846c79ec453b6179b3c Mon Sep 17 00:00:00 2001 From: jichuanh Date: Sat, 30 May 2026 23:54:35 +0000 Subject: [PATCH 4/4] Revert kitless conversion of test_visualization_markers 13 of 19 tests pass without Kit booted, but 6 (test_instantiation, test_usd_marker, test_rendering_context_authors_visible_usd_point_instancer, test_multiple_prototypes_marker, test_visualization_skips_updates_when_invisible, test_newton_marker_backend_registers_and_updates_state_without_frame_capture) ERROR at setup with ``ModuleNotFoundError: No module named 'omni.physics'``. Those tests construct a physx-backed visualizer path through ``string_to_callable``, which imports ``omni.physics.tensors`` from ``isaaclab_physx.physics.physx_manager``. Dropping AppLauncher costs the file 6 tests of coverage. Restoring Kit boot keeps all 19. Until those 6 paths are made kitless-aware (physics-manager selection should bypass the physx import when newton is the active backend), this file must keep its AppLauncher. --- .../test/markers/test_visualization_markers.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/isaaclab/test/markers/test_visualization_markers.py b/source/isaaclab/test/markers/test_visualization_markers.py index a325a1a93e4f..b9ae8387cf0f 100644 --- a/source/isaaclab/test/markers/test_visualization_markers.py +++ b/source/isaaclab/test/markers/test_visualization_markers.py @@ -3,9 +3,14 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Kitless newton-only test: no AppLauncher boot. See test_articulation.py -for rationale (avoid Kit lifecycle SIGHUP/shutdown-hang under concurrent -multi-GPU CI; SimulationContext's existing has_kit() gate carries the test).""" +"""Launch Isaac Sim Simulator first.""" + +from isaaclab.app import AppLauncher + +# launch omniverse app +simulation_app = AppLauncher(headless=True).app + +"""Rest everything follows.""" import isaaclab_visualizers.newton.newton_visualization_markers as newton_markers import isaaclab_visualizers.newton.newton_visualizer as newton_visualizer