- 机器人
【免费下载链接】gz-sim
Open source robotics simulator. The latest version of Gazebo.
ERB(Embedded Ruby)是一种把 Ruby 代码嵌入文本模板的模板语言。在 gz-sim(Gazebo Sim)中,仿真世界几乎全部由 SDFormat 描述,而 ERB 可以在编写.sdf文件时直接嵌入循环、条件判断与 Ruby 数学运算,从而用几十行代码生成包含成千上万个实体的仿真世界。本篇教程以「用 ERB 生成 1000 个盒子」为起点,带你掌握 ERB 模板的语法、命令行传参、生成与运行 SDF 的完整流程,并深入仓库中的真实用例,理解 ERB 在群体仿真、性能基准与分布式仿真中的实战价值。
本文对应仓库文档 tutorials/erb_template.md,并在examples/worlds/、examples/scripts/、test/worlds/等目录中提供了完整可运行的 ERB 模板与生成产物,读者可边读边对照源码验证。
为什么使用 ERB
SDF 文件本身只是结构化文本,并不具备「生成」能力。当需要往世界中放置大量相同或相似的模型时,手动复制粘贴<model>标签既低效又容易出错。ERB 的价值在于把 Ruby 编程能力引入 SDF 编写过程,典型的收益包括:
- 在 SDF 中嵌入逻辑:使用循环(
for)和条件判断(if)批量生成实体,无需逐条手写标签; - 完整使用 Ruby 数学库:从简单的常量
PI,到矩阵运算、随机数生成(rand、srand),都能直接在模板中调用; - 拆分 SDF 为多个小文件:将可复用的片段组织成模板,便于维护与组织;
- 批量放置同一模型的多个实例:同一段
<model>代码配合循环即可生成任意数量的实例,每个实例拥有不同的名称与位姿,避免手工复制粘贴。
在仓库中,ERB 教程被列入 tutorials.md.in 的 Advanced(进阶)章节;整个仓库一共提供了 5 个.erb模板,覆盖不同的实战场景:
| 模板文件 | 用途 |
|---|---|
| examples/worlds/shapes_population.sdf.erb | 循环生成 3000 个静态几何体(box/cylinder/sphere 各 1000 个) |
| examples/worlds/actors_population.sdf.erb | 用嵌套循环生成r × c个 actor,支持命令行传参 |
| examples/scripts/distributed_levels/distributed_levels.sdf.erb | 按type参数生成分布式仿真的 primary/secondary 世界 |
| test/worlds/benchmark.sdf.erb | 带随机种子与瓦片类型的程序化世界生成器 |
| test/worlds/fluid_added_mass.sdf.erb | 遍历数组生成 21 个流体附加质量测试模型 |
原文档还曾引用开源项目srcsim中的unique.world.erb作为综合范例,本节列出的四个优势在仓库中均有对应的落地实现,下文会逐一展开。
准备 Ruby 环境
ERB 模板由 Ruby 解释执行,因此首先需要确认系统已安装 Ruby。如果你已经按照 gz-sim 的官方安装指南完成环境搭建,Ruby 通常已随依赖一并安装。
检查 Ruby 是否可用:
ruby --version如果提示命令未找到,安装 Ruby:
sudo apt-get install -y ruby安装完成后,负责渲染模板的命令行工具erb也会随 Ruby 一并可用(后续章节会用到)。在 Ubuntu / Debian 系发行版上,这就是生成 SDF 所需的全部外部依赖。
创建第一个 ERB 模板
为了与普通 SDF 文件区分,ERB 模板习惯上使用.erb作为文件后缀,例如my_first_erb.erb。ERB 语法直接内嵌在 SDF 文本中,用两对特殊定界符标记 Ruby 代码:
<% ... %>:执行 Ruby 代码,但不向输出写入内容(用于for、if、变量赋值等逻辑);<%= ... %>:执行 Ruby 表达式,并把求值结果写入输出(用于插入模型名、位姿数值等)。
第一步:搭建 SDF 骨架
先按 SDFormat 语法创建一个空世界。需要同时声明 XML 与 SDF 的版本号,并给世界命名:
<?xml version="1.0" ?> <sdf version="1.6"> <world name="shapes"> <!-- This is where you put `<plugin>`, `<model>` and etc. tags --> </world> </sdf>仓库中的模板同样使用<sdf version="1.6">版本,例如 examples/worlds/shapes_population.sdf.erb。
第二步:用 for 循环生成 1000 个盒子
在<world>标签之间插入下面的 Ruby 循环。代码用for m in (0..n)遍历 0 到 1000 的整数,每轮迭代都输出一个完整的<model>标签;<% end %>标记循环或if语句的结束。每个盒子的模型名和位姿都依赖变量m计算,从而保证它们在仿真中彼此独立、各不相同:
<% # number of population n = 1000 for m in (0..n) %> <model name="box_<%= m.to_s %>"> <pose><%= -750 + m*1.5 %> 0 0.5 0 0 0</pose> <link name="box_link"> <inertial> <inertia> <ixx>1</ixx> <ixy>0</ixy> <ixz>0</ixz> <iyy>1</iyy> <iyz>0</iyz> <izz>1</izz> </inertia> <mass>1.0</mass> </inertial> <collision name="box_collision"> <geometry> <box> <size>1 1 1</size> </box> </geometry> </collision> <visual name="box_visual"> <geometry> <box> <size>1 1 1</size> </box> </geometry> <material> <ambient>1 0 0 1</ambient> <diffuse>1 0 0 1</diffuse> <specular>1 0 0 1</specular> </material> </visual> </link> </model> <% end %>对这段模板做三点解读:
<%= m.to_s %>把整数m转成字符串后嵌入name="box_0"、name="box_1"…… 每个模型名唯一;<%= -750 + m*1.5 %>让盒子沿 X 轴以 1.5 米为间隔从 -750 米开始铺开,Y 与 Z 保持固定(0 0.5),位姿格式为x y z roll pitch yaw;- 注意
(0..n)是闭区间,n = 1000时实际生成的是box_0到box_1000共 1001 个模型。仓库中的 shapes_population.sdf.erb 沿用完全相同的(0..n)写法,需要精确控制数量时请留意这一点。
第三步:在循环内放置多个几何体
仅生成盒子还只是「入门」。仓库中的完整示例 shapes_population.sdf.erb 在同一个循环里定义了三组几何体变量geom_1、geom_2、geom_3(分别对应box、cylinder、sphere),并在每次迭代中输出三个模型:
<% n = 1000 for m in (0..n) geom_1 = 'box' geom_2 = 'cylinder' geom_3 = 'sphere' %> <model name="<%= geom_1 %>_<%= m.to_s %>"> <pose><%= -750 + m*1.5 %> 0 0.5 0 0 0</pose> ... </model> <model name="<%= geom_2 %>_<%= m.to_s %>"> <pose><%= -750 + m*1.5 %> -1.5 0.5 0 0 0</pose> ... </model> <model name="<%= geom_3 %>_<%= m.to_s %>"> <pose><%= -750 + m*1.5 %> 1.5 0.5 0 0 0</pose> ... </model> <% end %>三种几何体被放到不同的 Y 偏移位置,共生成约 3000 个模型。该模板实际生成的 SDF 文件 examples/worlds/3k_shapes.sdf 有超过 11 万行,并且被 test/benchmark/server_run.cc 用作3k_shapes场景,在 bullet 与 dartsim 两种物理引擎下做加载与运行时的性能基准测试——这是 ERB 在「程序化生成大规模测试世界」上的典型应用:手写 11 万行 SDF 不现实,而模板只需百行左右。
第四步:嵌套循环生成 actor 群体
除了简单几何体,还可以用嵌套循环把实体排布成网格。下面这段模板生成 100 个 actor,每个 actor 拥有皮肤(skin)、动画(animation)与带航点(waypoint)的轨迹脚本(script)。内层循环控制 X 方向间隔,外层循环控制 Y 方向间隔:
<% # number of population total = 10 for i in (0..total-1) for j in (1..total) %> <actor name="actor_<%= 10*i+j %>"> <skin> <filename>https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/talk_b.dae</filename> <scale>1.0</scale> </skin> <animation name="talk_b"> <filename>https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/talk_b.dae</filename> <scale>0.055</scale> <interpolate_x>true</interpolate_x> </animation> <script> <loop>true</loop> <auto_start>true</auto_start> <trajectory id="<%= 10*i+j %>" type="talk_b"> <waypoint> <time>0</time> <pose><%= -5 + 2*j %> <%= 5 + -2*i %> 1.0 0 0 0</pose> </waypoint> <waypoint> <time>30</time> <pose><%= -5 + 2*j %> <%= 5 + -2*i %> 1.0 0 0 0</pose> </waypoint> </trajectory> </script> </actor> <% end end %>这里10*i+j把二维下标(i, j)折叠成一维编号,同时用于<actor name>和<trajectory id>,保证每个 actor 及其轨迹的标识唯一;位姿-5 + 2*j、5 + -2*i让 actor 在 XY 平面上呈 2 米间距的方阵排布。
仓库中的 actors_population.sdf.erb 是这一思路的增强版:它把行列数做成命令行参数(默认10 × 10 = 100个 actor),用户可通过erb r=10 c=10 actors_population.sdf.erb随时调整群体规模,详见下一节。
进阶:给 ERB 模板传命令行参数
把参数从外部传入模板,是让模板从「一次性脚本」升级为「可复用生成器」的关键。仓库中的模板普遍采用下面这段惯用写法来解析erb 键=值形式的参数(见 actors_population.sdf.erb):
<% vars = ARGV.take_while {|arg| arg[/^\w+=/]} ARGV.slice!(0, vars.size) vars.each do |var| k, v = var.split('=', 2) TOPLEVEL_BINDING.eval %Q(#{k} = "#{v}") end column = 10 row = 10 if (defined? c) column = c.to_i end for j in (0..column-1) if (defined? r) row = r.to_i end for i in (0..row-1) %>其工作原理是:从ARGV中截取形如r=10、c=10的参数,用TOPLEVEL_BINDING.eval把它们定义成模板内的 Ruby 变量,再通过defined?判断用户是否传入,未传入时使用默认值。这样调用端只需一条命令即可控制输出规模:
# 默认生成 10×10=100 个 actor erb actors_population.sdf.erb > actors_population.sdf # 指定行列,生成 8×8=64 个 actor erb r=8 c=8 actors_population.sdf.erb > actors_population.sdftest/worlds/benchmark.sdf.erb把这一机制发挥到了极致,它支持四个参数并带有默认值与校验:
s:随机种子,默认rand(0..10000000)随机取值,并通过srand(seed)固定随机序列,便于复现同一世界;t:瓦片类型,仅接受tunnel与cave,非法取值会直接raise('Unsupported tile type')终止;n_x:X 方向瓦片数量,默认 20;n_y:Y 方向瓦片数量,默认 5。
配合 Ruby 的sample()随机选取瓦片类型与朝向、Math::PI计算旋转角度,这个模板能在秒级生成一个随机化的大型测试世界(test/worlds/benchmark.sdf.erb)。
examples/scripts/distributed_levels/则示范了参数化在分布式仿真中的应用:同一份模板 distributed_levels.sdf.erb 通过type参数(primary/secondary/standalone)决定输出哪个角色的世界——primary 世界带SceneBroadcaster插件,secondary 世界带Physics插件,standalone 两者都带。其配套 README(examples/scripts/distributed_levels/README.md)给出了完整的三条生成命令,仓库中也已经提交了对应的primary.sdf、secondary.sdf、standalone.sdf生成产物。
从 ERB 模板生成 SDF 文件
模板写好后保存为my_first_erb.erb,在终端执行以下命令即可生成对应的 SDF 文件:
# generate SDF with the ERB template erb my_first_erb.erb > my_first_erb.sdf命令的核心是erb渲染器与 shell 重定向:>把 ERB 渲染产生的文本写入my_first_erb.sdf。
两个值得留意的细节:
缩进控制:模板中
<% %>块与输出文本混排时,默认模式会把部分空白字符也渲染进结果。仓库模板的头部注释给出了erb -T 1的写法(见 examples/worlds/shapes_population.sdf.erb 与 examples/worlds/3k_shapes.sdf),-T 1指定 trim mode,可让模板里的 Ruby 块不残留多余空行,生成更干净的文件:erb -T 1 shapes_population.sdf.erb > shapes_population.sdf规模意识:循环次数很大时,生成的 SDF 会非常庞大(
3k_shapes.sdf超过 11 万行)。生成后可以用wc -l my_first_erb.sdf查看行数,确认输出符合预期。
运行仿真世界并验证
生成 SDF 后,用gz sim命令加载并运行,验证模板是否正确:
# run with Gazebo gz sim my_first_erb.sdf如果运行过程中出现错误或警告,通常说明 ERB 模板本身有编码问题,需要回到.erb文件排查,常见原因包括:
- 循环或
if语句缺少配对的<% end %>; - 插值表达式(
<%= ... %>)内部 Ruby 语法错误; - 生成的模型名重复,或位姿表达式计算出非法数值。
调试时可以先用erb my_first_erb.erb > my_first_erb.sdf单独生成文件并直接检查输出内容,确认无误后再交给gz sim运行,把「模板渲染」与「仿真加载」两个环节的错误分开定位。
小结与进阶方向
围绕 tutorials/erb_template.md,本文完成了从环境准备、模板语法、批量生成、命令行传参到生成与运行验证的完整闭环。掌握这套方法后,你可以进一步在仓库中对照学习:
- 群体仿真:参考 examples/worlds/shapes_population.sdf.erb 与 examples/worlds/actors_population.sdf.erb,理解循环、嵌套循环与参数化;
- 程序化随机世界:参考 test/worlds/benchmark.sdf.erb 的随机种子与瓦片类型机制,用于生成可复现的测试场景;
- 分布式仿真:参考 examples/scripts/distributed_levels/distributed_levels.sdf.erb 及其 README,用同一模板生成不同角色的世界文件;
- 性能基准:观察 test/benchmark/server_run.cc 如何把 ERB 生成的 3k 级实体世界用于引擎加载与运行时的基准测试。
ERB 的真正价值在于「用编程的方式描述世界」:把重复劳动交给循环,把布局交给数学,把参数交给命令行,从而让 SDF 从手写文本变成可维护、可复用、可扩展的世界生成程序。
- 机器人
【免费下载链接】gz-sim
Open source robotics simulator. The latest version of Gazebo.
相关推荐
gz-sim 实体创建指南:使用 /world/create 服务在仿真中生成模型与灯光
gz sim 实体创建指南:使用 /world/create 服务在仿真中生成模型与灯光 导读 本指南介绍 Gazebo Sim(gz sim)通过传输服务 /
机器人gz-sim 分布式仿真实战:用 primary/secondary 角色将物理计算拆分到多个进程与机器
gz sim 分布式仿真实战:用 primary/secondary 角色将物理计算拆分到多个进程与机器 本指南以 examples/scripts/distr
机器人gz-sim 日志回放视频录制实战:使用 LogVideoRecorder 系统批量生成实体跟踪录像
gz sim 日志回放视频录制实战:使用 LogVideoRecorder 系统批量生成实体跟踪录像 本指南以 gz sim(Gazebo 的最新版本)仓库中的
机器人
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考