Robot anymal d
Articulations
ANYmal D in many worlds on MuJoCo or Kamino.
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 robot_anymal_duv run shelf play robot_anymal_d --solver mujocouv run shelf play robot_anymal_d --solver kamino# change solver settings liveuv run shelf tune robot_anymal_dSource: examples/shelf/07_articulations/robot_anymal_d/ · ported from newton/examples/robot/example_robot_anymal_d.py @ skild-ai/newton 7565a26b1
Presets
| Preset | Solver | Status | Checked |
|---|---|---|---|
steeldefault | steel | works | 2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090 |
mujoco | mujoco | 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 Robot Anymal D## Shows how to simulate Anymal D with multiple worlds using MuJoCo or Kamino.## Command: python -m shelf run robot_anymal_d --world-count 16# python -m shelf run robot_anymal_d --solver kamino############################################################################
import warp as wp
import newtonimport newton.examplesimport newton.utilsfrom newton import JointTargetMode, JointTypefrom common import presets
class Example: def __init__(self, viewer, args): self.preset = presets.load(__file__) newton.use_coord_layout_targets = True 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
self.world_count = args.world_count self.solver_type = self.preset.name
self.viewer = viewer
self.device = wp.get_device()
articulation_builder = newton.ModelBuilder(up_axis=newton.Axis.Z) if self.solver_type == "kamino": newton.solvers.SolverKamino.register_custom_attributes(articulation_builder) else: newton.solvers.SolverMuJoCo.register_custom_attributes(articulation_builder) articulation_builder.default_joint_cfg = newton.ModelBuilder.JointDofConfig( limit_ke=1.0e3, limit_kd=1.0e1, friction=1e-5 ) articulation_builder.default_shape_cfg.ke = 2.0e3 articulation_builder.default_shape_cfg.kd = 1.0e2 articulation_builder.default_shape_cfg.kf = 1.0e3 articulation_builder.default_shape_cfg.mu = 0.75
asset_path = newton.utils.download_asset("anybotics_anymal_d") asset_file = str(asset_path / "usd" / "anymal_d.usda") articulation_builder.add_usd( asset_file, collapse_fixed_joints=False, enable_self_collisions=False, hide_collision_shapes=True, )
articulation_builder.joint_q[:3] = [0.0, 0.0, 0.68] if len(articulation_builder.joint_q) > 6: articulation_builder.joint_q[3:7] = [0.0, 0.0, 0.0, 1.0]
for joint_id, joint_type in enumerate(articulation_builder.joint_type): if joint_type != JointType.REVOLUTE: continue dof_start = articulation_builder.joint_qd_start[joint_id] dof_end = ( articulation_builder.joint_qd_start[joint_id + 1] if joint_id + 1 < articulation_builder.joint_count else articulation_builder.joint_dof_count ) for dof_id in range(dof_start, dof_end): articulation_builder.joint_target_ke[dof_id] = 150 articulation_builder.joint_target_kd[dof_id] = 5 articulation_builder.joint_target_mode[dof_id] = int(JointTargetMode.POSITION)
builder = newton.ModelBuilder(up_axis=newton.Axis.Z) for _ in range(self.world_count): builder.add_world(articulation_builder)
self.root_dof_indices = [] for root_joint_id in builder.articulation_start: root_dof_start = builder.joint_qd_start[root_joint_id] root_dof_end = builder.joint_qd_start[root_joint_id + 1] self.root_dof_indices.extend(range(root_dof_start, root_dof_end))
builder.default_shape_cfg.ke = 1.0e3 builder.default_shape_cfg.kd = 1.0e2 builder.add_ground_plane()
self.model = builder.finalize() self.use_mujoco_contacts = self.solver_type == "mujoco" and args.use_mujoco_contacts self.build_solver()
self.state_0 = self.model.state() self.state_1 = self.model.state() self.control = self.model.control()
# Evaluate forward kinematics for collision detection newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, self.state_0)
# ensure this is called at the end of the Example constructor 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.""" if self.solver_type == "mujoco": self.solver = self.preset.make_solver(self.model, use_mujoco_contacts=self.use_mujoco_contacts) else: self.solver = self.preset.make_solver(self.model) if self.use_mujoco_contacts: self.collision_pipeline = newton.CollisionPipeline( self.model, rigid_contact_max=self.solver.get_max_contact_count(), soft_contact_max=0 ) 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} )
def capture(self): self.graph = None if self.device.is_cuda: with wp.ScopedCapture() as capture: self.simulate() self.graph = capture.graph
# simulate() performs one frame's worth of updates def simulate(self): if not self.use_mujoco_contacts: self.collision_pipeline.collide(self.state_0, self.contacts) for substep in range(self.sim_substeps): self.state_0.clear_forces()
# apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) 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 substep count ends in state_1; copy it back so the captured graph keeps its buffers 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 render(self): self.viewer.begin_frame(self.sim_time) self.viewer.log_state(self.state_0) self.viewer.log_contacts(self.contacts, self.state_0, observables=self.solver_observables) self.viewer.end_frame()
def test_final(self): """Verify the robot settles without actuating its floating base.""" target_mode = self.model.joint_target_mode.numpy() target_ke = self.model.joint_target_ke.numpy() target_kd = self.model.joint_target_kd.numpy() if any( target_mode[dof_id] != int(JointTargetMode.NONE) or target_ke[dof_id] != 0.0 or target_kd[dof_id] != 0.0 for dof_id in self.root_dof_indices ): raise AssertionError("floating-base DOFs must remain unactuated")
newton.examples.test_body_state( self.model, self.state_0, "all bodies are above the ground", lambda q, qd: q[2] > -0.006, ) # Only check velocities on CUDA where we run 500 frames (enough time to settle) # On CPU we only run 10 frames and the robot is still falling (~0.65 m/s) if self.device.is_cuda: # fmt: off newton.examples.test_body_state( self.model, self.state_0, "body velocities are small", lambda q, qd: max(abs(qd)) < 0.25, # Relaxed from 0.1 - collision pipeline has residual velocities up to ~0.2 ) # fmt: on
if self.solver_type == "kamino": body_q = self.state_0.body_q.numpy() base_heights = [body_q[i, 2] for i, label in enumerate(self.model.body_label) if label.endswith("/base")] foot_heights = [body_q[i, 2] for i, label in enumerate(self.model.body_label) if label.endswith("_FOOT")] if not base_heights or min(base_heights) < 0.6: raise AssertionError(f"ANYmal D collapsed: base heights {base_heights}") if not foot_heights or min(foot_heights) < -0.01: raise AssertionError(f"ANYmal D feet sank into the ground: foot heights {foot_heights}")
@staticmethod def create_parser(): parser = newton.examples.create_parser() newton.examples.add_world_count_arg(parser) newton.examples.add_mujoco_contacts_arg(parser) parser.set_defaults(world_count=8) return parser
if __name__ == "__main__": parser = Example.create_parser() viewer, args = newton.examples.init(parser)
newton.examples.run(Example(viewer, args), args)example = "robot_anymal_d"source = "newton/examples/robot/example_robot_anymal_d.py @ skild-ai/newton 7565a26b1"default = "steel"
[check]# Arguments `python -m shelf check` passes in test mode (from Newton's test_examples.py).args = ["--num-frames", "500"]
[gallery]short = "ANYmal D in many worlds on MuJoCo or Kamino."
[xpbd]status = "untested"
[vbd]status = "untested"
[mujoco]status = "works"sim = { fps = 50, substeps = 4 }solver = { cone = "elliptic", impratio = 100, iterations = 100, ls_iterations = 50, nconmax = 45, njmax = 100 }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[featherstone]status = "untested"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 1 substep x 4 iterations, immediate response: fastest setting that settles with lower peak and mean penetration# than MuJoCo (2 iterations sinks 10 mm and fails the test). The propagation responses need 2 substeps x 8# iterations for the same and run 3.6x slower. articulation_constraint_max: the immediate response keeps contact# rows per world in this family; the robot uses up to 43 (32 drops rows). Joint limits off: the standing robot# never reaches them (identical result with them on, 18% slower).sim = { fps = 50, substeps = 1 }solver = { solve_mode = "matrix_free", iterations = 4, articulated_contact_response = "immediate", articulation_constraint_max = 128, enable_joint_limits = false }checked = "2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090"
[kamino]status = "works"sim = { fps = 50, substeps = 4 }# use_schur_complement: eliminate bilateral rows once before the projected inequality sweeps.solver = { dynamics_solver = "dvi", sparse_dynamics = true, sparse_jacobian = true, dvi = { max_alternating_iterations = 32, use_schur_complement = true, bilateral_solver_type = "LLTBRCM" } }# Newton test_examples.py tests the Kamino variant with these arguments.check = { args = ["--num-frames", "500", "--world-count", "1"] }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.