# URDF Frame Semantics Explained: Joint Origins, Link Frames, and Axis Conventions in text-to-cad

> Master URDF frame semantics with text-to-cad. Understand joint origins, link frames, and axis conventions for accurate robot modeling. Learn more!

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

---

**The text-to-cad repository implements a strict URDF parser in [`packages/cadjs/src/lib/urdf/parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadjs/src/lib/urdf/parseUrdf.js) that extracts joint origin transforms, defines link-relative visual frames, and enforces axis conventions in the child link coordinate system.**

The **Unified Robot Description Format (URDF)** defines how robotic systems are modeled as kinematic trees of links connected by joints. Understanding URDF frame semantics—how joint origins position child links, where link frames originate, and how axis vectors specify motion—is essential for correct visualization, simulation, and control. The *text-to-cad* repository provides a production-grade parser that implements these semantics with precision, validated through comprehensive tree integrity checks.

## How Link Frames Are Defined in text-to-cad

Every `<link>` element in a URDF file becomes a link object with a **name** and an array of **visuals**. The parser enforces structural requirements and computes local transforms for each visual element.

### Link Name Validation and Visual Parsing

The parser requires non-empty link names, throwing `URDF link name is required` if violated—see lines 45-48 in [`parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/parseUrdf.js). For each `<visual>` child element, the parser distinguishes between mesh-based and primitive geometry:

- **Mesh visuals**: The parser resolves the `filename` attribute, extracts a display label from the path, and computes a `localTransform` that composes the visual's origin with any scaling factors:

```javascript
localTransform: multiplyTransforms(
  parseOriginTransform(childElementsByTag(visualElement, "origin")[0]),
  parseScaleTransform(childElementsByTag(geometryElement, "mesh")[0]?.getAttribute("scale"))
)

```

*Implementation at lines 65-73.*

- **Primitive geometries**: For `<box>`, `<cylinder>`, or `<sphere>` elements, the parser still extracts dimensions and applies the visual's origin transform—see lines 77-87.

**Critical semantic**: Each visual's `localTransform` is **expressed relative to its link frame**. This establishes the link frame as the reference coordinate system for all downstream joint transformations.

## Joint Origin Transforms: Connecting Parent and Child Frames

The joint origin transform is the fundamental mechanism for positioning child links relative to parent links in the URDF kinematic tree.

### Parsing and Composing Joint Origins

The `parseOriginTransform` helper function (lines 77-86) builds a 4×4 homogeneous transformation matrix from optional `xyz` (translation) and `rpy` (roll-pitch-yaw) attributes:

```javascript
function parseOriginTransform(element) {
  const xyz = parseNumberList(element?.getAttribute("xyz"), 3, [0, 0, 0]);
  const rpy = parseNumberList(element?.getAttribute("rpy"), 3, [0, 0, 0]);
  return multiplyTransforms(
    translationTransform(xyz),
    rotationTransformFromRpy(rpy)
  );
}

```

Within `parseJoint`, this transform is assigned directly to the joint's `originTransform` field—line 58. This matrix **connects the child link frame to the parent link frame**, enabling the viewer to:

- Position child geometry correctly in world space
- Compute forward kinematics through chain multiplication
- Animate joint motions by applying additional transforms about the joint axis

## Axis Conventions: Motion Direction in Child Link Frames

URDF joint axes follow a specific convention that affects how rotations and translations are applied.

### Axis Parsing and Default Handling

The parser handles the `<axis>` element according to joint type—lines 27-30:

```javascript
const axis = type === "fixed"
  ? [1, 0, 0]
  : parseNumberList(childElementsByTag(jointElement, "axis")[0]?.getAttribute("xyz"), 3, [1, 0, 0]);

```

**Key semantics**:

- **Fixed joints**: Default axis `[1, 0, 0]`—arbitrary, as no motion occurs
- **Revolute, prismatic, continuous joints**: Axis vector parsed from `xyz` attribute, with fallback to `[1, 0, 0]`
- **Reference frame**: The axis is expressed **in the child link's coordinate system**

This convention means the viewer applies joint motion transforms **after** applying the joint origin transform. The sequence for a revolute joint with angle θ becomes:

```

T_world_child = T_world_parent × T_joint_origin × R_axis(θ)

```

