101 lines
9.2 KiB
Markdown
101 lines
9.2 KiB
Markdown
# 实现计划: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. 持久化 mock(Api3dRepository,挂三维体)
|
||
|
||
- `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,降耦合;可选接真实只读。
|