|
32 | 32 | import isaaclab.sim as sim_utils |
33 | 33 | from isaaclab.assets import RigidObjectCfg |
34 | 34 | from isaaclab.sim import SimulationCfg, build_simulation_context |
35 | | -from isaaclab.sim.spawners import materials |
36 | 35 | from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR |
37 | 36 | from isaaclab.utils.math import ( |
38 | 37 | combine_frame_transforms, |
@@ -155,76 +154,16 @@ def test_initialization(num_cubes, device): |
155 | 154 |
|
156 | 155 |
|
157 | 156 | @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: |
164 | 161 | 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") |
167 | 163 |
|
168 | 164 | # Check that the framework doesn't hold excessive strong references. |
169 | 165 | assert sys.getrefcount(cube_object) < 10 |
170 | 166 |
|
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 |
228 | 167 | with pytest.raises(RuntimeError): |
229 | 168 | sim.reset() |
230 | 169 |
|
@@ -610,234 +549,6 @@ def test_rigid_body_set_material_properties(num_cubes, device): |
610 | 549 | torch.testing.assert_close(restitution_check, restitution) |
611 | 550 |
|
612 | 551 |
|
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 | | - |
841 | 552 | @pytest.mark.isaacsim_ci |
842 | 553 | @pytest.mark.parametrize("num_cubes", [2]) |
843 | 554 | @pytest.mark.parametrize("device", test_devices()) |
|
0 commit comments