Simulation Description Format  ·  .sdf

SDFormat, the format Gazebo reads

SDF is how Gazebo describes a robot and the world around it. It covers the same ground as a URDF and then some — and it disagrees with URDF about where a pose is measured from, which is where converted robots come apart. Here is the format, its frames, and what libsdformat assumes when a file says nothing, checked against libsdformat itself.

What SDFormat is

The Simulation Description Format is XML, maintained with Gazebo as the library libsdformat. One file can hold a single robot — a <model> of links and joints — or a whole <world>: several models, the ground, lights, the physics engine's settings and the plugins that drive it all. Drake reads it too, which is how the claims on this page were checked.

Two other formats share the .sdf extension — chemistry's structure-data files and the timing files of chip design. Neither has anything to do with robots.

The same arm, in SDF

The two-link arm from the URDF walkthrough and the MJCF page, written as SDFormat 1.9. Loaded in libsdformat it puts every link and joint where the URDF does, to the last digit.

two-link-arm.sdf Open it in the viewer
<?xml version="1.0"?>
<sdf version="1.9">
  <model name="two_link_arm">

    <link name="base">
      <inertial>
        <mass>2.0</mass>
        <inertia>
          <ixx>0.00412</ixx><iyy>0.00412</iyy><izz>0.0049</izz>
          <ixy>0</ixy><ixz>0</ixz><iyz>0</iyz>
        </inertia>
      </inertial>
      <visual name="visual">
        <geometry><cylinder><radius>0.07</radius><length>0.10</length></cylinder></geometry>
      </visual>
      <collision name="collision">
        <geometry><cylinder><radius>0.07</radius><length>0.10</length></cylinder></geometry>
      </collision>
    </link>

    <!-- A pose names the frame it is measured from. -->
    <joint name="shoulder" type="revolute">
      <pose relative_to="base">0 0 0.05 0 0 0</pose>
      <parent>base</parent>
      <child>arm</child>
      <axis>
        <xyz>0 0 1</xyz>
        <limit>
          <lower>-1.57</lower><upper>1.57</upper>
          <effort>12</effort><velocity>2.0</velocity>
        </limit>
        <dynamics><damping>0.1</damping></dynamics>
      </axis>
    </joint>

    <!-- The arm is placed at its joint, where URDF would put it. -->
    <link name="arm">
      <pose relative_to="shoulder">0 0 0 0 0 0</pose>
      <inertial>
        <pose>0.15 0 0 0 0 0</pose>
        <mass>0.8</mass>
        <inertia>
          <ixx>0.000213</ixx><iyy>0.00611</iyy><izz>0.00611</izz>
          <ixy>0</ixy><ixz>0</ixz><iyz>0</iyz>
        </inertia>
      </inertial>
      <visual name="visual">
        <pose>0.15 0 0 0 0 0</pose>
        <geometry><box><size>0.30 0.04 0.04</size></box></geometry>
      </visual>
      <collision name="collision">
        <pose>0.15 0 0 0 0 0</pose>
        <geometry><box><size>0.30 0.04 0.04</size></box></geometry>
      </collision>
    </link>

  </model>
</sdf>
  • Poses name their frame. relative_to="base" puts the shoulder 5 cm above the base's origin. Leave it off a joint and the same numbers are measured from the child link instead — the opposite of URDF.
  • A link is placed, not implied. In URDF a child link's frame is its joint's frame by definition; here the arm says so itself, with a pose relative to the shoulder.
  • Limits live inside the axis, with damping beside them, and every value is an element rather than an attribute.
  • Nothing holds the base down. Gazebo would let this arm fall. A joint to world, or <static>true</static> on the model, fixes it in place.

SDF and URDF, side by side

The two describe the same robot well enough to convert between, and differ in the places that decide whether a converted robot behaves the same:

URDFSDFormat
DescribesOne robotA world: models, lights, physics settings, plugins
Placing a partA joint's origin, measured from the parentAny pose, measured from any frame it names
Joint typesRevolute, continuous, prismatic, fixed, floating, planarThose but floating and planar, plus ball, universal, revolute2, screw, gearbox
ShapesBox, cylinder, sphere, meshThose plus capsule, ellipsoid, plane, heightmap, polyline
A link with no inertialMassless1 kg, unit inertia
Sensors and pluginsOnly through <gazebo> extension tagsElements of the format
ReuseXacro macros, expanded before loading<include> of other models, and models nested in models

Frames: where converted robots come apart

URDF has one rule: a joint's origin is measured from its parent link, and the child link lives at the joint. SDFormat, from version 1.7, lets any pose name the frame it is measured from — a link, a joint, a <frame> declared for the purpose, or the model itself — and chooses a default for each element when the name is left off. Those defaults are not URDF's.

