目标:学习一些使用 Xacro 减少 URDF 文件代码量的技巧
教程级别:中级
时间:20 分钟
目录
使用 Xacro
常量
数学
宏
简单宏
参数化宏
实际使用
腿宏
到目前为止,如果你在家里按照这些步骤设计自己的机器人,你可能已经厌倦了做各种数学运算来正确解析非常简单的机器人描述。幸运的是,你可以使用 xacro 包来简化你的生活。它做了三件非常有用的事情。
常量
简单数学
宏
在本教程中,我们将看看所有这些快捷方式,以帮助减少 URDF 文件的整体大小,并使其更易于阅读和维护。
使用 Xacro
顾名思义,xacro https://index.ros.org/p/xacro/ 是一种用于 XML 的宏语言。xacro 程序运行所有宏并输出结果。典型用法如下所示:
xacro model.xacro > model.urdf
您还可以在启动文件中自动生成 urdf。这很方便,因为它保持最新状态并且不会占用硬盘空间。然而,生成它需要时间,因此请注意,您的启动文件可能需要更长时间才能启动。
要在启动文件中运行 xacro,您需要将 Command
替换作为 robot_state_publisher
的参数。
# 获取'turtlebot3_description'包的共享路径,并拼接成URDF文件的路径
path_to_urdf = get_package_share_path('turtlebot3_description') / 'urdf' / 'turtlebot3_burger.urdf'
# 创建一个ROS节点,用于发布机器人状态
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 # 使用xacro命令处理URDF文件
)
}]
)
一种更简单的加载机器人模型的方法是使用 urdf_launch 包自动加载 xacro/urdf。
from launch import LaunchDescription # 从launch模块导入LaunchDescription类
from launch.actions import IncludeLaunchDescription # 从launch.actions模块导入IncludeLaunchDescription类
from launch.substitutions import PathJoinSubstitution # 从launch.substitutions模块导入PathJoinSubstitution类
from launch_ros.substitutions import FindPackageShare # 从launch_ros.substitutions模块导入FindPackageShare类
def generate_launch_description():
ld = LaunchDescription() # 创建一个LaunchDescription对象
# 添加一个IncludeLaunchDescription动作,用于包含另一个launch文件
ld.add_action(IncludeLaunchDescription(
PathJoinSubstitution([FindPackageShare('urdf_launch'), 'launch', 'display.launch.py']), # 指定要包含的launch文件的路径
launch_arguments={ # 指定传递给launch文件的参数
'urdf_package': 'turtlebot3_description', # urdf包的名称
'urdf_package_path': PathJoinSubstitution(['urdf', 'turtlebot3_burger.urdf']) # urdf文件的路径
}.items() # 将参数字典转换为items对象
))
return ld # 返回LaunchDescription对象
在 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参数决定 -->
<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">
<!-- 定义一个名为blue_shape的宏,带有name和shape参数 -->
<link name="${name}"> <!-- 定义一个link元素,名称由name参数决定 -->
<visual> <!-- 定义视觉元素 -->
<geometry> <!-- 定义几何形状 -->
<xacro:insert_block name="shape" /> <!-- 插入由shape参数指定的几何形状 -->
</geometry>
<material name="blue"/> <!-- 定义材质为蓝色 -->
</visual>
<collision> <!-- 定义碰撞元素 -->
<geometry> <!-- 定义几何形状 -->
<xacro:insert_block name="shape" /> <!-- 插入由shape参数指定的几何形状 -->
</geometry>
</collision>
</link>
</xacro:macro>
<xacro:blue_shape name="base_link"> <!-- 使用blue_shape宏定义一个名为base_link的link -->
<cylinder radius=".42" length=".01" /> <!-- 定义一个圆柱形状,半径为0.42,长度为0.01 -->
</xacro:blue_shape>
要指定块参数,请在其参数名称前包含星号。
可以使用 insert_block 命令插入一个块
请随意插入该块。
实际使用
xacro 语言在允许您执行操作方面相当灵活。以下是 xacro 在 R2D2 模型中使用的一些有用方法,除了上面显示的默认惯性宏之外。
要查看由 xacro 文件 https://github.com/ros/urdf_tutorial/blob/ros2/urdf/08-macroed.urdf.xacro 生成的模型,请运行与之前教程相同的命令:
ros2 launch urdf_tutorial display.launch.py model:=urdf/08-macroed.urdf.xacro
<?xml version="1.0"?>
<!-- 定义名为"macroed"的机器人模型 -->
<robot name="macroed" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- 定义一些属性 -->
<xacro:property name="width" value="0.2" /> <!-- 宽度 -->
<xacro:property name="leglen" value="0.6" /> <!-- 腿长 -->
<xacro:property name="polelen" value="0.2" /> <!-- 杆长 -->
<xacro:property name="bodylen" value="0.6" /> <!-- 身体长度 -->
<xacro:property name="baselen" value="0.4" /> <!-- 底座长度 -->
<xacro:property name="wheeldiam" value="0.07" /> <!-- 轮子直径 -->
<!-- 定义一些材料 -->
<material name="blue"> <!-- 蓝色材料 -->
<color rgba="0 0 0.8 1"/>
</material>
<material name="black"> <!-- 黑色材料 -->
<color rgba="0 0 0 1"/>
</material>
<material name="white"> <!-- 白色材料 -->
<color rgba="1 1 1 1"/>
</material>
<!-- 定义一个宏,用于创建默认的惯性 -->
<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>
<!-- 定义基础链接 -->
<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>
<xacro:default_inertial mass="10"/> <!-- 使用默认的惯性,质量为10 -->
</link>
<!-- 定义一个宏,用于创建轮子 -->
<xacro:macro name="wheel" params="prefix suffix reflect">
<link name="${prefix}_${suffix}_wheel">
<visual>
<origin xyz="0 0 0" rpy="${pi/2} 0 0" />
<geometry>
<cylinder radius="${wheeldiam/2}" length="0.1"/> <!-- 几何形状为圆柱体 -->
</geometry>
<material name="black"/> <!-- 使用黑色材料 -->
</visual>
<collision>
<origin xyz="0 0 0" rpy="${pi/2} 0 0" />
<geometry>
<cylinder radius="${wheeldiam/2}" length="0.1"/> <!-- 碰撞检测的几何形状也为圆柱体 -->
</geometry>
</collision>
<xacro:default_inertial mass="1"/> <!-- 使用默认的惯性,质量为1 -->
</link>
<joint name="${prefix}_${suffix}_wheel_joint" type="continuous">
<axis xyz="0 1 0" rpy="0 0 0" />
<parent link="${prefix}_base"/>
<child link="${prefix}_${suffix}_wheel"/>
<origin xyz="${baselen*reflect/3} 0 -${wheeldiam/2+.05}" rpy="0 0 0"/>
</joint>
</xacro:macro>
<!-- 定义一个宏,用于创建腿 -->
<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"/> <!-- 使用默认的惯性,质量为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>
<link name="${prefix}_base">
<visual>
<geometry>
<box size="${baselen} 0.1 0.1"/> <!-- 几何形状为长方体 -->
</geometry>
<material name="white"/> <!-- 使用白色材料 -->
</visual>
<collision>
<geometry>
<box size="${baselen} 0.1 0.1"/> <!-- 碰撞检测的几何形状也为长方体 -->
</geometry>
</collision>
<xacro:default_inertial mass="10"/> <!-- 使用默认的惯性,质量为10 -->
</link>
<joint name="${prefix}_base_joint" type="fixed">
<parent link="${prefix}_leg"/>
<child link="${prefix}_base"/>
<origin xyz="0 0 ${-leglen}" />
</joint>
<xacro:wheel prefix="${prefix}" suffix="front" reflect="1"/> <!-- 创建前轮 -->
<xacro:wheel prefix="${prefix}" suffix="back" reflect="-1"/> <!-- 创建后轮 -->
</xacro:macro>
<xacro:leg prefix="right" reflect="-1" /> <!-- 创建右腿 -->
<xacro:leg prefix="left" reflect="1" /> <!-- 创建左腿 -->
<joint name="gripper_extension" type="prismatic">
<parent link="base_link"/>
<child link="gripper_pole"/>
<limit effort="1000.0" lower="-${width*2-.02}" upper="0" velocity="0.5"/>
<origin rpy="0 0 0" xyz="${width-.01} 0 0.2"/>
</joint>
<link name="gripper_pole">
<visual>
<geometry>
<cylinder length="${polelen}" radius="0.01"/> <!-- 几何形状为圆柱体 -->
</geometry>
<origin xyz="${polelen/2} 0 0" rpy="0 ${pi/2} 0 "/>
</visual>
<collision>
<geometry>
<cylinder length="${polelen}" radius="0.01"/> <!-- 碰撞检测的几何形状也为圆柱体 -->
</geometry>
<origin xyz="${polelen/2} 0 0" rpy="0 ${pi/2} 0 "/>
</collision>
<xacro:default_inertial mass="0.05"/> <!-- 使用默认的惯性,质量为0.05 -->
</link>
<!-- 定义一个宏,用于创建抓手 -->
<xacro:macro name="gripper" params="prefix reflect">
<joint name="${prefix}_gripper_joint" type="revolute">
<axis xyz="0 0 ${reflect}"/>
<limit effort="1000.0" lower="0.0" upper="0.548" velocity="0.5"/>
<origin rpy="0 0 0" xyz="${polelen} ${reflect*0.01} 0"/>
<parent link="gripper_pole"/>
<child link="${prefix}_gripper"/>
</joint>
<link name="${prefix}_gripper">
<visual>
<origin rpy="${(reflect-1)/2*pi} 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
<!-- 使用3D模型文件作为几何形状 -->
</geometry>
</visual>
<collision>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/> <!-- 碰撞检测的几何形状也使用3D模型文件 -->
</geometry>
<origin rpy="${(reflect-1)/2*pi} 0 0" xyz="0 0 0"/>
</collision>
<xacro:default_inertial mass="0.05"/> <!-- 使用默认的惯性,质量为0.05 -->
</link>
<joint name="${prefix}_tip_joint" type="fixed">
<parent link="${prefix}_gripper"/>
<child link="${prefix}_tip"/>
</joint>
<link name="${prefix}_tip">
<visual>
<origin rpy="${(reflect-1)/2*pi} 0 0" xyz="0.09137 0.00495 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger_tip.dae"/> <!-- 使用3D模型文件作为几何形状 -->
</geometry>
</visual>
<collision>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger_tip.dae"/> <!-- 碰撞检测的几何形状也使用3D模型文件 -->
</geometry>
<origin rpy="${(reflect-1)/2*pi} 0 0" xyz="0.09137 0.00495 0"/>
</collision>
<xacro:default_inertial mass="0.05"/> <!-- 使用默认的惯性,质量为0.05 -->
</link>
</xacro:macro>
<xacro:gripper prefix="left" reflect="1" /> <!-- 创建左抓手 -->
<xacro:gripper prefix="right" reflect="-1" /> <!-- 创建右抓手 -->
<link name="head">
<visual>
<geometry>
<sphere radius="${width}"/> <!-- 几何形状为球体 -->
</geometry>
<material name="white"/> <!-- 使用白色材料 -->
</visual>
<collision>
<geometry>
<sphere radius="${width}"/> <!-- 碰撞检测的几何形状也为球体 -->
</geometry>
</collision>
<xacro:default_inertial mass="2"/> <!-- 使用默认的惯性,质量为2 -->
</link>
<joint name="head_swivel" type="continuous">
<parent link="base_link"/>
<child link="head"/>
<axis xyz="0 0 1"/>
<origin xyz="0 0 ${bodylen/2}"/>
</joint>
<link name="box">
<visual>
<geometry>
<box size="0.08 0.08 0.08"/> <!-- 几何形状为长方体 -->
</geometry>
<material name="blue"/> <!-- 使用蓝色材料 -->
<origin xyz="-0.04 0 0"/>
</visual>
<collision>
<geometry>
<box size="0.08 0.08 0.08"/> <!-- 碰撞检测的几何形状也为长方体 -->
</geometry>
</collision>
<xacro:default_inertial mass="1"/> <!-- 使用默认的惯性,质量为1 -->
</link>
<joint name="tobox" type="fixed">
<parent link="head"/>
<child link="box"/>
<origin xyz="${.707*width+0.04} 0 ${.707*width}"/>
</joint>
</robot>
启动文件一直在运行 xacro 命令,但由于没有宏可以展开,所以无关紧要
腿宏/轮宏
通常情况下,您会希望在不同位置创建多个外观相似的对象。您可以使用宏和一些简单的数学运算来减少需要编写的代码量,就像我们对 R2 的两条腿所做的那样。
<xacro:macro name="leg" params="prefix reflect">
<!-- 定义腿部链接 -->
<link name="${prefix}_leg">
<visual>
<!-- 定义视觉元素 -->
<geometry>
<!-- 定义几何形状为长方体,尺寸为leglen, 0.1, 0.2 -->
<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>
<!-- 定义几何形状为长方体,尺寸为leglen, 0.1, 0.2 -->
<box size="${leglen} 0.1 0.2"/>
</geometry>
<!-- 定义碰撞元素的原点和旋转 -->
<origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
</collision>
<!-- 使用default_inertial宏定义惯性参数,质量为10 -->
<xacro:default_inertial mass="10"/>
</link>
<!-- 定义腿部关节 -->
<joint name="base_to_${prefix}_leg" type="fixed">
<!-- 父链接为base_link -->
<parent link="base_link"/>
<!-- 子链接为prefix_leg -->
<child link="${prefix}_leg"/>
<!-- 定义关节的原点 -->
<origin xyz="0 ${reflect*(width+.02)} 0.25" />
</joint>
<!-- 定义腿部基础链接 -->
<link name="${prefix}_base">
<visual>
<!-- 定义视觉元素 -->
<geometry>
<!-- 定义几何形状为长方体,尺寸为baselen, 0.1, 0.1 -->
<box size="${baselen} 0.1 0.1"/>
</geometry>
<!-- 定义材质为白色 -->
<material name="white"/>
</visual>
<collision>
<!-- 定义碰撞元素 -->
<geometry>
<!-- 定义几何形状为长方体,尺寸为baselen, 0.1, 0.1 -->
<box size="${baselen} 0.1 0.1"/>
</geometry>
</collision>
<!-- 使用default_inertial宏定义惯性参数,质量为10 -->
<xacro:default_inertial mass="10"/>
</link>
<!-- 定义基础关节 -->
<joint name="${prefix}_base_joint" type="fixed">
<!-- 父链接为prefix_leg -->
<parent link="${prefix}_leg"/>
<!-- 子链接为prefix_base -->
<child link="${prefix}_base"/>
<!-- 定义关节的原点 -->
<origin xyz="0 0 ${-leglen}" />
</joint>
<!-- 使用wheel宏定义前轮 -->
<xacro:wheel prefix="${prefix}" suffix="front" reflect="1"/>
<!-- 使用wheel宏定义后轮 -->
<xacro:wheel prefix="${prefix}" suffix="back" reflect="-1"/>
</xacro:macro>
<!-- 使用leg宏定义右腿,反射参数为1 -->
<xacro:leg prefix="right" reflect="1" />
<!-- 使用leg宏定义左腿,反射参数为-1 -->
<xacro:leg prefix="left" reflect="-1" />
常见技巧 1:使用名称前缀来获取两个名称相似的对象。
常用技巧 2:使用数学计算关节原点。如果你改变了机器人的大小,使用一些数学计算关节偏移量的属性会省去很多麻烦。
常见技巧 3:使用反射参数,并将其设置为 1 或-1。看看我们如何使用反射参数将腿放在 base_to_${prefix}_leg 原点的身体两侧。
在URDF中,关节(joint)是连接父连杆(parent link)和子连杆(child link)的。关节的原点(origin)和姿态(pose)是相对于父连杆定义的。因此,关节是跟父连杆固连的,而子连杆则通过关节与父连杆连接。