> This page is for version Release 3.0 (default).
> For other versions, use one of these documentation indexes:
> - Release 3.0 (default): https://docs.nvidia.com/isaaclab/3.0/llms.txt
> - Release 2.3: https://docs.nvidia.com/isaaclab/2.3/llms.txt
> - Release 2.2: https://docs.nvidia.com/isaaclab/2.2/llms.txt
> - Release 2.1: https://docs.nvidia.com/isaaclab/2.1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/isaaclab/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/isaaclab/_mcp/server.

# Interacting with a deformable object

While deformable objects sometimes refer to a broader class of objects, such as cloths, fluids and soft bodies,
Isaac Lab represents deformable objects as either surface or volume deformables. Unlike rigid objects, soft bodies can
deform under external forces and collisions. In this tutorial, we focus on volume deformable bodies. For an example of
surface deformables (cloth), run the `deformables` example.

The deformable object API and schema define/modify functions are shared across backends, while deformable
property and material configuration classes are backend-specific. PhysX simulates soft bodies using the Finite
Element Method (FEM); the Newton experimental backend uses the core VBD solver from
`isaaclab_newton.physics` with the deformable object integration from
`isaaclab_newton.assets`.
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 PhysX-specific details, please check the [PhysX documentation](https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html).

This tutorial shows how to interact with a deformable object in the simulation. We will spawn a
set of soft cubes and see how to set their nodal positions and velocities, along with apply kinematic
commands to the mesh nodes to move the soft body.

> **Note**
>
> This tutorial automatically tetrahedralizes volume deformables, and its default
> visualizer is Kit. Run it with the `isaacsim` and `tetrahedralization` extras:
>
> ```bash
> uv run --extra isaacsim --extra tetrahedralization python scripts/tutorials/01_assets/run_deformable_object.py --visualizer kit
> ```
>
> With the legacy installer, install the optional dependencies first:
>
> ```bash
> ./isaaclab.sh -i tetrahedralization
> ```

## The Code

The tutorial corresponds to the `run_deformable_object.py` script in the `scripts/tutorials/01_assets` directory.

