URDF for Humanoids
What is URDF?
URDF (Unified Robot Description Format) is an XML-based language for describing robot kinematics, dynamics, and visual/collision geometry. It's the standard for:
- Visualization: Rendering robot models in RViz2
- Simulation: Spawning robots in Gazebo with physics
- Motion Planning: MoveIt! uses URDF for collision detection and inverse kinematics
- Control: Joint controllers reference URDF for limits and dynamics
For humanoid robots with 20+ degrees of freedom, URDF captures the kinematic tree—from torso to fingertips.
Core Concepts
Links and Joints
- Link: A rigid body (e.g., forearm, thigh, torso)
- Joint: Connection between links (e.g., elbow, knee, hip)
Humanoid Kinematic Tree:
torso
/ | \
l_arm | r_arm
|
head
|
pelvis
/ \
l_leg r_leg
Joint Types
| Type | Description | Use Case | Example |
|---|---|---|---|
| Revolute | Rotation around axis, limited range | Elbow, knee | 0° to 150° |
| Continuous | Unlimited rotation | Wheel, rotating sensor | -∞ to +∞ |
| Prismatic | Linear motion | Elevator, gripper | 0 to 0.1m |
| Fixed | No motion | Camera mount | N/A |
| Planar | Motion in plane | Mobile base | (x, y, θ) |
| Floating | 6-DOF (position + orientation) | Underwater robot | Rarely used |
Basic URDF Structure
<?xml version="1.0"?>
<robot name="simple_humanoid">
<!-- Base Link (Torso) -->
<link name="torso">
<visual>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.2 0.8 1.0"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.4" ixy="0.0" ixz="0.0"
iyy="0.5" iyz="0.0" izz="0.3"/>
</inertial>
</link>
<!-- Left Arm Link -->
<link name="left_upper_arm">
<visual>
<geometry>
<cylinder length="0.3" radius="0.03"/>
</geometry>
<material name="red">
<color rgba="0.8 0.2 0.2 1.0"/>
</material>
</visual>
<collision>
<geometry>
<cylinder length="0.3" radius="0.03"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.015" ixy="0.0" ixz="0.0"
iyy="0.015" iyz="0.0" izz="0.0015"/>
</inertial>
</link>
<!-- Shoulder Joint (Revolute) -->
<joint name="left_shoulder" type="revolute">
<parent link="torso"/>
<child link="left_upper_arm"/>
<origin xyz="0.0 0.15 0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="100.0" lower="-1.57" upper="3.14" velocity="2.0"/>
</joint>
</robot>
Key Elements:
<link>: Defines visual geometry, collision mesh, and inertia<joint>: Connects parent and child links with motion constraints<origin>: Position (xyz) and orientation (rpy=roll-pitch-yaw) relative to parent<axis>: Rotation/translation axis in joint frame<limit>: Effort (torque), velocity, and position bounds
XACRO: Programmable URDF
XACRO (XML Macros) adds programming features to URDF:
- Variables: Avoid hardcoding constants
- Macros: Reuse patterns (left/right arms, legs)
- Math: Calculate derived values (inertia, centers of mass)
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="humanoid">
<!-- Constants -->
<xacro:property name="torso_length" value="0.5"/>
<xacro:property name="arm_length" value="0.3"/>
<xacro:property name="arm_radius" value="0.03"/>
<!-- Arm Macro (reuse for left/right) -->
<xacro:macro name="arm" params="prefix reflect">
<link name="${prefix}_upper_arm">
<visual>
<geometry>
<cylinder length="${arm_length}" radius="${arm_radius}"/>
</geometry>
<material name="red">
<color rgba="0.8 0.2 0.2 1.0"/>
</material>
</visual>
<collision>
<geometry>
<cylinder length="${arm_length}" radius="${arm_radius}"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.015" ixy="0.0" ixz="0.0"
iyy="0.015" iyz="0.0" izz="0.0015"/>
</inertial>
</link>
<joint name="${prefix}_shoulder" type="revolute">
<parent link="torso"/>
<child link="${prefix}_upper_arm"/>
<origin xyz="0.0 ${reflect * 0.15} 0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="100.0" lower="-1.57" upper="3.14" velocity="2.0"/>
</joint>
</xacro:macro>
<!-- Torso -->
<link name="torso">
<visual>
<geometry>
<box size="0.3 0.2 ${torso_length}"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.2 0.8 1.0"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.3 0.2 ${torso_length}"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.4" ixy="0.0" ixz="0.0"
iyy="0.5" iyz="0.0" izz="0.3"/>
</inertial>
</link>
<!-- Instantiate Arms -->
<xacro:arm prefix="left" reflect="1"/>
<xacro:arm prefix="right" reflect="-1"/>
</robot>
Converting XACRO to URDF:
# Generate URDF from XACRO
xacro humanoid.urdf.xacro > humanoid.urdf
# Validate URDF
check_urdf humanoid.urdf
Inertial Properties
Accurate inertia is critical for simulation stability. For basic shapes:
Box
<inertial>
<mass value="m"/>
<inertia ixx="${(m/12) * (y^2 + z^2)}"
iyy="${(m/12) * (x^2 + z^2)}"
izz="${(m/12) * (x^2 + y^2)}"
ixy="0" ixz="0" iyz="0"/>
</inertial>
Cylinder (length L, radius R)
<inertial>
<mass value="m"/>
<inertia ixx="${(m/12) * (3*R^2 + L^2)}"
iyy="${(m/12) * (3*R^2 + L^2)}"
izz="${(m/2) * R^2}"
ixy="0" ixz="0" iyz="0"/>
</inertial>
Tools for Complex Meshes:
- MeshLab: Compute mass properties from STL/OBJ files
- SolidWorks/Fusion 360: Export inertia tensors directly
Gazebo Integration
To simulate in Gazebo, add Gazebo-specific tags:
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="humanoid">
<!-- Include standard Gazebo plugins -->
<xacro:include filename="$(find gazebo_ros)/urdf/gazebo.urdf.xacro"/>
<!-- Link with Gazebo properties -->
<link name="torso">
<!-- Visual, collision, inertial (as before) -->
</link>
<!-- Gazebo-specific material -->
<gazebo reference="torso">
<material>Gazebo/Blue</material>
<mu1>0.2</mu1> <!-- Friction coefficient 1 -->
<mu2>0.2</mu2> <!-- Friction coefficient 2 -->
</gazebo>
<!-- Joint with effort controller -->
<joint name="left_shoulder" type="revolute">
<!-- Standard joint definition -->
</joint>
<transmission name="left_shoulder_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="left_shoulder">
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
</joint>
<actuator name="left_shoulder_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<!-- Gazebo ROS Control Plugin -->
<gazebo>
<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
<robotNamespace>/</robotNamespace>
</plugin>
</gazebo>
</robot>
Spawning in Gazebo:
# Launch Gazebo
gazebo
# Spawn robot
ros2 run gazebo_ros spawn_entity.py -entity humanoid -file humanoid.urdf -x 0 -y 0 -z 1
Visualization in RViz2
# Launch RViz with robot model
ros2 launch urdf_tutorial display.launch.py model:=humanoid.urdf
RViz Display Types:
- RobotModel: Visualizes URDF with TF frames
- TF: Shows coordinate frame tree
- JointStatePublisher GUI: Manually control joints
Humanoid URDF Example
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="atlas_v2">
<!-- Properties -->
<xacro:property name="PI" value="3.14159"/>
<!-- Leg Macro (20 DOF total: 2 legs × 5 DOF each) -->
<xacro:macro name="leg" params="prefix reflect">
<!-- Hip Yaw -->
<joint name="${prefix}_hip_yaw" type="revolute">
<parent link="pelvis"/>
<child link="${prefix}_hip_yaw_link"/>
<origin xyz="0 ${reflect * 0.1} -0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit effort="150" lower="${-PI/4}" upper="${PI/4}" velocity="2.0"/>
</joint>
<link name="${prefix}_hip_yaw_link">
<inertial>
<mass value="3.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>
<!-- Hip Roll -->
<joint name="${prefix}_hip_roll" type="revolute">
<parent link="${prefix}_hip_yaw_link"/>
<child link="${prefix}_hip_roll_link"/>
<origin xyz="0 0 0" rpy="0 0 0"/>
<axis xyz="1 0 0"/>
<limit effort="150" lower="${-PI/6}" upper="${PI/3}" velocity="2.0"/>
</joint>
<link name="${prefix}_hip_roll_link">
<inertial>
<mass value="5.0"/>
<inertia ixx="0.02" ixy="0" ixz="0" iyy="0.02" iyz="0" izz="0.01"/>
</inertial>
</link>
<!-- Hip Pitch -->
<joint name="${prefix}_hip_pitch" type="revolute">
<parent link="${prefix}_hip_roll_link"/>
<child link="${prefix}_thigh"/>
<origin xyz="0 0 -0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="200" lower="${-PI/2}" upper="${PI/2}" velocity="2.0"/>
</joint>
<link name="${prefix}_thigh">
<visual>
<geometry>
<cylinder length="0.4" radius="0.05"/>
</geometry>
<material name="gray"><color rgba="0.5 0.5 0.5 1.0"/></material>
</visual>
<collision>
<geometry>
<cylinder length="0.4" radius="0.05"/>
</geometry>
</collision>
<inertial>
<mass value="8.0"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.02"/>
</inertial>
</link>
<!-- Knee -->
<joint name="${prefix}_knee" type="revolute">
<parent link="${prefix}_thigh"/>
<child link="${prefix}_shin"/>
<origin xyz="0 0 -0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="200" lower="0" upper="${PI}" velocity="2.0"/>
</joint>
<link name="${prefix}_shin">
<visual>
<geometry>
<cylinder length="0.4" radius="0.04"/>
</geometry>
<material name="gray"><color rgba="0.5 0.5 0.5 1.0"/></material>
</visual>
<collision>
<geometry>
<cylinder length="0.4" radius="0.04"/>
</geometry>
</collision>
<inertial>
<mass value="6.0"/>
<inertia ixx="0.08" ixy="0" ixz="0" iyy="0.08" iyz="0" izz="0.015"/>
</inertial>
</link>
<!-- Ankle Pitch -->
<joint name="${prefix}_ankle_pitch" type="revolute">
<parent link="${prefix}_shin"/>
<child link="${prefix}_foot"/>
<origin xyz="0 0 -0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit effort="100" lower="${-PI/4}" upper="${PI/4}" velocity="2.0"/>
</joint>
<link name="${prefix}_foot">
<visual>
<geometry>
<box size="0.2 0.1 0.05"/>
</geometry>
<material name="black"><color rgba="0.1 0.1 0.1 1.0"/></material>
</visual>
<collision>
<geometry>
<box size="0.2 0.1 0.05"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.005" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>
</xacro:macro>
<!-- Base Link (Pelvis) -->
<link name="pelvis">
<visual>
<geometry>
<box size="0.3 0.25 0.15"/>
</geometry>
<material name="blue"><color rgba="0.2 0.2 0.8 1.0"/></material>
</visual>
<collision>
<geometry>
<box size="0.3 0.25 0.15"/>
</geometry>
</collision>
<inertial>
<mass value="12.0"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.15" iyz="0" izz="0.12"/>
</inertial>
</link>
<!-- Instantiate Legs -->
<xacro:leg prefix="left" reflect="1"/>
<xacro:leg prefix="right" reflect="-1"/>
</robot>
Best Practices
1. Validate URDF
check_urdf my_robot.urdf
# Visualize kinematic tree
urdf_to_graphiz my_robot.urdf
2. Use Realistic Masses
- Lightweight arms: 1-3 kg
- Torso: 10-20 kg
- Legs: 5-10 kg per leg
- Total humanoid: 50-100 kg
3. Set Joint Limits
<!-- Knee: 0° to 150° -->
<limit lower="0" upper="2.618" effort="200" velocity="2.0"/>
4. Collision vs. Visual Geometry
- Visual: High-detail meshes (STL, DAE)
- Collision: Simplified primitives (boxes, spheres) for performance
<collision>
<geometry>
<sphere radius="0.1"/> <!-- Simple collision -->
</geometry>
</collision>
<visual>
<geometry>
<mesh filename="package://my_robot/meshes/head.stl"/> <!-- Detailed visual -->
</geometry>
</visual>
Common Pitfalls
1. Missing <inertial> Tags:
- Symptom: Robot collapses in Gazebo
- Fix: Add realistic mass and inertia
2. Unrealistic Joint Limits:
- Symptom: Robot bends in impossible ways
- Fix: Reference human joint ROM (range of motion)
3. Wrong <origin> Values:
- Symptom: Links floating in space
- Fix: Use
ros2 launch urdf_tutorial display.launch.pyto debug visually
Key Takeaways
✅ URDF: XML format for robot kinematics and dynamics
✅ XACRO: Macros reduce duplication (left/right symmetry)
✅ Joints: Revolute (rotation), Prismatic (linear), Fixed
✅ Inertia: Use physics formulas or CAD tools for accuracy
✅ Gazebo: Add transmissions and plugins for simulation
Next Steps
You've completed Module 1! You now understand ROS 2 architecture, communication, Python development, and robot modeling. Time to apply these skills in Project 1: ROS 2 Humanoid Control System.