Proposal for parameter passing
- Authors:
Jenn Nguyen
<jenn@openrobotics.org>
, Addisu Taddese<addisu@openrobotics.org>
- Status: Draft
- SDFormat Version: 1.7
libsdformat
Version: 10
Introduction
Purpose statement
This proposal suggests extending the composition behavior in SDFormat 1.7
(i.e., using the <include>
tag) to pass additional arguments to SDFormat files
which will allow a user to send custom data into a model file and reduce the
need for model file duplication.
Background
Currently, through the <include>
(i.e., //include
) tag, model parameters
such as //model/name
and //model/pose
may be overwritten but there are
cases when other parameters are desired to be updated as well. For example, if
several robots have the same base but different sensor configurations then
several model files need to be created to compose all the robot variations.
This is an issue because these model files are most likely copies of each other
with certain parameters changed.
Conclusion
The aim of adding additional parameter support is to avoid this duplication,
where a model file contains all configurations with default parameters and a
user may update and/or override certain parameters as desired in a model or
world file when using the //include
tag.
Document summary
- Proposed changes: details the proposed design and the actions available to users
- Example: provides an example of how this proposed approach can be utilized
Motivation
The motivation behind this proposal is to avoid duplication when constructing models. For example, in SubT's robots, X1 has 8-10 different sensor configurations but all have the same base. If we take a look at all the different configurations in Fuel, we see that they are essentially copies of each other but with different parameters for these sensors.
Proposed changes
Parameters will be specified by the user in either model or world files through
the use of model
composition (or nested models)
which will contain //include
tags and
custom elements.
Discussed in this section is the proposed design for specifying parameters and
the actions available to the user. The intent is to provide a means of
manipulating almost all parameters in SDF models through model/world files
without requiring modifications to already existing/created model files.
The new attributes element_id
and action
(detailed below) will be reserved
from being used in other elements in SDFormat.
1. Add custom element //include/experimental:params
In a world file, the user can construct a model using model composition and
include components of the model using the //include
tag. If a user wants to
modify parameters of the included model, then the user will use the experimental
custom element //include/experimental:params
that will describe the
adjustments to elements. In the custom element //include/experimental:params
,
experimental
is the namespace prefix and params
is the custom element. This
approach was chosen so that downstream users can choose to ignore the namespaced
custom element and its contents. The goal is to implement passing additional
arguments in //include
through an experimental custom element in
SDFormat 1.7 / libsdformat
10 then after being vetted it will be made official
in a future SDFormat / libsdformat
release where the custom element will be
changed to //include/params
. The included model (not the constructed model in
the model/world file) will be referred to as the original model/file.
In the model/world file and under //include
, the elements listed under
//include/experimental:params
will indicate elements from the original model
and will specify new values and/or elements to be updated, added, and/or
removed.
Details
To specify an element from the original file, the tag (e.g., //link
,
//sensor
, //visual
) needs to be provided as well as the element_id
attribute
where the element_id
is the name of all the parent elements leading to the
specified element separated by double colons (::
). For example, given:
<link name="chassis">
<sensor name="camera">
<camera name="cam">
...
</camera>
</sensor>
</link>
If the user wants to specify the //camera
element, then
element_id="chassis::camera::cam"
where "chassis" is the name of the //link
,
"camera" is the name of the //link/sensor
, and "cam" is the name of the
//link/sensor/camera
. This is how the correct element will be identified and
will be called the element identifier. Although it is possible to identify the
element without requiring the user to specify the tag, enforcing the user to
provide the tag allows less ambiguity to the user as well as provides a means of
error checking. Then the user will need to specify a corresponding action with
that element to dictate the alteration they would like to make using a new
attribute action
. The attribute takes in a string and the available actions
are:
add
: adds new elements to the original modelmodify
: modifies values and/or attributes of elements. This only updates existing elements and does not add or remove them.remove
: removes the elements from the original modelreplace
: replaces the elements from the original model to the new provided elements
The action
attribute must be provided either in the element identifier or in
the direct children of the identifier.
Alternatives considered
Alternatively, instead of using the proposed element identifier, the user could
use the attribute id
for each customizable element in the original file but
the intent of using the named path (i.e., the name of the parent elements and
itself separated by double colons) was to provide a way for the user to modify
the original model without requiring large amounts of updates to the original
file.
Examples
Example 1
Let's look at an example, here is an original model base_robot
:
<model name="base_robot">
...
<link name="chassis">
<visual name="lidar_visual">
...
</visual>
<sensor name="lidar">
...
</sensor>
<sensor name="camera">
<pose>0.5 0.02 0 0 0</pose>
...
</sensor>
</link>
<link name="top">
<visual name="camera_visual">
...
<geometry>
<box>
<size>0.03 0.03 0.05</size>
</box>
</geometry>
<material>
<ambient>0.5 0.5 0 1</ambient>
<diffuse>0 1 0 1</diffuse>
<specular>0.5 0.5 0.5 1</specular>
</material>
</visual>
<sensor name="camera">
<pose>0.2 1 0.5 0 0 0</pose>
<update_rate>20</update_rate>
...
</sensor>
</link>
Now the user would like to use the original model base_robot
in a world file
but with modifications. The modifications include:
- Removing the lidar from the chassis link
- Adding a plugin to the camera sensor in the chassis link
- Adding a camera visual to the chassis link
- Updating camera information in the top link
- Alterations of a top link's camera visual element:
- Replacing the geometry
- Modifying the material
- Adding transparency
<sdf version="1.7"
xmlns:experimental="http://sdformat.org/schemas/experimental">
<model name="custom_robot">
<include>
<uri>model://base_robot</uri>
<experimental:params>
<visual element_id="chassis::lidar_visual" action="remove"/>
<sensor element_id="chassis::lidar" action="remove"/>
<plugin element_id="chassis::camera" action="add"
name="camera_depth_sensor" filename="libcamera_depth_sensor.so"/>
<visual element_id="chassis" name="camera_visual" action="add">
<pose>0.5 0.02 0 0 0</pose>
<geometry>
<box>
<size>0.02 0.02 0.02</size>
</box>
</geometry>
</visual>
<sensor element_id="top::camera" action="modify">
<pose>0 1 0 0 0 0</pose>
<update_rate>60</update_rate>
</sensor>
<visual element_id="top::camera_visual">
<geometry action="replace">
<sphere>
<radius>0.05</radius>
</sphere>
</geometry>
<material action="modify">
<ambient>0 1 0 1</ambient>
</material>
<transparency action="add">0.5</transparency>
</visual>
</experimental:params>
</include>
</model>
</sdf>
In //include/experimental:params
, the action can be either listed in the
element identifier or listed individually in the children element(s).
For example,
<sensor element_id="top::camera" action="modify">
<pose>0 1 0 0 0 0</pose>
<update_rate>60</update_rate>
</sensor>
The action is defined at the top level where the element_id
is, which means
that all children listed in this element will be modified. If the user wants to
do several different actions to the children elements then the user can specify
the action for each child element individually. For instance,
<visual element_id="top::camera_visual">
<geometry action="replace">
<sphere>
<radius>0.05</radius>
</sphere>
</geometry>
<material action="modify">
<ambient>0 1 0 1</ambient>
</material>
<transparency action="add">0.5</transparency>
</visual>
If the original model has several corresponding elements, it will be up to the
user to update all appropriately. For example, if a lidar has a //visual
and
//sensor
element and the user would like to remove the lidar information, then
both will need to be listed for removal:
<visual element_id="chassis::lidar_visual" action="remove"/>
<sensor element_id="chassis::lidar" action="remove"/>
When a user wants to add
an element that does not exist in the original model,
then the add
action can be used similarly as follows:
<visual element_id="chassis" name="camera_visual" action="add">
<pose>0.5 0.02 0 0 0</pose>
<geometry>
<box>
<size>0.02 0.02 0.02</size>
</box>
</geometry>
</visual>
In this example, the //visual[@name='camera_visual']
element will be added as a
child of //link[@element_id='chassis']
.
When the action='add'
is specified where element_id
is, then the name
attribute is required.
A check will be performed to ensure that the element does not already exist.
If it does exist, then a warning/error will be printed and the element will be skipped.
The process of skipping
elements will also occur when the user uses the modify
, replace
, and/or
remove
actions and the element is not found in the original model.
Expected output
Below shows what the example's output should be after parsing
//experimental:params
:
<model name="base_robot">
...
<link name="chassis">
<sensor name="camera">
<pose>0.5 0.02 0 0 0</pose>
<plugin name="camera_depth_sensor" filename="libcamera_depth_sensor.so"/>
...
</sensor>
<visual name="camera_visual">
<pose>0.5 0.02 0 0 0</pose>
<geometry>
<box>
<size>0.02 0.02 0.02</size>
</box>
</geometry>
</visual>
</link>
<link name="top">
<visual name="camera_visual">
...
<geometry>
<sphere>
<radius>0.05</radius>
</sphere>
</geometry>
<material>
<ambient>0 1 0 1</ambient>
<diffuse>0 1 0 1</diffuse>
<specular>0.5 0.5 0.5 1</specular>
</material>
<transparency>0.5</transparency>
</visual>
<sensor name="camera">
<pose>0 1 0 0 0 0</pose>
<update_rate>60</update_rate>
...
</sensor>
</link>
Example 2
If the original model instead contained:
...
<link name="top">
<pose relative_to="some_frame">1 0 0 0 0 0</pose>
<inertial>
<mass>1.14395</mass>
<inertia>
<ixx>0.126164</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.416519</iyy>
<iyz>0</iyz>
<izz>0.481014</izz>
</inertia>
</inertial>
<visual name="camera_visual">
...
<geometry>
<sphere>
<radius>0.1</radius>
</sphere>
</geometry>
</visual>
</link>
...
And the user would like to modify the radius of the sphere in the //visual
element. Then under //experimental:params
the user would specify:
<visual name="top::camera_visual">
<geometry action="modify">
<sphere>
<radius>0.05</radius>
</sphere>
</geometry>
</visual>
To modify the attribute of the //pose
and values under the //inertial
element the user would specify under //experimental:params
:
<link element_id="top" action="modify">
<pose relative_to="new_frame">1 0 0 0 0 0</pose>
<inertial>
<mass>2.637</mass>
<inertia>
<ixx>0.02467</ixx>
<iyy>0.04411</iyy>
<izz>0.02467</izz>
</inertia>
</inertial>
</link>
The expected output after parsing would be:
...
<link name="top">
<pose relative_to="new_frame">1 0 0 0 0 0</pose>
<inertial>
<mass>2.637</mass>
<inertia>
<ixx>0.02467</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.04411</iyy>
<iyz>0</iyz>
<izz>0.02467</izz>
</inertia>
</inertial>
<visual name="camera_visual">
...
<geometry>
<sphere>
<radius>0.05</radius>
</sphere>
</geometry>
</visual>
</link>
...
When the user wants to modify an attribute, the attribute must be specified as well as
the values regardless if they are being changed or not. If there is no desire to
modify the attribute, then not listing the attribute will keep the original value
(similar to not listing elements).
For example, if //pose
was listed in //experimental:params
as:
<link element_id="top" action="modify">
<pose>0 0 0 0 0 3.14</pose>
...
</link>
Then the expected output for //pose
would be:
<pose relative_to="some_frame">0 0 0 0 0 3.14</pose>
Ease of parameter modification
This section discusses how this proposed functionality provides easier parameter modification instead of using only model composition.
First, adding a //plugin
to already existing elements (which can not be done
using model composition) such as a //link/sensor
or //link/joint
can quickly
be done using the add
action.
Also, adding elements including a //sensor
to an existing element can easily
be done using the add
action. If done through model composition, then a dummy
link and joint would need to be created to attach the //sensor
to the desired
element.
Lastly, this provides a way to update values such as
//sensor/camera/update_rate
or //sensor/camera/topic
using the action
modify
or replace
. This can not be easily done with composition, if another
value is desired for a particular model then a duplicate model containing the
other value must be created.