3.8 KiB
3.8 KiB
SDF builder helpers
Builder helpers are optional. They exist to reduce common LLM mistakes in XML construction, not to replace SDFormat or libsdformat.
Why helpers exist
LLMs often make these mistakes when writing raw XML:
- wrong pose value length;
- hidden degree/radian conversion;
- missing
relative_toorexpressed_in; - zero or non-finite joint axes;
- negative primitive dimensions;
- mesh scale applied inconsistently;
- inertial values copied from visuals;
- plugin filenames invented from plausible names.
Helpers should make the common path explicit, typed-ish, and auditable while still returning ordinary xml.etree.ElementTree.Element nodes.
Design constraints
- Standard library only.
- No mandatory Gazebo, ROS, NumPy, lxml, CAD, or mesh dependency.
- Return ElementTree elements.
- Compose with raw ElementTree calls.
- Validate local numeric shape and finiteness.
- Do not attempt full SDFormat schema validation.
- Do not silently invent frames, axes, inertials, plugins, or sensor parameters.
Recommended helper surface
Names may vary to match the current runtime package, but the helper surface should stay small.
# XML basics
text(parent, tag, value, attrib=None)
fmt_float(value)
fmt_vector(values)
# Poses and axes
pose(parent, xyz=(0, 0, 0), rpy=(0, 0, 0), *, relative_to=None,
rotation_format="euler_rpy", degrees=False)
quat_pose(parent, xyz=(0, 0, 0), quat_xyzw=(0, 0, 0, 1), *, relative_to=None)
axis(parent, xyz=(0, 0, 1), *, expressed_in=None)
# Document structure
sdf_root(version="1.12")
world(parent, name)
model(parent, name, *, static=None, pose=None)
frame(parent, name, *, attached_to=None, pose=None)
link(parent, name, *, pose=None, inertial=None)
joint(parent, name, joint_type, parent_link, child_link, *, pose=None,
axis_xyz=None, axis_expressed_in=None, axis2_xyz=None, limits=None)
# Geometry
visual(parent, name, *, pose=None)
collision(parent, name, *, pose=None)
box(parent, size_xyz)
sphere(parent, radius)
cylinder(parent, radius, length)
capsule(parent, radius, length)
mesh(parent, uri, *, scale=None)
# Physics and metadata
inertial(parent, mass, inertia, *, pose=None)
sensor(parent, name, sensor_type, *, pose=None, topic=None, update_rate=None)
plugin(parent, name, filename, params=None)
include(parent, uri, *, name=None, pose=None)
Numeric behavior
Helpers should reject:
- non-finite numbers;
- vectors with the wrong length;
- negative or zero primitive dimensions;
- zero joint axes;
- zero-mass inertials;
- zero-norm quaternions.
Helpers may warn, but should not silently fix:
- non-unit axes;
- non-normalized quaternions;
- use of degrees;
- missing
relative_toon nontrivial poses.
Example
from sdf.builder import axis, box, collision, joint, link, model, sdf_root, visual
BASE_SIZE_M = (0.4, 0.3, 0.1)
LIFT_AXIS_Z = (0.0, 0.0, 1.0)
def gen_sdf():
sdf = sdf_root("1.12")
robot = model(sdf, "lift_fixture", static=False)
base = link(robot, "base_link")
v = visual(base, "base_visual")
box(v, BASE_SIZE_M)
c = collision(base, "base_collision")
box(c, BASE_SIZE_M)
carriage = link(robot, "carriage_link")
j = joint(
robot,
"lift_joint",
"prismatic",
parent_link="base_link",
child_link="carriage_link",
axis_xyz=LIFT_AXIS_Z,
axis_expressed_in="base_link",
)
return {
"xml": sdf,
"assumptions": [
{"code": "inertials_placeholder", "message": "Inertials omitted pending measured masses."}
],
}
When not to use helpers
Raw ElementTree is acceptable when:
- a target simulator needs unusual extension XML;
- a plugin has arbitrary nested configuration;
- an existing generator already has a clear internal abstraction;
- the user asked for a minimal XML-only source.
Even then, keep named constants, ledger comments, and validation.