feat(review): 几何干涉检查 find_collisions(BVH 粗筛 + 容器过滤)

C++(cad_mcp_plugins):
- 新增 bvh.h:header-only 2D BVH(质心中位分割,叶<=4,SelfPairs 交叉对枚举)
- 新增 find_collisions.cpp:粗筛 AABB 相交实体对,附重叠盒 + 逐轴重叠比 ratio
  (= 逐轴重叠长/较短长 之积,退化轴取 1.0),按 ratio 降序 + 分页;
  narrow=true 只对返回页跑 intersectWith 细筛(附 exact)
- 容器过滤(关键):图框块/分区边界会与一切相交,用重叠【度】判定
  deg >= max(exclude_container_deg=8, exclude_container_frac=0.25 * 其他实体数),
  与图幅/缩放无关;被剔除者在 excluded_containers 里如实报告
  (初版用面积判据对多图框并排失效:3 框只占并集 27/13/7% 全部漏过,
   全模型 163 对噪声;改度判据后图框 deg=35/25/23 vs 其余 deg<=3,分离干净)
- cmakelists.txt + 两个 mcp_config.json 注册(28 工具)

agent 侧(Python):
- cad_tools.READ_ONLY_TOOLS += find_collisions(受控子循环可调用)
- checks.check_collisions + run_deterministic_checks(受 CHECK_COLLISIONS 开关,默认关)
- config += CHECK_COLLISIONS=False / COLLISION_MIN_OVERLAP=0.5
- checklist.py / review_checklist.json += 「干涉/碰撞」类别(high)+ 提示词协议
  (先 find_collisions 拿候选 → inspect_region 目视确认;AABB 相交 != 真干涉)

验证:
- verify_collisions.py 17/17 PASS:全模型候选 163→80(全部 ratio=0.000),
  min_overlap=0.5 → 0 对;逐图框 0 对;narrow top-5 中 4 条 exact=true;
  --inject 注入深重叠矩形报出 ratio=0.902 并清理 2/2
- pytest 355 passed(新增 12 项)
- 端到端:LLM 主动调用 find_collisions{min_overlap:0.2,narrow:true,window:<图框>},未误报
This commit is contained in:
gjm committed 2026-10-16 19:46:44 +08:00
1 parent a77a090281
commit d4a9442e30
20 files changed
+1152 -13

No files matched your search

