How to Handle Frame Semantics and Coordinate Transforms in URDF: A Complete Guide
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. 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 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 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 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 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
scaleattribute; 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:
<!-- 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:
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.mdand the test suite intest_source.pyto 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, 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →