diff --git a/src/app/main.cpp b/src/app/main.cpp index c94cd8f..5df0741 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -90,6 +90,7 @@ #include "ApiClient.hpp" #include "AuthService.hpp" #include "DatasetDimension.hpp" +#include "DatasetCategory.hpp" #include "Credential.hpp" #include "Glyphs.hpp" #include "Logging.hpp" @@ -135,6 +136,7 @@ #include "panels/ObjectExceptionPanel.hpp" #include "TileBasemap.hpp" #include "panels/columns/ColumnDrawer.hpp" +#include "panels/columns/CategoryAnalysisTab.hpp" #include "panels/columns/Column3DDataset.hpp" #include "panels/columns/Column2DDataset.hpp" #include "panels/columns/Column3DAnalysis.hpp" @@ -444,12 +446,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // 三维分析栏 = 后端 Analysis 行(dd_slice) + 客户端创建的三维体(mock)。生成的三维体是"分析产物" // (设计 §2.1:三维分析栏按 对象/三维体模型/切片 三级树),归三维分析栏(非数据集栏)。 // 后端列表每次勾选测线全量覆盖,故客户端体单独维护、刷新时合并注入(不被冲掉)。 - auto lastAnalysisRows = std::make_shared>(); - auto refreshAnalysis = [drawer, scene3dRepo, lastAnalysisRows]() { - std::vector rows = *lastAnalysisRows; + // 三维分析数据源 = 最近对象树勾选拉取的 ds + 客户端三维体(mock) + 已保存切片; + // splitByCategory 后注入 5 段(电阻率/视电阻率/瞬变/三维体/切片);二维(足迹)经 dim2D 仍走 col2D。 + auto lastSourceRows = std::make_shared>(); + auto refreshAnalysis = [drawer, scene3dRepo, lastSourceRows]() { + std::vector rows = *lastSourceRows; for (auto& vr : scene3dRepo->volumeRows()) rows.push_back(std::move(vr)); // 客户端三维体 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; pushChecked(); }); + + // ── 三维分析 tab(5 段)信号接线(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(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点位置/字体本期 stub(TODO P4:弹框)。 QObject::connect(c3, &geopro::app::Column3DDataset::oPointClicked, vtkWidget, []() { /* TODO P4: O点位置弹框 */ }); @@ -1115,27 +1183,22 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re auto generation = std::make_shared(0); QObject::connect( objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, &window, - [&projectRepo, &nav, drawer, emptyState, generation, lastAnalysisRows, + [&projectRepo, &nav, drawer, emptyState, generation, lastSourceRows, refreshAnalysis](const QStringList& tmIds) { const unsigned long long myGen = ++(*generation); emptyState->setVisible(tmIds.isEmpty()); // 有勾选→隐藏引导层,露出中央渲染 if (tmIds.isEmpty()) { - drawer->col3D()->setDatasets({}); - drawer->col2D()->setDatasets({}); - *lastAnalysisRows = {}; - refreshAnalysis(); // 后端分析行清空,但客户端三维体仍驻留三维分析栏 + *lastSourceRows = {}; + refreshAnalysis(); // 清空 5 段(客户端三维体仍驻留) + col2D return; } - // 多 TM 异步汇总:每个 TM 取整棵 ds 子树,全部回来后按维度分发到三栏。 + // 多 TM 异步汇总:每个 TM 取整棵 ds 子树,全部回来后 splitByCategory 分发到 5 段。 auto acc = std::make_shared>(); auto remaining = std::make_shared(tmIds.size()); - auto finish = [acc, drawer, generation, myGen, lastAnalysisRows, refreshAnalysis]() { + auto finish = [acc, generation, myGen, lastSourceRows, refreshAnalysis]() { if (*generation != myGen) return; // 已被更新的勾选批次取代→丢弃陈旧结果 - geopro::app::DimBuckets b = geopro::app::splitByDimension(*acc); - drawer->col3D()->setDatasets(b.dim3D); - drawer->col2D()->setDatasets(b.dim2D); - *lastAnalysisRows = b.analysis; - refreshAnalysis(); // 后端切片 + 客户端三维体合并注入三维分析栏 + *lastSourceRows = *acc; // 全部对象树 ds 作分析数据源 + refreshAnalysis(); // splitByCategory→5段 + 合并三维体/切片 + dim2D→col2D }; for (const QString& tm : tmIds) { geopro::data::NavRequest* req = projectRepo.loadRowsAsync( @@ -1208,14 +1271,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // 切换到「不同项目」时先清空中央区,避免新项目残留旧项目的三栏数据与 VTK 渲染。 // 仅真正换项目用(delete-refresh 等 switchProject(currentProjectId) 不走此处,避免误清)。 - auto clearCentral = [drawer, sceneCtrl, emptyState, checkedProfiles, checkedAnalysis, - pushChecked, lastAnalysisRows, refreshAnalysis, checkedSliceIds, + auto clearCentral = [sceneCtrl, emptyState, checkedProfiles, checkedAnalysis, + pushChecked, lastSourceRows, refreshAnalysis, checkedSliceIds, syncSlices, basemap, sceneView]() { - // 三栏清空(col2D/col3D setDatasets({}) 会顺带发空勾选 → setChecked2DDatasets({})/帘面清空)。 - drawer->col3D()->setDatasets({}); - drawer->col2D()->setDatasets({}); - *lastAnalysisRows = {}; - refreshAnalysis(); // 后端分析行清空(客户端三维体仍按设计驻留三维分析栏) + // 数据源清空 → 5 段 + col2D 清空(refreshAnalysis 内 setBuckets/dim2D;客户端三维体仍驻留)。 + *lastSourceRows = {}; + refreshAnalysis(); // 勾选集清空并下发空到 VTK(帘面/体素/切片/2D 足迹全部撤场)。 checkedProfiles->clear(); checkedAnalysis->clear();