机器人描述中的Xacro、SDF与URDF在实际项目中的协作流程
摘要:ROS 2 机器人建模采用分层架构,Xacro(参数化模板)编译生成URDF(标准描述文件),再转换为SDF(Gazebo仿真格式)。Xacro增强支持ROS 2特性如YAML参数加载和条件编译,URDF通过robot_state_publisher加载,最终SDF用于Gazebo仿真。典型项目通过launch文件管理转换流程,文件结构包含Xacro模块化设计、参数配置和平台特定描述,实现从
·
详细解析ROS 2 中 Xacro、SDF 与 ** URDF** 的关系,以及它们在实际项目中的协作流程。
核心关系总览
┌─────────────────────────────────────────────────────────────┐
│ 建模与仿真栈 │
├─────────────────────────────────────────────────────────────┤
│ 抽象层 │ Xacro(宏/参数/模板) │
│ │ ↓ 编译/展开 │
│ 标准层 │ URDF(ROS 标准机器人描述) │
│ │ ↓ 转换/加载 │
│ 仿真层 │ SDF(Gazebo 仿真描述) │
│ │ ↓ 实例化 │
│ 运行时 │ Gazebo / RViz / MoveIt / ros2_control │
└─────────────────────────────────────────────────────────────┘
1. URDF 在 ROS 2 中的角色
1.1 ROS 2 中的变化
| 特性 | ROS 1 | ROS 2 |
|---|---|---|
| 解析库 | urdfdom |
urdfdom(保持兼容) |
| 加载方式 | robot_state_publisher |
robot_state_publisher(ROS 2 版本) |
| 构建工具 | catkin |
colcon / ament |
| 包格式 | package.xml format 2 |
package.xml format 3 |
| 多机器人 | 复杂 | 通过命名空间简化 |
1.2 ROS 2 URDF 加载流程
# ROS 2 典型 launch 文件(Python)
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import Command, FindExecutable, PathJoinSubstitution
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
# 1. 使用 xacro 编译 URDF
robot_description = Command([
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution([
FindPackageShare("my_robot"),
"urdf",
"robot.urdf.xacro"
]),
" ", # 传递参数
"use_gazebo:=true",
" ",
"sim_mode:=true"
])
# 2. 发布 robot_state
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description": robot_description}]
)
# 3. 关节状态发布(真实硬件或仿真)
joint_state_pub = Node(
package="joint_state_publisher",
executable="joint_state_publisher"
)
return LaunchDescription([robot_state_pub, joint_state_pub])
2. Xacro → URDF → SDF 的完整转换链
2.1 阶段一:Xacro 编译
# ROS 2 命令
ros2 run xacro xacro robot.xacro use_gazebo:=true > robot.urdf
# 或查看展开结果
ros2 run xacro xacro robot.xacro
Xacro 在 ROS 2 中的增强:
- 支持
$(find-pkg-share ...)替代$(find ...) - 更好的 YAML 参数集成
- 条件编译支持
<!-- ROS 2 风格的 xacro -->
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">
<!-- 使用 ROS 2 包路径 -->
<xacro:include filename="$(find-pkg-share my_robot)/urdf/materials.xacro"/>
<!-- 从 YAML 加载参数(ROS 2 推荐方式) -->
<xacro:property name="config" value="${xacro.load_yaml('$(find-pkg-share my_robot)/config/robot_params.yaml')}"/>
<xacro:property name="wheel_radius" value="${config['wheel']['radius']}"/>
<!-- 条件参数 -->
<xacro:arg name="use_gazebo" default="false"/>
<xacro:if value="$(arg use_gazebo)">
<xacro:include filename="$(find-pkg-share my_robot)/urdf/gazebo.xacro"/>
</xacro:if>
</robot>
2.2 阶段二:URDF 解析
# ROS 2 中解析 URDF(Python)
from ament_index_python.packages import get_package_share_directory
import xacro
import xml.etree.ElementTree as ET
# 处理 xacro
xacro_file = get_package_share_directory('my_robot') + '/urdf/robot.xacro'
doc = xacro.process_file(xacro_file, mappings={'use_gazebo': 'true'})
robot_desc = doc.toxml()
# 解析为 DOM
root = ET.fromstring(robot_desc)
for link in root.findall('link'):
print(f"Link: {link.get('name')}")
2.3 阶段三:URDF → SDF 转换
# 使用 gz 命令(Gazebo Sim / Ignition)
gz sdf -p robot.urdf > robot.sdf
# 或从 ROS 包直接转换
gz sdf -p $(ros2 pkg prefix my_robot)/share/my_robot/urdf/robot.urdf > robot.sdf
转换细节:
| URDF 元素 | SDF 对应 | 说明 |
|---|---|---|
<link> |
<link> |
直接映射,SDF 支持更多表面属性 |
<joint> |
<joint> |
SDF 类型更丰富(ball, universal, screw) |
<transmission> |
移除 | 转为 <joint><physics> 或插件 |
<gazebo> |
直接合并 | 保留所有属性 |
<sensor> |
<sensor> |
SDF 传感器配置更详细 |
<plugin> |
<plugin> |
需确认 Gazebo 版本兼容性 |
3. ROS 2 中的实际协作架构
3.1 典型项目文件结构
my_robot/
├── CMakeLists.txt
├── package.xml
├── config/
│ ├── robot_params.yaml # Xacro 参数
│ ├── controllers.yaml # ros2_control 配置
│ └── gazebo_params.yaml # Gazebo 特定参数
├── launch/
│ ├── robot_description.launch.py # 仅加载描述
│ ├── simulation.launch.py # Gazebo 仿真
│ └── real_hardware.launch.py # 真实硬件
├── urdf/
│ ├── robot.urdf.xacro # 主入口
│ ├── robot_core.xacro # 核心结构
│ ├── materials.xacro # 材质定义
│ ├── wheels.xacro # 轮子宏
│ ├── sensors/
│ │ ├── lidar.xacro
│ │ ├── camera.xacro
│ │ └── imu.xacro
│ └── gazebo.xacro # Gazebo 特定(条件包含)
├── sdf/
│ └── my_robot/ # 手动调优的 SDF(可选)
│ ├── model.sdf
│ └── model.config
└── worlds/
└── my_world.sdf # Gazebo 世界文件
3.2 条件化构建:同一 Xacro 多目标输出
<!-- robot.urdf.xacro -->
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">
<!-- 参数定义 -->
<xacro:arg name="use_gazebo" default="false"/>
<xacro:arg name="use_ros2_control" default="true"/>
<xacro:arg name="sim_mode" default="false"/>
<!-- 核心结构(始终包含) -->
<xacro:include filename="robot_core.xacro"/>
<!-- Gazebo 特定(仿真时) -->
<xacro:if value="$(arg use_gazebo)">
<xacro:include filename="gazebo.xacro"/>
<!-- Gazebo 插件 -->
<gazebo>
<plugin filename="gz_ros2_control-system" name="gz_ros2_control::GazeboSimROS2ControlPlugin">
<parameters>$(find my_robot)/config/controllers.yaml</parameters>
</plugin>
</gazebo>
</xacro:if>
<!-- ROS 2 Control(硬件或仿真) -->
<xacro:if value="$(arg use_ros2_control)">
<xacro:include filename="ros2_control.xacro"/>
</xacro:if>
<!-- 真实硬件特定 -->
<xacro:unless value="$(arg sim_mode)">
<xacro:include filename="real_hardware.xacro"/>
</xacro:unless>
</robot>
4. ROS 2 中的关键集成点
4.1 ros2_control 与 URDF/SDF
<!-- ros2_control.xacro -->
<ros2_control name="RobotSystem" type="system">
<!-- 硬件接口(真实硬件) -->
<hardware>
<plugin>my_robot_hardware/RobotHardware</plugin>
<param name="serial_port">/dev/ttyUSB0</param>
</hardware>
<!-- 或 Gazebo 仿真接口 -->
<!--
<hardware>
<plugin>gz_ros2_control/GazeboSimSystem</plugin>
</hardware>
-->
<!-- 关节接口定义 -->
<joint name="left_wheel_joint">
<command_interface name="velocity"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="right_wheel_joint">
<command_interface name="velocity"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
</ros2_control>
4.2 Gazebo Sim(Ignition)与 ROS 2
<!-- 在 Xacro 中嵌入 Gazebo Sim 配置 -->
<gazebo reference="lidar_link">
<sensor name="lidar" type="gpu_lidar">
<topic>scan</topic>
<update_rate>10</update_rate>
<ray>
<scan>
<horizontal>
<samples>640</samples>
<min_angle>-1.396263</min_angle>
<max_angle>1.396263</max_angle>
</horizontal>
</scan>
<range>
<min>0.08</min>
<max>10.0</max>
</range>
</ray>
<!-- ROS 2 桥接配置 -->
<plugin filename="gz-sim-sensors-system" name="gz::sim::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>
</sensor>
</gazebo>
ROS 2 桥接(自动):
- Gazebo Sim 自动发布到 ROS 2 话题(通过
gz-transport→ros_gz_bridge) - 无需额外配置即可在 RViz2 中显示
5. 三种格式的选择策略
5.1 决策流程图
开始建模
│
▼
是否需要复杂参数化/宏? ──是──► 使用 Xacro
│否 │
▼ ▼
直接在 URDF 中编写 ◄────────── 编译为 URDF
│ │
▼ ▼
是否需要 Gazebo 仿真? ──是──► 转换为 SDF(自动或手动)
│否 │
▼ ▼
用于 ROS 2 控制/可视化 ◄────── 在 Gazebo 中仿真
(RViz2, MoveIt 2,
ros2_control)
5.2 使用场景对照
| 场景 | 推荐格式 | 说明 |
|---|---|---|
| ROS 2 导航/控制开发 | Xacro → URDF | 标准流程,支持 ros2_control |
| MoveIt 2 运动规划 | Xacro → URDF | MoveIt Setup Assistant 需要 URDF |
| Gazebo 经典仿真 | Xacro → URDF → SDF | gazebo_ros_pkgs 自动处理 |
| Gazebo Sim (Ignition) | Xacro → URDF → SDF | 推荐方式,或使用原生 SDF |
| 多机器人仿真 | SDF World | 直接编写 SDF 世界文件 |
| 闭链机构(并联机器人) | 手动 SDF | URDF 不支持,必须直接写 SDF |
| 复杂环境/传感器配置 | 手动 SDF | 比 URDF 转换更灵活 |
| Web 可视化(webots/three.js) | URDF 或 Xacro | 多数 Web 工具支持 URDF |
6. ROS 2 中的高级集成模式
6.1 混合模式:URDF + SDF 覆盖
# launch/simulation.launch.py
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.substitutions import FindPackageShare
from launch_ros.actions import Node
def generate_launch_description():
# 参数
use_rviz = LaunchConfiguration('use_rviz')
# 1. 生成 URDF(用于 ROS 2 生态)
robot_description = Command([
'xacro ',
PathJoinSubstitution([
FindPackageShare('my_robot'),
'urdf',
'robot.urdf.xacro'
]),
' use_gazebo:=true'
])
# 2. 生成 SDF(用于 Gazebo,可选手动优化)
# 或直接使用 URDF,让 Gazebo 自动转换
# 3. 启动 Gazebo Sim
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
PathJoinSubstitution([
FindPackageShare('ros_gz_sim'),
'launch',
'gz_sim.launch.py'
])
]),
launch_arguments={
'gz_args': PathJoinSubstitution([
FindPackageShare('my_robot'),
'worlds',
'my_world.sdf' # SDF 世界,包含机器人
]) + ' -r -v 4'
}.items()
)
# 4. 发布机器人状态(TF)
robot_state_pub = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{
'robot_description': robot_description,
'publish_frequency': 50.0
}]
)
# 5. 桥接 Gazebo ↔ ROS 2
bridge = Node(
package='ros_gz_bridge',
executable='parameter_bridge',
arguments=[
'/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock',
'/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist',
'/odom@nav_msgs/msg/Odometry[gz.msgs.Odometry',
'/scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan',
'/camera/image@sensor_msgs/msg/Image[gz.msgs.Image',
'/camera/camera_info@sensor_msgs/msg/CameraInfo[gz.msgs.CameraInfo',
],
output='screen'
)
# 6. RViz2
rviz = Node(
package='rviz2',
executable='rviz2',
arguments=['-d', PathJoinSubstitution([
FindPackageShare('my_robot'),
'config',
'view_robot.rviz'
])],
condition=IfCondition(use_rviz)
)
return LaunchDescription([
DeclareLaunchArgument('use_rviz', default_value='true'),
gazebo,
robot_state_pub,
bridge,
rviz
])
6.2 运行时关系
┌──────────────────────────────────────────────────────────────┐
│ ROS 2 运行时 │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ RViz2 │◄────►│ robot_state_publisher │ │
│ │ (可视化) │ │ (TF 树 + 机器人描述) │ │
│ └─────────────────┘ └──────────────┬───────────────┘ │
│ ▲ │ │
│ │ │ URDF (string) │
│ │ ┌──────▼──────┐ │
│ │ │ xacro 编译 │ │
│ │ └──────┬──────┘ │
│ │ │ │
│ ┌──────┴──────────┐ ┌─────────────▼──────────────┐ │
│ │ MoveIt 2 │◄────►│ ros2_control │ │
│ │ (运动规划) │ │ (控制器管理) │ │
│ └─────────────────┘ └─────────────┬──────────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ 硬件接口或仿真接口 │ │
│ │ (Gazebo/Robot) │ │
│ └──────────┬──────────┘ │
└─────────────────────────────────────────┼────────────────────┘
│
┌─────────────────────────────────────────┼────────────────────┐
│ Gazebo Sim (Ignition) │ │
│ ┌──────────────────────────────────────┴──────────────┐ │
│ │ SDF World (自动从 URDF 转换或手动编写) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ SDF Model (机器人实体) │ │ │
│ │ │ ├─ Links, Joints, Sensors │ │ │
│ │ │ ├─ Plugins (gz_ros2_control, sensors) │ │ │
│ │ │ └─ Physics properties │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
7. 关键注意事项
7.1 版本兼容性
| 组件 | ROS 2 版本 | Gazebo 版本 | SDF 版本 |
|---|---|---|---|
| Foxy | Gazebo 11 | 1.7 | |
| Galactic | Gazebo 11 / Ignition Edifice | 1.8 | |
| Humble | Gazebo 11 / Ignition Fortress | 1.9 | |
| Iron | Gazebo 11 / Harmonic | 1.10 | |
| Jazzy | Harmonic | 1.11 |
7.2 常见陷阱
-
坐标系差异
- URDF:右手坐标系,Z 向上
- SDF:相同,但某些旧版 Gazebo 插件可能有差异
-
插件命名空间
<!-- ROS 1 (Gazebo Classic) --> <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so"/> <!-- ROS 2 (Gazebo Sim) --> <plugin filename="gz-sim-diff-drive-system" name="gz::sim::systems::DiffDrive"/> -
话题映射
- ROS 2 使用 DDS,话题名称可能不同
- 使用
ros_gz_bridge进行显式映射
-
资源路径
# 确保模型路径在 GAZEBO_MODEL_PATH 或 SDF_PATH 中 export GZ_SIM_RESOURCE_PATH=$GZ_SIM_RESOURCE_PATH:~/ros2_ws/install/my_robot/share
总结
| 问题 | 答案 |
|---|---|
| Xacro 是什么? | URDF 的预处理器,用于参数化建模 |
| URDF 在 ROS 2 中变了吗? | 格式不变,但加载工具和生态集成更新 |
| SDF 必须手动写吗? | 可以从 URDF 自动转换,复杂场景建议手动优化 |
| 三者如何协作? | Xacro 生成 URDF,URDF 用于 ROS 2 生态,转换为 SDF 用于 Gazebo |
| ROS 2 推荐流程? | Xacro → URDF(同时用于 RViz/ros2_control)→ 自动/手动转 SDF(Gazebo) |
理解这三者的关系,是构建完整 ROS 2 机器人应用的基础。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)