使用 Xacro 清理代码

目标: 学习一些使用 Xacro 减少 URDF 文件中代码量的技巧

教程级别: 中级

时间: 20 分钟

到目前为止,如果您在家中按照所有这些步骤设计自己的机器人,您可能会厌倦做各种数学运算来获得非常简单的机器人描述并正确解析。 幸运的是,您可以使用 xacro 包让您的生活更简单。 它做了三件非常有用的事情。

  • 常量

  • 简单数学

在本教程中,我们将介绍所有这些快捷方式,以帮助减少 URDF 文件的整体大小并使其更易于阅读和维护。

使用 Xacro

顾名思义,xacro 是一种用于 XML 的宏语言。 xacro 程序运行所有宏并输出结果。 典型用法如下所示:

xacro model.xacro > model.urdf

您还可以在启动文件中自动生成 urdf。 这很方便,因为它保持最新状态并且不会占用硬盘空间。 但是,生成它确实需要时间,因此请注意,您的启动文件可能需要更长时间才能启动。

要在启动文件中运行 xacro,您需要将“Command”替换作为“robot_state_publisher”的参数。

path_to_urdf = get_package_share_path('turtlebot3_description') / 'urdf' / 'turtlebot3_burger.urdf'
robot_state_publisher_node = launch_ros.actions.Node(
    package='robot_state_publisher',
    executable='robot_state_publisher',
    parameters=[{
        'robot_description': ParameterValue(
            Command(['xacro ', str(path_to_urdf)]), value_type=str
        )
    }]
)

加载机器人模型的一个更简单的方法是使用 urdf_launch 包自动加载 xacro/urdf。

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackageShare


def generate_launch_description():
    ld = LaunchDescription()

    ld.add_action(IncludeLaunchDescription(
        PathJoinSubstitution([FindPackageShare('urdf_launch'), 'launch', 'display.launch.py']),
        launch_arguments={
            'urdf_package': 'turtlebot3_description',
            'urdf_package_path': PathJoinSubstitution(['urdf', 'turtlebot3_burger.urdf'])}.items()
    ))
    return ld

在 URDF 文件的顶部,您必须指定一个命名空间,以便文件能够正确解析。 例如,以下是有效 xacro 文件的前两行:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="firefighter">

常量

让我们快速看一下 R2D2 中的 base_link。

<link name="base_link">
  <visual>
    <geometry>
      <cylinder length="0.6" radius="0.2"/>
    </geometry>
    <material name="blue"/>
  </visual>
  <collision>
    <geometry>
      <cylinder length="0.6" radius="0.2"/>
    </geometry>
  </collision>
</link>

这里的信息有点多余。 我们指定了两次圆柱体的长度和半径。 更糟糕的是,如果我们想改变它,我们需要在两个不同的地方这样做。

幸运的是,xacro 允许您指定充当常量的属性。 除了上面的代码,我们可以写这个。

<xacro:property name="width" value="0.2" />
<xacro:property name="bodylen" value="0.6" />
<link name="base_link">
    <visual>
        <geometry>
            <cylinder radius="${width}" length="${bodylen}"/>
        </geometry>
        <material name="blue"/>
    </visual>
    <collision>
        <geometry>
            <cylinder radius="${width}" length="${bodylen}"/>
        </geometry>
    </collision>
</link>
  • 这两个值在前两行中指定。

它们可以在任何地方(假设有效的 XML)、任何级别、使用之前或之后进行定义。 通常它们位于顶部。 * 我们使用美元符号和花括号来表示值,而不是在几何元素中指定实际半径。 * 此代码将生成与上面显示的相同的代码。

然后使用 ${} 构造的内容的值来替换 ${}。 这意味着您可以将其与属性中的其他文本组合。

<xacro:property name=”robotname” value=”marvin” />
<link name=”${robotname}s_leg” />

这将产生

<link name=”marvins_leg” />

但是,${} 中的内容不一定只能是属性,这将我们引向下一个要点…

数学

您可以使用四种基本运算(+、-、*、/)、一元减法和括号在 ${} 结构中构建任意复杂的表达式。 示例:

<cylinder radius="${wheeldiam/2}" length="0.1"/>
<origin xyz="${reflect*(width+.02)} 0 0.25" />

