Compare commits

..

1 Commits

Author SHA1 Message Date
Scunfu 59b0583444 Initialize odin1 workspace 2026-08-13 12:52:06 +08:00
1179 changed files with 1170 additions and 2389897 deletions
-2
View File
@@ -1,2 +0,0 @@
# Keep structured point-cloud assets byte-identical across platforms.
*.pcd -text
-55
View File
@@ -1,55 +0,0 @@
# Local reference material
/00_reference/
# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.uv-cache/
# Build outputs
build/
install/
devel/
log/
*.o
*.so
*.a
*.elf
*.hex
*.bin
*.axf
# Required vendored Odin runtime libraries in the early Sim2Real release
!05_software/real/sim2real/vendored/odin1_imu/build/
!05_software/real/sim2real/vendored/odin1_imu/build/libodin1_imu_bridge.so
!05_software/real/sim2real/vendored/odin1_imu/lib/*.a
!05_software/real/sim2real_v2/vendored/odin1_imu/lib/*.a
!05_software/real/sim2real_ros2_v2/src/odin_ros_driver/lib/*.a
!05_software/real/sim2real_ros2_v3/src/odin_ros_driver/lib/*.a
# Training outputs
logs/
checkpoints/
wandb/
sim2sim_log_*.txt
**/sim2sim_temp.xml
**/route_check_runs/
**/route_experiments/suite_*/
**/tools/nav_tools/points/auto_candidates/
# IDE and operating system files
.idea/
.vscode/
.trae/
.DS_Store
Thumbs.db
# Temporary and backup files
*.Bak
~$*
-15
View File
@@ -1,15 +0,0 @@
# 项目文档
本目录用于保存 16DOF 轮足项目自身的技术文档和使用说明。
当前文档结构:
```text
01_doc/
├─ architecture/
│ └─ early_software_stack.md # 第一代训练—仿真—真机闭环
├─ training_evolution.md # v0.4v0.6 训练架构演进
└─ version_history.md # 全项目 Tag 与里程碑
```
具体运行说明放在对应工程目录内,避免在顶层重复并逐渐失真:训练见 `05_software/train/rc_mjlab/`,真机部署见 `05_software/real/`
@@ -1,22 +0,0 @@
# 第一代软件闭环
第一代 16DOF 软件的目标是先打通“训练、仿真验证、真机执行”闭环,而不是在初期建立复杂的分布式系统。
## 训练与策略
`05_software/train/rc_mjlab` 使用本地修改的 mjlab 和 MuJoCo 模型训练轮腿混合策略。训练任务包括 Flat、Rough 和 Crawl,腿部 12 个关节输出位置目标,4 个轮子输出速度目标。
## MuJoCo 与 Sim2Sim
- `mujoco_sim` 用于不加载 RL 策略时的模型、动力学和控制调试。
- `sim2sim` 加载训练策略,在独立 MuJoCo 环境中验证观测、动作、地形和导航行为。
- 两者与训练任务共同使用 `rc_mjlab/mjcf`,避免早期模型定义不一致。
## 真机控制
- `ik_real` 先以逆运动学和轨迹插值验证电机控制链路。
- `sim2real` 再将训练策略部署到 Python 真机运行时,加入 IMU、站立、安全和 Web 调试。
## 阶段特点
这一版本的优势是链路完整、模块直观,便于快速验证;局限是训练、仿真和部署仍存在重复资源,Python 真机运行时的实时性和系统集成能力有限。这些问题推动了后续统一训练版本和 ROS 2/C++ 部署架构。
-37
View File
@@ -1,37 +0,0 @@
# 训练代码演进
本项目将“训练代码架构”和“训练产生的模型 checkpoint”分别管理。代码、奖励、课程或观测契约发生变化时形成新的 Git 版本;同一架构下继续训练产生的模型编号作为实验和部署工件记录。
| 版本 | 来源快照 | 主要目的 |
| --- | --- | --- |
| `v0.4.0` | `uni_mjlab(1)` | 第一份完整的新 MJCF 与新 mjlab 训练工程 |
| `v0.5.0` | `uni_mjlab_new` | 扩大观测、延迟和动力学随机化,探索更强 Sim2Real 鲁棒性 |
| `v0.6.0` | `best` | 面向比赛越障重新设计奖励、课程、站姿和诊断体系 |
## 从随机化增强到比赛训练
`v0.6.0` 不是简单地继续增大 `v0.5.0` 的随机范围。实践中重新降低了部分噪声、延迟和动力学随机化强度,并把训练重点转向可控的比赛任务课程:
- 投影重力噪声由 `±0.08` 调回 `±0.05`
- 腿和轮动作最大随机延迟由 4 步调回 2 步。
- 摩擦、刚度和阻尼随机化调回较窄范围。
- 移除该阶段的连续机身外力扰动与腿部质量随机化。
- 将 x、y 和 yaw 跟踪拆成独立奖励和独立指令课程。
- 障碍由课程逐步释放,高墙训练地形改为五道重复横墙。
- 增加楼梯侧向漂移和偏航漂移惩罚。
- 增加大量只用于训练评估的误差、轮速、姿态和接触指标。
这种调整反映的是从“广泛鲁棒性探索”转向“比赛场景定向优化”,不表示 `v0.5.0` 被删除;它仍由对应 Tag 完整保留。
## 模型与比赛部署
比赛训练可能先获得基模,再修改参数继续训练和筛选 checkpoint。最终关系为:
```text
训练代码架构:v0.6.0 / best
比赛 Rough 策略:model_6800.onnx
比赛真机工程:last_not_slalom_1050
比赛得分:1050
```
`model_6800.onnx` 是比赛最终部署工件,不用模型编号替代训练代码版本号。它已随比赛部署归档;当前规范目录为 `05_software/real/sim2real_ros2_v3/policies/`
-150
View File
@@ -1,150 +0,0 @@
# 版本演进
本项目使用里程碑 Tag 保存时间演进;同一架构的小步迭代不复制目录。ROS 2 的无后缀、`_v2``_v3` 目录分别代表三个架构大版本,并在 `v1.1.0` 中同时保留。
| Tag | 阶段 | 核心内容 |
| --- | --- | --- |
| `v0.1.0` | 8DOF 中期检查 | 8DOF 串联足机械与大疆 A 板实机版本 |
| `v0.2.0` | 16DOF 机械 | 16DOF 串联轮足机械 CAD 与 STEP |
| `v0.3.0` | 第一代软件闭环 | 早期训练、MJCF、MuJoCo、Sim2Sim、IK 与 Python Sim2Real |
| `v0.3.1` | 实机记录 | 补充第一代 Sim2Real 实机视频 |
| `v0.4.0` | 新训练基线 | 第一份完整的新 MJCF、新 mjlab 框架和 Rough 策略工程 |
| `v0.5.0` | 随机化增强 | 扩大观测、延迟和动力学随机化,加入持续外力扰动 |
| `v0.6.0` | 比赛训练架构 | 分轴奖励、自适应指令课程、障碍释放课程和比赛站姿 |
| `v0.7.0` | MuJoCo 工具 | 姿态优化、IK 扫描、动力学、MPC 和 GUI 调试工具 |
| `v0.8.0` | 后期 Sim2Sim | ONNX 回放、IK/路线检查工具和比赛最终 Rough 策略 |
| `v0.8.1` | 导航打点工具 | 地图/航点编辑、路线迭代和抽样 PCD 补充包 |
| `v0.9.0` | Python Sim2Real v2 | 反馈新鲜度、Odin odom 诊断、Web 调试和安全监控增强 |
| `v0.10.0` | ROS 2/C++ 初版 | 50 Hz C++ 推理、200 Hz CAN 热路径和 ROS 2 系统集成 |
| `v0.11.0` | ROS 2 导航原型 | 简单导航、PCD 交互定位、任务点和 Web 导航调试 |
| `v0.11.1` | Odin 与站姿调参 | 完整 Odin 驱动、TensorRT、多策略切换和调参站姿 |
| `v0.12.0` | 里程计导航联调 | 纯里程计 fallback、A_min 路线、TF 冲突保护和 model_9600 |
| `v1.0.0` | 比赛最终部署初次归档 | last_not_slalom_1050、model_6800/model_84、最终路线和触控屏;当时暂存于无后缀目录 |
| `v1.0.1` | 比赛成果媒体补充 | 最终机器人图片与 1050 分比赛视频 |
| `v1.0.2` | 文档一致性修正 | 统一历史 Tag、当前快照和成果媒体的描述 |
| `v1.1.0` | 三代目录规范化 | 恢复无后缀初版、保留 v2 里程计版、明确最终比赛 v3,并校准训练 README |
> 原先临时归档为 `v0.9.0` 的最终 ROS 2/C++ 比赛部署已保存在 `backup/final-ros2-v0.9.0` 分支和 `backup-v0.9.0-ros2-final` 标签中,重排后已正式归入 `v1.0.0`。
## `v0.9.0` 的 Python Sim2Real v2
- 归档 `real/sim2real_v2` 真机部署版本,保持 `53D -> 16D` 策略观测和动作契约。
- 增加电机反馈新鲜度判断、Odin odom 诊断、命令限加速度平滑和 Web 运行时诊断。
- 保留 Python 策略运行时、ONNX/PT 模型、MJCF、Odin 接口、Web 工具和安全保护链路。
- 排除运行日志、测试日志、临时 XML 和开发交接草稿;后续 ROS 2/C++ 版本另行归档。
## `v0.10.0` 的 ROS 2/C++ Sim2Real 初版
- 归档 `real/sim2real_ros2`,将 Python 部署契约迁移到 ROS 2 Humble 与 C++ 运行时。
- 保留 53D 观测、16D 动作、50 Hz 策略循环和 200 Hz SocketCAN 电机热路径。
- 增加消息接口、硬件桥、策略运行时、命令仲裁、Nav2 配置、Docker 和 Windows Web 调试工具。
- 原始快照中的 `src/odin_ros_driver` 为空目录,因此本版本仍需外部 Odin 驱动,不能宣称传感器依赖已自包含。
- 保留原始候选 ONNX 文件以记录初版部署试验;排除计划、任务和 walkthrough 草稿。
## `v0.11.0` 的 ROS 2 Sim2Real v2
- 归档 `real/sim2real_ros2_v2`,保持 `v0.10.0` 的 ROS 2/C++ 控制契约。
- 增加 `simple_nav_node.py`、PCD 点击工具、任务点/任务序列配置和 Web 导航控制入口。
- 默认命令源从遥控切换为 `NAV`,加入简单导航状态、PCD 位姿和地图显示链路。
- 原始 `map1.pcd``map6.pcd` 分别约 49.05 MiB、44.24 MiB,归档时确定性抽样到 10 MB 以下并记录哈希。
- 原始快照中的 Odin 驱动仍为空目录;排除设备运行日志和开发草稿。
## `v0.11.1` 的 Odin、TensorRT 与站姿调参
- 归档 `real/sim2real_ros2_v2(z=0.380 hip=0.670 knee=-1.390)`,在同一 `sim2real_ros2_v2` 目录中记录真实差异。
- 首次随部署工程保留完整 Odin ROS 驱动、Apache-2.0 许可证、设备标定参数和预编译 SDK 静态库。
- 策略运行时增加 TensorRT、ONNX 回退、Rough/Crawl 模式切换、事件日志和更完整的电机失效诊断。
- 默认 Rough 策略为 `NEWmodel_1900`,默认站姿为髋俯仰 `0.670`、膝关节 `-1.390`Crawl 配置使用 IK 后端。
- `map_b.pcd` 从 1,080,047 点确定性抽样为 270,012 点,并保留原始和抽样哈希。
- 排除嵌套 Git、Odin 运行日志、缓存、开发草稿和未被配置引用的候选策略。
## `v0.12.0` 的里程计导航联调
- 归档 `real/sim2real_ros2_v2(odom)`,继续沿用 `sim2real_ros2_v2` 目录的线性演进。
- 将 Rough 策略切换为 `model_9600`,默认站姿恢复为髋俯仰 `0.550`、膝关节 `-1.125`
- Odin `custom_map_mode` 固定为纯里程计,加入 odom 新鲜度、外部 map/odom TF 冲突和任务结束交接保护。
- 增加 A_min 路线、PCD 地图编辑工具和三份抽样点云;原始大 PCD 不直接进入 Git。
- 保留完整 Odin 驱动、标定参数和 SDK 静态库;未找到的 `map_a.bin` 仍不伪造,重定位闭环不在本 Tag 声称已复现。
## `v1.0.0` 的比赛最终部署
- 首次归档原始 `sim2real_ros2_v2(last_not_slalom_1050)` 最终 ROS 2/C++ 真机工程;该 Tag 中暂存于无后缀 `real/sim2real_ros2`,目录命名在 `v1.1.0` 才修正为 `real/sim2real_ros2_v3``1050` 是比赛成绩,不是模型编号。
- Rough 使用 `model_6800`Wall 使用 `model_84`,Crawl 按比赛配置使用 IK 后端。
- 保留最终五份路线、1 号场地抽样 PCD、Odin 驱动、CAN 硬件桥、命令仲裁、导航和 Orin 触控屏 UI。
- 最终配置默认命令源为 `NAV`、定位模式为 `relocal`,但真实 Odin `1hao.bin` 不在备份中,重定位闭环需要从比赛设备补回。
- 排除嵌套 Git、日志、备份、候选策略、构建产物和开发草稿;TensorRT engine 仅代表比赛机环境。
## `v1.0.1` 的比赛成果媒体补充
- 保持 `v1.0.0` 的比赛最终代码和部署内容不变。
- 补充最终机器人图片和比赛视频,成绩为 1050 分、第七名(前 5%)。
- 代码复现可查看 `v1.0.0`,包含成果媒体的对应快照可查看 `v1.0.1`
## `v1.0.2` 的文档一致性修正
- 统一 ROS 2 历史 Tag、当前工作树和媒体补丁的说明。
- 该版本仅修正文档,没有改变训练或真机运行代码。
## `v1.1.0` 的目录与说明规范化
-`v0.10.0` 恢复无后缀 `sim2real_ros2` 初版快照。
- `sim2real_ros2_v2` 保持 `v0.12.0` 里程计联调快照。
-`last_not_slalom_1050` 最终比赛部署正式命名为 `sim2real_ros2_v3`
- 依据当前源码重新校准 `rc_mjlab` README 中的物理步长、控制频率、环境数、执行器、地形、奖励和随机化说明。
## `v0.4.0` 的模型变化
- 机械 CAD 不变。
- MJCF 更新整机质量和惯性参数,旧、新 `wheelleg.xml` 的 SHA-256 不同。
- mjlab 上游基准从 `00409797` 更新到 `40f8d93e`
- 保留轮腿分组执行器随机化所需的本地补丁。
- 本阶段归档 `model_rough.pt`,不将生成日志、缓存和临时 XML 纳入版本库。
## `v0.5.0` 的训练变化
- MJCF、mjlab 基准和已有模型文件保持不变。
- 投影重力噪声由 `±0.05` 扩大到 `±0.08`
- 腿与轮动作的最大随机延迟由 2 步增加到 4 步。
- 地面摩擦随机范围由 `0.31.0` 扩大到 `0.151.25`
- 执行器刚度和阻尼缩放由 `0.91.1` 扩大到 `0.51.5`
- 增加膝部和轮部质量的 `0.71.3` 随机缩放。
- 增加作用于机身的连续随机外力和力矩扰动。
## `v0.6.0` 的比赛训练架构
- 保持 `v0.4.0` 引入的新 MJCF 和 mjlab 框架不变。
- 将线速度奖励拆分为 x/y 两轴,并独立配置偏航角速度奖励。
- 增加自适应 x/y/yaw 指令范围课程。
- 增加障碍地形逐步释放与更严格的地形晋级逻辑。
- 增加楼梯横向速度和偏航漂移约束。
- 默认站姿调整为髋俯仰 `0.550`、膝关节 `-1.125`,初始机身高度为 `0.42 m`
- 增加速度误差、轮速跟踪、动作和姿态等训练诊断指标。
- 该 Tag 保存比赛训练代码架构,不把每次继续训练产生的 checkpoint 误记为新的软件版本。
训练阶段的详细关系见 [`training_evolution.md`](training_evolution.md)。
## `v0.7.0` 的独立 MuJoCo 工具
- 比赛训练架构、MJCF、模型和依赖锁文件保持 `v0.6.0` 状态不变。
- 增加解析姿态表、RL 友好姿态筛选和 MuJoCo 静态姿态优化。
- 增加 IK/差速轮参数扫描,可导出 JSON 结果。
- 增加 Robot、Controller、Dynamics、MPCController 和 GUI 调试链路。
- 记录历史工具常量与新版 MJCF 质量、比赛默认站姿之间的参数边界,避免将分析结果直接当作已校准真机参数。
## `v0.8.0` 的后期 Sim2Sim
- 比赛训练任务、MJCF 和 `v0.7.0` 的 MuJoCo 工具保持不变。
- 策略运行器增加 ONNX 加载,并允许在缺少 `pynput` 时关闭后台键盘监听继续运行。
- MuJoCo 执行器重建同时兼容新旧 Spec 删除接口。
- 增加 PT→ONNX 导出、IK 补偿扫描、纯 IK 绕桩和 ONNX 批量路线检查入口。
- 归档比赛最终 Rough 策略 `model_6800.onnx`;其 SHA-256 为 `3C994BDD3434AD15770A52AC0E8D229F502F00D6511CDD42C2E2C742301AEF13`
- Crawl 权重、运行日志、临时 XML 和大量重复路线实验不在本阶段归档。
## `v0.8.1` 的导航打点工具
- 补充 Pygame 地图/PCD/航点综合编辑器、避障区域编辑器和坐标变换工具。
- 补充路线安全检查、候选航点优化、XML/航点镜像和批量 Sim2Sim 实验入口。
- 按源文件时间保留 14 份比赛路线 JSON,不将开发期文件名误解释为正式版本号。
- 补充 `1hao.xml``2hao.xml``A_C.xml`,并为 `1B_FF.json` 补齐其引用的 `B_C.xml`
- 将两份约 915 MiB 的原始 ASCII PCD 确定性抽样为各小于 10 MB 的预览点云;抽样参数、点数和哈希记录在工具 README。
- 训练代码、MJCF、比赛策略和历史依赖锁保持 `v0.8.0` 状态不变。
-5
View File
@@ -1,5 +0,0 @@
# SolidWorks 源文件
`solidworks/` 保留原始装配层级和文件名。
建议使用 `solidworks/WEEKDOG.SLDASM` 作为整机入口。`26版sw单件/` 包含组成整机的单件和子装配体,不应随意改名或拆散。
Binary file not shown.
Binary file not shown.
Binary file not shown.
-40
View File
@@ -1,40 +0,0 @@
# 16DOF 串联轮足机械
本目录保存山东华宇工学院 16DOF 串联轮足机器人的机械设计资料。
机器人由 12 个腿部关节和 4 个驱动轮组成,共 16 个执行器。源资料说明其使用 SolidWorks 26 版本完成设计、装配和 URDF 导出准备。
## 目录结构
```text
02_mechanical/
├─ CAD/
│ └─ solidworks/
│ ├─ WEEKDOG.SLDASM # 整机装配体入口
│ ├─ 轮子完整.SLDASM # 轮组装配体
│ ├─ 轮毂垫片.SLDPRT # 独立零件
│ └─ 26版sw单件/ # 单件与子装配体
├─ STEP/
│ ├─ WEEKDOG.STEP # 整机通用交换文件
│ └─ *.STEP # 身体、关节、腿部和轮组
└─ source_notes.txt # 原始资料说明
```
## 文件统计
- SolidWorks 装配体:4 个 `*.SLDASM`
- SolidWorks 零件:26 个 `*.SLDPRT`
- STEP 文件:10 个
## 使用方式
- 需要继续编辑设计时,从 `CAD/solidworks/WEEKDOG.SLDASM` 打开整机。
- 只需查看、测量或导入其他 CAD 软件时,使用 `STEP/WEEKDOG.STEP`
- 子部件调试可以使用身体、髋关节、大腿、轮小腿和轮组 STEP。
- 为避免装配引用丢失,不要单独移动或重命名 `CAD/solidworks` 内部文件。
## 当前缺项
- 尚未提供关键零件二维 PDF 工程图。
- 尚未形成 BOM、装配步骤和干涉检查报告。
- 尚未核对 CAD 参数与训练使用的 URDF/MJCF 是否完全一致。
File diff suppressed because it is too large Load Diff
-11
View File
@@ -1,11 +0,0 @@
# STEP 交换文件
本目录提供不依赖 SolidWorks 的通用机械交换文件。
- `WEEKDOG.STEP`:整机
- `DOG身体.STEP`:机器人身体
- `髋关节END.STEP`:髋关节
- `大腿.STEP`:大腿组件
- `轮小腿.STEP`:轮足小腿组件
- `轮子完整.STEP`:完整轮组
- 其余文件:轮子、轮毂垫片和连接零件
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-660
View File
@@ -1,660 +0,0 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION (( 'STEP AP214' ),
'1' );
FILE_NAME ('ÂÖì±µæÆ¬.STEP',
'2026-04-27T05:39:26',
( '' ),
( '' ),
'SwSTEP 2.0',
'SolidWorks 2026',
'' );
FILE_SCHEMA (( 'AUTOMOTIVE_DESIGN' ));
ENDSEC;
DATA;
#1 = ADVANCED_FACE ( 'NONE', ( #454 ), #386, .F. ) ;
#2 = PRODUCT_DEFINITION_CONTEXT ( 'detailed design', #631, 'design' ) ;
#3 = CYLINDRICAL_SURFACE ( 'NONE', #580, 7.000000000000000000 ) ;
#4 = CARTESIAN_POINT ( 'NONE', ( -8.242304845413723768, -7.487354696433185630, 8.000000000000000000 ) ) ;
#5 = FACE_BOUND ( 'NONE', #375, .T. ) ;
#6 = CARTESIAN_POINT ( 'NONE', ( 19.00000000000000000, -1.487354696433177415, 8.000000000000000000 ) ) ;
#7 = EDGE_CURVE ( 'NONE', #419, #33, #450, .T. ) ;
#8 = CARTESIAN_POINT ( 'NONE', ( 1.469576158976869898E-15, -13.48735469643318119, 8.000000000000000000 ) ) ;
#9 = EDGE_CURVE ( 'NONE', #83, #573, #139, .T. ) ;
#10 = AXIS2_PLACEMENT_3D ( 'NONE', #50, #65, #101 ) ;
#11 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#12 = VERTEX_POINT ( 'NONE', #91 ) ;
#13 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#14 = ORIENTED_EDGE ( 'NONE', *, *, #582, .F. ) ;
#15 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#16 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541302957, 4.512645303566650057, 8.000000000000000000 ) ) ;
#17 = VERTEX_POINT ( 'NONE', #548 ) ;
#18 = AXIS2_PLACEMENT_3D ( 'NONE', #216, #64, #11 ) ;
#19 = CYLINDRICAL_SURFACE ( 'NONE', #263, 2.149999999999539391 ) ;
#20 = LINE ( 'NONE', #116, #596 ) ;
#21 = ORIENTED_EDGE ( 'NONE', *, *, #473, .T. ) ;
#22 = VERTEX_POINT ( 'NONE', #58 ) ;
#23 = AXIS2_PLACEMENT_3D ( 'NONE', #570, #275, #180 ) ;
#24 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541344879, 4.512645303567090593, 8.000000000000000000 ) ) ;
#25 = CIRCLE ( 'NONE', #186, 2.150000000000000355 ) ;
#26 = ORIENTED_EDGE ( 'NONE', *, *, #169, .T. ) ;
#27 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#28 = CARTESIAN_POINT ( 'NONE', ( 2.150000000000000355, 10.51264530356682236, 0.000000000000000000 ) ) ;
#29 = VERTEX_POINT ( 'NONE', #488 ) ;
#30 = CARTESIAN_POINT ( 'NONE', ( -12.54230484541302992, 4.512645303566650057, 8.000000000000000000 ) ) ;
#31 = VECTOR ( 'NONE', #168, 1000.000000000000000 ) ;
#32 = ORIENTED_EDGE ( 'NONE', *, *, #241, .F. ) ;
#33 = VERTEX_POINT ( 'NONE', #244 ) ;
#34 = VECTOR ( 'NONE', #522, 1000.000000000000000 ) ;
#35 = CARTESIAN_POINT ( 'NONE', ( -2.150000000000000355, 10.51264530356682236, 8.000000000000000000 ) ) ;
#36 = ORIENTED_EDGE ( 'NONE', *, *, #190, .F. ) ;
#37 = SURFACE_SIDE_STYLE ('',( #482 ) ) ;
#38 = EDGE_CURVE ( 'NONE', #573, #33, #556, .T. ) ;
#39 = CARTESIAN_POINT ( 'NONE', ( -8.242304845413029213, 4.512645303566650057, 8.000000000000000000 ) ) ;
#40 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#41 = VECTOR ( 'NONE', #251, 1000.000000000000000 ) ;
#42 = VECTOR ( 'NONE', #144, 1000.000000000000000 ) ;
#43 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541326405, -7.487354696433185630, 0.000000000000000000 ) ) ;
#44 = VERTEX_POINT ( 'NONE', #304 ) ;
#45 = LINE ( 'NONE', #594, #73 ) ;
#46 = ADVANCED_FACE ( 'NONE', ( #253 ), #588, .F. ) ;
#47 = VECTOR ( 'NONE', #87, 1000.000000000000000 ) ;
#48 = SHAPE_DEFINITION_REPRESENTATION ( #95, #280 ) ;
#49 = APPLICATION_CONTEXT ( 'automotive_design' ) ;
#50 = CARTESIAN_POINT ( 'NONE', ( 1.469576158976869898E-15, -13.48735469643318119, 8.000000000000000000 ) ) ;
#51 = ADVANCED_FACE ( 'NONE', ( #192 ), #530, .F. ) ;
#52 = CARTESIAN_POINT ( 'NONE', ( -2.149999999999433697, -13.48735469643318119, 0.000000000000000000 ) ) ;
#53 = VERTEX_POINT ( 'NONE', #293 ) ;
#54 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#55 = EDGE_CURVE ( 'NONE', #53, #17, #643, .T. ) ;
#56 = EDGE_CURVE ( 'NONE', #418, #620, #364, .T. ) ;
#57 = CARTESIAN_POINT ( 'NONE', ( 2.149999999999436362, -13.48735469643318119, 0.000000000000000000 ) ) ;
#58 = CARTESIAN_POINT ( 'NONE', ( 12.54230484541344914, 4.512645303567090593, 0.000000000000000000 ) ) ;
#59 = CIRCLE ( 'NONE', #425, 2.149999999999435030 ) ;
#60 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#61 = FACE_BOUND ( 'NONE', #360, .T. ) ;
#62 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#63 = EDGE_LOOP ( 'NONE', ( #142, #640, #372, #268 ) ) ;
#64 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#65 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#66 = ORIENTED_EDGE ( 'NONE', *, *, #563, .F. ) ;
#67 = ORIENTED_EDGE ( 'NONE', *, *, #55, .T. ) ;
#68 = EDGE_CURVE ( 'NONE', #614, #418, #322, .T. ) ;
#69 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#70 = ORIENTED_EDGE ( 'NONE', *, *, #221, .T. ) ;
#71 = CIRCLE ( 'NONE', #401, 2.150000000000000799 ) ;
#72 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#73 = VECTOR ( 'NONE', #436, 1000.000000000000000 ) ;
#74 = CARTESIAN_POINT ( 'NONE', ( -8.242304845413029213, 4.512645303566650057, 8.000000000000000000 ) ) ;
#75 = ORIENTED_EDGE ( 'NONE', *, *, #7, .T. ) ;
#76 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#77 = LINE ( 'NONE', #30, #247 ) ;
#78 = ORIENTED_EDGE ( 'NONE', *, *, #7, .F. ) ;
#79 = CIRCLE ( 'NONE', #327, 19.00000000000000000 ) ;
#80 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#81 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#82 = AXIS2_PLACEMENT_3D ( 'NONE', #525, #122, #471 ) ;
#83 = VERTEX_POINT ( 'NONE', #284 ) ;
#84 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#85 = ADVANCED_FACE ( 'NONE', ( #134 ), #433, .F. ) ;
#86 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#87 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#88 = VERTEX_POINT ( 'NONE', #28 ) ;
#89 = AXIS2_PLACEMENT_3D ( 'NONE', #521, #564, #416 ) ;
#90 = CYLINDRICAL_SURFACE ( 'NONE', #319, 19.00000000000000000 ) ;
#91 = CARTESIAN_POINT ( 'NONE', ( 12.54230484541344914, 4.512645303567090593, 8.000000000000000000 ) ) ;
#92 = ORIENTED_EDGE ( 'NONE', *, *, #515, .F. ) ;
#93 = AXIS2_PLACEMENT_3D ( 'NONE', #405, #248, #447 ) ;
#94 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 10.51264530356682236, 0.000000000000000000 ) ) ;
#95 = PRODUCT_DEFINITION_SHAPE ( 'NONE', 'NONE', #223 ) ;
#96 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#97 = ADVANCED_FACE ( 'NONE', ( #385 ), #477, .F. ) ;
#98 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#99 = AXIS2_PLACEMENT_3D ( 'NONE', #602, #107, #403 ) ;
#100 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#101 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#102 = EDGE_CURVE ( 'NONE', #17, #44, #618, .T. ) ;
#103 = AXIS2_PLACEMENT_3D ( 'NONE', #246, #86, #391 ) ;
#104 = PLANE ( 'NONE', #99 ) ;
#105 = EDGE_CURVE ( 'NONE', #371, #620, #77, .T. ) ;
#106 = CARTESIAN_POINT ( 'NONE', ( -7.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#107 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#108 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#109 = LINE ( 'NONE', #106, #151 ) ;
#110 = ORIENTED_EDGE ( 'NONE', *, *, #635, .F. ) ;
#111 = CYLINDRICAL_SURFACE ( 'NONE', #344, 19.00000000000000000 ) ;
#112 = FACE_OUTER_BOUND ( 'NONE', #63, .T. ) ;
#113 = ORIENTED_EDGE ( 'NONE', *, *, #445, .F. ) ;
#114 = AXIS2_PLACEMENT_3D ( 'NONE', #422, #434, #128 ) ;
#115 = LINE ( 'NONE', #310, #205 ) ;
#116 = CARTESIAN_POINT ( 'NONE', ( -2.150000000000000355, 10.51264530356682236, 8.000000000000000000 ) ) ;
#117 = EDGE_LOOP ( 'NONE', ( #167, #611 ) ) ;
#118 = EDGE_CURVE ( 'NONE', #22, #432, #299, .T. ) ;
#119 = FACE_OUTER_BOUND ( 'NONE', #626, .T. ) ;
#120 = VERTEX_POINT ( 'NONE', #633 ) ;
#121 = ORIENTED_EDGE ( 'NONE', *, *, #474, .T. ) ;
#122 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#123 = ORIENTED_EDGE ( 'NONE', *, *, #343, .T. ) ;
#124 = ADVANCED_FACE ( 'NONE', ( #377 ), #380, .F. ) ;
#125 = VECTOR ( 'NONE', #298, 1000.000000000000000 ) ;
#126 = CARTESIAN_POINT ( 'NONE', ( 1.469576158976869898E-15, -13.48735469643318119, 8.000000000000000000 ) ) ;
#127 = AXIS2_PLACEMENT_3D ( 'NONE', #547, #394, #533 ) ;
#128 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#129 = ORIENTED_EDGE ( 'NONE', *, *, #397, .T. ) ;
#130 = EDGE_LOOP ( 'NONE', ( #129, #415 ) ) ;
#131 = CARTESIAN_POINT ( 'NONE', ( 8.242304845413610082, -7.487354696433178525, 0.000000000000000000 ) ) ;
#132 = FACE_BOUND ( 'NONE', #442, .T. ) ;
#133 = CIRCLE ( 'NONE', #381, 7.000000000000000000 ) ;
#134 = FACE_OUTER_BOUND ( 'NONE', #478, .T. ) ;
#135 = AXIS2_PLACEMENT_3D ( 'NONE', #472, #581, #527 ) ;
#136 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#137 = VERTEX_POINT ( 'NONE', #35 ) ;
#138 = CARTESIAN_POINT ( 'NONE', ( 12.54230484541292512, -7.487354696433178525, 8.000000000000000000 ) ) ;
#139 = LINE ( 'NONE', #486, #413 ) ;
#140 = AXIS2_PLACEMENT_3D ( 'NONE', #24, #72, #27 ) ;
#141 = CARTESIAN_POINT ( 'NONE', ( -12.54230484541302992, 4.512645303566650057, 0.000000000000000000 ) ) ;
#142 = ORIENTED_EDGE ( 'NONE', *, *, #610, .F. ) ;
#143 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000082E-05 ), #537, 'distance_accuracy_value', 'NONE');
#144 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#145 = ORIENTED_EDGE ( 'NONE', *, *, #296, .F. ) ;
#146 = PRODUCT_CONTEXT ( 'NONE', #49, 'mechanical' ) ;
#147 = ADVANCED_FACE ( 'NONE', ( #237, #132, #326, #628, #279, #183, #234, #431 ), #330, .T. ) ;
#148 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#149 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#150 = CARTESIAN_POINT ( 'NONE', ( -12.54230484541280255, -7.487354696433185630, 8.000000000000000000 ) ) ;
#151 = VECTOR ( 'NONE', #497, 1000.000000000000000 ) ;
#152 = VECTOR ( 'NONE', #638, 1000.000000000000000 ) ;
#153 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#154 = EDGE_LOOP ( 'NONE', ( #209, #255, #475, #276 ) ) ;
#155 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#156 = MANIFOLD_SOLID_BREP ( '\X2\51f853f0\X0\-\X2\62c94f38\X0\1', #196 ) ;
#157 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#158 = CARTESIAN_POINT ( 'NONE', ( 2.149999999999436362, -13.48735469643318119, 8.000000000000000000 ) ) ;
#159 = CIRCLE ( 'NONE', #440, 2.149999999999657518 ) ;
#160 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#161 = LINE ( 'NONE', #456, #125 ) ;
#162 = ADVANCED_FACE ( 'NONE', ( #559 ), #622, .F. ) ;
#163 = EDGE_LOOP ( 'NONE', ( #469, #566, #378, #338 ) ) ;
#164 = AXIS2_PLACEMENT_3D ( 'NONE', #404, #499, #639 ) ;
#165 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#166 = ORIENTED_EDGE ( 'NONE', *, *, #351, .T. ) ;
#167 = ORIENTED_EDGE ( 'NONE', *, *, #169, .F. ) ;
#168 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#169 = EDGE_CURVE ( 'NONE', #29, #12, #194, .T. ) ;
#170 = ORIENTED_EDGE ( 'NONE', *, *, #531, .F. ) ;
#171 = EDGE_LOOP ( 'NONE', ( #218, #291, #368, #513 ) ) ;
#172 = ORIENTED_EDGE ( 'NONE', *, *, #38, .F. ) ;
#173 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541302957, 4.512645303566650057, 8.000000000000000000 ) ) ;
#174 = AXIS2_PLACEMENT_3D ( 'NONE', #514, #225, #369 ) ;
#175 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#176 = ORIENTED_EDGE ( 'NONE', *, *, #328, .T. ) ;
#177 = CIRCLE ( 'NONE', #290, 7.000000000000000000 ) ;
#178 = ORIENTED_EDGE ( 'NONE', *, *, #448, .F. ) ;
#179 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#180 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#181 = ORIENTED_EDGE ( 'NONE', *, *, #397, .F. ) ;
#182 = ORIENTED_EDGE ( 'NONE', *, *, #448, .T. ) ;
#183 = FACE_BOUND ( 'NONE', #117, .T. ) ;
#184 = AXIS2_PLACEMENT_3D ( 'NONE', #595, #398, #546 ) ;
#185 = EDGE_CURVE ( 'NONE', #429, #259, #361, .T. ) ;
#186 = AXIS2_PLACEMENT_3D ( 'NONE', #94, #345, #239 ) ;
#187 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#188 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#189 = ORIENTED_EDGE ( 'NONE', *, *, #335, .F. ) ;
#190 = EDGE_CURVE ( 'NONE', #466, #465, #59, .T. ) ;
#191 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#192 = FACE_OUTER_BOUND ( 'NONE', #154, .T. ) ;
#193 = ORIENTED_EDGE ( 'NONE', *, *, #560, .T. ) ;
#194 = CIRCLE ( 'NONE', #140, 2.150000000000000799 ) ;
#195 = VERTEX_POINT ( 'NONE', #222 ) ;
#196 = CLOSED_SHELL ( 'NONE', ( #285, #470, #85, #46, #97, #1, #584, #577, #147, #483, #505, #124, #162, #383, #512, #212, #51, #355 ) ) ;
#197 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#198 = PRODUCT_RELATED_PRODUCT_CATEGORY ( 'part', '', ( #601 ) ) ;
#199 = FACE_OUTER_BOUND ( 'NONE', #315, .T. ) ;
#200 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541302957, 4.512645303566650057, 0.000000000000000000 ) ) ;
#201 = FACE_OUTER_BOUND ( 'NONE', #365, .T. ) ;
#202 = ORIENTED_EDGE ( 'NONE', *, *, #68, .F. ) ;
#203 = CARTESIAN_POINT ( 'NONE', ( -8.242304845413723768, -7.487354696433185630, 0.000000000000000000 ) ) ;
#204 = CIRCLE ( 'NONE', #10, 2.149999999999435030 ) ;
#205 = VECTOR ( 'NONE', #506, 1000.000000000000000 ) ;
#206 = FACE_BOUND ( 'NONE', #625, .T. ) ;
#207 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#208 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#209 = ORIENTED_EDGE ( 'NONE', *, *, #393, .F. ) ;
#210 = PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE ( 'ä»', '', #601, .NOT_KNOWN. ) ;
#211 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 10.51264530356682236, 8.000000000000000000 ) ) ;
#212 = ADVANCED_FACE ( 'NONE', ( #119 ), #510, .F. ) ;
#213 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#214 = AXIS2_PLACEMENT_3D ( 'NONE', #592, #552, #302 ) ;
#215 = ORIENTED_EDGE ( 'NONE', *, *, #105, .T. ) ;
#216 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541326760, -7.487354696433178525, 8.000000000000000000 ) ) ;
#217 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#218 = ORIENTED_EDGE ( 'NONE', *, *, #306, .T. ) ;
#219 = ORIENTED_EDGE ( 'NONE', *, *, #424, .T. ) ;
#220 = VECTOR ( 'NONE', #153, 1000.000000000000000 ) ;
#221 = EDGE_CURVE ( 'NONE', #314, #465, #609, .T. ) ;
#222 = CARTESIAN_POINT ( 'NONE', ( 12.54230484541292512, -7.487354696433178525, 0.000000000000000000 ) ) ;
#223 = PRODUCT_DEFINITION ( 'æœ', '', #210, #2 ) ;
#224 = ORIENTED_EDGE ( 'NONE', *, *, #583, .T. ) ;
#225 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#226 = SURFACE_STYLE_USAGE ( .BOTH. , #37 ) ;
#227 = ORIENTED_EDGE ( 'NONE', *, *, #632, .T. ) ;
#228 = ORIENTED_EDGE ( 'NONE', *, *, #334, .T. ) ;
#229 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#230 = AXIS2_PLACEMENT_3D ( 'NONE', #617, #476, #568 ) ;
#231 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541344879, 4.512645303567090593, 8.000000000000000000 ) ) ;
#232 = ORIENTED_EDGE ( 'NONE', *, *, #306, .F. ) ;
#233 = EDGE_LOOP ( 'NONE', ( #504, #75, #219, #370 ) ) ;
#234 = FACE_BOUND ( 'NONE', #238, .T. ) ;
#235 = ORIENTED_EDGE ( 'NONE', *, *, #445, .T. ) ;
#236 = EDGE_CURVE ( 'NONE', #12, #29, #553, .T. ) ;
#237 = FACE_BOUND ( 'NONE', #518, .T. ) ;
#238 = EDGE_LOOP ( 'NONE', ( #113, #621 ) ) ;
#239 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#240 = CIRCLE ( 'NONE', #318, 2.150000000000000799 ) ;
#241 = EDGE_CURVE ( 'NONE', #264, #195, #441, .T. ) ;
#242 = CIRCLE ( 'NONE', #350, 7.000000000000000000 ) ;
#243 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#244 = CARTESIAN_POINT ( 'NONE', ( 19.00000000000000000, -1.487354696433177415, 0.000000000000000000 ) ) ;
#245 = AXIS2_PLACEMENT_3D ( 'NONE', #211, #496, #160 ) ;
#246 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 10.51264530356682236, 8.000000000000000000 ) ) ;
#247 = VECTOR ( 'NONE', #480, 1000.000000000000000 ) ;
#248 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#249 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#250 = EDGE_LOOP ( 'NONE', ( #333, #558 ) ) ;
#251 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#252 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#253 = FACE_OUTER_BOUND ( 'NONE', #629, .T. ) ;
#254 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#255 = ORIENTED_EDGE ( 'NONE', *, *, #529, .T. ) ;
#256 = AXIS2_PLACEMENT_3D ( 'NONE', #80, #316, #217 ) ;
#257 = FACE_BOUND ( 'NONE', #354, .T. ) ;
#258 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#259 = VERTEX_POINT ( 'NONE', #467 ) ;
#260 = AXIS2_PLACEMENT_3D ( 'NONE', #438, #197, #191 ) ;
#261 = FACE_OUTER_BOUND ( 'NONE', #562, .T. ) ;
#262 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#263 = AXIS2_PLACEMENT_3D ( 'NONE', #453, #157, #362 ) ;
#264 = VERTEX_POINT ( 'NONE', #131 ) ;
#265 = EDGE_CURVE ( 'NONE', #419, #83, #376, .T. ) ;
#266 = EDGE_LOOP ( 'NONE', ( #523, #636, #181, #324 ) ) ;
#267 = CARTESIAN_POINT ( 'NONE', ( -2.149999999999433697, -13.48735469643318119, 8.000000000000000000 ) ) ;
#268 = ORIENTED_EDGE ( 'NONE', *, *, #118, .F. ) ;
#269 = AXIS2_PLACEMENT_3D ( 'NONE', #407, #605, #555 ) ;
#270 = FACE_OUTER_BOUND ( 'NONE', #171, .T. ) ;
#271 = PRESENTATION_STYLE_ASSIGNMENT (( #226 ) ) ;
#272 = ORIENTED_EDGE ( 'NONE', *, *, #335, .T. ) ;
#273 = ORIENTED_EDGE ( 'NONE', *, *, #185, .T. ) ;
#274 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#275 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#276 = ORIENTED_EDGE ( 'NONE', *, *, #185, .F. ) ;
#277 = ORIENTED_EDGE ( 'NONE', *, *, #593, .T. ) ;
#278 = AXIS2_PLACEMENT_3D ( 'NONE', #126, #321, #69 ) ;
#279 = FACE_BOUND ( 'NONE', #250, .T. ) ;
#280 = ADVANCED_BREP_SHAPE_REPRESENTATION ( '\X2\8f6e6bc257ab7247\X0\', ( #156, #260 ), #539 ) ;
#281 = EDGE_CURVE ( 'NONE', #432, #22, #623, .T. ) ;
#282 = ORIENTED_EDGE ( 'NONE', *, *, #473, .F. ) ;
#283 = FILL_AREA_STYLE ('',( #578 ) ) ;
#284 = CARTESIAN_POINT ( 'NONE', ( -19.00000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#285 = ADVANCED_FACE ( 'NONE', ( #270 ), #420, .F. ) ;
#286 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#287 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 10.51264530356682236, 8.000000000000000000 ) ) ;
#288 = EDGE_LOOP ( 'NONE', ( #590, #277, #123, #189 ) ) ;
#289 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#290 = AXIS2_PLACEMENT_3D ( 'NONE', #402, #544, #557 ) ;
#291 = ORIENTED_EDGE ( 'NONE', *, *, #68, .T. ) ;
#292 = ORIENTED_EDGE ( 'NONE', *, *, #56, .T. ) ;
#293 = CARTESIAN_POINT ( 'NONE', ( -7.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#294 = AXIS2_PLACEMENT_3D ( 'NONE', #503, #62, #258 ) ;
#295 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#296 = EDGE_CURVE ( 'NONE', #465, #466, #516, .T. ) ;
#297 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#298 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#299 = CIRCLE ( 'NONE', #23, 2.150000000000000799 ) ;
#300 = CIRCLE ( 'NONE', #269, 2.149999999999539391 ) ;
#301 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#302 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#303 = FACE_OUTER_BOUND ( 'NONE', #406, .T. ) ;
#304 = CARTESIAN_POINT ( 'NONE', ( 7.000000000000000000, -1.487354696433178969, 0.000000000000000000 ) ) ;
#305 = CARTESIAN_POINT ( 'NONE', ( 2.150000000000000355, 10.51264530356682236, 8.000000000000000000 ) ) ;
#306 = EDGE_CURVE ( 'NONE', #371, #614, #240, .T. ) ;
#307 = AXIS2_PLACEMENT_3D ( 'NONE', #81, #187, #572 ) ;
#308 = FACE_BOUND ( 'NONE', #367, .T. ) ;
#309 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#310 = CARTESIAN_POINT ( 'NONE', ( 8.242304845413448433, 4.512645303567090593, 8.000000000000000000 ) ) ;
#311 = CARTESIAN_POINT ( 'NONE', ( -7.000000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#312 = EDGE_LOOP ( 'NONE', ( #232, #400 ) ) ;
#313 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#314 = VERTEX_POINT ( 'NONE', #356 ) ;
#315 = EDGE_LOOP ( 'NONE', ( #67, #535, #524, #479 ) ) ;
#316 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#317 = ORIENTED_EDGE ( 'NONE', *, *, #296, .T. ) ;
#318 = AXIS2_PLACEMENT_3D ( 'NONE', #606, #309, #13 ) ;
#319 = AXIS2_PLACEMENT_3D ( 'NONE', #100, #396, #446 ) ;
#320 = VERTEX_POINT ( 'NONE', #311 ) ;
#321 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#322 = LINE ( 'NONE', #74, #493 ) ;
#323 = ORIENTED_EDGE ( 'NONE', *, *, #221, .F. ) ;
#324 = ORIENTED_EDGE ( 'NONE', *, *, #328, .F. ) ;
#325 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#326 = FACE_BOUND ( 'NONE', #607, .T. ) ;
#327 = AXIS2_PLACEMENT_3D ( 'NONE', #349, #54, #254 ) ;
#328 = EDGE_CURVE ( 'NONE', #137, #120, #20, .T. ) ;
#329 = EDGE_LOOP ( 'NONE', ( #202, #567, #215, #340 ) ) ;
#330 = PLANE ( 'NONE', #619 ) ;
#331 = AXIS2_PLACEMENT_3D ( 'NONE', #287, #207, #439 ) ;
#332 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#333 = ORIENTED_EDGE ( 'NONE', *, *, #55, .F. ) ;
#334 = EDGE_CURVE ( 'NONE', #616, #264, #495, .T. ) ;
#335 = EDGE_CURVE ( 'NONE', #44, #320, #177, .T. ) ;
#336 = CARTESIAN_POINT ( 'NONE', ( 1.469576158976869898E-15, -13.48735469643318119, 8.000000000000000000 ) ) ;
#337 = ORIENTED_EDGE ( 'NONE', *, *, #583, .F. ) ;
#338 = ORIENTED_EDGE ( 'NONE', *, *, #412, .F. ) ;
#339 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#340 = ORIENTED_EDGE ( 'NONE', *, *, #56, .F. ) ;
#341 = LINE ( 'NONE', #585, #42 ) ;
#342 = EDGE_LOOP ( 'NONE', ( #317, #615 ) ) ;
#343 = EDGE_CURVE ( 'NONE', #53, #320, #109, .T. ) ;
#344 = AXIS2_PLACEMENT_3D ( 'NONE', #301, #502, #399 ) ;
#345 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#346 = FACE_OUTER_BOUND ( 'NONE', #329, .T. ) ;
#347 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#348 = AXIS2_PLACEMENT_3D ( 'NONE', #427, #165, #366 ) ;
#349 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#350 = AXIS2_PLACEMENT_3D ( 'NONE', #347, #295, #98 ) ;
#351 = EDGE_CURVE ( 'NONE', #417, #314, #204, .T. ) ;
#352 = CARTESIAN_POINT ( 'NONE', ( 12.54230484541292512, -7.487354696433178525, 8.000000000000000000 ) ) ;
#353 = EDGE_CURVE ( 'NONE', #620, #418, #71, .T. ) ;
#354 = EDGE_LOOP ( 'NONE', ( #561, #182 ) ) ;
#355 = ADVANCED_FACE ( 'NONE', ( #346 ), #600, .F. ) ;
#356 = CARTESIAN_POINT ( 'NONE', ( -2.149999999999433697, -13.48735469643318119, 8.000000000000000000 ) ) ;
#357 = FACE_OUTER_BOUND ( 'NONE', #163, .T. ) ;
#358 = CIRCLE ( 'NONE', #245, 2.150000000000000355 ) ;
#359 = CARTESIAN_POINT ( 'NONE', ( 19.00000000000000000, -1.487354696433177415, 8.000000000000000000 ) ) ;
#360 = EDGE_LOOP ( 'NONE', ( #637, #426 ) ) ;
#361 = CIRCLE ( 'NONE', #174, 2.149999999999539391 ) ;
#362 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#363 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541326760, -7.487354696433178525, 8.000000000000000000 ) ) ;
#364 = CIRCLE ( 'NONE', #114, 2.150000000000000799 ) ;
#365 = EDGE_LOOP ( 'NONE', ( #78, #460, #519, #574 ) ) ;
#366 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#367 = EDGE_LOOP ( 'NONE', ( #121, #272 ) ) ;
#368 = ORIENTED_EDGE ( 'NONE', *, *, #353, .F. ) ;
#369 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#370 = ORIENTED_EDGE ( 'NONE', *, *, #9, .F. ) ;
#371 = VERTEX_POINT ( 'NONE', #500 ) ;
#372 = ORIENTED_EDGE ( 'NONE', *, *, #531, .T. ) ;
#373 = CARTESIAN_POINT ( 'NONE', ( 1.469576158976869898E-15, -13.48735469643318119, 0.000000000000000000 ) ) ;
#374 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#375 = EDGE_LOOP ( 'NONE', ( #508, #273 ) ) ;
#376 = CIRCLE ( 'NONE', #256, 19.00000000000000000 ) ;
#377 = FACE_OUTER_BOUND ( 'NONE', #288, .T. ) ;
#378 = ORIENTED_EDGE ( 'NONE', *, *, #627, .F. ) ;
#379 = ORIENTED_EDGE ( 'NONE', *, *, #424, .F. ) ;
#380 = CYLINDRICAL_SURFACE ( 'NONE', #307, 7.000000000000000000 ) ;
#381 = AXIS2_PLACEMENT_3D ( 'NONE', #252, #551, #249 ) ;
#382 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#383 = ADVANCED_FACE ( 'NONE', ( #112 ), #507, .F. ) ;
#384 = CIRCLE ( 'NONE', #103, 2.150000000000000355 ) ;
#385 = FACE_OUTER_BOUND ( 'NONE', #509, .T. ) ;
#386 = CYLINDRICAL_SURFACE ( 'NONE', #331, 2.150000000000000355 ) ;
#387 = VERTEX_POINT ( 'NONE', #4 ) ;
#388 = APPLICATION_PROTOCOL_DEFINITION ( 'draft international standard', 'automotive_design', 1998, #631 ) ;
#389 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION ( '', ( #641 ), #452 ) ;
#390 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541326405, -7.487354696433185630, 8.000000000000000000 ) ) ;
#391 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#392 = AXIS2_PLACEMENT_3D ( 'NONE', #231, #374, #428 ) ;
#393 = EDGE_CURVE ( 'NONE', #387, #429, #161, .T. ) ;
#394 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#395 = CARTESIAN_POINT ( 'NONE', ( -8.242304845413029213, 4.512645303566650057, 0.000000000000000000 ) ) ;
#396 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#397 = EDGE_CURVE ( 'NONE', #120, #88, #569, .T. ) ;
#398 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#399 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#400 = ORIENTED_EDGE ( 'NONE', *, *, #421, .F. ) ;
#401 = AXIS2_PLACEMENT_3D ( 'NONE', #200, #148, #262 ) ;
#402 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#403 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, -0.000000000000000000 ) ) ;
#404 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541326760, -7.487354696433178525, 8.000000000000000000 ) ) ;
#405 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541344879, 4.512645303567090593, 8.000000000000000000 ) ) ;
#406 = EDGE_LOOP ( 'NONE', ( #550, #21, #228, #178 ) ) ;
#407 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541326405, -7.487354696433185630, 8.000000000000000000 ) ) ;
#408 = CIRCLE ( 'NONE', #18, 2.149999999999657518 ) ;
#409 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#410 = CYLINDRICAL_SURFACE ( 'NONE', #526, 2.149999999999657518 ) ;
#411 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541344879, 4.512645303567090593, 0.000000000000000000 ) ) ;
#412 = EDGE_CURVE ( 'NONE', #430, #259, #603, .T. ) ;
#413 = VECTOR ( 'NONE', #243, 1000.000000000000000 ) ;
#414 = VERTEX_POINT ( 'NONE', #305 ) ;
#415 = ORIENTED_EDGE ( 'NONE', *, *, #563, .T. ) ;
#416 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#417 = VERTEX_POINT ( 'NONE', #158 ) ;
#418 = VERTEX_POINT ( 'NONE', #395 ) ;
#419 = VERTEX_POINT ( 'NONE', #359 ) ;
#420 = CYLINDRICAL_SURFACE ( 'NONE', #634, 2.150000000000000799 ) ;
#421 = EDGE_CURVE ( 'NONE', #614, #371, #604, .T. ) ;
#422 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541302957, 4.512645303566650057, 0.000000000000000000 ) ) ;
#423 = ORIENTED_EDGE ( 'NONE', *, *, #610, .T. ) ;
#424 = EDGE_CURVE ( 'NONE', #33, #573, #79, .T. ) ;
#425 = AXIS2_PLACEMENT_3D ( 'NONE', #373, #325, #613 ) ;
#426 = ORIENTED_EDGE ( 'NONE', *, *, #118, .T. ) ;
#427 = CARTESIAN_POINT ( 'NONE', ( 1.469576158976869898E-15, -13.48735469643318119, 0.000000000000000000 ) ) ;
#428 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#429 = VERTEX_POINT ( 'NONE', #203 ) ;
#430 = VERTEX_POINT ( 'NONE', #451 ) ;
#431 = FACE_BOUND ( 'NONE', #312, .T. ) ;
#432 = VERTEX_POINT ( 'NONE', #549 ) ;
#433 = CYLINDRICAL_SURFACE ( 'NONE', #579, 2.149999999999435030 ) ;
#434 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#435 = CIRCLE ( 'NONE', #494, 2.149999999999539391 ) ;
#436 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#437 = ORIENTED_EDGE ( 'NONE', *, *, #463, .F. ) ;
#438 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#439 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#440 = AXIS2_PLACEMENT_3D ( 'NONE', #363, #586, #84 ) ;
#441 = CIRCLE ( 'NONE', #89, 2.149999999999657518 ) ;
#442 = EDGE_LOOP ( 'NONE', ( #437, #282 ) ) ;
#443 = LINE ( 'NONE', #138, #152 ) ;
#444 = AXIS2_PLACEMENT_3D ( 'NONE', #390, #538, #96 ) ;
#445 = EDGE_CURVE ( 'NONE', #314, #417, #575, .T. ) ;
#446 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#447 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#448 = EDGE_CURVE ( 'NONE', #195, #264, #462, .T. ) ;
#449 = FACE_BOUND ( 'NONE', #342, .T. ) ;
#450 = LINE ( 'NONE', #6, #47 ) ;
#451 = CARTESIAN_POINT ( 'NONE', ( -12.54230484541280255, -7.487354696433185630, 8.000000000000000000 ) ) ;
#452 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #540 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #332, #484, #313 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#453 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541326405, -7.487354696433185630, 8.000000000000000000 ) ) ;
#454 = FACE_OUTER_BOUND ( 'NONE', #266, .T. ) ;
#455 = AXIS2_PLACEMENT_3D ( 'NONE', #16, #213, #208 ) ;
#456 = CARTESIAN_POINT ( 'NONE', ( -8.242304845413723768, -7.487354696433185630, 8.000000000000000000 ) ) ;
#457 = ORIENTED_EDGE ( 'NONE', *, *, #334, .F. ) ;
#458 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541326760, -7.487354696433178525, 8.000000000000000000 ) ) ;
#459 = CARTESIAN_POINT ( 'NONE', ( 8.242304845413610082, -7.487354696433178525, 8.000000000000000000 ) ) ;
#460 = ORIENTED_EDGE ( 'NONE', *, *, #560, .F. ) ;
#461 = CARTESIAN_POINT ( 'NONE', ( 2.150000000000000355, 10.51264530356682236, 8.000000000000000000 ) ) ;
#462 = CIRCLE ( 'NONE', #294, 2.149999999999657518 ) ;
#463 = EDGE_CURVE ( 'NONE', #616, #554, #159, .T. ) ;
#464 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#465 = VERTEX_POINT ( 'NONE', #52 ) ;
#466 = VERTEX_POINT ( 'NONE', #57 ) ;
#467 = CARTESIAN_POINT ( 'NONE', ( -12.54230484541280255, -7.487354696433185630, 0.000000000000000000 ) ) ;
#468 = CIRCLE ( 'NONE', #127, 19.00000000000000000 ) ;
#469 = ORIENTED_EDGE ( 'NONE', *, *, #635, .T. ) ;
#470 = ADVANCED_FACE ( 'NONE', ( #357 ), #19, .F. ) ;
#471 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#472 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#473 = EDGE_CURVE ( 'NONE', #554, #616, #408, .T. ) ;
#474 = EDGE_CURVE ( 'NONE', #320, #44, #133, .T. ) ;
#475 = ORIENTED_EDGE ( 'NONE', *, *, #412, .T. ) ;
#476 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#477 = CYLINDRICAL_SURFACE ( 'NONE', #392, 2.150000000000000799 ) ;
#478 = EDGE_LOOP ( 'NONE', ( #235, #224, #145, #323 ) ) ;
#479 = ORIENTED_EDGE ( 'NONE', *, *, #343, .F. ) ;
#480 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#481 = ORIENTED_EDGE ( 'NONE', *, *, #265, .T. ) ;
#482 = SURFACE_STYLE_FILL_AREA ( #283 ) ;
#483 = ADVANCED_FACE ( 'NONE', ( #261, #308, #545, #61, #257, #449, #5, #206 ), #104, .F. ) ;
#484 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#485 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541326405, -7.487354696433185630, 8.000000000000000000 ) ) ;
#486 = CARTESIAN_POINT ( 'NONE', ( -19.00000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#487 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#488 = CARTESIAN_POINT ( 'NONE', ( 8.242304845413448433, 4.512645303567090593, 8.000000000000000000 ) ) ;
#489 = CARTESIAN_POINT ( 'NONE', ( 8.242304845413610082, -7.487354696433178525, 8.000000000000000000 ) ) ;
#490 = ORIENTED_EDGE ( 'NONE', *, *, #281, .F. ) ;
#491 = VECTOR ( 'NONE', #464, 1000.000000000000000 ) ;
#492 =( NAMED_UNIT ( * ) PLANE_ANGLE_UNIT ( ) SI_UNIT ( $, .RADIAN. ) );
#493 = VECTOR ( 'NONE', #576, 1000.000000000000000 ) ;
#494 = AXIS2_PLACEMENT_3D ( 'NONE', #43, #286, #487 ) ;
#495 = LINE ( 'NONE', #459, #41 ) ;
#496 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#497 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#498 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#499 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#500 = CARTESIAN_POINT ( 'NONE', ( -12.54230484541302992, 4.512645303566650057, 8.000000000000000000 ) ) ;
#501 = PRESENTATION_LAYER_ASSIGNMENT ( '', '', ( #641 ) ) ;
#502 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#503 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541326760, -7.487354696433178525, 0.000000000000000000 ) ) ;
#504 = ORIENTED_EDGE ( 'NONE', *, *, #265, .F. ) ;
#505 = ADVANCED_FACE ( 'NONE', ( #201 ), #111, .T. ) ;
#506 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#507 = CYLINDRICAL_SURFACE ( 'NONE', #93, 2.150000000000000799 ) ;
#508 = ORIENTED_EDGE ( 'NONE', *, *, #627, .T. ) ;
#509 = EDGE_LOOP ( 'NONE', ( #26, #423, #490, #170 ) ) ;
#510 = CYLINDRICAL_SURFACE ( 'NONE', #278, 2.149999999999435030 ) ;
#511 = ORIENTED_EDGE ( 'NONE', *, *, #582, .T. ) ;
#512 = ADVANCED_FACE ( 'NONE', ( #303 ), #410, .F. ) ;
#513 = ORIENTED_EDGE ( 'NONE', *, *, #105, .F. ) ;
#514 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541326405, -7.487354696433185630, 0.000000000000000000 ) ) ;
#515 = EDGE_CURVE ( 'NONE', #137, #414, #358, .T. ) ;
#516 = CIRCLE ( 'NONE', #348, 2.149999999999435030 ) ;
#517 = CIRCLE ( 'NONE', #599, 2.149999999999539391 ) ;
#518 = EDGE_LOOP ( 'NONE', ( #110, #565 ) ) ;
#519 = ORIENTED_EDGE ( 'NONE', *, *, #9, .T. ) ;
#520 = ORIENTED_EDGE ( 'NONE', *, *, #353, .T. ) ;
#521 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541326760, -7.487354696433178525, 0.000000000000000000 ) ) ;
#522 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#523 = ORIENTED_EDGE ( 'NONE', *, *, #515, .T. ) ;
#524 = ORIENTED_EDGE ( 'NONE', *, *, #474, .F. ) ;
#525 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541344879, 4.512645303567090593, 8.000000000000000000 ) ) ;
#526 = AXIS2_PLACEMENT_3D ( 'NONE', #458, #498, #15 ) ;
#527 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#528 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#529 = EDGE_CURVE ( 'NONE', #387, #430, #300, .T. ) ;
#530 = CYLINDRICAL_SURFACE ( 'NONE', #444, 2.149999999999539391 ) ;
#531 = EDGE_CURVE ( 'NONE', #29, #432, #115, .T. ) ;
#532 = COLOUR_RGB ( '',0.7921568627450980005, 0.8196078431372548767, 0.9333333333333333481 ) ;
#533 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#534 = EDGE_CURVE ( 'NONE', #414, #88, #597, .T. ) ;
#535 = ORIENTED_EDGE ( 'NONE', *, *, #102, .T. ) ;
#536 = AXIS2_PLACEMENT_3D ( 'NONE', #411, #179, #76 ) ;
#537 =( LENGTH_UNIT ( ) NAMED_UNIT ( * ) SI_UNIT ( .MILLI., .METRE. ) );
#538 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#539 =( GEOMETRIC_REPRESENTATION_CONTEXT ( 3 ) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT ( ( #143 ) ) GLOBAL_UNIT_ASSIGNED_CONTEXT ( ( #537, #492, #642 ) ) REPRESENTATION_CONTEXT ( 'NONE', 'WORKASPACE' ) );
#540 = UNCERTAINTY_MEASURE_WITH_UNIT (LENGTH_MEASURE( 1.000000000000000082E-05 ), #332, 'distance_accuracy_value', 'NONE');
#541 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#542 = AXIS2_PLACEMENT_3D ( 'NONE', #8, #155, #60 ) ;
#543 = CARTESIAN_POINT ( 'NONE', ( -19.00000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#544 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#545 = FACE_BOUND ( 'NONE', #130, .T. ) ;
#546 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#547 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 8.000000000000000000 ) ) ;
#548 = CARTESIAN_POINT ( 'NONE', ( 7.000000000000000000, -1.487354696433178969, 8.000000000000000000 ) ) ;
#549 = CARTESIAN_POINT ( 'NONE', ( 8.242304845413448433, 4.512645303567090593, 0.000000000000000000 ) ) ;
#550 = ORIENTED_EDGE ( 'NONE', *, *, #632, .F. ) ;
#551 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#552 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#553 = CIRCLE ( 'NONE', #82, 2.150000000000000799 ) ;
#554 = VERTEX_POINT ( 'NONE', #352 ) ;
#555 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#556 = CIRCLE ( 'NONE', #608, 19.00000000000000000 ) ;
#557 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#558 = ORIENTED_EDGE ( 'NONE', *, *, #593, .F. ) ;
#559 = FACE_OUTER_BOUND ( 'NONE', #587, .T. ) ;
#560 = EDGE_CURVE ( 'NONE', #83, #419, #468, .T. ) ;
#561 = ORIENTED_EDGE ( 'NONE', *, *, #241, .T. ) ;
#562 = EDGE_LOOP ( 'NONE', ( #172, #379 ) ) ;
#563 = EDGE_CURVE ( 'NONE', #88, #120, #25, .T. ) ;
#564 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#565 = ORIENTED_EDGE ( 'NONE', *, *, #529, .F. ) ;
#566 = ORIENTED_EDGE ( 'NONE', *, *, #393, .T. ) ;
#567 = ORIENTED_EDGE ( 'NONE', *, *, #421, .T. ) ;
#568 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#569 = CIRCLE ( 'NONE', #184, 2.150000000000000355 ) ;
#570 = CARTESIAN_POINT ( 'NONE', ( 10.39230484541344879, 4.512645303567090593, 0.000000000000000000 ) ) ;
#571 = EDGE_LOOP ( 'NONE', ( #193, #481 ) ) ;
#572 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#573 = VERTEX_POINT ( 'NONE', #543 ) ;
#574 = ORIENTED_EDGE ( 'NONE', *, *, #38, .T. ) ;
#575 = CIRCLE ( 'NONE', #542, 2.149999999999435030 ) ;
#576 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#577 = ADVANCED_FACE ( 'NONE', ( #589 ), #90, .T. ) ;
#578 = FILL_AREA_STYLE_COLOUR ( '', #532 ) ;
#579 = AXIS2_PLACEMENT_3D ( 'NONE', #336, #175, #136 ) ;
#580 = AXIS2_PLACEMENT_3D ( 'NONE', #541, #289, #149 ) ;
#581 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#582 = EDGE_CURVE ( 'NONE', #414, #137, #384, .T. ) ;
#583 = EDGE_CURVE ( 'NONE', #417, #466, #341, .T. ) ;
#584 = ADVANCED_FACE ( 'NONE', ( #199 ), #3, .F. ) ;
#585 = CARTESIAN_POINT ( 'NONE', ( 2.149999999999436362, -13.48735469643318119, 8.000000000000000000 ) ) ;
#586 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#587 = EDGE_LOOP ( 'NONE', ( #598, #511, #176, #66 ) ) ;
#588 = CYLINDRICAL_SURFACE ( 'NONE', #164, 2.149999999999657518 ) ;
#589 = FACE_OUTER_BOUND ( 'NONE', #233, .T. ) ;
#590 = ORIENTED_EDGE ( 'NONE', *, *, #102, .F. ) ;
#591 = APPLICATION_PROTOCOL_DEFINITION ( 'draft international standard', 'automotive_design', 1998, #49 ) ;
#592 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541302957, 4.512645303566650057, 8.000000000000000000 ) ) ;
#593 = EDGE_CURVE ( 'NONE', #17, #53, #242, .T. ) ;
#594 = CARTESIAN_POINT ( 'NONE', ( 12.54230484541344914, 4.512645303567090593, 8.000000000000000000 ) ) ;
#595 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 10.51264530356682236, 0.000000000000000000 ) ) ;
#596 = VECTOR ( 'NONE', #409, 1000.000000000000000 ) ;
#597 = LINE ( 'NONE', #461, #220 ) ;
#598 = ORIENTED_EDGE ( 'NONE', *, *, #534, .F. ) ;
#599 = AXIS2_PLACEMENT_3D ( 'NONE', #485, #188, #297 ) ;
#600 = CYLINDRICAL_SURFACE ( 'NONE', #455, 2.150000000000000799 ) ;
#601 = PRODUCT ( '\X2\8f6e6bc257ab7247\X0\', '\X2\8f6e6bc257ab7247\X0\', '', ( #146 ) ) ;
#602 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, -1.487354696433179857, 0.000000000000000000 ) ) ;
#603 = LINE ( 'NONE', #150, #491 ) ;
#604 = CIRCLE ( 'NONE', #214, 2.150000000000000799 ) ;
#605 = DIRECTION ( 'NONE', ( 0.000000000000000000, 0.000000000000000000, 1.000000000000000000 ) ) ;
#606 = CARTESIAN_POINT ( 'NONE', ( -10.39230484541302957, 4.512645303566650057, 8.000000000000000000 ) ) ;
#607 = EDGE_LOOP ( 'NONE', ( #92, #14 ) ) ;
#608 = AXIS2_PLACEMENT_3D ( 'NONE', #339, #40, #108 ) ;
#609 = LINE ( 'NONE', #267, #31 ) ;
#610 = EDGE_CURVE ( 'NONE', #12, #22, #45, .T. ) ;
#611 = ORIENTED_EDGE ( 'NONE', *, *, #236, .F. ) ;
#612 = ORIENTED_EDGE ( 'NONE', *, *, #463, .T. ) ;
#613 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#614 = VERTEX_POINT ( 'NONE', #39 ) ;
#615 = ORIENTED_EDGE ( 'NONE', *, *, #190, .T. ) ;
#616 = VERTEX_POINT ( 'NONE', #489 ) ;
#617 = CARTESIAN_POINT ( 'NONE', ( 0.000000000000000000, 10.51264530356682236, 8.000000000000000000 ) ) ;
#618 = LINE ( 'NONE', #624, #34 ) ;
#619 = AXIS2_PLACEMENT_3D ( 'NONE', #382, #528, #630 ) ;
#620 = VERTEX_POINT ( 'NONE', #141 ) ;
#621 = ORIENTED_EDGE ( 'NONE', *, *, #351, .F. ) ;
#622 = CYLINDRICAL_SURFACE ( 'NONE', #230, 2.150000000000000355 ) ;
#623 = CIRCLE ( 'NONE', #536, 2.150000000000000799 ) ;
#624 = CARTESIAN_POINT ( 'NONE', ( 7.000000000000000000, -1.487354696433178969, 8.000000000000000000 ) ) ;
#625 = EDGE_LOOP ( 'NONE', ( #520, #292 ) ) ;
#626 = EDGE_LOOP ( 'NONE', ( #337, #166, #70, #36 ) ) ;
#627 = EDGE_CURVE ( 'NONE', #259, #429, #435, .T. ) ;
#628 = FACE_OUTER_BOUND ( 'NONE', #571, .T. ) ;
#629 = EDGE_LOOP ( 'NONE', ( #612, #227, #32, #457 ) ) ;
#630 = DIRECTION ( 'NONE', ( 1.000000000000000000, 0.000000000000000000, -0.000000000000000000 ) ) ;
#631 = APPLICATION_CONTEXT ( 'automotive_design' ) ;
#632 = EDGE_CURVE ( 'NONE', #554, #195, #443, .T. ) ;
#633 = CARTESIAN_POINT ( 'NONE', ( -2.150000000000000355, 10.51264530356682236, 0.000000000000000000 ) ) ;
#634 = AXIS2_PLACEMENT_3D ( 'NONE', #173, #274, #229 ) ;
#635 = EDGE_CURVE ( 'NONE', #430, #387, #517, .T. ) ;
#636 = ORIENTED_EDGE ( 'NONE', *, *, #534, .T. ) ;
#637 = ORIENTED_EDGE ( 'NONE', *, *, #281, .T. ) ;
#638 = DIRECTION ( 'NONE', ( -0.000000000000000000, -0.000000000000000000, -1.000000000000000000 ) ) ;
#639 = DIRECTION ( 'NONE', ( -1.000000000000000000, 0.000000000000000000, 0.000000000000000000 ) ) ;
#640 = ORIENTED_EDGE ( 'NONE', *, *, #236, .T. ) ;
#641 = STYLED_ITEM ( 'NONE', ( #271 ), #156 ) ;
#642 =( NAMED_UNIT ( * ) SI_UNIT ( $, .STERADIAN. ) SOLID_ANGLE_UNIT ( ) );
#643 = CIRCLE ( 'NONE', #135, 7.000000000000000000 ) ;
ENDSEC;
END-ISO-10303-21;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
机械设计采用26版本sw设计,装配,以及urdf导出,为减少sw装配出现问题,采用单件装配小件各个关节装配体,转step关节文件,step各关节装配总装配体方案
-7
View File
@@ -1,7 +0,0 @@
# 硬件
本目录用于保存 16DOF 轮足机器人的自研电路、接线图、BOM、传感器与计算平台说明。
第三方硬件资料不作为自研成果提交。
当前目录仅有范围说明,尚未归档 16DOF 平台的自研原理图、PCB、BOM 或正式接线图,不应将本目录视为已完成的硬件开源包。
-7
View File
@@ -1,7 +0,0 @@
# 嵌入式固件
本目录用于保存运行在 MCU 或其他嵌入式控制器上的固件源码和构建工程。
编译生成的 `.hex``.bin``.elf``.axf` 等文件不进入源码目录,可在需要时作为 Release 附件发布。
当前 16DOF 主线尚未在本目录归档 MCU/Keil 工程;该目录是预留入口。8DOF 大疆 A 板 Keil 工程由 `8dof` 分支和 `v0.1.0` 保存。
-45
View File
@@ -1,45 +0,0 @@
# 软件
本目录保存 16DOF 轮足机器人的训练、仿真和真机软件演进。
```text
05_software/
├─ train/
│ └─ rc_mjlab/ # 训练、MJCF、MuJoCo、Sim2Sim 和本地 mjlab 依赖
└─ real/
├─ ik_real/ # IK 轨迹与早期真机控制
├─ sim2real/ # 第一代 Python 策略真机部署
├─ sim2real_v2/ # Python Sim2Real v2
├─ sim2real_ros2/ # ROS 2/C++ Sim2Real 初版
├─ sim2real_ros2_v2/ # ROS 2 导航原型及里程计演进
└─ sim2real_ros2_v3/ # 最终比赛 ROS 2/C++ 部署
```
当前工作树按架构大版本同时保留三个 ROS 2 目录:无后缀目录是初版,`_v2` 是第二版演进的最终里程计快照,`_v3``last_not_slalom_1050` 最终比赛部署。各目录内部的小阶段仍可通过对应 Tag 恢复。
## 数据流
```text
MJCF + mjlab task
|
v
PPO 训练策略
|
+----> MuJoCo 姿态 / IK / MPC 调试
|
+----> Sim2Sim 策略验证
|
+----> Python Sim2Real / v2 ----> 电机 / IMU
|
+----> ROS 2/C++ Sim2Real -----> CAN / IMU / 导航
IK real --------------------------------> 电机
```
`rc_mjlab` 是自包含工程。训练、MJCF、MuJoCo、Sim2Sim、导航工具和策略权重通过相对路径绑定,因此保留其内部布局,没有为了目录外观拆散。第一代完整闭环见 `v0.3.0`,第一份新版 MJCF 与训练框架见 `v0.4.0`,随机化增强版见 `v0.5.0`,比赛最终训练架构见 `v0.6.0`,后期 MuJoCo 工具集见 `v0.7.0`,后期 Sim2Sim 与比赛 Rough 策略见 `v0.8.0`,完整导航打点工具见 `v0.8.1`Python Sim2Real v2 对应 `v0.9.0`ROS 2/C++ 初版对应 `v0.10.0`,简单导航原型对应 `v0.11.0`,完整 Odin/TensorRT 与站姿调参对应 `v0.11.1`,纯里程计导航联调对应 `v0.12.0`1050 分比赛最终部署对应 `v1.0.0`
详细说明见:
- [`train/README.md`](train/README.md)
- [`real/README.md`](real/README.md)
- [`../01_doc/architecture/early_software_stack.md`](../01_doc/architecture/early_software_stack.md)
-51
View File
@@ -1,51 +0,0 @@
# 真机控制版本演进
本目录保存 16DOF 轮足机器人从早期 Python 闭环到 ROS 2 部署的真机控制演进。
## `ik_real`
基于几何逆运动学和轨迹插值的真机控制探索,不依赖强化学习策略。主要用于验证电机接口、关节映射和姿态轨迹。
## `sim2real`
第一代 Python 策略部署栈,包含:
- 53D 观测到 16D 动作的策略运行时
- 电机映射和真机 IO
- IMU 接入
- 站立初始化与平衡
- 运行时安全检查和阻尼刹车
- Web 调试界面
- 对齐、标定和独立检查工具
部署说明见 [`sim2real/README.md`](sim2real/README.md) 与 [`sim2real/DEPLOYMENT.md`](sim2real/DEPLOYMENT.md)。
## `sim2real_v2`
Python Sim2Real v2,保留 `53D -> 16D` 策略接口,并增加电机反馈新鲜度、Odin odom 诊断、命令平滑、Web 运行时诊断和安全监控工具,对应 `v0.9.0`
部署说明见 [`sim2real_v2/README.md`](sim2real_v2/README.md) 与 [`sim2real_v2/DEPLOYMENT.md`](sim2real_v2/DEPLOYMENT.md)。
## ROS 2/C++ 版本线
### `sim2real_ros2`(初版,`v0.10.0`
无后缀目录固定表示 ROS 2/C++ Sim2Real 初版:将策略热路径迁移为 50 Hz C++ 推理和 200 Hz CAN 电机循环,并加入 ROS 2 消息、命令仲裁、Nav2 与统一启动结构。原始快照未随工程保存 Odin ROS 2 驱动源码,依赖边界见 [`sim2real_ros2/README.md`](sim2real_ros2/README.md)。
### `sim2real_ros2_v2``v0.11.0``v0.12.0`
`v0.11.0` 中,该目录是 ROS 2 Sim2Real v2 导航原型,增加简单导航节点、PCD 交互定位、任务点/任务序列和 Web 导航调试。
`v0.11.1` 在同一路径继续演进,首次归档完整 Odin 驱动、TensorRT、多策略切换和硬件诊断,并使用 `hip=0.670``knee=-1.390` 的调参站姿。
`v0.12.0` 仍在同一路径上形成里程计导航联调快照:固定纯里程计模式,加入 odom fallback 的 TF 冲突保护、A_min 路线和多地图工具;默认 Rough 策略为 `model_9600`,默认站姿回到比赛站姿。当前该目录保持 `v0.12.0` 快照,阶段说明见 [`sim2real_ros2_v2/README.md`](sim2real_ros2_v2/README.md)。
### `sim2real_ros2_v3`(最终比赛版;代码快照 `v1.0.0`,规范目录 `v1.1.0`
第三版来自原始目录 `sim2real_ros2_v2(last_not_slalom_1050)`,整理时正式命名为 `sim2real_ros2_v3`。它是 1050 分比赛最终部署,包含 `model_6800` Rough、`model_84` Wall、最终路线、完整 Odin 驱动、CAN 和触控屏。部署说明见 [`sim2real_ros2_v3/README.md`](sim2real_ros2_v3/README.md)。
## 实机记录
[![第一代 Sim2Real 真机验证](../../06_assets/images/early_sim2real_preview.jpg)](../../06_assets/videos/early_sim2real.mp4)
该视频记录了这一阶段的早期真机测试,用于对应本目录中的第一代控制与部署实现。
-9
View File
@@ -1,9 +0,0 @@
# IK 真机控制探索
该目录保存强化学习部署前的逆运动学真机控制代码。
- `sim2real_control_api.py`:真机控制接口
- `trajectory_interpolator.py`:关节/姿态轨迹插值
- `sim_to_real_deploy_beifen.py`:早期部署脚本备份
文件名中的 `beifen` 来自原始资料。为保持早期版本可追溯性,本次归档不修改源码和文件名。
@@ -1,274 +0,0 @@
"""Sim-to-real control/IK API extracted from mature mujoco_sim controller.
This module provides a deployment-friendly wrapper around:
- wheel mode posture control
- trot swing-leg IK + wheel assist
- differential wheel speed mapping
No MuJoCo runtime is required for using the API itself.
For trot IK, Pinocchio model is used via Dynamics.
"""
from dataclasses import dataclass
from typing import Dict, Optional, Tuple
import numpy as np
from config import (
LEG_NAMES,
LEG_JOINTS,
WHEEL_JOINT,
DEFAULT_JOINT_ANGLES,
WHEEL_RADIUS,
WHEEL_TRACK,
WHEEL_VEL_MAX,
KP_ROLL,
KP_PITCH,
GAIT_FREQ,
GAIT_DUTY,
SWING_HEIGHT,
PHASE_OFFSETS,
)
from dynamics import Dynamics
@dataclass
class DeployState:
"""Minimal state for deployment control."""
rpy: np.ndarray # (3,) roll, pitch, yaw
class Sim2RealControlAPI:
"""Deployment-friendly control and IK API.
Supported modes:
- wheel: wheel differential drive + leg posture hold
- trot: swing foot IK + stance posture + wheel assist
"""
def __init__(self):
self.mode = "wheel"
self.prone = False
self.vel_x = 0.0
self.vel_y = 0.0
self.yaw_rate = 0.0
self.height = 0.33
self._gait_phase = 0.0
self._smooth_vx = 0.0
self._smooth_vy = 0.0
self._smooth_yaw = 0.0
self._default_q = np.array([
DEFAULT_JOINT_ANGLES["hip_abduction"],
DEFAULT_JOINT_ANGLES["hip_pitch"],
DEFAULT_JOINT_ANGLES["knee"],
])
self.dynamics = Dynamics()
self._swing_start_foot = {leg: np.zeros(3) for leg in LEG_NAMES}
self._last_contact = {leg: True for leg in LEG_NAMES}
def set_mode(self, mode: str):
if mode not in ("wheel", "trot"):
raise ValueError("mode must be one of: wheel, trot")
self.mode = mode
def set_command(self, vel_x: float, vel_y: float, yaw_rate: float, height: Optional[float] = None):
self.vel_x = float(vel_x)
self.vel_y = float(vel_y)
self.yaw_rate = float(yaw_rate)
if height is not None:
self.height = float(height)
def compute(
self,
state: DeployState,
dt: float,
q_pin: Optional[np.ndarray] = None,
dq_pin: Optional[np.ndarray] = None,
) -> Tuple[np.ndarray, np.ndarray]:
"""Compute leg and wheel commands.
Returns:
leg_targets: (12,) [fl(3), fr(3), rl(3), rr(3)]
wheel_targets: (4,) [fl, fr, rl, rr] in rad/s
Notes:
- wheel mode does not require q_pin/dq_pin
- trot mode requires q_pin/dq_pin for IK/FK through Pinocchio
"""
alpha = min(float(dt) * 3.0, 1.0)
self._smooth_vx += alpha * (self.vel_x - self._smooth_vx)
self._smooth_vy += alpha * (self.vel_y - self._smooth_vy)
self._smooth_yaw += alpha * (self.yaw_rate - self._smooth_yaw)
if self.prone:
return self._prone_mode()
if self.mode == "wheel":
return self._wheel_mode(state)
if q_pin is None or dq_pin is None:
raise ValueError("trot mode requires q_pin and dq_pin")
return self._trot_mode(state, float(dt), q_pin, dq_pin)
def to_joint_dict(self, leg_targets: np.ndarray, wheel_targets: np.ndarray) -> Dict[str, float]:
"""Convert array commands to named joint-command dictionary."""
out: Dict[str, float] = {}
for i, leg in enumerate(LEG_NAMES):
out[f"{leg}_{LEG_JOINTS[0]}"] = float(leg_targets[i * 3 + 0])
out[f"{leg}_{LEG_JOINTS[1]}"] = float(leg_targets[i * 3 + 1])
out[f"{leg}_{LEG_JOINTS[2]}"] = float(leg_targets[i * 3 + 2])
out[f"{leg}_{WHEEL_JOINT}"] = float(wheel_targets[i])
return out
def _prone_mode(self):
leg_targets = np.zeros(12)
for i, leg in enumerate(LEG_NAMES):
side = 1.0 if leg[1] == "l" else -1.0
leg_targets[i * 3 + 0] = side * 0.3
leg_targets[i * 3 + 1] = 1.5
leg_targets[i * 3 + 2] = -2.65
return leg_targets, np.zeros(4)
def _wheel_mode(self, state: DeployState):
wheel_targets = self._differential_drive(self._smooth_vx, self._smooth_yaw)
leg_targets = self._posture_control(state)
return leg_targets, wheel_targets
def _posture_control(self, state: DeployState) -> np.ndarray:
leg_targets = np.zeros(12)
_H = [0.157, 0.248, 0.311, 0.366, 0.411, 0.448]
_HIP = [1.5, 1.2, 1.0, 0.8, 0.6, 0.4]
_KNEE = [-2.5, -2.1, -1.8, -1.5, -1.2, -0.9]
h_clamp = np.clip(self.height, _H[0], _H[-1])
q_hip_base = float(np.interp(h_clamp, _H, _HIP))
q_knee_base = float(np.interp(h_clamp, _H, _KNEE))
roll_corr = -KP_ROLL * float(state.rpy[0])
pitch_corr = -KP_PITCH * float(state.rpy[1])
lateral_lean = 0.3 * self.vel_y
for i, leg in enumerate(LEG_NAMES):
side = 1.0 if leg[1] == "l" else -1.0
leg_targets[i * 3 + 0] = np.clip(side * roll_corr + lateral_lean, -0.5, 0.5)
leg_targets[i * 3 + 1] = np.clip(q_hip_base + pitch_corr, -1.0, 2.5)
leg_targets[i * 3 + 2] = np.clip(q_knee_base, -2.6, -0.3)
return leg_targets
def _trot_mode(self, state: DeployState, dt: float, q_pin: np.ndarray, dq_pin: np.ndarray):
self._gait_phase = (self._gait_phase + dt * GAIT_FREQ) % 1.0
contacts: Dict[str, bool] = {}
for leg in LEG_NAMES:
phase = (self._gait_phase + PHASE_OFFSETS[leg]) % 1.0
contacts[leg] = bool(phase < GAIT_DUTY)
self.dynamics.update(q_pin, dq_pin)
leg_targets = np.zeros(12)
wheel_targets = np.zeros(4)
for i, leg in enumerate(LEG_NAMES):
if contacts[leg]:
leg_targets[i * 3:(i + 1) * 3] = self._stance_leg_target(state, leg)
self._swing_start_foot[leg] = self.dynamics.get_foot_pos(leg)
self._last_contact[leg] = True
wheel_targets[i] = self._differential_drive_single(self._smooth_vx, self._smooth_yaw, leg)
else:
swing_phase = self._get_swing_phase(leg)
target_foot = self._compute_swing_target(leg, state, swing_phase)
q_ik = self.dynamics.inverse_kinematics(leg, target_foot, q_pin)
leg_targets[i * 3:(i + 1) * 3] = q_ik
self._last_contact[leg] = False
wheel_targets[i] = 0.0
return leg_targets, wheel_targets
def _stance_leg_target(self, state: DeployState, leg: str) -> np.ndarray:
_H = [0.157, 0.248, 0.311, 0.366, 0.411, 0.448]
_HIP = [1.5, 1.2, 1.0, 0.8, 0.6, 0.4]
_KNEE = [-2.5, -2.1, -1.8, -1.5, -1.2, -0.9]
h_clamp = np.clip(self.height, _H[0], _H[-1])
q_hip = float(np.interp(h_clamp, _H, _HIP))
q_knee = float(np.interp(h_clamp, _H, _KNEE))
roll_corr = -KP_ROLL * float(state.rpy[0])
pitch_corr = -KP_PITCH * float(state.rpy[1])
side = 1.0 if leg[1] == "l" else -1.0
lateral_lean = 0.3 * self.vel_y
return np.array([
np.clip(side * roll_corr + lateral_lean, -0.5, 0.5),
np.clip(q_hip + pitch_corr, -1.0, 2.5),
np.clip(q_knee, -2.6, -0.3),
])
def _differential_drive(self, vel_x: float, yaw_rate: float) -> np.ndarray:
vel_left = (vel_x - 0.5 * WHEEL_TRACK * yaw_rate) / WHEEL_RADIUS
vel_right = (vel_x + 0.5 * WHEEL_TRACK * yaw_rate) / WHEEL_RADIUS
targets = np.zeros(4)
for i, leg in enumerate(LEG_NAMES):
targets[i] = vel_left if leg[1] == "l" else vel_right
return np.clip(targets, -WHEEL_VEL_MAX, WHEEL_VEL_MAX)
def _differential_drive_single(self, vel_x: float, yaw_rate: float, leg: str) -> float:
if leg[1] == "l":
v = (vel_x - 0.5 * WHEEL_TRACK * yaw_rate) / WHEEL_RADIUS
else:
v = (vel_x + 0.5 * WHEEL_TRACK * yaw_rate) / WHEEL_RADIUS
return float(np.clip(v, -WHEEL_VEL_MAX, WHEEL_VEL_MAX))
def _get_swing_phase(self, leg: str) -> float:
phase = (self._gait_phase + PHASE_OFFSETS[leg]) % 1.0
if phase < GAIT_DUTY:
return 0.0
return (phase - GAIT_DUTY) / (1.0 - GAIT_DUTY)
def _compute_swing_target(self, leg: str, state: DeployState, swing_phase: float) -> np.ndarray:
p_start = self._swing_start_foot[leg]
p_end = self._compute_touchdown(leg, state)
s = swing_phase
s_mj = 10 * s**3 - 15 * s**4 + 6 * s**5
pos = p_start + (p_end - p_start) * s_mj
z_lift = 64.0 * s**3 * (1.0 - s)**3
pos[2] = p_start[2] + SWING_HEIGHT * z_lift
return pos
def _compute_touchdown(self, leg: str, state: DeployState) -> np.ndarray:
td = self._swing_start_foot[leg].copy()
t_stance = (1.0 / GAIT_FREQ) * GAIT_DUTY
yaw = float(state.rpy[2])
c, s = np.cos(yaw), np.sin(yaw)
R_z = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
cmd_vel_world = R_z @ np.array([self._smooth_vx, self._smooth_vy, 0.0])
td[0] += cmd_vel_world[0] * t_stance * 0.5
td[1] += cmd_vel_world[1] * t_stance * 0.5
td[2] = WHEEL_RADIUS
return td
if __name__ == "__main__":
api = Sim2RealControlAPI()
api.set_mode("wheel")
api.set_command(vel_x=0.3, vel_y=0.0, yaw_rate=0.0, height=0.33)
state = DeployState(rpy=np.array([0.0, 0.0, 0.0]))
leg, wheel = api.compute(state=state, dt=0.004)
cmd = api.to_joint_dict(leg, wheel)
print("Example wheel-mode command:")
for k, v in sorted(cmd.items()):
print(f"{k}: {v:.6f}")
@@ -1,234 +0,0 @@
#!/usr/bin/env python3
import argparse, sys, threading, time
from dataclasses import dataclass
from pathlib import Path
import numpy as np
sys.path.append('/home/rc2/work/rcwork/control')
from drivers.motor_driver import RobStrideDriver
ROOT = Path('/home/rc2/work/rcwork/wheelleg_deploy_swj/wheelleg_deploy/wheelleg_mjlab/beifen')
sys.path += [str(ROOT), str(ROOT / 'mujoco_sim')]
from sim2real_control_api import DeployState, Sim2RealControlAPI # type: ignore
sys.path.append('/home/rc2/work/rcwork')
from trajectory_interpolator import TrajectoryInterpolator
LEGS = ('fl','fr','rl','rr')
LJ = ('hip_abduction_joint','hip_pitch_joint','knee_joint')
WJ = 'wheel_joint'
JNS = [f'{l}_{j}' for l in LEGS for j in (*LJ, WJ)]
@dataclass
class Cfg:
mid:int; model:str; sign:float; off:float; bus:str
class Deploy:
def __init__(self, can1, can2, hz=100.0, use_interpolation=True, interp_method='quintic', interp_time=1.5):
self.d1, self.d2 = RobStrideDriver(can1, False), RobStrideDriver(can2, False)
self.api = Sim2RealControlAPI(); self.dt = 1.0/hz; self.lk = threading.Lock()
self.run = False; self.enabled = False; self.estop = True
self.mode='stand'; self.vx=0.0; self.vy=0.0; self.yaw=0.0; self.h=0.33; self.roll=0.0; self.pitch=0.0
self.prone = False
self.kp_leg, self.kd_leg, self.kd_wheel = 80.0, 2.5, 2.0
self.cfg = self._cfg(); self.q=np.zeros(23); self.dq=np.zeros(22)
self.use_interpolation = use_interpolation
if self.use_interpolation:
self.interpolator = TrajectoryInterpolator(method=interp_method, transition_time=interp_time)
print(f'[Interpolation] Enabled: method={interp_method}, transition_time={interp_time}s')
else:
self.interpolator = None
print('[Interpolation] Disabled')
def _cfg(self):
sign={'fl_hip_abduction_joint':-1,'fl_hip_pitch_joint':-1,'fl_knee_joint':-1,'fl_wheel_joint':-1,'fr_hip_abduction_joint':-1,'fr_hip_pitch_joint':1,'fr_knee_joint':1,'fr_wheel_joint':1,'rl_hip_abduction_joint':1,'rl_hip_pitch_joint':-1,'rl_knee_joint':-1,'rl_wheel_joint':-1,'rr_hip_abduction_joint':1,'rr_hip_pitch_joint':1,'rr_knee_joint':1,'rr_wheel_joint':1}
off={'fl_hip_abduction_joint':0.003,'fl_hip_pitch_joint':0.030,'fl_knee_joint':0.028,'fl_wheel_joint':0.0,'fr_hip_abduction_joint':0.004,'fr_hip_pitch_joint':0.038,'fr_knee_joint':0.011,'fr_wheel_joint':0.0,'rl_hip_abduction_joint':0.019,'rl_hip_pitch_joint':-0.034,'rl_knee_joint':0.025,'rl_wheel_joint':0.0,'rr_hip_abduction_joint':-0.001,'rr_hip_pitch_joint':0.039,'rr_knee_joint':0.018,'rr_wheel_joint':0.0}
ids={'fl_hip_abduction_joint':1,'fl_hip_pitch_joint':2,'fl_knee_joint':3,'fl_wheel_joint':4,'fr_hip_abduction_joint':5,'fr_hip_pitch_joint':6,'fr_knee_joint':7,'fr_wheel_joint':8,'rl_hip_abduction_joint':1,'rl_hip_pitch_joint':2,'rl_knee_joint':3,'rl_wheel_joint':4,'rr_hip_abduction_joint':5,'rr_hip_pitch_joint':6,'rr_knee_joint':7,'rr_wheel_joint':8}
bus={k:('can1' if k.startswith('f') else 'can2') for k in JNS}
return {jn:Cfg(ids[jn],'rs-06',float(sign[jn]),float(off[jn]),bus[jn]) for jn in JNS}
def _drv(self, jn): return self.d1 if self.cfg[jn].bus=='can1' else self.d2
def connect(self):
self.d1.connect(); self.d2.connect()
for jn,c in self.cfg.items(): self._drv(jn).add_motor(jn,c.mid,c.model)
def enable_all(self):
for jn in JNS: self._drv(jn).enable(jn)
with self.lk: self.enabled=True; self.estop=False; self.vx=self.vy=self.yaw=0.0
def disable_all(self):
for jn in JNS: self._drv(jn).disable(jn)
with self.lk: self.enabled=False
def clear(self):
for jn in JNS: self._drv(jn).clear_warnings(jn)
def set_estop(self,on):
with self.lk:
self.estop=on
if on: self.vx=self.vy=self.yaw=0.0
if on: self.disable_all()
def set_prone(self,on):
with self.lk: self.prone=on; self.api.prone=on
def _update_pin(self,leg,wheel):
q=np.zeros(23); dq=np.zeros(22); q[2]=self.h; q[6]=1.0
for i,_ in enumerate(LEGS):
b=7+i*4; q[b:b+3]=leg[i*3:i*3+3]; dq[6+i*4+3]=wheel[i]
self.q,self.dq=q,dq
def step(self):
with self.lk:
if self.estop or (not self.enabled): return
m=self.mode; vx=float(np.clip(self.vx,-0.8,0.8)); vy=float(np.clip(self.vy,-0.5,0.5)); yaw=float(np.clip(self.yaw,-3,3)); h=float(np.clip(self.h,0.157,0.448)); r=float(np.clip(self.roll,-0.4,0.4)); p=float(np.clip(self.pitch,-0.4,0.4)); prone=self.prone
cm='trot' if m=='trot' else 'wheel'
if m=='stand': vx=vy=yaw=0.0
self.api.prone=prone; self.api.set_mode(cm); self.api.set_command(vx,vy,yaw,height=h)
st=DeployState(rpy=np.array([r,p,0.0]))
leg,wheel = self.api.compute(st,self.dt,self.q,self.dq) if cm=='trot' else self.api.compute(st,self.dt)
self._update_pin(leg,wheel); cmd=self.api.to_joint_dict(leg,wheel)
if self.use_interpolation and self.interpolator is not None:
leg_cmd = {jn: cmd[jn] for jn in JNS if not jn.endswith(WJ)}
self.interpolator.set_target(leg_cmd)
smooth_cmd = self.interpolator.update(self.dt)
for jn in leg_cmd:
cmd[jn] = smooth_cmd[jn]
for jn in JNS:
d=self._drv(jn); c=self.cfg[jn]
if jn.endswith(WJ): d.control_mit(jn,0.0,c.sign*float(cmd.get(jn,0.0)),0.0,self.kd_wheel,0.0)
else: d.control_mit(jn,c.sign*float(cmd[jn])+c.off,0.0,self.kp_leg,self.kd_leg,0.0)
def loop(self):
while self.run:
t=time.time()
try: self.step()
except Exception as e: print('[control]',e)
time.sleep(max(0.0,self.dt-(time.time()-t)))
def start(self): self.run=True; threading.Thread(target=self.loop,daemon=True).start()
def stop(self):
self.run=False; time.sleep(0.05)
try: self.disable_all()
finally: self.d1.disconnect(); self.d2.disconnect()
def status(self):
with self.lk: return f'mode={self.mode} en={self.enabled} estop={self.estop} prone={self.prone} vx={self.vx:.2f} vy={self.vy:.2f} yaw={self.yaw:.2f} h={self.h:.3f}'
class CLI:
def __init__(self,d): self.d=d
def run(self):
print('enable disable clear estop_on estop_off prone_on prone_off status')
print('mode stand|wheel|trot, vx vy yaw h roll pitch, stop, quit')
print('interp_on interp_off interp_time <sec>, interp_method linear|cubic|quintic|cosine')
while True:
try: s=input('cmd> ').strip().lower()
except (EOFError,KeyboardInterrupt): s='quit'
if s in ('quit','exit'): break
if s=='enable': self.d.enable_all(); continue
if s=='disable': self.d.disable_all(); continue
if s=='clear': self.d.clear(); continue
if s=='estop_on': self.d.set_estop(True); continue
if s=='estop_off': self.d.set_estop(False); continue
if s=='prone_on': self.d.set_prone(True); continue
if s=='prone_off': self.d.set_prone(False); continue
if s=='status': print(self.d.status()); continue
if s=='stop':
with self.d.lk: self.d.vx=self.d.vy=self.d.yaw=0.0
continue
if s=='interp_on':
with self.d.lk: self.d.use_interpolation=True
print('Interpolation enabled'); continue
if s=='interp_off':
with self.d.lk: self.d.use_interpolation=False
print('Interpolation disabled'); continue
if s.startswith('interp_time '):
try:
t=float(s.split()[1])
if self.d.interpolator: self.d.interpolator.set_transition_time(t)
print(f'Interpolation time set to {t}s')
except Exception as e: print(f'Error: {e}')
continue
if s.startswith('interp_method '):
try:
method=s.split()[1]
if self.d.interpolator: self.d.interpolator.set_method(method)
print(f'Interpolation method set to {method}')
except Exception as e: print(f'Error: {e}')
continue
if s.startswith('mode '):
m=s.split()[1]
if m in ('stand','wheel','trot'):
with self.d.lk: self.d.mode=m
else: print('bad mode')
continue
try:
k,v=s.split()[0],float(s.split()[1])
with self.d.lk:
if k=='vx': self.d.vx=v
elif k=='vy': self.d.vy=v
elif k=='yaw': self.d.yaw=v
elif k=='h': self.d.h=v
elif k=='roll': self.d.roll=v
elif k=='pitch': self.d.pitch=v
else: print('unknown')
except Exception: print('unknown/bad')
class GUI:
def __init__(self,d):
import tkinter as tk
from tkinter import ttk
self.d=d; self.root=tk.Tk(); self.root.title('WheelLeg Deploy')
f=ttk.Frame(self.root,padding=8); f.grid(row=0,column=0,sticky='nsew')
self.state=tk.StringVar(value='E-STOP ON'); ttk.Label(f,textvariable=self.state).grid(row=0,column=0,columnspan=4,sticky='w')
ttk.Button(f,text='Enable',command=self.en).grid(row=1,column=0)
ttk.Button(f,text='Disable',command=self.dis).grid(row=1,column=1)
ttk.Button(f,text='E-STOP ON',command=lambda:self.es(True)).grid(row=1,column=2)
ttk.Button(f,text='E-STOP OFF',command=lambda:self.es(False)).grid(row=1,column=3)
ttk.Button(f,text='Prone ON',command=lambda:self.pr(True)).grid(row=2,column=2)
ttk.Button(f,text='Prone OFF',command=lambda:self.pr(False)).grid(row=2,column=3)
self.mode=tk.StringVar(value='stand'); self.vx=tk.DoubleVar(value=0.0); self.vy=tk.DoubleVar(value=0.0); self.yaw=tk.DoubleVar(value=0.0); self.h=tk.DoubleVar(value=0.33)
self.roll=tk.DoubleVar(value=0.0); self.pitch=tk.DoubleVar(value=0.0)
cb=ttk.Combobox(f,textvariable=self.mode,values=['stand','wheel','trot'],state='readonly'); cb.grid(row=3,column=0,columnspan=2,sticky='ew'); cb.bind('<<ComboboxSelected>>',lambda _:self.sync())
ttk.Button(f,text='Stop',command=self.stp).grid(row=3,column=3)
self.sl(f,4,'vx',self.vx,-0.8,0.8); self.sl(f,5,'vy',self.vy,-0.5,0.5); self.sl(f,6,'yaw',self.yaw,-3,3); self.sl(f,7,'height',self.h,0.157,0.448); self.sl(f,8,'roll',self.roll,-0.4,0.4); self.sl(f,9,'pitch',self.pitch,-0.4,0.4)
self.info=tk.StringVar(value=''); ttk.Label(f,textvariable=self.info).grid(row=10,column=0,columnspan=4,sticky='w'); self.tick()
def sl(self,f,r,n,v,lo,hi):
from tkinter import ttk
ttk.Label(f,text=n).grid(row=r,column=0,sticky='w'); ttk.Scale(f,from_=lo,to=hi,variable=v,command=lambda _:self.sync()).grid(row=r,column=1,columnspan=3,sticky='ew')
def sync(self):
with self.d.lk:
self.d.mode=self.mode.get(); self.d.vx=float(self.vx.get()); self.d.vy=float(self.vy.get()); self.d.yaw=float(self.yaw.get()); self.d.h=float(self.h.get()); self.d.roll=float(self.roll.get()); self.d.pitch=float(self.pitch.get())
def en(self): self.d.enable_all(); self.state.set('Enabled')
def dis(self): self.d.disable_all(); self.state.set('Disabled')
def es(self,on): self.d.set_estop(on); self.state.set('E-STOP ON' if on else 'E-STOP OFF')
def pr(self,on): self.d.set_prone(on)
def stp(self):
with self.d.lk: self.d.vx=self.d.vy=self.d.yaw=0.0
self.vx.set(0.0); self.vy.set(0.0); self.yaw.set(0.0)
def tick(self): self.info.set(self.d.status()); self.root.after(150,self.tick)
def run(self): self.root.mainloop()
def main():
ap=argparse.ArgumentParser()
ap.add_argument('--port-can1',default='/dev/can1')
ap.add_argument('--port-can2',default='/dev/can2')
ap.add_argument('--hz',type=float,default=100.0)
ap.add_argument('--no-gui',action='store_true')
ap.add_argument('--no-interp',action='store_true',help='Disable trajectory interpolation')
ap.add_argument('--interp-method',default='quintic',choices=['linear','cubic','quintic','cosine'],help='Interpolation method')
ap.add_argument('--interp-time',type=float,default=0.3,help='Interpolation transition time (seconds)')
a=ap.parse_args()
d=Deploy(a.port_can1,a.port_can2,a.hz,use_interpolation=not a.no_interp,interp_method=a.interp_method,interp_time=a.interp_time); d.connect(); d.start()
try:
cli=CLI(d); t=threading.Thread(target=cli.run,daemon=True); t.start()
if a.no_gui:
while t.is_alive(): time.sleep(0.2)
else: GUI(d).run()
finally: d.stop()
if __name__=='__main__': main()
@@ -1,237 +0,0 @@
#!/usr/bin/env python3
"""
轨迹插值模块 - 用于平滑关节角度过渡,避免突变和冲击
支持多种插值方法:
- linear: 线性插值
- cubic: 三次多项式(速度连续)
- quintic: 五次多项式(速度和加速度连续,最平滑)
- cosine: 余弦 S 曲线
使用示例:
interp = TrajectoryInterpolator(method='quintic', transition_time=0.5)
# 设置新目标
interp.set_target({'joint1': 1.5, 'joint2': 0.8})
# 每个控制周期调用
smooth_q = interp.update(dt=0.01, current_q={'joint1': 0.5, 'joint2': 0.3})
"""
import time
from typing import Dict, Optional
import numpy as np
class TrajectoryInterpolator:
def __init__(self, method: str = 'quintic', transition_time: float = 0.5):
"""
初始化轨迹插值器
Args:
method: 插值方法 ('linear', 'cubic', 'quintic', 'cosine')
transition_time: 过渡时间(秒)
"""
self.method = method
self.transition_time = transition_time
self.q_start: Dict[str, float] = {}
self.q_target: Dict[str, float] = {}
self.q_current: Dict[str, float] = {}
self.transition_start_time: Optional[float] = None
self.is_transitioning = False
self._interpolation_funcs = {
'linear': self._linear,
'cubic': self._cubic,
'quintic': self._quintic,
'cosine': self._cosine,
}
if method not in self._interpolation_funcs:
raise ValueError(f"Unknown interpolation method: {method}. "
f"Available: {list(self._interpolation_funcs.keys())}")
def set_target(self, q_target: Dict[str, float], force_restart: bool = False):
"""
设置新的目标角度,开始新的过渡
Args:
q_target: 目标关节角度字典 {joint_name: angle}
force_restart: 是否强制重新开始过渡(即使已经在过渡中)
"""
if not self.q_current:
self.q_current = q_target.copy()
self.q_target = q_target.copy()
self.q_start = q_target.copy()
self.is_transitioning = False
return
if not force_restart and self.is_transitioning:
self.q_target = q_target.copy()
return
self.q_start = self.q_current.copy()
self.q_target = q_target.copy()
self.transition_start_time = time.time()
self.is_transitioning = True
def update(self, dt: float, current_q: Optional[Dict[str, float]] = None) -> Dict[str, float]:
"""
更新插值状态,返回当前应该下发的平滑角度
Args:
dt: 时间步长(秒)
current_q: 可选的当前实际角度(用于初始化或同步)
Returns:
平滑后的关节角度字典
"""
if current_q is not None and not self.q_current:
self.q_current = current_q.copy()
self.q_start = current_q.copy()
self.q_target = current_q.copy()
return self.q_current.copy()
if not self.is_transitioning:
return self.q_target.copy()
elapsed = time.time() - self.transition_start_time
if elapsed >= self.transition_time:
self.q_current = self.q_target.copy()
self.is_transitioning = False
return self.q_current.copy()
s = elapsed / self.transition_time
alpha = self._interpolation_funcs[self.method](s)
self.q_current = {}
for joint_name in self.q_target:
start_val = self.q_start.get(joint_name, 0.0)
target_val = self.q_target[joint_name]
self.q_current[joint_name] = start_val + (target_val - start_val) * alpha
return self.q_current.copy()
def reset(self, q_init: Optional[Dict[str, float]] = None):
"""
重置插值器状态
Args:
q_init: 初始角度,如果为 None 则清空所有状态
"""
if q_init is None:
self.q_start = {}
self.q_target = {}
self.q_current = {}
else:
self.q_start = q_init.copy()
self.q_target = q_init.copy()
self.q_current = q_init.copy()
self.transition_start_time = None
self.is_transitioning = False
def is_done(self) -> bool:
"""返回是否已完成当前过渡"""
return not self.is_transitioning
def set_transition_time(self, t: float):
"""动态修改过渡时间"""
self.transition_time = max(0.01, t)
def set_method(self, method: str):
"""动态修改插值方法"""
if method not in self._interpolation_funcs:
raise ValueError(f"Unknown method: {method}")
self.method = method
@staticmethod
def _linear(s: float) -> float:
"""线性插值:alpha = s"""
return np.clip(s, 0.0, 1.0)
@staticmethod
def _cubic(s: float) -> float:
"""三次多项式:alpha = 3s² - 2s³"""
s = np.clip(s, 0.0, 1.0)
return 3.0 * s**2 - 2.0 * s**3
@staticmethod
def _quintic(s: float) -> float:
"""五次多项式:alpha = 10s³ - 15s⁴ + 6s⁵"""
s = np.clip(s, 0.0, 1.0)
return 10.0 * s**3 - 15.0 * s**4 + 6.0 * s**5
@staticmethod
def _cosine(s: float) -> float:
"""余弦 S 曲线:alpha = (1 - cos(πs)) / 2"""
s = np.clip(s, 0.0, 1.0)
return (1.0 - np.cos(np.pi * s)) / 2.0
if __name__ == '__main__':
import matplotlib.pyplot as plt
print("轨迹插值模块测试")
print("=" * 60)
methods = ['linear', 'cubic', 'quintic', 'cosine']
colors = ['blue', 'green', 'red', 'purple']
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
for method, color in zip(methods, colors):
interp = TrajectoryInterpolator(method=method, transition_time=1.0)
interp.reset({'joint1': 0.0})
interp.set_target({'joint1': 1.0})
times = []
positions = []
velocities = []
t = 0.0
dt = 0.01
last_pos = 0.0
while t <= 1.0:
q = interp.update(dt)
pos = q['joint1']
vel = (pos - last_pos) / dt if t > 0 else 0.0
times.append(t)
positions.append(pos)
velocities.append(vel)
last_pos = pos
t += dt
ax1.plot(times, positions, label=method, color=color, linewidth=2)
ax2.plot(times, velocities, label=method, color=color, linewidth=2)
ax1.set_xlabel('时间 (s)')
ax1.set_ylabel('位置 (rad)')
ax1.set_title('不同插值方法的位置曲线')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.set_xlabel('时间 (s)')
ax2.set_ylabel('速度 (rad/s)')
ax2.set_title('不同插值方法的速度曲线')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/home/rc2/work/rcwork/trajectory_interpolation_comparison.png', dpi=150)
print("已保存对比图到: trajectory_interpolation_comparison.png")
print("\n测试完成")
print("=" * 60)
print("推荐使用:")
print(" - quintic: 最平滑,速度和加速度连续")
print(" - cosine: 平滑且计算简单")
print(" - cubic: 速度连续,比 quintic 稍快")
print(" - linear: 最简单但速度会突变")
-52
View File
@@ -1,52 +0,0 @@
# `sim2real` 部署说明
> 版本范围:第一代 Python Sim2Real`v0.3.0`)。本文“当前”均指该快照。
## 模型
当前只使用:
- `policies/model_rough.pt`
## 模型契约
- `obs_dim = 53`
- `action_dim = 16`
- 单帧输入
-`base_lin_vel`
-`height_scan`
## 启动流程
1. 连接硬件
2. 使能电机
3. 从当前实测姿态起立
4. 进入 `stand_balance` 闭环站立
5. prime 当前观测
6. 进入 `50Hz` runtime
## 为什么这样改
- 之前版本在 `startup` 后只维持固定 `STAND_POSE`
- 实机上纯 PD 不足以持续抗姿态扰动
- 现在增加独立站立闭环,先保证身体支撑,再进入策略
## 运行开关
- `config.yaml > policy.enable_zero_cmd_suppression`
- `config.yaml > stand_balance.enabled`
- `config.yaml > policy.hold_zero_command_pose`
- `config.yaml > policy.command_release_s`
## 纯 Python 命令
默认前提:当前目录是 `05_software/real/sim2real/`
```bash
python -m pip install -r requirements-orin.txt
python tools/alignment_check.py --policy policies/model_rough.pt --manifest deployment_manifest.yaml
python tools/standalone_check.py
python main.py --dry-run
python main.py
python web/server.py --host 0.0.0.0 --port 8080
```
@@ -1,50 +0,0 @@
# `FACTS_AND_ASSUMPTIONS`
> 版本范围:第一代 Python Sim2Real`v0.3.0`)。事实项只适用于该快照。
## 已确认
- 当前部署模型:`policies/model_rough.pt`
- 原始说明记录的源模型名:`model_2000.pt`;同名文件未随本目录归档
- actor 输入:`53D`
- actor 输出:`16D`
- 当前 actor 不吃 `base_lin_vel`
- 当前 actor 不吃 `height_scan`
## 当前观测顺序
1. `base_ang_vel * 0.25`
2. `projected_gravity`
3. `command`
4. `joint_pos_rel`12
5. `joint_vel_rel * 0.05`12
6. `wheel_vel * 0.05`4
7. `last_actions`16
## 当前控制定义
- 控制频率:`50Hz`
- 腿缩放:`0.125 / 0.25`
- 轮缩放:`5.0`
- 腿 LPF`5Hz`
- 轮 LPF`15Hz`
## 当前仍依赖现场一致的部分
- IMU 安装方向与上一版校正一致
- 当前 MJCF / 电机参数对应这次重新训练后的模型
- 电机零位、方向、接线已按当前硬件修正
## 本次实现边界
不再支持:
- `crawl` 模型
- 多策略切换
- `318D` 历史输入
- 旧版 `startup.start_pose`
## 本次排查结论
代码应只围绕当前 rough 模型运行。
如果后续模型结构再改,必须重新核对观测、动作缩放、控制频率和部署文档。
@@ -1,47 +0,0 @@
# `Orin Nano` 部署说明
> 版本范围:第一代 Python Sim2Real`v0.3.0`)。本文不是最终 ROS 2 v3 部署指南。
## 是否必须转 ONNX
不必须。
当前优先级仍然是:
1. 先保证观测、动作、站立控制对齐
2. 再测 `50Hz` 实际环路稳定性
3. 最后才决定是否转 `ONNX/TensorRT`
## 当前代码重点
- `stand_balance` 已加入 `main.py``web/session.py`
- 启动后先站稳,再允许策略接管
- `PolicyRunner.step()` 仍保留零命令抑制开关,默认开启
## Orin 上先测什么
- 机器人能否在不启动策略时,仅靠 `startup + stand_balance` 稳定站住
- `loop_dt_ms`
- `imu_age_ms`
- 电机 stale
- policy forward 耗时
## 纯 Python 部署命令
默认前提:当前目录是 `05_software/real/sim2real/`
```bash
python3 -m pip install -r requirements-orin.txt
python3 tools/alignment_check.py --policy policies/model_rough.pt --manifest deployment_manifest.yaml
python3 tools/standalone_check.py
python3 main.py --dry-run
python3 main.py
python3 web/server.py --host 0.0.0.0 --port 8080
```
## 首轮实机建议
1. 先不启动策略
2. 只验证 `startup -> stand_balance`
3. 站稳后再启动策略
4. 只给很小的 `vx / vy / yaw`
-78
View File
@@ -1,78 +0,0 @@
# `sim2real`
本目录是第一代 Python Sim2Real 快照,对应 `v0.3.0`。下文“当前”均指该历史快照,不指仓库 `main` 的最终 ROS 2 v3。
该快照只部署当时的 `53D -> 16D` Rough 模型,不兼容更早的 Crawl、多策略和其他历史观测契约。
## 当前部署模型
- 使用文件:`policies/model_rough.pt`
- 原始说明记录的来源名:`model_2000.pt`;该同名源文件未随本目录归档,仓库只保留重命名后的 `policies/model_rough.pt`
## 当前 actor 输入
- 单帧 `53D`
- 顺序:
- `base_ang_vel * 0.25`
- `projected_gravity`
- `command`
- `joint_pos_rel`12
- `joint_vel_rel * 0.05`12
- `wheel_vel * 0.05`4
- `last_actions`16
不包含:
- `base_lin_vel`
- `height_scan`
## 当前控制参数
- 控制频率:`50Hz`
- 站立保持:`startup/stand_balance.py`
- `hip_abduction` 缩放:`0.125`
- 其他腿关节缩放:`0.25`
- 轮速缩放:`5.0`
- 腿 LPF`5Hz`
- 轮 LPF`15Hz`
- 零命令抑制:默认开启,可通过 `config.yaml > policy.enable_zero_cmd_suppression` 关闭
- 零命令保持:默认开启,可通过 `config.yaml > policy.hold_zero_command_pose` 控制
- 首次命令解锁:默认开启,可通过 `config.yaml > policy.require_active_command_to_release` 控制
## 当前站立逻辑
- `startup`:从实测姿态过渡到默认站姿
- `stand_balance`:根据 `IMU roll/pitch + gyro` 动态修正四条腿目标
- `runtime`:只有站立稳定后才进入策略控制
这次改动的重点是:策略不再承担“先把身体撑住”的职责。
另外当前部署逻辑改成:
- 零命令时默认不让策略直接接管腿和轮,保持站立目标
- 命令从零变为非零时,策略输出在 `command_release_s` 内平滑放开
## 启动命令
默认前提:当前目录是 `05_software/real/sim2real/`
`python`
```bash
python -m pip install -r requirements-orin.txt
python tools/alignment_check.py --policy policies/model_rough.pt --manifest deployment_manifest.yaml
python tools/standalone_check.py
python main.py --dry-run
python main.py
python web/server.py --host 0.0.0.0 --port 8080
```
Windows 本机可使用目标虚拟环境中的 Python;不要依赖个人机器的绝对安装路径:
```bash
python -m pip install -r requirements-orin.txt
python tools\alignment_check.py --policy policies\model_rough.pt --manifest deployment_manifest.yaml
python tools\standalone_check.py
python main.py
python web\server.py --host 0.0.0.0 --port 8080
```
-74
View File
@@ -1,74 +0,0 @@
can1_port: "/dev/can1"
can2_port: "/dev/can2"
motor_model: "rs-02"
debug: false
control_freq: 50
imu_lib_path: null
controller:
kp_leg: 80.0
kd_leg: 2.5
kd_wheel: 2.0
max_vx: 0.8
max_vy: 0.3
max_yaw_rate: 0.5
policy:
enable_zero_cmd_suppression: true
hold_zero_command_pose: true
command_release_s: 0.35
require_active_command_to_release: true
zero_cmd_use_yaw_rate: false
action_scale: [0.125, 0.25, 0.25, 0.125, 0.25, 0.25, 0.125, 0.25, 0.25, 0.125, 0.25, 0.25, 5.0, 5.0, 5.0, 5.0]
release_command_hold_s: 0.12
release_posture_max_err: 0.35
release_target_blend_s: 0.30
stand_balance:
enabled: true
height: 0.33
kp_roll: 0.85
kp_pitch: 0.70
kd_roll_rate: 0.03
kd_pitch_rate: 0.025
lateral_lean_gain: 0.0
hip_abduction_clip: 0.45
hip_pitch_clip: [-1.0, 2.5]
knee_clip: [-2.6, -0.3]
stable_roll_deg: 6.0
stable_pitch_deg: 8.0
stable_gyro_deg_s: 45.0
enter_hold_s: 1.0
profile_h: [0.157, 0.248, 0.311, 0.366, 0.411, 0.448]
profile_hip: [1.5, 1.2, 1.0, 0.8, 0.6, 0.4]
profile_knee: [-2.5, -2.1, -1.8, -1.5, -1.2, -0.9]
startup:
enabled: true
wait_for_enter_before_rise: false
soft_hold_duration: 1.0
ramp_kp_time: 1.0
transition_time_min: 2.0
transition_time_max: 6.0
transition_seconds_per_rad: 1.5
timeout_extra: 3.0
hold_time: 1.0
settle_pos_threshold: 0.30
settle_vel_threshold: 0.6
progress_log_interval: 0.5
max_dev_warn: 1.5
max_dev_abort: 3.0
require_user_confirm: true
safety:
enabled: true
max_target_offset: 0.6
max_ang_vel: 10.0
max_tilt_z: -0.3
clip_to_brake: 3
imu_age_warn_ms: 60.0
imu_age_stop_ms: 200.0
log_dir: "logs"
log_every: 1
@@ -1,71 +0,0 @@
model:
path: "/home/rc2/work/rcwork/real/rc_mjlab/rc_mjlab/model_rough.pt"
obs_dim: 53
action_dim: 16
enable_zero_cmd_suppression: true
observation:
terms:
- name: base_ang_vel
dim: 3
scale: 0.25
- name: projected_gravity
dim: 3
- name: command
dim: 3
- name: joint_pos_rel
dim: 12
- name: joint_vel_rel
dim: 12
scale: 0.05
- name: wheel_vel
dim: 4
scale: 0.05
- name: last_actions
dim: 16
action:
scale:
- 0.125
- 0.25
- 0.25
- 0.125
- 0.25
- 0.25
- 0.125
- 0.25
- 0.25
- 0.125
- 0.25
- 0.25
- 5.0
- 5.0
- 5.0
- 5.0
default_dof_pos:
- 0.0
- 0.9
- -1.8
- 0.0
- 0.9
- -1.8
- 0.0
- 0.9
- -1.8
- 0.0
- 0.9
- -1.8
- 0.0
- 0.0
- 0.0
- 0.0
control:
control_freq_hz: 50
leg_lpf_hz: 5
wheel_lpf_hz: 15
safety:
zero_cmd_lin_thresh: 0.05
zero_cmd_yaw_thresh: 0.05
zero_yaw_rate_thresh: 0.10
@@ -1,89 +0,0 @@
"""键盘控制器 — 兼容 sim2sim/input_dev/keyboard.py 的接口与平滑参数。"""
import numpy as np
try:
from pynput import keyboard
PYNPUT_AVAILABLE = True
except ImportError:
PYNPUT_AVAILABLE = False
keyboard = None # type: ignore
class KeyboardCommandController:
"""方向键 + AD 键的键盘指令源。
指令: [vx, vy, yaw_rate],平滑加减速;空格触发急停标志。
"""
def __init__(self,
max_x_vel: float = 0.8,
max_y_vel: float = 0.3,
max_yaw_vel: float = 0.5,
acc_step: float = 0.05,
dec_step: float = 0.1):
if not PYNPUT_AVAILABLE:
raise RuntimeError("pynput 不可用,无法使用键盘控制;改用其他输入源。")
self.current_cmd = np.zeros(3, dtype=np.float32)
self.max_x_vel = max_x_vel
self.max_y_vel = max_y_vel
self.max_yaw_vel = max_yaw_vel
self.acc_step = acc_step
self.dec_step = dec_step
self._pressed = set()
self._estop = False
self.listener = keyboard.Listener(
on_press=self._on_press, on_release=self._on_release
)
def start(self):
self.listener.start()
print("[Keyboard] 启动。↑↓ 前后, ←→ 转向, A/D 横移, SPACE 急停")
def stop(self):
try:
self.listener.stop()
except Exception:
pass
def _on_press(self, key):
self._pressed.add(key)
if key == keyboard.Key.space:
self._estop = True
def _on_release(self, key):
self._pressed.discard(key)
def is_estop_triggered(self) -> bool:
return self._estop
def reset_estop(self):
self._estop = False
def get_command(self) -> np.ndarray:
target = np.zeros(3, dtype=np.float32)
if keyboard.Key.up in self._pressed:
target[0] += self.max_x_vel
if keyboard.Key.down in self._pressed:
target[0] -= self.max_x_vel
if keyboard.Key.left in self._pressed:
target[2] += self.max_yaw_vel
if keyboard.Key.right in self._pressed:
target[2] -= self.max_yaw_vel
try:
if keyboard.KeyCode.from_char('a') in self._pressed:
target[1] += self.max_y_vel
if keyboard.KeyCode.from_char('d') in self._pressed:
target[1] -= self.max_y_vel
except Exception:
pass
for i, max_v in enumerate((self.max_x_vel, self.max_y_vel, self.max_yaw_vel)):
step = self.acc_step if target[i] != 0 else self.dec_step
if i == 2:
step *= 2.0
if self.current_cmd[i] < target[i]:
self.current_cmd[i] = min(self.current_cmd[i] + step, target[i])
else:
self.current_cmd[i] = max(self.current_cmd[i] - step, target[i])
return self.current_cmd.copy()
@@ -1,136 +0,0 @@
"""Odin1 IMU 客户端封装。
核心改动相对 sim_rl/odin1/python/odin1_imu.py
- 自动加载默认 .so 路径,调用方只需要 IMUClient(lib_path=...)
- 启动后做一次"重力对齐" — 用静止时的加速度计读数初始化 Mahony 滤波器,
把首步姿态偏差从可能的 5°+ 降到 0.3° 内。这是方法论 D4 的关键一步。
- 数据老化检测:若 imu_age_ms > stale_threshold 则报警(不阻塞)。
"""
import sys
import time
from pathlib import Path
from typing import Optional
import numpy as np
class IMUClient:
"""Odin1 IMU 包装。
Args:
lib_path: libodin1_imu_bridge.so 的绝对路径;None 则按方法论 1.2 中
约定的相对位置寻找。
gravity_align_samples: 启动时取多少帧加速度计平均值用于姿态初始化
stale_threshold_ms: 单帧数据超过该 age 视为陈旧
"""
def __init__(self, lib_path: Optional[str] = None, gravity_align_samples: int = 50,
stale_threshold_ms: float = 50.0):
# 优先级 1: vendored/odin1_imu(独立部署模式)
# 优先级 2: ../../odin1/odin1/python(开发模式,即 sim_rl/odin1/odin1/python
sim2real_root = Path(__file__).resolve().parents[1]
candidates = [
sim2real_root / "vendored" / "odin1_imu",
sim2real_root.parents[1] / "odin1" / "odin1" / "python",
]
for cand in candidates:
if cand.exists() and str(cand) not in sys.path:
sys.path.insert(0, str(cand))
break
try:
from odin1_imu import Odin1ImuClient # type: ignore
except ImportError as e:
raise ImportError(
f"无法导入 Odin1ImuClient,已尝试的路径: {[str(c) for c in candidates]}: {e}"
)
# lib_path 默认查找:vendored/odin1_imu/build/libodin1_imu_bridge.so → 开发路径
if lib_path is None:
so_candidates = [
sim2real_root / "vendored" / "odin1_imu" / "build" / "libodin1_imu_bridge.so",
sim2real_root / "vendored" / "odin1_imu" / "libodin1_imu_bridge.so",
sim2real_root.parents[1] / "odin1" / "odin1" / "build" / "libodin1_imu_bridge.so",
]
for so in so_candidates:
if so.exists():
lib_path = str(so)
break
self._client = Odin1ImuClient(lib_path=lib_path)
self._gravity_align_samples = gravity_align_samples
self._stale_threshold_ms = stale_threshold_ms
self._initial_gravity: Optional[np.ndarray] = None
# 用本机时钟追踪数据新鲜度(stamp_ns 是设备单调时钟,不能和 time.time 混算)
self._last_seq: int = -1
self._last_fresh_time: float = 0.0
def version(self) -> str:
return self._client.version()
def start(self, timeout_ms: int = 8000):
"""启动 IMU 流,并采集若干帧用于重力对齐。"""
self._client.start(timeout_ms=timeout_ms)
self._wait_for_stream()
self._initial_gravity = self._collect_gravity_samples()
self._last_fresh_time = time.time()
def stop(self):
try:
self._client.stop()
except Exception:
pass
@property
def initial_gravity(self) -> Optional[np.ndarray]:
"""启动后的初始重力向量(机身坐标系),用于初始化 Mahony 四元数。"""
return self._initial_gravity
def get_latest(self):
"""返回 (gyro[3], accel[3], age_ms, fresh)fresh=False 表示无新数据。"""
sample = self._client.get_latest()
if sample is None:
return (np.zeros(3, dtype=np.float32),
np.array([0.0, 0.0, 9.81], dtype=np.float32),
-1.0, False)
gyro = np.array([sample.gyro_x, sample.gyro_y, sample.gyro_z], dtype=np.float32)
accel = np.array([sample.accel_x, sample.accel_y, sample.accel_z], dtype=np.float32)
# 用 stamp_ns 判断是否有新数据,因为 sequence 字段在 C++ 中可能没有赋值,导致永远为 0
stamp = getattr(sample, "stamp_ns", 0)
now = time.time()
if stamp != self._last_seq:
self._last_seq = stamp
self._last_fresh_time = now
fresh = True
else:
fresh = False
age_ms = (now - self._last_fresh_time) * 1000.0
return gyro, accel, age_ms, fresh
# ---- 内部方法 ----
def _wait_for_stream(self, timeout: float = 3.0):
deadline = time.time() + timeout
while time.time() < deadline:
if self._client.wait_for_data(timeout_ms=200):
# 有数据进来后清空一次队列以保证后续 get_latest 拿到的都是最新
while self._client.pop_sample() is not None:
pass
return
raise RuntimeError("IMU 启动超时,未收到任何样本")
def _collect_gravity_samples(self) -> np.ndarray:
accels = []
for _ in range(self._gravity_align_samples):
sample = self._client.pop_sample()
if sample is None:
if not self._client.wait_for_data(timeout_ms=100):
continue
sample = self._client.pop_sample()
if sample is None:
continue
accels.append([sample.accel_x, sample.accel_y, sample.accel_z])
if not accels:
print("[IMU] 警告: 重力对齐期间未收到样本,使用默认重力 [0,0,-9.81]")
return np.array([0.0, 0.0, -9.81], dtype=np.float32)
gravity = np.mean(accels, axis=0).astype(np.float32)
print(f"[IMU] 重力对齐完成: g_body = {gravity}")
return gravity
@@ -1,310 +0,0 @@
"""RobStride 电机驱动包装。
职责:
- 封装 ik_real 中 RobStrideDriver 的 enable/disable/clear/control_mit 调用
- **真实的丢包检测**:旧版用「value=0 启发式」会误判(电机回机械零位时也是 0)。
新方案:
1. 调用 process_messages 前快照所有电机的 (pos, vel, torque)
2. 调用后比较:状态变了 → 这一帧有新反馈;状态完全没变 → 累计 stale_count
3. stale_count 超过阈值才沿用上一帧(方法论 3.4.2)
仍然不完美(电机长时间静止确实会有连续多帧 state 不变),但比 0 启发式可靠。
- 通过 driver_factory 由调用方注入:远程 Linux 主机用 RobStrideDriver
本地 Windows 调试可用 Mock。
"""
from dataclasses import dataclass
import threading
from typing import Callable, Dict, List, Optional, Tuple
import numpy as np
from interface.motor_mapping import MotorMapping
@dataclass
class MotorReading:
position: float
velocity: float
torque: float = 0.0
fresh: bool = False # True 表示本帧驱动板有新反馈
class HardwareIO:
"""统一的电机+IMU总线接口(不含策略),主控调用这一层。
Args:
driver_factory: () -> (drv1, drv2),由调用方注入;返回的对象需要满足:
connect()/disconnect()/disable(name)/enable(name)/clear_warnings(name)
add_motor(name, mid, model)/process_messages()
control_mit(name, q, dq, kp, kd, tau)
.motors: dict[name -> motor], motor.state.position / .velocity / .torque
config: yaml 解析后的字典
"""
def __init__(self, driver_factory: Callable[[str, str, bool], Tuple[object, object]],
motor_model: str, can1_port: str, can2_port: str, debug: bool = False,
stale_frames_to_holdover: int = 2):
self.mapper = MotorMapping()
drv1, drv2 = driver_factory(can1_port, can2_port, debug)
self.driver_can1 = drv1
self.driver_can2 = drv2
self.motor_model = motor_model
self.stale_frames_to_holdover = stale_frames_to_holdover
# 上一帧反馈(按 (bus, can_id) 索引),用于丢包兜底
self._last_pos: Dict[Tuple[int, int], float] = {}
self._last_vel: Dict[Tuple[int, int], float] = {}
self._last_torque: Dict[Tuple[int, int], float] = {}
# 每个电机连续多少帧没收到新反馈
self._stale_counts: Dict[Tuple[int, int], int] = {}
# 第一次必须读到才能解锁,避免初始化时直接用零位发送大力矩
self._initialized = False
self.lock = threading.Lock()
# 累计诊断
self.holdover_total = 0 # 累计被沿用上一帧的次数
# ---- 总线管理 ----
def connect(self):
self.driver_can1.connect()
self.driver_can2.connect()
for jk in self.mapper.SIM_JOINT_ORDER:
leg, joint = jk
bus, mid = self.mapper.CAN_ID_MAP[jk]
name = f"{leg}_{joint}"
drv = self.driver_can1 if bus == 1 else self.driver_can2
drv.add_motor(name, mid, self.motor_model)
self._stale_counts[(bus, mid)] = 0
def disconnect(self):
try:
self.driver_can1.disconnect()
finally:
self.driver_can2.disconnect()
def enable_all(self):
for drv in (self.driver_can1, self.driver_can2):
for name in drv.motors:
drv.clear_warnings(name)
drv.enable(name)
def disable_all(self):
for drv in (self.driver_can1, self.driver_can2):
for name in drv.motors:
drv.disable(name)
# ---- 状态读取 ----
def _snapshot_state(self) -> Dict[Tuple[int, int], Tuple[float, float, float, int]]:
"""快照所有电机的 (pos, vel, torque, update_count)process_messages 前后比较即可判 fresh。"""
snap: Dict[Tuple[int, int], Tuple[float, float, float, int]] = {}
for drv_idx, drv in enumerate((self.driver_can1, self.driver_can2)):
bus = drv_idx + 1
for name, motor in drv.motors.items():
parts = name.split("_", 1)
if len(parts) != 2:
continue
key = (parts[0], parts[1])
if key not in self.mapper.CAN_ID_MAP:
continue
_, mid = self.mapper.CAN_ID_MAP[key]
s = motor.state
snap[(bus, mid)] = (s.position, s.velocity, s.torque, getattr(s, "update_count", 0))
return snap
def read_state(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Dict[str, object]]:
"""返回 (sim_joint_pos[16], sim_joint_vel[16], sim_joint_torque[16], debug_info)。"""
with self.lock:
# 1) 抓取上一次的状态作为「pre」快照(基线)
pre = self._snapshot_state()
# 2) 拉取本帧反馈
self.driver_can1.process_messages()
self.driver_can2.process_messages()
# 3) 抓取「post」快照
post = self._snapshot_state()
# 4) 比较:state 元组变了 → 本帧有新反馈,stale_count 清零;否则 stale_count++
per_motor_fresh: Dict[Tuple[int, int], bool] = {}
for key in post:
fresh = (pre.get(key) != post[key])
per_motor_fresh[key] = fresh
if fresh:
self._stale_counts[key] = 0
else:
self._stale_counts[key] += 1
# 5) 取出本帧 pos/vel;若该电机连续多帧没刷新,沿用上一帧(方法论 3.4.2)
real_pos: Dict[Tuple[int, int], float] = {}
real_vel: Dict[Tuple[int, int], float] = {}
real_torque: Dict[Tuple[int, int], float] = {}
holdover_this_frame = 0
for key, (pos, vel, tor, _) in post.items():
if (not per_motor_fresh[key]) and self._stale_counts[key] >= self.stale_frames_to_holdover:
# 长时间不刷新视作丢包:沿用上一帧
if key in self._last_pos:
real_pos[key] = self._last_pos[key]
real_vel[key] = self._last_vel[key]
real_torque[key] = self._last_torque[key]
holdover_this_frame += 1
else:
real_pos[key] = pos
real_vel[key] = vel
real_torque[key] = tor
else:
real_pos[key] = pos
real_vel[key] = vel
real_torque[key] = tor
self.holdover_total += holdover_this_frame
# 缓存本帧(即便部分是 holdover 也缓存)
self._last_pos = real_pos.copy()
self._last_vel = real_vel.copy()
self._last_torque = real_torque.copy()
if not self._initialized:
self._initialized = True
cur_pos = self.mapper.real_to_sim(real_pos)
cur_vel = self.mapper.real_vel_to_sim(real_vel)
cur_torque = self.mapper.real_vel_to_sim(real_torque)
# 诊断信息
stale_max = max(self._stale_counts.values()) if self._stale_counts else 0
n_stale_motors = sum(1 for c in self._stale_counts.values()
if c >= self.stale_frames_to_holdover)
# 按 SIM_JOINT_ORDER 排列的每个电机连续丢帧数
per_motor_stale = [
self._stale_counts.get(self.mapper.CAN_ID_MAP[jk], 99)
for jk in self.mapper.SIM_JOINT_ORDER
]
return cur_pos, cur_vel, cur_torque, {
"holdover_this_frame": holdover_this_frame,
"stale_max": stale_max,
"n_stale_motors": n_stale_motors,
"fresh_count": sum(1 for v in per_motor_fresh.values() if v),
"per_motor_stale": per_motor_stale,
}
def passive_poll(self):
"""发送全 0 (0刚度0阻尼0力矩) 的 MIT 指令给所有电机。
目的:在 ENABLED 状态下,不产生力矩地索要反馈(因为 RobStride 在 MIT 模式下必须有指令才反馈)。"""
with self.lock:
for jk in self.mapper.SIM_JOINT_ORDER:
bus, mid = self.mapper.CAN_ID_MAP[jk]
name = f"{jk[0]}_{jk[1]}"
drv = self.driver_can1 if bus == 1 else self.driver_can2
if name in drv.motors:
drv.control_mit(name, 0.0, 0.0, 0.0, 0.0, 0.0)
# ---- 控制下发 ----
def send_control(self, target_angles: np.ndarray, kp_leg: float, kd_leg: float,
kd_wheel: float):
"""与 sim2sim 的 PD 模型对齐:
- 腿: position 控制,目标角度由 target_angles[:12] 给出,kp/kd 来自配置
- 轮: velocity 控制,目标速度由 target_angles[12:] 给出,kd 阻尼
"""
with self.lock:
if target_angles.shape != (16,):
raise ValueError("target_angles must be (16,)")
real_targets = self.mapper.sim_to_real(target_angles.astype(np.float32))
# 轮毂速度目标暂且用 0,如果 target_angles 里包含了速度,就在 policy 那里处理,
# 这里的 target_angles 是 pose 目标,轮毂作为连续旋转关节其实位置控制没有意义。
# 为了兼容旧代码,这里构造一个 16 维的 velocity array,只有后 4 个是目标(如果当作速度的话)。
vel_targets = np.zeros(16, dtype=np.float32)
vel_targets[12:] = target_angles[12:].astype(np.float32)
real_wheel = self.mapper.sim_vel_to_real(vel_targets)
for jk in self.mapper.SIM_JOINT_ORDER:
leg, joint = jk
bus, mid = self.mapper.CAN_ID_MAP[jk]
name = f"{leg}_{joint}"
drv = self.driver_can1 if bus == 1 else self.driver_can2
if name not in drv.motors:
continue
if joint == "wheel":
v = real_wheel[(bus, mid)]
drv.control_mit(name, 0.0, v, 0.0, kd_wheel, 0.0)
else:
q = real_targets[(bus, mid)]
drv.control_mit(name, q, 0.0, kp_leg, kd_leg, 0.0)
def damping_brake(self, kd_leg: float, kd_wheel: float):
"""急停模式:所有关节卸载刚度,仅保留阻尼。
对应 270_SimToReal 方法论 97.11 Level 2 "刹车"
"""
with self.lock:
for jk in self.mapper.SIM_JOINT_ORDER:
leg, joint = jk
bus, _ = self.mapper.CAN_ID_MAP[jk]
name = f"{leg}_{joint}"
drv = self.driver_can1 if bus == 1 else self.driver_can2
if name not in drv.motors:
continue
kd = kd_wheel if joint == "wheel" else kd_leg
drv.control_mit(name, 0.0, 0.0, 0.0, kd, 0.0)
def wait_feedback_ready(self, max_attempts: int = 20,
poll_interval: float = 0.05) -> Tuple[bool, list]:
"""enable 后调用:尝试 max_attempts 次读总线,等所有 16 个电机
都至少给出一帧反馈。
返回 (all_ready, missing_motors)missing_motors 是 (bus, mid, name) 列表。
"""
import time
seen: Dict[Tuple[int, int], bool] = {
self.mapper.CAN_ID_MAP[jk]: False for jk in self.mapper.SIM_JOINT_ORDER
}
# 用第一次读到的 (pos, vel, torque) 三元组的"非零"或"已变化"作为反馈到达的判据。
# 启动瞬间所有 motor.state 默认全 0,要么收到反馈让其变化,要么收到反馈但值确实是 0。
# 退化情况下电机静止时 vel=0 且 pos=机械零位也=0,那种情况只能等多帧确认。
snap_prev = self._snapshot_state()
for attempt in range(max_attempts):
with self.lock:
self.driver_can1.process_messages()
self.driver_can2.process_messages()
snap_cur = self._snapshot_state()
for key, fields_cur in snap_cur.items():
if seen[key]:
continue
fields_prev = snap_prev.get(key)
# 任一字段不为 0 → 一定有反馈(因为初始值都是 0)
if any(v != 0.0 for v in fields_cur):
seen[key] = True
# 与上一次快照不同 → 一定有反馈(即便都很小)
elif fields_prev is not None and fields_cur != fields_prev:
seen[key] = True
snap_prev = snap_cur
if all(seen.values()):
return True, []
time.sleep(poll_interval)
# 超时:列出仍未反馈的电机
missing = []
rev_can = {v: k for k, v in self.mapper.CAN_ID_MAP.items()}
for key, ok in seen.items():
if not ok:
leg, joint = rev_can[key]
missing.append((key[0], key[1], f"{leg}_{joint}"))
return False, missing
def read_measured_pose(self) -> np.ndarray:
"""返回 (16,) 当前实测 sim 坐标系下的关节位置。
会先 process_messages 一次保证拿到本帧。
"""
self.driver_can1.process_messages()
self.driver_can2.process_messages()
real_pos: Dict[Tuple[int, int], float] = {}
for drv_idx, drv in enumerate((self.driver_can1, self.driver_can2)):
bus = drv_idx + 1
for name, motor in drv.motors.items():
parts = name.split("_", 1)
if len(parts) != 2:
continue
key = (parts[0], parts[1])
if key not in self.mapper.CAN_ID_MAP:
continue
_, mid = self.mapper.CAN_ID_MAP[key]
real_pos[(bus, mid)] = motor.state.position
return self.mapper.real_to_sim(real_pos)
@@ -1,99 +0,0 @@
"""仿真→实机电机映射。
数据来源:sim_rl/ik_real/sim_to_real_deploy_beifen.py 和
sim_rl/sim2real/motor_mapping.py 中的 sign / offset / can_id 表(已在实机上验证)。
关节顺序与 rc_mjlab/sim2sim 完全一致:[12 个腿关节] + [4 个轮子]。
"""
from typing import Dict, Tuple
import numpy as np
class MotorMapping:
LEG_NAMES = ("fl", "fr", "rl", "rr")
JOINT_NAMES = ("hip_abduction", "hip_pitch", "knee", "wheel")
SIM_JOINT_ORDER = (
("fl", "hip_abduction"), ("fl", "hip_pitch"), ("fl", "knee"),
("fr", "hip_abduction"), ("fr", "hip_pitch"), ("fr", "knee"),
("rl", "hip_abduction"), ("rl", "hip_pitch"), ("rl", "knee"),
("rr", "hip_abduction"), ("rr", "hip_pitch"), ("rr", "knee"),
("fl", "wheel"), ("fr", "wheel"), ("rl", "wheel"), ("rr", "wheel"),
)
SIM_INDEX_MAP = {jk: i for i, jk in enumerate(SIM_JOINT_ORDER)}
CAN_ID_MAP: Dict[Tuple[str, str], Tuple[int, int]] = {
("fl", "hip_abduction"): (1, 1), ("fl", "hip_pitch"): (1, 2),
("fl", "knee"): (1, 3), ("fl", "wheel"): (1, 4),
("fr", "hip_abduction"): (1, 5), ("fr", "hip_pitch"): (1, 6),
("fr", "knee"): (1, 7), ("fr", "wheel"): (1, 8),
("rl", "hip_abduction"): (2, 1), ("rl", "hip_pitch"): (2, 2),
("rl", "knee"): (2, 3), ("rl", "wheel"): (2, 4),
("rr", "hip_abduction"): (2, 5), ("rr", "hip_pitch"): (2, 6),
("rr", "knee"): (2, 7), ("rr", "wheel"): (2, 8),
}
DIRECTION_MAP: Dict[Tuple[str, str], int] = {
("fl", "hip_abduction"): -1, ("fl", "hip_pitch"): -1,
("fl", "knee"): -1, ("fl", "wheel"): -1,
("fr", "hip_abduction"): -1, ("fr", "hip_pitch"): 1,
("fr", "knee"): 1, ("fr", "wheel"): 1,
("rl", "hip_abduction"): 1, ("rl", "hip_pitch"): -1,
("rl", "knee"): -1, ("rl", "wheel"): -1,
("rr", "hip_abduction"): 1, ("rr", "hip_pitch"): 1,
("rr", "knee"): 1, ("rr", "wheel"): 1,
}
ZERO_OFFSET_MAP: Dict[Tuple[str, str], float] = {
("fl", "hip_abduction"): 0.003, ("fl", "hip_pitch"): 0.030,
("fl", "knee"): 0.028, ("fl", "wheel"): 0.000,
("fr", "hip_abduction"): 0.004, ("fr", "hip_pitch"): 0.038,
("fr", "knee"): 0.011, ("fr", "wheel"): 0.000,
("rl", "hip_abduction"): 0.019, ("rl", "hip_pitch"): -0.034,
("rl", "knee"): 0.025, ("rl", "wheel"): 0.000,
("rr", "hip_abduction"): -0.001, ("rr", "hip_pitch"): 0.039,
("rr", "knee"): 0.018, ("rr", "wheel"): 0.000,
}
def __init__(self):
self.num_motors = len(self.SIM_JOINT_ORDER)
self._sign = np.array([self.DIRECTION_MAP[jk] for jk in self.SIM_JOINT_ORDER], dtype=np.float32)
self._offset = np.array([self.ZERO_OFFSET_MAP[jk] for jk in self.SIM_JOINT_ORDER], dtype=np.float32)
def sim_to_real(self, sim_angles: np.ndarray) -> Dict[Tuple[int, int], float]:
if len(sim_angles) != 16:
raise ValueError(f"expected 16 sim angles, got {len(sim_angles)}")
out: Dict[Tuple[int, int], float] = {}
for i, jk in enumerate(self.SIM_JOINT_ORDER):
real = float(self._sign[i] * sim_angles[i] + self._offset[i])
out[self.CAN_ID_MAP[jk]] = real
return out
def sim_vel_to_real(self, sim_vels: np.ndarray) -> Dict[Tuple[int, int], float]:
# 速度只受方向影响,不应用 offset。
out: Dict[Tuple[int, int], float] = {}
for i, jk in enumerate(self.SIM_JOINT_ORDER):
out[self.CAN_ID_MAP[jk]] = float(self._sign[i] * sim_vels[i])
return out
def real_to_sim(self, real_pos: Dict[Tuple[int, int], float]) -> np.ndarray:
out = np.zeros(16, dtype=np.float32)
for i, jk in enumerate(self.SIM_JOINT_ORDER):
v = real_pos.get(self.CAN_ID_MAP[jk])
if v is None:
continue
out[i] = (v - self._offset[i]) / self._sign[i]
return out
def real_vel_to_sim(self, real_vel: Dict[Tuple[int, int], float]) -> np.ndarray:
out = np.zeros(16, dtype=np.float32)
for i, jk in enumerate(self.SIM_JOINT_ORDER):
v = real_vel.get(self.CAN_ID_MAP[jk])
if v is None:
continue
out[i] = v / self._sign[i]
return out
def joint_name_at(self, idx: int) -> str:
leg, joint = self.SIM_JOINT_ORDER[idx]
return f"{leg}_{joint}_joint"
@@ -1,135 +0,0 @@
import time
from typing import Callable, Dict, Tuple
import numpy as np
from interface.imu_client import IMUClient
from interface.motor_driver import HardwareIO
from tools.math_utils import LowPassFilter, MahonyFilter, get_gravity_orientation
class RealIO:
def __init__(
self,
driver_factory: Callable[[str, str, bool], Tuple[object, object]],
motor_model: str,
can1_port: str,
can2_port: str,
imu_lib_path: str,
control_dt: float = 0.02,
kp_leg: float = 80.0,
kd_leg: float = 2.5,
kd_wheel: float = 2.0,
debug: bool = False,
):
self.control_dt = control_dt
self.kp_leg = kp_leg
self.kd_leg = kd_leg
self.kd_wheel = kd_wheel
print("[RealIO] 初始化电机驱动...")
self.hw = HardwareIO(driver_factory, motor_model, can1_port, can2_port, debug)
print("[RealIO] 初始化 IMU...")
self.imu = IMUClient(lib_path=imu_lib_path)
self.imu_filter = MahonyFilter(kp=2.0, ki=0.0, dt=control_dt)
self.quat_wxyz = np.array([1.0, 0.0, 0.0, 0.0], dtype=np.float32)
self.lpf_legs = LowPassFilter(cutoff_freq=5.0, dt=control_dt, dim=12)
self.lpf_wheels = LowPassFilter(cutoff_freq=15.0, dt=control_dt, dim=4)
self._last_imu_age_ms = -1.0
self._last_imu_fresh = False
def connect(self, imu_timeout_ms: int = 8000):
self.hw.connect()
self.imu.start(timeout_ms=imu_timeout_ms)
if self.imu.initial_gravity is not None:
self.imu_filter.reset_with_accel(self.imu.initial_gravity)
self.quat_wxyz = self.imu_filter.q.copy()
def disconnect(self):
try:
self.hw.disable_all()
finally:
self.imu.stop()
self.hw.disconnect()
def enable_motors(self):
self.hw.enable_all()
def disable_motors(self):
self.hw.disable_all()
def damping_brake(self):
self.hw.damping_brake(self.kd_leg, self.kd_wheel)
def wait_feedback_ready(self, max_attempts: int = 20, poll_interval: float = 0.05):
return self.hw.wait_feedback_ready(max_attempts=max_attempts, poll_interval=poll_interval)
def read_measured_pose(self) -> np.ndarray:
return self.hw.read_measured_pose()
def read_state(self) -> Dict[str, object]:
joint_pos, joint_vel, joint_torque, motor_diag = self.hw.read_state()
gyro, accel, age_ms, fresh = self.imu.get_latest()
self._last_imu_age_ms = age_ms
self._last_imu_fresh = fresh
self.quat_wxyz = self.imu_filter.update(accel, gyro)
projected_gravity = get_gravity_orientation(self.quat_wxyz)
return {
"joint_pos": joint_pos,
"joint_vel": joint_vel,
"joint_torque": joint_torque,
"imu_gyro": gyro,
"imu_accel": accel,
"quat_wxyz": self.quat_wxyz.copy(),
"projected_gravity": projected_gravity,
"imu_age_ms": age_ms,
"imu_fresh": fresh,
"motor_stale": motor_diag,
}
def get_obs_policy(
self,
state: Dict[str, object],
command: np.ndarray,
default_dof_pos: np.ndarray,
last_actions_raw: np.ndarray,
) -> np.ndarray:
gyro = state["imu_gyro"]
joint_pos = state["joint_pos"]
joint_vel = state["joint_vel"]
projected_gravity = state["projected_gravity"]
base_ang_vel = (gyro * 0.25).astype(np.float32)
joint_pos_rel = (joint_pos[:12] - default_dof_pos[:12]).astype(np.float32)
joint_vel_leg = (joint_vel[:12] * 0.05).astype(np.float32)
wheel_vel = (joint_vel[12:] * 0.05).astype(np.float32)
return np.concatenate(
[
base_ang_vel,
projected_gravity,
command.astype(np.float32),
joint_pos_rel,
joint_vel_leg,
wheel_vel,
last_actions_raw,
]
).astype(np.float32)
def send_actions(self, scaled_actions: np.ndarray, default_dof_pos: np.ndarray):
act = (scaled_actions + default_dof_pos).astype(np.float32)
act = np.clip(act, -100.0, 100.0)
act[:12] = self.lpf_legs.filter(act[:12])
act[12:] = self.lpf_wheels.filter(act[12:])
self.hw.send_control(act, self.kp_leg, self.kd_leg, self.kd_wheel)
return act
def hold_pose(self, sim_target_pose: np.ndarray, kp_scale: float = 1.0):
target = np.clip(sim_target_pose.astype(np.float32), -100.0, 100.0)
kp_scale = float(np.clip(kp_scale, 0.0, 1.0))
self.hw.send_control(target, self.kp_leg * kp_scale, self.kd_leg, self.kd_wheel)
return target
-725
View File
@@ -1,725 +0,0 @@
"""CLI entrypoint for current sim2real deployment."""
import argparse
import os
import sys
import threading
import time
from pathlib import Path
import numpy as np
import yaml
sys.path.insert(0, str(Path(__file__).resolve().parent))
from input_dev.keyboard import KeyboardCommandController
from interface.real_io import RealIO
from policy.policy_runner import PolicyRunner
from safety.runtime_guard import GuardLevel, RuntimeGuard
from safety.safety_monitor import SafetyLevel, SafetyMonitor
from startup.pose_initializer import PoseInitFailed, PoseInitializer, STAND_POSE
from startup.stand_balance import StandBalanceController
from tools.logger import LogBundle
from tools.math_utils import get_gravity_orientation
JOINT_LABELS = LogBundle.JOINT_LABELS
def make_real_driver_factory():
def factory(can1_port, can2_port, debug):
sim2real_root = Path(__file__).resolve().parent
for path in (
sim2real_root / "vendored",
"/home/rc2/work/rcwork/control",
"/home/rc2/work/rcwork",
):
path_str = str(path)
if path_str not in sys.path and Path(path).exists():
sys.path.append(path_str)
from drivers.motor_driver import RobStrideDriver # type: ignore
return RobStrideDriver(can1_port, debug), RobStrideDriver(can2_port, debug)
return factory
def make_dry_driver_factory():
class MockMotor:
def __init__(self):
class State:
position = 0.0
velocity = 0.0
torque = 0.0
self.state = State()
class MockDriver:
def __init__(self, port, debug):
self.port = port
self.motors = {}
def connect(self): ...
def disconnect(self): ...
def add_motor(self, name, motor_id, model): self.motors[name] = MockMotor()
def enable(self, name): ...
def disable(self, name): ...
def clear_warnings(self, name): ...
def process_messages(self): ...
def control_mit(self, *args, **kwargs): ...
def factory(can1_port, can2_port, debug):
return MockDriver(can1_port, debug), MockDriver(can2_port, debug)
return factory
def _sleep_to(next_exec: float) -> float:
slack = next_exec - time.perf_counter()
if slack > 0:
time.sleep(slack)
return next_exec + 0.0
return time.perf_counter()
def build_action_diag(
*,
joint_pos: np.ndarray,
default_pose: np.ndarray,
raw: np.ndarray,
scaled: np.ndarray,
tentative: np.ndarray,
cmd: np.ndarray,
zero_command: bool,
runtime_released: bool,
release_alpha: float,
safety_details: dict | None = None,
) -> dict:
details = dict(safety_details or {})
joint_indices = list(details.get("joint_indices", []))
pos_err = tentative - joint_pos
leg_offset = tentative[:12] - default_pose[:12]
diag = {
"joint_indices": joint_indices,
"joint_names": [JOINT_LABELS[i] for i in joint_indices if 0 <= i < len(JOINT_LABELS)],
"cmd": cmd.tolist(),
"zero_command": bool(zero_command),
"runtime_released": bool(runtime_released),
"release_alpha": float(release_alpha),
"max_raw": float(np.max(np.abs(raw))) if raw.size else 0.0,
"max_scaled": float(np.max(np.abs(scaled[:12]))) if scaled.size else 0.0,
"max_target": float(np.max(np.abs(tentative[:12]))) if tentative.size else 0.0,
}
if joint_indices:
primary = int(joint_indices[0])
diag.update(
{
"primary_joint_index": primary,
"primary_joint_name": JOINT_LABELS[primary],
"primary_target": float(tentative[primary]),
"primary_default": float(default_pose[primary]),
"primary_measured": float(joint_pos[primary]),
"primary_pos_err": float(pos_err[primary]),
"primary_raw": float(raw[primary]),
"primary_scaled": float(scaled[primary]),
}
)
if primary < 12:
diag["primary_leg_offset"] = float(leg_offset[primary])
details.update(diag)
return details
def policy_release_cfg(cfg: dict) -> dict[str, float]:
policy_cfg = cfg.get("policy", {})
return {
"command_hold_s": max(float(policy_cfg.get("release_command_hold_s", 0.12)), 0.0),
"posture_max_err": max(float(policy_cfg.get("release_posture_max_err", 0.35)), 0.0),
"target_blend_s": max(float(policy_cfg.get("release_target_blend_s", 0.30)), 1e-3),
}
def compute_release_metrics(runner: PolicyRunner, state: dict, hold_target: np.ndarray, cmd: np.ndarray) -> dict:
joint_pos = np.asarray(state["joint_pos"], dtype=np.float32)
default_pose = np.asarray(runner.default_dof_pos, dtype=np.float32)
hold_target = np.asarray(hold_target, dtype=np.float32)
planar_cmd, yaw_cmd = runner.command_activation_metrics(cmd)
return {
"planar_cmd": float(planar_cmd),
"yaw_cmd": float(yaw_cmd),
"max_hold_err": float(np.max(np.abs(joint_pos[:12] - hold_target[:12]))),
"max_default_err": float(np.max(np.abs(joint_pos[:12] - default_pose[:12]))),
"max_hold_default_gap": float(np.max(np.abs(hold_target[:12] - default_pose[:12]))),
}
def blend_runtime_target(
runner: PolicyRunner,
hold_target: np.ndarray,
policy_target: np.ndarray,
release_alpha: float,
target_blend_s: float,
control_dt: float,
) -> np.ndarray:
blend = min(1.0, release_alpha * (runner.command_release_s / max(target_blend_s, control_dt)))
return ((1.0 - blend) * hold_target + blend * policy_target).astype(np.float32)
def compute_target_error_metrics(
state: dict,
hold_target: np.ndarray,
policy_target: np.ndarray,
) -> dict[str, float]:
joint_pos = np.asarray(state["joint_pos"], dtype=np.float32)
hold_target = np.asarray(hold_target, dtype=np.float32)
policy_target = np.asarray(policy_target, dtype=np.float32)
return {
"hold_target_max_err": float(np.max(np.abs(joint_pos[:12] - hold_target[:12]))),
"policy_target_max_err": float(np.max(np.abs(joint_pos[:12] - policy_target[:12]))),
"hold_policy_max_gap": float(np.max(np.abs(hold_target[:12] - policy_target[:12]))),
}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--config", default=str(Path(__file__).parent / "config.yaml"))
parser.add_argument("--policy", default=None)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
with open(args.config, "r", encoding="utf-8") as file_obj:
cfg = yaml.safe_load(file_obj)
sim2real_root = Path(__file__).resolve().parent
policy_path = Path(args.policy) if args.policy else sim2real_root / "policies" / "model_rough.pt"
if not policy_path.exists():
print(f"[Main] policy not found: {policy_path}")
sys.exit(1)
control_dt = 1.0 / float(cfg["control_freq"])
driver_factory = make_dry_driver_factory() if args.dry_run else make_real_driver_factory()
logger = LogBundle(cfg["log_dir"])
logger.event(
"CONFIG_LOADED",
config_path=args.config,
policy=str(policy_path),
dry_run=args.dry_run,
control_freq=cfg["control_freq"],
motor_model=cfg["motor_model"],
)
io = RealIO(
driver_factory=driver_factory,
motor_model=cfg["motor_model"],
can1_port=cfg["can1_port"],
can2_port=cfg["can2_port"],
imu_lib_path=cfg.get("imu_lib_path"),
control_dt=control_dt,
kp_leg=cfg["controller"]["kp_leg"],
kd_leg=cfg["controller"]["kd_leg"],
kd_wheel=cfg["controller"]["kd_wheel"],
debug=cfg.get("debug", False),
)
runner = PolicyRunner(
policy_path,
enable_zero_cmd_suppression=cfg.get("policy", {}).get("enable_zero_cmd_suppression", True),
hold_zero_command_pose=cfg.get("policy", {}).get("hold_zero_command_pose", True),
command_release_s=cfg.get("policy", {}).get("command_release_s", 0.35),
action_scale=np.asarray(
cfg.get("policy", {}).get(
"action_scale",
[0.125, 0.25, 0.25, 0.125, 0.25, 0.25, 0.125, 0.25, 0.25, 0.125, 0.25, 0.25, 5.0, 5.0, 5.0, 5.0],
),
dtype=np.float32,
),
zero_cmd_use_yaw_rate=cfg.get("policy", {}).get("zero_cmd_use_yaw_rate", False),
)
require_active_command = cfg.get("policy", {}).get("require_active_command_to_release", True)
keyboard = KeyboardCommandController(
max_x_vel=cfg["controller"]["max_vx"],
max_y_vel=cfg["controller"]["max_vy"],
max_yaw_vel=cfg["controller"]["max_yaw_rate"],
)
safety = SafetyMonitor(
max_target_offset=cfg["safety"]["max_target_offset"],
max_ang_vel=cfg["safety"]["max_ang_vel"],
max_tilt_z=cfg["safety"]["max_tilt_z"],
clip_to_brake=cfg["safety"]["clip_to_brake"],
)
safety.reset()
guard = RuntimeGuard(
max_ang_vel=cfg["safety"]["max_ang_vel"],
max_tilt_z=cfg["safety"]["max_tilt_z"],
imu_age_warn_ms=cfg["safety"].get("imu_age_warn_ms", 60.0),
imu_age_stop_ms=cfg["safety"].get("imu_age_stop_ms", 200.0),
)
initializer = PoseInitializer(
io,
control_dt=control_dt,
transition_time_min=cfg["startup"].get("transition_time_min", 2.0),
transition_time_max=cfg["startup"].get("transition_time_max", 6.0),
transition_seconds_per_rad=cfg["startup"].get("transition_seconds_per_rad", 1.5),
hold_time=cfg["startup"]["hold_time"],
settle_pos_threshold=cfg["startup"]["settle_pos_threshold"],
settle_vel_threshold=cfg["startup"]["settle_vel_threshold"],
timeout_extra=cfg["startup"].get("timeout_extra", 3.0),
progress_log_interval=cfg["startup"]["progress_log_interval"],
ramp_kp_time=cfg["startup"].get("ramp_kp_time", 1.0),
soft_hold_duration=cfg["startup"].get("soft_hold_duration", 1.0),
max_dev_warn=cfg["startup"].get("max_dev_warn", 1.5),
max_dev_abort=cfg["startup"].get("max_dev_abort", 3.0),
)
initializer.attach(logger=logger, guard=guard, keyboard=keyboard)
stand_balance = StandBalanceController(cfg.get("stand_balance", {}), control_dt=control_dt)
print("\n[Main] connecting hardware...")
keyboard.start()
try:
io.connect()
logger.event("CAN_IMU_CONNECTED", initial_gravity=io.imu.initial_gravity)
except Exception as exc:
logger.event("HARDWARE_CONNECT_FAILED", error=str(exc))
keyboard.stop()
logger.close()
raise
try:
io.enable_motors()
logger.event("MOTORS_ENABLED")
time.sleep(0.5)
target_pose = initializer.transition_to_stand_from_current(target_pose=STAND_POSE) if cfg["startup"]["enabled"] else STAND_POSE.copy()
if stand_balance.enabled:
logger.event("STAND_BALANCE_BEGIN")
print("[Main] waiting for stand-balance to settle...")
stand_balance.reset()
next_exec = time.perf_counter()
while True:
state = io.read_state()
target_pose = stand_balance.compute_target(state, np.zeros(3, dtype=np.float32))
io.hold_pose(target_pose, kp_scale=1.0)
debug = stand_balance.last_debug
if stand_balance.is_stable():
logger.event(
"STAND_BALANCE_STABLE",
roll_deg=float(np.degrees(debug.roll)),
pitch_deg=float(np.degrees(debug.pitch)),
)
break
next_exec += control_dt
next_exec = _sleep_to(next_exec)
logger.event("STAND_BALANCE_END")
if cfg["startup"]["require_user_confirm"]:
print("[Main] standing complete. Press Enter to release policy control...")
done = threading.Event()
def _wait():
try:
input()
except EOFError:
pass
done.set()
threading.Thread(target=_wait, daemon=True).start()
if not initializer.hold_until_user_confirm(target_pose, done):
raise PoseInitFailed("WAIT_USER interrupted")
print("[Main] priming current observation...")
logger.event("PRIME_BEGIN")
zero_cmd = np.zeros(3, dtype=np.float32)
next_exec = time.perf_counter()
for index in range(1):
if stand_balance.enabled:
state = io.read_state()
target_pose = stand_balance.compute_target(state, zero_cmd)
io.hold_pose(target_pose, kp_scale=1.0)
else:
io.hold_pose(target_pose, kp_scale=1.0)
state = io.read_state()
obs = io.get_obs_policy(state, zero_cmd, runner.default_dof_pos, runner.last_actions)
if index == 0:
runner.reset(prime_obs=obs)
logger.state(
phase="PRIME",
joint_pos=state["joint_pos"],
joint_vel=state["joint_vel"],
joint_torque=state.get("joint_torque", np.zeros(16, dtype=np.float32)),
target_pose=target_pose,
raw_action=None,
gyro=state["imu_gyro"],
accel=state["imu_accel"],
quat=state["quat_wxyz"],
proj_gravity=state["projected_gravity"],
command=zero_cmd,
imu_age_ms=float(state["imu_age_ms"]),
loop_dt_ms=0.0,
kp_scale=1.0,
)
next_exec += control_dt
next_exec = _sleep_to(next_exec)
logger.event("PRIME_END")
print("[Main] entering 50Hz control loop... (space = estop)")
logger.event("RUNTIME_BEGIN")
next_exec = time.perf_counter()
loop_count = 0
last_print = next_exec
log_every = int(cfg.get("log_every", 1))
recent_dt_ms = []
runtime_released = not require_active_command
release_cfg = policy_release_cfg(cfg)
release_active_time = 0.0
while True:
loop_t0 = time.perf_counter()
cmd = keyboard.get_command()
state = io.read_state()
obs = io.get_obs_policy(state, cmd, runner.default_dof_pos, runner.last_actions)
zero_command = runner._is_zero_command(cmd, state["imu_gyro"])
obs_nan = bool(np.any(np.isnan(obs)) or np.any(np.isinf(obs)))
if obs_nan:
logger.event("OBS_NAN", obs_max=float(np.nanmax(obs)))
io.damping_brake()
break
if not runtime_released and zero_command:
raw = np.zeros(16, dtype=np.float32)
scaled = np.zeros(16, dtype=np.float32)
target_hold = stand_balance.compute_target(state, np.zeros(3, dtype=np.float32)) if stand_balance.enabled else runner.default_dof_pos.copy()
actual_target = io.hold_pose(target_hold, kp_scale=1.0)
policy_target = runner.default_dof_pos.copy()
release_metrics = compute_release_metrics(runner, state, target_hold, cmd)
target_metrics = compute_target_error_metrics(state, target_hold, policy_target)
release_active_time = 0.0
safety_decision = SafetyMonitor().check(
target_pose=target_hold,
default_pose=runner.default_dof_pos,
imu_gyro=state["imu_gyro"],
projected_gravity=state["projected_gravity"],
estop_triggered=keyboard.is_estop_triggered(),
)
guard_decision = guard.check(
imu_gyro=state["imu_gyro"],
projected_gravity=state["projected_gravity"],
imu_age_ms=float(state["imu_age_ms"]),
estop_triggered=keyboard.is_estop_triggered(),
extra_nan_arrays=(target_hold,),
)
else:
target_hold = stand_balance.compute_target(state, np.zeros(3, dtype=np.float32)) if stand_balance.enabled else runner.default_dof_pos.copy()
release_metrics = compute_release_metrics(runner, state, target_hold, cmd)
if not runtime_released:
release_active_time += control_dt if runner.is_command_active(cmd) else 0.0
active_ready = release_active_time >= release_cfg["command_hold_s"]
posture_ready = release_metrics["max_hold_err"] <= release_cfg["posture_max_err"]
if active_ready and posture_ready:
runtime_released = True
logger.event(
"RUNTIME_COMMAND_RELEASED",
cmd=cmd.tolist(),
active_hold_s=release_active_time,
max_hold_err=release_metrics["max_hold_err"],
max_default_err=release_metrics["max_default_err"],
max_hold_default_gap=release_metrics["max_hold_default_gap"],
)
else:
reasons = []
if not active_ready:
reasons.append(f"cmd_hold<{release_cfg['command_hold_s']:.2f}s")
if not posture_ready:
reasons.append(f"hold_err>{release_cfg['posture_max_err']:.3f}")
logger.event(
"RUNTIME_RELEASE_BLOCKED",
reason=",".join(reasons),
cmd=cmd.tolist(),
active_hold_s=release_active_time,
max_hold_err=release_metrics["max_hold_err"],
max_default_err=release_metrics["max_default_err"],
max_hold_default_gap=release_metrics["max_hold_default_gap"],
)
raw = np.zeros(16, dtype=np.float32)
scaled = np.zeros(16, dtype=np.float32)
actual_target = io.hold_pose(target_hold, kp_scale=1.0)
policy_target = runner.default_dof_pos.copy()
target_metrics = compute_target_error_metrics(state, target_hold, policy_target)
safety_decision = SafetyMonitor().check(
target_pose=target_hold,
default_pose=runner.default_dof_pos,
imu_gyro=state["imu_gyro"],
projected_gravity=state["projected_gravity"],
estop_triggered=keyboard.is_estop_triggered(),
)
guard_decision = guard.check(
imu_gyro=state["imu_gyro"],
projected_gravity=state["projected_gravity"],
imu_age_ms=float(state["imu_age_ms"]),
estop_triggered=keyboard.is_estop_triggered(),
extra_nan_arrays=(target_hold,),
)
loop_dt_ms = (time.perf_counter() - loop_t0) * 1000.0
if log_every and (loop_count % log_every == 0):
motor_diag = state.get("motor_stale", {})
logger.state(
phase="RUNTIME",
joint_pos=state["joint_pos"],
joint_vel=state["joint_vel"],
joint_torque=state.get("joint_torque", np.zeros(16, dtype=np.float32)),
target_pose=actual_target,
raw_action=raw,
gyro=state["imu_gyro"],
accel=state["imu_accel"],
quat=state["quat_wxyz"],
proj_gravity=state["projected_gravity"],
command=cmd,
imu_age_ms=float(state["imu_age_ms"]),
loop_dt_ms=loop_dt_ms,
safety_level=int(safety_decision.level),
guard_level=int(guard_decision.level),
holdover=int(motor_diag.get("holdover_this_frame", 0)),
stale_max=int(motor_diag.get("stale_max", 0)),
fresh_count=int(motor_diag.get("fresh_count", 16)),
kp_scale=1.0,
nan_flag=0,
kp_leg_cmd=float(io.kp_leg),
kd_leg_cmd=float(io.kd_leg),
kd_wheel_cmd=float(io.kd_wheel),
runtime_release_alpha=0.0,
runtime_release_hold_s=release_active_time,
runtime_blend_ratio=0.0,
hold_target_max_err=target_metrics["hold_target_max_err"],
policy_target_max_err=target_metrics["policy_target_max_err"],
hold_policy_max_gap=target_metrics["hold_policy_max_gap"],
target_source="runtime_hold",
clip_primary_joint="",
safety_reason=f"release_blocked:{','.join(reasons)}",
guard_reason=guard_decision.reason,
)
next_exec += control_dt
next_exec = _sleep_to(next_exec)
loop_count += 1
continue
scaled, raw = runner.step(obs)
act_nan = bool(np.any(np.isnan(raw)) or np.any(np.isinf(raw)))
if act_nan:
logger.event("ACTION_NAN")
io.damping_brake()
break
policy_target = (scaled + runner.default_dof_pos).astype(np.float32)
tentative = blend_runtime_target(
runner,
target_hold,
policy_target,
float(getattr(runner, "_command_release_alpha", 0.0)),
release_cfg["target_blend_s"],
control_dt,
)
scaled = tentative - runner.default_dof_pos
target_metrics = compute_target_error_metrics(state, target_hold, policy_target)
runtime_blend_ratio = min(
1.0,
float(getattr(runner, "_command_release_alpha", 0.0))
* (runner.command_release_s / max(release_cfg["target_blend_s"], control_dt)),
)
projected_gravity = get_gravity_orientation(state["quat_wxyz"])
guard_decision = guard.check(
imu_gyro=state["imu_gyro"],
projected_gravity=projected_gravity,
imu_age_ms=float(state["imu_age_ms"]),
estop_triggered=keyboard.is_estop_triggered(),
extra_nan_arrays=(raw, tentative),
)
if guard_decision.level == GuardLevel.STOP:
logger.event("GUARD_STOP", phase="RUNTIME", reason=guard_decision.reason)
io.damping_brake()
break
safety_decision = safety.check(
target_pose=tentative,
default_pose=runner.default_dof_pos,
imu_gyro=state["imu_gyro"],
projected_gravity=projected_gravity,
estop_triggered=keyboard.is_estop_triggered(),
)
if safety_decision.level == SafetyLevel.ESTOP:
logger.event("SAFETY_ESTOP", reason=safety_decision.message)
io.damping_brake()
break
if safety_decision.level == SafetyLevel.BRAKE:
safety_diag = build_action_diag(
joint_pos=state["joint_pos"],
default_pose=runner.default_dof_pos,
raw=raw,
scaled=scaled,
tentative=tentative,
cmd=cmd,
zero_command=zero_command,
runtime_released=runtime_released,
release_alpha=float(getattr(runner, "_command_release_alpha", 0.0)),
safety_details=safety_decision.details,
)
logger.event(
"SAFETY_BRAKE",
reason=safety_decision.message,
details=safety_diag,
primary_joint=safety_diag.get("primary_joint_name"),
primary_offset=safety_diag.get("primary_leg_offset"),
primary_target=safety_diag.get("primary_target"),
primary_measured=safety_diag.get("primary_measured"),
primary_raw=safety_diag.get("primary_raw"),
primary_scaled=safety_diag.get("primary_scaled"),
cmd=cmd.tolist(),
release_alpha=float(getattr(runner, "_command_release_alpha", 0.0)),
)
io.damping_brake()
break
if safety_decision.level == SafetyLevel.CLIP and safety_decision.clipped_target is not None:
scaled = safety_decision.clipped_target - runner.default_dof_pos
safety_diag = build_action_diag(
joint_pos=state["joint_pos"],
default_pose=runner.default_dof_pos,
raw=raw,
scaled=scaled,
tentative=tentative,
cmd=cmd,
zero_command=zero_command,
runtime_released=runtime_released,
release_alpha=float(getattr(runner, "_command_release_alpha", 0.0)),
safety_details=safety_decision.details,
)
logger.event(
"SAFETY_CLIP",
reason=safety_decision.message,
details=safety_diag,
primary_joint=safety_diag.get("primary_joint_name"),
primary_offset=safety_diag.get("primary_leg_offset"),
primary_target=safety_diag.get("primary_target"),
primary_measured=safety_diag.get("primary_measured"),
primary_raw=safety_diag.get("primary_raw"),
primary_scaled=safety_diag.get("primary_scaled"),
max_raw=float(np.max(np.abs(raw))),
cmd=cmd.tolist(),
release_alpha=float(getattr(runner, "_command_release_alpha", 0.0)),
)
actual_target = io.send_actions(scaled, runner.default_dof_pos)
loop_dt_ms = (time.perf_counter() - loop_t0) * 1000.0
if log_every and (loop_count % log_every == 0):
motor_diag = state.get("motor_stale", {})
logger.state(
phase="RUNTIME",
joint_pos=state["joint_pos"],
joint_vel=state["joint_vel"],
joint_torque=state.get("joint_torque", np.zeros(16, dtype=np.float32)),
target_pose=actual_target,
raw_action=raw,
gyro=state["imu_gyro"],
accel=state["imu_accel"],
quat=state["quat_wxyz"],
proj_gravity=projected_gravity,
command=cmd,
imu_age_ms=float(state["imu_age_ms"]),
loop_dt_ms=loop_dt_ms,
safety_level=int(safety_decision.level),
guard_level=int(guard_decision.level),
holdover=int(motor_diag.get("holdover_this_frame", 0)),
stale_max=int(motor_diag.get("stale_max", 0)),
fresh_count=int(motor_diag.get("fresh_count", 16)),
kp_scale=1.0,
nan_flag=int(obs_nan or act_nan),
kp_leg_cmd=float(io.kp_leg),
kd_leg_cmd=float(io.kd_leg),
kd_wheel_cmd=float(io.kd_wheel),
runtime_release_alpha=float(getattr(runner, "_command_release_alpha", 0.0)),
runtime_release_hold_s=release_active_time,
runtime_blend_ratio=runtime_blend_ratio,
hold_target_max_err=target_metrics["hold_target_max_err"],
policy_target_max_err=target_metrics["policy_target_max_err"],
hold_policy_max_gap=target_metrics["hold_policy_max_gap"],
target_source="runtime_blend" if runtime_blend_ratio < 0.999 else "runtime_policy",
clip_primary_joint=str((safety_decision.details or {}).get("primary_joint_name", "")),
clip_primary_target=float((safety_decision.details or {}).get("primary_target", 0.0) or 0.0),
clip_primary_measured=float((safety_decision.details or {}).get("primary_measured", 0.0) or 0.0),
clip_primary_default=float((safety_decision.details or {}).get("primary_default", 0.0) or 0.0),
clip_primary_pos_err=float((safety_decision.details or {}).get("primary_pos_err", 0.0) or 0.0),
clip_primary_raw=float((safety_decision.details or {}).get("primary_raw", 0.0) or 0.0),
clip_primary_scaled=float((safety_decision.details or {}).get("primary_scaled", 0.0) or 0.0),
safety_reason=(
f"{safety_decision.message};zero_cmd={int(zero_command)};"
f"released={int(runtime_released)};alpha={getattr(runner, '_command_release_alpha', 0.0):.2f};"
f"max_raw={float(np.max(np.abs(raw))):.2f};"
f"clip={((safety_decision.details or {}).get('joint_indices', []))}"
),
guard_reason=guard_decision.reason,
)
next_exec += control_dt
slack = next_exec - time.perf_counter()
if slack > 0:
coarse = slack - 0.002
if coarse > 0:
time.sleep(coarse)
while time.perf_counter() < next_exec:
pass
elif slack < -control_dt:
logger.event("LOOP_OVERRUN", over_ms=-slack * 1000.0)
next_exec = time.perf_counter()
recent_dt_ms.append(loop_dt_ms)
if len(recent_dt_ms) > 50:
recent_dt_ms.pop(0)
if len(recent_dt_ms) == 50:
median_dt = float(np.median(recent_dt_ms))
if median_dt > 22.0:
logger.event("SLOW_LOOP_TREND", median_dt_ms=median_dt)
recent_dt_ms.clear()
loop_count += 1
if time.perf_counter() - last_print > 1.0:
print(
f"[Loop] cmd=[{cmd[0]:+.2f},{cmd[1]:+.2f},{cmd[2]:+.2f}] "
f"|raw|={float(np.max(np.abs(raw))):.2f} "
f"zero={int(zero_command)} rel={int(runtime_released)} "
f"alpha={getattr(runner, '_command_release_alpha', 0.0):.2f} "
f"imu_age={state['imu_age_ms']:.1f}ms "
f"holdover={io.hw.holdover_total} "
f"safety={int(safety_decision.level)}"
)
last_print = time.perf_counter()
except PoseInitFailed as exc:
print(f"[Main] startup aborted: {exc}")
logger.event("POSE_INIT_FAILED", error=str(exc))
except KeyboardInterrupt:
print("\n[Main] Ctrl+C received, stopping...")
logger.event("KEYBOARD_INTERRUPT")
except Exception as exc:
import traceback
print(f"\n[Main] exception: {exc}")
traceback.print_exc()
logger.event("UNEXPECTED_ERROR", error=str(exc), traceback=traceback.format_exc())
finally:
print("[Main] cleaning up...")
try:
io.damping_brake()
time.sleep(0.05)
logger.event("DAMPING_BRAKE_APPLIED")
except Exception as exc:
logger.event("DAMPING_BRAKE_FAILED", error=str(exc))
try:
io.disconnect()
logger.event("HARDWARE_DISCONNECTED")
finally:
keyboard.stop()
logger.close()
os._exit(0)
if __name__ == "__main__":
main()
Binary file not shown.
-22
View File
@@ -1,22 +0,0 @@
<mujoco model="wheelleg_scene">
<include file="wheelleg.xml"/>
<option timestep="0.002" gravity="0 0 -9.81" integrator="implicitfast"/>
<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3"/>
<global azimuth="120" elevation="-20"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/>
<texture type="2d" name="groundplane" builtin="checker" mark="edge"
rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300"/>
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/>
</asset>
<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="true"/>
<geom name="floor" size="0 0 0.05" type="plane" material="groundplane" friction="0.8 0.05 0.01"/>
</worldbody>
</mujoco>
@@ -1,327 +0,0 @@
<mujoco model="go2w scene">
<include file="C:/Users/31560/Documents/00_legged/new_rl/rc_mjlab/mjcf/wheelleg.xml"/>
<statistic center="3.7 -9.0 0.4" extent="5.0"/>
<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/>
<rgba haze="0.15 0.25 0.35 1"/>
<global azimuth="90" elevation="-20"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/>
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300"/>
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/>
<hfield name="perlin_hfield" size="1.0 0.75 0.2 0.2" file="C:/Users/31560/Documents/00_legged/new_rl/rc_mjlab/sim2sim/terrain/height_field.png"/>
<hfield name="image_hfield" size="1.0 1.0 0.02 0.1" file="C:/Users/31560/Documents/00_legged/new_rl/rc_mjlab/sim2sim/terrain/unitree_hfield.png"/>
</asset>
<worldbody>
<light pos="0 0 1.5" dir="0 0 -1" directional="true" />
<geom name="floor" size="0 0 0.05" type="plane" material="groundplane" />
<!-- 30cm高墙:旋转90度,沿x轴方向放置,并与T型楼梯中心线 y=-3.50 对齐 -->
<geom pos="1.8 -7.0 0.15"
type="box"
size="0.025 0.5 0.15"
quat="0.7071068 0.0 0.0 0.7071068"
rgba="1.0 0.9 0.4 1.0"/>
<!-- 沙砾碎木坑:x正方向边界与10度斜坡+x边界对齐,y正边界距斜坡y负边界4m -->
<geom pos="4.8361 -12.5 0.075"
type="box"
size="0.5 0.5 0.075"
quat="0.0 0.0 0.0 1.0"
rgba="0.75 0.72 0.55 1.0"/>
<geom pos="5.8361 -12.0 0.075"
type="box"
size="0.5 1.0 0.075"
quat="0.0 0.0 0.0 1.0"
rgba="0.75 0.72 0.55 1.0"/>
<!-- 限高杆 -->
<geom pos="6.2 -9.0 0.155"
type="cylinder"
size="0.025 0.155"
quat="1.0 0.0 0.0 0.0"
rgba="0.8 0.1 0.1 1.0" />
<geom pos="5.2 -9.0 0.155"
type="cylinder"
size="0.025 0.155"
quat="1.0 0.0 0.0 0.0"
rgba="0.8 0.1 0.1 1.0" />
<geom pos="5.7 -9.0 0.325"
type="cylinder"
size="0.015 0.5"
quat="0.7071068 0.0 0.7071068 0.0"
rgba="1.0 0.9 0.4 1.0"/>
<!-- 1m × 1m 正方形颜色块,出发区-->
<geom pos="3.7 -9.0 0.0"
type="box"
size="0.5 0.5 0.001"
rgba="1.0 0.0 0.0 0.35"
contype="0"
conaffinity="0" />
<!-- 10cm梯形台阶:T型楼梯,最高平台与 x=5.7 y=-3.5 平台在y轴方向对齐 -->
<geom pos="1.80 -4.75 0.05" type="box" size="0.15 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0" />
<geom pos="1.80 -4.45 0.15" type="box" size="0.15 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="1.80 -4.15 0.25" type="box" size="0.15 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0"/>
<!-- 最高平台:y = -3.50,与目标平台y轴对齐 -->
<geom pos="1.80 -3.50 0.35" type="box" size="0.5 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="1.80 -2.85 0.25" type="box" size="0.15 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="1.80 -2.55 0.15" type="box" size="0.15 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="1.80 -2.25 0.05" type="box" size="0.15 0.5 0.05" quat="0.7071068 0.0 0.0 0.7071068" rgba="0.75 0.72 0.55 1.0"/>
<!-- 顶部平台向 +x 方向连接地面的10cm台阶,同样y轴移动到 -3.50 -->
<geom pos="2.45 -3.50 0.25" type="box" size="0.15 0.5 0.05" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="2.75 -3.50 0.15" type="box" size="0.15 0.5 0.05" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="3.05 -3.50 0.05" type="box" size="0.15 0.5 0.05" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<!-- 斜坡木桥A木桥B -->
<geom pos="1.8 -0.88 0.1" type="box" size="0.40 0.5 0.005" quat="0.701836 0.086175 -0.086175 0.701836" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="1.8 0.0 0.10" type="box" size="0.5 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="2.65 0.0 0.10" type="box" size="0.2 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="3.2 0.0 0.10" type="box" size="0.2 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="3.75 0.0 0.10" type="box" size="0.2 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="4.3 0.0 0.10" type="box" size="0.2 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="4.85 0.0 0.10" type="box" size="0.2 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="5.7 -0.5 0.10" type="box" size="0.5 1.0 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="4.8002 -1.0 0.0951"
type="box"
size="0.4133 0.5 0.005"
quat="0.992546 0.0 -0.121869 0.0"
rgba="0.75 0.72 0.55 1.0"/>
<geom pos="5.35 -2.25 0.10" type="box" size="0.1 0.75 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="5.65 -2.25 0.10" type="box" size="0.1 0.75 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="5.95 -2.25 0.10" type="box" size="0.1 0.75 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<geom pos="5.7 -3.5 0.10" type="box" size="0.5 0.5 0.10" quat="1.0 0.0 0.0 0.0" rgba="0.75 0.72 0.55 1.0"/>
<!-- 10度斜坡:宽4m,斜坡 y正方向边缘 与平台 y正方向边缘 对齐 -->
<geom pos="4.6338 -5.0 0.0951"
type="box"
size="0.5759 2.0 0.005"
quat="0.9961947 0.0 -0.0871557 0.0"
rgba="0.75 0.72 0.55 1.0"/>
<!-- 新建10度斜坡:宽3m,高端与前一个10度斜坡高端衔接,向+x方向下坡 -->
<geom pos="5.7681 -5.5 0.0951"
type="box"
size="0.5759 1.5 0.005"
quat="0.9961947 0.0 0.0871557 0.0"
rgba="0.75 0.72 0.55 1.0"/>
<!--绕杆-->
<!-- 直径1m圆形颜色块,仅显示,不碰撞 -->
<geom pos="1.8 -10.1 0.0"
type="cylinder"
size="0.1 0.001"
rgba="1.0 0.0 0.0 0.35"
contype="0"
conaffinity="0" />
<geom pos="3.2 -12.5 0.0"
type="cylinder"
size="0.1 0.001"
rgba="1.0 0.0 0.0 0.35"
contype="0"
conaffinity="0" />
<geom pos="1.55 -12.75 0.0"
type="cylinder"
size="0.1 0.001"
rgba="1.0 0.0 0.0 0.35"
contype="0"
conaffinity="0" />
<!-- 原杆 -->
<geom pos="1.8 -10.5 0.02"
type="cylinder"
size="0.05 0.02"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<geom pos="1.8 -10.5 0.37"
type="cylinder"
size="0.015 0.33"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<!-- y轴负方向第1根:间隔1m -->
<geom pos="1.8 -11.5 0.02"
type="cylinder"
size="0.05 0.02"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<geom pos="1.8 -11.5 0.37"
type="cylinder"
size="0.015 0.33"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<!-- y轴负方向第2根:继续间隔1m -->
<geom pos="1.8 -12.5 0.02"
type="cylinder"
size="0.05 0.02"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<geom pos="1.8 -12.5 0.37"
type="cylinder"
size="0.015 0.33"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<!-- x轴正方向第3根:继续间隔1m -->
<geom pos="2.8 -12.5 0.02"
type="cylinder"
size="0.05 0.02"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<geom pos="2.8 -12.5 0.37"
type="cylinder"
size="0.015 0.33"
quat="1.0 0.0 0.0 0.0"
rgba="0.75 0.72 0.55 1.0" />
<!--===================================================================其他障碍=====================================================================================-->
<!-- 5cm台阶 -->
<geom pos="1.0 2.0 0.025" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="1.3 2.0 0.075" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="1.6 2.0 0.125" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="1.9 2.0 0.175" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="2.2 2.0 0.225" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="2.5 2.0 0.275" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="2.8 2.0 0.325" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="3.45 2.0 0.375" type="box" size="0.5 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="4.1 2.0 0.325" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="4.4 2.0 0.275" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="4.7 2.0 0.225" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="5.0 2.0 0.175" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="5.3 2.0 0.125" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="5.6 2.0 0.075" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<geom pos="5.9 2.0 0.025" type="box" size="0.15 1.0 0.025" quat="1.0 0.0 0.0 0.0" />
<!-- 斜坡 -->
<geom pos="2.0 4.0 0.1" type="box" size="1.5 0.75 0.005" quat="0.9950041652780258 0.0 -0.09983341664682815 0.0" />
<geom pos="1.4 6.0 0.165" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="1.6 6.0 0.275" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="1.8 6.0 0.385" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="2.0 6.0 0.495" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="2.2 6.0 0.605" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="2.4 6.0 0.715" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="2.5999999999999996 6.0 0.825" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="2.8 6.0 0.9349999999999999" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="3.0 6.0 1.045" type="box" size="0.1 0.75 0.0049999999999999975" quat="1.0 0.0 0.0 0.0"/>
<geom pos="-2.3179973398407565 5.173660321080885 -0.25" type="box" size="0.2568216778785459 0.2608020098770089 0.2619541072037832" quat="0.9930658271270357 -0.05910360995856133 -0.06731065544310916 0.0761334482746007"/>
<geom pos="-2.3179973398407565 5.3620612607436735 -0.25" type="box" size="0.23778028947050467 0.26051225137569556 0.27137457425982286" quat="0.9959802827127009 0.07435995796871331 0.0004862731479656538 -0.04993632582444846"/>
<geom pos="-2.3179973398407565 5.545897059602109 -0.25" type="box" size="0.23841994611378936 0.2717839884518381 0.22585827399286504" quat="0.9983504429181294 -0.004811901627841528 0.03471877670768104 -0.045473566737405234"/>
<geom pos="-2.3179973398407565 5.7436240471772795 -0.25" type="box" size="0.2552179019048769 0.2548992578792955 0.22547735976326444" quat="0.9968270877924189 -0.029673908987198697 0.06777847718526858 -0.029347814214192768"/>
<geom pos="-2.3179973398407565 5.940214647011584 -0.25" type="box" size="0.24313116620329878 0.2372064979204117 0.26079933745117434" quat="0.9952106364954509 0.05088987714749159 -0.07605843245051555 -0.03436748846516202"/>
<geom pos="-2.3179973398407565 6.165430585901471 -0.25" type="box" size="0.24786042386990592 0.2322559052231109 0.2644037606269708" quat="0.9936075351397807 -0.05017393314139304 0.06986162224674641 -0.07311632022760194"/>
<geom pos="-2.3179973398407565 6.315657865031069 -0.25" type="box" size="0.23704265198840277 0.24982080672772003 0.2530694373586838" quat="0.9981716459301547 0.036179123385437884 0.04523497974247521 0.017269420947002005"/>
<geom pos="-2.3179973398407565 6.489372835072359 -0.25" type="box" size="0.2647428927965494 0.2716292502682415 0.23725049444938928" quat="0.9954041516289313 0.019099981466976248 0.07663366510923347 0.05415761257454444"/>
<geom pos="-2.094119617957536 5.194943631570213 -0.25" type="box" size="0.23176840693038148 0.23782936054799508 0.2282032657053922" quat="0.9973366591969123 0.030692664684553082 -0.030015392527106853 0.058962910104125923"/>
<geom pos="-2.094119617957536 5.441090326561234 -0.25" type="box" size="0.2602142310926322 0.27213502289176367 0.2574009440402366" quat="0.9948915480473169 0.023650461407535205 0.08508706956405496 -0.04890453856469255"/>
<geom pos="-2.094119617957536 5.642958951230403 -0.25" type="box" size="0.24800055056479955 0.24050676557282252 0.23522489807277194" quat="0.9912055235117067 0.06188914817453858 -0.09720856678264424 -0.0650525790586179"/>
<geom pos="-2.094119617957536 5.884810142659838 -0.25" type="box" size="0.24637516954806898 0.24364583504893206 0.2682443460752295" quat="0.9964495083946477 -0.07467561549070643 -0.038754439691442995 0.003165924091257204"/>
<geom pos="-2.094119617957536 6.129893093768145 -0.25" type="box" size="0.2378958269703999 0.25045408022075055 0.24760364656411665" quat="0.9946240462872651 -0.03963038800955788 -0.08917648605658504 0.03464091840526007"/>
<geom pos="-2.094119617957536 6.31135792935651 -0.25" type="box" size="0.2612154064649683 0.2252849660353503 0.26933214617336004" quat="0.997742809172301 0.019590451180868298 0.06391648942914147 -0.006339033565912353"/>
<geom pos="-2.094119617957536 6.4905285510291915 -0.25" type="box" size="0.22768983323309258 0.23025468157122184 0.2748062344121069" quat="0.9940843963100783 0.05105793020576152 0.009164090402224958 -0.0954218016127878"/>
<geom pos="-2.094119617957536 6.650993287072798 -0.25" type="box" size="0.25443787917913946 0.24575387105878618 0.22934424641420587" quat="0.9953832160779188 -0.0075142647566912866 0.08305501342257929 0.04751477371219154"/>
<geom pos="-1.8951777534978893 5.178742263102535 -0.25" type="box" size="0.256127371544264 0.24353861032614957 0.2714578153598507" quat="0.996547165679354 -0.008558846106514282 0.009384988866828401 0.08205129318749771"/>
<geom pos="-1.8951777534978893 5.353646654030374 -0.25" type="box" size="0.23858897994497189 0.2426691342857623 0.26961659613969635" quat="0.997689480058865 0.014145078555906384 0.009115651940537827 -0.06582190381793727"/>
<geom pos="-1.8951777534978893 5.575640096199311 -0.25" type="box" size="0.24430646334884096 0.2676093509240798 0.23693271670520474" quat="0.9976019172675967 -0.06794745094616256 0.006187887696065785 -0.011630503849579829"/>
<geom pos="-1.8951777534978893 5.728196634759373 -0.25" type="box" size="0.25668697148716296 0.2743986770827004 0.23696403861156407" quat="0.9946311966964959 -0.0774649076913635 0.05866511796477466 0.03558615698054331"/>
<geom pos="-1.8951777534978893 5.954288148947323 -0.25" type="box" size="0.2748179458028994 0.25407956175122554 0.25142548243710355" quat="0.9927720663552536 0.03885622734718657 0.06180632563910765 0.09525647469886914"/>
<geom pos="-1.8951777534978893 6.198076908897641 -0.25" type="box" size="0.23522918501552104 0.2714895927340788 0.23659922178360912" quat="0.9944408196408429 0.027722532803612636 -0.06883505448003607 -0.07470376618171105"/>
<geom pos="-1.8951777534978893 6.356335079330304 -0.25" type="box" size="0.253243646653926 0.26648639763264487 0.22751627090926196" quat="0.9924345646356405 0.06351743627133029 0.09661714031088077 -0.041283149154730255"/>
<geom pos="-1.8951777534978893 6.6037178705715744 -0.25" type="box" size="0.2664884535468762 0.26472237049442093 0.2545826559482188" quat="0.996208812433608 0.04792667434016892 0.03088022810200046 -0.06570728596343135"/>
<geom pos="-1.7450143557797366 5.181798049538029 -0.25" type="box" size="0.22764974830145798 0.2314500042225232 0.26635647118774647" quat="0.996420562587967 -0.02530598413067966 -0.01712092616039989 -0.07881968983996092"/>
<geom pos="-1.7450143557797366 5.396539066742657 -0.25" type="box" size="0.24515338305934362 0.25502436912192245 0.23509532716059323" quat="0.9974817825235419 0.033698559354477776 -0.06057332552735373 -0.01501242370995589"/>
<geom pos="-1.7450143557797366 5.5477605493550115 -0.25" type="box" size="0.2368415768982088 0.2653984778068547 0.25193186806340717" quat="0.9933816957092091 -0.04426535864216467 0.0892264769079807 0.057201577537394625"/>
<geom pos="-1.7450143557797366 5.764238738853998 -0.25" type="box" size="0.257475541143157 0.25587521442146555 0.2684267956125346" quat="0.9955849335110497 0.004076118120639651 -0.09299366924131539 0.012091439447074191"/>
<geom pos="-1.7450143557797366 5.942956213887727 -0.25" type="box" size="0.23647345784635188 0.22779489919605103 0.2690566454457882" quat="0.9997372307105926 0.020286738832439623 -0.010113822112874779 0.003410038259163467"/>
<geom pos="-1.7450143557797366 6.162981335139796 -0.25" type="box" size="0.2336152227884161 0.23785626414299832 0.26272786991330355" quat="0.9954569549676405 0.08638675499005252 -0.03150236191317218 -0.024705881136540285"/>
<geom pos="-1.7450143557797366 6.344025407207907 -0.25" type="box" size="0.2364006101773331 0.23674709170116234 0.2660167000427503" quat="0.9941020005048972 -0.09350614404282417 -0.05493059192638792 0.0006660998579442658"/>
<geom pos="-1.7450143557797366 6.5791872438688825 -0.25" type="box" size="0.259653062502181 0.26359758888480966 0.27170867851854713" quat="0.9955751902075182 0.06572529509454274 0.04059253213564211 -0.05350207998588654"/>
<geom pos="-1.4950537649717406 5.196975242498044 -0.25" type="box" size="0.24902209995429173 0.24604186796843594 0.26555264385759036" quat="0.9981753741369879 0.027059282981497578 0.02078151255996191 0.049818133312398136"/>
<geom pos="-1.4950537649717406 5.415093649392617 -0.25" type="box" size="0.22561317519118573 0.23246623591498758 0.2516053992906602" quat="0.9961431033663982 -0.019679813255757937 0.07777937635153075 0.035524515199325105"/>
<geom pos="-1.4950537649717406 5.596072881787104 -0.25" type="box" size="0.2545347271371952 0.2527292932516396 0.272364707011277" quat="0.9987990019474599 -0.0468132050914785 0.002764373695484487 -0.01419280718860587"/>
<geom pos="-1.4950537649717406 5.788444207457658 -0.25" type="box" size="0.257823578756761 0.22815201323013437 0.2506904868770564" quat="0.9907800223935689 0.08207700037219429 -0.06679273748567588 -0.08459931119619979"/>
<geom pos="-1.4950537649717406 5.962479724512656 -0.25" type="box" size="0.23447002037921025 0.260091883647859 0.2613547781123637" quat="0.994185747243896 -0.045779429335849345 -0.09537527521663794 -0.020062420196245392"/>
<geom pos="-1.4950537649717406 6.137620949716146 -0.25" type="box" size="0.25502378303486756 0.24137626830945555 0.26521755821448284" quat="0.9954511067004865 0.04827241542514884 0.023378972724717166 -0.07874193109224191"/>
<geom pos="-1.4950537649717406 6.345742058941403 -0.25" type="box" size="0.23763376291279575 0.27259418014745557 0.24184880568666417" quat="0.9979730938550316 -0.0521706328108408 -0.03256129013590577 0.01636127740178204"/>
<geom pos="-1.4950537649717406 6.548966807280673 -0.25" type="box" size="0.23004532593424165 0.24736965888987583 0.22917624237245732" quat="0.993938322382385 -0.041286377558295506 -0.09233464220936707 0.04308549844056845"/>
<geom pos="-1.2872521554407157 5.194072124237239 -0.25" type="box" size="0.2725857334366114 0.23611730648841156 0.25109418723334265" quat="0.9870880287314421 -0.09538067591152324 -0.0888119213223197 -0.09312460914696315"/>
<geom pos="-1.2872521554407157 5.418639818976418 -0.25" type="box" size="0.2326397456179607 0.2609646699674687 0.2717115772948157" quat="0.9928206111485406 -0.07383059897166898 -0.08473752136579614 0.040936892980589765"/>
<geom pos="-1.2872521554407157 5.655974569843163 -0.25" type="box" size="0.22633109461398734 0.25911291311168594 0.23532484499883452" quat="0.9947769304653071 -0.023834507399340135 0.09176246279588879 -0.037820963666801724"/>
<geom pos="-1.2872521554407157 5.8981628648303595 -0.25" type="box" size="0.27278934520816167 0.2559269445001904 0.26076472835929454" quat="0.9936318869346414 0.0561019165840355 0.09234219275279033 0.0319557140415977"/>
<geom pos="-1.2872521554407157 6.064939027275534 -0.25" type="box" size="0.2481756437845515 0.2613413397088905 0.24788858471207542" quat="0.9992310163557966 0.006919915638721734 0.03652449975321466 -0.012468024618680441"/>
<geom pos="-1.2872521554407157 6.288670079370122 -0.25" type="box" size="0.2574127585385611 0.27445220033632356 0.22507618952620437" quat="0.9984337612791753 0.04947883993440395 -0.0010074967830211658 -0.026093173185657285"/>
<geom pos="-1.2872521554407157 6.4987760234638605 -0.25" type="box" size="0.2442462188069663 0.2639082925274208 0.24918893213917415" quat="0.9910480688126146 0.08639971284060838 -0.0903343762232188 -0.04688832899783648"/>
<geom pos="-1.2872521554407157 6.675611985491267 -0.25" type="box" size="0.2507168814441121 0.26699708557208374 0.26588306060638556" quat="0.9968882348111167 0.009466759168649483 -0.06920917552433178 0.03652831489763918"/>
<geom pos="-1.0678149575697586 5.238251070535694 -0.25" type="box" size="0.2331754586513582 0.22873009754409884 0.2593258638743009" quat="0.9962563091989393 0.014506022500180732 0.069646748226122 0.049114887295595266"/>
<geom pos="-1.0678149575697586 5.471732496077581 -0.25" type="box" size="0.2653495693182789 0.26581370557074685 0.2509273010188512" quat="0.9948019811445705 -0.05898118120008214 0.04034485064897968 -0.07254330845220838"/>
<geom pos="-1.0678149575697586 5.691153717662407 -0.25" type="box" size="0.26309077142424103 0.26536949948987093 0.26566703066149744" quat="0.998331608401392 -0.025703385975953945 -0.05139460706256987 0.005650661991725779"/>
<geom pos="-1.0678149575697586 5.938910564562482 -0.25" type="box" size="0.23082606038737274 0.23890770539441533 0.25941695887199245" quat="0.99040745217393 -0.09494180013051996 0.05600240658814475 -0.08332384846283197"/>
<geom pos="-1.0678149575697586 6.13746533389903 -0.25" type="box" size="0.2676431215498913 0.2569308659994288 0.24597694927356487" quat="0.9955894165044442 0.04511524606234168 -0.003741035154326279 -0.08217258042101841"/>
<geom pos="-1.0678149575697586 6.328213254628996 -0.25" type="box" size="0.24188540919954635 0.25556145122229207 0.2605619987765001" quat="0.9929731934387934 -0.06598945456828725 -0.06809928840246085 0.07079629875087447"/>
<geom pos="-1.0678149575697586 6.554891603524267 -0.25" type="box" size="0.23121916641107804 0.25266417867731916 0.25489063309084503" quat="0.9984565221200774 -0.02714563973423854 0.029423903667849492 0.03849573446816443"/>
<geom pos="-1.0678149575697586 6.718482966003368 -0.25" type="box" size="0.27418971733780156 0.2623437838864593 0.23694037285314332" quat="0.9968347573500278 0.015128289848683302 0.06990516881209438 0.03471121949050888"/>
<geom pos="-0.8851136474992356 5.233272057743225 -0.25" type="box" size="0.2587161160845902 0.2542459313914242 0.25268742624288776" quat="0.9956323623605605 0.07455213252171443 -0.05585558926184348 0.006191260373025539"/>
<geom pos="-0.8851136474992356 5.410759300563648 -0.25" type="box" size="0.24019625793101548 0.2509280955260936 0.26698317271101046" quat="0.9960056830073374 0.025487333399660517 0.08516393482003458 -0.008377318140719903"/>
<geom pos="-0.8851136474992356 5.622546965631826 -0.25" type="box" size="0.22527085609750916 0.22924847380626232 0.23073331588883172" quat="0.9982201065088221 0.016292210993739946 0.013620740418279952 0.05572843307423328"/>
<geom pos="-0.8851136474992356 5.856118124892678 -0.25" type="box" size="0.26833255247166926 0.2512767990265972 0.2502231376336179" quat="0.9914910532543448 0.0657880493114159 0.05073139628229043 -0.10021850784978792"/>
<geom pos="-0.8851136474992356 6.010914595891683 -0.25" type="box" size="0.24926441451407527 0.22868800964152894 0.26501630221174116" quat="0.990736605756697 -0.0765632176593298 -0.09876375004658954 0.05314859727296924"/>
<geom pos="-0.8851136474992356 6.1657945570092965 -0.25" type="box" size="0.26823427325895977 0.263134568634285 0.23692064318485426" quat="0.9903439387899988 0.07219524819688558 0.09310840228148876 0.07305856872598579"/>
<geom pos="-0.8851136474992356 6.383066094410735 -0.25" type="box" size="0.23772929143507357 0.2619329708548053 0.23258884134606547" quat="0.9928463525385446 0.07348810370848206 0.09252710448645944 -0.017156742103069993"/>
<geom pos="-0.8851136474992356 6.551482176174038 -0.25" type="box" size="0.25127741834221506 0.25307864976328337 0.234931895271364" quat="0.994062479132312 -0.084918495271965 -0.05565932062022146 -0.03912386445844099"/>
<geom pos="-0.6441452943469552 5.193190826541208 -0.25" type="box" size="0.27016649011158844 0.23778885128164629 0.25859862297032477" quat="0.9957774962333289 0.08893738517887846 -0.021654911548168992 0.006955883744530942"/>
<geom pos="-0.6441452943469552 5.407266358883468 -0.25" type="box" size="0.24982888074520473 0.26972381569065607 0.2275629646713632" quat="0.9942571418988088 0.02444442288698024 0.050336521935401994 -0.09122193010664256"/>
<geom pos="-0.6441452943469552 5.56363948710079 -0.25" type="box" size="0.24725848381417467 0.2326432801330426 0.2476341019968084" quat="0.9935769557551848 -0.05688836927692267 -0.06858315894908604 0.0697488117593134"/>
<geom pos="-0.6441452943469552 5.716461923308758 -0.25" type="box" size="0.226746163574222 0.25188961955216527 0.24650452053954758" quat="0.9981029078160676 0.03471313574303335 -0.0493522060742233 -0.012245136651074443"/>
<geom pos="-0.6441452943469552 5.896430073001983 -0.25" type="box" size="0.25284949298017617 0.23421620066432108 0.2621382648463894" quat="0.9996413412447853 0.009523771195346772 -0.02374945051322109 -0.007902547492127014"/>
<geom pos="-0.6441452943469552 6.110845613879558 -0.25" type="box" size="0.23421403581041014 0.2504556848552827 0.24096555776925194" quat="0.9982371379292613 0.05906199571247059 0.004184466367765714 0.004097238396071534"/>
<geom pos="-0.6441452943469552 6.301410327752269 -0.25" type="box" size="0.2296817145699627 0.2701766916372966 0.22803599234835198" quat="0.9993879202059675 0.00903361239651759 0.0071600522792900426 -0.03302896372607247"/>
<geom pos="-0.6441452943469552 6.470340592771366 -0.25" type="box" size="0.2742166750011587 0.23990439594655139 0.2609404931878803" quat="0.9954980192986956 -0.06477558432537366 -0.03478863652617152 0.059812774691783824"/>
<geom pos="-0.43033316974190594 5.176946043237525 -0.25" type="box" size="0.2511344405622137 0.26383180387822514 0.2729516287367074" quat="0.9939152287364423 -0.054017256979398534 -0.08418383902399898 -0.0461273810376057"/>
<geom pos="-0.43033316974190594 5.409693792302493 -0.25" type="box" size="0.23878438774624536 0.22858057493504688 0.24808981791323623" quat="0.9942721168135948 -0.06322741578652484 -0.032958930763236916 -0.0796175891553822"/>
<geom pos="-0.43033316974190594 5.573593446642387 -0.25" type="box" size="0.2647545698926724 0.25215736713350106 0.25522885339490087" quat="0.9971918839448468 -0.004732654353889047 0.07222000940536273 0.019241071144380294"/>
<geom pos="-0.43033316974190594 5.819531161619798 -0.25" type="box" size="0.2538318010250847 0.23661725487197 0.26323696729639623" quat="0.9929867679849618 0.07588268800385974 -0.046355180482866284 -0.07791208834634974"/>
<geom pos="-0.43033316974190594 6.047888701789067 -0.25" type="box" size="0.25684720236482583 0.25568474221031684 0.24397094296107352" quat="0.9925256546500537 -0.08214715587137877 0.06615741576234473 0.06138294537877935"/>
<geom pos="-0.43033316974190594 6.283676056613722 -0.25" type="box" size="0.2432692787269645 0.2742601402399693 0.2689467974776609" quat="0.9989897410981501 0.0047037010179340685 0.0014590493232283363 -0.04466814919444852"/>
<geom pos="-0.43033316974190594 6.458239009480634 -0.25" type="box" size="0.2474330649412728 0.2551435998627988 0.23002773988805483" quat="0.9982311862818315 0.0506459597703481 0.025989046975463247 -0.01714802993392417"/>
<geom pos="-0.43033316974190594 6.703309377175586 -0.25" type="box" size="0.2301029552870502 0.2556223065028505 0.2349557527965884" quat="0.9948672058648923 -0.034182171957127715 -0.0030330068097560517 -0.09519255582537542"/>
<geom type="hfield" hfield="perlin_hfield" pos="-1.5 4.0 0.0" quat="1.0 0.0 0.0 0.0"/>
<geom type="hfield" hfield="image_hfield" pos="-1.5 2.0 0.0" quat="0.7073882691671998 0.0 0.0 -0.706825181105366"/>
</worldbody>
</mujoco>
-157
View File
@@ -1,157 +0,0 @@
<mujoco model="wheelleg">
<compiler angle="radian" meshdir="meshes/"/>
<default>
<geom margin="0"/>
</default>
<asset>
<mesh name="base_link" content_type="model/stl" file="base_link.STL"/>
<mesh name="fl_hip_abduction_Link" content_type="model/stl" file="fl_hip_abduction_Link.STL"/>
<mesh name="fl_hip_pitch_Link" content_type="model/stl" file="fl_hip_pitch_Link.STL"/>
<mesh name="fl_knee_Link" content_type="model/stl" file="fl_knee_Link.STL"/>
<mesh name="fl_wheel_Link" content_type="model/stl" file="fl_wheel_Link.STL"/>
<mesh name="fr_hip_abduction_Link" content_type="model/stl" file="fr_hip_abduction_Link.STL"/>
<mesh name="fr_hip_pitch_Link" content_type="model/stl" file="fr_hip_pitch_Link.STL"/>
<mesh name="fr_knee_Link" content_type="model/stl" file="fr_knee_Link.STL"/>
<mesh name="fr_wheel_Link" content_type="model/stl" file="fr_wheel_Link.STL"/>
<mesh name="rl_hip_abduction_Link" content_type="model/stl" file="rl_hip_abduction_Link.STL"/>
<mesh name="rl_hip_pitch_Link" content_type="model/stl" file="rl_hip_pitch_Link.STL"/>
<mesh name="rl_knee_Link" content_type="model/stl" file="rl_knee_Link.STL"/>
<mesh name="rl_wheel_Link" content_type="model/stl" file="rl_wheel_Link.STL"/>
<mesh name="rr_hip_abduction_Link" content_type="model/stl" file="rr_hip_abduction_Link.STL"/>
<mesh name="rr_hip_pitch_Link" content_type="model/stl" file="rr_hip_pitch_Link.STL"/>
<mesh name="rr_knee_Link" content_type="model/stl" file="rr_knee_Link.STL"/>
<mesh name="rr_wheel_Link" content_type="model/stl" file="rr_wheel_Link.STL"/>
</asset>
<worldbody>
<body name="base_link">
<inertial pos="0.1517 0.0002 0.0542" mass="3.5" diaginertia="0.0215 0.0904 0.0985"/>
<joint type="free"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="base_link"/>
<geom size="0.178 0.1175 0.073" pos="0.1518 0 0.054" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="fl_hip_abduction_Link" pos="0.32826 0.066172 0.053981">
<inertial pos="0.0488 -0.0026 0.0007" mass="0.5" diaginertia="0.0003 0.0006 0.0005"/>
<joint name="fl_hip_abduction_joint" pos="0 0 0" axis="1 0 0" range="-0.436 0.611" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fl_hip_abduction_Link"/>
<body name="fl_hip_pitch_Link" pos="0.06389 -0.027344 0.00010727" quat="0.999997 -0.0025023 0 0">
<inertial pos="0.0019 0.1119 -0.048" mass="0.935" diaginertia="0.0062 0.0064 0.001"/>
<joint name="fl_hip_pitch_joint" pos="0 0 0" axis="0 1 0" range="-2.58 2.58" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fl_hip_pitch_Link"/>
<geom size="0.046 0.048" pos="0 0.048 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.0435 0.0115 0.06" pos="0 0.1155 -0.06" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="fl_knee_Link" pos="0 0.1035 -0.25" quat="0.999997 0.0025023 0 0">
<inertial pos="0.0002 0.0242 -0.1539" mass="0.651" fullinertia="0.0042 0.0045 0.0005 0 0 0.0002"/>
<joint name="fl_knee_joint" pos="0 0 0" axis="0 1 0" range="-2.65 2.65" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fl_knee_Link"/>
<geom size="0.0475 0.015" pos="0 0.025 -0.20011" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.015 0.0125 0.06" pos="0 0.0125 -0.09" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="fl_wheel_Link" pos="0 0.014699 -0.20011">
<inertial pos="-0.0002 0.0407 -0.0001" mass="0.53" diaginertia="0.0017 0.0032 0.0017"/>
<joint name="fl_wheel_joint" pos="0 0 0" axis="0 1 0" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fl_wheel_Link"/>
<geom size="0.1 0.015" pos="0 0.04074 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
</body>
</body>
</body>
</body>
<body name="fr_hip_abduction_Link" pos="0.32826 -0.065853 0.054034">
<inertial pos="0.0488 0.0026 0.0008" mass="0.5" diaginertia="0.0003 0.0006 0.0005"/>
<joint name="fr_hip_abduction_joint" pos="0 0 0" axis="1 0 0" range="-0.611 0.436" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fr_hip_abduction_Link"/>
<body name="fr_hip_pitch_Link" pos="0.06389 0.027311 -0.00036027" quat="0.999976 -0.00686995 0 0">
<inertial pos="-0.0019 -0.1119 -0.048" mass="0.935" diaginertia="0.0062 0.0064 0.001"/>
<joint name="fr_hip_pitch_joint" pos="0 0 0" axis="0 1 0" range="-2.58 2.58" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fr_hip_pitch_Link"/>
<geom size="0.046 0.048" pos="0 -0.048 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.0435 0.0115 0.06" pos="0 -0.1155 -0.06" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="fr_knee_Link" pos="-0.00075079 -0.1035 -0.25" quat="0.999976 0.00686995 0 0">
<inertial pos="-0.0002 -0.0242 -0.1539" mass="0.651" fullinertia="0.0042 0.0045 0.0005 0 0 0.0001"/>
<joint name="fr_knee_joint" pos="0 0 0" axis="0 1 0" range="-2.65 2.65" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fr_knee_Link"/>
<geom size="0.0475 0.015" pos="0 -0.025 -0.1998" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.015 0.0125 0.06" pos="0 -0.0125 -0.09" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="fr_wheel_Link" pos="0 -0.018447 -0.1998">
<inertial pos="0.0002 -0.0407 -0.0001" mass="0.53" diaginertia="0.0017 0.0032 0.0017"/>
<joint name="fr_wheel_joint" pos="0 0 0" axis="0 1 0" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="fr_wheel_Link"/>
<geom size="0.1 0.015" pos="0 -0.040735 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
</body>
</body>
</body>
</body>
<body name="rl_hip_abduction_Link" pos="-0.024743 0.066141 0.054034">
<inertial pos="-0.0488 -0.0026 -0.0008" mass="0.5" diaginertia="0.0003 0.0006 0.0005"/>
<joint name="rl_hip_abduction_joint" pos="0 0 0" axis="1 0 0" range="-0.436 0.611" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rl_hip_abduction_Link"/>
<body name="rl_hip_pitch_Link" pos="-0.06389 -0.027309 0.00045509">
<inertial pos="0.0019 0.1119 -0.048" mass="0.935" diaginertia="0.0062 0.0064 0.001"/>
<joint name="rl_hip_pitch_joint" pos="0 0 0" axis="0 1 0" range="-2.58 2.58" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rl_hip_pitch_Link"/>
<geom size="0.046 0.048" pos="0 0.048 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.0435 0.0115 0.06" pos="0 0.1155 -0.06" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="rl_knee_Link" pos="0 0.099459 -0.25163">
<inertial pos="0.0002 0.0242 -0.1539" mass="0.651" fullinertia="0.0042 0.0045 0.0005 0 0 -0.0003"/>
<joint name="rl_knee_joint" pos="0 0 0" axis="0 1 0" range="-2.65 2.65" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rl_knee_Link"/>
<geom size="0.0475 0.015" pos="0 0.025 -0.20027" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.015 0.0125 0.06" pos="0 0.0125 -0.09" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="rl_wheel_Link" pos="0 0.012475 -0.20027">
<inertial pos="-0.0002 0.0407 -0.0001" mass="0.53" diaginertia="0.0017 0.0032 0.0017"/>
<joint name="rl_wheel_joint" pos="0 0 0" axis="0 1 0" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rl_wheel_Link"/>
<geom size="0.1 0.015" pos="0 0.040737 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
</body>
</body>
</body>
</body>
<body name="rr_hip_abduction_Link" pos="-0.024743 -0.065884 0.053981">
<inertial pos="-0.0488 0.0026 0.0008" mass="0.5" diaginertia="0.0003 0.0006 0.0005"/>
<joint name="rr_hip_abduction_joint" pos="0 0 0" axis="1 0 0" range="-0.611 0.436" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rr_hip_abduction_Link"/>
<body name="rr_hip_pitch_Link" pos="-0.06389 0.027341 0.00041625">
<inertial pos="-0.002 -0.1111 -0.0498" mass="0.935" diaginertia="0.0062 0.0064 0.001"/>
<joint name="rr_hip_pitch_joint" pos="0 0 0" axis="0 1 0" range="-2.58 2.58" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rr_hip_pitch_Link"/>
<geom size="0.046 0.048" pos="0 -0.048 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.0435 0.0115 0.06" pos="0 -0.1155 -0.06" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="rr_knee_Link" pos="-0.00075079 -0.099408 -0.25165">
<inertial pos="-0.0002 -0.0225 -0.1541" mass="0.651" fullinertia="0.0042 0.0045 0.0005 0 0 -0.0001"/>
<joint name="rr_knee_joint" pos="0 0 0" axis="0 1 0" range="-2.65 2.65" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rr_knee_Link"/>
<geom size="0.0475 0.015" pos="0 -0.025 -0.20027" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
<geom size="0.015 0.0125 0.06" pos="0 -0.0125 -0.09" type="box" rgba="0.75294 0.75294 0.75294 1"/>
<body name="rr_wheel_Link" pos="0 -0.012435 -0.20027">
<inertial pos="0.0002 -0.0407 -0.0005" mass="0.53" diaginertia="0.0017 0.0032 0.0017"/>
<joint name="rr_wheel_joint" pos="0 0 0" axis="0 1 0" actuatorfrcrange="-17 17" damping="0.01" frictionloss="0.01" armature="0.0042"/>
<geom type="mesh" contype="0" conaffinity="0" group="1" density="0" rgba="0.75294 0.75294 0.75294 1" mesh="rr_wheel_Link"/>
<geom size="0.1 0.015" pos="0 -0.040737 0" quat="0.707105 0.707108 0 0" type="cylinder" rgba="0.75294 0.75294 0.75294 1"/>
</body>
</body>
</body>
</body>
<body name="imu_link" pos="0.1518 0 0.127">
<inertial pos="0 0 0" mass="0" diaginertia="0 0 0"/>
</body>
</body>
</worldbody>
<actuator>
<general name="fl_hip_abduction_joint" joint="fl_hip_abduction_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="fl_hip_pitch_joint" joint="fl_hip_pitch_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="fl_knee_joint" joint="fl_knee_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="fl_wheel_joint" joint="fl_wheel_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="0.5"/>
<general name="fr_hip_abduction_joint" joint="fr_hip_abduction_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="fr_hip_pitch_joint" joint="fr_hip_pitch_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="fr_knee_joint" joint="fr_knee_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="fr_wheel_joint" joint="fr_wheel_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="0.5"/>
<general name="rl_hip_abduction_joint" joint="rl_hip_abduction_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="rl_hip_pitch_joint" joint="rl_hip_pitch_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="rl_knee_joint" joint="rl_knee_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="rl_wheel_joint" joint="rl_wheel_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="0.5"/>
<general name="rr_hip_abduction_joint" joint="rr_hip_abduction_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="rr_hip_pitch_joint" joint="rr_hip_pitch_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="rr_knee_joint" joint="rr_knee_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="120" biasprm="0 -8 -8"/>
<general name="rr_wheel_joint" joint="rr_wheel_joint" ctrlrange="-17 17" forcerange="-17 17" gainprm="0.5"/>
</actuator>
</mujoco>
Binary file not shown.
@@ -1,176 +0,0 @@
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
class PolicyMLP(nn.Module):
def __init__(self, obs_dim: int, action_dim: int):
super().__init__()
self.register_buffer("obs_mean", torch.zeros(obs_dim))
self.register_buffer("obs_std", torch.ones(obs_dim))
self.net = nn.Sequential(
nn.Linear(obs_dim, 512),
nn.ELU(),
nn.Linear(512, 256),
nn.ELU(),
nn.Linear(256, 128),
nn.ELU(),
nn.Linear(128, action_dim),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = (x - self.obs_mean) / torch.clamp(self.obs_std, min=1e-6)
return self.net(x)
def load_policy(model_path: Path, device: torch.device) -> PolicyMLP:
checkpoint = torch.load(model_path, map_location=device, weights_only=False)
state_dict = checkpoint["actor_state_dict"]
input_key = "mlp.0.weight" if "mlp.0.weight" in state_dict else "net.0.weight"
output_key = "mlp.6.weight" if "mlp.6.weight" in state_dict else "net.6.weight"
obs_dim = int(state_dict[input_key].shape[1])
action_dim = int(state_dict[output_key].shape[0])
model = PolicyMLP(obs_dim=obs_dim, action_dim=action_dim)
remapped_state_dict: dict[str, torch.Tensor] = {}
for key, value in state_dict.items():
if key.startswith("mlp."):
remapped_state_dict[key.replace("mlp.", "net.")] = value
elif key.startswith("net."):
remapped_state_dict[key] = value
elif key == "obs_normalizer._mean":
remapped_state_dict["obs_mean"] = value.squeeze()
elif key == "obs_normalizer._var":
remapped_state_dict["obs_std"] = torch.sqrt(value.squeeze() + 1e-5)
model.load_state_dict(remapped_state_dict, strict=False)
model.eval()
model.to(device)
model.expected_obs_dim = obs_dim
model.expected_action_dim = action_dim
return model
class PolicyRunner:
BASE_OBS_DIM = 53
DEFAULT_STAND_POSE = np.array(
[
0.0, 0.9, -1.8,
0.0, 0.9, -1.8,
0.0, 0.9, -1.8,
0.0, 0.9, -1.8,
0.0, 0.0, 0.0, 0.0,
],
dtype=np.float32,
)
def __init__(
self,
policy_path: Path,
device: torch.device | None = None,
enable_zero_cmd_suppression: bool = True,
hold_zero_command_pose: bool = True,
command_release_s: float = 0.35,
action_scale: np.ndarray | None = None,
zero_cmd_use_yaw_rate: bool = True,
):
self.device = device or torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.policy_path = Path(policy_path)
self.enable_zero_cmd_suppression = bool(enable_zero_cmd_suppression)
self.hold_zero_command_pose = bool(hold_zero_command_pose)
self.command_release_s = max(float(command_release_s), 1e-3)
print(f"[PolicyRunner] device={self.device}, policy={self.policy_path}")
self.policy = load_policy(self.policy_path, self.device)
if self.policy.expected_obs_dim != self.BASE_OBS_DIM:
raise ValueError(
f"Unsupported policy obs dim {self.policy.expected_obs_dim}. "
f"Current sim2real only supports {self.BASE_OBS_DIM}-D actor observations."
)
self.default_dof_pos = self.DEFAULT_STAND_POSE.copy()
self.last_actions = np.zeros(16, dtype=np.float32)
self.action_scale = np.asarray(
action_scale
if action_scale is not None
else [
0.125, 0.25, 0.25,
0.125, 0.25, 0.25,
0.125, 0.25, 0.25,
0.125, 0.25, 0.25,
5.0, 5.0, 5.0, 5.0,
],
dtype=np.float32,
)
if self.action_scale.shape != (16,):
raise ValueError(f"action_scale must be shape (16,), got {self.action_scale.shape}")
self.zero_cmd_lin_thresh = 0.05
self.zero_cmd_yaw_thresh = 0.05
self.zero_yaw_rate_thresh = 0.10
self.zero_cmd_use_yaw_rate = bool(zero_cmd_use_yaw_rate)
self._command_release_alpha = 0.0
print(
f"[PolicyRunner] obs_dim={self.policy.expected_obs_dim}, "
f"base_obs_dim={self.BASE_OBS_DIM}, history=1, "
f"action_dim={self.policy.expected_action_dim}, "
f"zero_cmd_suppression={self.enable_zero_cmd_suppression}, "
f"hold_zero_command_pose={self.hold_zero_command_pose}"
)
def reset(self, prime_obs: np.ndarray | None = None) -> None:
self.last_actions = np.zeros(16, dtype=np.float32)
self._command_release_alpha = 0.0
def _is_zero_command(self, command: np.ndarray, base_ang_vel: np.ndarray) -> bool:
cmd_is_zero = (
np.linalg.norm(command[:2]) < self.zero_cmd_lin_thresh
and abs(command[2]) < self.zero_cmd_yaw_thresh
)
if not self.zero_cmd_use_yaw_rate:
return cmd_is_zero
return cmd_is_zero and abs(base_ang_vel[2]) < self.zero_yaw_rate_thresh
def command_activation_metrics(self, command: np.ndarray) -> tuple[float, float]:
command = np.asarray(command, dtype=np.float32)
planar = float(np.linalg.norm(command[:2]))
yaw = float(abs(command[2]))
return planar, yaw
def is_command_active(self, command: np.ndarray) -> bool:
planar, yaw = self.command_activation_metrics(command)
return planar >= self.zero_cmd_lin_thresh or yaw >= self.zero_cmd_yaw_thresh
def step(self, obs: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
obs = np.asarray(obs, dtype=np.float32)
expected_obs_dim = int(self.policy.expected_obs_dim)
if obs.shape[0] != expected_obs_dim:
raise ValueError(
f"Observation dim mismatch: got {obs.shape[0]}, expected {expected_obs_dim}."
)
obs_tensor = torch.tensor(obs, dtype=torch.float32, device=self.device).unsqueeze(0)
with torch.no_grad():
raw_actions = self.policy(obs_tensor).squeeze(0).cpu().numpy()
raw_actions = np.clip(raw_actions, -10.0, 10.0).astype(np.float32)
command = obs[6:9]
base_ang_vel = obs[0:3] / 0.25
zero_command = self._is_zero_command(command, base_ang_vel)
if zero_command:
self._command_release_alpha = 0.0
if self.hold_zero_command_pose:
raw_actions[:] = 0.0
elif self.enable_zero_cmd_suppression:
raw_actions[12:16] = 0.0
raw_actions[:12] *= 0.5
else:
self._command_release_alpha = min(1.0, self._command_release_alpha + 0.02 / self.command_release_s)
raw_actions *= self._command_release_alpha
self.last_actions = raw_actions.copy()
scaled_actions = raw_actions * self.action_scale
return scaled_actions, raw_actions
@@ -1,7 +0,0 @@
numpy
PyYAML
torch
pyserial
# Optional:
# pynput # only needed for CLI keyboard control
@@ -1,78 +0,0 @@
"""通用运行期守护:每个控制周期调用一次,无副作用,只做检查。
设计原则:
- 守护函数本身不下发动作、不打印(除非 verbose),只返回判定
- 调用方决定收到 GuardStop 时怎么办(damping_brake 或 raise
- 起立期 / 等待期 / 主循环都共用同一组检查
"""
from dataclasses import dataclass
from enum import IntEnum
from typing import Optional
import numpy as np
class GuardLevel(IntEnum):
OK = 0
WARN = 1 # 仅记录,不停
STOP = 2 # 主调方应立刻 damping_brake + 退出当前阶段
@dataclass
class GuardDecision:
level: GuardLevel
reason: str # 触发时人类可读说明,OK 时为空
class RuntimeGuard:
"""启动/起立/主循环共用的安全守护。
不监控目标位置范围(那是 SafetyMonitor 的职责)。这里只关心
机身整体状态:是否倾倒、是否翻滚、是否检测到 NaN、用户是否按急停。
"""
def __init__(self,
max_ang_vel: float = 12.0,
max_tilt_z: float = -0.30,
imu_age_warn_ms: float = 60.0,
imu_age_stop_ms: float = 200.0):
self.max_ang_vel = max_ang_vel
self.max_tilt_z = max_tilt_z
self.imu_age_warn_ms = imu_age_warn_ms
self.imu_age_stop_ms = imu_age_stop_ms
def check(self,
imu_gyro: np.ndarray,
projected_gravity: np.ndarray,
imu_age_ms: float,
estop_triggered: bool,
extra_nan_arrays: tuple = ()) -> GuardDecision:
# 1) 用户急停
if estop_triggered:
return GuardDecision(GuardLevel.STOP, "user E-stop")
# 2) NaN 检查(任意输入数组中出现 NaN)
for arr in (imu_gyro, projected_gravity, *extra_nan_arrays):
if arr is None:
continue
if np.any(np.isnan(arr)) or np.any(np.isinf(arr)):
return GuardDecision(GuardLevel.STOP, "NaN/Inf detected in observation/action")
# 3) IMU 数据陈旧
if imu_age_ms > self.imu_age_stop_ms:
return GuardDecision(GuardLevel.STOP, f"IMU stale {imu_age_ms:.0f}ms")
warned_imu = imu_age_ms > self.imu_age_warn_ms
# 4) 倾倒
if projected_gravity[2] > self.max_tilt_z:
return GuardDecision(GuardLevel.STOP,
f"tilt: g_z={projected_gravity[2]:.3f}")
# 5) 角速度爆表
ang_norm = float(np.linalg.norm(imu_gyro))
if ang_norm > self.max_ang_vel:
return GuardDecision(GuardLevel.STOP, f"ang_vel overflow: |w|={ang_norm:.2f}")
if warned_imu:
return GuardDecision(GuardLevel.WARN, f"IMU age {imu_age_ms:.0f}ms")
return GuardDecision(GuardLevel.OK, "")
@@ -1,107 +0,0 @@
"""三级安全监控(对应方法论 97.11)。
Level 0: 正常
Level 1: 限幅(位置/速度异常)— 截断目标位置幅值,记录连续触发次数
Level 2: 刹车(连续限幅 N 次 / IMU 角速度过大 / 倾倒)— 卸载刚度只留阻尼
Level 3: 急停(用户触发)— 让上层断电
设计原则:监控只判定,不直接关电机;返回 SafetyDecision 由上层决策。
"""
from dataclasses import dataclass
from enum import IntEnum
from typing import Any, Optional
import numpy as np
class SafetyLevel(IntEnum):
NORMAL = 0
CLIP = 1
BRAKE = 2
ESTOP = 3
@dataclass
class SafetyDecision:
level: SafetyLevel
message: str
clipped_target: Optional[np.ndarray]
details: Optional[dict[str, Any]] = None
class SafetyMonitor:
"""安全监控(按 50Hz 控制频率调用)。
Args:
max_target_offset: 单关节相对默认位姿的最大偏离 (rad)
max_ang_vel: IMU 角速度模 (rad/s)
max_tilt_rad: 机身重力 z 轴投影低于该值认为已严重倾倒
clip_to_brake: 连续 clip 多少帧升级为刹车
"""
def __init__(self,
max_target_offset: float = 0.6,
max_ang_vel: float = 10.0,
max_tilt_z: float = -0.3,
clip_to_brake: int = 3):
self.max_target_offset = max_target_offset
self.max_ang_vel = max_ang_vel
self.max_tilt_z = max_tilt_z # projected_gravity z 应当 ~ -1,明显小于 -0.3 视作倾倒
self.clip_to_brake = clip_to_brake
self.consecutive_clips = 0
def check(self,
target_pose: np.ndarray,
default_pose: np.ndarray,
imu_gyro: np.ndarray,
projected_gravity: np.ndarray,
estop_triggered: bool) -> SafetyDecision:
if estop_triggered:
return SafetyDecision(SafetyLevel.ESTOP, "user E-stop", None, None)
# 倾倒(projected_gravity[2] 应在 -1 附近,越接近 0 越倾斜)
if projected_gravity[2] > self.max_tilt_z:
return SafetyDecision(
SafetyLevel.BRAKE,
f"tilt detected: g_z={projected_gravity[2]:.3f}",
None,
{"g_z": float(projected_gravity[2])},
)
# 角速度爆表(猛烈翻滚)
if np.linalg.norm(imu_gyro) > self.max_ang_vel:
return SafetyDecision(
SafetyLevel.BRAKE,
f"angular velocity overflow: |w|={np.linalg.norm(imu_gyro):.2f}",
None,
{"ang_vel_norm": float(np.linalg.norm(imu_gyro))},
)
# 目标位置偏离过大 → 截断到允许范围
offset_leg = target_pose[:12] - default_pose[:12]
clipped_offset = np.clip(offset_leg, -self.max_target_offset, self.max_target_offset)
if not np.allclose(offset_leg, clipped_offset):
self.consecutive_clips += 1
clipped = target_pose.copy()
clipped[:12] = default_pose[:12] + clipped_offset
exceeded = np.where(np.abs(offset_leg) > self.max_target_offset)[0].tolist()
max_offset = float(np.max(np.abs(offset_leg)))
details = {
"joint_indices": exceeded,
"max_leg_offset": max_offset,
"consecutive_clips": int(self.consecutive_clips),
}
if self.consecutive_clips >= self.clip_to_brake:
return SafetyDecision(
SafetyLevel.BRAKE,
f"clipped {self.consecutive_clips} frames in a row",
clipped,
details,
)
return SafetyDecision(SafetyLevel.CLIP, "target leg offset out of range", clipped, details)
self.consecutive_clips = 0
return SafetyDecision(SafetyLevel.NORMAL, "", None, None)
def reset(self):
self.consecutive_clips = 0

Some files were not shown because too many files have changed in this diff Show More