# How to Build SDF Simulator Models Using Text‑to‑CAD: A Complete Guide

> Learn to build SDF simulator models with Text-to-CAD. This guide shows how to convert Python scripts into validated SDF models using gen_sdf() for seamless integration.

- Repository: [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Text‑to‑CAD provides an SDF skill that converts Python scripts into validated SDF simulator models through a `gen_sdf()` function, XML validation, and optional Gazebo compliance checks.**

The text-to-cad repository by earthtojake includes a specialized SDF skill that streamlines the creation of simulator-ready models for Gazebo and Ignition. Instead of manually authoring complex XML, developers write concise Python scripts that generate structured SDF output with automated validation and intelligent mesh resolution.

## Understanding the SDF Skill Architecture

The SDF skill operates through three distinct layers that transform a user script into a production-ready simulator file.

### The Three-Layer Pipeline

1. **User Script Layer**: Supplies a `gen_sdf()` function returning SDF XML via [`skills/sdf/scripts/sdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py). This layer utilizes the **`SdfSource`** dataclass to capture parsed models, including version strings, model and world names, plus lists of links, joints, and mesh file paths.

2. **CLI Driver Layer**: Orchestrates execution through [`skills/sdf/scripts/sdf/cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/cli.py). The `generate_sdf_targets()` function manages module loading, payload normalization via `_normalize_sdf_payload()`, and final report generation.

3. **Validation Layer**: Ensures structural integrity through [`skills/sdf/scripts/sdf/validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py). The `validate_sdf_xml()` function enforces SDF schema compliance, validates unique naming conventions via `_raise_on_duplicates()`, and resolves mesh references through `_resolve_local_mesh_uri()`.

### URI Resolution and External Assets

The validator handles multiple URI schemes intelligently. External references using `model://`, `package://`, `http(s)://`, and `fuel://` pass through unchanged for runtime resolution, while `file://` URIs and plain relative paths resolve against the script's base directory. This architecture allows mesh files to sit adjacent to source scripts or in centralized `models/` directories without path ambiguity.

## Creating Your First SDF Generator Script

Every generator script must expose a zero-argument `gen_sdf()` function returning one of three supported envelope formats: an `ET.Element` representing the `<sdf>` root, a raw XML string, or a dictionary containing an `"xml"` key with optional `"metadata"`, `"assumptions"`, and `"warnings"` fields.

### Minimal Robot Example

Create a file named [`my_robot.sdf.py`](https://github.com/earthtojake/text-to-cad/blob/main/my_robot.sdf.py):

```python
import xml.etree.ElementTree as ET

def gen_sdf():
    sdf = ET.Element("sdf", version="1.7")
    world = ET.SubElement(sdf, "world", name="default")
    model = ET.SubElement(world, "model", name="simple_box")
    link = ET.SubElement(model, "link", name="body")
    visual = ET.SubElement(link, "visual", name="visual")
    geometry = ET.SubElement(visual, "geometry")
    mesh = ET.SubElement(geometry, "mesh")
    uri = ET.SubElement(mesh, "uri")
    uri.text = "model://my_robot/meshes/box.stl"
    return sdf

```

This script constructs a basic SDF tree programmatically. The function returns an `ET.Element` object, which the CLI normalizes internally before validation and file generation.

### Envelope Format with Metadata

For production workflows requiring traceability, return the dictionary envelope to embed provenance data:

```python
def gen_sdf():
    sdf = ET.Element("sdf", version="1.7")
    world = ET.SubElement(sdf, "world", name="default")
    model = ET.SubElement(world, "model", name="advanced_robot")
    # ... additional link and joint definitions ...

    
    return {
        "xml": sdf,
        "metadata": {"author": "Jane Doe", "version": "1.0.0"},
        "assumptions": [
            {"code": "mesh_placeholder", "message": "Mesh files provided at runtime"}
        ],
        "warnings": [
            {"code": "inertia_approx", "message": "Using approximate inertia tensor"}
        ]
    }

```

The CLI processes this envelope through `_normalize_sdf_payload()` and embeds metadata via `xml_with_text_to_cad_metadata()` from the `cadpy_metadata` package, making outputs traceable to their Python source.

## Running the CLI and Validating Output

The command-line interface in [`skills/sdf/scripts/sdf/cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/cli.py) automates module loading, validation, and file generation.

### Basic Generation

Execute your script using the installed entry point:

```bash
text-to-cad sdf my_robot.sdf.py

```

Alternatively, invoke the module directly:

```bash
python -m text_to_cad.sdf my_robot.sdf.py

```

The `_load_generator_module()` function dynamically imports your script, temporarily injects its directory into `sys.path`, and invokes `gen_sdf()`. By default, output writes to the same directory with the `.sdf` extension.

### Strict Validation with Gazebo

For deployment-ready simulator assets, enforce Gazebo's native validator:

```bash
text-to-cad sdf my_robot.sdf.py=output/robot.sdf \
  --gz-check required \
  --strict

```

The `--gz-check required` flag triggers `run_gz_sdf_check()` from [`skills/sdf/scripts/sdf/external.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/external.py), executing the external `gz sdf --check` command and merging its findings with internal validation. The `--strict` flag elevates warnings to fatal errors, ensuring non-zero exit codes on any validation issue.

### Debugging Validation Failures

All errors surface as **`SdfSourceError`** instances containing human-readable messages with repo-relative file references. Common failures include duplicate names detected by `_raise_on_duplicates()` in [`skills/sdf/scripts/sdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py) or unresolvable mesh paths flagged by `_resolve_local_mesh_uri()`.

## Advanced Configuration and Target Management

The CLI supports complex generation workflows through flexible target specifications and metadata handling.

### Multiple Target Generation

The `_resolve_target_specs()` function accepts multiple `SOURCE=OUTPUT` mappings:

```bash
text-to-cad sdf robot.sdf.py=models/robot.sdf gripper.sdf.py=models/gripper.sdf

```

You may also use the global `--output` flag to specify a destination directory for all generated files.

### Local Mesh Path Resolution

Place mesh files relative to your script or use standard Gazebo URI schemes. The validator at [`skills/sdf/scripts/sdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py) distinguishes between external schemes (preserved as-is) and local paths (resolved against the script location), supporting workflows where `.stl` or `.dae` files reside in `models/my_robot/meshes/` adjacent to the generator script.

## Summary

- **Build SDF simulator models** by writing Python scripts containing a `gen_sdf()` function that returns XML elements, raw strings, or metadata envelopes.
- The **CLI driver** at [`skills/sdf/scripts/sdf/cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/cli.py) handles dynamic module loading via `_load_generator_module()`, payload normalization, and orchestrates the validation pipeline.
- **Structural validation** occurs in [`skills/sdf/scripts/sdf/validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py) through `validate_sdf_xml()`, enforcing unique names, valid mesh URIs, and SDF schema compliance.
- Use **`--gz-check required`** to invoke Gazebo's native validator via [`skills/sdf/scripts/sdf/external.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/external.py) and **`--strict`** to fail on warnings.
- The **`SdfSource`** dataclass in [`skills/sdf/scripts/sdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py) provides the internal representation for models, worlds, links, and joints.
- **Metadata embedding** via `cadpy_metadata` utilities ensures generated files contain provenance blocks tracing back to the original Python source.

## Frequently Asked Questions

### What Python function must my script define to build SDF simulator models?

Your script must define a zero-argument callable named `gen_sdf()` that returns either an `xml.etree.ElementTree.Element` representing the SDF root, a raw XML string, or a dictionary containing an `"xml"` key. The CLI loader in [`skills/sdf/scripts/sdf/cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/cli.py) specifically looks for this function signature when executing `_load_generator_module()`.

### How does text-to-cad handle external mesh file references?

The validator in [`skills/sdf/scripts/sdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py) preserves external URI schemes including `model://`, `package://`, `http(s)://`, and `fuel://` unchanged for runtime resolution by the simulator. Local `file://` URIs and relative paths resolve against the script's directory via `_resolve_local_mesh_uri()`, allowing meshes to sit next to source files or in structured `models/` directories.

### Can I enforce Gazebo-specific validation rules when generating SDF files?

Yes. Pass `--gz-check required` to the CLI to invoke `run_gz_sdf_check()`, which executes the external `gz sdf --check` command through [`skills/sdf/scripts/sdf/external.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/external.py). Combine this with `--strict` to treat all warnings as fatal errors, ensuring the generated SDF meets Gazebo's strictest compliance requirements before deployment.

### Where does the SDF skill store metadata about the generation process?

The CLI calls `xml_with_text_to_cad_metadata()` from the `cadpy_metadata` package located at [`packages/cadpy_metadata/src/cadpy_metadata/generator.py`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadpy_metadata/src/cadpy_metadata/generator.py) to embed a provenance block inside the generated XML. This block captures the original Python source identity and any assumptions or warnings provided in the envelope's `"metadata"` field, creating an auditable trail from script to simulator model.