diff --git a/src/EnergyScaleForm.cpp b/src/EnergyScaleForm.cpp index f4c5995..1dedeef 100644 --- a/src/EnergyScaleForm.cpp +++ b/src/EnergyScaleForm.cpp @@ -1,14 +1,1023 @@ #include "EnergyScaleForm.h" #include "ui_EnergyScaleForm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "CustomQwtPlot.h" +#include "DataCalcProcess/GaussPolyCoe.h" +#include "DataCalcProcess/MathModelDefine.h" +#include "DataCalcProcess/NolinearLeastSquaresCurveFit.h" +#include +#include +#include +#include +#include + EnergyScaleForm::EnergyScaleForm(QWidget *parent) - : QWidget(parent) + : MeasureAnalysisView(parent) , ui(new Ui::EnergyScaleForm) + , _plot(nullptr) + , _rawDataCurve(nullptr) + , _fitCurve(nullptr) + , _rawDataSymbol(nullptr) + , m_isLoadingTable(false) { ui->setupUi(this); + this->_plot = new CustomQwtPlot(this); + ui->layout_fittingCurve->addWidget(this->_plot); + _rawDataCurve = new QwtPlotCurve("原始数据"); + _fitCurve = new QwtPlotCurve("拟合曲线"); + _rawDataSymbol = new QwtSymbol(QwtSymbol::Ellipse); + _rawDataSymbol->setSize(8); + _rawDataSymbol->setBrush(Qt::red); + _rawDataSymbol->setPen(QPen(Qt::black, 1)); + setupPlot(); + initComboBoxUi(); + initScaleDataTable(); + loadAllFilesInTheFolder(); + connect(ui->comboBox_channel, &QComboBox::currentTextChanged, this, &EnergyScaleForm::on_comboBox_channel_currentTextChanged); + connect(ui->comboBox_fit_type, &QComboBox::currentTextChanged,this, &EnergyScaleForm::on_comboBox_fit_type_currentTextChanged); + connect(ui->tablew_scale_data, &QTableWidget::cellChanged,this, &EnergyScaleForm::on_tablew_scale_data_cellChanged); } EnergyScaleForm::~EnergyScaleForm() { delete ui; } + +void EnergyScaleForm::InitViewWorkspace(const QString &project_name) +{ + +} + +void EnergyScaleForm::SetAnalyzeDataFilename(const QMap &data_files_set) +{ + if (!data_files_set.isEmpty()) { + const QString& data_name = data_files_set.firstKey(); + const QString& data_filename = data_files_set.first().toString(); + if (QFileInfo(data_filename).exists()) { + ui->groupBox_3->hide(); + QFileInfo fileInfo(data_filename); + QString baseName = fileInfo.baseName(); + ui->lineEdit_name->setText(baseName); + m_currentFilePath = data_filename; + QString errorMsg; + m_channelList = parseJsonToChannels(data_filename, errorMsg); + on_comboBox_channel_currentTextChanged(QStringLiteral(u"通道1")); + } + } +} + +void EnergyScaleForm::setupPlot() +{ + _plot->setCanvasBackground(Qt::white); + QwtPlotCanvas* canvas = qobject_cast(_plot->canvas()); + canvas->setFrameStyle(QFrame::NoFrame); + QFont font = this->font(); + font.setBold(false); + QwtText energy_label = QStringLiteral(u"道址"); + energy_label.setFont(font); + QwtText count_label = QStringLiteral(u"能量"); + count_label.setFont(font); + _plot->setAxisTitle(QwtPlot::xBottom, energy_label); + _plot->setAxisTitle(QwtPlot::yLeft, count_label); + // 设置轴自动缩放 + _plot->setAxisAutoScale(QwtPlot::xBottom, true); + _plot->setAxisAutoScale(QwtPlot::yLeft, true); + _plot->enableAxis(QwtPlot::xBottom); + _plot->enableAxis(QwtPlot::yLeft); + _plot->SetAxisDragScale(QwtPlot::xBottom, true); + // 启用鼠标追踪 + _plot->canvas()->setMouseTracking(true); +} + +void EnergyScaleForm::loadAllFilesInTheFolder() +{ + const QString& energy_scale_dir_path = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); + QDir dir(energy_scale_dir_path); + QFileInfoList infoList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + ui->listWidget->clear(); + for (const QFileInfo &info : infoList) { + QString displayName; + if (info.isFile()) { + displayName = info.baseName(); + } else { + displayName = info.fileName(); + } + + QListWidgetItem *item = new QListWidgetItem(displayName); + item->setData(Qt::UserRole, info.absoluteFilePath()); + ui->listWidget->addItem(item); + } + connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, &EnergyScaleForm::onItemDoubleClicked); +} + +QMap EnergyScaleForm::parseJsonToChannels(const QString &filePath, QString &errorMsg) +{ + QMap result; + errorMsg.clear(); + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + errorMsg = QString("无法打开文件: %1").arg(file.errorString()); + return result; + } + + QByteArray data = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); + if (parseError.error != QJsonParseError::NoError) { + errorMsg = QString("JSON 解析错误: %1").arg(parseError.errorString()); + return result; + } + + if (!doc.isObject()) { + errorMsg = "JSON 根节点不是对象"; + return result; + } + + QJsonObject rootObj = doc.object(); + if (rootObj.contains("Remark") && rootObj["Remark"].isString()) + ui->plainTextEdit_description->setPlainText(rootObj["Remark"].toString()); + for (auto it = rootObj.begin(); it != rootObj.end(); ++it) { + QString channelName = it.key(); + QJsonValue channelVal = it.value(); + if (!channelVal.isObject()) { + errorMsg += QString("通道 %1 的值不是对象,跳过\n").arg(channelName); + continue; + } + QJsonObject channelObj = channelVal.toObject(); + + ChannelData chData; + + if (channelObj.contains("EnergyFitDegree") && channelObj["EnergyFitDegree"].isDouble()) { + chData.energyFitDegree = channelObj["EnergyFitDegree"].toInt(); + } else { + errorMsg += QString("通道 %1 缺少 EnergyFitDegree 字段\n").arg(channelName); + continue; + } + + if (channelObj.contains("EnergyFitResultCoeffs") && channelObj["EnergyFitResultCoeffs"].isArray()) { + QJsonArray coeffsArr = channelObj["EnergyFitResultCoeffs"].toArray(); + for (QJsonValue v : coeffsArr) { + if (v.isDouble()) + chData.energyFitResultCoeffs.append(v.toDouble()); + else + errorMsg += QString("通道 %1 的 EnergyFitResultCoeffs 包含非数字\n").arg(channelName); + } + } else { + errorMsg += QString("通道 %1 缺少 EnergyFitResultCoeffs 字段\n").arg(channelName); + } + + if (channelObj.contains("FitData") && channelObj["FitData"].isArray()) { + QJsonArray fitDataArr = channelObj["FitData"].toArray(); + for (QJsonValue rowVal : fitDataArr) { + if (!rowVal.isArray()) { + errorMsg += QString("通道 %1 的 FitData 行不是数组\n").arg(channelName); + continue; + } + QJsonArray rowArr = rowVal.toArray(); + if (rowArr.size() != 7) { + errorMsg += QString("通道 %1 的 FitData 行长度不为 7(实际 %2)\n") + .arg(channelName).arg(rowArr.size()); + continue; + } + FitDataRow row; + row.daoSite = rowArr[0].toDouble(); + row.energy = rowArr[1].toDouble(); + row.fittingEnergy = rowArr[2].toDouble(); + row.energyFittingDeviation = rowArr[3].toDouble(); + row.resolution = rowArr[4].toDouble(); + row.fitResolution = rowArr[5].toDouble(); + row.resolutionFittingDeviation = rowArr[6].toDouble(); + chData.fitData.append(row); + } + } else { + errorMsg += QString("通道 %1 缺少 FitData 字段\n").arg(channelName); + } + + if (channelObj.contains("FwhmFitResultCoeffs") && channelObj["FwhmFitResultCoeffs"].isArray()) { + QJsonArray fwhmArr = channelObj["FwhmFitResultCoeffs"].toArray(); + for (QJsonValue v : fwhmArr) { + if (v.isDouble()) + chData.fwhmFitResultCoeffs.append(v.toDouble()); + else + errorMsg += QString("通道 %1 的 FwhmFitResultCoeffs 包含非数字\n").arg(channelName); + } + } else { + errorMsg += QString("通道 %1 缺少 FwhmFitResultCoeffs 字段\n").arg(channelName); + } + + result.insert(channelName, chData); + } + + if (result.isEmpty() && errorMsg.isEmpty()) + errorMsg = "未找到任何通道数据。"; + + return result; +} + +void EnergyScaleForm::initComboBoxUi() +{ + ui->comboBox_fit_type->addItem(QStringLiteral(u"一次拟合")); + ui->comboBox_fit_type->addItem(QStringLiteral(u"二次拟合")); + for(int i = 1;i <= 32; ++i) + { + QString channelName = QStringLiteral(u"通道%1").arg(i); + ui->comboBox_channel->addItem(channelName); + if (!m_channelList.contains(channelName)) { + ChannelData emptyData; + emptyData.energyFitDegree = 1; + m_channelList.insert(channelName, emptyData); + } + } +} + +void EnergyScaleForm::initScaleDataTable() +{ + QTableWidget* table = ui->tablew_scale_data; + table->setColumnCount(7); + // 设置表格列名 + QStringList headers = { + "道址", "能量", "拟合能量", "能量拟合偏差", + "分辨率", "拟合分辨率", "分辨率拟合偏差" + }; + table->setHorizontalHeaderLabels(headers); + table->setEditTriggers(QTableWidget::DoubleClicked | QTableWidget::EditKeyPressed); + table->setSelectionBehavior(QTableWidget::SelectRows); // 整行选中 + table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); +} + +void EnergyScaleForm::showChannelData(const QString channelName) +{ + if (!m_channelList.contains(channelName)) { + return; + } + + m_isLoadingTable = true; // 防止触发cellChanged + ui->tablew_scale_data->setRowCount(0); + + const ChannelData& chData = m_channelList[channelName]; + + switch (chData.energyFitDegree) { + case 1: + { + ui->comboBox_fit_type->setCurrentIndex(0); + ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx")); + QString coefficient = QStringLiteral(u"a = %1 , b = %2").arg(chData.energyFitResultCoeffs.at(0)).arg(chData.energyFitResultCoeffs.at(1)); + ui->label_fit_result->setText(coefficient); + } break; + case 2: + ui->comboBox_fit_type->setCurrentIndex(1); + ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx + c*x^2")); + QString coefficient = QStringLiteral(u"a = %1 , b = %2, c = %3").arg(chData.energyFitResultCoeffs.at(0)).arg(chData.energyFitResultCoeffs.at(1)).arg(chData.energyFitResultCoeffs.at(2)); + ui->label_fit_result->setText(coefficient); + break; + } + QString energyCoeffsStr; + for (int i = 0; i < chData.energyFitResultCoeffs.size(); ++i) { + energyCoeffsStr += QString("系数%1: %2 ").arg(i+1).arg(chData.energyFitResultCoeffs[i], 0, 'f', 6); + } + QTableWidget* table = ui->tablew_scale_data; + table->setRowCount(chData.fitData.size()); + for (int row = 0; row < chData.fitData.size(); ++row) { + const FitDataRow& fitRow = chData.fitData[row]; + table->setItem(row, 0, new QTableWidgetItem(QString::number(fitRow.daoSite, 'f', 3))); + table->setItem(row, 1, new QTableWidgetItem(QString::number(fitRow.energy, 'f', 3))); + table->setItem(row, 2, new QTableWidgetItem(QString::number(fitRow.fittingEnergy, 'f', 3))); + table->setItem(row, 3, new QTableWidgetItem(QString::number(fitRow.energyFittingDeviation, 'f', 3))); + table->setItem(row, 4, new QTableWidgetItem(QString::number(fitRow.resolution, 'f', 3))); + table->setItem(row, 5, new QTableWidgetItem(QString::number(fitRow.fitResolution, 'f', 3))); + table->setItem(row, 6, new QTableWidgetItem(QString::number(fitRow.resolutionFittingDeviation, 'f', 3))); + + for (int col = 0; col < 7; ++col) { + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + if (col == 0 || col == 1 || col == 4) { + flags |= Qt::ItemIsEditable; + } + table->item(row, col)->setFlags(flags); + } + } + m_isLoadingTable = false; +} + +void EnergyScaleForm::showCurve() +{ + clearPlot(); + + QString channelName = ui->comboBox_channel->currentText(); + if (!m_channelList.contains(channelName)) + { + _plot->replot(); + return; + } + const ChannelData &chData = m_channelList[channelName]; + if (chData.fitData.isEmpty()) return; + + QVector xRaw, yRaw; + double xMin = 0; + double xMax = 4096; + + for (const FitDataRow &row : chData.fitData) { + xRaw.append(row.daoSite); + yRaw.append(row.energy); + } + _rawDataCurve->setSamples(xRaw, yRaw); + _rawDataCurve->setSymbol(_rawDataSymbol); + _rawDataCurve->setStyle(QwtPlotCurve::NoCurve); + _rawDataCurve->attach(_plot); + + if (!chData.energyFitResultCoeffs.isEmpty()) { + QVector fitPoints = generateFitCurvePoints(chData); + _fitCurve->setSamples(fitPoints); + _fitCurve->setPen(QPen(Qt::blue, 2)); // 拟合曲线用蓝色粗线 + _fitCurve->setStyle(QwtPlotCurve::Lines); + _fitCurve->attach(_plot); + } + + _plot->replot(); +} + +void EnergyScaleForm::clearPlot() +{ + _rawDataCurve->detach(); + _fitCurve->detach(); + _plot->detachItems(QwtPlotItem::Rtti_PlotCurve, false); + _plot->replot(); + +} + +QVector EnergyScaleForm::generateFitCurvePoints(const ChannelData &chData) +{ + int pointCount = 4096; + QVector points; + if (chData.energyFitResultCoeffs.isEmpty()) return points; + + for (int i = 0; i < pointCount; ++i) { + double y = 0.0; + switch (chData.energyFitDegree) { + case 1: // 一次拟合:y = a + b*x + y = LinearFunction(i,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1]); + break; + case 2: // 二次拟合:y = a + b*x + c*x² + if (chData.energyFitResultCoeffs.size() >= 3) { + y = QuadraticPolynomial(i,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1],chData.energyFitResultCoeffs[2]); + } + break; + default: + break; + } + points.append(QPointF(i, y)); + } + return points; +} + +QVector EnergyScaleForm::generateFitCurvePoints(const double a, const double b, const double c) +{ + int pointCount = 4096; + QVector points; + // if (chData.energyFitResultCoeffs.isEmpty()) return points; + + for (int i = 0; i < pointCount; ++i) { + double y = 0.0; + switch (ui->comboBox_fit_type->currentIndex() + 1) { + case 1: y = LinearFunction(i,a,b); ;break; + case 2: y = QuadraticPolynomial(i,a,b,c);break; + } + points.append(QPointF(i, y)); + } + return points; +} + +void EnergyScaleForm::calculateFitValues(ChannelData &chData) +{ + if (chData.fitData.isEmpty()) + return; + + if (!chData.energyFitResultCoeffs.isEmpty()) { + for (auto &row : chData.fitData) { + double x = row.daoSite; + double fitE = 0.0; + + if (chData.energyFitDegree == 1 && chData.energyFitResultCoeffs.size() >= 2) { + fitE = LinearFunction(x,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1]); + } + else if (chData.energyFitDegree == 2 && chData.energyFitResultCoeffs.size() >= 3) { + fitE = QuadraticPolynomial(x,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1],chData.energyFitResultCoeffs[2]); + } + + row.fittingEnergy = fitE; + row.energyFittingDeviation = row.energy - fitE; + } + } + + if (chData.fwhmFitResultCoeffs.size() >= 2) + { + arma::vec p(2); + p(0) = chData.fwhmFitResultCoeffs[0]; // k + p(1) = chData.fwhmFitResultCoeffs[1]; // c + + for (auto &row : chData.fitData) { + double E = row.fittingEnergy; + if (E <= 0) { + row.fitResolution = 0.0; + row.resolutionFittingDeviation = 0.0; + continue; + } + double fitFwhm = FwhmModel(E, p); + + row.fitResolution = fitFwhm; + row.resolutionFittingDeviation = row.resolution - fitFwhm; + } + } + else { + // 没有分辨率参数就清空 + for (auto &row : chData.fitData) { + row.fitResolution = 0.0; + row.resolutionFittingDeviation = 0.0; + } + } +} + +bool EnergyScaleForm::fitFwhmModel(ChannelData &chData) +{ + std::vector E_list, fwhm_list; + for (const auto& row : chData.fitData) { + if (row.fittingEnergy > 0 && row.resolution > 0) { + E_list.push_back(row.fittingEnergy); + fwhm_list.push_back(row.resolution); + } + } + if (E_list.size() < 2) { + return false; + } + arma::vec E(E_list.data(), E_list.size()); + arma::vec FWHM(fwhm_list.data(), fwhm_list.size()); + arma::vec params = NolinearLeastSquaresCurveFit::Lsqcurvefit(FwhmModel,E,FWHM, { 1.0, 1.0 }); + chData.fwhmFitResultCoeffs.clear(); + chData.fwhmFitResultCoeffs.append(params(0)); // k + chData.fwhmFitResultCoeffs.append(params(1)); // c + return true; +} + + +bool EnergyScaleForm::saveChannelDataToJson(const QString &filePath) +{ + if (filePath.isEmpty()) return false; + + QJsonObject rootObj; + + QString remark = ui->plainTextEdit_description->toPlainText().trimmed(); + rootObj["Remark"] = remark; + + for (auto it = m_channelList.begin(); it != m_channelList.end(); ++it) { + QString channelName = it.key(); + const ChannelData& chData = it.value(); + + QJsonObject channelObj; + channelObj["EnergyFitDegree"] = chData.energyFitDegree; + + // 能量拟合系数 + QJsonArray energyCoeffsArr; + for (double coeff : chData.energyFitResultCoeffs) { + energyCoeffsArr.append(coeff); + } + channelObj["EnergyFitResultCoeffs"] = energyCoeffsArr; + + // 拟合数据 + QJsonArray fitDataArr; + for (const FitDataRow& row : chData.fitData) { + QJsonArray rowArr; + rowArr.append(row.daoSite); + rowArr.append(row.energy); + rowArr.append(row.fittingEnergy); + rowArr.append(row.energyFittingDeviation); + rowArr.append(row.resolution); + rowArr.append(row.fitResolution); + rowArr.append(row.resolutionFittingDeviation); + fitDataArr.append(rowArr); + } + channelObj["FitData"] = fitDataArr; + + QJsonArray fwhmCoeffsArr; + for (double coeff : chData.fwhmFitResultCoeffs) { + fwhmCoeffsArr.append(coeff); + } + channelObj["FwhmFitResultCoeffs"] = fwhmCoeffsArr; + + rootObj[channelName] = channelObj; + } + + QJsonDocument doc(rootObj); + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, "错误", QString("保存文件失败:%1").arg(file.errorString())); + return false; + } + + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + return true; +} + +bool EnergyScaleForm::isDaoSiteDuplicated(const QString &channelName, double daoSite) +{ + if (!m_channelList.contains(channelName)) return false; + const auto& fitData = m_channelList[channelName].fitData; + for (const auto& row : fitData) { + if (qAbs(row.daoSite - daoSite) < 1e-3) { + return true; + } + } + return false; +} + + +void EnergyScaleForm::onItemDoubleClicked(QListWidgetItem *item) +{ + if (!item) return; + ui->plainTextEdit_description->clear(); + QString filePath = item->data(Qt::UserRole).toString(); + if (filePath.isEmpty()) return; + ui->lineEdit_name->setText(item->text()); + m_currentFilePath = filePath; + QString errorMsg; + m_channelList = parseJsonToChannels(filePath, errorMsg); + on_comboBox_channel_currentTextChanged(QStringLiteral(u"通道1")); +} + +void EnergyScaleForm::on_comboBox_channel_currentTextChanged(const QString &text) +{ + if (text.isEmpty()) { + return; + } + if (!m_channelList.contains(text)) { + ChannelData emptyData; + emptyData.energyFitDegree = 1; + m_channelList.insert(text, emptyData); + } + showChannelData(text); + showCurve(); +} + +void EnergyScaleForm::on_comboBox_fit_type_currentTextChanged(const QString &text) +{ + if(text == QStringLiteral(u"一次拟合")) { + ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx")); + } else if(text == QStringLiteral(u"二次拟合")) { + ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx + c*x^2")); + } + showCurve(); +} + +void EnergyScaleForm::on_pBtn_Add_clicked() +{ + QString channelName = ui->comboBox_channel->currentText(); + QDialog dialog(this); + dialog.setWindowTitle("添加能量刻度点"); + dialog.setFixedSize(300, 180); // 调整高度,预留分辨率输入 + dialog.setModal(true); + QVBoxLayout* mainLayout = new QVBoxLayout(&dialog); + mainLayout->setSpacing(15); + mainLayout->setContentsMargins(20, 20, 20, 20); + + // 道址输入 + QHBoxLayout* daoLayout = new QHBoxLayout(); + QLabel* daoLabel = new QLabel("道址:"); + QLineEdit* daoEdit = new QLineEdit(); + daoEdit->setPlaceholderText("请输入道址值(数字)"); + QDoubleValidator* daoValidator = new QDoubleValidator(0, 100000, 3, daoEdit); + daoValidator->setNotation(QDoubleValidator::StandardNotation); + daoEdit->setValidator(daoValidator); + daoLayout->addWidget(daoLabel); + daoLayout->addWidget(daoEdit); + + // 能量输入 + QHBoxLayout* energyLayout = new QHBoxLayout(); + QLabel* energyLabel = new QLabel("能量:"); + QLineEdit* energyEdit = new QLineEdit(); + energyEdit->setPlaceholderText("请输入能量值(数字)"); + QDoubleValidator* energyValidator = new QDoubleValidator(0, 10000, 3, energyEdit); + energyValidator->setNotation(QDoubleValidator::StandardNotation); + energyEdit->setValidator(energyValidator); + energyLayout->addWidget(energyLabel); + energyLayout->addWidget(energyEdit); + + //分辨率输入 + QHBoxLayout* resolutionLayout = new QHBoxLayout(); + QLabel* resolutionLabel = new QLabel("分辨率:"); + QLineEdit* resolutionEdit = new QLineEdit(); + resolutionEdit->setPlaceholderText("请输入分辨率(数字)"); + QDoubleValidator* resolutionValidator = new QDoubleValidator(0, 1000, 3, resolutionEdit); + resolutionValidator->setNotation(QDoubleValidator::StandardNotation); + resolutionEdit->setValidator(resolutionValidator); + resolutionLayout->addWidget(resolutionLabel); + resolutionLayout->addWidget(resolutionEdit); + + // 按钮行 + QHBoxLayout* btnLayout = new QHBoxLayout(); + QPushButton* okBtn = new QPushButton("确定"); + QPushButton* cancelBtn = new QPushButton("取消"); + okBtn->setDefault(true); + btnLayout->addStretch(); + btnLayout->addWidget(okBtn); + btnLayout->addWidget(cancelBtn); + + mainLayout->addLayout(daoLayout); + mainLayout->addLayout(energyLayout); + mainLayout->addLayout(resolutionLayout); + mainLayout->addLayout(btnLayout); + + connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept); + connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject); + daoEdit->setFocus(); + + if (dialog.exec() != QDialog::Accepted) { + return; + } + + QString daoStr = daoEdit->text().trimmed(); + QString energyStr = energyEdit->text().trimmed(); + QString resolutionStr = resolutionEdit->text().trimmed(); + if (daoStr.isEmpty() || energyStr.isEmpty()) { + QMessageBox::warning(this, "输入错误", "道址和能量不能为空"); + return; + } + + bool daoOk = false, energyOk = false, resolutionOk = false; + double daoSite = daoStr.toDouble(&daoOk); + double energy = energyStr.toDouble(&energyOk); + double resolution = resolutionStr.isEmpty() ? 0.0 : resolutionStr.toDouble(&resolutionOk); + if (!daoOk || !energyOk || (!resolutionStr.isEmpty() && !resolutionOk)) { + QMessageBox::warning(this, "输入错误", "请输入有效的数字"); + return; + } + + if (isDaoSiteDuplicated(channelName, daoSite)) { + QMessageBox::warning(this, "输入错误", "该道址已存在,无法重复添加"); + return; + } + + ChannelData& chData = m_channelList[channelName]; + QTableWidget* table = ui->tablew_scale_data; + FitDataRow newRow; + newRow.daoSite = daoSite; + newRow.energy = energy; + newRow.resolution = resolution; + newRow.fittingEnergy = 0.0; + newRow.energyFittingDeviation = 0.0; + newRow.fitResolution = 0.0; + newRow.resolutionFittingDeviation = 0.0; + chData.fitData.append(newRow); + + m_isLoadingTable = true; + int newRowIndex = table->rowCount(); + table->insertRow(newRowIndex); + table->setItem(newRowIndex, 0, new QTableWidgetItem(QString::number(daoSite, 'f', 3))); + table->setItem(newRowIndex, 1, new QTableWidgetItem(QString::number(energy, 'f', 3))); + table->setItem(newRowIndex, 4, new QTableWidgetItem(QString::number(resolution, 'f', 3))); + for (int col = 2; col < 7; ++col) { + if (col != 4) { + table->setItem(newRowIndex, col, new QTableWidgetItem("0.000")); + } + } + for (int col = 0; col < 7; ++col) { + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + if (col == 0 || col == 1 || col == 4) { + flags |= Qt::ItemIsEditable; + } + table->item(newRowIndex, col)->setFlags(flags); + } + m_isLoadingTable = false; + + table->scrollToBottom(); + table->selectRow(newRowIndex); + showCurve(); + QMessageBox::information(this, "成功", "数据点添加成功"); +} + + +void EnergyScaleForm::on_pBtn_Delete_clicked() +{ + + QTableWidget* table = ui->tablew_scale_data; + int currentRow = table->currentRow(); + if (currentRow < 0 || currentRow >= table->rowCount()) { + QMessageBox::warning(this, "提示", "请选中要删除的行"); + return; + } + + if (QMessageBox::question(this, "确认", "是否删除选中的行?", QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + + table->removeRow(currentRow); + + QString channelName = ui->comboBox_channel->currentText(); + if (m_channelList.contains(channelName)) { + ChannelData& chData = m_channelList[channelName]; + if (currentRow < chData.fitData.size()) { + chData.fitData.remove(currentRow); + } + // 删除后重新计算拟合值(如果有拟合系数) + calculateFitValues(chData); + // 刷新表格显示 + showChannelData(channelName); + } + + showCurve(); +} + + +void EnergyScaleForm::on_pBtn_fitting_clicked() +{ + QString channelName = ui->comboBox_channel->currentText(); + if (!m_channelList.contains(channelName)) { + QMessageBox::warning(this, "拟合失败", "当前通道无数据"); + return; + } + + ChannelData& chData = m_channelList[channelName]; + int rowCount = ui->tablew_scale_data->rowCount(); + int fitDegree = ui->comboBox_fit_type->currentIndex() + 1; + int minEnergyPoints = (fitDegree == 1) ? 2 : 3; + + if (rowCount < minEnergyPoints) { + QMessageBox::warning(this, "拟合失败", + QString("至少需要%1个数据点才能进行%2").arg(minEnergyPoints).arg(fitDegree == 1 ? "一次拟合" : "二次拟合")); + return; + } + + std::vector xRow,yRow; + for(int i = 0; i < rowCount;++i) { + QTableWidgetItem* daoItem = ui->tablew_scale_data->item(i,0); + QTableWidgetItem* energyItem = ui->tablew_scale_data->item(i,1); + if (!daoItem || !energyItem) continue; + bool daoOk = false, energyOk = false; + double dao = daoItem->text().toDouble(&daoOk); + double energy = energyItem->text().toDouble(&energyOk); + if (daoOk && energyOk) { + xRow.push_back(dao); + yRow.push_back(energy); + } + } + if (xRow.size() < minEnergyPoints || yRow.size() < minEnergyPoints) { + QMessageBox::warning(this, "拟合失败", "有效能量数据点不足"); + return; + } + + std::vector energyCoeff = GaussPolyCoe::PolynomialFit(xRow, yRow, fitDegree); + if (energyCoeff.size() < fitDegree + 1) { + QMessageBox::warning(this, "拟合失败", "能量拟合计算出错"); + return; + } + chData.energyFitDegree = fitDegree; + chData.energyFitResultCoeffs.clear(); + for (double coeff : energyCoeff) { + chData.energyFitResultCoeffs.append(coeff); + } + + fitFwhmModel(chData); + + + calculateFitValues(chData); + + m_isLoadingTable = true; + showChannelData(channelName); + m_isLoadingTable = false; + showCurve(); + + QString energyMsg; + if (fitDegree == 1) { + energyMsg = QString("能量拟合成功:a = %1 , b = %2") + .arg(energyCoeff[0], 0, 'f', 6) + .arg(energyCoeff[1], 0, 'f', 6); + } else { + energyMsg = QString("能量拟合成功:a = %1 , b = %2 , c = %3") + .arg(energyCoeff[0], 0, 'f', 6) + .arg(energyCoeff[1], 0, 'f', 6) + .arg(energyCoeff[2], 0, 'f', 6); + } +} + +void EnergyScaleForm::on_tablew_scale_data_cellChanged(int row, int column) +{ + if (m_isLoadingTable) return; + + QString channelName = ui->comboBox_channel->currentText(); + if (!m_channelList.contains(channelName)) return; + if (row < 0 || row >= m_channelList[channelName].fitData.size()) return; + + ChannelData& chData = m_channelList[channelName]; + FitDataRow& currentRow = chData.fitData[row]; + QTableWidgetItem* item = ui->tablew_scale_data->item(row, column); + if (!item) return; + + bool ok = false; + double value = item->text().toDouble(&ok); + if (!ok) { + // 输入非数字时恢复原值 + m_isLoadingTable = true; + if (column == 0) { + item->setText(QString::number(currentRow.daoSite, 'f', 3)); + } else if (column == 1) { + item->setText(QString::number(currentRow.energy, 'f', 3)); + } else if (column == 4) { + item->setText(QString::number(currentRow.resolution, 'f', 3)); + } + m_isLoadingTable = false; + QMessageBox::warning(this, "输入错误", "请输入有效的数字"); + return; + } + + // 只同步道址和能量列的修改 + bool needRecalc = false; + if (column == 0) { + // 检查道址重复 + if (isDaoSiteDuplicated(channelName, value) && qAbs(currentRow.daoSite - value) > 1e-3) { + m_isLoadingTable = true; + item->setText(QString::number(currentRow.daoSite, 'f', 3)); + m_isLoadingTable = false; + QMessageBox::warning(this, "提示", "道址重复,修改失败"); + return; + } + currentRow.daoSite = value; + needRecalc = true; + } else if (column == 1) { + currentRow.energy = value; + needRecalc = true; + } else if (column == 4) { + currentRow.resolution = value; + needRecalc = true; + } + + if (needRecalc) { + calculateFitValues(chData); + m_isLoadingTable = true; + showChannelData(channelName); + m_isLoadingTable = false; + showCurve(); + } +} + +void EnergyScaleForm::on_pBtn_SaveAs_clicked() +{ + // 选择保存路径 + QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); + QString filePath = QFileDialog::getSaveFileName(this, + "另存为能量刻度文件", + defaultDir, + "JSON文件 (*.json);;所有文件 (*.*)"); + + if (filePath.isEmpty()) { + return; + } + + // 确保后缀是.json + if (!filePath.endsWith(".json", Qt::CaseInsensitive)) { + filePath += ".json"; + } + + // 保存文件 + if (saveChannelDataToJson(filePath)) { + m_currentFilePath = filePath; // 更新当前文件路径 + ui->lineEdit_name->setText(QFileInfo(filePath).baseName()); + loadAllFilesInTheFolder(); + QMessageBox::information(this, "成功", "文件另存为成功"); + } else { + QMessageBox::critical(this, "失败", "文件另存为失败"); + } +} + +void EnergyScaleForm::on_pBtn_Save_clicked() +{ + if (saveChannelDataToJson(m_currentFilePath)) { + QMessageBox::information(this, "成功", "数据保存成功"); + } else { + QMessageBox::critical(this, "失败", "数据保存失败"); + } +} + + +void EnergyScaleForm::on_pBtn_Add_File_clicked() +{ + bool isOk = false; + QString fileName = QInputDialog::getText(this, + QStringLiteral(u"新建能量刻度文件"), + QStringLiteral(u"请输入文件名(无需后缀):"), + QLineEdit::Normal, + QStringLiteral(u"新刻度文件"), + &isOk); + if (!isOk || fileName.trimmed().isEmpty()) { + return; // 用户取消或输入为空 + } + fileName = fileName.trimmed(); + const QString energyScaleDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); + QDir dir(energyScaleDir); + if (!dir.exists() && !dir.mkpath(".")) { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"无法创建目录:%1").arg(energyScaleDir)); + return; + } + + QString filePath = dir.filePath(fileName + ".json"); + if (QFile::exists(filePath)) { + QMessageBox::warning(this, QStringLiteral(u"提示"), + QStringLiteral(u"文件%1已存在,请更换文件名").arg(fileName)); + return; + } + + QJsonObject rootObj; + for (int i = 1; i <= 32; ++i) { + QString channelName = QStringLiteral(u"通道%1").arg(i); + QJsonObject channelObj; + channelObj["EnergyFitDegree"] = 1; // 默认一次拟合 + channelObj["EnergyFitResultCoeffs"] = QJsonArray(); // 空系数 + channelObj["FitData"] = QJsonArray(); // 空拟合数据 + channelObj["FwhmFitResultCoeffs"] = QJsonArray(); // 空分辨率系数 + rootObj[channelName] = channelObj; + } + + QJsonDocument doc(rootObj); + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"无法创建文件:%1\n%2").arg(filePath).arg(file.errorString())); + return; + } + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + + loadAllFilesInTheFolder(); + for (int i = 0; i < ui->listWidget->count(); ++i) { + QListWidgetItem* item = ui->listWidget->item(i); + if (item->text() == fileName) { + ui->listWidget->setCurrentItem(item); + break; + } + } + + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"文件%1创建成功").arg(fileName + ".json")); +} + + +void EnergyScaleForm::on_pBtn_Delete_File_clicked() +{ + QListWidgetItem* selectedItem = ui->listWidget->currentItem(); + if (!selectedItem) { + QMessageBox::warning(this, QStringLiteral(u"提示"), + QStringLiteral(u"请先选中要删除的文件/文件夹")); + return; + } + + QString filePath = selectedItem->data(Qt::UserRole).toString(); + if (filePath.isEmpty()) { + QMessageBox::warning(this, QStringLiteral(u"错误"), + QStringLiteral(u"无法获取文件路径")); + return; + } + + QMessageBox::StandardButton ret = QMessageBox::question(this, + QStringLiteral(u"确认删除"), + QStringLiteral(u"是否确定删除%1?\n删除后无法恢复!").arg(selectedItem->text()), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + if (ret != QMessageBox::Yes) { + return; + } + + bool isDeleted = false; + QFileInfo fileInfo(filePath); + if (fileInfo.isFile()) { + QFile file(filePath); + isDeleted = file.remove(); + } + + if (isDeleted) { + loadAllFilesInTheFolder(); + if (m_currentFilePath == filePath) { + m_currentFilePath.clear(); + ui->lineEdit_name->clear(); + ui->tablew_scale_data->setRowCount(0); + clearPlot(); + _plot->replot(); + ui->label_fit_result->clear(); + ui->label_fit_type_text->clear(); + } + QMessageBox::information(this, QStringLiteral(u"成功"), + QStringLiteral(u"删除成功")); + } else { + QMessageBox::critical(this, QStringLiteral(u"错误"), + QStringLiteral(u"删除失败:%1").arg(filePath)); + } +} + diff --git a/src/EnergyScaleForm.h b/src/EnergyScaleForm.h index 8a5a911..6c78953 100644 --- a/src/EnergyScaleForm.h +++ b/src/EnergyScaleForm.h @@ -2,21 +2,104 @@ #define ENERGYSCALEFORM_H #include +#include +#include +#include +#include +#include +class CustomQwtPlot; +class QListWidgetItem; namespace Ui { class EnergyScaleForm; } -class EnergyScaleForm : public QWidget +struct FitDataRow { + double daoSite;//道址 + double energy;//能量 + double fittingEnergy;//拟合能量 + double energyFittingDeviation;//能量拟合偏差 + double resolution;//分辨率 + double fitResolution;//拟合分辨率 + double resolutionFittingDeviation;//分辨率拟合偏差 +}; + +struct ChannelData { + int energyFitDegree;//拟合公式 + QVector energyFitResultCoeffs;//能量拟合系数 + QVector fitData;// 多条拟合数据行 + QVector fwhmFitResultCoeffs;// FWHM 拟合系数 +}; + +class EnergyScaleForm : public MeasureAnalysisView { Q_OBJECT public: explicit EnergyScaleForm(QWidget *parent = nullptr); ~EnergyScaleForm(); + virtual void InitViewWorkspace(const QString& project_name) override final; + virtual void SetAnalyzeDataFilename(const QMap& data_files_set); +private: + void setupPlot(); + //加载能量刻度文件夹下所有的文件 + void loadAllFilesInTheFolder(); + //读取JSON文件 + QMap parseJsonToChannels(const QString &filePath, QString &errorMsg); + //初始化通道数 + void initComboBoxUi(); + void initScaleDataTable(); + void showChannelData(const QString channelName); + void showCurve(); + + void clearPlot(); + QVector generateFitCurvePoints(const ChannelData &chData); + QVector generateFitCurvePoints(const double a = 0,const double b = 0,const double c = 0); + + //计算拟合值和偏差 + void calculateFitValues(ChannelData &chData); + //保存通道数据到JSON文件 + bool saveChannelDataToJson(const QString &filePath); + //检查道址是否重复 + bool isDaoSiteDuplicated(const QString &channelName, double daoSite); + bool fitFwhmModel(ChannelData &chData); + + +private slots: + void onItemDoubleClicked(QListWidgetItem *item); + void on_comboBox_channel_currentTextChanged(const QString &text); + void on_comboBox_fit_type_currentTextChanged(const QString &text); + + void on_pBtn_Add_clicked(); + + void on_pBtn_Delete_clicked(); + + void on_pBtn_fitting_clicked(); + + void on_tablew_scale_data_cellChanged(int row, int column); + + void on_pBtn_SaveAs_clicked(); + + void on_pBtn_Save_clicked(); + + void on_pBtn_Add_File_clicked(); + + void on_pBtn_Delete_File_clicked(); private: Ui::EnergyScaleForm *ui; + CustomQwtPlot* _plot = nullptr; + QMap m_channelList; + + QwtPlotCurve *_rawDataCurve; // 原始数据散点曲线 + QwtPlotCurve *_fitCurve; // 拟合曲线 + QwtSymbol *_rawDataSymbol; // 原始数据点样式 + + // 防止表格填充时触发cellChanged信号 + bool m_isLoadingTable = false; + //记录当前加载的文件路径 + QString m_currentFilePath; + }; #endif // ENERGYSCALEFORM_H diff --git a/src/EnergyScaleForm.ui b/src/EnergyScaleForm.ui index 19daa12..fc1274b 100644 --- a/src/EnergyScaleForm.ui +++ b/src/EnergyScaleForm.ui @@ -6,8 +6,8 @@ 0 0 - 831 - 568 + 1086 + 584 @@ -19,338 +19,347 @@ 能量刻度 - - - - 190 - 420 - 621 - 101 - - - - - 0 - 0 - - - - true - - - - 道址 - - - - - 能量 - - - - - 拟合能量 - - - - - 能量拟合偏差 - - - - - 分辨率 - - - - - 拟合分辨率 - - - - - 分辨率拟合偏差 - - - - - - - 20 - 10 - 151 - 531 - - - - 能量刻度管理列表 - - - - - - - 1 - - - - - - + + + + + 能量刻度管理列表 + + + + 0 + + + 0 + + + 0 + + + 0 + - - - 添加 - - + - - - 删除 - - + + + + + 添加 + + + + + + + 删除 + + + + - - - - - - - 191 - 9 - 631 - 338 - - - - - - - - - 能量刻度信息 - - + + + + + + + + - + + + 能量刻度信息 + + + + + + + + + 85 + 0 + + + + 能量刻度命名: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 85 + 0 + + + + 备注说明: + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + 0 + 0 + + + + + + + + + + + - - + + + + + 通道: + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + - 85 - 0 + 40 + 20 - - 能量刻度命名: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + - + + + + + 保存 + + + + + + + 另存为 + + + + + + + + 公式拟合 + + + + + + 拟合公式: + + + + + + + + + + 0 + 0 + + + + + + + + 拟合 + + + + + + + + + y = a + bx + + + + + + + a = 1.21823, b = -5.0542 + + + + + + + + + + Qt::Horizontal + + + + + + + 能量刻度拟合数据 + + + - - + + + 新增 + + + + + + + 删除 + + + + + + + Qt::Horizontal + + - 85 - 0 + 40 + 20 - - 设备测量配置: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - - - - - - - 85 - 0 - - - - 备注说明: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - 0 - 0 - - - + - - - - - - - - - - 通道: - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - - - 保存 - - - - - - - 另存为 - - - - - - - - - - - 公式拟合 - - - - - - 拟合公式: - - - - - - - - - - 0 - 0 - - - - - - - - 拟合 - - - - - - - - - y = a + bx - - - - - - - a = 1.21823, b = -5.0542 - - - - - - - - - - - - background-color: rgb(85, 170, 255); - - + - - - Qt::Horizontal + + + - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + - - - - + + + + + + 0 + 0 + + + + true + + + + 道址 + + + + + 能量 + + + + + 拟合能量 + + + + + 能量拟合偏差 + + + + + 分辨率 + + + + + 拟合分辨率 + + + + + 分辨率拟合偏差 + + + + + + + diff --git a/src/GvfToCsv/GvfToCsv.cpp b/src/GvfToCsv/GvfToCsv.cpp index 08b1f25..93c393d 100644 --- a/src/GvfToCsv/GvfToCsv.cpp +++ b/src/GvfToCsv/GvfToCsv.cpp @@ -196,6 +196,12 @@ void GvfToCsv::convertGVF2CSVAsync(const QString &gvfPath, const QString &csvPat void GvfToCsv::onSqliteOperationCompleted(bool success, const QString &msg) { + if (!m_sqliteWorker) { + setLastError("SQLite 工作对象已被销毁"); + emit conversionFinished(false); + return; + } + if (!success) { setLastError(QString("GVF数据读取失败: %1").arg(msg)); cleanUp(); @@ -219,9 +225,14 @@ void GvfToCsv::onSqliteOperationCompleted(bool success, const QString &msg) void GvfToCsv::processDBData() { + if (!m_sqliteWorker) { + throw std::runtime_error("SQLite 工作对象已销毁,无法读取数据"); + } + QVector dbList = m_sqliteWorker->DataBaseList(); if (dbList.isEmpty()) { throw std::runtime_error("GVF数据库中无任何记录"); + return; } QFile outFile(m_csvPath); if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { diff --git a/src/MeasureAnalysisTreeView.cpp b/src/MeasureAnalysisTreeView.cpp index 5d5baed..b917b87 100644 --- a/src/MeasureAnalysisTreeView.cpp +++ b/src/MeasureAnalysisTreeView.cpp @@ -100,6 +100,16 @@ void MeasureAnalysisTreeView::onNodeDoubleClicked(const QModelIndex& index) } } } break; + case AnalysisType::EnergyScale:{ + MeasureAnalysisProjectModel* project_model = _model->GetProjectModel(project_name); + if (project_model) { + auto file_name = project_model->GetEnergyScaleFilename(); + if ( !file_name.isEmpty() ) { + data_files_set[QStringLiteral(u"能量刻度")] = file_name; + } + } + } + ;break; case AnalysisType::ParticleData: { MeasureAnalysisProjectModel* project_model = _model->GetProjectModel(project_name); if (project_model) { diff --git a/src/MeasureAnalysisView.cpp b/src/MeasureAnalysisView.cpp index f8cdf07..8e2012f 100644 --- a/src/MeasureAnalysisView.cpp +++ b/src/MeasureAnalysisView.cpp @@ -13,6 +13,7 @@ #include "AntiConformEnergySpectrumView.h" #include "CoincidenceEventTimeView.h" #include "NuclideAnalysisView.h" +#include "EnergyScaleForm.h" #include MeasureAnalysisView* MeasureAnalysisView::NewAnalyzeView(AnalysisType view_type) @@ -28,8 +29,8 @@ MeasureAnalysisView* MeasureAnalysisView::NewAnalyzeView(AnalysisType view_type) new_view->setDeleteOnClose(true); } break; case AnalysisType::EnergyScale: { - // new_view = new MeasureAnalysisDataTableView; - // new_view->setDeleteOnClose(true); + new_view = new EnergyScaleForm; + new_view->setDeleteOnClose(true); } break; case AnalysisType::EfficiencyScale: { // new_view = new MeasureAnalysisDataTableView; diff --git a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp index a255c75..edf96e9 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp +++ b/src/ThreeDimensionalConformityAnalysisView/ConformityAnalysis.cpp @@ -467,7 +467,7 @@ void ConformityAnalysis::loadHistoryFromFile() QString binPath = QDir(_workspace).filePath(QString("surface_%1.bin").arg(c)); if (!QFile::exists(jsonPath)) { - qDebug() << "[ConformityAnalysis] " << c << "重符合历史文件不存在:" << jsonPath; + // qDebug() << "[ConformityAnalysis] " << c << "重符合历史文件不存在:" << jsonPath; continue; } diff --git a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui index 3460110..0c50f9a 100644 --- a/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui +++ b/src/ThreeDimensionalConformityAnalysisView/ThreeDDisplay.ui @@ -180,7 +180,7 @@ - 106 + 50 21 @@ -200,7 +200,7 @@ - 106 + 50 21 @@ -266,7 +266,7 @@ - 106 + 50 21 @@ -286,7 +286,7 @@ - 106 + 50 21 @@ -358,7 +358,7 @@ - 150 + 100 21