Skip to content
logeexpluoqiPublic

About

一个简单的、比较通用的机械臂开发库,实现了基于MDH建模的正向运动学解算,基于自适应阻尼最小二乘法的逆运动学解算以及一些机械臂开发过程中的函数功能

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

55 Commits

Folders and files

Repository files navigation

robospace

机器人运动学 C++20 头文件库。基于 Craig 改进 DH(Modified DH)参数描述串联机器人, 提供正运动学、几何雅可比、伪逆雅可比与逆运动学求解,并附完整单元测试与数学推导文档。

  • 头文件实现,无编译产物,#include 即用
  • 仅依赖 Eigen(仓库内 3rd/Eigen 已内置)
  • 关节角度单位可选(角度 / 弧度),长度单位统一为 mm
  • 217 个单元测试(56 个套件,含 6R / 3R / 移动关节 / 7 自由度冗余等机型与批量扫描、契约矩阵、精度交叉),核心模块行覆盖率 >99%

1. 目录结构

robospace/
├── README.md                本文件
├── CMakeLists.txt           顶层构建脚本(可选 demo 示例 + 测试开关)
├── robospace/               运动学库本体(头文件库)
│   ├── robotype.h           基础类型与别名:AngleUnit、PoseVec/JointVec/VecX/MatX、DYNAMIC
│   ├── robomath.hpp         数学工具:角度换算、欧拉角/旋转矩阵/四元数/齐次变换互转、秩、伪逆
│   ├── robolink.hpp         单连杆模型:关节类型、改进 DH 参数、关节状态与限位
│   ├── robochain.hpp        核心:机器人定义(MDH 连杆与关节、限位、关节状态、拓扑、变换缓存)
│   │                        与运动学算法接口(虚函数)及默认算法实现(正运动学 tr / fk、几何雅可比
│   │                        jacob / pinvjacob、逆运动学 ik,位于内部命名空间 robospace::detail,可整体替换,见 §2.4)
│   ├── robodynamic.hpp      动力学与逆动力学(占位,接口规划见文件头)
│   ├── robotensor.hpp       张量工具:Kronecker 积、AX = YB 约束矩阵(手眼标定用)
│   └── robospace.h          聚合头文件:只 include 各模块,不实现任何功能
├── test/                    单元测试(GoogleTest + GoogleMock)
│   ├── CMakeLists.txt       测试与覆盖率构建脚本
│   ├── test_helpers.h       测试便捷包装(避免断言宏与模板逗号冲突)
│   ├── test_models.h        测试模型、独立参考实现、数值雅可比
│   ├── robomath_test.cpp    数学工具测试
│   ├── robolink_test.cpp    连杆测试
│   ├── robochain_test.cpp   运动学链 API / 生命周期 / 虚函数派发与算法外部替代测试
│   ├── kinematics_test.cpp  运动学数值正确性测试(解析解、参考实现、有限差分)
│   ├── ik_test.cpp          逆运动学测试(7 自由度冗余机型)
│   ├── robot_arm6_test.cpp  6 自由度机械臂测试
│   ├── robot_arm3_test.cpp  3 自由度位置控制臂测试(任务维数 3)
│   ├── robot_prismatic_test.cpp 含移动关节的机型测试(直角坐标龙门 / 龙门+手腕)
│   ├── robot_arm7_test.cpp  7 自由度冗余机械臂测试(S-R-S 构型)
│   ├── robolink_stdh_test.cpp 标准 DH → MDH 转换测试(PUMA560 / UR5 / 随机构型)
│   ├── sweep_test.cpp       参数化批量扫描(FK / 雅可比 / IK 成功率与最坏残差)
│   ├── contract_test.cpp    异常与契约矩阵(异常类型与消息关键字)
│   ├── precision_test.cpp   单位 × 精度 × 类型交叉(DEG/RAD × float/double)
│   ├── algorithm_swap_test.cpp 核心算法外部替代测试
│   ├── robotensor_test.cpp  张量工具测试
│   └── gcov_report.py       覆盖率汇总脚本(gcovr 的轻量替代)
├── doc/                     数学原理与算法文档与示例说明(中文,详见 §7)
├── demo/                    独立示例程序与 MuJoCo 可视化(不属于库本体,可整体删除)
│   ├── demo_models.h        **真实机型单一来源**:7 个机型的 DH 表、参数来源与外部核验、初始位姿与工具路径
│   ├── demo_runner.h        示例共用的打印与「初始位姿 -> 工具路径 -> 运动过程」运行器
│   ├── demo_ur5.cpp         Universal Robots UR5(6R,位姿任务)-> 目标 demo_ur5
│   ├── demo_iiwa7.cpp       KUKA LBR iiwa 7 R800(7R 冗余)+ 其前 3 关节子链(demo_iiwa7)
│   ├── demo_puma560.cpp     Unimation PUMA 560(标准 DH 转换)+ 工具连杆(demo_puma560)
│   ├── demo_scara.cpp       AdeptOne SCARA(R-R-P-R,位置任务)(demo_scara)
│   ├── demo_cartesian.cpp   三轴直角坐标机器人(3 移动关节)(demo_cartesian)
│   ├── mj_export.cpp        导出 MDH 表 + 工具路径 + 运动轨迹为 JSON(可选目标 mj_export)
│   ├── run_demo.py          一键入口(跨平台 Python):构建 → 导出 → 渲染,有显示则开交互窗口
│   ├── pyproject.toml       demo 的 Python 环境(由 uv 管理:mujoco / numpy / pillow)
│   ├── robot/               机器人描述文件:每个机型一个 MuJoCo MJCF(<id>.xml)
│   └── mujoco/              Python 渲染器(把描述文件拼进场景并渲染,细节见其中 README.md)
└── 3rd/                     第三方依赖(Eigen、nlohmann/json,均为 header-only)

demo/ 与库本体完全解耦:robospace/、test/、doc/ 不引用其中任何文件, 删除或通过 -DROBOSPACE_BUILD_DEMO=OFF 关闭均不影响库的使用与测试。 唯一例外是 test/real_robot_test.cpp,它 刻意 包含 demo/demo_models.h:这样测试验证的 就是 demo 与可视化实际使用的那份真实机型参数表(单一事实来源,避免「测试和 demo 用了两份表」)。

