refactor(vtk): 抽屉去tab单列; 勾选经描述符路由渲染策略(统一入口,无维度散判)
- ColumnDrawer 去 QTabWidget/Column2DDataset/analysisModeChanged, body 直持 CategoryAnalysisTab 单列 - VtkSceneController: 统一入口 setCheckedDatasets((dsId,typeId)) 经 catalog.renderStrategyId 派 3 策略 add/remove, 维护每 typeId 活跃计数 onTypeActivated/Deactivated; checkedDs_/checked2dDs_ 合并为 checked_ - 3 策略持控制器友元引用, 委托回 addDatasetAsync/add2DDatasetAsync/view.removeDataset - main: pushChecked 解析 dsId→typeId 下发统一入口; 删 col2D 全部接线; 2D 轨迹经 trajectory 段并入单列 - rebuildInternal 据 checked_ 经策略重放(主题/VE/坐标轴重建不丢数据) - 控制器单测迁移至统一 API (18/18 通过)
This commit is contained in:
parent
286054720e
commit
70f847058d
109
src/app/main.cpp
109
src/app/main.cpp
|
|
@ -95,6 +95,7 @@
|
|||
#include "AuthService.hpp"
|
||||
#include "DatasetDimension.hpp"
|
||||
#include "DatasetCategory.hpp"
|
||||
#include "repo/CategoryDescriptor.hpp"
|
||||
#include "Credential.hpp"
|
||||
#include "Glyphs.hpp"
|
||||
#include "Logging.hpp"
|
||||
|
|
@ -149,7 +150,6 @@
|
|||
#include "AxesSettingsDialog.hpp"
|
||||
#include "AxesSettingsPanel.hpp"
|
||||
#include "repo/DatasetFieldDictionary.hpp"
|
||||
#include "panels/columns/Column2DDataset.hpp"
|
||||
|
||||
#include "CameraPreset.hpp"
|
||||
#include "ColorLutBuilder.hpp"
|
||||
|
|
@ -556,20 +556,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
return true;
|
||||
};
|
||||
}
|
||||
// 双向选择联动:列表行选中 ↔ VTK 足迹高亮。两向各自屏蔽回环(setSelectedMapLines 不回调、
|
||||
// setSelectedDsIds 屏蔽信号),故无需额外守卫。
|
||||
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::selectedDatasetsChanged, &window,
|
||||
[sceneView](const QStringList& ids) {
|
||||
std::vector<std::string> v;
|
||||
for (const QString& s : ids) v.push_back(s.toStdString());
|
||||
sceneView->setSelectedMapLines(v);
|
||||
});
|
||||
sceneView->onMapLineSelectionChanged = [sceneView, drawer]() {
|
||||
QStringList ids;
|
||||
for (const std::string& s : sceneView->selectedMapLines())
|
||||
ids << QString::fromStdString(s);
|
||||
drawer->col2D()->setSelectedDsIds(ids);
|
||||
};
|
||||
// 双向选择联动(B2:去 col2D 后由统一段 datasetSelected 承担 2D 选中,此处旧 col2D 链已移除)。
|
||||
|
||||
// 坐标轴设置抽屉面板:叠加 vtkWidget、工具条右侧滑出,默认隐藏(点设置 toggle)。
|
||||
auto* axesPanel = new geopro::app::AxesSettingsPanel(vtkWidget);
|
||||
|
|
@ -650,7 +637,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// (设计 §2.1:三维分析栏按 对象/三维体模型/切片 三级树),归三维分析栏(非数据集栏)。
|
||||
// 后端列表每次勾选测线全量覆盖,故客户端体单独维护、刷新时合并注入(不被冲掉)。
|
||||
// 三维分析数据源 = 最近对象树勾选拉取的 ds + 客户端三维体(mock) + 已保存切片;
|
||||
// splitByCategory 后注入 5 段(电阻率/视电阻率/瞬变/三维体/切片);二维(足迹)经 dim2D 仍走 col2D。
|
||||
// splitByCategory 后注入各段(电阻率/视电阻率/瞬变/三维体/轨迹);二维(足迹/轨迹)并入同一单列(B2)。
|
||||
auto lastSourceRows = std::make_shared<std::vector<geopro::data::DsRow>>();
|
||||
auto lastStructNodes = std::make_shared<std::vector<geopro::data::StructNode>>(); // 生成位置候选(项目内 GS/TM)
|
||||
auto refreshAnalysis = [drawer, scene3dRepo, lastSourceRows, lastStructNodes]() {
|
||||
|
|
@ -667,7 +654,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
for (const auto& s : slices) voxelTree.push_back(s);
|
||||
for (const auto& a : anomalies) voxelTree.push_back(a);
|
||||
if (auto* sec = drawer->analysisTab()->section("voxel")) sec->setDatasets(voxelTree);
|
||||
drawer->col2D()->setDatasets(geopro::app::splitByDimension(*lastSourceRows).dim2D);
|
||||
// B2:二维(足迹/轨迹)不再走 col2D;splitByCategory 已含 trajectory 段,随 setBuckets 自动入单列。
|
||||
drawer->analysisTab()->refreshVisibility(); // voxel 段单独喂数据后刷新分段可见性(Task B1)
|
||||
};
|
||||
|
||||
// 渲染勾选聚合:三维数据集栏(剖面→帘面)+ 三维分析栏(三维体/切片→体素/切片)两套勾选并集
|
||||
|
|
@ -678,10 +666,31 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// 让「三维分析栏勾选(体/切片)」这条渲染路径也能隐藏不透明引导层——否则它盖住已渲染的体
|
||||
// (雷达体由分析栏勾选触发渲染,但旧逻辑只在对象树勾选时隐藏引导层 → 体被盖住看不到)。
|
||||
auto setSceneEmptyVisible = std::make_shared<std::function<void(bool)>>();
|
||||
auto pushChecked = [sceneCtrl, checkedProfiles, checkedAnalysis, setSceneEmptyVisible]() {
|
||||
// 勾选并集 → (dsId, typeId=描述符 id) 列表 → 控制器统一入口(B2:经描述符路由渲染策略,无维度散判)。
|
||||
// typeId 解析:三维体(mock,不在 lastSourceRows) → "voxel";其余(剖面/轨迹)在 lastSourceRows
|
||||
// 按 categoryCatalog().classify 命中描述符 → 其 id。控制器据 catalog[typeId].renderStrategyId 派策略。
|
||||
auto pushChecked = [sceneCtrl, checkedProfiles, checkedAnalysis, setSceneEmptyVisible,
|
||||
scene3dRepo, lastSourceRows]() {
|
||||
QStringList all = *checkedProfiles;
|
||||
all += *checkedAnalysis;
|
||||
sceneCtrl->setCheckedDatasets(all);
|
||||
std::vector<std::pair<std::string, std::string>> idType;
|
||||
const auto& cat = geopro::data::categoryCatalog();
|
||||
for (const QString& q : all) {
|
||||
const std::string id = q.toStdString();
|
||||
std::string typeId;
|
||||
if (scene3dRepo->isVolumeDataset(id)) {
|
||||
typeId = "voxel"; // 客户端三维体(mock) 不在 lastSourceRows,按仓储谓词直判
|
||||
} else {
|
||||
const auto& rows = *lastSourceRows; // 剖面/轨迹按描述符 classify 解析具体类型
|
||||
auto it = std::find_if(rows.begin(), rows.end(),
|
||||
[&](const geopro::data::DsRow& r) { return r.id == id; });
|
||||
if (it != rows.end())
|
||||
for (const auto& d : cat)
|
||||
if (d.classify && d.classify(*it)) { typeId = d.id; break; }
|
||||
}
|
||||
if (!typeId.empty()) idType.push_back({id, typeId});
|
||||
}
|
||||
sceneCtrl->setCheckedDatasets(idType);
|
||||
if (*setSceneEmptyVisible) (*setSceneEmptyVisible)(all.isEmpty()); // 场景有内容→隐藏引导层
|
||||
};
|
||||
|
||||
|
|
@ -1385,48 +1394,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// 当前底图选择(默认 天地图=Satellite,对齐 Column2DDataset 默认项);数据重锚后据此在数据位置加载。
|
||||
auto basemapKind =
|
||||
std::make_shared<geopro::app::TileBasemap::Kind>(geopro::app::TileBasemap::Satellite);
|
||||
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::basemapChanged, basemap,
|
||||
[basemap, basemapKind](int idx) {
|
||||
// 地图下拉:0 天地图(卫星影像) / 1 Google(暂未实现→隐藏) / 2 隐藏。
|
||||
*basemapKind = (idx == 0) ? geopro::app::TileBasemap::Satellite
|
||||
: geopro::app::TileBasemap::Hidden;
|
||||
basemap->show(*basemapKind);
|
||||
});
|
||||
// ── 二维数据集栏:勾选足迹(测线/轨迹) → 平铺进 View3D 地图;2D视图下拉控摆放高度 ──
|
||||
// 足迹经控制器 loadMapLine(Api3dRepository 走 dd/ert/trajectory/line 端点) → addMapLine 至
|
||||
// 当前摆放 Z,与帘面/底图共享 GeoLocalFrame 配准。与 3D 勾选集独立、按 dsId 增量。
|
||||
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::checkedDatasetsChanged,
|
||||
sceneCtrl, &geopro::controller::VtkSceneController::setChecked2DDatasets);
|
||||
// 2D视图下拉(0关闭/1 Z=0/2顶部/3底部/4自定义) + 自定义 Z → 控制器重摆已勾选足迹。
|
||||
auto custom2dZ = std::make_shared<double>(0.0);
|
||||
// 默认 1(Z=0):与「2D视图」下拉可见默认项(setCurrentIndex(1))及控制器默认摆放一致——
|
||||
// 组合框初始 view2DModeChanged 因 connect 前发射而丢失,此处不可依赖其同步初值。
|
||||
auto view2dMode = std::make_shared<int>(1);
|
||||
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::view2DModeChanged, sceneCtrl,
|
||||
[sceneCtrl, custom2dZ, view2dMode](int mode) {
|
||||
*view2dMode = mode;
|
||||
sceneCtrl->set2DPlacement(mode, *custom2dZ);
|
||||
});
|
||||
// 自定义 Z 变化:记录;若当前正处自定义模式则即时重摆(控制器内 changed 判定避免无谓重画)。
|
||||
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::customZChanged, sceneCtrl,
|
||||
[sceneCtrl, custom2dZ, view2dMode](double z) {
|
||||
*custom2dZ = z;
|
||||
if (*view2dMode == 4) sceneCtrl->set2DPlacement(4, z);
|
||||
});
|
||||
|
||||
// ── 二维分析改造 A 期:切「三维分析/二维分析」tab → 一场景两相机 ──────────────────
|
||||
// 三处协作:①切片隐藏+交互锁(仅平移+缩放) [InteractionManager];②按目标维度重置取景基线
|
||||
// [VtkSceneController]——使切换后该维度首条数据自动取景;③维度显隐+近俯视/自由相机+取景+坐标轴+
|
||||
// 渲染 [VtkSceneView]。顺序:先 ①②(都不渲染),最后 ③ 收尾统一渲染。只翻可见标志、不清空/重建 →
|
||||
// 切换瞬时;地形+底图常驻。
|
||||
QObject::connect(drawer, &geopro::app::ColumnDrawer::analysisModeChanged, &window,
|
||||
[interactionMgr, sceneCtrl, sceneView, viewToolbar, refreshAlongLineBar](bool is2D) {
|
||||
interactionMgr->setMode2D(is2D);
|
||||
sceneCtrl->onAnalysisModeChanged(is2D);
|
||||
sceneView->setAnalysisMode2D(is2D);
|
||||
viewToolbar->setAnalysisMode2D(is2D); // 二维下禁用 6 向快捷视图
|
||||
(*refreshAlongLineBar)(); // 二维隐藏沿线滑块、三维细长体显示(B#2)
|
||||
});
|
||||
// B2:col2D 已删 —— 旧 2D 面板信号(basemapChanged / checkedDatasetsChanged / view2DModeChanged /
|
||||
// customZChanged)接线随之移除。其替代(工具条 3D 底图、平面 z 滑块、2D 底图弹层)由 Phase D3/E3/F2
|
||||
// 重接。轨迹(足迹)勾选现经统一段 checkedDatasetsChanged → pushChecked → "plane2d" 策略渲染。
|
||||
// 底图默认仍为天地图(basemapKind=Satellite),首个数据重锚后由 onFrameReanchored 显示。
|
||||
// 抽屉去 tab 后无「三维/二维分析」切换,视图固定三维分析;旧 analysisModeChanged 接线移除(C2/D3 重接)。
|
||||
|
||||
// 首个真实剖面到达 → frame 重锚到数据 lat/lon 后,把选中的底图加载到数据所在位置
|
||||
// (默认天地图即在此刻出现,避免启动时在样本原点拉无关瓦片)。
|
||||
|
|
@ -1701,7 +1673,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
emptyState->setVisible(sources.isEmpty() && checkedAnalysis->isEmpty());
|
||||
if (sources.isEmpty()) {
|
||||
*lastSourceRows = {};
|
||||
refreshAnalysis(); // 清空 5 段(客户端三维体仍驻留) + col2D
|
||||
refreshAnalysis(); // 清空各段(客户端三维体仍驻留)
|
||||
return;
|
||||
}
|
||||
// 多源异步汇总:每个源(TM / GS·项目根直挂)按 confType 取整棵 ds 子树,全部回来后 splitByCategory 分 5 段。
|
||||
|
|
@ -1710,7 +1682,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
auto finish = [acc, generation, myGen, lastSourceRows, refreshAnalysis]() {
|
||||
if (*generation != myGen) return; // 已被更新的勾选批次取代→丢弃陈旧结果
|
||||
*lastSourceRows = *acc; // 全部对象树 ds 作分析数据源
|
||||
refreshAnalysis(); // splitByCategory→5段 + 合并三维体/切片 + dim2D→col2D
|
||||
refreshAnalysis(); // splitByCategory→各段 + 合并三维体/切片(单列)
|
||||
};
|
||||
for (const geopro::data::DataSource& src : sources) {
|
||||
// 第3参 confType:1=GS/项目根(直挂 ds),2=TM(测线下 ds)——透传给 loadRowsAsync(spec §6)。
|
||||
|
|
@ -1802,21 +1774,20 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
|
||||
// 切换到「不同项目」时先清空中央区,避免新项目残留旧项目的三栏数据与 VTK 渲染。
|
||||
// 仅真正换项目用(delete-refresh 等 switchProject(currentProjectId) 不走此处,避免误清)。
|
||||
auto clearCentral = [sceneCtrl, emptyState, checkedProfiles, checkedAnalysis,
|
||||
auto clearCentral = [emptyState, checkedProfiles, checkedAnalysis,
|
||||
pushChecked, lastSourceRows, refreshAnalysis, checkedSliceIds,
|
||||
syncSlices, basemap, sceneView, scene3dRepo]() {
|
||||
// 切项目:先清内存态三维体/切片/异常(否则上个项目的产物残留进新项目列表),再 refreshAnalysis。
|
||||
scene3dRepo->clearMockData();
|
||||
// 数据源清空 → 5 段 + col2D 清空(refreshAnalysis 内 setBuckets/dim2D)。
|
||||
// 数据源清空 → 各段清空(refreshAnalysis 内 setBuckets,含 trajectory 段)。
|
||||
*lastSourceRows = {};
|
||||
refreshAnalysis();
|
||||
// 勾选集清空并下发空到 VTK(帘面/体素/切片/2D 足迹全部撤场)。
|
||||
// 勾选集清空并下发空到 VTK(帘面/体素/切片/2D 足迹全部撤场——统一入口一并清)。
|
||||
checkedProfiles->clear();
|
||||
checkedAnalysis->clear();
|
||||
checkedSliceIds->clear();
|
||||
pushChecked(); // setCheckedDatasets({}) → 帘面/体素清空
|
||||
syncSlices(); // 切片随空勾选调和
|
||||
sceneCtrl->setChecked2DDatasets({}); // 2D 足迹显式撤场(与 col2D 空勾选双保险)
|
||||
pushChecked(); // setCheckedDatasets({}) → 帘面/体素/足迹清空(统一勾选集 diff 全移除)
|
||||
syncSlices(); // 切片随空勾选调和
|
||||
// 复位重锚标志:增量勾选不走 clear(),不复位则旧标志残留 → 新项目数据不重锚 →
|
||||
// onFrameReanchored 不触发 → 下面 hide() 的底图永不再显。复位后新项目首个数据重锚→重显。
|
||||
sceneView->resetFrameAnchor();
|
||||
|
|
|
|||
|
|
@ -1,34 +1,19 @@
|
|||
#include "panels/columns/ColumnDrawer.hpp"
|
||||
#include "panels/columns/Column2DDataset.hpp"
|
||||
#include "panels/columns/CategoryAnalysisTab.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
#include "Glyphs.hpp"
|
||||
#include "Theme.hpp"
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QTabBar>
|
||||
#include <QTabWidget>
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
ColumnDrawer::ColumnDrawer(QWidget* parent, geopro::data::DatasetFieldDictionary* dict)
|
||||
: QWidget(parent)
|
||||
{
|
||||
col2D_ = new Column2DDataset(this);
|
||||
// 单列承载:去 QTabWidget,body_ 直接为 CategoryAnalysisTab(含 trajectory 段,二维并入同列)。
|
||||
analysisTab_ = new CategoryAnalysisTab(dict, this);
|
||||
|
||||
// Tab 容器(body_):两 tab(三维分析[分段] / 二维分析)。
|
||||
auto* tabs = new QTabWidget(this);
|
||||
body_ = tabs;
|
||||
tabs->addTab(analysisTab_, QStringLiteral("三维分析"));
|
||||
tabs->addTab(col2D_, QStringLiteral("二维分析"));
|
||||
tabs->tabBar()->setUsesScrollButtons(false); // 永不出左右滚动箭头(两 tab 必能平铺)
|
||||
// 切 tab → 发 analysisModeChanged(is2D):以"当前 widget 是否 col2D"判定,不写死索引。
|
||||
connect(tabs, &QTabWidget::currentChanged, this, [this, tabs](int idx) {
|
||||
emit analysisModeChanged(tabs->widget(idx) == col2D_);
|
||||
});
|
||||
body_ = analysisTab_;
|
||||
|
||||
// 折叠按钮:固定宽 18px,垂直拉伸。
|
||||
// 用 SVG 图标(makeGlyph)而非 ◀/▶ 文字——三角符(U+25C0/25B6)不在 YaHei,作按钮文字会触发
|
||||
|
|
@ -58,22 +43,6 @@ ColumnDrawer::ColumnDrawer(QWidget* parent, geopro::data::DatasetFieldDictionary
|
|||
setMaximumWidth(560);
|
||||
}
|
||||
|
||||
void ColumnDrawer::resizeEvent(QResizeEvent* e)
|
||||
{
|
||||
QWidget::resizeEvent(e);
|
||||
// 两 tab 平分抽屉宽度填满(带样式表的 tab 不响应 setExpanding,须按 barWidth/n 显式给宽)。
|
||||
// 消除旧 3 栏布局遗留的右侧空白——重构成 2 栏后不再三分、留空第三位。
|
||||
if (auto* tabs = qobject_cast<QTabWidget*>(body_)) {
|
||||
const int n = tabs->count();
|
||||
if (n > 0 && tabs->width() > 0) {
|
||||
// 每 tab 内容宽 = 总宽/n - 每 tab 非内容开销(全局 QSS padding 8+16+16=… 约 32 + margin 4)。
|
||||
// 稍欠一点宽避免溢出(溢出会触发滚动箭头);setUsesScrollButtons(false) 再兜底。
|
||||
const int w = std::max(40, tabs->width() / n - 42);
|
||||
tabs->tabBar()->setStyleSheet(QStringLiteral("QTabBar::tab{width:%1px;}").arg(w));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColumnDrawer::toggleCollapsed()
|
||||
{
|
||||
collapsed_ = !collapsed_;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
#include <QWidget>
|
||||
|
||||
class QPushButton;
|
||||
class QResizeEvent;
|
||||
|
||||
namespace geopro::data {
|
||||
class DatasetFieldDictionary;
|
||||
|
|
@ -10,36 +9,29 @@ class DatasetFieldDictionary;
|
|||
|
||||
namespace geopro::app {
|
||||
|
||||
class Column2DDataset;
|
||||
class CategoryAnalysisTab;
|
||||
|
||||
// VTK视图左侧内嵌抽屉:两 tab(三维分析[按数据类型分段]/二维分析) + 折叠开关。
|
||||
// VTK视图左侧内嵌抽屉:单列承载 CategoryAnalysisTab(按数据类型分段,含 trajectory 段) + 折叠开关。
|
||||
// B2 去 tab:原「三维分析 / 二维分析」双 tab 合一,二维(足迹/轨迹)经描述符分段并入同一列。
|
||||
class ColumnDrawer : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ColumnDrawer(QWidget* parent = nullptr,
|
||||
geopro::data::DatasetFieldDictionary* dict = nullptr);
|
||||
|
||||
Column2DDataset* col2D() const { return col2D_; }
|
||||
CategoryAnalysisTab* analysisTab() const { return analysisTab_; }
|
||||
|
||||
signals:
|
||||
// 切换「三维分析 / 二维分析」tab:is2D=true 进入二维分析。上层据此切相机+翻维度可见标志。
|
||||
void analysisModeChanged(bool is2D);
|
||||
// 折叠/展开切换:上层据此调整 QSplitter 尺寸,回收/恢复抽屉栏宽(否则收起残留空白区)。
|
||||
void collapsedChanged(bool collapsed);
|
||||
|
||||
public slots:
|
||||
void toggleCollapsed();
|
||||
void expand(); // 强制展开(进入全屏时确保三栏可见)
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* e) override; // 两 tab 按抽屉宽平分(消除右侧空白"第三栏位")
|
||||
void expand(); // 强制展开(进入全屏时确保单列可见)
|
||||
|
||||
private:
|
||||
Column2DDataset* col2D_ = nullptr;
|
||||
CategoryAnalysisTab* analysisTab_ = nullptr;
|
||||
QWidget* body_ = nullptr; // QTabWidget,折叠时隐藏
|
||||
QWidget* body_ = nullptr; // = analysisTab_(折叠时隐藏)
|
||||
QPushButton* toggleBtn_ = nullptr;
|
||||
bool collapsed_ = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,24 @@
|
|||
#include "controller/DatasetRenderStrategy.hpp"
|
||||
|
||||
#include "controller/VtkSceneController.hpp" // 完整类型 + 含 I3dSceneView(view_.removeDataset)
|
||||
|
||||
namespace geopro::controller {
|
||||
|
||||
// 骨架实现:渲染体由后续阶段填充(Phase B / E / F)。当前为空实现,仅保证链接。
|
||||
void VolumeRenderStrategy::add(const std::string& /*typeId*/, const std::string& /*dsId*/) {}
|
||||
void VolumeRenderStrategy::remove(const std::string& /*dsId*/) {}
|
||||
// 各策略委托回控制器既有渲染路径(友元访问其私有 addDatasetAsync/add2DDatasetAsync/view_)。
|
||||
// 增量 gen 沿用控制器当前 rebuildGeneration_(不自增,与并发增量互不作废)。
|
||||
void VolumeRenderStrategy::add(const std::string& /*typeId*/, const std::string& dsId) {
|
||||
ctrl_.addDatasetAsync(dsId, ctrl_.rebuildGeneration_);
|
||||
}
|
||||
void VolumeRenderStrategy::remove(const std::string& dsId) { ctrl_.view_.removeDataset(dsId); }
|
||||
|
||||
void CurtainRenderStrategy::add(const std::string& /*typeId*/, const std::string& /*dsId*/) {}
|
||||
void CurtainRenderStrategy::remove(const std::string& /*dsId*/) {}
|
||||
void CurtainRenderStrategy::add(const std::string& /*typeId*/, const std::string& dsId) {
|
||||
ctrl_.addDatasetAsync(dsId, ctrl_.rebuildGeneration_);
|
||||
}
|
||||
void CurtainRenderStrategy::remove(const std::string& dsId) { ctrl_.view_.removeDataset(dsId); }
|
||||
|
||||
void Plane2DRenderStrategy::add(const std::string& /*typeId*/, const std::string& /*dsId*/) {}
|
||||
void Plane2DRenderStrategy::remove(const std::string& /*dsId*/) {}
|
||||
void Plane2DRenderStrategy::add(const std::string& /*typeId*/, const std::string& dsId) {
|
||||
ctrl_.add2DDatasetAsync(dsId, ctrl_.rebuildGeneration_);
|
||||
}
|
||||
void Plane2DRenderStrategy::remove(const std::string& dsId) { ctrl_.view_.removeDataset(dsId); }
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
namespace geopro::controller {
|
||||
|
||||
class VtkSceneController; // 策略委托回控制器既有渲染路径(add→addDatasetAsync/add2DDatasetAsync;remove→view.removeDataset)
|
||||
|
||||
class IDatasetRenderStrategy {
|
||||
public:
|
||||
virtual ~IDatasetRenderStrategy() = default;
|
||||
|
|
@ -27,26 +29,34 @@ private:
|
|||
std::map<std::string, std::unique_ptr<IDatasetRenderStrategy>> strategies_;
|
||||
};
|
||||
|
||||
// 3 骨架策略:本任务仅建类与注册。add/remove 的真实渲染实现由后续阶段填充——
|
||||
// Phase B(VolumeRenderStrategy/CurtainRenderStrategy 包现有 3D 分支)与
|
||||
// Phase E/F(Plane2DRenderStrategy 平面 z + 底图)。届时各自接所需的
|
||||
// VtkSceneController& / View / Repo 引用。当前 .cpp 给空实现使链接通过。
|
||||
// 3 策略:各持 VtkSceneController& 引用,把 add/remove 委托回控制器既有渲染路径。
|
||||
// Volume/Curtain::add 均转调 addDatasetAsync(其内部按 isVolumeDataset 自分体/帘面分支);
|
||||
// Plane2D::add 暂转调 add2DDatasetAsync(Phase E/F 改平面 z + 底图)。remove 统一 view.removeDataset。
|
||||
class VolumeRenderStrategy : public IDatasetRenderStrategy {
|
||||
public:
|
||||
explicit VolumeRenderStrategy(VtkSceneController& ctrl) : ctrl_(ctrl) {}
|
||||
void add(const std::string& typeId, const std::string& dsId) override;
|
||||
void remove(const std::string& dsId) override;
|
||||
private:
|
||||
VtkSceneController& ctrl_;
|
||||
};
|
||||
|
||||
class CurtainRenderStrategy : public IDatasetRenderStrategy {
|
||||
public:
|
||||
explicit CurtainRenderStrategy(VtkSceneController& ctrl) : ctrl_(ctrl) {}
|
||||
void add(const std::string& typeId, const std::string& dsId) override;
|
||||
void remove(const std::string& dsId) override;
|
||||
private:
|
||||
VtkSceneController& ctrl_;
|
||||
};
|
||||
|
||||
class Plane2DRenderStrategy : public IDatasetRenderStrategy {
|
||||
public:
|
||||
explicit Plane2DRenderStrategy(VtkSceneController& ctrl) : ctrl_(ctrl) {}
|
||||
void add(const std::string& typeId, const std::string& dsId) override;
|
||||
void remove(const std::string& dsId) override;
|
||||
private:
|
||||
VtkSceneController& ctrl_;
|
||||
};
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include "DatasetViewState.hpp"
|
||||
#include "I3dSceneView.hpp"
|
||||
#include "controller/DatasetRenderStrategy.hpp"
|
||||
#include "repo/CategoryDescriptor.hpp"
|
||||
#include "repo/IDatasetRepository.hpp"
|
||||
|
||||
namespace geopro::controller {
|
||||
|
|
@ -23,7 +25,19 @@ constexpr double kBottomOffsetZ = -50.0; // 底部:参考面下方
|
|||
VtkSceneController::VtkSceneController(data::IDatasetRepository& dsRepo,
|
||||
data::I3dSceneRepository& sceneRepo, I3dSceneView& view,
|
||||
QObject* parent)
|
||||
: QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {}
|
||||
: QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {
|
||||
// 注册 3 渲染策略(键与 CategoryDescriptor.renderStrategyId 对应)。各持本控制器引用,
|
||||
// add/remove 委托回既有渲染路径(addDatasetAsync/add2DDatasetAsync/view_.removeDataset)。
|
||||
registry_.registerStrategy("volume", std::make_unique<VolumeRenderStrategy>(*this));
|
||||
registry_.registerStrategy("curtain", std::make_unique<CurtainRenderStrategy>(*this));
|
||||
registry_.registerStrategy("plane2d", std::make_unique<Plane2DRenderStrategy>(*this));
|
||||
}
|
||||
|
||||
IDatasetRenderStrategy* VtkSceneController::strategyForType(const std::string& typeId) const {
|
||||
for (const auto& d : geopro::data::categoryCatalog())
|
||||
if (d.id == typeId) return registry_.get(d.renderStrategyId);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VtkSceneController::setViewState(DatasetViewState* state) {
|
||||
state_ = state;
|
||||
|
|
@ -58,84 +72,41 @@ void VtkSceneController::recolorDataset(const QString& qid) {
|
|||
if (changed) view_.renderIncremental();
|
||||
}
|
||||
|
||||
void VtkSceneController::setCheckedDatasets(const QStringList& dsIds) {
|
||||
std::vector<std::string> newDs;
|
||||
newDs.reserve(static_cast<std::size_t>(dsIds.size()));
|
||||
for (const QString& id : dsIds) newDs.push_back(id.toStdString());
|
||||
void VtkSceneController::setCheckedDatasets(
|
||||
const std::vector<std::pair<std::string, std::string>>& idType) {
|
||||
std::map<std::string, std::string> next; // dsId→typeId
|
||||
for (const auto& p : idType) next[p.first] = p.second;
|
||||
|
||||
// 2D 俯视测线:保持全量重建(测线非按 ds 跟踪移除)。
|
||||
if (mode_ == ViewMode::Map2D) {
|
||||
checkedDs_ = std::move(newDs);
|
||||
rebuildInternal();
|
||||
return;
|
||||
}
|
||||
const std::map<std::string, std::string> prev = checked_; // diff 快照(区分新增/移除)
|
||||
|
||||
// 3D:增量 diff —— 只处理新增/移除,不全量重建(底图、其余 ds、相机均不动)。
|
||||
const std::set<std::string> oldSet(checkedDs_.begin(), checkedDs_.end());
|
||||
const std::set<std::string> newSet(newDs.begin(), newDs.end());
|
||||
// 移除:旧有新无 → 派该 ds 类型策略 remove;活跃计数归零则 onTypeDeactivated。
|
||||
for (const auto& [id, typeId] : prev)
|
||||
if (!next.count(id)) {
|
||||
if (auto* s = strategyForType(typeId)) s->remove(id);
|
||||
if (--typeActive_[typeId] == 0)
|
||||
if (auto* s = strategyForType(typeId)) s->onTypeDeactivated(typeId);
|
||||
}
|
||||
|
||||
for (const auto& id : checkedDs_)
|
||||
if (!newSet.count(id)) view_.removeDataset(id); // 移除:旧有新无 → 仅删该 ds 图元
|
||||
|
||||
checkedDs_ = std::move(newDs);
|
||||
// 取景意图按「场景是否已有数据到场过」判定,而非 checkedDs_ 是否空——否则连续快速勾选第二个
|
||||
// ds 时 checkedDs_ 已非空但首批尚未到场,会被误清取景意图,相机不对准数据 → 看似不渲染。
|
||||
// 取景意图按「场景是否已有数据到场过」判定(连续快速勾选时 checked_ 已非空但首批未到场,
|
||||
// 不可据 checked_ 空否清取景意图,否则相机不对准数据 → 看似不渲染)。全消时复位基线。
|
||||
fitOnArrival_ = !hadArrivedData_;
|
||||
// 仅当 3D 与 2D 都空才复位取景基线:否则取消全部 3D 但仍有 2D 足迹时,下个 3D 勾选会误跳取景。
|
||||
if (checkedDs_.empty() && checked2dDs_.empty()) hadArrivedData_ = false;
|
||||
if (next.empty()) hadArrivedData_ = false;
|
||||
|
||||
const unsigned long long gen = rebuildGeneration_; // 不自增:并发增量互不作废
|
||||
for (const auto& id : checkedDs_)
|
||||
if (!oldSet.count(id)) addDatasetAsync(id, gen); // 新增:新有旧无 → 异步取数增量入场
|
||||
// 先提交勾选集,再派 add:异步取数回调以 isChecked() 守「仍勾选?」,同步仓储(测试)会即时回灌,
|
||||
// 若 add 时 checked_ 未更新则 isChecked 假、回调丢弃 → 不渲染。故必须先 commit 再 add。
|
||||
checked_ = next;
|
||||
|
||||
// 新增:新有旧无 → 活跃计数 0→1 时 onTypeActivated;再派策略 add(委托回既有渲染路径)。
|
||||
for (const auto& [id, typeId] : checked_)
|
||||
if (!prev.count(id)) {
|
||||
if (typeActive_[typeId]++ == 0)
|
||||
if (auto* s = strategyForType(typeId)) s->onTypeActivated(typeId);
|
||||
if (auto* s = strategyForType(typeId)) s->add(typeId, id);
|
||||
}
|
||||
|
||||
view_.renderIncremental(); // 立即反映移除 / 触发坐标轴重算
|
||||
}
|
||||
|
||||
void VtkSceneController::setChecked2DDatasets(const QStringList& dsIds) {
|
||||
std::vector<std::string> newDs;
|
||||
newDs.reserve(static_cast<std::size_t>(dsIds.size()));
|
||||
for (const QString& id : dsIds) newDs.push_back(id.toStdString());
|
||||
|
||||
// 二维足迹始终画进 View3D 场景,且按 dsId 跟踪 → 一律增量 diff(不全量重建,不打断 3D 帘面/体)。
|
||||
const std::set<std::string> oldSet(checked2dDs_.begin(), checked2dDs_.end());
|
||||
const std::set<std::string> newSet(newDs.begin(), newDs.end());
|
||||
|
||||
for (const auto& id : checked2dDs_)
|
||||
if (!newSet.count(id)) view_.removeDataset(id); // 取消勾选 → 移除该足迹图元
|
||||
|
||||
checked2dDs_ = std::move(newDs);
|
||||
// 取景基线与 3D 路径统一用 hadArrivedData_(而非"两栏皆空"):否则二维分析下若已有隐藏的 3D 数据,
|
||||
// 勾选首条足迹会因 wasEmpty=false 而不取景 → 足迹落在视野外。切 tab 时 onAnalysisModeChanged 已按
|
||||
// 目标维度是否有数据重置该基线,故此处首条可见维度数据能正确取景。
|
||||
fitOnArrival_ = !hadArrivedData_;
|
||||
if (checkedDs_.empty() && checked2dDs_.empty()) hadArrivedData_ = false;
|
||||
|
||||
// 足迹画进 View3D 场景;mode=0 关闭 → 仅记录勾选不渲染(见 set2DPlacement 切回时补画)。
|
||||
if (placement2dMode_ != 0 && mode_ == ViewMode::View3D) {
|
||||
const unsigned long long gen = rebuildGeneration_; // 不自增:与 3D 增量互不作废
|
||||
for (const auto& id : checked2dDs_)
|
||||
if (!oldSet.count(id)) add2DDatasetAsync(id, gen); // 新增 → 异步取足迹增量入场
|
||||
}
|
||||
|
||||
view_.renderIncremental(); // 立即反映移除
|
||||
}
|
||||
|
||||
void VtkSceneController::set2DPlacement(int mode, double customZ) {
|
||||
const bool changed = (mode != placement2dMode_) || (mode == 4 && customZ != customZ2d_);
|
||||
placement2dMode_ = mode;
|
||||
customZ2d_ = customZ;
|
||||
if (!changed || checked2dDs_.empty()) return;
|
||||
|
||||
// 摆放变化 → 对已勾选足迹重摆:先全部移除,再按新 Z 重加(mode=0 关闭则只移除不重加)。
|
||||
for (const auto& id : checked2dDs_) view_.removeDataset(id);
|
||||
if (placement2dMode_ != 0 && mode_ == ViewMode::View3D) {
|
||||
const unsigned long long gen = rebuildGeneration_;
|
||||
fitOnArrival_ = false; // 重摆:保持相机
|
||||
for (const auto& id : checked2dDs_) add2DDatasetAsync(id, gen);
|
||||
}
|
||||
view_.renderIncremental();
|
||||
}
|
||||
|
||||
double VtkSceneController::placementZ() const {
|
||||
const double surf = view_.zRefElev(); // 真实地表高程基准(测线地表高程)
|
||||
switch (placement2dMode_) {
|
||||
|
|
@ -260,11 +231,11 @@ void VtkSceneController::onDatasetArrived() {
|
|||
}
|
||||
|
||||
bool VtkSceneController::isChecked(const std::string& dsId) const {
|
||||
return std::find(checkedDs_.begin(), checkedDs_.end(), dsId) != checkedDs_.end();
|
||||
return checked_.count(dsId) > 0; // 统一勾选集(异步回调「仍勾选?」守护)
|
||||
}
|
||||
|
||||
bool VtkSceneController::is2DChecked(const std::string& dsId) const {
|
||||
return std::find(checked2dDs_.begin(), checked2dDs_.end(), dsId) != checked2dDs_.end();
|
||||
return checked_.count(dsId) > 0; // 同上:2D 足迹与 3D 同处统一勾选集
|
||||
}
|
||||
|
||||
void VtkSceneController::setViewMode(ViewMode mode) {
|
||||
|
|
@ -277,7 +248,9 @@ void VtkSceneController::onAnalysisModeChanged(bool is2D) {
|
|||
// 目标维度空 → hadArrivedData_=false:切换后该维度第一条数据自动取景(治"3D 数据不知生成到哪")。
|
||||
// 目标维度非空 → hadArrivedData_=true:视图切换时已 fit 到该维度,后续勾选不再跳(与三维一致)。
|
||||
// 显隐/相机/坐标轴由 VtkSceneView::setAnalysisMode2D 处理(上层在同一处调用);此处只管取景基线。
|
||||
hadArrivedData_ = is2D ? !checked2dDs_.empty() : !checkedDs_.empty();
|
||||
// 注:B2 重构后抽屉去 tab、analysisModeChanged 接线移除,本槽暂无调用方(保留待 C2/D3 复用)。
|
||||
(void)is2D;
|
||||
hadArrivedData_ = !checked_.empty();
|
||||
}
|
||||
|
||||
void VtkSceneController::setLayer(SceneLayer layer, bool on) {
|
||||
|
|
@ -393,7 +366,7 @@ void VtkSceneController::rebuildInternal() {
|
|||
// 坏 dsId(loadGrid/loadColorScale 抛异常)= best-effort 跳过:emit loadFailed 但不中断。
|
||||
try {
|
||||
if (is2D) {
|
||||
for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId));
|
||||
for (const auto& [dsId, typeId] : checked_) view_.addSurveyLine(grid(dsId));
|
||||
} else {
|
||||
// 回调用 QPointer<self> 守对象存活 + gen 守数据新鲜:迟到回调若已析构/作废则丢弃。
|
||||
QPointer<VtkSceneController> self(this);
|
||||
|
|
@ -409,10 +382,10 @@ void VtkSceneController::rebuildInternal() {
|
|||
emit self->loadFailed(QString::fromStdString(m));
|
||||
});
|
||||
}
|
||||
for (const auto& dsId : checkedDs_) addDatasetAsync(dsId, gen);
|
||||
// 二维足迹随全量重建一并重画(clear 已移除其图元);mode=0 关闭则跳过。
|
||||
if (placement2dMode_ != 0)
|
||||
for (const auto& dsId : checked2dDs_) add2DDatasetAsync(dsId, gen);
|
||||
// 全量重建:clear 已移除全部图元,据统一勾选集经各 ds 类型策略重放 add(不动活跃计数:
|
||||
// 已在 setCheckedDatasets 计入;策略 add 内部转调 addDatasetAsync/add2DDatasetAsync)。
|
||||
for (const auto& [dsId, typeId] : checked_)
|
||||
if (auto* s = strategyForType(typeId)) s->add(typeId, dsId);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
emit loadFailed(QString::fromStdString(e.what()));
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@
|
|||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "I3dSceneView.hpp"
|
||||
#include "controller/DatasetRenderStrategy.hpp"
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/I3dSceneRepository.hpp"
|
||||
|
|
@ -34,6 +37,10 @@ enum class SceneLayer { Curtain, Voxel, Terrain };
|
|||
// 不持有 widget;不认 vtkActor/vtkVolume(全交给 I3dSceneView)。
|
||||
class VtkSceneController : public QObject {
|
||||
Q_OBJECT
|
||||
// 渲染策略委托回控制器既有路径(addDatasetAsync/add2DDatasetAsync/view_);友元免widen公有面。
|
||||
friend class VolumeRenderStrategy;
|
||||
friend class CurtainRenderStrategy;
|
||||
friend class Plane2DRenderStrategy;
|
||||
public:
|
||||
VtkSceneController(data::IDatasetRepository& dsRepo, data::I3dSceneRepository& sceneRepo,
|
||||
I3dSceneView& view, QObject* parent = nullptr);
|
||||
|
|
@ -42,12 +49,13 @@ public:
|
|||
// 构造后由 main.cpp 注入一次。
|
||||
void setViewState(DatasetViewState* state);
|
||||
|
||||
public:
|
||||
// 勾选并集统一入口(取代旧 setCheckedDatasets(QStringList)/setChecked2DDatasets):
|
||||
// 每项 = (dsId, typeId=描述符 id)。diff vs 上次后按 catalog[typeId].renderStrategyId 派给策略
|
||||
// add/remove,并维护「每 typeId 活跃数」在首勾/全消时调 onTypeActivated/Deactivated。
|
||||
void setCheckedDatasets(const std::vector<std::pair<std::string, std::string>>& idType);
|
||||
|
||||
public slots:
|
||||
void setCheckedDatasets(const QStringList& dsIds);
|
||||
// 二维数据集栏勾选(足迹型测线/轨迹)→ 平铺进 View3D 地图。与 3D 勾选集独立,按 dsId 增量。
|
||||
void setChecked2DDatasets(const QStringList& dsIds);
|
||||
// 二维足迹摆放高度(mode:0关闭 /1 Z=0 /2 顶部 /3 底部 /4 自定义;customZ 仅 mode=4 用)。
|
||||
void set2DPlacement(int mode, double customZ);
|
||||
void setViewMode(ViewMode mode);
|
||||
// 切「三维分析/二维分析」tab(A 期):按目标维度是否已有数据重置取景基线,使切换后该维度第一条
|
||||
// 数据自动取景。显隐/相机/坐标轴由 VtkSceneView::setAnalysisMode2D 负责(上层在同一处一并调用)。
|
||||
|
|
@ -96,6 +104,8 @@ private:
|
|||
void onDatasetArrived(); // 单个 ds 落地后:增量渲染 + 首批数据自动取景
|
||||
bool isChecked(const std::string& dsId) const;
|
||||
bool is2DChecked(const std::string& dsId) const;
|
||||
// 按 typeId 查其渲染策略(catalog[typeId].renderStrategyId → registry_)。未知 typeId 返回 nullptr。
|
||||
IDatasetRenderStrategy* strategyForType(const std::string& typeId) const;
|
||||
// 当前摆放模式下足迹的世界 Z(mode 0=关闭由调用方拦截;此处算 1/2/3/4 的 Z)。
|
||||
double placementZ() const;
|
||||
|
||||
|
|
@ -103,13 +113,14 @@ private:
|
|||
data::I3dSceneRepository& sceneRepo_;
|
||||
I3dSceneView& view_;
|
||||
|
||||
std::vector<std::string> checkedDs_;
|
||||
// 二维足迹勾选集(与 checkedDs_ 独立;都画进 View3D 场景)。按 dsId 增量加/删。
|
||||
std::vector<std::string> checked2dDs_;
|
||||
// 统一勾选集(2D+3D 合一):dsId→typeId(描述符 id)。增量 diff 的真源;rebuildInternal 据此重放。
|
||||
std::map<std::string, std::string> checked_;
|
||||
// 每 typeId 活跃计数:首勾(0→1)调 onTypeActivated、全消(1→0)调 onTypeDeactivated。
|
||||
std::map<std::string, int> typeActive_;
|
||||
// 渲染策略注册表(构造时注册 volume/curtain/plane2d 三策略,各持本控制器引用)。
|
||||
RenderStrategyRegistry registry_;
|
||||
// 二维足迹摆放:mode 0关闭/1 Z=0/2顶部/3底部/4自定义;customZ2d_ 仅 mode=4 用。
|
||||
// 默认 Z=0(1) 与 Column2DDataset「2D视图」下拉可见默认项一致——避免「下拉显示 Z=0 但
|
||||
// 控制器实为关闭」的初始信号丢失desync(组合框 setCurrentIndex 在 connect 前发射、且
|
||||
// 组件早于 main.cpp 接线构造,初始 view2DModeChanged 永不送达),致勾选足迹静默不渲染。
|
||||
// 默认 Z=0(1);摆放 UI(平面 z 滑块/底图)由 Phase E/F 重接,当前固定默认。
|
||||
int placement2dMode_ = 1;
|
||||
double customZ2d_ = 0.0;
|
||||
ViewMode mode_ = ViewMode::Map2D;
|
||||
|
|
|
|||
|
|
@ -203,26 +203,27 @@ struct FakeSceneRepo : data::I3dSceneRepository {
|
|||
|
||||
} // namespace
|
||||
|
||||
// 2D 模式 + 勾选 1 ds → 1 个测线 actor,无帘面/体素/地形。
|
||||
TEST(VtkSceneController, Map2DWithOneDatasetAddsSurveyLine) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::Map2D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
|
||||
EXPECT_EQ(view.surveyLines, 1);
|
||||
EXPECT_EQ(view.curtains, 0);
|
||||
EXPECT_EQ(view.volumes, 0);
|
||||
EXPECT_GE(view.renders, 1);
|
||||
EXPECT_TRUE(view.lastIs2D);
|
||||
// B2 后勾选统一入口 = (dsId, typeId=描述符 id) 列表。便捷构造:电阻率(curtain)/三维体(volume)/轨迹(plane2d)。
|
||||
namespace {
|
||||
using IdType = std::vector<std::pair<std::string, std::string>>;
|
||||
IdType curtainIds(std::initializer_list<std::string> ids) {
|
||||
IdType v;
|
||||
for (const auto& id : ids) v.push_back({id, "resistivity"}); // resistivity → renderStrategyId "curtain"
|
||||
return v;
|
||||
}
|
||||
IdType voxelIds(std::initializer_list<std::string> ids) {
|
||||
IdType v;
|
||||
for (const auto& id : ids) v.push_back({id, "voxel"}); // voxel → "volume"
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// 3D 模式 + 帘面图层 → 1 帘面 actor。
|
||||
// 3D 帘面:勾选电阻率(curtain 策略) → 1 帘面 actor。
|
||||
TEST(VtkSceneController, View3DCurtainAddsCurtain) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(curtainIds({"ds1"}));
|
||||
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
EXPECT_EQ(view.surveyLines, 0);
|
||||
|
|
@ -235,7 +236,7 @@ TEST(VtkSceneController, View3DVolumeDatasetAddsVolume) {
|
|||
sc.volumeIds = {"ds1"}; // ds1 = 三维体类型 → 体素渲染路径
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(voxelIds({"ds1"}));
|
||||
|
||||
EXPECT_EQ(view.volumes, 1);
|
||||
EXPECT_EQ(view.curtains, 0); // 体素数据集不再同时出帘面
|
||||
|
|
@ -247,18 +248,18 @@ TEST(VtkSceneController, View3DWithTerrainAddsTerrain) {
|
|||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setLayer(SceneLayer::Terrain, true);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(curtainIds({"ds1"}));
|
||||
|
||||
EXPECT_EQ(view.terrains, 1);
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
}
|
||||
|
||||
// 取消勾选 → 增量移除该 ds 图元(不整场 clear,3D 增量路径)。
|
||||
// 取消勾选 → 增量移除该 ds 图元(不整场 clear,增量路径)。
|
||||
TEST(VtkSceneController, UncheckRemovesDatasetIncrementally) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(curtainIds({"ds1"}));
|
||||
ASSERT_EQ(view.curtains, 1);
|
||||
const int clearsAfterCheck = view.clears;
|
||||
|
||||
|
|
@ -273,11 +274,11 @@ TEST(VtkSceneController, IncrementalAddKeepsExisting) {
|
|||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(curtainIds({"ds1"}));
|
||||
const int clearsAfterFirst = view.clears;
|
||||
ASSERT_EQ(view.curtains, 1);
|
||||
|
||||
c.setCheckedDatasets({"ds1", "ds2"}); // 增量加 ds2
|
||||
c.setCheckedDatasets(curtainIds({"ds1", "ds2"})); // 增量加 ds2
|
||||
EXPECT_EQ(view.curtains, 2);
|
||||
EXPECT_EQ(view.clears, clearsAfterFirst); // 不重建 → 无新 clear
|
||||
}
|
||||
|
|
@ -288,7 +289,7 @@ TEST(VtkSceneController, VerticalExaggerationForwarded) {
|
|||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setVerticalExaggeration(3.5);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(curtainIds({"ds1"}));
|
||||
EXPECT_DOUBLE_EQ(view.ve, 3.5);
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +298,7 @@ TEST(VtkSceneController, MultipleDatasetsAddMultipleCurtains) {
|
|||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1", "ds2", "ds3"});
|
||||
c.setCheckedDatasets(curtainIds({"ds1", "ds2", "ds3"}));
|
||||
EXPECT_EQ(view.curtains, 3);
|
||||
}
|
||||
|
||||
|
|
@ -309,7 +310,7 @@ TEST(VtkSceneController, SetVolumeColorScaleRebuildsCheckedVolume) {
|
|||
sc.volumeIds = {"ds1"};
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
c.setCheckedDatasets(voxelIds({"ds1"}));
|
||||
ASSERT_EQ(view.volumes, 1);
|
||||
const int removesBefore = view.removeCalls;
|
||||
|
||||
|
|
@ -330,7 +331,7 @@ TEST(VtkSceneController, SetVolumeColorScalePersistsAcrossRecheck) {
|
|||
sc.volumeIds = {"ds1"};
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"}); // 加载体(填充 volumeCache_)
|
||||
c.setCheckedDatasets(voxelIds({"ds1"})); // 加载体(填充 volumeCache_)
|
||||
ASSERT_EQ(view.lastVolumeScale.stopCount(), 2u); // 初始两段
|
||||
|
||||
core::ColorScale edited; // 编辑成三段
|
||||
|
|
@ -340,8 +341,8 @@ TEST(VtkSceneController, SetVolumeColorScalePersistsAcrossRecheck) {
|
|||
c.setVolumeColorScale("ds1", edited);
|
||||
ASSERT_EQ(view.lastVolumeScale.stopCount(), 3u);
|
||||
|
||||
c.setCheckedDatasets({}); // 取消勾选
|
||||
c.setCheckedDatasets({"ds1"}); // 再勾选 → 命中缓存(含编辑后色阶)
|
||||
c.setCheckedDatasets({}); // 取消勾选
|
||||
c.setCheckedDatasets(voxelIds({"ds1"})); // 再勾选 → 命中缓存(含编辑后色阶)
|
||||
EXPECT_EQ(view.lastVolumeScale.stopCount(), 3u);
|
||||
}
|
||||
|
||||
|
|
@ -409,146 +410,56 @@ TEST(VtkSceneController, ZoomAndFitForwarded) {
|
|||
EXPECT_EQ(view.fitCalls, 1);
|
||||
}
|
||||
|
||||
// ── 二维数据集视图:足迹平铺进 View3D ──
|
||||
|
||||
// 显式关闭摆放(0) → 勾选 2D 足迹不渲染(仅记录勾选)。
|
||||
TEST(VtkSceneController, TwoDPlacementOffDoesNotRender) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.set2DPlacement(0, 0.0); // 显式关闭
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
EXPECT_EQ(view.mapLines, 0);
|
||||
// ── 二维数据集(轨迹/足迹)经 plane2d 策略平铺进场景 ──
|
||||
// B2:去 col2D + setChecked2DDatasets/set2DPlacement 公有入口,2D 与 3D 合一经统一入口
|
||||
// setCheckedDatasets((dsId, typeId))。trajectory 描述符 → "plane2d" 策略 → add2DDatasetAsync。
|
||||
// 摆放暂固定默认(Z=0);置/底/自定义 + analysisMode 取景基线相关用例随旧入口移除(Phase E/F 重接后补)。
|
||||
namespace {
|
||||
IdType trajIds(std::initializer_list<std::string> ids) {
|
||||
IdType v;
|
||||
for (const auto& id : ids) v.push_back({id, "trajectory"}); // trajectory → renderStrategyId "plane2d"
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// 回归(足迹默认不渲染 bug):默认摆放=Z=0(1),与 2D视图下拉可见默认项一致 →
|
||||
// 仅勾选 2D 足迹(不手动调 set2DPlacement)即应在 View3D 渲染,worldZ=0。
|
||||
TEST(VtkSceneController, TwoDDefaultPlacementRendersAtZeroOnCheck) {
|
||||
// 勾选轨迹(plane2d 策略) → 1 条 mapLine,默认摆放 worldZ=0;不影响帘面/体素计数。
|
||||
TEST(VtkSceneController, TrajectoryRendersAsMapLineAtDefaultZero) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setChecked2DDatasets({"traj1"}); // 不调 set2DPlacement,依赖默认摆放
|
||||
EXPECT_EQ(view.mapLines, 1);
|
||||
EXPECT_DOUBLE_EQ(view.lastMapLineZ, 0.0);
|
||||
}
|
||||
|
||||
// 摆放 Z=0(1) + 勾选足迹 → 1 条 mapLine,worldZ=0;不影响帘面/体素计数。
|
||||
TEST(VtkSceneController, TwoDPlacementZeroAddsMapLine) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.set2DPlacement(1, 0.0); // Z=0
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
c.setCheckedDatasets(trajIds({"traj1"}));
|
||||
EXPECT_EQ(view.mapLines, 1);
|
||||
EXPECT_DOUBLE_EQ(view.lastMapLineZ, 0.0);
|
||||
EXPECT_EQ(view.curtains, 0);
|
||||
EXPECT_EQ(view.volumes, 0);
|
||||
}
|
||||
|
||||
// 顶部/底部摆放锚定真实地表高程:worldZ = zRefElev ± 偏移(而非世界 0 ± 偏移)。
|
||||
TEST(VtkSceneController, TwoDPlacementTopBottomAnchorToSurfaceElev) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
view.refElev = 1200.0; // 地表高程基准
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.set2DPlacement(2, 0.0); // 顶部
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
EXPECT_DOUBLE_EQ(view.lastMapLineZ, 1200.0 + 50.0); // 贴地表上方
|
||||
c.set2DPlacement(3, 0.0); // 底部
|
||||
EXPECT_DOUBLE_EQ(view.lastMapLineZ, 1200.0 - 50.0); // 地表下方
|
||||
}
|
||||
|
||||
// 取消勾选 2D 足迹 → 增量移除该足迹图元(不整场 clear)。
|
||||
TEST(VtkSceneController, TwoDUncheckRemovesMapLine) {
|
||||
// 取消勾选轨迹 → 增量移除该足迹图元(不整场 clear)。
|
||||
TEST(VtkSceneController, TrajectoryUncheckRemovesMapLine) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.set2DPlacement(1, 0.0);
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
c.setCheckedDatasets(trajIds({"traj1"}));
|
||||
ASSERT_EQ(view.mapLines, 1);
|
||||
const int clearsBefore = view.clears;
|
||||
|
||||
c.setChecked2DDatasets({}); // 取消勾选
|
||||
c.setCheckedDatasets({}); // 取消勾选
|
||||
EXPECT_EQ(view.mapLines, 0);
|
||||
EXPECT_EQ(view.clears, clearsBefore); // 增量移除,不整场 clear
|
||||
}
|
||||
|
||||
// 2D 足迹与 3D 帘面共存且独立:勾选剖面 + 足迹,各出各的图元,互不影响。
|
||||
TEST(VtkSceneController, TwoDFootprintCoexistsWith3DCurtain) {
|
||||
// 轨迹足迹与 3D 帘面经同一入口共存且独立:各出各的图元,取消足迹不影响帘面。
|
||||
TEST(VtkSceneController, TrajectoryCoexistsWith3DCurtain) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"prof1"}); // 3D 帘面
|
||||
c.set2DPlacement(1, 0.0);
|
||||
c.setChecked2DDatasets({"traj1"}); // 2D 足迹
|
||||
IdType both = curtainIds({"prof1"});
|
||||
both.push_back({"traj1", "trajectory"});
|
||||
c.setCheckedDatasets(both); // 帘面 + 足迹(统一入口并集)
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
EXPECT_EQ(view.mapLines, 1);
|
||||
|
||||
c.setChecked2DDatasets({}); // 取消足迹 → 帘面不受影响
|
||||
c.setCheckedDatasets(curtainIds({"prof1"})); // 仅留帘面 → 足迹移除
|
||||
EXPECT_EQ(view.mapLines, 0);
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
}
|
||||
|
||||
// 回归(BUG3:二维分析切回三维分析后,三维数据"不知生成到哪",要手动适配才定位):
|
||||
// 二维勾选足迹自动取景后 hadArrivedData_=true;切回三维前 onAnalysisModeChanged(false) 按"三维栏空"
|
||||
// 复位取景基线 → 勾选三维数据应自动取景(fitView),而非停在旧相机。
|
||||
TEST(VtkSceneController, ThreeDDataFitsAfterSwitchingBackFrom2D) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
|
||||
c.onAnalysisModeChanged(true); // 切到二维(2D 栏空 → 基线允许取景)
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
ASSERT_EQ(view.mapLines, 1);
|
||||
const int fitsAfter2D = view.fitCalls;
|
||||
EXPECT_GE(fitsAfter2D, 1); // 足迹首次到场已取景
|
||||
|
||||
c.onAnalysisModeChanged(false); // 切回三维(3D 栏空 → 基线允许取景)
|
||||
c.setCheckedDatasets({"prof1"});
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
EXPECT_GT(view.fitCalls, fitsAfter2D); // 三维数据到场自动取景(修复前不取景)
|
||||
}
|
||||
|
||||
// 回归(二维分析下已有隐藏 3D 数据时,勾选首条足迹也应取景;旧 wasEmpty 逻辑因 3D 非空而漏取景):
|
||||
TEST(VtkSceneController, TwoDFootprintFitsEvenWhenHidden3DExists) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"prof1"}); // 三维数据(取景一次)
|
||||
const int fitsAfter3D = view.fitCalls;
|
||||
|
||||
c.onAnalysisModeChanged(true); // 切到二维(2D 栏空 → 基线允许取景)
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
EXPECT_EQ(view.mapLines, 1);
|
||||
EXPECT_GT(view.fitCalls, fitsAfter3D); // 首条足迹取景(旧逻辑因有隐藏 3D 而漏)
|
||||
}
|
||||
|
||||
// 自定义摆放(4) → worldZ=customZ;改摆放重摆已勾选足迹(移除旧 + 按新 Z 重加)。
|
||||
TEST(VtkSceneController, TwoDPlacementCustomZAndReplace) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.set2DPlacement(4, 123.5); // 自定义 Z
|
||||
c.setChecked2DDatasets({"traj1"});
|
||||
ASSERT_EQ(view.mapLines, 1);
|
||||
EXPECT_DOUBLE_EQ(view.lastMapLineZ, 123.5);
|
||||
const int removesBefore = view.removeCalls;
|
||||
|
||||
c.set2DPlacement(4, 200.0); // 改自定义 Z → 重摆
|
||||
EXPECT_EQ(view.mapLines, 1); // 移除 1 + 新增 1 → 净计数不变
|
||||
EXPECT_EQ(view.removeCalls, removesBefore + 1); // 旧足迹被移除
|
||||
EXPECT_DOUBLE_EQ(view.lastMapLineZ, 200.0); // 新 Z 已下发
|
||||
}
|
||||
|
||||
// 摆放从关闭(0)切到 Z=0(1) → 已勾选但未渲染的足迹补画。
|
||||
TEST(VtkSceneController, TwoDPlacementOffToOnDrawsCheckedFootprint) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.set2DPlacement(0, 0.0); // 显式关闭(默认已是 Z=0)
|
||||
c.setChecked2DDatasets({"traj1"}); // 关闭态:仅记录
|
||||
ASSERT_EQ(view.mapLines, 0);
|
||||
|
||||
c.set2DPlacement(1, 0.0); // 切到 Z=0 → 补画
|
||||
EXPECT_EQ(view.mapLines, 1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue