feat(app): 接通对象树→splitByCategory→三维分析5段数据流+勾选分流渲染+生成入口(Task12 阶段A)

This commit is contained in:
gaozheng 2026-06-24 19:49:18 +08:00
parent 7815bf7d4c
commit 901c84e0ae
1 changed files with 84 additions and 23 deletions

View File

@ -90,6 +90,7 @@
#include "ApiClient.hpp" #include "ApiClient.hpp"
#include "AuthService.hpp" #include "AuthService.hpp"
#include "DatasetDimension.hpp" #include "DatasetDimension.hpp"
#include "DatasetCategory.hpp"
#include "Credential.hpp" #include "Credential.hpp"
#include "Glyphs.hpp" #include "Glyphs.hpp"
#include "Logging.hpp" #include "Logging.hpp"
@ -135,6 +136,7 @@
#include "panels/ObjectExceptionPanel.hpp" #include "panels/ObjectExceptionPanel.hpp"
#include "TileBasemap.hpp" #include "TileBasemap.hpp"
#include "panels/columns/ColumnDrawer.hpp" #include "panels/columns/ColumnDrawer.hpp"
#include "panels/columns/CategoryAnalysisTab.hpp"
#include "panels/columns/Column3DDataset.hpp" #include "panels/columns/Column3DDataset.hpp"
#include "panels/columns/Column2DDataset.hpp" #include "panels/columns/Column2DDataset.hpp"
#include "panels/columns/Column3DAnalysis.hpp" #include "panels/columns/Column3DAnalysis.hpp"
@ -444,12 +446,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 三维分析栏 = 后端 Analysis 行(dd_slice) + 客户端创建的三维体mock。生成的三维体是"分析产物" // 三维分析栏 = 后端 Analysis 行(dd_slice) + 客户端创建的三维体mock。生成的三维体是"分析产物"
// (设计 §2.1:三维分析栏按 对象/三维体模型/切片 三级树),归三维分析栏(非数据集栏)。 // (设计 §2.1:三维分析栏按 对象/三维体模型/切片 三级树),归三维分析栏(非数据集栏)。
// 后端列表每次勾选测线全量覆盖,故客户端体单独维护、刷新时合并注入(不被冲掉)。 // 后端列表每次勾选测线全量覆盖,故客户端体单独维护、刷新时合并注入(不被冲掉)。
auto lastAnalysisRows = std::make_shared<std::vector<geopro::data::DsRow>>(); // 三维分析数据源 = 最近对象树勾选拉取的 ds + 客户端三维体(mock) + 已保存切片;
auto refreshAnalysis = [drawer, scene3dRepo, lastAnalysisRows]() { // splitByCategory 后注入 5 段(电阻率/视电阻率/瞬变/三维体/切片);二维(足迹)经 dim2D 仍走 col2D。
std::vector<geopro::data::DsRow> rows = *lastAnalysisRows; auto lastSourceRows = std::make_shared<std::vector<geopro::data::DsRow>>();
auto refreshAnalysis = [drawer, scene3dRepo, lastSourceRows]() {
std::vector<geopro::data::DsRow> rows = *lastSourceRows;
for (auto& vr : scene3dRepo->volumeRows()) rows.push_back(std::move(vr)); // 客户端三维体 for (auto& vr : scene3dRepo->volumeRows()) rows.push_back(std::move(vr)); // 客户端三维体
for (auto& sr : scene3dRepo->sliceRows()) rows.push_back(std::move(sr)); // 已保存切片(挂父体下) for (auto& sr : scene3dRepo->sliceRows()) rows.push_back(std::move(sr)); // 已保存切片(挂父体下)
drawer->colAnalysis()->setDatasets(rows); drawer->analysisTab()->setBuckets(geopro::app::splitByCategory(rows));
drawer->col2D()->setDatasets(geopro::app::splitByDimension(rows).dim2D);
}; };
// 渲染勾选聚合:三维数据集栏(剖面→帘面)+ 三维分析栏(三维体/切片→体素/切片)两套勾选并集 // 渲染勾选聚合:三维数据集栏(剖面→帘面)+ 三维分析栏(三维体/切片→体素/切片)两套勾选并集
@ -643,6 +648,69 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
*checkedProfiles = ids; *checkedProfiles = ids;
pushChecked(); pushChecked();
}); });
// ── 三维分析 tab5 段信号接线Task 12──────────────────────────────────
auto* analysisTab = drawer->analysisTab();
// 5 段勾选并集 → 按类型分流渲染:反演剖面→帘面(checkedProfiles);三维体→体素(checkedAnalysis)
// 切片(dd_slice)→不进控制器,经 syncSlices 在父体上还原。
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::checkedDatasetsChanged, sceneCtrl,
[checkedProfiles, checkedAnalysis, checkedSliceIds, syncSlices, pushChecked,
scene3dRepo](const QStringList& ids) {
QStringList profiles, analysis;
checkedSliceIds->clear();
for (const QString& id : ids) {
const std::string s = id.toStdString();
if (scene3dRepo->isSliceDataset(s))
checkedSliceIds->insert(s);
else if (scene3dRepo->isVolumeDataset(s))
analysis << id;
else
profiles << id; // 反演剖面 → 帘面
}
*checkedProfiles = profiles;
*checkedAnalysis = analysis;
pushChecked();
syncSlices();
});
// 段头「+新增三维体」:弹参数对话框 → 组装真实 VoxelGenerateRequest → createVolume(mock) → 刷新。
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::generateVolumeRequested, &window,
[&window, &nav, scene3dRepo, refreshAnalysis](const QString& /*dsTypeCode*/,
const QStringList& sourceIds) {
if (sourceIds.isEmpty()) return;
geopro::app::VolumeParamsDialog dlg(static_cast<int>(sourceIds.size()), &window);
if (dlg.exec() != QDialog::Accepted) return;
const geopro::data::VolumeBuildParams p = dlg.params();
geopro::data::VoxelGenerateRequest req;
req.projectId = nav.currentProjectId().toStdString();
req.name = dlg.volumeName().toStdString();
for (const QString& id : sourceIds) req.sourceDatasetIds.push_back(id.toStdString());
req.interpModel =
(p.interpModel == geopro::data::VolumeBuildParams::Model::Kriging) ? "Kriging" : "Idw";
req.cellXY = p.cellXY;
req.cellZ = p.cellZ;
req.power = p.power;
req.maxDist = p.maxDist;
req.colorScaleId = p.colorScaleId;
scene3dRepo->createVolume(req);
refreshAnalysis();
});
// 双击数据详情dd_slice→切片属性dd_voxel→三维体属性同 colAnalysis 详情口径)。
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::detailRequested, &window,
[&window, scene3dRepo](const QString& dsId, const QString& ddCode, const QString& name) {
if (ddCode == QStringLiteral("dd_slice")) {
geopro::data::I3dSceneRepository::SliceSpec sp;
if (scene3dRepo->sliceSpec(dsId.toStdString(), sp)) {
geopro::app::SlicePropertiesDialog dlg(name, sp, &window);
dlg.exec();
}
} else if (ddCode == QStringLiteral("dd_voxel")) {
geopro::data::Api3dRepository::VolumeInfo info;
if (scene3dRepo->volumeInfo(dsId.toStdString(), info)) {
geopro::app::VolumePropertiesDialog dlg(name, info, &window);
dlg.exec();
}
}
});
// O点位置/字体本期 stubTODO P4弹框 // O点位置/字体本期 stubTODO P4弹框
QObject::connect(c3, &geopro::app::Column3DDataset::oPointClicked, vtkWidget, QObject::connect(c3, &geopro::app::Column3DDataset::oPointClicked, vtkWidget,
[]() { /* TODO P4: O点位置弹框 */ }); []() { /* TODO P4: O点位置弹框 */ });
@ -1115,27 +1183,22 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
auto generation = std::make_shared<unsigned long long>(0); auto generation = std::make_shared<unsigned long long>(0);
QObject::connect( QObject::connect(
objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, &window, objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, &window,
[&projectRepo, &nav, drawer, emptyState, generation, lastAnalysisRows, [&projectRepo, &nav, drawer, emptyState, generation, lastSourceRows,
refreshAnalysis](const QStringList& tmIds) { refreshAnalysis](const QStringList& tmIds) {
const unsigned long long myGen = ++(*generation); const unsigned long long myGen = ++(*generation);
emptyState->setVisible(tmIds.isEmpty()); // 有勾选→隐藏引导层,露出中央渲染 emptyState->setVisible(tmIds.isEmpty()); // 有勾选→隐藏引导层,露出中央渲染
if (tmIds.isEmpty()) { if (tmIds.isEmpty()) {
drawer->col3D()->setDatasets({}); *lastSourceRows = {};
drawer->col2D()->setDatasets({}); refreshAnalysis(); // 清空 5 段(客户端三维体仍驻留) + col2D
*lastAnalysisRows = {};
refreshAnalysis(); // 后端分析行清空,但客户端三维体仍驻留三维分析栏
return; return;
} }
// 多 TM 异步汇总:每个 TM 取整棵 ds 子树,全部回来后按维度分发到三栏 // 多 TM 异步汇总:每个 TM 取整棵 ds 子树,全部回来后 splitByCategory 分发到 5 段
auto acc = std::make_shared<std::vector<geopro::data::DsRow>>(); auto acc = std::make_shared<std::vector<geopro::data::DsRow>>();
auto remaining = std::make_shared<int>(tmIds.size()); auto remaining = std::make_shared<int>(tmIds.size());
auto finish = [acc, drawer, generation, myGen, lastAnalysisRows, refreshAnalysis]() { auto finish = [acc, generation, myGen, lastSourceRows, refreshAnalysis]() {
if (*generation != myGen) return; // 已被更新的勾选批次取代→丢弃陈旧结果 if (*generation != myGen) return; // 已被更新的勾选批次取代→丢弃陈旧结果
geopro::app::DimBuckets b = geopro::app::splitByDimension(*acc); *lastSourceRows = *acc; // 全部对象树 ds 作分析数据源
drawer->col3D()->setDatasets(b.dim3D); refreshAnalysis(); // splitByCategory→5段 + 合并三维体/切片 + dim2D→col2D
drawer->col2D()->setDatasets(b.dim2D);
*lastAnalysisRows = b.analysis;
refreshAnalysis(); // 后端切片 + 客户端三维体合并注入三维分析栏
}; };
for (const QString& tm : tmIds) { for (const QString& tm : tmIds) {
geopro::data::NavRequest* req = projectRepo.loadRowsAsync( geopro::data::NavRequest* req = projectRepo.loadRowsAsync(
@ -1208,14 +1271,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 切换到「不同项目」时先清空中央区,避免新项目残留旧项目的三栏数据与 VTK 渲染。 // 切换到「不同项目」时先清空中央区,避免新项目残留旧项目的三栏数据与 VTK 渲染。
// 仅真正换项目用delete-refresh 等 switchProject(currentProjectId) 不走此处,避免误清)。 // 仅真正换项目用delete-refresh 等 switchProject(currentProjectId) 不走此处,避免误清)。
auto clearCentral = [drawer, sceneCtrl, emptyState, checkedProfiles, checkedAnalysis, auto clearCentral = [sceneCtrl, emptyState, checkedProfiles, checkedAnalysis,
pushChecked, lastAnalysisRows, refreshAnalysis, checkedSliceIds, pushChecked, lastSourceRows, refreshAnalysis, checkedSliceIds,
syncSlices, basemap, sceneView]() { syncSlices, basemap, sceneView]() {
// 三栏清空col2D/col3D setDatasets({}) 会顺带发空勾选 → setChecked2DDatasets({})/帘面清空)。 // 数据源清空 → 5 段 + col2D 清空refreshAnalysis 内 setBuckets/dim2D客户端三维体仍驻留
drawer->col3D()->setDatasets({}); *lastSourceRows = {};
drawer->col2D()->setDatasets({}); refreshAnalysis();
*lastAnalysisRows = {};
refreshAnalysis(); // 后端分析行清空(客户端三维体仍按设计驻留三维分析栏)
// 勾选集清空并下发空到 VTK帘面/体素/切片/2D 足迹全部撤场)。 // 勾选集清空并下发空到 VTK帘面/体素/切片/2D 足迹全部撤场)。
checkedProfiles->clear(); checkedProfiles->clear();
checkedAnalysis->clear(); checkedAnalysis->clear();