Skip to content

Load-dependent joint friction

35 · Joints and drives

Three pendulums with tip densities of 500, 2000 and 8000 kg/m³ and the same 0.05 N·m joint friction. Constant friction stops a light pendulum sooner than a heavy one. steel:joint_friction_load_coefficient adds c·(|F| + |T|) of the load the joint carries, the joint friction law of older PhysX articulations, so friction grows with the weight and all three stop alike.

0.00 s

What to look at

  • Constant friction: the heaviest pendulum is still swinging at 5 s.
  • With c = 0.02: all three settle near the bottom.

Key parameter

steel:joint_friction_load_coefficient

Per DOF, with enable_joint_friction. Experimental.

Try changing

  • Use c = 0.1.
  • Set the base friction to zero and keep c.

Concept

Load-dependent joint friction

What it is

steel:joint_friction_load_coefficient adds c(|F| + |T|) to a DOF's friction, where F and T are the force and torque the joint carries, the older PhysX articulation law. Friction then scales with the load, like Coulomb friction in a bearing.

In this scene

With constant friction the heaviest pendulum still swings at 5 s; with c = 0.02 all three settle.

Considerations

Accuracy
The law sums N and N·m, as PhysX did.
Interactions
Needs enable_joint_friction; a model built with all coefficients zero must keep them zero.
Use it when
Matching PhysX assets that relied on load friction.

The script

cd tutorials && python 35_load_joint_friction.py prints each variant's final metric; --record out.json also writes the replay. Every tutorial imports replay.py from the same folder.

tutorials/35_load_joint_friction.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Load-dependent joint friction with steel:joint_friction_load_coefficient."""
import math
import newton
import numpy as np
import warp as wp
from newton.solvers import SolverSteel
from replay import Recorder
DT = 1.0 / 240.0
SUBSTEPS = 8
TIP_DENSITY = [500.0, 2000.0, 8000.0] # three tip masses on identical hinges
def tf(x, y, z):
return wp.transform((x, y, z), wp.quat_identity())
def build(coefficient):
builder = newton.ModelBuilder()
SolverSteel.register_custom_attributes(builder)
for i, density in enumerate(TIP_DENSITY):
y = 0.45 * i - 0.45
link = builder.add_link(xform=tf(0.0, y, 0.8))
builder.add_shape_capsule(
link,
xform=tf(0.0, 0.0, -0.2),
radius=0.02,
half_height=0.18,
cfg=newton.ModelBuilder.ShapeConfig(density=400.0),
)
builder.add_shape_sphere(
link,
xform=tf(0.0, 0.0, -0.4),
radius=0.05,
cfg=newton.ModelBuilder.ShapeConfig(density=density),
)
j = builder.add_joint_revolute(
-1, link, axis=newton.Axis.Y, parent_xform=tf(0.0, y, 0.8), friction=0.05
)
builder.add_articulation([j])
model = builder.finalize()
# friction bound = joint_friction + c * (|F| + |T|) of the load the joint carries
model.steel.joint_friction_load_coefficient.fill_(coefficient)
model.joint_q.fill_(math.radians(50.0))
return model
def simulate(rec, label, coefficient):
model = build(coefficient)
solver = SolverSteel(model, enable_joint_friction=True)
pipeline = newton.CollisionPipeline(model)
contacts = pipeline.contacts()
state_0, state_1, control = model.state(), model.state(), model.control()
newton.eval_fk(model, model.joint_q, model.joint_qd, state_0)
run = rec.variant(label, model)
for _ in range(rec.frames(5.0)):
q = np.degrees(state_0.joint_q.numpy())
run.frame(state_0, **{f"tip_{d:.0f}": q[i] for i, d in enumerate(TIP_DENSITY)})
for _ in range(SUBSTEPS):
pipeline.collide(state_0, contacts)
solver.step(state_0, state_1, control, contacts, DT)
state_0, state_1 = state_1, state_0
run.check(solver)
if __name__ == "__main__":
rec = Recorder()
simulate(rec, "constant friction 0.05 N m", 0.0)
simulate(rec, "+ load coefficient 0.02", 0.02)
rec.save()

Recorded with SolverSteel on Newton 1.9.0.dev0, Warp 1.19.0, NVIDIA RTX PRO 6000 Blackwell Server Edition. Replays read the state back every frame, so they are not a speed benchmark. Script · page text