refactor(CloudView): 拆分缩放按钮布局逻辑为独立函数

1.  将原eventFilter中的缩放按钮位置计算代码抽离为updateZoomButtonGeometry独立函数
2.  在创建视图区域时主动调用该函数初始化按钮位置
3.  统一处理GL控件resize时的按钮重定位逻辑,提升代码可读性
4.  修复原代码未在初始化时设置按钮位置的问题
5.  新增按钮置顶操作确保缩放按钮始终显示在最上层
This commit is contained in:
杰仔 2026-09-11 15:18:33 +08:00
parent 72db40115f
commit 2ee6bcd414
5 changed files with 933 additions and 331 deletions

View File

@ -32,6 +32,15 @@ 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

View File

@ -6,27 +6,23 @@
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QMatrix4x4>
#include <QVector3D>
#include <QQuaternion>
#include <QMouseEvent>
<<<<<<< HEAD
#include <QWheelEvent>
#include <vector>
#include <deque>
=======
#include <QVector3D>
#include <QQuaternion>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QResizeEvent>
#include <deque>
#include <vector>
>>>>>>> c6df042456fa4411f7b5269f8781f91c45da70c2
#include "CloudView3DTypes.h"
#include "VZNL_Types.h"
class QToolButton;
class QPushButton;
class QPinchGesture;
class QResizeEvent;
class QPushButton;
class QPinchGesture;
class QResizeEvent;
class QTimer;
/**
* @brief 点云显示颜色枚举
@ -185,25 +181,36 @@ public:
PointCloudGLWidget(QWidget* parent, bool showControlButtons);
~PointCloudGLWidget() override;
<<<<<<< HEAD
void addPointCloud(const PointCloudXYZ& cloud, const QString& name = "");
void addPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "",
bool resetCamera = true);
void appendPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "");
void removeOldestPointCloud();
void clearPointClouds();
=======
void setControlButtonsVisible(bool visible);
bool controlButtonsVisible() const { return m_showControlButtons; }
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();
void clearPointClouds();
>>>>>>> c6df042456fa4411f7b5269f8781f91c45da70c2
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);
@ -406,8 +413,10 @@ signals:
float dx,
float dy,
float dz);
void lineSelected(const SelectedLineInfo& line);
void viewAnglesChanged(float rotX, float rotY, float rotZ);
void lineSelected(const SelectedLineInfo& line);
void viewAnglesChanged(float rotX, float rotY, float rotZ);
/** 30 Hz 刷新边界;数据源应在该信号处理中批量填充缓存。 */
void streamRefreshRequested();
protected:
bool event(QEvent* event) override;
@ -423,6 +432,13 @@ protected:
void resizeEvent(QResizeEvent* event) override;
private:
struct QueuedPointCloudBatch
{
PointCloudXYZ cloud;
QString name;
int sourceLineCount;
};
struct PointCloudData
{
std::vector<float> vertices;
@ -434,9 +450,11 @@ private:
bool pseudoColor;
bool hasLineInfo; // 是否有线信息
QString name;
int colorIndex; // 点云颜色索引
int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云)
int colorIndex; // 点云颜色索引
int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云)
bool streamBatch;
int sourceLineCount;
// 自定义点大小分组(RGBA 中 A > 1 的点按大小分组)
struct PointSizeGroup {
@ -450,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)
, hasCustomPointSizes(false)
: 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 不支持拷贝)
@ -471,14 +494,19 @@ private:
, pseudoColor(other.pseudoColor)
, hasLineInfo(other.hasLineInfo)
, name(std::move(other.name))
, colorIndex(other.colorIndex)
, totalLines(other.totalLines)
, pointsPerLine(other.pointsPerLine)
, customPointSizeGroups(std::move(other.customPointSizeGroups))
, 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;
@ -502,11 +530,16 @@ private:
pseudoColor = other.pseudoColor;
hasLineInfo = other.hasLineInfo;
name = std::move(other.name);
colorIndex = other.colorIndex;
totalLines = other.totalLines;
pointsPerLine = other.pointsPerLine;
customPointSizeGroups = std::move(other.customPointSizeGroups);
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;
}
@ -515,13 +548,15 @@ private:
// 禁止拷贝
PointCloudData(const PointCloudData&) = delete;
PointCloudData& operator=(const PointCloudData&) = delete;
};
void computeBoundingBox();
PointCloudData& operator=(const PointCloudData&) = delete;
};
void computeBoundingBox();
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();
@ -534,21 +569,26 @@ private:
void drawBasicShapeSegments(); // 绘制基础图形
void drawBasicShapePoints();
void drawLineSegmentList(const QVector<LineSegment>& segments);
<<<<<<< HEAD
void drawPosePoints(); // 绘制姿态点
void drawAxisLabels(); // 绘制坐标轴 XYZ 标注
void drawSelectionInfoOverlay(); // 右上角显示选点和测量结果
=======
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();
>>>>>>> c6df042456fa4411f7b5269f8781f91c45da70c2
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
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;
@ -570,17 +610,8 @@ private:
void createOverlayControls();
void repositionOverlayControls();
void showViewAngleDialog();
<<<<<<< HEAD
void updateViewAngleDisplay();
bool handlePinchGesture(QPinchGesture* gesture);
// Each entry owns QOpenGLBuffer objects. deque avoids relocating existing
// entries when new scan lines are appended or the oldest one is removed.
std::deque<PointCloudData> m_pointClouds;
=======
void updateViewAngleDisplay();
bool handlePinchGesture(QPinchGesture* gesture);
>>>>>>> c6df042456fa4411f7b5269f8781f91c45da70c2
QMatrix4x4 m_projection;
QMatrix4x4 m_view;
@ -653,8 +684,16 @@ private:
QVector<QToolButton*> m_viewButtons;
QPushButton* m_viewAngleButton; // 左下角旋转角度显示按钮
QToolButton* m_zoomInButton;
QToolButton* m_zoomOutButton;
};
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

