Creating a soft-body simulation#

This tutorial shows how to create a soft-body simulation using SimulationManager. It covers the setup of the simulation context, adding a deformable mesh (soft object), and running the simulation loop.

The Code#

The tutorial corresponds to the create_softbody.py script in the scripts/tutorials/sim directory.

Code for create_softbody.py
  1# ----------------------------------------------------------------------------
  2# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
  3#
  4# Licensed under the Apache License, Version 2.0 (the "License");
  5# you may not use this file except in compliance with the License.
  6# You may obtain a copy of the License at
  7#
  8#     http://www.apache.org/licenses/LICENSE-2.0
  9#
 10# Unless required by applicable law or agreed to in writing, software
 11# distributed under the License is distributed on an "AS IS" BASIS,
 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13# See the License for the specific language governing permissions and
 14# limitations under the License.
 15# ----------------------------------------------------------------------------
 16
 17"""
 18This script demonstrates how to create a simulation scene using SimulationManager.
 19It shows the basic setup of simulation context, adding objects, lighting, and sensors.
 20"""
 21
 22from __future__ import annotations
 23
 24import argparse
 25import time
 26from embodichain.cli.sim import add_sim_args_to_parser
 27
 28
 29def build_parser() -> argparse.ArgumentParser:
 30    """Build CLI options without initializing simulation resources."""
 31    parser = argparse.ArgumentParser(
 32        description="Create a simulation scene with SimulationManager"
 33    )
 34    add_sim_args_to_parser(parser)
 35    parser.set_defaults(device="cuda", physics="newton")
 36    return parser
 37
 38
 39if __name__ == "__main__":
 40    # Parse before importing optional simulation/planning dependencies.
 41    _cli_args = build_parser().parse_args()
 42
 43
 44from embodichain.data import get_data_path
 45from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
 46from embodichain.lab.visualization import visualization_cfg_from_args
 47from embodichain.lab.sim.cfg import (
 48    NewtonCollisionPipelineCfg,
 49    NewtonPhysicsCfg,
 50    RenderCfg,
 51    VolumeDeformableObjectCfg,
 52    VolumeDeformableMeshingCfg,
 53    VolumeDeformablePhysicsCfg,
 54)
 55from embodichain.lab.sim.shapes import MeshCfg
 56from embodichain.lab.sim.objects import VolumeDeformableObject
 57
 58
 59def main(args: argparse.Namespace | None = None) -> None:
 60    """Main function to create and run the simulation scene."""
 61
 62    # Parse command line arguments
 63    parser = build_parser()
 64    if args is None:
 65        args = parser.parse_args()
 66    if args.physics != "newton":
 67        parser.error("Soft bodies require --physics newton.")
 68    if not str(args.device).startswith("cuda"):
 69        parser.error("Soft bodies require a CUDA device.")
 70
 71    # Configure the simulation
 72    sim_cfg = SimulationManagerCfg(
 73        width=1920,
 74        height=1080,
 75        headless=args.headless,
 76        num_envs=args.num_envs,
 77        arena_space=args.arena_space,
 78        gpu_id=args.gpu_id,
 79        physics_dt=1.0 / 100.0,  # Physics timestep (100 Hz)
 80        device=args.device,
 81        render_cfg=RenderCfg(
 82            renderer=args.renderer
 83        ),  # Enable ray tracing for better visuals
 84        physics_cfg=NewtonPhysicsCfg(
 85            num_substeps=6,
 86            solver_cfg={
 87                "solver_type": "vbd",
 88                "iterations": 8,
 89                "particle_enable_self_contact": False,
 90                "particle_self_contact_radius": 0.001,
 91                "particle_self_contact_margin": 0.001,
 92                "particle_topological_contact_filter_threshold": 3,
 93                "particle_enable_tile_solve": True,
 94                "soft_contact_ke": 5.0e4,
 95                "soft_contact_kd": 1.0e-3,
 96                "soft_contact_mu": 1.5,
 97            },
 98            collision_cfg=NewtonCollisionPipelineCfg(
 99                soft_contact_margin=0.002,
100            ),
101        ),
102        visualization=visualization_cfg_from_args(args),
103    )
104
105    # Create the simulation instance
106    sim = SimulationManager(sim_cfg)
107
108    print("[INFO]: Scene setup complete!")
109
110    # add softbody to the scene
111    cow: VolumeDeformableObject = sim.add_deformable_object(
112        cfg=VolumeDeformableObjectCfg(
113            uid="cow",
114            shape=MeshCfg(
115                fpath=get_data_path("Cow/cow.obj"),
116            ),
117            init_pos=[0.0, 5.0, 3.0],
118            particle_radius=0.01,
119            meshing=VolumeDeformableMeshingCfg(
120                triangle_remesh_resolution=24,
121                simulation_mesh_resolution=16,
122                voxel_num_relaxation_iters=5,
123            ),
124            attrs=VolumeDeformablePhysicsCfg(
125                # Equivalent to the DexSim demo's k_mu=1e4 and k_lambda=5e4.
126                youngs=2.833333333e4,
127                poissons=5.0 / 12.0,
128                density=50.0,
129                elasticity_damping=2.0e-3,
130            ),
131        ),
132    )
133    print("[INFO]: Add soft object complete!")
134
135    sim.prepare()
136
137    # Open window when the scene has been set up
138    if not args.headless:
139        sim.open_window()
140
141    print(f"[INFO]: Running simulation with {args.num_envs} environment(s)")
142    print("[INFO]: Press Ctrl+C to stop the simulation")
143
144    # Run the simulation
145    run_simulation(sim, cow)
146
147
148def run_simulation(sim: SimulationManager, soft_obj: VolumeDeformableObject) -> None:
149    """Run the simulation loop.
150
151    Args:
152        sim: The SimulationManager instance to run
153        soft_obj: soft object
154    """
155
156    step_count = 0
157
158    try:
159        last_time = time.time()
160        last_step = 0
161        while True:
162            # Update physics simulation
163            sim.update(step=1)
164            step_count += 1
165
166            # Print FPS every second
167            if step_count % 100 == 0:
168                current_time = time.time()
169                elapsed = current_time - last_time
170                fps = (
171                    sim.num_envs * (step_count - last_step) / elapsed
172                    if elapsed > 0
173                    else 0
174                )
175                print(f"[INFO]: Simulation step: {step_count}, FPS: {fps:.2f}")
176                last_time = current_time
177                last_step = step_count
178                if step_count % 500 == 0:
179                    soft_obj.reset()
180
181    except KeyboardInterrupt:
182        print("\n[INFO]: Stopping simulation...")
183    finally:
184        # Clean up resources
185        sim.destroy()
186        print("[INFO]: Simulation terminated successfully")
187
188
189if __name__ == "__main__":
190    main(_cli_args)

