feat(app): 中央二维地图(测线线)/三维视图(断面墙)两视图 + 数据详情(#18纵向夸张), 接入已验证渲染积木

This commit is contained in:
gaozheng 2026-06-07 23:30:55 +08:00
parent e59b6b3dfe
commit ebd7779b51
1 changed files with 92 additions and 118 deletions

View File

@ -1,14 +1,15 @@
// M1 工作台(视图重构 Task B正确产品模型。 // M1 工作台(视图重构 Task B正确产品模型。
// - 左 对象树GS→TM→DS复选框。勾选 dd_section → 中央场景显示该测线竖直帘面(立体断面墙),可多条共存。 // - 左 对象树GS→TM→DS复选框。勾选 dd_section → 在中央当前视图显示该数据集,可多条共存。
// - 中央「二维/三维视图」:单一 VTK 3D 场景,内容是测线竖直帘面。工具条仅「二维/三维」相机开关 // - 中央「二维地图 / 三维视图」:两个互斥视图(内容不同,不是同一物体换相机)。
// (作用整场景):二维=俯视正交(看测线俯视布局)、三维=透视看立体墙。因内容立体2D/3D 有真实区别。 // 二维地图 = 对每个勾选数据集 buildSurveyLinelat/lon 红线俯视z=0+ applyTop2D浅底背景
// 三维视图 = 对每个勾选数据集 buildCurtain竖直断面墙actor SetScale(1,1,3) 纵向夸张 + applyFree3D白底
// 切视图 / 勾选变化 → 按当前勾选集重建对应内容。
// - 下方「数据详情」:独立 QVTK 小视图。单击某 DS → 显示该数据集平面反演剖面(#18 banded 等值面+等值线, // - 下方「数据详情」:独立 QVTK 小视图。单击某 DS → 显示该数据集平面反演剖面(#18 banded 等值面+等值线,
// 平躺俯视正交)+ 属性。 // 两 actor SetScale(1,1.5,1) 纵向夸张,平躺俯视正交)+ 属性。
// - 右 属性:选中数据集属性文本。 // - 右 属性:选中数据集属性文本。
// 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame全项目共享保证多帘面配准)。 // 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame全项目共享保证多视图配准)。
#include <fstream> #include <fstream>
#include <map>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string> #include <string>
@ -41,6 +42,7 @@
#include "Scene.hpp" #include "Scene.hpp"
#include "actors/CurtainActor.hpp" #include "actors/CurtainActor.hpp"
#include "actors/GridContourActor.hpp" #include "actors/GridContourActor.hpp"
#include "actors/MapLineActor.hpp"
#include "geo/GeoLocalFrame.hpp" #include "geo/GeoLocalFrame.hpp"
@ -49,7 +51,6 @@
#include <QVTKOpenGLStereoWidget.h> #include <QVTKOpenGLStereoWidget.h>
#include <vtkGenericOpenGLRenderWindow.h> #include <vtkGenericOpenGLRenderWindow.h>
#include <vtkProp.h>
#include <vtkRenderer.h> #include <vtkRenderer.h>
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
@ -104,8 +105,12 @@ double median(std::vector<double> v)
return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]); return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]);
} }
// 当前相机模式(默认二维俯视)。 // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
enum class CameraMode { Top2D, Free3D }; enum class ViewMode { Map2D, View3D };
// 纵向夸张倍数:三维断面墙沿 z 拉伸成墙;数据详情 #18 沿 y 拉伸填面板。
constexpr double kCurtainZScale = 3.0;
constexpr double kDetailYScale = 1.5;
// 在给定 QMainWindow 上构建 M1 工作台。 // 在给定 QMainWindow 上构建 M1 工作台。
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。 // repo 生命周期须覆盖到事件循环结束(由调用方保证)。
@ -130,38 +135,33 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
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 viewMode = std::make_shared<ViewMode>(ViewMode::Map2D);
if (*cameraMode == CameraMode::Top2D)
geopro::render::applyTop2D(rendererPtr);
else
geopro::render::applyFree3D(rendererPtr);
};
auto* dockManager = new ads::CDockManager(&window); auto* dockManager = new ads::CDockManager(&window);
window.setCentralWidget(dockManager); window.setCentralWidget(dockManager);
// 中央容器:顶部 2D/3D 工具条 + 下方帘面 QVTK 视图。 // 中央容器:顶部「二维地图/三维视图」工具条 + 下方 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);
// 工具条:仅「二维/三维」相机开关,作用整场景(不改内容)。互斥二选一,默认二维 // 工具条:「二维地图/三维视图」两个互斥可勾选 action。切换=按当前勾选集重建对应内容。默认二维地图
auto* viewToolBar = new QToolBar(); auto* viewToolBar = new QToolBar();
auto* cameraGroup = new QActionGroup(viewToolBar); auto* viewGroup = new QActionGroup(viewToolBar);
cameraGroup->setExclusive(true); viewGroup->setExclusive(true);
auto* act2D = viewToolBar->addAction(QStringLiteral("二维")); auto* act2D = viewToolBar->addAction(QStringLiteral("二维地图"));
auto* act3D = viewToolBar->addAction(QStringLiteral("三维")); auto* act3D = viewToolBar->addAction(QStringLiteral("三维视图"));
act2D->setCheckable(true); act2D->setCheckable(true);
act3D->setCheckable(true); act3D->setCheckable(true);
cameraGroup->addAction(act2D); viewGroup->addAction(act2D);
cameraGroup->addAction(act3D); viewGroup->addAction(act3D);
act2D->setChecked(true); // 默认二维 act2D->setChecked(true); // 默认二维地图
centerLayout->addWidget(viewToolBar); centerLayout->addWidget(viewToolBar);
centerLayout->addWidget(vtkWidget, 1); centerLayout->addWidget(vtkWidget, 1);
auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维/三维视图")); auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维地图/三维视图"));
vtkDock->setWidget(centerWidget); vtkDock->setWidget(centerWidget);
auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
@ -198,67 +198,58 @@ 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 映射(帘面=单个 actor。多数据集叠加共存不 clear 场景。 // 两个互斥视图按当前勾选集整体重建scene.clear() → 对每个勾选 dd_section 加对应 actor。
using PropList = std::vector<vtkSmartPointer<vtkProp>>; // 二维地图 = buildSurveyLine红线俯视浅底背景+ applyTop2D。
auto dsProps = std::make_shared<std::map<QString, PropList>>(); // 三维视图 = buildCurtain断面墙SetScale(1,1,kCurtainZScale) + applyFree3D白底
// frame 全局共享;切视图/勾选变化都调用此函数重建当前视图。
auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, &repo, frame, tree]() {
scene->clear();
// 初始化守卫populateTree 程序化 setCheckState 触发 itemChanged需避免重入。 const bool is2D = (*viewMode == ViewMode::Map2D);
auto building = std::make_shared<bool>(false); rendererPtr->SetBackground(is2D ? 0.96 : 1.0, is2D ? 0.97 : 1.0, is2D ? 0.99 : 1.0);
// 是否已有任意可见对象(用于首个对象时套用当前相机预设)。 // 遍历对象树收集所有勾选的 dd_section逐个加入当前视图内容。
auto hasVisible = [dsProps]() { QList<QTreeWidgetItem*> stack;
for (const auto& kv : *dsProps) { for (int i = 0; i < tree->topLevelItemCount(); ++i) stack.append(tree->topLevelItem(i));
if (!kv.second.empty()) return true; while (!stack.isEmpty()) {
} QTreeWidgetItem* cur = stack.takeFirst();
return false; for (int i = 0; i < cur->childCount(); ++i) stack.append(cur->child(i));
};
const QString dsId = cur->data(0, kRoleDsId).toString();
if (dsId.isEmpty()) continue; // GS/TM 节点忽略
if (cur->checkState(0) != Qt::Checked) continue; // 仅显示勾选的
const QString ddType = cur->data(0, kRoleDdType).toString();
if (ddType != "dd_section") continue; // 当前仅支持剖面网格
// 构建某测线竖直帘面dd_section并返回其 props。其它类型暂不入中央场景。
auto buildCurtainProps = [&repo, frame](const QString& dsId,
const QString& ddType) -> PropList {
PropList props;
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);
if (is2D) {
auto line = geopro::render::buildSurveyLine(g, *frame);
if (line) scene->addActor(line);
} else {
const auto cs = repo.loadColorScale(id); const auto cs = repo.loadColorScale(id);
auto curtain = geopro::render::buildCurtain(g, cs, *frame); auto curtain = geopro::render::buildCurtain(g, cs, *frame);
if (curtain) props.push_back(curtain); if (curtain) {
curtain->SetScale(1.0, 1.0, kCurtainZScale); // 纵向夸张成墙
scene->addActor(curtain);
} }
return props; }
}
if (is2D)
geopro::render::applyTop2D(rendererPtr);
else
geopro::render::applyFree3D(rendererPtr);
rendererPtr->ResetCamera();
renderWindowPtr->Render();
}; };
// 勾选态变化:勾选 → 构建/显示帘面;取消 → 移除其 props。多数据集叠加共存。 // 勾选/取消某 dd_section → 重建当前视图内容(勾的才显示;可多条共存)。
QObject::connect( QObject::connect(tree, &QTreeWidget::itemChanged, tree,
tree, &QTreeWidget::itemChanged, tree, [rebuildCentral](QTreeWidgetItem* item, int) {
[dsProps, building, buildCurtainProps, hasVisible, applyCurrentCamera, rendererPtr, if (item->data(0, kRoleDsId).toString().isEmpty()) return; // GS/TM 忽略
renderWindowPtr](QTreeWidgetItem* item, int) { rebuildCentral();
if (*building) return; // 程序化改动期间不处理,避免初始化递归
const QString dsId = item->data(0, kRoleDsId).toString();
if (dsId.isEmpty()) return; // GS/TM 节点忽略
const QString ddType = item->data(0, kRoleDdType).toString();
const bool checked = item->checkState(0) == Qt::Checked;
if (checked) {
const bool wasEmpty = !hasVisible();
auto it = dsProps->find(dsId);
if (it == dsProps->end() || it->second.empty()) {
PropList props = buildCurtainProps(dsId, ddType);
for (const auto& p : props) rendererPtr->AddViewProp(p);
(*dsProps)[dsId] = std::move(props);
} else {
for (const auto& p : it->second) p->SetVisibility(1);
}
if (wasEmpty) applyCurrentCamera(); // 首个可见对象 → 套用当前相机
rendererPtr->ResetCamera();
} else {
auto it = dsProps->find(dsId);
if (it != dsProps->end()) {
for (const auto& p : it->second) rendererPtr->RemoveViewProp(p);
dsProps->erase(it);
}
}
renderWindowPtr->Render();
}); });
// ── 单击 DS → 下方数据详情显示平面反演剖面 + 右侧属性(与勾选区分;不改帘面可见性)── // ── 单击 DS → 下方数据详情显示平面反演剖面 + 右侧属性(与勾选区分;不改帘面可见性)──
@ -276,10 +267,17 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
const auto cs = repo.loadColorScale(id); const auto cs = repo.loadColorScale(id);
// 下方数据详情:平面反演剖面(#18 banded 等值面 + 等值线),平躺俯视正交。 // 下方数据详情:平面反演剖面(#18 banded 等值面 + 等值线),平躺俯视正交。
// 两个 actor 都纵向夸张 1.5x(沿 y填满面板。
const auto actors = geopro::render::buildGridContour(g, cs); const auto actors = geopro::render::buildGridContour(g, cs);
detailRendererPtr->RemoveAllViewProps(); detailRendererPtr->RemoveAllViewProps();
if (actors.bands) detailRendererPtr->AddViewProp(actors.bands); if (actors.bands) {
if (actors.edges) detailRendererPtr->AddViewProp(actors.edges); actors.bands->SetScale(1.0, kDetailYScale, 1.0);
detailRendererPtr->AddViewProp(actors.bands);
}
if (actors.edges) {
actors.edges->SetScale(1.0, kDetailYScale, 1.0);
detailRendererPtr->AddViewProp(actors.edges);
}
geopro::render::applyTop2D(detailRendererPtr); geopro::render::applyTop2D(detailRendererPtr);
detailRendererPtr->ResetCamera(); detailRendererPtr->ResetCamera();
detailRenderWindowPtr->Render(); detailRenderWindowPtr->Render();
@ -291,43 +289,19 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax)); .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, [viewMode, rebuildCentral]() {
[cameraMode, applyCurrentCamera, rendererPtr, renderWindowPtr]() { *viewMode = ViewMode::Map2D;
*cameraMode = CameraMode::Top2D; rebuildCentral();
applyCurrentCamera();
rendererPtr->ResetCamera();
renderWindowPtr->Render();
}); });
QObject::connect(act3D, &QAction::triggered, vtkWidget, QObject::connect(act3D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() {
[cameraMode, applyCurrentCamera, rendererPtr, renderWindowPtr]() { *viewMode = ViewMode::View3D;
*cameraMode = CameraMode::Free3D; rebuildCentral();
applyCurrentCamera();
rendererPtr->ResetCamera();
renderWindowPtr->Render();
}); });
// ── 启动默认dd_section 已设为 Checked但 itemChanged 在 connect 之前触发故未渲染。 // ── 启动默认dd_section 已勾选,但 itemChanged 在 connect 之前触发故未渲染。
// 这里 connect 之后对已勾选 DS 主动触发一次帘面显示。 // 这里 connect 之后主动按默认视图(二维地图)重建一次中央内容。
{ rebuildCentral();
QList<QTreeWidgetItem*> stack;
for (int i = 0; i < tree->topLevelItemCount(); ++i) stack.append(tree->topLevelItem(i));
while (!stack.isEmpty()) {
QTreeWidgetItem* cur = stack.takeFirst();
const QString dsId = cur->data(0, kRoleDsId).toString();
if (!dsId.isEmpty() && cur->checkState(0) == Qt::Checked) {
const QString ddType = cur->data(0, kRoleDdType).toString();
PropList props = buildCurtainProps(dsId, ddType);
const bool wasEmpty = !hasVisible();
for (const auto& p : props) rendererPtr->AddViewProp(p);
(*dsProps)[dsId] = std::move(props);
if (wasEmpty) applyCurrentCamera();
}
for (int i = 0; i < cur->childCount(); ++i) stack.append(cur->child(i));
}
rendererPtr->ResetCamera();
renderWindowPtr->Render();
}
} }
} // namespace } // namespace