自动推导URDF运动学链

This commit is contained in:
zhangshun
2026-06-16 12:34:17 +08:00
parent a3efcd66a5
commit c2d79a74b9
3 changed files with 149 additions and 3 deletions

View File

@@ -8,6 +8,7 @@
#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>
#include <kdl/tree.hpp>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -34,6 +35,10 @@ private:
int minSteps, int maxSteps, int minSteps, int maxSteps,
double positionResolution, double orientationResolution); double positionResolution, double orientationResolution);
bool parseJointChildLinkUuidsFromUrdf(const std::string &urdfString); bool parseJointChildLinkUuidsFromUrdf(const std::string &urdfString);
// 自动收集 KDL Tree 中没有子节点的末端 link 名称。
std::vector<std::string> collectLeafSegmentNames(const KDL::Tree &tree) const;
// 自动选择可用于正逆运动学的 6 轴串联链,避免固定依赖 base/tool0 命名。
bool selectKinematicChain(const KDL::Tree &tree, KDL::Chain &selectedChain) const;
public: public:
/** /**

View File

@@ -142,6 +142,51 @@ function buildInverseRequest(command, robotUuid, poseStr, qInitStr, extra = {})
}; };
} }
// 构造 link 名称被整体替换的 URDF用于验证初始化能自动推导运动学链。
function buildRenamedUrdfFromDocs() {
const urdfPath = path.join(rootDir, "docs", "urdf.xml");
return fs
.readFileSync(urdfPath, "utf8")
.replaceAll("base_link-base", "renamed_root_link-renamed_world")
.replaceAll("base_link", "renamed_root_link")
.replaceAll("tool0", "renamed_tcp")
.replaceAll('link name="base"', 'link name="renamed_world"')
.replaceAll('parent link="base"', 'parent link="renamed_world"');
}
// 注册改名后的 URDF 并执行一次 FK确认底层不再依赖固定 base/tool0。
async function runRenamedUrdfChainCase(module) {
const robotUuid = "renamed_chain_robot";
const urdfBase64 = Buffer.from(buildRenamedUrdfFromDocs(), "utf8").toString("base64");
const initResponse = callBusinessApi(module, {
msg: "init renamed urdf",
req_code: "AUTO_RENAMED_INIT",
req_from: "wasm_test",
req_cmd: "Cmd_InitRobot",
req_param: {
robot_uuid: robotUuid,
urdf_base64: urdfBase64,
force_update: true,
},
});
ensure(initResponse.success === true, "renamed URDF init request failed");
ensure(getByPath(initResponse, "res_data.success") === true, "renamed URDF init result is not successful");
const forwardResponse = callBusinessApi(module, buildForwardRequest(robotUuid, [0, 0, 0, 0, 0, 0]));
ensure(forwardResponse.success === true, "renamed URDF forward request failed");
ensure(getByPath(forwardResponse, "res_data.success") === true, "renamed URDF forward result is not successful");
ensure(Array.isArray(getByPath(forwardResponse, "res_data.position")), "renamed URDF forward position is missing");
return {
details: {
position: getByPath(forwardResponse, "res_data.position"),
orientation: getByPath(forwardResponse, "res_data.orientation"),
},
};
}
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);
@@ -353,6 +398,10 @@ const suite = [
{ path: "res_data.OPERATION.OPERATION.frames", lengthEquals: 2 }, { path: "res_data.OPERATION.OPERATION.frames", lengthEquals: 2 },
], ],
}, },
{
id: "kinematics_renamed_urdf_chain",
type: "renamed_urdf_chain",
},
{ {
id: "spc_basic_5x30", id: "spc_basic_5x30",
type: "static", type: "static",
@@ -522,6 +571,8 @@ async function runCase(module, testCase) {
return runRoundtripSingleCase(module, testCase); return runRoundtripSingleCase(module, testCase);
case "roundtrip_path": case "roundtrip_path":
return runRoundtripPathCase(module, testCase); return runRoundtripPathCase(module, testCase);
case "renamed_urdf_chain":
return runRenamedUrdfChainCase(module, testCase);
default: default:
throw new Error(`Unknown test type: ${testCase.type}`); throw new Error(`Unknown test type: ${testCase.type}`);
} }

View File

@@ -11,6 +11,7 @@
#include <kdl/trajectory_segment.hpp> #include <kdl/trajectory_segment.hpp>
#include <kdl/rotational_interpolation_sa.hpp> #include <kdl/rotational_interpolation_sa.hpp>
#include <regex> #include <regex>
#include <vector>
using namespace KDL; using namespace KDL;
@@ -38,11 +39,101 @@ Robot::~Robot()
delete ikSolverLMA; delete ikSolverLMA;
} }
// 自动收集 KDL Tree 中的叶子节点,用作自动推导运动学末端候选。
std::vector<std::string> Robot::collectLeafSegmentNames(const KDL::Tree &tree) const
{
std::vector<std::string> leafSegmentNames;
const auto &segments = tree.getSegments();
for (const auto &segmentPair : segments)
{
if (GetTreeElementChildren(segmentPair.second).empty())
{
leafSegmentNames.push_back(segmentPair.first);
}
}
return leafSegmentNames;
}
// 自动选择运动学链:优先兼容旧命名,失败后从 URDF 根节点推导 6 轴叶子链。
bool Robot::selectKinematicChain(const KDL::Tree &tree, KDL::Chain &selectedChain) const
{
KDL::Chain legacyChain;
if (tree.getChain("base", "tool0", legacyChain) && legacyChain.getNrOfJoints() == 6)
{
selectedChain = legacyChain;
std::cout << "Selected kinematic chain by legacy names: base -> tool0" << std::endl;
return true;
}
const auto &segments = tree.getSegments();
auto rootSegment = tree.getRootSegment();
if (rootSegment == segments.end())
{
std::cerr << "Failed to infer kinematic chain: tree root segment not found" << std::endl;
return false;
}
const std::string rootName = rootSegment->first;
const std::vector<std::string> leafSegmentNames = collectLeafSegmentNames(tree);
bool foundChain = false;
std::string selectedTipName;
unsigned int selectedSegmentCount = 0;
for (const auto &tipName : leafSegmentNames)
{
KDL::Chain candidateChain;
if (!tree.getChain(rootName, tipName, candidateChain))
{
continue;
}
if (candidateChain.getNrOfJoints() != 6)
{
continue;
}
// 多个 6 轴叶子链并存时,优先选择段数最多的完整工具链。
if (!foundChain || candidateChain.getNrOfSegments() > selectedSegmentCount)
{
selectedChain = candidateChain;
selectedTipName = tipName;
selectedSegmentCount = candidateChain.getNrOfSegments();
foundChain = true;
}
}
if (!foundChain)
{
std::cerr << "Failed to infer a 6-joint kinematic chain from root segment: "
<< rootName << std::endl;
return false;
}
std::cout << "Selected kinematic chain by inference: " << rootName
<< " -> " << selectedTipName
<< " (" << selectedChain.getNrOfJoints() << " joints, "
<< selectedChain.getNrOfSegments() << " segments)" << std::endl;
return true;
}
// 根据 URDF 初始化机器人,并提取关节与 child link 的 UUID 映射。 // 根据 URDF 初始化机器人,并提取关节与 child link 的 UUID 映射。
bool Robot::initRobot(const std::string &urdfString) bool Robot::initRobot(const std::string &urdfString)
{ {
try try
{ {
delete fkSolver;
delete ikVelSolver;
delete ikSolverNR;
delete ikSolverLMA;
fkSolver = nullptr;
ikVelSolver = nullptr;
ikSolverNR = nullptr;
ikSolverLMA = nullptr;
m_initialized = false;
kinematicChain = KDL::Chain();
KDL::Tree tree; KDL::Tree tree;
// 从 URDF 字符串解析 KDL Tree。 // 从 URDF 字符串解析 KDL Tree。
@@ -52,10 +143,9 @@ bool Robot::initRobot(const std::string &urdfString)
return false; return false;
} }
// 提取 basetool0 的运动学链 // 自动选择可用运动学链,避免固定依赖 base/tool0 命名
if (!tree.getChain("base", "tool0", kinematicChain)) if (!selectKinematicChain(tree, kinematicChain))
{ {
std::cerr << "Failed to get chain from base to tool0" << std::endl;
return false; return false;
} }