The Code Explained#

Configuring the simulation#

The first step is to configure the simulation environment. This is done using the SimulationManagerCfg data class, which allows you to specify parameters like window dimensions, headless mode, physics timestep, simulation device (CPU/GPU), and rendering options like ray tracing. Soft-body simulation requires a CUDA device and the Newton backend; the script validates both options before creating the scene.

    # Configure the simulation
    sim_cfg = SimulationManagerCfg(
        width=1920,
        height=1080,
        headless=args.headless,
        num_envs=args.num_envs,
        arena_space=args.arena_space,
        gpu_id=args.gpu_id,
        physics_dt=1.0 / 100.0,  # Physics timestep (100 Hz)
        device=args.device,
        render_cfg=RenderCfg(
            renderer=args.renderer
        ),  # Enable ray tracing for better visuals
        physics_cfg=NewtonPhysicsCfg(
            num_substeps=6,
            solver_cfg={
                "solver_type": "vbd",
                "iterations": 8,
                "particle_enable_self_contact": False,
                "particle_self_contact_radius": 0.001,
                "particle_self_contact_margin": 0.001,
                "particle_topological_contact_filter_threshold": 3,
                "particle_enable_tile_solve": True,
                "soft_contact_ke": 5.0e4,
                "soft_contact_kd": 1.0e-3,
                "soft_contact_mu": 1.5,
            },
            collision_cfg=NewtonCollisionPipelineCfg(
                soft_contact_margin=0.002,
            ),
        ),
        visualization=visualization_cfg_from_args(args),
    )

    # Create the simulation instance
    sim = SimulationManager(sim_cfg)

    print("[INFO]: Scene setup complete!")

Adding a soft body to the scene#

With the simulation context created, we can add a soft (deformable) object. This tutorial demonstrates adding a soft-body cow mesh to the scene using the SimulationManager.add_deformable_object() method. The object’s geometry and physical parameters are defined through configuration objects:

    # add softbody to the scene
    cow: VolumeDeformableObject = sim.add_deformable_object(
        cfg=VolumeDeformableObjectCfg(
            uid="cow",
            shape=MeshCfg(
                fpath=get_data_path("Cow/cow.obj"),
            ),
            init_pos=[0.0, 5.0, 3.0],
            particle_radius=0.01,
            meshing=VolumeDeformableMeshingCfg(
                triangle_remesh_resolution=24,
                simulation_mesh_resolution=16,
                voxel_num_relaxation_iters=5,
            ),
            attrs=VolumeDeformablePhysicsCfg(
                # Equivalent to the DexSim demo's k_mu=1e4 and k_lambda=5e4.
                youngs=2.833333333e4,
                poissons=5.0 / 12.0,
                density=50.0,
                elasticity_damping=2.0e-3,
            ),
        ),
    )
    print("[INFO]: Add soft object complete!")

The Code Execution#

To run the script and see the result, execute the following command:

python scripts/tutorials/sim/create_softbody.py

A window should appear showing a soft-body cow mesh falling onto a ground plane. To stop the simulation, you can either close the window or press Ctrl+C in the terminal.

You can also pass arguments to customize the simulation. For example, to run in headless mode with n parallel environments using the specified device:

python scripts/tutorials/sim/create_softbody.py --headless --num_envs <n> --device <cuda/cpu>

To inspect the deforming collision surface in a browser:

python scripts/tutorials/sim/create_softbody.py \
    --viser \
    --viser-soft-body-fps 5

DexSim exposes live soft-body collision vertices but not their triangle connectivity. The Viser preview therefore uses a stable convex-hull surface: motion and deformation are visible, while concave details of the original cow render mesh are intentionally simplified. Lower --viser-soft-body-fps when publishing large soft bodies or several environments.

See Browser visualization with Viser for details.

Now that we have a basic understanding of how to create a soft-body scene, let’s move on to more advanced topics.