d32e6ae53f
一、场景物料(新增)
devices/sim/gazebo/models/workpiece/ 带三个孔的铝板。碰撞体是"带方孔的平板",
视觉额外加两个 φ14/φ7 亮环 —— 因为真机的 HoughCircles 就是靠这两个亮环找孔,
只按碰撞体建视觉会让"孔检测"在仿真里永远失败,而那看起来像算法问题。
devices/sim/gazebo/models/gauge/ 通止规:通端/套环/握柄/止端四段,各段异色
便于肉眼判断方向。接触刚度刻意比台面软 —— 位置控制器会"刚性压进去",
用默认刚度会被弹飞(注释里写了原因,不是随手抄的参数)。
devices/sim/gazebo/models/gauge_stand/ 规座(static)。规直接站在台面上会立刻倒,
加个方孔底座把它立住。
三个模型都是 model.sdf + model.config,原点在**底面中心**,单位米。
二、场景配置与校验(新增)
config/scene.yaml 物体不再写死在 world 里,改为 scene.yaml + 启动时动态 spawn。
理由:world 是"环境"(地面/台面/相机/系统插件),场景是"这一批活做什么"。
动 world 的风险面太大 —— 它只要丢一个系统声明,所有模型都 spawn 不进去(见旧坑 2)。
含 operating(相机到工件的距离等作业条件)与 gates_px(像素门槛)。
sim_scene.py 摆放校验:从 SDF **逐碰撞体**解析 AABB(不抄尺寸,避免漂移),
检查原点在底面 / 不穿台面 / 不出界 / 物体之间不嵌入。
重叠判定必须逐碰撞体三维判,不能拿总包围盒相交 ——
正常装配里本来就有嵌套(规立在规座方孔里),粗判会误报,
而误报的后果是人把检查整个关掉,之后真重叠也抓不到。
另有成像几何:把亮环/孔间距按相机距离换算成像素,与门槛比对。
scripts/check_sim_scene.py 验收工具(退出码可用):摆放 + 门槛漂移 + 成像几何 +
悬停高度 vs 工作空间 + 手外相机是否**完整**看得见工件 + Gazebo 实际位姿。
三、修掉两处静默失效(都是"看起来一切正常")
1) parse_spawned_poses 一直是**死代码**:它按 "x: N / y: N / z: N" 解析,
而 Fortress 实际输出是方括号两行(XYZ / RPY)。对不上 → 永远返回空 →
调用方按设计"降级成跳过位姿核对" → 位姿检查**从来没跑过**,
而日志只说"跳过",看起来像正常降级。
现在两种格式都支持,并用真实抓下来的输出样本做回归测试。
教训:宽容降级会把"没检查"伪装成"检查通过"。
2) stop_sim_gz.sh 的模式表里漏了 robot_state_publisher(而注释里写着它有覆盖):
脚本报"已停止",它却还在发上一份 /robot_description 与 TF,
下一次启动就会"改了描述却不生效"。
顺带修掉同源的两个问题:awk 用 -v 传模式导致它匹配到自己、每次复查都多报
一个空命令行的幽灵 PID;以及"杀的时候"和"复查的时候"两份扫描实现会各自漂移。
现在共用同一个 scan_pids(),模式改用环境变量传递,并在杀完之后**再扫一遍**:
有残留就报出来并非零退出 —— 下次再有漏项,不再静默。
实测:--list 列出全套 9 个进程;停止后 rc=0 且 pgrep 干净。
四、相机:D405 分辨率对齐真机
仿真的 D405 此前渲染 640x480,而真机跑 848x480(fx 337 而非 447)。后果是
58mm 的两孔间距成像只剩 85px,低于 hole_distance_min=90 ——
孔检测在仿真里根本不可能通过,而现象看起来像"算法不行"。
已把 xacro 与 cameras.yaml 一起改成 848x480,并把启动时的比对从"只比视场"
扩成"视场 + 图幅"(sim_cameras.check_camera_consistency),
不一致时说明它对像素门槛的影响。
五、文档
devices/sim/gazebo/README.md 重写为中文手册:使用方法 / 建模要求 / 导入格式 /
配置方式(含"改哪里才真的生效"的接线表)/ 11 条实测坑(每条都有"怎么发现")。
manifest.yaml 的进度、assets、description、坑清单与本轮状态对齐。
scripts/README.md 补两个验收工具与停止脚本;根 README 补仿真入口与手册链接。
cameras.yaml 里一处过期的函数名(check_fov_consistency)修正。
六、测试
新增 test_sim_scene.py(79 条,此前 sim_scene.py 完全没有单测 —— 这正是
位姿解析死掉却无人发现的原因)与 test_check_sim_scene.py(21 条),
含三条回归:world_boxes 的偏移碰撞体不能被对称化放大(假重叠)、
center_bore 不能被 _bore 后缀吃掉、Fortress 位姿输出格式。
全量:src/arm_sim_gazebo + arm_interfaces + arm_application 共 515 通过。
同时附上 config/site.example.yaml 的重新生成(此前 site.yaml 的注释改动没同步,
它的 parity 测试是红的)。
Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
12 KiB
YAML
196 lines
12 KiB
YAML
# =============================================================================
|
||
# devices/sim/gazebo/manifest.yaml
|
||
#
|
||
# Gazebo 仿真平台清单。
|
||
#
|
||
# 进度(截至 P5c-6,如实记录,不要凭印象改):
|
||
# ✔ 环境 独立 conda 环境 ros_gz_sim(Gazebo Fortress + ROS 2 Humble)
|
||
# 安装见 scripts/install_sim_env.sh,激活见 scripts/sim_env.sh
|
||
# ✔ 世界文件 worlds/assembly.world(sdformat 校验通过)
|
||
# ✔ 机器人描述 urdf/(复用官方 flexiv_description,已通过 sdformat 校验)
|
||
# ✔ 控制器 config/controllers.yaml(控制器名与真机一致)
|
||
# ✔ 描述归一化 arm_sim_gazebo 包的 urdf_normalize(37 项单测)
|
||
# ✔ 启动器 gazebo_sim_launcher 已实现并端到端跑通
|
||
# 起 Gazebo → 发归一化 URDF → spawn → 拉起两个控制器(active)
|
||
# 入口脚本 scripts/run_sim_gz.sh(此前缺失,被 install_sim_env.sh
|
||
# 与 build_sim.sh 的提示语引用,照做会报文件不存在)
|
||
# ✔ 场景模型 models/ 已有工件(带检测亮环的孔板)、通止规、规座
|
||
# 布局在 config/scene.yaml,启动时动态 spawn 并做摆放/几何校验
|
||
# ✔ 触觉 **不需要自定义插件**:用 Gazebo 内置的 force_torque(三向力)
|
||
# + contact(接触布尔)传感器,由 sim_tactile_adapter 合成
|
||
# arm_msgs/TactileStatus。标定与轴向映射在 config/tactile.yaml
|
||
# (轴向是实测的,不是猜的)。视触觉图像只能是占位。
|
||
# ✔ 相机出图 D405/D435 已注册并桥接到 ROS,且**几何正确性已验证**
|
||
# 桥接+适配:config/cameras.yaml → sim_cameras(纯逻辑:
|
||
# 话题映射 / 深度换算 / 内参,单测覆盖)
|
||
# → sim_camera_adapter(改光学系 frame_id、米→毫米、覆盖内参)
|
||
# 验收工具:scripts/check_sim_cameras.py(反投影必须命中地面/台面)
|
||
# 实测:d435 台面 11.9% / 地面 75.3%;d405 台面 37.8% / 地面 54.0%
|
||
# ✔ 配置接线 sim.world 现在真的从 config/site.yaml 读(此前硬编码,改了无效)
|
||
#
|
||
# 所以:相机、触觉三力、场景物体都可用,装配状态机(P6)可以在这里验几何与接触。
|
||
# 仍然做不到的:视触觉的**图像**(深度图/力矢量图只有占位),以及走真机那套
|
||
# 夹爪服务接口(仿真里目前直接发关节指令)。
|
||
#
|
||
# 实测踩到的坑(都属于"静默失效",后续改仿真务必先看这几条):
|
||
# 1. 官方 flexiv_description 直接喂 Gazebo 会被 sdformat 拒掉:7 轴本体缺
|
||
# <inertial> 包裹;夹爪有零质量 link 导致惯性叠加出 NaN。两者都只有
|
||
# Gazebo 会暴露,RViz/MoveIt 完全正常。已在启动时归一化解决。
|
||
# 2. world 文件里只要声明了**任何一个** <plugin>,Gazebo 就不再加载默认的
|
||
# Physics/UserCommands/SceneBroadcaster。少了 UserCommands,
|
||
# /world/<名>/create 服务不存在 → 模型根本没进 Gazebo,而 create 仍打印
|
||
# "OK creation of entity"。所以这三个系统必须在 world 里显式声明。
|
||
# 3. gz 的插件搜索路径不含 $CONDA_PREFIX/lib(ros_gz_sim 的 launch 只拼
|
||
# LD_LIBRARY_PATH,而 conda 激活不往那里加),于是 gz_ros2_control 报
|
||
# "couldn't find shared library" 而库其实就在那儿。必须在 ros2 launch 之前设
|
||
# GZ_SIM_SYSTEM_PLUGIN_PATH;资源路径同理(否则 mesh 全加载不出来)。
|
||
# 两者都由 scripts/sim_env.sh 负责。详见该文件里的长注释。
|
||
# 4. **gz 发的 camera_info 是错的**:恒为 fx=fy=277, cx=160, cy=120
|
||
# (等于它的默认 60° 视场 + 半分辨率主点),而渲染用的是配置的 87° 视场。
|
||
# 照抄它做三维反投影,点会偏半米量级,而单目预览完全看不出来。
|
||
# 已改为由 sim_cameras.intrinsics() 按 cameras.yaml 的视场重算并覆盖。
|
||
# 代价:cameras.yaml 的 horizontal_fov 必须与 SDF 一致,启动时会自动比对告警。
|
||
# 5. **相机位姿的 rpy 顺序**:URDF 相机是"X 朝前"约定,**视线是本体 X 轴**;
|
||
# 而 rpy 复合顺序是 Rz·Ry·Rx,"先俯 50° 再偏航 180°"会把视线翻到背面。
|
||
# d435 就踩了这个坑:相机背对台面、只看得见地面,却什么都不报,
|
||
# 而且几何自洽(点确实落在真实地面上),只查数值发现不了。
|
||
# 已修正并加回归测试(test_robot_description 的 d435 位姿用例)。
|
||
# 6. 接触传感器的消息是**事件式**的:只在接触时发,离开时不发。写成
|
||
# "收到=接触/没收到=不管"会让 contact 永远停在 True。判据必须是
|
||
# "最近 contact_timeout_s 内收到过"。
|
||
# 7. **contact-system 发的 Contacts 消息里没有力**(wrenches 恒空,实测确认)。
|
||
# 消息结构里有这个字段,看起来"应该有"—— 这是最耗时的一个坑。三向力改由
|
||
# force_torque 传感器提供;且它所需的 forcetorque-system 也必须像坑 2 那样
|
||
# 在 world 里显式声明,否则话题根本不出现。
|
||
# 8. **sdformat 会用占位值填缺失的东西**:<contact> 缺 <collision> → "__default__",
|
||
# 缺 <topic> → "__default_topic__"。于是"检查字段是否为空"的校验代码永远通过。
|
||
# check_tactile_collisions 已显式判这两个占位值(有负向单测)。
|
||
# 通用教训:校验代码自己会静默失效。
|
||
# 9. **<mimic> 标签会被 sdformat 丢掉**:夹爪从动指节不再跟随(控制器 active
|
||
# 但手指不动)。解法是在归一化时记录 mimic 关系、去掉标签、把全部 7 个夹爪
|
||
# 关节都接入 ros2_control(从动关节原先 effort=0,也一并修掉)。
|
||
# 10. `ign model -m <名> -p` 的文本输出没有稳定契约,验收脚本按"宽松解析 +
|
||
# 解析不出来就跳过"处理 —— 宁可漏报,不可误报。
|
||
# =============================================================================
|
||
|
||
id: gazebo
|
||
label: Gazebo 仿真
|
||
kind: sim
|
||
vendor: openrobotics
|
||
# 实测版本。注意:Humble 配的是 Fortress(ignition-gazebo6),不是现代 gz-sim ——
|
||
# 后者要 libprotobuf>=6.31,与 RoboStack 给 Humble 钉的 5.29.3 解不开依赖。
|
||
version: "Fortress 6.16 (ignition-gazebo6, ros_gz + gz_ros2_control 0.7.15)"
|
||
|
||
description: >
|
||
物理仿真平台。环境、世界、机器人描述、控制器与启动器均已就绪并端到端验证
|
||
(起 Gazebo → spawn → 两个控制器 active → 机械臂在重力下站住)。
|
||
D405/D435 已桥接到 ROS,且几何正确性有自动验收(scripts/check_sim_cameras.py)。
|
||
场景物体(工件/通止规/规座)在 devices/sim/gazebo/models/,布局在 config/scene.yaml,
|
||
摆放与成像几何有自动验收(scripts/check_sim_scene.py)。
|
||
触觉用 Gazebo 内置的 force_torque + contact 传感器,由 sim_tactile_adapter
|
||
合成 arm_msgs/TactileStatus;轴向映射是实测的(config/tactile.yaml)。
|
||
视触觉的**图像**(深度图/力矢量图)在仿真里只能是占位,不可信。
|
||
sim.world 已真正从 config/site.yaml 读取(physics_enable / joint_prefix 尚未接线,
|
||
写了不一致的值会告警而非静默忽略,见文件头 config_schema)。
|
||
手册见 devices/sim/gazebo/README.md(含建模要求、导入格式、配置方式、避坑清单)。
|
||
|
||
# --- 物料放置要求 ----------------------------------------------------------
|
||
assets:
|
||
- path: worlds
|
||
required: false
|
||
what: Gazebo 世界文件(.world / .sdf)
|
||
format: Gazebo 原生格式
|
||
naming: 场景名.world,如 assembly.world
|
||
where_used: 由 site.yaml 的 sim.world 指向
|
||
|
||
- path: models
|
||
required: false
|
||
what: 工件 / 夹具 / 通止规模型
|
||
format: >
|
||
每个模型一个子目录,内含 model.sdf 与 model.config(两个文件名都是固定的,
|
||
Gazebo 按这个约定查找)。原点必须在**底面中心**,这样 scene.yaml 里的
|
||
pose.z 就等于"放在台面上的高度"。碰撞体用 box/cylinder 等简单几何,
|
||
视觉可以额外加只服务于算法的细节(如工件上的检测亮环)。
|
||
naming: >
|
||
目录名 = SDF 里的 <model name> = model.config 里的 <name> = 推理用的类别名
|
||
(如 workpiece),四者必须一致,否则仿真里算出的坐标与推理结果对不上。
|
||
网格单位必须是米,用毫米建模的必须先缩放。
|
||
where_used: >
|
||
由 config/scene.yaml 的 objects[].model 引用,启动时动态 spawn;
|
||
摆放合法性与成像几何由 scripts/check_sim_scene.py 验收。
|
||
|
||
- path: urdf
|
||
required: false
|
||
what: 仿真版机器人描述
|
||
format: .urdf / .xacro,需含 gazebo 与 ros2_control 标签
|
||
naming: 关节前缀必须以 sim_ 开头,与真实臂(Rizon4-xxxxxx_)区分
|
||
where_used: 仿真机器人加载
|
||
|
||
- path: sensor_plugins
|
||
required: false
|
||
what: 传感器插件(自定义 C++ 插件用;当前**为空**)
|
||
format: 插件源码 + CMakeLists.txt
|
||
naming: 插件类名以 Orisys 开头
|
||
where_used: >
|
||
目前没有使用者:触觉走 Gazebo 内置的 force_torque + contact 传感器,
|
||
由 sim_tactile_adapter(Python)合成 TactileStatus,无需自研插件。
|
||
保留此目录用于"以后真需要写插件"的场景(如自研视触觉弹性体成像)。
|
||
|
||
# --- 适配器 ----------------------------------------------------------------
|
||
driver:
|
||
package: arm_sim_gazebo
|
||
executable: gazebo_sim_launcher
|
||
build: colcon
|
||
notes: >
|
||
已实现并在 setup.py 注册。它只做"准备描述(展开 + 归一化)+ 转发给
|
||
launch/gazebo_sim.launch.py",启动逻辑不在可执行文件里 —— ros2 launch 的
|
||
日志/退出码/中断处理都更成熟,自己重造一份只会更难调试。
|
||
入口建议走 scripts/run_sim_gz.sh(它会先 source scripts/sim_env.sh,
|
||
而 Gazebo 的插件/资源搜索路径必须在 ros2 launch 之前设好,见 sim_env.sh)。
|
||
单独排查描述用:gazebo_sim_launcher --check / --print-urdf。
|
||
|
||
# --- 配置项声明 ------------------------------------------------------------
|
||
# 设计约束:仿真与真机共用同一套 site.yaml 参数(servo.* / hole_detect.stage2.*),
|
||
# 避免「仿真调好的值搬到真机不生效」。只有物理引擎自身的参数放在 sim.* 下。
|
||
#
|
||
# ⚠️ 每个键都必须如实标注"是否真的接线"(wired)。声明了却没接线的键是最难查的
|
||
# 一类问题:用户按文档改了 site.yaml,行为毫无变化,也不报错。
|
||
# 目前由 arm_sim_gazebo.sim_site 读取的是 world(已接线);
|
||
# physics_enable / joint_prefix 尚未接线,若其值与世界/描述的实际状态冲突,
|
||
# 启动时会打印告警 —— 见 sim_site.unwired_warnings()。
|
||
config_schema:
|
||
- key: world
|
||
site_path: sim.world
|
||
type: str
|
||
required: false
|
||
default: ""
|
||
wired: true
|
||
doc: >
|
||
仿真世界文件路径。留空 = 用内置默认(devices/sim/gazebo/worlds/assembly.world)。
|
||
相对路径按仓库根解析。优先级:命令行 --world > 本项 > 内置默认。
|
||
实现在 arm_sim_gazebo/sim_site.resolve_world()。
|
||
|
||
- key: physics_enable
|
||
site_path: sim.physics_enable
|
||
type: bool
|
||
required: false
|
||
default: true
|
||
wired: false
|
||
doc: >
|
||
是否启用物理引擎。**尚未接线**:世界文件里的 <physics>(重力/步长)是写死的,
|
||
本项不会传导过去。若这里写的值与世界实际状态不符,启动时告警而不是静默忽略。
|
||
要真正生效,需改 world 文件里的 <gravity>,或把本项接进启动流程。
|
||
|
||
- key: joint_prefix
|
||
site_path: sim.joint_prefix
|
||
type: str
|
||
required: false
|
||
default: sim_
|
||
wired: false
|
||
doc: >
|
||
仿真关节名前缀。必须与真实臂的前缀(Rizon4-xxxxxx_)不同,
|
||
否则仿真与真机的 TF 会互相污染。
|
||
**尚未接线**:实际前缀由 urdf/arm.sim.urdf.xacro 的 arm_prefix 决定,
|
||
且 config/controllers.yaml 里硬编码了 sim_joint1..7。写了个不同的值会告警。
|
||
要真改,这三处必须一起改。
|