docs: 保留并解耦中央三维编排(CentralScene helper) + 补充下一轮对接真实DS步骤

This commit is contained in:
gaozheng 2026-06-09 10:49:31 +08:00
parent 46358f2964
commit 1fd8bb4d63
2 changed files with 249 additions and 29 deletions

View File

@ -35,6 +35,7 @@
- `src/controller/CMakeLists.txt``geopro_controller` 静态库AUTOMOC ON - `src/controller/CMakeLists.txt``geopro_controller` 静态库AUTOMOC ON
- `src/controller/WorkbenchNavController.hpp` / `.cpp` — 导航状态机 - `src/controller/WorkbenchNavController.hpp` / `.cpp` — 导航状态机
- `src/app/panels/ObjectTreePanel.hpp` / `.cpp` — 被动对象树视图 - `src/app/panels/ObjectTreePanel.hpp` / `.cpp` — 被动对象树视图
- `src/app/CentralScene.hpp` / `.cpp` — 中央三维编排的数据驱动 helper脱离对象树下一轮接真实 DS 复用)
- `tests/data/test_nav_dto.cpp` — DTO + 树构建单测 - `tests/data/test_nav_dto.cpp` — DTO + 树构建单测
**改造** **改造**
@ -1247,7 +1248,128 @@ git commit -m "feat(app): ObjectTreePanel 被动对象树项目→GS→TM"
--- ---
## Task 9: main.cpp 接线(构造仓储/控制器 + 信号 + 移除启动 demo ## Task 9: CentralScene 数据驱动 helper解耦中央三维编排
**Files:**
- Create: `src/app/CentralScene.hpp`, `src/app/CentralScene.cpp`
- Modify: `src/app/CMakeLists.txt`
把"每个剖面 section 的中央渲染"从对象树解耦为显式数据驱动 helper。本轮用空 sections中央占位
下一轮用真实 DS 构建 sections 调同一 helper 即复活spec §8.1 / §12.1。无单测VTK 渲染,手动联调)。
- [ ] **Step 1: 创建 `src/app/CentralScene.hpp`**
```cpp
#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
```
- [ ] **Step 2: 创建 `src/app/CentralScene.cpp`**
```cpp
#include "CentralScene.hpp"
#include <vtkActor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include "CameraPreset.hpp"
#include "Scene.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);
renderer->SetBackground(is2D ? 0.96 : 1.0, is2D ? 0.97 : 1.0, is2D ? 0.99 : 1.0);
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
```
> 头文件名核对自现有 `main.cpp` 用法:`buildSurveyLine`→`actors/MapLineActor.hpp`
> `buildCurtain`→`actors/CurtainActor.hpp``applyTop2D/applyFree3D`→`CameraPreset.hpp`
> `Scene`→`Scene.hpp`。这些符号来自 `geopro_render`app 已链接)。
- [ ] **Step 3: 接入 `src/app/CMakeLists.txt` 源列表**
`add_executable(geopro_desktop WIN32 ...)` 源列表加 `CentralScene.cpp`(与 Task 8 的 `panels/ObjectTreePanel.cpp` 同处):
```cmake
panels/ObjectTreePanel.cpp
CentralScene.cpp)
```
- [ ] **Step 4: 构建确认通过**
Run: `cmake --build build/release --target geopro_desktop`
Expected: 编译/链接通过CentralScene 暂未被引用,单独编译即可)。
- [ ] **Step 5: Commit**
```bash
git add src/app/CentralScene.hpp src/app/CentralScene.cpp src/app/CMakeLists.txt
git commit -m "feat(app): CentralScene 数据驱动 helper解耦中央三维编排下一轮接真实DS复用"
```
---
## Task 10: main.cpp 接线(构造仓储/控制器 + 信号 + 移除启动 demo
**Files:** **Files:**
- Modify: `src/app/main.cpp` - Modify: `src/app/main.cpp`
@ -1259,11 +1381,21 @@ git commit -m "feat(app): ObjectTreePanel 被动对象树项目→GS→TM"
在 main.cpp 顶部 include 区(`#include "TopBar.hpp"` 附近)加: 在 main.cpp 顶部 include 区(`#include "TopBar.hpp"` 附近)加:
```cpp ```cpp
#include "CentralScene.hpp"
#include "WorkbenchNavController.hpp" #include "WorkbenchNavController.hpp"
#include "api/ApiProjectRepository.hpp" #include "api/ApiProjectRepository.hpp"
#include "panels/ObjectTreePanel.hpp" #include "panels/ObjectTreePanel.hpp"
``` ```
并把匿名命名空间里原本的本地枚举定义:
```cpp
enum class ViewMode { Map2D, View3D };
```
替换为复用 helper 的同名枚举(保持后续 `ViewMode::Map2D` 等引用不变):
```cpp
using geopro::app::ViewMode;
```
- [ ] **Step 2: 修改 `buildWorkbench` 签名,注入控制器** - [ ] **Step 2: 修改 `buildWorkbench` 签名,注入控制器**
把: 把:
@ -1294,27 +1426,37 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
> 删除 `auto structure = std::make_shared<...>(repo.loadStructure());`(真实结构改由控制器提供)。 > 删除 `auto structure = std::make_shared<...>(repo.loadStructure());`(真实结构改由控制器提供)。
> 注意:原 `rebuildCentral` lambda 捕获了 `tree`/`structure`。见 Step 4 处理。 > 注意:原 `rebuildCentral` lambda 捕获了 `tree`/`structure`。见 Step 4 处理。
- [ ] **Step 4: 中央视图改为占位(移除本地结构驱动** - [ ] **Step 4: 中央视图改为调 CentralScene helper空 sections = 占位)**
`rebuildCentral` 原实现遍历 `tree` 勾选项并渲染本地 grid。本轮中央不接真实数据改为清空场景 + 应用背景,不再依赖 `tree`/`structure`。把 `rebuildCentral` lambda 整体替换为: `rebuildCentral` 原实现遍历 `tree` 勾选项 + `repo.loadGrid` + 体素/切片/地形。本轮中央不接真实数据,改为
委托 Task 9 的 `rebuildCentralScene`,传**空 sections** → 空背景占位(下一轮喂真实 DS 即复活)。把
`rebuildCentral` lambda 整体替换为:
```cpp ```cpp
// 本轮中央视图不接真实剖面数据(下一轮接 dd 接口):仅维护视图模式背景,内容占位为空。 // 中央编排已解耦到 CentralScene::rebuildCentralSceneTask 9。本轮空 sections → 空背景占位。
auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, slicePlane]() { // 下一轮:用真实 DS 数据构建 sections 调同一 helper 即复活spec §8.1 / §12.1)。
if (*slicePlane) { (*slicePlane)->Off(); *slicePlane = nullptr; } auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, showCurtain, frame]() {
scene->clear(); geopro::app::rebuildCentralScene(*scene, rendererPtr, renderWindowPtr, *viewMode,
const bool is2D = (*viewMode == ViewMode::Map2D); std::vector<geopro::app::SectionInput>{}, *showCurtain,
rendererPtr->SetBackground(is2D ? 0.96 : 1.0, is2D ? 0.97 : 1.0, is2D ? 0.99 : 1.0); *frame, kVerticalExaggeration);
if (is2D)
geopro::render::applyTop2D(rendererPtr);
else
geopro::render::applyFree3D(rendererPtr);
rendererPtr->ResetCamera();
renderWindowPtr->Render();
}; };
``` ```
> 这样 `showVoxel/showTerrain/crs/frame/refElev` 等捕获不再被 `rebuildCentral` 使用。它们仍被其它 lambda图层勾选引用——图层勾选回调保留但因中央为空而无可视效果本轮可接受**不删**这些变量与回调(保留渲染基础设施)。 > 这样 `showVoxel/showTerrain/showSlice/slicePlane/crs/refElev/structure` 不再被 `rebuildCentral` 捕获使用;
> 它们的声明与图层勾选回调**保留**(渲染基础设施不删),仅不再产生可视效果。
并把"视图详情"浮层里**体素 / 切片 / 地形**三个勾选框本轮置灰提示(它们不绑定单 DS需独立真实数据源
见 spec §12.1 E。在三个 `chk*` 创建之后、`if (!crs)` 块附近,追加:
```cpp
// 本轮中央不接真实派生层:体素/切片/地形勾选置灰,待下一轮接入对应数据源。
for (QCheckBox* c : {chkVoxel, chkSlice, chkTerrain}) {
c->setEnabled(false);
c->setToolTip(QStringLiteral("(下一轮接入真实数据源)"));
}
```
> `chkCurtain` 保持可用(切换 `showCurtain`,被 `rebuildCentralScene` 使用)。
- [ ] **Step 5: 删除"对象树驱动中央/数据列表"的旧连接** - [ ] **Step 5: 删除"对象树驱动中央/数据列表"的旧连接**
@ -1483,15 +1625,20 @@ git commit -m "feat(app): 工作台接入真实导航(空间/项目/对象树/
## 自检结论spec 覆盖核对) ## 自检结论spec 覆盖核对)
- 工作空间列表/切换 → Task 5仓储+ Task 6控制器+ Task 7TopBar+ Task 9(接线)✅ - 工作空间列表/切换 → Task 5仓储+ Task 6控制器+ Task 7TopBar+ Task 10(接线)✅
- 项目列表/切换 → 同上 ✅ - 项目列表/切换 → 同上 ✅
- 对象树 项目→GS→TM叶子=TM→ Task 4buildStructTree+ Task 8ObjectTreePanel - 对象树 项目→GS→TM叶子=TM→ Task 4buildStructTree+ Task 8ObjectTreePanel
- TM 下 DS 列表 → Task 3parseDatasets+ Task 5 + Task 9 ✅ - TM 下 DS 列表 → Task 3parseDatasets+ Task 5 + Task 10 ✅
- 失败显示错误/空状态、不回退本地样本 → Task 8showMessage+ Task 9loadFailed 接线)✅ - 失败显示错误/空状态、不回退本地样本 → Task 8showMessage+ Task 10loadFailed 接线)✅
- 渲染解耦占位、移除启动 demo、保留 render/LocalSampleRepository → Task 9 ✅ - 中央三维编排解耦为数据驱动 helper保留可复用→ Task 9CentralScene
- 渲染占位、移除启动 demo、保留 render/LocalSampleRepository/rebuildDetail → Task 10 ✅
- 项目 crsCode 存入控制器(下一轮替换 EPSG:4547→ Task 6currentCrsCode_ - 项目 crsCode 存入控制器(下一轮替换 EPSG:4547→ Task 6currentCrsCode_
- 分层:接口(net 复用)/数据(data: 仓储+dto)/逻辑(controller)/UI(app) → 各 Task 就位、依赖单向向下 ✅ - 分层:接口(net 复用)/数据(data: 仓储+dto)/逻辑(controller)/UI(app) → 各 Task 就位、依赖单向向下 ✅
- 纯逻辑单测dto + 树构建)→ Task 2/3/4 ✅;线程同步+WaitCursor → Task 9 - 纯逻辑单测dto + 树构建)→ Task 2/3/4 ✅;线程同步+WaitCursor → Task 10
## 下一轮(不在本计划) ## 下一轮(不在本计划,详见 spec §12.1
dd/ert/gpr 真实渲染替换占位crsCode 重建 GeoLocalFrame异步仓储 + 分页“加载更多”;用户信息 `auth/getUserInfo`token 过期自动跳登录;顶部菜单接真实页面。 接真实 DS 渲染分四步render 层零改):**A 取数**(新增 DS 内容仓储方法dd/ert/exception/clr 接口)→
**B 映射**DTO → `core::Grid/ScatterField/ColorScale/Anomaly`,加单测)→ **C 接线**(构建 `app::SectionInput`
调 Task 9 的 `rebuildCentralScene`;真实数据触发保留的 `rebuildDetail`,替换占位)→ **D 坐标系**(用
`currentCrsCode()` 重建 `GeoLocalFrame`,替换硬编码 EPSG:4547。另异步仓储+分页、用户信息、token 过期跳登录、
体素/切片/地形真实数据源、顶部菜单接页面。

View File

@ -228,12 +228,48 @@ private:
- 输入边界:`tmObjectId` / `projectId` 为空时短路不发请求。 - 输入边界:`tmObjectId` / `projectId` 为空时短路不发请求。
## 8. 渲染解耦 ## 8. 渲染解耦
现状:对象树(本地 grid1/grid2…直接驱动中央与数据详情。本轮真实树 id 与本地样本对不上,故:
- 启动不再自动渲染本地 demo。 现状:对象树(本地 grid1/grid2…直接驱动中央与数据详情`rebuildCentral`/`rebuildDetail` 都是
- 真实 DS 点击 → 中央/详情显示占位文案。 `main.cpp` 内捕获了本地 `tree`/`structure` 的 lambda。本轮真实树 id 与本地样本对不上,故解耦:
- 启动不再自动渲染本地 demo真实 DS 点击 → 中央/详情显示占位文案。
- `render/*`、`LocalSampleRepository`、`VoxelFromScatters` 等全部保留,待下轮按 dd/ert 接口复用。 - `render/*`、`LocalSampleRepository`、`VoxelFromScatters` 等全部保留,待下轮按 dd/ert 接口复用。
- 项目 `crsCode` 由 controller 存住,下一轮替换 `main.cpp` 中硬编码 `EPSG:4547` - 项目 `crsCode` 由 controller 存住,下一轮替换 `main.cpp` 中硬编码 `EPSG:4547`
### 8.1 中央三维编排:保留并解耦为数据驱动 helper关键改动
`rebuildCentral` 直接读对象树 + `repo.loadGrid`,与本地样本强耦合、无法复用到真实 DS。
**本轮把"每个剖面 section 的中央渲染"抽成显式数据驱动的 helper**,使下一轮"喂真实数据"即可复活,
无需重写编排:
```cpp
// src/app/CentralScene.{hpp,cpp}
namespace geopro::app {
enum class ViewMode { Map2D, View3D };
// 一个待渲染剖面grid2D 测线 / 3D 帘面都用)+ colorScale3D 帘面上色用)。
struct SectionInput {
geopro::core::Grid grid;
geopro::core::ColorScale colorScale;
};
// 中央场景重建(脱离对象树,按显式 sections 渲染):
// 2D = 每个 section 的 buildSurveyLine红线俯视
// 3D = 每个 section 的 buildCurtain断面墙受 showCurtain 开关 + 纵向夸张)。
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);
}
```
- **本轮**`main.cpp` 用**空 `sections`** 调用该 helper → 中央为空背景(占位)。视图 2D/3D 切换、帘面勾选仍走它,只是无内容。
- **下一轮**`main.cpp` 用真实 DS 数据构建 `std::vector<SectionInput>` 再调同一 helper —— 编排零改动。
- **`rebuildDetail`(数据详情:#18/#17/异常/电极)**:保留在 `main.cpp`(暂不触发),下一轮改触发条件即复活。
- **体素 / 切片 / 地形**:是 demo 专属派生层(来自两条交叉本地剖面散点 / 本地 DEM**不绑定单个 DS**
不纳入 `rebuildCentralScene`。本轮移除其 `main.cpp` 内联编排(`render/` 函数保留),其"视图详情"勾选项
本轮置灰并提示"(下一轮接入)"。它们的复活属独立未来工作(需真实体素/地形数据源),见 §12.1。
## 9. 测试策略 ## 9. 测试策略
依既有无测试桩 + 依赖 live 服务器的现实,聚焦**纯逻辑单测**GoogleTest + CTest 依既有无测试桩 + 依赖 live 服务器的现实,聚焦**纯逻辑单测**GoogleTest + CTest
- `dto/NavDto` 映射:喂样本 JSON取自 OpenAPI example / 手造)验证 - `dto/NavDto` 映射:喂样本 JSON取自 OpenAPI example / 手造)验证
@ -254,7 +290,8 @@ private:
- `src/data/dto/NavDto.{hpp,cpp}` - `src/data/dto/NavDto.{hpp,cpp}`
- `src/controller/WorkbenchNavController.{hpp,cpp}` - `src/controller/WorkbenchNavController.{hpp,cpp}`
- `src/app/panels/ObjectTreePanel.{hpp,cpp}`(若不抽,则树构建函数留在 main但 TopBar 必抽) - `src/app/panels/ObjectTreePanel.{hpp,cpp}`(若不抽,则树构建函数留在 main但 TopBar 必抽)
- 测试:`tests/`(或既有测试目录)`NavDtoTest.cpp`、`BuildProjectTreeTest.cpp` - `src/app/CentralScene.{hpp,cpp}`(中央三维编排的数据驱动 helper见 §8.1
- 测试:`tests/data/test_nav_dto.cpp`NavDto 映射 + buildStructTree
**改造** **改造**
- `src/app/TopBar.{hpp,cpp}` — 升级为数据驱动类 + 信号 - `src/app/TopBar.{hpp,cpp}` — 升级为数据驱动类 + 信号
@ -263,10 +300,46 @@ private:
**保留不删**`LocalSampleRepository`、`render/*`、`VoxelFromScatters`、现有详情/中央渲染代码。 **保留不删**`LocalSampleRepository`、`render/*`、`VoxelFromScatters`、现有详情/中央渲染代码。
## 12. 未决 / 下一轮 ## 12. 未决 / 下一轮(概览)
- dd/ert/gpr 真实剖面/反演/雷达数据渲染(替换占位)。 - dd/ert/gpr 真实剖面/反演/雷达数据渲染(替换占位)—— 详见 §12.1
- 项目 `crsCode` 替换硬编码 `EPSG:4547`,重建 `GeoLocalFrame` - 项目 `crsCode` 替换硬编码 `EPSG:4547`,重建 `GeoLocalFrame`
- 异步仓储QFuture + 取消 + 分页"加载更多")。 - 异步仓储QFuture + 取消 + 分页"加载更多")。
- 用户头像/姓名接 `auth/getUserInfo`token 过期自动跳登录。 - 用户头像/姓名接 `auth/getUserInfo`token 过期自动跳登录。
- 顶部菜单(视图/项目管理/业务工具/设备)接真实页面。 - 顶部菜单(视图/项目管理/业务工具/设备)接真实页面。
### 12.1 下一轮:对接真实 DS 数据要做什么
本轮已把导航与渲染解耦、并保留 render 层与数据驱动 helper§8.1)。下一轮把"占位"换成真实剖面/反演,
**render 层函数原样复用**,只需补"取数 → 映射 → 接线 → 坐标系"四件事:
**A. 取数:新增 DS 内容仓储(接口/数据层)**
- 在 `IProjectRepository`(或新建 `IDatasetContentRepository`)补方法,按 DS 拉真实内容。候选接口:
- 反演网格(#18 来源):`GET /business/dd/ert/inversion/rows/{dsObjectId}`、`horizontal/rows/{dsObjectId}`、
`dynamic/form/{dsObjectId}`(按实际返回选定网格来源)。
- 原始散点(#17 来源):`GET /business/dd/ert/inversion/getErtRawDataScatterGraph/{dsObjectId}`
`dd/indicator/currentmethod/scatter/graph/{dsObjectId}`
- 异常:`GET /business/exception/queryException/{dsId}`。
- 色阶:`GET /business/clr/colorGradation/queryCLRColorGradation/{projectId}` /
`lvlTemplate/queryLVLTemplate/{projectId}`
- 在 `ApiProjectRepository` 实现;返回 `RepoResult<T>`
**B. 映射DTO → 现有 core 模型(数据层 `dto/`**
- 把上述接口的 JSON 映射成**现有类型**`core::Grid`、`core::ScatterField`、`core::ColorScale`、`core::Anomaly`。
(这是新增解析,每个 dd 接口形状不同;参考 `data/parse/SampleParsers` 对本地样本的映射约定:
`v``[j=y][i=x]`、east/north 名值约定、纵向夸张统一常量等。)
- 单测:喂样本 JSON 验证映射(沿用本轮 NavDto 测试套路)。
**C. 接线:复活中央 + 详情编排UI 层,零改 render**
- 中央三维:`controller.selectDataset(dsId)` → 取反演网格 + 色阶 → 构建 `std::vector<app::SectionInput>`
**本轮已写好的** `app::rebuildCentralScene(...)`§8.1)。勾选多 TM/DS 即多 section 共存。
- 数据详情:用真实 `Grid/ScatterField/ColorScale/Anomaly` 触发**本轮保留的** `rebuildDetail`(改其触发为真实数据)。
- 把本轮的"占位文案"替换为真实渲染调用。
**D. 坐标系:用真实项目 CRS**
- 用本轮控制器已存的 `currentCrsCode()` 重建 `GeoLocalFrame``CrsTransform`,替换 `main.cpp` 硬编码 `EPSG:4547`
切项目时重建世界系,保证多视图配准。
**E.(可选)体素 / 切片 / 地形**
- 这些是不绑定单 DS 的派生层,需各自的真实数据源(多剖面体素插值 / DEM 影像服务)才能复活;
与 AD 的"按 DS 渲染剖面"是独立工作,按需另起一轮。
``` ```