Joint Wrench Sensor#

A JointWrenchSensor reports the incoming reaction wrench at each selected articulation body’s parent joint. It exposes force [N] and torque [N·m] separately, with entries ordered by body_names.

Wrench convention#

The incoming_joint_frame convention expresses the wrench in the child-side joint frame at the child-side joint anchor. This matches the placement of a six-axis force/torque sensor mounted at the joint. Backend implementations convert their native solver output to this common convention.

PhysX’s get_link_incoming_joint_force() already returns the wrench in the child-side joint frame, referenced at its anchor, so the PhysX sensor exposes those components directly. Applying the USD localPos1 and localRot1 again would shift and rotate the wrench twice.

Configure the sensor#

Set prim_path to the articulation root. Reported body coverage depends on the physics backend:

  • PhysX and OVPhysX report every articulation link, including the root link.

  • Newton reports the child link of each non-free joint in the articulation tree, including fixed connections between bodies such as a welded wrist sensor or tool flange. Free joints, fixed joints to the world, and loop-closing constraints are excluded. The reported wrench at a weld includes the loads transmitted by its child subtree, even though the joint has no degrees of freedom.

Use body_names or find_bodies() instead of assuming that different backends expose the same number or order of entries:

    joint_wrench = JointWrenchSensorCfg(prim_path="{ENV_REGEX_NS}/Robot")

Manager-based environments can select a body subset through SceneEntityCfg and use body_incoming_wrench() as an observation term:

        feet_body_forces = ObsTerm(
            func=mdp.body_incoming_wrench,
            scale=0.1,
            params={"sensor_cfg": SceneEntityCfg("joint_wrench", body_names=FEET_BODY_NAMES)},
        )
        actions = ObsTerm(func=mdp.last_action)

Read the data#

For E environments and B reported bodies, force.torch and torque.torch each have shape (E, B, 3). Both buffers are None before simulation initialization.

joint_wrench = scene["joint_wrench"]
foot_ids, _ = joint_wrench.find_bodies([".*foot"])

force = joint_wrench.data.force.torch[:, foot_ids]
torque = joint_wrench.data.torque.torch[:, foot_ids]
wrench = torch.cat((force, torque), dim=-1)

The composed wrench has shape (E, num_selected_bodies, 6) with force components followed by torque components. B and the entry ordering can differ between backends.