RobotForge
Published·~18 min

URDF and Xacro: describing a robot to ROS

Every ROS-based robot ships a URDF — the XML file that defines its links, joints, meshes, and inertias. Here's the minimal mental model and the Xacro patterns that keep big URDFs sane.

by RobotForge
#ros2#urdf#xacro#modeling

A URDF (Unified Robot Description Format) is how you tell ROS what your robot looks like. Links, joints, meshes, masses — without it, half the ROS ecosystem (RViz, Gazebo, MoveIt, robot_state_publisher) doesn't work. The good news: it's just XML.

The mental model

A robot is a tree of links connected by joints. Each link is a rigid body (a chassis, an arm segment, a wheel). Each joint defines how one link moves relative to another (revolute, prismatic, fixed, continuous, planar, floating).

base_link left_wheel_joint lidar_joint right_wheel_joint left_wheel lidar_link right_wheel arm_joint (revolute) arm_link
URDF as a tree: links (boxes) connected by joints (edges). Orange = root link.

Minimal URDF

<?xml version="1.0"?>
<robot name="my_robot">
  <link name="base_link">
    <visual>
      <geometry><box size="0.4 0.3 0.1"/></geometry>
      <material name="orange"><color rgba="1 0.5 0 1"/></material>
    </visual>
    <collision>
      <geometry><box size="0.4 0.3 0.1"/></geometry>
    </collision>
    <inertial>
      <mass value="2.0"/>
      <inertia ixx="0.02" iyy="0.04" izz="0.06" ixy="0" ixz="0" iyz="0"/>
    </inertial>
  </link>

  <link name="left_wheel">
    <visual>
      <geometry><cylinder radius="0.06" length="0.02"/></geometry>
    </visual>
  </link>

  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 0.16 0" rpy="1.5708 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>
</robot>

That's the entire URDF format. Three sections per link (visual, collision, inertial — only visual is strictly required), and a joint per connection. RViz can render this immediately; Gazebo can simulate it (with a few more tags); MoveIt can plan with it.

The five joint types you actually use

  • fixed — no relative motion. Sensor mounts, structural attachments.
  • revolute — rotates around an axis with a limit. Most arm joints.
  • continuous — rotates around an axis, no limit. Wheels.
  • prismatic — translates along an axis with a limit. Linear actuators.
  • planar / floating — rare, mostly for free-flying / floating-base robots.

Inertia: the tag everyone gets wrong

The <inertial> tag specifies mass and the 3×3 inertia tensor. Wrong values here mean the robot in Gazebo behaves nothing like the real one. Two rules:

  1. Never leave inertias blank or use placeholder values like 1e-3. Gazebo won't fail loudly; it'll just produce robots that wobble like jello or refuse to move.
  2. Compute them. Use a CAD tool, OnShape's mass-properties, or the cookbook formulas (box: I = m/12 · diag(h² + d², w² + d², w² + h²)).

Why Xacro exists

A real robot URDF gets to 1000+ lines. You need macros, parameters, and includes. Plain XML doesn't have those. Xacro (XML Macros) is a preprocessor that adds them. Files are named robot.xacro; the build system runs xacro robot.xacro > robot.urdf at build time.

Xacro features that matter

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">
  <!-- Parameters at the top -->
  <xacro:property name="wheel_radius" value="0.06"/>
  <xacro:property name="wheel_separation" value="0.32"/>

  <!-- Macro for a wheel link + joint -->
  <xacro:macro name="wheel" params="prefix y_offset">
    <link name="${prefix}_wheel">
      <visual>
        <geometry><cylinder radius="${wheel_radius}" length="0.02"/></geometry>
      </visual>
    </link>
    <joint name="${prefix}_wheel_joint" type="continuous">
      <parent link="base_link"/>
      <child link="${prefix}_wheel"/>
      <origin xyz="0 ${y_offset} 0" rpy="1.5708 0 0"/>
      <axis xyz="0 0 1"/>
    </joint>
  </xacro:macro>

  <!-- Use it -->
  <xacro:wheel prefix="left" y_offset="${wheel_separation/2}"/>
  <xacro:wheel prefix="right" y_offset="${-wheel_separation/2}"/>
</robot>

Two wheels from one macro definition. Multiply by every repeating subassembly on a real robot — torso links, arm joints, leg modules — and Xacro becomes non-optional.

How to load a URDF in ROS 2

Standard pattern in a launch file:

from launch_ros.actions import Node
from launch.substitutions import Command, PathJoinSubstitution
from launch_ros.substitutions import FindPackageShare

robot_description = Command([
    'xacro ',
    PathJoinSubstitution([FindPackageShare('my_robot'), 'urdf', 'robot.xacro'])
])

return LaunchDescription([
    Node(package='robot_state_publisher', executable='robot_state_publisher',
         parameters=[{'robot_description': robot_description}]),
    Node(package='joint_state_publisher_gui', executable='joint_state_publisher_gui'),
])

robot_state_publisher reads the URDF, listens to /joint_states, and publishes the TF tree. Without it, RViz shows nothing. With it, you can see your robot move.

Common URDF mistakes

  • Mesh paths use package:// URIs, not file paths. package://my_robot/meshes/arm.stl, not /home/.../arm.stl.
  • Joint origin is the parent-to-child transform, not the location of the joint axis itself. Place it at the child's frame origin.
  • Inertia tensors must be expressed in the link's COM frame, not its visual frame.
  • Visual and collision geometry don't have to match. A simplified collision (capsule, box) is much faster than the visual mesh.

Visualizing a URDF

ros2 launch urdf_tutorial display.launch.py model:=path/to/your.urdf

This brings up RViz with the URDF, a joint slider GUI, and a TF tree. Best ten-second sanity check that exists.

What URDF doesn't cover (and what does)

  • SDF (Simulation Description Format) — Gazebo's preferred format; richer than URDF (lights, plugins, more sensors). Auto-converts from URDF for most uses.
  • USD (Universal Scene Description) — NVIDIA's format used by Isaac Sim. Conversion tools exist but lossy.
  • MJCF — MuJoCo's format. More control over actuator dynamics. Convert from URDF with mujoco.from_urdf.

For your first robot, write a Xacro that exports URDF. URDF gives you ROS, RViz, MoveIt, Nav2 for free. Add SDF tags later if you specifically need Gazebo features.

Next

ros2_control — the standard hardware-interface layer that turns the URDF + a controller config into actual hardware commands.

Comments

    Sign in to post a comment.