Skip to content

Franka plate rack

Mesh and SDF contacts

A Franka lifts a dinner plate and places it in a wire rack beside two resting plates.

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 franka_plate_rack
# change solver settings live
uv run shelf tune franka_plate_rack

Source: examples/shelf/03_mesh_sdf_contacts/franka_plate_rack/ · ported from newton/examples/robot/example_steel_plate_rack.py @ skild-ai/newton 7565a26b1

Presets

PresetSolverStatusChecked

Solvers with no row are untested on this example. The full settings are inpresets.toml.

Code

examples/shelf/03_mesh_sdf_contacts/franka_plate_rack/example.py
# SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers
# SPDX-License-Identifier: Apache-2.0
# Modified for Steel Shelf: preset-driven execution and shared runner integration.
"""Lift a free dinner plate and place it in a wire rack beside two resting plates.
Command: python -m shelf run franka_plate_rack
"""
import numpy as np
import warp as wp
import newton.examples
from common import presets
from newton.examples.robot import _plate_rack
from newton.examples.robot._tabletop_demo import TabletopDemo
class Example(TabletopDemo):
task = "plates"
def __init__(self, viewer, args):
self.preset = presets.load(__file__)
# TabletopDemo (installed newton package) builds SolverSteel from these args, so fill them
# from the preset before handing args over. Its frame rate is fixed at 60 Hz.
args.substeps = self.preset.substeps
args.iterations = self.preset.solver.need("iterations")
args.solve_mode = self.preset.solver.need("solve_mode")
args.contact_response = self.preset.solver.need("articulated_contact_response")
super().__init__(viewer, args)
@staticmethod
def create_parser():
# TabletopDemo.create_parser() without its solver-tuning arguments (--substeps, --iterations,
# --solve-mode, --contact-response); those values now live in presets.toml.
parser = newton.examples.create_parser()
parser.set_defaults(num_frames=900)
return parser
def populate(self, builder, info, ee, down, fingers):
_plate_rack.populate(builder, info, ee, fingers, wp.get_device(), 3)
def test_final(self):
"""Verify a real lift followed by a supported plate placement."""
self.check_capacity()
body = self.info["tracked_body"]
pose = self.state_0.body_q.numpy()[body]
assert self.peak_height > 0.25, f"Plate was not lifted: {self.peak_height}"
center = wp.transform_point(wp.transform(*pose), wp.vec3(*self.info["plate_center_local"]))
assert abs(center[0] - self.info["plate_slot_x"]) < self.info["plate_slot_half_width"], (
f"Plate missed rack slot: center={center}, slot={self.info['plate_slot_x']}"
)
assert abs(pose[1] - self.info["rack_origin"][1]) < 0.04, f"Plate missed rack: {pose}"
assert 0.07 < pose[2] < 0.16, f"Plate is unsupported: {pose}"
normal = np.asarray(wp.quat_rotate(wp.quat(*pose[3:]), wp.vec3(0, 0, 1)))
goal_normal = np.asarray(wp.quat_rotate(wp.quat(*self.info["plate_goal_rotation"]), wp.vec3(0, 0, 1)))
assert abs(np.dot(normal, goal_normal)) > np.cos(np.deg2rad(15)), f"Plate is not upright: {pose}"
speed = np.linalg.norm(self.state_0.body_qd.numpy()[body])
assert speed < 0.25, f"Plate did not settle: {speed}"
if __name__ == "__main__":
viewer, args = newton.examples.init(Example.create_parser())
example = Example(viewer, args)
newton.examples.run(example, args)