refactor(app): 中央地图/3D单场景(竖直帘面)+下方数据详情(#18平面剖面), 去除体素节点与模式混乱
This commit is contained in:
parent
c9d0d90433
commit
7713271557
265
src/app/main.cpp
265
src/app/main.cpp
|
|
@ -1,7 +1,11 @@
|
||||||
// M1 工作台(Phase 2 / Task 4):对象树 → 选中数据集 → 中央 QVTK 渲染剖面 + 右侧属性。
|
// M1 工作台(视图重构 Task B):正确产品模型。
|
||||||
// 端到端:data(LocalSampleRepository 读真实中文路径样本) + core(Grid/ColorScale) +
|
// - 左 对象树:GS→TM→DS(复选框)。勾选 dd_section → 中央场景显示该测线竖直帘面(立体断面墙),可多条共存。
|
||||||
// render(VTK banded contour) + view(Qt/ADS 三栏停靠)。
|
// - 中央「二维/三维视图」:单一 VTK 3D 场景,内容是测线竖直帘面。工具条仅「二维/三维」相机开关
|
||||||
// 数据:docs/剖面网格数据的色阶数据2等文件/(真实样本,UTF-8 中文路径,经 QFile 读取)。
|
// (作用整场景):二维=俯视正交(看测线俯视布局)、三维=透视(看立体墙)。因内容立体,2D/3D 有真实区别。
|
||||||
|
// - 下方「数据详情」:独立 QVTK 小视图。单击某 DS → 显示该数据集平面反演剖面(#18 banded 等值面+等值线,
|
||||||
|
// 平躺俯视正交)+ 属性。
|
||||||
|
// - 右 属性:选中数据集属性文本。
|
||||||
|
// 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame(全项目共享,保证多帘面配准)。
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
@ -13,7 +17,6 @@
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QFormLayout>
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
|
|
@ -36,24 +39,19 @@
|
||||||
|
|
||||||
#include "CameraPreset.hpp"
|
#include "CameraPreset.hpp"
|
||||||
#include "Scene.hpp"
|
#include "Scene.hpp"
|
||||||
|
#include "actors/CurtainActor.hpp"
|
||||||
#include "actors/GridContourActor.hpp"
|
#include "actors/GridContourActor.hpp"
|
||||||
#include "actors/VoxelActor.hpp"
|
|
||||||
|
|
||||||
#include "algo/IInterpolator.hpp"
|
#include "geo/GeoLocalFrame.hpp"
|
||||||
#include "algo/IdwInterpolator.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <vector>
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <QVTKOpenGLStereoWidget.h>
|
#include <QVTKOpenGLStereoWidget.h>
|
||||||
#include <vtkGenericOpenGLRenderWindow.h>
|
#include <vtkGenericOpenGLRenderWindow.h>
|
||||||
#include <vtkImageData.h>
|
|
||||||
#include <vtkProp.h>
|
#include <vtkProp.h>
|
||||||
#include <vtkRenderWindowInteractor.h>
|
|
||||||
#include <vtkRenderer.h>
|
#include <vtkRenderer.h>
|
||||||
#include <vtkSmartPointer.h>
|
#include <vtkSmartPointer.h>
|
||||||
#include <vtkVolume.h>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -62,8 +60,8 @@ constexpr int kRoleDsId = Qt::UserRole;
|
||||||
constexpr int kRoleDdType = Qt::UserRole + 1;
|
constexpr int kRoleDdType = Qt::UserRole + 1;
|
||||||
|
|
||||||
// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层。
|
// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层。
|
||||||
// DS 项可勾选(复选框):勾选驱动该数据集在场景中的显示;UserRole 存 dsId、UserRole+1 存 ddType。
|
// DS 项可勾选(复选框):勾选驱动该测线竖直帘面在中央场景显示;UserRole 存 dsId、UserRole+1 存 ddType。
|
||||||
// 网格剖面(dd_section)默认勾选,启动即显示。
|
// 网格剖面(dd_section)默认勾选,启动即显示帘面。
|
||||||
void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& gss)
|
void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& gss)
|
||||||
{
|
{
|
||||||
for (const auto& gs : gss) {
|
for (const auto& gs : gss) {
|
||||||
|
|
@ -77,9 +75,8 @@ void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& gs
|
||||||
dsItem->setText(0, QString::fromStdString(ds.name));
|
dsItem->setText(0, QString::fromStdString(ds.name));
|
||||||
dsItem->setData(0, kRoleDsId, QString::fromStdString(ds.id));
|
dsItem->setData(0, kRoleDsId, QString::fromStdString(ds.id));
|
||||||
dsItem->setData(0, kRoleDdType, QString::fromStdString(ds.ddType));
|
dsItem->setData(0, kRoleDdType, QString::fromStdString(ds.ddType));
|
||||||
// DS 项可勾选:勾选 = 在场景中显示该数据集。
|
|
||||||
dsItem->setFlags(dsItem->flags() | Qt::ItemIsUserCheckable);
|
dsItem->setFlags(dsItem->flags() | Qt::ItemIsUserCheckable);
|
||||||
// 网格剖面默认显示;其余默认不勾。
|
// 网格剖面默认勾选 → 启动即显帘面;其余默认不勾。
|
||||||
dsItem->setCheckState(
|
dsItem->setCheckState(
|
||||||
0, ds.ddType == "dd_section" ? Qt::Checked : Qt::Unchecked);
|
0, ds.ddType == "dd_section" ? Qt::Checked : Qt::Unchecked);
|
||||||
}
|
}
|
||||||
|
|
@ -98,114 +95,42 @@ std::string readPem(const std::string& path)
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// dd_voxel 构建结果:体绘制 volume + 其底层 vtkImageData(供切片 widget)+ 网格信息。
|
// 取 vector 中位数(用于由测线 lat/lon 推世界系原点)。空则返回 0。
|
||||||
struct VoxelBuildResult {
|
double median(std::vector<double> v)
|
||||||
vtkSmartPointer<vtkVolume> volume;
|
|
||||||
vtkSmartPointer<vtkImageData> image;
|
|
||||||
int nx = 0, ny = 0, nz = 0;
|
|
||||||
double vmin = 0.0, vmax = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// dd_voxel:合并两条交叉剖面散点 → 局部化 → IDW 体素 → GPU 体绘制。
|
|
||||||
// 与 tools/validate_voxel.py 同逻辑(power=2, maxDist≈4, dxy=1m, dz=0.5m)。
|
|
||||||
VoxelBuildResult buildVoxelFromScatters(
|
|
||||||
const std::vector<geopro::core::ScatterField>& scatters,
|
|
||||||
const geopro::core::ColorScale& cs)
|
|
||||||
{
|
{
|
||||||
using geopro::core::GridSpec;
|
if (v.empty()) return 0.0;
|
||||||
using geopro::core::IdwInterpolator;
|
std::sort(v.begin(), v.end());
|
||||||
using geopro::core::PointSet;
|
const size_t n = v.size();
|
||||||
|
return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]);
|
||||||
constexpr double kDxy = 2.0; // 水平步长(米);粗化体素网格保证实时(~1.4万格)
|
|
||||||
constexpr double kDz = 1.0; // 垂向步长(米)
|
|
||||||
constexpr double kPower = 2.0; // IDW 幂(=2 走 1/d² 快速路径)
|
|
||||||
constexpr double kMaxDist = 4.0; // 超距留空(NaN)
|
|
||||||
|
|
||||||
// 合并两剖面点为一组 (X=projX, Y=projY, Z=z)。
|
|
||||||
std::vector<double> X, Y, Z, V;
|
|
||||||
for (const auto& s : scatters) {
|
|
||||||
const size_t n = s.v.size();
|
|
||||||
for (size_t p = 0; p < n; ++p) {
|
|
||||||
// projX/projY 为 GIS 平面坐标;z 为高程;v 为值。容错缺失字段。
|
|
||||||
if (p < s.projX.size() && p < s.projY.size() && p < s.z.size()) {
|
|
||||||
X.push_back(s.projX[p]);
|
|
||||||
Y.push_back(s.projY[p]);
|
|
||||||
Z.push_back(s.z[p]);
|
|
||||||
V.push_back(s.v[p]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (V.empty()) return VoxelBuildResult{};
|
|
||||||
|
|
||||||
// 局部化:三轴各减最小值(规避大坐标;ox=oy=oz=0)。
|
|
||||||
const double minX = *std::min_element(X.begin(), X.end());
|
|
||||||
const double minY = *std::min_element(Y.begin(), Y.end());
|
|
||||||
const double minZ = *std::min_element(Z.begin(), Z.end());
|
|
||||||
PointSet pts;
|
|
||||||
double extX = 0.0, extY = 0.0, extZ = 0.0;
|
|
||||||
for (size_t p = 0; p < V.size(); ++p) {
|
|
||||||
const double lx = X[p] - minX, ly = Y[p] - minY, lz = Z[p] - minZ;
|
|
||||||
pts.x.push_back(lx);
|
|
||||||
pts.y.push_back(ly);
|
|
||||||
pts.z.push_back(lz);
|
|
||||||
pts.v.push_back(V[p]);
|
|
||||||
extX = std::max(extX, lx);
|
|
||||||
extY = std::max(extY, ly);
|
|
||||||
extZ = std::max(extZ, lz);
|
|
||||||
}
|
|
||||||
|
|
||||||
GridSpec spec;
|
|
||||||
spec.ox = spec.oy = spec.oz = 0.0;
|
|
||||||
spec.dx = spec.dy = kDxy;
|
|
||||||
spec.dz = kDz;
|
|
||||||
spec.nx = static_cast<int>(extX / kDxy) + 1;
|
|
||||||
spec.ny = static_cast<int>(extY / kDxy) + 1;
|
|
||||||
spec.nz = static_cast<int>(extZ / kDz) + 1;
|
|
||||||
spec.power = kPower;
|
|
||||||
spec.maxDist = kMaxDist;
|
|
||||||
|
|
||||||
const auto vol = IdwInterpolator().interpolate(pts, spec);
|
|
||||||
|
|
||||||
// 算 vmin/vmax(忽略 NaN)。
|
|
||||||
double vmin = std::numeric_limits<double>::max();
|
|
||||||
double vmax = std::numeric_limits<double>::lowest();
|
|
||||||
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; } // 全 NaN 兜底
|
|
||||||
|
|
||||||
VoxelBuildResult r;
|
|
||||||
r.image = vtkSmartPointer<vtkImageData>::New();
|
|
||||||
r.volume = geopro::render::buildVoxel(
|
|
||||||
vol, cs, spec.ox, spec.oy, spec.oz, spec.dx, spec.dy, spec.dz, vmin, vmax, r.image);
|
|
||||||
r.nx = spec.nx; r.ny = spec.ny; r.nz = spec.nz;
|
|
||||||
r.vmin = vmin; r.vmax = vmax;
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在给定 QMainWindow 上构建 M1 工作台:ADS 三栏 + 对象树 → 渲染联动 + 属性面板。
|
// 当前相机模式(默认二维俯视)。
|
||||||
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
|
|
||||||
// 相机模式:默认二维俯视。
|
|
||||||
enum class CameraMode { Top2D, Free3D };
|
enum class CameraMode { Top2D, Free3D };
|
||||||
|
|
||||||
|
// 在给定 QMainWindow 上构建 M1 工作台。
|
||||||
|
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
|
||||||
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo)
|
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo)
|
||||||
{
|
{
|
||||||
// 中央 QVTK 视图(指针供联动回调使用)。renderer 由 render::Scene 持有。
|
// ── 世界系:启动取一次 grid1 的 lat/lon,用中位数作 GeoLocalFrame 原点 ──
|
||||||
// Scene 需在 lambda 回调期间存活 ⇒ 堆分配,挂到 window 父链随窗口销毁。
|
// 全项目共享(shared_ptr 持有):所有帘面用同一 frame 投影,保证多条测线空间配准。
|
||||||
|
const auto baseGrid = repo.loadGrid("grid1");
|
||||||
|
const double lat0 = median(baseGrid.lat);
|
||||||
|
const double lon0 = median(baseGrid.lon);
|
||||||
|
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(lat0, lon0);
|
||||||
|
|
||||||
|
// ── 中央 QVTK + Scene(竖直帘面场景)─────────────────────────────────
|
||||||
|
// Scene 非 QObject:堆分配,用 widget 销毁信号清理(widget 随 window 销毁)。
|
||||||
auto* scene = new geopro::render::Scene();
|
auto* scene = new geopro::render::Scene();
|
||||||
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
||||||
// Scene 非 QObject:用 widget 销毁信号清理,避免泄漏(widget 随 window 销毁)。
|
|
||||||
QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; });
|
QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; });
|
||||||
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
||||||
vtkWidget->setRenderWindow(renderWindow);
|
vtkWidget->setRenderWindow(renderWindow);
|
||||||
renderWindow->AddRenderer(scene->renderer());
|
renderWindow->AddRenderer(scene->renderer());
|
||||||
|
|
||||||
// 当前相机模式(默认二维)。用 shared_ptr 让多个 lambda 共享同一状态。
|
|
||||||
auto cameraMode = std::make_shared<CameraMode>(CameraMode::Top2D);
|
|
||||||
vtkRenderer* rendererPtr = scene->renderer();
|
vtkRenderer* rendererPtr = scene->renderer();
|
||||||
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
|
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
|
||||||
|
|
||||||
|
auto cameraMode = std::make_shared<CameraMode>(CameraMode::Top2D);
|
||||||
auto applyCurrentCamera = [rendererPtr, cameraMode]() {
|
auto applyCurrentCamera = [rendererPtr, cameraMode]() {
|
||||||
if (*cameraMode == CameraMode::Top2D)
|
if (*cameraMode == CameraMode::Top2D)
|
||||||
geopro::render::applyTop2D(rendererPtr);
|
geopro::render::applyTop2D(rendererPtr);
|
||||||
|
|
@ -216,13 +141,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
auto* dockManager = new ads::CDockManager(&window);
|
auto* dockManager = new ads::CDockManager(&window);
|
||||||
window.setCentralWidget(dockManager);
|
window.setCentralWidget(dockManager);
|
||||||
|
|
||||||
// 中央剖面容器:顶部 2D/3D 工具条 + 下方 QVTK 视图。
|
// 中央容器:顶部 2D/3D 工具条 + 下方帘面 QVTK 视图。
|
||||||
auto* centerWidget = new QWidget();
|
auto* centerWidget = new QWidget();
|
||||||
auto* centerLayout = new QVBoxLayout(centerWidget);
|
auto* centerLayout = new QVBoxLayout(centerWidget);
|
||||||
centerLayout->setContentsMargins(0, 0, 0, 0);
|
centerLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
centerLayout->setSpacing(0);
|
centerLayout->setSpacing(0);
|
||||||
|
|
||||||
// 工具条:仅「二维/三维」相机开关,作用于整个场景(不绑定内容)。互斥二选一,默认二维。
|
// 工具条:仅「二维/三维」相机开关,作用整场景(不改内容)。互斥二选一,默认二维。
|
||||||
auto* viewToolBar = new QToolBar();
|
auto* viewToolBar = new QToolBar();
|
||||||
auto* cameraGroup = new QActionGroup(viewToolBar);
|
auto* cameraGroup = new QActionGroup(viewToolBar);
|
||||||
cameraGroup->setExclusive(true);
|
cameraGroup->setExclusive(true);
|
||||||
|
|
@ -238,7 +163,23 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
|
|
||||||
auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维/三维视图"));
|
auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维/三维视图"));
|
||||||
vtkDock->setWidget(centerWidget);
|
vtkDock->setWidget(centerWidget);
|
||||||
dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
|
auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
|
||||||
|
|
||||||
|
// ── 下方「数据详情」dock:独立 QVTK 小视图(独立 renderer/renderWindow)──
|
||||||
|
// 单击 DS → 显示该数据集平面反演剖面(#18 banded,平躺俯视正交)。
|
||||||
|
auto* detailWidget = new QVTKOpenGLStereoWidget();
|
||||||
|
vtkNew<vtkGenericOpenGLRenderWindow> detailRenderWindow;
|
||||||
|
vtkNew<vtkRenderer> detailRenderer;
|
||||||
|
detailRenderer->SetBackground(1.0, 1.0, 1.0); // 白底
|
||||||
|
detailWidget->setRenderWindow(detailRenderWindow);
|
||||||
|
detailRenderWindow->AddRenderer(detailRenderer);
|
||||||
|
vtkRenderer* detailRendererPtr = detailRenderer.Get();
|
||||||
|
vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get();
|
||||||
|
|
||||||
|
auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情"));
|
||||||
|
detailDock->setWidget(detailWidget);
|
||||||
|
// 放在中央视图下方。
|
||||||
|
dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea);
|
||||||
|
|
||||||
// 左 dock:对象树。
|
// 左 dock:对象树。
|
||||||
auto* tree = new QTreeWidget();
|
auto* tree = new QTreeWidget();
|
||||||
|
|
@ -249,7 +190,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
dockManager->addDockWidget(ads::LeftDockWidgetArea, leftDock);
|
dockManager->addDockWidget(ads::LeftDockWidgetArea, leftDock);
|
||||||
|
|
||||||
// 右 dock:属性。
|
// 右 dock:属性。
|
||||||
auto* propLabel = new QLabel(QStringLiteral("(选择左侧数据集查看属性)"));
|
auto* propLabel = new QLabel(QStringLiteral("(单击左侧数据集查看属性与平面剖面)"));
|
||||||
propLabel->setWordWrap(true);
|
propLabel->setWordWrap(true);
|
||||||
propLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
propLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||||
propLabel->setMargin(8);
|
propLabel->setMargin(8);
|
||||||
|
|
@ -257,17 +198,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
rightDock->setWidget(propLabel);
|
rightDock->setWidget(propLabel);
|
||||||
dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock);
|
dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock);
|
||||||
|
|
||||||
// ── 勾选驱动显示(核心)──────────────────────────────────────────────
|
// ── 勾选驱动帘面显示(核心)─────────────────────────────────────────
|
||||||
// 已显示数据集 → 其 VTK props 的映射。一个数据集可对应多个 prop(如剖面 bands+edges)。
|
// 已显示数据集 → 其 VTK props 映射(帘面=单个 actor)。多数据集叠加共存,不 clear 场景。
|
||||||
// 用 shared_ptr 让多个 lambda 共享同一映射;props 由 renderer 引用计数保活。
|
|
||||||
using PropList = std::vector<vtkSmartPointer<vtkProp>>;
|
using PropList = std::vector<vtkSmartPointer<vtkProp>>;
|
||||||
auto dsProps = std::make_shared<std::map<QString, PropList>>();
|
auto dsProps = std::make_shared<std::map<QString, PropList>>();
|
||||||
|
|
||||||
// 初始化守卫:populateTree 会程序化 setCheckState,触发 itemChanged;
|
// 初始化守卫:populateTree 程序化 setCheckState 触发 itemChanged,需避免重入。
|
||||||
// 也用于「点击 DS 自动勾选」等程序化改动期间,避免回调递归/重入。
|
|
||||||
auto building = std::make_shared<bool>(false);
|
auto building = std::make_shared<bool>(false);
|
||||||
|
|
||||||
// 是否已有任意可见对象(用于决定首个对象时套用当前相机预设)。
|
// 是否已有任意可见对象(用于首个对象时套用当前相机预设)。
|
||||||
auto hasVisible = [dsProps]() {
|
auto hasVisible = [dsProps]() {
|
||||||
for (const auto& kv : *dsProps) {
|
for (const auto& kv : *dsProps) {
|
||||||
if (!kv.second.empty()) return true;
|
if (!kv.second.empty()) return true;
|
||||||
|
|
@ -275,42 +214,28 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 按 ddType 构建某数据集的 props 并加入 renderer。返回构建出的 props(可能为空)。
|
// 构建某测线竖直帘面(dd_section)并返回其 props。其它类型暂不入中央场景。
|
||||||
// dd_section:loadGrid + loadColorScale → banded contour(bands + edges)。
|
auto buildCurtainProps = [&repo, frame](const QString& dsId,
|
||||||
// dd_voxel:两条交叉剖面散点 → IDW 体素 → GPU 体绘制(WaitCursor 包裹耗时构建)。
|
const QString& ddType) -> PropList {
|
||||||
auto buildProps = [&repo, propLabel](const QString& dsId,
|
|
||||||
const QString& ddType) -> PropList {
|
|
||||||
PropList props;
|
PropList props;
|
||||||
if (ddType == "dd_section") {
|
if (ddType == "dd_section") {
|
||||||
const std::string id = dsId.toStdString();
|
const std::string id = dsId.toStdString();
|
||||||
const auto g = repo.loadGrid(id);
|
const auto g = repo.loadGrid(id);
|
||||||
const auto cs = repo.loadColorScale(id);
|
const auto cs = repo.loadColorScale(id);
|
||||||
const auto actors = geopro::render::buildGridContour(g, cs);
|
auto curtain = geopro::render::buildCurtain(g, cs, *frame);
|
||||||
if (actors.bands) props.push_back(actors.bands);
|
if (curtain) props.push_back(curtain);
|
||||||
if (actors.edges) props.push_back(actors.edges);
|
|
||||||
} else if (ddType == "dd_voxel") {
|
|
||||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
||||||
const auto scatters = repo.loadVoxelScatters();
|
|
||||||
const auto cs = repo.loadColorScale("grid1"); // 体素复用 grid1 色阶
|
|
||||||
auto result = buildVoxelFromScatters(scatters, cs);
|
|
||||||
QApplication::restoreOverrideCursor();
|
|
||||||
if (result.volume) props.push_back(result.volume);
|
|
||||||
else propLabel->setText(QStringLiteral("dd_voxel: 无可用散点数据"));
|
|
||||||
}
|
}
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 勾选态变化:勾选 → 构建/显示该数据集 props;取消 → 移除其 props。
|
// 勾选态变化:勾选 → 构建/显示帘面;取消 → 移除其 props。多数据集叠加共存。
|
||||||
// 多个数据集可同时勾选并叠加共存(不 clear 场景,按数据集增删 props)。
|
|
||||||
// 已知限制:剖面用「距离-深度」局部坐标,体素用 GIS 局部坐标,二者坐标基不同,
|
|
||||||
// 叠加时不会真正空间对齐(后续 curtain/坐标统一工作,见 STATUS §5)。
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
tree, &QTreeWidget::itemChanged, tree,
|
tree, &QTreeWidget::itemChanged, tree,
|
||||||
[dsProps, building, buildProps, hasVisible, applyCurrentCamera, rendererPtr,
|
[dsProps, building, buildCurtainProps, hasVisible, applyCurrentCamera, rendererPtr,
|
||||||
renderWindowPtr](QTreeWidgetItem* item, int) {
|
renderWindowPtr](QTreeWidgetItem* item, int) {
|
||||||
if (*building) return; // 程序化改动期间不处理,避免初始化递归
|
if (*building) return; // 程序化改动期间不处理,避免初始化递归
|
||||||
const QString dsId = item->data(0, kRoleDsId).toString();
|
const QString dsId = item->data(0, kRoleDsId).toString();
|
||||||
if (dsId.isEmpty()) return; // GS/TM 节点(无 dsId)忽略
|
if (dsId.isEmpty()) return; // GS/TM 节点忽略
|
||||||
const QString ddType = item->data(0, kRoleDdType).toString();
|
const QString ddType = item->data(0, kRoleDdType).toString();
|
||||||
const bool checked = item->checkState(0) == Qt::Checked;
|
const bool checked = item->checkState(0) == Qt::Checked;
|
||||||
|
|
||||||
|
|
@ -318,18 +243,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
const bool wasEmpty = !hasVisible();
|
const bool wasEmpty = !hasVisible();
|
||||||
auto it = dsProps->find(dsId);
|
auto it = dsProps->find(dsId);
|
||||||
if (it == dsProps->end() || it->second.empty()) {
|
if (it == dsProps->end() || it->second.empty()) {
|
||||||
// 首次显示:构建并加入 renderer。
|
PropList props = buildCurtainProps(dsId, ddType);
|
||||||
PropList props = buildProps(dsId, ddType);
|
|
||||||
for (const auto& p : props) rendererPtr->AddViewProp(p);
|
for (const auto& p : props) rendererPtr->AddViewProp(p);
|
||||||
(*dsProps)[dsId] = std::move(props);
|
(*dsProps)[dsId] = std::move(props);
|
||||||
} else {
|
} else {
|
||||||
// 之前隐藏过(保留了 props):仅恢复可见。
|
|
||||||
for (const auto& p : it->second) p->SetVisibility(1);
|
for (const auto& p : it->second) p->SetVisibility(1);
|
||||||
}
|
}
|
||||||
if (wasEmpty) applyCurrentCamera(); // 首个可见对象 → 套用当前相机预设
|
if (wasEmpty) applyCurrentCamera(); // 首个可见对象 → 套用当前相机
|
||||||
rendererPtr->ResetCamera(); // 框住所有可见对象
|
rendererPtr->ResetCamera();
|
||||||
} else {
|
} else {
|
||||||
// 取消勾选:从场景移除该数据集 props。
|
|
||||||
auto it = dsProps->find(dsId);
|
auto it = dsProps->find(dsId);
|
||||||
if (it != dsProps->end()) {
|
if (it != dsProps->end()) {
|
||||||
for (const auto& p : it->second) rendererPtr->RemoveViewProp(p);
|
for (const auto& p : it->second) rendererPtr->RemoveViewProp(p);
|
||||||
|
|
@ -339,29 +261,37 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
renderWindowPtr->Render();
|
renderWindowPtr->Render();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 单击 DS → 更新右侧属性(与勾选区分;不改变可见性)────────────────
|
// ── 单击 DS → 下方数据详情显示平面反演剖面 + 右侧属性(与勾选区分;不改帘面可见性)──
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
tree, &QTreeWidget::itemClicked, tree,
|
tree, &QTreeWidget::itemClicked, tree,
|
||||||
[&repo, propLabel](QTreeWidgetItem* item, int) {
|
[&repo, propLabel, detailRendererPtr, detailRenderWindowPtr](QTreeWidgetItem* item, int) {
|
||||||
const QString dsId = item->data(0, kRoleDsId).toString();
|
const QString dsId = item->data(0, kRoleDsId).toString();
|
||||||
if (dsId.isEmpty()) return; // GS/TM 节点无属性
|
if (dsId.isEmpty()) return; // GS/TM 节点无详情
|
||||||
const QString ddType = item->data(0, kRoleDdType).toString();
|
const QString ddType = item->data(0, kRoleDdType).toString();
|
||||||
const QString name = item->text(0);
|
const QString name = item->text(0);
|
||||||
if (ddType == "dd_section") {
|
if (ddType != "dd_section") return;
|
||||||
const auto g = repo.loadGrid(dsId.toStdString());
|
|
||||||
propLabel->setText(
|
const std::string id = dsId.toStdString();
|
||||||
QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n"
|
const auto g = repo.loadGrid(id);
|
||||||
"vmin / vmax: %4 / %5")
|
const auto cs = repo.loadColorScale(id);
|
||||||
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax));
|
|
||||||
} else if (ddType == "dd_voxel") {
|
// 下方数据详情:平面反演剖面(#18 banded 等值面 + 等值线),平躺俯视正交。
|
||||||
propLabel->setText(
|
const auto actors = geopro::render::buildGridContour(g, cs);
|
||||||
QStringLiteral("数据集: %1\n类型: 三维体素 (dd_voxel)\n"
|
detailRendererPtr->RemoveAllViewProps();
|
||||||
"说明: 两交叉剖面 IDW 体素")
|
if (actors.bands) detailRendererPtr->AddViewProp(actors.bands);
|
||||||
.arg(name));
|
if (actors.edges) detailRendererPtr->AddViewProp(actors.edges);
|
||||||
}
|
geopro::render::applyTop2D(detailRendererPtr);
|
||||||
|
detailRendererPtr->ResetCamera();
|
||||||
|
detailRenderWindowPtr->Render();
|
||||||
|
|
||||||
|
// 右侧属性。
|
||||||
|
propLabel->setText(
|
||||||
|
QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n"
|
||||||
|
"vmin / vmax: %4 / %5")
|
||||||
|
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax));
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 工具条「二维/三维」:仅切换整场景相机预设,不改变内容可见性 ──────────
|
// ── 工具条「二维/三维」:仅切换整场景相机预设,不改内容可见性 ────────────
|
||||||
QObject::connect(act2D, &QAction::triggered, vtkWidget,
|
QObject::connect(act2D, &QAction::triggered, vtkWidget,
|
||||||
[cameraMode, applyCurrentCamera, rendererPtr, renderWindowPtr]() {
|
[cameraMode, applyCurrentCamera, rendererPtr, renderWindowPtr]() {
|
||||||
*cameraMode = CameraMode::Top2D;
|
*cameraMode = CameraMode::Top2D;
|
||||||
|
|
@ -377,8 +307,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
renderWindowPtr->Render();
|
renderWindowPtr->Render();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 启动默认:网格剖面 DS 已在 populateTree 中设为 Checked,但 itemChanged
|
// ── 启动默认:dd_section 已设为 Checked,但 itemChanged 在 connect 之前触发故未渲染。
|
||||||
// 在 connect 之前触发故未渲染。这里在 connect 之后,对已勾选的 DS 主动触发一次显示。
|
// 这里 connect 之后对已勾选 DS 主动触发一次帘面显示。
|
||||||
{
|
{
|
||||||
QList<QTreeWidgetItem*> stack;
|
QList<QTreeWidgetItem*> stack;
|
||||||
for (int i = 0; i < tree->topLevelItemCount(); ++i) stack.append(tree->topLevelItem(i));
|
for (int i = 0; i < tree->topLevelItemCount(); ++i) stack.append(tree->topLevelItem(i));
|
||||||
|
|
@ -387,7 +317,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
const QString dsId = cur->data(0, kRoleDsId).toString();
|
const QString dsId = cur->data(0, kRoleDsId).toString();
|
||||||
if (!dsId.isEmpty() && cur->checkState(0) == Qt::Checked) {
|
if (!dsId.isEmpty() && cur->checkState(0) == Qt::Checked) {
|
||||||
const QString ddType = cur->data(0, kRoleDdType).toString();
|
const QString ddType = cur->data(0, kRoleDdType).toString();
|
||||||
PropList props = buildProps(dsId, ddType);
|
PropList props = buildCurtainProps(dsId, ddType);
|
||||||
const bool wasEmpty = !hasVisible();
|
const bool wasEmpty = !hasVisible();
|
||||||
for (const auto& p : props) rendererPtr->AddViewProp(p);
|
for (const auto& p : props) rendererPtr->AddViewProp(p);
|
||||||
(*dsProps)[dsId] = std::move(props);
|
(*dsProps)[dsId] = std::move(props);
|
||||||
|
|
@ -404,7 +334,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
// QVTK 默认 surface format 必须在 QApplication 之前设置。
|
// QVTK 默认 surface format 必须在 QApplication 之前设置(全局一次,两个 QVTK widget 共用)。
|
||||||
QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat());
|
QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat());
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
|
@ -420,7 +350,6 @@ int main(int argc, char* argv[])
|
||||||
api.setToken(login.token()); // 注入 token 供后续 API 使用
|
api.setToken(login.token()); // 注入 token 供后续 API 使用
|
||||||
|
|
||||||
// 登录成功 → 构建并显示工作台。
|
// 登录成功 → 构建并显示工作台。
|
||||||
// 本地样本仓储(中文路径,末尾带 '/')。生命周期覆盖事件循环。
|
|
||||||
geopro::data::LocalSampleRepository repo(
|
geopro::data::LocalSampleRepository repo(
|
||||||
"D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/");
|
"D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,25 +51,17 @@ std::string LocalSampleRepository::readFile(const std::string& fileNameUtf8) con
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GsNode> LocalSampleRepository::loadStructure() {
|
std::vector<GsNode> LocalSampleRepository::loadStructure() {
|
||||||
// 剖面网格数据集(dd_section):在对象树勾选后渲染 banded contour 帘面。
|
// 剖面网格数据集(dd_section):勾选后在中央场景显示竖直帘面;单击显示平面反演剖面。
|
||||||
DsNode dsSection;
|
DsNode dsSection;
|
||||||
dsSection.id = kDsId;
|
dsSection.id = kDsId;
|
||||||
dsSection.name = u8"剖面网格数据1";
|
dsSection.name = u8"剖面网格数据1";
|
||||||
dsSection.ddType = "dd_section";
|
dsSection.ddType = "dd_section";
|
||||||
|
|
||||||
// 三维体素数据集(dd_voxel):勾选后由两条交叉剖面散点 IDW 出体素并叠加显示。
|
|
||||||
// 注意:体素与剖面坐标基不同(见 main.cpp 渲染处注释 + STATUS §5),暂不空间配准。
|
|
||||||
DsNode dsVoxel;
|
|
||||||
dsVoxel.id = "voxel1";
|
|
||||||
dsVoxel.name = u8"三维体素";
|
|
||||||
dsVoxel.ddType = "dd_voxel";
|
|
||||||
|
|
||||||
TmNode tm;
|
TmNode tm;
|
||||||
tm.id = "tm-ert1";
|
tm.id = "tm-ert1";
|
||||||
tm.name = "ERT1";
|
tm.name = "ERT1";
|
||||||
tm.confCode = "ERT";
|
tm.confCode = "ERT";
|
||||||
tm.dss.push_back(std::move(dsSection));
|
tm.dss.push_back(std::move(dsSection));
|
||||||
tm.dss.push_back(std::move(dsVoxel));
|
|
||||||
|
|
||||||
GsNode gs;
|
GsNode gs;
|
||||||
gs.id = "gs-1";
|
gs.id = "gs-1";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue