# How to Generate URDF Robot Descriptions from Python with `gen_urdf()`

> Easily generate URDF robot descriptions from Python using genurdf(). Access links, joints, and mesh resources programmatically for advanced robot control and simulation.

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

---

**Use the `genurdf()` function in [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py) to parse, validate, and inspect URDF files, returning a typed `UrdfSource` object with full programmatic access to links, joints, and mesh resources.**

Generating URDF robot descriptions from Python is essential for robotics workflows that require automated validation, inspection, or transformation of robot models. The **text-to-CAD** repository provides a purpose-built URDF skill that exposes `genurdf()`—a robust parser that handles XML validation, mesh URI resolution, and kinematic tree verification in a single call.

## What `genurdf()` Does

The `genurdf()` function serves as the primary entry point for the URDF skill. It wraps `read_urdf_source` to provide a complete analysis pipeline:

- **Parses XML structure** using `ET.fromstring`, requiring a root `<robot>` element with a `name` attribute
- **Validates link definitions**, checking for duplicate names and consistent inertial properties via `_validate_link_inertials`
- **Processes geometry elements**, classifying and resolving mesh URIs through `classify_mesh_uri` and `resolve_mesh_uri`
- **Verifies joint integrity**, ensuring supported types (`fixed`, `continuous`, `revolute`, `prismatic`), valid parent/child references, and proper axis/limit specifications
- **Confirms tree connectivity**, enforcing a single root link and acyclic structure through depth-first traversal

All errors raise `UrdfSourceError`; warnings emit `UrdfSourceWarning` for non-critical issues.

## Function Signature and Parameters

```python
def genurdf(
    path: Path,
    package_map: dict[str, Path] | None = None
) -> UrdfSource

```

| Parameter | Type | Description |
|-----------|------|-------------|
| `path` | `Path` | Absolute or relative path to the `.urdf` file |
| `package_map` | `dict[str, Path] \| None` | Optional mapping of ROS package names to local directories for resolving `package://` mesh URIs |

The function returns a frozen `UrdfSource` dataclass containing:

- `file_ref`, `source_path`, `robot_name`, `root_link`
- `links: tuple[str, …]` — all link names in declaration order
- `joints: tuple[UrdfJoint, …]` — validated joint objects with limit information
- `mesh_paths`, `visual_mesh_paths`, `collision_mesh_paths` — resolved filesystem paths

## Complete Python Example

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

# Load a URDF file (absolute or relative to repo root)

urdf_path = Path("models/my_robot.urdf")

# Optional: map ROS package names to local directories

package_map = {"my_robot_description": Path("models/my_robot_description")}

# Parse & validate

robot = genurdf(urdf_path, package_map=package_map)

# Inspect the robot description

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

print("\nLinks:")
for link in robot.links:
    print(f" - {link}")

print("\nJoints:")
for joint in robot.joints:
    print(
        f" - {joint.name} ({joint.joint_type}) "
        f"parent={joint.parent_link} child={joint.child_link} "
        f"limits=[{joint.min_value_deg}, {joint.max_value_deg}]"
    )

print("\nVisual meshes:")
for mesh in robot.visual_mesh_paths:
    print(f" • {mesh}")

print("\nCollision meshes:")
for mesh in robot.collision_mesh_paths:
    print(f" • {mesh}")

```

## Source File Architecture

The URDF skill maintains clean separation between core logic and interfaces:

| File | Purpose | Key Components |
|------|---------|----------------|
| [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py) | Parser and validator implementation | `genurdf()`, `read_urdf_source()`, `UrdfSource` dataclass, `_validate_link_inertials()`, `_joint_limits_deg()` |
| [`skills/urdf/scripts/urdf/cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/cli.py) | Command-line frontend | Thin wrapper exposing `text-to-cad urdf ...` commands |

Functions in [`source.py`](https://github.com/earthtojake/text-to-cad/blob/main/source.py) follow a strict validation hierarchy: XML parsing → link collection → geometry extraction → joint processing → tree validation. This ordering ensures that structural errors surface early with precise `UrdfSourceError` messages.

## Mesh URI Resolution

The `genurdf()` function handles three mesh URI types through `classify_mesh_uri`:

1. **`package://`** — Resolved against `package_map` entries
2. **`file://`** — Converted to absolute filesystem paths
3. **Relative paths** — Interpreted relative to `source_path`

Unsupported geometry tags trigger immediate `UrdfSourceError` exceptions, preventing downstream processing of invalid robot descriptions.

## Integration with text-to-CAD

The URDF skill adheres to repository-wide isolation principles:

- **No cross-skill imports** — All dependencies live within `skills/urdf/` or shared utilities in `packages/cadpy_metadata`
- **Reusable geometry utilities** — `resolve_mesh_uri` and `classify_mesh_uri` leverage `packages/cadpy_metadata` for consistent mesh handling across skills
- **Test coverage** — Unit tests in [`tests/python/skills/urdf/test_source.py`](https://github.com/earthtojake/text-to-cad/blob/main/tests/python/skills/urdf/test_source.py) and [`test_cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/test_cli.py) verify parser behavior against malformed inputs

## Extending `genurdf()` for Custom Workflows

To adapt `genurdf()` for specialized requirements:

- **Custom package resolution** — Pass an expanded `package_map` with additional URI scheme handlers
- **Serialization pipelines** — The frozen `UrdfSource` dataclass supports direct JSON conversion for MoveIt! SRDF generation or Gazebo SDF export
- **Validation overrides** — Subclass `UrdfSourceError` to capture specific failure modes in automated CI/CD pipelines

## Summary

- **`genurdf()`** in [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py) provides complete URDF parsing, validation, and inspection from Python
- Returns an immutable **`UrdfSource`** dataclass with full access to kinematic structure and resolved mesh paths
- Handles **ROS package URI resolution** through the optional `package_map` parameter
- Enforces **strict validation**—duplicate links, cyclic joints, and unsupported geometry types raise `UrdfSourceError`
- Integrates cleanly with the **text-to-CAD** ecosystem while maintaining skill isolation

## Frequently Asked Questions

### What Python version does `genurdf()` require?

The text-to-CAD repository uses Python 3.10+ features including union type syntax (`dict[str, Path] | None`) and the `match` statement in related skills. Ensure your environment matches the repository's [`pyproject.toml`](https://github.com/earthtojake/text-to-cad/blob/main/pyproject.toml) specifications.

### How do I resolve `package://` URIs without ROS installed?

Provide a `package_map` dictionary mapping package names to local directories. The `genurdf()` function intercepts these URIs before any ROS infrastructure is invoked, making pure-Python URDF processing possible without a full ROS installation.

### Can `genurdf()` export modified URDF files?

The current implementation focuses on ingestion and validation. To generate modified URDF XML, access the `UrdfSource` fields programmatically and reconstruct the XML using `xml.etree.ElementTree` or template engines. The dataclass structure provides all necessary data for faithful reconstruction.

### What's the difference between `genurdf()` and `read_urdf_source()`?

They are aliases for the same function—`genurdf()` is the preferred public name, while `read_urdf_source` appears in internal import statements. Both reside in [`skills/urdf/scripts/urdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/scripts/urdf/source.py) and exhibit identical behavior.