Contact Sensor

Contact Sensor#

A contact sensor with filtering

The contact sensor is designed to return the net contact force acting on a given ridgid body. The sensor is written to behave as a physical object, and so the “scope” of the contact sensor is limited to the body (or bodies) that defines it. There are multiple ways to define this scope, depending on your need to filter the forces coming from the contact.

By default, the reported force is the total contact force, but your application may only care about contact forces due to specific objects. Retrieving contact forces from specific objects requires filtering, and this can only be done in a “many-to-one” way. A multi-legged robot that needs filterable contact information for its feet would require one sensor per foot to be defined in the environment, but a robotic hand with contact sensors on the tips of each finger can be defined with a single sensor.

Consider a simple environment with an Anymal Quadruped and a block

# Pre-defined configs
##
from isaaclab_assets.robots.anymal import ANYMAL_C_CFG  # isort: skip


@configclass
class ContactSensorSceneCfg(InteractiveSceneCfg):
    """Design the scene with sensors on the robot."""

    # ground plane
    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())

    # lights
    dome_light = AssetBaseCfg(
        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
    )

    # robot
    robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")

    # Rigid Object
    cube = RigidObjectCfg(
        prim_path="{ENV_REGEX_NS}/Cube",
        spawn=sim_utils.CuboidCfg(
            size=(0.5, 0.5, 0.1),
            rigid_props=sim_utils.RigidBodyPropertiesCfg(),
            mass_props=sim_utils.MassPropertiesCfg(mass=100.0),
            collision_props=sim_utils.CollisionPropertiesCfg(),
            physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=1.0),
            visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
        ),
        init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.5, 0.05)),
    )

    contact_forces_LF = ContactSensorCfg(
        prim_path="{ENV_REGEX_NS}/Robot/LF_FOOT",
        update_period=0.0,
        history_length=6,
        debug_vis=True,
        filter_prim_paths_expr=["{ENV_REGEX_NS}/Cube"],
    )

    contact_forces_RF = ContactSensorCfg(
        prim_path="{ENV_REGEX_NS}/Robot/RF_FOOT",
        update_period=0.0,
        history_length=6,
        debug_vis=True,
        filter_prim_paths_expr=["{ENV_REGEX_NS}/Cube"],
    )

    contact_forces_H = ContactSensorCfg(

We define the sensors on the feet of the robot in two different ways. The front feet are independent sensors (one sensor body per foot) and the “Cube” is placed under the left foot. The hind feet are defined as a single sensor with multiple bodies.

We can then run the scene and print the data from the sensors

def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
  .
  .
  .
  # Simulate physics
  while simulation_app.is_running():
    .
    .
    .
    # print information from the sensors
    print("-------------------------------")
    print(scene["contact_forces_LF"])
    print("Received force matrix of: ", scene["contact_forces_LF"].data.force_matrix_w)
    print("Received contact force of: ", scene["contact_forces_LF"].data.net_forces_w)
    print("-------------------------------")
    print(scene["contact_forces_RF"])
    print("Received force matrix of: ", scene["contact_forces_RF"].data.force_matrix_w)
    print("Received contact force of: ", scene["contact_forces_RF"].data.net_forces_w)
    print("-------------------------------")
    print(scene["contact_forces_H"])
    print("Received force matrix of: ", scene["contact_forces_H"].data.force_matrix_w)
    print("Received contact force of: ", scene["contact_forces_H"].data.net_forces_w)

Here, we print both the net contact force and the filtered force matrix for each contact sensor defined in the scene. The front left and front right feet report the following

-------------------------------
Contact sensor @ '/World/envs/env_.*/Robot/LF_FOOT':
        view type         : <class 'omni.physics.tensors.impl.api.RigidBodyView'>
        update period (s) : 0.0
        number of bodies  : 1
        body names        : ['LF_FOOT']

Received force matrix of:  tensor([[[[-1.3923e-05,  1.5727e-04,  1.1032e+02]]]], device='cuda:0')
Received contact force of:  tensor([[[-1.3923e-05,  1.5727e-04,  1.1032e+02]]], device='cuda:0')
-------------------------------
Contact sensor @ '/World/envs/env_.*/Robot/RF_FOOT':
        view type         : <class 'omni.physics.tensors.impl.api.RigidBodyView'>
        update period (s) : 0.0
        number of bodies  : 1
        body names        : ['RF_FOOT']

Received force matrix of:  tensor([[[[0., 0., 0.]]]], device='cuda:0')
Received contact force of:  tensor([[[1.3529e-05, 0.0000e+00, 1.0069e+02]]], device='cuda:0')
The contact sensor visualization

Notice that even with filtering, both sensors report the net contact force acting on the foot. However, the “force matrix” on the right foot is zero because that foot isn’t in contact with the filtered body, /World/envs/env_.*/Cube. Now, checkout the data coming from the hind feet!

-------------------------------
Contact sensor @ '/World/envs/env_.*/Robot/.*H_FOOT':
        view type         : <class 'omni.physics.tensors.impl.api.RigidBodyView'>
        update period (s) : 0.0
        number of bodies  : 2
        body names        : ['LH_FOOT', 'RH_FOOT']

Received force matrix of:  None
Received contact force of:  tensor([[[9.7227e-06, 0.0000e+00, 7.2364e+01],
        [2.4322e-05, 0.0000e+00, 1.8102e+02]]], device='cuda:0')

In this case, the contact sensor has two bodies: the left and right hind feet. When the force matrix is queried, the result is None because this is a many body sensor, and presently Isaac Lab only supports “many to one” contact force filtering. Unlike the single body contact sensor, the reported force tensor has multiple entries, with each “row” corresponding to the contact force on a single body of the sensor (matching the ordering at construction).

Code for contact_sensor.py
  1# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
  2# All rights reserved.
  3#
  4# SPDX-License-Identifier: BSD-3-Clause
  5
  6# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
  7# All rights reserved.
  8#
  9# SPDX-License-Identifier: BSD-3-Clause
 10
 11"""Launch Isaac Sim Simulator first."""
 12
 13import argparse
 14
 15from isaaclab.app import AppLauncher
 16
 17# add argparse arguments
 18parser = argparse.ArgumentParser(description="Example on using the contact sensor.")
 19parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to spawn.")
 20# append AppLauncher cli args
 21AppLauncher.add_app_launcher_args(parser)
 22# parse the arguments
 23args_cli = parser.parse_args()
 24
 25# launch omniverse app
 26app_launcher = AppLauncher(args_cli)
 27simulation_app = app_launcher.app
 28
 29"""Rest everything follows."""
 30
 31import torch
 32
 33import isaaclab.sim as sim_utils
 34from isaaclab.assets import AssetBaseCfg, RigidObjectCfg
 35from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
 36from isaaclab.sensors import ContactSensorCfg
 37from isaaclab.utils import configclass
 38
 39##
 40# Pre-defined configs
 41##
 42from isaaclab_assets.robots.anymal import ANYMAL_C_CFG  # isort: skip
 43
 44
 45@configclass
 46class ContactSensorSceneCfg(InteractiveSceneCfg):
 47    """Design the scene with sensors on the robot."""
 48
 49    # ground plane
 50    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
 51
 52    # lights
 53    dome_light = AssetBaseCfg(
 54        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
 55    )
 56
 57    # robot
 58    robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
 59
 60    # Rigid Object
 61    cube = RigidObjectCfg(
 62        prim_path="{ENV_REGEX_NS}/Cube",
 63        spawn=sim_utils.CuboidCfg(
 64            size=(0.5, 0.5, 0.1),
 65            rigid_props=sim_utils.RigidBodyPropertiesCfg(),
 66            mass_props=sim_utils.MassPropertiesCfg(mass=100.0),
 67            collision_props=sim_utils.CollisionPropertiesCfg(),
 68            physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=1.0),
 69            visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
 70        ),
 71        init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.5, 0.05)),
 72    )
 73
 74    contact_forces_LF = ContactSensorCfg(
 75        prim_path="{ENV_REGEX_NS}/Robot/LF_FOOT",
 76        update_period=0.0,
 77        history_length=6,
 78        debug_vis=True,
 79        filter_prim_paths_expr=["{ENV_REGEX_NS}/Cube"],
 80    )
 81
 82    contact_forces_RF = ContactSensorCfg(
 83        prim_path="{ENV_REGEX_NS}/Robot/RF_FOOT",
 84        update_period=0.0,
 85        history_length=6,
 86        debug_vis=True,
 87        filter_prim_paths_expr=["{ENV_REGEX_NS}/Cube"],
 88    )
 89
 90    contact_forces_H = ContactSensorCfg(
 91        prim_path="{ENV_REGEX_NS}/Robot/.*H_FOOT",
 92        update_period=0.0,
 93        history_length=6,
 94        debug_vis=True,
 95    )
 96
 97
 98def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
 99    """Run the simulator."""
