360 lines
14 KiB
C++
360 lines
14 KiB
C++
#include "dialogalgoarg.h"
|
|
#include "ui_dialogalgoarg.h"
|
|
|
|
#include "PathManager.h"
|
|
#include "StyledMessageBox.h"
|
|
#include "HandEyeCalibWidget.h"
|
|
#include "ToolExtrinsicWidget.h"
|
|
#include "WeldSeamPresenter.h"
|
|
|
|
#include <QDoubleValidator>
|
|
#include <QIntValidator>
|
|
#include <QLineEdit>
|
|
#include <QMessageBox>
|
|
|
|
#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();
|
|
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<ToolExtrinsicCameraInfo> toolCameras;
|
|
QVector<HandEyeCalibCameraInfo> 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<int>(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<uint16_t>(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<int>(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("手眼标定参数已保存。"));
|
|
}
|
|
}
|