moveL function added

This commit is contained in:
NuoDaJia
2026-03-03 17:45:37 +08:00
parent 2be6713107
commit 87a3bf68f9
4 changed files with 509 additions and 75 deletions

View File

@@ -11,6 +11,7 @@
#include "control_msgs/action/follow_joint_trajectory.hpp"
#include "interfaces/action/dual_arm.hpp"
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include "geometry_msgs/msg/pose.hpp"
#include "actions/action_context.hpp"
@@ -44,7 +45,7 @@ private:
void handle_accepted_(const std::shared_ptr<GoalHandleDualArm> goal_handle);
void execute_(const std::shared_ptr<GoalHandleDualArm> goal_handle);
bool collect_movej_targets_(
bool collect_dual_arm_joint_targets_(
const DualArm::Goal& goal,
size_t arm_dof,
bool* need_left,
@@ -53,6 +54,10 @@ private:
std::vector<double>* right_target,
std::string* error_msg) const;
bool validate_movel_pose_(
const geometry_msgs::msg::Pose& pose,
std::string* error_msg) const;
bool export_and_normalize_trajectory_(
const MotionParameters& motion_params,
trajectory_msgs::msg::JointTrajectory* out,
@@ -160,6 +165,9 @@ private:
bool save_trajectory_enabled_{true};
bool trajectory_execution_enabled_{true};
bool movel_sync_use_s_curve_progress_{true};
double movel_sync_min_dt_sec_{0.01};
int movel_sync_max_samples_{500};
};
} // namespace robot_control

View File

@@ -9,6 +9,7 @@
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
namespace robot_control
@@ -143,4 +144,226 @@ inline trajectory_msgs::msg::JointTrajectory make_deceleration_trajectory(
return traj;
}
/**
* @brief 将值限制到 [0, 1]
*/
inline double clamp01(double v)
{
if (v < 0.0) return 0.0;
if (v > 1.0) return 1.0;
return v;
}
/**
* @brief Quintic smoothstep 进度映射S型
*
* 输入/输出均为 [0,1],且在 0/1 两端的一阶、二阶导均为 0
* 适合做起停平滑的时间重标定。
*/
inline double s_curve_progress(double u)
{
u = clamp01(u);
const double u2 = u * u;
const double u3 = u2 * u;
const double u4 = u3 * u;
const double u5 = u4 * u;
return 6.0 * u5 - 15.0 * u4 + 10.0 * u3;
}
/**
* @brief 在指定时刻对轨迹做关节位置线性插值
*
* - t 早于首点时,返回首点位置
* - t 晚于尾点时,返回尾点位置
*/
inline bool interpolate_positions_at_time(
const trajectory_msgs::msg::JointTrajectory& traj,
double t_sec,
std::vector<double>* out_positions)
{
if (!out_positions || traj.points.empty()) return false;
const size_t dof = traj.joint_names.size();
if (dof == 0) return false;
out_positions->assign(dof, 0.0);
const auto time_of = [](const trajectory_msgs::msg::JointTrajectoryPoint& p) {
return static_cast<double>(p.time_from_start.sec) +
static_cast<double>(p.time_from_start.nanosec) * 1e-9;
};
const double t0 = time_of(traj.points.front());
const double tN = time_of(traj.points.back());
if (traj.points.front().positions.size() < dof ||
traj.points.back().positions.size() < dof) {
return false;
}
if (t_sec <= t0 + 1e-9) {
*out_positions = traj.points.front().positions;
return true;
}
if (t_sec >= tN - 1e-9) {
*out_positions = traj.points.back().positions;
return true;
}
for (size_t i = 0; i + 1 < traj.points.size(); ++i) {
const double ta = time_of(traj.points[i]);
const double tb = time_of(traj.points[i + 1]);
if (t_sec <= tb + 1e-9) {
if (traj.points[i].positions.size() < dof ||
traj.points[i + 1].positions.size() < dof) {
return false;
}
const double den = std::max(1e-9, tb - ta);
const double a = clamp01((t_sec - ta) / den);
for (size_t j = 0; j < dof; ++j) {
const double q0 = traj.points[i].positions[j];
const double q1 = traj.points[i + 1].positions[j];
(*out_positions)[j] = q0 + a * (q1 - q0);
}
return true;
}
}
*out_positions = traj.points.back().positions;
return true;
}
/**
* @brief 基于位置点列回填速度与加速度(数值差分)
*/
inline void fill_velocity_acceleration(
trajectory_msgs::msg::JointTrajectory* traj)
{
if (!traj || traj->points.empty()) return;
const size_t n = traj->points.size();
const size_t dof = traj->joint_names.size();
if (dof == 0) return;
const auto time_of = [](const trajectory_msgs::msg::JointTrajectoryPoint& p) {
return static_cast<double>(p.time_from_start.sec) +
static_cast<double>(p.time_from_start.nanosec) * 1e-9;
};
for (auto& p : traj->points) {
if (p.positions.size() < dof) return;
p.velocities.assign(dof, 0.0);
p.accelerations.assign(dof, 0.0);
}
if (n < 2) return;
for (size_t i = 0; i < n; ++i) {
const size_t i0 = (i == 0) ? 0 : (i - 1);
const size_t i1 = (i + 1 >= n) ? (n - 1) : (i + 1);
const double t0 = time_of(traj->points[i0]);
const double t1 = time_of(traj->points[i1]);
const double dt = std::max(1e-6, t1 - t0);
for (size_t j = 0; j < dof; ++j) {
const double q0 = traj->points[i0].positions[j];
const double q1 = traj->points[i1].positions[j];
traj->points[i].velocities[j] = (q1 - q0) / dt;
}
}
for (size_t i = 0; i < n; ++i) {
const size_t i0 = (i == 0) ? 0 : (i - 1);
const size_t i1 = (i + 1 >= n) ? (n - 1) : (i + 1);
const double t0 = time_of(traj->points[i0]);
const double t1 = time_of(traj->points[i1]);
const double dt = std::max(1e-6, t1 - t0);
for (size_t j = 0; j < dof; ++j) {
const double v0 = traj->points[i0].velocities[j];
const double v1 = traj->points[i1].velocities[j];
traj->points[i].accelerations[j] = (v1 - v0) / dt;
}
}
}
/**
* @brief 双臂轨迹统一时间轴重采样可选S型进度
*
* 目标是让左右臂输出轨迹拥有相同的时间戳序列,实现同步执行。
* @param use_s_curve_progress true 时对进度使用 S 型映射,降低起停冲击
*/
inline bool synchronize_dual_arm_trajectory_timebase(
const trajectory_msgs::msg::JointTrajectory& left_in,
const trajectory_msgs::msg::JointTrajectory& right_in,
trajectory_msgs::msg::JointTrajectory* left_out,
trajectory_msgs::msg::JointTrajectory* right_out,
double* out_total_time,
std::string* error_msg = nullptr,
bool use_s_curve_progress = true,
double min_dt = 0.01,
size_t max_samples = 500)
{
if (!left_out || !right_out || !out_total_time) {
if (error_msg) *error_msg = "Null output pointer";
return false;
}
if (left_in.points.empty() || right_in.points.empty()) {
if (error_msg) *error_msg = "Empty left/right trajectory";
return false;
}
const auto total_time = [](const trajectory_msgs::msg::JointTrajectory& t) {
const auto& p = t.points.back();
return static_cast<double>(p.time_from_start.sec) +
static_cast<double>(p.time_from_start.nanosec) * 1e-9;
};
const double left_T = total_time(left_in);
const double right_T = total_time(right_in);
const double T = std::max(left_T, right_T);
*out_total_time = T;
left_out->header = left_in.header;
right_out->header = right_in.header;
left_out->joint_names = left_in.joint_names;
right_out->joint_names = right_in.joint_names;
left_out->points.clear();
right_out->points.clear();
if (T <= 1e-6) {
left_out->points.push_back(left_in.points.back());
right_out->points.push_back(right_in.points.back());
fill_velocity_acceleration(left_out);
fill_velocity_acceleration(right_out);
return true;
}
min_dt = std::max(0.001, min_dt);
max_samples = std::max<size_t>(3, max_samples);
size_t samples = std::max(left_in.points.size(), right_in.points.size());
samples = std::max<size_t>(samples, 3);
const size_t min_samples_by_dt = static_cast<size_t>(std::ceil(T / min_dt)) + 1;
if (samples < min_samples_by_dt) samples = min_samples_by_dt;
if (samples > max_samples) samples = max_samples;
left_out->points.reserve(samples);
right_out->points.reserve(samples);
for (size_t k = 0; k < samples; ++k) {
const double u = (samples == 1) ? 1.0 : (static_cast<double>(k) / static_cast<double>(samples - 1));
const double progress = use_s_curve_progress ? s_curve_progress(u) : u;
const double sample_t = progress * T;
const double output_t = u * T;
trajectory_msgs::msg::JointTrajectoryPoint lp;
trajectory_msgs::msg::JointTrajectoryPoint rp;
if (!interpolate_positions_at_time(left_in, sample_t, &lp.positions) ||
!interpolate_positions_at_time(right_in, sample_t, &rp.positions)) {
if (error_msg) *error_msg = "Trajectory interpolation failed";
return false;
}
sec_to_duration(output_t, &lp.time_from_start);
sec_to_duration(output_t, &rp.time_from_start);
left_out->points.push_back(std::move(lp));
right_out->points.push_back(std::move(rp));
}
fill_velocity_acceleration(left_out);
fill_velocity_acceleration(right_out);
return true;
}
} // namespace robot_control

View File

@@ -35,6 +35,40 @@ void DualArmAction::initialize()
ctx_.node, "/left_arm_controller/follow_joint_trajectory");
right_fjt_client_ = rclcpp_action::create_client<FollowJTA>(
ctx_.node, "/right_arm_controller/follow_joint_trajectory");
if (!ctx_.node->has_parameter("dual_arm.movel_sync.use_s_curve_progress")) {
movel_sync_use_s_curve_progress_ =
ctx_.node->declare_parameter<bool>(
"dual_arm.movel_sync.use_s_curve_progress",
movel_sync_use_s_curve_progress_);
} else {
(void)ctx_.node->get_parameter(
"dual_arm.movel_sync.use_s_curve_progress",
movel_sync_use_s_curve_progress_);
}
if (!ctx_.node->has_parameter("dual_arm.movel_sync.min_dt_sec")) {
movel_sync_min_dt_sec_ =
ctx_.node->declare_parameter<double>(
"dual_arm.movel_sync.min_dt_sec",
movel_sync_min_dt_sec_);
} else {
(void)ctx_.node->get_parameter(
"dual_arm.movel_sync.min_dt_sec",
movel_sync_min_dt_sec_);
}
if (!ctx_.node->has_parameter("dual_arm.movel_sync.max_samples")) {
movel_sync_max_samples_ =
ctx_.node->declare_parameter<int>(
"dual_arm.movel_sync.max_samples",
movel_sync_max_samples_);
} else {
(void)ctx_.node->get_parameter(
"dual_arm.movel_sync.max_samples",
movel_sync_max_samples_);
}
movel_sync_min_dt_sec_ = std::max(0.001, movel_sync_min_dt_sec_);
movel_sync_max_samples_ = std::max(3, movel_sync_max_samples_);
}
server_ = rclcpp_action::create_server<DualArm>(
@@ -43,7 +77,12 @@ void DualArmAction::initialize()
std::bind(&DualArmAction::handle_goal_, this, _1, _2),
std::bind(&DualArmAction::handle_cancel_, this, _1),
std::bind(&DualArmAction::handle_accepted_, this, _1));
RCLCPP_INFO(ctx_.node->get_logger(), "DualArm action server is ready");
RCLCPP_INFO(
ctx_.node->get_logger(),
"DualArm action server is ready (movel_sync: s_curve=%s, min_dt=%.4f, max_samples=%d)",
movel_sync_use_s_curve_progress_ ? "true" : "false",
movel_sync_min_dt_sec_,
movel_sync_max_samples_);
}
rclcpp_action::GoalResponse DualArmAction::handle_goal_(
@@ -111,20 +150,26 @@ rclcpp_action::GoalResponse DualArmAction::handle_goal_(
return rclcpp_action::GoalResponse::REJECT;
}
// 目前只支持 MOVEJ
if (arm_param.motion_type != arm_param.MOVEJ) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: only MOVEJ is supported for now (got motion_type=%d for arm_id=%d)",
arm_param.motion_type, arm_param.arm_id);
return rclcpp_action::GoalResponse::REJECT;
}
// 关节限位检查
std::string err;
if (!ctx_.robot_control_manager->ValidateArmJointTarget(arm_param.arm_id, arm_param.target_joints, &err)) {
if (arm_param.motion_type == arm_param.MOVEJ) {
if (!ctx_.robot_control_manager->ValidateArmJointTarget(
arm_param.arm_id, arm_param.target_joints, &err)) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: MOVEJ target_joints invalid for arm_id %d: %s",
arm_param.arm_id, err.c_str());
return rclcpp_action::GoalResponse::REJECT;
}
} else if (arm_param.motion_type == arm_param.MOVEL) {
if (!validate_movel_pose_(arm_param.target_pose, &err)) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: MOVEL target_pose invalid for arm_id %d: %s",
arm_param.arm_id, err.c_str());
return rclcpp_action::GoalResponse::REJECT;
}
} else {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: MOVEJ target_joints exceed limits for arm_id %d: %s",
arm_param.arm_id, err.c_str());
"DualArm goal rejected: unsupported motion_type=%d for arm_id=%d",
arm_param.motion_type, arm_param.arm_id);
return rclcpp_action::GoalResponse::REJECT;
}
@@ -152,7 +197,7 @@ rclcpp_action::GoalResponse DualArmAction::handle_goal_(
return rclcpp_action::GoalResponse::REJECT;
}
// 双臂模式:两只手都不能在执行,且只允许 MOVEJ
// 双臂模式:两只手都不能在执行
if (executing_left_.load() || executing_right_.load()) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: left or right arm is executing another goal");
@@ -160,17 +205,26 @@ rclcpp_action::GoalResponse DualArmAction::handle_goal_(
}
for (const auto& arm_param : goal->arm_motion_params) {
if (arm_param.motion_type != arm_param.MOVEJ) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: only MOVEJ is supported for now (got motion_type=%d for arm_id=%d)",
arm_param.motion_type, arm_param.arm_id);
return rclcpp_action::GoalResponse::REJECT;
}
std::string err;
if (!ctx_.robot_control_manager->ValidateArmJointTarget(arm_param.arm_id, arm_param.target_joints, &err)) {
if (arm_param.motion_type == arm_param.MOVEJ) {
if (!ctx_.robot_control_manager->ValidateArmJointTarget(
arm_param.arm_id, arm_param.target_joints, &err)) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: MOVEJ target_joints invalid for arm_id %d: %s",
arm_param.arm_id, err.c_str());
return rclcpp_action::GoalResponse::REJECT;
}
} else if (arm_param.motion_type == arm_param.MOVEL) {
if (!validate_movel_pose_(arm_param.target_pose, &err)) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: MOVEL target_pose invalid for arm_id %d: %s",
arm_param.arm_id, err.c_str());
return rclcpp_action::GoalResponse::REJECT;
}
} else {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: MOVEJ target_joints exceed limits for arm_id %d: %s",
arm_param.arm_id, err.c_str());
"DualArm goal rejected: unsupported motion_type=%d for arm_id=%d",
arm_param.motion_type, arm_param.arm_id);
return rclcpp_action::GoalResponse::REJECT;
}
}
@@ -196,7 +250,7 @@ void DualArmAction::handle_accepted_(const std::shared_ptr<GoalHandleDualArm> go
std::thread{std::bind(&DualArmAction::execute_, this, _1), goal_handle}.detach();
}
bool DualArmAction::collect_movej_targets_(
bool DualArmAction::collect_dual_arm_joint_targets_(
const DualArm::Goal& goal,
size_t arm_dof,
bool* need_left,
@@ -216,25 +270,67 @@ bool DualArmAction::collect_movej_targets_(
right_target->clear();
for (const auto& arm_param : goal.arm_motion_params) {
if (arm_param.motion_type != arm_param.MOVEJ) {
if (error_msg) *error_msg = "Only MOVEJ is supported for DualArm";
return false;
}
if (arm_param.target_joints.size() != arm_dof) {
std::vector<double> target_joint;
if (arm_param.motion_type == arm_param.MOVEJ) {
target_joint = arm_param.target_joints;
if (target_joint.size() != arm_dof) {
if (error_msg) {
*error_msg =
"target_joints size mismatch for arm_id " + std::to_string(arm_param.arm_id) +
" (expected " + std::to_string(arm_dof) +
", got " + std::to_string(target_joint.size()) + ")";
}
return false;
}
} else if (arm_param.motion_type == arm_param.MOVEL) {
std::string err;
if (!validate_movel_pose_(arm_param.target_pose, &err)) {
if (error_msg) {
*error_msg = "MOVEL target_pose invalid for arm_id " +
std::to_string(arm_param.arm_id) + ": " + err;
}
return false;
}
if (!ctx_.robot_control_manager->SolveArmInverseKinematics(
arm_param.arm_id, arm_param.target_pose, &target_joint, &err)) {
if (error_msg) {
*error_msg = "MOVEL IK failed for arm_id " +
std::to_string(arm_param.arm_id) + ": " + err;
}
return false;
}
if (target_joint.size() != arm_dof) {
if (error_msg) {
*error_msg =
"MOVEL IK output size mismatch for arm_id " + std::to_string(arm_param.arm_id) +
" (expected " + std::to_string(arm_dof) +
", got " + std::to_string(target_joint.size()) + ")";
}
return false;
}
} else {
if (error_msg) {
*error_msg =
"target_joints size mismatch for arm_id " + std::to_string(arm_param.arm_id) +
" (expected " + std::to_string(arm_dof) +
", got " + std::to_string(arm_param.target_joints.size()) + ")";
*error_msg = "Unsupported motion_type for arm_id " + std::to_string(arm_param.arm_id);
}
return false;
}
std::string limit_err;
if (!ctx_.robot_control_manager->ValidateArmJointTarget(
arm_param.arm_id, target_joint, &limit_err)) {
if (error_msg) {
*error_msg = "Target joint out of limits for arm_id " +
std::to_string(arm_param.arm_id) + ": " + limit_err;
}
return false;
}
if (arm_param.arm_id == 0) {
*need_left = true;
*left_target = arm_param.target_joints;
*left_target = target_joint;
} else if (arm_param.arm_id == 1) {
*need_right = true;
*right_target = arm_param.target_joints;
*right_target = target_joint;
} else {
if (error_msg) *error_msg = "Invalid arm_id (expected 0=left, 1=right)";
return false;
@@ -248,6 +344,29 @@ bool DualArmAction::collect_movej_targets_(
return true;
}
bool DualArmAction::validate_movel_pose_(
const geometry_msgs::msg::Pose& pose,
std::string* error_msg) const
{
auto finite = [](double v) { return std::isfinite(v); };
if (!finite(pose.position.x) || !finite(pose.position.y) || !finite(pose.position.z) ||
!finite(pose.orientation.x) || !finite(pose.orientation.y) ||
!finite(pose.orientation.z) || !finite(pose.orientation.w)) {
if (error_msg) *error_msg = "Pose contains NaN/Inf";
return false;
}
const double qn2 = pose.orientation.x * pose.orientation.x +
pose.orientation.y * pose.orientation.y +
pose.orientation.z * pose.orientation.z +
pose.orientation.w * pose.orientation.w;
if (qn2 < 1e-8) {
if (error_msg) *error_msg = "Quaternion norm is too small";
return false;
}
return true;
}
bool DualArmAction::export_and_normalize_trajectory_(
const MotionParameters& motion_params,
trajectory_msgs::msg::JointTrajectory* out,
@@ -620,52 +739,117 @@ void DualArmAction::execute_dual_arm_(
std::vector<double> left_target;
std::vector<double> right_target;
std::string err;
if (!collect_movej_targets_(*goal, ARM_DOF, &need_left, &left_target, &need_right, &right_target, &err)) {
const ArmMotionParams* left_param = nullptr;
const ArmMotionParams* right_param = nullptr;
bool has_movel = false;
for (const auto& arm_param : goal->arm_motion_params) {
if (arm_param.arm_id == ArmMotionParams::ARM_LEFT) {
left_param = &arm_param;
} else if (arm_param.arm_id == ArmMotionParams::ARM_RIGHT) {
right_param = &arm_param;
}
if (arm_param.motion_type == arm_param.MOVEL) {
has_movel = true;
}
}
if (!left_param || !right_param) {
planner_busy_.store(false);
result->success = false;
result->message = std::string("DualArm goal invalid: ") + err;
result->message = "DualArm goal invalid: missing left or right arm params";
goal_handle->abort(result);
return;
}
if (!need_left || !need_right) {
planner_busy_.store(false);
result->success = false;
result->message = "DualArm goal invalid: dual-arm requires both left and right targets";
goal_handle->abort(result);
return;
}
const double velocity_scaling = accepted_velocity_scale_;
trajectory_msgs::msg::JointTrajectory traj_left;
trajectory_msgs::msg::JointTrajectory traj_right;
double total_time = 0.0;
if (!ctx_.robot_control_manager->PlanDualArmJointMotion(
need_left, left_target, need_right, right_target,
velocity_scaling, acceleration_scaling))
{
planner_busy_.store(false);
result->success = false;
result->message = "DualArm MoveIt planning failed (dual_arm group)";
goal_handle->abort(result);
return;
if (!has_movel) {
if (!collect_dual_arm_joint_targets_(
*goal, ARM_DOF, &need_left, &left_target, &need_right, &right_target, &err)) {
planner_busy_.store(false);
result->success = false;
result->message = std::string("DualArm goal invalid: ") + err;
goal_handle->abort(result);
return;
}
if (!need_left || !need_right) {
planner_busy_.store(false);
result->success = false;
result->message = "DualArm goal invalid: dual-arm requires both left and right targets";
goal_handle->abort(result);
return;
}
if (!ctx_.robot_control_manager->PlanDualArmJointMotion(
need_left, left_target, need_right, right_target,
velocity_scaling, acceleration_scaling))
{
planner_busy_.store(false);
result->success = false;
result->message = "DualArm MoveIt planning failed (dual_arm group, MOVEJ)";
goal_handle->abort(result);
return;
}
trajectory_msgs::msg::JointTrajectory traj;
if (!export_and_normalize_trajectory_(motion_params, &traj, &total_time, &err)) {
planner_busy_.store(false);
result->success = false;
result->message = std::string("Export/normalize trajectory failed: ") + err;
goal_handle->abort(result);
return;
}
split_dual_arm_trajectory_(motion_params, ARM_DOF, traj, &traj_left, &traj_right);
} else {
// MOVEL 需要保持笛卡尔直线路径:分别对左右臂做单臂笛卡尔/关节规划,再统一时间轴同步执行
if (!ctx_.robot_control_manager->PlanArmMotion(
*left_param, velocity_scaling, acceleration_scaling)) {
planner_busy_.store(false);
result->success = false;
result->message = "DualArm planning failed: left arm PlanArmMotion failed";
goal_handle->abort(result);
return;
}
if (!ctx_.robot_control_manager->PlanArmMotion(
*right_param, velocity_scaling, acceleration_scaling)) {
planner_busy_.store(false);
result->success = false;
result->message = "DualArm planning failed: right arm PlanArmMotion failed";
goal_handle->abort(result);
return;
}
trajectory_msgs::msg::JointTrajectory left_raw;
trajectory_msgs::msg::JointTrajectory right_raw;
if (!ctx_.robot_control_manager->ExportArmPlannedTrajectory(ArmMotionParams::ARM_LEFT, left_raw) ||
!ctx_.robot_control_manager->ExportArmPlannedTrajectory(ArmMotionParams::ARM_RIGHT, right_raw)) {
planner_busy_.store(false);
result->success = false;
result->message = "DualArm planning failed: export single-arm trajectory failed";
goal_handle->abort(result);
return;
}
if (!synchronize_dual_arm_trajectory_timebase(
left_raw, right_raw, &traj_left, &traj_right, &total_time, &err,
movel_sync_use_s_curve_progress_,
movel_sync_min_dt_sec_,
static_cast<size_t>(movel_sync_max_samples_))) {
planner_busy_.store(false);
result->success = false;
result->message = std::string("DualArm MOVEL sync failed: ") + err;
goal_handle->abort(result);
return;
}
}
planner_busy_.store(false);
feedback->status = 1;
trajectory_msgs::msg::JointTrajectory traj;
double total_time = 0.0;
if (!export_and_normalize_trajectory_(motion_params, &traj, &total_time, &err)) {
result->success = false;
result->message = std::string("Export/normalize trajectory failed: ") + err;
goal_handle->abort(result);
return;
}
trajectory_msgs::msg::JointTrajectory traj_left;
trajectory_msgs::msg::JointTrajectory traj_right;
split_dual_arm_trajectory_(motion_params, ARM_DOF, traj, &traj_left, &traj_right);
ensure_nonzero_start_time(&traj_left, 0.05);
ensure_nonzero_start_time(&traj_right, 0.05);
total_time = std::max(trajectory_total_time_sec_(traj_left), trajectory_total_time_sec_(traj_right));
save_trajectory_to_file_(traj_left, "dual_left");
save_trajectory_to_file_(traj_right, "dual_right");
if (!trajectory_execution_enabled_) {

View File

@@ -12,6 +12,7 @@
#include "moveit/move_group_interface/move_group_interface.h"
#include "moveit/robot_state/robot_state.h"
#include "moveit_msgs/msg/move_it_error_codes.hpp"
#include "moveit_msgs/msg/robot_trajectory.hpp"
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -286,19 +287,37 @@ bool ArmControl::PlanCartesianMotion(
move_group->setMaxVelocityScalingFactor(velocity_scaling);
move_group->setMaxAccelerationScalingFactor(acceleration_scaling);
// 设置目标位姿
move_group->setPoseTarget(target_pose);
// 使用 computeCartesianPath 生成真实笛卡尔直线路径(而非仅末端位姿目标)
std::vector<geometry_msgs::msg::Pose> waypoints;
waypoints.reserve(2);
waypoints.push_back(move_group->getCurrentPose().pose);
waypoints.push_back(target_pose);
// 规划
moveit::planning_interface::MoveGroupInterface::Plan plan;
moveit::core::MoveItErrorCode error_code = move_group->plan(plan);
moveit_msgs::msg::RobotTrajectory cartesian_traj;
constexpr double kEefStep = 0.01; // 1cm 离散步长
constexpr double kJumpThreshold = 0.0; // 禁用 jump 判据
const double fraction = move_group->computeCartesianPath(
waypoints, kEefStep, kJumpThreshold, cartesian_traj, true);
if (error_code != moveit::core::MoveItErrorCode::SUCCESS) {
if (fraction < 0.999) {
if (node_) {
RCLCPP_ERROR(node_->get_logger(), "PlanCartesianMotion failed: error_code=%d arm_id=%d", error_code.val, static_cast<int>(arm_id));
RCLCPP_ERROR(
node_->get_logger(),
"PlanCartesianMotion failed: Cartesian path fraction=%.3f arm_id=%d",
fraction,
static_cast<int>(arm_id));
}
return false;
}
if (cartesian_traj.joint_trajectory.points.empty()) {
if (node_) {
RCLCPP_ERROR(node_->get_logger(), "PlanCartesianMotion failed: empty cartesian trajectory arm_id=%d", static_cast<int>(arm_id));
}
return false;
}
moveit::planning_interface::MoveGroupInterface::Plan plan;
plan.trajectory_ = cartesian_traj;
return timeParameterizeAndStore_(
arm_id, plan, velocity_scaling, acceleration_scaling);
} catch (const std::exception& e) {