Week 5: URDF for Humanoid Robots
Introduction to URDFβ
The Unified Robot Description Format (URDF) is an XML-based specification for describing robot kinematics, dynamics, and visual appearance. URDF files define the robot's structure as a tree of links (rigid bodies) connected by joints (movable or fixed connections).
For humanoid robots, URDF enables:
- Simulation: Visualize robot motion in Gazebo or RViz before hardware testing
- Kinematics: Compute forward/inverse kinematics for pose planning
- Collision detection: Prevent self-collisions during complex motions
- Control: Generate controller configurations from joint definitions
URDF Philosophyβ
URDF represents robots as kinematic trees with a single root link (typically base_link). Each joint connects a parent link to a child link, defining the relative motion between them. This tree structure matches the physical reality of most robots: a humanoid has a torso (root) with limbs branching outward.
Joint Typesβ
URDF supports six joint types, each defining different motion constraints:
Revolute Jointsβ
Rotate around a single axis with angle limits (most common for robot joints).
<!-- Shoulder pitch joint: rotates around Y-axis -->
<joint name="left_shoulder_pitch" type="revolute">
<parent link="torso"/>
<child link="left_upper_arm"/>
<!-- Joint origin relative to parent link -->
<origin xyz="0.0 0.15 0.4" rpy="0 0 0"/>
<!-- Axis of rotation in joint frame -->
<axis xyz="0 1 0"/>
<!-- Joint limits (radians) -->
<limit lower="-1.57" upper="1.57" effort="100.0" velocity="2.0"/>
<!-- Joint dynamics (optional) -->
<dynamics damping="0.7" friction="0.5"/>
</joint>
Parameters:
lower/upper: Angle limits in radians (Β±90Β° in this example)effort: Maximum torque (Nm) the actuator can applyvelocity: Maximum angular velocity (rad/s)damping: Simulates energy dissipation in bearingsfriction: Models static/kinetic friction
Prismatic Jointsβ
Translate along a single axis (used for telescoping limbs or linear actuators).
<!-- Telescoping torso joint -->
<joint name="torso_lift" type="prismatic">
<parent link="base_link"/>
<child link="torso"/>
<origin xyz="0 0 0.5" rpy="0 0 0"/>
<!-- Axis of translation -->
<axis xyz="0 0 1"/>
<!-- Linear limits (meters) -->
<limit lower="0.0" upper="0.3" effort="500.0" velocity="0.1"/>
</joint>
Continuous Jointsβ
Revolute joints without angle limits (for wheels or unrestricted rotation).
<!-- Waist rotation (360Β° rotation) -->
<joint name="waist_yaw" type="continuous">
<parent link="pelvis"/>
<child link="torso"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit effort="80.0" velocity="1.5"/>
</joint>
Fixed Jointsβ
Rigidly attach links (no motion). Used for sensors, end-effectors, or structural components.
<!-- Camera mounted on head -->
<joint name="head_camera_joint" type="fixed">
<parent link="head"/>
<child link="camera_link"/>
<origin xyz="0.05 0 0.05" rpy="0 0 0"/>
</joint>
Link Structure: Visual and Collision Geometryβ
Each link defines both visual appearance (for rendering) and collision geometry (for physics simulation).
<link name="left_upper_arm">
<!-- Visual appearance (what you see in RViz/Gazebo) -->
<visual>
<origin xyz="0 0 -0.15" rpy="0 0 0"/>
<geometry>
<!-- Cylinder: radius=0.05m, length=0.3m -->
<cylinder radius="0.05" length="0.3"/>
</geometry>
<material name="blue">
<color rgba="0.0 0.0 0.8 1.0"/>
</material>
</visual>
<!-- Collision geometry (for physics/contact detection) -->
<!-- Often simplified for performance -->
<collision>
<origin xyz="0 0 -0.15" rpy="0 0 0"/>
<geometry>
<!-- Same as visual for simple shapes -->
<cylinder radius="0.05" length="0.3"/>
</geometry>
</collision>
<!-- Inertial properties (for dynamics simulation) -->
<inertial>
<origin xyz="0 0 -0.15" rpy="0 0 0"/>
<mass value="2.5"/> <!-- kg -->
<!-- Inertia tensor for cylinder about center of mass -->
<!-- Computed using: I = m*rΒ²/2 (z-axis), I = m*(3rΒ²+hΒ²)/12 (x,y-axes) -->
<inertia
ixx="0.0208" ixy="0.0" ixz="0.0"
iyy="0.0208" iyz="0.0"
izz="0.003125"/>
</inertial>
</link>
Geometry Optionsβ
<!-- Box (width, depth, height in meters) -->
<geometry>
<box size="0.1 0.2 0.05"/>
</geometry>
<!-- Sphere (radius in meters) -->
<geometry>
<sphere radius="0.08"/>
</geometry>
<!-- Mesh (load from STL/DAE file) -->
<geometry>
<mesh filename="package://humanoid_description/meshes/hand.stl" scale="1.0 1.0 1.0"/>
</geometry>
Xacro: Modular URDFβ
Writing raw URDF for complex robots leads to repetitive code. Xacro (XML Macros) extends URDF with:
- Variables: Define constants (link lengths, masses)
- Macros: Reusable templates for repeated structures (left/right limbs)
- Math: Compute values (inertia tensors, compound transforms)
- File inclusion: Split robot into modular files
Xacro Example: Parameterized Armβ
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="humanoid_arm">
<!-- Define constants -->
<xacro:property name="upper_arm_length" value="0.3"/>
<xacro:property name="forearm_length" value="0.25"/>
<xacro:property name="arm_radius" value="0.05"/>
<xacro:property name="arm_mass" value="2.5"/>
<!-- Macro for arm links (reusable for left/right) -->
<xacro:macro name="arm_links" params="side reflect">
<!-- Upper arm link -->
<link name="${side}_upper_arm">
<visual>
<origin xyz="0 0 ${-upper_arm_length/2}" rpy="0 0 0"/>
<geometry>
<cylinder radius="${arm_radius}" length="${upper_arm_length}"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.2 0.8 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0 0 ${-upper_arm_length/2}" rpy="0 0 0"/>
<geometry>
<cylinder radius="${arm_radius}" length="${upper_arm_length}"/>
</geometry>
</collision>
<xacro:cylinder_inertia
mass="${arm_mass}"
radius="${arm_radius}"
length="${upper_arm_length}"/>
</link>
<!-- Shoulder joint -->
<joint name="${side}_shoulder_pitch" type="revolute">
<parent link="torso"/>
<child link="${side}_upper_arm"/>
<!-- Reflect parameter: 1 for left, -1 for right -->
<origin xyz="0.0 ${reflect * 0.15} 0.4" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.57" upper="1.57" effort="100.0" velocity="2.0"/>
<dynamics damping="0.7"/>
</joint>
<!-- Forearm link -->
<link name="${side}_forearm">
<visual>
<origin xyz="0 0 ${-forearm_length/2}" rpy="0 0 0"/>
<geometry>
<cylinder radius="${arm_radius * 0.8}" length="${forearm_length}"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<origin xyz="0 0 ${-forearm_length/2}" rpy="0 0 0"/>
<geometry>
<cylinder radius="${arm_radius * 0.8}" length="${forearm_length}"/>
</geometry>
</collision>
<xacro:cylinder_inertia
mass="${arm_mass * 0.8}"
radius="${arm_radius * 0.8}"
length="${forearm_length}"/>
</link>
<!-- Elbow joint -->
<joint name="${side}_elbow" type="revolute">
<parent link="${side}_upper_arm"/>
<child link="${side}_forearm"/>
<origin xyz="0 0 ${-upper_arm_length}" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<!-- Elbow only bends one direction -->
<limit lower="0.0" upper="2.356" effort="80.0" velocity="2.0"/>
<dynamics damping="0.5"/>
</joint>
</xacro:macro>
<!-- Macro for computing cylinder inertia -->
<xacro:macro name="cylinder_inertia" params="mass radius length">
<inertial>
<origin xyz="0 0 ${-length/2}" rpy="0 0 0"/>
<mass value="${mass}"/>
<inertia
ixx="${mass * (3*radius*radius + length*length) / 12}"
ixy="0.0" ixz="0.0"
iyy="${mass * (3*radius*radius + length*length) / 12}"
iyz="0.0"
izz="${mass * radius * radius / 2}"/>
</inertial>
</xacro:macro>
<!-- Torso link (root) -->
<link name="torso">
<visual>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
<material name="grey">
<color rgba="0.5 0.5 0.5 1.0"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
</collision>
<inertial>
<mass value="15.0"/>
<inertia ixx="0.4" ixy="0.0" ixz="0.0"
iyy="0.5" iyz="0.0" izz="0.2"/>
</inertial>
</link>
<!-- Instantiate left and right arms -->
<xacro:arm_links side="left" reflect="1"/>
<xacro:arm_links side="right" reflect="-1"/>
</robot>
Processing Xacro Filesβ
# Convert Xacro to URDF
xacro humanoid_arm.urdf.xacro > humanoid_arm.urdf
# View in RViz
ros2 launch urdf_tutorial display.launch.py model:=humanoid_arm.urdf.xacro
# Check for errors
check_urdf humanoid_arm.urdf
Complete Humanoid URDF Exampleβ
See the next chapter (Week 5: Complete URDF Example) for a full working humanoid robot with torso, arms, legs, and handsβready for simulation in Gazebo and control with ros2_control.
Best Practicesβ
- Use Xacro for all but trivial robots: Avoid copy-paste errors and enable easy parameter tuning
- Define inertial properties: Physics simulation requires accurate mass/inertia
- Simplify collision geometry: Use primitive shapes (boxes, cylinders) instead of complex meshes for performance
- Follow naming conventions: Use
<side>_<body_part>_<motion>(e.g.,left_shoulder_pitch) - Set realistic limits: Joint limits should match hardware specifications
- Test incrementally: Build robot link-by-link, testing visualization after each addition
Next Stepsβ
The next chapter covers ros2_control, the framework for connecting URDF models to actual controllers (position, velocity, effort) and hardware interfaces. You'll learn how to make your URDF robot move in simulation and transition to real hardware.
Practice Exerciseβ
Extend the arm Xacro macro to include:
- A wrist rotation joint (continuous type)
- A simple gripper with two fingers (prismatic joints)
- Force/torque sensor link at the wrist (fixed joint)
Visualize in RViz and verify joint limits work as expected.