GrabBag/App/DiscHolePose/DiscHolePoseApp/dialogdetectionconfig.cpp

469 lines
17 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 "dialogdetectionconfig.h"
#include "ui_dialogdetectionconfig.h"
#include <QComboBox>
#include <QDoubleValidator>
#include <QIntValidator>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QVector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include "HandEyeCalibWidget.h"
#include "ToolExtrinsicWidget.h"
#include "PathManager.h"
#include "DiscHolePosePresenter.h"
#include "StyledMessageBox.h"
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
DialogDetectionConfig::DialogDetectionConfig(QWidget *parent)
: QDialog(parent)
, ui(new Ui::DialogDetectionConfig)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("砂轮盘孔定位 - 检测对象配置"));
initNumericEditors();
initHandEyeCalibTab();
initToolParamsTab();
initNetworkConfigControls();
// 检测对象 ComboBox 选项DiscHolePose: 砂轮盘孔 / 砂轮盘架子)
ui->comboType->blockSignals(true);
ui->comboType->clear();
ui->comboType->addItem(detectionTypeLabel(DETECTION_TYPE_DISC_HOLE), static_cast<int>(DETECTION_TYPE_DISC_HOLE));
ui->comboType->addItem(detectionTypeLabel(DETECTION_TYPE_DISC_RACK), static_cast<int>(DETECTION_TYPE_DISC_RACK));
ui->comboType->blockSignals(false);
connect(ui->comboCamera, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &DialogDetectionConfig::onSelectionChanged);
connect(ui->comboType, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &DialogDetectionConfig::onSelectionChanged);
connect(ui->btnOk, &QPushButton::clicked, this, &DialogDetectionConfig::onOkClicked);
connect(ui->btnCancel, &QPushButton::clicked, this, &DialogDetectionConfig::onCancelClicked);
connect(ui->btnApply, &QPushButton::clicked, this, &DialogDetectionConfig::onApplyClicked);
connect(ui->btnReset, &QPushButton::clicked, this, &DialogDetectionConfig::onResetCurrentClicked);
}
DialogDetectionConfig::~DialogDetectionConfig()
{
delete ui;
}
void DialogDetectionConfig::SetPresenter(DiscHolePosePresenter* presenter)
{
m_presenter = presenter;
loadFromConfig();
loadNetworkConfig();
}
void DialogDetectionConfig::initNumericEditors()
{
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);
};
// DiscHolePose 仅使用角点检测参数6 个)
setupDouble(ui->spinCornerTh, 0.0, 180.0, 6);
setupDouble(ui->spinScale, 0.0, 200.0, 6);
setupDouble(ui->spinMinEndingGap, 0.0, 100.0, 6);
setupDouble(ui->spinMinEndingGapZ, 0.0, 100.0, 6);
setupDouble(ui->spinJumpCornerTh1, 0.0, 100.0, 6);
setupDouble(ui->spinJumpCornerTh2, 0.0, 180.0, 6);
}
void DialogDetectionConfig::initHandEyeCalibTab()
{
if (!ui->verticalLayout_handEyeCalibHost || m_handEyeCalibWidget) {
return;
}
m_handEyeCalibWidget = new HandEyeCalibWidget(this);
m_handEyeCalibWidget->setMatrixEditable(true);
m_handEyeCalibWidget->setExtrinsicControlsVisible(false);
m_handEyeCalibWidget->setTargetOffsetVisible(false);
m_handEyeCalibWidget->setDefaultFilePath(PathManager::GetInstance().GetAppConfigDirectory());
ui->verticalLayout_handEyeCalibHost->addWidget(m_handEyeCalibWidget);
}
void DialogDetectionConfig::initToolParamsTab()
{
if (!ui->verticalLayout_toolParamsHost || m_toolParamsWidget) {
return;
}
m_toolParamsWidget = new ToolExtrinsicWidget(this);
m_toolParamsWidget->setTargetOffsetVisible(true);
ui->verticalLayout_toolParamsHost->addWidget(m_toolParamsWidget);
}
void DialogDetectionConfig::initNetworkConfigControls()
{
ui->comboPoseOutputOrder->clear();
ui->comboPoseOutputOrder->addItem(QStringLiteral("RX-RY-RZ"), 0);
ui->comboPoseOutputOrder->addItem(QStringLiteral("RX-RZ-RY"), 1);
ui->comboPoseOutputOrder->addItem(QStringLiteral("RY-RX-RZ"), 2);
ui->comboPoseOutputOrder->addItem(QStringLiteral("RY-RZ-RX"), 3);
ui->comboPoseOutputOrder->addItem(QStringLiteral("RZ-RX-RY"), 4);
ui->comboPoseOutputOrder->addItem(QStringLiteral("RZ-RY-RX"), 5);
ui->comboByteOrder->clear();
ui->comboByteOrder->addItem(QStringLiteral("大端序 (ABCD)"), 0);
ui->comboByteOrder->addItem(QStringLiteral("小端序 (DCBA)"), 1);
ui->editTcpPort->setValidator(new QIntValidator(1, 65535, ui->editTcpPort));
}
void DialogDetectionConfig::loadNetworkConfig()
{
if (!m_presenter || !m_presenter->GetConfigManager()) {
return;
}
const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult();
int idx = ui->comboPoseOutputOrder->findData(configResult.poseOutputOrder);
if (idx >= 0) {
ui->comboPoseOutputOrder->setCurrentIndex(idx);
}
idx = ui->comboByteOrder->findData(configResult.byteOrder);
if (idx >= 0) {
ui->comboByteOrder->setCurrentIndex(idx);
}
ui->editTcpPort->setText(QString::number(configResult.tcpPort));
}
void DialogDetectionConfig::rebuildCameraSelector()
{
if (!ui->comboCamera) return;
ui->comboCamera->blockSignals(true);
ui->comboCamera->clear();
for (int camIdx = 1; camIdx <= m_cameraCount; ++camIdx) {
QString name = QStringLiteral("相机 %1").arg(camIdx);
if (m_presenter && m_presenter->GetConfigManager()) {
const auto& cameras = m_presenter->GetConfigManager()->GetConfigResult().cameraList;
const int arrayIndex = camIdx - 1;
if (arrayIndex >= 0 && arrayIndex < static_cast<int>(cameras.size())) {
name = QString::fromStdString(cameras[arrayIndex].name);
}
}
ui->comboCamera->addItem(name, camIdx);
}
ui->comboCamera->blockSignals(false);
}
void DialogDetectionConfig::loadFromConfig()
{
if (!m_presenter || !m_presenter->GetConfigManager()) {
return;
}
const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult();
m_cameraCount = std::max(1, static_cast<int>(configResult.cameraList.size()));
// 把当前 ConfigResult 中所有 (相机, 对象) set 抓到 m_workingSets
// 缺失的槽位用默认值兜底补齐。
m_workingSets.clear();
const DetectionType allTypes[2] = { DETECTION_TYPE_DISC_HOLE, DETECTION_TYPE_DISC_RACK };
for (int camIdx = 1; camIdx <= m_cameraCount; ++camIdx) {
for (DetectionType t : allTypes) {
WorkingSet ws;
ws.cameraIndex = camIdx;
ws.detectionType = t;
ws.handEyeMatrix.cameraIndex = camIdx;
if (const auto* item = configResult.FindDetectionConfig(camIdx, t)) {
ws.handEyeMatrix = item->handEyeMatrix;
ws.handEyeMatrix.cameraIndex = camIdx;
ws.algorithmParams = item->algorithmParams;
}
m_workingSets.push_back(ws);
}
}
// 把 HandEyeCalibWidget 配置成「单条目」模式:只展示当前选中那一份。
QVector<HandEyeCalibCameraInfo> singleCam;
HandEyeCalibCameraInfo info;
info.cameraIndex = 1;
info.displayName = QStringLiteral("当前");
singleCam.append(info);
m_handEyeCalibWidget->setCameraList(singleCam);
rebuildCameraSelector();
// 默认选中 (相机 1, 砂轮盘孔)
ui->comboCamera->setCurrentIndex(0);
ui->comboType->setCurrentIndex(0);
m_currentLoadedIndex = -1;
loadWorkingSetToEditors(currentWorkingIndex());
}
int DialogDetectionConfig::findWorkingIndex(int cameraIndex, DetectionType type) const
{
for (size_t i = 0; i < m_workingSets.size(); ++i) {
if (m_workingSets[i].cameraIndex == cameraIndex
&& m_workingSets[i].detectionType == type) {
return static_cast<int>(i);
}
}
return -1;
}
int DialogDetectionConfig::currentWorkingIndex() const
{
if (!ui || !ui->comboCamera || !ui->comboType) return -1;
const int camIdx = ui->comboCamera->currentData().toInt();
const auto type = static_cast<DetectionType>(ui->comboType->currentData().toInt());
return findWorkingIndex(camIdx, type);
}
void DialogDetectionConfig::loadWorkingSetToEditors(int workingIndex)
{
if (workingIndex < 0 || workingIndex >= static_cast<int>(m_workingSets.size())) {
return;
}
const auto& ws = m_workingSets[workingIndex];
// 手眼控件单条目模式:始终用虚拟 cameraIndex=1
m_handEyeCalibWidget->setCalibData(1, ws.handEyeMatrix.matrix, !IsIdentityMatrix(ws.handEyeMatrix.matrix));
// 工具参数
if (m_toolParamsWidget) {
m_toolParamsWidget->setData(
ws.handEyeMatrix.eulerOrder,
ws.handEyeMatrix.rotX, ws.handEyeMatrix.rotY, ws.handEyeMatrix.rotZ,
ws.handEyeMatrix.offsetX, ws.handEyeMatrix.offsetY, ws.handEyeMatrix.offsetZ);
}
// DiscHolePose 仅加载角点检测参数
const auto& corner = ws.algorithmParams.cornerParam;
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);
m_currentLoadedIndex = workingIndex;
}
bool DialogDetectionConfig::commitCurrentEditorsToWorkingSet(QString* errMsg)
{
if (m_currentLoadedIndex < 0 || m_currentLoadedIndex >= static_cast<int>(m_workingSets.size())) {
return true;
}
auto& ws = m_workingSets[m_currentLoadedIndex];
// 手眼控件(矩阵,虚拟 cameraIndex=1
if (m_handEyeCalibWidget) {
double matrix[16];
bool isCalibrated = false;
if (m_handEyeCalibWidget->getCalibData(1, matrix, isCalibrated)) {
std::memcpy(ws.handEyeMatrix.matrix, matrix, sizeof(matrix));
}
}
// 工具参数
if (m_toolParamsWidget) {
m_toolParamsWidget->getData(
ws.handEyeMatrix.eulerOrder,
ws.handEyeMatrix.rotX, ws.handEyeMatrix.rotY, ws.handEyeMatrix.rotZ,
ws.handEyeMatrix.offsetX, ws.handEyeMatrix.offsetY, ws.handEyeMatrix.offsetZ);
}
ws.handEyeMatrix.cameraIndex = ws.cameraIndex;
auto fail = [&](const QString& m) {
if (errMsg) *errMsg = m;
return false;
};
auto& corner = ws.algorithmParams.cornerParam;
// DiscHolePose 仅读取角点检测参数
if (!tryGetDouble(ui->spinCornerTh, QStringLiteral("拐角阈值"), corner.cornerTh)) return fail(QStringLiteral("拐角阈值"));
if (!tryGetDouble(ui->spinScale, QStringLiteral("窗口比例因子"), corner.scale)) return fail(QStringLiteral("窗口比例因子"));
if (!tryGetDouble(ui->spinMinEndingGap, QStringLiteral("Y方向最小结束间隙"), corner.minEndingGap)) return fail(QStringLiteral("Y方向最小结束间隙"));
if (!tryGetDouble(ui->spinMinEndingGapZ, QStringLiteral("Z方向最小结束间隙"), corner.minEndingGap_z)) return fail(QStringLiteral("Z方向最小结束间隙"));
if (!tryGetDouble(ui->spinJumpCornerTh1, QStringLiteral("跳变拐角阈值1"), corner.jumpCornerTh_1)) return fail(QStringLiteral("跳变拐角阈值1"));
if (!tryGetDouble(ui->spinJumpCornerTh2, QStringLiteral("跳变拐角阈值2"), corner.jumpCornerTh_2)) return fail(QStringLiteral("跳变拐角阈值2"));
return true;
}
bool DialogDetectionConfig::saveAllWorkingSetsToConfig()
{
if (!m_presenter || !m_presenter->GetConfigManager()) {
StyledMessageBox::warning(this, QStringLiteral("失败"),
QStringLiteral("ConfigManager 未初始化。"));
return false;
}
QString err;
if (!commitCurrentEditorsToWorkingSet(&err)) {
// tryGetDouble/tryGetInt 内部已经弹了具体错误框;这里不再重复
return false;
}
SystemConfig systemConfig = m_presenter->GetConfigManager()->GetConfig();
// 用 m_workingSets 全量覆盖 detectionConfigList
systemConfig.configResult.detectionConfigList.clear();
for (const auto& ws : m_workingSets) {
VrDetectionConfigSet item;
item.cameraIndex = ws.cameraIndex;
item.detectionType = ws.detectionType;
item.handEyeMatrix = ws.handEyeMatrix;
item.handEyeMatrix.cameraIndex = ws.cameraIndex;
item.algorithmParams = ws.algorithmParams;
systemConfig.configResult.detectionConfigList.push_back(item);
}
bool ok = false;
const int tcpPort = ui->editTcpPort->text().trimmed().toInt(&ok);
if (!ok || tcpPort <= 0 || tcpPort > 65535) {
StyledMessageBox::warning(this, QStringLiteral("错误"),
QStringLiteral("TCP端口必须在 1 到 65535 之间。"));
ui->editTcpPort->setFocus();
ui->editTcpPort->selectAll();
return false;
}
systemConfig.configResult.tcpPort = static_cast<uint16_t>(tcpPort);
systemConfig.configResult.poseOutputOrder = ui->comboPoseOutputOrder->currentData().toInt();
systemConfig.configResult.byteOrder = ui->comboByteOrder->currentData().toInt();
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 DialogDetectionConfig::onSelectionChanged()
{
// 切换前先提交当前编辑器内容到旧 set失败则回滚 combo 选择。
QString err;
if (!commitCurrentEditorsToWorkingSet(&err)) {
// 用户编辑了非法值,重新加载旧 set避免进入未定义状态
loadWorkingSetToEditors(m_currentLoadedIndex);
return;
}
const int newIndex = currentWorkingIndex();
loadWorkingSetToEditors(newIndex);
}
void DialogDetectionConfig::onOkClicked()
{
if (saveAllWorkingSetsToConfig()) {
accept();
}
}
void DialogDetectionConfig::onCancelClicked()
{
reject();
}
void DialogDetectionConfig::onApplyClicked()
{
if (saveAllWorkingSetsToConfig()) {
StyledMessageBox::information(this, QStringLiteral("提示"),
QStringLiteral("4 套配置已应用并写入配置文件。"));
}
}
void DialogDetectionConfig::onResetCurrentClicked()
{
if (m_currentLoadedIndex < 0 || m_currentLoadedIndex >= static_cast<int>(m_workingSets.size())) {
return;
}
auto ret = StyledMessageBox::question(this, QStringLiteral("确认重置"),
QStringLiteral("确定要把当前 (相机, 对象) 配置重置为默认值吗?\n(仅影响内存中正在编辑的副本,确定/应用前不会落盘)"),
QMessageBox::Yes | QMessageBox::No);
if (ret != QMessageBox::Yes) return;
WorkingSet defaults;
defaults.cameraIndex = m_workingSets[m_currentLoadedIndex].cameraIndex;
defaults.detectionType = m_workingSets[m_currentLoadedIndex].detectionType;
defaults.handEyeMatrix.cameraIndex = defaults.cameraIndex;
m_workingSets[m_currentLoadedIndex] = defaults;
loadWorkingSetToEditors(m_currentLoadedIndex);
}
// —— 工具函数 ——
void DialogDetectionConfig::setDoubleEditorText(QLineEdit* edit, double value)
{
if (edit) edit->setText(QString::number(value, 'g', 12));
}
void DialogDetectionConfig::setIntEditorText(QLineEdit* edit, int value)
{
if (edit) edit->setText(QString::number(value));
}
bool DialogDetectionConfig::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 DialogDetectionConfig::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;
}
QString DialogDetectionConfig::detectionTypeLabel(DetectionType type)
{
return (type == DETECTION_TYPE_DISC_RACK)
? QStringLiteral("砂轮盘架子(discRack)")
: QStringLiteral("砂轮盘孔(discHole)");
}