ROS 2 · Robot Modeling

Draw a robot
with URDF

Write links and joints in XML, then see it in RViz
— building one manipulator from scratch

URDF link · joint RViz xacro
testbot.urdf 5 links · 4 joints
<robot name="testbot"> ├─ <material> green · orange ├─ <link> base root ├─ <link> link1 … link4 │ └─ visual · collision · inertial └─ <joint> base_joint └─ link1_link2 · link2_link3 · …
A URDF is just a list of links and a list of joints
To work with a robot in ROS 2 you first have to write down "what this robot looks like" in a form a machine can read. URDF is that standard format. Today we write one manipulator (a robot arm) in XML, put it in RViz, and move its joints by hand. In a 40-minute slot, spend the first half on tag syntax and the second half on running it and on tooling. If the audience is new to ROS, confirm the workspace (~/robot_ws) idea here before moving on.
01

What URDF is

A file that records a robot's shape in a machine-readable form. Several similar formats exist, so we sort out what each is for — and what URDF cannot express.

The goal of this chapter is a shared sense of "what kind of thing URDF is." If time is short, cut the SDF·SRDF comparison (04) to one line and go straight to the limits (05).
definitionUnified Robot Description Format

URDF is a robot spec written in XML

  • An XML file holding the robot's geometry, joints and sensor data
  • Parts are <link>, motion is <joint>
  • Stitch those two together and you have one robot
  • The finished model runs straight in RViz and Gazebo
<?xml version="1.0" ?>
<robot name="testbot">

  <link name="base"/>
  <link name="link1"> ... </link>

  <joint name="base_joint" type="fixed">
    <parent link="base"/>
    <child  link="link1"/>
  </joint>

</robot>
in one line Links say what exists; joints say how it attaches and how it moves.
Point out here that there is no reason to fear the XML. There are barely ten tags, and half of them can be left out and the robot still shows up in RViz. The code on the right is the skeleton we keep extending, so read it aloud once: "there is a robot, there are links, and there are joints connecting the links."
compareURDF · SDF · SRDF

Three lookalike formats, three different tools

formatwhat it describesmain tool
URDFlinks · joints — shape and motionRViz
SDFsimulation data, physics and world includedGazebo
SRDFgroups, path planning, collision checksMoveIt
URDF is always the start gz sdf -p model.urdf > model.sdf gives you SDF, and Setup Assistant generates SRDF.
Don't read the table row by row — give the rule of thumb: "just display it, URDF; run physics, SDF; plan arm motion, SRDF." The key point is that almost nobody hand-writes SDF or SRDF in practice; they are converted from URDF.
limitsnot every robot fits

URDF can only draw trees

Tree structures only

A link has exactly one parent. Closed loops — parallel mechanisms such as a delta robot — cannot be expressed in URDF.

that is why check_urdf prints a tree

Links are assumed rigid

Linked bodies are taken never to bend or stretch. Flexible parts like cables and springs are not supported.

solve those on the Gazebo plugin side

know it up front The spec is as general as it can be, but it cannot describe every robot. Most arms and mobile robots are fine as trees.
The reason to state the limits early is so nobody burns time later on "why won't this work?" If someone in the room works on parallel mechanisms, point them at SDF or MuJoCo. Don't linger here — 30 seconds is enough.
02

What we model — a manipulator

A robot that moves like a human arm is called a manipulator. Before writing XML, we look at how it splits into parts and which of them URDF actually cares about.

This is a vocabulary chapter. If many in the audience have no robotics background, spend a bit more time here; if most do robotics, skip both slides quickly.
structuremanipulator

A manipulator splits into four parts

basewhere the robot is anchored — the floor, or a mobile robot
linkrigid frame joining base, joints and tip
jointrotation · translation — where motion comes from
end effectorthe hand — gripper, suction cup, spray nozzle

Arrows run parent → child. The side nearer the base is always the parent, and links and joints alternate as the arm gets longer.

Explain why the middle two boxes are marked hero — URDF really only describes links and joints, and even the base and the end effector end up expressed as links. So the four parts are a conceptual split that collapses into two kinds of tag in the file.
corelink vs joint

In the end it is links and joints

<link> — what exists

Name · shape · mass (kg) · moment of inertia (kg·m²).

Shapes default to primitives — cylinder, cone, box. Complex bodies come from stl·dae meshes.

<joint> — how it attaches and moves

Name · type · axis of motion · min/max values · effort and velocity.

It always describes the relation between two links. A joint alone means nothing.

the rest is detail Get these two tags right and the robot shows up in RViz. Every other tag is a detail of one of them.
This slide is the map for the whole talk. Nail down in advance that visual·collision·inertial·origin·material all live under link, and axis·limit·parent·child all live under joint — then the audience never loses the thread.
03

Create the package, raise the skeleton

By convention a ROS robot model lives in a robotname_description package. We create testbot_description and put the outermost URDF skeleton inside it.

Hands start moving from here. If you are doing a live demo, switch to the terminal at this chapter. Without a demo, reading the commands out loud is enough.
stepsros2 pkg create

The convention is robotname_description

1
cd ~/robot_ws/src
move into the workspace source folder
2
ros2 pkg create testbot_description --build-type ament_cmake --dependencies urdf
create a package holding only modeling data
3
mkdir urdf && vim urdf/testbot.urdf
make a home for the URDF file
4
search "description" on index.ros.org
open someone else's polished URDF as an example
why this name It is a community convention, so the same rule finds other people's robot packages too.
Stress step 4. Rather than writing URDF from nothing, it is far faster to open a similar robot's description package and edit it. If there is time, actually open ROS Index and show the URDF of a turtlebot or a UR robot.
skeleton<robot>

<robot> is a list of links and a list of joints

<?xml version="1.0" ?>
<robot name="testbot">

  <!-- parts: what exists -->
  <link name="base"/>
  <link name="link1"> ... </link>
  <link name="link2"> ... </link>

  <!-- connections: how they attach and move -->
  <joint name="base_joint"  type="fixed">    ... </joint>
  <joint name="link1_link2" type="revolute"> ... </joint>

</robot>
one root <robot> is declared first as the document root. Links connect only through joints.
Ordering comes up often — mixing links and joints still parses, but listing all links first and then all joints reads better. The real testbot.urdf goes material → four links → four joints as well.
04

<link> — the robot's parts

How to write one link properly. The shape you see, the shape that collides, and the mass and inertia used for physics — what each is, and why they are kept apart.

This is the densest chapter of the talk. Five slides follow, so keep signalling priority — "you don't need this just to see it in RViz" — so the audience doesn't wear out.
partsvisual · collision · inertial

One link wears three faces

  • <visual> — the shape drawn on screen
  • <collision> — the volume used for collision checks
  • <inertial> — mass and inertia tensor, for physics
  • All three carry their own <origin> and <geometry>
<link name="link1">
  <visual>
    <origin xyz="0 0 0.25" rpy="0 0 0"/>
    <geometry><box size="0.1 0.1 0.5"/></geometry>
    <material name="green"/>
  </visual>
  <collision> ... </collision>
  <inertial> ... </inertial>
</link>
the three are independent The shape you see need not match the shape that collides. For RViz alone, <visual> is enough.
"For RViz alone, visual is enough" is a big relief to beginners. inertial matters once you run physics in Gazebo, and it sticks if you add the anecdote that bad values make the robot fly apart as if it exploded.
conceptwhy keep them apart

The body you see and the body that collides differ

<visual> — display volume

box·cylinder·sphere by default. Awkward shapes load from CAD files such as STL or DAE.

the point is looking right

<collision> — interference volume

Same tags, different purpose. It is sometimes made larger than the visual to leave a safety margin.

the point is being fast and safe

practical tip Detailed meshes in <visual>, plain boxes and cylinders in <collision>. It looks good and collision stays cheap.
This tip really does decide simulation performance. Run collision checks against a mesh with tens of thousands of triangles and the simulation stops keeping up with real time. Anyone with Gazebo experience will nod along here.
frames<origin>

Position is xyz (meters), orientation is rpy (radians)

  • xyz — position in 3D space, in meters (m)
  • rpy — roll·pitch·yaw Euler angles, in radians (rad)
  • roll about x, pitch about y, yaw about z
  • Always relative to the parent frame
<!-- lift a 0.5-long box by half in z -->
<origin xyz="0.0 0.0 0.25" rpy="0.0 0.0 0.0"/>
<geometry>
  <box size="0.1 0.1 0.5"/>
</geometry>

<!-- yaw 90 deg = pi/2 = 1.5708 rad -->
<origin xyz="0 0 0" rpy="0 0 1.5708"/>
common mistake Putting degrees straight into rpy. Write 90 and you get 90 radians — about fourteen and a half turns.
Explain why the box is raised by 0.25 — a geometry's origin sits at the center of the shape, so a 0.5-long box has to be lifted by half to start at the ground. Miss this and links render half-buried in the floor. Best of all is showing it live in RViz.
physics<inertial>

