Incline
Basic contacts
A box and two stacks rest on a 22° slope without sliding.
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 inclineuv run shelf play incline --solver steel_off# change solver settings liveuv run shelf tune inclineSource: examples/shelf/01_basic_contacts/incline/ · ported from steel-webgpu tools/scenes.py incline() @ ad03e49
Presets
| Preset | Solver | Status | Checked |
|---|---|---|---|
steeldefault | steel | works | 2026-11-10, newton f6fc82a8b, NVIDIA RTX PRO 6000 Blackwell Server Edition |
steel_off | steel variant | works | 2026-11-10, newton f6fc82a8b, NVIDIA RTX PRO 6000 Blackwell Server Edition |
Solvers with no row are untested on this example. The full settings are inpresets.toml.
Code
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors# SPDX-License-Identifier: Apache-2.0# Direct source: steel-webgpu; see presets.toml and THIRD_PARTY.md.# Modified for Steel Shelf: preset-driven execution and shared runner integration.
############################################################################ Example Incline## Friction anchors: boxes and short stacks on a 22 deg incline with mu 0.5.## Ported from steel-webgpu's tools/scenes.py (incline); the scene builds# as it does there and the shelf's preset drives the solver.## Command: python -m shelf run incline############################################################################
import math
import numpy as npimport warp as wp
import newtonimport newton.examplesfrom common.steel_scenes import BLUE, GOLD, GREY, RED, TEAL, SceneExample, cfg, tf
class Example(SceneExample): file = __file__ camera = {'eye': (0.4, -1.9, 1.1), 'target': (0.0, 0.0, 0.4)}
def build(self): """Friction anchors: boxes and short stacks on a 22 deg incline with mu 0.5.""" b = newton.ModelBuilder() b.add_ground_plane() ang = math.radians(22) q = wp.quat_from_axis_angle(wp.vec3(0.0, 1.0, 0.0), -ang) n = np.array(wp.quat_rotate(q, wp.vec3(0.0, 0.0, 1.0))) b.add_shape_box(-1, xform=tf((0, 0, 0.35), q), hx=0.6, hy=0.45, hz=0.02, cfg=cfg(mu=0.5), color=GREY) for y, levels in ((-0.25, 1), (0.0, 2), (0.25, 3)): for k in range(levels): c = np.array((0, y, 0.35)) + n * (0.02 + 0.03 + 0.06 * k + 0.0005 * (k + 1)) body = b.add_body(xform=tf(c, q)) b.add_shape_box(body, hx=0.075, hy=0.075, hz=0.03, cfg=cfg(mu=0.5, density=400), color=[BLUE, TEAL, GOLD, RED][k]) return b, {}, "Friction anchors: a box and stacks of 2 and 3 rest on a 22 deg incline (mu 0.5) without creeping."
def test_post_step(self): self._frame = getattr(self, "_frame", 0) + 1 if self._frame == 60: self._after_1s = self.state_0.body_q.numpy()[:, :3].copy()
def check(self): """Nothing creeps: after the first second every box stays within 1 mm, and within 5 mm of where it was built.""" q = self.state_0.body_q.numpy()[:, :3] creep = np.linalg.norm(q - self._after_1s, axis=1) moved = np.linalg.norm(q - self.model.body_q.numpy()[:, :3], axis=1) assert creep.max() < 0.001, f"box {creep.argmax()} crept {1e3 * creep.max():.1f} mm after the first second" assert moved.max() < 0.005, f"box {moved.argmax()} moved {1e3 * moved.max():.1f} mm"
if __name__ == "__main__": parser = Example.create_parser() viewer, args = newton.examples.init(parser) newton.examples.run(Example(viewer, args), args)example = "incline"source = "steel-webgpu tools/scenes.py incline() @ ad03e49"default = "steel"
[check]# Arguments `python -m shelf check` passes in test mode: 600 frames, 10 s, long enough to see creep; the example's check() asserts the outcome.args = ["--num-frames", "600"]
[gallery]short = "A box and two stacks rest on a 22° slope without sliding."title = "Incline"
[xpbd]status = "untested"
[vbd]status = "untested"
[mujoco]status = "untested"
[featherstone]status = "untested"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 2 substeps x 8 iterations with contact warm start: nothing creeps over 20 s, as at 16 x 50 and 8 x 24. The# browser's 4 x 12 without warm start lets the stacks creep 0.3 to 1 mm/s (the top box 10 mm in 10 s), and so do# 4 x 24, 6 x 16 and 8 x 12. Warm start at 2 x 6 also holds; 2 x 4 slides the top box 41 mm, so 2 x 8 for margin.# contact_gap_gate 0.02: contacts more than 2 cm apart get no rows (141 -> 50 rows), same result, 20% faster.# No joints: limits off.sim = { fps = 60, substeps = 2 }solver = { solve_mode = "matrix_free", iterations = 8, articulated_contact_response = "propagation_colored", contact_regularization = 0.0, contact_warmstart = true, contact_gap_gate = 0.02 }checked = "2026-11-10, newton f6fc82a8b, NVIDIA RTX PRO 6000 Blackwell Server Edition"
[kamino]status = "untested"
[implicit_mpm]status = "untested"
[style3d]status = "untested"
[steel_off]kind = "steel"status = "works"# the browser's "Friction anchors" toggle off: friction_anchor_beta = 0.0, the rest as [steel]. With warm start the# stacks hold without anchors too; without warm start at 2 x 8 they slide (the top box 0.48 m in 20 s).sim = { fps = 60, substeps = 2 }solver = { solve_mode = "matrix_free", iterations = 8, articulated_contact_response = "propagation_colored", contact_regularization = 0.0, contact_warmstart = true, contact_gap_gate = 0.02, friction_anchor_beta = 0.0 }checked = "2026-11-10, newton f6fc82a8b, NVIDIA RTX PRO 6000 Blackwell Server Edition"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.