diff --git a/docs/superpowers/STATUS.md b/docs/superpowers/STATUS.md index cb35038..fa169d6 100644 --- a/docs/superpowers/STATUS.md +++ b/docs/superpowers/STATUS.md @@ -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. 渲染验证手段(务必用) diff --git a/docs/superpowers/plans/2026-06-08-m1-prototype-layout.md b/docs/superpowers/plans/2026-06-08-m1-prototype-layout.md index 1634ee9..b7b9e08 100644 --- a/docs/superpowers/plans/2026-06-08-m1-prototype-layout.md +++ b/docs/superpowers/plans/2026-06-08-m1-prototype-layout.md @@ -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): 数据详情电极标记 + 工具条对齐原型`。 diff --git a/src/app/main.cpp b/src/app/main.cpp index d9fecc5..81c403c 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -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); @@ -399,13 +406,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // 当前选中数据集 id(空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。 auto currentDsId = std::make_shared(); auto detailMode = std::make_shared(DetailMode::Section18); - auto showAnomalies = std::make_shared(true); // 默认显示异常(对齐原型) + auto showAnomalies = std::make_shared(true); // 默认显示异常(对齐原型) + auto showElectrodes = std::make_shared(true); // 默认显示电极 ▼ + auto showContour = std::make_shared(true); // 默认显示等值线 auto hiddenAnoms = std::make_shared>(); // 异常列表中被取消勾选(隐藏)的异常下标 // 按当前选中 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) { diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index b6ef9ba..b5abaf2 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -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) diff --git a/src/render/actors/ElectrodeActor.cpp b/src/render/actors/ElectrodeActor.cpp new file mode 100644 index 0000000..32f9e75 --- /dev/null +++ b/src/render/actors/ElectrodeActor.cpp @@ -0,0 +1,57 @@ +#include "actors/ElectrodeActor.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace geopro::render { + +vtkSmartPointer buildElectrodes(const geopro::core::Grid& g, double markerW, + double markerH) +{ + const int nx = g.nx(); + if (nx < 1 || g.x.size() < static_cast(nx) || g.y.empty()) { + return vtkSmartPointer::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 points; + vtkNew 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 t; + t->GetPointIds()->SetId(0, a); + t->GetPointIds()->SetId(1, l); + t->GetPointIds()->SetId(2, r); + tris->InsertNextCell(t); + } + + vtkNew poly; + poly->SetPoints(points); + poly->SetPolys(tris); + + vtkNew mapper; + mapper->SetInputData(poly); + mapper->ScalarVisibilityOff(); + + auto actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->GetProperty()->SetColor(0.15, 0.15, 0.15); // 深灰 ▼ + return actor; +} + +} // namespace geopro::render diff --git a/src/render/actors/ElectrodeActor.hpp b/src/render/actors/ElectrodeActor.hpp new file mode 100644 index 0000000..817389c --- /dev/null +++ b/src/render/actors/ElectrodeActor.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +#include "model/Field.hpp" + +namespace geopro::render { + +// 剖面顶部电极标记(对齐原型 ▼):在剖面顶边(最浅深度)各 x 列位置画一个朝下的小三角。 +// 坐标与 #18 数据详情一致:x=g.x[i]、顶边 y=-min(深度)、z=0。调用方应施加与剖面相同的 +// SetScale 以对齐(三角会随之略拉伸)。markerW/markerH 为数据单位下的三角半宽/高。 +// 退化网格(无 x)返回空 actor。 +vtkSmartPointer buildElectrodes(const geopro::core::Grid& g, + double markerW = 0.5, + double markerH = 1.4); + +} // namespace geopro::render diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 885d6e2..8cfb977 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,6 +70,8 @@ target_sources(geopro_tests PRIVATE render/test_curtain.cpp) target_sources(geopro_tests PRIVATE render/test_scatter.cpp) # Anomaly:buildAnomalies(markType 点/线/面 -> vtkActor) 几何/闭合/颜色/y取负/空跳过。 target_sources(geopro_tests PRIVATE render/test_anomaly.cpp) +# Electrode:buildElectrodes(剖面顶边朝下三角 ▼) 三角数/顶点位置/空安全。 +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}) diff --git a/tests/render/test_electrode.cpp b/tests/render/test_electrode.cpp new file mode 100644 index 0000000..baf4b98 --- /dev/null +++ b/tests/render/test_electrode.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +#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); +} diff --git a/tests/spike/render_verify.cpp b/tests/spike/render_verify.cpp index 5bc15f2..dbf26a5 100644 --- a/tests/spike/render_verify.cpp +++ b/tests/spike/render_verify.cpp @@ -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());