The inertia tensor is symmetric — six values suffice

3×3 inertia matrixxyz
xixxixyixz
yixyiyyiyz
zixziyzizz
symmetric, so upper triangle only Since ixy = iyx, the six values ixx · ixy · ixz · iyy · iyz · izz are enough. <mass> in kg, <inertia> in kg·m².
Somebody always asks where the values come from. Answer: Wikipedia's "List of 3D inertia tensors" collects formulas per shape, and CAD tools compute them too. Setting everything to 1.0 like the example and just opening RViz is a common early choice as well.
looks<material>

Color is rgba between 0 and 1

<!-- define once near the top of the robot -->
<material name="green">
  <color rgba="0 0.6 0 1" />
</material>
<material name="orange">
  <color rgba="1.0 0.4 0.0 1.0"/>
</material>

<!-- a link refers to it by name -->
<visual>
  ...
  <material name="green"/>
</visual>
the last number is alpha Red, green, blue and alpha, each 0.0–1.0. Alpha 1.0 means opaque. <texture> can map a png instead.
Point out that the range is 0–1, not 0–255. People used to web colors put in 255 and everything turns white. Giving each link a different color makes it easy to see which link moves how in RViz — that is why the example alternates green and orange.
05

<joint> — what creates motion

A joint connects two links and decides how that connection may move. We look at the six types, plus the axis and limit values that shape the real motion.

This is the peak of URDF. The link chapter was the static story; this one is the dynamic story. If you have an RViz demo, placing it at the end of this chapter is the dramatic choice.
typestype="..."

There are six kinds of joint

typemotionanalogy
fixeddoes not movebase bolted to link1
revoluterotation with angle limitsa fan panning side to side
continuousunlimited continuous rotationa car wheel
prismaticstraight travel along one axisa sliding drawer
floating6-DoF travel + rotationa free-floating body
planartravel + rotation on one planean object gliding on a plane
In practice only four get used: fixed · revolute · continuous · prismatic. Saying that floating and planar are in the standard but rare in real robot URDFs takes memorization pressure off the audience. The revolute vs continuous difference (limits or not) shows up in the quiz too.
wiringparent · child

Joints tie parent to child and build the tree

  • <parent>·<child>the side nearer the base is the parent
  • The tree is built here: base → link1 → link2 → …
  • <origin> — joint position relative to the parent link
  • type decides how this connection moves
<joint name="link1_link2" type="revolute">
  <origin xyz="0.0 0.0 0.5" rpy="0.0 0.0 0.0"/>
  <parent link="link1"/>
  <child  link="link2"/>
  <axis xyz="0 0 1"/>
  <limit lower="-2.617" upper="2.617"
         effort="30.0" velocity="1.571"/>
</joint>
naming rule Put both linked names in the joint name, like link1_link2, and you can read the tree by eye.
Point out why origin z is 0.5 — link1 is 0.5 long, so the next joint sits at its end. In other words a joint's origin says "where on the parent link do I mount this joint." Once that clicks, time lost wandering through URDF coordinates drops sharply.
motionaxis · limit

<axis> and <limit> decide the real motion

  • axis xyz="0 0 1"rotates about z. Pans like a fan's neck
  • axis xyz="0 1 0"rotates about y. Bends up and down like an elbow
  • lower·upper — rotation range, in radians
  • effort — force at the joint (N), velocity — speed (rad/s)
<!-- joint that turns like a neck -->
<axis xyz="0.0 0.0 1.0"/>

<!-- joint that bends like an elbow -->
<axis xyz="0.0 1.0 0.0"/>

<limit lower="-2.617" upper="2.617"
       effort="30.0" velocity="1.571"/>
2.617 rad ≈ 150° Changing values and turning the joint yourself in RViz is the fastest way to understand URDF.
What matters is that the axis follows the robot's reference frame. In the example link1_link2 uses z while link2_link3 and link3_link4 use y, so the first joint swings the whole arm sideways and the other two fold and unfold it. Move them one at a time with the joint_state_publisher GUI sliders in RViz and it lands immediately.
06

Show it in RViz and move the joints

The file alone tells you little. Launch three nodes and the robot appears on screen, with sliders to turn its joints by hand.

Results start here. If you prepared a demo video or screenshots, use them in this chapter. Spend the most time making the node structure (23) land, and move fast through the launch file code (24).
structurenode & topic

