Skip to content

Robot anymal d

Articulations

ANYmal D in many worlds on MuJoCo or Kamino.

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 robot_anymal_d
uv run shelf play robot_anymal_d --solver mujoco
uv run shelf play robot_anymal_d --solver kamino
# change solver settings live
uv run shelf tune robot_anymal_d

Source: examples/shelf/07_articulations/robot_anymal_d/ · ported from newton/examples/robot/example_robot_anymal_d.py @ skild-ai/newton 7565a26b1

Presets

PresetSolverStatusChecked
mujocomujocoworks2026-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/07_articulations/robot_anymal_d/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 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 newton
import newton.examples
import newton.utils
from newton import JointTargetMode, JointType
from 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)