feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
3 changed files with 106 additions and 31 deletions
Showing only changes of commit a41b428b09 - Show all commits

View File

@ -3,11 +3,12 @@
#include <QComboBox> #include <QComboBox>
#include "EmptyAwareComboBox.hpp" #include "EmptyAwareComboBox.hpp"
#include <QDialogButtonBox>
#include <QDoubleSpinBox> #include <QDoubleSpinBox>
#include <QFormLayout> #include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QListWidget>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -22,37 +23,68 @@ constexpr double kDefCellXY = 1.0;
constexpr double kDefCellZ = 0.5; constexpr double kDefCellZ = 0.5;
constexpr double kDefPower = 2.0; constexpr double kDefPower = 2.0;
constexpr double kDefMaxDist = 4.0; constexpr double kDefMaxDist = 4.0;
constexpr int kRoleDsId = Qt::UserRole + 1; // 源列表项存 dsId
constexpr int kRoleMountId = Qt::UserRole + 1; // 生成位置项存 iditemData 默认存 confType
} // namespace } // namespace
VolumeParamsDialog::VolumeParamsDialog(int sourceCount, QWidget* parent) : QDialog(parent) { VolumeParamsDialog::VolumeParamsDialog(const QVector<QPair<QString, QString>>& sources,
const QVector<VolumeMountTarget>& mounts,
int defaultMountIndex, QWidget* parent)
: QDialog(parent) {
setWindowTitle(QStringLiteral("生成三维体")); setWindowTitle(QStringLiteral("生成三维体"));
setModal(true); setModal(true);
auto* root = formkit::dialogRoot(this); auto* root = formkit::dialogRoot(this);
auto* intro = new QLabel(QStringLiteral("由 %1 个源数据集插值生成三维体").arg(sourceCount)); auto* intro = new QLabel(QStringLiteral("勾选参与生成的源数据集,设置参数与生成位置"));
geopro::app::applyTokenizedStyleSheet(intro, QStringLiteral("color:{{text/secondary}};")); geopro::app::applyTokenizedStyleSheet(intro, QStringLiteral("color:{{text/secondary}};"));
root->addWidget(intro); root->addWidget(intro);
// 统一外壳:表单卡片 + 分组标题(与「数据详情 / 属性面板」同款),编辑态/只读态一致。 // 左右两栏:左·源数据集列表(可勾选/取消,二次确认或修改) | 右·参数卡片。
auto* cols = new QHBoxLayout();
// ── 左:源数据集列表 ──
auto* leftCard = formkit::formCard(this);
auto* leftLay = formkit::cardBody(leftCard);
formkit::addSection(leftLay, QStringLiteral("源数据集"), leftCard, false);
sourceList_ = new QListWidget();
for (const auto& s : sources) {
auto* it = new QListWidgetItem(s.second.isEmpty() ? s.first : s.second, sourceList_);
it->setData(kRoleDsId, s.first);
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
it->setCheckState(Qt::Checked); // 默认全勾(=传入的当前勾选源)
}
leftLay->addWidget(sourceList_);
cols->addWidget(leftCard, 1);
// ── 右:参数 ──
auto* card = formkit::formCard(this); auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card); auto* cardLay = formkit::cardBody(card);
formkit::addSection(cardLay, QStringLiteral("参数"), card, false); formkit::addSection(cardLay, QStringLiteral("参数"), card, false);
auto* form = formkit::makeEditForm(); auto* form = formkit::makeEditForm();
name_ = new QLineEdit(QStringLiteral("三维体")); name_ = new QLineEdit(QStringLiteral("三维体"));
formkit::capField(name_); formkit::capField(name_);
form->addRow(formkit::editLabel(QStringLiteral("名称")), name_); form->addRow(formkit::editLabel(QStringLiteral("名称")), name_);
// 生成位置下拉:项目内 GS/项目根/TMitemData 存 confTypeUserRole+1 存 id
mount_ = new EmptyAwareComboBox();
for (const auto& m : mounts) {
mount_->addItem(m.name, m.confType);
mount_->setItemData(mount_->count() - 1, m.id, kRoleMountId);
}
if (defaultMountIndex >= 0 && defaultMountIndex < mounts.size())
mount_->setCurrentIndex(defaultMountIndex);
formkit::capField(mount_);
form->addRow(formkit::editLabel(QStringLiteral("生成位置")), mount_);
model_ = new EmptyAwareComboBox(); model_ = new EmptyAwareComboBox();
model_->addItem(QStringLiteral("反距离加权 (IDW)"), model_->addItem(QStringLiteral("反距离加权 (IDW)"),
static_cast<int>(geopro::data::VolumeBuildParams::Model::Idw)); static_cast<int>(geopro::data::VolumeBuildParams::Model::Idw));
model_->addItem(QStringLiteral("克里金 (Kriging)"), model_->addItem(QStringLiteral("克里金 (Kriging)"),
static_cast<int>(geopro::data::VolumeBuildParams::Model::Kriging)); static_cast<int>(geopro::data::VolumeBuildParams::Model::Kriging));
// 克里金本期未实现core 仅 IDW→ 禁用该项,默认选 IDW。
if (auto* m = qobject_cast<QStandardItemModel*>(model_->model())) { if (auto* m = qobject_cast<QStandardItemModel*>(model_->model())) {
if (auto* it = m->item(1)) it->setEnabled(false); if (auto* it = m->item(1)) it->setEnabled(false); // 克里金本期未实现 → 禁用
} }
model_->setCurrentIndex(0); model_->setCurrentIndex(0);
formkit::capField(model_); formkit::capField(model_);
@ -77,7 +109,8 @@ VolumeParamsDialog::VolumeParamsDialog(int sourceCount, QWidget* parent) : QDial
form->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_); form->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_);
cardLay->addLayout(form); cardLay->addLayout(form);
root->addWidget(card); cols->addWidget(card, 1);
root->addLayout(cols);
formkit::addDialogButtons(root, this, QStringLiteral("生成"), QStringLiteral("取消")); formkit::addDialogButtons(root, this, QStringLiteral("生成"), QStringLiteral("取消"));
} }
@ -97,4 +130,22 @@ geopro::data::VolumeBuildParams VolumeParamsDialog::params() const {
return p; return p;
} }
QStringList VolumeParamsDialog::sourceDatasetIds() const {
QStringList ids;
for (int i = 0; i < sourceList_->count(); ++i) {
QListWidgetItem* it = sourceList_->item(i);
if (it->checkState() == Qt::Checked) ids << it->data(kRoleDsId).toString();
}
return ids;
}
QString VolumeParamsDialog::mountTargetId() const {
return mount_->currentData(kRoleMountId).toString();
}
int VolumeParamsDialog::mountConfType() const {
const int ct = mount_->currentData().toInt();
return ct == 0 ? 1 : ct; // 缺省按 GS/项目根
}
} // namespace geopro::app } // namespace geopro::app

