3、机器人模型与SDF/URDF:创建你的第一个机器人模型
做机器人导航,第一步是什么?
不是写算法,也不是调参数。你得先有个「机器人」。
在Gazebo里,这个「机器人」就是模型文件。说白了,就是告诉仿真器:我的机器人长什么样、有多重、轮子在哪、传感器装在哪。没有模型,导航就是空中楼阁。
我个人习惯,每次开始一个新项目,都会花半小时把模型文件从头写一遍。不是为了炫技,而是为了把机器人的每个关节、每个传感器都刻在脑子里。嗯,这招挺管用的。
核心要点:URDF和SDF是Gazebo里描述机器人的两种格式。URDF更老、更简单;SDF更现代、功能更强。但别急着站队——实际项目中,两者经常混用。
3.1 从零开始:写一个最简单的URDF
先别管那些复杂的。我们写一个「会动的方块」。
<robot name="my_first_robot">
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
<material name="blue">
<color rgba="0 0 1 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
</collision>
<inertial>
<mass value="1.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>
</robot>
这段代码干了什么?
- link:机器人的一个刚体部件。这里只有一个方块。
- visual:长得什么样。蓝色方块,0.4米长、0.3米宽、0.2米高。
- collision:碰撞检测用的形状。我建议你让collision和visual保持一致,否则仿真时会出现「看着没撞上,实际撞上了」的诡异情况。我在项目里吃过这个亏。
- inertial:质量、惯性张量。这个容易被忽略,但少了它,Gazebo会报错。
我的小技巧:惯性张量不会算?用这个公式:对于长方体,Ixx = (1/12)*m*(h² + d²)。懒得算的话,直接用在线计算器。别在这上面卡太久。
3.2 给机器人装上轮子:joint的使用
一个方块太无聊了。我们加两个轮子,让它能跑起来。
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="-0.2 0.25 0" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
joint就是关节。它连接两个link,决定了它们之间的相对运动。
- type="continuous":轮子嘛,得一直转。continuous就是无限旋转。
- parent/child:谁是谁的爹。这里base_link是父,轮子是子。
- origin:轮子装在底盘的什么位置。xyz是偏移,rpy是旋转。
- axis:绕哪个轴转。轮子绕Y轴转,所以是"0 1 0"。
你想想看,如果axis写错了,轮子就会在地上「横着滚」。我刚开始学的时候,轮子在地上平移就是不转圈,查了半天才发现是轴写错了。嗯,这种错误犯一次就够了。
3.3 SDF vs URDF:到底该用哪个?
这个问题,几乎每个初学者都会问。我直接给你结论:
| 特性 | URDF | SDF |
|---|---|---|
| 出身 | ROS原生格式 | Gazebo原生格式 |
| 支持世界环境 | ❌ 只能描述单个机器人 | ✅ 可以描述整个场景 |
| 物理属性 | 基础支持 | 更丰富(摩擦、阻尼、弹性) |
| 传感器模型 | 需要插件扩展 | 原生支持 |
| 代码可读性 | 简单直观 | 稍复杂但更灵活 |
我的建议是:
- 做ROS2导航:用URDF。因为Nav2和robot_state_publisher对URDF支持最好。
- 做复杂仿真:用SDF。比如你要模拟一个带传送带的工厂,SDF是唯一选择。
- 混合使用:URDF描述机器人,SDF描述环境。这是最常用的方案。
注意:千万不要在URDF里写<gazebo>标签来定义物理属性。我曾经这么干过,结果Gazebo和ROS的解析器打架,模型加载出来轮子陷进地里。正确的做法是用单独的SDF文件或world文件来覆盖物理参数。
3.4 在Gazebo中加载模型
模型写好了,怎么让它出现在Gazebo里?
方法一:直接启动时加载
gazebo --verbose my_robot.world
方法二:通过ROS2 launch文件
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', 'my_robot', '-file', 'my_robot.urdf'],
output='screen'
)
])
方法三:手动插入(调试时最方便)
# 在Gazebo GUI中
Model -> Insert -> 选择你的模型文件
我个人调试时,喜欢先用方法三。看到模型出现在场景里了,再写launch文件。这样能快速定位是模型问题还是启动脚本问题。
3.5 避坑指南:模型加载失败的常见原因
模型加载失败,90%是这几个原因:
- 路径问题:Gazebo找不到你的模型文件。解决办法:设置GAZEBO_MODEL_PATH环境变量。
- 缺少依赖:URDF里引用了其他模型(比如轮子单独一个文件),但没把路径加进去。
- 惯性参数错误:惯性张量不合法(比如对角线为0)。Gazebo会直接崩溃。
- 编码问题:文件保存成了UTF-8带BOM,Gazebo解析到第一个字符就报错。
调试技巧:用check_urdf命令检查URDF语法。用gz sdf --check检查SDF语法。这两个命令能帮你省下至少一半的调试时间。
3.6 本章知识体系
下面这张图,是我梳理的本章核心逻辑。建议你保存下来,写模型时对照着看。
这张图把本章内容串起来了。左边是URDF路线,右边是SDF路线,底部是加载方式。你写模型时,沿着这条线走,基本不会漏掉关键环节。
好了,模型文件的基础就这些。下一节我们会把这个模型跑起来,让它在Gazebo里真正动起来。到时候你会发现——嗯,写模型只是开始,让模型「听话」才是真功夫。