SDF Quickstart Guide

This guide will walk you through sdformat's API, showing you the basics on how to use it to parse your SDFs. You'll find the API quite regular, making it very easy to pick up.

Prerequisites

Make sure you have sdformat installed on your system.

Parsing a model SDF

Developing the code

Consider the code below:

#include <iostream>

#include <sdf/sdf.hh>

int main(int argc, const char* argv[])
{
  // check arguments
  if (argc < 2)
  {
    std::cerr << "Usage: " << argv[0]
              << " <sdf-path>" << std::endl;
    return -1;
  }
  const std::string sdfPath(argv[1]);

  // load and check sdf file
  sdf::SDFPtr sdfElement(new sdf::SDF());
  sdf::init(sdfElement);
  if (!sdf::readFile(sdfPath, sdfElement))
  {
    std::cerr << sdfPath << " is not a valid SDF file!" << std::endl;
    return -2;
  }

  // start parsing model
  const sdf::ElementPtr rootElement = sdfElement->Root();
  if (!rootElement->HasElement("model"))
  {
    std::cerr << sdfPath << " is not a model SDF file!" << std::endl;
    return -3;
  }
  const sdf::ElementPtr modelElement = rootElement->GetElement("model");
  const std::string modelName = modelElement->Get<std::string>("name");
  std::cout << "Found " << modelName << " model!" << std::endl;

  // parse model links
  sdf::ElementPtr linkElement = modelElement->GetElement("link");
  while (linkElement)
  {
    const std::string linkName = linkElement->Get<std::string>("name");
    std::cout << "Found " << linkName << " link in "
              << modelName << " model!" << std::endl;
    linkElement = linkElement->GetNextElement("link");
  }

  // parse model joints
  sdf::ElementPtr jointElement = modelElement->GetElement("joint");
  while (jointElement)
  {
    const std::string jointName = jointElement->Get<std::string>("name");
    std::cout << "Found " << jointName << " joint in "
              << modelName << " model!" << std::endl;

    const sdf::ElementPtr parentElement = jointElement->GetElement("parent");
    const std::string parentLinkName = parentElement->Get<std::string>();

    const sdf::ElementPtr childElement = jointElement->GetElement("child");
    const std::string childLinkName = childElement->Get<std::string>();

    std::cout << "Joint " << jointName << " connects " << parentLinkName
              << " link to " << childLinkName << " link" << std::endl;

    jointElement = jointElement->GetNextElement("joint");
  }

  return 0;
}

Let's break it down a bit.


sdf::SDFPtr sdfElement(new sdf::SDF());
sdf::init(sdfElement);

This is the main entrypoint to the API: any SDF tree, either in a file or as a string, will be parsed into this object. On calling sdf::init(), the XML schemas for all SDF versions are loaded in order to provide format validation later on.


if (!sdf::readFile(sdfPath, sdfElement))
{
  std::cerr << sdfPath << " is not a valid SDF file!" << std::endl;
  return -2;
}

Given an sdf_path , sdf::readFile will attempt to parse such file into our sdf_element. If it fails to do so, either because the file could not be accessed or it was not a valid SDF or URDF (yes, sdformat handles both formats seamlessly!), it will return false .


const sdf::ElementPtr rootElement = sdfElement->Root();
if (!rootElement->HasElement("model"))
{
  std::cerr << sdfPath << " is not a model SDF file!" << std::endl;
  return -3;
}

To traverse the parsed SDF tree, we start at the root. It is important to note that the root_element here is not the same element as the one given explicitly on the SDF (i.e. the a <world> element on a typical world SDF file) but one above. That's why we check for model element existence.


Next, you'll notice a pattern for iterating through an element children of the same type (that is, with the same tag):

sdf::ElementPtr linkElement = modelElement->GetElement("link");
while (linkElement)
{
    // parse link element
    linkElement = linkElement->GetNextElement("link");
}

At the beginning, we get the first link element from the model element. If it doesn't exist, model_element->GetElement("link") will return a null pointer, and the loop will be skipped. If it does exist, we'll process that link element in the loop. We then get the next link element, but this time from the current link element instead of the model element using link_element->GetNextElement("link"). The latter method also returns a null pointer if no element could be found, so we'll eventually leave the loop when no more elements of that child remain.

To summarize, while GetElement() retrieves child elements, GetNextElement() retrieves sibling elements. With that in mind, you'll find the iterations over links, joints, collisions, etc., quite straightforward and easy to grasp.


Let's now focus on the joints elements' iteration:

const std::string jointName = jointElement->Get<std::string>("name");
std::cout << "Found " << jointName << " joint in "
          << modelName << " model!" << std::endl;

const sdf::ElementPtr parentElement = jointElement->GetElement("parent");
const std::string parentLinkName = parentElement->Get<std::string>();

const sdf::ElementPtr childElement = jointElement->GetElement("child");
const std::string childLinkName = childElement->Get<std::string>();

std::cout << "Joint " << jointName << " connects " << parentLinkName
          << " link to " << childLinkName << " link" << std::endl;

As you might guess, we're getting the name attribute of the current joint_element and then retrieving its parent and child elements' values. It is interesting to note all Get() method's use cases: Get() is a template method that returns the given attribute value or the element value itself (that is, the plain text in between tags) if called with no arguments. To this end, it has been specialized to parse such value accordingly into primitive types (i.e. double), common std types (i.e. std::string) or ignition::math types for the most complex ones (like poses).

Building the code

Create an empty directory:

mkdir sdf_tutorial
cd sdf_tutorial

Copy the code into a file and name it check_sdf.cc. Along with it, add a CMakeLists.txt file and copy the following into it:

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

find_package(SDFormat REQUIRED)
# find_package(sdformat<version of sdformat> REQUIRED) # sdformat7 or higher

include_directories(${SDFormat_INCLUDE_DIRS})
link_directories(${SDFormat_LIBRARY_DIRS})

add_executable(check_sdf check_sdf.cc)
target_link_libraries(check_sdf ${SDFormat_LIBRARIES})

Note: If you are using a higher version than 6 you should use to find the library find_package(sdformat<version of sdformat> REQUIRED)

Now build it:

mkdir build
cd build
cmake ..
make

You can find plenty of SDF samples to play with here. Just fetch the model.sdf of your model of choice and give it a more meaningful name. For example, running ./check_sdf on the husky.sdf file (husky/model.sdf on the site) will generate the following output:

Found husky model!
Found base_link link in husky model!
Found back_left_wheel link in husky model!
Found back_right_wheel link in husky model!
Found front_left_wheel link in husky model!
Found front_right_wheel link in husky model!
Found back_left_joint joint in husky model!
Joint back_left_joint connects base_link link to back_left_wheel link
Found back_right_joint joint in husky model!
Joint back_right_joint connects base_link link to back_right_wheel link
Found front_left_joint joint in husky model!
Joint front_left_joint connects base_link link to front_left_wheel link
Found front_right_joint joint in husky model!
Joint front_right_joint connects base_link link to front_right_wheel link