Skip to main content

Understanding URDF for Humanoid Robots

πŸ€– What is URDF?​

URDF (Unified Robot Description Format) is an XML-based format for describing a robot's physical structure. It defines:

  • Links: Rigid bodies (robot parts like torso, arms, legs)
  • Joints: Connections between links (revolute, prismatic, fixed)
  • Visual properties: How the robot looks
  • Collision properties: For physics simulation
  • Inertial properties: Mass and inertia for dynamics

πŸ—οΈ Basic URDF Structure​

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

<!-- Base Link -->
<link name="base_link">
<visual>
<geometry>
<box size="0.3 0.2 0.4"/>
</geometry>
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.3 0.2 0.4"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.1" ixy="0.0" ixz="0.0"
iyy="0.1" iyz="0.0" izz="0.1"/>
</inertial>
</link>

</robot>

A link represents a rigid body in the robot.

<link name="torso">
<!-- Visual: What you see -->
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
<material name="gray">
<color rgba="0.5 0.5 0.5 1"/>
</material>
</visual>

<!-- Collision: For physics -->
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
</collision>

<!-- Inertial: For dynamics -->
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="15.0"/>
<inertia ixx="0.2" ixy="0" ixz="0"
iyy="0.3" iyz="0"
izz="0.1"/>
</inertial>
</link>

Common Geometries​

GeometryURDF SyntaxUse Case
Box<box size="x y z"/>Torso, simple limbs
Cylinder<cylinder radius="r" length="l"/>Arms, legs
Sphere<sphere radius="r"/>Joints, head
Mesh<mesh filename="model.stl"/>Complex shapes

πŸ”— Defining Joints​

Joints connect links and define how they move relative to each other.

Joint Types​

<!-- Revolute Joint (Rotational with limits) -->
<joint name="shoulder_joint" type="revolute">
<parent link="torso"/>
<child link="upper_arm"/>
<origin xyz="0 0.15 0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.57" upper="1.57" effort="100" velocity="1.0"/>
</joint>

<!-- Continuous Joint (Unlimited rotation) -->
<joint name="wheel_joint" type="continuous">
<parent link="base"/>
<child link="wheel"/>
<origin xyz="0 0 -0.1" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>

<!-- Prismatic Joint (Linear motion) -->
<joint name="gripper_joint" type="prismatic">
<parent link="hand"/>
<child link="finger"/>
<origin xyz="0 0 0" rpy="0 0 0"/>
<axis xyz="1 0 0"/>
<limit lower="0" upper="0.05" effort="50" velocity="0.1"/>
</joint>

<!-- Fixed Joint (No motion) -->
<joint name="camera_joint" type="fixed">
<parent link="head"/>
<child link="camera"/>
<origin xyz="0.05 0 0.1" rpy="0 0 0"/>
</joint>

🧍 Complete Humanoid Example​

Let's build a simplified humanoid robot:

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

<!-- Torso -->
<link name="torso">
<visual>
<geometry>
<box size="0.3 0.2 0.5"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.4 0.8 1"/>
</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" ixz="0" iyy="0.5" iyz="0" izz="0.2"/>
</inertial>
</link>

<!-- Head -->
<link name="head">
<visual>
<geometry>
<sphere radius="0.12"/>
</geometry>
<material name="skin">
<color rgba="0.9 0.7 0.6 1"/>
</material>
</visual>
<collision>
<geometry>
<sphere radius="0.12"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>

<joint name="neck_joint" type="revolute">
<parent link="torso"/>
<child link="head"/>
<origin xyz="0 0 0.3" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-0.785" upper="0.785" effort="10" velocity="1.0"/>
</joint>

<!-- Right Arm -->
<link name="right_upper_arm">
<visual>
<geometry>
<cylinder radius="0.04" length="0.3"/>
</geometry>
<material name="gray">
<color rgba="0.5 0.5 0.5 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder radius="0.04" length="0.3"/>
</geometry>
</collision>
<inertial>
<mass value="1.5"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.001"/>
</inertial>
</link>

<joint name="right_shoulder" type="revolute">
<parent link="torso"/>
<child link="right_upper_arm"/>
<origin xyz="0 -0.15 0.2" rpy="0 1.57 0"/>
<axis xyz="0 1 0"/>
<limit lower="-3.14" upper="3.14" effort="50" velocity="2.0"/>
</joint>