```python
# 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

"""
This script demonstrates how to work with the deformable object and interact with it.

~~~bash
# Usage with default PhysX physics and default kit visualizer.
uv run --extra isaacsim --extra tetrahedralization python scripts/tutorials/01_assets/run_deformable_object.py

# Usage with Newton VBD physics and default kit visualizer.
uv run --extra isaacsim --extra tetrahedralization python scripts/tutorials/01_assets/run_deformable_object.py \
    --backend newton_vbd

# Usage with OvPhysX physics without a visualizer.
uv run --extra ovphysx --extra tetrahedralization python scripts/tutorials/01_assets/run_deformable_object.py \
    --backend ovphysx
~~~

"""

"""Parse CLI first so we can decide whether to launch Isaac Sim Kit."""

import argparse
from typing import TYPE_CHECKING

from isaaclab.app import add_launcher_args, launch_simulation

# add argparse arguments
parser = argparse.ArgumentParser(description="Tutorial on interacting with a deformable object.")
parser.add_argument(
    "--backend", type=str, default="physx", choices=["physx", "newton_vbd", "ovphysx"], help="Physics backend."
)
# append simulation launcher CLI arguments
add_launcher_args(parser)
# Kit cannot be combined with OvPhysX, so use no visualizer by default for that backend
backend_args, _ = parser.parse_known_args()
parser.set_defaults(visualizer=None if backend_args.backend == "ovphysx" else ["kit"])
# parse the arguments
args_cli = parser.parse_args()
args_cli.physics = args_cli.backend

"""Rest everything follows."""

import torch

import isaaclab.sim as sim_utils
import isaaclab.utils.math as math_utils
from isaaclab.assets import AssetBaseCfg, DeformableObjectCfg
from isaaclab.cloner import CloneCfg
from isaaclab.physics import PhysicsCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils import configclass, index_fill_, instantiate

if TYPE_CHECKING:
    from isaaclab.assets import DeformableObject
    from isaaclab.scene import InteractiveScene

youngs_modulus = 1e5
poissons_ratio = 0.4
density = 500.0
if args_cli.backend == "newton_vbd":
    from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg
    from isaaclab_newton.sim.spawners.materials import NewtonDeformableBodyMaterialCfg

    deformable_props = NewtonDeformableBodyPropertiesCfg()
    # Newton's VBD path skips the simulation mesh collider, so collision offsets do not apply
    collision_props = None
    physics_material = NewtonDeformableBodyMaterialCfg(
        k_mu=youngs_modulus / (2.0 * (1.0 + poissons_ratio)),
        k_lambda=youngs_modulus * poissons_ratio / ((1.0 + poissons_ratio) * (1.0 - 2.0 * poissons_ratio)),
        density=density,
    )
else:
    from isaaclab_physx.sim.schemas import PhysxCollisionCfg, PhysxDeformableBodyPropertiesCfg
    from isaaclab_physx.sim.spawners.materials import PhysxDeformableBodyMaterialCfg

    deformable_props = PhysxDeformableBodyPropertiesCfg()
    collision_props = [PhysxCollisionCfg(rest_offset=0.0, contact_offset=0.001)]
    physics_material = PhysxDeformableBodyMaterialCfg(
        poissons_ratio=poissons_ratio, youngs_modulus=youngs_modulus, density=density
    )

@configclass
class DeformableSceneCfg(InteractiveSceneCfg):
    """Soft cubes on a shared ground plane."""

    filter_collisions = False
    clone_cfg = CloneCfg(clone_template="/World/env_{}")

    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
    light = AssetBaseCfg(
        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=2000.0, color=(0.8, 0.8, 0.8))
    )

    # 3D Deformable Object
    cube_object = DeformableObjectCfg(
        prim_path="{ENV_REGEX_NS}/Cube",
        # PhysX deformables must be parsed from USD while other assets retain native replication.
        cloning_contexts=("isaaclab.cloner:UsdReplicateContext",) if args_cli.backend == "physx" else None,
        spawn=sim_utils.MeshCuboidCfg(
            size=(0.2, 0.2, 0.2),
            deformable_props=deformable_props,
            collision_props=collision_props,
            visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)),
            physics_material=physics_material,
        ),
        init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)),
        debug_vis=True,
    )

def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"):
    """Runs the simulation loop."""
    # Extract scene entities
    cube_object: DeformableObject = scene["cube_object"]

    # Define simulation stepping
    sim_dt = sim.get_physics_dt()
    sim_time = 0.0
    count = 0

    # Nodal kinematic targets of the deformable bodies
    nodal_kinematic_target = cube_object.data.nodal_kinematic_target.torch.clone()

    # Simulate physics
    while sim.is_running():
        # reset at start and after 3 seconds
        if count % int(3.0 / sim_dt) == 0:
            # reset counters
            count = 0

            # reset the nodal state of the object
            nodal_state = cube_object.data.default_nodal_state_w.torch.clone()
            # apply random pose to the object
            pos_w = torch.rand(cube_object.num_instances, 3, device=sim.device) * 0.1 + scene.env_origins
            quat_w = math_utils.random_orientation(cube_object.num_instances, device=sim.device)
            nodal_state[..., :3] = cube_object.transform_nodal_pos(nodal_state[..., :3], pos_w, quat_w)

            # write nodal state to simulation
            cube_object.write_nodal_state_to_sim_index(nodal_state)

            # Write the nodal state to the kinematic target and free all vertices
            nodal_kinematic_target[..., :3] = nodal_state[..., :3]
            nodal_kinematic_target[..., 3] = 1.0
            cube_object.write_nodal_kinematic_target_to_sim_index(nodal_kinematic_target)

            # reset buffers
            cube_object.reset()

            print("----------------------------------------")
            print("[INFO]: Resetting object state...")

        # update the kinematic target for cubes at the positive and negative diagonal corners
        kinematic_cubes = [1, 2]
        # we slightly move the cube in the z-direction by picking the vertex at index 0
        nodal_kinematic_target[kinematic_cubes, 0, 2] += 0.2 * sim_dt
        # set vertex at index 0 to be kinematically constrained
        # 0: constrained, 1: free
        index_fill_(nodal_kinematic_target[:, 0, 3], kinematic_cubes, 0.0)
        # write kinematic target to simulation
        cube_object.write_nodal_kinematic_target_to_sim_index(nodal_kinematic_target)

        # write internal data to simulation
        cube_object.write_data_to_sim()
        # perform step
        sim.step()
        # update sim-time
        sim_time += sim_dt
        count += 1
        # update buffers
        cube_object.update(sim_dt)

        # print the root positions every second
        if count % int(1.0 / sim_dt) == 0:
            print(f"Time {sim_time:.2f}s: \tRoot position (in world): {cube_object.data.root_pos_w.torch[:, :3]}")

def main():
    """Main function."""
    with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg:
        if args_cli.backend == "newton_vbd":
            physics_cfg.solver_cfg.iterations = 10
            physics_cfg.num_substeps = 4
        sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg)
        sim = sim_utils.SimulationContext(sim_cfg)
        # Set main camera
        sim.set_camera_view(eye=[2.0, 2.0, 2.0], target=[0.0, 0.0, 0.75])
        scene_cfg = DeformableSceneCfg(num_envs=4, env_spacing=0.5)
        scene = instantiate(scene_cfg)
        # Play the simulator
        sim.reset()
        # Now we are ready!
        print("[INFO]: Setup complete...")
        # Run the simulator
        run_simulator(sim, scene)
        print("[INFO]: Simulation complete...")

if __name__ == "__main__":
    # run the main function
    main()
```

