# SDF Simulator Model Generation with Physics, Sensors, and Lights: A Complete Guide

> Generate SDF simulator models with physics, sensors, and lights using the text-to-CAD pipeline. Create validated SDFormat models for Gazebo and more.

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

---

**The text-to-CAD repository provides a three-layer pipeline—source parsing, model inspection, and CLI generation—to create validated SDFormat models for Gazebo and other physics simulators.**

The **SDF skill** in [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad) enables automated creation of simulator-ready assets. This guide explains how the pipeline handles physics properties, sensor definitions, and lighting configurations while maintaining strict validation and single-source-of-truth principles.

## Architecture of the SDF Simulator Generation Pipeline

The SDF skill is organized into three functional layers that transform Python generators or raw XML into validated simulator models.

### Layer 1: Source Parsing with SdfSource

The entry point is the `SdfSource` dataclass defined in [[`skills/sdf/scripts/sdf/source.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py)](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/source.py). This class:

- Parses SDF XML files into structured Python objects
- Validates structure via the `validation` module
- Extracts models, worlds, links, joints, and mesh references

```python
from skills.sdf.scripts.sdf.source import SdfSource, read_sdf_source

# Parse and validate an existing SDF file

source = read_sdf_source("path/to/robot.sdf")
print(f"Found {len(source.models)} model(s), {len(source.worlds)} world(s)")
print(f"Mesh dependencies: {source.mesh_paths}")

```

The `read_sdf_source()` function orchestrates the workflow: it loads XML, calls `validate_sdf_root()` for schema compliance, then builds the `SdfSource` instance with all extracted metadata.

### Layer 2: Model Inspection and Mesh Resolution

Helper functions in [`source.py`](https://github.com/earthtojake/text-to-cad/blob/main/source.py) perform deep inspection of simulator components:

| Function | Purpose |
|----------|---------|
| `_read_model()` | Extracts link names, joint definitions, and physics properties from a model element |
| `_geometry_mesh_paths()` | Locates all mesh URI references in geometry elements |
| `_resolve_local_mesh_uri()` | Converts relative mesh paths to absolute repository-relative paths |
| `_validate_link_reference()` | Ensures joint parent/child links exist and are properly named |
| `_raise_on_duplicates()` | Detects duplicate model, link, or joint identifiers |

All errors raise `SdfSourceError` with repository-relative paths computed by `_relative_to_repo()`. This makes debugging straightforward for both developers and automated agents.

```python

# Example: Accessing parsed physics and sensor data

for model in source.models:
    for link in model.links:
        print(f"Link: {link.name}")
        if link.inertial:
            print(f"  Mass: {link.inertial.mass}")
        for sensor in link.sensors:
            print(f"  Sensor: {sensor.name} ({sensor.type})")

```

### Layer 3: Command-Line Interface for Generation and Validation

The CLI wrapper in [[`skills/sdf/scripts/sdf/cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/cli.py)](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/cli.py) provides three operational modes:

**Mode 1: Default sibling output**

```bash
python scripts/sdf path/to/my_model.py

# Creates my_model.sdf next to my_model.py

```

**Mode 2: Custom output location**

```bash
python scripts/sdf path/to/my_model.py -o models/robot.sdf

```

**Mode 3: Batch generation**

```bash
python scripts/sdf path/to/a.py=out/a.sdf path/to/b.py=out/b.sdf

```

**Optional Gazebo validation**

```bash
python scripts/sdf path/to/my_model.py --gz-check auto

```

When `--gz-check` is enabled and `gz` is installed, the CLI pipes output through `gz sdf --check` for additional simulator-specific validation.

## Adding Physics, Sensors, and Lights Programmatically

For **SDF simulator model generation with physics, sensors, and lights**, use the builder API in [[`skills/sdf/scripts/sdf/builder.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/builder.py)](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/builder.py):

```python
from skills.sdf.scripts.sdf.builder import SdfBuilder, ModelBuilder

# Initialize SDF 1.12 format

builder = SdfBuilder(version="1.12")

# Create a model with physics properties

model = ModelBuilder(name="mobile_robot")
model.set_static(False)
model.set_self_collide(True)

# Add a base link with inertial physics

base = model.add_link("base_link")
base.set_inertial(
    mass=5.0,
    inertia=[0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1]  # Ixx, Ixy, Ixz, Iyx, Iyy, Iyz, Izx, Izy, Izz

)

# Attach a LiDAR sensor

lidar = base.add_sensor("lidar_2d", type="gpu_lidar")
lidar.set_ray_properties(
    samples=640,
    resolution=1.0,
    min_angle=-1.396263,
    max_angle=1.396263
)
lidar.set_range_properties(min=0.08, max=10.0, resolution=0.01)

# Add illumination

light = model.add_light("headlamp", type="directional")
light.set_direction(x=0, y=0, z=-1)
light.set_diffuse(r=0.5, g=0.5, b=0.5, a=1.0)

builder.add_model(model.build())
sdf_xml = builder.build()

# Save and validate

with open("robot_with_physics.sdf", "w") as f:
    f.write(sdf_xml)

```

The builder enforces schema compliance at construction time, preventing invalid combinations of **physics**, **sensor**, and **light** elements.

## Validation and Error Handling

The validation system in [[`skills/sdf/scripts/sdf/validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py)](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py) provides two key functions:

