GrabBag/App/DiscHolePose/DiscHolePoseApp/dialogdetectionconfig.cpp

469 lines
17 KiB
C++
Raw Normal View History

#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)");
}