更新了拆包伯朗特的协议;提取协议到common,logcommon
This commit is contained in:
parent
b0108a4602
commit
c6c3c2edeb
@ -83,9 +83,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
QGraphicsScene* scene = new QGraphicsScene(this);
|
||||
ui->detect_image->setScene(scene);
|
||||
|
||||
// 初始化日志模型
|
||||
m_logModel = new QStringListModel(this);
|
||||
ui->detect_log->setModel(m_logModel);
|
||||
// 初始化日志辅助类
|
||||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
|
||||
// 注册自定义类型,使其能够在信号槽中跨线程传递
|
||||
qRegisterMetaType<DetectionResult>("DetectionResult");
|
||||
@ -98,12 +97,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// 连接检测结果更新信号槽
|
||||
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
|
||||
|
||||
// 连接日志更新信号槽
|
||||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||||
|
||||
// 连接清空日志信号槽
|
||||
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
|
||||
|
||||
// 初始化右键菜单
|
||||
setupContextMenu();
|
||||
|
||||
@ -129,14 +122,18 @@ MainWindow::~MainWindow()
|
||||
|
||||
void MainWindow::updateStatusLog(const QString& message)
|
||||
{
|
||||
// 通过信号槽机制更新detect_log控件
|
||||
emit logUpdateRequested(message);
|
||||
// 使用日志辅助类更新日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->appendLog(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLog()
|
||||
{
|
||||
// 通过信号槽机制清空日志
|
||||
emit logClearRequested();
|
||||
// 使用日志辅助类清空日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->clearLog();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Init()
|
||||
@ -369,65 +366,13 @@ void MainWindow::updateWorkStatusLabel(WorkStatus status)
|
||||
void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
|
||||
{
|
||||
// 显示检测结果图像
|
||||
updateDetectionLog(tr("检测结果更新"));
|
||||
updateStatusLog(tr("检测结果更新"));
|
||||
displayImage(result.image);
|
||||
|
||||
// 更新检测结果到列表
|
||||
addDetectionResult(result);
|
||||
}
|
||||
|
||||
void MainWindow::updateDetectionLog(const QString& message)
|
||||
{
|
||||
// 在UI线程中更新detect_log控件(QListView)
|
||||
if (!m_logModel) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 检查是否与最后一条消息相同
|
||||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
|
||||
// 替换最后一条
|
||||
logList[logList.size() - 1] = logEntry;
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
|
||||
// 添加新的日志条目
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
ui->detect_log->scrollTo(lastIndex);
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLogUI()
|
||||
{
|
||||
// 在UI线程中清空检测日志
|
||||
if (m_logModel) {
|
||||
m_logModel->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// 重置日志计数器
|
||||
m_lastLogMessage.clear();
|
||||
m_lastLogCount = 0;
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_start_clicked()
|
||||
{
|
||||
// 检查Presenter是否已初始化
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "dialogalgoarg.h"
|
||||
#include "CommonDialogCameraLevel.h"
|
||||
#include "DeviceStatusWidget.h"
|
||||
#include "DetectLogHelper.h"
|
||||
#include "BagThreadPositionPresenter.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -51,12 +52,6 @@ signals:
|
||||
// 检测结果更新信号
|
||||
void detectionResultUpdateRequested(const DetectionResult& result);
|
||||
|
||||
// 日志更新信号
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
// 清空日志信号
|
||||
void logClearRequested();
|
||||
|
||||
private slots:
|
||||
|
||||
// 工作状态更新槽函数
|
||||
@ -65,12 +60,6 @@ private slots:
|
||||
// 检测结果更新槽函数
|
||||
void updateDetectionResultDisplay(const DetectionResult& result);
|
||||
|
||||
// 日志更新槽函数
|
||||
void updateDetectionLog(const QString& message);
|
||||
|
||||
// 清空日志槽函数
|
||||
void clearDetectionLogUI();
|
||||
|
||||
// 处理相机点击事件
|
||||
void onCameraClicked(int cameraIndex);
|
||||
|
||||
@ -103,8 +92,8 @@ private:
|
||||
// 业务逻辑处理类
|
||||
BagThreadPositionPresenter* m_presenter = nullptr;
|
||||
|
||||
// 日志模型
|
||||
QStringListModel* m_logModel = nullptr;
|
||||
// 日志辅助类
|
||||
DetectLogHelper* m_logHelper = nullptr;
|
||||
|
||||
// 设备状态显示widget的引用
|
||||
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
|
||||
@ -134,10 +123,5 @@ private:
|
||||
void setupContextMenu();
|
||||
void showContextMenu(const QPoint& pos, QGraphicsView* view);
|
||||
bool saveDetectionDataToFile(const QString& filePath, int cameraIndex);
|
||||
|
||||
private:
|
||||
// 日志去重相关
|
||||
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
|
||||
int m_lastLogCount = 0; // 最后一条日志的重复次数
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@ -40,9 +40,10 @@ public:
|
||||
/**
|
||||
* @brief 发送坐标数据
|
||||
* @param multiTargetData 多目标数据
|
||||
* @param errorCode 检测错误码,0表示成功,-2201表示空垛,其他表示错误
|
||||
* @return 0-成功,其他-错误码
|
||||
*/
|
||||
int SendCoordinateData(const MultiTargetData& multiTargetData);
|
||||
int SendCoordinateData(const MultiTargetData& multiTargetData, int errorCode = 0);
|
||||
|
||||
/**
|
||||
* @brief 获取连接状态
|
||||
@ -73,9 +74,16 @@ public:
|
||||
* @param dsID 附加命令的dsID
|
||||
* @param address 附加命令的地址
|
||||
* @param successValue 检测成功时的值
|
||||
* @param failValue 检测失败时的值
|
||||
* @param emptyValue 检测结果空时的值
|
||||
* @param startValue 开启检测/检测失败时的值
|
||||
*/
|
||||
void SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int failValue);
|
||||
void SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int emptyValue, int startValue);
|
||||
|
||||
/**
|
||||
* @brief 发送开启检测/检测失败附加指令
|
||||
* @return 0-成功,其他-错误码
|
||||
*/
|
||||
int SendStartDetectCommand();
|
||||
|
||||
/**
|
||||
* @brief 设置第二个坐标的Z轴偏移量
|
||||
@ -138,7 +146,8 @@ private:
|
||||
std::string m_strPostCoordDsID; // 坐标发送后附加命令的dsID
|
||||
int m_nPostCoordAddress; // 坐标发送后附加命令的地址
|
||||
int m_nPostCoordSuccessValue; // 检测成功时的值
|
||||
int m_nPostCoordFailValue; // 检测失败时的值
|
||||
int m_nPostCoordEmptyValue; // 检测结果空时的值
|
||||
int m_nPostCoordStartValue; // 开启检测/检测失败时的值
|
||||
double m_dZOffset; // 第二个坐标Z轴偏移量
|
||||
double m_dZMin; // Z轴最小值
|
||||
double m_dZMax; // Z轴最大值
|
||||
|
||||
@ -25,16 +25,17 @@ public:
|
||||
|
||||
|
||||
int DetectBag( std::vector<SVzNL3DLaserLine>& detectionDataCache,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
DetectionResult& detectionResult);
|
||||
|
||||
DetectionResult& detectionResult,
|
||||
double planeHeightOffset = -10.0);
|
||||
|
||||
int DetectBag( std::vector<SVzNLXYZRGBDLaserLine>& detectionDataCache,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
@ -42,15 +43,16 @@ public:
|
||||
const RGB& rgbColorPattern,
|
||||
const double frontColorTemplate[RGN_HIST_SIZE],
|
||||
const double backColorTemplate[RGN_HIST_SIZE],
|
||||
DetectionResult& detectionResult);
|
||||
DetectionResult& detectionResult,
|
||||
double planeHeightOffset = -10.0);
|
||||
|
||||
|
||||
|
||||
// 新增:处理统一数据格式和项目类型的DetectBag函数
|
||||
int DetectBag( int cameraIndex,
|
||||
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
ProjectType projectType,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
@ -58,7 +60,8 @@ public:
|
||||
const RGB& rgbColorPattern,
|
||||
const double frontColorTemplate[RGN_HIST_SIZE],
|
||||
const double backColorTemplate[RGN_HIST_SIZE],
|
||||
DetectionResult& detectionResult);
|
||||
DetectionResult& detectionResult,
|
||||
double planeHeightOffset = -10.0);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ struct CurrentExecutionParams
|
||||
int cameraIndex; // 相机序号
|
||||
VrCameraParams cameraParam; // 相机参数
|
||||
SSG_planeCalibPara planeCalibParam; // 调平参数
|
||||
double planeHeightOffset = -10.0; // 平面高度偏移量
|
||||
CalibMatrix handEyeCalibMatrix; // 手眼标定参数
|
||||
VrBagParam bagParam; // 包裹参数(尺寸)
|
||||
const TCPClient* pClient = nullptr; // 请求的客户端指针
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "json/json.h"
|
||||
#include "VrLog.h"
|
||||
#include "VrError.h"
|
||||
#include "SG_errCode.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <thread>
|
||||
@ -15,7 +16,8 @@ BolunteProtocol::BolunteProtocol()
|
||||
, m_strPostCoordDsID("www.hc-system.com.RemoteMonitor")
|
||||
, m_nPostCoordAddress(800)
|
||||
, m_nPostCoordSuccessValue(10)
|
||||
, m_nPostCoordFailValue(12)
|
||||
, m_nPostCoordEmptyValue(12)
|
||||
, m_nPostCoordStartValue(13)
|
||||
, m_dZOffset(300.0)
|
||||
, m_dZMin(-1500.0)
|
||||
, m_dZMax(2500.0)
|
||||
@ -120,7 +122,7 @@ int BolunteProtocol::SendPhotoResult(const std::string& packID, int camID, bool
|
||||
}
|
||||
}
|
||||
|
||||
int BolunteProtocol::SendCoordinateData(const MultiTargetData& multiTargetData)
|
||||
int BolunteProtocol::SendCoordinateData(const MultiTargetData& multiTargetData, int errorCode)
|
||||
{
|
||||
if (!m_bConnected)
|
||||
{
|
||||
@ -128,46 +130,102 @@ int BolunteProtocol::SendCoordinateData(const MultiTargetData& multiTargetData)
|
||||
return ERR_CODE(NET_ERR_NOTINIT);
|
||||
}
|
||||
|
||||
std::string jsonStr = GenerateCoordinateJson(multiTargetData);
|
||||
bool ret = m_pTcpServer->SendAllData(jsonStr.c_str(), jsonStr.length());
|
||||
bool hasTargets = !multiTargetData.targets.empty();
|
||||
|
||||
// 根据检测结果和错误码确定返回值
|
||||
// 1. 正常(有结果):发送坐标 + 附加指令 10
|
||||
// 2. 空垛(errorCode == -2201):只发送附加指令 12
|
||||
// 3. 错误(相机开启失败、没有结果且不是空垛):只发送附加指令 13
|
||||
int resultValue;
|
||||
if (hasTargets) {
|
||||
resultValue = m_nPostCoordSuccessValue; // 10
|
||||
} else if (SX_BAG_TRAY_EMPTY == errorCode) {
|
||||
resultValue = m_nPostCoordEmptyValue; // 12
|
||||
} else {
|
||||
resultValue = m_nPostCoordStartValue; // 13
|
||||
}
|
||||
|
||||
// 正常情况:发送坐标数据
|
||||
if (hasTargets) {
|
||||
std::string jsonStr = GenerateCoordinateJson(multiTargetData);
|
||||
bool ret = m_pTcpServer->SendAllData(jsonStr.c_str(), jsonStr.length());
|
||||
if (!ret) {
|
||||
LOG_ERROR("Failed to send coordinate data\n");
|
||||
return ERR_CODE(NET_ERR_SEND_DATA);
|
||||
}
|
||||
LOG_INFO("Sent coordinate data: count=%d\n", multiTargetData.count);
|
||||
}
|
||||
|
||||
// 发送附加命令
|
||||
if (!m_strPostCoordDsID.empty())
|
||||
{
|
||||
if (hasTargets) {
|
||||
// 正常情况:延迟100ms后发送附加指令
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
// 生成附加命令JSON
|
||||
VA::Json::Value postCmd;
|
||||
postCmd["dsID"] = m_strPostCoordDsID;
|
||||
postCmd["reqType"] = "command";
|
||||
|
||||
VA::Json::Value cmdData(VA::Json::arrayValue);
|
||||
cmdData.append("rewriteData");
|
||||
cmdData.append(std::to_string(m_nPostCoordAddress));
|
||||
cmdData.append(std::to_string(resultValue));
|
||||
cmdData.append("0");
|
||||
postCmd["cmdData"] = cmdData;
|
||||
|
||||
VA::Json::FastWriter writer;
|
||||
std::string postCmdStr = writer.write(postCmd);
|
||||
|
||||
m_pTcpServer->SendAllData(postCmdStr.c_str(), postCmdStr.length());
|
||||
LOG_INFO("Sent post coordinate command: hasTargets=%d, errorCode=%d, resultValue=%d\n", hasTargets, errorCode, resultValue);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int BolunteProtocol::SendStartDetectCommand()
|
||||
{
|
||||
if (!m_bConnected)
|
||||
{
|
||||
LOG_ERROR("TCP server has no connected client\n");
|
||||
return ERR_CODE(NET_ERR_NOTINIT);
|
||||
}
|
||||
|
||||
// 如果附加指令dsID为空则不发送
|
||||
if (m_strPostCoordDsID.empty())
|
||||
{
|
||||
LOG_INFO("PostCoordinateDsID is empty, skip sending start detect command\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// 生成开启检测附加命令JSON
|
||||
VA::Json::Value postCmd;
|
||||
postCmd["dsID"] = m_strPostCoordDsID;
|
||||
postCmd["reqType"] = "command";
|
||||
|
||||
VA::Json::Value cmdData(VA::Json::arrayValue);
|
||||
cmdData.append("rewriteData");
|
||||
cmdData.append(std::to_string(m_nPostCoordAddress));
|
||||
cmdData.append(std::to_string(m_nPostCoordStartValue));
|
||||
cmdData.append("0");
|
||||
postCmd["cmdData"] = cmdData;
|
||||
|
||||
VA::Json::FastWriter writer;
|
||||
std::string postCmdStr = writer.write(postCmd);
|
||||
|
||||
bool ret = m_pTcpServer->SendAllData(postCmdStr.c_str(), postCmdStr.length());
|
||||
|
||||
if (ret)
|
||||
{
|
||||
LOG_INFO("Sent coordinate data: count=%d\n", multiTargetData.count);
|
||||
|
||||
// 100ms后发送附加命令,根据检测结果拼接成功/失败值
|
||||
if (!m_strPostCoordDsID.empty())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// 根据检测结果选择成功/失败值
|
||||
bool detectionSuccess = !multiTargetData.targets.empty();
|
||||
int resultValue = detectionSuccess ? m_nPostCoordSuccessValue : m_nPostCoordFailValue;
|
||||
|
||||
// 动态生成附加命令JSON
|
||||
VA::Json::Value postCmd;
|
||||
postCmd["dsID"] = m_strPostCoordDsID;
|
||||
postCmd["reqType"] = "command";
|
||||
|
||||
VA::Json::Value cmdData(VA::Json::arrayValue);
|
||||
cmdData.append("rewriteData");
|
||||
cmdData.append(std::to_string(m_nPostCoordAddress));
|
||||
cmdData.append(std::to_string(resultValue));
|
||||
cmdData.append("0");
|
||||
postCmd["cmdData"] = cmdData;
|
||||
|
||||
VA::Json::FastWriter writer;
|
||||
std::string postCmdStr = writer.write(postCmd);
|
||||
|
||||
m_pTcpServer->SendAllData(postCmdStr.c_str(), postCmdStr.length());
|
||||
LOG_INFO("Sent post coordinate command: success=%d, value=%d\n", detectionSuccess, resultValue);
|
||||
}
|
||||
|
||||
LOG_INFO("Sent start detect command: value=%d\n", m_nPostCoordStartValue);
|
||||
return SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("Failed to send coordinate data\n");
|
||||
LOG_ERROR("Failed to send start detect command\n");
|
||||
return ERR_CODE(NET_ERR_SEND_DATA);
|
||||
}
|
||||
}
|
||||
@ -192,12 +250,13 @@ void BolunteProtocol::SetDsID(const std::string& dsID)
|
||||
m_strDsID = dsID;
|
||||
}
|
||||
|
||||
void BolunteProtocol::SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int failValue)
|
||||
void BolunteProtocol::SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int emptyValue, int startValue)
|
||||
{
|
||||
m_strPostCoordDsID = dsID;
|
||||
m_nPostCoordAddress = address;
|
||||
m_nPostCoordSuccessValue = successValue;
|
||||
m_nPostCoordFailValue = failValue;
|
||||
m_nPostCoordEmptyValue = emptyValue;
|
||||
m_nPostCoordStartValue = startValue;
|
||||
}
|
||||
|
||||
void BolunteProtocol::SetZOffset(double zOffset)
|
||||
|
||||
@ -11,12 +11,13 @@ DetectPresenter::~DetectPresenter()
|
||||
}
|
||||
|
||||
int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
DetectionResult& detectionResult)
|
||||
DetectionResult& detectionResult,
|
||||
double planeHeightOffset)
|
||||
{
|
||||
if (detectionDataCache.empty()) {
|
||||
LOG_WARNING("No cached detection data available\n");
|
||||
@ -25,7 +26,7 @@ int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache
|
||||
|
||||
// 2. 数据预处理:调平和去除地面(使用当前相机的调平参数)
|
||||
for (size_t i = 0; i < detectionDataCache.size(); i++) {
|
||||
sg_lineDataR(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight);
|
||||
sg_lineDataR(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight + planeHeightOffset);
|
||||
}
|
||||
|
||||
// 3. 调用算法检测接口(使用当前相机的调平参数)
|
||||
@ -77,10 +78,8 @@ int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache
|
||||
detectionResult.positions.push_back(pos);
|
||||
|
||||
if(debugParam.enableDebug && debugParam.printDetailLog){
|
||||
LOG_INFO("[Algo Thread] Object %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n",
|
||||
i, obj.centerPos.x, obj.centerPos.y, obj.centerPos.z);
|
||||
LOG_INFO("[Algo Thread] Object %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n",
|
||||
i, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
|
||||
LOG_INFO("[Algo Thread] Object %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", i, obj.centerPos.x, obj.centerPos.y, obj.centerPos.z);
|
||||
LOG_INFO("[Algo Thread] Object %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n", i, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
|
||||
}
|
||||
|
||||
}
|
||||
@ -90,8 +89,8 @@ int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache
|
||||
|
||||
|
||||
int DetectPresenter::DetectBag(std::vector<SVzNLXYZRGBDLaserLine>& detectionDataCache,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
@ -99,7 +98,8 @@ int DetectPresenter::DetectBag(std::vector<SVzNLXYZRGBDLaserLine>& detectionData
|
||||
const RGB& rgbColorPattern,
|
||||
const double frontColorTemplate[RGN_HIST_SIZE],
|
||||
const double backColorTemplate[RGN_HIST_SIZE],
|
||||
DetectionResult& detectionResult)
|
||||
DetectionResult& detectionResult,
|
||||
double planeHeightOffset)
|
||||
{
|
||||
|
||||
if (detectionDataCache.empty()) {
|
||||
@ -109,7 +109,7 @@ int DetectPresenter::DetectBag(std::vector<SVzNLXYZRGBDLaserLine>& detectionData
|
||||
|
||||
// 2. 数据预处理:调平和去除地面(使用当前相机的调平参数)
|
||||
for (size_t i = 0; i < detectionDataCache.size(); i++) {
|
||||
sg_lineDataR_RGBD(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight);
|
||||
sg_lineDataR_RGBD(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight + planeHeightOffset);
|
||||
}
|
||||
|
||||
|
||||
@ -183,8 +183,8 @@ int DetectPresenter::DetectBag(
|
||||
int cameraIndex,
|
||||
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
|
||||
ProjectType projectType,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const SG_bagPositionParam& algoParam,
|
||||
const SSG_planeCalibPara& cameraCalibParam,
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
@ -192,7 +192,8 @@ int DetectPresenter::DetectBag(
|
||||
const RGB& rgbColorPattern,
|
||||
const double frontColorTemplate[RGN_HIST_SIZE],
|
||||
const double backColorTemplate[RGN_HIST_SIZE],
|
||||
DetectionResult& detectionResult)
|
||||
DetectionResult& detectionResult,
|
||||
double planeHeightOffset)
|
||||
{
|
||||
if (laserLines.empty()) {
|
||||
LOG_WARNING("No laser lines data available\n");
|
||||
@ -234,7 +235,7 @@ int DetectPresenter::DetectBag(
|
||||
algoParam.growParam.yDeviation_max, algoParam.growParam.zDeviation_max,
|
||||
algoParam.growParam.maxLineSkipNum, algoParam.growParam.maxSkipDistance);
|
||||
|
||||
LOG_INFO(" Plane height: %.3f\n", cameraCalibParam.planeHeight);
|
||||
LOG_INFO(" Plane height: %.3f offset: %.3f\n", cameraCalibParam.planeHeight, planeHeightOffset);
|
||||
LOG_INFO(" Plane calibration matrix: [%.3f, %.3f, %.3f; %.3f, %.3f, %.3f; %.3f, %.3f, %.3f]\n",
|
||||
cameraCalibParam.planeCalib[0], cameraCalibParam.planeCalib[1], cameraCalibParam.planeCalib[2],
|
||||
cameraCalibParam.planeCalib[3], cameraCalibParam.planeCalib[4], cameraCalibParam.planeCalib[5],
|
||||
@ -248,7 +249,8 @@ int DetectPresenter::DetectBag(
|
||||
std::vector<SVzNLXYZRGBDLaserLine> rgbdData;
|
||||
int convertResult = dataLoader.ConvertToSVzNLXYZRGBDLaserLine(laserLines, rgbdData);
|
||||
if (convertResult == SUCCESS && !rgbdData.empty()) {
|
||||
nRet = DetectBag(rgbdData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, hsvCmpParam, rgbColorPattern, frontColorTemplate, backColorTemplate, detectionResult);
|
||||
nRet = DetectBag(rgbdData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, hsvCmpParam, rgbColorPattern, frontColorTemplate,
|
||||
backColorTemplate, detectionResult, planeHeightOffset);
|
||||
|
||||
// 清理RGBD内存
|
||||
dataLoader.FreeConvertedData(rgbdData);
|
||||
@ -263,7 +265,7 @@ int DetectPresenter::DetectBag(
|
||||
std::vector<SVzNL3DLaserLine> xyzData;
|
||||
int convertResult = dataLoader.ConvertToSVzNL3DLaserLine(laserLines, xyzData);
|
||||
if (convertResult == SUCCESS && !xyzData.empty()) {
|
||||
nRet = DetectBag(xyzData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, detectionResult);
|
||||
nRet = DetectBag(xyzData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, detectionResult, planeHeightOffset);
|
||||
// 清理XYZ内存
|
||||
dataLoader.FreeConvertedData(xyzData);
|
||||
|
||||
|
||||
@ -452,7 +452,8 @@ int GrabBagPresenter::InitBolunteProtocol()
|
||||
configResult.bolunteConfig.postCoordinateDsID,
|
||||
configResult.bolunteConfig.postCoordinateAddress,
|
||||
configResult.bolunteConfig.postCoordinateSuccessValue,
|
||||
configResult.bolunteConfig.postCoordinateFailValue);
|
||||
configResult.bolunteConfig.postCoordinateEmptyValue,
|
||||
configResult.bolunteConfig.postCoordinateStartValue);
|
||||
|
||||
// 设置连接状态回调
|
||||
m_pBolunteProtocol->SetConnectionCallback([this](bool connected) {
|
||||
@ -1546,7 +1547,8 @@ int GrabBagPresenter::_DetectTask()
|
||||
m_pParameterManager->GetHsvCmpParam(),
|
||||
m_pParameterManager->GetRgbColorPattern(),
|
||||
m_pParameterManager->GetFrontColorTemplate(),
|
||||
m_pParameterManager->GetBackColorTemplate(), detectionResult);
|
||||
m_pParameterManager->GetBackColorTemplate(), detectionResult,
|
||||
m_currentExecutionParams.planeHeightOffset);
|
||||
LOG_INFO("[Algo Thread] DetectBag return: %d\n", nRet);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("[Algo Thread] Exception in DetectBag: %s\n", e.what());
|
||||
@ -1567,10 +1569,6 @@ int GrabBagPresenter::_DetectTask()
|
||||
m_pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString());
|
||||
}
|
||||
|
||||
if(SUCCESS != nRet){
|
||||
ERR_CODE_RETURN(nRet);
|
||||
}
|
||||
|
||||
LOG_INFO("[Algo Thread] algo detected %zu objects time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec());
|
||||
|
||||
// 8. 返回检测结果
|
||||
@ -1588,7 +1586,6 @@ int GrabBagPresenter::_DetectTask()
|
||||
// 更新机械臂协议状态(发送转换后的目标位置数据)
|
||||
_SendDetectionResultToRobot(detectionResult, m_currentExecutionParams.cameraIndex);
|
||||
|
||||
|
||||
// 设置机械臂工作状态为相应相机工作完成(相机ID从1开始)
|
||||
if (m_pRobotProtocol) {
|
||||
uint16_t workStatus = (m_currentExecutionParams.cameraIndex == 1) ? RobotProtocol::WORK_STATUS_CAMERA1_DONE : RobotProtocol::WORK_STATUS_CAMERA2_DONE;
|
||||
@ -1734,12 +1731,11 @@ void GrabBagPresenter::_SendDetectionResultToRobot(const DetectionResult& detect
|
||||
// 设置相机ID
|
||||
multiTargetData.cameraId = cameraIndex;
|
||||
|
||||
// 发送坐标数据
|
||||
int result = m_pBolunteProtocol->SendCoordinateData(multiTargetData);
|
||||
// 发送坐标数据,传递错误码以区分正常/空垛/错误
|
||||
int result = m_pBolunteProtocol->SendCoordinateData(multiTargetData, detectionResult.errorCode);
|
||||
|
||||
if (result == SUCCESS) {
|
||||
LOG_INFO("[Bolunte] SendCoordinateData successful for camera: %d, targets: %d\n",
|
||||
cameraIndex, multiTargetData.count);
|
||||
LOG_INFO("[Bolunte] SendCoordinateData successful for camera: %d, targets: %d\n", cameraIndex, multiTargetData.count);
|
||||
if (m_pStatus) {
|
||||
m_pStatus->OnStatusUpdate(QString("机械臂坐标发送成功").toStdString());
|
||||
}
|
||||
@ -1982,6 +1978,10 @@ bool GrabBagPresenter::OnBoluntePhotoCommand(int camID)
|
||||
if (result != SUCCESS) {
|
||||
LOG_ERROR("Detection failed for camera %d, error: %d\n", cameraIndex, result);
|
||||
SetWorkStatus(WorkStatus::Error);
|
||||
// 开启检测失败,发送开启检测失败附加指令
|
||||
if (m_pBolunteProtocol && m_bBolunteConnected) {
|
||||
m_pBolunteProtocol->SendStartDetectCommand();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2282,6 +2282,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams()
|
||||
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = identityMatrix9[i];
|
||||
}
|
||||
m_currentExecutionParams.planeCalibParam.planeHeight = -1.0;
|
||||
m_currentExecutionParams.planeHeightOffset = -10.0; // 默认高度偏移
|
||||
|
||||
// 3. 初始化手眼标定矩阵为单位矩阵(默认值)
|
||||
double identityMatrix16[16] = {
|
||||
@ -2357,6 +2358,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams()
|
||||
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = currentCamera->planeCalibParam.invRMatrix[i];
|
||||
}
|
||||
m_currentExecutionParams.planeCalibParam.planeHeight = currentCamera->planeCalibParam.planeHeight;
|
||||
m_currentExecutionParams.planeHeightOffset = currentCamera->planeCalibParam.planeHeightOffset;
|
||||
LOG_INFO("Using calibrated plane parameters from camera %s (height=%.3f)\n", currentCamera->cameraName.c_str(), currentCamera->planeCalibParam.planeHeight);
|
||||
} else {
|
||||
LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n", currentCamera->cameraName.c_str());
|
||||
@ -2403,6 +2405,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* w
|
||||
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = identityMatrix9[i];
|
||||
}
|
||||
m_currentExecutionParams.planeCalibParam.planeHeight = -1.0;
|
||||
m_currentExecutionParams.planeHeightOffset = -10.0; // 默认高度偏移
|
||||
|
||||
// 3. 初始化手眼标定矩阵为单位矩阵(默认值)
|
||||
double identityMatrix16[16] = {
|
||||
@ -2450,11 +2453,11 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* w
|
||||
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = camera->planeCalibParam.invRMatrix[i];
|
||||
}
|
||||
m_currentExecutionParams.planeCalibParam.planeHeight = camera->planeCalibParam.planeHeight;
|
||||
m_currentExecutionParams.planeHeightOffset = camera->planeCalibParam.planeHeightOffset;
|
||||
LOG_INFO("Using calibrated plane parameters from camera %s (height=%.3f)\n",
|
||||
camera->cameraName.c_str(), camera->planeCalibParam.planeHeight);
|
||||
} else {
|
||||
LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n",
|
||||
camera->cameraName.c_str());
|
||||
LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n", camera->cameraName.c_str());
|
||||
}
|
||||
|
||||
// 6. 设置手眼标定参数(从相机配置中获取)
|
||||
@ -2466,8 +2469,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* w
|
||||
}
|
||||
LOG_INFO("Using calibrated hand-eye matrix from camera %s\n", camera->cameraName.c_str());
|
||||
} else {
|
||||
LOG_INFO("Camera %s hand-eye is not calibrated, using identity matrix\n",
|
||||
camera->cameraName.c_str());
|
||||
LOG_INFO("Camera %s hand-eye is not calibrated, using identity matrix\n", camera->cameraName.c_str());
|
||||
}
|
||||
|
||||
LOG_INFO("Current execution parameters updated successfully\n");
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#define VERSION_H
|
||||
|
||||
#define GRABBAG_VERSION_STRING "1.3.5"
|
||||
#define GRABBAG_BUILD_STRING "5"
|
||||
#define GRABBAG_BUILD_STRING "7"
|
||||
#define GRABBAG_FULL_VERSION_STRING "V" GRABBAG_VERSION_STRING "_" GRABBAG_BUILD_STRING
|
||||
|
||||
// 获取版本信息的便捷函数
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
# 1.3.5
|
||||
## build_6 & 7 20260128
|
||||
1. 修改协议附属命令结果增加失败回复13
|
||||
|
||||
## build_5 20260126
|
||||
1. 修改协议附属命令结果
|
||||
|
||||
|
||||
## build_4 20260124
|
||||
1. 修复算法检测线程在析构时未退出的问题
|
||||
|
||||
|
||||
@ -392,7 +392,8 @@ void DialogCamera::LoadBolunteConfig()
|
||||
ui->lineEdit_bolunte_postcmd->setText(QString::fromStdString(configResult->bolunteConfig.postCoordinateDsID));
|
||||
ui->spinBox_bolunte_postaddr->setValue(configResult->bolunteConfig.postCoordinateAddress);
|
||||
ui->spinBox_bolunte_postsuccess->setValue(configResult->bolunteConfig.postCoordinateSuccessValue);
|
||||
ui->spinBox_bolunte_postfail->setValue(configResult->bolunteConfig.postCoordinateFailValue);
|
||||
ui->spinBox_bolunte_postempty->setValue(configResult->bolunteConfig.postCoordinateEmptyValue);
|
||||
ui->spinBox_bolunte_poststart->setValue(configResult->bolunteConfig.postCoordinateStartValue);
|
||||
|
||||
LOG_INFO("Loaded Bolunte config: port=%d, zOffset=%.1f\n",
|
||||
configResult->bolunteConfig.port,
|
||||
@ -424,7 +425,8 @@ bool DialogCamera::SaveBolunteConfig()
|
||||
configResult->bolunteConfig.postCoordinateDsID = ui->lineEdit_bolunte_postcmd->text().toStdString();
|
||||
configResult->bolunteConfig.postCoordinateAddress = ui->spinBox_bolunte_postaddr->value();
|
||||
configResult->bolunteConfig.postCoordinateSuccessValue = ui->spinBox_bolunte_postsuccess->value();
|
||||
configResult->bolunteConfig.postCoordinateFailValue = ui->spinBox_bolunte_postfail->value();
|
||||
configResult->bolunteConfig.postCoordinateEmptyValue = ui->spinBox_bolunte_postempty->value();
|
||||
configResult->bolunteConfig.postCoordinateStartValue = ui->spinBox_bolunte_poststart->value();
|
||||
|
||||
LOG_INFO("Saving Bolunte config: port=%d, zOffset=%.1f\n",
|
||||
configResult->bolunteConfig.port,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>660</width>
|
||||
<height>650</height>
|
||||
<height>690</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -226,7 +226,7 @@ QPushButton:disabled {
|
||||
<x>50</x>
|
||||
<y>330</y>
|
||||
<width>560</width>
|
||||
<height>261</height>
|
||||
<height>301</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -669,7 +669,7 @@ background-color: rgb(47, 48, 52);</string>
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_bolunte_postfail">
|
||||
<widget class="QLabel" name="label_bolunte_postempty">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>360</x>
|
||||
@ -687,10 +687,10 @@ background-color: rgb(47, 48, 52);</string>
|
||||
<string notr="true">color: rgb(221, 225, 233);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>失败值:</string>
|
||||
<string>空值:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSpinBox" name="spinBox_bolunte_postfail">
|
||||
<widget class="QSpinBox" name="spinBox_bolunte_postempty">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>420</x>
|
||||
@ -718,12 +718,61 @@ background-color: rgb(47, 48, 52);</string>
|
||||
<number>12</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_bolunte_poststart">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>260</y>
|
||||
<width>80</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(221, 225, 233);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>开启值:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSpinBox" name="spinBox_bolunte_poststart">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>110</x>
|
||||
<y>260</y>
|
||||
<width>80</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(221, 225, 233);
|
||||
background-color: rgb(47, 48, 52);</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>13</number>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_save">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>600</y>
|
||||
<y>640</y>
|
||||
<width>111</width>
|
||||
<height>38</height>
|
||||
</rect>
|
||||
@ -744,7 +793,7 @@ background-color: rgb(47, 48, 52);</string>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>340</x>
|
||||
<y>600</y>
|
||||
<y>640</y>
|
||||
<width>111</width>
|
||||
<height>38</height>
|
||||
</rect>
|
||||
|
||||
@ -987,6 +987,9 @@ void DialogCameraLevel::checkAndDisplayCalibrationStatus(int workPosIndex, int c
|
||||
.arg(cameraName));
|
||||
ui->label_level_result->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
||||
// 加载高度偏移值
|
||||
loadHeightOffset(workPosIndex, cameraIndex);
|
||||
}
|
||||
|
||||
// 设置调平时的状态回调
|
||||
@ -1015,15 +1018,125 @@ void DialogCameraLevel::restorePresenterStatusCallback()
|
||||
LOG_DEBUG("Presenter status callback already restored, skipping\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!m_presenter) {
|
||||
LOG_ERROR("Presenter is null, cannot restore status callback\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 恢复Presenter的状态回调 - 使用GrabBagPresenter的静态回调
|
||||
m_presenter->SetCameraStatusCallback(&GrabBagPresenter::_StaticCameraNotify, m_presenter);
|
||||
|
||||
|
||||
LOG_INFO("Presenter status callback restored for all cameras\n");
|
||||
}
|
||||
|
||||
// 加载高度偏移值
|
||||
void DialogCameraLevel::loadHeightOffset(int workPosIndex, int cameraIndex)
|
||||
{
|
||||
if (!m_pConfigResult || !m_presenter) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workPosIndex < 0 || workPosIndex >= static_cast<int>(m_pConfigResult->workPositions.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& workPos = m_pConfigResult->workPositions[workPosIndex];
|
||||
int configCameraIndex = cameraIndex + 1; // 转换为1-based索引
|
||||
|
||||
// 查找对应的相机配置
|
||||
for (const auto& cam : workPos.cameras) {
|
||||
if (cam.cameraIndex == configCameraIndex) {
|
||||
ui->doubleSpinBox_heightoffset->setValue(cam.planeCalibParam.planeHeightOffset);
|
||||
LOG_INFO("Loaded height offset: %.2f for camera %d at work position %s\n",
|
||||
cam.planeCalibParam.planeHeightOffset, configCameraIndex,
|
||||
workPos.name.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没找到,使用默认值
|
||||
ui->doubleSpinBox_heightoffset->setValue(-10.0);
|
||||
}
|
||||
|
||||
// 保存高度偏移值
|
||||
bool DialogCameraLevel::saveHeightOffset(int workPosIndex, int cameraIndex, double heightOffset)
|
||||
{
|
||||
if (!m_pConfigResult || !m_presenter) {
|
||||
LOG_ERROR("ConfigResult or Presenter is null\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (workPosIndex < 0 || workPosIndex >= static_cast<int>(m_pConfigResult->workPositions.size())) {
|
||||
LOG_ERROR("Invalid work position index: %d\n", workPosIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
|
||||
LOG_ERROR("Invalid camera index: %d\n", cameraIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
IVrConfig* config = m_presenter->GetConfig();
|
||||
if (!config) {
|
||||
LOG_ERROR("Config is null\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
QString configPath = PathManager::GetInstance().GetConfigFilePath();
|
||||
ConfigResult configResult = config->LoadConfig(configPath.toStdString());
|
||||
|
||||
const auto& workPos = m_pConfigResult->workPositions[workPosIndex];
|
||||
int configCameraIndex = cameraIndex + 1;
|
||||
|
||||
// 查找并更新对应的相机配置
|
||||
for (auto& wp : configResult.workPositions) {
|
||||
if (wp.id == workPos.id) {
|
||||
for (auto& cam : wp.cameras) {
|
||||
if (cam.cameraIndex == configCameraIndex) {
|
||||
cam.planeCalibParam.planeHeightOffset = heightOffset;
|
||||
|
||||
// 保存配置
|
||||
bool saveResult = config->SaveConfig(configPath.toStdString(), configResult);
|
||||
if (saveResult) {
|
||||
LOG_INFO("Height offset saved: %.2f for camera %d at work position %s\n",
|
||||
heightOffset, configCameraIndex, workPos.name.c_str());
|
||||
}
|
||||
return saveResult;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ERROR("Camera config not found for camera index %d\n", configCameraIndex);
|
||||
return false;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Exception in saveHeightOffset: %s\n", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存高度偏移按钮点击
|
||||
void DialogCameraLevel::on_btn_save_heightoffset_clicked()
|
||||
{
|
||||
if (m_currentWorkPosIndex < 0 || m_currentCameraIndex < 0) {
|
||||
StyledMessageBox::warning(this, "错误", "请先选择工位和相机!");
|
||||
return;
|
||||
}
|
||||
|
||||
double heightOffset = ui->doubleSpinBox_heightoffset->value();
|
||||
|
||||
if (saveHeightOffset(m_currentWorkPosIndex, m_currentCameraIndex, heightOffset)) {
|
||||
StyledMessageBox::information(this, "成功", "高度偏移已保存!");
|
||||
} else {
|
||||
StyledMessageBox::warning(this, "失败", "保存高度偏移失败!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ private slots:
|
||||
void on_btn_cancel_clicked();
|
||||
void on_combo_camera_currentIndexChanged(int index);
|
||||
void on_combo_workposition_currentIndexChanged(int index);
|
||||
void on_btn_save_heightoffset_clicked();
|
||||
|
||||
private:
|
||||
Ui::DialogCameraLevel *ui;
|
||||
@ -115,6 +116,10 @@ private:
|
||||
|
||||
// 检查并显示相机标定状态
|
||||
void checkAndDisplayCalibrationStatus(int workPosIndex, int cameraIndex);
|
||||
|
||||
// 加载和保存高度偏移
|
||||
void loadHeightOffset(int workPosIndex, int cameraIndex);
|
||||
bool saveHeightOffset(int workPosIndex, int cameraIndex, double heightOffset);
|
||||
};
|
||||
|
||||
#endif // DialogCameraLevel_H
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>150</y>
|
||||
<y>190</y>
|
||||
<width>111</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
@ -37,6 +37,93 @@
|
||||
<string>调平结果:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_heightoffset">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>150</y>
|
||||
<width>111</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(221, 225, 233);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>高度偏移:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox_heightoffset">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>150</y>
|
||||
<width>120</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(221, 225, 233);
|
||||
background-color: rgb(47, 48, 52);</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>-10.000000000000000</double>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_save_heightoffset">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>280</x>
|
||||
<y>150</y>
|
||||
<width>80</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">
|
||||
QPushButton {
|
||||
color: rgb(221, 225, 233);
|
||||
background-color: rgb(0, 120, 212);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: rgb(0, 140, 232);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: rgb(0, 100, 192);
|
||||
}
|
||||
</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>保存</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
@ -107,9 +194,9 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>150</y>
|
||||
<y>190</y>
|
||||
<width>501</width>
|
||||
<height>241</height>
|
||||
<height>201</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
|
||||
@ -70,7 +70,8 @@ struct BolunteConfig
|
||||
std::string postCoordinateDsID = "www.hc-system.com.RemoteMonitor"; // 坐标发送后附加命令的dsID
|
||||
int postCoordinateAddress = 800; // 坐标发送后附加命令的地址
|
||||
int postCoordinateSuccessValue = 10; // 检测成功时的值
|
||||
int postCoordinateFailValue = 12; // 检测失败时的值
|
||||
int postCoordinateEmptyValue = 12; // 检测结果空时的值
|
||||
int postCoordinateStartValue = 13; // 开启检测/检测失败时的值
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -269,7 +269,9 @@ ConfigResult CVrConfig::LoadConfig(const std::string& filePath)
|
||||
cameraParam.isCalibrated = cameraCalibParamElement->BoolAttribute("isCalibrated");
|
||||
if (cameraCalibParamElement->Attribute("planeHeight"))
|
||||
cameraParam.planeHeight = cameraCalibParamElement->DoubleAttribute("planeHeight");
|
||||
|
||||
if (cameraCalibParamElement->Attribute("planeHeightOffset"))
|
||||
cameraParam.planeHeightOffset = cameraCalibParamElement->DoubleAttribute("planeHeightOffset");
|
||||
|
||||
// 读取旋转矩阵planeCalib[9] (3x3矩阵)
|
||||
if (cameraCalibParamElement->Attribute("planeCalib_00"))
|
||||
cameraParam.planeCalib[0] = cameraCalibParamElement->DoubleAttribute("planeCalib_00");
|
||||
@ -494,10 +496,25 @@ ConfigResult CVrConfig::LoadConfig(const std::string& filePath)
|
||||
result.bolunteConfig.postCoordinateSuccessValue = postSuccessElement->IntText();
|
||||
}
|
||||
|
||||
XMLElement* postFailElement = bolunteConfigElement->FirstChildElement("PostCoordinateFailValue");
|
||||
if (postFailElement)
|
||||
XMLElement* postEmptyElement = bolunteConfigElement->FirstChildElement("PostCoordinateEmptyValue");
|
||||
if (postEmptyElement)
|
||||
{
|
||||
result.bolunteConfig.postCoordinateFailValue = postFailElement->IntText();
|
||||
result.bolunteConfig.postCoordinateEmptyValue = postEmptyElement->IntText();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 兼容旧配置:如果没有 EmptyValue,尝试读取旧的 FailValue
|
||||
XMLElement* postFailElement = bolunteConfigElement->FirstChildElement("PostCoordinateFailValue");
|
||||
if (postFailElement)
|
||||
{
|
||||
result.bolunteConfig.postCoordinateEmptyValue = postFailElement->IntText();
|
||||
}
|
||||
}
|
||||
|
||||
XMLElement* postStartElement = bolunteConfigElement->FirstChildElement("PostCoordinateStartValue");
|
||||
if (postStartElement)
|
||||
{
|
||||
result.bolunteConfig.postCoordinateStartValue = postStartElement->IntText();
|
||||
}
|
||||
}
|
||||
|
||||
@ -649,7 +666,8 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
|
||||
cameraCalibParamElement->SetAttribute("cameraName", cameraParam.cameraName.c_str());
|
||||
cameraCalibParamElement->SetAttribute("isCalibrated", cameraParam.isCalibrated);
|
||||
cameraCalibParamElement->SetAttribute("planeHeight", cameraParam.planeHeight);
|
||||
|
||||
cameraCalibParamElement->SetAttribute("planeHeightOffset", cameraParam.planeHeightOffset);
|
||||
|
||||
// 保存旋转矩阵planeCalib[9] (3x3矩阵)
|
||||
cameraCalibParamElement->SetAttribute("planeCalib_00", cameraParam.planeCalib[0]);
|
||||
cameraCalibParamElement->SetAttribute("planeCalib_01", cameraParam.planeCalib[1]);
|
||||
@ -788,9 +806,13 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
|
||||
boluntePostSuccessElement->SetText(configResult.bolunteConfig.postCoordinateSuccessValue);
|
||||
bolunteConfigElement->InsertEndChild(boluntePostSuccessElement);
|
||||
|
||||
XMLElement* boluntePostFailElement = doc.NewElement("PostCoordinateFailValue");
|
||||
boluntePostFailElement->SetText(configResult.bolunteConfig.postCoordinateFailValue);
|
||||
bolunteConfigElement->InsertEndChild(boluntePostFailElement);
|
||||
XMLElement* boluntePostEmptyElement = doc.NewElement("PostCoordinateEmptyValue");
|
||||
boluntePostEmptyElement->SetText(configResult.bolunteConfig.postCoordinateEmptyValue);
|
||||
bolunteConfigElement->InsertEndChild(boluntePostEmptyElement);
|
||||
|
||||
XMLElement* boluntePostStartElement = doc.NewElement("PostCoordinateStartValue");
|
||||
boluntePostStartElement->SetText(configResult.bolunteConfig.postCoordinateStartValue);
|
||||
bolunteConfigElement->InsertEndChild(boluntePostStartElement);
|
||||
|
||||
// 保存工作点配置(新增)
|
||||
SaveWorkPositions(doc, root, configResult.workPositions);
|
||||
|
||||
@ -135,6 +135,8 @@ void CVrConfig::ParseWorkPositions(XMLElement* workPositionsElement, ConfigResul
|
||||
cam.planeCalibParam.isCalibrated = planeCalibElement->BoolAttribute("isCalibrated");
|
||||
if (planeCalibElement->Attribute("planeHeight"))
|
||||
cam.planeCalibParam.planeHeight = planeCalibElement->DoubleAttribute("planeHeight");
|
||||
if (planeCalibElement->Attribute("planeHeightOffset"))
|
||||
cam.planeCalibParam.planeHeightOffset = planeCalibElement->DoubleAttribute("planeHeightOffset");
|
||||
|
||||
ParseMatrix3x3(planeCalibElement, "planeCalib", cam.planeCalibParam.planeCalib);
|
||||
ParseMatrix3x3(planeCalibElement, "invRMatrix", cam.planeCalibParam.invRMatrix);
|
||||
@ -258,17 +260,20 @@ void CVrConfig::SaveWorkPositions(XMLDocument& doc, XMLElement* root, const std:
|
||||
if (!cam.defaultPackageId.empty())
|
||||
cameraElement->SetAttribute("defaultPackageId", cam.defaultPackageId.c_str());
|
||||
|
||||
// 保存平面校准参数
|
||||
if (cam.planeCalibParam.isCalibrated)
|
||||
// 保存平面校准参数(即使未校准也保存planeHeightOffset)
|
||||
{
|
||||
XMLElement* planeCalibElement = doc.NewElement("PlaneCalib");
|
||||
planeCalibElement->SetAttribute("cameraIndex", cam.planeCalibParam.cameraIndex);
|
||||
planeCalibElement->SetAttribute("cameraName", cam.planeCalibParam.cameraName.c_str());
|
||||
planeCalibElement->SetAttribute("isCalibrated", cam.planeCalibParam.isCalibrated);
|
||||
planeCalibElement->SetAttribute("planeHeight", cam.planeCalibParam.planeHeight);
|
||||
planeCalibElement->SetAttribute("planeHeightOffset", cam.planeCalibParam.planeHeightOffset);
|
||||
|
||||
SaveMatrix3x3(planeCalibElement, "planeCalib", cam.planeCalibParam.planeCalib);
|
||||
SaveMatrix3x3(planeCalibElement, "invRMatrix", cam.planeCalibParam.invRMatrix);
|
||||
if (cam.planeCalibParam.isCalibrated)
|
||||
{
|
||||
SaveMatrix3x3(planeCalibElement, "planeCalib", cam.planeCalibParam.planeCalib);
|
||||
SaveMatrix3x3(planeCalibElement, "invRMatrix", cam.planeCalibParam.invRMatrix);
|
||||
}
|
||||
|
||||
cameraElement->InsertEndChild(planeCalibElement);
|
||||
}
|
||||
|
||||
@ -279,6 +279,7 @@
|
||||
<PostCoordinateDsID>www.hc-system.com.RemoteMonitor</PostCoordinateDsID>
|
||||
<PostCoordinateAddress>800</PostCoordinateAddress>
|
||||
<PostCoordinateSuccessValue>10</PostCoordinateSuccessValue>
|
||||
<PostCoordinateFailValue>12</PostCoordinateFailValue>
|
||||
<PostCoordinateEmptyValue>12</PostCoordinateEmptyValue>
|
||||
<PostCoordinateStartValue>13</PostCoordinateStartValue>
|
||||
</BolunteConfig>
|
||||
</GrabBagConfig>
|
||||
@ -96,10 +96,9 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// 为第二个图像视图初始化GraphicsScene
|
||||
QGraphicsScene* scene2 = new QGraphicsScene(this);
|
||||
ui->detect_image_2->setScene(scene2);
|
||||
|
||||
// 初始化日志模型
|
||||
m_logModel = new QStringListModel(this);
|
||||
ui->detect_log->setModel(m_logModel);
|
||||
|
||||
// 初始化日志辅助类
|
||||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
|
||||
// 注册自定义类型,使其能够在信号槽中跨线程传递
|
||||
qRegisterMetaType<DetectionResult>("DetectionResult");
|
||||
@ -108,16 +107,10 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
// 连接工作状态更新信号槽
|
||||
connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel);
|
||||
|
||||
|
||||
// 连接检测结果更新信号槽
|
||||
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
|
||||
|
||||
// 连接日志更新信号槽
|
||||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||||
|
||||
// 连接清空日志信号槽
|
||||
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
|
||||
|
||||
|
||||
// 初始化右键菜单
|
||||
setupContextMenu();
|
||||
|
||||
@ -143,17 +136,18 @@ MainWindow::~MainWindow()
|
||||
|
||||
void MainWindow::updateStatusLog(const QString& message)
|
||||
{
|
||||
// 更新状态栏
|
||||
// statusBar()->showMessage(message);
|
||||
|
||||
// 通过信号槽机制更新detect_log控件
|
||||
emit logUpdateRequested(message);
|
||||
// 使用日志辅助类更新日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->appendLog(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLog()
|
||||
{
|
||||
// 通过信号槽机制清空日志
|
||||
emit logClearRequested();
|
||||
// 使用日志辅助类清空日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->clearLog();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Init()
|
||||
@ -447,70 +441,18 @@ void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
|
||||
// 根据相机索引决定显示在哪个图像视图上
|
||||
if (result.cameraIndex == 2) {
|
||||
// 第二个相机的结果显示在detect_image_2上
|
||||
updateDetectionLog(tr("相机2检测结果更新"));
|
||||
updateStatusLog(tr("相机2检测结果更新"));
|
||||
displayImageInSecondView(result.image);
|
||||
} else {
|
||||
// 第一个相机或默认显示在detect_image上
|
||||
updateDetectionLog(tr("相机1检测结果更新"));
|
||||
updateStatusLog(tr("相机1检测结果更新"));
|
||||
displayImage(result.image);
|
||||
}
|
||||
|
||||
|
||||
// 更新检测结果到列表
|
||||
addDetectionResult(result);
|
||||
}
|
||||
|
||||
void MainWindow::updateDetectionLog(const QString& message)
|
||||
{
|
||||
// 在UI线程中更新detect_log控件(QListView)
|
||||
if (!m_logModel) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 检查是否与最后一条消息相同
|
||||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
|
||||
// 替换最后一条
|
||||
logList[logList.size() - 1] = logEntry;
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
|
||||
// 添加新的日志条目
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
ui->detect_log->scrollTo(lastIndex);
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLogUI()
|
||||
{
|
||||
// 在UI线程中清空检测日志
|
||||
if (m_logModel) {
|
||||
m_logModel->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// 重置日志计数器
|
||||
m_lastLogMessage.clear();
|
||||
m_lastLogCount = 0;
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_start_clicked()
|
||||
{
|
||||
// 检查Presenter是否已初始化
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "dialogalgoarg.h"
|
||||
#include "CommonDialogCameraLevel.h"
|
||||
#include "DeviceStatusWidget.h"
|
||||
#include "DetectLogHelper.h"
|
||||
#include "ParticleSizePresenter.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -47,30 +48,18 @@ public:
|
||||
signals:
|
||||
// 工作状态更新信号
|
||||
void workStatusUpdateRequested(WorkStatus status);
|
||||
|
||||
|
||||
// 检测结果更新信号
|
||||
void detectionResultUpdateRequested(const DetectionResult& result);
|
||||
|
||||
// 日志更新信号
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
// 清空日志信号
|
||||
void logClearRequested();
|
||||
|
||||
private slots:
|
||||
|
||||
// 工作状态更新槽函数
|
||||
void updateWorkStatusLabel(WorkStatus status);
|
||||
|
||||
|
||||
// 检测结果更新槽函数
|
||||
void updateDetectionResultDisplay(const DetectionResult& result);
|
||||
|
||||
// 日志更新槽函数
|
||||
void updateDetectionLog(const QString& message);
|
||||
|
||||
// 清空日志槽函数
|
||||
void clearDetectionLogUI();
|
||||
|
||||
|
||||
// 处理相机点击事件
|
||||
void onCameraClicked(int cameraIndex);
|
||||
|
||||
@ -102,10 +91,10 @@ private:
|
||||
|
||||
// 业务逻辑处理类
|
||||
ParticleSizePresenter* m_presenter = nullptr;
|
||||
|
||||
// 日志模型
|
||||
QStringListModel* m_logModel = nullptr;
|
||||
|
||||
|
||||
// 日志辅助类
|
||||
DetectLogHelper* m_logHelper = nullptr;
|
||||
|
||||
// 设备状态显示widget的引用
|
||||
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
|
||||
|
||||
@ -148,9 +137,5 @@ private:
|
||||
|
||||
private:
|
||||
QLabel* m_cpuSerialLabel = nullptr; // CPU序列号标签
|
||||
|
||||
// 日志去重相关
|
||||
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
|
||||
int m_lastLogCount = 0; // 最后一条日志的重复次数
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@ -83,9 +83,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
QGraphicsScene* scene = new QGraphicsScene(this);
|
||||
ui->detect_image->setScene(scene);
|
||||
|
||||
// 初始化日志模型
|
||||
m_logModel = new QStringListModel(this);
|
||||
ui->detect_log->setModel(m_logModel);
|
||||
// 初始化日志辅助类
|
||||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
|
||||
// 注册自定义类型,使其能够在信号槽中跨线程传递
|
||||
qRegisterMetaType<DetectionResult>("DetectionResult");
|
||||
@ -98,12 +97,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// 连接检测结果更新信号槽
|
||||
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
|
||||
|
||||
// 连接日志更新信号槽
|
||||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||||
|
||||
// 连接清空日志信号槽
|
||||
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
|
||||
|
||||
// 初始化右键菜单
|
||||
setupContextMenu();
|
||||
|
||||
@ -129,14 +122,18 @@ MainWindow::~MainWindow()
|
||||
|
||||
void MainWindow::updateStatusLog(const QString& message)
|
||||
{
|
||||
// 通过信号槽机制更新detect_log控件
|
||||
emit logUpdateRequested(message);
|
||||
// 使用日志辅助类更新日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->appendLog(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLog()
|
||||
{
|
||||
// 通过信号槽机制清空日志
|
||||
emit logClearRequested();
|
||||
// 使用日志辅助类清空日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->clearLog();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Init()
|
||||
@ -374,65 +371,13 @@ void MainWindow::updateWorkStatusLabel(WorkStatus status)
|
||||
void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
|
||||
{
|
||||
// 显示检测结果图像
|
||||
updateDetectionLog(tr("检测结果更新"));
|
||||
updateStatusLog(tr("检测结果更新"));
|
||||
displayImage(result.image);
|
||||
|
||||
// 更新检测结果到列表
|
||||
addDetectionResult(result);
|
||||
}
|
||||
|
||||
void MainWindow::updateDetectionLog(const QString& message)
|
||||
{
|
||||
// 在UI线程中更新detect_log控件(QListView)
|
||||
if (!m_logModel) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 检查是否与最后一条消息相同
|
||||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
|
||||
// 替换最后一条
|
||||
logList[logList.size() - 1] = logEntry;
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
|
||||
// 添加新的日志条目
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
ui->detect_log->scrollTo(lastIndex);
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLogUI()
|
||||
{
|
||||
// 在UI线程中清空检测日志
|
||||
if (m_logModel) {
|
||||
m_logModel->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// 重置日志计数器
|
||||
m_lastLogMessage.clear();
|
||||
m_lastLogCount = 0;
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_start_clicked()
|
||||
{
|
||||
// 检查Presenter是否已初始化
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "dialogalgoarg.h"
|
||||
#include "CommonDialogCameraLevel.h"
|
||||
#include "DeviceStatusWidget.h"
|
||||
#include "DetectLogHelper.h"
|
||||
#include "ScrewPositionPresenter.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -51,12 +52,6 @@ signals:
|
||||
// 检测结果更新信号
|
||||
void detectionResultUpdateRequested(const DetectionResult& result);
|
||||
|
||||
// 日志更新信号
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
// 清空日志信号
|
||||
void logClearRequested();
|
||||
|
||||
private slots:
|
||||
|
||||
// 工作状态更新槽函数
|
||||
@ -65,12 +60,6 @@ private slots:
|
||||
// 检测结果更新槽函数
|
||||
void updateDetectionResultDisplay(const DetectionResult& result);
|
||||
|
||||
// 日志更新槽函数
|
||||
void updateDetectionLog(const QString& message);
|
||||
|
||||
// 清空日志槽函数
|
||||
void clearDetectionLogUI();
|
||||
|
||||
// 处理相机点击事件
|
||||
void onCameraClicked(int cameraIndex);
|
||||
|
||||
@ -103,8 +92,8 @@ private:
|
||||
// 业务逻辑处理类
|
||||
ScrewPositionPresenter* m_presenter = nullptr;
|
||||
|
||||
// 日志模型
|
||||
QStringListModel* m_logModel = nullptr;
|
||||
// 日志辅助类
|
||||
DetectLogHelper* m_logHelper = nullptr;
|
||||
|
||||
// 设备状态显示widget的引用
|
||||
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
|
||||
@ -134,10 +123,5 @@ private:
|
||||
void setupContextMenu();
|
||||
void showContextMenu(const QPoint& pos, QGraphicsView* view);
|
||||
bool saveDetectionDataToFile(const QString& filePath, int cameraIndex);
|
||||
|
||||
private:
|
||||
// 日志去重相关
|
||||
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
|
||||
int m_lastLogCount = 0; // 最后一条日志的重复次数
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@ -86,12 +86,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// 设置版本信息显示
|
||||
setupVersionDisplay();
|
||||
|
||||
// 初始化日志模型
|
||||
m_logModel = new QStringListModel(this);
|
||||
ui->detect_log->setModel(m_logModel);
|
||||
|
||||
// 连接日志更新信号
|
||||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||||
// 初始化日志辅助类
|
||||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
|
||||
m_presenter->Init();
|
||||
}
|
||||
@ -197,18 +193,18 @@ void MainWindow::on_btn_test_clicked()
|
||||
m_measureResultWidget->clearAllResults();
|
||||
}
|
||||
|
||||
// emit logUpdateRequested(QString("正在加载调试数据: %1").arg(fileName));
|
||||
// if (m_logHelper) m_logHelper->appendLog(QString("正在加载调试数据: %1").arg(fileName));
|
||||
|
||||
// 在后台线程中执行加载和检测
|
||||
std::thread t([this, fileName]() {
|
||||
int result = m_presenter->LoadDebugDataAndDetect(fileName.toStdString());
|
||||
if (result == 0) {
|
||||
QMetaObject::invokeMethod(this, [this]() {
|
||||
emit logUpdateRequested("调试数据加载和检测成功");
|
||||
if (m_logHelper) m_logHelper->appendLog("调试数据加载和检测成功");
|
||||
}, Qt::QueuedConnection);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(this, [this, result]() {
|
||||
emit logUpdateRequested(QString("调试数据复检失败: %1").arg(result));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("调试数据复检失败: %1").arg(result));
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
});
|
||||
@ -224,7 +220,7 @@ void MainWindow::on_btn_start_clicked()
|
||||
|
||||
// 启动所有相机的检测
|
||||
m_presenter->StartAllDetection();
|
||||
emit logUpdateRequested("已启动所有相机检测");
|
||||
if (m_logHelper) m_logHelper->appendLog("已启动所有相机检测");
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_stop_clicked()
|
||||
@ -235,7 +231,7 @@ void MainWindow::on_btn_stop_clicked()
|
||||
|
||||
// 停止所有相机的检测
|
||||
m_presenter->StopAllDetection();
|
||||
emit logUpdateRequested("已停止所有相机检测");
|
||||
if (m_logHelper) m_logHelper->appendLog("已停止所有相机检测");
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_camera_config_clicked()
|
||||
@ -341,7 +337,7 @@ void MainWindow::onDeviceClicked(const QString& deviceName)
|
||||
}
|
||||
|
||||
if (foundIndex < 0) {
|
||||
emit logUpdateRequested(QString("未找到相机: %1").arg(deviceName));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("未找到相机: %1").arg(deviceName));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -352,7 +348,7 @@ void MainWindow::onDeviceClicked(const QString& deviceName)
|
||||
|
||||
// 直接开始检测
|
||||
m_presenter->ResetDetect(foundIndex);
|
||||
emit logUpdateRequested(QString("已启动相机 \"%1\" 的检测").arg(deviceName));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("已启动相机 \"%1\" 的检测").arg(deviceName));
|
||||
}
|
||||
|
||||
void MainWindow::onConfigSaved()
|
||||
@ -374,7 +370,7 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
|
||||
|
||||
// 检查点击的是否是当前检测的相机
|
||||
if (index != currentDetectIndex) {
|
||||
emit logUpdateRequested(QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||||
StyledMessageBox::information(this, "提示",
|
||||
QString("设备 \"%1\" 无缓存数据\n\n当前缓存的是设备 %2 的数据")
|
||||
.arg(alias)
|
||||
@ -385,7 +381,7 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
|
||||
// 检查是否有缓存数据
|
||||
int cacheSize = m_presenter->GetDetectionDataCacheSize();
|
||||
if (cacheSize <= 0) {
|
||||
emit logUpdateRequested(QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||||
StyledMessageBox::information(this, "提示", QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||||
return;
|
||||
}
|
||||
@ -409,10 +405,10 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
|
||||
// 调用Presenter保存数据
|
||||
int result = m_presenter->SaveDetectionDataToFile(filePath.toStdString());
|
||||
if (result == 0) {
|
||||
emit logUpdateRequested(QString("检测数据已保存: %1").arg(filePath));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("检测数据已保存: %1").arg(filePath));
|
||||
StyledMessageBox::information(this, "成功", QString("检测数据已保存到:\n%1").arg(filePath));
|
||||
} else {
|
||||
emit logUpdateRequested(QString("保存检测数据失败: %1").arg(result));
|
||||
if (m_logHelper) m_logHelper->appendLog(QString("保存检测数据失败: %1").arg(result));
|
||||
StyledMessageBox::warning(this, "失败", QString("保存检测数据失败,错误码: %1").arg(result));
|
||||
}
|
||||
}
|
||||
@ -420,7 +416,7 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
|
||||
// IWheelMeasureStatus接口方法实现
|
||||
void MainWindow::OnStatusUpdate(const QString &statusMessage)
|
||||
{
|
||||
emit logUpdateRequested(statusMessage);
|
||||
if (m_logHelper) m_logHelper->appendLog(statusMessage);
|
||||
LOG_DEBUG("Status update: %s \n", statusMessage.toStdString().c_str());
|
||||
}
|
||||
|
||||
@ -477,14 +473,14 @@ void MainWindow::OnMeasureResult(const WheelMeasureResult &result)
|
||||
void MainWindow::OnCameraConnected(const QString &cameraName)
|
||||
{
|
||||
QString message = QString("相机已连接: %1").arg(cameraName);
|
||||
emit logUpdateRequested(message);
|
||||
if (m_logHelper) m_logHelper->appendLog(message);
|
||||
LOG_DEBUG("%s \n", message.toStdString().c_str());
|
||||
}
|
||||
|
||||
void MainWindow::OnCameraDisconnected(const QString &cameraName)
|
||||
{
|
||||
QString message = QString("相机断开连接: %1").arg(cameraName);
|
||||
emit logUpdateRequested(message);
|
||||
if (m_logHelper) m_logHelper->appendLog(message);
|
||||
LOG_DEBUG("%s \n", message.toStdString().c_str());
|
||||
}
|
||||
|
||||
@ -516,13 +512,13 @@ void MainWindow::OnWorkStatusChangedImpl(WorkStatus status)
|
||||
}
|
||||
|
||||
QString message = QString("工作状态: %1").arg(statusStr);
|
||||
emit logUpdateRequested(message);
|
||||
if (m_logHelper) m_logHelper->appendLog(message);
|
||||
LOG_DEBUG("%s \n", message.toStdString().c_str());
|
||||
}
|
||||
|
||||
void MainWindow::OnErrorOccurred(const QString &errorMessage)
|
||||
{
|
||||
emit logUpdateRequested("错误: " + errorMessage);
|
||||
if (m_logHelper) m_logHelper->appendLog("错误: " + errorMessage);
|
||||
LOG_ERROR("Error occurred: %s \n", errorMessage.toStdString().c_str());
|
||||
}
|
||||
|
||||
@ -563,43 +559,3 @@ void MainWindow::setupVersionDisplay()
|
||||
|
||||
LOG_INFO("Version display initialized: %s\n", versionText.toStdString().c_str());
|
||||
}
|
||||
|
||||
void MainWindow::updateDetectionLog(const QString& message)
|
||||
{
|
||||
// 在UI线程中更新detect_log控件(QListView)
|
||||
if (!m_logModel) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 获取当前时间
|
||||
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss");
|
||||
|
||||
// 检查是否与最后一条消息相同
|
||||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
QString lastEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
logList.replace(logList.size() - 1, lastEntry);
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
// 限制日志条数,最多保留200条
|
||||
if (logList.size() >= 200) {
|
||||
logList.removeFirst();
|
||||
}
|
||||
|
||||
// 添加新的日志条目
|
||||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
ui->detect_log->scrollTo(lastIndex);
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <QStringListModel>
|
||||
#include "Presenter/Inc/WheelMeasurePresenter.h"
|
||||
#include "IWheelMeasureStatus.h"
|
||||
#include "DetectLogHelper.h"
|
||||
|
||||
// 前向声明widgets
|
||||
class ImageGridWidget;
|
||||
@ -30,9 +31,6 @@ class MainWindow : public QMainWindow, public IWheelMeasureStatus
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
@ -64,7 +62,6 @@ private slots:
|
||||
void onConfigSaved();
|
||||
void onDeviceClicked(const QString& deviceName);
|
||||
void onTileRightClicked(int index, const QString& alias);
|
||||
void updateDetectionLog(const QString& message);
|
||||
|
||||
private:
|
||||
void setupVersionDisplay();
|
||||
@ -80,10 +77,8 @@ private:
|
||||
WheelMeasurePresenter* m_presenter = nullptr;
|
||||
QLabel* m_versionLabel = nullptr;
|
||||
|
||||
// 日志相关
|
||||
QStringListModel* m_logModel = nullptr;
|
||||
QString m_lastLogMessage;
|
||||
int m_lastLogCount = 0;
|
||||
// 日志辅助类
|
||||
DetectLogHelper* m_logHelper = nullptr;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@ -95,10 +95,9 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// 为第二个图像视图初始化GraphicsScene
|
||||
QGraphicsScene* scene2 = new QGraphicsScene(this);
|
||||
ui->detect_image_2->setScene(scene2);
|
||||
|
||||
// 初始化日志模型
|
||||
m_logModel = new QStringListModel(this);
|
||||
ui->detect_log->setModel(m_logModel);
|
||||
|
||||
// 初始化日志辅助类
|
||||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
|
||||
// 注册自定义类型,使其能够在信号槽中跨线程传递
|
||||
qRegisterMetaType<DetectionResult>("DetectionResult");
|
||||
@ -107,16 +106,10 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
// 连接工作状态更新信号槽
|
||||
connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel);
|
||||
|
||||
|
||||
// 连接检测结果更新信号槽
|
||||
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
|
||||
|
||||
// 连接日志更新信号槽
|
||||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||||
|
||||
// 连接清空日志信号槽
|
||||
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
|
||||
|
||||
|
||||
// 初始化右键菜单
|
||||
setupContextMenu();
|
||||
|
||||
@ -139,17 +132,18 @@ MainWindow::~MainWindow()
|
||||
|
||||
void MainWindow::updateStatusLog(const QString& message)
|
||||
{
|
||||
// 更新状态栏
|
||||
// statusBar()->showMessage(message);
|
||||
|
||||
// 通过信号槽机制更新detect_log控件
|
||||
emit logUpdateRequested(message);
|
||||
// 使用日志辅助类更新日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->appendLog(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLog()
|
||||
{
|
||||
// 通过信号槽机制清空日志
|
||||
emit logClearRequested();
|
||||
// 使用日志辅助类清空日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->clearLog();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Init()
|
||||
@ -497,70 +491,18 @@ void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
|
||||
// 根据相机索引决定显示在哪个图像视图上
|
||||
if (result.cameraIndex == 2) {
|
||||
// 第二个相机的结果显示在detect_image_2上
|
||||
updateDetectionLog(tr("相机2检测结果更新"));
|
||||
updateStatusLog(tr("相机2检测结果更新"));
|
||||
displayImageInSecondView(result.image);
|
||||
} else {
|
||||
// 第一个相机或默认显示在detect_image上
|
||||
updateDetectionLog(tr("相机1检测结果更新"));
|
||||
updateStatusLog(tr("相机1检测结果更新"));
|
||||
displayImage(result.image);
|
||||
}
|
||||
|
||||
|
||||
// 更新检测结果到列表
|
||||
addDetectionResult(result);
|
||||
}
|
||||
|
||||
void MainWindow::updateDetectionLog(const QString& message)
|
||||
{
|
||||
// 在UI线程中更新detect_log控件(QListView)
|
||||
if (!m_logModel) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 检查是否与最后一条消息相同
|
||||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
|
||||
// 替换最后一条
|
||||
logList[logList.size() - 1] = logEntry;
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
|
||||
// 添加新的日志条目
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
ui->detect_log->scrollTo(lastIndex);
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLogUI()
|
||||
{
|
||||
// 在UI线程中清空检测日志
|
||||
if (m_logModel) {
|
||||
m_logModel->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// 重置日志计数器
|
||||
m_lastLogMessage.clear();
|
||||
m_lastLogCount = 0;
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_start_clicked()
|
||||
{
|
||||
// 检查Presenter是否已初始化
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "dialogalgoarg.h"
|
||||
#include "CommonDialogCameraLevel.h"
|
||||
#include "DeviceStatusWidget.h"
|
||||
#include "DetectLogHelper.h"
|
||||
#include "WorkpiecePresenter.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -47,30 +48,18 @@ public:
|
||||
signals:
|
||||
// 工作状态更新信号
|
||||
void workStatusUpdateRequested(WorkStatus status);
|
||||
|
||||
|
||||
// 检测结果更新信号
|
||||
void detectionResultUpdateRequested(const DetectionResult& result);
|
||||
|
||||
// 日志更新信号
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
// 清空日志信号
|
||||
void logClearRequested();
|
||||
|
||||
private slots:
|
||||
|
||||
// 工作状态更新槽函数
|
||||
void updateWorkStatusLabel(WorkStatus status);
|
||||
|
||||
|
||||
// 检测结果更新槽函数
|
||||
void updateDetectionResultDisplay(const DetectionResult& result);
|
||||
|
||||
// 日志更新槽函数
|
||||
void updateDetectionLog(const QString& message);
|
||||
|
||||
// 清空日志槽函数
|
||||
void clearDetectionLogUI();
|
||||
|
||||
|
||||
// 处理相机点击事件
|
||||
void onCameraClicked(int cameraIndex);
|
||||
|
||||
@ -102,10 +91,10 @@ private:
|
||||
|
||||
// 业务逻辑处理类
|
||||
WorkpiecePresenter* m_presenter = nullptr;
|
||||
|
||||
// 日志模型
|
||||
QStringListModel* m_logModel = nullptr;
|
||||
|
||||
|
||||
// 日志辅助类
|
||||
DetectLogHelper* m_logHelper = nullptr;
|
||||
|
||||
// 设备状态显示widget的引用
|
||||
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
|
||||
|
||||
@ -148,9 +137,5 @@ private:
|
||||
|
||||
private:
|
||||
QLabel* m_cpuSerialLabel = nullptr; // CPU序列号标签
|
||||
|
||||
// 日志去重相关
|
||||
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
|
||||
int m_lastLogCount = 0; // 最后一条日志的重复次数
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@ -97,9 +97,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
QGraphicsScene* scene2 = new QGraphicsScene(this);
|
||||
ui->detect_image_2->setScene(scene2);
|
||||
|
||||
// 初始化日志模型
|
||||
m_logModel = new QStringListModel(this);
|
||||
ui->detect_log->setModel(m_logModel);
|
||||
// 初始化日志辅助类
|
||||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
|
||||
// 注册自定义类型,使其能够在信号槽中跨线程传递
|
||||
qRegisterMetaType<WorkpieceHoleDetectionResult>("WorkpieceHoleDetectionResult");
|
||||
@ -112,12 +111,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// 连接检测结果更新信号槽
|
||||
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
|
||||
|
||||
// 连接日志更新信号槽
|
||||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||||
|
||||
// 连接清空日志信号槽
|
||||
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
|
||||
|
||||
// 初始化右键菜单
|
||||
setupContextMenu();
|
||||
|
||||
@ -140,17 +133,18 @@ MainWindow::~MainWindow()
|
||||
|
||||
void MainWindow::updateStatusLog(const QString& message)
|
||||
{
|
||||
// 更新状态栏
|
||||
// statusBar()->showMessage(message);
|
||||
|
||||
// 通过信号槽机制更新detect_log控件
|
||||
emit logUpdateRequested(message);
|
||||
// 使用日志辅助类更新日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->appendLog(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLog()
|
||||
{
|
||||
// 通过信号槽机制清空日志
|
||||
emit logClearRequested();
|
||||
// 使用日志辅助类清空日志
|
||||
if (m_logHelper) {
|
||||
m_logHelper->clearLog();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Init()
|
||||
@ -403,11 +397,11 @@ void MainWindow::updateDetectionResultDisplay(const WorkpieceHoleDetectionResult
|
||||
// 根据相机索引决定显示在哪个图像视图上
|
||||
if (result.cameraIndex == 2) {
|
||||
// 第二个相机的结果显示在detect_image_2上
|
||||
updateDetectionLog(tr("相机2检测结果更新"));
|
||||
updateStatusLog(tr("相机2检测结果更新"));
|
||||
displayImageInSecondView(result.image);
|
||||
} else {
|
||||
// 第一个相机或默认显示在detect_image上
|
||||
updateDetectionLog(tr("相机1检测结果更新"));
|
||||
updateStatusLog(tr("相机1检测结果更新"));
|
||||
displayImage(result.image);
|
||||
}
|
||||
|
||||
@ -415,58 +409,6 @@ void MainWindow::updateDetectionResultDisplay(const WorkpieceHoleDetectionResult
|
||||
addDetectionResult(result);
|
||||
}
|
||||
|
||||
void MainWindow::updateDetectionLog(const QString& message)
|
||||
{
|
||||
// 在UI线程中更新detect_log控件(QListView)
|
||||
if (!m_logModel) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 检查是否与最后一条消息相同
|
||||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
|
||||
// 替换最后一条
|
||||
logList[logList.size() - 1] = logEntry;
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
// 添加时间戳
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
|
||||
// 添加新的日志条目
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
ui->detect_log->scrollTo(lastIndex);
|
||||
}
|
||||
|
||||
void MainWindow::clearDetectionLogUI()
|
||||
{
|
||||
// 在UI线程中清空检测日志
|
||||
if (m_logModel) {
|
||||
m_logModel->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// 重置日志计数器
|
||||
m_lastLogMessage.clear();
|
||||
m_lastLogCount = 0;
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_start_clicked()
|
||||
{
|
||||
// 检查Presenter是否已初始化
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "dialogalgoarg.h"
|
||||
#include "CommonDialogCameraLevel.h"
|
||||
#include "DeviceStatusWidget.h"
|
||||
#include "DetectLogHelper.h"
|
||||
#include "WorkpieceHolePresenter.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -51,12 +52,6 @@ signals:
|
||||
// 检测结果更新信号
|
||||
void detectionResultUpdateRequested(const WorkpieceHoleDetectionResult& result);
|
||||
|
||||
// 日志更新信号
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
// 清空日志信号
|
||||
void logClearRequested();
|
||||
|
||||
private slots:
|
||||
|
||||
// 工作状态更新槽函数
|
||||
@ -65,12 +60,6 @@ private slots:
|
||||
// 检测结果更新槽函数
|
||||
void updateDetectionResultDisplay(const WorkpieceHoleDetectionResult& result);
|
||||
|
||||
// 日志更新槽函数
|
||||
void updateDetectionLog(const QString& message);
|
||||
|
||||
// 清空日志槽函数
|
||||
void clearDetectionLogUI();
|
||||
|
||||
// 处理相机点击事件
|
||||
void onCameraClicked(int cameraIndex);
|
||||
|
||||
@ -103,8 +92,8 @@ private:
|
||||
// 业务逻辑处理类
|
||||
WorkpieceHolePresenter* m_presenter = nullptr;
|
||||
|
||||
// 日志模型
|
||||
QStringListModel* m_logModel = nullptr;
|
||||
// 日志辅助类
|
||||
DetectLogHelper* m_logHelper = nullptr;
|
||||
|
||||
// 设备状态显示widget的引用
|
||||
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
|
||||
@ -148,9 +137,5 @@ private:
|
||||
|
||||
private:
|
||||
QLabel* m_cpuSerialLabel = nullptr; // CPU序列号标签
|
||||
|
||||
// 日志去重相关
|
||||
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
|
||||
int m_lastLogCount = 0; // 最后一条日志的重复次数
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@ -25,6 +25,7 @@ INCLUDEPATH += $$PWD/../../SDK/Device/VzNLSDK/Inc
|
||||
INCLUDEPATH += $$PWD/../../Module/ShareMem/Inc # ConfigMonitor
|
||||
INCLUDEPATH += $$PWD/../../Module/ModbusTCPServer/Inc # ModbusTCP服务器接口
|
||||
INCLUDEPATH += $$PWD/../../Module/AuthModule/Inc # 授权模块
|
||||
INCLUDEPATH += $$PWD/../../VrNets/TCPServer/Inc # TCP服务器接口
|
||||
|
||||
# 头文件
|
||||
HEADERS += \
|
||||
@ -35,7 +36,9 @@ HEADERS += \
|
||||
Inc/VrCommonConfig.h \
|
||||
Inc/ConfigMonitor.h \
|
||||
Inc/BaseConfigManager.h \
|
||||
Inc/ConfigEncryption.h
|
||||
Inc/ConfigEncryption.h \
|
||||
Inc/ProtocolCommon.h \
|
||||
Inc/TCPServerProtocol.h
|
||||
|
||||
# 源文件
|
||||
SOURCES += \
|
||||
@ -43,7 +46,8 @@ SOURCES += \
|
||||
Src/PathManager.cpp \
|
||||
Src/BasePresenter.cpp \
|
||||
Src/ConfigMonitor.cpp \
|
||||
Src/ConfigEncryption.cpp
|
||||
Src/ConfigEncryption.cpp \
|
||||
Src/TCPServerProtocol.cpp
|
||||
# 注意: BaseConfigManager.cpp 不在这里编译
|
||||
# 它需要在各个应用中编译,因为它依赖应用特定的 IVrConfig.h
|
||||
|
||||
|
||||
72
AppUtils/AppCommon/Inc/ProtocolCommon.h
Normal file
72
AppUtils/AppCommon/Inc/ProtocolCommon.h
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef PROTOCOLCOMMON_H
|
||||
#define PROTOCOLCOMMON_H
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief 机械臂坐标结构体
|
||||
*/
|
||||
struct RobotCoordinate {
|
||||
float x; // X坐标
|
||||
float y; // Y坐标
|
||||
float z; // Z坐标
|
||||
float rx; // X轴旋转
|
||||
float ry; // Y轴旋转
|
||||
float rz; // Z轴旋转
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单个目标位置数据(简化版,只包含必要的x,y,z,rz)
|
||||
*/
|
||||
struct TargetPosition {
|
||||
float x; // X坐标
|
||||
float y; // Y坐标
|
||||
float z; // Z坐标
|
||||
float rz; // Z轴旋转(yaw角)
|
||||
uint16_t cameraId; // 相机ID(从1开始编号)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 多目标检测结果数据结构
|
||||
*/
|
||||
struct MultiTargetData {
|
||||
uint16_t count; // 目标数量
|
||||
uint16_t cameraId; // 当前检测的相机ID(从1开始编号)
|
||||
std::vector<TargetPosition> targets; // 目标位置列表
|
||||
|
||||
MultiTargetData() : count(0), cameraId(1) {} // 默认相机ID为1
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 通用连接状态枚举
|
||||
*/
|
||||
enum ConnectionStatus {
|
||||
STATUS_DISCONNECTED = 0, // 断开连接
|
||||
STATUS_CONNECTED = 1, // 已连接
|
||||
STATUS_IDLE = 2, // 空闲状态
|
||||
STATUS_WORKING = 3, // 工作中
|
||||
STATUS_ERROR = 4 // 错误状态
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 连接状态回调函数类型
|
||||
* @param connected true-连接,false-断开
|
||||
*/
|
||||
using ConnectionCallback = std::function<void(bool connected)>;
|
||||
|
||||
/**
|
||||
* @brief 开始工作信号回调函数类型
|
||||
* @param startWork true-开始工作,false-停止工作
|
||||
* @param cameraId 相机ID,从1开始编号(1,2,...)
|
||||
*/
|
||||
using WorkSignalCallback = std::function<bool(bool startWork, int cameraId)>;
|
||||
|
||||
// 工作状态定义(公共常量)
|
||||
static const uint16_t WORK_STATUS_IDLE = 0; // 空闲
|
||||
static const uint16_t WORK_STATUS_CAMERA1_DONE = 1; // 相机1工作完成
|
||||
static const uint16_t WORK_STATUS_CAMERA2_DONE = 2; // 相机2工作完成
|
||||
static const uint16_t WORK_STATUS_BUSY = 3; // 忙碌
|
||||
|
||||
#endif // PROTOCOLCOMMON_H
|
||||
151
AppUtils/AppCommon/Inc/TCPServerProtocol.h
Normal file
151
AppUtils/AppCommon/Inc/TCPServerProtocol.h
Normal file
@ -0,0 +1,151 @@
|
||||
#ifndef TCPSERVERPROTOCOL_H
|
||||
#define TCPSERVERPROTOCOL_H
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include "IYTCPServer.h"
|
||||
#include "ProtocolCommon.h"
|
||||
|
||||
/**
|
||||
* @brief TCP服务器协议封装类
|
||||
*
|
||||
* 通用的TCP/IP通信协议基类,支持:
|
||||
* - 服务端模式,等待客户端连接
|
||||
* - JSON格式数据交换
|
||||
* - 支持start_detection/ScanRequest指令
|
||||
* - 返回检测结果
|
||||
*/
|
||||
class TCPServerProtocol
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 检测结果结构体
|
||||
*/
|
||||
struct DetectionResultData {
|
||||
int code = 0; // 响应码:0-成功,其他-错误
|
||||
bool success = true; // 是否成功
|
||||
QString message = "检测成功"; // 响应消息
|
||||
qint64 timestamp = 0; // 时间戳(毫秒)
|
||||
std::vector<std::vector<QJsonObject>> result; // 检测结果:3D坐标数组
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 连接状态枚举(使用公共状态定义)
|
||||
*/
|
||||
using TCPStatus = ConnectionStatus;
|
||||
|
||||
/**
|
||||
* @brief 检测结果回调函数类型
|
||||
* @param success 是否成功触发检测
|
||||
* @param cameraIndex 相机索引(从开始检测指令中解析,-1表示所有相机)
|
||||
* @param timestamp 请求时间戳
|
||||
*/
|
||||
using DetectionTriggerCallback = std::function<bool(bool success, int cameraIndex, qint64 timestamp)>;
|
||||
|
||||
public:
|
||||
TCPServerProtocol();
|
||||
virtual ~TCPServerProtocol();
|
||||
|
||||
/**
|
||||
* @brief 初始化TCP服务器
|
||||
* @param port TCP端口号,默认5020
|
||||
* @return 0-成功,其他-错误码
|
||||
*/
|
||||
int Initialize(uint16_t port = 5020);
|
||||
|
||||
/**
|
||||
* @brief 反初始化,停止服务
|
||||
*/
|
||||
void Deinitialize();
|
||||
|
||||
/**
|
||||
* @brief 发送检测结果给客户端
|
||||
* @param result 检测结果数据
|
||||
* @param pClient 目标客户端,如果为nullptr则发送给所有客户端
|
||||
* @return 0-成功,其他-错误码
|
||||
*/
|
||||
int SendDetectionResult(const QJsonObject& result, const TCPClient* pClient = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 获取当前连接状态
|
||||
* @return 当前状态
|
||||
*/
|
||||
TCPStatus GetConnectionStatus() const;
|
||||
|
||||
/**
|
||||
* @brief 设置连接状态回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void SetConnectionCallback(const ConnectionCallback& callback);
|
||||
|
||||
/**
|
||||
* @brief 设置检测触发回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void SetDetectionTriggerCallback(const DetectionTriggerCallback& callback);
|
||||
|
||||
/**
|
||||
* @brief 获取服务运行状态
|
||||
* @return true-运行中,false-已停止
|
||||
*/
|
||||
bool IsRunning() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief TCP客户端连接事件处理
|
||||
* @param pClient 客户端对象
|
||||
* @param eventType 事件类型
|
||||
*/
|
||||
virtual void OnTCPEvent(const TCPClient* pClient, TCPServerEventType eventType);
|
||||
|
||||
/**
|
||||
* @brief TCP数据接收处理
|
||||
* @param pClient 客户端对象
|
||||
* @param pData 数据指针
|
||||
* @param nLen 数据长度
|
||||
*/
|
||||
virtual void OnTCPDataReceived(const TCPClient* pClient, const char* pData, unsigned int nLen);
|
||||
|
||||
/**
|
||||
* @brief 解析JSON命令(子类可重写以支持自定义命令)
|
||||
* @param pClient 客户端对象
|
||||
* @param jsonData JSON数据
|
||||
*/
|
||||
virtual void ParseJSONCommand(const TCPClient* pClient, const QJsonObject& jsonData);
|
||||
|
||||
/**
|
||||
* @brief 处理开始检测命令
|
||||
* @param pClient 客户端对象
|
||||
* @param timestamp 时间戳
|
||||
*/
|
||||
virtual void HandleStartDetectionCommand(const TCPClient* pClient, qint64 timestamp);
|
||||
|
||||
/**
|
||||
* @brief 发送错误响应
|
||||
* @param pClient 客户端对象
|
||||
* @param code 错误码
|
||||
* @param message 错误消息
|
||||
* @param timestamp 时间戳
|
||||
*/
|
||||
void SendErrorResponse(const TCPClient* pClient, int code, const QString& message, qint64 timestamp);
|
||||
|
||||
protected:
|
||||
// TCP服务器相关
|
||||
IYTCPServer* m_pTCPServer; // TCP服务器实例
|
||||
bool m_bServerRunning; // 服务器运行状态
|
||||
uint16_t m_nPort; // TCP端口
|
||||
|
||||
// 连接状态
|
||||
TCPStatus m_connectionStatus; // TCP连接状态
|
||||
|
||||
// 回调函数
|
||||
ConnectionCallback m_connectionCallback; // 连接状态回调
|
||||
DetectionTriggerCallback m_detectionTriggerCallback; // 检测触发回调
|
||||
};
|
||||
|
||||
#endif // TCPSERVERPROTOCOL_H
|
||||
@ -104,6 +104,7 @@ struct VrCameraPlaneCalibParam
|
||||
std::string cameraName = ""; // 相机名称
|
||||
double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵(默认单位矩阵)
|
||||
double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据
|
||||
double planeHeightOffset = -10.0; // 平面高度偏移量,用于数据预处理时去除地面的余量
|
||||
double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵(默认单位矩阵)
|
||||
bool isCalibrated = false; // 是否已经校准
|
||||
|
||||
@ -114,6 +115,7 @@ struct VrCameraPlaneCalibParam
|
||||
cameraName = other.cameraName;
|
||||
memcpy(planeCalib, other.planeCalib, sizeof(planeCalib));
|
||||
planeHeight = other.planeHeight;
|
||||
planeHeightOffset = other.planeHeightOffset;
|
||||
memcpy(invRMatrix, other.invRMatrix, sizeof(invRMatrix));
|
||||
isCalibrated = other.isCalibrated;
|
||||
}
|
||||
@ -125,6 +127,7 @@ struct VrCameraPlaneCalibParam
|
||||
: cameraIndex(other.cameraIndex)
|
||||
, cameraName(other.cameraName)
|
||||
, planeHeight(other.planeHeight)
|
||||
, planeHeightOffset(other.planeHeightOffset)
|
||||
, isCalibrated(other.isCalibrated) {
|
||||
memcpy(planeCalib, other.planeCalib, sizeof(planeCalib));
|
||||
memcpy(invRMatrix, other.invRMatrix, sizeof(invRMatrix));
|
||||
|
||||
252
AppUtils/AppCommon/Src/TCPServerProtocol.cpp
Normal file
252
AppUtils/AppCommon/Src/TCPServerProtocol.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
#include "TCPServerProtocol.h"
|
||||
#include "VrLog.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDateTime>
|
||||
|
||||
TCPServerProtocol::TCPServerProtocol()
|
||||
: m_pTCPServer(nullptr)
|
||||
, m_bServerRunning(false)
|
||||
, m_nPort(5020)
|
||||
, m_connectionStatus(STATUS_DISCONNECTED)
|
||||
{
|
||||
}
|
||||
|
||||
TCPServerProtocol::~TCPServerProtocol()
|
||||
{
|
||||
Deinitialize();
|
||||
}
|
||||
|
||||
int TCPServerProtocol::Initialize(uint16_t port)
|
||||
{
|
||||
LOG_DEBUG("Initializing TCP server protocol on port %d\n", port);
|
||||
|
||||
m_nPort = port;
|
||||
|
||||
// 创建TCP服务器实例
|
||||
if (!VrCreatYTCPServer(&m_pTCPServer)) {
|
||||
LOG_ERROR("Failed to create TCP server instance\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 初始化TCP服务器
|
||||
if (!m_pTCPServer->Init(port)) {
|
||||
LOG_ERROR("Failed to initialize TCP server on port %d\n", port);
|
||||
delete m_pTCPServer;
|
||||
m_pTCPServer = nullptr;
|
||||
return -2;
|
||||
}
|
||||
|
||||
// 设置事件回调
|
||||
m_pTCPServer->SetEventCallback([this](const TCPClient* pClient, TCPServerEventType eventType) {
|
||||
this->OnTCPEvent(pClient, eventType);
|
||||
});
|
||||
|
||||
// 启动TCP服务器
|
||||
if (!m_pTCPServer->Start([this](const TCPClient* pClient, const char* pData, const unsigned int nLen) {
|
||||
this->OnTCPDataReceived(pClient, pData, nLen);
|
||||
})) {
|
||||
LOG_ERROR("Failed to start TCP server\n");
|
||||
m_pTCPServer->Close();
|
||||
delete m_pTCPServer;
|
||||
m_pTCPServer = nullptr;
|
||||
return -3;
|
||||
}
|
||||
|
||||
m_bServerRunning = true;
|
||||
m_connectionStatus = STATUS_CONNECTED;
|
||||
|
||||
LOG_DEBUG("TCP server protocol initialized successfully on port %d\n", port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TCPServerProtocol::Deinitialize()
|
||||
{
|
||||
if (m_pTCPServer) {
|
||||
LOG_DEBUG("Stopping TCP server protocol\n");
|
||||
|
||||
m_bServerRunning = false;
|
||||
m_connectionStatus = ConnectionStatus::STATUS_DISCONNECTED;
|
||||
|
||||
m_pTCPServer->Stop();
|
||||
m_pTCPServer->Close();
|
||||
delete m_pTCPServer;
|
||||
m_pTCPServer = nullptr;
|
||||
|
||||
LOG_DEBUG("TCP server protocol stopped\n");
|
||||
}
|
||||
}
|
||||
|
||||
int TCPServerProtocol::SendDetectionResult(const QJsonObject& result, const TCPClient* pClient)
|
||||
{
|
||||
if (!m_pTCPServer || !m_bServerRunning) {
|
||||
LOG_ERROR("TCP server is not running\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 转换为JSON字符串
|
||||
QJsonDocument doc(result);
|
||||
QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
|
||||
|
||||
// 发送数据
|
||||
bool success = false;
|
||||
if (pClient) {
|
||||
// 发送给指定客户端
|
||||
success = m_pTCPServer->SendData(pClient, jsonData.constData(), jsonData.size());
|
||||
LOG_DEBUG("Sent detection result to specific client, size: %d bytes\n", jsonData.size());
|
||||
} else {
|
||||
// 发送给所有客户端
|
||||
success = m_pTCPServer->SendAllData(jsonData.constData(), jsonData.size());
|
||||
LOG_DEBUG("Sent detection result to all clients, size: %d bytes\n", jsonData.size());
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR("Failed to send detection result\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
TCPServerProtocol::TCPStatus TCPServerProtocol::GetConnectionStatus() const
|
||||
{
|
||||
return m_connectionStatus;
|
||||
}
|
||||
|
||||
void TCPServerProtocol::SetConnectionCallback(const ConnectionCallback& callback)
|
||||
{
|
||||
m_connectionCallback = callback;
|
||||
}
|
||||
|
||||
void TCPServerProtocol::SetDetectionTriggerCallback(const DetectionTriggerCallback& callback)
|
||||
{
|
||||
m_detectionTriggerCallback = callback;
|
||||
}
|
||||
|
||||
bool TCPServerProtocol::IsRunning() const
|
||||
{
|
||||
return m_bServerRunning;
|
||||
}
|
||||
|
||||
void TCPServerProtocol::OnTCPEvent(const TCPClient* pClient, TCPServerEventType eventType)
|
||||
{
|
||||
switch (eventType) {
|
||||
case TCP_EVENT_CLIENT_CONNECTED:
|
||||
LOG_DEBUG("TCP client connected: %p\n", pClient);
|
||||
m_connectionStatus = STATUS_CONNECTED;
|
||||
if (m_connectionCallback) {
|
||||
m_connectionCallback(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case TCP_EVENT_CLIENT_DISCONNECTED:
|
||||
LOG_DEBUG("TCP client disconnected: %p\n", pClient);
|
||||
// 注意:这里不立即设置为断开状态,因为可能还有其他客户端连接
|
||||
if (m_connectionCallback) {
|
||||
m_connectionCallback(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case TCP_EVENT_CLIENT_EXCEPTION:
|
||||
LOG_WARNING("TCP client exception: %p\n", pClient);
|
||||
if (m_connectionCallback) {
|
||||
m_connectionCallback(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TCPServerProtocol::OnTCPDataReceived(const TCPClient* pClient, const char* pData, unsigned int nLen)
|
||||
{
|
||||
if (!pData || nLen == 0) {
|
||||
LOG_WARNING("Received empty data from client %p\n", pClient);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Received TCP data from client %p, size: %u bytes\n", pClient, nLen);
|
||||
|
||||
// 解析JSON数据
|
||||
QByteArray data(pData, nLen);
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG_ERROR("JSON parse error: %s\n", error.errorString().toStdString().c_str());
|
||||
SendErrorResponse(pClient, -1, "JSON格式错误", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doc.isObject()) {
|
||||
LOG_ERROR("Received JSON is not an object\n");
|
||||
SendErrorResponse(pClient, -2, "JSON数据必须是对象格式", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
ParseJSONCommand(pClient, doc.object());
|
||||
}
|
||||
|
||||
void TCPServerProtocol::ParseJSONCommand(const TCPClient* pClient, const QJsonObject& jsonData)
|
||||
{
|
||||
// 获取消息类型和时间戳
|
||||
QString messageType = jsonData["MessageType"].toString();
|
||||
QString timestamp = jsonData["Timestamp"].toString();
|
||||
|
||||
LOG_DEBUG("Received MessageType: %s, Timestamp: %s\n", messageType.toStdString().c_str(), timestamp.toStdString().c_str());
|
||||
|
||||
if (messageType == "ScanRequest") {
|
||||
HandleStartDetectionCommand(pClient, QDateTime::currentMSecsSinceEpoch());
|
||||
} else {
|
||||
LOG_WARNING("Unknown MessageType: %s\n", messageType.toStdString().c_str());
|
||||
SendErrorResponse(pClient, -3, QString("未知消息类型: %1").arg(messageType), QDateTime::currentMSecsSinceEpoch());
|
||||
}
|
||||
}
|
||||
|
||||
void TCPServerProtocol::HandleStartDetectionCommand(const TCPClient* pClient, qint64 timestamp)
|
||||
{
|
||||
LOG_DEBUG("Handling start_detection command, timestamp: %lld\n", timestamp);
|
||||
|
||||
// 先发送ScanResponse确认收到请求
|
||||
QJsonObject response;
|
||||
response["MessageType"] = "ScanResponse";
|
||||
response["Timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
|
||||
QJsonObject data;
|
||||
data["Status"] = "Accepted";
|
||||
response["Data"] = data;
|
||||
SendDetectionResult(response, pClient);
|
||||
|
||||
bool success = false;
|
||||
int cameraIndex = -1; // -1表示所有相机
|
||||
|
||||
// 触发检测回调
|
||||
if (m_detectionTriggerCallback) {
|
||||
success = m_detectionTriggerCallback(true, cameraIndex, timestamp);
|
||||
} else {
|
||||
LOG_WARNING("Detection trigger callback not set\n");
|
||||
SendErrorResponse(pClient, -4, "检测服务未准备就绪", timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR("Failed to trigger detection\n");
|
||||
SendErrorResponse(pClient, -5, "检测启动失败", timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
// 注意:这里不立即发送结果,实际的检测结果将通过 SendDetectionResult 方法异步发送
|
||||
LOG_DEBUG("Detection triggered successfully, waiting for results...\n");
|
||||
}
|
||||
|
||||
void TCPServerProtocol::SendErrorResponse(const TCPClient* pClient, int code, const QString& message, qint64 timestamp)
|
||||
{
|
||||
QJsonObject response;
|
||||
response["MessageType"] = "Error";
|
||||
response["Timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
QJsonObject data;
|
||||
data["ErrorCode"] = code;
|
||||
data["ErrorMessage"] = message;
|
||||
response["Data"] = data;
|
||||
|
||||
SendDetectionResult(response, pClient);
|
||||
}
|
||||
198
AppUtils/UICommon/Inc/DetectImageWidget.h
Normal file
198
AppUtils/UICommon/Inc/DetectImageWidget.h
Normal file
@ -0,0 +1,198 @@
|
||||
#ifndef DETECTIMAGEWIDGET_H
|
||||
#define DETECTIMAGEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QGraphicsView>
|
||||
#include <QGraphicsScene>
|
||||
#include <QImage>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
#include <QStackedWidget>
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* @brief 放大查看控件
|
||||
*
|
||||
* 支持滚轮缩放、拖拽平移、双击还原
|
||||
*/
|
||||
class ImageZoomView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ImageZoomView(QWidget* parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 设置显示的图像
|
||||
* @param image 图像
|
||||
* @param title 标题(可选)
|
||||
*/
|
||||
void setImage(const QImage& image, const QString& title = "");
|
||||
|
||||
/**
|
||||
* @brief 重置缩放到适合窗口
|
||||
*/
|
||||
void resetZoom();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief 请求退出放大视图
|
||||
*/
|
||||
void exitRequested();
|
||||
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
private:
|
||||
void updateImageDisplay();
|
||||
|
||||
QScrollArea* m_scrollArea = nullptr;
|
||||
QLabel* m_imageLabel = nullptr;
|
||||
QLabel* m_titleLabel = nullptr;
|
||||
QLabel* m_infoLabel = nullptr;
|
||||
QImage m_originalImage;
|
||||
double m_scaleFactor = 1.0;
|
||||
bool m_dragging = false;
|
||||
bool m_needResetZoom = false;
|
||||
QPoint m_lastPos;
|
||||
|
||||
static constexpr double MIN_SCALE = 0.1;
|
||||
static constexpr double MAX_SCALE = 10.0;
|
||||
static constexpr double SCALE_STEP = 0.1;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 检测结果图像显示控件
|
||||
*
|
||||
* 统一的图像显示组件,支持:
|
||||
* - 图像显示与自适应缩放
|
||||
* - 点击放大查看(滚轮缩放、拖拽平移)
|
||||
* - 右键菜单保存数据
|
||||
*/
|
||||
class DetectImageWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 保存数据回调函数类型
|
||||
* @param filePath 保存路径
|
||||
* @return true-保存成功,false-保存失败
|
||||
*/
|
||||
using SaveDataCallback = std::function<bool(const QString& filePath)>;
|
||||
|
||||
explicit DetectImageWidget(QWidget* parent = nullptr);
|
||||
~DetectImageWidget();
|
||||
|
||||
/**
|
||||
* @brief 设置显示的图像
|
||||
* @param image 图像
|
||||
*/
|
||||
void setImage(const QImage& image);
|
||||
|
||||
/**
|
||||
* @brief 获取当前图像
|
||||
* @return 当前显示的图像
|
||||
*/
|
||||
QImage getImage() const;
|
||||
|
||||
/**
|
||||
* @brief 清空图像
|
||||
*/
|
||||
void clearImage();
|
||||
|
||||
/**
|
||||
* @brief 设置标题(用于放大视图显示)
|
||||
* @param title 标题
|
||||
*/
|
||||
void setTitle(const QString& title);
|
||||
|
||||
/**
|
||||
* @brief 获取标题
|
||||
*/
|
||||
QString title() const { return m_title; }
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用保存数据菜单项
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setSaveDataEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 设置保存数据回调函数
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void setSaveDataCallback(const SaveDataCallback& callback);
|
||||
|
||||
/**
|
||||
* @brief 设置保存数据菜单项文本
|
||||
* @param text 菜单项文本
|
||||
*/
|
||||
void setSaveDataMenuText(const QString& text);
|
||||
|
||||
/**
|
||||
* @brief 显示放大视图
|
||||
*/
|
||||
void showZoomView();
|
||||
|
||||
/**
|
||||
* @brief 显示普通视图
|
||||
*/
|
||||
void showNormalView();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief 图像被点击
|
||||
*/
|
||||
void clicked();
|
||||
|
||||
/**
|
||||
* @brief 图像被右键点击
|
||||
*/
|
||||
void rightClicked();
|
||||
|
||||
/**
|
||||
* @brief 保存数据请求信号
|
||||
* @param widget 触发保存的控件
|
||||
*/
|
||||
void saveDataRequested(DetectImageWidget* widget);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void onSaveDataTriggered();
|
||||
void showContextMenu(const QPoint& pos);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void updateImageDisplay();
|
||||
|
||||
// 界面元素
|
||||
QStackedWidget* m_stackedWidget = nullptr;
|
||||
QGraphicsView* m_graphicsView = nullptr;
|
||||
QGraphicsScene* m_scene = nullptr;
|
||||
ImageZoomView* m_zoomView = nullptr;
|
||||
|
||||
// 右键菜单
|
||||
QMenu* m_contextMenu = nullptr;
|
||||
QAction* m_saveDataAction = nullptr;
|
||||
|
||||
// 数据
|
||||
QImage m_image;
|
||||
QString m_title;
|
||||
bool m_saveDataEnabled = false;
|
||||
SaveDataCallback m_saveDataCallback;
|
||||
};
|
||||
|
||||
#endif // DETECTIMAGEWIDGET_H
|
||||
111
AppUtils/UICommon/Inc/DetectLogHelper.h
Normal file
111
AppUtils/UICommon/Inc/DetectLogHelper.h
Normal file
@ -0,0 +1,111 @@
|
||||
#ifndef DETECTLOGHELPER_H
|
||||
#define DETECTLOGHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QListView>
|
||||
#include <QStringListModel>
|
||||
#include <QDateTime>
|
||||
|
||||
/**
|
||||
* @brief 检测日志辅助类
|
||||
*
|
||||
* 用于管理现有 QListView 的日志显示逻辑,支持:
|
||||
* - 日志去重(相同消息显示计数,如 "x2", "x3")
|
||||
* - 自动添加时间戳
|
||||
* - 自动滚动到底部
|
||||
* - 跨线程安全更新
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. 在 mainwindow.h 中声明: DetectLogHelper* m_logHelper;
|
||||
* 2. 在构造函数中初始化: m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||||
* 3. 添加日志: m_logHelper->appendLog("消息");
|
||||
* 4. 清空日志: m_logHelper->clearLog();
|
||||
*/
|
||||
class DetectLogHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param listView 要管理的 QListView 指针(通常是 ui->detect_log)
|
||||
* @param parent 父对象
|
||||
*/
|
||||
explicit DetectLogHelper(QListView* listView, QObject *parent = nullptr);
|
||||
~DetectLogHelper();
|
||||
|
||||
/**
|
||||
* @brief 添加日志消息(线程安全,可从任意线程调用)
|
||||
* @param message 日志消息内容
|
||||
*/
|
||||
void appendLog(const QString& message);
|
||||
|
||||
/**
|
||||
* @brief 清空所有日志(线程安全,可从任意线程调用)
|
||||
*/
|
||||
void clearLog();
|
||||
|
||||
/**
|
||||
* @brief 设置日期格式
|
||||
* @param format 日期格式字符串,默认 "hh:mm:ss"
|
||||
*/
|
||||
void setTimestampFormat(const QString& format);
|
||||
|
||||
/**
|
||||
* @brief 设置是否显示时间戳
|
||||
* @param show 是否显示,默认 true
|
||||
*/
|
||||
void setShowTimestamp(bool show);
|
||||
|
||||
/**
|
||||
* @brief 设置是否启用日志去重
|
||||
* @param enable 是否启用,默认 true
|
||||
*/
|
||||
void setDeduplicationEnabled(bool enable);
|
||||
|
||||
/**
|
||||
* @brief 获取内部 QStringListModel 指针
|
||||
* @return QStringListModel 指针
|
||||
*/
|
||||
QStringListModel* model() const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief 日志更新请求信号(内部使用,用于跨线程更新)
|
||||
*/
|
||||
void logUpdateRequested(const QString& message);
|
||||
|
||||
/**
|
||||
* @brief 日志清空请求信号(内部使用,用于跨线程更新)
|
||||
*/
|
||||
void logClearRequested();
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief 在UI线程中更新日志
|
||||
*/
|
||||
void updateLogInUI(const QString& message);
|
||||
|
||||
/**
|
||||
* @brief 在UI线程中清空日志
|
||||
*/
|
||||
void clearLogInUI();
|
||||
|
||||
private:
|
||||
QListView* m_listView;
|
||||
QStringListModel* m_logModel;
|
||||
|
||||
// 日志去重相关
|
||||
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
|
||||
int m_lastLogCount; // 最后一条日志的重复次数
|
||||
|
||||
// 配置项
|
||||
QString m_timestampFormat; // 时间戳格式
|
||||
bool m_showTimestamp; // 是否显示时间戳
|
||||
bool m_deduplicationEnabled; // 是否启用去重
|
||||
|
||||
// 初始化信号槽连接
|
||||
void initConnections();
|
||||
};
|
||||
|
||||
#endif // DETECTLOGHELPER_H
|
||||
447
AppUtils/UICommon/Src/DetectImageWidget.cpp
Normal file
447
AppUtils/UICommon/Src/DetectImageWidget.cpp
Normal file
@ -0,0 +1,447 @@
|
||||
#include "DetectImageWidget.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QWheelEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QShowEvent>
|
||||
#include <QEvent>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include <cmath>
|
||||
|
||||
// ============================================================================
|
||||
// ImageZoomView 实现
|
||||
// ============================================================================
|
||||
ImageZoomView::ImageZoomView(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setStyleSheet("background-color: rgb(25, 26, 28);");
|
||||
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(10, 10, 10, 10);
|
||||
mainLayout->setSpacing(10);
|
||||
|
||||
// 顶部标题栏
|
||||
QHBoxLayout* topLayout = new QHBoxLayout();
|
||||
|
||||
m_titleLabel = new QLabel(this);
|
||||
m_titleLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 16px; font-weight: bold; background: transparent; }");
|
||||
topLayout->addWidget(m_titleLabel);
|
||||
|
||||
topLayout->addStretch();
|
||||
|
||||
// 返回按钮
|
||||
QPushButton* btnBack = new QPushButton(QString::fromUtf8("返回"), this);
|
||||
btnBack->setFixedSize(80, 30);
|
||||
btnBack->setStyleSheet(
|
||||
"QPushButton { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); "
|
||||
"border: 1px solid rgb(80, 82, 88); border-radius: 5px; font-size: 14px; }"
|
||||
"QPushButton:hover { background-color: rgb(80, 82, 88); }"
|
||||
"QPushButton:pressed { background-color: rgb(50, 52, 58); }"
|
||||
);
|
||||
connect(btnBack, &QPushButton::clicked, this, &ImageZoomView::exitRequested);
|
||||
topLayout->addWidget(btnBack);
|
||||
|
||||
mainLayout->addLayout(topLayout);
|
||||
|
||||
// 滚动区域
|
||||
m_scrollArea = new QScrollArea(this);
|
||||
m_scrollArea->setWidgetResizable(false);
|
||||
m_scrollArea->setAlignment(Qt::AlignCenter);
|
||||
m_scrollArea->setStyleSheet(
|
||||
"QScrollArea { background-color: rgb(38, 40, 47); border: none; }"
|
||||
"QScrollBar:vertical { background-color: rgb(38, 40, 47); width: 12px; }"
|
||||
"QScrollBar::handle:vertical { background-color: rgb(80, 82, 88); border-radius: 6px; min-height: 20px; }"
|
||||
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"
|
||||
"QScrollBar:horizontal { background-color: rgb(38, 40, 47); height: 12px; }"
|
||||
"QScrollBar::handle:horizontal { background-color: rgb(80, 82, 88); border-radius: 6px; min-width: 20px; }"
|
||||
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }"
|
||||
);
|
||||
|
||||
m_imageLabel = new QLabel(m_scrollArea);
|
||||
m_imageLabel->setAlignment(Qt::AlignCenter);
|
||||
m_imageLabel->setStyleSheet("background-color: rgb(38, 40, 47);");
|
||||
m_imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||
m_imageLabel->setScaledContents(false);
|
||||
m_scrollArea->setWidget(m_imageLabel);
|
||||
|
||||
mainLayout->addWidget(m_scrollArea, 1);
|
||||
|
||||
// 底部信息栏
|
||||
m_infoLabel = new QLabel(this);
|
||||
m_infoLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 12px; background: transparent; }");
|
||||
m_infoLabel->setText(QString::fromUtf8("滚轮缩放 | 拖拽平移 | 双击还原 | 点击返回按钮退出"));
|
||||
mainLayout->addWidget(m_infoLabel);
|
||||
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
}
|
||||
|
||||
void ImageZoomView::setImage(const QImage& image, const QString& title)
|
||||
{
|
||||
m_originalImage = image;
|
||||
m_titleLabel->setText(title);
|
||||
m_needResetZoom = true;
|
||||
|
||||
updateImageDisplay();
|
||||
}
|
||||
|
||||
void ImageZoomView::showEvent(QShowEvent* event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
if (m_needResetZoom && !m_originalImage.isNull()) {
|
||||
m_needResetZoom = false;
|
||||
QMetaObject::invokeMethod(this, &ImageZoomView::resetZoom, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageZoomView::updateImageDisplay()
|
||||
{
|
||||
if (m_originalImage.isNull()) {
|
||||
m_imageLabel->setText(QString::fromUtf8("无图像"));
|
||||
return;
|
||||
}
|
||||
|
||||
int newWidth = static_cast<int>(m_originalImage.width() * m_scaleFactor);
|
||||
int newHeight = static_cast<int>(m_originalImage.height() * m_scaleFactor);
|
||||
|
||||
QImage scaledImage = m_originalImage.scaled(newWidth, newHeight,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
m_imageLabel->setPixmap(QPixmap::fromImage(scaledImage));
|
||||
m_imageLabel->resize(scaledImage.size());
|
||||
|
||||
QString info = QString::fromUtf8("缩放: %1% | 尺寸: %2x%3 | 滚轮缩放 | 拖拽平移 | 双击还原")
|
||||
.arg(static_cast<int>(m_scaleFactor * 100))
|
||||
.arg(m_originalImage.width())
|
||||
.arg(m_originalImage.height());
|
||||
m_infoLabel->setText(info);
|
||||
}
|
||||
|
||||
void ImageZoomView::resetZoom()
|
||||
{
|
||||
if (m_originalImage.isNull()) return;
|
||||
|
||||
QSize viewSize = m_scrollArea->viewport()->size();
|
||||
double scaleX = static_cast<double>(viewSize.width() - 20) / m_originalImage.width();
|
||||
double scaleY = static_cast<double>(viewSize.height() - 20) / m_originalImage.height();
|
||||
m_scaleFactor = qMin(scaleX, scaleY);
|
||||
m_scaleFactor = qMax(m_scaleFactor, MIN_SCALE);
|
||||
|
||||
updateImageDisplay();
|
||||
|
||||
m_scrollArea->horizontalScrollBar()->setValue(
|
||||
(m_imageLabel->width() - m_scrollArea->viewport()->width()) / 2);
|
||||
m_scrollArea->verticalScrollBar()->setValue(
|
||||
(m_imageLabel->height() - m_scrollArea->viewport()->height()) / 2);
|
||||
}
|
||||
|
||||
void ImageZoomView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
if (m_originalImage.isNull()) return;
|
||||
|
||||
double delta = event->angleDelta().y() > 0 ? SCALE_STEP : -SCALE_STEP;
|
||||
|
||||
double newScale = m_scaleFactor + delta;
|
||||
newScale = qMax(newScale, MIN_SCALE);
|
||||
newScale = qMin(newScale, MAX_SCALE);
|
||||
|
||||
if (qAbs(newScale - m_scaleFactor) > 0.001) {
|
||||
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
|
||||
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
|
||||
|
||||
double hRatio = hBar->maximum() > 0 ? static_cast<double>(hBar->value()) / hBar->maximum() : 0.5;
|
||||
double vRatio = vBar->maximum() > 0 ? static_cast<double>(vBar->value()) / vBar->maximum() : 0.5;
|
||||
|
||||
m_scaleFactor = newScale;
|
||||
updateImageDisplay();
|
||||
|
||||
hBar->setValue(static_cast<int>(hRatio * hBar->maximum()));
|
||||
vBar->setValue(static_cast<int>(vRatio * vBar->maximum()));
|
||||
}
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void ImageZoomView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_dragging = true;
|
||||
m_lastPos = event->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void ImageZoomView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if (m_dragging) {
|
||||
QPoint delta = event->pos() - m_lastPos;
|
||||
m_lastPos = event->pos();
|
||||
|
||||
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
|
||||
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
|
||||
|
||||
hBar->setValue(hBar->value() - delta.x());
|
||||
vBar->setValue(vBar->value() - delta.y());
|
||||
}
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ImageZoomView::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_dragging = false;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void ImageZoomView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
resetZoom();
|
||||
}
|
||||
QWidget::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void ImageZoomView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Escape:
|
||||
emit exitRequested();
|
||||
break;
|
||||
case Qt::Key_Plus:
|
||||
case Qt::Key_Equal:
|
||||
m_scaleFactor = qMin(m_scaleFactor + SCALE_STEP, MAX_SCALE);
|
||||
updateImageDisplay();
|
||||
break;
|
||||
case Qt::Key_Minus:
|
||||
m_scaleFactor = qMax(m_scaleFactor - SCALE_STEP, MIN_SCALE);
|
||||
updateImageDisplay();
|
||||
break;
|
||||
case Qt::Key_0:
|
||||
m_scaleFactor = 1.0;
|
||||
updateImageDisplay();
|
||||
break;
|
||||
case Qt::Key_F:
|
||||
resetZoom();
|
||||
break;
|
||||
default:
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageZoomView::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
if (!m_originalImage.isNull()) {
|
||||
resetZoom();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DetectImageWidget 实现
|
||||
// ============================================================================
|
||||
DetectImageWidget::DetectImageWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
DetectImageWidget::~DetectImageWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void DetectImageWidget::setupUI()
|
||||
{
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
|
||||
// 使用 QStackedWidget 切换普通视图和放大视图
|
||||
m_stackedWidget = new QStackedWidget(this);
|
||||
|
||||
// 普通视图 - QGraphicsView
|
||||
m_graphicsView = new QGraphicsView(this);
|
||||
m_scene = new QGraphicsScene(this);
|
||||
m_graphicsView->setScene(m_scene);
|
||||
m_graphicsView->setRenderHint(QPainter::Antialiasing);
|
||||
m_graphicsView->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
m_graphicsView->setDragMode(QGraphicsView::NoDrag);
|
||||
m_graphicsView->setStyleSheet("QGraphicsView { background-color: rgb(47, 48, 52); border: none; }");
|
||||
m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_graphicsView->setCursor(Qt::PointingHandCursor);
|
||||
|
||||
// 设置点击事件过滤
|
||||
m_graphicsView->viewport()->installEventFilter(this);
|
||||
|
||||
m_stackedWidget->addWidget(m_graphicsView);
|
||||
|
||||
// 放大视图
|
||||
m_zoomView = new ImageZoomView(this);
|
||||
connect(m_zoomView, &ImageZoomView::exitRequested, this, &DetectImageWidget::showNormalView);
|
||||
m_stackedWidget->addWidget(m_zoomView);
|
||||
|
||||
mainLayout->addWidget(m_stackedWidget);
|
||||
|
||||
// 默认显示普通视图
|
||||
m_stackedWidget->setCurrentIndex(0);
|
||||
|
||||
// 设置右键菜单
|
||||
m_graphicsView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_graphicsView, &QGraphicsView::customContextMenuRequested,
|
||||
this, &DetectImageWidget::showContextMenu);
|
||||
|
||||
// 创建右键菜单
|
||||
m_contextMenu = new QMenu(this);
|
||||
m_saveDataAction = new QAction(QString::fromUtf8("保存检测数据"), this);
|
||||
m_saveDataAction->setEnabled(false);
|
||||
|
||||
// 设置菜单样式
|
||||
m_contextMenu->setStyleSheet(
|
||||
"QMenu { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); border: 1px solid rgb(80, 82, 88); }"
|
||||
"QMenu::item { padding: 8px 25px; }"
|
||||
"QMenu::item:selected { background-color: rgb(80, 82, 88); }"
|
||||
"QMenu::item:disabled { color: rgb(120, 120, 120); }"
|
||||
);
|
||||
|
||||
m_contextMenu->addAction(m_saveDataAction);
|
||||
connect(m_saveDataAction, &QAction::triggered, this, &DetectImageWidget::onSaveDataTriggered);
|
||||
}
|
||||
|
||||
bool DetectImageWidget::eventFilter(QObject* obj, QEvent* event)
|
||||
{
|
||||
if (obj == m_graphicsView->viewport()) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
// 点击放大
|
||||
if (!m_image.isNull()) {
|
||||
showZoomView();
|
||||
emit clicked();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void DetectImageWidget::setImage(const QImage& image)
|
||||
{
|
||||
m_image = image;
|
||||
updateImageDisplay();
|
||||
}
|
||||
|
||||
QImage DetectImageWidget::getImage() const
|
||||
{
|
||||
return m_image;
|
||||
}
|
||||
|
||||
void DetectImageWidget::clearImage()
|
||||
{
|
||||
m_image = QImage();
|
||||
m_scene->clear();
|
||||
}
|
||||
|
||||
void DetectImageWidget::setTitle(const QString& title)
|
||||
{
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
void DetectImageWidget::setSaveDataEnabled(bool enabled)
|
||||
{
|
||||
m_saveDataEnabled = enabled;
|
||||
m_saveDataAction->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void DetectImageWidget::setSaveDataCallback(const SaveDataCallback& callback)
|
||||
{
|
||||
m_saveDataCallback = callback;
|
||||
}
|
||||
|
||||
void DetectImageWidget::setSaveDataMenuText(const QString& text)
|
||||
{
|
||||
m_saveDataAction->setText(text);
|
||||
}
|
||||
|
||||
void DetectImageWidget::showZoomView()
|
||||
{
|
||||
if (m_image.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_zoomView->setImage(m_image, m_title);
|
||||
m_stackedWidget->setCurrentIndex(1);
|
||||
m_zoomView->setFocus();
|
||||
}
|
||||
|
||||
void DetectImageWidget::showNormalView()
|
||||
{
|
||||
m_stackedWidget->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void DetectImageWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
updateImageDisplay();
|
||||
}
|
||||
|
||||
void DetectImageWidget::updateImageDisplay()
|
||||
{
|
||||
if (m_image.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scene->clear();
|
||||
|
||||
QPixmap pixmap = QPixmap::fromImage(m_image);
|
||||
m_scene->addPixmap(pixmap);
|
||||
m_scene->setSceneRect(pixmap.rect());
|
||||
|
||||
// 自适应显示
|
||||
m_graphicsView->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
void DetectImageWidget::showContextMenu(const QPoint& pos)
|
||||
{
|
||||
// 检查是否有数据可保存
|
||||
if (!m_saveDataEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_contextMenu->exec(m_graphicsView->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void DetectImageWidget::onSaveDataTriggered()
|
||||
{
|
||||
// 发射信号,让外部处理保存逻辑
|
||||
emit saveDataRequested(this);
|
||||
|
||||
// 如果设置了回调,也调用回调
|
||||
if (m_saveDataCallback) {
|
||||
// 获取默认保存路径
|
||||
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
|
||||
QString defaultFileName = QString("%1/detect_data_%2.pcd").arg(defaultPath).arg(timestamp);
|
||||
|
||||
QString filePath = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
QString::fromUtf8("保存检测数据"),
|
||||
defaultFileName,
|
||||
QString::fromUtf8("点云文件 (*.pcd);;所有文件 (*.*)")
|
||||
);
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
m_saveDataCallback(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
AppUtils/UICommon/Src/DetectLogHelper.cpp
Normal file
132
AppUtils/UICommon/Src/DetectLogHelper.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
#include "DetectLogHelper.h"
|
||||
|
||||
DetectLogHelper::DetectLogHelper(QListView* listView, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_listView(listView)
|
||||
, m_logModel(nullptr)
|
||||
, m_lastLogCount(0)
|
||||
, m_timestampFormat("hh:mm:ss")
|
||||
, m_showTimestamp(true)
|
||||
, m_deduplicationEnabled(true)
|
||||
{
|
||||
if (m_listView) {
|
||||
// 创建并设置 QStringListModel
|
||||
m_logModel = new QStringListModel(this);
|
||||
m_listView->setModel(m_logModel);
|
||||
|
||||
// 设置 QListView 属性
|
||||
m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_listView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
}
|
||||
|
||||
initConnections();
|
||||
}
|
||||
|
||||
DetectLogHelper::~DetectLogHelper()
|
||||
{
|
||||
}
|
||||
|
||||
void DetectLogHelper::initConnections()
|
||||
{
|
||||
// 连接信号槽,支持跨线程更新
|
||||
connect(this, &DetectLogHelper::logUpdateRequested,
|
||||
this, &DetectLogHelper::updateLogInUI,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &DetectLogHelper::logClearRequested,
|
||||
this, &DetectLogHelper::clearLogInUI,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void DetectLogHelper::appendLog(const QString& message)
|
||||
{
|
||||
// 通过信号槽机制,确保在UI线程中更新
|
||||
emit logUpdateRequested(message);
|
||||
}
|
||||
|
||||
void DetectLogHelper::clearLog()
|
||||
{
|
||||
// 通过信号槽机制,确保在UI线程中清空
|
||||
emit logClearRequested();
|
||||
}
|
||||
|
||||
void DetectLogHelper::setTimestampFormat(const QString& format)
|
||||
{
|
||||
m_timestampFormat = format;
|
||||
}
|
||||
|
||||
void DetectLogHelper::setShowTimestamp(bool show)
|
||||
{
|
||||
m_showTimestamp = show;
|
||||
}
|
||||
|
||||
void DetectLogHelper::setDeduplicationEnabled(bool enable)
|
||||
{
|
||||
m_deduplicationEnabled = enable;
|
||||
}
|
||||
|
||||
QStringListModel* DetectLogHelper::model() const
|
||||
{
|
||||
return m_logModel;
|
||||
}
|
||||
|
||||
void DetectLogHelper::updateLogInUI(const QString& message)
|
||||
{
|
||||
if (!m_logModel || !m_listView) return;
|
||||
|
||||
// 获取当前数据
|
||||
QStringList logList = m_logModel->stringList();
|
||||
|
||||
// 构建日志条目
|
||||
QString logEntry;
|
||||
|
||||
if (m_deduplicationEnabled && message == m_lastLogMessage && !logList.isEmpty()) {
|
||||
// 相同消息,增加计数并替换最后一条
|
||||
m_lastLogCount++;
|
||||
|
||||
if (m_showTimestamp) {
|
||||
QString timestamp = QDateTime::currentDateTime().toString(m_timestampFormat);
|
||||
logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||||
} else {
|
||||
logEntry = QString("%1 (x%2)").arg(message).arg(m_lastLogCount);
|
||||
}
|
||||
|
||||
// 替换最后一条
|
||||
logList[logList.size() - 1] = logEntry;
|
||||
} else {
|
||||
// 新消息,重置计数
|
||||
m_lastLogMessage = message;
|
||||
m_lastLogCount = 1;
|
||||
|
||||
if (m_showTimestamp) {
|
||||
QString timestamp = QDateTime::currentDateTime().toString(m_timestampFormat);
|
||||
logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
} else {
|
||||
logEntry = message;
|
||||
}
|
||||
|
||||
// 添加新的日志条目
|
||||
logList.append(logEntry);
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
m_logModel->setStringList(logList);
|
||||
|
||||
// 自动滚动到最底部
|
||||
if (!logList.isEmpty()) {
|
||||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||||
m_listView->scrollTo(lastIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void DetectLogHelper::clearLogInUI()
|
||||
{
|
||||
if (m_logModel) {
|
||||
m_logModel->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// 重置日志计数器
|
||||
m_lastLogMessage.clear();
|
||||
m_lastLogCount = 0;
|
||||
}
|
||||
@ -29,7 +29,9 @@ HEADERS += \
|
||||
Inc/CommonDialogCamera.h \
|
||||
Inc/CommonDialogCameraLevel.h \
|
||||
Inc/DialogImageViewer.h \
|
||||
Inc/AuthView.h
|
||||
Inc/AuthView.h \
|
||||
Inc/DetectLogHelper.h \
|
||||
Inc/DetectImageWidget.h
|
||||
|
||||
SOURCES += \
|
||||
Src/StyledMessageBox.cpp \
|
||||
@ -38,7 +40,9 @@ SOURCES += \
|
||||
Src/CommonDialogCamera.cpp \
|
||||
Src/CommonDialogCameraLevel.cpp \
|
||||
Src/DialogImageViewer.cpp \
|
||||
Src/AuthView.cpp
|
||||
Src/AuthView.cpp \
|
||||
Src/DetectLogHelper.cpp \
|
||||
Src/DetectImageWidget.cpp
|
||||
|
||||
FORMS += \
|
||||
DeviceStatusWidget.ui \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user