Customising Ignition models using ERB templates
To launch multiple ArduPilot controlled vehicles in the same Ignition session, each vehicle must have a unique control port assigned in the ArduPilot plugin. The issue is that in the model.sdf files, each of the models statically define a fixed port (9002), and the question is how to modify these without asking users to make copies of each model and manually edit the SDF.
The Ignition tutorials suggest using embedded ruby (ERB) templates: Tutorial: ERB Template and provide an example of how to inject multiple copies of a model into a world file.
With some modification, we can use this approach to modify the model files as well as it turns out ERB templates can themselves reference additional templates.
An example: we have two model templates and one world template.
model_cylinder.sdf.erb
<%
# defaults
if !defined?(model_name) then model_name = 'cylinder' end
if !defined?(radius) then radius = 1 end
if !defined?(length) then length = 1 end
# sdf header and footer
header=''
footer=''
if defined?(sdf)
header='<?xml version="1.0" ?>
<sdf version="1.7">'
footer='</sdf>'
end
%>
<%= header %>
<model name="<%= model_name %>">
<link name="base_link">
<visual name="visual">
<geometry>
<cylinder>
<radius><%= radius%></radius>
<length><%= length%></length>
</cylinder>
</geometry>
</visual>
</link>
</model>
<%= footer %>
model_box.sdf.erb
<%
# defaults
if !defined?(model_name) then model_name = 'box' end
if !defined?(x) then x = 1 end
if !defined?(y) then y = 2 end
if !defined?(z) then z = 3 end
# sdf header and footer
header=''
footer=''
if defined?(sdf)
header='<?xml version="1.0" ?>
<sdf version="1.7">'
footer='</sdf>'
end
%>
<%= header %>
<model name="<%= model_name %>">
<link name="base_link">
<visual name="visual">
<geometry>
<box>
<size><%= x %> <%= y %> <%= z %></size>
</box>
</geometry>
</visual>
</link>
</model>
<%= footer %>
example_world.sdf.erb
<?xml version="1.0" ?>
<sdf version="1.7">
<world name="example_world">
<plugin filename="libignition-gazebo-physics-system.so"
name="ignition::gazebo::systems::Physics">
</plugin>
<plugin filename="libignition-gazebo-user-commands-system.so"
name="ignition::gazebo::systems::UserCommands">
</plugin>
<plugin filename="libignition-gazebo-scene-broadcaster-system.so"
name="ignition::gazebo::systems::SceneBroadcaster">
</plugin>
<scene>
<ambient>1.0 1.0 1.0</ambient>
<background>0.8 0.8 0.8</background>
<sky></sky>
</scene>
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.8 0.8 0.8 1</specular>
<attenuation>
<range>1000</range>
<constant>0.9</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
<direction>-0.5 0.1 -0.9</direction>
</light>
<%
def add_box model_name, x, y, z
ERB.new(File.read('model_box.sdf.erb')).result(binding)
end
def add_cylinder model_name, radius, length
ERB.new(File.read('model_cylinder.sdf.erb')).result(binding)
end
%>
<%= add_box('box', 1, 2, 3) %>
<%= add_cylinder('cylinder', 1, 2) %>
</world>
</sdf>
To generate the SDF file for the model:
$ erb -T 1 sdf=true model_box.sdf.erb > model_box.sdf
To generate the SDF file for the world:
$ erb -T 1 example_world.sdf.erb > example_world.sdf
Providing the model.sdf.erb
files with defaults and a conditional ensures the definition of the model is in one place. The template can be used to create the default standalone model that is released, and may also be used to embed the model into new worlds that may be dynamically generated at runtime. If the generation of the world is included in the launch script, the generated world can be placed in a subdirectory along side the subdirectories for each SITL vehicle which keeps everything organised and reduces the need to release many custom worlds for different configurations.
This will also be useful for creating worlds with swarms of vehicles…