feat/vtk-merged-dataset-column #10

Merged
gaozheng merged 40 commits from feat/vtk-merged-dataset-column into main 2026-07-01 14:48:38 +08:00
18 changed files with 243 additions and 168 deletions
Showing only changes of commit e8df41b9f2 - Show all commits

View File

@ -109,7 +109,6 @@ add_executable(geopro_desktop WIN32
VolumeParamsDialog.cpp
VolumePropertiesDialog.cpp
Logging.cpp
DatasetDimension.cpp
DatasetCategory.cpp
VtkViewToolbar.cpp
AxesSettingsDialog.cpp

View File

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

View File

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

View File

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

View File

@ -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_);
}

View File

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

View File

@ -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(); });
// F22D 平面底图工厂下发 → 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);

View File

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

View File

@ -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();

View File

@ -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仿工具条底图控件本类型平面底图 矢量平面(默认)/无 + 透明度滑块(0100默认 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

View File

@ -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 图标:弹底图选择 popupTask 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; // 上次底图透明度0100重开 popup 回显;默认 50
QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(同 planeZ停手后一次发射免抖动重铺瓦片
double pendingBasemapOpacity_ = 0.5; // 防抖待发的底图透明度[0,1](定时器到点发射 basemapOpacityChanged
};
} // namespace geopro::app

View File

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

View File

@ -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/add2DDatasetAsyncremove→view.removeDataset
// 平面底图抽象:控制器层不依赖 VTK/appgeopro_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

View File

@ -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() {
// 坏 dsIdloadGrid/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

View File

@ -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 重建 addCurtainloadSection 的 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);
};

View File

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

View File

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

View File

@ -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());
}