444 lines
18 KiB
C++
Raw Normal View History

2026-03-28 10:49:55 +08:00
#include "dialogalgoarg.h"
#include "ui_dialogalgoarg.h"
#include "HandEyeCalibWidget.h"
#include "NetworkConfigWidget.h"
#include "PathManager.h"
#include "ScrewPositionPresenter.h"
#include "StyledMessageBox.h"
#include <QDoubleValidator>
#include <QIntValidator>
#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;
}
}
2026-03-28 10:49:55 +08:00
DialogAlgoArg::DialogAlgoArg(QWidget *parent)
: QDialog(parent)
, ui(new Ui::DialogAlgoArg)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("算法参数设置"));
initNumericEditors();
initHandEyeCalibTab();
initNetworkConfigTab();
2026-03-28 10:49:55 +08:00
}
DialogAlgoArg::~DialogAlgoArg()
{
delete ui;
}
void DialogAlgoArg::SetPresenter(ScrewPositionPresenter* presenter)
{
m_presenter = presenter;
loadParams();
loadHandEyeCalibConfig();
loadNetworkConfig();
2026-03-28 10:49:55 +08:00
}
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);
};
auto setupIntEditor = [](QLineEdit* edit, int min, int max) {
edit->setValidator(new QIntValidator(min, max, edit));
};
setupDoubleEditor(ui->spinRodDiameter, 1.0, 100.0, 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);
setupDoubleEditor(ui->spinContinuityTh, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinOutlierTh, 0.0, 50.0, 6);
setupIntEditor(ui->spinMaxLineSkipNum, -1, 100);
setupDoubleEditor(ui->spinYDeviationMax, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinMaxSkipDistance, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinZDeviationMax, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinMinLTypeTreeLen, 0.0, 1000.0, 6);
setupDoubleEditor(ui->spinMinVTypeTreeLen, 0.0, 1000.0, 6);
}
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->setApproachOffsetVisible(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(false);
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;
}
2026-03-28 10:49:55 +08:00
void DialogAlgoArg::loadParams()
{
if (!m_presenter) {
return;
}
const auto params = m_presenter->GetAlgoParams();
2026-03-28 10:49:55 +08:00
setDoubleEditorText(ui->spinRodDiameter, params.screwParam.rodDiameter);
2026-03-28 10:49:55 +08:00
ui->chkHorizonScan->setChecked(params.screwParam.isHorizonScan);
setDoubleEditorText(ui->spinCornerTh, params.cornerParam.cornerTh);
setDoubleEditorText(ui->spinScale, params.cornerParam.scale);
setDoubleEditorText(ui->spinMinEndingGap, params.cornerParam.minEndingGap);
setDoubleEditorText(ui->spinMinEndingGapZ, params.cornerParam.minEndingGap_z);
setDoubleEditorText(ui->spinJumpCornerTh1, params.cornerParam.jumpCornerTh_1);
setDoubleEditorText(ui->spinJumpCornerTh2, params.cornerParam.jumpCornerTh_2);
setDoubleEditorText(ui->spinContinuityTh, params.filterParam.continuityTh);
setDoubleEditorText(ui->spinOutlierTh, params.filterParam.outlierTh);
setIntEditorText(ui->spinMaxLineSkipNum, params.growParam.maxLineSkipNum);
setDoubleEditorText(ui->spinYDeviationMax, params.growParam.yDeviation_max);
setDoubleEditorText(ui->spinMaxSkipDistance, params.growParam.maxSkipDistance);
setDoubleEditorText(ui->spinZDeviationMax, params.growParam.zDeviation_max);
setDoubleEditorText(ui->spinMinLTypeTreeLen, params.growParam.minLTypeTreeLen);
setDoubleEditorText(ui->spinMinVTypeTreeLen, params.growParam.minVTypeTreeLen);
2026-03-28 10:49:55 +08:00
}
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) {
// 以螺杆 set 作为「该相机的公共默认」展示
const VrDetectionConfigSet* savedSet =
configResult.FindDetectionConfig(info.cameraIndex, DETECTION_TYPE_SCREW);
if (savedSet) {
const auto& savedMatrix = savedSet->handEyeMatrix;
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;
2026-04-17 10:18:03 +08:00
netConfig.poseOutputOrder = configResult.poseOutputOrder;
netConfig.byteOrder = configResult.byteOrder;
m_networkConfigWidget->setConfig(netConfig);
}
bool DialogAlgoArg::saveParams()
2026-03-28 10:49:55 +08:00
{
if (!m_presenter || !m_presenter->GetConfigManager()) {
return false;
2026-03-28 10:49:55 +08:00
}
SystemConfig systemConfig = m_presenter->GetConfigManager()->GetConfig();
// 旧对话框只编辑「公共默认」语义:算法参数 + 每相机一份矩阵,
// 都同步写到 (camera, screw) 与 (camera, toolDisk) 两个槽位上。
VrAlgorithmParams sharedParams;
sharedParams.planeCalibParam = systemConfig.configResult.algorithmParams.planeCalibParam;
if (!tryGetDouble(ui->spinRodDiameter, QStringLiteral("螺杆直径"), sharedParams.screwParam.rodDiameter)) return false;
sharedParams.screwParam.isHorizonScan = ui->chkHorizonScan->isChecked();
if (!tryGetDouble(ui->spinCornerTh, QStringLiteral("拐角阈值"), sharedParams.cornerParam.cornerTh)) return false;
if (!tryGetDouble(ui->spinScale, QStringLiteral("窗口比例因子"), sharedParams.cornerParam.scale)) return false;
if (!tryGetDouble(ui->spinMinEndingGap, QStringLiteral("Y方向最小结束间隙"), sharedParams.cornerParam.minEndingGap)) return false;
if (!tryGetDouble(ui->spinMinEndingGapZ, QStringLiteral("Z方向最小结束间隙"), sharedParams.cornerParam.minEndingGap_z)) return false;
if (!tryGetDouble(ui->spinJumpCornerTh1, QStringLiteral("跳变拐角阈值1"), sharedParams.cornerParam.jumpCornerTh_1)) return false;
if (!tryGetDouble(ui->spinJumpCornerTh2, QStringLiteral("跳变拐角阈值2"), sharedParams.cornerParam.jumpCornerTh_2)) return false;
if (!tryGetDouble(ui->spinContinuityTh, QStringLiteral("连续性阈值"), sharedParams.filterParam.continuityTh)) return false;
if (!tryGetDouble(ui->spinOutlierTh, QStringLiteral("离群点阈值"), sharedParams.filterParam.outlierTh)) return false;
if (!tryGetInt(ui->spinMaxLineSkipNum, QStringLiteral("最大跳线数"), sharedParams.growParam.maxLineSkipNum)) return false;
if (!tryGetDouble(ui->spinYDeviationMax, QStringLiteral("Y偏差最大值"), sharedParams.growParam.yDeviation_max)) return false;
if (!tryGetDouble(ui->spinMaxSkipDistance, QStringLiteral("最大跳跃距离"), sharedParams.growParam.maxSkipDistance)) return false;
if (!tryGetDouble(ui->spinZDeviationMax, QStringLiteral("Z偏差最大值"), sharedParams.growParam.zDeviation_max)) return false;
if (!tryGetDouble(ui->spinMinLTypeTreeLen, QStringLiteral("L型树最小长度"), sharedParams.growParam.minLTypeTreeLen)) return false;
if (!tryGetDouble(ui->spinMinVTypeTreeLen, QStringLiteral("V型树最小长度"), sharedParams.growParam.minVTypeTreeLen)) return false;
// 把刚刚解析出来的「公共默认」算法参数写入全部 4 个 (camera, type) 槽位。
for (auto& item : systemConfig.configResult.detectionConfigList) {
item.algorithmParams.screwParam = sharedParams.screwParam;
item.algorithmParams.cornerParam = sharedParams.cornerParam;
item.algorithmParams.filterParam = sharedParams.filterParam;
item.algorithmParams.growParam = sharedParams.growParam;
}
if (m_handEyeCalibWidget) {
int cameraCount = std::max(1, static_cast<int>(systemConfig.configResult.cameraList.size()));
// 兜底:保证 (camera N, screw) + (camera N, toolDisk) 槽位都存在
for (int camIdx = 1; camIdx <= cameraCount; ++camIdx) {
systemConfig.configResult.EnsureDetectionConfig(camIdx, DETECTION_TYPE_SCREW);
systemConfig.configResult.EnsureDetectionConfig(camIdx, DETECTION_TYPE_TOOL_DISK);
}
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);
// 同一矩阵写入该相机的两个 setscrew + toolDisk
const DetectionType types[2] = { DETECTION_TYPE_SCREW, DETECTION_TYPE_TOOL_DISK };
for (DetectionType t : types) {
VrDetectionConfigSet* setPtr = systemConfig.configResult.FindDetectionConfig(camIdx, t);
if (!setPtr) continue;
setPtr->handEyeMatrix.cameraIndex = camIdx;
if (hasMatrix) {
std::memcpy(setPtr->handEyeMatrix.matrix, matrix, sizeof(double) * 16);
}
if (hasExtrinsic) {
setPtr->handEyeMatrix.eulerOrder = eulerOrder;
setPtr->handEyeMatrix.rotX = rotX;
setPtr->handEyeMatrix.rotY = rotY;
setPtr->handEyeMatrix.rotZ = rotZ;
setPtr->handEyeMatrix.approachOffset = approachOffset;
setPtr->handEyeMatrix.offsetX = offsetX;
setPtr->handEyeMatrix.offsetY = offsetY;
setPtr->handEyeMatrix.offsetZ = offsetZ;
}
}
}
}
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);
2026-04-17 10:18:03 +08:00
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;
}
2026-03-28 10:49:55 +08:00
m_presenter->OnConfigChanged(systemConfig.configResult);
return true;
2026-03-28 10:49:55 +08:00
}
void DialogAlgoArg::resetParams()
{
setDoubleEditorText(ui->spinRodDiameter, 10.0);
2026-03-28 10:49:55 +08:00
ui->chkHorizonScan->setChecked(true);
setDoubleEditorText(ui->spinCornerTh, 60.0);
setDoubleEditorText(ui->spinScale, 50.0);
setDoubleEditorText(ui->spinMinEndingGap, 20.0);
setDoubleEditorText(ui->spinMinEndingGapZ, 20.0);
setDoubleEditorText(ui->spinJumpCornerTh1, 10.0);
setDoubleEditorText(ui->spinJumpCornerTh2, 60.0);
setDoubleEditorText(ui->spinContinuityTh, 20.0);
setDoubleEditorText(ui->spinOutlierTh, 5.0);
setIntEditorText(ui->spinMaxLineSkipNum, 10);
setDoubleEditorText(ui->spinYDeviationMax, 10.0);
setDoubleEditorText(ui->spinMaxSkipDistance, 10.0);
setDoubleEditorText(ui->spinZDeviationMax, 10.0);
setDoubleEditorText(ui->spinMinLTypeTreeLen, 100.0);
setDoubleEditorText(ui->spinMinVTypeTreeLen, 100.0);
2026-03-28 10:49:55 +08:00
}
void DialogAlgoArg::on_btnOK_clicked()
{
if (saveParams()) {
accept();
}
2026-03-28 10:49:55 +08:00
}
void DialogAlgoArg::on_btnCancel_clicked()
{
reject();
}
void DialogAlgoArg::on_btnApply_clicked()
{
if (saveParams()) {
StyledMessageBox::information(this, QStringLiteral("提示"),
QStringLiteral("参数已应用。"));
}
2026-03-28 10:49:55 +08:00
}
void DialogAlgoArg::on_btnReset_clicked()
{
const auto ret = StyledMessageBox::question(this, QStringLiteral("确认重置"),
QStringLiteral("确定要重置为默认参数吗?"), QMessageBox::Yes | QMessageBox::No);
2026-03-28 10:49:55 +08:00
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("保存手眼标定参数失败。"));
}
}