Balance bird
Basic contacts
A procedural toy bird balances on a narrow pedestal.
steel preset (the default), recordedon an NVIDIA RTX PRO 6000 Blackwell.Run it
cd examples# the default preset (steel), then the othersuv run shelf play balance_birduv run shelf play balance_bird --solver xpbduv run shelf play balance_bird --solver vbduv run shelf play balance_bird --solver mujocouv run shelf play balance_bird --solver featherstoneuv run shelf play balance_bird --solver kamino# change solver settings liveuv run shelf tune balance_birdSource: examples/shelf/01_basic_contacts/balance_bird/ · ported from newton/examples/contacts/example_balance_bird.py @ skild-ai/newton 7565a26b1
Presets
| Preset | Solver | Status | Checked |
|---|---|---|---|
steeldefault | steel | works | 2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090 |
xpbd | xpbd | works | 2026-10-28, newton 7565a26b1, NVIDIA RTX A6000 |
vbd | vbd | works | 2026-10-28, newton 7565a26b1, NVIDIA RTX A6000 |
mujoco | mujoco | works | 2026-10-28, newton 7565a26b1, NVIDIA RTX A6000 |
featherstone | featherstone | unstable | 2026-10-28, newton 7565a26b1, NVIDIA RTX A6000 |
kamino | kamino | works | 2026-10-28, newton 7565a26b1, NVIDIA RTX A6000 |
Solvers with no row are untested on this example. The full settings are inpresets.toml.
Code
# SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers# SPDX-License-Identifier: Apache-2.0# Modified for Steel Shelf: preset-driven execution and shared runner integration.
############################################################################ Example Balance Bird## Balances a small procedural toy body on a narrow pedestal. The body is# built from primitive shapes so the example has no external mesh asset.## Command: python -m shelf run balance_bird############################################################################
from __future__ import annotations
import warp as wp
import newtonimport newton.examplesfrom common import presets
PEDESTAL_BOTTOM_RADIUS = 0.08PEDESTAL_TOP_RADIUS = 0.006PEDESTAL_HEIGHT = 0.12PEDESTAL_SEGMENTS = 32TIP_RADIUS = 0.008GRAVITY = -9.81
NATIVE_CONTACT_SOLVERS = {"mujoco", "kamino"}
class Example: def __init__(self, viewer, args): self.preset = presets.load(__file__) self.viewer = viewer self.solver_name = self.preset.name
self.fps = self.preset.fps self.frame_dt = 1.0 / self.fps self.sim_time = 0.0 self.sim_substeps = self.preset.substeps self.sim_dt = self.frame_dt / self.sim_substeps
builder = newton.ModelBuilder(gravity=(0.0, 0.0, GRAVITY)) builder.rigid_gap = 0.001 builder.default_shape_cfg.ke = 1.0e5 builder.default_shape_cfg.kd = 0.0 builder.default_shape_cfg.kf = 1.0e3 builder.default_shape_cfg.mu = 0.8 builder.default_shape_cfg.restitution = 0.0 builder.add_ground_plane(cfg=builder.default_shape_cfg.copy())
pedestal_mesh = newton.Mesh.create_cylinder( PEDESTAL_BOTTOM_RADIUS, PEDESTAL_HEIGHT / 2.0, up_axis=newton.Axis.Z, segments=PEDESTAL_SEGMENTS, top_radius=PEDESTAL_TOP_RADIUS, compute_inertia=False, ) builder.add_shape_mesh( -1, xform=wp.transform(p=wp.vec3(0.0, 0.0, PEDESTAL_HEIGHT / 2.0), q=wp.quat_identity()), mesh=pedestal_mesh, cfg=builder.default_shape_cfg.copy(), color=wp.vec3(0.55, 0.48, 0.32), )
body = builder.add_body( xform=wp.transform(p=wp.vec3(0.0, 0.0, PEDESTAL_HEIGHT + TIP_RADIUS + 0.001), q=wp.quat_identity()), label="balance_bird", )
light_cfg = builder.default_shape_cfg.copy() light_cfg.density = 350.0 heavy_cfg = builder.default_shape_cfg.copy() heavy_cfg.density = 1800.0
# The two lower weights place the center of mass below the contact tip. builder.add_shape_sphere(body, radius=TIP_RADIUS, cfg=light_cfg, color=wp.vec3(0.9, 0.35, 0.1)) builder.add_shape_box( body, xform=wp.transform(p=wp.vec3(0.05, 0.0, 0.025), q=wp.quat_identity()), hx=0.065, hy=0.022, hz=0.018, cfg=light_cfg, color=wp.vec3(0.25, 0.45, 0.85), ) builder.add_shape_box( body, xform=wp.transform(p=wp.vec3(0.02, 0.0, 0.02), q=wp.quat_identity()), hx=0.018, hy=0.19, hz=0.01, cfg=light_cfg, color=wp.vec3(0.15, 0.65, 0.8), ) builder.add_shape_box( body, xform=wp.transform(p=wp.vec3(0.12, 0.0, 0.035), q=wp.quat_identity()), hx=0.04, hy=0.055, hz=0.012, cfg=light_cfg, color=wp.vec3(0.8, 0.35, 0.25), ) builder.add_shape_sphere( body, xform=wp.transform(p=wp.vec3(-0.055, 0.145, -0.075), q=wp.quat_identity()), radius=0.025, cfg=heavy_cfg, color=wp.vec3(0.1, 0.2, 0.7), ) builder.add_shape_sphere( body, xform=wp.transform(p=wp.vec3(-0.055, -0.145, -0.075), q=wp.quat_identity()), radius=0.025, cfg=heavy_cfg, color=wp.vec3(0.1, 0.2, 0.7), )
builder.color() self.model = builder.finalize()
if self.solver_name in NATIVE_CONTACT_SOLVERS: self.collision_pipeline = None self.contacts = None else: self.collision_pipeline = newton.CollisionPipeline(self.model) self.contacts = self.collision_pipeline.contacts()
self.solver = self.preset.make_solver(self.model) self.state_0 = self.model.state() self.state_1 = self.model.state() self.control = self.model.control()
self.viewer.set_model(self.model) self.viewer.set_camera(pos=wp.vec3(-0.42, -0.42, 0.28), pitch=-18.9, yaw=43.7)
self.capture()
def capture(self): if wp.get_device().is_cuda: with wp.ScopedCapture() as capture: self.simulate() self.graph = capture.graph else: self.graph = None
def simulate(self): for substep in range(self.sim_substeps): self.state_0.clear_forces() self.viewer.apply_forces(self.state_0) if self.solver_name in NATIVE_CONTACT_SOLVERS: contacts = None else: self.collision_pipeline.collide(self.state_0, self.contacts) contacts = self.contacts self.solver.step(self.state_0, self.state_1, self.control, contacts, self.sim_dt) if self.sim_substeps % 2 == 1 and substep == self.sim_substeps - 1: # an odd count under graph capture: copy the last result into state_0 instead of swapping, # so every replay of the captured graph starts from the newest state self.state_0.assign(self.state_1) else: self.state_0, self.state_1 = self.state_1, self.state_0
def step(self): if self.graph: wp.capture_launch(self.graph) else: self.simulate() self.sim_time += self.frame_dt
def test_final(self): newton.examples.test_body_state( self.model, self.state_0, "balance bird remains upright on the pedestal", lambda q, qd: ( abs(q[0]) < 0.05 and abs(q[1]) < 0.05 and 0.1 < q[2] < 0.2 and wp.quat_rotate(wp.transform_get_rotation(q), wp.vec3(0.0, 0.0, 1.0))[2] > 0.8 ), )
def render(self): self.viewer.begin_frame(self.sim_time) self.viewer.log_state(self.state_0) if self.solver_name not in NATIVE_CONTACT_SOLVERS: self.viewer.log_contacts(self.contacts, self.state_0) self.viewer.end_frame()
@staticmethod def create_parser(): parser = newton.examples.create_parser() return parser
if __name__ == "__main__": parser = Example.create_parser() viewer, args = newton.examples.init(parser) newton.examples.run(Example(viewer, args), args)example = "balance_bird"source = "newton/examples/contacts/example_balance_bird.py @ skild-ai/newton 7565a26b1"default = "steel"
[check]# Arguments `python -m shelf check` passes in test mode (from Newton's test_examples.py).args = []
[gallery]short = "A procedural toy bird balances on a narrow pedestal."
[xpbd]status = "works"sim = { fps = 60, substeps = 10 }solver = { iterations = 10 }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[vbd]status = "works"sim = { fps = 60, substeps = 10 }solver = { iterations = 10 }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[mujoco]status = "works"sim = { fps = 60, substeps = 10 }solver = { njmax = 2048, nconmax = 1024, cone = "elliptic" }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[featherstone]status = "unstable"sim = { fps = 60, substeps = 50 }solver = { angular_damping = 0.0 }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"note = "check failed: ValueError: Test \"balance bird remains upright on the pedestal\" failed for the following bodies: [balance_bird]"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 2 substeps x 4 iterations, immediate response: the fastest setting that rocks the bird like XPBD# (lowest up-axis cosine 0.969 vs 0.972, 2 mm drift) without penetration. One substep freezes it on# the tip; more iterations change nothing; the colored response costs 45% more on one free body.sim = { fps = 60, substeps = 2 }solver = { solve_mode = "matrix_free", iterations = 4, articulated_contact_response = "immediate" }checked = "2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090"
[kamino]status = "works"sim = { fps = 60, substeps = 5 }solver = { use_collision_detector = true }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[implicit_mpm]status = "untested"
[style3d]status = "untested"Steel docs and tutorials are licensed under Apache-2.0. Third-party notices. "Skild", "Skild AI" and the Skild logo are trademarks of Skild AI and are not licensed under Apache-2.0.