# How to Create URDF Robot Descriptions with Joints, Limits, and Inertials: A Complete Guide

> Learn to create URDF robot descriptions with joints, limits, and inertials. This guide uses the earthtojake/text-to-cad repository for robust, simulation-ready robot models.

- Repository: [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad)
- Tags: how-to-guide
- Published: 2026-07-29

---

**The earthtojake/text-to-cad repository provides a production-ready URDF parser in [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py) that validates joint limits, link inertials, and kinematic tree structure to ensure your robot descriptions are simulation-ready.**

Creating valid URDF (Unified Robot Description Format) files requires precise specifications for joint motion constraints and physical properties. The text-to-cad project offers robust validation tools that catch malformed inertial tensors and invalid joint configurations before they reach your simulator. This guide walks through the exact validation logic and APIs you need to build compliant robot descriptions.

## Understanding the URDF Parser Architecture

The core validation engine resides in [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py), featuring strict parsing rules that enforce URDF compliance. The parser constructs a rich `UrdfSource` dataclass (defined at lines [50-61](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L50)) containing tuples of `UrdfJoint` objects (lines [40-48](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L40)) and validated link data.

The `read_urdf_source()` function serves as the primary entry point, performing three critical validation layers:

1. **Structural validation** - Ensures exactly one root link and a single-rooted tree structure (no cycles)
2. **Joint validation** - Verifies parent/child consistency and extracts motion limits (lines [123-174](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L123))
3. **Inertial validation** - Confirms mass positivity and inertia tensor validity via `_validate_link_inertials()` (lines [220-298](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L220))

## Defining Joint Limits in URDF

Joint limits define the operational range of your robot's motion and are mandatory for certain joint types. The parser categorizes joints into three distinct types with specific limit requirements.

### Joint Types and Limit Requirements

The `_joint_limits_deg()` method (lines [408-452](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L408)) handles each category differently:

- **Fixed joints** - Implicitly limited to `0°, 0°` with no motion allowed (lines [408-410](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L408))
- **Continuous joints** - Unlimited rotation mapped to `-180°, 180°` (lines [411-412](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L411))
- **Revolute and prismatic joints** - Require explicit `<limit>` tags with `lower` and `upper` attributes in radians

### Parsing and Validation Logic

For revolute and prismatic joints, the parser performs rigorous validation at lines [424-452](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L424):

- Verifies limit values are finite numbers
- Ensures `lower ≤ upper` constraint
- Converts radian values to degrees for revolute joints using `degrees(lower)` and `degrees(upper)`
- Optionally parses `effort` and `velocity` limits via `_validate_optional_float_attr()` (lines [362-371](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L362))

If validation fails, the parser raises `UrdfSourceError` with specific diagnostic messages indicating which joint violates the constraints.

## Adding Inertial Properties to Links

Inertial properties define how your robot responds to forces in simulation. Each `<link>` element may contain an `<inertial>` child element that the parser validates through multiple sub-checks.

### Mass and Inertia Tensor Validation

The `_validate_inertia_values()` function (lines [322-344](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L322)) enforces physical correctness:

- **Mass** must be a positive float value (validated via `_required_float_attr()` with positivity checks)
- **Inertia tensor** requires six finite values (`ixx`, `ixy`, `ixz`, `iyy`, `iyz`, `izz`)
- **Diagonal entries** (`ixx`, `iyy`, `izz`) must be strictly positive
- **Triangle inequality** must hold between diagonal and off-diagonal elements to ensure physically realizable inertia

### Origin Validation

The inertial frame origin is validated by `_validate_origin()` (lines [665-684](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L665)), which parses optional `xyz` (position) and `rpy` (roll-pitch-yaw) vectors. While the origin is optional in the URDF specification, providing it ensures accurate center-of-mass calculations in physics engines.

## Step-by-Step Workflow for Valid URDF Creation

Follow this workflow to ensure your URDF passes the text-to-cad validation suite:

1. **Define the kinematic structure** - Create `<link>` elements for each rigid body and `<joint>` elements connecting them, ensuring the graph forms a single-rooted tree (no cycles, exactly `links - 1` joints)

2. **Add joint limits** - Include `<limit lower="..." upper="..."/>` tags for all revolute and prismatic joints, specifying angles in radians for revolute joints and meters for prismatic joints

3. **Specify inertial data** - Add `<inertial>` elements to links that participate in physics simulation, including `<mass>`, `<inertia>`, and optionally `<origin>` sub-elements

4. **Validate with the parser** - Run `read_urdf_source(Path("your_robot.urdf"))` to catch syntax errors, invalid inertia tensors, or joint limit violations before deployment

5. **Consume the parsed data** - Access the returned `UrdfSource` object to extract joint names, limit ranges, and validated link properties for your CAD or simulation pipeline

## Practical Code Examples

### Example URDF with Joints and Inertials

Save this as `robot_arm.urdf`:

```xml
<robot name="example_arm">
  <!-- Base link with complete inertial specification -->
  <link name="base_link">
    <inertial>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <mass value="5.0"/>
      <inertia ixx="0.1" ixy="0.0" ixz="0.0"
               iyy="0.1" iyz="0.0" izz="0.1"/>
    </inertial>
    <visual>
      <geometry><box size="0.2 0.2 0.1"/></geometry>
    </visual>
  </link>

  <!-- Revolute joint with explicit limits +/- 90 degrees -->
  <joint name="shoulder_pan" type="revolute">
    <parent link="base_link"/>
    <child link="upper_arm"/>
    <origin xyz="0 0 0.1" rpy="0 0 0"/>
    <axis xyz="0 0 1"/>
    <limit lower="-1.5708" upper="1.5708" effort="10" velocity="1.0"/>
  </joint>

  <link name="upper_arm">
    <inertial>
      <origin xyz="0 0 0.15"/>
      <mass value="2.0"/>
      <inertia ixx="0.02" ixy="0.0" ixz="0.0"
               iyy="0.02" iyz="0.0" izz="0.005"/>
    </inertial>
    <visual>
      <geometry><cylinder radius="0.05" length="0.3"/></geometry>
    </visual>
  </link>

  <!-- Fixed joint requires no limit tag -->
  <joint name="wrist_fixed" type="fixed">
    <parent link="upper_arm"/>
    <child link="wrist"/>
    <origin xyz="0 0 0.3"/>
  </joint>

  <link name="wrist"/>
</robot>

```

### Parsing and Validating with the Repository API

Use the text-to-cad parser to validate your URDF and extract joint limits:

```python
from pathlib import Path
from skills.urdf.scripts.urdf.source import read_urdf_source

# Load and validate the URDF

urdf_path = Path("robot_arm.urdf")
urdf_data = read_urdf_source(urdf_path)

# Access validated data

print(f"Robot: {urdf_data.robot_name}")
print(f"Root link: {urdf_data.root_link}")

# Display joint limits with automatic radian-to-degree conversion

for joint in urdf_data.joints:
    print(f"Joint {joint.name} ({joint.joint_type}):")
    print(f"  Range: [{joint.min_value_deg}°, {joint.max_value_deg}°]")
    if joint.max_effort:
        print(f"  Max effort: {joint.max_effort} N·m")

```

This script validates that `shoulder_pan` limits convert correctly to `-90°` to `+90°` and confirms the inertial tensor satisfies physical constraints.

### Integration with ROS Simulation

After validation, load the URDF into ROS MoveIt:

```python
import moveit_commander

# Initialize ROS (requires running roscore)

moveit_commander.roscpp_initialize([])

# Load the validated URDF

robot = moveit_commander.RobotCommander()

# Verify limits match the URDF specification

for joint_name in robot.get_joint_names():
    limits = robot.get_joint_limits(joint_name)
    print(f"{joint_name}: {limits.min_position} to {limits.max_position}")

```

Because `read_urdf_source()` pre-validates the structure, ROS will not reject the file due to malformed inertial data or missing joint limits.

## Summary

- **The text-to-cad parser** in [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py) provides rigorous validation for URDF joint limits and inertial properties before simulation
- **Joint limits** require explicit `<limit>` tags for revolute and prismatic joints, with automatic radian-to-degree conversion and finite-value checking
- **Inertial validation** enforces positive mass, valid inertia tensors satisfying triangle inequality, and optional origin frames
- **The `read_urdf_source()` function** returns a structured `UrdfSource` dataclass containing validated `UrdfJoint` objects ready for downstream CAD pipelines
- **Single-rooted tree validation** ensures your URDF topology is compatible with ROS, MoveIt, and other standard robotics tools

## Frequently Asked Questions

### What happens if I omit joint limits for a revolute joint?

The parser will raise a `UrdfSourceError` during validation. According to the logic at lines [424-452](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L424) in [`source.py`](https://github.com/earthtojake/text-to-cad/blob/main/source.py), revolute and prismatic joints must include `<limit>` elements with valid `lower` and `upper` attributes. Fixed and continuous joints are the only types that can omit explicit limits.

### How does the parser validate that an inertia tensor is physically possible?

The `_validate_inertia_values()` function (lines [322-344](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py#L322)) checks three conditions: all six values must be finite numbers, diagonal entries (`ixx`, `iyy`, `izz`) must be positive, and the values must satisfy the triangle inequality required for real 3D objects. This prevents simulation instability caused by impossible mass distributions.

### Can I use this parser to validate URDF files before loading them into Gazebo or ROS?

Yes. Running `read_urdf_source()` performs the same structural and numerical validations that ROS requires, including checking for single-rooted tree structure, valid joint parent/child relationships, and positive-definite inertia matrices. Valid files are guaranteed to load successfully into ROS environments without URDF parsing errors.

### What units does the parser expect for joint limits and mass values?

The parser expects **radians** for revolute joint limits and **meters** for prismatic joint limits in the URDF file, which it converts to degrees for internal storage. Mass values must be in **kilograms**. The `origin` `xyz` attributes use meters, while `rpy` (roll-pitch-yaw) uses radians according to standard URDF conventions enforced throughout [`source.py`](https://github.com/earthtojake/text-to-cad/blob/main/source.py).