feat(vtk): 三维分析区 3D 异常控制(#4c-1)-列表+显示过滤+单条显隐+删除
三维分析栏加 3D 异常控制(异常挂三维体,mock): - 布局:数据集树 + 「异常」分组框(显示过滤下拉 + 异常列表)放进可拖拽竖向 Splitter,数据集树占多 - 显示过滤(R86-87):全部显示/随GS/随数据集/全部隐藏 —— 独立于体勾选控制 VTK 异常可见性 (随GS 暂同随数据集;loadAnomalyTree 空key=全部、非空=该体) - 单条显隐勾选 → setAnomalyVisible;右键「删除异常」→ deleteAnomaly + 刷新 - refreshAnomalies:按档位算异常集合 → clear+addAnomaly 重渲染 + 填列表 + Render (修过滤切换后 VTK 不重绘、与列表脱节的 bug) - 创建异常后经 refreshAnomalies 入列表 编译链接绿(build.bat app exit 0);用户实测通过。 待做:4c-2 列表选中→VTK 高亮联动(R84,需视图 setAnomalySelected);4c-3 异常属性面板(R83);单条显隐态持久化。
This commit is contained in:
parent
6210d615f3
commit
324d4ac605
|
|
@ -386,23 +386,43 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
}
|
||||
};
|
||||
|
||||
// 当前活动体的异常重载渲染(#4b):体到场→载其异常 actor;体移除→清空。持久化跨重勾可见。
|
||||
auto reloadAnomalies = [sceneView, scene3dRepo]() {
|
||||
// 异常刷新渲染 + 填充三维分析栏异常列表(#4b/4c):按显示过滤档位决定异常集合。
|
||||
// 0 全部显示=所有异常;1 随GS/2 随数据集=当前活动体的异常;3 全部隐藏=不渲染、列表空。
|
||||
// (随GS 暂同随数据集,无 GS 分组数据。loadAnomalyTree 空 key→全部,非空→该体。mock 同步回调。)
|
||||
auto refreshAnomalies = [sceneView, scene3dRepo, drawer, renderWindowPtr]() {
|
||||
sceneView->clearAnomalies();
|
||||
const std::string vol = sceneView->currentVolumeDsId();
|
||||
if (vol.empty()) return;
|
||||
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(
|
||||
vol,
|
||||
[sceneView](geopro::data::I3dSceneRepository::AnomalyTree tree) {
|
||||
key,
|
||||
[&set](geopro::data::I3dSceneRepository::AnomalyTree tree) {
|
||||
for (auto& b : tree.bodies)
|
||||
for (auto& a : b.members) sceneView->addAnomaly(a);
|
||||
for (auto& a : tree.loose) sceneView->addAnomaly(a);
|
||||
for (auto& a : b.members) set.push_back(a);
|
||||
for (auto& a : tree.loose) set.push_back(a);
|
||||
},
|
||||
[](const std::string&) {});
|
||||
for (const auto& a : set) sceneView->addAnomaly(a);
|
||||
ca->setAnomalies(set); // 填充列表(每条显隐勾选默认显示)
|
||||
renderWindowPtr->Render(); // 必须重绘:clear+addAnomaly 改了 prop,否则 VTK 不刷新(与列表脱节)
|
||||
};
|
||||
|
||||
// 体素变化(重建/清场)后把体素 image 推给 InteractionManager(切片基底),并调和已保存切片 + 异常。
|
||||
sceneView->onVolumeChanged = [interactionMgr, sceneView, syncSlices, reloadAnomalies]() {
|
||||
sceneView->onVolumeChanged = [interactionMgr, sceneView, syncSlices, refreshAnomalies]() {
|
||||
if (sceneView->hasVolume())
|
||||
interactionMgr->setVolumeImage(sceneView->currentVolumeImage(),
|
||||
sceneView->currentColorScale(), sceneView->currentVmin(),
|
||||
|
|
@ -410,7 +430,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
else
|
||||
interactionMgr->setVolumeImage(nullptr, sceneView->currentColorScale(), 0.0, 0.0);
|
||||
syncSlices(); // 体到场/移除后重建当前体下已勾选的切片
|
||||
reloadAnomalies(); // 同步重载当前体的异常 actor
|
||||
refreshAnomalies(); // 同步重载异常 actor + 刷新异常列表
|
||||
};
|
||||
|
||||
// ── 三栏抽屉信号 → 控制器/交互(Task 7 接线)──────────────────────────────
|
||||
|
|
@ -442,8 +462,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// 保存=按"未保存/已保存"分派(新建+链接+自动勾选 / 覆盖位姿);导出统一为「导出▸图片·dat」;
|
||||
// 正视/翻转/关闭=接现有交互(关闭已保存切片→onSliceClosed 取消列表勾选);创建异常=占位(#4)。
|
||||
interactionMgr->onSliceContextMenuRequested =
|
||||
[&window, interactionMgr, sceneView, scene3dRepo, refreshAnalysis, drawer, anomalyDrawTool,
|
||||
renderWindowPtr]() {
|
||||
[&window, interactionMgr, sceneView, scene3dRepo, refreshAnalysis, refreshAnomalies, drawer,
|
||||
anomalyDrawTool, renderWindowPtr]() {
|
||||
QMenu menu(&window);
|
||||
QAction* aAnomaly = menu.addAction(QStringLiteral("创建异常"));
|
||||
QAction* aSave = menu.addAction(QStringLiteral("保存"));
|
||||
|
|
@ -472,7 +492,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
const std::string volId = sceneView->currentVolumeDsId();
|
||||
anomalyDrawTool->start(
|
||||
o, normal,
|
||||
[&window, sceneView, scene3dRepo, renderWindowPtr, refreshAnalysis, volId,
|
||||
[&window, sceneView, scene3dRepo, renderWindowPtr, refreshAnomalies, volId,
|
||||
normal, o](const std::vector<ri::Vec3>& worldPts) {
|
||||
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
||||
geopro::core::Anomaly a;
|
||||
|
|
@ -504,16 +524,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
a.typeName = dlg.typeName().toStdString();
|
||||
a.exceptionTypeId = dlg.typeId().toStdString();
|
||||
a.remark = dlg.remark().toStdString();
|
||||
geopro::core::Anomaly finalA = a;
|
||||
scene3dRepo->saveAnomaly(
|
||||
finalA, shot.toStdString(),
|
||||
[sceneView, renderWindowPtr, refreshAnalysis, draftId,
|
||||
finalA](std::string id) mutable {
|
||||
sceneView->removeAnomaly(draftId); // 撤草稿,换真实 id 重渲染
|
||||
finalA.id = id;
|
||||
sceneView->addAnomaly(finalA);
|
||||
a, shot.toStdString(),
|
||||
[sceneView, renderWindowPtr, refreshAnomalies, draftId](std::string) {
|
||||
sceneView->removeAnomaly(draftId); // 撤草稿
|
||||
refreshAnomalies(); // 重渲染 + 刷新异常列表(含新异常)
|
||||
renderWindowPtr->Render();
|
||||
refreshAnalysis(); // 列表刷新(4c 异常面板接入后体现)
|
||||
},
|
||||
[&window](const std::string& m) {
|
||||
QMessageBox::warning(&window, QStringLiteral("保存异常"),
|
||||
|
|
@ -754,6 +770,24 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
QStringLiteral("色阶设置开发中。"));
|
||||
});
|
||||
|
||||
// ── 3D 异常控制(#4c):显示过滤 / 单条显隐 / 删除 → 驱动 VTK 异常渲染 ──────────
|
||||
// 过滤档位变化 → 重算异常集合并重渲染 + 刷新列表(独立于体勾选)。
|
||||
QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyDisplayFilterChanged, vtkWidget,
|
||||
[refreshAnomalies](int) { refreshAnomalies(); });
|
||||
// 单条显隐 → 切该异常 actor 可见性。
|
||||
QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyVisibilityChanged, vtkWidget,
|
||||
[sceneView, renderWindowPtr](const QString& id, bool vis) {
|
||||
sceneView->setAnomalyVisible(id.toStdString(), vis);
|
||||
renderWindowPtr->Render();
|
||||
});
|
||||
// 删除异常 → 删 mock + 刷新渲染/列表。
|
||||
QObject::connect(ca, &geopro::app::Column3DAnalysis::anomalyDeleteRequested, &window,
|
||||
[scene3dRepo, refreshAnomalies](const QString& id) {
|
||||
scene3dRepo->deleteAnomaly(
|
||||
id.toStdString(), [refreshAnomalies]() { refreshAnomalies(); },
|
||||
[](const std::string&) {});
|
||||
});
|
||||
|
||||
// ── 二维数据集栏:天地图底图开关(③,复用轨迹图 token,经同一共享 GeoLocalFrame 配准)──
|
||||
auto* basemap = new geopro::app::TileBasemap(*scene, renderWindowPtr, frame, &window);
|
||||
// 当前底图选择(默认 天地图=Satellite,对齐 Column2DDataset 默认项);数据重锚后据此在数据位置加载。
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
#include "panels/columns/Column3DAnalysis.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QSet>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSplitter>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QTreeWidgetItemIterator>
|
||||
|
|
@ -35,7 +40,78 @@ Column3DAnalysis::Column3DAnalysis(QWidget* parent) : QWidget(parent) {
|
|||
emit checkedItemsChanged(ids);
|
||||
});
|
||||
|
||||
root->addWidget(tree_, 1);
|
||||
// ── 数据集树(上) + 「异常」分组(下) 放进竖向 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());
|
||||
});
|
||||
|
||||
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 QComboBox();
|
||||
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) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,16 @@
|
|||
#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:
|
||||
|
|
@ -19,6 +21,10 @@ public:
|
|||
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); // 三维体右键 切片▸(上下/前后/左右/任意)
|
||||
|
|
@ -30,10 +36,18 @@ signals:
|
|||
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); // 右键删除
|
||||
|
||||
private:
|
||||
void onContextMenu(const QPoint& pos);
|
||||
void onAnomalyContextMenu(const QPoint& pos);
|
||||
QTreeWidget* tree_ = nullptr;
|
||||
QTreeWidget* anomalyTree_ = nullptr;
|
||||
QComboBox* anomalyFilter_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
Loading…
Reference in New Issue