Files
ros_contorl/scripts/sim_env.sh
T
arm_dev aed5d326a0 仿真环境串味根治:清残留路径 + 补 conda lib + 修入口脚本 shebang
症状一(最隐蔽):同一 shell 里先 source 过真机环境(scripts/env.sh)再
source 仿真环境,install/ 与 ros_flexiv 的路径不会被 conda deactivate 撤掉,
长在 install_sim/ 前面:
  - PYTHONPATH 里真机那份 arm_msgs 压住仿真那份 → 消息类型混用
  - LD_LIBRARY_PATH 里没有 $CONDA_PREFIX/lib → sim_tactile_adapter 订阅
    ros_gz_interfaces/Contacts 时抛
      Type support not from this implementation ...
      cannot open shared object file
    而库其实就在 conda 里、ldd 全齐。整条触觉链静默死掉(话题不发)。

症状二:setuptools 写 console_scripts 用的是**构建那一刻**的 python。
若在真机环境里跑过 build_sim.sh,install_sim/ 里 7 个入口脚本的 shebang
会指向 ros_flexiv —— 而 ros2 run 是照 shebang 执行的,于是**仿真节点一直
跑在真机 conda 环境里**。两个环境都是 Humble+py3.11,所以"居然能跑",
只有偶发 dlopen / 反序列化怪错。

修法:
  - conda_switch.sh 新增 path_drop_patterns:按 glob 从冒号列表里删条目
  - sim_env.sh 清掉真机残留 + 末尾补 $CONDA_PREFIX/lib(优先级最低)
  - 新增 lib/fix_shebangs.sh,build.sh / build_sim.sh 收尾各扫一遍(幂等)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 14:42:50 +08:00

138 lines
7.3 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
# --- 清掉真机环境留下的路径残留 ---------------------------------------------
# 同一个 shell 里先 source 过 scripts/env.sh(真机)再 source 本文件时,install/
# 与 ros_flexiv 的路径不会被 conda deactivate 撤掉,会长在 install_sim/ 前面,
# 导致 import 到真机那份包、dlopen 到真机那份库。详见 conda_switch.sh 里
# path_drop_patterns 的注释(那里记了 sim_tactile_adapter 崩溃的实测症状)。
path_drop_patterns PYTHONPATH "${ROOT}/install/*" "${ROOT}/build/*" "*/envs/${REAL_ENV_NAME}/*"
path_drop_patterns LD_LIBRARY_PATH "${ROOT}/install/*" "*/envs/${REAL_ENV_NAME}/*"
path_drop_patterns PATH "${ROOT}/install/*" "*/envs/${REAL_ENV_NAME}/*"
path_drop_patterns AMENT_PREFIX_PATH "${ROOT}/install" "*/envs/${REAL_ENV_NAME}"
path_drop_patterns CMAKE_PREFIX_PATH "${ROOT}/install" "*/envs/${REAL_ENV_NAME}"
# --- 补上 conda 环境自己的 lib ----------------------------------------------
# conda 激活**不会**把 $CONDA_PREFIX/lib 放进 LD_LIBRARY_PATH(见下面 GZ 插件
# 路径那段长注释)。对 gz 我们靠 GZ_SIM_SYSTEM_PLUGIN_PATH 兜住了,但
# rosidl 的 typesupport 是靠 dlopen("lib<包名>__rosidl_typesupport_*.so") 找的,
# 只认 ld.so 的搜索路径。于是 conda 里带的(不在本工作区的)消息包会在
# create_publisher/create_subscription 时抛
# Type support not from this implementation ... cannot open shared object file
# 追加在**末尾**(优先级最低),避免压过系统/工作区里已有的同名库。
if [[ -n "${CONDA_PREFIX:-}" && -d "${CONDA_PREFIX}/lib" ]]; then
case ":${LD_LIBRARY_PATH:-}:" in
*":${CONDA_PREFIX}/lib:"*) ;;
*) export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${CONDA_PREFIX}/lib" ;;
esac
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}"