Robot URDF visualizerΒΆ

Requires yourdfpy and robot_descriptions. Any URDF supported by yourdfpy should work.

The viser.extras.ViserUrdf is a lightweight interface between yourdfpy and viser. It can also take a path to a local URDF file as input.

  1from __future__ import annotations
  2
  3import time
  4from typing import Literal
  5
  6import numpy as np
  7import tyro
  8from robot_descriptions.loaders.yourdfpy import load_robot_description
  9
 10import viser
 11from viser.extras import ViserUrdf
 12
 13
 14def create_robot_control_sliders(
 15    server: viser.ViserServer, viser_urdf: ViserUrdf
 16) -> tuple[list[viser.GuiInputHandle[float]], list[float]]:
 17    """Create slider for each joint of the robot. We also update robot model
 18    when slider moves."""
 19    slider_handles: list[viser.GuiInputHandle[float]] = []
 20    initial_config: list[float] = []
 21    for joint_name, (
 22        lower,
 23        upper,
 24    ) in viser_urdf.get_actuated_joint_limits().items():
 25        lower = lower if lower is not None else -np.pi
 26        upper = upper if upper is not None else np.pi
 27        initial_pos = 0.0 if lower < 0 and upper > 0 else (lower + upper) / 2.0
 28        slider = server.gui.add_slider(
 29            label=joint_name,
 30            min=lower,
 31            max=upper,
 32            step=1e-3,
 33            initial_value=initial_pos,
 34        )
 35        slider.on_update(  # When sliders move, we update the URDF configuration.
 36            lambda _: viser_urdf.update_cfg(
 37                np.array([slider.value for slider in slider_handles])
 38            )
 39        )
 40        slider_handles.append(slider)
 41        initial_config.append(initial_pos)
 42    return slider_handles, initial_config
 43
 44
 45def main(
 46    robot_type: Literal[
 47        "panda",
 48        "ur10",
 49        "cassie",
 50        "allegro_hand",
 51        "barrett_hand",
 52        "robotiq_2f85",
 53        "atlas_drc",
 54        "g1",
 55        "h1",
 56        "anymal_c",
 57        "go2",
 58    ] = "panda",
 59) -> None:
 60    # Start viser server.
 61    server = viser.ViserServer()
 62    server.scene.enable_default_lights(cast_shadow=True)
 63
 64    # Load URDF.
 65    #
 66    # This takes either a yourdfpy.URDF object or a path to a .urdf file.
 67    viser_urdf = ViserUrdf(
 68        server,
 69        urdf_or_path=load_robot_description(robot_type + "_description"),
 70    )
 71
 72    # Create sliders in GUI that help us move the robot joints.
 73    with server.gui.add_folder("Joint position control"):
 74        (slider_handles, initial_config) = create_robot_control_sliders(
 75            server, viser_urdf
 76        )
 77
 78    # Set initial robot configuration.
 79    viser_urdf.update_cfg(np.array(initial_config))
 80
 81    # Create grid.
 82    server.scene.add_grid(
 83        "/grid",
 84        width=2,
 85        height=2,
 86        position=(
 87            0.0,
 88            0.0,
 89            # Get the minimum z value of the trimesh scene.
 90            viser_urdf._urdf.scene.bounds[0, 2],
 91        ),
 92    )
 93
 94    # Create joint reset button.
 95    reset_button = server.gui.add_button("Reset")
 96
 97    @reset_button.on_click
 98    def _(_):
 99        for s, init_q in zip(slider_handles, initial_config):
100            s.value = init_q
101
102    # Sleep forever.
103    while True:
104        time.sleep(10.0)
105
106
107if __name__ == "__main__":
108    tyro.cli(main)