feat(vtk): P1 复活中央渲染 — VtkSceneController + I3dSceneRepository + Scene加vtkProp
复活被 6241eb3 摘除的中央 VTK 数据驱动渲染:
- Scene 新增 addViewProp(vtkProp*):体绘制 vtkVolume(非 vtkActor)经此进场
- I3dSceneRepository(异步回调契约) + LocalSample3dRepository:dimensionOf 维度映射 /
loadVolume→VolumeGrid(std::array 去裸数组) / loadTerrainPaths;data 层零 VTK 依赖
- VtkSceneController(QObject) 取代 main.cpp 死掉的 rebuildCentral lambda + 裸 show* 标志:
勾选数据集/视图模式/图层/比例 → 经仓储取 core::* → I3dSceneView 重建场景;
QPointer+generation 守异步回调生命周期与新鲜度;inRebuild_ 避免同步路径双 render
- I3dSceneView 抽象解耦编排与 VTK(VtkSceneView 真实现 + 测试 fake)
- 删除被取代的 CentralScene;main.cpp 接线 对象勾选/2D-3D/图层/主题(主题 context 用 sceneCtrl 防悬垂)
- 新增测试 14(Scene/3d-repo/VtkSceneController),ctest 172/172 全绿
构建基建修复(本就潜在缺陷,任何 clean 构建/新人 checkout 都会撞):
- vcpkg.json 加 builtin-baseline:新版 vcpkg manifest 模式必需,否则全新 checkout 无法 configure
- build.bat 修 vswhere(VS2026 预览 -latest 恒空 → -all -prerelease -requires VC.Tools)
+ 括号块内路径变量加引号(防 Program Files (x86) 的 ) 提前闭合)
This commit is contained in:
parent
918088e67a
commit
0f521c5b24
11
build.bat
11
build.bat
|
|
@ -25,14 +25,17 @@ if not exist "%VSWHERE%" (
|
|||
echo [build] vswhere not found. Open "x64 Native Tools Command Prompt for VS" and build manually.
|
||||
exit /b 1
|
||||
)
|
||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -property installationPath`) do set "VSPATH=%%i"
|
||||
if not defined VSPATH ( echo [build] Visual Studio not found. & exit /b 1 )
|
||||
REM -all -prerelease for VS2026 preview (note: -latest yields empty on this preview, and
|
||||
REM -products * would pull in the bundled BuildTools whose vcpkg/env breaks our preset);
|
||||
REM -requires ensures the C++ toolset is present. Multiple installs -> last one wins.
|
||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -all -prerelease -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
|
||||
if not defined VSPATH ( echo [build] Visual Studio with C++ toolset not found. Install the VS Desktop C++ workload. & exit /b 1 )
|
||||
|
||||
set "VCVARS=%VSPATH%\VC\Auxiliary\Build\vcvars64.bat"
|
||||
set "CMAKE=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
|
||||
set "CTEST=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe"
|
||||
if not exist "%VCVARS%" ( echo [build] vcvars64.bat not found: %VCVARS% & exit /b 1 )
|
||||
if not exist "%CMAKE%" ( echo [build] cmake not found: %CMAKE% & exit /b 1 )
|
||||
if not exist "%VCVARS%" ( echo [build] vcvars64.bat not found: "%VCVARS%" & exit /b 1 )
|
||||
if not exist "%CMAKE%" ( echo [build] cmake not found: "%CMAKE%" & exit /b 1 )
|
||||
|
||||
REM --- activate MSVC environment (cl / link / include / lib) ---
|
||||
call "%VCVARS%" >nul
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ add_executable(geopro_desktop WIN32
|
|||
panels/LoadingOverlay.cpp
|
||||
panels/DatasetDetailPage.cpp
|
||||
panels/DatasetDetailPanel.cpp
|
||||
CentralScene.cpp
|
||||
VtkSceneView.cpp
|
||||
ProjectListDialog.cpp
|
||||
ObjectFormDialog.cpp
|
||||
ImportDatasetDialog.cpp
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
#include "CentralScene.hpp"
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderer.h>
|
||||
|
||||
#include "CameraPreset.hpp"
|
||||
#include "Scene.hpp"
|
||||
#include "Theme.hpp"
|
||||
#include "actors/CurtainActor.hpp"
|
||||
#include "actors/MapLineActor.hpp"
|
||||
#include "geo/GeoLocalFrame.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
void rebuildCentralScene(geopro::render::Scene& scene, vtkRenderer* renderer,
|
||||
vtkRenderWindow* renderWindow, ViewMode mode,
|
||||
const std::vector<SectionInput>& sections, bool showCurtain,
|
||||
const geopro::core::GeoLocalFrame& frame, double verticalExaggeration) {
|
||||
scene.clear();
|
||||
const bool is2D = (mode == ViewMode::Map2D);
|
||||
(void)is2D;
|
||||
// 背景永远深色(规范§0.5 视图区常深,不随明暗切换),让色阶数据更突出。
|
||||
double bgR, bgG, bgB;
|
||||
geopro::app::vtkBackground(bgR, bgG, bgB);
|
||||
renderer->SetBackground(bgR, bgG, bgB);
|
||||
|
||||
for (const auto& s : sections) {
|
||||
if (is2D) {
|
||||
auto line = geopro::render::buildSurveyLine(s.grid, frame);
|
||||
if (line) scene.addActor(line);
|
||||
} else if (showCurtain) {
|
||||
auto curtain = geopro::render::buildCurtain(s.grid, s.colorScale, frame);
|
||||
if (curtain) {
|
||||
curtain->SetScale(1.0, 1.0, verticalExaggeration); // 纵向夸张成墙
|
||||
scene.addActor(curtain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is2D)
|
||||
geopro::render::applyTop2D(renderer);
|
||||
else
|
||||
geopro::render::applyFree3D(renderer);
|
||||
renderer->ResetCamera();
|
||||
renderWindow->Render();
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
|
||||
namespace geopro::core { class GeoLocalFrame; }
|
||||
namespace geopro::render { class Scene; }
|
||||
class vtkRenderer;
|
||||
class vtkRenderWindow;
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
// 中央视图模式:二维地图(测线红线俯视)/ 三维视图(断面墙)。
|
||||
enum class ViewMode { Map2D, View3D };
|
||||
|
||||
// 一个待渲染剖面:grid(2D 测线 / 3D 帘面都用)+ colorScale(3D 帘面上色)。
|
||||
struct SectionInput {
|
||||
geopro::core::Grid grid;
|
||||
geopro::core::ColorScale colorScale;
|
||||
};
|
||||
|
||||
// 中央场景重建(脱离对象树,按显式 sections 渲染):
|
||||
// 2D = 每个 section 的 buildSurveyLine;3D = 每个 section 的 buildCurtain(受 showCurtain)。
|
||||
// 下一轮接真实 DS:构建 sections 后调用本函数即可,render 层零改动。
|
||||
void rebuildCentralScene(geopro::render::Scene& scene, vtkRenderer* renderer,
|
||||
vtkRenderWindow* renderWindow, ViewMode mode,
|
||||
const std::vector<SectionInput>& sections, bool showCurtain,
|
||||
const geopro::core::GeoLocalFrame& frame, double verticalExaggeration);
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#include "VtkSceneView.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkVolume.h>
|
||||
|
||||
#include "CameraPreset.hpp"
|
||||
#include "Scene.hpp"
|
||||
#include "Theme.hpp"
|
||||
#include "actors/CurtainActor.hpp"
|
||||
#include "actors/MapLineActor.hpp"
|
||||
#include "actors/TerrainActor.hpp"
|
||||
#include "actors/VoxelActor.hpp"
|
||||
#include "geo/GeoLocalFrame.hpp"
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
VtkSceneView::VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
|
||||
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev)
|
||||
: scene_(scene),
|
||||
renderWindow_(renderWindow),
|
||||
frame_(std::move(frame)),
|
||||
zRefElev_(zRefElev) {}
|
||||
|
||||
void VtkSceneView::clear() { scene_.clear(); }
|
||||
|
||||
void VtkSceneView::setVerticalExaggeration(double ve) { verticalExaggeration_ = ve; }
|
||||
|
||||
void VtkSceneView::addSurveyLine(const geopro::core::Grid& grid) {
|
||||
auto line = geopro::render::buildSurveyLine(grid, *frame_);
|
||||
if (line) scene_.addActor(line);
|
||||
}
|
||||
|
||||
void VtkSceneView::addCurtain(const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) {
|
||||
auto curtain = geopro::render::buildCurtain(grid, cs, *frame_);
|
||||
if (curtain) {
|
||||
curtain->SetScale(1.0, 1.0, verticalExaggeration_); // 纵向夸张成墙
|
||||
scene_.addActor(curtain);
|
||||
}
|
||||
}
|
||||
|
||||
void VtkSceneView::addVolume(const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) {
|
||||
// 纵向夸张烤进 image 的 z 原点/间距(与帘面 SetScale 同倍,保证纵向一致)。
|
||||
auto volume = geopro::render::buildVoxel(
|
||||
vol.vol, cs, vol.origin[0], vol.origin[1], vol.origin[2] * verticalExaggeration_,
|
||||
vol.spacing[0], vol.spacing[1], vol.spacing[2] * verticalExaggeration_, vol.vmin, vol.vmax);
|
||||
if (volume) scene_.addViewProp(volume);
|
||||
}
|
||||
|
||||
void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) {
|
||||
auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_,
|
||||
verticalExaggeration_);
|
||||
if (terrain) scene_.addActor(terrain);
|
||||
}
|
||||
|
||||
void VtkSceneView::render(bool is2D) {
|
||||
// 视图区背景永远深色(规范 §0.5:不随明暗切换),让色阶数据更突出。
|
||||
double bgR, bgG, bgB;
|
||||
geopro::app::vtkBackground(bgR, bgG, bgB);
|
||||
scene_.renderer()->SetBackground(bgR, bgG, bgB);
|
||||
if (is2D)
|
||||
geopro::render::applyTop2D(scene_.renderer());
|
||||
else
|
||||
geopro::render::applyFree3D(scene_.renderer());
|
||||
scene_.renderer()->ResetCamera();
|
||||
if (renderWindow_) renderWindow_->Render();
|
||||
}
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
#include "I3dSceneView.hpp"
|
||||
|
||||
namespace geopro::core { class GeoLocalFrame; }
|
||||
namespace geopro::render { class Scene; }
|
||||
class vtkRenderer;
|
||||
class vtkRenderWindow;
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
// I3dSceneView 的真实实现:把编排层的"加图元"指令翻译为 render actor + Scene 调用。
|
||||
// 持有 Scene / renderer / renderWindow(非拥有)+ 共享 GeoLocalFrame(多视图空间配准)。
|
||||
// 纵向夸张统一作用:帘面/地形 actor SetScale(1,1,VE),体素 z 原点/间距烤入 VE。
|
||||
// render 层零业务:actor 只吃 core::*,本类负责装配。
|
||||
class VtkSceneView : public geopro::controller::I3dSceneView {
|
||||
public:
|
||||
// 入参生命周期须覆盖本对象(由调用方保证)。zRefElev:地形 z 基准(测线地表高程)。
|
||||
VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
|
||||
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev);
|
||||
|
||||
void clear() override;
|
||||
void setVerticalExaggeration(double ve) override;
|
||||
void addSurveyLine(const geopro::core::Grid& grid) override;
|
||||
void addCurtain(const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) override;
|
||||
void addVolume(const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) override;
|
||||
void addTerrain(const geopro::data::TerrainPaths& paths) override;
|
||||
void render(bool is2D) override;
|
||||
|
||||
private:
|
||||
geopro::render::Scene& scene_;
|
||||
vtkRenderWindow* renderWindow_;
|
||||
std::shared_ptr<geopro::core::GeoLocalFrame> frame_;
|
||||
double zRefElev_;
|
||||
double verticalExaggeration_ = 2.0;
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
120
src/app/main.cpp
120
src/app/main.cpp
|
|
@ -78,6 +78,7 @@
|
|||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/LocalSampleRepository.hpp"
|
||||
#include "repo/LocalSample3dRepository.hpp"
|
||||
|
||||
#include "ApiClient.hpp"
|
||||
#include "AuthService.hpp"
|
||||
|
|
@ -88,11 +89,12 @@
|
|||
#include "Theme.hpp"
|
||||
#include "SettingsDialog.hpp"
|
||||
#include "TopBar.hpp"
|
||||
#include "CentralScene.hpp"
|
||||
#include "ProjectListDialog.hpp"
|
||||
#include "ObjectFormDialog.hpp"
|
||||
#include "ImportDatasetDialog.hpp"
|
||||
#include "WorkbenchNavController.hpp"
|
||||
#include "VtkSceneController.hpp"
|
||||
#include "VtkSceneView.hpp"
|
||||
#include "api/NavRequest.hpp"
|
||||
#include "api/NavLoads.hpp"
|
||||
#include "DatasetDetailController.hpp"
|
||||
|
|
@ -138,7 +140,6 @@
|
|||
#include <vtkCamera.h>
|
||||
#include <vtkCameraInterpolator.h>
|
||||
#include <vtkGenericOpenGLRenderWindow.h>
|
||||
#include <vtkImagePlaneWidget.h>
|
||||
#include <vtkLookupTable.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
|
|
@ -199,9 +200,6 @@ double median(std::vector<double> v)
|
|||
return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]);
|
||||
}
|
||||
|
||||
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
|
||||
using geopro::app::ViewMode;
|
||||
|
||||
// 纵向夸张倍数(Z 基准统一,M-3):全项目共用同一倍数,使 帘面(z) / 体素 / 切片 /
|
||||
// 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。
|
||||
// 单一可调常量:要整体调纵向观感改这一处即可。
|
||||
|
|
@ -231,29 +229,36 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// Scene 非 QObject:堆分配,用 widget 销毁信号清理(widget 随 window 销毁)。
|
||||
auto* scene = new geopro::render::Scene();
|
||||
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
||||
QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; });
|
||||
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
||||
vtkWidget->setRenderWindow(renderWindow);
|
||||
renderWindow->AddRenderer(scene->renderer());
|
||||
|
||||
vtkRenderer* rendererPtr = scene->renderer();
|
||||
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
|
||||
|
||||
// 当前视图模式(全局共享,切视图/勾选时据此重建内容)。默认二维地图。
|
||||
auto viewMode = std::make_shared<ViewMode>(ViewMode::Map2D);
|
||||
// 中央渲染编排(VtkSceneController + VtkSceneView,取代旧 rebuildCentral lambda 与裸 show* 标志)。
|
||||
// 3D 场景仓储用 LocalSample3dRepository(本期样本驱动;接口异步,将来换 Api 实现不动上层)。
|
||||
// 视图(VtkSceneView)非 QObject、控制器/3D 仓储亦然:随 scene 一并在 vtkWidget 销毁时清理。
|
||||
auto* scene3dRepo = new geopro::data::LocalSample3dRepository(repo, kProjectCrs, lat0, lon0);
|
||||
auto* sceneView = new geopro::app::VtkSceneView(*scene, renderWindowPtr,
|
||||
frame, refElev);
|
||||
auto* sceneCtrl = new geopro::controller::VtkSceneController(repo, *scene3dRepo, *sceneView,
|
||||
vtkWidget);
|
||||
sceneCtrl->setVerticalExaggeration(kVerticalExaggeration);
|
||||
// 非 QObject 堆对象统一在此清理,按构造逆序:sceneView(持 scene&) → scene3dRepo → scene。
|
||||
// (sceneCtrl 是 vtkWidget 的 QObject 子对象,由 Qt 在 destroyed 前先析构,不再触发信号回灌。)
|
||||
QObject::connect(vtkWidget, &QObject::destroyed, [scene, scene3dRepo, sceneView]() {
|
||||
delete sceneView;
|
||||
delete scene3dRepo;
|
||||
delete scene;
|
||||
});
|
||||
|
||||
// 三维图层显隐(由「视图详情」浮层控制)+ 项目 CRS→WGS84(体素配准)。
|
||||
auto showCurtain = std::make_shared<bool>(true); // 帘面,默认显示
|
||||
auto showVoxel = std::make_shared<bool>(false); // 体素,默认关
|
||||
auto showTerrain = std::make_shared<bool>(false); // 地形(DEM+影像),默认关
|
||||
auto showSlice = std::make_shared<bool>(false); // dd_slice 交互切片,默认关
|
||||
// 持久的切片 widget(挂 interactor,跨重建保活;rebuildCentral 据条件创建/拆除)。
|
||||
auto slicePlane = std::make_shared<vtkSmartPointer<vtkImagePlaneWidget>>();
|
||||
std::shared_ptr<geopro::core::CrsTransform> crs; // PROJ 失败→空→体素层无效(不崩)
|
||||
// PROJ 可用性(体素/地形/切片层都需配准):失败则浮层相应勾选禁用并提示。
|
||||
bool crsAvailable = false;
|
||||
try {
|
||||
crs = std::make_shared<geopro::core::CrsTransform>(kProjectCrs, kWgs84);
|
||||
geopro::core::CrsTransform probe(kProjectCrs, kWgs84);
|
||||
crsAvailable = true;
|
||||
} catch (const std::exception&) {
|
||||
crs.reset();
|
||||
crsAvailable = false;
|
||||
}
|
||||
|
||||
// 停靠系统配置(必须在 CDockManager 构造前设置):对齐原型——面板固定、
|
||||
|
|
@ -345,17 +350,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
chkTerrain->setChecked(false);
|
||||
auto* chkSlice = new QCheckBox(QStringLiteral("切片(dd_slice)"));
|
||||
chkSlice->setChecked(false);
|
||||
if (!crs) { // PROJ 不可用 → 体素/切片/地形层(都需配准)禁用并提示
|
||||
if (!crsAvailable) { // PROJ 不可用 → 体素/地形层(都需配准)禁用并提示
|
||||
const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用");
|
||||
chkVoxel->setEnabled(false); chkVoxel->setToolTip(tip);
|
||||
chkTerrain->setEnabled(false); chkTerrain->setToolTip(tip);
|
||||
chkSlice->setEnabled(false); chkSlice->setToolTip(tip);
|
||||
}
|
||||
// 本轮中央不接真实派生层:体素/切片/地形勾选置灰,待下一轮接入对应数据源。
|
||||
for (QCheckBox* c : {chkVoxel, chkSlice, chkTerrain}) {
|
||||
c->setEnabled(false);
|
||||
c->setToolTip(QStringLiteral("(下一轮接入真实数据源)"));
|
||||
}
|
||||
// 切片(dd_slice)交互切片留待 P3:本轮禁用。
|
||||
chkSlice->setEnabled(false);
|
||||
chkSlice->setToolTip(QStringLiteral("(切片交互 P3 接入)"));
|
||||
layerLayout->addWidget(layerTitle);
|
||||
layerLayout->addWidget(chkCurtain);
|
||||
layerLayout->addWidget(chkVoxel);
|
||||
|
|
@ -503,13 +505,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
};
|
||||
hideDockTitleBars();
|
||||
|
||||
// 中央编排已解耦到 CentralScene::rebuildCentralScene(数据驱动)。本轮空 sections → 空背景占位。
|
||||
// 下一轮:用真实 DS 数据构建 sections 调同一 helper 即复活。
|
||||
auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, showCurtain, frame]() {
|
||||
geopro::app::rebuildCentralScene(*scene, rendererPtr, renderWindowPtr, *viewMode,
|
||||
std::vector<geopro::app::SectionInput>{}, *showCurtain,
|
||||
*frame, kVerticalExaggeration);
|
||||
};
|
||||
// 中央渲染由 sceneCtrl(VtkSceneController)驱动:勾选对象/2D-3D切换/图层勾选/主题 → 重建场景。
|
||||
// (旧 rebuildCentral lambda + 裸 show* 标志已由控制器取代。)
|
||||
|
||||
// ── 单击左下数据列表的采集批次(DS) → 属性表单 + 聚焦详情已开页 ──
|
||||
QObject::connect(datasetList, &QTreeWidget::itemClicked, datasetList,
|
||||
|
|
@ -580,51 +577,44 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
}
|
||||
};
|
||||
|
||||
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 重建内容 + 图层浮层显隐 ──
|
||||
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 控制器重建 + 图层浮层显隐 ──
|
||||
using geopro::controller::SceneLayer;
|
||||
using CtrlViewMode = geopro::controller::ViewMode;
|
||||
QObject::connect(act2D, &QAbstractButton::clicked, vtkWidget,
|
||||
[viewMode, rebuildCentral, showLayerPanel]() {
|
||||
*viewMode = ViewMode::Map2D;
|
||||
[sceneCtrl, showLayerPanel]() {
|
||||
showLayerPanel(false);
|
||||
rebuildCentral();
|
||||
sceneCtrl->setViewMode(CtrlViewMode::Map2D);
|
||||
});
|
||||
QObject::connect(act3D, &QAbstractButton::clicked, vtkWidget,
|
||||
[viewMode, rebuildCentral, showLayerPanel]() {
|
||||
*viewMode = ViewMode::View3D;
|
||||
[sceneCtrl, showLayerPanel]() {
|
||||
showLayerPanel(true);
|
||||
rebuildCentral();
|
||||
sceneCtrl->setViewMode(CtrlViewMode::View3D);
|
||||
});
|
||||
|
||||
// ──「视图详情」图层勾选 → 更新图层显隐 → 重建中央 ──
|
||||
// ──「视图详情」图层勾选 → 控制器更新图层 → 重建中央 ──
|
||||
QObject::connect(chkCurtain, &QCheckBox::toggled, vtkWidget,
|
||||
[showCurtain, rebuildCentral](bool on) {
|
||||
*showCurtain = on;
|
||||
rebuildCentral();
|
||||
});
|
||||
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Curtain, on); });
|
||||
QObject::connect(chkVoxel, &QCheckBox::toggled, vtkWidget,
|
||||
[showVoxel, rebuildCentral](bool on) {
|
||||
*showVoxel = on;
|
||||
rebuildCentral();
|
||||
});
|
||||
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Voxel, on); });
|
||||
QObject::connect(chkTerrain, &QCheckBox::toggled, vtkWidget,
|
||||
[showTerrain, rebuildCentral](bool on) {
|
||||
*showTerrain = on;
|
||||
rebuildCentral();
|
||||
});
|
||||
QObject::connect(chkSlice, &QCheckBox::toggled, vtkWidget,
|
||||
[showSlice, rebuildCentral](bool on) {
|
||||
*showSlice = on;
|
||||
rebuildCentral();
|
||||
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Terrain, on); });
|
||||
|
||||
// ── 左上对象树勾选 → 渲染勾选数据集(本期样本驱动:任意勾选 → 样本 ds "grid1",空 → 清场)──
|
||||
// 真实接 Api 时改为把勾选 TM 映射到其 ds 维度过滤后的真实 dsId 列表(spec §6.1/§8)。
|
||||
QObject::connect(objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, sceneCtrl,
|
||||
[sceneCtrl](const QStringList& tmIds) {
|
||||
sceneCtrl->setCheckedDatasets(tmIds.isEmpty() ? QStringList{}
|
||||
: QStringList{QStringLiteral("grid1")});
|
||||
});
|
||||
|
||||
// ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。
|
||||
rebuildCentral();
|
||||
// ── 启动:建立一次中央视图(默认 2D,无勾选 → 空场景 + 背景)。
|
||||
sceneCtrl->setViewMode(CtrlViewMode::Map2D);
|
||||
|
||||
// VTK 背景随主题切换:直接重跑 rebuildCentral(走完整渲染路径、末尾必 Render,
|
||||
// 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window,
|
||||
[rebuildCentral]() {
|
||||
rebuildCentral();
|
||||
});
|
||||
// VTK 背景随主题切换:控制器重渲染(走完整渲染路径、末尾必 Render)。
|
||||
// context 用 sceneCtrl(非 window):ThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开,
|
||||
// 否则 window 析构期间 sceneCtrl(其孙级子对象)已销毁、主题异步变化会触悬垂指针。
|
||||
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, sceneCtrl,
|
||||
[sceneCtrl]() { sceneCtrl->rebuild(); });
|
||||
|
||||
// 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备),
|
||||
// 下=工具条(工作空间切换 + 项目 + 帮助/通知/设置 + 用户)。纵向堆叠后挂到主窗口顶部。
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
find_package(Qt6 COMPONENTS Core REQUIRED)
|
||||
add_library(geopro_controller STATIC
|
||||
WorkbenchNavController.cpp
|
||||
DatasetDetailController.cpp)
|
||||
DatasetDetailController.cpp
|
||||
VtkSceneController.cpp)
|
||||
target_include_directories(geopro_controller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(geopro_controller PUBLIC geopro_data Qt6::Core)
|
||||
target_compile_features(geopro_controller PUBLIC cxx_std_17)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/I3dSceneRepository.hpp"
|
||||
|
||||
namespace geopro::controller {
|
||||
|
||||
// 三维场景视图抽象(编排层与 VTK 渲染解耦的缝):
|
||||
// VtkSceneController 只发出"清场 / 加某类图元 / 提交渲染"指令,不认 vtkActor/vtkVolume;
|
||||
// 真实实现(VtkSceneView)调 render actor + Scene;测试用 fake 记录调用断言编排。
|
||||
// verticalExaggeration 由视图统一作用于 3D 图元(actor SetScale(1,1,VE) / image z 烤入)。
|
||||
class I3dSceneView {
|
||||
public:
|
||||
virtual ~I3dSceneView() = default;
|
||||
|
||||
virtual void clear() = 0;
|
||||
virtual void setVerticalExaggeration(double ve) = 0;
|
||||
|
||||
// 2D:俯视测线红线(z=0)。
|
||||
virtual void addSurveyLine(const geopro::core::Grid& grid) = 0;
|
||||
// 3D:竖直帘面(grid + colorScale 着色)。
|
||||
virtual void addCurtain(const geopro::core::Grid& grid,
|
||||
const geopro::core::ColorScale& cs) = 0;
|
||||
// 3D:体绘制(IDW 体素 + colorScale)。
|
||||
virtual void addVolume(const geopro::data::VolumeGrid& vol,
|
||||
const geopro::core::ColorScale& cs) = 0;
|
||||
// 3D:DEM 地形 + 影像纹理。
|
||||
virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0;
|
||||
|
||||
// 应用相机预设(2D 俯视 / 3D 自由)并提交渲染。
|
||||
virtual void render(bool is2D) = 0;
|
||||
};
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#include "VtkSceneController.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
#include "I3dSceneView.hpp"
|
||||
#include "repo/IDatasetRepository.hpp"
|
||||
|
||||
namespace geopro::controller {
|
||||
|
||||
VtkSceneController::VtkSceneController(data::IDatasetRepository& dsRepo,
|
||||
data::I3dSceneRepository& sceneRepo, I3dSceneView& view,
|
||||
QObject* parent)
|
||||
: QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {}
|
||||
|
||||
void VtkSceneController::setCheckedDatasets(const QStringList& dsIds) {
|
||||
checkedDs_.clear();
|
||||
checkedDs_.reserve(static_cast<std::size_t>(dsIds.size()));
|
||||
for (const QString& id : dsIds) checkedDs_.push_back(id.toStdString());
|
||||
rebuildInternal();
|
||||
}
|
||||
|
||||
void VtkSceneController::setViewMode(ViewMode mode) {
|
||||
mode_ = mode;
|
||||
rebuildInternal();
|
||||
}
|
||||
|
||||
void VtkSceneController::setLayer(SceneLayer layer, bool on) {
|
||||
switch (layer) {
|
||||
case SceneLayer::Curtain: showCurtain_ = on; break;
|
||||
case SceneLayer::Voxel: showVoxel_ = on; break;
|
||||
case SceneLayer::Terrain: showTerrain_ = on; break;
|
||||
}
|
||||
rebuildInternal();
|
||||
}
|
||||
|
||||
void VtkSceneController::setVerticalExaggeration(double ve) {
|
||||
verticalExaggeration_ = ve;
|
||||
rebuildInternal();
|
||||
}
|
||||
|
||||
void VtkSceneController::rebuild() { rebuildInternal(); }
|
||||
|
||||
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())
|
||||
it = colorScaleCache_.emplace(dsId, dsRepo_.loadColorScale(dsId)).first;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void VtkSceneController::rebuildInternal() {
|
||||
const unsigned long long gen = ++rebuildGeneration_;
|
||||
const bool is2D = (mode_ == ViewMode::Map2D);
|
||||
|
||||
view_.clear();
|
||||
view_.setVerticalExaggeration(verticalExaggeration_);
|
||||
|
||||
inRebuild_ = true;
|
||||
// 坏 dsId(loadGrid/loadColorScale 抛异常)= best-effort 跳过:emit loadFailed 但不中断,
|
||||
// 其余勾选数据集照常渲染(非 fail-fast)。
|
||||
try {
|
||||
if (is2D) {
|
||||
for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId));
|
||||
} else {
|
||||
// 回调用 QPointer<self> 守对象存活(控制器是 QObject)+ gen 守数据新鲜:
|
||||
// 将来 Api 实现在网络线程迟到回调时,self 已析构则直接丢弃,不触 dangling。
|
||||
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));
|
||||
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
|
||||
},
|
||||
[self, gen](const std::string& m) {
|
||||
if (!self || gen != self->rebuildGeneration_) return;
|
||||
emit self->loadFailed(QString::fromStdString(m));
|
||||
});
|
||||
}
|
||||
if (showCurtain_) {
|
||||
for (const auto& dsId : checkedDs_) view_.addCurtain(grid(dsId), colorScale(dsId));
|
||||
}
|
||||
if (showVoxel_) {
|
||||
for (const auto& dsId : checkedDs_) {
|
||||
auto cached = volumeCache_.find(dsId);
|
||||
if (cached != volumeCache_.end()) {
|
||||
view_.addVolume(cached->second, colorScale(dsId));
|
||||
continue;
|
||||
}
|
||||
sceneRepo_.loadVolume(
|
||||
dsId,
|
||||
[self, gen, dsId](data::VolumeGrid g) {
|
||||
if (!self) return; // 控制器已析构:丢弃
|
||||
if (gen != self->rebuildGeneration_) return; // 迟到回灌:丢弃
|
||||
auto it = self->volumeCache_.emplace(dsId, std::move(g)).first;
|
||||
self->view_.addVolume(it->second, self->colorScale(dsId));
|
||||
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
|
||||
},
|
||||
[self, gen](const std::string& m) {
|
||||
if (!self || gen != self->rebuildGeneration_) return;
|
||||
emit self->loadFailed(QString::fromStdString(m));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
emit loadFailed(QString::fromStdString(e.what()));
|
||||
}
|
||||
|
||||
inRebuild_ = false;
|
||||
view_.render(is2D);
|
||||
}
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/I3dSceneRepository.hpp"
|
||||
|
||||
namespace geopro::data {
|
||||
class IDatasetRepository;
|
||||
}
|
||||
|
||||
namespace geopro::controller {
|
||||
|
||||
class I3dSceneView;
|
||||
|
||||
// 中央视图模式:二维地图(俯视测线)/ 三维视图(帘面/体素/地形)。
|
||||
enum class ViewMode { Map2D, View3D };
|
||||
|
||||
// 三维图层("视图详情"浮层勾选)。
|
||||
enum class SceneLayer { Curtain, Voxel, Terrain };
|
||||
|
||||
// 中央 VTK 渲染编排(spec §8):聚合 勾选数据集 + 视图模式 + 图层开关 + 纵向比例,
|
||||
// 经仓储取 core::* 数据,命令 I3dSceneView 重建场景。取代 main.cpp 的 rebuildCentral lambda。
|
||||
// 异步:经 I3dSceneRepository 回调取体素/地形(回调内置幂请求标记防迟到回灌)。
|
||||
// 缓存:Grid / VolumeGrid 按 dsId 缓存,避免重复取数。
|
||||
// 不持有 widget;不认 vtkActor/vtkVolume(全交给 I3dSceneView)。
|
||||
class VtkSceneController : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VtkSceneController(data::IDatasetRepository& dsRepo, data::I3dSceneRepository& sceneRepo,
|
||||
I3dSceneView& view, QObject* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setCheckedDatasets(const QStringList& dsIds);
|
||||
void setViewMode(ViewMode mode);
|
||||
void setLayer(SceneLayer layer, bool on);
|
||||
void setVerticalExaggeration(double ve);
|
||||
void rebuild(); // 主题切换等外部触发的重渲染
|
||||
|
||||
signals:
|
||||
void loadFailed(const QString& message);
|
||||
|
||||
private:
|
||||
void rebuildInternal();
|
||||
|
||||
data::IDatasetRepository& dsRepo_;
|
||||
data::I3dSceneRepository& sceneRepo_;
|
||||
I3dSceneView& view_;
|
||||
|
||||
std::vector<std::string> checkedDs_;
|
||||
ViewMode mode_ = ViewMode::Map2D;
|
||||
bool showCurtain_ = true;
|
||||
bool showVoxel_ = false;
|
||||
bool showTerrain_ = false;
|
||||
double verticalExaggeration_ = 2.0;
|
||||
|
||||
// 缓存(按 dsId):避免重复读盘/插值。
|
||||
std::map<std::string, geopro::core::Grid> gridCache_;
|
||||
std::map<std::string, geopro::core::ColorScale> colorScaleCache_;
|
||||
std::map<std::string, data::VolumeGrid> volumeCache_;
|
||||
|
||||
// 异步回灌防护:每次 rebuild 自增,回调比对丢弃迟到结果。
|
||||
unsigned long long rebuildGeneration_ = 0;
|
||||
// rebuild 进行中标志:同步回调(LocalSample)在 rebuild 内立即触发时跳过自身 render,
|
||||
// 由 rebuildInternal 末尾统一 render 覆盖(避免双重 ResetCamera/Render);
|
||||
// 真异步回调迟到时 inRebuild_ 已 false → 自行 render 追加。
|
||||
bool inRebuild_ = false;
|
||||
|
||||
const geopro::core::Grid& grid(const std::string& dsId);
|
||||
const geopro::core::ColorScale& colorScale(const std::string& dsId);
|
||||
};
|
||||
|
||||
} // namespace geopro::controller
|
||||
|
|
@ -3,6 +3,7 @@ find_package(Qt6 COMPONENTS Core REQUIRED)
|
|||
add_library(geopro_data STATIC
|
||||
parse/SampleParsers.cpp
|
||||
repo/LocalSampleRepository.cpp
|
||||
repo/LocalSample3dRepository.cpp
|
||||
dto/NavDto.cpp
|
||||
dto/DatasetChartDto.cpp
|
||||
dto/MeasurementDto.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/RepoTypes.hpp"
|
||||
|
||||
namespace geopro::data {
|
||||
|
||||
// ds 维度属性(由 ds 类型/ddCode 决定,spec §6.1)。三栏列表筛选用。
|
||||
enum class DsDimension { Dim2D, Dim3D, Analysis3D, Other };
|
||||
|
||||
// 三维体模型数据:规则标量体 + 世界系原点/间距 + 值域(去裸 double[],用 std::array,spec §6.2)。
|
||||
struct VolumeGrid {
|
||||
geopro::core::ScalarVolume vol{0, 0, 0};
|
||||
std::array<double, 3> origin{{0.0, 0.0, 0.0}}; // ox, oy, oz(世界米)
|
||||
std::array<double, 3> spacing{{0.0, 0.0, 0.0}}; // dx, dy, dz
|
||||
double vmin = 0.0, vmax = 0.0;
|
||||
bool valid() const { return vol.nx() > 0 && vol.ny() > 0 && vol.nz() > 0 && vmax > vmin; }
|
||||
};
|
||||
|
||||
// DEM/影像 GeoTIFF 绝对路径(供 render::buildTerrain 经 GDAL 读,spec §6.2)。
|
||||
struct TerrainPaths {
|
||||
std::string demPath, imagePath;
|
||||
};
|
||||
|
||||
// 三维场景仓储抽象(异步,spec §6 评审 HIGH)。
|
||||
// 取数方法走回调 std::function(LocalSample 本地数据同步算好后直接回调;
|
||||
// 将来 Api3dRepository 在网络完成时回调,上层不变)。
|
||||
// **契约:onOk/onErr 必须在主(GUI)线程调用**——上层(VtkSceneController)回调内直接操作
|
||||
// 场景/发 Qt 信号,依赖主线程亲和;Api 实现若在工作线程完成须 post 回主线程再回调。
|
||||
// dimensionOf 是同步纯函数(无 I/O,只做类型→维度映射)。
|
||||
// 切片/异常/任务等签名本期不在接口内(留 P3/P4)。
|
||||
class I3dSceneRepository {
|
||||
public:
|
||||
using OnError = std::function<void(const std::string& message)>;
|
||||
|
||||
virtual ~I3dSceneRepository() = default;
|
||||
|
||||
// 同步纯函数:ds 类型 → 维度(spec §6.1 映射表)。
|
||||
virtual DsDimension dimensionOf(const DsRow& ds) const = 0;
|
||||
|
||||
// 异步:加载三维体模型(成功回调 VolumeGrid,失败回调消息)。
|
||||
virtual void loadVolume(const std::string& dsId,
|
||||
std::function<void(VolumeGrid)> onOk, OnError onErr) = 0;
|
||||
|
||||
// 异步:加载地形 DEM/影像路径。
|
||||
virtual void loadTerrainPaths(std::function<void(TerrainPaths)> onOk, OnError onErr) = 0;
|
||||
};
|
||||
|
||||
} // namespace geopro::data
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
#include "repo/LocalSample3dRepository.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "algo/IdwInterpolator.hpp"
|
||||
#include "geo/CrsTransform.hpp"
|
||||
#include "geo/GeoLocalFrame.hpp"
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/LocalSampleRepository.hpp"
|
||||
|
||||
namespace geopro::data {
|
||||
|
||||
using geopro::core::ColorScale;
|
||||
using geopro::core::CrsTransform;
|
||||
using geopro::core::GeoLocalFrame;
|
||||
using geopro::core::GridSpec;
|
||||
using geopro::core::IdwInterpolator;
|
||||
using geopro::core::PointSet;
|
||||
using geopro::core::ScalarVolume;
|
||||
using geopro::core::ScatterField;
|
||||
|
||||
namespace {
|
||||
|
||||
// 与 render::VoxelFromScatters 的默认参数同口径(保持渲染/切片纵向一致)。
|
||||
// TODO(P2/P3): 与 render::buildVoxelFromScatters 的 cellXY/cellZ/power/maxDist 默认值重复,
|
||||
// 宜把"散点→配准→GridSpec→IDW→ScalarVolume"提到 core::algo 共享,避免单方调参静默不一致。
|
||||
constexpr double kCellXY = 1.0;
|
||||
constexpr double kCellZ = 0.5;
|
||||
constexpr double kPower = 2.0;
|
||||
constexpr double kMaxDist = 4.0;
|
||||
constexpr int kMaxDim = 400;
|
||||
constexpr const char* kWgs84 = "EPSG:4326";
|
||||
|
||||
int clampDim(double ext, double cell) {
|
||||
int n = static_cast<int>(ext / cell) + 1;
|
||||
if (n < 1) n = 1;
|
||||
if (n > kMaxDim) n = kMaxDim;
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocalSample3dRepository::LocalSample3dRepository(LocalSampleRepository& base, std::string projectCrs,
|
||||
double baseLat, double baseLon)
|
||||
: base_(base), projectCrs_(std::move(projectCrs)), baseLat_(baseLat), baseLon_(baseLon) {}
|
||||
|
||||
DsDimension LocalSample3dRepository::dimensionOf(const DsRow& ds) const {
|
||||
const std::string& c = ds.ddCode;
|
||||
// 真三维体 / 体素 / 帘面(dd_section/反演剖面摆成竖直帘面)入三维数据集。
|
||||
if (c == "dd_voxel" || c == "dd_Structual3D" || c == "dd_Property3D" || c == "dd_section" ||
|
||||
c == "dd_inversion_data") {
|
||||
return DsDimension::Dim3D;
|
||||
}
|
||||
// 切片:三维分析栏。
|
||||
if (c == "dd_slice") return DsDimension::Analysis3D;
|
||||
// 轨迹:二维数据集。
|
||||
if (c == "dd_trajectory_data") return DsDimension::Dim2D;
|
||||
return DsDimension::Other;
|
||||
}
|
||||
|
||||
void LocalSample3dRepository::loadVolume(const std::string& /*dsId*/,
|
||||
std::function<void(VolumeGrid)> onOk, OnError onErr) {
|
||||
// P1 样本:dsId 暂未使用,固定读同一组交叉剖面散点→体素(真实 Api 实现按 dsId 取)。
|
||||
try {
|
||||
// 1) 读两条交叉剖面散点 + 色阶;配准到世界局部米 + 深度,组装 IDW 输入点集。
|
||||
const std::vector<ScatterField> profiles = base_.loadVoxelScatters();
|
||||
const CrsTransform crs(projectCrs_, kWgs84);
|
||||
const GeoLocalFrame frame(baseLat_, baseLon_);
|
||||
|
||||
PointSet pts;
|
||||
for (const auto& s : profiles) {
|
||||
const std::size_t n = s.v.size();
|
||||
if (s.projX.size() < n || s.projY.size() < n || s.y.size() < n) continue;
|
||||
for (std::size_t i = 0; i < n; ++i) {
|
||||
const auto ll = crs.forward(s.projX[i], s.projY[i]); // (lon, lat)
|
||||
const auto local = frame.toLocal(ll.y, ll.x); // (x East, y North) 米
|
||||
pts.x.push_back(local.x);
|
||||
pts.y.push_back(local.y);
|
||||
pts.z.push_back(-s.y[i]); // 深度向下:z 取负
|
||||
pts.v.push_back(s.v[i]);
|
||||
}
|
||||
}
|
||||
if (pts.v.empty()) {
|
||||
onErr("LocalSample3dRepository: no voxel points after registration");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) 点集包络 → GridSpec(角点对齐)。
|
||||
double minx = pts.x[0], maxx = pts.x[0];
|
||||
double miny = pts.y[0], maxy = pts.y[0];
|
||||
double minz = pts.z[0], maxz = pts.z[0];
|
||||
for (std::size_t i = 1; i < pts.v.size(); ++i) {
|
||||
minx = std::min(minx, pts.x[i]); maxx = std::max(maxx, pts.x[i]);
|
||||
miny = std::min(miny, pts.y[i]); maxy = std::max(maxy, pts.y[i]);
|
||||
minz = std::min(minz, pts.z[i]); maxz = std::max(maxz, pts.z[i]);
|
||||
}
|
||||
|
||||
GridSpec spec{};
|
||||
spec.ox = minx; spec.oy = miny; spec.oz = minz;
|
||||
spec.dx = kCellXY; spec.dy = kCellXY; spec.dz = kCellZ;
|
||||
spec.nx = clampDim(maxx - minx, kCellXY);
|
||||
spec.ny = clampDim(maxy - miny, kCellXY);
|
||||
spec.nz = clampDim(maxz - minz, kCellZ);
|
||||
spec.power = kPower;
|
||||
spec.maxDist = kMaxDist;
|
||||
|
||||
// 3) IDW → ScalarVolume(maxDist 外 NaN 留空)。
|
||||
const IdwInterpolator idw;
|
||||
ScalarVolume vol = idw.interpolate(pts, spec);
|
||||
|
||||
// 4) 值域:优先 colorBar 真实分段值,否则数据实测。
|
||||
double vmin, vmax;
|
||||
ColorScale cs;
|
||||
try {
|
||||
cs = base_.loadScatterColorScale("grid1");
|
||||
} catch (const std::exception&) {
|
||||
// 色阶缺失 → 退化为数据实测范围。
|
||||
}
|
||||
const std::vector<double> stops = cs.stopValues();
|
||||
if (stops.size() >= 2) {
|
||||
vmin = stops.front(); vmax = stops.back();
|
||||
} else {
|
||||
vmin = std::numeric_limits<double>::infinity();
|
||||
vmax = -std::numeric_limits<double>::infinity();
|
||||
for (double v : vol.data()) {
|
||||
if (std::isnan(v)) continue;
|
||||
vmin = std::min(vmin, v); vmax = std::max(vmax, v);
|
||||
}
|
||||
if (!(vmin < vmax)) { vmin = 0.0; vmax = 1.0; }
|
||||
}
|
||||
|
||||
VolumeGrid out{std::move(vol),
|
||||
{{spec.ox, spec.oy, spec.oz}},
|
||||
{{spec.dx, spec.dy, spec.dz}},
|
||||
vmin, vmax};
|
||||
onOk(std::move(out));
|
||||
} catch (const std::exception& e) {
|
||||
onErr(std::string("LocalSample3dRepository::loadVolume: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSample3dRepository::loadTerrainPaths(std::function<void(TerrainPaths)> onOk,
|
||||
OnError onErr) {
|
||||
try {
|
||||
TerrainPaths p{base_.demPath(), base_.imagePath()};
|
||||
onOk(std::move(p));
|
||||
} catch (const std::exception& e) {
|
||||
onErr(std::string("LocalSample3dRepository::loadTerrainPaths: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace geopro::data
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "repo/I3dSceneRepository.hpp"
|
||||
|
||||
namespace geopro::data {
|
||||
|
||||
class LocalSampleRepository;
|
||||
|
||||
// 本地样本三维场景仓储(spec §6):组合 LocalSampleRepository。
|
||||
// loadVolume:读两条交叉剖面散点 → 项目 CRS→WGS84→GeoLocalFrame 配准 → IDW → VolumeGrid(纯 core/data,无 VTK)。
|
||||
// loadTerrainPaths:直透 LocalSampleRepository 的 demPath/imagePath。
|
||||
// dimensionOf:按 ddCode 内置映射表(同步纯函数)。
|
||||
// 本地数据同步算好后直接回调(异步壳:接口异步,本实现内联完成)。
|
||||
class LocalSample3dRepository : public I3dSceneRepository {
|
||||
public:
|
||||
// base 生命周期须覆盖本对象(由调用方保证);projectCrs 为项目 CRS(如 "EPSG:4547")。
|
||||
// baseLat/baseLon 为全项目共享 GeoLocalFrame 原点(与帘面/地图同系,保证空间配准)。
|
||||
LocalSample3dRepository(LocalSampleRepository& base, std::string projectCrs,
|
||||
double baseLat, double baseLon);
|
||||
|
||||
DsDimension dimensionOf(const DsRow& ds) const override;
|
||||
void loadVolume(const std::string& dsId, std::function<void(VolumeGrid)> onOk,
|
||||
OnError onErr) override;
|
||||
void loadTerrainPaths(std::function<void(TerrainPaths)> onOk, OnError onErr) override;
|
||||
|
||||
private:
|
||||
LocalSampleRepository& base_;
|
||||
std::string projectCrs_;
|
||||
double baseLat_, baseLon_;
|
||||
};
|
||||
|
||||
} // namespace geopro::data
|
||||
|
|
@ -17,4 +17,9 @@ void Scene::addActor(vtkActor* a)
|
|||
if (a) renderer_->AddActor(a);
|
||||
}
|
||||
|
||||
void Scene::addViewProp(vtkProp* p)
|
||||
{
|
||||
if (p) renderer_->AddViewProp(p);
|
||||
}
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <vtkSmartPointer.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkProp.h>
|
||||
namespace geopro::render {
|
||||
|
||||
// 单一渲染场景:持有 vtkRenderer(白底),统一管理 actor 的加入与清除。
|
||||
|
|
@ -12,8 +13,10 @@ public:
|
|||
|
||||
vtkRenderer* renderer() const { return renderer_.Get(); }
|
||||
|
||||
void clear(); // 移除所有 view prop,支持重复切换数据集
|
||||
void clear(); // 移除所有 view prop(含体绘制 vtkVolume),支持重复切换数据集
|
||||
void addActor(vtkActor* a); // actor 由 renderer 引用计数保活
|
||||
// 体绘制 vtkVolume 是 vtkProp3D(非 vtkActor),经此通用入口进场;prop 由 renderer 引用计数保活。
|
||||
void addViewProp(vtkProp* p);
|
||||
|
||||
private:
|
||||
vtkSmartPointer<vtkRenderer> renderer_;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ target_link_libraries(geopro_tests PRIVATE geopro_core)
|
|||
|
||||
target_sources(geopro_tests PRIVATE data/test_parsers.cpp)
|
||||
target_sources(geopro_tests PRIVATE data/test_local_repo.cpp)
|
||||
# I3dSceneRepository/LocalSample3dRepository:dimensionOf 映射 + loadVolume/loadTerrainPaths 异步回调(需 PROJ_DATA)。
|
||||
target_sources(geopro_tests PRIVATE data/test_3d_repo.cpp)
|
||||
target_sources(geopro_tests PRIVATE data/test_nav_dto.cpp)
|
||||
target_sources(geopro_tests PRIVATE data/test_dataset_chart_dto.cpp)
|
||||
target_sources(geopro_tests PRIVATE data/test_measurement_dto.cpp)
|
||||
|
|
@ -76,6 +78,8 @@ endif()
|
|||
# render 层:ColorLutBuilder(core ColorScale -> vtkLookupTable)。
|
||||
# 需 vtkLookupTable(VTK::CommonCore);geopro_render 已 PUBLIC 传递其余 VTK 组件。
|
||||
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel RenderingCore)
|
||||
# Scene:addActor/addViewProp 计数 + clear 清空(vtkVolume 经 addViewProp 进场)。
|
||||
target_sources(geopro_tests PRIVATE render/test_scene.cpp)
|
||||
target_sources(geopro_tests PRIVATE render/test_color_lut.cpp)
|
||||
target_sources(geopro_tests PRIVATE render/test_contour_bands.cpp)
|
||||
# dd_voxel:buildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) 构建不崩 + dims 正确。
|
||||
|
|
@ -117,6 +121,8 @@ target_sources(geopro_tests PRIVATE app/test_scatter_hover.cpp)
|
|||
find_package(Qt6 COMPONENTS Test REQUIRED)
|
||||
target_sources(geopro_tests PRIVATE controller/test_dataset_detail_controller.cpp)
|
||||
target_sources(geopro_tests PRIVATE controller/test_workbench_nav_controller.cpp)
|
||||
# VtkSceneController 编排:注入 fake repo + fake view,断言 视图模式×图层 组合下 add 的图元类型/数量;取消勾选清空。
|
||||
target_sources(geopro_tests PRIVATE controller/test_vtk_scene_controller.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_controller Qt6::Test)
|
||||
|
||||
add_subdirectory(spike) # spike S3: banded contour 渲染验证
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "I3dSceneView.hpp"
|
||||
#include "VtkSceneController.hpp"
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
#include "repo/I3dSceneRepository.hpp"
|
||||
#include "repo/IDatasetRepository.hpp"
|
||||
|
||||
using namespace geopro;
|
||||
using namespace geopro::controller;
|
||||
|
||||
namespace {
|
||||
|
||||
// 记录视图收到的图元调用类型/数量。
|
||||
struct FakeView : I3dSceneView {
|
||||
int clears = 0;
|
||||
int surveyLines = 0;
|
||||
int curtains = 0;
|
||||
int volumes = 0;
|
||||
int terrains = 0;
|
||||
int renders = 0;
|
||||
bool lastIs2D = false;
|
||||
double ve = -1.0;
|
||||
|
||||
// clear 模型化"移除所有图元":图元计数归零(反映当前场景状态),clears 累加。
|
||||
void clear() override {
|
||||
++clears;
|
||||
surveyLines = curtains = volumes = terrains = 0;
|
||||
}
|
||||
void setVerticalExaggeration(double v) override { ve = v; }
|
||||
void addSurveyLine(const core::Grid&) override { ++surveyLines; }
|
||||
void addCurtain(const core::Grid&, const core::ColorScale&) override { ++curtains; }
|
||||
void addVolume(const data::VolumeGrid&, const core::ColorScale&) override { ++volumes; }
|
||||
void addTerrain(const data::TerrainPaths&) override { ++terrains; }
|
||||
void render(bool is2D) override { ++renders; lastIs2D = is2D; }
|
||||
|
||||
int props() const { return surveyLines + curtains + volumes + terrains; }
|
||||
};
|
||||
|
||||
// 同步小数据仓储:loadGrid 返回 2x2 grid,loadColorScale 返回两段色阶。
|
||||
struct FakeDsRepo : data::IDatasetRepository {
|
||||
std::vector<data::GsNode> loadStructure() override { return {}; }
|
||||
core::Grid loadGrid(const std::string&) override {
|
||||
core::Grid g(2, 2);
|
||||
g.lat = {22.0, 22.001};
|
||||
g.lon = {114.0, 114.001};
|
||||
return g;
|
||||
}
|
||||
core::ScatterField loadScatter(const std::string&) override { return {}; }
|
||||
core::ColorScale loadColorScale(const std::string&) override {
|
||||
core::ColorScale cs;
|
||||
cs.addStop(0.0, core::Rgba{0, 0, 255, 255});
|
||||
cs.addStop(1.0, core::Rgba{255, 0, 0, 255});
|
||||
return cs;
|
||||
}
|
||||
core::ColorScale loadScatterColorScale(const std::string&) override { return loadColorScale(""); }
|
||||
std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; }
|
||||
};
|
||||
|
||||
// 同步三维仓储:dimensionOf 全当 3D;loadVolume 立即回调一个最小有效体。
|
||||
struct FakeSceneRepo : data::I3dSceneRepository {
|
||||
data::DsDimension dimensionOf(const data::DsRow&) const override {
|
||||
return data::DsDimension::Dim3D;
|
||||
}
|
||||
void loadVolume(const std::string&, std::function<void(data::VolumeGrid)> onOk,
|
||||
OnError) override {
|
||||
data::VolumeGrid g;
|
||||
g.vol = core::ScalarVolume(2, 2, 2);
|
||||
g.spacing = {{1.0, 1.0, 1.0}};
|
||||
g.vmin = 0.0; g.vmax = 1.0;
|
||||
onOk(std::move(g)); // 同步回调(异步壳)
|
||||
}
|
||||
void loadTerrainPaths(std::function<void(data::TerrainPaths)> onOk, OnError) override {
|
||||
onOk(data::TerrainPaths{"dem.tif", "image.tif"});
|
||||
}
|
||||
};
|
||||
|
||||
} // 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);
|
||||
}
|
||||
|
||||
// 3D 模式 + 帘面图层 → 1 帘面 actor。
|
||||
TEST(VtkSceneController, View3DCurtainAddsCurtain) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
EXPECT_EQ(view.surveyLines, 0);
|
||||
EXPECT_FALSE(view.lastIs2D);
|
||||
}
|
||||
|
||||
// 3D + 帘面 + 体素 → 帘面 1 + 体素 1(体素经异步回调进场)。
|
||||
TEST(VtkSceneController, View3DWithVoxelAddsVolume) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setLayer(SceneLayer::Voxel, true);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
EXPECT_EQ(view.volumes, 1);
|
||||
}
|
||||
|
||||
// 3D + 地形 → 地形 1(与勾选数据集无关,地形是场景图层)。
|
||||
TEST(VtkSceneController, View3DWithTerrainAddsTerrain) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setLayer(SceneLayer::Terrain, true);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
|
||||
EXPECT_EQ(view.terrains, 1);
|
||||
EXPECT_EQ(view.curtains, 1);
|
||||
}
|
||||
|
||||
// 取消勾选 → clear 后无任何图元。
|
||||
TEST(VtkSceneController, UncheckAllClearsScene) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
ASSERT_EQ(view.curtains, 1);
|
||||
|
||||
c.setCheckedDatasets({}); // 取消全部勾选
|
||||
EXPECT_EQ(view.curtains, 0);
|
||||
EXPECT_EQ(view.volumes, 0);
|
||||
// 最后一次重建仍调用 clear。
|
||||
EXPECT_GE(view.clears, 2);
|
||||
}
|
||||
|
||||
// 纵向比例传到视图。
|
||||
TEST(VtkSceneController, VerticalExaggerationForwarded) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setVerticalExaggeration(3.5);
|
||||
c.setCheckedDatasets({"ds1"});
|
||||
EXPECT_DOUBLE_EQ(view.ve, 3.5);
|
||||
}
|
||||
|
||||
// 多个数据集 → 每个一个帘面。
|
||||
TEST(VtkSceneController, MultipleDatasetsAddMultipleCurtains) {
|
||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||
VtkSceneController c(ds, sc, view);
|
||||
c.setViewMode(ViewMode::View3D);
|
||||
c.setCheckedDatasets({"ds1", "ds2", "ds3"});
|
||||
EXPECT_EQ(view.curtains, 3);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "repo/I3dSceneRepository.hpp"
|
||||
#include "repo/LocalSample3dRepository.hpp"
|
||||
#include "repo/LocalSampleRepository.hpp"
|
||||
|
||||
using namespace geopro::data;
|
||||
|
||||
static const std::string kDir =
|
||||
"D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/";
|
||||
static const std::string kCrs = "EPSG:4547";
|
||||
|
||||
namespace {
|
||||
DsRow rowWith(const std::string& ddCode) {
|
||||
DsRow r;
|
||||
r.ddCode = ddCode;
|
||||
return r;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// dimensionOf:各 ddCode → 维度映射(同步纯函数,spec §6.1)。
|
||||
TEST(LocalSample3dRepo, DimensionOfMapsDdCode) {
|
||||
LocalSampleRepository base(kDir);
|
||||
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
|
||||
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_voxel")), DsDimension::Dim3D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_Structual3D")), DsDimension::Dim3D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_Property3D")), DsDimension::Dim3D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_section")), DsDimension::Dim3D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_inversion_data")), DsDimension::Dim3D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_slice")), DsDimension::Analysis3D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_trajectory_data")), DsDimension::Dim2D);
|
||||
EXPECT_EQ(repo.dimensionOf(rowWith("dd_unknown_xyz")), DsDimension::Other);
|
||||
}
|
||||
|
||||
// loadVolume:回调收到有效 VolumeGrid(nx>0 且 vmax>vmin),需 PROJ_DATA。
|
||||
TEST(LocalSample3dRepo, LoadVolumeCallsBackWithValidGrid) {
|
||||
LocalSampleRepository base(kDir);
|
||||
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
|
||||
|
||||
bool ok = false;
|
||||
std::string err;
|
||||
VolumeGrid got;
|
||||
repo.loadVolume("voxel1", [&](VolumeGrid g) { ok = true; got = std::move(g); },
|
||||
[&](const std::string& m) { err = m; });
|
||||
|
||||
ASSERT_TRUE(ok) << "loadVolume onErr: " << err;
|
||||
EXPECT_GT(got.vol.nx(), 0);
|
||||
EXPECT_GT(got.vol.ny(), 0);
|
||||
EXPECT_GT(got.vol.nz(), 0);
|
||||
EXPECT_GT(got.vmax, got.vmin);
|
||||
EXPECT_TRUE(got.valid());
|
||||
}
|
||||
|
||||
// loadTerrainPaths:回调收到 dem/image 绝对路径(非空)。
|
||||
TEST(LocalSample3dRepo, LoadTerrainPathsCallsBack) {
|
||||
LocalSampleRepository base(kDir);
|
||||
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
|
||||
|
||||
bool ok = false;
|
||||
TerrainPaths got;
|
||||
repo.loadTerrainPaths([&](TerrainPaths p) { ok = true; got = std::move(p); },
|
||||
[&](const std::string&) {});
|
||||
|
||||
ASSERT_TRUE(ok);
|
||||
EXPECT_FALSE(got.demPath.empty());
|
||||
EXPECT_FALSE(got.imagePath.empty());
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkVolume.h>
|
||||
|
||||
#include "Scene.hpp"
|
||||
|
||||
using geopro::render::Scene;
|
||||
|
||||
// addActor 把 vtkActor 加入 renderer,view prop 计数 +1。
|
||||
TEST(SceneTest, AddActorIncrementsViewProps) {
|
||||
Scene scene;
|
||||
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 0);
|
||||
auto a = vtkSmartPointer<vtkActor>::New();
|
||||
scene.addActor(a);
|
||||
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 1);
|
||||
}
|
||||
|
||||
// addViewProp 接受 vtkVolume(vtkProp3D,非 vtkActor)——体绘制必经此口。
|
||||
TEST(SceneTest, AddViewPropAcceptsVolume) {
|
||||
Scene scene;
|
||||
auto vol = vtkSmartPointer<vtkVolume>::New();
|
||||
scene.addViewProp(vol);
|
||||
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 1);
|
||||
}
|
||||
|
||||
// clear() 经 RemoveAllViewProps 清空 actor 与 volume(覆盖体绘制 prop)。
|
||||
TEST(SceneTest, ClearRemovesActorsAndVolumes) {
|
||||
Scene scene;
|
||||
scene.addActor(vtkSmartPointer<vtkActor>::New());
|
||||
scene.addViewProp(vtkSmartPointer<vtkVolume>::New());
|
||||
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 2);
|
||||
scene.clear();
|
||||
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 0);
|
||||
}
|
||||
|
||||
// 空指针安全:addActor/addViewProp(nullptr) 不崩、不增计数。
|
||||
TEST(SceneTest, NullPropsAreIgnored) {
|
||||
Scene scene;
|
||||
scene.addActor(nullptr);
|
||||
scene.addViewProp(nullptr);
|
||||
EXPECT_EQ(scene.renderer()->GetViewProps()->GetNumberOfItems(), 0);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
"name": "geopro-desktop",
|
||||
"version": "0.1.0",
|
||||
"description": "Geopro 3.0 desktop client (Qt6 + VTK9) - M1. 方案②-修订: Qt/VTK/ADS/QtKeychain 对接官方 MSVC Qt(不走 vcpkg); 仅非 Qt 依赖走 vcpkg, 按层递增。",
|
||||
"builtin-baseline": "10ceb139a610ebf3c6aa49cdc4a4b7f3db5d3f2b",
|
||||
"dependencies": [
|
||||
"eigen3",
|
||||
"gdal",
|
||||
|
|
|
|||
Loading…
Reference in New Issue