From 519d0ed1df61a3b40fafc5ae13f7b6f021018fa8 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Sun, 7 Jun 2026 20:39:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(app):=20=E5=AF=B9=E8=B1=A1=E6=A0=91->?= =?UTF-8?q?=E9=80=89=E4=B8=AD=E6=95=B0=E6=8D=AE=E9=9B=86->=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=89=96=E9=9D=A2+=E5=B1=9E=E6=80=A7=20=E8=81=94?= =?UTF-8?q?=E5=8A=A8(=E6=9C=AC=E5=9C=B0=E6=A0=B7=E6=9C=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/CMakeLists.txt | 1 + src/app/main.cpp | 162 ++++++++++++++++++++++++++++++----------- 2 files changed, 120 insertions(+), 43 deletions(-) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 53718be..ad58f05 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -18,6 +18,7 @@ target_link_libraries(geopro_desktop PRIVATE ads::qt6advanceddocking nlohmann_json::nlohmann_json geopro_core # Phase 1:ColorScale 上色 + geopro_data # Phase 2:本地样本仓储(对象树 / 网格 / 色阶) ) vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES}) diff --git a/src/app/main.cpp b/src/app/main.cpp index f9f4711..21b283c 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -1,23 +1,25 @@ -// M1 demo 工作台:ADS 停靠 + QVTK 视图渲染真实 ERT 网格剖面(设计图 #18)。 -// 端到端打通:core(ColorScale) + render(VTK banded contour) + view(Qt/ADS)。 -// 数据:D:/dev/spike_data/grid.json + colorbar.json(样本拷贝,ASCII 路径)。 -// 注:真实数据加载(Repository + 中文路径 + API)属 Phase 2,本处为可视化里程碑 demo。 +// M1 工作台(Phase 2 / Task 4):对象树 → 选中数据集 → 中央 QVTK 渲染剖面 + 右侧属性。 +// 端到端:data(LocalSampleRepository 读真实中文路径样本) + core(Grid/ColorScale) + +// render(VTK banded contour) + view(Qt/ADS 三栏停靠)。 +// 数据:docs/剖面网格数据的色阶数据2等文件/(真实样本,UTF-8 中文路径,经 QFile 读取)。 -#include #include -#include #include +#include #include #include #include +#include +#include +#include #include #include -#include - -#include "model/ColorScale.hpp" // Phase 1 core +#include "model/ColorScale.hpp" +#include "model/Field.hpp" +#include "repo/LocalSampleRepository.hpp" #include #include @@ -34,53 +36,55 @@ #include #include -using json = nlohmann::json; -using geopro::core::AlphaScale; -using geopro::core::ColorScale; -using geopro::core::parseColor; - namespace { -// 读真实网格样本,建 banded contour actors(填充面 + 黑色等值线),返回到 renderer。 -void buildGridSection(vtkRenderer* ren, const std::string& dataDir) +// 把 core 模型(Grid + ColorScale)渲染为 banded contour(填充面 + 黑色等值线)。 +// 入参是已加载的 core 模型,不读文件(数据加载由 Repository 负责)。 +void renderGrid(vtkRenderer* ren, const geopro::core::Grid& g, const geopro::core::ColorScale& cs) { - json g = json::parse(std::ifstream(dataDir + "grid.json"))["data"]; - auto x = g["x"].get>(); - auto y = g["y"].get>(); - auto v = g["v"].get>>(); // [j=y][i=x] - const int nx = static_cast(x.size()), ny = static_cast(y.size()); + ren->RemoveAllViewProps(); // 清旧 actor,支持重复切换数据集 + + const int nx = g.nx(), ny = g.ny(); + if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) { + ren->SetBackground(1, 1, 1); + ren->ResetCamera(); + return; + } vtkNew img; img->SetDimensions(nx, ny, 1); - img->SetOrigin(x[0], y[0], 0.0); - img->SetSpacing(x[1] - x[0], y[1] - y[0], 1.0); + img->SetOrigin(g.x[0], g.y[0], 0.0); + img->SetSpacing(g.x[1] - g.x[0], g.y[1] - g.y[0], 1.0); + vtkNew sc; sc->SetName("v"); sc->SetNumberOfTuples(static_cast(nx) * ny); for (int j = 0; j < ny; ++j) for (int i = 0; i < nx; ++i) - sc->SetValue(static_cast(j) * nx + i, v[j][i]); // i 最快 + sc->SetValue(static_cast(j) * nx + i, g.valueAt(i, j)); // i 最快 img->GetPointData()->SetScalars(sc); - // 色阶:用 Phase 1 core 的 ColorScale 解析 colorBar(网格色阶 alpha=0-255) - json cb = json::parse(std::ifstream(dataDir + "colorbar.json"))["data"]["properties"]["colorBar"]; - ColorScale cs; - std::vector levels; - for (auto& pr : cb) { - double val = std::stod(pr[0].get()); - cs.addStop(val, parseColor(pr[1].get(), AlphaScale::Bit255)); - levels.push_back(val); + // vmin/vmax 来自 Grid;若退化(==)则用数据极值兜底,避免 LUT/contour 退化。 + double vmin = g.vmin, vmax = g.vmax; + if (vmin >= vmax) { + const auto& vals = g.values(); + vmin = vals.empty() ? 0.0 : vals.front(); + vmax = vmin; + for (double v : vals) { + if (v < vmin) vmin = v; + if (v > vmax) vmax = v; + } + if (vmin >= vmax) vmax = vmin + 1.0; } - const double vmin = levels.front(), vmax = levels.back(); - // 用 ColorScale 填一张精细 LUT(256 级,按真实 colorBar 阶梯取色) + // 256 级 LUT,按 ColorScale 阶梯取色。 const int N = 256; vtkNew lut; lut->SetNumberOfTableValues(N); lut->SetTableRange(vmin, vmax); for (int t = 0; t < N; ++t) { - double val = vmin + (vmax - vmin) * t / (N - 1); - auto c = cs.colorAt(val); + const double val = vmin + (vmax - vmin) * t / (N - 1); + const auto c = cs.colorAt(val); lut->SetTableValue(t, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0); } lut->Build(); @@ -90,8 +94,7 @@ void buildGridSection(vtkRenderer* ren, const std::string& dataDir) vtkNew banded; banded->SetInputConnection(surf->GetOutputPort()); - banded->SetNumberOfContours(static_cast(levels.size())); - for (int k = 0; k < static_cast(levels.size()); ++k) banded->SetValue(k, levels[k]); + banded->GenerateValues(20, vmin, vmax); banded->GenerateContourEdgesOn(); banded->SetScalarModeToValue(); @@ -118,6 +121,25 @@ void buildGridSection(vtkRenderer* ren, const std::string& dataDir) ren->ResetCamera(); } +// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层;DS 项在 UserRole 存 dsId。 +void populateTree(QTreeWidget* tree, const std::vector& gss) +{ + for (const auto& gs : gss) { + auto* gsItem = new QTreeWidgetItem(tree); + gsItem->setText(0, QString::fromStdString(gs.name)); + for (const auto& tm : gs.tms) { + auto* tmItem = new QTreeWidgetItem(gsItem); + tmItem->setText(0, QString::fromStdString(tm.name)); + for (const auto& ds : tm.dss) { + auto* dsItem = new QTreeWidgetItem(tmItem); + dsItem->setText(0, QString::fromStdString(ds.name)); + dsItem->setData(0, Qt::UserRole, QString::fromStdString(ds.id)); + } + } + } + tree->expandAll(); +} + } // namespace int main(int argc, char* argv[]) @@ -125,18 +147,21 @@ int main(int argc, char* argv[]) QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat()); QApplication app(argc, argv); + // 本地样本仓储(中文路径,末尾带 '/',readFile 直接拼文件名)。生命周期覆盖事件循环。 + geopro::data::LocalSampleRepository repo( + "D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/"); + QMainWindow window; - window.setWindowTitle(QStringLiteral("Geopro 3.0 — ERT 网格剖面 (M1 demo)")); + window.setWindowTitle(QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)")); window.resize(1280, 800); + // 中央 QVTK 视图(指针供联动回调使用)。 auto* vtkWidget = new QVTKOpenGLStereoWidget(); vtkNew renderWindow; vtkWidget->setRenderWindow(renderWindow); vtkNew renderer; renderWindow->AddRenderer(renderer); - buildGridSection(renderer, "D:/dev/spike_data/"); - auto* dockManager = new ads::CDockManager(&window); window.setCentralWidget(dockManager); @@ -144,14 +169,65 @@ int main(int argc, char* argv[]) vtkDock->setWidget(vtkWidget); dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); + // 左 dock:对象树。 + auto* tree = new QTreeWidget(); + tree->setHeaderLabel(QStringLiteral("对象")); + populateTree(tree, repo.loadStructure()); auto* leftDock = new ads::CDockWidget(QStringLiteral("对象列表")); - leftDock->setWidget(new QLabel(QStringLiteral("(Phase 2 接入)"))); + leftDock->setWidget(tree); dockManager->addDockWidget(ads::LeftDockWidgetArea, leftDock); + // 右 dock:属性。 + auto* propLabel = new QLabel(QStringLiteral("(选择左侧数据集查看属性)")); + propLabel->setWordWrap(true); + propLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + propLabel->setMargin(8); auto* rightDock = new ads::CDockWidget(QStringLiteral("数据集 / 属性")); - rightDock->setWidget(new QLabel(QStringLiteral("(Phase 2 接入)"))); + rightDock->setWidget(propLabel); dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock); + // 联动:点击 DS 项 → 加载 grid/colorScale → 渲染 + 更新属性。 + // [&] 捕获:repo / renderer / renderWindow / propLabel 均在 main 作用域, + // 生命周期覆盖到 app.exec() 返回,事件回调期间安全。 + auto renderDataset = [&](QTreeWidgetItem* item) { + const QString id = item->data(0, Qt::UserRole).toString(); + if (id.isEmpty()) return; // GS/TM 节点无 dsId,忽略 + const std::string dsId = id.toStdString(); + const auto g = repo.loadGrid(dsId); + const auto cs = repo.loadColorScale(dsId); + renderGrid(renderer, g, cs); + renderWindow->Render(); + propLabel->setText(QStringLiteral("数据集: %1\n网格: %2 x %3\nvmin / vmax: %4 / %5") + .arg(item->text(0)) + .arg(g.nx()) + .arg(g.ny()) + .arg(g.vmin) + .arg(g.vmax)); + }; + + QObject::connect(tree, &QTreeWidget::itemClicked, tree, + [&](QTreeWidgetItem* it, int) { renderDataset(it); }); + window.show(); + + // 默认渲染第一个 DS,让窗口一打开就有图。 + if (auto* first = tree->topLevelItemCount() > 0 ? tree->topLevelItem(0) : nullptr) { + // 递归找到首个带 dsId 的项。 + QTreeWidgetItem* dsItem = nullptr; + QList stack{first}; + while (!stack.isEmpty() && !dsItem) { + QTreeWidgetItem* cur = stack.takeFirst(); + if (!cur->data(0, Qt::UserRole).toString().isEmpty()) { + dsItem = cur; + break; + } + for (int i = 0; i < cur->childCount(); ++i) stack.append(cur->child(i)); + } + if (dsItem) { + tree->setCurrentItem(dsItem); + renderDataset(dsItem); + } + } + return app.exec(); }