agents/openai.yaml
interface:
display_name: "URDF"
short_description: "Author and validate URDF robot descriptions."
default_prompt: "Use $urdf to author, update, and validate URDF robot descriptions as direct XML, handing new or changed URDFs to $cad-viewer when available."
LICENSE
MIT License
Copyright (c) 2026 Thompson Labs LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
references/authoring-contract.md
# URDF Authoring Contract
Use this reference when writing or editing URDF XML directly. The `.urdf` file is the source of truth: it must carry its own documentation, and its structure must be predictable enough that any later agent or engineer can audit it without external context.
## File Shape
Every authored `.urdf` follows this shape, in this order:
1. XML declaration: `<?xml version="1.0"?>`.
2. Design-ledger comment block (see below).
3. One `<robot name="...">` root element.
4. All `<link>` elements, root link first, then in tree order (parents before children).
5. All `<joint>` elements, in the same tree order as the child links they create.
Keep two-space indentation and one element per line. Do not interleave links and joints arbitrarily; a reader should be able to walk the kinematic tree top-to-bottom.
## Design-Ledger Comment Block
The ledger lives in the file, immediately after the XML declaration, as XML comments. Minimum content:
```xml
<?xml version="1.0"?>
<!--
robot: <name> | consumers: <RViz / Gazebo / MoveIt / driver / viewer>
units: meters, kilograms, radians | frames: +X forward, +Y left, +Z up (REP-103)
root: <root_link> | source of dimensions: <CAD file / drawing / measured / assumption>
meshes: <dir>, exported per-link in link frame, source units <mm|m>, scale <...>
inertials: <CAD mass properties / primitive formulas / assumed density X kg/m^3 / omitted>
assumptions: <every guessed value, sign convention, or approximation, one per line>
-->
```
Update the ledger in the same edit that changes the modeled facts. A stale ledger is worse than no ledger. See `references/design-ledger.md` for the full ledger checklist.
## Naming
- Links: `<part>_link` for physical links (`base_link`, `forearm_link`), bare descriptive names for frame-only links (`base_footprint`, `tool0`, `camera_optical_frame`).
- Joints: `<child-function>_joint` or `<parent>_to_<child>` (`shoulder_pan_joint`, `wrist_roll`). One convention per file.
- Names are identifiers consumed by SRDF, controllers, and TF; never rename casually. If a rename is required, update every consumer (SRDF groups, group states, disabled collisions) in the same task.
## Element Contract
For every `<link>` that represents physical geometry, author the subelements in this order: `inertial`, `visual`, `collision`. Frame-only links are empty (`<link name="tool0" />`) and must be listed as frame-only in the ledger.
For every `<joint>`:
- Attributes: `name`, `type` (`fixed`, `revolute`, `continuous`, or `prismatic`; use `floating`/`planar` only when the consumer and validation path support them — the bundled validator rejects them).
- Children in order: `<parent>`, `<child>`, `<origin>`, `<axis>` (movable joints), `<limit>` (revolute/prismatic), then optional `<dynamics>`, `<mimic>`, `<calibration>`, `<safety_controller>`.
- `<origin>` is the parent-link-frame transform to the joint frame at zero position; the child link frame coincides with the joint frame.
- `<axis>` is expressed in the joint (child) frame and should be a signed unit vector along a principal axis whenever the mechanism allows (`1 0 0`, `0 -1 0`, ...). A non-principal axis is a red flag: re-check the frame definitions before accepting one.
- `<limit>` carries radians (revolute) or meters (prismatic) plus `effort` and `velocity` when the consumer needs them. `continuous` joints take no lower/upper limits.
Never encode a kinematic fix by offsetting only the visual mesh; correct the joint/link frames instead, unless the mesh is genuinely offset from the link frame.
## Golden Skeleton
Copy this shape for new robots. It shows the ledger, ordering, a frame-only root, one fixed and one revolute joint, mesh + primitive geometry, and a computed inertial:
```xml
<?xml version="1.0"?>
<!--
robot: example_arm | consumers: CAD Viewer, MoveIt
units: meters, kilograms, radians | frames: +X forward, +Y left, +Z up (REP-103)
root: base_footprint | source of dimensions: STEP/example_arm.step
meshes: 3MF/, exported per-link in link frame, source units mm, scale 0.001
inertials: primitive-formula approximations at assumed uniform density 1200 kg/m^3
assumptions:
- shoulder axis sign chosen so positive motion raises the arm (+Y rotation)
- base mass 1.2 kg estimated, not weighed
-->
<robot name="example_arm">
<link name="base_footprint" />
<link name="base_link">
<inertial>
<origin xyz="0 0 0.03" rpy="0 0 0" />
<mass value="1.2" />
<!-- solid cylinder r=0.06 l=0.06: ixx=iyy=m(3r^2+l^2)/12, izz=m r^2/2 -->
<inertia ixx="0.00144" ixy="0" ixz="0" iyy="0.00144" iyz="0" izz="0.00216" />
</inertial>
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<mesh filename="3MF/base_link.3mf" scale="0.001 0.001 0.001" />
</geometry>
</visual>
<collision>
<origin xyz="0 0 0.03" rpy="0 0 0" />
<geometry>
<cylinder radius="0.06" length="0.06" />
</geometry>
</collision>
</link>
<link name="shoulder_link">
<inertial>
<origin xyz="0 0 0.08" rpy="0 0 0" />
<mass value="0.6" />
<!-- solid box 0.06x0.06x0.16: ixx=iyy=m(y^2+z^2)/12, izz=m(x^2+y^2)/12 -->
<inertia ixx="0.00146" ixy="0" ixz="0" iyy="0.00146" iyz="0" izz="0.00036" />
</inertial>
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<mesh filename="3MF/shoulder_link.3mf" scale="0.001 0.001 0.001" />
</geometry>
</visual>
<collision>
<origin xyz="0 0 0.08" rpy="0 0 0" />
<geometry>
<box size="0.06 0.06 0.16" />
</geometry>
</collision>
</link>
<joint name="base_footprint_to_base" type="fixed">
<parent link="base_footprint" />
<child link="base_link" />
<origin xyz="0 0 0" rpy="0 0 0" />
</joint>
<joint name="shoulder_pitch" type="revolute">
<parent link="base_link" />
<child link="shoulder_link" />
<origin xyz="0 0 0.06" rpy="0 0 0" />
<axis xyz="0 1 0" />
<limit lower="-1.5708" upper="1.5708" effort="8" velocity="2" />
</joint>
</robot>
```
## Helper Scripts
Direct authoring does not mean freehand numbers. Write a throwaway script whenever:
- inertials come from meshes or CAD solids (mass properties integration);
- more than a handful of transforms share a conversion (mm-to-m tables, mirrored left/right chains);
- disabled-collision or adjacency data must be derived downstream.
For complex or genuinely parametric models, the helper may be kept on disk next to related model source (for example beside STEP generator sources) and referenced from the ledger. The checked-in `.urdf` is still canonical: regenerating is an explicit editing action, never an implicit build step.
references/design-ledger.md
# URDF Design Ledger
Use this reference before creating or editing a `.urdf`. The ledger is the written spatial model that prevents silent frame, axis, unit, and mesh-scale mistakes.
The ledger's canonical home is a comment block at the top of the `.urdf` file itself (see the compact format in `references/authoring-contract.md`), optionally expanded in an adjacent README for large robots. It must be specific enough that another engineer can audit the URDF without reverse-engineering the XML, and it must be updated in the same edit that changes the modeled facts.
The sections below are the checklist of what the ledger must cover. For small robots the per-link and per-joint tables can collapse into a few comment lines; the information, not the table format, is what is required.
## Required Sections
### Robot Metadata
Record:
- robot name
- target consumers: RViz, robot_state_publisher, Gazebo/Ignition, MoveIt, real robot driver, or other
- unit convention: meters, kilograms, seconds, radians unless the project explicitly states otherwise
- frame convention: REP-103-style body convention when applicable, or a documented exception
- mesh unit convention: meters, millimeters, inches, or other
- source of dimensions: CAD, drawing, measured data, vendor documentation, existing URDF, or assumption
### Link Ledger
For every link, record:
| Field | Meaning |
|---|---|
| link name | Exact URDF `<link name="...">` value. |
| role | Physical link, frame-only link, sensor frame, tool frame, base frame, or other. |
| frame definition | Where the link frame is located and how its axes point. |
| parent joint | Joint that creates this child link frame, or `none` for root. |
| visual geometry | Primitive or mesh source, with origin relative to the link frame. |
| collision geometry | Primitive or mesh source, with origin relative to the link frame. |
| inertial source | CAD mass properties, vendor data, approximation, or intentionally omitted. |
Frame-only links such as `base_footprint`, optical frames, and `tool0` may omit inertial, visual, and collision blocks. Mark them explicitly as frame-only rather than leaving intent ambiguous.
### Joint Ledger
For every joint, record:
| Field | Meaning |
|---|---|
| joint name | Exact URDF `<joint name="...">` value. |
| type | `fixed`, `revolute`, `continuous`, or `prismatic` for the bundled validator; record `floating` or `planar` only if the project has a different supported validation path. |
| parent link | Link whose frame expresses the joint origin. |
| child link | Link whose frame is created at the joint frame. |
| origin xyz/rpy | Parent-link-frame transform from parent link to joint frame. |
| axis | Axis vector expressed in the joint frame, for movable joints. |
| limits | Radians for revolute, meters for prismatic, no finite lower/upper limits for continuous. |
| positive motion | What positive joint motion physically does. |
| source | CAD, drawing, measured data, existing model, or documented assumption. |
Do not write a movable joint without an explicit positive-motion convention. The sign of the axis is part of the model, not a cosmetic detail.
### Geometry Ledger
For every visual or collision item, record:
| Field | Meaning |
|---|---|
| link | Owning link. |
| kind | `visual` or `collision`. |
| geometry type | `mesh`, `box`, `cylinder`, or `sphere`. |
| source | CAD export, primitive approximation, vendor mesh, generated mesh, or temporary placeholder. |
| origin xyz/rpy | Transform from link frame to geometry frame. |
| scale | Mesh scale if applicable. |
| units | Mesh source units and URDF scale needed to express meters. |
Visual geometry is for display. Collision geometry is for contact, planning, and physics. It may intentionally be simpler than the visual geometry.
### Inertial Ledger
For every physical link, record:
| Field | Meaning |
|---|---|
| mass | Kilograms. |
| center of mass | Inertial origin xyz in the link frame. |
| inertia tensor | Tensor values and frame. |
| source | CAD mass properties, vendor data, calculation, approximation, or intentionally omitted. |
| confidence | Exact, estimated, placeholder, or unknown. |
Do not silently copy visual origins into inertial origins. The visual frame, collision frame, link frame, and center of mass can all differ.
### Assumption Ledger
Record every inferred or guessed value, including:
- unknown dimensions
- mesh units
- sign conventions
- joint axes
- parent/child direction
- visual or collision offsets
- mass, COM, and inertia approximations
- frame-only link intent
- unverified package URI resolution
List assumptions one per line in the ledger comment block so they survive with the file. In helper scripts, name assumed values by physical meaning (`ASSUMED_BASE_TO_SHOULDER_Z_M`), never as unlabelled numeric literals.
## When Information Is Missing
If spatial information is missing, do not invent a precise-looking transform. Choose one of these outcomes:
1. preserve existing source data unchanged;
2. create a frame-only or placeholder structure with explicit assumption comments;
3. use a clearly named approximate constant;
4. ask for dimensions or CAD data when the workflow allows interaction;
5. report that the model is structurally valid but spatially provisional.
A provisional URDF is acceptable when clearly labelled. A plausible but undocumented URDF is not.
references/frame-semantics.md
# URDF Frame Semantics
Use this reference whenever editing origins, axes, visual placement, collision placement, or inertials. Most URDF authoring errors are frame errors.
## Core Semantics
URDF represents a robot as a tree of links connected by joints.
For a joint:
- `<parent link="...">` names the parent link.
- `<child link="...">` names the child link.
- `<origin xyz="..." rpy="...">` is the transform from the parent link frame to the joint frame.
- The child link frame is coincident with the joint frame.
- `<axis xyz="...">` for a movable joint is expressed in the joint frame, not automatically in the world frame and not in the visual mesh frame.
For link subelements:
- `<visual><origin ...>` is expressed in the link frame.
- `<collision><origin ...>` is expressed in the link frame.
- `<inertial><origin ...>` is the center-of-mass/inertial frame expressed in the link frame.
These origins are independent. A mesh can be offset from its link frame, and the center of mass can be offset differently.
## Units and Angles
Use:
- meters for length;
- kilograms for mass;
- radians for angles;
- seconds for time;
- right-handed coordinate frames unless the project documents an exception.
Do not store revolute limits in degrees in URDF. Convert degrees to radians before writing them.
Do not use finite lower/upper limits for a `continuous` joint unless the project is intentionally not using URDF continuous-joint semantics.
## Joint Axis Checklist
For every non-fixed joint, confirm:
1. the axis is present;
2. the axis vector has three finite numbers;
3. the vector is nonzero;
4. the vector is normalized or intentionally normalized by helper code;
5. the vector is expressed in the joint frame;
6. positive motion is documented.
Examples:
```xml
<joint name="shoulder_pan_joint" type="revolute">
<parent link="base_link" />
<child link="shoulder_link" />
<origin xyz="0 0 0.24" rpy="0 0 0" />
<axis xyz="0 0 1" />
<limit lower="-3.14159" upper="3.14159" effort="40" velocity="2" />
</joint>
```
This means the child link frame is at `z = 0.24` in `base_link`, and positive joint motion rotates about +Z of the joint/child frame.
## Visual and Collision Placement Checklist
For every visual or collision block, confirm:
1. the origin is relative to the owning link frame;
2. mesh scale converts the mesh source units into meters;
3. visual and collision geometry are intentionally the same or intentionally different;
4. collision geometry is simple enough for the intended physics/planning consumer;
5. mesh paths are stable from the URDF file's location or use an intended package URI.
Example:
```xml
<link name="forearm_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<mesh filename="package://robot_description/meshes/forearm.stl" scale="0.001 0.001 0.001" />
</geometry>
</visual>
<collision>
<origin xyz="0.12 0 0" rpy="0 1.57079632679 0" />
<geometry>
<cylinder radius="0.035" length="0.24" />
</geometry>
</collision>
</link>
```
This places the visual mesh at the link frame and uses a simplified collision cylinder offset in the same link frame.
## Inertial Placement Checklist
For every physical link with inertial data, confirm:
1. mass is positive and finite;
2. inertial origin is the center of mass in the link frame;
3. inertia tensor values are in SI units;
4. tensor values correspond to the inertial frame being declared;
5. approximations are documented.
Do not infer inertial origin from visual or collision origin unless the source data proves they coincide.
references/inertials.md
# URDF Inertials
Inertials are the most-freehanded and least-checked part of LLM-authored URDFs. The rule is absolute: **never type inertia numbers from intuition.** Every `mass`, inertial `origin`, and `inertia` value is either copied from source data (CAD mass properties, vendor datasheet, measurement) or computed by a formula or script in the same task. Record which, per link, in the design ledger.
## When Inertials Are Required
- Simulation and dynamics consumers (Gazebo/Ignition, physics engines, torque control): every physical link needs a valid `inertial`.
- Visualization-only or kinematics-only consumers (RViz, CAD Viewer, MoveIt kinematic planning): inertials are optional but still recommended so the model stays simulator-ready.
- Frame-only links (`base_footprint`, optical frames, TCP markers): intentionally omit `inertial` and mark the link frame-only in the ledger.
Do not give movable physical links zero or missing mass for a simulation target; most engines misbehave. If mass is unknown, use a documented assumed density or assumed total mass distributed by volume, and say so in the ledger.
## Semantics
- `<inertial><origin>` is the center of mass expressed in the link frame. It is not the visual origin, the collision origin, or the joint origin.
- `<inertia>` is the rotational inertia tensor about the center of mass, expressed in the inertial frame (link frame rotated by the inertial origin `rpy`), in kg·m².
- If source data gives the tensor about another point, transfer it to the COM (parallel axis theorem, subtracting) before writing it down — or simpler, re-export CAD mass properties about the COM.
## Closed-Form Formulas (solid, uniform density)
With mass `m` (kg) and dimensions in meters, about the COM, axis-aligned:
- Box `x × y × z`: `ixx = m(y² + z²)/12`, `iyy = m(x² + z²)/12`, `izz = m(x² + y²)/12`.
- Cylinder radius `r`, length `l`, axis +Z: `ixx = iyy = m(3r² + l²)/12`, `izz = m r²/2`.
- Sphere radius `r`: `ixx = iyy = izz = 2m r²/5`.
- Thin rod length `l`, axis +Z: `ixx = iyy = m l²/12`, `izz ≈ 0` (use a small positive value, not 0).
Off-diagonal terms are zero for these primitives when the axes align with the link frame. If the primitive is rotated relative to the link frame, express the rotation in the inertial origin `rpy` instead of hand-rotating the tensor.
## Mesh-Derived Inertials
For mesh geometry, write a throwaway helper script; do not approximate a complex part as a primitive without saying so in the ledger. The standard recipe with `trimesh`:
```python
import trimesh
mesh = trimesh.load("3MF/forearm_link.3mf", force="mesh")
mesh.apply_scale(0.001) # mm -> m, match the URDF mesh scale
mesh.density = 1200.0 # documented assumed density, kg/m^3
print("mass", mesh.mass)
print("com", mesh.center_mass) # -> inertial <origin xyz>
print(mesh.moment_inertia) # about COM, link-frame axes -> ixx..izz
```
Requirements for the script route:
- The mesh must be the same file, same frame, and same scale as the URDF reference; otherwise the COM and tensor land in the wrong frame.
- The mesh should be watertight for volume integration; if it is not, fix the export or fall back to a documented primitive approximation.
- Uniform density is an assumption — record the chosen density (or the total-mass target it was derived from) in the ledger.
- CAD-native mass properties (for example OCP/OpenCascade `BRepGProp` on the STEP solid) are better than mesh integration when the STEP source is available; same rules apply.
For complex or parametric models it is reasonable to keep this helper script on disk next to the model's other source files and reference it from the ledger. That is optional; the checked-in URDF values remain canonical.
## Sanity Gates
Before accepting any inertial block, check:
1. `mass > 0`, all values finite; diagonal `ixx, iyy, izz > 0`.
2. Triangle inequality: `ixx + iyy ≥ izz`, `ixx + izz ≥ iyy`, `iyy + izz ≥ ixx` (the bundled validator enforces this).
3. Magnitude plausibility: for a part with characteristic size `d` and mass `m`, diagonal terms should be within roughly an order of magnitude of `m·d²/10`. A 0.5 kg, 10 cm part with `ixx = 2.0` kg·m² is wrong by ~1000×.
4. COM plausibility: the inertial origin lies inside (or very near) the part's bounding volume.
5. Off-diagonal terms are small relative to diagonals unless the part is genuinely skewed in the link frame — large `ixy/ixz/iyz` values usually mean the tensor was expressed in the wrong frame.
A common unit bug to watch for: CAD systems report mass properties in mm-based units. Converting mm-based inertia to kg·m² requires a factor of `1e-6` on top of any density conversion (lengths enter the tensor squared, volumes cubed). If the magnitude gate fails by a clean power of ten, suspect this first.
references/meshes.md
# URDF Mesh Preparation and References
Bad mesh handling is a top URDF failure mode, and it usually happens *before* any XML is written: source CAD is split into per-link assets incorrectly, exported in the wrong frame, or referenced at the wrong scale. Prepare assets first, then author XML that matches them.
## Splitting Source CAD Into Link Assets
The unit of export is the **link**, not the assembly and not the CAD feature tree:
1. Enumerate the links from the design ledger first. Every link that shows geometry gets exactly one visual asset (or an explicit set of assets); rigidly-joined parts that belong to one link are merged into that link's single export.
2. Never point multiple links at one combined assembly mesh with compensating origins. If two links share a source body, the body must be split at the joint in CAD before export.
3. Export each link's mesh **in that link's own frame** — the frame the ledger defines, coincident with the parent joint frame at zero position. Done right, every `<visual><origin>` is identity (`0 0 0`, `0 0 0`), which is the convention to follow and the easiest state to audit.
4. If an export cannot be re-framed (vendor mesh, scanned part), a nonzero visual origin is acceptable but must be recorded in the ledger with its source; it is a per-link constant, not a tuning knob.
5. Splitting, re-framing, and exporting STEP/STL/3MF/GLB assets belongs to the owning CAD workflow (`$cad`, `$step-parts` when installed). Do not attempt to fix a wrong split by editing URDF origins.
Checklist after export, before authoring:
- one file per link, named after the link (`3MF/forearm_link.3mf`);
- zero pose in the mesh file corresponds to the link frame;
- units of the export are known and recorded (mm is common for 3MF/STL);
- the export actually contains only that link's geometry (open it, or check bounding boxes with a one-line script).
## Units and Scale
- URDF lengths are meters. Mesh files are frequently millimeters, and STL carries no unit metadata at all.
- Express the conversion explicitly with `scale` on every mesh reference: `scale="0.001 0.001 0.001"` for mm sources. Omit `scale` only when the mesh is genuinely authored in meters.
- One convention per robot: do not mix mm and m assets in the same file without a ledger entry per exception.
- A robot rendering ~1000× too large or too small in the viewer is a scale-attribute bug; fix the scale, not the joint origins.
## Reference Forms
- **Local relative paths** (`3MF/forearm_link.3mf`) resolve from the `.urdf` file's directory. Preferred for a robot kept in a project tree — the bundled validator verifies these files exist.
- **`package://name/path` URIs** are for ROS-package consumers. The validator checks syntax only and warns that resolution is consumer-specific; confirm the consuming environment resolves the package root as expected.
- Remote URIs are accepted with warnings; avoid them for durable fixtures.
Keep mesh files under the same model directory tree as the URDF, so the file and its assets move together.
## Visual vs Collision Assets
- Visual meshes are for display: full detail, colors preserved where the format supports it.
- Collision geometry is for physics/planning: prefer primitives (`box`, `cylinder`, `sphere`) sized from the part's bounding volume, or a coarse closed mesh. Using the visual mesh for collision is a temporary fallback, not a default — concave visual meshes make physics engines slow and unstable.
- Collision origins are expressed in the link frame, independent of the visual origin.
## When Meshes Change
Mesh assets are owned by the CAD workflow. If the CAD source changed shape, re-export the affected link assets with the owning workflow first, then re-check the URDF: link frames, visual origins, collision approximations, and inertials may all be stale. Re-run the validator and the viewer sweep afterwards.
references/urdf-workflow.md
# URDF Workflow
Use this reference when editing robot-description structure, frame placement, mesh references, inertial data, or any `.urdf` output.
## Edit Loop
1. Locate the target `.urdf`. It is the source of truth; edit it directly.
2. Identify target consumers and strictness requirements: visualization, TF tree, simulation, planning, or real robot integration.
3. Read the design-ledger comment block at the top of the file; create it if missing (see `references/design-ledger.md`). Update the ledger in the same edit that changes modeled facts.
4. If links reference meshes, prepare or verify the per-link assets first (see `references/meshes.md`).
5. Apply URDF frame semantics exactly: joint origin in parent frame, child link frame at joint frame, joint axis in joint frame, visual/collision/inertial origins in link frame (see `references/frame-semantics.md`).
6. Author links, joints, limits, axes, origins, inertials, and geometry per `references/authoring-contract.md`. Compute derived numbers — inertia tensors, unit conversions, mirrored transforms — with formulas or a helper script; never freehand them (see `references/inertials.md`).
7. Validate with `cadgen urdf validate <file.urdf>` and fix findings until clean.
8. Run the rest of the verification recipe in `references/validation.md`: external tools when available, then a `$cad-viewer` sweep of every movable joint against the ledger's positive-motion statements.
9. Report smoke tests run, checks skipped, and remaining assumptions.
## Spatial-Reasoning Guardrails
LLMs are prone to plausible-looking spatial mistakes. Use these guardrails:
- Do not infer dimensions, handedness, axes, mesh units, or joint signs from vague descriptions.
- Do not silently mirror left/right parts. A mirrored chain changes axis signs and off-diagonal inertia terms; derive the mirror transform explicitly (helper script) and record it in the ledger.
- Do not assume visual mesh origin equals link frame, collision frame, or center of mass.
- Do not assume CAD mesh units are meters. STL files carry no reliable unit metadata.
- Do not encode a kinematic correction by offsetting only the visual mesh; correct the link and joint frames unless the visual mesh is genuinely offset.
- Preserve existing proven transforms unless the task explicitly requires changing them.
- Record every assumed value in the ledger comment block; in helper scripts, name constants by physical meaning (`ASSUMED_BASE_TO_SHOULDER_Z_M`).
## Standard Link Tags
Use these tags for each link that represents physical robot geometry:
- `inertial`: mass, center of mass, and inertia tensor used by simulators.
- `visual`: display geometry and optional material.
- `collision`: contact geometry used by physics and planning.
Frame-only links, such as `base_footprint`, optical frames, or tool-center marker frames, may intentionally omit these tags when they represent no physical mass or geometry.
For movable physical links, avoid zero or missing mass unless the target simulator explicitly supports that modeling choice. If exact mass properties are unavailable, use a documented approximation and make the approximation easy to replace later.
## Joint Authoring
For every joint, confirm:
- parent and child direction are correct;
- joint origin is expressed in the parent link frame and places the child frame at zero position;
- non-fixed joint axis is expressed in the joint frame, preferably a signed unit vector along a principal axis;
- positive motion is stated in words in the ledger ("positive shoulder_pitch raises the arm") — the axis sign is part of the model, not a cosmetic detail;
- revolute limits are radians, prismatic limits are meters;
- continuous joints are not given artificial finite lower/upper limits;
- fixed joints are used for frame relationships and rigid assemblies.
Supported joint types may vary by consumer. The bundled validator supports `fixed`, `continuous`, `revolute`, and `prismatic`; do not author `floating` or `planar` joints unless the consumer and validation path support them.
After authoring, the axis-sign check is non-negotiable: sweep the joint in the viewer and compare the motion with the ledger statement. Structural validation cannot catch a flipped sign.
## Collision Geometry
Add collision geometry under each `<link>` that should participate in physics, contact, or collision-aware planning. Do not encode collision behavior on joints.
Use one or more `<collision>` blocks per link. The `<origin>` is expressed in the link frame, just like `<visual>`, and mesh scales must match the units of the exported mesh.
Prefer simplified collision geometry over detailed visual meshes, from simplest to most specific:
- primitive `<box>`, `<cylinder>`, or `<sphere>` geometry when it approximates the part well;
- a coarse, closed collision mesh exported from CAD;
- the visual mesh as a temporary fallback for loading and smoke tests.
## Inertials
For each physical link, use an explicit `inertial` block when the target simulator or dynamics consumer needs mass properties. The inertial origin is the center of mass in the link frame — not automatically the visual mesh origin, collision origin, or link origin.
All values are computed or copied from source data, never freehanded; see `references/inertials.md` for formulas, the mesh-script recipe, and sanity gates.
## Downstream Ownership
- CAD or mesh workflows own mesh generation and per-link splitting/export.
- This skill owns the `.urdf`: references, scales, placements, structure.
- SRDF/MoveIt workflows own semantic groups, named joint poses via `<group_state>`, and planning metadata. Renaming links or joints here breaks them; update both in the same task.
references/validation.md
# URDF Validation and Verification
Every created or modified `.urdf` runs this recipe before the task is reported complete. Validation is a guardrail, not a substitute for the design ledger or a viewer/consumer smoke test: a URDF can pass every structural check while still having incorrect spatial assumptions.
## Recipe
Run in order; stop and fix at the first failing step:
1. **Bundled validator** (always): `cadgen urdf validate path/to/robot.urdf`. It collects *all* findings in one pass (severity, code, XML path); fix them and re-run until clean. Use `--strict` to fail on warnings, `--json` for machine-readable output, and `--packages NAME=PATH` (repeatable) to resolve `package://` mesh URIs.
2. **External URDF tools** (when installed): `check_urdf robot.urdf` (ros liburdfdom) parses with the reference parser and prints the link tree. Report as skipped when unavailable.
3. **Viewer sweep** (whenever `$cad-viewer` is available): load the file, confirm meshes appear at sane scale and pose, then sweep **every** movable joint through its limits and compare the motion against the ledger's positive-motion statement, joint by joint. This is the only step that catches a wrong axis sign.
4. **Consumer smoke test** (when the target runtime is available): RViz display, robot_state_publisher TF tree, Gazebo/Ignition load, or MoveIt model load.
Report which steps ran and which were skipped.
## What the Bundled Validator Checks
Structure:
- root element is `<robot>` with a non-empty name;
- links and joints have unique, non-empty names;
- every joint has parent and child links that exist;
- each child link has at most one parent; exactly one root link; connected, acyclic, exactly `links - 1` joints.
Joints:
- type is `fixed`, `continuous`, `revolute`, or `prismatic` (`floating`/`planar` are rejected — use them only with a consumer-specific validation path);
- origins have three finite values for `xyz`/`rpy` when present;
- movable joints have a nonzero, finite axis; warnings for an omitted axis (spec default `1 0 0`) and non-unit axes;
- revolute/prismatic joints have finite `lower <= upper` limits; `effort`/`velocity` must be non-negative and warn when omitted; fixed/continuous joints warn when they carry ignored position limits;
- `<dynamics>` damping/friction must be non-negative;
- `<mimic>` must reference an existing, non-fixed, non-self joint with no mimic cycles;
- joint names colliding with link names warn (URDF-to-SDF conversion breaks).
Geometry and meshes:
- each visual/collision has exactly one geometry child from `mesh`, `box`, `cylinder`, `sphere`;
- primitive dimensions are positive and finite; mesh `scale` values nonzero and finite (negative scale mirrors the mesh and warns — consumer support varies);
- local mesh paths resolve to existing files relative to the `.urdf`; `package://` and remote URIs pass with a warning because resolution is consumer-specific.
Inertials (when present):
- at most one `<inertial>` per link; `mass` positive and finite; all six tensor values present and finite;
- diagonal values positive; the full tensor must be positive semidefinite (eigenvalue check — catches bad off-diagonals);
- principal moments violating the triangle inequality (`l1 + l2 >= l3`) warn (real-world exports often violate slightly; `--strict` promotes it);
- movable links with geometry but no inertial warn.
Authoring hygiene:
- unknown elements under `<robot>`, `<link>`, `<joint>`, `<visual>`, `<collision>`, and `<inertial>` warn — misspelled elements are otherwise silently ignored by consumers (namespaced extensions like `<gazebo>` pass through);
- visual `<material name>` references without a matching definition warn;
- mesh extensions outside the common set (stl/dae/obj/3mf/glb/gltf/ply) warn.
The validator intentionally does not require inertials or collision geometry on every link — that is target-consumer policy, decided in the ledger (see `references/inertials.md`). When a file fails a *project* policy rather than these checks, report it as policy failure, not URDF invalidity.
## What Validation Cannot Prove
- That a joint origin or axis matches the physical robot — only the ledger plus the viewer sweep checks that.
- That mesh source units match the declared `scale`.
- That inertial values match the actual part, beyond plausibility gates.
- That `package://` URIs resolve in the target environment.
Call these out explicitly in the final report when they were not independently verified.
## Failure Handling
When validation fails: fix the `.urdf` (and the ledger if the modeled facts changed), re-run the validator, and continue the recipe from the top. If the root cause is a bad mesh export, fix it in the owning CAD workflow first — do not paper over asset problems with URDF origins.
requirements.txt
cadgen[snapshot]==0.5.0
SKILL.md
---
name: urdf
description: URDF robot description authoring and validation. Use when creating, editing, inspecting, validating, or debugging `.urdf` files, robot links, joints, limits, inertials, visual/collision geometry, mesh references, frame conventions, or robot-description artifacts. Use the SRDF skill for MoveIt2 semantic groups and IK/path-planning semantics; use the CAD skill for STEP/STL/3MF/DXF/GLB outputs.
---
# URDF
Provenance: maintained in [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad).
Use the installed local skill files as the runtime source of truth; the
repository link is only for provenance and release review.
Use this skill for URDF robot-description outputs. Treat URDF work as constrained kinematic modeling, not just XML writing. The main correctness risks are frame placement, joint-axis semantics, unit consistency, mesh scale, and inertial data.
## Setup
This skill's commands are thin entrypoints over the `cadgen` distribution, which
carries the Python build runtime and the JavaScript it executes. Install it once:
```bash
python -m pip install -r requirements.txt
```
Rendering additionally needs a browser, which pip cannot supply:
```bash
python -m playwright install chromium
```
## Core Rules
1. The `.urdf` file is the source of truth. Author and edit URDF XML directly; do not build a Python generation pipeline for it. There is no `gen_urdf()` contract.
2. Before writing or changing URDF XML, establish the robot's frame, joint, geometry, unit, and assumption ledger and embed it as a comment block at the top of the `.urdf` file. See `references/design-ledger.md`.
3. Use URDF frame semantics exactly. Joint origins, link frames, joint axes, and visual/collision/inertial origins use different reference frames. See `references/frame-semantics.md`.
4. Do not infer spatial transforms, mesh units, handedness, axes, or joint signs from vague prose. Use CAD transforms, dimensioned drawings, measured values, existing source data, or explicit documented assumptions.
5. Never freehand numeric values that are the result of computation — inertia tensors, centers of mass, unit conversions across many links, mirrored transforms. Compute them: closed-form formulas for primitives, or a throwaway helper script for mesh-derived values. See `references/inertials.md`.
6. For physical links, model `inertial`, `visual`, and `collision` separately when the target consumer needs them. Frame-only links may intentionally omit mass and geometry.
7. Validate every created or modified `.urdf` with `cadgen urdf validate` before reporting completion. See `references/validation.md`.
8. Helper scripts are allowed and encouraged for computation, but they are scaffolding, not the artifact's source of truth. For complex or genuinely parametric models it is reasonable to keep a model-local helper script on disk next to related source code (for example STEP generator sources) and note it in the ledger; this is optional, and the checked-in `.urdf` remains canonical.
## CAD Viewer Handoff
After completing URDF work that creates or modifies a `.urdf`, you must ALWAYS hand the explicit file path to `$cad-viewer` when that skill is installed. `$cad-viewer` must start CAD Viewer if it is not already running and return link(s) to the relevant created or updated file(s); if `$cad-viewer` is unavailable or startup fails, report that instead of silently omitting the handoff.
## Workflow
1. Identify the target `.urdf` file and its consumers: RViz, robot_state_publisher, Gazebo/Ignition, MoveIt, a real robot driver, or another simulator.
2. Read or create the design ledger before editing frames, origins, axes, mesh scale, limits, or inertials. Keep the ledger as a comment block in the `.urdf` itself.
3. Prepare mesh assets first when links reference meshes: one mesh per link, exported in that link's frame by the owning CAD/mesh workflow. See `references/meshes.md`.
4. Author or edit the URDF XML directly, following `references/authoring-contract.md` for structure, ordering, and naming.
5. Compute — never guess — inertials and other derived numbers. See `references/inertials.md`.
6. Validate with `cadgen urdf validate`; fix findings and re-validate until clean.
7. Run the verification recipe in `references/validation.md`: external tools when available (`check_urdf`), then a viewer review sweeping every joint.
8. Report remaining assumptions, unchecked spatial data, and validation gaps.
## Commands
Run with the Python environment for the project or workspace. Treat `python` in examples as an interpreter placeholder; if bare `python` is unavailable, substitute `python3`, a project virtualenv interpreter, or the configured interpreter path. The validator uses only the Python standard library.
The validator shape is:
```bash
cadgen urdf validate path/to/robot.urdf
cadgen urdf validate path/to/robot.urdf --strict
cadgen urdf validate path/to/robot.urdf --json
cadgen urdf validate path/to/robot.urdf --packages robot_description=/path/to/pkg
cadgen urdf snapshot path/to/robot.urdf review.png
```
The validator collects all findings in one pass (severity, code, XML path) across XML structure, tree topology, joint semantics (limits, mimic, dynamics), geometry, mesh references, materials, inertial physics, and misspelled elements, and prints a summary. One run validates ONE file: `--strict` treats warnings as failures; `--json` emits the machine-readable findings document; `--packages NAME=PATH` resolves `package://` mesh URIs and repeats for several roots. It exits nonzero if the target fails. Relative targets resolve from the current working directory; run from the workspace that owns the files.
Validation is a guardrail, not spatial proof: a URDF can pass every structural check while placing a joint in the wrong spot. The ledger and viewer sweep exist for that reason.
## Snapshot Tool
`cadgen urdf snapshot` renders the robot to a PNG still, using the same shared
CLI and headless browser runtime every rendering skill uses — so a snapshot matches what
the CAD Viewer shows.
```bash
cadgen urdf snapshot path/to/robot.urdf review.png
```
It accepts `.urdf` only. Pose the robot with `--joint-values` — `{joint: degrees}` JSON,
joints you do not name staying at the rest pose (the `"jointValues"` job field is the same
thing in a packet). Robots are authored in metres and are framed on the robot scene scale
automatically.
Theme settings live under one `--theme`, mirroring the viewer's Theme tab. The default
theme is `snapshot` — Workbench Light with the ground grid, origin axis and shadows
removed, because in a still image those read as geometry. There is no `--display` on this
door: display settings (mode, clip, exploded, edges) are CAD topology settings, and a robot
carries none.
Link meshes are resolved relative to the description, so they must be present: an
unhydrated Git LFS pointer fails as "No link mesh loaded for robot". Run
`git lfs checkout <mesh dir>` first.
The grammar is `cadgen urdf snapshot TARGET [OUT] [flags]`, the same one every
format door uses. Use `cadgen urdf snapshot --help` for the complete current
interface — the flags a robot cannot act on are absent from it, not refused by it.
## References
- Authoring contract (structure, ordering, golden skeleton): `references/authoring-contract.md`
- Design ledger: `references/design-ledger.md`
- Frame semantics: `references/frame-semantics.md`
- Mesh preparation and references: `references/meshes.md`
- Inertials (formulas, scripts, sanity gates): `references/inertials.md`
- URDF edit workflow: `references/urdf-workflow.md`
- Validation and verification recipe: `references/validation.md`