Composition
This document describes SDFormat's support for model level composition in which a model can be constructed from other models nested within itself.
Note: This describes older legacy behavior. Newer features, intended for
SDFormat 1.8 / libsdformat
11, are described in the
Composition Proposal.
Introduction
Models are the fundamental building blocks of a world in SDFormat. As described
in the Creating Worlds documentation, models can be
defined directly inside a <world>
element or they can be defined in separate
files and get added into the world with the help of the <include>
tag. The
latter approach is a form of composition that allows a model to be defined once
and get instantiated multiple times with different parameters such as the
model's name and pose. This form of composition has been available in SDFormat
since version 1.4.
SDFormat version 1.5 introduced a form of composition that allows users to build a single model by combining other models. This document will focus on this form of composition.
Model level composition
Model level composition can be achieved by either defining models directly
inside other models or by defining each model in its own file and including it
into the parent model with the help of the <include>
tag.
Defining models directly inside parent models
Models can be nested inside other models by directly defining the nested model
inside the <model>
tag of the parent model. The following snippet shows
a nested model sphere
defined inside a parent model Pm
.
<model name="Pm">
<link name="body"/>
<model name="sphere">
<pose>0 0 0.5 0 0 0</pose>
<link name="body"/>
</model>
</model>
WARNING
Early versions of libsdformat's DOM API (libsdformat
<= 9.2.x) do not
support models defined directly inside parent models. As a workaround, such
models can be accessed using the Element
API.
This will change in SDFormat 1.7 as implemented by libsdformat
9.3.0.
Please see the
Pose Frame Semantics - Directly Nested Models section for more information.
Defining models in separate files
Models can be defined in individual files by adhering to a model database
directory structure. This directory structure requires that each model has its
own directory that contains at least a metadata file named model.config
and
a file containing the model definition. The model definition file has <sdf>
as its root element followed by <model>
. The following example shows the
directory structure and contents of a model named sphere
.
.
├── sphere
├── model.config
└── model.sdf
<!-- Metadata file: sphere/model.config -->
<?xml version="1.0" ?>
<model>
<sdf version="1.5">model.sdf</sdf>
</model>
<!-- Model definition: sphere/model.sdf -->
<?xml version="1.0" ?>
<sdf version="1.5">
<model name="sphere">
<link name="body">
<visual name="v1">
<geometry>
<sphere>
<radius>0.1</radius>
</sphere>
</geometry>
</visual>
</link>
</model>
</sdf>
More details about the directory structure can be found here.
Including a model
A model that has been defined in a model database can be added to a parent
model using the <include>
tag.
The <include>
tag may contain the following elements that specify the
location of the model and optionally override some of its properties:
<uri>
: A string used to indicate the location of the model directory to be included. This is described in more detail below.<name>
: Overrides the name of the nested model. References from the parent model to elements inside the nested model must take into account this new name. This new name must also be unique among sibling nested models.<pose>
: Overrides the pose of the nested model. The pose is specified with respect to the parent model frame.<static>
: Overrides the static value of the included model.<plugin>
: A plugin element to be added to the list of plugins associated with the included model.
In libsdformat
, the contents of the <uri>
element are passed to the
sdf::findFile
function defined in
sdf/SDFImpl.hh.
This function searches for the model files using the steps in the following
order until the model is found:
Users can define paths on their system associated with a specific URI scheme using sdf::addURIPath. For example, if
sdf::addURIPath("model://", path);
has been called, including a<uri>model://sphere</uri>
will search the folder inpath
for subfolders namedsphere
.libsdformat
treats the URI as a directory path and proceeds to search for it within its installationshare
path.libsdformat
treats the URI as a directory path and proceeds to search for it in the file system. If the URI is an absolute path,libsdformat
checks if the path exists. Otherwise,libsdformat
searches for the path relative to the current working directory of the process that useslibsdformat
.If the environment variable
SDF_PATH
is set,libsdformat
forms a new path by appending the URI to the contents ofSDF_PATH
and checks if the path exists.libsdformat
treats the URI as a directory path and forms a new path by appending it to the current working directory of the process. It then checks if this path exists.Finally,
libsdformat
uses a mechanism by which users can register a custom callback function for URIs that could not be found by any of the previous methods.
The following example shows the use of the <include>
tag to create a model
that contains multiple instances of the sphere
model shown in the earlier
example.
<model name="Pm">
<include>
<uri>/path/to/sphere</uri>
<pose>0 0 0.5 0 0 0</pose>
</include>
<include>
<uri>/path/to/sphere</uri>
<name>sphere1</name>
<pose>0 0 1 0 0 0</pose>
</include>
<include>
<uri>/path/to/sphere</uri>
<name>sphere2</name>
<pose>1 0 1 0 0 0</pose>
</include>
</model>
libsdformat
's implementation of <include>
in models
libsdformat
's current implementation of the <include>
tag works by copying
all links and joints of the child model into the parent model with their poses
modified to be relative to the parent model frame. To avoid name collisions,
the name of the nested model followed by ::
is prepended to the names of
these links and joints. The following example shows how this works. Consider
the following model named ChildModel
and its parent model ParentModel
:
<model name="ChildModel">
<link name="L1">
<pose>0 1 0 0 0 0</pose>
<visual name="v1">
<geometry>
<sphere>
<radius>0.1</radius>
</sphere>
</geometry>
</visual>
</link>
<link name="L2"/>
<joint name="J1" type="revolute">
<parent>L1</parent>
<child>L2</child>
</joint>
</model>
<model name="ParentModel">
<include>
<uri>/path/to/ChildModel</uri>
<pose>1 0 1 0 0 0</pose>
</include>
</model>
The result of processing ParentModel
results in the following model (as of
libsdformat 9.2
):
<model name="ParentModel">
<frame name="ChildModel::__model__" attached_to="ChildModel::L1">
<pose relative_to="__model__">1 0 1 0 0 0</pose>
</frame>
<link name="ChildModel::L1">
<pose relative_to="ChildModel::__model__">0 1 0 0 0 0</pose>
<visual name="v1">
<geometry>
<sphere>
<radius>0.1</radius>
</sphere>
</geometry>
</visual>
</link>
<link name="ChildModel::L2">
<pose relative_to="ChildModel::__model__">0 0 0 0 0 0</pose>
</link>
<joint name="ChildModel::J1" type="revolute">
<parent>ChildModel::L1</parent>
<child>ChildModel::L2</child>
</joint>
</model>
Note Due to a bug in libsdformat, the
xyz
vector of joint axes in nested models is always interpreted to be expressed in the model frame regardless of the value of the<use_parent_model_frame>
element.Note: Before
libsdformat 9.2
, the flattened model had poses merged together and did not leverage the nested model's frame.
Characteristics of nested models
Whether nested by direct definition or through the use of the <include>
tag,
the following characteristics are true about nested models.
Pose and nested models
The pose of a nested model is expressed with respect to the parent model. Consider the following example.
<model name="Pm">
<include>
<uri>/path/to/sphere</uri>
<pose>0 0 0.5 0 0 0</pose>
<name>sphere</name>
</include>
<model name="M1">
<pose>0 1 0 0 0 0</pose>
<link name="L1">
<pose>1 0 0 0 0 0</pose>
</link>
</model>
</model>
The pose of the sphere
model is translated 0.5 meter in the z
direction
relative to the origin of Pm
.
The poses of links inside a nested model are expressed with respect to the
frame of the model that contains them. In the example above, the pose of link
L1
is translated 1 meter in the x
direction relative to the origin of M1
, which, in turn is translated 1 meter in the y
direction relative to the origin
of Pm
.
Joints and nested models
Joints can connect links from the parent model to links in the nested model.
Normal rules apply for referencing links found in the parent model. Links in
nested models are referenced by using a model_name::link_name
syntax where
model_name
is the name of the model as defined by the <name>
of the model
definition or as overridden by the <include><name>
. While this syntax is
imposed by libsdformat
when model's are composed using the <include>
tag by
virtue of it prepending link and joint names with model_name::
, the current
specification does not dictate how child elements are referenced. Nevertheless,
this documentation assumes this syntax as the convention for referencing nested
elements.
The link from the nested model can be referenced in either the <parent>
or
<child>
elements of the joint. Note, however, that libsdformat
does not
currently check that nested model links referenced by joints are valid. This
responsibility is left for downstream applications.
The following snippet demonstrates a fixed joint between one link in the parent model and another in a nested model:
<model name="spheres">
<link name="body"/>
<include>
<uri>/path/to/sphere</uri>
<pose>0 0 0.5 0 0 0</pose>
<name>sphere</name>
</include>
<joint name='j1' type='fixed'>
<parent>body</parent>
<child>sphere::body</child>
</joint>
</model>
A joint in a parent model can reference links in nested models as its
<parent>
or <child>
element. That is, a joint can be created between two
links that reside in two separate instances of nested models. The following
snippet demonstrates this usage:
<model name="spheres">
<link name="body"/>
<include>
<uri>/path/to/sphere</uri>
<pose>0 0 0.5 0 0 0</pose>
<name>sphere1</name>
</include>
<include>
<uri>/path/to/sphere</uri>
<pose>1 0 0.5 0 0 0</pose>
<name>sphere2</name>
</include>
<joint name='j1' type='fixed'>
<parent>sphere1::body</parent> <!-- Link contained in model sphere1 -->
<child>sphere2::body</child> <!-- Link contained in model sphere2 -->
</joint>
</model>
A joint in a child model can access a link in the parent model or a sibling
model with the syntax model_name::link_name
. However, this is not recommended
as it breaks encapsulation. The model is no longer standalone, and requires
hard coding the name of the parent sibling model in the definition of the child
model.
Using <include>
in <world>
When the <include>
tag is used in worlds the included model is simply copied
into the world with its properties modified as specified by elements such as
<include><name>
and <include><pose>
. Other than these modifications, the
model behaves as though it was defined directly inside the world element. The
child elements of the <include>
tag, such as <uri>
, <name>
and <pose>
are processed by libsdformat
as described in the Including
a model section.
The following example shows the use of the <include>
tag to create a world
that contains multiple instances of the sphere
model shown in the earlier
example.
<world name="W">
<include>
<uri>/path/to/sphere</uri>
<pose>0 0 0.5 0 0 0</pose>
</include>
<include>
<uri>/path/to/sphere</uri>
<name>sphere1</name>
<pose>0 0 1 0 0 0</pose>
</include>
<include>
<uri>/path/to/sphere</uri>
<name>sphere2</name>
<pose>1 0 1 0 0 0</pose>
</include>
</model>