#include "CloudViewMainWindow.h" #include #include "VrLog.h" CloudViewMainWindow::CloudViewMainWindow(QWidget* parent) : QMainWindow(parent) , m_glWidget(nullptr) , m_converter(std::make_unique()) , m_cloudCount(0) , m_currentLineNum(0) , m_currentLinePtNum(0) { setupUI(); LOG_INFO("CloudViewMainWindow initialized\n"); } CloudViewMainWindow::~CloudViewMainWindow() { } void CloudViewMainWindow::setupUI() { // 创建中央控件 QWidget* centralWidget = new QWidget(this); setCentralWidget(centralWidget); // 创建主布局 QHBoxLayout* mainLayout = new QHBoxLayout(centralWidget); mainLayout->setContentsMargins(5, 5, 5, 5); mainLayout->setSpacing(5); // 创建分割器 QSplitter* splitter = new QSplitter(Qt::Horizontal, centralWidget); // 左侧:点云显示区域 QWidget* viewerArea = createViewerArea(); splitter->addWidget(viewerArea); // 右侧:控制面板 QWidget* controlPanel = createControlPanel(); splitter->addWidget(controlPanel); // 设置分割器初始大小(左侧 70%,右侧 30%) splitter->setSizes({700, 300}); mainLayout->addWidget(splitter); // 状态栏 statusBar()->showMessage("就绪"); } QWidget* CloudViewMainWindow::createViewerArea() { QWidget* widget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(widget); layout->setContentsMargins(0, 0, 0, 0); m_glWidget = new PointCloudGLWidget(widget); m_glWidget->setMinimumSize(400, 300); // 设置最小尺寸 m_glWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addWidget(m_glWidget); // 连接信号 connect(m_glWidget, &PointCloudGLWidget::pointSelected, this, &CloudViewMainWindow::onPointSelected); connect(m_glWidget, &PointCloudGLWidget::twoPointsSelected, this, &CloudViewMainWindow::onTwoPointsSelected); connect(m_glWidget, &PointCloudGLWidget::lineSelected, this, &CloudViewMainWindow::onLineSelected); return widget; } QWidget* CloudViewMainWindow::createControlPanel() { QWidget* widget = new QWidget(this); widget->setMaximumWidth(300); QVBoxLayout* layout = new QVBoxLayout(widget); layout->setContentsMargins(5, 5, 5, 5); layout->setSpacing(10); // 文件操作组 layout->addWidget(createFileGroup()); // 选点测距组 layout->addWidget(createMeasureGroup()); // 选线拟合组 layout->addWidget(createLineGroup()); // 点云列表组 layout->addWidget(createCloudListGroup()); // 添加弹性空间 layout->addStretch(); return widget; } QGroupBox* CloudViewMainWindow::createFileGroup() { QGroupBox* group = new QGroupBox("文件操作", this); group->setMaximumWidth(300); QVBoxLayout* layout = new QVBoxLayout(group); m_btnOpenFile = new QPushButton("打开文件", group); m_btnOpenFile->setMinimumHeight(30); connect(m_btnOpenFile, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenFile); layout->addWidget(m_btnOpenFile); m_btnClearAll = new QPushButton("清除所有", group); connect(m_btnClearAll, &QPushButton::clicked, this, &CloudViewMainWindow::onClearAll); layout->addWidget(m_btnClearAll); m_btnResetView = new QPushButton("重置视图", group); connect(m_btnResetView, &QPushButton::clicked, this, &CloudViewMainWindow::onResetView); layout->addWidget(m_btnResetView); return group; } QGroupBox* CloudViewMainWindow::createMeasureGroup() { QGroupBox* group = new QGroupBox("选点测距", this); group->setMaximumWidth(300); QVBoxLayout* layout = new QVBoxLayout(group); // 操作说明 QLabel* lblTip = new QLabel("Ctrl+左键点击点云选择点", group); lblTip->setWordWrap(true); lblTip->setStyleSheet("color: gray; font-size: 10px;"); layout->addWidget(lblTip); // 清除选点按钮 m_btnClearPoints = new QPushButton("清除选点", group); connect(m_btnClearPoints, &QPushButton::clicked, this, &CloudViewMainWindow::onClearSelectedPoints); layout->addWidget(m_btnClearPoints); // 点1信息 QHBoxLayout* point1Layout = new QHBoxLayout(); QLabel* lblPoint1Title = new QLabel("点1:", group); m_lblPoint1 = new QLabel("--", group); m_lblPoint1->setWordWrap(true); point1Layout->addWidget(lblPoint1Title); point1Layout->addWidget(m_lblPoint1, 1); layout->addLayout(point1Layout); // 点2信息 QHBoxLayout* point2Layout = new QHBoxLayout(); QLabel* lblPoint2Title = new QLabel("点2:", group); m_lblPoint2 = new QLabel("--", group); m_lblPoint2->setWordWrap(true); point2Layout->addWidget(lblPoint2Title); point2Layout->addWidget(m_lblPoint2, 1); layout->addLayout(point2Layout); // 距离信息 QHBoxLayout* distLayout = new QHBoxLayout(); QLabel* lblDistTitle = new QLabel("距离:", group); m_lblDistance = new QLabel("--", group); m_lblDistance->setStyleSheet("font-weight: bold; color: green;"); distLayout->addWidget(lblDistTitle); distLayout->addWidget(m_lblDistance, 1); layout->addLayout(distLayout); return group; } QGroupBox* CloudViewMainWindow::createLineGroup() { QGroupBox* group = new QGroupBox("选线", this); group->setMaximumWidth(300); QVBoxLayout* layout = new QVBoxLayout(group); // 操作说明 QLabel* lblTip = new QLabel("Shift+左键点击点云选择线", group); lblTip->setWordWrap(true); lblTip->setStyleSheet("color: gray; font-size: 10px;"); layout->addWidget(lblTip); // 选线模式选择 QHBoxLayout* modeLayout = new QHBoxLayout(); m_rbVertical = new QRadioButton("纵向", group); m_rbHorizontal = new QRadioButton("横向", group); m_rbVertical->setChecked(true); connect(m_rbVertical, &QRadioButton::toggled, this, &CloudViewMainWindow::onLineSelectModeChanged); modeLayout->addWidget(m_rbVertical); modeLayout->addWidget(m_rbHorizontal); layout->addLayout(modeLayout); // 输入索引选择 QHBoxLayout* inputLayout = new QHBoxLayout(); m_lineNumberInput = new QLineEdit(group); m_lineNumberInput->setPlaceholderText("输入索引"); m_btnSelectByNumber = new QPushButton("选择", group); connect(m_btnSelectByNumber, &QPushButton::clicked, this, &CloudViewMainWindow::onSelectLineByNumber); inputLayout->addWidget(m_lineNumberInput, 1); inputLayout->addWidget(m_btnSelectByNumber); layout->addLayout(inputLayout); // 清除选线按钮 m_btnClearLine = new QPushButton("清除选线", group); connect(m_btnClearLine, &QPushButton::clicked, this, &CloudViewMainWindow::onClearLinePoints); layout->addWidget(m_btnClearLine); // 线索引信息 QHBoxLayout* indexLayout = new QHBoxLayout(); QLabel* lblIndexTitle = new QLabel("索引:", group); m_lblLineIndex = new QLabel("--", group); indexLayout->addWidget(lblIndexTitle); indexLayout->addWidget(m_lblLineIndex, 1); layout->addLayout(indexLayout); // 点数信息 QHBoxLayout* countLayout = new QHBoxLayout(); QLabel* lblCountTitle = new QLabel("点数:", group); m_lblLinePointCount = new QLabel("--", group); countLayout->addWidget(lblCountTitle); countLayout->addWidget(m_lblLinePointCount, 1); layout->addLayout(countLayout); return group; } QGroupBox* CloudViewMainWindow::createCloudListGroup() { QGroupBox* group = new QGroupBox("点云列表", this); group->setMaximumWidth(300); QVBoxLayout* layout = new QVBoxLayout(group); m_cloudList = new QListWidget(group); m_cloudList->setMinimumHeight(100); layout->addWidget(m_cloudList); return group; } void CloudViewMainWindow::onOpenFile() { QString fileName = QFileDialog::getOpenFileName( this, "打开点云文件", QString(), "点云文件 (*.pcd *.txt);;PCD 文件 (*.pcd);;TXT 文件 (*.txt);;所有文件 (*.*)" ); if (fileName.isEmpty()) { return; } statusBar()->showMessage("正在加载点云..."); // 使用 PointCloudConverter 加载点云 PointCloudXYZ cloud; int result = m_converter->loadFromFile(fileName.toStdString(), cloud); if (result != 0) { QMessageBox::critical(this, "错误", QString("加载点云失败: %1").arg(QString::fromStdString(m_converter->getLastError()))); statusBar()->showMessage("加载失败"); return; } // 保存原始完整点云(包含0,0,0点,用于旋转) m_originalCloud = cloud; // 添加到显示控件(显示时会自动过滤0,0,0点) QFileInfo fileInfo(fileName); QString cloudName = QString("Cloud_%1 (%2)").arg(++m_cloudCount).arg(fileInfo.fileName()); m_glWidget->addPointCloud(cloud, cloudName); // 保存线信息(用于旋转功能) int lineCount = m_converter->getLoadedLineCount(); if (lineCount > 0) { m_currentLineNum = lineCount; // 计算每线点数(假设均匀分布) m_currentLinePtNum = static_cast(m_converter->getLoadedPointCount()) / lineCount; } else { m_currentLineNum = 0; m_currentLinePtNum = 0; } // 添加到列表 QString itemText; if (lineCount > 0) { itemText = QString("%1 - %2 点, %3 线").arg(cloudName).arg(m_converter->getLoadedPointCount()).arg(lineCount); } else { itemText = QString("%1 - %2 点").arg(cloudName).arg(m_converter->getLoadedPointCount()); } m_cloudList->addItem(itemText); statusBar()->showMessage(QString("已加载 %1 个点").arg(m_converter->getLoadedPointCount())); } void CloudViewMainWindow::onClearAll() { m_glWidget->clearPointClouds(); m_cloudList->clear(); m_cloudCount = 0; m_currentLineNum = 0; m_currentLinePtNum = 0; m_originalCloud.clear(); // 清除选点信息 m_lblPoint1->setText("--"); m_lblPoint2->setText("--"); m_lblDistance->setText("--"); // 清除选线信息 m_lblLineIndex->setText("--"); m_lblLinePointCount->setText("--"); statusBar()->showMessage("已清除所有点云"); } void CloudViewMainWindow::onResetView() { m_glWidget->resetView(); statusBar()->showMessage("视图已重置"); } void CloudViewMainWindow::onClearSelectedPoints() { m_glWidget->clearSelectedPoints(); m_lblPoint1->setText("--"); m_lblPoint2->setText("--"); m_lblDistance->setText("--"); statusBar()->showMessage("已清除选中的点"); } void CloudViewMainWindow::onPointSelected(const SelectedPointInfo& point) { if (!point.valid) { return; } updateSelectedPointsDisplay(); statusBar()->showMessage(QString("已选中点: (%1, %2, %3)") .arg(point.x, 0, 'f', 3) .arg(point.y, 0, 'f', 3) .arg(point.z, 0, 'f', 3)); } void CloudViewMainWindow::onTwoPointsSelected(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance) { updateSelectedPointsDisplay(); m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); statusBar()->showMessage(QString("测量距离: %1 mm").arg(distance, 0, 'f', 3)); } void CloudViewMainWindow::updateSelectedPointsDisplay() { auto selectedPoints = m_glWidget->getSelectedPoints(); if (selectedPoints.size() >= 1 && selectedPoints[0].valid) { QString text = QString("(%1, %2, %3)") .arg(selectedPoints[0].x, 0, 'f', 3) .arg(selectedPoints[0].y, 0, 'f', 3) .arg(selectedPoints[0].z, 0, 'f', 3); if (selectedPoints[0].lineIndex >= 0) { text += QString("\n线索引:%1").arg(selectedPoints[0].lineIndex); if (selectedPoints[0].pointIndexInLine >= 0) { text += QString(" 点索引:%1").arg(selectedPoints[0].pointIndexInLine); } } m_lblPoint1->setText(text); } else { m_lblPoint1->setText("--"); } if (selectedPoints.size() >= 2 && selectedPoints[1].valid) { QString text = QString("(%1, %2, %3)") .arg(selectedPoints[1].x, 0, 'f', 3) .arg(selectedPoints[1].y, 0, 'f', 3) .arg(selectedPoints[1].z, 0, 'f', 3); if (selectedPoints[1].lineIndex >= 0) { text += QString("\n线索引:%1").arg(selectedPoints[1].lineIndex); if (selectedPoints[1].pointIndexInLine >= 0) { text += QString(" 点索引:%1").arg(selectedPoints[1].pointIndexInLine); } } m_lblPoint2->setText(text); } else { m_lblPoint2->setText("--"); } } void CloudViewMainWindow::onLineSelectModeChanged(bool checked) { if (checked) { // 纵向模式 m_glWidget->setLineSelectMode(LineSelectMode::Vertical); } else { // 横向模式 m_glWidget->setLineSelectMode(LineSelectMode::Horizontal); } m_lineNumberInput->setPlaceholderText("输入索引"); } void CloudViewMainWindow::onClearLinePoints() { m_glWidget->clearSelectedLine(); m_lblLineIndex->setText("--"); m_lblLinePointCount->setText("--"); statusBar()->showMessage("已清除选线"); } void CloudViewMainWindow::onLineSelected(const SelectedLineInfo& line) { if (!line.valid) { m_lblLineIndex->setText("--"); m_lblLinePointCount->setText("--"); return; } if (line.mode == LineSelectMode::Vertical) { m_lblLineIndex->setText(QString::number(line.lineIndex)); statusBar()->showMessage(QString("已选中索引 %1 的线,共 %2 个点") .arg(line.lineIndex) .arg(line.pointCount)); } else { // 横向选线:显示索引(从0开始) m_lblLineIndex->setText(QString::number(line.pointIndex)); statusBar()->showMessage(QString("已选中索引 %1 的横向线,共 %2 个点") .arg(line.pointIndex) .arg(line.pointCount)); } m_lblLinePointCount->setText(QString::number(line.pointCount)); } void CloudViewMainWindow::onSelectLineByNumber() { if (m_glWidget->getCloudCount() == 0) { QMessageBox::warning(this, "提示", "请先加载点云"); return; } QString text = m_lineNumberInput->text().trimmed(); if (text.isEmpty()) { QMessageBox::warning(this, "提示", "请输入索引"); return; } bool ok; int index = text.toInt(&ok); if (!ok || index < 0) { QMessageBox::warning(this, "提示", "请输入有效的索引(非负整数)"); return; } bool success = false; if (m_rbVertical->isChecked()) { // 纵向选线:直接使用索引 success = m_glWidget->selectLineByIndex(index); if (!success) { QMessageBox::warning(this, "提示", QString("索引 %1 不存在").arg(index)); } } else { // 横向选线:直接使用索引 success = m_glWidget->selectHorizontalLineByIndex(index); if (!success) { QMessageBox::warning(this, "提示", QString("索引 %1 不存在").arg(index)); } } }