Compare commits

..

No commits in common. "2c204a134aec12fe91a0970d04a0f2d511ff4ff5" and "498b786c806ac13276e5f8fec893da05907a9456" have entirely different histories.

25 changed files with 152 additions and 1463 deletions

View File

@ -25,17 +25,14 @@ if not exist "%VSWHERE%" (
echo [build] vswhere not found. Open "x64 Native Tools Command Prompt for VS" and build manually. echo [build] vswhere not found. Open "x64 Native Tools Command Prompt for VS" and build manually.
exit /b 1 exit /b 1
) )
REM -all -prerelease for VS2026 preview (note: -latest yields empty on this preview, and for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -property installationPath`) do set "VSPATH=%%i"
REM -products * would pull in the bundled BuildTools whose vcpkg/env breaks our preset); if not defined VSPATH ( echo [build] Visual Studio not found. & exit /b 1 )
REM -requires ensures the C++ toolset is present. Multiple installs -> last one wins.
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -all -prerelease -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
if not defined VSPATH ( echo [build] Visual Studio with C++ toolset not found. Install the VS Desktop C++ workload. & exit /b 1 )
set "VCVARS=%VSPATH%\VC\Auxiliary\Build\vcvars64.bat" set "VCVARS=%VSPATH%\VC\Auxiliary\Build\vcvars64.bat"
set "CMAKE=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" set "CMAKE=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
set "CTEST=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe" set "CTEST=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe"
if not exist "%VCVARS%" ( echo [build] vcvars64.bat not found: "%VCVARS%" & exit /b 1 ) if not exist "%VCVARS%" ( echo [build] vcvars64.bat not found: %VCVARS% & exit /b 1 )
if not exist "%CMAKE%" ( echo [build] cmake not found: "%CMAKE%" & exit /b 1 ) if not exist "%CMAKE%" ( echo [build] cmake not found: %CMAKE% & exit /b 1 )
REM --- activate MSVC environment (cl / link / include / lib) --- REM --- activate MSVC environment (cl / link / include / lib) ---
call "%VCVARS%" >nul call "%VCVARS%" >nul

View File

@ -1,73 +0,0 @@
# P1复活中央 VTK 渲染(地基)
- 日期2026-06-15
- 分支:`feat/vtk-3d-view`
- 上游 spec`specs/2026-06-15-vtk-3d-supplementary-design.md`§8 编排层、§5.2 Scene 缺口、§6 接口)
- 目标:把当前**空壳**中央 VTK 复活为数据驱动——勾选对象 → 经仓储取数据 → 调现有 actor → 渲染。复用 render 层零改 actor除 Scene 加 `vtkProp` 入口)。这是后续所有 3D 功能的地基,**最低风险、最快见效**。
- 不在本期:三栏 UI、坐标轴、切片交互、异常体、任务面板P2+)。本期只让"勾选→出图"重新跑通,并立起 `I3dSceneRepository` + `VtkSceneController` 两个骨架。
---
## 成功判据goal-driven
1. 启动 → 勾选样本对象 → 中央 3D 视图出现帘面(`buildCurtain`2D 模式出现俯视测线(`buildSurveyLine`)。
2. 「视图详情」浮层勾选 体素/地形 → 对应 actor 出现/消失(不再是死代码)。
3. `VtkSceneController` 的编排逻辑有单测fake repo断言 actor 集合),不依赖 GUI。
4. `main.cpp` 中死掉的 `rebuildCentral` lambda + 裸 `show*` 标志被 `VtkSceneController` 取代;`slicePlane` 等未用声明清理(仅清本期引入/相关的孤儿,不动无关代码)。
5. 构建通过 + 现有测试全绿 + 新测试通过。
---
## 阶段(每步含 verify
### Step 0 — 基线
- 跑现有测试与构建,记录绿基线。→ verify`ctest` 全绿、`cmake --build` 通过。
### Step 1 — Scene 支持体绘制 vtkVolume评审 HIGHTDD
- `src/render/Scene.hpp/.cpp`:新增 `void addViewProp(vtkProp* p)`(内部 `renderer_->AddViewProp``addActor` 保留(可改为转调 addViewProp
- 先写测试 `tests/render/test_scene.cpp`:加入一个 `vtkVolume`(或 mock prop`renderer()->GetViewProps()` 计数 +1`clear()` 后归零。
- → verifyRED→GREEN体绘制 prop 能进场。
### Step 2 — I3dSceneRepository + LocalSample3dRepositoryTDD
- `src/data/repo/I3dSceneRepository.hpp`**异步**接口(回调/Qt 信号范式,见 spec §6。本期最小集
- `DsDimension dimensionOf(const DsRow&)`同步纯函数§6.1 映射表)。
- `loadVolume(dsId, onOk(VolumeGrid), onErr)`§6.2)。
- `loadTerrainPaths(onOk(demPath,imagePath), onErr)`(包 `LocalSampleRepository::demPath/imagePath`)。
- 切片/异常/任务等签名**本期只声明占位**(留 P3/P4不实现。
- `src/data/repo/LocalSample3dRepository.*`:组合现有 `LocalSampleRepository``loadVoxelScatters`→`VoxelFromScatters` 出 `VolumeGrid`DEM/影像路径直透)。同步数据包成异步壳。
- 测试 `tests/data/test_3d_repo.cpp``dimensionOf` 各 ddCode 映射正确;`loadVolume` 回调收到 `vol.nx()>0 && vmax>vmin`
- → verifyRED→GREEN。
### Step 3 — VtkSceneControllerTDD 编排逻辑)
- `src/controller/VtkSceneController.hpp/.cpp`QObject
- 输入(槽/setter`setCheckedDatasets(QStringList)`、`setViewMode(Map2D/View3D)`、`setLayer(Curtain/Voxel/Terrain, bool)`、`setVerticalExaggeration(double)`。
- 内部:按维度/图层决定调哪些 actor builder缓存 `VolumeGrid`/`Grid` 避免重复取数。
- 输出:`scene.clear()` → `addActor/addViewProp``renderWindow->Render()`。把现有 `rebuildCentralScene` 的帘面/测线逻辑并入,新增 voxel(`buildVoxelFromScatters`→`addViewProp`)、terrain(`buildTerrain`) 分支。
- 测试 `tests/controller/test_vtk_scene_controller.cpp`:注入 **fake repo + fake scene**(记录 add 的 prop 类型/数量),断言:
- 2D 模式 + 勾选 1 ds → 1 个测线 actor
- 3D 模式 + showCurtain → 1 帘面 actor+ showVoxel → 多 1 个 volume prop+ showTerrain → 多 1 个 terrain actor
- 取消勾选 → clear 后无 prop。
- → verifyRED→GREEN编排逻辑脱离 GUI 可测。
### Step 4 — 接入 main.cpp集成
- 用 `VtkSceneController` 实例取代 `rebuildCentral` lambda`main.cpp:508`)与裸 `show*` 标志246251
- 接线:`ObjectTreePanel` 勾选变化 → `setCheckedDatasets``act2D/act3D` → `setViewMode``chkCurtain/chkVoxel/chkTerrain` → `setLayer`;主题切换 → 触发重渲染(控制器内重跑)。
- 清理本期产生的孤儿:未用的 `slicePlane` 声明、被取代的 lambda/标志(只清相关项,遵守 surgical changes。`chkSlice` 暂保留禁用P3 接)。
- → verify构建通过启动手测达成「成功判据 1、2」。
### Step 5 — 构建 + 运行验证
- `cmake --build` + `ctest`
- 运行客户端,勾选样本对象,截图确认帘面/体素/地形渲染。
- → verify成功判据 15 全达成。
### Step 6 — 代码审查 + 提交
- `cpp-reviewer` 审查(内存安全/RAII/分层);按需修。
- 提交:`feat(vtk): P1 复活中央渲染 — VtkSceneController + I3dSceneRepository(LocalSample) + Scene 加 vtkProp 入口`。
---
## 风险 / 注意
- **Ninja 增量不可靠**(记忆 [[build-ninja-stale-shared-header]]):改 `Field/RepoTypes` 等共享头后,若崩溃先 clean 重编、验 obj 新鲜度。
- 体绘制 `vtkVolume``addViewProp` 进场;`clear()` 须用 `RemoveAllViewProps` 覆盖 volume确认现有 `clear()` 实现)。
- LocalSample 只合成单 GS→TM→DS勾选粒度对齐其结构即可真实多对象/多剖面在接 Api 实现时验证。
- 接口务必异步壳,别图省事写同步(评审 HIGH否则 P3+ 接后端返工)。

View File

@ -1,43 +0,0 @@
# P2三维数据集栏坐标轴 / 比例 / 快捷视图 / Zoom
- 日期2026-06-15
- 分支:`feat/vtk-3d-view`
- 上游spec `2026-06-15-vtk-3d-supplementary-design.md`§4 行 C3C8、§5.3、§7.2);接 P1`VtkSceneController`/`I3dSceneView`/`VtkSceneView` 已就位)
- 目标:给三维视图加一组**纯前端、无后端依赖**的相机/坐标轴/比例控件(补充需求"三维数据集栏"工具条的功能项。P1 已让中央渲染复活P2 让用户能调坐标轴、纵向比例、快捷视角、缩放。
## 范围
**范围内**
- **快捷视图**C7前/后/左/右/上/下 6 向相机预设 + **Zoom In/Out/Fit**C8
- **水平/垂直比例**C6可调纵向夸张复用 P1 已有 `VtkSceneController::setVerticalExaggeration`UI 滑块/输入;范围如 110默认 2。
- **坐标轴**C3I5显示方式 标准/三维立体/不显示;刻度单位 无/米/英尺/经纬度O 点位置;字体。
- 控件挂到三维视图工具条/浮层(**不做**完整三栏 tab 重构——留后续 UI 期;本期控件先以工具条形式接入现有三维视图)。
**范围外/后续**:三栏 tab 结构重构、二维数据集栏底图P5、三维分析切片P3
## 关键设计
- **CameraPreset 扩展**`src/render/CameraPreset.*``applyView(vtkRenderer*, ViewDir)``ViewDir∈{Front,Back,Left,Right,Top,Bottom}`,设 position/focalPoint/viewUp 后 `ResetCamera``zoomBy(vtkRenderer*, factor)`In=1.2/Out=1/1.2`fit(=ResetCamera)`。世界系x=East,y=North,z=-depth与 actor 一致)。
- **坐标轴**(新 `src/render/actors/AxesActor.*``CubeAxes.*`
- 标准 = `vtkCubeAxesActor`(包围盒 + 刻度 + 标签);三维立体 = `vtkCubeAxesActor` 闭合立方/或叠加 `vtkAxesActor` 方向标(**语义待 1.0 确认,先合理近似**);不显示 = 不加。
- 刻度单位:米(原值)/英尺(×3.28084)/经纬度(用 `GeoLocalFrame` 反算)/无(隐藏刻度标签)。
- O 点:默认数据包围盒角;字体:`SetTitleTextProperty/SetLabelTextProperty` 设字号字体。
- 输入 = renderer bounds + frame产出 `vtkSmartPointer<vtkCubeAxesActor>`,由 `I3dSceneView` 加入场景(经 `addViewProp`)。
- **GeoLocalFrame 补反算**`LocalXY → (lat,lon)`,加 `toLatLon(double x,double y)`等距圆柱反算lon=lon0+x/mPerDegLonlat=lat0+y/mPerDegLat
- **I3dSceneView 扩接口**`setAxes(mode,unit,font,...)`、`applyCameraView(ViewDir)`、`zoom(factor)`、`fitView()``VtkSceneView` 实现fake 测试记录。
- **VtkSceneController**:加 `setAxesMode/setAxesUnit/...`、`applyView/zoomIn/zoomOut/fit` 槽,转发给 view坐标轴在 rebuild 时按当前模式重建(轴随数据包围盒变)。
## 步骤TDD
0. 基线:`build.bat test` 全绿。
1. **GeoLocalFrame.toLatLon**TDD往返 `toLocal∘toLatLon≈恒等` 测试。
2. **CameraPreset 6 向 + zoom**TDD各 ViewDir 断言 position/focalPoint/viewUp 方向正确(如 Top: pos.z>focal.z、viewUp=+yFront: pos.y<focalviewUp=+z zoomBy 改变 parallelScale/距离
3. **AxesActor 构建**TDD给定 bounds+unit 产出 vtkCubeAxesActor断言单位换算英尺/经纬度标签)、不显示返回空。
4. **I3dSceneView 扩接口 + VtkSceneView 实现 + 控制器槽**TDD 控制器编排fake view 断言 axes/camera 调用)。
5. **UI 接入 main.cpp**:三维视图工具条加 坐标轴下拉/刻度下拉/比例滑块/6 向快捷钮/Zoom 钮;连到控制器槽。仅三维模式显示该工具条。
6. `build.bat test` 全绿 + `build.bat app` 链接通过;目视清单交用户。
7. cpp-reviewer 审查 + 提交。
## 风险/注意
- **Ninja/工具链**:增量构建若链接错用 `cmake --build build\release --clean-first`(保留 cache**勿删 build 目录**vcpkg baseline 已修但仍按记忆 [[build-vs2026-vcpkg-gotchas]] 谨慎)。
- 坐标轴 "标准 vs 三维立体" 语义无 1.0 参考 → 先合理近似UI 可切换,待 1.0 实地学习再精修(记忆 [[study-original-via-playwright]])。
- 经纬度刻度仅在有 frame 配准的 3D 世界系下有意义;纯剖面坐标系下退化为米。
- 比例滑块改变 → 控制器 setVerticalExaggeration → 全 3D actor SetScale 同步P1 已统一)。
- 字体中文标签需可用字体VTK 文本默认 Arial中文可能缺字形 → 标签用数字/英文单位优先。

View File

@ -1,392 +0,0 @@
# VTK 三维视图「补充需求」设计
- 日期2026-06-15
- 分支建议:`feat/vtk-3d-view`
- 状态:**设计稿 v2已纳入架构评审 + web 端实地分析)**。本轮只产出设计,实现分期另立 plan。
- 需求来源:`D:\Projects\GEOPRO\Geopro3.0 需求表.xlsx` →「补充需求」页签已逐行通读§4 全文映射交叉参考「DD类型」页签原版 web 实地分析见 §1.5、记忆 [[web-3d-view-threetile]]。
- 关键约束(用户已拍板):
1. **后端 API 尚未就绪** —— 本轮用 `LocalSampleRepository` 静态数据驱动渲染与交互,**但仓储接口必须按真实后端形态设计好**§6将来后端到位只换实现、不动上层。
2. 三维相关 ddCode`dd_Structual3D`/`dd_Property3D`/切片/异常体/任务记录)后端、客户端**都还没做**,是净增建。
3. **三栏结构 + 切片是客户端新需求**web 端没有三栏,见 §1.5)。故这两块**以 xlsx 为规格、按 VTK 工程新设计**,不受"复刻须先实地学习"约束;而"参考 Geopro 1.0"的色阶/异常框F26/F50属复刻项、目前无 1.0 参考、先近似后精修。
---
## 0. TL;DR一页结论
「补充需求」整页都是 **VTK 中央视图**的规格:把视图区拆成三个子列表栏(**三维数据集 / 二维数据集 / 三维分析**),并补齐坐标轴、相机预设、底图、**切片交互**、**异常体管理**、三维体详情、任务管理。
**当前真实状态(已源码核实,非推测):中央 VTK 是空壳。**
- `rebuildCentral``src/app/main.cpp:508-512`)只捕获 `showCurtain`,喂入**空 `sections`**;启动后中央区只有背景色 + 相机,不渲染任何数据。
- 浮层勾选框 `chkVoxel/chkSlice/chkTerrain``main.cpp:603-617`)只改 `bool`,而 `rebuildCentralScene``src/app/CentralScene.cpp:16-47`)只处理 `is2D`/`showCurtain` 两支 —— 体素/切片/地形勾选**当前什么都不做**。
- `slicePlane``vtkImagePlaneWidget``main.cpp:251`)声明后**全代码零使用**`buildVoxelFromScatters/buildTerrain/loadVoxelScatters` 在 app 层**零调用**。
**好消息render 层 actor 全部完整且有测试**§5.1),数据源 `LocalSampleRepository` 也在。"之前用静态数据做的原型渲染"git `7007619`/`9b77d07` 等)的**装配代码**在 `6241eb3`"CentralScene 数据驱动重构"时被摘除,但**渲染原语没丢**。
> 一句话地基actor 管线)稳固,缺的是「编排层 + 交互层 + 三栏 UI + 真实/样本数据接入」。
**评审已纳入的硬伤修正**(详见各节):① `Scene` 只能加 `vtkActor`、加不了体绘制 `vtkVolume`"actor 零改动复活"对三维体不成立 → §5.2/§8 增 `vtkProp` 入口;② `I3dSceneRepository` 必须**异步**(项目已 ApiChain 异步化,同步签名会让"换实现不动上层"破产)→ §6③ 任意切片**钉死 `vtkImageReslice`**`vtkCutter` 切体素只出交线不出着色剖面)→ §9.1;④ 接口数据结构去裸 `double[]`/出参 → §6.2/§6.3。
---
## 1. 背景与问题
需求表「补充需求」页签定义的是 Geopro 1.0 风格的三维工作台 —— VTK 视图不再是"二维地图/三维视图"二选一的展示器,而是一个带**子列表 + 工具栏 + 右键菜单 + 切片/异常交互**的分析环境。
当前客户端M1的 VTK 现状与之差距:
1. **结构不符**:现在是单一 `QVTKOpenGLStereoWidget` + 顶部「二维地图/三维视图」互斥分段按钮(`main.cpp:308-316`),左下一个统一数据集列表。需求要的是**视图内置三个子列表栏**。
2. **不渲染数据**:见 §0编排层喂空数据所有图层勾选是死代码。
3. **无坐标轴 / 无相机预设 / 无底图 / 无切片交互 / 无拾取联动 / 无 3D 异常体 / 无任务面板**:这些都是净增建。
4. **后端缺接口**:已核对 `ApiClient` 端点§6.0),只有 2D ERT 那套;三维体模型/切片保存/异常创建/任务记录端点客户端都没有,且后端也未做。
---
## 1.5 原版web实地分析结论Playwright + JS 逆推2026-06-15
实地分析 `http://tenant.geomative.cn/#/projectSpace/dataView`(详见记忆 [[web-3d-view-threetile]]
- **渲染技术栈**web 3D 地球是 **ThreeTileThree.js 瓦片地球)**——DOM 无 `cesium-widget`、单 WebGL2 canvas、`__THREE__` 在Cesium 1.107 虽加载但本页未用。⇒ 桌面用 VTK 是**另一套实现**,三栏/切片是桌面新设计,不存在"web 怎么做桌面照搬"。
- **底图多源已确证**`threeMap` chunk天地图/Google/Bing/高德/腾讯/ArcGIS/Mapbox/中科星图 ⇒ 坐实补充需求 C13 底图需求,桌面 VTK 需自建瓦片层。
- **3D 数据通路已确证**(网络):勾选 ERT 对象 → 批量 `dd/ert/inversion/rows/{id}`(2D 反演剖面) + `lvl/colorGradation` → 在 3D 地理空间摆成**竖直帘面**= 桌面 `CurtainActor` 目标)。
- **3D 分析功能集已确证**index chunk i18n `three*` 键):模型 选/移/转/缩放/恢复 + X·Y·Z 轴分散;剖切面 开/关/显/隐/平移/旋转;创建异常/异常列表;导出 dat/grd/bin/lvl。3D 模型从 URL 加载。⇒ 这些功能桌面要做,但**实现自定**web 用 Three.js桌面用 VTK
- **web 不是三栏**web 是 左(数据筛选+对象树)/中(地球+显示隐藏地图+滑块)/右(异常列表)。补充需求的三栏(三维数据集/二维数据集/三维分析)是**桌面新结构**。
- **威立雅项目只有 2D 反演剖面(帘面),无真正三维体模型**;真三维体(`dd_Structual3D`,"地大展示")很可能在别的项目,待用户指认后再实地学习其切片/异常几何(不阻塞 P1P3因切片可对 LocalSample 体素开发)。
---
## 2. 目标与范围
### 2.1 总目标
在现有 render actor 地基上,复活并扩展中央 VTK 为「补充需求」描述的三维分析工作台;**用静态样本数据驱动**,但所有数据出入口走**为真实后端预留的仓储接口**§6
### 2.2 范围内(本设计覆盖)
- 三子列表栏 UI 结构(三维数据集 / 二维数据集 / 三维分析)。
- 三维数据集栏:坐标轴设置、水平/垂直比例、快捷视图前后左右上下、ZoomIn/Out/Fit、数据集勾选 → 渲染。
- 二维数据集栏:底图(天地图/Google/隐藏、2D 视图切面位置、数据集勾选 → 渲染。
- 三维分析栏:体模型/切片树、右键菜单、切片交互工具、拾取选中联动、切片分析(含创建异常、保存为数据集、导出、正视/翻转/关闭)。
- 三维体数据集详情、切片数据集详情。
- 异常/异常体管理树、删除、属性、VTK 联动、显示过滤、截图属性)。
- 任务管理(任务记录 + 可使用任务列表)。
- **仓储接口设计**§6—— 本设计的重头,决定将来接后端的成本。
### 2.3 范围外 / 后续
- 后端真实接口实现(本轮仅设计接口 + LocalSample 实现)。
- 克里金插值IDW 已有;克里金作为插值模型选项后续补)。
- 二维底图的高保真瓦片缓存/离线(本轮先打通"能显示底图")。
- 与 Web 端完全像素级一致的色阶编辑器(沿用现有 colorBar 解析)。
### 2.4 设计原则
- **render 层零业务**actor 只吃 `core::*` 数据结构(`Grid/ScalarVolume/ScatterField/Anomaly`),不认 ds/API。复活编排即可不改 actor。
- **接口先行**:上层只依赖 `I3dSceneRepository`§6`LocalSample3dRepository` 与未来 `Api3dRepository` 可互换。
- **交互独立成层**:新建 `src/render/interact/`README 早有规划但目录不存在),切片/拾取/圈异常都是 `InteractionTool`
---
## 3. 与既有架构的关系
```
┌─────────────────────────── 中央 VTK 工作台 ───────────────────────────┐
ObjectTreePanel │ ┌ 子列表栏(三选一/并列)────────┐ ┌ VTK 视图区 ──────────────────┐ │
(左上,勾选对象) │ │ [三维数据集] [二维数据集] [三维分析]│ │ Scene(renderer) + actors │ │
│勾选 │ │ · 坐标轴/比例/快捷视图/Zoom 工具条 │ │ + CubeAxes + 底图层 │ │
▼ │ │ · 数据集勾选列表(按维度属性筛) │ │ + InteractionManager(切片/拾取)│ │
VtkSceneController ─┼─→ I3dSceneRepository ─→ core::数据 ─→ render actors ─→ Scene │ │
│ └────────────────────────────────┘ └──────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
右栏: 三维体/切片 详情 · 异常体树 · 任务管理(任务记录 + 可用任务列表)
```
- `VtkSceneController`(新)取代当前散落在 `main.cpp` 的 lambda 编排,订阅"勾选对象/数据集/图层/视图模式"变化,经 `I3dSceneRepository` 取数据,调 render actor 重建场景。
- 现有 `WorkbenchNavController`(对象/数据集导航)、`DatasetDetailController`(详情)保持职责不变,新控制器与之并列。
---
## 4. 「补充需求」全文逐行映射(权威对照表)
> 维度属性来源:需求称"ds 类型的显示属性为 3D/2D",即**维度属性挂在 ds 类型上**(非单条 ds。`DsRow` 当前无此字段(`src/data/repo/RepoTypes.hpp:10`),须由 §6.1 的类型→维度映射补。
| 行 | 需求原文(摘) | 现状 | 落地方式 |
|---|---|---|---|
| A1 | VTK 提供三子列表栏:三维数据集/二维数据集/三维分析 | 单一 2D/3D 切换 | §7.1 三栏 UI |
| C3I3 | 坐标轴显示方式:标准/三维立体/不显示 | 无坐标轴 | `vtkCubeAxesActor`(标准)/`vtkAxesActor`(立体)/隐藏§7.2 |
| D4 | O 点位置 | 无 | 坐标轴原点配置(世界系原点 / 数据包围盒角)|
| D5I5 | 刻度:无刻度/米/英尺/经纬度/字体 | 无 | 刻度单位换算 + 经纬度用 `GeoLocalFrame` 反算 + 字体设置 |
| C6 | 水平/垂直比例 | 硬编码 `kVerticalExaggeration=2.0``main.cpp:208`| 做成可调,作用于全部 3D actor `SetScale(1,1,VE)` |
| C7 / D7I7 | 快捷视图:前/后/左/右/上/下 | `CameraPreset` 仅 Top2D/Free3D | 新增 6 方向预设§5.3|
| C8 / D8F8 | ZoomIn/Out/Fit | 仅 `ResetCamera`(=Fit) | In/Out 调 `camera->Zoom()`Fit=ResetCamera |
| C9D10 | 三维数据集列表:筛勾选对象中 ds 维度=3D勾选多个显示 | sections 为空 | §6.1 维度筛选 + §8 编排复活 |
| C13F13 | 地图:天地图/Google Map/隐藏 | 中央无底图 | §7.3 底图层VTK 瓦片)|
| C14 | 2D 视图:关闭/Z=0/顶部/底部/自定义 Z | 无 | 切面高度控制 |
| C15D16 | 二维数据集列表ds 维度=2D | 同上 | §6.1 + §8 |
| C19D20 | 三维分析列表:按 对象/三维体模型/切片 树 | 无 | §7.4 分析树 |
| D21,F22G22 | 右键·三维体:上下切片 | 无 | §9 轴向切片工具(水平面)|
| F23 | 前后切片 | 无 | §9 轴向切片 |
| F24 | 左右切片 | 无 | §9 轴向切片 |
| F25 | 任意切片(初始 45°可任意调整| 无 | §9 任意角度切片(`vtkPlaneWidget` 控面 + **`vtkImageReslice`** 重采样着色,非 `vtkCutter`|
| F26 | 色阶(参考 Geopro 1.0 优化)| 有 colorBar 解析 | 复用 `ColorScale` + 色阶编辑入口 |
| F27 | 显示/隐藏 | 无 | actor 可见性 |
| F28G28 | 数据详情 → 详情栏 | 详情面板在 | §10 三维体详情类型 |
| E29,F30F36 | 右键·切片数据集:保存/保存为/导出/删除/色阶/显隐/详情 | 无 | §6.3 切片 CRUD 接口 + §10 |
| C38,D39 | 选中三维体/切片为中心拖动旋转 | 无拾取/自定义 style | §9.3 拾取 + 自定义 InteractorStyle |
| D40 | 双击切片 → 正视该切片 | 无 | §9.3 双击→相机正视切面法向 |
| C44,D44 | 右键三维体创建切片工具 | 无 | §9 |
| D45 | 双击切片工具正视 | 无 | §9.3 |
| D46 | 选中切片滚轮 → 内外切片(对象=所属三维体)| 无 | §9.2 滚轮沿法向平移切面 |
| D47 | 切片任一位置可保存为新切片数据集 | 无 | §6.3 createSlice |
| D48,E49,F49F50 | 创建异常:异常工具,光标拾取起点;结束保存弹框(确定截图大小/异常坐标,参考 1.0| 有 2D `AnomalyActor`,无创建流 | §9.4 异常圈定工具 + §6.4 saveAnomaly + 截图 |
| E51F51 | 保存为数据集 | 无 | §6.3 |
| E52F52 | 导出为图片 | 无 | `vtkWindowToImageFilter` |
| E53F53 | 导出到 dat | 无 | 切面采样导出 |
| E54F54 | 正视图 | 无 | §9.3 |
| E55F55 | 视图翻转(水平旋转 180°| 无 | 相机 `Azimuth(180)` |
| E56F56 | 关闭(取消当前切片)| 无 | 移除切片工具 |
| A58,B59B65 | 三维体详情:源数据/切片数据/异常/插值模型(克里金,IDW)/插值参数/色阶参数/测量(点数,体积)| 详情引擎在,类型未做 | §10.1 |
| A67 | 切片详情:参照 dd_Section | `dd_section` 渲染已有 | §10.2 复用 |
| A69,B70,C71D77 | 异常体列表树:异常体→分组→异常 | `ObjectExceptionPanel` 只读树在 | §11 扩展为可操作 |
| C79,D80D81 | 操作:删除/删除分组 | 无 | §6.4 delete/deleteGroup |
| C83,C84 | 异常属性 + VTK↔列表 双向选中联动 | 无联动 | §9.3 + §11 |
| B86,C87F87 | 异常体显示过滤:全部显示/随GS/随数据集/全部隐藏 | 无 | §11 过滤模式 |
| B88,C88 | 异常增加截图属性(保存标识时截图)| 无 | §6.4 截图字段 |
| A90,C90 | 任务管理:任务记录 + 可使用任务列表 | 有 `model/list`,无任务面板 | §12 |
| B91,C91 | 任务记录:当前数据集调用任务的记录 | 无 | §6.5 taskRecords |
| B92,C92 | 可使用任务列表:与 ds 类型相符的任务插件 | `ModelInfo`(model/list) 在 | §6.5 usableTasks(按 ddType 过滤) |
---
## 5. 现状资产盘点render 层,已源码核实)
### 5.1 可直接复用的 actor完整 + 有测试)
| 组件 | 文件 | 吃什么 | 产出 | 复用于补充需求 |
|---|---|---|---|---|
| ScatterActor | `src/render/actors/ScatterActor.*` | `ScatterField+ColorScale` | 散点 actor | 三维体源数据点 |
| GridContourActor | `actors/GridContourActor.*` | `Grid+ColorScale` | 色带+等值线 | 切片面着色 |
| VoxelActor | `actors/VoxelActor.*` | `ScalarVolume+ColorScale+原点/间距` | **GPU 体绘制 `vtkVolume`**,可导出内部 `vtkImageData` | 三维体模型渲染 + 切片输入 |
| VoxelFromScatters | `render/VoxelFromScatters.*` | 多剖面散点+CRS+frame | IDW→体绘制+image | 三维体(散点插值)|
| AnomalyActor | `actors/AnomalyActor.*` | `vector<Anomaly>` | 点/折线/多边形(**2D 剖面**| 切片上异常圈定(需扩 3D 异常体)|
| TerrainActor | `actors/TerrainActor.*` | DEM+影像 GeoTIFF | 纹理地形 | 三维场景地形图层 |
| CurtainActor | `actors/CurtainActor.*` | `Grid+ColorScale+frame` | 竖直帘面 | dd_section 三维帘面 |
| MapLineActor | `actors/MapLineActor.*` | `Grid+frame` | 俯视红线 | 二维数据集测线 |
| ElectrodeActor | `actors/ElectrodeActor.*` | `Grid` | 电极 ▼ | 剖面电极 |
| ColorLutBuilder / ContourBands | `render/ColorLutBuilder.*` `ContourBands.*` | colorBar | LUT / 离线等值线几何 | 色阶 / 导出 |
### 5.2 缺口(净增建)
- **Scene 加不了体绘制**(评审 HIGH`Scene::addActor(vtkActor*)``src/render/Scene.hpp`)只收 `vtkActor`,而 VoxelActor 产物 `vtkVolume``vtkProp3D`、**不是** `vtkActor`。三维体(核心需求)渲染必经此口 → P1 须给 Scene 加 `addViewProp(vtkProp*)`(或 `addVolume(vtkVolume*)`5 行小改,但"actor 零改动复活"对三维体不成立。
- **坐标轴**:无 `vtkCubeAxesActor`/`vtkAxesActor`。
- **相机预设**`CameraPreset.cpp` 仅 `applyTop2D`/`applyFree3D`,缺前后左右上下 + Zoom In/Out。
- **底图**:无 VTK 瓦片底图层README 提的 `ground/TileGroundLayer` 未实现)。
- **切片交互**`vtkImagePlaneWidget` 声明未用;无 reslice/cutter/任意角度切片;`src/render/interact/` 目录不存在。
- **拾取/选中联动**:无 picker、无自定义 `vtkInteractorStyle`
- **3D 异常体**`AnomalyActor` 仅 2D 剖面;无三维异常体 actor、无 VTK↔列表联动。
- **截图**:无 `vtkWindowToImageFilter` 接线。
- **编排层**`rebuildCentralScene` 喂空数据;图层勾选死代码。
### 5.3 CameraPreset 扩展(小量、低风险,可独立先做)
新增 `applyView(vtkRenderer*, ViewDir)``ViewDir ∈ {Front,Back,Left,Right,Top,Bottom}`,设置 position/focalPoint/viewUp 后 `ResetCamera``zoomIn/zoomOut` 调 `camera->Zoom(1.2 / 0.83)``Render`
---
## 6. 仓储接口设计(本设计重头 —— 用户要求"接口设计好"
> 现状:`IDatasetRepository``src/data/repo/IDatasetRepository.hpp`)已有 2D 那套loadGrid/Scatter/ColorScale/Anomalies。本设计**新增并列接口 `I3dSceneRepository`**,承载三维体/切片/异常体/任务。本轮 `LocalSample3dRepository` 实现(读样本文件 + 内存态增删),将来 `Api3dRepository` 换实现。
> **接口必须异步(评审 HIGH**:项目已 ApiChain/`IAsyncProjectRepository` 异步化。`I3dSceneRepository` 取数方法**一律走回调/Qt 信号**(如 `void loadVolume(dsId, std::function<void(VolumeGrid)> onOk, OnErr onErr)`,或返回 `ApiChain`LocalSample 同步数据也包成异步壳。否则将来换 Api 实现会阻塞 UI、上层全要改"换实现不动上层"破产。下文签名为**简化示意(省略回调形参)**,落地按异步范式。
### 6.0 后端端点现状(已核对,非推测)
现有 `ApiClient` 仅:`dd/ert/{grid,inversion,measurement,trajectory}`、`dsObject` CRUD/import/detail、`exception/queryException`**只读**)、`model/list`、`lvl/colorGradation`、`templateExport`。
**缺**:三维体模型数据、切片 CRUD、异常**创建/保存**、任务记录。→ 本轮接口为这些预留方法签名LocalSample 内存实现。
### 6.1 维度属性(解决"ds 维度=3D/2D"筛选)
```cpp
enum class DsDimension { Dim2D, Dim3D, Analysis3D, Other };
// 由 ds 类型ddCode / dsTypeId决定。LocalSample 用内置映射表;
// 未来后端可在 dsObject/getDetail 返回 dimension 字段。
DsDimension dimensionOf(const DsRow& ds) const; // 列表筛选用
```
> LocalSample 映射依据「DD类型」页签语义`dd_Structual3D`/`dd_Property3D`/`dd_voxel` → `Dim3D`/`Analysis3D``dd_section`/`dd_inversion_data` 帘面可入 3D俯视测线入 2D`dd_trajectory_data` → `Dim2D`
### 6.2 三维体模型数据集
```cpp
struct VolumeModelMeta {
std::string dsId, name;
std::string interpMethod; // "IDW" / "Kriging" / ...
std::vector<DynamicFormField> interpParams; // 插值参数(复用既有动态表单字段)
std::string colorScaleId; // 色阶参数引用
double pointCount = 0, volume = 0; // 测量数据(点数/体积)
std::vector<std::string> sourceDsIds; // 源数据集
std::vector<std::string> sliceDsIds; // 切片数据集
std::vector<std::string> anomalyBodyIds; // 异常体
};
// 规则体 + 原点/间距聚合返回(去 double& 出参,评审 MEDIUM
struct VolumeGrid {
geopro::core::ScalarVolume vol;
std::array<double,3> origin; // ox,oy,oz
std::array<double,3> spacing; // dx,dy,dz
double vmin = 0, vmax = 0;
};
VolumeModelMeta loadVolumeMeta(const std::string& dsId); // 异步示意
VolumeGrid loadVolume(const std::string& dsId); // 异步示意
```
> LocalSample`VoxelFromScatters` 把样本两条交叉剖面散点 IDW 成 `ScalarVolume` 当作一个三维体模型(产物即含 `vtkImageData`,切片直接复用)。
### 6.3 切片数据集CRUD —— 后端缺,先内存实现)
```cpp
struct SliceSpec {
std::string volumeDsId; // 所属三维体
std::array<double,3> origin; // 切面一点(去裸数组,评审 MEDIUM
std::array<double,3> normal; // 切面法向(轴向/任意)
std::string colorScaleId;
};
struct SliceDataset { std::string dsId, name; SliceSpec spec; geopro::core::Grid section; };
// 落库 CRUD进仓储
std::string createSlice(const SliceSpec& spec, const std::string& name); // 保存为数据集→返回新 dsId
void saveSlice(const std::string& dsId, const SliceSpec& spec); // 保存
void deleteSlice(const std::string& dsId);
// 导出:导图片/dat 由 UI 层用 vtkWindowToImageFilter / 采样写文件(不入仓储)
```
> **实时切片不进仓储(评审 MEDIUM**:交互拖切面时每帧重采样应直接走 VTK 管线(`vtkImageReslice` 挂在切片工具上§9.1**不**经 repository 回算 `Grid`(避免慢路径)。仓储只管"切片保存为数据集"这种持久化动作。
### 6.4 异常 / 异常体(树 + CRUD + 截图)
```cpp
struct AnomalyBody { // 对应"异常体"(树中间层)
std::string id, name, typeName;
std::vector<core::Anomaly> members; // 组内异常(复用既有 2D Anomaly3D 用包围几何)
};
struct AnomalyTree { // 对象 → 异常体/分组 → 异常
std::vector<AnomalyBody> bodies;
std::vector<core::Anomaly> loose; // 未分组异常
};
AnomalyTree loadAnomalyTree(const std::string& objectId);
std::string saveAnomaly(const core::Anomaly& a, const std::string& screenshotPngPath); // 截图属性
void deleteAnomaly(const std::string& anomalyId);
void deleteAnomalyGroup(const std::string& bodyId);
// 显示过滤是 UI/渲染层状态不进仓储enum AnomalyFilter{All,FollowGs,FollowDs,None}
```
### 6.5 任务管理
```cpp
struct TaskRecord { std::string id, taskName, status, createTime; }; // 任务记录
struct UsableTask { std::string scriptId, scriptCode, name; int opType; }; // = 既有 ModelInfo
std::vector<TaskRecord> loadTaskRecords(const std::string& dsId); // 当前 ds 的调用记录(后端缺→空/样本)
std::vector<UsableTask> loadUsableTasks(const std::string& ddCode); // 按 ds 类型过滤插件(复用 model/list
```
---
## 7. UI 设计
### 7.1 三子列表栏(客户端新设计 —— web 无三栏)
> 已确认§1.5web 端**没有三栏**,三栏是补充需求为桌面定义的新结构,以 xlsx 为准设计。
在 VTK dock 内(或左侧新 dock放三个可切换 tab/分段:**三维数据集 / 二维数据集 / 三维分析**。每栏顶部是该栏专属工具条,下方是数据集勾选列表(按 §6.1 维度筛选当前勾选对象的 ds
**职责切分(决策,不再是开放问题)**:保留现有 `ObjectTreePanel`(左上勾选对象)作为"对象勾选源";三栏列表只在被勾选对象范围内、按维度过滤显示 ds。**左下数据集列表服务"详情查看",三栏列表服务"VTK 渲染勾选"**——两条线并存。依据web 端本身也是"对象树勾选 + 独立异常列表"的多列表结构,两条线与原版心智一致;且补充需求明确三栏服务于 VTK 渲染,与详情查看是不同动作。
### 7.2 三维数据集栏工具条
坐标轴下拉(标准/立体/不显示)+ O 点 + 刻度(无/米/英尺/经纬度)+ 字体 | 水平/垂直比例(滑块/输入)| 快捷视图(前后左右上下 6 钮)| ZoomIn/Out/Fit
### 7.3 二维数据集栏
底图下拉(天地图/Google/隐藏)+ 2D 视图位置(关闭/Z=0/顶部/底部/自定义 Z
**底图层(评审 HIGH被低估单列风险子项**web 用 ThreeTile 多源瓦片§1.5 已确证:天地图/Google/Bing/高德/腾讯…)。桌面 VTK 无现成瓦片图层,需自建小型瓦片引擎:可视范围 → 瓦片行列号 → 异步拉 PNG → 贴 `vtkPlaneSource`+texture → 随相机换 LOD。**关键配准**:天地图瓦片是 EPSG:3857须把每块瓦片范围反算到 `GeoLocalFrame` 局部米才能与帘面/体素对齐(复用 `TerrainActor` 已有的 3857→4326→frame 流程)。本轮先打通"天地图能显示+隐藏"Google/缓存/LOD 后续。**排 P5**(复杂度高、依赖天地图 token
### 7.4 三维分析栏
树:对象 → 三维体模型数据集 → 切片。右键菜单按节点类型分派(三维体 / 切片,见 §4 行 D21F36
---
## 8. 渲染编排层 `VtkSceneController`(复活原型)
职责:聚合"勾选对象 + 当前栏 + 图层开关 + 视图模式"→ 经 `I3dSceneRepository``core::*` → 调 actor → `Scene`。**取代 `main.cpp` 里的 `rebuildCentral` lambda 与死掉的图层标志。**
```cpp
class VtkSceneController : public QObject {
// 输入信号:勾选对象变 / 栏切换 / 图层勾选 / 比例变 / 快捷视图 / 切片增删
// 内部:维护 ScalarVolume/Grid 缓存,按需重建;统一 SetScale(1,1,VE)
// 输出scene.clear()+addActor()+renderWindow->Render()
};
```
落地第一步(最低风险、最快见效):把 `extractSlice`/`loadVolume`/`buildCurtain`/`buildVoxelFromScatters`/`buildTerrain` 用真实勾选 ds 接通 —— **render actor 零改动即复活**
---
## 9. 交互层 `src/render/interact/`(新目录,最重模块)
### 9.1 切片工具
- **轴向切片**(上下/前后/左右):`vtkImagePlaneWidget` 贴体素 `vtkImageData`VoxelActor 已能导出 image沿固定轴向、自动重采样着色角度不可调符合 G22G24
- **任意切片**F25`vtkPlaneWidget`(控制面位姿)+ **`vtkImageReslice`**(按斜面对体素重采样出着色剖面),初始 45°可任意旋转。**不用 `vtkCutter`**(评审 HIGHcutter 切 `vtkImageData` 只产几何交线/多边形,得不到带标量的连续剖面图)。
- 统一抽象 `SliceTool`:持切面位姿 + `vtkImageReslice`,直接产出贴 `vtkImageActor` 的着色剖面(避免回算 `core::Grid` 的绕路;仅"保存为数据集"时才转 Grid 落库)。
### 9.2 滚轮切片D46
选中切片 → 自定义 InteractorStyle 截 `OnMouseWheel` → 沿切面法向平移 origin → 重算切面。
### 9.3 拾取 + 选中联动C38/D39/D40/C84
`vtkPropPicker` + 自定义 `vtkInteractorStyle`
- 选中三维体/切片 → 以其包围盒中心设相机 focalPoint拖动绕其旋转
- 双击切片 → 相机正视切面法向D40/D45/E54
- 拾取异常体 ↔ 异常树列表双向高亮C84
### 9.4 异常圈定工具D48/E49/F49F50
自定义 widget光标拾取起点 → 连续描点成多边形(复用 `core::Anomaly` Polygon→ 结束弹保存对话框(截图大小 / 异常坐标,参考 Geopro 1.0)→ `saveAnomaly(a, screenshot)`。截图用 `vtkWindowToImageFilter`
> **异常几何 = 切片面上的多边形(决策,不再是开放问题)**:需求 F49"以光标拾取点为起点圈异常"+ web 同款(在切片上描点),即异常是**画在切片平面上的多边形**——本质 2D-in-3D把多边形顶点存为切面局部坐标 + 切面位姿即可在 3D 中定位。**不需要真三维体几何**。故 `core::Anomaly`(2D 多边形) 复用成立§6.4 `AnomalyBody.members``core::Anomaly` + 所属切面引用即可。截图属性B88即圈定时的视图截图。
---
## 10. 详情视图
### 10.1 三维体详情类型(新)
详情面板(`DatasetDetailPanel`ddCode 驱动)新增三维体详情:源数据 / 切片数据 / 异常 / 插值模型(IDW·克里金) / 插值参数 / 色阶参数 / 测量(点数·体积)。数据来自 `VolumeModelMeta`§6.2),多为表单/列表展示。
### 10.2 切片详情
参照 `dd_section`(已有渲染),复用现有等值面+等值线引擎。
---
## 11. 异常体管理面板
扩展现有 `ObjectExceptionPanel`(当前只读树)为可操作:
- 树:对象 → 异常体 → 异常(含未分组异常)。
- 操作:删除 / 删除分组§6.4)。
- 异常属性面板(选中异常显详情)。
- VTK↔列表双向选中联动§9.3)。
- 显示过滤:全部显示 / 随 GS / 随数据集 / 全部隐藏(渲染层可见性状态)。
- 异常截图属性保存时存截图§6.4)。
---
## 12. 任务管理面板(新)
两段:
- **任务记录**:当前数据集的任务调用记录(`loadTaskRecords`,后端缺→样本/空态)。
- **可使用任务列表**:按当前 ds 的 ddCode 过滤的任务插件(`loadUsableTasks`,复用 `model/list`)。
---
## 13. 开放问题 / 待确认
### 已决(评审 + 实地分析后关闭)
- ✅ **三栏 vs 左下列表**:两条线并存(三栏=渲染勾选、左下=详情查看web 本身即多列表结构§7.1)。
- ✅ **异常 3D 表达**:异常 = 切片面上的 2D 多边形,复用 `core::Anomaly`无需真三维体几何§9.4)。
- ✅ **任意切片技术**`vtkImageReslice`,非 `vtkCutter`§9.1)。
- ✅ **接口异步 + 数据结构**:异步范式 + `VolumeGrid`/`std::array` 聚合§6
### 仍开放(不阻塞 P1/P2相关期开工前定
1. **坐标系单位 / O 点**§7.2P2刻度"经纬度"用 `GeoLocalFrame` 反算;"米/英尺"基于世界米。O 点默认世界原点还是数据包围盒角?→ P2 开工前定,倾向"数据包围盒角"。
2. **切片"保存为数据集"落库形态**§6.3P3本轮 LocalSample 内存态;是否持久化到本地文件以便重启可见?
3. **真三维体项目**§1.5P3/P4威立雅无真三维体需用户指认有 `dd_Structual3D` 的项目,实地学习其切片/异常几何与交互细节。**不阻塞 P3**(切片对 LocalSample 体素先开发)。
4. **"参考 Geopro 1.0"**F26 色阶 / F50 异常保存框P4目前无 1.0 参考 → 先用现有 colorBar 解析 + 标准保存框近似,拿到 1.0 后精修。
5. **底图 token / Google 可用性**§7.3P5天地图需 tokenGoogle 国内可用性。本轮先天地图 + 隐藏。
---
## 14. 分期建议(详细 plan 另立,此处仅排序)
1. **P1 复活渲染(地基,低风险)**`VtkSceneController` + `I3dSceneRepository`/`LocalSample3dRepository` + 勾选 ds → 真实/样本数据 → actor。复活帘面/体素/地形/切面着色。
2. **P2 三维数据集栏(纯前端,独立)**:坐标轴 + 水平/垂直比例 + 快捷视图 6 向 + Zoom。
3. **P3 三维分析·切片交互(最重)**`interact/` + 轴向/任意切片 + 滚轮 + 拾取联动 + 双击正视 + 正视/翻转/关闭 + 切片 CRUD + 导出。
4. **P4 异常体 + 圈异常 + 详情 + 任务**:异常圈定工具 + 异常体树管理 + 三维体详情 + 任务面板(依赖 1.0 参考)。
5. **P5 二维底图**:天地图瓦片层 + 2D 视图位置。
> 依赖P1/P2/P3 均可用 xlsx + 现有代码 + LocalSample 开工(切片对 LocalSample 体素开发§1.5。P4 的色阶/异常框精修依赖 Geopro 1.0 参考先近似P3/P4 的真三维体细节待用户指认 3D 项目后实地补。P5 底图依赖天地图 token。
>
> **可立即开工P1含 Scene 加 vtkProp 入口)+ P2。** 评审结论spec 修订后可作为 plan 基础。

View File

@ -54,7 +54,7 @@ add_executable(geopro_desktop WIN32
panels/LoadingOverlay.cpp panels/LoadingOverlay.cpp
panels/DatasetDetailPage.cpp panels/DatasetDetailPage.cpp
panels/DatasetDetailPanel.cpp panels/DatasetDetailPanel.cpp
VtkSceneView.cpp CentralScene.cpp
ProjectListDialog.cpp ProjectListDialog.cpp
ObjectFormDialog.cpp ObjectFormDialog.cpp
ImportDatasetDialog.cpp ImportDatasetDialog.cpp

49
src/app/CentralScene.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "CentralScene.hpp"
#include <vtkActor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include "CameraPreset.hpp"
#include "Scene.hpp"
#include "Theme.hpp"
#include "actors/CurtainActor.hpp"
#include "actors/MapLineActor.hpp"
#include "geo/GeoLocalFrame.hpp"
namespace geopro::app {
void rebuildCentralScene(geopro::render::Scene& scene, vtkRenderer* renderer,
vtkRenderWindow* renderWindow, ViewMode mode,
const std::vector<SectionInput>& sections, bool showCurtain,
const geopro::core::GeoLocalFrame& frame, double verticalExaggeration) {
scene.clear();
const bool is2D = (mode == ViewMode::Map2D);
(void)is2D;
// 背景永远深色规范§0.5 视图区常深,不随明暗切换),让色阶数据更突出。
double bgR, bgG, bgB;
geopro::app::vtkBackground(bgR, bgG, bgB);
renderer->SetBackground(bgR, bgG, bgB);
for (const auto& s : sections) {
if (is2D) {
auto line = geopro::render::buildSurveyLine(s.grid, frame);
if (line) scene.addActor(line);
} else if (showCurtain) {
auto curtain = geopro::render::buildCurtain(s.grid, s.colorScale, frame);
if (curtain) {
curtain->SetScale(1.0, 1.0, verticalExaggeration); // 纵向夸张成墙
scene.addActor(curtain);
}
}
}
if (is2D)
geopro::render::applyTop2D(renderer);
else
geopro::render::applyFree3D(renderer);
renderer->ResetCamera();
renderWindow->Render();
}
} // namespace geopro::app

31
src/app/CentralScene.hpp Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include <vector>
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
namespace geopro::core { class GeoLocalFrame; }
namespace geopro::render { class Scene; }
class vtkRenderer;
class vtkRenderWindow;
namespace geopro::app {
// 中央视图模式:二维地图(测线红线俯视)/ 三维视图(断面墙)。
enum class ViewMode { Map2D, View3D };
// 一个待渲染剖面grid2D 测线 / 3D 帘面都用)+ colorScale3D 帘面上色)。
struct SectionInput {
geopro::core::Grid grid;
geopro::core::ColorScale colorScale;
};
// 中央场景重建(脱离对象树,按显式 sections 渲染):
// 2D = 每个 section 的 buildSurveyLine3D = 每个 section 的 buildCurtain受 showCurtain
// 下一轮接真实 DS构建 sections 后调用本函数即可render 层零改动。
void rebuildCentralScene(geopro::render::Scene& scene, vtkRenderer* renderer,
vtkRenderWindow* renderWindow, ViewMode mode,
const std::vector<SectionInput>& sections, bool showCurtain,
const geopro::core::GeoLocalFrame& frame, double verticalExaggeration);
} // namespace geopro::app

View File

@ -1,72 +0,0 @@
#include "VtkSceneView.hpp"
#include <utility>
#include <vtkActor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkVolume.h>
#include "CameraPreset.hpp"
#include "Scene.hpp"
#include "Theme.hpp"
#include "actors/CurtainActor.hpp"
#include "actors/MapLineActor.hpp"
#include "actors/TerrainActor.hpp"
#include "actors/VoxelActor.hpp"
#include "geo/GeoLocalFrame.hpp"
namespace geopro::app {
VtkSceneView::VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev)
: scene_(scene),
renderWindow_(renderWindow),
frame_(std::move(frame)),
zRefElev_(zRefElev) {}
void VtkSceneView::clear() { scene_.clear(); }
void VtkSceneView::setVerticalExaggeration(double ve) { verticalExaggeration_ = ve; }
void VtkSceneView::addSurveyLine(const geopro::core::Grid& grid) {
auto line = geopro::render::buildSurveyLine(grid, *frame_);
if (line) scene_.addActor(line);
}
void VtkSceneView::addCurtain(const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) {
auto curtain = geopro::render::buildCurtain(grid, cs, *frame_);
if (curtain) {
curtain->SetScale(1.0, 1.0, verticalExaggeration_); // 纵向夸张成墙
scene_.addActor(curtain);
}
}
void VtkSceneView::addVolume(const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) {
// 纵向夸张烤进 image 的 z 原点/间距(与帘面 SetScale 同倍,保证纵向一致)。
auto volume = geopro::render::buildVoxel(
vol.vol, cs, vol.origin[0], vol.origin[1], vol.origin[2] * verticalExaggeration_,
vol.spacing[0], vol.spacing[1], vol.spacing[2] * verticalExaggeration_, vol.vmin, vol.vmax);
if (volume) scene_.addViewProp(volume);
}
void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) {
auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_,
verticalExaggeration_);
if (terrain) scene_.addActor(terrain);
}
void VtkSceneView::render(bool is2D) {
// 视图区背景永远深色(规范 §0.5:不随明暗切换),让色阶数据更突出。
double bgR, bgG, bgB;
geopro::app::vtkBackground(bgR, bgG, bgB);
scene_.renderer()->SetBackground(bgR, bgG, bgB);
if (is2D)
geopro::render::applyTop2D(scene_.renderer());
else
geopro::render::applyFree3D(scene_.renderer());
scene_.renderer()->ResetCamera();
if (renderWindow_) renderWindow_->Render();
}
} // namespace geopro::app

View File

@ -1,39 +0,0 @@
#pragma once
#include <memory>
#include "I3dSceneView.hpp"
namespace geopro::core { class GeoLocalFrame; }
namespace geopro::render { class Scene; }
class vtkRenderer;
class vtkRenderWindow;
namespace geopro::app {
// I3dSceneView 的真实实现:把编排层的"加图元"指令翻译为 render actor + Scene 调用。
// 持有 Scene / renderer / renderWindow非拥有+ 共享 GeoLocalFrame多视图空间配准
// 纵向夸张统一作用:帘面/地形 actor SetScale(1,1,VE),体素 z 原点/间距烤入 VE。
// render 层零业务actor 只吃 core::*,本类负责装配。
class VtkSceneView : public geopro::controller::I3dSceneView {
public:
// 入参生命周期须覆盖本对象由调用方保证。zRefElev地形 z 基准(测线地表高程)。
VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev);
void clear() override;
void setVerticalExaggeration(double ve) override;
void addSurveyLine(const geopro::core::Grid& grid) override;
void addCurtain(const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) override;
void addVolume(const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) override;
void addTerrain(const geopro::data::TerrainPaths& paths) override;
void render(bool is2D) override;
private:
geopro::render::Scene& scene_;
vtkRenderWindow* renderWindow_;
std::shared_ptr<geopro::core::GeoLocalFrame> frame_;
double zRefElev_;
double verticalExaggeration_ = 2.0;
};
} // namespace geopro::app

View File

@ -78,7 +78,6 @@
#include "model/ColorScale.hpp" #include "model/ColorScale.hpp"
#include "model/Field.hpp" #include "model/Field.hpp"
#include "repo/LocalSampleRepository.hpp" #include "repo/LocalSampleRepository.hpp"
#include "repo/LocalSample3dRepository.hpp"
#include "ApiClient.hpp" #include "ApiClient.hpp"
#include "AuthService.hpp" #include "AuthService.hpp"
@ -89,12 +88,11 @@
#include "Theme.hpp" #include "Theme.hpp"
#include "SettingsDialog.hpp" #include "SettingsDialog.hpp"
#include "TopBar.hpp" #include "TopBar.hpp"
#include "CentralScene.hpp"
#include "ProjectListDialog.hpp" #include "ProjectListDialog.hpp"
#include "ObjectFormDialog.hpp" #include "ObjectFormDialog.hpp"
#include "ImportDatasetDialog.hpp" #include "ImportDatasetDialog.hpp"
#include "WorkbenchNavController.hpp" #include "WorkbenchNavController.hpp"
#include "VtkSceneController.hpp"
#include "VtkSceneView.hpp"
#include "api/NavRequest.hpp" #include "api/NavRequest.hpp"
#include "api/NavLoads.hpp" #include "api/NavLoads.hpp"
#include "DatasetDetailController.hpp" #include "DatasetDetailController.hpp"
@ -140,6 +138,7 @@
#include <vtkCamera.h> #include <vtkCamera.h>
#include <vtkCameraInterpolator.h> #include <vtkCameraInterpolator.h>
#include <vtkGenericOpenGLRenderWindow.h> #include <vtkGenericOpenGLRenderWindow.h>
#include <vtkImagePlaneWidget.h>
#include <vtkLookupTable.h> #include <vtkLookupTable.h>
#include <vtkProperty.h> #include <vtkProperty.h>
#include <vtkRenderWindowInteractor.h> #include <vtkRenderWindowInteractor.h>
@ -200,6 +199,9 @@ double median(std::vector<double> v)
return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]); return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]);
} }
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
using geopro::app::ViewMode;
// 纵向夸张倍数Z 基准统一M-3全项目共用同一倍数使 帘面(z) / 体素 / 切片 / // 纵向夸张倍数Z 基准统一M-3全项目共用同一倍数使 帘面(z) / 体素 / 切片 /
// 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。 // 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。
// 单一可调常量:要整体调纵向观感改这一处即可。 // 单一可调常量:要整体调纵向观感改这一处即可。
@ -229,36 +231,29 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// Scene 非 QObject堆分配用 widget 销毁信号清理widget 随 window 销毁)。 // Scene 非 QObject堆分配用 widget 销毁信号清理widget 随 window 销毁)。
auto* scene = new geopro::render::Scene(); auto* scene = new geopro::render::Scene();
auto* vtkWidget = new QVTKOpenGLStereoWidget(); auto* vtkWidget = new QVTKOpenGLStereoWidget();
QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; });
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow; vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
vtkWidget->setRenderWindow(renderWindow); vtkWidget->setRenderWindow(renderWindow);
renderWindow->AddRenderer(scene->renderer()); renderWindow->AddRenderer(scene->renderer());
vtkRenderer* rendererPtr = scene->renderer();
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get(); vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
// 中央渲染编排VtkSceneController + VtkSceneView取代旧 rebuildCentral lambda 与裸 show* 标志)。 // 当前视图模式(全局共享,切视图/勾选时据此重建内容)。默认二维地图。
// 3D 场景仓储用 LocalSample3dRepository本期样本驱动接口异步将来换 Api 实现不动上层)。 auto viewMode = std::make_shared<ViewMode>(ViewMode::Map2D);
// 视图VtkSceneView非 QObject、控制器/3D 仓储亦然:随 scene 一并在 vtkWidget 销毁时清理。
auto* scene3dRepo = new geopro::data::LocalSample3dRepository(repo, kProjectCrs, lat0, lon0);
auto* sceneView = new geopro::app::VtkSceneView(*scene, renderWindowPtr,
frame, refElev);
auto* sceneCtrl = new geopro::controller::VtkSceneController(repo, *scene3dRepo, *sceneView,
vtkWidget);
sceneCtrl->setVerticalExaggeration(kVerticalExaggeration);
// 非 QObject 堆对象统一在此清理按构造逆序sceneView(持 scene&) → scene3dRepo → scene。
// sceneCtrl 是 vtkWidget 的 QObject 子对象,由 Qt 在 destroyed 前先析构,不再触发信号回灌。)
QObject::connect(vtkWidget, &QObject::destroyed, [scene, scene3dRepo, sceneView]() {
delete sceneView;
delete scene3dRepo;
delete scene;
});
// PROJ 可用性(体素/地形/切片层都需配准):失败则浮层相应勾选禁用并提示。 // 三维图层显隐(由「视图详情」浮层控制)+ 项目 CRS→WGS84(体素配准)。
bool crsAvailable = false; auto showCurtain = std::make_shared<bool>(true); // 帘面,默认显示
auto showVoxel = std::make_shared<bool>(false); // 体素,默认关
auto showTerrain = std::make_shared<bool>(false); // 地形(DEM+影像),默认关
auto showSlice = std::make_shared<bool>(false); // dd_slice 交互切片,默认关
// 持久的切片 widget(挂 interactor跨重建保活rebuildCentral 据条件创建/拆除)。
auto slicePlane = std::make_shared<vtkSmartPointer<vtkImagePlaneWidget>>();
std::shared_ptr<geopro::core::CrsTransform> crs; // PROJ 失败→空→体素层无效(不崩)
try { try {
geopro::core::CrsTransform probe(kProjectCrs, kWgs84); crs = std::make_shared<geopro::core::CrsTransform>(kProjectCrs, kWgs84);
crsAvailable = true;
} catch (const std::exception&) { } catch (const std::exception&) {
crsAvailable = false; crs.reset();
} }
// 停靠系统配置(必须在 CDockManager 构造前设置):对齐原型——面板固定、 // 停靠系统配置(必须在 CDockManager 构造前设置):对齐原型——面板固定、
@ -350,14 +345,17 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
chkTerrain->setChecked(false); chkTerrain->setChecked(false);
auto* chkSlice = new QCheckBox(QStringLiteral("切片dd_slice")); auto* chkSlice = new QCheckBox(QStringLiteral("切片dd_slice"));
chkSlice->setChecked(false); chkSlice->setChecked(false);
if (!crsAvailable) { // PROJ 不可用 → 体素/地形层(都需配准)禁用并提示 if (!crs) { // PROJ 不可用 → 体素/切片/地形层(都需配准)禁用并提示
const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用"); const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用");
chkVoxel->setEnabled(false); chkVoxel->setToolTip(tip); chkVoxel->setEnabled(false); chkVoxel->setToolTip(tip);
chkTerrain->setEnabled(false); chkTerrain->setToolTip(tip); chkTerrain->setEnabled(false); chkTerrain->setToolTip(tip);
chkSlice->setEnabled(false); chkSlice->setToolTip(tip);
}
// 本轮中央不接真实派生层:体素/切片/地形勾选置灰,待下一轮接入对应数据源。
for (QCheckBox* c : {chkVoxel, chkSlice, chkTerrain}) {
c->setEnabled(false);
c->setToolTip(QStringLiteral("(下一轮接入真实数据源)"));
} }
// 切片dd_slice交互切片留待 P3本轮禁用。
chkSlice->setEnabled(false);
chkSlice->setToolTip(QStringLiteral("(切片交互 P3 接入)"));
layerLayout->addWidget(layerTitle); layerLayout->addWidget(layerTitle);
layerLayout->addWidget(chkCurtain); layerLayout->addWidget(chkCurtain);
layerLayout->addWidget(chkVoxel); layerLayout->addWidget(chkVoxel);
@ -505,8 +503,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}; };
hideDockTitleBars(); hideDockTitleBars();
// 中央渲染由 sceneCtrlVtkSceneController驱动勾选对象/2D-3D切换/图层勾选/主题 → 重建场景。 // 中央编排已解耦到 CentralScene::rebuildCentralScene数据驱动。本轮空 sections → 空背景占位。
// (旧 rebuildCentral lambda + 裸 show* 标志已由控制器取代。) // 下一轮:用真实 DS 数据构建 sections 调同一 helper 即复活。
auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, showCurtain, frame]() {
geopro::app::rebuildCentralScene(*scene, rendererPtr, renderWindowPtr, *viewMode,
std::vector<geopro::app::SectionInput>{}, *showCurtain,
*frame, kVerticalExaggeration);
};
// ── 单击左下数据列表的采集批次(DS) → 属性表单 + 聚焦详情已开页 ── // ── 单击左下数据列表的采集批次(DS) → 属性表单 + 聚焦详情已开页 ──
QObject::connect(datasetList, &QTreeWidget::itemClicked, datasetList, QObject::connect(datasetList, &QTreeWidget::itemClicked, datasetList,
@ -577,46 +580,51 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
} }
}; };
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 控制器重建 + 图层浮层显隐 ── // ── 工具条「二维地图/三维视图」:切换互斥视图 → 重建内容 + 图层浮层显隐 ──
using geopro::controller::SceneLayer;
using CtrlViewMode = geopro::controller::ViewMode;
QObject::connect(act2D, &QAbstractButton::clicked, vtkWidget, QObject::connect(act2D, &QAbstractButton::clicked, vtkWidget,
[sceneCtrl, showLayerPanel]() { [viewMode, rebuildCentral, showLayerPanel]() {
*viewMode = ViewMode::Map2D;
showLayerPanel(false); showLayerPanel(false);
sceneCtrl->setViewMode(CtrlViewMode::Map2D); rebuildCentral();
}); });
QObject::connect(act3D, &QAbstractButton::clicked, vtkWidget, QObject::connect(act3D, &QAbstractButton::clicked, vtkWidget,
[sceneCtrl, showLayerPanel]() { [viewMode, rebuildCentral, showLayerPanel]() {
*viewMode = ViewMode::View3D;
showLayerPanel(true); showLayerPanel(true);
sceneCtrl->setViewMode(CtrlViewMode::View3D); rebuildCentral();
}); });
// ──「视图详情」图层勾选 → 控制器更新图层 → 重建中央 ── // ──「视图详情」图层勾选 → 更新图层显隐 → 重建中央 ──
QObject::connect(chkCurtain, &QCheckBox::toggled, vtkWidget, QObject::connect(chkCurtain, &QCheckBox::toggled, vtkWidget,
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Curtain, on); }); [showCurtain, rebuildCentral](bool on) {
*showCurtain = on;
rebuildCentral();
});
QObject::connect(chkVoxel, &QCheckBox::toggled, vtkWidget, QObject::connect(chkVoxel, &QCheckBox::toggled, vtkWidget,
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Voxel, on); }); [showVoxel, rebuildCentral](bool on) {
*showVoxel = on;
rebuildCentral();
});
QObject::connect(chkTerrain, &QCheckBox::toggled, vtkWidget, QObject::connect(chkTerrain, &QCheckBox::toggled, vtkWidget,
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Terrain, on); }); [showTerrain, rebuildCentral](bool on) {
*showTerrain = on;
// ── 左上对象树勾选 → 渲染勾选数据集(本期样本驱动:任意勾选 → 样本 ds "grid1",空 → 清场)── rebuildCentral();
// 真实接 Api 时改为把勾选 TM 映射到其 ds 维度过滤后的真实 dsId 列表spec §6.1/§8 });
QObject::connect(objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, sceneCtrl, QObject::connect(chkSlice, &QCheckBox::toggled, vtkWidget,
[sceneCtrl, emptyState](const QStringList& tmIds) { [showSlice, rebuildCentral](bool on) {
const bool hasData = !tmIds.isEmpty(); *showSlice = on;
emptyState->setVisible(!hasData); // 有勾选→隐藏引导层,露出中央渲染 rebuildCentral();
sceneCtrl->setCheckedDatasets(
hasData ? QStringList{QStringLiteral("grid1")} : QStringList{});
}); });
// ── 启动:建立一次中央视图(默认 2D无勾选 → 空场景 + 背景)。 // ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。
sceneCtrl->setViewMode(CtrlViewMode::Map2D); rebuildCentral();
// VTK 背景随主题切换:控制器重渲染(走完整渲染路径、末尾必 Render // VTK 背景随主题切换:直接重跑 rebuildCentral走完整渲染路径、末尾必 Render
// context 用 sceneCtrl非 windowThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开, // 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。
// 否则 window 析构期间 sceneCtrl(其孙级子对象)已销毁、主题异步变化会触悬垂指针。 QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window,
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, sceneCtrl, [rebuildCentral]() {
[sceneCtrl]() { sceneCtrl->rebuild(); }); rebuildCentral();
});
// 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备), // 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备),
// 下=工具条(工作空间切换 + 项目 + 帮助/通知/设置 + 用户)。纵向堆叠后挂到主窗口顶部。 // 下=工具条(工作空间切换 + 项目 + 帮助/通知/设置 + 用户)。纵向堆叠后挂到主窗口顶部。

