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, log details. If False, suppress details. If None, log 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. contacts must be the buffer that produced contact_f (observables.contacts). 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 contact_f was produced with 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.