refactor(app): 中央地图/3D单场景(竖直帘面)+下方数据详情(#18平面剖面), 去除体素节点与模式混乱

This commit is contained in:
gaozheng 2026-06-07 22:53:29 +08:00
parent c9d0d90433
commit 7713271557
2 changed files with 98 additions and 177 deletions

View File

@ -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;
// 从对象结构树构建 QTreeWidgetGS → TM → DS 三层。 // 从对象结构树构建 QTreeWidgetGS → 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_sectionloadGrid + loadColorScale → banded contourbands + 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等文件/");

View File

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