使用Xacro简化URDF文件
如果我们直接使用前面介绍的方法编写复杂的机器人模型,会面临两个问题:
-
冗余代码多:相同的参数(如轮子尺寸)在多处重复出现,修改时极易遗漏。
-
计算繁琐:需要手动计算各种几何参数(如惯性矩阵),一旦设计调整,所有数据都要重新计算。
为了解决这些问题,ROS 提供了 Xacro (XML Macros) 功能包。你可以把 Xacro 看作是 URDF 的“升级版”或“模版语言”。它主要提供三大核心功能来简化设计流程:
-
常量 (Properties):定义变量,一处修改,全局生效。
-
数学计算 (Math):在 XML 中直接进行加减乘除。
-
宏 (Macros):类似于编程中的“函数”,用于封装重复的模块(如封装一个通用的“轮子”模块)。
我们首先学习 Xacro 的基础用法:基础与文件转换、常量与数学计算。
Xacro 基础与文件转换
Xacro 文件本质上是 XML 文件,通常以 .xacro 为后缀。在使用前,必须确保文件头部包含 Xacro 的命名空间声明。
1. 必要的头部声明
每一个可运行的 Xacro 文件,其顶部的 <robot> 标签必须包含 xmlns:xacro 声明。
示例:
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">
</robot>
注意:如果没有这一行,系统将无法识别 Xacro 语法,解析会报错。
2. 将 Xacro 转换为 URDF
计算机和仿真器最终只认识 .urdf 文件。因此,我们需要将编写好的 .xacro 文件“翻译”成 .urdf 文件。
方法一:手动转换(命令行) 在终端中使用以下命令,用于检查生成的 URDF 是否正确:
xacro model.xacro > model.urdf
方法二:自动转换(在 Launch 文件中) 在 ROS2 的启动文件中,不需要手动生成中间文件,而是使用 Command 指令在运行时动态生成 URDF。这种做法能保证模型永远是最新的,且不占用硬盘空间。
Launch 文件编写示例 (display.launch.py):
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import Command, LaunchConfiguration
from launch_ros.parameter_descriptions import ParameterValue
def generate_launch_description():
# 1. 获取 xacro 文件路径
pkg_share = get_package_share_directory('turtlebot3_description')
xacro_file = os.path.join(pkg_share, 'urdf', 'turtlebot3_burger.urdf.xacro')
# 2. 核心步骤:使用 Command 指令运行 xacro
# 这相当于在终端执行 "xacro path/to/file.xacro"
robot_description_config = Command(['xacro ', xacro_file])
# 3. 创建 robot_state_publisher 节点
params = {'robot_description': ParameterValue(robot_description_config, value_type=str)}
node_robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
output='screen',
parameters=[params]
)
return LaunchDescription([
node_robot_state_publisher
])
启动文件中必不可少的就是 robot_state_publisher 节点,该节点位于robot_state_publisher 功能包中。它的核心作用是将静态的模型文件与动态的关节数据结合起来:
-
输入:它读取 URDF 文件(知道骨架长什么样)和 /joint_states 话题(知道当前关节转了多少度)。
-
处理:利用正向运动学,实时计算出机器人每一个部件(Link)在三维空间中的确切位置。
-
输出:将计算结果发布到 TF(坐标变换树) 中,告诉 Rviz、导航等组件机器人现在的具体姿态。
URDF 好比图纸,关节角度是数据,robot_state_publisher 负责把它们变成机器人在空间中的实时姿态。
使用常量 (Property)
在设计机器人底盘时,假设有一个圆柱体 base_link。按照之前介绍的方法,我们需要在 <visual>和 <collision>中分别写一次尺寸,为了简化这个过程,我们可以使用 Xacro 常量:
使用之前的方法:
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</collision>
</link>
使用 Xacro 常量:
我们可以使用 <xacro:property> 标签定义常量,然后使用 ${名称} 的语法来调用它。
<xacro:property name="width" value="0.2" />
<xacro:property name="bodylen" value="0.6" />
<link name="base_link">
<visual>
<geometry>
<cylinder radius="${width}" length="${bodylen}"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder radius="${width}" length="${bodylen}"/>
</geometry>
</collision>
</link>
${} 内部的值不仅可以是数字,也可以用于字符串拼接:
<xacro:property name="robotname" value="marvin" />
<link name="${robotname}s_leg" />
使用数学运算 (Math)
Xacro 允许在 ${} 内部直接进行数学运算,这意味着我们只需定义基础参数(如直径),其他参数(如半径)可以让系统自动计算。
支持的运算:
-
基本运算:加 (
+)、减 (-)、乘 (*)、除 (/)、取负 (-) -
常用函数:
sin,cos,sqrt(开方),pi(圆周率) 等
示例:
-
自动计算半径:
如果我们得到了轮子的直径,但 编写 URDF 需要半径,可以通过Xacro自动计算得到:
<xacro:property name="wheel_diameter" value="0.1" />
<cylinder radius="${wheel_diameter / 2}" length="0.05"/> -
几何位置计算:
假设你需要将轮子安装在底盘边缘,可以使用公式自动推算位置:
<xacro:property name="width" value="0.2" />
<origin xyz="${width / 2 + 0.02} 0 0.25" />
通过使用常量和数学运算, URDF 文件不仅代码量会显著减少,而且具备了“参数化设计”的能力,修改机器人尺寸将变得非常简单。
Xacro 宏
宏(Macro)是 Xacro 中最强大、最实用的功能。 如果你有编程基础,可以把宏理解为函数:
-
它可以封装一段重复使用的代码(比如一个轮子的定义)。
-
它可以接收参数(比如轮子的名字、位置、左右方向)。
-
调用宏时,系统会自动展开成完整的 URDF 代码。
1. 简单宏 (无参数)
我们先看一个最简单的宏,它不接收任何参数,仅仅是封装了一段固定的 XML 代码:
<xacro:macro name="default_origin">
<origin xyz="0 0 0" rpy="0 0 0"/>
</xacro:macro>
调用宏:
<xacro:default_origin />
生成的 URDF 结果:
<origin rpy="0 0 0" xyz="0 0 0"/>
注意:
name属性是必须的,它相当于函数名,调用时语法为<xacro:宏名称 />。
2. 参数化宏 (带参数)
宏的真正优势在于参数化,通过传入不同的参数,我们可以用同一个宏生成不同的部件(例如:不同质量的物体,或者不同名字的连杆)。
(1). 普通参数
这是一个封装了惯性矩阵(Inertial)的宏,它接收一个参数 mass(质量)。
定义宏:
<xacro:macro name="default_inertial" params="mass">
<inertial>
<mass value="${mass}" />
<inertia ixx="1e-3" ixy="0.0" ixz="0.0"
iyy="1e-3" iyz="0.0"
izz="1e-3" />
</inertial>
</xacro:macro>
调用宏:
<xacro:default_inertial mass="10"/>
(2). 块参数 (Block Parameters)
有时候我们不仅想传入数字或字符串,还想传入整段 XML 代码块(例如,传入一个几何形状 <cylinder ... /> 或者 <box ... />)。这时我们需要在参数名前加 * 号。
定义宏:
<xacro:macro name="blue_shape" params="name *shape">
<link name="${name}">
<visual>
<geometry>
<xacro:insert_block name="shape" />
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<xacro:insert_block name="shape" />
</geometry>
</collision>
</link>
</xacro:macro>
调用宏:
<xacro:blue_shape name="base_link">
<cylinder radius="0.42" length="0.01" />
</xacro:blue_shape>
宏的实际应用示例
Xacro 在机器人建模中最经典的用法是通过数学计算和镜像参数,快速生成对称部件(如左腿和右腿,左轮和右轮)。
假设我们要创建机器人的两条腿。
-
腿的形状是一样的(Box)。
-
除了安装位置的 Y 轴坐标相反(一个在左,一个在右),其他属性都相同。
可以利用 prefix(前缀)来区分名字,利用 reflect(反射系数)来处理对称坐标。
<xacro:macro name="leg" params="prefix reflect">
<link name="${prefix}_leg">
<visual>
<geometry>
<box size="${leglen} 0.1 0.2"/>
</geometry>
<origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
<material name="white"/>
</visual>
<collision>
<geometry>
<box size="${leglen} 0.1 0.2"/>
</geometry>
<origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
</collision>
<xacro:default_inertial mass="10"/>
</link>
<joint name="base_to_${prefix}_leg" type="fixed">
<parent link="base_link"/>
<child link="${prefix}_leg"/>
<origin xyz="0 ${reflect * (width + 0.02)} 0.25" />
</joint>
</xacro:macro>
<xacro:leg prefix="right" reflect="1" />
<xacro:leg prefix="left" reflect="-1" />
关键技巧总结
-
名称前缀 (Prefix): 使用
${prefix}_leg这样的写法,可以避免为左腿和右腿分别写两套代码,保证了命名的一致性。 -
数学计算原点 (Math in Origin): 在
<origin>标签中使用数学公式,当机器人整体尺寸(如width)发生变化时,腿的安装位置会自动调整,无需手动重新计算坐标。 -
镜像反射 (Reflect): 这是 URDF/Xacro 中处理对称结构的“杀手锏”。
-
将
reflect设置为1或-1。 -
在坐标计算中乘以该系数(例如
y="${reflect * offset}"),即可轻松把部件放置在身体的两侧。
-