Files
ros_contorl/scripts/run_sim_gz.sh
T
arm_dev 01940f54e7 P5c-3 仿真启动器跑通:Gazebo + spawn + 两个控制器 active
端到端验收(headless,本机无显示器):
    ign 加载 Physics/UserCommands/SceneBroadcaster/Sensors + gz_ros2_control
    机械臂 spawn 进 assembly 世界,7 个 sim_joint 全在 /joint_states
    joint_state_broadcaster 与 rizon_arm_controller 双双 active
    world → sim_tcp 可查,且末端位姿 3 秒内不变(重力下站住)
    网格解析报错 0 条

新增:
  scripts/run_sim_gz.sh                  此前**不存在**,却被 install_sim_env.sh
                                         与 build_sim.sh 的提示语引用
  arm_sim_gazebo/robot_description.py    展开 + 归一化,launch 与 CLI 共用一份
  arm_sim_gazebo/gazebo_sim_launcher.py  薄壳:准备描述后转发给 launch 文件
  launch/gazebo_sim.launch.py            起 gz → 发归一化 URDF → spawn → 拉控制器
  test/test_robot_description.py         含"仓库里这份 world 必须过守卫"的测试

接线过程中实测踩到并修掉的三个坑,共同特征是**每一步看起来都成功**:

1) urdf_normalize 在 Python 路径下静默失效(我自己引入的,最严重)
   xacro 的输出格式在不同环境里不一样:真机环境的 CLI 缩进成多行,而仿真环境
   走 xacro.process_file().toxml() 时全挤在一行。归一化的正则要求标签之间必须有
   \n,于是**仿真路径下它什么都没做**(报告"包裹 0 处"),而 CLI 路径下正常 ——
   之前那次"已修好"的验证走的就是 CLI,所以看起来过了。
   后果是 Gazebo 会丢掉整条机械臂,正是这个归一化本来要防的事。
   已改成对空白格式零假设,并加回归测试锁住。

2) world 里声明了 <plugin> 会把默认系统挤掉
   只要 world 声明任何一个 <plugin>,Gazebo 就不再加载 server.config 的
   Physics/UserCommands/SceneBroadcaster。缺 UserCommands →
   /world/<名>/create 服务不存在 → ros_gz_sim create 调服务超时后回退发 topic、
   没人收 → **模型从未进入 Gazebo**,而 create 节点仍打印
   "OK creation of entity",控制器则永远卡在 "waiting for service"。
   已在这三个系统在 assembly.world 里显式声明,并加纯文本守卫
   check_base_plugins(),让"加了插件忘了补基础系统"在启动前就被指出。

3) gz 的插件与资源搜索路径缺 $CONDA_PREFIX/lib
   gz_ros2_control 报 "Failed to load system plugin
   [libgz_ros2_control-system.so] : couldn't find shared library",而 ldd 全齐、
   库就在 $CONDA_PREFIX/lib 下。原因是 gz 的 SystemLoader 先在搜索路径里找文件,
   找不到就直接放弃,不去 dlopen 裸文件名(所以 conda 二进制的 RPATH 兜不住);
   而 ros_gz_sim 只把 LD_LIBRARY_PATH 拼进搜索路径,conda 激活并不往那里加。
   同一原因导致 mesh 的 model:// 也解析不了(视觉报错很吵,但更要紧的是
   **碰撞网格也加载不出来**,会让后续装配的接触判据失效)。
   由于 gz_sim.launch.py 用 `from os import environ` 在生成 launch 描述时就读
   进程环境,这些变量**必须在 ros2 launch 之前**设好,因此放在 sim_env.sh ——
   在 launch 文件里用 SetEnvironmentVariable 补是无效的,会被它自己算的那份
   字典覆盖。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 14:48:16 +08:00

