newton.sensors.SensorContact#

class newton.sensors.SensorContact(model, *, sensing_bodies=None, sensing_shapes=None, counterpart_bodies=None, counterpart_shapes=None, measure_total=True, verbose=None, request_contact_attributes=True)[source]#

Bases: object

Measures contact forces, friction, and force-weighted positions on sensing objects (bodies or shapes).

In its simplest form the sensor reports total_force — the total contact force on each sensing object — and total_force_friction, its tangential (friction) component. Optionally, specify counterparts to separate these measurements by interacting body or shape. force_matrix reports the per-counterpart contact forces, force_matrix_friction reports their frictional (tangential) components, and position_matrix reports the average interaction positions. In each matrix, row i corresponds to sensing_indices[i] and column j within that row corresponds to counterpart_indices[i][j]. Columns beyond a row’s counterpart list are zero padding.

Each position_matrix entry is the average of the contact midpoints accumulated at that index, weighted by contact-force magnitude. It provides a representative location for where each counterpart interacts with the sensing object. Force weighting reduces the influence of weak contacts, so adding or removing a low-force contact perturbs the reported position less.

total_force and total_force_friction are None when measure_total=False. Per-counterpart outputs force_matrix, force_matrix_friction, and position_matrix are None when no counterparts are specified.

Multi-world behavior

When the model contains multiple worlds, counterpart mappings are resolved per-world. The collision pipeline and solver are expected to produce only within-world contacts, so cross-world force accumulation does not arise in practice. Global counterparts (e.g. ground plane) contribute to every world they contact.

In single-world models where no add_world() call was made (all entities are global / world=-1), the sensor treats the entire model as one implicit world and all entities are valid sensing objects.

When counterparts are specified, the per-counterpart matrices have shape (sum_of_sensors_across_worlds, max_counterparts), where max_counterparts is the maximum counterpart count of any single world. Row order matches sensing_indices. Columns beyond a world’s own counterpart count are zero-padded.

sensing_indices and counterpart_indices are flat lists that describe the structure of the output arrays.

Terms

  • Sensing object – body or shape carrying a contact sensor.

  • Counterpart – the other body or shape in a contact interaction.

Construction and update order

Construct a CollisionPipeline and pass its contacts to observables() when requesting CONTACT_F from the solver. Pass the resulting Observables and the pipeline’s Contacts buffer to both the solver step and update(). The sensor uses only the linear part of each CONTACT_F wrench (force [N]); the torque part is ignored.

Parameters that select bodies or shapes accept label patterns – see Label Matching.

Example

Measure total contact force on a sphere resting on the ground:

import warp as wp
import newton
from newton.sensors import SensorContact

builder = newton.ModelBuilder()
builder.add_ground_plane()
body = builder.add_body(xform=wp.transform((0, 0, 0.1), wp.quat_identity()))
builder.add_shape_sphere(body, radius=0.1, label="ball")
model = builder.finalize()

sensor = SensorContact(model, sensing_shapes="ball", request_contact_attributes=False)
solver = newton.solvers.SolverMuJoCo(model)
state = model.state()
collision_pipeline = newton.CollisionPipeline(
    model, rigid_contact_max=solver.get_max_contact_count(), soft_contact_max=0
)
contacts = collision_pipeline.contacts()
observables = solver.observables(kinds=sensor.solver_observable_kinds, contacts=contacts)

solver.step(state, state, None, contacts, dt=1.0 / 60.0, observables=observables)
sensor.update(state, contacts, observables=observables)
force = sensor.total_force.numpy()  # (n_sensing, 3)
Raises:

ValueError – If the configuration of sensing/counterpart objects is invalid.

__init__(model, *, sensing_bodies=None, sensing_shapes=None, counterpart_bodies=None, counterpart_shapes=None, measure_total=True, verbose=None, request_contact_attributes=True)#

Initialize the SensorContact.

Exactly one of sensing_bodies or sensing_shapes must be specified to define the sensing objects. At most one of counterpart_bodies or counterpart_shapes may be specified. If neither is specified, only total_force and total_force_friction are available (no per-counterpart breakdown or contact positions).

