feat/vtk-3d-view #7
422
src/app/main.cpp
422
src/app/main.cpp
|
|
@ -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); // 默认二维,不显示图层浮层
|
|
||||||
|
|
||||||
// ──「三维数据集栏」工具条浮层(P2,spec §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); // 默认米(与控制器默认一致)
|
|
||||||
// 纵向比例滑块(范围 1–10,默认 2;spec §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);
|
|
||||||
|
|
||||||
// ──「切片」工具条浮层(P3,spec §9):浮于 QVTK 左下,仅三维 + 有体素时可用。
|
|
||||||
// 上下/前后/左右/任意 → 创建对应切片;关闭 → 关当前选中切片;翻转 → 水平 180°。
|
|
||||||
// 深色主题复用 P2 工具条同款样式(canvas/* token,不设 border-radius,GL 上四角露浅底)。
|
|
||||||
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点位置/字体本期 stub(TODO 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(非 window):ThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开,
|
// context 用 sceneCtrl(非 window):ThemeManager 是进程级单例,连接须随 sceneCtrl 析构自动断开,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue