# How to Handle Frame Semantics and Coordinate Transforms in URDF: A Complete Guide

> Master URDF frame semantics and coordinate transforms. Learn how to define origins and axes for seamless robot model integration and analysis. A complete guide.

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

---

**To handle frame semantics and coordinate transforms in URDF, define the joint origin relative to the parent link frame, express joint axes in the joint frame itself, and declare visual, collision, and inertial origins relative to their containing link frame.**

The **Unified Robot Description Format (URDF)** encodes a robot’s kinematic tree as a hierarchy of *links* and *joints*. Correctly managing which coordinate frame each transform, axis, and geometry is expressed in is essential when generating valid URDF files within the **text-to-cad** repository and for ensuring compatibility with downstream tools such as MoveIt! and motion planning pipelines.

## Core URDF Frame Rules

URDF defines a strict hierarchy where every transform exists in a specific parent frame. Understanding these relationships prevents ambiguous or invalid robot descriptions.

| Element | Meaning | Frame of Reference |
|---------|---------|---------------------|
| `<parent link="…">` | Name of the parent link | – |
| `<child link="…">` | Name of the child link (coincident with the joint frame) | – |
| `<origin xyz="…" rpy="…">` (under `<joint>`) | Transform **from the parent link frame to the joint frame** | Parent link |
| `<axis xyz="…">` (movable joint) | Joint axis vector **expressed in the joint frame** | Joint |
| `<visual><origin …>` | Pose of the visual geometry **relative to the link frame** | Link |
| `<collision><origin …>` | Pose of the collision geometry **relative to the link frame** | Link |
| `<inertial><origin …>` | Center‑of‑mass pose **relative to the link frame** | Link |

