fix: 完善 MuJoCo 退出与测试环境
This commit is contained in:
@@ -104,7 +104,7 @@ source install/setup.bash
|
|||||||
|
|
||||||
真机模式还需要安装睿尔曼 Python API2。若未安装,mock 模式仍可正常使用;真机启动时会提示缺少 `Robotic_Arm` 包。
|
真机模式还需要安装睿尔曼 Python API2。若未安装,mock 模式仍可正常使用;真机启动时会提示缺少 `Robotic_Arm` 包。
|
||||||
|
|
||||||
遥操作节点固定由 `/home/robot/miniconda3/envs/xr/bin/python` 启动,并复用其中的 Python 3.10、Placo 0.9.4、Pinocchio 3.7.0 和 NumPy 2.2.6。`ros2`、`colcon` 和 `udp_controller_receiver` 仍使用系统 Python。禁止通过 `pip --user`、`sudo pip` 或系统安装升级 Placo、Pinocchio、EigenPy 和 NumPy。
|
遥操作和 MuJoCo 节点固定由 `/home/robot/miniconda3/envs/xr/bin/python` 启动,并复用其中的 Python 3.10、Placo 0.9.4、Pinocchio 3.7.0、NumPy 2.2.6 和 MuJoCo 3.10.0。`ros2`、`colcon`、pytest 和 `udp_controller_receiver` 仍使用系统 Python。禁止通过 `pip --user`、`sudo pip` 或系统安装升级 Placo、Pinocchio、EigenPy 和 NumPy。
|
||||||
|
|
||||||
只读检查 Placo 版本:
|
只读检查 Placo 版本:
|
||||||
|
|
||||||
@@ -115,6 +115,20 @@ source install/setup.bash
|
|||||||
|
|
||||||
输出必须为 `0.9.4`。
|
输出必须为 `0.9.4`。
|
||||||
|
|
||||||
|
同时检查 MuJoCo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/robot/miniconda3/envs/xr/bin/python -c \
|
||||||
|
"import mujoco; print(mujoco.__version__)"
|
||||||
|
```
|
||||||
|
|
||||||
|
当前验证版本为 `3.10.0`。系统 pytest 会通过 `xr_rm_mujoco/test/conftest.py` 复用该固定 XR 环境中的 MuJoCo,因此新包可直接按 ROS2 标准方式测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
colcon test --packages-select xr_rm_mujoco --event-handlers console_direct+
|
||||||
|
colcon test-result --verbose
|
||||||
|
```
|
||||||
|
|
||||||
如果希望 `launcher_ui.py` 从任意目录找到工作空间,可以设置:
|
如果希望 `launcher_ui.py` 从任意目录找到工作空间,可以设置:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"""让 ROS2 系统 pytest 复用项目固定 XR 环境中的 MuJoCo。"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
XR_SITE_PACKAGES = Path(
|
||||||
|
"/home/robot/miniconda3/envs/xr/lib/python3.10/site-packages"
|
||||||
|
)
|
||||||
|
if XR_SITE_PACKAGES.is_dir():
|
||||||
|
sys.path.append(str(XR_SITE_PACKAGES))
|
||||||
@@ -80,7 +80,8 @@ def test_main_closes_viewer_cleanly_on_keyboard_interrupt(monkeypatch) -> None:
|
|||||||
node.close_viewer = lambda: close_viewer(node)
|
node.close_viewer = lambda: close_viewer(node)
|
||||||
init = Mock()
|
init = Mock()
|
||||||
spin = Mock(side_effect=KeyboardInterrupt)
|
spin = Mock(side_effect=KeyboardInterrupt)
|
||||||
sleep = Mock()
|
sleep = Mock(side_effect=[KeyboardInterrupt, None])
|
||||||
|
monotonic = Mock(side_effect=[0.0, 0.0, 0.04])
|
||||||
shutdown = Mock()
|
shutdown = Mock()
|
||||||
|
|
||||||
monkeypatch.setattr(simulator_module, "DualArmSimulator", lambda: node)
|
monkeypatch.setattr(simulator_module, "DualArmSimulator", lambda: node)
|
||||||
@@ -88,6 +89,7 @@ def test_main_closes_viewer_cleanly_on_keyboard_interrupt(monkeypatch) -> None:
|
|||||||
monkeypatch.setattr(simulator_module.rclpy, "spin", spin)
|
monkeypatch.setattr(simulator_module.rclpy, "spin", spin)
|
||||||
monkeypatch.setattr(simulator_module.rclpy, "ok", lambda: True)
|
monkeypatch.setattr(simulator_module.rclpy, "ok", lambda: True)
|
||||||
monkeypatch.setattr(simulator_module.time, "sleep", sleep)
|
monkeypatch.setattr(simulator_module.time, "sleep", sleep)
|
||||||
|
monkeypatch.setattr(simulator_module.time, "monotonic", monotonic)
|
||||||
monkeypatch.setattr(simulator_module.rclpy, "shutdown", shutdown)
|
monkeypatch.setattr(simulator_module.rclpy, "shutdown", shutdown)
|
||||||
|
|
||||||
simulator_module.main(["--test"])
|
simulator_module.main(["--test"])
|
||||||
@@ -95,7 +97,9 @@ def test_main_closes_viewer_cleanly_on_keyboard_interrupt(monkeypatch) -> None:
|
|||||||
init.assert_called_once_with(args=["--test"])
|
init.assert_called_once_with(args=["--test"])
|
||||||
spin.assert_called_once_with(node)
|
spin.assert_called_once_with(node)
|
||||||
viewer.close.assert_called_once_with()
|
viewer.close.assert_called_once_with()
|
||||||
sleep.assert_called_once_with(0.1)
|
assert [call.args[0] for call in sleep.call_args_list] == pytest.approx(
|
||||||
|
[0.1, 0.06]
|
||||||
|
)
|
||||||
node.destroy_node.assert_called_once_with()
|
node.destroy_node.assert_called_once_with()
|
||||||
shutdown.assert_called_once_with()
|
shutdown.assert_called_once_with()
|
||||||
assert node._viewer is None
|
assert node._viewer is None
|
||||||
|
|||||||
@@ -181,7 +181,16 @@ class DualArmSimulator(Node):
|
|||||||
if self._viewer is not None:
|
if self._viewer is not None:
|
||||||
self._viewer.close()
|
self._viewer.close()
|
||||||
# MuJoCo 在后台 daemon 线程释放 GLX;立即退出解释器会触发段错误。
|
# MuJoCo 在后台 daemon 线程释放 GLX;立即退出解释器会触发段错误。
|
||||||
time.sleep(0.1)
|
deadline = time.monotonic() + 0.1
|
||||||
|
while True:
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0.0:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
time.sleep(remaining)
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
continue
|
||||||
self._viewer = None
|
self._viewer = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user