feat(app): 对象树->选中数据集->渲染剖面+属性 联动(本地样本)
This commit is contained in:
parent
f48b9ebb8f
commit
519d0ed1df
|
|
@ -18,6 +18,7 @@ target_link_libraries(geopro_desktop PRIVATE
|
||||||
ads::qt6advanceddocking
|
ads::qt6advanceddocking
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
geopro_core # Phase 1:ColorScale 上色
|
geopro_core # Phase 1:ColorScale 上色
|
||||||
|
geopro_data # Phase 2:本地样本仓储(对象树 / 网格 / 色阶)
|
||||||
)
|
)
|
||||||
|
|
||||||
vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES})
|
vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES})
|
||||||
|
|
|
||||||
162
src/app/main.cpp
162
src/app/main.cpp
|
|
@ -1,23 +1,25 @@
|
||||||
// M1 demo 工作台:ADS 停靠 + QVTK 视图渲染真实 ERT 网格剖面(设计图 #18)。
|
// M1 工作台(Phase 2 / Task 4):对象树 → 选中数据集 → 中央 QVTK 渲染剖面 + 右侧属性。
|
||||||
// 端到端打通:core(ColorScale) + render(VTK banded contour) + view(Qt/ADS)。
|
// 端到端:data(LocalSampleRepository 读真实中文路径样本) + core(Grid/ColorScale) +
|
||||||
// 数据:D:/dev/spike_data/grid.json + colorbar.json(样本拷贝,ASCII 路径)。
|
// render(VTK banded contour) + view(Qt/ADS 三栏停靠)。
|
||||||
// 注:真实数据加载(Repository + 中文路径 + API)属 Phase 2,本处为可视化里程碑 demo。
|
// 数据:docs/剖面网格数据的色阶数据2等文件/(真实样本,UTF-8 中文路径,经 QFile 读取)。
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QFormLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QTreeWidgetItem>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
#include <DockManager.h>
|
#include <DockManager.h>
|
||||||
#include <DockWidget.h>
|
#include <DockWidget.h>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include "model/ColorScale.hpp"
|
||||||
|
#include "model/Field.hpp"
|
||||||
#include "model/ColorScale.hpp" // Phase 1 core
|
#include "repo/LocalSampleRepository.hpp"
|
||||||
|
|
||||||
#include <QVTKOpenGLStereoWidget.h>
|
#include <QVTKOpenGLStereoWidget.h>
|
||||||
#include <vtkActor.h>
|
#include <vtkActor.h>
|
||||||
|
|
@ -34,53 +36,55 @@
|
||||||
#include <vtkProperty.h>
|
#include <vtkProperty.h>
|
||||||
#include <vtkRenderer.h>
|
#include <vtkRenderer.h>
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
using geopro::core::AlphaScale;
|
|
||||||
using geopro::core::ColorScale;
|
|
||||||
using geopro::core::parseColor;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// 读真实网格样本,建 banded contour actors(填充面 + 黑色等值线),返回到 renderer。
|
// 把 core 模型(Grid + ColorScale)渲染为 banded contour(填充面 + 黑色等值线)。
|
||||||
void buildGridSection(vtkRenderer* ren, const std::string& dataDir)
|
// 入参是已加载的 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"];
|
ren->RemoveAllViewProps(); // 清旧 actor,支持重复切换数据集
|
||||||
auto x = g["x"].get<std::vector<double>>();
|
|
||||||
auto y = g["y"].get<std::vector<double>>();
|
const int nx = g.nx(), ny = g.ny();
|
||||||
auto v = g["v"].get<std::vector<std::vector<double>>>(); // [j=y][i=x]
|
if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) {
|
||||||
const int nx = static_cast<int>(x.size()), ny = static_cast<int>(y.size());
|
ren->SetBackground(1, 1, 1);
|
||||||
|
ren->ResetCamera();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
vtkNew<vtkImageData> img;
|
vtkNew<vtkImageData> img;
|
||||||
img->SetDimensions(nx, ny, 1);
|
img->SetDimensions(nx, ny, 1);
|
||||||
img->SetOrigin(x[0], y[0], 0.0);
|
img->SetOrigin(g.x[0], g.y[0], 0.0);
|
||||||
img->SetSpacing(x[1] - x[0], y[1] - y[0], 1.0);
|
img->SetSpacing(g.x[1] - g.x[0], g.y[1] - g.y[0], 1.0);
|
||||||
|
|
||||||
vtkNew<vtkDoubleArray> sc;
|
vtkNew<vtkDoubleArray> sc;
|
||||||
sc->SetName("v");
|
sc->SetName("v");
|
||||||
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
||||||
for (int j = 0; j < ny; ++j)
|
for (int j = 0; j < ny; ++j)
|
||||||
for (int i = 0; i < nx; ++i)
|
for (int i = 0; i < nx; ++i)
|
||||||
sc->SetValue(static_cast<vtkIdType>(j) * nx + i, v[j][i]); // i 最快
|
sc->SetValue(static_cast<vtkIdType>(j) * nx + i, g.valueAt(i, j)); // i 最快
|
||||||
img->GetPointData()->SetScalars(sc);
|
img->GetPointData()->SetScalars(sc);
|
||||||
|
|
||||||
// 色阶:用 Phase 1 core 的 ColorScale 解析 colorBar(网格色阶 alpha=0-255)
|
// vmin/vmax 来自 Grid;若退化(==)则用数据极值兜底,避免 LUT/contour 退化。
|
||||||
json cb = json::parse(std::ifstream(dataDir + "colorbar.json"))["data"]["properties"]["colorBar"];
|
double vmin = g.vmin, vmax = g.vmax;
|
||||||
ColorScale cs;
|
if (vmin >= vmax) {
|
||||||
std::vector<double> levels;
|
const auto& vals = g.values();
|
||||||
for (auto& pr : cb) {
|
vmin = vals.empty() ? 0.0 : vals.front();
|
||||||
double val = std::stod(pr[0].get<std::string>());
|
vmax = vmin;
|
||||||
cs.addStop(val, parseColor(pr[1].get<std::string>(), AlphaScale::Bit255));
|
for (double v : vals) {
|
||||||
levels.push_back(val);
|
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;
|
const int N = 256;
|
||||||
vtkNew<vtkLookupTable> lut;
|
vtkNew<vtkLookupTable> lut;
|
||||||
lut->SetNumberOfTableValues(N);
|
lut->SetNumberOfTableValues(N);
|
||||||
lut->SetTableRange(vmin, vmax);
|
lut->SetTableRange(vmin, vmax);
|
||||||
for (int t = 0; t < N; ++t) {
|
for (int t = 0; t < N; ++t) {
|
||||||
double val = vmin + (vmax - vmin) * t / (N - 1);
|
const double val = vmin + (vmax - vmin) * t / (N - 1);
|
||||||
auto c = cs.colorAt(val);
|
const auto c = cs.colorAt(val);
|
||||||
lut->SetTableValue(t, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0);
|
lut->SetTableValue(t, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0);
|
||||||
}
|
}
|
||||||
lut->Build();
|
lut->Build();
|
||||||
|
|
@ -90,8 +94,7 @@ void buildGridSection(vtkRenderer* ren, const std::string& dataDir)
|
||||||
|
|
||||||
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
||||||
banded->SetInputConnection(surf->GetOutputPort());
|
banded->SetInputConnection(surf->GetOutputPort());
|
||||||
banded->SetNumberOfContours(static_cast<int>(levels.size()));
|
banded->GenerateValues(20, vmin, vmax);
|
||||||
for (int k = 0; k < static_cast<int>(levels.size()); ++k) banded->SetValue(k, levels[k]);
|
|
||||||
banded->GenerateContourEdgesOn();
|
banded->GenerateContourEdgesOn();
|
||||||
banded->SetScalarModeToValue();
|
banded->SetScalarModeToValue();
|
||||||
|
|
||||||
|
|
@ -118,6 +121,25 @@ void buildGridSection(vtkRenderer* ren, const std::string& dataDir)
|
||||||
ren->ResetCamera();
|
ren->ResetCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层;DS 项在 UserRole 存 dsId。
|
||||||
|
void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& 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
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
|
|
@ -125,18 +147,21 @@ int main(int argc, char* argv[])
|
||||||
QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat());
|
QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat());
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
// 本地样本仓储(中文路径,末尾带 '/',readFile 直接拼文件名)。生命周期覆盖事件循环。
|
||||||
|
geopro::data::LocalSampleRepository repo(
|
||||||
|
"D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/");
|
||||||
|
|
||||||
QMainWindow window;
|
QMainWindow window;
|
||||||
window.setWindowTitle(QStringLiteral("Geopro 3.0 — ERT 网格剖面 (M1 demo)"));
|
window.setWindowTitle(QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)"));
|
||||||
window.resize(1280, 800);
|
window.resize(1280, 800);
|
||||||
|
|
||||||
|
// 中央 QVTK 视图(指针供联动回调使用)。
|
||||||
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
||||||
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
||||||
vtkWidget->setRenderWindow(renderWindow);
|
vtkWidget->setRenderWindow(renderWindow);
|
||||||
vtkNew<vtkRenderer> renderer;
|
vtkNew<vtkRenderer> renderer;
|
||||||
renderWindow->AddRenderer(renderer);
|
renderWindow->AddRenderer(renderer);
|
||||||
|
|
||||||
buildGridSection(renderer, "D:/dev/spike_data/");
|
|
||||||
|
|
||||||
auto* dockManager = new ads::CDockManager(&window);
|
auto* dockManager = new ads::CDockManager(&window);
|
||||||
window.setCentralWidget(dockManager);
|
window.setCentralWidget(dockManager);
|
||||||
|
|
||||||
|
|
@ -144,14 +169,65 @@ int main(int argc, char* argv[])
|
||||||
vtkDock->setWidget(vtkWidget);
|
vtkDock->setWidget(vtkWidget);
|
||||||
dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
|
dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
|
||||||
|
|
||||||
|
// 左 dock:对象树。
|
||||||
|
auto* tree = new QTreeWidget();
|
||||||
|
tree->setHeaderLabel(QStringLiteral("对象"));
|
||||||
|
populateTree(tree, repo.loadStructure());
|
||||||
auto* leftDock = new ads::CDockWidget(QStringLiteral("对象列表"));
|
auto* leftDock = new ads::CDockWidget(QStringLiteral("对象列表"));
|
||||||
leftDock->setWidget(new QLabel(QStringLiteral("(Phase 2 接入)")));
|
leftDock->setWidget(tree);
|
||||||
dockManager->addDockWidget(ads::LeftDockWidgetArea, leftDock);
|
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("数据集 / 属性"));
|
auto* rightDock = new ads::CDockWidget(QStringLiteral("数据集 / 属性"));
|
||||||
rightDock->setWidget(new QLabel(QStringLiteral("(Phase 2 接入)")));
|
rightDock->setWidget(propLabel);
|
||||||
dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock);
|
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();
|
window.show();
|
||||||
|
|
||||||
|
// 默认渲染第一个 DS,让窗口一打开就有图。
|
||||||
|
if (auto* first = tree->topLevelItemCount() > 0 ? tree->topLevelItem(0) : nullptr) {
|
||||||
|
// 递归找到首个带 dsId 的项。
|
||||||
|
QTreeWidgetItem* dsItem = nullptr;
|
||||||
|
QList<QTreeWidgetItem*> 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();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue