P5a 框架规则文档:M1-M3 元规则 + R1-R6 分领域规矩 + 反例清单
背景:这个框架踩过的坑几乎都是同一类——"没报错但结果是错的"。
之前这些教训只存在于聊天记录和零散注释里,改一次代码就要重踩一遍。
现在收敛成一份可引用的文档,并压缩成 .cursor/rules/ 常驻规则。
docs/FRAMEWORK_RULES.md(414 行)
第 0 节 三条元规则:M1 静默失效是头号敌人 / M2 单一数据源 / M3 口径必须显式
第 1 节 配置与数据 R1.1-R1.5(分层优先级、site.yaml 新键必须接线或告警、设备走 devices/、权重走 models/)
第 2 节 几何/单位/坐标系 R2.1-R2.7(米与弧度、frame_id 必须是光学系、深度 mm↔m、
rpy=Rz·Ry·Rx、Gazebo camera_info 不可信、像素门槛绑图幅、位姿容差要写理由)
第 3 节 触觉与力 R3.1-R3.6(力是相对基线的增量、fnormal 压为正、轴向映射必须实测、
接触布尔要用超时、force_scale 口径、仿真图像只能占位)
第 4 节 仿真 R4.1-R4.10(唯一 site.yaml、world 里写 plugin 会挤掉默认系统、搜索路径要在
launch 前设好、上游 description 不许改、场景走 scene.yaml、校验要逐碰撞体、
不用"有话题就算通"验收、停仿真用 stop_sim_gz.sh)
第 5 节 代码与测试 R5.1-R5.7(纯逻辑抽纯函数、校验要有测试证明有效、不许把降级写成通过、
解析外部输出先确认真实格式、删除前扫全仓库引用、别在中文路径 colcon build、真机仿真产物隔离)
第 6 节 文档与提交 R6.1-R6.4(文档与实现不一致立刻修文档、每份文档说明记录什么事实、
结论数字要可复现、提交信息讲为什么并附实测数据)
第 7 节 改动前检查清单
第 8 节 历史反例清单——按"你看到的症状"查表,不是按道理排序
第 9 节 给 AI 的具体指令
.cursor/rules/*.mdc 三份压缩版(常驻)
framework-core.mdc alwaysApply,元规则 + 检查清单 + 禁止项
sim-gazebo.mdc globs=devices/sim/**,src/arm_sim_gazebo/**
config-and-devices.mdc globs=config/**,devices/**,models/**,src/arm_interfaces/**
顺手修掉文档与实现不一致(R6.1)
docs/WORKSPACE.md 此前【完全没有 devices/ 这一层】——通用化落点却没进目录总表。
补 devices/ 一节(kind/brand 目录约定、registry.yaml 与 manifest.yaml 分工、
assets[].required 缺失会报错),补 build_sim/install_sim/log_sim 与 .cursor/rules/,
并把 FRAMEWORK_RULES.md 标为 docs/ 首行
README.md 文档表首行加 FRAMEWORK_RULES.md
顺手删死代码(R5.5:先扫引用)
src/arm_interfaces/arm_interfaces/paths.py 删 assets_root()
它返回 ros2_root()/"assets",而仓库里从来没有 assets/ 这个目录——实际权重树是 models/,
且 models/ 的定位已由 arm_application.weights_layout.default_weights_root() 负责
(带 SITE_WEIGHTS_ROOT 覆盖,且能从 install/ 反查仓库根)。
两个实现并存违反 M2,且这个还指向不存在的路径,属于"照着名字用就踩空"。
src/arm_interfaces/test/test_real_devices.py 从清环境变量列表里去掉 SITE_ASSETS_ROOT
验收
src/arm_application 233 passed / arm_driver_flexiv 69 / arm_interfaces 46 /
arm_kinematics 24 / arm_sim_gazebo 236 = 608 passed
(flexiv_rdk 的 test 需装厂商 flexivrdk 包,仿真环境无此依赖,与本改动无关)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
description: 配置与设备模块的改动规则(site.yaml 分层、devices 布局、权重归位)
|
||||||
|
globs: config/**,devices/**,models/**,src/arm_interfaces/**
|
||||||
|
---
|
||||||
|
|
||||||
|
# 配置与设备改动规则
|
||||||
|
|
||||||
|
权威全文:`docs/FRAMEWORK_RULES.md` 第 1 节(R1.1–R1.5)。
|
||||||
|
|
||||||
|
## 配置分层
|
||||||
|
|
||||||
|
- 优先级固定:**命令行 > `config/site.yaml` > 包内默认值**。
|
||||||
|
- `config/site.yaml` 是站点数据(SN / IP / 序列号 / 锁高 / 权重路径),**不进 git**;
|
||||||
|
改过它之后必须重跑 `python3 scripts/gen_site_example.py`,否则模板漂移
|
||||||
|
(有一条 parity 测试会红)。
|
||||||
|
- **禁止硬编码站点值到代码里**。历史反例:15 处硬编码的 `Rizon4-063989`,
|
||||||
|
换一台臂就会静默连错机器。
|
||||||
|
- 新增 `site.yaml` 键时二选一:**真的接线**,或在启动时**告警**。
|
||||||
|
声明了却没接线、也不告警,是最难查的一类问题(用户改了配置毫无反应)。
|
||||||
|
注意 `wired:` 只是 manifest 里的注释性约定,`arm_interfaces.manifest` 不解析它 ——
|
||||||
|
保障要靠代码里的告警函数(如 `sim_site.unwired_warnings()`),别指望清单。
|
||||||
|
|
||||||
|
## 设备模块布局
|
||||||
|
|
||||||
|
```
|
||||||
|
devices/<kind>/<brand>/
|
||||||
|
├── manifest.yaml # id/label/kind/vendor/version + assets + driver + capabilities + models + config_schema
|
||||||
|
├── driver/ # 与厂商 SDK 对接的实现(可替换)
|
||||||
|
└── models/<型号>/ # 该品牌的型号专属文件
|
||||||
|
```
|
||||||
|
|
||||||
|
- `kind` 用固定那几个(`robots`/`grippers`/`cameras`/`sensors`/`sim`),目录名要与
|
||||||
|
`arm_interfaces.KIND_FOLDERS` 一致。
|
||||||
|
- **换品牌 = 新增目录**,不是改现有目录;上层按 `manifest.yaml` 选型,不认品牌名。
|
||||||
|
- `assets[]` 里标 `required: true` 的物料缺失时,报错信息要给出**格式与命名要求**,
|
||||||
|
不能只说"文件不存在"。
|
||||||
|
|
||||||
|
## 权重
|
||||||
|
|
||||||
|
- 二进制(`.pt/.onnx/.engine`)**不进 git**,`.labels.yaml`(文本标签)**要进**。
|
||||||
|
- 转换链:`models/staging/` → `weights.sh convert` → 自动改名归位 + 固化标签。
|
||||||
|
- 配置里指向不存在的模型(悬空引用)必须修掉:`bash scripts/weights.sh doctor`。
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
description: 本仓库的框架硬规矩(三条元规则 + 改动前检查清单),改任何代码前先读
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# 框架硬规矩(权威全文:`docs/FRAMEWORK_RULES.md`,规则编号 R1.1…R8)
|
||||||
|
|
||||||
|
这不是风格建议。违反它们的改动会在真机上以**不报错但结果是错的**方式失败,
|
||||||
|
而防这件事是这个仓库花最多代价在做的事。要绕开某条规则,先改文档并写清理由。
|
||||||
|
|
||||||
|
## 三条元规则
|
||||||
|
|
||||||
|
1. **静默失效是头号敌人。** 任何检查/校验都必须**能真的失败** ——
|
||||||
|
写完先问"我故意弄坏它,这个检查会红吗?"。不会红 = 等于没检查。
|
||||||
|
宁可当场报错,也不要"宽容降级":降级会把"没检查"伪装成"检查通过"。
|
||||||
|
校验代码自己也会静默失效(外部工具会用 `__default__` 这类**假值**填缺失字段)。
|
||||||
|
2. **单一数据源。** 同一事实只写一处,其余派生;能算的不要抄。
|
||||||
|
必须抄的(如仿真侧的像素门槛)要有人守着 —— 脚本交叉核对真机配置,不一致判失败。
|
||||||
|
3. **口径必须显式。** 单位/坐标系/符号/参考基准写下来,换算只做一次、放在适配层。
|
||||||
|
|
||||||
|
## 改动前必答
|
||||||
|
|
||||||
|
- 这个事实仓库里有几处在写?其余的是派生的吗?
|
||||||
|
- 我加的配置键真的接线了吗?没接线会在启动时告警吗?
|
||||||
|
- 涉及的检查有**负向测试**吗(故意弄坏能红)?
|
||||||
|
- 解析外部输出前,我看过它的**真实**输出吗?(文本格式与直觉不一致是常态)
|
||||||
|
- 我改的是上游第三方文件吗?→ 不许直接改,走归一化/适配层。
|
||||||
|
- 文档里的"已就绪 / 未接线"标注与实现还一致吗?不一致立刻改文档(以实现为准)。
|
||||||
|
|
||||||
|
## 严禁
|
||||||
|
|
||||||
|
- 硬编码站点数据(SN / IP / 相机序列号 / 锁高 / 权重路径)—— 一律走 `config/site.yaml`。
|
||||||
|
- 用"有图像 / 有话题 / TF 查得到"作为验收 —— 必须断言物理量。
|
||||||
|
- 把"降级跳过"写成"通过"。
|
||||||
|
- 在中文路径下 `colcon build`。
|
||||||
|
- 提交信息只写"改了什么"而不写"为什么"(中文,附实测验收数据)。
|
||||||
|
|
||||||
|
## 出问题先查症状表
|
||||||
|
|
||||||
|
`docs/FRAMEWORK_RULES.md` 第 8 节按**症状**列出全部历史反例
|
||||||
|
("三维点偏半米"、"深度差 1000 倍"、"夹爪 active 但不动"、"脚本说已停止进程还在"…),
|
||||||
|
先按症状查表,再动手。
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
description: Gazebo 仿真的改动规则(静默失效高发区:路径/内参/接触力/描述归一化)
|
||||||
|
globs: devices/sim/**,src/arm_sim_gazebo/**
|
||||||
|
---
|
||||||
|
|
||||||
|
# 仿真改动规则
|
||||||
|
|
||||||
|
权威全文:`docs/FRAMEWORK_RULES.md` 第 4 节;平台手册:`devices/sim/gazebo/README.md`。
|
||||||
|
R4.1–R4.10 全部适用,以下是**最容易踩的几条**:
|
||||||
|
|
||||||
|
- **world 里声明任何一个 `<plugin>` 都会挤掉默认系统**(Physics / UserCommands /
|
||||||
|
SceneBroadcaster)。少了 UserCommands,模型根本没进 Gazebo,而 `create` **仍然打印**
|
||||||
|
`OK creation of entity`。要加插件时必须把这几个系统一起显式声明。
|
||||||
|
- **插件/资源搜索路径必须在 `ros2 launch` 之前设好**(conda 激活不会加
|
||||||
|
`$CONDA_PREFIX/lib`)。不要绕过 `scripts/sim_env.sh` 直接 launch。
|
||||||
|
- **不许改上游描述文件**(`flexiv_description` 等):`<mimic>` 会被 `sdformat` 丢弃、
|
||||||
|
零质量 link 会出 NaN 惯性、无质量根 link 会连子树被丢。一律走启动时归一化。
|
||||||
|
- **gz 发的 `camera_info` 是错的**(恒为 60° 视场的默认值),内参必须按
|
||||||
|
`cameras.yaml` 的 `horizontal_fov` 与图幅重算并覆盖。
|
||||||
|
- **深度单位**:gz 是 `32FC1` 米,真机是 `16UC1` 毫米。换算只做一次(适配器)。
|
||||||
|
- **相机 `frame_id` 必须是光学系**;`rpy` 的复合顺序是 `Rz·Ry·Rx`。
|
||||||
|
- **触觉**:轴向映射必须实测(不许从 URDF 推导);接触布尔必须用超时判据
|
||||||
|
(接触消息是事件式的,没有"离开"事件);`force_scale` 决定仿真与真机能否共用阈值。
|
||||||
|
- **验收不用"有图像 / TF 查得到"**:跑 `scripts/check_sim_cameras.py` 与
|
||||||
|
`scripts/check_sim_scene.py`(判据是物理量:反投影命中平面、像素门槛、位姿一致)。
|
||||||
|
- **停仿真用 `scripts/stop_sim_gz.sh`**,不要 `pkill -f`(会先杀掉调用它的 shell)。
|
||||||
|
给栈里加了新进程时,记得加进该脚本的模式表 —— 它末尾会复查残留,漏项会报出来。
|
||||||
|
- **模型约定**:原点在**底面中心**、单位米、目录名 = `<model name>` =
|
||||||
|
`model.config` 的 `<name>` = 推理类别名;碰撞体用简单几何,`visual` 可以加
|
||||||
|
只服务于检测的细节(如工件上的亮环),但 `visual` **名字有意义**,改名字要同步改
|
||||||
|
`sim_scene.features_from_workpiece`。
|
||||||
@@ -62,6 +62,7 @@ bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control
|
|||||||
|
|
||||||
| 文档 | 内容 |
|
| 文档 | 内容 |
|
||||||
|------|------|
|
|------|------|
|
||||||
|
| [docs/FRAMEWORK_RULES.md](docs/FRAMEWORK_RULES.md) | **框架规则**(改代码前先读;含历史反例的症状表) |
|
||||||
| [docs/WORKSPACE.md](docs/WORKSPACE.md) | 每个目录 / 脚本 / 包 |
|
| [docs/WORKSPACE.md](docs/WORKSPACE.md) | 每个目录 / 脚本 / 包 |
|
||||||
| [docs/QUICKSTART.md](docs/QUICKSTART.md) | 环境与第一次跑通 |
|
| [docs/QUICKSTART.md](docs/QUICKSTART.md) | 环境与第一次跑通 |
|
||||||
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 进程与几何 |
|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 进程与几何 |
|
||||||
|
|||||||
@@ -0,0 +1,414 @@
|
|||||||
|
# 框架规则(`docs/FRAMEWORK_RULES.md`)
|
||||||
|
|
||||||
|
> **这份文档给两类读者看:**
|
||||||
|
> - **改这个仓库的 AI**:开工前先读,逐条遵守。这些不是风格建议,是**硬规矩** ——
|
||||||
|
> 违反它们的改动会在真机上以"不报错但结果是错的"方式失败,而这正是本仓库
|
||||||
|
> 花最多代价在防的事。
|
||||||
|
> - **review 人**:每条例规都有编号(`R1`…),可以直接引用,例如
|
||||||
|
> "这条违反 R7.2"。
|
||||||
|
>
|
||||||
|
> 配套:`.cursor/rules/*.mdc` 是给 Cursor 的常驻规则(自动注入),内容是本文件的
|
||||||
|
> 压缩版;本文件是**唯一权威**,两者冲突时以本文件为准。
|
||||||
|
>
|
||||||
|
> 规则是可以改的 —— 但必须改这里,而不是在代码里偷偷绕开。改规则时写清**为什么**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 三条元规则
|
||||||
|
|
||||||
|
其余所有规则都是从这三条推出来的。拿不准某件事该怎么做时,回到这三条。
|
||||||
|
|
||||||
|
### M1. 静默失效是头号敌人
|
||||||
|
|
||||||
|
本仓库遇到的大部分问题都不报错:**话题在、图像有、TF 查得到、日志干净,结果是错的。**
|
||||||
|
所以:
|
||||||
|
|
||||||
|
- 任何"检查 / 校验 / 验收"都必须**能真的失败**。写完先问一句:
|
||||||
|
"我故意把它弄坏,这条检查会红吗?" 不会红 = 这条检查等于没有(见 `R5.2`)。
|
||||||
|
- 宁可**当场报错**,也不要"宽容降级"。降级会把"没检查"伪装成"检查通过"。
|
||||||
|
真实案例:位姿解析函数格式对不上,于是永远返回空、调用方按设计"跳过位姿核对",
|
||||||
|
结果位姿检查从来没跑过,而日志只说"跳过"(见 `R5.4`)。
|
||||||
|
- 校验代码自己也会静默失效。凡是"检查 X 是否为空"的地方,都要问:
|
||||||
|
"它会不会被填成一个**假值**?"(真实案例:`sdformat` 用 `__default__` /
|
||||||
|
`__default_topic__` 填缺失字段,见 `R4.6`)
|
||||||
|
|
||||||
|
### M2. 单一数据源
|
||||||
|
|
||||||
|
同一个事实**只允许写一处**,其余全部派生。
|
||||||
|
|
||||||
|
- 能算出来的不要抄(内参由视场与图幅算、相机位姿从 world 文件解析)。
|
||||||
|
- 必须抄的(如仿真侧要提前知道像素门槛)**必须有人守着**:
|
||||||
|
用脚本交叉核对真机配置,不一致就判失败(`R2.6`)。
|
||||||
|
- 同一份事实出现两处时,一定有一处会先过期。过期的那处不会报错。
|
||||||
|
|
||||||
|
### M3. 口径必须显式
|
||||||
|
|
||||||
|
**单位、坐标系、符号、参考基准**必须写下来,并且**只在一个地方换算**。
|
||||||
|
|
||||||
|
- 毫米还是米?相对零还是相对基线?压下去为正还是为负?
|
||||||
|
这些都不写清就会"数值看起来合理、语义是错的"。
|
||||||
|
- 换算只做一次,放在适配层(相机适配器、触觉适配器),下游不许再猜。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 配置与数据
|
||||||
|
|
||||||
|
### R1.1 分层优先级固定:命令行 > `site.yaml` > 包内默认
|
||||||
|
|
||||||
|
`config/site.yaml` 是**站点数据**(SN/IP/相机序列号/锁高/权重路径),**不进 git**;
|
||||||
|
包内 `config/*.yaml` 是默认值,被 site 覆盖。
|
||||||
|
|
||||||
|
**禁止**在代码里硬编码站点值。真实反例:15 处硬编码的 `Rizon4-063989`
|
||||||
|
(见 `R8` 的清单),换一台臂就会静默连错机器。
|
||||||
|
|
||||||
|
### R1.2 新增 `site.yaml` 键必须二选一:**接线** 或 **明确告警**
|
||||||
|
|
||||||
|
声明了却没接线的键是最难查的一类问题:用户按文档改了 `site.yaml`,行为毫无变化,
|
||||||
|
也不报错。所以每个新键都要么真的接线,要么在启动时**告警**(`wired: false` +
|
||||||
|
`unwired_warnings()` 那套做法)。
|
||||||
|
|
||||||
|
> ⚠️ 现状:`wired:` 目前只是 `manifest.yaml` 里的**注释性约定**,
|
||||||
|
> `arm_interfaces.manifest` 并不解析它。所以"未接线"的保障实际来自
|
||||||
|
> `sim_site.unwired_warnings()` 这类**代码里的告警**,不是清单。
|
||||||
|
> 加键时不要指望清单替你守法。
|
||||||
|
|
||||||
|
### R1.3 设备一律走 `devices/<kind>/<brand>/`,并带 `manifest.yaml`
|
||||||
|
|
||||||
|
```
|
||||||
|
devices/<kind>/<brand>/
|
||||||
|
├── manifest.yaml # id/label/kind/vendor/version + assets + driver + capabilities + models + config_schema
|
||||||
|
├── driver/ # 与厂商 SDK 对接的实现(可替换)
|
||||||
|
└── models/<型号>/ # 该品牌的型号专属文件
|
||||||
|
```
|
||||||
|
|
||||||
|
- `kind` 用固定的那几个(`robots` / `grippers` / `cameras` / `sensors` / `sim`),
|
||||||
|
目录名要与 `arm_interfaces.KIND_FOLDERS` 一致。
|
||||||
|
- **换品牌 = 新增一个目录**,不是改现有目录。上层按 `manifest.yaml` 选型,
|
||||||
|
不认具体品牌名。
|
||||||
|
- `assets[].required: true` 的物料缺失时,框架会报错并给出**格式与命名要求**
|
||||||
|
(不只是"文件不存在")。
|
||||||
|
|
||||||
|
### R1.4 权重一律走 `models/`,规则在 `models/README.md`
|
||||||
|
|
||||||
|
- 二进制(`.pt/.onnx/.engine`)**不进 git**;`.labels.yaml`(文本标签)**要进**。
|
||||||
|
- 转换链:`models/staging/` 放待转换的原始文件 → `weights.sh convert` →
|
||||||
|
自动改名归位到 `models/<分组>/` 并固化标签。
|
||||||
|
- `models/**/best.pt` 这类**悬空引用**(配置指向不存在的文件)必须修掉或改成实际存在的,
|
||||||
|
查法:`weights.sh doctor`。
|
||||||
|
|
||||||
|
### R1.5 什么入库、什么不入库
|
||||||
|
|
||||||
|
| 入库 | 不入库 | 理由 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `src/` `scripts/` `docs/` `devices/` `config/site.example.yaml` | `config/site.yaml` | 站点数据含本机 IP/序列号 |
|
||||||
|
| `models/README.md`、`models/**/*.labels.yaml` | `models/**/*.{pt,onnx,engine}` 等 | 二进制体积大、每台机器重转 |
|
||||||
|
| `handeye/poses/` `handeye/results/` | `handeye/tmp/` | 示教点与标定结果是用真机时间换来的**资产**,要能回滚 |
|
||||||
|
|
||||||
|
`config/site.yaml` 改了以后**必须**重跑 `python3 scripts/gen_site_example.py`,
|
||||||
|
否则模板会漂移(有一条 parity 测试会红)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 几何、单位与坐标系
|
||||||
|
|
||||||
|
### R2.1 单位一律米与弧度
|
||||||
|
|
||||||
|
SDF / URDF / 几何计算里不允许出现毫米。用毫米建模的 mesh 必须先缩放。
|
||||||
|
|
||||||
|
**例外且必须显式**:真机 RealSense 的深度图是 `16UC1` **毫米**(见 `R2.3`)。
|
||||||
|
|
||||||
|
### R2.2 相机的 `frame_id` 必须是**光学系**
|
||||||
|
|
||||||
|
ROS 的 `image` / `camera_info` 必须用光学约定(Z 朝前 / X 朝右 / Y 朝下,REP-103/105),
|
||||||
|
不能用相机本体系(X 朝前 / Y 朝左 / Z 朝上)。两者差 `rpy=(-90°, 0, -90°)`。
|
||||||
|
|
||||||
|
盖错 frame 的后果:`tf2` 查询**能查到、不报错**,但反投影出的三维点整体转错,
|
||||||
|
而单目预览完全看不出来。
|
||||||
|
|
||||||
|
### R2.3 深度:真机是毫米,Gazebo 是米 —— 必须真换算
|
||||||
|
|
||||||
|
| 来源 | 编码 | 单位 |
|
||||||
|
|------|------|------|
|
||||||
|
| 真机 RealSense aligned_depth | `16UC1` | 毫米,`0` = 无读数 |
|
||||||
|
| Gazebo 深度相机 | `32FC1` | 米 |
|
||||||
|
|
||||||
|
直接改个名字转发,应用侧按毫米解释会整体差 1000 倍。换算只做一次,
|
||||||
|
放在 `sim_camera_adapter`。
|
||||||
|
|
||||||
|
### R2.4 `rpy` 的复合顺序是 `Rz·Ry·Rx`
|
||||||
|
|
||||||
|
不是 `Ry·Rz·Rx`。单轴旋转看不出来,多轴组合才暴露。
|
||||||
|
|
||||||
|
**URDF 相机的视线是本体 X 轴**("X 朝前"约定)。所以"先俯 50° 再偏航 180°"
|
||||||
|
会把视线翻到**背面**。D435 就这么翻过:背对台面只看得见地面,什么都不报,
|
||||||
|
**而且几何自洽**(点确实落在真实地面上)。
|
||||||
|
|
||||||
|
### R2.5 Gazebo 发的 `camera_info` 不可信
|
||||||
|
|
||||||
|
Fortress 恒发 `fx=fy=277, cx=160, cy=120`(它的默认 60° 视场 + 半分辨率主点),
|
||||||
|
与真正渲染用的视场无关。内参必须由 `cameras.yaml` 的 `horizontal_fov` 与图幅**算出**,
|
||||||
|
并覆盖 gz 发的那份。
|
||||||
|
|
||||||
|
### R2.6 像素门槛与**图幅**绑定
|
||||||
|
|
||||||
|
`hole_detect.yaml` 的门槛(`hough_min_radius`、`hole_distance_min`)是**像素**量,
|
||||||
|
只在特定分辨率下有意义。改图幅要同时改三处(`cameras.yaml`、SDF、
|
||||||
|
必要时重算真机门槛),启动时会比对并告警。
|
||||||
|
|
||||||
|
### R2.7 位姿检查要有容差,且容差要说明理由
|
||||||
|
|
||||||
|
物体的落座微动是正常的。卡到 `1e-6` 只会得到永远为真的告警,人就会把检查关掉。
|
||||||
|
默认 5mm 起步(见 `sim_scene.pose_mismatches`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 触觉与力
|
||||||
|
|
||||||
|
### R3.1 力的语义是"相对静止基线的增量"
|
||||||
|
|
||||||
|
真机 Orisys 输出的是形变任意单位(非牛顿),且基线会漂移,所以全链路
|
||||||
|
(含下游阈值)都以**相对基线的增量**为语义。仿真必须做同样的事:
|
||||||
|
不减基线,下游阈值就对不上。
|
||||||
|
|
||||||
|
基线要慢跟踪(只在确认无接触时更新),否则换姿态后的残余会被当成接触。
|
||||||
|
|
||||||
|
### R3.2 `fnormal` 压下去为正,且 clamp 到非负
|
||||||
|
|
||||||
|
两指符号一致。这条是**实测**结论(`config/tactile.yaml` 里有实测记录),
|
||||||
|
不是从 URDF 推导出来的。
|
||||||
|
|
||||||
|
### R3.3 轴向映射必须实测,不许推导
|
||||||
|
|
||||||
|
哪个轴是"指腹法向"要用实验确定(夹一块大平面薄板等稳态)。猜错不会报错,
|
||||||
|
只会让所有力的语义静默错位。实测方法与结果记在 `config/tactile.yaml`。
|
||||||
|
|
||||||
|
用小方块做这个实验是错的:点/边接触会让法向偏、摩擦混进来。
|
||||||
|
|
||||||
|
### R3.4 接触布尔必须用**超时**判据
|
||||||
|
|
||||||
|
Gazebo 的接触消息是**事件式**的:接触时发,离开时**一条都不发**。
|
||||||
|
写成"收到=接触 / 没收到=不管",离开工件后 `contact` 会永远停在 `True`。
|
||||||
|
|
||||||
|
判据 = "最近 `contact_timeout_s` 内收到过消息"。
|
||||||
|
|
||||||
|
### R3.5 仿真与真机的力的量级不同,`force_scale` 是必须确认的口径
|
||||||
|
|
||||||
|
仿真输出 = Gazebo 牛顿 × `force_scale`。`force_scale = 1.0` 时输出牛顿,
|
||||||
|
**不能**直接套用真机那套阈值(250/600)。位置控制器是"刚性地压进去"的,
|
||||||
|
实测能到几百。
|
||||||
|
|
||||||
|
选错口径不报任何错:阈值只是"不触发"或"一碰就炸",看起来像算法问题。
|
||||||
|
|
||||||
|
### R3.6 视触觉的**图像**在仿真里只能是占位
|
||||||
|
|
||||||
|
仿真没有弹性体成像,深度图/力矢量图只能是空/NaN。话题与消息结构要保留
|
||||||
|
(下游不用改代码),但要打一次明确日志说明它不是真实数据。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 仿真(Gazebo)
|
||||||
|
|
||||||
|
### R4.1 仿真与真机**共用同一套 `site.yaml` 参数**
|
||||||
|
|
||||||
|
任何"仿真专用参数"都是未来"仿真里调好的值搬到真机不生效"的隐患。
|
||||||
|
只允许**物理引擎自身的**参数(重力、步长)放在 `sim.*` 下。
|
||||||
|
|
||||||
|
### R4.2 `world` 里声明任何一个 `<plugin>` 都会挤掉默认系统
|
||||||
|
|
||||||
|
声明了任何插件后,Gazebo **不再**加载默认的 Physics / UserCommands / SceneBroadcaster。
|
||||||
|
|
||||||
|
少了 UserCommands 的后果最阴:`/world/<名>/create` 服务不存在 → 模型根本没进 Gazebo,
|
||||||
|
而 `create` **仍然打印** `OK creation of entity`。
|
||||||
|
|
||||||
|
所以这些系统必须在 world 里**显式声明**(含 `contact-system`、`forcetorque-system`)。
|
||||||
|
|
||||||
|
### R4.3 插件与资源的搜索路径必须在 `ros2 launch` **之前**设好
|
||||||
|
|
||||||
|
conda 激活不会往 `LD_LIBRARY_PATH` 加东西,而 gz 的插件搜索路径需要
|
||||||
|
`$CONDA_PREFIX/lib`。症状是 `couldn't find shared library` 而库其实就在那儿。
|
||||||
|
|
||||||
|
**不要绕过 `scripts/sim_env.sh` 直接 `ros2 launch`。**
|
||||||
|
|
||||||
|
### R4.4 上游描述(`flexiv_description` 等)**不许直接改**
|
||||||
|
|
||||||
|
第三方包一律在**启动时归一化**,不在上游文件里下手:
|
||||||
|
|
||||||
|
- 缺 `<inertial>` / 零质量 link → NaN 惯性、物体消失
|
||||||
|
- `<mimic>` 标签被 `sdformat` **丢弃** → 夹爪"控制器 active 但手指不动"
|
||||||
|
- 无质量的根 link(world link)会**连整棵子树被静默丢掉**
|
||||||
|
|
||||||
|
改上游还有个额外代价:下次 `fetch_flexiv.sh` 会把你的改动覆盖掉。
|
||||||
|
|
||||||
|
### R4.5 场景物体走 `scene.yaml`,不进 world
|
||||||
|
|
||||||
|
`world` 是"环境"(地面/台面/相机/系统插件),`scene.yaml` 是"这一批活做什么"。
|
||||||
|
动 world 的风险面太大(`R4.2`)。
|
||||||
|
|
||||||
|
模型约定:**原点在底面中心**、单位米、目录名 = `<model name>` = `model.config`
|
||||||
|
里的 `<name>` = 推理类别名。
|
||||||
|
|
||||||
|
### R4.6 模型/场景校验必须逐**碰撞体**、三维判
|
||||||
|
|
||||||
|
- 不能拿总包围盒判重叠:正常装配里本来就有嵌套(规立在规座方孔里),
|
||||||
|
粗判会把正常嵌套报成碰撞,逼着人把检查关掉。
|
||||||
|
- 恰好贴合不算重叠(规的台肩正好坐在规座顶面上)。
|
||||||
|
- 变换必须用"中心 + 半宽":对 min/max 取绝对值再对称化会把偏移量吃掉、
|
||||||
|
盒子凭空变大。
|
||||||
|
- 校验要用**从 SDF 解析出来的**几何,不能在 `scene.yaml` 里再抄一份尺寸。
|
||||||
|
|
||||||
|
### R4.7 建模:碰撞体用简单几何,视觉可以加"只给算法看"的细节
|
||||||
|
|
||||||
|
`<collision>` 决定物理,用 box/cylinder;`<visual>` 决定相机看见什么,
|
||||||
|
可以加亮环、色块这类只服务于检测的东西。
|
||||||
|
|
||||||
|
工件的碰撞体是方孔、视觉是圆环,**这是刻意的**:真机的 HoughCircles 靠亮环找孔,
|
||||||
|
只按碰撞体建视觉会让孔检测在仿真里永远失败,而那看起来像算法问题。
|
||||||
|
|
||||||
|
> 由此产生的约定:`visual` 的**名字有意义**(`insert_*_ring` / `center_bore`),
|
||||||
|
> `sim_scene.features_from_workpiece` 按名字前缀读特征。改名字要同步改那个函数。
|
||||||
|
|
||||||
|
### R4.8 夹爪/被夹物体的接触刚度要按"位置控制"的实际行为调
|
||||||
|
|
||||||
|
位置控制器会"刚性地压进去",默认刚性接触会把物体弹飞。需要放软时要**写明理由**
|
||||||
|
(照抄现成参数前先想清楚你的物体是不是也要被夹)。
|
||||||
|
|
||||||
|
### R4.9 不用"有图像 / 有话题 / TF 查得到"验收
|
||||||
|
|
||||||
|
这些只能证明"链路通"。验收必须是对**物理量**的断言:
|
||||||
|
|
||||||
|
| 工具 | 判据 |
|
||||||
|
|------|------|
|
||||||
|
| `scripts/check_sim_cameras.py` | 深度反投影必须命中已知平面(地面 z=0 / 台面 z=0.40) |
|
||||||
|
| `scripts/check_sim_scene.py` | 摆放自洽 + 像素门槛与真机一致 + 成像几何达标 + 手外相机**完整**看得见工件 |
|
||||||
|
|
||||||
|
### R4.10 停仿真用 `scripts/stop_sim_gz.sh`,不要 `pkill -f`
|
||||||
|
|
||||||
|
`pkill -f` 会先匹配到**调用它的那个 shell**(它的命令行里含同样的字符串),
|
||||||
|
表现是"命令秒返回、什么都没停"。脚本按进程树精确识别,并在杀完之后**复查残留**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 代码与测试
|
||||||
|
|
||||||
|
### R5.1 纯逻辑要抽成纯函数,才可能被钉住
|
||||||
|
|
||||||
|
把"数据变换"从 ROS 节点里抽出来(`sim_cameras`、`sim_tactile`、`sim_scene`),
|
||||||
|
因为静默失效点的错误**不会在运行时暴露**:轴向映射反了照样"有力、会响应、
|
||||||
|
松开归零"。抽出来才有单测可写。
|
||||||
|
|
||||||
|
### R5.2 每条"检查/校验"都要有能证明它有效的测试
|
||||||
|
|
||||||
|
尤其是**负向**测试:故意造出坏数据,断言检查会报错。
|
||||||
|
只测"好数据通过"的测试,无法区分"检查有效"和"检查恒真"。
|
||||||
|
|
||||||
|
### R5.3 不许把"降级跳过"写成"通过"
|
||||||
|
|
||||||
|
调用方必须把降级当作**告警**打印出来并说明原因。真实反例见 `R5.4`。
|
||||||
|
|
||||||
|
### R5.4 解析外部输出:先确认真实格式,再写解析器
|
||||||
|
|
||||||
|
外部工具(`ign`、`sdformat`、厂商 SDK)的文本输出**没有稳定契约**,
|
||||||
|
而且格式与直觉不一致是常态。
|
||||||
|
|
||||||
|
先用真实输出跑一遍看清楚了再写解析;解析失败要返回"未知"而不是"零"。
|
||||||
|
真实反例:按 `x: N` 键值对写解析器,而 Fortress 实际输出是方括号两行,
|
||||||
|
于是位姿检查从来没跑过。
|
||||||
|
|
||||||
|
### R5.5 删除 / 重构前先做全仓库引用扫描
|
||||||
|
|
||||||
|
不靠印象。反例:一个 `assets_root()` 函数指向从未存在的 `assets/` 目录、
|
||||||
|
零调用者,长期没人发现(见 `R8`)。
|
||||||
|
|
||||||
|
### R5.6 不要在中文路径下 `colcon build`
|
||||||
|
|
||||||
|
ROS 构建对非 ASCII 路径会出各种问题。工作目录保持 ASCII(推荐 `~/ros2`)。
|
||||||
|
|
||||||
|
### R5.7 真机与仿真的构建产物必须隔离
|
||||||
|
|
||||||
|
`build/ install/ log/` 是真机;`build_sim/ install_sim/ log_sim/` 是仿真
|
||||||
|
(`scripts/build_sim.sh`)。两套并存是刻意的:同一份源码,两个环境的依赖路径不同,
|
||||||
|
共用 `install/` 会互相覆盖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 文档与提交
|
||||||
|
|
||||||
|
### R6.1 文档与实现不一致时:以实现为准,**立刻**修文档
|
||||||
|
|
||||||
|
文档里"已就绪 / 未接线"必须**如实**标注。看到 ✘ 不要凭印象改成 ✔。
|
||||||
|
|
||||||
|
### R6.2 每份文档要有"读法"和"这份文档记录什么事实"的说明
|
||||||
|
|
||||||
|
本仓库的文档面向"明天就要动手的人",所以要写清:先读哪节、哪些是硬事实、
|
||||||
|
哪些是踩过的坑、怎么验证。
|
||||||
|
|
||||||
|
### R6.3 结论性数字必须能复现
|
||||||
|
|
||||||
|
写进文档的性能/几何数字(如"台面命中 11.9%")要么给出命令,要么给出脚本名。
|
||||||
|
|
||||||
|
### R6.4 提交信息:中文,讲**为什么**,附实测验收数据
|
||||||
|
|
||||||
|
格式:`<阶段号> <一句话结果>` + 正文说明"问题是什么、为什么这么改、怎么验证的"。
|
||||||
|
修静默失效时,把"症状"写进去 —— 那是后来人搜索时唯一能匹配上的东西。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 改动前的检查清单
|
||||||
|
|
||||||
|
改任何东西之前/之后,逐条过一遍:
|
||||||
|
|
||||||
|
| # | 检查 | 对应规则 |
|
||||||
|
|---|------|----------|
|
||||||
|
| 1 | 我要改的这个事实,仓库里有几处在写?其余的是派生的吗? | M2 |
|
||||||
|
| 2 | 我加/改的配置键,真的接线了吗?没接线会告警吗? | R1.2 |
|
||||||
|
| 3 | 涉及单位/坐标系/符号吗?换算只做了一次吗?写下来了吗? | M3、R2 |
|
||||||
|
| 4 | 我加的检查,故意弄坏数据它会红吗?有负向测试吗? | M1、R5.2 |
|
||||||
|
| 5 | 解析外部输出前,我看过真实输出了吗? | R5.4 |
|
||||||
|
| 6 | 我改的是上游第三方文件吗?(应该走归一化/适配层) | R4.4 |
|
||||||
|
| 7 | 改完跑了验收工具吗(`check_sim_cameras` / `check_sim_scene`)? | R4.9 |
|
||||||
|
| 8 | 文档里的状态标注与实现还一致吗? | R6.1 |
|
||||||
|
| 9 | 提交信息里有"为什么"和实测数据吗? | R6.4 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 历史反例清单(别再犯)
|
||||||
|
|
||||||
|
按"症状"排序 —— 因为出问题时你是先看到症状,再回来查这张表。
|
||||||
|
|
||||||
|
| 症状 | 真实原因 | 规则 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 三维点整体偏半米,但单目预览正常 | 用了 gz 发的 `camera_info`(恒为 60° 视场的默认值) | R2.5 |
|
||||||
|
| 深度差 1000 倍 / `frombuffer` 读出垃圾 | 把 gz 的 `32FC1` 米直接当毫米用 | R2.3 |
|
||||||
|
| 反投影的点转到别处,`tf2` 却能查到 | `frame_id` 盖成了相机本体系 | R2.2 |
|
||||||
|
| 相机背对目标,只看得见地面 | `rpy` 复合顺序理解错 / 视线是本体 X 轴 | R2.4 |
|
||||||
|
| 模型"根本没进 Gazebo",但 `create` 打印成功 | world 里声明了插件,挤掉了 UserCommands | R4.2 |
|
||||||
|
| `gz_ros2_control` 报找不到共享库,但 `ldd` 全齐 | 插件搜索路径没含 `$CONDA_PREFIX/lib` | R4.3 |
|
||||||
|
| 物体一 spawn 就消失 | 缺 `<inertial>` / 零质量 link → 惯性 NaN | R4.4 |
|
||||||
|
| 夹爪控制器 active 但手指不动 | `<mimic>` 被 `sdformat` 丢弃 | R4.4 |
|
||||||
|
| 世界相机与它的话题一起消失 | 挂在无质量的 world link 上,整棵子树被丢 | R4.4 |
|
||||||
|
| `contact` 离开工件后永远是 `True` | 接触消息是事件式的,没有"离开"事件 | R3.4 |
|
||||||
|
| 力的语义全错,但"有力、会响应、松开归零" | 轴向映射猜的,没实测 | R3.3 |
|
||||||
|
| 明明夹住了,`fnormal` 恒为 0 | 基线把真实接触吃掉了(基线更新条件写错) | R3.1 |
|
||||||
|
| 阈值永不触发或一碰就炸 | 仿真牛顿与真机任意单位混用(`force_scale` 没标定) | R3.5 |
|
||||||
|
| 位姿检查从来没跑过,日志只说"跳过" | 解析器格式对不上,降级把"没检查"伪装成通过 | M1、R5.4 |
|
||||||
|
| 校验代码永远通过 | `sdformat` 用 `__default__` / `__default_topic__` 填了缺失字段 | M1 |
|
||||||
|
| 报出根本不存在的碰撞 | 包围盒变换对 min/max 取了绝对值、把偏移吃掉 | R4.6 |
|
||||||
|
| 正常嵌套被判成重叠,只好把检查关掉 | 拿总包围盒判重叠,没逐碰撞体三维判 | R4.6 |
|
||||||
|
| 孔检测在仿真里根本不可能通过 | 仿真分辨率与真机不同,像素门槛失效 | R2.6 |
|
||||||
|
| 仿真"通过"、真机不认 | 像素门槛抄了两份,漂移了 | M2、R2.6 |
|
||||||
|
| 脚本说"已停止",进程还在跑 | 进程模式表漏项(漏过 `robot_state_publisher`) | M1、R4.10 |
|
||||||
|
| `pkill -f gz` 秒返回、什么都没停 | 它先匹配到调用它的 shell | R4.10 |
|
||||||
|
| 换了台臂却连错机器 | 硬编码的 `Rizon4-063989` 默认值 | R1.1 |
|
||||||
|
| `assets_root()` 指向从未存在的 `assets/` | 零调用者的死代码,没人发现 | R5.5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 这份文档怎么用(给 AI 的具体指令)
|
||||||
|
|
||||||
|
改这个仓库时:
|
||||||
|
|
||||||
|
1. 先读第 0 节,记住三条元规则。
|
||||||
|
2. 按第 7 节的清单逐条过。
|
||||||
|
3. 出问题时先查第 8 节(按症状查)。
|
||||||
|
4. 要在代码里"绕开"某条规则时,**先改这份文档**并写清理由 ——
|
||||||
|
绕开而不改文档,下一个人会按文档改回去。
|
||||||
+34
-2
@@ -15,20 +15,51 @@
|
|||||||
| 路径 | 进 git? | 做什么 |
|
| 路径 | 进 git? | 做什么 |
|
||||||
|------|----------|--------|
|
|------|----------|--------|
|
||||||
| `README.md` | 是 | 克隆后怎么跑 |
|
| `README.md` | 是 | 克隆后怎么跑 |
|
||||||
| `docs/` | 是 | 说明文档(本文件是目录总表) |
|
| `docs/` | 是 | 说明文档(本文件是目录总表);**规矩在 [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md)** |
|
||||||
| `config/site.example.yaml` | 是 | 现场配置模板 |
|
| `config/site.example.yaml` | 是 | 现场配置模板 |
|
||||||
| `config/site.yaml` | **否** | 本机 SN / IP / 相机 / 锁高 |
|
| `config/site.yaml` | **否** | 本机 SN / IP / 相机 / 锁高 |
|
||||||
| `config/fastdds_visual_servo.xml` | 是 | 可选 FastDDS;默认不用 |
|
| `config/fastdds_visual_servo.xml` | 是 | 可选 FastDDS;默认不用 |
|
||||||
| `scripts/` | 是 | 唯一操作入口 |
|
| `scripts/` | 是 | 唯一操作入口 |
|
||||||
| `src/` | 是 | ROS 2 包源码 |
|
| `src/` | 是 | ROS 2 包源码 |
|
||||||
|
| `devices/` | 是 | **设备模块树**(机器人/夹爪/相机/传感器/仿真),见下节 |
|
||||||
| `handeye/` | 目录是;`tmp/` 内容 **否** | 手眼标定配置 / 采点 / 结果,见 `handeye/README.md` |
|
| `handeye/` | 目录是;`tmp/` 内容 **否** | 手眼标定配置 / 采点 / 结果,见 `handeye/README.md` |
|
||||||
| `models/` | 目录与 `.labels.yaml` 是;权重二进制 **否** | 算法权重模块,见 `models/README.md` |
|
| `models/` | 目录与 `.labels.yaml` 是;权重二进制 **否** | 算法权重模块,见 `models/README.md` |
|
||||||
| `build/` `install/` `log/` | **否** | `colcon` 产物,本地编译生成 |
|
| `build/` `install/` `log/` | **否** | 真机环境的 `colcon` 产物 |
|
||||||
|
| `build_sim/` `install_sim/` `log_sim/` | **否** | **仿真环境**的产物(`scripts/build_sim.sh`)。两套并存是刻意的:同一份源码、两个环境的依赖路径不同,共用 `install/` 会互相覆盖 |
|
||||||
|
| `.cursor/rules/` | 是 | 给 AI 的常驻规则(`FRAMEWORK_RULES.md` 的压缩版,权威仍是那份文档) |
|
||||||
| `thirdparty/` `vendor/` | **否** | 可选离线 RDK / Isaac 克隆 |
|
| `thirdparty/` `vendor/` | **否** | 可选离线 RDK / Isaac 克隆 |
|
||||||
| `.gitignore` | 是 | 上面「否」的规则 |
|
| `.gitignore` | 是 | 上面「否」的规则 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## `devices/` — 设备模块树(换品牌 = 加目录,不改代码)
|
||||||
|
|
||||||
|
这一层是"通用化"的落点:上层按 `manifest.yaml` **选型**,不认具体品牌名。
|
||||||
|
所以换一台机器人/夹爪/相机/传感器,是**新增一个 `<品牌>` 目录**,
|
||||||
|
而不是改现有目录或改应用代码。
|
||||||
|
|
||||||
|
```
|
||||||
|
devices/<kind>/<brand>/
|
||||||
|
├── manifest.yaml # id/label/kind/vendor/version + assets + driver + capabilities + models + config_schema
|
||||||
|
├── driver/ # 与厂商 SDK 对接的实现(可替换)
|
||||||
|
└── models/<型号>/ # 该品牌的型号专属文件
|
||||||
|
```
|
||||||
|
|
||||||
|
| kind | 现有品牌 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| `robots/` | `flexiv` | Flexiv Rizon / AICO(RDK) |
|
||||||
|
| `grippers/` | `dh_pgea` | 大寰 PGEA 平行爪 / RGI 旋转爪 |
|
||||||
|
| `cameras/` | `realsense` | Intel RealSense D405 / D435 |
|
||||||
|
| `sensors/` | `orisys` | Orisys 视触觉(三向力 + 深度图 + 力矢量图) |
|
||||||
|
| `sim/` | `gazebo` | Gazebo 物理仿真平台,手册见 `devices/sim/gazebo/README.md` |
|
||||||
|
|
||||||
|
`devices/registry.yaml` 声明"本仓库装了哪些品牌";`manifest.yaml` 里
|
||||||
|
`assets[].required: true` 的物料缺失时框架会报错,并给出**格式与命名要求**。
|
||||||
|
|
||||||
|
> 选型与接线细节见 [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 第 1 节(R1.3)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## `config/` — 现场配置
|
## `config/` — 现场配置
|
||||||
|
|
||||||
| 文件 | 作用 |
|
| 文件 | 作用 |
|
||||||
@@ -147,6 +178,7 @@
|
|||||||
|
|
||||||
| 文件 | 内容 |
|
| 文件 | 内容 |
|
||||||
|------|------|
|
|------|------|
|
||||||
|
| `FRAMEWORK_RULES.md` | **框架规则**(硬规矩,改代码前先读;第 8 节是按症状查的反例表) |
|
||||||
| `WORKSPACE.md` | 本页,目录与职责 |
|
| `WORKSPACE.md` | 本页,目录与职责 |
|
||||||
| `QUICKSTART.md` | 环境、编译、第一次实机 |
|
| `QUICKSTART.md` | 环境、编译、第一次实机 |
|
||||||
| `ARCHITECTURE.md` | 进程与数据流 |
|
| `ARCHITECTURE.md` | 进程与数据流 |
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ def registry_path() -> Path:
|
|||||||
return devices_root() / "registry.yaml"
|
return devices_root() / "registry.yaml"
|
||||||
|
|
||||||
|
|
||||||
def assets_root() -> Path:
|
|
||||||
"""Return the root of the model-weight asset tree (``assets/``)."""
|
|
||||||
override = os.environ.get("SITE_ASSETS_ROOT", "").strip()
|
|
||||||
if override:
|
|
||||||
return Path(override).expanduser()
|
|
||||||
return ros2_root() / "assets"
|
|
||||||
|
|
||||||
|
|
||||||
def handeye_root() -> Path:
|
def handeye_root() -> Path:
|
||||||
"""Return the root of the hand-eye module directory (``handeye/``)."""
|
"""Return the root of the hand-eye module directory (``handeye/``)."""
|
||||||
return ros2_root() / "handeye"
|
return ros2_root() / "handeye"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TestRealWorkspace:
|
|||||||
def test_contract_satisfied(self, monkeypatch):
|
def test_contract_satisfied(self, monkeypatch):
|
||||||
"""The shipped device tree and site.yaml must pass validation."""
|
"""The shipped device tree and site.yaml must pass validation."""
|
||||||
# Guard against a leftover SITE_* override from another test leaking in.
|
# Guard against a leftover SITE_* override from another test leaking in.
|
||||||
for var in ("SITE_DEVICES_ROOT", "SITE_CONFIG", "SITE_ROS2_ROOT", "SITE_ASSETS_ROOT"):
|
for var in ("SITE_DEVICES_ROOT", "SITE_CONFIG", "SITE_ROS2_ROOT"):
|
||||||
monkeypatch.delenv(var, raising=False)
|
monkeypatch.delenv(var, raising=False)
|
||||||
|
|
||||||
stack = load_device_stack()
|
stack = load_device_stack()
|
||||||
|
|||||||
Reference in New Issue
Block a user