可视化平台RVIZ2与URDF建模语言
可视化简介



坐标相关、激光雷达相关、摄像头相关的rviz2插件





rviz2基本使用

以sudo apt install ros-[ROS_DISTRO]-desktop格式安装ROS2时,RViz已经默认被安装了。
sudo apt install ros-[ROS_DISTRO]-rviz2
备注: 命令中的 [ROS_DISTRO] 指代ROS2版本。

方式1:rviz2;
方式2:ros2 run rviz2 rviz2。

rviz2 启动之后,默认界面如下:
- 上部为工具栏:包括视角控制、预估位姿设置、目标设置等,还可以添加自定义插件;
- 左侧为插件显示区:包括添加、删除、复制、重命名插件,显示插件,以及设置插件属性等功能;
- 中间为3D试图显示区:以可视化的方式显示添加的插件信息;
- 右侧为观测视角设置区:可以设置不同的观测视角;
- 下侧为时间显示区:包括系统时间和ROS时间。
左侧插件显示区默认有两个插件:
- Global Options:该插件用于设置全局显示相关的参数,一般情况下,需要自行设置的是 Fixed Frame 选项,该选项是其他所有数据在可视化显示时所参考的全局坐标系;
- Global Status:该插件用于显示在 Global Options 设置完毕 Fixed Frame 之后,所有的坐标变换是否正常。

最上面是菜单区,左侧是插件显示区,中间是3D调试区,右侧是视角切换区域,最下方是时间区ROS Time是ROS2时间,Wall Time是系统时间,Elapsed是Rviz2运行的时间。

可以保存rviz2的配置,也可以打开配置

设置显示面板

可以设置平面网格的个数

可以设置竖直方向网格的个数

可以设置网格边长是多大

改变sRGB的4个通道

可以改变视角

设置偏移量,如果Z是-1,那么网格会相对于坐标系下沉1个单位

Fixed name一般是根坐标系的名称
background color就是背景色
frame rate是坐标系的发布频率

全局状态,当fixed name设置对后,就无警告了

视角切换(一般默认)

可以翻转Z轴
常用插件:
| 序号 | 名称 | 功能 | 消息类型 |
|---|---|---|---|
| 1 | Axes | 显示 rviz2 默认的坐标系。 | |
| 2 | Camera | 显示相机图像,必须需要使用消息:CameraInfo。 | sensor_msgs/msg/Image,sensor_msgs/msg/CameraInfo |
| 3 | Grid | 显示以参考坐标系原点为中心的网格。 | |
| 4 | Grid Cells | 从网格中绘制单元格,通常是导航堆栈中成本地图中的障碍物。 | nav_msgs/msg/GridCells |
| 5 | Image | 显示相机图像,但是和Camera插件不同,它不需要使用 CameraInfo 消息。 | sensor_msgs/msg/Image |
| 6 | InteractiveMarker | 显示来自一个或多个交互式标记服务器的 3D 对象,并允许与它们进行鼠标交互。 | visualization_msgs/msg/InteractiveMarker |
| 7 | Laser Scan | 显示激光雷达数据。 | sensor_msgs/msg/LaserScan |
| 8 | Map | 显示地图数据。 | nav_msgs/msg/OccupancyGrid |
| 9 | Markers | 允许开发者通过主题显示任意原始形状的几何体。 | visualization_msgs/msg/Marker,visualization_msgs/msg/MarkerArray |
| 10 | Path | 显示机器人导航中的路径相关数据。 | nav_msgs/msg/Path |
| 11 | PointStamped | 以小球的形式绘制一个点。 | geometry_msgs/msg/PointStamped |
| 12 | Pose | 以箭头或坐标轴的方式绘制位姿。 | geometry_msgs/msg/PoseStamped |
| 13 | Pose Array | 绘制一组 Pose。 | geometry_msgs/msg/PoseArray |
| 14 | Point Cloud2 | 绘制点云数据。 | sensor_msgs/msg/PointCloud,sensor_msgs/msg/PointCloud2 |
| 15 | Polygon | 将多边形的轮廓绘制为线。 | geometry_msgs/msg/Polygon |
| 16 | Odometry | 显示随着时间推移累积的里程计消息。 | nav_msgs/msg/Odometry |
| 17 | Range | 显示表示来自声纳或红外距离传感器的距离测量值的圆锥。 | sensor_msgs/msg/Range |
| 18 | RobotModel | 显示机器人模型。 | |
| 19 | TF | 显示 tf 变换层次结构。 | |
| 20 | Wrench | 将geometry_msgs /WrenchStamped消息显示为表示力的箭头和表示扭矩的箭头加圆圈。 | geometry_msgs/msg/WrenchStamped |
| 21 | Oculus | 将 RViz 场景渲染到 Oculus 头戴设备。 |
上述每一种插件又包含了诸多属性,可以通过设置插件属性来控制插件的最终显示效果。

Image是摄像头数据插件
LaserScan是激光雷达数据插件
TF是坐标变换插件
RobotModel是机器人模型插件
ros2 run rviz2 rviz2 -d xxx.rviz
#可以读取自己保存的rviz配置
rviz2集成URDF基本流程
案例分析





请调用如下命令,安装案例所需的两个功能包(可以控制机器人关节运动):
sudo apt install ros-humble-joint-state-publisher
sudo apt install ros-humble-joint-state-publisher-gui
终端下进入工作空间的src目录,调用如下命令创建C++功能包。
ros2 pkg create cpp06_urdf --build-type ament_cmake
功能包下新建 urdf、rviz、launch、meshes目录以备用,其中 urdf 目录下再新建子目录 urdf 与 xacro,分别用于存储 urdf 文件和 xacro 文件。

launch存放launch文件
urdf文件里面存放urdf三维模型文件
meshes存放stl模型
xacro可以简化urdf文件,并且增强其灵活性
rviz存放rviz2的配置
框架搭建

<robot> name="hello_world"
<link> name="base_link"
<visual>
<geometry>
<box size="0.5 0.2 0.1"/>
</geometry>
</visual>
</link>
</robot>
标准的XML文件


