#include "dialogalgoarg.h" #include "ui_dialogalgoarg.h" #include "workpiecesizeactions.h" #include #include #include #include #include #include #include #include #include #include #include #include "HandEyeCalibWidget.h" #include "PathManager.h" #include "StyledMessageBox.h" #include "ToolExtrinsicWidget.h" #include "NetworkConfigWidget.h" #include "PointCloudFilterWidget.h" #include "workpiecesizeactions.h" #include "VrLog.h" namespace { const int kHandEyeConfigIndex = 1; bool isIdentityMatrix(const double matrix[16]) { for (int index = 0; index < 16; ++index) { const double expected = (index / 4 == index % 4) ? 1.0 : 0.0; if (qAbs(matrix[index] - expected) > 1e-9) { return false; } } return true; } } DialogAlgoarg::DialogAlgoarg(ConfigManager* configManager, QWidget *parent) : QDialog(parent) , ui(new Ui::DialogAlgoarg) , m_pConfigManager(configManager) , m_handEyeCalibWidget(nullptr) , m_toolExtrinsicWidget(nullptr) { try { ui->setupUi(this); m_pointCloudFilterWidget = ui->pointCloudFilterWidget; if (ui->lineEdit_spacerLength) { ui->lineEdit_spacerLength->setValidator( new QDoubleValidator(0.0, 1e6, 6, ui->lineEdit_spacerLength)); } if (ui->lineEdit_spacerWidth) { ui->lineEdit_spacerWidth->setValidator( new QDoubleValidator(0.0, 1e6, 6, ui->lineEdit_spacerWidth)); } if (ui->lineEdit_layerRatio) ui->lineEdit_layerRatio->setValidator(new QDoubleValidator(-1.0, 10.0, 6, ui->lineEdit_layerRatio)); if (ui->lineEdit_distToSideRatio) ui->lineEdit_distToSideRatio->setValidator(new QDoubleValidator(0.0, 10.0, 6, ui->lineEdit_distToSideRatio)); if (ui->lineEdit_groundRemoveOffset) ui->lineEdit_groundRemoveOffset->setValidator(new QDoubleValidator(-1e6, 1e6, 6, ui->lineEdit_groundRemoveOffset)); InitHandEyeCalibTab(); InitNetworkConfigTab(); InitToolExtrinsicTab(); initWorkpieceSizesTab(); initModelParamsTab(); // 隐藏标题栏 // setWindowFlags(Qt::FramelessWindowHint); // 获取配置文件路径 m_configFilePath = PathManager::GetInstance().GetConfigFilePath(); // 检查配置文件路径是否有效 if (m_configFilePath.isEmpty()) { StyledMessageBox::critical(this, "错误", "无法获取配置文件路径!"); return; } // 从配置文件加载数据到界面 LoadConfigToUI(); } catch (const std::exception& e) { StyledMessageBox::critical(this, "初始化错误", QString("对话框初始化失败: %1").arg(e.what())); } catch (...) { StyledMessageBox::critical(this, "初始化错误", "对话框初始化失败!(未知错误)"); } } DialogAlgoarg::~DialogAlgoarg() { delete ui; } void DialogAlgoarg::LoadConfigToUI() { if (!m_pConfigManager) { StyledMessageBox::critical(this, "错误", "配置对象未初始化!"); return; } try { // 从ConfigManager获取配置数据 ConfigResult configData = m_pConfigManager->GetConfigResult(); loadPointCloudFilterConfig(configData); // 检查配置文件路径是否有效 if (m_configFilePath.isEmpty()) { LOG_WARNING("Config file path is empty\n"); } const VrToolParam& toolParam = configData.algorithmParams.toolParam; LoadModelDetectionParamsToUI(configData.algorithmParams); LoadBinDetectionParamsToUI(configData.algorithmParams.binParam); LoadRotorCoreParamsToUI(configData.algorithmParams.rotorCoreParam); if (m_toolExtrinsicWidget) { m_toolExtrinsicWidget->setData( toolParam.eulerOrder, toolParam.rotX, toolParam.rotY, toolParam.rotZ, toolParam.offsetX, toolParam.offsetY, toolParam.offsetZ); } // 加载网络配置(JSON TCP 端口 + Modbus 寄存器地址) LoadPlcRobotServerConfigToUI(configData.plcRobotServerConfig); LoadHandEyeCalibConfig(); } catch (const std::exception& e) { LOG_ERROR("Exception in LoadConfigToUI: %s\n", e.what()); StyledMessageBox::warning(this, "警告", QString("加载配置时发生异常: %1\n将使用默认参数显示").arg(e.what())); ConfigResult configData; loadPointCloudFilterConfig(configData); LoadModelDetectionParamsToUI(configData.algorithmParams); LoadBinDetectionParamsToUI(configData.algorithmParams.binParam); LoadRotorCoreParamsToUI(configData.algorithmParams.rotorCoreParam); const VrToolParam& toolParam = configData.algorithmParams.toolParam; if (m_toolExtrinsicWidget) { m_toolExtrinsicWidget->setData( toolParam.eulerOrder, toolParam.rotX, toolParam.rotY, toolParam.rotZ, toolParam.offsetX, toolParam.offsetY, toolParam.offsetZ); } } catch (...) { LOG_ERROR("Unknown exception in LoadConfigToUI\n"); StyledMessageBox::warning(this, "警告", "加载配置文件失败(未知错误),将使用默认参数显示"); ConfigResult configData; loadPointCloudFilterConfig(configData); LoadModelDetectionParamsToUI(configData.algorithmParams); LoadBinDetectionParamsToUI(configData.algorithmParams.binParam); LoadRotorCoreParamsToUI(configData.algorithmParams.rotorCoreParam); const VrToolParam& toolParam = configData.algorithmParams.toolParam; if (m_toolExtrinsicWidget) { m_toolExtrinsicWidget->setData( toolParam.eulerOrder, toolParam.rotX, toolParam.rotY, toolParam.rotZ, toolParam.offsetX, toolParam.offsetY, toolParam.offsetZ); } } } void DialogAlgoarg::InitToolExtrinsicTab() { if (!ui || m_toolExtrinsicWidget) { return; } m_toolExtrinsicWidget = ui->toolExtrinsicWidget; m_toolExtrinsicWidget->setCameraSelectorVisible(false); m_toolExtrinsicWidget->setTargetOffsetVisible(true); m_toolExtrinsicWidget->setAxialAngleRangeVisible(false); } void DialogAlgoarg::InitHandEyeCalibTab() { if (!ui || m_handEyeCalibWidget) { return; } m_handEyeCalibWidget = ui->handEyeCalibWidget; m_handEyeCalibWidget->setMatrixEditable(true); m_handEyeCalibWidget->setDefaultCalibType(0); m_handEyeCalibWidget->setDefaultFilePath( PathManager::GetInstance().GetAppConfigDirectory()); QVector cameraList; HandEyeCalibCameraInfo cameraInfo; cameraInfo.cameraIndex = kHandEyeConfigIndex; cameraInfo.displayName = QStringLiteral("手眼标定"); cameraList.append(cameraInfo); m_handEyeCalibWidget->setCameraList(cameraList); m_handEyeCalibWidget->setCalibType(kHandEyeConfigIndex, 0); connect(m_handEyeCalibWidget, &HandEyeCalibWidget::calibMatrixLoaded, this, &DialogAlgoarg::onCalibMatrixLoaded); connect(m_handEyeCalibWidget, &HandEyeCalibWidget::saveCalibRequested, this, &DialogAlgoarg::onSaveCalibRequested); } void DialogAlgoarg::InitNetworkConfigTab() { if (!ui || m_networkConfigWidget) { return; } m_networkConfigWidget = ui->networkConfigWidget; m_networkConfigWidget->setExtrinsicControlsVisible(false); m_networkConfigWidget->setPoseOutputOrderVisible(true); if (QGroupBox* group = m_networkConfigWidget->findChild("groupPlc")) { group->setTitle(QStringLiteral("TCP端口与Modbus寄存器配置")); } const char* hideNames[] = {"labelPlcIp", "editPlcIp", "labelPlcPort", "editPlcPort"}; for (const char* name : hideNames) { if (QWidget* widget = m_networkConfigWidget->findChild(name)) { widget->hide(); } } } void DialogAlgoarg::loadPointCloudFilterConfig(const ConfigResult& configResult) { if (!m_pointCloudFilterWidget) { return; } QVector cameras; const int cameraCount = std::max(1, static_cast(configResult.cameraList.size())); for (int cameraIndex = 1; cameraIndex <= cameraCount; ++cameraIndex) { PointCloudFilterCameraInfo camera; camera.cameraIndex = cameraIndex; if (cameraIndex <= static_cast(configResult.cameraList.size())) { camera.displayName = QString::fromStdString(configResult.cameraList[cameraIndex - 1].name); } if (camera.displayName.isEmpty()) { camera.displayName = QStringLiteral("相机 %1").arg(cameraIndex); } cameras.append(camera); } m_pointCloudFilterWidget->setCameraList(cameras); for (int cameraIndex = 1; cameraIndex <= cameraCount; ++cameraIndex) { PointCloudFilterData data; for (const VrPointCloudFilterParam& param : configResult.algorithmParams.pointCloudFilterParams) { if (param.cameraIndex == cameraIndex) { data.enabled = param.enabled; data.minX = param.minX; data.maxX = param.maxX; data.minY = param.minY; data.maxY = param.maxY; data.minZ = param.minZ; data.maxZ = param.maxZ; break; } } m_pointCloudFilterWidget->setData(cameraIndex, data); } } bool DialogAlgoarg::savePointCloudFilterConfig(ConfigResult& configResult) { if (!m_pointCloudFilterWidget) { return false; } std::vector filterParams; const int cameraCount = std::max(1, static_cast(configResult.cameraList.size())); for (int cameraIndex = 1; cameraIndex <= cameraCount; ++cameraIndex) { PointCloudFilterData data; if (!m_pointCloudFilterWidget->getData(cameraIndex, data)) { return false; } if (data.minX > data.maxX || data.minY > data.maxY || data.minZ > data.maxZ) { StyledMessageBox::warning(this, QStringLiteral("点云过滤"), QStringLiteral("相机 %1 的过滤范围下限不能大于上限!").arg(cameraIndex)); return false; } VrPointCloudFilterParam param; param.cameraIndex = cameraIndex; if (cameraIndex <= static_cast(configResult.cameraList.size())) { param.cameraName = configResult.cameraList[cameraIndex - 1].name; } param.enabled = data.enabled; param.minX = data.minX; param.maxX = data.maxX; param.minY = data.minY; param.maxY = data.maxY; param.minZ = data.minZ; param.maxZ = data.maxZ; filterParams.push_back(param); } configResult.algorithmParams.pointCloudFilterParams = filterParams; return true; } bool DialogAlgoarg::SaveConfigFromUI() { if (!m_pConfigManager) { return false; } try { // 获取当前配置 SystemConfig systemConfig = m_pConfigManager->GetConfig(); if (!savePointCloudFilterConfig(systemConfig.configResult)) { return false; } VrToolParam& toolParam = systemConfig.configResult.algorithmParams.toolParam; if (!SaveModelDetectionParamsFromUI(systemConfig.configResult.algorithmParams)) { StyledMessageBox::warning(this, "错误", "模型检测参数输入有误,请检查!"); return false; } if (!SaveBinDetectionParamsFromUI(systemConfig.configResult.algorithmParams.binParam)) { StyledMessageBox::warning(this, "错误", "料框检测参数输入有误,请检查!"); return false; } if (!SaveRotorCoreParamsFromUI(systemConfig.configResult.algorithmParams.rotorCoreParam)) { StyledMessageBox::warning(this, "错误", "转子钢芯算法参数输入有误,请检查!"); return false; } if (!m_toolExtrinsicWidget || !m_toolExtrinsicWidget->hasAcceptableInput()) { StyledMessageBox::warning(this, "错误", "工具参数输入有误,请检查!"); return false; } m_toolExtrinsicWidget->getData( toolParam.eulerOrder, toolParam.rotX, toolParam.rotY, toolParam.rotZ, toolParam.offsetX, toolParam.offsetY, toolParam.offsetZ); // 保存网络配置(JSON TCP 端口 + Modbus 寄存器地址) if (!SavePlcRobotServerConfigFromUI(systemConfig.configResult.plcRobotServerConfig)) { StyledMessageBox::warning(this, "错误", "网络配置输入有误,请检查!"); return false; } // 保存手眼标定矩阵到配置 if (!SaveCalibMatrixToConfig(systemConfig.configResult.handEyeCalibMatrix)) { StyledMessageBox::warning(this, "错误", "手眼标定矩阵输入有误,请检查!"); return false; } // 更新并保存配置到文件 if (!m_pConfigManager->UpdateFullConfig(systemConfig)) { return false; } return m_pConfigManager->SaveConfigToFile(m_configFilePath.toStdString()); } catch (const std::exception&) { return false; } } void DialogAlgoarg::LoadModelDetectionParamsToUI(const VrAlgorithmParams& algorithmParams) { if (!ui || !ui->tableWidget_modelParams) { return; } ui->tableWidget_modelParams->setRowCount(0); ui->doubleSpinBox_targetGray->setValue(algorithmParams.modelGraySelection.targetGray); ui->doubleSpinBox_maxGrayDeviation->setValue(algorithmParams.modelGraySelection.maxGrayDeviation); const VrModelDetectionRegion& region = algorithmParams.modelDetectionRegion; ui->groupBox_modelDetectionRegion->setChecked(region.enabled); ui->spinBox_modelRegionLeft->setValue(region.left); ui->spinBox_modelRegionTop->setValue(region.top); ui->spinBox_modelRegionRight->setValue(region.right); ui->spinBox_modelRegionBottom->setValue(region.bottom); const std::vector modelParams = algorithmParams.modelParams.empty() ? std::vector{VrWorkpieceModelParam()} : algorithmParams.modelParams; for (const auto& modelParam : modelParams) { addModelParamRow(modelParam); } refreshModelParamRows(); } void DialogAlgoarg::LoadBinDetectionParamsToUI(const VrBinParam& binParam) { Q_UNUSED(binParam); } bool DialogAlgoarg::SaveBinDetectionParamsFromUI(VrBinParam& binParam) { Q_UNUSED(binParam); return true; } void DialogAlgoarg::LoadRotorCoreParamsToUI(const VrRotorCoreParam& rotorParam) { loadWorkpieceSizes(rotorParam); if (!ui || !ui->lineEdit_spacerLength || !ui->lineEdit_spacerWidth || !ui->lineEdit_layerRatio || !ui->lineEdit_distToSideRatio || !ui->lineEdit_groundRemoveOffset) { return; } ui->lineEdit_spacerLength->setText(QString::number(rotorParam.spacerLength, 'f', 6)); ui->lineEdit_spacerWidth->setText(QString::number(rotorParam.spacerWidth, 'f', 6)); ui->lineEdit_layerRatio->setText(QString::number(rotorParam.layerRatio, 'f', 6)); ui->lineEdit_distToSideRatio->setText(QString::number(rotorParam.distToSideRatio, 'f', 6)); ui->lineEdit_groundRemoveOffset->setText(QString::number(rotorParam.groundRemoveOffset, 'f', 6)); ui->comboBox_toolNormalMode->setCurrentIndex(rotorParam.useGroundCalibNormal == 1 ? 1 : 0); } bool DialogAlgoarg::SaveRotorCoreParamsFromUI(VrRotorCoreParam& rotorParam) { if (!saveWorkpieceSizes(rotorParam)) { return false; } if (!ui || !ui->lineEdit_spacerLength || !ui->lineEdit_spacerWidth || !ui->lineEdit_layerRatio || !ui->lineEdit_distToSideRatio || !ui->lineEdit_groundRemoveOffset) { return false; } bool ok = false; rotorParam.spacerLength = ui->lineEdit_spacerLength->text().toDouble(&ok); if (!ok || rotorParam.spacerLength <= 0.0) { return false; } rotorParam.spacerWidth = ui->lineEdit_spacerWidth->text().toDouble(&ok); if (!ok || rotorParam.spacerWidth <= 0.0) { return false; } rotorParam.layerRatio = ui->lineEdit_layerRatio->text().toDouble(&ok); // -1 表示现场未配置分层系数;其余负数没有业务含义。 if (!ok || (rotorParam.layerRatio != -1.0 && rotorParam.layerRatio <= 0.0)) return false; rotorParam.distToSideRatio = ui->lineEdit_distToSideRatio->text().toDouble(&ok); if (!ok || rotorParam.distToSideRatio <= 0.0) return false; rotorParam.groundRemoveOffset = ui->lineEdit_groundRemoveOffset->text().toDouble(&ok); if (!ok) return false; // .ui 选项顺序与算法约定一致:0=工件自身法向,1=调平后的地面法向。 const int normalMode = ui->comboBox_toolNormalMode->currentIndex(); if (normalMode != 0 && normalMode != 1) { return false; } rotorParam.useGroundCalibNormal = normalMode; return true; } void DialogAlgoarg::initWorkpieceSizesTab() { // 页面和编辑触发方式定义在 .ui,此处只连接数据操作。 connect(ui->btn_workpieceAdd, &QPushButton::clicked, this, [this]() { if (ui->tableWidget_workpieceSizes->rowCount() >= 65535) { return; } const int defaultRow = ui->comboBox_defaultWorkpiece->currentIndex(); addWorkpieceSizeRow(VrRotorWorkpieceSize()); refreshWorkpieceRows(defaultRow); ui->tableWidget_workpieceSizes->selectRow(ui->tableWidget_workpieceSizes->rowCount() - 1); }); } void DialogAlgoarg::removeWorkpieceRow(int row) { const int count = ui->tableWidget_workpieceSizes->rowCount(); if (row < 0 || row >= count || count <= 1) { return; } int defaultRow = ui->comboBox_defaultWorkpiece->currentIndex(); if (defaultRow == row) { defaultRow = 0; } else if (defaultRow > row) { --defaultRow; } ui->tableWidget_workpieceSizes->removeRow(row); refreshWorkpieceRows(defaultRow); ui->tableWidget_workpieceSizes->selectRow(std::min(row, ui->tableWidget_workpieceSizes->rowCount() - 1)); } void DialogAlgoarg::addWorkpieceSizeRow(const VrRotorWorkpieceSize& size) { const int row = ui->tableWidget_workpieceSizes->rowCount(); ui->tableWidget_workpieceSizes->insertRow(row); auto* index = new QTableWidgetItem(QString::number(row + 1)); index->setFlags(index->flags() & ~Qt::ItemIsEditable); ui->tableWidget_workpieceSizes->setItem(row, 0, index); for (int column = 1; column <= 2; ++column) { const double dimension = column == 1 ? size.radius : size.height; // 使用文本项,双击时由 Qt 提供普通文本编辑器,不创建数值步进控件。 ui->tableWidget_workpieceSizes->setItem(row, column, new QTableWidgetItem(QString::number(dimension, 'g', 15))); } auto* actions = new WorkpieceSizeActions(ui->tableWidget_workpieceSizes); ui->tableWidget_workpieceSizes->setCellWidget(row, 3, actions); // 持久索引跟随删除后的行号变化,按钮始终操作所在行,不依赖选中行。 const QPersistentModelIndex rowIndex(ui->tableWidget_workpieceSizes->model()->index(row, 0)); connect(actions, &WorkpieceSizeActions::moveRequested, this, [this, rowIndex](int offset) { if (rowIndex.isValid()) { moveWorkpieceRow(rowIndex.row(), offset); } }); connect(actions, &WorkpieceSizeActions::removeRequested, this, [this, rowIndex]() { if (rowIndex.isValid()) { removeWorkpieceRow(rowIndex.row()); } }); } void DialogAlgoarg::refreshWorkpieceRows(int defaultRow) { ui->comboBox_defaultWorkpiece->clear(); for (int row = 0; row < ui->tableWidget_workpieceSizes->rowCount(); ++row) { ui->tableWidget_workpieceSizes->item(row, 0)->setText(QString::number(row + 1)); ui->comboBox_defaultWorkpiece->addItem(QString::number(row + 1), row + 1); } ui->comboBox_defaultWorkpiece->setCurrentIndex(defaultRow >= 0 ? defaultRow : 0); updateWorkpieceActions(); } void DialogAlgoarg::updateWorkpieceActions() { const int count = ui->tableWidget_workpieceSizes->rowCount(); for (int row = 0; row < count; ++row) { auto* actions = qobject_cast(ui->tableWidget_workpieceSizes->cellWidget(row, 3)); if (actions) { actions->setRowPosition(row, count); } } ui->btn_workpieceAdd->setEnabled(count < 65535); } void DialogAlgoarg::moveWorkpieceRow(int row, int offset) { const int targetRow = row + offset; if (row < 0 || targetRow < 0 || targetRow >= ui->tableWidget_workpieceSizes->rowCount()) { return; } int defaultRow = ui->comboBox_defaultWorkpiece->currentIndex(); for (int column = 1; column <= 2; ++column) { auto* source = ui->tableWidget_workpieceSizes->takeItem(row, column); auto* target = ui->tableWidget_workpieceSizes->takeItem(targetRow, column); ui->tableWidget_workpieceSizes->setItem(row, column, target); ui->tableWidget_workpieceSizes->setItem(targetRow, column, source); } // 默认工件跟随尺寸项移动,序号始终对应新的行顺序。 if (defaultRow == row) { defaultRow = targetRow; } else if (defaultRow == targetRow) { defaultRow = row; } refreshWorkpieceRows(defaultRow); ui->tableWidget_workpieceSizes->selectRow(targetRow); } void DialogAlgoarg::loadWorkpieceSizes(const VrRotorCoreParam& rotorParam) { ui->tableWidget_workpieceSizes->setRowCount(0); int defaultRow = -1; for (const auto& size : rotorParam.workpieceSizes) { if (size.index == rotorParam.defaultWorkpieceIndex) { defaultRow = ui->tableWidget_workpieceSizes->rowCount(); } addWorkpieceSizeRow(size); } refreshWorkpieceRows(defaultRow); if (!rotorParam.isWorkpieceListValid()) { StyledMessageBox::warning(this, "工件尺寸", "尺寸列表存在无效参数,请检查尺寸和默认工件后保存。"); } } bool DialogAlgoarg::saveWorkpieceSizes(VrRotorCoreParam& rotorParam) { VrRotorCoreParam updated = rotorParam; updated.workpieceSizes.clear(); updated.defaultWorkpieceIndex = ui->comboBox_defaultWorkpiece->currentData().toInt(); for (int row = 0; row < ui->tableWidget_workpieceSizes->rowCount(); ++row) { VrRotorWorkpieceSize size; size.index = row + 1; bool radiusOk = false; bool heightOk = false; size.radius = ui->tableWidget_workpieceSizes->item(row, 1)->text().toDouble(&radiusOk); size.height = ui->tableWidget_workpieceSizes->item(row, 2)->text().toDouble(&heightOk); if (!radiusOk || !heightOk || !std::isfinite(size.radius) || !std::isfinite(size.height) || size.radius <= 0.0 || size.height <= 0.0 || size.radius > 1e6 || size.height > 1e6) { StyledMessageBox::warning(this, "工件尺寸", QString("第 %1 项半径和高度必须为大于 0 且不超过 1000000 的数值。").arg(row + 1)); return false; } updated.workpieceSizes.push_back(size); } VrRotorWorkpieceSize defaultSize; if (!updated.getWorkpieceSize(0, defaultSize)) { StyledMessageBox::warning(this, "工件尺寸", "工件序号不能重复,默认序号必须在列表中,半径和高度必须大于 0。"); return false; } // 同步旧字段便于配置兼容;检测始终按列表选择。 updated.radius = defaultSize.radius; updated.height = defaultSize.height; rotorParam = updated; return true; } bool DialogAlgoarg::SaveModelDetectionParamsFromUI(VrAlgorithmParams& algorithmParams) { if (!ui || !ui->tableWidget_modelParams || ui->tableWidget_modelParams->rowCount() <= 0) { return false; } algorithmParams.modelParams.clear(); algorithmParams.modelGraySelection.targetGray = ui->doubleSpinBox_targetGray->value(); algorithmParams.modelGraySelection.maxGrayDeviation = ui->doubleSpinBox_maxGrayDeviation->value(); VrModelDetectionRegion& region = algorithmParams.modelDetectionRegion; region.enabled = ui->groupBox_modelDetectionRegion->isChecked(); region.left = ui->spinBox_modelRegionLeft->value(); region.top = ui->spinBox_modelRegionTop->value(); region.right = ui->spinBox_modelRegionRight->value(); region.bottom = ui->spinBox_modelRegionBottom->value(); if (!region.isValid()) { StyledMessageBox::warning(this, "2D识别区域", "右边界必须大于左边界,下边界必须大于上边界。"); return false; } if (!algorithmParams.modelGraySelection.isValid()) { return false; } for (int row = 0; row < ui->tableWidget_modelParams->rowCount(); ++row) { if (!ui->tableWidget_modelParams->item(row, 1) || !ui->tableWidget_modelParams->item(row, 2) || !ui->tableWidget_modelParams->item(row, 3)) { return false; } VrWorkpieceModelParam model; bool exposureOk = false; bool gainOk = false; bool scoreOk = false; model.exposure = ui->tableWidget_modelParams->item(row, 1)->text().toUInt(&exposureOk); model.gain = ui->tableWidget_modelParams->item(row, 2)->text().toUInt(&gainOk); model.minimumScore = ui->tableWidget_modelParams->item(row, 3)->text().toDouble(&scoreOk); if (!exposureOk || !gainOk || !scoreOk || !model.isValid()) { return false; } algorithmParams.modelParams.push_back(model); } // 保留公共配置结构对旧项目调用方的兼容镜像;转子检测始终使用上面的列表。 algorithmParams.modelParam.exposure = algorithmParams.modelParams.front().exposure; algorithmParams.modelParam.gain = algorithmParams.modelParams.front().gain; algorithmParams.modelParam.minimumScore = algorithmParams.modelParams.front().minimumScore; return true; } void DialogAlgoarg::initModelParamsTab() { if (ui && ui->tableWidget_modelParams) { auto* header = ui->tableWidget_modelParams->horizontalHeader(); for (int column = 0; column < ui->tableWidget_modelParams->columnCount(); ++column) { header->setSectionResizeMode(column, QHeaderView::Stretch); } ui->tableWidget_modelParams->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } connect(ui->btn_modelParamAdd, &QPushButton::clicked, this, [this]() { if (ui->tableWidget_modelParams->rowCount() < 65535) { addModelParamRow(VrWorkpieceModelParam()); refreshModelParamRows(); ui->tableWidget_modelParams->selectRow(ui->tableWidget_modelParams->rowCount() - 1); } }); } void DialogAlgoarg::addModelParamRow(const VrWorkpieceModelParam& modelParam) { const int row = ui->tableWidget_modelParams->rowCount(); ui->tableWidget_modelParams->insertRow(row); const QStringList values = {QString::number(row + 1), QString::number(modelParam.exposure), QString::number(modelParam.gain), QString::number(modelParam.minimumScore, 'g', 15)}; for (int column = 0; column < values.size(); ++column) { auto* item = new QTableWidgetItem(values[column]); item->setForeground(QColor(221, 225, 233)); if (column == 0) item->setFlags(item->flags() & ~Qt::ItemIsEditable); ui->tableWidget_modelParams->setItem(row, column, item); } auto* actions = new WorkpieceSizeActions(ui->tableWidget_modelParams); ui->tableWidget_modelParams->setCellWidget(row, 4, actions); const QPersistentModelIndex rowIndex(ui->tableWidget_modelParams->model()->index(row, 0)); connect(actions, &WorkpieceSizeActions::moveRequested, this, [this, rowIndex](int offset) { if (rowIndex.isValid()) moveModelParamRow(rowIndex.row(), offset); }); connect(actions, &WorkpieceSizeActions::removeRequested, this, [this, rowIndex]() { if (rowIndex.isValid()) removeModelParamRow(rowIndex.row()); }); } void DialogAlgoarg::refreshModelParamRows() { for (int row = 0; row < ui->tableWidget_modelParams->rowCount(); ++row) { ui->tableWidget_modelParams->item(row, 0)->setText(QString::number(row + 1)); } updateModelParamActions(); } void DialogAlgoarg::updateModelParamActions() { const int count = ui->tableWidget_modelParams->rowCount(); for (int row = 0; row < count; ++row) { if (auto* actions = qobject_cast(ui->tableWidget_modelParams->cellWidget(row, 4))) actions->setRowPosition(row, count); } } void DialogAlgoarg::moveModelParamRow(int row, int offset) { const int target = row + offset; if (row < 0 || target < 0 || target >= ui->tableWidget_modelParams->rowCount()) return; for (int column = 1; column <= 3; ++column) { auto* source = ui->tableWidget_modelParams->takeItem(row, column); auto* destination = ui->tableWidget_modelParams->takeItem(target, column); ui->tableWidget_modelParams->setItem(row, column, destination); ui->tableWidget_modelParams->setItem(target, column, source); } refreshModelParamRows(); ui->tableWidget_modelParams->selectRow(target); } void DialogAlgoarg::removeModelParamRow(int row) { if (ui->tableWidget_modelParams->rowCount() <= 1) return; ui->tableWidget_modelParams->removeRow(row); refreshModelParamRows(); } void DialogAlgoarg::on_btn_camer_ok_clicked() { if (SaveConfigFromUI()) { StyledMessageBox::information(this, "成功", "配置保存成功!"); accept(); } else { StyledMessageBox::warning(this, "失败", "配置保存失败,请检查文件权限或参数输入!"); } } void DialogAlgoarg::on_btn_camer_cancel_clicked() { reject(); } void DialogAlgoarg::onCalibMatrixLoaded(int cameraIndex, const double* matrix) { Q_UNUSED(cameraIndex); Q_UNUSED(matrix); StyledMessageBox::information(this, "提示", "已从文件导入标定矩阵"); } void DialogAlgoarg::onSaveCalibRequested(int cameraIndex, const double* matrix) { Q_UNUSED(cameraIndex); Q_UNUSED(matrix); if (SaveConfigFromUI()) { StyledMessageBox::information(this, "成功", "手眼标定参数已保存!"); } else { StyledMessageBox::warning(this, "失败", "保存手眼标定参数失败!"); } } void DialogAlgoarg::LoadHandEyeCalibConfig() { if (!m_pConfigManager || !m_handEyeCalibWidget) { return; } const ConfigResult configData = m_pConfigManager->GetConfigResult(); const VrHandEyeCalibMatrix& calibMatrix = configData.handEyeCalibMatrix; m_handEyeCalibWidget->setCalibData( kHandEyeConfigIndex, calibMatrix.matrix, !isIdentityMatrix(calibMatrix.matrix)); } bool DialogAlgoarg::SaveCalibMatrixToConfig(VrHandEyeCalibMatrix& calibMatrix) { if (!m_handEyeCalibWidget) { return false; } bool isCalibrated = false; return m_handEyeCalibWidget->getCalibData( kHandEyeConfigIndex, calibMatrix.matrix, isCalibrated); } void DialogAlgoarg::LoadPlcRobotServerConfigToUI(const VrPlcRobotServerConfig& config) { if (!m_networkConfigWidget) return; NetworkConfigData data; data.poseOutputOrder = config.poseOutputOrder; data.byteOrder = config.byteOrder; data.dirVectorInvert = config.dirVectorInvert; data.addrPhotoRequest = config.registerConfig.addrTrigger; data.addrDataComplete = config.registerConfig.addrWorkStatus; data.addrCoordDataStart = config.registerConfig.addrCoordDataStart; data.tcpServerPort = config.robotServerPort; m_networkConfigWidget->setConfig(data); } bool DialogAlgoarg::SavePlcRobotServerConfigFromUI(VrPlcRobotServerConfig& config) { if (!m_networkConfigWidget) return false; const NetworkConfigData data = m_networkConfigWidget->getConfig(); if (data.tcpServerPort <= 0 || data.tcpServerPort > 65535 || data.addrPhotoRequest < 0 || data.addrDataComplete < 0 || data.addrCoordDataStart < 0) { return false; } config.robotServerPort = data.tcpServerPort; config.registerConfig.addrTrigger = data.addrPhotoRequest; config.registerConfig.addrWorkStatus = data.addrDataComplete; config.registerConfig.addrCoordDataStart = data.addrCoordDataStart; config.poseOutputOrder = data.poseOutputOrder; config.dirVectorInvert = data.dirVectorInvert; config.byteOrder = data.byteOrder; return true; }