feat/vtk-3d-view #7
|
|
@ -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; // 生成位置项存 id(itemData 默认存 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/项目根/TM(itemData 存 confType,UserRole+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
|
||||||
|
|
|
||||||
|
|
@ -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。confType:1=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; // 生成位置 id(structParentId)
|
||||||
|
int mountConfType() const; // 生成位置 confType(1=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
|
||||||
|
|
|
||||||
|
|
@ -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/项目根/TM(StructNode.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: 字体弹框 */ });
|
||||||
// 三维数据集栏右键「生成三维体」:弹参数对话框 → 客户端 createVolume(mock)→ 刷新三维分析栏
|
// (旧三维数据集栏「生成三维体」接线已退役——改由 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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue