Skip to content

Basic shapes

Basics

Every collision shape the ModelBuilder offers, dropped together.

0.00 s
The steel preset (the default), recordedon an NVIDIA RTX PRO 6000 Blackwell.

Run it

Terminal window
cd examples
# the default preset (steel), then the others
uv run shelf play basic_shapes
uv run shelf play basic_shapes --solver xpbd
uv run shelf play basic_shapes --solver vbd
uv run shelf play basic_shapes --solver kamino
# change solver settings live
uv run shelf tune basic_shapes

Source: examples/shelf/00_basics/basic_shapes/ · ported from newton/examples/basic/example_basic_shapes.py @ skild-ai/newton 7565a26b1

Presets

PresetSolverStatusChecked
xpbdxpbdworks2026-10-28, newton 7565a26b1, NVIDIA RTX A6000
vbdvbdworks2026-10-28, newton 7565a26b1, NVIDIA RTX A6000
kaminokaminoworks2026-10-28, newton 7565a26b1, NVIDIA RTX A6000

Solvers with no row are untested on this example. The full settings are inpresets.toml.

Code

examples/shelf/00_basics/basic_shapes/example.py
# 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 wp
from pxr import Usd
import newton
import newton.examples
from common import presets
import 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)