From c1cfe7e5605e374bd2ee2214f87e553043309243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=B0=E4=BB=94?= Date: Fri, 3 Jul 2026 12:46:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=96=B0=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CloudUtils/Inc/LaserDataLoader.h | 14 +- CloudUtils/Src/LaserDataLoader.cpp | 387 +++++++++++++++++++++++--- CloudView/Inc/CloudViewMainWindow.h | 1 + CloudView/Inc/PointCloudConverter.h | 4 +- CloudView/Src/CloudViewMainWindow.cpp | 190 ++++++++++++- CloudView/Src/PointCloudConverter.cpp | 8 +- CloudView/main.cpp | 2 +- VrUtils/Src/VrLog.cpp | 31 ++- 8 files changed, 580 insertions(+), 57 deletions(-) diff --git a/CloudUtils/Inc/LaserDataLoader.h b/CloudUtils/Inc/LaserDataLoader.h index bd342a1..dd692bf 100644 --- a/CloudUtils/Inc/LaserDataLoader.h +++ b/CloudUtils/Inc/LaserDataLoader.h @@ -83,9 +83,17 @@ private: int _ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData); // 获取激光数据类型 - int _GetLaserType(const std::string& fileName, EVzResultDataType& eDataType); - - std::string m_lastError; + int _GetLaserType(const std::string& fileName, EVzResultDataType& eDataType); + + // Read binary .dat laser scan data. + int _LoadLaserScanDataFromDatFile(const std::string& fileName, + std::vector>& laserLines, + int& lineNum, + float& scanSpeed, + int& maxTimeStamp, + int& clockPerSecond); + + std::string m_lastError; static const int VZ_LASER_LINE_PT_MAX_NUM = 4096; }; diff --git a/CloudUtils/Src/LaserDataLoader.cpp b/CloudUtils/Src/LaserDataLoader.cpp index 7fadd92..7512483 100644 --- a/CloudUtils/Src/LaserDataLoader.cpp +++ b/CloudUtils/Src/LaserDataLoader.cpp @@ -1,23 +1,134 @@ -#include "LaserDataLoader.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "LaserDataLoader.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "VrLog.h" // 辅助函数:去除字符串末尾的 \r 字符(处理 Windows 格式的 CR LF 换行符) -static inline void TrimCarriageReturn(std::string& str) -{ - if (!str.empty() && str.back() == '\r') { - str.pop_back(); - } -} +static inline void TrimCarriageReturn(std::string& str) +{ + if (!str.empty() && str.back() == '\r') { + str.pop_back(); + } +} + +namespace +{ +struct DatValue2DFloat +{ + double x; + double y; + double value; +}; + +struct Dat3DPoint +{ + double X; + double Y; + double Z; +}; + +struct Dat3DFeaturePoint +{ + int index; + int flag; + DatValue2DFloat stereo; + DatValue2DFloat stereoRight; + Dat3DPoint eye; + Dat3DPoint world; + std::uint64_t next; +}; + +struct DatLaserScan3DLinesHeader +{ + char dataCalibrated; + char adjusted; + int scanLineNum; + int vr1; + int vr2; + double lineV; + int clock; + std::uint32_t maxTimeStamp; + std::uint64_t scanLineList; +}; + +struct DatLineScan3DData +{ + int lineIndex; + std::uint32_t timeStamp; + double lineStepping; +}; + +static_assert(sizeof(DatLaserScan3DLinesHeader) == 40, "Unexpected dat header layout"); +static_assert(sizeof(Dat3DFeaturePoint) == 112, "Unexpected dat feature point layout"); + +template +bool ReadBinary(std::ifstream& inputFile, T& value) +{ + inputFile.read(reinterpret_cast(&value), sizeof(T)); + return static_cast(inputFile); +} + +bool HasFileExtension(const std::string& fileName, const char* extension) +{ + const size_t dotPos = fileName.find_last_of('.'); + if (dotPos == std::string::npos) { + return false; + } + + const size_t slashPos = fileName.find_last_of("/\\"); + if (slashPos != std::string::npos && dotPos < slashPos) { + return false; + } + + std::string fileExt = fileName.substr(dotPos); + std::transform(fileExt.begin(), fileExt.end(), fileExt.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return fileExt == extension; +} + +bool LooksLikeTextLaserFile(const std::string& fileName) +{ + std::ifstream inputFile(fileName, std::ios::binary); + if (!inputFile.is_open()) { + return false; + } + + char header[8] = {}; + inputFile.read(header, sizeof(header)); + return inputFile.gcount() == sizeof(header) && std::memcmp(header, "LineNum:", sizeof(header)) == 0; +} + +bool ReadDatLineRecord(std::ifstream& inputFile, int recordSize, DatLineScan3DData& lineData) +{ + char buffer[40] = {}; + if (recordSize <= 0 || recordSize > static_cast(sizeof(buffer))) { + return false; + } + + inputFile.read(buffer, recordSize); + if (!inputFile) { + return false; + } + + std::memcpy(&lineData.lineIndex, buffer, sizeof(lineData.lineIndex)); + std::memcpy(&lineData.timeStamp, buffer + 4, sizeof(lineData.timeStamp)); + std::memcpy(&lineData.lineStepping, buffer + 8, sizeof(lineData.lineStepping)); + return true; +} +} LaserDataLoader::LaserDataLoader() { @@ -42,7 +153,11 @@ int LaserDataLoader::LoadLaserScanData(const std::string& fileName, lineNum = 0; scanSpeed = 0.0f; maxTimeStamp = 0; - clockPerSecond = 0; + clockPerSecond = 0; + + if (HasFileExtension(fileName, ".dat") && !LooksLikeTextLaserFile(fileName)) { + return _LoadLaserScanDataFromDatFile(fileName, laserLines, lineNum, scanSpeed, maxTimeStamp, clockPerSecond); + } // 判断文件类型 std::ifstream inputFile(fileName); @@ -161,11 +276,175 @@ int LaserDataLoader::LoadLaserScanData(const std::string& fileName, return SUCCESS; } -// 保存激光扫描数据到文件 - 统一接口,支持两种类型的数据 -int LaserDataLoader::SaveLaserScanData(const std::string& fileName, - const std::vector>& laserLines, - int lineNum, - float scanSpeed, +// Read binary .dat laser scan data. +int LaserDataLoader::_LoadLaserScanDataFromDatFile(const std::string& fileName, + std::vector>& laserLines, + int& lineNum, + float& scanSpeed, + int& maxTimeStamp, + int& clockPerSecond) +{ + std::ifstream inputFile(fileName, std::ios::binary); + if (!inputFile.is_open()) { + m_lastError = "Cannot open file: " + fileName; + LOG_ERROR("Cannot open dat file: %s\n", fileName.c_str()); + return ERR_CODE(FILE_ERR_NOEXIST); + } + + auto fail = [&](int code, const std::string& message) -> int { + m_lastError = message; + LOG_ERROR("%s\n", message.c_str()); + FreeLaserScanData(laserLines); + lineNum = 0; + scanSpeed = 0.0f; + maxTimeStamp = 0; + clockPerSecond = 0; + return ERR_CODE(code); + }; + + inputFile.seekg(0, std::ios::end); + const std::streamoff fileSize = inputFile.tellg(); + inputFile.seekg(0, std::ios::beg); + + DatLaserScan3DLinesHeader header; + std::memset(&header, 0, sizeof(header)); + if (!ReadBinary(inputFile, header)) { + return fail(FILE_ERR_READ, "Cannot read dat header: " + fileName); + } + + if (header.scanLineNum < 0 || fileSize < static_cast(sizeof(header))) { + return fail(FILE_ERR_FORMAT, "Invalid dat header: " + fileName); + } + + const int kFeaturePointSize = static_cast(sizeof(Dat3DFeaturePoint)); + auto validateLayout = [&](int lineRecordSize) -> bool { + std::streamoff pos = static_cast(sizeof(header)); + inputFile.clear(); + inputFile.seekg(pos, std::ios::beg); + + for (int i = 0; i < header.scanLineNum; ++i) { + if (pos + lineRecordSize + static_cast(sizeof(int)) > fileSize) { + return false; + } + + inputFile.seekg(lineRecordSize, std::ios::cur); + pos += lineRecordSize; + + int pointCount = 0; + if (!ReadBinary(inputFile, pointCount)) { + return false; + } + pos += static_cast(sizeof(pointCount)); + + if (pointCount < 0 || pointCount > VZ_LASER_LINE_PT_MAX_NUM) { + return false; + } + + const std::streamoff pointBytes = + static_cast(pointCount) * static_cast(kFeaturePointSize); + if (pointBytes < 0 || pos + pointBytes > fileSize) { + return false; + } + + inputFile.seekg(pointBytes, std::ios::cur); + pos += pointBytes; + } + + return pos == fileSize; + }; + + int lineRecordSize = 40; + if (!validateLayout(lineRecordSize)) { + lineRecordSize = 32; + if (!validateLayout(lineRecordSize)) { + return fail(FILE_ERR_FORMAT, "Invalid dat layout: " + fileName); + } + } + + lineNum = header.scanLineNum; + scanSpeed = static_cast(header.lineV); + maxTimeStamp = static_cast(header.maxTimeStamp); + clockPerSecond = header.clock; + + laserLines.clear(); + laserLines.reserve(static_cast(lineNum)); + + inputFile.clear(); + inputFile.seekg(static_cast(sizeof(header)), std::ios::beg); + + for (int i = 0; i < lineNum; ++i) { + DatLineScan3DData datLine; + std::memset(&datLine, 0, sizeof(datLine)); + if (!ReadDatLineRecord(inputFile, lineRecordSize, datLine)) { + return fail(FILE_ERR_READ, "Cannot read dat line header: " + fileName); + } + + int pointCount = 0; + if (!ReadBinary(inputFile, pointCount)) { + return fail(FILE_ERR_READ, "Cannot read dat point count: " + fileName); + } + if (pointCount < 0 || pointCount > VZ_LASER_LINE_PT_MAX_NUM) { + return fail(FILE_ERR_FORMAT, "Invalid dat point count: " + fileName); + } + + SVzLaserLineData lineData; + std::memset(&lineData, 0, sizeof(lineData)); + lineData.nPointCount = pointCount; + lineData.dTotleOffset = datLine.lineStepping; + lineData.dStep = datLine.lineStepping; + lineData.llFrameIdx = static_cast(datLine.lineIndex); + lineData.llTimeStamp = static_cast(datLine.timeStamp); + + std::unique_ptr points3D; + std::unique_ptr points2D; + + if (pointCount > 0) { + points3D.reset(new (std::nothrow) SVzNL3DPosition[pointCount]); + points2D.reset(new (std::nothrow) SVzNL2DPosition[pointCount]); + if (!points3D || !points2D) { + return fail(DATA_ERR_MEM, "Memory allocation failed while reading dat: " + fileName); + } + + std::memset(points3D.get(), 0, sizeof(SVzNL3DPosition) * static_cast(pointCount)); + std::memset(points2D.get(), 0, sizeof(SVzNL2DPosition) * static_cast(pointCount)); + + for (int j = 0; j < pointCount; ++j) { + Dat3DFeaturePoint featurePoint; + std::memset(&featurePoint, 0, sizeof(featurePoint)); + if (!ReadBinary(inputFile, featurePoint)) { + return fail(FILE_ERR_READ, "Cannot read dat feature point: " + fileName); + } + + points3D[j].nPointIdx = j; + points3D[j].pt3D.x = featurePoint.eye.X; + points3D[j].pt3D.y = featurePoint.eye.Y; + points3D[j].pt3D.z = featurePoint.eye.Z; + + points2D[j].nPointIdx = j; + points2D[j].ptLeft2D.x = static_cast(featurePoint.stereo.x); + points2D[j].ptLeft2D.y = static_cast(featurePoint.stereo.y); + points2D[j].ptRight2D.x = static_cast(featurePoint.stereoRight.x); + points2D[j].ptRight2D.y = static_cast(featurePoint.stereoRight.y); + } + + lineData.p3DPoint = points3D.get(); + lineData.p2DPoint = points2D.get(); + } + + laserLines.push_back(std::make_pair(keResultDataType_Position, lineData)); + points3D.release(); + points2D.release(); + } + + LOG_INFO("Successfully loaded %d dat laser scan lines from file: %s\n", lineNum, fileName.c_str()); + return SUCCESS; +} + +// 保存激光扫描数据到文件 - 统一接口,支持两种类型的数据 +int LaserDataLoader::SaveLaserScanData(const std::string& fileName, + const std::vector>& laserLines, + int lineNum, + float scanSpeed, int maxTimeStamp, int clockPerSecond) { @@ -486,15 +765,61 @@ void LaserDataLoader::FreeLaserScanData(std::vector(lineData.p3DPoint); + break; + case keResultDataType_PositionF: + delete[] static_cast(lineData.p3DPoint); + break; + case keResultDataType_PointF: + delete[] static_cast(lineData.p3DPoint); + break; + case keResultDataType_PointXYZ: + delete[] static_cast(lineData.p3DPoint); + break; + case keResultDataType_PointXYZRGBA: + delete[] static_cast(lineData.p3DPoint); + break; + case keResultDataType_PointGray: + delete[] static_cast(lineData.p3DPoint); + break; + case keResultDataType_PointXYZI: + delete[] static_cast(lineData.p3DPoint); + break; + case keResultDataType_PointRGBA_D: + delete[] static_cast(lineData.p3DPoint); + break; + default: + LOG_WARN("Unknown laser data type when freeing p3DPoint: %d\n", dataType); + break; + } + lineData.p3DPoint = nullptr; + } + + if (lineData.p2DPoint) { + switch (dataType) { + case keResultDataType_Position: + delete[] static_cast(lineData.p2DPoint); + break; + case keResultDataType_PositionF: + delete[] static_cast(lineData.p2DPoint); + break; + case keResultDataType_PointF: + case keResultDataType_PointXYZ: + case keResultDataType_PointXYZRGBA: + case keResultDataType_PointGray: + case keResultDataType_PointXYZI: + case keResultDataType_PointRGBA_D: + delete[] static_cast(lineData.p2DPoint); + break; + default: + LOG_WARN("Unknown laser data type when freeing p2DPoint: %d\n", dataType); + break; + } + lineData.p2DPoint = nullptr; + } } laserLines.clear(); } diff --git a/CloudView/Inc/CloudViewMainWindow.h b/CloudView/Inc/CloudViewMainWindow.h index d5d3701..3309197 100644 --- a/CloudView/Inc/CloudViewMainWindow.h +++ b/CloudView/Inc/CloudViewMainWindow.h @@ -167,6 +167,7 @@ private slots: void onRotateCloudByZ(); void onSavePointCloud(); // 保存 txt 点云 void onSavePlyPcdCloud(); // 保存 ply/pcd 点云 + void onBatchConvertTxtToPly(); // 批量转换 txt -> ply void onConvertEulerMatrix(); /** diff --git a/CloudView/Inc/PointCloudConverter.h b/CloudView/Inc/PointCloudConverter.h index dfc446c..18a7ded 100644 --- a/CloudView/Inc/PointCloudConverter.h +++ b/CloudView/Inc/PointCloudConverter.h @@ -54,7 +54,7 @@ using PointCloudXYZRGB = SimplePointCloud; /** * @brief 点云数据转换器 - * 负责加载 txt 和 pcd 格式的点云文件 + * 负责加载 txt/dat、pcd、ply 格式的点云文件 */ class PointCloudConverter { @@ -63,7 +63,7 @@ public: ~PointCloudConverter(); /** - * @brief 从 txt 文件加载点云(使用 CloudUtils) + * @brief 从 txt/dat 文件加载点云(使用 CloudUtils) */ int loadFromTxt(const std::string& fileName, PointCloudXYZ& cloud); int loadFromTxt(const std::string& fileName, PointCloudXYZRGB& cloud); diff --git a/CloudView/Src/CloudViewMainWindow.cpp b/CloudView/Src/CloudViewMainWindow.cpp index 18d1cfc..d2f92a8 100644 --- a/CloudView/Src/CloudViewMainWindow.cpp +++ b/CloudView/Src/CloudViewMainWindow.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -421,6 +423,9 @@ void CloudViewMainWindow::setupUI() QAction* actSavePlyPcd = fileMenu->addAction("保存ply/pcd点云...\tCtrl+Shift+S"); actSavePlyPcd->setShortcut(QKeySequence("Ctrl+Shift+S")); connect(actSavePlyPcd, &QAction::triggered, this, &CloudViewMainWindow::onSavePlyPcdCloud); + fileMenu->addSeparator(); + QAction* actBatchConvert = fileMenu->addAction("批量转换txt->ply..."); + connect(actBatchConvert, &QAction::triggered, this, &CloudViewMainWindow::onBatchConvertTxtToPly); // ── 点云菜单 ── QMenu* cloudMenu = menuBar()->addMenu("点云"); @@ -1136,7 +1141,7 @@ void CloudViewMainWindow::onOpenFile() this, "打开文件", QString(), - "所有支持格式 (*.pcd *.ply *.txt);;PCD 文件 (*.pcd);;PLY 文件 (*.ply);;TXT 文件 (*.txt);;所有文件 (*.*)" + "所有支持格式 (*.pcd *.ply *.txt *.dat);;PCD 文件 (*.pcd);;PLY 文件 (*.ply);;TXT 文件 (*.txt);;DAT 文件 (*.dat);;所有文件 (*.*)" ); if (fileName.isEmpty()) { @@ -2655,6 +2660,189 @@ void CloudViewMainWindow::onSavePlyPcdCloud() cloud.points.size(), fileName.toStdString().c_str()); } +void CloudViewMainWindow::onBatchConvertTxtToPly() +{ + // 选择源文件夹 + const QString srcDir = QFileDialog::getExistingDirectory( + this, "选择源文件夹(包含 txt 点云文件)", QString(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (srcDir.isEmpty()) return; + + // 选择目标文件夹 + const QString dstDir = QFileDialog::getExistingDirectory( + this, "选择目标文件夹(PLY 文件输出位置)", srcDir, + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (dstDir.isEmpty()) return; + + // 递归收集所有 .txt 文件及其相对路径 + struct FileEntry + { + QString absolutePath; + QString relativePath; // 相对于源文件夹的路径,用于构建目标路径 + }; + QVector fileEntries; + + { + QDirIterator it(srcDir, QStringList("*.txt"), QDir::Files, + QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (it.hasNext()) { + it.next(); + FileEntry entry; + entry.absolutePath = it.filePath(); + // 计算相对路径(文件路径去掉源目录前缀) + entry.relativePath = QDir(srcDir).relativeFilePath(it.filePath()); + fileEntries.append(entry); + } + } + + if (fileEntries.isEmpty()) { + QMessageBox::information(this, "提示", + QString("源文件夹及其子目录中没有找到 .txt 文件:\n%1").arg(srcDir)); + return; + } + + // 按相对路径排序 + std::sort(fileEntries.begin(), fileEntries.end(), + [](const FileEntry& a, const FileEntry& b) { + return a.relativePath < b.relativePath; + }); + + // 进度对话框 + QProgressDialog progress("正在批量转换 txt -> ply...", "取消", 0, fileEntries.size(), this); + progress.setWindowTitle("批量转换"); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(0); + progress.setAutoClose(true); + + // 使用独立的转换器,避免干扰主窗口状态 + PointCloudConverter converter; + int successCount = 0; + int failCount = 0; + int skipCount = 0; + QStringList failedFiles; + + const float kZeroEps = 1e-6f; + + for (int i = 0; i < fileEntries.size(); ++i) { + if (progress.wasCanceled()) { + statusBar()->showMessage("批量转换已取消"); + break; + } + + const FileEntry& entry = fileEntries[i]; + const QString& srcPath = entry.absolutePath; + const QString& relPath = entry.relativePath; + + // 构建目标路径:目标文件夹 + 相对路径,扩展名改为 .ply + const QString baseName = QFileInfo(relPath).completeBaseName(); + const QString relDir = QFileInfo(relPath).path(); // 相对路径中的目录部分 + const QString dstRelPath = (relDir == "." ? baseName + ".ply" + : relDir + "/" + baseName + ".ply"); + const QString dstPath = dstDir + "/" + dstRelPath; + + // 确保目标子目录存在 + const QString dstParentDir = QFileInfo(dstPath).absolutePath(); + if (!QDir().mkpath(dstParentDir)) { + failCount++; + failedFiles.append(QString("%1 (无法创建目标目录)").arg(relPath)); + LOG_WARN("[CloudView] Batch convert: failed to create dir %s\n", + dstParentDir.toStdString().c_str()); + continue; + } + + // 显示简短的相对路径便于阅读 + const QString displayPath = relPath.length() > 60 + ? ("..." + relPath.right(57)) : relPath; + + progress.setLabelText(QString("正在转换: %1 (%2/%3)") + .arg(displayPath).arg(i + 1).arg(fileEntries.size())); + progress.setValue(i); + QCoreApplication::processEvents(); + + // 加载 txt 点云 + PointCloudXYZ cloud; + int loadRet = converter.loadFromTxt(srcPath.toStdString(), cloud); + if (loadRet != 0) { + failCount++; + failedFiles.append(QString("%1 (加载失败: %2)") + .arg(relPath) + .arg(QString::fromStdString(converter.getLastError()))); + LOG_WARN("[CloudView] Batch convert: failed to load %s\n", + srcPath.toStdString().c_str()); + continue; + } + + // 过滤掉零点,构建用于保存的点云 + PointCloudXYZ filteredCloud; + filteredCloud.reserve(cloud.size()); + for (const auto& pt : cloud.points) { + if (std::fabs(pt.x) < kZeroEps && + std::fabs(pt.y) < kZeroEps && + std::fabs(pt.z) < kZeroEps) + continue; + filteredCloud.push_back(pt); + } + + if (filteredCloud.empty()) { + failCount++; + failedFiles.append(QString("%1 (无有效点)").arg(relPath)); + LOG_WARN("[CloudView] Batch convert: no valid points in %s\n", + srcPath.toStdString().c_str()); + continue; + } + + // 保存为 PLY + int saveRet = converter.saveToPly(dstPath.toStdString(), filteredCloud); + if (saveRet != 0) { + failCount++; + failedFiles.append(QString("%1 (保存失败: %2)") + .arg(relPath) + .arg(QString::fromStdString(converter.getLastError()))); + LOG_WARN("[CloudView] Batch convert: failed to save %s\n", + dstPath.toStdString().c_str()); + continue; + } + + successCount++; + LOG_INFO("[CloudView] Batch convert: %s -> %s (%zu points)\n", + srcPath.toStdString().c_str(), + dstPath.toStdString().c_str(), + filteredCloud.size()); + } + + progress.setValue(fileEntries.size()); + + // 汇总结果 + const int totalCount = fileEntries.size(); + QString summary = QString("批量转换完成!\n\n" + "总文件数: %1\n" + "成功: %2\n" + "失败: %3") + .arg(totalCount) + .arg(successCount) + .arg(failCount); + if (skipCount > 0) { + summary += QString("\n跳过: %1").arg(skipCount); + } + + if (!failedFiles.isEmpty()) { + summary += "\n\n失败文件:\n"; + // 最多显示前 10 个失败文件 + const int maxShow = std::min(failedFiles.size(), 10); + for (int i = 0; i < maxShow; ++i) { + summary += QString(" • %1\n").arg(failedFiles[i]); + } + if (failedFiles.size() > maxShow) { + summary += QString(" ... 还有 %1 个\n") + .arg(failedFiles.size() - maxShow); + } + } + + QMessageBox::information(this, "转换结果", summary); + statusBar()->showMessage( + QString("批量转换完成: %1 成功, %2 失败").arg(successCount).arg(failCount)); +} + void CloudViewMainWindow::onRotateCloudByZ() { if (m_glWidget->getCloudCount() == 0) { diff --git a/CloudView/Src/PointCloudConverter.cpp b/CloudView/Src/PointCloudConverter.cpp index c556f2e..59124cb 100644 --- a/CloudView/Src/PointCloudConverter.cpp +++ b/CloudView/Src/PointCloudConverter.cpp @@ -444,8 +444,8 @@ int PointCloudConverter::loadFromFile(const std::string& fileName, PointCloudXYZ if (ext == "pcd") { return loadFromPcd(fileName, cloud); - } else if (ext == "txt") { - return loadFromTxt(fileName, cloud); + } else if (ext == "txt" || ext == "dat") { + return loadFromTxt(fileName, cloud); } else if (ext == "ply") { return loadFromPly(fileName, cloud); } else { @@ -460,8 +460,8 @@ int PointCloudConverter::loadFromFile(const std::string& fileName, PointCloudXYZ if (ext == "pcd") { return loadFromPcd(fileName, cloud); - } else if (ext == "txt") { - return loadFromTxt(fileName, cloud); + } else if (ext == "txt" || ext == "dat") { + return loadFromTxt(fileName, cloud); } else if (ext == "ply") { return loadFromPly(fileName, cloud); } else { diff --git a/CloudView/main.cpp b/CloudView/main.cpp index 7266b1b..6455439 100644 --- a/CloudView/main.cpp +++ b/CloudView/main.cpp @@ -3,7 +3,7 @@ #include #include "CloudViewMainWindow.h" -#define APP_VERSION "1.1.5" +#define APP_VERSION "1.1.6" int main(int argc, char* argv[]) { diff --git a/VrUtils/Src/VrLog.cpp b/VrUtils/Src/VrLog.cpp index 02c20c3..65ff378 100644 --- a/VrUtils/Src/VrLog.cpp +++ b/VrUtils/Src/VrLog.cpp @@ -1,11 +1,12 @@ #include "VrLog.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -307,15 +308,15 @@ void VrLogUtils::EchoLog(VrLogLevel eLogLevel, const char* sFilePath, const int } // load log info - va_list args; - va_start(args, sFormat); - char szLogInfo[1024] = { 0 }; -#ifdef _WIN32 - vsprintf_s(szLogInfo, sFormat, args); -#else - vsprintf(szLogInfo, sFormat, args); -#endif - va_end(args); + va_list args; + va_start(args, sFormat); + char szLogInfo[1024] = { 0 }; +#ifdef _WIN32 + vsnprintf_s(szLogInfo, sizeof(szLogInfo), _TRUNCATE, sFormat, args); +#else + vsnprintf(szLogInfo, sizeof(szLogInfo), sFormat, args); +#endif + va_end(args); log4cpp::Category& root = log4cpp::Category::getRoot(); switch (eLogLevel)