Files
ros_contorl/scripts/sim_env.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

112 lines
5.5 KiB
Bash
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 Fortress + 本工作区的仿真构建产物)。
#
# 用法: source scripts/sim_env.sh
#
# 与 scripts/env.sh(真机环境)的关系:
# 两者互斥,各自用一套 conda 环境 + 一套构建产物。
# 真机: ros_flexiv → build/ install/
# 仿真: ros_gz_sim → build_sim/ install_sim/
# 切换时**最好开新的 shell**。同一 shell 里来回切会残留 PATH/PYTHONPATH,
# 症状详见 scripts/lib/conda_switch.sh 顶部的说明(实测踩过)。
# 这里会先退到 base 再切以尽量规避,但开新 shell 才是稳妥做法。
#
# 为什么仿真用独立的 build/install 目录:
# 工作区只有一份源码。如果仿真也往 install/ 里写,就会把真机环境的构建
# 产物覆盖掉(两边的依赖路径不同)。分目录后互不干扰。
# =============================================================================
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SIM_ENV_NAME="${SIM_ENV_NAME:-ros_gz_sim}"
REAL_ENV_NAME="${REAL_ENV_NAME:-ros_flexiv}"
CONDA_SH="${HOME}/anaconda3/etc/profile.d/conda.sh"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/conda_switch.sh"
if [[ ! -f "${CONDA_SH}" ]]; then
echo "找不到 conda: ${CONDA_SH}" >&2
return 1 2>/dev/null || exit 1
fi
# shellcheck disable=SC1090
source "${CONDA_SH}"
# 关掉 `set -u`:ROS 的 activate.d 脚本会引用未绑定的 CONDA_BUILD,
# 调用方常带 set -u,那样会直接把 source 打断,表现为"脚本毫无输出就退出"。
_sim_had_u=0
case "$-" in *u*) _sim_had_u=1; set +u ;; esac
if ! conda_switch_clean "${SIM_ENV_NAME}" "${REAL_ENV_NAME}"; then
[[ "${_sim_had_u}" -eq 1 ]] && set -u
unset _sim_had_u
echo "无法激活 conda 环境 ${SIM_ENV_NAME}。" >&2
echo "先创建: bash ${ROOT}/scripts/install_sim_env.sh" >&2
return 1 2>/dev/null || exit 1
fi
# 仿真构建产物(由 scripts/build_sim.sh 用 --install-base 生成,与真机隔离)
if [[ -f "${ROOT}/install_sim/setup.bash" ]]; then
# shellcheck disable=SC1091
source "${ROOT}/install_sim/setup.bash"
else
echo "提示: install_sim/ 还不存在,先跑 bash scripts/build_sim.sh" >&2
fi
[[ "${_sim_had_u}" -eq 1 ]] && set -u
unset _sim_had_u
export YOLO_AUTOINSTALL=false
export ROS2_WS="${ROOT}"
# 仿真单独一个 DDS domain:避免与正在跑的真机节点互相抢话题/污染 TF。
# 真机用的是默认 domain,两者天然隔离。
export ROS_DOMAIN_ID="${ROS_DOMAIN_ID:-77}"
# =============================================================================
# Gazebo 的插件与资源搜索路径 —— 必须在这里设,不能只在 launch 里设
# =============================================================================
# ros_gz_sim 的 gz_sim.launch.py 用的是 `from os import environ`,即在**生成
# launch 描述时**读进程环境,然后把它算出来的值作为 additional_env 传给 gz。
# 所以用 launch 的 SetEnvironmentVariable 补是没用的:那个值会被 gz_sim.launch.py
# 自己算的那份字典覆盖掉。必须在 ros2 launch 之前就进到进程环境里。
#
# 1) 插件搜索路径
# 缺了它,Gazebo 会在加载 gz_ros2_control 时报:
# [Err] [SystemLoader.cc:94] Failed to load system plugin
# [libgz_ros2_control-system.so] : couldn't find shared library.
# 而库其实就在 $CONDA_PREFIX/lib 下(`ldd` 也全齐)。原因是 gz 的
# SystemLoader 先在搜索路径里找文件,找不到就直接放弃,**不会**去
# dlopen 一个裸文件名(所以 conda 二进制的 RPATH 兜不住)。
# 又因为 gz_sim.launch.py 只把 LD_LIBRARY_PATH 拼进搜索路径,而 conda 激活
# 并不往 LD_LIBRARY_PATH 里加 $CONDA_PREFIX/lib,于是这条路径永远缺失。
#
# 2) 资源搜索路径
# gz 的 URDF 解析器会把 mesh 的 package:// 改写成 model://,
# 而 model://<名> 是去"资源路径下的子目录"里找的。因此这里放的必须是
# **各包 share 目录的上一层**(该层的子目录名就是包名),不是 share 目录本身。
# 缺了它的表现是一堆非致命但很吵的报错(视觉网格加载不出来),
# 并且**碰撞网格同样加载不出来** —— 后者会让装配任务的接触判据失效。
if [[ -n "${CONDA_PREFIX:-}" ]]; then
export GZ_SIM_SYSTEM_PLUGIN_PATH="${CONDA_PREFIX}/lib${GZ_SIM_SYSTEM_PLUGIN_PATH:+:${GZ_SIM_SYSTEM_PLUGIN_PATH}}"
# IGN_* 是 Fortress(改名之前)用的名字,两个都设,兼容新旧插件查找逻辑
export IGN_GAZEBO_SYSTEM_PLUGIN_PATH="${CONDA_PREFIX}/lib${IGN_GAZEBO_SYSTEM_PLUGIN_PATH:+:${IGN_GAZEBO_SYSTEM_PLUGIN_PATH}}"
fi
_res_paths=()
for _d in "${ROOT}"/install_sim/*/share; do
[[ -d "${_d}" ]] && _res_paths+=("${_d}")
done
_res_paths+=("${ROOT}/devices/sim/gazebo/models") # 以后往这里放自定义模型
_res_joined="$(IFS=:; echo "${_res_paths[*]}")"
export GZ_SIM_RESOURCE_PATH="${_res_joined}${GZ_SIM_RESOURCE_PATH:+:${GZ_SIM_RESOURCE_PATH}}"
export IGN_GAZEBO_RESOURCE_PATH="${_res_joined}${IGN_GAZEBO_RESOURCE_PATH:+:${IGN_GAZEBO_RESOURCE_PATH}}"
unset _res_paths _res_joined _d
# 仿真与真机共用同一份 site.yaml —— 这样"仿真里调好的参数搬到真机也生效"。
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/load_site_config.sh" 2>/dev/null || true
echo "[sim_env] CONDA=${CONDA_DEFAULT_ENV} ROS_DISTRO=${ROS_DISTRO} ROS_DOMAIN_ID=${ROS_DOMAIN_ID}"