diff --git a/App/ScrewPosition/Doc/螺杆定位TCP协议文档.md b/App/ScrewPosition/Doc/螺杆定位TCP协议文档.md index cd7e3d1..44a322e 100644 --- a/App/ScrewPosition/Doc/螺杆定位TCP协议文档.md +++ b/App/ScrewPosition/Doc/螺杆定位TCP协议文档.md @@ -71,15 +71,15 @@ RX = 89.211 格式: ```text -PointNum_AX1_AY1_AZ1_A1_B1_C1_X1_Y1_Z1_A1_B1_C1/AX2_AY2_AZ2_A2_B2_C2_X2_Y2_Z2_A2_B2_C2/\r\n +PointNum_AX_AY_AZ_A_B_C_X_Y_Z_A_B_C\r\n ``` -每个目标由 12 个浮点组成:先 **接近点** (`AX/AY/AZ + A/B/C`),再 **目标点** (`X/Y/Z + A/B/C`),两者姿态完全相同。 +每次只发送一个目标(第一个),由 12 个浮点组成:先 **接近点** (`AX/AY/AZ + A/B/C`),再 **目标点** (`X/Y/Z + A/B/C`),两者姿态完全相同。 示例: ```text -2_90.500_200.300_50.100_15.500_-2.300_45.000_100.500_200.300_50.100_15.500_-2.300_45.000/110.000_210.500_48.000_-5.000_1.200_30.000_120.000_210.500_48.000_-5.000_1.200_30.000/\r\n +1_90.500_200.300_50.100_15.500_-2.300_45.000_100.500_200.300_50.100_15.500_-2.300_45.000\r\n 0\r\n ``` diff --git a/App/ScrewPosition/ScrewPositionApp/Presenter/Inc/ScrewPositionTCPProtocol.h b/App/ScrewPosition/ScrewPositionApp/Presenter/Inc/ScrewPositionTCPProtocol.h index 8dc2af8..2655611 100644 --- a/App/ScrewPosition/ScrewPositionApp/Presenter/Inc/ScrewPositionTCPProtocol.h +++ b/App/ScrewPosition/ScrewPositionApp/Presenter/Inc/ScrewPositionTCPProtocol.h @@ -41,8 +41,9 @@ public: // Protocol: // request : T1_X_Y_Z_A_B_C\r\n - // response: PointNum_AX1_AY1_AZ1_A1_B1_C1_X1_Y1_Z1_A1_B1_C1/.../\r\n - // 每个目标 12 个浮点:先接近点(AX/AY/AZ + A/B/C),后目标点(X/Y/Z + A/B/C),两者姿态相同 + // response: PointNum_AX_AY_AZ_A_B_C_X_Y_Z_A_B_C\r\n + // PointNum 为检测到的目标数量,每次只发送第一个目标的 12 个浮点 + // 先接近点(AX/AY/AZ + A/B/C),后目标点(X/Y/Z + A/B/C),两者姿态相同 int SendTextResult(const std::string& text, const TCPClient* pClient = nullptr); private: diff --git a/App/ScrewPosition/ScrewPositionApp/Presenter/Src/TCPServerMethods.cpp b/App/ScrewPosition/ScrewPositionApp/Presenter/Src/TCPServerMethods.cpp index 3245e4d..80a3e5d 100644 --- a/App/ScrewPosition/ScrewPositionApp/Presenter/Src/TCPServerMethods.cpp +++ b/App/ScrewPosition/ScrewPositionApp/Presenter/Src/TCPServerMethods.cpp @@ -132,16 +132,18 @@ void ScrewPositionPresenter::_SendDetectionResultToTCP(const DetectionResult& re int count = static_cast(result.positions.size()); std::string resultText = std::to_string(count); - for (const auto& pos : result.positions) { + // 每次只发送一个目标,取第一个 + if (!result.positions.empty()) { + const auto& pos = result.positions.front(); double angle1 = 0.0; double angle2 = 0.0; double angle3 = 0.0; ReorderPoseAngles(pos.roll, pos.pitch, pos.yaw, poseOutputOrder, angle1, angle2, angle3); - // 每组输出 12 个浮点:先接近点(X/Y/Z/A/B/C),后目标点(X/Y/Z/A/B/C),姿态相同 + // 输出 12 个浮点:先接近点(AX/AY/AZ + A/B/C),后目标点(X/Y/Z + A/B/C),姿态相同 char buf[512]; std::snprintf(buf, sizeof(buf), - "_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f/", + "_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f", pos.approachX, pos.approachY, pos.approachZ, angle1, angle2, angle3, pos.x, pos.y, pos.z, angle1, angle2, angle3); resultText += buf; diff --git a/App/ScrewPosition/ScrewPositionApp/Version.h b/App/ScrewPosition/ScrewPositionApp/Version.h index 2bd76b9..6c2e90b 100644 --- a/App/ScrewPosition/ScrewPositionApp/Version.h +++ b/App/ScrewPosition/ScrewPositionApp/Version.h @@ -3,8 +3,8 @@ #define SCREWPOSITION_APP_NAME "螺杆定位" -#define SCREWPOSITION_VERSION_STRING "1.1.5" -#define SCREWPOSITION_BUILD_STRING "3" +#define SCREWPOSITION_VERSION_STRING "1.1.6" +#define SCREWPOSITION_BUILD_STRING "1" #define SCREWPOSITION_FULL_VERSION_STRING "V" SCREWPOSITION_VERSION_STRING "_" SCREWPOSITION_BUILD_STRING // 获取版本信息的便捷函数 diff --git a/App/ScrewPosition/ScrewPositionApp/Version.md b/App/ScrewPosition/ScrewPositionApp/Version.md index e0b3ba5..e003751 100644 --- a/App/ScrewPosition/ScrewPositionApp/Version.md +++ b/App/ScrewPosition/ScrewPositionApp/Version.md @@ -1,6 +1,12 @@ -# 1.1.5 2026-04-19 +# 1.1.6 2026-04-25 ## build_1 +1. 双相机 + +# 1.1.5 2026-04-19 +## build_1-5 1. 增加目标点的偏移并且协议更新 +2. 目标只输出一个目标 +3. 调试姿态转换 # 1.1.4 2026-04-19 ## build_1 diff --git a/App/ScrewPosition/ScrewPositionApp/mainwindow.cpp b/App/ScrewPosition/ScrewPositionApp/mainwindow.cpp index 0ad2121..717a5b0 100644 --- a/App/ScrewPosition/ScrewPositionApp/mainwindow.cpp +++ b/App/ScrewPosition/ScrewPositionApp/mainwindow.cpp @@ -28,6 +28,7 @@ #include #include #include "ScrewPositionPresenter.h" +#include "ResultListLayoutHelper.h" #include "Version.h" #include "IVrUtils.h" @@ -399,7 +400,7 @@ void MainWindow::OnCameraCountChanged(int cameraCount) { // 设置设备状态widget中的相机数量 if (m_deviceStatusWidget) { - m_deviceStatusWidget->setCameraCount(cameraCount); + m_deviceStatusWidget->setCameraCount(cameraCount, true); // 设置相机名称(从配置文件中读取) if (m_presenter) { @@ -415,6 +416,7 @@ void MainWindow::OnCameraCountChanged(int cameraCount) } } + // 如果只有一个相机,更新状态消息 if (cameraCount < 2) { updateStatusLog(tr("系统使用单相机模式")); diff --git a/App/WheelMeasure/WheelMeasureApp/Version.h b/App/WheelMeasure/WheelMeasureApp/Version.h index 2b192af..ae74c0c 100644 --- a/App/WheelMeasure/WheelMeasureApp/Version.h +++ b/App/WheelMeasure/WheelMeasureApp/Version.h @@ -3,7 +3,7 @@ #define WHEELMEASURE_APP_NAME "轮眉高度测量" -#define WHEELMEASURE_VERSION_STRING "1.1.5" +#define WHEELMEASURE_VERSION_STRING "1.1.6" #define WHEELMEASURE_BUILD_STRING "1" #define WHEELMEASURE_FULL_VERSION_STRING "V" WHEELMEASURE_VERSION_STRING "_" WHEELMEASURE_BUILD_STRING diff --git a/App/WheelMeasure/WheelMeasureApp/Version.md b/App/WheelMeasure/WheelMeasureApp/Version.md index bb9ac97..0f24187 100644 --- a/App/WheelMeasure/WheelMeasureApp/Version.md +++ b/App/WheelMeasure/WheelMeasureApp/Version.md @@ -1,3 +1,7 @@ +# 1.1.6 +## build_1 2026-04-25 +1. 优化存储数据 + # 1.1.5 ## build_1 2026-04-20 1. 补偿系数 diff --git a/App/WheelMeasure/WheelMeasureApp/WheelMeasureApp.pro b/App/WheelMeasure/WheelMeasureApp/WheelMeasureApp.pro index 7cfc00e..0756e59 100644 --- a/App/WheelMeasure/WheelMeasureApp/WheelMeasureApp.pro +++ b/App/WheelMeasure/WheelMeasureApp/WheelMeasureApp.pro @@ -152,3 +152,6 @@ else:unix:!macx: { qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + +DISTFILES += \ + Version.md diff --git a/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so b/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so index 42c8fce..4f85592 100644 Binary files a/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so and b/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so differ diff --git a/AppAlgo/workpieceHolePositioning/Inc/SG_baseAlgo_Export.h b/AppAlgo/workpieceHolePositioning/Inc/SG_baseAlgo_Export.h index 39fcf0d..d287e93 100644 --- a/AppAlgo/workpieceHolePositioning/Inc/SG_baseAlgo_Export.h +++ b/AppAlgo/workpieceHolePositioning/Inc/SG_baseAlgo_Export.h @@ -4,6 +4,11 @@ #include #include +// +SG_APISHARED_EXPORT SVzNL3DPoint vec3_cross(const SVzNL3DPoint& a, const SVzNL3DPoint& b); + +//ʱתʱ > 0 ˳ʱתʱ < 0 +SG_APISHARED_EXPORT SVzNL3DPoint wd_rotate2D(const SVzNL3DPoint& pt, const double angle); //˳Ⱥ:z޷Ϊεж SG_APISHARED_EXPORT void sg_lineDataRemoveOutlier( @@ -872,3 +877,15 @@ SG_APISHARED_EXPORT void scanLinesSmooting3x3( std::vector< std::vector>& gridDataInput, std::vector< std::vector>& smoothingData ); + + +SG_APISHARED_EXPORT void WD_getHoleInfo( + std::vector< std::vector>& scanLines, + const SSG_lineSegParam lineSegPara, + const SSG_outlierFilterParam filterParam, + const SSG_treeGrowParam growParam, + const double valieCommonNumRatio, + std::vector& segTrees_v, + std::vector& segTrees_h, + std::vector& validObjects +); diff --git a/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h b/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h index 7f9a6db..fcc9992 100644 --- a/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h +++ b/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h @@ -44,3 +44,6 @@ //շλ #define SX_ERR_UNKNOWN_OBJECT -2501 +//칤λ +#define SX_ERR_UNKNOWN_PLATE_DIR -2601 + diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll index 2b87b39..09d5d57 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb index 3cab170..4146a72 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll index e420341..a9b8466 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb index efee67c..72f771a 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll index 5898d96..95ce5d3 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb index f1cc1e1..440d1b7 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll index 21e745e..66ad3cf 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb index 00cf660..045454e 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb differ diff --git a/AppUtils/AppCommon/AppCommon.pro b/AppUtils/AppCommon/AppCommon.pro index 177867e..5bed5a8 100644 --- a/AppUtils/AppCommon/AppCommon.pro +++ b/AppUtils/AppCommon/AppCommon.pro @@ -35,13 +35,15 @@ HEADERS += \ Inc/BasePresenter.h \ Inc/VrCommonConfig.h \ Inc/ProtocolCommon.h \ - Inc/TCPServerProtocol.h + Inc/TCPServerProtocol.h \ + Inc/DebugDataSaver.h # 源文件 SOURCES += \ Src/SingleInstanceManager.cpp \ Src/BasePresenter.cpp \ - Src/TCPServerProtocol.cpp + Src/TCPServerProtocol.cpp \ + Src/DebugDataSaver.cpp # Windows平台库链接 win32:CONFIG(release, debug|release): { diff --git a/AppUtils/AppCommon/Inc/BasePresenter.h b/AppUtils/AppCommon/Inc/BasePresenter.h index 258b251..c8cbed5 100644 --- a/AppUtils/AppCommon/Inc/BasePresenter.h +++ b/AppUtils/AppCommon/Inc/BasePresenter.h @@ -15,6 +15,7 @@ #include "VrCommonConfig.h" // 包含公共配置结构体 #include "LaserDataLoader.h" // 激光数据加载器 #include "IVisionApplicationStatus.h" // 工作状态枚举 +#include "DebugDataSaver.h" // 调试数据异步存储 // 前向声明 class IYModbusTCPServer; @@ -573,6 +574,9 @@ protected: // 调试参数 VrDebugParam m_debugParam; + // 调试数据异步存储 + DebugDataSaver m_debugDataSaver; + private: // 启动ModbusTCP服务(在Init中调用) int StartModbusServer(int port = 5020); diff --git a/AppUtils/AppCommon/Inc/DebugDataSaver.h b/AppUtils/AppCommon/Inc/DebugDataSaver.h new file mode 100644 index 0000000..cbab477 --- /dev/null +++ b/AppUtils/AppCommon/Inc/DebugDataSaver.h @@ -0,0 +1,60 @@ +#ifndef DEBUG_DATA_SAVER_H +#define DEBUG_DATA_SAVER_H + +#include +#include +#include +#include +#include +#include +#include +#include "VZNL_Types.h" + +class DebugDataSaver +{ +public: + explicit DebugDataSaver(int maxThreads = 4); + ~DebugDataSaver(); + + // 异步保存点云数据(内部深拷贝数据,不阻塞调用线程) + // timestamp: 检测时刻的时间戳,格式 YYYYMMDD_HHmmss + // 调用方需在持有数据锁时调用,DeepCopyData 会在调用线程完成深拷贝 + void SaveAsync(const std::string& basePath, + const std::string& timestamp, + const std::vector>& data, + const std::string& deviceName = ""); + + // 停止所有工作线程(等待队列中任务完成后退出) + void Stop(); + +private: + struct SaveTask { + std::string filePath; + std::string deviceName; + std::vector> data; + }; + + void WorkerThread(); + + // 深拷贝检测数据(含原始指针数组的完整拷贝) + static std::vector> DeepCopyData( + const std::vector>& src); + + // 释放深拷贝的数据 + static void FreeData(std::vector>& data); + + // 生成带日期目录的完整保存路径: basePath/YYYYMMDD/pointcloud[_deviceName]_YYYYMMDD_HHmmss_NNN.txt + // timestamp: 检测时刻的时间戳,格式 YYYYMMDD_HHmmss + // deviceName: 设备名称(为空时不包含在文件名中) + std::string GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName = ""); + + int m_maxThreads; + std::vector m_workers; + std::queue m_taskQueue; + std::mutex m_queueMutex; + std::condition_variable m_queueCondition; + std::atomic m_running; + std::atomic m_taskCounter{0}; +}; + +#endif // DEBUG_DATA_SAVER_H diff --git a/AppUtils/AppCommon/Src/BasePresenter.cpp b/AppUtils/AppCommon/Src/BasePresenter.cpp index 4989422..2b3aa8f 100644 --- a/AppUtils/AppCommon/Src/BasePresenter.cpp +++ b/AppUtils/AppCommon/Src/BasePresenter.cpp @@ -39,6 +39,9 @@ BasePresenter::~BasePresenter() // 停止检测线程 StopAlgoDetectThread(); + // 停止调试数据异步存储 + m_debugDataSaver.Stop(); + // 释放检测数据缓存中的动态内存 ClearDetectionDataCache(); @@ -282,12 +285,13 @@ int BasePresenter::InitCamera(std::vector& cameraList, bool bRGB, bo LOG_INFO("[BasePresenter] init eyedevice list\n"); // 初始化相机列表,预分配空间 m_vrEyeDeviceList.resize(cameraCount, std::make_pair("", nullptr)); + LOG_INFO("[BasePresenter] camera count : %d\n", cameraCount); for(int i = 0; i < cameraCount; i++) { m_vrEyeDeviceList[i] = std::make_pair(cameraList[i].name, nullptr); + LOG_INFO("[BasePresenter] camera %d name : %s, ip : %s\n", i, cameraList[i].name.c_str(), cameraList[i].ip.c_str()); } - LOG_INFO("[BasePresenter] camera count : %d\n", cameraCount); // 尝试初始化所有相机 bool allCamerasConnected = true; @@ -496,37 +500,41 @@ int BasePresenter::DetectTask() LOG_INFO("[BasePresenter] 检测数据缓存大小: %zu\n", m_detectionDataCache.size()); } - // 2. 调试模式 - 保存点云数据 + // 2. 调试模式 - 异步保存点云数据 if (debugParam.enableDebug && debugParam.savePointCloud) { // 确定输出路径 QString outputPath; if (debugParam.debugOutputPath.empty()) { - // 默认使用应用程序目录下的 debug 子目录 outputPath = QCoreApplication::applicationDirPath() + "/debug"; } else { outputPath = QString::fromStdString(debugParam.debugOutputPath); } - // 确保输出目录存在 + // 确保输出根目录存在 QDir dir(outputPath); if (!dir.exists()) { dir.mkpath("."); } - // 生成带时间戳的文件名 - QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"); - QString fileName = QString("%1/pointcloud_%2.txt").arg(outputPath).arg(timestamp); + // 在检测时刻生成时间戳,确保文件名反映实际检测时间 + std::string timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss").toStdString(); - if (debugParam.printDetailLog) { - LOG_INFO("[BasePresenter] 保存点云数据到: %s\n", fileName.toStdString().c_str()); + // 获取当前相机名称,用于生成更直观的文件名 + std::string deviceName; + int arrayIndex = m_currentCameraIndex - 1; + LOG_INFO("[BasePresenter] currentCameraIndex: %d, arrayIndex: %d\n", m_currentCameraIndex, arrayIndex); + if (arrayIndex >= 0 && arrayIndex < static_cast(m_vrEyeDeviceList.size())) { + deviceName = m_vrEyeDeviceList[arrayIndex].first; } - // 保存点云数据 - int saveRet = SaveDetectionDataToFile(fileName.toStdString()); - if (saveRet != SUCCESS) { - LOG_WARNING("[BasePresenter] 保存点云数据失败,错误码: %d\n", saveRet); - } else { - LOG_INFO("[BasePresenter] 点云数据保存成功\n"); + if (debugParam.printDetailLog) { + LOG_INFO("[BasePresenter] 提交点云数据异步保存任务,路径: %s,时间: %s,设备: %s\n", outputPath.toStdString().c_str(), timestamp.c_str(), deviceName.c_str()); + } + + // 深拷贝数据后异步保存,不阻塞检测流程 + { + std::lock_guard lock(m_detectionDataMutex); + m_debugDataSaver.SaveAsync(outputPath.toStdString(), timestamp, m_detectionDataCache, deviceName); } } diff --git a/AppUtils/AppCommon/Src/DebugDataSaver.cpp b/AppUtils/AppCommon/Src/DebugDataSaver.cpp new file mode 100644 index 0000000..8e50204 --- /dev/null +++ b/AppUtils/AppCommon/Src/DebugDataSaver.cpp @@ -0,0 +1,185 @@ +#include "DebugDataSaver.h" +#include "LaserDataLoader.h" +#include "VrLog.h" +#include "VrError.h" +#include +#include +#include + +DebugDataSaver::DebugDataSaver(int maxThreads) + : m_maxThreads(std::max(1, std::min(maxThreads, 4))) + , m_running(true) +{ + for (int i = 0; i < m_maxThreads; ++i) { + m_workers.emplace_back(&DebugDataSaver::WorkerThread, this); + } + LOG_INFO("[DebugDataSaver] 已启动 %d 个存储线程\n", m_maxThreads); +} + +DebugDataSaver::~DebugDataSaver() +{ + Stop(); +} + +void DebugDataSaver::Stop() +{ + if (!m_running.exchange(false)) { + return; + } + + m_queueCondition.notify_all(); + + for (auto& worker : m_workers) { + if (worker.joinable()) { + worker.join(); + } + } + m_workers.clear(); + + // 释放队列中未处理的任务 + while (!m_taskQueue.empty()) { + FreeData(m_taskQueue.front().data); + m_taskQueue.pop(); + } + + LOG_INFO("[DebugDataSaver] 已停止\n"); +} + +void DebugDataSaver::SaveAsync(const std::string& basePath, const std::string& timestamp, const std::vector>& data, const std::string& deviceName) +{ + if (data.empty() || !m_running) return; + + SaveTask task; + task.deviceName = deviceName; + task.filePath = GenerateSavePath(basePath, timestamp, deviceName); + task.data = DeepCopyData(data); + + { + std::lock_guard lock(m_queueMutex); + m_taskQueue.push(std::move(task)); + } + m_queueCondition.notify_one(); +} + +void DebugDataSaver::WorkerThread() +{ + LaserDataLoader dataLoader; + + while (true) { + SaveTask task; + { + std::unique_lock lock(m_queueMutex); + m_queueCondition.wait(lock, [this] { + return !m_taskQueue.empty() || !m_running; + }); + + if (!m_running && m_taskQueue.empty()) { + break; + } + if (m_taskQueue.empty()) { + continue; + } + + task = std::move(m_taskQueue.front()); + m_taskQueue.pop(); + } + + int lineNum = static_cast(task.data.size()); + float scanSpeed = 0.0f; + int maxTimeStamp = 0; + int clockPerSecond = 0; + + int result = dataLoader.SaveLaserScanData(task.filePath, task.data, lineNum, scanSpeed, maxTimeStamp, clockPerSecond); + + if (result == SUCCESS) { + LOG_INFO("[DebugDataSaver] 保存成功: %s (%d行)\n", task.filePath.c_str(), lineNum); + } else { + LOG_ERROR("[DebugDataSaver] 保存失败: %s, 错误: %s\n", task.filePath.c_str(), dataLoader.GetLastError().c_str()); + } + + FreeData(task.data); + } +} + +std::vector> DebugDataSaver::DeepCopyData( + const std::vector>& src) +{ + std::vector> dst; + dst.reserve(src.size()); + + for (const auto& item : src) { + EVzResultDataType dataType = item.first; + const SVzLaserLineData& srcLine = item.second; + + SVzLaserLineData dstLine; + memset(&dstLine, 0, sizeof(SVzLaserLineData)); + + if (srcLine.nPointCount > 0) { + if (dataType == keResultDataType_Position) { + dstLine.p3DPoint = new SVzNL3DPosition[srcLine.nPointCount]; + if (srcLine.p3DPoint) { + memcpy(dstLine.p3DPoint, srcLine.p3DPoint, sizeof(SVzNL3DPosition) * srcLine.nPointCount); + } + dstLine.p2DPoint = new SVzNL2DPosition[srcLine.nPointCount]; + if (srcLine.p2DPoint) { + memcpy(dstLine.p2DPoint, srcLine.p2DPoint, sizeof(SVzNL2DPosition) * srcLine.nPointCount); + } + } else if (dataType == keResultDataType_PointXYZRGBA) { + dstLine.p3DPoint = new SVzNLPointXYZRGBA[srcLine.nPointCount]; + if (srcLine.p3DPoint) { + memcpy(dstLine.p3DPoint, srcLine.p3DPoint, sizeof(SVzNLPointXYZRGBA) * srcLine.nPointCount); + } + dstLine.p2DPoint = new SVzNL2DLRPoint[srcLine.nPointCount]; + if (srcLine.p2DPoint) { + memcpy(dstLine.p2DPoint, srcLine.p2DPoint, sizeof(SVzNL2DLRPoint) * srcLine.nPointCount); + } + } + } + + dstLine.nPointCount = srcLine.nPointCount; + dstLine.llTimeStamp = srcLine.llTimeStamp; + dstLine.llFrameIdx = srcLine.llFrameIdx; + dstLine.nEncodeNo = srcLine.nEncodeNo; + dstLine.fSwingAngle = srcLine.fSwingAngle; + dstLine.bEndOnceScan = srcLine.bEndOnceScan; + + dst.push_back(std::make_pair(dataType, dstLine)); + } + + return dst; +} + +void DebugDataSaver::FreeData(std::vector>& data) +{ + LaserDataLoader loader; + loader.FreeLaserScanData(data); + data.clear(); +} + +std::string DebugDataSaver::GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName) +{ + // 从时间戳提取日期部分作为子文件夹名(前8位:YYYYMMDD) + std::string dateFolder = timestamp.substr(0, 8); + + QString fullPath = QString::fromStdString(basePath) + "/" + QString::fromStdString(dateFolder); + + QDir dir(fullPath); + if (!dir.exists()) { + dir.mkpath("."); + } + + int taskId = m_taskCounter.fetch_add(1); + QString fileName; + if (!deviceName.empty()) { + fileName = QString("%1/%2_%3_%4.txt").arg(fullPath) + .arg(taskId, 4, 10, QChar('0')) + .arg(QString::fromStdString(deviceName)) + .arg(QString::fromStdString(timestamp)); + } else { + fileName = QString("%1/pointcloud_%2_%3.txt").arg(fullPath) + .arg(QString::fromStdString(timestamp)) + .arg(taskId, 4, 10, QChar('0')); + } + + return fileName.toStdString(); +} diff --git a/AppUtils/UICommon/Inc/DeviceStatusWidget.h b/AppUtils/UICommon/Inc/DeviceStatusWidget.h index 358b2d2..96ab1a2 100644 --- a/AppUtils/UICommon/Inc/DeviceStatusWidget.h +++ b/AppUtils/UICommon/Inc/DeviceStatusWidget.h @@ -40,7 +40,8 @@ public: void setCamera2Name(const QString& name); // 设置相机数量(用于控制相机二的显示和布局方向) - void setCameraCount(int cameraCount); + // cameraCount>=2 时 horizontal 有效:true=横向不调整布局,false=纵向(默认) + void setCameraCount(int cameraCount, bool horizontal = false); // 设置机械臂是否可见(默认可见) void setRobotVisible(bool visible); @@ -76,6 +77,9 @@ private: // 相机数量 int m_cameraCount; + // 双相机布局方向(true=横向,false=纵向) + bool m_horizontalLayout; + // 相机是否可点击 bool m_cameraClickable; diff --git a/AppUtils/UICommon/Src/DeviceStatusWidget.cpp b/AppUtils/UICommon/Src/DeviceStatusWidget.cpp index 098169e..8e07994 100644 --- a/AppUtils/UICommon/Src/DeviceStatusWidget.cpp +++ b/AppUtils/UICommon/Src/DeviceStatusWidget.cpp @@ -13,6 +13,7 @@ DeviceStatusWidget::DeviceStatusWidget(QWidget *parent) , m_camera2Name("") , m_robotVisible(true) , m_cameraCount(1) + , m_horizontalLayout(false) , m_cameraClickable(true) { ui->setupUi(this); @@ -235,13 +236,14 @@ void DeviceStatusWidget::updateSerialStatus(bool isConnected) } // 设置相机数量(用于控制相机二的显示和布局方向) -void DeviceStatusWidget::setCameraCount(int cameraCount) +void DeviceStatusWidget::setCameraCount(int cameraCount, bool horizontal) { - LOG_DEBUG("setCameraCount cameraCount: %d \n", cameraCount); + LOG_DEBUG("setCameraCount cameraCount: %d, horizontal: %d\n", cameraCount, horizontal); // 检查是否在主线程中 if (QThread::currentThread() == this->thread()) { m_cameraCount = cameraCount; + m_horizontalLayout = horizontal; // 如果相机数量小于2,隐藏相机二相关的UI元素 bool showCamera2 = (cameraCount >= 2); @@ -261,12 +263,15 @@ void DeviceStatusWidget::setCameraCount(int cameraCount) } } - // 更新布局 - updateLayout(); + // 横向布局时不需要调整布局 + if (!horizontal) { + updateLayout(); + } } else { // 在其他线程中,使用队列方式确保线程安全 - QMetaObject::invokeMethod(this, [this, cameraCount]() { + QMetaObject::invokeMethod(this, [this, cameraCount, horizontal]() { m_cameraCount = cameraCount; + m_horizontalLayout = horizontal; // 如果相机数量小于2,隐藏相机二相关的UI元素 bool showCamera2 = (cameraCount >= 2); @@ -286,8 +291,10 @@ void DeviceStatusWidget::setCameraCount(int cameraCount) } } - // 更新布局 - updateLayout(); + // 横向布局时不需要调整布局 + if (!horizontal) { + updateLayout(); + } }, Qt::QueuedConnection); } } diff --git a/GrabBagPrj/AppList.md b/GrabBagPrj/AppList.md index 8db003d..f6474dc 100644 --- a/GrabBagPrj/AppList.md +++ b/GrabBagPrj/AppList.md @@ -8,15 +8,15 @@ | 2 | 皮带撕裂 | BeltTearingServer | 2.0.8.1 | | 3 | 孔洞检测 | HoleDetection | 1.1.6.2 | | 4 | 棒材定位 | RodAndBarPosition | 1.1.0.1 | -| 5 | 车轮拱高测量 | WheelMeasure | 1.1.5.1 | +| 5 | 车轮拱高测量 | WheelMeasure | 1.1.6.1 | | 6 | 定子定位 | StatorPosition | 1.0.0.1 | | 7 | 焊接 | LapWeld | 1.0.0.1 | | 8 | 工件定位 | Workpiece | 1.0.3.0 | | 9 | 颗粒尺寸检测 | ParticleSize | 1.0.0.0 | | 10 | 双目标记检测 | BinocularMarkServer | 1.0.0.4 | | 11 | 铁路隧道槽道测量 | TunnelChannel | 1.0.0.3 | -| 12 | 螺杆定位 | ScrewPosition | 1.1.5.3 | +| 12 | 螺杆定位 | ScrewPosition | 1.1.6.1 | | 13 | 包裹拆线位置定位 | BagThreadPosition | 1.0.0.4 | -| 14 | 工件孔定位 | WorkpieceHole | 1.1.3.1 | +| 14 | 工件孔定位 | WorkpieceHole | 1.1.3.2 | | 16 | 坑孔定位 | HolePitPosition | 无 | diff --git a/Utils b/Utils index 598cd17..c088ad3 160000 --- a/Utils +++ b/Utils @@ -1 +1 @@ -Subproject commit 598cd176f5390520aa400dbe8cb7cc7b7a213b91 +Subproject commit c088ad39c4b0ee5d41d9a6ec2ca5f1efdaea9bf6