2. 模块组成

模块 职责 关键接口
robotype.h 类型基础设施 AngleUnit{DEG,RAD}、PoseVec<FP>、JointVec<N,FP>、VecX<N,FP>、MatX<R,C,FP>
robomath.hpp 位姿表示与线性代数 deg2rad/rad2deg、eul2rot/rot2eul、quat2rot/rot2quat、eul2quat/quat2eul、tform2trvec/trvec2tform、itform、rank、pinv
robolink.hpp 单连杆建模 + 标准 DH 转换 RoboLink<FP,UNIT>:DH 恒定量(alpha/a/d/theta)、关节偏移(offset)、限位(qlim)、状态(q)、类型(jtype)
robochain.hpp 核心层:机器人定义 + 算法接口与默认实现 RoboChain<TASK_DIM,LINK_NUM,FP,UNIT>:create/initialized/name/links/joints/offsets/topology/tr() 变换缓存,虚接口 tr/fk/jacob/pinvjacob/ik,RoboSpace 占位类型;默认算法(MDH 正运动学、几何雅可比、阻尼最小二乘 IK)及内部工具 task_error/clamp_joints/step_to_joint_unit 位于内部命名空间 robospace::detail
robodynamic.hpp 动力学与逆动力学 占位头文件,规划接口见文件头注释(RNEA / CRBA / 正动力学 / 回归矩阵)
robotensor.hpp 张量/标定 kron(A,B)、vecmat(A,B)
robospace.h 聚合入口 只 #include 各模块头文件,不含任何类或函数定义

以下模块位于 demo/,属于示例与可视化设施,不属于库本体:

模块 职责 关键接口
demo/demo_models.h 真实机型单一来源 7 个机型的 DH 表 + 参数来源 + 外部核验 + 初始位姿 + 工具路径(直线段 / 圆弧回转段)+ 路点生成
demo/demo_runner.h 示例共用呈现 参数表/位姿打印、run_point_to_point(逐路点 ik() 并追踪残差)
demo/demo_*.cpp 独立示例程序 demo_ur5 / demo_iiwa7 / demo_puma560 / demo_scara / demo_cartesian,各自自包含 main()
demo/mj_export.cpp 导出可视化数据 目标 mj_export:导出各机型 MDH 表 + 工具路径 + 运动轨迹为 build/mj_export.json
demo/robot/<id>.xml 机器人描述文件 每个机型一个 MuJoCo MJCF(运动学 + 图元几何 + 关节限位 + ee 末端 site),单独存放,可直接被 MuJoCo 加载
demo/run_demo.py 一键运行(跨平台) 构建 → 导出 JSON → 检查描述文件 → 渲染,支持 --demo NAME / --robot NAME / --list / --gui / --no-gui / --jobs N
demo/pyproject.toml Python 环境 uv 管理:uv sync 装 mujoco / numpy / pillow,无需系统 pip
demo/mujoco/ MuJoCo 渲染 把 demo/robot/<id>.xml 拼进场景并渲染 GIF/PNG(绿方块 = 初始位姿、红方块 = 终点、黄点 + 红/绿/蓝三轴 = TCP 工具坐标系、淡蓝带 = TCP 轨迹)

2.1 真实机型一览(demo 与可视化使用的 7 个模型)

robot id demo 真实机型 DH 约定 任务 外部核验
ur5 demo_ur5 Universal Robots UR5 标准 DH → form_sdh 6 维位姿 零位 |p| = 839.4 mm vs 官方 850 mm 臂展(1.3%);与官方 ROS 2 描述 URDF 逐行一致
iiwa7 demo_iiwa7 KUKA LBR iiwa 7 R800 改进 DH(原表) 6 维位姿(冗余) 零位法兰 (0,0,1266) mm = KUKA 公布尺寸 A,偏差 0;与 iiwa_stack URDF 的限位/偏移一致
iiwa3 demo_iiwa7 同上机型的前 3 关节(真实子链) 改进 DH 3 维位置(秩 2) 末端锁在半径 d3 = 400 mm 球面上
puma560 demo_puma560 Unimation PUMA 560 标准 DH → form_sdh 6 维位姿 零位腕心 (452.1, −150.05, 431.8) mm;与 RTB mdl_puma560.m 的 DH 与 qlim 逐项一致
puma560_tool demo_puma560 PUMA 560 + 工具行 + 锁死连杆 标准 DH(非平凡工具行) 6 维位姿 T(0,7) = T(0,6)·E 误差 0
scara4 demo_scara AdeptOne SCARA(R-R-P-R) 标准 DH → form_sdh 3 维位置 零位水平半径 800 mm = a1+a2 = 425+375
cartesian3 demo_cartesian 三轴直角坐标机器人 正交构型(厂商无 DH 表) 3 维位置 J 为常数正交置换阵,JᵀJ = I

参数来源与逐条核验记录在 demo/demo_models.h 的注释里,test/real_robot_test.cpp 会把这些 外部核验(1266 mm、800 mm、秩、零列等)固化成断言。UR5 与 iiwa 7 R800 的运动学还与公开开源 描述(Universal_Robots_ROS2_Description、IFL-CAMP/iiwa_stack)逐项核对过,PUMA 560 与 RTB 的 mdl_puma560.m 逐项一致;注意 MuJoCo Menagerie 里只有 UR5e 与 iiwa 14,不是同型号。 两处未确认的外部信息已在代码注释中明确标注为建模选择而非厂商数据:AdeptOne 的关节限位 (原厂手册未能提取)与直角坐标构型的轴顺序。详见 demo/mujoco/README.md 的「开源模型调研」。

2.2 设计架构(分层与解耦)

