☰
从零造一个腱绳驱动灵巧手——仿真器篇1:选型、架构与第一行代码
2026/11/3 6:45:33 网站建设 项目流程

一、我为什么要做这个?

先说背景,我是一个本科毕设选手,2027 年就毕业了,学的软件工程技术。毕设课题是“手”。

要做就做个好的嘛,用市面上现成的那种 6DOF 或者 10DOF 的机械手,我觉得没有什么意义,要做就做一个没人做过的东西。

其实我的进度比文章的进度要快,在这个过程中我遇到了很多困难,哪怕借助 AI 去解决,仍然有很多问题。这个系列的文章是为了向后来者提供一些参考,也是为了鼓励自己持续完成分享。

当然,也是为了进入一家更好的公司,向自己的未来奋斗。

顺便一提,我是理科生嘛,文章写得不是很好,文章有很多是 AI 润色的,希望大家不要介意哈。

文章中的代码只是节选,全部代码我会开源到GitHub,等我做完毕设,整理一下就开源。到时候也会编辑一下文章,地址贴在这里。

这个灵巧手简单说就是:有一个机械手(五根手指、一堆关节),我要先做一个桌面软件,可以:

- 加载各零件的 STL 模型,在 3D 里组装成一只手
- 给关节插上旋转轴,拖动关节角,手指跟着弯
- 设置关节限位(比如食指只能弯 -120° 到 0°)
- 用姿势串驱动多个关节,做握拳、比 OK、张开等手势
- 后续还要接腱绳映射和简化动力学

听起来是不是像一个“小型 Gazebo”?差不多。但区别在于——现有的引擎一个都不用,全部自己做。

为什么?因为赵老师说:“我在软件、仿真、框架上偏弱”。

做着的时候我发现,这些其实是最好的架构师。它们逼我分层、逼我解耦、逼我理解每一个矩阵乘法在干什么。毫不夸张地说,这个项目中所学到的东西,可能比我大学四年中学到的东西都要多。


二、技术选型:为什么是 Qt + VTK + Eigen?

选型这一步,我在“用现成的”和“自己造”之间反复横跳了三天。最后的选择如下:

层选型为什么不用别的
数学Eigen 3.4矩阵、四元数、最小二乘全齐了。不选 GLM(太图形向)、不选 Armadillo(太重)。Eigen 是头文件库,零链接,完美
渲染VTK 9.3STL 加载、三角网格渲染、拾取(ray casting)、相机交互全都有。不选 OpenGL 裸写(造轮子太苦)、不选 Qt3D(Qt 6.8 已弃用)、不选 Assimp+OpenGL(交互层全自己写)
曲线QCustomPlot(后续接入)轻量、纯 Qt、不依赖 OpenGL
构建CMake 3.30 + Ninja + MinGW 13.1全家桶都在QT路径下,一条 PATH 搞定

为什么坚持 core/ 不碰 Qt 和 VTK?

这是整个项目最重要的一条架构纪律。我的core/目录编译出来是一个纯 C++ 静态库,只依赖 Eigen:

// core/types.hpp —— 只有数据定义,没有一行 Qt / VTK #include <Eigen/Core> #include <Eigen/Geometry> namespace hand_core { using Vec3 = Eigen::Vector3d; using Transform = Eigen::Isometry3d; // 刚体变换:旋转 + 平移 struct Joint { /* ... */ }; struct Link { /* ... */ }; // ... }

这意味着什么?意味着我可以“脱离界面”写一个命令行测试程序来验证运动学:

// _fk_test.cpp —— 无 GUI,纯数值验证 #include <core/kinematics.hpp> #include <cstdio> int main() { hand_core::Tree tree; // ... 建几个节点,设关节角 ... auto poses = hand_core::forwardKinematics(tree); std::printf("FK 计算完成,共 %zu 个连杆\n", poses.size()); }

不用启动 Qt、不用开窗口、不用管 OpenGL 上下文,一行g++编译就能跑。

测试速度快 100 倍,调试痛苦少 100000000 倍。


三、架构分层:五层蛋糕

项目长这样:
hand_sim/
├── core/ 纯 C++17 + Eigen:运动学、树、质量特性、几何包围体
├── model/ JSON 工程文件读写(nlohmann/json vendored)
├── render/ VTK 渲染:STL 加载、LinkActor、场景、相机交互
├── ui/ Qt Widgets:主窗口、属性面板、树控件、视口封装
├── plot/ 曲线显示(规划中)
├── 3rdparty/ Eigen 3.4.0 + nlohmann/json 3.11.3(vendored)
└── build/ 构建产物 + 测试程序

依赖方向严格单向:
ui → render → core
ui → model → core

core 不知道上层的存在。它只管算矩阵,不管矩阵算完后是画在屏幕上还是存进文件。


四、数据模型:一棵树统治一切

灵巧手的运动学本质是一棵树——手掌是根,手指是子树,指尖是叶子。每个节点由一个Joint(关节)和一个Link(连杆)组成:

// core/tree.hpp struct TreeNode { int id = -1; int parent = -1; // -1 = 根;-2 = 已删除 Joint joint; Link link; };

其中 Joint 描述“这个零件怎么动”:

struct Joint { JointType type = JointType::Fixed; // Fixed(焊死)或 Revolute(绕轴转) Transform origin = Transform::Identity(); // joint frame 相对父连杆的固定变换 Vec3 axis = Vec3::UnitZ(); // 转轴方向(定义在 joint frame 里) JointLimit limit; // 关节限位 [lower, upper] JointMimic mimic; // 关节角联动 };

关键约定(跟 URDF 一模一样):

T_link = T_parent · joint.origin · Rot(axis, q)

翻译成人话:一个连杆的位置 = 爸爸的位置 × 我出生时的位移 × 我绕自己轴转了多少度。

就这么简单。但就是这么简单的公式,驱动了整只手的运动。


五、正向运动学:10 行代码让手动起来

FK(Forward Kinematics)是整个仿真器的心脏。它回答一个问题:“给定每个关节的旋转角度,每个零件在哪里?”

// core/kinematics.cpp —— 核心中的核心 Transform linkPose(const Tree& tree, int node_id) { // 从当前节点一路爬到根节点 const std::vector<int> chain = tree.chainToRoot(node_id); Transform T = Transform::Identity(); // 从根侧开始累乘 for (auto it = chain.rbegin(); it != chain.rend(); ++it) { const TreeNode* n = tree.node(*it); if (!n || n-&gt;parent == -2) break; // T = T · (关节固定变换 · 关节旋转) T = T * (n-&gt;joint.origin * jointMotion(*n, nodeQ(tree, n-&gt;id))); } return T; }

其中jointMotion负责“绕轴转 q 度”:

Transform jointMotion(const TreeNode& n, double q) { if (n.joint.type != JointType::Revolute) return Transform::Identity(); Transform R = Transform::Identity(); R.linear() = Eigen::AngleAxisd(q, n.joint.axis).toRotationMatrix(); return R; }

注意一个设计红线:

限位(limit)不在 FK 里截断!FK 只管计算T_parent · origin · Rot(axis, q),至于 q 该不该超限,那是积分器层的事。

为什么?因为如果你在 FK 里偷偷把 q 截断了,那么仿真步进、轨迹插值、逆运动学求解器……所有上游调用者都被你坑了。


六、配一种“绿色无污染”的构建方式

工具链安装完毕后,编译一行搞定:

# PowerShell(注意:Qt/MinGW 不在默认 PATH,要手动加) $env:PATH = "C:\Qt\Tools\mingw1310_64\bin;C:\Qt\Tools\CMake_64\bin;C:\Qt\Tools\Ninja;$env:PATH" cd hand_sim/build ninja hand_sim

测试也是一行:

.\_fk_test.exe # 验证正向运动学 .\_bind_test.exe # 验证几何绑定 .\_mimic_test.exe # 验证关节联动

先测试,后界面。

每个数值测试都是独立的 .exe,跑一次不到 1 秒。

比起“打开软件、点击、截图、放大看像素”的传统验证方式,效率高到飞起。


咱们下篇见。👋

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询