82 lines
3.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =============================================================================
# 启动 Gazebo 仿真。
#
# 用法:
# bash scripts/run_sim_gz.sh # 无头(无显示器环境用这个)
# bash scripts/run_sim_gz.sh --gui # 带 Gazebo GUI
# bash scripts/run_sim_gz.sh --check # 只自检描述,不启动任何东西
# bash scripts/run_sim_gz.sh --print-urdf # 打印归一化后的 URDF
# bash scripts/run_sim_gz.sh --dry-run # 只打印将执行的命令
# bash scripts/run_sim_gz.sh --verbose # 打开 Gazebo 详细日志
# bash scripts/run_sim_gz.sh --world /abs/other.world
# bash scripts/run_sim_gz.sh --no-gripper --no-sensors # 最省资源的运动学验证
#
# 与 scripts/run_sim.sh 的区别(两个名字容易混,这里说清):
# run_sim.sh ← 走 arm_bringup 的 **mock** 驱动:假数据 + RViz,无物理
# run_sim_gz.sh ← 走 Gazebo:真物理、真接触
# 两者互不影响,也都不需要真机。要验证力控/接触类逻辑只能用后者。
#
# ⚠️ 这个脚本此前**不存在**,却被 install_sim_env.sh(两处)与 build_sim.sh 的
# 提示语引用 —— 照做会直接报"文件不存在"。现在补上。
# =============================================================================
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${ROOT}"
# 显式映射到 gazebo_sim_launcher 的选项,不做原样透传。
# 透传会把拼错的选项静默吞掉(ros2 launch 对未知的 key:=value 不报错),
# 而"我明明设了 --gui 却没生效"这类问题极难查。
LAUNCH_ARGS=()
PASS_ONLY=0 # 1 = 只准备描述,不启动(--check / --print-urdf / --dry-run)
while [[ $# -gt 0 ]]; do
case "$1" in
--gui) LAUNCH_ARGS+=(--headless false); shift ;;
--headless) LAUNCH_ARGS+=(--headless true); shift ;;
--verbose|-v) LAUNCH_ARGS+=(--verbose); shift ;;
--no-gripper) LAUNCH_ARGS+=(--no-gripper); shift ;;
--no-sensors) LAUNCH_ARGS+=(--no-sensors); shift ;;
--check|--print-urdf|--dry-run)
LAUNCH_ARGS+=("$1"); PASS_ONLY=1; shift ;;
--world)
[[ -n "${2:-}" ]] || { echo "--world 需要跟一个路径" >&2; exit 2; }
LAUNCH_ARGS+=(--world "$2"); shift 2 ;;
-h|--help) sed -n '2,18p' "$0"; exit 0 ;;
*) echo "未知参数: $1(用 --help 看用法)" >&2; exit 2 ;;
esac
done
if [[ ! -f "${ROOT}/install_sim/setup.bash" ]]; then
echo "install_sim/ 还不存在。先跑:" >&2
echo " bash scripts/build_sim.sh" >&2
exit 1
fi
# sim_env.sh 会先退到 base 再激活目标环境,避免同一 shell 里来回切造成的
# PATH/PYTHONPATH 残留(详见 scripts/lib/conda_switch.sh 顶部的实测说明)。
# shellcheck disable=SC1091
source "${ROOT}/scripts/sim_env.sh"
if ! command -v ign >/dev/null 2>&1; then
echo "找不到 ign(Gazebo Fortress 的 CLI)。先跑:" >&2
echo " bash scripts/install_sim_env.sh" >&2
exit 1
fi
if [[ "${PASS_ONLY}" -eq 1 ]]; then
# 这些选项不启动任何东西,只准备描述,直接转发。
exec ros2 run arm_sim_gazebo gazebo_sim_launcher "${LAUNCH_ARGS[@]}"
fi
echo "=== 启动 Gazebo 仿真 ==="
echo " 环境 : ${CONDA_DEFAULT_ENV:-?} ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-?}"
echo " 退出方式 : Ctrl-C"
echo
echo " 首次启动约需 10~20 秒(Gazebo 初始化 + spawn + 拉控制器)。"
echo " 验收: ros2 control list_controllers 应看到两个 active"
echo
exec ros2 run arm_sim_gazebo gazebo_sim_launcher "${LAUNCH_ARGS[@]}"