Compare commits
No commits in common. "2c204a134aec12fe91a0970d04a0f2d511ff4ff5" and "498b786c806ac13276e5f8fec893da05907a9456" have entirely different histories.
2c204a134a
...
498b786c80
11
build.bat
11
build.bat
|
|
@ -25,17 +25,14 @@ if not exist "%VSWHERE%" (
|
|||
echo [build] vswhere not found. Open "x64 Native Tools Command Prompt for VS" and build manually.
|
||||
exit /b 1
|
||||
)
|
||||
REM -all -prerelease for VS2026 preview (note: -latest yields empty on this preview, and
|
||||
REM -products * would pull in the bundled BuildTools whose vcpkg/env breaks our preset);
|
||||
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 )
|
||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -property installationPath`) do set "VSPATH=%%i"
|
||||
if not defined VSPATH ( echo [build] Visual Studio not found. & exit /b 1 )
|
||||
|
||||
set "VCVARS=%VSPATH%\VC\Auxiliary\Build\vcvars64.bat"
|
||||
set "CMAKE=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.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 "%CMAKE%" ( echo [build] cmake not found: "%CMAKE%" & 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 )
|
||||
|
||||
REM --- activate MSVC environment (cl / link / include / lib) ---
|
||||
call "%VCVARS%" >nul
|
||||
|
|
|
|||
|
|
@ -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(评审 HIGH,TDD)
|
||||
- `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()` 后归零。
|
||||
- → verify:RED→GREEN;体绘制 prop 能进场。
|
||||
|
||||
### Step 2 — I3dSceneRepository + LocalSample3dRepository(TDD)
|
||||
- `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`。
|
||||
- → verify:RED→GREEN。
|
||||
|
||||
### Step 3 — VtkSceneController(TDD 编排逻辑)
|
||||
- `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。
|
||||
- → verify:RED→GREEN,编排逻辑脱离 GUI 可测。
|
||||
|
||||
### Step 4 — 接入 main.cpp(集成)
|
||||
- 用 `VtkSceneController` 实例取代 `rebuildCentral` lambda(`main.cpp:508`)与裸 `show*` 标志(246–251)。
|
||||
- 接线:`ObjectTreePanel` 勾选变化 → `setCheckedDatasets`;`act2D/act3D` → `setViewMode`;`chkCurtain/chkVoxel/chkTerrain` → `setLayer`;主题切换 → 触发重渲染(控制器内重跑)。
|
||||
- 清理本期产生的孤儿:未用的 `slicePlane` 声明、被取代的 lambda/标志(只清相关项,遵守 surgical changes)。`chkSlice` 暂保留禁用(P3 接)。
|
||||
- → verify:构建通过;启动手测达成「成功判据 1、2」。
|
||||
|
||||
### Step 5 — 构建 + 运行验证
|
||||
- `cmake --build` + `ctest`。
|
||||
- 运行客户端,勾选样本对象,截图确认帘面/体素/地形渲染。
|
||||
- → verify:成功判据 1–5 全达成。
|
||||
|
||||
### 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+ 接后端返工)。
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
# P2:三维数据集栏(坐标轴 / 比例 / 快捷视图 / Zoom)
|
||||
|
||||
- 日期:2026-06-15
|
||||
- 分支:`feat/vtk-3d-view`
|
||||
- 上游:spec `2026-06-15-vtk-3d-supplementary-design.md`(§4 行 C3–C8、§5.3、§7.2);接 P1(`VtkSceneController`/`I3dSceneView`/`VtkSceneView` 已就位)
|
||||
- 目标:给三维视图加一组**纯前端、无后端依赖**的相机/坐标轴/比例控件(补充需求"三维数据集栏"工具条的功能项)。P1 已让中央渲染复活,P2 让用户能调坐标轴、纵向比例、快捷视角、缩放。
|
||||
|
||||
## 范围
|
||||
**范围内**:
|
||||
- **快捷视图**(C7):前/后/左/右/上/下 6 向相机预设 + **Zoom In/Out/Fit**(C8)。
|
||||
- **水平/垂直比例**(C6):可调纵向夸张(复用 P1 已有 `VtkSceneController::setVerticalExaggeration`),UI 滑块/输入;范围如 1–10,默认 2。
|
||||
- **坐标轴**(C3–I5):显示方式 标准/三维立体/不显示;刻度单位 无/米/英尺/经纬度;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/mPerDegLon,lat=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=+y;Front: pos.y<focal、viewUp=+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,中文可能缺字形 → 标签用数字/英文单位优先。
|
||||
|
|
@ -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 地球是 **ThreeTile(Three.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`,"地大展示")很可能在别的项目,待用户指认后再实地学习其切片/异常几何(不阻塞 P1–P3,因切片可对 LocalSample 体素开发)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 目标与范围
|
||||
|
||||
### 2.1 总目标
|
||||
在现有 render actor 地基上,复活并扩展中央 VTK 为「补充需求」描述的三维分析工作台;**用静态样本数据驱动**,但所有数据出入口走**为真实后端预留的仓储接口**(§6)。
|
||||
|
||||
### 2.2 范围内(本设计覆盖)
|
||||
- 三子列表栏 UI 结构(三维数据集 / 二维数据集 / 三维分析)。
|
||||
- 三维数据集栏:坐标轴设置、水平/垂直比例、快捷视图(前后左右上下)、Zoom(In/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 |
|
||||
| C3–I3 | 坐标轴显示方式:标准/三维立体/不显示 | 无坐标轴 | `vtkCubeAxesActor`(标准)/`vtkAxesActor`(立体)/隐藏;§7.2 |
|
||||
| D4 | O 点位置 | 无 | 坐标轴原点配置(世界系原点 / 数据包围盒角)|
|
||||
| D5–I5 | 刻度:无刻度/米/英尺/经纬度/字体 | 无 | 刻度单位换算 + 经纬度用 `GeoLocalFrame` 反算 + 字体设置 |
|
||||
| C6 | 水平/垂直比例 | 硬编码 `kVerticalExaggeration=2.0`(`main.cpp:208`)| 做成可调,作用于全部 3D actor `SetScale(1,1,VE)` |
|
||||
| C7 / D7–I7 | 快捷视图:前/后/左/右/上/下 | `CameraPreset` 仅 Top2D/Free3D | 新增 6 方向预设(§5.3)|
|
||||
| C8 / D8–F8 | Zoom:In/Out/Fit | 仅 `ResetCamera`(=Fit) | In/Out 调 `camera->Zoom()`;Fit=ResetCamera |
|
||||
| C9–D10 | 三维数据集列表:筛勾选对象中 ds 维度=3D;勾选多个显示 | sections 为空 | §6.1 维度筛选 + §8 编排复活 |
|
||||
| C13–F13 | 地图:天地图/Google Map/隐藏 | 中央无底图 | §7.3 底图层(VTK 瓦片)|
|
||||
| C14 | 2D 视图:关闭/Z=0/顶部/底部/自定义 Z | 无 | 切面高度控制 |
|
||||
| C15–D16 | 二维数据集列表:ds 维度=2D | 同上 | §6.1 + §8 |
|
||||
| C19–D20 | 三维分析列表:按 对象/三维体模型/切片 树 | 无 | §7.4 分析树 |
|
||||
| D21,F22–G22 | 右键·三维体:上下切片 | 无 | §9 轴向切片工具(水平面)|
|
||||
| F23 | 前后切片 | 无 | §9 轴向切片 |
|
||||
| F24 | 左右切片 | 无 | §9 轴向切片 |
|
||||
| F25 | 任意切片(初始 45°,可任意调整)| 无 | §9 任意角度切片(`vtkPlaneWidget` 控面 + **`vtkImageReslice`** 重采样着色,非 `vtkCutter`)|
|
||||
| F26 | 色阶(参考 Geopro 1.0 优化)| 有 colorBar 解析 | 复用 `ColorScale` + 色阶编辑入口 |
|
||||
| F27 | 显示/隐藏 | 无 | actor 可见性 |
|
||||
| F28–G28 | 数据详情 → 详情栏 | 详情面板在 | §10 三维体详情类型 |
|
||||
| E29,F30–F36 | 右键·切片数据集:保存/保存为/导出/删除/色阶/显隐/详情 | 无 | §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,F49–F50 | 创建异常:异常工具,光标拾取起点;结束保存弹框(确定截图大小/异常坐标,参考 1.0)| 有 2D `AnomalyActor`,无创建流 | §9.4 异常圈定工具 + §6.4 saveAnomaly + 截图 |
|
||||
| E51–F51 | 保存为数据集 | 无 | §6.3 |
|
||||
| E52–F52 | 导出为图片 | 无 | `vtkWindowToImageFilter` |
|
||||
| E53–F53 | 导出到 dat | 无 | 切面采样导出 |
|
||||
| E54–F54 | 正视图 | 无 | §9.3 |
|
||||
| E55–F55 | 视图翻转(水平旋转 180°)| 无 | 相机 `Azimuth(180)` |
|
||||
| E56–F56 | 关闭(取消当前切片)| 无 | 移除切片工具 |
|
||||
| A58,B59–B65 | 三维体详情:源数据/切片数据/异常/插值模型(克里金,IDW)/插值参数/色阶参数/测量(点数,体积)| 详情引擎在,类型未做 | §10.1 |
|
||||
| A67 | 切片详情:参照 dd_Section | `dd_section` 渲染已有 | §10.2 复用 |
|
||||
| A69,B70,C71–D77 | 异常体列表树:异常体→分组→异常 | `ObjectExceptionPanel` 只读树在 | §11 扩展为可操作 |
|
||||
| C79,D80–D81 | 操作:删除/删除分组 | 无 | §6.4 delete/deleteGroup |
|
||||
| C83,C84 | 异常属性 + VTK↔列表 双向选中联动 | 无联动 | §9.3 + §11 |
|
||||
| B86,C87–F87 | 异常体显示过滤:全部显示/随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 Anomaly;3D 用包围几何)
|
||||
};
|
||||
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.5):web 端**没有三栏**,三栏是补充需求为桌面定义的新结构,以 xlsx 为准设计。
|
||||
|
||||
在 VTK dock 内(或左侧新 dock)放三个可切换 tab/分段:**三维数据集 / 二维数据集 / 三维分析**。每栏顶部是该栏专属工具条,下方是数据集勾选列表(按 §6.1 维度筛选当前勾选对象的 ds)。
|
||||
|
||||
**职责切分(决策,不再是开放问题)**:保留现有 `ObjectTreePanel`(左上勾选对象)作为"对象勾选源";三栏列表只在被勾选对象范围内、按维度过滤显示 ds。**左下数据集列表服务"详情查看",三栏列表服务"VTK 渲染勾选"**——两条线并存。依据:web 端本身也是"对象树勾选 + 独立异常列表"的多列表结构,两条线与原版心智一致;且补充需求明确三栏服务于 VTK 渲染,与详情查看是不同动作。
|
||||
|
||||
### 7.2 三维数据集栏工具条
|
||||
坐标轴下拉(标准/立体/不显示)+ O 点 + 刻度(无/米/英尺/经纬度)+ 字体 | 水平/垂直比例(滑块/输入)| 快捷视图(前后左右上下 6 钮)| Zoom(In/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 行 D21–F36)。
|
||||
|
||||
---
|
||||
|
||||
## 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),沿固定轴向、自动重采样着色,角度不可调(符合 G22–G24)。
|
||||
- **任意切片**(F25):`vtkPlaneWidget`(控制面位姿)+ **`vtkImageReslice`**(按斜面对体素重采样出着色剖面),初始 45°,可任意旋转。**不用 `vtkCutter`**(评审 HIGH:cutter 切 `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/F49–F50)
|
||||
自定义 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.2,P2):刻度"经纬度"用 `GeoLocalFrame` 反算;"米/英尺"基于世界米。O 点默认世界原点还是数据包围盒角?→ P2 开工前定,倾向"数据包围盒角"。
|
||||
2. **切片"保存为数据集"落库形态**(§6.3,P3):本轮 LocalSample 内存态;是否持久化到本地文件以便重启可见?
|
||||
3. **真三维体项目**(§1.5,P3/P4):威立雅无真三维体;需用户指认有 `dd_Structual3D` 的项目,实地学习其切片/异常几何与交互细节。**不阻塞 P3**(切片对 LocalSample 体素先开发)。
|
||||
4. **"参考 Geopro 1.0"**(F26 色阶 / F50 异常保存框,P4):目前无 1.0 参考 → 先用现有 colorBar 解析 + 标准保存框近似,拿到 1.0 后精修。
|
||||
5. **底图 token / Google 可用性**(§7.3,P5):天地图需 token;Google 国内可用性。本轮先天地图 + 隐藏。
|
||||
|
||||
---
|
||||
|
||||
## 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 基础。
|
||||
|
|
@ -54,7 +54,7 @@ add_executable(geopro_desktop WIN32
|
|||
panels/LoadingOverlay.cpp
|
||||
panels/DatasetDetailPage.cpp
|
||||
panels/DatasetDetailPanel.cpp
|
||||
VtkSceneView.cpp
|
||||
CentralScene.cpp
|
||||
ProjectListDialog.cpp
|
||||
ObjectFormDialog.cpp
|
||||
ImportDatasetDialog.cpp
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 };
|
||||
|
||||
// 一个待渲染剖面:grid(2D 测线 / 3D 帘面都用)+ colorScale(3D 帘面上色)。
|
||||
struct SectionInput {
|
||||
geopro::core::Grid grid;
|
||||
geopro::core::ColorScale colorScale;
|
||||
};
|
||||
|
||||
// 中央场景重建(脱离对象树,按显式 sections 渲染):
|
||||
// 2D = 每个 section 的 buildSurveyLine;3D = 每个 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
122
src/app/main.cpp
122
src/app/main.cpp
|
|
@ -78,7 +78,6 @@
|
|||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/LocalSampleRepository.hpp"
|
||||
#include "repo/LocalSample3dRepository.hpp"
|
||||
|
||||
#include "ApiClient.hpp"
|
||||
#include "AuthService.hpp"
|
||||
|
|
@ -89,12 +88,11 @@
|
|||
#include "Theme.hpp"
|
||||
#include "SettingsDialog.hpp"
|
||||
#include "TopBar.hpp"
|
||||
#include "CentralScene.hpp"
|
||||
#include "ProjectListDialog.hpp"
|
||||
#include "ObjectFormDialog.hpp"
|
||||
#include "ImportDatasetDialog.hpp"
|
||||
#include "WorkbenchNavController.hpp"
|
||||
#include "VtkSceneController.hpp"
|
||||
#include "VtkSceneView.hpp"
|
||||
#include "api/NavRequest.hpp"
|
||||
#include "api/NavLoads.hpp"
|
||||
#include "DatasetDetailController.hpp"
|
||||
|
|
@ -140,6 +138,7 @@
|
|||
#include <vtkCamera.h>
|
||||
#include <vtkCameraInterpolator.h>
|
||||
#include <vtkGenericOpenGLRenderWindow.h>
|
||||
#include <vtkImagePlaneWidget.h>
|
||||
#include <vtkLookupTable.h>
|
||||
#include <vtkProperty.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]);
|
||||
}
|
||||
|
||||
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
|
||||
using geopro::app::ViewMode;
|
||||
|
||||
// 纵向夸张倍数(Z 基准统一,M-3):全项目共用同一倍数,使 帘面(z) / 体素 / 切片 /
|
||||
// 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。
|
||||
// 单一可调常量:要整体调纵向观感改这一处即可。
|
||||
|
|
@ -229,36 +231,29 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// Scene 非 QObject:堆分配,用 widget 销毁信号清理(widget 随 window 销毁)。
|
||||
auto* scene = new geopro::render::Scene();
|
||||
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
||||
QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; });
|
||||
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
||||
vtkWidget->setRenderWindow(renderWindow);
|
||||
renderWindow->AddRenderer(scene->renderer());
|
||||
|
||||
vtkRenderer* rendererPtr = scene->renderer();
|
||||
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
|
||||
|
||||
// 中央渲染编排(VtkSceneController + VtkSceneView,取代旧 rebuildCentral lambda 与裸 show* 标志)。
|
||||
// 3D 场景仓储用 LocalSample3dRepository(本期样本驱动;接口异步,将来换 Api 实现不动上层)。
|
||||
// 视图(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;
|
||||
});
|
||||
// 当前视图模式(全局共享,切视图/勾选时据此重建内容)。默认二维地图。
|
||||
auto viewMode = std::make_shared<ViewMode>(ViewMode::Map2D);
|
||||
|
||||
// PROJ 可用性(体素/地形/切片层都需配准):失败则浮层相应勾选禁用并提示。
|
||||
bool crsAvailable = false;
|
||||
// 三维图层显隐(由「视图详情」浮层控制)+ 项目 CRS→WGS84(体素配准)。
|
||||
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 {
|
||||
geopro::core::CrsTransform probe(kProjectCrs, kWgs84);
|
||||
crsAvailable = true;
|
||||
crs = std::make_shared<geopro::core::CrsTransform>(kProjectCrs, kWgs84);
|
||||
} catch (const std::exception&) {
|
||||
crsAvailable = false;
|
||||
crs.reset();
|
||||
}
|
||||
|
||||
// 停靠系统配置(必须在 CDockManager 构造前设置):对齐原型——面板固定、
|
||||
|
|
@ -350,14 +345,17 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
chkTerrain->setChecked(false);
|
||||
auto* chkSlice = new QCheckBox(QStringLiteral("切片(dd_slice)"));
|
||||
chkSlice->setChecked(false);
|
||||
if (!crsAvailable) { // PROJ 不可用 → 体素/地形层(都需配准)禁用并提示
|
||||
if (!crs) { // PROJ 不可用 → 体素/切片/地形层(都需配准)禁用并提示
|
||||
const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用");
|
||||
chkVoxel->setEnabled(false); chkVoxel->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(chkCurtain);
|
||||
layerLayout->addWidget(chkVoxel);
|
||||
|
|
@ -505,8 +503,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
};
|
||||
hideDockTitleBars();
|
||||
|
||||
// 中央渲染由 sceneCtrl(VtkSceneController)驱动:勾选对象/2D-3D切换/图层勾选/主题 → 重建场景。
|
||||
// (旧 rebuildCentral lambda + 裸 show* 标志已由控制器取代。)
|
||||
// 中央编排已解耦到 CentralScene::rebuildCentralScene(数据驱动)。本轮空 sections → 空背景占位。
|
||||
// 下一轮:用真实 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) → 属性表单 + 聚焦详情已开页 ──
|
||||
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,
|
||||
[sceneCtrl, showLayerPanel]() {
|
||||
[viewMode, rebuildCentral, showLayerPanel]() {
|
||||
*viewMode = ViewMode::Map2D;
|
||||
showLayerPanel(false);
|
||||
sceneCtrl->setViewMode(CtrlViewMode::Map2D);
|
||||
rebuildCentral();
|
||||
});
|
||||
QObject::connect(act3D, &QAbstractButton::clicked, vtkWidget,
|
||||
[sceneCtrl, showLayerPanel]() {
|
||||
[viewMode, rebuildCentral, showLayerPanel]() {
|
||||
*viewMode = ViewMode::View3D;
|
||||
showLayerPanel(true);
|
||||
sceneCtrl->setViewMode(CtrlViewMode::View3D);
|
||||
rebuildCentral();
|
||||
});
|
||||
|
||||
// ──「视图详情」图层勾选 → 控制器更新图层 → 重建中央 ──
|
||||
// ──「视图详情」图层勾选 → 更新图层显隐 → 重建中央 ──
|
||||
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,
|
||||
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Voxel, on); });
|
||||
[showVoxel, rebuildCentral](bool on) {
|
||||
*showVoxel = on;
|
||||
rebuildCentral();
|
||||
});
|
||||
QObject::connect(chkTerrain, &QCheckBox::toggled, vtkWidget,
|
||||
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Terrain, on); });
|
||||
|
||||
// ── 左上对象树勾选 → 渲染勾选数据集(本期样本驱动:任意勾选 → 样本 ds "grid1",空 → 清场)──
|
||||
// 真实接 Api 时改为把勾选 TM 映射到其 ds 维度过滤后的真实 dsId 列表(spec §6.1/§8)。
|
||||
QObject::connect(objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, sceneCtrl,
|
||||
[sceneCtrl, emptyState](const QStringList& tmIds) {
|
||||
const bool hasData = !tmIds.isEmpty();
|
||||
emptyState->setVisible(!hasData); // 有勾选→隐藏引导层,露出中央渲染
|
||||
sceneCtrl->setCheckedDatasets(
|
||||
hasData ? QStringList{QStringLiteral("grid1")} : QStringList{});
|
||||
[showTerrain, rebuildCentral](bool on) {
|
||||
*showTerrain = on;
|
||||
rebuildCentral();
|
||||
});
|
||||
QObject::connect(chkSlice, &QCheckBox::toggled, vtkWidget,
|
||||
[showSlice, rebuildCentral](bool on) {
|
||||
*showSlice = on;
|
||||
rebuildCentral();
|
||||
});
|
||||
|
||||
// ── 启动:建立一次中央视图(默认 2D,无勾选 → 空场景 + 背景)。
|
||||
sceneCtrl->setViewMode(CtrlViewMode::Map2D);
|
||||
// ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。
|
||||
rebuildCentral();
|
||||
|
||||
// VTK 背景随主题切换:控制器重渲染(走完整渲染路径、末尾必 Render)。
|
||||
// context 用 sceneCtrl(非 window):ThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开,
|
||||
// 否则 window 析构期间 sceneCtrl(其孙级子对象)已销毁、主题异步变化会触悬垂指针。
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, sceneCtrl,
|
||||
[sceneCtrl]() { sceneCtrl->rebuild(); });
|
||||
// VTK 背景随主题切换:直接重跑 rebuildCentral(走完整渲染路径、末尾必 Render,
|
||||
// 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window,
|
||||
[rebuildCentral]() {
|
||||
rebuildCentral();
|
||||
});
|
||||
|
||||
// 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备),
|
||||
// 下=工具条(工作空间切换 + 项目 + 帮助/通知/设置 + 用户)。纵向堆叠后挂到主窗口顶部。
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
find_package(Qt6 COMPONENTS Core REQUIRED)
|
||||
add_library(geopro_controller STATIC
|
||||
WorkbenchNavController.cpp
|
||||
DatasetDetailController.cpp
|
||||
VtkSceneController.cpp)
|
||||
DatasetDetailController.cpp)
|
||||
target_include_directories(geopro_controller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(geopro_controller PUBLIC geopro_data Qt6::Core)
|
||||
target_compile_features(geopro_controller PUBLIC cxx_std_17)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
// 3D:DEM 地形 + 影像纹理。
|
||||
virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0;
|
||||
|
||||
// 应用相机预设(2D 俯视 / 3D 自由)并提交渲染。
|
||||
virtual void render(bool is2D) = 0;
|
||||
};
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
@ -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;
|
||||
// 坏 dsId(loadGrid/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
|
||||
|
|
@ -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
|
||||
|
|
@ -3,7 +3,6 @@ find_package(Qt6 COMPONENTS Core REQUIRED)
|
|||
add_library(geopro_data STATIC
|
||||
parse/SampleParsers.cpp
|
||||
repo/LocalSampleRepository.cpp
|
||||
repo/LocalSample3dRepository.cpp
|
||||
dto/NavDto.cpp
|
||||
dto/DatasetChartDto.cpp
|
||||
dto/MeasurementDto.cpp
|
||||
|
|
|
|||
|
|
@ -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::array,spec §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::function(LocalSample 本地数据同步算好后直接回调;
|
||||
// 将来 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
|
||||
|
|
@ -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 → ScalarVolume(maxDist 外 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
|
||||
|
|
@ -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
|
||||
|
|
@ -17,9 +17,4 @@ void Scene::addActor(vtkActor* a)
|
|||
if (a) renderer_->AddActor(a);
|
||||
}
|
||||
|
||||
void Scene::addViewProp(vtkProp* p)
|
||||
{
|
||||
if (p) renderer_->AddViewProp(p);
|
||||
}
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
#include <vtkSmartPointer.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkProp.h>
|
||||
namespace geopro::render {
|
||||
|
||||
// 单一渲染场景:持有 vtkRenderer(白底),统一管理 actor 的加入与清除。
|
||||
|
|
@ -13,10 +12,8 @@ public:
|
|||
|
||||
vtkRenderer* renderer() const { return renderer_.Get(); }
|
||||
|
||||
void clear(); // 移除所有 view prop(含体绘制 vtkVolume),支持重复切换数据集
|
||||
void clear(); // 移除所有 view prop,支持重复切换数据集
|
||||
void addActor(vtkActor* a); // actor 由 renderer 引用计数保活
|
||||
// 体绘制 vtkVolume 是 vtkProp3D(非 vtkActor),经此通用入口进场;prop 由 renderer 引用计数保活。
|
||||
void addViewProp(vtkProp* p);
|
||||
|
||||
private:
|
||||
vtkSmartPointer<vtkRenderer> renderer_;
|
||||
|
|
|
|||
|
|
@ -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_local_repo.cpp)
|
||||
# I3dSceneRepository/LocalSample3dRepository:dimensionOf 映射 + 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_dataset_chart_dto.cpp)
|
||||
target_sources(geopro_tests PRIVATE data/test_measurement_dto.cpp)
|
||||
|
|
@ -78,8 +76,6 @@ endif()
|
|||
# render 层:ColorLutBuilder(core ColorScale -> vtkLookupTable)。
|
||||
# 需 vtkLookupTable(VTK::CommonCore);geopro_render 已 PUBLIC 传递其余 VTK 组件。
|
||||
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel RenderingCore)
|
||||
# Scene:addActor/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_contour_bands.cpp)
|
||||
# dd_voxel:buildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) 构建不崩 + dims 正确。
|
||||
|
|
@ -121,8 +117,6 @@ target_sources(geopro_tests PRIVATE app/test_scatter_hover.cpp)
|
|||
find_package(Qt6 COMPONENTS Test REQUIRED)
|
||||
target_sources(geopro_tests PRIVATE controller/test_dataset_detail_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)
|
||||
|
||||
add_subdirectory(spike) # spike S3: banded contour 渲染验证
|
||||
|
|
|
|||
|
|
@ -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 grid,loadColorScale 返回两段色阶。
|
||||
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 全当 3D;loadVolume 立即回调一个最小有效体。
|
||||
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);
|
||||
}
|
||||
|
|
@ -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:回调收到有效 VolumeGrid(nx>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());
|
||||
}
|
||||
|
|
@ -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 加入 renderer,view 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 接受 vtkVolume(vtkProp3D,非 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);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
"name": "geopro-desktop",
|
||||
"version": "0.1.0",
|
||||
"description": "Geopro 3.0 desktop client (Qt6 + VTK9) - M1. 方案②-修订: Qt/VTK/ADS/QtKeychain 对接官方 MSVC Qt(不走 vcpkg); 仅非 Qt 依赖走 vcpkg, 按层递增。",
|
||||
"builtin-baseline": "10ceb139a610ebf3c6aa49cdc4a4b7f3db5d3f2b",
|
||||
"dependencies": [
|
||||
"eigen3",
|
||||
"gdal",
|
||||
|
|
|
|||
Loading…
Reference in New Issue