The `multiplyTransforms` and `rotationTransformFromAxisAngle` helpers in [`kinematics.js`](https://github.com/earthtojake/text-to-cad/blob/main/kinematics.js) implement this mathematics.

## Tree Validation: Ensuring Frame Hierarchy Integrity

The parser enforces that URDF describes a **single-rooted directed acyclic graph** through the `validateTree` function—lines 79-124.

### Validation Checks and Their Frame Implications

| Check | Error Message | Lines | Purpose |
|-------|-------------|-------|---------|
| Unique joint names | `Duplicate URDF joint name` | 84-87 | Prevents ambiguous frame references |
| Single parent per link | `URDF link … has multiple parents` | 90-91 | Guarantees tree structure (no link has two incoming edges) |
| Single root existence | `URDF must form a single rooted tree` | 98-100 | Ensures one base frame for global coordinates |
| Cycle detection | `URDF joint graph contains a cycle` | 104-110 | Prevents infinite loops in kinematic chains |
| Disconnected link detection | `URDF leaves links disconnected from the root` | 118-122 | Validates all links are reachable from root |

These checks are prerequisite for any valid frame computation—cycles would break forward kinematics, and multiple roots would create ambiguous world coordinate definitions.

## Mimic Joints: Coupled Axis Motion

Advanced URDF models use `<mimic>` elements to synchronize joint motion. The parser extracts `joint`, `multiplier`, and `offset` attributes—lines 85-100—and validates that the referenced joint exists—lines 73-75.

When rendering mimicked joints, the viewer computes the dependent joint's position as:

```

position_mimic = multiplier × position_master + offset

```

The axis convention applies identically: the mimic joint's axis vector (in its child frame) determines how this computed position manifests as motion.

## Complete Parsing Example

Load and inspect URDF frame semantics programmatically:

```javascript
import { parseUrdf } from "@earthtojake/text-to-cad/packages/cadjs/urdf";

const urdfText = await fetch("robot.urdf").then(r => r.text());
const robot = parseUrdf(urdfText, { 
  sourceUrl: "https://example.com/robot.urdf" 
});

// Inspect root frame
console.log("Root link:", robot.rootLink);

// Examine joint frame connections
robot.joints.forEach(j => {
  console.log(`${j.name}:`);
  console.log("  Parent → Child:", j.parent, "→", j.child);
  console.log("  Origin matrix:", j.originTransform);
  console.log("  Axis (child frame):", j.axis);
  console.log("  Type:", j.type);
});

// Access link-local visual transforms
robot.links.forEach(l => {
  l.visuals.forEach(v => {
    console.log(`${l.name} visual: localTransform =`, v.localTransform);
  });
});

```

*Entry point at lines 26-27 of [`parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/parseUrdf.js).*

## Visualizing Frames in the CAD Viewer

The viewer consumes parsed URDF data to render axes and animate joints:

```javascript
// Draw joint axes at their origins
robot.joints.forEach(joint => {
  if (joint.type === "fixed") return;
  
  const [tx, ty, tz] = joint.originTransform.slice(12, 15);
  const [ax, ay, az] = joint.axis;
  
  viewer.addCoordinateFrame({
    position: [tx, ty, tz],
    xAxis: joint.originTransform.slice(0, 3),   // transformed x
    yAxis: joint.originTransform.slice(4, 7),   // transformed y
    zAxis: joint.originTransform.slice(8, 11),  // transformed z
    axisColor: joint.type === "revolute" ? "#ff0000" : "#00ff00"
  });
});

```

## Key Source Files

| File | Purpose | Location |
|------|---------|----------|
| [`parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/parseUrdf.js) | Core URDF parser: link extraction, joint origins, axis handling, tree validation | [`packages/cadjs/src/lib/urdf/parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadjs/src/lib/urdf/parseUrdf.js) |
| [`kinematics.js`](https://github.com/earthtojake/text-to-cad/blob/main/kinematics.js) | Transformation math: `multiplyTransforms`, `translationTransform`, `rotationTransformFromRpy` | [`packages/cadjs/src/lib/urdf/kinematics.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadjs/src/lib/urdf/kinematics.js) |
| [`CadWorkspace.js`](https://github.com/earthtojake/text-to-cad/blob/main/CadWorkspace.js) | Viewer integration: URDF animation constants and frame update loop | [`viewer/src/client/components/CadWorkspace.js`](https://github.com/earthtojake/text-to-cad/blob/main/viewer/src/client/components/CadWorkspace.js) |

## Summary

- **Link frames** serve as the base coordinate system for all visual geometry, with transforms expressed locally per visual element
- **Joint origin transforms** (4×4 matrices parsed by `parseOriginTransform`) position child links relative to parent links
- **Axis vectors** are defined in the **child link frame** and determine motion direction for revolute, prismatic, and continuous joints
- **Tree validation** enforces single-root structure, acyclicity, and complete connectivity—essential for valid kinematics
- The *text-to-cad* implementation in [`packages/cadjs/src/lib/urdf/parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadjs/src/lib/urdf/parseUrdf.js) provides strict, spec-compliant URDF parsing for reliable robot visualization

## Frequently Asked Questions

### What coordinate system is the URDF joint axis defined in?

The joint axis is defined in the **child link's coordinate system**. This matches the official URDF specification: after applying the joint origin transform to position the child link relative to the parent, any joint motion (rotation or translation) occurs about this axis vector in the child's local frame. The *text-to-cad* parser implements this at lines 27-30 of [`parseUrdf.js`](https://github.com/earthtojake/text-to-cad/blob/main/parseUrdf.js).

### How does text-to-cad handle missing joint origin elements?

When a `<joint>` lacks an `<origin>` child element, `parseOriginTransform` receives `undefined` and applies default values of `[0, 0, 0]` for both `xyz` and `rpy`. This produces an identity transform, meaning the child link frame coincides with the parent link frame. The `parseNumberList` helper handles these defaults—see its usage in lines 77-86.

### Why does the parser reject URDF files with multiple root links?

The `validateTree` function explicitly checks for exactly one link with no parent—lines 98-100. Multiple roots would create **ambiguous world coordinate definitions**: without a single base frame, there is no consistent reference for computing global positions. The viewer requires one root to establish the world transform hierarchy.

### Can fixed joints have non-default axis values?

While the parser defaults fixed joint axes to `[1, 0, 0]` (lines 27-28), this value is purely conventional. Fixed joints prohibit all motion, so their axis vectors have no kinematic effect. The parser still stores the axis for completeness, but the viewer ignores it during animation.