Parameters:
  • model (Model) – The simulation model providing shape/body definitions and world layout.

  • sensing_bodies (str | list[str] | Pattern[str] | list[int] | None) – Glob pattern, list of glob patterns, compiled regular-expression pattern to match against body labels, or list of body indices. Regular expressions use full matching.

  • sensing_shapes (str | list[str] | Pattern[str] | list[int] | None) – Glob pattern, list of glob patterns, compiled regular-expression pattern to match against shape labels, or list of shape indices. Regular expressions use full matching.

  • counterpart_bodies (str | list[str] | Pattern[str] | list[int] | None) – Glob pattern, list of glob patterns, compiled regular-expression pattern to match against body labels, or list of body indices. Regular expressions use full matching.

  • counterpart_shapes (str | list[str] | Pattern[str] | list[int] | None) – Glob pattern, list of glob patterns, compiled regular-expression pattern to match against shape labels, or list of shape indices. Regular expressions use full matching.

  • measure_total (bool) – If True (default), total_force and total_force_friction are allocated. If False, both are None.

  • verbose (bool | None) – If True, print details. If False, suppress details. If None, print details when wp.config.log_level is configured for debug logging.

  • request_contact_attributes (bool) –

    If True, request the deprecated contacts.force extended attribute for compatibility. Defaults to True during the deprecation period. Pass False and supply solver observables to update() instead.

    Deprecated since version 1.7: Passing True is deprecated. Allocate solver_observable_kinds through the solver and pass Observables to update() instead.

update(state, contacts, *, observables=None)#

Update the contact sensor readings based on the provided state and contacts.

Computes world-frame transforms for all sensing objects and evaluates contact forces and their friction (tangential) components (total and/or per-counterpart, depending on sensor configuration). When state provides body transforms, also computes force-weighted per-counterpart contact positions.

Parameters:
  • state (State | None) – The simulation state providing body transforms. If None (or a state without body_q), sensing_transforms is left unchanged and position_matrix is reset to zero. Contact-force outputs are updated in either case.

  • contacts (Contacts) – The contact data to evaluate.

  • observables (SolverBase.Observables | None) – Solver observable arrays containing contact_f. If omitted, the deprecated contacts.force array is used when available. Must use the same contacts supplied to observables(). Before the first solver step, newly allocated observables report zero contact forces. See Solver Observables for allocation and usage examples.

Raises:
  • ValueError – If observables belong to a different model, no contact-force output is available, or the observables are bound to a different contacts instance.

  • ValueError – If legacy contacts are on a different device from the sensor.

counterpart_indices: list[list[int]]#

Counterpart body or shape indices per sensing object. counterpart_indices[i] lists the counterparts for row i. Global counterparts appear first, followed by per-world locals in ascending index order.

counterpart_type: Literal['body', 'shape'] | None#

Whether counterpart_indices contains body indices ("body") or shape indices ("shape"). None when no counterparts are specified.

force_matrix: wp.array2d[wp.vec3f] | None#

Per-counterpart contact forces [N], shape (n_sensing, max_counterparts), dtype vec3. Entry [i, j] is the force on sensing object i from counterpart counterpart_indices[i][j], in world frame. None when no counterparts are specified.

force_matrix_friction: wp.array2d[wp.vec3f] | None#

Per-counterpart friction (tangential) contact forces [N], shape (n_sensing, max_counterparts), dtype vec3. Entry [i, j] is the friction force on sensing object i from counterpart counterpart_indices[i][j], in world frame. None when no counterparts are specified.

position_matrix: wp.array2d[wp.vec3f] | None#

Average contact positions [m] per counterpart, shape (n_sensing, max_counterparts), dtype vec3. Entry [i, j] is the average world-frame interaction position between sensing_indices[i] and counterpart_indices[i][j]. It averages the midpoint of all contacts between these objects, weighted by linear contact-force magnitude. Entries are zero when the interaction force is zero or update() receives no body transforms. None when no counterparts are specified.

sensing_indices: list[int]#

Body or shape index per sensing object, matching the row of output arrays. For list[int] inputs the caller’s order is preserved; for string patterns the order follows ascending body/shape index.

sensing_transforms: wp.array[wp.transformf]#

World-frame transforms of sensing objects [m, unitless quaternion], shape (n_sensing,), dtype transform.

sensing_type: Literal['body', 'shape']#

Whether sensing_indices contains body indices ("body") or shape indices ("shape").

solver_observable_kinds = frozenset({'contact_f'})#

Solver observables required by update().

Experimental

The solver observable API may change while additional observables are migrated to it.

total_force: wp.array[wp.vec3f] | None#

Total contact force [N] per sensing object, shape (n_sensing,), dtype vec3. None when measure_total=False.

total_force_friction: wp.array[wp.vec3f] | None#

Total friction (tangential) contact force [N] per sensing object, shape (n_sensing,), dtype vec3. None when measure_total=False.