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/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] 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..efb7f7da689e --- /dev/null +++ b/docs/source/migration/migrating_deformables.rst @@ -0,0 +1,224 @@ +.. _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 v3.0.0 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`` + + +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**: + +.. code-block:: python + :emphasize-lines: 1,2 + + 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(), + visual_material=sim_utils.PreviewSurfaceCfg(), + physics_material=sim_utils.DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + ), + ) + 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 + 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(), + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + ), + ) + 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(poissons_ratio=0.4, youngs_modulus=1e5), + ), + ) + 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 + + from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR + + cfg = DeformableObjectCfg( + prim_path="/World/Origin.*/Teddy", + spawn=sim_utils.UsdFileCfg( + usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Objects/Teddy_Bear/teddy_bear.usd", + deformable_props=DeformableBodyPropertiesCfg(), + physics_material=DeformableBodyMaterialCfg(poissons_ratio=0.4, youngs_modulus=1e5), + scale=[0.05, 0.05, 0.05], + ), + ) + 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 through Isaac Lab yet. + + +.. _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 diff --git a/docs/source/tutorials/01_assets/run_deformable_object.rst b/docs/source/tutorials/01_assets/run_deformable_object.rst index d8567c953244..963acac46aa1 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 @@ -159,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 @@ -174,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 fc759a3142d1..b73846ef72df 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -21,44 +21,106 @@ # 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 +# 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.assets import DeformableObject, DeformableObjectCfg +from isaaclab.sensors.camera import Camera, CameraCfg +from isaaclab.utils import convert_dict_to_backend +from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR + +# 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] = torch.rand(num_origins) + 1.0 - # 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 for rendering frames.""" + # Setup camera sensor + 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, + height=800, + width=1000, + data_types=[ + "rgb", + ], + spawn=sim_utils.PinholeCameraCfg(), + ) + # Create camera + camera = Camera(cfg=camera_cfg) + + return camera + + def design_scene() -> tuple[dict, list[list[float]]]: """Designs the scene.""" # Ground-plane @@ -74,37 +136,51 @@ def design_scene() -> tuple[dict, list[list[float]]]: # spawn a red cone cfg_sphere = sim_utils.MeshSphereCfg( - radius=0.25, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + radius=0.4, + deformable_props=DeformableBodyPropertiesCfg(), 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), + size=(0.6, 0.6, 0.6), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) cfg_cylinder = sim_utils.MeshCylinderCfg( - radius=0.15, + radius=0.25, height=0.5, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), ) cfg_capsule = sim_utils.MeshCapsuleCfg( - radius=0.15, + radius=0.35, height=0.5, - deformable_props=sim_utils.DeformableBodyPropertiesCfg(rest_offset=0.0), + deformable_props=DeformableBodyPropertiesCfg(), 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), + radius=0.35, + height=0.75, + deformable_props=DeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(), - physics_material=sim_utils.DeformableBodyMaterialCfg(), + physics_material=DeformableBodyMaterialCfg(), + ) + cfg_cloth = sim_utils.MeshSquareCfg( + size=1.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 = { @@ -113,58 +189,108 @@ 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=64, spacing=0.6) + 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 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(5e5, 1e8) + # higher mesh resolution causes instability at low stiffness + 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.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 - obj_cfg.func(f"/World/Origin/Object{idx:02d}", obj_cfg, translation=origin) + # 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 + # 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 = {} + 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: + camera = define_sensor() + scene_entities["camera"] = camera # return the scene information - scene_entities = {"deformable_object": deformable_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[str, DeformableObject], output_dir: str = "outputs"): """Runs the simulation loop.""" + # 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([[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) + # 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 # 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(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...") @@ -177,6 +303,17 @@ 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.""" @@ -187,15 +324,54 @@ 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, camera_output) + print("[INFO]: Simulation complete...") - # Run the simulator - run_simulator(sim, scene_entities, scene_origins) + # 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}") if __name__ == "__main__": diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index 873566534e29..73ec176eec70 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -15,15 +15,29 @@ """Launch Isaac Sim Simulator first.""" - import argparse +import os 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=4.0, + help="Total simulation time in seconds.", +) +parser.add_argument( + "--dt", + type=float, + default=1.0 / 60, + help="Simulation timestep.", +) # 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() @@ -37,6 +51,9 @@ 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 @@ -51,39 +68,45 @@ def design_scene(): cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.8, 0.8, 0.8)) cfg.func("/World/Light", cfg) - # Create separate groups called "Origin1", "Origin2", "Origin3" + # Create a dictionary for the scene entities + scene_entities = {} + + # 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): 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( size=(0.2, 0.2, 0.2), - deformable_props=sim_utils.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=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, ) + cube_object = DeformableObject(cfg=cfg) + scene_entities["cube_object"] = cube_object # 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"] + # Define simulation stepping sim_dt = sim.get_physics_dt() + num_steps = int(args_cli.total_time / sim_dt) sim_time = 0.0 count = 0 @@ -91,9 +114,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(): - # reset - if count % 250 == 0: + for t in range(num_steps): + # 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 @@ -120,11 +143,12 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab 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.001 + 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) @@ -137,18 +161,21 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Deformab count += 1 # update buffers cube_object.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]}") + + # 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]}" + ) 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]) + 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) @@ -157,7 +184,9 @@ 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) + print("[INFO]: Simulation complete...") if __name__ == "__main__": 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/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index 32a5ea87e197..aa1816845242 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", @@ -41,7 +39,6 @@ __all__ = [ "CollisionPropertiesCfg", "ConvexDecompositionPropertiesCfg", "ConvexHullPropertiesCfg", - "DeformableBodyPropertiesCfg", "FixedTendonPropertiesCfg", "JointDrivePropertiesCfg", "MassPropertiesCfg", @@ -53,7 +50,6 @@ __all__ = [ "TriangleMeshSimplificationPropertiesCfg", "SpawnerCfg", "RigidObjectSpawnerCfg", - "DeformableObjectSpawnerCfg", "spawn_from_mjcf", "spawn_from_urdf", "spawn_from_usd", @@ -71,9 +67,7 @@ __all__ = [ "DomeLightCfg", "LightCfg", "SphereLightCfg", - "spawn_deformable_body_material", "spawn_rigid_body_material", - "DeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -87,12 +81,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", @@ -186,13 +182,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, @@ -205,7 +199,6 @@ from .schemas import ( CollisionPropertiesCfg, ConvexDecompositionPropertiesCfg, ConvexHullPropertiesCfg, - DeformableBodyPropertiesCfg, FixedTendonPropertiesCfg, JointDrivePropertiesCfg, MassPropertiesCfg, @@ -219,7 +212,6 @@ from .schemas import ( from .spawners import ( SpawnerCfg, RigidObjectSpawnerCfg, - DeformableObjectSpawnerCfg, spawn_from_mjcf, spawn_from_urdf, spawn_from_usd, @@ -237,9 +229,7 @@ from .spawners import ( DomeLightCfg, LightCfg, SphereLightCfg, - spawn_deformable_body_material, spawn_rigid_body_material, - DeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, spawn_from_mdl_file, @@ -253,12 +243,14 @@ from .spawners import ( spawn_mesh_cuboid, spawn_mesh_cylinder, spawn_mesh_sphere, + spawn_mesh_square, MeshCapsuleCfg, MeshCfg, MeshConeCfg, MeshCuboidCfg, MeshCylinderCfg, MeshSphereCfg, + MeshSquareCfg, spawn_camera, FisheyeCameraCfg, PinholeCameraCfg, diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi index 947d4ab47c92..f413b3ded12d 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", @@ -29,7 +27,6 @@ __all__ = [ "CollisionPropertiesCfg", "ConvexDecompositionPropertiesCfg", "ConvexHullPropertiesCfg", - "DeformableBodyPropertiesCfg", "FixedTendonPropertiesCfg", "JointDrivePropertiesCfg", "MassPropertiesCfg", @@ -48,13 +45,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, @@ -69,7 +64,6 @@ from .schemas_cfg import ( CollisionPropertiesCfg, ConvexDecompositionPropertiesCfg, ConvexHullPropertiesCfg, - DeformableBodyPropertiesCfg, FixedTendonPropertiesCfg, JointDrivePropertiesCfg, MassPropertiesCfg, diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index dec9de5bbada..65ac974a6e2d 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -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, ) @@ -844,174 +843,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.") - - # traverse the prim and get the mesh - matching_prims = get_all_matching_child_prims(prim_path, lambda p: p.GetTypeName() == "Mesh") - # 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() - if "PhysxDeformableBodyAPI" not in mesh_applied: - mesh_prim.AddAppliedSchema("PhysxDeformableBodyAPI") - # set deformable body properties - modify_deformable_body_properties(mesh_prim.GetPrimPath(), 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 and has the deformable-body API - if not deformable_body_prim.IsValid(): - return False - if "PhysxDeformableBodyAPI" not in deformable_body_prim.GetAppliedSchemas(): - return False - - # 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", - ] - } - from omni.physx.scripts import deformableUtils as deformable_utils - - status = deformable_utils.add_physx_deformable_body(stage, prim_path=prim_path, **attr_kwargs) - # 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") - - # 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"]: - safe_set_attribute_on_usd_prim( - deformable_body_prim, f"physxCollision:{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 - ) - - # success - return True - - """ Collision mesh properties. """ diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index df2569c57714..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_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/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/__init__.pyi index 78d2c19021ee..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", @@ -24,9 +23,7 @@ __all__ = [ "DomeLightCfg", "LightCfg", "SphereLightCfg", - "spawn_deformable_body_material", "spawn_rigid_body_material", - "DeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -40,12 +37,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", @@ -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, @@ -89,9 +88,7 @@ from .lights import ( SphereLightCfg, ) from .materials import ( - spawn_deformable_body_material, spawn_rigid_body_material, - DeformableBodyMaterialCfg, PhysicsMaterialCfg, RigidBodyMaterialCfg, spawn_from_mdl_file, @@ -107,11 +104,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/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index ddb7f9ff2fb3..260bf0a1b081 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 @@ -363,9 +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) + prim = stage.GetPrimAtPath(prim_path) + deformable_type = "surface" if isinstance(cfg.physics_material, SurfaceDeformableBodyMaterialCfg) else "volume" + 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) # apply visual material if cfg.visual_material is not None: @@ -381,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 0de0b85911ea..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,9 +8,12 @@ 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 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 @@ -67,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): diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi index 294a9a7d9bf7..93142ddab389 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/spawners/materials/__init__.pyi @@ -4,9 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ - "spawn_deformable_body_material", "spawn_rigid_body_material", - "DeformableBodyMaterialCfg", "PhysicsMaterialCfg", "RigidBodyMaterialCfg", "spawn_from_mdl_file", @@ -17,9 +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, PhysicsMaterialCfg, RigidBodyMaterialCfg, ) diff --git a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py index 1577f9e134dc..d9f93e09e817 100644 --- a/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py +++ b/source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py @@ -5,16 +5,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING - 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 from isaaclab.utils.string import to_camel_case -if TYPE_CHECKING: - from . import physics_materials_cfg +from . import physics_materials_cfg @clone @@ -74,58 +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 "PhysxDeformableBodyMaterialAPI" not in applied: - prim.AddAppliedSchema("PhysxDeformableBodyMaterialAPI") - - # convert to dict - cfg = cfg.to_dict() - del cfg["func"] - # set into PhysX API (prim attributes: physxDeformableBodyMaterial:*) - 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 - ) - # 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..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,44 +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. """ - - -@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.""" - - 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 - """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. - """ 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 b6f441fc22d3..45f70d80cea9 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -11,12 +11,16 @@ 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 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 RigidBodyMaterialCfg if TYPE_CHECKING: from . import meshes_cfg @@ -256,6 +260,51 @@ def spawn_mesh_cone( return stage.GetPrimAtPath(prim_path) +@clone +def spawn_mesh_square( + prim_path: str, + 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 square 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 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 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) + + """ Helper functions. """ @@ -342,14 +391,15 @@ 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.define_deformable_body_properties(mesh_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=deformable_type + ) elif cfg.collision_props is not None: # decide on type of collision approximation based on the mesh if cfg.__class__.__name__ == "MeshSphereCfg": @@ -369,13 +419,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: @@ -386,7 +436,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/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index 9cf3d45716f5..bbde8b3cadd3 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -9,8 +9,11 @@ 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 DeformableObjectSpawnerCfg, RigidObjectSpawnerCfg +from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg from isaaclab.utils import configclass @@ -140,3 +143,18 @@ class MeshConeCfg(MeshCfg): """Height of the v (in m).""" axis: Literal["X", "Y", "Z"] = "Z" """Axis of the cone. Defaults to "Z".""" + + +@configclass +class MeshSquareCfg(MeshCfg): + """Configuration parameters for a 2D square mesh prim. + + See :meth:`spawn_mesh_square` for more information. + """ + + func: Callable | str = "{DIR}.meshes:spawn_mesh_square" + + size: float = MISSING + """Edge length of the square (in m).""" + resolution: tuple[int, int] = (5, 5) + """Resolution of the square (in vertices per side).""" diff --git a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py index ba86e4937fc3..7a803ad0e0dd 100644 --- a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py @@ -100,25 +100,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.DeformableBodyPropertiesCfg | None = None - """Deformable body properties.""" diff --git a/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py b/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py index 1533c29687e8..9aacb87426c2 100644 --- a/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/wrappers/wrappers_cfg.py @@ -5,8 +5,11 @@ 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 DeformableObjectSpawnerCfg, RigidObjectSpawnerCfg, SpawnerCfg +from isaaclab.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg from isaaclab.utils import configclass diff --git a/source/isaaclab/isaaclab/sim/utils/prims.py b/source/isaaclab/isaaclab/sim/utils/prims.py index f78b8ec22e0e..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 = "PhysxDeformableBodyAPI" in applied + 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( 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) ~~~~~~~~~~~~~~~~~~~ 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..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 @@ -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. @@ -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 @@ -102,7 +104,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 +113,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 +121,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 +135,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 +266,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 +334,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, @@ -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: @@ -401,7 +406,9 @@ 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 +548,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,27 +578,69 @@ 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(): + 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: - logger.info( + 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." + " 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" + 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 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(".*", "*")) + 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(".*", "*") + ) + 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: 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(): + 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: @@ -604,7 +653,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 +690,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) ) @@ -660,20 +709,24 @@ def _create_buffers(self): device=self.device, ) - # kinematic targets — allocate our own buffer and copy from PhysX - kinematic_raw = self.root_view.get_sim_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. @@ -692,10 +745,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 9e7e82499167..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 @@ -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 @@ -62,25 +62,6 @@ def __init__(self, root_view: physx.SoftBodyView, 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) @@ -125,9 +106,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 +120,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)) ) @@ -162,88 +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). - """ - 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 - - @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). - """ - 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 - - @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 - - @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 - - @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 - - @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 - ## # Derived properties. ## 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..c75cccf3f04a --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/__init__.pyi @@ -0,0 +1,26 @@ +# 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", + "DeformableBodyPropertiesCfg", + "DeformableObjectSpawnerCfg", + "spawn_deformable_body_material", + "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", +] + +from .schemas import ( + define_deformable_body_properties, + modify_deformable_body_properties, + DeformableBodyPropertiesCfg +) +from .spawners import ( + DeformableObjectSpawnerCfg, + 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..bb0e51a19b4b --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__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__ = [ + "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.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py new file mode 100644 index 000000000000..5b57b93d7ff4 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas.py @@ -0,0 +1,208 @@ +# 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 + +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 isaaclab_physx.sim.schemas.schemas_cfg import DeformableBodyPropertiesCfg + +# import logger +logger = logging.getLogger(__name__) + + +""" +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, +): + """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. + 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. + 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: + 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.") + + # 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: + 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." + ) + mesh_prim = matching_prims[0] + mesh_prim_path = mesh_prim.GetPrimPath() + + # 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, + 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" 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, + 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: + raise ValueError( + f"""Unsupported deformable type: '{deformable_type}'. + Only surface and volume deformables are supported.""" + ) + + # api failure + if not success: + raise RuntimeError(f"Failed to set deformable body properties on prim '{mesh_prim_path}'.") + + # set deformable body properties + 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): + """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. + + 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 + 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, 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 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.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. + 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 + # check if deformable body API is applied + if "OmniPhysicsDeformableBodyAPI" 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(): + deformable_body_prim.AddAppliedSchema("PhysxCollisionAPI") + + # convert to dict + cfg = cfg.to_dict() + # set into PhysX API + 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(): + for attr_name in attr_list: + 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 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..4c87528d8807 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -0,0 +1,158 @@ +# 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 + +import dataclasses + +from isaaclab.utils import configclass + + +@configclass +class OmniPhysicsPropertiesCfg: + """OmniPhysics properties for a deformable body. + + 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``. + + See the OmniPhysics documentation for more information on the available properties. + """ + + 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.""" + + +@configclass +class PhysXDeformableBodyPropertiesCfg: + """PhysX-specific properties for a deformable body. + + These properties are set with the prefix ``physxDeformableBody:`` + + For more information on the available properties, please refer to the `documentation `_. + """ + + solver_position_iteration_count: int = 16 + """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/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 + 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:`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:`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 + """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 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 constrained to range [:attr:`rest_offset`*2, inf]. + """ + + enable_speculative_c_c_d: bool | None = None + """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. + + 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. + + Valid range: [1, :attr:`solver_position_iteration_count` / 2]. + """ + + +@configclass +class PhysXCollisionPropertiesCfg: + """PhysX-specific collision properties for a deformable body. + + These properties are set with the prefix ``physxCollision:``. + + See the PhysX documentation for more information on the available properties. + """ + + 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. + """ + + +@configclass +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. + + 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. + + 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.""" 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..805a9f79fd52 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/__init__.pyi @@ -0,0 +1,18 @@ +# 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__ = [ + "DeformableObjectSpawnerCfg", + "spawn_deformable_body_material", + "DeformableBodyMaterialCfg", + "SurfaceDeformableBodyMaterialCfg", +] + +from .spawner_cfg import DeformableObjectSpawnerCfg +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..6e8a48b2f118 --- /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, +) 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..78920cca9f8d --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials.py @@ -0,0 +1,80 @@ +# 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, UsdShade + +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 . 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 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"{prefix}:{to_camel_case(attr_name, 'cC')}", cfg[attr_name], 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..ba2b68378e15 --- /dev/null +++ b/source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py @@ -0,0 +1,122 @@ +# 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 + +import dataclasses +from collections.abc import Callable + +from isaaclab.sim.spawners.materials import PhysicsMaterialCfg +from isaaclab.utils import configclass + + +@configclass +class OmniPhysicsDeformableMaterialCfg: + """OmniPhysics material properties for a deformable body. + + 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. + """ + + 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 = 1000000.0 + """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]). + """ + + 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. + """ + + +@configclass +class OmniPhysicsSurfaceDeformableMaterialCfg(OmniPhysicsDeformableMaterialCfg): + """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``. + + See the OmniPhysics documentation for more information on the available properties. + """ + + 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.""" + + +@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.""" 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..dda028508762 --- /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.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 + + +@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.""" diff --git a/source/isaaclab_physx/test/assets/test_deformable_object.py b/source/isaaclab_physx/test/assets/test_deformable_object.py index a0dcd199b1ee..7f83765e740b 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, SurfaceDeformableBodyMaterialCfg import carb @@ -38,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. @@ -50,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: @@ -65,11 +68,14 @@ 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() + 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 @@ -142,41 +148,40 @@ 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.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") + + # 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 @@ -194,33 +199,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): @@ -251,7 +229,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 @@ -282,7 +260,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 @@ -299,7 +277,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): @@ -319,10 +297,10 @@ 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)) + 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) @@ -331,7 +309,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) )