4.6 KiB
SDF generator contract
Use this reference when creating or editing Python sources that generate SDFormat/SDF files.
Source of truth
The Python source that defines gen_sdf() is the source of truth. The configured .sdf output is generated and should not be hand-edited.
Required shape
gen_sdf() must be a top-level zero-argument function. Prefer returning the root xml.etree.ElementTree.Element for a complete SDFormat document.
import xml.etree.ElementTree as ET
def gen_sdf():
sdf = ET.Element("sdf", {"version": "1.12"})
model = ET.SubElement(sdf, "model", {"name": "sample_robot"})
ET.SubElement(model, "link", {"name": "base_link"})
return sdf
The root must be <sdf> and must include a non-empty version attribute. Default new outputs to version="1.12" unless the user or target simulator requires another version.
Accepted return values
Preferred:
def gen_sdf():
return sdf_root_element
Accepted for compatibility:
def gen_sdf():
return """<?xml version="1.0"?>
<sdf version="1.12">...</sdf>
"""
Envelope form:
def gen_sdf():
return {
"xml": sdf_root_element,
"assumptions": [
{"code": "mesh_units", "message": "Assumed mesh units are meters."},
"Assumed lidar frame is coincident with lidar_link.",
],
"warnings": [
{"code": "plugin_unverified", "message": "Camera plugin filename was not verified in the target simulator."}
],
"metadata": {
"target_consumer": "Gazebo Harmonic",
"sdf_version": "1.12",
},
}
Envelope rules:
xmlis required and may be an XML element or XML string.assumptions, when present, must be a list of strings or dicts withcode,message, and optionalsource.warnings, when present, must be a list of strings or dicts withcode,message, and optionalsource.metadata, when present, must be a JSON-serializable dict with scalar values.- Output-path fields such as
sdf_outputare unsupported; select output paths with CLI targets. - Unsupported envelope fields should fail with a clear error.
Use envelope assumptions to make spatial, physical, simulator, and resource assumptions auditable. Do not hide guesses in XML literals.
Output path
The generated .sdf output path is selected by the CLI:
- plain source target: sibling
.sdfbeside the source; -o/--output: override for one plain target;SOURCE.py=OUTPUT.sdf: per-target override for multi-target generation.
The generator should not write the output file itself.
Source-relative asset paths
Do not rely on the shell working directory for mesh or resource paths inside generator code. Prefer source-relative constants:
from pathlib import Path
SOURCE_DIR = Path(__file__).resolve().parent
MESH_DIR = SOURCE_DIR / "meshes"
For model packages, prefer URI conventions understood by the target simulator, such as model://..., package://..., or stable relative paths from the generated .sdf location.
Pose and frame discipline
Before emitting <pose>, <frame>, <joint>, <axis>, <visual>, <collision>, sensor, or plugin placement elements, fill out the design ledger:
- pose value;
- pose frame or
relative_to; - joint axis and
expressed_inframe; - positive-motion convention;
- mesh units and scale;
- source of every value.
Use constants with names that expose assumptions:
ASSUMED_BASE_TO_LIDAR_Z_M = 0.18
ASSUMED_LIDAR_YAW_RAD = 0.0
Do not hide spatial guesses inside XML literals.
Minimal model example with explicit pose intent
import xml.etree.ElementTree as ET
def text(parent, tag, value, attrib=None):
child = ET.SubElement(parent, tag, attrib or {})
child.text = str(value)
return child
def pose(parent, xyz=(0, 0, 0), rpy=(0, 0, 0), *, relative_to=None):
attrib = {"relative_to": relative_to} if relative_to else {}
return text(parent, "pose", " ".join(str(v) for v in (*xyz, *rpy)), attrib)
def gen_sdf():
sdf = ET.Element("sdf", {"version": "1.12"})
model = ET.SubElement(sdf, "model", {"name": "sample_robot"})
pose(model, relative_to="world")
base = ET.SubElement(model, "link", {"name": "base_link"})
visual = ET.SubElement(base, "visual", {"name": "base_visual"})
geometry = ET.SubElement(visual, "geometry")
box = ET.SubElement(geometry, "box")
text(box, "size", "0.4 0.3 0.1")
return {
"xml": sdf,
"assumptions": ["base_link is coincident with the model frame."],
"metadata": {"target_consumer": "visualization"},
}