flowchart TB
    USER(["用户代码"])

    subgraph PUB["公开 API | 一级命名空间 robospace"]
        TYPE["robotype.h<br/>AngleUnit · ChainTopo · PoseVec / JointVec / VecX / MatX"]
        MATH["robomath.hpp · robotensor.hpp<br/>角度 / 姿态 / 齐次变换互转 · rank / pinv · kron / vecmat"]
        LINK["robolink.hpp<br/>RoboLink(DH 恒定量 + 关节状态)· StandardDhLink · form_sdh"]
        CHAIN["robochain.hpp | 核心<br/>RoboChain / RoboSpace:连杆 · 关节状态 · 限位 · 变换缓存 T_<br/>算法虚接口 tr · fk · jacob · pinvjacob · ik"]
    end

    subgraph INNER["内部实现层(非公开 API)"]
        DETAIL["robospace::detail<br/>默认算法 tr · jacob · ik<br/>工具 task_error · clamp_joints · step_to_joint_unit"]
    end

    AGG["robospace.h | 聚合头(仅 include,不实现功能)"]

    TYPE --> MATH
    MATH --> LINK
    LINK --> CHAIN
    MATH --> CHAIN
    CHAIN -->|"默认转发"| DETAIL
    AGG -->|"一次性引入全部模块"| CHAIN
    USER -->|"调用公开接口"| CHAIN
Loading
  • 公开接口集中在 robospace 一级命名空间:RoboLink / RoboChain / RoboSpace / form_sdh 以及 robomath / robotensor 的工具函数。
  • 模型与算法解耦:RoboChain 只持有模型(连杆、关节状态、限位、变换缓存)与算法虚接口; 默认算法实现位于内部命名空间 robospace::detail(实现细节,非公开 API),由虚接口转发调用。
  • 聚合头不做实现:robospace.h 仅 #include 全部模块,可整库引入,也可按需只包含单个模块。

2.3 数据流与状态管理

flowchart LR
    subgraph MODEL["模型 / 状态"]
        RLINK["RoboLink&lt;FP, UNIT&gt;<br/>DH 恒定量 alpha / a / d / theta(构造后只读)<br/>关节状态 q · 限位 qlim"]
        RCHAIN["RoboChain&lt;TASK_DIM, LINK_NUM, FP, UNIT&gt;<br/>links_ 连杆列表 · T_ 变换缓存"]
        RLINK -->|"DH 恒定量(只读)"| RCHAIN
        RCHAIN -->|"关节状态 / 限位"| RLINK
    end

    subgraph FLOW["运动学数据流"]
        Q(["关节值 q"])
        Q -->|"前向"| TR["tr(q)"] --> TT["齐次变换 T(0,n)"] --> FK["fk(q)"] --> POSE(["位姿 [x, y, z, rx, ry, rz]"])
        Q -->|"微分"| JAC["jacob(q)"] --> J["J (6×n)"] --> PV["pinvjacob(q)"] --> JP["J⁺ (n×6)"] --> IK["ik(pt, q0)"] --> QS(["关节解 q"])
    end

    RCHAIN -->|"缓存 T(0,n)"| TT
Loading
  • DH 恒定量与关节状态分离:RoboLink 中 alpha/a/d/theta 为构造后只读的参数, 关节状态由 q() / q(v) 读写,RoboChain::joints() / joints(q) 是链级封装。 几何参数不会因写关节角而被覆盖。
  • 变换缓存:fk(q, settr=true)、jacob(q, settr=true) 会把该位形的 T(0,n) 缓存到 tr(),避免重复前向计算;默认 settr=false,不产生副作用。
  • 显式关节量优先:fk/jacob/tr/ik 均通过参数接收 q,与内部关节状态解耦; 仅不传 q0 的 ik 重载使用 joints() 作为迭代初值。

2.4 核心算法可外部替代的三种方式

robochain.hpp 同时承载模型与默认算法:RoboChain 的虚接口 tr/fk/jacob/pinvjacob/ik 只有转发体,默认算法实现在内部命名空间 robospace::detail(属实现细节,非公开 API); robospace.h 只是聚合 #include。模型与算法彼此解耦,核心算法可由外部整体替换,共有三种方式:

  1. 派生覆写虚函数:继承 RoboChain 覆写 tr / fk / jacob / pinvjacob / ik 中任意一个, 通过基类指针调用即进入派生实现。test/robochain_test.cpp 的 RoboChainVirtual.* 用例演示了 tr、fk、jacob、ik 的替换(含 GoogleMock 派发测试)。
  2. 替换 robospace::detail 的默认实现:内部算法是自由函数模板(首参为链对象, 经 chain.links() / dh_terms() / dh_matrix() / check_joints() 访问模型), 直接改写 robochain.hpp 中 robospace::detail 的 tr / jacob / ik 或新增自己的实现即可(这是内部实现层,不属于公开 API)。
  3. 自行组合算法:不依赖前两者,直接用 robomath.hpp / robotensor.hpp 的工具与 RoboChain 的公开模型访问器(links()、joints()、dh_terms()、dh_matrix()) 编写求解器,RoboSpace 等占位类型可用于承载机型层扩展。

说明:fk 经由虚函数 tr() 获取变换、ik 经由虚函数 jacob() 获取雅可比并缓存变换, 所以覆写 tr / jacob 同样会改变 fk / ik 的行为。

只包含 robochain.hpp 的编译单元即可定义机器人、读写关节状态与偏移、查看拓扑/连杆与变换缓存, 并直接调用 tr/fk/jacob/pinvjacob/ik:算法接口的转发体、默认算法定义与虚表都在 robochain.hpp 内,无需再额外包含其它算法头文件。

3. 核心功能与典型用例

功能 接口 说明
正运动学 fk(q, settr, order) 关节量 → 末端位姿 [x,y,z,rx,ry,rz]
变换矩阵 tr(q, j_start, j_end) 关节量 → 齐次变换矩阵,可只算子链
几何雅可比 jacob(q, settr) 世界系表达,J = [Jv ; Jω]
伪逆雅可比 pinvjacob(q, eps, settr) 基于 SVD 的 Moore-Penrose 伪逆
逆运动学 ik(pt, q0, tol, max_iter, order) / ik(pt, tol, max_iter, setj, order) 阻尼最小二乘 + 回溯线搜索
关节状态 joints() / joints(q) 读写关节状态,写入时校验限位
关节偏移 offsets() / offsets(q) 零位偏移,与关节量叠加
标准 DH 接入 form_sdh(sdh, types, qlim, base*, tool*) 标准 DH 参数表 → 本库改进 DH 模型(含工具补偿)
位姿互转 eul2rot/rot2eul、tform2trvec/trvec2tform 6 种欧拉角顺序(ZYX/XYZ/YXZ/YZX/XZY/ZXY)
四元数 quat2rot/rot2quat、eul2quat/quat2eul 基于 Eigen::Quaternion,输入自动归一化
数值线性代数 rank、pinv 秩、Moore-Penrose 伪逆(带相对容差)
张量 kron、vecmat Kronecker 积、AX = YB 约束矩阵

