Skip to main content

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​

  • 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​

TypeDescriptionUse CaseExample
RevoluteRotation around axis, limited rangeElbow, knee0° to 150°
ContinuousUnlimited rotationWheel, rotating sensor-∞ to +∞
PrismaticLinear motionElevator, gripper0 to 0.1m
FixedNo motionCamera mountN/A
PlanarMotion in planeMobile base(x, y, θ)
Floating6-DOF (position + orientation)Underwater robotRarely used

Basic URDF Structure​

simple_robot.urdf
<?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)
humanoid.urdf.xacro
<?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:

humanoid_gazebo.urdf.xacro
<?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​

full_humanoid.urdf.xacro
<?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.py to 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.

View Project 1 Requirements →

Continue to Module 2: Digital Twin Simulation →