View File

@ -1,33 +1,51 @@
#pragma once #pragma once
#include <QDialog> #include <QDialog>
#include <QPair>
#include <QString> #include <QString>
#include <QStringList>
#include <QVector>
#include "repo/VolumeBuildParams.hpp" #include "repo/VolumeBuildParams.hpp"
class QLineEdit; class QLineEdit;
class QComboBox; class QComboBox;
class QDoubleSpinBox; class QDoubleSpinBox;
class QListWidget;
namespace geopro::app { namespace geopro::app {
// 「生成三维体」参数对话框:名称 + 插值模型IDW克里金占位禁用+ cellXY/cellZ/power/maxDist。 // 生成位置候选spec §7/§8项目内 GS/项目根/TM。confType1=GS/项目根 2=TM。
// sourceCount 仅用于提示文案("由 N 个源数据集插值生成")。 struct VolumeMountTarget {
// accept 后经 volumeName()/params() 取结果params() 不含 sourceDatasetIds由调用方填充 QString id;
QString name;
int confType = 1;
};
// 「生成三维体」对话框spec §7/§8左侧·源数据集列表可勾选/取消,二次确认或修改);
// 右侧·参数(名称 + 生成位置下拉 + 插值模型 + cellXY/cellZ/power/maxDist
class VolumeParamsDialog : public QDialog { class VolumeParamsDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit VolumeParamsDialog(int sourceCount, QWidget* parent = nullptr); // sources参与生成的源数据集 (dsId, 显示名)mounts生成位置候选defaultMountIndex默认选中位置下标。
VolumeParamsDialog(const QVector<QPair<QString, QString>>& sources,
const QVector<VolumeMountTarget>& mounts, int defaultMountIndex,
QWidget* parent = nullptr);
QString volumeName() const; QString volumeName() const;
geopro::data::VolumeBuildParams params() const; geopro::data::VolumeBuildParams params() const;
QStringList sourceDatasetIds() const; // 左侧最终勾选的源 ds id
QString mountTargetId() const; // 生成位置 idstructParentId
int mountConfType() const; // 生成位置 confType1=GS/项目根 2=TM
private: private:
QLineEdit* name_ = nullptr; QLineEdit* name_ = nullptr;
QComboBox* mount_ = nullptr;
QComboBox* model_ = nullptr; QComboBox* model_ = nullptr;
QDoubleSpinBox* cellXY_ = nullptr; QDoubleSpinBox* cellXY_ = nullptr;
QDoubleSpinBox* cellZ_ = nullptr; QDoubleSpinBox* cellZ_ = nullptr;
QDoubleSpinBox* power_ = nullptr; QDoubleSpinBox* power_ = nullptr;
QDoubleSpinBox* maxDist_ = nullptr; QDoubleSpinBox* maxDist_ = nullptr;
QListWidget* sourceList_ = nullptr;
}; };
} // namespace geopro::app } // namespace geopro::app

