feat/vtk-merged-dataset-column #10
|
|
@ -109,7 +109,6 @@ add_executable(geopro_desktop WIN32
|
|||
VolumeParamsDialog.cpp
|
||||
VolumePropertiesDialog.cpp
|
||||
Logging.cpp
|
||||
DatasetDimension.cpp
|
||||
DatasetCategory.cpp
|
||||
VtkViewToolbar.cpp
|
||||
AxesSettingsDialog.cpp
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include "repo/CategoryConfig.hpp"
|
||||
#include "repo/RepoTypes.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
struct CategoryBuckets {
|
||||
std::vector<std::vector<geopro::data::DsRow>> segments; // 与 categoryConfigs() 同序同长
|
||||
std::vector<std::vector<geopro::data::DsRow>> segments; // 与 categoryCatalog() 同序同长
|
||||
};
|
||||
|
||||
// 按 CategoryConfig 把 ds 分入大类段:先判 ddCode 白名单(三维体/切片),否则按 dsTypeCode 匹配;
|
||||
// 按 categoryCatalog() 把 ds 分入大类段:遍历目录,命中首个 classify(row)==true 的描述符即归入该段;
|
||||
// 不在表内的丢弃(接地电阻/原始数据/白化/坐标等)。保留原顺序。
|
||||
CategoryBuckets splitByCategory(const std::vector<geopro::data::DsRow>& rows);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
#include "DatasetDimension.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
namespace {
|
||||
// 与 LocalSample3dRepository::dimensionOf 同一映射(spec §6.1)。
|
||||
enum class Dim { D3, D2, Analysis, Other };
|
||||
Dim dimOf(const std::string& c) {
|
||||
if (c == "dd_voxel" || c == "dd_Structual3D" || c == "dd_Property3D" ||
|
||||
c == "dd_section" || c == "dd_inversion_data" || c == "dd_radar_3d")
|
||||
return Dim::D3;
|
||||
if (c == "dd_slice") return Dim::Analysis;
|
||||
if (c == "dd_trajectory_data") return Dim::D2;
|
||||
return Dim::Other;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DimBuckets splitByDimension(const std::vector<geopro::data::DsRow>& rows) {
|
||||
DimBuckets b;
|
||||
for (const auto& r : rows) {
|
||||
switch (dimOf(r.ddCode)) {
|
||||
case Dim::D3: b.dim3D.push_back(r); break;
|
||||
case Dim::D2: b.dim2D.push_back(r); break;
|
||||
case Dim::Analysis: b.analysis.push_back(r); break;
|
||||
case Dim::Other: break;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include "repo/RepoTypes.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
struct DimBuckets {
|
||||
std::vector<geopro::data::DsRow> dim3D;
|
||||
std::vector<geopro::data::DsRow> dim2D;
|
||||
std::vector<geopro::data::DsRow> analysis;
|
||||
};
|
||||
|
||||
// 按 ddCode 把 ds 分流到 三维数据集 / 二维数据集 / 三维分析 三栏。
|
||||
// Other 维度不入任何栏(保留原顺序)。
|
||||
DimBuckets splitByDimension(const std::vector<geopro::data::DsRow>& rows);
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -139,6 +139,10 @@ void TileBasemap::requestRender() {
|
|||
}
|
||||
|
||||
TileBasemap::~TileBasemap() {
|
||||
// 移除本实例所有已贴瓦片:多实例(每 2D 平面一份)动态建销时,析构须撤回瓦片,否则渲染器仍持引用、
|
||||
// 底图不随平面消失。共享 3D 底图存活至退出故旧码无此清理也无碍,但 per-plane 实例必须清。
|
||||
if (auto* ren = scene_.renderer())
|
||||
for (auto& kv : placed_) ren->RemoveViewProp(kv.second);
|
||||
if (styleObs_ && observer_) styleObs_->RemoveObserver(observer_);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "TileBasemap.hpp"
|
||||
#include "controller/DatasetRenderStrategy.hpp" // geopro::controller::IPlaneBasemap
|
||||
|
||||
namespace geopro::render { class Scene; }
|
||||
namespace geopro::core { class GeoLocalFrame; }
|
||||
class vtkRenderWindow;
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
// 2D 平面底图适配器:把 app 层 TileBasemap 适配到控制器层抽象 IPlaneBasemap,
|
||||
// 使 geopro_controller(仅链 geopro_data+Qt::Core) 不反依赖 app 层与 VTK(仿 I3dSceneView/VtkSceneView)。
|
||||
// main.cpp 经底图工厂按平面 z 造之;持 TileBasemap 值成员,随适配器析构而析构(移除瓦片→底图随平面消失)。
|
||||
class TileBasemapPlaneAdapter : public geopro::controller::IPlaneBasemap {
|
||||
public:
|
||||
TileBasemapPlaneAdapter(geopro::render::Scene& scene, vtkRenderWindow* rw,
|
||||
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double groundZ,
|
||||
std::function<double()> radiusProvider)
|
||||
: bm_(scene, rw, std::move(frame), nullptr, groundZ) {
|
||||
bm_.setDataRadiusProvider(std::move(radiusProvider));
|
||||
}
|
||||
void show(int kind) override {
|
||||
bm_.show(kind == 0 ? TileBasemap::Street : TileBasemap::Hidden);
|
||||
}
|
||||
void hide() override { bm_.hide(); }
|
||||
void setOpacity(double o) override { bm_.setOpacity(o); }
|
||||
|
||||
private:
|
||||
TileBasemap bm_;
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -93,7 +93,6 @@
|
|||
|
||||
#include "ApiClient.hpp"
|
||||
#include "AuthService.hpp"
|
||||
#include "DatasetDimension.hpp"
|
||||
#include "DatasetCategory.hpp"
|
||||
#include "repo/CategoryDescriptor.hpp"
|
||||
#include "Credential.hpp"
|
||||
|
|
@ -143,6 +142,7 @@
|
|||
#include "panels/DatasetAttrPanel.hpp"
|
||||
#include "panels/ObjectExceptionPanel.hpp"
|
||||
#include "TileBasemap.hpp"
|
||||
#include "TileBasemapPlaneAdapter.hpp"
|
||||
#include "panels/columns/ColumnDrawer.hpp"
|
||||
#include "panels/columns/CategoryAnalysisTab.hpp"
|
||||
#include "panels/columns/CategorySection.hpp"
|
||||
|
|
@ -1276,6 +1276,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// 2D 段「z 值」滑块 → 整体升降该 2D 类型平面(Plane2DRenderStrategy 重摆其全部足迹)。
|
||||
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::planeZChanged, sceneCtrl,
|
||||
&geopro::controller::VtkSceneController::setPlaneZ);
|
||||
// 2D 段「底图」弹窗 → 切该类型平面底图 矢量平面/无 + 透明度(Plane2DRenderStrategy 多实例底图)。
|
||||
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::basemapKindChanged, sceneCtrl,
|
||||
&geopro::controller::VtkSceneController::setBasemapKind);
|
||||
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::basemapOpacityChanged, sceneCtrl,
|
||||
&geopro::controller::VtkSceneController::setBasemapOpacity);
|
||||
// 反向 VTK→list:在 VTK 里点中/选中一张切片 → 在三维体段树里同步选中该切片行(②反向)。
|
||||
// 点空白/清选(dsId 空) → 一并清 VTK 异常高亮(否则取消选中后异常图形仍高亮,用户反馈)。
|
||||
interactionMgr->onSliceSelectionChanged = [drawer, sceneView, renderWindowPtr](
|
||||
|
|
@ -1322,6 +1327,16 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
[basemap](double o) { basemap->setOpacity(o); });
|
||||
// 底图最大范围按当前勾选剖面合并范围动态定(随增删自动伸缩);刷新时实时查询。
|
||||
basemap->setDataRadiusProvider([sceneView]() { return sceneView->dataHorizontalRadius(); });
|
||||
// F2:2D 平面底图工厂下发 → Plane2DRenderStrategy 据此为每个 2D 类型平面按需建/销平面矢量底图
|
||||
// (与共享 3D 底图同源 scene/渲染窗/frame/数据半径规则;随平面 z 升降重建、全消随平面消失)。
|
||||
// 工厂造 TileBasemapPlaneAdapter(app 层适配 IPlaneBasemap),使 geopro_controller 不反依赖 app/VTK。
|
||||
sceneCtrl->setPlaneBasemapFactory(
|
||||
[scene, renderWindowPtr, frame, sceneView](double groundZ)
|
||||
-> std::unique_ptr<geopro::controller::IPlaneBasemap> {
|
||||
return std::make_unique<geopro::app::TileBasemapPlaneAdapter>(
|
||||
*scene, renderWindowPtr, frame, groundZ,
|
||||
[sceneView]() { return sceneView->dataHorizontalRadius(); });
|
||||
});
|
||||
// 垂直夸张:地形须与剖面用同一 VE 才对齐(都按真实高程×VE)。单一来源 kVerticalExaggeration 下发
|
||||
// 控制器(上方)/底图;运行时 VE 改由坐标轴设置抽屉「应用」下发(旧三维数据集栏滑块已退役)。
|
||||
basemap->setVerticalExaggeration(kVerticalExaggeration);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ CategoryAnalysisTab::CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* d
|
|||
&CategoryAnalysisTab::anomalyVisibilityChanged);
|
||||
connect(sec, &CategorySection::datasetSelected, this, &CategoryAnalysisTab::datasetSelected);
|
||||
connect(sec, &CategorySection::planeZChanged, this, &CategoryAnalysisTab::planeZChanged);
|
||||
connect(sec, &CategorySection::basemapKindChanged, this,
|
||||
&CategoryAnalysisTab::basemapKindChanged);
|
||||
connect(sec, &CategorySection::basemapOpacityChanged, this,
|
||||
&CategoryAnalysisTab::basemapOpacityChanged);
|
||||
// #7:各段等分 stretch → 内容都少时四段平分高度填满面板(初始与 VTK 区等高、不出滚动条);
|
||||
// 某段内容增多时其最小高度(=内容总高)撑大,超出视口则由外层 QScrollArea 统一出纵向滚动条。
|
||||
col->addWidget(sec, 1);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ signals:
|
|||
void anomalyVisibilityChanged(const QString& dsId, bool vis);
|
||||
void datasetSelected(const QString& dsId, const QString& ddCode); // 树选中→VTK 高亮联动
|
||||
void planeZChanged(const QString& typeId, double z); // 2D 段 z 值滑块:整体升降该类型平面
|
||||
void basemapKindChanged(const QString& typeId, int kind); // 2D 段底图弹窗:矢量平面(0)/无(1)
|
||||
void basemapOpacityChanged(const QString& typeId, double o); // 2D 段底图弹窗:透明度[0,1]
|
||||
|
||||
private:
|
||||
void recomputeCheckedUnion();
|
||||
|
|
|
|||
|
|
@ -88,9 +88,7 @@ CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc,
|
|||
break;
|
||||
case geopro::data::OpKind::Basemap:
|
||||
acts.push_back({QStringLiteral("map"), QStringLiteral("底图"), {},
|
||||
[this](QToolButton*) { // Task F2 建底图 popup;当前先发请求信号
|
||||
emit basemapPopupRequested(QString::fromStdString(desc_.id));
|
||||
}});
|
||||
[this](QToolButton* host) { showBasemapPopup(host); }});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -500,4 +498,54 @@ void CategorySection::showPlaneZPopup(QToolButton* host) {
|
|||
menu.exec(host->mapToGlobal(QPoint(0, host->height())));
|
||||
}
|
||||
|
||||
void CategorySection::showBasemapPopup(QToolButton* host) {
|
||||
// 底图 popup(仿工具条底图控件):本类型平面底图 矢量平面(默认)/无 + 透明度滑块(0–100,默认 50)。
|
||||
// 类型切换为离散单次事件直发;透明度拖动逐步触发→单发 QTimer(150ms)防抖,停手后一次发射,免抖动重铺瓦片。
|
||||
QMenu menu(this);
|
||||
auto* wa = new QWidgetAction(&menu);
|
||||
auto* box = new QWidget(&menu);
|
||||
auto* lay = new QVBoxLayout(box);
|
||||
lay->setContentsMargins(space::kMd, space::kSm, space::kMd, space::kSm);
|
||||
lay->setSpacing(space::kXs);
|
||||
|
||||
auto* kindCombo = new QComboBox(box);
|
||||
kindCombo->addItem(QStringLiteral("矢量平面")); // index 0
|
||||
kindCombo->addItem(QStringLiteral("无")); // index 1
|
||||
kindCombo->setCurrentIndex(lastBasemapKind_);
|
||||
connect(kindCombo, &QComboBox::currentIndexChanged, this, [this](int idx) {
|
||||
lastBasemapKind_ = idx;
|
||||
emit basemapKindChanged(QString::fromStdString(desc_.id), idx);
|
||||
});
|
||||
|
||||
auto* lab = new QLabel(box);
|
||||
auto* sld = new QSlider(Qt::Horizontal, box);
|
||||
sld->setRange(0, 100);
|
||||
sld->setValue(lastBasemapOpacity_);
|
||||
sld->setMinimumWidth(160);
|
||||
sld->setToolTip(QStringLiteral("底图透明度"));
|
||||
auto syncLabel = [lab](int v) { lab->setText(QStringLiteral("透明度:%1%").arg(v)); };
|
||||
syncLabel(sld->value());
|
||||
if (!basemapOpacityTimer_) { // 定时器 parent=this,存活于 modal popup 之外,停手后安全发射终值
|
||||
basemapOpacityTimer_ = new QTimer(this);
|
||||
basemapOpacityTimer_->setSingleShot(true);
|
||||
basemapOpacityTimer_->setInterval(150);
|
||||
connect(basemapOpacityTimer_, &QTimer::timeout, this, [this]() {
|
||||
emit basemapOpacityChanged(QString::fromStdString(desc_.id), pendingBasemapOpacity_);
|
||||
});
|
||||
}
|
||||
connect(sld, &QSlider::valueChanged, this, [this, syncLabel](int v) {
|
||||
lastBasemapOpacity_ = v;
|
||||
syncLabel(v);
|
||||
pendingBasemapOpacity_ = v / 100.0;
|
||||
basemapOpacityTimer_->start(); // 重启防抖窗口:覆盖拖动、键盘、点轨——停手后一次性发射
|
||||
});
|
||||
|
||||
lay->addWidget(kindCombo);
|
||||
lay->addWidget(lab);
|
||||
lay->addWidget(sld);
|
||||
wa->setDefaultWidget(box);
|
||||
menu.addAction(wa);
|
||||
menu.exec(host->mapToGlobal(QPoint(0, host->height())));
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ signals:
|
|||
void collapsedChanged(); // 折叠/展开切换 → 外层 CategoryAnalysisTab 重排各段 stretch
|
||||
void generateVolumeRequested(const QString& dsTypeCode, const QStringList& sourceDsIds); // 段头「+新增三维体」(接收方按 sourceIds 解析类型)
|
||||
void planeZChanged(const QString& typeId, double z); // PlaneZ 滑块:整体升降该 2D 类型平面(z=绝对高程,米)
|
||||
void basemapPopupRequested(const QString& typeId); // Basemap 图标:弹底图选择 popup(Task F2 实现)
|
||||
void basemapKindChanged(const QString& typeId, int kind); // 底图弹窗:矢量平面(0)/无(1)
|
||||
void basemapOpacityChanged(const QString& typeId, double o); // 底图弹窗透明度滑块[0,1](防抖发射)
|
||||
void detailRequested(const QString& dsId, const QString& ddCode, const QString& name); // 双击/右键=详情
|
||||
void deleteDatasetRequested(const QString& dsId, const QString& ddCode); // 右键删除(切片/异常)
|
||||
// ── 三维体段右键操作(迁自旧 Column3DAnalysis,全接)──
|
||||
|
|
@ -71,6 +72,7 @@ signals:
|
|||
private:
|
||||
void showContextMenu(const QPoint& pos); // 段体树右键菜单(详情 + 删除)
|
||||
void showPlaneZPopup(QToolButton* host); // PlaneZ 图标:弹 z 值滑块 popup → planeZChanged
|
||||
void showBasemapPopup(QToolButton* host); // Basemap 图标:弹 矢量平面/无 + 透明度滑块 popup
|
||||
void rebuildList(); // 据 rows_(经装置/日期筛选)重建段体树并复原勾选
|
||||
void refreshArrayCombo(); // 据当前 rows_ 重填装置类型下拉项(经字典 value→中文)
|
||||
void emitChecked(); // 收集勾选 → checkedDatasetsChanged
|
||||
|
|
@ -94,6 +96,10 @@ private:
|
|||
double lastPlaneZ_ = 0.0; // 上次 z 值滑块设定的平面高程(重开 popup 时回显,无则 0)
|
||||
QTimer* planeZTimer_ = nullptr; // z 值滑块发射防抖:拖动/键盘/点轨期合并为停手后一次重摆(owned by this,存活于 popup 之外)
|
||||
double pendingPlaneZ_ = 0.0; // 防抖待发的平面 z(定时器到点时取此值发射 planeZChanged)
|
||||
int lastBasemapKind_ = 0; // 上次底图选择(0=矢量平面/1=无),重开 popup 时回显
|
||||
int lastBasemapOpacity_ = 50; // 上次底图透明度(0–100,重开 popup 回显;默认 50)
|
||||
QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(同 planeZ:停手后一次发射,免抖动重铺瓦片)
|
||||
double pendingBasemapOpacity_ = 0.5; // 防抖待发的底图透明度[0,1](定时器到点发射 basemapOpacityChanged)
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
namespace geopro::controller {
|
||||
|
||||
namespace {
|
||||
constexpr double kDefaultBasemapOpacity = 0.5; // 平面底图默认半透明(地下足迹可透过看)
|
||||
}
|
||||
|
||||
// 各策略委托回控制器既有渲染路径(友元访问其私有 addDatasetAsync/add2DDatasetAsync/view_)。
|
||||
// 增量 gen 沿用控制器当前 rebuildGeneration_(不自增,与并发增量互不作废)。
|
||||
void VolumeRenderStrategy::add(const std::string& /*typeId*/, const std::string& dsId) {
|
||||
|
|
@ -30,8 +34,21 @@ void Plane2DRenderStrategy::remove(const std::string& dsId) {
|
|||
planeReg_.onUnchecked(it->second, dsId); // 该类型成员集空时平面自动消失
|
||||
dsToType_.erase(it);
|
||||
}
|
||||
// 该类型全消(控制器活跃计数归零):此时 planeReg_.hasPlane==false。Phase F2 在此销毁平面底图。
|
||||
void Plane2DRenderStrategy::onTypeDeactivated(const std::string& /*typeId*/) {}
|
||||
// 该类型首勾(控制器活跃计数 0→1,先于 add 触发):建该类型平面矢量底图。
|
||||
// 注意 onTypeActivated 在 add 之前触发 → 此刻 planeReg_ 尚无该平面,createBasemap 以 zRefElev() 兜底
|
||||
// (即首勾 add 即将写入的平面 z,二者一致)。kind/opacity 复位为默认(矢量平面/0.5)。
|
||||
void Plane2DRenderStrategy::onTypeActivated(const std::string& typeId) {
|
||||
bmKind_[typeId] = 0;
|
||||
bmOpacity_[typeId] = kDefaultBasemapOpacity;
|
||||
createBasemap(typeId);
|
||||
}
|
||||
|
||||
// 该类型全消(控制器活跃计数 1→0):销毁该类型底图(析构移除瓦片→底图随平面一并消失),遗忘其 kind/opacity。
|
||||
void Plane2DRenderStrategy::onTypeDeactivated(const std::string& typeId) {
|
||||
bms_.erase(typeId);
|
||||
bmKind_.erase(typeId);
|
||||
bmOpacity_.erase(typeId);
|
||||
}
|
||||
|
||||
void Plane2DRenderStrategy::setPlaneZ(const std::string& typeId, double z) {
|
||||
planeReg_.setPlaneZ(typeId, z); // 平面 z 真源更新(类型不存在则无操作)
|
||||
|
|
@ -42,7 +59,38 @@ void Plane2DRenderStrategy::setPlaneZ(const std::string& typeId, double z) {
|
|||
ctrl_.view_.removeDataset(dsId);
|
||||
ctrl_.add2DDatasetAsync(dsId, ctrl_.rebuildGeneration_, z);
|
||||
}
|
||||
// 底图同步升降:销毁旧实例、按新平面 z 重建(复用当前 kind/opacity),简单可靠(无 setGroundZ 增量重铺)。
|
||||
if (bms_.count(typeId)) createBasemap(typeId);
|
||||
ctrl_.view_.renderIncremental();
|
||||
}
|
||||
|
||||
void Plane2DRenderStrategy::setBasemapKind(const std::string& typeId, int kind) {
|
||||
bmKind_[typeId] = kind;
|
||||
auto it = bms_.find(typeId);
|
||||
if (it == bms_.end()) return;
|
||||
if (kind == 0) it->second->show(0); // 矢量平面
|
||||
else it->second->hide(); // 无(保留实例,仅隐瓦片)
|
||||
}
|
||||
|
||||
void Plane2DRenderStrategy::setBasemapOpacity(const std::string& typeId, double o) {
|
||||
bmOpacity_[typeId] = o;
|
||||
auto it = bms_.find(typeId);
|
||||
if (it != bms_.end()) it->second->setOpacity(o);
|
||||
}
|
||||
|
||||
void Plane2DRenderStrategy::createBasemap(const std::string& typeId) {
|
||||
if (!basemapFactory_) return; // 工厂未注入(如纯逻辑单测,无 VTK) → 不建底图
|
||||
// 平面 z:已建平面取 planeReg_(setPlaneZ 重建走此);onTypeActivated 时平面尚未建 → 以 zRefElev() 兜底
|
||||
// (= add 即将写入的首勾平面 z,二者一致)。
|
||||
const double gz =
|
||||
planeReg_.hasPlane(typeId) ? planeReg_.planeZ(typeId) : ctrl_.view_.zRefElev();
|
||||
auto bm = basemapFactory_(gz);
|
||||
if (!bm) return;
|
||||
const int kind = bmKind_.count(typeId) ? bmKind_[typeId] : 0;
|
||||
const double op = bmOpacity_.count(typeId) ? bmOpacity_[typeId] : kDefaultBasemapOpacity;
|
||||
bm->setOpacity(op); // 先定透明度,再 show 套用到初次铺瓦
|
||||
if (kind == 0) bm->show(0); // 默认矢量平面;kind=1(无)则仅留实例
|
||||
bms_[typeId] = std::move(bm); // 替换旧实例 → 旧底图适配器析构移除其瓦片
|
||||
}
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
|
@ -9,6 +10,20 @@ namespace geopro::controller {
|
|||
|
||||
class VtkSceneController; // 策略委托回控制器既有渲染路径(add→addDatasetAsync/add2DDatasetAsync;remove→view.removeDataset)
|
||||
|
||||
// 平面底图抽象:控制器层不依赖 VTK/app(geopro_controller 仅链 geopro_data+Qt::Core)。
|
||||
// app 层(main.cpp)经工厂注入具体 TileBasemap 适配器,仿既有 I3dSceneView/VtkSceneView 边界,
|
||||
// 避免 geopro_controller 反向依赖 app 层与 VTK。
|
||||
class IPlaneBasemap {
|
||||
public:
|
||||
virtual ~IPlaneBasemap() = default;
|
||||
virtual void show(int kind) = 0; // 0=矢量平面(Street)/其它=无(hide)
|
||||
virtual void hide() = 0;
|
||||
virtual void setOpacity(double o) = 0; // 半透明度[0,1]
|
||||
};
|
||||
// 工厂:按平面 z 造一份平面底图(底图所需 scene/渲染窗/frame/数据半径规则由 app 闭包捕获)。
|
||||
// 未注入(空)则不建底图——便于无 VTK 的纯逻辑单测。
|
||||
using PlaneBasemapFactory = std::function<std::unique_ptr<IPlaneBasemap>(double groundZ)>;
|
||||
|
||||
class IDatasetRenderStrategy {
|
||||
public:
|
||||
virtual ~IDatasetRenderStrategy() = default;
|
||||
|
|
@ -59,13 +74,26 @@ 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;
|
||||
void onTypeDeactivated(const std::string& typeId) override;
|
||||
// 滑块整体升降该类型平面 z:更新 planeReg_ 后,对该类型已勾选足迹移除并按新 z 重摆。
|
||||
void onTypeActivated(const std::string& typeId) override; // 首勾:建该类型平面矢量底图
|
||||
void onTypeDeactivated(const std::string& typeId) override; // 全消:销毁该类型底图(瓦片随之消失)
|
||||
// 滑块整体升降该类型平面 z:更新 planeReg_ 后,对该类型已勾选足迹移除并按新 z 重摆 + 底图重建于新 z。
|
||||
void setPlaneZ(const std::string& typeId, double z);
|
||||
// 底图工厂注入(main.cpp 构造后一次性下发;未注入则底图建造静默跳过,便于纯逻辑单测)。
|
||||
void setBasemapFactory(PlaneBasemapFactory f) { basemapFactory_ = std::move(f); }
|
||||
void setBasemapKind(const std::string& typeId, int kind); // 0=矢量平面(show)/1=无(hide)
|
||||
void setBasemapOpacity(const std::string& typeId, double o); // 该类型底图半透明度[0,1]
|
||||
private:
|
||||
void createBasemap(const std::string& typeId); // 按当前 z/kind/opacity 建(或重建)该类型底图
|
||||
|
||||
VtkSceneController& ctrl_;
|
||||
PlaneZRegistry planeReg_; // 按类型的平面 z 生命周期
|
||||
std::map<std::string, std::string> dsToType_; // dsId→typeId(remove 只得 dsId,需自存反查)
|
||||
|
||||
// 每 2D 类型一份平面矢量底图(贴该类型平面 z);随平面建/销/升降。键=typeId。
|
||||
std::map<std::string, std::unique_ptr<IPlaneBasemap>> bms_;
|
||||
std::map<std::string, int> bmKind_; // 该类型底图选择(0=矢量平面/1=无),重建时复用
|
||||
std::map<std::string, double> bmOpacity_; // 该类型底图透明度,重建时复用
|
||||
PlaneBasemapFactory basemapFactory_; // app 注入的底图工厂(空=不建底图)
|
||||
};
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
|
|||
|
|
@ -32,6 +32,18 @@ void VtkSceneController::setPlaneZ(const QString& typeId, double z) {
|
|||
if (plane2d_) plane2d_->setPlaneZ(typeId.toStdString(), z);
|
||||
}
|
||||
|
||||
void VtkSceneController::setPlaneBasemapFactory(PlaneBasemapFactory factory) {
|
||||
if (plane2d_) plane2d_->setBasemapFactory(std::move(factory));
|
||||
}
|
||||
|
||||
void VtkSceneController::setBasemapKind(const QString& typeId, int kind) {
|
||||
if (plane2d_) plane2d_->setBasemapKind(typeId.toStdString(), kind);
|
||||
}
|
||||
|
||||
void VtkSceneController::setBasemapOpacity(const QString& typeId, double opacity) {
|
||||
if (plane2d_) plane2d_->setBasemapOpacity(typeId.toStdString(), opacity);
|
||||
}
|
||||
|
||||
IDatasetRenderStrategy* VtkSceneController::strategyForType(const std::string& typeId) const {
|
||||
for (const auto& d : geopro::data::categoryCatalog())
|
||||
if (d.id == typeId) return registry_.get(d.renderStrategyId);
|
||||
|
|
@ -316,12 +328,6 @@ void VtkSceneController::zoomIn() { view_.zoom(1.2); }
|
|||
void VtkSceneController::zoomOut() { view_.zoom(1.0 / 1.2); }
|
||||
void VtkSceneController::fit() { view_.fitView(); }
|
||||
|
||||
const geopro::core::Grid& VtkSceneController::grid(const std::string& dsId) {
|
||||
auto it = gridCache_.find(dsId);
|
||||
if (it == gridCache_.end()) it = gridCache_.emplace(dsId, dsRepo_.loadGrid(dsId)).first;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const geopro::core::ColorScale& VtkSceneController::colorScale(const std::string& dsId) {
|
||||
auto it = colorScaleCache_.find(dsId);
|
||||
if (it == colorScaleCache_.end())
|
||||
|
|
@ -331,7 +337,6 @@ const geopro::core::ColorScale& VtkSceneController::colorScale(const std::string
|
|||
|
||||
void VtkSceneController::rebuildInternal() {
|
||||
const unsigned long long gen = ++rebuildGeneration_; // 自增:作废此前所有在途增量回调
|
||||
const bool is2D = (mode_ == ViewMode::Map2D);
|
||||
|
||||
view_.clear(); // 移除全部数据图元(保留底图);frame 重锚标志复位
|
||||
loadingDs_.clear(); // 旧在途加载随之作废(回调按 gen 丢弃)
|
||||
|
|
@ -343,34 +348,30 @@ void VtkSceneController::rebuildInternal() {
|
|||
|
||||
// 坏 dsId(loadGrid/loadColorScale 抛异常)= best-effort 跳过:emit loadFailed 但不中断。
|
||||
try {
|
||||
if (is2D) {
|
||||
for (const auto& [dsId, typeId] : checked_) view_.addSurveyLine(grid(dsId));
|
||||
} else {
|
||||
// 回调用 QPointer<self> 守对象存活 + gen 守数据新鲜:迟到回调若已析构/作废则丢弃。
|
||||
QPointer<VtkSceneController> self(this);
|
||||
if (showTerrain_) {
|
||||
sceneRepo_.loadTerrainPaths(
|
||||
[self, gen](data::TerrainPaths p) {
|
||||
if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃
|
||||
self->view_.addTerrain(std::move(p));
|
||||
self->onDatasetArrived();
|
||||
},
|
||||
[self, gen](const std::string& m) {
|
||||
if (!self || gen != self->rebuildGeneration_) return;
|
||||
emit self->loadFailed(QString::fromStdString(m));
|
||||
});
|
||||
}
|
||||
// 全量重建:clear 已移除全部图元,据统一勾选集经各 ds 类型策略重放 add(不动活跃计数:
|
||||
// 已在 setCheckedDatasets 计入;策略 add 内部转调 addDatasetAsync/add2DDatasetAsync)。
|
||||
for (const auto& [dsId, typeId] : checked_)
|
||||
if (auto* s = strategyForType(typeId)) s->add(typeId, dsId);
|
||||
// 回调用 QPointer<self> 守对象存活 + gen 守数据新鲜:迟到回调若已析构/作废则丢弃。
|
||||
QPointer<VtkSceneController> self(this);
|
||||
if (showTerrain_) {
|
||||
sceneRepo_.loadTerrainPaths(
|
||||
[self, gen](data::TerrainPaths p) {
|
||||
if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃
|
||||
self->view_.addTerrain(std::move(p));
|
||||
self->onDatasetArrived();
|
||||
},
|
||||
[self, gen](const std::string& m) {
|
||||
if (!self || gen != self->rebuildGeneration_) return;
|
||||
emit self->loadFailed(QString::fromStdString(m));
|
||||
});
|
||||
}
|
||||
// 全量重建: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()));
|
||||
}
|
||||
|
||||
// 保留相机重建(改VE):不 ResetCamera,原地按新夸张重绘。
|
||||
view_.render(is2D, /*resetCamera=*/!preserveCameraOnRebuild_);
|
||||
// 保留相机重建(改VE):不 ResetCamera,原地按新夸张重绘。视图恒三维(is2D=false)。
|
||||
view_.render(/*is2D=*/false, /*resetCamera=*/!preserveCameraOnRebuild_);
|
||||
}
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ namespace geopro::controller {
|
|||
|
||||
class DatasetViewState; // 跨视图共享色阶真源(统一同步机制)
|
||||
|
||||
// 中央视图模式:二维地图(俯视测线)/ 三维视图(帘面/体素/地形)。
|
||||
enum class ViewMode { Map2D, View3D };
|
||||
// 中央视图模式:固定三维视图(帘面/体素/地形)。旧二维俯视测线(Map2D)路径已退役(main 恒 View3D)。
|
||||
enum class ViewMode { View3D };
|
||||
|
||||
// 三维图层("视图详情"浮层勾选)。
|
||||
enum class SceneLayer { Curtain, Voxel, Terrain };
|
||||
|
|
@ -49,6 +49,10 @@ public:
|
|||
// 构造后由 main.cpp 注入一次。
|
||||
void setViewState(DatasetViewState* state);
|
||||
|
||||
// 注入 2D 平面底图工厂(app 层闭包捕获 scene/渲染窗/frame/数据半径规则,造 TileBasemap 适配器):
|
||||
// 转交 Plane2DRenderStrategy,供各类型平面按需建底图。main.cpp 构造后一次性下发。
|
||||
void setPlaneBasemapFactory(PlaneBasemapFactory factory);
|
||||
|
||||
public:
|
||||
// 勾选并集统一入口(取代旧 setCheckedDatasets(QStringList)/setChecked2DDatasets):
|
||||
// 每项 = (dsId, typeId=描述符 id)。diff vs 上次后按 catalog[typeId].renderStrategyId 派给策略
|
||||
|
|
@ -78,6 +82,9 @@ public slots:
|
|||
const AxisRangeCfg& z);
|
||||
// 2D 段「z 值」滑块:整体升降某 2D 类型平面(含其上全部已勾选足迹)。转交 Plane2DRenderStrategy。
|
||||
void setPlaneZ(const QString& typeId, double z);
|
||||
// 2D 段「底图」弹窗:切该类型平面底图 矢量平面(0)/无(1) + 透明度[0,1]。转交 Plane2DRenderStrategy。
|
||||
void setBasemapKind(const QString& typeId, int kind);
|
||||
void setBasemapOpacity(const QString& typeId, double opacity);
|
||||
|
||||
void applyView(ViewDir dir); // 6 向快捷视图
|
||||
void zoomIn(); // Zoom In (×1.2)
|
||||
|
|
@ -119,7 +126,7 @@ private:
|
|||
// 渲染策略注册表(构造时注册 volume/curtain/plane2d 三策略,各持本控制器引用)。
|
||||
RenderStrategyRegistry registry_;
|
||||
Plane2DRenderStrategy* plane2d_ = nullptr; // registry_ 中 plane2d 策略的裸指针(setPlaneZ 免下转型)
|
||||
ViewMode mode_ = ViewMode::Map2D;
|
||||
ViewMode mode_ = ViewMode::View3D;
|
||||
bool showCurtain_ = true;
|
||||
bool showVoxel_ = false;
|
||||
bool showTerrain_ = false;
|
||||
|
|
@ -135,7 +142,6 @@ private:
|
|||
QPointer<DatasetViewState> state_; // 跨视图色阶真源(注入;编辑/加载/重着色都经它;QPointer 防悬挂)
|
||||
|
||||
// 缓存(按 dsId):避免重复读盘/插值。
|
||||
std::map<std::string, geopro::core::Grid> gridCache_;
|
||||
std::map<std::string, geopro::core::ColorScale> colorScaleCache_;
|
||||
// 帘面源网格缓存:帘面重着色需 grid 重建 addCurtain(loadSection 的 s.grid 不在 gridCache_)。
|
||||
std::map<std::string, geopro::core::Grid> sectionGridCache_;
|
||||
|
|
@ -154,7 +160,6 @@ private:
|
|||
// 正在加载的 ds:防重复勾选竞态重复请求;全量重建时清空。
|
||||
std::set<std::string> loadingDs_;
|
||||
|
||||
const geopro::core::Grid& grid(const std::string& dsId);
|
||||
const geopro::core::ColorScale& colorScale(const std::string& dsId);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
// 一个数据类型大类段的配置(spec §5)。识别键二选一:dsTypeCode 优先;ddCode 用于三维体/切片。
|
||||
struct CategorySpec {
|
||||
std::string id; // 段稳定 id
|
||||
std::string title; // 段标题(UI 显示)
|
||||
std::string dsTypeCode; // 主识别键(空=不按 dsTypeCode)
|
||||
std::string ddCode; // 次识别键(dd_voxel/dd_slice;空=不按 ddCode)
|
||||
bool canGenerateVolume; // 段内是否提供「生成三维体」入口(仅反演类)
|
||||
bool hasArrayTypeFilter; // 段头是否显示装置类型筛选(仅 ERT 类)
|
||||
};
|
||||
|
||||
// 5 段固定有序(spec §5 表)。
|
||||
inline const std::vector<CategorySpec>& categoryConfigs() {
|
||||
static const std::vector<CategorySpec> kCfg = {
|
||||
{"resistivity", "电阻率数据", "ERT platform inversion data", "", true, true},
|
||||
{"apparent", "视电阻率数据", "visual resistivity data", "", true, true},
|
||||
{"transient", "瞬变电磁数据", "DD TRANSIENT ELECTROMAGNETIC INVERSION", "", true, false},
|
||||
{"voxel", "三维体", "", "dd_voxel", false, false},
|
||||
// 切片不单列段——挂在三维体段「体→切片/异常」三级树下(spec §8 修订)。
|
||||
};
|
||||
return kCfg;
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -172,11 +172,6 @@ target_sources(geopro_tests PRIVATE
|
|||
app/test_color_scale_io.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/app/ColorScaleIO.cpp
|
||||
)
|
||||
# 维度过滤纯函数(splitByDimension: ddCode -> 三维/二维/分析三栏,无 Qt/VTK 依赖)。
|
||||
target_sources(geopro_tests PRIVATE
|
||||
app/test_dataset_dimension.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/app/DatasetDimension.cpp
|
||||
)
|
||||
# 大类分类纯函数(splitByCategory: dsTypeCode/ddCode -> 5 个数据类型大类段,无 Qt/VTK 依赖)。
|
||||
target_sources(geopro_tests PRIVATE
|
||||
app/test_dataset_category.cpp
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "DatasetDimension.hpp"
|
||||
#include "repo/RepoTypes.hpp"
|
||||
|
||||
using geopro::data::DsRow;
|
||||
using geopro::app::splitByDimension;
|
||||
using geopro::app::DimBuckets;
|
||||
|
||||
static DsRow row(const char* id, const char* ddCode) {
|
||||
DsRow r; r.id = id; r.ddCode = ddCode; return r;
|
||||
}
|
||||
|
||||
TEST(DatasetDimension, SplitsByDdCode) {
|
||||
std::vector<DsRow> in{
|
||||
row("a", "dd_section"), // 3D
|
||||
row("b", "dd_voxel"), // 3D
|
||||
row("f", "dd_radar_3d"), // 3D(三维雷达体,spec §6.1)
|
||||
row("c", "dd_trajectory_data"), // 2D
|
||||
row("d", "dd_slice"), // Analysis
|
||||
row("e", "dd_unknownxyz"), // Other -> not in any bucket
|
||||
};
|
||||
DimBuckets b = splitByDimension(in);
|
||||
ASSERT_EQ(b.dim3D.size(), 3u);
|
||||
EXPECT_EQ(b.dim3D[0].id, "a");
|
||||
EXPECT_EQ(b.dim3D[1].id, "b");
|
||||
EXPECT_EQ(b.dim3D[2].id, "f");
|
||||
ASSERT_EQ(b.dim2D.size(), 1u);
|
||||
EXPECT_EQ(b.dim2D[0].id, "c");
|
||||
ASSERT_EQ(b.analysis.size(), 1u);
|
||||
EXPECT_EQ(b.analysis[0].id, "d");
|
||||
}
|
||||
|
||||
TEST(DatasetDimension, EmptyInput) {
|
||||
DimBuckets b = splitByDimension({});
|
||||
EXPECT_TRUE(b.dim3D.empty());
|
||||
EXPECT_TRUE(b.dim2D.empty());
|
||||
EXPECT_TRUE(b.analysis.empty());
|
||||
}
|
||||
Loading…
Reference in New Issue