100    # Define simulation stepping
101    sim_dt = sim.get_physics_dt()
102    sim_time = 0.0
103    count = 0
104
105    # Simulate physics
106    while simulation_app.is_running():
107
108        if count % 500 == 0:
109            # reset counter
110            count = 0
111            # reset the scene entities
112            # root state
113            # we offset the root state by the origin since the states are written in simulation world frame
114            # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
115            root_state = scene["robot"].data.default_root_state.clone()
116            root_state[:, :3] += scene.env_origins
117            scene["robot"].write_root_pose_to_sim(root_state[:, :7])
118            scene["robot"].write_root_velocity_to_sim(root_state[:, 7:])
119            # set joint positions with some noise
120            joint_pos, joint_vel = (
121                scene["robot"].data.default_joint_pos.clone(),
122                scene["robot"].data.default_joint_vel.clone(),
123            )
124            joint_pos += torch.rand_like(joint_pos) * 0.1
125            scene["robot"].write_joint_state_to_sim(joint_pos, joint_vel)
126            # clear internal buffers
127            scene.reset()
128            print("[INFO]: Resetting robot state...")
129        # Apply default actions to the robot
130        # -- generate actions/commands
131        targets = scene["robot"].data.default_joint_pos
132        # -- apply action to the robot
133        scene["robot"].set_joint_position_target(targets)
134        # -- write data to sim
135        scene.write_data_to_sim()
136        # perform step
137        sim.step()
138        # update sim-time
139        sim_time += sim_dt
140        count += 1
141        # update buffers
142        scene.update(sim_dt)
143
144        # print information from the sensors
145        print("-------------------------------")
146        print(scene["contact_forces_LF"])
147        print("Received force matrix of: ", scene["contact_forces_LF"].data.force_matrix_w)
148        print("Received contact force of: ", scene["contact_forces_LF"].data.net_forces_w)
149        print("-------------------------------")
150        print(scene["contact_forces_RF"])
151        print("Received force matrix of: ", scene["contact_forces_RF"].data.force_matrix_w)
152        print("Received contact force of: ", scene["contact_forces_RF"].data.net_forces_w)
153        print("-------------------------------")
154        print(scene["contact_forces_H"])
155        print("Received force matrix of: ", scene["contact_forces_H"].data.force_matrix_w)
156        print("Received contact force of: ", scene["contact_forces_H"].data.net_forces_w)
157
158
159def main():
160    """Main function."""
161
162    # Initialize the simulation context
163    sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
164    sim = sim_utils.SimulationContext(sim_cfg)
165    # Set main camera
166    sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0])
167    # design scene
168    scene_cfg = ContactSensorSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
169    scene = InteractiveScene(scene_cfg)
170    # Play the simulator
171    sim.reset()
172    # Now we are ready!
173    print("[INFO]: Setup complete...")
174    # Run the simulator
175    run_simulator(sim, scene)
176
177
178if __name__ == "__main__":
179    # run the main function
180    main()
181    # close sim app
182    simulation_app.close()