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

11 KiB
Raw Blame History

实现计划:客户端「生成三维体」完整流程(#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-89VtkSceneController.hpp:70
取源散点最干净路径 dsRepo_.loadAsync("inversion.scatter", dsId)core::ScatterPayloadApiDatasetRepository.cpp:152-165;散点含 projX/projY + z(hlist=高程) + vDatasetChartDto.cpp:39-46
帘面/地形竖向 = +elevationZ=+g.y 交接文档 §2CurtainActor.cpp
LocalSample loadVolume 参考实现散点→配准→GridSpec→IDW但用 -s.y 深度且固定样本 LocalSample3dRepository.cpp:68-147
散点→GridSpec→IDW 在 LocalSample/Api 重复,已有 TODO 标记漂移风险 LocalSample3dRepository.cpp:31-37
数据集列表后端全量加载、每次勾选测线变化全量覆盖 main.cpp:626-665DatasetListPanel.cpp:187-216append=false
DsRow 字段 RepoTypes.hpp:10-15id/dsName/typeName/ddCode/parentId/...
维度分流 dd_voxel→Dim3D DatasetDimension.cpp:8-15Api3dRepository::dimensionOf
Column3DDataset 列表是 checkbox 勾选(渲染),无多选/右键 Column3DDataset.cpp:114-128
col3D 信号接线 main.cpp:362-386

1. 数据模型VolumeBuildParams设计 §7.4

新增 src/data/repo/VolumeBuildParams.hpp(贴近消费它的 I3dSceneRepository/Api3dRepository

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

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——因取源走 loadSectionGrid,其节点已带 lat/lon + 高程轴 g.y无须 CRS 正变换。

内存 mock 存储(成员):

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) constvolumes_.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::addDatasetAsyncVtkSceneController.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
  • acceptvolumeName() / params()(不含 sourceDatasetIds由调用方填

7. 装配main.cpp 接线(体→三维分析栏 + 两栏勾选聚合)

  • main.cpp 改 Api3dRepository 构造,传 frameshared_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.generateVolumeRequestedVolumeParamsDialog → 填 params.sourceDatasetIdsscene3dRepo->createVolumerefreshAnalysis()(新体出现在三维分析栏)。
  • 连接 c3.checkedDatasetsChanged→更新 checkedProfilesca.checkedItemsChanged→更新 checkedAnalysispushChecked()
  • 后端加载回调col3D 直接 setDatasets(b.dim3D)analysis 经 refreshAnalysis(),保证体在测线重勾后仍驻留。

8. 阶段与验收(每阶段编译绿 = build.bat app

  • 阶段 A模型+管线+loadVolume§1 §2 §3。可单测 buildVolumeloadVolume 单源/多源逻辑成形。无 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若源真被改 ⇒ 体应失效重建,而非套旧锚点(旧锚点已是脏数据)。