Skip to content

Commit 10d9782

Browse files
committed
Consolidate overlapping isaaclab_newton tests
Merge the dynamics accessor shape and FK-refresh checks into one sim per asset, fold paired sensor and schema checks that rebuilt the same scene, remove permanently skipped tests, and skip the non-strict xfail contact sensor cases that ran to completion without producing a verdict.
1 parent 995d96c commit 10d9782

13 files changed

Lines changed: 197 additions & 1120 deletions
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Test-only change: consolidated overlapping isaaclab_newton tests (articulation dynamics
2+
accessor shapes and FK-refresh checks, sensor initialization/at-rest checks, schema
3+
author/unset pairs, frame-view guards), removed permanently skipped tests, and replaced
4+
non-strict xfail contact-sensor cases with skips. No user-visible behavior change.

source/isaaclab_newton/test/assets/test_articulation.py

Lines changed: 74 additions & 473 deletions
Large diffs are not rendered by default.

source/isaaclab_newton/test/assets/test_rigid_object.py

Lines changed: 5 additions & 294 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import isaaclab.sim as sim_utils
3333
from isaaclab.assets import RigidObjectCfg
3434
from isaaclab.sim import SimulationCfg, build_simulation_context
35-
from isaaclab.sim.spawners import materials
3635
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR
3736
from isaaclab.utils.math import (
3837
combine_frame_transforms,
@@ -155,76 +154,16 @@ def test_initialization(num_cubes, device):
155154

156155

157156
@pytest.mark.isaacsim_ci
158-
@pytest.mark.skip(reason="Newton does not support kinematic rigid bodies")
159-
@pytest.mark.parametrize("num_cubes", [1, 2])
160-
@pytest.mark.parametrize("device", test_devices())
161-
def test_initialization_with_kinematic_enabled(num_cubes, device):
162-
"""Test that initialization for prim with kinematic flag enabled."""
163-
with _newton_sim_context(device, auto_add_lighting=True) as sim:
157+
@pytest.mark.parametrize("api", ["none", "articulation_root"])
158+
def test_initialization_rejects_non_rigid_body_prims(api):
159+
"""Initialization fails when the prim path has no rigid body API or carries an articulation root."""
160+
with _newton_sim_context("cpu", auto_add_lighting=True) as sim:
164161
sim._app_control_on_stop_handle = None
165-
# Generate cubes scene
166-
cube_object, origins = generate_cubes_scene(num_cubes=num_cubes, kinematic_enabled=True, device=device)
162+
cube_object, _ = generate_cubes_scene(num_cubes=1, api=api, device="cpu")
167163

168164
# Check that the framework doesn't hold excessive strong references.
169165
assert sys.getrefcount(cube_object) < 10
170166

171-
# Play sim
172-
sim.reset()
173-
174-
# Check if object is initialized
175-
assert cube_object.is_initialized
176-
assert len(cube_object.body_names) == 1
177-
178-
# Check buffers that exists and have correct shapes
179-
assert cube_object.data.root_pos_w.torch.shape == (num_cubes, 3)
180-
assert cube_object.data.root_quat_w.torch.shape == (num_cubes, 4)
181-
182-
# Simulate physics
183-
for _ in range(2):
184-
# perform rendering
185-
sim.step()
186-
# update object
187-
cube_object.update(sim.cfg.dt)
188-
# check that the object is kinematic
189-
default_root_pose = cube_object.data.default_root_pose.torch.clone()
190-
default_root_vel = cube_object.data.default_root_vel.torch.clone()
191-
default_root_pose[:, :3] += origins
192-
torch.testing.assert_close(cube_object.data.root_link_pose_w.torch, default_root_pose)
193-
torch.testing.assert_close(cube_object.data.root_com_vel_w.torch, default_root_vel)
194-
195-
196-
@pytest.mark.isaacsim_ci
197-
@pytest.mark.parametrize("num_cubes", [1, 2])
198-
@pytest.mark.parametrize("device", test_devices())
199-
def test_initialization_with_no_rigid_body(num_cubes, device):
200-
"""Test that initialization fails when no rigid body is found at the provided prim path."""
201-
with _newton_sim_context(device, auto_add_lighting=True) as sim:
202-
sim._app_control_on_stop_handle = None
203-
# Generate cubes scene
204-
cube_object, _ = generate_cubes_scene(num_cubes=num_cubes, api="none", device=device)
205-
206-
# Check that the framework doesn't hold excessive strong references.
207-
assert sys.getrefcount(cube_object) < 10
208-
209-
# Play sim
210-
with pytest.raises(RuntimeError):
211-
sim.reset()
212-
213-
214-
@pytest.mark.isaacsim_ci
215-
@pytest.mark.parametrize("num_cubes", [1, 2])
216-
@pytest.mark.parametrize("device", test_devices())
217-
def test_initialization_with_articulation_root(num_cubes, device):
218-
"""Test that initialization fails when an articulation root is found at the provided prim path."""
219-
with _newton_sim_context(device, auto_add_lighting=True) as sim:
220-
sim._app_control_on_stop_handle = None
221-
# Generate cubes scene
222-
cube_object, _ = generate_cubes_scene(num_cubes=num_cubes, api="articulation_root", device=device)
223-
224-
# Check that the framework doesn't hold excessive strong references.
225-
assert sys.getrefcount(cube_object) < 10
226-
227-
# Play sim
228167
with pytest.raises(RuntimeError):
229168
sim.reset()
230169

@@ -610,234 +549,6 @@ def test_rigid_body_set_material_properties(num_cubes, device):
610549
torch.testing.assert_close(restitution_check, restitution)
611550

612551

613-
def _set_newton_material_properties(cube_object, friction_val, restitution_val, device):
614-
"""Helper to set material properties via Newton view-level APIs."""
615-
model = SimulationManager.get_model()
616-
friction_binding = cube_object._root_view.get_attribute("shape_material_mu", model)[:, 0]
617-
restitution_binding = cube_object._root_view.get_attribute("shape_material_restitution", model)[:, 0]
618-
num_envs = friction_binding.shape[0]
619-
num_shapes = friction_binding.shape[1]
620-
621-
friction_tensor = torch.full((num_envs, num_shapes), friction_val, device=device)
622-
restitution_tensor = torch.full((num_envs, num_shapes), restitution_val, device=device)
623-
624-
wp.to_torch(friction_binding)[:] = friction_tensor
625-
wp.to_torch(restitution_binding)[:] = restitution_tensor
626-
SimulationManager.add_model_change(ModelFlags.SHAPE_PROPERTIES)
627-
628-
629-
@pytest.mark.isaacsim_ci
630-
@pytest.mark.skip(reason="MuJoCo contact at height=0 does not settle the same as PhysX — cube falls on z-axis")
631-
@pytest.mark.parametrize("num_cubes", [1, 2])
632-
@pytest.mark.parametrize("device", test_devices())
633-
def test_rigid_body_no_friction(num_cubes, device):
634-
"""Test that a rigid object with no friction will maintain it's velocity when sliding across a plane."""
635-
with _newton_sim_context(device, auto_add_lighting=True) as sim:
636-
sim._app_control_on_stop_handle = None
637-
# Generate cubes scene
638-
cube_object, _ = generate_cubes_scene(num_cubes=num_cubes, height=0.0, device=device)
639-
640-
# Create ground plane with near-zero friction
641-
# Note: MuJoCo requires friction >= MJ_MINMU (1e-5), so we use 1e-4
642-
cfg = sim_utils.GroundPlaneCfg(
643-
physics_material=materials.RigidBodyMaterialCfg(
644-
static_friction=1e-4,
645-
dynamic_friction=1e-4,
646-
restitution=0.0,
647-
)
648-
)
649-
cfg.func("/World/GroundPlane", cfg)
650-
651-
# Play sim
652-
sim.reset()
653-
654-
# Set material friction to near-zero via view-level API
655-
_set_newton_material_properties(cube_object, friction_val=1e-4, restitution_val=0.0, device=device)
656-
657-
# Set initial velocity
658-
# Initial velocity in X to get the block moving
659-
initial_velocity = torch.zeros((num_cubes, 6), device=sim.cfg.device)
660-
initial_velocity[:, 0] = 0.1
661-
662-
cube_object.write_root_velocity_to_sim_index(root_velocity=initial_velocity)
663-
664-
# Simulate physics
665-
for _ in range(5):
666-
# perform rendering
667-
sim.step()
668-
# update object
669-
cube_object.update(sim.cfg.dt)
670-
671-
# Non-deterministic when on GPU, so we use different tolerances
672-
if device.startswith("cuda"):
673-
tolerance = 1e-2
674-
else:
675-
tolerance = 1e-5
676-
677-
torch.testing.assert_close(
678-
cube_object.data.root_lin_vel_w.torch, initial_velocity[:, :3], rtol=1e-5, atol=tolerance
679-
)
680-
681-
682-
@pytest.mark.isaacsim_ci
683-
@pytest.mark.skip(reason="MuJoCo uses Coulomb friction (single mu), no static/dynamic distinction")
684-
@pytest.mark.parametrize("num_cubes", [1, 2])
685-
@pytest.mark.parametrize("device", test_devices())
686-
def test_rigid_body_with_static_friction(num_cubes, device):
687-
"""Test that static friction applied to rigid object works as expected.
688-
689-
This test works by applying a force to the object and checking if the object moves or not based on the
690-
mu (coefficient of static friction) value set for the object. We set the static friction to be non-zero and
691-
apply a force to the object. When the force applied is below mu, the object should not move. When the force
692-
applied is above mu, the object should move.
693-
"""
694-
with _newton_sim_context(device, dt=0.01, add_ground_plane=False, auto_add_lighting=True) as sim:
695-
sim._app_control_on_stop_handle = None
696-
cube_object, _ = generate_cubes_scene(num_cubes=num_cubes, height=0.03125, device=device)
697-
698-
# Create ground plane
699-
static_friction_coefficient = 0.5
700-
cfg = sim_utils.GroundPlaneCfg(
701-
physics_material=materials.RigidBodyMaterialCfg(
702-
static_friction=static_friction_coefficient,
703-
dynamic_friction=static_friction_coefficient,
704-
)
705-
)
706-
cfg.func("/World/GroundPlane", cfg)
707-
708-
# Play sim
709-
sim.reset()
710-
711-
# Set friction via view-level API
712-
_set_newton_material_properties(
713-
cube_object,
714-
friction_val=static_friction_coefficient,
715-
restitution_val=0.0,
716-
device=device,
717-
)
718-
719-
# let everything settle
720-
for _ in range(100):
721-
sim.step()
722-
cube_object.update(sim.cfg.dt)
723-
cube_object.write_root_velocity_to_sim_index(root_velocity=torch.zeros((num_cubes, 6), device=sim.device))
724-
cube_mass = cube_object.data.body_mass.torch
725-
gravity_magnitude = abs(sim.cfg.gravity[2])
726-
# 2 cases: force applied is below and above mu
727-
# below mu: block should not move as the force applied is <= mu
728-
# above mu: block should move as the force applied is > mu
729-
for force in "below_mu", "above_mu":
730-
# set initial velocity to zero
731-
cube_object.write_root_velocity_to_sim_index(root_velocity=torch.zeros((num_cubes, 6), device=sim.device))
732-
733-
external_wrench_b = torch.zeros((num_cubes, 1, 6), device=sim.device)
734-
if force == "below_mu":
735-
external_wrench_b[..., 0] = static_friction_coefficient * cube_mass * gravity_magnitude * 0.99
736-
else:
737-
external_wrench_b[..., 0] = static_friction_coefficient * cube_mass * gravity_magnitude * 1.01
738-
739-
cube_object.permanent_wrench_composer.set_forces_and_torques_index(
740-
forces=external_wrench_b[..., :3],
741-
torques=external_wrench_b[..., 3:],
742-
)
743-
744-
# Get root state
745-
initial_root_pos = cube_object.data.root_pos_w.torch.clone()
746-
# Simulate physics
747-
for _ in range(200):
748-
# apply the wrench
749-
cube_object.write_data_to_sim()
750-
sim.step()
751-
# update object
752-
cube_object.update(sim.cfg.dt)
753-
if force == "below_mu":
754-
# Assert that the block has not moved
755-
torch.testing.assert_close(
756-
cube_object.data.root_pos_w.torch, initial_root_pos, rtol=2e-3, atol=2e-3
757-
)
758-
if force == "above_mu":
759-
assert (cube_object.data.root_pos_w.torch[..., 0] - initial_root_pos[..., 0] > 0.02).all()
760-
761-
762-
@pytest.mark.isaacsim_ci
763-
@pytest.mark.skip(reason="MuJoCo restitution model differs from PhysX — inelastic collisions still bounce")
764-
@pytest.mark.parametrize("num_cubes", [1, 2])
765-
@pytest.mark.parametrize("device", test_devices())
766-
def test_rigid_body_with_restitution(num_cubes, device):
767-
"""Test that restitution when applied to rigid object works as expected.
768-
769-
This test works by dropping a block from a height and checking if the block bounces or not based on the
770-
restitution value set for the object. We set the restitution to be non-zero and drop the block from a height.
771-
When the restitution is 0, the block should not bounce. When the restitution is between 0 and 1, the block
772-
should bounce with less energy.
773-
"""
774-
for expected_collision_type in "partially_elastic", "inelastic":
775-
with _newton_sim_context(device, add_ground_plane=False, auto_add_lighting=True) as sim:
776-
sim._app_control_on_stop_handle = None
777-
cube_object, _ = generate_cubes_scene(num_cubes=num_cubes, height=1.0, device=device)
778-
779-
# Set static friction to be non-zero
780-
if expected_collision_type == "inelastic":
781-
restitution_coefficient = 0.0
782-
elif expected_collision_type == "partially_elastic":
783-
restitution_coefficient = 0.5
784-
785-
# Create ground plane
786-
cfg = sim_utils.GroundPlaneCfg(
787-
physics_material=materials.RigidBodyMaterialCfg(
788-
restitution=restitution_coefficient,
789-
)
790-
)
791-
cfg.func("/World/GroundPlane", cfg)
792-
793-
# Play sim
794-
sim.reset()
795-
796-
root_pose = torch.zeros(num_cubes, 7, device=sim.device)
797-
root_pose[:, 3] = 1.0 # To make orientation a quaternion
798-
for i in range(num_cubes):
799-
root_pose[i, 1] = 1.0 * i
800-
root_pose[:, 2] = 1.0 # Set an initial drop height
801-
root_vel = torch.zeros(num_cubes, 6, device=sim.device)
802-
root_vel[:, 2] = -1.0 # Set an initial downward velocity
803-
804-
cube_object.write_root_pose_to_sim_index(root_pose=root_pose)
805-
cube_object.write_root_velocity_to_sim_index(root_velocity=root_vel)
806-
807-
# Set restitution via view-level API
808-
_set_newton_material_properties(
809-
cube_object,
810-
friction_val=0.0,
811-
restitution_val=restitution_coefficient,
812-
device=device,
813-
)
814-
815-
curr_z_velocity = cube_object.data.root_lin_vel_w.torch[:, 2].clone()
816-
817-
for _ in range(100):
818-
sim.step()
819-
820-
# update object
821-
cube_object.update(sim.cfg.dt)
822-
curr_z_velocity = cube_object.data.root_lin_vel_w.torch[:, 2].clone()
823-
824-
if expected_collision_type == "inelastic":
825-
# assert that the block has not bounced by checking that the z velocity is less than or equal to 0
826-
assert (curr_z_velocity <= 0.0).all()
827-
828-
if torch.all(curr_z_velocity <= 0.0):
829-
# Still in the air
830-
prev_z_velocity = curr_z_velocity
831-
else:
832-
# collision has happened, exit the for loop
833-
break
834-
835-
if expected_collision_type == "partially_elastic":
836-
# Assert that the block has lost some energy by checking that the z velocity is less
837-
assert torch.all(torch.le(abs(curr_z_velocity), abs(prev_z_velocity)))
838-
assert (curr_z_velocity > 0.0).all()
839-
840-
841552
@pytest.mark.isaacsim_ci
842553
@pytest.mark.parametrize("num_cubes", [2])
843554
@pytest.mark.parametrize("device", test_devices())

source/isaaclab_newton/test/assets/test_rigid_object_collection.py

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -243,51 +243,11 @@ def test_set_body_inertial_properties_updates_inverses(device):
243243
torch.testing.assert_close(wp.to_torch(model_inv_mass), updated_inv_mass)
244244

245245

246-
@pytest.mark.skip(reason="Newton doesn't support kinematic rigid bodies yet")
247-
@pytest.mark.parametrize("num_envs", [1, 2])
248-
@pytest.mark.parametrize("num_cubes", [1, 3])
249-
@pytest.mark.parametrize("device", test_devices())
250-
def test_initialization_with_kinematic_enabled(num_envs, num_cubes, device):
251-
"""Test that initialization for prim with kinematic flag enabled."""
252-
with _newton_sim_context(device, auto_add_lighting=True) as sim:
253-
sim._app_control_on_stop_handle = None
254-
object_collection, origins = generate_cubes_scene(
255-
num_envs=num_envs, num_cubes=num_cubes, kinematic_enabled=True, device=device
256-
)
257-
258-
# Check that the framework doesn't hold excessive strong references.
259-
assert sys.getrefcount(object_collection) < 10
260-
261-
# Play sim
262-
sim.reset()
263-
264-
# Check if object is initialized
265-
assert object_collection.is_initialized
266-
assert len(object_collection.body_names) == num_cubes
267-
268-
# Check buffers that exist and have correct shapes
269-
assert object_collection.data.body_link_pos_w.torch.shape == (num_envs, num_cubes, 3)
270-
assert object_collection.data.body_link_quat_w.torch.shape == (num_envs, num_cubes, 4)
271-
272-
# Simulate physics
273-
for _ in range(2):
274-
sim.step()
275-
object_collection.update(sim.cfg.dt)
276-
# check that the object is kinematic
277-
default_body_pose = object_collection.data.default_body_pose.torch.clone()
278-
default_body_vel = object_collection.data.default_body_vel.torch.clone()
279-
default_body_pose[..., :3] += origins.unsqueeze(1)
280-
torch.testing.assert_close(object_collection.data.body_link_pose_w.torch, default_body_pose)
281-
torch.testing.assert_close(object_collection.data.body_link_vel_w.torch, default_body_vel)
282-
283-
284-
@pytest.mark.parametrize("num_cubes", [2])
285-
@pytest.mark.parametrize("device", test_devices())
286-
def test_initialization_with_no_rigid_body(num_cubes, device):
246+
def test_initialization_with_no_rigid_body():
287247
"""Test that initialization fails when no rigid body is found at the provided prim path."""
288-
with _newton_sim_context(device, auto_add_lighting=True) as sim:
248+
with _newton_sim_context("cpu", auto_add_lighting=True) as sim:
289249
sim._app_control_on_stop_handle = None
290-
object_collection, _ = generate_cubes_scene(num_cubes=num_cubes, has_api=False, device=device)
250+
object_collection, _ = generate_cubes_scene(num_cubes=2, has_api=False, device="cpu")
291251

292252
# Check that the framework doesn't hold excessive strong references.
293253
assert sys.getrefcount(object_collection) < 10

0 commit comments

Comments
 (0)