#include "dialogalgoarg.h" #include "ui_dialogalgoarg.h" #include "HandEyeCalibWidget.h" #include "NetworkConfigWidget.h" #include "ToolExtrinsicWidget.h" #include "PathManager.h" #include "JiuruiWorkpiecePosePresenter.h" #include "StyledMessageBox.h" #include #include #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(); initWorkpieceParamEditors(); initHandEyeCalibTab(); initNetworkConfigTab(); } DialogAlgoArg::~DialogAlgoArg() { delete ui; } void DialogAlgoArg::SetPresenter(JiuruiWorkpiecePosePresenter* presenter) { m_presenter = presenter; loadParams(); loadHandEyeCalibConfig(); // 配置工具参数控件 ui->toolExtrinsicWidget->setTargetOffsetVisible(true); ui->toolExtrinsicWidget->setAxialAngleRangeVisible(true); loadToolExtrinsicConfig(); loadNetworkConfig(); } void DialogAlgoArg::initNumericEditors() { auto setupDoubleEditor = [](QLineEdit* edit, double min, double max, int decimals) { auto* validator = new QDoubleValidator(min, max, decimals, edit); validator->setNotation(QDoubleValidator::StandardNotation); edit->setValidator(validator); }; // 角点检测的 6 个参数 setupDoubleEditor(ui->spinCornerTh, 0.0, 180.0, 6); setupDoubleEditor(ui->spinScale, 0.0, 200.0, 6); setupDoubleEditor(ui->spinMinEndingGap, 0.0, 100.0, 6); setupDoubleEditor(ui->spinMinEndingGapZ, 0.0, 100.0, 6); setupDoubleEditor(ui->spinJumpCornerTh1, 0.0, 100.0, 6); setupDoubleEditor(ui->spinJumpCornerTh2, 0.0, 180.0, 6); } void DialogAlgoArg::initWorkpieceParamEditors() { // 在 tabWidget 中新增独立的"工件参数"Tab 页 if (!ui->tabWidget || m_editWpLength) { return; } // 创建 Tab 页容器 auto* tabWorkpiece = new QWidget(); tabWorkpiece->setObjectName(QStringLiteral("tabWorkpiece")); tabWorkpiece->setStyleSheet(ui->tabCorner->styleSheet()); // 工件参数 GroupBox(三个参数:长、宽、厚) auto* groupBoxWorkpiece = new QGroupBox(QStringLiteral("工件参数 (WD_JiuruiWorkpieceParam)"), tabWorkpiece); groupBoxWorkpiece->setGeometry(10, 20, 671, 260); QFont groupFont = ui->groupBox_corner->font(); groupBoxWorkpiece->setFont(groupFont); // 工件长度 auto* labelLength = new QLabel(QStringLiteral("工件长度(len):"), groupBoxWorkpiece); labelLength->setGeometry(30, 50, 220, 32); labelLength->setFont(groupFont); m_editWpLength = new QLineEdit(groupBoxWorkpiece); m_editWpLength->setObjectName(QStringLiteral("spinWpLength")); m_editWpLength->setGeometry(280, 50, 200, 35); m_editWpLength->setFont(groupFont); m_editWpLength->setText(QStringLiteral("320")); // 工件宽度 auto* labelWidth = new QLabel(QStringLiteral("工件宽度(width):"), groupBoxWorkpiece); labelWidth->setGeometry(30, 110, 220, 32); labelWidth->setFont(groupFont); m_editWpWidth = new QLineEdit(groupBoxWorkpiece); m_editWpWidth->setObjectName(QStringLiteral("spinWpWidth")); m_editWpWidth->setGeometry(280, 110, 200, 35); m_editWpWidth->setFont(groupFont); m_editWpWidth->setText(QStringLiteral("140")); // 工件厚度 auto* labelThicknessWp = new QLabel(QStringLiteral("工件厚度(thickness):"), groupBoxWorkpiece); labelThicknessWp->setGeometry(30, 170, 220, 32); labelThicknessWp->setFont(groupFont); m_editWpThickness = new QLineEdit(groupBoxWorkpiece); m_editWpThickness->setObjectName(QStringLiteral("spinWpThickness")); m_editWpThickness->setGeometry(280, 170, 200, 35); m_editWpThickness->setFont(groupFont); m_editWpThickness->setText(QStringLiteral("48")); // 设置验证器 auto setupDouble = [](QLineEdit* edit, double min, double max, int decimals) { auto* validator = new QDoubleValidator(min, max, decimals, edit); validator->setNotation(QDoubleValidator::StandardNotation); edit->setValidator(validator); }; setupDouble(m_editWpLength, 0.0, 10000.0, 6); setupDouble(m_editWpWidth, 0.0, 10000.0, 6); setupDouble(m_editWpThickness, 0.0, 10000.0, 6); // 在"角点检测"Tab 之后插入"工件参数"Tab(索引 1) ui->tabWidget->insertTab(1, tabWorkpiece, QStringLiteral("工件参数")); } void DialogAlgoArg::initHandEyeCalibTab() { if (!ui || !ui->verticalLayout_handEyeCalibHost || m_handEyeCalibWidget) { return; } m_handEyeCalibWidget = new HandEyeCalibWidget(this); m_handEyeCalibWidget->setMatrixEditable(true); m_handEyeCalibWidget->setExtrinsicControlsVisible(true); 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::initNetworkConfigTab() { if (!ui || !ui->verticalLayout_networkConfigHost || m_networkConfigWidget) { return; } m_networkConfigWidget = new NetworkConfigWidget(false, true, this); m_networkConfigWidget->setExtrinsicControlsVisible(true); ui->verticalLayout_networkConfigHost->addWidget(m_networkConfigWidget); } void DialogAlgoArg::setDoubleEditorText(QLineEdit* edit, double value) { if (edit) { edit->setText(QString::number(value, 'g', 12)); } } void DialogAlgoArg::setIntEditorText(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) { StyledMessageBox::warning(this, QStringLiteral("错误"), QStringLiteral("\"%1\" 输入的数值无效。").arg(label)); edit->setFocus(); edit->selectAll(); return false; } return true; } bool DialogAlgoArg::tryGetInt(QLineEdit* edit, const QString& label, int& value) { bool ok = false; value = edit->text().trimmed().toInt(&ok); if (!ok) { StyledMessageBox::warning(this, QStringLiteral("错误"), QStringLiteral("\"%1\" 输入的整数无效。").arg(label)); edit->setFocus(); edit->selectAll(); return false; } return true; } void DialogAlgoArg::loadParams() { if (!m_presenter || !m_presenter->GetConfigManager()) { return; } const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult(); const VrCornerParam corner = configResult.algorithmParams.cornerParam; const VrJiuruiWorkpieceParam wp = configResult.algorithmParams.workpieceParam; setDoubleEditorText(ui->spinCornerTh, corner.cornerTh); setDoubleEditorText(ui->spinScale, corner.scale); setDoubleEditorText(ui->spinMinEndingGap, corner.minEndingGap); setDoubleEditorText(ui->spinMinEndingGapZ, corner.minEndingGap_z); setDoubleEditorText(ui->spinJumpCornerTh1, corner.jumpCornerTh_1); setDoubleEditorText(ui->spinJumpCornerTh2, corner.jumpCornerTh_2); setDoubleEditorText(m_editWpLength, wp.len); setDoubleEditorText(m_editWpWidth, wp.width); setDoubleEditorText(m_editWpThickness, wp.thickness); } void DialogAlgoArg::loadHandEyeCalibConfig() { if (!m_presenter || !m_handEyeCalibWidget || !m_presenter->GetConfigManager()) { return; } const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult(); QVector cameraInfos; if (configResult.cameraList.empty()) { HandEyeCalibCameraInfo defaultCam; defaultCam.cameraIndex = 1; defaultCam.displayName = QStringLiteral("相机 1"); cameraInfos.append(defaultCam); } else { for (size_t i = 0; i < configResult.cameraList.size(); ++i) { HandEyeCalibCameraInfo info; info.cameraIndex = static_cast(i + 1); info.displayName = QString::fromStdString(configResult.cameraList[i].name); cameraInfos.append(info); } } m_handEyeCalibWidget->setCameraList(cameraInfos); for (const auto& info : cameraInfos) { const VrHandEyeCalibMatrix* savedMatrix = configResult.FindHandEyeMatrix(info.cameraIndex); if (savedMatrix) { m_handEyeCalibWidget->setCalibData(info.cameraIndex, savedMatrix->matrix, !IsIdentityMatrix(savedMatrix->matrix)); m_handEyeCalibWidget->setExtrinsicData(info.cameraIndex, savedMatrix->eulerOrder, savedMatrix->rotX, savedMatrix->rotY, savedMatrix->rotZ, savedMatrix->approachOffset, savedMatrix->offsetX, savedMatrix->offsetY, savedMatrix->offsetZ); } else { const CalibMatrix calibMatrix = m_presenter->GetClibMatrix(info.cameraIndex - 1); m_handEyeCalibWidget->setCalibData(info.cameraIndex, calibMatrix.clibMatrix, !IsIdentityMatrix(calibMatrix.clibMatrix)); m_handEyeCalibWidget->setExtrinsicData(info.cameraIndex, 11, 0.0, 0.0, 0.0, 0.0); } } } void DialogAlgoArg::loadNetworkConfig() { if (!m_presenter || !m_presenter->GetConfigManager() || !m_networkConfigWidget) { return; } const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult(); NetworkConfigData netConfig; netConfig.tcpServerPort = configResult.tcpPort; netConfig.eulerOrder = configResult.eulerOrder; netConfig.outputEulerOrder = configResult.outputEulerOrder; netConfig.poseOutputOrder = configResult.poseOutputOrder; netConfig.byteOrder = configResult.byteOrder; m_networkConfigWidget->setConfig(netConfig); } bool DialogAlgoArg::saveParams() { if (!m_presenter || !m_presenter->GetConfigManager()) { return false; } SystemConfig systemConfig = m_presenter->GetConfigManager()->GetConfig(); // —— 读取共享算法参数 —— VrCornerParam& corner = systemConfig.configResult.algorithmParams.cornerParam; if (!tryGetDouble(ui->spinCornerTh, QStringLiteral("拐角阈值"), corner.cornerTh)) return false; if (!tryGetDouble(ui->spinScale, QStringLiteral("窗口比例因子"), corner.scale)) return false; if (!tryGetDouble(ui->spinMinEndingGap, QStringLiteral("Y方向最小结束间隙"), corner.minEndingGap)) return false; if (!tryGetDouble(ui->spinMinEndingGapZ, QStringLiteral("Z方向最小结束间隙"), corner.minEndingGap_z)) return false; if (!tryGetDouble(ui->spinJumpCornerTh1, QStringLiteral("跳变拐角阈值1"), corner.jumpCornerTh_1)) return false; if (!tryGetDouble(ui->spinJumpCornerTh2, QStringLiteral("跳变拐角阈值2"), corner.jumpCornerTh_2)) return false; VrJiuruiWorkpieceParam& wp = systemConfig.configResult.algorithmParams.workpieceParam; if (!tryGetDouble(m_editWpLength, QStringLiteral("工件长度"), wp.len)) return false; if (!tryGetDouble(m_editWpWidth, QStringLiteral("工件宽度"), wp.width)) return false; if (!tryGetDouble(m_editWpThickness, QStringLiteral("工件厚度"), wp.thickness)) return false; // —— 保存手眼标定矩阵到 handEyeCalibMatrixList —— if (m_handEyeCalibWidget) { const int cameraCount = std::max(1, static_cast(systemConfig.configResult.cameraList.size())); for (int camIdx = 1; camIdx <= cameraCount; ++camIdx) { systemConfig.configResult.EnsureHandEyeMatrix(camIdx); } for (int camIdx = 1; camIdx <= cameraCount; ++camIdx) { bool isCalibrated = false; double matrix[16]; int eulerOrder = 11; double rotX = 0.0, rotY = 0.0, rotZ = 0.0; double approachOffset = 0.0; double offsetX = 0.0, offsetY = 0.0, offsetZ = 0.0; const bool hasMatrix = m_handEyeCalibWidget->getCalibData(camIdx, matrix, isCalibrated) && isCalibrated; const bool hasExtrinsic = m_handEyeCalibWidget->getExtrinsicData( camIdx, eulerOrder, rotX, rotY, rotZ, approachOffset, offsetX, offsetY, offsetZ); VrHandEyeCalibMatrix* hePtr = systemConfig.configResult.FindHandEyeMatrix(camIdx); if (!hePtr) { systemConfig.configResult.EnsureHandEyeMatrix(camIdx); hePtr = systemConfig.configResult.FindHandEyeMatrix(camIdx); } if (hePtr) { hePtr->cameraIndex = camIdx; if (hasMatrix) { std::memcpy(hePtr->matrix, matrix, sizeof(double) * 16); } if (hasExtrinsic) { hePtr->eulerOrder = eulerOrder; hePtr->rotX = rotX; hePtr->rotY = rotY; hePtr->rotZ = rotZ; hePtr->offsetX = offsetX; hePtr->offsetY = offsetY; hePtr->offsetZ = offsetZ; } // 从工具参数控件获取该相机的工具外参 if (ui->toolExtrinsicWidget) { int toolEulerOrder = hePtr->eulerOrder; double toolRotX = hePtr->rotX; double toolRotY = hePtr->rotY; double toolRotZ = hePtr->rotZ; double toolOffsetX = hePtr->offsetX; double toolOffsetY = hePtr->offsetY; double toolOffsetZ = hePtr->offsetZ; double axialAngleMin = hePtr->axialAngleMin; double axialAngleMax = hePtr->axialAngleMax; if (ui->toolExtrinsicWidget->getData(camIdx, toolEulerOrder, toolRotX, toolRotY, toolRotZ, toolOffsetX, toolOffsetY, toolOffsetZ, axialAngleMin, axialAngleMax)) { hePtr->eulerOrder = toolEulerOrder; hePtr->rotX = toolRotX; hePtr->rotY = toolRotY; hePtr->rotZ = toolRotZ; hePtr->offsetX = toolOffsetX; hePtr->offsetY = toolOffsetY; hePtr->offsetZ = toolOffsetZ; hePtr->axialAngleMin = axialAngleMin; hePtr->axialAngleMax = axialAngleMax; } } } } } // —— 网络配置 —— if (m_networkConfigWidget) { const NetworkConfigData netConfig = m_networkConfigWidget->getConfig(); if (netConfig.tcpServerPort <= 0 || netConfig.tcpServerPort > 65535) { StyledMessageBox::warning(this, QStringLiteral("错误"), QStringLiteral("TCP端口必须在 1 到 65535 之间。")); return false; } systemConfig.configResult.tcpPort = static_cast(netConfig.tcpServerPort); systemConfig.configResult.eulerOrder = netConfig.eulerOrder; systemConfig.configResult.outputEulerOrder = netConfig.outputEulerOrder; systemConfig.configResult.poseOutputOrder = netConfig.poseOutputOrder; systemConfig.configResult.byteOrder = netConfig.byteOrder; } if (!m_presenter->GetConfigManager()->UpdateFullConfig(systemConfig)) { StyledMessageBox::warning(this, QStringLiteral("失败"), QStringLiteral("更新配置缓存失败。")); return false; } const QString configPath = PathManager::GetInstance().GetConfigFilePath(); if (!m_presenter->GetConfigManager()->SaveConfigToFile(configPath.toStdString())) { StyledMessageBox::warning(this, QStringLiteral("失败"), QStringLiteral("保存配置文件失败。")); return false; } m_presenter->OnConfigChanged(systemConfig.configResult); return true; } void DialogAlgoArg::resetParams() { setDoubleEditorText(ui->spinCornerTh, 60.0); setDoubleEditorText(ui->spinScale, 10.0); setDoubleEditorText(ui->spinMinEndingGap, 10.0); setDoubleEditorText(ui->spinMinEndingGapZ, 5.0); setDoubleEditorText(ui->spinJumpCornerTh1, 15.0); setDoubleEditorText(ui->spinJumpCornerTh2, 60.0); setDoubleEditorText(m_editWpLength, 320.0); setDoubleEditorText(m_editWpWidth, 140.0); setDoubleEditorText(m_editWpThickness, 48.0); } void DialogAlgoArg::on_btnOK_clicked() { if (saveParams()) { accept(); } } void DialogAlgoArg::on_btnCancel_clicked() { reject(); } void DialogAlgoArg::on_btnApply_clicked() { if (saveParams()) { StyledMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("参数已应用。")); } } void DialogAlgoArg::on_btnReset_clicked() { const auto ret = StyledMessageBox::question(this, QStringLiteral("确认重置"), QStringLiteral("确定要重置为默认参数吗?"), QMessageBox::Yes | QMessageBox::No); if (ret == QMessageBox::Yes) { resetParams(); } } 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 (saveParams()) { StyledMessageBox::information(this, QStringLiteral("成功"), QStringLiteral("手眼标定参数已保存。")); } else { StyledMessageBox::warning(this, QStringLiteral("失败"), QStringLiteral("保存手眼标定参数失败。")); } } // ========== 工具参数相关实现 ========== void DialogAlgoArg::loadToolExtrinsicConfig() { if (!m_presenter || !ui->toolExtrinsicWidget) return; ConfigManager* configManager = m_presenter->GetConfigManager(); if (!configManager) return; const ConfigResult configResult = configManager->GetConfigResult(); const auto& cameraList = configResult.cameraList; int cameraCount = std::max(1, static_cast(cameraList.size())); // 设置相机列表 QVector toolCameraList; for (size_t i = 0; i < cameraList.size(); ++i) { ToolExtrinsicCameraInfo info; info.cameraIndex = static_cast(i + 1); info.displayName = QString::fromStdString(cameraList[i].name); toolCameraList.append(info); } if (toolCameraList.empty()) { ToolExtrinsicCameraInfo defaultCam; defaultCam.cameraIndex = 1; defaultCam.displayName = QString::fromUtf8("相机 1"); toolCameraList.append(defaultCam); } ui->toolExtrinsicWidget->setCameraList(toolCameraList); // 加载每台相机的工具参数 for (int i = 0; i < cameraCount; ++i) { int camIdx = i + 1; int toolEulerOrder = 11; double rotX = 0.0; double rotY = 0.0; double rotZ = 0.0; double offsetX = 0.0; double offsetY = 0.0; double offsetZ = 0.0; double axialAngleMin = -180.0; double axialAngleMax = 180.0; for (const auto& toolConfig : configResult.handEyeCalibMatrixList) { if (toolConfig.cameraIndex == camIdx) { toolEulerOrder = toolConfig.eulerOrder; rotX = toolConfig.rotX; rotY = toolConfig.rotY; rotZ = toolConfig.rotZ; offsetX = toolConfig.offsetX; offsetY = toolConfig.offsetY; offsetZ = toolConfig.offsetZ; axialAngleMin = toolConfig.axialAngleMin; axialAngleMax = toolConfig.axialAngleMax; break; } } ui->toolExtrinsicWidget->setData(camIdx, toolEulerOrder, rotX, rotY, rotZ, offsetX, offsetY, offsetZ, axialAngleMin, axialAngleMax); } }