These rules are documented in the repository’s reference file at [`skills/urdf/references/frame-semantics.md`](https://github.com/earthtojake/text-to-cad/blob/main/skills/urdf/references/frame-semantics.md). Keeping each origin independent allows a visual mesh, a collision shape, and the inertial center of mass to be offset differently without frame ambiguity.

## Units and Angular Conventions

Consistency in units ensures that physics engines and planning algorithms interpret values correctly:

- **Lengths** – Always specify in **meters**.
- **Masses** – Use **kilograms**.
- **Angles** – Express in **radians** (remember to convert degrees to radians before emitting joint limits).

## Validation Checklists for URDF Compliance

The text-to-cad repository provides two dedicated reference checklists in [`frame-semantics.md`](https://github.com/earthtojake/text-to-cad/blob/main/frame-semantics.md) to enforce correctness:

- **Joint Axis Checklist** – Verifies presence, finiteness, non‑zero magnitude, normalization, and correct frame expression for every movable joint.
- **Visual/Collision Placement Checklist** – Validates that origins are relative to the owning link, confirms correct mesh scaling, geometry choices, and stable URDF‑relative paths.

These checklists are enforced automatically by the URDF generation skill to catch frame semantic errors before runtime.

## URDF Processing Pipeline in text-to-cad

The repository implements a four-stage pipeline to generate and validate URDF documents while respecting frame semantics:

### Metadata Extraction

When a skill script declares `gen_urdf()`, the helper `_parse_urdf_envelope_metadata` in [`viewer/packages/cadpy/src/cadpy/metadata.py`](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/metadata.py) parses the surrounding “envelope” comment block for required fields, including the `xml` content containing the URDF body.

### URDF Generation

The actual file is written by `gen_urdf()` within the skill script. The generation pipeline in [`viewer/packages/cadpy/src/cadpy/generation.py`](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/generation.py) collects the generated URDF path (`source.urdf_path`) and registers it with the catalog so downstream services can locate the asset.

### Runtime Consumption

MoveIt! adapters (`moveit2_server`) resolve the URDF path via the SRDF metadata (`tcad:urdf`) and parse the document using `xml.etree.ElementTree`. Helper functions such as `_urdf_joint_types` and `_active_urdf_joints` rely strictly on the correct frame semantics to compute joint limits and active joint lists.

### Testing

The test suite [`tests/python/skills/urdf/test_source.py`](https://github.com/earthtojake/text-to-cad/blob/main/tests/python/skills/urdf/test_source.py) exercises the URDF source reader (`read_urdf_source`) and ensures that meshes, collision primitives, and inertial definitions respect the frame rules defined in the specification.

## Practical Implementation Guide

Follow these guidelines when authoring URDF files to maintain correct coordinate transforms:

- **Always write the `<origin>` of any visual, collision, or inertial element relative to the link frame**; never assume inheritance from the joint or world frame.
- **Express joint axes in the joint frame**; if you need a different direction, pre‑rotate the joint via the `<origin rpy>` tag rather than modifying the axis definition.
- **Convert all angular limits to radians** before emitting them in `<limit lower="…" upper="…" …>`.
- **Scale meshes** to meters using the `scale` attribute; URDF does not infer units from the mesh file itself.
- **Use stable package URIs** (`package://…`) for mesh paths; relative paths may break when the URDF is relocated to different runtime environments.

## Code Examples

The following XML demonstrates correct frame semantics for a revolute elbow joint and its associated link geometry:

```xml
<!-- Joint definition: axis expressed in joint frame, origin from parent link -->
<joint name="elbow_joint" type="revolute">
  <parent link="upper_arm_link"/>
  <child link="forearm_link"/>
  <origin xyz="0 0 0.15" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>               <!-- Y‑axis in joint frame -->
  <limit lower="-1.57" upper="1.57" effort="30" velocity="1.5"/>
</joint>

<!-- Visual geometry: pose relative to forearm_link -->
<link name="forearm_link">
  <visual>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <geometry>
      <mesh filename="package://my_robot/meshes/forearm.stl" scale="0.001 0.001 0.001"/>
    </geometry>
  </visual>
  <collision>
    <origin xyz="0.02 0 0" rpy="0 0 0"/>
    <geometry>
      <cylinder radius="0.035" length="0.30"/>
    </geometry>
  </collision>
  <inertial>
    <origin xyz="0 0 0.15" rpy="0 0 0"/>
    <mass value="1.2"/>
    <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.0012" iyz="0" izz="0.0015"/>
  </inertial>
</link>

```

To programmatically access a generated URDF in the text-to-cad environment:

```python
from urdf.source import read_urdf_source

# Assume a skill script generated a URDF at `my_robot.urdf`

urdf_path = read_urdf_source("my_robot.urdf")
print(urdf_path.read_text())   # Inspect the raw XML

```

## Summary

- **Joint origins** define the transform from the **parent link frame** to the **joint frame**.
- **Joint axes** must be expressed in the **joint frame**, not the world or visual mesh frames.
- **Visual, collision, and inertial origins** are always relative to the **link frame** that contains them.
- Use **meters**, **kilograms**, and **radians** exclusively to ensure physical correctness.
- Leverage the validation checklists in [`frame-semantics.md`](https://github.com/earthtojake/text-to-cad/blob/main/frame-semantics.md) and the test suite in [`test_source.py`](https://github.com/earthtojake/text-to-cad/blob/main/test_source.py) to verify compliance.

## Frequently Asked Questions

### What coordinate frame is the joint axis defined in?

The **joint axis** is defined in the **joint frame** itself, not the parent link frame or world frame. According to the URDF specification used in text-to-cad, the `<axis xyz="...">` element defines the direction of motion for revolute or prismatic joints relative to the joint's own coordinate system. If you need the axis to point in a different direction relative to the parent, rotate the joint using the `<origin rpy="...">` tag instead of altering the axis vector.

### How do I specify the pose of a visual mesh in URDF?

The pose of a **visual mesh** is specified relative to the **link frame** using the `<visual><origin ...>` element. This is distinct from the joint origin, which defines where the link attaches to its parent. The text-to-cad validation checklist enforces that visual origins are independent of the joint frame to prevent misalignment when the joint moves or rotates.

### Why are my joint limits not working in MoveIt!?

Joint limits must be expressed in **radians** for revolute joints and **meters** for prismatic joints. If you pass degree values directly into the `<limit lower="..." upper="...">` attributes, the planning pipeline will interpret them as very small angular displacements (e.g., 90 degrees becomes 90 radians, which is ~5156 degrees), causing planning failures. Always convert degrees to radians before emitting URDF XML.

### Where does text-to-cad store URDF generation metadata?

The repository stores URDF generation metadata in [`viewer/packages/cadpy/src/cadpy/metadata.py`](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/metadata.py), specifically within the `_parse_urdf_envelope_metadata` function. This function extracts the XML envelope from skill scripts, while [`viewer/packages/cadpy/src/cadpy/generation.py`](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/generation.py) handles the actual file creation and catalog registration via `gen_urdf()`. The runtime system then resolves these files using the `tcad:urdf` SRDF metadata key.