第 9 节

可视化平台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 启动之后,默认界面如下:

  1. 上部为工具栏:包括视角控制、预估位姿设置、目标设置等,还可以添加自定义插件;
  2. 左侧为插件显示区:包括添加、删除、复制、重命名插件,显示插件,以及设置插件属性等功能;
  3. 中间为3D试图显示区:以可视化的方式显示添加的插件信息;
  4. 右侧为观测视角设置区:可以设置不同的观测视角;
  5. 下侧为时间显示区:包括系统时间和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轴

常用插件:

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

没加尖括号的是属性,加了的是标签

(可选):用于描述link的可视化属性,可以设置link的形状(立方体、球体、圆柱等)。

  • 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与仿真有关系,我们可以给我们的机器人的刚体设置一个碰撞区间,只要障碍物进入了区间,那么就发生了碰撞,一般碰撞区间要比实际大小要大。

(可选):link的碰撞属性。可以与link的视觉属性一致,也可以不同,比如:我们会通常使用更简单的碰撞模型来减少计算时间,或者设置的值大于link的视觉属性,以尽量避免碰撞。另外,同一链接可以存在多个 标签实例,多个几何图形组合表示link的碰撞属性。

  • name (可选):为collision设置名称。
  • (必须):请参考visual标签的geometry使用规则。
  • (可选):请参考visual标签的origin使用规则。

Inertial是设置惯性矩阵的,也是和仿真有关系的。比如说机器人刹车,会出现前倾的情况,比如说惯性矩阵的重心高一些,那么急刹车就会出现翻车的情况了。

(可选):用于设置link的质量、质心位置和中心惯性特性,如果未指定,则默认为质量为0、惯性为0。

  • (可选):该位姿(平移、旋转)描述了链接的质心框架 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 的乘积。

注意: 在仿真环境下才需要使用到,如果只是在 rviz2 中集成 urdf,那么不必须为 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>

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天半就能学会,只是一个工具,只会画图没有啥水平,最重要的还是机械设计比较难。想学的可以看兄弟社团机械学会微信公众号的视频进行学习。

https://mp.weixin.qq.com/mp/homepage?\_\_biz=MzI4MjkyMDgyMA==&hid=7&sn=1efc3d3cee0142970227785f767cc7c8&scene=18

当然,没时间学习可以直接用别人画好的机器人,在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

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

  1. 查看SW的安装路径

比如我的路径"C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\"

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

  1. 查看插件是否安装成功

如图这样就是安装成功了。

导出URDF与Meshes

  1. 如图是一个标准的SW装配体图(大家也可以尝试自己手撸一个,里面没有传动装置也可以)

这个过程看不太懂的话,可以参考一下古月居老师的视频,结合本教程一起学习。 【SolidWorks模型导出urdf(古月居老师)-哔哩哔哩】https://www.bilibili.com/video/BV1Tx411o7rH

  1. 对joint关节建立基准轴

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

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

建完所有系后

  1. 打开export as URDF选项

  1. 需要先选择base_link基底刚体

这里的Global Origin可能需要自己选择,就选择Origin_Global即可,但是一般选择automatically Generate即可。

  1. 因为他的base_link通过一个joint连接了一个child_link,所以,这个选项要填1。

咱们把base_link连接的关节叫joint1,把joint1连接的更上面的刚体叫做link1,

然后参考基准轴选择刚才咱们在这个关节处建立的基准轴1,然后joint type关节类型选择revolute(有限位的关节,只有这样,机器人关节才可以正常运动)

这里的参考坐标系系统可以选择automatically Generate,也可以选择Origin_Joint1。

然后再在link1上加一,创建link2。

后面的link3,link4添加步骤是一样的,不再详细展示。

  1. 点击preview and export

  1. 对关节进行限位设置

coordinates是坐标系,axis是轴,如果这里Axis坐标显示错误,那么说明Coordinates或者Axis选择错了,请手动选择正确的坐标系或者基准轴。

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

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

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

这样就成功生成ROS1的WS了,将WS移动至Linux系统。

将ROS1的WS转化为ROS2的WS

  1. 先将ROS1的WS移动到Linux系统上

  1. 新建一个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 .
  1. 完善WS的目录

在./src/cpp01_urdf路径下创建launch、meshes、rviz、urdf等等文件夹,在urdf文件夹下再新建urdf和xacro文件夹

  1. 复制ROS1的WS里的URDF和Meshes到ROS2的WS中

  1. 对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>
  1. 对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])
  1. 对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>
  1. 编译
colcon build --packages-select cpp01_urdf

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

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

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

  1. 使用joint_state_publisher_gui可以调关节的角度

  1. 保存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.  简介

![](https://cdn.tungchiahui.cn/tungwebsite/assets/images/2023/12/30/image1611.webp)

  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>
```

![](https://cdn.tungchiahui.cn/tungwebsite/assets/images/2023/12/30/image1612.webp)

   **宏**

  类似于函数实现,提高代码复用率,优化代码结构,提高安全性。宏的基本使用语法包括宏的定义与调用。

  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>
```

![](https://cdn.tungchiahui.cn/tungwebsite/assets/images/2023/12/30/image1613.webp)

   **文件**

机器人由多部件组成,不同部件可能封装为单独的 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 中可以显示与需求类似的机器人模型。

小结

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