## The Code Explained

### Designing the scene

We declare the ground plane, light, and deformable cube in a subclass of `scene.InteractiveSceneCfg`.
`scene.InteractiveScene` constructs the assets and handles replication internally.
`DeformableSceneCfg(num_envs=4, env_spacing=0.5)` selects four environment origins on a centered grid
with 0.5 m spacing.

In this tutorial, we create a cubical soft object using the spawn configuration similar to the deformable cube
in the [Spawn Objects](/source/how-to/spawn_prims) tutorial. The only difference is that now we wrap
the spawning configuration into the `assets.DeformableObjectCfg` class. This class contains information about
the asset's spawning strategy and default initial state. When this class is passed to
the `assets.DeformableObject` class, it spawns the object and initializes the corresponding physics handles
when the simulation is played.

> **Note**
>
> Deformable objects require a mesh object to be spawned with backend-specific deformable body physics
> properties and a matching deformable physics material.
> Use `--backend physx` for the PhysX implementation or `--backend newton_vbd` for the experimental Newton
> implementation.

The scene constructs the deformable objects from their cfg and owns their clone lifecycle.
The simulation loop accesses the resulting asset through `scene["cube_object"]`.

```python
@configclass
class DeformableSceneCfg(InteractiveSceneCfg):
    """Soft cubes on a shared ground plane."""

    filter_collisions = False
    clone_cfg = CloneCfg(clone_template="/World/env_{}")

    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
    light = AssetBaseCfg(
        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=2000.0, color=(0.8, 0.8, 0.8))
    )

    # 3D Deformable Object
    cube_object = DeformableObjectCfg(
        prim_path="{ENV_REGEX_NS}/Cube",
        # PhysX deformables must be parsed from USD while other assets retain native replication.
        cloning_contexts=("isaaclab.cloner:UsdReplicateContext",) if args_cli.backend == "physx" else None,
        spawn=sim_utils.MeshCuboidCfg(
            size=(0.2, 0.2, 0.2),
            deformable_props=deformable_props,
            collision_props=collision_props,
            visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.1, 0.0)),
            physics_material=physics_material,
        ),
        init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)),
        debug_vis=True,
    )
```

### Running the simulation loop

Continuing from the rigid body tutorial, we reset the simulation at regular intervals, apply kinematic commands
to the deformable body, step the simulation, and update the deformable object's internal buffers.

#### Resetting the simulation state

Unlike rigid bodies and articulations, deformable objects have a different state representation. The state of a
deformable object is defined by the nodal positions and velocities of the mesh. The nodal positions and velocities
are defined in the **simulation world frame** and are stored in the `assets.DeformableObject.data` attribute.

We use the `assets.DeformableObject.data.default_nodal_state_w` attribute to get the default nodal state of the
spawned object prims. This default state can be configured from the `assets.DeformableObjectCfg.init_state`
attribute, which places the cube 1 m above its environment origin in this tutorial.

> **Warning**
>
> The initial state in the configuration `assets.DeformableObjectCfg` specifies the pose
> of the deformable object at the time of spawning. Based on this initial state, the default nodal state is
> obtained when the simulation is played for the first time.

We apply transformations to the nodal positions to randomize the initial state of the deformable object.

```python
# reset the nodal state of the object
nodal_state = cube_object.data.default_nodal_state_w.torch.clone()
# apply random pose to the object
pos_w = torch.rand(cube_object.num_instances, 3, device=sim.device) * 0.1 + scene.env_origins
quat_w = math_utils.random_orientation(cube_object.num_instances, device=sim.device)
nodal_state[..., :3] = cube_object.transform_nodal_pos(nodal_state[..., :3], pos_w, quat_w)
```

To reset the deformable object, we first set the nodal state by calling the `assets.DeformableObject.write_nodal_state_to_sim`
method. This method writes the nodal state of the deformable object prim into the simulation buffer.
Additionally, we free all the kinematic targets set for the nodes in the previous simulation step by calling
the `assets.DeformableObject.write_nodal_kinematic_target_to_sim` method. We explain the
kinematic targets in the next section.

