diff --git a/docs/superpowers/plans/2026-06-09-real-api-navigation.md b/docs/superpowers/plans/2026-06-09-real-api-navigation.md index 59d6eab..2890d05 100644 --- a/docs/superpowers/plans/2026-06-09-real-api-navigation.md +++ b/docs/superpowers/plans/2026-06-09-real-api-navigation.md @@ -35,6 +35,7 @@ - `src/controller/CMakeLists.txt` — `geopro_controller` 静态库(AUTOMOC ON) - `src/controller/WorkbenchNavController.hpp` / `.cpp` — 导航状态机 - `src/app/panels/ObjectTreePanel.hpp` / `.cpp` — 被动对象树视图 +- `src/app/CentralScene.hpp` / `.cpp` — 中央三维编排的数据驱动 helper(脱离对象树,下一轮接真实 DS 复用) - `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 + +#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& sections, bool showCurtain, + const geopro::core::GeoLocalFrame& frame, double verticalExaggeration); + +} // namespace geopro::app +``` + +- [ ] **Step 2: 创建 `src/app/CentralScene.cpp`** + +```cpp +#include "CentralScene.hpp" + +#include +#include +#include + +#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& 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:** - Modify: `src/app/main.cpp` @@ -1259,11 +1381,21 @@ git commit -m "feat(app): ObjectTreePanel 被动对象树(项目→GS→TM)" 在 main.cpp 顶部 include 区(`#include "TopBar.hpp"` 附近)加: ```cpp +#include "CentralScene.hpp" #include "WorkbenchNavController.hpp" #include "api/ApiProjectRepository.hpp" #include "panels/ObjectTreePanel.hpp" ``` +并把匿名命名空间里原本的本地枚举定义: +```cpp +enum class ViewMode { Map2D, View3D }; +``` +替换为复用 helper 的同名枚举(保持后续 `ViewMode::Map2D` 等引用不变): +```cpp +using geopro::app::ViewMode; +``` + - [ ] **Step 2: 修改 `buildWorkbench` 签名,注入控制器** 把: @@ -1294,27 +1426,37 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re > 删除 `auto structure = std::make_shared<...>(repo.loadStructure());`(真实结构改由控制器提供)。 > 注意:原 `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 - // 本轮中央视图不接真实剖面数据(下一轮接 dd 接口):仅维护视图模式背景,内容占位为空。 - auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, slicePlane]() { - if (*slicePlane) { (*slicePlane)->Off(); *slicePlane = nullptr; } - scene->clear(); - const bool is2D = (*viewMode == ViewMode::Map2D); - rendererPtr->SetBackground(is2D ? 0.96 : 1.0, is2D ? 0.97 : 1.0, is2D ? 0.99 : 1.0); - if (is2D) - geopro::render::applyTop2D(rendererPtr); - else - geopro::render::applyFree3D(rendererPtr); - rendererPtr->ResetCamera(); - renderWindowPtr->Render(); + // 中央编排已解耦到 CentralScene::rebuildCentralScene(Task 9)。本轮空 sections → 空背景占位。 + // 下一轮:用真实 DS 数据构建 sections 调同一 helper 即复活(spec §8.1 / §12.1)。 + auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, showCurtain, frame]() { + geopro::app::rebuildCentralScene(*scene, rendererPtr, renderWindowPtr, *viewMode, + std::vector{}, *showCurtain, + *frame, kVerticalExaggeration); }; ``` -> 这样 `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: 删除"对象树驱动中央/数据列表"的旧连接** @@ -1483,15 +1625,20 @@ git commit -m "feat(app): 工作台接入真实导航(空间/项目/对象树/ ## 自检结论(spec 覆盖核对) -- 工作空间列表/切换 → Task 5(仓储)+ Task 6(控制器)+ Task 7(TopBar)+ Task 9(接线)✅ +- 工作空间列表/切换 → Task 5(仓储)+ Task 6(控制器)+ Task 7(TopBar)+ Task 10(接线)✅ - 项目列表/切换 → 同上 ✅ - 对象树 项目→GS→TM(叶子=TM)→ Task 4(buildStructTree)+ Task 8(ObjectTreePanel)✅ -- TM 下 DS 列表 → Task 3(parseDatasets)+ Task 5 + Task 9 ✅ -- 失败显示错误/空状态、不回退本地样本 → Task 8(showMessage)+ Task 9(loadFailed 接线)✅ -- 渲染解耦占位、移除启动 demo、保留 render/LocalSampleRepository → Task 9 ✅ +- TM 下 DS 列表 → Task 3(parseDatasets)+ Task 5 + Task 10 ✅ +- 失败显示错误/空状态、不回退本地样本 → Task 8(showMessage)+ Task 10(loadFailed 接线)✅ +- 中央三维编排解耦为数据驱动 helper(保留可复用)→ Task 9(CentralScene)✅ +- 渲染占位、移除启动 demo、保留 render/LocalSampleRepository/rebuildDetail → Task 10 ✅ - 项目 crsCode 存入控制器(下一轮替换 EPSG:4547)→ Task 6(currentCrsCode_)✅ - 分层:接口(net 复用)/数据(data: 仓储+dto)/逻辑(controller)/UI(app) → 各 Task 就位、依赖单向向下 ✅ -- 纯逻辑单测(dto + 树构建)→ Task 2/3/4 ✅;线程同步+WaitCursor → Task 9 ✅ +- 纯逻辑单测(dto + 树构建)→ Task 2/3/4 ✅;线程同步+WaitCursor → Task 10 ✅ -## 下一轮(不在本计划) -dd/ert/gpr 真实渲染替换占位;crsCode 重建 GeoLocalFrame;异步仓储 + 分页“加载更多”;用户信息 `auth/getUserInfo`;token 过期自动跳登录;顶部菜单接真实页面。 +## 下一轮(不在本计划,详见 spec §12.1) +接真实 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 过期跳登录、 +体素/切片/地形真实数据源、顶部菜单接页面。 diff --git a/docs/superpowers/specs/2026-06-09-real-api-navigation-design.md b/docs/superpowers/specs/2026-06-09-real-api-navigation-design.md index 790baf3..a676e02 100644 --- a/docs/superpowers/specs/2026-06-09-real-api-navigation-design.md +++ b/docs/superpowers/specs/2026-06-09-real-api-navigation-design.md @@ -228,12 +228,48 @@ private: - 输入边界:`tmObjectId` / `projectId` 为空时短路不发请求。 ## 8. 渲染解耦 -现状:对象树(本地 grid1/grid2…)直接驱动中央与数据详情。本轮真实树 id 与本地样本对不上,故: -- 启动不再自动渲染本地 demo。 -- 真实 DS 点击 → 中央/详情显示占位文案。 + +现状:对象树(本地 grid1/grid2…)直接驱动中央与数据详情;`rebuildCentral`/`rebuildDetail` 都是 +`main.cpp` 内捕获了本地 `tree`/`structure` 的 lambda。本轮真实树 id 与本地样本对不上,故解耦: + +- 启动不再自动渲染本地 demo;真实 DS 点击 → 中央/详情显示占位文案。 - `render/*`、`LocalSampleRepository`、`VoxelFromScatters` 等全部保留,待下轮按 dd/ert 接口复用。 - 项目 `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 }; + +// 一个待渲染剖面:grid(2D 测线 / 3D 帘面都用)+ colorScale(3D 帘面上色用)。 +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& sections, bool showCurtain, + const geopro::core::GeoLocalFrame& frame, double verticalExaggeration); +} +``` + +- **本轮**:`main.cpp` 用**空 `sections`** 调用该 helper → 中央为空背景(占位)。视图 2D/3D 切换、帘面勾选仍走它,只是无内容。 +- **下一轮**:`main.cpp` 用真实 DS 数据构建 `std::vector` 再调同一 helper —— 编排零改动。 +- **`rebuildDetail`(数据详情:#18/#17/异常/电极)**:保留在 `main.cpp`(暂不触发),下一轮改触发条件即复活。 +- **体素 / 切片 / 地形**:是 demo 专属派生层(来自两条交叉本地剖面散点 / 本地 DEM),**不绑定单个 DS**, + 不纳入 `rebuildCentralScene`。本轮移除其 `main.cpp` 内联编排(`render/` 函数保留),其"视图详情"勾选项 + 本轮置灰并提示"(下一轮接入)"。它们的复活属独立未来工作(需真实体素/地形数据源),见 §12.1。 + ## 9. 测试策略 依既有无测试桩 + 依赖 live 服务器的现实,聚焦**纯逻辑单测**(GoogleTest + CTest): - `dto/NavDto` 映射:喂样本 JSON(取自 OpenAPI example / 手造)验证 @@ -254,7 +290,8 @@ private: - `src/data/dto/NavDto.{hpp,cpp}` - `src/controller/WorkbenchNavController.{hpp,cpp}` - `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}` — 升级为数据驱动类 + 信号 @@ -263,10 +300,46 @@ private: **保留不删**:`LocalSampleRepository`、`render/*`、`VoxelFromScatters`、现有详情/中央渲染代码。 -## 12. 未决 / 下一轮 -- dd/ert/gpr 真实剖面/反演/雷达数据渲染(替换占位)。 +## 12. 未决 / 下一轮(概览) +- dd/ert/gpr 真实剖面/反演/雷达数据渲染(替换占位)—— 详见 §12.1。 - 项目 `crsCode` 替换硬编码 `EPSG:4547`,重建 `GeoLocalFrame`。 - 异步仓储(QFuture + 取消 + 分页"加载更多")。 - 用户头像/姓名接 `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`。 + +**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::rebuildCentralScene(...)`(§8.1)。勾选多 TM/DS 即多 section 共存。 +- 数据详情:用真实 `Grid/ScatterField/ColorScale/Anomaly` 触发**本轮保留的** `rebuildDetail`(改其触发为真实数据)。 +- 把本轮的"占位文案"替换为真实渲染调用。 + +**D. 坐标系:用真实项目 CRS** +- 用本轮控制器已存的 `currentCrsCode()` 重建 `GeoLocalFrame` 与 `CrsTransform`,替换 `main.cpp` 硬编码 `EPSG:4547`; + 切项目时重建世界系,保证多视图配准。 + +**E.(可选)体素 / 切片 / 地形** +- 这些是不绑定单 DS 的派生层,需各自的真实数据源(多剖面体素插值 / DEM 影像服务)才能复活; + 与 A–D 的"按 DS 渲染剖面"是独立工作,按需另起一轮。 ```