agents/openai.yaml
interface:
display_name: "SDF"
short_description: "Author and validate SDFormat robot models."
default_prompt: "Use $sdf to author, update, and validate SDFormat/SDF robot or simulator models as direct XML, handing new or changed SDFs 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/design-ledger.md
# SDF design ledger
Create or update this ledger before writing SDF XML. The ledger's canonical home is a comment block at the top of the `.sdf` file itself, optionally expanded in an adjacent note for large worlds. The goal is to externalize spatial and simulator assumptions before they become hard-to-audit XML.
## Document
| Field | Value |
|---|---|
| SDF path | |
| SDF version | `1.12` unless constrained |
| Document kind | model / world / model-in-world |
| Target consumer | Gazebo / other simulator / visualization-only / model package |
| Units | meters, kilograms, seconds, radians unless documented otherwise |
| Coordinate convention | REP-103-like / simulator-specific / documented exception |
| World support needed | yes/no; reason |
| Optional external checks | `gz sdf --check`, simulator load, CAD Viewer, other |
## Model or world scope
| Item | Value |
|---|---|
| Model/world name | |
| Static or dynamic | |
| Canonical link, if relevant | |
| Model/world pose | xyz + rpy/quaternion |
| Model/world pose `relative_to` | |
| Includes | URI + purpose |
| World physics/lights/plugins | source and target simulator |
## Frames
| Frame | Scope | Attached to | Pose | Pose `relative_to` | Purpose | Source |
|---|---|---|---|---|---|---|
| | | | | | | |
Use named frames for clarity when multiple sensors, nested models, tool frames, plugin frames, or repeated transforms depend on the same relationship.
## Links
| Link | Physical / frame-like | Pose | Pose `relative_to` | Inertial source | Sensor/plugin attached | Notes |
|---|---|---|---|---|---|---|
| | | | | | | |
Physical dynamic links need inertials. Frame-like links may omit inertials only when documented.
## Joints
| Joint | Type | Parent | Child | Pose | Pose frame / `relative_to` | Axis | Axis frame / `expressed_in` | Limits | Positive motion | Source |
|---|---|---|---|---|---|---|---|---|---|---|
| | | | | | | | | | | |
For revolute and prismatic joints, record limit units: radians for revolute, meters for prismatic. Continuous joints should not be given artificial finite position limits unless a simulator-specific reason is documented.
## Geometry
| Owner | Visual/collision | Name | Geometry type | Pose | Pose `relative_to` | URI or dimensions | Mesh units | Scale | Source |
|---|---|---|---|---|---|---|---|---|---|
| | | | | | | | | | |
Collision geometry should be selected for simulation cost and stability, not just visual similarity.
## Inertials
| Link | Mass | COM pose | Inertia tensor | Method/source | Confidence |
|---|---|---|---|---|---|
| | | | | | |
Mark approximations clearly. Very small, zero, negative, or guessed inertias are high risk for simulation.
## Sensors and plugins
| Element | Parent | Pose/frame | Filename/type | Parameters | Source docs | Assumptions |
|---|---|---|---|---|---|---|
| | | | | | | |
Do not invent plugin filenames, topics, frame names, namespaces, controller parameters, or update rates. Derive them from simulator documentation or user-provided configuration.
## Mesh URI policy
| URI kind | Allowed? | Resolution expectation | Notes |
|---|---|---|---|
| Relative local path | | Relative to the `.sdf` file location | |
| `file://` | | Absolute local file | |
| `model://` | | Simulator model path | |
| `package://` | | Simulator/ROS package environment | |
| `fuel://`, `http://`, `https://` | | External resource | |
## Assumptions to report
List every guessed or inferred value:
- transform or pose;
- axis sign or positive-motion convention;
- mesh unit or scale;
- mass, COM, or inertia;
- target simulator behavior;
- plugin parameter;
- unresolved external URI;
- skipped validation or smoke test.
If a value cannot be derived or safely assumed, generate a minimal placeholder only when the user asked for a placeholder, and label it as such.
## Compact response template
```text
SDF file: path/to/model.sdf
Target consumer: Gazebo Harmonic, SDF 1.12
Bundled validation: passed with 2 warnings
External checks: gz sdf --check skipped, gz not installed
Assumptions:
- Assumed mesh units are meters.
- Assumed camera optical frame follows simulator plugin documentation.
```
references/examples.md
# SDF examples
These examples illustrate the intended authoring style: explicit `relative_to` frames, a ledger comment block, computed inertials with their formula named, and structured assumptions.
## Minimal model
```xml
<?xml version="1.0"?>
<!--
model: calibration_box | consumer: Gazebo Harmonic (SDF 1.12)
units: meters, kilograms, radians
inertials: uniform-density solid-box formula, m=1.0 kg
assumptions: uniform density; box is rigid
-->
<sdf version="1.12">
<model name="calibration_box">
<link name="body">
<inertial>
<mass>1.0</mass>
<!-- solid box 0.1^3: ixx=iyy=izz=m(a^2+a^2)/12 -->
<inertia>
<ixx>0.0016666667</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.0016666667</iyy>
<iyz>0</iyz>
<izz>0.0016666667</izz>
</inertia>
</inertial>
<visual name="body_visual">
<geometry>
<box><size>0.1 0.1 0.1</size></box>
</geometry>
</visual>
<collision name="body_collision">
<geometry>
<box><size>0.1 0.1 0.1</size></box>
</geometry>
</collision>
</link>
</model>
</sdf>
```
## Minimal world
```xml
<?xml version="1.0"?>
<!--
world: empty_lit_world | consumer: Gazebo Harmonic (SDF 1.12)
world intentionally contains no inline model
-->
<sdf version="1.12">
<world name="empty_lit_world">
<light name="sun" type="directional">
<pose relative_to="world">0 0 10 0 0 0</pose>
<cast_shadows>true</cast_shadows>
</light>
</world>
</sdf>
```
## Two-link model with an explicit joint frame
Note the explicit `relative_to` on every nontrivial pose, and the joint axis with explicit `expressed_in`:
```xml
<?xml version="1.0"?>
<!--
model: two_link_demo | consumer: Gazebo Harmonic (SDF 1.12)
frames: base_link at model origin; arm_link placed by shoulder_pan joint
inertials: solid-box formulas at stated masses (base 2.0 kg, arm 0.5 kg)
assumptions: positive shoulder_pan rotates arm counterclockwise viewed from +Z
-->
<sdf version="1.12">
<model name="two_link_demo">
<link name="base_link">
<inertial>
<mass>2.0</mass>
<!-- solid box 0.4 x 0.3 x 0.1 -->
<inertia>
<ixx>0.0166667</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>0.0283333</iyy><iyz>0</iyz>
<izz>0.0416667</izz>
</inertia>
</inertial>
<visual name="base_visual">
<geometry><box><size>0.4 0.3 0.1</size></box></geometry>
</visual>
<collision name="base_collision">
<geometry><box><size>0.4 0.3 0.1</size></box></geometry>
</collision>
</link>
<link name="arm_link">
<pose relative_to="shoulder_pan">0 0 0 0 0 0</pose>
<inertial>
<mass>0.5</mass>
<!-- solid box 0.3 x 0.05 x 0.05 -->
<inertia>
<ixx>0.000208333</ixx><ixy>0</ixy><ixz>0</ixz>
<iyy>0.00385417</iyy><iyz>0</iyz>
<izz>0.00385417</izz>
</inertia>
</inertial>
<visual name="arm_visual">
<geometry><box><size>0.3 0.05 0.05</size></box></geometry>
</visual>
<collision name="arm_collision">
<geometry><box><size>0.3 0.05 0.05</size></box></geometry>
</collision>
</link>
<joint name="shoulder_pan" type="revolute">
<pose relative_to="base_link">0 0 0.05 0 0 0</pose>
<parent>base_link</parent>
<child>arm_link</child>
<axis>
<xyz expressed_in="base_link">0 0 1</xyz>
<limit>
<lower>-1.5708</lower>
<upper>1.5708</upper>
</limit>
</axis>
</joint>
</model>
</sdf>
```
This uses the SDF 1.8+ frame-graph pattern: the joint pose is expressed relative to the parent link, and the child link's pose is `relative_to` the joint frame. That mirrors URDF semantics and makes URDF-derived SDF mechanical to audit.
## Plugin block from documentation
When a plugin block is copied from target simulator documentation, preserve its explicit parameters and cite the source in a comment:
```xml
<!-- Source: gz-sim diff_drive tutorial (Harmonic docs). Unverified in target env. -->
<plugin name="example_control" filename="libexample_control.so">
<namespace>robot1</namespace>
</plugin>
```
Do not invent plugin fields. If the documentation source is not available, mark the plugin as unverified in the ledger and the final report.
references/frame-semantics.md
# SDF frame and pose semantics
Use this reference before editing any SDF `<pose>`, `<frame>`, `<joint>`, `<axis>`, `<visual>`, `<collision>`, sensor, or plugin placement.
## Core pose rules
A typical SDF pose is:
```xml
<pose relative_to="some_frame">x y z roll pitch yaw</pose>
```
or, when using quaternion rotation:
```xml
<pose rotation_format="quat_xyzw" relative_to="some_frame">x y z qx qy qz qw</pose>
```
Rules to keep in mind:
- The first three values are position.
- With the default `rotation_format="euler_rpy"`, the pose has six values: `x y z roll pitch yaw`.
- With `rotation_format="quat_xyzw"`, the pose has seven values: `x y z qx qy qz qw`.
- Euler angles are radians by default. `degrees="true"` is valid SDF but should be avoided unless the target explicitly requires it.
- `relative_to` names the frame in which the pose is expressed.
- If `relative_to` is omitted, SDF applies element-specific defaults, commonly the frame of the parent XML element. This may be valid but is easy to misread. Prefer explicit `relative_to` for every nontrivial pose.
- Nested scopes may use `::`, for example `outer_model::inner_model::sensor_frame`.
## Joint pose and axes
For SDF joints:
- `<parent>` names the parent frame or `world`.
- `<child>` names the child frame; `world` is not valid as the child.
- Joint pose defaults are easy to misinterpret. Use explicit `<pose relative_to="...">` when the joint frame is not obviously the child-link frame.
- `<axis><xyz>...</xyz></axis>` is the unit axis vector.
- An axis is expressed in the joint frame unless the axis `expressed_in` attribute specifies another frame.
- `axis2` is used for multi-axis joints such as `revolute2` and `universal`.
- Axis vectors should be finite, nonzero, and normalized.
Record the expected positive motion in the design ledger. Example: “positive shoulder_pan rotates the arm counterclockwise when viewed from +Z.”
## Visual and collision poses
A `<visual>` or `<collision>` pose places that geometry owner relative to its parent frame unless `relative_to` says otherwise. In ordinary model-level use, that parent is the link frame.
Do not use visual offsets to hide a wrong link or joint frame. If a mesh needs an offset because the mesh asset origin is not the link frame, record that fact in the geometry table.
## Named frames
Use `<frame>` when a reusable transform is meaningful:
```xml
<frame name="camera_optical_frame" attached_to="camera_link">
<pose relative_to="camera_link">0 0 0 -1.57079632679 0 -1.57079632679</pose>
</frame>
```
Frames are useful for sensors, plugins, tool frames, nested models, and repeated placement logic. They also make SDF more auditable.
`attached_to` and `relative_to` are different:
- `attached_to` says what the frame moves with.
- `relative_to` says how the frame's pose numbers are represented.
The `attached_to` chain should not cycle and should eventually resolve to a link, model, world, joint, or another valid frame target.
## LLM guardrails
Do not infer any of the following from prose alone:
- sign of a joint axis;
- frame in which an axis is expressed;
- RPY order or units;
- mesh origin convention;
- `relative_to` frame;
- nested-scope reference;
- sensor optical-frame transform;
- plugin frame/topic semantics.
When data is missing, either ask for the source data or write an explicitly labeled assumption.
## Useful official references
- SDFormat pose semantics: `https://sdformat.org/tutorials?tut=pose_frame_semantics`
- SDFormat pose fields: `https://sdformat.org/spec/1.12/world/`
- SDFormat joint element: `https://sdformat.org/spec/1.12/joint/`
references/interoperability.md
# SDF interoperability notes
Use this reference when SDF work touches upstream geometry, robot-description data, Gazebo/libsdformat, model packages, or CAD Viewer.
## Geometry assets
SDF should reference geometry and mesh assets; it should not regenerate them.
When SDF references exported mesh assets, record:
- source geometry file;
- exported mesh path;
- mesh unit convention;
- mesh origin convention;
- visual scale;
- collision simplification decision.
Regenerate geometry and mesh artifacts with their owning workflow before regenerating SDF if geometry changed.
## Robot descriptions
Keep the simulator document aligned with the upstream robot-description source when one exists.
**Derive, don't re-author.** When a robot already has a URDF, the SDF model for that robot is derived from it: same link/joint names, same tree, same limits, same inertials, same mesh assets. Translate mechanically — URDF joint `<origin>` becomes the SDF joint `<pose relative_to="parent_link">`, and each child link gets `<pose relative_to="joint_name">0 0 0 0 0 0</pose>` — or use `gz sdf -p robot.urdf` as a starting point when Gazebo tooling is available, then review its output against this skill's contract. Independently re-authoring the same robot in SDF creates divergence that no validator catches. Record the source URDF (path and revision) in the SDF ledger comment.
Upstream robot-description data usually owns:
- link and joint structure used by robot-state publishing;
- physical joint limits;
- inertials and visual/collision geometry when that source is authoritative;
- control-related structure and runtime interfaces.
Use SDF for simulator/world concerns:
- simulator plugins;
- sensors requiring simulator-specific XML;
- surfaces/contact/friction;
- lights, terrain, physics, and worlds;
- nested models and includes;
- simulator-specific metadata.
Do not use SDF to paper over a wrong upstream frame tree unless the task explicitly targets a simulator-only model.
## Planning metadata
SDF should not define planning groups, end-effectors, group states, or disabled-collision matrices. If the task becomes IK or path-planning work, use the planning metadata workflow that owns those semantics.
## CAD Viewer
CAD Viewer can review `.sdf` files visually through `$cad-viewer` and help catch gross placement or resource issues. It cannot prove simulator dynamics, inertial validity, plugin loading, sensor topics, or joint-axis semantics.
Pass explicit new or modified `.sdf` paths to `$cad-viewer` whenever it is available, and return the live viewer link it prints.
CAD Viewer renders SDF as static structure plus direct inspection controls. It lists plugins, sensors, lights, includes, and nested models as metadata, but does not execute plugins or consume file-authored motion contracts.
## Gazebo / libsdformat
The bundled validator is a lightweight preflight check. Use the target simulator's parser and loader when compatibility matters.
Good checks include:
```bash
gz sdf --check path/to/model.sdf
```
and a real simulator load in the target environment.
## Model packages and URIs
SDF resource resolution is environment-dependent. Record which URI forms the target consumer can resolve:
- relative paths from the `.sdf` file location;
- `model://` paths under the simulator model path;
- `package://` paths under ROS/package resolution;
- `fuel://` resources;
- `http://` or `https://` assets if external fetches are allowed.
The bundled validator can confirm local relative paths, but it cannot prove external simulator resource paths unless the target environment is available.
references/llm-guardrails.md
# LLM guardrails for SDF authoring
This skill assumes agents are useful at structuring SDFormat documents and weak at silently deriving precise spatial, physical, and simulator-specific values. The workflow should route those weaknesses into explicit ledgers, constants, helpers, validators, and smoke tests.
## What agents can usually do well
- organize an SDF model or world into links, joints, frames, visuals, collisions, sensors, plugins, and includes;
- translate user intent into a plausible document structure;
- maintain naming consistency when names are explicit;
- write small throwaway Python scripts for derived numbers and transformations;
- explain assumptions and create checklists;
- preserve existing patterns when examples are nearby.
## What agents should not be trusted to infer silently
- exact link poses, frame transforms, or joint origins;
- positive joint-axis directions from visual theme;
- mesh units, mesh scale, or coordinate-system conventions;
- center of mass or inertia tensors from rendered shape alone;
- plugin filenames, parameters, topics, namespaces, or sensor schemas;
- whether a plugin is a simulator runtime plugin or a CAD Viewer visualization-only extension;
- target simulator support for a given SDFormat version or extension;
- whether collision geometry is stable for physics;
- whether external URIs resolve in the deployment environment.
## Required mitigation pattern
For every spatial, physical, or simulator-specific value, use one of these sources:
1. user-provided requirement;
2. upstream geometry, robot-description, planning-metadata, mesh manifest, or model package source;
3. target simulator documentation;
4. measured or calculated value with method stated;
5. explicit assumption recorded in the ledger comment block and the final report.
Do not hide guessed values in raw XML: every non-obvious number carries a comment or a ledger line naming its source.
## Placeholder policy
Placeholders are allowed only when the user asks for a scaffold, draft, or minimal example. Mark them as placeholders and keep them easy to replace.
Examples of acceptable placeholders:
```xml
<!-- placeholder_inertial: primitive approximation pending measured mass properties -->
<inertial>
<mass>0.5</mass>
...
</inertial>
```
Examples of unacceptable placeholders:
- invented plugin filenames;
- adding CAD Viewer-only motion plugins to SDF files;
- arbitrary inertia values on a dynamic robot without a warning;
- guessed mesh scale that makes the visual look plausible;
- silently flipping a joint axis to match an expected screenshot.
## Spatial reasoning checklist
Before generating or modifying SDF, answer these questions in the ledger or final report:
| Question | Required evidence |
|---|---|
| What frame is each pose expressed in? | `relative_to`, source file, or documented default |
| What frame is each joint axis expressed in? | `expressed_in` or documented default |
| What is positive motion for each non-fixed joint? | command/test expectation or upstream source |
| Are mesh units and scales known? | manifest, CAD export config, or explicit assumption |
| Are visual and collision poses intentionally different? | simulation reason or source geometry |
| Are inertials measured, calculated, approximated, or omitted? | method and confidence |
| Are plugin and sensor parameters copied from target docs? | target simulator/version and source |
## Authoring style
Prefer this pattern:
```xml
<!-- Source: project CAD frame export 2026-05-12. RPY radians. -->
<pose relative_to="base_link">0.18 0 0.12 0 -0.2 0</pose>
```
Avoid this pattern:
```xml
<pose>0.18 0 .12 0 -11.5 0</pose>
```
The second version omits the frame, uses degrees without saying so, and makes the source of the transform impossible to audit.
## Validation expectations
The validator should catch cheap deterministic mistakes, but it cannot prove the design is physically or simulator-correct. After bundled validation, use optional external checks and simulator smoke tests when the task depends on simulator behavior.
Report skipped checks explicitly. A skipped check is not automatically a failure, but it is relevant risk information.
## Response behavior for agents
When finishing an SDF task, state:
- the `.sdf` path(s) created or modified;
- checks run and their result;
- checks skipped and why;
- assumptions and placeholders;
- risks that need simulator verification.
Do not simply say that the file is valid. Say which validator or smoke test passed.
references/sdf-workflow.md
# SDF workflow
Use this reference when editing SDF robot model structure, world structure, mesh references, or simulator metadata.
## Edit loop
1. Locate the target `.sdf`. It is the source of truth; author and edit the XML directly.
2. Identify the target consumer and required SDFormat version.
3. Decide whether the output is model-level, world-level, or model-in-world.
4. Fill or update the design ledger before writing XML; keep the compact form as a comment block in the `.sdf` (see `references/design-ledger.md`).
5. If the model describes a robot that already has a URDF, derive the SDF from that URDF rather than re-authoring geometry from scratch (see `references/interoperability.md`).
6. For every pose and axis, state the frame in which it is expressed. Write `relative_to` / `expressed_in` explicitly wherever ambiguity would otherwise remain (see `references/frame-semantics.md`).
7. Author the XML per the golden skeletons in `references/examples.md`. Compute derived numbers — inertia tensors, unit conversions — with formulas or a throwaway helper script; never freehand them.
8. Validate with `cadgen sdf validate <file.sdf>`; review errors as structural guardrails, not exhaustive simulator proof.
9. Hand new or modified `.sdf` files to `$cad-viewer` for live viewer links when available.
10. Run available smoke tests (`gz sdf --check`, simulator load).
11. Report assumptions and skipped checks.
## Model vs world
Use **model-level SDF** when exporting a reusable robot or object model that another world can include.
Use **world-level SDF** when the task includes:
- physics engine settings;
- lights or scene setup;
- terrain or ground plane;
- multiple initial model placements;
- world plugins;
- includes of external model packages;
- simulator scene setup.
Use **model-in-world SDF** when the task explicitly needs both an inline model and world-specific context.
The lightweight validator should allow pure world-only documents. A world-only document with lights, physics, actors, or includes can be valid SDFormat even when it contains no inline `<model>`.
## Mesh references
SDF mesh URIs should be stable from the `.sdf` file's perspective or use a simulator/package URI convention understood by the consumer.
Good URI choices include:
- relative paths beside the SDF when the model is self-contained;
- `model://...` for simulator model packages;
- `package://...` when the simulator environment resolves package roots;
- `fuel://...`, `http://...`, or `https://...` only when the consumer is expected to fetch external assets.
Mesh assets themselves are owned by the CAD/mesh workflow: one asset per link, exported in the link's own frame, with source units recorded. If a mesh is wrong, fix the export, not the SDF poses.
## Inertials and physics
For dynamic models, inertial data is simulation-critical. If inertials are estimated, record the approximation method. Do not copy visual origins into inertial origins unless that is physically justified.
Collision geometry should be selected for stable and fast physics, not visual fidelity. Use primitive or simplified collision geometry when possible.
## Plugins and sensors
For plugins and sensors, record:
- plugin filename or sensor type;
- expected simulator distribution/version;
- topics, frames, update rates, namespaces;
- parameter source;
- startup smoke test result.
Do not invent plugin parameters. Incorrect plugin XML can pass lightweight validation and still fail at simulator load time.
CAD Viewer reviews SDF files as static model/world structure through `$cad-viewer` links. Do not add Explorer-only motion plugins; use simulator-native controllers, plugins, or test harnesses for simulator behavior.
## Existing SDF inspection
When inspecting existing `.sdf` files, separate three questions:
1. Is the XML structurally valid enough for the bundled validator?
2. Is it compatible with the target SDFormat/libsdformat/simulator version?
3. Does it satisfy this project's packaging, mesh, and workflow policy?
Do not reject valid SDF solely because it violates a project preference unless the task or repository policy requires that preference.
references/smoke-tests.md
# SDF smoke tests
Use smoke tests after the SDF passes bundled validation. The goal is to catch simulator and spatial failures that dependency-light XML checks cannot detect.
## Recommended checks
### Bundled validation
```bash
cadgen sdf validate path/to/model.sdf
cadgen sdf validate path/to/model.sdf --strict
```
Bundled validation runs during explicit target generation. Use `--strict` when warnings should block handoff.
### SDFormat parser check
When Gazebo tooling is installed:
```bash
gz sdf --check path/to/model.sdf
```
or through the skill CLI:
```bash
cadgen sdf validate path/to/model.sdf --gz-check auto
```
Use the exact simulator environment that will consume the file when possible.
### Simulator load check
Load the model or world in the target simulator and check:
- no parser warnings or plugin load errors;
- model appears at the intended pose;
- visual and collision assets resolve;
- collision geometry is not visibly offset from visuals;
- dynamic model does not explode, fall through the floor, or produce invalid inertia warnings.
### Joint motion check
For each non-fixed joint:
- command a small positive motion;
- confirm the moving child moves in the expected direction;
- confirm limits stop motion where expected;
- confirm continuous joints can rotate continuously if intended.
### CAD Viewer static review
After generating or modifying an `.sdf`, hand the explicit path to `$cad-viewer` for a live viewer link when available.
- confirm direct model links, joints, frames, visuals, and collisions are placed correctly;
- confirm includes, plugins, sensors, lights, nested models, and unsupported geometry are listed as static metadata;
- record any simulator-only behavior that CAD Viewer cannot execute.
### Sensor and plugin check
For each sensor or plugin:
- confirm plugin library loads;
- confirm expected topics/services appear;
- confirm frame names match the design ledger;
- confirm update rate and namespace behavior;
- capture one sample output if practical.
### Visual review
When CAD Viewer or an equivalent viewer is available through `$cad-viewer`, return the viewer link. Visual review is useful but insufficient: it can catch gross placement and mesh problems, but it cannot prove axis frames, inertials, dynamics, or plugin behavior.
## Report format
Use a compact report:
```text
Checks run:
- bundled SDF validation: passed
- gz sdf --check: skipped, gz not installed
- simulator load: passed in Gazebo Harmonic
- joint motion: shoulder_pan positive motion verified; gripper joints skipped
- plugin startup: camera plugin unresolved, requires target simulator package
Assumptions:
- Assumed mesh units are meters.
- Assumed lidar frame is coincident with lidar_link.
```
## When to stop
Stop and fix the SDF (or its upstream assets) when:
- bundled validation has errors;
- `gz sdf --check` fails under a required external-check policy;
- the simulator reports invalid inertias or unresolved required assets;
- a joint moves opposite from the documented positive direction;
- plugin startup fails for a plugin required by the task.
references/validation.md
# SDF validation
Every created or modified `.sdf` is validated with `cadgen sdf validate <file.sdf>` before the task is reported complete. The validator collects all findings in one pass (severity, code, XML path); `--strict` fails on warnings and `--json` emits a machine-readable document. The bundled validation is dependency-light and intended to catch common structural errors. It is not a replacement for libsdformat, Gazebo, or target-simulator validation.
## Validation model
The validator should produce structured diagnostics with severities:
- `error`: invalid or unsafe enough to block writing output;
- `warning`: likely problem or unverified simulator behavior; output can be written unless `--strict` is used;
- `info`: assumption, skipped check, or useful context.
`--strict` treats warnings as failures.
## Bundled checks
### Root and document shape
The validator should check that:
- the root element is `<sdf>`;
- the root has a non-empty `version` attribute;
- the version looks like `major.minor`;
- the document contains meaningful SDF content such as a model, world, actor, light, include, or plugin;
- structurally valid pure world files are accepted even when they contain no inline model.
### Names and scopes
The validator should check that:
- world names are non-empty and unique at root scope;
- root model names are non-empty and unique;
- model link, joint, frame, sensor, light, visual, and collision names are non-empty where required and unique within their owner scope;
- links, joints, frames, and nested models share one frame-graph namespace per scope: cross-type name collisions are errors;
- a model with no links, includes, or nested models is an error;
- unknown elements under model/link/joint/visual/collision/inertial warn (misspelled elements are otherwise silently ignored);
- the version must be a known SDFormat release (1.4–1.12) or it warns;
- world- and link-level lights need a valid type (`point`/`directional`/`spot`) and validated poses;
- duplicate names are reported with a path and scope.
### Poses
The validator should check all `<pose>` elements:
- default `rotation_format="euler_rpy"` has exactly six finite values;
- `rotation_format="quat_xyzw"` has exactly seven finite values;
- unsupported `rotation_format` is an error;
- quaternion values are approximately normalized;
- `degrees="true"` is a warning unless strict mode is enabled;
- nontrivial omitted `relative_to` is a warning;
- `relative_to` resolves within local scope when possible;
- nested `::` references have valid syntax and resolve when the local tree is available.
### Frames
The validator should check that:
- `<frame name="...">` has a non-empty unique name in its scope;
- `attached_to`, when present, resolves locally when possible;
- frame attachment chains do not cycle;
- unresolved nested or external frame references are reported as warnings when local validation cannot prove them invalid.
### Joints
Known SDF 1.12 joint types:
```text
continuous, revolute, gearbox, revolute2, prismatic, ball, screw, universal, fixed
```
The validator should check that:
- joint type is non-empty and known;
- `<parent>` and `<child>` text exists;
- `world` is allowed as parent but not child;
- unscoped parent/child references exist in the same model;
- `axis` and `axis2` vectors are finite, nonzero, and normalized;
- `axis2` is used only where the joint type supports a second axis;
- `expressed_in` resolves when local resolution is possible;
- limit and dynamics values are finite or documented infinities where SDFormat permits them;
- effort/velocity/stiffness/dissipation must be non-negative (`-1` is accepted as the unlimited sentinel for effort/velocity); axis `<dynamics>` damping/friction must be non-negative;
- finite lower limits do not exceed finite upper limits;
- continuous joints with fake finite position limits produce a warning.
### Geometry and mesh URIs
The validator should check that:
- each visual/collision owner has one geometry element;
- each geometry has exactly one known primitive or mesh child when possible;
- box size has 3 positive finite values;
- cylinder radius and length are positive and finite;
- sphere radius is positive and finite;
- plane size has 2 positive finite values;
- mesh URI values are non-empty;
- mesh scale has 3 nonzero finite values when present (negative scale mirrors the mesh and warns — consumer support varies);
- local mesh references resolve relative to the `.sdf` file's location;
- known external URI schemes such as `model://`, `package://`, `fuel://`, `http://`, and `https://` are accepted without local filesystem resolution.
### Inertials
The validator should check that:
- mass is positive and finite;
- inertial pose is valid when present;
- inertia tensor components are finite;
- inertia matrix is positive semidefinite within tolerance; principal moments violating the triangle inequality warn;
- missing inertial data on dynamic physical links is at least a warning;
- frame-like or static links can omit inertials when documented.
### Sensors and plugins
The validator should check that:
- sensor names are non-empty and unique within owner scope;
- sensor `type` is non-empty and from the known SDFormat sensor-type list (unknown types warn);
- sensor `update_rate`, when present, is finite and non-negative;
- sensor pose is valid;
- plugin filename is non-empty;
- plugin name, when present, is non-empty;
- arbitrary simulator-specific plugin schemas are not invented by the validator.
Plugin filenames and parameters can pass bundled validation and still fail in the target simulator. Use smoke tests.
### CAD Viewer review
CAD Viewer treats SDF plugins, sensors, lights, includes, and nested models as static metadata. The bundled validator checks generic structure only; it does not validate Explorer-only motion contracts or execute simulator plugins.
After `.sdf` files are created or modified, hand explicit paths to `$cad-viewer` for live viewer links when available.
This plugin is for CAD Viewer visualization and review. It is not a Gazebo physics/controller plugin and should not be represented as simulator runtime behavior.
## External checks
When Gazebo tooling is available, run:
```bash
gz sdf --check path/to/file.sdf
```
The CLI option should be:
```bash
cadgen sdf validate path/to/file.sdf --gz-check auto
```
External checks should be recorded in the diagnostics report. A skipped optional check is not a bundled-validation failure unless the user requested `--gz-check required`.
## SDF validity vs project policy
Separate these categories:
| Category | Examples |
|---|---|
| SDF structural validity | root `<sdf>`, version, legal element shape, non-empty names, references |
| Numeric plausibility | finite poses, positive dimensions, positive mass, normalized axes, PSD inertia |
| Simulator compatibility | libsdformat version, supported joint types, plugin availability, sensor support |
| Project policy | mesh location, preferred URI style, STL/DAE preference, collision simplification, no unresolved external URIs |
Do not reject valid SDF merely because it violates a project policy unless the task or repository requires that policy. Prefer warnings and strict-mode controls.
requirements.txt
cadgen[snapshot]==0.5.0
SKILL.md
---
name: sdf
description: SDFormat/SDF model and world authoring, validation, and simulator handoff. Use for `.sdf` files, SDFormat XML, models, worlds, links, joints, poses, frames, inertials, visual/collision geometry, mesh URIs, sensors, lights, physics, plugins, includes, Gazebo, static SDF review, or simulator-specific metadata. Do not use for signed-distance-field geometry.
---
# SDF
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 when the deliverable is an SDFormat document. SDFormat describes simulator and world behavior: models, worlds, frames, poses, links, joints, inertials, visuals, collisions, sensors, lights, physics, plugins, includes, and simulator metadata.
This skill is for **SDFormat**, not signed-distance-field geometry.
The `.sdf` file is the source of truth: author and edit the XML directly. There is no `gen_sdf()` contract.
## 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. Author `.sdf` XML directly and validate every created or modified file with `cadgen sdf validate` before reporting completion.
2. Identify the target consumer before editing: Gazebo/libsdformat version, another simulator, visualization-only tooling, model package, or world handoff.
3. Decide document kind: model-level SDF, world-level SDF, or model-in-world. Prefer model-level SDF for reusable robot/object exports.
4. Use SI units unless the target explicitly requires otherwise: meters, kilograms, seconds, radians.
5. Prefer `version="1.12"` for new outputs unless the target consumer constrains the version.
6. Establish the design ledger before writing poses, frames, joint axes, mesh scales, inertials, sensors, or plugins, and keep it as a comment block at the top of the `.sdf`. Use `references/design-ledger.md` and `references/llm-guardrails.md`.
7. Write `relative_to` / `expressed_in` explicitly on every nontrivial pose and axis. Implicit frame defaults are the top SDF failure mode. See `references/frame-semantics.md`.
8. Do not infer spatial transforms from visual impression alone. Derive poses, axes, scale, mass, inertia, and frame names from upstream source data, drawings, simulator documentation, measured values, or explicit assumptions. Never freehand computed numbers — use formulas or a throwaway helper script (inertia tensors, unit conversions).
9. When the robot already has a URDF, derive the SDF from it instead of re-authoring geometry; see `references/interoperability.md`.
10. Regenerate upstream geometry, mesh, robot-description, render, topology, or package assets with their owning workflows before editing SDF that references them.
11. After authoring, run available checks: bundled validation, optional `gz sdf --check`, simulator load, joint motion, and plugin/sensor startup.
12. Report assumptions, skipped checks, unresolved resource paths, and target-specific compatibility risks.
## Scope
Use this skill for SDFormat outputs. Do not use it for signed-distance-field modeling, raw geometry generation, planning semantics, or to paper over incorrect upstream robot/source data unless the task is explicitly simulator-only.
## CAD Viewer Handoff
After completing SDF work that creates or modifies a `.sdf`, 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. Locate the target `.sdf` and its consumers.
2. Read or create the design ledger comment block.
3. Read `references/frame-semantics.md` before editing any `<pose>`, `<frame>`, joint axis, `relative_to`, `expressed_in`, nested scope, sensor frame, or plugin frame.
4. Author the XML directly, following the worked examples in `references/examples.md`.
5. Validate the explicit target with `cadgen sdf validate`; treat bundled validation as a guardrail, not simulator proof.
6. Run target-consumer smoke tests when available (`references/smoke-tests.md`).
7. Hand the file to `$cad-viewer`. Static rendering does not execute SDF plugins or read file-authored motion metadata.
8. Report checks run, checks skipped, and assumptions.
## Commands
Run with the project or workspace Python environment. 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.
```bash
cadgen sdf validate path/to/model.sdf
cadgen sdf validate path/to/model.sdf --strict
cadgen sdf validate path/to/model.sdf --json
cadgen sdf snapshot path/to/model.sdf review.png
```
The validator checks document shape, name scopes, pose/frame graphs, joints, geometry, mesh URIs, inertials, sensors, and plugins, and prints its findings plus a summary. One run validates ONE file: `--strict` treats warnings as failures and `--json` emits the machine-readable findings document. It exits nonzero if the target fails.
Optional external checking:
```bash
cadgen sdf validate path/to/model.sdf --gz-check auto
cadgen sdf validate path/to/model.sdf --gz-check required
cadgen sdf validate path/to/model.sdf --gz-check never
```
`gz sdf --check` is optional target-consumer validation. It should be reported as skipped when unavailable unless explicitly required.
## Required report shape
When finishing an SDF task, include a compact report:
```text
Validated: path/to/model.sdf
Checks run:
- bundled SDF validation: passed
- gz sdf --check: skipped, gz not installed
- simulator load: skipped, target simulator unavailable
- viewer handoff: `$cad-viewer` link returned
Assumptions:
- Assumed mesh units are meters.
- Assumed lidar frame is coincident with lidar_link.
Risks:
- Camera plugin filename was not verified in the target simulator environment.
```
## Snapshot Tool
`cadgen sdf 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 sdf snapshot path/to/robot.sdf review.png
```
It accepts `.sdf` only (a format door, same `TARGET [OUT]` grammar as the rest). 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`: 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 sdf snapshot TARGET [OUT] [flags]`, the same one every
format door uses. Use `cadgen sdf snapshot --help` for the complete current
interface — the flags a robot cannot act on are absent from it, not refused by it.
## References
- SDF workflow: `references/sdf-workflow.md`
- Worked examples (golden skeletons): `references/examples.md`
- LLM guardrails: `references/llm-guardrails.md`
- Design ledger: `references/design-ledger.md`
- Frame semantics: `references/frame-semantics.md`
- Validation scope: `references/validation.md`
- Smoke tests: `references/smoke-tests.md`
- Interoperability notes (URDF-derived SDF, meshes, Gazebo): `references/interoperability.md`