# ============================================================================= # 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 轴本体缺 # 包裹;夹爪有零质量 link 导致惯性叠加出 NaN。两者都只有 # Gazebo 会暴露,RViz/MoveIt 完全正常。已在启动时归一化解决。 # 2. world 文件里只要声明了**任何一个** ,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 会用占位值填缺失的东西**: 缺 → "__default__", # 缺 → "__default_topic__"。于是"检查字段是否为空"的校验代码永远通过。 # check_tactile_collisions 已显式判这两个占位值(有负向单测)。 # 通用教训:校验代码自己会静默失效。 # 9. ** 标签会被 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.config 里的 = 推理用的类别名 (如 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: > 是否启用物理引擎。**尚未接线**:世界文件里的 (重力/步长)是写死的, 本项不会传导过去。若这里写的值与世界实际状态不符,启动时告警而不是静默忽略。 要真正生效,需改 world 文件里的 ,或把本项接进启动流程。 - 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。写了个不同的值会告警。 要真改,这三处必须一起改。