From 0e449e082d6cf17932cdad8c9530f469edd97f11 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 1 Jul 2026 11:38:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(vtk):=20=E5=88=97=E8=A1=A8=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E5=85=A8=E5=B1=80=E4=BA=92=E6=96=A5+=E4=BB=BB?= =?UTF-8?q?=E6=84=8F=E7=B1=BB=E5=9E=8Bds=E9=80=89=E4=B8=AD=E6=98=BE?= =?UTF-8?q?=E5=AD=90=E6=A0=91=E8=B4=B4=E5=90=88=E8=BD=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 解除 CategorySection itemSelectionChanged→datasetSelected 的 voxel-only 门控, 各类型段(反演帘面/轨迹/三维体等)选中均发选中信号→显子树贴合坐标轴 - 新增 CategorySection::clearSelection()(QSignalBlocker 下清选,不回发信号) - CategoryAnalysisTab 选中转发改为跨段互斥:某段选中非空则清其余各段选中, 全列至多一个 ds 选中;inSelectionSync_ 兜底防重入 - 复用既有 applyFittedAxes/subtreeDsIds/datasetBounds,无平行路径 - voxel 切片/异常高亮、视口点选贴合轴对称、T3 gnomon 均未回归 --- .../panels/columns/CategoryAnalysisTab.cpp | 14 ++++++++- .../panels/columns/CategoryAnalysisTab.hpp | 1 + src/app/panels/columns/CategorySection.cpp | 29 ++++++++++++------- src/app/panels/columns/CategorySection.hpp | 1 + 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/app/panels/columns/CategoryAnalysisTab.cpp b/src/app/panels/columns/CategoryAnalysisTab.cpp index f2b0122..e86fec0 100644 --- a/src/app/panels/columns/CategoryAnalysisTab.cpp +++ b/src/app/panels/columns/CategoryAnalysisTab.cpp @@ -68,7 +68,19 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d &CategoryAnalysisTab::sliceExportDatRequested); connect(sec, &CategorySection::anomalyVisibilityChanged, this, &CategoryAnalysisTab::anomalyVisibilityChanged); - connect(sec, &CategorySection::datasetSelected, this, &CategoryAnalysisTab::datasetSelected); + // 全列互斥选中:某段选中非空数据行 → 清其余各段选中,保证整列至多一个 ds 选中, + // 避免「电阻率段与三维体段各选一行」的二义(贴合轴该听谁)。clearSelection 内部 QSignalBlocker + // 阻断,被清段不回发 datasetSelected(空),无环路;inSelectionSync_ 为兜底防重入。 + connect(sec, &CategorySection::datasetSelected, this, + [this, sec](const QString& dsId, const QString& ddCode) { + if (!dsId.isEmpty() && !inSelectionSync_) { + inSelectionSync_ = true; + for (auto* other : ordered_) + if (other != sec) other->clearSelection(); + inSelectionSync_ = false; + } + emit datasetSelected(dsId, ddCode); + }); connect(sec, &CategorySection::planeZChanged, this, &CategoryAnalysisTab::planeZChanged); connect(sec, &CategorySection::basemapKindChanged, this, &CategoryAnalysisTab::basemapKindChanged); diff --git a/src/app/panels/columns/CategoryAnalysisTab.hpp b/src/app/panels/columns/CategoryAnalysisTab.hpp index 7753da9..17ba9a3 100644 --- a/src/app/panels/columns/CategoryAnalysisTab.hpp +++ b/src/app/panels/columns/CategoryAnalysisTab.hpp @@ -77,6 +77,7 @@ private: QVBoxLayout* col_ = nullptr; // 段堆叠布局(末项=尾部弹簧) QWidget* placeholder_ = nullptr; // 全空时显示的占位提示(与 scroll_ 同级,互斥显隐) std::map checkedBySeg_; // 各段当前勾选(合并成并集) + bool inSelectionSync_ = false; // 跨段互斥清选进行中标记(防重入信号环,clearSelection 已阻断信号,此为兜底) }; } // namespace geopro::app diff --git a/src/app/panels/columns/CategorySection.cpp b/src/app/panels/columns/CategorySection.cpp index acf6b07..a63cd28 100644 --- a/src/app/panels/columns/CategorySection.cpp +++ b/src/app/panels/columns/CategorySection.cpp @@ -158,19 +158,21 @@ CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc, // 属性弹窗改由右键「详情」触发(detailRequested),双击不再弹属性框(决策6:三维体只适配、静默)。 emit datasetActivated(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString()); }); + // 树选中任意数据行 → datasetSelected(各类型段通用):驱动该 ds 子树贴合坐标轴(spec §3.2), + // 并由上层 CategoryAnalysisTab 做全列互斥。voxel 段切片/异常的 VTK 高亮由上层据 ddCode 处理, + // 故此信号对所有段发射即可,任意类型选中都能显子树贴合轴(非仅三维体)。 + // 空选中(点树空白/清选)→ 发空 datasetSelected:上层据此恢复全景轴、清高亮(贴合轴取消)。 + connect(list_, &QTreeWidget::itemSelectionChanged, this, [this] { + const auto items = list_->selectedItems(); + if (items.isEmpty()) { emit datasetSelected(QString(), QString()); return; } + QTreeWidgetItem* it = items.first(); + const QString id = it->data(0, kDsIdRole).toString(); + const QString dd = it->data(0, kDsDdCodeRole).toString(); + if (!id.isEmpty() && dd != QStringLiteral("container")) emit datasetSelected(id, dd); + }); if (desc_.id == "voxel") { // 仅三维体段提供右键操作菜单(体/切片/异常) list_->setContextMenuPolicy(Qt::CustomContextMenu); connect(list_, &QTreeWidget::customContextMenuRequested, this, &CategorySection::showContextMenu); - // 树选中切片/异常 → VTK 高亮联动(正向 list→VTK)。 - connect(list_, &QTreeWidget::itemSelectionChanged, this, [this] { - const auto items = list_->selectedItems(); - // 空选中(点树空白/清选)→ 发空 datasetSelected:上层据此恢复全景轴、清高亮(贴合轴取消,spec §3.2)。 - if (items.isEmpty()) { emit datasetSelected(QString(), QString()); return; } - QTreeWidgetItem* it = items.first(); - const QString id = it->data(0, kDsIdRole).toString(); - const QString dd = it->data(0, kDsDdCodeRole).toString(); - if (!id.isEmpty() && dd != QStringLiteral("container")) emit datasetSelected(id, dd); - }); } body->addWidget(list_, 1); @@ -247,6 +249,13 @@ void CategorySection::selectItem(const QString& dsId) { list_->setCurrentItem(nullptr); // 空 dsId / 未找到 → 清选中 } +void CategorySection::clearSelection() { + if (!list_) return; + const QSignalBlocker block(list_); // 跨段互斥清选:被清段不回发 datasetSelected(空),避免环路/误恢复全景轴 + list_->setCurrentItem(nullptr); + list_->clearSelection(); +} + void CategorySection::setChecked(const QString& dsId, bool on) { for (QTreeWidgetItemIterator it(list_); *it; ++it) if ((*it)->data(0, kDsIdRole).toString() == dsId && diff --git a/src/app/panels/columns/CategorySection.hpp b/src/app/panels/columns/CategorySection.hpp index f3ecf3d..1a05562 100644 --- a/src/app/panels/columns/CategorySection.hpp +++ b/src/app/panels/columns/CategorySection.hpp @@ -41,6 +41,7 @@ public: void setBusy(const QString& dsId, bool busy); void clearAllBusy(); // 撤回本段所有 spinner(失败兜底) void selectItem(const QString& dsId); // 程序化选中某行(VTK→list 反向联动);空/未找到=清选中 + void clearSelection(); // 清本段树选中(信号阻断,不回发 datasetSelected);供跨段全列互斥用 QStringList checkedIds() const { return checkedDsIds(); } // 当前勾选 ds(异常显隐同步用) void refreshArrayFilter() { refreshArrayCombo(); } // 装置枚举异步加载后重填下拉 bool isExpanded() const; // 段头展开态(供外层按折叠状态重分配 stretch,实现"折叠向上收")