from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command,LaunchConfiguration
from launch.actions import DeclareLaunchArgument
#示例:ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo01_helloworld.urdf
def generate_launch_description():
cpp06_urdf_dir = get_package_share_directory("cpp06_urdf")
default_model_path = os.path.join(cpp06_urdf_dir,"urdf/urdf","demo01_helloworld.urdf")
default_rviz_path = os.path.join(cpp06_urdf_dir,"rviz","display.rviz")
model = DeclareLaunchArgument(name="model", default_value=default_model_path)
# 加载机器人模型
# 1.启动 robot_state_publisher 节点并以参数方式加载 urdf 文件
robot_description = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_publisher = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description": robot_description}]
)
# 2.启动 joint_state_publisher 节点发布非固定关节状态
joint_state_publisher = Node(
package="joint_state_publisher",
executable="joint_state_publisher"
)
# rviz2 节点
rviz2 = Node(
package="rviz2",
executable="rviz2"
# arguments=["-d", default_rviz_path]
)
return LaunchDescription([
model,
robot_state_publisher,
joint_state_publisher,
rviz2
])

<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>

install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)

colcon build --packages-select cpp06_urdf

source install/setup.bash
ros2 launch cpp06_urdf display.launch.py

小提示:
在本章的后续案例中,所有实现都遵循上述步骤,在后续案例中我们只需要关注 urdf 实现即可,launch 文件和 配置文件无需修改。




urdf文件
按ctrl+\生成注释



因为安装过urdf插件,所以有提示,需要创建robot根标签

第一个属性为机器人名字

第二个有个xml namespace,指向xacro


第三个有个xml namespace,指向xacro,然后还有一个机器人名字
(暂时用最简单的,也就是第一个)

link标签叫连杆,也需要起个名字,连杆一般指刚体部分
link有个子集标签,叫visual
visual标签下要写机器人形状
然后该标签下又有一个子集标签叫geometry(几何形状)
然后又有子集标签叫box(矩形体状)后面的size后面对应长宽高

<robot name="boxrobot">
<link name="base_link">
<visual>
<geometry>
<box size="1.0 0.5 0.1"/>
</geometry>
</visual>
</link>
</robot>
xacro工具(将磁盘文件加载到ROS2中的工具)
搜索是否安装过xacro
ros2 pkg list | grep -i xacro

如果打印了xacro说明安装了,如果没打印,则要手动安装
sudo apt-get update
sudo apt-get install ros-humble-xacro
使用xacro读取文件

文件里的内容被输出到了终端,咱们一般集成到launch文件中。咱们在终端里是只能查看内容,但是用launch就可以把文件弄到节点里,也就是集成到ROS2里。
launch核心实现
核心实现就三步,加载机器人模型,节点发布非固定关节的状态,启动rviz2节点

创建rviz2节点很简单,就声明下包名,声明下executable。
加载机器人模型比较复杂,加载机器人模型,也要创建一个节点,
然后有参数,参数里有个键叫robot_description,然后这个键对应一个值
值是ParameterValue对象,这个对象里执行了一个指令,叫xacro,然后后面又有一个Launch配置,其实就是urdf文件的路径。




这个值,其实就是URDF文件里的内容,但是内容太长了,所以我们把它封装成一个对象。


命令行,不能直接当对象参数值,所以还要封装

Comand是专门封装终端指令执行的


记得xacro后面要有空格,这里填路径


此时已经定位到cpp06_urdf的share路径下的cpp06_urdf路径了,返回的也就是该路径的字符串



from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
p_value = ParameterValue(Command(["xacro ",get_package_share_directory("cpp06_urdf") + "/urdf/urdf/demo01_boxrobot.urdf"]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
rviz2 = Node(
package="rviz2",
executable="rviz2"
)
def generate_launch_description():
return LaunchDescription([robot_state_pub,rviz2])




点击左下角Add添加RobotModel插件







新建一个坐标系插件,长沿着X,宽沿着Y,高沿着Z
launch优化说明与实现
我们还需要优化三个点,第一个是打开关节节点,第二是设置rviz2默认配置文件,第三是Launch文件中我们将读取的urdf文件写死了,所以要优化结构。

还要启动这个节点,来控制关节运动,可以改成joint_state_publisher_gui,出现图形化界面。





保存一下rviz2的配置

正常的指令是
ros2 run rviz2 rviz2 -d rviz2配置的路径



创建一个参数叫model,值是后面那一长串。

LaunchConfiguration是解析参数

记得要把model放在最前面,放在后面是不可以的,现在已经把路径封装完毕了。
现在启动是正常启动默认的urdf路径。

解析非默认值的urdf,在终端里也有类似于get_package_share_directory,以下就是(这里参数model少写了个L)要把参数值用反引号(ESC与TAB中间的按键)框起来。

ros2 pkg prefix --share cpp06_urdf
ros2 run launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/hahah.urdf



from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
model = DeclareLaunchArgument(name="model",default_value=get_package_share_directory("cpp06_urdf") + "/urdf/urdf/demo01_boxrobot.urdf")
p_value = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
joint_state_pub = Node(
package="joint_state_publisher",
executable="joint_state_publisher"
)
rviz2 = Node(
package="rviz2",
executable="rviz2",
arguments=["-d",get_package_share_directory("cpp06_urdf") + "/rviz/urdf.rviz"]
)
def generate_launch_description():
return LaunchDescription([model,robot_state_pub,joint_state_pub,rviz2])
继续优化最终的代码为:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
import os
cpp06_urdf_dir = get_package_share_directory("cpp06_urdf")
default_model_path = os.path.join(cpp06_urdf_dir,"urdf/urdf","demo01_boxrobot.urdf")
default_rviz_path = os.path.join(cpp06_urdf_dir,"rviz","urdf.rviz")
model = DeclareLaunchArgument(name="model",default_value=default_model_path)
p_value = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
# 关节信息节点
# joint_state_pub = Node(
# package="joint_state_publisher",
# executable="joint_state_publisher"
# )
# 关节信息节点图形界面(建议)
joint_state_pub = Node(
package="joint_state_publisher_gui",
executable="joint_state_publisher_gui"
)
rviz2 = Node(
package="rviz2",
executable="rviz2",
# arguments=["-d",get_package_share_directory("cpp06_urdf") + "/rviz/urdf.rviz"]
arguments=["-d",default_rviz_path]
)
def generate_launch_description():
return LaunchDescription([model,robot_state_pub,joint_state_pub,rviz2])
URDF语法
简介

robot根标签


对机器人进行分割,分割成几个子集,比如一个子集描述头,一个子集描述身子,最后再合成合集,成机器人
虽然我的子文件和主文件在逻辑上是有包含关系的,但是其实,他们都是单独的urdf文件。主文件中,robot标签的name属性必须写,子文件可以不写,如果写,那么子文件name的值与主文件的必须相同!


link标签
简介

每一个link都是刚体,都是独立部件
Link是通过joint进行拼接的
Link主要包含三部分,Visual,Collision和Inertial

没加尖括号的是属性,加了的是标签
- name (可选):指定link名称,此名称会映射为同名坐标系,还可以通过引用该值定位定位link。
(必填):用于设置link的形状,比如:立方体、球体或圆柱。 :立方体标签,通过size属性设置立方体的边长,原点为其几何中心。 :圆柱标签,通过radius属性设置圆柱半径,通过length属性设置圆柱高度,原点为其几何中心。 :球体标签,通过radius属性设置球体半径,原点为其几何中心。 :通过属性filename引用“皮肤”文件,为link设置外观,该文件必须是本地文件。使用 package:// / 为文件名添加前缀。 (可选):用于设置link的相对偏移量以及旋转角度,如未指定则使用默认值(无偏移且无旋转)。 - xyz :表示x、y、z三个维度上的偏移量(以米为单位),不同数值之间使用空格分隔,如未指定则使用默认值(三个维度无偏移)。
- rpy :表示翻滚、俯仰与偏航的角度(以弧度为单位),不同数值之间使用空格分隔,如未指定则使用默认值(三个维度无旋转)。
(可选):视觉元素的材质。也可以在根标签robot中定义material标签,然后,可以在link中按名称进行引用。 - name (可选):为material指定名称,可以通过该值进行引用。
(可选):rgba 材质的颜色,由代表red/green/blue/alpha 的四个数字组成,每个数字的范围为 [0,1]。 (可选):材质的纹理,可以由属性filename设置。
当有多个Visual的时候,需要给Visual设置name,所以是个可选项。
Collision与仿真有关系,我们可以给我们的机器人的刚体设置一个碰撞区间,只要障碍物进入了区间,那么就发生了碰撞,一般碰撞区间要比实际大小要大。
- name (可选):为collision设置名称。
(必须):请参考visual标签的geometry使用规则。 (可选):请参考visual标签的origin使用规则。
Inertial是设置惯性矩阵的,也是和仿真有关系的。比如说机器人刹车,会出现前倾的情况,比如说惯性矩阵的重心高一些,那么急刹车就会出现翻车的情况了。
(可选):该位姿(平移、旋转)描述了链接的质心框架 C 相对于链接框架 L 的位置和方向。 - xyz :表示从 Lo(链接框架原点)到 Co(链接的质心)的位置向量为 x L̂x + y L̂y + z L̂z,其中 L̂x、L̂y、L̂z 是链接框架 L 的正交单位向量。
- rpy :将 C 的单位向量 Ĉx、Ĉy、Ĉz 相对于链接框架 L 的方向表示为以弧度为单位的欧拉旋转序列 (r p y)。注意:Ĉx、Ĉy、Ĉz 不需要与连杆的惯性主轴对齐。
(必填):通过其value属性设置link的质量。 (必填):对于固定在质心坐标系 C 中的单位向量 Ĉx、Ĉy、Ĉz,该连杆的惯性矩 ixx、iyy、izz 以及关于 Co(连杆的质心)的惯性 ixy、ixz、iyz 的乘积。
注意:
使用

<robot name="link_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
</robot>

ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo02_link.urdf


矩形体,球形,圆柱体






平移量,X平移1,Y和Z平移0,rpy旋转度设置为0(旋转度分别是欧拉角里的翻滚角Roll(绕X),俯仰角Pitch(绕Y),航向角Yaw(绕Z))


发现长方体在X上偏移了1


让航向角Yaw(绕Z运动)为0.5 rad


设置翻滚角Roll(绕X运动)为0.5rad


设置俯仰角Pitch(绕Y运动)为0.5rad

sRGB是R,G,B,Alpha(浮点模型,所以范围是0-1.0)




如果一个颜色要被用好几次,可以封装成一个类似于全局变量的东西,然后在其他link中调用时,直接用

<robot name="link_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
</robot>
使用补充

mesh标签是引用皮肤文件,一般是stl文件,可以用SolidWorks导出,可以看文档后面的SW2URDF
如果不会使用solidworks,可以学,这东西2天半就能学会,只是一个工具,只会画图没有啥水平,最重要的还是机械设计比较难。想学的可以看兄弟社团机械学会微信公众号的视频进行学习。
当然,没时间学习可以直接用别人画好的机器人,在Github上搜索turtlebot3



这里有已经导出的模型,bases里是外观的模型,sensors是传感器的模型,wheels是轮子的模型。
我们克隆下仓库,注意要克隆branch是ros2的分支。

我们引用一个就行了,看看效果即可,真正的应用还是要用SolidWorks通过SW2URDF插件进行导出
把项目里的meshes/bases里的burger_base.stl拷贝到我们WS里的meshes目录


filename是写刚才的皮肤文件,package://就是协议名,后面跟包名,也就是cpp06_urdf。然后跟功能包下的文件路径,也就是meshes/burger_base.stl(其实也就是share下的路径)

因为是个三维模型,所以在填scale大小缩放时,需要填3个比例,咱们都填1.0即可。

为什么显示的模型会这么大呢,因为rviz2以米为单位,而stl是以mm为单位,注意。在机械上,默认不说单位就都是mm,不要乱改单位,一般都要以mm为单位,咱们是做机器人的,要专业一些。


joint标签
简介
urdf 中的 joint 标签用于描述机器人关节的运动学和动力学属性,还可以指定关节运动的安全极限,机器人的两个部件(分别称之为 parent link 与 child link)以”关节“的形式相连接,不同的关节有不同的运动形式: 旋转、滑动、固定、旋转速度、旋转角度限制....,比如:安装在底座上的轮子可以360度旋转,而摄像头则可能是完全固定在底座上。

- name (必填):为关节命名,名称需要唯一。
- type (必填):设置关节类型,可用类型如下:
- continuous:旋转关节,可以绕单轴无限旋转。
- revolute:旋转关节,类似于 continues,但是有旋转角度限制。
- prismatic:滑动关节,沿某一轴线移动的关节,有位置极限。
- planer:平面关节,允许在平面正交方向上平移或旋转。
- floating:浮动关节,允许进行平移、旋转运动。
- fixed:固定关节,不允许运动的特殊关节。
以下是子级标签
(必填):指定父级link。 - link (必填):父级link的名字,是这个link在机器人结构树中的名字。
(必填):指定子级link。 - link (必填):子级link的名字,是这个link在机器人结构树中的名字。
(可选):这是从父link到子link的转换,关节位于子link的原点。 - xyz :各轴线上的偏移量。
- rpy :各轴线上的偏移弧度。
- <axis> (可选):如不设置,默认值为(1,0,0)。
- xyz :用于设置围绕哪个关节轴运动。
(可选):关节的参考位置,用于校准关节的绝对位置。 - rising (可选):当关节向正方向移动时,该参考位置将触发上升沿。
- falling (可选):当关节向正方向移动时,该参考位置将触发下降沿。
(可选):指定接头物理特性的元素。这些值用于指定关节的建模属性,对仿真较为有用。 - damping (可选):关节的物理阻尼值,默认为0。
- friction (可选):关节的物理静摩擦值,默认为0。
(关节类型是revolute或prismatic时为必须的): - lower (可选):指定关节下限的属性(旋转关节以弧度为单位,棱柱关节以米为单位)。如果关节是连续的,则省略。
- upper (可选):指定关节上限的属性(旋转关节以弧度为单位,棱柱关节以米为单位)。如果关节是连续的,则省略。
- effort (必填):指定关节可受力的最大值。
- velocity (必填):用于设置最大关节速度(旋转关节以弧度每秒 [rad/s] 为单位,棱柱关节以米每秒 [m/s] 为单位)。
(可选):此标签用于指定定义的关节模仿另一个现有关节。该关节的值可以计算为value = multiplier * other_joint_value + offset。 - joint (必填):指定要模拟的关节的名称。
- multiplier (可选):指定上述公式中的乘法因子。
- offset (可选):指定要在上述公式中添加的偏移量,默认为 0(旋转关节的单位是弧度,棱柱关节的单位是米)。
- <safety_controller> (可选):安全控制器。
- soft_lower_limit (可选):指定安全控制器开始限制关节位置的下关节边界,此限制需要大于joint下限。
- soft_upper_limit (可选):指定安全控制器开始限制关节位置的关节上边界的属性,此限制需要小于joint上限。
- k_position (可选):指定位置和速度限制之间的关系。
- k_velocity (必填):指定力和速度限制之间的关系。

关节名称是必填的,且是唯一的。

咱们最常用的是有限位的revolute类型的关节,continuous可无限旋转的关节,fixed固定关节,这个根据具体的关节类型来填。
revolute一般用于工业机器人机械臂的关节,continuous比如舵轮结构的“关节”,fixed就是一些固定的不能运动的结构关节。

子集标签很多,咱们用的最常用的就几个,记住常用的即可。

parent标签,link属性指定父级link的名字。

child标签类似。


这个轴,默认是1,0,0,以X轴进行旋转,但是咱们一般是需要设置的,可通过SolidWorks设置基准轴进行设置。
剩下的标签,都与关节类型有关,比如limit,如果关节类型是revolute,而且不设置limit,那么在joint_state_publisher_gui里是无法调关节的角度的。

其他标签用到的时候再进行介绍。
练习

先把俩关节,单独实现,然后再通过joint关节进行连接。







黄色的话,红和绿要多一些。

这是红色。

底盘Link就创建完毕了,

摄像头Link也创建完毕了,

关节名字设置为camera2base_link,也就是摄像头连接底座的joint,然后类型是360度都可以转的continuous。

填入子级Link与父级Link

这样两个Link就通过该Joint连接到一起了。

还需要设置这俩选项,咱们先不设置,先看默认是什么状态。
colcon build --packages-select cpp06_urdf
source install/setup.bash
ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo03_joint.urdf


可以打开TF看看坐标系,勾上Show Names,发现是重合的。所以显示效果不满足咱们的逻辑业务。(默认状态下),所以需要设置偏移量。

如果咱们想把摄像头移动到车头,如图所示,在X上有偏移量,Y没有,但是Z有。然后roll,pitch,yaw上没有。



Z的高度就是1/2的底盘高度+1/2的摄像头高度



咱们要绕Yaw旋转,所以也就是绕Z轴旋转,也就是001,注意一定得是整形,不能是浮点型。

想要看是否能旋转,要打开joint_state_publisher_gui,可以从launch中打开,也可以从终端中直接打开。


ros2 run joint_state_publisher_gui joint_state_publisher_gui


拖拽滚动条可改变摄像头的Yaw

Randomize是随机数,Center是回中(复位)
<robot name="joint_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<link name="camera">
<visual>
<geometry>
<box size="0.02 0.05 0.05" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="red" />
</visual>
</link>
<joint name="camera2baselink" type="continuous">
<parent link="base_link"/>
<child link="camera" />
<origin xyz="0.2 0 0.075" rpy="0 0 0" />
<axis xyz="0 0 1" />
</joint>
</robot>
joint_state_publisher
bug:Yaw不稳定,会一直回中。



其实是因为launch里启动的和终端里启动的冲突了。
解决方案:只启动其中一个。

解决方案:直接在Launch里启动GUI版本的,这样即可解决。(但是不建议)
建议方案:用非GUI版本的,因为以后,我们控制关节是用程序控制,而不是GUI控制。

如果只是想展示模型,用GUI,
如果想用程序控制,用普通版。
base_footprint
bug:机器人底盘半沉入地下




<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.05"/>
</joint>
Z的偏移量要填下沉底盘的距离,也就是整车底盘的一半。


修改参考坐标系
其实这个优化,可以不做,影响不大。但是建议用有basefootprint版本的。
<robot name="base_footprint_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.05"/>
</joint>
<link name="camera">
<visual>
<geometry>
<box size="0.02 0.05 0.05" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="red" />
</visual>
</link>
<joint name="camera2baselink" type="fixed">
<parent link="base_link"/>
<child link="camera" />
<origin xyz="0.2 0 0.075" rpy="0 0 0" />
<axis xyz="0 0 1" />
</joint>
</robot>
练习

没必要太深入练习,咱们可以直接用SolidWorks建模,更为友好。

<robot name="exercise_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<material name="gray">
<color rgba="0.2 0.2 0.2 0.8" />
</material>
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<box size="0.2 0.12 0.07" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.05"/>
</joint>
<link name="front_left_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="frontleftwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="front_left_wheel" />
<origin xyz="0.075 0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<link name="front_right_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="frontrightwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="front_right_wheel" />
<origin xyz="0.075 -0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<link name="back_left_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="backleftwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="back_left_wheel" />
<origin xyz="-0.075 0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<link name="back_right_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="backrightwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="back_right_wheel" />
<origin xyz="-0.075 -0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
</robot>
终端运行
ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo05_exercise.urdf

URDF工具
在 ROS2 中,提供了一些URDF文件相关的工具,比如:
check_urdf命令可以检查复杂的 urdf 文件是否存在语法问题;urdf_to_graphviz命令可以查看 urdf 模型结构,显示不同 link 的层级关系。
当然,要使用工具之前,请先安装,安装命令:sudo apt install liburdfdom-tools。
check_urdf 语法检查
进入urdf文件所属目录,调用:check_urdf urdf文件,如果不抛出异常,说明文件合法,否则非法。
示例,终端下进入功能包 cpp06_urdf 的 urdf/urdf 目录,执行如下命令:
check_urdf demo05_exercise.urdf
urdf 文件如无异常,将显示urdf中link的层级关系,如下图所示:

否则将会给出错误提示。

演示错误,

urdf_to_graphviz 结构查看
进入urdf文件所属目录,调用:urdf_to_graphviz urdf文件,当前目录下会生成 pdf 文件。
示例,终端下进入功能包 cpp06_urdf 的 urdf/urdf 目录,执行如下命令:
urdf_to_graphviz demo05_exercise.urdf
当前目录下,将生成以urdf中robot名称命名的.pdf和.gv文件,打开pdf文件会显示如下图内容:

在上图中会以树形结构显示link与joint的关系。
注意: 该工具以前名为urdf_to_graphiz现建议使用urdf_to_graphviz替代。

urdf_to_graphiz是历史版本,已经被废弃,建议用urdf_to_graphviz。




黑色方框代表Link,蓝色代表Joint,也会展示平移量和旋转度等信息。
SW2URDF
solidworks简介
SolidWorks是一种计算机辅助设计(CAD)和计算机辅助制造(CAM)软件,由Dassault Systèmes SolidWorks Corp.开发。它主要用于工程设计和制造,可用于创建3D三维模型、进行装配设计、进行工程分析和绘图等。SolidWorks具有直观的用户界面和强大的功能,使工程师和设计师能够快速而精确地设计复杂的零部件和装配体。该软件广泛应用于机械、航空航天、汽车、医疗设备等行业,是工程设计领域的重要工具之一。

solidworks插件sw2urdf介绍
sw2urdf插件维基百科:
https://wiki.ros.org/sw\_urdf\_exporter
github下载链接:
https://github.com/ros/solidworks\_urdf\_exporter/releases
虽然github链接上写着只支持到SW2021,但是目前发现最新版SW(2022、2024经测试)也是可以正常用的。

安装sw2urdf
- 去github上下载sw2urdf插件(这个SW版本不用管,实测在后续的SW版本依然可用)

- 查看SW的安装路径
比如我的路径"C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\"

- 打开插件安装器,默认情况下会自动找到你的SW路径进行安装,如果没有自动找到路径安装,那么需要手动选取SW安装路径。

- 查看插件是否安装成功


如图这样就是安装成功了。
导出URDF与Meshes
- 如图是一个标准的SW装配体图(大家也可以尝试自己手撸一个,里面没有传动装置也可以)
这个过程看不太懂的话,可以参考一下古月居老师的视频,结合本教程一起学习。 【SolidWorks模型导出urdf(古月居老师)-哔哩哔哩】https://www.bilibili.com/video/BV1Tx411o7rH

- 对joint关节建立基准轴

选择关节的圆柱面或者其他面,对转轴进行标定

如果建完基准轴,发现不能够正常展示,那么就打开该选项。


建完所有系后
- 打开export as URDF选项

- 需要先选择base_link基底刚体

这里的Global Origin可能需要自己选择,就选择Origin_Global即可,但是一般选择automatically Generate即可。
- 因为他的base_link通过一个joint连接了一个child_link,所以,这个选项要填1。


咱们把base_link连接的关节叫joint1,把joint1连接的更上面的刚体叫做link1,
然后参考基准轴选择刚才咱们在这个关节处建立的基准轴1,然后joint type关节类型选择revolute(有限位的关节,只有这样,机器人关节才可以正常运动)

这里的参考坐标系系统可以选择automatically Generate,也可以选择Origin_Joint1。
然后再在link1上加一,创建link2。
后面的link3,link4添加步骤是一样的,不再详细展示。



- 点击preview and export

- 对关节进行限位设置
coordinates是坐标系,axis是轴,如果这里Axis坐标显示错误,那么说明Coordinates或者Axis选择错了,请手动选择正确的坐标系或者基准轴。

如果转轴生成错误,比如都是000,可以自己填一下。

比如这里是X轴,可以把左轮全填-100,右轮100(这里,仅供参考,chatgpt提供的解决方案)

- 查看矩阵,并点导出urdf与meshes




这样就成功生成ROS1的WS了,将WS移动至Linux系统。
将ROS1的WS转化为ROS2的WS
- 先将ROS1的WS移动到Linux系统上

- 新建一个ROS2的WS

mkdir -p ros2_4axisrobot_ws/src
cd ros2_4axisrobot_ws/
colcon build
cd src
ros2 pkg create cpp01_urdf --build-type ament_cmake
cd ..
code .
- 完善WS的目录


在./src/cpp01_urdf路径下创建launch、meshes、rviz、urdf等等文件夹,在urdf文件夹下再新建urdf和xacro文件夹
- 复制ROS1的WS里的URDF和Meshes到ROS2的WS中



- 对CMake和package进行配置
在CMake中请添加
install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)
在Package.xml中请添加
<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>
CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(cpp01_urdf)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp01_urdf</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="tungchiahui@gmail.com">tungchiahui</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
- 对launch文件进行编写
详细过程请看URDF有关Launch的核心优化那一节

from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
import os
cpp01_urdf_dir = get_package_share_directory("cpp01_urdf")
default_model_path = os.path.join(cpp01_urdf_dir,"urdf/urdf","4axisrobot.urdf")
default_rviz_path = os.path.join(cpp01_urdf_dir,"rviz","urdf.rviz")
model = DeclareLaunchArgument(name="model",default_value=default_model_path)
p_value = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
# 关节信息节点(建议)
# joint_state_pub = Node(
# package="joint_state_publisher",
# executable="joint_state_publisher"
# )
# 关节信息节点图形界面
joint_state_pub = Node(
package="joint_state_publisher_gui",
executable="joint_state_publisher_gui"
)
rviz2 = Node(
package="rviz2",
executable="rviz2",
# arguments=["-d",get_package_share_directory("cpp06_urdf") + "/rviz/urdf.rviz"]
arguments=["-d",default_rviz_path]
)
def generate_launch_description():
return LaunchDescription([model,robot_state_pub,joint_state_pub,rviz2])
- 对URDF文件进行修改
修改的内容主要有两项,一个是meshes的路径,一个是删掉易引起报错的注释。
主要由于ROS1的WS和ROS2的WS路径不同,所以,我们只需要修改有关路径的内容,比如说Meshes的路径


package://一般是share目录,所以我们需要从cpp01_urdf这一级开始写(share目录需要编译后才会显示)

然后按Ctrl+H


然后还需要删除掉urdf文件刚开始的注释,否则也会报错

如下是替换完毕的URDF文件:
<?xml version="1.0" encoding="utf-8"?>
<robot
name="4axisrobot">
<link
name="base_link">
<inertial>
<origin
xyz="-0.0819771145953119 -0.0394328123681598 -0.0605612328464761"
rpy="0 0 0" />
<mass
value="7.43807257011133" />
<inertia
ixx="0.33173827700719"
ixy="-1.91350798633049E-05"
ixz="-2.38586363043925E-05"
iyy="0.510937907009661"
iyz="1.14706407748075E-05"
izz="0.66107272632796" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/base_link.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/base_link.STL" />
</geometry>
</collision>
</link>
<link
name="link1">
<inertial>
<origin
xyz="0.19141 -0.050676 -0.030057"
rpy="0 0 0" />
<mass
value="21.822" />
<inertia
ixx="0.50464"
ixy="0.13295"
ixz="0.087353"
iyy="0.59136"
iyz="-0.066053"
izz="0.64081" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link1.STL" />
</geometry>
<material
name="">
<color
rgba="0.79216 0.81961 0.93333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link1.STL" />
</geometry>
</collision>
</link>
<joint
name="joint1"
type="revolute">
<origin
xyz="-0.081947 -0.039447 0.11191"
rpy="0.40394 -1.5708 0" />
<parent
link="base_link" />
<child
link="link1" />
<axis
xyz="1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
<link
name="link2">
<inertial>
<origin
xyz="0.00711598521454873 0.430460941882332 -0.181074256126024"
rpy="0 0 0" />
<mass
value="30.6115349647897" />
<inertia
ixx="2.84056101478768"
ixy="0.00176700425270318"
ixz="-0.000605507690039326"
iyy="0.803420593303961"
iyz="0.881921487314161"
izz="2.25142021571439" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link2.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link2.STL" />
</geometry>
</collision>
</link>
<joint
name="joint2"
type="revolute">
<origin
xyz="0.35141 -0.28119 0.13918"
rpy="-2.0566 1.1982 1.5708" />
<parent
link="link1" />
<child
link="link2" />
<axis
xyz="-1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
<link
name="link3">
<inertial>
<origin
xyz="0.00112887115421612 0.0366346915274407 0.31720330934553"
rpy="0 0 0" />
<mass
value="32.192726667578" />
<inertia
ixx="4.01515983126431"
ixy="0.0013193752174692"
ixz="0.0113529938102155"
iyy="3.86196311673823"
iyz="-0.336440022491883"
izz="0.399977905540088" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link3.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link3.STL" />
</geometry>
</collision>
</link>
<joint
name="joint3"
type="revolute">
<origin
xyz="0.3765 0.82481 -0.49591"
rpy="2.5802 0 0" />
<parent
link="link2" />
<child
link="link3" />
<axis
xyz="1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
<link
name="link4">
<inertial>
<origin
xyz="0.162516620557178 -0.213844847423599 3.38133504529381E-07"
rpy="0 0 0" />
<mass
value="12.1646007734167" />
<inertia
ixx="0.288208127188405"
ixy="0.00080326659836716"
ixz="8.72489392803044E-07"
iyy="0.107000706575729"
iyz="-1.48935044230573E-07"
izz="0.295147863994059" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link4.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link4.STL" />
</geometry>
</collision>
</link>
<joint
name="joint4"
type="revolute">
<origin
xyz="0 0.070837 0.99728"
rpy="0.82315 -1.5708 0" />
<parent
link="link3" />
<child
link="link4" />
<axis
xyz="1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
</robot>
- 编译
colcon build --packages-select cpp01_urdf


- 更新终端环境
source install/setup.bash

- 运行Launch
ros2 launch cpp01_urdf display.launch.py


- 在Rviz2中添加插件以及基本配置

- 使用joint_state_publisher_gui可以调关节的角度


- 保存rviz2的配置到rviz文件夹中
首先点击rviz2的菜单栏上的File,然后选择Save Config as

可以去本节简介看效果视频
注意事项(仅供参考,ChatGPT的解决方案)
坐标系1
轮子要用continuous的关节,并且尽量自己选坐标系,要求从后面看车的话,左是Y,前是X,上是Z。所有的坐标系都要求。
可以先让他帮你自动生成一个,你再去修改坐标系,要简单一些。



坐标系让车躺着
如果你的车在rviz2里是侧着睡觉的,那么:
哈哈,没错,这种“整车侧着躺尸”的情况 90%就是 STL 的坐标轴方向搞错了 。很多 CAD 工具导出的 STL 模型默认是:
- Z 轴朝前(例如 SolidWorks:Z朝前,Y朝上,用右手坐标系推X坐标系位置,从车屁股后面看是朝左。)
- 或者 Y 轴朝上(例如 Blender)
而 ROS/URDF 的坐标系统是:
- X 向前(前进方向)
- Y 向左(平移方向)
- Z 向上(重力方向)
车直走变旋转,旋转变直走
那说明你的转轴有问题。
如果向前走成了左转,那么说明左轮的转轴错了,少了个符号,加上就行了。
比如这个010,你应该改成0-10

xacro
场景、作用与概念
场景
前面 URDF 文件构建机器人模型的过程中,存在若干问题。
问题1:在设计关节的位置时,需要按照一定的规则计算,规则是固定的,但是在 URDF 中依赖于人工计算,存在不便,容易计算失误,且当某些参数发生改变时,还需要重新计算。
问题2:URDF中的部分内容是高度重复的,比如车轮的设计实现,不同轮子只是部分参数不同,形状、颜色、翻转量都是一致的,在实际应用中,构建复杂的机器人模型时,更是易于出现高度重复的设计,按照一般的编程思想涉及到重复代码应该考虑封装、复用,但是在之前的URDF文件中并没有相关操作。
......
如果在一般编程语言中遇到类似问题,我们可以通过变量结合函数解决。对应的,在 ROS 中也给出了类似编程的优化方案,该方案称之为: Xacro(可以理解为urdf2.0) 。
概念
Xacro 是 XML Macros 的缩写,Xacro 是一种 XML 宏语言,是可编程的 XML。
Xacro 可以声明变量,可以通过数学运算求解;可以使用流程控制控制执行顺序;还可以通过宏封装(可以想成函数)、复用功能,从而提高代码复用率以及程序的安全性。
作用
较之于纯粹的 URDF 实现,可以编写更安全、精简、易读性更强的机器人模型文件,且可以提高编写效率。
快速体验
先安装xacro
#humble版本
sudo apt install ros-humble-xacro
#jazzy版本
sudo apt install ros-jazzy-xacro
1.需求
使用xacro优化 6.4.4 URDF练习 中的小车底盘实现,需要使用变量封装车辆参数,并使用 xacro 宏封装轮子重复的代码并调用宏创建四个轮子(注意: 在此,演示 xacro 的基本使用,不必要生成合法的 URDF )。
2.实现
功能包cpp06_urdf的urdf/xacro目录下,新建xacro文件demo01_helloworld.urdf.xacro,并编辑文件,输入如下内容:
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="wheel_radius" value="0.025" />
<xacro:property name="wheel_length" value="0.02" />
<xacro:property name="PI" value="3.1415927" />
<xacro:macro name="wheel_func" params="wheel_name" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="wheel_color">
<color rgba="0 0 0 0.3" />
</material>
</visual>
</link>
</xacro:macro>
<xacro:wheel_func wheel_name="left_front"/>
<xacro:wheel_func wheel_name="left_back"/>
<xacro:wheel_func wheel_name="right_front"/>
<xacro:wheel_func wheel_name="right_back"/>
</robot>

宏类似函数
params类似入口参数
子标签类似函数体
终端下进入当前文件所述目录,输入如下指令:
cd src/cpp06_urdf/urdf/xacro/
xacro demo01_helloworld.urdf.xacro
#或者
ros2 run xacro xacro demo01_helloworld.urdf.xacro
终端将会输出如下内容(以下内容是纯urdf):
<?xml version="1.0" ?>
<robot name="mycar">
<link name="left_front_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
<link name="left_back_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
<link name="right_front_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
<link name="right_back_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
</robot>
显然的,通过xacro我们方便的实现了代码复用。

语法
1. 简介

xacro 提供了可编程接口,类似于计算机语言,包括变量声明调用、函数声明与调用等语法实现。在使用 xacro 生成 urdf 时,根标签`robot`中**必须**包含命名空间声明:`xmlns:xacro="``http://wiki.ros.org/xacro``"`。
**变量**
变量用于封装 URDF 中的一些字段,比如: PAI 值,小车的尺寸,轮子半径 ....,变量的基本使用语法包括变量定义、变量调用、变量运算等。
1.1变量定义
语法格式:
```xml
<xacro:property name="变量名" value="变量值" />
```
示例:
```xml
<xacro:property name="PI" value="3.1416"/>
<xacro:property name="wheel_radius" value="0.025"/>
<xacro:property name="wheel_length" value="0.02"/>
```
1.2变量调用
语法格式:
```xml
${变量名}
```
示例:
```xml
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
```
1.3变量运算
语法格式:
```xml
${数学表达式}
```
示例:
```xml
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
```
```xml
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="demo2_pro">
<xacro:property name="num1" value="10"/>
<xacro:property name="num2" value="20"/>
<car length="${num1}" width="${num2}"/>
<sum value="${num1 + num2}"/>
</robot>
```

**宏**
类似于函数实现,提高代码复用率,优化代码结构,提高安全性。宏的基本使用语法包括宏的定义与调用。
2.1宏定义
语法格式:
```xml
<xacro:macro name="宏名称" params="参数列表(多参数之间使用空格分隔)">
.....
参数调用格式: ${参数名}
</xacro:macro>
```
示例:
```xml
<xacro:macro name="wheel_func" params="wheel_name" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="wheel_color">
<color rgba="0 0 0 0.3" />
</material>
</visual>
</link>
</xacro:macro>
```
2.2宏调用
语法格式:
```xml
<xacro:宏名称 参数1=xxx 参数2=xxx/>
```
示例:
```xml
<xacro:wheel_func wheel_name="left_front"/>
<xacro:wheel_func wheel_name="left_back"/>
<xacro:wheel_func wheel_name="right_front"/>
<xacro:wheel_func wheel_name="right_back"/>
```
```xml
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="demo3_func">
<xacro:macro name="get_sum" params="num1 num2">
<sum value="${num1 + num2}"/>
</xacro:macro>
<xacro:get_sum num1="20" num2="30"/>
<xacro:get_sum num1="70" num2="30"/>
</robot>
```

**文件**
机器人由多部件组成,不同部件可能封装为单独的 xacro 文件,最后再将不同的文件集成,组合为完整机器人,可以使用文件包含实现。
语法格式:
<xacro:include filename="其他xacro文件" />
示例:
<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="car_base.xacro" />
<xacro:include filename="car_camera.xacro" />
<xacro:include filename="car_laser.xacro" />
</robot>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="demo4_include">
<xacro:include filename="demo02_base_pro.urdf.xacro"/>
<xacro:include filename="demo03_base_func.urdf.xacro"/>
</robot>

但不建议这样,建议父级xacro和子级xacro使用同样的name。
练习
框架
1.需求
使用xacro创建一个四轮机器人模型,该模型底盘可以参考 6.4.4 URDF练习 中的实现,并且在底盘之上添加了相机与激光雷达。相机与激光雷达的尺寸参数、安装位置可自定义。

2.实现分析
需求中的机器人模型是由底盘、摄像头和雷达三部分组成的,那么可以将每一部分都封装进一个xacro文件,最后再通过xacro文件包含组织成一个完整的机器人模型。
3.实现
功能包cpp06_urdf的urdf/xacro目录下,新建多个xacro文件,分别为:
- car.urdf.xacro:用于包含不同机器人部件对应的xacro文件;
- car_base.urdf.xacro:描述机器人底盘的xacro文件;
- car_camera.urdf.xacro:描述摄像头的xacro文件;
- car_laser.urdf.xacro:描述雷达的xacro文件。
编辑car.urdf.xacro文件,输入如下内容:
<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="car_base.urdf.xacro"/>
<xacro:include filename="car_camera.urdf.xacro"/>
<xacro:include filename="car_laser.urdf.xacro"/>
</robot>
车体
编辑car_base.urdf.xacro文件,输入如下内容:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="PI" value="3.1416"/>
<xacro:property name="base_link_x" value="0.2"/>
<xacro:property name="base_link_y" value="0.12"/>
<xacro:property name="base_link_z" value="0.07"/>
<xacro:property name="distance" value="0.015"/>
<xacro:property name="wheel_radius" value="0.025"/>
<xacro:property name="wheel_length" value="0.02"/>
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<material name="gray">
<color rgba="0.2 0.2 0.2 0.95" />
</material>
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<box size="${base_link_x} ${base_link_y} ${base_link_z}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 ${distance + base_link_z / 2}"/>
</joint>
<xacro:macro name="wheel_func" params="wheel_name is_front is_left" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="gray"/>
</visual>
</link>
<joint name="${wheel_name}2baselink" type="continuous">
<parent link="base_link" />
<child link="${wheel_name}_wheel" />
<origin xyz="${(base_link_x / 2 - wheel_radius) * is_front} ${base_link_y / 2 * is_left} ${(base_link_z / 2 + distance - wheel_radius) * -1}" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
</xacro:macro>
<xacro:wheel_func wheel_name="left_front" is_front="1" is_left="1" />
<xacro:wheel_func wheel_name="left_back" is_front="-1" is_left="1" />
<xacro:wheel_func wheel_name="right_front" is_front="1" is_left="-1" />
<xacro:wheel_func wheel_name="right_back" is_front="-1" is_left="-1" />
</robot>
添加摄像头
编辑car_camera.urdf.xacro文件,输入如下内容:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="camera_x" value="0.012" />
<xacro:property name="camera_y" value="0.05" />
<xacro:property name="camera_z" value="0.01" />
<xacro:property name="camera_joint_x" value="${base_link_x / 2 - camera_x / 2}" />
<xacro:property name="camera_joint_y" value="0.0" />
<xacro:property name="camera_joint_z" value="${base_link_z / 2 + camera_z / 2}" />
<link name="camera">
<visual>
<geometry>
<box size="${camera_x} ${camera_y} ${camera_z}" />
</geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="red" />
</visual>
</link>
<joint name="camera2baselink" type="fixed">
<parent link="base_link" />
<child link="camera" />
<origin xyz="${camera_joint_x} ${camera_joint_y} ${camera_joint_z}" />
</joint>
</robot>
添加雷达
编辑car_laser.urdf.xacro文件,输入如下内容:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<material name="blue">
<color rgba="0.0 0.0 0.4 0.95" />
</material>
<xacro:property name="laser_length" value="0.03" />
<xacro:property name="laser_radius" value="0.03" />
<xacro:property name="laser_joint_x" value="0.0" />
<xacro:property name="laser_joint_y" value="0.0" />
<xacro:property name="laser_joint_z" value="${base_link_z / 2 + laser_length / 2}" />
<link name="laser">
<visual>
<geometry>
<cylinder radius="${laser_radius}" length="${laser_length}" />
</geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="blue" />
</visual>
</link>
<joint name="laser2baselink" type="fixed">
<parent link="base_link" />
<child link="laser" />
<origin xyz="${laser_joint_x} ${laser_joint_y} ${laser_joint_z}" />
</joint>
</robot>
执行
编译后,工作空间终端下调用如下命令执行:
# ROS Humble
ros2 launch cpp06_urdf display.launch.py model:=ros2 pkg prefix --share cpp06_urdf/urdf/xacro/car.urdf.xacro
#ROS Jazzy
colcon build
source install/setup.bash
ros2 run xacro xacro $(ros2 pkg prefix cpp06_urdf)/share/cpp06_urdf/urdf/xacro/car.urdf.xacro -o ./src/cpp06_urdf/urdf/urdf/car.urdf
ros2 launch cpp06_urdf display.launch.py model:=./src/cpp06_urdf/urdf/urdf/car.urdf
命令执行后,rviz2 中可以显示与需求类似的机器人模型。

小结

目前只是空壳,激光雷达和摄像头以及轮子还都是空壳,到进阶联系中,才可以实现作用。