diff --git a/src/app/main.cpp b/src/app/main.cpp index 8359dd2..8803be9 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -4,9 +4,11 @@ // 数据:docs/剖面网格数据的色阶数据2等文件/(真实样本,UTF-8 中文路径,经 QFile 读取)。 #include +#include #include #include #include +#include #include #include @@ -47,7 +49,7 @@ #include #include #include -#include +#include #include #include #include @@ -55,7 +57,13 @@ namespace { -// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层;DS 项在 UserRole 存 dsId。 +// 角色:DS 项的 dd 类型存在 UserRole+1(dsId 存在 UserRole)。 +constexpr int kRoleDsId = Qt::UserRole; +constexpr int kRoleDdType = Qt::UserRole + 1; + +// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层。 +// DS 项可勾选(复选框):勾选驱动该数据集在场景中的显示;UserRole 存 dsId、UserRole+1 存 ddType。 +// 网格剖面(dd_section)默认勾选,启动即显示。 void populateTree(QTreeWidget* tree, const std::vector& gss) { for (const auto& gs : gss) { @@ -67,7 +75,13 @@ void populateTree(QTreeWidget* tree, const std::vector& gs 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)); + dsItem->setData(0, kRoleDsId, QString::fromStdString(ds.id)); + dsItem->setData(0, kRoleDdType, QString::fromStdString(ds.ddType)); + // DS 项可勾选:勾选 = 在场景中显示该数据集。 + dsItem->setFlags(dsItem->flags() | Qt::ItemIsUserCheckable); + // 网格剖面默认显示;其余默认不勾。 + dsItem->setCheckState( + 0, ds.ddType == "dd_section" ? Qt::Checked : Qt::Unchecked); } } } @@ -208,6 +222,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re centerLayout->setContentsMargins(0, 0, 0, 0); centerLayout->setSpacing(0); + // 工具条:仅「二维/三维」相机开关,作用于整个场景(不绑定内容)。互斥二选一,默认二维。 auto* viewToolBar = new QToolBar(); auto* cameraGroup = new QActionGroup(viewToolBar); cameraGroup->setExclusive(true); @@ -218,23 +233,10 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re cameraGroup->addAction(act2D); cameraGroup->addAction(act3D); act2D->setChecked(true); // 默认二维 - viewToolBar->addSeparator(); - auto* actVoxel = viewToolBar->addAction(QStringLiteral("三维体素")); - // 三按钮做成统一互斥的"3 路视图模式":任意时刻恰一个勾选。 - actVoxel->setCheckable(true); - cameraGroup->addAction(actVoxel); centerLayout->addWidget(viewToolBar); centerLayout->addWidget(vtkWidget, 1); - // 交互切片 widget:dd_voxel 时对体素 vtkImageData 加可拖切面(dd_slice)。 - // 需 interactor(render window 已设),切回非体素视图时 Off。用 shared_ptr 跨 lambda 共享。 - auto sliceWidget = std::make_shared>(); - - auto hideSlice = [sliceWidget]() { - if (sliceWidget->Get()) (*sliceWidget)->Off(); - }; - - auto* vtkDock = new ads::CDockWidget(QStringLiteral("剖面视图")); + auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维/三维视图")); vtkDock->setWidget(centerWidget); dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); @@ -255,124 +257,146 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re rightDock->setWidget(propLabel); dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock); - // 视图模式状态:当前数据集 id / 名称(供「二维/三维」按钮重渲染剖面用)。 - auto currentDsId = std::make_shared(); - auto currentDsName = std::make_shared(); + // ── 勾选驱动显示(核心)────────────────────────────────────────────── + // 已显示数据集 → 其 VTK props 的映射。一个数据集可对应多个 prop(如剖面 bands+edges)。 + // 用 shared_ptr 让多个 lambda 共享同一映射;props 由 renderer 引用计数保活。 + using PropList = std::vector>; + auto dsProps = std::make_shared>(); - // 渲染当前数据集的【剖面】:按 *cameraMode 选相机;体素态切回剖面也走这里。 - // Scene/renderWindow 按【裸指针值】捕获:Scene 挂 window 父链、renderer 被 renderWindow - // 引用计数持有,生命周期覆盖事件循环。repo 由调用方保活。 - auto renderSectionCurrent = - [&repo, scene, renderWindowPtr, applyCurrentCamera, hideSlice, propLabel, currentDsId, - currentDsName]() { - if (currentDsId->isEmpty()) return; // 还没有选中任何数据集 - const std::string dsId = currentDsId->toStdString(); - const auto g = repo.loadGrid(dsId); - const auto cs = repo.loadColorScale(dsId); - hideSlice(); // 回到剖面视图:关闭可能存在的体素切片 - scene->clear(); - const auto actors = geopro::render::buildGridContour(g, cs); - scene->addActor(actors.bands); - scene->addActor(actors.edges); - applyCurrentCamera(); // 按当前 2D/3D 模式重设相机 - renderWindowPtr->Render(); - propLabel->setText(QStringLiteral("数据集: %1\n网格: %2 x %3\nvmin / vmax: %4 / %5") - .arg(*currentDsName) - .arg(g.nx()) - .arg(g.ny()) - .arg(g.vmin) - .arg(g.vmax)); - }; + // 初始化守卫:populateTree 会程序化 setCheckState,触发 itemChanged; + // 也用于「点击 DS 自动勾选」等程序化改动期间,避免回调递归/重入。 + auto building = std::make_shared(false); - // 联动:点击 DS 项 → 记为当前数据集 → 默认回到二维剖面(勾「二维」+ 俯视)。 - auto renderDataset = [currentDsId, currentDsName, cameraMode, act2D, renderSectionCurrent]( - QTreeWidgetItem* item) { - const QString id = item->data(0, Qt::UserRole).toString(); - if (id.isEmpty()) return; // GS/TM 节点无 dsId,忽略 - *currentDsId = id; - *currentDsName = item->text(0); - *cameraMode = CameraMode::Top2D; - act2D->setChecked(true); // 点数据集默认回到二维剖面 - renderSectionCurrent(); + // 是否已有任意可见对象(用于决定首个对象时套用当前相机预设)。 + auto hasVisible = [dsProps]() { + for (const auto& kv : *dsProps) { + if (!kv.second.empty()) return true; + } + return false; }; - QObject::connect(tree, &QTreeWidget::itemClicked, tree, - [renderDataset](QTreeWidgetItem* it, int) { renderDataset(it); }); + // 按 ddType 构建某数据集的 props 并加入 renderer。返回构建出的 props(可能为空)。 + // dd_section:loadGrid + loadColorScale → banded contour(bands + edges)。 + // dd_voxel:两条交叉剖面散点 → IDW 体素 → GPU 体绘制(WaitCursor 包裹耗时构建)。 + auto buildProps = [&repo, propLabel](const QString& dsId, + const QString& ddType) -> PropList { + PropList props; + if (ddType == "dd_section") { + const std::string id = dsId.toStdString(); + const auto g = repo.loadGrid(id); + const auto cs = repo.loadColorScale(id); + const auto actors = geopro::render::buildGridContour(g, cs); + if (actors.bands) props.push_back(actors.bands); + if (actors.edges) props.push_back(actors.edges); + } else if (ddType == "dd_voxel") { + QApplication::setOverrideCursor(Qt::WaitCursor); + const auto scatters = repo.loadVoxelScatters(); + const auto cs = repo.loadColorScale("grid1"); // 体素复用 grid1 色阶 + auto result = buildVoxelFromScatters(scatters, cs); + QApplication::restoreOverrideCursor(); + if (result.volume) props.push_back(result.volume); + else propLabel->setText(QStringLiteral("dd_voxel: 无可用散点数据")); + } + return props; + }; - // 视图模式:二维 = 剖面 + 俯视;三维 = 剖面 + 斜视。两者都重渲染当前剖面。 + // 勾选态变化:勾选 → 构建/显示该数据集 props;取消 → 移除其 props。 + // 多个数据集可同时勾选并叠加共存(不 clear 场景,按数据集增删 props)。 + // 已知限制:剖面用「距离-深度」局部坐标,体素用 GIS 局部坐标,二者坐标基不同, + // 叠加时不会真正空间对齐(后续 curtain/坐标统一工作,见 STATUS §5)。 + QObject::connect( + tree, &QTreeWidget::itemChanged, tree, + [dsProps, building, buildProps, hasVisible, applyCurrentCamera, rendererPtr, + renderWindowPtr](QTreeWidgetItem* item, int) { + if (*building) return; // 程序化改动期间不处理,避免初始化递归 + const QString dsId = item->data(0, kRoleDsId).toString(); + if (dsId.isEmpty()) return; // GS/TM 节点(无 dsId)忽略 + const QString ddType = item->data(0, kRoleDdType).toString(); + const bool checked = item->checkState(0) == Qt::Checked; + + if (checked) { + const bool wasEmpty = !hasVisible(); + auto it = dsProps->find(dsId); + if (it == dsProps->end() || it->second.empty()) { + // 首次显示:构建并加入 renderer。 + PropList props = buildProps(dsId, ddType); + for (const auto& p : props) rendererPtr->AddViewProp(p); + (*dsProps)[dsId] = std::move(props); + } else { + // 之前隐藏过(保留了 props):仅恢复可见。 + for (const auto& p : it->second) p->SetVisibility(1); + } + if (wasEmpty) applyCurrentCamera(); // 首个可见对象 → 套用当前相机预设 + rendererPtr->ResetCamera(); // 框住所有可见对象 + } else { + // 取消勾选:从场景移除该数据集 props。 + auto it = dsProps->find(dsId); + if (it != dsProps->end()) { + for (const auto& p : it->second) rendererPtr->RemoveViewProp(p); + dsProps->erase(it); + } + } + renderWindowPtr->Render(); + }); + + // ── 单击 DS → 更新右侧属性(与勾选区分;不改变可见性)──────────────── + QObject::connect( + tree, &QTreeWidget::itemClicked, tree, + [&repo, propLabel](QTreeWidgetItem* item, int) { + const QString dsId = item->data(0, kRoleDsId).toString(); + if (dsId.isEmpty()) return; // GS/TM 节点无属性 + const QString ddType = item->data(0, kRoleDdType).toString(); + const QString name = item->text(0); + if (ddType == "dd_section") { + const auto g = repo.loadGrid(dsId.toStdString()); + propLabel->setText( + QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n" + "vmin / vmax: %4 / %5") + .arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax)); + } else if (ddType == "dd_voxel") { + propLabel->setText( + QStringLiteral("数据集: %1\n类型: 三维体素 (dd_voxel)\n" + "说明: 两交叉剖面 IDW 体素") + .arg(name)); + } + }); + + // ── 工具条「二维/三维」:仅切换整场景相机预设,不改变内容可见性 ────────── QObject::connect(act2D, &QAction::triggered, vtkWidget, - [cameraMode, renderSectionCurrent]() { + [cameraMode, applyCurrentCamera, rendererPtr, renderWindowPtr]() { *cameraMode = CameraMode::Top2D; - renderSectionCurrent(); + applyCurrentCamera(); + rendererPtr->ResetCamera(); + renderWindowPtr->Render(); }); QObject::connect(act3D, &QAction::triggered, vtkWidget, - [cameraMode, renderSectionCurrent]() { + [cameraMode, applyCurrentCamera, rendererPtr, renderWindowPtr]() { *cameraMode = CameraMode::Free3D; - renderSectionCurrent(); - }); - - // 当前体素 vtkImageData(dd_slice 切面 widget 需复用,保活到下次重建)。 - auto voxelImage = std::make_shared>(); - - // dd_voxel:读两条交叉剖面 → IDW 体素 → GPU 体绘制 + 默认 3D 视角 + 可拖切片。 - QObject::connect(actVoxel, &QAction::triggered, vtkWidget, - [&repo, scene, rendererPtr, renderWindowPtr, cameraMode, actVoxel, propLabel, - sliceWidget, voxelImage]() { - QApplication::setOverrideCursor(Qt::WaitCursor); - const auto scatters = repo.loadVoxelScatters(); - const auto cs = repo.loadColorScale("grid1"); - auto result = buildVoxelFromScatters(scatters, cs); - QApplication::restoreOverrideCursor(); - if (!result.volume) { - propLabel->setText(QStringLiteral("dd_voxel: 无可用散点数据")); - return; - } - - // 关闭旧切片(避免引用已释放的旧 image)。 - if (sliceWidget->Get()) (*sliceWidget)->Off(); - - scene->clear(); - rendererPtr->AddViewProp(result.volume); - *voxelImage = result.image; - - // 体素 = 体块 + 3D 视角;高亮「三维体素」自身(互斥组会清掉 2D/3D)。 - *cameraMode = CameraMode::Free3D; - actVoxel->setChecked(true); - geopro::render::applyFree3D(rendererPtr); + applyCurrentCamera(); rendererPtr->ResetCamera(); - renderWindowPtr->Render(); // 先渲一帧确保 interactor 就绪 - - // 切片面在稀疏体素(两测线"十字"、其余空)上多为全黑空区、徒增困惑, - // M1 暂不默认显示;待有意义的切片交互(沿数据面)再加回。 - renderWindowPtr->Render(); - propLabel->setText( - QStringLiteral("dd_voxel 体素\n网格: %1 x %2 x %3\nvmin / vmax: %4 / %5\n" - "(拖动切面查看 dd_slice)") - .arg(result.nx) - .arg(result.ny) - .arg(result.nz) - .arg(result.vmin) - .arg(result.vmax)); }); - // 默认渲染第一个 DS,让窗口一打开就有图。 - if (auto* first = tree->topLevelItemCount() > 0 ? tree->topLevelItem(0) : nullptr) { - // 递归找到首个带 dsId 的项。 - QTreeWidgetItem* dsItem = nullptr; - QList stack{first}; - while (!stack.isEmpty() && !dsItem) { + // ── 启动默认:网格剖面 DS 已在 populateTree 中设为 Checked,但 itemChanged + // 在 connect 之前触发故未渲染。这里在 connect 之后,对已勾选的 DS 主动触发一次显示。 + { + QList stack; + for (int i = 0; i < tree->topLevelItemCount(); ++i) stack.append(tree->topLevelItem(i)); + while (!stack.isEmpty()) { QTreeWidgetItem* cur = stack.takeFirst(); - if (!cur->data(0, Qt::UserRole).toString().isEmpty()) { - dsItem = cur; - break; + const QString dsId = cur->data(0, kRoleDsId).toString(); + if (!dsId.isEmpty() && cur->checkState(0) == Qt::Checked) { + const QString ddType = cur->data(0, kRoleDdType).toString(); + PropList props = buildProps(dsId, ddType); + const bool wasEmpty = !hasVisible(); + for (const auto& p : props) rendererPtr->AddViewProp(p); + (*dsProps)[dsId] = std::move(props); + if (wasEmpty) applyCurrentCamera(); } for (int i = 0; i < cur->childCount(); ++i) stack.append(cur->child(i)); } - if (dsItem) { - tree->setCurrentItem(dsItem); - renderDataset(dsItem); - } + rendererPtr->ResetCamera(); + renderWindowPtr->Render(); } } diff --git a/src/data/repo/LocalSampleRepository.cpp b/src/data/repo/LocalSampleRepository.cpp index e684b2a..bcc379b 100644 --- a/src/data/repo/LocalSampleRepository.cpp +++ b/src/data/repo/LocalSampleRepository.cpp @@ -51,16 +51,25 @@ std::string LocalSampleRepository::readFile(const std::string& fileNameUtf8) con } std::vector LocalSampleRepository::loadStructure() { - DsNode ds; - ds.id = kDsId; - ds.name = u8"剖面网格数据1"; - ds.ddType = "grid"; + // 剖面网格数据集(dd_section):在对象树勾选后渲染 banded contour 帘面。 + DsNode dsSection; + dsSection.id = kDsId; + dsSection.name = u8"剖面网格数据1"; + dsSection.ddType = "dd_section"; + + // 三维体素数据集(dd_voxel):勾选后由两条交叉剖面散点 IDW 出体素并叠加显示。 + // 注意:体素与剖面坐标基不同(见 main.cpp 渲染处注释 + STATUS §5),暂不空间配准。 + DsNode dsVoxel; + dsVoxel.id = "voxel1"; + dsVoxel.name = u8"三维体素"; + dsVoxel.ddType = "dd_voxel"; TmNode tm; tm.id = "tm-ert1"; tm.name = "ERT1"; tm.confCode = "ERT"; - tm.dss.push_back(std::move(ds)); + tm.dss.push_back(std::move(dsSection)); + tm.dss.push_back(std::move(dsVoxel)); GsNode gs; gs.id = "gs-1";