Rerun features a built-in importer for URDF files.
Overview
Using a URDF in Rerun only requires you to load the file with the logging API.
This will automatically invoke the importer, which will take care of:
- resolving paths to meshes
- loading meshes and shapes as Rerun entities
- loading the joint transforms and associated frame IDs of links
Once that is done, the joints can be updated by sending Transform3Ds, where you have to set the parent_frame and child_frame fields explicitly to each joint's specific frame IDs.
Meshes and package:// paths
Mesh paths in the URDF are resolved like this:
- Relative paths are resolved relative to the folder of the
.urdffile. package://my_package/meshes/arm.stlis resolved by looking formy_packagein each folder ofROS_PACKAGE_PATH(ROS 1), then in<prefix>/share/my_packagefor each prefix inAMENT_PREFIX_PATH(ROS 2). If neither variable is set, or the package is not found, the mesh is skipped and a warning is logged. Source your ROS workspace (or set one of these variables) before you start the Viewer or your logging script.file://,http://, andhttps://URIs are also supported.
Mesh files can be in any format that Asset3D supports, including .stl, .obj, .glb/.gltf, and .dae (COLLADA).
The scale attribute of a <mesh> is applied.
Material textures are not supported yet; material colors are.
Entity paths and collision geometries
The importer logs the geometry of the robot under an entity named after the robot:
<robot_name>/visual_geometries/…for the visual geometries.<robot_name>/collision_geometries/<geometry_type>/<link_name>/…for the collision geometries.
Collision geometries are visible by default.
To hide them, hide the collision_geometries subtree in the blueprint, for example with rrb.EntityBehavior(visible=False) as an override on <robot_name>/collision_geometries, or click the eye icon next to it in the blueprint panel.
Example
Here is an example that demonstrates how to load and update a URDF with the Python SDK:
from pathlib import Path
import rerun as rr
from rerun import RecordingStream
with RecordingStream("rerun_example_load_urdf") as rec:
rec.spawn()
# `log_file_from_path` automatically uses the built-in URDF importer.
urdf_path = Path(__file__).parent / "minimal.urdf"
rec.log_file_from_path(urdf_path, static=True)
# The `flush` call is optional, but it helps with logging consistency,
# because it ensures that the URDF finishes loading before continuing.
rec.flush()
# Later, in your logging code, you'll update the joints using transforms.
# A minimal example for updating a revolute joint that connects two links:
joint_axis = [0, 0, 1] # comes from URDF
joint_angle = 1.216 # radians
origin_xyz = [0, 0, 0.1] # comes from URDF
# Make sure that `parent_frame` and `child_frame` match the joint's
# frame IDs in the URDF file.
rec.log(
"transforms",
rr.Transform3D(
rotation=rr.RotationAxisAngle(axis=joint_axis, angle=joint_angle),
translation=origin_xyz,
parent_frame="base_link",
child_frame="arm_link",
),
)
For a full animation example, see the Python animated URDF example. There's also a Rust example.
URDF utilities (Python)
Rerun provides the rr.urdf Python module that can facilitate the handling of URDF models in your code.
It can be used as an alternative to other 3rd-party packages like yourdfpy or pytransforms3d.
As shown below, you can use it e.g. to access individual joints of the URDF model and to compute their respective transforms based on joint states (e.g. angles for revolute joints).
These transforms can be directly sent to Rerun.
UrdfTree
Load a URDF file and access its structure:
urdf_tree = rr.urdf.UrdfTree.from_file_path("robot.urdf", entity_path_prefix=None)
# Access properties
robot_name = urdf_tree.name
root_link = urdf_tree.root_link()
joints = urdf_tree.joints()
# Lookup by name
urdf_tree.get_joint_by_name("shoulder")
urdf_tree.get_link_by_name("base_link")
# Get the entity paths of collision or visual geometries of a link.
# This can be used for example to update the color / transparency during runtime:
for visual_path in urdf_tree.get_visual_geometry_paths("gripper"):
rec.log(visual_path, rr.Asset3D.from_fields(albedo_factor=[255, 0, 0, 100]), static=True)Frame prefix
When loading the same URDF multiple times (e.g. a dual-arm setup), use frame_prefix to give each instance unique frame IDs and entity_path_prefix to separate their geometry in the entity tree. Use log_urdf_to_recording() to log the model with prefixed frame IDs:
left = rr.urdf.UrdfTree.from_file_path("robot.urdf", entity_path_prefix="left", frame_prefix="left/")
right = rr.urdf.UrdfTree.from_file_path("robot.urdf", entity_path_prefix="right", frame_prefix="right/")
left.log_urdf_to_recording()
right.log_urdf_to_recording()Transforms computed via joint.compute_transform() will automatically use the prefixed frame IDs (e.g. "left/base", "right/shoulder").
UrdfJoint
Each joint exposes properties from the URDF file:
namejoint_type(e.g.revolute,continuous,prismatic,fixed)parent_link,child_linkaxis,origin_xyz,origin_rpylimit_lower,limit_upper,limit_effort,limit_velocity
Use compute_transform() to get a Transform3D with the correct parent_frame and child_frame already set:
# For revolute/continuous joints: pass angle in radians
# For prismatic joints: pass distance in meters
transform = joint.compute_transform(angle)
rec.log("transforms", transform)By default, values outside the joint limits are clamped, with a warning.
Pass clamp=False to use the value as is: joint.compute_transform(angle, clamp=False).
To send many joint values at once, use compute_transform_columns() with send_columns:
angles = [0.0, 0.1, 0.2, 0.3]
rec.send_columns(
"transforms",
indexes=[rr.TimeColumn("step", sequence=range(len(angles)))],
columns=joint.compute_transform_columns(angles),
)Mimic joints
Mimic joints are not resolved automatically.
joint.mimic returns the <mimic> specification (joint, multiplier, and offset), or None.
Compute the value of the mimic joint from its driver joint yourself, then call compute_transform() with it:
if (mimic := joint.mimic) is not None:
value = mimic.multiplier * driver_value + mimic.offset
rec.log("transforms", joint.compute_transform(value))Streaming the model as chunks
UrdfTree.stream() returns the chunks of the model as an experimental rerun.chunk.LazyChunkStream, for use with the Chunk Processing API.
Pass include_joint_transforms=False to leave out the static joint transforms.
Load URDF into an existing recording
If you already have a recording with transforms loaded in Rerun and want to add an URDF to it, you can do so via drag-and-drop or the menu ("Import into current recording").
In this video, we load an ROS 2 .mcap file with TF messages that automatically get translated into Rerun Transform3D.
As indicated by the errors displayed in the viewer, there are some connections missing in the transform tree of this example MCAP.
In our case, these missing transforms are static links that are stored in URDF models separate from the MCAP file.
To add them, we can simply drag the corresponding URDF files into the viewer where we have loaded the MCAP: