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

The text-to-cad repository implements a strict URDF parser in 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.

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.

The parser requires non-empty link names, throwing URDF link name is required if violated—see lines 45-48 in 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:
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:

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

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:

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 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:

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.

Visualizing Frames in the CAD Viewer

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

// 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 Core URDF parser: link extraction, joint origins, axis handling, tree validation packages/cadjs/src/lib/urdf/parseUrdf.js
kinematics.js Transformation math: multiplyTransforms, translationTransform, rotationTransformFromRpy packages/cadjs/src/lib/urdf/kinematics.js
CadWorkspace.js Viewer integration: URDF animation constants and frame update loop 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 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.

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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →