#include "dialogalgoarg.h" #include "ui_dialogalgoarg.h" #include "PathManager.h" #include "StyledMessageBox.h" #include "HandEyeCalibWidget.h" #include "ToolExtrinsicWidget.h" #include "WeldSeamPresenter.h" #include #include #include #include #include #include namespace { bool IsIdentityMatrix(const double matrix[16]) { for (int i = 0; i < 16; ++i) { const double expected = (i / 4 == i % 4) ? 1.0 : 0.0; if (std::fabs(matrix[i] - expected) > 1e-9) return false; } return true; } } // namespace DialogAlgoArg::DialogAlgoArg(QWidget* parent) : QDialog(parent) , ui(new Ui::DialogAlgoArg) { ui->setupUi(this); setWindowTitle(QStringLiteral("焊缝定位 - 算法参数设置")); InitNumericEditors(); InitCommunicationWidgets(); } DialogAlgoArg::~DialogAlgoArg() { delete ui; } void DialogAlgoArg::SetPresenter(WeldSeamPresenter* presenter) { m_presenter = presenter; Load(); } void DialogAlgoArg::InitNumericEditors() { auto setupDouble = [](QLineEdit* edit, double minimum, double maximum) { auto* validator = new QDoubleValidator(minimum, maximum, 6, edit); validator->setNotation(QDoubleValidator::StandardNotation); edit->setValidator(validator); }; setupDouble(ui->editValleyChkWinWidth, 0.0, 10000.0); setupDouble(ui->editValleyMaxWidth, 0.0, 10000.0); setupDouble(ui->editValleyMinHeight, 0.0, 10000.0); setupDouble(ui->editYDeviationMax, 0.0, 10000.0); setupDouble(ui->editMaxSkipDistance, 0.0, 100000.0); setupDouble(ui->editZDeviationMax, 0.0, 10000.0); setupDouble(ui->editMinLTypeTreeLen, 0.0, 100000.0); setupDouble(ui->editMinVTypeTreeLen, 0.0, 100000.0); ui->editMaxLineSkipNum->setValidator(new QIntValidator(0, 100000, ui->editMaxLineSkipNum)); ui->editWeldRefPoints->setValidator(new QIntValidator(0, 1000, ui->editWeldRefPoints)); ui->editTcpPort->setValidator(new QIntValidator(1, 65535, ui->editTcpPort)); } void DialogAlgoArg::InitCommunicationWidgets() { if (!m_toolExtrinsicWidget && ui->verticalLayout_toolExtrinsicHost) { m_toolExtrinsicWidget = new ToolExtrinsicWidget(this); m_toolExtrinsicWidget->setTargetOffsetVisible(true); m_toolExtrinsicWidget->setAxialAngleRangeVisible(true); ui->verticalLayout_toolExtrinsicHost->addWidget(m_toolExtrinsicWidget); } if (!m_handEyeCalibWidget && ui->verticalLayout_handEyeCalibHost) { m_handEyeCalibWidget = new HandEyeCalibWidget(this); m_handEyeCalibWidget->setMatrixEditable(true); m_handEyeCalibWidget->setExtrinsicControlsVisible(false); m_handEyeCalibWidget->setTargetOffsetVisible(false); m_handEyeCalibWidget->setApproachOffsetVisible(false); m_handEyeCalibWidget->setDefaultFilePath( PathManager::GetInstance().GetAppConfigDirectory()); ui->verticalLayout_handEyeCalibHost->addWidget(m_handEyeCalibWidget); connect(m_handEyeCalibWidget, &HandEyeCalibWidget::calibMatrixLoaded, this, &DialogAlgoArg::onCalibMatrixLoaded); connect(m_handEyeCalibWidget, &HandEyeCalibWidget::saveCalibRequested, this, &DialogAlgoArg::onSaveCalibRequested); } } void DialogAlgoArg::SetDoubleText(QLineEdit* edit, double value) { if (edit) edit->setText(QString::number(value, 'g', 12)); } void DialogAlgoArg::SetIntText(QLineEdit* edit, int value) { if (edit) edit->setText(QString::number(value)); } bool DialogAlgoArg::TryGetDouble(QLineEdit* edit, const QString& label, double& value) { bool ok = false; value = edit->text().trimmed().toDouble(&ok); if (ok && edit->hasAcceptableInput()) return true; StyledMessageBox::warning(this, QStringLiteral("参数错误"), QStringLiteral("“%1”输入的数值无效或超出范围。").arg(label)); edit->setFocus(); edit->selectAll(); return false; } bool DialogAlgoArg::TryGetInt(QLineEdit* edit, const QString& label, int& value) { bool ok = false; value = edit->text().trimmed().toInt(&ok); if (ok && edit->hasAcceptableInput()) return true; StyledMessageBox::warning(this, QStringLiteral("参数错误"), QStringLiteral("“%1”输入的整数无效或超出范围。").arg(label)); edit->setFocus(); edit->selectAll(); return false; } void DialogAlgoArg::Load() { if (!m_presenter || !m_presenter->GetConfigManager()) return; const ConfigResult config = m_presenter->GetConfigManager()->GetConfigResult(); const auto& p = config.algorithmParams; SetDoubleText(ui->editValleyChkWinWidth, p.valleyParam.chkWinWidth); SetDoubleText(ui->editValleyMaxWidth, p.valleyParam.maxWidth); SetDoubleText(ui->editValleyMinHeight, p.valleyParam.minHeight); SetIntText(ui->editMaxLineSkipNum, p.treeGrowParam.maxLineSkipNum); SetDoubleText(ui->editYDeviationMax, p.treeGrowParam.yDeviationMax); SetDoubleText(ui->editMaxSkipDistance, p.treeGrowParam.maxSkipDistance); SetDoubleText(ui->editZDeviationMax, p.treeGrowParam.zDeviationMax); SetDoubleText(ui->editMinLTypeTreeLen, p.treeGrowParam.minLTypeTreeLen); SetDoubleText(ui->editMinVTypeTreeLen, p.treeGrowParam.minVTypeTreeLen); SetIntText(ui->editWeldRefPoints, p.weldSeamParam.weldRefPoints); SetIntText(ui->editTcpPort, config.tcpPort); LoadCommunicationConfig(); } void DialogAlgoArg::LoadCommunicationConfig() { if (!m_presenter || !m_presenter->GetConfigManager() || !m_toolExtrinsicWidget || !m_handEyeCalibWidget) return; const ConfigResult config = m_presenter->GetConfigManager()->GetConfigResult(); QVector toolCameras; QVector handEyeCameras; if (config.cameraList.empty()) { toolCameras.append({1, QStringLiteral("相机 1")}); handEyeCameras.append({1, QStringLiteral("相机 1")}); } else { for (size_t i = 0; i < config.cameraList.size(); ++i) { const int cameraIndex = static_cast(i + 1); const QString name = QString::fromStdString(config.cameraList[i].name); toolCameras.append({cameraIndex, name}); handEyeCameras.append({cameraIndex, name}); } } m_toolExtrinsicWidget->setCameraList(toolCameras); m_handEyeCalibWidget->setCameraList(handEyeCameras); for (const auto& camera : handEyeCameras) { const VrHandEyeCalibMatrix* matrix = config.FindHandEyeMatrix(camera.cameraIndex); VrHandEyeCalibMatrix defaults; defaults.cameraIndex = camera.cameraIndex; if (!matrix) matrix = &defaults; m_toolExtrinsicWidget->setData( camera.cameraIndex, matrix->eulerOrder, matrix->rotX, matrix->rotY, matrix->rotZ, matrix->offsetX, matrix->offsetY, matrix->offsetZ, matrix->axialAngleMin, matrix->axialAngleMax); m_handEyeCalibWidget->setCalibData( camera.cameraIndex, matrix->matrix, !IsIdentityMatrix(matrix->matrix)); m_handEyeCalibWidget->setCalibType(camera.cameraIndex, matrix->calibType); } } bool DialogAlgoArg::Save() { if (!m_presenter || !m_presenter->GetConfigManager()) return false; if (m_presenter->GetScanState() != WeldSeamPresenter::ScanState::Ready) { StyledMessageBox::warning(this, QStringLiteral("无法保存"), QStringLiteral("扫描或计算期间不能修改参数。")); return false; } SystemConfig config = m_presenter->GetConfigManager()->GetConfig(); auto& p = config.configResult.algorithmParams; if (!TryGetDouble(ui->editValleyChkWinWidth, QStringLiteral("检测窗口宽度"), p.valleyParam.chkWinWidth)) return false; if (!TryGetDouble(ui->editValleyMaxWidth, QStringLiteral("V 型缺陷最大宽度"), p.valleyParam.maxWidth)) return false; if (!TryGetDouble(ui->editValleyMinHeight, QStringLiteral("V 型缺陷最小高度"), p.valleyParam.minHeight)) return false; if (!TryGetInt(ui->editMaxLineSkipNum, QStringLiteral("最大跳过扫描线数"), p.treeGrowParam.maxLineSkipNum)) return false; if (!TryGetDouble(ui->editYDeviationMax, QStringLiteral("Y 最大偏差"), p.treeGrowParam.yDeviationMax)) return false; if (!TryGetDouble(ui->editMaxSkipDistance, QStringLiteral("最大跳过距离"), p.treeGrowParam.maxSkipDistance)) return false; if (!TryGetDouble(ui->editZDeviationMax, QStringLiteral("Z 最大偏差"), p.treeGrowParam.zDeviationMax)) return false; if (!TryGetDouble(ui->editMinLTypeTreeLen, QStringLiteral("L 型树最小长度"), p.treeGrowParam.minLTypeTreeLen)) return false; if (!TryGetDouble(ui->editMinVTypeTreeLen, QStringLiteral("V 型树最小长度"), p.treeGrowParam.minVTypeTreeLen)) return false; if (!TryGetInt(ui->editWeldRefPoints, QStringLiteral("焊缝中间参考点数"), p.weldSeamParam.weldRefPoints)) return false; int tcpPort = 0; if (!TryGetInt(ui->editTcpPort, QStringLiteral("TCP 服务端口"), tcpPort)) return false; config.configResult.tcpPort = static_cast(tcpPort); if (!SaveCommunicationConfig(config)) return false; auto* manager = m_presenter->GetConfigManager(); if (!manager->UpdateFullConfig(config) || !manager->SaveConfigToFile(PathManager::GetInstance().GetConfigFilePath().toStdString())) { StyledMessageBox::warning(this, QStringLiteral("保存失败"), QStringLiteral("无法写入配置文件。")); return false; } m_presenter->OnConfigChanged(config.configResult); return true; } bool DialogAlgoArg::SaveCommunicationConfig(SystemConfig& config) { if (!m_toolExtrinsicWidget || !m_handEyeCalibWidget) return true; if (!m_toolExtrinsicWidget->hasAcceptableInput()) { StyledMessageBox::warning(this, QStringLiteral("参数错误"), QStringLiteral("工具外参输入无效,请检查。")); return false; } ConfigResult& result = config.configResult; const int cameraCount = result.cameraList.empty() ? 1 : static_cast(result.cameraList.size()); for (int cameraIndex = 1; cameraIndex <= cameraCount; ++cameraIndex) { VrHandEyeCalibMatrix& matrix = result.EnsureHandEyeMatrix(cameraIndex); matrix.cameraIndex = cameraIndex; double values[16]; bool isCalibrated = false; if (m_handEyeCalibWidget->getCalibData(cameraIndex, values, isCalibrated) && isCalibrated) { std::memcpy(matrix.matrix, values, sizeof(values)); } int calibType = matrix.calibType; if (m_handEyeCalibWidget->getCalibType(cameraIndex, calibType)) { matrix.calibType = calibType; } int eulerOrder = matrix.eulerOrder; double rotX = matrix.rotX; double rotY = matrix.rotY; double rotZ = matrix.rotZ; double offsetX = matrix.offsetX; double offsetY = matrix.offsetY; double offsetZ = matrix.offsetZ; double axialAngleMin = matrix.axialAngleMin; double axialAngleMax = matrix.axialAngleMax; if (m_toolExtrinsicWidget->getData( cameraIndex, eulerOrder, rotX, rotY, rotZ, offsetX, offsetY, offsetZ, axialAngleMin, axialAngleMax)) { matrix.eulerOrder = eulerOrder; matrix.rotX = rotX; matrix.rotY = rotY; matrix.rotZ = rotZ; matrix.offsetX = offsetX; matrix.offsetY = offsetY; matrix.offsetZ = offsetZ; matrix.axialAngleMin = axialAngleMin; matrix.axialAngleMax = axialAngleMax; } } return true; } void DialogAlgoArg::ResetDefaults() { SetDoubleText(ui->editValleyChkWinWidth, 2.5); SetDoubleText(ui->editValleyMaxWidth, 20.0); SetDoubleText(ui->editValleyMinHeight, 1.0); SetIntText(ui->editMaxLineSkipNum, 50); SetDoubleText(ui->editYDeviationMax, 1.0); SetDoubleText(ui->editMaxSkipDistance, 30.0); SetDoubleText(ui->editZDeviationMax, 5.0); SetDoubleText(ui->editMinLTypeTreeLen, 50.0); SetDoubleText(ui->editMinVTypeTreeLen, 50.0); SetIntText(ui->editWeldRefPoints, 3); SetIntText(ui->editTcpPort, 7800); } void DialogAlgoArg::on_btnOK_clicked() { if (Save()) accept(); } void DialogAlgoArg::on_btnCancel_clicked() { reject(); } void DialogAlgoArg::on_btnApply_clicked() { if (Save()) { StyledMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("参数已应用。")); } } void DialogAlgoArg::on_btnReset_clicked() { const auto result = StyledMessageBox::question( this, QStringLiteral("确认重置"), QStringLiteral("确定要恢复算法测试默认参数吗?"), QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::Yes) ResetDefaults(); } void DialogAlgoArg::onCalibMatrixLoaded(int cameraIndex, const double* matrix) { Q_UNUSED(cameraIndex); Q_UNUSED(matrix); StyledMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("已从文件导入手眼标定矩阵。")); } void DialogAlgoArg::onSaveCalibRequested(int cameraIndex, const double* matrix) { Q_UNUSED(cameraIndex); Q_UNUSED(matrix); if (Save()) { StyledMessageBox::information(this, QStringLiteral("成功"), QStringLiteral("手眼标定参数已保存。")); } }