Basic urdf
Basics
A quadruped articulation loaded from URDF.
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_urdfuv run shelf play basic_urdf --solver xpbduv run shelf play basic_urdf --solver vbduv run shelf play basic_urdf --solver kamino# change solver settings liveuv run shelf tune basic_urdfSource: examples/shelf/00_basics/basic_urdf/ · ported from newton/examples/basic/example_basic_urdf.py @ skild-ai/newton 7565a26b1
Presets
| Preset | Solver | Status | Checked |
|---|---|---|---|
steeldefault | steel | works | 2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090 |
xpbd | xpbd | unstable | 2026-10-28, newton 7565a26b1, NVIDIA RTX A6000 |
vbd | vbd | 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) 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 npimport warp as wp
import newtonimport newton.examplesfrom 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)example = "basic_urdf"source = "newton/examples/basic/example_basic_urdf.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 quadruped articulation loaded from URDF."
[xpbd]status = "unstable"sim = { fps = 100, substeps = 10 }model = { default_joint_cfg = { target_ke = 2000.0, target_kd = 1.0 }, default_shape_cfg = { mu = 1.0 } }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"note = "check failed: ValueError: Test \"quadruped links are not moving too fast\" failed for the following bodies: [quadruped/base, quadruped/LF_HAA, quadruped/LF_THIGH, quadruped/LF_SHANK, quadruped/RF_HAA, quadruped/RF_THIGH, quadruped/RF_SHANK, quadruped/LH_HAA, quadruped/LH_THIGH, quadruped/LH_SHANK, quadruped/RH_HAA,"
[vbd]status = "unstable"sim = { fps = 100, substeps = 10 }solver = { iterations = 2 }model = { default_joint_cfg = { target_ke = 2.0e3, target_kd = 1.0e1 }, default_shape_cfg = { ke = 1.0e5, kd = 1.0e4, mu = 1.0 } }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"note = "check failed: ValueError: Test \"quadruped links are not moving too fast\" failed for the following bodies: [quadruped/base, quadruped/LF_HAA, quadruped/LF_THIGH, quadruped/LF_SHANK, quadruped/RF_HAA, quadruped/RF_THIGH, quadruped/RF_SHANK, quadruped/LH_HAA, quadruped/LH_THIGH, quadruped/LH_SHANK, quadruped/RH_HAA,"
[mujoco]status = "untested"
[featherstone]status = "untested"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 1 substep x 4 iterations, immediate response: fastest setting with peak and mean penetration below XPBD's at 10# substeps (0.77/0.13 mm vs 3.19/0.18 mm); 3 iterations raise the mean to XPBD's. The four feet of each quadruped# overflow the default 32 articulation rows per world (every world flags constraint_overflow), so 128. The URDF has no# finite joint limits, so enable_joint_limits stays off.sim = { fps = 100, substeps = 1 }model = { default_joint_cfg = { target_ke = 2000.0, target_kd = 1.0 }, default_shape_cfg = { mu = 1.0 } }solver = { solve_mode = "matrix_free", iterations = 4, articulated_contact_response = "immediate", articulation_constraint_max = 128 }checked = "2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090"
[kamino]status = "works"sim = { fps = 100, substeps = 5 }solver = { dynamics_solver = "dvi", sparse_dynamics = false, sparse_jacobian = true, use_collision_detector = true, integrator = "moreau", dvi = { bilateral_solver_type = "LLTBRCM", max_alternating_iterations = 8, use_schur_complement = true } }model = { default_joint_cfg = { target_ke = 2000.0, target_kd = 1.0 }, default_shape_cfg = { mu = 1.0 } }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.