# How to Generate SDF Simulation Models with Physics and Sensors Using Text-to-CAD

> Easily generate SDF simulation models with physics and sensors using Text-to-CAD's Python generators. Create validated SDFormat XML files with joints, inertia, and sensors effortlessly.

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

---

**Use the SDF skill's `gen_sdf()` function with the builder helpers API to create Python generators that output validated SDFormat XML files complete with physics joints, inertial properties, and sensor definitions.**

The text-to-CAD repository provides a specialized SDF skill that transforms concise Python scripts into simulator-ready SDFormat (`.sdf`) models. This workflow follows a source-as-truth pattern, allowing you to programmatically define complex simulation models with physics and sensors while eliminating common XML formatting errors through automated validation.

## Understanding the SDF Skill Architecture

The SDF skill is built around three core components that work together to produce valid simulation models according to the architecture defined in [`skills/sdf/SKILL.md`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/SKILL.md).

### The gen_sdf() Source File

Every SDF model starts as a Python script containing a `gen_sdf()` function located in `skills/sdf/scripts/sdf/...`. This function serves as the **single source of truth** for your model definition. Rather than editing XML directly, you modify this Python file and regenerate the `.sdf` output, ensuring reproducibility and maintaining clear version control history.

### Builder Helpers Library

The `sdf.builder` module provides typed helper functions that construct an `xml.etree.ElementTree` safely. Implemented in [`skills/sdf/scripts/sdf/builder.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/builder.py) and documented in [`skills/sdf/references/builder-helpers.md`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/references/builder-helpers.md), these helpers validate numeric inputs, enforce correct vector lengths, and eliminate common LLM-generated XML bugs such as invalid poses, missing `relative_to` attributes, or non-finite numbers.

### CLI Launcher and Validation

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) orchestrates the generation process. It parses your source file, executes `gen_sdf()`, writes the output `.sdf`, and runs the bundled validation checks defined in [`skills/sdf/scripts/sdf/validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py). The CLI can optionally invoke `gz sdf --check` when the `--gz-check` flag is provided, and automatically hands off valid results to the CAD Viewer (`$cad-viewer`).

## Step-by-Step Workflow to Generate SDF Models

Follow this systematic approach to create simulation models with physics and sensors:

