Compare commits

...

7 Commits

Author SHA1 Message Date
9287c8050a merge:合并提取的PathUtils,加图片显示的优化 2026-10-10 23:14:18 +08:00
7b84fffd2a style:调整文件结束符 2026-10-10 19:02:17 +08:00
杰仔
2ee6bcd414 refactor(CloudView): 拆分缩放按钮布局逻辑为独立函数
1.  将原eventFilter中的缩放按钮位置计算代码抽离为updateZoomButtonGeometry独立函数
2.  在创建视图区域时主动调用该函数初始化按钮位置
3.  统一处理GL控件resize时的按钮重定位逻辑,提升代码可读性
4.  修复原代码未在初始化时设置按钮位置的问题
5.  新增按钮置顶操作确保缩放按钮始终显示在最上层
2026-09-11 15:18:33 +08:00
杰仔
72db40115f refactor: 重构项目结构并新增点云查看应用
- 将原CloudView重命名为CloudViewApp
- 新增ImageView可缩放图片控件库
- 新增点云过滤、XYZ范围过滤工具类
- 重构Qt项目配置,移除硬编码输出路径
- 新增版本日志文档和应用图标资源
- 扩展激光数据加载器支持回调流式加载和范围过滤
- 新增点云转换器支持多格式加载和回调加载
- 新增点云查看器配置管理功能
- 统一错误码编号偏移量避免冲突
- 修复CloudView3D项目的代码合并冲突
2026-09-11 15:18:09 +08:00
杰仔
9271a37a81 refactor: 重构项目构建系统与点云查看器功能
1.  新增CloudUtils静态库与相关CMake配置
2.  重构VrUtils构建脚本,修正编译选项与依赖
3.  为Windows平台添加utf-8编译选项
4.  扩展PointCloudGLWidget功能:新增点云移除、选择信息叠加、增量相机适配
5.  优化点云添加接口,支持不重置相机视图
6.  修正激光数据保存时的DataType输出
7.  为Point3D结构体添加单位注释
8.  调整鼠标交互逻辑,统一按键操作行为
2026-08-18 12:26:28 +08:00
杰仔
44180b7f85 refactor(cloudview): 重构项目结构与代码格式,优化构建配置
1.  调整.gitignore规则,新增资源文件忽略项
2.  重构子项目依赖配置,统一项目构建路径
3.  修复CloudView头文件和pro文件的包含路径
4.  格式化代码缩进与注释风格,调整类成员布局
5.  为CloudView新增--help/-h和--version/-v命令行参数
6.  重构点云渲染模块,新增GLES2 shader渲染支持与悬浮控件
2026-08-18 12:26:08 +08:00
杰仔
74072f9620 增加3D数据的移除功能 2026-08-12 14:48:18 +08:00
22 changed files with 4482 additions and 3527 deletions

23
CMakeLists.txt Normal file
View File

@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.14)
project(WoodCamUtils LANGUAGES C CXX)
option(WOODCAM_BUILD_CLOUDVIEW3D "Build the Qt/OpenGL CloudView3D utility" ON)
option(WOODCAM_BUILD_CLOUDUTILS "Build the CloudUtils static library" ON)
# VrCommon is a header-oriented compatibility module. Keep it as an interface
# target so consumers get the canonical VZNL_Types.h include path.
if(NOT TARGET VrCommon)
add_library(VrCommon INTERFACE)
target_include_directories(VrCommon INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/VrCommon/Inc
)
endif()
if(WOODCAM_BUILD_CLOUDVIEW3D)
add_subdirectory(CloudView3D)
endif()
if(WOODCAM_BUILD_CLOUDUTILS)
add_subdirectory(CloudUtils)
endif()

55
CloudUtils/CMakeLists.txt Normal file
View File

@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.14)
find_package(Qt5 REQUIRED COMPONENTS Core Gui)
if(NOT TARGET VrCommon)
add_library(VrCommon INTERFACE)
target_include_directories(VrCommon INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../VrCommon/Inc
)
endif()
if(NOT TARGET VrUtils)
add_subdirectory(
"${CMAKE_CURRENT_SOURCE_DIR}/../VrUtils"
"${CMAKE_CURRENT_BINARY_DIR}/VrUtils"
)
endif()
add_library(CloudUtils STATIC
Src/LaserDataLoader.cpp
Src/PointCloudImageUtils.cpp
Inc/LaserDataLoader.h
Inc/PointCloudImageUtils.h
)
set_target_properties(CloudUtils PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
target_include_directories(CloudUtils PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/Inc
)
# When CamTool is built with WoodSDK, use its VZ compatibility types first.
# This keeps LaserDataLoader's SVzNL* signatures identical to the application
# translation units, where WoodSDK maps them to the public SWD* structures.
if(TARGET WoodSDK)
target_include_directories(CloudUtils BEFORE PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/../../App/woodcam-sdk/WoodSDK/VzToWDInc"
)
endif()
target_link_libraries(CloudUtils
PUBLIC
VrCommon
Qt5::Core
Qt5::Gui
PRIVATE
VrUtils
)
if(MSVC)
target_compile_options(CloudUtils PRIVATE /utf-8)
endif()

View File

@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.14)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets OpenGL)
if(NOT TARGET VrCommon)
add_library(VrCommon INTERFACE)
target_include_directories(VrCommon INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../VrCommon/Inc
)
endif()
add_library(CloudView3D STATIC
Src/PointCloudGLWidget.cpp
Inc/CloudView3D.h
Inc/CloudView3DTypes.h
Inc/PointCloudGLWidget.h
)
set_target_properties(CloudView3D PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
AUTOMOC ON
)
target_include_directories(CloudView3D PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/Inc
${CMAKE_CURRENT_SOURCE_DIR}/../VrCommon/Inc
)
target_link_libraries(CloudView3D PUBLIC
VrCommon
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::OpenGL
)
if(WIN32)
target_compile_options(CloudView3D PRIVATE /utf-8)
target_link_libraries(CloudView3D PUBLIC opengl32 glu32)
elseif(UNIX AND NOT APPLE)
target_link_libraries(CloudView3D PUBLIC GL GLU)
endif()

View File

@ -11,8 +11,8 @@ win32-msvc {
INCLUDEPATH += $$PWD/Inc
INCLUDEPATH += $$PWD/../VrUtils/Inc
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/Inc
INCLUDEPATH += $$PWD/../VrCommon/Inc
# VZNL_Types.h is maintained in the shared Utils module.
HEADERS += \
Inc/CloudView3D.h \

View File

@ -7,14 +7,14 @@
struct Point3D
{
float x, y, z;
float x, y, z; // millimetres
Point3D() : x(0), y(0), z(0) {}
Point3D(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
};
struct Point3DRGB
{
float x, y, z;
float x, y, z; // millimetres
uint8_t r, g, b;
float pointSize;

View File

@ -8,6 +8,7 @@
#include <QMatrix4x4>
#include <QVector3D>
#include <QQuaternion>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QResizeEvent>
@ -21,6 +22,7 @@ class QToolButton;
class QPushButton;
class QPinchGesture;
class QResizeEvent;
class QTimer;
/**
* @brief 点云显示颜色枚举
@ -185,10 +187,30 @@ public:
void addPointCloud(const PointCloudXYZ& cloud, const QString& name = "", bool resetView = true);
void addPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "", bool resetView = true);
/** 删除最早添加的一组点云,用于维护滚动显示窗口。 */
bool removeOldestPointCloud();
bool removeOldestPointCloud(bool refreshDisplay = true);
/** 删除最近添加的一组点云,用于回滚未完成的增量加载。 */
bool removeNewestPointCloud();
bool removeNewestPointCloud(bool refreshDisplay = true);
void clearPointClouds();
/** 配置流式点云刷新频率;CamTool 使用 30 Hz。 */
void setStreamRefreshRate(int refreshRateHz);
int streamRefreshRate() const
{
return m_streamRefreshRate;
}
/** 配置流式点云最多保留的源扫描线数。 */
void setStreamLineLimit(int maxLines);
int streamLineLimit() const
{
return m_streamLineLimit;
}
void startStreamRefresh();
void stopStreamRefresh(bool flushPending = true);
/** 缓存一批本地线号从 0 开始的扫描线,下一次刷新时统一提交。 */
void queuePointCloudBatch(PointCloudXYZ cloud, int sourceLineCount, const QString& name = QString());
void flushPointCloudBatches();
void clearPointCloudBatches();
void setPointCloudColor(PointCloudColor color);
void setPointSize(float size);
@ -393,6 +415,8 @@ signals:
float dz);
void lineSelected(const SelectedLineInfo& line);
void viewAnglesChanged(float rotX, float rotY, float rotZ);
/** 30 Hz 刷新边界;数据源应在该信号处理中批量填充缓存。 */
void streamRefreshRequested();
protected:
bool event(QEvent* event) override;
@ -408,6 +432,13 @@ protected:
void resizeEvent(QResizeEvent* event) override;
private:
struct QueuedPointCloudBatch
{
PointCloudXYZ cloud;
QString name;
int sourceLineCount;
};
struct PointCloudData
{
std::vector<float> vertices;
@ -422,6 +453,8 @@ private:
int colorIndex; // 点云颜色索引
int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云)
bool streamBatch;
int sourceLineCount;
// 自定义点大小分组(RGBA 中 A > 1 的点按大小分组)
struct PointSizeGroup {
@ -435,14 +468,19 @@ private:
QOpenGLBuffer vertexBuffer; // 顶点 VBO
QOpenGLBuffer colorBuffer; // 颜色 VBO
bool vboCreated; // VBO 是否已创建
QVector3D minBound;
QVector3D maxBound;
bool hasBounds;
PointCloudData()
: hasColor(false), pseudoColor(false), hasLineInfo(false), colorIndex(0)
, totalLines(0), pointsPerLine(0)
, streamBatch(false), sourceLineCount(0)
, hasCustomPointSizes(false)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
, hasBounds(false)
{}
// 移动构造函数(QOpenGLBuffer 不支持拷贝)
@ -459,11 +497,16 @@ private:
, colorIndex(other.colorIndex)
, totalLines(other.totalLines)
, pointsPerLine(other.pointsPerLine)
, streamBatch(other.streamBatch)
, sourceLineCount(other.sourceLineCount)
, customPointSizeGroups(std::move(other.customPointSizeGroups))
, hasCustomPointSizes(other.hasCustomPointSizes)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
, minBound(other.minBound)
, maxBound(other.maxBound)
, hasBounds(other.hasBounds)
{
// VBO 需要在 GL 上下文中重建,标记为未创建
other.vboCreated = false;
@ -490,8 +533,13 @@ private:
colorIndex = other.colorIndex;
totalLines = other.totalLines;
pointsPerLine = other.pointsPerLine;
streamBatch = other.streamBatch;
sourceLineCount = other.sourceLineCount;
customPointSizeGroups = std::move(other.customPointSizeGroups);
hasCustomPointSizes = other.hasCustomPointSizes;
minBound = other.minBound;
maxBound = other.maxBound;
hasBounds = other.hasBounds;
// VBO 需要重建
other.vboCreated = false;
}
@ -507,6 +555,8 @@ private:
void updateProjection(const QMatrix4x4& modelView);
float fittedCameraDistance(float padding = 1.05f) const;
float minimumCameraDistance() const;
/** 按当前缩放把点大小从逻辑像素换到实际绘制像素,避免放大后点散成看不见的碎点。 */
float effectivePointSize() const;
QVector3D colorForIndex(int colorIndex) const;
SelectedPointInfo pickPoint(int screenX, int screenY);
void drawSelectedPoints();
@ -519,15 +569,26 @@ private:
void drawBasicShapeSegments(); // 绘制基础图形
void drawBasicShapePoints();
void drawLineSegmentList(const QVector<LineSegment>& segments);
void drawPosePoints(); // 绘制姿态点
void drawAxisLabels(); // 绘制坐标轴 XYZ 标注
void drawSelectionInfoOverlay(); // 右上角显示选点和测量结果
QVector3D pseudoColorForZ(float z) const;
void rebuildPseudoColors(PointCloudData& data, size_t firstVertex);
void updatePseudoColors();
void createControlButtons();
void updateControlButtonGeometry();
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
void expandCloudBounds(PointCloudData& data, float x, float y, float z);
void addPointCloudInternal(const PointCloudXYZ& cloud, const QString& name, bool resetViewAfterAdd, bool refreshDisplay);
void trimQueuedPointCloudBatches();
void trimDisplayedStreamBatches(bool refreshDisplay);
bool removeOldestStreamPointCloud(bool refreshDisplay);
void discardLeadingLines(QueuedPointCloudBatch& batch, int lineCount);
void discardLeadingStreamLines(PointCloudData& data, int lineCount);
void recomputeCloudBounds(PointCloudData& data);
std::deque<PointCloudData> m_pointClouds;
@ -625,6 +686,14 @@ private:
QToolButton* m_zoomInButton;
QToolButton* m_zoomOutButton;
// 高速输入先进入 CPU 有界缓存,再由 GUI 线程按固定频率批量提交 OpenGL。
QTimer* m_streamRefreshTimer;
std::deque<QueuedPointCloudBatch> m_queuedPointCloudBatches;
int m_queuedPointCloudLineCount;
int m_streamDisplayedLineCount;
int m_streamRefreshRate;
int m_streamLineLimit;
};
#endif // POINT_CLOUD_GL_WIDGET_H

File diff suppressed because it is too large Load Diff

View File

@ -86,6 +86,7 @@ private:
void buildStartupSequence(const StartupFiles& files);
bool loadStartupItem(const StartupItem& item);
bool loadCurrentStartupSequenceItem();
void updateZoomButtonGeometry();
private slots:
/**

View File

@ -1249,19 +1249,35 @@ bool CloudViewMainWindow::eventFilter(QObject* obj, QEvent* event)
{
if (obj == m_glWidget && event->type() == QEvent::Resize &&
m_btnZoomIn && m_btnZoomOut) {
const auto* resizeEvent = static_cast<QResizeEvent*>(event);
const int width = resizeEvent->size().width();
const int height = resizeEvent->size().height();
updateZoomButtonGeometry();
}
return QMainWindow::eventFilter(obj, event);
}
void CloudViewMainWindow::updateZoomButtonGeometry()
{
if (!m_glWidget || !m_btnZoomIn || !m_btnZoomOut)
{
return;
}
const int widgetWidth = m_glWidget->width();
const int widgetHeight = m_glWidget->height();
if (widgetWidth <= 0 || widgetHeight <= 0)
{
return;
}
const int buttonWidth = m_btnZoomIn->width();
const int buttonHeight = m_btnZoomIn->height();
const int margin = 8;
const int gap = 4;
const int x = width - buttonWidth - margin;
const int y = qMax(0, (height - buttonHeight * 2 - gap) / 2);
const int x = qMax(0, widgetWidth - buttonWidth - margin);
const int y = qMax(0, (widgetHeight - buttonHeight * 2 - gap) / 2);
m_btnZoomIn->move(x, y);
m_btnZoomOut->move(x, y + buttonHeight + gap);
}
return QMainWindow::eventFilter(obj, event);
m_btnZoomIn->raise();
m_btnZoomOut->raise();
}
void CloudViewMainWindow::closeEvent(QCloseEvent* event)
@ -1420,6 +1436,8 @@ QWidget* CloudViewMainWindow::createViewerArea()
connect(m_btnZoomOut, &QToolButton::clicked, m_glWidget, &PointCloudGLWidget::zoomOut);
m_glWidget->installEventFilter(this);
// 初始创建时可能没有收到 resize 事件,立即将按钮放到视口右侧居中。
updateZoomButtonGeometry();
// 连接信号
connect(m_glWidget, &PointCloudGLWidget::pointSelected, this, &CloudViewMainWindow::onPointSelected);

View File

@ -1,34 +1,112 @@
ADD_DEFINITIONS(-D__OPENVMS__) #log4cpp 编译选项
if(NOT WIN32)
ADD_DEFINITIONS(-DLOG4CPP_HAVE_GETTIMEOFDAY) #ARM/Linux 上直接启用 gettimeofday,确保毫秒正确
endif()
ADD_DEFINITIONS(-DVR_UTILS_EXPORTS) #VrUtils 导出宏定义
# ADD_COMPILE_OPTIONS(-Wno-deprecated-declarations) #auto_ptr警告消除
cmake_minimum_required(VERSION 3.14)
# 设置CMake策略,允许AUTOMOC和AUTOUIC处理.hh文件
# cmake_policy(SET CMP0100 NEW)
INCLUDE_DIRECTORIES(
./_Inc
./Inc
./MD5
./tinyxml2
./log4cpp/include
# Keep this source list aligned with VrUtils.pro. Do not glob Src/: it contains
# legacy standalone sources such as getopt.c that are not part of VrUtils.
set(VRUTILS_SOURCES
Src/VrDateUtils.cpp
Src/VrFileUtils.cpp
Src/VrLog.cpp
Src/VrNetUtils.cpp
Src/VrStringUtils.cpp
Src/VrTimeUtils.cpp
ini/ConvertUTF.c
log4cpp/src/AbortAppender.cpp
log4cpp/src/Appender.cpp
log4cpp/src/AppenderSkeleton.cpp
log4cpp/src/AppendersFactory.cpp
log4cpp/src/BasicConfigurator.cpp
log4cpp/src/BasicLayout.cpp
log4cpp/src/BufferingAppender.cpp
log4cpp/src/Category.cpp
log4cpp/src/CategoryStream.cpp
log4cpp/src/Configurator.cpp
log4cpp/src/DailyRollingFileAppender.cpp
log4cpp/src/DllMain.cpp
log4cpp/src/DummyThreads.cpp
log4cpp/src/FactoryParams.cpp
log4cpp/src/FileAppender.cpp
log4cpp/src/Filter.cpp
log4cpp/src/FixedContextCategory.cpp
log4cpp/src/HierarchyMaintainer.cpp
log4cpp/src/IdsaAppender.cpp
log4cpp/src/LayoutAppender.cpp
log4cpp/src/LayoutsFactory.cpp
log4cpp/src/LevelEvaluator.cpp
log4cpp/src/Localtime.cpp
log4cpp/src/LoggingEvent.cpp
log4cpp/src/MSThreads.cpp
log4cpp/src/Manipulator.cpp
log4cpp/src/NDC.cpp
log4cpp/src/NTEventLogAppender.cpp
log4cpp/src/OmniThreads.cpp
log4cpp/src/OstreamAppender.cpp
log4cpp/src/PThreads.cpp
log4cpp/src/PassThroughLayout.cpp
log4cpp/src/PatternLayout.cpp
log4cpp/src/PortabilityImpl.cpp
log4cpp/src/Priority.cpp
log4cpp/src/Properties.cpp
log4cpp/src/PropertyConfigurator.cpp
log4cpp/src/PropertyConfiguratorImpl.cpp
log4cpp/src/RemoteSyslogAppender.cpp
log4cpp/src/RollingFileAppender.cpp
log4cpp/src/SimpleConfigurator.cpp
log4cpp/src/SimpleLayout.cpp
log4cpp/src/SmtpAppender.cpp
log4cpp/src/StringQueueAppender.cpp
log4cpp/src/StringUtil.cpp
log4cpp/src/SyslogAppender.cpp
log4cpp/src/TimeStamp.cpp
log4cpp/src/TriggeringEventEvaluatorFactory.cpp
log4cpp/src/Win32DebugAppender.cpp
log4cpp/src/snprintf.c
tinyxml2/tinyxml2.cpp
jsoncpp/json_reader.cpp
jsoncpp/json_value.cpp
jsoncpp/json_writer.cpp
)
AUX_SOURCE_DIRECTORY(./Src SrcS)
AUX_SOURCE_DIRECTORY(./MD5 MD5SrcS)
AUX_SOURCE_DIRECTORY(./tinyxml2 TinyxmlSrcS)
AUX_SOURCE_DIRECTORY(./log4cpp/src LogSrcS)
add_library(VrUtils STATIC ${VRUTILS_SOURCES})
# 对特定的.hh文件设置SKIP_AUTOGEN属性
set_property(SOURCE log4cpp/src/StringUtil.hh PROPERTY SKIP_AUTOGEN ON)
set_property(SOURCE log4cpp/src/PortabilityImpl.hh PROPERTY SKIP_AUTOGEN ON)
set_property(SOURCE log4cpp/src/Properties.hh PROPERTY SKIP_AUTOGEN ON)
set_property(SOURCE log4cpp/src/Localtime.hh PROPERTY SKIP_AUTOGEN ON)
set_property(SOURCE log4cpp/src/PropertyConfiguratorImpl.hh PROPERTY SKIP_AUTOGEN ON)
set_target_properties(VrUtils PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
ADD_LIBRARY(VrUtils STATIC ${SrcS} ${MD5SrcS}
${TinyxmlSrcS}
${LogSrcS}
)
target_include_directories(VrUtils
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/Inc
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ini
${CMAKE_CURRENT_SOURCE_DIR}/tinyxml2
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp
${CMAKE_CURRENT_SOURCE_DIR}/log4cpp/include
${CMAKE_CURRENT_SOURCE_DIR}/log4cpp/src
)
target_compile_definitions(VrUtils PRIVATE
TINYXML2_EXPORT
HAVE_SNPRINTF
LOG4CPP_HAVE_SSTREAM=1
VR_UTILS_EXPORTS
)
if(UNIX AND NOT APPLE)
target_compile_definitions(VrUtils PRIVATE LOG4CPP_HAVE_UNISTD_H)
set_target_properties(VrUtils PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
find_package(Threads REQUIRED)
target_link_libraries(VrUtils PUBLIC Threads::Threads)
if(WIN32)
# qmake's Windows mkspec supplies these definitions. log4cpp uses the
# generic Win32 API names and therefore must see the same character mode.
target_compile_definitions(VrUtils PRIVATE UNICODE _UNICODE)
target_link_libraries(VrUtils PUBLIC ws2_32 iphlpapi advapi32 shell32)
endif()
if(MSVC)
target_compile_definitions(VrUtils PRIVATE _HAS_AUTO_PTR_ETC=1)
target_compile_options(VrUtils PRIVATE /utf-8)
endif()