Xacro(XML Macros)Xacro 是一种 XML 宏语言. 使用 xacro, 可以通过使用宏命令构建更精悍短小但又具有更高可读性的 XML 文件, 这种宏命令可以扩展达到更大的 XML 表达范围. 此包在处理大型 XML 文档(如机器人说明)时最为有用. 它在如 urdf 的包中大量使用. 本文主要来源于官方教程.

目录

1. 案例
2. 属性和属性块
3. 数学表达式
4. 条件块
5. Rospack命令
6. 宏命令
6.1 默认参数
6.2 局部属性
7. 包含其他xacro文件
8. YAML语言支持
9. 用CMakeLists.txt进行构建
10. 元素和属性
11. 处理顺序
12. 已弃用的语法

案例

参考以下的Xacro XML片段:

1
2
3
4
5
6
7
<xacro:macro name="pr2_arm" params="suffix parent reflect">
<pr2_upperarm suffix="${suffix}" reflect="${reflect}" parent="${parent}" />
<pr2_forearm suffix="${suffix}" reflect="${reflect}" parent="elbow_flex_${suffix}" />
</xacro:macro>

<xacro:pr2_arm suffix="left" reflect="1" parent="torso" />
<xacro:pr2_arm suffix="right" reflect="-1" parent="torso" />

上面的片段扩展成为下面的样子:

1
2
3
4
<pr2_upperarm suffix="left" reflect="1" parent="torso" />
<pr2_forearm suffix="left" reflect="1" parent="elbow_flex_left" />
<pr2_upperarm suffix="right" reflect="-1" parent="torso" />
<pr2_forearm suffix="right" reflect="-1" parent="elbow_flex_right" />

如果我们还定义了pr2_upperarmpr2_forearm的宏, 那么这个片段可以扩展以描述整个机器人臂.

本文的其余部分描述了xacro的功能.

属性和属性块

属性是可以插入到XML文档中的任何位置的值. 属性块是XML的名称片段, 可以插入允许XML的任何位置. 两者都使用属性标签来定义值. 属性标签不能在xacro:macro中声明. 以下示例将显示如何声明和使用属性:

1
2
3
4
<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}" />

通过将名称放在dollared-braces (${})中, 你可以将两个属性值插入到几何表达式中. 如果你想要一个文本"{", 你应该将其转义为"$${".
下面是使用属性块的示例:

1
2
3
4
5
6
7
<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>

数学表达式

dollared-braces (${})中, 您还可以编写简单的数学表达式. 目前, 该结构支持基本算术和变量替换. 下面是一个例子:

1
2
<xacro:property name="pi" value="3.1415926535897931" />
<circle circumference="${2.5 * pi}" />

Jade版本的ROS中更新的地方:
ROS Jade以来, Xacro使用python来评估包含在dollared-braces (${})中的表达式. 这允许用户使用更复杂的算术表达式. 此外, 一些基本常数, 例如pi, 已经被预定义过了:

1
2
3
<xacro:property name="R" value="2" />
<xacro:property name="alpha" value="${30/180*pi}" />
<circle circumference="${2 * pi * R}" pos="${sin(alpha)} ${cos(alpha)}" />

条件块

Hydro版本的ROS中更新的地方:
自从ROS Hydro以来, Xacro有类似于roslaunch的条件块. 这对于可配置的机器人或加载不同的Gazebo插件等事情很有用. 它遵循以下语法:

1
2
3
4
5
6
<xacro:if value="<expression>">
<!-- <... some xml code here ...> -->
</xacro:if>
<xacro:unless value="<expression>">
<!-- <... some xml code here ...> -->
</xacro:unless>

表达式需要计算结果为"0", "1", "true""false", 否则将抛出错误.

Jade版本的ROS中更新的地方:
ROS Jade中更强大的评估功能允许使用更复杂的表达式. 几乎任何python表达式的计算结果都是可行的:

1
2
3
4
5
6
<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}"/>

Rospack命令

Xacro允许你使用某些rospack命令和dollared-braces (${}).

1
2
<foo value="$(find xacro)" />
<foo value="$(arg myvar)" />

Xacro目前支持使用substitution argsroslaunch支持的所有rospack命令. 参数需要在命令行中使用myvar := true语法指定.

Indigo版本的ROS中更新的地方:
自从ROS Indigo, 它也可以像如下定义默认值:

1
<xacro:arg name="myvar" default="false"/>

通过这种方式, 你可以像下面这样运行xacro:

1
<param name="robot_description" command="$(find xacro)/xacro.py $(arg model) myvar:=true" />

宏命令

xacro的主要特性是它对宏的支持. 使用宏标签定义宏, 并指定宏名称和参数列表. 参数列表应以空格分隔. 它们变成macro-local属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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">
<pose xyz="0 1 0" rpy="0 0 0" />
<container>
<color name="yellow"/>
<mass>0.1</mass>
</container>
<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", 它有两个参数: suffixorigin. 请注意, “origin”已加星标. 这表明 origin 是一个块参数, 而不是一个简单的文本参数. 看下pr2_caster的使用. suffix属性(property)在pr2_caster标签中定义为属性(attribute), 但没有定义origin属性. 相反, origin指的是第一个元素("pose"块). 双星号版本("content", "anothercontent")允许插入在随后可用的元素(在上面的示例中分别是"container", "another")中传递的任意数量的元素. 此示例扩展为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
<joint name="caster_front_left_joint">
<axis xyz="0 0 1" />
</joint>
<link name="caster_front_left">
<pose xyz="0 1 0" rpy="0 0 0" />
<color name="yellow" />
<mass>0.1</mass>
<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>
</link>

多个块参数将按指定的顺序处理:

1
2
3
4
5
6
7
8
<xacro:macro name="reorder" params="*first *second">
<xacro:insert_block name="second"/>
<xacro:insert_block name="first"/>
</xacro:macro>
<reorder>
<first/>
<second/>
</reorder>

宏可能包含其他宏. 外部宏将首先展开, 然后内部宏将展开. 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<a>
<xacro:macro name="foo" params="x">
<in_foo the_x="${x}" />
</xacro:macro>

<xacro:macro name="bar" params="y">
<in_bar>
<xacro:foo x="${y}" />
</in_bar>
</xacro:macro>

<xacro:bar y="12" />
</a>

变成:

1
2
3
4
5
<a>
<in_bar>
<in_foo the_x="12.0"/>
</in_bar>
</a>

默认参数

Indigo中更新
宏参数可以有默认值:

1
<xacro:macro name="foo" params="x:=${x} y:=${2*y} z:=0"/>

如果默认值包含评估表达式, 则将在实例化时评估它们.

Jade中更新
通常, 您需要将外部变量传递到本地宏参数(如上面的x). 要简化此任务, 可以使用^语法:

1
<xacro:macro name="foo" params="p1 p2:=expr_a p3:=^ p4:=^|expr_b">

插入符号^表示使用外部scope属性(具有相同名称). 管道|表示如果属性未在外部范围中定义, 则使用给定的回退.

局部属性

Jade中更新
在宏中定义的属性和宏对于该宏来说是局部的, 即在外部不可见. 使用可选属性scope ="parent | global", 属性定义可以导出到宏的父作用域(或全局作用域).

包含其他xacro文件

您可以使用xacro: include标签来包含其他xacro文件:

1
2
3
<xacro:include filename="$(find package)/other_file.xacro" />
<xacro:include filename="other_file.xacro" />
<xacro:include filename="$(cwd)/other_file.xacro" />

文件"other_file.xacro"将被xacro包含和扩展. Jade中的新功能: 相对文件名相对于当前处理的文件进行解释. 注意: 当在宏中包含文件时, 包含处理不是宏定义, 而是宏调用文件! $(cwd)显式的允许访问当前工作目录中的文件.
为了避免各种包含文件的属性和宏之间的名称冲突, 可以为包含的文件指定命名空间 - 提供属性ns:

1
<xacro:include filename="other_file.xacro" ns="namespace"/>

访问命名空间宏和属性是通过预先命名空间来实现的, 用点分隔:

1
${namespace.property}

YAML语言支

Jade中的新功能
属性可以是用python语法声明的字典或列表, 如下所示:

1
2
<xacro:property name="props" value="${dict(a=1, b=2, c=3)}"/>
<xacro:property name="numbers" value="${[1,2,3,4]}"/>

或者从YAML文件中加载, 如下:

1
<xacro:property name="props" value="${load_yaml('props.yaml')}"/

注意, 通过evaluation brackets ${}区分评价和纯文本定义. 校准数据是从 YAML 加载的理想候选.

CMakeLists.txt进行构建

以下代码段显示如何在程序包的申明期间使用xacro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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 extension
string(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})

Jade中的新功能:
虽然这个cmake代码提供了对目标名称和构建顺序的完全控制, 但还有一个更简便的宏:

1
2
file(GLOB xacro_files worlds/*.world.xacro)
xacro_add_files(${xacro_files} TARGET media_files)

元素与属性

Jade中的新功能:
要使用动态定义的名称添加元素或属性, 可以使用特殊的xacro标签

1
2
3
4
<xacro:element xacro:name="${element_name}" [other attributes]>
[content]
</xacro:element>
<xacro:attribute name="${attribute_name}" value="${attribute_value}"/>

处理顺序

Classicly Xacro首先加载所有包含项, 然后处理所有属性和宏定义, 最后实例化宏并计算表达式. 因此, 后期定义的属性或宏定义将覆盖先前的定义. 此外, 条件标签, <if><unless>, 对宏或属性定义以及包含的其他文件没有影响.

Jade中的新功能:
ROS Jade以来, Xacro提供了命令行选项–-inorder, 允许以读取的顺序来处理整个文档. 因此, 将使用属性或宏的最新定义. 这是一个更直观的评估过程, 提供了一些不错的新功能:

  • 如果<include>标签分别放置在宏内或条件标签中, 则可以推迟或完全禁止包含文件.
  • 包含文件名可以通过属性或宏参数指定.
  • 通过更改全局范围的属性, 如果在宏中使用这些属性, 则宏的实例化可以产生不同的结果.
  • 属性定义可以是有条件的.
  • 宏可以在局部范围内定义属性, 而不会影响外部.

因为–-inorder处理更强大, 在未来Jade以后的版本, 新的处理风格将成为默认风格, 所以你应该检查你的xacro文件的兼容性. 通常, 两种加工方式应该给出相同的结果. 你可以很容易地像下面这样检查:

1
2
3
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上列出:

1
2
3
Document is incompatible to --inorder processing.
The following properties were redefined after usage:
foo redefined in issues.xacro

使用命令行选项-vv-vvv, 可以增加详细程度级别以记录属性的所有定义.

已弃用的语法

Jade中的新功能:
虽然在以前的版本中, 没有命名空间前缀的xacro标签能够被接受并使用, 但是这种草率的语法是我们应当强烈反对的, 因为它阻止在最终的XML中使用这些标签. 从Jade开始, 这种语法已被弃用, 你应该相应地更新你的文件. 以下脚本将为您更新文件:

1
find . -iname "*.xacro" | xargs sed -i 's#<\([/]\?\)\(if\|unless\|include\|arg\|property\|macro\|insert_block\)#<\1xacro:\2#g'

参考链接: