feat(view): 剖面电极标记 ▼ + 显示电极/显示等值线 开关(对齐原型, 增量4)

- ElectrodeActor(buildElectrodes): 剖面顶边(最浅深度)各 x 列画朝下三角 ▼; 坐标与 #18 一致。
  离屏 verify_section_anomaly.png 顶边电极带核对吻合; +2 单测(三角数/顶点位置/空安全)。
- 数据详情工具条加「显示电极」(默认开,网格数据模式)+「显示等值线」(默认开, gate #18 黑色等值线)。
- 全 38 测试绿; app 构建干净; 待人工登录复核。
- 增量4 剩: 数值标签 / 色阶配置 / 滤波处理(进阶/M1.5)。
This commit is contained in:
gaozheng 2026-06-08 09:52:18 +08:00
parent a2efef8ada
commit 8466fe3a5a
9 changed files with 164 additions and 11 deletions

View File

@ -18,10 +18,10 @@
- **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换):
- 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。
- 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。中央工具条**仅**「二维地图/三维视图」。**3D 左上「视图详情」浮层**(对齐原型,仅 3D 显示):图层勾选 **帘面 / 体素**(体素=`buildVoxelFromScatters` 两交叉测线散点经 EPSG:4547 配准 IDW,与帘面同纵向夸张;PROJ 不可用则禁用)。
- **下方 数据详情**:工具条「原数据/网格数据」切换 +「显示异常」开关(对齐原型命名)。单击数据集 → 网格数据=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17,x=距离/y=深度取负,用散点自带色阶);显示异常=`AnomalyActor` 在上图叠加异常 dashed 折线(同纵向夸张对齐)。
- **下方 数据详情**:工具条「原数据/网格数据」切换 +「显示异常/显示电极/显示等值线」开关(对齐原型)。单击数据集 → 网格数据=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17);显示异常=`AnomalyActor` dashed 折线叠加;显示电极=`ElectrodeActor` 顶边 ▼ 标记;显示等值线=#18 黑色等值线显隐(同纵向夸张对齐)。
- **右上 异常列表**(对齐原型):单击数据集→列该数据集异常(颜色块+名称(类型)+派生「位置/深/尺寸」),勾选框显隐,与数据详情异常叠加联动(取消勾选→该异常虚线隐藏)。
- **右下 属性**:名称/网格 nx×ny/vmin·vmax/异常数。
- 单元测试累计 **36 个全绿**(core/data/net/render;含 Scatter 2 + Anomaly 4 + VoxelRegister 1、修复了陈旧的 Curtain mapper 类型断言);离屏 `verify_section/map/curtain_3d/scatter/section_anomaly/voxel_top/voxel_3d.png` 均核对正确(scatter 吻合 ref_17、异常吻合 ref_18、体素 footprint 吻合 ref voxel_hslice 的两臂支撑)
- 单元测试累计 **38 个全绿**(core/data/net/render;含 Scatter 2 + Anomaly 4 + VoxelRegister 1 + Electrode 2、修复了陈旧的 Curtain mapper 类型断言);离屏 `verify_section/map/curtain_3d/scatter/section_anomaly(含电极▼)/voxel_top/voxel_3d.png` 均核对正确。
## 2. 各 Phase 完成度
@ -79,7 +79,8 @@
- ✅ **增量1 右上「异常列表」**(2026-06-08,`panels/AnomalyListPanel`,与数据详情显隐联动;待人工复核)。
- ✅ **增量2 左下「数据列表」+ 对象树到 TM 层**(2026-06-08,`panels/DatasetListPanel`;树 GS→TM 复选驱动中央, DS 移出树入数据列表 tab 数据/文件, DS 单击→详情+异常+属性, 启动自动选首测线/首数据集;待人工复核)。
- ✅ **增量3 3D「视图详情」图层浮层**(2026-06-08,QFrame 浮于 QVTK 左上,仅 3D 显示;帘面/体素图层勾选;体素经此正经接入=之前移除的工具条开关的正确归宿;main() 设 PROJ_DATA;待人工复核浮层渲染+体素显隐)。
- ⬜ **增量4 数据详情富工具条 + 电极标记 + 数值标签**;底图影像=DEM/底图任务。
- 🔶 **增量4 电极标记 + 工具条**(2026-06-08,`ElectrodeActor` 顶边 ▼ PNG 核对吻合; +「显示电极/显示等值线」开关;待人工复核)。**剩**:数值标签、色阶配置/滤波处理(M1.5/进阶)。
- 渲染积木累计含 `ElectrodeActor`(顶边 ▼)。底图影像=DEM/底图任务(需 GDAL)。
- 架构:新面板抽到 `src/app/panels/`(暂随 app 编译,如 login/),控制 main.cpp 体量;后续可升 `src/view/` 库。
## 7. 渲染验证手段(务必用)

