19 KiB
VTK 三维分析视图重构设计(按数据类型分组 + 对象树联动)
日期 2026-06-24 · 分支
feat/vtk-3d-view· 状态:设计待评审 配套后端契约见HANDOFF-vtk-3d-backend-api.md;持久化定论见记忆vtk-3d-persistence-structure。
1. 背景与目标
现状 VTK 视图左侧 ColumnDrawer 是三个固定 tab(三维数据集 / 二维数据集 / 三维分析),splitByDimension 仅按 ddCode 把数据集分到 3 个维度桶。
新需求:把「三维数据集」并入「三维分析」,改成按数据类型大类分组的视图(电阻率 / 视电阻率 / 瞬变电磁 / 三维体 / 切片),每类有各自的筛选条与操作;对象树勾选与 VTK 列表联动,且支持 GS / 项目层级直挂的数据集;全局视图控制移到 VTK 画布工具条。
目标:在保持现有渲染/交互能力的前提下,完成上述信息架构与交互重构,分类与筛选数据全部走已查实的真实字段,不引入臆测。
2. 范围
含:
- 「三维数据集 + 三维分析」合并为按大类分组的「三维分析」tab。
- 分类层由
ddCode(3 维)改为dsTypeCode(大类)映射表驱动。 - 对象树联动改造:非根 GS 三态复选框 + 右键 ds/tm;项目根直挂数据固定显示;按
structParentConfType分流拉取。 - 装置类型 / 采集时间筛选落地(客户端可做,不依赖后端补字段)。
- 全局视图控制(坐标轴 / 比例 / 快捷视图 / 缩放)移到中央 VTK 画布工具条。
- 三维体/切片生成(
createVolume/createSlice)按契约 DTO 组装出真实VoxelGenerateRequest/SliceGenerateRequest请求体结构(仍走 mock 存储、假 id),供后端联调、并为将来切真实端点铺序列化路径。
不含:
- 「二维分析」tab(现
Column2DDataset)本次不改。 - 三维体 / 切片 / 异常的持久化切真实(仍走
Api3dRepositorymock,后端就绪后另行切换,见配套 handoff)。 - 后端接口改动(装置类型值→中文字典源待坐实,见 §11)。
3. 实测依据(关键事实,已通过真实接口核实)
后端基址 http://tenant.geomative.cn/pop-api,样本项目「演示项目(高密度+瞬变)」1438889436225536。
-
大类必须按
dsTypeCode分,不能按ddCode——电阻率 / 视电阻率 / 瞬变电磁反演剖面三者ddCode同为dd_inversion_data,仅dsTypeCode/name不同:大类 ds 行 namedsTypeCode电阻率数据 电阻率数据 ERT platform inversion data视电阻率数据 视电阻率数据 visual resistivity data瞬变电磁数据 瞬变电磁反演剖面 DD TRANSIENT ELECTROMAGNETIC INVERSION -
装置类型与采集时间是 ds 的结构化属性:
dsObject/dynamicForm/{id}的formList定义字段arrayType(装置类型,下拉)、collectTime(采集时间);ds 行properties按 confFieldId 携带其值。装置类型只 ERT 类(电阻率/视电阻率)有,瞬变电磁没有。 -
装置类型枚举:
GET /business/script/arrayTypeList→[{itemValue,name}](温纳排列(α)…施伦贝谢尔…共 15 项)。 -
层级拉取:
dsObject/data/page按structParentId+structParentConfType(1=GS/项目,2=TM)查;客户端loadRowsAsync第 3 参即parentConfType,现状写死 2。 -
遗留:
arrayType值是 id(如1429468249448449),实测不在该字段optionsObject(另一套 id 体系)里,value→中文字典源待坐实(§11)。
4. 整体架构
ColumnDrawer 改为两 tab:「三维分析」「二维分析」。二维分析不动。
「三维分析」tab = QScrollArea 纵向堆叠 5 个类型段:
┌─ 三维分析 ──────────── 二维分析 ─┐
│ ▼ 电阻率数据 [日期▾][装置类型▾]│ 段头:类型级筛选
│ ▸ 演示项目(根·直挂ds固定·无复选框) │
│ ▾ ☑ ERT1 (GS·三态) [右键:生成体]│
│ ☑ ERT1-WN (数据行·勾选=渲染)│
│ ☑ ERT1-WS │
│ ▾ ☐ ERT2 (GS) [右键:生成体]│
│ ▼ 视电阻率数据 [日期▾][装置类型▾]│
│ ▼ 瞬变电磁数据 [日期▾] │ 无装置类型筛选
│ ▼ 三维体 [日期▾] │ 体→[切片们 + 直接挂体的异常]三级树
│ ▾ 体A │
│ ▾ 切片S1(挂体A) │
│ 异常a1(挂S1) │
│ 异常a2(挂体A,临时切片上画) │
│ ▼ 切片 [日期▾] │ 已保存切片(挂父体下,与三维体段切片同源)
└──────────────────────────────────────┘
全局视图控制移到中央 VTK 画布竖排工具条: 设置(⚙→坐标轴设置对话框) / 快捷视图(前后上下左右) / 放大·缩小·复位(=现"适配")。
5. 数据模型与分类层
DsRow 扩展(RepoTypes.hpp):现解析 id/dsName/typeName(=name)/ddCode/createTime/parentId/file*,新增:
dsTypeCode—— 大类分类主键。arrayType—— 装置类型值(从 ds 行properties[]按 confFieldId 取)。collectTime—— 采集时间(同上)。
NavDto::parseDsRows 补这三个字段的解析。两接口 properties 形态不同(实测):dsObject/data/page 的 ds 行 properties 是 [{confFieldId,value}] 数组(按 confFieldId 取值);dsObject/dynamicForm/{id} 的 properties 是 {fieldCode:value} map(现 parseDynamicForm 用 fieldCode 取值,正确、不冲突)。DsRow 解析的是前者,故需配合 §10 的 confFieldId↔fieldCode 映射定位 arrayType / collectTime。
分类配置表 CategoryConfig(新文件,集中一处,开闭原则)——每段一条,带元数据:
| 段序 | 段名 | 识别键 | 可生成三维体 | 装置类型筛选 |
|---|---|---|---|---|
| 1 | 电阻率数据 | dsTypeCode = ERT platform inversion data |
✓ | ✓ |
| 2 | 视电阻率数据 | dsTypeCode = visual resistivity data |
✓ | ✓ |
| 3 | 瞬变电磁数据 | dsTypeCode = DD TRANSIENT ELECTROMAGNETIC INVERSION |
✓ | ✗ |
| 4 | 三维体 | ddCode = dd_voxel |
— | ✗ |
| 5 | 切片 | ddCode = dd_slice |
— | ✗ |
splitByCategory(rows) -> CategoryBuckets:替代 splitByDimension,按配置表把 DsRow 分入有序大类桶;不在表内的 dsTypeCode(接地电阻/原始数据/白化/坐标等)丢弃。
6. 对象树联动(ObjectTreePanel 改造)
节点交互模型:
| 节点 | 复选框 | 右键新增项 |
|---|---|---|
| 项目根 | 无(不可勾,直挂 ds 固定显示) | —(生成三维体改在段头,见 §7) |
| 非根 GS | 三态 | 选择 ▸ ds / tm(带对号) |
| TM 叶子 | 普通二态 | — |
GS 三态语义(标准 tristate 聚合):GS 复选框 = [GS 自身 ds] + [所有子 TM 勾选] 的聚合;都有=Checked,都无=Unchecked,部分=PartiallyChecked。
- 右键「选择 ▸ ds」= 切换「GS 自身 ds」开关(GS 自身 ds 在树中无独立复选框载体,故必须由此控制)。
- 右键「选择 ▸ tm」= 一键全选/全不选所有子 TM(子 TM 仍可单独勾,此项是批量便捷)。
- 点 GS 复选框:任一开 → 全关;全关 → 全开。
- 菜单项按有无动态禁用(无直挂 ds 禁 ds 项,无 TM 禁 tm 项)。
- 实现约束(必须):停用现有
Qt::ItemIsAutoTristate(ObjectTreePanel.cpp:123)——它只聚合子项 checkState、看不到「GS 自身 ds 开关」这第二维度,直接套用会产出错误聚合态。改为:用一个UserRole在 GS 节点存「自身 ds 开关」布尔,itemChanged里手动按「ds 开关 ∨ 子 TM 勾选」计算父三态并setCheckState(复用现有 0ms 合并防重入 pattern,避免级联多次触发)。
信号扩展:checkedTmsChanged(QStringList) → checkedSourcesChanged(QList<DataSource>),每个 DataSource = {id, confType}:
- 勾选 TM →
{tmId, 2};GS 自身 ds 开关开 →{gsId, 1};项目根直挂(固定)→{rootId, 1}。 - 集合为按
{id,confType}去重的并集:一条 TM 既被 GS 聚合又被单独勾时只算一次;confType=1(GS/项目)拉该节点直挂 ds、confType=2(TM)拉 TM 下 ds,二者物理数据不重叠,不会重复进桶。
数据流(main.cpp 接线):
checkedSourcesChanged
→ 对每源 loadRowsAsync(projId, src.id, src.confType, classify=3, …) // 第3参按源传(1/2)
→ 汇总 DsRow[] → splitByCategory → 各 CategorySection.setDatasets
7. 类型段组件 CategorySection
一个可参数化的类型段(单一职责,高内聚),由 CategoryConfig 一条配置驱动:
- 段头:标题 + 折叠开关 + 日期范围筛选 + 装置类型下拉(仅
装置类型筛选=✓的段显示)。 - 段体:项目根 / GS / TM 树 + 数据行,复用
DatasetListPanel::populateDatasetList(按 parentId 建树)与卡片委托;数据行可勾选 = 渲染。 - 渲染勾选链承接(必须):CategorySection 暴露
checkedDatasetsChanged,接管退役的Column3DDataset原有「剖面勾选→帘面渲染」主链——main.cpp 把 5 段(电阻率/视电阻率/瞬变=帘面,三维体/切片=体素/切片)的勾选并集后下发pushChecked(沿用现有checkedProfiles/checkedAnalysis并集模型),否则帘面渲染整体失联。 - 生成三维体入口:仅
可生成三维体=✓的段(电阻率/视电阻率/瞬变电磁)段头有「+新增三维体」按钮。源数据集 = 三维分析中当前勾选的同类型 ds(本段类型,天然按段隔离、可跨 GS)。点击 →VolumeParamsDialog(页面中心弹窗):左侧·数据列表 树状(按 GS 分组)展示这些已勾选源,每项可勾选/取消供确认或二次修改;右侧·插值参数:名称、生成位置(下拉)、插值模型、水平/竖向间距、IDW 幂次、最大影响距离。生成位置(归属)规则:默认 = 源同属单 GS→该 GS、源跨 GS→项目根;用户可改为项目内任意 GS / TM(故归属structParentConfType可为 1 或 2,与源数据解耦)。提交 → 组装VoxelGenerateRequest→createVolume。 - 筛选:复用并扩展
applyDatasetFilter,日期比较字段由createTime改为collectTime(§10)。
「三维分析」tab 容器(替代原 Column3DDataset/Column3DAnalysis 在 tab 中的位置):QScrollArea + 垂直布局,按配置表实例化 5 个 CategorySection。
8. 三维体 / 切片 / 异常段
复用现有 Api3dRepository(mock)与 refreshAnalysis 合并注入机制,仅重新组织到段:
三维体归属由「生成位置」选择决定:默认单 GS→该 GS、跨 GS→项目根,用户可改为项目内任意 GS / TM(
structParentConfType可 1 或 2)。后端契约docs/api/vtk-3d-openapi.json已同步至 v0.6-draft(structParentConfType放开 1/2 + 默认规则);客户端createVolume接真实端点时需补structParentId/structParentConfType,并新增按归属实体 id 查异常的queryException/{remarkSourceId}调用。
三维体段是「体 → 切片 / 异常」三级树(取消独立异常区——异常不再单列,而是作为叶子挂在它归属的实体节点下):
- 三维体段:列已生成的体(客户端 mock + 后端
dd_voxel),按归属(项目/GS/TM)分组。(「正在生成…」状态:现createVolume同步登记、首次loadVolume惰性插值,本期不引入异步生成态机、体即时出行。)体节点下挂:① 基于该体生成的切片子节点;② 直接挂体的异常(见归属规则)。多体可同时勾选渲染(dsProps_按 dsId 各存 actor),切片/异常操作针对「当前激活体」volumeOwnerDs_(=切片源体currentVolumeImage_)。 - 异常归属(核心规则):异常必基于切片(在某切片平面上画),切片必基于体(
SliceSpec.volumeDsId)。查找链异常 → 所在切片 → 切片所属体。挂载目标按该切片是否已保存成dd_slice决定:- 切片已保存(是
dd_slice实体)→ 异常挂该切片(remarkSourceId=切片dsId)。 - 切片未保存(临时圈定平面)→ 异常挂切片所属体(
remarkSourceId=体dsId=volumeOwnerDs_)。 - 数据模型:
Anomaly的volumeDsId改名为remarkSourceId(= 挂载实体 dsId,体 or 切片;对齐后端remarkSourceId=dsObjectId)。挂体/挂切片由remarkSourceId指向的实体类型区分(查isVolumeDataset/isSliceDataset),展示树按parentId=remarkSourceId自动挂到对应节点——不引入新 type 字段。⚠️ 后端remarkSourceType已是标注几何形态(1点/2线/3面/4文字 =Anomaly.markType),勿与"挂体/挂切片"混淆。仍 mock 存储。
- 切片已保存(是
- 切片段:列已保存切片(
dd_slice),按父体分组(parentId= 所属体)。与三维体段内的切片子节点同源(同一批sliceRows)。
体素 / 切片 / 异常的渲染、生成、保存路径不变(VtkSceneController / InteractionManager / Api3dRepository),只改列表的承载位置。
请求体组装(本次新增):createVolume 扩参为 (projectId, structParentId, structParentConfType, params, name)——归属来自对话框「生成位置」选择(GS/项目根/TM,默认单GS→该GS、跨GS→项目根);createSlice 补 projectId。两者内部按新增客户端 DTO VoxelGenerateRequest/SliceGenerateRequest(对齐 docs/api/vtk-3d-openapi.json schema)组装出完整请求体并提供 toJson 序列化;mock 路径存内存 +(debug)打印请求体,将来切真实端点只把「存」改「发」、组装逻辑原样复用。如此重构一落地即可从 UI 真实产出请求体(值为 mock、结构与字段为真)。createSlice 补 projectId 会波及 main.cpp 切片保存调用点(切片右键/列表保存路径),须一并传 nav.currentProjectId()。
9. VTK 画布工具条 VtkViewToolbar
从 Column3DDataset 抽出全局视图控制,做成中央 VTK 画布上的竖排工具条(新组件):
- 设置(⚙) → 弹「坐标轴设置」对话框
AxesSettingsDialog:X 轴 / Y 轴 / 深度(m) 各带「显示」开关 + 最小值 / 最大值,取消 / 应用。 - 快捷视图:前、后、上、下、左、右(按钮文字即此六字,沿用现有
ViewDir与功能)。 - 缩放:放大 / 缩小 / 复位(复位 = 现「适配」)。
信号沿用现有 Column3DDataset 已接的控制器槽(axesModeChanged/verticalExaggerationChanged/viewRequested/zoom*/fitRequested 等),仅迁移承载控件。水平/垂直比例的承载位置随工具条一并迁移(或并入坐标轴设置对话框,实现时取最贴合者);setVerticalExaggeration 默认值回灌(main.cpp:902)也要迁到新工具条,避免默认夸张值不同步。
10. 装置类型 / 采集时间筛选落地
字段映射服务 DatasetFieldDictionary(新组件,按 dsType 缓存)——为何必要:ds 列表行 properties 只给 [{confFieldId,value}](数字 confFieldId、无字段名),要判定哪个 confFieldId 是 arrayType/collectTime,必须从 dynamicForm 的 formList(含 confFieldId↔fieldCode 对应)取映射。
- 对每个 dsType 拉一次
dsObject/dynamicForm,缓存:①arrayType/collectTime字段的confFieldId(用于从 ds 行properties取值);② 装置类型选项字典(value→中文)。 - 列表行装置类型 = ds 行
properties中confFieldId == arrayType 的 confFieldId那项的 value,经字典翻中文展示与分组。 - 段头装置类型下拉 = 该段当前数据出现过的装置类型集合;选中按值过滤。
采集时间:日期筛选按 collectTime;三维体 / 切片段无此字段 → 回退 createTime。
11. 待坐实 / 风险
- 装置类型 value→中文字典源:实测
arrayType值不在该字段optionsObject里(另一套 id),parseDynamicForm也不翻译;但客户端属性面板据称显中文。需在实现前坐实正确字典源(候选:fieldConfigJsonObject.fieldDataRadius指向的全局字典 /script/arrayTypeList)——请提供客户端数据集属性面板截图以最快定位现有翻译路径。不阻塞其余设计。 - 三维体 / 切片 / 异常仍 mock:后端就绪后切真实(接口
I3dSceneRepository留缝),见配套 handoff。 - 跨 GS 生成体:本次以「单容器(项目根/GS)范围」为主;跨多 GS 拼大体暂不做。
12. 组件 / 文件边界
| 类别 | 组件 | 说明 |
|---|---|---|
| 新增 | CategoryConfig |
dsTypeCode/ddCode→大类映射表 + 段元数据 |
| 新增 | splitByCategory |
替代 splitByDimension |
| 新增 | CategorySection |
单个类型段(段头筛选/操作 + 段体树) |
| 新增 | 三维分析 tab 容器 | QScrollArea 堆叠 5 段 |
| 新增 | VtkViewToolbar + AxesSettingsDialog |
VTK 画布工具条 + 坐标轴设置 |
| 新增 | DatasetFieldDictionary |
按 dsType 缓存 arrayType/collectTime 映射 + 装置字典 |
| 改造 | ObjectTreePanel |
GS 三态 + 右键 ds/tm + 信号扩展为带 confType 源集合 |
| 改造 | DsRow + NavDto::parseDsRows |
补 dsTypeCode/arrayType/collectTime |
| 改造 | ColumnDrawer |
三 tab → 两 tab |
| 改造 | main.cpp |
数据流按 confType 分流拉取 + 段接线 + 生成入口传容器归属 |
| 新增 | VoxelGenerateRequest/SliceGenerateRequest DTO + toJson |
对齐 openapi schema 的客户端请求体结构 + 序列化 |
| 改造 | Api3dRepository::createVolume/createSlice |
扩参带归属/projectId,内部组装请求体 DTO(mock 存 / 将来发) |
| 复用 | DatasetListPanel |
populateDatasetList / 卡片委托 / applyDatasetFilter |
| 复用 | Api3dRepository / refreshAnalysis |
三维体/切片/异常 mock 与合并注入 |
| 复用 | Column2DDataset |
二维分析 tab,不动 |
| 退役 | Column3DDataset / Column3DAnalysis |
功能拆分到 CategorySection / VtkViewToolbar / 三维体段 |
13. 非目标
- 不改二维分析。
- 不改后端接口、不新增后端字段(装置类型走客户端已有数据)。
- 不切换三维体/切片/异常持久化为真实后端。
- 不做跨 GS 拼体、不做装置类型以外的新筛选维度。