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>
𦴠Defining Linksβ
A link represents a rigid body in the robot.
Link Componentsβ
<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β
| Geometry | URDF Syntax | Use 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β
- URDF defines robot structure using links and joints
- Links represent rigid bodies with visual, collision, and inertial properties
- Joints connect links and define motion constraints
- Xacro makes URDF more maintainable with macros and properties
- 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 β