The launch file starts three nodes

joint_state_publisher_guiproduces joint values from sliders
robot_state_publisherURDF + joint values → transforms (TF)
rviz2draws the robot in 3D from the TF it receives

Arrows show which way topics flow. The URDF itself is handed to robot_state_publisher up front via the robot_description parameter.

This one picture replaces five slides. The key is that "the side producing joint values" and "the side computing frames from them" are separated. Always add that on a real robot a driver node reading encoders takes the place of the GUI on the left — that is what makes the split make sense.
hands-ontestbot.launch.py

The launch file really does just one thing

def generate_launch_description():
    urdf_file = os.path.join(
        get_package_share_directory('testbot_description'),
        'urdf', 'testbot.urdf')
    with open(urdf_file, 'r') as infp:
        robot_description_file = infp.read()

    robot_state_publisher = Node(
        package='robot_state_publisher', executable='robot_state_publisher',
        parameters=[{'robot_description': robot_description_file}])
    joint_state_publisher_gui = Node(
        package='joint_state_publisher_gui', executable='joint_state_publisher_gui')
    rviz2 = Node(
        package='rviz2', executable='rviz2',
        arguments=['-d', rviz_display_config_file])

    return LaunchDescription([robot_state_publisher, joint_state_publisher_gui, rviz2])
this is the point Read the URDF file and pass it as the robot_description parameter. The rest is boilerplate that starts nodes.
The code is trimmed to fit the slide (the original also carries output='screen', use_sim_time and more). Don't dwell — stress the one line and move on. rviz_display_config_file is the path to a .rviz file storing the RViz layout, used so you don't re-add displays every time.
roleswho passes what to whom

The two publishers do different jobs

nodesubscribespublishes
joint_state_publisher/joint_states
robot_state_publisher/joint_states/tf · /tf_static · /robot_description
rviz2/tf · /robot_description
the flow in one line Joint values come in, get combined with the URDF into transforms, and go back out. RViz only draws the result.
/joint_states is sensor_msgs/msg/JointState and /tf is tf2_msgs/msg/TFMessage. Nobody needs to memorize message types, but mention that ros2 topic echo lets you watch the URDF turn into numbers. /tf_static holds only transforms that never change, such as fixed joints.
stepsinstall → build → launch

Install it, build it, launch it

1
sudo apt install ros-humble-joint-state-publisher-gui ros-humble-robot-state-publisher
install the packages behind the nodes the launch file starts
2
cd ~/robot_ws && colcon build --symlink-install
build the workspace
3
ros2 launch testbot_description testbot.launch.py
RViz and the joint slider GUI come up together
set an alias Shorten it with alias cba='colcon build --symlink-install' and the loop gets much lighter.
--symlink-install matters. With it, edits to urdf or launch files take effect without rebuilding. URDF work means changing values and rechecking constantly, so this one option shortens the loop a lot. Remind people not to forget source install/local_setup.bash after building.
shortcuturdf_tutorial

You can look without a launch file at all

# install the urdf_tutorial package
$ sudo apt install ros-humble-urdf-tutorial

# pass only the model path and RViz opens
$ ros2 launch urdf_tutorial display.launch.py \
    model:=/home/parallels/robot_ws/src/testbot_description/urdf/testbot.urdf
when to use it The early stage, while you are still changing tag values. Lean on it until you have a launch file and a .rviz config.
You get exactly the same screen as with your own launch file. In the end the urdf_tutorial package just contains a launch file like the one we saw. Recommend starting here while learning URDF and writing your own package's launch file once the robot is reasonably complete.
07

How to verify, and the tooling

Commands that check syntax and the tree before RViz ever opens, plus xacro, which cuts repetition once a URDF gets long.

The closing chapter. If time runs out, show check_urdf (29) only and jump to xacro (31). The tool table (30) can be left as reference material.
verify$ check_urdf testbot.urdf

check_urdf — syntax and link tree at once

robot name is: testbot ---------- Successfully Parsed XML --------------- root Link: base has 1 child(ren) child(1): link1 child(1): link2 child(1): link3 child(1): link4
what it catches Misspelled link names, links with no parent, breaks in the tree. Run it before opening RViz.
The staircase indentation in the output is the tree structure. If it differs from what you intended, a joint's parent/child is wrong. RViz sometimes silently draws nothing when a URDF is broken, so running this command first is a good habit for finding problems fast.
toolswhat each shows

Tools to reach for when you are stuck

toolwhat it shows
check_urdfsyntax errors and the link tree
urdf_to_graphizlink · joint relations and relative transforms as a PDF diagram
ros2 run tf2_tools view_framesthe running TF tree as a PDF
rqthow nodes and topics are actually wired
URDF for VSCodesyntax highlighting and snippets for .urdf·.xacro
rviz2the final render — where you really check
JetBrains PyCharm has no usable URDF plugin, so VSCode plus the URDF extension is effectively the standard. Don't count on VSCode's "ROS: Preview URDF" — it works on ROS 1 but sometimes shows a blank screen on ROS 2. urdf_to_graphiz produces both a .gv and a .pdf.
scalingXML Macro

xacro turns repeated XML into macros

  • Short for XML Macro. A macro language that generates URDF
  • <xacro:property> — manage a value by name
  • Expressions like pi·radians(x)·sqrt(x) are available
  • <xacro:macro> — link · joint templates taking arguments
  • <xacro:if> — insert a different block by condition
<xacro:property name="the_radius" value="2.1" />
<geometry type="cylinder" radius="${the_radius}" />

<!-- no need to compute radians by hand -->
<xacro:property name="upper" value="${radians(150)}"/>

<xacro:macro name="link_box" params="suffix">
  <link name="link_${suffix}"> ... </link>
</xacro:macro>

<xacro:link_box suffix="1" />
when to switch Plain URDF is fine up to four or five links. Once you start copying blocks, it is time for xacro.
The testbot we built today repeats nearly identical content four times across link1–link4 — exactly where xacro is needed. Saying that being able to write radians(150) is reason enough to switch gets a lot of nods. Most real robot packages ship .urdf.xacro rather than .urdf.
summarywhat to take away

Only two things to remember

A model is links and joints

<link> for parts, <joint> for connection and motion.

visual·collision·inertial·origin·axis·limit are all details of those two

You check it in RViz

Changing values and relaunching is the fastest way to understand URDF.

but check the tree with check_urdf first

next steps Control the robot you modeled · physics simulation in Gazebo · Navigation.
You can close the talk here, or continue into the quiz. The three next steps come straight from the article's "next study topics," so this is a good place to trail a follow-up talk. Take questions while staying on this slide.
check ①click to reveal

Answer for yourself — formats and links

URDF in RViz (modeling), SDF in Gazebo (simulation), SRDF in MoveIt (planning, collision). All three are XML, and URDF is the starting point. → Slide 04
Closed-loop (parallel) structures. URDF only expresses trees, one parent per link. Flexible parts that bend or stretch are out too. → Slide 05
Because the shape shown and the collision volume may differ. Enlarge collision for a safety margin, or use simple shapes instead of heavy meshes to keep it cheap. → Slide 13 · 14
1.5708 radians about z (yaw), roughly 90°. rpy is in radians and xyz in meters. Put degrees in raw and it faces somewhere else. → Slide 15
The 3×3 inertia matrix is symmetric: ixy=iyx, ixz=izx, iyz=izy. The upper triangle ixx·ixy·ixz·iyy·iyz·izz fixes the rest. → Slide 16
Read one question, wait 5–10 seconds, then open the answer. Q3 and Q5 split the room best. If time is short, Q2 and Q4 alone still cover the core of the earlier slides.
check ②click to reveal

Answer for yourself — joints and running it

Both rotate, but revolute has lower·upper angle limits while continuous spins without any. A car wheel is continuous. → Slide 19
The one nearer the base. That is how the tree base → link1 → link2 → … is built, and check_urdf prints exactly that tree. → Slide 20
About ±150°. limit angles are radians; effort next to it is newtons (N) and velocity is rad/s. → Slide 21
It subscribes to /joint_states, combines it with the URDF into transforms, and publishes /tf·/tf_static. The joint values themselves come from joint_state_publisher. → Slide 23 · 25
Run check_urdf for syntax and tree, then ros2 launch urdf_tutorial display.launch.py model:=... to put it straight into RViz. → Slide 27 · 29
The last slide. Q5 is the habit you will use most often, so make sure to land it before closing. Leaving every answer open turns the screen into a summary while you take questions.
Speaker notes
← Article URDF Robot Modeling
01 / 34

Shortcuts

→ · Space · PgDn
Next slide
← · PgUp
Previous slide
Home · End
First · Last
O
Slide overview
N
Speaker notes
T
Light / dark theme
F
Fullscreen
Esc
Close