A link without relative_to is measured from the model's frame. A joint without it is measured from its child link. A joint axis is expressed in the joint's own frame unless it says expressed_in. Each of these is reasonable alone; together they mean that the numbers from a URDF, typed into the same places in an SDF file, describe a different robot — one whose joints sit at the children's origins and whose links pile up at the model's.

Older files make it worse. Before 1.7 there was no relative_to: every link was placed in the model's frame and every joint in its child's, so converting a URDF meant composing every pose down the tree. libsdformat still reads those files, and converts them on load, but a pose in a 1.6 file means what 1.6 said it meant.

What libsdformat assumes, and what it refuses

None of the assumptions comes with a warning. Each was checked by loading a file that leaves the value out:

When the file haslibsdformat readsWhat it means
A link with no <inertial>1 kg, unit inertiaA bracket left without one weighs as much as a bag of sugar and turns like a flywheel. URDF reads the same omission as massless.
A joint <pose> without relative_tomeasured from the child linkURDF measures a joint from its parent. A pose copied straight across lands somewhere else by the child's own offset.
An axis <xyz>in the joint's frameexpressed_in="__model__" measures it in the model's frame instead; before 1.7 that was use_parent_model_frame.
Angles in a <pose>radians, roll pitch yawTurned about fixed axes, exactly like URDF's rpy. From 1.9, degrees="true" changes the unit and rotation_format="quat_xyzw" takes a quaternion — scalar last, unlike MuJoCo's.
A revolute joint with no <limit>unlimitedROS's URDF parser refuses the same joint. A file moving between the two has to decide which one it meant.
A link named worldrefusedThe name is reserved for the world frame — and URDF files use a link called world for exactly that, so a naive conversion fails here.
Two poses relative to each otherrefused"PoseRelativeToGraph cycle detected", with the name of the frame where the loop closed.
type="hinge", or any other unknown typerefusedThe types are revolute, continuous, prismatic, fixed, ball, universal, revolute2, screw and gearbox.

A URDF in Gazebo

Gazebo has no URDF reader of its own: libsdformat converts the URDF to SDF as it loads, and Gazebo simulates the result. Two things happen on the way that surprise people. Links joined by fixed joints are merged into a single body, so a sensor link vanishes from the model unless a <gazebo reference="…"> tag asks to preserve its joint. And everything URDF has no place for — friction (mu1, mu2), contact stiffness (kp, kd), sensors, plugins — has to ride along inside <gazebo> tags, which the rest of ROS ignores.

Converting to SDF yourself makes those choices visible in a file instead of happening inside the loader each time. Either way, give every moving link a real mass first: the viewer lists the ones without one.

SDF and URDF, both ways

  • URDF to SDF writes SDFormat 1.9: each joint placed relative to its parent link, each link relative to the joint that carries it, the world frame by its reserved name. Loaded in libsdformat, the result puts links and joints exactly where the URDF did.
  • SDF to URDF resolves every frame first — relative_to, nested models, <frame> elements, degrees and quaternion poses — and then writes the tree. A link without an inertial gets libsdformat's 1 kg, and the file says it did. Plugins, sensors and the world around the model are listed as lost.
  • SDF to MJCF and SDF to USD take the same model to MuJoCo and to Isaac Sim.

Common questions

What is SDFormat?

The Simulation Description Format: the XML that Gazebo reads, maintained alongside it as libsdformat. An SDF file can hold a single robot or a whole world — several models, the ground, lights, physics settings and plugins.

Is an SDF file the same as a URDF?

No, though they describe the same robot well enough to convert between. URDF is one robot as a tree of links and joints; SDF is a world, with poses that name the frame they are measured from and joint types URDF does not have. The table above goes through the differences that change a model.

How do I open an SDF file?

A robot SDF opens in the viewer here: the link tree, joints, collision shapes and inertia, with meshes if you drop the folder. Two other formats share the extension — the structure-data files of chemistry and the delay files of chip design — and those are different things entirely.

Can Gazebo load a URDF?

Yes: it converts the URDF to SDF as it loads it. Links joined by fixed joints are merged into one on the way unless a <gazebo> tag asks to keep the joint, and Gazebo-only settings — friction, contact stiffness, sensors, plugins — ride along in <gazebo> tags the rest of ROS ignores.

How do I convert URDF to SDF?

Gazebo's own command line does it with gz sdf -p robot.urdf. Without Gazebo installed, the URDF to SDF converter writes SDFormat 1.9 with every pose relative to a named frame, and lists what could not cross.

Which SDFormat version should I write?

1.9 or later if your Gazebo is Fortress or newer: it is the version with degrees="true" and quaternion poses, and its frames are the ones described here. libsdformat reads older files and converts them on load, so a 1.6 model still opens — with its poses interpreted the 1.6 way.

Where to go next

  • The viewer — open an SDF model and check its frames, joints and masses
  • MJCF — the same questions asked of MuJoCo's format
  • All conversions — URDF, SDF, MJCF, USD and Xacro, each direction with its losses