- 生成轨迹中points点数至少“1+路点数”,1是机器人当前状态。此时每个路点都没有分步长。
- 笛卡儿规划要用运动学求器解,用不到路径规划器。
示例
[0/2]{p: (0.281330, -0.000289, 0.296321), M: (-0.418335i+0.258038j+-0.736527k+0.464692)}
[1/2]{p: (0.381415, -0.000289, 0.118198), M: (-0.737963i+0.340484j+-0.526792k+0.248938)}
[2/2]{p: (0.298734, -0.000288, 0.293430), M: (-0.277421i+0.271752j+-0.650418k+0.652796)}
一、MoveGroupCartesianPathService::computeService
bool MoveGroupCartesianPathService::computeService(moveit_msgs::GetCartesianPath::Request& req,
moveit_msgs::GetCartesianPath::Response& res)
{
ROS_INFO_NAMED(getName(), "Received request to compute Cartesian path");
context_->planning_scene_monitor_->updateFrameTransforms();
moveit::core::RobotState start_state =
planning_scene_monitor::LockedPlanningSceneRO(context_->planning_scene_monitor_)->getCurrentState();
moveit::core::robotStateMsgToRobotState(req.start_state, start_state);
if (const moveit::core::JointModelGroup* jmg = start_state.getJointModelGroup(req.group_name))
{
std::string link_name = req.link_name;
if (link_name.empty() && !jmg->getLinkModelNames().empty())
link_name = jmg->getLinkModelNames().back();
bool ok = true;
EigenSTL::vector_Isometry3d waypoints(req.waypoints.size());
const std::string& default_frame = context_->planning_scene_monitor_->getRobotModel()->getModelFrame();
bool no_transform = req.header.frame_id.empty() ||
moveit::core::Transforms::sameFrame(req.header.frame_id, default_frame) ||
moveit::core::Transforms::sameFrame(req.header.frame_id, link_name);
for (std::size_t i = 0; i < req.waypoints.size(); ++i)
{
if (no_transform)
tf2::fromMsg(req.waypoints[i], waypoints[i]);位姿变换[1/5]tf2::fromMsg(req.waypoints[i], waypoints[i])。位姿类型从geometry_msgs::Pose转换为Isometry3d,值不变。
waypoints[1]:{p(0.381415, -0.000289, 0.118198) quat(0.737963i+-0.340484j+0.526792k+-0.248938)}
0.21312 -0.240252 0.947026
-0.764808 -0.644199 0.00868566
0.607987 -0.726145 -0.321038
else
{
geometry_msgs::PoseStamped p;
p.header = req.header;
p.pose = req.waypoints[i];
if (performTransform(p, default_frame))
tf2::fromMsg(p.pose, waypoints[i]);
else
{
ROS_ERROR_NAMED(getName(), "Error encountered transforming waypoints to frame '%s'", default_frame.c_str());
ok = false;
break;
}
}
}
if (ok)
{
if (req.max_step < std::numeric_limits<double>::epsilon())
{
ROS_ERROR_NAMED(getName(), "Maximum step to take between consecutive configrations along Cartesian path"
"was not specified (this value needs to be > 0)");
res.error_code.val = moveit_msgs::MoveItErrorCodes::FAILURE;
}
else
{
if (!waypoints.empty())
{
moveit::core::GroupStateValidityCallbackFn constraint_fn;
std::unique_ptr<planning_scene_monitor::LockedPlanningSceneRO> ls;
std::unique_ptr<kinematic_constraints::KinematicConstraintSet> kset;
if (req.avoid_collisions || !moveit::core::isEmpty(req.path_constraints))
{
ls = std::make_unique<planning_scene_monitor::LockedPlanningSceneRO>(context_->planning_scene_monitor_);
kset = std::make_unique<kinematic_constraints::KinematicConstraintSet>((*ls)->getRobotModel());
kset->add(req.path_constraints, (*ls)->getTransforms());
constraint_fn = std::bind(
&isStateValid,
req.avoid_collisions ? static_cast<const planning_scene::PlanningSceneConstPtr&>(*ls).get() : nullptr,
kset->empty() ? nullptr : kset.get(), std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3);req.avoid_collisions一般非0,因而会进这入口。创建了指向isStateValid的函数指针constraint_fn,它的调用时机是在运动学求解器逆解一个位姿成功后。除运动学约束,isStateValid还会把逆解出的关节状态设置向start_pose。
} bool global_frame = !moveit::core::Transforms::sameFrame(link_name, req.header.frame_id); ROS_INFO_NAMED(getName(), "Attempting to follow %u waypoints for link '%s' using a step of %lf m " "and jump threshold %lf (in %s reference frame)", (unsigned int)waypoints.size(), link_name.c_str(), req.max_step, req.jump_threshold, global_frame ? "global" : "link"); std::vector<moveit::core::RobotStatePtr> traj; res.fraction = moveit::core::CartesianInterpolator::computeCartesianPath( &start_state, jmg, traj, start_state.getLinkModel(link_name), waypoints, global_frame, moveit::core::MaxEEFStep(req.max_step), moveit::core::JumpThreshold(req.jump_threshold), constraint_fn);
traj存储着所有路点的关节状态,4个。
moveit::core::robotStateToRobotStateMsg(start_state, res.start_state);
robot_trajectory::RobotTrajectory rt(context_->planning_scene_monitor_->getRobotModel(), req.group_name);
for (const moveit::core::RobotStatePtr& traj_state : traj)
rt.addSuffixWayPoint(traj_state, 0.0);
// time trajectory
// \todo optionally compute timing to move the eef with constant speed
trajectory_processing::IterativeParabolicTimeParameterization time_param;
time_param.computeTimeStamps(rt, 1.0);
rt.getRobotTrajectoryMsg(res.solution);
ROS_INFO_NAMED(getName(), "Computed Cartesian path with %u points (followed %lf%% of requested trajectory)",
(unsigned int)traj.size(), res.fraction * 100.0);
if (display_computed_paths_ && rt.getWayPointCount() > 0)
{
moveit_msgs::DisplayTrajectory disp;
disp.model_id = context_->planning_scene_monitor_->getRobotModel()->getName();
disp.trajectory.resize(1, res.solution);
moveit::core::robotStateToRobotStateMsg(rt.getFirstWayPoint(), disp.trajectory_start);
display_path_.publish(disp);
}
}
res.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
}
}
else
res.error_code.val = moveit_msgs::MoveItErrorCodes::FRAME_TRANSFORM_FAILURE;
}
else
res.error_code.val = moveit_msgs::MoveItErrorCodes::INVALID_GROUP_NAME;
return true;
}
二、外层CartesianInterpolator::computeCartesianPath
double CartesianInterpolator::computeCartesianPath(RobotState* start_state, const JointModelGroup* group,
std::vector<RobotStatePtr>& traj, const LinkModel* link,
const EigenSTL::vector_Isometry3d& waypoints,
bool global_reference_frame, const MaxEEFStep& max_step,
const JumpThreshold& jump_threshold,
const GroupStateValidityCallbackFn& validCallback,
const kinematics::KinematicsQueryOptions& options)
{
double percentage_solved = 0.0;
for (std::size_t i = 0; i < waypoints.size(); ++i)
{
// Don't test joint space jumps for every waypoint, test them later on the whole trajectory.
static const JumpThreshold NO_JOINT_SPACE_JUMP_TEST;
std::vector<RobotStatePtr> waypoint_traj;
double wp_percentage_solved =
computeCartesianPath(start_state, group, waypoint_traj, link, waypoints[i], global_reference_frame, max_step,
NO_JOINT_SPACE_JUMP_TEST, validCallback, options);waypoint_traj存储着此轮路点逆解出的位姿,一旦成功,最小长度是2。[0]总是start_state。
wp_percentage_solved。当前路点求解了的百分比。范围[0, 1]。
percentage_solved。累计求解了的百分比。范围[0, 1]。
if (fabs(wp_percentage_solved - 1.0) < std::numeric_limits<double>::epsilon())
{此个路点中所有位姿都逆解成功。
percentage_solved = (double)(i + 1) / (double)waypoints.size();
std::vector<RobotStatePtr>::iterator start = waypoint_traj.begin();
if (i > 0 && !waypoint_traj.empty())
std::advance(start, 1);
traj.insert(traj.end(), start, waypoint_traj.end());
}
else
{此个路点有位姿逆解失败。
percentage_solved += wp_percentage_solved / (double)waypoints.size(); std::vector<RobotStatePtr>::iterator start = waypoint_traj.begin(); if (i > 0 && !waypoint_traj.empty()) std::advance(start, 1); traj.insert(traj.end(), start, waypoint_traj.end()); break; } }
if1, wp_percentage_solved: 1.000000, percentage_solved: 0.333333, waypoint_traj.size: 2
if1, wp_percentage_solved: 1.000000, percentage_solved: 0.666666, waypoint_traj.size: 2
if1, wp_percentage_solved: 1.000000, percentage_solved: 1.000000, waypoint_traj.size: 2
percentage_solved *= checkJointSpaceJump(group, traj, jump_threshold); return percentage_solved; }
三、内层CartesianInterpolator::computeCartesianPath
double CartesianInterpolator::computeCartesianPath(RobotState* start_state, const JointModelGroup* group,
std::vector<RobotStatePtr>& traj, const LinkModel* link,
const Eigen::Isometry3d& target, bool global_reference_frame,
const MaxEEFStep& max_step, const JumpThreshold& jump_threshold,
const GroupStateValidityCallbackFn& validCallback,
const kinematics::KinematicsQueryOptions& options)
{
const std::vector<const JointModel*>& cjnt = group->getContinuousJointModels();
// make sure that continuous joints wrap
for (const JointModel* joint : cjnt)
start_state->enforceBounds(joint);
// this is the Cartesian pose we start from, and we move in the direction indicated
// getGlobalLinkTransform() returns a valid isometry by contract
Eigen::Isometry3d start_pose = start_state->getGlobalLinkTransform(link); // valid isometry第一个路点的start_pose是机器人当前位姿,后续路点的start_pose是上一个路点的位姿。
ASSERT_ISOMETRY(target) // unsanitized input, could contain a non-isometry // the target can be in the local reference frame (in which case we rotate it) Eigen::Isometry3d rotated_target = global_reference_frame ? target : start_pose * target; // valid isometry
start_pose:机器人当前正处于的位姿。start_state: {position_: -0.56,0.79,-0.45}
start_pose: {p(0.281330, -0.000289, 0.296320) quat(0.418337i+-0.258039j+0.736526k+-0.464691)}
-0.218111 0.468621 0.85605
-0.900411 -0.434954 0.00868973
0.376415 -0.768901 0.51682
target:上面由tf2::fromMsg(req.waypoints[i], waypoints[i])算出的waypoints[i]。
target: {p(0.381415, -0.000289, 0.118198) quat(0.737963i+-0.340484j+0.526792k+-0.248938)}
0.21312 -0.240252 0.947026
-0.764808 -0.644199 0.00868566
0.607987 -0.726145 -0.321038
Eigen::Isometry3d rotated_target = global_reference_frame ? target : start_pose * target;
bool global_frame = !moveit::core::Transforms::sameFrame(link_name, req.header.frame_id)。global_reference_frame表示target是否是规划组末端link坐标系。false表示是末端link坐标系,像grasping_frame。那target存储的是相对于start_pose的相对位姿,否则target存储的是绝对位姿。
位姿变换[2/5]由于global_reference_frame是true,rotated_target直接取自target。
rotated_target: {p(0.381415, -0.000289, 0.118198) quat(0.737963i+-0.340484j+0.526792k+-0.248938)}
0.21312 -0.240252 0.947026
-0.764808 -0.644199 0.00868566
0.607987 -0.726145 -0.321038
Eigen::Quaterniond start_quaternion(start_pose.linear());
Eigen::Quaterniond target_quaternion(rotated_target.linear());
if (max_step.translation <= 0.0 && max_step.rotation <= 0.0)
{
ROS_ERROR_NAMED(LOGNAME,
"Invalid MaxEEFStep passed into computeCartesianPath. Both the MaxEEFStep.rotation and "
"MaxEEFStep.translation components must be non-negative and at least one component must be "
"greater than zero");
return 0.0;
}
double rotation_distance = start_quaternion.angularDistance(target_quaternion);
double translation_distance = (rotated_target.translation() - start_pose.translation()).norm();
// decide how many steps we will need for this trajectory
std::size_t translation_steps = 0;
if (max_step.translation > 0.0)
translation_steps = floor(translation_distance / max_step.translation);
std::size_t rotation_steps = 0;
if (max_step.rotation > 0.0)
rotation_steps = floor(rotation_distance / max_step.rotation);
// If we are testing for relative jumps, we always want at least MIN_STEPS_FOR_JUMP_THRESH steps
std::size_t steps = std::max(translation_steps, rotation_steps) + 1;
if (jump_threshold.factor > 0 && steps < MIN_STEPS_FOR_JUMP_THRESH)
steps = MIN_STEPS_FOR_JUMP_THRESH;translation_steps是位置上分出的段数,rotation_steps是角度上分出的段数,它们较大值就是此个在(start_pose, 路点)这个位姿范围内要分出的段数。
// To limit absolute joint-space jumps, we pass consistency limits to the IK solver
std::vector<double> consistency_limits;
if (jump_threshold.prismatic > 0 || jump_threshold.revolute > 0)
for (const JointModel* jm : group->getActiveJointModels())
{
double limit;
switch (jm->getType())
{
case JointModel::REVOLUTE:
limit = jump_threshold.revolute;
break;
case JointModel::PRISMATIC:
limit = jump_threshold.prismatic;
break;
default:
limit = 0.0;
}
if (limit == 0.0)
limit = jm->getMaximumExtent();
consistency_limits.push_back(limit);
}
traj.clear();
traj.push_back(std::make_shared<moveit::core::RobotState>(*start_state));由路点生成的traj,[0]肯定是start_state。
double last_valid_percentage = 0.0;
for (std::size_t i = 1; i <= steps; ++i)
{
double percentage = (double)i / (double)steps;
Eigen::Isometry3d pose(start_quaternion.slerp(percentage, target_quaternion));位姿变换[3/5]pose。得到按比例角度,位置保持0。
pose: {p(0, 0, 0) quat(0.737963i+-0.340484j+0.526792k+-0.248938)}
0.21312 -0.240252 0.947026
-0.764808 -0.644199 0.00868566
0.607987 -0.726145 -0.321038
pose.translation() = percentage * rotated_target.translation() + (1 - percentage) * start_pose.translation();
位姿变换[4/5]pose。加上按比例位置,至此形成一个完整的按比例位姿。
pose: {p(0.381415, -0.000289, 0.118198) quat(0.737963i+-0.340484j+0.526792k+-0.248938)}
0.21312 -0.240252 0.947026
-0.764808 -0.644199 0.00868566
0.607987 -0.726145 -0.321038
// Explicitly use a single IK attempt only: We want a smooth trajectory. // Random seeding (of additional attempts) would probably create IK jumps. if (start_state->setFromIK(group, pose, link->getName(), consistency_limits, 0.0, validCallback, options)) traj.push_back(std::make_shared<moveit::core::RobotState>(*start_state));
setFromIK执行依次执行1)求pose的逆解。2)调用setJointGroupPositions,求出的逆解存储在start_state的position_。
traj.push_back(...)把state_state添加到traj,这同时把求出的逆解加到了traj。
percentage指是一但完成此个i位姿,那已完成的百分比。对于特例,当i==steps,percentage是100%。一量i逆解失败,那成功的就是上次的last_valid_percentage。
else break; last_valid_percentage = percentage; } last_valid_percentage *= checkJointSpaceJump(group, traj, jump_threshold); return last_valid_percentage; }
四、RobotState::setToIKSolverFrame
bool RobotState::setToIKSolverFrame(Eigen::Isometry3d& pose, const std::string& ik_frame)
{
// Bring the pose to the frame of the IK solver
if (!Transforms::sameFrame(ik_frame, robot_model_->getModelFrame()))
{
const LinkModel* link_model =
getLinkModel((!ik_frame.empty() && ik_frame[0] == '/') ? ik_frame.substr(1) : ik_frame);
if (!link_model)
{
ROS_ERROR_STREAM_NAMED(LOGNAME, "IK frame '" << ik_frame << "' does not exist.");
return false;
}
pose = getGlobalLinkTransform(link_model).inverse() * pose;右侧pose就此[4/5]时生成的pose,是base_link坐标系下的位姿。变量ik_frame值是arm_joint,getGlobalLinkTransform(link_model)返回base_link <-- arm_joint的变换矩阵。位姿变换[5/5]pose是乘法算出的arm_joint下位姿。由于是要从base_link算出arm_joint,要用变换矩阵的逆。
getGlobalLinkTransform(link_model): {p(0.117869, 0, 0.078854) quat(0.274591i+0.274592j+0.651614k+0.651611)}
-3.67321e-06 -0.698398 0.71571
1 -2.56536e-06 2.62895e-06
-2.60902e-15 0.71571 0.698398
位姿变换[5/5]pose是乘法算出的arm_joint下位姿。此个pose就是要送入运动学求解器要逆解的位姿。
pose: {p(-0.000289, -0.155900, 0.216099) quat(0.182704i+-0.489721j+0.801609k+0.290197)}
-0.764809 -0.644198 0.00868218
0.286302 -0.351916 -0.891171
0.577147 -0.67909 0.453584
} return true; }