1127 lines
48 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "mainwindow.h"
#include <QCheckBox>
#include <QCloseEvent>
#include <QAbstractItemView>
#include <QDateTime>
#include <QDoubleSpinBox>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFontDatabase>
#include <QGridLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QHBoxLayout>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QLabel>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPixmap>
#include <QPushButton>
#include <QResizeEvent>
#include <QSpinBox>
#include <QSplitter>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextStream>
#include <QTabWidget>
#include <QTcpSocket>
#include <QTimer>
#include <QVBoxLayout>
#include <QDataStream>
#include <utility>
#include "PointCloudGLWidget.h"
#include "LaserDataLoader.h"
#include "VZNL_Types.h"
namespace
{
constexpr quint8 kTextType = 0x01;
constexpr quint8 kImageType = 0x02;
constexpr quint8 kReadConfigType = 0x03;
constexpr quint8 kWriteConfigType = 0x04;
constexpr quint8 kPointCloudType = 0x05;
constexpr int kFrameHeaderSize = 5;
constexpr int kFrameTailSize = 11;
constexpr quint64 kMaxPayloadSize = 256ULL * 1024ULL * 1024ULL;
const QByteArray kFrameTail("___END___\r\n");
const QByteArray kJsonFrameHeader("##START#");
const QByteArray kJsonFrameTail("#END");
constexpr int kJsonFrameHeaderSize = 8;
constexpr int kJsonFrameLengthSize = 8;
constexpr int kJsonFrameTailSize = 4;
constexpr quint32 kMaxJsonPayloadSize = 10U * 1024U * 1024U;
quint32 readUInt32BE(const QByteArray& data, int offset)
{
return (static_cast<quint32>(static_cast<quint8>(data.at(offset))) << 24)
| (static_cast<quint32>(static_cast<quint8>(data.at(offset + 1))) << 16)
| (static_cast<quint32>(static_cast<quint8>(data.at(offset + 2))) << 8)
| static_cast<quint32>(static_cast<quint8>(data.at(offset + 3)));
}
QString formatRange(double minValue, double maxValue)
{
return QStringLiteral("%1 .. %2").arg(minValue, 0, 'f', 3).arg(maxValue, 0, 'f', 3);
}
}
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent),
m_socket(new QTcpSocket(this))
{
setupUi();
connect(m_socket, &QTcpSocket::connected, this, &MainWindow::onConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &MainWindow::onDisconnected);
connect(m_socket, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(m_socket, &QAbstractSocket::errorOccurred,
this, &MainWindow::onSocketError);
#else
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(onSocketError(QAbstractSocket::SocketError)));
#endif
updateConnectionControls(false);
}
MainWindow::~MainWindow()
{
disconnectFromServer();
}
void MainWindow::setupUi()
{
setWindowTitle(QStringLiteral("BeltTearing TCP 协议测试"));
resize(1080, 760);
setMinimumSize(900, 620);
auto* central = new QWidget(this);
auto* mainLayout = new QVBoxLayout(central);
mainLayout->setContentsMargins(6, 6, 6, 6);
mainLayout->setSpacing(4);
auto* connectionGroup = new QGroupBox(QStringLiteral("连接"), central);
auto* connectionLayout = new QGridLayout(connectionGroup);
connectionLayout->setContentsMargins(8, 6, 8, 6);
connectionLayout->setHorizontalSpacing(6);
connectionLayout->setVerticalSpacing(4);
m_hostEdit = new QLineEdit(QStringLiteral("127.0.0.1"), connectionGroup);
m_portSpin = new QSpinBox(connectionGroup);
m_portSpin->setRange(1, 65535);
m_portSpin->setValue(5800);
m_connectButton = new QPushButton(QStringLiteral("连接"), connectionGroup);
m_disconnectButton = new QPushButton(QStringLiteral("断开"), connectionGroup);
m_connectionLabel = new QLabel(QStringLiteral("未连接"), connectionGroup);
connectionLayout->addWidget(new QLabel(QStringLiteral("服务器地址"), connectionGroup), 0, 0);
connectionLayout->addWidget(m_hostEdit, 0, 1);
connectionLayout->addWidget(new QLabel(QStringLiteral("BeltTearing TCP 端口"), connectionGroup), 0, 2);
connectionLayout->addWidget(m_portSpin, 0, 3);
connectionLayout->addWidget(m_connectButton, 0, 4);
connectionLayout->addWidget(m_disconnectButton, 0, 5);
connectionLayout->addWidget(m_connectionLabel, 0, 6);
connectionLayout->setColumnStretch(1, 1);
connectionLayout->setColumnStretch(6, 1);
mainLayout->addWidget(connectionGroup);
auto* realtimeGroup = new QGroupBox(QStringLiteral("控制测试"), central);
auto* realtimeLayout = new QHBoxLayout(realtimeGroup);
realtimeLayout->setContentsMargins(8, 4, 8, 4);
realtimeLayout->setSpacing(6);
m_realtimeCheck = new QCheckBox(QStringLiteral("启用实时上报"), realtimeGroup);
m_resetAlarmButton = new QPushButton(QStringLiteral("RESET_ALARM"), realtimeGroup);
m_restDetectButton = new QPushButton(QStringLiteral("REST_DETECT"), realtimeGroup);
realtimeLayout->addWidget(m_realtimeCheck);
realtimeLayout->addWidget(m_resetAlarmButton);
realtimeLayout->addWidget(m_restDetectButton);
realtimeLayout->addStretch();
mainLayout->addWidget(realtimeGroup);
auto* configGroup = new QGroupBox(QStringLiteral("参数配置测试GET_CONFIG / SET_CONFIG"), central);
auto* configLayout = new QGridLayout(configGroup);
configLayout->setContentsMargins(8, 6, 8, 6);
configLayout->setHorizontalSpacing(10);
configLayout->setVerticalSpacing(4);
auto makeDoubleSpinBox = [configGroup](double value, double minimum = 0.0) {
auto* spinBox = new QDoubleSpinBox(configGroup);
spinBox->setRange(minimum, 1000000000.0);
spinBox->setDecimals(4);
spinBox->setValue(value);
return spinBox;
};
auto makeIntSpinBox = [configGroup](int value, int minimum = 1) {
auto* spinBox = new QSpinBox(configGroup);
spinBox->setRange(minimum, 1000000000);
spinBox->setValue(value);
return spinBox;
};
auto addField = [configLayout, configGroup](int row, int column, const QString& title, QWidget* field) {
configLayout->addWidget(new QLabel(title, configGroup), row, column);
configLayout->addWidget(field, row, column + 1);
};
m_scanXScaleSpin = makeDoubleSpinBox(1.0, 0.0001);
m_scanYScaleSpin = makeDoubleSpinBox(1.0, 0.0001);
m_differenceBinThresholdSpin = makeDoubleSpinBox(1.0);
m_tearingMinLengthSpin = makeDoubleSpinBox(5.0);
m_tearingMinWidthSpin = makeDoubleSpinBox(0.0);
m_tearingMinDepthSpin = makeDoubleSpinBox(0.0);
m_tearingMinGapSpin = makeDoubleSpinBox(2.0);
m_sameGapThresholdSpin = makeDoubleSpinBox(2.0);
m_gapCheckWindowSpin = makeIntSpinBox(5);
addField(0, 0, QStringLiteral("X 缩放"), m_scanXScaleSpin);
addField(0, 2, QStringLiteral("Y 缩放"), m_scanYScaleSpin);
addField(0, 4, QStringLiteral("差分阈值"), m_differenceBinThresholdSpin);
addField(1, 0, QStringLiteral("最小撕裂长度"), m_tearingMinLengthSpin);
addField(1, 2, QStringLiteral("最小撕裂宽度"), m_tearingMinWidthSpin);
addField(1, 4, QStringLiteral("最小撕裂深度"), m_tearingMinDepthSpin);
addField(2, 0, QStringLiteral("最小撕裂间隙"), m_tearingMinGapSpin);
addField(2, 2, QStringLiteral("同裂间隙阈值"), m_sameGapThresholdSpin);
addField(2, 4, QStringLiteral("间隙检查窗口"), m_gapCheckWindowSpin);
m_cameraIpEdit = new QLineEdit(QStringLiteral("192.168.1.100"), configGroup);
m_maxQueueSizeSpin = makeIntSpinBox(300);
m_generationIntervalSpin = makeIntSpinBox(100);
m_imageLineCountSpin = makeIntSpinBox(300);
addField(3, 0, QStringLiteral("相机 IP"), m_cameraIpEdit);
addField(3, 2, QStringLiteral("最大队列数"), m_maxQueueSizeSpin);
addField(3, 4, QStringLiteral("生成间隔"), m_generationIntervalSpin);
addField(4, 0, QStringLiteral("图像线数"), m_imageLineCountSpin);
m_enabledCheck = new QCheckBox(QStringLiteral("启用点云发送"), configGroup);
m_enabledCheck->setChecked(true);
m_lineStepSpin = makeIntSpinBox(1);
m_pointStepSpin = makeIntSpinBox(1);
m_readConfigButton = new QPushButton(QStringLiteral("读取参数配置"), configGroup);
m_setConfigButton = new QPushButton(QStringLiteral("写入参数配置"), configGroup);
configLayout->addWidget(m_enabledCheck, 4, 2, 1, 2);
addField(4, 4, QStringLiteral("抽线步长"), m_lineStepSpin);
addField(5, 0, QStringLiteral("抽点步长"), m_pointStepSpin);
configLayout->addWidget(m_readConfigButton, 5, 2, 1, 2);
configLayout->addWidget(m_setConfigButton, 5, 4, 1, 2);
for (int column = 1; column < 6; column += 2)
configLayout->setColumnStretch(column, 1);
configLayout->setRowStretch(6, 1);
mainLayout->addWidget(configGroup);
auto* pointCloudGroup = new QGroupBox(QStringLiteral("点云测试"), central);
auto* pointCloudLayout = new QVBoxLayout(pointCloudGroup);
pointCloudLayout->setContentsMargins(6, 6, 6, 6);
pointCloudLayout->setSpacing(4);
auto* pointCloudToolbar = new QHBoxLayout;
pointCloudToolbar->setContentsMargins(0, 0, 0, 0);
pointCloudToolbar->setSpacing(6);
m_pointCloudClearButton = new QPushButton(QStringLiteral("清空显示"), pointCloudGroup);
m_displayLineCountSpin = new QSpinBox(pointCloudGroup);
m_displayLineCountSpin->setRange(1, 10000);
m_displayLineCountSpin->setValue(200);
m_saveCloudUtilsCheck = new QCheckBox(QStringLiteral("使用 CloudUtils 保存"), pointCloudGroup);
pointCloudToolbar->addWidget(m_pointCloudClearButton);
pointCloudToolbar->addWidget(new QLabel(QStringLiteral("显示线数"), pointCloudGroup));
pointCloudToolbar->addWidget(m_displayLineCountSpin);
pointCloudToolbar->addWidget(m_saveCloudUtilsCheck);
pointCloudToolbar->addStretch();
pointCloudLayout->addLayout(pointCloudToolbar);
m_cloudView = new PointCloudGLWidget(pointCloudGroup);
m_cloudView->setMinimumHeight(260);
pointCloudLayout->addWidget(m_cloudView, 1);
auto* storageGroup = new QGroupBox(QStringLiteral("保存选项"), central);
auto* storageLayout = new QGridLayout(storageGroup);
storageLayout->setContentsMargins(8, 6, 8, 6);
storageLayout->setHorizontalSpacing(8);
storageLayout->setVerticalSpacing(4);
m_savePointsCheck = new QCheckBox(QStringLiteral("追加点云到 CSV"), storageGroup);
m_saveImagesCheck = new QCheckBox(QStringLiteral("保存 JPEG 图像"), storageGroup);
m_outputDirEdit = new QLineEdit(QDir::current().filePath(QStringLiteral("tcp_test_output")), storageGroup);
auto* browseButton = new QPushButton(QStringLiteral("选择目录"), storageGroup);
storageLayout->addWidget(m_savePointsCheck, 0, 0);
storageLayout->addWidget(m_saveImagesCheck, 0, 1);
storageLayout->addWidget(new QLabel(QStringLiteral("输出目录"), storageGroup), 1, 0);
storageLayout->addWidget(m_outputDirEdit, 1, 1);
storageLayout->addWidget(browseButton, 1, 2);
storageLayout->setColumnStretch(1, 1);
storageLayout->setRowStretch(2, 1);
auto* statsGroup = new QGroupBox(QStringLiteral("接收统计"), central);
auto* statsLayout = new QGridLayout(statsGroup);
statsLayout->setContentsMargins(6, 6, 6, 6);
statsLayout->setHorizontalSpacing(6);
statsLayout->setVerticalSpacing(4);
auto addStat = [statsLayout, statsGroup](int row, int column, const QString& title, QLabel*& value) {
auto* cell = new QWidget(statsGroup);
auto* cellLayout = new QVBoxLayout(cell);
cellLayout->setContentsMargins(4, 0, 4, 0);
cellLayout->setSpacing(1);
cellLayout->addWidget(new QLabel(title, cell));
value = new QLabel(QStringLiteral("-"), cell);
value->setTextInteractionFlags(Qt::TextSelectableByMouse);
cellLayout->addWidget(value);
statsLayout->addWidget(cell, row, column);
};
addStat(0, 0, QStringLiteral("接收点云行数"), m_linesValue);
addStat(0, 1, QStringLiteral("接收字节数"), m_bytesValue);
addStat(0, 2, QStringLiteral("协议错误数"), m_errorsValue);
addStat(1, 0, QStringLiteral("最近帧"), m_frameValue);
addStat(1, 1, QStringLiteral("点数 原始/抽样"), m_countValue);
addStat(1, 2, QStringLiteral("步长 抽线/抽点"), m_stepsValue);
addStat(2, 0, QStringLiteral("X 范围"), m_xRangeValue);
addStat(2, 1, QStringLiteral("Y 范围"), m_yRangeValue);
addStat(2, 2, QStringLiteral("Z 范围"), m_zRangeValue);
for (int column = 0; column < 3; ++column)
statsLayout->setColumnStretch(column, 1);
auto* realtimeSplitter = new QSplitter(Qt::Horizontal, statsGroup);
auto* realtimeDataGroup = new QGroupBox(QStringLiteral("实时撕裂结果"), realtimeSplitter);
auto* realtimeDataLayout = new QVBoxLayout(realtimeDataGroup);
realtimeDataLayout->setContentsMargins(4, 4, 4, 4);
realtimeDataLayout->setSpacing(3);
m_realtimeSummaryLabel = new QLabel(QStringLiteral("时间:- 数量0"), realtimeDataGroup);
m_realtimeTearTable = new QTableWidget(realtimeDataGroup);
m_realtimeTearTable->setColumnCount(5);
m_realtimeTearTable->setHorizontalHeaderLabels({
QStringLiteral("ID"), QStringLiteral("状态"), QStringLiteral("长度(mm)"),
QStringLiteral("宽度(mm)"), QStringLiteral("深度(mm)")
});
m_realtimeTearTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_realtimeTearTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_realtimeTearTable->verticalHeader()->setVisible(false);
m_realtimeTearTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
realtimeDataLayout->addWidget(m_realtimeSummaryLabel);
realtimeDataLayout->addWidget(m_realtimeTearTable, 1);
auto* realtimeImageGroup = new QGroupBox(QStringLiteral("结果图片"), realtimeSplitter);
auto* realtimeImageLayout = new QVBoxLayout(realtimeImageGroup);
realtimeImageLayout->setContentsMargins(4, 4, 4, 4);
m_realtimeImageLabel = new QLabel(QStringLiteral("暂无图片"), realtimeImageGroup);
m_realtimeImageLabel->setAlignment(Qt::AlignCenter);
m_realtimeImageLabel->setMinimumSize(200, 140);
m_realtimeImageLabel->setStyleSheet(QStringLiteral(
"QLabel { background-color: rgb(37, 38, 42); color: rgb(180, 180, 180); }"));
realtimeImageLayout->addWidget(m_realtimeImageLabel, 1);
realtimeSplitter->addWidget(realtimeDataGroup);
realtimeSplitter->addWidget(realtimeImageGroup);
realtimeSplitter->setStretchFactor(0, 3);
realtimeSplitter->setStretchFactor(1, 5);
statsLayout->addWidget(realtimeSplitter, 3, 0, 1, 3);
statsLayout->setRowStretch(3, 1);
auto* tabs = new QTabWidget(central);
tabs->addTab(statsGroup, QStringLiteral("接收统计"));
tabs->addTab(configGroup, QStringLiteral("参数配置测试"));
tabs->addTab(pointCloudGroup, QStringLiteral("点云测试"));
tabs->addTab(storageGroup, QStringLiteral("保存选项"));
mainLayout->addWidget(tabs, 2);
auto* logGroup = new QGroupBox(QStringLiteral("协议日志"), central);
auto* logLayout = new QVBoxLayout(logGroup);
logLayout->setContentsMargins(4, 4, 4, 4);
logLayout->setSpacing(2);
m_logEdit = new QPlainTextEdit(logGroup);
m_logEdit->setReadOnly(true);
m_logEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
m_logEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
logLayout->addWidget(m_logEdit);
mainLayout->addWidget(logGroup, 1);
setCentralWidget(central);
connect(m_connectButton, &QPushButton::clicked, this, &MainWindow::connectToServer);
connect(m_disconnectButton, &QPushButton::clicked, this, &MainWindow::disconnectFromServer);
connect(m_readConfigButton, &QPushButton::clicked, this, &MainWindow::readConfig);
connect(m_setConfigButton, &QPushButton::clicked, this, &MainWindow::setConfig);
connect(m_realtimeCheck, &QCheckBox::toggled, this, [this](bool) {
setRealtime();
});
connect(m_resetAlarmButton, &QPushButton::clicked, this, &MainWindow::resetAlarm);
connect(m_restDetectButton, &QPushButton::clicked, this, &MainWindow::restDetect);
connect(m_displayLineCountSpin, qOverload<int>(&QSpinBox::valueChanged),
this, [this](int) {
trimDisplayedPointClouds();
});
connect(realtimeSplitter, &QSplitter::splitterMoved, this, [this](int, int) {
updateRealtimeImageDisplay();
});
connect(m_pointCloudClearButton, &QPushButton::clicked,
this, [this]() {
m_cloudView->clearPointClouds();
logMessage(QStringLiteral("已清空点云显示"));
});
connect(browseButton, &QPushButton::clicked, this, &MainWindow::chooseOutputDir);
}
void MainWindow::connectToServer()
{
if (m_socket->state() != QAbstractSocket::UnconnectedState)
m_socket->abort();
m_receiveBuffer.clear();
m_connectionLabel->setText(QStringLiteral("连接中..."));
logMessage(QStringLiteral("连接 %1:%2").arg(m_hostEdit->text().trimmed()).arg(m_portSpin->value()));
m_socket->connectToHost(m_hostEdit->text().trimmed(), static_cast<quint16>(m_portSpin->value()));
}
void MainWindow::disconnectFromServer()
{
if (m_socket->state() != QAbstractSocket::UnconnectedState)
m_socket->abort();
updateConnectionControls(false);
}
void MainWindow::readConfig()
{
if (m_socket->state() != QAbstractSocket::ConnectedState) {
recordError(QStringLiteral("读取配置失败:尚未连接"));
return;
}
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("GET_CONFIG");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("requestId")] = QStringLiteral("cfg-%1")
.arg(QDateTime::currentMSecsSinceEpoch());
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 GET_CONFIG 请求"));
}
void MainWindow::setConfig()
{
if (m_socket->state() != QAbstractSocket::ConnectedState) {
recordError(QStringLiteral("写入参数配置失败:尚未连接"));
return;
}
QJsonObject request;
QJsonObject algorithm;
algorithm[QStringLiteral("scanXScale")] = m_scanXScaleSpin->value();
algorithm[QStringLiteral("scanYScale")] = m_scanYScaleSpin->value();
algorithm[QStringLiteral("differnceBinTh")] = m_differenceBinThresholdSpin->value();
algorithm[QStringLiteral("tearingMinLen")] = m_tearingMinLengthSpin->value();
algorithm[QStringLiteral("tearingMinWidthY")] = m_tearingMinWidthSpin->value();
algorithm[QStringLiteral("tearingMinDepthZ")] = m_tearingMinDepthSpin->value();
algorithm[QStringLiteral("tearingMinGap")] = m_tearingMinGapSpin->value();
algorithm[QStringLiteral("sameGapTh")] = m_sameGapThresholdSpin->value();
algorithm[QStringLiteral("gapChkWin")] = m_gapCheckWindowSpin->value();
QJsonObject camera;
camera[QStringLiteral("cameraIP")] = m_cameraIpEdit->text().trimmed();
QJsonObject queue;
queue[QStringLiteral("maxQueueSize")] = m_maxQueueSizeSpin->value();
queue[QStringLiteral("generationInterval")] = m_generationIntervalSpin->value();
queue[QStringLiteral("imageLineCount")] = m_imageLineCountSpin->value();
QJsonObject pointCloud;
pointCloud[QStringLiteral("enabled")] = m_enabledCheck->isChecked();
pointCloud[QStringLiteral("lineStep")] = m_lineStepSpin->value();
pointCloud[QStringLiteral("pointStep")] = m_pointStepSpin->value();
QJsonObject config;
config[QStringLiteral("algorithmParams")] = algorithm;
config[QStringLiteral("cameraConfig")] = camera;
config[QStringLiteral("queueProcessParam")] = queue;
config[QStringLiteral("pointCloudSendParam")] = pointCloud;
request[QStringLiteral("msgType")] = QStringLiteral("SET_CONFIG");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("requestId")] = QStringLiteral("cfg-%1")
.arg(QDateTime::currentMSecsSinceEpoch());
request[QStringLiteral("config")] = config;
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 SET_CONFIG算法、相机、队列和点云参数"));
}
void MainWindow::setRealtime()
{
if (m_socket->state() != QAbstractSocket::ConnectedState) {
recordError(QStringLiteral("设置实时上报失败:尚未连接"));
return;
}
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("SET_REALTIME");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
request[QStringLiteral("enable")] = m_realtimeCheck->isChecked();
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 SET_REALTIMEenable=%1")
.arg(m_realtimeCheck->isChecked() ? QStringLiteral("true") : QStringLiteral("false")));
}
void MainWindow::resetAlarm()
{
if (m_socket->state() != QAbstractSocket::ConnectedState) {
recordError(QStringLiteral("RESET_ALARM 失败:尚未连接"));
return;
}
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("RESET_ALARM");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 RESET_ALARM"));
}
void MainWindow::restDetect()
{
if (m_socket->state() != QAbstractSocket::ConnectedState) {
recordError(QStringLiteral("REST_DETECT 失败:尚未连接"));
return;
}
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("REST_DETECT");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
request[QStringLiteral("timestamp")] = QDateTime::currentMSecsSinceEpoch();
sendJsonMessage(request);
logMessage(QStringLiteral("已发送 REST_DETECT"));
}
void MainWindow::trimDisplayedPointClouds()
{
if (!m_cloudView || !m_displayLineCountSpin) {
return;
}
const size_t maximumLines = static_cast<size_t>(m_displayLineCountSpin->value());
while (m_cloudView->getCloudCount() > maximumLines) {
if (!m_cloudView->removeOldestPointCloud()) {
break;
}
}
}
void MainWindow::chooseOutputDir()
{
const QString selected = QFileDialog::getExistingDirectory(this, QStringLiteral("选择输出目录"),
m_outputDirEdit->text());
if (!selected.isEmpty())
m_outputDirEdit->setText(selected);
}
void MainWindow::onConnected()
{
m_resultLogKeys.clear();
m_resultLogMessages.clear();
m_resultLogTimes.clear();
m_resultLogBlocks.clear();
m_resultLogRepeatCounts.clear();
updateConnectionControls(true);
logMessage(QStringLiteral("已连接到 %1:%2").arg(m_hostEdit->text().trimmed()).arg(m_portSpin->value()));
QTimer::singleShot(0, this, &MainWindow::requestProtocolInfo);
}
void MainWindow::onDisconnected()
{
updateConnectionControls(false);
logMessage(QStringLiteral("连接已断开"));
}
void MainWindow::onReadyRead()
{
m_receiveBuffer.append(m_socket->readAll());
processFrames();
}
void MainWindow::onSocketError(QAbstractSocket::SocketError error)
{
Q_UNUSED(error);
recordError(QStringLiteral("Socket 错误:%1").arg(m_socket->errorString()));
}
void MainWindow::closeEvent(QCloseEvent* event)
{
disconnectFromServer();
event->accept();
}
void MainWindow::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
updateRealtimeImageDisplay();
}
void MainWindow::updateConnectionControls(bool connected)
{
m_connectButton->setEnabled(!connected);
m_disconnectButton->setEnabled(connected);
m_readConfigButton->setEnabled(connected);
m_setConfigButton->setEnabled(connected);
m_realtimeCheck->setEnabled(connected);
m_resetAlarmButton->setEnabled(connected);
m_restDetectButton->setEnabled(connected);
m_hostEdit->setEnabled(!connected);
m_portSpin->setEnabled(!connected);
if (!connected && m_socket->state() == QAbstractSocket::UnconnectedState)
m_connectionLabel->setText(QStringLiteral("未连接"));
else if (connected)
m_connectionLabel->setText(QStringLiteral("已连接"));
}
void MainWindow::requestProtocolInfo()
{
if (m_socket->state() != QAbstractSocket::ConnectedState)
return;
QJsonObject request;
request[QStringLiteral("msgType")] = QStringLiteral("GET_PROTOCOL_INFO");
request[QStringLiteral("protocolVersion")] = QStringLiteral("1.6");
sendJsonMessage(request);
logMessage(QStringLiteral("已发送新协议 GET_PROTOCOL_INFO 请求"));
}
void MainWindow::sendFrame(quint8 dataType, const QByteArray& payload)
{
QByteArray frame;
QDataStream stream(&frame, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << dataType << static_cast<quint32>(payload.size());
frame.append(payload);
frame.append(kFrameTail);
m_socket->write(frame);
m_socket->flush();
}
void MainWindow::sendJsonMessage(const QJsonObject& message)
{
const QByteArray payload = QJsonDocument(message).toJson(QJsonDocument::Compact);
QByteArray frame;
frame.append(kJsonFrameHeader);
frame.append(QByteArray::number(payload.size()).rightJustified(kJsonFrameLengthSize, '0'));
frame.append(payload);
frame.append(kJsonFrameTail);
m_socket->write(frame);
m_socket->flush();
}
void MainWindow::processFrames()
{
while (!m_receiveBuffer.isEmpty()) {
const int sizeBefore = m_receiveBuffer.size();
if (m_receiveBuffer.startsWith(kJsonFrameHeader))
processJsonFrames();
else
processPointCloudFrames();
if (m_receiveBuffer.size() == sizeBefore)
break;
}
}
void MainWindow::processPointCloudFrames()
{
const int minimumFrameSize = kFrameHeaderSize + kFrameTailSize;
while (m_receiveBuffer.size() >= minimumFrameSize) {
if (m_receiveBuffer.startsWith(kJsonFrameHeader))
return;
const quint8 dataType = static_cast<quint8>(m_receiveBuffer.at(0));
const quint32 payloadSize = readUInt32BE(m_receiveBuffer, 1);
if (payloadSize > kMaxPayloadSize) {
recordError(QStringLiteral("点云帧负载长度非法:%1").arg(payloadSize));
m_receiveBuffer.remove(0, 1);
continue;
}
const quint64 frameSize = static_cast<quint64>(kFrameHeaderSize)
+ payloadSize + kFrameTailSize;
if (static_cast<quint64>(m_receiveBuffer.size()) < frameSize)
return;
const int tailOffset = kFrameHeaderSize + static_cast<int>(payloadSize);
if (m_receiveBuffer.mid(tailOffset, kFrameTailSize) != kFrameTail) {
recordError(QStringLiteral("点云帧尾非法"));
m_receiveBuffer.remove(0, 1);
continue;
}
const QByteArray payload = m_receiveBuffer.mid(
kFrameHeaderSize, static_cast<int>(payloadSize));
m_receiveBuffer.remove(0, static_cast<int>(frameSize));
handleFrame(dataType, payload, static_cast<int>(frameSize));
}
}
void MainWindow::processJsonFrames()
{
const int minimumFrameSize = kJsonFrameHeaderSize + kJsonFrameLengthSize + kJsonFrameTailSize;
while (m_receiveBuffer.size() >= minimumFrameSize) {
if (!m_receiveBuffer.startsWith(kJsonFrameHeader))
return;
const int headerPos = m_receiveBuffer.indexOf(kJsonFrameHeader);
if (headerPos < 0)
return;
if (headerPos > 0)
m_receiveBuffer.remove(0, headerPos);
if (m_receiveBuffer.size() < kJsonFrameHeaderSize + kJsonFrameLengthSize)
return;
bool lengthOk = false;
const quint32 payloadSize = m_receiveBuffer
.mid(kJsonFrameHeaderSize, kJsonFrameLengthSize).toUInt(&lengthOk);
if (!lengthOk || payloadSize > kMaxJsonPayloadSize) {
recordError(QStringLiteral("新协议负载长度非法"));
m_receiveBuffer.remove(0, kJsonFrameHeaderSize);
continue;
}
const quint64 frameSize = static_cast<quint64>(kJsonFrameHeaderSize)
+ kJsonFrameLengthSize + payloadSize + kJsonFrameTailSize;
if (static_cast<quint64>(m_receiveBuffer.size()) < frameSize)
return;
const int tailOffset = kJsonFrameHeaderSize + kJsonFrameLengthSize + static_cast<int>(payloadSize);
if (m_receiveBuffer.mid(tailOffset, kJsonFrameTailSize) != kJsonFrameTail) {
recordError(QStringLiteral("新协议帧尾非法"));
m_receiveBuffer.remove(0, kJsonFrameHeaderSize);
continue;
}
const QByteArray payload = m_receiveBuffer.mid(
kJsonFrameHeaderSize + kJsonFrameLengthSize, static_cast<int>(payloadSize));
m_receiveBuffer.remove(0, static_cast<int>(frameSize));
m_receivedBytes += static_cast<qint64>(frameSize);
m_bytesValue->setText(QString::number(m_receivedBytes));
handleNewJsonPayload(payload);
}
}
void MainWindow::handleFrame(quint8 dataType, const QByteArray& payload, int frameSize)
{
m_receivedBytes += frameSize;
m_bytesValue->setText(QString::number(m_receivedBytes));
if (dataType == kPointCloudType) {
PointCloudSummary summary;
QVector<PointCloudPoint> points;
QString error;
if (!parsePointCloud(payload, summary, points, error)) {
recordError(QStringLiteral("点云帧解析失败:%1").arg(error));
return;
}
++m_pointLineCount;
++m_pointCloudFrameCount;
m_linesValue->setText(QString::number(m_pointLineCount));
updatePointCloudStats(summary);
PointCloudXYZRGB cloud;
cloud.reserve(static_cast<size_t>(points.size()));
for (const PointCloudPoint& point : points) {
cloud.push_back(Point3DRGB(static_cast<float>(point.x),
static_cast<float>(point.y),
static_cast<float>(point.z),
170, 170, 170), 0);
}
m_cloudView->addPointCloud(cloud,
QStringLiteral("frame-%1").arg(summary.frameIndex),
false);
trimDisplayedPointClouds();
if (m_savePointsCheck->isChecked())
appendPoints(summary, points);
if (m_saveCloudUtilsCheck->isChecked())
saveCloudUtilsFrame(summary, points);
return;
}
if (dataType == kImageType) {
++m_imageCount;
if (m_saveImagesCheck->isChecked())
saveImage(payload);
if (m_imageCount <= 3 || m_imageCount % 20 == 0)
logMessage(QStringLiteral("图像 #%1%2 字节").arg(m_imageCount).arg(payload.size()));
return;
}
if (dataType == kTextType || dataType == kReadConfigType || dataType == kWriteConfigType) {
handleJsonPayload(dataType, payload);
return;
}
recordError(QStringLiteral("未知数据类型0x%1").arg(dataType, 2, 16, QLatin1Char('0')));
}
void MainWindow::handleJsonPayload(quint8 dataType, const QByteArray& payload)
{
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
if (document.isNull()) {
recordError(QStringLiteral("JSON 解析失败:%1").arg(parseError.errorString()));
return;
}
const QJsonObject object = document.object();
const QJsonObject config = object.value(QStringLiteral("config")).toObject();
const QJsonObject pointCloud = config.value(QStringLiteral("pointCloudSendParam")).toObject();
if (!pointCloud.isEmpty()) {
m_enabledCheck->setChecked(pointCloud.value(QStringLiteral("enabled")).toBool(true));
m_lineStepSpin->setValue(qMax(1, pointCloud.value(QStringLiteral("lineStep")).toInt(1)));
m_pointStepSpin->setValue(qMax(1, pointCloud.value(QStringLiteral("pointStep")).toInt(1)));
}
const QString typeName = dataType == kTextType ? QStringLiteral("Text")
: dataType == kReadConfigType ? QStringLiteral("ReadConfig")
: QStringLiteral("WriteConfig");
logMessage(QStringLiteral("%1: %2").arg(typeName,
QString::fromUtf8(QJsonDocument(object).toJson(QJsonDocument::Compact))));
}
void MainWindow::handleNewJsonPayload(const QByteArray& payload)
{
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
if (document.isNull() || !document.isObject()) {
recordError(QStringLiteral("新协议 JSON 解析失败:%1").arg(parseError.errorString()));
return;
}
const QJsonObject object = document.object();
const QJsonObject config = object.value(QStringLiteral("config")).toObject();
if (!config.isEmpty())
updateConfigControls(config);
const QString messageType = object.value(QStringLiteral("msgType")).toString();
if (messageType == QStringLiteral("REALTIME_RESULT") ||
messageType == QStringLiteral("DETECT_RESULT")) {
const QJsonArray tears = object.value(QStringLiteral("tears")).toArray();
const int count = object.value(QStringLiteral("count")).toInt(tears.size());
const bool hasImage = !object.value(QStringLiteral("visimg")).toString().isEmpty();
updateRealtimeResult(object);
m_frameValue->setText(QStringLiteral("实时 ts=%1")
.arg(object.value(QStringLiteral("timestamp")).toVariant().toLongLong()));
m_countValue->setText(QString::number(count));
QJsonObject repeatObject = object;
repeatObject.remove(QStringLiteral("timestamp"));
repeatObject.remove(QStringLiteral("visimg"));
const QString repeatKey = messageType + QLatin1Char(':')
+ QString::fromUtf8(QJsonDocument(repeatObject).toJson(QJsonDocument::Compact));
logResultMessage(messageType,
QStringLiteral("%1count=%2 tears=%3 visimg=%4")
.arg(messageType)
.arg(count)
.arg(tears.size())
.arg(hasImage ? QStringLiteral("") : QStringLiteral("")),
repeatKey);
return;
}
logMessage(QStringLiteral("新协议 %1: %2").arg(
messageType.isEmpty() ? QStringLiteral("消息") : messageType,
QString::fromUtf8(QJsonDocument(object).toJson(QJsonDocument::Compact))));
}
void MainWindow::updateRealtimeResult(const QJsonObject& object)
{
const QJsonArray tears = object.value(QStringLiteral("tears")).toArray();
const int count = object.value(QStringLiteral("count")).toInt(tears.size());
const qint64 timestamp = object.value(QStringLiteral("timestamp")).toVariant().toLongLong();
const QString timeText = timestamp > 0
? QDateTime::fromMSecsSinceEpoch(timestamp).toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz"))
: QStringLiteral("-");
m_realtimeSummaryLabel->setText(
QStringLiteral("时间:%1 数量:%2").arg(timeText).arg(count));
m_realtimeTearTable->setRowCount(tears.size());
auto statusText = [](int status) {
switch (status) {
case 1: return QStringLiteral("新发现");
case 2: return QStringLiteral("增长中");
case 3: return QStringLiteral("已结束");
case 4: return QStringLiteral("无效");
default: return QStringLiteral("未知");
}
};
for (int row = 0; row < tears.size(); ++row) {
const QJsonObject tear = tears.at(row).toObject();
const QStringList values = {
QString::number(tear.value(QStringLiteral("id")).toInt()),
statusText(tear.value(QStringLiteral("status")).toInt()),
QString::number(tear.value(QStringLiteral("length")).toDouble()),
QString::number(tear.value(QStringLiteral("width")).toDouble()),
QString::number(tear.value(QStringLiteral("depth")).toDouble())
};
for (int column = 0; column < values.size(); ++column) {
auto* item = new QTableWidgetItem(values.at(column));
item->setTextAlignment(Qt::AlignCenter);
m_realtimeTearTable->setItem(row, column, item);
}
}
QByteArray encodedImage = object.value(QStringLiteral("visimg")).toString().toLatin1();
const int dataSeparator = encodedImage.indexOf(',');
if (encodedImage.startsWith("data:") && dataSeparator >= 0) {
encodedImage.remove(0, dataSeparator + 1);
}
const QByteArray imageData = QByteArray::fromBase64(encodedImage);
QImage image;
if (!imageData.isEmpty() && image.loadFromData(imageData)) {
m_realtimeImage = image;
updateRealtimeImageDisplay();
} else if (encodedImage.isEmpty()) {
m_realtimeImage = QImage();
m_realtimeImageLabel->setPixmap(QPixmap());
m_realtimeImageLabel->setText(QStringLiteral("暂无图片"));
} else {
recordError(QStringLiteral("实时结果图片解码失败"));
}
}
void MainWindow::updateRealtimeImageDisplay()
{
if (!m_realtimeImageLabel || m_realtimeImage.isNull()) {
return;
}
const QSize targetSize = m_realtimeImageLabel->contentsRect().size();
if (targetSize.isEmpty()) {
return;
}
m_realtimeImageLabel->setText(QString());
m_realtimeImageLabel->setPixmap(QPixmap::fromImage(m_realtimeImage).scaled(
targetSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
void MainWindow::updateConfigControls(const QJsonObject& config)
{
const QJsonObject algorithm = config.value(QStringLiteral("algorithmParams")).toObject();
auto updateDouble = [&algorithm](const QString& key, QDoubleSpinBox* spinBox) {
if (algorithm.contains(key) && algorithm.value(key).isDouble())
spinBox->setValue(algorithm.value(key).toDouble());
};
updateDouble(QStringLiteral("scanXScale"), m_scanXScaleSpin);
updateDouble(QStringLiteral("scanYScale"), m_scanYScaleSpin);
updateDouble(QStringLiteral("differnceBinTh"), m_differenceBinThresholdSpin);
updateDouble(QStringLiteral("tearingMinLen"), m_tearingMinLengthSpin);
updateDouble(QStringLiteral("tearingMinWidthY"), m_tearingMinWidthSpin);
updateDouble(QStringLiteral("tearingMinDepthZ"), m_tearingMinDepthSpin);
updateDouble(QStringLiteral("tearingMinGap"), m_tearingMinGapSpin);
updateDouble(QStringLiteral("sameGapTh"), m_sameGapThresholdSpin);
if (algorithm.contains(QStringLiteral("gapChkWin")))
m_gapCheckWindowSpin->setValue(qMax(1, algorithm.value(QStringLiteral("gapChkWin")).toInt(1)));
const QJsonObject camera = config.value(QStringLiteral("cameraConfig")).toObject();
if (camera.contains(QStringLiteral("cameraIP")))
m_cameraIpEdit->setText(camera.value(QStringLiteral("cameraIP")).toString());
const QJsonObject queue = config.value(QStringLiteral("queueProcessParam")).toObject();
if (queue.contains(QStringLiteral("maxQueueSize")))
m_maxQueueSizeSpin->setValue(qMax(1, queue.value(QStringLiteral("maxQueueSize")).toInt(1)));
if (queue.contains(QStringLiteral("generationInterval")))
m_generationIntervalSpin->setValue(qMax(1, queue.value(QStringLiteral("generationInterval")).toInt(1)));
if (queue.contains(QStringLiteral("imageLineCount")))
m_imageLineCountSpin->setValue(qMax(1, queue.value(QStringLiteral("imageLineCount")).toInt(1)));
const QJsonObject pointCloud = config.value(QStringLiteral("pointCloudSendParam")).toObject();
if (!pointCloud.isEmpty()) {
m_enabledCheck->setChecked(pointCloud.value(QStringLiteral("enabled")).toBool(true));
m_lineStepSpin->setValue(qMax(1, pointCloud.value(QStringLiteral("lineStep")).toInt(1)));
m_pointStepSpin->setValue(qMax(1, pointCloud.value(QStringLiteral("pointStep")).toInt(1)));
}
}
bool MainWindow::parsePointCloud(const QByteArray& payload, PointCloudSummary& summary,
QVector<PointCloudPoint>& points, QString& error) const
{
constexpr int headerSize = 33;
constexpr int pointSize = 28;
if (payload.size() < headerSize) {
error = QStringLiteral("负载过短:%1").arg(payload.size());
return false;
}
QDataStream stream(payload);
stream.setByteOrder(QDataStream::BigEndian);
stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
quint8 version = 0;
stream >> version >> summary.frameIndex >> summary.timestamp >> summary.originalCount
>> summary.sampledCount >> summary.lineStep >> summary.pointStep;
if (stream.status() != QDataStream::Ok || version != 1) {
error = QStringLiteral("不支持的点云格式版本:%1").arg(version);
return false;
}
const quint64 expectedSize = static_cast<quint64>(headerSize)
+ static_cast<quint64>(summary.sampledCount) * pointSize;
if (expectedSize != static_cast<quint64>(payload.size())) {
error = QStringLiteral("长度不匹配,期望 %1收到 %2").arg(expectedSize).arg(payload.size());
return false;
}
points.reserve(static_cast<int>(summary.sampledCount));
for (quint32 i = 0; i < summary.sampledCount; ++i) {
qint32 pointIndex = 0;
double x = 0.0;
double y = 0.0;
double z = 0.0;
stream >> pointIndex >> x >> y >> z;
if (stream.status() != QDataStream::Ok) {
error = QStringLiteral("点数据读取失败");
return false;
}
points.append(PointCloudPoint{pointIndex, x, y, z});
if (i == 0) {
summary.xMin = summary.xMax = x;
summary.yMin = summary.yMax = y;
summary.zMin = summary.zMax = z;
} else {
summary.xMin = qMin(summary.xMin, x);
summary.xMax = qMax(summary.xMax, x);
summary.yMin = qMin(summary.yMin, y);
summary.yMax = qMax(summary.yMax, y);
summary.zMin = qMin(summary.zMin, z);
summary.zMax = qMax(summary.zMax, z);
}
}
return true;
}
void MainWindow::appendPoints(const PointCloudSummary& summary, const QVector<PointCloudPoint>& points)
{
const QString directory = ensureOutputDir();
if (directory.isEmpty())
return;
const QString path = QDir(directory).filePath(QStringLiteral("point_cloud.csv"));
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
recordError(QStringLiteral("无法写入 CSV%1").arg(file.errorString()));
return;
}
QTextStream output(&file);
if (file.size() == 0)
output << "frame_index,timestamp,point_index,x,y,z\n";
for (const PointCloudPoint& point : points)
output << summary.frameIndex << ',' << summary.timestamp << ','
<< point.index << ',' << point.x << ',' << point.y << ',' << point.z << '\n';
}
void MainWindow::saveImage(const QByteArray& payload)
{
const QString directory = ensureOutputDir();
if (directory.isEmpty())
return;
const QString path = QDir(directory).filePath(
QStringLiteral("image_%1.jpg").arg(m_imageCount, 6, 10, QLatin1Char('0')));
QFile file(path);
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size())
recordError(QStringLiteral("无法保存图像:%1").arg(file.errorString()));
}
void MainWindow::saveCloudUtilsFrame(const PointCloudSummary& summary,
const QVector<PointCloudPoint>& points)
{
const QString directory = ensureOutputDir();
if (directory.isEmpty())
return;
std::vector<std::vector<SVzNL3DPosition>> scanLines(1);
scanLines.front().reserve(static_cast<size_t>(points.size()));
for (const PointCloudPoint& point : points) {
SVzNL3DPosition value{};
value.nPointIdx = point.index;
value.pt3D.x = point.x;
value.pt3D.y = point.y;
value.pt3D.z = point.z;
scanLines.front().push_back(value);
}
const QString path = QDir(directory).filePath(
QStringLiteral("point_cloud_%1.txt").arg(summary.frameIndex, 12, 10, QLatin1Char('0')));
LaserDataLoader loader;
const int result = loader.DebugSaveLaser(path.toStdString(), std::move(scanLines));
if (result != 0) {
recordError(QStringLiteral("CloudUtils 保存点云失败:%1").arg(
QString::fromStdString(loader.GetLastError())));
return;
}
}
QString MainWindow::ensureOutputDir()
{
const QString directory = m_outputDirEdit->text().trimmed();
if (directory.isEmpty()) {
recordError(QStringLiteral("输出目录为空"));
return QString();
}
QDir dir(directory);
if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
recordError(QStringLiteral("无法创建输出目录:%1").arg(directory));
return QString();
}
return dir.absolutePath();
}
void MainWindow::logMessage(const QString& message)
{
if (!m_logEdit)
return;
if (m_lastLogRepeatCount > 0 && message == m_lastLogKey) {
++m_lastLogRepeatCount;
QTextCursor cursor(m_logEdit->document());
cursor.movePosition(QTextCursor::End);
cursor.select(QTextCursor::BlockUnderCursor);
cursor.insertText(QStringLiteral("[%1] %2 x%3")
.arg(m_lastLogTime, m_lastLogMessage)
.arg(m_lastLogRepeatCount));
m_logEdit->ensureCursorVisible();
return;
}
m_lastLogKey = message;
m_lastLogMessage = message;
m_lastLogTime = QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss"));
m_lastLogRepeatCount = 1;
m_logEdit->appendPlainText(QStringLiteral("[%1] %2").arg(m_lastLogTime, message));
}
void MainWindow::logResultMessage(const QString& resultType, const QString& message,
const QString& repeatKey)
{
if (!m_logEdit)
return;
if (m_resultLogKeys.value(resultType) == repeatKey) {
const int repeatCount = m_resultLogRepeatCounts.value(resultType, 1) + 1;
const QString renderedLine = QStringLiteral("[%1] %2 x%3")
.arg(m_resultLogTimes.value(resultType),
m_resultLogMessages.value(resultType))
.arg(repeatCount);
const QTextBlock block = m_logEdit->document()->findBlockByNumber(
m_resultLogBlocks.value(resultType, -1));
if (block.isValid()) {
QTextCursor cursor(block);
cursor.select(QTextCursor::BlockUnderCursor);
cursor.insertText(renderedLine);
m_resultLogRepeatCounts[resultType] = repeatCount;
m_logEdit->ensureCursorVisible();
m_lastLogRepeatCount = 0;
return;
}
}
const QString logTime = QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss"));
const QString renderedLine = QStringLiteral("[%1] %2").arg(logTime, message);
m_logEdit->appendPlainText(renderedLine);
m_resultLogKeys[resultType] = repeatKey;
m_resultLogMessages[resultType] = message;
m_resultLogTimes[resultType] = logTime;
m_resultLogBlocks[resultType] = m_logEdit->document()->lastBlock().blockNumber();
m_resultLogRepeatCounts[resultType] = 1;
m_lastLogRepeatCount = 0;
}
void MainWindow::recordError(const QString& message)
{
++m_errorCount;
if (m_errorsValue)
m_errorsValue->setText(QString::number(m_errorCount));
logMessage(QStringLiteral("错误:%1").arg(message));
}
void MainWindow::updatePointCloudStats(const PointCloudSummary& summary)
{
m_frameValue->setText(QStringLiteral("%1 (ts=%2)").arg(summary.frameIndex).arg(summary.timestamp));
m_countValue->setText(QStringLiteral("%1 / %2").arg(summary.originalCount).arg(summary.sampledCount));
m_stepsValue->setText(QStringLiteral("%1 / %2").arg(summary.lineStep).arg(summary.pointStep));
m_xRangeValue->setText(formatRange(summary.xMin, summary.xMax));
m_yRangeValue->setText(formatRange(summary.yMin, summary.yMax));
m_zRangeValue->setText(formatRange(summary.zMin, summary.zMax));
}