Skip to content

Kinematic bodies and per-body gravity

10 · Contact and friction

A kinematic paddle (is_kinematic=True) turns at 1.2 rad/s and sweeps boxes as if it had infinite mass. Steel holds a kinematic body's joint coordinates as given, so the script writes its pose and velocity every step, then runs eval_fk so the collision pipeline sees the new body pose. The box above it was added with disable_gravity=True and tumbles in place.

0.00 s

What to look at

  • The boxes are pushed outward, and none of them moves the paddle.
  • The floating box keeps its height.

Key parameter

is_kinematic / disable_gravity

Arguments of add_body. Write a kinematic body's joint_q and joint_qd each step, then call newton.eval_fk so body_q and body_qd match before collide.

Try changing

  • Leave out the joint_qd write and compare the push.
  • Make the paddle move along x instead of turning.

Concept

Kinematic bodies

What it is

A kinematic body moves on a prescribed path and is not pushed back by contacts. Steel gives its joint an armature of 1e10, so it has no response, and holds its coordinates as written. The script writes pose and velocity, then runs eval_fk so collision sees the same pose.

In this scene

The paddle sweeps the ring of boxes outward (mean radius 0.32 → 0.40 m) and is not slowed.

Considerations

Accuracy
Contacts use the written velocity; write a consistent velocity or the push is wrong.
Interactions
A kinematic global body may touch every world; a kinematic global articulation with joints may not.
Use it when
Conveyors, robot playback and moving platforms whose motion is known.

The script

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

tutorials/10_kinematic_bodies.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Kinematic bodies and per-body gravity: a prescribed paddle sweeps boxes."""
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
SPIN = 1.2 # rad/s
def build():
builder = newton.ModelBuilder()
builder.add_ground_plane(cfg=newton.ModelBuilder.ShapeConfig(mu=0.5))
paddle = builder.add_body(
xform=wp.transform((0.0, 0.0, 0.05), wp.quat_identity()), is_kinematic=True
)
builder.add_shape_box(paddle, hx=0.42, hy=0.015, hz=0.04)
for k in range(10):
a = 2.0 * math.pi * (k + 0.5) / 10
box = builder.add_body(
xform=wp.transform((0.32 * math.cos(a), 0.32 * math.sin(a), 0.03), wp.quat_identity())
)
builder.add_shape_box(
box,
hx=0.03,
hy=0.03,
hz=0.03,
cfg=newton.ModelBuilder.ShapeConfig(density=300.0, mu=0.5),
)
floater = builder.add_body(
xform=wp.transform((0.0, 0.0, 0.4), wp.quat_identity()), disable_gravity=True
)
builder.add_shape_box(
floater, hx=0.04, hy=0.04, hz=0.04, cfg=newton.ModelBuilder.ShapeConfig(density=300.0)
)
model = builder.finalize()
qd = model.joint_qd.numpy()
qd[-3:] = (0.3, 0.5, 0.0) # the floater tumbles in place
model.joint_qd.assign(qd)
return model
def move_paddle(model, state, t):
"""Prescribe the paddle's pose at time t; Steel holds kinematic joint coordinates as given."""
angle = SPIN * t
q, qd = state.joint_q.numpy(), state.joint_qd.numpy()
q[0:7] = (0.0, 0.0, 0.05, 0.0, 0.0, math.sin(angle / 2), math.cos(angle / 2))
qd[0:6] = (0.0, 0.0, 0.0, 0.0, 0.0, SPIN)
state.joint_q.assign(q)
state.joint_qd.assign(qd)
# collision reads body poses, so bring them up to date with the joint coordinates
newton.eval_fk(model, state.joint_q, state.joint_qd, state)
def simulate(rec, label):
model = build()
solver = SolverSteel(model)
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)
t = 0.0
for _ in range(rec.frames(5.0)):
q = state_0.body_q.numpy()
box_r = np.linalg.norm(q[1:-1, :2], axis=1)
run.frame(state_0, mean_box_radius=box_r.mean(), floater_z=q[-1, 2])
for _ in range(SUBSTEPS):
move_paddle(model, state_0, t + DT)
pipeline.collide(state_0, contacts)
solver.step(state_0, state_1, control, contacts, DT)
state_0, state_1 = state_1, state_0
t += DT
run.check(solver)
if __name__ == "__main__":
rec = Recorder()
simulate(rec, "kinematic paddle at 1.2 rad/s")
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