From 0e8de0efbb081c2e67a136db86394b8e98957f57 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 4 Mar 2026 17:28:04 +0100 Subject: [PATCH 01/73] add camera for rendering deformable object, automatic video with ffmpeg, change timestepping --- .../01_assets/run_deformable_object.py | 132 ++++++++++++++++-- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 873566534e29..6f8f83a4d8c0 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -15,17 +15,44 @@ """Launch Isaac Sim Simulator first.""" - +import os import argparse +import subprocess from isaaclab.app import AppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on interacting with a deformable object.") +parser.add_argument( + "--total_time", + type=float, + default=5.0, + help="Total simulation time in seconds.", +) +parser.add_argument( + "--dt", + type=float, + default=1.0/60, + help="Simulation timestep.", +) +parser.add_argument( + "--video_fps", + type=int, + default=60, + help="FPS for the output video if --save is enabled.", +) +parser.add_argument( + "--save", + action="store_true", + default=False, + help="Save the data from camera.", +) # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments args_cli = parser.parse_args() +if args_cli.save: + args_cli.enable_cameras = True # launch omniverse app app_launcher = AppLauncher(args_cli) @@ -37,9 +64,39 @@ import warp as wp from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +import omni.replicator.core as rep + import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils from isaaclab.sim import SimulationContext +from isaaclab.sensors.camera import Camera, CameraCfg +from isaaclab.utils import convert_dict_to_backend + +# from isaaclab.assets import DeformableObject, DeformableObjectCfg +# For now import PhysX implementation for testing. +from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg + + +def define_sensor() -> Camera: + """Defines the camera sensor to add to the scene.""" + # Setup camera sensor + # In contrast to the ray-cast camera, we spawn the prim at these locations. + # This means the camera sensor will be attached to these prims. + sim_utils.create_prim("/World/OriginCamera", "Xform", translation=[0.0, 0.0, 0.0]) + camera_cfg = CameraCfg( + prim_path="/World/OriginCamera/CameraSensor", + update_period=1.0/args_cli.video_fps, + height=480, + width=640, + data_types=["rgb",], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) + ), + ) + # Create camera + camera = Camera(cfg=camera_cfg) + + return camera def design_scene(): @@ -51,6 +108,9 @@ def design_scene(): cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.8, 0.8, 0.8)) cfg.func("/World/Light", cfg) + # Create a dictionary for the scene entities + scene_entities = {} + # Create separate groups called "Origin1", "Origin2", "Origin3" # Each group will have a robot in it origins = [[0.25, 0.25, 0.0], [-0.25, 0.25, 0.0], [0.25, -0.25, 0.0], [-0.25, -0.25, 0.0]] @@ -70,20 +130,44 @@ def design_scene(): debug_vis=True, ) cube_object = DeformableObject(cfg=cfg) + scene_entities["cube_object"] = cube_object + + # Sensors + if args_cli.save: + camera = define_sensor() + scene_entities["camera"] = camera # return the scene information - scene_entities = {"cube_object": cube_object} return scene_entities, origins -def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, DeformableObject], origins: torch.Tensor): +def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: torch.Tensor, output_dir: str): """Runs the simulation loop.""" # Extract scene entities # note: we only do this here for readability. In general, it is better to access the entities directly from # the dictionary. This dictionary is replaced by the InteractiveScene class in the next tutorial. - cube_object = entities["cube_object"] + cube_object: DeformableObject = entities["cube_object"] + + # Write camera outputs + if args_cli.save: + camera: Camera = entities["camera"] + + # Create replicator writer + rep_writer = rep.BasicWriter( + output_dir=output_dir, + frame_padding=0, + rgb=True, + ) + # Camera positions, targets, orientations + camera_positions = torch.tensor([[2.5, 2.5, 2.5]], device=sim.device) + camera_targets = torch.tensor([[0.0, 0.0, 0.25]], device=sim.device) + camera.set_world_poses_from_view(camera_positions, camera_targets) + + # Define simulation stepping sim_dt = sim.get_physics_dt() + assert sim_dt <= 1.0 / args_cli.video_fps, "Simulation timestep must be smaller than the inverse of the video FPS to save frames properly." + num_steps = int(args_cli.total_time / sim_dt) sim_time = 0.0 count = 0 @@ -91,9 +175,9 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab nodal_kinematic_target = wp.to_torch(cube_object.data.nodal_kinematic_target).clone() # Simulate physics - while simulation_app.is_running(): + for t in range(num_steps): # reset - if count % 250 == 0: + if sim_time > 2.5: # reset counters sim_time = 0.0 count = 0 @@ -121,7 +205,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab # update the kinematic target for cubes at index 0 and 3 # we slightly move the cube in the z-direction by picking the vertex at index 0 - nodal_kinematic_target[[0, 3], 0, 2] += 0.001 + nodal_kinematic_target[[0, 3], 0, 2] += 0.2 * sim_dt # set vertex at index 0 to be kinematically constrained # 0: constrained, 1: free nodal_kinematic_target[[0, 3], 0, 3] = 0.0 @@ -137,15 +221,27 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab count += 1 # update buffers cube_object.update(sim_dt) + if args_cli.save: + camera.update(sim_dt) + # print the root position - if count % 50 == 0: - print(f"Root position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") + if t % args_cli.video_fps == 0: + print(f"Time {t*sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") + + # Extract camera data + if args_cli.save: + if camera.data.output["rgb"] is not None: + cam_data = convert_dict_to_backend(camera.data.output, backend="numpy") + rep_writer.write({ + "annotators": {"rgb": {"render_product": {"data": cam_data["rgb"][0]}}}, + "trigger_outputs": {"on_time": camera.frame[0]} + }) def main(): """Main function.""" # Load kit helper - sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) + sim_cfg = sim_utils.SimulationCfg(dt=args_cli.dt, device=args_cli.device) sim = SimulationContext(sim_cfg) # Set main camera sim.set_camera_view(eye=[3.0, 0.0, 1.0], target=[0.0, 0.0, 0.5]) @@ -157,8 +253,20 @@ def main(): # Now we are ready! print("[INFO]: Setup complete...") # Run the simulator - run_simulator(sim, scene_entities, scene_origins) - + camera_output = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera") + run_simulator(sim, scene_entities, scene_origins, camera_output) + # Store video if saving frames + if args_cli.save: + video_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.mp4") + fps = args_cli.video_fps + subprocess.run([ + "ffmpeg", "-y", "-loglevel", "error", + "-framerate", str(fps), + "-i", os.path.join(camera_output, "rgb_%d_0.png"), + "-c:v", "libx264", "-pix_fmt", "yuv420p", + video_path, + ], check=True) + print(f"[INFO]: Video saved to {video_path}") if __name__ == "__main__": # run the main function From c2d534553fa21ac5fac8d303063f1fe63d778953 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 6 Mar 2026 13:29:38 +0100 Subject: [PATCH 02/73] Feat: Update API to new Physx (with OmniPhysics) for deformables. All Schemata updated for DeformableBody and DeformableMaterial, but only for volume deformable. Surface deformable WIP. --- .../01_assets/run_deformable_object.py | 13 ++- .../isaaclab/isaaclab/sim/schemas/schemas.py | 33 +++++--- .../spawners/materials/physics_materials.py | 16 +++- .../materials/physics_materials_cfg.py | 5 +- .../isaaclab/sim/spawners/meshes/meshes.py | 46 +++++++++-- source/isaaclab/isaaclab/sim/utils/prims.py | 2 +- .../deformable_object/deformable_object.py | 35 ++++---- .../deformable_object_data.py | 82 ++++++++----------- 8 files changed, 136 insertions(+), 96 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 6f8f83a4d8c0..933c7ccce25c 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -26,7 +26,7 @@ parser.add_argument( "--total_time", type=float, - default=5.0, + default=4.0, help="Total simulation time in seconds.", ) parser.add_argument( @@ -177,7 +177,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # Simulate physics for t in range(num_steps): # reset - if sim_time > 2.5: + if sim_time > 4.0: # reset counters sim_time = 0.0 count = 0 @@ -204,11 +204,12 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor print("[INFO]: Resetting object state...") # update the kinematic target for cubes at index 0 and 3 + kinematic_cubes = [0, 3] # we slightly move the cube in the z-direction by picking the vertex at index 0 - nodal_kinematic_target[[0, 3], 0, 2] += 0.2 * sim_dt + nodal_kinematic_target[kinematic_cubes, 0, 2] += 0.2 * sim_dt # set vertex at index 0 to be kinematically constrained # 0: constrained, 1: free - nodal_kinematic_target[[0, 3], 0, 3] = 0.0 + nodal_kinematic_target[kinematic_cubes, 0, 3] = 0.0 # write kinematic target to simulation cube_object.write_nodal_kinematic_target_to_sim_index(nodal_kinematic_target) @@ -227,6 +228,10 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # print the root position if t % args_cli.video_fps == 0: print(f"Time {t*sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") + print(f"Cube 0 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[0].mean(0)}") + print(f"Cube 1 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[1].mean(0)}") + print(f"Cube 2 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[2].mean(0)}") + print(f"Cube 3 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[3].mean(0)}") # Extract camera data if args_cli.save: diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index dec9de5bbada..052134fc1d99 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -882,7 +882,8 @@ def define_deformable_body_properties( raise ValueError(f"Prim path '{prim_path}' is not valid.") # traverse the prim and get the mesh - matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "Mesh") + # TODO: currently we only allow volume deformables (TetMesh), if surface deformable we want the prim type to be Mesh. + matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "TetMesh") # check if the mesh is valid if len(matching_prims) == 0: raise ValueError(f"Could not find any mesh in '{prim_path}'. Please check asset.") @@ -897,9 +898,10 @@ def define_deformable_body_properties( # get deformable-body USD prim mesh_prim = matching_prims[0] # ensure PhysX deformable body API is applied - mesh_applied = mesh_prim.GetAppliedSchemas() - if "PhysxDeformableBodyAPI" not in mesh_applied: - mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") + # mesh_applied = mesh_prim.GetAppliedSchemas() + # if "PhysxDeformableBodyAPI" not in mesh_applied: + # mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") + # set deformable body properties modify_deformable_body_properties(mesh_prim.GetPrimPath(), cfg, stage) @@ -958,8 +960,8 @@ def modify_deformable_body_properties( # check if the prim is valid and has the deformable-body API if not deformable_body_prim.IsValid(): return False - if "PhysxDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - return False + # if "PhysxDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + # return False # convert to dict cfg = cfg.to_dict() @@ -983,19 +985,26 @@ def modify_deformable_body_properties( "self_collision_filter_distance", ] } + # TODO: These all belong somewhere, I need to figure out the concrete schema API locations. from omni.physx.scripts import deformableUtils as deformable_utils - status = deformable_utils.add_physx_deformable_body(stage, prim_path=prim_path, **attr_kwargs) + # mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") # Deprecated + + # status = deformable_utils.add_physx_deformable_body(stage, prim_path=prim_path, **attr_kwargs) + status = deformable_utils.set_physics_volume_deformable_body(stage, prim_path) # check if the deformable body was successfully added if not status: return False # ensure PhysX deformable API is applied (set when add_physx_deformable_body runs; apply if missing) - deformable_applied = deformable_body_prim.GetAppliedSchemas() - if "PhysxCollisionAPI" not in deformable_applied: - deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") - if "PhysxDeformableAPI" not in deformable_applied: - deformable_body_prim.AddAppliedSchema("PhysxDeformableAPI") + # deformable_applied = deformable_body_prim.GetAppliedSchemas() + # if "PhysxCollisionAPI" not in deformable_applied: + # deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") + # if "PhysxDeformableAPI" not in deformable_applied: + # deformable_body_prim.AddAppliedSchema("PhysxDeformableAPI") + + deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") + # deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # Optional # set into PhysX API (prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest) for attr_name, value in cfg.items(): diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index 1577f9e134dc..1c9c35dfa84f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -116,16 +116,24 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") # ensure PhysX deformable body material API is applied applied = prim.GetAppliedSchemas() - if "PhysxDeformableBodyMaterialAPI" not in applied: - prim.AddAppliedSchema("PhysxDeformableBodyMaterialAPI") + if "OmniPhysicsDeformableMaterialAPI" not in applied: # TODO: check surface vs volume deformable + prim.AddAppliedSchema("OmniPhysicsDeformableMaterialAPI") + if "PhysxDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("PhysxDeformableMaterialAPI") # convert to dict cfg = cfg.to_dict() del cfg["func"] - # set into PhysX API (prim attributes: physxDeformableBodyMaterial:*) + # set base attributes into OmniPhysics API + for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) for attr_name, value in cfg.items(): safe_set_attribute_on_usd_prim( - prim, f"physxDeformableBodyMaterial:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + prim, f"physxDeformableMaterial:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) # return the prim return prim diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index 222ebd8ce28c..ad39f7be1e45 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -92,6 +92,9 @@ class DeformableBodyMaterialCfg(PhysicsMaterialCfg): density: float | None = None """The material density. Defaults to None, in which case the simulation decides the default density.""" + static_friction: float = 0.25 + """The static friction. Defaults to 0.25.""" + dynamic_friction: float = 0.25 """The dynamic friction. Defaults to 0.25.""" @@ -112,7 +115,7 @@ class DeformableBodyMaterialCfg(PhysicsMaterialCfg): elasticity_damping: float = 0.005 """The elasticity damping for the deformable material. Defaults to 0.005.""" - damping_scale: float = 1.0 + damping_scale: float = 1.0 # DEPRECATED since change from SoftBodyMaterialView to DefrmableMaterialView """The damping scale for the deformable material. Defaults to 1.0. A scale of 1 corresponds to default damping. A value of 0 will only apply damping to certain motions leading diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index b6f441fc22d3..e73f9ded7944 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -328,25 +328,57 @@ def _spawn_mesh_geom_from_mesh( geom_prim_path = prim_path + "/geometry" mesh_prim_path = geom_prim_path + "/mesh" - # create the mesh prim - mesh_prim = create_prim( - mesh_prim_path, - prim_type="Mesh", - scale=scale, - attributes={ + # deformable will create a volumetric mesh, otherwise a surface mesh is used + if cfg.deformable_props is not None: + # convert surface trimesh to volumetric tet mesh for deformables + import tetgen + from collections import Counter + + tet = tetgen.TetGen(mesh.vertices, mesh.faces) + tet_vertices, tet_elements, _, _ = tet.tetrahedralize() + + # extract surface faces: triangles belonging to exactly one tetrahedron + face_count = Counter() + unique_faces = {} + for elem in tet_elements: + # TODO: Check if order matters + for i, j, k in [(0, 2, 1), (1, 2, 3), (0, 1, 3), (0, 3, 2)]: + key = tuple(sorted([elem[i], elem[j], elem[k]])) + face_count[key] += 1 + unique_faces[key] = (elem[i], elem[j], elem[k]) + surface_faces = np.array([unique_faces[k] for k, c in face_count.items() if c == 1]) + + prim_type = "TetMesh" + attributes = { + "points": tet_vertices, + "tetVertexIndices": tet_elements.flatten(), + "surfaceFaceVertexIndices": surface_faces.flatten(), + } + else: + prim_type = "Mesh" + attributes = { "points": mesh.vertices, "faceVertexIndices": mesh.faces.flatten(), "faceVertexCounts": np.asarray([3] * len(mesh.faces)), "subdivisionScheme": "bilinear", - }, + } + + # create the mesh prim + mesh_prim = create_prim( + mesh_prim_path, + prim_type=prim_type, + scale=scale, + attributes=attributes, stage=stage, ) + # note: in case of deformable objects, we need to apply the deformable properties to the mesh prim. # this is different from rigid objects where we apply the properties to the parent prim. if cfg.deformable_props is not None: # apply mass properties if cfg.mass_props is not None: + # TODO: will the deformable not set the mass automatically from the density? schemas.define_mass_properties(mesh_prim_path, cfg.mass_props, stage=stage) # apply deformable body properties schemas.define_deformable_body_properties(mesh_prim_path, cfg.deformable_props, stage=stage) diff --git a/source/isaaclab/isaaclab/sim/utils/prims.py b/source/isaaclab/isaaclab/sim/utils/prims.py index f78b8ec22e0e..39832c29612c 100644 --- a/source/isaaclab/isaaclab/sim/utils/prims.py +++ b/source/isaaclab/isaaclab/sim/utils/prims.py @@ -834,7 +834,7 @@ def bind_physics_material( applied = prim.GetAppliedSchemas() has_physics_scene_api = "PhysxSceneAPI" in applied has_collider = prim.HasAPI(UsdPhysics.CollisionAPI) - has_deformable_body = "PhysxDeformableBodyAPI" in applied + has_deformable_body = "OmniPhysicsDeformableBodyAPI" in applied # TODO: might need to check between surface and volume deformable. has_particle_system = prim.GetTypeName() == "PhysxParticleSystem" if not (has_physics_scene_api or has_collider or has_deformable_body or has_particle_system): logger.debug( diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 34dafff38a0e..e57272c47096 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -102,7 +102,7 @@ def num_bodies(self) -> int: return 1 @property - def root_view(self) -> physx.SoftBodyView: + def root_view(self) -> physx.DeformableBodyView: """Deformable body view for the asset. .. note:: @@ -111,7 +111,7 @@ def root_view(self) -> physx.SoftBodyView: return self._root_physx_view @property - def root_physx_view(self) -> physx.SoftBodyView: + def root_physx_view(self) -> physx.DeformableBodyView: """Deprecated property. Please use :attr:`root_view` instead.""" logger.warning( "The `root_physx_view` property will be deprecated in a future release. Please use `root_view` instead." @@ -119,7 +119,7 @@ def root_physx_view(self) -> physx.SoftBodyView: return self.root_view @property - def material_physx_view(self) -> physx.SoftBodyMaterialView | None: + def material_physx_view(self) -> physx.DeformableMaterialView | None: """Deformable material view for the asset (PhysX). This view is optional and may not be available if the material is not bound to the deformable body. @@ -133,22 +133,22 @@ def material_physx_view(self) -> physx.SoftBodyMaterialView | None: @property def max_sim_elements_per_body(self) -> int: """The maximum number of simulation mesh elements per deformable body.""" - return self.root_view.max_sim_elements_per_body + return self.root_view.max_simulation_elements_per_body @property def max_collision_elements_per_body(self) -> int: """The maximum number of collision mesh elements per deformable body.""" - return self.root_view.max_elements_per_body + return self.root_view.max_collision_elements_per_body @property def max_sim_vertices_per_body(self) -> int: """The maximum number of simulation mesh vertices per deformable body.""" - return self.root_view.max_sim_vertices_per_body + return self.root_view.max_simulation_nodes_per_body @property def max_collision_vertices_per_body(self) -> int: """The maximum number of collision mesh vertices per deformable body.""" - return self.root_view.max_vertices_per_body + return self.root_view.max_collision_nodes_per_body """ Operations. @@ -264,7 +264,7 @@ def write_nodal_pos_to_sim_index( self._data._nodal_state_w.timestamp = -1.0 self._data._root_pos_w.timestamp = -1.0 # set into simulation - self.root_view.set_sim_nodal_positions(self._data._nodal_pos_w.data.view(wp.float32), indices=env_ids) + self.root_view.set_simulation_nodal_positions(self._data._nodal_pos_w.data.view(wp.float32), indices=env_ids) def write_nodal_pos_to_sim_mask( self, @@ -332,7 +332,7 @@ def write_nodal_velocity_to_sim_index( self._data._nodal_state_w.timestamp = -1.0 self._data._root_vel_w.timestamp = -1.0 # set into simulation - self.root_view.set_sim_nodal_velocities(self._data._nodal_vel_w.data.view(wp.float32), indices=env_ids) + self.root_view.set_simulation_nodal_velocities(self._data._nodal_vel_w.data.view(wp.float32), indices=env_ids) def write_nodal_velocity_to_sim_mask( self, @@ -401,7 +401,7 @@ def write_nodal_kinematic_target_to_sim_index( device=self.device, ) # set into simulation - self.root_view.set_sim_kinematic_targets(self._data.nodal_kinematic_target.view(wp.float32), indices=env_ids) + self.root_view.set_simulation_nodal_kinematic_targets(self._data.nodal_kinematic_target.view(wp.float32), indices=env_ids) def write_nodal_kinematic_target_to_sim_mask( self, @@ -541,13 +541,13 @@ def _initialize_impl(self): # find deformable root prims root_prims = sim_utils.get_all_matching_child_prims( template_prim_path, - predicate=lambda prim: "PhysxDeformableBodyAPI" in prim.GetAppliedSchemas(), + predicate=lambda prim: "OmniPhysicsDeformableBodyAPI" in prim.GetAppliedSchemas(), traverse_instance_prims=False, ) if len(root_prims) == 0: raise RuntimeError( f"Failed to find a deformable body when resolving '{self.cfg.prim_path}'." - " Please ensure that the prim has 'PhysxDeformableBodyAPI' applied." + " Please ensure that the prim has 'OmniPhysicsDeformableBodyAPI' applied." ) if len(root_prims) > 1: raise RuntimeError( @@ -571,7 +571,8 @@ def _initialize_impl(self): if len(material_paths) > 0: for mat_path in material_paths: mat_prim = root_prim.GetStage().GetPrimAtPath(mat_path) - if "PhysxDeformableBodyMaterialAPI" in mat_prim.GetAppliedSchemas(): + # TODO: surface deformable requires different check OmniPhysicsSurfaceDeformableMaterialAPI + if "OmniPhysicsDeformableMaterialAPI" in mat_prim.GetAppliedSchemas(): material_prim = mat_prim break if material_prim is None: @@ -587,7 +588,7 @@ def _initialize_impl(self): root_prim_path = root_prim.GetPath().pathString root_prim_path_expr = self.cfg.prim_path + root_prim_path[len(template_prim_path) :] # -- object view - self._root_physx_view = self._physics_sim_view.create_soft_body_view(root_prim_path_expr.replace(".*", "*")) + self._root_physx_view = self._physics_sim_view.create_volume_deformable_body_view(root_prim_path_expr.replace(".*", "*")) # Return if the asset is not found if self._root_physx_view._backend is None: @@ -604,7 +605,7 @@ def _initialize_impl(self): else: material_prim_path_expr = material_prim_path # -- material view - self._material_physx_view = self._physics_sim_view.create_soft_body_material_view( + self._material_physx_view = self._physics_sim_view.create_deformable_material_view( material_prim_path_expr.replace(".*", "*") ) else: @@ -641,7 +642,7 @@ def _create_buffers(self): # default state # we use the initial nodal positions at spawn time as the default state # note: these are all in the simulation frame - nodal_positions_raw = self.root_view.get_sim_nodal_positions() # (N, V, 3) float32 + nodal_positions_raw = self.root_view.get_simulation_nodal_positions() # (N, V, 3) float32 nodal_positions = nodal_positions_raw.view(wp.vec3f).reshape( (self.num_instances, self.max_sim_vertices_per_body) ) @@ -661,7 +662,7 @@ def _create_buffers(self): ) # kinematic targets — allocate our own buffer and copy from PhysX - kinematic_raw = self.root_view.get_sim_kinematic_targets() # (N, V, 4) float32 + kinematic_raw = self.root_view.get_simulation_nodal_kinematic_targets() # (N, V, 4) float32 kinematic_view = kinematic_raw.view(wp.vec4f).reshape((self.num_instances, self.max_sim_vertices_per_body)) self._data.nodal_kinematic_target = wp.zeros( (self.num_instances, self.max_sim_vertices_per_body), dtype=wp.vec4f, device=self.device diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py index 9e7e82499167..d275378330da 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py @@ -34,7 +34,7 @@ class DeformableObjectData: is older than the current simulation timestamp. The timestamp is updated whenever the data is updated. """ - def __init__(self, root_view: physx.SoftBodyView, device: str): + def __init__(self, root_view: physx.DeformableBodyView, device: str): """Initializes the deformable object data. Args: @@ -46,13 +46,13 @@ def __init__(self, root_view: physx.SoftBodyView, device: str): # Set the root deformable body view # note: this is stored as a weak reference to avoid circular references between the asset class # and the data container. This is important to avoid memory leaks. - self._root_view: physx.SoftBodyView = weakref.proxy(root_view) + self._root_view: physx.DeformableBodyView = weakref.proxy(root_view) # Store dimensions self._num_instances = root_view.count - self._max_sim_vertices = root_view.max_sim_vertices_per_body - self._max_sim_elements = root_view.max_sim_elements_per_body - self._max_collision_elements = root_view.max_elements_per_body + self._max_sim_vertices = root_view.max_simulation_nodes_per_body + self._max_sim_elements = root_view.max_simulation_elements_per_body + self._max_collision_elements = root_view.max_collision_elements_per_body # Set initial time stamp self._sim_timestamp = 0.0 @@ -125,9 +125,9 @@ def update(self, dt: float): def nodal_pos_w(self) -> wp.array: """Nodal positions in simulation world frame. Shape is (num_instances, max_sim_vertices_per_body) vec3f.""" if self._nodal_pos_w.timestamp < self._sim_timestamp: - # get_sim_nodal_positions() returns (N, V, 3) float32 — view as (N, V) vec3f + # get_simulation_nodal_positions() returns (N, V, 3) float32 — view as (N, V) vec3f self._nodal_pos_w.data = ( - self._root_view.get_sim_nodal_positions() + self._root_view.get_simulation_nodal_positions() .view(wp.vec3f) .reshape((self._num_instances, self._max_sim_vertices)) ) @@ -139,7 +139,7 @@ def nodal_vel_w(self) -> wp.array: """Nodal velocities in simulation world frame. Shape is (num_instances, max_sim_vertices_per_body) vec3f.""" if self._nodal_vel_w.timestamp < self._sim_timestamp: self._nodal_vel_w.data = ( - self._root_view.get_sim_nodal_velocities() + self._root_view.get_simulation_nodal_velocities() .view(wp.vec3f) .reshape((self._num_instances, self._max_sim_vertices)) ) @@ -169,14 +169,10 @@ def sim_element_quat_w(self) -> wp.array: The rotations are stored as quaternions in the order (x, y, z, w). """ - if self._sim_element_quat_w.timestamp < self._sim_timestamp: - self._sim_element_quat_w.data = ( - self._root_view.get_sim_element_rotations() - .reshape((self._num_instances, self._max_sim_elements, 4)) - .view(wp.quatf) - ) - self._sim_element_quat_w.timestamp = self._sim_timestamp - return self._sim_element_quat_w.data + # deprecated + raise NotImplementedError( + "The sim_element_quat_w property is deprecated and will be removed in future versions. " + ) @property def collision_element_quat_w(self) -> wp.array: @@ -185,64 +181,50 @@ def collision_element_quat_w(self) -> wp.array: The rotations are stored as quaternions in the order (x, y, z, w). """ - if self._collision_element_quat_w.timestamp < self._sim_timestamp: - self._collision_element_quat_w.data = ( - self._root_view.get_element_rotations() - .reshape((self._num_instances, self._max_collision_elements, 4)) - .view(wp.quatf) - ) - self._collision_element_quat_w.timestamp = self._sim_timestamp - return self._collision_element_quat_w.data + # deprecated + raise NotImplementedError( + "The collision_element_quat_w property is deprecated and will be removed in future versions. " + ) @property def sim_element_deform_gradient_w(self) -> wp.array: """Simulation mesh element-wise second-order deformation gradient tensors for the deformable bodies in simulation world frame. Shape is (num_instances, max_sim_elements_per_body, 3, 3). """ - if self._sim_element_deform_gradient_w.timestamp < self._sim_timestamp: - self._sim_element_deform_gradient_w.data = self._root_view.get_sim_element_deformation_gradients().reshape( - (self._num_instances, self._max_sim_elements, 3, 3) - ) - self._sim_element_deform_gradient_w.timestamp = self._sim_timestamp - return self._sim_element_deform_gradient_w.data + # deprecated + raise NotImplementedError( + "The sim_element_deform_gradient_w property is deprecated and will be removed in future versions. " + ) @property def collision_element_deform_gradient_w(self) -> wp.array: """Collision mesh element-wise second-order deformation gradient tensors for the deformable bodies in simulation world frame. Shape is (num_instances, max_collision_elements_per_body, 3, 3). """ - if self._collision_element_deform_gradient_w.timestamp < self._sim_timestamp: - self._collision_element_deform_gradient_w.data = ( - self._root_view.get_element_deformation_gradients().reshape( - (self._num_instances, self._max_collision_elements, 3, 3) - ) - ) - self._collision_element_deform_gradient_w.timestamp = self._sim_timestamp - return self._collision_element_deform_gradient_w.data + # deprecated + raise NotImplementedError( + "The collision_element_deform_gradient_w property is deprecated and will be removed in future versions. " + ) @property def sim_element_stress_w(self) -> wp.array: """Simulation mesh element-wise second-order Cauchy stress tensors for the deformable bodies in simulation world frame. Shape is (num_instances, max_sim_elements_per_body, 3, 3). """ - if self._sim_element_stress_w.timestamp < self._sim_timestamp: - self._sim_element_stress_w.data = self._root_view.get_sim_element_stresses().reshape( - (self._num_instances, self._max_sim_elements, 3, 3) - ) - self._sim_element_stress_w.timestamp = self._sim_timestamp - return self._sim_element_stress_w.data + # deprecated + raise NotImplementedError( + "The sim_element_stress_w property is deprecated and will be removed in future versions. " + ) @property def collision_element_stress_w(self) -> wp.array: """Collision mesh element-wise second-order Cauchy stress tensors for the deformable bodies in simulation world frame. Shape is (num_instances, max_collision_elements_per_body, 3, 3). """ - if self._collision_element_stress_w.timestamp < self._sim_timestamp: - self._collision_element_stress_w.data = self._root_view.get_element_stresses().reshape( - (self._num_instances, self._max_collision_elements, 3, 3) - ) - self._collision_element_stress_w.timestamp = self._sim_timestamp - return self._collision_element_stress_w.data + # deprecated + raise NotImplementedError( + "The collision_element_stress_w property is deprecated and will be removed in future versions. " + ) ## # Derived properties. From 0a3c3c236371c202003d41b3f4a008b3aadc9d79 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 9 Mar 2026 09:05:56 +0100 Subject: [PATCH 03/73] Feat: Manually binding render mesh to simulation TetMesh --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 68 ++++++----- .../isaaclab/sim/schemas/schemas_cfg.py | 2 +- .../isaaclab/sim/spawners/meshes/meshes.py | 107 ++++++++++++------ 3 files changed, 114 insertions(+), 63 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 052134fc1d99..5647d835e82a 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -966,25 +966,25 @@ def modify_deformable_body_properties( # convert to dict cfg = cfg.to_dict() # set into deformable body API - attr_kwargs = { - attr_name: cfg.pop(attr_name) - for attr_name in [ - "kinematic_enabled", - "collision_simplification", - "collision_simplification_remeshing", - "collision_simplification_remeshing_resolution", - "collision_simplification_target_triangle_count", - "collision_simplification_force_conforming", - "simulation_hexahedral_resolution", - "solver_position_iteration_count", - "vertex_velocity_damping", - "sleep_damping", - "sleep_threshold", - "settling_threshold", - "self_collision", - "self_collision_filter_distance", - ] - } + # attr_kwargs = { + # attr_name: cfg.pop(attr_name) + # for attr_name in [ + # "kinematic_enabled", + # "collision_simplification", + # "collision_simplification_remeshing", + # "collision_simplification_remeshing_resolution", + # "collision_simplification_target_triangle_count", + # "collision_simplification_force_conforming", + # "simulation_hexahedral_resolution", + # "solver_position_iteration_count", + # "vertex_velocity_damping", + # "sleep_damping", + # "sleep_threshold", + # "settling_threshold", + # "self_collision", + # "self_collision_filter_distance", + # ] + # } # TODO: These all belong somewhere, I need to figure out the concrete schema API locations. from omni.physx.scripts import deformableUtils as deformable_utils @@ -995,17 +995,33 @@ def modify_deformable_body_properties( # check if the deformable body was successfully added if not status: return False - + # ensure PhysX deformable API is applied (set when add_physx_deformable_body runs; apply if missing) - # deformable_applied = deformable_body_prim.GetAppliedSchemas() - # if "PhysxCollisionAPI" not in deformable_applied: - # deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") + deformable_applied = deformable_body_prim.GetAppliedSchemas() + if "PhysxDeformableBodyAPI" not in deformable_applied: + deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # TODO: This will contain all the solverPositionIterationCount, linearDamping, settlingThreshold, etc. + + for attr_name in ["solver_position_iteration_count", "linear_damping", "max_linear_velocity", "settling_damping", "settling_threshold", "sleep_threshold", "max_depenetration_velocity", "self_collision", "self_collision_filter_distance", "enable_speculative_ccd", "disable_gravity"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + + # ensure OmniPhysics API is applied + if "OmniPhysicsBodyAPI" not in deformable_applied: + deformable_body_prim.AddAppliedSchema("OmniPhysicsBodyAPI") + + for attr_name in ["kinematic_enabled", "deformable_body_enabled"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + deformable_body_prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + + if "PhysxCollisionAPI" not in deformable_applied: + deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") # if "PhysxDeformableAPI" not in deformable_applied: # deformable_body_prim.AddAppliedSchema("PhysxDeformableAPI") - deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") - # deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # Optional - # set into PhysX API (prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest) for attr_name, value in cfg.items(): if attr_name in ["rest_offset", "contact_offset"]: diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index df2569c57714..f50e652cb35b 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -323,7 +323,7 @@ class DeformableBodyPropertiesCfg: the properties and leave the rest as-is. """ - deformable_enabled: bool | None = None + deformable_body_enabled: bool | None = None """Enables deformable body.""" kinematic_enabled: bool = False diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index e73f9ded7944..81129655ad26 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -328,50 +328,85 @@ def _spawn_mesh_geom_from_mesh( geom_prim_path = prim_path + "/geometry" mesh_prim_path = geom_prim_path + "/mesh" + # create the mesh prim + if cfg.deformable_props is None: + mesh_prim = create_prim( + mesh_prim_path, + prim_type="Mesh", + scale=scale, + attributes={ + "points": mesh.vertices, + "faceVertexIndices": mesh.faces.flatten(), + "faceVertexCounts": np.asarray([3] * len(mesh.faces)), + "subdivisionScheme": "bilinear", + }, + stage=stage, + ) # deformable will create a volumetric mesh, otherwise a surface mesh is used - if cfg.deformable_props is not None: + else: + # create all the paths we need for clarity + sim_mesh_prim_path = prim_path + "/sim_mesh" + render_mesh_prim_path = sim_mesh_prim_path + "/render_mesh" + # convert surface trimesh to volumetric tet mesh for deformables import tetgen - from collections import Counter + # from collections import Counter tet = tetgen.TetGen(mesh.vertices, mesh.faces) tet_vertices, tet_elements, _, _ = tet.tetrahedralize() # extract surface faces: triangles belonging to exactly one tetrahedron - face_count = Counter() - unique_faces = {} - for elem in tet_elements: - # TODO: Check if order matters - for i, j, k in [(0, 2, 1), (1, 2, 3), (0, 1, 3), (0, 3, 2)]: - key = tuple(sorted([elem[i], elem[j], elem[k]])) - face_count[key] += 1 - unique_faces[key] = (elem[i], elem[j], elem[k]) - surface_faces = np.array([unique_faces[k] for k, c in face_count.items() if c == 1]) - - prim_type = "TetMesh" - attributes = { - "points": tet_vertices, - "tetVertexIndices": tet_elements.flatten(), - "surfaceFaceVertexIndices": surface_faces.flatten(), - } - else: - prim_type = "Mesh" - attributes = { - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", - } - - # create the mesh prim - mesh_prim = create_prim( - mesh_prim_path, - prim_type=prim_type, - scale=scale, - attributes=attributes, - stage=stage, - ) - + # face_count = Counter() + # unique_faces = {} + # for elem in tet_elements: + # # TODO: Check if order matters + # for i, j, k in [(0, 2, 1), (1, 2, 3), (0, 1, 3), (0, 3, 2)]: + # key = tuple(sorted([elem[i], elem[j], elem[k]])) + # face_count[key] += 1 + # unique_faces[key] = (elem[i], elem[j], elem[k]) + # surface_faces = np.array([unique_faces[k] for k, c in face_count.items() if c == 1]) + + mesh_prim = create_prim( + sim_mesh_prim_path, + prim_type="TetMesh", + scale=scale, + attributes={ + "points": tet_vertices, + "tetVertexIndices": tet_elements.flatten(), + }, + stage=stage, + ) + + + # create the mesh prim + render_mesh_prim = create_prim( + render_mesh_prim_path, + prim_type="Mesh", + scale=scale, + attributes={ + "points": mesh.vertices, + "faceVertexIndices": mesh.faces.flatten(), + "faceVertexCounts": np.asarray([3] * len(mesh.faces)), + "subdivisionScheme": "bilinear", + }, + stage=stage, + ) + + + # bind pose of render mesh to sim mesh for deformable objects + purposesAttrName = "deformablePose:default:omniphysics:purposes" + pointsAttrName = "deformablePose:default:omniphysics:points" + mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") + if mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): + mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) + mesh_prim.GetAttribute(pointsAttrName).Set(mesh_prim.GetAttribute("points").Get()) + + render_mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") + if render_mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): + render_mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) + render_mesh_prim.GetAttribute(pointsAttrName).Set(render_mesh_prim.GetAttribute("points").Get()) + + mesh_prim_path = sim_mesh_prim_path # note: in case of deformable objects, we need to apply the deformable properties to the mesh prim. # this is different from rigid objects where we apply the properties to the parent prim. From 342b5c05833c2b043049b30106af73c8f9e59aa6 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 9 Mar 2026 09:06:11 +0100 Subject: [PATCH 04/73] Test: Visualize COM for deformable cubes --- .../01_assets/run_deformable_object.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 933c7ccce25c..92bb14b976a7 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -64,6 +64,9 @@ import warp as wp from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +import matplotlib.pyplot as plt +import numpy as np + import omni.replicator.core as rep import isaaclab.sim as sim_utils @@ -175,6 +178,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor nodal_kinematic_target = wp.to_torch(cube_object.data.nodal_kinematic_target).clone() # Simulate physics + com_traj = [] for t in range(num_steps): # reset if sim_time > 4.0: @@ -225,6 +229,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor if args_cli.save: camera.update(sim_dt) + com_traj.append(wp.to_torch(cube_object.data.nodal_pos_w).mean(1).cpu().numpy()) # print the root position if t % args_cli.video_fps == 0: print(f"Time {t*sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") @@ -233,6 +238,18 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor print(f"Cube 2 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[2].mean(0)}") print(f"Cube 3 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[3].mean(0)}") + trajectories = np.stack(com_traj, axis=1) + time_axis = np.arange(trajectories.shape[1]) * sim_dt + fig, ax = plt.subplots(figsize=(4, 3)) + for i in range(4): + ax.plot(time_axis, trajectories[i, :, 2], label=f"Cube {i}") + ax.set_xlabel("Time (s)") + ax.set_ylabel("Z Position (m)") + ax.legend() + ax.grid() + fig.savefig(os.path.join(output_dir, f"com_trajectory.png"), dpi=300, bbox_inches="tight") + plt.close(fig) + # Extract camera data if args_cli.save: if camera.data.output["rgb"] is not None: From 70db7b054c2eb5bdcae0078eca4a9a2520239e58 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 9 Mar 2026 10:33:01 +0100 Subject: [PATCH 05/73] Feat: Simpler loading for volume deformables --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 113 ++++++++++-------- .../isaaclab/sim/spawners/meshes/meshes.py | 65 ++++------ 2 files changed, 84 insertions(+), 94 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 5647d835e82a..694333393654 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -880,30 +880,35 @@ def define_deformable_body_properties( # check if prim path is valid if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") + # check if prim has deformable body applied on it + if "OmniPhysicsDeformableBodyAPI" not in prim.GetAppliedSchemas(): + raise ValueError(f"Prim path '{prim_path}' does not have the deformable body schema applied.") - # traverse the prim and get the mesh + # traverse the prim and get the collision mesh # TODO: currently we only allow volume deformables (TetMesh), if surface deformable we want the prim type to be Mesh. - matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "TetMesh") - # check if the mesh is valid - if len(matching_prims) == 0: - raise ValueError(f"Could not find any mesh in '{prim_path}'. Please check asset.") - if len(matching_prims) > 1: - # get list of all meshes found - mesh_paths = [p.GetPrimPath() for p in matching_prims] - raise ValueError( - f"Found multiple meshes in '{prim_path}': {mesh_paths}." - " Deformable body schema can only be applied to one mesh." - ) + # matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "TetMesh") + # # check if the mesh is valid + # if len(matching_prims) == 0: + # raise ValueError(f"Could not find any mesh in '{prim_path}'. Please check asset.") + # if len(matching_prims) > 1: + # # get list of all meshes found + # mesh_paths = [p.GetPrimPath() for p in matching_prims] + # raise ValueError( + # f"Found multiple meshes in '{prim_path}': {mesh_paths}." + # " Deformable body schema can only be applied to one mesh." + # ) # get deformable-body USD prim - mesh_prim = matching_prims[0] + # mesh_prim = matching_prims[0] # ensure PhysX deformable body API is applied # mesh_applied = mesh_prim.GetAppliedSchemas() - # if "PhysxDeformableBodyAPI" not in mesh_applied: - # mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") + + # TODO: Make this general for engines besides PhysX + if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): + prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # set deformable body properties - modify_deformable_body_properties(mesh_prim.GetPrimPath(), cfg, stage) + modify_deformable_body_properties(prim_path, cfg, stage) @apply_nested @@ -960,8 +965,32 @@ def modify_deformable_body_properties( # check if the prim is valid and has the deformable-body API if not deformable_body_prim.IsValid(): return False - # if "PhysxDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - # return False + # TODO: Make this general for engines besides PhysX + deformable_applied_schemas = deformable_body_prim.GetAppliedSchemas() + if "PhysxBaseDeformableBodyAPI" not in deformable_applied_schemas: + return False + if "OmniPhysicsBodyAPI" not in deformable_applied_schemas: + return False + + # get collision prim + matching_prims = get_all_matching_child_prims(prim_path, lambda p: "PhysicsCollisionAPI" in p.GetAppliedSchemas()) + # check if the mesh is valid + if len(matching_prims) == 0: + raise ValueError(f"Could not find any collision mesh in '{prim_path}'. Please check asset.") + if len(matching_prims) > 1: + # get list of all meshes found + mesh_paths = [p.GetPrimPath() for p in matching_prims] + raise ValueError( + f"Found multiple collision meshes in '{prim_path}': {mesh_paths}." + " Deformable body schema can only be applied to one mesh." + ) + col_prim = matching_prims[0] + + # ensure PhysX collision API is applied on the collision mesh + # TODO: Specific to PhysX + if "PhysxCollisionAPI" not in col_prim.GetAppliedSchemas(): + col_prim.AddAppliedSchema("PhysxCollisionAPI") + # convert to dict cfg = cfg.to_dict() @@ -991,48 +1020,34 @@ def modify_deformable_body_properties( # mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") # Deprecated # status = deformable_utils.add_physx_deformable_body(stage, prim_path=prim_path, **attr_kwargs) - status = deformable_utils.set_physics_volume_deformable_body(stage, prim_path) + # status = deformable_utils.set_physics_volume_deformable_body(stage, prim_path) # check if the deformable body was successfully added - if not status: - return False - - # ensure PhysX deformable API is applied (set when add_physx_deformable_body runs; apply if missing) - deformable_applied = deformable_body_prim.GetAppliedSchemas() - if "PhysxDeformableBodyAPI" not in deformable_applied: - deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # TODO: This will contain all the solverPositionIterationCount, linearDamping, settlingThreshold, etc. - - for attr_name in ["solver_position_iteration_count", "linear_damping", "max_linear_velocity", "settling_damping", "settling_threshold", "sleep_threshold", "max_depenetration_velocity", "self_collision", "self_collision_filter_distance", "enable_speculative_ccd", "disable_gravity"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) + # if not status: + # return False - # ensure OmniPhysics API is applied - if "OmniPhysicsBodyAPI" not in deformable_applied: - deformable_body_prim.AddAppliedSchema("OmniPhysicsBodyAPI") - - for attr_name in ["kinematic_enabled", "deformable_body_enabled"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - if "PhysxCollisionAPI" not in deformable_applied: - deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") - # if "PhysxDeformableAPI" not in deformable_applied: - # deformable_body_prim.AddAppliedSchema("PhysxDeformableAPI") - - # set into PhysX API (prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest) + # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) for attr_name, value in cfg.items(): if attr_name in ["rest_offset", "contact_offset"]: safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + col_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + elif attr_name in ["kinematic_enabled", "deformable_body_enabled"]: + safe_set_attribute_on_usd_prim( + deformable_body_prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) else: safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxDeformable:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) + # explicit: + # for attr_name in ["solver_position_iteration_count", "linear_damping", "max_linear_velocity", "settling_damping", "settling_threshold", "sleep_threshold", "max_depenetration_velocity", "self_collision", "self_collision_filter_distance", "enable_speculative_ccd", "disable_gravity"]: + # value = cfg.pop(attr_name, None) + # safe_set_attribute_on_usd_prim( + # deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + # ) + # success return True diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 81129655ad26..78e101136d3d 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -342,41 +342,11 @@ def _spawn_mesh_geom_from_mesh( }, stage=stage, ) - # deformable will create a volumetric mesh, otherwise a surface mesh is used else: # create all the paths we need for clarity - sim_mesh_prim_path = prim_path + "/sim_mesh" - render_mesh_prim_path = sim_mesh_prim_path + "/render_mesh" - - # convert surface trimesh to volumetric tet mesh for deformables - import tetgen - # from collections import Counter - - tet = tetgen.TetGen(mesh.vertices, mesh.faces) - tet_vertices, tet_elements, _, _ = tet.tetrahedralize() - - # extract surface faces: triangles belonging to exactly one tetrahedron - # face_count = Counter() - # unique_faces = {} - # for elem in tet_elements: - # # TODO: Check if order matters - # for i, j, k in [(0, 2, 1), (1, 2, 3), (0, 1, 3), (0, 3, 2)]: - # key = tuple(sorted([elem[i], elem[j], elem[k]])) - # face_count[key] += 1 - # unique_faces[key] = (elem[i], elem[j], elem[k]) - # surface_faces = np.array([unique_faces[k] for k, c in face_count.items() if c == 1]) - - mesh_prim = create_prim( - sim_mesh_prim_path, - prim_type="TetMesh", - scale=scale, - attributes={ - "points": tet_vertices, - "tetVertexIndices": tet_elements.flatten(), - }, - stage=stage, - ) - + render_mesh_prim_path = prim_path + "/RenderMesh" + sim_mesh_prim_path = prim_path + "/SimulationMesh" + col_mesh_prim_path = prim_path + "/CollisionMesh" # create the mesh prim render_mesh_prim = create_prim( @@ -392,21 +362,26 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) + from omni.physx.scripts import deformableUtils + from omni.physx import get_physx_cooking_interface - # bind pose of render mesh to sim mesh for deformable objects - purposesAttrName = "deformablePose:default:omniphysics:purposes" - pointsAttrName = "deformablePose:default:omniphysics:points" - mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") - if mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): - mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) - mesh_prim.GetAttribute(pointsAttrName).Set(mesh_prim.GetAttribute("points").Get()) + success = deformableUtils.create_auto_volume_deformable_hierarchy( + stage=stage, + root_prim_path=prim_path, + simulation_tetmesh_path=sim_mesh_prim_path, + collision_tetmesh_path=sim_mesh_prim_path, + cooking_src_mesh_path=render_mesh_prim_path, + simulation_hex_mesh_enabled=False, + cooking_src_simplification_enabled=False, + set_visibility_with_guide_purpose=True, + ) + if not success: + raise RuntimeError("Failed to create deformable hierarchy from the given mesh.") - render_mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") - if render_mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): - render_mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) - render_mesh_prim.GetAttribute(pointsAttrName).Set(render_mesh_prim.GetAttribute("points").Get()) + # generate tet mesh for deformable object + get_physx_cooking_interface().cook_auto_deformable_body(prim_path) - mesh_prim_path = sim_mesh_prim_path + mesh_prim_path = prim_path # note: in case of deformable objects, we need to apply the deformable properties to the mesh prim. # this is different from rigid objects where we apply the properties to the parent prim. From e3b53b805ad9d8478c2bbe0bb1d95b00d7a9d837 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 9 Mar 2026 14:22:21 +0100 Subject: [PATCH 06/73] Test: Ability to store deformable scene as USD and run from USD --- scripts/demos/deformables.py | 116 ++++++++++++++++-- .../01_assets/run_deformable_object.py | 52 ++++++-- .../isaaclab/sim/spawners/meshes/meshes.py | 3 +- 3 files changed, 150 insertions(+), 21 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index fc759a3142d1..53a14edc44ac 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -21,27 +21,60 @@ # create argparser parser = argparse.ArgumentParser(description="This script demonstrates how to spawn deformable prims into the scene.") +parser.add_argument( + "--total_time", + type=float, + default=4.0, + help="Total simulation time in seconds.", +) +parser.add_argument( + "--dt", + type=float, + default=1.0/60, + help="Simulation timestep.", +) +parser.add_argument( + "--video_fps", + type=int, + default=60, + help="FPS for the output video if --save is enabled.", +) +parser.add_argument( + "--save", + action="store_true", + default=False, + help="Save the data from camera.", +) # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # demos should open Kit visualizer by default parser.set_defaults(visualizer=["kit"]) # parse the arguments args_cli = parser.parse_args() +if args_cli.save: + args_cli.enable_cameras = True # launch omniverse app app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" +import os import random +import subprocess import numpy as np import torch import tqdm import warp as wp +import omni.replicator.core as rep + import isaaclab.sim as sim_utils -from isaaclab.assets import DeformableObject, DeformableObjectCfg +from isaaclab.utils import convert_dict_to_backend +from isaaclab.sensors.camera import Camera, CameraCfg +# from isaaclab.assets import DeformableObject, DeformableObjectCfg +from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg def define_origins(num_origins: int, spacing: float) -> list[list[float]]: @@ -59,6 +92,28 @@ def define_origins(num_origins: int, spacing: float) -> list[list[float]]: return env_origins.tolist() +def define_sensor() -> Camera: + """Defines the camera sensor to add to the scene.""" + # Setup camera sensor + # In contrast to the ray-cast camera, we spawn the prim at these locations. + # This means the camera sensor will be attached to these prims. + sim_utils.create_prim("/World/OriginCamera", "Xform", translation=[0.0, 0.0, 0.0]) + camera_cfg = CameraCfg( + prim_path="/World/OriginCamera/CameraSensor", + update_period=1.0/args_cli.video_fps, + height=480, + width=640, + data_types=["rgb",], + spawn=sim_utils.PinholeCameraCfg( + focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) + ), + ) + # Create camera + camera = Camera(cfg=camera_cfg) + + return camera + + def design_scene() -> tuple[dict, list[list[float]]]: """Designs the scene.""" # Ground-plane @@ -116,7 +171,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: } # Create separate groups of deformable objects - origins = define_origins(num_origins=64, spacing=0.6) + origins = define_origins(num_origins=6, spacing=0.6) print("[INFO]: Spawning objects...") # Iterate over all the origins and randomly spawn objects for idx, origin in tqdm.tqdm(enumerate(origins), total=len(origins)): @@ -142,21 +197,45 @@ def design_scene() -> tuple[dict, list[list[float]]]: ) deformable_object = DeformableObject(cfg=cfg) - # return the scene information scene_entities = {"deformable_object": deformable_object} + if args_cli.save: + camera = define_sensor() + scene_entities["camera"] = camera + + # return the scene information return scene_entities, origins -def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, DeformableObject], origins: torch.Tensor): +def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, DeformableObject], origins: torch.Tensor, output_dir: str = "outputs"): """Runs the simulation loop.""" + + objects: DeformableObject = entities["deformable_object"] + # Write camera outputs + if args_cli.save: + camera: Camera = entities["camera"] + + # Create replicator writer + rep_writer = rep.BasicWriter( + output_dir=output_dir, + frame_padding=0, + rgb=True, + ) + # Camera positions, targets, orientations + camera_positions = torch.tensor([[2.5, 2.5, 2.5]], device=sim.device) + camera_targets = torch.tensor([[0.0, 0.0, 0.25]], device=sim.device) + camera.set_world_poses_from_view(camera_positions, camera_targets) + # Define simulation stepping sim_dt = sim.get_physics_dt() + assert sim_dt <= 1.0 / args_cli.video_fps, "Simulation timestep must be smaller than the inverse of the video FPS to save frames properly." + num_steps = int(args_cli.total_time / sim_dt) sim_time = 0.0 count = 0 + # Simulate physics - while simulation_app.is_running(): + for t in range(num_steps): # reset - if count % 400 == 0: + if sim_time > 4.0: # reset counters sim_time = 0.0 count = 0 @@ -177,6 +256,15 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab for deform_body in entities.values(): deform_body.update(sim_dt) + # Extract camera data + if args_cli.save: + if camera.data.output["rgb"] is not None: + cam_data = convert_dict_to_backend(camera.data.output, backend="numpy") + rep_writer.write({ + "annotators": {"rgb": {"render_product": {"data": cam_data["rgb"][0]}}}, + "trigger_outputs": {"on_time": camera.frame[0]} + }) + def main(): """Main function.""" @@ -194,8 +282,20 @@ def main(): # Now we are ready! print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene_entities, scene_origins) + camera_output = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera") + run_simulator(sim, scene_entities, scene_origins, camera_output) + # Store video if saving frames + if args_cli.save: + video_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.mp4") + fps = args_cli.video_fps + subprocess.run([ + "ffmpeg", "-y", "-loglevel", "error", + "-framerate", str(fps), + "-i", os.path.join(camera_output, "rgb_%d_0.png"), + "-c:v", "libx264", "-pix_fmt", "yuv420p", + video_path, + ], check=True) + print(f"[INFO]: Video saved to {video_path}") if __name__ == "__main__": diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 92bb14b976a7..0cc2298fa084 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -121,17 +121,47 @@ def design_scene(): sim_utils.create_prim(f"/World/Origin{i}", "Xform", translation=origin) # Deformable Object - cfg = DeformableObjectCfg( - prim_path="/World/Origin.*/Cube", - spawn=sim_utils.MeshCuboidCfg( - size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), - physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), - ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), - debug_vis=True, - ) + cube_asset_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cube_deformable.usda") + if os.path.exists(cube_asset_path): + print(f"[INFO]: Found existing Cube Asset USD at {cube_asset_path}. Loading from USD to skip cooking...") + cube_asset_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cube_deformable.usda") + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Cube", + spawn=sim_utils.UsdFileCfg( + usd_path=cube_asset_path, + deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), + debug_vis=True, + ) + else: + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Cube", + spawn=sim_utils.MeshCuboidCfg( + size=(0.2, 0.2, 0.2), + deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), + physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), + debug_vis=True, + ) + + # Export just the Cube subtree (one instance) as a standalone USD for fast reloading. + # Uses Sdf.CopySpec on the flattened stage to capture all layer opinions (including cooked tet data). + import omni.usd + from pxr import Usd, Sdf + src_stage = omni.usd.get_context().get_stage() + flat_layer = src_stage.Flatten() + out_usd = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cube_deformable.usda") + new_stage = Usd.Stage.CreateNew(out_usd) or Usd.Stage.Open(out_usd) + new_stage.GetRootLayer().Clear() + Sdf.CopySpec(flat_layer, Sdf.Path("/World/Origin0/Cube"), new_stage.GetRootLayer(), Sdf.Path("/Cube")) + new_stage.SetDefaultPrim(new_stage.GetPrimAtPath("/Cube")) + new_stage.GetRootLayer().Save() + print(f"[INFO]: Exported Cube subtree to {out_usd}") + + cube_object = DeformableObject(cfg=cfg) scene_entities["cube_object"] = cube_object diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 78e101136d3d..df0bd1d442fc 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -343,10 +343,9 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) else: - # create all the paths we need for clarity + # create all the paths we need for clarity, we use the same mesh for simulation and collision render_mesh_prim_path = prim_path + "/RenderMesh" sim_mesh_prim_path = prim_path + "/SimulationMesh" - col_mesh_prim_path = prim_path + "/CollisionMesh" # create the mesh prim render_mesh_prim = create_prim( From a4ae19264b36f72f3a7f1fdeb6027e426a5f345f Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 9 Mar 2026 14:59:07 +0100 Subject: [PATCH 07/73] Test: WIP trying to add cloths --- .../isaaclab/sim/spawners/meshes/meshes.py | 42 +++++++++++++++++++ .../sim/spawners/meshes/meshes_cfg.py | 18 ++++++++ 2 files changed, 60 insertions(+) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index df0bd1d442fc..ad5104389de3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -256,6 +256,48 @@ def spawn_mesh_cone( return stage.GetPrimAtPath(prim_path) +@clone +def spawn_mesh_rectangle( + prim_path: str, + cfg: meshes_cfg.MeshRectangleCfg, + translation: tuple[float, float, float] | None = None, + orientation: tuple[float, float, float, float] | None = None, + **kwargs, +) -> Usd.Prim: + """Create a USD-Mesh 2D rectangle prim with the given attributes. + + .. note:: + This function is decorated with :func:`clone` that resolves prim path into list of paths + if the input prim path is a regex pattern. This is done to support spawning multiple assets + from a single and cloning the USD prim at the given path expression. + + Args: + prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, + then the asset is spawned at all the matching prim paths. + cfg: The configuration instance. + translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None, in which case + this is set to the origin. + orientation: The orientation in (x, y, z, w) to apply to the prim w.r.t. its parent prim. Defaults to None, + in which case this is set to identity. + **kwargs: Additional keyword arguments, like ``clone_in_fabric``. + + Returns: + The created prim. + + Raises: + ValueError: If a prim already exists at the given path. + """ + # create a trimesh box + box = trimesh.creation.box(cfg.size) + + # obtain stage handle + stage = get_current_stage() + # spawn the rectangle as a mesh + _spawn_mesh_geom_from_mesh(prim_path, cfg, box, translation, orientation, None, stage=stage) + # return the prim + return stage.GetPrimAtPath(prim_path) + + """ Helper functions. """ diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index 9cf3d45716f5..c88b34761308 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -140,3 +140,21 @@ class MeshConeCfg(MeshCfg): """Height of the v (in m).""" axis: Literal["X", "Y", "Z"] = "Z" """Axis of the cone. Defaults to "Z".""" + + +@configclass +class MeshRectangleCfg(MeshCfg): + """Configuration parameters for a 2D rectangle mesh prim with deformable properties. + + See :meth:`spawn_mesh_rectangle` for more information. + """ + + func: Callable | str = "{DIR}.meshes:spawn_mesh_rectangle" + + size: tuple[float, float] = MISSING + """Size of the rectangle (in m).""" + nx: int = 1 + """Number of vertices along the x-axis.""" + ny: int = 1 + """Number of vertices along the y-axis.""" + From 53dce9d66a067e97cebecb5fc84a1909a950394d Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 10 Mar 2026 15:17:48 +0100 Subject: [PATCH 08/73] Fix: Experimental UJITSO geometry for meshes fails for deformable visualization. --- apps/isaaclab.python.kit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/isaaclab.python.kit b/apps/isaaclab.python.kit index cc54d43a4da3..5bfbf378236a 100644 --- a/apps/isaaclab.python.kit +++ b/apps/isaaclab.python.kit @@ -237,7 +237,7 @@ renderer.startupMessageDisplayed = true # hides the IOMMU popup window resourcemonitor.timeBetweenQueries = 100 # improves performance simulation.defaultMetersPerUnit = 1.0 # Meters default omni.replicator.captureOnPlay = true -UJITSO.geometry = true +UJITSO.geometry = false UJITSO.enabled = true [settings] From 2c5c8e4ad8360d2b59687ca7f8890b80e1dc71d5 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 10 Mar 2026 15:36:56 +0100 Subject: [PATCH 09/73] Fix: Reset at beginning, remove USD scene writing --- .../01_assets/run_deformable_object.py | 65 +++++-------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 0cc2298fa084..975bd334da61 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -89,8 +89,8 @@ def define_sensor() -> Camera: camera_cfg = CameraCfg( prim_path="/World/OriginCamera/CameraSensor", update_period=1.0/args_cli.video_fps, - height=480, - width=640, + height=800, + width=800, data_types=["rgb",], spawn=sim_utils.PinholeCameraCfg( focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) @@ -121,46 +121,17 @@ def design_scene(): sim_utils.create_prim(f"/World/Origin{i}", "Xform", translation=origin) # Deformable Object - cube_asset_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cube_deformable.usda") - if os.path.exists(cube_asset_path): - print(f"[INFO]: Found existing Cube Asset USD at {cube_asset_path}. Loading from USD to skip cooking...") - cube_asset_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cube_deformable.usda") - cfg = DeformableObjectCfg( - prim_path="/World/Origin.*/Cube", - spawn=sim_utils.UsdFileCfg( - usd_path=cube_asset_path, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), - ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), - debug_vis=True, - ) - else: - cfg = DeformableObjectCfg( - prim_path="/World/Origin.*/Cube", - spawn=sim_utils.MeshCuboidCfg( - size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), - physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), - ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), - debug_vis=True, - ) - - # Export just the Cube subtree (one instance) as a standalone USD for fast reloading. - # Uses Sdf.CopySpec on the flattened stage to capture all layer opinions (including cooked tet data). - import omni.usd - from pxr import Usd, Sdf - src_stage = omni.usd.get_context().get_stage() - flat_layer = src_stage.Flatten() - out_usd = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cube_deformable.usda") - new_stage = Usd.Stage.CreateNew(out_usd) or Usd.Stage.Open(out_usd) - new_stage.GetRootLayer().Clear() - Sdf.CopySpec(flat_layer, Sdf.Path("/World/Origin0/Cube"), new_stage.GetRootLayer(), Sdf.Path("/Cube")) - new_stage.SetDefaultPrim(new_stage.GetPrimAtPath("/Cube")) - new_stage.GetRootLayer().Save() - print(f"[INFO]: Exported Cube subtree to {out_usd}") - + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Cube", + spawn=sim_utils.MeshCuboidCfg( + size=(0.2, 0.2, 0.2), + deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), + physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), + debug_vis=True, + ) cube_object = DeformableObject(cfg=cfg) scene_entities["cube_object"] = cube_object @@ -192,8 +163,8 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor rgb=True, ) # Camera positions, targets, orientations - camera_positions = torch.tensor([[2.5, 2.5, 2.5]], device=sim.device) - camera_targets = torch.tensor([[0.0, 0.0, 0.25]], device=sim.device) + camera_positions = torch.tensor([[2., 2., 2.]], device=sim.device) + camera_targets = torch.tensor([[0.0, 0.0, 0.75]], device=sim.device) camera.set_world_poses_from_view(camera_positions, camera_targets) @@ -210,8 +181,8 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # Simulate physics com_traj = [] for t in range(num_steps): - # reset - if sim_time > 4.0: + # reset at start and after N seconds + if sim_time == 0.0 or sim_time > 3.0: # reset counters sim_time = 0.0 count = 0 @@ -277,7 +248,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor ax.set_ylabel("Z Position (m)") ax.legend() ax.grid() - fig.savefig(os.path.join(output_dir, f"com_trajectory.png"), dpi=300, bbox_inches="tight") + fig.savefig(os.path.join(os.path.dirname(output_dir), f"com_trajectory.png"), dpi=300, bbox_inches="tight") plt.close(fig) # Extract camera data From 4e3d5163d788ed015146f331f7b94937f3b6633f Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 10 Mar 2026 16:38:01 +0100 Subject: [PATCH 10/73] Test: Storing gifs for easy visualization --- scripts/demos/deformables.py | 18 +++++++++++++----- .../01_assets/run_deformable_object.py | 14 +++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 53a14edc44ac..108b2ac6b651 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -101,8 +101,8 @@ def define_sensor() -> Camera: camera_cfg = CameraCfg( prim_path="/World/OriginCamera/CameraSensor", update_period=1.0/args_cli.video_fps, - height=480, - width=640, + height=600, + width=800, data_types=["rgb",], spawn=sim_utils.PinholeCameraCfg( focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) @@ -129,7 +129,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # spawn a red cone cfg_sphere = sim_utils.MeshSphereCfg( - radius=0.25, + radius=0.5, deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=sim_utils.DeformableBodyMaterialCfg(), @@ -148,7 +148,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: physics_material=sim_utils.DeformableBodyMaterialCfg(), ) cfg_capsule = sim_utils.MeshCapsuleCfg( - radius=0.15, + radius=0.35, height=0.5, deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), @@ -181,7 +181,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # randomize the young modulus (somewhere between a Silicone 30 and Silicone 70) obj_cfg.physics_material.youngs_modulus = random.uniform(0.7e6, 3.3e6) # randomize the poisson's ratio - obj_cfg.physics_material.poissons_ratio = random.uniform(0.25, 0.5) + obj_cfg.physics_material.poissons_ratio = random.uniform(0.25, 0.45) # randomize the color obj_cfg.visual_material.diffuse_color = (random.random(), random.random(), random.random()) # spawn the object @@ -295,6 +295,14 @@ def main(): "-c:v", "libx264", "-pix_fmt", "yuv420p", video_path, ], check=True) + # Also generate gif for quick preview + gif_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.gif") + subprocess.run([ + "ffmpeg", "-y", "-loglevel", "error", + "-i", video_path, + "-vf", "fps=15,scale=320:-1:flags=lanczos", + gif_path, + ], check=True) print(f"[INFO]: Video saved to {video_path}") diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 975bd334da61..904cdb25f30a 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -82,9 +82,6 @@ def define_sensor() -> Camera: """Defines the camera sensor to add to the scene.""" - # Setup camera sensor - # In contrast to the ray-cast camera, we spawn the prim at these locations. - # This means the camera sensor will be attached to these prims. sim_utils.create_prim("/World/OriginCamera", "Xform", translation=[0.0, 0.0, 0.0]) camera_cfg = CameraCfg( prim_path="/World/OriginCamera/CameraSensor", @@ -96,7 +93,6 @@ def define_sensor() -> Camera: focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) ), ) - # Create camera camera = Camera(cfg=camera_cfg) return camera @@ -114,7 +110,7 @@ def design_scene(): # Create a dictionary for the scene entities scene_entities = {} - # Create separate groups called "Origin1", "Origin2", "Origin3" + # Create separate groups called "Origin0", "Origin1", ... # Each group will have a robot in it origins = [[0.25, 0.25, 0.0], [-0.25, 0.25, 0.0], [0.25, -0.25, 0.0], [-0.25, -0.25, 0.0]] for i, origin in enumerate(origins): @@ -289,6 +285,14 @@ def main(): "-c:v", "libx264", "-pix_fmt", "yuv420p", video_path, ], check=True) + # Also generate gif for quick preview + gif_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.gif") + subprocess.run([ + "ffmpeg", "-y", "-loglevel", "error", + "-i", video_path, + "-vf", "fps=15,scale=320:-1:flags=lanczos", + gif_path, + ], check=True) print(f"[INFO]: Video saved to {video_path}") if __name__ == "__main__": From 7ab4fdc265d12bfbe8dbc0097e8aee5ec1a5d73e Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 10 Mar 2026 19:03:29 +0100 Subject: [PATCH 11/73] Feat: Surface deformable material --- source/isaaclab/isaaclab/sim/__init__.pyi | 5 ++++ .../sim/spawners/materials/__init__.pyi | 2 ++ .../materials/physics_materials_cfg.py | 24 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index 32a5ea87e197..705a58b016c7 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -74,6 +74,7 @@ __all__ = [ "spawn_deformable_body_material", "spawn_rigid_body_material", "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -87,6 +88,7 @@ __all__ = [ "spawn_mesh_cuboid", "spawn_mesh_cylinder", "spawn_mesh_sphere", + "spawn_mesh_rectangle", "MeshCapsuleCfg", "MeshCfg", "MeshConeCfg", @@ -240,6 +242,7 @@ from .spawners import ( spawn_deformable_body_material, spawn_rigid_body_material, DeformableBodyMaterialCfg, + SurfaceDeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, spawn_from_mdl_file, @@ -252,12 +255,14 @@ from .spawners import ( spawn_mesh_cone, spawn_mesh_cuboid, spawn_mesh_cylinder, + spawn_mesh_rectangle, spawn_mesh_sphere, MeshCapsuleCfg, MeshCfg, MeshConeCfg, MeshCuboidCfg, MeshCylinderCfg, + MeshRectangleCfg, MeshSphereCfg, spawn_camera, FisheyeCameraCfg, diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi index 294a9a7d9bf7..ebe0f25c3b58 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi @@ -7,6 +7,7 @@ __all__ = [ "spawn_deformable_body_material", "spawn_rigid_body_material", "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -20,6 +21,7 @@ __all__ = [ from .physics_materials import spawn_deformable_body_material, spawn_rigid_body_material from .physics_materials_cfg import ( DeformableBodyMaterialCfg, + SurfaceDeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, ) diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index ad39f7be1e45..0e60a7fe319c 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -121,3 +121,27 @@ class DeformableBodyMaterialCfg(PhysicsMaterialCfg): A scale of 1 corresponds to default damping. A value of 0 will only apply damping to certain motions leading to special effects that look similar to water filled soft bodies. """ + + +@configclass +class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): + """Physics material parameters for surface deformable bodies, extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. + + See :meth:`spawn_deformable_body_material` for more information. + + """ + + func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" + + surface_thickness: float = 0.0 + """The thickness of the deformable body's surface. Defaults to 0.0.""" + + surface_stretch_stiffness: float = 0.0 + """The stretch stiffness of the deformable body's surface. Defaults to 0.0.""" + + surface_shear_stiffness: float = 0.0 + """The shear stiffness of the deformable body's surface. Defaults to 0.0.""" + + surface_bend_stiffness: float = 0.0 + """The bend stiffness of the deformable body's surface. Defaults to 0.0.""" + From f6a8b49252315b58fcf2315910b2ed291f7c8093 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 11 Mar 2026 15:15:48 +0100 Subject: [PATCH 12/73] Feat: Create square trimesh for 2d surface deformables --- source/isaaclab/isaaclab/sim/__init__.pyi | 7 +++--- .../isaaclab/sim/spawners/__init__.pyi | 6 +++++ .../spawners/materials/physics_materials.py | 23 ++++++++++++++--- .../materials/physics_materials_cfg.py | 3 +++ .../isaaclab/sim/spawners/meshes/__init__.pyi | 4 +++ .../isaaclab/sim/spawners/meshes/meshes.py | 25 +++++++++++-------- .../sim/spawners/meshes/meshes_cfg.py | 19 ++++++-------- source/isaaclab/isaaclab/sim/utils/prims.py | 2 +- 8 files changed, 59 insertions(+), 30 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index 705a58b016c7..11a3b2eac081 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -88,13 +88,14 @@ __all__ = [ "spawn_mesh_cuboid", "spawn_mesh_cylinder", "spawn_mesh_sphere", - "spawn_mesh_rectangle", + "spawn_mesh_square", "MeshCapsuleCfg", "MeshCfg", "MeshConeCfg", "MeshCuboidCfg", "MeshCylinderCfg", "MeshSphereCfg", + "MeshSquareCfg", "spawn_camera", "FisheyeCameraCfg", "PinholeCameraCfg", @@ -255,15 +256,15 @@ from .spawners import ( spawn_mesh_cone, spawn_mesh_cuboid, spawn_mesh_cylinder, - spawn_mesh_rectangle, spawn_mesh_sphere, + spawn_mesh_square, MeshCapsuleCfg, MeshCfg, MeshConeCfg, MeshCuboidCfg, MeshCylinderCfg, - MeshRectangleCfg, MeshSphereCfg, + MeshSquareCfg, spawn_camera, FisheyeCameraCfg, PinholeCameraCfg, diff --git a/source/isaaclab/isaaclab/sim/spawners/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi index 78d2c19021ee..30539298e0a6 100644 --- a/source/isaaclab/isaaclab/sim/spawners/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi @@ -27,6 +27,7 @@ __all__ = [ "spawn_deformable_body_material", "spawn_rigid_body_material", "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -40,12 +41,14 @@ __all__ = [ "spawn_mesh_cuboid", "spawn_mesh_cylinder", "spawn_mesh_sphere", + "spawn_mesh_square", "MeshCapsuleCfg", "MeshCfg", "MeshConeCfg", "MeshCuboidCfg", "MeshCylinderCfg", "MeshSphereCfg", + "MeshSquareCfg", "spawn_camera", "FisheyeCameraCfg", "PinholeCameraCfg", @@ -92,6 +95,7 @@ from .materials import ( spawn_deformable_body_material, spawn_rigid_body_material, DeformableBodyMaterialCfg, + SurfaceDeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, spawn_from_mdl_file, @@ -107,11 +111,13 @@ from .meshes import ( spawn_mesh_cuboid, spawn_mesh_cylinder, spawn_mesh_sphere, + spawn_mesh_square, MeshCapsuleCfg, MeshCfg, MeshConeCfg, MeshCuboidCfg, MeshCylinderCfg, + MeshSquareCfg, MeshSphereCfg, ) from .sensors import spawn_camera, FisheyeCameraCfg, PinholeCameraCfg diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index 1c9c35dfa84f..075c34653111 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -13,8 +13,8 @@ from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.string import to_camel_case -if TYPE_CHECKING: - from . import physics_materials_cfg +# if TYPE_CHECKING: +from . import physics_materials_cfg @clone @@ -116,20 +116,35 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") # ensure PhysX deformable body material API is applied applied = prim.GetAppliedSchemas() - if "OmniPhysicsDeformableMaterialAPI" not in applied: # TODO: check surface vs volume deformable + if "OmniPhysicsDeformableMaterialAPI" not in applied: prim.AddAppliedSchema("OmniPhysicsDeformableMaterialAPI") if "PhysxDeformableMaterialAPI" not in applied: prim.AddAppliedSchema("PhysxDeformableMaterialAPI") + + # surface deformable material API + is_surface_deformable = isinstance(cfg, physics_materials_cfg.SurfaceDeformableBodyMaterialCfg) + if is_surface_deformable: + if "OmniPhysicsSurfaceDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("OmniPhysicsSurfaceDeformableMaterialAPI") + if "PhysxSurfaceDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("PhysxSurfaceDeformableMaterialAPI") # convert to dict cfg = cfg.to_dict() del cfg["func"] - # set base attributes into OmniPhysics API + # set base attributes into OmniPhysics API (prim attributes: omniphysics:*) for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: value = cfg.pop(attr_name, None) safe_set_attribute_on_usd_prim( prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) + # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) + if is_surface_deformable: + for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) for attr_name, value in cfg.items(): safe_set_attribute_on_usd_prim( diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index 0e60a7fe319c..ee3033750fe3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -145,3 +145,6 @@ class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): surface_bend_stiffness: float = 0.0 """The bend stiffness of the deformable body's surface. Defaults to 0.0.""" + bend_damping: float = 0.0 + """The bend damping for the deformable body's surface. Defaults to 0.0.""" + diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.pyi index 9bfeb4cd4327..06490befb33f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/__init__.pyi @@ -9,12 +9,14 @@ __all__ = [ "spawn_mesh_cuboid", "spawn_mesh_cylinder", "spawn_mesh_sphere", + "spawn_mesh_square", "MeshCapsuleCfg", "MeshCfg", "MeshConeCfg", "MeshCuboidCfg", "MeshCylinderCfg", "MeshSphereCfg", + "MeshSquareCfg", ] from .meshes import ( @@ -23,6 +25,7 @@ from .meshes import ( spawn_mesh_cuboid, spawn_mesh_cylinder, spawn_mesh_sphere, + spawn_mesh_square, ) from .meshes_cfg import ( MeshCapsuleCfg, @@ -31,4 +34,5 @@ from .meshes_cfg import ( MeshCuboidCfg, MeshCylinderCfg, MeshSphereCfg, + MeshSquareCfg, ) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index ad5104389de3..08cc1717cfd4 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -16,7 +16,7 @@ from isaaclab.sim import schemas from isaaclab.sim.utils import bind_physics_material, bind_visual_material, clone, create_prim, get_current_stage -from ..materials import DeformableBodyMaterialCfg, RigidBodyMaterialCfg +from ..materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg, RigidBodyMaterialCfg if TYPE_CHECKING: from . import meshes_cfg @@ -257,14 +257,14 @@ def spawn_mesh_cone( @clone -def spawn_mesh_rectangle( +def spawn_mesh_square( prim_path: str, - cfg: meshes_cfg.MeshRectangleCfg, + cfg: meshes_cfg.MeshSquareCfg, translation: tuple[float, float, float] | None = None, orientation: tuple[float, float, float, float] | None = None, **kwargs, ) -> Usd.Prim: - """Create a USD-Mesh 2D rectangle prim with the given attributes. + """Create a USD-Mesh 2D square prim with the given attributes. .. note:: This function is decorated with :func:`clone` that resolves prim path into list of paths @@ -287,13 +287,15 @@ def spawn_mesh_rectangle( Raises: ValueError: If a prim already exists at the given path. """ - # create a trimesh box - box = trimesh.creation.box(cfg.size) + # create a 2D triangle mesh grid + from omni.physx.scripts import deformableUtils + vertices, faces = deformableUtils.create_triangle_mesh_square(cfg.resolution[0], cfg.resolution[1], scale=cfg.size) + grid = trimesh.Trimesh(vertices=vertices, faces=np.array(faces).reshape(-1,3), process=False) # obtain stage handle stage = get_current_stage() - # spawn the rectangle as a mesh - _spawn_mesh_geom_from_mesh(prim_path, cfg, box, translation, orientation, None, stage=stage) + # spawn the square as a mesh + _spawn_mesh_geom_from_mesh(prim_path, cfg, grid, translation, orientation, None, stage=stage) # return the prim return stage.GetPrimAtPath(prim_path) @@ -371,7 +373,8 @@ def _spawn_mesh_geom_from_mesh( mesh_prim_path = geom_prim_path + "/mesh" # create the mesh prim - if cfg.deformable_props is None: + if cfg.deformable_props is None or isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): + # non-deformables and surface deformables mesh_prim = create_prim( mesh_prim_path, prim_type="Mesh", @@ -385,10 +388,11 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) else: + # volume deformable # create all the paths we need for clarity, we use the same mesh for simulation and collision render_mesh_prim_path = prim_path + "/RenderMesh" sim_mesh_prim_path = prim_path + "/SimulationMesh" - + # create the mesh prim render_mesh_prim = create_prim( render_mesh_prim_path, @@ -429,7 +433,6 @@ def _spawn_mesh_geom_from_mesh( if cfg.deformable_props is not None: # apply mass properties if cfg.mass_props is not None: - # TODO: will the deformable not set the mass automatically from the density? schemas.define_mass_properties(mesh_prim_path, cfg.mass_props, stage=stage) # apply deformable body properties schemas.define_deformable_body_properties(mesh_prim_path, cfg.deformable_props, stage=stage) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index c88b34761308..03dfc742f8fe 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -143,18 +143,15 @@ class MeshConeCfg(MeshCfg): @configclass -class MeshRectangleCfg(MeshCfg): - """Configuration parameters for a 2D rectangle mesh prim with deformable properties. +class MeshSquareCfg(MeshCfg): + """Configuration parameters for a 2D square mesh prim. - See :meth:`spawn_mesh_rectangle` for more information. + See :meth:`spawn_mesh_square` for more information. """ - func: Callable | str = "{DIR}.meshes:spawn_mesh_rectangle" - - size: tuple[float, float] = MISSING - """Size of the rectangle (in m).""" - nx: int = 1 - """Number of vertices along the x-axis.""" - ny: int = 1 - """Number of vertices along the y-axis.""" + func: Callable | str = "{DIR}.meshes:spawn_mesh_square" + size: float = MISSING + """Edge length of the square (in m).""" + resolution: tuple[int, int] = (2, 2) + """Resolution of the square (in vertices).""" diff --git a/source/isaaclab/isaaclab/sim/utils/prims.py b/source/isaaclab/isaaclab/sim/utils/prims.py index 39832c29612c..d54ca890d2b7 100644 --- a/source/isaaclab/isaaclab/sim/utils/prims.py +++ b/source/isaaclab/isaaclab/sim/utils/prims.py @@ -834,7 +834,7 @@ def bind_physics_material( applied = prim.GetAppliedSchemas() has_physics_scene_api = "PhysxSceneAPI" in applied has_collider = prim.HasAPI(UsdPhysics.CollisionAPI) - has_deformable_body = "OmniPhysicsDeformableBodyAPI" in applied # TODO: might need to check between surface and volume deformable. + has_deformable_body = "OmniPhysicsDeformableBodyAPI" in applied has_particle_system = prim.GetTypeName() == "PhysxParticleSystem" if not (has_physics_scene_api or has_collider or has_deformable_body or has_particle_system): logger.debug( From f15b150405d9741b17dcaa4c1ab647456450ad13 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 11 Mar 2026 16:36:02 +0100 Subject: [PATCH 13/73] Test: Adding cloth to simulation deformable --- .../01_assets/run_deformable_object.py | 21 ++++- .../isaaclab/isaaclab/sim/schemas/schemas.py | 83 ++++++++++++------- .../deformable_object/deformable_object.py | 47 +++++++---- 3 files changed, 104 insertions(+), 47 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 904cdb25f30a..195cc72fe69c 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -116,7 +116,7 @@ def design_scene(): for i, origin in enumerate(origins): sim_utils.create_prim(f"/World/Origin{i}", "Xform", translation=origin) - # Deformable Object + # 3D Deformable Object cfg = DeformableObjectCfg( prim_path="/World/Origin.*/Cube", spawn=sim_utils.MeshCuboidCfg( @@ -132,6 +132,20 @@ def design_scene(): cube_object = DeformableObject(cfg=cfg) scene_entities["cube_object"] = cube_object + # 2D Cloth Object + cfg = DeformableObjectCfg( + prim_path="/World/Origin0/Cloth", + spawn=sim_utils.MeshSquareCfg( + size=0.5, + resolution=(3, 3), + deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), + physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + ), + ) + cloth_object = DeformableObject(cfg=cfg) + scene_entities["cloth_object"] = cloth_object + # Sensors if args_cli.save: camera = define_sensor() @@ -147,6 +161,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # note: we only do this here for readability. In general, it is better to access the entities directly from # the dictionary. This dictionary is replaced by the InteractiveScene class in the next tutorial. cube_object: DeformableObject = entities["cube_object"] + cloth_object: DeformableObject = entities["cloth_object"] # Write camera outputs if args_cli.save: @@ -223,9 +238,13 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor count += 1 # update buffers cube_object.update(sim_dt) + cloth_object.update(sim_dt) if args_cli.save: camera.update(sim_dt) + print(f"Cloth coords: {wp.to_torch(cloth_object.data.nodal_pos_w)}") + breakpoint() + com_traj.append(wp.to_torch(cube_object.data.nodal_pos_w).mean(1).cpu().numpy()) # print the root position if t % args_cli.video_fps == 0: diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 694333393654..68da4bdac362 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -10,7 +10,7 @@ import math from typing import Any -from pxr import Usd, UsdPhysics +from pxr import Usd, UsdGeom, UsdPhysics from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.string import to_camel_case @@ -881,8 +881,8 @@ def define_deformable_body_properties( if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") # check if prim has deformable body applied on it - if "OmniPhysicsDeformableBodyAPI" not in prim.GetAppliedSchemas(): - raise ValueError(f"Prim path '{prim_path}' does not have the deformable body schema applied.") + # if "OmniPhysicsDeformableBodyAPI" not in prim.GetAppliedSchemas(): + # raise ValueError(f"Prim path '{prim_path}' does not have the deformable body schema applied.") # traverse the prim and get the collision mesh # TODO: currently we only allow volume deformables (TetMesh), if surface deformable we want the prim type to be Mesh. @@ -904,8 +904,8 @@ def define_deformable_body_properties( # mesh_applied = mesh_prim.GetAppliedSchemas() # TODO: Make this general for engines besides PhysX - if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): - prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") + # if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): + # prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # set deformable body properties modify_deformable_body_properties(prim_path, cfg, stage) @@ -967,30 +967,10 @@ def modify_deformable_body_properties( return False # TODO: Make this general for engines besides PhysX deformable_applied_schemas = deformable_body_prim.GetAppliedSchemas() - if "PhysxBaseDeformableBodyAPI" not in deformable_applied_schemas: - return False - if "OmniPhysicsBodyAPI" not in deformable_applied_schemas: - return False - - # get collision prim - matching_prims = get_all_matching_child_prims(prim_path, lambda p: "PhysicsCollisionAPI" in p.GetAppliedSchemas()) - # check if the mesh is valid - if len(matching_prims) == 0: - raise ValueError(f"Could not find any collision mesh in '{prim_path}'. Please check asset.") - if len(matching_prims) > 1: - # get list of all meshes found - mesh_paths = [p.GetPrimPath() for p in matching_prims] - raise ValueError( - f"Found multiple collision meshes in '{prim_path}': {mesh_paths}." - " Deformable body schema can only be applied to one mesh." - ) - col_prim = matching_prims[0] - - # ensure PhysX collision API is applied on the collision mesh - # TODO: Specific to PhysX - if "PhysxCollisionAPI" not in col_prim.GetAppliedSchemas(): - col_prim.AddAppliedSchema("PhysxCollisionAPI") - + # if "PhysxBaseDeformableBodyAPI" not in deformable_applied_schemas: + # return False + # if "OmniPhysicsBodyAPI" not in deformable_applied_schemas: + # return False # convert to dict cfg = cfg.to_dict() @@ -1015,8 +995,9 @@ def modify_deformable_body_properties( # ] # } # TODO: These all belong somewhere, I need to figure out the concrete schema API locations. - from omni.physx.scripts import deformableUtils as deformable_utils + from omni.physx.scripts import deformableUtils + # from omni.physx.scripts import deformableUtils as deformable_utils # mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") # Deprecated # status = deformable_utils.add_physx_deformable_body(stage, prim_path=prim_path, **attr_kwargs) @@ -1024,7 +1005,49 @@ def modify_deformable_body_properties( # check if the deformable body was successfully added # if not status: # return False + + + if deformable_body_prim.IsA(UsdGeom.Mesh): + success = deformableUtils.set_physics_surface_deformable_body(stage, prim_path) + if not success: + return False + elif deformable_body_prim.IsA(UsdGeom.TetMesh): + # success = deformableUtils.set_physics_volume_deformable_body(stage, prim_path) + # if not success: + # return False + pass + else: + # raise ValueError( + # f"Deformable body prim '{prim_path}' is not a Mesh or TetMesh. Found type: {deformable_body_prim.GetTypeName()}" + # ) + pass + + if "OmniPhysicsDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + raise ValueError(f"Prim path '{prim_path}' does not have the deformable body schema applied.") + # if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): + # prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") + + + # get collision prim + matching_prims = get_all_matching_child_prims(prim_path, lambda p: "PhysicsCollisionAPI" in p.GetAppliedSchemas()) + # check if the mesh is valid + if len(matching_prims) == 0: + raise ValueError(f"Could not find any collision mesh in '{prim_path}'. Please check asset.") + if len(matching_prims) > 1: + # get list of all meshes found + mesh_paths = [p.GetPrimPath() for p in matching_prims] + raise ValueError( + f"Found multiple collision meshes in '{prim_path}': {mesh_paths}." + " Deformable body schema can only be applied to one mesh." + ) + col_prim = matching_prims[0] + + # ensure PhysX collision API is applied on the collision mesh + # TODO: Specific to PhysX + if "PhysxCollisionAPI" not in col_prim.GetAppliedSchemas(): + col_prim.AddAppliedSchema("PhysxCollisionAPI") + # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) for attr_name, value in cfg.items(): diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index e57272c47096..a01f32b79d9a 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -80,6 +80,8 @@ def __init__(self, cfg: DeformableObjectCfg): super().__init__(cfg) # Register custom vec6f type for nodal state validation. self._DTYPE_TO_TORCH_TRAILING_DIMS = {**self._DTYPE_TO_TORCH_TRAILING_DIMS, vec6f: (6,)} + # initialize deformable type to None, should be set to either surface or volume on initialization + self._deformable_type: str | None = None """ Properties @@ -377,6 +379,9 @@ def write_nodal_kinematic_target_to_sim_index( env_ids: Environment indices. If None, then all indices are used. full_data: Whether to expect full data. Defaults to False. """ + if self._deformable_type != "volume": + raise ValueError("Kinematic targets can only be set for volume deformable bodies.") + # resolve env_ids env_ids = self._resolve_env_ids(env_ids) if full_data: @@ -571,7 +576,6 @@ def _initialize_impl(self): if len(material_paths) > 0: for mat_path in material_paths: mat_prim = root_prim.GetStage().GetPrimAtPath(mat_path) - # TODO: surface deformable requires different check OmniPhysicsSurfaceDeformableMaterialAPI if "OmniPhysicsDeformableMaterialAPI" in mat_prim.GetAppliedSchemas(): material_prim = mat_prim break @@ -588,11 +592,20 @@ def _initialize_impl(self): root_prim_path = root_prim.GetPath().pathString root_prim_path_expr = self.cfg.prim_path + root_prim_path[len(template_prim_path) :] # -- object view - self._root_physx_view = self._physics_sim_view.create_volume_deformable_body_view(root_prim_path_expr.replace(".*", "*")) + self._deformable_type = "surface" if root_prim.HasAPI("OmniPhysicsSurfaceDeformableSimAPI") else "volume" + if self._deformable_type == "surface": + # surface deformable + self._root_physx_view = self._physics_sim_view.create_surface_deformable_body_view(root_prim_path_expr.replace(".*", "*")) + else: + # volume deformable + self._root_physx_view = self._physics_sim_view.create_volume_deformable_body_view(root_prim_path_expr.replace(".*", "*")) # Return if the asset is not found if self._root_physx_view._backend is None: raise RuntimeError(f"Failed to create deformable body at: {self.cfg.prim_path}. Please check PhysX logs.") + # Check validity of deformables in view + if not self._root_physx_view.check(): + raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression if material_prim is not None: @@ -661,20 +674,22 @@ def _create_buffers(self): device=self.device, ) - # kinematic targets — allocate our own buffer and copy from PhysX - kinematic_raw = self.root_view.get_simulation_nodal_kinematic_targets() # (N, V, 4) float32 - kinematic_view = kinematic_raw.view(wp.vec4f).reshape((self.num_instances, self.max_sim_vertices_per_body)) - self._data.nodal_kinematic_target = wp.zeros( - (self.num_instances, self.max_sim_vertices_per_body), dtype=wp.vec4f, device=self.device - ) - wp.copy(self._data.nodal_kinematic_target, kinematic_view) - # set all nodes as non-kinematic targets by default (flag = 1.0) - wp.launch( - set_kinematic_flags_to_one, - dim=(self.num_instances * self.max_sim_vertices_per_body,), - inputs=[self._data.nodal_kinematic_target.reshape((self.num_instances * self.max_sim_vertices_per_body,))], - device=self.device, - ) + # kinematic targets (only for volume deformables, surface deformables do not support kinematic targets) + if self._deformable_type == "volume": + # kinematic targets — allocate our own buffer and copy from PhysX + kinematic_raw = self.root_view.get_simulation_nodal_kinematic_targets() # (N, V, 4) float32 + kinematic_view = kinematic_raw.view(wp.vec4f).reshape((self.num_instances, self.max_sim_vertices_per_body)) + self._data.nodal_kinematic_target = wp.zeros( + (self.num_instances, self.max_sim_vertices_per_body), dtype=wp.vec4f, device=self.device + ) + wp.copy(self._data.nodal_kinematic_target, kinematic_view) + # set all nodes as non-kinematic targets by default (flag = 1.0) + wp.launch( + set_kinematic_flags_to_one, + dim=(self.num_instances * self.max_sim_vertices_per_body,), + inputs=[self._data.nodal_kinematic_target.reshape((self.num_instances * self.max_sim_vertices_per_body,))], + device=self.device, + ) """ Internal simulation callbacks. From 7248caee858d85c47da1662d2f62380f5f0141b5 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 11 Mar 2026 18:58:22 +0100 Subject: [PATCH 14/73] Test: Using auto create for surface deformables --- .../01_assets/run_deformable_object.py | 2 +- .../isaaclab/sim/spawners/meshes/meshes.py | 52 +++++++++++++------ .../deformable_object/deformable_object.py | 10 ++-- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 195cc72fe69c..3ab7488dfb12 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -138,7 +138,7 @@ def design_scene(): spawn=sim_utils.MeshSquareCfg( size=0.5, resolution=(3, 3), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 08cc1717cfd4..59c9bfb94851 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -373,8 +373,8 @@ def _spawn_mesh_geom_from_mesh( mesh_prim_path = geom_prim_path + "/mesh" # create the mesh prim - if cfg.deformable_props is None or isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): - # non-deformables and surface deformables + if cfg.deformable_props is None: + # non-deformables mesh_prim = create_prim( mesh_prim_path, prim_type="Mesh", @@ -388,12 +388,13 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) else: - # volume deformable + from omni.physx.scripts import deformableUtils + from omni.physx import get_physx_cooking_interface # create all the paths we need for clarity, we use the same mesh for simulation and collision render_mesh_prim_path = prim_path + "/RenderMesh" sim_mesh_prim_path = prim_path + "/SimulationMesh" - - # create the mesh prim + + # create surface mesh prim render_mesh_prim = create_prim( render_mesh_prim_path, prim_type="Mesh", @@ -407,19 +408,36 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) - from omni.physx.scripts import deformableUtils - from omni.physx import get_physx_cooking_interface + if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): + # surface deformable + success = deformableUtils.create_auto_surface_deformable_hierarchy( + stage=stage, + root_prim_path=prim_path, + simulation_mesh_path=sim_mesh_prim_path, + cooking_src_mesh_path=render_mesh_prim_path, + cooking_src_simplification_enabled=False, + set_visibility_with_guide_purpose=True, + ) + + root_prim = stage.GetPrimAtPath(prim_path) + if "PhysxSurfaceDeformableBodyAPI" not in root_prim.GetAppliedSchemas(): + root_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") + root_prim.GetAttribute("physxDeformableBody:disableGravity").Set(True) + root_prim.GetAttribute("physxDeformableBody:selfCollision").Set(True) + + else: + # volume deformable + success = deformableUtils.create_auto_volume_deformable_hierarchy( + stage=stage, + root_prim_path=prim_path, + simulation_tetmesh_path=sim_mesh_prim_path, + collision_tetmesh_path=sim_mesh_prim_path, + cooking_src_mesh_path=render_mesh_prim_path, + simulation_hex_mesh_enabled=False, + cooking_src_simplification_enabled=False, + set_visibility_with_guide_purpose=True, + ) - success = deformableUtils.create_auto_volume_deformable_hierarchy( - stage=stage, - root_prim_path=prim_path, - simulation_tetmesh_path=sim_mesh_prim_path, - collision_tetmesh_path=sim_mesh_prim_path, - cooking_src_mesh_path=render_mesh_prim_path, - simulation_hex_mesh_enabled=False, - cooking_src_simplification_enabled=False, - set_visibility_with_guide_purpose=True, - ) if not success: raise RuntimeError("Failed to create deformable hierarchy from the given mesh.") diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index a01f32b79d9a..6e6f096a03c3 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -586,13 +586,14 @@ def _initialize_impl(self): " If you want to modify the material properties, please ensure that the material is bound" " to the deformable body." ) + # deformable type based on material that is applied + self._deformable_type = "surface" if material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") else "volume" # resolve root path back into regex expression # -- root prim expression root_prim_path = root_prim.GetPath().pathString root_prim_path_expr = self.cfg.prim_path + root_prim_path[len(template_prim_path) :] # -- object view - self._deformable_type = "surface" if root_prim.HasAPI("OmniPhysicsSurfaceDeformableSimAPI") else "volume" if self._deformable_type == "surface": # surface deformable self._root_physx_view = self._physics_sim_view.create_surface_deformable_body_view(root_prim_path_expr.replace(".*", "*")) @@ -604,8 +605,9 @@ def _initialize_impl(self): if self._root_physx_view._backend is None: raise RuntimeError(f"Failed to create deformable body at: {self.cfg.prim_path}. Please check PhysX logs.") # Check validity of deformables in view - if not self._root_physx_view.check(): - raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") + # if not self._root_physx_view.check(): + # print(f"Simulation nodal positions: {self._root_physx_view.get_simulation_nodal_positions().numpy()}") + # raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression if material_prim is not None: @@ -633,7 +635,7 @@ def _initialize_impl(self): logger.info(f"Number of instances: {self._material_physx_view.count}") else: logger.info("No deformable material found. Material properties will be set to default values.") - + # container for data access self._data = DeformableObjectData(self.root_view, self.device) From 935cf130ed47f6dd928158a9cd409691101f67c4 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 11 Mar 2026 19:47:55 +0100 Subject: [PATCH 15/73] Feat: Working surface and volume deformables in same scene --- .../01_assets/run_deformable_object.py | 14 +++++--- .../spawners/materials/physics_materials.py | 34 +++++++++++++------ .../materials/physics_materials_cfg.py | 4 +-- .../isaaclab/sim/spawners/meshes/meshes.py | 2 +- .../deformable_object/deformable_object.py | 8 ++--- 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 3ab7488dfb12..cd40b2b41769 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -133,14 +133,15 @@ def design_scene(): scene_entities["cube_object"] = cube_object # 2D Cloth Object + sim_utils.create_prim(f"/World/OriginCloth", "Xform", translation=[0,0,0.75]) cfg = DeformableObjectCfg( - prim_path="/World/Origin0/Cloth", + prim_path="/World/OriginCloth/Cloth", spawn=sim_utils.MeshSquareCfg( size=0.5, resolution=(3, 3), deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), - physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5, surface_thickness=0.01), ), ) cloth_object = DeformableObject(cfg=cfg) @@ -216,6 +217,11 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # reset buffers cube_object.reset() + # reset the cloth object as well + nodal_state = wp.to_torch(cloth_object.data.default_nodal_state_w).clone() + cloth_object.write_nodal_state_to_sim(nodal_state) + cloth_object.reset() + print("----------------------------------------") print("[INFO]: Resetting object state...") @@ -242,9 +248,6 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor if args_cli.save: camera.update(sim_dt) - print(f"Cloth coords: {wp.to_torch(cloth_object.data.nodal_pos_w)}") - breakpoint() - com_traj.append(wp.to_torch(cube_object.data.nodal_pos_w).mean(1).cpu().numpy()) # print the root position if t % args_cli.video_fps == 0: @@ -253,6 +256,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor print(f"Cube 1 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[1].mean(0)}") print(f"Cube 2 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[2].mean(0)}") print(f"Cube 3 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[3].mean(0)}") + print(f"Cloth COM: {wp.to_torch(cloth_object.data.nodal_pos_w)[0].mean(0)}") trajectories = np.stack(com_traj, axis=1) time_axis = np.arange(trajectories.shape[1]) * sim_dt diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index 075c34653111..a3052e6c2be3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -132,19 +132,33 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De # convert to dict cfg = cfg.to_dict() del cfg["func"] + attr_kwargs = { + attr_name: cfg.pop(attr_name) + for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"] + } + from omni.physx.scripts import deformableUtils + # set base attributes into OmniPhysics API (prim attributes: omniphysics:*) - for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) + # for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: + # value = cfg.pop(attr_name, None) + # safe_set_attribute_on_usd_prim( + # prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + # ) # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) if is_surface_deformable: - for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) + attr_kwargs.update({ + attr_name: cfg.pop(attr_name) + for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"] + }) + deformableUtils.add_surface_deformable_material(stage, prim_path, **attr_kwargs) + # for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: + # value = cfg.pop(attr_name, None) + # safe_set_attribute_on_usd_prim( + # prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + # ) + else: + deformableUtils.add_deformable_material(stage, prim_path, **attr_kwargs) + # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) for attr_name, value in cfg.items(): safe_set_attribute_on_usd_prim( diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index ee3033750fe3..f7f381d1e717 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -133,8 +133,8 @@ class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" - surface_thickness: float = 0.0 - """The thickness of the deformable body's surface. Defaults to 0.0.""" + surface_thickness: float | None = None + """The thickness of the deformable body's surface. Defaults to None, in which case the simulation decides the default thickness.""" surface_stretch_stiffness: float = 0.0 """The stretch stiffness of the deformable body's surface. Defaults to 0.0.""" diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 59c9bfb94851..ebc0e1e0c22b 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -422,7 +422,7 @@ def _spawn_mesh_geom_from_mesh( root_prim = stage.GetPrimAtPath(prim_path) if "PhysxSurfaceDeformableBodyAPI" not in root_prim.GetAppliedSchemas(): root_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") - root_prim.GetAttribute("physxDeformableBody:disableGravity").Set(True) + root_prim.GetAttribute("physxDeformableBody:disableGravity").Set(False) root_prim.GetAttribute("physxDeformableBody:selfCollision").Set(True) else: diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 6e6f096a03c3..cb5034b154ee 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -605,9 +605,9 @@ def _initialize_impl(self): if self._root_physx_view._backend is None: raise RuntimeError(f"Failed to create deformable body at: {self.cfg.prim_path}. Please check PhysX logs.") # Check validity of deformables in view - # if not self._root_physx_view.check(): - # print(f"Simulation nodal positions: {self._root_physx_view.get_simulation_nodal_positions().numpy()}") - # raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") + if not self._root_physx_view.check(): + # raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") + logger.warning(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression if material_prim is not None: @@ -635,7 +635,7 @@ def _initialize_impl(self): logger.info(f"Number of instances: {self._material_physx_view.count}") else: logger.info("No deformable material found. Material properties will be set to default values.") - + # container for data access self._data = DeformableObjectData(self.root_view, self.device) From 28267025f6dd143af64ee5ff52e713d6c617e6ef Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 11 Mar 2026 20:55:41 +0100 Subject: [PATCH 16/73] Feat: Default set the thickness of surface deformables --- .../spawners/materials/physics_materials.py | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index a3052e6c2be3..81110c9a1801 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -5,15 +5,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from pxr import Usd, UsdPhysics, UsdShade +from pxr import Usd, UsdGeom, UsdPhysics, UsdShade from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_prim, safe_set_attribute_on_usd_schema from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.string import to_camel_case -# if TYPE_CHECKING: from . import physics_materials_cfg @@ -132,33 +129,25 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De # convert to dict cfg = cfg.to_dict() del cfg["func"] - attr_kwargs = { - attr_name: cfg.pop(attr_name) - for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"] - } - from omni.physx.scripts import deformableUtils - # set base attributes into OmniPhysics API (prim attributes: omniphysics:*) - # for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: - # value = cfg.pop(attr_name, None) - # safe_set_attribute_on_usd_prim( - # prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - # ) + for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) if is_surface_deformable: - attr_kwargs.update({ - attr_name: cfg.pop(attr_name) - for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"] - }) - deformableUtils.add_surface_deformable_material(stage, prim_path, **attr_kwargs) - # for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: - # value = cfg.pop(attr_name, None) - # safe_set_attribute_on_usd_prim( - # prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - # ) - else: - deformableUtils.add_deformable_material(stage, prim_path, **attr_kwargs) + # auto set value for thickness, same as usdLoad/Materials.cpp + mpu = UsdGeom.GetStageMetersPerUnit(stage) + cfg["surface_thickness"] = 0.001 / mpu if cfg["surface_thickness"] is None else cfg["surface_thickness"] + if cfg["surface_thickness"] <= 0: + raise ValueError("Surface thickness must be greater than 0.") + for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) for attr_name, value in cfg.items(): safe_set_attribute_on_usd_prim( From ab56f45b1694ad75c79f0186810875ac0ef7e8c5 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Thu, 12 Mar 2026 09:40:34 +0100 Subject: [PATCH 17/73] Feat: Use set_physics for surface deformable instead of auto_create --- .../01_assets/run_deformable_object.py | 8 ++-- .../materials/physics_materials_cfg.py | 4 +- .../isaaclab/sim/spawners/meshes/meshes.py | 44 ++++++------------- 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index cd40b2b41769..c60041120173 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -133,15 +133,15 @@ def design_scene(): scene_entities["cube_object"] = cube_object # 2D Cloth Object - sim_utils.create_prim(f"/World/OriginCloth", "Xform", translation=[0,0,0.75]) + sim_utils.create_prim(f"/World/OriginCloth", "Xform", translation=[0,0,1.5]) cfg = DeformableObjectCfg( prim_path="/World/OriginCloth/Cloth", spawn=sim_utils.MeshSquareCfg( - size=0.5, - resolution=(3, 3), + size=1.5, + resolution=(13, 13), deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), - physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5, surface_thickness=0.01), + physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), ) cloth_object = DeformableObject(cfg=cfg) diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index f7f381d1e717..4c5c8ba2fbd5 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -133,8 +133,8 @@ class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" - surface_thickness: float | None = None - """The thickness of the deformable body's surface. Defaults to None, in which case the simulation decides the default thickness.""" + surface_thickness: float = 0.01 + """The thickness of the deformable body's surface. Defaults to 0.01 meters (m).""" surface_stretch_stiffness: float = 0.0 """The stretch stiffness of the deformable body's surface. Defaults to 0.0.""" diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index ebc0e1e0c22b..0d9391cec25e 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -373,8 +373,8 @@ def _spawn_mesh_geom_from_mesh( mesh_prim_path = geom_prim_path + "/mesh" # create the mesh prim - if cfg.deformable_props is None: - # non-deformables + if cfg.deformable_props is None or isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): + # non-deformables and surface deformables mesh_prim = create_prim( mesh_prim_path, prim_type="Mesh", @@ -408,35 +408,17 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) - if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): - # surface deformable - success = deformableUtils.create_auto_surface_deformable_hierarchy( - stage=stage, - root_prim_path=prim_path, - simulation_mesh_path=sim_mesh_prim_path, - cooking_src_mesh_path=render_mesh_prim_path, - cooking_src_simplification_enabled=False, - set_visibility_with_guide_purpose=True, - ) - - root_prim = stage.GetPrimAtPath(prim_path) - if "PhysxSurfaceDeformableBodyAPI" not in root_prim.GetAppliedSchemas(): - root_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") - root_prim.GetAttribute("physxDeformableBody:disableGravity").Set(False) - root_prim.GetAttribute("physxDeformableBody:selfCollision").Set(True) - - else: - # volume deformable - success = deformableUtils.create_auto_volume_deformable_hierarchy( - stage=stage, - root_prim_path=prim_path, - simulation_tetmesh_path=sim_mesh_prim_path, - collision_tetmesh_path=sim_mesh_prim_path, - cooking_src_mesh_path=render_mesh_prim_path, - simulation_hex_mesh_enabled=False, - cooking_src_simplification_enabled=False, - set_visibility_with_guide_purpose=True, - ) + # volume deformable + success = deformableUtils.create_auto_volume_deformable_hierarchy( + stage=stage, + root_prim_path=prim_path, + simulation_tetmesh_path=sim_mesh_prim_path, + collision_tetmesh_path=sim_mesh_prim_path, + cooking_src_mesh_path=render_mesh_prim_path, + simulation_hex_mesh_enabled=False, + cooking_src_simplification_enabled=False, + set_visibility_with_guide_purpose=True, + ) if not success: raise RuntimeError("Failed to create deformable hierarchy from the given mesh.") From b7df47b4cae925b77dcdfc38b294b2b8cc5f35d9 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Thu, 12 Mar 2026 12:10:06 +0100 Subject: [PATCH 18/73] Test: Manual tetmesh configuration --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 103 ++++-------------- .../spawners/materials/physics_materials.py | 7 -- .../isaaclab/sim/spawners/meshes/meshes.py | 84 +++++++------- 3 files changed, 60 insertions(+), 134 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 68da4bdac362..0deb81d48f46 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -965,95 +965,39 @@ def modify_deformable_body_properties( # check if the prim is valid and has the deformable-body API if not deformable_body_prim.IsValid(): return False - # TODO: Make this general for engines besides PhysX - deformable_applied_schemas = deformable_body_prim.GetAppliedSchemas() - # if "PhysxBaseDeformableBodyAPI" not in deformable_applied_schemas: - # return False - # if "OmniPhysicsBodyAPI" not in deformable_applied_schemas: - # return False + # TODO: PhysX specific APIs here # convert to dict - cfg = cfg.to_dict() - # set into deformable body API - # attr_kwargs = { - # attr_name: cfg.pop(attr_name) - # for attr_name in [ - # "kinematic_enabled", - # "collision_simplification", - # "collision_simplification_remeshing", - # "collision_simplification_remeshing_resolution", - # "collision_simplification_target_triangle_count", - # "collision_simplification_force_conforming", - # "simulation_hexahedral_resolution", - # "solver_position_iteration_count", - # "vertex_velocity_damping", - # "sleep_damping", - # "sleep_threshold", - # "settling_threshold", - # "self_collision", - # "self_collision_filter_distance", - # ] - # } - # TODO: These all belong somewhere, I need to figure out the concrete schema API locations. from omni.physx.scripts import deformableUtils - - # from omni.physx.scripts import deformableUtils as deformable_utils - # mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") # Deprecated - - # status = deformable_utils.add_physx_deformable_body(stage, prim_path=prim_path, **attr_kwargs) - # status = deformable_utils.set_physics_volume_deformable_body(stage, prim_path) - # check if the deformable body was successfully added - # if not status: - # return False - - + # set deformable body properties based on the type of the mesh (surface vs volume) if deformable_body_prim.IsA(UsdGeom.Mesh): success = deformableUtils.set_physics_surface_deformable_body(stage, prim_path) - if not success: - return False + # apply physx extension api + if "PhysxSurfaceDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") elif deformable_body_prim.IsA(UsdGeom.TetMesh): - # success = deformableUtils.set_physics_volume_deformable_body(stage, prim_path) - # if not success: - # return False - pass + success = deformableUtils.set_physics_volume_deformable_body(stage, prim_path) + # apply physx extension api + if "PhysxBaseDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") else: - # raise ValueError( - # f"Deformable body prim '{prim_path}' is not a Mesh or TetMesh. Found type: {deformable_body_prim.GetTypeName()}" - # ) - pass - - if "OmniPhysicsDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - raise ValueError(f"Prim path '{prim_path}' does not have the deformable body schema applied.") - - # if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): - # prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") - - - # get collision prim - matching_prims = get_all_matching_child_prims(prim_path, lambda p: "PhysicsCollisionAPI" in p.GetAppliedSchemas()) - # check if the mesh is valid - if len(matching_prims) == 0: - raise ValueError(f"Could not find any collision mesh in '{prim_path}'. Please check asset.") - if len(matching_prims) > 1: - # get list of all meshes found - mesh_paths = [p.GetPrimPath() for p in matching_prims] - raise ValueError( - f"Found multiple collision meshes in '{prim_path}': {mesh_paths}." - " Deformable body schema can only be applied to one mesh." - ) - col_prim = matching_prims[0] - - # ensure PhysX collision API is applied on the collision mesh - # TODO: Specific to PhysX - if "PhysxCollisionAPI" not in col_prim.GetAppliedSchemas(): - col_prim.AddAppliedSchema("PhysxCollisionAPI") + print(f"Unsupported deformable body prim type: '{deformable_body_prim.GetTypeName()}'. Only Mesh and TetMesh are supported.") + success = False + # api failure + if not success: + return False + # ensure PhysX collision API is applied on the collision mesh + if "PhysxCollisionAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") + # convert to dict + cfg = cfg.to_dict() # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) for attr_name, value in cfg.items(): if attr_name in ["rest_offset", "contact_offset"]: safe_set_attribute_on_usd_prim( - col_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + deformable_body_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) elif attr_name in ["kinematic_enabled", "deformable_body_enabled"]: safe_set_attribute_on_usd_prim( @@ -1064,13 +1008,6 @@ def modify_deformable_body_properties( deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) - # explicit: - # for attr_name in ["solver_position_iteration_count", "linear_damping", "max_linear_velocity", "settling_damping", "settling_threshold", "sleep_threshold", "max_depenetration_velocity", "self_collision", "self_collision_filter_distance", "enable_speculative_ccd", "disable_gravity"]: - # value = cfg.pop(attr_name, None) - # safe_set_attribute_on_usd_prim( - # deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - # ) - # success return True diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index 81110c9a1801..01269d9b508c 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -117,7 +117,6 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De prim.AddAppliedSchema("OmniPhysicsDeformableMaterialAPI") if "PhysxDeformableMaterialAPI" not in applied: prim.AddAppliedSchema("PhysxDeformableMaterialAPI") - # surface deformable material API is_surface_deformable = isinstance(cfg, physics_materials_cfg.SurfaceDeformableBodyMaterialCfg) if is_surface_deformable: @@ -137,12 +136,6 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De ) # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) if is_surface_deformable: - # auto set value for thickness, same as usdLoad/Materials.cpp - mpu = UsdGeom.GetStageMetersPerUnit(stage) - cfg["surface_thickness"] = 0.001 / mpu if cfg["surface_thickness"] is None else cfg["surface_thickness"] - if cfg["surface_thickness"] <= 0: - raise ValueError("Surface thickness must be greater than 0.") - for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: value = cfg.pop(attr_name, None) safe_set_attribute_on_usd_prim( diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 0d9391cec25e..3648c7f2aac0 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -373,60 +373,56 @@ def _spawn_mesh_geom_from_mesh( mesh_prim_path = geom_prim_path + "/mesh" # create the mesh prim - if cfg.deformable_props is None or isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): - # non-deformables and surface deformables - mesh_prim = create_prim( - mesh_prim_path, - prim_type="Mesh", - scale=scale, - attributes={ - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", - }, - stage=stage, - ) - else: + # non-deformables and surface deformables use UsdGeom.Mesh + mesh_prim = create_prim( + mesh_prim_path, + prim_type="Mesh", + scale=scale, + attributes={ + "points": mesh.vertices, + "faceVertexIndices": mesh.faces.flatten(), + "faceVertexCounts": np.asarray([3] * len(mesh.faces)), + "subdivisionScheme": "bilinear", + }, + stage=stage, + ) + + if cfg.deformable_props is not None and not isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): + # volume deformables have both a triangle surface UsdGeom.Mesh for visualization and a tetrahedral UsdGeom.TetMesh for simulation from omni.physx.scripts import deformableUtils - from omni.physx import get_physx_cooking_interface # create all the paths we need for clarity, we use the same mesh for simulation and collision - render_mesh_prim_path = prim_path + "/RenderMesh" - sim_mesh_prim_path = prim_path + "/SimulationMesh" - - # create surface mesh prim - render_mesh_prim = create_prim( - render_mesh_prim_path, - prim_type="Mesh", + sim_mesh_prim_path = geom_prim_path + "/tetmesh" + + # tetrahedralize surface mesh + tet_mesh_points, tet_mesh_indices = deformableUtils.compute_conforming_tetrahedral_mesh(mesh.vertices, mesh.faces.flatten()) + + # create simulation tetmesh prim + sim_mesh_prim = create_prim( + sim_mesh_prim_path, + prim_type="TetMesh", scale=scale, attributes={ - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", + "points": tet_mesh_points, + "tetVertexIndices": np.asarray(tet_mesh_indices).reshape(-1, 4), }, stage=stage, ) - # volume deformable - success = deformableUtils.create_auto_volume_deformable_hierarchy( - stage=stage, - root_prim_path=prim_path, - simulation_tetmesh_path=sim_mesh_prim_path, - collision_tetmesh_path=sim_mesh_prim_path, - cooking_src_mesh_path=render_mesh_prim_path, - simulation_hex_mesh_enabled=False, - cooking_src_simplification_enabled=False, - set_visibility_with_guide_purpose=True, - ) - - if not success: - raise RuntimeError("Failed to create deformable hierarchy from the given mesh.") + # bind pose of visual triangle mesh to simulation tetmesh + purposesAttrName = "deformablePose:default:omniphysics:purposes" + pointsAttrName = "deformablePose:default:omniphysics:points" + + mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") + if mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): + mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) + mesh_prim.GetAttribute(pointsAttrName).Set(mesh_prim.GetAttribute("points").Get()) - # generate tet mesh for deformable object - get_physx_cooking_interface().cook_auto_deformable_body(prim_path) + sim_mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") + if sim_mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): + sim_mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) + sim_mesh_prim.GetAttribute(pointsAttrName).Set(sim_mesh_prim.GetAttribute("points").Get()) - mesh_prim_path = prim_path + mesh_prim_path = sim_mesh_prim_path # note: in case of deformable objects, we need to apply the deformable properties to the mesh prim. # this is different from rigid objects where we apply the properties to the parent prim. From 758ae73fa20891bf652ada9e26647ee2d43d93c9 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Thu, 12 Mar 2026 15:00:31 +0100 Subject: [PATCH 19/73] Feat: Creating render mesh as child of volume deformable simulation TetMesh automatically binds their poses --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 30 +------- .../isaaclab/sim/spawners/meshes/meshes.py | 68 +++++++++---------- 2 files changed, 35 insertions(+), 63 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 0deb81d48f46..174e4adeacff 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -880,32 +880,6 @@ def define_deformable_body_properties( # check if prim path is valid if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") - # check if prim has deformable body applied on it - # if "OmniPhysicsDeformableBodyAPI" not in prim.GetAppliedSchemas(): - # raise ValueError(f"Prim path '{prim_path}' does not have the deformable body schema applied.") - - # traverse the prim and get the collision mesh - # TODO: currently we only allow volume deformables (TetMesh), if surface deformable we want the prim type to be Mesh. - # matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "TetMesh") - # # check if the mesh is valid - # if len(matching_prims) == 0: - # raise ValueError(f"Could not find any mesh in '{prim_path}'. Please check asset.") - # if len(matching_prims) > 1: - # # get list of all meshes found - # mesh_paths = [p.GetPrimPath() for p in matching_prims] - # raise ValueError( - # f"Found multiple meshes in '{prim_path}': {mesh_paths}." - # " Deformable body schema can only be applied to one mesh." - # ) - - # get deformable-body USD prim - # mesh_prim = matching_prims[0] - # ensure PhysX deformable body API is applied - # mesh_applied = mesh_prim.GetAppliedSchemas() - - # TODO: Make this general for engines besides PhysX - # if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): - # prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # set deformable body properties modify_deformable_body_properties(prim_path, cfg, stage) @@ -962,12 +936,10 @@ def modify_deformable_body_properties( # get deformable-body USD prim deformable_body_prim = stage.GetPrimAtPath(prim_path) - # check if the prim is valid and has the deformable-body API + # check if the prim is valid if not deformable_body_prim.IsValid(): return False - # TODO: PhysX specific APIs here - # convert to dict from omni.physx.scripts import deformableUtils # set deformable body properties based on the type of the mesh (surface vs volume) if deformable_body_prim.IsA(UsdGeom.Mesh): diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 3648c7f2aac0..0e26398dce0c 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -370,35 +370,37 @@ def _spawn_mesh_geom_from_mesh( # create all the paths we need for clarity geom_prim_path = prim_path + "/geometry" - mesh_prim_path = geom_prim_path + "/mesh" # create the mesh prim - # non-deformables and surface deformables use UsdGeom.Mesh - mesh_prim = create_prim( - mesh_prim_path, - prim_type="Mesh", - scale=scale, - attributes={ - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", - }, - stage=stage, - ) - - if cfg.deformable_props is not None and not isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): + if cfg.deformable_props is None or isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): + # non-deformables and surface deformables use UsdGeom.Mesh + mesh_prim_path = geom_prim_path + "/mesh" + + mesh_prim = create_prim( + mesh_prim_path, + prim_type="Mesh", + scale=scale, + attributes={ + "points": mesh.vertices, + "faceVertexIndices": mesh.faces.flatten(), + "faceVertexCounts": np.asarray([3] * len(mesh.faces)), + "subdivisionScheme": "bilinear", + }, + stage=stage, + ) + else: # volume deformables have both a triangle surface UsdGeom.Mesh for visualization and a tetrahedral UsdGeom.TetMesh for simulation from omni.physx.scripts import deformableUtils # create all the paths we need for clarity, we use the same mesh for simulation and collision - sim_mesh_prim_path = geom_prim_path + "/tetmesh" + mesh_prim_path = geom_prim_path + "/tetmesh" + render_mesh_prim_path = mesh_prim_path + "/rendermesh" # tetrahedralize surface mesh tet_mesh_points, tet_mesh_indices = deformableUtils.compute_conforming_tetrahedral_mesh(mesh.vertices, mesh.faces.flatten()) # create simulation tetmesh prim - sim_mesh_prim = create_prim( - sim_mesh_prim_path, + mesh_prim = create_prim( + mesh_prim_path, prim_type="TetMesh", scale=scale, attributes={ @@ -408,21 +410,19 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) - # bind pose of visual triangle mesh to simulation tetmesh - purposesAttrName = "deformablePose:default:omniphysics:purposes" - pointsAttrName = "deformablePose:default:omniphysics:points" - - mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") - if mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): - mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) - mesh_prim.GetAttribute(pointsAttrName).Set(mesh_prim.GetAttribute("points").Get()) - - sim_mesh_prim.ApplyAPI("OmniPhysicsDeformablePoseAPI", "default") - if sim_mesh_prim.HasAPI("OmniPhysicsDeformablePoseAPI", "default"): - sim_mesh_prim.GetAttribute(purposesAttrName).Set(["bindPose"]) - sim_mesh_prim.GetAttribute(pointsAttrName).Set(sim_mesh_prim.GetAttribute("points").Get()) - - mesh_prim_path = sim_mesh_prim_path + # create visualization mesh prim + render_mesh_prim = create_prim( + render_mesh_prim_path, + prim_type="Mesh", + scale=scale, + attributes={ + "points": mesh.vertices, + "faceVertexIndices": mesh.faces.flatten(), + "faceVertexCounts": np.asarray([3] * len(mesh.faces)), + "subdivisionScheme": "bilinear", + }, + stage=stage, + ) # note: in case of deformable objects, we need to apply the deformable properties to the mesh prim. # this is different from rigid objects where we apply the properties to the parent prim. From 21c2e5caf39e5a9c2e0be8eb19cf805b30d99dcb Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Thu, 12 Mar 2026 16:51:03 +0100 Subject: [PATCH 20/73] Feat: Move deformable setup into physx specific --- .../01_assets/run_deformable_object.py | 10 +- source/isaaclab/isaaclab/sim/__init__.pyi | 10 -- .../isaaclab/sim/schemas/__init__.pyi | 4 - .../isaaclab/isaaclab/sim/schemas/schemas.py | 140 --------------- .../isaaclab/sim/spawners/__init__.pyi | 6 - .../sim/spawners/from_files/from_files.py | 3 +- .../spawners/materials/physics_materials.py | 77 -------- .../materials/physics_materials_cfg.py | 70 -------- .../isaaclab/sim/spawners/meshes/meshes.py | 8 +- .../isaaclab_physx/sim/__init__.py | 10 ++ .../isaaclab_physx/sim/__init__.pyi | 22 +++ .../isaaclab_physx/sim/schemas/__init__.py | 10 ++ .../isaaclab_physx/sim/schemas/__init__.pyi | 14 ++ .../isaaclab_physx/sim/schemas/schemas.py | 168 ++++++++++++++++++ .../isaaclab_physx/sim/spawners/__init__.py | 10 ++ .../isaaclab_physx/sim/spawners/__init__.pyi | 16 ++ .../sim/spawners/materials/__init__.py | 10 ++ .../sim/spawners/materials/__init__.pyi | 16 ++ .../spawners/materials/physics_materials.py | 91 ++++++++++ .../materials/physics_materials_cfg.py | 84 +++++++++ 20 files changed, 464 insertions(+), 315 deletions(-) create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/__init__.py create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.py create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.py create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.py create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index c60041120173..42e08dd10a8d 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -75,9 +75,9 @@ from isaaclab.sensors.camera import Camera, CameraCfg from isaaclab.utils import convert_dict_to_backend -# from isaaclab.assets import DeformableObject, DeformableObjectCfg -# For now import PhysX implementation for testing. +# deformables supported in PhysX from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +from isaaclab_physx.sim import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg def define_sensor() -> Camera: @@ -123,7 +123,7 @@ def design_scene(): size=(0.2, 0.2, 0.2), deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), - physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), debug_vis=True, @@ -138,10 +138,10 @@ def design_scene(): prim_path="/World/OriginCloth/Cloth", spawn=sim_utils.MeshSquareCfg( size=1.5, - resolution=(13, 13), + resolution=(21, 21), deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), - physics_material=sim_utils.SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + physics_material=SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), ) cloth_object = DeformableObject(cfg=cfg) diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index 11a3b2eac081..f8a2d09f1755 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -22,13 +22,11 @@ __all__ = [ "activate_contact_sensors", "define_articulation_root_properties", "define_collision_properties", - "define_deformable_body_properties", "define_mass_properties", "define_mesh_collision_properties", "define_rigid_body_properties", "modify_articulation_root_properties", "modify_collision_properties", - "modify_deformable_body_properties", "modify_fixed_tendon_properties", "modify_joint_drive_properties", "modify_mass_properties", @@ -71,10 +69,7 @@ __all__ = [ "DomeLightCfg", "LightCfg", "SphereLightCfg", - "spawn_deformable_body_material", "spawn_rigid_body_material", - "DeformableBodyMaterialCfg", - "SurfaceDeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -189,13 +184,11 @@ from .schemas import ( activate_contact_sensors, define_articulation_root_properties, define_collision_properties, - define_deformable_body_properties, define_mass_properties, define_mesh_collision_properties, define_rigid_body_properties, modify_articulation_root_properties, modify_collision_properties, - modify_deformable_body_properties, modify_fixed_tendon_properties, modify_joint_drive_properties, modify_mass_properties, @@ -240,10 +233,7 @@ from .spawners import ( DomeLightCfg, LightCfg, SphereLightCfg, - spawn_deformable_body_material, spawn_rigid_body_material, - DeformableBodyMaterialCfg, - SurfaceDeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, spawn_from_mdl_file, diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi index 947d4ab47c92..4e65cb447833 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi @@ -10,13 +10,11 @@ __all__ = [ "activate_contact_sensors", "define_articulation_root_properties", "define_collision_properties", - "define_deformable_body_properties", "define_mass_properties", "define_mesh_collision_properties", "define_rigid_body_properties", "modify_articulation_root_properties", "modify_collision_properties", - "modify_deformable_body_properties", "modify_fixed_tendon_properties", "modify_joint_drive_properties", "modify_mass_properties", @@ -48,13 +46,11 @@ from .schemas import ( activate_contact_sensors, define_articulation_root_properties, define_collision_properties, - define_deformable_body_properties, define_mass_properties, define_mesh_collision_properties, define_rigid_body_properties, modify_articulation_root_properties, modify_collision_properties, - modify_deformable_body_properties, modify_fixed_tendon_properties, modify_joint_drive_properties, modify_mass_properties, diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 174e4adeacff..916d142e2008 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -844,146 +844,6 @@ def modify_spatial_tendon_properties( return True -""" -Deformable body properties. -""" - - -def define_deformable_body_properties( - prim_path: str, cfg: schemas_cfg.DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None -): - """Apply the deformable body schema on the input prim and set its properties. - - See :func:`modify_deformable_body_properties` for more details on how the properties are set. - - .. note:: - If the input prim is not a mesh, this function will traverse the prim and find the first mesh - under it. If no mesh or multiple meshes are found, an error is raised. This is because the deformable - body schema can only be applied to a single mesh. - - Args: - prim_path: The prim path where to apply the deformable body schema. - cfg: The configuration for the deformable body. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Raises: - ValueError: When the prim path is not valid. - ValueError: When the prim has no mesh or multiple meshes. - """ - # get stage handle - if stage is None: - stage = get_current_stage() - - # get USD prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid - if not prim.IsValid(): - raise ValueError(f"Prim path '{prim_path}' is not valid.") - - # set deformable body properties - modify_deformable_body_properties(prim_path, cfg, stage) - - -@apply_nested -def modify_deformable_body_properties( - prim_path: str, cfg: schemas_cfg.DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None -): - """Modify PhysX parameters for a deformable body prim. - - A `deformable body`_ is a single body that can be simulated by PhysX. Unlike rigid bodies, deformable bodies - support relative motion of the nodes in the mesh. Consequently, they can be used to simulate deformations - under applied forces. - - PhysX soft body simulation employs Finite Element Analysis (FEA) to simulate the deformations of the mesh. - It uses two tetrahedral meshes to represent the deformable body: - - 1. **Simulation mesh**: This mesh is used for the simulation and is the one that is deformed by the solver. - 2. **Collision mesh**: This mesh only needs to match the surface of the simulation mesh and is used for - collision detection. - - For most applications, we assume that the above two meshes are computed from the "render mesh" of the deformable - body. The render mesh is the mesh that is visible in the scene and is used for rendering purposes. It is composed - of triangles and is the one that is used to compute the above meshes based on PhysX cookings. - - The schema comprises of attributes that belong to the `PhysxDeformableBodyAPI`_. schemas containing the PhysX - parameters for the deformable body. - - .. caution:: - The deformable body schema is still under development by the Omniverse team. The current implementation - works with the PhysX schemas shipped with Isaac Sim 4.0.0 onwards. It may change in future releases. - - .. note:: - This function is decorated with :func:`apply_nested` that sets the properties to all the prims - (that have the schema applied on them) under the input prim path. - - .. _deformable body: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html - .. _PhysxDeformableBodyAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_deformable_a_p_i.html - - Args: - prim_path: The prim path to the deformable body. - cfg: The configuration for the deformable body. - stage: The stage where to find the prim. Defaults to None, in which case the - current stage is used. - - Returns: - True if the properties were successfully set, False otherwise. - """ - # get stage handle - if stage is None: - stage = get_current_stage() - - # get deformable-body USD prim - deformable_body_prim = stage.GetPrimAtPath(prim_path) - - # check if the prim is valid - if not deformable_body_prim.IsValid(): - return False - - from omni.physx.scripts import deformableUtils - # set deformable body properties based on the type of the mesh (surface vs volume) - if deformable_body_prim.IsA(UsdGeom.Mesh): - success = deformableUtils.set_physics_surface_deformable_body(stage, prim_path) - # apply physx extension api - if "PhysxSurfaceDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - deformable_body_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") - elif deformable_body_prim.IsA(UsdGeom.TetMesh): - success = deformableUtils.set_physics_volume_deformable_body(stage, prim_path) - # apply physx extension api - if "PhysxBaseDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") - else: - print(f"Unsupported deformable body prim type: '{deformable_body_prim.GetTypeName()}'. Only Mesh and TetMesh are supported.") - success = False - # api failure - if not success: - return False - - # ensure PhysX collision API is applied on the collision mesh - if "PhysxCollisionAPI" not in deformable_body_prim.GetAppliedSchemas(): - deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") - - # convert to dict - cfg = cfg.to_dict() - # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) - for attr_name, value in cfg.items(): - if attr_name in ["rest_offset", "contact_offset"]: - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - elif attr_name in ["kinematic_enabled", "deformable_body_enabled"]: - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - else: - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - - # success - return True - - """ Collision mesh properties. """ diff --git a/source/isaaclab/isaaclab/sim/spawners/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi index 30539298e0a6..c673b60418d5 100644 --- a/source/isaaclab/isaaclab/sim/spawners/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi @@ -24,10 +24,7 @@ __all__ = [ "DomeLightCfg", "LightCfg", "SphereLightCfg", - "spawn_deformable_body_material", "spawn_rigid_body_material", - "DeformableBodyMaterialCfg", - "SurfaceDeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -92,10 +89,7 @@ from .lights import ( SphereLightCfg, ) from .materials import ( - spawn_deformable_body_material, spawn_rigid_body_material, - DeformableBodyMaterialCfg, - SurfaceDeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, spawn_from_mdl_file, diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index ddb7f9ff2fb3..64ed4de2ef7f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -365,7 +365,8 @@ def _spawn_from_usd_file( # modify deformable body properties if cfg.deformable_props is not None: - schemas.modify_deformable_body_properties(prim_path, cfg.deformable_props) + # schemas.modify_deformable_body_properties(prim_path, cfg.deformable_props) #TODO + raise NotImplementedError("Deformable body properties modification is not implemented yet.") # apply visual material if cfg.visual_material is not None: diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index 01269d9b508c..b0b3b8fd7b7f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -71,80 +71,3 @@ def spawn_rigid_body_material(prim_path: str, cfg: physics_materials_cfg.RigidBo safe_set_attribute_on_usd_prim(prim, f"physxMaterial:{to_camel_case(attr_name, 'cC')}", value, camel_case=False) # return the prim return prim - - -@clone -def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.DeformableBodyMaterialCfg) -> Usd.Prim: - """Create material with deformable-body physics properties. - - Deformable body materials are used to define the physical properties to meshes of a deformable body. These - include the friction and deformable body properties. For more information on deformable body material, - please refer to the documentation on `PxFEMSoftBodyMaterial`_. - - .. note:: - This function is decorated with :func:`clone` that resolves prim path into list of paths - if the input prim path is a regex pattern. This is done to support spawning multiple assets - from a single and cloning the USD prim at the given path expression. - - Args: - prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, - then the asset is spawned at all the matching prim paths. - cfg: The configuration for the physics material. - - Returns: - The spawned deformable body material prim. - - Raises: - ValueError: When a prim already exists at the specified prim path and is not a material. - - .. _PxFEMSoftBodyMaterial: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/_api_build/structPxFEMSoftBodyMaterialModel.html - """ - # get stage handle - stage = get_current_stage() - - # create material prim if no prim exists - if not stage.GetPrimAtPath(prim_path).IsValid(): - _ = UsdShade.Material.Define(stage, prim_path) - - # obtain prim - prim = stage.GetPrimAtPath(prim_path) - # check if prim is a material - if not prim.IsA(UsdShade.Material): - raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") - # ensure PhysX deformable body material API is applied - applied = prim.GetAppliedSchemas() - if "OmniPhysicsDeformableMaterialAPI" not in applied: - prim.AddAppliedSchema("OmniPhysicsDeformableMaterialAPI") - if "PhysxDeformableMaterialAPI" not in applied: - prim.AddAppliedSchema("PhysxDeformableMaterialAPI") - # surface deformable material API - is_surface_deformable = isinstance(cfg, physics_materials_cfg.SurfaceDeformableBodyMaterialCfg) - if is_surface_deformable: - if "OmniPhysicsSurfaceDeformableMaterialAPI" not in applied: - prim.AddAppliedSchema("OmniPhysicsSurfaceDeformableMaterialAPI") - if "PhysxSurfaceDeformableMaterialAPI" not in applied: - prim.AddAppliedSchema("PhysxSurfaceDeformableMaterialAPI") - - # convert to dict - cfg = cfg.to_dict() - del cfg["func"] - # set base attributes into OmniPhysics API (prim attributes: omniphysics:*) - for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) - if is_surface_deformable: - for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_prim( - prim, f"physxDeformableMaterial:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - # return the prim - return prim diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index 4c5c8ba2fbd5..1db748940288 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -78,73 +78,3 @@ class RigidBodyMaterialCfg(PhysicsMaterialCfg): rigid contacts are active. """ - -@configclass -class DeformableBodyMaterialCfg(PhysicsMaterialCfg): - """Physics material parameters for deformable bodies. - - See :meth:`spawn_deformable_body_material` for more information. - - """ - - func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" - - density: float | None = None - """The material density. Defaults to None, in which case the simulation decides the default density.""" - - static_friction: float = 0.25 - """The static friction. Defaults to 0.25.""" - - dynamic_friction: float = 0.25 - """The dynamic friction. Defaults to 0.25.""" - - youngs_modulus: float = 50000000.0 - """The Young's modulus, which defines the body's stiffness. Defaults to 50000000.0. - - The Young's modulus is a measure of the material's ability to deform under stress. It is measured in Pascals (Pa). - """ - - poissons_ratio: float = 0.45 - """The Poisson's ratio which defines the body's volume preservation. Defaults to 0.45. - - The Poisson's ratio is a measure of the material's ability to expand in the lateral direction when compressed - in the axial direction. It is a dimensionless number between 0 and 0.5. Using a value of 0.5 will make the - material incompressible. - """ - - elasticity_damping: float = 0.005 - """The elasticity damping for the deformable material. Defaults to 0.005.""" - - damping_scale: float = 1.0 # DEPRECATED since change from SoftBodyMaterialView to DefrmableMaterialView - """The damping scale for the deformable material. Defaults to 1.0. - - A scale of 1 corresponds to default damping. A value of 0 will only apply damping to certain motions leading - to special effects that look similar to water filled soft bodies. - """ - - -@configclass -class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): - """Physics material parameters for surface deformable bodies, extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. - - See :meth:`spawn_deformable_body_material` for more information. - - """ - - func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" - - surface_thickness: float = 0.01 - """The thickness of the deformable body's surface. Defaults to 0.01 meters (m).""" - - surface_stretch_stiffness: float = 0.0 - """The stretch stiffness of the deformable body's surface. Defaults to 0.0.""" - - surface_shear_stiffness: float = 0.0 - """The shear stiffness of the deformable body's surface. Defaults to 0.0.""" - - surface_bend_stiffness: float = 0.0 - """The bend stiffness of the deformable body's surface. Defaults to 0.0.""" - - bend_damping: float = 0.0 - """The bend damping for the deformable body's surface. Defaults to 0.0.""" - diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 0e26398dce0c..2d8467866483 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -16,7 +16,11 @@ from isaaclab.sim import schemas from isaaclab.sim.utils import bind_physics_material, bind_visual_material, clone, create_prim, get_current_stage -from ..materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg, RigidBodyMaterialCfg +from ..materials import RigidBodyMaterialCfg + +# TODO: PhysX dependency +from isaaclab_physx.sim import schemas as schemas_physx +from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg if TYPE_CHECKING: from . import meshes_cfg @@ -431,7 +435,7 @@ def _spawn_mesh_geom_from_mesh( if cfg.mass_props is not None: schemas.define_mass_properties(mesh_prim_path, cfg.mass_props, stage=stage) # apply deformable body properties - schemas.define_deformable_body_properties(mesh_prim_path, cfg.deformable_props, stage=stage) + schemas_physx.define_deformable_body_properties(mesh_prim_path, cfg.deformable_props, stage=stage) elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh if cfg.__class__.__name__ == "MeshSphereCfg": diff --git a/source/isaaclab_physx/isaaclab_physx/sim/__init__.py b/source/isaaclab_physx/isaaclab_physx/sim/__init__.py new file mode 100644 index 000000000000..c315db7b9b3e --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Sub-package containing simulation-specific functionalities for PhysX backend.""" + +from isaaclab.utils.module import lazy_export + +lazy_export() diff --git a/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi new file mode 100644 index 000000000000..7c639f151105 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi @@ -0,0 +1,22 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +__all__ = [ + "define_deformable_body_properties", + "modify_deformable_body_properties", + "spawn_deformable_body_material", + "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", +] + +from .schemas import ( + define_deformable_body_properties, + modify_deformable_body_properties, +) +from .spawners import ( + spawn_deformable_body_material, + DeformableBodyMaterialCfg, + SurfaceDeformableBodyMaterialCfg, +) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.py new file mode 100644 index 000000000000..b0d1477483d6 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Sub-module containing utilities for schemas used in Omniverse for PhysX backend.""" + +from isaaclab.utils.module import lazy_export + +lazy_export() diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi new file mode 100644 index 000000000000..d0203b116f64 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi @@ -0,0 +1,14 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +__all__ = [ + "define_deformable_body_properties", + "modify_deformable_body_properties", +] + +from .schemas import ( + define_deformable_body_properties, + modify_deformable_body_properties, +) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py new file mode 100644 index 000000000000..f796b4e0f73b --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -0,0 +1,168 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# needed to import for allowing type-hinting: Usd.Stage | None +from __future__ import annotations + +import logging +import math +from typing import Any + +from pxr import Usd, UsdGeom, UsdPhysics + +from isaaclab.sim.utils.stage import get_current_stage +from isaaclab.utils.string import to_camel_case + +from isaaclab.sim.utils import ( + apply_nested, + find_global_fixed_joint_prim, + get_all_matching_child_prims, + safe_set_attribute_on_usd_prim, + safe_set_attribute_on_usd_schema, +) + +from isaaclab.sim.schemas import schemas_cfg + +# import logger +logger = logging.getLogger(__name__) + + +""" +Deformable body properties. +""" + +def define_deformable_body_properties( + prim_path: str, cfg: schemas_cfg.DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None +): + """Apply the deformable body schema on the input prim and set its properties. + + See :func:`modify_deformable_body_properties` for more details on how the properties are set. + + .. note:: + If the input prim is not a mesh, this function will traverse the prim and find the first mesh + under it. If no mesh or multiple meshes are found, an error is raised. This is because the deformable + body schema can only be applied to a single mesh. + + Args: + prim_path: The prim path where to apply the deformable body schema. + cfg: The configuration for the deformable body. + stage: The stage where to find the prim. Defaults to None, in which case the + current stage is used. + + Raises: + ValueError: When the prim path is not valid. + ValueError: When the prim has no mesh or multiple meshes. + """ + # get stage handle + if stage is None: + stage = get_current_stage() + + # get USD prim + prim = stage.GetPrimAtPath(prim_path) + # check if prim path is valid + if not prim.IsValid(): + raise ValueError(f"Prim path '{prim_path}' is not valid.") + + # set deformable body properties + modify_deformable_body_properties(prim_path, cfg, stage) + + +@apply_nested +def modify_deformable_body_properties( + prim_path: str, cfg: schemas_cfg.DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None +): + """Modify PhysX parameters for a deformable body prim. + + A `deformable body`_ is a single body that can be simulated by PhysX. Unlike rigid bodies, deformable bodies + support relative motion of the nodes in the mesh. Consequently, they can be used to simulate deformations + under applied forces. + + PhysX soft body simulation employs Finite Element Analysis (FEA) to simulate the deformations of the mesh. + It uses two tetrahedral meshes to represent the deformable body: + + 1. **Simulation mesh**: This mesh is used for the simulation and is the one that is deformed by the solver. + 2. **Collision mesh**: This mesh only needs to match the surface of the simulation mesh and is used for + collision detection. + + For most applications, we assume that the above two meshes are computed from the "render mesh" of the deformable + body. The render mesh is the mesh that is visible in the scene and is used for rendering purposes. It is composed + of triangles and is the one that is used to compute the above meshes based on PhysX cookings. + + The schema comprises of attributes that belong to the `PhysxDeformableBodyAPI`_. schemas containing the PhysX + parameters for the deformable body. + + .. caution:: + The deformable body schema is still under development by the Omniverse team. The current implementation + works with the PhysX schemas shipped with Isaac Sim 4.0.0 onwards. It may change in future releases. + + .. note:: + This function is decorated with :func:`apply_nested` that sets the properties to all the prims + (that have the schema applied on them) under the input prim path. + + .. _deformable body: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html + .. _PhysxDeformableBodyAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_deformable_a_p_i.html + + Args: + prim_path: The prim path to the deformable body. + cfg: The configuration for the deformable body. + stage: The stage where to find the prim. Defaults to None, in which case the + current stage is used. + + Returns: + True if the properties were successfully set, False otherwise. + """ + # get stage handle + if stage is None: + stage = get_current_stage() + + # get deformable-body USD prim + deformable_body_prim = stage.GetPrimAtPath(prim_path) + + # check if the prim is valid + if not deformable_body_prim.IsValid(): + return False + + from omni.physx.scripts import deformableUtils + # set deformable body properties based on the type of the mesh (surface vs volume) + if deformable_body_prim.IsA(UsdGeom.Mesh): + success = deformableUtils.set_physics_surface_deformable_body(stage, prim_path) + # apply physx extension api + if "PhysxSurfaceDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") + elif deformable_body_prim.IsA(UsdGeom.TetMesh): + success = deformableUtils.set_physics_volume_deformable_body(stage, prim_path) + # apply physx extension api + if "PhysxBaseDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") + else: + print(f"Unsupported deformable body prim type: '{deformable_body_prim.GetTypeName()}'. Only Mesh and TetMesh are supported.") + success = False + # api failure + if not success: + return False + + # ensure PhysX collision API is applied on the collision mesh + if "PhysxCollisionAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") + + # convert to dict + cfg = cfg.to_dict() + # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) + for attr_name, value in cfg.items(): + if attr_name in ["rest_offset", "contact_offset"]: + safe_set_attribute_on_usd_prim( + deformable_body_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + elif attr_name in ["kinematic_enabled", "deformable_body_enabled"]: + safe_set_attribute_on_usd_prim( + deformable_body_prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + else: + safe_set_attribute_on_usd_prim( + deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + + # success + return True diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.py new file mode 100644 index 000000000000..82b931452f4f --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Sub-module containing utilities for creating prims in Omniverse for the PhysX backend.""" + +from isaaclab.utils.module import lazy_export + +lazy_export() diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi new file mode 100644 index 000000000000..09ef7f14c636 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi @@ -0,0 +1,16 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +__all__ = [ + "spawn_deformable_body_material", + "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", +] + +from .materials import ( + spawn_deformable_body_material, + DeformableBodyMaterialCfg, + SurfaceDeformableBodyMaterialCfg, +) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.py new file mode 100644 index 000000000000..4877bb0c1470 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Sub-module for spawners that spawn PhysX-based materials.""" + +from isaaclab.utils.module import lazy_export + +lazy_export() diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi new file mode 100644 index 000000000000..f4af357a3e93 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi @@ -0,0 +1,16 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +__all__ = [ + "spawn_deformable_body_material", + "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", +] + +from .physics_materials import spawn_deformable_body_material +from .physics_materials_cfg import ( + DeformableBodyMaterialCfg, + SurfaceDeformableBodyMaterialCfg, +) \ No newline at end of file diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py new file mode 100644 index 000000000000..7a4110d28bca --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py @@ -0,0 +1,91 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from pxr import Usd, UsdGeom, UsdPhysics, UsdShade + +from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_prim, safe_set_attribute_on_usd_schema +from isaaclab.sim.utils.stage import get_current_stage +from isaaclab.utils.string import to_camel_case + +from . import physics_materials_cfg + + +@clone +def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.DeformableBodyMaterialCfg) -> Usd.Prim: + """Create material with deformable-body physics properties. + + Deformable body materials are used to define the physical properties to meshes of a deformable body. These + include the friction and deformable body properties. For more information on deformable body material, + please refer to the documentation on `PxFEMSoftBodyMaterial`_. + + .. note:: + This function is decorated with :func:`clone` that resolves prim path into list of paths + if the input prim path is a regex pattern. This is done to support spawning multiple assets + from a single and cloning the USD prim at the given path expression. + + Args: + prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, + then the asset is spawned at all the matching prim paths. + cfg: The configuration for the physics material. + + Returns: + The spawned deformable body material prim. + + Raises: + ValueError: When a prim already exists at the specified prim path and is not a material. + + .. _PxFEMSoftBodyMaterial: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/_api_build/structPxFEMSoftBodyMaterialModel.html + """ + # get stage handle + stage = get_current_stage() + + # create material prim if no prim exists + if not stage.GetPrimAtPath(prim_path).IsValid(): + _ = UsdShade.Material.Define(stage, prim_path) + + # obtain prim + prim = stage.GetPrimAtPath(prim_path) + # check if prim is a material + if not prim.IsA(UsdShade.Material): + raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") + # ensure PhysX deformable body material API is applied + applied = prim.GetAppliedSchemas() + if "OmniPhysicsDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("OmniPhysicsDeformableMaterialAPI") + if "PhysxDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("PhysxDeformableMaterialAPI") + # surface deformable material API + is_surface_deformable = isinstance(cfg, physics_materials_cfg.SurfaceDeformableBodyMaterialCfg) + if is_surface_deformable: + if "OmniPhysicsSurfaceDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("OmniPhysicsSurfaceDeformableMaterialAPI") + if "PhysxSurfaceDeformableMaterialAPI" not in applied: + prim.AddAppliedSchema("PhysxSurfaceDeformableMaterialAPI") + + # convert to dict + cfg = cfg.to_dict() + del cfg["func"] + # set base attributes into OmniPhysics API (prim attributes: omniphysics:*) + for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) + if is_surface_deformable: + for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: + value = cfg.pop(attr_name, None) + safe_set_attribute_on_usd_prim( + prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) + for attr_name, value in cfg.items(): + safe_set_attribute_on_usd_prim( + prim, f"physxDeformableMaterial:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) + # return the prim + return prim diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py new file mode 100644 index 000000000000..cdea74f19a4e --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -0,0 +1,84 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import MISSING +from typing import Literal + +from isaaclab.utils import configclass +from isaaclab.sim.spawners.materials import PhysicsMaterialCfg + + +@configclass +class DeformableBodyMaterialCfg(PhysicsMaterialCfg): + """Physics material parameters for deformable bodies. + + See :meth:`spawn_deformable_body_material` for more information. + + """ + + func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" + + density: float | None = None + """The material density. Defaults to None, in which case the simulation decides the default density.""" + + static_friction: float = 0.25 + """The static friction. Defaults to 0.25.""" + + dynamic_friction: float = 0.25 + """The dynamic friction. Defaults to 0.25.""" + + youngs_modulus: float = 50000000.0 + """The Young's modulus, which defines the body's stiffness. Defaults to 50000000.0. + + The Young's modulus is a measure of the material's ability to deform under stress. It is measured in Pascals (Pa). + """ + + poissons_ratio: float = 0.45 + """The Poisson's ratio which defines the body's volume preservation. Defaults to 0.45. + + The Poisson's ratio is a measure of the material's ability to expand in the lateral direction when compressed + in the axial direction. It is a dimensionless number between 0 and 0.5. Using a value of 0.5 will make the + material incompressible. + """ + + elasticity_damping: float = 0.005 + """The elasticity damping for the deformable material. Defaults to 0.005.""" + + damping_scale: float = 1.0 # DEPRECATED since change from SoftBodyMaterialView to DefrmableMaterialView + """The damping scale for the deformable material. Defaults to 1.0. + + A scale of 1 corresponds to default damping. A value of 0 will only apply damping to certain motions leading + to special effects that look similar to water filled soft bodies. + """ + + +@configclass +class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): + """Physics material parameters for surface deformable bodies, extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. + + See :meth:`spawn_deformable_body_material` for more information. + + """ + + func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" + + surface_thickness: float = 0.01 + """The thickness of the deformable body's surface. Defaults to 0.01 meters (m).""" + + surface_stretch_stiffness: float = 0.0 + """The stretch stiffness of the deformable body's surface. Defaults to 0.0.""" + + surface_shear_stiffness: float = 0.0 + """The shear stiffness of the deformable body's surface. Defaults to 0.0.""" + + surface_bend_stiffness: float = 0.0 + """The bend stiffness of the deformable body's surface. Defaults to 0.0.""" + + bend_damping: float = 0.0 + """The bend damping for the deformable body's surface. Defaults to 0.0.""" + From 429bd49f1ffac87a9c08a1ea54b1c6746d248a1a Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 07:21:21 +0100 Subject: [PATCH 21/73] Fix: Non-existing remaining import in materials --- .../isaaclab/isaaclab/sim/spawners/materials/__init__.pyi | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi index ebe0f25c3b58..93142ddab389 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi @@ -4,10 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ - "spawn_deformable_body_material", "spawn_rigid_body_material", - "DeformableBodyMaterialCfg", - "SurfaceDeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -18,10 +15,8 @@ __all__ = [ "VisualMaterialCfg", ] -from .physics_materials import spawn_deformable_body_material, spawn_rigid_body_material +from .physics_materials import spawn_rigid_body_material from .physics_materials_cfg import ( - DeformableBodyMaterialCfg, - SurfaceDeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, ) From 3488dfc67b6e0586f4dec76f99c8aad10ef13307 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 07:24:54 +0100 Subject: [PATCH 22/73] Style: Remove deprecated properties of deformables --- scripts/tutorials/01_assets/run_deformable_object.py | 6 ++---- .../isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 7 +------ .../sim/spawners/materials/physics_materials_cfg.py | 9 --------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 42e08dd10a8d..18ef6c231c53 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -243,10 +243,8 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor sim_time += sim_dt count += 1 # update buffers - cube_object.update(sim_dt) - cloth_object.update(sim_dt) - if args_cli.save: - camera.update(sim_dt) + for entity in entities.values(): + entity.update(sim_dt) com_traj.append(wp.to_torch(cube_object.data.nodal_pos_w).mean(1).cpu().numpy()) # print the root position diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index f796b4e0f73b..e9d995751d5a 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -7,20 +7,15 @@ from __future__ import annotations import logging -import math -from typing import Any -from pxr import Usd, UsdGeom, UsdPhysics +from pxr import Usd, UsdGeom from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.string import to_camel_case from isaaclab.sim.utils import ( apply_nested, - find_global_fixed_joint_prim, - get_all_matching_child_prims, safe_set_attribute_on_usd_prim, - safe_set_attribute_on_usd_schema, ) from isaaclab.sim.schemas import schemas_cfg diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py index cdea74f19a4e..4a164a5cebc8 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -6,8 +6,6 @@ from __future__ import annotations from collections.abc import Callable -from dataclasses import MISSING -from typing import Literal from isaaclab.utils import configclass from isaaclab.sim.spawners.materials import PhysicsMaterialCfg @@ -49,13 +47,6 @@ class DeformableBodyMaterialCfg(PhysicsMaterialCfg): elasticity_damping: float = 0.005 """The elasticity damping for the deformable material. Defaults to 0.005.""" - damping_scale: float = 1.0 # DEPRECATED since change from SoftBodyMaterialView to DefrmableMaterialView - """The damping scale for the deformable material. Defaults to 1.0. - - A scale of 1 corresponds to default damping. A value of 0 will only apply damping to certain motions leading - to special effects that look similar to water filled soft bodies. - """ - @configclass class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): From fe07392ce16c0ac85741e9125a252076f5933df5 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 07:41:48 +0100 Subject: [PATCH 23/73] Style: Move DeformableBodyPropertiesCfg to isaaclab_physx backend --- source/isaaclab/isaaclab/sim/__init__.pyi | 2 - .../isaaclab/sim/schemas/__init__.pyi | 2 - .../isaaclab/sim/schemas/schemas_cfg.py | 133 ----------------- .../isaaclab/sim/spawners/spawner_cfg.py | 5 +- .../isaaclab_physx/sim/__init__.pyi | 2 + .../isaaclab_physx/sim/schemas/__init__.pyi | 2 + .../isaaclab_physx/sim/schemas/schemas_cfg.py | 141 ++++++++++++++++++ .../spawners/materials/physics_materials.py | 4 +- 8 files changed, 150 insertions(+), 141 deletions(-) create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index f8a2d09f1755..8a1c949a748f 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -39,7 +39,6 @@ __all__ = [ "CollisionPropertiesCfg", "ConvexDecompositionPropertiesCfg", "ConvexHullPropertiesCfg", - "DeformableBodyPropertiesCfg", "FixedTendonPropertiesCfg", "JointDrivePropertiesCfg", "MassPropertiesCfg", @@ -201,7 +200,6 @@ from .schemas import ( CollisionPropertiesCfg, ConvexDecompositionPropertiesCfg, ConvexHullPropertiesCfg, - DeformableBodyPropertiesCfg, FixedTendonPropertiesCfg, JointDrivePropertiesCfg, MassPropertiesCfg, diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi index 4e65cb447833..f413b3ded12d 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi @@ -27,7 +27,6 @@ __all__ = [ "CollisionPropertiesCfg", "ConvexDecompositionPropertiesCfg", "ConvexHullPropertiesCfg", - "DeformableBodyPropertiesCfg", "FixedTendonPropertiesCfg", "JointDrivePropertiesCfg", "MassPropertiesCfg", @@ -65,7 +64,6 @@ from .schemas_cfg import ( CollisionPropertiesCfg, ConvexDecompositionPropertiesCfg, ConvexHullPropertiesCfg, - DeformableBodyPropertiesCfg, FixedTendonPropertiesCfg, JointDrivePropertiesCfg, MassPropertiesCfg, diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index f50e652cb35b..0d1b69314189 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -297,139 +297,6 @@ class SpatialTendonPropertiesCfg: """ -@configclass -class DeformableBodyPropertiesCfg: - """Properties to apply to a deformable body. - - A deformable body is a body that can deform under forces. The configuration allows users to specify - the properties of the deformable body, such as the solver iteration counts, damping, and self-collision. - - An FEM-based deformable body is created by providing a collision mesh and simulation mesh. The collision mesh - is used for collision detection and the simulation mesh is used for simulation. The collision mesh is usually - a simplified version of the simulation mesh. - - Based on the above, the PhysX team provides APIs to either set the simulation and collision mesh directly - (by specifying the points) or to simplify the collision mesh based on the simulation mesh. The simplification - process involves remeshing the collision mesh and simplifying it based on the target triangle count. - - Since specifying the collision mesh points directly is not a common use case, we only expose the parameters - to simplify the collision mesh based on the simulation mesh. If you want to provide the collision mesh points, - please open an issue on the repository and we can add support for it. - - See :meth:`modify_deformable_body_properties` for more information. - - .. note:: - If the values are :obj:`None`, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. - """ - - deformable_body_enabled: bool | None = None - """Enables deformable body.""" - - kinematic_enabled: bool = False - """Enables kinematic body. Defaults to False, which means that the body is not kinematic. - - Similar to rigid bodies, this allows setting user-driven motion for the deformable body. For more information, - please refer to the `documentation `__. - """ - - self_collision: bool | None = None - """Whether to enable or disable self-collisions for the deformable body based on the rest position distances.""" - - self_collision_filter_distance: float | None = None - """Penetration value that needs to get exceeded before contacts for self collision are generated. - - This parameter must be greater than of equal to twice the :attr:`rest_offset` value. - - This value has an effect only if :attr:`self_collision` is enabled. - """ - - settling_threshold: float | None = None - """Threshold vertex velocity (in m/s) under which sleep damping is applied in addition to velocity damping.""" - - sleep_damping: float | None = None - """Coefficient for the additional damping term if fertex velocity drops below setting threshold.""" - - sleep_threshold: float | None = None - """The velocity threshold (in m/s) under which the vertex becomes a candidate for sleeping in the next step.""" - - solver_position_iteration_count: int | None = None - """Number of the solver positional iterations per step. Range is [1,255]""" - - vertex_velocity_damping: float | None = None - """Coefficient for artificial damping on the vertex velocity. - - This parameter can be used to approximate the effect of air drag on the deformable body. - """ - - simulation_hexahedral_resolution: int = 10 - """The target resolution for the hexahedral mesh used for simulation. Defaults to 10. - - Note: - This value is ignored if the user provides the simulation mesh points directly. However, we assume that - most users will not provide the simulation mesh points directly. If you want to provide the simulation mesh - directly, please set this value to :obj:`None`. - """ - - collision_simplification: bool = True - """Whether or not to simplify the collision mesh before creating a soft body out of it. Defaults to True. - - Note: - This flag is ignored if the user provides the simulation mesh points directly. However, we assume that - most users will not provide the simulation mesh points directly. Hence, this flag is enabled by default. - - If you want to provide the simulation mesh points directly, please set this flag to False. - """ - - collision_simplification_remeshing: bool = True - """Whether or not the collision mesh should be remeshed before simplification. Defaults to True. - - This parameter is ignored if :attr:`collision_simplification` is False. - """ - - collision_simplification_remeshing_resolution: int = 0 - """The resolution used for remeshing. Defaults to 0, which means that a heuristic is used to determine the - resolution. - - This parameter is ignored if :attr:`collision_simplification_remeshing` is False. - """ - - collision_simplification_target_triangle_count: int = 0 - """The target triangle count used for the simplification. Defaults to 0, which means that a heuristic based on - the :attr:`simulation_hexahedral_resolution` is used to determine the target count. - - This parameter is ignored if :attr:`collision_simplification` is False. - """ - - collision_simplification_force_conforming: bool = True - """Whether or not the simplification should force the output mesh to conform to the input mesh. Defaults to True. - - The flag indicates that the tretrahedralizer used to generate the collision mesh should produce tetrahedra - that conform to the triangle mesh. If False, the simplifier uses the output from the tretrahedralizer used. - - This parameter is ignored if :attr:`collision_simplification` is False. - """ - - contact_offset: float | None = None - """Contact offset for the collision shape (in m). - - The collision detector generates contact points as soon as two shapes get closer than the sum of their - contact offsets. This quantity should be non-negative which means that contact generation can potentially start - before the shapes actually penetrate. - """ - - rest_offset: float | None = None - """Rest offset for the collision shape (in m). - - The rest offset quantifies how close a shape gets to others at rest, At rest, the distance between two - vertically stacked objects is the sum of their rest offsets. If a pair of shapes have a positive rest - offset, the shapes will be separated at rest by an air gap. - """ - - max_depenetration_velocity: float | None = None - """Maximum depenetration velocity permitted to be introduced by the solver (in m/s).""" - - @configclass class MeshCollisionPropertiesCfg: """Properties to apply to a mesh in regards to collision. diff --git a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py index ba86e4937fc3..8532b16ef26f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py @@ -15,6 +15,7 @@ from pxr import Usd from isaaclab.sim import schemas + from isaaclab_physx.sim import schemas as schemas_physx @configclass @@ -120,5 +121,5 @@ class DeformableObjectSpawnerCfg(SpawnerCfg): mass_props: schemas.MassPropertiesCfg | None = None """Mass properties.""" - deformable_props: schemas.DeformableBodyPropertiesCfg | None = None - """Deformable body properties.""" + deformable_props: schemas_physx.DeformableBodyPropertiesCfg | None = None + """Deformable body properties. Only supported on PhysX backend for now.""" diff --git a/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi index 7c639f151105..4cb6a20c6f26 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi @@ -6,6 +6,7 @@ __all__ = [ "define_deformable_body_properties", "modify_deformable_body_properties", + "DeformableBodyPropertiesCfg", "spawn_deformable_body_material", "DeformableBodyMaterialCfg", "SurfaceDeformableBodyMaterialCfg", @@ -14,6 +15,7 @@ __all__ = [ from .schemas import ( define_deformable_body_properties, modify_deformable_body_properties, + DeformableBodyPropertiesCfg ) from .spawners import ( spawn_deformable_body_material, diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi index d0203b116f64..bb0e51a19b4b 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi @@ -6,9 +6,11 @@ __all__ = [ "define_deformable_body_properties", "modify_deformable_body_properties", + "DeformableBodyPropertiesCfg", ] from .schemas import ( define_deformable_body_properties, modify_deformable_body_properties, ) +from .schemas_cfg import DeformableBodyPropertiesCfg diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py new file mode 100644 index 000000000000..188ba9589749 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -0,0 +1,141 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from isaaclab.utils import configclass + + +@configclass +class DeformableBodyPropertiesCfg: + """Properties to apply to a deformable body. + + A deformable body is a body that can deform under forces. The configuration allows users to specify + the properties of the deformable body, such as the solver iteration counts, damping, and self-collision. + + An FEM-based deformable body is created by providing a collision mesh and simulation mesh. The collision mesh + is used for collision detection and the simulation mesh is used for simulation. The collision mesh is usually + a simplified version of the simulation mesh. + + Based on the above, the PhysX team provides APIs to either set the simulation and collision mesh directly + (by specifying the points) or to simplify the collision mesh based on the simulation mesh. The simplification + process involves remeshing the collision mesh and simplifying it based on the target triangle count. + + Since specifying the collision mesh points directly is not a common use case, we only expose the parameters + to simplify the collision mesh based on the simulation mesh. If you want to provide the collision mesh points, + please open an issue on the repository and we can add support for it. + + See :meth:`modify_deformable_body_properties` for more information. + + .. note:: + If the values are :obj:`None`, they are not modified. This is useful when you want to set only a subset of + the properties and leave the rest as-is. + """ + + deformable_body_enabled: bool | None = None + """Enables deformable body.""" + + kinematic_enabled: bool = False + """Enables kinematic body. Defaults to False, which means that the body is not kinematic. + + Similar to rigid bodies, this allows setting user-driven motion for the deformable body. For more information, + please refer to the `documentation `__. + """ + + self_collision: bool | None = None + """Whether to enable or disable self-collisions for the deformable body based on the rest position distances.""" + + self_collision_filter_distance: float | None = None + """Penetration value that needs to get exceeded before contacts for self collision are generated. + + This parameter must be greater than of equal to twice the :attr:`rest_offset` value. + + This value has an effect only if :attr:`self_collision` is enabled. + """ + + settling_threshold: float | None = None + """Threshold vertex velocity (in m/s) under which sleep damping is applied in addition to velocity damping.""" + + sleep_damping: float | None = None + """Coefficient for the additional damping term if fertex velocity drops below setting threshold.""" + + sleep_threshold: float | None = None + """The velocity threshold (in m/s) under which the vertex becomes a candidate for sleeping in the next step.""" + + solver_position_iteration_count: int | None = None + """Number of the solver positional iterations per step. Range is [1,255]""" + + vertex_velocity_damping: float | None = None + """Coefficient for artificial damping on the vertex velocity. + + This parameter can be used to approximate the effect of air drag on the deformable body. + """ + + simulation_hexahedral_resolution: int = 10 + """The target resolution for the hexahedral mesh used for simulation. Defaults to 10. + + Note: + This value is ignored if the user provides the simulation mesh points directly. However, we assume that + most users will not provide the simulation mesh points directly. If you want to provide the simulation mesh + directly, please set this value to :obj:`None`. + """ + + collision_simplification: bool = True + """Whether or not to simplify the collision mesh before creating a soft body out of it. Defaults to True. + + Note: + This flag is ignored if the user provides the simulation mesh points directly. However, we assume that + most users will not provide the simulation mesh points directly. Hence, this flag is enabled by default. + + If you want to provide the simulation mesh points directly, please set this flag to False. + """ + + collision_simplification_remeshing: bool = True + """Whether or not the collision mesh should be remeshed before simplification. Defaults to True. + + This parameter is ignored if :attr:`collision_simplification` is False. + """ + + collision_simplification_remeshing_resolution: int = 0 + """The resolution used for remeshing. Defaults to 0, which means that a heuristic is used to determine the + resolution. + + This parameter is ignored if :attr:`collision_simplification_remeshing` is False. + """ + + collision_simplification_target_triangle_count: int = 0 + """The target triangle count used for the simplification. Defaults to 0, which means that a heuristic based on + the :attr:`simulation_hexahedral_resolution` is used to determine the target count. + + This parameter is ignored if :attr:`collision_simplification` is False. + """ + + collision_simplification_force_conforming: bool = True + """Whether or not the simplification should force the output mesh to conform to the input mesh. Defaults to True. + + The flag indicates that the tretrahedralizer used to generate the collision mesh should produce tetrahedra + that conform to the triangle mesh. If False, the simplifier uses the output from the tretrahedralizer used. + + This parameter is ignored if :attr:`collision_simplification` is False. + """ + + contact_offset: float | None = None + """Contact offset for the collision shape (in m). + + The collision detector generates contact points as soon as two shapes get closer than the sum of their + contact offsets. This quantity should be non-negative which means that contact generation can potentially start + before the shapes actually penetrate. + """ + + rest_offset: float | None = None + """Rest offset for the collision shape (in m). + + The rest offset quantifies how close a shape gets to others at rest, At rest, the distance between two + vertically stacked objects is the sum of their rest offsets. If a pair of shapes have a positive rest + offset, the shapes will be separated at rest by an air gap. + """ + + max_depenetration_velocity: float | None = None + """Maximum depenetration velocity permitted to be introduced by the solver (in m/s).""" diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py index 7a4110d28bca..a2455d780c3a 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py @@ -5,9 +5,9 @@ from __future__ import annotations -from pxr import Usd, UsdGeom, UsdPhysics, UsdShade +from pxr import Usd, UsdShade -from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_prim, safe_set_attribute_on_usd_schema +from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_prim from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.string import to_camel_case From 20586a36df3d283b0f6d9665765442973f9c0fa1 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 07:42:49 +0100 Subject: [PATCH 24/73] Fix: Demo import fix --- scripts/tutorials/01_assets/run_deformable_object.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 18ef6c231c53..3597ef0aeca4 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -77,7 +77,7 @@ # deformables supported in PhysX from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg +from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg def define_sensor() -> Camera: @@ -121,7 +121,7 @@ def design_scene(): prim_path="/World/Origin.*/Cube", spawn=sim_utils.MeshCuboidCfg( size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), @@ -139,7 +139,7 @@ def design_scene(): spawn=sim_utils.MeshSquareCfg( size=1.5, resolution=(21, 21), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), physics_material=SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), From e63b7dea22af8867082157b4407e5271c83557e4 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 08:25:00 +0100 Subject: [PATCH 25/73] Fix: Update deformable demo --- scripts/demos/deformables.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 108b2ac6b651..5418abea3487 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -73,8 +73,10 @@ import isaaclab.sim as sim_utils from isaaclab.utils import convert_dict_to_backend from isaaclab.sensors.camera import Camera, CameraCfg -# from isaaclab.assets import DeformableObject, DeformableObjectCfg + +# deformables supported in PhysX from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg def define_origins(num_origins: int, spacing: float) -> list[list[float]]: @@ -130,36 +132,36 @@ def design_scene() -> tuple[dict, list[list[float]]]: # spawn a red cone cfg_sphere = sim_utils.MeshSphereCfg( radius=0.5, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) cfg_cuboid = sim_utils.MeshCuboidCfg( size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) cfg_cylinder = sim_utils.MeshCylinderCfg( radius=0.15, height=0.5, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) cfg_capsule = sim_utils.MeshCapsuleCfg( radius=0.35, height=0.5, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) cfg_cone = sim_utils.MeshConeCfg( radius=0.15, height=0.5, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) # create a dictionary of all the objects to be spawned objects_cfg = { From 2966ec3f8cde819bafb82bd87e7c90707aa163f1 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 08:49:03 +0100 Subject: [PATCH 26/73] Feat: Move DeformableObjectSpawnerCfg into isaaclab_physx as well --- source/isaaclab/isaaclab/sim/__init__.pyi | 2 - .../isaaclab/sim/spawners/__init__.pyi | 3 +- .../sim/spawners/from_files/from_files_cfg.py | 5 ++- .../sim/spawners/meshes/meshes_cfg.py | 5 ++- .../isaaclab/sim/spawners/spawner_cfg.py | 22 ----------- .../isaaclab_physx/sim/__init__.pyi | 2 + .../isaaclab_physx/sim/spawners/__init__.pyi | 2 + .../sim/spawners/spawner_cfg.py | 39 +++++++++++++++++++ 8 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index 8a1c949a748f..aa1816845242 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -50,7 +50,6 @@ __all__ = [ "TriangleMeshSimplificationPropertiesCfg", "SpawnerCfg", "RigidObjectSpawnerCfg", - "DeformableObjectSpawnerCfg", "spawn_from_mjcf", "spawn_from_urdf", "spawn_from_usd", @@ -213,7 +212,6 @@ from .schemas import ( from .spawners import ( SpawnerCfg, RigidObjectSpawnerCfg, - DeformableObjectSpawnerCfg, spawn_from_mjcf, spawn_from_urdf, spawn_from_usd, diff --git a/source/isaaclab/isaaclab/sim/spawners/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi index c673b60418d5..ba8f6d3d7b69 100644 --- a/source/isaaclab/isaaclab/sim/spawners/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi @@ -6,7 +6,6 @@ __all__ = [ "SpawnerCfg", "RigidObjectSpawnerCfg", - "DeformableObjectSpawnerCfg", "spawn_from_mjcf", "spawn_from_urdf", "spawn_from_usd", @@ -66,7 +65,7 @@ __all__ = [ "MultiUsdFileCfg", ] -from .spawner_cfg import SpawnerCfg, RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg +from .spawner_cfg import SpawnerCfg, RigidObjectSpawnerCfg from .from_files import ( spawn_from_mjcf, spawn_from_urdf, diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py index 0de0b85911ea..343911910690 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py @@ -10,10 +10,13 @@ from isaaclab.sim import converters, schemas from isaaclab.sim.spawners import materials -from isaaclab.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg, RigidObjectSpawnerCfg, SpawnerCfg +from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg from isaaclab.utils import configclass from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR +# deformables only supported on PhysX backend +from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg + @configclass class FileCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index 03dfc742f8fe..22452f6b9cd9 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -10,9 +10,12 @@ from typing import Literal from isaaclab.sim.spawners import materials -from isaaclab.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg, RigidObjectSpawnerCfg +from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg from isaaclab.utils import configclass +# deformables only supported on PhysX backend +from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg + @configclass class MeshCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): diff --git a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py index 8532b16ef26f..5b4f8b4379c3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py @@ -101,25 +101,3 @@ class RigidObjectSpawnerCfg(SpawnerCfg): This adds the PhysxContactReporter API to all the rigid bodies in the given prim path and its children. """ - - -@configclass -class DeformableObjectSpawnerCfg(SpawnerCfg): - """Configuration parameters for spawning a deformable asset. - - Unlike rigid objects, deformable objects are affected by forces and can deform when subjected to - external forces. This class is used to configure the properties of the deformable object. - - Deformable bodies don't have a separate collision mesh. The collision mesh is the same as the visual mesh. - The collision properties such as rest and collision offsets are specified in the :attr:`deformable_props`. - - Note: - By default, all properties are set to None. This means that no properties will be added or modified - to the prim outside of the properties available by default when spawning the prim. - """ - - mass_props: schemas.MassPropertiesCfg | None = None - """Mass properties.""" - - deformable_props: schemas_physx.DeformableBodyPropertiesCfg | None = None - """Deformable body properties. Only supported on PhysX backend for now.""" diff --git a/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi index 4cb6a20c6f26..c75cccf3f04a 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi @@ -7,6 +7,7 @@ __all__ = [ "define_deformable_body_properties", "modify_deformable_body_properties", "DeformableBodyPropertiesCfg", + "DeformableObjectSpawnerCfg", "spawn_deformable_body_material", "DeformableBodyMaterialCfg", "SurfaceDeformableBodyMaterialCfg", @@ -18,6 +19,7 @@ from .schemas import ( DeformableBodyPropertiesCfg ) from .spawners import ( + DeformableObjectSpawnerCfg, spawn_deformable_body_material, DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg, diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi index 09ef7f14c636..805a9f79fd52 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi @@ -4,11 +4,13 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ + "DeformableObjectSpawnerCfg", "spawn_deformable_body_material", "DeformableBodyMaterialCfg", "SurfaceDeformableBodyMaterialCfg", ] +from .spawner_cfg import DeformableObjectSpawnerCfg from .materials import ( spawn_deformable_body_material, DeformableBodyMaterialCfg, diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py new file mode 100644 index 000000000000..e746158b8130 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py @@ -0,0 +1,39 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from isaaclab.utils import configclass +from isaaclab.sim.spawners.spawner_cfg import SpawnerCfg + + +if TYPE_CHECKING: + from isaaclab.sim import schemas + # deformables only supported on PhysX backend + from isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg + + +@configclass +class DeformableObjectSpawnerCfg(SpawnerCfg): + """Configuration parameters for spawning a deformable asset. + + Unlike rigid objects, deformable objects are affected by forces and can deform when subjected to + external forces. This class is used to configure the properties of the deformable object. + + Deformable bodies don't have a separate collision mesh. The collision mesh is the same as the visual mesh. + The collision properties such as rest and collision offsets are specified in the :attr:`deformable_props`. + + Note: + By default, all properties are set to None. This means that no properties will be added or modified + to the prim outside of the properties available by default when spawning the prim. + """ + + mass_props: schemas.MassPropertiesCfg | None = None + """Mass properties.""" + + deformable_props: DeformableBodyPropertiesCfg | None = None + """Deformable body properties. Only supported on PhysX backend for now.""" From 86090adc8d3e422cd420bab0ae29a2938d4c2571 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 09:57:05 +0100 Subject: [PATCH 27/73] Style: Clean up prefix selection in defromable body configs --- .../isaaclab_physx/sim/schemas/schemas.py | 22 ++-- .../isaaclab_physx/sim/schemas/schemas_cfg.py | 101 ++++++++++++------ 2 files changed, 76 insertions(+), 47 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index e9d995751d5a..36a23dded222 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -20,6 +20,8 @@ from isaaclab.sim.schemas import schemas_cfg +from isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg + # import logger logger = logging.getLogger(__name__) @@ -29,7 +31,7 @@ """ def define_deformable_body_properties( - prim_path: str, cfg: schemas_cfg.DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None + prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None ): """Apply the deformable body schema on the input prim and set its properties. @@ -66,7 +68,7 @@ def define_deformable_body_properties( @apply_nested def modify_deformable_body_properties( - prim_path: str, cfg: schemas_cfg.DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None + prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None ): """Modify PhysX parameters for a deformable body prim. @@ -145,18 +147,12 @@ def modify_deformable_body_properties( # convert to dict cfg = cfg.to_dict() # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) - for attr_name, value in cfg.items(): - if attr_name in ["rest_offset", "contact_offset"]: - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxCollision:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - elif attr_name in ["kinematic_enabled", "deformable_body_enabled"]: - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - else: + # prefixes for each attribute + property_prefixes = cfg["_property_prefix"] + for prefix, attr_list in property_prefixes.items(): + for attr_name in attr_list: safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxDeformableBody:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + deformable_body_prim, f"{prefix}:{to_camel_case(attr_name, 'cC')}", cfg[attr_name], camel_case=False ) # success diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py index 188ba9589749..1a579b46cf4a 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -5,33 +5,19 @@ from __future__ import annotations +import dataclasses + from isaaclab.utils import configclass @configclass -class DeformableBodyPropertiesCfg: - """Properties to apply to a deformable body. - - A deformable body is a body that can deform under forces. The configuration allows users to specify - the properties of the deformable body, such as the solver iteration counts, damping, and self-collision. - - An FEM-based deformable body is created by providing a collision mesh and simulation mesh. The collision mesh - is used for collision detection and the simulation mesh is used for simulation. The collision mesh is usually - a simplified version of the simulation mesh. +class OmniPhysicsPropertiesCfg: + """OmniPhysics properties for a deformable body. - Based on the above, the PhysX team provides APIs to either set the simulation and collision mesh directly - (by specifying the points) or to simplify the collision mesh based on the simulation mesh. The simplification - process involves remeshing the collision mesh and simplifying it based on the target triangle count. - - Since specifying the collision mesh points directly is not a common use case, we only expose the parameters - to simplify the collision mesh based on the simulation mesh. If you want to provide the collision mesh points, - please open an issue on the repository and we can add support for it. - - See :meth:`modify_deformable_body_properties` for more information. + These properties are set with the prefix ``omniphysics:``. For example, to set the mass of the + deformable body, you would set the property ``omniphysics:mass``. - .. note:: - If the values are :obj:`None`, they are not modified. This is useful when you want to set only a subset of - the properties and leave the rest as-is. + See the OmniPhysics documentation for more information on the available properties. """ deformable_body_enabled: bool | None = None @@ -44,6 +30,20 @@ class DeformableBodyPropertiesCfg: please refer to the `documentation `__. """ + +@configclass +class PhysXDeformableBodyPropertiesCfg: + """PhysX-specific properties for a deformable body. + + These properties are set with the prefix ``physxDeformableBody:``. For example, to set the rest + offset of the deformable body, you would set the property ``physxDeformableBody:restOffset``. + + See the PhysX documentation for more information on the available properties. + """ + + solver_position_iteration_count: int | None = None + """Number of the solver positional iterations per step. Range is [1,255]""" + self_collision: bool | None = None """Whether to enable or disable self-collisions for the deformable body based on the rest position distances.""" @@ -64,8 +64,6 @@ class DeformableBodyPropertiesCfg: sleep_threshold: float | None = None """The velocity threshold (in m/s) under which the vertex becomes a candidate for sleeping in the next step.""" - solver_position_iteration_count: int | None = None - """Number of the solver positional iterations per step. Range is [1,255]""" vertex_velocity_damping: float | None = None """Coefficient for artificial damping on the vertex velocity. @@ -73,15 +71,6 @@ class DeformableBodyPropertiesCfg: This parameter can be used to approximate the effect of air drag on the deformable body. """ - simulation_hexahedral_resolution: int = 10 - """The target resolution for the hexahedral mesh used for simulation. Defaults to 10. - - Note: - This value is ignored if the user provides the simulation mesh points directly. However, we assume that - most users will not provide the simulation mesh points directly. If you want to provide the simulation mesh - directly, please set this value to :obj:`None`. - """ - collision_simplification: bool = True """Whether or not to simplify the collision mesh before creating a soft body out of it. Defaults to True. @@ -121,6 +110,20 @@ class DeformableBodyPropertiesCfg: This parameter is ignored if :attr:`collision_simplification` is False. """ + max_depenetration_velocity: float | None = None + """Maximum depenetration velocity permitted to be introduced by the solver (in m/s).""" + + +@configclass +class PhysXCollisionPropertiesCfg: + """PhysX-specific collision properties for a deformable body. + + These properties are set with the prefix ``physxCollision:``. For example, to set the rest offset + of the deformable body's collision mesh, you would set the property ``physxCollision:restOffset``. + + See the PhysX documentation for more information on the available properties. + """ + contact_offset: float | None = None """Contact offset for the collision shape (in m). @@ -137,5 +140,35 @@ class DeformableBodyPropertiesCfg: offset, the shapes will be separated at rest by an air gap. """ - max_depenetration_velocity: float | None = None - """Maximum depenetration velocity permitted to be introduced by the solver (in m/s).""" +@configclass +class DeformableBodyPropertiesCfg(OmniPhysicsPropertiesCfg, PhysXDeformableBodyPropertiesCfg, PhysXCollisionPropertiesCfg): + """Properties to apply to a deformable body. + + A deformable body is a body that can deform under forces. The configuration allows users to specify + the properties of the deformable body, such as the solver iteration counts, damping, and self-collision. + + An FEM-based deformable body is created by providing a collision mesh and simulation mesh. The collision mesh + is used for collision detection and the simulation mesh is used for simulation. The collision mesh is usually + a simplified version of the simulation mesh. + + Based on the above, the PhysX team provides APIs to either set the simulation and collision mesh directly + (by specifying the points) or to simplify the collision mesh based on the simulation mesh. The simplification + process involves remeshing the collision mesh and simplifying it based on the target triangle count. + + Since specifying the collision mesh points directly is not a common use case, we only expose the parameters + to simplify the collision mesh based on the simulation mesh. If you want to provide the collision mesh points, + please open an issue on the repository and we can add support for it. + + See :meth:`modify_deformable_body_properties` for more information. + + .. note:: + If the values are :obj:`None`, they are not modified. This is useful when you want to set only a subset of + the properties and leave the rest as-is. + """ + + _property_prefix: dict[str, list[str]] = { + "omniphysics": [field.name for field in dataclasses.fields(OmniPhysicsPropertiesCfg)], + "physxDeformableBody": [field.name for field in dataclasses.fields(PhysXDeformableBodyPropertiesCfg)], + "physxCollision": [field.name for field in dataclasses.fields(PhysXCollisionPropertiesCfg)], + } + """Mapping between the property prefixes and the properties that fall under each prefix.""" From 6c2eb334de8f673815a44d127dc0d2920ac77919 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 10:44:31 +0100 Subject: [PATCH 28/73] Style: Cleaner way of setting prefix for physics materials --- .../spawners/materials/physics_materials.py | 21 ++---- .../materials/physics_materials_cfg.py | 69 +++++++++++++++---- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py index a2455d780c3a..78920cca9f8d 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py @@ -69,23 +69,12 @@ def spawn_deformable_body_material(prim_path: str, cfg: physics_materials_cfg.De # convert to dict cfg = cfg.to_dict() del cfg["func"] - # set base attributes into OmniPhysics API (prim attributes: omniphysics:*) - for attr_name in ["static_friction", "dynamic_friction", "density", "youngs_modulus", "poissons_ratio"]: - value = cfg.pop(attr_name, None) - safe_set_attribute_on_usd_prim( - prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) - # set surface deformable body material attributes into OmniPhysics API (prim attributes: omniphysics:*) - if is_surface_deformable: - for attr_name in ["surface_thickness", "surface_stretch_stiffness", "surface_shear_stiffness", "surface_bend_stiffness"]: - value = cfg.pop(attr_name, None) + # set into PhysX API, gather prefixes for each attribute + property_prefixes = cfg["_property_prefix"] + for prefix, attr_list in property_prefixes.items(): + for attr_name in attr_list: safe_set_attribute_on_usd_prim( - prim, f"omniphysics:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + prim, f"{prefix}:{to_camel_case(attr_name, 'cC')}", cfg[attr_name], camel_case=False ) - # set extras into PhysX API (prim attributes: physxDeformableMaterial:*) - for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_prim( - prim, f"physxDeformableMaterial:{to_camel_case(attr_name, 'cC')}", value, camel_case=False - ) # return the prim return prim diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py index 4a164a5cebc8..d85e8d7b91eb 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -5,6 +5,7 @@ from __future__ import annotations +import dataclasses from collections.abc import Callable from isaaclab.utils import configclass @@ -12,15 +13,15 @@ @configclass -class DeformableBodyMaterialCfg(PhysicsMaterialCfg): - """Physics material parameters for deformable bodies. +class OmniPhysicsDeformableMaterialCfg: + """OmniPhysics material properties for a deformable body. - See :meth:`spawn_deformable_body_material` for more information. + These properties are set with the prefix ``omniphysics:``. For example, to set the density of the + deformable body, you would set the property ``omniphysics:density``. + See the OmniPhysics documentation for more information on the available properties. """ - func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" - density: float | None = None """The material density. Defaults to None, in which case the simulation decides the default density.""" @@ -44,20 +45,17 @@ class DeformableBodyMaterialCfg(PhysicsMaterialCfg): material incompressible. """ - elasticity_damping: float = 0.005 - """The elasticity damping for the deformable material. Defaults to 0.005.""" - @configclass -class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): - """Physics material parameters for surface deformable bodies, extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. +class OmniPhysicsSurfaceDeformableMaterialCfg(OmniPhysicsDeformableMaterialCfg): + """OmniPhysics material properties for a surface deformable body, extending on :class:`OmniPhysicsDeformableMaterialCfg` with additional parameters for surface deformable bodies. - See :meth:`spawn_deformable_body_material` for more information. + These properties are set with the prefix ``omniphysics:``. For example, to set the surface thickness of the + surface deformable body, you would set the property ``omniphysics:surfaceThickness``. + See the OmniPhysics documentation for more information on the available properties. """ - func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" - surface_thickness: float = 0.01 """The thickness of the deformable body's surface. Defaults to 0.01 meters (m).""" @@ -73,3 +71,48 @@ class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg): bend_damping: float = 0.0 """The bend damping for the deformable body's surface. Defaults to 0.0.""" + +@configclass +class PhysXDeformableMaterialCfg: + """PhysX-specific material properties for a deformable body. + + These properties are set with the prefix ``physxDeformableBody:``. For example, to set the elasticity damping of the + deformable body, you would set the property ``physxDeformableBody:elasticityDamping``. + + See the PhysX documentation for more information on the available properties. + """ + + elasticity_damping: float = 0.005 + """The elasticity damping for the deformable material. Defaults to 0.005.""" + + +@configclass +class DeformableBodyMaterialCfg(PhysicsMaterialCfg, OmniPhysicsDeformableMaterialCfg, PhysXDeformableMaterialCfg): + """Physics material parameters for deformable bodies. + + See :meth:`spawn_deformable_body_material` for more information. + """ + + func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" + + _property_prefix: dict[str, list[str]] = { + "omniphysics": [field.name for field in dataclasses.fields(OmniPhysicsDeformableMaterialCfg)], + "physxDeformableBody": [field.name for field in dataclasses.fields(PhysXDeformableMaterialCfg)], + } + """Mapping between the property prefixes and the properties that fall under each prefix.""" + + +@configclass +class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg, OmniPhysicsSurfaceDeformableMaterialCfg): + """Physics material parameters for surface deformable bodies, extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. + + See :meth:`spawn_deformable_body_material` for more information. + """ + + func: Callable | str = "{DIR}.physics_materials:spawn_deformable_body_material" + + _property_prefix: dict[str, list[str]] = { + "omniphysics": [field.name for field in dataclasses.fields(OmniPhysicsSurfaceDeformableMaterialCfg)], + "physxDeformableBody": [field.name for field in dataclasses.fields(PhysXDeformableMaterialCfg)], + } + """Extend DeformableBodyMaterialCfg properties under each prefix.""" \ No newline at end of file From a16294c94e2fc4435e0b7eaa1425c30e87413e0f Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 11:28:31 +0100 Subject: [PATCH 29/73] Doc: schemas well documented --- .../isaaclab_physx/sim/schemas/schemas_cfg.py | 115 ++++++------------ 1 file changed, 40 insertions(+), 75 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py index 1a579b46cf4a..77721c405a60 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -24,102 +24,75 @@ class OmniPhysicsPropertiesCfg: """Enables deformable body.""" kinematic_enabled: bool = False - """Enables kinematic body. Defaults to False, which means that the body is not kinematic. - - Similar to rigid bodies, this allows setting user-driven motion for the deformable body. For more information, - please refer to the `documentation `__. - """ + """Enables kinematic body. Defaults to False, which means that the body is not kinematic.""" @configclass class PhysXDeformableBodyPropertiesCfg: """PhysX-specific properties for a deformable body. - These properties are set with the prefix ``physxDeformableBody:``. For example, to set the rest - offset of the deformable body, you would set the property ``physxDeformableBody:restOffset``. + These properties are set with the prefix ``physxDeformableBody:`` - See the PhysX documentation for more information on the available properties. + For more information on the available properties, please refer to the `documentation `_. """ - solver_position_iteration_count: int | None = None - """Number of the solver positional iterations per step. Range is [1,255]""" - - self_collision: bool | None = None - """Whether to enable or disable self-collisions for the deformable body based on the rest position distances.""" + solver_position_iteration_count: int = 16 + """Number of the solver positional iterations per step. Range is [1,255], default to 16.""" - self_collision_filter_distance: float | None = None - """Penetration value that needs to get exceeded before contacts for self collision are generated. + linear_damping: float | None = None + """Linear damping coefficient, in units of 1/seconds and constrainted to the range [0, inf).""" - This parameter must be greater than of equal to twice the :attr:`rest_offset` value. + max_linear_velocity: float | None = None + """Maximum allowable linear velocity for the deformable body, in units of distance/second and constrained to the range [0, inf). A negative value allows the simulation to choose suitable a per vertex value dynamically, currently only supported for surface deformables. This can help prevent surface-surface intersections.""" - This value has an effect only if :attr:`self_collision` is enabled. - """ + settling_damping: float | None = None + """Additional damping applied when a vertex's velocity falls below :attr:`settlingThreshold`. Specified in units of 1/seconds and constrained to the range [0, inf).""" settling_threshold: float | None = None - """Threshold vertex velocity (in m/s) under which sleep damping is applied in addition to velocity damping.""" - - sleep_damping: float | None = None - """Coefficient for the additional damping term if fertex velocity drops below setting threshold.""" + """Velocity threshold below which :attr:`settlingDamping` is applied in addition to standard damping. Specified in units of distance/second and constrained to the range [0, inf).""" sleep_threshold: float | None = None - """The velocity threshold (in m/s) under which the vertex becomes a candidate for sleeping in the next step.""" + """Velocity threshold below which a vertex becomes a candidate for sleeping. Specified in units of distance/seconds and constraint to the range [0, inf).""" + max_depenetration_velocity: float | None = None + """Maximum velocity that the solver may apply to resolve intersections. Specified in units of distance/seconds and constraint to the range [0, inf).""" - vertex_velocity_damping: float | None = None - """Coefficient for artificial damping on the vertex velocity. - - This parameter can be used to approximate the effect of air drag on the deformable body. - """ - - collision_simplification: bool = True - """Whether or not to simplify the collision mesh before creating a soft body out of it. Defaults to True. - - Note: - This flag is ignored if the user provides the simulation mesh points directly. However, we assume that - most users will not provide the simulation mesh points directly. Hence, this flag is enabled by default. - - If you want to provide the simulation mesh points directly, please set this flag to False. - """ - - collision_simplification_remeshing: bool = True - """Whether or not the collision mesh should be remeshed before simplification. Defaults to True. + self_collision: bool | None = None + """Enables self-collisions for the deformable body, preventing self-intersections.""" - This parameter is ignored if :attr:`collision_simplification` is False. + self_collision_filter_distance: float | None = None + """Distance below which self-collision is disabled. The default value of -inf indicates that the simulation selects a suitable value. Specified in units of distance and constraint to the range [:attr:`rest_offset` * 2, inf]. """ - collision_simplification_remeshing_resolution: int = 0 - """The resolution used for remeshing. Defaults to 0, which means that a heuristic is used to determine the - resolution. - - This parameter is ignored if :attr:`collision_simplification_remeshing` is False. - """ + enable_speculative_c_c_d: bool | None = None + """Enables dynamic adjustment of the contact offset based on velocity (speculative continuous colision detection).""" - collision_simplification_target_triangle_count: int = 0 - """The target triangle count used for the simplification. Defaults to 0, which means that a heuristic based on - the :attr:`simulation_hexahedral_resolution` is used to determine the target count. + disable_gravity: bool | None = None + """Disables gravity for the deformable body.""" - This parameter is ignored if :attr:`collision_simplification` is False. + # specific to surface deformables + collision_pair_update_frequency: int | None = None + """Determines how often surface-to-surface collision pairs are updated during each time step. Increasing this value results in more frequent updates to the contact pairs, which provides better contact points. + + For example, a value of 2 means collision pairs are updated twice per time step: once at the beginning and once in the middle of the time step (i.e., during the middle solver iteration). If set to 0, the solver adaptively determines when to update the surface-to-surface contact pairs, instead of using a fixed frequency. + + Valid range: [1, :attr:`solver_position_iteration_count`]. """ - collision_simplification_force_conforming: bool = True - """Whether or not the simplification should force the output mesh to conform to the input mesh. Defaults to True. - - The flag indicates that the tretrahedralizer used to generate the collision mesh should produce tetrahedra - that conform to the triangle mesh. If False, the simplifier uses the output from the tretrahedralizer used. - - This parameter is ignored if :attr:`collision_simplification` is False. + collision_iteration_multiplier: float | None = None + """Determines how many collision subiterations are used in each solver iteration. By default, collision constraints are applied once per solver iteration. Increasing this value applies collision constraints more frequently within each solver iteration. + + For example, a value of 2 means collision constraints are applied twice per solver iteration (i.e., collision constraints are applied 2 x :attr:`solver_position_iteration_count` times per time step). Increasing this value does not update collision pairs more frequently; refer to :attr:`collision_pair_update_frequency` for that. + + Valid range: [1, :attr:`solver_position_iteration_count` / 2]. """ - max_depenetration_velocity: float | None = None - """Maximum depenetration velocity permitted to be introduced by the solver (in m/s).""" - @configclass class PhysXCollisionPropertiesCfg: """PhysX-specific collision properties for a deformable body. - These properties are set with the prefix ``physxCollision:``. For example, to set the rest offset - of the deformable body's collision mesh, you would set the property ``physxCollision:restOffset``. + These properties are set with the prefix ``physxCollision:``. See the PhysX documentation for more information on the available properties. """ @@ -140,24 +113,16 @@ class PhysXCollisionPropertiesCfg: offset, the shapes will be separated at rest by an air gap. """ + @configclass class DeformableBodyPropertiesCfg(OmniPhysicsPropertiesCfg, PhysXDeformableBodyPropertiesCfg, PhysXCollisionPropertiesCfg): """Properties to apply to a deformable body. - A deformable body is a body that can deform under forces. The configuration allows users to specify + A deformable body is a body that can deform under forces, both surface and volume deformables. The configuration allows users to specify the properties of the deformable body, such as the solver iteration counts, damping, and self-collision. An FEM-based deformable body is created by providing a collision mesh and simulation mesh. The collision mesh - is used for collision detection and the simulation mesh is used for simulation. The collision mesh is usually - a simplified version of the simulation mesh. - - Based on the above, the PhysX team provides APIs to either set the simulation and collision mesh directly - (by specifying the points) or to simplify the collision mesh based on the simulation mesh. The simplification - process involves remeshing the collision mesh and simplifying it based on the target triangle count. - - Since specifying the collision mesh points directly is not a common use case, we only expose the parameters - to simplify the collision mesh based on the simulation mesh. If you want to provide the collision mesh points, - please open an issue on the repository and we can add support for it. + is used for collision detection and the simulation mesh is used for simulation. See :meth:`modify_deformable_body_properties` for more information. From d3780897b6ec88a3262d4e46ba2552a9cf9e691e Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 11:38:53 +0100 Subject: [PATCH 30/73] Docs: Clean up schemas description --- .../isaaclab_physx/sim/schemas/schemas.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 36a23dded222..5abe81dbdb3a 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -72,12 +72,12 @@ def modify_deformable_body_properties( ): """Modify PhysX parameters for a deformable body prim. - A `deformable body`_ is a single body that can be simulated by PhysX. Unlike rigid bodies, deformable bodies + A `deformable body`_ is a single body (either surface or volume deformable) that can be simulated by PhysX. Unlike rigid bodies, deformable bodies support relative motion of the nodes in the mesh. Consequently, they can be used to simulate deformations under applied forces. - PhysX soft body simulation employs Finite Element Analysis (FEA) to simulate the deformations of the mesh. - It uses two tetrahedral meshes to represent the deformable body: + PhysX deformable body simulation employs Finite Element Analysis (FEA) to simulate the deformations of the mesh. + It uses two meshes to represent the deformable body: 1. **Simulation mesh**: This mesh is used for the simulation and is the one that is deformed by the solver. 2. **Collision mesh**: This mesh only needs to match the surface of the simulation mesh and is used for @@ -85,21 +85,18 @@ def modify_deformable_body_properties( For most applications, we assume that the above two meshes are computed from the "render mesh" of the deformable body. The render mesh is the mesh that is visible in the scene and is used for rendering purposes. It is composed - of triangles and is the one that is used to compute the above meshes based on PhysX cookings. - - The schema comprises of attributes that belong to the `PhysxDeformableBodyAPI`_. schemas containing the PhysX - parameters for the deformable body. + of triangles, while the simulation mesh is composed of tetrahedrons for volume deformables, and triangles for surface deformables. .. caution:: The deformable body schema is still under development by the Omniverse team. The current implementation - works with the PhysX schemas shipped with Isaac Sim 4.0.0 onwards. It may change in future releases. + works with the PhysX schemas shipped with Isaac Sim 6.0.0 onwards. It may change in future releases. .. note:: This function is decorated with :func:`apply_nested` that sets the properties to all the prims (that have the schema applied on them) under the input prim path. - .. _deformable body: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html - .. _PhysxDeformableBodyAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_deformable_a_p_i.html + .. _deformable body: https://nvidia-omniverse.github.io/PhysX/physx/5.6.1/docs/DeformableVolume.html + .. _PhysxDeformableBodyAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/physxschema/annotated.html Args: prim_path: The prim path to the deformable body. @@ -154,6 +151,5 @@ def modify_deformable_body_properties( safe_set_attribute_on_usd_prim( deformable_body_prim, f"{prefix}:{to_camel_case(attr_name, 'cC')}", cfg[attr_name], camel_case=False ) - # success return True From fc07d0e2e5c4382040203e287d1c388a7be608d0 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 13:56:41 +0100 Subject: [PATCH 31/73] Feat: Allow deformable properties set on Xform root of mesh --- .../isaaclab/sim/spawners/meshes/meshes.py | 16 ++++++-------- .../isaaclab_physx/sim/schemas/schemas.py | 22 ++++++++++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 2d8467866483..db76678c9fea 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -18,7 +18,7 @@ from ..materials import RigidBodyMaterialCfg -# TODO: PhysX dependency +# deformables only supported on PhysX backend from isaaclab_physx.sim import schemas as schemas_physx from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg @@ -397,7 +397,7 @@ def _spawn_mesh_geom_from_mesh( from omni.physx.scripts import deformableUtils # create all the paths we need for clarity, we use the same mesh for simulation and collision mesh_prim_path = geom_prim_path + "/tetmesh" - render_mesh_prim_path = mesh_prim_path + "/rendermesh" + render_mesh_prim_path = mesh_prim_path + "/mesh" # tetrahedralize surface mesh tet_mesh_points, tet_mesh_indices = deformableUtils.compute_conforming_tetrahedral_mesh(mesh.vertices, mesh.faces.flatten()) @@ -428,14 +428,12 @@ def _spawn_mesh_geom_from_mesh( stage=stage, ) - # note: in case of deformable objects, we need to apply the deformable properties to the mesh prim. - # this is different from rigid objects where we apply the properties to the parent prim. if cfg.deformable_props is not None: # apply mass properties if cfg.mass_props is not None: - schemas.define_mass_properties(mesh_prim_path, cfg.mass_props, stage=stage) + schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply deformable body properties - schemas_physx.define_deformable_body_properties(mesh_prim_path, cfg.deformable_props, stage=stage) + schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage=stage) elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh if cfg.__class__.__name__ == "MeshSphereCfg": @@ -455,13 +453,13 @@ def _spawn_mesh_geom_from_mesh( # apply visual material if cfg.visual_material is not None: if not cfg.visual_material_path.startswith("/"): - material_path = f"{geom_prim_path}/{cfg.visual_material_path}" + material_path = f"{prim_path}/{cfg.visual_material_path}" else: material_path = cfg.visual_material_path # create material cfg.visual_material.func(material_path, cfg.visual_material) # apply material - bind_visual_material(mesh_prim_path, material_path, stage=stage) + bind_visual_material(prim_path, material_path, stage=stage) # apply physics material if cfg.physics_material is not None: @@ -472,7 +470,7 @@ def _spawn_mesh_geom_from_mesh( # create material cfg.physics_material.func(material_path, cfg.physics_material) # apply material - bind_physics_material(mesh_prim_path, material_path, stage=stage) + bind_physics_material(prim_path, material_path, stage=stage) # note: we apply the rigid properties to the parent prim in case of rigid objects. if cfg.rigid_props is not None: diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 5abe81dbdb3a..f51ae1bfdaf7 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -15,11 +15,10 @@ from isaaclab.sim.utils import ( apply_nested, + get_all_matching_child_prims, safe_set_attribute_on_usd_prim, ) -from isaaclab.sim.schemas import schemas_cfg - from isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg # import logger @@ -62,8 +61,25 @@ def define_deformable_body_properties( if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") + # traverse the prim and get the mesh. + # Check for existence of TetMesh (volume), if not found check for Mesh (surface). If multiple meshes are found, raise error. + matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "TetMesh") + # check if the volume deformable mesh is valid + if len(matching_prims) == 0: + matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "Mesh") + if len(matching_prims) == 0: + raise ValueError(f"Could not find any tetmesh or mesh in '{prim_path}'. Please check asset.") + if len(matching_prims) > 1: + # get list of all meshes found + mesh_paths = [p.GetPrimPath() for p in matching_prims] + raise ValueError( + f"Found multiple meshes in '{prim_path}': {mesh_paths}." + " Deformable body schema can only be applied to one mesh." + ) + mesh_prim = matching_prims[0] + # set deformable body properties - modify_deformable_body_properties(prim_path, cfg, stage) + modify_deformable_body_properties(mesh_prim.GetPrimPath(), cfg, stage) @apply_nested From e7dadb89b3389478976814af7eb7a860bdef00ec Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 14:14:04 +0100 Subject: [PATCH 32/73] Feat: Allow nested deformables under a root Xform --- .../isaaclab_physx/sim/schemas/schemas.py | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index f51ae1bfdaf7..774e454b8506 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -19,6 +19,7 @@ safe_set_attribute_on_usd_prim, ) +from omni.physx.scripts import deformableUtils from isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg # import logger @@ -76,13 +77,35 @@ def define_deformable_body_properties( f"Found multiple meshes in '{prim_path}': {mesh_paths}." " Deformable body schema can only be applied to one mesh." ) - mesh_prim = matching_prims[0] + deformable_body_prim = matching_prims[0] + deformable_prim_path = deformable_body_prim.GetPrimPath() + + # check if the prim is valid + if not deformable_body_prim.IsValid(): + return False + + # set root prim properties based on the type of the deformable mesh (surface vs volume) + if deformable_body_prim.IsA(UsdGeom.Mesh): + success = deformableUtils.set_physics_surface_deformable_body(stage, deformable_prim_path) + # apply physx extension api + if "PhysxSurfaceDeformableBodyAPI" not in prim.GetAppliedSchemas(): + prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") + elif deformable_body_prim.IsA(UsdGeom.TetMesh): + success = deformableUtils.set_physics_volume_deformable_body(stage, deformable_prim_path) + # apply physx extension api + if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): + prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") + else: + print(f"Unsupported deformable body prim type: '{deformable_body_prim.GetTypeName()}'. Only Mesh and TetMesh are supported.") + success = False + # api failure + if not success: + raise RuntimeError(f"Failed to set deformable body properties on prim '{deformable_prim_path}'.") # set deformable body properties - modify_deformable_body_properties(mesh_prim.GetPrimPath(), cfg, stage) + modify_deformable_body_properties(deformable_prim_path, cfg, stage) -@apply_nested def modify_deformable_body_properties( prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None ): @@ -129,30 +152,10 @@ def modify_deformable_body_properties( # get deformable-body USD prim deformable_body_prim = stage.GetPrimAtPath(prim_path) - # check if the prim is valid if not deformable_body_prim.IsValid(): return False - from omni.physx.scripts import deformableUtils - # set deformable body properties based on the type of the mesh (surface vs volume) - if deformable_body_prim.IsA(UsdGeom.Mesh): - success = deformableUtils.set_physics_surface_deformable_body(stage, prim_path) - # apply physx extension api - if "PhysxSurfaceDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - deformable_body_prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") - elif deformable_body_prim.IsA(UsdGeom.TetMesh): - success = deformableUtils.set_physics_volume_deformable_body(stage, prim_path) - # apply physx extension api - if "PhysxBaseDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") - else: - print(f"Unsupported deformable body prim type: '{deformable_body_prim.GetTypeName()}'. Only Mesh and TetMesh are supported.") - success = False - # api failure - if not success: - return False - # ensure PhysX collision API is applied on the collision mesh if "PhysxCollisionAPI" not in deformable_body_prim.GetAppliedSchemas(): deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") From 653ed5b29cec789e2f8e9d5c6bd739e4ab355b1b Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 15:03:01 +0100 Subject: [PATCH 33/73] Feat: Use create auto instead for simplified workflow --- .../isaaclab/sim/spawners/meshes/meshes.py | 66 ++++--------------- .../isaaclab_physx/sim/schemas/schemas.py | 62 ++++++++++------- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index db76678c9fea..a0616276c129 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -374,66 +374,28 @@ def _spawn_mesh_geom_from_mesh( # create all the paths we need for clarity geom_prim_path = prim_path + "/geometry" + mesh_prim_path = geom_prim_path + "/mesh" # create the mesh prim - if cfg.deformable_props is None or isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg): - # non-deformables and surface deformables use UsdGeom.Mesh - mesh_prim_path = geom_prim_path + "/mesh" - - mesh_prim = create_prim( - mesh_prim_path, - prim_type="Mesh", - scale=scale, - attributes={ - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", - }, - stage=stage, - ) - else: - # volume deformables have both a triangle surface UsdGeom.Mesh for visualization and a tetrahedral UsdGeom.TetMesh for simulation - from omni.physx.scripts import deformableUtils - # create all the paths we need for clarity, we use the same mesh for simulation and collision - mesh_prim_path = geom_prim_path + "/tetmesh" - render_mesh_prim_path = mesh_prim_path + "/mesh" - - # tetrahedralize surface mesh - tet_mesh_points, tet_mesh_indices = deformableUtils.compute_conforming_tetrahedral_mesh(mesh.vertices, mesh.faces.flatten()) - - # create simulation tetmesh prim - mesh_prim = create_prim( - mesh_prim_path, - prim_type="TetMesh", - scale=scale, - attributes={ - "points": tet_mesh_points, - "tetVertexIndices": np.asarray(tet_mesh_indices).reshape(-1, 4), - }, - stage=stage, - ) - - # create visualization mesh prim - render_mesh_prim = create_prim( - render_mesh_prim_path, - prim_type="Mesh", - scale=scale, - attributes={ - "points": mesh.vertices, - "faceVertexIndices": mesh.faces.flatten(), - "faceVertexCounts": np.asarray([3] * len(mesh.faces)), - "subdivisionScheme": "bilinear", - }, - stage=stage, - ) + mesh_prim = create_prim( + mesh_prim_path, + prim_type="Mesh", + scale=scale, + attributes={ + "points": mesh.vertices, + "faceVertexIndices": mesh.faces.flatten(), + "faceVertexCounts": np.asarray([3] * len(mesh.faces)), + "subdivisionScheme": "bilinear", + }, + stage=stage, + ) if cfg.deformable_props is not None: # apply mass properties if cfg.mass_props is not None: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply deformable body properties - schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage=stage) + schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage=stage, deformable_type="surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume") elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh if cfg.__class__.__name__ == "MeshSphereCfg": diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 774e454b8506..2d1990569318 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -31,7 +31,7 @@ """ def define_deformable_body_properties( - prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None + prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None, deformable_type: str = "volume" ): """Apply the deformable body schema on the input prim and set its properties. @@ -47,10 +47,13 @@ def define_deformable_body_properties( cfg: The configuration for the deformable body. stage: The stage where to find the prim. Defaults to None, in which case the current stage is used. + deformable_type: The type of the deformable body (surface or volume). + This is used to determine which PhysX API to use for the deformable body. Defaults to "volume". Raises: ValueError: When the prim path is not valid. ValueError: When the prim has no mesh or multiple meshes. + RuntimeError: When setting the deformable body properties fails. """ # get stage handle if stage is None: @@ -62,14 +65,11 @@ def define_deformable_body_properties( if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") - # traverse the prim and get the mesh. - # Check for existence of TetMesh (volume), if not found check for Mesh (surface). If multiple meshes are found, raise error. - matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "TetMesh") + # traverse the prim and get the mesh. If none or multiple meshes are found, raise error. + matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "Mesh") # check if the volume deformable mesh is valid if len(matching_prims) == 0: - matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "Mesh") - if len(matching_prims) == 0: - raise ValueError(f"Could not find any tetmesh or mesh in '{prim_path}'. Please check asset.") + raise ValueError(f"Could not find any tetmesh or mesh in '{prim_path}'. Please check asset.") if len(matching_prims) > 1: # get list of all meshes found mesh_paths = [p.GetPrimPath() for p in matching_prims] @@ -77,33 +77,47 @@ def define_deformable_body_properties( f"Found multiple meshes in '{prim_path}': {mesh_paths}." " Deformable body schema can only be applied to one mesh." ) - deformable_body_prim = matching_prims[0] - deformable_prim_path = deformable_body_prim.GetPrimPath() + mesh_prim = matching_prims[0] + mesh_prim_path = mesh_prim.GetPrimPath() # check if the prim is valid - if not deformable_body_prim.IsValid(): - return False + if not mesh_prim.IsValid(): + raise ValueError(f"Mesh prim path '{mesh_prim_path}' is not valid.") # set root prim properties based on the type of the deformable mesh (surface vs volume) - if deformable_body_prim.IsA(UsdGeom.Mesh): - success = deformableUtils.set_physics_surface_deformable_body(stage, deformable_prim_path) - # apply physx extension api - if "PhysxSurfaceDeformableBodyAPI" not in prim.GetAppliedSchemas(): - prim.AddAppliedSchema("PhysxSurfaceDeformableBodyAPI") - elif deformable_body_prim.IsA(UsdGeom.TetMesh): - success = deformableUtils.set_physics_volume_deformable_body(stage, deformable_prim_path) - # apply physx extension api - if "PhysxBaseDeformableBodyAPI" not in prim.GetAppliedSchemas(): - prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") + sim_mesh_prim_path = None + if deformable_type == "surface": + sim_mesh_prim_path = prim_path + "/sim_mesh" + success = deformableUtils.create_auto_surface_deformable_hierarchy( + stage=stage, + root_prim_path=prim_path, + simulation_mesh_path=sim_mesh_prim_path, + cooking_src_mesh_path=mesh_prim_path, + cooking_src_simplification_enabled=False, + set_visibility_with_guide_purpose=True + ) + elif deformable_type == "volume": + sim_mesh_prim_path = prim_path + "/sim_tetmesh" + success = deformableUtils.create_auto_volume_deformable_hierarchy( + stage=stage, + root_prim_path=prim_path, + simulation_tetmesh_path=sim_mesh_prim_path, + collision_tetmesh_path=sim_mesh_prim_path, + cooking_src_mesh_path=mesh_prim_path, + simulation_hex_mesh_enabled=False, + cooking_src_simplification_enabled=False, + set_visibility_with_guide_purpose=True + ) else: - print(f"Unsupported deformable body prim type: '{deformable_body_prim.GetTypeName()}'. Only Mesh and TetMesh are supported.") + print(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") success = False + # api failure if not success: - raise RuntimeError(f"Failed to set deformable body properties on prim '{deformable_prim_path}'.") + raise RuntimeError(f"Failed to set deformable body properties on prim '{mesh_prim_path}'.") # set deformable body properties - modify_deformable_body_properties(deformable_prim_path, cfg, stage) + modify_deformable_body_properties(sim_mesh_prim_path, cfg, stage) def modify_deformable_body_properties( From f9a65331c51376a0c3a9dc145ed7eac8e09493b5 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 15:15:05 +0100 Subject: [PATCH 34/73] Fix: Apply correct API to deformable root prim --- .../isaaclab_physx/sim/schemas/schemas.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 2d1990569318..301777b7356a 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -117,9 +117,10 @@ def define_deformable_body_properties( raise RuntimeError(f"Failed to set deformable body properties on prim '{mesh_prim_path}'.") # set deformable body properties - modify_deformable_body_properties(sim_mesh_prim_path, cfg, stage) + modify_deformable_body_properties(prim_path, cfg, stage) +@apply_nested def modify_deformable_body_properties( prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None ): @@ -169,6 +170,13 @@ def modify_deformable_body_properties( # check if the prim is valid if not deformable_body_prim.IsValid(): return False + # check if deformable body API is applied + if "OmniPhysicsBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + return False + + # apply customization to deformable API + if "PhysxBaseDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") # ensure PhysX collision API is applied on the collision mesh if "PhysxCollisionAPI" not in deformable_body_prim.GetAppliedSchemas(): From 909b3c8888a2ce81201dabef6a60297aa336caa3 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 15:31:09 +0100 Subject: [PATCH 35/73] Feat: Load USD file with deformable properties --- .../01_assets/run_deformable_object.py | 30 +++++++++++++++++-- .../sim/spawners/from_files/from_files.py | 25 ++++++++++++++-- .../sim/spawners/from_files/from_files_cfg.py | 14 +++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 3597ef0aeca4..c39dd268d90d 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -74,6 +74,7 @@ from isaaclab.sim import SimulationContext from isaaclab.sensors.camera import Camera, CameraCfg from isaaclab.utils import convert_dict_to_backend +from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR # deformables supported in PhysX from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg @@ -121,7 +122,7 @@ def design_scene(): prim_path="/World/Origin.*/Cube", spawn=sim_utils.MeshCuboidCfg( size=(0.2, 0.2, 0.2), - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0, contact_offset=0.001), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), @@ -139,7 +140,7 @@ def design_scene(): spawn=sim_utils.MeshSquareCfg( size=1.5, resolution=(21, 21), - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.01, contact_offset=0.02), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), physics_material=SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), @@ -147,6 +148,31 @@ def design_scene(): cloth_object = DeformableObject(cfg=cfg) scene_entities["cloth_object"] = cloth_object + # USD Teddy Bear + sim_utils.create_prim(f"/World/OriginTeddy", "Xform", translation=[0,0,2.0]) + cfg = DeformableObjectCfg( + prim_path="/World/OriginTeddy/Teddy", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", + # usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Mug/mug.usd", + deformable_props=DeformableBodyPropertiesCfg(), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.6, 0.35, 0.15)), + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5) + ) + ) + teddy_object = DeformableObject(cfg=cfg) + scene_entities["teddy_object"] = teddy_object + + from pxr import Usd + stage = sim_utils.get_current_stage() + root = stage.GetPrimAtPath("/World/OriginTeddy") + for prim in Usd.PrimRange(root): + print(prim.GetPath(), prim.GetTypeName()) + + breakpoint() + + + # Sensors if args_cli.save: camera = define_sensor() diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 64ed4de2ef7f..e949f96b0c47 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -30,6 +30,10 @@ from isaaclab.utils.assets import check_file_path, retrieve_file_path from isaaclab.utils.version import has_kit +# deformables only supported on PhysX backend +from isaaclab_physx.sim import schemas as schemas_physx +from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg + if TYPE_CHECKING: from . import from_files_cfg @@ -363,10 +367,14 @@ def _spawn_from_usd_file( if cfg.joint_drive_props is not None: schemas.modify_joint_drive_properties(prim_path, cfg.joint_drive_props) - # modify deformable body properties + # define deformable body properties, or modify if deformable body API is present (PhysX only) if cfg.deformable_props is not None: - # schemas.modify_deformable_body_properties(prim_path, cfg.deformable_props) #TODO - raise NotImplementedError("Deformable body properties modification is not implemented yet.") + prim = stage.GetPrimAtPath(prim_path) + deformable_type = "surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume" + if "OmniPhysicsBodyAPI" in prim.GetAppliedSchemas(): + schemas_physx.modify_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) + else: + schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) # apply visual material if cfg.visual_material is not None: @@ -382,6 +390,17 @@ def _spawn_from_usd_file( # apply material bind_visual_material(prim_path, material_path, stage=stage) + # apply physics material + if cfg.physics_material is not None: + if not cfg.physics_material_path.startswith("/"): + material_path = f"{prim_path}/{cfg.physics_material_path}" + else: + material_path = cfg.physics_material_path + # create material + cfg.physics_material.func(material_path, cfg.physics_material) + # apply material + bind_physics_material(prim_path, material_path, stage=stage) + # return the prim return stage.GetPrimAtPath(prim_path) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py index 343911910690..71153ac1424d 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py @@ -70,6 +70,20 @@ class FileCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): If None, then no visual material will be added. """ + physics_material_path: str = "material" + """Path to the physics material to use for the prim. Defaults to "material". + + If the path is relative, then it will be relative to the prim's path. + This parameter is ignored if `physics_material` is not None. + """ + + physics_material: materials.PhysicsMaterialCfg | None = None + """Physics material properties. + + Note: + If None, then no physics material will be added. + """ + @configclass class UsdFileCfg(FileCfg): From f7bf4ed5532c6fe972461cdce271f92240342c8b Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 16:08:26 +0100 Subject: [PATCH 36/73] Test: Working example with imported USD surface mesh asset --- .../tutorials/01_assets/run_deformable_object.py | 16 +++------------- .../sim/spawners/from_files/from_files.py | 3 +++ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index c39dd268d90d..75feca03c4d5 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -149,30 +149,20 @@ def design_scene(): scene_entities["cloth_object"] = cloth_object # USD Teddy Bear - sim_utils.create_prim(f"/World/OriginTeddy", "Xform", translation=[0,0,2.0]) + sim_utils.create_prim(f"/World/OriginTeddy", "Xform", translation=[0.1,-0.25,2.0]) cfg = DeformableObjectCfg( prim_path="/World/OriginTeddy/Teddy", spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", - # usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Mug/mug.usd", deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.6, 0.35, 0.15)), - physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5) + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e8), + scale=[0.05, 0.05, 0.05] ) ) teddy_object = DeformableObject(cfg=cfg) scene_entities["teddy_object"] = teddy_object - from pxr import Usd - stage = sim_utils.get_current_stage() - root = stage.GetPrimAtPath("/World/OriginTeddy") - for prim in Usd.PrimRange(root): - print(prim.GetPath(), prim.GetTypeName()) - - breakpoint() - - - # Sensors if args_cli.save: camera = define_sensor() diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index e949f96b0c47..6b87962673b5 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -374,6 +374,9 @@ def _spawn_from_usd_file( if "OmniPhysicsBodyAPI" in prim.GetAppliedSchemas(): schemas_physx.modify_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) else: + # clear existing schemas + for schema in prim.GetAppliedSchemas(): + prim.RemoveAppliedSchema(schema) schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) # apply visual material From 9ab8e178377a9baeabad0bff49aa5424f8d28c1f Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 16:48:03 +0100 Subject: [PATCH 37/73] Test: Load Teddy USD with scaling --- .../tutorials/01_assets/run_deformable_object.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 75feca03c4d5..396659ea814c 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -148,20 +148,22 @@ def design_scene(): cloth_object = DeformableObject(cfg=cfg) scene_entities["cloth_object"] = cloth_object - # USD Teddy Bear - sim_utils.create_prim(f"/World/OriginTeddy", "Xform", translation=[0.1,-0.25,2.0]) + # USD File + uniform_scale = 0.50 + sim_utils.create_prim(f"/World/OriginUSD", "Xform", translation=[0.1,-0.25,2.0]) cfg = DeformableObjectCfg( - prim_path="/World/OriginTeddy/Teddy", + prim_path="/World/OriginUSD/Teddy", spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.6, 0.35, 0.15)), - physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e8), - scale=[0.05, 0.05, 0.05] + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e9), + scale=[uniform_scale, uniform_scale, uniform_scale] ) ) - teddy_object = DeformableObject(cfg=cfg) - scene_entities["teddy_object"] = teddy_object + usd_object = DeformableObject(cfg=cfg) + scene_entities["usd_object"] = usd_object + # Sensors if args_cli.save: From 3b25ded632e71da068a433b21096a7225e9d7667 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 17:53:30 +0100 Subject: [PATCH 38/73] Test: future proofing if we want to load tetmesh in usd --- .../isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 301777b7356a..15987450da8f 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -31,7 +31,7 @@ """ def define_deformable_body_properties( - prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None, deformable_type: str = "volume" + prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None, deformable_type: str = "volume", sim_mesh_prim_path: str | None = None ): """Apply the deformable body schema on the input prim and set its properties. @@ -85,9 +85,8 @@ def define_deformable_body_properties( raise ValueError(f"Mesh prim path '{mesh_prim_path}' is not valid.") # set root prim properties based on the type of the deformable mesh (surface vs volume) - sim_mesh_prim_path = None if deformable_type == "surface": - sim_mesh_prim_path = prim_path + "/sim_mesh" + sim_mesh_prim_path = prim_path + "/sim_mesh" if sim_mesh_prim_path is None else sim_mesh_prim_path success = deformableUtils.create_auto_surface_deformable_hierarchy( stage=stage, root_prim_path=prim_path, @@ -97,7 +96,7 @@ def define_deformable_body_properties( set_visibility_with_guide_purpose=True ) elif deformable_type == "volume": - sim_mesh_prim_path = prim_path + "/sim_tetmesh" + sim_mesh_prim_path = prim_path + "/sim_tetmesh" if sim_mesh_prim_path is None else sim_mesh_prim_path success = deformableUtils.create_auto_volume_deformable_hierarchy( stage=stage, root_prim_path=prim_path, From 1556ed8b22e1421d536b70d24d692ad99be38895 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 13 Mar 2026 17:54:06 +0100 Subject: [PATCH 39/73] Test: Change range of parameters in deformable demo to be feasible, add usd and cloth --- scripts/demos/deformables.py | 66 +++++++++++++------ .../sim/spawners/from_files/from_files.py | 3 - 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 5418abea3487..69e17cf5c04f 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -73,10 +73,11 @@ import isaaclab.sim as sim_utils from isaaclab.utils import convert_dict_to_backend from isaaclab.sensors.camera import Camera, CameraCfg +from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR # deformables supported in PhysX from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg +from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg def define_origins(num_origins: int, spacing: float) -> list[list[float]]: @@ -89,7 +90,7 @@ def define_origins(num_origins: int, spacing: float) -> list[list[float]]: xx, yy = torch.meshgrid(torch.arange(num_rows), torch.arange(num_cols), indexing="xy") env_origins[:, 0] = spacing * xx.flatten()[:num_origins] - spacing * (num_rows - 1) / 2 env_origins[:, 1] = spacing * yy.flatten()[:num_origins] - spacing * (num_cols - 1) / 2 - env_origins[:, 2] = torch.rand(num_origins) + 1.0 + env_origins[:, 2] = 2.0 * torch.rand(num_origins) + 0.5 # return the origins return env_origins.tolist() @@ -103,8 +104,8 @@ def define_sensor() -> Camera: camera_cfg = CameraCfg( prim_path="/World/OriginCamera/CameraSensor", update_period=1.0/args_cli.video_fps, - height=600, - width=800, + height=800, + width=1000, data_types=["rgb",], spawn=sim_utils.PinholeCameraCfg( focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) @@ -131,37 +132,51 @@ def design_scene() -> tuple[dict, list[list[float]]]: # spawn a red cone cfg_sphere = sim_utils.MeshSphereCfg( - radius=0.5, - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), + radius=0.4, + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(), ) cfg_cuboid = sim_utils.MeshCuboidCfg( - size=(0.2, 0.2, 0.2), - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), + size=(0.6, 0.6, 0.6), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(), ) cfg_cylinder = sim_utils.MeshCylinderCfg( - radius=0.15, + radius=0.25, height=0.5, - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(), ) cfg_capsule = sim_utils.MeshCapsuleCfg( radius=0.35, height=0.5, - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(), ) cfg_cone = sim_utils.MeshConeCfg( - radius=0.15, - height=0.5, - deformable_props=DeformableBodyPropertiesCfg(rest_offset=0.0), + radius=0.35, + height=0.75, + deformable_props=DeformableBodyPropertiesCfg(), + visual_material=sim_utils.PreviewSurfaceCfg(), + physics_material=DeformableBodyMaterialCfg(), + ) + cfg_cloth = sim_utils.MeshSquareCfg( + size=2.5, + resolution=(21, 21), + deformable_props=DeformableBodyPropertiesCfg(), + visual_material=sim_utils.PreviewSurfaceCfg(), + physics_material=SurfaceDeformableBodyMaterialCfg(), + ) + cfg_usd = sim_utils.UsdFileCfg( + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(), + scale=[0.05, 0.05, 0.05] ) # create a dictionary of all the objects to be spawned objects_cfg = { @@ -170,22 +185,30 @@ def design_scene() -> tuple[dict, list[list[float]]]: "cylinder": cfg_cylinder, "capsule": cfg_capsule, "cone": cfg_cone, + "cloth": cfg_cloth, + "usd": cfg_usd } # Create separate groups of deformable objects - origins = define_origins(num_origins=6, spacing=0.6) + origins = define_origins(num_origins=16, spacing=0.8) print("[INFO]: Spawning objects...") # Iterate over all the origins and randomly spawn objects for idx, origin in tqdm.tqdm(enumerate(origins), total=len(origins)): # randomly select an object to spawn obj_name = random.choice(list(objects_cfg.keys())) obj_cfg = objects_cfg[obj_name] - # randomize the young modulus (somewhere between a Silicone 30 and Silicone 70) - obj_cfg.physics_material.youngs_modulus = random.uniform(0.7e6, 3.3e6) + # randomize the young modulus + obj_cfg.physics_material.youngs_modulus = random.uniform(1e6, 1e8) + # higher mesh resolution causes instability at low stiffness + if obj_name == "sphere" or obj_name == "capsule" or obj_name == "cloth": + obj_cfg.physics_material.youngs_modulus = random.uniform(5e8, 5e9) # randomize the poisson's ratio obj_cfg.physics_material.poissons_ratio = random.uniform(0.25, 0.45) # randomize the color obj_cfg.visual_material.diffuse_color = (random.random(), random.random(), random.random()) + # spawn cloth a bit higher than the rest + if obj_name == "cloth": + origin[2] += 1.5 # spawn the object obj_cfg.func(f"/World/Origin/Object{idx:02d}", obj_cfg, translation=origin) @@ -210,8 +233,6 @@ def design_scene() -> tuple[dict, list[list[float]]]: def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, DeformableObject], origins: torch.Tensor, output_dir: str = "outputs"): """Runs the simulation loop.""" - - objects: DeformableObject = entities["deformable_object"] # Write camera outputs if args_cli.save: camera: Camera = entities["camera"] @@ -223,10 +244,13 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab rgb=True, ) # Camera positions, targets, orientations - camera_positions = torch.tensor([[2.5, 2.5, 2.5]], device=sim.device) - camera_targets = torch.tensor([[0.0, 0.0, 0.25]], device=sim.device) + camera_positions = torch.tensor([[7.0, 7.0, 3.0]], device=sim.device) + camera_targets = torch.tensor([[0.0, 0.0, 0.5]], device=sim.device) camera.set_world_poses_from_view(camera_positions, camera_targets) + + print(f"Solving for {torch.tensor(entities["deformable_object"].root_view.get_simulation_nodal_positions().shape).prod().item():,} Degrees of Freedom.") + # Define simulation stepping sim_dt = sim.get_physics_dt() assert sim_dt <= 1.0 / args_cli.video_fps, "Simulation timestep must be smaller than the inverse of the video FPS to save frames properly." diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 6b87962673b5..e949f96b0c47 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -374,9 +374,6 @@ def _spawn_from_usd_file( if "OmniPhysicsBodyAPI" in prim.GetAppliedSchemas(): schemas_physx.modify_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) else: - # clear existing schemas - for schema in prim.GetAppliedSchemas(): - prim.RemoveAppliedSchema(schema) schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) # apply visual material From 928edc5b9bffde9254bbcaebc40c214d8bcbd39a Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 11:55:19 +0100 Subject: [PATCH 40/73] Style: Clean up deformable tutorial --- .../01_assets/run_deformable_object.py | 162 +----------------- 1 file changed, 7 insertions(+), 155 deletions(-) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 396659ea814c..03ae0bbec073 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -17,7 +17,6 @@ import os import argparse -import subprocess from isaaclab.app import AppLauncher @@ -35,24 +34,10 @@ default=1.0/60, help="Simulation timestep.", ) -parser.add_argument( - "--video_fps", - type=int, - default=60, - help="FPS for the output video if --save is enabled.", -) -parser.add_argument( - "--save", - action="store_true", - default=False, - help="Save the data from camera.", -) # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) # parse the arguments args_cli = parser.parse_args() -if args_cli.save: - args_cli.enable_cameras = True # launch omniverse app app_launcher = AppLauncher(args_cli) @@ -64,39 +49,13 @@ import warp as wp from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -import matplotlib.pyplot as plt -import numpy as np - -import omni.replicator.core as rep - import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils from isaaclab.sim import SimulationContext -from isaaclab.sensors.camera import Camera, CameraCfg -from isaaclab.utils import convert_dict_to_backend -from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR # deformables supported in PhysX from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg - - -def define_sensor() -> Camera: - """Defines the camera sensor to add to the scene.""" - sim_utils.create_prim("/World/OriginCamera", "Xform", translation=[0.0, 0.0, 0.0]) - camera_cfg = CameraCfg( - prim_path="/World/OriginCamera/CameraSensor", - update_period=1.0/args_cli.video_fps, - height=800, - width=800, - data_types=["rgb",], - spawn=sim_utils.PinholeCameraCfg( - focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) - ), - ) - camera = Camera(cfg=camera_cfg) - - return camera +from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg def design_scene(): @@ -133,43 +92,6 @@ def design_scene(): cube_object = DeformableObject(cfg=cfg) scene_entities["cube_object"] = cube_object - # 2D Cloth Object - sim_utils.create_prim(f"/World/OriginCloth", "Xform", translation=[0,0,1.5]) - cfg = DeformableObjectCfg( - prim_path="/World/OriginCloth/Cloth", - spawn=sim_utils.MeshSquareCfg( - size=1.5, - resolution=(21, 21), - deformable_props=DeformableBodyPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.1, 0.5, 0.1)), - physics_material=SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), - ), - ) - cloth_object = DeformableObject(cfg=cfg) - scene_entities["cloth_object"] = cloth_object - - # USD File - uniform_scale = 0.50 - sim_utils.create_prim(f"/World/OriginUSD", "Xform", translation=[0.1,-0.25,2.0]) - cfg = DeformableObjectCfg( - prim_path="/World/OriginUSD/Teddy", - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", - deformable_props=DeformableBodyPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.6, 0.35, 0.15)), - physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e9), - scale=[uniform_scale, uniform_scale, uniform_scale] - ) - ) - usd_object = DeformableObject(cfg=cfg) - scene_entities["usd_object"] = usd_object - - - # Sensors - if args_cli.save: - camera = define_sensor() - scene_entities["camera"] = camera - # return the scene information return scene_entities, origins @@ -180,27 +102,9 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # note: we only do this here for readability. In general, it is better to access the entities directly from # the dictionary. This dictionary is replaced by the InteractiveScene class in the next tutorial. cube_object: DeformableObject = entities["cube_object"] - cloth_object: DeformableObject = entities["cloth_object"] - - # Write camera outputs - if args_cli.save: - camera: Camera = entities["camera"] - - # Create replicator writer - rep_writer = rep.BasicWriter( - output_dir=output_dir, - frame_padding=0, - rgb=True, - ) - # Camera positions, targets, orientations - camera_positions = torch.tensor([[2., 2., 2.]], device=sim.device) - camera_targets = torch.tensor([[0.0, 0.0, 0.75]], device=sim.device) - camera.set_world_poses_from_view(camera_positions, camera_targets) - # Define simulation stepping sim_dt = sim.get_physics_dt() - assert sim_dt <= 1.0 / args_cli.video_fps, "Simulation timestep must be smaller than the inverse of the video FPS to save frames properly." num_steps = int(args_cli.total_time / sim_dt) sim_time = 0.0 count = 0 @@ -209,7 +113,6 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor nodal_kinematic_target = wp.to_torch(cube_object.data.nodal_kinematic_target).clone() # Simulate physics - com_traj = [] for t in range(num_steps): # reset at start and after N seconds if sim_time == 0.0 or sim_time > 3.0: @@ -235,11 +138,6 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor # reset buffers cube_object.reset() - # reset the cloth object as well - nodal_state = wp.to_torch(cloth_object.data.default_nodal_state_w).clone() - cloth_object.write_nodal_state_to_sim(nodal_state) - cloth_object.reset() - print("----------------------------------------") print("[INFO]: Resetting object state...") @@ -261,39 +159,11 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor sim_time += sim_dt count += 1 # update buffers - for entity in entities.values(): - entity.update(sim_dt) + cube_object.update(sim_dt) - com_traj.append(wp.to_torch(cube_object.data.nodal_pos_w).mean(1).cpu().numpy()) - # print the root position - if t % args_cli.video_fps == 0: + # print the root positions every second + if t % int(1/sim_dt) == 0: print(f"Time {t*sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") - print(f"Cube 0 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[0].mean(0)}") - print(f"Cube 1 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[1].mean(0)}") - print(f"Cube 2 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[2].mean(0)}") - print(f"Cube 3 COM: {wp.to_torch(cube_object.data.nodal_pos_w)[3].mean(0)}") - print(f"Cloth COM: {wp.to_torch(cloth_object.data.nodal_pos_w)[0].mean(0)}") - - trajectories = np.stack(com_traj, axis=1) - time_axis = np.arange(trajectories.shape[1]) * sim_dt - fig, ax = plt.subplots(figsize=(4, 3)) - for i in range(4): - ax.plot(time_axis, trajectories[i, :, 2], label=f"Cube {i}") - ax.set_xlabel("Time (s)") - ax.set_ylabel("Z Position (m)") - ax.legend() - ax.grid() - fig.savefig(os.path.join(os.path.dirname(output_dir), f"com_trajectory.png"), dpi=300, bbox_inches="tight") - plt.close(fig) - - # Extract camera data - if args_cli.save: - if camera.data.output["rgb"] is not None: - cam_data = convert_dict_to_backend(camera.data.output, backend="numpy") - rep_writer.write({ - "annotators": {"rgb": {"render_product": {"data": cam_data["rgb"][0]}}}, - "trigger_outputs": {"on_time": camera.frame[0]} - }) def main(): @@ -302,7 +172,7 @@ def main(): sim_cfg = sim_utils.SimulationCfg(dt=args_cli.dt, device=args_cli.device) sim = SimulationContext(sim_cfg) # Set main camera - sim.set_camera_view(eye=[3.0, 0.0, 1.0], target=[0.0, 0.0, 0.5]) + sim.set_camera_view(eye=[2.0, 2.0, 2.0], target=[0.0, 0.0, 0.75]) # Design scene scene_entities, scene_origins = design_scene() scene_origins = torch.tensor(scene_origins, device=sim.device) @@ -313,26 +183,8 @@ def main(): # Run the simulator camera_output = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera") run_simulator(sim, scene_entities, scene_origins, camera_output) - # Store video if saving frames - if args_cli.save: - video_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.mp4") - fps = args_cli.video_fps - subprocess.run([ - "ffmpeg", "-y", "-loglevel", "error", - "-framerate", str(fps), - "-i", os.path.join(camera_output, "rgb_%d_0.png"), - "-c:v", "libx264", "-pix_fmt", "yuv420p", - video_path, - ], check=True) - # Also generate gif for quick preview - gif_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.gif") - subprocess.run([ - "ffmpeg", "-y", "-loglevel", "error", - "-i", video_path, - "-vf", "fps=15,scale=320:-1:flags=lanczos", - gif_path, - ], check=True) - print(f"[INFO]: Video saved to {video_path}") + print("[INFO]: Simulation complete...") + if __name__ == "__main__": # run the main function From eb6d4fce7180484fb9b00ce983859a51499c12bb Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 13:47:43 +0100 Subject: [PATCH 41/73] Style: Clean deformable demo --- scripts/demos/deformables.py | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 69e17cf5c04f..2a7bbf47aaf7 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -68,8 +68,6 @@ import tqdm import warp as wp -import omni.replicator.core as rep - import isaaclab.sim as sim_utils from isaaclab.utils import convert_dict_to_backend from isaaclab.sensors.camera import Camera, CameraCfg @@ -79,37 +77,41 @@ from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg +# import only used for rendering frames +if args_cli.save: + import omni.replicator.core as rep + + +def define_origins(num_origins: int, radius: float = 2.0, center_height: float = 3.0) -> list[list[float]]: + """Defines origins distributed on the surface of a sphere, sampled according to a Fibonacci lattice. -def define_origins(num_origins: int, spacing: float) -> list[list[float]]: - """Defines the origins of the the scene.""" - # create tensor based on number of environments + Args: + num_origins: Number of points to place. + radius: Radius of the sphere [m]. + center_height: Height of the sphere center above ground [m]. + """ + golden_ratio = (1 + np.sqrt(5)) / 2 env_origins = torch.zeros(num_origins, 3) - # create a grid of origins - num_cols = np.floor(np.sqrt(num_origins)) - num_rows = np.ceil(num_origins / num_cols) - xx, yy = torch.meshgrid(torch.arange(num_rows), torch.arange(num_cols), indexing="xy") - env_origins[:, 0] = spacing * xx.flatten()[:num_origins] - spacing * (num_rows - 1) / 2 - env_origins[:, 1] = spacing * yy.flatten()[:num_origins] - spacing * (num_cols - 1) / 2 - env_origins[:, 2] = 2.0 * torch.rand(num_origins) + 0.5 - # return the origins + for i in range(num_origins): + theta = 2 * np.pi * i / golden_ratio + phi = np.arccos(1 - 2 * (i + 0.5) / num_origins) + env_origins[i, 0] = radius * np.cos(theta) * np.sin(phi) + env_origins[i, 1] = radius * np.sin(theta) * np.sin(phi) + env_origins[i, 2] = radius * np.cos(phi) + center_height return env_origins.tolist() def define_sensor() -> Camera: - """Defines the camera sensor to add to the scene.""" + """Defines the camera sensor to add to the scene for rendering frames.""" # Setup camera sensor - # In contrast to the ray-cast camera, we spawn the prim at these locations. - # This means the camera sensor will be attached to these prims. - sim_utils.create_prim("/World/OriginCamera", "Xform", translation=[0.0, 0.0, 0.0]) + sim_utils.create_prim("/World/CameraOrigin", "Xform", translation=[0.0, 0.0, 0.0]) camera_cfg = CameraCfg( - prim_path="/World/OriginCamera/CameraSensor", + prim_path="/World/CameraOrigin/CameraSensor", update_period=1.0/args_cli.video_fps, height=800, width=1000, data_types=["rgb",], - spawn=sim_utils.PinholeCameraCfg( - focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5) - ), + spawn=sim_utils.PinholeCameraCfg(), ) # Create camera camera = Camera(cfg=camera_cfg) @@ -165,7 +167,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: physics_material=DeformableBodyMaterialCfg(), ) cfg_cloth = sim_utils.MeshSquareCfg( - size=2.5, + size=1.5, resolution=(21, 21), deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), @@ -190,7 +192,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: } # Create separate groups of deformable objects - origins = define_origins(num_origins=16, spacing=0.8) + origins = define_origins(num_origins=12, radius=1.5, center_height=2.0) print("[INFO]: Spawning objects...") # Iterate over all the origins and randomly spawn objects for idx, origin in tqdm.tqdm(enumerate(origins), total=len(origins)): @@ -198,17 +200,14 @@ def design_scene() -> tuple[dict, list[list[float]]]: obj_name = random.choice(list(objects_cfg.keys())) obj_cfg = objects_cfg[obj_name] # randomize the young modulus - obj_cfg.physics_material.youngs_modulus = random.uniform(1e6, 1e8) + obj_cfg.physics_material.youngs_modulus = random.uniform(5e5, 1e8) # higher mesh resolution causes instability at low stiffness - if obj_name == "sphere" or obj_name == "capsule" or obj_name == "cloth": - obj_cfg.physics_material.youngs_modulus = random.uniform(5e8, 5e9) + if obj_name in ["sphere", "capsule", "cloth", "usd"]: + obj_cfg.physics_material.youngs_modulus = random.uniform(1e8, 5e9) # randomize the poisson's ratio obj_cfg.physics_material.poissons_ratio = random.uniform(0.25, 0.45) # randomize the color obj_cfg.visual_material.diffuse_color = (random.random(), random.random(), random.random()) - # spawn cloth a bit higher than the rest - if obj_name == "cloth": - origin[2] += 1.5 # spawn the object obj_cfg.func(f"/World/Origin/Object{idx:02d}", obj_cfg, translation=origin) @@ -221,8 +220,9 @@ def design_scene() -> tuple[dict, list[list[float]]]: init_state=DeformableObjectCfg.InitialStateCfg(), ) deformable_object = DeformableObject(cfg=cfg) - scene_entities = {"deformable_object": deformable_object} + + # Create camera if saving frames if args_cli.save: camera = define_sensor() scene_entities["camera"] = camera @@ -231,7 +231,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: return scene_entities, origins -def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, DeformableObject], origins: torch.Tensor, output_dir: str = "outputs"): +def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, DeformableObject], output_dir: str = "outputs"): """Runs the simulation loop.""" # Write camera outputs if args_cli.save: @@ -249,7 +249,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab camera.set_world_poses_from_view(camera_positions, camera_targets) - print(f"Solving for {torch.tensor(entities["deformable_object"].root_view.get_simulation_nodal_positions().shape).prod().item():,} Degrees of Freedom.") + print(f"[INFO]: Solving for {torch.tensor(entities["deformable_object"].root_view.get_simulation_nodal_positions().shape).prod().item():,} Degrees of Freedom.") # Define simulation stepping sim_dt = sim.get_physics_dt() @@ -301,15 +301,15 @@ def main(): sim.set_camera_view([4.0, 4.0, 3.0], [0.5, 0.5, 0.0]) # Design scene by adding assets to it - scene_entities, scene_origins = design_scene() - scene_origins = torch.tensor(scene_origins, device=sim.device) + scene_entities, _ = design_scene() # Play the simulator sim.reset() # Now we are ready! print("[INFO]: Setup complete...") - camera_output = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera") - run_simulator(sim, scene_entities, scene_origins, camera_output) + run_simulator(sim, scene_entities, camera_output) + print("[INFO]: Simulation complete...") + # Store video if saving frames if args_cli.save: video_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.mp4") From 7d200755197e65ccc68028509620b8a710830b07 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 14:12:38 +0100 Subject: [PATCH 42/73] Fix: Kinematic targets only for volume deformables --- .../assets/deformable_object/deformable_object.py | 12 +++++++----- .../deformable_object/deformable_object_data.py | 12 ++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index cb5034b154ee..b049ebcad823 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -55,7 +55,7 @@ class DeformableObject(AssetBase): The state of a deformable object comprises of its nodal positions and velocities, and not the object's root position and orientation. The nodal positions and velocities are in the simulation frame. - Soft bodies can be `partially kinematic`_, where some nodes are driven by kinematic targets, and the rest are + Volume deformables can be `partially kinematic`_, where some nodes are driven by kinematic targets, and the rest are simulated. The kinematic targets are the desired positions of the nodes, and the simulation drives the nodes towards these targets. This is useful for partial control of the object, such as moving a stuffed animal's head while the rest of the body is simulated. @@ -710,10 +710,12 @@ def _set_debug_vis_impl(self, debug_vis: bool): self.target_visualizer.set_visibility(False) def _debug_vis_callback(self, event): - # check where to visualize - kinematic_target_torch = wp.to_torch(self.data.nodal_kinematic_target) - targets_enabled = kinematic_target_torch[:, :, 3] == 0.0 - num_enabled = int(torch.sum(targets_enabled).item()) + # check where to visualize, kinematic targets only supported for volume deformables + num_enabled = 0 + if self._deformable_type == "volume": + kinematic_target_torch = wp.to_torch(self.data.nodal_kinematic_target) + targets_enabled = kinematic_target_torch[:, :, 3] == 0.0 + num_enabled = int(torch.sum(targets_enabled).item()) # get positions if any targets are enabled if num_enabled == 0: # create a marker below the ground diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py index d275378330da..f3d794987a55 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py @@ -171,7 +171,7 @@ def sim_element_quat_w(self) -> wp.array: """ # deprecated raise NotImplementedError( - "The sim_element_quat_w property is deprecated and will be removed in future versions. " + "The sim_element_quat_w property is deprecated." ) @property @@ -183,7 +183,7 @@ def collision_element_quat_w(self) -> wp.array: """ # deprecated raise NotImplementedError( - "The collision_element_quat_w property is deprecated and will be removed in future versions. " + "The collision_element_quat_w property is deprecated." ) @property @@ -193,7 +193,7 @@ def sim_element_deform_gradient_w(self) -> wp.array: """ # deprecated raise NotImplementedError( - "The sim_element_deform_gradient_w property is deprecated and will be removed in future versions. " + "The sim_element_deform_gradient_w property is deprecated." ) @property @@ -203,7 +203,7 @@ def collision_element_deform_gradient_w(self) -> wp.array: """ # deprecated raise NotImplementedError( - "The collision_element_deform_gradient_w property is deprecated and will be removed in future versions. " + "The collision_element_deform_gradient_w property is deprecated." ) @property @@ -213,7 +213,7 @@ def sim_element_stress_w(self) -> wp.array: """ # deprecated raise NotImplementedError( - "The sim_element_stress_w property is deprecated and will be removed in future versions. " + "The sim_element_stress_w property is deprecated." ) @property @@ -223,7 +223,7 @@ def collision_element_stress_w(self) -> wp.array: """ # deprecated raise NotImplementedError( - "The collision_element_stress_w property is deprecated and will be removed in future versions. " + "The collision_element_stress_w property is deprecated." ) ## From d0c3d37c66655cd0f6c0d11446325f0b4ae82e93 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 15:28:29 +0100 Subject: [PATCH 43/73] Docs: Move deformables to physx backend and add migration guide --- docs/index.rst | 1 + docs/source/api/index.rst | 4 + docs/source/api/lab/isaaclab.sim.schemas.rst | 15 +- docs/source/api/lab/isaaclab.sim.spawners.rst | 24 +- .../lab_physx/isaaclab_physx.sim.schemas.rst | 30 +++ .../lab_physx/isaaclab_physx.sim.spawners.rst | 52 ++++ .../migration/migrating_deformables.rst | 232 ++++++++++++++++++ 7 files changed, 340 insertions(+), 18 deletions(-) create mode 100644 docs/source/api/lab_physx/isaaclab_physx.sim.schemas.rst create mode 100644 docs/source/api/lab_physx/isaaclab_physx.sim.spawners.rst create mode 100644 docs/source/migration/migrating_deformables.rst diff --git a/docs/index.rst b/docs/index.rst index 820351b314d5..9a8d91664f7b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -157,6 +157,7 @@ Table of Contents :titlesonly: source/migration/migrating_to_isaaclab_3-0 + source/migration/migrating_deformables source/migration/migrating_from_isaacgymenvs source/migration/migrating_from_omniisaacgymenvs source/migration/migrating_from_orbit diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 46343eb6419a..cea73de6802d 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -129,6 +129,8 @@ The following modules are available in the ``isaaclab_physx`` extension: renderers scene_data_providers sensors + sim.schemas + sim.spawners .. toctree:: :hidden: @@ -139,6 +141,8 @@ The following modules are available in the ``isaaclab_physx`` extension: lab_physx/isaaclab_physx.renderers lab_physx/isaaclab_physx.scene_data_providers lab_physx/isaaclab_physx.sensors + lab_physx/isaaclab_physx.sim.schemas + lab_physx/isaaclab_physx.sim.spawners isaaclab_newton extension ------------------------- diff --git a/docs/source/api/lab/isaaclab.sim.schemas.rst b/docs/source/api/lab/isaaclab.sim.schemas.rst index 77ac6512dbce..263e3152e596 100644 --- a/docs/source/api/lab/isaaclab.sim.schemas.rst +++ b/docs/source/api/lab/isaaclab.sim.schemas.rst @@ -13,7 +13,6 @@ MassPropertiesCfg JointDrivePropertiesCfg FixedTendonPropertiesCfg - DeformableBodyPropertiesCfg .. rubric:: Functions @@ -30,8 +29,6 @@ modify_mass_properties modify_joint_drive_properties modify_fixed_tendon_properties - define_deformable_body_properties - modify_deformable_body_properties Articulation Root ----------------- @@ -95,9 +92,11 @@ Fixed Tendon Deformable Body --------------- -.. autoclass:: DeformableBodyPropertiesCfg - :members: - :exclude-members: __init__ +.. note:: + + Deformable body schemas have moved to the PhysX backend extension. See + :class:`isaaclab_physx.sim.schemas.DeformableBodyPropertiesCfg`, + :func:`isaaclab_physx.sim.schemas.define_deformable_body_properties`, and + :func:`isaaclab_physx.sim.schemas.modify_deformable_body_properties`. -.. autofunction:: define_deformable_body_properties -.. autofunction:: modify_deformable_body_properties + For migration details, see :ref:`migrating-deformables`. diff --git a/docs/source/api/lab/isaaclab.sim.spawners.rst b/docs/source/api/lab/isaaclab.sim.spawners.rst index 701efda84e18..a31968edfe07 100644 --- a/docs/source/api/lab/isaaclab.sim.spawners.rst +++ b/docs/source/api/lab/isaaclab.sim.spawners.rst @@ -21,7 +21,6 @@ SpawnerCfg RigidObjectSpawnerCfg - DeformableObjectSpawnerCfg Spawners -------- @@ -35,10 +34,12 @@ Spawners :show-inheritance: :exclude-members: __init__ -.. autoclass:: DeformableObjectSpawnerCfg - :members: - :show-inheritance: - :exclude-members: __init__ +.. note:: + + ``DeformableObjectSpawnerCfg`` has moved to the PhysX backend extension. See + :class:`isaaclab_physx.sim.spawners.DeformableObjectSpawnerCfg`. + + For migration details, see :ref:`migrating-deformables`. Shapes ------ @@ -260,7 +261,6 @@ Materials GlassMdlCfg PhysicsMaterialCfg RigidBodyMaterialCfg - DeformableBodyMaterialCfg Visual Materials ~~~~~~~~~~~~~~~~ @@ -298,11 +298,15 @@ Physical Materials :members: :exclude-members: __init__, func -.. autofunction:: spawn_deformable_body_material +.. note:: -.. autoclass:: DeformableBodyMaterialCfg - :members: - :exclude-members: __init__, func + ``DeformableBodyMaterialCfg``, ``SurfaceDeformableBodyMaterialCfg``, and + ``spawn_deformable_body_material`` have moved to the PhysX backend extension. See + :class:`isaaclab_physx.sim.spawners.materials.DeformableBodyMaterialCfg`, + :class:`isaaclab_physx.sim.spawners.materials.SurfaceDeformableBodyMaterialCfg`, and + :func:`isaaclab_physx.sim.spawners.materials.spawn_deformable_body_material`. + + For migration details, see :ref:`migrating-deformables`. Wrappers -------- diff --git a/docs/source/api/lab_physx/isaaclab_physx.sim.schemas.rst b/docs/source/api/lab_physx/isaaclab_physx.sim.schemas.rst new file mode 100644 index 000000000000..40fb3addb275 --- /dev/null +++ b/docs/source/api/lab_physx/isaaclab_physx.sim.schemas.rst @@ -0,0 +1,30 @@ +isaaclab_physx.sim.schemas +========================== + +.. automodule:: isaaclab_physx.sim.schemas + + .. rubric:: Classes + + .. autosummary:: + + DeformableBodyPropertiesCfg + + .. rubric:: Functions + + .. autosummary:: + + define_deformable_body_properties + modify_deformable_body_properties + +.. currentmodule:: isaaclab_physx.sim.schemas + +Deformable Body +--------------- + +.. autoclass:: DeformableBodyPropertiesCfg + :members: + :show-inheritance: + :exclude-members: __init__ + +.. autofunction:: define_deformable_body_properties +.. autofunction:: modify_deformable_body_properties diff --git a/docs/source/api/lab_physx/isaaclab_physx.sim.spawners.rst b/docs/source/api/lab_physx/isaaclab_physx.sim.spawners.rst new file mode 100644 index 000000000000..27463a44425d --- /dev/null +++ b/docs/source/api/lab_physx/isaaclab_physx.sim.spawners.rst @@ -0,0 +1,52 @@ +isaaclab_physx.sim.spawners +=========================== + +.. automodule:: isaaclab_physx.sim.spawners + + .. rubric:: Submodules + + .. autosummary:: + + materials + + .. rubric:: Classes + + .. autosummary:: + + DeformableObjectSpawnerCfg + +.. currentmodule:: isaaclab_physx.sim.spawners + +Spawners +-------- + +.. autoclass:: DeformableObjectSpawnerCfg + :members: + :show-inheritance: + :exclude-members: __init__ + +Materials +--------- + +.. automodule:: isaaclab_physx.sim.spawners.materials + + .. rubric:: Classes + + .. autosummary:: + + DeformableBodyMaterialCfg + SurfaceDeformableBodyMaterialCfg + +.. currentmodule:: isaaclab_physx.sim.spawners.materials + +.. autofunction:: spawn_deformable_body_material + +.. autoclass:: DeformableBodyMaterialCfg + :members: + :show-inheritance: + :exclude-members: __init__, func + +.. autoclass:: SurfaceDeformableBodyMaterialCfg + :members: + :show-inheritance: + :exclude-members: __init__, func diff --git a/docs/source/migration/migrating_deformables.rst b/docs/source/migration/migrating_deformables.rst new file mode 100644 index 000000000000..5cbbdfca3ffc --- /dev/null +++ b/docs/source/migration/migrating_deformables.rst @@ -0,0 +1,232 @@ +.. _migrating-deformables: + +Migration of Deformables +======================== + +.. currentmodule:: isaaclab + +In the newer versions of Omni Physics (107.0 and later), the old deformable body functionality has become deprecated. +The following sections describe the changes to migrate to the new Omni Physics API, specifically moving away from +Soft Bodies and towards Surface and Volume Deformables. We currently only support deformable bodies in the PhysX +backend, hence these features are implemented in ``isaaclab_physx``. + +.. note:: + + The following changes are with respect to Isaac Lab v2.3.3 and Omni Physics v110.0. Please refer to the + `release notes`_ for any changes in the future releases. + + +Surface and Volume Deformables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +With the new Omni Physics API, deformable bodies are split into two distinct types, as described in the +`Omni Physics documentation`_: + +- **Volume deformables**: 3D objects simulated with a tetrahedral FEM mesh (e.g., soft cubes, spheres, capsules). + These support kinematic targets on individual vertices. The simulation operates on a tetrahedral mesh internally, + while a separate triangle surface mesh handles rendering. +- **Surface deformables**: 2D surfaces simulated directly on a triangle mesh (e.g., cloth, fabric, membranes). + These have additional material properties for controlling stretch, shear, and bend stiffness, but do not support + kinematic vertex targets. + +The type of deformable is determined by the **physics material** assigned to the object: + +- :class:`~isaaclab_physx.sim.DeformableBodyMaterialCfg` creates a **volume** deformable. +- :class:`~isaaclab_physx.sim.SurfaceDeformableBodyMaterialCfg` creates a **surface** deformable. + + +Migration from the Old API +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Import Changes +^^^^^^^^^^^^^^ + +All deformable-related classes have moved from ``isaaclab`` to ``isaaclab_physx``. The table below summarizes the +import changes: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Old Import + - New Import + * - ``from isaaclab.sim import DeformableBodyPropertiesCfg`` + - ``from isaaclab_physx.sim import DeformableBodyPropertiesCfg`` + * - ``from isaaclab.sim import DeformableBodyMaterialCfg`` + - ``from isaaclab_physx.sim import DeformableBodyMaterialCfg`` + * - ``import isaaclab.sim as sim_utils`` then ``sim_utils.DeformableBodyPropertiesCfg`` + - ``from isaaclab_physx.sim import DeformableBodyPropertiesCfg`` + * - ``import isaaclab.sim as sim_utils`` then ``sim_utils.DeformableBodyMaterialCfg`` + - ``from isaaclab_physx.sim import DeformableBodyMaterialCfg`` + +.. note:: + + The ``isaaclab_physx`` extension is installed automatically with Isaac Lab. No additional installation steps + are required. + +Removed Properties +^^^^^^^^^^^^^^^^^^ + +The following properties have been **removed** from :class:`~isaaclab_physx.sim.DeformableBodyPropertiesCfg`: + +- ``collision_simplification`` and related parameters (``collision_simplification_remeshing``, + ``collision_simplification_target_triangle_count``, ``collision_simplification_force_conforming``, + ``collision_simplification_remove_open_edges``) — collision mesh generation is now handled automatically by + PhysX through ``deformableUtils.create_auto_volume_deformable_hierarchy()`` and + ``deformableUtils.create_auto_surface_deformable_hierarchy()``. +- ``simulation_hexahedral_resolution`` — the simulation mesh resolution is no longer user-configurable; + PhysX determines it automatically. +- ``vertex_velocity_damping`` — replaced by the more general ``linear_damping`` property from the + `PhysX deformable schema`_. +- ``sleep_damping`` — replaced by ``settling_damping`` in the `PhysX deformable schema`_. + +Added Properties +^^^^^^^^^^^^^^^^ + +The following properties have been **added** to :class:`~isaaclab_physx.sim.DeformableBodyPropertiesCfg`: + +- ``linear_damping`` — linear damping coefficient [1/s]. +- ``max_linear_velocity`` — maximum allowable linear velocity [m/s]. A negative value lets the simulation choose + a per-vertex value dynamically (currently only supported for surface deformables). +- ``settling_damping`` — additional damping applied when vertex velocity falls below ``settling_threshold`` [1/s]. +- ``enable_speculative_c_c_d`` — enables speculative continuous collision detection. +- ``disable_gravity`` — per-deformable gravity control. +- ``collision_pair_update_frequency`` — how often surface-to-surface collision pairs are updated per time step + (surface deformables only). +- ``collision_iteration_multiplier`` — collision subiterations per solver iteration (surface deformables only). + +For a full description of all available properties, refer to the `PhysX deformable schema`_ and +`OmniPhysics deformable schema`_ documentation. + +Material Changes +^^^^^^^^^^^^^^^^ + +The old :class:`DeformableBodyMaterialCfg` (from ``isaaclab.sim``) has been replaced by a new hierarchy in +``isaaclab_physx``: + +- :class:`~isaaclab_physx.sim.DeformableBodyMaterialCfg` — for volume deformables. Contains ``density``, + ``static_friction``, ``dynamic_friction``, ``youngs_modulus``, ``poissons_ratio``, and ``elasticity_damping``. +- :class:`~isaaclab_physx.sim.SurfaceDeformableBodyMaterialCfg` — extends the volume material config with + surface-specific properties: ``surface_thickness``, ``surface_stretch_stiffness``, ``surface_shear_stiffness``, + ``surface_bend_stiffness``, and ``bend_damping``. + +The old ``damping_scale`` property has been removed. Use ``elasticity_damping`` directly instead. + +DeformableObject View Change +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The internal PhysX view type has changed from ``physx.SoftBodyView`` to ``physx.DeformableBodyView``. +The property ``root_physx_view`` has been deprecated in favor of ``root_view``. + + +Code Examples +~~~~~~~~~~~~~ + +Volume Deformable (Before and After) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Before** (Isaac Lab v2.3.3): + +.. code-block:: python + + import isaaclab.sim as sim_utils + from isaaclab.assets import DeformableObject, DeformableObjectCfg + + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Cube", + spawn=sim_utils.MeshCuboidCfg( + size=(0.2, 0.2, 0.2), + deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), + physics_material=sim_utils.DeformableBodyMaterialCfg(), + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), + ) + cube_object = DeformableObject(cfg=cfg) + +**After**: + +.. code-block:: python + + import isaaclab.sim as sim_utils + from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg + from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg + + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Cube", + spawn=sim_utils.MeshCuboidCfg( + size=(0.2, 0.2, 0.2), + deformable_props=DeformableBodyPropertiesCfg(), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), + ) + cube_object = DeformableObject(cfg=cfg) + +Surface Deformable (New) +^^^^^^^^^^^^^^^^^^^^^^^^ + +Surface deformables use :class:`~isaaclab.sim.spawners.meshes.MeshSquareCfg` for 2D meshes, combined with +:class:`~isaaclab_physx.sim.SurfaceDeformableBodyMaterialCfg`: + +.. code-block:: python + + import isaaclab.sim as sim_utils + from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg + from isaaclab_physx.sim import DeformableBodyPropertiesCfg, SurfaceDeformableBodyMaterialCfg + + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Cloth", + spawn=sim_utils.MeshSquareCfg( + size=1.5, + resolution=(21, 21), + deformable_props=DeformableBodyPropertiesCfg(), + visual_material=sim_utils.PreviewSurfaceCfg(), + physics_material=SurfaceDeformableBodyMaterialCfg(), + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)), + ) + cloth_object = DeformableObject(cfg=cfg) + +USD File Deformable +^^^^^^^^^^^^^^^^^^^ + +Deformable properties can also be applied to imported USD assets using +:class:`~isaaclab.sim.spawners.from_files.UsdFileCfg`: + +.. code-block:: python + + import isaaclab.sim as sim_utils + from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg + from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg + + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Teddy", + spawn=sim_utils.UsdFileCfg( + usd_path="path/to/teddy_bear.usd", + deformable_props=DeformableBodyPropertiesCfg(), + physics_material=DeformableBodyMaterialCfg(), + scale=[0.05, 0.05, 0.05], + ), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)), + ) + teddy_object = DeformableObject(cfg=cfg) + + +Limitations +~~~~~~~~~~~ + +- **Kinematic targets are volume-only.** Calling + :meth:`~isaaclab_physx.assets.DeformableObject.write_nodal_kinematic_target_to_sim_index` on a surface + deformable will raise a ``ValueError``. +- **Surface-specific solver properties** (``collision_pair_update_frequency``, + ``collision_iteration_multiplier``) have no effect on volume deformables. +- **Deformables are PhysX-only.** The ``isaaclab_physx`` extension is required; other physics backends + do not support deformable bodies. + + +.. _Omni Physics documentation: https://docs.omniverse.nvidia.com/kit/docs/omni_physics/110.0/dev_guide/deformables/deformable_bodies.html +.. _PhysX deformable schema: https://docs.omniverse.nvidia.com/kit/docs/omni_physics/110.0/dev_guide/deformables/physx_deformable_schema.html#physxbasedeformablebodyapi +.. _OmniPhysics deformable schema: https://docs.omniverse.nvidia.com/kit/docs/omni_physics/110.0/dev_guide/deformables/omniphysics_deformable_schema.html +.. _release notes: https://github.com/isaac-sim/IsaacLab/releases From 3ee89c83eea9395c958f243d74575e963ae40970 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 15:47:12 +0100 Subject: [PATCH 44/73] Docs: Fix reference to deformable in multi asset spawner --- source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py | 1 - .../isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py index 5b4f8b4379c3..7a803ad0e0dd 100644 --- a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py @@ -15,7 +15,6 @@ from pxr import Usd from isaaclab.sim import schemas - from isaaclab_physx.sim import schemas as schemas_physx @configclass diff --git a/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py b/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py index 1533c29687e8..08f3106db8ab 100644 --- a/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py @@ -6,9 +6,12 @@ from dataclasses import MISSING from isaaclab.sim.spawners.from_files import UsdFileCfg -from isaaclab.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg, RigidObjectSpawnerCfg, SpawnerCfg +from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg from isaaclab.utils import configclass +# deformables only supported in PhysX backend +from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg + @configclass class MultiAssetSpawnerCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): From 65f0c9aacc2dce75e5dbcb8f8c38b8d1a20f93a9 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 16:46:24 +0100 Subject: [PATCH 45/73] Docs: Clean up migration guide --- .../migration/migrating_deformables.rst | 36 ++++++++----------- .../01_assets/run_deformable_object.rst | 13 +++---- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/docs/source/migration/migrating_deformables.rst b/docs/source/migration/migrating_deformables.rst index 5cbbdfca3ffc..efb7f7da689e 100644 --- a/docs/source/migration/migrating_deformables.rst +++ b/docs/source/migration/migrating_deformables.rst @@ -12,7 +12,7 @@ backend, hence these features are implemented in ``isaaclab_physx``. .. note:: - The following changes are with respect to Isaac Lab v2.3.3 and Omni Physics v110.0. Please refer to the + The following changes are with respect to Isaac Lab v3.0.0 and Omni Physics v110.0. Please refer to the `release notes`_ for any changes in the future releases. @@ -54,15 +54,7 @@ import changes: - ``from isaaclab_physx.sim import DeformableBodyPropertiesCfg`` * - ``from isaaclab.sim import DeformableBodyMaterialCfg`` - ``from isaaclab_physx.sim import DeformableBodyMaterialCfg`` - * - ``import isaaclab.sim as sim_utils`` then ``sim_utils.DeformableBodyPropertiesCfg`` - - ``from isaaclab_physx.sim import DeformableBodyPropertiesCfg`` - * - ``import isaaclab.sim as sim_utils`` then ``sim_utils.DeformableBodyMaterialCfg`` - - ``from isaaclab_physx.sim import DeformableBodyMaterialCfg`` - -.. note:: - The ``isaaclab_physx`` extension is installed automatically with Isaac Lab. No additional installation steps - are required. Removed Properties ^^^^^^^^^^^^^^^^^^ @@ -125,9 +117,10 @@ Code Examples Volume Deformable (Before and After) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -**Before** (Isaac Lab v2.3.3): +**Before**: .. code-block:: python + :emphasize-lines: 1,2 import isaaclab.sim as sim_utils from isaaclab.assets import DeformableObject, DeformableObjectCfg @@ -136,17 +129,17 @@ Volume Deformable (Before and After) prim_path="/World/Origin.*/Cube", spawn=sim_utils.MeshCuboidCfg( size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + deformable_props=sim_utils.DeformableBodyPropertiesCfg(), + visual_material=sim_utils.PreviewSurfaceCfg(), + physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), ) cube_object = DeformableObject(cfg=cfg) **After**: .. code-block:: python + :emphasize-lines: 1,2,3 import isaaclab.sim as sim_utils from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg @@ -157,10 +150,9 @@ Volume Deformable (Before and After) spawn=sim_utils.MeshCuboidCfg( size=(0.2, 0.2, 0.2), deformable_props=DeformableBodyPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)), + visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), ) cube_object = DeformableObject(cfg=cfg) @@ -183,9 +175,8 @@ Surface deformables use :class:`~isaaclab.sim.spawners.meshes.MeshSquareCfg` for resolution=(21, 21), deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=SurfaceDeformableBodyMaterialCfg(), + physics_material=SurfaceDeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)), ) cloth_object = DeformableObject(cfg=cfg) @@ -201,15 +192,16 @@ Deformable properties can also be applied to imported USD assets using from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg + from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR + cfg = DeformableObjectCfg( prim_path="/World/Origin.*/Teddy", spawn=sim_utils.UsdFileCfg( - usd_path="path/to/teddy_bear.usd", + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", deformable_props=DeformableBodyPropertiesCfg(), - physics_material=DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), scale=[0.05, 0.05, 0.05], ), - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)), ) teddy_object = DeformableObject(cfg=cfg) @@ -223,7 +215,7 @@ Limitations - **Surface-specific solver properties** (``collision_pair_update_frequency``, ``collision_iteration_multiplier``) have no effect on volume deformables. - **Deformables are PhysX-only.** The ``isaaclab_physx`` extension is required; other physics backends - do not support deformable bodies. + do not support deformable bodies through Isaac Lab yet. .. _Omni Physics documentation: https://docs.omniverse.nvidia.com/kit/docs/omni_physics/110.0/dev_guide/deformables/deformable_bodies.html diff --git a/docs/source/tutorials/01_assets/run_deformable_object.rst b/docs/source/tutorials/01_assets/run_deformable_object.rst index d8567c953244..a0f98ee2455f 100644 --- a/docs/source/tutorials/01_assets/run_deformable_object.rst +++ b/docs/source/tutorials/01_assets/run_deformable_object.rst @@ -7,10 +7,11 @@ Interacting with a deformable object .. currentmodule:: isaaclab While deformable objects sometimes refer to a broader class of objects, such as cloths, fluids and soft bodies, -in PhysX, deformable objects syntactically correspond to soft bodies. Unlike rigid objects, soft bodies can deform -under external forces and collisions. +in PhysX, deformable objects are represented as either surface or volume deformables. Unlike rigid objects, soft bodies can deform +under external forces and collisions. In this tutorial, we will focus on volume deformable bodies. For an example of surface +deformables (cloth), see the deformable demo at ``scripts/demos/deformables.py``. -Soft bodies are simulated using Finite Element Method (FEM) in PhysX. The soft body comprises of two tetrahedral +Soft bodies are simulated using Finite Element Method (FEM) in PhysX. The volume deformable comprises of two tetrahedral meshes -- a simulation mesh and a collision mesh. The simulation mesh is used to simulate the deformations of the soft body, while the collision mesh is used to detect collisions with other objects in the scene. For more details, please check the `PhysX documentation`_. @@ -30,7 +31,7 @@ The tutorial corresponds to the ``run_deformable_object.py`` script in the ``scr .. literalinclude:: ../../../../scripts/tutorials/01_assets/run_deformable_object.py :language: python - :emphasize-lines: 61-73, 75-77, 102-110, 112-115, 117-118, 123-130, 132-133, 139-140 + :emphasize-lines: 79-96, 123-139, 144-152, 154-162 :linenos: @@ -62,7 +63,7 @@ an instance of the :class:`assets.DeformableObject` class by passing the configu .. literalinclude:: ../../../../scripts/tutorials/01_assets/run_deformable_object.py :language: python - :start-at: # Create separate groups called "Origin1", "Origin2", "Origin3" + :start-at: # Create separate groups called "Origin0", "Origin1", ... :end-at: cube_object = DeformableObject(cfg=cfg) Running the simulation loop @@ -149,7 +150,7 @@ the average position of all the nodes in the mesh. .. literalinclude:: ../../../../scripts/tutorials/01_assets/run_deformable_object.py :language: python :start-at: # update buffers - :end-at: print(f"Root position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") + :end-at: print(f"Time {t*sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") The Code Execution From 48497c36ffc5e2d0ce69dc3adfe90268710c2cc5 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 16 Mar 2026 17:27:26 +0100 Subject: [PATCH 46/73] Style: Run linter --- .../01_assets/run_deformable_object.rst | 4 +- scripts/demos/deformables.py | 85 ++++++++++++------- .../01_assets/run_deformable_object.py | 19 +++-- .../isaaclab/isaaclab/sim/schemas/schemas.py | 3 +- .../sim/spawners/from_files/from_files.py | 8 +- .../sim/spawners/from_files/from_files_cfg.py | 6 +- .../spawners/materials/physics_materials.py | 2 +- .../materials/physics_materials_cfg.py | 1 - .../isaaclab/sim/spawners/meshes/meshes.py | 20 +++-- .../sim/spawners/meshes/meshes_cfg.py | 6 +- .../sim/spawners/wrappers/wrappers_cfg.py | 6 +- .../deformable_object/deformable_object.py | 25 ++++-- .../deformable_object_data.py | 24 ++---- .../isaaclab_physx/sim/schemas/schemas.py | 49 ++++++----- .../isaaclab_physx/sim/schemas/schemas_cfg.py | 61 ++++++++----- .../sim/spawners/materials/__init__.pyi | 2 +- .../materials/physics_materials_cfg.py | 20 +++-- .../sim/spawners/spawner_cfg.py | 4 +- 18 files changed, 200 insertions(+), 145 deletions(-) diff --git a/docs/source/tutorials/01_assets/run_deformable_object.rst b/docs/source/tutorials/01_assets/run_deformable_object.rst index a0f98ee2455f..963acac46aa1 100644 --- a/docs/source/tutorials/01_assets/run_deformable_object.rst +++ b/docs/source/tutorials/01_assets/run_deformable_object.rst @@ -160,7 +160,7 @@ Now that we have gone through the code, let's run the script and see the result: .. code-block:: bash - ./isaaclab.sh -p scripts/tutorials/01_assets/run_deformable_object.py + ./isaaclab.sh -p scripts/tutorials/01_assets/run_deformable_object.py --visualizer kit This should open a stage with a ground plane, lights, and several green cubes. Two of the four cubes must be dropping @@ -175,7 +175,7 @@ To stop the simulation, you can either close the window, or press ``Ctrl+C`` in This tutorial showed how to spawn deformable objects and wrap them in a :class:`DeformableObject` class to initialize their physics handles which allows setting and obtaining their state. We also saw how to apply kinematic commands to the -deformable object to move the mesh nodes in a controlled manner. In the next tutorial, we will see how to create +deformable object to move the mesh nodes in a controlled manner. An advanced demo of deformable objects, including surface deformables and loading USD assets and applying deformable material on them, can be found in ``scripts/demos/deformables.py``. In the next tutorial, we will see how to create a scene using the :class:`InteractiveScene` class. .. _PhysX documentation: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 2a7bbf47aaf7..d49e938c5d21 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -30,7 +30,7 @@ parser.add_argument( "--dt", type=float, - default=1.0/60, + default=1.0 / 60, help="Simulation timestep.", ) parser.add_argument( @@ -68,15 +68,15 @@ import tqdm import warp as wp +# deformables supported in PhysX +from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +from isaaclab_physx.sim import DeformableBodyMaterialCfg, DeformableBodyPropertiesCfg, SurfaceDeformableBodyMaterialCfg + import isaaclab.sim as sim_utils -from isaaclab.utils import convert_dict_to_backend from isaaclab.sensors.camera import Camera, CameraCfg +from isaaclab.utils import convert_dict_to_backend from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR -# deformables supported in PhysX -from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg - # import only used for rendering frames if args_cli.save: import omni.replicator.core as rep @@ -107,10 +107,12 @@ def define_sensor() -> Camera: sim_utils.create_prim("/World/CameraOrigin", "Xform", translation=[0.0, 0.0, 0.0]) camera_cfg = CameraCfg( prim_path="/World/CameraOrigin/CameraSensor", - update_period=1.0/args_cli.video_fps, + update_period=1.0 / args_cli.video_fps, height=800, width=1000, - data_types=["rgb",], + data_types=[ + "rgb", + ], spawn=sim_utils.PinholeCameraCfg(), ) # Create camera @@ -178,7 +180,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), physics_material=DeformableBodyMaterialCfg(), - scale=[0.05, 0.05, 0.05] + scale=[0.05, 0.05, 0.05], ) # create a dictionary of all the objects to be spawned objects_cfg = { @@ -188,7 +190,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: "capsule": cfg_capsule, "cone": cfg_cone, "cloth": cfg_cloth, - "usd": cfg_usd + "usd": cfg_usd, } # Create separate groups of deformable objects @@ -248,12 +250,14 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab camera_targets = torch.tensor([[0.0, 0.0, 0.5]], device=sim.device) camera.set_world_poses_from_view(camera_positions, camera_targets) - - print(f"[INFO]: Solving for {torch.tensor(entities["deformable_object"].root_view.get_simulation_nodal_positions().shape).prod().item():,} Degrees of Freedom.") + dof = torch.tensor(entities["deformable_object"].root_view.get_simulation_nodal_positions().shape).prod().item() + print(f"[INFO]: Solving for {dof:,} Degrees of Freedom.") # Define simulation stepping sim_dt = sim.get_physics_dt() - assert sim_dt <= 1.0 / args_cli.video_fps, "Simulation timestep must be smaller than the inverse of the video FPS to save frames properly." + assert sim_dt <= 1.0 / args_cli.video_fps, ( + "Simulation timestep must be smaller than the inverse of the video FPS to save frames properly." + ) num_steps = int(args_cli.total_time / sim_dt) sim_time = 0.0 count = 0 @@ -286,10 +290,12 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab if args_cli.save: if camera.data.output["rgb"] is not None: cam_data = convert_dict_to_backend(camera.data.output, backend="numpy") - rep_writer.write({ - "annotators": {"rgb": {"render_product": {"data": cam_data["rgb"][0]}}}, - "trigger_outputs": {"on_time": camera.frame[0]} - }) + rep_writer.write( + { + "annotators": {"rgb": {"render_product": {"data": cam_data["rgb"][0]}}}, + "trigger_outputs": {"on_time": camera.frame[0]}, + } + ) def main(): @@ -314,21 +320,40 @@ def main(): if args_cli.save: video_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.mp4") fps = args_cli.video_fps - subprocess.run([ - "ffmpeg", "-y", "-loglevel", "error", - "-framerate", str(fps), - "-i", os.path.join(camera_output, "rgb_%d_0.png"), - "-c:v", "libx264", "-pix_fmt", "yuv420p", - video_path, - ], check=True) + subprocess.run( + [ + "ffmpeg", + "-y", + "-loglevel", + "error", + "-framerate", + str(fps), + "-i", + os.path.join(camera_output, "rgb_%d_0.png"), + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + video_path, + ], + check=True, + ) # Also generate gif for quick preview gif_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "output.gif") - subprocess.run([ - "ffmpeg", "-y", "-loglevel", "error", - "-i", video_path, - "-vf", "fps=15,scale=320:-1:flags=lanczos", - gif_path, - ], check=True) + subprocess.run( + [ + "ffmpeg", + "-y", + "-loglevel", + "error", + "-i", + video_path, + "-vf", + "fps=15,scale=320:-1:flags=lanczos", + gif_path, + ], + check=True, + ) print(f"[INFO]: Video saved to {video_path}") diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 03ae0bbec073..e1677281d416 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -15,8 +15,8 @@ """Launch Isaac Sim Simulator first.""" -import os import argparse +import os from isaaclab.app import AppLauncher @@ -31,7 +31,7 @@ parser.add_argument( "--dt", type=float, - default=1.0/60, + default=1.0 / 60, help="Simulation timestep.", ) # append AppLauncher cli args @@ -49,14 +49,13 @@ import warp as wp from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +# deformables supported in PhysX +from isaaclab_physx.sim import DeformableBodyMaterialCfg, DeformableBodyPropertiesCfg + import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils from isaaclab.sim import SimulationContext -# deformables supported in PhysX -from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyPropertiesCfg, DeformableBodyMaterialCfg - def design_scene(): """Designs the scene.""" @@ -88,7 +87,7 @@ def design_scene(): init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)), debug_vis=True, ) - + cube_object = DeformableObject(cfg=cfg) scene_entities["cube_object"] = cube_object @@ -162,8 +161,10 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict, origins: tor cube_object.update(sim_dt) # print the root positions every second - if t % int(1/sim_dt) == 0: - print(f"Time {t*sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}") + if t % int(1 / sim_dt) == 0: + print( + f"Time {t * sim_dt:.2f}s: \tRoot position (in world): {wp.to_torch(cube_object.data.root_pos_w)[:, :3]}" + ) def main(): diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 916d142e2008..65ac974a6e2d 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -10,7 +10,7 @@ import math from typing import Any -from pxr import Usd, UsdGeom, UsdPhysics +from pxr import Usd, UsdPhysics from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.string import to_camel_case @@ -18,7 +18,6 @@ from ..utils import ( apply_nested, find_global_fixed_joint_prim, - get_all_matching_child_prims, safe_set_attribute_on_usd_prim, safe_set_attribute_on_usd_schema, ) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index e949f96b0c47..a3fda6153452 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -11,6 +11,10 @@ import tempfile from typing import TYPE_CHECKING +# deformables only supported on PhysX backend +from isaaclab_physx.sim import schemas as schemas_physx +from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg + from pxr import Gf, Sdf, Usd, UsdGeom from isaaclab.sim import converters, schemas @@ -30,10 +34,6 @@ from isaaclab.utils.assets import check_file_path, retrieve_file_path from isaaclab.utils.version import has_kit -# deformables only supported on PhysX backend -from isaaclab_physx.sim import schemas as schemas_physx -from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg - if TYPE_CHECKING: from . import from_files_cfg diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py index 71153ac1424d..e51363908e13 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py @@ -8,15 +8,15 @@ from collections.abc import Callable from dataclasses import MISSING +# deformables only supported on PhysX backend +from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg + from isaaclab.sim import converters, schemas from isaaclab.sim.spawners import materials from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg from isaaclab.utils import configclass from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -# deformables only supported on PhysX backend -from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg - @configclass class FileCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index b0b3b8fd7b7f..d9f93e09e817 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -5,7 +5,7 @@ from __future__ import annotations -from pxr import Usd, UsdGeom, UsdPhysics, UsdShade +from pxr import Usd, UsdPhysics, UsdShade from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_prim, safe_set_attribute_on_usd_schema from isaaclab.sim.utils.stage import get_current_stage diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py index 1db748940288..dde9aec6d905 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py @@ -77,4 +77,3 @@ class RigidBodyMaterialCfg(PhysicsMaterialCfg): Irrelevant if compliant contacts are disabled when :obj:`compliant_contact_stiffness` is set to zero and rigid contacts are active. """ - diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index a0616276c129..79fc4b618046 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -11,6 +11,10 @@ import trimesh import trimesh.transformations +# deformables only supported on PhysX backend +from isaaclab_physx.sim import schemas as schemas_physx +from isaaclab_physx.sim.spawners.materials import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg + from pxr import Usd, UsdPhysics from isaaclab.sim import schemas @@ -18,10 +22,6 @@ from ..materials import RigidBodyMaterialCfg -# deformables only supported on PhysX backend -from isaaclab_physx.sim import schemas as schemas_physx -from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg, DeformableBodyMaterialCfg - if TYPE_CHECKING: from . import meshes_cfg @@ -293,8 +293,9 @@ def spawn_mesh_square( """ # create a 2D triangle mesh grid from omni.physx.scripts import deformableUtils + vertices, faces = deformableUtils.create_triangle_mesh_square(cfg.resolution[0], cfg.resolution[1], scale=cfg.size) - grid = trimesh.Trimesh(vertices=vertices, faces=np.array(faces).reshape(-1,3), process=False) + grid = trimesh.Trimesh(vertices=vertices, faces=np.array(faces).reshape(-1, 3), process=False) # obtain stage handle stage = get_current_stage() @@ -395,7 +396,14 @@ def _spawn_mesh_geom_from_mesh( if cfg.mass_props is not None: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply deformable body properties - schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage=stage, deformable_type="surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume") + schemas_physx.define_deformable_body_properties( + prim_path, + cfg.deformable_props, + stage=stage, + deformable_type="surface" + if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) + else "volume", + ) elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh if cfg.__class__.__name__ == "MeshSphereCfg": diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index 22452f6b9cd9..bf5141dca2d5 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -9,13 +9,13 @@ from dataclasses import MISSING from typing import Literal +# deformables only supported on PhysX backend +from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg + from isaaclab.sim.spawners import materials from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg from isaaclab.utils import configclass -# deformables only supported on PhysX backend -from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg - @configclass class MeshCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): diff --git a/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py b/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py index 08f3106db8ab..9aacb87426c2 100644 --- a/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py @@ -5,13 +5,13 @@ from dataclasses import MISSING +# deformables only supported in PhysX backend +from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg + from isaaclab.sim.spawners.from_files import UsdFileCfg from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg from isaaclab.utils import configclass -# deformables only supported in PhysX backend -from isaaclab_physx.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg - @configclass class MultiAssetSpawnerCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg): diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index b049ebcad823..e88490666ad9 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -381,7 +381,7 @@ def write_nodal_kinematic_target_to_sim_index( """ if self._deformable_type != "volume": raise ValueError("Kinematic targets can only be set for volume deformable bodies.") - + # resolve env_ids env_ids = self._resolve_env_ids(env_ids) if full_data: @@ -406,7 +406,9 @@ def write_nodal_kinematic_target_to_sim_index( device=self.device, ) # set into simulation - self.root_view.set_simulation_nodal_kinematic_targets(self._data.nodal_kinematic_target.view(wp.float32), indices=env_ids) + self.root_view.set_simulation_nodal_kinematic_targets( + self._data.nodal_kinematic_target.view(wp.float32), indices=env_ids + ) def write_nodal_kinematic_target_to_sim_mask( self, @@ -587,7 +589,9 @@ def _initialize_impl(self): " to the deformable body." ) # deformable type based on material that is applied - self._deformable_type = "surface" if material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") else "volume" + self._deformable_type = ( + "surface" if material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") else "volume" + ) # resolve root path back into regex expression # -- root prim expression @@ -596,18 +600,21 @@ def _initialize_impl(self): # -- object view if self._deformable_type == "surface": # surface deformable - self._root_physx_view = self._physics_sim_view.create_surface_deformable_body_view(root_prim_path_expr.replace(".*", "*")) + self._root_physx_view = self._physics_sim_view.create_surface_deformable_body_view( + root_prim_path_expr.replace(".*", "*") + ) else: # volume deformable - self._root_physx_view = self._physics_sim_view.create_volume_deformable_body_view(root_prim_path_expr.replace(".*", "*")) + self._root_physx_view = self._physics_sim_view.create_volume_deformable_body_view( + root_prim_path_expr.replace(".*", "*") + ) # Return if the asset is not found if self._root_physx_view._backend is None: raise RuntimeError(f"Failed to create deformable body at: {self.cfg.prim_path}. Please check PhysX logs.") # Check validity of deformables in view if not self._root_physx_view.check(): - # raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") - logger.warning(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") + raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression if material_prim is not None: @@ -689,7 +696,9 @@ def _create_buffers(self): wp.launch( set_kinematic_flags_to_one, dim=(self.num_instances * self.max_sim_vertices_per_body,), - inputs=[self._data.nodal_kinematic_target.reshape((self.num_instances * self.max_sim_vertices_per_body,))], + inputs=[ + self._data.nodal_kinematic_target.reshape((self.num_instances * self.max_sim_vertices_per_body,)) + ], device=self.device, ) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py index f3d794987a55..5d18c7193240 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py @@ -170,9 +170,7 @@ def sim_element_quat_w(self) -> wp.array: The rotations are stored as quaternions in the order (x, y, z, w). """ # deprecated - raise NotImplementedError( - "The sim_element_quat_w property is deprecated." - ) + raise NotImplementedError("The sim_element_quat_w property is deprecated.") @property def collision_element_quat_w(self) -> wp.array: @@ -182,9 +180,7 @@ def collision_element_quat_w(self) -> wp.array: The rotations are stored as quaternions in the order (x, y, z, w). """ # deprecated - raise NotImplementedError( - "The collision_element_quat_w property is deprecated." - ) + raise NotImplementedError("The collision_element_quat_w property is deprecated.") @property def sim_element_deform_gradient_w(self) -> wp.array: @@ -192,9 +188,7 @@ def sim_element_deform_gradient_w(self) -> wp.array: in simulation world frame. Shape is (num_instances, max_sim_elements_per_body, 3, 3). """ # deprecated - raise NotImplementedError( - "The sim_element_deform_gradient_w property is deprecated." - ) + raise NotImplementedError("The sim_element_deform_gradient_w property is deprecated.") @property def collision_element_deform_gradient_w(self) -> wp.array: @@ -202,9 +196,7 @@ def collision_element_deform_gradient_w(self) -> wp.array: in simulation world frame. Shape is (num_instances, max_collision_elements_per_body, 3, 3). """ # deprecated - raise NotImplementedError( - "The collision_element_deform_gradient_w property is deprecated." - ) + raise NotImplementedError("The collision_element_deform_gradient_w property is deprecated.") @property def sim_element_stress_w(self) -> wp.array: @@ -212,9 +204,7 @@ def sim_element_stress_w(self) -> wp.array: in simulation world frame. Shape is (num_instances, max_sim_elements_per_body, 3, 3). """ # deprecated - raise NotImplementedError( - "The sim_element_stress_w property is deprecated." - ) + raise NotImplementedError("The sim_element_stress_w property is deprecated.") @property def collision_element_stress_w(self) -> wp.array: @@ -222,9 +212,7 @@ def collision_element_stress_w(self) -> wp.array: in simulation world frame. Shape is (num_instances, max_collision_elements_per_body, 3, 3). """ # deprecated - raise NotImplementedError( - "The collision_element_stress_w property is deprecated." - ) + raise NotImplementedError("The collision_element_stress_w property is deprecated.") ## # Derived properties. diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 15987450da8f..f3c92cd8845c 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -8,18 +8,17 @@ import logging -from pxr import Usd, UsdGeom - -from isaaclab.sim.utils.stage import get_current_stage -from isaaclab.utils.string import to_camel_case +from omni.physx.scripts import deformableUtils +from pxr import Usd from isaaclab.sim.utils import ( apply_nested, get_all_matching_child_prims, safe_set_attribute_on_usd_prim, ) +from isaaclab.sim.utils.stage import get_current_stage +from isaaclab.utils.string import to_camel_case -from omni.physx.scripts import deformableUtils from isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg # import logger @@ -30,8 +29,13 @@ Deformable body properties. """ + def define_deformable_body_properties( - prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None, deformable_type: str = "volume", sim_mesh_prim_path: str | None = None + prim_path: str, + cfg: DeformableBodyPropertiesCfg, + stage: Usd.Stage | None = None, + deformable_type: str = "volume", + sim_mesh_prim_path: str | None = None, ): """Apply the deformable body schema on the input prim and set its properties. @@ -47,7 +51,7 @@ def define_deformable_body_properties( cfg: The configuration for the deformable body. stage: The stage where to find the prim. Defaults to None, in which case the current stage is used. - deformable_type: The type of the deformable body (surface or volume). + deformable_type: The type of the deformable body (surface or volume). This is used to determine which PhysX API to use for the deformable body. Defaults to "volume". Raises: @@ -83,29 +87,29 @@ def define_deformable_body_properties( # check if the prim is valid if not mesh_prim.IsValid(): raise ValueError(f"Mesh prim path '{mesh_prim_path}' is not valid.") - + # set root prim properties based on the type of the deformable mesh (surface vs volume) if deformable_type == "surface": sim_mesh_prim_path = prim_path + "/sim_mesh" if sim_mesh_prim_path is None else sim_mesh_prim_path success = deformableUtils.create_auto_surface_deformable_hierarchy( - stage=stage, + stage=stage, root_prim_path=prim_path, simulation_mesh_path=sim_mesh_prim_path, cooking_src_mesh_path=mesh_prim_path, cooking_src_simplification_enabled=False, - set_visibility_with_guide_purpose=True + set_visibility_with_guide_purpose=True, ) elif deformable_type == "volume": sim_mesh_prim_path = prim_path + "/sim_tetmesh" if sim_mesh_prim_path is None else sim_mesh_prim_path success = deformableUtils.create_auto_volume_deformable_hierarchy( - stage=stage, + stage=stage, root_prim_path=prim_path, simulation_tetmesh_path=sim_mesh_prim_path, collision_tetmesh_path=sim_mesh_prim_path, cooking_src_mesh_path=mesh_prim_path, simulation_hex_mesh_enabled=False, cooking_src_simplification_enabled=False, - set_visibility_with_guide_purpose=True + set_visibility_with_guide_purpose=True, ) else: print(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") @@ -120,14 +124,12 @@ def define_deformable_body_properties( @apply_nested -def modify_deformable_body_properties( - prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None -): +def modify_deformable_body_properties(prim_path: str, cfg: DeformableBodyPropertiesCfg, stage: Usd.Stage | None = None): """Modify PhysX parameters for a deformable body prim. - A `deformable body`_ is a single body (either surface or volume deformable) that can be simulated by PhysX. Unlike rigid bodies, deformable bodies - support relative motion of the nodes in the mesh. Consequently, they can be used to simulate deformations - under applied forces. + A `deformable body`_ is a single body (either surface or volume deformable) that can be simulated by PhysX. + Unlike rigid bodies, deformable bodies support relative motion of the nodes in the mesh. + Consequently, they can be used to simulate deformations under applied forces. PhysX deformable body simulation employs Finite Element Analysis (FEA) to simulate the deformations of the mesh. It uses two meshes to represent the deformable body: @@ -138,7 +140,8 @@ def modify_deformable_body_properties( For most applications, we assume that the above two meshes are computed from the "render mesh" of the deformable body. The render mesh is the mesh that is visible in the scene and is used for rendering purposes. It is composed - of triangles, while the simulation mesh is composed of tetrahedrons for volume deformables, and triangles for surface deformables. + of triangles, while the simulation mesh is composed of tetrahedrons for volume deformables, + and triangles for surface deformables. .. caution:: The deformable body schema is still under development by the Omniverse team. The current implementation @@ -172,19 +175,19 @@ def modify_deformable_body_properties( # check if deformable body API is applied if "OmniPhysicsBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): return False - + # apply customization to deformable API if "PhysxBaseDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): deformable_body_prim.AddAppliedSchema("PhysxBaseDeformableBodyAPI") - # ensure PhysX collision API is applied on the collision mesh + # ensure PhysX collision API is applied on the collision mesh if "PhysxCollisionAPI" not in deformable_body_prim.GetAppliedSchemas(): deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") # convert to dict cfg = cfg.to_dict() - # set into PhysX API (collision prim attributes: physxCollision:* for rest/contact offset, physxDeformable:* for rest on deformable prim) - # prefixes for each attribute + # set into PhysX API + # prefixes for each attribute (collision attributes: physxCollision:*, and physxDeformable:* for rest) property_prefixes = cfg["_property_prefix"] for prefix, attr_list in property_prefixes.items(): for attr_name in attr_list: diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py index 77721c405a60..7d4c934440ba 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -40,50 +40,66 @@ class PhysXDeformableBodyPropertiesCfg: """Number of the solver positional iterations per step. Range is [1,255], default to 16.""" linear_damping: float | None = None - """Linear damping coefficient, in units of 1/seconds and constrainted to the range [0, inf).""" + """Linear damping coefficient, in units of 1/seconds and constrained to the range [0, inf).""" max_linear_velocity: float | None = None - """Maximum allowable linear velocity for the deformable body, in units of distance/second and constrained to the range [0, inf). A negative value allows the simulation to choose suitable a per vertex value dynamically, currently only supported for surface deformables. This can help prevent surface-surface intersections.""" + """Maximum allowable linear velocity for the deformable body, in units of distance/second and constrained to the + range [0, inf). A negative value allows the simulation to choose suitable a per vertex value dynamically, + currently only supported for surface deformables. This can help prevent surface-surface intersections.""" settling_damping: float | None = None - """Additional damping applied when a vertex's velocity falls below :attr:`settlingThreshold`. Specified in units of 1/seconds and constrained to the range [0, inf).""" + """Additional damping applied when a vertex's velocity falls below :attr:`settlingThreshold`. + Specified in units of 1/seconds and constrained to the range [0, inf).""" settling_threshold: float | None = None - """Velocity threshold below which :attr:`settlingDamping` is applied in addition to standard damping. Specified in units of distance/second and constrained to the range [0, inf).""" + """Velocity threshold below which :attr:`settlingDamping` is applied in addition to standard damping. + Specified in units of distance/second and constrained to the range [0, inf).""" sleep_threshold: float | None = None - """Velocity threshold below which a vertex becomes a candidate for sleeping. Specified in units of distance/seconds and constraint to the range [0, inf).""" + """Velocity threshold below which a vertex becomes a candidate for sleeping. + Specified in units of distance/seconds and constrained to the range [0, inf).""" max_depenetration_velocity: float | None = None - """Maximum velocity that the solver may apply to resolve intersections. Specified in units of distance/seconds and constraint to the range [0, inf).""" + """Maximum velocity that the solver may apply to resolve intersections. + Specified in units of distance/seconds and constrained to the range [0, inf).""" self_collision: bool | None = None """Enables self-collisions for the deformable body, preventing self-intersections.""" self_collision_filter_distance: float | None = None - """Distance below which self-collision is disabled. The default value of -inf indicates that the simulation selects a suitable value. Specified in units of distance and constraint to the range [:attr:`rest_offset` * 2, inf]. + """Distance below which self-collision is disabled. The default value of -inf indicates that the simulation + selects a suitable value. Specified in units of distance and constrained to range [:attr:`rest_offset`*2, inf]. """ enable_speculative_c_c_d: bool | None = None - """Enables dynamic adjustment of the contact offset based on velocity (speculative continuous colision detection).""" + """Enables dynamic adjustment of contact offset based on velocity (speculative continuous collision detection).""" disable_gravity: bool | None = None """Disables gravity for the deformable body.""" # specific to surface deformables collision_pair_update_frequency: int | None = None - """Determines how often surface-to-surface collision pairs are updated during each time step. Increasing this value results in more frequent updates to the contact pairs, which provides better contact points. - - For example, a value of 2 means collision pairs are updated twice per time step: once at the beginning and once in the middle of the time step (i.e., during the middle solver iteration). If set to 0, the solver adaptively determines when to update the surface-to-surface contact pairs, instead of using a fixed frequency. - + """Determines how often surface-to-surface collision pairs are updated during each time step. + Increasing this value results in more frequent updates to the contact pairs, which provides better contact points. + + For example, a value of 2 means collision pairs are updated twice per time step: + once at the beginning and once in the middle of the time step (i.e., during the middle solver iteration). + If set to 0, the solver adaptively determines when to update the surface-to-surface contact pairs, + instead of using a fixed frequency. + Valid range: [1, :attr:`solver_position_iteration_count`]. """ collision_iteration_multiplier: float | None = None - """Determines how many collision subiterations are used in each solver iteration. By default, collision constraints are applied once per solver iteration. Increasing this value applies collision constraints more frequently within each solver iteration. - - For example, a value of 2 means collision constraints are applied twice per solver iteration (i.e., collision constraints are applied 2 x :attr:`solver_position_iteration_count` times per time step). Increasing this value does not update collision pairs more frequently; refer to :attr:`collision_pair_update_frequency` for that. - + """Determines how many collision subiterations are used in each solver iteration. + By default, collision constraints are applied once per solver iteration. + Increasing this value applies collision constraints more frequently within each solver iteration. + + For example, a value of 2 means collision constraints are applied twice per solver iteration + (i.e., collision constraints are applied 2 x :attr:`solver_position_iteration_count` times per time step). + Increasing this value does not update collision pairs more frequently; + refer to :attr:`collision_pair_update_frequency` for that. + Valid range: [1, :attr:`solver_position_iteration_count` / 2]. """ @@ -96,7 +112,7 @@ class PhysXCollisionPropertiesCfg: See the PhysX documentation for more information on the available properties. """ - + contact_offset: float | None = None """Contact offset for the collision shape (in m). @@ -115,14 +131,17 @@ class PhysXCollisionPropertiesCfg: @configclass -class DeformableBodyPropertiesCfg(OmniPhysicsPropertiesCfg, PhysXDeformableBodyPropertiesCfg, PhysXCollisionPropertiesCfg): +class DeformableBodyPropertiesCfg( + OmniPhysicsPropertiesCfg, PhysXDeformableBodyPropertiesCfg, PhysXCollisionPropertiesCfg +): """Properties to apply to a deformable body. - A deformable body is a body that can deform under forces, both surface and volume deformables. The configuration allows users to specify - the properties of the deformable body, such as the solver iteration counts, damping, and self-collision. + A deformable body is a body that can deform under forces, both surface and volume deformables. + The configuration allows users to specify the properties of the deformable body, + such as the solver iteration counts, damping, and self-collision. An FEM-based deformable body is created by providing a collision mesh and simulation mesh. The collision mesh - is used for collision detection and the simulation mesh is used for simulation. + is used for collision detection and the simulation mesh is used for simulation. See :meth:`modify_deformable_body_properties` for more information. diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi index f4af357a3e93..6e8a48b2f118 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/__init__.pyi @@ -13,4 +13,4 @@ from .physics_materials import spawn_deformable_body_material from .physics_materials_cfg import ( DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg, -) \ No newline at end of file +) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py index d85e8d7b91eb..dd4bbb5e4f56 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -8,8 +8,8 @@ import dataclasses from collections.abc import Callable -from isaaclab.utils import configclass from isaaclab.sim.spawners.materials import PhysicsMaterialCfg +from isaaclab.utils import configclass @configclass @@ -48,10 +48,12 @@ class OmniPhysicsDeformableMaterialCfg: @configclass class OmniPhysicsSurfaceDeformableMaterialCfg(OmniPhysicsDeformableMaterialCfg): - """OmniPhysics material properties for a surface deformable body, extending on :class:`OmniPhysicsDeformableMaterialCfg` with additional parameters for surface deformable bodies. + """OmniPhysics material properties for a surface deformable body, + extending on :class:`OmniPhysicsDeformableMaterialCfg` with additional parameters for surface deformable bodies. - These properties are set with the prefix ``omniphysics:``. For example, to set the surface thickness of the - surface deformable body, you would set the property ``omniphysics:surfaceThickness``. + These properties are set with the prefix ``omniphysics:``. + For example, to set the surface thickness of the surface deformable body, + you would set the property ``omniphysics:surfaceThickness``. See the OmniPhysics documentation for more information on the available properties. """ @@ -76,8 +78,9 @@ class OmniPhysicsSurfaceDeformableMaterialCfg(OmniPhysicsDeformableMaterialCfg): class PhysXDeformableMaterialCfg: """PhysX-specific material properties for a deformable body. - These properties are set with the prefix ``physxDeformableBody:``. For example, to set the elasticity damping of the - deformable body, you would set the property ``physxDeformableBody:elasticityDamping``. + These properties are set with the prefix ``physxDeformableBody:``. + For example, to set the elasticity damping of the deformable body, + you would set the property ``physxDeformableBody:elasticityDamping``. See the PhysX documentation for more information on the available properties. """ @@ -104,7 +107,8 @@ class DeformableBodyMaterialCfg(PhysicsMaterialCfg, OmniPhysicsDeformableMateria @configclass class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg, OmniPhysicsSurfaceDeformableMaterialCfg): - """Physics material parameters for surface deformable bodies, extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. + """Physics material parameters for surface deformable bodies, + extending on :class:`DeformableBodyMaterialCfg` with additional parameters for surface deformable bodies. See :meth:`spawn_deformable_body_material` for more information. """ @@ -115,4 +119,4 @@ class SurfaceDeformableBodyMaterialCfg(DeformableBodyMaterialCfg, OmniPhysicsSur "omniphysics": [field.name for field in dataclasses.fields(OmniPhysicsSurfaceDeformableMaterialCfg)], "physxDeformableBody": [field.name for field in dataclasses.fields(PhysXDeformableMaterialCfg)], } - """Extend DeformableBodyMaterialCfg properties under each prefix.""" \ No newline at end of file + """Extend DeformableBodyMaterialCfg properties under each prefix.""" diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py index e746158b8130..dda028508762 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/spawner_cfg.py @@ -7,12 +7,12 @@ from typing import TYPE_CHECKING -from isaaclab.utils import configclass from isaaclab.sim.spawners.spawner_cfg import SpawnerCfg - +from isaaclab.utils import configclass if TYPE_CHECKING: from isaaclab.sim import schemas + # deformables only supported on PhysX backend from isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg From 908995febf57f2f656e367f3e741ae518d08e808 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 09:49:34 +0100 Subject: [PATCH 47/73] Style: Cleanup structure --- source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 79fc4b618046..4d9ec980aea5 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -396,13 +396,12 @@ def _spawn_mesh_geom_from_mesh( if cfg.mass_props is not None: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply deformable body properties + deformable_type = "surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume" schemas_physx.define_deformable_body_properties( prim_path, cfg.deformable_props, stage=stage, - deformable_type="surface" - if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) - else "volume", + deformable_type=deformable_type ) elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh From 7b790953c48a6ace6321ca833ac8d7b4f31ed0d0 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 10:49:08 +0100 Subject: [PATCH 48/73] Fix: without physics material, a default is spawned. DeformableObject cannot be instantiated without physics material --- .../isaaclab/sim/spawners/meshes/meshes.py | 19 +++++++++ .../deformable_object/deformable_object.py | 39 ++++++++----------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 4d9ec980aea5..e5260dca09eb 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -5,6 +5,7 @@ from __future__ import annotations +import logging from typing import TYPE_CHECKING import numpy as np @@ -25,6 +26,9 @@ if TYPE_CHECKING: from . import meshes_cfg +# import logger +logger = logging.getLogger(__name__) + @clone def spawn_mesh_sphere( @@ -440,6 +444,21 @@ def _spawn_mesh_geom_from_mesh( cfg.physics_material.func(material_path, cfg.physics_material) # apply material bind_physics_material(prim_path, material_path, stage=stage) + elif cfg.deformable_props is not None: + # if deformable properties are used but no physics material is specified, then we create a default material + material_path = "/World/DefaultDeformableMaterial" + if not stage.GetPrimAtPath(material_path).IsValid(): + default_physics_material = DeformableBodyMaterialCfg() + default_physics_material.func(material_path, default_physics_material) + # apply material + bind_physics_material(prim_path, material_path, stage=stage) + + logger.info( + f"Failed to find a deformable material binding for '{prim_path}'." + " The material properties will be set to default values and are not modifiable at runtime." + " If you want to modify the material properties, please ensure that the material is bound" + " to the deformable body." + ) # note: we apply the rigid properties to the parent prim in case of rigid objects. if cfg.rigid_props is not None: diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index e88490666ad9..2a3f278b2220 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -10,6 +10,7 @@ from collections.abc import Sequence from typing import TYPE_CHECKING +from isaaclab_physx.sim import DeformableBodyMaterialCfg import numpy as np import torch import warp as wp @@ -582,11 +583,8 @@ def _initialize_impl(self): material_prim = mat_prim break if material_prim is None: - logger.info( + raise RuntimeError( f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." - " The material properties will be set to default values and are not modifiable at runtime." - " If you want to modify the material properties, please ensure that the material is bound" - " to the deformable body." ) # deformable type based on material that is applied self._deformable_type = ( @@ -617,31 +615,26 @@ def _initialize_impl(self): raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression - if material_prim is not None: - # -- material prim expression - material_prim_path = material_prim.GetPath().pathString - # check if the material prim is under the template prim - # if not then we are assuming that the single material prim is used for all the deformable bodies - if template_prim_path in material_prim_path: - material_prim_path_expr = self.cfg.prim_path + material_prim_path[len(template_prim_path) :] - else: - material_prim_path_expr = material_prim_path - # -- material view - self._material_physx_view = self._physics_sim_view.create_deformable_material_view( - material_prim_path_expr.replace(".*", "*") - ) + # -- material prim expression + material_prim_path = material_prim.GetPath().pathString + # check if the material prim is under the template prim + # if not then we are assuming that the single material prim is used for all the deformable bodies + if template_prim_path in material_prim_path: + material_prim_path_expr = self.cfg.prim_path + material_prim_path[len(template_prim_path) :] else: - self._material_physx_view = None + material_prim_path_expr = material_prim_path + # -- material view + self._material_physx_view = self._physics_sim_view.create_deformable_material_view( + material_prim_path_expr.replace(".*", "*") + ) # log information about the deformable body logger.info(f"Deformable body initialized at: {root_prim_path_expr}") logger.info(f"Number of instances: {self.num_instances}") logger.info(f"Number of bodies: {self.num_bodies}") - if self._material_physx_view is not None: - logger.info(f"Deformable material initialized at: {material_prim_path_expr}") - logger.info(f"Number of instances: {self._material_physx_view.count}") - else: - logger.info("No deformable material found. Material properties will be set to default values.") + logger.info(f"Deformable material initialized at: {material_prim_path_expr}") + logger.info(f"Number of instances: {self._material_physx_view.count}") + # container for data access self._data = DeformableObjectData(self.root_view, self.device) From 8d59b9ffe71f57f528b0576e1789283235528d4f Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 10:49:23 +0100 Subject: [PATCH 49/73] Fix: Throw warning for using kinematic enabled with physx backend --- source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index f3c92cd8845c..e1699a1db4cc 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -187,6 +187,12 @@ def modify_deformable_body_properties(prim_path: str, cfg: DeformableBodyPropert # convert to dict cfg = cfg.to_dict() # set into PhysX API + # TODO: kinematic enabled does not work properly with creating DeformableView in Omni Physics 110.0. + if cfg["kinematic_enabled"]: + logger.warning( + "Kinematic deformable bodies are not fully supported in the current version of Omni Physics. " + "Setting kinematic_enabled to True may lead to unexpected behavior." + ) # prefixes for each attribute (collision attributes: physxCollision:*, and physxDeformable:* for rest) property_prefixes = cfg["_property_prefix"] for prefix, attr_list in property_prefixes.items(): From 129a8405a79828cda1077b1e39e470c94a6d73af Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 10:53:03 +0100 Subject: [PATCH 50/73] Feat: Allow default material for deformable USD --- .../sim/spawners/from_files/from_files.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index a3fda6153452..682017e5d1b8 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -13,7 +13,7 @@ # deformables only supported on PhysX backend from isaaclab_physx.sim import schemas as schemas_physx -from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg +from isaaclab_physx.sim.spawners.materials import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg from pxr import Gf, Sdf, Usd, UsdGeom @@ -400,6 +400,22 @@ def _spawn_from_usd_file( cfg.physics_material.func(material_path, cfg.physics_material) # apply material bind_physics_material(prim_path, material_path, stage=stage) + elif cfg.deformable_props is not None: + # if deformable properties are used but no physics material is specified, then we create a default material + material_path = "/World/DefaultDeformableMaterial" + if not stage.GetPrimAtPath(material_path).IsValid(): + default_physics_material = DeformableBodyMaterialCfg() + default_physics_material.func(material_path, default_physics_material) + # apply material + bind_physics_material(prim_path, material_path, stage=stage) + + logger.info( + f"Failed to find a deformable material binding for '{prim_path}'." + " The material properties will be set to default values and are not modifiable at runtime." + " If you want to modify the material properties, please ensure that the material is bound" + " to the deformable body." + ) + # return the prim return stage.GetPrimAtPath(prim_path) From 2e5c5cb0eb15712d72c36d3a9fa3636033dc9bea Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 11:01:37 +0100 Subject: [PATCH 51/73] Style: Return to physx defaults if material is none --- .../sim/spawners/from_files/from_files.py | 18 +------- .../isaaclab/sim/spawners/meshes/meshes.py | 19 --------- .../deformable_object/deformable_object.py | 41 +++++++++++-------- 3 files changed, 26 insertions(+), 52 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 682017e5d1b8..a3fda6153452 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -13,7 +13,7 @@ # deformables only supported on PhysX backend from isaaclab_physx.sim import schemas as schemas_physx -from isaaclab_physx.sim.spawners.materials import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg +from isaaclab_physx.sim.spawners.materials import SurfaceDeformableBodyMaterialCfg from pxr import Gf, Sdf, Usd, UsdGeom @@ -400,22 +400,6 @@ def _spawn_from_usd_file( cfg.physics_material.func(material_path, cfg.physics_material) # apply material bind_physics_material(prim_path, material_path, stage=stage) - elif cfg.deformable_props is not None: - # if deformable properties are used but no physics material is specified, then we create a default material - material_path = "/World/DefaultDeformableMaterial" - if not stage.GetPrimAtPath(material_path).IsValid(): - default_physics_material = DeformableBodyMaterialCfg() - default_physics_material.func(material_path, default_physics_material) - # apply material - bind_physics_material(prim_path, material_path, stage=stage) - - logger.info( - f"Failed to find a deformable material binding for '{prim_path}'." - " The material properties will be set to default values and are not modifiable at runtime." - " If you want to modify the material properties, please ensure that the material is bound" - " to the deformable body." - ) - # return the prim return stage.GetPrimAtPath(prim_path) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index e5260dca09eb..4d9ec980aea5 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -5,7 +5,6 @@ from __future__ import annotations -import logging from typing import TYPE_CHECKING import numpy as np @@ -26,9 +25,6 @@ if TYPE_CHECKING: from . import meshes_cfg -# import logger -logger = logging.getLogger(__name__) - @clone def spawn_mesh_sphere( @@ -444,21 +440,6 @@ def _spawn_mesh_geom_from_mesh( cfg.physics_material.func(material_path, cfg.physics_material) # apply material bind_physics_material(prim_path, material_path, stage=stage) - elif cfg.deformable_props is not None: - # if deformable properties are used but no physics material is specified, then we create a default material - material_path = "/World/DefaultDeformableMaterial" - if not stage.GetPrimAtPath(material_path).IsValid(): - default_physics_material = DeformableBodyMaterialCfg() - default_physics_material.func(material_path, default_physics_material) - # apply material - bind_physics_material(prim_path, material_path, stage=stage) - - logger.info( - f"Failed to find a deformable material binding for '{prim_path}'." - " The material properties will be set to default values and are not modifiable at runtime." - " If you want to modify the material properties, please ensure that the material is bound" - " to the deformable body." - ) # note: we apply the rigid properties to the parent prim in case of rigid objects. if cfg.rigid_props is not None: diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 2a3f278b2220..28a1674cb560 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -583,12 +583,16 @@ def _initialize_impl(self): material_prim = mat_prim break if material_prim is None: - raise RuntimeError( + logger.info( f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." + " The material properties will be set to default values and are not modifiable at runtime." + " If you want to modify the material properties, please ensure that the material is bound" + " to the deformable body." ) # deformable type based on material that is applied self._deformable_type = ( - "surface" if material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") else "volume" + "surface" if material_prim is not None and material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") + else "volume" ) # resolve root path back into regex expression @@ -615,26 +619,31 @@ def _initialize_impl(self): raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression - # -- material prim expression - material_prim_path = material_prim.GetPath().pathString - # check if the material prim is under the template prim - # if not then we are assuming that the single material prim is used for all the deformable bodies - if template_prim_path in material_prim_path: - material_prim_path_expr = self.cfg.prim_path + material_prim_path[len(template_prim_path) :] + if material_prim is not None: + # -- material prim expression + material_prim_path = material_prim.GetPath().pathString + # check if the material prim is under the template prim + # if not then we are assuming that the single material prim is used for all the deformable bodies + if template_prim_path in material_prim_path: + material_prim_path_expr = self.cfg.prim_path + material_prim_path[len(template_prim_path) :] + else: + material_prim_path_expr = material_prim_path + # -- material view + self._material_physx_view = self._physics_sim_view.create_deformable_material_view( + material_prim_path_expr.replace(".*", "*") + ) else: - material_prim_path_expr = material_prim_path - # -- material view - self._material_physx_view = self._physics_sim_view.create_deformable_material_view( - material_prim_path_expr.replace(".*", "*") - ) + self._material_physx_view = None # log information about the deformable body logger.info(f"Deformable body initialized at: {root_prim_path_expr}") logger.info(f"Number of instances: {self.num_instances}") logger.info(f"Number of bodies: {self.num_bodies}") - logger.info(f"Deformable material initialized at: {material_prim_path_expr}") - logger.info(f"Number of instances: {self._material_physx_view.count}") - + if self._material_physx_view is not None: + logger.info(f"Deformable material initialized at: {material_prim_path_expr}") + logger.info(f"Number of instances: {self._material_physx_view.count}") + else: + logger.info("No deformable material found. Material properties will be set to default values.") # container for data access self._data = DeformableObjectData(self.root_view, self.device) From acc4339a61af340c1af82fc766c8132222a61f06 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 11:24:04 +0100 Subject: [PATCH 52/73] Fix: surface and volume deformables cannot share a view --- scripts/demos/deformables.py | 42 ++++++++++++------- .../deformable_object/deformable_object.py | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index d49e938c5d21..b3aba99570eb 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -196,6 +196,8 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Create separate groups of deformable objects origins = define_origins(num_origins=12, radius=1.5, center_height=2.0) print("[INFO]: Spawning objects...") + num_volumes = 0 + num_surfaces = 0 # Iterate over all the origins and randomly spawn objects for idx, origin in tqdm.tqdm(enumerate(origins), total=len(origins)): # randomly select an object to spawn @@ -210,19 +212,34 @@ def design_scene() -> tuple[dict, list[list[float]]]: obj_cfg.physics_material.poissons_ratio = random.uniform(0.25, 0.45) # randomize the color obj_cfg.visual_material.diffuse_color = (random.random(), random.random(), random.random()) - # spawn the object - obj_cfg.func(f"/World/Origin/Object{idx:02d}", obj_cfg, translation=origin) - - # create a view for all the deformables + # spawn the object, separate groups for surface and volume deformables + if obj_name in ["cloth"]: + obj_cfg.func(f"/World/Origin/Surface{idx:02d}", obj_cfg, translation=origin) + num_surfaces += 1 + else: + obj_cfg.func(f"/World/Origin/Volume{idx:02d}", obj_cfg, translation=origin) + num_volumes += 1 + + # create a view for all the deformables, separate views for volume and surface deformables # note: since we manually spawned random deformable meshes above, we don't need to # specify the spawn configuration for the deformable object - cfg = DeformableObjectCfg( - prim_path="/World/Origin/Object.*", - spawn=None, - init_state=DeformableObjectCfg.InitialStateCfg(), - ) - deformable_object = DeformableObject(cfg=cfg) - scene_entities = {"deformable_object": deformable_object} + scene_entities = {} + if num_volumes > 0: + cfg = DeformableObjectCfg( + prim_path="/World/Origin/Volume.*", + spawn=None, + init_state=DeformableObjectCfg.InitialStateCfg(), + ) + volume_deformable_object = DeformableObject(cfg=cfg) + scene_entities["volume_deformable_object"] = volume_deformable_object + if num_surfaces > 0: + cfg = DeformableObjectCfg( + prim_path="/World/Origin/Surface.*", + spawn=None, + init_state=DeformableObjectCfg.InitialStateCfg(), + ) + surface_deformable_object = DeformableObject(cfg=cfg) + scene_entities["surface_deformable_object"] = surface_deformable_object # Create camera if saving frames if args_cli.save: @@ -250,9 +267,6 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab camera_targets = torch.tensor([[0.0, 0.0, 0.5]], device=sim.device) camera.set_world_poses_from_view(camera_positions, camera_targets) - dof = torch.tensor(entities["deformable_object"].root_view.get_simulation_nodal_positions().shape).prod().item() - print(f"[INFO]: Solving for {dof:,} Degrees of Freedom.") - # Define simulation stepping sim_dt = sim.get_physics_dt() assert sim_dt <= 1.0 / args_cli.video_fps, ( diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 28a1674cb560..fd8c34b97295 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -616,7 +616,7 @@ def _initialize_impl(self): raise RuntimeError(f"Failed to create deformable body at: {self.cfg.prim_path}. Please check PhysX logs.") # Check validity of deformables in view if not self._root_physx_view.check(): - raise RuntimeError(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") + logger.warning(f"Deformable body view is not valid for: {self.cfg.prim_path}. Please check PhysX logs.") # resolve material path back into regex expression if material_prim is not None: From 03ede7dfe191b878536577551255e5698b4a6f19 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 11:55:46 +0100 Subject: [PATCH 53/73] Fix: Deprecated tests removed, changed default stiffness to stable value --- .../materials/physics_materials_cfg.py | 4 +- .../test/assets/test_deformable_object.py | 72 ++----------------- 2 files changed, 7 insertions(+), 69 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py index dd4bbb5e4f56..ea09a2330546 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -31,8 +31,8 @@ class OmniPhysicsDeformableMaterialCfg: dynamic_friction: float = 0.25 """The dynamic friction. Defaults to 0.25.""" - youngs_modulus: float = 50000000.0 - """The Young's modulus, which defines the body's stiffness. Defaults to 50000000.0. + youngs_modulus: float = 1000000.0 + """The Young's modulus, which defines the body's stiffness. Defaults to 1MPa. The Young's modulus is a measure of the material's ability to deform under stress. It is measured in Pascals (Pa). """ diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index a0dcd199b1ee..10d86fb28130 100644 --- a/source/isaaclab_physx/test/assets/test_deformable_object.py +++ b/source/isaaclab_physx/test/assets/test_deformable_object.py @@ -23,6 +23,7 @@ import warp as wp from flaky import flaky from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg +from isaaclab_physx.sim import DeformableBodyMaterialCfg, DeformableBodyPropertiesCfg import carb @@ -65,11 +66,11 @@ def generate_cubes_scene( if has_api: spawn_cfg = sim_utils.MeshCuboidCfg( size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.DeformableBodyPropertiesCfg(kinematic_enabled=kinematic_enabled), + deformable_props=DeformableBodyPropertiesCfg(kinematic_enabled=kinematic_enabled), ) # Add physics material if provided if material_path is not None: - spawn_cfg.physics_material = sim_utils.DeformableBodyMaterialCfg() + spawn_cfg.physics_material = DeformableBodyMaterialCfg() spawn_cfg.physics_material_path = material_path else: spawn_cfg.physics_material = None @@ -142,42 +143,6 @@ def test_initialization(sim, num_cubes, material_path): assert wp.to_torch(cube_object.data.root_pos_w).shape == (num_cubes, 3) assert wp.to_torch(cube_object.data.root_vel_w).shape == (num_cubes, 3) - # Simulate physics - for _ in range(2): - sim.step() - cube_object.update(sim.cfg.dt) - - # Check sim data - assert wp.to_torch(cube_object.data.sim_element_quat_w).shape == ( - num_cubes, - cube_object.max_sim_elements_per_body, - 4, - ) - assert cube_object.data.sim_element_deform_gradient_w.shape == ( - num_cubes, - cube_object.max_sim_elements_per_body, - 3, - 3, - ) - assert cube_object.data.sim_element_stress_w.shape == (num_cubes, cube_object.max_sim_elements_per_body, 3, 3) - assert wp.to_torch(cube_object.data.collision_element_quat_w).shape == ( - num_cubes, - cube_object.max_collision_elements_per_body, - 4, - ) - assert cube_object.data.collision_element_deform_gradient_w.shape == ( - num_cubes, - cube_object.max_collision_elements_per_body, - 3, - 3, - ) - assert cube_object.data.collision_element_stress_w.shape == ( - num_cubes, - cube_object.max_collision_elements_per_body, - 3, - 3, - ) - @pytest.mark.isaacsim_ci def test_initialization_on_device_cpu(): @@ -194,33 +159,6 @@ def test_initialization_on_device_cpu(): sim.reset() -@pytest.mark.parametrize("num_cubes", [1, 2]) -@pytest.mark.isaacsim_ci -def test_initialization_with_kinematic_enabled(sim, num_cubes): - """Test that initialization for prim with kinematic flag enabled.""" - cube_object = generate_cubes_scene(num_cubes=num_cubes, kinematic_enabled=True) - - # Check that the framework doesn't hold excessive strong references. - assert sys.getrefcount(cube_object) < 10 - - # Play sim - sim.reset() - - # Check if object is initialized - assert cube_object.is_initialized - - # Check buffers that exist and have correct shapes - assert wp.to_torch(cube_object.data.root_pos_w).shape == (num_cubes, 3) - assert wp.to_torch(cube_object.data.root_vel_w).shape == (num_cubes, 3) - - # Simulate physics - for _ in range(2): - sim.step() - cube_object.update(sim.cfg.dt) - default_nodal_state_w = wp.to_torch(cube_object.data.default_nodal_state_w).clone() - torch.testing.assert_close(wp.to_torch(cube_object.data.nodal_state_w), default_nodal_state_w) - - @pytest.mark.parametrize("num_cubes", [1, 2]) @pytest.mark.isaacsim_ci def test_set_nodal_state(sim, num_cubes): @@ -282,7 +220,7 @@ def test_set_nodal_state_with_applied_transform(num_cubes, randomize_pos, random mean_nodal_pos_default = nodal_state[..., :3].mean(dim=1) if randomize_pos: - pos_w = torch.rand(cube_object.num_instances, 3, device=sim.device) + pos_w = 0.5*torch.rand(cube_object.num_instances, 3, device=sim.device) pos_w[:, 2] += 0.5 else: pos_w = None @@ -319,7 +257,7 @@ def test_set_kinematic_targets(sim, num_cubes): sim.reset() - nodal_kinematic_targets = wp.to_torch(cube_object.root_view.get_sim_kinematic_targets()).clone() + nodal_kinematic_targets = wp.to_torch(cube_object.root_view.get_simulation_nodal_kinematic_targets()).clone() for _ in range(5): cube_object.write_nodal_state_to_sim(wp.to_torch(cube_object.data.default_nodal_state_w)) From debc97fe619278643fbae05ae1dd208fc7f2e3c3 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 12:00:18 +0100 Subject: [PATCH 54/73] Style: formatting --- source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py | 5 +---- .../assets/deformable_object/deformable_object.py | 4 ++-- source/isaaclab_physx/test/assets/test_deformable_object.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 4d9ec980aea5..45f70d80cea9 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -398,10 +398,7 @@ def _spawn_mesh_geom_from_mesh( # apply deformable body properties deformable_type = "surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume" schemas_physx.define_deformable_body_properties( - prim_path, - cfg.deformable_props, - stage=stage, - deformable_type=deformable_type + prim_path, cfg.deformable_props, stage=stage, deformable_type=deformable_type ) elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index fd8c34b97295..296670c274ca 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -10,7 +10,6 @@ from collections.abc import Sequence from typing import TYPE_CHECKING -from isaaclab_physx.sim import DeformableBodyMaterialCfg import numpy as np import torch import warp as wp @@ -591,7 +590,8 @@ def _initialize_impl(self): ) # deformable type based on material that is applied self._deformable_type = ( - "surface" if material_prim is not None and material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") + "surface" + if material_prim is not None and material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") else "volume" ) diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index 10d86fb28130..746f208b86be 100644 --- a/source/isaaclab_physx/test/assets/test_deformable_object.py +++ b/source/isaaclab_physx/test/assets/test_deformable_object.py @@ -220,7 +220,7 @@ def test_set_nodal_state_with_applied_transform(num_cubes, randomize_pos, random mean_nodal_pos_default = nodal_state[..., :3].mean(dim=1) if randomize_pos: - pos_w = 0.5*torch.rand(cube_object.num_instances, 3, device=sim.device) + pos_w = 0.5 * torch.rand(cube_object.num_instances, 3, device=sim.device) pos_w[:, 2] += 0.5 else: pos_w = None From 3d1914daa000518b9e26eb5336100d905273d9da Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 13:50:19 +0100 Subject: [PATCH 55/73] Style: Updated changelog --- CONTRIBUTORS.md | 1 + source/isaaclab/config/extension.toml | 2 +- source/isaaclab/docs/CHANGELOG.rst | 42 +++++++++++++++++ source/isaaclab_physx/config/extension.toml | 2 +- source/isaaclab_physx/docs/CHANGELOG.rst | 51 +++++++++++++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 318e1106c688..c383bf715092 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -34,6 +34,7 @@ Guidelines for modifications: * Kelly (Yunrong) Guo * Matthew Trepte * Mayank Mittal +* Mike Yan Michelis * Nikita Rudin * Octi (Zhengyu) Zhang * Ossama Ahmed diff --git a/source/isaaclab/config/extension.toml b/source/isaaclab/config/extension.toml index d6e20b221327..59b139eda365 100644 --- a/source/isaaclab/config/extension.toml +++ b/source/isaaclab/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "4.5.22" +version = "4.5.23" # Description title = "Isaac Lab framework for Robot Learning" diff --git a/source/isaaclab/docs/CHANGELOG.rst b/source/isaaclab/docs/CHANGELOG.rst index 11524c3b3182..54a1c51a8af9 100644 --- a/source/isaaclab/docs/CHANGELOG.rst +++ b/source/isaaclab/docs/CHANGELOG.rst @@ -1,6 +1,48 @@ Changelog --------- +4.5.23 (2026-03-17) +~~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added :class:`~isaaclab.sim.spawners.meshes.MeshSquareCfg` and + :func:`~isaaclab.sim.spawners.meshes.spawn_mesh_square` for spawning 2D triangle + mesh grids, used as surface deformable bodies (cloth). +* Added physics material support to + :func:`~isaaclab.sim.spawners.from_files.spawn_from_usd` for deformable bodies + loaded from USD files. + +Changed +^^^^^^^ + +* Moved :class:`DeformableBodyPropertiesCfg`, :class:`DeformableBodyMaterialCfg`, + and :class:`DeformableObjectSpawnerCfg` from ``isaaclab`` to ``isaaclab_physx``. + These are PhysX-specific and are now imported from + ``isaaclab_physx.sim.schemas``, ``isaaclab_physx.sim.spawners.materials``, and + ``isaaclab_physx.sim.spawners.spawner_cfg`` respectively. +* Changed deformable body property and material application in mesh and USD spawners + to use ``isaaclab_physx.sim.schemas`` instead of ``isaaclab.sim.schemas``. +* Changed :func:`~isaaclab.sim.spawners.from_files.spawn_from_usd` to call + ``define_deformable_body_properties`` when the deformable body API is not yet + present on the prim, instead of always calling ``modify_deformable_body_properties``. + +Removed +^^^^^^^ + +* Removed :func:`define_deformable_body_properties` and + :func:`modify_deformable_body_properties` from ``isaaclab.sim.schemas``. Use + ``isaaclab_physx.sim.schemas`` instead. +* Removed :class:`DeformableBodyPropertiesCfg` from ``isaaclab.sim.schemas``. Use + :class:`isaaclab_physx.sim.schemas.DeformableBodyPropertiesCfg` instead. +* Removed :class:`DeformableBodyMaterialCfg` and + :func:`spawn_deformable_body_material` from ``isaaclab.sim.spawners.materials``. + Use ``isaaclab_physx.sim.spawners.materials`` instead. +* Removed :class:`DeformableObjectSpawnerCfg` from ``isaaclab.sim.spawners``. Use + ``isaaclab_physx.sim.spawners.spawner_cfg.DeformableObjectSpawnerCfg`` instead. + + 4.5.22 (2026-03-16) ~~~~~~~~~~~~~~~~~~~ diff --git a/source/isaaclab_physx/config/extension.toml b/source/isaaclab_physx/config/extension.toml index b583489cd2c0..996af61314e5 100644 --- a/source/isaaclab_physx/config/extension.toml +++ b/source/isaaclab_physx/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.5.12" +version = "0.5.13" # Description title = "PhysX simulation interfaces for IsaacLab core package" diff --git a/source/isaaclab_physx/docs/CHANGELOG.rst b/source/isaaclab_physx/docs/CHANGELOG.rst index bcbdde0f9a3b..83e6518e8d99 100644 --- a/source/isaaclab_physx/docs/CHANGELOG.rst +++ b/source/isaaclab_physx/docs/CHANGELOG.rst @@ -1,6 +1,57 @@ Changelog --------- +0.5.13 (2026-03-17) +~~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added :class:`~isaaclab_physx.sim.schemas.DeformableBodyPropertiesCfg` with + namespace-aware property routing. Properties are organized into + ``omniphysics:``, ``physxDeformableBody:``, and ``physxCollision:`` prefixed + parent classes, allowing correct USD attribute mapping for the updated PhysX + deformable body schema. +* Added :func:`~isaaclab_physx.sim.schemas.define_deformable_body_properties` and + :func:`~isaaclab_physx.sim.schemas.modify_deformable_body_properties` to + ``isaaclab_physx.sim.schemas``, supporting both surface and volume deformable + types via the ``deformable_type`` parameter. +* Added :class:`~isaaclab_physx.sim.spawners.materials.DeformableBodyMaterialCfg` + and :class:`~isaaclab_physx.sim.spawners.materials.SurfaceDeformableBodyMaterialCfg` + with namespace-aware property routing for ``omniphysics:`` and + ``physxDeformableBody:`` material attributes. +* Added :class:`~isaaclab_physx.sim.spawners.spawner_cfg.DeformableObjectSpawnerCfg` + for configuring deformable body properties and materials when spawning. +* Added surface deformable body support to + :class:`~isaaclab_physx.assets.DeformableObject`. The asset now detects whether + the deformable is a surface or volume type based on the applied material API + and creates the appropriate PhysX tensor view + (``create_surface_deformable_body_view`` vs ``create_volume_deformable_body_view``). + +Changed +^^^^^^^ + +* Changed :attr:`~isaaclab_physx.assets.DeformableObject.root_view` return type + from ``physx.SoftBodyView`` to ``physx.DeformableBodyView`` to align with the + updated PhysX API. +* Changed :attr:`~isaaclab_physx.assets.DeformableObject.material_physx_view` + return type from ``physx.SoftBodyMaterialView`` to + ``physx.DeformableMaterialView``. +* Changed deformable body root prim discovery to check for + ``OmniPhysicsDeformableBodyAPI`` instead of ``PhysxDeformableBodyAPI``. +* Changed material prim discovery to check for ``OmniPhysicsDeformableMaterialAPI`` + instead of ``PhysxDeformableBodyMaterialAPI``. +* Changed PhysX view API calls to use updated method names: + ``get_simulation_nodal_positions``, ``set_simulation_nodal_positions``, + ``set_simulation_nodal_velocities``, ``get_simulation_nodal_kinematic_targets``, + ``set_simulation_nodal_kinematic_targets``. +* Changed property accessors to use updated PhysX view attributes: + ``max_simulation_elements_per_body``, ``max_collision_elements_per_body``, + ``max_simulation_nodes_per_body``, ``max_collision_nodes_per_body``. +* Changed kinematic target operations to raise ``ValueError`` when called on + surface deformable bodies, which do not support kinematic targets. + + 0.5.12 (2026-03-16) ~~~~~~~~~~~~~~~~~~~ From 3fea0e2d0904d6bc786ec45d4f6d046a68cd12a8 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 14:04:25 +0100 Subject: [PATCH 56/73] Fix: incorrect function call for modify_deformable_body_properties --- source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index a3fda6153452..34d8f9863472 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -372,7 +372,7 @@ def _spawn_from_usd_file( prim = stage.GetPrimAtPath(prim_path) deformable_type = "surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume" if "OmniPhysicsBodyAPI" in prim.GetAppliedSchemas(): - schemas_physx.modify_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) + schemas_physx.modify_deformable_body_properties(prim_path, cfg.deformable_props, stage) else: schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) From 149c04b1831d2aa4e33cbf22d1083758139b7ced Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 14:09:01 +0100 Subject: [PATCH 57/73] Style: Remove print statement for logger.error --- source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index e1699a1db4cc..79bbfebe6ce4 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -112,7 +112,7 @@ def define_deformable_body_properties( set_visibility_with_guide_purpose=True, ) else: - print(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") + logger.error(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") success = False # api failure From 10f3da0c59a23fad958b16d40eb7687d15f7d055 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 14:10:26 +0100 Subject: [PATCH 58/73] Style: raise value error instead of error message --- source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 79bbfebe6ce4..ca245ab455b5 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -112,8 +112,7 @@ def define_deformable_body_properties( set_visibility_with_guide_purpose=True, ) else: - logger.error(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") - success = False + raise ValueError(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") # api failure if not success: From 35bb9462e1d1cecc0bb00bab89309441ce52e34d Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 14:11:51 +0100 Subject: [PATCH 59/73] Docs: Document extra parameter in deformable body properties call --- source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index ca245ab455b5..5d8850d43364 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -53,6 +53,7 @@ def define_deformable_body_properties( current stage is used. deformable_type: The type of the deformable body (surface or volume). This is used to determine which PhysX API to use for the deformable body. Defaults to "volume". + sim_mesh_prim_path: Optional override for the simulation mesh prim path. If None, it is set to ``{prim_path}/sim_mesh`` for surface deformables and ``{prim_path}/sim_tetmesh`` for volume deformables. Raises: ValueError: When the prim path is not valid. From c77c83376ad5a7d02e51a38f09f9a0409062810e Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 14:15:19 +0100 Subject: [PATCH 60/73] Docs: Add explanation to default choice --- .../assets/deformable_object/deformable_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 296670c274ca..2b876f81d310 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -584,11 +584,11 @@ def _initialize_impl(self): if material_prim is None: logger.info( f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." - " The material properties will be set to default values and are not modifiable at runtime." + " The material properties will be set to default values (volume deformable) and are not modifiable at runtime." " If you want to modify the material properties, please ensure that the material is bound" " to the deformable body." ) - # deformable type based on material that is applied + # deformable type based on material that is applied, without a valid physics material we apply a volume deformable type by default self._deformable_type = ( "surface" if material_prim is not None and material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") From 79a362ca97e772cfc98f52c7b191f2a73f6ef913 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 15:13:54 +0100 Subject: [PATCH 61/73] Fix: remove deprecated functions in demos and tests --- scripts/demos/deformables.py | 2 +- .../isaaclab_physx/test/assets/test_deformable_object.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index b3aba99570eb..4eb6af6872e2 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -287,7 +287,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab for _, deform_body in enumerate(entities.values()): # root state nodal_state = wp.to_torch(deform_body.data.default_nodal_state_w).clone() - deform_body.write_nodal_state_to_sim(nodal_state) + deform_body.write_nodal_state_to_sim_index(nodal_state) # reset the internal state deform_body.reset() print("[INFO]: Resetting deformable object state...") diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index 746f208b86be..4590087d9ff6 100644 --- a/source/isaaclab_physx/test/assets/test_deformable_object.py +++ b/source/isaaclab_physx/test/assets/test_deformable_object.py @@ -189,7 +189,7 @@ def test_set_nodal_state(sim, num_cubes): ], dim=-1, ) - cube_object.write_nodal_state_to_sim(nodal_state) + cube_object.write_nodal_state_to_sim_index(nodal_state) torch.testing.assert_close( wp.to_torch(cube_object.data.nodal_state_w), nodal_state, rtol=1e-5, atol=1e-5 @@ -237,7 +237,7 @@ def test_set_nodal_state_with_applied_transform(num_cubes, randomize_pos, random else: torch.testing.assert_close(mean_nodal_pos_init, mean_nodal_pos_default + pos_w, rtol=1e-5, atol=1e-5) - cube_object.write_nodal_state_to_sim(nodal_state) + cube_object.write_nodal_state_to_sim_index(nodal_state) cube_object.reset() for _ in range(50): @@ -260,7 +260,7 @@ def test_set_kinematic_targets(sim, num_cubes): nodal_kinematic_targets = wp.to_torch(cube_object.root_view.get_simulation_nodal_kinematic_targets()).clone() for _ in range(5): - cube_object.write_nodal_state_to_sim(wp.to_torch(cube_object.data.default_nodal_state_w)) + cube_object.write_nodal_state_to_sim_index(wp.to_torch(cube_object.data.default_nodal_state_w)) default_root_pos = wp.to_torch(cube_object.data.default_nodal_state_w).mean(dim=1) @@ -269,7 +269,7 @@ def test_set_kinematic_targets(sim, num_cubes): nodal_kinematic_targets[1:, :, 3] = 1.0 nodal_kinematic_targets[0, :, 3] = 0.0 nodal_kinematic_targets[0, :, :3] = wp.to_torch(cube_object.data.default_nodal_state_w)[0, :, :3] - cube_object.write_nodal_kinematic_target_to_sim( + cube_object.write_nodal_kinematic_target_to_sim_index( nodal_kinematic_targets[0:1], env_ids=torch.tensor([0], device=sim.device) ) From db294dcc69cf4f44a5d92cea32f2fc9a0c91dad7 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 15:17:02 +0100 Subject: [PATCH 62/73] Docs: update units in configs --- .../isaaclab_physx/sim/schemas/schemas.py | 2 +- .../isaaclab_physx/sim/schemas/schemas_cfg.py | 12 ++++++------ .../sim/spawners/materials/physics_materials_cfg.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 5d8850d43364..d2a1f790d228 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -74,7 +74,7 @@ def define_deformable_body_properties( matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "Mesh") # check if the volume deformable mesh is valid if len(matching_prims) == 0: - raise ValueError(f"Could not find any tetmesh or mesh in '{prim_path}'. Please check asset.") + raise ValueError(f"Could not find any mesh in '{prim_path}'. Please check asset.") if len(matching_prims) > 1: # get list of all meshes found mesh_paths = [p.GetPrimPath() for p in matching_prims] diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py index 7d4c934440ba..4c87528d8807 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -40,7 +40,7 @@ class PhysXDeformableBodyPropertiesCfg: """Number of the solver positional iterations per step. Range is [1,255], default to 16.""" linear_damping: float | None = None - """Linear damping coefficient, in units of 1/seconds and constrained to the range [0, inf).""" + """Linear damping coefficient, in units of [1/s] and constrained to the range [0, inf).""" max_linear_velocity: float | None = None """Maximum allowable linear velocity for the deformable body, in units of distance/second and constrained to the @@ -48,11 +48,11 @@ class PhysXDeformableBodyPropertiesCfg: currently only supported for surface deformables. This can help prevent surface-surface intersections.""" settling_damping: float | None = None - """Additional damping applied when a vertex's velocity falls below :attr:`settlingThreshold`. - Specified in units of 1/seconds and constrained to the range [0, inf).""" + """Additional damping applied when a vertex's velocity falls below :attr:`settling_threshold`. + Specified in units of [1/s] and constrained to the range [0, inf).""" settling_threshold: float | None = None - """Velocity threshold below which :attr:`settlingDamping` is applied in addition to standard damping. + """Velocity threshold below which :attr:`settling_damping` is applied in addition to standard damping. Specified in units of distance/second and constrained to the range [0, inf).""" sleep_threshold: float | None = None @@ -114,7 +114,7 @@ class PhysXCollisionPropertiesCfg: """ contact_offset: float | None = None - """Contact offset for the collision shape (in m). + """Contact offset for the collision shape (in [m]). The collision detector generates contact points as soon as two shapes get closer than the sum of their contact offsets. This quantity should be non-negative which means that contact generation can potentially start @@ -122,7 +122,7 @@ class PhysXCollisionPropertiesCfg: """ rest_offset: float | None = None - """Rest offset for the collision shape (in m). + """Rest offset for the collision shape (in [m]). The rest offset quantifies how close a shape gets to others at rest, At rest, the distance between two vertically stacked objects is the sum of their rest offsets. If a pair of shapes have a positive rest diff --git a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py index ea09a2330546..ba2b68378e15 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -32,9 +32,9 @@ class OmniPhysicsDeformableMaterialCfg: """The dynamic friction. Defaults to 0.25.""" youngs_modulus: float = 1000000.0 - """The Young's modulus, which defines the body's stiffness. Defaults to 1MPa. + """The Young's modulus, which defines the body's stiffness. Defaults to 1[MPa]. - The Young's modulus is a measure of the material's ability to deform under stress. It is measured in Pascals (Pa). + The Young's modulus is a measure of the material's ability to deform under stress. It is measured in Pascals ([Pa]). """ poissons_ratio: float = 0.45 @@ -59,7 +59,7 @@ class OmniPhysicsSurfaceDeformableMaterialCfg(OmniPhysicsDeformableMaterialCfg): """ surface_thickness: float = 0.01 - """The thickness of the deformable body's surface. Defaults to 0.01 meters (m).""" + """The thickness of the deformable body's surface. Defaults to 0.01 meters ([m]).""" surface_stretch_stiffness: float = 0.0 """The stretch stiffness of the deformable body's surface. Defaults to 0.0.""" From 4dc1410f0c9c9477a5d3e93fe2571ebd4a706d00 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 15:34:42 +0100 Subject: [PATCH 63/73] Test: Add unit test for surface deformable --- .../test/assets/test_deformable_object.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index 4590087d9ff6..fb11daf9f101 100644 --- a/source/isaaclab_physx/test/assets/test_deformable_object.py +++ b/source/isaaclab_physx/test/assets/test_deformable_object.py @@ -23,7 +23,7 @@ import warp as wp from flaky import flaky from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyMaterialCfg, DeformableBodyPropertiesCfg +from isaaclab_physx.sim import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg, DeformableBodyPropertiesCfg import carb @@ -39,6 +39,7 @@ def generate_cubes_scene( has_api: bool = True, material_path: str | None = "material", kinematic_enabled: bool = False, + deformable_type: str = "volume", device: str = "cuda:0", ) -> DeformableObject: """Generate a scene with the provided number of cubes. @@ -51,6 +52,7 @@ def generate_cubes_scene( material_path: Path to the material file. If None, no material is added. Default is "material", which is path relative to the spawned object prim path. kinematic_enabled: Whether the cubes are kinematic. + deformable_type: The type of deformable body to spawn. Supported values are "volume" and "surface". device: Device to use for the simulation. Returns: @@ -70,7 +72,10 @@ def generate_cubes_scene( ) # Add physics material if provided if material_path is not None: - spawn_cfg.physics_material = DeformableBodyMaterialCfg() + if deformable_type == "surface": + spawn_cfg.physics_material = SurfaceDeformableBodyMaterialCfg() + else: + spawn_cfg.physics_material = DeformableBodyMaterialCfg() spawn_cfg.physics_material_path = material_path else: spawn_cfg.physics_material = None @@ -144,6 +149,40 @@ def test_initialization(sim, num_cubes, material_path): assert wp.to_torch(cube_object.data.root_vel_w).shape == (num_cubes, 3) +@pytest.mark.parametrize("num_cubes", [1, 2]) +def test_initialization_surface_deformable(sim, num_cubes): + """Test initialization of a surface deformable body.""" + cube_object = generate_cubes_scene(num_cubes=num_cubes, deformable_type="surface") + + # Play sim + sim.reset() + + # Check if object is initialized + assert cube_object.is_initialized + assert cube_object._deformable_type == "surface" + + # Check correct number of instances + assert cube_object.num_instances == num_cubes + assert cube_object.root_view.count == num_cubes + + # Check material view is created + assert cube_object.material_physx_view is not None + assert cube_object.material_physx_view.count == num_cubes + + # Check nodal state buffers have correct shapes + assert wp.to_torch(cube_object.data.nodal_state_w).shape == (num_cubes, cube_object.max_sim_vertices_per_body, 6) + assert wp.to_torch(cube_object.data.root_pos_w).shape == (num_cubes, 3) + assert wp.to_torch(cube_object.data.root_vel_w).shape == (num_cubes, 3) + + # Kinematic targets are not allocated for surface deformables + assert cube_object.data.nodal_kinematic_target is None + + # Writing kinematic targets should raise ValueError + dummy_targets = torch.zeros(num_cubes, cube_object.max_sim_vertices_per_body, 4, device=sim.device) + with pytest.raises(ValueError, match="Kinematic targets can only be set for volume deformable bodies"): + cube_object.write_nodal_kinematic_target_to_sim_index(dummy_targets) + + @pytest.mark.isaacsim_ci def test_initialization_on_device_cpu(): """Test that initialization fails with deformable body API on the CPU.""" From ab030f380e42f9a5ae84c1e6ed949e56a67a1b96 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 15:48:03 +0100 Subject: [PATCH 64/73] Style: remove long lines --- .../assets/deformable_object/deformable_object.py | 9 +++++---- .../isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 9 +++++++-- .../isaaclab_physx/test/assets/test_deformable_object.py | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 2b876f81d310..06f3cfe56a64 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -584,11 +584,12 @@ def _initialize_impl(self): if material_prim is None: logger.info( f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." - " The material properties will be set to default values (volume deformable) and are not modifiable at runtime." - " If you want to modify the material properties, please ensure that the material is bound" - " to the deformable body." + " The material properties will be set to default values (volume deformable) and are not modifiable " + "at runtime. If you want to modify the material properties, please ensure that the material is " + "bound to the deformable body." ) - # deformable type based on material that is applied, without a valid physics material we apply a volume deformable type by default + # deformable type based on material that is applied + # without a valid physics material we apply a volume deformable type by default self._deformable_type = ( "surface" if material_prim is not None and material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index d2a1f790d228..f0f0a3301dfa 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -53,7 +53,9 @@ def define_deformable_body_properties( current stage is used. deformable_type: The type of the deformable body (surface or volume). This is used to determine which PhysX API to use for the deformable body. Defaults to "volume". - sim_mesh_prim_path: Optional override for the simulation mesh prim path. If None, it is set to ``{prim_path}/sim_mesh`` for surface deformables and ``{prim_path}/sim_tetmesh`` for volume deformables. + sim_mesh_prim_path: Optional override for the simulation mesh prim path. + If None, it is set to ``{prim_path}/sim_mesh`` for surface deformables + and ``{prim_path}/sim_tetmesh`` for volume deformables. Raises: ValueError: When the prim path is not valid. @@ -113,7 +115,10 @@ def define_deformable_body_properties( set_visibility_with_guide_purpose=True, ) else: - raise ValueError(f"Unsupported deformable type: '{deformable_type}'. Only surface and volume deformables are supported.") + raise ValueError( + f"""Unsupported deformable type: '{deformable_type}'. + Only surface and volume deformables are supported.""" + ) # api failure if not success: diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index fb11daf9f101..b234a5ab3e83 100644 --- a/source/isaaclab_physx/test/assets/test_deformable_object.py +++ b/source/isaaclab_physx/test/assets/test_deformable_object.py @@ -23,7 +23,7 @@ import warp as wp from flaky import flaky from isaaclab_physx.assets import DeformableObject, DeformableObjectCfg -from isaaclab_physx.sim import DeformableBodyMaterialCfg, SurfaceDeformableBodyMaterialCfg, DeformableBodyPropertiesCfg +from isaaclab_physx.sim import DeformableBodyMaterialCfg, DeformableBodyPropertiesCfg, SurfaceDeformableBodyMaterialCfg import carb From b97d59077ba839a130ea1896d3a9f06ea0d99fe2 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 15:54:48 +0100 Subject: [PATCH 65/73] Fix: Remove unnecessary buffer allocations in deformable data --- .../deformable_object_data.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py index 5d18c7193240..bc50b88715db 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py @@ -62,25 +62,6 @@ def __init__(self, root_view: physx.DeformableBodyView, device: str): self._nodal_pos_w = TimestampedBuffer((self._num_instances, self._max_sim_vertices), device, wp.vec3f) self._nodal_vel_w = TimestampedBuffer((self._num_instances, self._max_sim_vertices), device, wp.vec3f) self._nodal_state_w = TimestampedBuffer((self._num_instances, self._max_sim_vertices), device, vec6f) - # -- mesh element-wise rotations - self._sim_element_quat_w = TimestampedBuffer((self._num_instances, self._max_sim_elements), device, wp.quatf) - self._collision_element_quat_w = TimestampedBuffer( - (self._num_instances, self._max_collision_elements), device, wp.quatf - ) - # -- mesh element-wise deformation gradients - self._sim_element_deform_gradient_w = TimestampedBuffer( - (self._num_instances, self._max_sim_elements, 3, 3), device, wp.float32 - ) - self._collision_element_deform_gradient_w = TimestampedBuffer( - (self._num_instances, self._max_collision_elements, 3, 3), device, wp.float32 - ) - # -- mesh element-wise stresses - self._sim_element_stress_w = TimestampedBuffer( - (self._num_instances, self._max_sim_elements, 3, 3), device, wp.float32 - ) - self._collision_element_stress_w = TimestampedBuffer( - (self._num_instances, self._max_collision_elements, 3, 3), device, wp.float32 - ) # -- derived: root pos/vel self._root_pos_w = TimestampedBuffer((self._num_instances,), device, wp.vec3f) self._root_vel_w = TimestampedBuffer((self._num_instances,), device, wp.vec3f) From 2be2e84fc700281ce3af07af592bcc6cdd2ce598 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 15:56:07 +0100 Subject: [PATCH 66/73] Fix: Remove deprecated properties from deformable object --- .../deformable_object_data.py | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py index bc50b88715db..9aa38ff7778d 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object_data.py @@ -143,58 +143,6 @@ def nodal_state_w(self) -> wp.array: self._nodal_state_w.timestamp = self._sim_timestamp return self._nodal_state_w.data - @property - def sim_element_quat_w(self) -> wp.array: - """Simulation mesh element-wise rotations as quaternions for the deformable bodies in simulation world frame. - Shape is (num_instances, max_sim_elements_per_body, 4). - - The rotations are stored as quaternions in the order (x, y, z, w). - """ - # deprecated - raise NotImplementedError("The sim_element_quat_w property is deprecated.") - - @property - def collision_element_quat_w(self) -> wp.array: - """Collision mesh element-wise rotations as quaternions for the deformable bodies in simulation world frame. - Shape is (num_instances, max_collision_elements_per_body, 4). - - The rotations are stored as quaternions in the order (x, y, z, w). - """ - # deprecated - raise NotImplementedError("The collision_element_quat_w property is deprecated.") - - @property - def sim_element_deform_gradient_w(self) -> wp.array: - """Simulation mesh element-wise second-order deformation gradient tensors for the deformable bodies - in simulation world frame. Shape is (num_instances, max_sim_elements_per_body, 3, 3). - """ - # deprecated - raise NotImplementedError("The sim_element_deform_gradient_w property is deprecated.") - - @property - def collision_element_deform_gradient_w(self) -> wp.array: - """Collision mesh element-wise second-order deformation gradient tensors for the deformable bodies - in simulation world frame. Shape is (num_instances, max_collision_elements_per_body, 3, 3). - """ - # deprecated - raise NotImplementedError("The collision_element_deform_gradient_w property is deprecated.") - - @property - def sim_element_stress_w(self) -> wp.array: - """Simulation mesh element-wise second-order Cauchy stress tensors for the deformable bodies - in simulation world frame. Shape is (num_instances, max_sim_elements_per_body, 3, 3). - """ - # deprecated - raise NotImplementedError("The sim_element_stress_w property is deprecated.") - - @property - def collision_element_stress_w(self) -> wp.array: - """Collision mesh element-wise second-order Cauchy stress tensors for the deformable bodies - in simulation world frame. Shape is (num_instances, max_collision_elements_per_body, 3, 3). - """ - # deprecated - raise NotImplementedError("The collision_element_stress_w property is deprecated.") - ## # Derived properties. ## From 17b42e2171c8ed190a3441011ebb13961f0aa264 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 16:06:45 +0100 Subject: [PATCH 67/73] Fix: Check mesh prim hierarchy for deformable type --- .../deformable_object/deformable_object.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index 06f3cfe56a64..b524e5049f93 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -584,17 +584,17 @@ def _initialize_impl(self): if material_prim is None: logger.info( f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." - " The material properties will be set to default values (volume deformable) and are not modifiable " + " The material properties will be set to default values and are not modifiable " "at runtime. If you want to modify the material properties, please ensure that the material is " "bound to the deformable body." ) - # deformable type based on material that is applied - # without a valid physics material we apply a volume deformable type by default - self._deformable_type = ( - "surface" - if material_prim is not None and material_prim.HasAPI("OmniPhysicsSurfaceDeformableMaterialAPI") - else "volume" - ) + # determine deformable type (surface vs volume) from simulation mesh hierarchy + # volume deformables have a TetMesh child, surface deformables do not + has_tetmesh = len(sim_utils.get_all_matching_child_prims( + root_prim.GetPath(), lambda p: p.GetTypeName() == "TetMesh" + )) > 0 + self._deformable_type = "volume" if has_tetmesh else "surface" + # resolve root path back into regex expression # -- root prim expression From 198cc535541966c017d3a7505a99f927fb9f3bd0 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 16:08:36 +0100 Subject: [PATCH 68/73] Style: Clean up documentation --- source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py | 4 ++-- source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index bf5141dca2d5..bbde8b3cadd3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -156,5 +156,5 @@ class MeshSquareCfg(MeshCfg): size: float = MISSING """Edge length of the square (in m).""" - resolution: tuple[int, int] = (2, 2) - """Resolution of the square (in vertices).""" + resolution: tuple[int, int] = (5, 5) + """Resolution of the square (in vertices per side).""" diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index f0f0a3301dfa..220e3541693d 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -192,7 +192,6 @@ def modify_deformable_body_properties(prim_path: str, cfg: DeformableBodyPropert # convert to dict cfg = cfg.to_dict() # set into PhysX API - # TODO: kinematic enabled does not work properly with creating DeformableView in Omni Physics 110.0. if cfg["kinematic_enabled"]: logger.warning( "Kinematic deformable bodies are not fully supported in the current version of Omni Physics. " From 7f411eb13b0f6bfe7b464c73c4f2b56b5f154940 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 16:39:53 +0100 Subject: [PATCH 69/73] Style: different check for deformable type from material and prim hierarchy --- .../deformable_object/deformable_object.py | 29 ++++++++++++++----- .../test/assets/test_deformable_object.py | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index b524e5049f93..bca99d902980 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -580,21 +580,28 @@ def _initialize_impl(self): mat_prim = root_prim.GetStage().GetPrimAtPath(mat_path) if "OmniPhysicsDeformableMaterialAPI" in mat_prim.GetAppliedSchemas(): material_prim = mat_prim + # determine deformable material type + if "PhysxSurfaceDeformableMaterialAPI" in mat_prim.GetAppliedSchemas(): + self._deformable_type = "surface" + elif "PhysxDeformableMaterialAPI" in mat_prim.GetAppliedSchemas(): + self._deformable_type = "volume" break + if material_prim is None: + # if a valid tetmesh is found under the root prim and no physics material is assigned + # then we assume it's a volume deformable with default material parameters. + has_tetmesh = ( + len(sim_utils.get_all_matching_child_prims(root_prim.GetPath(), lambda p: p.GetTypeName() == "TetMesh")) + > 0 + ) + if has_tetmesh: + self._deformable_type = "volume" logger.info( f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." " The material properties will be set to default values and are not modifiable " "at runtime. If you want to modify the material properties, please ensure that the material is " "bound to the deformable body." ) - # determine deformable type (surface vs volume) from simulation mesh hierarchy - # volume deformables have a TetMesh child, surface deformables do not - has_tetmesh = len(sim_utils.get_all_matching_child_prims( - root_prim.GetPath(), lambda p: p.GetTypeName() == "TetMesh" - )) > 0 - self._deformable_type = "volume" if has_tetmesh else "surface" - # resolve root path back into regex expression # -- root prim expression @@ -606,11 +613,17 @@ def _initialize_impl(self): self._root_physx_view = self._physics_sim_view.create_surface_deformable_body_view( root_prim_path_expr.replace(".*", "*") ) - else: + elif self._deformable_type == "volume": # volume deformable self._root_physx_view = self._physics_sim_view.create_volume_deformable_body_view( root_prim_path_expr.replace(".*", "*") ) + else: + raise RuntimeError( + f"Failed to determine deformable material type for '{root_prim.GetPath().pathString}'." + " Please ensure that the material has either 'PhysxSurfaceDeformableMaterialAPI' or " + "'PhysxDeformableMaterialAPI' applied, or that a valid tetmesh is found under the root prim." + ) # Return if the asset is not found if self._root_physx_view._backend is None: diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index b234a5ab3e83..7f83765e740b 100644 --- a/source/isaaclab_physx/test/assets/test_deformable_object.py +++ b/source/isaaclab_physx/test/assets/test_deformable_object.py @@ -150,6 +150,7 @@ def test_initialization(sim, num_cubes, material_path): @pytest.mark.parametrize("num_cubes", [1, 2]) +@pytest.mark.isaacsim_ci def test_initialization_surface_deformable(sim, num_cubes): """Test initialization of a surface deformable body.""" cube_object = generate_cubes_scene(num_cubes=num_cubes, deformable_type="surface") From 2967f13f160be9504907553e425b16da067824f7 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 17:08:07 +0100 Subject: [PATCH 70/73] Style: change surface or volume deformable detection when no material is assigned --- .../deformable_object/deformable_object.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py index bca99d902980..2b1116994f4a 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/deformable_object/deformable_object.py @@ -588,20 +588,30 @@ def _initialize_impl(self): break if material_prim is None: - # if a valid tetmesh is found under the root prim and no physics material is assigned - # then we assume it's a volume deformable with default material parameters. + logger.warning( + f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." + " The material properties will be set to default values and are not modifiable " + "at runtime. If you want to modify the material properties, please ensure that the material is " + "bound to the deformable body." + ) + + # fall back to prim hierarchy heuristic when material type detection was inconclusive + if self._deformable_type is None: + # volume deformables must have a tetmesh in the hierarchy has_tetmesh = ( len(sim_utils.get_all_matching_child_prims(root_prim.GetPath(), lambda p: p.GetTypeName() == "TetMesh")) > 0 ) if has_tetmesh: self._deformable_type = "volume" - logger.info( - f"Failed to find a deformable material binding for '{root_prim.GetPath().pathString}'." - " The material properties will be set to default values and are not modifiable " - "at runtime. If you want to modify the material properties, please ensure that the material is " - "bound to the deformable body." - ) + else: + # surface deformables must have a mesh in the hierarchy + has_mesh = ( + len(sim_utils.get_all_matching_child_prims(root_prim.GetPath(), lambda p: p.GetTypeName() == "Mesh")) + > 0 + ) + if has_mesh: + self._deformable_type = "surface" # resolve root path back into regex expression # -- root prim expression From bc31a94b1e9d60c9bbb0cd50791a12091607d88d Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 17:27:29 +0100 Subject: [PATCH 71/73] Fix: Reset in deformable demo while saving frames --- scripts/demos/deformables.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 4eb6af6872e2..b73846ef72df 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -285,6 +285,9 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab count = 0 # reset deformable object state for _, deform_body in enumerate(entities.values()): + # skip camera + if isinstance(deform_body, Camera): + continue # root state nodal_state = wp.to_torch(deform_body.data.default_nodal_state_w).clone() deform_body.write_nodal_state_to_sim_index(nodal_state) From e6887d74bf7d3f50762ba48f5045836d704b9f39 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 17:49:29 +0100 Subject: [PATCH 72/73] Fix: Add more specific API check for deformables --- source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py | 2 +- source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 34d8f9863472..260bf0a1b081 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -371,7 +371,7 @@ def _spawn_from_usd_file( if cfg.deformable_props is not None: prim = stage.GetPrimAtPath(prim_path) deformable_type = "surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume" - if "OmniPhysicsBodyAPI" in prim.GetAppliedSchemas(): + if "OmniPhysicsDeformableBodyAPI" in prim.GetAppliedSchemas(): schemas_physx.modify_deformable_body_properties(prim_path, cfg.deformable_props, stage) else: schemas_physx.define_deformable_body_properties(prim_path, cfg.deformable_props, stage, deformable_type) diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py index 220e3541693d..5b57b93d7ff4 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -178,7 +178,7 @@ def modify_deformable_body_properties(prim_path: str, cfg: DeformableBodyPropert if not deformable_body_prim.IsValid(): return False # check if deformable body API is applied - if "OmniPhysicsBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): + if "OmniPhysicsDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): return False # apply customization to deformable API From 9d22d6202a8a5f17d02ee9e7f984ee81e3ed0c21 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 17 Mar 2026 18:36:01 +0100 Subject: [PATCH 73/73] Feat: Set default visualizer to kit for tutorial --- scripts/tutorials/01_assets/run_deformable_object.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index e1677281d416..73ec176eec70 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -36,6 +36,8 @@ ) # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) +# demos should open Kit visualizer by default +parser.set_defaults(visualizer=["kit"]) # parse the arguments args_cli = parser.parse_args()