feat(view): 3D「视图详情」图层浮层 + 体素正经接入(对齐原型, 增量3)

- 中央 QVTK 左上浮层(QFrame, 仅三维显示, 工具条下方, raise 置顶): 图层勾选「帘面 / 体素」。
- rebuildCentral: 帘面层 gate buildCurtain; 体素层 → buildVoxelFromScatters 体绘制(同纵向夸张)。
  showCurtain(默认开)/showVoxel(默认关)/crs 共享态; 切视图自动显隐浮层。
- 体素经此正经接入(取代 42a7ed1 移除的困惑工具条开关, 这才是它对齐原型的归宿)。
- main() 自动定位 PROJ_DATA(候选路径; 部署须随包附带 proj 数据); PROJ 不可用→体素勾选禁用+提示。
- app 构建干净; 待人工登录复核(浮层渲染于 QVTK 之上 + 勾体素出十字片)。
This commit is contained in:
gaozheng 2026-06-08 09:42:09 +08:00
parent 50c4de4019
commit a2efef8ada
3 changed files with 126 additions and 20 deletions

View File

@ -17,7 +17,7 @@
- **左下 数据真实显示栏**(对齐原型):单击测线 → 列其采集批次(数据集,tab 数据/文件);单击采集批次 → 数据详情+异常列表+属性。启动自动选首测线+首数据集。
- **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换):
- 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。
- 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。中央工具条**仅**「二维地图/三维视图」(对齐原型,无体素按钮)。
- 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。中央工具条**仅**「二维地图/三维视图」。**3D 左上「视图详情」浮层**(对齐原型,仅 3D 显示):图层勾选 **帘面 / 体素**(体素=`buildVoxelFromScatters` 两交叉测线散点经 EPSG:4547 配准 IDW,与帘面同纵向夸张;PROJ 不可用则禁用)。
- **下方 数据详情**:工具条「原数据/网格数据」切换 +「显示异常」开关(对齐原型命名)。单击数据集 → 网格数据=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17,x=距离/y=深度取负,用散点自带色阶);显示异常=`AnomalyActor` 在上图叠加异常 dashed 折线(同纵向夸张对齐)。
- **右上 异常列表**(对齐原型):单击数据集→列该数据集异常(颜色块+名称(类型)+派生「位置/深/尺寸」),勾选框显隐,与数据详情异常叠加联动(取消勾选→该异常虚线隐藏)。
- **右下 属性**:名称/网格 nx×ny/vmin·vmax/异常数。
@ -69,7 +69,7 @@
1. ~~**散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),数据详情"原数据"视图~~**已完成**(2026-06-08,离屏 PNG 核对吻合 Python 真值,接入数据详情「反演剖面/原数据」切换;app 待人工登录肉眼复核交互)。
2. ~~**异常叠加**:`AnomalyActor`(markType 点/线/面)~~**已完成**(2026-06-08,叠加在数据详情 #18/#17 上,「显示异常」开关默认开;离屏 `verify_section_anomaly.png` 折线位置吻合 ref_18;样本 3 异常均 markType=2 dashed;app 待人工登录复核)。**注**:dashed 点画在 VTK OpenGL2 下偏弱(几乎实线),几何/颜色/位置正确,纯观感项可后续调。
3. **DEM/影像地形**:加 vcpkg `gdal`;GDAL 读 dem.tif/image.tif;**影像 EPSG:3857 必须 PROJ 重投影到世界系**;`vtkWarpScalar` 地形面 + 纹理。
4. **dd_voxel 回归**:🔶 **引擎已完成并验证**(2026-06-08,CRS 已定 EPSG:4547)。`render::buildVoxelFromScatters`:散点 projX/Y→4547→4326→GeoLocalFrame 配准 + IDW(maxDist 裁剪)→ `buildVoxel`;离屏 `verify_voxel_top.png` 两臂支撑吻合 ref voxel_hslice、`verify_voxel_3d.png` profile1 片贴合帘面;+1 单测(VoxelRegister,需 PROJ_DATA)。**UI 未接入**:曾加"体素"工具条开关,但与二维/三维平级令人困惑且不在原型,**已移除**;待做 **3D 图层控制(对齐原型「视图详情」浮层)** 再正经接入(届时 main() 需设 PROJ_DATA + 部署随包附带 proj 数据)。**注**:仅 2 交叉线→薄十字片(15.9% 充填,半透明偏淡),可信满体需≥3线(设计 §10/§14)。**dd_slice 交互切片未做**(buildVoxel 已暴露 image 供 reslice widget)。
4. **dd_voxel 回归**:**已完成**(2026-06-08,CRS 已定 EPSG:4547)。`render::buildVoxelFromScatters`:散点 projX/Y→4547→4326→GeoLocalFrame 配准 + IDW(maxDist 裁剪)→ `buildVoxel`;离屏 `verify_voxel_top.png` 两臂支撑吻合 ref voxel_hslice、`verify_voxel_3d.png` profile1 片贴合帘面;+1 单测(VoxelRegister,需 PROJ_DATA)。**UI 已接入**(增量3):3D「视图详情」浮层「体素」图层勾选驱动;main() 自动设 PROJ_DATA(部署须随包附带 proj 数据);PROJ 不可用则该层禁用。**注**:仅 2 交叉线→薄十字片(15.9% 充填,半透明偏淡),可信满体需≥3线(设计 §10/§14)。**dd_slice 交互切片未做**(buildVoxel 已暴露 image 供 reslice widget)。
5. **底图瓦片**(二维地图,天地图/Mapbox):M1.5。
6. **Credential(QtKeychain)**:记住一个月免登录持久化(P3 Task2 未做)。
7. 多测线:当前样本仅 1 条 dd_section(grid1);多条共存机制已就绪,加数据即叠加。
@ -78,7 +78,7 @@
10. **布局对齐原型**(权威参考 `http://prototype.geomative.cn/`;截图存 `.playwright-mcp/`)。**计划见 `plans/2026-06-08-m1-prototype-layout.md`(六面板 + view/ 抽取,增量序列)**。进度:
- ✅ **增量1 右上「异常列表」**(2026-06-08,`panels/AnomalyListPanel`,与数据详情显隐联动;待人工复核)。
- ✅ **增量2 左下「数据列表」+ 对象树到 TM 层**(2026-06-08,`panels/DatasetListPanel`;树 GS→TM 复选驱动中央, DS 移出树入数据列表 tab 数据/文件, DS 单击→详情+异常+属性, 启动自动选首测线/首数据集;待人工复核)。
- **增量3 3D「视图详情」图层浮层**(体素的正确归宿;体素引擎已就绪,UI 待此接入)。
- **增量3 3D「视图详情」图层浮层**(2026-06-08,QFrame 浮于 QVTK 左上,仅 3D 显示;帘面/体素图层勾选;体素经此正经接入=之前移除的工具条开关的正确归宿;main() 设 PROJ_DATA;待人工复核浮层渲染+体素显隐)。
- ⬜ **增量4 数据详情富工具条 + 电极标记 + 数值标签**;底图影像=DEM/底图任务。
- 架构:新面板抽到 `src/app/panels/`(暂随 app 编译,如 login/),控制 main.cpp 体量;后续可升 `src/view/` 库。

View File

@ -70,7 +70,11 @@
- **人工复核**:点 ERT1 → 左下列出采集批次 → 单击 → 数据详情出图。
- 提交 `feat(view): 左下数据列表 + 对象树到测线层(DS 移出树)`
### 增量 33D「视图详情」图层浮层体素的正确归宿
### 增量 33D「视图详情」图层浮层 ✅ 已完成(2026-06-08)
> main.cpp 内联 QFrame 浮层(child of centerWidget,move 到 QVTK 左上=工具条下方,raise;仅 3D setVisible)。
> 含 帘面/体素 QCheckBox。rebuildCentral:帘面层 gate buildCurtain;体素层→buildVoxelFromScatters 加体绘制
> (同纵向夸张)。showCurtain/showVoxel/crs 共享态。main() 自动设 PROJ_DATA;crs 失败→体素勾选禁用。
> app 构建净;**待人工复核**(浮层是否正确浮于 QVTK 之上、勾体素是否出十字片)。**原计划存档**:
- 中央三维视图左上浮层QWidget overlay on QVTK或工具区勾选显示 测线/帘面/**体素**/(地形)。
- 勾"体素" → 调 `buildVoxelFromScatters`(已验证)加入 3D 场景;勾选驱动 rebuildCentral 的图层集。
- main() 设 PROJ_DATA体素配准需 proj.db按候选路径部署随包附带

View File

@ -3,10 +3,9 @@
// - 左下 数据真实显示栏:单击测线 → 列其采集批次(数据集,tab 数据/文件)。单击采集批次 → 数据详情+异常+属性。
// - 中央「二维地图 / 三维视图」:两个互斥视图(内容不同,不是同一物体换相机)。
// 二维地图 = 对每个勾选数据集 buildSurveyLinelat/lon 红线俯视z=0+ applyTop2D浅底背景
// 三维视图 = 对每个勾选数据集 buildCurtain竖直断面墙actor SetScale(1,1,3) 纵向夸张 + applyFree3D白底
// 切视图 / 勾选变化 → 按当前勾选集重建对应内容。
// 注dd_voxel 体素引擎(render::buildVoxelFromScatters)已就绪并验证,但**不**作为工具条开关
// (那样与二维/三维平级会令人困惑、且不在原型);待做 3D 图层控制(对齐原型「视图详情」浮层)再接。
// 三维视图 = 勾选测线的 buildCurtain竖直断面墙actor SetScale(1,1,3) 纵向夸张 + applyFree3D白底
// 三维左上「视图详情」浮层(对齐原型):图层勾选 帘面 / 体素(dd_voxel,散点经 EPSG:4547 配准 IDW)。
// 切视图 / 勾选变化 / 图层变化 → 重建对应内容。
// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「原数据 / 网格数据」切换 +「显示异常」开关(对齐原型)。
// 单击某 DS → 显示该数据集:
// 网格数据 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。
@ -24,11 +23,15 @@
#include <QActionGroup>
#include <QApplication>
#include <QCheckBox>
#include <QDialog>
#include <QFile>
#include <QFrame>
#include <QLabel>
#include <QListWidget>
#include <QListWidgetItem>
#include <QSignalBlocker>
#include <QStringList>
#include <QTabWidget>
#include <QMainWindow>
#include <QSurfaceFormat>
@ -53,15 +56,18 @@
#include "CameraPreset.hpp"
#include "Scene.hpp"
#include "VoxelFromScatters.hpp"
#include "actors/AnomalyActor.hpp"
#include "actors/CurtainActor.hpp"
#include "actors/GridContourActor.hpp"
#include "actors/MapLineActor.hpp"
#include "actors/ScatterActor.hpp"
#include "geo/CrsTransform.hpp"
#include "geo/GeoLocalFrame.hpp"
#include <algorithm>
#include <exception>
#include <memory>
#include <set>
#include <vector>
@ -140,6 +146,10 @@ constexpr float kScatterPointSize = 4.0F;
constexpr double kCurtainZScale = 3.0;
constexpr double kDetailYScale = 1.5;
// 项目 CRS(实证确定,STATUS §4):散点 projX/Y→经纬→GeoLocalFrame 配准体素到世界系。
constexpr const char* kProjectCrs = "EPSG:4547"; // CGCS2000 / 3-degree GK CM 114E
constexpr const char* kWgs84 = "EPSG:4326";
// 在给定 QMainWindow 上构建 M1 工作台。
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo)
@ -166,6 +176,16 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 当前视图模式(全局共享,切视图/勾选时据此重建内容)。默认二维地图。
auto viewMode = std::make_shared<ViewMode>(ViewMode::Map2D);
// 三维图层显隐(由「视图详情」浮层控制)+ 项目 CRS→WGS84(体素配准)。
auto showCurtain = std::make_shared<bool>(true); // 帘面,默认显示
auto showVoxel = std::make_shared<bool>(false); // 体素,默认关
std::shared_ptr<geopro::core::CrsTransform> crs; // PROJ 失败→空→体素层无效(不崩)
try {
crs = std::make_shared<geopro::core::CrsTransform>(kProjectCrs, kWgs84);
} catch (const std::exception&) {
crs.reset();
}
auto* dockManager = new ads::CDockManager(&window);
window.setCentralWidget(dockManager);
@ -189,6 +209,31 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
centerLayout->addWidget(viewToolBar);
centerLayout->addWidget(vtkWidget, 1);
// ──「视图详情」图层浮层(对齐原型 3D 视图左上):浮在 QVTK 之上,控制三维图层显隐。
// 仅三维视图显示;含 帘面 / 体素 勾选(体素=两交叉测线散点配准 IDW 的派生层,正确归宿)。
auto* layerPanel = new QFrame(centerWidget);
layerPanel->setFrameShape(QFrame::StyledPanel);
layerPanel->setStyleSheet(
QStringLiteral("QFrame{background:rgba(255,255,255,0.92);border:1px solid #b0b4bb;"
"border-radius:6px;} QCheckBox{padding:1px;}"));
auto* layerLayout = new QVBoxLayout(layerPanel);
layerLayout->setContentsMargins(10, 8, 12, 8);
layerLayout->setSpacing(4);
auto* layerTitle = new QLabel(QStringLiteral("视图详情"));
layerTitle->setStyleSheet(QStringLiteral("font-weight:bold;border:none;background:transparent;"));
auto* chkCurtain = new QCheckBox(QStringLiteral("帘面(断面墙)"));
chkCurtain->setChecked(true);
auto* chkVoxel = new QCheckBox(QStringLiteral("体素dd_voxel"));
chkVoxel->setChecked(false);
if (!crs) { // PROJ 不可用 → 体素层禁用并提示
chkVoxel->setEnabled(false);
chkVoxel->setToolTip(QStringLiteral("PROJ 数据(proj.db)缺失,体素配准不可用"));
}
layerLayout->addWidget(layerTitle);
layerLayout->addWidget(chkCurtain);
layerLayout->addWidget(chkVoxel);
layerPanel->setVisible(false); // 默认二维,不显示图层浮层
auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维地图/三维视图"));
vtkDock->setWidget(centerWidget);
auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
@ -277,19 +322,19 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 三维视图 = buildCurtain断面墙SetScale(1,1,kCurtainZScale) + applyFree3D白底
// frame/structure 全局共享;切视图/勾选变化都调用此函数重建当前视图。
auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, &repo, frame, tree,
structure]() {
structure, showCurtain, showVoxel, crs]() {
scene->clear();
const bool is2D = (*viewMode == ViewMode::Map2D);
rendererPtr->SetBackground(is2D ? 0.96 : 1.0, is2D ? 0.97 : 1.0, is2D ? 0.99 : 1.0);
// 渲染单个 dd_section 数据集到当前视图
// 渲染单个 dd_section 数据集:二维=测线线;三维=帘面(受「帘面」图层开关控制)
auto renderSection = [&](const std::string& id) {
const auto g = repo.loadGrid(id);
if (is2D) {
auto line = geopro::render::buildSurveyLine(g, *frame);
if (line) scene->addActor(line);
} else {
} else if (*showCurtain) {
const auto cs = repo.loadColorScale(id);
auto curtain = geopro::render::buildCurtain(g, cs, *frame);
if (curtain) {
@ -315,6 +360,17 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
if (ds.ddType == "dd_section") renderSection(ds.id);
}
// 三维「体素」图层:两交叉测线散点经 CRS 配准 IDW 成体素(派生层),与帘面同纵向夸张。
if (!is2D && *showVoxel && crs) {
const auto profs = repo.loadVoxelScatters();
const auto vcs = repo.loadScatterColorScale("grid1");
auto vr = geopro::render::buildVoxelFromScatters(profs, vcs, *crs, *frame);
if (vr.valid()) {
vr.volume->SetScale(1.0, 1.0, kCurtainZScale);
rendererPtr->AddVolume(vr.volume);
}
}
if (is2D)
geopro::render::applyTop2D(rendererPtr);
else
@ -461,13 +517,41 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
rebuildDetail();
});
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ──
QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() {
// 「视图详情」浮层显隐:仅三维显示,置于 QVTK 左上(工具条下方)并置顶。
auto showLayerPanel = [layerPanel, viewToolBar](bool show3D) {
if (show3D) {
layerPanel->move(14, viewToolBar->height() + 12);
layerPanel->adjustSize();
layerPanel->setVisible(true);
layerPanel->raise();
} else {
layerPanel->setVisible(false);
}
};
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 重建内容 + 图层浮层显隐 ──
QObject::connect(act2D, &QAction::triggered, vtkWidget,
[viewMode, rebuildCentral, showLayerPanel]() {
*viewMode = ViewMode::Map2D;
showLayerPanel(false);
rebuildCentral();
});
QObject::connect(act3D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() {
QObject::connect(act3D, &QAction::triggered, vtkWidget,
[viewMode, rebuildCentral, showLayerPanel]() {
*viewMode = ViewMode::View3D;
showLayerPanel(true);
rebuildCentral();
});
// ──「视图详情」图层勾选 → 更新图层显隐 → 重建中央 ──
QObject::connect(chkCurtain, &QCheckBox::toggled, vtkWidget,
[showCurtain, rebuildCentral](bool on) {
*showCurtain = on;
rebuildCentral();
});
QObject::connect(chkVoxel, &QCheckBox::toggled, vtkWidget,
[showVoxel, rebuildCentral](bool on) {
*showVoxel = on;
rebuildCentral();
});
@ -502,6 +586,24 @@ int main(int argc, char* argv[])
QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat());
QApplication app(argc, argv);
// PROJ 数据(proj.db)定位:体素配准的 CrsTransform 需要。优先已设环境变量;
// 否则按 exe 旁 / 构建目录候选设置。部署时须随包附带 proj 数据并设此变量。
if (qEnvironmentVariableIsEmpty("PROJ_DATA")) {
const QString appDir = QCoreApplication::applicationDirPath();
const QStringList candidates = {
appDir + "/proj",
appDir + "/../../vcpkg_installed/x64-windows/share/proj",
QStringLiteral(
"D:/Git/lanbingtech/geopro/build/release/vcpkg_installed/x64-windows/share/proj"),
};
for (const auto& c : candidates) {
if (QFile::exists(c + "/proj.db")) {
qputenv("PROJ_DATA", c.toUtf8());
break;
}
}
}
// 网络层:共享会话 ApiClient + 登录编排 AuthServiceRSA 公钥从 resources 读取)。
geopro::net::ApiClient api(QStringLiteral("http://tenant.geomative.cn/pop-api"));
const std::string pem = readPem("D:/Git/lanbingtech/geopro/resources/rsa_public_key.pem");