您还可以使用除基本数学运算之外的其他运算,例如“sin”和“cos”。

这是 xacro 包中最大、最有用的组件。

简单宏

让我们看一个简单而无用的宏。

<xacro:macro name="default_origin">
    <origin xyz="0 0 0" rpy="0 0 0"/>
</xacro:macro>
<xacro:default_origin />

(这没用,因为如果没有指定来源,它具有与此相同的值。)此代码将生成以下内容。

<origin rpy="0 0 0" xyz="0 0 0"/>
  • 从技术上讲,名称不是必需元素,但您需要指定它才能使用它。

  • <xacro:$NAME /> 的每个实例都将被 xacro:macro 标记的内容替换。

  • 请注意,即使不完全相同(两个属性已切换顺序),生成的 XML 也是等效的。

  • 如果未找到具有指定名称的 xacro,则不会展开它,也不会生成错误。

参数化宏

您还可以参数化宏,以便它们不会每次都生成完全相同的文本。 当与数学功能结合使用时,它的功能更加强大。

首先,让我们以 R2D2 中使用的简单宏为例。

<xacro:macro name="default_inertial" params="mass">
    <inertial>
            <mass value="${mass}" />
            <inertia ixx="1e-3" ixy="0.0" ixz="0.0"
                 iyy="1e-3" iyz="0.0"
                 izz="1e-3" />
    </inertial>
</xacro:macro>

这可以与代码一起使用

<xacro:default_inertial mass="10"/>

参数的作用就像属性一样,您可以在表达式中使用它们

您也可以将整个块用作参数。

<xacro:macro name="blue_shape" params="name *shape">
    <link name="${name}">
        <visual>
            <geometry>
                <xacro:insert_block name="shape" />
            </geometry>
            <material name="blue"/>
        </visual>
        <collision>
            <geometry>
                <xacro:insert_block name="shape" />
            </geometry>
        </collision>
    </link>
</xacro:macro>

<xacro:blue_shape name="base_link">
    <cylinder radius=".42" length=".01" />
</xacro:blue_shape>
  • 要指定块参数,请在其参数名称前添加星号。

  • 可以使用 insert_block 命令插入块

  • 根据需要多次插入块。

实际用法

xacro 语言在允许您执行的操作方面相当灵活。 除了上面显示的默认惯性宏之外,以下是在 R2D2 模型 中使用 xacro 的几种有用方法。

要查看 xacro 文件生成的模型,请运行与以前的教程相同的命令:

ros2 launch urdf_tutorial display.launch.py model:=urdf/08-macroed.urdf.xacro

(启动文件一直在运行 xacro 命令,但由于没有宏可以扩展,所以这并不重要)

腿部宏

通常,您希望在不同位置创建多个外观相似的对象。 您可以使用宏和一些简单的数学运算来减少必须编写的代码量,就像我们对 R2 的两条腿所做的那样。

<xacro:macro name="leg" params="prefix reflect">
    <link name="${prefix}_leg">
        <visual>
            <geometry>
                <box size="${leglen} 0.1 0.2"/>
            </geometry>
            <origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
            <material name="white"/>
        </visual>
        <collision>
            <geometry>
                <box size="${leglen} 0.1 0.2"/>
            </geometry>
            <origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
        </collision>
        <xacro:default_inertial mass="10"/>
    </link>

    <joint name="base_to_${prefix}_leg" type="fixed">
        <parent link="base_link"/>
        <child link="${prefix}_leg"/>
        <origin xyz="0 ${reflect*(width+.02)} 0.25" />
    </joint>
    <!-- A bunch of stuff cut -->
</xacro:macro>
<xacro:leg prefix="right" reflect="1" />
<xacro:leg prefix="left" reflect="-1" />
  • 常用技巧 1:使用名称前缀来获取两个名称相似的对象。

  • 常用技巧 2:使用数学计算关节原点。

如果您更改机器人的大小,使用一些数学运算更改属性来计算关节偏移将省去很多麻烦。 * 常用技巧 3:使用反射参数,并将其设置为 1 或 -1。 看看我们如何使用反射参数将腿放在 base_to_${prefix}_leg 原点的身体两侧。