diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6a640e5..8f01088 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -79,8 +79,6 @@ add_executable(geopro_desktop WIN32 panels/chart/ScatterMarqueePicker.cpp panels/chart/ContourDrawTool.cpp panels/columns/Column2DDataset.cpp - panels/columns/Column3DDataset.cpp - panels/columns/Column3DAnalysis.cpp panels/columns/CategorySection.cpp panels/columns/CategoryAnalysisTab.cpp panels/columns/DateRangeEdit.cpp diff --git a/src/app/main.cpp b/src/app/main.cpp index 33d9bec..f10779d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -143,9 +143,7 @@ #include "AxesSettingsDialog.hpp" #include "AxesSettingsPanel.hpp" #include "repo/DatasetFieldDictionary.hpp" -#include "panels/columns/Column3DDataset.hpp" #include "panels/columns/Column2DDataset.hpp" -#include "panels/columns/Column3DAnalysis.hpp" #include "CameraPreset.hpp" #include "ColorLutBuilder.hpp" @@ -424,30 +422,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re } }; - // 异常刷新渲染 + 填充三维分析栏异常列表(#4b/4c):按显示过滤档位决定异常集合。 - // 0 全部显示=所有异常;1 随GS/2 随数据集=当前活动体的异常;3 全部隐藏=不渲染、列表空。 - // (随GS 暂同随数据集,无 GS 分组数据。loadAnomalyTree 空 key→全部,非空→该体。mock 同步回调。) - auto refreshAnomalies = [sceneView, scene3dRepo, drawer, renderWindowPtr]() { + // 异常刷新渲染(#4b/4c):恒「全部显示」——旧三维分析栏的过滤档位 UI 已退役,新分段 tab 暂无档位 + // 控件(功能缺失,待补)。异常列表由 refreshAnalysis 经 voxelTree 全量注入三维体段,此处只管渲染。 + // (loadAnomalyTree 空 key=全部。mock 同步回调。) + auto refreshAnomalies = [sceneView, scene3dRepo, renderWindowPtr]() { sceneView->clearAnomalies(); - auto* ca = drawer->colAnalysis(); - const int mode = ca->anomalyFilterMode(); - if (mode == 3) { // 全部隐藏 - ca->setAnomalies({}); - renderWindowPtr->Render(); - return; - } - std::string key; // 空 = 全部 - if (mode != 0) { // 随GS/随数据集 → 当前活动体 - key = sceneView->currentVolumeDsId(); - if (key.empty()) { // 无活动体 → 空 - ca->setAnomalies({}); - renderWindowPtr->Render(); - return; - } - } std::vector set; scene3dRepo->loadAnomalyTree( - key, + std::string{}, // 空 key = 全部 [&set](geopro::data::I3dSceneRepository::AnomalyTree tree) { for (auto& b : tree.bodies) for (auto& a : b.members) set.push_back(a); @@ -455,8 +437,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re }, [](const std::string&) {}); for (const auto& a : set) sceneView->addAnomaly(a); - ca->setAnomalies(set); // 填充列表(每条显隐勾选默认显示) - renderWindowPtr->Render(); // 必须重绘:clear+addAnomaly 改了 prop,否则 VTK 不刷新(与列表脱节) + renderWindowPtr->Render(); // 必须重绘:clear+addAnomaly 改了 prop,否则 VTK 不刷新 }; // 体素变化(重建/清场)后把体素 image 推给 InteractionManager(切片基底),并调和已保存切片 + 异常。 @@ -471,9 +452,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re refreshAnomalies(); // 同步重载异常 actor + 刷新异常列表 }; - // ── 三栏抽屉信号 → 控制器/交互(Task 7 接线)────────────────────────────── - auto* c3 = drawer->col3D(); - + // ── 抽屉信号 → 控制器/交互(Task 7/12 接线)────────────────────────────── // 三维分析栏 = 后端 Analysis 行(dd_slice) + 客户端创建的三维体(mock)。生成的三维体是"分析产物" // (设计 §2.1:三维分析栏按 对象/三维体模型/切片 三级树),归三维分析栏(非数据集栏)。 // 后端列表每次勾选测线全量覆盖,故客户端体单独维护、刷新时合并注入(不被冲掉)。 @@ -629,11 +608,10 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re if (!ok) return; scene3dRepo->createSlice( spec, name.isEmpty() ? std::string("切片") : name.toStdString(), - [interactionMgr, refreshAnalysis, drawer](std::string newId) { + [interactionMgr, refreshAnalysis](std::string newId) { interactionMgr->tagSelectedSlice(newId); // 链接当前切片 → 新数据集(不重绘) refreshAnalysis(); // 新行进列表(勾选集不变→不发多余信号) - drawer->colAnalysis()->setItemChecked(QString::fromStdString(newId), - true); // 自动展开+勾选(syncSlices 去重) + // TODO:新分段 tab 暂无 setItemChecked,新切片列表默认未勾(已渲染但列表不打勾,待补)。 }, [&window](const std::string& m) { QMessageBox::warning(&window, QStringLiteral("保存切片"), @@ -669,25 +647,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re } }; - // 关闭已保存切片(VTK 视图「关闭」) → 取消三维分析栏对应勾选(场景↔列表双向同步)。 - interactionMgr->onSliceClosed = [drawer](const std::string& dsId) { - drawer->colAnalysis()->setItemChecked(QString::fromStdString(dsId), false); - }; - - QObject::connect(c3, &geopro::app::Column3DDataset::axesModeChanged, sceneCtrl, - &geopro::controller::VtkSceneController::setAxesMode); - QObject::connect(c3, &geopro::app::Column3DDataset::axesUnitChanged, sceneCtrl, - &geopro::controller::VtkSceneController::setAxesUnit); - QObject::connect(c3, &geopro::app::Column3DDataset::verticalExaggerationChanged, sceneCtrl, - &geopro::controller::VtkSceneController::setVerticalExaggeration); - QObject::connect(c3, &geopro::app::Column3DDataset::viewRequested, sceneCtrl, - &geopro::controller::VtkSceneController::applyView); - QObject::connect(c3, &geopro::app::Column3DDataset::zoomInRequested, sceneCtrl, - &geopro::controller::VtkSceneController::zoomIn); - QObject::connect(c3, &geopro::app::Column3DDataset::zoomOutRequested, sceneCtrl, - &geopro::controller::VtkSceneController::zoomOut); - QObject::connect(c3, &geopro::app::Column3DDataset::fitRequested, sceneCtrl, - &geopro::controller::VtkSceneController::fit); + // TODO:关闭已保存切片(VTK「关闭」)时同步取消列表勾选——旧三维分析栏退役后,新分段 tab 暂无 + // setItemChecked,场景→列表的取消勾选同步暂缺(actor 已移除,仅列表打勾态不同步,待补)。 // ── VTK 画布工具条(Task 12 §9):全局视图控制 → sceneCtrl ────────────────── QObject::connect(viewToolbar, &geopro::app::VtkViewToolbar::viewRequested, sceneCtrl, @@ -712,13 +673,6 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re }); QObject::connect(axesPanel, &geopro::app::AxesSettingsPanel::closed, &window, [axesPanel]() { axesPanel->hide(); }); - // 三维数据集栏勾选(反演剖面)→ 并入渲染勾选集(剖面走帘面路径)。 - QObject::connect(c3, &geopro::app::Column3DDataset::checkedDatasetsChanged, sceneCtrl, - [checkedProfiles, pushChecked](const QStringList& ids) { - *checkedProfiles = ids; - pushChecked(); - }); - // ── 三维分析 tab(5 段)信号接线(Task 12)────────────────────────────────── auto* analysisTab = drawer->analysisTab(); // 5 段勾选并集 → 按类型分流渲染:反演剖面→帘面(checkedProfiles);三维体→体素(checkedAnalysis); @@ -817,62 +771,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re else if (ddCode == QStringLiteral("dd_anomaly")) scene3dRepo->deleteAnomaly(id, ok, err); }); - // O点位置/字体本期 stub(TODO P4:弹框)。 - QObject::connect(c3, &geopro::app::Column3DDataset::oPointClicked, vtkWidget, - []() { /* TODO P4: O点位置弹框 */ }); - QObject::connect(c3, &geopro::app::Column3DDataset::fontClicked, vtkWidget, - []() { /* TODO P4: 字体弹框 */ }); - // (旧三维数据集栏「生成三维体」接线已退役——改由 analysisTab::generateVolumeRequested 走新对话框。) - - auto* ca = drawer->colAnalysis(); - // 三维分析栏勾选(三维体/切片):体走控制器体素路径;切片(dd_slice)不进控制器(否则 loadSection - // 会对 slice id 失败),单独经 syncSlices 在父体上还原渲染。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::checkedItemsChanged, sceneCtrl, - [checkedAnalysis, pushChecked, checkedSliceIds, syncSlices, - scene3dRepo](const QStringList& ids) { - QStringList nonSlice; - checkedSliceIds->clear(); - for (const QString& id : ids) { - const std::string s = id.toStdString(); - if (scene3dRepo->isSliceDataset(s)) - checkedSliceIds->insert(s); - else - nonSlice << id; - } - *checkedAnalysis = nonSlice; - pushChecked(); // 体/其它 → 控制器(增删图元,可能触发 onVolumeChanged→syncSlices) - syncSlices(); // 切片勾选变化即时调和(父体已在场时立即显隐) - }); + // (O点位置/字体、旧栏「生成三维体」「勾选→渲染」接线均已退役——分别由 analysisTab 的 + // generateVolumeRequested / checkedDatasetsChanged 接管。) QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::sliceRequested, vtkWidget, [interactionMgr](geopro::render::interact::SliceAxis axis) { interactionMgr->addSlice(axis); }); - // 三维分析栏「数据详情」:项非体即切片(dd_slice / dd_voxel),按 ddCode 分派到只读属性 - // 对话框(仿异常详情)。数据直接从具体 scene3dRepo 取(体/切片在 3D 仓储,非 detailCtrl 的 2D 管线)。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::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 { // dd_voxel:三维体 - geopro::data::Api3dRepository::VolumeInfo info; - if (scene3dRepo->volumeInfo(dsId.toStdString(), info)) { - geopro::app::VolumePropertiesDialog dlg(name, info, &window); - dlg.exec(); - } - } - }); - // 三维分析栏切片右键「删除」→ 删除 mock 切片 + 刷新列表(若在渲染,删后行消失→取消勾选→自动移除图元)。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::sliceDeleteRequested, &window, - [scene3dRepo, refreshAnalysis](const QString& dsId) { - scene3dRepo->deleteSlice( - dsId.toStdString(), [refreshAnalysis]() { refreshAnalysis(); }, - [](const std::string&) {}); - }); // 列表切片「保存」=把当前(可能被拖动过的)位姿覆盖更新到该 dd_slice;须该切片正在渲染才有位姿可取。 QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::sliceSaveRequested, &window, [&window, interactionMgr, scene3dRepo, sceneView](const QString& dsId) { @@ -983,34 +887,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re sceneCtrl->setVolumeColorScale(dsId, dlg.colorScale()); }); - // ── 3D 异常控制(#4c):显示过滤 / 单条显隐 / 删除 → 驱动 VTK 异常渲染 ────────── - // 过滤档位变化 → 重算异常集合并重渲染 + 刷新列表(独立于体勾选)。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyDisplayFilterChanged, vtkWidget, - [refreshAnomalies](int) { refreshAnomalies(); }); + // ── 3D 异常控制(#4c):单条显隐 → 驱动 VTK 异常渲染 ────────── // 单条显隐 → 切该异常 actor 可见性。 QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::anomalyVisibilityChanged, vtkWidget, [sceneView, renderWindowPtr](const QString& id, bool vis) { sceneView->setAnomalyVisible(id.toStdString(), vis); renderWindowPtr->Render(); }); - // 列表选中异常 → VTK 高亮联动(R84,list→VTK)。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalySelected, vtkWidget, - [sceneView](const QString& id) { - sceneView->setSelectedAnomaly(id.toStdString()); - }); - // 双击异常 → 只读属性对话框(R83,名称/类型/标记/归属/坐标/备注)。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyPropertiesRequested, &window, - [&window](const geopro::core::Anomaly& a) { - geopro::app::AnomalyPropertiesDialog dlg(a, &window); - dlg.exec(); - }); - // 删除异常 → 删 mock + 刷新渲染/列表。 - QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyDeleteRequested, &window, - [scene3dRepo, refreshAnomalies](const QString& id) { - scene3dRepo->deleteAnomaly( - id.toStdString(), [refreshAnomalies]() { refreshAnomalies(); }, - [](const std::string&) {}); - }); + // 异常双击属性(R83)/右键删除已并入 analysisTab 的 detailRequested(dd_anomaly) / + // deleteDatasetRequested(dd_anomaly);列表选中→VTK高亮(R84)随旧栏退役暂缺,待新段补 anomalySelected。 // ── 二维数据集栏:天地图底图开关(③,复用轨迹图 token,经同一共享 GeoLocalFrame 配准)── auto* basemap = new geopro::app::TileBasemap(*scene, renderWindowPtr, frame, &window); @@ -1055,12 +940,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re sceneView->onCameraChanged = [basemap]() { basemap->refresh(); }; // 底图最大范围按当前勾选剖面合并范围动态定(随增删自动伸缩);刷新时实时查询。 basemap->setDataRadiusProvider([sceneView]() { return sceneView->dataHorizontalRadius(); }); - // 垂直夸张:地形须与剖面用同一 VE 才对齐(都按真实高程×VE)。 - QObject::connect(drawer->col3D(), &geopro::app::Column3DDataset::verticalExaggerationChanged, - basemap, [basemap](double ve) { basemap->setVerticalExaggeration(ve); }); - // 单一来源:kVerticalExaggeration 一处定义,组合根下发到 控制器(上方259) / 底图 / UI 显示。 + // 垂直夸张:地形须与剖面用同一 VE 才对齐(都按真实高程×VE)。单一来源 kVerticalExaggeration 下发 + // 控制器(上方)/底图;运行时 VE 改由坐标轴设置抽屉「应用」下发(旧三维数据集栏滑块已退役)。 basemap->setVerticalExaggeration(kVerticalExaggeration); - drawer->col3D()->setVerticalExaggeration(kVerticalExaggeration); // 坐标轴设置抽屉「应用」:轴显示开关 + 放大系数(=垂直夸张,下发控制器+底图);范围/单位待控制器 API。 QObject::connect(axesPanel, &geopro::app::AxesSettingsPanel::applied, &window, [axesPanel, sceneCtrl, basemap](geopro::app::AxisRange x, geopro::app::AxisRange y, @@ -1293,7 +1175,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re }); // ── 左上对象树勾选 → 拉取各 TM 的 ds 子树,按维度分发到三栏列表(spec §6.1/§8)── - // 渲染由三栏勾选框驱动(Task 7:Column3DDataset::checkedDatasetsChanged → setCheckedDatasets)。 + // 渲染由分段勾选框驱动(Task 12:CategoryAnalysisTab::checkedDatasetsChanged → setCheckedDatasets)。 auto generation = std::make_shared(0); QObject::connect( objectTree, &geopro::app::ObjectTreePanel::checkedSourcesChanged, &window, diff --git a/src/app/panels/columns/Column3DAnalysis.cpp b/src/app/panels/columns/Column3DAnalysis.cpp deleted file mode 100644 index 3433ad6..0000000 --- a/src/app/panels/columns/Column3DAnalysis.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "panels/columns/Column3DAnalysis.hpp" - -#include - -#include "EmptyAwareComboBox.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Theme.hpp" -#include "panels/DatasetListPanel.hpp" - -namespace geopro::app { - -Column3DAnalysis::Column3DAnalysis(QWidget* parent) : QWidget(parent) { - auto* root = new QVBoxLayout(this); - root->setContentsMargins(space::kMd, space::kMd, space::kMd, space::kMd); - root->setSpacing(space::kMd); - - tree_ = new QTreeWidget(); - tree_->setHeaderHidden(true); - tree_->setRootIsDecorated(true); - applyDatasetCardDelegate(tree_); - tree_->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(tree_, &QTreeWidget::customContextMenuRequested, this, &Column3DAnalysis::onContextMenu); - - connect(tree_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { - QStringList ids; - for (QTreeWidgetItemIterator it(tree_); *it; ++it) { - if ((*it)->checkState(0) == Qt::Checked) - ids << (*it)->data(0, kDsIdRole).toString(); - } - emit checkedItemsChanged(ids); - }); - - // ── 数据集树(上) + 「异常」分组(下) 放进竖向 Splitter:可拖拽、清晰分隔,数据集树占多数 ── - // ── 3D 异常控制(#4c):分组框内含 显示过滤下拉 + 异常列表(每条显隐勾选;选中联动 VTK)── - anomalyTree_ = new QTreeWidget(); - anomalyTree_->setHeaderHidden(true); - anomalyTree_->setRootIsDecorated(false); - anomalyTree_->setContextMenuPolicy(Qt::CustomContextMenu); - connect(anomalyTree_, &QTreeWidget::customContextMenuRequested, this, - &Column3DAnalysis::onAnomalyContextMenu); - connect(anomalyTree_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* it, int) { - if (it == nullptr) return; - emit anomalyVisibilityChanged(it->data(0, kDsIdRole).toString(), - it->checkState(0) == Qt::Checked); - }); - connect(anomalyTree_, &QTreeWidget::currentItemChanged, this, - [this](QTreeWidgetItem* cur, QTreeWidgetItem*) { - if (cur != nullptr) emit anomalySelected(cur->data(0, kDsIdRole).toString()); - }); - // 双击异常项 → 属性对话框(R83):按 id 回查当前集合发出整条异常。 - connect(anomalyTree_, &QTreeWidget::itemDoubleClicked, this, - [this](QTreeWidgetItem* it, int) { - if (it == nullptr) return; - const QString id = it->data(0, kDsIdRole).toString(); - for (const auto& a : anomalies_) - if (QString::fromStdString(a.id) == id) { - emit anomalyPropertiesRequested(a); - return; - } - }); - - auto* anomGroup = new QGroupBox(QStringLiteral("异常")); - auto* gv = new QVBoxLayout(anomGroup); - gv->setContentsMargins(space::kSm, space::kSm, space::kSm, space::kSm); - gv->setSpacing(space::kSm); - { - auto* fr = new QHBoxLayout(); - fr->addWidget(new QLabel(QStringLiteral("显示"))); - anomalyFilter_ = new EmptyAwareComboBox(); - anomalyFilter_->addItem(QStringLiteral("全部显示")); // 0 - anomalyFilter_->addItem(QStringLiteral("随GS")); // 1 - anomalyFilter_->addItem(QStringLiteral("随数据集")); // 2 - anomalyFilter_->addItem(QStringLiteral("全部隐藏")); // 3 - anomalyFilter_->setCurrentIndex(2); // 默认随数据集(= 跟当前三维体显隐) - connect(anomalyFilter_, qOverload(&QComboBox::currentIndexChanged), this, - [this](int idx) { emit anomalyDisplayFilterChanged(idx); }); - fr->addWidget(anomalyFilter_, 1); - gv->addLayout(fr); - } - gv->addWidget(anomalyTree_, 1); - - auto* splitter = new QSplitter(Qt::Vertical); - splitter->setChildrenCollapsible(false); - splitter->addWidget(tree_); - splitter->addWidget(anomGroup); - splitter->setStretchFactor(0, 3); // 数据集树占多 - splitter->setStretchFactor(1, 2); - root->addWidget(splitter, 1); -} - -int Column3DAnalysis::anomalyFilterMode() const { - return anomalyFilter_ ? anomalyFilter_->currentIndex() : 2; -} - -void Column3DAnalysis::setAnomalies(const std::vector& anoms) { - anomalies_ = anoms; // 留存供双击查属性(R83) - QSignalBlocker block(anomalyTree_); // 填充不触发 visibilityChanged - anomalyTree_->clear(); - for (const auto& a : anoms) { - auto* item = new QTreeWidgetItem(anomalyTree_); - const QString name = a.name.empty() ? QStringLiteral("异常") : QString::fromStdString(a.name); - const QString type = a.typeName.empty() ? QString() : QString::fromStdString(a.typeName); - item->setText(0, type.isEmpty() ? name : QStringLiteral("%1(%2)").arg(name, type)); - item->setData(0, kDsIdRole, QString::fromStdString(a.id)); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(0, Qt::Checked); // 默认显示 - } -} - -void Column3DAnalysis::onAnomalyContextMenu(const QPoint& pos) { - QTreeWidgetItem* it = anomalyTree_->itemAt(pos); - if (it == nullptr) return; - const QString id = it->data(0, kDsIdRole).toString(); - QMenu menu(this); - menu.addAction(QStringLiteral("删除异常"), this, [this, id] { emit anomalyDeleteRequested(id); }); - menu.exec(anomalyTree_->viewport()->mapToGlobal(pos)); -} - -void Column3DAnalysis::setDatasets(const std::vector& rows) { - // 按 dsId 保留刷新前的勾选态:列表重建(保存切片/生成体追加一行也会整树重建)不应丢已勾选项 - // 的渲染态——否则保存切片会连带取消三维体勾选、把它从场景移除(实测 bug)。 - // 切换测线(新数据)时旧 id 不匹配 → 自然全空,行为与原先一致。 - QSet wasChecked; - for (QTreeWidgetItemIterator it(tree_); *it; ++it) - if ((*it)->checkState(0) == Qt::Checked) - wasChecked.insert((*it)->data(0, kDsIdRole).toString()); - - { - QSignalBlocker blocker(tree_); - populateDatasetList(tree_, rows, /*append=*/false); - for (QTreeWidgetItemIterator it(tree_); *it; ++it) { - (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable); - const QString id = (*it)->data(0, kDsIdRole).toString(); - (*it)->setCheckState(0, wasChecked.contains(id) ? Qt::Checked : Qt::Unchecked); - } - } // blocker released here - // 仅当勾选集真正变化才发信号:重建但勾选集不变(如保存切片仅追加一行)→ 不发, - // 避免下游 syncSlices 用"尚未勾选新切片"的中间态误隐藏刚链接的切片(闪烁/重复)。 - QStringList ids; - QSet nowChecked; - for (QTreeWidgetItemIterator it(tree_); *it; ++it) - if ((*it)->checkState(0) == Qt::Checked) { - const QString id = (*it)->data(0, kDsIdRole).toString(); - ids << id; - nowChecked.insert(id); - } - if (nowChecked != wasChecked) emit checkedItemsChanged(ids); -} - -void Column3DAnalysis::setItemChecked(const QString& dsId, bool checked) { - for (QTreeWidgetItemIterator it(tree_); *it; ++it) { - if ((*it)->data(0, kDsIdRole).toString() != dsId) continue; - for (QTreeWidgetItem* p = (*it)->parent(); p != nullptr; p = p->parent()) - p->setExpanded(true); // 展开父链 → 新勾选行可见 - // setCheckState 仅在状态变化时发 itemChanged → checkedItemsChanged(驱动渲染同步)。 - (*it)->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); - return; - } -} - -void Column3DAnalysis::onContextMenu(const QPoint& pos) { - QTreeWidgetItem* it = tree_->itemAt(pos); - if (!it) return; - - const QString dsId = it->data(0, kDsIdRole).toString(); - const QString ddCode = it->data(0, kDsDdCodeRole).toString(); - const QString name = it->data(0, kDsNameRole).toString(); - const bool isSlice = (ddCode == QStringLiteral("dd_slice")); - - QMenu menu(this); - if (!isSlice) { - // 三维体数据集:切片▸(上下/前后/左右/任意) / 色阶 / 数据详情。 - // 显示/隐藏 = 勾选框,故菜单不再重复提供(去冗余)。 - QMenu* sub = menu.addMenu(QStringLiteral("切片")); - using SA = geopro::render::interact::SliceAxis; - sub->addAction(QStringLiteral("上下"), this, [this]{ emit sliceRequested(SA::UpDown); }); - sub->addAction(QStringLiteral("前后"), this, [this]{ emit sliceRequested(SA::FrontBack); }); - sub->addAction(QStringLiteral("左右"), this, [this]{ emit sliceRequested(SA::LeftRight); }); - sub->addAction(QStringLiteral("任意"), this, [this]{ emit sliceRequested(SA::Oblique); }); - menu.addAction(QStringLiteral("色阶"), this, [this, dsId]{ emit colorScaleRequested(dsId); }); - menu.addAction(QStringLiteral("数据详情"), this, [this, dsId, ddCode, name]{ emit detailRequested(dsId, ddCode, name); }); - } else { - // 切片数据集:保存(覆盖位姿) / 保存为(另存新切片) / 导出▸(图片·dat) / 删除 / 色阶 / 数据详情。 - // 显示/隐藏 = 勾选框,去冗余。导出与 VTK 视图切片右键统一为二级菜单。 - menu.addAction(QStringLiteral("保存"), this, [this, dsId]{ emit sliceSaveRequested(dsId); }); - menu.addAction(QStringLiteral("保存为"), this, [this, dsId]{ emit sliceSaveAsRequested(dsId); }); - QMenu* exp = menu.addMenu(QStringLiteral("导出")); - exp->addAction(QStringLiteral("图片"), this, [this, dsId]{ emit sliceExportImageRequested(dsId); }); - exp->addAction(QStringLiteral("dat"), this, [this, dsId]{ emit sliceExportDatRequested(dsId); }); - menu.addAction(QStringLiteral("删除"), this, [this, dsId]{ emit sliceDeleteRequested(dsId); }); - menu.addSeparator(); - menu.addAction(QStringLiteral("色阶"), this, [this, dsId]{ emit colorScaleRequested(dsId); }); - menu.addAction(QStringLiteral("数据详情"), this, [this, dsId, ddCode, name]{ emit detailRequested(dsId, ddCode, name); }); - } - - menu.exec(tree_->viewport()->mapToGlobal(pos)); -} - -} // namespace geopro::app diff --git a/src/app/panels/columns/Column3DAnalysis.hpp b/src/app/panels/columns/Column3DAnalysis.hpp deleted file mode 100644 index e936ae7..0000000 --- a/src/app/panels/columns/Column3DAnalysis.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#include -#include -#include -#include "repo/RepoTypes.hpp" -#include "model/Anomaly.hpp" -#include "interact/SlicePlaneMath.hpp" // SliceAxis - -class QTreeWidget; -class QComboBox; -class QPoint; - -namespace geopro::app { - -// 三维分析栏:对象→三维体模型→切片 树 + 三维体/切片两套右键菜单 + 3D 异常控制(列表/过滤/显隐)。 -class Column3DAnalysis : public QWidget { - Q_OBJECT -public: - explicit Column3DAnalysis(QWidget* parent = nullptr); - // 本期:按 ds parentId 建树(切片挂源数据下);完整 对象→三维体→切片 三级树待后端数据(P4)。 - void setDatasets(const std::vector& rows); // Analysis 维度(三维体/切片) - // 程序化勾选某 dsId 的行(保存切片后自动勾选新行)+ 展开其父节点使可见。 - void setItemChecked(const QString& dsId, bool checked); - // 3D 异常列表(#4c):每条带显隐勾选;选中联动 VTK。anoms 为当前应展示的异常集合。 - void setAnomalies(const std::vector& anoms); - // 当前显示过滤档位(0全部显示/1随GS/2随数据集/3全部隐藏)。 - int anomalyFilterMode() const; - -signals: - void sliceRequested(geopro::render::interact::SliceAxis axis); // 三维体右键 切片▸(上下/前后/左右/任意) - void colorScaleRequested(const QString& dsId); - void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); - void sliceSaveRequested(const QString& dsId); - void sliceSaveAsRequested(const QString& dsId); - void sliceExportImageRequested(const QString& dsId); // 导出▸图片 - void sliceExportDatRequested(const QString& dsId); // 导出▸dat - void sliceDeleteRequested(const QString& dsId); - void checkedItemsChanged(const QStringList& dsIds); - // ── 异常(#4c)── - void anomalyVisibilityChanged(const QString& anomalyId, bool visible); // 单条显隐勾选 - void anomalyDisplayFilterChanged(int mode); // 过滤档位 0..3 - void anomalySelected(const QString& anomalyId); // 列表选中→VTK 高亮 - void anomalyDeleteRequested(const QString& anomalyId); // 右键删除 - void anomalyPropertiesRequested(const geopro::core::Anomaly& a); // 双击→属性对话框(R83) - -private: - void onContextMenu(const QPoint& pos); - void onAnomalyContextMenu(const QPoint& pos); - QTreeWidget* tree_ = nullptr; - QTreeWidget* anomalyTree_ = nullptr; - QComboBox* anomalyFilter_ = nullptr; - std::vector anomalies_; // 当前展示集合(双击查属性按 id 回查) -}; - -} // namespace geopro::app diff --git a/src/app/panels/columns/Column3DDataset.cpp b/src/app/panels/columns/Column3DDataset.cpp deleted file mode 100644 index c17c46e..0000000 --- a/src/app/panels/columns/Column3DDataset.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "panels/columns/Column3DDataset.hpp" - -#include - -#include -#include -#include - -#include "EmptyAwareComboBox.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Theme.hpp" -#include "panels/DatasetListPanel.hpp" - -using geopro::controller::AxesMode; -using geopro::controller::AxesUnit; -using geopro::controller::ViewDir; - -namespace geopro::app { - -Column3DDataset::Column3DDataset(QWidget* parent) : QWidget(parent) { - auto* root = new QVBoxLayout(this); - root->setContentsMargins(space::kMd, space::kMd, space::kMd, space::kMd); - root->setSpacing(space::kMd); - - // 坐标轴设置 - { - auto* form = new QFormLayout(); - auto* mode = new EmptyAwareComboBox(); - mode->addItem(QStringLiteral("标准"), static_cast(AxesMode::Standard)); - mode->addItem(QStringLiteral("三维立体"), static_cast(AxesMode::Stereo)); - mode->addItem(QStringLiteral("不显示"), static_cast(AxesMode::None)); - connect(mode, qOverload(&QComboBox::currentIndexChanged), this, - [this, mode](int) { emit axesModeChanged(static_cast(mode->currentData().toInt())); }); - auto* oPoint = new QPushButton(QStringLiteral("设置…")); - connect(oPoint, &QPushButton::clicked, this, &Column3DDataset::oPointClicked); - auto* unit = new EmptyAwareComboBox(); - unit->addItem(QStringLiteral("无刻度"), static_cast(AxesUnit::None)); - unit->addItem(QStringLiteral("米"), static_cast(AxesUnit::Meter)); - unit->addItem(QStringLiteral("英尺"), static_cast(AxesUnit::Feet)); - unit->addItem(QStringLiteral("经纬度"), static_cast(AxesUnit::LatLon)); - unit->setCurrentIndex(1); - connect(unit, qOverload(&QComboBox::currentIndexChanged), this, - [this, unit](int) { emit axesUnitChanged(static_cast(unit->currentData().toInt())); }); - auto* font = new QPushButton(QStringLiteral("设置…")); - connect(font, &QPushButton::clicked, this, &Column3DDataset::fontClicked); - form->addRow(QStringLiteral("显示方式"), mode); - form->addRow(QStringLiteral("O点位置"), oPoint); - form->addRow(QStringLiteral("刻度"), unit); - form->addRow(QStringLiteral("字体"), font); - root->addWidget(new QLabel(QStringLiteral("坐标轴设置"))); - root->addLayout(form); - } - - // 水平/垂直比例(单滑块 + 数值)。初值为中性 1×(无夸张);实际配置默认由组合根 setVerticalExaggeration 下发。 - { - auto* row = new QHBoxLayout(); - veSlider_ = new QSlider(Qt::Horizontal); - veSlider_->setMinimum(1); - veSlider_->setMaximum(10); - veSlider_->setValue(1); - veLabel_ = new QLabel(QStringLiteral("1.0×")); - connect(veSlider_, &QSlider::valueChanged, this, [this](int v) { - veLabel_->setText(QStringLiteral("%1.0×").arg(v)); - emit verticalExaggerationChanged(static_cast(v)); - }); - row->addWidget(veSlider_, 1); - row->addWidget(veLabel_); - root->addWidget(new QLabel(QStringLiteral("水平/垂直比例"))); - root->addLayout(row); - } - - // 快捷视图 - { - auto* row = new QHBoxLayout(); - struct V { - const char* t; - ViewDir d; - }; - const V views[] = { - {"前", ViewDir::Front}, {"后", ViewDir::Back}, {"左", ViewDir::Left}, - {"右", ViewDir::Right}, {"上", ViewDir::Top}, {"下", ViewDir::Bottom}, - }; - for (const V& v : views) { - auto* b = new QPushButton(QString::fromUtf8(v.t)); - ViewDir d = v.d; - connect(b, &QPushButton::clicked, this, [this, d] { emit viewRequested(d); }); - row->addWidget(b); - } - root->addWidget(new QLabel(QStringLiteral("快捷视图"))); - root->addLayout(row); - } - - // 缩放 - { - auto* row = new QHBoxLayout(); - auto* in = new QPushButton(QStringLiteral("放大")); - auto* out = new QPushButton(QStringLiteral("缩小")); - auto* fit = new QPushButton(QStringLiteral("适配")); - connect(in, &QPushButton::clicked, this, &Column3DDataset::zoomInRequested); - connect(out, &QPushButton::clicked, this, &Column3DDataset::zoomOutRequested); - connect(fit, &QPushButton::clicked, this, &Column3DDataset::fitRequested); - row->addWidget(in); - row->addWidget(out); - row->addWidget(fit); - root->addWidget(new QLabel(QStringLiteral("缩放"))); - root->addLayout(row); - } - - // 数据集列表(可勾选)。勾选 = 渲染为帘面,同时是「生成三维体」的源集合(右键菜单据勾选集生成)。 - list_ = new QTreeWidget(); - list_->setHeaderHidden(true); - list_->setRootIsDecorated(true); - list_->setContextMenuPolicy(Qt::CustomContextMenu); - applyDatasetCardDelegate(list_); - connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { - QStringList ids; - for (QTreeWidgetItemIterator it(list_); *it; ++it) { - if ((*it)->checkState(0) == Qt::Checked) - ids << (*it)->data(0, kDsIdRole).toString(); - } - emit checkedDatasetsChanged(ids); - }); - connect(list_, &QTreeWidget::customContextMenuRequested, this, - &Column3DDataset::showListContextMenu); - root->addWidget(list_, 1); -} - -void Column3DDataset::showListContextMenu(const QPoint& pos) { - // 按**勾选集合**收集"可作三维体源"的数据集(反演剖面类)——与右键点在哪一项无关。 - static const QSet kSourceDdCodes = {QStringLiteral("dd_section"), - QStringLiteral("dd_inversion_data")}; - QStringList sourceIds; - for (QTreeWidgetItemIterator it(list_); *it; ++it) { - if ((*it)->checkState(0) != Qt::Checked) continue; // 仅勾选项 - const QString ddCode = (*it)->data(0, kDsDdCodeRole).toString(); - if (kSourceDdCodes.contains(ddCode)) - sourceIds << (*it)->data(0, kDsIdRole).toString(); - } - qInfo().noquote() << "[volsrc] 按勾选收集源 ds 数 =" << sourceIds.size() << ":" - << sourceIds.join(','); - - QMenu menu(this); - QAction* gen = menu.addAction(QStringLiteral("生成三维体")); - gen->setEnabled(!sourceIds.isEmpty()); // 无可作源选中 → 灰显(提示需选反演剖面) - QAction* chosen = menu.exec(list_->viewport()->mapToGlobal(pos)); - if (chosen == gen && !sourceIds.isEmpty()) - emit generateVolumeRequested(sourceIds); -} - -void Column3DDataset::setVerticalExaggeration(double ve) { - const int v = std::max(1, static_cast(ve + 0.5)); - QSignalBlocker block(veSlider_); // 仅同步 UI 显示;传播由组合根分发,避免重复发信号 - veSlider_->setValue(v); - veLabel_->setText(QStringLiteral("%1.0×").arg(v)); -} - -void Column3DDataset::setDatasets(const std::vector& rows) { - { - QSignalBlocker blocker(list_); - populateDatasetList(list_, rows, /*append=*/false); - for (QTreeWidgetItemIterator it(list_); *it; ++it) { - (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable); - (*it)->setCheckState(0, Qt::Unchecked); - } - } // blocker released here - // 填充后统一发一次(新载入必为空选):清掉上一次的渲染勾选 - QStringList ids; - for (QTreeWidgetItemIterator it(list_); *it; ++it) - if ((*it)->checkState(0) == Qt::Checked) - ids << (*it)->data(0, kDsIdRole).toString(); - emit checkedDatasetsChanged(ids); -} - -} // namespace geopro::app diff --git a/src/app/panels/columns/Column3DDataset.hpp b/src/app/panels/columns/Column3DDataset.hpp deleted file mode 100644 index 90ee445..0000000 --- a/src/app/panels/columns/Column3DDataset.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include -#include -#include -#include "I3dSceneView.hpp" // AxesMode/AxesUnit/ViewDir -#include "repo/RepoTypes.hpp" - -class QTreeWidget; -class QSlider; -class QLabel; - -namespace geopro::app { - -// 三维数据集栏:坐标轴设置 + 水平/垂直比例 + 快捷视图 + 缩放 + 3D 数据集列表。 -class Column3DDataset : public QWidget { - Q_OBJECT -public: - explicit Column3DDataset(QWidget* parent = nullptr); - void setDatasets(const std::vector& rows); - void setVerticalExaggeration(double ve); // 组合根下发配置默认值(仅同步UI显示,不重复发信号) - -signals: - void axesModeChanged(geopro::controller::AxesMode mode); - void axesUnitChanged(geopro::controller::AxesUnit unit); - void verticalExaggerationChanged(double ve); - void viewRequested(geopro::controller::ViewDir dir); - void zoomInRequested(); - void zoomOutRequested(); - void fitRequested(); - void oPointClicked(); - void fontClicked(); - void checkedDatasetsChanged(const QStringList& dsIds); - // 右键「生成三维体」:选中的源数据集 id(≥1,均为可作源类型)→ 组合根弹参数对话框 + 客户端插值。 - void generateVolumeRequested(const QStringList& sourceDsIds); - -private: - // 右键菜单:选中可作源数据集(dd_section / dd_inversion_data)时提供「生成三维体」。 - void showListContextMenu(const QPoint& pos); - - QTreeWidget* list_ = nullptr; - QSlider* veSlider_ = nullptr; // 水平/垂直比例滑块 - QLabel* veLabel_ = nullptr; -}; - -} // namespace geopro::app diff --git a/src/app/panels/columns/ColumnDrawer.cpp b/src/app/panels/columns/ColumnDrawer.cpp index 07b30df..41b5522 100644 --- a/src/app/panels/columns/ColumnDrawer.cpp +++ b/src/app/panels/columns/ColumnDrawer.cpp @@ -1,7 +1,5 @@ #include "panels/columns/ColumnDrawer.hpp" -#include "panels/columns/Column3DDataset.hpp" #include "panels/columns/Column2DDataset.hpp" -#include "panels/columns/Column3DAnalysis.hpp" #include "panels/columns/CategoryAnalysisTab.hpp" #include "Glyphs.hpp" #include "Theme.hpp" @@ -16,11 +14,6 @@ ColumnDrawer::ColumnDrawer(QWidget* parent, geopro::data::DatasetFieldDictionary { col2D_ = new Column2DDataset(this); analysisTab_ = new CategoryAnalysisTab(dict, this); - // 过渡:旧三维数据集/三维分析栏仍实例化以兼容 main 现有接线,但隐藏、不入 tab(Task 12 退役)。 - col3D_ = new Column3DDataset(this); - colAnalysis_ = new Column3DAnalysis(this); - col3D_->hide(); - colAnalysis_->hide(); // Tab 容器(body_):两 tab(三维分析[分段] / 二维分析)。 auto* tabs = new QTabWidget(this); diff --git a/src/app/panels/columns/ColumnDrawer.hpp b/src/app/panels/columns/ColumnDrawer.hpp index ce60442..f75b5ac 100644 --- a/src/app/panels/columns/ColumnDrawer.hpp +++ b/src/app/panels/columns/ColumnDrawer.hpp @@ -9,22 +9,17 @@ class DatasetFieldDictionary; namespace geopro::app { -class Column3DDataset; class Column2DDataset; -class Column3DAnalysis; class CategoryAnalysisTab; // VTK视图左侧内嵌抽屉:两 tab(三维分析[按数据类型分段]/二维分析) + 折叠开关。 -// 旧 col3D_/colAnalysis_ 仍实例化但隐藏(不入 tab),保留访问器供 main 现有接线过渡,Task 12 退役。 class ColumnDrawer : public QWidget { Q_OBJECT public: explicit ColumnDrawer(QWidget* parent = nullptr, geopro::data::DatasetFieldDictionary* dict = nullptr); - Column3DDataset* col3D() const { return col3D_; } // 【过渡,Task 12 删】 Column2DDataset* col2D() const { return col2D_; } - Column3DAnalysis* colAnalysis() const { return colAnalysis_; } // 【过渡,Task 12 删】 CategoryAnalysisTab* analysisTab() const { return analysisTab_; } public slots: @@ -32,9 +27,7 @@ public slots: void expand(); // 强制展开(进入全屏时确保三栏可见) private: - Column3DDataset* col3D_ = nullptr; // 【过渡,隐藏不入 tab,Task 12 删】 Column2DDataset* col2D_ = nullptr; - Column3DAnalysis* colAnalysis_ = nullptr; // 【过渡,隐藏不入 tab,Task 12 删】 CategoryAnalysisTab* analysisTab_ = nullptr; QWidget* body_ = nullptr; // QTabWidget,折叠时隐藏 QPushButton* toggleBtn_ = nullptr;