View File

@ -1,8 +1,7 @@
find_package(Qt6 COMPONENTS Core REQUIRED) find_package(Qt6 COMPONENTS Core REQUIRED)
add_library(geopro_controller STATIC add_library(geopro_controller STATIC
WorkbenchNavController.cpp WorkbenchNavController.cpp
DatasetDetailController.cpp DatasetDetailController.cpp)
VtkSceneController.cpp)
target_include_directories(geopro_controller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(geopro_controller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(geopro_controller PUBLIC geopro_data Qt6::Core) target_link_libraries(geopro_controller PUBLIC geopro_data Qt6::Core)
target_compile_features(geopro_controller PUBLIC cxx_std_17) target_compile_features(geopro_controller PUBLIC cxx_std_17)

View File

@ -1,34 +0,0 @@
#pragma once
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
#include "repo/I3dSceneRepository.hpp"
namespace geopro::controller {
// 三维场景视图抽象(编排层与 VTK 渲染解耦的缝):
// VtkSceneController 只发出"清场 / 加某类图元 / 提交渲染"指令,不认 vtkActor/vtkVolume
// 真实实现VtkSceneView调 render actor + Scene测试用 fake 记录调用断言编排。
// verticalExaggeration 由视图统一作用于 3D 图元actor SetScale(1,1,VE) / image z 烤入)。
class I3dSceneView {
public:
virtual ~I3dSceneView() = default;
virtual void clear() = 0;
virtual void setVerticalExaggeration(double ve) = 0;
// 2D俯视测线红线z=0
virtual void addSurveyLine(const geopro::core::Grid& grid) = 0;
// 3D竖直帘面grid + colorScale 着色)。
virtual void addCurtain(const geopro::core::Grid& grid,
const geopro::core::ColorScale& cs) = 0;
// 3D体绘制IDW 体素 + colorScale
virtual void addVolume(const geopro::data::VolumeGrid& vol,
const geopro::core::ColorScale& cs) = 0;
// 3DDEM 地形 + 影像纹理。
virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0;
// 应用相机预设2D 俯视 / 3D 自由)并提交渲染。
virtual void render(bool is2D) = 0;
};
} // namespace geopro::controller

View File

@ -1,121 +0,0 @@
#include "VtkSceneController.hpp"
#include <utility>
#include <QPointer>
#include "I3dSceneView.hpp"
#include "repo/IDatasetRepository.hpp"
namespace geopro::controller {
VtkSceneController::VtkSceneController(data::IDatasetRepository& dsRepo,
data::I3dSceneRepository& sceneRepo, I3dSceneView& view,
QObject* parent)
: QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {}
void VtkSceneController::setCheckedDatasets(const QStringList& dsIds) {
checkedDs_.clear();
checkedDs_.reserve(static_cast<std::size_t>(dsIds.size()));
for (const QString& id : dsIds) checkedDs_.push_back(id.toStdString());
rebuildInternal();
}
void VtkSceneController::setViewMode(ViewMode mode) {
mode_ = mode;
rebuildInternal();
}
void VtkSceneController::setLayer(SceneLayer layer, bool on) {
switch (layer) {
case SceneLayer::Curtain: showCurtain_ = on; break;
case SceneLayer::Voxel: showVoxel_ = on; break;
case SceneLayer::Terrain: showTerrain_ = on; break;
}
rebuildInternal();
}
void VtkSceneController::setVerticalExaggeration(double ve) {
verticalExaggeration_ = ve;
rebuildInternal();
}
void VtkSceneController::rebuild() { rebuildInternal(); }
const geopro::core::Grid& VtkSceneController::grid(const std::string& dsId) {
auto it = gridCache_.find(dsId);
if (it == gridCache_.end()) it = gridCache_.emplace(dsId, dsRepo_.loadGrid(dsId)).first;
return it->second;
}
const geopro::core::ColorScale& VtkSceneController::colorScale(const std::string& dsId) {
auto it = colorScaleCache_.find(dsId);
if (it == colorScaleCache_.end())
it = colorScaleCache_.emplace(dsId, dsRepo_.loadColorScale(dsId)).first;
return it->second;
}
void VtkSceneController::rebuildInternal() {
const unsigned long long gen = ++rebuildGeneration_;
const bool is2D = (mode_ == ViewMode::Map2D);
view_.clear();
view_.setVerticalExaggeration(verticalExaggeration_);
inRebuild_ = true;
// 坏 dsIdloadGrid/loadColorScale 抛异常)= best-effort 跳过emit loadFailed 但不中断,
// 其余勾选数据集照常渲染(非 fail-fast
try {
if (is2D) {
for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId));
} else {
// 回调用 QPointer<self> 守对象存活(控制器是 QObject+ gen 守数据新鲜:
// 将来 Api 实现在网络线程迟到回调时self 已析构则直接丢弃,不触 dangling。
QPointer<VtkSceneController> self(this);
if (showTerrain_) {
sceneRepo_.loadTerrainPaths(
[self, gen](data::TerrainPaths p) {
if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃
self->view_.addTerrain(std::move(p));
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
},
[self, gen](const std::string& m) {
if (!self || gen != self->rebuildGeneration_) return;
emit self->loadFailed(QString::fromStdString(m));
});
}
if (showCurtain_) {
for (const auto& dsId : checkedDs_) view_.addCurtain(grid(dsId), colorScale(dsId));
}
if (showVoxel_) {
for (const auto& dsId : checkedDs_) {
auto cached = volumeCache_.find(dsId);
if (cached != volumeCache_.end()) {
view_.addVolume(cached->second, colorScale(dsId));
continue;
}
sceneRepo_.loadVolume(
dsId,
[self, gen, dsId](data::VolumeGrid g) {
if (!self) return; // 控制器已析构:丢弃
if (gen != self->rebuildGeneration_) return; // 迟到回灌:丢弃
auto it = self->volumeCache_.emplace(dsId, std::move(g)).first;
self->view_.addVolume(it->second, self->colorScale(dsId));
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
},
[self, gen](const std::string& m) {
if (!self || gen != self->rebuildGeneration_) return;
emit self->loadFailed(QString::fromStdString(m));
});
}
}
}
} catch (const std::exception& e) {
emit loadFailed(QString::fromStdString(e.what()));
}
inRebuild_ = false;
view_.render(is2D);
}
} // namespace geopro::controller

View File

@ -1,78 +0,0 @@
#pragma once
#include <QObject>
#include <QString>
#include <QStringList>
#include <map>
#include <optional>
#include <string>
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
#include "repo/I3dSceneRepository.hpp"
namespace geopro::data {
class IDatasetRepository;
}
namespace geopro::controller {
class I3dSceneView;
// 中央视图模式:二维地图(俯视测线)/ 三维视图(帘面/体素/地形)。
enum class ViewMode { Map2D, View3D };
// 三维图层("视图详情"浮层勾选)。
enum class SceneLayer { Curtain, Voxel, Terrain };
// 中央 VTK 渲染编排spec §8聚合 勾选数据集 + 视图模式 + 图层开关 + 纵向比例,
// 经仓储取 core::* 数据,命令 I3dSceneView 重建场景。取代 main.cpp 的 rebuildCentral lambda。
// 异步:经 I3dSceneRepository 回调取体素/地形(回调内置幂请求标记防迟到回灌)。
// 缓存Grid / VolumeGrid 按 dsId 缓存,避免重复取数。
// 不持有 widget不认 vtkActor/vtkVolume全交给 I3dSceneView
class VtkSceneController : public QObject {
Q_OBJECT
public:
VtkSceneController(data::IDatasetRepository& dsRepo, data::I3dSceneRepository& sceneRepo,
I3dSceneView& view, QObject* parent = nullptr);
public slots:
void setCheckedDatasets(const QStringList& dsIds);
void setViewMode(ViewMode mode);
void setLayer(SceneLayer layer, bool on);
void setVerticalExaggeration(double ve);
void rebuild(); // 主题切换等外部触发的重渲染
signals:
void loadFailed(const QString& message);
private:
void rebuildInternal();
data::IDatasetRepository& dsRepo_;
data::I3dSceneRepository& sceneRepo_;
I3dSceneView& view_;
std::vector<std::string> checkedDs_;
ViewMode mode_ = ViewMode::Map2D;
bool showCurtain_ = true;
bool showVoxel_ = false;
bool showTerrain_ = false;
double verticalExaggeration_ = 2.0;
// 缓存(按 dsId避免重复读盘/插值。
std::map<std::string, geopro::core::Grid> gridCache_;
std::map<std::string, geopro::core::ColorScale> colorScaleCache_;
std::map<std::string, data::VolumeGrid> volumeCache_;
// 异步回灌防护:每次 rebuild 自增,回调比对丢弃迟到结果。
unsigned long long rebuildGeneration_ = 0;
// rebuild 进行中标志同步回调LocalSample在 rebuild 内立即触发时跳过自身 render
// 由 rebuildInternal 末尾统一 render 覆盖(避免双重 ResetCamera/Render
// 真异步回调迟到时 inRebuild_ 已 false → 自行 render 追加。
bool inRebuild_ = false;
const geopro::core::Grid& grid(const std::string& dsId);
const geopro::core::ColorScale& colorScale(const std::string& dsId);
};
} // namespace geopro::controller

View File

@ -3,7 +3,6 @@ find_package(Qt6 COMPONENTS Core REQUIRED)
add_library(geopro_data STATIC add_library(geopro_data STATIC
parse/SampleParsers.cpp parse/SampleParsers.cpp
repo/LocalSampleRepository.cpp repo/LocalSampleRepository.cpp
repo/LocalSample3dRepository.cpp
dto/NavDto.cpp dto/NavDto.cpp
dto/DatasetChartDto.cpp dto/DatasetChartDto.cpp
dto/MeasurementDto.cpp dto/MeasurementDto.cpp

View File

@ -1,52 +0,0 @@
#pragma once
#include <array>
#include <functional>
#include <string>
#include "model/Field.hpp"
#include "repo/RepoTypes.hpp"
namespace geopro::data {
// ds 维度属性(由 ds 类型/ddCode 决定spec §6.1)。三栏列表筛选用。
enum class DsDimension { Dim2D, Dim3D, Analysis3D, Other };
// 三维体模型数据:规则标量体 + 世界系原点/间距 + 值域(去裸 double[],用 std::arrayspec §6.2)。
struct VolumeGrid {
geopro::core::ScalarVolume vol{0, 0, 0};
std::array<double, 3> origin{{0.0, 0.0, 0.0}}; // ox, oy, oz世界米
std::array<double, 3> spacing{{0.0, 0.0, 0.0}}; // dx, dy, dz
double vmin = 0.0, vmax = 0.0;
bool valid() const { return vol.nx() > 0 && vol.ny() > 0 && vol.nz() > 0 && vmax > vmin; }
};
// DEM/影像 GeoTIFF 绝对路径(供 render::buildTerrain 经 GDAL 读spec §6.2)。
struct TerrainPaths {
std::string demPath, imagePath;
};
// 三维场景仓储抽象异步spec §6 评审 HIGH
// 取数方法走回调 std::functionLocalSample 本地数据同步算好后直接回调;
// 将来 Api3dRepository 在网络完成时回调,上层不变)。
// **契约onOk/onErr 必须在主(GUI)线程调用**——上层(VtkSceneController)回调内直接操作
// 场景/发 Qt 信号依赖主线程亲和Api 实现若在工作线程完成须 post 回主线程再回调。
// dimensionOf 是同步纯函数(无 I/O只做类型→维度映射
// 切片/异常/任务等签名本期不在接口内(留 P3/P4
class I3dSceneRepository {
public:
using OnError = std::function<void(const std::string& message)>;
virtual ~I3dSceneRepository() = default;
// 同步纯函数ds 类型 → 维度spec §6.1 映射表)。
virtual DsDimension dimensionOf(const DsRow& ds) const = 0;
// 异步:加载三维体模型(成功回调 VolumeGrid失败回调消息
virtual void loadVolume(const std::string& dsId,
std::function<void(VolumeGrid)> onOk, OnError onErr) = 0;
// 异步:加载地形 DEM/影像路径。
virtual void loadTerrainPaths(std::function<void(TerrainPaths)> onOk, OnError onErr) = 0;
};
} // namespace geopro::data

View File

@ -1,159 +0,0 @@
#include "repo/LocalSample3dRepository.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <exception>
#include <limits>
#include <utility>
#include <vector>
#include "algo/IdwInterpolator.hpp"
#include "geo/CrsTransform.hpp"
#include "geo/GeoLocalFrame.hpp"
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
#include "repo/LocalSampleRepository.hpp"
namespace geopro::data {
using geopro::core::ColorScale;
using geopro::core::CrsTransform;
using geopro::core::GeoLocalFrame;
using geopro::core::GridSpec;
using geopro::core::IdwInterpolator;
using geopro::core::PointSet;
using geopro::core::ScalarVolume;
using geopro::core::ScatterField;
namespace {
// 与 render::VoxelFromScatters 的默认参数同口径(保持渲染/切片纵向一致)。
// TODO(P2/P3): 与 render::buildVoxelFromScatters 的 cellXY/cellZ/power/maxDist 默认值重复,
// 宜把"散点→配准→GridSpec→IDW→ScalarVolume"提到 core::algo 共享,避免单方调参静默不一致。
constexpr double kCellXY = 1.0;
constexpr double kCellZ = 0.5;
constexpr double kPower = 2.0;
constexpr double kMaxDist = 4.0;
constexpr int kMaxDim = 400;
constexpr const char* kWgs84 = "EPSG:4326";
int clampDim(double ext, double cell) {
int n = static_cast<int>(ext / cell) + 1;
if (n < 1) n = 1;
if (n > kMaxDim) n = kMaxDim;
return n;
}
} // namespace
LocalSample3dRepository::LocalSample3dRepository(LocalSampleRepository& base, std::string projectCrs,
double baseLat, double baseLon)
: base_(base), projectCrs_(std::move(projectCrs)), baseLat_(baseLat), baseLon_(baseLon) {}
DsDimension LocalSample3dRepository::dimensionOf(const DsRow& ds) const {
const std::string& c = ds.ddCode;
// 真三维体 / 体素 / 帘面dd_section/反演剖面摆成竖直帘面)入三维数据集。
if (c == "dd_voxel" || c == "dd_Structual3D" || c == "dd_Property3D" || c == "dd_section" ||
c == "dd_inversion_data") {
return DsDimension::Dim3D;
}
// 切片:三维分析栏。
if (c == "dd_slice") return DsDimension::Analysis3D;
// 轨迹:二维数据集。
if (c == "dd_trajectory_data") return DsDimension::Dim2D;
return DsDimension::Other;
}
void LocalSample3dRepository::loadVolume(const std::string& /*dsId*/,
std::function<void(VolumeGrid)> onOk, OnError onErr) {
// P1 样本dsId 暂未使用,固定读同一组交叉剖面散点→体素(真实 Api 实现按 dsId 取)。
try {
// 1) 读两条交叉剖面散点 + 色阶;配准到世界局部米 + 深度,组装 IDW 输入点集。
const std::vector<ScatterField> profiles = base_.loadVoxelScatters();
const CrsTransform crs(projectCrs_, kWgs84);
const GeoLocalFrame frame(baseLat_, baseLon_);
PointSet pts;
for (const auto& s : profiles) {
const std::size_t n = s.v.size();
if (s.projX.size() < n || s.projY.size() < n || s.y.size() < n) continue;
for (std::size_t i = 0; i < n; ++i) {
const auto ll = crs.forward(s.projX[i], s.projY[i]); // (lon, lat)
const auto local = frame.toLocal(ll.y, ll.x); // (x East, y North) 米
pts.x.push_back(local.x);
pts.y.push_back(local.y);
pts.z.push_back(-s.y[i]); // 深度向下z 取负
pts.v.push_back(s.v[i]);
}
}
if (pts.v.empty()) {
onErr("LocalSample3dRepository: no voxel points after registration");
return;
}
// 2) 点集包络 → GridSpec角点对齐
double minx = pts.x[0], maxx = pts.x[0];
double miny = pts.y[0], maxy = pts.y[0];
double minz = pts.z[0], maxz = pts.z[0];
for (std::size_t i = 1; i < pts.v.size(); ++i) {
minx = std::min(minx, pts.x[i]); maxx = std::max(maxx, pts.x[i]);
miny = std::min(miny, pts.y[i]); maxy = std::max(maxy, pts.y[i]);
minz = std::min(minz, pts.z[i]); maxz = std::max(maxz, pts.z[i]);
}
GridSpec spec{};
spec.ox = minx; spec.oy = miny; spec.oz = minz;
spec.dx = kCellXY; spec.dy = kCellXY; spec.dz = kCellZ;
spec.nx = clampDim(maxx - minx, kCellXY);
spec.ny = clampDim(maxy - miny, kCellXY);
spec.nz = clampDim(maxz - minz, kCellZ);
spec.power = kPower;
spec.maxDist = kMaxDist;
// 3) IDW → ScalarVolumemaxDist 外 NaN 留空)。
const IdwInterpolator idw;
ScalarVolume vol = idw.interpolate(pts, spec);
// 4) 值域:优先 colorBar 真实分段值,否则数据实测。
double vmin, vmax;
ColorScale cs;
try {
cs = base_.loadScatterColorScale("grid1");
} catch (const std::exception&) {
// 色阶缺失 → 退化为数据实测范围。
}
const std::vector<double> stops = cs.stopValues();
if (stops.size() >= 2) {
vmin = stops.front(); vmax = stops.back();
} else {
vmin = std::numeric_limits<double>::infinity();
vmax = -std::numeric_limits<double>::infinity();
for (double v : vol.data()) {
if (std::isnan(v)) continue;
vmin = std::min(vmin, v); vmax = std::max(vmax, v);
}
if (!(vmin < vmax)) { vmin = 0.0; vmax = 1.0; }
}
VolumeGrid out{std::move(vol),
{{spec.ox, spec.oy, spec.oz}},
{{spec.dx, spec.dy, spec.dz}},
vmin, vmax};
onOk(std::move(out));
} catch (const std::exception& e) {
onErr(std::string("LocalSample3dRepository::loadVolume: ") + e.what());
}
}
void LocalSample3dRepository::loadTerrainPaths(std::function<void(TerrainPaths)> onOk,
OnError onErr) {
try {
TerrainPaths p{base_.demPath(), base_.imagePath()};
onOk(std::move(p));
} catch (const std::exception& e) {
onErr(std::string("LocalSample3dRepository::loadTerrainPaths: ") + e.what());
}
}
} // namespace geopro::data

View File

@ -1,34 +0,0 @@
#pragma once
#include <functional>
#include <string>
#include "repo/I3dSceneRepository.hpp"
namespace geopro::data {
class LocalSampleRepository;
// 本地样本三维场景仓储spec §6组合 LocalSampleRepository。
// loadVolume读两条交叉剖面散点 → 项目 CRS→WGS84→GeoLocalFrame 配准 → IDW → VolumeGrid纯 core/data无 VTK
// loadTerrainPaths直透 LocalSampleRepository 的 demPath/imagePath。
// dimensionOf按 ddCode 内置映射表(同步纯函数)。
// 本地数据同步算好后直接回调(异步壳:接口异步,本实现内联完成)。
class LocalSample3dRepository : public I3dSceneRepository {
public:
// base 生命周期须覆盖本对象由调用方保证projectCrs 为项目 CRS如 "EPSG:4547")。
// baseLat/baseLon 为全项目共享 GeoLocalFrame 原点(与帘面/地图同系,保证空间配准)。
LocalSample3dRepository(LocalSampleRepository& base, std::string projectCrs,
double baseLat, double baseLon);
DsDimension dimensionOf(const DsRow& ds) const override;
void loadVolume(const std::string& dsId, std::function<void(VolumeGrid)> onOk,
OnError onErr) override;
void loadTerrainPaths(std::function<void(TerrainPaths)> onOk, OnError onErr) override;
private:
LocalSampleRepository& base_;
std::string projectCrs_;
double baseLat_, baseLon_;
};
} // namespace geopro::data

View File

@ -17,9 +17,4 @@ void Scene::addActor(vtkActor* a)
if (a) renderer_->AddActor(a); if (a) renderer_->AddActor(a);
} }
void Scene::addViewProp(vtkProp* p)
{
if (p) renderer_->AddViewProp(p);
}
} // namespace geopro::render } // namespace geopro::render

