在 MoveIt 中,运动规划器是使用插件基础设施 plugin infrastructure 加载的。这允许 MoveIt 在运行时加载运动规划器。在这个例子中,我们将运行所需的 C++代码来实现这一点。
入门
如果您还没有这样做,请确保您已完成入门中的步骤。
运行演示
打开两个 shell。在第一个 shell 中启动 RViz 并等待所有内容加载完成:
ros2 launch moveit2_tutorials move_group.launch.py
在第二个 shell 中,运行启动文件:
ros2 launch moveit2_tutorials motion_planning_api_tutorial.launch.py
注意:本教程使用 RvizVisualToolsGui 面板逐步演示。要将此面板添加到 RViz,请按照可视化教程中的说明进行操作。
片刻之后,RViz 窗口应该会出现,并且看起来与此页面顶部的窗口类似。要通过每个演示步骤,请按屏幕底部的 RvizVisualToolsGui 面板中的 Next 按钮,或在屏幕顶部的 Tools 面板中选择 Key Tool,然后在 RViz 聚焦时按键盘上的 N 键。
预期输出
在 RViz 中,我们最终应该能够看到四条轨迹被重放:
机器人将其手臂移动到第一个姿势目标,
机器人将其手臂移动到关节目标,
机器人将其手臂移回到原始姿势目标,
机器人在保持末端执行器水平的同时,将其手臂移动到一个新的姿态目标。
整个代码
整个代码可以在 moveit_tutorials GitHub 项目中看到。https://github.com/moveit/moveit2_tutorials/blob/main/doc/examples/motion_planning_api
这段代码展示了如何使用MoveIt和ROS 2进行运动规划。以下是代码的主要步骤和功能:
初始化和节点创建:
初始化ROS 2。
创建一个名为
motion_planning_api_tutorial
的节点,并使用单线程执行器运行该节点。
加载机器人模型和规划场景:
使用
RobotModelLoader
从ROS参数服务器加载机器人描述,并创建RobotModel
对象。创建
RobotState
和JointModelGroup
对象以跟踪当前的机器人姿态和规划组。使用
RobotModel
创建PlanningScene
对象,该对象维护世界的状态(包括机器人)。
加载规划器插件:
使用ROS的
pluginlib
库加载规划器插件。从ROS参数服务器获取规划插件的名称,并实例化规划器。
MoveGroupInterface和可视化工具:
创建
MoveGroupInterface
对象以与规划组进行交互。使用
MoveItVisualTools
进行可视化,包括在RViz中显示文本、轴和轨迹。
姿态目标规划:
创建一个运动规划请求,指定末端执行器的目标姿态。
设置位置和方向的容差。
调用规划器并解决规划问题。
可视化规划结果并在RViz中显示轨迹。
关节空间目标规划:
设置关节空间目标,并创建相应的运动规划请求。
调用规划器并解决规划问题。
可视化规划结果并在RViz中显示轨迹。
路径约束规划:
创建一个新的姿态目标,并添加路径约束(例如,保持末端执行器水平)。
设置规划空间的边界。
调用规划器并解决规划问题。
可视化规划结果并在RViz中显示轨迹。
结束和清理:
等待用户输入以继续演示或退出。
关闭ROS 2节点。
这段代码展示了如何使用MoveIt和ROS 2进行复杂的运动规划和可视化,包括姿态目标、关节空间目标和路径约束。
#include <pluginlib/class_loader.hpp>
// 插件库加载器
// MoveIt
#include <moveit/robot_model_loader/robot_model_loader.h>
// 机器人模型加载器
#include <moveit/planning_interface/planning_interface.h>
// 规划接口
#include <moveit/planning_scene/planning_scene.h>
// 规划场景
#include <moveit/kinematic_constraints/utils.h>
// 运动学约束工具
#include <moveit_msgs/msg/display_trajectory.hpp>
// 显示轨迹消息
#include <moveit_msgs/msg/planning_scene.h>
// 规划场景消息
#include <moveit_visual_tools/moveit_visual_tools.h>
// MoveIt可视化工具
#include <moveit/move_group_interface/move_group_interface.h>
// MoveIt移动组接口
static const rclcpp::Logger LOGGER = rclcpp::get_logger("motion_planning_api_tutorial");
// 定义日志记录器
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
// 初始化ROS
rclcpp::NodeOptions node_options;
// 定义节点选项
node_options.automatically_declare_parameters_from_overrides(true);
// 自动声明参数
std::shared_ptr<rclcpp::Node> motion_planning_api_tutorial_node =
rclcpp::Node::make_shared("motion_planning_api_tutorial", node_options);
// 创建节点
rclcpp::executors::SingleThreadedExecutor executor;
// 单线程执行器
executor.add_node(motion_planning_api_tutorial_node);
// 添加节点到执行器
std::thread([&executor]() { executor.spin(); }).detach();
// 启动线程执行
// BEGIN_TUTORIAL
// 开始教程
// ^^^^^
// 开始使用规划器的设置非常简单。规划器作为插件在MoveIt中设置,
// 你可以使用ROS的插件库接口加载任何你想使用的规划器。
// 在我们加载规划器之前,我们需要两个对象,一个是RobotModel,另一个是PlanningScene。
// 我们首先实例化一个RobotModelLoader对象,该对象会在ROS参数服务器上查找机器人描述,
// 并为我们构建一个RobotModel以供使用。
const std::string PLANNING_GROUP = "panda_arm";
// 规划组名称
robot_model_loader::RobotModelLoader robot_model_loader(motion_planning_api_tutorial_node, "robot_description");
// 加载机器人模型
const moveit::core::RobotModelPtr& robot_model = robot_model_loader.getModel();
// 获取机器人模型
/* 创建一个RobotState和JointModelGroup来跟踪当前的机器人姿态和规划组 */
moveit::core::RobotStatePtr robot_state(new moveit::core::RobotState(robot_model));
// 创建机器人状态
const moveit::core::JointModelGroup* joint_model_group = robot_state->getJointModelGroup(PLANNING_GROUP);
// 获取关节模型组
// 使用RobotModel,我们可以构建一个PlanningScene,它维护世界的状态(包括机器人)。
planning_scene::PlanningScenePtr planning_scene(new planning_scene::PlanningScene(robot_model));
// 创建规划场景
// 配置一个有效的机器人状态
planning_scene->getCurrentStateNonConst().setToDefaultValues(joint_model_group, "ready");
// 设置默认机器人状态
// 现在我们将构建一个加载器来按名称加载一个规划器。
// 请注意,我们在这里使用的是ROS插件库。
std::unique_ptr<pluginlib::ClassLoader<planning_interface::PlannerManager>> planner_plugin_loader;
// 定义规划器插件加载器
planning_interface::PlannerManagerPtr planner_instance;
// 定义规划器实例
std::vector<std::string> planner_plugin_names;
// 定义规划器插件名称列表
// 我们将从ROS参数服务器获取我们想要加载的规划插件的名称,然后加载规划器,并确保捕获所有异常。
if (!motion_planning_api_tutorial_node->get_parameter("ompl.planning_plugins", planner_plugin_names))
RCLCPP_FATAL(LOGGER, "无法找到规划器插件名称");
// 获取规划器插件名称,若获取失败则打印错误信息
try
{
planner_plugin_loader.reset(new pluginlib::ClassLoader<planning_interface::PlannerManager>(
"moveit_core", "planning_interface::PlannerManager"));
// 尝试创建规划器插件加载器
}
catch (pluginlib::PluginlibException& ex)
{
RCLCPP_FATAL(LOGGER, "创建规划器插件加载器时出现异常 %s", ex.what());
// 若创建失败则打印异常信息
}
if (planner_plugin_names.empty())
{
RCLCPP_ERROR(LOGGER, "未定义任何规划器插件。请确保planning_plugins参数不为空。");
// 若未定义任何规划器插件则打印错误信息
return -1;
}
const auto& planner_name = planner_plugin_names.at(0);
// 获取第一个规划器插件名称
try
{
planner_instance.reset(planner_plugin_loader->createUnmanagedInstance(planner_name));
// 尝试创建规划器实例
if (!planner_instance->initialize(robot_model, motion_planning_api_tutorial_node,
motion_planning_api_tutorial_node->get_namespace()))
RCLCPP_FATAL(LOGGER, "无法初始化规划器实例");
// 若初始化失败则打印错误信息
RCLCPP_INFO(LOGGER, "使用规划接口 '%s'", planner_instance->getDescription().c_str());
// 打印使用的规划接口信息
}
catch (pluginlib::PluginlibException& ex)
{
const std::vector<std::string>& classes = planner_plugin_loader->getDeclaredClasses();
std::stringstream ss;
for (const auto& cls : classes)
ss << cls << " ";
RCLCPP_ERROR(LOGGER, "加载规划器 '%s' 时出现异常: %s\n可用插件: %s", planner_name.c_str(),
ex.what(), ss.str().c_str());
// 若加载规划器失败则打印异常信息以及可用插件列表
}
moveit::planning_interface::MoveGroupInterface move_group(motion_planning_api_tutorial_node, PLANNING_GROUP);
// 初始化移动组接口
// 可视化
// ^^^^^^^^^^^^^
// MoveItVisualTools包提供了许多功能,用于在RViz中可视化物体、机器人和轨迹,
// 以及调试工具,如逐步检查脚本。
namespace rvt = rviz_visual_tools;
moveit_visual_tools::MoveItVisualTools visual_tools(motion_planning_api_tutorial_node, "panda_link0",
"move_group_tutorial", move_group.getRobotModel());
// 初始化MoveIt可视化工具
visual_tools.enableBatchPublishing();
// 启用批量发布
visual_tools.deleteAllMarkers(); // 清除所有旧的标记
visual_tools.trigger();
// 触发批量发布
/* 远程控制是一个检查工具,允许用户通过RViz中的按钮和键盘快捷键逐步执行高级脚本 */
visual_tools.loadRemoteControl();
// 加载远程控制
/* RViz提供了多种类型的标记,在本演示中我们将使用文本、圆柱体和球体 */
Eigen::Isometry3d text_pose = Eigen::Isometry3d::Identity();
text_pose.translation().z() = 1.75;
visual_tools.publishText(text_pose, "Motion Planning API Demo", rvt::WHITE, rvt::XLARGE);
// 发布文本标记
/* 批量发布用于减少发送到RViz的大型可视化的消息数量 */
visual_tools.trigger();
/* 我们还可以使用visual_tools等待用户输入 */
visual_tools.prompt("在RvizVisualToolsGui窗口中按'next'键以开始演示");
// 姿态目标
// ^^^^^^^^^
// 我们现在为Panda机器人的手臂创建一个运动计划请求,
// 指定末端执行器的期望姿态作为输入。
visual_tools.trigger();
planning_interface::MotionPlanRequest req;
// 定义运动计划请求
planning_interface::MotionPlanResponse res;
// 定义运动计划响应
geometry_msgs::msg::PoseStamped pose;
// 定义姿态消息
pose.header.frame_id = "panda_link0";
pose.pose.position.x = 0.3;
pose.pose.position.y = 0.4;
pose.pose.position.z = 0.75;
pose.pose.orientation.w = 1.0;
// 设置末端执行器的期望姿态
// 指定0.01米的位置容差和0.01弧度的角度容差
std::vector<double> tolerance_pose(3, 0.01);
// 设置姿态容差
std::vector<double> tolerance_angle(3, 0.01);
// 设置角度容差
// 我们将使用moveit_core/kinematic_constraints中的辅助函数构建约束请求。
moveit_msgs::msg::Constraints pose_goal =
kinematic_constraints::constructGoalConstraints("panda_link8", pose, tolerance_pose, tolerance_angle);
// 构建姿态目标
req.group_name = PLANNING_GROUP;
req.goal_constraints.push_back(pose_goal);
// 定义工作空间边界
req.workspace_parameters.min_corner.x = req.workspace_parameters.min_corner.y =
req.workspace_parameters.min_corner.z = -5.0;
req.workspace_parameters.max_corner.x = req.workspace_parameters.max_corner.y =
req.workspace_parameters.max_corner.z = 5.0;
// 我们现在构造一个规划上下文,封装场景、请求和响应。
// 我们使用这个规划上下文调用规划器
planning_interface::PlanningContextPtr context =
planner_instance->getPlanningContext(planning_scene, req, res.error_code);
if (!context)
{
RCLCPP_ERROR(LOGGER, "创建规划上下文失败");
return -1;
}
context->solve(res);
if (res.error_code.val != res.error_code.SUCCESS)
{
RCLCPP_ERROR(LOGGER, "无法成功计算规划");
return -1;
}
// 可视化结果
// ^^^^^^^^^^^^^^^^^^^^
std::shared_ptr<rclcpp::Publisher<moveit_msgs::msg::DisplayTrajectory>> display_publisher =
motion_planning_api_tutorial_node->create_publisher<moveit_msgs::msg::DisplayTrajectory>("/display_planned_path",
1);
moveit_msgs::msg::DisplayTrajectory display_trajectory;
/* 可视化轨迹 */
moveit_msgs::msg::MotionPlanResponse response;
res.getMessage(response);
display_trajectory.trajectory_start = response.trajectory_start;
display_trajectory.trajectory.push_back(response.trajectory);
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group);
visual_tools.trigger();
display_publisher->publish(display_trajectory);
/* 将规划场景中的状态设置为上一个规划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions);
planning_scene->setCurrentState(*robot_state.get());
// 显示目标状态
visual_tools.publishAxisLabeled(pose.pose, "goal_1");
visual_tools.publishText(text_pose, "Pose Goal (1)", rvt::WHITE, rvt::XLARGE);
visual_tools.trigger();
/* 我们还可以使用visual_tools等待用户输入 */
visual_tools.prompt("请在RvizVisualToolsGui窗口中按'下一步'以继续演示");
// 关节空间目标
// ^^^^^^^^^^^^^^^^^
// 现在,设置一个关节空间目标
moveit::core::RobotState goal_state(robot_model);
std::vector<double> joint_values = { -1.0, 0.7, 0.7, -1.5, -0.7, 2.0, 0.0 };
goal_state.setJointGroupPositions(joint_model_group, joint_values);
moveit_msgs::msg::Constraints joint_goal =
kinematic_constraints::constructGoalConstraints(goal_state, joint_model_group);
req.goal_constraints.clear();
req.goal_constraints.push_back(joint_goal);
// 调用规划器并可视化轨迹
/* 重新构建规划上下文 */
context = planner_instance->getPlanningContext(planning_scene, req, res.error_code);
/* 调用规划器 */
context->solve(res);
/* 检查规划是否成功 */
if (res.error_code.val != res.error_code.SUCCESS)
{
RCLCPP_ERROR(LOGGER, "无法成功计算规划");
return -1;
}
/* 可视化轨迹 */
res.getMessage(response);
display_trajectory.trajectory.push_back(response.trajectory);
/* 现在你应该看到两个依次规划的轨迹 */
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group);
visual_tools.trigger();
display_publisher->publish(display_trajectory);
/* 我们将添加更多目标。但首先,将规划场景中的状态设置为上一个规划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions);
planning_scene->setCurrentState(*robot_state.get());
// 显示目标状态
visual_tools.publishAxisLabeled(pose.pose, "goal_2");
visual_tools.publishText(text_pose, "Joint Space Goal (2)", rvt::WHITE, rvt::XLARGE);
visual_tools.trigger();
/* 等待用户输入 */
visual_tools.prompt("请在RvizVisualToolsGui窗口中按'下一步'以继续演示");
/* 现在,我们回到第一个目标,以准备进行受方向约束的规划 */
req.goal_constraints.clear();
req.goal_constraints.push_back(pose_goal);
context = planner_instance->getPlanningContext(planning_scene, req, res.error_code);
context->solve(res);
res.getMessage(response);
display_trajectory.trajectory.push_back(response.trajectory);
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group);
visual_tools.trigger();
display_publisher->publish(display_trajectory);
/* 将规划场景中的状态设置为上一个规划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions);
planning_scene->setCurrentState(*robot_state.get());
// 显示目标状态
visual_tools.trigger();
/* 等待用户输入 */
visual_tools.prompt("请在RvizVisualToolsGui窗口中按'下一步'以继续演示");
// 添加路径约束
// ^^^^^^^^^^^^^^^^^^^^^^^
// 让我们再次添加一个新的姿势目标。这次我们还会添加一个路径约束。
/* 让我们创建一个新的姿势目标 */
pose.pose.position.x = 0.32;
pose.pose.position.y = -0.25;
pose.pose.position.z = 0.65;
pose.pose.orientation.w = 1.0;
moveit_msgs::msg::Constraints pose_goal_2 =
kinematic_constraints::constructGoalConstraints("panda_link8", pose, tolerance_pose, tolerance_angle);
/* 现在,让我们尝试移动到这个新的姿势目标 */
req.goal_constraints.clear();
req.goal_constraints.push_back(pose_goal_2);
/* 但是,让我们对运动施加路径约束。
// 在这里,我们要求末端执行器保持水平 */
geometry_msgs::msg::QuaternionStamped quaternion;
quaternion.header.frame_id = "panda_link0";
req.path_constraints = kinematic_constraints::constructGoalConstraints("panda_link8", quaternion);
// 施加路径约束要求规划器在末端执行器的可能位置空间中进行推理
// (机器人的工作空间)
// 因此,我们需要为允许的规划体积指定一个边界;
// 注意:默认边界由WorkspaceBounds请求适配器自动填充(OMPL管道的一部分,
// 但在此示例中未使用)。
// 我们使用一个绝对包括手臂可达空间的边界。这样做没问题,因为在规划手臂时不会在该体积内进行采样;
// 边界仅用于确定采样配置是否有效
req.workspace_parameters.min_corner.x = req.workspace_parameters.min_corner.y =
req.workspace_parameters.min_corner.z = -2.0;
req.workspace_parameters.max_corner.x = req.workspace_parameters.max_corner.y =
req.workspace_parameters.max_corner.z = 2.0;
// 调用规划器并可视化迄今为止创建的所有规划。
context = planner_instance->getPlanningContext(planning_scene, req, res.error_code);
context->solve(res);
res.getMessage(response);
display_trajectory.trajectory.push_back(response.trajectory);
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group);
visual_tools.trigger();
display_publisher->publish(display_trajectory);
/* 将规划场景中的状态设置为上一个规划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions);
planning_scene->setCurrentState(*robot_state.get());
// 显示目标状态
visual_tools.publishAxisLabeled(pose.pose, "goal_3");
visual_tools.publishText(text_pose, "Orientation Constrained Motion Plan (3)", rvt::WHITE, rvt::XLARGE);
visual_tools.trigger();
// 结束教程
/* 等待用户输入 */
visual_tools.prompt("请在RvizVisualToolsGui窗口中按'下一步'以退出演示");
planner_instance.reset();
rclcpp::shutdown();
return 0;
}
开始
设置开始使用规划器非常容易。规划器在 MoveIt 中设置为插件,您可以使用 ROS pluginlib 接口加载任何您想使用的规划器。在我们加载规划器之前,我们需要两个对象,一个 RobotModel 和一个 PlanningScene。我们将首先实例化一个 RobotModelLoader 对象,它将在 ROS 参数服务器上查找机器人描述并为我们构建一个 RobotModel
const std::string PLANNING_GROUP = "panda_arm"; // 规划组
robot_model_loader::RobotModelLoader robot_model_loader(motion_planning_api_tutorial_node, "robot_description"); // 机器人模型加载器
const moveit::core::RobotModelPtr& robot_model = robot_model_loader.getModel(); // 获取机器人模型
/* 创建一个RobotState和JointModelGroup来跟踪当前的机器人姿态和规划组 */
moveit::core::RobotStatePtr robot_state(new moveit::core::RobotState(robot_model)); // 创建机器人状态
const moveit::core::JointModelGroup* joint_model_group = robot_state->getJointModelGroup(PLANNING_GROUP); // 获取关节模型组
使用 RobotModel,我们可以构建一个保持世界状态(包括机器人)的 PlanningScene。
planning_scene::PlanningScenePtr planning_scene(new planning_scene::PlanningScene(robot_model)); // 创建规划场景
配置有效的机器人状态
planning_scene->getCurrentStateNonConst().setToDefaultValues(joint_model_group, "ready"); // 设置默认值
我们现在将构建一个加载器来加载一个规划器,按名称。请注意,我们在这里使用的是 ROS pluginlib 库。
std::unique_ptr<pluginlib::ClassLoader<planning_interface::PlannerManager>> planner_plugin_loader; // 规划器插件加载器
planning_interface::PlannerManagerPtr planner_instance; // 规划器实例
std::vector<std::string> planner_plugin_names; // 规划器插件名称列表
我们将从 ROS 参数服务器获取要加载的规划插件的名称,然后加载规划器,确保捕获所有异常。
if (!motion_planning_api_tutorial_node->get_parameter("ompl.planning_plugins", planner_plugin_names))
RCLCPP_FATAL(LOGGER, "无法找到规划器插件名称"); // 获取规划器插件名称失败
try
{
planner_plugin_loader.reset(new pluginlib::ClassLoader<planning_interface::PlannerManager>(
"moveit_core", "planning_interface::PlannerManager")); // 重置规划器插件加载器
}
catch (pluginlib::PluginlibException& ex)
{
RCLCPP_FATAL(LOGGER, "创建规划插件加载器时发生异常 %s", ex.what()); // 创建规划插件加载器时发生异常
}
if (planner_plugin_names.empty())
{
RCLCPP_ERROR(LOGGER,
"未定义任何规划器插件。请确保planning_plugins参数不为空。"); // 未定义任何规划器插件
return -1;
}
const auto& planner_name = planner_plugin_names.at(0); // 获取第一个规划器插件名称
try
{
planner_instance.reset(planner_plugin_loader->createUnmanagedInstance(planner_name)); // 创建未管理的规划器实例
if (!planner_instance->initialize(robot_model, motion_planning_api_tutorial_node,
motion_planning_api_tutorial_node->get_namespace()))
RCLCPP_FATAL(LOGGER, "无法初始化规划器实例"); // 初始化规划器实例失败
RCLCPP_INFO(LOGGER, "使用规划接口 '%s'", planner_instance->getDescription().c_str()); // 使用规划接口
}
catch (pluginlib::PluginlibException& ex)
{
const std::vector<std::string>& classes = planner_plugin_loader->getDeclaredClasses(); // 获取声明的类
std::stringstream ss;
for (const auto& cls : classes)
ss << cls << " ";
RCLCPP_ERROR(LOGGER, "加载规划器 '%s' 时发生异常: %s\n可用插件: %s", planner_name.c_str(),
ex.what(), ss.str().c_str()); // 加载规划器时发生异常
}
moveit::planning_interface::MoveGroupInterface move_group(motion_planning_api_tutorial_node, PLANNING_GROUP); // Move组接口
可视化
该软件包 MoveItVisualTools 提供了许多在 RViz 中可视化对象、机器人和轨迹的功能,以及诸如脚本逐步内省等调试工具。
namespace rvt = rviz_visual_tools; // rviz可视化工具命名空间
moveit_visual_tools::MoveItVisualTools visual_tools(motion_planning_api_tutorial_node, "panda_link0",
"move_group_tutorial", move_group.getRobotModel()); // MoveIt可视化工具
visual_tools.enableBatchPublishing(); // 启用批量发布
visual_tools.deleteAllMarkers(); // 清除所有旧标记
visual_tools.trigger(); // 触发可视化工具
/* 远程控制是一个检查工具,允许用户通过RViz中的按钮和键盘快捷键逐步执行高级脚本 */
visual_tools.loadRemoteControl(); // 加载远程控制
/* RViz提供了多种类型的标记,在本演示中我们将使用文本、圆柱体和球体 */
Eigen::Isometry3d text_pose = Eigen::Isometry3d::Identity(); // 文本姿态
text_pose.translation().z() = 1.75; // 设置文本位置
visual_tools.publishText(text_pose, "运动规划API演示", rvt::WHITE, rvt::XLARGE); // 发布文本
/* 批量发布用于减少发送到RViz的大型可视化的消息数量 */
visual_tools.trigger(); // 触发可视化工具
/* 我们还可以使用visual_tools等待用户输入 */
visual_tools.prompt("在RvizVisualToolsGui窗口中按'next'开始演示"); // 提示用户输入
姿势目标
我们现在将为 Panda 的手臂创建一个运动规划请求,指定末端执行器的期望姿态作为输入。
visual_tools.trigger(); // 触发可视化工具
planning_interface::MotionPlanRequest req; // 运动计划请求
planning_interface::MotionPlanResponse res; // 运动计划响应
geometry_msgs::msg::PoseStamped pose; // 姿态消息
pose.header.frame_id = "panda_link0"; // 设置帧ID
pose.pose.position.x = 0.3; // 设置位置x
pose.pose.position.y = 0.4; // 设置位置y
pose.pose.position.z = 0.75; // 设置位置z
pose.pose.orientation.w = 1.0; // 设置方向w
位置公差为 0.01 米,方向公差为 0.01 弧度。
std::vector<double> tolerance_pose(3, 0.01); // 位置公差
std::vector<double> tolerance_angle(3, 0.01); // 角度公差
我们将使用 kinematic_constraints 包中可用的辅助函数将请求创建为约束。
moveit_msgs::msg::Constraints pose_goal =
kinematic_constraints::constructGoalConstraints("panda_link8", pose, tolerance_pose, tolerance_angle); // 构建目标约束
req.group_name = PLANNING_GROUP; // 设置规划组名称
req.goal_constraints.push_back(pose_goal); // 添加目标约束
定义工作区边界
req.workspace_parameters.min_corner.x = req.workspace_parameters.min_corner.y =
req.workspace_parameters.min_corner.z = -5.0; // 设置最小角点
req.workspace_parameters.max_corner.x = req.workspace_parameters.max_corner.y =
req.workspace_parameters.max_corner.z = 5.0; // 设置最大角点
我们现在构建一个规划上下文,封装场景、请求和响应。我们使用这个规划上下文调用规划器。
planning_interface::PlanningContextPtr context =
planner_instance->getPlanningContext(planning_scene, req, res.error_code); // 获取规划上下文
if (!context)
{
RCLCPP_ERROR(LOGGER, "创建规划上下文失败"); // 创建规划上下文失败
return -1;
}
context->solve(res); // 解决规划问题
if (res.error_code.val != res.error_code.SUCCESS)
{
RCLCPP_ERROR(LOGGER, "无法成功计算规划"); // 计算规划失败
return -1;
}
可视化结果
std::shared_ptr<rclcpp::Publisher<moveit_msgs::msg::DisplayTrajectory>> display_publisher =
motion_planning_api_tutorial_node->create_publisher<moveit_msgs::msg::DisplayTrajectory>("/display_planned_path",
1); // 创建显示轨迹的发布者
moveit_msgs::msg::DisplayTrajectory display_trajectory; // 显示轨迹消息
/* 可视化轨迹 */
moveit_msgs::msg::MotionPlanResponse response; // 运动计划响应
res.getMessage(response); // 获取响应消息
display_trajectory.trajectory_start = response.trajectory_start; // 设置轨迹起点
display_trajectory.trajectory.push_back(response.trajectory); // 添加轨迹
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group); // 发布轨迹线
visual_tools.trigger(); // 触发可视化工具
display_publisher->publish(display_trajectory); // 发布显示轨迹
/* 将规划场景中的状态设置为最后一个计划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions); // 设置关节组位置
planning_scene->setCurrentState(*robot_state.get()); // 设置当前状态
显示目标状态
visual_tools.publishAxisLabeled(pose.pose, "goal_1"); // 发布标记轴
visual_tools.publishText(text_pose, "姿态目标 (1)", rvt::WHITE, rvt::XLARGE); // 发布文本
visual_tools.trigger(); // 触发可视化工具
/* 我们还可以使用visual_tools等待用户输入 */
visual_tools.prompt("在RvizVisualToolsGui窗口中按'next'继续演示"); // 提示用户输入
关节空间目标
现在,设定一个关节空间目标
moveit::core::RobotState goal_state(robot_model); // 创建机器人状态
std::vector<double> joint_values = { -1.0, 0.7, 0.7, -1.5, -0.7, 2.0, 0.0 }; // 设置关节值
goal_state.setJointGroupPositions(joint_model_group, joint_values); // 设置关节组位置
moveit_msgs::msg::Constraints joint_goal =
kinematic_constraints::constructGoalConstraints(goal_state, joint_model_group); // 构建关节目标约束
req.goal_constraints.clear(); // 清除目标约束
req.goal_constraints.push_back(joint_goal); // 添加关节目标约束
调用规划器并可视化轨迹
/* 重新构建规划上下文 */
context = planner_instance->getPlanningContext(planning_scene, req, res.error_code); // 获取规划上下文
/* 调用规划器 */
context->solve(res); // 解决规划问题
/* 检查规划是否成功 */
if (res.error_code.val != res.error_code.SUCCESS)
{
RCLCPP_ERROR(LOGGER, "无法成功计算规划"); // 计算规划失败
return -1;
}
/* 可视化轨迹 */
res.getMessage(response); // 获取响应消息
display_trajectory.trajectory.push_back(response.trajectory); // 添加轨迹
/* 现在你应该看到两个连续的规划轨迹 */
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group); // 发布轨迹线
visual_tools.trigger(); // 触发可视化工具
display_publisher->publish(display_trajectory); // 发布显示轨迹
/* 我们将添加更多目标。但首先,将规划场景中的状态设置为最后一个计划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions); // 设置关节组位置
planning_scene->setCurrentState(*robot_state.get()); // 设置当前状态
显示目标状态
visual_tools.publishAxisLabeled(pose.pose, "goal_2"); // 发布标记轴
visual_tools.publishText(text_pose, "关节空间目标 (2)", rvt::WHITE, rvt::XLARGE); // 发布文本
visual_tools.trigger(); // 触发可视化工具
/* 等待用户输入 */
visual_tools.prompt("在RvizVisualToolsGui窗口中按'next'继续演示"); // 提示用户输入
/* 现在,我们回到第一个目标,为方向约束规划做准备 */
req.goal_constraints.clear(); // 清除目标约束
req.goal_constraints.push_back(pose_goal); // 添加姿态目标约束
context = planner_instance->getPlanningContext(planning_scene, req, res.error_code); // 获取规划上下文
context->solve(res); // 解决规划问题
res.getMessage(response); // 获取响应消息
display_trajectory.trajectory.push_back(response.trajectory); // 添加轨迹
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group); // 发布轨迹线
visual_tools.trigger(); // 触发可视化工具
display_publisher->publish(display_trajectory); // 发布显示轨迹
/* 将规划场景中的状态设置为最后一个计划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions); // 设置关节组位置
planning_scene->setCurrentState(*robot_state.get()); // 设置当前状态
显示目标状态
// 显示目标状态
visual_tools.trigger(); // 触发可视化工具
/* 等待用户输入 */
visual_tools.prompt("在RvizVisualToolsGui窗口中按'next'继续演示"); // 提示用户输入
添加路径约束
让我们再次添加一个新的姿势目标。这次我们还将为运动添加路径约束。
pose.pose.position.x = 0.32; // 设置位置x
pose.pose.position.y = -0.25; // 设置位置y
pose.pose.position.z = 0.65; // 设置位置z
pose.pose.orientation.w = 1.0; // 设置方向w
moveit_msgs::msg::Constraints pose_goal_2 =
kinematic_constraints::constructGoalConstraints("panda_link8", pose, tolerance_pose, tolerance_angle); // 构建姿态目标约束
/* 现在,让我们尝试移动到这个新的姿态目标 */
req.goal_constraints.clear(); // 清除目标约束
req.goal_constraints.push_back(pose_goal_2); // 添加姿态目标约束
/* 但是,让我们对运动施加路径约束。
在这里,我们要求末端执行器保持水平 */
geometry_msgs::msg::QuaternionStamped quaternion; // 四元数消息
quaternion.header.frame_id = "panda_link0"; // 设置帧ID
req.path_constraints = kinematic_constraints::constructGoalConstraints("panda_link8", quaternion); // 构建路径约束
施加路径约束要求规划器在末端执行器可能的位置空间(机器人工作空间)中进行推理,因此,我们还需要为允许的规划体积指定一个边界;注意:默认边界由 WorkspaceBounds 请求适配器自动填充(OMPL 管道的一部分,但在此示例中未使用)。我们使用的边界肯定包括手臂的可达空间。这是可以的,因为在为手臂规划时不会在此体积中进行采样;边界仅用于确定采样的配置是否有效。
req.workspace_parameters.min_corner.x = req.workspace_parameters.min_corner.y =
req.workspace_parameters.min_corner.z = -2.0; // 设置最小角点
req.workspace_parameters.max_corner.x = req.workspace_parameters.max_corner.y =
req.workspace_parameters.max_corner.z = 2.0// 设置最大角点
调用规划器并可视化到目前为止创建的所有规划。
context = planner_instance->getPlanningContext(planning_scene, req, res.error_code); // 获取规划上下文
context->solve(res); // 解决规划问题
res.getMessage(response); // 获取响应消息
display_trajectory.trajectory.push_back(response.trajectory); // 添加轨迹
visual_tools.publishTrajectoryLine(display_trajectory.trajectory.back(), joint_model_group); // 发布轨迹线
visual_tools.trigger(); // 触发可视化工具
display_publisher->publish(display_trajectory); // 发布显示轨迹
/* 将规划场景中的状态设置为最后一个计划的最终状态 */
robot_state->setJointGroupPositions(joint_model_group, response.trajectory.joint_trajectory.points.back().positions); // 设置关节组位置
planning_scene->setCurrentState(*robot_state.get()); // 设置当前状态
显示目标状态
visual_tools.publishAxisLabeled(pose.pose, "goal_3"); // 发布标记轴
visual_tools.publishText(text_pose, "姿态目标 (3)", rvt::WHITE, rvt::XLARGE); // 发布文本
visual_tools.trigger(); // 触发可视化工具
/* 等待用户输入 */
visual_tools.prompt("在RvizVisualToolsGui窗口中按'next'继续演示"); // 提示用户输入
启动文件
整个启动文件在 GitHub 上。本教程中的所有代码都可以从 moveit_tutorials 包中编译和运行。https://github.com/moveit/moveit2_tutorials/blob/main/doc/examples/motion_planning_api/launch/motion_planning_api_tutorial.launch.py
from launch import LaunchDescription # 从launch模块导入LaunchDescription类
from launch_ros.actions import Node # 从launch_ros.actions模块导入Node类
from moveit_configs_utils import MoveItConfigsBuilder # 从moveit_configs_utils模块导入MoveItConfigsBuilder类
def generate_launch_description(): # 定义generate_launch_description函数
moveit_config = ( # 创建moveit_config对象
MoveItConfigsBuilder("moveit_resources_panda") # 使用MoveItConfigsBuilder类构建配置
.robot_description(file_path="config/panda.urdf.xacro") # 设置机器人描述文件路径
.robot_description_semantic(file_path="config/panda.srdf") # 设置机器人语义描述文件路径
.trajectory_execution(file_path="config/moveit_controllers.yaml") # 设置轨迹执行配置文件路径
.planning_pipelines(pipelines=["ompl"]) # 设置规划管道
.to_moveit_configs() # 转换为MoveIt配置
)
return LaunchDescription( # 返回LaunchDescription对象
[
Node( # 创建Node对象
package="moveit2_tutorials", # 设置包名为moveit2_tutorials
executable="motion_planning_api_tutorial", # 设置可执行文件名为motion_planning_api_tutorial
name="motion_planning_api_tutorial", # 设置节点名为motion_planning_api_tutorial
output="screen", # 设置输出为屏幕
parameters=[moveit_config.to_dict()], # 设置参数为moveit_config的字典表示
)
]
)