1127 lines
48 KiB
C++
Raw Normal View History

2026-08-12 18:03:28 +08:00
#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_REALTIME:enable=%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("%1:count=%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));
}