Parameter passing tutorial
This documentation explains the implementation proposed in the Parameter passing proposal.
Prerequisites:
Introduction
Parameter passing extends on using the //include
tag to pass additional arguments
to SDFormat files, which allows a user to send custom data into a model file and
reduce the need for file duplication.
Limitations
Manipulating custom elements has limited support but will be addressed in the future (see custom elements for details). Also, since plugins do not require unique names, referencing the correct plugin from the original file may not be possible. Only the first plugin with the provided name can be referenced for modification.
Updating included model with //include/experimental:params
A model can be constructed using model composition and include components of the
model using the //include
tag.
To modify parameters of the included model, the experimental custom element
//include/experimental:params
can be used to 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 arugments 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 reference elements from the original model
and will specify new values and/or elements to be updated, added, and/or removed.
Specify element using element_id
To refer to 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 element_id
is
the name of all the parent elements leading to the specified element separated by
double colons (::
) except for the //model
name.
For example, given an original model:
<!-- original model -->
<model name="robot">
...
<link name="chassis">
<visual name="camera_visual">
...
</visual>
<sensor name="camera_sensor">
<camera name="camera">
...
<image>
<width>320</width>
<height>240</height>
</image>
</camera>
</sensor>
</link>
...
</model>
To specify the //camera
element, then element_id="chassis::camera_sensor::camera"
where "chassis" is the name of the //link
, "camera_sensor" is the name of //link/sensor
,
and "camera" is the name of the //link/sensor/camera
.
This is how the correct element will be identified and will be called the element identifier.
A corresponding action
needs to be specified with the identified element to
dictate the desired alteration using the action
attribute.
The action
attribute must be provided in the element identifier or in the direct
children of the identifier.
Depending on the action
used, if the action
is specified in the element identifier
(i.e., where element_id
is specified) then any children elements listed will
follow the same action. If the action
is specified in the direct children, then
each child will follow the stated action
. Let's look at an example.
Using the original model example above, the following snippet would live in the
model/world file (that uses the //include/experimental:params
tag):
...
<include>
<uri>/path/to/original/model</uri>
...
<experimental:params>
<!-- action stated in element identifier -->
<visual element_id="chassis::camera_visual" action="remove"/>
<!-- actions stated in direct children of element identifier -->
<sensor element_id="chassis::camera_sensor">
<camera name="camera" action="modify">
<image>
<width>1280</width>
</image>
</camera>
<plugin name="camera_plugin" filename="/path/to/plugin" action="add"/>
</sensor>
</experimental:params>
</include>
...
The next section details each available action and provides several examples. For a full combined example, please see example 1 from the proposal.
Summary of available actions:
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
Parameter passing works off the original model using a top-down approach so subsequent actions can manipulate newly added/updated elements but need to refer to any new changes (see Replace examples > Example 1).
Add action
The add
action adds new elements to the original model.
When the add
action is declared where @element_id
is declared,
then the @name
attribute is required for adding elements
such as a //link
, //visual
, //sensor
, etc.
When the action is declared in one of the immediate children elements of
@element_id
, then the @name
attribute is not required unless it is required
by SDFormat specification.
When element_id=""
then it assumed that the element to be added is a direct
child of the original model.
Add examples
Example 1
Given the original model has a //link[@name="chassis"]
, the below example
adds the //visual[@name="camera_visual"]
element as a direct child of
//link[@name="chassis"]
:
<!-- In //experimental:params -->
<visual element_id="chassis" name="camera_visual" action="add">
<pose>1 0 0 0 0 0</pose>
<geometry>
<box>
<size>0.05 0.05 0.05</size>
</box>
</geometry>
</visual>
The following example is equivalent (but it is recommended to add
the element
following the structure above when the element requires the @name
attribute):
<!-- //experimental:params -->
<link element_id="chassis">
<visual name="camera_visual" action="add">
<pose>1 0 0 0 0 0</pose>
<geometry>
<box>
<size>0.05 0.05 0.05</size>
</box>
</geometry>
</visual>
</link>
In the above example, the add
action is specified in a direct child of where
@element_id
is declared.
Example 2
For adding elements that do not have the @name
attribute
(e.g., //transparency
, //pose
, //inertial
, or //axis
),
specify the @action
in the direct children of the @element_id
element.
<!-- //experimental:params -->
<link element_id="chassis">
<inertial action="add">
<mass>1</mass>
<inertia>
...
</inertia>
</inertial>
</link>
<!-- //experimental:params -->
<visual element_id="top::camera_visual">
<transparency action="add">0.5</transparency>
<pose relative_to="something" action="add">0 0 1 0 0 0</pose>
</visual>
Example 3
To add a new element as a direct child of the included/original model, use element_id=""
:
<!-- //experimental:params -->
<frame element_id="" name="some_frame" action="add">
<pose>0 1 0 0 0 0</pose>
</frame>
This adds the //frame[@name="some_frame"]
to the included //model
(i.e., the output would be //model/frame[@name="some_frame"]
).
Modify action
The modify
action provides the ability to modify values and/or attributes of elements.
This action only updates existing elements and does not add or remove them.
It can update or add attributes to existing elements but can not remove them.
To modify
elements and/or attributes, the full element path to that element is needed with the new value(s).
For example, if the original model has a //visual[@name="visual"]/geometry/cylinder[radius=0.5]
and modifying only the //radius
is desired, then under //experimental:params
:
<visual element_id="path::to::visual" action="modify">
<geometry>
<cylinder>
<radius custom:attr="foo">1.0</radius>
</cylinder>
</geometry>
</visual>
This modifies the original //radius
from 0.5 to be 1.0 (//length
is left unchanged)
and adds a new attribute @custom:attr="foo"
to the element.
If the original //visual
has other children elements,
these elements are left unchanged since they are not provided under //experimental:params
.
To modify an attribute, the attribute must be specified in the desired element
and if the element has a value (not children elements) then the value must be provided as well
even if it is desired to leave the value unchanged (see modify
example 2).
If the element does have child elements then only providing the attribute is needed (see example 1).
If no attributes are provided, the original attributes will be left unchanged.
Although, it is not possible to remove an attribute, a work around is to assign
the attribute an empty value (e.g., //pose[@relative_to=""]
).
The @name
attribute can only be modified when the modify
action is defined in the @element_id
element.
Modify examples
Example 1
To modify
a name attribute only, in this case the original model has //visual[@name="visual"]
:
<!-- //experimental:params -->
<visual element_id="path::to::visual" name="new_name" action="modify"/>
this only modifies the @name
of the //visual
to be new_name
.
There are no other listed attributes or child elements so nothing else is changed.
To modify
child elements and not the @name
attribute provide
the full path to the element to be modified with the updated value (see above example).
Example 2
Looking at an //inertial
example, if the original model has:
<!-- included model -->
...
<link name="chassis">
<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>
...
</link>
...
To modify
the @relative_to
, //mass
, and some //inertia
elements:
<!-- //experimental:params -->
<link element_id="path::to::chassis" 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>
Note that //pose
values are the same values as the original.
Currently, there is no way to specify if element values should remain the same
so they must be provided (this can only be done with attributes by not providing them).
The expected updated model would be:
<!-- updated included model -->
...
<link name="chassis">
<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>
...
</link>
...
Remove action
The remove
action removes elements from the original model.
When remove
is declared where @element_id
is specified, the entire element is removed.
Any listed children elements are ignored.
When remove
is declared in the direct children of @element_id
there are 2 cases to consider.
If the direct child (where remove
is defined):
- Has no children elements, the entire element is removed (see Remove examples > Example 1)
- Has children, the individual children are removed but not the element where
remove
is defined (see Remove examples > Example 2)
When remove
is declared in the direct children and that element has a @name
attribute,
the @name
should be provided. If @name
isn't specified
(when @name
exists in the original model) and there are multiple elements
with the same tag in the original model, the first element found with the same tag is assumed.
It is better to specify the element directly using @element_id
when possible to
avoid potential confusion.
See below remove
examples for more details.
Remove examples
Example 1
Given the original model has a //link[@name="chassis"]/visual[@name="camera_visual"]
,
to remove
the //visual[@name="camera_visual"]
element:
<!-- //experimental:params -->
<visual element_id="chassis::camera_visual" action="remove"/>
This removes the entire //visual
element (including all its children) from the original model.
If there are attributes or children declared where @action
is defined,
they are ignored. For example,
<!-- //experimental:params -->
<visual element_id="chassis::camera_visual" action="remove" some="attribute">
<material>
...
</material>
</visual>
The attribute some="attribute"
and //material
elements are ignored since
the entire //visual
element is being removed.
The following example is equivalent (but it is recommended to remove
the element
following the structure above if removing the entire element is desired):
<!-- //experimental:params -->
<link element_id="chassis">
<visual name="camera_visual" action="remove"/>
</link>
In this situation, stating @name
is not required but is encouraged
since there could exist multiple //visual
elements in the original.
When there are multiple, the first found //visual
element found is assumed to be removed.
Example 2
For removing elements that do not have the @name
attribute
(e.g., //transparency
, //material
, or //inertial
),
specify the @action
in the direct children of the @element_id
element.
<!-- //experimental:params -->
<visual element_id="chassis::camera_visual">
<material action="remove"/>
</visual>
In the above example, since //material
does not have listed children elements,
the entire //material
element is removed.
To remove only individual children of //material
, list them as follows:
<!-- //experimental:params -->
<visual element_id="chassis::camera_visual">
<material action="remove">
<diffuse/>
<ambient/>
</material>
</visual>
This removes, //diffuse
and //ambient
from //material
but not //material
.
Note that it is not possible to remove further sub elements. For example, if the original model has:
<!-- included model -->
<joint name="joint" type="revolute">
...
<axis>
...
<dynamics>
...
<damping>0.2</damping>
</dynamics>
</axis>
</joint>
//damping
can not be removed using the remove
action, only //dynamics
can be.
Use the replace
action to replace //dynamics
with the desired instead
(see replace
action example 2).
Replace action
The replace
action replaces elements from the original model to the new provided elements.
Elements can only be replaced with the same type of elements
(e.g., a //link
can not be replaced by a //visual
).
When using the replace
action where @element_id
is declared,
then the element requires a @name
attribute (even if the old name is being used)
since we are replacing the original element with the newly provided.
Similar to remove
, if replace
is stated in the direct children of the
@element_id
element then the @name
attribute is used to find the child of @element_id
.
If none is provided, the first matching element (with the same tag) is assumed to be replaced.
Replacing @name
with a new @name
can only be done when the replace
action
is defined in the @element_id
element.
Replace examples
Example 1
Given the original model has a //link[@name="chassis"]/visual[@name="camera_visual"]
,
to replace
the //visual[@name="camera_visual"]
element:
<!-- //experimental:params -->
<visual element_id="chassis::camera_visual" name="camera_visual" action="replace">
...
</visual>
This replaces the original element with the provided element above
but notice the @name
attribute will remain the same.
The following example is equivalent but is not recommended:
<!-- //experimental:params -->
<link element_id="chassis">
<visual name="camera_visual" action="replace">
...
</visual>
</link>
In this situation, stating @name
is not required but is encouraged
since there could exist multiple //visual
elements in the original.
When there are multiple, the first found //visual
element found is assumed to be replaced.
Also, in this case, the @name
can not be changed.
To replace
the element with a new name:
<!-- //experimental:params -->
<visual element_id="chassis::camera_visual" name="new_name" action="replace">
...
</visual>
It is possible to further alter this //visual
element but will need to refer
to the new element @name
:
<!-- //experimental:params -->
<visual element_id="chassis::new_name" action="modify">
<!-- modifications -->
</visual>
Example 2
For replacing elements that do not have the @name
attribute
(e.g., //material
, //inertial
, or //axis
),
specify the @action
in the direct children of the @element_id
element.
For example, if the original model has:
<!-- included model -->
<joint name="joint" type="revolute">
...
<axis>
...
<dynamics>
...
<damping>0.2</damping>
</dynamics>
</axis>
</joint>
to replace
//axis
:
<!-- //experimental:params -->
<joint element_id="path::to::joint">
...
<axis action="replace">
...
<dynamics>
...
<damping>0</damping>
</dynamics>
</axis>
</joint>
Custom elements
Currently, the only way to add
a custom element is by setting the action
on a non-custom parent element. For example, we can use link
as the non-custom parent element to add foo:custom_elem
:
<!-- //experimental:params -->
<link element_id="" name="link1" action="add">
<foo:custom_elem>foo</foo:custom_elem>
</link>
The alterations that can be done are modify
or remove
when the action
is stated in the child of @element_id
. For instance,
<!-- //experimental:params -->
<link element_id="link1">
<foo:custom_elem action="modify">bar</foo:custom_elem>
</link>
The expected output of the original model will be:
<link name="link1">
<foo:custom_elem>bar</foo:custom_elem>
</link>