2026-07-03 19:21:24 +08:00

566 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <QDoubleValidator>
#include <QGroupBox>
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <algorithm>
#include <cmath>
#include <cstring>
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<HandEyeCalibCameraInfo> 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<int>(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<int>(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<uint16_t>(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<int>(cameraList.size()));
// 设置相机列表
QVector<ToolExtrinsicCameraInfo> toolCameraList;
for (size_t i = 0; i < cameraList.size(); ++i) {
ToolExtrinsicCameraInfo info;
info.cameraIndex = static_cast<int>(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);
}
}