137 lines
3.8 KiB
Markdown
137 lines
3.8 KiB
Markdown
# 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_to` or `expressed_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.
|
|
|
|
```python
|
|
# 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_to` on nontrivial poses.
|
|
|
|
## Example
|
|
|
|
```python
|
|
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.
|