21 KiB
VTK 视图重构:合并数据集单栏 + 动态段 + 图标工具条 + 2D 平面底图 — Spec(2026-06-30)
分支起点:
main(PR #9 已合并)。职责范围:VTK 视图左侧数据集栏(面板结构 + 段交互 + 2D/3D 共存渲染 + 底图)。 本 spec 替代 2026-06-26「二维分析:锁定俯视」模型的相机/显隐部分(见 §10 迁移说明)。
0. 一句话目标
把「三维分析 + 二维分析」两个 tab 合并成一个无标题的单列数据集栏:2D 与 3D 数据集用一致的分段组织同列呈现;段按数据有无动态显隐;段操作改响应式图标工具条;2D 数据以按类型一块平面的方式与 3D 体/帘面在同一个自由透视场景里共存。
1. 背景与现状(接手必读)
- 面板:
ColumnDrawer= 左侧抽屉,QTabWidget两 tab:- 三维分析
CategoryAnalysisTab:QScrollArea竖堆CategorySection×4(电阻率/视电阻率/瞬变/三维体)。 - 二维分析
Column2DDataset:平铺树 + 底图下拉 + 2D视图模式下拉 + 自定义Z 滑块。 - 切 tab 发
analysisModeChanged(bool is2D)。
- 三维分析
- 段
CategorySection:段头(chevron+标题 | 新增三维体[反演类] | 导入雷达[voxel])+ 段体(固定显示的筛选行[日期范围+装置类型] + 可勾选数据树)。 - 分类:3D 用
splitByCategory()(categoryConfigs()4 段);2D 用splitByDimension().dim2D(当前仅dd_trajectory_data)。 - 渲染:
- 2D 轨迹 →
MapLineActor(橙色vtkPolyLine),经VtkSceneController::set2DPlacement(mode,z)摆到单一全局 Z(5 模式:0关/1 Z=0/2顶+50/3底-50/4自定义)。已有逐 ds 的 Z 拖动偏移mapLineZOffset_。 VtkSceneView::setAnalysisMode2D(is2D):切 tab 时按维度翻 actor 可见标志 + 相机锁定近俯视 +VtkViewToolbar禁 6 向视图。- 底图
TileBasemap(单例):天地图 WMTS(卫星img_w→buildWarped带高程地形 / 矢量vec_w→buildFlat纯平面),透明度固定 0.55,瓦片范围 =dataHorizontalRadius()×10钳[2000,30000]m,置 Z=0。buildFlat已能纯平贴矢量瓦片。
- 2D 轨迹 →
- 顶部工具条
TopBar:视图/项目管理/业务工具/设备 四菜单。 - 渲染区竖排工具栏
VtkViewToolbar:段1Gear(坐标轴设置) → 分隔线 → 6向视图 → 缩放。
2. 已确认的关键决策(与用户逐条确认)
- 单一自由场景共存:合并删 tab 后,取消「锁定近俯视相机 + 按维度自动显隐」。场景恒为自由透视,勾选的 2D 平面与 3D 体/帘面同时可见。
- 底图 = 1 个 3D 底图 + N 个 2D 底图:所有三维数据共用一个 3D 底图(现状
TileBasemap);每个 2D 类型(段) 一块独立平面底图(N = 2D 段数,非每条 ds 一张)。 - 默认勾选:沿用现有「直接挂项目下的 ds 默认进 VTK」逻辑,本次只保证动态显隐段时不破坏它。
- 2D z 值按类型一块平面:每个 2D 类型一块平面 + 一个 z 滑块;平面初始 z = 该类型第一个被勾选 ds 的 z;同类型其余 ds 投影到此平面。
- 3D 底图控件移到
VtkViewToolbar(Gear 之后),不在 3D 段上。 - 「导入雷达」移到
TopBar设备菜单(临时测试功能,后续整体移除)。 view2DMode5 模式下拉废弃,2D 高度完全由「z值」滑块替代。
3. 架构方案
采用方案 A:统一单列 DatasetColumn + 类型抽象(§5)。
- 用一份类目描述符目录
categoryCatalog()(§5,每类型一份CategoryDescriptor:分类/筛选/操作/渲染策略)同时驱动 3D/2D 段;CategorySection按描述符建筛选器与图标条;VtkSceneController按描述符的renderStrategyId查可插拔渲染策略(§5.4)渲染。消费方不再if dimension/ddCode散判。 - 删除
Column2DDataset与QTabWidget;ColumnDrawer承载单个DatasetColumn,保留折叠开关。 - 取舍:复用已验证的勾选保留/折叠/spinner/结构树逻辑;改动集中在描述符目录、段头工具条(OpKind 映射)、渲染策略注册表。
(备选 B「两 widget 去 tab 竖堆」因 2D/3D 段不一致、重复逻辑被否;C「全重写」工作量过大被否。)
4. 面板结构(§1 重构)
ColumnDrawer:去QTabWidget+Column2DDataset;改持DatasetColumn;移除analysisModeChanged信号链。折叠开关保留。DatasetColumn(原CategoryAnalysisTab改名/改造):QScrollArea竖堆 N 个CategorySection,无栏目标题。- 动态显隐:段 bucket 为空 → 段
hide();非空 →show()。三维体段同理(默认空→默认不显示)。relayoutSections()/stretch 逻辑只对可见段生效。 - 空面板占位:所有段均空时,滚动区中央显示提示语占位(文案:「请在左侧对象树勾选测线 / 数据集」);任一段非空则隐藏占位。
5. 类型抽象(扩展契约 —— 本 spec 的架构基石)
目标:接入一个新 ds 类型 = 实现一份描述符(必要时再补一个渲染策略 / 一个操作 / 一个筛选器),无论它「按什么规则接入、有什么操作、怎么渲染」,UI 层与渲染层都只消费抽象、不再
if dimension/ddCode散判。这是「数据集栏目」的统一规范,所有现存 5 类与未来新类都走它。
5.1 类目描述符 CategoryDescriptor(data 层,纯 C++,无 Qt/VTK)
namespace geopro::data {
enum class SceneKind { Volume3D, Curtain3D, Plane2D }; // 渲染语义 / 共存规则
enum class FilterKind { DateRange, ArrayType }; // 筛选器契约(可扩展)
enum class OpKind { GenerateVolume, Filter, PlaneZ, Basemap }; // 段操作契约(可扩展)
struct CategoryDescriptor {
std::string id; // "resistivity"/"apparent"/"transient"/"voxel"/"trajectory" ...
std::string title; // 段标题
SceneKind sceneKind; // 渲染语义
std::function<bool(const DsRow&)> classify; // 轴1 数据来源/分类("无论按什么规则接入")
std::vector<FilterKind> filters; // 轴2 本段筛选器(顺序=显示顺序)
std::vector<OpKind> operations; // 轴3 段头图标操作(顺序=显示顺序)
std::string renderStrategyId; // 轴4 渲染策略键(解析到注册表,见 §5.4)
};
// classify 便捷构造器(覆盖现有按 ddCode / dsTypeCode 接入的常见情形;任意复杂规则可直接写 lambda)
std::function<bool(const DsRow&)> byDdCode(std::initializer_list<std::string> codes);
std::function<bool(const DsRow&)> byDsTypeCode(std::initializer_list<std::string> codes);
const std::vector<CategoryDescriptor>& categoryCatalog(); // 有序目录,取代 categoryConfigs()
} // namespace geopro::data
5.2 目录定义(catalog,取代 categoryConfigs())
| id | title | sceneKind | classify | filters | operations | renderStrategyId |
|---|---|---|---|---|---|---|
| resistivity | 电阻率数据 | Curtain3D | byDsTypeCode({"ERT platform inversion data"}) |
DateRange,ArrayType | GenerateVolume,Filter | "curtain" |
| apparent | 视电阻率数据 | Curtain3D | byDsTypeCode({"visual resistivity data"}) |
DateRange,ArrayType | GenerateVolume,Filter | "curtain" |
| transient | 瞬变电磁数据 | Curtain3D | byDsTypeCode({"DD TRANSIENT ELECTROMAGNETIC INVERSION"}) |
DateRange | GenerateVolume,Filter | "curtain" |
| voxel | 三维体 | Volume3D | byDdCode({"dd_voxel"})(mock 注入,见 §10) |
DateRange | Filter | "volume" |
| trajectory | 轨迹数据 | Plane2D | byDdCode({"dd_trajectory_data"}) |
DateRange | PlaneZ,Filter,Basemap | "plane2d" |
段顺序即表序(电阻率→…→轨迹)。分流:splitByCategory(rows) 改为遍历 catalog,对每行命中首个 classify(row)==true 的描述符即归入该段(保留原顺序)。Column2DDataset / splitByDimension 退役;旧 categoryConfigs()/CategorySpec 由 categoryCatalog()/CategoryDescriptor 取代。
5.3 消费方只认抽象
CategorySection(descriptor):按descriptor.filters建筛选器(FilterKind→UI映射一处)、按descriptor.operations建图标条(OpKind→按钮+信号映射一处,见 §6/§7)。DatasetColumn:遍历categoryCatalog()建段、用descriptor.classify路由数据(即splitByCategory)。VtkSceneController:勾选某 ds → 查其所属描述符 →renderStrategyId→ 渲染策略add/remove+ 首勾/全消onTypeActivated/Deactivated(见 §5.4、§8)。
5.4 可插拔渲染策略 IDatasetRenderStrategy(controller/render 层)
namespace geopro::controller {
class IDatasetRenderStrategy {
public:
virtual ~IDatasetRenderStrategy() = default;
virtual void add(const std::string& typeId, const std::string& dsId) = 0; // 异步加载+入场
virtual void remove(const std::string& dsId) = 0;
// 每类型场景资源生命周期(可选):本类型首个 ds 入场 / 全部离场
virtual void onTypeActivated(const std::string& typeId) {}
virtual void onTypeDeactivated(const std::string& typeId) {}
};
// 注册表:renderStrategyId(字符串键) → 策略实例。当前 3 实现:
// "volume" VolumeRenderStrategy —— 体素/雷达体(包现 isVolumeDataset 分支)
// "curtain" CurtainRenderStrategy —— 反演帘面(包现 dd_section 等分支)
// "plane2d" Plane2DRenderStrategy —— 2D 折线落类型平面 + 平面底图(封装 §8.2 平面 z + §9.2 底图)
} // namespace geopro::controller
关键:2D 的全部特殊性(按类型平面 z 生命周期 + N 个平面底图)封死在 Plane2DRenderStrategy 一个类内(内含 PlaneZRegistry + 平面底图管理);3D 两个策略只是包住现有渲染分支。控制器主流程不再含维度/ddCode 分支,只做「查描述符 → 取策略 → 调用」。
5.5 「接入一个新 ds 类型」标准动作(即本规范)
| 场景 | 要做的 |
|---|---|
| 新类型、沿用已有筛选/操作/渲染 | 只加一条 CategoryDescriptor(纯数据),完。 |
| 新类型、要新渲染方式 | 加描述符 + 实现一个 IDatasetRenderStrategy 并注册(新 renderStrategyId)。 |
| 新类型、要新操作 | OpKind 加一项 + CategorySection 加该 kind→UI 的一处映射;描述符 operations 列上。 |
| 新类型、要新筛选器 | FilterKind 加一项 + CategorySection 加一处映射;描述符 filters 列上。 |
数据驱动优先;只有真出现「新渲染/新操作/新筛选器」才动对应那一个扩展点,且改动收敛在单一位置,不扩散。
6. 响应式图标工具条(新建 SectionIconBar)
- 段头右侧承载图标工具条:默认最多显示 3 个图标;图标总数超 3,或段宽被挤压放不下时,右侧图标依次收进末尾「…」下拉菜单。
resizeEvent动态重算可见数 —— 这是必须实现并可验证的行为:即使图标 ≤3,当栏位宽度收窄到放不下时,右侧图标也要实时折进「…」,栏位变宽再弹回。 - 每个图标 =
QToolButton(autoRaise + glyph + tooltip),点击触发对应操作(部分弹 popup)。 - 图标集由描述符
operations(OpKind列表)驱动,不是按维度硬编码。CategorySection内有一处OpKind→(图标 glyph + tooltip + 点击/popup)映射表;新增操作只加一项映射(§5.5)。当前 catalog 各段对应:段 operations(左→右,右侧优先收进…)3D 反演(电阻率/视电阻率/瞬变) GenerateVolume、Filter3D 三维体 Filter2D 轨迹 PlaneZ、Filter、Basemap - 注:当前各段图标 ≤3,「数量超 3」分支暂不触发,但**「宽度挤压」分支必须工作**(窄栏即折叠);两个分支都要实现(后续图标会增加,数量分支随之生效)。
7. 段内操作行为(每个 = 一个 OpKind)
下列每个操作对应 §5 的一个
OpKind;CategorySection的OpKind→UI映射表据描述符operations装配。PlaneZ/Basemap的渲染落点封装在Plane2DRenderStrategy(§5.4/§8.2/§9.2)。
7.1 筛选 OpKind::Filter(通用,D2+D3)
- 现「固定显示的筛选行(日期范围 + 装置类型[仅 ERT])」改为默认折叠(段体内不占位)。
- 点「筛选」图标 → 展开筛选行;再点 → 收起(toggle)。筛选逻辑(
passesFilters/rebuildList)不变。
7.2 新增三维体 OpKind::GenerateVolume(仅 3D 反演段)
- 保持原功能(发
generateVolumeRequested),入口从文字按钮改图标。
7.3 z值 OpKind::PlaneZ(仅 2D 段)
- 点图标弹 popup:一个滑块,整体上下移动该 2D 类型那块平面的 z。
- 初值 = 该类型第一个被勾选 ds 的 z(见 §8.2);范围按场景高程量级合理取(实现期定)。
7.4 底图 OpKind::Basemap(仅 2D 段)
- 点图标弹 popup:【底图类型:矢量平面(默认) / 无】+【透明度滑块(默认 50%)】。
- 作用于该 2D 类型自己那块平面底图(§9.2,由
Plane2DRenderStrategy持有)。
7.5 3D 底图(移到 VtkViewToolbar,非段操作)
- 在
Gear(坐标轴设置) 正下方新增「地图」图标按钮,点击弹 popup:【底图类型:天地图(默认) / 无】+【透明度滑块(默认 50%)】。 - 控制全局唯一的 3D 底图(
TileBasemap):「无」= 隐藏;透明度去掉固定0.55、改默认0.5可调。
7.6 导入雷达(移到 TopBar 设备菜单,临时测试)
TopBar设备菜单加「导入雷达测线」→ 子项「规范化(.head/.data)…」「Impulse(.iprb)…」,发等价于现radarImportRequested(impulse)的信号到既有导入流程。- 从
CategorySection(voxel 段头) 移除该入口及radarImportRequested转发。
8. 渲染模型(经渲染策略 §5.4)
控制器主流程:勾选 diff → 对每个新增/移除 ds 查其描述符 → 取
renderStrategyId对应策略 →add/remove;某类型「首勾」「全消」时调onTypeActivated/Deactivated。无维度/ddCode 分支。
8.1 删除维度耦合
- 移除
VtkSceneView::setAnalysisMode2D的「相机锁定俯视 + 按维度翻可见标志」与VtkViewToolbar::setAnalysisMode2D的 6 向禁用。场景恒自由透视;2D/3D actor 同时可见(各自由勾选控制显隐)。 - 移除
view2DMode5 模式与旧set2DPlacementmode 维度(其职责并入Plane2DRenderStrategy)。
8.2 2D 按类型平面(需求 5)—— 封装在 Plane2DRenderStrategy
- 同一 2D 类型(段)勾选的全部 ds 投影到一块平面:
- 平面 z = 该类型第一个被勾选 ds 的 z(首个勾选时确定后固定不变,仅由 z 滑块整体升降)。
- 平面纯平、不渲染高程。
- 同类型其余 ds 的折线全部落在此平面 z。
- 平面生命周期:该类型首个 ds 被勾选 → 创建平面(定 z)+ 创建其平面底图(§9.2);期间 z 固定;该类型全部 ds 取消勾选 → 平面与其底图一并销毁。
- 逐 ds 独立拖动 Z(旧
nudgeSelectedMapLinesZ/mapLineZOffset_)废弃,统一到类型平面 z。 - 渲染复用
MapLineActor(折线几何不变),仅 Z 落点改为「所属类型平面 z」。
9. 底图体系(§6)
9.1 3D 共享底图(1 个)
- 沿用
TileBasemap单例(带高程地形,Z=0)。透明度参数化(默认 0.5,由 §7.5 popup 调);支持隐藏。瓦片范围规则不变。
9.2 2D 平面底图(N 个,每类型一块)—— 实现选型已定
- 决策:参数化
TileBasemap支持多实例(不另抽PlaneBasemap)。依据:TileBasemap的「相机驱动 LOD + 四叉树细分 + 视锥剔除 + 限并发下载 + GeoLocalFrame 配准」正是平面底图所需,且其状态全为 per-instance(无全局/静态状态,tileKey为纯函数),多实例天然可行。新抽类要么重写数百行 LOD/网络逻辑,要么退化成单层无 LOD 大平面(缩放发虚、且违背「瓦片范围参考三维规则」)。 - 需改的 3 处硬编码:
改动 现状 改为 地面 Z kGroundZ=0常量构造/setter 传 groundZ(2D 平面 = 类型平面 z)透明度 kTerrainOpacity=0.55固定参数化,默认 0.5 可调 平面/矢量模式 由 Kind隐含复用 Street(vec_w)+buildFlat(已是纯平矢量路径,跳过 DEM/warp) - 最终布局:1 个
TileBasemap(3D,Satellite/带高程,Z=0,§9.1,挂控制器)+ N 个TileBasemap(每 2D 类型一个,Street/纯平矢量,groundZ=平面 z,由Plane2DRenderStrategy持有)。共享同一Scene/GeoLocalFrame。 - 生命周期(在
Plane2DRenderStrategy内,经onTypeActivated/Deactivated):按 2D 类型持有 N 个实例 + 各自平面 z(PlaneZRegistry);该类型首勾 → 建实例(§8.2 定 z);该类型全消 → 销毁实例(连同折线平面)。 - 瓦片范围复用
dataHorizontalRadius()×10钳[2000,30000]m规则(各实例共享同一dataRadiusProvider)。 - 坐标对齐沿用
GeoLocalFrame(经纬→局部);frame 重锚逻辑不变。
10. 信号 / 默认勾选(§7)
- 2D 勾选/选中信号从
Column2DDataset迁到 2DCategorySection(复用checkedDatasetsChanged/datasetSelected)。basemapChanged/view2DModeChanged/customZChanged退役,由 §7.4/§7.5 的 popup + §8.2 平面 z 替代。 - 「直接挂项目下 ds 默认进 VTK」既有逻辑保留;动态显隐段时确保默认勾选的 ds 所属段被显示且勾选状态正确。
11. 迁移说明 / 取舍 / 待定
- 替代 2026-06-26 spec:该 spec 的「一场景两相机 + 按维度显隐 + 高程拖动分层」中,相机锁定 + 维度显隐被本 spec 推翻(改单一自由场景共存)。其「2D 沿 Z 拖动分离」语义改由「按类型平面 + z 滑块」承担——逐 ds 独立拖动
nudgeSelectedMapLinesZ/mapLineZOffset_(及拾取拖动浮层读数)废弃移除,统一到类型平面 z。 dd_raster仍未接(2026-06-26 §6 遗留),本 spec 不含;后续作为新的 2D 类型段加入时复用 §8.2/§9.2 平面+底图机制。- 3D 反演段 vs 三维体段:均为 D3,但 3D 反演段渲染为帘面、三维体段为体素/雷达体——共用 3D 底图与自由场景,无需区分。
- 责任拆分:
CategoryDescriptor/categoryCatalog(描述符)、IDatasetRenderStrategy注册表(渲染)、SectionIconBar(响应式工具条)、TileBasemap多实例、DatasetColumn(动态显隐)相互独立,可分别实现与测试。 - 抽象的 YAGNI 边界:
FilterKind/OpKind/SceneKind只列当前真用到的枚举值;渲染策略只实现volume/curtain/plane2d三个。扩展点是「加新值/新策略」的预留口,不预先实现任何未用类型。
12. 验收
- 左侧只有一个无标题数据集栏(无 tab);其中同时出现 3D 类型段(电阻率/视电阻率/瞬变/三维体)与 2D 类型段(轨迹)。
- 段动态显隐:无对应数据的段不显示;面板全空时显示居中占位提示。
- 段头操作为图标工具条:默认最多 3 个,超出/挤压时右侧收进「…」下拉。3D 反演段含「新增三维体/筛选」,2D 轨迹段含「z值/筛选/底图」。
- 「筛选」图标可展开/收起段内筛选行(默认折叠)。
- 勾选 2D 轨迹:同类型 ds 投影到一块纯平平面,平面 z = 首个勾选 ds 的 z(之后固定);「z值」滑块整体升降该平面;「底图」popup 可换矢量平面底图/无 + 调透明度(默认 50%)。该类型全部取消勾选 → 平面与其底图一并消失。
- 3D 与 2D 数据在同一自由透视场景同时可见,可自由旋转(无锁定俯视、无 tab 切换)。
- 渲染区工具栏 Gear 下方新增「地图」按钮,控制全局 3D 底图(天地图/无 + 透明度默认 50%)。
- 「导入雷达」入口出现在顶部「设备」菜单;三维体段头不再有该按钮。
- 「直接挂项目下的 ds」加载时默认勾选进 VTK 不被破坏。
- 可扩展性:现存 5 类全部经
categoryCatalog()描述符 + 渲染策略注册表驱动,控制器/段头无维度/ddCode 散判。新增一个「沿用已有渲染/操作/筛选」的类型,只需在 catalog 加一条描述符即可显示+渲染(以一个验证性 demo 描述符或单元测试佐证分类/路由走通)。