View File

@ -2,7 +2,6 @@
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
#include <vtkRenderer.h> #include <vtkRenderer.h>
#include <vtkActor.h> #include <vtkActor.h>
#include <vtkProp.h>
namespace geopro::render { namespace geopro::render {
// 单一渲染场景:持有 vtkRenderer白底统一管理 actor 的加入与清除。 // 单一渲染场景:持有 vtkRenderer白底统一管理 actor 的加入与清除。
@ -13,10 +12,8 @@ public:
vtkRenderer* renderer() const { return renderer_.Get(); } vtkRenderer* renderer() const { return renderer_.Get(); }
void clear(); // 移除所有 view prop(含体绘制 vtkVolume,支持重复切换数据集 void clear(); // 移除所有 view prop,支持重复切换数据集
void addActor(vtkActor* a); // actor 由 renderer 引用计数保活 void addActor(vtkActor* a); // actor 由 renderer 引用计数保活
// 体绘制 vtkVolume 是 vtkProp3D非 vtkActor经此通用入口进场prop 由 renderer 引用计数保活。
void addViewProp(vtkProp* p);
private: private:
vtkSmartPointer<vtkRenderer> renderer_; vtkSmartPointer<vtkRenderer> renderer_;

View File

@ -36,8 +36,6 @@ target_link_libraries(geopro_tests PRIVATE geopro_core)
target_sources(geopro_tests PRIVATE data/test_parsers.cpp) target_sources(geopro_tests PRIVATE data/test_parsers.cpp)
target_sources(geopro_tests PRIVATE data/test_local_repo.cpp) target_sources(geopro_tests PRIVATE data/test_local_repo.cpp)
# I3dSceneRepository/LocalSample3dRepositorydimensionOf + loadVolume/loadTerrainPaths PROJ_DATA
target_sources(geopro_tests PRIVATE data/test_3d_repo.cpp)
target_sources(geopro_tests PRIVATE data/test_nav_dto.cpp) target_sources(geopro_tests PRIVATE data/test_nav_dto.cpp)
target_sources(geopro_tests PRIVATE data/test_dataset_chart_dto.cpp) target_sources(geopro_tests PRIVATE data/test_dataset_chart_dto.cpp)
target_sources(geopro_tests PRIVATE data/test_measurement_dto.cpp) target_sources(geopro_tests PRIVATE data/test_measurement_dto.cpp)
@ -78,8 +76,6 @@ endif()
# render ColorLutBuildercore ColorScale -> vtkLookupTable # render ColorLutBuildercore ColorScale -> vtkLookupTable
# vtkLookupTableVTK::CommonCoregeopro_render PUBLIC VTK # vtkLookupTableVTK::CommonCoregeopro_render PUBLIC VTK
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel RenderingCore) find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel RenderingCore)
# SceneaddActor/addViewProp + clear vtkVolume addViewProp
target_sources(geopro_tests PRIVATE render/test_scene.cpp)
target_sources(geopro_tests PRIVATE render/test_color_lut.cpp) target_sources(geopro_tests PRIVATE render/test_color_lut.cpp)
target_sources(geopro_tests PRIVATE render/test_contour_bands.cpp) target_sources(geopro_tests PRIVATE render/test_contour_bands.cpp)
# dd_voxelbuildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) + dims # dd_voxelbuildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) + dims
@ -121,8 +117,6 @@ target_sources(geopro_tests PRIVATE app/test_scatter_hover.cpp)
find_package(Qt6 COMPONENTS Test REQUIRED) find_package(Qt6 COMPONENTS Test REQUIRED)
target_sources(geopro_tests PRIVATE controller/test_dataset_detail_controller.cpp) target_sources(geopro_tests PRIVATE controller/test_dataset_detail_controller.cpp)
target_sources(geopro_tests PRIVATE controller/test_workbench_nav_controller.cpp) target_sources(geopro_tests PRIVATE controller/test_workbench_nav_controller.cpp)
# VtkSceneController fake repo + fake view × add /
target_sources(geopro_tests PRIVATE controller/test_vtk_scene_controller.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_controller Qt6::Test) target_link_libraries(geopro_tests PRIVATE geopro_controller Qt6::Test)
add_subdirectory(spike) # spike S3: banded contour add_subdirectory(spike) # spike S3: banded contour

View File

@ -1,167 +0,0 @@
#include <gtest/gtest.h>
#include <functional>
#include <string>
#include <vector>
#include "I3dSceneView.hpp"
#include "VtkSceneController.hpp"
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
#include "repo/I3dSceneRepository.hpp"
#include "repo/IDatasetRepository.hpp"
using namespace geopro;
using namespace geopro::controller;
namespace {
// 记录视图收到的图元调用类型/数量。
struct FakeView : I3dSceneView {
int clears = 0;
int surveyLines = 0;
int curtains = 0;
int volumes = 0;
int terrains = 0;
int renders = 0;
bool lastIs2D = false;
double ve = -1.0;
// clear 模型化"移除所有图元"图元计数归零反映当前场景状态clears 累加。
void clear() override {
++clears;
surveyLines = curtains = volumes = terrains = 0;
}
void setVerticalExaggeration(double v) override { ve = v; }
void addSurveyLine(const core::Grid&) override { ++surveyLines; }
void addCurtain(const core::Grid&, const core::ColorScale&) override { ++curtains; }
void addVolume(const data::VolumeGrid&, const core::ColorScale&) override { ++volumes; }
void addTerrain(const data::TerrainPaths&) override { ++terrains; }
void render(bool is2D) override { ++renders; lastIs2D = is2D; }
int props() const { return surveyLines + curtains + volumes + terrains; }
};
// 同步小数据仓储loadGrid 返回 2x2 gridloadColorScale 返回两段色阶。
struct FakeDsRepo : data::IDatasetRepository {
std::vector<data::GsNode> loadStructure() override { return {}; }
core::Grid loadGrid(const std::string&) override {
core::Grid g(2, 2);
g.lat = {22.0, 22.001};
g.lon = {114.0, 114.001};
return g;
}
core::ScatterField loadScatter(const std::string&) override { return {}; }
core::ColorScale loadColorScale(const std::string&) override {
core::ColorScale cs;
cs.addStop(0.0, core::Rgba{0, 0, 255, 255});
cs.addStop(1.0, core::Rgba{255, 0, 0, 255});
return cs;
}
core::ColorScale loadScatterColorScale(const std::string&) override { return loadColorScale(""); }
std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; }
};
// 同步三维仓储dimensionOf 全当 3DloadVolume 立即回调一个最小有效体。
struct FakeSceneRepo : data::I3dSceneRepository {
data::DsDimension dimensionOf(const data::DsRow&) const override {
return data::DsDimension::Dim3D;
}
void loadVolume(const std::string&, std::function<void(data::VolumeGrid)> onOk,
OnError) override {
data::VolumeGrid g;
g.vol = core::ScalarVolume(2, 2, 2);
g.spacing = {{1.0, 1.0, 1.0}};
g.vmin = 0.0; g.vmax = 1.0;
onOk(std::move(g)); // 同步回调(异步壳)
}
void loadTerrainPaths(std::function<void(data::TerrainPaths)> onOk, OnError) override {
onOk(data::TerrainPaths{"dem.tif", "image.tif"});
}
};
} // namespace
// 2D 模式 + 勾选 1 ds → 1 个测线 actor无帘面/体素/地形。
TEST(VtkSceneController, Map2DWithOneDatasetAddsSurveyLine) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::Map2D);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.surveyLines, 1);
EXPECT_EQ(view.curtains, 0);
EXPECT_EQ(view.volumes, 0);
EXPECT_GE(view.renders, 1);
EXPECT_TRUE(view.lastIs2D);
}
// 3D 模式 + 帘面图层 → 1 帘面 actor。
TEST(VtkSceneController, View3DCurtainAddsCurtain) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.curtains, 1);
EXPECT_EQ(view.surveyLines, 0);
EXPECT_FALSE(view.lastIs2D);
}
// 3D + 帘面 + 体素 → 帘面 1 + 体素 1体素经异步回调进场
TEST(VtkSceneController, View3DWithVoxelAddsVolume) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setLayer(SceneLayer::Voxel, true);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.curtains, 1);
EXPECT_EQ(view.volumes, 1);
}
// 3D + 地形 → 地形 1与勾选数据集无关地形是场景图层
TEST(VtkSceneController, View3DWithTerrainAddsTerrain) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setLayer(SceneLayer::Terrain, true);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.terrains, 1);
EXPECT_EQ(view.curtains, 1);
}
// 取消勾选 → clear 后无任何图元。
TEST(VtkSceneController, UncheckAllClearsScene) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setCheckedDatasets({"ds1"});
ASSERT_EQ(view.curtains, 1);
c.setCheckedDatasets({}); // 取消全部勾选
EXPECT_EQ(view.curtains, 0);
EXPECT_EQ(view.volumes, 0);
// 最后一次重建仍调用 clear。
EXPECT_GE(view.clears, 2);
}
// 纵向比例传到视图。
TEST(VtkSceneController, VerticalExaggerationForwarded) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setVerticalExaggeration(3.5);
c.setCheckedDatasets({"ds1"});
EXPECT_DOUBLE_EQ(view.ve, 3.5);
}
// 多个数据集 → 每个一个帘面。
TEST(VtkSceneController, MultipleDatasetsAddMultipleCurtains) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setCheckedDatasets({"ds1", "ds2", "ds3"});
EXPECT_EQ(view.curtains, 3);
}

View File

@ -1,70 +0,0 @@
#include <gtest/gtest.h>
#include <string>
#include "repo/I3dSceneRepository.hpp"
#include "repo/LocalSample3dRepository.hpp"
#include "repo/LocalSampleRepository.hpp"
using namespace geopro::data;
static const std::string kDir =
"D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/";
static const std::string kCrs = "EPSG:4547";
namespace {
DsRow rowWith(const std::string& ddCode) {
DsRow r;
r.ddCode = ddCode;
return r;
}
} // namespace
// dimensionOf各 ddCode → 维度映射同步纯函数spec §6.1)。
TEST(LocalSample3dRepo, DimensionOfMapsDdCode) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_voxel")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_Structual3D")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_Property3D")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_section")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_inversion_data")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_slice")), DsDimension::Analysis3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_trajectory_data")), DsDimension::Dim2D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_unknown_xyz")), DsDimension::Other);
}
// loadVolume回调收到有效 VolumeGridnx>0 且 vmax>vmin需 PROJ_DATA。
TEST(LocalSample3dRepo, LoadVolumeCallsBackWithValidGrid) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
bool ok = false;
std::string err;
VolumeGrid got;
repo.loadVolume("voxel1", [&](VolumeGrid g) { ok = true; got = std::move(g); },
[&](const std::string& m) { err = m; });
ASSERT_TRUE(ok) << "loadVolume onErr: " << err;
EXPECT_GT(got.vol.nx(), 0);
EXPECT_GT(got.vol.ny(), 0);
EXPECT_GT(got.vol.nz(), 0);
EXPECT_GT(got.vmax, got.vmin);
EXPECT_TRUE(got.valid());
}
// loadTerrainPaths回调收到 dem/image 绝对路径(非空)。
TEST(LocalSample3dRepo, LoadTerrainPathsCallsBack) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
bool ok = false;
TerrainPaths got;
repo.loadTerrainPaths([&](TerrainPaths p) { ok = true; got = std::move(p); },
[&](const std::string&) {});
ASSERT_TRUE(ok);
EXPECT_FALSE(got.demPath.empty());
EXPECT_FALSE(got.imagePath.empty());
}

View File

@ -1,45 +0,0 @@
#include <gtest/gtest.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
#include <vtkVolume.h>
#include "Scene.hpp"
using geopro::render::Scene;
// addActor 把 vtkActor 加入 rendererview prop 计数 +1。
TEST(SceneTest, AddActorIncrementsViewProps) {
Scene scene;
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 0);
auto a = vtkSmartPointer<vtkActor>::New();
scene.addActor(a);
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 1);
}
// addViewProp 接受 vtkVolumevtkProp3D非 vtkActor——体绘制必经此口。
TEST(SceneTest, AddViewPropAcceptsVolume) {
Scene scene;
auto vol = vtkSmartPointer<vtkVolume>::New();
scene.addViewProp(vol);
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 1);
}
// clear() 经 RemoveAllViewProps 清空 actor 与 volume覆盖体绘制 prop
TEST(SceneTest, ClearRemovesActorsAndVolumes) {
Scene scene;
scene.addActor(vtkSmartPointer<vtkActor>::New());
scene.addViewProp(vtkSmartPointer<vtkVolume>::New());
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 2);
scene.clear();
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 0);
}
// 空指针安全addActor/addViewProp(nullptr) 不崩、不增计数。
TEST(SceneTest, NullPropsAreIgnored) {
Scene scene;
scene.addActor(nullptr);
scene.addViewProp(nullptr);
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 0);
}

View File

@ -2,7 +2,6 @@
"name": "geopro-desktop", "name": "geopro-desktop",
"version": "0.1.0", "version": "0.1.0",
"description": "Geopro 3.0 desktop client (Qt6 + VTK9) - M1. 方案②-修订: Qt/VTK/ADS/QtKeychain 对接官方 MSVC Qt(不走 vcpkg); 仅非 Qt 依赖走 vcpkg, 按层递增。", "description": "Geopro 3.0 desktop client (Qt6 + VTK9) - M1. 方案②-修订: Qt/VTK/ADS/QtKeychain 对接官方 MSVC Qt(不走 vcpkg); 仅非 Qt 依赖走 vcpkg, 按层递增。",
"builtin-baseline": "10ceb139a610ebf3c6aa49cdc4a4b7f3db5d3f2b",
"dependencies": [ "dependencies": [
"eigen3", "eigen3",
"gdal", "gdal",