+57 -2
View File
@@ -1,6 +1,6 @@
---
name: Agent 路线与多源架构(2026-10-15)
description: dwg_review agent 的总目标、多源图纸分层架构(视觉为最大公约数)、工具层与 agent 层的正交分工、多源证据层、截图去 UI、grounding 实测、热力图 + 自适应瓦片规划 + VLM 分辨率标定、绘图区最大化(set_drawing_area)与空瓦片过滤、审查阶段分辨率对齐、agent 侧虚拟工具 inspect_region(放大取证闭环)、后续路线
description: dwg_review agent 的总目标、多源图纸分层架构(视觉为最大公约数)、工具层与 agent 层的正交分工、多源证据层、截图去 UI、grounding 实测、热力图 + 自适应瓦片规划 + VLM 分辨率标定、绘图区最大化(set_drawing_area)与空瓦片过滤、审查阶段分辨率对齐、agent 侧虚拟工具 inspect_region(放大取证闭环)、几何干涉检查 find_collisions(BVH + 容器过滤)、后续路线
type: project
---
@@ -93,6 +93,7 @@ Agent 推理层(审查逻辑 / 清单 / 规则)
(只回 handle/type/bbox、放开 limit),**而不是新增"热力图工具"**。
- `CollisionDetection.h/cpp`(用户写的 BVH)**本步不用**,留作后续**几何相交/干涉检查**引擎。
其按质心中位分割 → 节点框**可能重叠**,不适合铺屏瓦片;瓦片切分用四叉树/网格(空间不相交)。
(**2026-10-16 已落地**:见下文「C2:几何干涉检查 `find_collisions`」——重写了 header-only 2D BVH。)
- 标定判据:**同时测「读出文字」与「分辨线细节」**;标定字高 **200 图形单位**(1:100 = 纸面 2mm,国标最小)。
**已落地(提交 `9b8a232` 热力图;`cba05b0` 瓦片规划 + 离线标定)**:
@@ -145,7 +146,8 @@ Agent 推理层(审查逻辑 / 清单 / 规则)
- **实测收益(屏幕 3440×1440)**:绘图区 **2110×462 → 3310×1084**(面积 **3.68×**),
窗口矩形 `[68,0,2316,960]` → `[-4,-2,3444,1442]`,还原**精确回位**。
- 验收脚本:`verify_drawing_area.py`(基线 → max → restore,断言矩形/最大化态/绘图区尺寸全回位)。
- 工具注册:`mcp_config.json` 的 `tools[]`(现 **27** 个),工厂 `CreateSetDrawingAreaTool`。
- 工具注册:`mcp_config.json` 的 `tools[]`(`set_drawing_area` 加入时 **27** 个;后加 `find_collisions` 现为 **28** 个),
工厂 `CreateSetDrawingAreaTool`。
`set_drawing_area` 以 `set_` 开头 → 被 `cad_tools.is_write_tool` 判为写类(不重试),
且**不在** `READ_ONLY_TOOLS` 里 → LLM 的受控子循环**不能**动用户界面。
@@ -244,3 +246,56 @@ LLM 在受控子循环里 zoom + 截图拿到的是**未最大化的 2110×462**
- **验收口径达成**:报告报出标题栏**乱码/占位字符 `???????????????`**,
而 `get_text_content` 对全图返回 **0 条文字**(本图文字全在块/自定义实体内)——
即**块内文字是被视觉放大读出来的**,不是结构化数据给的。
## C2:几何干涉检查 `find_collisions`(2026-10-16)
**方向选择**:用户拍板「C 两个都做,C2 先(BVH → MCP 工具)」;并说明目录里的
`CollisionDetection.h/cpp` 是**参考例子**(可用可重写)。分析后:其中纯 ObjectARX 的
`AABB/EntityProxy/BVHTree`(约 1~367 行)可移植,其余(`needCD` 领域过滤 + MFC 对话框)
依赖用户 App 私有头文件(不在仓库),故**只重写 BVH 内核**。
**新增(C++,`src/cad_mcp_plugins/`)**:
- `bvh.h`:header-only 2D BVH(`namespace dwg_bvh`)。质心中位分割(取较长轴,
`std::nth_element`),叶 ≤ `kLeafSize=4`;`SelfPairs` 枚举相交对(叶内 `i<j` + 子树交叉)。
- `find_collisions.cpp`:`mcp_Tool_FindCollisions`,`EXPORT_MCP_TOOL` 导出。
参数:`window/layer_filter/limit/offset`(通用)+ `min_overlap` / `narrow` /
`include_same_layer` / `exclude_oversized` / `exclude_container_deg` / `exclude_container_frac`。
- **`ratio` = 逐轴重叠比之积**;某轴重叠比 = 该轴重叠长 / 两实体该轴**较短**长,
任一方该轴退化为 0 时取 1.0。于是共线相接→0、横管穿阀门→≈1。
- 按 `ratio` 降序 + 分页;`narrow=true` 只对**返回页**跑 `AcDbEntity::intersectWith` 细筛(附 `exact`)。
- 返回 `{total, returned, offset, entity_count, items[], excluded_containers[]}`。
**关键调参发现:容器(图框块)过滤必须用「重叠度」而非「面积」**。
初版用面积(`exclude_oversized` 相对**实体并集**)——3 张图框并排在模型空间里,
union 面积极大,图框只占 27%/13%/7%,**全部漏过** → 全模型 86 实体产生 **163 对候选**,
top 全是 `INSERT/40B × 一切` 且 ratio=1.0(图框完全包住小实体),`min_overlap=0.99` 仍剩 83 对。
实测重叠**度**分布:**图框 deg=35/25/23,其余实体全部 deg≤3**(分离极干净)。
故新增容器过滤:`deg >= max(exclude_container_deg=8, exclude_container_frac=0.25 × 其他实体数)`
→ 剔除其全部配对并在 `excluded_containers` 里**如实报告**(含 reason=oversized/container)。
**与图幅/缩放无关**,窗口态与非窗口态都生效。
**实测(真实 Plant 3D 图纸 `agent_test.dwg`,86 实体,`verify_collisions.py` 17/17 PASS)**:
- 全模型候选 **163 → 80**,且 80 对**全部 ratio=0.000**(仅管线接口相接);
`min_overlap=0.5` → **0 对**;逐图框(window)同样 0 对 → 本图**无真实干涉**(真阴性)。
- 容器过滤剔除 3 个 INSERT(deg 35/25/23,reason=container)。
- `narrow` 细筛:top-5 里 **4 条 `exact=true`**(接口处 pipe/fitting 真的几何相交)——
与 ratio=0.0 一致:**相交但未深度重叠**。
- **注入式正例**(`--inject`,画 2 个深重叠矩形于图框外 → 应报出 → 删除):
报出 `ratio=0.902`(= (380/400)²,与理论一致),清理 2/2 成功。
**agent 侧接入**(Python):
- `cad_tools.READ_ONLY_TOOLS` 加 `find_collisions` → LLM 受控子循环可调用(写类判定不受影响)。
- `checks.check_collisions` + `run_deterministic_checks`:受 `CHECK_COLLISIONS` 开关控制,
**默认关闭**(AABB 相交噪声大,平面图里管线穿墙/引线跨图元属正常相交),
改由 LLM 按「干涉/碰撞」清单条目**按需**调用;`COLLISION_MIN_OVERLAP` 默认 0.5。
产出的 issue `category="干涉"`(清单新增类别,输出枚举同步加「干涉」)。
- `checklist.py` / `review_checklist.json`:新增「干涉/碰撞」类别(severity=high),
提示词写明「先 `find_collisions` 拿候选 → 对可疑 handle 用 `inspect_region` 目视确认,
**AABB 相交 ≠ 真干涉**」。
**端到端实测(`python -m dwg_review --frames 1`)**:清单显示 **6 类**(含干涉/碰撞);
LLM 在受控子循环里**主动调用** `find_collisions {'min_overlap': 0.2, 'narrow': True,
'limit': 60, 'window': <图框>}`,并结合 `inspect_region`;最终**未误报干涉**(正确真阴性)。
**待办**:C1(PDF / 图片源适配,见「后续路线」第 2 条)仍未做。