geopro/docs/superpowers/plans/2026-06-18-vtk-3d-anomaly.md

101 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实现计划VTK 三维异常(#4全量含异常体/列表/过滤)
- 日期2026-06-18
- 分支:`feat/vtk-3d-view`
- 上位设计:`docs/superpowers/specs/2026-06-17-vtk-3d-volume-slice-anomaly-design.md`;补充需求 R49-56(切片右键创建异常) + R58-65(三维体详情·异常) + R69-88(异常/异常体列表/属性/过滤)。
- 关键决策(用户 2026-06-18 定):
- **异常挂「三维体」**`remarkSourceId`=三维体 ds id不挂切片(切片是临时圈定载体)、不挂源 ds。见记忆 `vtk-3d-persistence-structure`
- **全做**:圈定 + 保存(含截图) + 3D 渲染 + 异常/异常体列表(对象→异常体→异常) + 选中联动 + 显示过滤 + 删除/删除分组。
- **不参考 Geopro1.0**:按需求 + 行业最佳实践(标准多边形圈定)。
- **持久化 mock**:三维体/切片/异常端点后端均未就绪 → 全 mock(内存)走 `I3dSceneRepository`,整链端点就绪再切真实。截图先存本地(R88 截图属性待后端新增)。
## 0. 现状(可复用 vs 新建,实证见探查)
| 资产 | 现状 |
|---|---|
| `core::Anomaly`(name/typeName/markType点线面/localPts Vec2/线样式) | ✅ 有,但**2D**(localPts=x距离·y深度),需补 3D 几何 |
| `I3dSceneRepository` 异常接口(loadAnomalyTree/saveAnomaly/deleteAnomaly/deleteAnomalyGroup + AnomalyTree/AnomalyBody) | ✅ 接口齐,**实现是 stub**(Api 回 onErr/空树) |
| `ObjectExceptionPanel`(对象→异常体→异常 树) | ✅ 只读树完整,**无勾选/选中/删除/过滤交互** |
| `render::buildAnomalies`(点/线/面 vtkActor) | ✅ 有,但坐标=2D(x,depth,0),需 3D(世界点) |
| 异常 DTO(parseExceptions/groupByConsortium) + 真实读取(loadExceptionsByTmAsync) | ✅ 真实读取链路通(后端就绪后用) |
| `I3dSceneView` 异常方法 / VtkSceneController 异常逻辑 / 3D 圈定工具 / 选中联动(3D) / 过滤 | ❌ 全无,需新建 |
## 1. 数据模型core::Anomaly 补 3D 几何
`src/core/model/Anomaly.hpp` 增(保留现有 2D 字段,新增 3D
- `struct Vec3 { double x,y,z; };`
- `std::vector<Vec3> worldPts;`:异常多边形/折线/点的**世界 3D 坐标**(落在所在切片平面上)。
- `Vec3 planeNormal{0,0,1}, planeOrigin{};`:所在切片平面(法向+一点)——供重定位/正视,及与切片解耦后仍能定位。
- 持久化补充字段(不入 core入仓储存储或 Anomaly 扩展)`id`、`volumeDsId`(=remarkSourceId)、`exceptionTypeId`/`typeName`、`remark`、`screenshotPath`、`consortiumId`(异常体分组,空=未分组)。
> core::Anomaly 保持渲染/几何纯数据id/归属/截图等持久化元数据放仓储的 StoredAnomaly 包装(同 StoredVolume/StoredSlice)。
## 2. 渲染3D 异常 actor + I3dSceneView 接口
- `render::buildAnomalies3D(const std::vector<core::Anomaly>&)`(新增或改造 AnomalyActor`worldPts` 直接建点/折线/闭合多边形 actor世界坐标不再 ×1 深度);样式复用(lineColor/width/dashed);选中高亮(加粗/变色)。
- `I3dSceneView` 新增:
- `addAnomaly(const core::Anomaly&)` / `removeAnomaly(id)` / `clearAnomalies()`
- `setAnomalyVisible(id, bool)` / `setAnomalySelected(id, bool)`(选中联动)
- `pickedAnomalyId()` 或经回调 `onAnomalyPicked(id)`VTK 点选异常→列表)
- `VtkSceneView``map<id, actor>`,实现上述。
## 3. 圈定工具(切片平面上画多边形)
`src/render/interact/AnomalyDrawTool.{hpp,cpp}`(新):
- 输入:当前选中切片的平面(origin/normal) + interactor + renderer。
- 交互(行业标准):左键逐点加顶点(投影到切片平面);右键/双击/回车闭合Esc 取消;实时预览折线。点类型=单击一点;面=多边形闭合;(线/文字按 markType)。
- 产物:`worldPts`(平面上的世界点) + planeNormal/origin → 回调上层。
- 入口VTK 视图切片右键「创建异常」(已占位) → 启动本工具(以光标拾取点为起点R49)。
## 4. 保存对话框 + 截图
`src/app/AnomalySaveDialog.{hpp,cpp}`(新,参考 VolumeParamsDialog 风格):
- 字段:异常名称、异常类型(下拉,**mock 几个类型**;真实类型端点 `exceptionType/*` 只读、后续可接)、备注。
- 截图(R50):圈定结束截当前 VTK 视图(或异常包络区) → 存本地文件 → 路径+大小入异常记录(`SliceExport` 同款 PNG 写)。
- accept → 组装 `core::Anomaly`(markType/worldPts/plane/样式) + 元数据(name/typeId/remark/screenshot) → `saveAnomaly`
## 5. 持久化 mockApi3dRepository挂三维体
- `StoredAnomaly { core::Anomaly geom; id; volumeDsId; exceptionTypeId/typeName; remark; screenshotPath; consortiumId; }``map<id, StoredAnomaly> anomalies_`。
- `saveAnomaly(a, screenshotPath, onOk(id), onErr)`:生成 `anomaly-N`,存,回 id。接口已含 screenshotPath 参数)
- `loadAnomalyTree(objectId, onOk(tree), onErr)`:按 objectId 下所有三维体聚合异常 → 组 `AnomalyTree`(bodies=异常体分组 + loose=未分组)。mock 阶段:以 volumeDsId 关联,未分组进 loose。
- `deleteAnomaly(id)` / `deleteAnomalyGroup(bodyId)`:删/删组。
- 异常体(consortium)分组mock 内存(`map<bodyId, {name,typeName,memberIds}>`);真实端点 `exceptionConsortium/*` 后续接。
- 接口签名不变;后端整链就绪仅换实现。
## 6. 异常展示与控制的摆放(用户 2026-06-18 定,需求实证 R28/R36/R58-88
需求结构R58/R67/R69/R90 均为 C1 顶级分节 = **数据详情栏**的各类详情内容R28/R36「数据详情 → 在数据详情栏显示」。R84 选中联动、R86-87 VTK 显示过滤 = **3D 场景操作**。结论(职责拆分,互补不重复)
- **三维分析区 = 3D 异常的"场景控制"**(本期 4c 重点3D 异常现为 mock
- 树:对象 → 三维体 → 异常异常挂三维体R61非切片非源 ds见记忆 `vtk-3d-persistence-structure`)。
- **显示过滤 4 档(R86-87)**:全部显示 / 随GS / 随数据集 / 全部隐藏 —— **独立于体勾选**控制 VTK 异常可见性(解决"异常被体勾选绑死")。
- 每条异常**单独显隐**(复用 AnomalyListPanel 的"眼睛")。
- **VTK 选中双向联动(R84)**:列表选中 ↔ VTK 高亮。
- 删除异常 / 删除分组(R79-81, deleteAnomaly/deleteAnomalyGroup)。
- **右侧「对象异常」面板(现有 `ObjectExceptionPanel`) = 异常全集 master**:对象下所有异常总表。**本期保持不动**(仍连后端 2D 异常);后端三维体/切片/异常整链就绪后3D 异常并入此处成全集。
- **三维体数据详情(R58-65)**:源数据/切片/**异常列表(R61,只读摘要)**/插值参数/色阶/测量——经右键「数据详情」打开。
> 不在右侧总表里塞 3D 场景控制(过滤/联动属 3D 操作,归三维分析区);不在三维分析区重复全集总表。
## 7. main.cpp 编排
- 切片右键「创建异常」→ 启动 `AnomalyDrawTool`(当前选中切片平面) → 圈定 → `AnomalySaveDialog``saveAnomaly` → 渲染(addAnomaly) + 刷新三维分析区异常列表。**[4b 已实现]**
- 体到场/移除(onVolumeChanged) → `loadAnomalyTree(volumeId)` → 渲染该体已存异常(reloadAnomalies)。**[4b 已实现,= "随数据集" 档默认]**
- 三维分析区异常列表:选中/显隐/过滤/删除 → 驱动 view 的 setAnomalyVisible/Selected + 仓储删VTK 点选异常 → 列表选中(联动)。**[4c]**
## 8. 阶段(每阶段编译绿 + 用户实测)
- **4a 基础 ✅ 已提交(4e1b8e7)**§1 模型 + §2 渲染/接口 + §5 mock 持久化 + 测试修复(228/228 绿)。
- **4b 圈定+保存 ✅ 已实现(未提交,用户已测通)**§3 `AnomalyDrawTool`(切片平面圈定,射线-平面求交,左键加点/双击·右键·回车闭合/Esc 取消/屏幕提示) + §4 `AnomalySaveDialog`(名称/类型 mock/备注/截图预览) + 切片右键「创建异常」接通 + onVolumeChanged→reloadAnomalies(随体重载渲染)。闭环:画→存→显示→跨重勾持久。
- 同批交互修复(待提交):生成体**按勾选集合**(非行高亮/右键项)、buildVolume 网格**覆盖全程**(跨 TM 多剖面不截断)、滚轮推进选中切片(点切片外取消选中→恢复缩放)。
- **4c 三维分析区 3D 异常控制(下一步)**§6 —— 三维分析区异常树(对象→三维体→异常) + **显示过滤 4 档(R86-87)** + **VTK 选中双向联动(R84)** + 每条显隐 + 删除/删组 + 异常属性(R83)。异常体分组 mock。右侧总表不动。
- **后续**:三维体/切片数据详情(R58-65/R67);真实端点整链就绪后切真实(异常并入右侧全集)。
## 9. 风险/待确认
- **core::Anomaly 改动影响 2D 路径**:补字段不动现有 2D 字段2D 渲染(ContourPlotItem/buildAnomalies)不受影响3D 走新 worldPts 路径。
- **异常体(consortium)创建入口**:需求 R71 有异常体,但"如何把异常归入异常体"的 UI 入口需求未细化 → 4c 落地时按最佳实践补(多选异常→成组),或先只做 loose + 展示分组。
- **截图属性后端缺**(R88 待新增):先本地存,后端加字段再上传。
- **真实类型/异常体端点只读可接**mock 阶段先 mock降耦合可选接真实只读。