Basic shapes
Basics
Every collision shape the ModelBuilder offers, dropped together.
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 basic_shapesuv run shelf play basic_shapes --solver xpbduv run shelf play basic_shapes --solver vbduv run shelf play basic_shapes --solver kamino# change solver settings liveuv run shelf tune basic_shapesSource: examples/shelf/00_basics/basic_shapes/ · ported from newton/examples/basic/example_basic_shapes.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 |
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) 2025 The Newton Developers# SPDX-License-Identifier: Apache-2.0# Modified for Steel Shelf: preset-driven execution and shared runner integration.
############################################################################ Example Basic Shapes## Shows how to programmatically create a variety of# collision shapes using the newton.ModelBuilder() API.# Supports XPBD (default), VBD, and Kamino DVI solvers.## Command: python -m shelf run basic_shapes# With VBD: python -m shelf run basic_shapes --solver vbd# With Kamino DVI: python -m shelf run basic_shapes --solver kamino#############################################################################
import warp as wpfrom pxr import Usd
import newtonimport newton.examplesfrom common import presetsimport newton.usd
class Example: def __init__(self, viewer, args): self.preset = presets.load(__file__) self.viewer = viewer self.solver_type = self.preset.name
# setup simulation parameters first 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() if self.solver_type == "kamino": newton.solvers.SolverKamino.register_custom_attributes(builder)
builder.default_shape_cfg.mu = 1.0 # Friction coefficient
# VBD: contact ke/kd; other solvers: torsional and rolling friction self.preset.apply_model(builder)
# add ground plane builder.add_ground_plane()
# z height to drop shapes from drop_z = 2.0
# SPHERE self.sphere_pos = wp.vec3(0.0, -2.0, drop_z) body_sphere = builder.add_body(xform=wp.transform(p=self.sphere_pos, q=wp.quat_identity()), label="sphere") builder.add_shape_sphere(body_sphere, radius=0.5)
# ELLIPSOID (flat disk shape: a=b > c for stability when resting on ground) self.ellipsoid_pos = wp.vec3(0.0, -6.0, drop_z) body_ellipsoid = builder.add_body( xform=wp.transform(p=self.ellipsoid_pos, q=wp.quat_identity()), label="ellipsoid" ) builder.add_shape_ellipsoid(body_ellipsoid, rx=0.5, ry=0.5, rz=0.25)
# CAPSULE self.capsule_pos = wp.vec3(0.0, 0.0, drop_z) body_capsule = builder.add_body(xform=wp.transform(p=self.capsule_pos, q=wp.quat_identity()), label="capsule") builder.add_shape_capsule(body_capsule, radius=0.3, half_height=0.7)
# CYLINDER self.cylinder_pos = wp.vec3(0.0, -4.0, drop_z) body_cylinder = builder.add_body( xform=wp.transform(p=self.cylinder_pos, q=wp.quat_identity()), label="cylinder" ) builder.add_shape_cylinder(body_cylinder, radius=0.4, half_height=0.6)
# BOX self.box_pos = wp.vec3(0.0, 2.0, drop_z) body_box = builder.add_body(xform=wp.transform(p=self.box_pos, q=wp.quat_identity()), label="box") builder.add_shape_box(body_box, hx=0.5, hy=0.35, hz=0.25)
# MESH (bunny) usd_stage = Usd.Stage.Open(newton.examples.get_asset("bunny.usd")) demo_mesh = newton.usd.get_mesh(usd_stage.GetPrimAtPath("/root/bunny"))
self.mesh_pos = wp.vec3(0.0, 4.0, drop_z - 0.5) body_mesh = builder.add_body(xform=wp.transform(p=self.mesh_pos, q=wp.quat(0.5, 0.5, 0.5, 0.5)), label="mesh") builder.add_shape_mesh(body_mesh, mesh=demo_mesh)
# CONE (no collision support in the standard collision pipeline) self.cone_pos = wp.vec3(0.0, 6.0, drop_z) body_cone = builder.add_body(xform=wp.transform(p=self.cone_pos, q=wp.quat_identity()), label="cone") builder.add_shape_cone(body_cone, radius=0.45, half_height=0.6)
# Color rigid bodies for VBD solver if self.solver_type == "vbd": builder.color()
# finalize model self.model = builder.finalize()
# Create solver based on type self.build_solver()
self.state_0 = self.model.state() self.state_1 = self.model.state() self.control = self.model.control()
self.viewer.set_model(self.model)
# Set camera to view all the shapes self.viewer.set_camera( pos=wp.vec3(10.0, -1.3, 2.0), pitch=0.0, yaw=-180.0, ) if hasattr(self.viewer, "camera"): self.viewer.camera.fov = 70.0
self.capture()
def build_solver(self): """Build the solver and everything bound to its instance; the tuner calls this again to swap it.""" self.solver = self.preset.make_solver(self.model) if self.solver_type == "kamino": self.collision_interval = self.preset.collision.need("interval") self.collision_pipeline = newton.CollisionPipeline( self.model, speculative_contact_gap_max=self.preset.collision.need("speculative_contact_gap_max"), ) self.contacts = self.collision_pipeline.contacts() else: self.collision_pipeline = newton.CollisionPipeline(self.model) self.contacts = self.collision_pipeline.contacts() self.solver_observables = ( self.solver.observables(contacts=self.contacts, kinds={self.solver.ObservableKind.CONTACT_F}) if self.solver_type == "kamino" else None )
def capture(self): with wp.ScopedCapture() as capture: self.simulate() self.graph = capture.graph
def simulate(self): for substep in range(self.sim_substeps): self.state_0.clear_forces()
# apply forces to the model self.viewer.apply_forces(self.state_0)
if self.solver_type == "kamino": if substep % self.collision_interval == 0: self.collision_pipeline.collide( self.state_0, self.contacts, dt=self.collision_interval * self.sim_dt, ) else: self.collision_pipeline.collide(self.state_0, self.contacts) self.solver.step( self.state_0, self.state_1, self.control, self.contacts, self.sim_dt, observables=self.solver_observables if substep == self.sim_substeps - 1 else None, )
# swap states 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): self.sphere_pos[2] = 0.5 sphere_q = wp.transform(self.sphere_pos, wp.quat_identity()) newton.examples.test_body_state( self.model, self.state_0, "sphere at rest pose", lambda q, qd: newton.math.vec_allclose(q, sphere_q, atol=2e-4), [0], ) # Ellipsoid with a=b=0.5, c=0.25 is stable (flat disk), rests at z=0.25 self.ellipsoid_pos[2] = 0.25 ellipsoid_q = wp.transform(self.ellipsoid_pos, wp.quat_identity()) newton.examples.test_body_state( self.model, self.state_0, "ellipsoid at rest pose", lambda q, qd: newton.math.vec_allclose(q, ellipsoid_q, atol=2e-2), [1], ) self.capsule_pos[2] = 1.0 capsule_q = wp.transform(self.capsule_pos, wp.quat_identity()) newton.examples.test_body_state( self.model, self.state_0, "capsule at rest pose", lambda q, qd: newton.math.vec_allclose(q, capsule_q, atol=2e-4), [2], ) # Custom test for cylinder: allow 0.01 error for X and Y, strict for Z and rotation self.cylinder_pos[2] = 0.6 cylinder_q = wp.transform(self.cylinder_pos, wp.quat_identity()) # fmt: off newton.examples.test_body_state( self.model, self.state_0, "cylinder at rest pose", lambda q, qd: abs(q[0] - cylinder_q[0]) < 0.01 and abs(q[1] - cylinder_q[1]) < 0.01 and abs(q[2] - cylinder_q[2]) < 1e-4 and abs(q[3] - cylinder_q[3]) < 1e-4 and abs(q[4] - cylinder_q[4]) < 1e-4 and abs(q[5] - cylinder_q[5]) < 1e-4 and abs(q[6] - cylinder_q[6]) < 1e-4, [3], ) # fmt: on self.box_pos[2] = 0.25 box_q = wp.transform(self.box_pos, wp.quat_identity()) newton.examples.test_body_state( self.model, self.state_0, "box at rest pose", lambda q, qd: newton.math.vec_allclose(q, box_q, atol=0.1), [4], ) # we only test that the bunny didn't fall through the ground and didn't slide too far # Allow slight penetration (z > -0.05) due to contact reduction newton.examples.test_body_state( self.model, self.state_0, "bunny at rest pose", lambda q, qd: q[2] > -0.05 and abs(q[0]) < 0.1 and abs(q[1] - 4.0) < 0.1, [5], ) if self.solver_type == "kamino": if int(self.contacts.rigid_contact_count.numpy()[0]) == 0: raise ValueError("Kamino did not export contacts for visualization")
def render(self): self.viewer.begin_frame(self.sim_time) self.viewer.log_state(self.state_0) if self.contacts is not None: self.viewer.log_contacts(self.contacts, self.state_1, observables=self.solver_observables) self.viewer.end_frame()
if __name__ == "__main__": viewer, args = newton.examples.init()
newton.examples.run(Example(viewer, args), args)example = "basic_shapes"source = "newton/examples/basic/example_basic_shapes.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 = "Every collision shape the ModelBuilder offers, dropped together."
[xpbd]status = "works"sim = { fps = 100, substeps = 5 }solver = { iterations = 5 }model = { default_shape_cfg = { mu_torsional = 0.01, mu_rolling = 3e-3 } }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[vbd]status = "works"sim = { fps = 100, substeps = 5 }solver = { iterations = 5 }model = { default_shape_cfg = { ke = 1.0e8, kd = 5.0e5 } }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[mujoco]status = "untested"
[featherstone]status = "untested"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 2 substeps x 4 iterations, immediate response: fastest setting with peak and mean penetration below XPBD's# (0.78/0.019 mm vs 4.80/0.024 mm); 3 iterations raise the mean to 0.031 mm, 2 to 0.060 mm. Odd substep counts# freeze this example's graph.sim = { fps = 100, 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 = 100, substeps = 6 }collision = { interval = 2, speculative_contact_gap_max = 0.1 }solver = { dynamics_solver = "dvi", sparse_dynamics = true, sparse_jacobian = true, dynamics = { cull_speculative_contacts = false }, dvi = { max_alternating_iterations = 2 } }model = { default_shape_cfg = { mu_torsional = 0.01, mu_rolling = 3e-3 } }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.