本文为转载(对于初学者还是很友好的),文中有提到的--inorder参数在noetic版本中可以不输。
更多语法(比如条件语句本文未涉及)及用法还请参考文末贴出的官方文档,再参考其他几个例子加深理解。
正文
Xacro(XML Macros)是一种 XML 宏语言。使用 xacro,可以使用扩展性更大的 XML 表达式的宏来构造更短且更易读的 XML 文件。
xacro可以声明变量,并通过类似函数的形式封装固定的逻辑,能够大大提高代码复用率和程序的可读性。
一、xacro语法
xacro语法主要包含5个方面内容,分别是命名空间、属性、计算、宏、文件包含。
1.1 命名空间
<robot name="myrobot"
xmlns:xacro="http://wiki.ros.org/xacro"
xmlns:sensor="http://playerstage.sourceforge.net/gazebo/xmlschema/#sensor"
xmlns:controller="http://playerstage.sourceforge.net/gazebo/xmlschema/#controller"
xmlns:interface="http://playerstage.sourceforge.net/gazebo/xmlschema/#interface" >
<!-- 其他的内容 -->
</robot>
xacro可以在XML中设置不同的命名空间,这样在进行属性和宏的定义时,就能避免名称发生冲突!
不同的命名空间也便于各个模块实现松耦合的连接,便于代码的复用!
命名空间可以自行设置,只要在属性、宏定义和调用的过程中,保持命名空间一致即可!
1.2 属性
xacro语法中的属性(property)用法与C语言中进行变量的定义、赋值、使用类似,需要先定义属性,然后才可借助属性名称进行调用。
在进行属性定义时,需要使用命名空间xacro
中的的preoperty
进行属性定义,而在使用时,则是通过${属性名称}
调用属性,从而获取属性的值。
1.2.1 常量定义
利用xacro中属性可以实现对urdf中一些常用数值的封装,如轮子的宽度,轮子的半径等。
<!-- 属性定义,效果类似于:float radius = 2.1 -->
<xacro:property name="radius" value="2.1" />
<xacro:property name="length" value="4.5" />
<!-- 属性调用,调用形式: ${属性名称} -->
<geometry type="cylinder" radius="${radius}" length="${length}" />
1.2.2 字符串拼接
属性在实际使用过程中与C语言的宏类似,是进行了字符替换,因此可以实现字符串拼接功能,如下:
<!-- 属性定义 -->
<xacro:property name="radius" value="3.1415" />
<!-- 字符串拼接形式: ${属性名称} -->
<xacro:property name="print" value=" radius_is_${radius}" />
1.2.3 属性块
可以通过属性对URDF中的固定代码进行封装,减少无用代码!
<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
的宏insert_block
而不是使用${属性名称}
方式,insert_block
通过属性块的属性名称
进行调用。
1.2.4 yaml支持
使用 python 语法手动声明属性为字典(dict
)或列表(list
)类型,如下所示:
<xacro:property name="param1" value="${dict(a=1, b=2, c=3)}"/>
<xacro:property name="param2" value="${dict([('1a',1), ('2b',2), ('3c',3)])}"/>
<xacro:property name="param3" 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文件!
props.yaml
文件内容如下:
val1: 10
val2: 20
从 YAML 文件加载的 xacro 属性被视为字典类型,props.yaml
被加载到props
属性中,可以以字典形式访问数据:
<xacro:property name="val1" value="${props['val1']}" />
1.2.5 动态名称
为了添加具有动态定义名称的元素或属性,可以使用<xacro:element>
和<xacro:attribute>
,前者适合内容较多,后者适合单个变量。
<xacro:element>
举例如下:
<xacro:element xacro:name="${element_name}" [other element]>
[content]
</xacro:element>
<xacro:element name="${element_name}" value="${element_value}"/>
<xacro:attribute>
举例如下:
<xacro:property name="foo" value="my_attribute_name"/>
<tag>
<xacro:attribute name="${foo}" value="my_attribute_value"/>
</tag>
其实property就能自定义名称!
<xacro:property name="${foo}" value="my_attribute_value"/>
1.3 数学计算
Xacro 使用python来计算括号${}
中包含的表达式。
1.3.1 基本运算
算术运算形式: ${数学表达式}
,加减乘除运算表达形式。
<!-- 算术运算形式: ${数学表达式} -->
<xacro:property name="diameter1" value=" ${radius + 2}" />
<xacro:property name="diameter2" value=" ${radius - 2}" />
<xacro:property name="diameter3" value=" ${radius * 2}" />
<xacro:property name="diameter4" value=" ${radius / 2}" />
1.3.2 复杂运算
可以在${}
中直接python数学模块中的函数和常量,如pi
和三角函数。
<!-- 属性定义 -->
<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)}" />
1.4 宏(类似函数)
xacro使用xacro:macro
进行宏定义,使用xacro:宏名称
进行宏调用,其中xacro:
是命名空间。
宏的定义和C语言中的函数定义类似,宏名称类似函数名称,宏的参数类似函数形参,格式如下:
<!-- 1、宏的定义格式 -->
<xacro:macro name="宏名称" params="param1 *param2 **param3 x:=${x} y:=${2*y} z:=0 p1:=^ p2:=^| expr_a ">
<!-- 参数调用格式: ${参数名} -->
</xacro:macro>
<!-- 2、宏的调用格式 -->
<xacro:宏名称 param1="data1" param2="data2"/>
宏的参数有多种形式,以下进行解释:
param1
:普通参数,使用宏时,必须填写此参数*param2
:块参数,不是简单的文本参数**param3
:允许插入任意数量的元素x:=${x}
:设置参数的默认值,为该宏定义外的一个属性值y:=${2*y}
:设置参数默认值时,仍可以进行运算z:=0
:设置参数默认值为常数p1:=^
:将外部变量传递到本地宏参数中(如上面的 x)。为了简化此任务,可以使用 ^ 语法p2:=^| expr_a
:插入符号 ^ 表示使用外部作用域属性(具有相同的名称),管道 | 指示如果属性未在外部范围内定义,则使用给定的回退。
一般而言,宏的定义在调用前面,类似函数定义在函数调用前,一个较为复杂宏定义如下:
<!-- 0、属性定义 -->
<xacro:property name="wheel_zd_radius" value="0.0325" />
<xacro:property name="wheel_zd_length" value="0.015" />
<xacro:property name="PI" value="3.1415" />
<xacro:property name="wheel_zd_joint_z" value="${(base_length/2 +lidi - wheel_zd_radius)* -1}" />
<!-- 1、宏的定义 -->
<xacro:macro name="wheel_fuc" params="wheel_name flag">
<link name="car_${wheel_name}">
<visual>
<geometry>
<cylinder radius="${wheel_zd_radius}" length="${wheel_zd_length}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="${wheel_name}_color" >
<color rgba="0 0 1 0.5" />
</material>
</visual>
</link>
<joint name="${wheel_name}_wheel_joint" type="continuous" >
<parent link="base_link" />
<child link="car_${wheel_name}" />
<origin xyz="0 ${0.1* flag} ${wheel_zd_joint_z}" rpy="${PI/2} 0 0" />
<axis xyz="0 1 0" />
</joint>
</xacro:macro>
<!-- 2、宏的调用 -->
<xacro:wheel_fuc wheel_name="base_l" flag="1"/>
<xacro:wheel_fuc wheel_name="base_r" flag="-1"/>
1.5 文件包含
机器人模型由多部件组成,如底盘、传感器等,不同部件可封装为单独的 xacro 文件,最后再将不同的文件集成,组合为完整的机器人。这个过程类似于C语言中,先封装好对应功能的库函数,然后再利用头文件进行调用的过程。
下面代码通过包含xacro_template.xacro
文件,利用该文件调用宏创建了一个box。
<?xml version="1.0"?>
<robot name="fishbot" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:include filename="xacro_template.xacro" />
<link name="base_link">
<xacro:box_visual w="0.809" d="0.5" h="0.1" origin_r="0" origin_p="0" origin_y="0"/>
<xacro:box_collision w="0.809" d="0.5" h="0.1" origin_r="0" origin_p="0" origin_y="0"/>
<xacro:box_inertia m="1.0" w="0.809" d="0.5" h="0.1"/>
</link>
</robot>
xacro_template.xacro
文件内容如下,该文件宏定义了一个box的interia
、visual
和collision
标签:
<?xml version="1.0"?>
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:macro name="box_inertia" params="m w h d">
<inertial>
<origin xyz="0 0 0" rpy="${pi/2} 0 ${pi/2}"/>
<mass value="${m}"/>
<inertia ixx="${(m/12) * (h*h + d*d)}" ixy="0.0" ixz="0.0" iyy="${(m/12) * (w*w + d*d)}" iyz="0.0" izz="${(m/12) * (w*w + h*h)}"/>
</inertial>
</xacro:macro>
<xacro:macro name="box_visual" params="w d h origin_r origin_p origin_y">
<visual>
<origin xyz="0 0 0" rpy="${origin_r} ${origin_p} ${origin_y}"/>
<geometry>
<box size="${w} ${d} ${h}" />
</geometry>
<material name="blue">
<color rgba="0.0 0.0 0.8 1.0"/>
</material>
</visual></xacro:macro>
<xacro:macro name="box_collision" params="w d h origin_r origin_p origin_y">
<collision>
<origin xyz="0 0 0" rpy="${origin_r} ${origin_p} ${origin_y}"/>
<geometry>
<box size="${w} ${d} ${h}" />
</geometry>
<material name="green">
<color rgba="0.0 0.8 0.0 1.0"/>
</material>
</collision></xacro:macro>
</robot>
利用xacro的包含特性,可以轻松实现机器人模型的各个模块解耦,可以分别编写机器人的底盘、摄像头、雷达的xacro文件,最后只需新建一个新的xacro文件就可以将这几部分整合到一起,参考下面的格式,实现对应xacro文件的包含:
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- 包含 底盘、摄像头、雷达的xacro文件-->
<xacro:include filename="demo05_car_base.xacro" />
<xacro:include filename="demo06_car_camera.xacro" />
<xacro:include filename="demo07_car_laser.xacro" />
</robot>
为了避免各种包含文件的属性和宏之间的名称冲突,可以通过属性ns
为包含文件指定一个命名空间。
<xacro:include filename="other_file.xacro" ns="namespace"/>
访问命名空间的宏和属性是通过在命名空间前面加上一个点分隔来实现的:
${namespace.property}
1.6 xacro文件的运行顺序
xacro 提供命令行选项--inorder
,允许按读取顺序处理整个文档。因此,将使用到目前为止看到的属性或宏的最新定义。
rosrun xacro xacro.py --inorder baxter.urdf.xacro > baxter_new.urdf
1.7 弃用的语法
虽然在ROS Jade(ubuntu14.04)以前的版本中,不带名称空间前缀的 xacro 标签被接受,但强烈建议不要使用这种草率的语法,因为它会阻止在最终 XML 中使用这些标签。从 Jade 开始,这种语法已被弃用!
二、使用xacro文件
2.1 使用xacro生成urdf文件
ROS官方提供多种命令行方法,实现XACRO转URDF,在URDF所在目录下打开终端,执行如下命令:
rosrun xacro xacro.py --inorder mbot.xacro > mbot.urdf
或者
rosrun xacro xacro urdf/GRobot.xacro --inorder > GRobot.xacro.urdf
或者
xacro --inorder mbot.xacro > mbot.urdf
一定需要添加--inorder
参数,否则生成模型会失败。
2.2 launch文件中加载xacro文件
这种方法不必显式的将.xacro文件转换为.urdf文件,在文件管理上有些方便。但是在每次通过launch文件使用它的时候,系统后台都会先进行一次转换, 这增加了系统的计算负担。
launch文件内容如下:
<launch>
<param name="robot_description" command="$(find xacro)/xacro --inorder '$(find learning_urdf)/urdf/GRobot.xacro'"/>
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
<node name="rviz" pkg="rviz" type="rviz" />
</launch>
这里是在第二行中用command
属性调用了xacro
工具对机器人描述文件进行了解析,而不是直接用textfile
指出描述文件路径。
三、一些xacro模板代码思路
3.1 将机器人模型按功能或模块拆分成多个部分,分开写xacro文件
如将机器人的底盘、传感器、电池等分开写xacro文件。
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- 包含 底盘、摄像头、雷达的xacro文件-->
<xacro:include filename="demo05_car_base.xacro" />
<xacro:include filename="demo06_car_camera.xacro" />
<xacro:include filename="demo07_car_laser.xacro" />
<joint name="world_joint" type="fixed">
<parent link="world" />
<child link = "base_link" />
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
</joint>
</robot>
注意:需要一个joint
连接各个模块!
3.2 将重复的物体写成一个xacro宏
如四轮车,有四个轮子,可以提取出相同和不同,将不同点作为xacro宏的参数重新定义车轮,简化代码。
<!-- 0、属性定义 -->
<xacro:property name="wheel_zd_radius" value="0.0325" />
<xacro:property name="wheel_zd_length" value="0.015" />
<xacro:property name="PI" value="3.1415" />
<xacro:property name="wheel_zd_joint_z" value="${(base_length/2 +lidi - wheel_zd_radius)* -1}" />
<!-- 1、宏的定义 -->
<xacro:macro name="wheel_fuc" params="wheel_name flag">
<link name="car_${wheel_name}">
<visual>
<geometry>
<cylinder radius="${wheel_zd_radius}" length="${wheel_zd_length}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="${wheel_name}_color" >
<color rgba="0 0 1 0.5" />
</material>
</visual>
</link>
<joint name="${wheel_name}_wheel_joint" type="continuous" >
<parent link="base_link" />
<child link="car_${wheel_name}" />
<origin xyz="0 ${0.1* flag} ${wheel_zd_joint_z}" rpy="${PI/2} 0 0" />
<axis xyz="0 1 0" />
</joint>
</xacro:macro>
<!-- 2、宏的调用 -->
<xacro:wheel_fuc wheel_name="base_l" flag="1"/>
<xacro:wheel_fuc wheel_name="base_r" flag="-1"/>
3.3 将link
或joint
的标签写成一个宏
xacro_template.xacro
文件内容如下,该文件宏定义了一个box的interia
、visual
和collision
标签:
<?xml version="1.0"?>
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:macro name="box_inertia" params="m w h d">
<inertial>
<origin xyz="0 0 0" rpy="${pi/2} 0 ${pi/2}"/>
<mass value="${m}"/>
<inertia ixx="${(m/12) * (h*h + d*d)}" ixy="0.0" ixz="0.0" iyy="${(m/12) * (w*w + d*d)}" iyz="0.0" izz="${(m/12) * (w*w + h*h)}"/>
</inertial>
</xacro:macro>
<xacro:macro name="box_visual" params="w d h origin_r origin_p origin_y">
<visual>
<origin xyz="0 0 0" rpy="${origin_r} ${origin_p} ${origin_y}"/>
<geometry>
<box size="${w} ${d} ${h}" />
</geometry>
<material name="blue">
<color rgba="0.0 0.0 0.8 1.0"/>
</material>
</visual></xacro:macro>
<xacro:macro name="box_collision" params="w d h origin_r origin_p origin_y">
<collision>
<origin xyz="0 0 0" rpy="${origin_r} ${origin_p} ${origin_y}"/>
<geometry>
<box size="${w} ${d} ${h}" />
</geometry>
<material name="green">
<color rgba="0.0 0.8 0.0 1.0"/>
</material>
</collision></xacro:macro>
</robot>
则编写时就简单许多,
<?xml version="1.0"?>
<robot name="fishbot" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:include filename="xacro_template.xacro" />
<link name="base_link">
<xacro:box_visual w="0.809" d="0.5" h="0.1" origin_r="0" origin_p="0" origin_y="0"/>
<xacro:box_collision w="0.809" d="0.5" h="0.1" origin_r="0" origin_p="0" origin_y="0"/>
<xacro:box_inertia m="1.0" w="0.809" d="0.5" h="0.1"/>
</link>
</robot>
urdf文件可以新建一个root link,没有visual,coliision,inertial,单纯作为一个根结点!
获得最佳纹理和颜色支持的推荐格式是 Collada .dae 文件。网格文件不会在引用同一模型的机器之间传输。
四、举例说明
4.1 块参数
在Xacro中,宏(macros)是一种强大的功能,允许定义可重用的模板,它们可以被插入到的URDF文件中。这些宏可以通过<xacro:macro>
标签来定义,并且包含一个名称和一系列参数。
示例中宏的定义如下:
<xacro:macro name="pr2_caster" params="suffix *origin **content **anothercontent">
这里,pr2_caster
是宏的名称,而params
属性定义了宏接受的参数。这些参数包括:
suffix
:一个普通参数。*origin
:一个带有星号的块参数。这表明origin
是一个复杂的数据块(而不仅仅是一个简单的文本值),它可以包含多个子元素。**content
和**anothercontent
:这些是带有双星号的参数,它们允许插入任意数量的子元素。
在使用这个宏的时候:
<xacro:pr2_caster suffix="front_left">
<origin xyz="0 1 0" rpy="0 0 0" />
...
</xacro:pr2_caster>
这里,suffix="front_left"
直接作为属性传递给宏。然而,origin
、content
和 anothercontent
不是以属性的形式传递,而是作为宏内部的元素。例如,<origin xyz="0 1 0" rpy="0 0 0" />
实际上是origin
块的内容。
在Xacro处理这个宏时,它将使用<xacro:insert_block>
来插入这些块参数。例如:
<xacro:insert_block name="origin" />
这会插入传递给origin
的所有内容(在这个例子中,就是pose
元素)。同样,content
和anothercontent
的内容也会被插入到相应的位置。
4.2 任意块参数
下面是如何定义和使用带有content
和 anothercontent
块参数的宏的示例:
宏的定义:
<xacro:macro name="my_macro" params="*origin **content **anothercontent">
<link name="my_link">
<xacro:insert_block name="origin" />
<xacro:insert_block name="content" />
</link>
<xacro:insert_block name="anothercontent" />
</xacro:macro>
在这个宏定义中,content
和 anothercontent
是预期会包含多个子元素的块参数。
宏的使用:
<xacro:my_macro>
<origin xyz="0 0 1" rpy="0 0 0" />
<content>
<visual>
<geometry>
<box size="1 1 1" />
</geometry>
</visual>
<collision>
<geometry>
<box size="1 1 1" />
</geometry>
</collision>
</content>
<anothercontent>
<inertial>
<mass value="1.0" />
<inertia ixx="1" ixy="0" ixz="0" iyy="1" iyz="0" izz="1" />
</inertial>
</anothercontent>
</xacro:my_macro>
在宏的实际使用中,<content>
包含了视觉(visual)和碰撞(collision)的描述,而 <anothercontent>
包含了惯性(inertial)的描述。这些块在宏内部通过<xacro:insert_block>
标签插入到相应的位置。
宏展开后的结果:
<link name="my_link">
<origin xyz="0 0 1" rpy="0 0 0" />
<visual>
<geometry>
<box size="1 1 1" />
</geometry>
</visual>
<collision>
<geometry>
<box size="1 1 1" />
</geometry>
</collision>
</link>
<inertial>
<mass value="1.0" />
<inertia ixx="1" ixy="0" ixz="0" iyy="1" iyz="0" izz="1" />
</inertial>
这就是宏展开之后的样子,<origin>
,<content>
,和<anothercontent>
块的内容被插入到了定义的相应位置。这样,通过宏,你可以复用这段结构,并为不同的部分插入不同的参数值和块内容。
最终,这个宏扩展成一个完整的URDF结构,其中包含了所有传递给它的参数和块内容。通过这种方式,Xacro允许高度复用和灵活的URDF文件组织。
参考:
ROS的xacro官方文档:http://wiki.ros.org/xacro
xacro函数及理解:https://zhuanlan.zhihu.com/p/452791019
车轮模板代码:https://suljaxm.github.io/2019/08/20/ros/urdf-he-xacro-de-qu-bie/
turtle的xacro模型:https://blog.youkuaiyun.com/QQ438152470/article/details/118249904
一个完整的xacro流程:https://blog.youkuaiyun.com/ktigerhero3/article/details/64922802
2种xacro使用方法:https://gaoyichao.com/Xiaotu/?book=ros&title=Xacro
鱼香ros的box模板:https://mp.weixin.qq.com/s/qkP_AeBqRBIi_uRH37ZSsg
特别复杂的URDF模型:https://www.guyuehome.com/14535