Skip to content

Basic urdf

Basics

A quadruped articulation loaded from URDF.

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_urdf
uv run shelf play basic_urdf --solver xpbd
uv run shelf play basic_urdf --solver vbd
uv run shelf play basic_urdf --solver kamino
# change solver settings live
uv run shelf tune basic_urdf

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

Presets

PresetSolverStatusChecked
xpbdxpbdunstable2026-10-28, newton 7565a26b1, NVIDIA RTX A6000
vbdvbdunstable2026-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_urdf/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 URDF
#
# Shows how to set up a simulation of a rigid-body quadruped articulation
# from a URDF using the newton.ModelBuilder().
# Note this example does not include a trained policy.
#
# Users can pick bodies by right-clicking and dragging with the mouse.
#
# Command: python -m shelf run basic_urdf
# Kamino DVI: python -m shelf run basic_urdf --solver kamino
#
###########################################################################
import numpy as np
import warp as wp
import newton
import newton.examples
from common import presets
class Example:
def __init__(self, viewer, args):
self.preset = presets.load(__file__)
# setup simulation parameters first
self.fps = self.preset.fps
self.frame_dt = 1.0 / self.fps
self.sim_time = 0.0
self.solver_type = self.preset.name
self.sim_substeps = self.preset.substeps
self.sim_dt = self.frame_dt / self.sim_substeps
self.world_count = args.world_count
self.viewer = viewer
quadruped = newton.ModelBuilder()
if self.solver_type == "kamino":
newton.solvers.SolverKamino.register_custom_attributes(quadruped)
# set default parameters for the quadruped
quadruped.default_joint_cfg.armature = 0.01
# joint drive gains, contact stiffness and friction
self.preset.apply_model(quadruped)
# parse the URDF file
quadruped.add_urdf(
newton.examples.get_asset("quadruped.urdf"),
xform=wp.transform(wp.vec3(0.0, 0.0, 0.7), wp.quat_identity()),
floating=True,
enable_self_collisions=False,
ignore_inertial_definitions=True, # Use geometry-based inertia for stability
joint_damping_mode="drive",
)
# apply additional inertia to the bodies for better stability
body_armature = 0.01
for body in range(quadruped.body_count):
inertia_np = np.asarray(quadruped.body_inertia[body], dtype=np.float32).reshape(3, 3)
inertia_np += np.eye(3, dtype=np.float32) * body_armature
inertia = wp.mat33(inertia_np)
quadruped.body_inertia[body] = inertia
# set initial joint positions
quadruped.joint_q[-12:] = [0.2, 0.4, -0.6, -0.2, -0.4, 0.6, -0.2, 0.4, -0.6, 0.2, -0.4, 0.6]
quadruped.joint_target_q[-12:] = quadruped.joint_q[-12:]
# use "scene" for the entire set of worlds
scene = newton.ModelBuilder()
# use the builder.replicate() function to create N copies of the world
scene.replicate(quadruped, self.world_count)
scene.add_ground_plane(cfg=quadruped.default_shape_cfg)
if self.solver_type == "vbd":
scene.color()
self.model = scene.finalize()
newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, self.model)
self.update_step_interval = 1
self.build_solver()
self.state_0 = self.model.state()
self.state_1 = self.model.state()
self.control = self.model.control()
self.contact_state = newton.State()
self.contact_state.body_q = wp.clone(self.state_0.body_q)
self.contact_state.body_qd = wp.clone(self.state_0.body_qd)
self.viewer.set_model(self.model)
# put graph capture into it's own function
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)
self.collision_pipeline = newton.CollisionPipeline(self.model)
self.contacts = self.collision_pipeline.contacts()
self.solver_observables = None
if self.solver_type == "kamino":
self.solver_observables = self.solver.observables(
contacts=self.contacts, kinds={self.solver.ObservableKind.CONTACT_F}
)
def capture(self):
if (wp.get_device().is_cuda or self.solver_type != "kamino") and not wp.config.verify_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()
# apply forces to the model
self.viewer.apply_forces(self.state_0)
# Collision detection and contact refresh cadence.
refresh_contacts = (substep % self.update_step_interval) == 0
if refresh_contacts and self.solver_type != "kamino":
self.collision_pipeline.collide(self.state_0, self.contacts)
if self.solver_type == "vbd":
self.solver.set_rigid_history_update(refresh_contacts)
last_substep = substep == self.sim_substeps - 1
if last_substep:
wp.copy(self.contact_state.body_q, self.state_0.body_q)
wp.copy(self.contact_state.body_qd, self.state_0.body_qd)
self.solver.step(
self.state_0,
self.state_1,
self.control,
self.contacts,
self.sim_dt,
observables=self.solver_observables if last_substep else None,
)
if self.sim_substeps % 2 == 1 and substep == self.sim_substeps - 1:
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,
"quadruped links are not moving too fast",
lambda q, qd: max(abs(qd)) < 0.15,
)
bodies_per_world = self.model.body_count // self.world_count
newton.examples.test_body_state(
self.model,
self.state_0,
"quadrupeds have reached the terminal height",
lambda q, qd: wp.abs(q[2] - 0.46) < 0.01,
# only select the root body of each world
indices=[i * bodies_per_world for i in range(self.world_count)],
)
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.contact_state, observables=self.solver_observables)
self.viewer.end_frame()
@staticmethod
def create_parser():
parser = newton.examples.create_parser()
newton.examples.add_world_count_arg(parser)
parser.set_defaults(world_count=100)
return parser
if __name__ == "__main__":
parser = Example.create_parser()
viewer, args = newton.examples.init(parser)
newton.examples.run(Example(viewer, args), args)