4、XACRO优化建模:XACRO语法、宏定义与参数化、模块化机器人模型、将XACRO转换为URDF
好,咱们继续。上一章我们用URDF搭了个机器人,但说实话,写起来挺累的。尤其是当你的机器人有6个完全一样的轮子,或者有8个相同的关节时,复制粘贴能让你怀疑人生。这时候,XACRO就来了。
XACRO是什么?说白了,它就是URDF的升级版——一个宏预处理工具。你写更少的代码,做更多的事。我个人习惯,但凡模型超过3个关节,我肯定用XACRO,绝不手写URDF。
核心思想: XACRO = URDF + 宏 + 数学运算 + 参数化。它本身不是一种新语言,而是对URDF的扩展,最终还是要转回URDF才能被Gazebo识别。
4.1 XACRO语法基础
XACRO的语法其实很简单。它基于XML,只是多了几个特殊的标签。你只要记住三个核心标签就够了:
<xacro:property>—— 定义常量/参数<xacro:macro>—— 定义宏<xacro:include>—— 包含其他文件
先看一个最简单的例子。假设你有一个轮子,半径是0.1米。在URDF里你得写:
<link name="wheel">
<visual>
<geometry>
<cylinder radius="0.1" length="0.05"/>
</geometry>
</visual>
</link>
用XACRO,你可以这样:
<xacro:property name="wheel_radius" value="0.1"/>
<link name="wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="0.05"/>
</geometry>
</visual>
</link>
看到了吗?${wheel_radius} 这个语法就是引用属性。以后想改轮子大小,改一个地方就行。我在项目中遇到过,客户突然说轮子要加大20%,如果是纯URDF我得改6个地方,用XACRO改一行就完事。
小技巧: XACRO支持数学运算!比如 ${wheel_radius * 2} 或者 ${2 * PI * 0.1}。这在计算关节位置时特别有用。
4.2 宏定义与参数化
宏才是XACRO的真正杀手锏。你想想看,一个四轮机器人,四个轮子结构完全一样,只是位置不同。难道要写四遍?
不,定义一个宏,然后调用四次就行了。
<xacro:macro name="wheel" params="name prefix reflect">
<link name="${prefix}_${name}">
<visual>
<geometry>
<cylinder radius="0.1" length="0.05"/>
</geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
</visual>
</link>
<joint name="${prefix}_${name}_joint" type="continuous">
<parent link="base_link"/>
<child link="${prefix}_${name}"/>
<origin xyz="${reflect * 0.2} ${reflect * 0.15} 0" rpy="0 0 0"/>
</joint>
</xacro:macro>
<!-- 调用宏 -->
<xacro:wheel name="front_left" prefix="robot" reflect="1"/>
<xacro:wheel name="front_right" prefix="robot" reflect="-1"/>
这里 params 定义了宏的参数。调用时传入不同的值,就能生成不同的轮子。注意那个 reflect 参数,我用了正负1来控制左右对称。这个技巧是我从一次做六足机器人时学到的,省了我大量重复计算。
注意: 宏的名字不能和URDF里的标签名冲突。比如你不能定义一个名为"link"的宏,那会出大问题。我曾经犯过这个错,排查了半天才发现是命名冲突。
4.3 模块化机器人模型
当你的机器人越来越复杂,把所有东西塞进一个文件就变得很蠢。我的做法是:按功能模块拆分文件。
比如一个带机械臂的移动机器人,我会拆成这样:
robot.urdf.xacro—— 主文件,负责组装chassis.urdf.xacro—— 底盘模块arm.urdf.xacro—— 机械臂模块sensors.urdf.xacro—— 传感器模块(激光雷达、摄像头等)
主文件里用 <xacro:include> 把它们组合起来:
<?xml version="1.0"?>
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find my_robot_description)/urdf/chassis.urdf.xacro"/>
<xacro:include filename="$(find my_robot_description)/urdf/arm.urdf.xacro"/>
<xacro:include filename="$(find my_robot_description)/urdf/sensors.urdf.xacro"/>
<!-- 组装各模块 -->
<xacro:chassis/>
<xacro:arm parent="base_link" x="0.1" y="0" z="0.2"/>
<xacro:lidar parent="base_link" z="0.3"/>
</robot>
这样做的好处太多了。团队协作时,每个人负责一个模块,互不干扰。而且想换机械臂?直接换一个 arm.urdf.xacro 文件就行,主文件几乎不用动。
我参与过一个项目,机器人有3种不同配置(标准版、加强版、轻量版)。我们就是靠模块化XACRO,通过不同的主文件组合出3种型号,代码复用率超过80%。
4.4 将XACRO转换为URDF
Gazebo不认识XACRO,它只认URDF。所以最后一步,必须把XACRO转成URDF。ROS里提供了现成的命令行工具:
# 方法一:使用 xacro 命令
xacro robot.urdf.xacro > robot.urdf
# 方法二:在 launch 文件中自动转换
<param name="robot_description"
command="$(find xacro)/xacro $(find my_robot_description)/urdf/robot.urdf.xacro"/>
第二种方法更常用。你在launch文件里直接指定xacro文件,ROS启动时会自动调用xacro工具,生成URDF并加载到参数服务器。这样你永远不需要手动维护一个URDF文件。
验证技巧: 转换后可以用 check_urdf robot.urdf 检查语法。我每次转换完都会跑一遍这个命令,能发现很多低级错误,比如缺少父链接之类的。
嗯,这里还要注意一点。如果你的xacro文件里用了 $(find ...) 这种路径,那必须在ROS环境下运行xacro命令。否则它找不到包路径,会报错。
知识体系总览
为了让你更直观地理解XACRO的整个工作流程,我画了张图:
这张图把整个流程串起来了。左边是三个输入源,中间是主文件负责组装,然后通过xacro命令转换成最终的URDF。说白了,XACRO就是一个代码生成器,你给它模板和参数,它给你吐出完整的URDF。
总结一下我的经验: 用XACRO建模,前期多花10分钟设计宏和模块,后期能省下10小时。尤其是当你的机器人需要迭代修改时,XACRO的价值会成倍放大。别偷懒,从一开始就用XACRO。