- `validate_sdf_root()` — Checks XML structure against SDF schema
- `raise_for_validation_errors()` — Converts validation failures to exceptions

This **early validation** architecture catches errors before any simulator execution, reducing downstream failures in Gazebo or other physics engines.

Error messages include repository-relative paths:

```

SdfSourceError: Duplicate link name 'wheel_left' in model 'mobile_robot'
  File: models/robot.sdf (line 47)
  Previous definition: line 23

```

## Workflow Integration and CAD Viewer Hand-off

The SDF skill enforces **single-source-of-truth** per its core rules in [[`SKILL.md`](https://github.com/earthtojake/text-to-cad/blob/main/SKILL.md)](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/SKILL.md) (lines 18-20). The parser operates only on source files—never on generated XML—ensuring modifications flow back to the original Python generator.

The standard workflow proceeds through these stages:

1. **Define** — Create Python generator using `SdfBuilder` or raw XML
2. **Generate** — Run `python scripts/sdf` to produce `.sdf` output
3. **Validate** — Automatic schema check plus optional `gz sdf --check`
4. **Inspect** — Parse with `SdfSource` to extract mesh dependencies
5. **Hand off** — Transfer to CAD Viewer with validated asset bundle

Reference documentation in [`skills/sdf/references/`](https://github.com/earthtojake/text-to-cad/tree/main/skills/sdf/references) provides detailed guidance on frame semantics, guard-rails, and design ledger entries for complex simulator configurations.

## Summary

- **`SdfSource` in [`source.py`](https://github.com/earthtojake/text-to-cad/blob/main/source.py)** provides the core parser with mesh URI resolution and duplicate detection
- **`read_sdf_source()`** orchestrates validation-first loading: XML → schema check → structured object
- **CLI in [`cli.py`](https://github.com/earthtojake/text-to-cad/blob/main/cli.py)** supports single-file, custom-output, and batch generation modes with optional Gazebo validation
- **`SdfBuilder` in [`builder.py`](https://github.com/earthtojake/text-to-cad/blob/main/builder.py)** enables programmatic construction of physics, sensor, and light elements
- **Single-source-of-truth enforcement** ensures all edits propagate to original generators, never to derived XML

## Frequently Asked Questions

### How do I add physics properties like mass and inertia to an SDF model?

Use `ModelBuilder` and `LinkBuilder` methods as shown in [[`builder.py`](https://github.com/earthtojake/text-to-cad/blob/main/builder.py)](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/builder.py). Call `link.set_inertial(mass=..., inertia=[...])` with a 9-element inertia matrix. The builder validates that mass is positive and the matrix is positive-definite before serialization.

### Can the SDF skill validate models against Gazebo's specific requirements?

Yes. Pass `--gz-check auto` or `--gz-check always` to the CLI. When `gz` is available, the command pipes output through `gz sdf --check` for simulator-specific validation beyond basic XML schema compliance.

### What sensor types are supported in programmatic SDF generation?

The [`builder.py`](https://github.com/earthtojake/text-to-cad/blob/main/builder.py) API supports all SDFormat 1.12 sensor types including `camera`, `depth_camera`, `gpu_lidar`, `imu`, `magnetometer`, `altimeter`, and `contact`. Each sensor type has specialized configuration methods—consult the [`references/`](https://github.com/earthtojake/text-to-cad/tree/main/skills/sdf/references) directory for type-specific guard-rails and frame semantics.

### How does the pipeline handle mesh file dependencies?

During parsing, `_geometry_mesh_paths()` extracts all URI references, `_resolve_local_mesh_uri()` converts them to absolute paths relative to the repository root, and `SdfSource.mesh_paths` stores the complete list. This enables automatic bundling of all required assets for simulator hand-off.