Bring in kinematic hard-write control with mesh-convex collision projection, SDK mapping aligned to Isaac, launch/run scripts, and implementation/tech writeups.
14 KiB
G20 左手 · MuJoCo 仿真实现说明
本文记录 MuJoCo G20 PLAN 名义弧度桥 从 URDF 到可跑仿真的完整实现过程,以及相对原始 URDF 的全部具体改动。
- 函数调用与逐步逻辑:见
G20_MuJoCo_技术详解.md(推荐先读此文理解代码) - Isaac 对照:
G20_Isaac_实现说明.md
1. 目标与数据流
1.1 目标
- 在 MuJoCo 中仿真 LinkerHand G20 左手。
- 默认兼容真机 SDK 话题协议:
sensor_msgs/JointState,position为 0~255。 - 可选订阅 PLAN 名义弧度(按关节名)。
- 关节名、0~255→弧度映射、mimic、拇指限位、对比视角与 Isaac 侧对齐。
- 默认 运动学硬写
qpos(跟手紧);用碰撞投影减轻穿模,而不是改成默认 PD。
1.2 端到端数据流
/g20/cb_left_hand_control_cmd (默认,0~255)
或 /retarget/g20/left/joint_target_nominal (--plan-nominal,弧度+关节名)
│
▼
utils/g20_joints.py
· range_to_arc_g20_left / sdk_range_to_urdf_positions
· SDK15:thumb_mcp = arc/1.02
· SDK16..19:*_pip = arc/0.89
· complete_g20_mimic_positions(dip/ip)
│
▼
g20_plan_bridge.py
· ROS 回调只写 goal(不碰 MjData)
· 仿真线程:slew → [collision_project] → 硬写 qpos/ctrl + mj_forward
│
▼
/sim/mujoco/g20/left/joint_state @ 50 Hz
+ MuJoCo viewer(可选)
2. 关键文件路径
| 角色 | 绝对路径 |
|---|---|
| 启动脚本 | /home/lxqs/linker_hand_mujoco_ros2/run_g20_mujoco_plan.sh |
| 桥主程序 | .../linker_hand_mujoco_ros2/g20_plan_bridge.py |
| 关节映射 | .../linker_hand_mujoco_ros2/utils/g20_joints.py |
| Launch | .../launch/g20_plan_bridge.launch.py |
| URDF | .../urdf/G20/linker_hand_g20_left/linkerhand_g20_left.urdf |
| MJCF | .../urdf/G20/linker_hand_g20_left/linker_hand_g20_left.xml |
| 网格 | .../urdf/G20/linker_hand_g20_left/meshes/*.STL(22 个) |
| entry_point | setup.py → g20_plan_bridge=linker_hand_mujoco_ros2.g20_plan_bridge:main |
包根目录:
/home/lxqs/linker_hand_mujoco_ros2/src/linkerhand-sim/linker_hand_mujoco_ros2/linker_hand_mujoco_ros2/
3. 从 URDF 到 MJCF
3.1 URDF 来源
- 原始模型由 SolidWorks → URDF Exporter 导出(URDF 头注释:Commit
1.6.0-4-g7f85cfe)。 - 本仓库资产来自 retarget 工程拷贝(非运行时自动下载),与 Isaac 侧网格 MD5 一致(22/22)。
- 入库后的 URDF 文件名:
linkerhand_g20_left.urdf。
3.2 URDF 上我们改过什么
相对更早的 G20 URDF(历史上限曾为 thumb_cmc_roll 1.4),当前仓库 URDF 已写入:
| 改动项 | 具体内容 |
|---|---|
thumb_cmc_roll 上限 |
upper="1.47"(rad);与映射表 G20_L_MAX[5]=1.47、Isaac 同步试调 |
| mimic(保留自原 URDF) | thumb_ip ← thumb_mcp × 1.02;四指 *_dip ← *_pip × 0.89 |
| 其它关节限位 | 与 Isaac URDF 一致(见下表),未再改数值 |
当前 URDF 关节限位(rad,与 MJCF range / actuator ctrlrange 一致):
| 关节 | lower | upper |
|---|---|---|
thumb_cmc_roll |
0 | 1.47 |
thumb_cmc_yaw |
0 | 1.57 |
thumb_cmc_pitch |
0 | 0.84 |
thumb_mcp |
0 | 1.26 |
thumb_ip |
0 | 1.29 |
四指 *_mcp_roll |
-0.23 | 0.23 |
四指 *_mcp_pitch |
0 | 1.22 |
四指 *_pip |
0 | 1.74 |
四指 *_dip |
0 | 1.55 |
说明:MuJoCo 不读取 URDF 的 <mimic>。mimic 在 Python 里用 complete_g20_mimic_positions 补全后硬写进 qpos。
3.3 转换步骤(URDF → MJCF)
仓库内 没有 保留可一键重跑的转换脚本;实际流程是:
- 初转:用 MuJoCo
MjSpec.from_file(urdf)编译得到骨架 MJCF(关节树、mesh、惯性)。 - 手改定稿 为当前
linker_hand_g20_left.xml(下列改动全部是相对「纯 URDF 导入结果」的人工修改)。
3.4 MJCF 相对 URDF / 纯导入结果的全部改动清单
A. 仿真选项与默认参数
| 项 | 写入值 |
|---|---|
compiler |
angle="radian" meshdir="meshes" balanceinertia="true" |
option |
timestep="0.002",gravity="0 0 -9.81",integrator="implicitfast" |
| 默认 joint | damping="0.15",armature="0.01" |
| 默认 position 执行器 | kp="200",kv="8",forcelimited="true",forcerange="-8 8" |
B. 显示 / 碰撞分层(相对 URDF「全身 mesh 碰撞」)
| 类型 | 改动 |
|---|---|
class="visual" |
type="mesh",contype="0",conaffinity="0",group="2" → 只显示,不参与碰撞 |
| 碰撞体 | 同视觉 STL 的凸包 mesh(type="mesh" + class="collision"):掌心 + 拇指三段 + 四指近/中/远节;比 AABB 更贴外形 |
| collision 默认 | margin="0.0005",friction="1 0.5 0.01",solref="0.004 1",solimp="0.9 0.95 0.001" |
当前碰撞体(与视觉共用同一 meshes/*.STL,MuJoCo 按凸包做碰撞):
| body | 类型 | 说明 |
|---|---|---|
hand_base |
mesh 凸包 | hand_base_link.STL |
thumb_metacarpals / thumb_proximal / thumb_distal |
mesh 凸包 | 同名 STL |
四指 *_proximal / *_middle / *_distal |
mesh 凸包 | 同名 STL |
| 掌骨 metacarpals、拇指 base1/base2 | 无碰撞 geom | 仅 visual;与掌心假重叠靠 exclude |
说明:MuJoCo 对 mesh 碰撞使用 凸包,不能还原 STL 凹坑,但整体轮廓远比 AABB/手写胶囊贴近视觉;张开/半握无假碰,握拳可检出指尖↔掌心穿透。
C. <contact><exclude>(共 28 条)
用途:排除 mesh/几何本就嵌套 造成的假碰撞(张开姿态假重叠)。
- 掌心
hand_base↔ 拇指 base1/base2/metacarpals、四指 metacarpals/proximal - 拇指链相邻 / 隔代相邻
- 四指各自 metacarpals–proximal、proximal–middle、middle–distal
未 exclude 的对(如指尖↔掌心、邻指远端)仍可产生接触,供投影使用。
D. 执行器
- 为 全部 21 个 hinge(含 5 个 mimic 关节)各加一个
<position name="*_pos" joint="...">。 thumb_cmc_roll的ctrlrange="0.0 1.47"(与 URDF 上限一致)。- 默认硬写模式下主要靠写
qpos;--pd-drive时才真正用这些执行器做mj_step。
E. 命名差异
| URDF | MJCF |
|---|---|
<robot name="..."> |
<mujoco model="linker_hand_g20_left"> |
link hand_base_link |
body 名 hand_base(geom 仍引用 mesh hand_base_link) |
4. 关节映射 g20_joints.py(相对 SDK / Isaac)
文件:utils/g20_joints.py。声明与 Isaac joint_mapping.py 的 G20 左手表对齐。
4.1 SDK 通道 → URDF 关节名
| SDK 索引 | 关节名 |
|---|---|
| 0 | thumb_cmc_pitch |
| 1–4 | index/middle/ring/pinky_mcp_pitch |
| 5 | thumb_cmc_roll |
| 6–9 | index/middle/ring/pinky_mcp_roll |
| 10 | thumb_cmc_yaw |
| 11–14 | 预留(转换时跳过) |
| 15 | thumb_mcp(见 4.3) |
| 16–19 | index/middle/ring/pinky_pip(见 4.3) |
4.2 线性插值表(我们写入的数值)
G20_L_MIN = [0,0,0,0,0, 0.0, -0.23,-0.23,-0.23,-0.23, 0.0, 0×4, 0×5]
G20_L_MAX = [0.84, 1.22×4, 1.47, 0.23×4, 1.57, 0×4, 1.29, 1.55×4]
G20_L_DIR = [-1×6, +1,+1,+1,+1, -1, 0×4, -1×5]
含义:
DIR == -1:SDK0 → MAX,255 → MIN(多数弯曲通道)。DIR == +1(仅侧摆 6..9):SDK0 → MIN,255 → MAX(左手侧摆与 retarget/g20_mapping一致)。- 通道 5 上限 1.47(相对历史 1.4 的试调,已写进 URDF/MJCF/Isaac)。
4.3 SDK 15–19 的「除 mimic」规则(与 Isaac 相同,必须写清)
SDK 通道语义不是「直接写主动关节弧度」,而是电机/指尖弧度空间:
| SDK | 先算出的弧度 arc |
写入主动关节 |
|---|---|---|
| 15 | 按 G20_L_* 插值得到 |
thumb_mcp = arc / 1.02 |
| 16–19 | 同上 | *_pip = arc / 0.89 |
然后再:
thumb_ip = thumb_mcp * 1.02*_dip = *_pip * 0.89
若漏掉「除 mimic」,DIP/IP 会整体偏大,表现为关节对不上 Isaac。
4.4 张开预设
G20_OPEN_CMD = [
255,255,255,255,255,255,
193,148,105,42, # 四指侧摆
245, # thumb_cmc_yaw
255,255,255,255, # 预留
255,255,255,255,255 # 15..19
]
open_hand_positions() / half_fist_positions() 基于此生成测试姿态。
5. 桥节点 g20_plan_bridge.py 做了哪些事
5.1 话题
| 方向 | 默认话题 | 内容 |
|---|---|---|
| 订阅(默认) | /g20/cb_left_hand_control_cmd |
position 0~255,取前 20,不足补 255 |
订阅(--plan-nominal) |
/retarget/g20/left/joint_target_nominal |
按关节名弧度 |
| 发布 | /sim/mujoco/g20/left/joint_state |
全部 hinge 的 name/position/velocity,50 Hz |
5.2 默认驱动:运动学硬写
- 默认(无
--pd-drive):把applied写入data.qpos与对应ctrl,qvel=0,调用mj_forward;不调用mj_step。 --pd-drive:写ctrl→mj_step→ 用真实qpos回写applied(跟手变差,接触可挡,一般不作为默认)。
5.3 速度相关默认值(相对「瞬移」做过的调参)
| 常量 | 值 | 含义 |
|---|---|---|
DEFAULT_SPEED |
1.35 |
相对墙钟倍率 |
DEFAULT_SLEW_RAD_S |
5.0 |
关节逼近 goal 的最大角速度 (rad/s);0 = 瞬移 |
sim_dt |
0.002(来自 MJCF) |
每步最大转角 ≈ 5.0 * 0.002 = 0.01 rad |
5.4 防穿模:collision_project(硬写专用,默认开)
相对早期实现,做过这些关键修正:
| 版本问题 | 改动 |
|---|---|
| 仅靠 STL 自碰 | 改为 visual(不碰)+ 同 STL 凸包 collision |
| 「相对上一帧更深才算穿」 | 改为 绝对 判据:min(contact.dist) < -5e-4 即穿 |
| 关节空间整体二分 | 拇指撞掌心会拖死四指 → 改为 按主动关节、Δq 从大到小 逐关节接受或二分 |
| 仍穿模 | 回退 _last_safe_pose |
开关:
- 默认:开(且仅在硬写模式生效)
--no-collision-project:关--track-test:强制关投影,且slew=0,方便验收跟手误差 ≤ 0.02 rad
常量:PENETRATION_TOL_M = 5e-4,COLLISION_PROJECT_ITERS = 12。
5.5 线程安全(修过闪退)
threading.Lock保护MjData。- ROS 回调 只 更新
goal/track_nominal(_set_goal_only),禁止在回调里碰MjData。 - 主循环持锁调用
step_simulation_unlocked();发 state 时持锁读qpos/qvel。
5.6 对比视角(与 Isaac 对齐)
| 常量 | 值 |
|---|---|
COMPARE_LOOKAT |
(0.04, -0.04, 0.12) |
COMPARE_DISTANCE |
0.50 |
COMPARE_AZIMUTH |
180.0(从 +X 看向掌心) |
COMPARE_ELEVATION |
-12.0 |
手模型放在世界原点;Isaac 手有安装偏置,但 lookat 本地坐标与 elevation/distance 相同。
5.7 启动时校验
- 用
G20_ALL_JOINT_NAMES检查 MJCF 是否缺关节;缺则 直接退出。 - 缺 position actuator 也会报错退出。
5.8 其它明确改动
- 不 import 真机包
linker_hand_ros2_sdk(只兼容同话题协议)。 - 不 写死
ROS_DOMAIN_ID(启动脚本里曾有过,已去掉)。 - 退出用
os._exit,规避 rclpy/mujoco 析构 abort。
6. 如何启动
cd /home/lxqs/linker_hand_mujoco_ros2
./run_g20_mujoco_plan.sh
脚本会:source /opt/ros/jazzy → 可选 install/setup.bash → 把 venv site-packages 与包源码加入 PYTHONPATH →
python3 -m linker_hand_mujoco_ros2.g20_plan_bridge。
常用参数:
./run_g20_mujoco_plan.sh --plan-nominal
./run_g20_mujoco_plan.sh --pd-drive
./run_g20_mujoco_plan.sh --no-collision-project
./run_g20_mujoco_plan.sh --speed 0.5 --slew-rad-s 3
./run_g20_mujoco_plan.sh --headless --track-test --max-steps 800
Launch(无参数透传,调参请用 shell):
ros2 launch linker_hand_mujoco_ros2 g20_plan_bridge.launch.py
测试半握示例:
ros2 topic pub -r 20 /g20/cb_left_hand_control_cmd sensor_msgs/msg/JointState \
"{position: [80,80,80,80,80,255,193,148,105,42,245,255,255,255,255,80,80,80,80,80]}"
7. 已知限制(实现层面)
- 硬写 + 投影 不是 真接触动力学;碰到凸包后关节会停在接触边界,不会「挤过去」。
- 碰撞是 凸包,凹进去的视觉细节不会产生接触;比 AABB 贴,但仍可能与非凸视觉有细微差别。
- 大量
exclude关掉了掌心–近端假碰;真实可碰区域依赖开启碰撞的那些 link。 - mimic 只在软件层保证;物理引擎内无 URDF mimic 约束。
- 仅左手 G20;右手未做。
8. 改动 chronologically(便于对照 Git / 联调)
- 拷贝 G20 URDF + meshes,生成带 position actuator 的 MJCF。
- 实现
g20_joints.py(对齐 Isaac;含 SDK15–19 除 mimic)。 - 实现桥:名校验、订 nominal / legacy、发
/sim/mujoco/...。 - 修回调碰
MjData导致的闪退 → 回调只改 goal。 - 话题改为
/g20/cb_left_hand_control_cmd;去掉脚本里 DOMAIN。 - 调
speed/slew到当前默认。 - 拇指
max=1.47写入映射 + URDF/MJCF。 - 视角
COMPARE_*与 Isaac 对齐。 - 默认保持硬写;加碰撞投影;后改为绝对穿透 + mesh 凸包碰撞 + 按关节投影。
9. 与 Isaac 的对照摘要
| 项 | MuJoCo(本文) | Isaac |
|---|---|---|
| 模型格式 | MJCF | 预烘焙 USD(非运行时 URDF 导入) |
| 状态话题 | /sim/mujoco/g20/left/joint_state |
/sim/isaac/g20/left/joint_state |
| 映射表 | g20_joints.py |
joint_mapping.py(G20 左手数值一致) |
| 默认驱动 | 硬写 qpos | 硬写 set_dof_positions |
| 防穿模 | mesh 凸包 + collision_project |
USD 选择性 collider + PhysX 自碰 |
| slew | 默认 5 rad/s | 默认 0(每物理步瞬达) |