1246 lines
42 KiB
C++
1246 lines
42 KiB
C++
#include "CloudViewMainWindow.h"
|
||
#include <QFileInfo>
|
||
#include <QDialog>
|
||
#include <QTextEdit>
|
||
#include <QTableWidget>
|
||
#include <QHeaderView>
|
||
#include <QVector3D>
|
||
#include <QFile>
|
||
#include <QTextStream>
|
||
#include <QRegExp>
|
||
#include <QFrame>
|
||
#include <QTabWidget>
|
||
#include <cmath>
|
||
#include "VrLog.h"
|
||
|
||
CloudViewMainWindow::CloudViewMainWindow(QWidget* parent)
|
||
: QMainWindow(parent)
|
||
, m_glWidget(nullptr)
|
||
, m_converter(std::make_unique<PointCloudConverter>())
|
||
, m_cloudCount(0)
|
||
, m_currentLineNum(0)
|
||
, m_currentLinePtNum(0)
|
||
, m_linePointsDialog(nullptr)
|
||
, m_linePointsTable(nullptr)
|
||
{
|
||
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(400); // 加宽到400
|
||
QVBoxLayout* layout = new QVBoxLayout(widget);
|
||
layout->setContentsMargins(5, 5, 5, 5);
|
||
layout->setSpacing(10);
|
||
|
||
// 文件操作组
|
||
layout->addWidget(createFileGroup());
|
||
|
||
// 创建 Tab 控件
|
||
QTabWidget* tabWidget = new QTabWidget(widget);
|
||
tabWidget->addTab(createMeasurePage(), "选点测距");
|
||
tabWidget->addTab(createLinePage(), "选线");
|
||
layout->addWidget(tabWidget);
|
||
|
||
// 点云列表组
|
||
layout->addWidget(createCloudListGroup());
|
||
|
||
// 添加弹性空间
|
||
layout->addStretch();
|
||
|
||
return widget;
|
||
}
|
||
|
||
QGroupBox* CloudViewMainWindow::createFileGroup()
|
||
{
|
||
QGroupBox* group = new QGroupBox("文件操作", this);
|
||
group->setMaximumWidth(400);
|
||
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_btnOpenSegment = new QPushButton("打开线段 {x,y,z}-{x,y,z}", group);
|
||
connect(m_btnOpenSegment, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenSegmentFile);
|
||
layout->addWidget(m_btnOpenSegment);
|
||
|
||
m_btnOpenPose = new QPushButton("打开姿态点 {x,y,z}-{r,p,y}", group);
|
||
connect(m_btnOpenPose, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenPoseFile);
|
||
layout->addWidget(m_btnOpenPose);
|
||
|
||
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;
|
||
}
|
||
|
||
QWidget* CloudViewMainWindow::createMeasurePage()
|
||
{
|
||
QWidget* page = new QWidget(this);
|
||
QVBoxLayout* layout = new QVBoxLayout(page);
|
||
layout->setContentsMargins(0, 5, 0, 0);
|
||
layout->addWidget(createMeasureGroup());
|
||
layout->addStretch();
|
||
return page;
|
||
}
|
||
|
||
QWidget* CloudViewMainWindow::createLinePage()
|
||
{
|
||
QWidget* page = new QWidget(this);
|
||
QVBoxLayout* layout = new QVBoxLayout(page);
|
||
layout->setContentsMargins(0, 5, 0, 0);
|
||
|
||
// 选线拟合组
|
||
layout->addWidget(createLineGroup());
|
||
|
||
// 输入线段组
|
||
QGroupBox* inputLineGroup = new QGroupBox("输入线段", page);
|
||
QVBoxLayout* inputLayout = new QVBoxLayout(inputLineGroup);
|
||
|
||
// 提示
|
||
QLabel* lblTip = new QLabel("输入两点坐标显示线段", inputLineGroup);
|
||
lblTip->setStyleSheet("color: gray; font-size: 10px;");
|
||
inputLayout->addWidget(lblTip);
|
||
|
||
// 点1坐标
|
||
QLabel* lblPoint1 = new QLabel("点1:", inputLineGroup);
|
||
lblPoint1->setStyleSheet("font-weight: bold;");
|
||
inputLayout->addWidget(lblPoint1);
|
||
|
||
QHBoxLayout* p1Layout = new QHBoxLayout();
|
||
p1Layout->addWidget(new QLabel("X:", inputLineGroup));
|
||
m_editLineX1 = new QLineEdit("0.0", inputLineGroup);
|
||
m_editLineX1->setMaximumWidth(70);
|
||
p1Layout->addWidget(m_editLineX1);
|
||
|
||
p1Layout->addWidget(new QLabel("Y:", inputLineGroup));
|
||
m_editLineY1 = new QLineEdit("0.0", inputLineGroup);
|
||
m_editLineY1->setMaximumWidth(70);
|
||
p1Layout->addWidget(m_editLineY1);
|
||
|
||
p1Layout->addWidget(new QLabel("Z:", inputLineGroup));
|
||
m_editLineZ1 = new QLineEdit("0.0", inputLineGroup);
|
||
m_editLineZ1->setMaximumWidth(70);
|
||
p1Layout->addWidget(m_editLineZ1);
|
||
inputLayout->addLayout(p1Layout);
|
||
|
||
// 点2坐标
|
||
QLabel* lblPoint2 = new QLabel("点2:", inputLineGroup);
|
||
lblPoint2->setStyleSheet("font-weight: bold;");
|
||
inputLayout->addWidget(lblPoint2);
|
||
|
||
QHBoxLayout* p2Layout = new QHBoxLayout();
|
||
p2Layout->addWidget(new QLabel("X:", inputLineGroup));
|
||
m_editLineX2 = new QLineEdit("100.0", inputLineGroup);
|
||
m_editLineX2->setMaximumWidth(70);
|
||
p2Layout->addWidget(m_editLineX2);
|
||
|
||
p2Layout->addWidget(new QLabel("Y:", inputLineGroup));
|
||
m_editLineY2 = new QLineEdit("100.0", inputLineGroup);
|
||
m_editLineY2->setMaximumWidth(70);
|
||
p2Layout->addWidget(m_editLineY2);
|
||
|
||
p2Layout->addWidget(new QLabel("Z:", inputLineGroup));
|
||
m_editLineZ2 = new QLineEdit("100.0", inputLineGroup);
|
||
m_editLineZ2->setMaximumWidth(70);
|
||
p2Layout->addWidget(m_editLineZ2);
|
||
inputLayout->addLayout(p2Layout);
|
||
|
||
// 按钮
|
||
QHBoxLayout* btnLayout = new QHBoxLayout();
|
||
m_btnShowLine = new QPushButton("显示线段", inputLineGroup);
|
||
connect(m_btnShowLine, &QPushButton::clicked, this, &CloudViewMainWindow::onShowInputLine);
|
||
btnLayout->addWidget(m_btnShowLine);
|
||
|
||
m_btnClearLine2 = new QPushButton("清除线段", inputLineGroup);
|
||
connect(m_btnClearLine2, &QPushButton::clicked, this, &CloudViewMainWindow::onClearInputLine);
|
||
btnLayout->addWidget(m_btnClearLine2);
|
||
inputLayout->addLayout(btnLayout);
|
||
|
||
layout->addWidget(inputLineGroup);
|
||
layout->addStretch();
|
||
return page;
|
||
}
|
||
|
||
QGroupBox* CloudViewMainWindow::createMeasureGroup()
|
||
{
|
||
QGroupBox* group = new QGroupBox("选点测距", this);
|
||
group->setMaximumWidth(400);
|
||
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_cbMeasureDistance = new QCheckBox("启用测距", group);
|
||
m_cbMeasureDistance->setChecked(false);
|
||
connect(m_cbMeasureDistance, &QCheckBox::toggled, this, [this](bool checked) {
|
||
m_glWidget->setMeasureDistanceEnabled(checked);
|
||
// 切换模式时清除已选点
|
||
m_glWidget->clearSelectedPoints();
|
||
m_lblPoint1->setText("--");
|
||
m_lblPoint2->setText("--");
|
||
m_lblDistance->setText("--");
|
||
});
|
||
layout->addWidget(m_cbMeasureDistance);
|
||
|
||
// 清除选点按钮
|
||
m_btnClearPoints = new QPushButton("清除选点", group);
|
||
connect(m_btnClearPoints, &QPushButton::clicked, this, &CloudViewMainWindow::onClearSelectedPoints);
|
||
layout->addWidget(m_btnClearPoints);
|
||
|
||
// 分隔线
|
||
QFrame* line1 = new QFrame(group);
|
||
line1->setFrameShape(QFrame::HLine);
|
||
line1->setFrameShadow(QFrame::Sunken);
|
||
layout->addWidget(line1);
|
||
|
||
// ========== 点1信息和姿态 ==========
|
||
QLabel* lblPoint1Title = new QLabel("点1:", group);
|
||
lblPoint1Title->setStyleSheet("font-weight: bold;");
|
||
layout->addWidget(lblPoint1Title);
|
||
|
||
m_lblPoint1 = new QLabel("--", group);
|
||
m_lblPoint1->setWordWrap(true);
|
||
layout->addWidget(m_lblPoint1);
|
||
|
||
// 点1姿态输入
|
||
QHBoxLayout* pose1Layout = new QHBoxLayout();
|
||
pose1Layout->addWidget(new QLabel("RX:", group));
|
||
m_editRx1 = new QLineEdit("0.0", group);
|
||
m_editRx1->setMaximumWidth(60);
|
||
pose1Layout->addWidget(m_editRx1);
|
||
pose1Layout->addWidget(new QLabel("°", group));
|
||
|
||
pose1Layout->addWidget(new QLabel("RY:", group));
|
||
m_editRy1 = new QLineEdit("0.0", group);
|
||
m_editRy1->setMaximumWidth(60);
|
||
pose1Layout->addWidget(m_editRy1);
|
||
pose1Layout->addWidget(new QLabel("°", group));
|
||
|
||
pose1Layout->addWidget(new QLabel("RZ:", group));
|
||
m_editRz1 = new QLineEdit("0.0", group);
|
||
m_editRz1->setMaximumWidth(60);
|
||
pose1Layout->addWidget(m_editRz1);
|
||
pose1Layout->addWidget(new QLabel("°", group));
|
||
layout->addLayout(pose1Layout);
|
||
|
||
m_btnShowPose1 = new QPushButton("显示点1姿态", group);
|
||
connect(m_btnShowPose1, &QPushButton::clicked, this, &CloudViewMainWindow::onShowPose1);
|
||
layout->addWidget(m_btnShowPose1);
|
||
|
||
// 分隔线
|
||
QFrame* line2 = new QFrame(group);
|
||
line2->setFrameShape(QFrame::HLine);
|
||
line2->setFrameShadow(QFrame::Sunken);
|
||
layout->addWidget(line2);
|
||
|
||
// ========== 点2信息和姿态 ==========
|
||
QLabel* lblPoint2Title = new QLabel("点2:", group);
|
||
lblPoint2Title->setStyleSheet("font-weight: bold;");
|
||
layout->addWidget(lblPoint2Title);
|
||
|
||
m_lblPoint2 = new QLabel("--", group);
|
||
m_lblPoint2->setWordWrap(true);
|
||
layout->addWidget(m_lblPoint2);
|
||
|
||
// 点2姿态输入
|
||
QHBoxLayout* pose2Layout = new QHBoxLayout();
|
||
pose2Layout->addWidget(new QLabel("RX:", group));
|
||
m_editRx2 = new QLineEdit("0.0", group);
|
||
m_editRx2->setMaximumWidth(60);
|
||
pose2Layout->addWidget(m_editRx2);
|
||
pose2Layout->addWidget(new QLabel("°", group));
|
||
|
||
pose2Layout->addWidget(new QLabel("RY:", group));
|
||
m_editRy2 = new QLineEdit("0.0", group);
|
||
m_editRy2->setMaximumWidth(60);
|
||
pose2Layout->addWidget(m_editRy2);
|
||
pose2Layout->addWidget(new QLabel("°", group));
|
||
|
||
pose2Layout->addWidget(new QLabel("RZ:", group));
|
||
m_editRz2 = new QLineEdit("0.0", group);
|
||
m_editRz2->setMaximumWidth(60);
|
||
pose2Layout->addWidget(m_editRz2);
|
||
pose2Layout->addWidget(new QLabel("°", group));
|
||
layout->addLayout(pose2Layout);
|
||
|
||
m_btnShowPose2 = new QPushButton("显示点2姿态", group);
|
||
connect(m_btnShowPose2, &QPushButton::clicked, this, &CloudViewMainWindow::onShowPose2);
|
||
layout->addWidget(m_btnShowPose2);
|
||
|
||
// 分隔线
|
||
QFrame* line3 = new QFrame(group);
|
||
line3->setFrameShape(QFrame::HLine);
|
||
line3->setFrameShadow(QFrame::Sunken);
|
||
layout->addWidget(line3);
|
||
|
||
// 距离信息
|
||
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(400);
|
||
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);
|
||
|
||
// 显示线上点按钮
|
||
m_btnShowLinePoints = new QPushButton("显示线上点", group);
|
||
connect(m_btnShowLinePoints, &QPushButton::clicked, this, &CloudViewMainWindow::onShowLinePoints);
|
||
layout->addWidget(m_btnShowLinePoints);
|
||
|
||
// 线索引信息
|
||
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(400);
|
||
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<int>(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::onOpenSegmentFile()
|
||
{
|
||
QString fileName = QFileDialog::getOpenFileName(
|
||
this,
|
||
"打开线段文件",
|
||
QString(),
|
||
"文本文件 (*.txt);;所有文件 (*.*)"
|
||
);
|
||
|
||
if (fileName.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
statusBar()->showMessage("正在加载线段...");
|
||
|
||
QFile file(fileName);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName));
|
||
statusBar()->showMessage("加载失败");
|
||
return;
|
||
}
|
||
|
||
QVector<LineSegment> segments;
|
||
QTextStream in(&file);
|
||
int lineNum = 0;
|
||
int validCount = 0;
|
||
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine().trimmed();
|
||
lineNum++;
|
||
|
||
// 跳过空行和注释
|
||
if (line.isEmpty() || line.startsWith('#')) {
|
||
continue;
|
||
}
|
||
|
||
// 解析格式:{x,y,z}-{x,y,z}
|
||
QRegExp regex("\\{([^}]+)\\}-\\{([^}]+)\\}");
|
||
if (regex.indexIn(line) == -1) {
|
||
LOG_WARN("[CloudView] Line %d: Invalid format, expected {x,y,z}-{x,y,z}\n", lineNum);
|
||
continue;
|
||
}
|
||
|
||
QString point1Str = regex.cap(1);
|
||
QString point2Str = regex.cap(2);
|
||
|
||
QStringList p1 = point1Str.split(',');
|
||
QStringList p2 = point2Str.split(',');
|
||
|
||
if (p1.size() != 3 || p2.size() != 3) {
|
||
LOG_WARN("[CloudView] Line %d: Invalid point format\n", lineNum);
|
||
continue;
|
||
}
|
||
|
||
bool ok = true;
|
||
float x1 = p1[0].toFloat(&ok); if (!ok) continue;
|
||
float y1 = p1[1].toFloat(&ok); if (!ok) continue;
|
||
float z1 = p1[2].toFloat(&ok); if (!ok) continue;
|
||
float x2 = p2[0].toFloat(&ok); if (!ok) continue;
|
||
float y2 = p2[1].toFloat(&ok); if (!ok) continue;
|
||
float z2 = p2[2].toFloat(&ok); if (!ok) continue;
|
||
|
||
// 默认白色
|
||
segments.append(LineSegment(x1, y1, z1, x2, y2, z2, 1.0f, 1.0f, 1.0f));
|
||
validCount++;
|
||
}
|
||
|
||
file.close();
|
||
|
||
if (segments.isEmpty()) {
|
||
QMessageBox::warning(this, "警告", "文件中没有有效的线段数据");
|
||
statusBar()->showMessage("加载失败");
|
||
return;
|
||
}
|
||
|
||
m_glWidget->addLineSegments(segments);
|
||
statusBar()->showMessage(QString("已加载 %1 条线段").arg(validCount));
|
||
LOG_INFO("[CloudView] Loaded %d line segments from %s\n", validCount, fileName.toStdString().c_str());
|
||
}
|
||
|
||
void CloudViewMainWindow::onOpenPoseFile()
|
||
{
|
||
QString fileName = QFileDialog::getOpenFileName(
|
||
this,
|
||
"打开姿态点文件",
|
||
QString(),
|
||
"文本文件 (*.txt);;所有文件 (*.*)"
|
||
);
|
||
|
||
if (fileName.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
statusBar()->showMessage("正在加载姿态点...");
|
||
|
||
QFile file(fileName);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName));
|
||
statusBar()->showMessage("加载失败");
|
||
return;
|
||
}
|
||
|
||
QVector<PosePoint> poses;
|
||
QTextStream in(&file);
|
||
int lineNum = 0;
|
||
int validCount = 0;
|
||
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine().trimmed();
|
||
lineNum++;
|
||
|
||
// 跳过空行和注释
|
||
if (line.isEmpty() || line.startsWith('#')) {
|
||
continue;
|
||
}
|
||
|
||
// 解析格式:{x,y,z}-{r,p,y}
|
||
QRegExp regex("\\{([^}]+)\\}-\\{([^}]+)\\}");
|
||
if (regex.indexIn(line) == -1) {
|
||
LOG_WARN("[CloudView] Line %d: Invalid format, expected {x,y,z}-{r,p,y}\n", lineNum);
|
||
continue;
|
||
}
|
||
|
||
QString posStr = regex.cap(1);
|
||
QString rotStr = regex.cap(2);
|
||
|
||
QStringList pos = posStr.split(',');
|
||
QStringList rot = rotStr.split(',');
|
||
|
||
if (pos.size() != 3 || rot.size() != 3) {
|
||
LOG_WARN("[CloudView] Line %d: Invalid point format\n", lineNum);
|
||
continue;
|
||
}
|
||
|
||
bool ok = true;
|
||
float x = pos[0].toFloat(&ok); if (!ok) continue;
|
||
float y = pos[1].toFloat(&ok); if (!ok) continue;
|
||
float z = pos[2].toFloat(&ok); if (!ok) continue;
|
||
float roll = rot[0].toFloat(&ok); if (!ok) continue;
|
||
float pitch = rot[1].toFloat(&ok); if (!ok) continue;
|
||
float yaw = rot[2].toFloat(&ok); if (!ok) continue;
|
||
|
||
// 固定大小为10
|
||
float scale = 10.0f;
|
||
|
||
poses.append(PosePoint(x, y, z, roll, pitch, yaw, scale));
|
||
validCount++;
|
||
}
|
||
|
||
file.close();
|
||
|
||
if (poses.isEmpty()) {
|
||
QMessageBox::warning(this, "警告", "文件中没有有效的姿态点数据");
|
||
statusBar()->showMessage("加载失败");
|
||
return;
|
||
}
|
||
|
||
m_glWidget->addPosePoints(poses);
|
||
statusBar()->showMessage(QString("已加载 %1 个姿态点").arg(validCount));
|
||
LOG_INFO("[CloudView] Loaded %d pose points from %s\n", validCount, fileName.toStdString().c_str());
|
||
}
|
||
|
||
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_glWidget->clearPosePoints(); // 清除选点时也清除姿态
|
||
m_lblPoint1->setText("--");
|
||
m_lblPoint2->setText("--");
|
||
m_lblDistance->setText("--");
|
||
statusBar()->showMessage("已清除选中的点");
|
||
}
|
||
|
||
void CloudViewMainWindow::onPointSelected(const SelectedPointInfo& point)
|
||
{
|
||
if (!point.valid) {
|
||
return;
|
||
}
|
||
|
||
// 选择新点时清除之前的姿态显示
|
||
m_glWidget->clearPosePoints();
|
||
|
||
updateSelectedPointsDisplay();
|
||
|
||
// 状态栏显示:坐标、线号、索引号
|
||
QString statusMsg = QString("选中点: (%1, %2, %3)")
|
||
.arg(point.x, 0, 'f', 3)
|
||
.arg(point.y, 0, 'f', 3)
|
||
.arg(point.z, 0, 'f', 3);
|
||
if (point.lineIndex >= 0) {
|
||
statusMsg += QString(" | 线号: %1").arg(point.lineIndex);
|
||
if (point.pointIndexInLine >= 0) {
|
||
statusMsg += QString(" | 索引号: %1").arg(point.pointIndexInLine);
|
||
}
|
||
}
|
||
statusBar()->showMessage(statusMsg);
|
||
}
|
||
|
||
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;
|
||
if (selectedPoints[0].lineIndex >= 0) {
|
||
text = QString("线索引:%1 点索引:%2\nx: %3 y: %4 z: %5")
|
||
.arg(selectedPoints[0].lineIndex)
|
||
.arg(selectedPoints[0].pointIndexInLine)
|
||
.arg(selectedPoints[0].x, 0, 'f', 3)
|
||
.arg(selectedPoints[0].y, 0, 'f', 3)
|
||
.arg(selectedPoints[0].z, 0, 'f', 3);
|
||
} else {
|
||
text = QString("x: %1 y: %2 z: %3")
|
||
.arg(selectedPoints[0].x, 0, 'f', 3)
|
||
.arg(selectedPoints[0].y, 0, 'f', 3)
|
||
.arg(selectedPoints[0].z, 0, 'f', 3);
|
||
}
|
||
m_lblPoint1->setText(text);
|
||
} else {
|
||
m_lblPoint1->setText("--");
|
||
}
|
||
|
||
if (selectedPoints.size() >= 2 && selectedPoints[1].valid) {
|
||
QString text;
|
||
if (selectedPoints[1].lineIndex >= 0) {
|
||
text = QString("线索引:%1 点索引:%2\nx: %3 y: %4 z: %5")
|
||
.arg(selectedPoints[1].lineIndex)
|
||
.arg(selectedPoints[1].pointIndexInLine)
|
||
.arg(selectedPoints[1].x, 0, 'f', 3)
|
||
.arg(selectedPoints[1].y, 0, 'f', 3)
|
||
.arg(selectedPoints[1].z, 0, 'f', 3);
|
||
} else {
|
||
text = QString("x: %1 y: %2 z: %3")
|
||
.arg(selectedPoints[1].x, 0, 'f', 3)
|
||
.arg(selectedPoints[1].y, 0, 'f', 3)
|
||
.arg(selectedPoints[1].z, 0, 'f', 3);
|
||
}
|
||
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_glWidget->clearListHighlightPoint(); // 清除列表选中的高亮点
|
||
m_lblLineIndex->setText("--");
|
||
m_lblLinePointCount->setText("--");
|
||
statusBar()->showMessage("已清除选线");
|
||
}
|
||
|
||
void CloudViewMainWindow::onLineSelected(const SelectedLineInfo& line)
|
||
{
|
||
// 重新选线时清除列表高亮点
|
||
m_glWidget->clearListHighlightPoint();
|
||
|
||
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 {
|
||
// 横向选线:显示索引号
|
||
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));
|
||
|
||
// 如果线上点对话框已打开,刷新内容
|
||
updateLinePointsDialog();
|
||
}
|
||
|
||
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));
|
||
}
|
||
}
|
||
}
|
||
|
||
QVector<QVector3D> CloudViewMainWindow::getOriginalLinePoints(const SelectedLineInfo& lineInfo)
|
||
{
|
||
QVector<QVector3D> points;
|
||
|
||
if (!lineInfo.valid || m_originalCloud.empty()) {
|
||
return points;
|
||
}
|
||
|
||
if (lineInfo.mode == LineSelectMode::Vertical) {
|
||
// 纵向选线:获取同一条扫描线上的所有点
|
||
for (size_t i = 0; i < m_originalCloud.points.size(); ++i) {
|
||
if (i < m_originalCloud.lineIndices.size() &&
|
||
m_originalCloud.lineIndices[i] == lineInfo.lineIndex) {
|
||
const auto& pt = m_originalCloud.points[i];
|
||
points.append(QVector3D(pt.x, pt.y, pt.z));
|
||
}
|
||
}
|
||
} else {
|
||
// 横向选线:获取所有线的相同索引点
|
||
if (m_currentLinePtNum > 0 && lineInfo.pointIndex >= 0) {
|
||
for (size_t i = 0; i < m_originalCloud.points.size(); ++i) {
|
||
int originalIdx = static_cast<int>(i);
|
||
if (originalIdx % m_currentLinePtNum == lineInfo.pointIndex) {
|
||
const auto& pt = m_originalCloud.points[i];
|
||
points.append(QVector3D(pt.x, pt.y, pt.z));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return points;
|
||
}
|
||
|
||
void CloudViewMainWindow::updateLinePointsDialog()
|
||
{
|
||
if (!m_linePointsDialog || !m_linePointsTable) {
|
||
return;
|
||
}
|
||
|
||
SelectedLineInfo lineInfo = m_glWidget->getSelectedLine();
|
||
if (!lineInfo.valid) {
|
||
m_linePointsTable->setRowCount(0);
|
||
m_linePointsDialog->setWindowTitle("线上点坐标");
|
||
m_currentLinePoints.clear();
|
||
return;
|
||
}
|
||
|
||
// 从原始数据获取线上点(包含0,0,0)
|
||
m_currentLinePoints = getOriginalLinePoints(lineInfo);
|
||
|
||
// 更新标题
|
||
QString title;
|
||
if (lineInfo.mode == LineSelectMode::Vertical) {
|
||
title = QString("线上点坐标 - 线号: %1 (共 %2 个点)")
|
||
.arg(lineInfo.lineIndex)
|
||
.arg(m_currentLinePoints.size());
|
||
} else {
|
||
title = QString("线上点坐标 - 索引号: %1 (共 %2 个点)")
|
||
.arg(lineInfo.pointIndex)
|
||
.arg(m_currentLinePoints.size());
|
||
}
|
||
m_linePointsDialog->setWindowTitle(title);
|
||
|
||
// 更新表格
|
||
m_linePointsTable->setRowCount(m_currentLinePoints.size());
|
||
|
||
// 斑马线颜色
|
||
QColor evenColor(245, 245, 245); // 浅灰色
|
||
QColor oddColor(255, 255, 255); // 白色
|
||
|
||
for (int i = 0; i < m_currentLinePoints.size(); ++i) {
|
||
const QVector3D& pt = m_currentLinePoints[i];
|
||
QColor rowColor = (i % 2 == 0) ? evenColor : oddColor;
|
||
|
||
// 序号
|
||
QTableWidgetItem* indexItem = new QTableWidgetItem(QString::number(i));
|
||
indexItem->setTextAlignment(Qt::AlignCenter);
|
||
indexItem->setBackground(rowColor);
|
||
indexItem->setFlags(indexItem->flags() & ~Qt::ItemIsEditable);
|
||
m_linePointsTable->setItem(i, 0, indexItem);
|
||
|
||
// X
|
||
QTableWidgetItem* xItem = new QTableWidgetItem(QString::number(pt.x(), 'f', 3));
|
||
xItem->setTextAlignment(Qt::AlignCenter);
|
||
xItem->setBackground(rowColor);
|
||
xItem->setFlags(xItem->flags() & ~Qt::ItemIsEditable);
|
||
m_linePointsTable->setItem(i, 1, xItem);
|
||
|
||
// Y
|
||
QTableWidgetItem* yItem = new QTableWidgetItem(QString::number(pt.y(), 'f', 3));
|
||
yItem->setTextAlignment(Qt::AlignCenter);
|
||
yItem->setBackground(rowColor);
|
||
yItem->setFlags(yItem->flags() & ~Qt::ItemIsEditable);
|
||
m_linePointsTable->setItem(i, 2, yItem);
|
||
|
||
// Z
|
||
QTableWidgetItem* zItem = new QTableWidgetItem(QString::number(pt.z(), 'f', 3));
|
||
zItem->setTextAlignment(Qt::AlignCenter);
|
||
zItem->setBackground(rowColor);
|
||
zItem->setFlags(zItem->flags() & ~Qt::ItemIsEditable);
|
||
m_linePointsTable->setItem(i, 3, zItem);
|
||
}
|
||
}
|
||
|
||
void CloudViewMainWindow::onShowLinePoints()
|
||
{
|
||
SelectedLineInfo lineInfo = m_glWidget->getSelectedLine();
|
||
if (!lineInfo.valid) {
|
||
QMessageBox::warning(this, "提示", "请先选择一条线");
|
||
return;
|
||
}
|
||
|
||
// 如果对话框已存在,刷新内容并显示
|
||
if (m_linePointsDialog) {
|
||
updateLinePointsDialog();
|
||
m_linePointsDialog->raise();
|
||
m_linePointsDialog->activateWindow();
|
||
return;
|
||
}
|
||
|
||
// 创建对话框
|
||
m_linePointsDialog = new QDialog(this);
|
||
m_linePointsDialog->resize(450, 500);
|
||
m_linePointsDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||
|
||
// 对话框关闭时清理指针
|
||
connect(m_linePointsDialog, &QDialog::destroyed, this, [this]() {
|
||
m_linePointsDialog = nullptr;
|
||
m_linePointsTable = nullptr;
|
||
m_currentLinePoints.clear();
|
||
m_glWidget->clearListHighlightPoint();
|
||
});
|
||
|
||
QVBoxLayout* layout = new QVBoxLayout(m_linePointsDialog);
|
||
|
||
// 提示标签
|
||
QLabel* lblTip = new QLabel("点击行在3D视图中高亮显示", m_linePointsDialog);
|
||
lblTip->setStyleSheet("color: gray; font-size: 10px;");
|
||
layout->addWidget(lblTip);
|
||
|
||
// 创建表格控件
|
||
m_linePointsTable = new QTableWidget(m_linePointsDialog);
|
||
m_linePointsTable->setColumnCount(4);
|
||
m_linePointsTable->setHorizontalHeaderLabels({"序号", "X", "Y", "Z"});
|
||
m_linePointsTable->setFont(QFont("Consolas", 9));
|
||
m_linePointsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
m_linePointsTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
m_linePointsTable->verticalHeader()->setVisible(false);
|
||
|
||
// 设置列宽
|
||
m_linePointsTable->setColumnWidth(0, 60); // 序号
|
||
m_linePointsTable->setColumnWidth(1, 110); // X
|
||
m_linePointsTable->setColumnWidth(2, 110); // Y
|
||
m_linePointsTable->setColumnWidth(3, 110); // Z
|
||
|
||
// 表头样式
|
||
m_linePointsTable->horizontalHeader()->setStretchLastSection(true);
|
||
m_linePointsTable->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter);
|
||
|
||
connect(m_linePointsTable, &QTableWidget::cellClicked,
|
||
this, &CloudViewMainWindow::onLinePointTableClicked);
|
||
layout->addWidget(m_linePointsTable);
|
||
|
||
// 关闭按钮
|
||
QPushButton* btnClose = new QPushButton("关闭", m_linePointsDialog);
|
||
connect(btnClose, &QPushButton::clicked, m_linePointsDialog, &QDialog::close);
|
||
layout->addWidget(btnClose);
|
||
|
||
// 填充数据
|
||
updateLinePointsDialog();
|
||
|
||
m_linePointsDialog->show();
|
||
}
|
||
|
||
void CloudViewMainWindow::onLinePointTableClicked(int row, int column)
|
||
{
|
||
Q_UNUSED(column);
|
||
|
||
if (row >= 0 && row < m_currentLinePoints.size()) {
|
||
const QVector3D& pt = m_currentLinePoints[row];
|
||
m_glWidget->setListHighlightPoint(pt);
|
||
|
||
// 在状态栏显示选中点信息
|
||
statusBar()->showMessage(QString("列表选中点 %1: (%2, %3, %4)")
|
||
.arg(row)
|
||
.arg(pt.x(), 0, 'f', 3)
|
||
.arg(pt.y(), 0, 'f', 3)
|
||
.arg(pt.z(), 0, 'f', 3));
|
||
}
|
||
}
|
||
|
||
void CloudViewMainWindow::onShowPose1()
|
||
{
|
||
auto selectedPoints = m_glWidget->getSelectedPoints();
|
||
if (selectedPoints.isEmpty() || !selectedPoints[0].valid) {
|
||
QMessageBox::warning(this, "提示", "请先选择点1(Ctrl+左键点击点云)");
|
||
return;
|
||
}
|
||
|
||
const auto& point = selectedPoints[0];
|
||
|
||
// 读取姿态参数
|
||
bool ok = true;
|
||
float rx = m_editRx1->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点1 RX 值无效");
|
||
return;
|
||
}
|
||
|
||
float ry = m_editRy1->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点1 RY 值无效");
|
||
return;
|
||
}
|
||
|
||
float rz = m_editRz1->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点1 RZ 值无效");
|
||
return;
|
||
}
|
||
|
||
// 固定大小为10
|
||
float scale = 10.0f;
|
||
|
||
// 清除之前的姿态点
|
||
m_glWidget->clearPosePoints();
|
||
|
||
// 创建点1的姿态点
|
||
PosePoint pose1(point.x, point.y, point.z, rx, ry, rz, scale);
|
||
QVector<PosePoint> poses;
|
||
poses.append(pose1);
|
||
|
||
// 如果点2也存在,添加点2的姿态
|
||
if (selectedPoints.size() >= 2 && selectedPoints[1].valid) {
|
||
const auto& point2 = selectedPoints[1];
|
||
float rx2 = m_editRx2->text().toFloat(&ok);
|
||
float ry2 = m_editRy2->text().toFloat(&ok);
|
||
float rz2 = m_editRz2->text().toFloat(&ok);
|
||
if (ok) {
|
||
PosePoint pose2(point2.x, point2.y, point2.z, rx2, ry2, rz2, scale);
|
||
poses.append(pose2);
|
||
}
|
||
}
|
||
|
||
// 添加到显示
|
||
m_glWidget->addPosePoints(poses);
|
||
|
||
statusBar()->showMessage(QString("已显示点1姿态 (%.3f, %.3f, %.3f) 旋转(%.1f°, %.1f°, %.1f°)")
|
||
.arg(point.x).arg(point.y).arg(point.z)
|
||
.arg(rx).arg(ry).arg(rz));
|
||
|
||
LOG_INFO("[CloudView] Show pose1 at (%.3f, %.3f, %.3f) with rotation (%.1f, %.1f, %.1f)\n",
|
||
point.x, point.y, point.z, rx, ry, rz);
|
||
}
|
||
|
||
void CloudViewMainWindow::onShowPose2()
|
||
{
|
||
auto selectedPoints = m_glWidget->getSelectedPoints();
|
||
if (selectedPoints.size() < 2 || !selectedPoints[1].valid) {
|
||
QMessageBox::warning(this, "提示", "请先选择点2(启用测距后,Ctrl+左键点击第二个点)");
|
||
return;
|
||
}
|
||
|
||
const auto& point = selectedPoints[1];
|
||
|
||
// 读取姿态参数
|
||
bool ok = true;
|
||
float rx = m_editRx2->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点2 RX 值无效");
|
||
return;
|
||
}
|
||
|
||
float ry = m_editRy2->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点2 RY 值无效");
|
||
return;
|
||
}
|
||
|
||
float rz = m_editRz2->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点2 RZ 值无效");
|
||
return;
|
||
}
|
||
|
||
// 固定大小为10
|
||
float scale = 10.0f;
|
||
|
||
// 清除之前的姿态点
|
||
m_glWidget->clearPosePoints();
|
||
|
||
// 创建点2的姿态点
|
||
PosePoint pose2(point.x, point.y, point.z, rx, ry, rz, scale);
|
||
QVector<PosePoint> poses;
|
||
|
||
// 如果点1也存在,添加点1的姿态
|
||
if (selectedPoints[0].valid) {
|
||
const auto& point1 = selectedPoints[0];
|
||
float rx1 = m_editRx1->text().toFloat(&ok);
|
||
float ry1 = m_editRy1->text().toFloat(&ok);
|
||
float rz1 = m_editRz1->text().toFloat(&ok);
|
||
if (ok) {
|
||
PosePoint pose1(point1.x, point1.y, point1.z, rx1, ry1, rz1, scale);
|
||
poses.append(pose1);
|
||
}
|
||
}
|
||
|
||
poses.append(pose2);
|
||
|
||
// 添加到显示
|
||
m_glWidget->addPosePoints(poses);
|
||
|
||
statusBar()->showMessage(QString("已显示点2姿态 (%.3f, %.3f, %.3f) 旋转(%.1f°, %.1f°, %.1f°)")
|
||
.arg(point.x).arg(point.y).arg(point.z)
|
||
.arg(rx).arg(ry).arg(rz));
|
||
|
||
LOG_INFO("[CloudView] Show pose2 at (%.3f, %.3f, %.3f) with rotation (%.1f, %.1f, %.1f)\n",
|
||
point.x, point.y, point.z, rx, ry, rz);
|
||
}
|
||
|
||
void CloudViewMainWindow::onShowInputLine()
|
||
{
|
||
// 读取点1坐标
|
||
bool ok = true;
|
||
float x1 = m_editLineX1->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点1 X 值无效");
|
||
return;
|
||
}
|
||
|
||
float y1 = m_editLineY1->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点1 Y 值无效");
|
||
return;
|
||
}
|
||
|
||
float z1 = m_editLineZ1->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点1 Z 值无效");
|
||
return;
|
||
}
|
||
|
||
// 读取点2坐标
|
||
float x2 = m_editLineX2->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点2 X 值无效");
|
||
return;
|
||
}
|
||
|
||
float y2 = m_editLineY2->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点2 Y 值无效");
|
||
return;
|
||
}
|
||
|
||
float z2 = m_editLineZ2->text().toFloat(&ok);
|
||
if (!ok) {
|
||
QMessageBox::warning(this, "错误", "点2 Z 值无效");
|
||
return;
|
||
}
|
||
|
||
// 清除之前的线段
|
||
m_glWidget->clearLineSegments();
|
||
|
||
// 创建线段(红色)
|
||
LineSegment segment(x1, y1, z1, x2, y2, z2, 1.0f, 0.0f, 0.0f);
|
||
QVector<LineSegment> segments;
|
||
segments.append(segment);
|
||
|
||
// 添加到显示
|
||
m_glWidget->addLineSegments(segments);
|
||
|
||
// 计算距离
|
||
float dx = x2 - x1;
|
||
float dy = y2 - y1;
|
||
float dz = z2 - z1;
|
||
float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||
|
||
statusBar()->showMessage(QString("已显示线段 (%1,%2,%3) → (%4,%5,%6) 长度: %7")
|
||
.arg(x1).arg(y1).arg(z1)
|
||
.arg(x2).arg(y2).arg(z2)
|
||
.arg(distance));
|
||
|
||
LOG_INFO("[CloudView] Show input line from (%.3f, %.3f, %.3f) to (%.3f, %.3f, %.3f) length=%.3f\n",
|
||
x1, y1, z1, x2, y2, z2, distance);
|
||
}
|
||
|
||
void CloudViewMainWindow::onClearInputLine()
|
||
{
|
||
m_glWidget->clearLineSegments();
|
||
statusBar()->showMessage("已清除输入的线段");
|
||
}
|
||
|