Robot ur10
Articulations
A UR10 arm from USD under a joint-space controller.
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_ur10uv run shelf play robot_ur10 --solver mujocouv run shelf play robot_ur10 --solver kamino# change solver settings liveuv run shelf tune robot_ur10Source: examples/shelf/07_articulations/robot_ur10/ · ported from newton/examples/robot/example_robot_ur10.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 UR10## Shows how to set up a simulation of a UR10 robot arm# from a USD file using newton.ModelBuilder.add_usd() and# applies a sinusoidal trajectory to the joint targets.## Command: python -m shelf run robot_ur10 --world-count 16# python -m shelf run robot_ur10 --solver kamino############################################################################
import numpy as npimport warp as wp
import newtonimport newton.examplesimport newton.utilsfrom newton import JointTargetModefrom newton.selection import ArticulationViewfrom common import presets
@wp.kerneldef update_joint_target_trajectory_kernel( joint_target_trajectory: wp.array3d[wp.float32], time: wp.array[wp.float32], dt: wp.float32, # output joint_target: wp.array3d[wp.float32],): world_idx = wp.tid() t = time[world_idx] t = wp.mod(t + dt, float(joint_target_trajectory.shape[0] - 1)) step = int(t) time[world_idx] = t
num_dofs = joint_target.shape[2] for dof in range(num_dofs): # add world_idx here to make the sequence of dofs different for each world di = (dof + world_idx) % num_dofs joint_target[world_idx, 0, dof] = wp.lerp( joint_target_trajectory[step, world_idx, di], joint_target_trajectory[step + 1, world_idx, di], wp.frac(t), )
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.solver_type = self.preset.name self.world_count = args.world_count if self.world_count is None: self.world_count = 4 if self.solver_type == "kamino" else 100
self.viewer = viewer
self.device = wp.get_device()
ur10 = newton.ModelBuilder() if self.solver_type == "kamino": newton.solvers.SolverKamino.register_custom_attributes(ur10) else: newton.solvers.SolverMuJoCo.register_custom_attributes(ur10)
asset_path = newton.utils.download_asset("universal_robots_ur10") asset_file = str(asset_path / "usd" / "ur10_instanceable.usda") height = 1.2 ur10.add_usd( asset_file, xform=wp.transform(wp.vec3(0.0, 0.0, height)), collapse_fixed_joints=False, enable_self_collisions=False, hide_collision_shapes=True, ) # create a pedestal ur10.add_shape_cylinder(-1, xform=wp.transform(wp.vec3(0, 0, height / 2)), half_height=height / 2, radius=0.08)
for i in range(len(ur10.joint_target_ke)): ur10.joint_target_ke[i] = 500 ur10.joint_target_kd[i] = 50 ur10.joint_target_mode[i] = int(JointTargetMode.POSITION)
builder = newton.ModelBuilder() builder.replicate(ur10, self.world_count, spacing=(2, 2, 0))
# set random joint configurations rng = np.random.default_rng(42) joint_q = rng.uniform(-wp.pi, wp.pi, builder.joint_dof_count) builder.joint_q = joint_q.tolist()
builder.add_ground_plane()
self.model = builder.finalize()
self.state_0 = self.model.state() self.state_1 = self.model.state() self.control = self.model.control() self.contacts = None newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, self.state_0)
self.articulation_view = ArticulationView( self.model, "*ur10*", exclude_joint_types=[newton.JointType.FREE, newton.JointType.DISTANCE] ) assert self.articulation_view.count == self.world_count, ( "Number of worlds must match the number of articulations" ) dof_count = self.articulation_view.joint_dof_count joint_target_trajectory = np.zeros((0, self.world_count, dof_count), dtype=np.float32)
self.control_speed = 50.0
dof_lower = self.articulation_view.get_attribute("joint_limit_lower", self.model)[0, 0].numpy() dof_upper = self.articulation_view.get_attribute("joint_limit_upper", self.model)[0, 0].numpy() joint_q = self.articulation_view.get_attribute("joint_q", self.state_0).numpy().squeeze(axis=1) for i in range(dof_count): # generate sinusoidal control signal for this dof lower = dof_lower[i] upper = dof_upper[i] if not np.isfinite(lower) or abs(lower) > 6.0: # no limits; assume the joint dof is angular lower = -wp.pi upper = wp.pi # first determine the phase shift such that the signal starts at the dof's initial joint_q limit_range = upper - lower normalized = (joint_q[:, i] - lower) / limit_range * 2.0 - 1.0 phase_shift = np.zeros(self.articulation_view.count) mask = abs(normalized) < 1.0 phase_shift[mask] = np.arcsin(normalized[mask])
traj = np.sin(np.linspace(phase_shift, 2 * np.pi + phase_shift, int(limit_range * 50))) traj = traj * (upper - lower) * 0.5 + 0.5 * (upper + lower)
target_trajectory = np.tile(joint_q, (len(traj), 1, 1)) target_trajectory[:, :, i] = traj
joint_target_trajectory = np.concatenate((joint_target_trajectory, target_trajectory), axis=0)
self.joint_target_trajectory = wp.array(joint_target_trajectory, dtype=wp.float32, device=self.device) self.time_step = wp.zeros(self.world_count, dtype=wp.float32, device=self.device)
self.ctrl = self.articulation_view.get_attribute("joint_target_q", self.control)
self.solver = self.preset.make_solver(self.model)
self.viewer.set_model(self.model)
self.capture()
def capture(self): self.graph = None 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 for picking, wind, etc self.viewer.apply_forces(self.state_0)
wp.launch( update_joint_target_trajectory_kernel, dim=self.world_count, inputs=[self.joint_target_trajectory, self.time_step, self.sim_dt * self.control_speed], outputs=[self.ctrl], device=self.device, ) self.articulation_view.set_attribute("joint_target_q", self.control, self.ctrl)
self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt)
# 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 render(self): self.viewer.begin_frame(self.sim_time) self.viewer.log_state(self.state_0) self.viewer.end_frame()
def test_final(self): """Verify the robot joint state remains finite while tracking targets.""" joint_q = self.state_0.joint_q.numpy() if not np.isfinite(joint_q).all(): raise ValueError("UR10 joint positions contain non-finite values")
@staticmethod def create_parser(): parser = newton.examples.create_parser() newton.examples.add_world_count_arg(parser) parser.set_defaults(world_count=None) return parser
if __name__ == "__main__": parser = Example.create_parser() viewer, args = newton.examples.init(parser)
newton.examples.run(Example(viewer, args), args)example = "robot_ur10"source = "newton/examples/robot/example_robot_ur10.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 = "A UR10 arm from USD under a joint-space controller."
[xpbd]status = "untested"
[vbd]status = "untested"
[mujoco]status = "works"sim = { fps = 50, substeps = 10 }solver = { disable_contacts = true }checked = "2026-10-28, newton 7565a26b1, NVIDIA RTX A6000"
[featherstone]status = "untested"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 2 substeps, immediate response: fastest setting; the arms track their sweeping targets as closely as with# MuJoCo's 10 substeps (RMS error 0.102 vs 0.101 rad) and stay within 0.02 rad of MuJoCo's path after the# first-target transient. Contacts are off, so the only rows are joint limits; the targets stay inside the limits,# which never engage (identical result with them on, 22% slower). The substep count must stay even: the# captured graph swaps state_0 and state_1 every substep.sim = { fps = 50, substeps = 2 }solver = { solve_mode = "matrix_free", iterations = 1, articulated_contact_response = "immediate", enable_joint_limits = false }checked = "2026-11-04, newton 7565a26b1, NVIDIA GeForce RTX 3090"
[kamino]status = "works"sim = { fps = 50, substeps = 10 }solver = { dynamics_solver = "dvi", sparse_dynamics = true, sparse_jacobian = true, dvi = { max_alternating_iterations = 8, bilateral_solve_interval = 8 } }# Newton test_examples.py tests the Kamino variant with these arguments.check = { args = ["--num-frames", "100", "--world-count", "2"] }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.