撕裂增加结果绘制

This commit is contained in:
yiyi 2025-10-08 21:45:37 +08:00
parent ef11044756
commit 716fd5089f
19 changed files with 623 additions and 250 deletions

View File

@ -99,8 +99,9 @@ struct BeltTearingResult {
std::vector<TearingData> result; // 检测结果描述
bool bResultVaild = false;
double confidence = 0.0; // 置信度
QString serverName; // 服务器名称
QDateTime timestamp; // 时间戳
QString serverName; // 服务器名称
QString aliasName; // 服务器别名
QDateTime timestamp; // 时间戳
};
@ -130,6 +131,9 @@ public:
// 错误信息回调
virtual void OnErrorOccurred(const QString& errorMessage) = 0;
// 清空撕裂数据回调
virtual void OnClearTearingData() = 0;
};
// 声明Qt元类型使这些结构体能够在信号槽中传递

View File

@ -27,7 +27,7 @@ public:
void Init(); // 状态更新接口
QStringList getServerNames() const;
QStringList getServerNames() {return m_serverInfos.keys();}
QString getServerIp(const QString &serverName) const ;
quint16 getServerPort(const QString &serverName) const ;
QString getServerDisplayName(const QString &serverName) const ;
@ -44,14 +44,17 @@ public:
// 处理服务器信息响应
void handleServerInfoResponse(const QString& serverName, const QJsonObject& responseObj);
// 重新检测方法
void ResetDetect(const QString& targetServerAlias);
private:
// IBeltTearingPresenter interface implementation
bool initializeConfig(const QString &configPath);
bool connectToServer(const ServerInfo &serverInfo, const QString &serverName = QString());
void disconnectFromServer(const QString &serverName = QString()) ;
bool isConnected(const QString &serverName = QString()) const ;
bool sendData(const QByteArray &data, const QString &serverName = QString());
bool connectToServer(const ServerInfo &serverInfo, const QString &aliasName);
void disconnectFromServer(const QString &aliasName = QString()) ;
bool isConnected(const QString &aliasName = QString()) const ;
bool sendData(const QByteArray &data, const QString &aliasName = QString());
private slots:
@ -65,8 +68,8 @@ private:
// static void tcpLinkStatusCallback(bool connected);
// 实例方法
void handleTcpDataReceived(const QString &serverName, const char* pData, const int nLen);
void handleTcpLinkStatus(const QString &serverName, bool connected);
void handleTcpDataReceived(const QString &aliasName, const char* pData, const int nLen);
void handleTcpLinkStatus(const QString &aliasName, bool connected);
private:
IVrBeltTearingConfig * m_config = nullptr; // 配置接口

View File

@ -71,16 +71,17 @@ bool BeltTearingPresenter::initializeConfig(const QString &configPath)
return false;
}
// 获取所有服务器配置
const auto &servers = configResult.servers;
if (servers.empty()) {
if (configResult.servers.empty()) {
LOG_WARNING("No servers configured");
return false;
}
// 清空现有配置
m_serverInfos.clear();
// 获取所有服务器配置
const auto &servers = configResult.servers;
// 存储所有启用的服务器信息
QList<DeviceInfo> devices;
QStringList deviceAliases;
@ -102,22 +103,17 @@ bool BeltTearingPresenter::initializeConfig(const QString &configPath)
}
// 连接到所有服务器
for(size_t i = 0 ; i < devices.size() ; i++){
for(size_t i = 0 ; i < servers.size() ; i++){
connectToServer(servers[i], deviceAliases[i]);
}
if (m_serverInfos.empty()) {
LOG_WARNING("No enabled servers found\n");
return false;
}
LOG_DEBUG("Config loaded successfully. Found %d enabled servers\n", m_serverInfos.size());
return true;
}
bool BeltTearingPresenter::connectToServer(const ServerInfo &serverInfo, const QString &serverName)
bool BeltTearingPresenter::connectToServer(const ServerInfo &serverInfo, const QString &aliasName)
{
QString targetServerName = serverName;
QString targetServerName = aliasName;
// 创建TCP客户端如果不存在
if (!m_tcpClients.contains(targetServerName)) {
@ -221,11 +217,6 @@ bool BeltTearingPresenter::sendData(const QByteArray &data, const QString &serve
return success;
}
QStringList BeltTearingPresenter::getServerNames() const
{
return m_serverInfos.keys();
}
QString BeltTearingPresenter::getServerIp(const QString &serverName) const
{
if (m_serverInfos.contains(serverName)) {
@ -282,15 +273,15 @@ void BeltTearingPresenter::onTcpError(const QString &serverName, const QString &
}
}
void BeltTearingPresenter::handleTcpDataReceived(const QString &serverName, const char* pData, const int nLen)
void BeltTearingPresenter::handleTcpDataReceived(const QString &aliasName, const char* pData, const int nLen)
{
if (!pData || nLen <= 0) return;
// 将新数据添加到缓存
m_dataBuffers[serverName].append(pData, nLen);
m_dataBuffers[aliasName].append(pData, nLen);
// 检查是否有完整的数据包
QByteArray &buffer = m_dataBuffers[serverName];
QByteArray &buffer = m_dataBuffers[aliasName];
const QByteArray endMarker = "___END___\r\n";
int endPos = buffer.indexOf(endMarker);
@ -300,7 +291,7 @@ void BeltTearingPresenter::handleTcpDataReceived(const QString &serverName, cons
// LOG_DEBUG("Found complete packet for %s: size=%d\n", serverName.toStdString().c_str(), completePacket.size());
// 处理完整数据包
processCompletePacket(serverName, completePacket);
processCompletePacket(aliasName, completePacket);
// 从缓存中移除已处理的数据(包括结束标记)
buffer.remove(0, endPos + endMarker.length());
@ -310,15 +301,15 @@ void BeltTearingPresenter::handleTcpDataReceived(const QString &serverName, cons
}
// 如果缓存过大,清空以避免内存问题
if (buffer.size() > 1024 * 1024) { // 1MB 限制
LOG_WARNING("Buffer too large for %s, clearing buffer\n", serverName.toStdString().c_str());
if (buffer.size() > 1024 * 1024 * 2) { // 2MB 限制
LOG_WARNING("Buffer too large for %s, clearing buffer\n", aliasName.toStdString().c_str());
buffer.clear();
}
}
void BeltTearingPresenter::processCompletePacket(const QString &serverName, const QByteArray &completeData)
void BeltTearingPresenter::processCompletePacket(const QString &aliasName, const QByteArray &completeData)
{
// LOG_DEBUG("Processing complete packet from %s: size=%d\n", serverName.toStdString().c_str(), completeData.size());
// LOG_DEBUG("Processing complete packet from %s: size=%d\n", aliasName.toStdString().c_str(), completeData.size());
// 解析数据包协议头
if (completeData.size() < 5) {
@ -397,7 +388,27 @@ void BeltTearingPresenter::processCompletePacket(const QString &serverName, cons
// 检查是否是配置响应
if (command == "configResponse") {
// 处理配置响应数据
emit serverDataReceived(serverName, jsonObj);
emit serverDataReceived(aliasName, jsonObj);
return;
}
// 检查是否是重新检测响应
else if (command == "resetDetectResponse") {
// 处理重新检测响应
QString status = jsonObj["status"].toString();
QString message = jsonObj["message"].toString();
LOG_INFO("Received reset detect response from server %s: status=%s, message=%s\n",
aliasName.toStdString().c_str(),
status.toStdString().c_str(),
message.toStdString().c_str());
// 通过状态更新接口通知上层
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString("来自服务器 %1 的重新检测响应: %2").arg(aliasName).arg(message));
// 清空撕裂数据表,避免残留数据
m_statusUpdate->OnClearTearingData();
}
return;
}
}
@ -412,8 +423,9 @@ void BeltTearingPresenter::processCompletePacket(const QString &serverName, cons
// 通知上层处理结果
if (m_statusUpdate) {
tearResult.serverName = serverName;
tearResult.timestamp = QDateTime::currentDateTime();
tearResult.serverName = QString::fromStdString(m_serverInfos[aliasName].name);
tearResult.aliasName = aliasName;
tearResult.timestamp = QDateTime::currentDateTime();
try {
m_statusUpdate->OnTearingResult(tearResult);
@ -425,34 +437,34 @@ void BeltTearingPresenter::processCompletePacket(const QString &serverName, cons
}
}
void BeltTearingPresenter::handleTcpLinkStatus(const QString &serverName, bool connected)
void BeltTearingPresenter::handleTcpLinkStatus(const QString &aliasName, bool connected)
{
m_connectionStatus[serverName] = connected;
m_connectionStatus[aliasName] = connected;
if (connected) {
onConnected(serverName);
onConnected(aliasName);
} else {
onDisconnected(serverName);
onDisconnected(aliasName);
}
}
bool BeltTearingPresenter::sendParametersToServer(const ByteDataType dataType, const QString& serverName, const QByteArray& paramData)
bool BeltTearingPresenter::sendParametersToServer(const ByteDataType dataType, const QString& aliasName, const QByteArray& paramData)
{
if (!m_serverInfos.contains(serverName)) {
LOG_ERROR("Server not found: %s\n", serverName.toStdString().c_str());
if (!m_serverInfos.contains(aliasName)) {
LOG_ERROR("Server not found: %s\n", aliasName.toStdString().c_str());
return false;
}
if (!m_connectionStatus.value(serverName, false)) {
LOG_ERROR("Server not connected: %s\n", serverName.toStdString().c_str());
if (!m_connectionStatus.value(aliasName, false)) {
LOG_ERROR("Server not connected: %s\n", aliasName.toStdString().c_str());
return false;
}
LOG_DEBUG("req : %s \n", serverName.toStdString().c_str());
LOG_DEBUG("req : %s \n", aliasName.toStdString().c_str());
IVrTCPClient* tcpClient = m_tcpClients.value(serverName, nullptr);
IVrTCPClient* tcpClient = m_tcpClients.value(aliasName, nullptr);
if (!tcpClient) {
LOG_ERROR("TCP client not found for server: %s\n", serverName.toStdString().c_str());
LOG_ERROR("TCP client not found for server: %s\n", aliasName.toStdString().c_str());
return false;
}
@ -476,9 +488,9 @@ bool BeltTearingPresenter::sendParametersToServer(const ByteDataType dataType, c
// 发送数据
bool success = tcpClient->SendData(packet.constData(), packet.size());
if (success) {
LOG_INFO("Algorithm parameters sent to server %s successfully\n", serverName.toStdString().c_str());
LOG_INFO("Algorithm parameters sent to server %s successfully\n", aliasName.toStdString().c_str());
} else {
LOG_ERROR("Failed to send algorithm parameters to server %s\n", serverName.toStdString().c_str());
LOG_ERROR("Failed to send algorithm parameters to server %s\n", aliasName.toStdString().c_str());
}
return success;
@ -488,10 +500,6 @@ void BeltTearingPresenter::handleServerInfoResponse(const QString& serverName, c
{
LOG_INFO("Received server info response from %s\n", serverName.toStdString().c_str());
// 检查是否有DialogNetConfig实例需要接收这个数据
// 这里可以通过信号槽机制来通知界面更新
// 暂时只记录日志实际实现中可能需要通过主窗口传递给DialogNetConfig
if (responseObj.contains("serverInfo")) {
QJsonObject serverInfo = responseObj["serverInfo"].toObject();
QString serverVersion = serverInfo["version"].toString();
@ -517,3 +525,53 @@ void BeltTearingPresenter::handleServerInfoResponse(const QString& serverName, c
emit serverDataReceived(serverName, responseObj);
}
void BeltTearingPresenter::ResetDetect(const QString& targetServerAlias)
{
LOG_INFO("Resetting detection for server: %s\n", targetServerAlias.toStdString().c_str());
// 检查目标服务端是否存在且已连接
if (!m_serverInfos.contains(targetServerAlias)) {
LOG_ERROR("Target server not found: %s\n", targetServerAlias.toStdString().c_str());
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString("未找到目标服务器: %1").arg(targetServerAlias));
}
return;
}
if (!isConnected(targetServerAlias)) {
LOG_ERROR("Target server not connected: %s\n", targetServerAlias.toStdString().c_str());
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString("目标服务器未连接: %1").arg(targetServerAlias));
}
return;
}
// 通知UI清空数据
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString("正在重新检测服务器: %1...").arg(targetServerAlias));
// 清空数据的操作应该由UI层处理
}
// 向指定的服务端发送重新检测指令
QJsonObject resetCommand;
resetCommand["command"] = "resetDetect";
resetCommand["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
QJsonDocument doc(resetCommand);
QByteArray commandData = doc.toJson(QJsonDocument::Compact);
// 只向指定的服务器发送重新检测指令
bool success = sendParametersToServer(ByteDataType::Text, targetServerAlias, commandData);
if (success) {
LOG_INFO("Reset detect command sent to server: %s\n", targetServerAlias.toStdString().c_str());
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString("重新检测指令已发送至服务器: %1").arg(targetServerAlias));
}
} else {
LOG_ERROR("Failed to send reset detect command to server: %s\n", targetServerAlias.toStdString().c_str());
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString("发送重新检测指令失败: %1").arg(targetServerAlias));
}
}
}

View File

@ -2,7 +2,7 @@
#define VERSION_H
#define BELT_TEARING_APP_VERSION_STRING "2.0.1"
#define BELT_TEARING_APP_VERSION_BUILD "4"
#define BELT_TEARING_APP_VERSION_BUILD "5"
#define BELT_TEARING_APP_PRODUCT_NAME "BeltTearingApp"
#define BELT_TEARING_APP_COMPANY_NAME "VisionRobot"
#define BELT_TEARING_APP_COPYRIGHT "Copyright (C) 2024 VisionRobot. All rights reserved."

View File

@ -18,6 +18,7 @@ int main(int argc, char *argv[])
qRegisterMetaType<QVector<int>>("QVector<int>");
qRegisterMetaType<QList<QPersistentModelIndex>>("QList<QPersistentModelIndex>");
qRegisterMetaType<QAbstractItemModel::LayoutChangeHint>("QAbstractItemModel::LayoutChangeHint");
qRegisterMetaType<Qt::SortOrder>("Qt::SortOrder");
// Register custom meta types for BeltTearing
qRegisterMetaType<TearingData>("TearingData");

View File

@ -26,6 +26,14 @@ MainWindow::MainWindow(QWidget *parent)
// 设置窗口图标
this->setWindowIcon(QIcon(":/resource/logo.png"));
// 设置状态栏字体
QFont statusFont = statusBar()->font();
statusFont.setPointSize(12);
statusBar()->setFont(statusFont);
// 设置状态栏颜色和padding
statusBar()->setStyleSheet("QStatusBar { color: rgb(239, 241, 245); padding: 20px; }");
// 隐藏标题栏
setWindowFlags(Qt::FramelessWindowHint);
@ -64,8 +72,10 @@ MainWindow::MainWindow(QWidget *parent)
m_presenter->setStatusUpdate(this);
// 连接服务器数据接收信号
connect(m_presenter, &BeltTearingPresenter::serverDataReceived,
this, &MainWindow::onServerDataReceived);
connect(m_presenter, &BeltTearingPresenter::serverDataReceived, this, &MainWindow::onServerDataReceived);
// 连接设备点击信号到重新检测槽函数
connect(m_deviceStatusWidget, &DeviceStatusWidget::deviceClicked, this, &MainWindow::onDeviceClicked);
// 设置版本信息显示
setupVersionDisplay();
@ -259,6 +269,52 @@ void MainWindow::onServerDataReceived(const QString& serverName, const QJsonObje
statusBar()->showMessage("从服务器 " + serverName + " 获取参数完成");
}
// 设备点击事件处理槽函数
void MainWindow::onDeviceClicked(const QString& deviceAlias)
{
// 获取设备显示名称
QString deviceName = deviceAlias;
if (m_presenter) {
deviceName = m_presenter->getServerDisplayName(deviceAlias);
if (deviceName.isEmpty()) {
deviceName = deviceAlias;
}
}
// 创建自定义的确认对话框
QMessageBox msgBox(this);
msgBox.setWindowTitle("重新检测确认");
msgBox.setText(QString("确定要对设备 \"%1\" 进行重新检测吗?").arg(deviceName));
msgBox.setIcon(QMessageBox::Question);
// 设置白色字体样式
msgBox.setStyleSheet("QLabel { color: white; } QPushButton { color: white; }");
// 添加中文按钮
QPushButton *yesButton = msgBox.addButton("", QMessageBox::YesRole);
QPushButton *noButton = msgBox.addButton("", QMessageBox::NoRole);
// 显示对话框并获取用户选择
msgBox.exec();
// 如果用户确认重新检测
if (msgBox.clickedButton() == yesButton) {
// 调用Presenter的ResetDetect方法进行重新检测传递设备别名
if (m_presenter) {
m_presenter->ResetDetect(deviceAlias);
}
// 清空检测数据表格
if (m_tearingDataTableWidget) {
m_tearingDataTableWidget->clearData();
}
// 更新状态栏信息
statusBar()->showMessage(QString("已启动对设备 \"%1\" 的重新检测").arg(deviceName));
}
}
void MainWindow::onConfigSaved()
{
// 重新初始化Presenter
@ -296,8 +352,7 @@ void MainWindow::OnNeedShowImageCount(const QStringList &deviceAliases)
for (const QString& alias : deviceAliases) {
QString displayName = m_presenter->getServerDisplayName(alias);
QString ip = m_presenter->getServerIp(alias);
devices.append(DeviceInfo(displayName.isEmpty() ? alias : displayName,
alias, ip, DeviceStatus::Offline, true));
devices.append(DeviceInfo(displayName.isEmpty() ? alias : displayName, alias, ip, DeviceStatus::Offline, true));
}
m_deviceStatusWidget->setDevices(devices);
}
@ -309,10 +364,10 @@ void MainWindow::OnTearingResult(const BeltTearingResult &result)
// 如果图像有效,显示在网格控件中
if (result.bImageValid && !result.image.isNull()) {
// 使用服务器名称作为别名
m_gridView->setImages(result.serverName, result.image);
m_gridView->setImages(result.aliasName, result.image);
} else if (result.bResultVaild) {
// 直接处理std::vector<TearingData>
m_tearingDataTableWidget->addDataBatch(result.result);
m_tearingDataTableWidget->addDataBatch(result.serverName, result.result);
}
}
@ -376,6 +431,13 @@ void MainWindow::OnDeviceStatusChanged(const QString &deviceName, int deviceStat
}
}
void MainWindow::OnClearTearingData()
{
if (m_tearingDataTableWidget) {
m_tearingDataTableWidget->clearData();
}
}
void MainWindow::setupVersionDisplay()
{
// 创建版本信息标签
@ -396,7 +458,7 @@ void MainWindow::setupVersionDisplay()
m_versionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
// 设置样式使其看起来更美观
m_versionLabel->setStyleSheet("QLabel { color: #666666; font-size: 12px; margin-right: 10px; }");
m_versionLabel->setStyleSheet("QLabel { color: rgb(239, 241, 245); font-size: 12px; margin-right: 10px; }");
// 将版本标签添加到状态栏的右侧
statusBar()->addPermanentWidget(m_versionLabel);

View File

@ -5,6 +5,8 @@
#include <QStringList>
#include <QSplitter>
#include <QResizeEvent>
#include <QMessageBox>
#include <QPushButton>
#include "models/ImageInfoModel.h"
#include "widgets/ImageGridWithTableWidget.h"
#include "widgets/ImageGridWidget.h"
@ -39,6 +41,7 @@ public:
void OnServerDisconnected(const QString& serverName) override;
void OnWorkStatusChanged(BeltTearingWorkStatus status) override;
void OnErrorOccurred(const QString& errorMessage) override;
void OnClearTearingData() override;
protected:
void resizeEvent(QResizeEvent* event) override;
@ -63,6 +66,8 @@ private slots:
void onServerDataReceived(const QString& serverName, const QJsonObject& data);
// 设备点击事件处理槽函数
void onDeviceClicked(const QString& deviceAlias);
private:
void resizeToFitContent();

View File

@ -105,29 +105,32 @@ border: none;</string>
<string/>
</property>
<widget class="QLabel" name="label_work">
<property name="geometry">
<rect>
<x>1470</x>
<y>20</y>
<width>271</width>
<height>81</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>24</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 255, 255);</string>
</property>
<property name="text">
<string>工作状态</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<property name="geometry">
<rect>
<x>1470</x>
<y>20</y>
<width>271</width>
<height>81</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>24</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 255, 255);</string>
</property>
<property name="text">
<string>工作状态</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
<widget class="QPushButton" name="btn_close">
<property name="geometry">
<rect>
@ -183,6 +186,9 @@ background-color: rgba(255, 255, 255, 0);</string>
<property name="text">
<string/>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
<widget class="QPushButton" name="btn_stop">
<property name="geometry">
@ -205,6 +211,9 @@ background-color: rgba(255, 255, 255, 0);</string>
<property name="text">
<string/>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
<widget class="QPushButton" name="btn_algo_config">
<property name="geometry">

View File

@ -12,6 +12,18 @@ TearingDataTableWidget::TearingDataTableWidget(QWidget *parent)
: QWidget(parent)
, m_tableWidget(nullptr)
{
// 初始化与服务端颜色协调的颜色数组
m_tearColors[0] = QColor(255, 105, 97); // 珊瑚红
m_tearColors[1] = QColor(255, 160, 122); // 浅鲑鱼色
m_tearColors[2] = QColor(173, 216, 230); // 浅蓝色
m_tearColors[3] = QColor(144, 238, 144); // 浅绿色
m_tearColors[4] = QColor(255, 182, 193); // 浅粉色
m_tearColors[5] = QColor(221, 160, 221); // 梅花色
m_tearColors[6] = QColor(255, 215, 0); // 金色
m_tearColors[7] = QColor(240, 128, 128); // 玫瑰色
m_tearColors[8] = QColor(135, 206, 250); // 天蓝色
m_tearColors[9] = QColor(127, 255, 212); // 碧绿色
setupUI();
setupTable();
}
@ -36,7 +48,7 @@ void TearingDataTableWidget::setupTable()
// 设置表头标签
QStringList headers;
// headers << "ID" << "等级" << "状态" << "类型" /*<< "起始行" << "结束行" */<< "深度" << "宽度" << "长度" << "老化";
headers << "ID" << "状态" << "类型" << "深度(mm)" << "宽度(mm)" << "长度(mm)";
headers << "设备" << "编号" << "状态" << "类型" << "深度(mm)" << "宽度(mm)" << "长度(mm)" << "时间";
// 设置表格属性
m_tableWidget->setColumnCount(headers.size());
@ -54,12 +66,14 @@ void TearingDataTableWidget::setupTable()
m_tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
// 设置列宽
m_tableWidget->setColumnWidth(0, 62); // ID列
m_tableWidget->setColumnWidth(1, 100); // 状态列
m_tableWidget->setColumnWidth(2, 80); // 类型列
m_tableWidget->setColumnWidth(3, 100); // 深度列
m_tableWidget->setColumnWidth(4, 100); // 宽度列
m_tableWidget->setColumnWidth(5, 100); // 长度列
m_tableWidget->setColumnWidth(0, 85); // 名称
m_tableWidget->setColumnWidth(1, 62); // ID列
m_tableWidget->setColumnWidth(2, 50); // 状态列
m_tableWidget->setColumnWidth(3, 50); // 类型列
m_tableWidget->setColumnWidth(4, 65); // 深度列
m_tableWidget->setColumnWidth(5, 65); // 宽度列
m_tableWidget->setColumnWidth(6, 65); // 长度列
m_tableWidget->setColumnWidth(7, 100); // 时间列
#endif
// 设置样式表
m_tableWidget->setStyleSheet(
@ -81,45 +95,39 @@ void TearingDataTableWidget::setupTable()
// 启用表格排序功能
m_tableWidget->setSortingEnabled(true);
// 设置默认按ID列序排序
m_tableWidget->sortItems(0, Qt::AscendingOrder);
// 设置默认按ID列序排序
m_tableWidget->sortItems(1, Qt::DescendingOrder);
}
void TearingDataTableWidget::addData(const TearingData &data)
{
// 禁用排序以提高性能,批量处理后再启用
m_tableWidget->setSortingEnabled(false);
// 查找是否已存在相同ID的行
int existingRow = findExistingRowById(data.id);
int row;
if (existingRow >= 0) {
// 如果存在相同ID更新该行
row = existingRow;
} else {
// 如果不存在,插入新行
row = m_tableWidget->rowCount();
m_tableWidget->insertRow(row);
}
void TearingDataTableWidget::_AddDataToTable(const QString devName, const TearingData &data, int row)
{
QTableWidgetItem *nameItem = new QTableWidgetItem(devName);
nameItem->setTextAlignment(Qt::AlignCenter);
m_tableWidget->setItem(row, 0, nameItem);
// 使用结构体数据填充表格,状态和类型显示中文,并设置居中对齐
NumericTableWidgetItem *idItem = new NumericTableWidgetItem(data.id);
idItem->setTextAlignment(Qt::AlignCenter); // ID列文字居中显示
m_tableWidget->setItem(row, 0, idItem);
m_tableWidget->setItem(row, 1, idItem);
QTableWidgetItem *statusItem = new QTableWidgetItem(getTearStatusText(data.tearStatus));
statusItem->setTextAlignment(Qt::AlignCenter); // 状态列文字居中显示
m_tableWidget->setItem(row, 1, statusItem);
m_tableWidget->setItem(row, 2, statusItem);
QTableWidgetItem *typeItem = new QTableWidgetItem(getTearTypeText(data.tearType));
typeItem->setTextAlignment(Qt::AlignCenter); // 类型列文字居中显示
m_tableWidget->setItem(row, 2, typeItem);
m_tableWidget->setItem(row, 3, typeItem);
m_tableWidget->setItem(row, 3, new QTableWidgetItem(data.tearDepth));
m_tableWidget->setItem(row, 4, new QTableWidgetItem(data.tearWidth));
m_tableWidget->setItem(row, 5, new QTableWidgetItem(data.tearLength));
// m_tableWidget->setItem(row, 5, new QTableWidgetItem(data.statLineIdx));
m_tableWidget->setItem(row, 4, new QTableWidgetItem(data.tearDepth));
m_tableWidget->setItem(row, 5, new QTableWidgetItem(data.tearWidth));
m_tableWidget->setItem(row, 6, new QTableWidgetItem(data.tearLength));
// 添加时间列
QString currentTime = QString::fromStdString(CVrDateUtils::GetStrNowTime(false));
QTableWidgetItem *timeItem = new QTableWidgetItem(currentTime);
timeItem->setTextAlignment(Qt::AlignCenter);
m_tableWidget->setItem(row, 7, timeItem);
// 设置item的文本颜色
for (int i = 0; i < m_tableWidget->horizontalHeader()->count(); i++) {
@ -128,15 +136,47 @@ void TearingDataTableWidget::addData(const TearingData &data)
item->setForeground(QBrush(Qt::white));
}
}
}
void TearingDataTableWidget::addData(const QString devName, const TearingData &data)
{
// 构造键值
QString key = devName + ":" + data.id;
// 禁用排序以提高性能,批量处理后再启用
m_tableWidget->setSortingEnabled(false);
// 查找是否已存在相同设备名称和ID的行
int existingRow = -1;
if (m_devIdSet.contains(key)) {
existingRow = findExistingRowById(devName, data.id);
}
int row;
if (existingRow >= 0) {
// 如果存在相同设备名称和ID更新该行
row = existingRow;
} else {
// 如果不存在,插入新行
row = m_tableWidget->rowCount();
m_tableWidget->insertRow(row);
// 添加到集合中
m_devIdSet.insert(key);
}
_AddDataToTable(devName, data, row);
// 重新启用排序
m_tableWidget->setSortingEnabled(true);
// 手动触发一次按ID列倒序排序
m_tableWidget->sortItems(1, Qt::DescendingOrder);
// 检查是否需要限制行数
limitRowsIfNeeded();
}
void TearingDataTableWidget::addData(const std::vector<TearingData> &dataList)
void TearingDataTableWidget::addData(const QString devName, const std::vector<TearingData> &dataList)
{
if (dataList.empty()) {
return;
@ -148,21 +188,21 @@ void TearingDataTableWidget::addData(const std::vector<TearingData> &dataList)
// 遍历vector中的所有数据并添加到表格
for (const auto &data : dataList) {
addData(data);
addData(devName, data);
}
// 重新启用更新和排序
m_tableWidget->setUpdatesEnabled(true);
m_tableWidget->setSortingEnabled(true);
// 手动触发一次排序
m_tableWidget->sortItems(0, Qt::AscendingOrder);
// 手动触发一次按ID列倒序排序
m_tableWidget->sortItems(1, Qt::DescendingOrder);
// 检查是否需要限制行数
limitRowsIfNeeded();
}
void TearingDataTableWidget::addDataBatch(const std::vector<TearingData> &dataList)
void TearingDataTableWidget::addDataBatch(const QString devName, const std::vector<TearingData> &dataList)
{
if (dataList.empty()) {
return;
@ -180,7 +220,8 @@ void TearingDataTableWidget::addDataBatch(const std::vector<TearingData> &dataLi
// 先找出需要新增的数据量
int rowsToAdd = 0;
for (const auto &data : dataList) {
if (findExistingRowById(data.id) < 0) {
QString key = devName + ":" + data.id;
if (!m_devIdSet.contains(key)) {
rowsToAdd++;
}
}
@ -192,62 +233,37 @@ void TearingDataTableWidget::addDataBatch(const std::vector<TearingData> &dataLi
// 填充数据
for (const auto &data : dataList) {
// 查找是否已存在相同ID的行
int existingRow = findExistingRowById(data.id);
// 构造键值
QString key = devName + ":" + data.id;
// 查找是否已存在相同设备名称和ID的行
int existingRow = -1;
if (m_devIdSet.contains(key)) {
existingRow = findExistingRowById(devName, data.id);
}
int row;
if (existingRow >= 0) {
// 如果存在相同ID更新该行
// 如果存在相同设备名称和ID更新该行
row = existingRow;
} else {
// 如果不存在,使用新行
row = m_tableWidget->rowCount() - rowsToAdd;
rowsToAdd--; // 减少剩余需要分配的行数
// 添加到集合中
m_devIdSet.insert(key);
}
// 使用结构体数据填充表格,状态和类型显示中文,并设置居中对齐
NumericTableWidgetItem *idItem = new NumericTableWidgetItem(data.id);
idItem->setTextAlignment(Qt::AlignCenter); // ID列文字居中显示
m_tableWidget->setItem(row, 0, idItem);
QTableWidgetItem *statusItem = new QTableWidgetItem(getTearStatusText(data.tearStatus));
statusItem->setTextAlignment(Qt::AlignCenter); // 状态列文字居中显示
m_tableWidget->setItem(row, 1, statusItem);
QTableWidgetItem *typeItem = new QTableWidgetItem(getTearTypeText(data.tearType));
typeItem->setTextAlignment(Qt::AlignCenter); // 类型列文字居中显示
m_tableWidget->setItem(row, 2, typeItem);
m_tableWidget->setItem(row, 3, new QTableWidgetItem(data.tearDepth));
m_tableWidget->setItem(row, 4, new QTableWidgetItem(data.tearWidth));
m_tableWidget->setItem(row, 5, new QTableWidgetItem(data.tearLength));
// 设置item的文本颜色
for (int i = 0; i < m_tableWidget->horizontalHeader()->count(); i++) {
QTableWidgetItem *item = m_tableWidget->item(row, i);
if (item) {
item->setForeground(QBrush(Qt::white));
}
}
_AddDataToTable(devName, data, row);
}
// 重新启用更新和排序
m_tableWidget->setUpdatesEnabled(true);
m_tableWidget->setSortingEnabled(true);
// 手动触发一次排序
m_tableWidget->sortItems(0, Qt::AscendingOrder);
}
void TearingDataTableWidget::addData(const QJsonObject &json)
{
// 添加空指针检查防止在m_tableWidget为nullptr时崩溃
if (!m_tableWidget) {
return;
}
TearingData data = TearingData::fromJsonObject(json);
addData(data);
// 手动触发一次按ID列倒序排序
m_tableWidget->sortItems(1, Qt::DescendingOrder);
}
void TearingDataTableWidget::clearData()
@ -260,9 +276,13 @@ void TearingDataTableWidget::clearData()
// 清除所有行数据
m_tableWidget->setRowCount(0);
// 重置排序状态
m_tableWidget->setSortingEnabled(false);
m_tableWidget->setSortingEnabled(true);
// 清空集合
m_devIdSet.clear();
// 重置排序状态默认按ID列倒序排序
m_tableWidget->setSortingEnabled(false);
m_tableWidget->setSortingEnabled(true);
m_tableWidget->sortItems(1, Qt::DescendingOrder);
}
}
@ -272,6 +292,20 @@ void TearingDataTableWidget::setMaximumRows(int maxRows)
// m_maxRows = maxRows;
}
void TearingDataTableWidget::removeRowFromSet(int row)
{
// 从表格中获取设备名称和ID
if (row >= 0 && row < m_tableWidget->rowCount()) {
QTableWidgetItem *devItem = m_tableWidget->item(row, 0); // 设备名称在第0列
QTableWidgetItem *idItem = m_tableWidget->item(row, 1); // ID在第1列
if (devItem && idItem) {
QString key = devItem->text() + ":" + idItem->text();
m_devIdSet.remove(key);
}
}
}
void TearingDataTableWidget::limitRowsIfNeeded()
{
// 添加空指针检查防止在m_tableWidget为nullptr时崩溃
@ -287,6 +321,8 @@ void TearingDataTableWidget::limitRowsIfNeeded()
// 删除最旧的行(假设新数据在后面)
for (int i = 0; i < rowsToDelete; i++) {
// 从集合中移除
removeRowFromSet(0);
m_tableWidget->removeRow(0);
}
@ -301,13 +337,13 @@ QString TearingDataTableWidget::getTearStatusText(const QString &status)
if (status == "0" || status.contains("Uknown")) {
return "未知";
} else if (status == "1" || status.contains("New")) {
return "发现";
return "";
} else if (status == "2" || status.contains("Growing")) {
return "正在增长";
return "进行";
} else if (status == "3" || status.contains("Ended")) {
return "撕裂结束";
return "结束";
} else if (status == "4" || status.contains("Invalid")) {
return "无效撕裂";
return "无效";
}
return status; // 如果无法识别,返回原值
}
@ -325,19 +361,22 @@ QString TearingDataTableWidget::getTearTypeText(const QString &type)
return type; // 如果无法识别,返回原值
}
int TearingDataTableWidget::findExistingRowById(const QString &id)
int TearingDataTableWidget::findExistingRowById(const QString &devName, const QString &id)
{
// 在表格中查找具有相同ID的行
// 使用二分查找优化性能假设表格按ID排序
int rowCount = m_tableWidget->rowCount();
if (rowCount == 0) {
return -1;
// 构造键值
QString key = devName + ":" + id;
// 使用哈希表快速判断是否存在
if (!m_devIdSet.contains(key)) {
return -1; // 不存在直接返回-1
}
// 简单的线性查找(在实际应用中,如果数据量很大,可以考虑使用哈希表或其他数据结构来优化)
// 如果存在,则在表格中查找具体行号(虽然概率很小,但为了确保数据一致性仍需查找)
int rowCount = m_tableWidget->rowCount();
for (int row = 0; row < rowCount; ++row) {
QTableWidgetItem *item = m_tableWidget->item(row, 0); // ID在第0列
if (item && item->text() == id) {
QTableWidgetItem *devItem = m_tableWidget->item(row, 0); // 设备名称在第0列
QTableWidgetItem *idItem = m_tableWidget->item(row, 1); // ID在第1列
if (devItem && idItem && devItem->text() == devName && idItem->text() == id) {
return row;
}
}

View File

@ -7,8 +7,10 @@
#include <QHeaderView>
#include <QJsonObject>
#include <QString>
#include <QSet>
#include <vector>
#include "IStatusUpdate.h"
#include "VrDateUtils.h"
// 自定义TableWidgetItem类支持按数字值排序
class NumericTableWidgetItem : public QTableWidgetItem
@ -36,30 +38,34 @@ public:
explicit TearingDataTableWidget(QWidget *parent = nullptr);
~TearingDataTableWidget();
// 添加单条数据
void addData(const TearingData &data);
// 添加多条数据(自动选择优化方法)
void addData(const std::vector<TearingData> &dataList);
// 添加单条JSON数据
void addData(const QJsonObject &data);
void addData(const QString devName, const TearingData &dataList);
// 添加多条数据(自动选择优化方法)
void addData(const QString devName, const std::vector<TearingData> &dataList);
// 批量添加数据,优化大量数据处理性能
void addDataBatch(const std::vector<TearingData> &dataList);
void addDataBatch(const QString devName, const std::vector<TearingData> &dataList);
// 清除所有数据
void clearData();
// 设置表格最大行数限制,防止内存占用过大
void setMaximumRows(int maxRows);
// 从集合中移除指定行的设备ID组合
void removeRowFromSet(int row);
private:
void setupUI();
void setupTable();
void _AddDataToTable(const QString devName, const TearingData &data, int row);
QString getTearStatusText(const QString &status);
QString getTearTypeText(const QString &type);
int findExistingRowById(const QString &id);
int findExistingRowById(const QString &devName, const QString &id);
// 内存管理相关方法
void limitRowsIfNeeded();
@ -67,8 +73,14 @@ private:
// 排序相关的私有方法
void connectHeaderSignals();
// 与服务端颜色协调的颜色数组
QColor m_tearColors[10];
QTableWidget *m_tableWidget;
int m_maxRows = -1; // 最大行数限制,-1表示无限制
// 使用QSet存储设备名称和ID的组合格式为"设备名:ID"
QSet<QString> m_devIdSet;
};
#endif // TEARINGDATATABLEWIDGET_H

View File

@ -581,9 +581,7 @@ void BeltTearingPresenter::sendTearingResults(const std::vector<SSG_beltTearingI
// 发送到所有连接的客户端
bool success = m_tcpServer->SendAllData(package.constData(), package.size());
if (success) {
LOG_DEBUG("Sent tearing results (%d bytes) to %d clients\n", package.size(), m_clients.size());
} else {
if (!success) {
LOG_WARNING("Failed to send tearing results to all clients\n");
}
}
@ -808,9 +806,7 @@ void BeltTearingPresenter::sendImageToClients(const QImage& image)
// 发送给所有连接的客户端
bool success = m_tcpServer->SendAllData(packet.constData(), packet.size());
if (success) {
LOG_DEBUG("Sent point cloud image (%d bytes) to %d clients\n", packet.size(), m_clients.size());
} else {
if (!success) {
LOG_WARNING("Failed to send image data to all clients\n");
}
@ -874,6 +870,37 @@ void BeltTearingPresenter::handleAlgorithmParameterUpdate(const QByteArray& para
} else if (command == "getServerInfo") {
// 处理服务器信息获取请求
handleGetServerInfo(paramObj);
} else if (command == "resetDetect") {
// 处理重新检测请求
LOG_INFO("Received reset detect command from client\n");
ResetDetect();
// 发送响应给客户端
QJsonObject responseObj;
responseObj["command"] = "resetDetectResponse";
responseObj["status"] = "success";
responseObj["message"] = "Detection reset completed";
responseObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
QJsonDocument responseDoc(responseObj);
QByteArray responseData = responseDoc.toJson();
// 构建数据包并发送给所有客户端
if (!m_clients.isEmpty() && m_tcpServer) {
QByteArray package;
QDataStream stream(&package, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint8(static_cast<quint8>(ByteDataType::Text)) << quint32(responseData.size());
stream.writeRawData(responseData.constData(), responseData.size());
package.append("___END___\r\n");
bool success = m_tcpServer->SendAllData(package.constData(), package.size());
if (success) {
LOG_INFO("Reset detect response sent to clients\n");
} else {
LOG_WARNING("Failed to send reset detect response to clients\n");
}
}
} else {
LOG_WARNING("Unknown command: %s\n", command.toStdString().c_str());
return;
@ -1120,7 +1147,7 @@ int BeltTearingPresenter::_DetectTask()
// 临时存储从队列中取出的数据,避免重复处理
std::vector<SVzNL3DLaserLine> algorithmDataCache;
std::vector<std::vector<SVzNL3DPosition>> imageScanLines;
// 从队列中获取数据
{
std::lock_guard<std::mutex> queueLock(m_queueMutex);
@ -1158,9 +1185,9 @@ int BeltTearingPresenter::_DetectTask()
if (line.p3DPoint && line.nPointCount > 0) {
std::vector<SVzNL3DPosition> linePoints;
linePoints.reserve(line.nPointCount); // 预分配内存提高效率
SVzNL3DPosition* p3DPoints = static_cast<SVzNL3DPosition*>(line.p3DPoint);
// 使用更高效的批量复制
linePoints.assign(p3DPoints, p3DPoints + line.nPointCount);
imageScanLines.emplace_back(std::move(linePoints)); // 使用move避免拷贝
@ -1168,13 +1195,6 @@ int BeltTearingPresenter::_DetectTask()
}
}
// 使用lambda线程分离图像处理和算法处理
std::thread imageThread([this, imageScanLines]() {
std::vector<std::vector<SVzNL3DPoint>> emptyResults; // 空的检测结果
QImage image = PointCloudImageUtils::GeneratePointCloudImage(imageScanLines, emptyResults, 800, 600);
sendImageToClients(image);
});
// 调用SDK算法进行皮带撕裂检测
int errorCode = 0;
std::vector<SSG_beltTearingInfo> allTearings;
@ -1213,15 +1233,17 @@ int BeltTearingPresenter::_DetectTask()
);
// 合并各个撕裂结果容器
// _MergeAndReplace(allTearings, m_beltTearings_new);
// _MergeAndReplace(allTearings, m_beltTearings_growing);
// _MergeAndReplace(allTearings, m_beltTearings_ended);
#if 1
_MergeAndReplace(allTearings, m_beltTearings_new);
_MergeAndReplace(allTearings, m_beltTearings_growing);
_MergeAndReplace(allTearings, m_beltTearings_ended);
// _MergeAndReplace(allTearings, m_beltTearings_unknown);
#else
allTearings.insert(allTearings.end(), m_beltTearings_new.begin(), m_beltTearings_new.end());
allTearings.insert(allTearings.end(), m_beltTearings_growing.begin(), m_beltTearings_growing.end());
allTearings.insert(allTearings.end(), m_beltTearings_ended.begin(), m_beltTearings_ended.end());
// allTearings.insert(allTearings.end(), m_beltTearings_unknown.begin(), m_beltTearings_unknown.end());
#endif
}
// 发送检测结果
@ -1229,7 +1251,7 @@ int BeltTearingPresenter::_DetectTask()
sendTearingResults(allTearings);
SendDetectionResultToRobot(allTearings); // 发送检测结果到机械臂
}
// 释放深拷贝的点云数据
for (auto& algorithmData : algorithmDataCache) {
if (algorithmData.p3DPosition) {
@ -1238,10 +1260,10 @@ int BeltTearingPresenter::_DetectTask()
}
}
// 等待图像线程完成
if (imageThread.joinable()) {
imageThread.join();
}
// 在算法处理完成后,生成带撕裂检测结果的图像
QImage image = PointCloudImageUtils::GeneratePointCloudImage(imageScanLines, allTearings, 800, 600);
sendImageToClients(image);
return 0; // 成功完成检测任务
}

View File

@ -6,13 +6,14 @@
#include "VrLog.h"
#include "VrTimeUtils.h"
#include "beltTearingDetection_Export.h"
#ifndef PI
#define PI 3.14159265358979323846
#endif
QImage PointCloudImageUtils::GeneratePointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
const std::vector<std::vector<SVzNL3DPoint>>& detectionResults,
const std::vector<SSG_beltTearingInfo>& beltTearings,
int imageWidth, int imageHeight)
{
if (scanLines.empty() || imageWidth <= 0 || imageHeight <= 0) {
@ -23,7 +24,7 @@ QImage PointCloudImageUtils::GeneratePointCloudImage(const std::vector<std::vect
// 快速计算点云范围
double xMin, xMax, yMin, yMax;
if (!CalculateRangeFast(scanLines, xMin, xMax, yMin, yMax)) {
LOG_WARNING("No valid points found in scan lines\n");
// LOG_WARNING("No valid points found in scan lines, arg=%d count=%d\n", scanLines.size(), scanLines.size() ? scanLines[0].size() : 0);
return QImage();
}
@ -46,17 +47,24 @@ QImage PointCloudImageUtils::GeneratePointCloudImage(const std::vector<std::vect
DrawPointCloudDirect(image, scanLines, xMin, xMax, yMin, yMax, imageWidth, imageHeight);
// 绘制检测结果(如果需要)
if (!detectionResults.empty()) {
QPainter painter(&image);
DrawLapWeldResults(painter, detectionResults, xMin, xMax, yMin, yMax, imageWidth, imageHeight);
QPainter painter(&image);
if (!beltTearings.empty()) {
DrawBeltTearingResults(painter, beltTearings, xMin, xMax, yMin, yMax, imageWidth, imageHeight);
}
return image;
}
bool PointCloudImageUtils::CalculateRangeFast(const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
double& xMin, double& xMax,
double& yMin, double& yMax)
double& xMin, double& xMax, double& yMin, double& yMax)
{
if (scanLines.empty()) {
// 设置默认范围以避免无效计算
xMin = xMax = yMin = yMax = 0.0;
return false;
}
// 使用局部变量减少内存写入
double localXMin = std::numeric_limits<double>::max();
double localXMax = std::numeric_limits<double>::lowest();
@ -107,6 +115,18 @@ void PointCloudImageUtils::DrawPointCloudDirect(QImage& image,
{
if (scanLines.empty()) return;
// 检查范围是否有效,避免除零错误
if (xMax <= xMin || yMax <= yMin) {
LOG_WARNING("Invalid range for drawing: xMin=%f, xMax=%f, yMin=%f, yMax=%f\n", xMin, xMax, yMin, yMax);
return;
}
// 检查图像尺寸是否有效
if (imageWidth <= 0 || imageHeight <= 0) {
LOG_WARNING("Invalid image dimensions: width=%d, height=%d\n", imageWidth, imageHeight);
return;
}
// 预计算缩放因子和偏移量
const double xScale = imageWidth / (xMax - xMin);
const double yScale = imageHeight / (yMax - yMin);
@ -170,8 +190,20 @@ void PointCloudImageUtils::DrawLapWeldResults(QPainter& painter,
{
if (weldResults.empty()) return;
double xScale = (xMax - xMin) / imageWidth;
double yScale = (yMax - yMin) / imageHeight;
// 检查范围是否有效,避免除零错误
if (xMax <= xMin || yMax <= yMin) {
LOG_WARNING("Invalid range for drawing: xMin=%f, xMax=%f, yMin=%f, yMax=%f\n", xMin, xMax, yMin, yMax);
return;
}
// 检查图像尺寸是否有效
if (imageWidth <= 0 || imageHeight <= 0) {
LOG_WARNING("Invalid image dimensions: width=%d, height=%d\n", imageWidth, imageHeight);
return;
}
double xScale = imageWidth / (xMax - xMin);
double yScale = imageHeight / (yMax - yMin);
// 使用不同颜色绘制每条焊缝
QColor weldColors[] = {
@ -195,10 +227,10 @@ void PointCloudImageUtils::DrawLapWeldResults(QPainter& painter,
// 绘制焊缝线段
for (size_t j = 1; j < weldLine.size(); j++) {
int px1 = (int)((weldLine[j-1].x - xMin) / xScale);
int py1 = (int)((weldLine[j-1].y - yMin) / yScale);
int px2 = (int)((weldLine[j].x - xMin) / xScale);
int py2 = (int)((weldLine[j].y - yMin) / yScale);
int px1 = (int)((weldLine[j-1].x - xMin) * xScale);
int py1 = (int)((weldLine[j-1].y - yMin) * yScale);
int px2 = (int)((weldLine[j].x - xMin) * xScale);
int py2 = (int)((weldLine[j].y - yMin) * yScale);
if (px1 >= 0 && px1 < imageWidth && py1 >= 0 && py1 < imageHeight &&
px2 >= 0 && px2 < imageWidth && py2 >= 0 && py2 < imageHeight) {
@ -209,8 +241,8 @@ void PointCloudImageUtils::DrawLapWeldResults(QPainter& painter,
// 在起点和终点绘制标记
if (!weldLine.empty()) {
// 起点标记 - 圆形
int startX = (int)((weldLine[0].x - xMin) / xScale);
int startY = (int)((weldLine[0].y - yMin) / yScale);
int startX = (int)((weldLine[0].x - xMin) * xScale);
int startY = (int)((weldLine[0].y - yMin) * yScale);
if (startX >= 0 && startX < imageWidth && startY >= 0 && startY < imageHeight) {
painter.setPen(QPen(weldColor, 2));
painter.setBrush(QBrush(weldColor));
@ -218,8 +250,8 @@ void PointCloudImageUtils::DrawLapWeldResults(QPainter& painter,
}
// 终点标记 - 方形
int endX = (int)((weldLine.back().x - xMin) / xScale);
int endY = (int)((weldLine.back().y - yMin) / yScale);
int endX = (int)((weldLine.back().x - xMin) * xScale);
int endY = (int)((weldLine.back().y - yMin) * yScale);
if (endX >= 0 && endX < imageWidth && endY >= 0 && endY < imageHeight) {
painter.setPen(QPen(weldColor, 2));
painter.setBrush(QBrush(weldColor));
@ -228,3 +260,68 @@ void PointCloudImageUtils::DrawLapWeldResults(QPainter& painter,
}
}
}
void PointCloudImageUtils::DrawBeltTearingResults(QPainter& painter,
const std::vector<SSG_beltTearingInfo>& tearings,
double xMin, double xMax, double yMin, double yMax,
int imageWidth, int imageHeight)
{
if (tearings.empty()) return;
// 检查范围是否有效,避免除零错误
if (xMax <= xMin || yMax <= yMin) {
LOG_WARNING("Invalid range for drawing: xMin=%f, xMax=%f, yMin=%f, yMax=%f\n", xMin, xMax, yMin, yMax);
return;
}
// 检查图像尺寸是否有效
if (imageWidth <= 0 || imageHeight <= 0) {
LOG_WARNING("Invalid image dimensions: width=%d, height=%d\n", imageWidth, imageHeight);
return;
}
double xScale = imageWidth / (xMax - xMin);
double yScale = imageHeight / (yMax - yMin);
// 使用10种协调的颜色根据撕裂ID进行绘制
QColor tearColors[] = {
QColor(255, 105, 97), // 珊瑚红
QColor(255, 160, 122), // 浅鲑鱼色
QColor(173, 216, 230), // 浅蓝色
QColor(144, 238, 144), // 浅绿色
QColor(255, 182, 193), // 浅粉色
QColor(221, 160, 221), // 梅花色
QColor(255, 215, 0), // 金色
QColor(240, 128, 128), // 玫瑰色
QColor(135, 206, 250), // 天蓝色
QColor(127, 255, 212) // 碧绿色
};
int numColors = sizeof(tearColors) / sizeof(tearColors[0]);
for (size_t i = 0; i < tearings.size(); i++) {
const auto& tearing = tearings[i];
// 根据撕裂ID选择颜色确保相同ID使用相同颜色
QColor tearColor = tearColors[tearing.tearID % numColors];
// 如果撕裂信息包含点集,则绘制点(每隔一定数量的点绘制一个,减少密度)
#if OUTPUT_TEARING_POINTS
if (!tearing.pts.empty()) {
// 每隔3个点绘制一个点减少点的密度
const int pointInterval = 3;
for (size_t idx = 0; idx < tearing.pts.size(); idx += pointInterval) {
const auto& point = tearing.pts[idx];
int px = (int)((point.pt3D.x - xMin) * xScale);
int py = (int)((point.pt3D.y - yMin) * yScale);
if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) {
// 设置画笔和画刷用于绘制点
painter.setPen(QPen(tearColor, 1)); // 减小画笔宽度
painter.setBrush(QBrush(tearColor));
// 绘制点为小圆圈,减小点的大小
painter.drawEllipse(px - 1, py - 1, 2, 2);
}
}
}
#endif
}
}

View File

@ -8,6 +8,7 @@
#include "VZNL_Types.h"
#include "SG_baseDataType.h"
#include "beltTearingDetection_Export.h"
struct PointRenderData {
double x, y;
@ -21,7 +22,7 @@ class PointCloudImageUtils
public:
// 点云转图像 - 使用std::vector格式的扫描线数据
static QImage GeneratePointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
const std::vector<std::vector<SVzNL3DPoint>>& detectionResults,
const std::vector<SSG_beltTearingInfo>& beltTearings,
int imageWidth, int imageHeight);
@ -55,11 +56,11 @@ private:
double xMin, double xMax, double yMin, double yMax,
int imageWidth, int imageHeight);
// 绘制scan lines点云数据 (原始版本)
static void DrawScanLinesPointCloud(QPainter& painter,
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
double xMin, double xMax, double yMin, double yMax,
int imageWidth, int imageHeight);
// 绘制皮带撕裂检测结果
static void DrawBeltTearingResults(QPainter& painter,
const std::vector<SSG_beltTearingInfo>& tearings,
double xMin, double xMax, double yMin, double yMax,
int imageWidth, int imageHeight);
// 优化版本:批量绘制点云
static void DrawPointCloudOptimized(QPainter& painter,

View File

@ -2,7 +2,7 @@
#define VERSION_H
#define BELT_TEARING_SERVER_VERSION_STRING "2.0.1"
#define BELT_TEARING_SERVER_VERSION_BUILD "4"
#define BELT_TEARING_SERVER_VERSION_BUILD "5"
#define BELT_TEARING_SERVER_PRODUCT_NAME "BeltTearingServer"
#define BELT_TEARING_SERVER_COMPANY_NAME "VisionRobot"
#define BELT_TEARING_SERVER_COPYRIGHT "Copyright (C) 2024 VisionRobot. All rights reserved."

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "BeltTearingApp"
#define MyAppVersion "2.0.1.4"
#define MyAppVersion "2.0.1.5"
#define MyAppPublisher ""
#define MyAppURL ""
#define MyAppExeName "BeltTearingApp.exe"
@ -12,7 +12,7 @@
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{7EFE68AC-05BD-4BA2-B420-2C71094031A1}
AppId={{7EFE68AC-05BD-4BA2-B420-2C71094031A1}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName}_V{#MyAppVersion}

View File

@ -118,28 +118,35 @@ cat > ${POSTINST_PATH} << 'EOF'
echo "配置 BeltTearingServer 应用程序..."
# 安装并启用 belttearingserver.service
if [ -f /opt/belttearingserver/belttearingserver.service ]; then
SERVICE_FILE="/opt/belttearingserver/belttearingserver.service"
TARGET_SERVICE_FILE="/etc/systemd/system/belttearingserver.service"
if [ -f "$SERVICE_FILE" ]; then
echo "安装 belttearingserver.service..."
# 检查服务是否已经存在,如果存在则先停止并禁用
if systemctl is-active --quiet belttearingserver.service; then
echo "检测到已运行的 belttearingserver.service正在停止..."
if systemctl is-active --quiet belttearingserver.service 2>/dev/null; then
echo "检测到已运行的 belttearingserver.service,正在停止..."
systemctl stop belttearingserver.service
fi
if systemctl is-enabled --quiet belttearingserver.service; then
echo "检测到已启用的 belttearingserver.service正在禁用..."
if systemctl is-enabled --quiet belttearingserver.service 2>/dev/null; then
echo "检测到已启用的 belttearingserver.service,正在禁用..."
systemctl disable belttearingserver.service
fi
# 复制服务文件
cp /opt/belttearingserver/belttearingserver.service /etc/systemd/system/
cp "$SERVICE_FILE" "$TARGET_SERVICE_FILE"
# 设置正确的权限
chmod 644 "$TARGET_SERVICE_FILE"
systemctl daemon-reload
systemctl enable belttearingserver.service
systemctl start belttearingserver.service
echo "belttearingserver.service 已安装并设置为开机自启"
else
echo "错误: 未找到 belttearingserver.service 文件"
echo "请检查文件是否存在: $SERVICE_FILE"
exit 1
fi
echo "BeltTearingServer 应用程序安装完成!"
@ -164,11 +171,17 @@ rm -f /etc/systemd/system/belttearingserver.service
# 重新加载systemd
systemctl daemon-reload
# 清理 /opt/belttearingserver 目录
rm -rf /opt/belttearingserver
# 只有在完全卸载时才删除 /opt/belttearingserver 目录
if [ "$1" = "remove" ]; then
# 清理 /opt/belttearingserver 目录
rm -rf /opt/belttearingserver
echo "已完全卸载 BeltTearingServer 应用程序"
else
echo "更新无需删除应用程序"
fi
echo "BeltTearingServer 应用程序卸载完成!"
echo "端口映射配置已清理"
EOF
chmod +x ${POSTRM_PATH}

View File

@ -108,6 +108,10 @@ int CVrEyeDevice::OpenDevice(const char* sIP, bool bRGBD, bool bSwing, bool bFil
VzNL_BeginDetectLaser(m_pHandle);
VzNL_EnableSwingMotor(m_pHandle, bSwing ? VzTrue : VzFalse);
VzNL_EnableLaserLight(m_pHandle, VzTrue);
VzBool bRet = VzNL_IsEnableLaserLight(m_pHandle);
LOG_DEBUG("EnableLaserLight ret : %d\n", bRet);
SVzNLVersionInfo sVersionInfo;
VzNL_GetVersion(m_pHandle, &sVersionInfo);
LOG_DEBUG("version : %s\n", sVersionInfo.szSDKVersion);
@ -155,6 +159,7 @@ int CVrEyeDevice::StartDetect(VzNL_AutoOutputLaserLineExCB fCallFunc, EVzResultD
{
int nErrCode = SUCCESS;
if(!m_pHandle) return ERRCODE(DEV_NO_OPEN);
VzNL_SetLaserLight(m_pHandle, VzTrue);
LOG_DEBUG("StartDetect eDataType: %d\n", eDataType);
nErrCode = VzNL_StartAutoDetectEx(m_pHandle, eDataType, keFlipType_None, fCallFunc, param);
return nErrCode;

View File

@ -9,6 +9,11 @@ namespace CVrDateUtils
*/
std::string GetNowTime();
/**
* :-- ::
*/
std::string GetStrNowTime(bool bYear = true);
/**
*
*/

View File

@ -26,6 +26,43 @@ std::string CVrDateUtils::GetNowTime()
return std::string(date);
}
/**
* :-- ::
*/
std::string CVrDateUtils::GetStrNowTime(bool bYear)
{
time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
#ifdef _WIN32
struct tm ptminfo;
localtime_s(&ptminfo, &tt);
#else
struct tm ptminfo = *(localtime(&tt));
#endif
char date[60] = { 0 };
#ifdef _WIN32
if (bYear)
{
sprintf_s(date, "%04d-%02d-%02d %02d:%02d:%02d", ptminfo.tm_year + 1900, \
ptminfo.tm_mon + 1, ptminfo.tm_mday, ptminfo.tm_hour, ptminfo.tm_min, ptminfo.tm_sec);
}
else
{
sprintf_s(date, "%02d-%02d %02d:%02d:%02d", ptminfo.tm_mon + 1, ptminfo.tm_mday, ptminfo.tm_hour, ptminfo.tm_min, ptminfo.tm_sec);
}
#else
if (bYear)
{
sprintf(date, "%04d-%02d-%02d %02d:%02d:%02d", ptminfo.tm_year + 1900, \
ptminfo.tm_mon + 1, ptminfo.tm_mday, ptminfo.tm_hour, ptminfo.tm_min, ptminfo.tm_sec);
}
else
{
sprintf(date, "%02d-%02d %02d:%02d:%02d", ptminfo.tm_mon + 1, ptminfo.tm_mday, ptminfo.tm_hour, ptminfo.tm_min, ptminfo.tm_sec);
}
#endif
return std::string(date);
}
/**
*
*/