Skip to content

Commit 2071c96

Browse files
Unify physics randomization events across PhysX and Newton (#5098)
# Description Asset APIs added: - PhysX: set_material_properties_index / set_material_properties_mask on Articulation, RigidObject, and RigidObjectCollection for vectorized material property writes. - Newton: set_friction_index / set_friction_mask, set_restitution_index / set_restitution_mask, and num_shapes property on all three asset types. Newton uses a single friction coefficient (mu), so friction and restitution are separate APIs. Event terms updated: - randomize_rigid_body_material — auto-detects backend at init; PhysX uses 3-tuple materials via set_material_properties_index, Newton uses separate set_friction_index / set_restitution_index. - randomize_rigid_body_collider_offsets — converted from duplicated PhysX/Newton branches to a single __call__ using writer lambdas set at init. PhysX writes rest/contact offsets; Newton maps to shape_margin / shape_gap and notifies solver. - randomize_rigid_body_com — passes body_ids through to set_coms_index and handles Newton's position-only (vec3) vs PhysX's full pose (pos + quat). NOTE: This is only implemented for completion but there seem to be an bug in mujoco warp in handing change of this value. - randomize_rigid_body_inertia — new event term for randomizing body inertia tensors. ## Type of change <!-- As you go through the list, delete the ones that are not applicable. --> - New feature (non-breaking change which adds functionality) ## Screenshots Please attach before and after screenshots of the change if applicable. <!-- Example: | Before | After | | ------ | ----- | | _gif/png before_ | _gif/png after_ | To upload images to a PR -- simply drag and drop an image while in edit mode and it should upload the image directly. You can then paste that source into the above before/after sections. --> ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there <!-- As you go through the checklist above, you can mark something as done by putting an x character in it For example, - [x] I have done this task - [ ] I have not done this task --> --------- Co-authored-by: Antoine Richard <antoiner@nvidia.com>
1 parent 3d2c67b commit 2071c96

13 files changed

Lines changed: 954 additions & 245 deletions

File tree

source/isaaclab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "4.5.30"
4+
version = "4.5.31"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,33 @@
11
Changelog
22
---------
33

4+
4.5.31 (2026-04-13)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added :class:`~isaaclab.envs.mdp.randomize_rigid_body_inertia` event term for
11+
randomizing body inertia tensors independently of mass. Supports diagonal-only
12+
(Ixx, Iyy, Izz) and full 3x3 modes.
13+
14+
Changed
15+
^^^^^^^
16+
17+
* Split :class:`~isaaclab.envs.mdp.randomize_rigid_body_material` into
18+
backend-specific implementations. PhysX uses bucket-based 3-tuple materials via the
19+
tensor API; Newton samples friction and restitution continuously per shape via
20+
view-level attribute bindings.
21+
* Converted ``randomize_rigid_body_com`` from a plain function to a
22+
:class:`~isaaclab.managers.ManagerTermBase` class with repeatable randomization
23+
from cached defaults. Newton passes position-only (vec3); PhysX passes full pose
24+
(pos + quat).
25+
* Converted ``randomize_rigid_body_collider_offsets`` from a plain function to a
26+
:class:`~isaaclab.managers.ManagerTermBase` class with backend-specific
27+
implementations. PhysX uses rest/contact offsets directly; Newton maps them to
28+
``shape_margin`` and ``shape_gap``.
29+
30+
431
4.5.30 (2026-04-13)
532
~~~~~~~~~~~~~~~~~~~
633

source/isaaclab/isaaclab/envs/mdp/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ __all__ = [
5555
"randomize_physics_scene_gravity",
5656
"randomize_rigid_body_collider_offsets",
5757
"randomize_rigid_body_com",
58+
"randomize_rigid_body_inertia",
5859
"randomize_rigid_body_mass",
5960
"randomize_rigid_body_material",
6061
"randomize_rigid_body_scale",
@@ -195,6 +196,7 @@ from .events import (
195196
randomize_physics_scene_gravity,
196197
randomize_rigid_body_collider_offsets,
197198
randomize_rigid_body_com,
199+
randomize_rigid_body_inertia,
198200
randomize_rigid_body_mass,
199201
randomize_rigid_body_material,
200202
randomize_rigid_body_scale,

source/isaaclab/isaaclab/envs/mdp/events.py

Lines changed: 620 additions & 161 deletions
Large diffs are not rendered by default.

source/isaaclab_newton/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.5.11"
4+
version = "0.5.12"
55

66
# Description
77
title = "Newton simulation interfaces for IsaacLab core package"

source/isaaclab_newton/docs/CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Changelog
22
---------
33

4+
0.5.12 (2026-04-13)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added ``set_friction_index/mask`` and ``set_restitution_index/mask`` methods to
11+
Newton assets for native material property randomization.
12+
13+
414
0.5.11 (2026-04-13)
515
~~~~~~~~~~~~~~~~~~~
616

@@ -12,6 +22,7 @@ Added
1222
registration, wildcard body matching, and zero-copy transform views.
1323

1424

25+
1526
0.5.10 (2026-04-05)
1627
~~~~~~~~~~~~~~~~~~~
1728

source/isaaclab_newton/test/assets/test_articulation.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,5 +2487,122 @@ def _patched_simulate(cls):
24872487
)
24882488

24892489

2490+
@pytest.mark.parametrize("add_ground_plane", [True])
2491+
@pytest.mark.parametrize("num_articulations", [1, 2])
2492+
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
2493+
@pytest.mark.parametrize("articulation_type", ["panda"])
2494+
@pytest.mark.isaacsim_ci
2495+
def test_set_material_properties(sim, num_articulations, device, add_ground_plane, articulation_type):
2496+
"""Test getting and setting material properties (friction/restitution) via view-level APIs."""
2497+
articulation_cfg = generate_articulation_cfg(articulation_type=articulation_type)
2498+
articulation, _ = generate_articulation(
2499+
articulation_cfg=articulation_cfg, num_articulations=num_articulations, device=device
2500+
)
2501+
2502+
# Play the simulator
2503+
sim.reset()
2504+
2505+
# Get friction/restitution bindings via view-level API
2506+
model = SimulationManager.get_model()
2507+
friction_binding = articulation._root_view.get_attribute("shape_material_mu", model)[:, 0]
2508+
restitution_binding = articulation._root_view.get_attribute("shape_material_restitution", model)[:, 0]
2509+
num_shapes = friction_binding.shape[1]
2510+
2511+
# Test 1: Set all shapes via in-place writes to the warp binding
2512+
friction = torch.empty(num_articulations, num_shapes, device=device).uniform_(0.4, 0.8)
2513+
restitution = torch.empty(num_articulations, num_shapes, device=device).uniform_(0.0, 0.2)
2514+
2515+
wp.to_torch(friction_binding)[:] = friction
2516+
wp.to_torch(restitution_binding)[:] = restitution
2517+
SimulationManager.add_model_change(SolverNotifyFlags.SHAPE_PROPERTIES)
2518+
2519+
# Simulate physics
2520+
sim.step()
2521+
articulation.update(sim.cfg.dt)
2522+
2523+
# Verify by reading back from the binding
2524+
mu = wp.to_torch(friction_binding)
2525+
restitution_check = wp.to_torch(restitution_binding)
2526+
torch.testing.assert_close(mu, friction)
2527+
torch.testing.assert_close(restitution_check, restitution)
2528+
2529+
# Test 2: Set subset of shapes (only shape 0)
2530+
if num_shapes > 1:
2531+
subset_friction = torch.empty(num_articulations, device=device).uniform_(0.1, 0.2)
2532+
subset_restitution = torch.empty(num_articulations, device=device).uniform_(0.5, 0.6)
2533+
2534+
wp.to_torch(friction_binding)[:, 0] = subset_friction
2535+
wp.to_torch(restitution_binding)[:, 0] = subset_restitution
2536+
SimulationManager.add_model_change(SolverNotifyFlags.SHAPE_PROPERTIES)
2537+
2538+
sim.step()
2539+
articulation.update(sim.cfg.dt)
2540+
2541+
# Check only the subset was updated
2542+
mu_updated = wp.to_torch(friction_binding)
2543+
restitution_updated = wp.to_torch(restitution_binding)
2544+
torch.testing.assert_close(mu_updated[:, 0], subset_friction)
2545+
torch.testing.assert_close(restitution_updated[:, 0], subset_restitution)
2546+
2547+
2548+
@pytest.mark.parametrize("num_articulations", [2])
2549+
@pytest.mark.parametrize("device", ["cuda:0"])
2550+
@pytest.mark.parametrize("add_ground_plane", [True])
2551+
@pytest.mark.parametrize("articulation_type", ["anymal"])
2552+
@pytest.mark.isaacsim_ci
2553+
def test_randomize_rigid_body_com(sim, num_articulations, device, add_ground_plane, articulation_type):
2554+
"""Test that randomize_rigid_body_com modifies CoM and affects simulation dynamics."""
2555+
articulation_cfg = generate_articulation_cfg(articulation_type=articulation_type)
2556+
articulation, _ = generate_articulation(articulation_cfg, num_articulations, device=device)
2557+
2558+
sim.reset()
2559+
assert articulation.is_initialized
2560+
2561+
original_com = wp.to_torch(articulation.data.body_com_pos_b).clone()
2562+
2563+
com_offset = torch.zeros(num_articulations, articulation.num_bodies, 3, device=device)
2564+
com_offset[..., 0] = 0.5
2565+
new_com = original_com + com_offset
2566+
env_ids = torch.arange(num_articulations, device=device, dtype=torch.int32)
2567+
articulation.set_coms_index(coms=new_com, env_ids=env_ids)
2568+
2569+
updated_com = wp.to_torch(articulation.data.body_com_pos_b)
2570+
torch.testing.assert_close(updated_com, new_com, atol=1e-5, rtol=1e-5)
2571+
2572+
2573+
@pytest.mark.parametrize("num_articulations", [2])
2574+
@pytest.mark.parametrize("device", ["cuda:0"])
2575+
@pytest.mark.parametrize("add_ground_plane", [True])
2576+
@pytest.mark.parametrize("articulation_type", ["anymal"])
2577+
@pytest.mark.isaacsim_ci
2578+
def test_randomize_rigid_body_collider_offsets(sim, num_articulations, device, add_ground_plane, articulation_type):
2579+
"""Test that Newton collider offset randomization (shape_margin, shape_gap) takes effect."""
2580+
articulation_cfg = generate_articulation_cfg(articulation_type=articulation_type)
2581+
articulation, _ = generate_articulation(articulation_cfg, num_articulations, device=device)
2582+
2583+
sim.reset()
2584+
assert articulation.is_initialized
2585+
2586+
model = SimulationManager.get_model()
2587+
original_margin = wp.to_torch(articulation.root_view.get_attribute("shape_margin", model)).clone()
2588+
original_gap = wp.to_torch(articulation.root_view.get_attribute("shape_gap", model)).clone()
2589+
2590+
new_margin = original_margin.clone()
2591+
new_margin[:, 0] += 0.01
2592+
articulation.root_view.set_attribute("shape_margin", model, wp.from_torch(new_margin, dtype=wp.float32))
2593+
2594+
new_gap = original_gap.clone()
2595+
new_gap[:, 0] += 0.005
2596+
articulation.root_view.set_attribute("shape_gap", model, wp.from_torch(new_gap, dtype=wp.float32))
2597+
2598+
with wp.ScopedDevice(device):
2599+
SimulationManager._solver.notify_model_changed(SolverNotifyFlags.SHAPE_PROPERTIES)
2600+
2601+
updated_margin = wp.to_torch(articulation.root_view.get_attribute("shape_margin", model))
2602+
updated_gap = wp.to_torch(articulation.root_view.get_attribute("shape_gap", model))
2603+
torch.testing.assert_close(updated_margin, new_margin)
2604+
torch.testing.assert_close(updated_gap, new_gap)
2605+
2606+
24902607
if __name__ == "__main__":
24912608
pytest.main([__file__, "-v", "--maxfail=1"])

0 commit comments

Comments
 (0)