Core URDF/Xacro syntax, joint types, kinematic structures, and URDF validation for robot descriptions
This skill provides comprehensive guidance on creating, structuring, and validating URDF (Unified Robot Description Format) files for robot descriptions.
Use this skill when:
Every URDF file has this basic structure:
<?xml version="1.0"?>
<robot name="robot_name">
<!-- Define links (rigid bodies) -->
<link name="base_link">
<!-- Visual geometry for rendering -->
<visual>
<geometry>
<box size="0.5 0.3 0.1"/>
</geometry>
<material name="blue">
<color rgba="0 0 1 1"/>
</material>
</visual>
<!-- Collision geometry for physics -->
<collision>
<geometry>
<box size="0.5 0.3 0.1"/>
</geometry>
</collision>
<!-- Inertial properties for dynamics -->
<inertial>
<mass value="5.0"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/>
</inertial>
</link>
<!-- Define joints (connections between links) -->
<joint name="base_to_wheel" type="continuous">
<parent link="base_link"/>
<child link="wheel_link"/>
<origin xyz="0.2 0.2 0" rpy="0 1.57 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- Define other links -->
<link name="wheel_link">
<!-- ... geometry ... -->
</link>
</robot>
<link> - Represents a rigid body in the robot:
name - Unique identifier<visual>, <collision>, <inertial> elements<joint> - Represents a connection between two links:
name - Unique identifiertype - Joint type (revolute, continuous, prismatic, fixed, etc.)<parent> - Parent link name<child> - Child link name<origin> - Relative position and orientation<axis> - Direction and rotation/translation axis<limit> - Joint constraints (for movable joints)<origin> - Specifies relative pose:
xyz - Position (x, y, z in meters)rpy - Orientation (roll, pitch, yaw in radians)Used for display and rendering:
<link name="example_link">
<visual>
<!-- Geometry type and dimensions -->
<geometry>
<box size="x y z"/>
<!-- or -->
<cylinder radius="r" length="l"/>
<!-- or -->
<sphere radius="r"/>
<!-- or -->
<mesh filename="package://robot_name/meshes/part.stl" scale="0.001 0.001 0.001"/>
</geometry>
<!-- Optional: Position and orientation within link -->
<origin xyz="0 0 0" rpy="0 0 0"/>
<!-- Optional: Material coloring -->
<material name="blue">
<color rgba="0 0 1 1"/> <!-- R G B Alpha (0-1) -->
</material>
</visual>
</link>
Geometry types:
<box size="x y z"/> - Rectangular box in meters<cylinder radius="r" length="l"/> - Cylinder with radius and length in meters<sphere radius="r"/> - Sphere with radius in meters<mesh filename="path" scale="sx sy sz"/> - External 3D mesh file (STL, DAE, OBJ)Material colors (RGBA):
rgba="1 0 0 1" - Redrgba="0 1 0 1" - Greenrgba="0 0 1 1" - Bluergba="1 1 1 1" - Whitergba="0 0 0 1" - BlackUsed for physics simulation and collision detection. Should typically be simpler than visual geometry:
<link name="complex_part">
<visual>
<!-- Detailed visual mesh -->
<geometry>
<mesh filename="package://robot/meshes/detailed_part.stl"/>
</geometry>
</visual>
<collision>
<!-- Simplified collision boxes -->
<geometry>
<box size="0.5 0.3 0.1"/>
</geometry>
</collision>
</link>
Best practices:
<link name="gripper">
<collision>
<origin xyz="0 0 0"/>
<geometry><box size="0.1 0.2 0.05"/></geometry>
</collision>
<collision>
<origin xyz="0.08 0.1 0"/>
<geometry><box size="0.04 0.1 0.05"/></geometry>
</collision>
</link>
Define mass and rotational inertia for dynamics simulation:
<link name="base_link">
<inertial>
<!-- Mass in kilograms -->
<mass value="5.0"/>
<!-- Inertia tensor (moment of inertia) -->
<!-- ixx, iyy, izz = rotational inertia about x, y, z axes -->
<!-- ixy, ixz, iyz = products of inertia (usually 0 for symmetric objects) -->
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.2"/>
<!-- Optional: Center of mass offset from link origin -->
<origin xyz="0 0 0.05" rpy="0 0 0"/>
</inertial>
</link>
Computing inertia:
I = (1/12) * m * (h^2 + d^2) where h, d are dimensionsI_x = I_y = (1/12) * m * (3*r^2 + h^2), I_z = (1/2) * m * r^2I = (2/5) * m * r^2Simplified approach: If exact inertia is unknown, use rough estimates:
<inertial>
<mass value="1.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
No movement - connects two links rigidly:
<joint name="fixed_joint" type="fixed">
<parent link="base_link"/>
<child link="sensor_link"/>
<origin xyz="0.1 0 0.05" rpy="0 0 0"/>
</joint>
Use for:
Rotational movement with limits (like a door hinge):
<joint name="shoulder_joint" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.3" rpy="0 0 0"/>
<axis xyz="0 1 0"/> <!-- Rotate around Y axis -->
<limit lower="-1.57" upper="1.57" effort="50" velocity="1.0"/>
</joint>
Parameters:
<axis> - Rotation axis (unit vector, typically one value = 1)<limit>:
lower, upper - Min/max angles in radianseffort - Maximum torque in Newton-metersvelocity - Maximum angular velocity in rad/sCommon axes:
xyz="1 0 0" - Rotate around X (roll)xyz="0 1 0" - Rotate around Y (pitch)xyz="0 0 1" - Rotate around Z (yaw)Unlimited rotation (like a wheel):
<joint name="wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="wheel_link"/>
<origin xyz="0.2 0.15 0" rpy="0 1.57 0"/>
<axis xyz="0 1 0"/>
</joint>
Use for:
Note: No <limit> element needed
Linear sliding movement (like a piston):
<joint name="linear_actuator" type="prismatic">
<parent link="base_link"/>
<child link="piston_link"/>
<origin xyz="0 0 0" rpy="0 0 0"/>
<axis xyz="0 0 1"/> <!-- Move along Z axis -->
<limit lower="0" upper="0.2" effort="100" velocity="0.1"/>
</joint>
Parameters:
<axis> - Direction of movement (unit vector)<limit>:
lower, upper - Min/max position in meterseffort - Maximum force in Newtonsvelocity - Maximum linear velocity in m/sMovement in a 2D plane (advanced, less common)
Free 6-DOF movement (for flying robots or underwater vehicles)
Always specify limits for revolute joints:
<!-- Limited rotation: safe joint -->
<joint name="elbow" type="revolute">
<axis xyz="0 1 0"/>
<limit lower="-2.0" upper="2.0" effort="30" velocity="1.5"/>
<!-- Can rotate -2 to +2 radians (~-114 to +114 degrees) -->
<!-- Max torque: 30 Nm, Max speed: 1.5 rad/s -->
</joint>
If you forget limits: Many simulators will refuse to load the file!
A robot is organized as a tree of links connected by joints:
base_link (root)
|
| shoulder_joint (revolute)
|
arm_link
|
| elbow_joint (revolute)
|
forearm_link
|
| wrist_joint (revolute)
|
hand_link
URDF representation:
<robot name="robot_arm">
<!-- Root link -->
<link name="base_link">
<visual>
<geometry><box size="0.3 0.3 0.1"/></geometry>
</visual>
</link>
<!-- First joint and child link -->
<joint name="shoulder_joint" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.15"/>
<axis xyz="0 0 1"/>
<limit lower="-1.57" upper="1.57" effort="50" velocity="1"/>
</joint>
<link name="arm_link">
<visual>
<geometry><cylinder radius="0.05" length="0.4"/></geometry>
</visual>
</link>
<!-- Continue chain... -->
</robot>
base_link base_link (root)
/ | \ \
/ | \ \
fl_hip fr_hip rl_hip rr_hip
/ / / /
fl_udeg fr_uleg rl_uleg rr_uleg
/ / / /
fl_leg fr_leg rl_leg rr_leg
<origin>The xyz and rpy in <origin> define the transform from parent to child:
<joint name="example" type="fixed">
<parent link="base_link"/>
<child link="camera_link"/>
<origin xyz="0.1 0 0.2" rpy="0 0 0"/>
<!-- camera_link is positioned 0.1m forward, 0.2m up from base_link -->
<!-- With no rotation -->
</joint>
Three rotations applied in order: X (roll), Y (pitch), Z (yaw):
<!-- 90 degree pitch (rotate around Y axis) -->
<origin xyz="0 0 0" rpy="0 1.57 0"/>
<!-- 45 degree roll and yaw (rotate around X and Z) -->
<origin xyz="1 2 3" rpy="0.785 0 0.785"/>
Converting degrees to radians: radians = degrees * π / 180
These are different:
<link name="camera">
<visual>
<!-- Position of visual geometry WITHIN the link -->
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry><box size="0.05 0.05 0.05"/></geometry>
</visual>
</link>
<joint name="camera_mount" type="fixed">
<!-- Position of camera_link RELATIVE TO parent (base_link) -->
<origin xyz="0.1 0 0.2" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="camera"/>
</joint>
Mesh files and resources use package-relative paths:
<!-- Correct: package:// URI (portable across machines) -->
<mesh filename="package://my_robot/meshes/wheel.stl"/>
<!-- Wrong: Absolute path (not portable) -->
<mesh filename="/home/user/ros_ws/my_robot/meshes/wheel.stl"/>
<!-- Wrong: Relative path (fragile) -->
<mesh filename="meshes/wheel.stl"/>
The extension finds packages by:
package.xml filesBest practice: Always use package:// URIs in your URDF files.
Typical project structure:
my_robot/
├── package.xml
├── urdf/
│ └── my_robot.urdf (or .xacro)
└── meshes/
├── base.stl
├── wheel.stl
└── gripper/
├── finger_left.stl
└── finger_right.stl
References in URDF:
<mesh filename="package://my_robot/meshes/base.stl"/>
<mesh filename="package://my_robot/meshes/wheel.stl"/>
<mesh filename="package://my_robot/meshes/gripper/finger_left.stl"/>
Mesh units may differ from URDF (which uses meters):
<!-- STL exported in millimeters, convert to meters -->
<mesh filename="package://my_robot/meshes/part.stl" scale="0.001 0.001 0.001"/>
<!-- Scale by 50% -->
<mesh filename="package://my_robot/meshes/part.stl" scale="0.5 0.5 0.5"/>
For complex geometry that cannot be created with basic shapes (boxes, cylinders, spheres), use OpenSCAD:
⚠️ IMPORTANT: Unit Mismatch
0.001 to convert mm → mWorkflow:
.scad file with your geometry logic in millimeters (e.g., meshes/robot_foot.scad).scad → .stl when saved.stl file in your URDF/Xacro (not the .scad)scale="0.001 0.001 0.001" to convert from mm to metersExample:
Create meshes/robot_foot.scad (dimensions in millimeters):
// robot_foot.scad - Complex foot geometry
// All dimensions are in MILLIMETERS
module robot_foot(length=100, width=50, height=20) {
difference() {
// Main foot body: 100mm × 50mm × 20mm
cube([length, width, height]);
// Mounting holes: 5mm radius
translate([20, width/2, 0])
cylinder(h=height, r=5);
translate([length-20, width/2, 0])
cylinder(h=height, r=5);
}
}
// Generate foot with dimensions in mm
robot_foot(length=120, width=60, height=25);
Reference in URDF (with 0.001 scale to convert mm → m):
<link name="foot">
<visual>
<geometry>
<!--
Reference the generated STL file (not the .scad).
Scale 0.001 converts from OpenSCAD millimeters to URDF meters.
The foot will be 0.12m × 0.06m × 0.025m in the robot.
-->
<mesh filename="package://my_robot/meshes/robot_foot.stl" scale="0.001 0.001 0.001"/>
</geometry>
</visual>
<collision>
<geometry>
<mesh filename="package://my_robot/meshes/robot_foot.stl" scale="0.001 0.001 0.001"/>
</geometry>
</collision>
</link>
Important notes:
scale="0.001 0.001 0.001" when referencing OpenSCAD-generated STL files.scad) stays in millimeters - this is standard CAD convention.stl file in URDF, never the .scad.scad file is the source; the .stl is what renders.scad file, the extension automatically regenerates the .stl.scad files can referenceError: "Duplicate link/joint name"
Error: "Parent link not found"
<!-- Wrong: Parent link doesn't exist -->
<joint name="wheel" type="continuous">
<parent link="baes_link"/> <!-- Typo! -->
<child link="wheel"/>
</joint>
Error: "Joint limits missing"
<!-- Wrong: revolute joint without limits -->
<joint name="arm" type="revolute">
<axis xyz="0 1 0"/>
<!-- Missing <limit> tag -->
</joint>
<!-- Correct: Always specify limits -->
<joint name="arm" type="revolute">
<axis xyz="0 1 0"/>
<limit lower="-1.57" upper="1.57" effort="50" velocity="1"/>
</joint>
Error: "Mesh file not found"
Follow ROS naming standards:
<!-- ✓ Good: lowercase with underscores -->
<link name="base_link"/>
<link name="left_front_wheel"/>
<joint name="base_to_wheel"/>
<joint name="shoulder_joint"/>
<!-- ✗ Bad: CamelCase or mixed case -->
<link name="BaseLink"/>
<link name="leftFrontWheel"/>
<joint name="baseToWheel"/>
<?xml version="1.0"?>
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- Root link representing the robot base -->
<link name="base_link">
<visual>
<geometry>
<!-- 50cm wide, 30cm deep, 10cm tall -->
<box size="0.5 0.3 0.1"/>
</geometry>
<material name="base_color">
<color rgba="0.8 0.8 0.8 1"/> <!-- Light gray -->
</material>
</visual>
<!-- Collision geometry approximates base as simple box -->
<collision>
<geometry>
<box size="0.5 0.3 0.1"/>
</geometry>
</collision>
</link>
<!-- Front-left wheel connection -->
<joint name="base_to_fl_wheel" type="continuous">
<parent link="base_link"/>
<child link="fl_wheel"/>
<!-- Position: 0.15m forward, 0.15m left, 0.05m down from base center -->
<origin xyz="0.15 0.15 -0.05" rpy="0 1.5708 0"/>
<!-- Wheel rotates around Y axis (left-right) -->
<axis xyz="0 1 0"/>
</joint>
<link name="fl_wheel">
<visual>
<geometry>
<!-- 10cm diameter wheel, 5cm wide -->
<cylinder radius="0.05" length="0.05"/>
</geometry>
<material name="black">
<color rgba="0.1 0.1 0.1 1"/>
</material>
</visual>
</link>
</robot>
The extension includes a custom 3D viewer for mesh files:
.stl, .dae, .glb, .gltf files in 3D| Joint Type | Movement | Use Case |
|---|---|---|
| Fixed | None | Sensors, non-moving attachments |
| Revolute | Rotational (limited) | Robot arms, articulated segments |
| Continuous | Rotational (unlimited) | Wheels, rotating shafts |
| Prismatic | Linear (limited) | Actuators, sliding mechanisms |
| Planar | 2D planar | Advanced wheeled robots |
| Floating | Free 6-DOF | Flying/underwater robots |
✅ DO:
base_link as the rootpackage:// URIs for mesh files❌ DON'T: