启用机器人关节上下限检查
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#include <kdl/chainfksolverpos_recursive.hpp>
|
#include <kdl/chainfksolverpos_recursive.hpp>
|
||||||
#include <kdl/chainiksolverpos_lma.hpp>
|
#include <kdl/chainiksolverpos_lma.hpp>
|
||||||
#include <kdl/chainiksolverpos_nr.hpp>
|
#include <kdl/chainiksolverpos_nr.hpp>
|
||||||
|
#include <kdl/chainiksolverpos_nr_jl.hpp>
|
||||||
#include <kdl/chainiksolvervel_pinv.hpp>
|
#include <kdl/chainiksolvervel_pinv.hpp>
|
||||||
#include <kdl/frames.hpp>
|
#include <kdl/frames.hpp>
|
||||||
#include <kdl/jntarray.hpp>
|
#include <kdl/jntarray.hpp>
|
||||||
@@ -22,9 +23,13 @@ private:
|
|||||||
KDL::Chain kinematicChain;
|
KDL::Chain kinematicChain;
|
||||||
KDL::ChainFkSolverPos_recursive *fkSolver;
|
KDL::ChainFkSolverPos_recursive *fkSolver;
|
||||||
KDL::ChainIkSolverVel_pinv *ikVelSolver;
|
KDL::ChainIkSolverVel_pinv *ikVelSolver;
|
||||||
KDL::ChainIkSolverPos_NR *ikSolverNR;
|
KDL::ChainIkSolverPos_NR_JL *ikSolverNR;
|
||||||
KDL::ChainIkSolverPos_LMA *ikSolverLMA;
|
KDL::ChainIkSolverPos_LMA *ikSolverLMA;
|
||||||
bool m_initialized;
|
bool m_initialized;
|
||||||
|
KDL::JntArray jointLowerLimits;
|
||||||
|
KDL::JntArray jointUpperLimits;
|
||||||
|
std::vector<std::string> activeJointNames;
|
||||||
|
bool m_hasJointLimits;
|
||||||
|
|
||||||
// 记录 joint 名称到 child link UUID 的映射,便于前端回写对象姿态。
|
// 记录 joint 名称到 child link UUID 的映射,便于前端回写对象姿态。
|
||||||
std::unordered_map<std::string, std::string> jointChildLinkUuidMap;
|
std::unordered_map<std::string, std::string> jointChildLinkUuidMap;
|
||||||
@@ -39,6 +44,12 @@ private:
|
|||||||
std::vector<std::string> collectLeafSegmentNames(const KDL::Tree &tree) const;
|
std::vector<std::string> collectLeafSegmentNames(const KDL::Tree &tree) const;
|
||||||
// 自动选择可用于正逆运动学的 6 轴串联链,避免固定依赖 base/tool0 命名。
|
// 自动选择可用于正逆运动学的 6 轴串联链,避免固定依赖 base/tool0 命名。
|
||||||
bool selectKinematicChain(const KDL::Tree &tree, KDL::Chain &selectedChain) const;
|
bool selectKinematicChain(const KDL::Tree &tree, KDL::Chain &selectedChain) const;
|
||||||
|
// 从 URDF 中提取当前运动学链的关节上下限。
|
||||||
|
bool parseJointLimitsFromUrdf(const std::string &urdfString);
|
||||||
|
// 检查关节数组是否处于 URDF 定义的上下限范围内。
|
||||||
|
bool validateJointLimits(const double joints[6], const std::string &context) const;
|
||||||
|
// 检查关节向量是否处于 URDF 定义的上下限范围内。
|
||||||
|
bool validateJointLimits(const std::vector<double> &joints, const std::string &context) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -187,6 +187,14 @@ async function runRenamedUrdfChainCase(module) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送超过 URDF 关节上下限的 FK 请求,确认底层会拒绝计算。
|
||||||
|
async function runJointLimitCase(module) {
|
||||||
|
const response = callBusinessApi(module, buildForwardRequest("abb_irb120_3_58", [7, 0, 0, 0, 0, 0]));
|
||||||
|
ensure(response.success === false, "joint limit case should fail at response level");
|
||||||
|
ensure(String(getByPath(response, "res_data.error")).includes("Forward kinematics calculation failed"), "joint limit error is missing");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
function assertStaticCase(response, assertions) {
|
function assertStaticCase(response, assertions) {
|
||||||
for (const assertion of assertions) {
|
for (const assertion of assertions) {
|
||||||
const actual = getByPath(response, assertion.path);
|
const actual = getByPath(response, assertion.path);
|
||||||
@@ -402,6 +410,10 @@ const suite = [
|
|||||||
id: "kinematics_renamed_urdf_chain",
|
id: "kinematics_renamed_urdf_chain",
|
||||||
type: "renamed_urdf_chain",
|
type: "renamed_urdf_chain",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "kinematics_joint_limit_rejects_fk",
|
||||||
|
type: "joint_limit",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "spc_basic_5x30",
|
id: "spc_basic_5x30",
|
||||||
type: "static",
|
type: "static",
|
||||||
@@ -573,6 +585,8 @@ async function runCase(module, testCase) {
|
|||||||
return runRoundtripPathCase(module, testCase);
|
return runRoundtripPathCase(module, testCase);
|
||||||
case "renamed_urdf_chain":
|
case "renamed_urdf_chain":
|
||||||
return runRenamedUrdfChainCase(module, testCase);
|
return runRenamedUrdfChainCase(module, testCase);
|
||||||
|
case "joint_limit":
|
||||||
|
return runJointLimitCase(module, testCase);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown test type: ${testCase.type}`);
|
throw new Error(`Unknown test type: ${testCase.type}`);
|
||||||
}
|
}
|
||||||
|
|||||||
156
src/Robot.cpp
156
src/Robot.cpp
@@ -3,6 +3,7 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <kdl_parser/kdl_parser.hpp>
|
#include <kdl_parser/kdl_parser.hpp>
|
||||||
#include <kdl/tree.hpp>
|
#include <kdl/tree.hpp>
|
||||||
|
#include <urdf_parser/urdf_parser.h>
|
||||||
|
|
||||||
// #include <cmath>
|
// #include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include <kdl/velocityprofile_trap.hpp>
|
#include <kdl/velocityprofile_trap.hpp>
|
||||||
#include <kdl/trajectory_segment.hpp>
|
#include <kdl/trajectory_segment.hpp>
|
||||||
#include <kdl/rotational_interpolation_sa.hpp>
|
#include <kdl/rotational_interpolation_sa.hpp>
|
||||||
|
#include <limits>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ using namespace KDL;
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
Robot::Robot() : m_initialized(false), fkSolver(nullptr), ikVelSolver(nullptr),
|
Robot::Robot() : m_initialized(false), fkSolver(nullptr), ikVelSolver(nullptr),
|
||||||
ikSolverNR(nullptr), ikSolverLMA(nullptr)
|
ikSolverNR(nullptr), ikSolverLMA(nullptr), m_hasJointLimits(false)
|
||||||
{
|
{
|
||||||
// 构造函数中显式初始化全部求解器指针。
|
// 构造函数中显式初始化全部求解器指针。
|
||||||
}
|
}
|
||||||
@@ -26,7 +28,8 @@ Robot::Robot(const std::string &urdfString) : m_initialized(false),
|
|||||||
fkSolver(nullptr),
|
fkSolver(nullptr),
|
||||||
ikVelSolver(nullptr),
|
ikVelSolver(nullptr),
|
||||||
ikSolverNR(nullptr),
|
ikSolverNR(nullptr),
|
||||||
ikSolverLMA(nullptr)
|
ikSolverLMA(nullptr),
|
||||||
|
m_hasJointLimits(false)
|
||||||
{
|
{
|
||||||
initRobot(urdfString);
|
initRobot(urdfString);
|
||||||
}
|
}
|
||||||
@@ -118,6 +121,116 @@ bool Robot::selectKinematicChain(const KDL::Tree &tree, KDL::Chain &selectedChai
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 URDF 模型中读取当前活动关节的 lower/upper 限位。
|
||||||
|
bool Robot::parseJointLimitsFromUrdf(const std::string &urdfString)
|
||||||
|
{
|
||||||
|
activeJointNames.clear();
|
||||||
|
jointLowerLimits = KDL::JntArray(kinematicChain.getNrOfJoints());
|
||||||
|
jointUpperLimits = KDL::JntArray(kinematicChain.getNrOfJoints());
|
||||||
|
m_hasJointLimits = false;
|
||||||
|
|
||||||
|
urdf::ModelInterfaceSharedPtr robotModel = urdf::parseURDF(urdfString);
|
||||||
|
if (!robotModel)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to parse URDF model for joint limits" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int jointIndex = 0;
|
||||||
|
for (unsigned int segmentIndex = 0; segmentIndex < kinematicChain.getNrOfSegments(); segmentIndex++)
|
||||||
|
{
|
||||||
|
const KDL::Joint &joint = kinematicChain.getSegment(segmentIndex).getJoint();
|
||||||
|
if (joint.getType() == KDL::Joint::Fixed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &jointName = joint.getName();
|
||||||
|
urdf::JointConstSharedPtr urdfJoint = robotModel->getJoint(jointName);
|
||||||
|
if (!urdfJoint)
|
||||||
|
{
|
||||||
|
std::cerr << "Joint limit missing: joint not found in URDF: " << jointName << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urdfJoint->type == urdf::Joint::CONTINUOUS)
|
||||||
|
{
|
||||||
|
jointLowerLimits(jointIndex) = -std::numeric_limits<double>::infinity();
|
||||||
|
jointUpperLimits(jointIndex) = std::numeric_limits<double>::infinity();
|
||||||
|
}
|
||||||
|
else if (urdfJoint->limits)
|
||||||
|
{
|
||||||
|
jointLowerLimits(jointIndex) = urdfJoint->limits->lower;
|
||||||
|
jointUpperLimits(jointIndex) = urdfJoint->limits->upper;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Joint limit missing: <limit> not found for joint: " << jointName << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jointLowerLimits(jointIndex) > jointUpperLimits(jointIndex))
|
||||||
|
{
|
||||||
|
std::cerr << "Joint limit invalid for joint " << jointName
|
||||||
|
<< ": lower is greater than upper" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeJointNames.push_back(jointName);
|
||||||
|
jointIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jointIndex != kinematicChain.getNrOfJoints())
|
||||||
|
{
|
||||||
|
std::cerr << "Joint limit count mismatch, expected "
|
||||||
|
<< kinematicChain.getNrOfJoints() << ", got " << jointIndex << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hasJointLimits = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验固定数组关节值是否处于 URDF 上下限内。
|
||||||
|
bool Robot::validateJointLimits(const double joints[6], const std::string &context) const
|
||||||
|
{
|
||||||
|
if (!m_hasJointLimits)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr double tolerance = 1e-9;
|
||||||
|
for (unsigned int i = 0; i < jointLowerLimits.rows(); i++)
|
||||||
|
{
|
||||||
|
const double value = joints[i];
|
||||||
|
const double lower = jointLowerLimits(i);
|
||||||
|
const double upper = jointUpperLimits(i);
|
||||||
|
|
||||||
|
if (value < lower - tolerance || value > upper + tolerance)
|
||||||
|
{
|
||||||
|
const std::string jointName = i < activeJointNames.size() ? activeJointNames[i] : ("joint_" + std::to_string(i + 1));
|
||||||
|
std::cerr << context << " joint limit violation: " << jointName
|
||||||
|
<< " value=" << value
|
||||||
|
<< ", range=[" << lower << ", " << upper << "]" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验 vector 关节值是否处于 URDF 上下限内。
|
||||||
|
bool Robot::validateJointLimits(const std::vector<double> &joints, const std::string &context) const
|
||||||
|
{
|
||||||
|
if (joints.size() != 6)
|
||||||
|
{
|
||||||
|
std::cerr << context << " joint count invalid: " << joints.size() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateJointLimits(joints.data(), context);
|
||||||
|
}
|
||||||
|
|
||||||
// 根据 URDF 初始化机器人,并提取关节与 child link 的 UUID 映射。
|
// 根据 URDF 初始化机器人,并提取关节与 child link 的 UUID 映射。
|
||||||
bool Robot::initRobot(const std::string &urdfString)
|
bool Robot::initRobot(const std::string &urdfString)
|
||||||
{
|
{
|
||||||
@@ -133,6 +246,8 @@ bool Robot::initRobot(const std::string &urdfString)
|
|||||||
ikSolverLMA = nullptr;
|
ikSolverLMA = nullptr;
|
||||||
m_initialized = false;
|
m_initialized = false;
|
||||||
kinematicChain = KDL::Chain();
|
kinematicChain = KDL::Chain();
|
||||||
|
activeJointNames.clear();
|
||||||
|
m_hasJointLimits = false;
|
||||||
|
|
||||||
KDL::Tree tree;
|
KDL::Tree tree;
|
||||||
|
|
||||||
@@ -157,6 +272,12 @@ bool Robot::initRobot(const std::string &urdfString)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 URDF 提取关节上下限,供 FK 输入和 IK 输出统一校验。
|
||||||
|
if (!parseJointLimitsFromUrdf(urdfString))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 解析 joint 到 child link UUID 的映射。
|
// 解析 joint 到 child link UUID 的映射。
|
||||||
if (!parseJointChildLinkUuidsFromUrdf(urdfString))
|
if (!parseJointChildLinkUuidsFromUrdf(urdfString))
|
||||||
{
|
{
|
||||||
@@ -166,7 +287,7 @@ bool Robot::initRobot(const std::string &urdfString)
|
|||||||
// 初始化 FK / IK 求解器。
|
// 初始化 FK / IK 求解器。
|
||||||
fkSolver = new KDL::ChainFkSolverPos_recursive(kinematicChain);
|
fkSolver = new KDL::ChainFkSolverPos_recursive(kinematicChain);
|
||||||
ikVelSolver = new KDL::ChainIkSolverVel_pinv(kinematicChain);
|
ikVelSolver = new KDL::ChainIkSolverVel_pinv(kinematicChain);
|
||||||
ikSolverNR = new KDL::ChainIkSolverPos_NR(kinematicChain, *fkSolver, *ikVelSolver, 100, 1e-6);
|
ikSolverNR = new KDL::ChainIkSolverPos_NR_JL(kinematicChain, jointLowerLimits, jointUpperLimits, *fkSolver, *ikVelSolver, 100, 1e-6);
|
||||||
|
|
||||||
// LMA 求解器按调用时动态构造,便于传入不同收敛参数。
|
// LMA 求解器按调用时动态构造,便于传入不同收敛参数。
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
@@ -405,6 +526,11 @@ bool Robot::calculateIK_NR(const double pose[7], const double iniJ[6], double re
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!validateJointLimits(iniJ, "IK initial joints"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 将输入位姿转换为 KDL Frame。
|
// 将输入位姿转换为 KDL Frame。
|
||||||
KDL::Vector position(pose[0], pose[1], pose[2]);
|
KDL::Vector position(pose[0], pose[1], pose[2]);
|
||||||
KDL::Rotation rotation = KDL::Rotation::Quaternion(pose[3], pose[4], pose[5], pose[6]);
|
KDL::Rotation rotation = KDL::Rotation::Quaternion(pose[3], pose[4], pose[5], pose[6]);
|
||||||
@@ -428,7 +554,7 @@ bool Robot::calculateIK_NR(const double pose[7], const double iniJ[6], double re
|
|||||||
{
|
{
|
||||||
resultJoints[i] = result(i);
|
resultJoints[i] = result(i);
|
||||||
}
|
}
|
||||||
return true;
|
return validateJointLimits(resultJoints, "IK result joints");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -454,6 +580,11 @@ bool Robot::calculateIK_LMA(const double pose[7], const double iniJ[6], double r
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!validateJointLimits(iniJ, "IK initial joints"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 每次调用时动态构造 LMA 求解器,便于传入不同参数。
|
// 每次调用时动态构造 LMA 求解器,便于传入不同参数。
|
||||||
KDL::ChainIkSolverPos_LMA ikSolverLMA(kinematicChain, eps, maxiter, eps_joints);
|
KDL::ChainIkSolverPos_LMA ikSolverLMA(kinematicChain, eps, maxiter, eps_joints);
|
||||||
|
|
||||||
@@ -480,7 +611,7 @@ bool Robot::calculateIK_LMA(const double pose[7], const double iniJ[6], double r
|
|||||||
{
|
{
|
||||||
resultJoints[i] = result(i);
|
resultJoints[i] = result(i);
|
||||||
}
|
}
|
||||||
return true;
|
return validateJointLimits(resultJoints, "IK result joints");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -505,6 +636,11 @@ bool Robot::calculateFK_TCP(const double joints[6], double tcpPose[7])
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!validateJointLimits(joints, "FK input joints"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 将关节数组转换为 KDL 关节对象。
|
// 将关节数组转换为 KDL 关节对象。
|
||||||
KDL::JntArray jointArray(6);
|
KDL::JntArray jointArray(6);
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < 6; i++)
|
||||||
@@ -550,6 +686,11 @@ bool Robot::calculateFK_TCP(const double joints[6], double tcpPose[7])
|
|||||||
// const double joints[6], double jointPoses[42]
|
// const double joints[6], double jointPoses[42]
|
||||||
bool Robot::forwardKinematics(const double inJoint[6], double outFrame[42])
|
bool Robot::forwardKinematics(const double inJoint[6], double outFrame[42])
|
||||||
{
|
{
|
||||||
|
if (!validateJointLimits(inJoint, "FK all joints input"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Frame F_result;
|
Frame F_result;
|
||||||
|
|
||||||
ChainFkSolverPos_recursive fkSolver(kinematicChain);
|
ChainFkSolverPos_recursive fkSolver(kinematicChain);
|
||||||
@@ -603,6 +744,11 @@ bool Robot::calculateFK_AllJointsforwardKinematics(const double joints[6], doubl
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!validateJointLimits(joints, "FK all joints input"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 将关节数组转换为 KDL 关节对象。
|
// 将关节数组转换为 KDL 关节对象。
|
||||||
KDL::JntArray jointArray(6);
|
KDL::JntArray jointArray(6);
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < 6; i++)
|
||||||
|
|||||||
Reference in New Issue
Block a user