feat/vtk-3d-view #7
|
|
@ -1,17 +1,22 @@
|
|||
#include "VolumeParamsDialog.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "EmptyAwareComboBox.hpp"
|
||||
#include <QComboBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHash>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QTreeWidgetItemIterator>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "EmptyAwareComboBox.hpp"
|
||||
#include "FormKit.hpp"
|
||||
#include "Theme.hpp"
|
||||
|
||||
|
|
@ -23,13 +28,14 @@ constexpr double kDefCellXY = 1.0;
|
|||
constexpr double kDefCellZ = 0.5;
|
||||
constexpr double kDefPower = 2.0;
|
||||
constexpr double kDefMaxDist = 4.0;
|
||||
constexpr int kRoleDsId = Qt::UserRole + 1; // 源列表项存 dsId
|
||||
constexpr int kRoleMountId = Qt::UserRole + 1; // 生成位置项存 id(itemData 默认存 confType)
|
||||
constexpr int kRoleDsId = Qt::UserRole + 1; // 源树项存 dsId
|
||||
constexpr int kRoleMountId = Qt::UserRole + 1; // 生成位置树项存 id
|
||||
constexpr int kRoleMountConfType = Qt::UserRole + 2; // 生成位置树项存 confType
|
||||
} // namespace
|
||||
|
||||
VolumeParamsDialog::VolumeParamsDialog(const QVector<QPair<QString, QString>>& sources,
|
||||
const QVector<VolumeMountTarget>& mounts,
|
||||
int defaultMountIndex, QWidget* parent)
|
||||
VolumeParamsDialog::VolumeParamsDialog(const QVector<VolumeSourceItem>& sources,
|
||||
const std::vector<geopro::data::StructNode>& structure,
|
||||
const QString& defaultMountId, QWidget* parent)
|
||||
: QDialog(parent) {
|
||||
setWindowTitle(QStringLiteral("生成三维体"));
|
||||
setModal(true);
|
||||
|
|
@ -40,21 +46,58 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector<QPair<QString, QString>>& s
|
|||
geopro::app::applyTokenizedStyleSheet(intro, QStringLiteral("color:{{text/secondary}};"));
|
||||
root->addWidget(intro);
|
||||
|
||||
// 左右两栏:左·源数据集列表(可勾选/取消,二次确认或修改) | 右·参数卡片。
|
||||
auto* cols = new QHBoxLayout();
|
||||
|
||||
// ── 左:源数据集列表 ──
|
||||
// ── 左:源数据集树(项目/GS/TM 容器 + 可勾选源 ds,挂到各自结构归属下)──
|
||||
auto* leftCard = formkit::formCard(this);
|
||||
auto* leftLay = formkit::cardBody(leftCard);
|
||||
formkit::addSection(leftLay, QStringLiteral("源数据集"), leftCard, false);
|
||||
sourceList_ = new QListWidget();
|
||||
sourceTree_ = new QTreeWidget();
|
||||
sourceTree_->setHeaderHidden(true);
|
||||
sourceTree_->setIndentation(14);
|
||||
{
|
||||
std::map<std::string, const geopro::data::StructNode*> byId;
|
||||
for (const auto& n : structure) byId[n.id] = &n;
|
||||
// 仅保留「源 ds 结构归属」向上的祖先链容器(树剪枝,不展示无关 GS/TM)。
|
||||
std::set<std::string> keep;
|
||||
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); // 默认全勾(=传入的当前勾选源)
|
||||
std::string p = s.structParentId.toStdString();
|
||||
while (!p.empty() && byId.count(p) && !keep.count(p)) {
|
||||
keep.insert(p);
|
||||
p = byId[p]->parentId;
|
||||
}
|
||||
leftLay->addWidget(sourceList_);
|
||||
}
|
||||
QHash<QString, QTreeWidgetItem*> cont; // 容器 id → item
|
||||
for (const auto& n : structure)
|
||||
if (keep.count(n.id)) {
|
||||
auto* it = new QTreeWidgetItem();
|
||||
it->setText(0, QString::fromStdString(n.name));
|
||||
cont.insert(QString::fromStdString(n.id), it);
|
||||
}
|
||||
for (const auto& n : structure)
|
||||
if (keep.count(n.id)) {
|
||||
auto* it = cont.value(QString::fromStdString(n.id));
|
||||
auto* par = cont.value(QString::fromStdString(n.parentId), nullptr);
|
||||
if (par)
|
||||
par->addChild(it);
|
||||
else
|
||||
sourceTree_->addTopLevelItem(it);
|
||||
}
|
||||
for (const auto& s : sources) {
|
||||
auto* it = new QTreeWidgetItem();
|
||||
it->setText(0, s.name.isEmpty() ? s.id : s.name);
|
||||
it->setData(0, kRoleDsId, s.id);
|
||||
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
|
||||
it->setCheckState(0, Qt::Checked); // 默认全勾(=传入的当前勾选源)
|
||||
auto* par = cont.value(s.structParentId, nullptr);
|
||||
if (par)
|
||||
par->addChild(it);
|
||||
else
|
||||
sourceTree_->addTopLevelItem(it);
|
||||
}
|
||||
}
|
||||
sourceTree_->expandAll();
|
||||
leftLay->addWidget(sourceTree_);
|
||||
cols->addWidget(leftCard, 1);
|
||||
|
||||
// ── 右:参数 ──
|
||||
|
|
@ -66,18 +109,40 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector<QPair<QString, QString>>& s
|
|||
name_ = new QLineEdit(QStringLiteral("三维体"));
|
||||
formkit::capField(name_);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("名称")), name_);
|
||||
cardLay->addLayout(form);
|
||||
|
||||
// 生成位置下拉:项目内 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);
|
||||
// 生成位置:GS/项目根/TM 层级树(单选),替代原扁平下拉。
|
||||
formkit::addSection(cardLay, QStringLiteral("生成位置"), card, false);
|
||||
mountTree_ = new QTreeWidget();
|
||||
mountTree_->setHeaderHidden(true);
|
||||
mountTree_->setIndentation(14);
|
||||
mountTree_->setMaximumHeight(geopro::app::scaledPx(160));
|
||||
{
|
||||
QHash<QString, QTreeWidgetItem*> mItems;
|
||||
for (const auto& n : structure) {
|
||||
auto* it = new QTreeWidgetItem();
|
||||
it->setText(0, QString::fromStdString(n.name));
|
||||
it->setData(0, kRoleMountId, QString::fromStdString(n.id));
|
||||
it->setData(0, kRoleMountConfType, n.type == 2 ? 2 : 1);
|
||||
mItems.insert(QString::fromStdString(n.id), it);
|
||||
}
|
||||
if (defaultMountIndex >= 0 && defaultMountIndex < mounts.size())
|
||||
mount_->setCurrentIndex(defaultMountIndex);
|
||||
formkit::capField(mount_);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("生成位置")), mount_);
|
||||
for (const auto& n : structure) {
|
||||
auto* it = mItems.value(QString::fromStdString(n.id));
|
||||
auto* par = mItems.value(QString::fromStdString(n.parentId), nullptr);
|
||||
if (par)
|
||||
par->addChild(it);
|
||||
else
|
||||
mountTree_->addTopLevelItem(it);
|
||||
}
|
||||
mountTree_->expandAll();
|
||||
// 默认选中:指定的 defaultMountId,否则首个节点。
|
||||
QTreeWidgetItem* def = mItems.value(defaultMountId, nullptr);
|
||||
if (!def && mountTree_->topLevelItemCount() > 0) def = mountTree_->topLevelItem(0);
|
||||
if (def) mountTree_->setCurrentItem(def);
|
||||
}
|
||||
cardLay->addWidget(mountTree_);
|
||||
|
||||
auto* form2 = formkit::makeEditForm();
|
||||
model_ = new EmptyAwareComboBox();
|
||||
model_->addItem(QStringLiteral("反距离加权 (IDW)"),
|
||||
static_cast<int>(geopro::data::VolumeBuildParams::Model::Idw));
|
||||
|
|
@ -88,7 +153,7 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector<QPair<QString, QString>>& s
|
|||
}
|
||||
model_->setCurrentIndex(0);
|
||||
formkit::capField(model_);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("插值模型")), model_);
|
||||
form2->addRow(formkit::editLabel(QStringLiteral("插值模型")), model_);
|
||||
|
||||
auto makeSpin = [this](double val, double min, double max, double step, int decimals) {
|
||||
auto* s = new QDoubleSpinBox();
|
||||
|
|
@ -103,12 +168,12 @@ VolumeParamsDialog::VolumeParamsDialog(const QVector<QPair<QString, QString>>& s
|
|||
cellZ_ = makeSpin(kDefCellZ, 0.01, 1000.0, 0.5, 2);
|
||||
power_ = makeSpin(kDefPower, 0.5, 6.0, 0.5, 1);
|
||||
maxDist_ = makeSpin(kDefMaxDist, 0.1, 10000.0, 1.0, 2);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("水平间距 (米)")), cellXY_);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("竖向间距 (米)")), cellZ_);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("IDW 幂次")), power_);
|
||||
form->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_);
|
||||
form2->addRow(formkit::editLabel(QStringLiteral("水平间距 (米)")), cellXY_);
|
||||
form2->addRow(formkit::editLabel(QStringLiteral("竖向间距 (米)")), cellZ_);
|
||||
form2->addRow(formkit::editLabel(QStringLiteral("IDW 幂次")), power_);
|
||||
form2->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_);
|
||||
cardLay->addLayout(form2);
|
||||
|
||||
cardLay->addLayout(form);
|
||||
cols->addWidget(card, 1);
|
||||
root->addLayout(cols);
|
||||
|
||||
|
|
@ -132,19 +197,21 @@ geopro::data::VolumeBuildParams VolumeParamsDialog::params() const {
|
|||
|
||||
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();
|
||||
for (QTreeWidgetItemIterator it(sourceTree_); *it; ++it) {
|
||||
const QString id = (*it)->data(0, kRoleDsId).toString();
|
||||
if (!id.isEmpty() && (*it)->checkState(0) == Qt::Checked) ids << id;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
QString VolumeParamsDialog::mountTargetId() const {
|
||||
return mount_->currentData(kRoleMountId).toString();
|
||||
auto* it = mountTree_->currentItem();
|
||||
return it ? it->data(0, kRoleMountId).toString() : QString();
|
||||
}
|
||||
|
||||
int VolumeParamsDialog::mountConfType() const {
|
||||
const int ct = mount_->currentData().toInt();
|
||||
auto* it = mountTree_->currentItem();
|
||||
const int ct = it ? it->data(0, kRoleMountConfType).toInt() : 0;
|
||||
return ct == 0 ? 1 : ct; // 缺省按 GS/项目根
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,53 @@
|
|||
#pragma once
|
||||
#include <QDialog>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <vector>
|
||||
|
||||
#include "repo/RepoTypes.hpp" // StructNode
|
||||
#include "repo/VolumeBuildParams.hpp"
|
||||
|
||||
class QLineEdit;
|
||||
class QComboBox;
|
||||
class QDoubleSpinBox;
|
||||
class QListWidget;
|
||||
class QTreeWidget;
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
// 生成位置候选(spec §7/§8):项目内 GS/项目根/TM。confType:1=GS/项目根 2=TM。
|
||||
struct VolumeMountTarget {
|
||||
// 源数据集(参与生成):id + 显示名 + 结构归属(structParentId,用于在左侧树里挂到其 GS/TM 下)。
|
||||
struct VolumeSourceItem {
|
||||
QString id;
|
||||
QString name;
|
||||
int confType = 1;
|
||||
QString structParentId;
|
||||
};
|
||||
|
||||
// 「生成三维体」对话框(spec §7/§8):左侧·源数据集列表(可勾选/取消,二次确认或修改);
|
||||
// 右侧·参数(名称 + 生成位置下拉 + 插值模型 + cellXY/cellZ/power/maxDist)。
|
||||
// 「生成三维体」对话框(spec §7/§8):左侧·源数据集**树**(项目/GS/TM/ds,可勾选/取消);
|
||||
// 右侧·参数(名称 + 生成位置**树**[GS/项目根/TM 层级单选] + 插值模型 + cellXY/cellZ/power/maxDist)。
|
||||
class VolumeParamsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// sources:参与生成的源数据集 (dsId, 显示名);mounts:生成位置候选;defaultMountIndex:默认选中位置下标。
|
||||
VolumeParamsDialog(const QVector<QPair<QString, QString>>& sources,
|
||||
const QVector<VolumeMountTarget>& mounts, int defaultMountIndex,
|
||||
QWidget* parent = nullptr);
|
||||
// sources:参与生成的源 ds(含结构归属);structure:项目结构(GS/TM),左右两树共用;
|
||||
// defaultMountId:默认选中的生成位置 id(空=选首个)。
|
||||
VolumeParamsDialog(const QVector<VolumeSourceItem>& sources,
|
||||
const std::vector<geopro::data::StructNode>& structure,
|
||||
const QString& defaultMountId, QWidget* parent = nullptr);
|
||||
|
||||
QString volumeName() const;
|
||||
geopro::data::VolumeBuildParams params() const;
|
||||
QStringList sourceDatasetIds() const; // 左侧最终勾选的源 ds id
|
||||
QStringList sourceDatasetIds() const; // 左侧树最终勾选的源 ds id
|
||||
QString mountTargetId() const; // 生成位置 id(structParentId)
|
||||
int mountConfType() const; // 生成位置 confType(1=GS/项目根 2=TM)
|
||||
|
||||
private:
|
||||
QLineEdit* name_ = nullptr;
|
||||
QComboBox* mount_ = nullptr;
|
||||
QComboBox* model_ = nullptr;
|
||||
QDoubleSpinBox* cellXY_ = nullptr;
|
||||
QDoubleSpinBox* cellZ_ = nullptr;
|
||||
QDoubleSpinBox* power_ = nullptr;
|
||||
QDoubleSpinBox* maxDist_ = nullptr;
|
||||
QListWidget* sourceList_ = nullptr;
|
||||
QTreeWidget* sourceTree_ = nullptr; // 左:源数据集树(可勾选)
|
||||
QTreeWidget* mountTree_ = nullptr; // 右:生成位置树(单选)
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -718,20 +718,23 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
[&window, &nav, scene3dRepo, refreshAnalysis, lastSourceRows,
|
||||
lastStructNodes](const QString& /*dsTypeCode*/, const QStringList& sourceIds) {
|
||||
if (sourceIds.isEmpty()) return;
|
||||
// 源 ds (id,名称):名称从最近拉取的行查(缺则用 id)。
|
||||
QVector<QPair<QString, QString>> sources;
|
||||
// 源 ds(id,名称,结构归属):名称/structParentId 从最近拉取的行查(缺则用 id)。
|
||||
QVector<geopro::app::VolumeSourceItem> sources;
|
||||
for (const QString& id : sourceIds) {
|
||||
QString name = id;
|
||||
geopro::app::VolumeSourceItem s;
|
||||
s.id = id;
|
||||
s.name = id;
|
||||
for (const auto& r : *lastSourceRows)
|
||||
if (r.id == id.toStdString()) { name = QString::fromStdString(r.dsName); break; }
|
||||
sources.push_back({id, name});
|
||||
if (r.id == id.toStdString()) {
|
||||
s.name = QString::fromStdString(r.dsName);
|
||||
s.structParentId = QString::fromStdString(r.structParentId);
|
||||
break;
|
||||
}
|
||||
// 生成位置候选:项目内 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);
|
||||
sources.push_back(std::move(s));
|
||||
}
|
||||
// 左右两树共用项目结构(GS/项目根/TM 层级);生成位置默认选首个节点。
|
||||
geopro::app::VolumeParamsDialog dlg(sources, *lastStructNodes, /*defaultMountId=*/QString(),
|
||||
&window);
|
||||
if (dlg.exec() != QDialog::Accepted) return;
|
||||
const QStringList chosen = dlg.sourceDatasetIds();
|
||||
if (chosen.isEmpty()) return; // 全取消则不生成
|
||||
|
|
|
|||
Loading…
Reference in New Issue