# Configuring Joint Limits and PD Control for Joints in Newton: A Complete Guide

> Learn to configure joint limits and PD control in Newton. Master limit_lower, limit_upper, limit_ke, limit_kd, and JointTargetMode for precise joint control.

- Repository: [Newton Physics/newton](https://github.com/newton-physics/newton)
- Tags: how-to-guide
- Published: 2026-03-19

---

**Use `JointDofConfig` to set per-degree-of-freedom limits with `limit_lower`/`limit_upper` and soft-limit stiffness `limit_ke`/`limit_kd`, while PD control gains `target_ke`/`target_kd` drive the joint toward desired positions and velocities via the `JointTargetMode` actuator system.**

Configuring joint limits and PD control for joints in Newton requires understanding the `JointDofConfig` dataclass in the `newton-physics/newton` repository. This configuration system allows you to define per-DoF motion bounds and proportional-derivative controllers that the VBD and XPBD solvers enforce during simulation.

## Understanding Joint Configuration in Newton

Newton’s physics simulation uses a builder pattern to construct models. The `ModelBuilder` class collects per-degree-of-freedom (DoF) configurations through the `JointDofConfig` dataclass before finalizing them into Warp arrays on the `Model` object.

### The JointDofConfig Dataclass

Located in [`newton/_src/sim/builder.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/sim/builder.py) (lines 380–460), `JointDofConfig` encapsulates every tunable parameter for a single joint axis:

```python
class JointDofConfig:
    def __init__(
        self,
        axis: AxisType | Vec3 = Axis.X,
        limit_lower: float = -MAXVAL,
        limit_upper: float = MAXVAL,
        limit_ke: float = 1e4,
        limit_kd: float = 1e1,
        target_pos: float = 0.0,
        target_vel: float = 0.0,
        target_ke: float = 0.0,
        target_kd: float = 0.0,
        armature: float = 0.0,
        effort_limit: float = 1e6,
        velocity_limit: float = 1e6,
        friction: float = 0.0,
        actuator_mode: JointTargetMode | None = None,
    ):
        ...

```

### Joint Limits vs. PD Control

Newton distinguishes between **limit constraints** and **PD drives**:

- **Limits** (`limit_lower`, `limit_upper`) define the admissible range of motion. The **soft-limit** parameters (`limit_ke`, `limit_kd`) add stiffness and damping that activate only when the joint exceeds its bounds.
- **PD Control** (`target_pos`, `target_vel`, `target_ke`, `target_kd`) defines a continuous drive that pushes the joint toward a desired state using the proportional-derivative law:

\[
\tau = \text{target\_ke} \cdot (\text{target\_pos} - q) + \text{target\_kd} \cdot (\text{target\_vel} - \dot{q})
\]

## Configuring Joint Limits and Soft Constraints

To enforce physical boundaries on joint motion, set the limit bounds and compliance parameters in `JointDofConfig`.

### Setting Hard and Soft Limits

```python
import newton
from newton import Axis

builder = newton.ModelBuilder()

# Configure a revolute joint with ±90 degree limits and soft compliance

dof_config = newton.ModelBuilder.JointDofConfig(
    axis=Axis.Z,
    limit_lower=-0.5,      # -0.5 radians (~ -28.6 degrees)

    limit_upper=0.5,       # +0.5 radians

    limit_ke=1e4,          # Soft-limit stiffness (N·m/rad)

    limit_kd=1e2,          # Soft-limit damping (N·m·s/rad)

)

```

The builder stores these values in internal lists (`self.joint_limit_lower`, `self.joint_limit_ke`, etc.) and copies them to Warp arrays during `finalize()` (around line 9995 in [`newton/_src/sim/builder.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/sim/builder.py)).

### Making Limits Rigid

To create effectively hard limits, increase the stiffness by several orders of magnitude:

```python
dof_config.limit_ke = 1e8  # Near-rigid limit stiffness

dof_config.limit_kd = 1e4  # High damping to prevent oscillation

```

## Implementing PD Control for Joint Actuation

PD controllers generate torques that track desired trajectories. Newton exposes this through the `target_*` fields of `JointDofConfig`.

### Basic PD Drive Configuration

```python

# Extend the previous config with PD control

dof_config.target_pos = 0.0        # Desired angle (radians)

dof_config.target_vel = 0.0        # Desired angular velocity

dof_config.target_ke = 200.0       # Proportional gain (Nm/rad)

dof_config.target_kd = 20.0        # Derivative gain (Nm·s/rad)

```

The solver computes the actuation torque each sub-step using the standard PD law. In [`newton/_src/solvers/vbd/rigid_vbd_kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/vbd/rigid_vbd_kernels.py) (lines 2843–2849), the VBD solver applies joint limits before evaluating the PD drive. The XPBD solver in [`newton/_src/solvers/xpbd/kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/xpbd/kernels.py) (lines 1155–1158) implements the same torque calculation.

## Understanding Actuator Modes with JointTargetMode

The `JointTargetMode` enum determines which control signals the physics engine exposes to the user API. Defined in [`newton/_src/sim/enums.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/sim/enums.py) (lines 46–76), it provides five distinct modes:

```python
class JointTargetMode(IntEnum):
    NONE = 0                 # No actuator installed

    POSITION = 1             # Position actuator (joint_target_pos)

    VELOCITY = 2             # Velocity actuator (joint_target_vel)

    POSITION_VELOCITY = 3    # Both position and velocity control

    EFFORT = 4               # Drive present but no gains → direct torque control

```

### Automatic Mode Inference

If you omit `actuator_mode` in `JointDofConfig`, Newton infers it via `JointTargetMode.from_gains(target_ke, target_kd, force_position_velocity, has_drive)`:

- **No drive** (`has_drive=False`) → `NONE`
- **Zero gains** (`target_ke=0`, `target_kd=0`) → `EFFORT` (direct torque)
- **Only position gain** (`target_ke>0`) → `POSITION`
- **Only velocity gain** (`target_kd>0`) → `VELOCITY`
- **Both gains** or `force_position_velocity=True` → `POSITION_VELOCITY`

This logic is used by the URDF and MJCF importers in [`newton/_src/utils/import_urdf.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/import_urdf.py) and [`newton/_src/utils/import_mjcf.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/import_mjcf.py) to map XML attributes to Newton's internal representation.

## Practical Implementation Examples

### Revolute Joint with Limits and PD Drive

```python
import newton
from newton import Axis

builder = newton.ModelBuilder()

# Create static parent and dynamic child

parent = builder.add_body(mass=0.0)  # World/ground

child = builder.add_body(mass=1.0, com=(0, 0, 0))

# Configure the joint DoF

joint_cfg = newton.ModelBuilder.JointDofConfig(
    axis=Axis.Z,
    limit_lower=-0.5,           # -28.6 degrees

    limit_upper=0.5,            # +28.6 degrees

    limit_ke=1e4,               # Soft limit stiffness

    limit_kd=1e2,               # Soft limit damping

    target_pos=0.0,             # Target angle

    target_vel=0.0,             # Target velocity

    target_ke=200.0,            # PD proportional gain

    target_kd=20.0,             # PD derivative gain

)

# Add the joint

builder.add_joint_revolute(
    parent=parent,
    child=child,
    angular_axes=[joint_cfg]
)

model = builder.finalize()

```

### Setting Global Default Gains

Instead of configuring every joint individually, modify `builder.default_joint_cfg` (initialized at line 775 in [`newton/_src/sim/builder.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/sim/builder.py)):

```python
builder = newton.ModelBuilder()

# Override defaults for all subsequent joints

builder.default_joint_cfg.target_ke = 150.0
builder.default_joint_cfg.target_kd = 15.0
builder.default_joint_cfg.limit_ke = 5e3
builder.default_joint_cfg.limit_kd = 50.0

# Now add joints without specifying gains explicitly

builder.add_joint_revolute(
    parent=p,
    child=c,
    angular_axes=[newton.ModelBuilder.JointDofConfig(axis=Axis.Z)]
)

```

### Importing URDF with Automatic Configuration

The URDF importer in [`newton/_src/utils/import_urdf.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/import_urdf.py) automatically maps XML tags to `JointDofConfig` fields:

```python
builder = newton.ModelBuilder()
builder.add_urdf(
    "robots/ur5e.urdf",
    force_position_velocity_actuation=True
)

```

The importer parses `<limit lower="..." upper="..."/>` into `limit_lower`/`limit_upper` and `<dynamics stiffness="..." damping="..."/>` into `target_ke`/`target_kd`. It calls `JointTargetMode.from_gains` (similar to lines 202-207 in the USD importer) to set the actuator mode.

## Solver Integration and Performance

Newton’s **VBD** and **XPBD** solvers read joint configuration directly from the `Model` object’s Warp arrays.

In [`newton/_src/solvers/vbd/rigid_vbd_kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/vbd/rigid_vbd_kernels.py) (lines 2843–2849), the VBD solver applies joint limits using `joint_limit_ke` and `joint_limit_kd` before evaluating the PD drive. The XPBD solver in [`newton/_src/solvers/xpbd/kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/xpbd/kernels.py) (lines 1155–1158) computes actuation torque using `joint_target_ke` and `joint_target_kd` according to the standard PD law.

Both solvers treat limit parameters as **soft constraints** that activate only at boundary violations, while PD parameters provide **continuous actuation** forces when the corresponding `JointTargetMode` is active.

## Advanced Configuration Techniques

- **Hard limits (zero compliance)**: Set `limit_ke=1e8` and `limit_kd=1e4` to minimize penetration and oscillation, creating effectively rigid stops as enforced in [`newton/_src/solvers/vbd/rigid_vbd_kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/vbd/rigid_vbd_kernels.py).

- **Disable actuation**: Set `target_ke=0`, `target_kd=0`, and `actuator_mode=JointTargetMode.NONE`, or rely on `JointTargetMode.from_gains` inference with zero gains to prevent the solver from applying drive forces.

- **Direct torque control**: Set `actuator_mode=JointTargetMode.EFFORT` with zero PD gains, then write raw torques directly to `Control.joint_f` during simulation steps.

- **Joint friction**: Use the `friction` field in `JointDofConfig`; the VBD solver incorporates this when computing joint forces in [`newton/_src/solvers/vbd/rigid_vbd_kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/vbd/rigid_vbd_kernels.py).

- **Per-world limit overrides**: After building the base model, use `model.joint_limit_lower.assign(new_vals)` inside world loops to vary limits across different simulation environments, as demonstrated in [`newton/tests/test_mujoco_solver.py`](https://github.com/newton-physics/newton/blob/main/newton/tests/test_mujoco_solver.py).

## Summary

- **Joint limits** are defined by `limit_lower` and `limit_upper` in `JointDofConfig`, with soft-limit compliance controlled by `limit_ke` (stiffness) and `limit_kd` (damping).
- **PD control** uses `target_pos`, `target_vel`, `target_ke`, and `target_kd` to compute actuation torques via the proportional-derivative law in the VBD and XPBD solvers.
- **Actuator modes** (`JointTargetMode`) determine which control signals are exposed; Newton infers the mode automatically from gains using `from_gains` if not specified explicitly.
- All configuration data flows from `JointDofConfig` through the `ModelBuilder` into Warp arrays (`joint_limit_*`, `joint_target_*`) that the physics kernels read directly.

## Frequently Asked Questions

### How do I configure hard joint limits with zero compliance in Newton?

Set the soft-limit stiffness `limit_ke` to a very large value (e.g., `1e8`) and the damping `limit_kd` to a proportionally high value (e.g., `1e4`). This minimizes penetration at the limit and prevents oscillation, effectively creating a hard stop as enforced in [`newton/_src/solvers/vbd/rigid_vbd_kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/vbd/rigid_vbd_kernels.py).

### How do I completely disable actuation on a specific joint?

Set both PD gains to zero (`target_ke=0.0`, `target_kd=0.0`) and explicitly set `actuator_mode=JointTargetMode.NONE` in the `JointDofConfig`. Alternatively, if both gains are zero and no drive is specified, `JointTargetMode.from_gains` automatically infers `NONE` mode, preventing the solver from applying any drive forces.

### How do I switch a joint from position control to direct torque control?

Set `actuator_mode=JointTargetMode.EFFORT` and ensure both `target_ke` and `target_kd` are zero. This configuration disables the PD drive and exposes the `joint_f` array in the `Control` object, allowing you to write raw torques directly during simulation steps.

### How do I add friction to a joint in Newton?

Use the `friction` field in `JointDofConfig`. The VBD solver incorporates this friction term when computing joint forces in [`newton/_src/solvers/vbd/rigid_vbd_kernels.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/solvers/vbd/rigid_vbd_kernels.py), applying a resistive torque proportional to the friction coefficient and joint velocity.