# Soft robots
A soft robot is a deformable body with **muscle fibers** embedded in it. Instead of joint motors turning a rigid skeleton, you drive it by contracting those fibers: each actuation signal adds an active stress along the fiber direction, and the body deforms in response. This page shows how to build muscle-actuated soft robots and control them.
Genesis World simulates muscles with two deformable solvers, and you pick one through the entity's material:
- `gs.materials.MPM.Muscle`: the Material Point Method solver, actuated per **particle**.
- `gs.materials.FEM.Muscle`: the Finite Element Method solver, actuated per tetrahedral **element**.
Both share the same control interface, so you can swap solvers without rewriting your control loop. The {doc}`beyond_rigid_bodies` tutorial covers the underlying solvers in more depth.
:::{note}
MPM and FEM are compute-heavy. Run them on the GPU by passing `backend=gs.gpu` to `gs.init()` for interactive frame rates.
:::
## Minimal example
The complete script is [`examples/tutorials/advanced_muscle.py`](https://github.com/Genesis-Embodied-AI/genesis-world/blob/main/examples/tutorials/advanced_muscle.py). It drops two spheres (one MPM, one FEM) into a zero-gravity scene and pulses them with a sine wave so you can compare the solvers side by side.
Only two things distinguish a soft robot from an ordinary deformable body. First, give the entity a `Muscle` material:
```python
E, nu = 3.0e4, 0.45 # Young's modulus (Pa) and Poisson ratio
rho = 1000.0 # density, kg/m³
robot_mpm = scene.add_entity(
morph=gs.morphs.Sphere(pos=(0.5, 0.2, 0.3), radius=0.1),
material=gs.materials.MPM.Muscle(E=E, nu=nu, rho=rho, model="neohooken"),
)
robot_fem = scene.add_entity(
morph=gs.morphs.Sphere(pos=(0.5, -0.2, 0.3), radius=0.1),
material=gs.materials.FEM.Muscle(E=E, nu=nu, rho=rho, model="stable_neohookean"),
)
```
Second, call `set_actuation` each step instead of a joint-control method:
```python
for i in range(1000):
actu = [0.2 * (0.5 + np.sin(0.01 * np.pi * i))] # one value per muscle group
robot_mpm.set_actuation(actu)
robot_fem.set_actuation(actu)
scene.step()
```
Everything else (the plane, the scene, `build`, `step`) is the standard flow from {doc}`/user_guide/getting_started/hello_genesis`.
:::{note}
The constitutive `model` names differ between solvers. MPM uses `"corotation"` or `"neohooken"`; FEM uses `"linear"` or `"stable_neohookean"`. (`"stable_neohooken"` is a deprecated spelling of the FEM model and will warn.)
:::
## The scene: timestep and gravity
Soft-body dynamics need small timesteps and several substeps for numerical stability. Set the timestep on each solver's options, not on `SimOptions`:
```python
dt = 5e-4 # seconds
scene = gs.Scene(
sim_options=gs.options.SimOptions(
substeps=10,
gravity=(0, 0, 0), # float freely so the muscle motion is easy to see
),
mpm_options=gs.options.MPMOptions(
dt=dt,
lower_bound=(-1.0, -1.0, -0.2), # MPM simulates on a fixed grid;
upper_bound=(1.0, 1.0, 1.0), # entities must stay inside these bounds
),
fem_options=gs.options.FEMOptions(dt=dt, damping=45.0),
show_viewer=True,
)
```
The MPM solver discretizes space onto a background grid; `lower_bound` and `upper_bound` set its extent in meters (Z-up). Any particle that leaves the grid is lost, so size the bounds to contain the robot's full range of motion.
## Muscle groups and fiber directions
By default a soft robot has a single muscle group spanning its whole body, with all fibers pointing along `+Z` (`[0, 0, 1]`). A single actuation value then contracts the entire body along that axis: useful for the sphere demo, useless for locomotion.
To make a robot move deliberately, partition it into groups and assign each part a fiber direction. Declare the number of groups on the material, then call `set_muscle` after `build` (it reads the built particle positions):
```python
worm = scene.add_entity(
morph=gs.morphs.Mesh(
file="meshes/worm/worm.obj",
pos=(0.3, 0.3, 0.001),
scale=0.1,
euler=(90, 0, 0), # extrinsic x-y-z, degrees
),
material=gs.materials.MPM.Muscle(
E=5e5,
nu=0.45,
rho=10000.0,
model="neohooken",
n_groups=4, # at most 4 independently actuated muscles
),
)
scene.build(n_envs=3)
```
`set_muscle` takes two per-unit arrays, where a *unit* is a particle for MPM and an element for FEM:
- `muscle_group`: an integer in `[0, n_groups)` per unit, naming which muscle each unit belongs to.
- `muscle_direction`: a fiber direction per unit (or one shared vector). Genesis does **not** normalize it; pass unit vectors.
The worm example carves the body into upper/lower and fore/hind quarters by particle position, then points every fiber along `+Y`:
```python
pos = worm.get_state().pos[0] # ([n_envs,] n_particles, 3) — take env 0
n_units = worm.n_particles # FEM instead uses worm.n_elements
pos_max, pos_min = pos.max(dim=0).values, pos.min(dim=0).values
pos_range = pos_max - pos_min
lu_thr, fh_thr = 0.3, 0.6
muscle_group = torch.zeros((n_units,), dtype=gs.tc_int, device=gs.device)
mask_upper = pos[:, 2] > (pos_min[2] + pos_range[2] * lu_thr)
mask_fore = pos[:, 1] < (pos_min[1] + pos_range[1] * fh_thr)
muscle_group[mask_upper & mask_fore] = 0 # upper fore body
muscle_group[mask_upper & ~mask_fore] = 1 # upper hind body
muscle_group[~mask_upper & mask_fore] = 2 # lower fore body
muscle_group[~mask_upper & ~mask_fore] = 3 # lower hind body
worm.set_muscle(
muscle_group=muscle_group,
muscle_direction=(0.0, 1.0, 0.0), # fibers along +Y, shared by all units
)
```
`set_actuation` now takes one value per group, so its input has shape `(n_groups,)`. Pulsing only the lower-hind group makes the worm crawl forward:
```python
for i in range(1000):
actu = (0.0, 0.0, 0.0, 1.0 * (0.5 + math.sin(0.005 * math.pi * i))) # shape (n_groups,)
worm.set_actuation(actu)
scene.step()
```
The full script is [`examples/tutorials/advanced_worm.py`](https://github.com/Genesis-Embodied-AI/genesis-world/blob/main/examples/tutorials/advanced_worm.py).
## Hybrid rigid-soft robots
A hybrid robot drives a soft outer skin with a rigid inner skeleton: the skeleton carries the degrees of freedom, and the skin deforms around it. Build one with `gs.materials.Hybrid`, which pairs a `gs.materials.Rigid` skeleton with a soft material that must be `gs.materials.MPM.Muscle`:
```python
robot = scene.add_entity(
morph=gs.morphs.URDF(
file="urdf/simple/two_link_arm.urdf",
pos=(0.5, 0.5, 0.3),
scale=0.2,
fixed=True,
),
material=gs.materials.Hybrid(
material_rigid=gs.materials.Rigid(gravity_compensation=1.0),
material_soft=gs.materials.MPM.Muscle(E=1e4, nu=0.45, rho=1000.0, model="neohooken"),
thickness=0.05, # skin thickness grown around the skeleton, meters
damping=1000.0,
),
)
```
Because the actuation comes from the rigid skeleton, you control a hybrid robot through the ordinary rigid interface (`control_dofs_velocity`, `control_dofs_position`, `control_dofs_force`) with as many values as the skeleton has degrees of freedom:
```python
for i in range(1000):
dofs_ctrl = [1.0 * np.sin(2 * np.pi * i * 0.001)] * robot.n_dofs
robot.control_dofs_velocity(dofs_ctrl) # drive the inner skeleton
scene.step()
```
The full script is [`examples/tutorials/advanced_hybrid_robot.py`](https://github.com/Genesis-Embodied-AI/genesis-world/blob/main/examples/tutorials/advanced_hybrid_robot.py). See {doc}`hybrid_entity` for how the skin is grown from the skeleton (or the skeleton from a mesh) and how to customize that association.
## See also
- {doc}`hybrid_entity`: building rigid-soft hybrid entities from a URDF or a mesh.
- {doc}`beyond_rigid_bodies`: the MPM, FEM, SPH, and PBD solvers behind fluids, cloth, and deformable bodies.