1. **Create a Python generator file** (e.g., [`my_robot.py`](https://github.com/earthtojake/text-to-cad/blob/main/my_robot.py)) containing a `gen_sdf()` function that uses builder helpers to construct the XML tree.

2. **Add physics and sensor elements** using `joint()`, `inertial()`, and `sensor()` helpers to define mechanical properties and sensing capabilities.

3. **Run the CLI** to generate and validate the SDF file using `python scripts/sdf my_robot.py -o my_robot.sdf`.

4. **Validate and visualize** the output through the bundled validator and optional CAD Viewer link for final visual confirmation.

## Adding Physics and Sensors

To generate SDF simulation models with physics and sensors, you must explicitly define inertial properties, joint mechanics, and sensor configurations using the builder API.

### Defining Joints and Inertial Properties

Attach **inertial** elements to each link using `inertial(link, mass, inertia)` with a 3x3 inertia matrix. Create mechanical connections using `joint()` with parameters for `joint_type` (e.g., `revolute`, `prismatic`), `parent_link`, `child_link`, and `axis_xyz`. Specify physical limits using the `limits` dictionary with keys for `lower`, `upper`, `effort`, and `velocity`.

### Configuring Sensors

Add perception capabilities by calling `sensor(parent, name, sensor_type, ...)` on a link element. Required parameters include `pose` (position and orientation), `topic` (ROS/Gazebo topic name), and `update_rate`. The builder automatically validates that sensor types are non-empty and update rates are finite numbers, preventing runtime configuration errors.

## Complete Code Example

Below is a complete Python generator that creates a robot with a revolute joint, inertial properties, and a lidar sensor:

```python

# my_robot.py

from sdf.builder import (
    sdf_root, model, link, joint, inertial,
    sensor, visual, collision, box,
)

def gen_sdf():
    # Root SDF document (version 1.12)

    doc = sdf_root(version="1.12")

    # Top-level model

    robot = model(doc, "my_robot", static=False)

    # Base link with box geometry

    base = link(robot, "base_link")
    base_vis = visual(base, "base_vis")
    box(base_vis, (0.3, 0.2, 0.1))
    base_col = collision(base, "base_col")
    box(base_col, (0.3, 0.2, 0.1))
    inertial(base, mass=2.0,
             inertia=[[0.01, 0, 0],
                      [0, 0.02, 0],
                      [0, 0, 0.03]])

    # Revolute joint connecting base to arm

    joint(robot,
          name="shoulder_joint",
          joint_type="revolute",
          parent_link="base_link",
          child_link="arm_link",
          axis_xyz=(0, 0, 1),
          limits={"lower": -1.57, "upper": 1.57, "effort": 10, "velocity": 1.0},
          pose=(0, 0, 0.1, 0, 0, 0))

    # Arm link

    arm = link(robot, "arm_link")

    # Lidar sensor attached to arm

    sensor(arm,
           name="front_lidar",
           sensor_type="lidar",
           pose=(0.1, 0, 0.2, 0, 0, 0),
           topic="/lidar",
           update_rate=10.0)

    return {"xml": doc}

```

Generate the SDF file and validate it:

```bash
python scripts/sdf my_robot.py -o my_robot.sdf

```

To additionally verify with Gazebo tools if installed:

```bash
python scripts/sdf my_robot.py --gz-check auto

```

## Validation and Visualization

After generation, the CLI produces a compact validation report. The bundled validator from [`skills/sdf/scripts/sdf/validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py) checks for unique element names, finite numeric values, and proper coordinate frame usage. If the CAD Viewer skill is installed, the CLI automatically launches it and prints a URL in the format `http://localhost:.../?dir=/abs/path/to/models&file=my_robot.sdf`, allowing you to visually inspect sensor frames and verify static metadata. Note that the viewer displays geometry and transforms but does not execute physics plugins.

## Summary

- **Source-only editing**: Modify only the Python `gen_sdf()` file; never edit the generated `.sdf` directly to maintain reproducibility.
- **Builder helpers**: Use functions from `sdf.builder` to safely construct XML with automatic validation of poses, inertial properties, and sensor parameters.
- **Physics definition**: Include `inertial()` elements on all links and `joint()` elements with proper types and limits to enable realistic simulation.
- **CLI workflow**: Run `python scripts/sdf <source.py>` to generate, validate, and optionally check with `gz sdf --check` before visualizing in the CAD Viewer.
- **Zero external dependencies**: The builder uses only Python standard library (`xml.etree.ElementTree`), making the skill portable across any Python environment.

## Frequently Asked Questions

### How do I add a physics plugin to my SDF model?

Use the `plugin()` helper from `sdf.builder` inside your `gen_sdf()` function. Pass the parent element (typically the model), a unique name, the shared library filename, and a dictionary of parameters. For example: `plugin(robot, name="gravity", filename="libgz-plugin-gravity.so", params={"gravity": "0 0 -9.81"})`.

### What validation checks does the bundled validator perform?

According to [`skills/sdf/scripts/sdf/validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/skills/sdf/scripts/sdf/validation.py), the validator ensures element names are unique within their scope, verifies that all numeric values are finite (not NaN or infinity), checks that pose vectors have exactly six elements, and validates that sensor update rates are positive numbers.

### Can I use this workflow without Gazebo installed?

Yes. The SDF skill is deliberately dependency-free and does not require Gazebo or `numpy`. While the CLI can optionally invoke `gz sdf --check` if Gazebo is present, the bundled validation in [`validation.py`](https://github.com/earthtojake/text-to-cad/blob/main/validation.py) and the CAD Viewer handoff work using only Python standard libraries.

### Why does the CLI generate a CAD Viewer URL instead of running simulation?

The workflow follows the text-to-CAD principle that every SDF modification must be visually confirmed before simulation. The CAD Viewer (accessed via `$cad-viewer`) displays the static model geometry, coordinate frames, and sensor positions to catch errors early, while actual physics simulation requires running the generated `.sdf` in a simulator like Gazebo separately.