View File

@ -454,6 +454,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 三维分析数据源 = 最近对象树勾选拉取的 ds + 客户端三维体(mock) + 已保存切片; // 三维分析数据源 = 最近对象树勾选拉取的 ds + 客户端三维体(mock) + 已保存切片;
// splitByCategory 后注入 5 段(电阻率/视电阻率/瞬变/三维体/切片);二维(足迹)经 dim2D 仍走 col2D。 // splitByCategory 后注入 5 段(电阻率/视电阻率/瞬变/三维体/切片);二维(足迹)经 dim2D 仍走 col2D。
auto lastSourceRows = std::make_shared<std::vector<geopro::data::DsRow>>(); auto lastSourceRows = std::make_shared<std::vector<geopro::data::DsRow>>();
auto lastStructNodes = std::make_shared<std::vector<geopro::data::StructNode>>(); // 生成位置候选(项目内 GS/TM)
auto refreshAnalysis = [drawer, scene3dRepo, lastSourceRows]() { auto refreshAnalysis = [drawer, scene3dRepo, lastSourceRows]() {
const auto vols = scene3dRepo->volumeRows(); const auto vols = scene3dRepo->volumeRows();
const auto slices = scene3dRepo->sliceRows(); const auto slices = scene3dRepo->sliceRows();
@ -715,16 +716,33 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}); });
// 段头「+新增三维体」:弹参数对话框 → 组装真实 VoxelGenerateRequest → createVolume(mock) → 刷新。 // 段头「+新增三维体」:弹参数对话框 → 组装真实 VoxelGenerateRequest → createVolume(mock) → 刷新。
QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::generateVolumeRequested, &window, QObject::connect(analysisTab, &geopro::app::CategoryAnalysisTab::generateVolumeRequested, &window,
[&window, &nav, scene3dRepo, refreshAnalysis](const QString& /*dsTypeCode*/, [&window, &nav, scene3dRepo, refreshAnalysis, lastSourceRows,
const QStringList& sourceIds) { lastStructNodes](const QString& /*dsTypeCode*/, const QStringList& sourceIds) {
if (sourceIds.isEmpty()) return; if (sourceIds.isEmpty()) return;
geopro::app::VolumeParamsDialog dlg(static_cast<int>(sourceIds.size()), &window); // 源 ds (id,名称):名称从最近拉取的行查(缺则用 id
QVector<QPair<QString, QString>> sources;
for (const QString& id : sourceIds) {
QString name = id;
for (const auto& r : *lastSourceRows)
if (r.id == id.toStdString()) { name = QString::fromStdString(r.dsName); break; }
sources.push_back({id, name});
}
// 生成位置候选:项目内 GS/项目根/TMStructNode.type==2 → TM(2),其余 → GS/项目根(1))。
QVector<geopro::app::VolumeMountTarget> mounts;
for (const auto& n : *lastStructNodes)
mounts.push_back({QString::fromStdString(n.id), QString::fromStdString(n.name),
n.type == 2 ? 2 : 1});
geopro::app::VolumeParamsDialog dlg(sources, mounts, /*defaultMountIndex=*/0, &window);
if (dlg.exec() != QDialog::Accepted) return; if (dlg.exec() != QDialog::Accepted) return;
const QStringList chosen = dlg.sourceDatasetIds();
if (chosen.isEmpty()) return; // 全取消则不生成
const geopro::data::VolumeBuildParams p = dlg.params(); const geopro::data::VolumeBuildParams p = dlg.params();
geopro::data::VoxelGenerateRequest req; geopro::data::VoxelGenerateRequest req;
req.projectId = nav.currentProjectId().toStdString(); req.projectId = nav.currentProjectId().toStdString();
req.structParentId = dlg.mountTargetId().toStdString(); // 生成位置(归属)
req.structParentConfType = dlg.mountConfType();
req.name = dlg.volumeName().toStdString(); req.name = dlg.volumeName().toStdString();
for (const QString& id : sourceIds) req.sourceDatasetIds.push_back(id.toStdString()); for (const QString& id : chosen) req.sourceDatasetIds.push_back(id.toStdString());
req.interpModel = req.interpModel =
(p.interpModel == geopro::data::VolumeBuildParams::Model::Kriging) ? "Kriging" : "Idw"; (p.interpModel == geopro::data::VolumeBuildParams::Model::Kriging) ? "Kriging" : "Idw";
req.cellXY = p.cellXY; req.cellXY = p.cellXY;
@ -763,20 +781,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
[]() { /* TODO P4: O点位置弹框 */ }); []() { /* TODO P4: O点位置弹框 */ });
QObject::connect(c3, &geopro::app::Column3DDataset::fontClicked, vtkWidget, QObject::connect(c3, &geopro::app::Column3DDataset::fontClicked, vtkWidget,
[]() { /* TODO P4: 字体弹框 */ }); []() { /* TODO P4: 字体弹框 */ });
// 三维数据集栏右键「生成三维体」:弹参数对话框 → 客户端 createVolumemock→ 刷新三维分析栏 // (旧三维数据集栏「生成三维体」接线已退役——改由 analysisTab::generateVolumeRequested 走新对话框。)
// (新三维体作为"分析产物"出现在三维分析栏,勾选即渲染体)。
QObject::connect(c3, &geopro::app::Column3DDataset::generateVolumeRequested, &window,
[&window, scene3dRepo, refreshAnalysis](const QStringList& sourceIds) {
geopro::app::VolumeParamsDialog dlg(static_cast<int>(sourceIds.size()),
&window);
if (dlg.exec() != QDialog::Accepted) return;
geopro::data::VolumeBuildParams params = dlg.params();
for (const QString& id : sourceIds)
params.sourceDatasetIds.push_back(id.toStdString());
scene3dRepo->createVolume(std::move(params),
dlg.volumeName().toStdString());
refreshAnalysis(); // 新体行进入三维分析栏,勾选即渲染体
});
auto* ca = drawer->colAnalysis(); auto* ca = drawer->colAnalysis();
// 三维分析栏勾选(三维体/切片):体走控制器体素路径;切片(dd_slice)不进控制器(否则 loadSection // 三维分析栏勾选(三维体/切片):体走控制器体素路径;切片(dd_slice)不进控制器(否则 loadSection
@ -1775,9 +1780,10 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}); });
QObject::connect(&nav, &geopro::controller::WorkbenchNavController::structureLoaded, objectTree, QObject::connect(&nav, &geopro::controller::WorkbenchNavController::structureLoaded, objectTree,
[objectTree, datasetList, fileList, datasetTitle, datasetTabs, exceptionPanel, [objectTree, datasetList, fileList, datasetTitle, datasetTabs, exceptionPanel,
objAttrView, propView, anomalyBadge]( objAttrView, propView, anomalyBadge, lastStructNodes](
const QString& projectName, const QString& projectName,
const std::vector<geopro::data::StructNode>& nodes) { const std::vector<geopro::data::StructNode>& nodes) {
*lastStructNodes = nodes; // 供「生成三维体」对话框的生成位置下拉
objectTree->setStructure(projectName, nodes); objectTree->setStructure(projectName, nodes);
datasetList->clear(); datasetList->clear();
fileList->clear(); fileList->clear();