View File

@ -81,7 +81,10 @@
- **人工复核**:三维视图勾"体素"→ 出十字片体素,与帘面同系;取消则移除。
- 提交 `feat(view): 3D 视图详情图层浮层(测线/帘面/体素) + 体素正经接入`
### 增量 4数据详情富工具条 + 电极 + 数值标签(对齐原型中下)
### 增量 4数据详情富工具条 + 电极 🔶 部分完成(2026-06-08)
> ✅ `actors/ElectrodeActor`(buildElectrodes:顶边各 x 列朝下三角 ▼,离屏 PNG 核对吻合) + 数据详情
> 「显示电极/显示等值线」开关(显示等值线 gate #18 edges actor)。+2 单测,全 38 测试绿。
> ⬜ 剩:数值标签(vtkLabeledDataMapper)、色阶配置/滤波处理(进阶/M1.5)。**原计划存档**:
- 工具条补 色阶配置/显示电极/显示等值线 等(按需,部分 M1 可占位)。
- 剖面顶部电极▼标记grid.lat/lon 或 x 轴电极位);可选数值标签。
- 提交 `feat(view): 数据详情电极标记 + 工具条对齐原型`

View File

@ -59,6 +59,7 @@
#include "VoxelFromScatters.hpp"
#include "actors/AnomalyActor.hpp"
#include "actors/CurtainActor.hpp"
#include "actors/ElectrodeActor.hpp"
#include "actors/GridContourActor.hpp"
#include "actors/MapLineActor.hpp"
#include "actors/ScatterActor.hpp"
@ -270,6 +271,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
auto* actShowAnomaly = detailToolBar->addAction(QStringLiteral("显示异常"));
actShowAnomaly->setCheckable(true);
actShowAnomaly->setChecked(true); // 默认显示异常(对齐原型 ☑显示异常)
auto* actShowElectrodes = detailToolBar->addAction(QStringLiteral("显示电极"));
actShowElectrodes->setCheckable(true);
actShowElectrodes->setChecked(true); // 默认显示电极 ▼(对齐原型)
auto* actShowContour = detailToolBar->addAction(QStringLiteral("显示等值线"));
actShowContour->setCheckable(true);
actShowContour->setChecked(true); // 默认显示等值线(对齐原型)
detailLayout->addWidget(detailToolBar);
detailLayout->addWidget(detailWidget, 1);
@ -400,12 +407,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
auto currentDsId = std::make_shared<QString>();
auto detailMode = std::make_shared<DetailMode>(DetailMode::Section18);
auto showAnomalies = std::make_shared<bool>(true); // 默认显示异常(对齐原型)
auto showElectrodes = std::make_shared<bool>(true); // 默认显示电极 ▼
auto showContour = std::make_shared<bool>(true); // 默认显示等值线
auto hiddenAnoms = std::make_shared<std::set<int>>(); // 异常列表中被取消勾选(隐藏)的异常下标
// 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。
// 勾选「显示异常」时在 #18/#17 上叠加异常 dashed 折线(同纵向夸张对齐)。
// 勾选「显示异常/电极/等值线」控制对应叠加(同纵向夸张对齐)。
auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, detailMode,
showAnomalies, hiddenAnoms]() {
showAnomalies, showElectrodes, showContour, hiddenAnoms]() {
detailRendererPtr->RemoveAllViewProps();
if (currentDsId->isEmpty()) { // 未选数据集:清空即可
detailRenderWindowPtr->Render();
@ -413,7 +422,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}
const std::string id = currentDsId->toStdString();
if (*detailMode == DetailMode::Section18) {
// 网格数据:#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y
// 网格数据:#18 banded 等值面+「显示等值线」时叠黑色等值线),纵向夸张 1.5x(沿 y
const auto g = repo.loadGrid(id);
const auto cs = repo.loadColorScale(id);
const auto actors = geopro::render::buildGridContour(g, cs);
@ -421,10 +430,18 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
actors.bands->SetScale(1.0, kDetailYScale, 1.0);
detailRendererPtr->AddViewProp(actors.bands);
}
if (actors.edges) {
if (actors.edges && *showContour) {
actors.edges->SetScale(1.0, kDetailYScale, 1.0);
detailRendererPtr->AddViewProp(actors.edges);
}
// 顶部电极标记 ▼(仅网格数据;同纵向夸张对齐)。
if (*showElectrodes) {
auto elec = geopro::render::buildElectrodes(g);
if (elec) {
elec->SetScale(1.0, kDetailYScale, 1.0);
detailRendererPtr->AddViewProp(elec);
}
}
} else {
// 原数据:#17 彩色散点,用散点自带色阶;纵向夸张同剖面以对齐观感。
const auto s = repo.loadScatter(id);
@ -510,12 +527,22 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
rebuildDetail();
});
// ──「显示异常」开关:切换异常叠加 → 重建数据详情 ──
// ──「显示异常 / 显示电极 / 显示等值线」开关:切换叠加 → 重建数据详情 ──
QObject::connect(actShowAnomaly, &QAction::toggled, detailWidget,
[showAnomalies, rebuildDetail](bool on) {
*showAnomalies = on;
rebuildDetail();
});
QObject::connect(actShowElectrodes, &QAction::toggled, detailWidget,
[showElectrodes, rebuildDetail](bool on) {
*showElectrodes = on;
rebuildDetail();
});
QObject::connect(actShowContour, &QAction::toggled, detailWidget,
[showContour, rebuildDetail](bool on) {
*showContour = on;
rebuildDetail();
});
// 「视图详情」浮层显隐:仅三维显示,置于 QVTK 左上(工具条下方)并置顶。
auto showLayerPanel = [layerPanel, viewToolBar](bool show3D) {

View File

@ -1,6 +1,6 @@
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingCore RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets)
add_library(geopro_render STATIC
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp VoxelFromScatters.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp actors/AnomalyActor.cpp)
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp VoxelFromScatters.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp actors/AnomalyActor.cpp actors/ElectrodeActor.cpp)
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES})
target_compile_features(geopro_render PUBLIC cxx_std_17)

View File

@ -0,0 +1,57 @@
#include "actors/ElectrodeActor.hpp"
#include <algorithm>
#include <cstddef>
#include <vtkCellArray.h>
#include <vtkNew.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkTriangle.h>
namespace geopro::render {
vtkSmartPointer<vtkActor> buildElectrodes(const geopro::core::Grid& g, double markerW,
double markerH)
{
const int nx = g.nx();
if (nx < 1 || g.x.size() < static_cast<size_t>(nx) || g.y.empty()) {
return vtkSmartPointer<vtkActor>::New();
}
// 剖面顶边(最浅) y_screen = -min(深度)#18 用 -y 使深部在下,故最浅处 y_screen 最大。
const double minDepth = *std::min_element(g.y.begin(), g.y.end());
const double yTop = -minDepth;
vtkNew<vtkPoints> points;
vtkNew<vtkCellArray> tris;
// 每个电极一个朝下三角:顶点(apex)触剖面顶边 (x, yTop),底边在其上方 (x±w, yTop+h)。
for (int i = 0; i < nx; ++i) {
const double x = g.x[i];
const vtkIdType a = points->InsertNextPoint(x, yTop, 0.0); // apex(下)
const vtkIdType l = points->InsertNextPoint(x - markerW, yTop + markerH, 0.0);
const vtkIdType r = points->InsertNextPoint(x + markerW, yTop + markerH, 0.0);
vtkNew<vtkTriangle> t;
t->GetPointIds()->SetId(0, a);
t->GetPointIds()->SetId(1, l);
t->GetPointIds()->SetId(2, r);
tris->InsertNextCell(t);
}
vtkNew<vtkPolyData> poly;
poly->SetPoints(points);
poly->SetPolys(tris);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(poly);
mapper->ScalarVisibilityOff();
auto actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
actor->GetProperty()->SetColor(0.15, 0.15, 0.15); // 深灰 ▼
return actor;
}
} // namespace geopro::render

View File

@ -0,0 +1,17 @@
#pragma once
#include <vtkActor.h>
#include <vtkSmartPointer.h>
#include "model/Field.hpp"
namespace geopro::render {
// 剖面顶部电极标记(对齐原型 ▼):在剖面顶边(最浅深度)各 x 列位置画一个朝下的小三角。
// 坐标与 #18 数据详情一致x=g.x[i]、顶边 y=-min(深度)、z=0。调用方应施加与剖面相同的
// SetScale 以对齐(三角会随之略拉伸)。markerW/markerH 为数据单位下的三角半宽/高。
// 退化网格(无 x)返回空 actor。
vtkSmartPointer<vtkActor> buildElectrodes(const geopro::core::Grid& g,
double markerW = 0.5,
double markerH = 1.4);
} // namespace geopro::render

View File

@ -70,6 +70,8 @@ target_sources(geopro_tests PRIVATE render/test_curtain.cpp)
target_sources(geopro_tests PRIVATE render/test_scatter.cpp)
# AnomalybuildAnomalies(markType 点/线/面 -> vtkActor) ///y/
target_sources(geopro_tests PRIVATE render/test_anomaly.cpp)
# ElectrodebuildElectrodes(剖面顶边朝下三角 ) //
target_sources(geopro_tests PRIVATE render/test_electrode.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES})

View File

@ -0,0 +1,42 @@
#include <gtest/gtest.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include "actors/ElectrodeActor.hpp"
#include "model/Field.hpp"
using namespace geopro::core;
// buildElectrodes: nx 列 → nx 个朝下三角(各 3 点);顶点 y = -min(深度)(剖面顶边)。
TEST(Electrode, BuildsDownTrianglesAtSectionTop) {
Grid g(3, 2);
g.x = {0.0, 10.0, 20.0};
g.y = {2.0, 8.0}; // 深度min=2 → 顶边 y_screen = -2
auto actor = geopro::render::buildElectrodes(g, /*markerW*/ 0.5, /*markerH*/ 1.0);
ASSERT_NE(actor.GetPointer(), nullptr);
auto* mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper());
ASSERT_NE(mapper, nullptr);
auto* poly = vtkPolyData::SafeDownCast(mapper->GetInput());
ASSERT_NE(poly, nullptr);
EXPECT_EQ(poly->GetNumberOfCells(), 3); // 3 电极 → 3 三角
EXPECT_EQ(poly->GetNumberOfPoints(), 3 * 3); // 每三角 3 点
// 第一个三角的 apex(点 0) 在 (x=0, y=-2)(顶边),底边两点在其上方 y=-1。
double apex[3];
poly->GetPoint(0, apex);
EXPECT_DOUBLE_EQ(apex[0], 0.0);
EXPECT_DOUBLE_EQ(apex[1], -2.0);
double base[3];
poly->GetPoint(1, base);
EXPECT_DOUBLE_EQ(base[1], -1.0); // -2 + markerH(1)
}
// 退化网格(无 x)返回空 actor。
TEST(Electrode, EmptyGridYieldsSafeActor) {
Grid g(0, 0);
auto actor = geopro::render::buildElectrodes(g);
ASSERT_NE(actor.GetPointer(), nullptr);
EXPECT_EQ(actor->GetMapper(), nullptr);
}

View File

@ -20,6 +20,7 @@
#include "VoxelFromScatters.hpp"
#include "actors/AnomalyActor.hpp"
#include "actors/CurtainActor.hpp"
#include "actors/ElectrodeActor.hpp"
#include "actors/GridContourActor.hpp"
#include "actors/MapLineActor.hpp"
#include "actors/ScatterActor.hpp"
@ -110,6 +111,9 @@ int main() {
act->SetScale(1.0, exag, 1.0);
ren->AddActor(act);
}
// 顶部电极标记 ▼(同纵向夸张对齐)。
auto elec = render::buildElectrodes(g);
if (elec) { elec->SetScale(1.0, exag, 1.0); ren->AddActor(elec); }
render::applyTop2D(ren);
renderToPng(ren, (dir + "verify_section_anomaly.png").c_str(), 1100, 360);
std::printf("ANOMALY n=%zu\n", anomalies.size());