ROS 系列学习教程(总目录)
本文目录
- 一、属性与属性块
- 二、数学表达式
- 三、宏
- 3.1 宏的基本使用
- 3.2 属性块做为宏的入参
- 3.3 任意数量元素做为宏的入参
- 3.4 指定多个块元素的处理顺序
- 3.5 宏嵌套
- 3.6 默认参数
- 3.7 局部属性
- 四、Rospack 命令
- 五、包含其他 xacro 文件
- 六、条件语句
- 七、YAML 支持
- 八、从 CMakeLists.txt 构建
- 九、处理顺序
- 十、调试语法错误
可以发现 urdf 不支持模块化编程,无法实现代码复用,也不支持数学计算, 代码可读性及复用性非常差,效率极低。
为了解决这一问题,ROS提供了 Xacro ,它是 XML Macros 的缩写,即 XML 宏,是可编程的 XML,支持使用变量、函数、数学公式计算、条件/循环流程控制等。
使用 Xacro 时,根标签 robot
中必须包含命名空间声明 xmlns:xacro = "http://wiki.ros.org/xacro"
<robot name = "xxx" xmlns:xacro="http://wiki.ros.org/xacro">...
</robot>
一、属性与属性块
属性可以理解为变量,属性块可以理解为结构体。
属性示例,将会一个圆柱体的半径和高封装到变量里:
<!-- 定义属性 -->
<xacro:property name="the_radius" value="2.1" />
<xacro:property name="the_length" value="4.5" /><!-- 调用属性 -->
<geometry type="cylinder" radius="${the_radius}" length="${the_length}" />
同样,可以使用属性块封装一个实体,在需要的地方调用:
<!-- 定义属性块 -->
<xacro:property name="front_left_origin"><origin xyz="0.3 0 0" rpy="0 0 0" />
</xacro:property><pr2_wheel name="front_left_wheel"><!-- 调用属性块 --><xacro:insert_block name="front_left_origin" />
</pr2_wheel>
二、数学表达式
Xacro支持基本的数学表达式运算,格式如下:
${ 数学表达式 }
示例如下:
<xacro:property name="radius" value="4.3" />
<circle diameter="${2 * radius}" />
在ROS Jade版本中,Xacro引入了python解析数学表达式,所以,Xacro数学表达式中可以使用Python math包中的函数与常量。示例如下:
<xacro:property name="R" value="2" />
<xacro:property name="alpha" value="${30/180*pi}" />
<circle circumference="${2 * pi * R}" pos="${sin(alpha)} ${cos(alpha)}" />
<limit lower="${radians(-90)}" upper="${radians(90)}" effort="0" velocity="${radians(75)}" />
三、宏
Xacro 宏可以理解为函数,目的是提高代码复用率,优化代码结构,提高安全性。
使用 macro
标记定义宏,并指定宏名称和参数列表,参数列表应以空格分隔。
3.1 宏的基本使用
<!-- 定义宏 -->
<xacro:macro name="add_wheels" params="name flag"><link name="${name}_wheel"><visual><geometry><cylinder radius="${wheel_radius}" length="${wheel_length}" /></geometry><origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" /><material name="black" /></visual></link><joint name="${name}_wheel2base_link" type="continuous"><parent link="base_link" /><child link="${name}_wheel" /><origin xyz="0 ${flag * base_link_radius} ${-(earth_space + base_link_length / 2 - wheel_radius) }" /><axis xyz="0 1 0" /></joint>
</xacro:macro><!-- 调用宏 -->
<xacro:add_wheels name="left" flag="1" />
<xacro:add_wheels name="right" flag="-1" />
该实例定义了一个 add_wheels
的宏,它接受两个参数: name
和 flag
,分别是轮子的名字和方向。
3.2 属性块做为宏的入参
<!-- 定义宏 -->
<xacro:macro name="pr2_caster" params="suffix *origin"><joint name="caster_${suffix}_joint"><axis xyz="0 0 1" /></joint><link name="caster_${suffix}"><xacro:insert_block name="origin" /></link>
</xacro:macro><!-- 调用宏 -->
<xacro:pr2_caster suffix="front_left"><pose xyz="0 1 0" rpy="0 0 0" />
</xacro:pr2_caster>
该示例声明了一个宏 pr2_caster
,它接受两个参数: suffix
和 origin
。请注意, origin
带 *
。这表明 origin
是一个块参数。调用时在子级标签中声名块参数,多个块参数时,按插入顺序处理,如下:
<!-- 定义宏 -->
<xacro:macro name="pr2_caster" params="suffix *origin *color *mass"><joint name="caster_${suffix}_joint"><axis xyz="0 0 1" /></joint><link name="caster_${suffix}"><xacro:insert_block name="origin" /> <xacro:insert_block name="color" /> <xacro:insert_block name="mass" /></link>
</xacro:macro><!-- 调用宏 -->
<xacro:pr2_caster suffix="front_left"><pose xyz="0 1 0" rpy="0 0 0" /> <!-- origin --><color name="yellow"/> <!-- color --><mass>0.1</mass> <!-- mass -->
</xacro:pr2_caster>
3.3 任意数量元素做为宏的入参
<!-- 定义宏 -->
<xacro:macro name="pr2_caster" params="suffix *origin **content **anothercontent"><joint name="caster_${suffix}_joint"><axis xyz="0 0 1" /></joint><link name="caster_${suffix}"><xacro:insert_block name="origin" /><xacro:insert_block name="content" /><xacro:insert_block name="anothercontent" /></link>
</xacro:macro><!-- 调用宏 -->
<xacro:pr2_caster suffix="front_left"><!-- origin --><pose xyz="0 1 0" rpy="0 0 0" /><!-- content --><container><color name="yellow"/><mass>0.1</mass></container><!-- anothercontent --><another><inertial><origin xyz="0 0 0.5" rpy="0 0 0"/><mass value="1"/><inertia ixx="100" ixy="0" ixz="0" iyy="100" iyz="0" izz="100" /></inertial></another>
</xacro:pr2_caster>
该示例声明了一个宏 pr2_caster
,除了前文讲到的参数 suffix
和 origin
,还有content
和anothercontent
,他们前面都带**
,表明他们允许插入任意数量的元素。按照块元素插入顺序,他们分别为 container
和 another
,在他们的子级可以插入任意数量的元素。
3.4 指定多个块元素的处理顺序
上文宏定义中 xacro:insert_block
用于指定插入的块元素,插入的顺序即处理顺序
<!-- 定义宏 -->
<xacro:macro name="reorder" params="*first *second"><xacro:insert_block name="second"/><xacro:insert_block name="first"/>
</xacro:macro><!-- 调用宏 -->
<xacro:reorder><first/><second/>
</xacro:reorder>
处理顺序为 second
-> first
3.5 宏嵌套
宏嵌套即一个宏内调用其他宏,这种宏在被调用时,各宏从外部到内部依次处理。
<!-- 定义宏 foo -->
<xacro:macro name="foo" params="x"><in_foo the_x="${x}" />
</xacro:macro><!-- 定义宏 bar -->
<xacro:macro name="bar" params="y"><in_bar><xacro:foo x="${y}" /></in_bar>
</xacro:macro><!-- 调用宏 bar -->
<xacro:bar y="12" />
调用宏bar
并传入12
,先展开宏bar
,再展开宏foo
,如下:
<in_bar><in_foo the_x="12" />
</in_bar>
3.6 默认参数
宏的入参可以有默认值,如下使用海象运算符 :=
:
<xacro:macro name="foo" params="x:=${x} y:=${2*y} z:=0"/>
如果默认值包含表达式,则它们将在实例化时进行计算。
<xacro:macro name="foo" params="p1 p2:=expr_a p3:=^ p4:=^|expr_b">
符号 ^
表示使用外部属性的值(具有相同名称)。管道 |
表示如果属性未在外部范围中定义,则使用给定的后备值。
3.7 局部属性
在宏中定义的属性和宏是该宏的局部属性和宏,即在外部不可见。使用可选字段 scope="parent | global"
,可以将属性定义导出到宏的父范围(或全局范围)。
四、Rospack 命令
Xacro 允许使用某些 rospack 命令:
<foo value="$(find xacro)" />
<foo value="$(arg myvar)" />
五、包含其他 xacro 文件
可以使用 xacro:include
标签包含其他 xacro 文件:
<xacro:include filename="$(find package)/other_file.xacro" />
<xacro:include filename="other_file.xacro" />
<xacro:include filename="$(cwd)/other_file.xacro" />
为了避免各个包含文件的属性和宏之间发生名称冲突,可以为包含的文件指定命名空间 - 提供属性 ns:
<xacro:include filename="other_file.xacro" ns="namespace"/>
通过在前面添加命名空间(用点分隔)可以访问命名空间的宏和属性:
${namespace.property}
六、条件语句
Xacro同样支持条件语句,示例如下:
<xacro:if value="<expression>"><... some xml code here ...>
</xacro:if>
<xacro:unless value="<expression>"><... some xml code here ...>
</xacro:unless>
其中 <expression>
表达式的结果必须是 0
、1
、false
、true
,否则会报错。
在 ROS Jade 版本中,Xacro引入了python解析表达式,所以,任何计算结果为布尔值的 Python 表达式都是合法的。
<xacro:property name="var" value="useit"/>
<xacro:if value="${var == 'useit'}"/>
<xacro:if value="${var.startswith('use') and var.endswith('it')}"/><xacro:property name="allowed" value="${[1,2,3]}"/>
<xacro:if value="${1 in allowed}"/>
七、YAML 支持
属性也可以是字典或列表 - 使用 python 语法声明,如下所示:
<xacro:property name="props" value="${dict(a=1, b=2, c=3)}"/>
<xacro:property name="props_alt" value="${dict([('1a',1), ('2b',2), ('3c',3)])}"/>
<xacro:property name="numbers" value="${[1,2,3,4]}"/>
或者从 YAML 文件加载,如下所示:
<xacro:property name="yaml_file" value="$(find package)/config/props.yaml" />
<xacro:property name="props" value="${load_yaml(yaml_file)}"/>
从 YAML 文件加载的 xacro 属性被视为字典。 因此,如果props.yaml
被加载到props
xacro 属性中(如上所述)并且包含如下内容:
val1: 10
val2: 20
则可以使用如下方法读取:
<xacro:property name="val1" value="${props['val1']}" />
八、从 CMakeLists.txt 构建
以下代码片段展示了如何在包的 make 调用期间使用 xacro:
# Generate .world files from .world.xacro files
find_package(xacro REQUIRED)
# You can also add xacro to the list of catkin packages:
# find_package(catkin REQUIRED COMPONENTS ... xacro)# Xacro files
file(GLOB xacro_files ${CMAKE_CURRENT_SOURCE_DIR}/worlds/*.world.xacro)foreach(it ${xacro_files})# remove .xacro extensionstring(REGEX MATCH "(.*)[.]xacro$" unused ${it})set(output_filename ${CMAKE_MATCH_1})# create a rule to generate ${output_filename} from {it}xacro_add_xacro_file(${it} ${output_filename})list(APPEND world_files ${output_filename})
endforeach(it)# add an abstract target to actually trigger the builds
add_custom_target(media_files ALL DEPENDS ${world_files})
虽然此 cmake 代码提供了对目标名称和构建顺序的完全控制,但也有一个便捷宏:
file(GLOB xacro_files worlds/*.world.xacro)
xacro_add_files(${xacro_files} TARGET media_files)
如果希望生成 .urdf
文件,可以提供以 .urdf.xacro
结尾的输入文件,CMake 函数将删除 .xacro
后缀。
九、处理顺序
通常的方法是,xacro 首先加载所有 <include>
的内容,然后处理所有属性和宏定义,最后实例化宏并计算表达式。因此,后面的属性或宏定义将覆盖前面的。此外,条件标签 <if>
和 <unless>
对宏或属性定义以及附加文件的 <include>
没有影响。
Jade 中的新功能:
自 ROS Jade 以来,xacro 提供了命令行选项 --inorder
,允许按读取顺序处理整个文档。因此,将使用最后读取的属性或宏。还允许一些不错的新功能:
- 如果将
<include>
标签分别放在宏或条件标签内,则可以推迟或完全抑制文件的包含。 - 可以通过属性或宏参数指定包含文件名。
- 通过在全局范围内改变属性,如果在宏中使用这些属性,宏的实例化可以产生不同的结果。
- 属性定义可以是有条件的。
- 宏可以在内部定义属性而不影响外部的东西。
因为 --inorder
处理功能更加强大,在 Jade 之后的未来版本中,它成为了默认方式,所以应该检查 xacro 文件的兼容性。通常,两种处理方式会给出相同的结果。可以像这样检查:
rosrun xacro xacro file.xacro > /tmp/old.xml
rosrun xacro xacro --inorder file.xacro > /tmp/new.xml
diff /tmp/old.xml /tmp/new.xml
如果两个文件有任何差异,应该检查并调整 xacro 文件。一个常见原因是校准数据(作为属性)加载较晚,在这种情况下,只需将它们移到前面,即使用之前。为了方便搜索错误放置的属性定义,可以使用选项 --check-order
运行 xacro 。如果有任何有问题的属性,将在 stderr 上列出:
Document is incompatible to --inorder processing.
The following properties were redefined after usage:
foo redefined in issues.xacro
使用命令行选项 -vv
或 -vvv
可以增加详细级别来记录所有属性的定义。
十、调试语法错误
要获得更详细的语法错误输出,可以运行以下命令,该命令将 xacro 转成 urdf 并检查语法错误,如果没有安装该命令,可以使用 sudo apt install liburdfdom-tools
安装
cd <path_to_xacro_file>
check_urdf <(xacro model.xacro)
或
xacro model.urdf.xacro > tmp.urdf && check_urdf tmp.urdf && rm tmp.urdf