4. 安装与配置

4.1 依赖

依赖 版本要求 说明
C++ 标准 C++20 顶层 CMakeLists.txt 已设置 CMAKE_CXX_STANDARD 20
CMake ≥ 3.10(测试需 ≥ 3.14) 测试使用 FetchContent / gtest_discover_tests
Eigen 3.x 仓库内置 3rd/Eigen,无需单独安装
GoogleTest 1.15.2 仅测试需要,构建时自动获取(见下)

4.2 构建

# 配置 + 编译(默认构建 demo/ 示例程序与单元测试)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

# 运行测试
ctest --test-dir build --output-on-failure

# 只构建库与测试,不构建 demo/ 示例程序
cmake -S . -B build -DROBOSPACE_BUILD_DEMO=OFF
cmake --build build

本项目统一使用单一构建目录 build/,不为测试、覆盖率等场景另建目录:单元测试默认 开启,切换配置只需在同一目录重新给出 CMake 选项(如 -DROBOSPACE_ENABLE_COVERAGE=ON) 再 cmake --build build,CMake 会自动重编受影响的目标。

主要 CMake 选项:

选项 默认 说明
ROBOSPACE_BUILD_DEMO ON 是否构建 demo/ 下的独立示例程序(5 个示例 + 可选的 mj_export)
ROBOSPACE_BUILD_TESTS ON 是否构建单元测试
ROBOSPACE_ENABLE_COVERAGE OFF 是否开启 --coverage 插桩并启用 cov-report 目标
ROBOSPACE_GTEST_FETCH ON 系统未安装 GoogleTest 时是否自动下载
ROBOSPACE_GTEST_URL 官方 v1.15.2 压缩包 可换用内网镜像地址

4.3 在其它工程中使用

robospace 是 INTERFACE(头文件)库,已导出三组包含目录,两种写法均可:

add_subdirectory(robospace)          # 或 add_subdirectory(<path-to-robospace>)
target_link_libraries(my_app PRIVATE robospace)
#include "robospace/robochain.hpp"       // 建模 + 运动学与逆运动学(推荐入口)
#include "robospace/robospace.h"         // 或一次性包含全部模块
// 或
#include "robochain.hpp"                 // 直接使用 robospace/ 目录

4.4 离线构建测试

FetchContent 首次配置需要联网下载 GoogleTest。离线环境可预先准备源码目录:

cmake -S . -B build \
      -DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=/path/to/googletest-src
# 或直接关闭测试
cmake -S . -B build -DROBOSPACE_BUILD_TESTS=OFF

5. 使用示例

5.1 建模与正运动学

#include "robospace/robospace.h"

namespace rbs = robospace;

// 改进 DH 参数:{ 名称, alpha, a, d, theta, offset, qlim, 关节类型 }
// 角度参数单位与模板参数 UNIT 一致(此处为度),长度单位统一为 mm
std::vector<rbs::RoboLink<double>> links = {
    { "L1",   0.0, 0.0, 100.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L2",  90.0, 0.0,   0.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L3", -90.0, 0.0, 300.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L4",  90.0, 0.0,   0.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L5", -90.0, 0.0, 250.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L6",  90.0, 0.0,   0.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L7", -90.0, 0.0, 150.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
};

// 第一个模板参数 = 任务空间维数(3 = 仅位置 [x,y,z],6 = 完整位姿 [x,y,z,rx,ry,rz])
// 第二个模板参数 = 关节数(此例 7 关节控制 6 维位姿,属冗余机器人)
rbs::RoboChain<6, 7, double, rbs::AngleUnit::DEG> robot("arm7", links);

rbs::VecX<7, double> q = { 15.0, -25.0, 40.0, -10.0, 60.0, 35.0, -5.0 };

rbs::PoseVec<double> pose = robot.fk(q, /*settr=*/true);   // [x, y, z, rx, ry, rz]
std::cout << "位姿: " << pose.transpose() << std::endl;
std::cout << "变换矩阵:\n" << robot.tr() << std::endl;  // fk(q,true) 缓存的 T(0,n)

5.2 雅可比与伪逆

auto J  = robot.jacob(q);        // 6 x 7,世界坐标系表达,单位 [mm/rad ; rad/rad]
auto Jp = robot.pinvjacob(q);    // 7 x 6,Moore-Penrose 伪逆

// 关节速度 → 末端速度(关节速度对转动关节为 rad/s)
rbs::VecX<7, double> qd = rbs::VecX<7, double>::Zero();
qd(0) = 1.0;
rbs::VecX<6, double> twist = J * qd;   // [线速度 mm/s ; 角速度 rad/s]

5.3 逆运动学

rbs::PoseVec<double> target = robot.fk(q);
rbs::VecX<7, double> q0 = rbs::VecX<7, double>::Zero();

// 收敛容差作用于 [位置(mm); 姿态(rad)] 误差向量的二范数
try {
    rbs::VecX<7, double> qi = robot.ik(target, q0, 1e-8, 4000);
    std::cout << "关节解: " << qi.transpose() << std::endl;
} catch(const std::runtime_error &e) {
    std::cout << "求解失败: " << e.what() << std::endl;   // 不可达 / 被限位阻挡
}

// 以当前关节状态为初值,并回写结果
robot.ik(target, 1e-8, 4000, /*setj=*/true);

5.4 位姿与姿态表示

// 欧拉角 <-> 旋转矩阵:向量恒为 [rx, ry, rz],order 只决定施加顺序
auto R  = rbs::eul2rot<double, rbs::AngleUnit::DEG>({ 10.0, 20.0, 30.0 }, "ZYX");
auto e  = rbs::rot2eul<double, rbs::AngleUnit::DEG>(R, "ZYX");        // 得到 [10, 20, 30]

// 齐次变换 <-> 位姿向量(ZYX 顺序与 IEEE 一致)
rbs::VecX<6, double> p = { 1.0, 2.0, 3.0, 10.0, 20.0, 30.0 };
auto T  = rbs::trvec2tform<double, rbs::AngleUnit::DEG>(p);
auto p2 = rbs::tform2trvec<double, rbs::AngleUnit::DEG>(T);            // 与 p 一致

// 四元数(Eigen 类型,输入自动归一化)
Eigen::Quaterniond qq = rbs::eul2quat<double, rbs::AngleUnit::DEG>(e);
auto R2 = rbs::quat2rot<double>(qq);

5.5 弧度单位

using rbs::AngleUnit;
std::vector<rbs::RoboLink<double, AngleUnit::RAD>> links_rad = {
    { "L1", 0.0,    0.0, 100.0, 0.0, 0.0, { -M_PI, M_PI }, rbs::JointType::REVOLUTE },
    { "L2", M_PI/2, 0.0,   0.0, 0.0, 0.0, { -M_PI, M_PI }, rbs::JointType::REVOLUTE },
    // ...
};
rbs::RoboChain<6, 2, double, AngleUnit::RAD> robot_rad("rad", links_rad);

5.6 MuJoCo 可视化 demo

demo/ 下除文本示例外还提供了一套 MuJoCo 可视化:mj_export 用库导出的 MDH 连杆表、 库 ik() 求出的关节轨迹与库 fk() 算出的位姿生成 JSON,Python 脚本据此自动生成 MJCF 并渲染成动画——MJCF 完全来自库的模型(单一事实来源),且用 MuJoCo 自己的正运动学与 库的 fk() 逐点交叉验证(实测 7 个机型的位置偏差 ≤ 1.3e-12 mm、姿态偏差 ≤ 4.0e-9 deg)。 它不属于库本体,C++ 构建本身不依赖 MuJoCo。

模型与运动定义在 demo/demo_models.h:7 个机型全部是真实公开机型(UR5、KUKA iiwa 7 R800、 PUMA 560、AdeptOne SCARA、三轴直角坐标,以及 iiwa 前 3 关节这条真实子链),示例程序、导出器与 测试共用同一份参数表,因此画面、文本输出与测试断言不可能互相矛盾。

每个示例演示的都是一条工具路径(demo_models.h 里的 PathBuilder):从 初始位姿 fk(q_init) 出发,依次走若干直线段与圆弧回转段(绕基座轴回转,可边转边升降),每段按长度离散成若干路点, 逐点用 ik() 求解并打印残差。路径是大范围的:TCP 行程 1.8 ~ 6.0 m(见下表),而库的 ik() / fk() 逐点跟住它 —— 这既演示了工作空间覆盖,也演示了限位余量与奇异性。每段路径同时给出 末端姿态变化:arc 段的 drx / dry / drz 让工具在回转中偏航、俯仰、滚转(直线段也能带姿态 偏置),因此动画里工具坐标系是转着走完全程的,而不是只平移。

渲染出的动画里 绿方块 = 初始位姿、红方块 = 路径终点、黄点 + 红/绿/蓝三轴 = TCP 工具坐标系、白点 = 路径拐点、淡蓝带 = TCP 轨迹。 机器的连杆画橙色、关节画深蓝色(关节轴处的圆柱壳体),二者一眼可分,不会糊成一种颜色。 TCP 就是库的 T(0, n)(描述文件里的 ee site):渲染器在工具坐标系原点画黄点 + 红/绿/蓝 x/y/z 三轴 —— 球体无法体现旋转,而三轴坐标系能,所以姿态变化一眼可见;沿途再稀疏地摆 6 个同样的坐标系快照, 于是整条路径上工具姿态如何演变也能一眼读出。库 fk() 给出的 TCP 轨迹则连成一条淡蓝带。

机型 路径段数 路点 TCP 行程 最紧关节用量 最大位置残差
ur5 96 543 6005 mm 65 %(J1) 9.5e-13 mm
iiwa7 74 433 3978 mm 86 %(J6) 1.3e-12 mm
puma560 34 285 1804 mm 88 %(J1) 1.1e-12 mm
scara4 72 471 3818 mm 83 %(J3) 3.6e-13 mm
cartesian3 9 343 5243 mm 90 %(J3) 6.7e-13 mm

各 demo 运行效果(GIF):下面每一张都对应 demo/gifs/<id>.gif,由 uv run demo/run_demo.py 渲染(绿方块 = 初始位姿,红方块 = 终点,黄点 + 三轴坐标系 = TCP 工具坐标系,淡蓝带 = TCP 轨迹,白点 = 路径拐点):

机型(demo) 机型(demo)
ur5(demo_ur5)
ur5
iiwa7(demo_iiwa7)
iiwa7
puma560(demo_puma560)
puma560
puma560_tool(demo_puma560)
puma560_tool
scara4(demo_scara)
scara4
cartesian3(demo_cartesian)
cartesian3
iiwa3(demo_iiwa7,关节空间演示)
iiwa3

GIF 分辨率 480×360、约 9 s 循环(放慢以便观看),单个 1.1 ~ 1.6 MB。

「最紧关节用量」= max |q_j - 中心| / 半行程,100 % 表示正好顶在限位上。这些数字不是估计值: mj_export 每导出一个机型就打一行,test/real_robot_test.cpp 把它们固化成断言(路径必须够长、 必须全部收敛、且限位用量不得超过 95 %)。路径设计中被卡住的实例也留在了注释里 —— 例如 PUMA 560 的回转不能超过 ~160°(否则工具会跑出 J1 的可达方位带),iiwa7 / PUMA 560 的工具偏航要跟着 位置方位角一起转,否则腕关节会先顶到限位。SCARA 与直角坐标是位置任务(3 维),路径里根本没有 姿态目标(waypoints() 会丢弃姿态偏置),所以它们的工具坐标系在整个动画里是不转的 —— 这也是库的 计算结果,而不是渲染的偷懒。

每个机型的 MuJoCo 描述单独放在 demo/robot/<id>.xml(7 个机型 7 个文件,MuJoCo 原生 MJCF 格式,只用 capsule / sphere / cylinder 等图元,无网格资产,单文件几 KB;连杆橙色、关节深蓝(关节轴处 的圆柱壳体)、底座灰蓝;TCP 由 ee site 标记,移动关节的滑块画成圆柱形轴承而不是方块)。

入口是跨平台的 Python 脚本 demo/run_demo.py,Python 环境由 uv 管理(demo/pyproject.toml, uv sync 会装好 mujoco / numpy / pillow,不依赖系统 pip):

uv sync                          # 首次:创建 demo/.venv 并安装依赖(uv 自动准备 Python)
cmake --build build              # 构建(含 mj_export 与 5 个示例;默认行为)
uv run demo/run_demo.py          # 一键:构建 → 导出 → 渲染;有显示则开交互窗口,否则离屏
uv run demo/run_demo.py --no-gui # 强制离屏,生成 build/mujoco/ 下全部 GIF/海报
uv run demo/run_demo.py --demo ur5   # 先跑该示例(打印初始位姿、工具路径与运动过程),再渲染它覆盖的机型
uv run demo/run_demo.py --list   # 列出机型、所属 demo 与各自的描述文件
ls build/mujoco/                 # 离屏产物:<robot>.gif 与 <robot>_poster.png

系统 Python 也能直接跑:python3 demo/run_demo.py —— 缺少依赖时它会自动改用 uv 环境执行。

在有显示的环境(含 WSL 的 WSLg)里,脚本默认打开 MuJoCo 交互窗口并循环播放轨迹,关闭窗口后 继续下一个机型;--no-gui 强制只生成文件。检测依据在 Linux 上是 DISPLAY / WAYLAND_DISPLAY 非空(macOS / Windows 无需环境变量),并要求标准输出为终端(非终端调用一律离屏渲染,避免无人值守 时被窗口阻塞)。

(--gui 强制开窗、--no-gui 强制离屏、--robot NAME 只渲染单个机型、--jobs N 控制构建并行度、 --help 查看全部参数。)

内存:demo/run_demo.py 默认用 -j4 构建而不是全部核心。Eigen 的重模板实例化会让单个编译 进程的峰值内存达到 1GB 量级,16GB 机器上 20 核全并行会把内存打满;渲染则按机型串行进行。 本机实测:全量构建 + 首轮渲染峰值常驻内存 1.79 GB,纯渲染(--no-build)553 MB。

详见 doc/07 §3 与 demo/mujoco/README.md。

5.7 位置任务:3 自由度机械臂

模板第一个参数是任务空间维数,取 3 表示只约束末端位置 [x, y, z],可用于位置控制的 机械臂(如 3 自由度取放臂、直角坐标龙门)。

// 3 转动关节,只做位置控制
std::vector<rbs::RoboLink<double>> links3 = {
    { "L1",   0.0, 0.0, 100.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L2",  90.0, 0.0,   0.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
    { "L3", -90.0, 0.0, 300.0, 0.0, 0.0, { -360, 360 }, rbs::JointType::REVOLUTE },
};
rbs::RoboChain<3, 3, double, rbs::AngleUnit::DEG> arm3("arm3", links3);

rbs::VecX<3, double> q = { 20.0, -30.0, 45.0 };
rbs::PoseVec<double> pose = arm3.fk(q);      // 仍是完整 6 维位姿
auto J3 = arm3.jacob(q);                 // 3 x 3,只含线速度行(单位 mm/rad)
auto T3 = arm3.tr(q);                    // 齐次变换矩阵

rbs::VecX<3, double> target = arm3.fk(rbs::VecX<3, double>{ -40.0, 55.0, -70.0 }).head<3>();
rbs::VecX<3, double> qs = arm3.ik(target, rbs::VecX<3, double>::Zero(), 1e-9, 2000);
// 位置误差已收敛;3 自由度位置任务不约束末端姿态,故解不唯一

5.8 标准 DH 参数机型接入

只拿到厂商手册里的标准 DH(T = Rz(θ)Dz(d)Dx(a)Rx(α))参数表时,用 robolink.hpp 的 form_sdh() 直接转换成库内的改进 DH 模型:

// 标准 DH 一行一个关节:{ 常量 theta, d, a, alpha }
std::vector<rbs::StandardDhLink<double>> sdh = {
    { 0.0,   0.0,    0.0,   90.0 },   // theta, d, a, alpha(角度单位随模板参数)
    { 0.0,   0.0,  431.8,    0.0 },
    { 0.0, 150.05,  20.3,  -90.0 },
    { 0.0, 431.8,    0.0,   90.0 },
    { 0.0,   0.0,    0.0,  -90.0 },
    { 0.0,   0.0,    0.0,    0.0 },
};
std::vector<rbs::JointType> types(sdh.size(), rbs::JointType::REVOLUTE);
std::vector<rbs::VecX<2, double>> qlim(sdh.size(), { -180.0, 180.0 });

rbs::MatX<4, 4, double> base_bias = rbs::MatX<4, 4, double>::Identity();
rbs::MatX<4, 4, double> tool_bias = rbs::MatX<4, 4, double>::Identity();
auto links = rbs::form_sdh<double, rbs::AngleUnit::DEG>(sdh, types, qlim, &base_bias, &tool_bias);

rbs::RoboChain<6, 6, double, rbs::AngleUnit::DEG> robot("puma", links);
// 关系:T_sdh(q) = base_bias * robot.tr(q) * tool_bias
//   base_bias 恒为单位阵;tool_bias = Rx(alpha_n)·Dx(a_n),末行 a/alpha 为 0 时亦为单位阵

要点(详见 doc/08):

  • 转换是参数下标移位:改进 DH 第 i 行的 (a, alpha) 取自标准 DH 第 i-1 行,首行取零; theta/d 保持不变;
  • 工具补偿 tool_bias 不可省略:当标准 DH 末行的 a_n 或 alpha_n 非零时必须右乘它, 否则末端位姿会整体偏差约 |a_n|(实测漏用导致 29.6 mm 误差,正确使用后残差 1.1e-13);
  • 精度:double 约 1e-13,float 约 2e-5;完整示例见 demo/demo_stdh.cpp。

移动关节(JointType::PRISMATIC)同样受支持:其关节量、d 常量、offset 与 qlim 都是长度量(mm),且不做角度取模。含移动关节的完整示例见 demo/demo_prismatic.cpp。

6. 测试与覆盖率

6.1 运行测试

cmake -S . -B build && cmake --build build
ctest --test-dir build --output-on-failure

# 或直接运行测试可执行文件(支持 GoogleTest 过滤)
./build/test/robospace_tests
./build/test/robospace_tests --gtest_filter='InverseKinematics.*'

测试组织(共 194 个用例,48 个测试套件):

文件 覆盖内容
robomath_test.cpp 角度换算、fmod/in_range/clip、6 种欧拉角顺序的互逆性、万向节死锁、位姿与变换互转、刚体逆、四元数归一化、秩、伪逆的 Moore-Penrose 四条件、容差行为、float 实例化
robolink_test.cpp DH 参数存取、单位换算(角度/弧度、转动/移动关节)、状态与限位校验、非法限位、类型切换、参数重置
robochain_test.cpp 构造校验、未初始化保护、子链区间、位姿与变换一致性、超限异常信息、雅可比形状/伪逆、关节状态与偏移、虚函数派发(GoogleMock 测试替身)
kinematics_test.cpp 平面 2R 臂解析解与解析雅可比、奇异位形秩降、移动关节轴向、与独立参考实现比对(随机 8 组位形)、有限差分校验雅可比(线速度+角速度)、角度/弧度链一致性、float 冒烟、变换缓存
ik_test.cpp 可达目标收敛(含 179° 大姿态误差)、随机初值 8 组、弧度链、限位约束、冗余解、6 种欧拉角顺序、不可达/限位阻挡/迭代不足的异常、参数校验、setj 回写、容差语义、近奇异收敛
robot_arm6_test.cpp 6 自由度机械臂:与独立参考实现比对 FK、有限差分校验雅可比、满秩判据、IK 往返与不可达异常、解满足限位
robot_arm3_test.cpp 3 自由度位置控制臂(TASK_DIM == 3):3×3 雅可比只含线速度行、位置 IK 收敛与多解、伪逆性质、不可达异常
robot_prismatic_test.cpp 含移动关节机型:3 轴直角坐标龙门(位置任务)与「龙门 + 3R 手腕」(位姿任务);移动/转动关节的雅可比列结构、移动关节常量与偏移语义、不做角度取模
robot_arm7_test.cpp 7 自由度冗余臂(S-R-S):满秩判据、多初值收敛且关节解互异(冗余性)、零空间维度 = LINK_NUM − rank(J)、容差与近奇异行为
sweep_test.cpp 参数化批量扫描(5 机型 × 50 组随机位形):FK 与独立参考实现、雅可比与中心差分(线/角速度)、子链一致性;IK 批量成功率与最坏残差(含失败样本的异常断言)
contract_test.cpp 异常与契约矩阵:逐接口覆盖未初始化 / 连杆数不符 / qlim 反序 / 超限 / 非法欧拉顺序 / 非法 tol 与 max_iter / 不可达 / 卡限位,断言异常类型与消息关键字;并固化 tr() 不校验限位、create() 重置缓存、失败时状态不变等既有契约
precision_test.cpp 单位 × 精度 × 类型交叉(TASK_DIM{3,6} × {DEG,RAD} × {float,double} 全部 8 组合):跨单位一致性、单精度收敛性、状态单位往返 ULP 现象固化
algorithm_swap_test.cpp 算法外部替代增量场景:覆写 tr/fk/jacob/ik 的调用次数、缓存来源与重载独立替换(含 gmock 记录调用)
robolink_stdh_test.cpp 标准 DH → MDH 转换:PUMA560 / UR5 / 含移动关节 / float / DEG 与异常校验;外加 34 组随机标准 DH 机型验证 $T_{sdh}=B\cdot T_{mdh}\cdot E$ 与「漏用 $E$ 显著不一致」的负向断言
robotensor_test.cpp Kronecker 积定义与维数、vecmat 方阵/非方阵(含用 SVD 零空间还原 X、Y 的端到端用例)、按列堆叠约定

6.2 覆盖率

cmake -S . -B build -DROBOSPACE_ENABLE_COVERAGE=ON
cmake --build build
ctest --test-dir build
cmake --build build --target cov-report           # 生成 build/coverage.txt

最新结果(gcov,同一模板实例化取并集):

文件 已执行行 可执行行 行覆盖率
robospace/robochain.hpp(核心:模型 + 内部 detail 默认算法) 212 214 99.1%
robospace/robolink.hpp 80 80 100.0%
robospace/robomath.hpp 91 91 100.0%
robospace/robotensor.hpp 19 20 95.0%
合计 402 405 99.3%

(robospace.h 仅聚合 include,无可执行行;上表为 217 个用例的实测结果。)

未覆盖的 3 行是防御性分支与依赖具体实例化的分支(ik() 中 LDLT 求解出现非有限值的兜底、 阻尼因子超过上限的保护性跳出,以及 robotensor.hpp:85),正常数值范围内不会触发。

函数覆盖率为 541/699 = 77.4%,低于行覆盖率属预期:统计口径按「模板实例化」计数, 而本库为虚函数接口,编译器会为每个被实例化的 TASK_DIM/LINK_NUM 组合都生成虚函数表项与 析构函数(即便从未被调用);若只按「被调用过的逻辑函数」统计则接近 100%。

提示:在同一 build/ 目录里从普通构建切到覆盖率构建(或反过来)会重编所有目标, 采集前建议先删除旧的 *.gcda(find build -name '*.gcda' -delete), 否则 gcov 可能报 profiling error: overwriting an existing profile data。

7. 数学原理与 API 文档

算法推导、接口契约与数值方法细节见 doc/ 目录:

文档 内容
doc/01-位姿表示与坐标变换.md 旋转矩阵性质与证明、内外旋等价性、6 种欧拉角顺序的解析求逆完整公式表、万向节死锁解集与本库返回分支、四元数与 Shepperd 分支、刚体逆、误差量级
doc/02-改进DH参数与正运动学.md 标准/改进 DH 完整对照、单连杆变换逐项推导(与代码逐行对应)、关节变量与常量/零位偏移、「末端原点在末关节轴上」定理与 3 条推论、RRP 规避方案、数值算例
doc/03-雅可比矩阵.md 两条推导路线与一致性证明、关节轴取法的命题化证明、分块与量纲、几何雅可比 vs 解析雅可比($B(\varphi)$、$\det B=\cos r_y$)、奇异值与条件数实测、工具点偏移雅可比变换、有限差分校验方法
doc/04-逆运动学数值方法.md 对数映射推导、四种误差表示对比、高斯-牛顿与 Tikhonov、量纲不平衡的三条定量机理与修复前后实测、信赖域+回溯线搜索(下降性证明)、$\lambda$ 自适应、限位策略对比、tol 选型、与 CCD/FABRIK/解析法对比、迭代次数实测
doc/05-线性代数工具.md SVD 几何意义、伪逆四条件与唯一性证明、四种特例闭式、相对容差公式推导与选型表、LU/SVD 判秩差异、Kronecker 与 vec 恒等式三步推导、$AX=YB$ 维数核对与零空间求解、手眼标定背景
doc/06-数值实现与工程约定.md 分层架构图与算法替换三途径、逐接口单位表、编译期校验清单、异常逐字消息表、精度与稳定性实测、性能与分配、线程安全、Eigen 协作、20 条陷阱(现象→原因→正确做法)、覆盖率工作流
doc/07-示例程序与Demo使用说明.md 构建运行、五个示例逐段详解(含真实输出与修改练习)、MuJoCo 可视化 demo、接入自有机器人的可编译走查 + 12 项自检清单、测试与覆盖率流程、FAQ、API 速查表、术语表
doc/08-标准DH到改进DH的转换.md 两套 DH 约定的坐标系差异、$T_{sdh}=B\cdot T_{mdh}\cdot E$ 的完整推导与数值证明、参数下标移位与工具补偿、真实机型(PUMA560 / UR5)验证、适用条件与限制
doc/09-API参考手册.md 完整 API 参考手册:逐接口的签名/参数/返回值/异常/单位/注意事项,全部示例可编译运行;与 01–08 的分工是「01–08 讲原理推导,09 讲接口契约」

8. 已知限制

  1. 任务空间为 3 维或 6 维:模板参数 TASK_DIM 只允许 3(位置任务 [x,y,z])或 6 (位姿任务 [x,y,z,rx,ry,rz],类内 static_assert 显式校验)。fk() 始终返回完整 6 维位姿, jacob()/ik() 按任务维数工作;关节数 LINK_NUM 可大于 TASK_DIM 以支持冗余机器人。 任意任务子空间(如平面位姿 [x,y,φ])尚不支持。
  2. 仅支持串联开链:不包含闭链、并联机构、浮动基座与关节弹性。 闭链是独立的模型族,库中已通过 ChainTopo(SERIAL / 预留的 CLOSED)与 robochain.hpp 末尾的接口预留做出区分,以保证后续加入闭链类时不会与现有 API 冲突; 闭链的 FK/IK 本身尚未实现。
  3. 逆运动学为局部数值解法:收敛性依赖初值,可能落入局部极小或在大姿态误差下 需要更多迭代;不可达或被限位阻挡时抛出 std::runtime_error,不做随机重启。 另外,同一物理目标在 DEG 与 RAD 两种 UNIT 下的收敛路径可能不同(步长换算与限位 截断的单位不同引入舍入差异,使回溯线搜索的接受/拒绝翻转),极端目标可能出现其中一个 不收敛;若遇到,请改用另一种 UNIT、调整初值或放宽 tol。
  4. 逆运动学忽略零空间优化:冗余机器人返回最小范数解,不支持零空间自定义代价 (如关节行程中心化、避障)。
  5. 限位检查为严格区间检查:不做角度环绕归一化,q 必须在 qlim 内; 连续回转关节请把 qlim 设为足够宽的区间(如 ±infinity)。
  6. 无动力学:不含质量、惯量、重力、摩擦与力矩计算。robodynamic.hpp 目前只是占位 头文件(其中写明了规划中的 RoboDynamics 接口与算法),动力学与逆动力学尚未实现, 因此本库当前只做运动学。
  7. 关节状态按弧度存储、按 UNIT 呈现:deg → rad → deg 往返存在约 2 ULP 误差 (例如 15° 读回 14.999999999999998°)。因此比较状态时请用 ≥1e-12 的容差, 不要用逐位相等(isApprox(x, 0.0));ik(..., setj=true) 回写的关节量同理。
  8. robotype.h 中 DYNAMIC 为 int 常量,与 Eigen::Dynamic 取值一致但不建议混用。
  9. vecmat 的列数约定:以 B.cols() 作为观测列数 q,非方阵情形要求 A(m×n)、B(p×q)满足 A X = Y B 的一致性维数。

9. 后续规划

  1. 闭链/并联机构:实现 RoboClosedChain(多支链 + 闭环约束求解), 接口预留与差异说明见 robospace/robochain.hpp 末尾。难点在于被动关节求解与 平台雅可比的隐函数推导,计划复用现有的阻尼最小二乘与支链雅可比。
  2. 任务子空间扩展:支持任意任务矩阵(如平面位姿 [x, y, φ]、仅姿态任务)。
  3. 逆运动学增强:零空间投影(次级代价优化)、随机重启与多初值求解、 解析解与数值解混合(针对常见 6R 构型)。
  4. 轨迹规划:关节空间插值(梯形/S 曲线)、笛卡尔直线与圆弧插补, 新增独立模块承载。
  5. 动力学与逆动力学:在 robodynamic.hpp 中实现递归牛顿-欧拉(RNEA)、 复合刚体算法(CRBA)、正动力学与辨识回归矩阵,并补充质量/惯量/摩擦模型。
  6. 标定能力完善:基于 vecmat 的 AX = YB 手眼标定求解流程封装。
  7. 工具链:CI 中固化单元测试与覆盖率门限(行覆盖率 ≥ 80%), 增加静态分析与 Sanitizer(ASan/UBSan)流水线。

10. 许可

见 LICENSE。

About

一个简单的、比较通用的机械臂开发库,实现了基于MDH建模的正向运动学解算,基于自适应阻尼最小二乘法的逆运动学解算以及一些机械臂开发过程中的函数功能

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages