refactor(vtk): 退役旧三维数据集/三维分析栏(Task 12 #6)-删 Column3D* 及全部死接线

旧 col3D_/colAnalysis_ 自分段重构后已 hide() 不入 tab、运行时不发信号,其 18 处
connect 全为死接线。本次彻底退役:
- 删 main.cpp 中 10 个 c3(Column3DDataset)+7 个 ca(Column3DAnalysis)+1 个 col3D() VE 接线
- 删 ColumnDrawer 的 col3D_/colAnalysis_ 成员/访问器/实例化/include/前向声明
- 删 4 个源文件 Column3DDataset.{hpp,cpp} / Column3DAnalysis.{hpp,cpp} + CMake 条目

唯一运行时仍在跑的 ca 消费是 refreshAnomalies(读 anomalyFilterMode 写 setAnomalies):
档位改恒「全部显示」(=隐藏 ca 的当前等效行为),异常列表已由 refreshAnalysis 经
voxelTree 全量注入新分段 tab,故去掉 ca->setAnomalies。零可见回归。

已知遗留(旧栏退役暴露,均非本次引入,待新分段段补 API):
- 新建/关闭切片不再自动勾选/取消列表(CategorySection 无 setItemChecked)
- 异常显示过滤档位、列表选中→VTK高亮(R84)随 ca 信号退役,待新段补信号

构建:configure+app 链接通过;测试 isolated 全绿(AsyncRegionBuilder 预取测试满载 flaky,
隔离复跑 3/3 过,属 GPR 域与本次无关)
This commit is contained in:
gaozheng 2026-06-25 15:52:14 +08:00
parent 7de221ddce
commit 70f77c1736
8 changed files with 19 additions and 651 deletions

View File

@ -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

View File

@ -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<geopro::core::Anomaly> 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();
});
// ── 三维分析 tab5 段信号接线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点位置/字体本期 stubTODO 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 高亮联动R84list→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 7Column3DDataset::checkedDatasetsChanged → setCheckedDatasets
// 渲染由分段勾选框驱动Task 12CategoryAnalysisTab::checkedDatasetsChanged → setCheckedDatasets
auto generation = std::make_shared<unsigned long long>(0);
QObject::connect(
objectTree, &geopro::app::ObjectTreePanel::checkedSourcesChanged, &window,

View File

@ -1,211 +0,0 @@
#include "panels/columns/Column3DAnalysis.hpp"
#include <QComboBox>
#include "EmptyAwareComboBox.hpp"
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QSet>
#include <QSignalBlocker>
#include <QSplitter>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QTreeWidgetItemIterator>
#include <QVBoxLayout>
#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<int>(&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<geopro::core::Anomaly>& 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<geopro::data::DsRow>& rows) {
// 按 dsId 保留刷新前的勾选态:列表重建(保存切片/生成体追加一行也会整树重建)不应丢已勾选项
// 的渲染态——否则保存切片会连带取消三维体勾选、把它从场景移除(实测 bug
// 切换测线(新数据)时旧 id 不匹配 → 自然全空,行为与原先一致。
QSet<QString> 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<QString> 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

View File

@ -1,55 +0,0 @@
#pragma once
#include <QWidget>
#include <QStringList>
#include <vector>
#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<geopro::data::DsRow>& rows); // Analysis 维度(三维体/切片)
// 程序化勾选某 dsId 的行(保存切片后自动勾选新行)+ 展开其父节点使可见。
void setItemChecked(const QString& dsId, bool checked);
// 3D 异常列表(#4c每条带显隐勾选选中联动 VTK。anoms 为当前应展示的异常集合。
void setAnomalies(const std::vector<geopro::core::Anomaly>& 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<geopro::core::Anomaly> anomalies_; // 当前展示集合(双击查属性按 id 回查)
};
} // namespace geopro::app

View File

@ -1,187 +0,0 @@
#include "panels/columns/Column3DDataset.hpp"
#include <algorithm>
#include <QAbstractItemView>
#include <QAction>
#include <QComboBox>
#include "EmptyAwareComboBox.hpp"
#include <QDebug>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QPoint>
#include <QPushButton>
#include <QSet>
#include <QSignalBlocker>
#include <QSlider>
#include <QTreeWidget>
#include <QTreeWidgetItemIterator>
#include <QVBoxLayout>
#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<int>(AxesMode::Standard));
mode->addItem(QStringLiteral("三维立体"), static_cast<int>(AxesMode::Stereo));
mode->addItem(QStringLiteral("不显示"), static_cast<int>(AxesMode::None));
connect(mode, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this, mode](int) { emit axesModeChanged(static_cast<AxesMode>(mode->currentData().toInt())); });
auto* oPoint = new QPushButton(QStringLiteral("设置…"));
connect(oPoint, &QPushButton::clicked, this, &Column3DDataset::oPointClicked);
auto* unit = new EmptyAwareComboBox();
unit->addItem(QStringLiteral("无刻度"), static_cast<int>(AxesUnit::None));
unit->addItem(QStringLiteral(""), static_cast<int>(AxesUnit::Meter));
unit->addItem(QStringLiteral("英尺"), static_cast<int>(AxesUnit::Feet));
unit->addItem(QStringLiteral("经纬度"), static_cast<int>(AxesUnit::LatLon));
unit->setCurrentIndex(1);
connect(unit, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this, unit](int) { emit axesUnitChanged(static_cast<AxesUnit>(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<double>(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<QString> 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<int>(ve + 0.5));
QSignalBlocker block(veSlider_); // 仅同步 UI 显示;传播由组合根分发,避免重复发信号
veSlider_->setValue(v);
veLabel_->setText(QStringLiteral("%1.0×").arg(v));
}
void Column3DDataset::setDatasets(const std::vector<geopro::data::DsRow>& 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

View File

@ -1,45 +0,0 @@
#pragma once
#include <QWidget>
#include <QStringList>
#include <vector>
#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<geopro::data::DsRow>& 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

View File

@ -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 现有接线,但隐藏、不入 tabTask 12 退役)。
col3D_ = new Column3DDataset(this);
colAnalysis_ = new Column3DAnalysis(this);
col3D_->hide();
colAnalysis_->hide();
// Tab 容器body_两 tab三维分析[分段] / 二维分析)。
auto* tabs = new QTabWidget(this);

View File

@ -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; // 【过渡,隐藏不入 tabTask 12 删】
Column2DDataset* col2D_ = nullptr;
Column3DAnalysis* colAnalysis_ = nullptr; // 【过渡,隐藏不入 tabTask 12 删】
CategoryAnalysisTab* analysisTab_ = nullptr;
QWidget* body_ = nullptr; // QTabWidget折叠时隐藏
QPushButton* toggleBtn_ = nullptr;