<!-- Right Leg -->
<link name="right_upper_leg">
<visual>
<geometry>
<cylinder radius="0.06" length="0.4"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder radius="0.06" length="0.4"/>
</geometry>
</collision>
<inertial>
<mass value="3.0"/>
<inertia ixx="0.04" ixy="0" ixz="0" iyy="0.04" iyz="0" izz="0.002"/>
</inertial>
</link>

<joint name="right_hip" type="revolute">
<parent link="torso"/>
<child link="right_upper_leg"/>
<origin xyz="0 -0.1 -0.25" rpy="0 1.57 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.57" upper="1.57" effort="100" velocity="1.5"/>
</joint>

</robot>

🎨 Using Meshes for Realistic Models​

For production humanoids, use 3D meshes:

<link name="realistic_head">
<visual>
<geometry>
<mesh filename="package://my_robot/meshes/head.dae" scale="1 1 1"/>
</geometry>
</visual>
<collision>
<!-- Use simplified geometry for collision -->
<geometry>
<sphere radius="0.12"/>
</geometry>
</collision>
</link>

πŸ”§ URDF with Xacro (Advanced)​

Xacro (XML Macros) makes URDF more maintainable:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="humanoid">

<!-- Define properties -->
<xacro:property name="arm_length" value="0.3"/>
<xacro:property name="arm_radius" value="0.04"/>

<!-- Define macro for arms -->
<xacro:macro name="arm" params="prefix reflect">
<link name="${prefix}_upper_arm">
<visual>
<geometry>
<cylinder radius="${arm_radius}" length="${arm_length}"/>
</geometry>
</visual>
<inertial>
<mass value="1.5"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.001"/>
</inertial>
</link>

<joint name="${prefix}_shoulder" type="revolute">
<parent link="torso"/>
<child link="${prefix}_upper_arm"/>
<origin xyz="0 ${reflect*0.15} 0.2" rpy="0 1.57 0"/>
<axis xyz="0 1 0"/>
<limit lower="-3.14" upper="3.14" effort="50" velocity="2.0"/>
</joint>
</xacro:macro>

<!-- Use macro -->
<xacro:arm prefix="right" reflect="-1"/>
<xacro:arm prefix="left" reflect="1"/>

</robot>

πŸ“Š Visualizing URDF in RViz2​

# Install urdf_tutorial if needed
sudo apt install ros-humble-urdf-tutorial

# Launch RViz with your URDF
ros2 launch urdf_tutorial display.launch.py model:=path/to/your/robot.urdf

πŸ§ͺ Testing Your URDF​

Check URDF Validity​

# Check for errors
check_urdf my_robot.urdf

# Convert URDF to graphical representation
urdf_to_graphiz my_robot.urdf

Publish Robot State​

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
import math

class JointStatePublisher(Node):
def __init__(self):
super().__init__('joint_state_publisher')
self.publisher = self.create_publisher(JointState, 'joint_states', 10)
self.timer = self.create_timer(0.1, self.publish_joint_states)
self.angle = 0.0

def publish_joint_states(self):
msg = JointState()
msg.header.stamp = self.get_clock().now().to_msg()
msg.name = ['right_shoulder', 'left_shoulder', 'neck_joint']

# Animate joints
self.angle += 0.05
msg.position = [
math.sin(self.angle),
math.sin(self.angle),
math.sin(self.angle) * 0.5
]

self.publisher.publish(msg)

def main():
rclpy.init()
node = JointStatePublisher()
rclpy.spin(node)
rclpy.shutdown()

🎯 Key Takeaways​

  1. URDF defines robot structure using links and joints
  2. Links represent rigid bodies with visual, collision, and inertial properties
  3. Joints connect links and define motion constraints
  4. Xacro makes URDF more maintainable with macros and properties
  5. RViz2 visualizes URDF models for debugging

πŸ§ͺ Practice Exercise​

Create a complete humanoid URDF with:

  • Torso, head, 2 arms, 2 legs
  • Appropriate joint types and limits
  • Realistic inertial properties
  • Test in RViz2

Next: Hands-On Lab β†’