【ROS-Gazebo】ERB:模块化生成SDF模型

1139
0
2020年5月15日 09时40分

前言

这是一个系列小文章,主要介绍在ROS-Gazebo中如何更好地使用SDF格式建模与仿真。众所周知,URDF是ROS的原生支持格式,但在某些情况下(尤其是Gazebo仿真时),使用SDF格式会更加合理。鉴于中文网络上几乎没有成体系的SDF介绍博文,因此我将自己在硕士期间关于SDF模型的使用经验稍作总结。如有谬误,还望友好地指出。

 

本系列规划如下内容,建议按顺序阅读。尤其是第一篇,以确认你是否有必要使用SDF建模:

 

1 ERB是什么?为什么要使用ERB?

熟悉URDF格式的小伙伴们都知道,我们在编写大型URDF文件时,一般不直接在URDF中操作,而是编写对应的.xacro。xacro可以定义宏变量,并将代码模块化,既减少了代码量,又节省了建模、调试时间。

 

那么,SDF模型可不可以使用xacro来编写呢?很遗憾,答案是否定的。然而好消息是,SDF也有可以使用的替代方法——嵌入式Ruby(Embedded Ruby),简称ERB。ERB常用来生成Web页面,但也可以用来生成诸如XML、RSS这样的结构化文件。

 

什么时候需要使用ERB呢?当我们的模型十分庞大复杂,尤其是存在许多相似关节,例如多个车轮的移动机器人、模块化机械臂、对称结构的机器人等,都可以采用ERB方法,既减少了代码量,又可参数化地调整相关变量,十分方便。

 

下面,我们就来看看怎样使用ERB来编写和生成SDF文件。

 

2 ERB基本语法

2.1 表达式

 

形如下面的代码块称为表达式,是由标签和等号组成的封闭代码。表达式用来将代码直接嵌入模板,或将某个变量替换为相应的值。下面的代码将会在渲染时被替换为name变量所代表的内容。

 

<%= @name %>

 

2.2 脚本

 

形如下面的代码块称为脚本,是由标签(而没有等号)组成的不封闭代码。脚本的运行结果将会被插入最终的代码。下面的代码将会执行一个循环,将shopping_list中的所有元素依次插入代码中。

 

<% for  @item in @shopping_list %>
  <%= @item %>
<% end  %>

 

默认情况下,每个标签后面都会添加一个换行符。如果不想要换行符,可以通过如下方法抑制换行符:

 

<% for  @item in @items -%>
  <%= @item %>
<% end  -%>

 

2.3 注释

 

用如下方法添加注释:

 

<%# This is just a comment %>

 

3 使用ERB生成SDF模型

Gazebo官方模型库中,有一部分模型就是用ERB生成的。这里直接借用一个汽车模型来说明ERB的使用方法。模型的链接和图片如下,这是一个四轮小车:官方模型:cart_front_steer​bitbucket.org

 
 
【ROS-Gazebo】ERB:模块化生成SDF模型插图

 

首先看看模型文件夹中有哪些内容,其中model.configmodel.sdf分别是配置信息和模型文件,是SDF模型中必须存在的两个文件。model.rsdf 是为了模块化生成SDF而建立的ERB代码文件,也就是说,我们的操作对象是model.rsdf,而不是model.sdf。

 

【ROS-Gazebo】ERB:模块化生成SDF模型插图(1)图1 常用模型文件夹中的内容

 

对照.rsdf和.sdf文件,可以看到ERB语法在建模中主要有两种用法:表达式嵌入变量、脚本嵌入代码块。嵌入变量可以减少我们后期参数调试的工作量,嵌入代码块可以减少模型的代码量。

 

3.1 用表达式嵌入变量

在.rsdf中,首先用一个不封闭的标签<% %>定义需要用到的值。可以看到,数值既可以定义为常量,也可以定义为变量表达式。在示例代码中,位置坐标定义为常量,惯量定义为变量表达式:

 

<%
...
  # Geometry
  chassis_dx   = 1.0
...
  # inertia
  chassis_ixx  = chassis_mass/12.0 * (chassis_dy**2 + chassis_dz**2)
...
%>

 

在接下来开始写模型的代码,用表达式<%= name %>的形式嵌入了先前定义的变量。如下代码嵌入了惯性矩阵在xx方向的元素:

 

...
<inertia>
          <ixx><%= chassis_ixx %></ixx>
...

 

3.2 用脚本嵌入代码块

除了直接嵌入变量外,还可以用脚本嵌入代码块。尤其是使用循环脚本,可以生成大量相似的link和joint。

 

首先还是在文件开头的不封闭的标签<% %>中定义了前轮和后轮的位置:

 

<%
...
  wheel_x0     = chassis_dx*0.5
  wheel_y0     = chassis_dy*0.5 + wheel_width*0.6
...  
  front_wheel_locations = {
    "front_left"  => {:x0 => wheel_x0, :y0 => wheel_y0 },
    "front_right" => {:x0 => wheel_x0, :y0 => -wheel_y0 },
  }
  rear_wheel_locations = {
    "rear_left"   => {:x0 => -wheel_x0, :y0 => wheel_y0 },
    "rear_right"  => {:x0 => -wheel_x0, :y0 => -wheel_y0 },
  }
...
%>

 

接下来在模型代码中使用了2个each..do..循环,分别创建了两个前轮和后轮的link和joint。下面展示了创建前轮的循环:

 

    <%
      front_wheel_locations.keys.each do |k|
        x0 = front_wheel_locations[k][:x0]
        y0 = front_wheel_locations[k][:y0]
    %>
    <%= "<link name=" + '"wheel_' + k + '">' %>
      <pose><%= x0 %> <%= y0 %>
...
    <%= "<joint name=" + '"wheel_' + k + '_steer_spin" type="universal">' %>
...
    <% end %>

 

从model.sdf文件中对照,可以看到上述代码分别创建了wheel_front_left、wheel_front_right两个link,以及wheel_front_left_steer_spin和wheel_front_left_steer_spin两个joint。

 

3.3 从.rsdf生成.sdf

当rsdf编写完毕后,在终端中运行如下命令即可生成对应的.sdf文件。注意:这一步是必须的,因为我们最终使用的是sdf文件,而不是rsdf。

 

erb model.rsdf > model.sdf

 

采用这种方法所生成的SDF文件,和我们直接编写的SDF没有任何区别。如果后续想要批量修改某个数值,只需要在.rsdf中修改相应的变量,再重新生成SDF即可。

 

参考资料

[1] Gazebo:模型结构教程

[2] erb简介

[3] Gazebo官方模型

发表评论

后才能评论