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

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 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). This class:

  • Parses SDF XML files into structured Python objects
  • Validates structure via the validation module
  • Extracts models, worlds, links, joints, and mesh references
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 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.


# 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) provides three operational modes:

Mode 1: Default sibling output

python scripts/sdf path/to/my_model.py

# Creates my_model.sdf next to my_model.py

Mode 2: Custom output location

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

Mode 3: Batch generation

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

Optional Gazebo validation

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):

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) 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/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/ provides detailed guidance on frame semantics, guard-rails, and design ledger entries for complex simulator configurations.

Summary

  • SdfSource in 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 supports single-file, custom-output, and batch generation modes with optional Gazebo validation
  • SdfBuilder in 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/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 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/ 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →