geopro/docs/superpowers/plans/2026-06-17-vtk-3d-volume-cr...

139 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实现计划:客户端「生成三维体」完整流程(#1
- 日期2026-06-17
- 分支:`feat/vtk-3d-view`
- 上位设计:`docs/superpowers/specs/2026-06-17-vtk-3d-volume-slice-anomaly-design.md`§2.4 客户端创建、§6 拆解、§7 持久化策略)
- 决策:用户已拍板「完整生成三维体客户端流程」(非最小 mock
- 原则:缺后端端点 → 内存 mock保持 `I3dSceneRepository` 接口可替换;客户端能做的先做。
## 0. 现状取证file:line
| 事实 | 证据 |
|---|---|
| 运行路径用 `Api3dRepository`(非 LocalSample | `main.cpp:254` |
| `Api3dRepository::loadVolume` 是 stub | `Api3dRepository.cpp:57-60` |
| 控制器→loadVolume→addVolume 管线已接好,但 voxel 路径靠全局 `showVoxel_=false` 且无 UI 触发 | `VtkSceneController.cpp:70-89`、`VtkSceneController.hpp:70` |
| 取源散点最干净路径 | `dsRepo_.loadAsync("inversion.scatter", dsId)``core::ScatterPayload``ApiDatasetRepository.cpp:152-165`;散点含 projX/projY + z(hlist=高程) + v`DatasetChartDto.cpp:39-46` |
| 帘面/地形竖向 = +elevationZ=+g.y | 交接文档 §2`CurtainActor.cpp` |
| LocalSample loadVolume 参考实现散点→配准→GridSpec→IDW但用 `-s.y` 深度且固定样本 | `LocalSample3dRepository.cpp:68-147` |
| 散点→GridSpec→IDW 在 LocalSample/Api 重复,已有 TODO 标记漂移风险 | `LocalSample3dRepository.cpp:31-37` |
| 数据集列表后端全量加载、每次勾选测线变化全量覆盖 | `main.cpp:626-665``DatasetListPanel.cpp:187-216`append=false |
| `DsRow` 字段 | `RepoTypes.hpp:10-15`id/dsName/typeName/ddCode/parentId/... |
| 维度分流 dd_voxel→Dim3D | `DatasetDimension.cpp:8-15``Api3dRepository::dimensionOf` |
| Column3DDataset 列表是 checkbox 勾选(渲染),无多选/右键 | `Column3DDataset.cpp:114-128` |
| col3D 信号接线 | `main.cpp:362-386` |
## 1. 数据模型VolumeBuildParams设计 §7.4
新增 `src/data/repo/VolumeBuildParams.hpp`(贴近消费它的 `I3dSceneRepository`/`Api3dRepository`
```cpp
struct VolumeBuildParams {
std::vector<std::string> sourceDatasetIds; // 源数据集 id≥1
enum class Model { Idw, Kriging };
Model interpModel = Model::Idw;
double cellXY = 1.0, cellZ = 0.5, power = 2.0, maxDist = 4.0; // interpParams
std::string colorScaleId; // 取哪个源的色阶(默认首个源)
double vmin = 0.0, vmax = 0.0; // 派生(缓存)
struct Measure { long points = 0; double volume = 0.0; } measure; // 派生(缓存)
};
```
> **不冻结 gridSpec**(用户决策 2026-06-17gridSpec 每次从源散点**确定性重算**IDW 确定 + 源锁定 → 结果必然一致),不存为"冻结锚点"。
> 切片/异常坐标的稳定性由**源 ds 锁定**不变式保证(见 §9而非冻结派生网格。
> 若三维体变了 ⇒ 源 ds 被动过 ⇒ 旧 gridSpec 本身已失效,冻结只会掩盖问题。
## 2. 共享管线core::algo解决 TODO 漂移)
新增 `src/core/algo/VolumeBuilder.{hpp,cpp}`(纯 core不碰 CRS
```cpp
struct BuiltVolume { core::ScalarVolume vol; core::GridSpec spec; double vmin, vmax; };
// 入参:世界局部米点集 + cell/power/maxDist。
// 包络→GridSpec(角点对齐, clampDim)→IDW→ScalarVolume→vmin/vmax(优先外部色阶 stops否则实测)。
// 确定性:同点集同参数 → 同 GridSpec 同体(不需冻结,见计划 §1 决策)。
BuiltVolume buildVolume(const core::PointSet& pts, double cellXY, double cellZ,
double power, double maxDist);
```
-`LocalSample3dRepository.cpp:95-137` 的「包络→GridSpec→IDW→vmin/vmax」逻辑提进来。
- `LocalSample3dRepository::loadVolume` 改为调用它(行为不变,回归保护)。
- `clampDim`/kMaxDim 一并移入。
## 3. Api3dRepository体存储 + loadVolume + createVolume已实现
构造注入:`Api3dRepository(IAsyncDatasetRepository& dsRepo, std::shared_ptr<core::GeoLocalFrame> frame)`。
- **只需 frame**(与帘面/底图同一共享 shared_ptr含 reanchor**不需 projectCrs/refElev**——因取源走 `loadSection``Grid`,其节点已带 lat/lon + 高程轴 g.y无须 CRS 正变换。
内存 mock 存储(成员):
```cpp
struct StoredVolume { VolumeBuildParams params; std::string name; std::optional<VolumeGrid> cachedGrid; };
std::map<std::string, StoredVolume> volumes_; // dsId → 体
int volumeCounter_ = 0;
```
新增公有方法concretemain.cpp 持具体指针调用):
- `std::string createVolume(VolumeBuildParams params, const std::string& name)`:生成 `vol-N` dsId、存入 `volumes_`、返回 id不立即插值插值在首次 loadVolume 惰性做 + 缓存)。
- `std::vector<DsRow> volumeRows() const`:转 `DsRow{id, dsName=name, ddCode="dd_voxel", typeName="三维体"}`,供列表合并。
- `bool isVolumeDataset(const std::string& dsId) const``volumes_.count`。
`loadVolume(dsId)` 实现(异步 N 源扇出,**复用 loadSection 保证与帘面同系对齐**
1.`volumes_[dsId]`;无 → onErr。
2.`cachedGrid` → 直接 onOk明细命中
3. 否则:对每个 sourceId 调 `this->loadSection(srcId)`(即 `inversion.grid` 路径),`shared_ptr<Agg>` 聚合 N 个回调(全到齐再继续;任一 failed → 整体 onErr 一次)。
4. `appendGridPoints`:对每个源 Grid 的**有限值**节点,按 CurtainActor 同口径定位 →
`frame.toLocal(g.lat[i], g.lon[i])` 作 (x,y)**世界 Z = g.y[j](高程)**v = g.valueAt(i,j)。跳过 NaN 格(与帘面消隐一致)。
5. 取首个到达源的色阶定 vmin/vmax否则 buildVolume 数据实测)。
6. `core::buildVolume(pts, cellXY/cellZ/power/maxDist)`(每次从源确定性重算 gridSpec
7. `cachedGrid` 缓存onOk(VolumeGrid)。
> 契约onOk/onErr 主线程回调。`DetailLoad::done` 在主线程发,扇出聚合用主线程共享 `Agg`(无锁)。
> 路径选择说明:弃用 `inversion.scatter` 端点(其 y=深度/z=高程语义是交接文档警告的坑);
> 改复用已正确的 `loadSection`(`inversion.grid`),与帘面**构造性对齐**,且免 CRS 正变换。
`I3dSceneRepository` 接口加纯虚 `virtual bool isVolumeDataset(const std::string& dsId) const = 0;`LocalSample 恒 false
## 4. 控制器:按类型分流 curtain vs voxel
`VtkSceneController::addDatasetAsync``VtkSceneController.cpp:49-90`)改:
- `if (sceneRepo_.isVolumeDataset(dsId))` → 走 loadVolume/addVolume 路径;
- `else` → 走 loadSection/addCurtain 路径。
- 移除对全局 `showVoxel_`/`showCurtain_` 作为分流条件的依赖(保留成员或删,二者均不再 gating 单 ds 分流;图层开关语义后续再议)。
## 5. UIColumn3DDataset源数据栏多选 + 右键「生成三维体」
> 归属修正2026-06-17用户指出**源选择**在三维数据集栏(剖面池);**生成的体**进**三维分析栏**§7
`Column3DDataset.{hpp,cpp}`(源数据栏):
- 列表 `setSelectionMode(ExtendedSelection)`(多选高亮,独立于 checkbox 渲染勾选)。
- `setContextMenuPolicy(CustomContextMenu)` + 槽:右键弹菜单「生成三维体」,仅当选中项 ≥1 且均为可作源的 ddCodedd_section/dd_inversion_data时启用。
- 新信号 `void generateVolumeRequested(const QStringList& sourceDsIds);`(取选中项的 kDsIdRole
## 6. UI插值参数对话框
新增 `src/app/VolumeParamsDialog.{hpp,cpp}`QDialog与现有对话框同放 app 根目录):
- 名称、插值模型IDW克里金项 disabled 占位、cellXY/cellZ/power/maxDistQDoubleSpinBox默认同 §1
- `accept``volumeName()` / `params()`(不含 sourceDatasetIds由调用方填
## 7. 装配main.cpp 接线(体→三维分析栏 + 两栏勾选聚合)
- main.cpp 改 `Api3dRepository` 构造,传 `frame`shared_ptr§3 不需 projectCrs/refElev
- **体归三维分析栏**`lastAnalysisRows` 缓存后端 Analysis 行(dd_slice)`refreshAnalysis()` = `colAnalysis()->setDatasets(lastAnalysisRows + scene3dRepo->volumeRows())`。三维数据集栏(col3D)恢复直接 `setDatasets(b.dim3D)`(仅后端剖面,源池)。
- **渲染勾选聚合**`checkedProfiles`(col3D 勾选剖面) + `checkedAnalysis`(colAnalysis 勾选体/切片) → `pushChecked()` 并集下发 `setCheckedDatasets`(控制器全量 diff必须并集否则一栏清掉另一栏
- 连接 `c3.generateVolumeRequested``VolumeParamsDialog` → 填 `params.sourceDatasetIds``scene3dRepo->createVolume``refreshAnalysis()`(新体出现在三维分析栏)。
- 连接 `c3.checkedDatasetsChanged`→更新 checkedProfiles`ca.checkedItemsChanged`→更新 checkedAnalysis`pushChecked()`
- 后端加载回调col3D 直接 `setDatasets(b.dim3D)`analysis 经 `refreshAnalysis()`,保证体在测线重勾后仍驻留。
## 8. 阶段与验收(每阶段编译绿 = build.bat app
- **阶段 A模型+管线+loadVolume**§1 §2 §3。可单测 `buildVolume`loadVolume 单源/多源逻辑成形。无 UI不可见但编译绿 + 回归 LocalSample 不变。**✅ 编译绿**
- **阶段 B创建流 UI**§5 §6 §7。可见数据集栏多选剖面→右键生成→参数→新体行出现在**三维分析栏**→勾选渲染体。**✅ 编译绿**
- **阶段 C分流**§4。dd_voxel 渲染为体、dd_section 渲染为帘面,二者可共存。**✅ 编译绿**
- 验收:用户启动 app 实测Claude 无法 GUI 验证 VTK按交接铁律用 `build.bat app` 仅编译验证;渲染问题查 `%LOCALAPPDATA%\Geomative\Geopro3\logs`)。
## 9. 风险 / 待确认
- **散点端点对反演剖面是否真有 hlist(高程)**:若某些数据 z 为空,竖向退化。落地时加日志取证,必要时回退用 grid.elevationloadSection 路径)。
- **克里金**UI 占位 disabled仅 IDW 实现(设计 §1.1 列了克里金,但 core 仅 IdwInterpolator
- **mock 持久化形态**:本期纯内存(重启丢失),符合设计 §5「次要待确认」本地文件持久化留后续。
- **源 ds 锁定不变式**(替代 gridSpec 冻结,用户决策):被三维体引用的源 ds **不可修改/删除**——保证 IDW 重算确定一致、切片/异常坐标稳定。
- 本期:内存 mock仅留 TODO在源 ds 删除/编辑入口校验"是否被某三维体引用,是则禁止/告警")。
- 推论:不冻结 gridSpec若源真被改 ⇒ 体应失效重建,而非套旧锚点(旧锚点已是脏数据)。