Finally, we call the `assets.DeformableObject.reset` method to reset any internal buffers and caches.

```python
# write nodal state to simulation
cube_object.write_nodal_state_to_sim_index(nodal_state)

# Write the nodal state to the kinematic target and free all vertices
nodal_kinematic_target[..., :3] = nodal_state[..., :3]
nodal_kinematic_target[..., 3] = 1.0
cube_object.write_nodal_kinematic_target_to_sim_index(nodal_kinematic_target)

# reset buffers
cube_object.reset()
```

#### Stepping the simulation

Deformable bodies support user-driven kinematic control where a user can specify position targets for some of
the mesh nodes while the rest of the nodes are simulated by the active deformable solver. This [partial kinematic](https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html#kinematic-soft-bodies)
control is useful for applications where the user wants to interact with the deformable object in a controlled manner.

In this tutorial, we apply kinematic commands to two out of the four cubes in the scene. We set the position
targets for the node at index 0 (bottom-left corner) to move the cube along the z-axis.

At every step, we increment the kinematic position target for the node by a small value. Additionally,
we set the flag to indicate that the target is a kinematic target for that node in the simulation buffer.
These are set into the simulation buffer by calling the `assets.DeformableObject.write_nodal_kinematic_target_to_sim`
method.

```python
# update the kinematic target for cubes at the positive and negative diagonal corners
kinematic_cubes = [1, 2]
# we slightly move the cube in the z-direction by picking the vertex at index 0
nodal_kinematic_target[kinematic_cubes, 0, 2] += 0.2 * sim_dt
# set vertex at index 0 to be kinematically constrained
# 0: constrained, 1: free
index_fill_(nodal_kinematic_target[:, 0, 3], kinematic_cubes, 0.0)
# write kinematic target to simulation
cube_object.write_nodal_kinematic_target_to_sim_index(nodal_kinematic_target)
```

Similar to the rigid object and articulation, we perform the `assets.DeformableObject.write_data_to_sim` method
before stepping the simulation. For deformable objects, this method does not apply any external forces to the object.
However, we keep this method for completeness and future extensions.

```python
# write internal data to simulation
cube_object.write_data_to_sim()
```

#### Updating the state

After stepping the simulation, we update the internal buffers of the deformable object prims to reflect their new state
inside the `assets.DeformableObject.data` attribute. This is done using the `assets.DeformableObject.update` method.

At a fixed interval, we print the root position of the deformable object to the terminal. As mentioned
earlier, there is no concept of a root state for deformable objects. However, we compute the root position as
the average position of all the nodes in the mesh.

```python
# update buffers
cube_object.update(sim_dt)

# print the root positions every second
if count % int(1.0 / sim_dt) == 0:
    print(f"Time {sim_time:.2f}s: \tRoot position (in world): {cube_object.data.root_pos_w.torch[:, :3]}")
```

## The Code Execution

Now that we have gone through the code, let's run the script and see the result:

#### uv (Recommended)

```bash
uv run --extra isaacsim --extra tetrahedralization python scripts/tutorials/01_assets/run_deformable_object.py --visualizer kit
```

#### isaaclab.sh / isaaclab.bat

```bash
./isaaclab.sh -p scripts/tutorials/01_assets/run_deformable_object.py --visualizer kit
```

To run the same tutorial with the experimental Newton deformable backend:

#### uv (Recommended)

```bash
uv run --extra isaacsim --extra tetrahedralization python scripts/tutorials/01_assets/run_deformable_object.py --backend newton_vbd --visualizer kit
```

#### isaaclab.sh / isaaclab.bat

```bash
./isaaclab.sh -p scripts/tutorials/01_assets/run_deformable_object.py --backend newton_vbd --visualizer kit
```

This should open a stage with a ground plane, lights, and several cubes. Two of the four cubes must be dropping
from a height and settling on to the ground. Meanwhile the other two cubes must be moving along the z-axis. You
should see a marker showing the kinematic target position for the nodes at the bottom-left corner of the cubes.
To stop the simulation, you can either close the window, or press `Ctrl+C` in the terminal

![result of run\_deformable\_object.py](/isaaclab/_fern-img/efe61c1fbb7bd6b6e437613379986a7ce26e8790ab330ab545f0a8f06a9f905e.webp)

This tutorial showed how to spawn deformable objects and wrap them in a `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. The `deformables` example provides a more advanced
example, including surface deformables, loading USD assets, and applying deformable materials.