feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
1 changed files with 62 additions and 360 deletions
Showing only changes of commit 5e57d462c8 - Show all commits

View File

@ -116,6 +116,10 @@
#include "panels/ObjectAttrPanel.hpp" #include "panels/ObjectAttrPanel.hpp"
#include "panels/DatasetAttrPanel.hpp" #include "panels/DatasetAttrPanel.hpp"
#include "panels/ObjectExceptionPanel.hpp" #include "panels/ObjectExceptionPanel.hpp"
#include "panels/columns/ColumnDrawer.hpp"
#include "panels/columns/Column3DDataset.hpp"
#include "panels/columns/Column2DDataset.hpp"
#include "panels/columns/Column3DAnalysis.hpp"
#include "CameraPreset.hpp" #include "CameraPreset.hpp"
#include "ColorLutBuilder.hpp" #include "ColorLutBuilder.hpp"
@ -186,59 +190,6 @@ private:
QWidget* host_; QWidget* host_;
}; };
// 把浮层锚定在 host 右上角P2 三维数据集栏工具条):随 host 尺寸变化重定位,
// 紧贴右边距 14px置于 header 下方 12px。仅在浮层可见时移动隐藏时不打扰
class RightTopAnchor : public QObject {
public:
RightTopAnchor(QWidget* overlay, QWidget* host, QWidget* header)
: QObject(host), overlay_(overlay), host_(host), header_(header)
{
host_->installEventFilter(this);
}
protected:
bool eventFilter(QObject* obj, QEvent* e) override
{
if (obj == host_ && (e->type() == QEvent::Resize || e->type() == QEvent::Show) &&
overlay_->isVisible()) {
overlay_->adjustSize();
overlay_->move(host_->width() - overlay_->width() - 14, header_->height() + 12);
overlay_->raise();
}
return QObject::eventFilter(obj, e);
}
private:
QWidget* overlay_;
QWidget* host_;
QWidget* header_;
};
// 浮层左下角锚定:随 host 尺寸变化贴左下(切片工具条用,评审 M4
class BottomLeftAnchor : public QObject {
public:
BottomLeftAnchor(QWidget* overlay, QWidget* host) : QObject(host), overlay_(overlay), host_(host)
{
host_->installEventFilter(this);
}
protected:
bool eventFilter(QObject* obj, QEvent* e) override
{
if (obj == host_ && (e->type() == QEvent::Resize || e->type() == QEvent::Show) &&
overlay_->isVisible()) {
overlay_->adjustSize();
overlay_->move(14, host_->height() - overlay_->height() - 14);
overlay_->raise();
}
return QObject::eventFilter(obj, e);
}
private:
QWidget* overlay_;
QWidget* host_;
};
// 读取 RSA 公钥 PEM 全文(登录时密码加密用)。读不到返回空串,登录将报错。 // 读取 RSA 公钥 PEM 全文(登录时密码加密用)。读不到返回空串,登录将报错。
std::string readPem(const std::string& path) std::string readPem(const std::string& path)
{ {
@ -308,7 +259,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 安装自定义拾取样式 + 持活动切片。仅三维 + 有体素可用;切到二维 closeAll。 // 安装自定义拾取样式 + 持活动切片。仅三维 + 有体素可用;切到二维 closeAll。
auto* interactionMgr = new geopro::render::interact::InteractionManager( auto* interactionMgr = new geopro::render::interact::InteractionManager(
renderWindowPtr->GetInteractor(), renderWindowPtr, scene->renderer()); renderWindowPtr->GetInteractor(), renderWindowPtr, scene->renderer());
// sceneView->onVolumeChanged 在切片 UI 接线处统一设置(需 updateSliceButtons 闭包,见下)。 // sceneView->onVolumeChanged 在三栏接线处设置(把体素 image 推给 InteractionManager,见下)。
// 非 QObject 堆对象统一在此清理,按构造逆序: // 非 QObject 堆对象统一在此清理,按构造逆序:
// interactionMgr(持 interactor/切片观察者) → sceneView(持 scene&) → scene3dRepo → scene。 // interactionMgr(持 interactor/切片观察者) → sceneView(持 scene&) → scene3dRepo → scene。
// interactionMgr 先析构closeAll() 解绑所有切片观察者,再拆 scene/interactor防悬挂崩溃。 // interactionMgr 先析构closeAll() 解绑所有切片观察者,再拆 scene/interactor防悬挂崩溃。
@ -320,7 +271,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
delete scene; delete scene;
}); });
// PROJ 可用性(体素/地形/切片层都需配准):失败则浮层相应勾选禁用并提示。 // PROJ 可用性探测(体素/地形/切片层都需配准):三栏重构后浮层勾选已移除,
// 仅保留探测以便将来在三栏里据此禁用相关项;本期结果暂未消费。
bool crsAvailable = false; bool crsAvailable = false;
try { try {
geopro::core::CrsTransform probe(kProjectCrs, kWgs84); geopro::core::CrsTransform probe(kProjectCrs, kWgs84);
@ -328,6 +280,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
} catch (const std::exception&) { } catch (const std::exception&) {
crsAvailable = false; crsAvailable = false;
} }
(void)crsAvailable;
// 停靠系统配置(必须在 CDockManager 构造前设置):对齐原型——面板固定、 // 停靠系统配置(必须在 CDockManager 构造前设置):对齐原型——面板固定、
// 标题栏不显示「关闭 / 浮动 / 标签菜单」等子窗口操作按钮,并关闭自动隐藏(钉住)。 // 标题栏不显示「关闭 / 浮动 / 标签菜单」等子窗口操作按钮,并关闭自动隐藏(钉住)。
@ -371,234 +324,71 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
return box; return box;
}; };
// 中央容器:顶部「二维地图/三维视图」工具条 + 下方 QVTK 视图 // 中央容器:顶部「VTK视图」表头 + 下方 [左三栏抽屉 | 右 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);
// 「二维地图/三维视图」分段切换表头:与「异常/属性」面板表头同款42px 表头底 + 强调色下划线页签)。 // VTK视图面板表头Task 7图标 + 标题「VTK视图」+ 全屏操作按钮(全屏 connect 见 Task 8
auto seg = geopro::app::buildSegmentedHeader( auto* viewHeader = geopro::app::buildPanelHeader(
{QStringLiteral("二维地图"), QStringLiteral("三维视图")}, geopro::app::Glyph::Map, QStringLiteral("VTK视图"),
{{geopro::app::Glyph::Collapse, QStringLiteral("折叠")}, {{geopro::app::Glyph::Fullscreen, QStringLiteral("全屏")}});
{geopro::app::Glyph::Download, QStringLiteral("导出")}});
auto* viewHeader = seg.header; // 左侧内嵌三栏抽屉(自带折叠按钮)+ 右侧 GL 画布,水平并列(非 GL 覆盖层,避免 z-order/圆角伪影)。
auto* act2D = seg.buttons[0]; auto* drawer = new geopro::app::ColumnDrawer(centerWidget);
auto* act3D = seg.buttons[1]; auto* viewRow = new QHBoxLayout();
viewRow->setContentsMargins(0, 0, 0, 0);
viewRow->setSpacing(0);
viewRow->addWidget(drawer); // 左侧抽屉(自带折叠按钮)
viewRow->addWidget(vtkWidget, 1); // 右侧 GL 画布
centerLayout->addWidget(viewHeader); centerLayout->addWidget(viewHeader);
centerLayout->addWidget(vtkWidget, 1); centerLayout->addLayout(viewRow, 1);
// ──「视图详情」图层浮层(对齐原型 3D 视图左上):浮在 QVTK 之上,控制三维图层显隐。 // 体素变化(重建/清场)后把体素 image 推给 InteractionManager切片基底
// 仅三维视图显示;含 帘面 / 体素 勾选(体素=两交叉测线散点配准 IDW 的派生层,正确归宿)。 sceneView->onVolumeChanged = [interactionMgr, sceneView]() {
auto* layerPanel = new QFrame(centerWidget);
layerPanel->setFrameShape(QFrame::StyledPanel);
geopro::app::applyTokenizedStyleSheet(
layerPanel,
// 不设 border-radius浮层是 centerWidget 的子控件,悬于原生 GL 画布上,圆角四角处会
// 露出父级浅底(浅色模式下即四个白直角)。改为直角矩形,不透明底铺满整块,无四角伪影。
QStringLiteral("QFrame{background:{{canvas/bg-soft}};border:1px solid {{canvas/grid}};}"
"QCheckBox{padding:2px 1px;color:{{canvas/text}};}"
"QCheckBox:disabled{color:{{canvas/text-dim}};}"));
auto* layerLayout = new QVBoxLayout(layerPanel);
// 浮层内边距取间距令牌:左右 lg(12)、上下 ml(10),对称(原 13/10/15/11 是手调奇数)。
layerLayout->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kMl,
geopro::app::space::kLg, geopro::app::space::kMl);
layerLayout->setSpacing(geopro::app::space::kSm);
auto* layerTitle = new QLabel(QStringLiteral("视图详情"));
geopro::app::applyTokenizedStyleSheet(
layerTitle, QStringLiteral("font-weight:%1;color:{{canvas/text}};border:none;background:transparent;"
"padding-bottom:3px;font-size:%2px;")
.arg(geopro::app::type::kWeightSemibold)
.arg(geopro::app::scaledPx(geopro::app::type::kTitle)));
auto* chkCurtain = new QCheckBox(QStringLiteral("帘面(断面墙)"));
chkCurtain->setChecked(true);
auto* chkVoxel = new QCheckBox(QStringLiteral("体素dd_voxel"));
chkVoxel->setChecked(false);
auto* chkTerrain = new QCheckBox(QStringLiteral("地形DEM+影像)"));
chkTerrain->setChecked(false);
// 切片交互已由 P3 左下「切片」工具条提供,此处不再放(原 P1 占位禁用项已移除)。
if (!crsAvailable) { // PROJ 不可用 → 体素/地形层(都需配准)禁用并提示
const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用");
chkVoxel->setEnabled(false); chkVoxel->setToolTip(tip);
chkTerrain->setEnabled(false); chkTerrain->setToolTip(tip);
}
layerLayout->addWidget(layerTitle);
layerLayout->addWidget(chkCurtain);
layerLayout->addWidget(chkVoxel);
layerLayout->addWidget(chkTerrain);
layerPanel->setVisible(false); // 默认二维,不显示图层浮层
// ──「三维数据集栏」工具条浮层P2spec §7.2):浮于 QVTK 右上,仅三维视图显示。
// 坐标轴下拉(标准/立体/不显示) + 刻度下拉(无/米/英尺/经纬度) + 纵向比例滑块 + 快捷视图 6 钮 + Zoom(In/Out/Fit)。
auto* axisBar = new QFrame(centerWidget);
axisBar->setFrameShape(QFrame::StyledPanel);
// 深色主题样式:控件须显式设深底(否则默认白底 + 浅字 → 看不清)。下拉弹窗(QAbstractItemView)
// 是独立顶层窗,须单独配色。与左上「视图详情」浮层同一套 canvas/* token。
geopro::app::applyTokenizedStyleSheet(
axisBar,
QStringLiteral(
// 不设 border-radius浮层悬于原生 GL 画布上,圆角四角会露父级浅底(四个白直角)。
// 与左上「视图详情」浮层同法:直角矩形 + 不透明底铺满。
"QFrame{background:{{canvas/bg-soft}};border:1px solid {{canvas/grid}};}"
"QLabel{color:{{canvas/text}};border:none;background:transparent;}"
"QComboBox{color:{{canvas/text}};background:{{canvas/bg}};border:1px solid {{canvas/grid}};"
"border-radius:4px;padding:2px 6px;}"
"QComboBox:hover{border-color:{{accent/primary}};}"
"QComboBox::drop-down{border:none;width:16px;}"
"QComboBox QAbstractItemView{color:{{canvas/text}};background:{{canvas/bg}};"
"border:1px solid {{canvas/grid}};selection-background-color:{{bg/selected}};"
"selection-color:{{canvas/text}};outline:none;}"
"QPushButton{color:{{canvas/text}};background:{{canvas/bg}};border:1px solid {{canvas/grid}};"
"border-radius:4px;padding:2px 8px;}"
"QPushButton:hover{background:{{bg/hover}};border-color:{{accent/primary}};}"
"QPushButton:pressed{background:{{bg/selected}};}"
"QSlider::groove:horizontal{height:4px;background:{{canvas/grid}};border-radius:2px;}"
"QSlider::handle:horizontal{width:12px;background:{{accent/primary}};"
"border-radius:6px;margin:-4px 0;}"));
auto* axisLayout = new QHBoxLayout(axisBar);
axisLayout->setContentsMargins(geopro::app::space::kMd, geopro::app::space::kSm,
geopro::app::space::kMd, geopro::app::space::kSm);
axisLayout->setSpacing(geopro::app::space::kSm);
// 坐标轴显示方式下拉(枚举值绑到 itemData槽用 currentData 取值,不依赖项顺序)。
auto* axesModeCombo = new QComboBox();
axesModeCombo->addItem(QStringLiteral("坐标轴:标准"),
static_cast<int>(geopro::controller::AxesMode::Standard));
axesModeCombo->addItem(QStringLiteral("坐标轴:三维立体"),
static_cast<int>(geopro::controller::AxesMode::Stereo));
axesModeCombo->addItem(QStringLiteral("坐标轴:不显示"),
static_cast<int>(geopro::controller::AxesMode::None));
// 刻度单位下拉。
auto* axesUnitCombo = new QComboBox();
axesUnitCombo->addItem(QStringLiteral("刻度:无"),
static_cast<int>(geopro::controller::AxesUnit::None));
axesUnitCombo->addItem(QStringLiteral("刻度:米"),
static_cast<int>(geopro::controller::AxesUnit::Meter));
axesUnitCombo->addItem(QStringLiteral("刻度:英尺"),
static_cast<int>(geopro::controller::AxesUnit::Feet));
axesUnitCombo->addItem(QStringLiteral("刻度:经纬度"),
static_cast<int>(geopro::controller::AxesUnit::LatLon));
axesUnitCombo->setCurrentIndex(1); // 默认米(与控制器默认一致)
// 纵向比例滑块(范围 110默认 2spec §4 C6
auto* veLabel = new QLabel(QStringLiteral("比例"));
auto* veSlider = new QSlider(Qt::Horizontal);
veSlider->setMinimum(1);
veSlider->setMaximum(10);
veSlider->setValue(static_cast<int>(kVerticalExaggeration));
veSlider->setFixedWidth(80);
auto* veValue = new QLabel(QStringLiteral("%1x").arg(static_cast<int>(kVerticalExaggeration)));
// 快捷视图 6 钮。
auto* btnTop = new QPushButton(QStringLiteral(""));
auto* btnBottom = new QPushButton(QStringLiteral(""));
auto* btnFront = new QPushButton(QStringLiteral(""));
auto* btnBack = new QPushButton(QStringLiteral(""));
auto* btnLeft = new QPushButton(QStringLiteral(""));
auto* btnRight = new QPushButton(QStringLiteral(""));
// Zoom 3 钮。
auto* btnZoomIn = new QPushButton(QStringLiteral("放大"));
auto* btnZoomOut = new QPushButton(QStringLiteral("缩小"));
auto* btnFit = new QPushButton(QStringLiteral("适配"));
axisLayout->addWidget(axesModeCombo);
axisLayout->addWidget(axesUnitCombo);
axisLayout->addWidget(veLabel);
axisLayout->addWidget(veSlider);
axisLayout->addWidget(veValue);
axisLayout->addWidget(btnFront);
axisLayout->addWidget(btnBack);
axisLayout->addWidget(btnLeft);
axisLayout->addWidget(btnRight);
axisLayout->addWidget(btnTop);
axisLayout->addWidget(btnBottom);
axisLayout->addWidget(btnZoomIn);
axisLayout->addWidget(btnZoomOut);
axisLayout->addWidget(btnFit);
axisBar->setVisible(false); // 默认二维,不显示
// P2 工具条右上锚定:随 centerWidget 尺寸变化重定位(紧贴右边距 14px工具条下方
// 锚定器 parent=centerWidget随其销毁不需保留指针。
new RightTopAnchor(axisBar, centerWidget, viewHeader);
// ──「切片」工具条浮层P3spec §9浮于 QVTK 左下,仅三维 + 有体素时可用。
// 上下/前后/左右/任意 → 创建对应切片;关闭 → 关当前选中切片;翻转 → 水平 180°。
// 深色主题复用 P2 工具条同款样式canvas/* token不设 border-radiusGL 上四角露浅底)。
auto* sliceBar = new QFrame(centerWidget);
sliceBar->setFrameShape(QFrame::StyledPanel);
geopro::app::applyTokenizedStyleSheet(
sliceBar,
QStringLiteral(
"QFrame{background:{{canvas/bg-soft}};border:1px solid {{canvas/grid}};}"
"QLabel{color:{{canvas/text}};border:none;background:transparent;}"
"QPushButton{color:{{canvas/text}};background:{{canvas/bg}};border:1px solid {{canvas/grid}};"
"border-radius:4px;padding:2px 8px;}"
"QPushButton:hover{background:{{bg/hover}};border-color:{{accent/primary}};}"
"QPushButton:pressed{background:{{bg/selected}};}"
"QPushButton:disabled{color:{{canvas/text-dim}};border-color:{{canvas/grid}};}"));
auto* sliceLayout = new QHBoxLayout(sliceBar);
sliceLayout->setContentsMargins(geopro::app::space::kMd, geopro::app::space::kSm,
geopro::app::space::kMd, geopro::app::space::kSm);
sliceLayout->setSpacing(geopro::app::space::kSm);
auto* sliceLabel = new QLabel(QStringLiteral("切片"));
auto* btnSliceUpDown = new QPushButton(QStringLiteral("上下"));
auto* btnSliceFrontBack = new QPushButton(QStringLiteral("前后"));
auto* btnSliceLeftRight = new QPushButton(QStringLiteral("左右"));
auto* btnSliceOblique = new QPushButton(QStringLiteral("任意"));
auto* btnSliceFlip = new QPushButton(QStringLiteral("翻转"));
auto* btnSliceClose = new QPushButton(QStringLiteral("关闭"));
sliceLayout->addWidget(sliceLabel);
sliceLayout->addWidget(btnSliceUpDown);
sliceLayout->addWidget(btnSliceFrontBack);
sliceLayout->addWidget(btnSliceLeftRight);
sliceLayout->addWidget(btnSliceOblique);
sliceLayout->addWidget(btnSliceFlip);
sliceLayout->addWidget(btnSliceClose);
sliceBar->setVisible(false); // 默认二维,不显示
new BottomLeftAnchor(sliceBar, centerWidget); // 随窗口 resize 贴左下(评审 M4
// 切片按钮可用性:仅三维 + 有体素时创建/翻转可用;关闭仅在有切片时可用。
auto updateSliceButtons = [interactionMgr, btnSliceUpDown, btnSliceFrontBack, btnSliceLeftRight,
btnSliceOblique, btnSliceFlip, btnSliceClose, sceneView]() {
const bool canSlice = sceneView->hasVolume() && interactionMgr->hasVolume();
btnSliceUpDown->setEnabled(canSlice);
btnSliceFrontBack->setEnabled(canSlice);
btnSliceLeftRight->setEnabled(canSlice);
btnSliceOblique->setEnabled(canSlice);
btnSliceFlip->setEnabled(canSlice);
btnSliceClose->setEnabled(interactionMgr->hasSlices());
};
updateSliceButtons();
using SliceAxis = geopro::render::interact::SliceAxis;
auto addSlice = [interactionMgr, updateSliceButtons](SliceAxis axis) {
interactionMgr->addSlice(axis);
updateSliceButtons();
};
QObject::connect(btnSliceUpDown, &QPushButton::clicked, vtkWidget,
[addSlice]() { addSlice(SliceAxis::UpDown); });
QObject::connect(btnSliceFrontBack, &QPushButton::clicked, vtkWidget,
[addSlice]() { addSlice(SliceAxis::FrontBack); });
QObject::connect(btnSliceLeftRight, &QPushButton::clicked, vtkWidget,
[addSlice]() { addSlice(SliceAxis::LeftRight); });
QObject::connect(btnSliceOblique, &QPushButton::clicked, vtkWidget,
[addSlice]() { addSlice(SliceAxis::Oblique); });
QObject::connect(btnSliceFlip, &QPushButton::clicked, vtkWidget,
[interactionMgr]() { interactionMgr->flipView(); });
QObject::connect(btnSliceClose, &QPushButton::clicked, vtkWidget,
[interactionMgr, updateSliceButtons]() {
interactionMgr->closeSelected();
updateSliceButtons();
});
// 体素变化(重建/清场)后刷新按钮可用性(切片可能已被 closeAll 清空)。
sceneView->onVolumeChanged = [interactionMgr, sceneView, updateSliceButtons]() {
if (sceneView->hasVolume()) if (sceneView->hasVolume())
interactionMgr->setVolumeImage(sceneView->currentVolumeImage(), interactionMgr->setVolumeImage(sceneView->currentVolumeImage(),
sceneView->currentColorScale(), sceneView->currentVmin(), sceneView->currentColorScale(), sceneView->currentVmin(),
sceneView->currentVmax()); sceneView->currentVmax());
else else
interactionMgr->setVolumeImage(nullptr, sceneView->currentColorScale(), 0.0, 0.0); interactionMgr->setVolumeImage(nullptr, sceneView->currentColorScale(), 0.0, 0.0);
updateSliceButtons();
}; };
// ── 三栏抽屉信号 → 控制器/交互Task 7 接线)──────────────────────────────
auto* c3 = drawer->col3D();
QObject::connect(c3, &geopro::app::Column3DDataset::axesModeChanged, sceneCtrl,
&geopro::controller::VtkSceneController::setAxesMode);
QObject::connect(c3, &geopro::app::Column3DDataset::axesUnitChanged, sceneCtrl,
&geopro::controller::VtkSceneController::setAxesUnit);
QObject::connect(c3, &geopro::app::Column3DDataset::verticalExaggerationChanged, sceneCtrl,
&geopro::controller::VtkSceneController::setVerticalExaggeration);
QObject::connect(c3, &geopro::app::Column3DDataset::viewRequested, sceneCtrl,
&geopro::controller::VtkSceneController::applyView);
QObject::connect(c3, &geopro::app::Column3DDataset::zoomInRequested, sceneCtrl,
&geopro::controller::VtkSceneController::zoomIn);
QObject::connect(c3, &geopro::app::Column3DDataset::zoomOutRequested, sceneCtrl,
&geopro::controller::VtkSceneController::zoomOut);
QObject::connect(c3, &geopro::app::Column3DDataset::fitRequested, sceneCtrl,
&geopro::controller::VtkSceneController::fit);
QObject::connect(c3, &geopro::app::Column3DDataset::checkedDatasetsChanged, sceneCtrl,
&geopro::controller::VtkSceneController::setCheckedDatasets);
// O点位置/字体本期 stubTODO P4弹框
QObject::connect(c3, &geopro::app::Column3DDataset::oPointClicked, vtkWidget,
[]() { /* TODO P4: O点位置弹框 */ });
QObject::connect(c3, &geopro::app::Column3DDataset::fontClicked, vtkWidget,
[]() { /* TODO P4: 字体弹框 */ });
auto* ca = drawer->colAnalysis();
QObject::connect(ca, &geopro::app::Column3DAnalysis::sliceRequested, vtkWidget,
[interactionMgr](geopro::render::interact::SliceAxis axis) {
interactionMgr->addSlice(axis);
});
QObject::connect(ca, &geopro::app::Column3DAnalysis::detailRequested, &detailCtrl,
[&detailCtrl](const QString& dsId, const QString& ddCode, const QString& name) {
detailCtrl.openDataset(dsId, ddCode, name);
});
// ── 中央“空状态”引导浮层:未接入真实 sections 时,引导首次使用者从左侧入手。── // ── 中央“空状态”引导浮层:未接入真实 sections 时,引导首次使用者从左侧入手。──
// 透明背景 + 鼠标穿透(不挡 QVTK 交互CenterOverlay 随视口尺寸保持居中; // 透明背景 + 鼠标穿透(不挡 QVTK 交互CenterOverlay 随视口尺寸保持居中;
// 接入真实中央数据后改成依 sections 是否为空调 setVisible 即可。 // 接入真实中央数据后改成依 sections 是否为空调 setVisible 即可。
@ -645,7 +435,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
auto* emptyCentering = new CenterOverlay(emptyState, vtkWidget); auto* emptyCentering = new CenterOverlay(emptyState, vtkWidget);
emptyCentering->reposition(); emptyCentering->reposition();
auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维地图/三维视图")); auto* vtkDock = new ads::CDockWidget(QStringLiteral("VTK视图"));
vtkDock->setWidget(centerWidget); vtkDock->setWidget(centerWidget);
auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
@ -799,95 +589,6 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
} }
}); });
// 「视图详情」浮层 + 「三维数据集栏」工具条显隐:仅三维显示。
// 视图详情浮层置左上P2 工具条置右上(工具条下方),二者均随相机/数据变化保持位置。
auto showLayerPanel = [layerPanel, axisBar, sliceBar, viewHeader, centerWidget,
updateSliceButtons](bool show3D) {
if (show3D) {
layerPanel->move(14, viewHeader->height() + 12);
layerPanel->adjustSize();
layerPanel->setVisible(true);
layerPanel->raise();
axisBar->adjustSize();
// 右上对齐:紧贴右边距 14px。
axisBar->move(centerWidget->width() - axisBar->width() - 14, viewHeader->height() + 12);
axisBar->setVisible(true);
axisBar->raise();
// 切片工具条:左下角(视图详情浮层下方)。
sliceBar->adjustSize();
sliceBar->move(14, centerWidget->height() - sliceBar->height() - 14);
sliceBar->setVisible(true);
sliceBar->raise();
updateSliceButtons();
} else {
layerPanel->setVisible(false);
axisBar->setVisible(false);
sliceBar->setVisible(false);
}
};
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 控制器重建 + 图层浮层显隐 ──
using geopro::controller::SceneLayer;
using CtrlViewMode = geopro::controller::ViewMode;
QObject::connect(act2D, &QAbstractButton::clicked, vtkWidget,
[sceneCtrl, showLayerPanel, interactionMgr, updateSliceButtons]() {
interactionMgr->closeAll(); // 切到二维:关闭所有切片(仅三维有切片)
updateSliceButtons();
showLayerPanel(false);
sceneCtrl->setViewMode(CtrlViewMode::Map2D);
});
QObject::connect(act3D, &QAbstractButton::clicked, vtkWidget,
[sceneCtrl, showLayerPanel]() {
showLayerPanel(true);
sceneCtrl->setViewMode(CtrlViewMode::View3D);
});
// ──「视图详情」图层勾选 → 控制器更新图层 → 重建中央 ──
QObject::connect(chkCurtain, &QCheckBox::toggled, vtkWidget,
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Curtain, on); });
QObject::connect(chkVoxel, &QCheckBox::toggled, vtkWidget,
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Voxel, on); });
QObject::connect(chkTerrain, &QCheckBox::toggled, vtkWidget,
[sceneCtrl](bool on) { sceneCtrl->setLayer(SceneLayer::Terrain, on); });
// ──「三维数据集栏」工具条 → 控制器槽P2──
using geopro::controller::AxesMode;
using geopro::controller::AxesUnit;
using geopro::controller::ViewDir;
QObject::connect(axesModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), sceneCtrl,
[sceneCtrl, axesModeCombo](int) {
sceneCtrl->setAxesMode(
static_cast<AxesMode>(axesModeCombo->currentData().toInt()));
});
QObject::connect(axesUnitCombo, qOverload<int>(&QComboBox::currentIndexChanged), sceneCtrl,
[sceneCtrl, axesUnitCombo](int) {
sceneCtrl->setAxesUnit(
static_cast<AxesUnit>(axesUnitCombo->currentData().toInt()));
});
QObject::connect(veSlider, &QSlider::valueChanged, sceneCtrl,
[sceneCtrl, veValue](int v) {
veValue->setText(QStringLiteral("%1x").arg(v));
sceneCtrl->setVerticalExaggeration(static_cast<double>(v));
});
QObject::connect(btnFront, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->applyView(ViewDir::Front); });
QObject::connect(btnBack, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->applyView(ViewDir::Back); });
QObject::connect(btnLeft, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->applyView(ViewDir::Left); });
QObject::connect(btnRight, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->applyView(ViewDir::Right); });
QObject::connect(btnTop, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->applyView(ViewDir::Top); });
QObject::connect(btnBottom, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->applyView(ViewDir::Bottom); });
QObject::connect(btnZoomIn, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->zoomIn(); });
QObject::connect(btnZoomOut, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->zoomOut(); });
QObject::connect(btnFit, &QPushButton::clicked, sceneCtrl,
[sceneCtrl]() { sceneCtrl->fit(); });
// ── 左上对象树勾选 → 渲染勾选数据集(本期样本驱动:任意勾选 → 样本 ds "grid1",空 → 清场)── // ── 左上对象树勾选 → 渲染勾选数据集(本期样本驱动:任意勾选 → 样本 ds "grid1",空 → 清场)──
// 真实接 Api 时改为把勾选 TM 映射到其 ds 维度过滤后的真实 dsId 列表spec §6.1/§8 // 真实接 Api 时改为把勾选 TM 映射到其 ds 维度过滤后的真实 dsId 列表spec §6.1/§8
QObject::connect(objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, sceneCtrl, QObject::connect(objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, sceneCtrl,
@ -898,8 +599,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
hasData ? QStringList{QStringLiteral("grid1")} : QStringList{}); hasData ? QStringList{QStringLiteral("grid1")} : QStringList{});
}); });
// ── 启动:建立一次中央视图(默认 2D无勾选 → 空场景 + 背景)。 // ── 启动:建立一次中央视图。三栏重构后删除了 2D/3D 切换,统一固定为三维视图
sceneCtrl->setViewMode(CtrlViewMode::Map2D); // (帘面默认开启 showCurtain_=true勾选 dd_section → 帘面)。无勾选 → 空场景 + 背景。
sceneCtrl->setViewMode(geopro::controller::ViewMode::View3D);
// VTK 背景随主题切换:控制器重渲染(走完整渲染路径、末尾必 Render // VTK 背景随主题切换:控制器重渲染(走完整渲染路径、末尾必 Render
// context 用 sceneCtrl非 windowThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开, // context 用 sceneCtrl非 windowThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开,