@ -83,9 +83,10 @@ private:
QString fileName;
};
void buildStartupSequence(const StartupFiles& files);
bool loadStartupItem(const StartupItem& item);
bool loadCurrentStartupSequenceItem();
void buildStartupSequence(const StartupFiles& files);
bool loadStartupItem(const StartupItem& item);
bool loadCurrentStartupSequenceItem();
void updateZoomButtonGeometry();
private slots:
/**

View File

@ -1245,24 +1245,40 @@ void CloudViewMainWindow::setPointCloudRange(const PointCloudXYZRange& range)
}
}
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();
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);
m_btnZoomIn->move(x, y);
m_btnZoomOut->move(x, y + buttonHeight + gap);
}
return QMainWindow::eventFilter(obj, event);
}
bool CloudViewMainWindow::eventFilter(QObject* obj, QEvent* event)
{
if (obj == m_glWidget && event->type() == QEvent::Resize &&
m_btnZoomIn && m_btnZoomOut) {
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 = 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);
m_btnZoomIn->raise();
m_btnZoomOut->raise();
}
void CloudViewMainWindow::closeEvent(QCloseEvent* event)
{
@ -1417,11 +1433,13 @@ QWidget* CloudViewMainWindow::createViewerArea()
m_btnZoomOut->setAutoRepeat(true);
m_btnZoomOut->setAutoRepeatDelay(300);
m_btnZoomOut->setAutoRepeatInterval(50);
connect(m_btnZoomOut, &QToolButton::clicked, m_glWidget, &PointCloudGLWidget::zoomOut);
m_glWidget->installEventFilter(this);
// 连接信号
connect(m_btnZoomOut, &QToolButton::clicked, m_glWidget, &PointCloudGLWidget::zoomOut);
m_glWidget->installEventFilter(this);
// 初始创建时可能没有收到 resize 事件,立即将按钮放到视口右侧居中。
updateZoomButtonGeometry();
// 连接信号
connect(m_glWidget, &PointCloudGLWidget::pointSelected, this, &CloudViewMainWindow::onPointSelected);
connect(m_glWidget, &PointCloudGLWidget::twoPointsSelected, this, &CloudViewMainWindow::onTwoPointsSelected);
connect(m_glWidget, &PointCloudGLWidget::lineSelected, this, &CloudViewMainWindow::onLineSelected);