feat: v0.1.0更新
This commit is contained in:
106
src/config/ConfigManager.cpp
Normal file
106
src/config/ConfigManager.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "config/ConfigManager.h"
|
||||
|
||||
ConfigManager::ConfigManager()
|
||||
: m_settings(std::make_unique<QSettings>("D330Viewer", "D330Viewer"))
|
||||
{
|
||||
// 构造函数:初始化QSettings
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager()
|
||||
{
|
||||
// 析构函数:QSettings会自动保存
|
||||
}
|
||||
|
||||
// ========== 网络配置 ==========
|
||||
QString ConfigManager::getIpAddress() const
|
||||
{
|
||||
return m_settings->value("Network/IpAddress", "192.168.0.90").toString();
|
||||
}
|
||||
|
||||
void ConfigManager::setIpAddress(const QString &ip)
|
||||
{
|
||||
m_settings->setValue("Network/IpAddress", ip);
|
||||
}
|
||||
|
||||
int ConfigManager::getControlPort() const
|
||||
{
|
||||
return m_settings->value("Network/ControlPort", 6790).toInt();
|
||||
}
|
||||
|
||||
void ConfigManager::setControlPort(int port)
|
||||
{
|
||||
m_settings->setValue("Network/ControlPort", port);
|
||||
}
|
||||
|
||||
int ConfigManager::getDataPort() const
|
||||
{
|
||||
return m_settings->value("Network/DataPort", 3957).toInt();
|
||||
}
|
||||
|
||||
void ConfigManager::setDataPort(int port)
|
||||
{
|
||||
m_settings->setValue("Network/DataPort", port);
|
||||
}
|
||||
|
||||
// ========== 相机配置 ==========
|
||||
int ConfigManager::getExposureTime() const
|
||||
{
|
||||
return m_settings->value("Camera/ExposureTime", 10000).toInt();
|
||||
}
|
||||
|
||||
void ConfigManager::setExposureTime(int exposure)
|
||||
{
|
||||
m_settings->setValue("Camera/ExposureTime", exposure);
|
||||
}
|
||||
|
||||
// ========== 拍照配置 ==========
|
||||
QString ConfigManager::getSavePath() const
|
||||
{
|
||||
return m_settings->value("Capture/SavePath", "").toString();
|
||||
}
|
||||
|
||||
void ConfigManager::setSavePath(const QString &path)
|
||||
{
|
||||
m_settings->setValue("Capture/SavePath", path);
|
||||
}
|
||||
|
||||
QString ConfigManager::getDepthFormat() const
|
||||
{
|
||||
return m_settings->value("Capture/DepthFormat", "both").toString();
|
||||
}
|
||||
|
||||
void ConfigManager::setDepthFormat(const QString &format)
|
||||
{
|
||||
m_settings->setValue("Capture/DepthFormat", format);
|
||||
}
|
||||
|
||||
QString ConfigManager::getPointCloudFormat() const
|
||||
{
|
||||
return m_settings->value("Capture/PointCloudFormat", "both").toString();
|
||||
}
|
||||
|
||||
void ConfigManager::setPointCloudFormat(const QString &format)
|
||||
{
|
||||
m_settings->setValue("Capture/PointCloudFormat", format);
|
||||
}
|
||||
|
||||
// ========== 窗口配置 ==========
|
||||
QByteArray ConfigManager::getWindowGeometry() const
|
||||
{
|
||||
return m_settings->value("Window/Geometry").toByteArray();
|
||||
}
|
||||
|
||||
void ConfigManager::setWindowGeometry(const QByteArray &geometry)
|
||||
{
|
||||
m_settings->setValue("Window/Geometry", geometry);
|
||||
}
|
||||
|
||||
QByteArray ConfigManager::getWindowState() const
|
||||
{
|
||||
return m_settings->value("Window/State").toByteArray();
|
||||
}
|
||||
|
||||
void ConfigManager::setWindowState(const QByteArray &state)
|
||||
{
|
||||
m_settings->setValue("Window/State", state);
|
||||
}
|
||||
49
src/config/ConfigManager.h
Normal file
49
src/config/ConfigManager.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef CONFIGMANAGER_H
|
||||
#define CONFIGMANAGER_H
|
||||
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
class ConfigManager
|
||||
{
|
||||
public:
|
||||
ConfigManager();
|
||||
~ConfigManager();
|
||||
|
||||
// 网络配置
|
||||
QString getIpAddress() const;
|
||||
void setIpAddress(const QString &ip);
|
||||
|
||||
int getControlPort() const;
|
||||
void setControlPort(int port);
|
||||
|
||||
int getDataPort() const;
|
||||
void setDataPort(int port);
|
||||
|
||||
// 相机配置
|
||||
int getExposureTime() const;
|
||||
void setExposureTime(int exposure);
|
||||
|
||||
// 拍照配置
|
||||
QString getSavePath() const;
|
||||
void setSavePath(const QString &path);
|
||||
|
||||
QString getDepthFormat() const;
|
||||
void setDepthFormat(const QString &format);
|
||||
|
||||
QString getPointCloudFormat() const;
|
||||
void setPointCloudFormat(const QString &format);
|
||||
|
||||
// 窗口配置
|
||||
QByteArray getWindowGeometry() const;
|
||||
void setWindowGeometry(const QByteArray &geometry);
|
||||
|
||||
QByteArray getWindowState() const;
|
||||
void setWindowState(const QByteArray &state);
|
||||
|
||||
private:
|
||||
std::unique_ptr<QSettings> m_settings;
|
||||
};
|
||||
|
||||
#endif // CONFIGMANAGER_H
|
||||
183
src/core/DeviceScanner.cpp
Normal file
183
src/core/DeviceScanner.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "DeviceScanner.h"
|
||||
#include <QNetworkDatagram>
|
||||
#include <QNetworkInterface>
|
||||
#include <QDebug>
|
||||
|
||||
DeviceScanner::DeviceScanner(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_socket(new QUdpSocket(this))
|
||||
, m_timeoutTimer(new QTimer(this))
|
||||
, m_scanTimer(new QTimer(this))
|
||||
, m_currentHost(HOST_START)
|
||||
, m_totalHosts(HOST_END - HOST_START + 1)
|
||||
, m_isScanning(false)
|
||||
{
|
||||
connect(m_socket, &QUdpSocket::readyRead, this, &DeviceScanner::onReadyRead);
|
||||
connect(m_timeoutTimer, &QTimer::timeout, this, &DeviceScanner::onTimeout);
|
||||
connect(m_scanTimer, &QTimer::timeout, this, &DeviceScanner::scanNextHost);
|
||||
|
||||
m_timeoutTimer->setSingleShot(true);
|
||||
m_scanTimer->setSingleShot(false);
|
||||
m_scanTimer->setInterval(SCAN_TIMEOUT);
|
||||
}
|
||||
|
||||
DeviceScanner::~DeviceScanner()
|
||||
{
|
||||
stopScan();
|
||||
}
|
||||
|
||||
void DeviceScanner::startScan(const QString &subnet)
|
||||
{
|
||||
if (m_isScanning) {
|
||||
qDebug() << "Scan already in progress";
|
||||
return;
|
||||
}
|
||||
|
||||
m_subnet = subnet.isEmpty() ? getLocalSubnet() : subnet;
|
||||
m_currentHost = HOST_START;
|
||||
m_foundDevices.clear();
|
||||
m_isScanning = true;
|
||||
|
||||
if (!m_socket->bind(QHostAddress::Any, 0)) {
|
||||
emit scanError("Failed to bind socket");
|
||||
m_isScanning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Starting fast device scan on subnet:" << m_subnet;
|
||||
|
||||
// Send DISCOVER to all hosts at once (batch mode)
|
||||
for (int host = HOST_START; host <= HOST_END; host++) {
|
||||
QString ip = m_subnet + "." + QString::number(host);
|
||||
sendDiscoveryPacket(ip);
|
||||
}
|
||||
|
||||
// Wait 3 seconds for all responses
|
||||
m_timeoutTimer->start(3000);
|
||||
qDebug() << "Sent discovery packets to all hosts, waiting for responses...";
|
||||
}
|
||||
|
||||
void DeviceScanner::stopScan()
|
||||
{
|
||||
if (!m_isScanning) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scanTimer->stop();
|
||||
m_timeoutTimer->stop();
|
||||
m_socket->close();
|
||||
m_isScanning = false;
|
||||
|
||||
qDebug() << "Scan stopped. Found" << m_foundDevices.size() << "devices";
|
||||
emit scanFinished(m_foundDevices.size());
|
||||
}
|
||||
|
||||
void DeviceScanner::onReadyRead()
|
||||
{
|
||||
while (m_socket->hasPendingDatagrams()) {
|
||||
QNetworkDatagram datagram = m_socket->receiveDatagram();
|
||||
QHostAddress senderAddr = datagram.senderAddress();
|
||||
|
||||
// Convert IPv6-mapped IPv4 address to IPv4
|
||||
if (senderAddr.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
QHostAddress ipv4Addr(senderAddr.toIPv4Address());
|
||||
if (!ipv4Addr.isNull()) {
|
||||
senderAddr = ipv4Addr;
|
||||
}
|
||||
}
|
||||
|
||||
QString senderIp = senderAddr.toString();
|
||||
QString response = QString::fromUtf8(datagram.data());
|
||||
|
||||
qDebug() << "Received response from" << senderIp << ":" << response;
|
||||
|
||||
if (response.contains("D330M_CAMERA")) {
|
||||
DeviceInfo device;
|
||||
device.ipAddress = senderIp;
|
||||
device.deviceName = "D330M Camera";
|
||||
device.port = SCAN_PORT;
|
||||
device.responseTime = 0;
|
||||
|
||||
m_foundDevices.append(device);
|
||||
emit deviceFound(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceScanner::onTimeout()
|
||||
{
|
||||
qDebug() << "Scan timeout reached";
|
||||
stopScan();
|
||||
}
|
||||
|
||||
void DeviceScanner::scanNextHost()
|
||||
{
|
||||
// This function is no longer used in batch mode
|
||||
// Kept for compatibility
|
||||
}
|
||||
|
||||
void DeviceScanner::sendDiscoveryPacket(const QString &ip)
|
||||
{
|
||||
QByteArray data = "DISCOVER";
|
||||
m_socket->writeDatagram(data, QHostAddress(ip), SCAN_PORT);
|
||||
}
|
||||
|
||||
QString DeviceScanner::getLocalSubnet()
|
||||
{
|
||||
// Get all network interfaces
|
||||
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
|
||||
// Priority 1: Look for Ethernet adapter (以太网)
|
||||
for (const QNetworkInterface &iface : interfaces) {
|
||||
QString name = iface.humanReadableName().toLower();
|
||||
|
||||
// Skip virtual adapters
|
||||
if (name.contains("virtual") || name.contains("vmware") ||
|
||||
name.contains("virtualbox") || name.contains("hyper-v") ||
|
||||
name.contains("vpn") || name.contains("tap") || name.contains("tun")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for Ethernet adapter
|
||||
if (name.contains("ethernet") || name.contains("以太网")) {
|
||||
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress addr = entry.ip();
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
|
||||
QString ip = addr.toString();
|
||||
QStringList parts = ip.split('.');
|
||||
if (parts.size() == 4) {
|
||||
qDebug() << "Found Ethernet adapter:" << iface.humanReadableName() << "IP:" << ip;
|
||||
return parts[0] + "." + parts[1] + "." + parts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Any non-virtual adapter
|
||||
for (const QNetworkInterface &iface : interfaces) {
|
||||
QString name = iface.humanReadableName().toLower();
|
||||
|
||||
if (name.contains("virtual") || name.contains("vmware") ||
|
||||
name.contains("virtualbox") || name.contains("hyper-v") ||
|
||||
name.contains("vpn") || name.contains("tap") || name.contains("tun")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress addr = entry.ip();
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
|
||||
QString ip = addr.toString();
|
||||
QStringList parts = ip.split('.');
|
||||
if (parts.size() == 4) {
|
||||
qDebug() << "Found adapter:" << iface.humanReadableName() << "IP:" << ip;
|
||||
return parts[0] + "." + parts[1] + "." + parts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "192.168.0";
|
||||
}
|
||||
62
src/core/DeviceScanner.h
Normal file
62
src/core/DeviceScanner.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef DEVICESCANNER_H
|
||||
#define DEVICESCANNER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
#include <QTimer>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
struct DeviceInfo {
|
||||
QString ipAddress;
|
||||
QString deviceName;
|
||||
int port;
|
||||
qint64 responseTime;
|
||||
};
|
||||
|
||||
class DeviceScanner : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeviceScanner(QObject *parent = nullptr);
|
||||
~DeviceScanner();
|
||||
|
||||
void startScan(const QString &subnet = "");
|
||||
void stopScan();
|
||||
bool isScanning() const { return m_isScanning; }
|
||||
|
||||
signals:
|
||||
void deviceFound(const DeviceInfo &device);
|
||||
void scanProgress(int current, int total);
|
||||
void scanFinished(int devicesFound);
|
||||
void scanError(const QString &error);
|
||||
|
||||
private slots:
|
||||
void onReadyRead();
|
||||
void onTimeout();
|
||||
void scanNextHost();
|
||||
|
||||
private:
|
||||
void sendDiscoveryPacket(const QString &ip);
|
||||
QString getLocalSubnet();
|
||||
|
||||
QUdpSocket *m_socket;
|
||||
QTimer *m_timeoutTimer;
|
||||
QTimer *m_scanTimer;
|
||||
|
||||
QString m_subnet;
|
||||
int m_currentHost;
|
||||
int m_totalHosts;
|
||||
bool m_isScanning;
|
||||
|
||||
QList<DeviceInfo> m_foundDevices;
|
||||
|
||||
static constexpr int SCAN_PORT = 6790; // Control port for device discovery
|
||||
static constexpr int SCAN_TIMEOUT = 10;
|
||||
static constexpr int HOST_START = 1;
|
||||
static constexpr int HOST_END = 254;
|
||||
};
|
||||
|
||||
#endif // DEVICESCANNER_H
|
||||
59
src/core/DeviceScanner_getLocalSubnet.cpp
Normal file
59
src/core/DeviceScanner_getLocalSubnet.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
QString DeviceScanner::getLocalSubnet()
|
||||
{
|
||||
// Get all network interfaces
|
||||
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
|
||||
// Priority 1: Look for Ethernet adapter (以太网)
|
||||
for (const QNetworkInterface &iface : interfaces) {
|
||||
QString name = iface.humanReadableName().toLower();
|
||||
|
||||
// Skip virtual adapters
|
||||
if (name.contains("virtual") || name.contains("vmware") ||
|
||||
name.contains("virtualbox") || name.contains("hyper-v") ||
|
||||
name.contains("vpn") || name.contains("tap") || name.contains("tun")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for Ethernet adapter
|
||||
if (name.contains("ethernet") || name.contains("以太网")) {
|
||||
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress addr = entry.ip();
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
|
||||
QString ip = addr.toString();
|
||||
QStringList parts = ip.split('.');
|
||||
if (parts.size() == 4) {
|
||||
qDebug() << "Found Ethernet adapter:" << iface.humanReadableName() << "IP:" << ip;
|
||||
return parts[0] + "." + parts[1] + "." + parts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Any non-virtual adapter
|
||||
for (const QNetworkInterface &iface : interfaces) {
|
||||
QString name = iface.humanReadableName().toLower();
|
||||
|
||||
if (name.contains("virtual") || name.contains("vmware") ||
|
||||
name.contains("virtualbox") || name.contains("hyper-v") ||
|
||||
name.contains("vpn") || name.contains("tap") || name.contains("tun")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress addr = entry.ip();
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
|
||||
QString ip = addr.toString();
|
||||
QStringList parts = ip.split('.');
|
||||
if (parts.size() == 4) {
|
||||
qDebug() << "Found adapter:" << iface.humanReadableName() << "IP:" << ip;
|
||||
return parts[0] + "." + parts[1] + "." + parts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "192.168.0";
|
||||
}
|
||||
229
src/core/GVSPParser.cpp
Normal file
229
src/core/GVSPParser.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "core/GVSPParser.h"
|
||||
#include <QDebug>
|
||||
#include <cstring>
|
||||
#include <winsock2.h>
|
||||
|
||||
GVSPParser::GVSPParser(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_isReceiving(false)
|
||||
, m_dataType(0)
|
||||
, m_currentBlockId(0)
|
||||
, m_expectedSize(0)
|
||||
, m_receivedSize(0)
|
||||
, m_imageWidth(0)
|
||||
, m_imageHeight(0)
|
||||
, m_pixelFormat(0)
|
||||
, m_lastBlockId(0)
|
||||
, m_packetCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
GVSPParser::~GVSPParser()
|
||||
{
|
||||
}
|
||||
|
||||
void GVSPParser::reset()
|
||||
{
|
||||
m_isReceiving = false;
|
||||
m_dataType = 0;
|
||||
m_currentBlockId = 0;
|
||||
m_dataBuffer.clear();
|
||||
m_expectedSize = 0;
|
||||
m_receivedSize = 0;
|
||||
m_packetCount = 0;
|
||||
}
|
||||
|
||||
void GVSPParser::parsePacket(const QByteArray &packet)
|
||||
{
|
||||
if (packet.size() < sizeof(GVSPPacketHeader)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t*>(packet.constData());
|
||||
const GVSPPacketHeader *header = reinterpret_cast<const GVSPPacketHeader*>(data);
|
||||
|
||||
// 注释掉调试日志以提高性能
|
||||
// static int debugCount = 0;
|
||||
// if (debugCount < 3) {
|
||||
// qDebug() << "Packet" << debugCount << "first 16 bytes (hex):";
|
||||
// QString hexStr;
|
||||
// for (int i = 0; i < qMin(16, packet.size()); i++) {
|
||||
// hexStr += QString("%1 ").arg(data[i], 2, 16, QChar('0'));
|
||||
// }
|
||||
// qDebug() << hexStr;
|
||||
// debugCount++;
|
||||
// }
|
||||
|
||||
// GVSP包头格式:status(2) + block_id(2) + packet_format(4)
|
||||
// 包类型在packet_format的高字节
|
||||
uint32_t packet_fmt = ntohl(header->packet_fmt_id);
|
||||
uint8_t packetType = (packet_fmt >> 24) & 0xFF;
|
||||
uint16_t blockId = ntohs(header->block_id);
|
||||
|
||||
// 注释掉频繁的日志输出以提高性能
|
||||
// static int leaderCount = 0;
|
||||
// static int trailerCount = 0;
|
||||
|
||||
switch (packetType) {
|
||||
case GVSP_LEADER_PACKET:
|
||||
handleLeaderPacket(data, packet.size());
|
||||
break;
|
||||
case GVSP_PAYLOAD_PACKET:
|
||||
handlePayloadPacket(data, packet.size());
|
||||
break;
|
||||
case GVSP_TRAILER_PACKET:
|
||||
handleTrailerPacket(data, packet.size());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GVSPParser::handleLeaderPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < sizeof(GVSPPacketHeader) + sizeof(GVSPImageDataLeader)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const GVSPPacketHeader *header = reinterpret_cast<const GVSPPacketHeader*>(data);
|
||||
m_currentBlockId = ntohs(header->block_id);
|
||||
|
||||
// Check payload type
|
||||
const uint16_t *payload_type_ptr = reinterpret_cast<const uint16_t*>(data + sizeof(GVSPPacketHeader) + 2);
|
||||
uint16_t payload_type = ntohs(*payload_type_ptr);
|
||||
|
||||
if (payload_type == PAYLOAD_TYPE_IMAGE) {
|
||||
// Image data leader
|
||||
const GVSPImageDataLeader *leader = reinterpret_cast<const GVSPImageDataLeader*>(data + sizeof(GVSPPacketHeader));
|
||||
|
||||
m_dataType = 1;
|
||||
m_imageWidth = ntohl(leader->size_x);
|
||||
m_imageHeight = ntohl(leader->size_y);
|
||||
m_pixelFormat = ntohl(leader->pixel_format);
|
||||
m_expectedSize = m_imageWidth * m_imageHeight * 2; // 12-bit packed in 16-bit
|
||||
|
||||
m_dataBuffer.clear();
|
||||
m_dataBuffer.reserve(m_expectedSize);
|
||||
m_receivedSize = 0;
|
||||
m_isReceiving = true;
|
||||
m_packetCount = 0;
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "Image Leader: Block" << m_currentBlockId
|
||||
// << "Size:" << m_imageWidth << "x" << m_imageHeight;
|
||||
}
|
||||
else if (payload_type == PAYLOAD_TYPE_BINARY) {
|
||||
// Depth data leader
|
||||
const GVSPBinaryDataLeader *leader = reinterpret_cast<const GVSPBinaryDataLeader*>(data + sizeof(GVSPPacketHeader));
|
||||
|
||||
m_dataType = 3;
|
||||
m_expectedSize = ntohl(leader->file_size);
|
||||
|
||||
m_dataBuffer.clear();
|
||||
m_dataBuffer.reserve(m_expectedSize);
|
||||
m_receivedSize = 0;
|
||||
m_isReceiving = true;
|
||||
m_packetCount = 0;
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "Depth Leader: Block" << m_currentBlockId
|
||||
// << "Size:" << m_expectedSize << "bytes";
|
||||
}
|
||||
}
|
||||
|
||||
void GVSPParser::handlePayloadPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (!m_isReceiving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (size <= sizeof(GVSPPacketHeader)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract payload data (skip header)
|
||||
const uint8_t *payload = data + sizeof(GVSPPacketHeader);
|
||||
size_t payload_size = size - sizeof(GVSPPacketHeader);
|
||||
|
||||
// Append to buffer
|
||||
m_dataBuffer.append(reinterpret_cast<const char*>(payload), payload_size);
|
||||
m_receivedSize += payload_size;
|
||||
m_packetCount++;
|
||||
}
|
||||
|
||||
void GVSPParser::handleTrailerPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (!m_isReceiving) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "Trailer received: Block" << m_currentBlockId
|
||||
// << "Received" << m_receivedSize << "/" << m_expectedSize << "bytes"
|
||||
// << "Packets:" << m_packetCount;
|
||||
|
||||
// Process complete data
|
||||
if (m_dataType == 1) {
|
||||
processImageData();
|
||||
} else if (m_dataType == 3) {
|
||||
processDepthData();
|
||||
}
|
||||
|
||||
// Reset state
|
||||
m_isReceiving = false;
|
||||
m_lastBlockId = m_currentBlockId;
|
||||
}
|
||||
|
||||
void GVSPParser::processImageData()
|
||||
{
|
||||
if (m_dataBuffer.size() < m_expectedSize) {
|
||||
// 注释掉频繁的警告日志
|
||||
// qDebug() << "Warning: Incomplete image data" << m_dataBuffer.size() << "/" << m_expectedSize;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert 16-bit depth data to 8-bit grayscale for display
|
||||
const uint16_t *src = reinterpret_cast<const uint16_t*>(m_dataBuffer.constData());
|
||||
QImage image(m_imageWidth, m_imageHeight, QImage::Format_Grayscale8);
|
||||
|
||||
// Find min/max for normalization
|
||||
uint16_t minVal = 65535, maxVal = 0;
|
||||
for (size_t i = 0; i < m_imageWidth * m_imageHeight; i++) {
|
||||
uint16_t val = src[i];
|
||||
if (val > 0) {
|
||||
if (val < minVal) minVal = val;
|
||||
if (val > maxVal) maxVal = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize to 0-255 and flip vertically
|
||||
uint8_t *dst = image.bits();
|
||||
float scale = (maxVal > minVal) ? (255.0f / (maxVal - minVal)) : 0.0f;
|
||||
|
||||
for (size_t y = 0; y < m_imageHeight; y++) {
|
||||
for (size_t x = 0; x < m_imageWidth; x++) {
|
||||
size_t src_idx = y * m_imageWidth + x;
|
||||
size_t dst_idx = (m_imageHeight - 1 - y) * m_imageWidth + x; // 垂直翻转
|
||||
|
||||
uint16_t val = src[src_idx];
|
||||
if (val == 0) {
|
||||
dst[dst_idx] = 0;
|
||||
} else {
|
||||
dst[dst_idx] = static_cast<uint8_t>((val - minVal) * scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit imageReceived(image, m_currentBlockId);
|
||||
}
|
||||
|
||||
void GVSPParser::processDepthData()
|
||||
{
|
||||
if (m_dataBuffer.size() < m_expectedSize) {
|
||||
// 注释掉频繁的警告日志
|
||||
// qDebug() << "Warning: Incomplete depth data" << m_dataBuffer.size() << "/" << m_expectedSize;
|
||||
return;
|
||||
}
|
||||
|
||||
emit depthDataReceived(m_dataBuffer, m_currentBlockId);
|
||||
}
|
||||
130
src/core/Logger.cpp
Normal file
130
src/core/Logger.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "core/Logger.h"
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
|
||||
Logger* Logger::s_instance = nullptr;
|
||||
|
||||
Logger* Logger::instance()
|
||||
{
|
||||
if (!s_instance) {
|
||||
s_instance = new Logger();
|
||||
}
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
Logger::Logger(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_maxLines(10000) // 保留最新10000行
|
||||
, m_currentLines(0)
|
||||
{
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
}
|
||||
|
||||
void Logger::setLogFile(const QString &filePath)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_logFilePath = filePath;
|
||||
|
||||
// Count existing lines
|
||||
QFile file(m_logFilePath);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
m_currentLines = 0;
|
||||
while (!file.atEnd()) {
|
||||
file.readLine();
|
||||
m_currentLines++;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::setMaxLines(int maxLines)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_maxLines = maxLines;
|
||||
}
|
||||
|
||||
void Logger::log(const QString &message)
|
||||
{
|
||||
writeLog("LOG", message);
|
||||
}
|
||||
|
||||
void Logger::debug(const QString &message)
|
||||
{
|
||||
writeLog("DEBUG", message);
|
||||
}
|
||||
|
||||
void Logger::info(const QString &message)
|
||||
{
|
||||
writeLog("INFO", message);
|
||||
}
|
||||
|
||||
void Logger::warning(const QString &message)
|
||||
{
|
||||
writeLog("WARN", message);
|
||||
}
|
||||
|
||||
void Logger::error(const QString &message)
|
||||
{
|
||||
writeLog("ERROR", message);
|
||||
}
|
||||
|
||||
void Logger::writeLog(const QString &level, const QString &message)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
if (m_logFilePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString timestamp = QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss.zzz]");
|
||||
QString logLine = QString("%1 [%2] %3\n").arg(timestamp).arg(level).arg(message);
|
||||
|
||||
QFile file(m_logFilePath);
|
||||
if (file.open(QIODevice::Append | QIODevice::Text)) {
|
||||
QTextStream out(&file);
|
||||
out << logLine;
|
||||
file.close();
|
||||
|
||||
m_currentLines++;
|
||||
|
||||
// Check if we need to trim the log
|
||||
if (m_currentLines > m_maxLines) {
|
||||
checkAndTrimLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::checkAndTrimLog()
|
||||
{
|
||||
// Read all lines
|
||||
QFile file(m_logFilePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList lines;
|
||||
QTextStream in(&file);
|
||||
while (!in.atEnd()) {
|
||||
lines.append(in.readLine());
|
||||
}
|
||||
file.close();
|
||||
|
||||
// Keep only the last m_maxLines
|
||||
if (lines.size() > m_maxLines) {
|
||||
lines = lines.mid(lines.size() - m_maxLines);
|
||||
}
|
||||
|
||||
// Write back
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
QTextStream out(&file);
|
||||
for (const QString &line : lines) {
|
||||
out << line << "\n";
|
||||
}
|
||||
file.close();
|
||||
m_currentLines = lines.size();
|
||||
}
|
||||
}
|
||||
174
src/core/NetworkManager.cpp
Normal file
174
src/core/NetworkManager.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "core/NetworkManager.h"
|
||||
#include "core/GVSPParser.h"
|
||||
#include <QDebug>
|
||||
#include <QVariant>
|
||||
|
||||
NetworkManager::NetworkManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_controlSocket(new QUdpSocket(this))
|
||||
, m_dataSocket(new QUdpSocket(this))
|
||||
, m_gvspParser(new GVSPParser(this))
|
||||
, m_controlPort(6790)
|
||||
, m_dataPort(3957)
|
||||
, m_isConnected(false)
|
||||
{
|
||||
// 连接数据接收信号
|
||||
connect(m_dataSocket, &QUdpSocket::readyRead, this, &NetworkManager::onReadyRead);
|
||||
connect(m_dataSocket, &QUdpSocket::errorOccurred, this, &NetworkManager::onError);
|
||||
|
||||
// 连接GVSP解析器信号
|
||||
connect(m_gvspParser, &GVSPParser::imageReceived, this, &NetworkManager::imageReceived);
|
||||
connect(m_gvspParser, &GVSPParser::depthDataReceived, this, &NetworkManager::depthDataReceived);
|
||||
}
|
||||
|
||||
NetworkManager::~NetworkManager()
|
||||
{
|
||||
disconnectFromCamera();
|
||||
}
|
||||
|
||||
// ========== 连接和断开 ==========
|
||||
bool NetworkManager::connectToCamera(const QString &ip, int controlPort, int dataPort)
|
||||
{
|
||||
m_cameraIp = ip;
|
||||
m_controlPort = controlPort;
|
||||
m_dataPort = dataPort;
|
||||
|
||||
// 绑定控制Socket到任意端口(让系统自动分配)
|
||||
if (!m_controlSocket->bind(QHostAddress::Any, 0)) {
|
||||
QString error = QString("Failed to bind control socket: %1")
|
||||
.arg(m_controlSocket->errorString());
|
||||
qDebug() << error;
|
||||
emit errorOccurred(error);
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Successfully bound control socket to port" << m_controlSocket->localPort();
|
||||
|
||||
// 绑定数据接收端口
|
||||
if (!m_dataSocket->bind(QHostAddress::Any, m_dataPort)) {
|
||||
QString error = QString("Failed to bind data port %1: %2")
|
||||
.arg(m_dataPort)
|
||||
.arg(m_dataSocket->errorString());
|
||||
qDebug() << error;
|
||||
emit errorOccurred(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置UDP接收缓冲区大小为64MB(减少丢包)
|
||||
m_dataSocket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, QVariant(64 * 1024 * 1024));
|
||||
|
||||
qDebug() << "Successfully bound data port" << m_dataPort;
|
||||
qDebug() << "Data socket state:" << m_dataSocket->state();
|
||||
qDebug() << "UDP receive buffer size:" << m_dataSocket->socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption).toInt();
|
||||
|
||||
m_isConnected = true;
|
||||
qDebug() << "Connected to camera:" << m_cameraIp << "Control port:" << m_controlPort << "Data port:" << m_dataPort;
|
||||
|
||||
// Send STOP command to register client IP on camera
|
||||
sendStopCommand();
|
||||
qDebug() << "Sent STOP command to register client IP";
|
||||
|
||||
emit connected();
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkManager::disconnectFromCamera()
|
||||
{
|
||||
if (m_isConnected) {
|
||||
m_controlSocket->close();
|
||||
m_dataSocket->close();
|
||||
m_isConnected = false;
|
||||
qDebug() << "Disconnected from camera";
|
||||
emit disconnected();
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkManager::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
// ========== 发送控制命令 ==========
|
||||
bool NetworkManager::sendCommand(const QString &command)
|
||||
{
|
||||
if (!m_isConnected) {
|
||||
qDebug() << "Not connected to camera";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray data = command.toUtf8();
|
||||
qDebug() << "Sending command to" << m_cameraIp << ":" << m_controlPort << "data:" << command;
|
||||
qDebug() << "Control socket state:" << m_controlSocket->state();
|
||||
qDebug() << "Control socket error:" << m_controlSocket->errorString();
|
||||
qDebug() << "Data to send (hex):" << data.toHex();
|
||||
qDebug() << "Data size:" << data.size();
|
||||
|
||||
qint64 sent = m_controlSocket->writeDatagram(data, QHostAddress(m_cameraIp), m_controlPort);
|
||||
|
||||
qDebug() << "writeDatagram returned:" << sent;
|
||||
if (sent == -1) {
|
||||
QString error = QString("Failed to send command: %1").arg(m_controlSocket->errorString());
|
||||
qDebug() << error;
|
||||
qDebug() << "Socket error code:" << m_controlSocket->error();
|
||||
emit errorOccurred(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Command sent successfully, bytes:" << sent;
|
||||
qDebug() << "Sent command:" << command;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkManager::sendStartCommand()
|
||||
{
|
||||
return sendCommand("START");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendStopCommand()
|
||||
{
|
||||
return sendCommand("STOP");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendOnceCommand()
|
||||
{
|
||||
return sendCommand("ONCE");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendExposureCommand(int exposureTime)
|
||||
{
|
||||
QString command = QString("EXPOSURE:%1").arg(exposureTime);
|
||||
return sendCommand(command);
|
||||
}
|
||||
|
||||
// ========== 槽函数 ==========
|
||||
void NetworkManager::onReadyRead()
|
||||
{
|
||||
static int packetCount = 0;
|
||||
|
||||
while (m_dataSocket->hasPendingDatagrams()) {
|
||||
QByteArray datagram;
|
||||
datagram.resize(m_dataSocket->pendingDatagramSize());
|
||||
|
||||
QHostAddress sender;
|
||||
quint16 senderPort;
|
||||
m_dataSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
||||
|
||||
// 临时添加日志以诊断问题
|
||||
if (packetCount < 5) {
|
||||
qDebug() << "[NetworkManager] Received packet" << packetCount << "from" << sender.toString() << ":" << senderPort << "size:" << datagram.size();
|
||||
}
|
||||
packetCount++;
|
||||
|
||||
// 将数据包传递给GVSP解析器
|
||||
m_gvspParser->parsePacket(datagram);
|
||||
|
||||
// 仍然发出原始数据信号(用于调试)
|
||||
emit dataReceived(datagram);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::onError(QAbstractSocket::SocketError socketError)
|
||||
{
|
||||
QString error = QString("Socket error: %1").arg(m_dataSocket->errorString());
|
||||
qDebug() << error;
|
||||
emit errorOccurred(error);
|
||||
}
|
||||
55
src/core/NetworkManager.h
Normal file
55
src/core/NetworkManager.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef NETWORKMANAGER_H
|
||||
#define NETWORKMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <memory>
|
||||
|
||||
class GVSPParser;
|
||||
|
||||
class NetworkManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NetworkManager(QObject *parent = nullptr);
|
||||
~NetworkManager();
|
||||
|
||||
// 连接和断开
|
||||
bool connectToCamera(const QString &ip, int controlPort, int dataPort);
|
||||
void disconnectFromCamera();
|
||||
bool isConnected() const;
|
||||
|
||||
// 发送控制命令
|
||||
bool sendCommand(const QString &command);
|
||||
bool sendStartCommand();
|
||||
bool sendStopCommand();
|
||||
bool sendOnceCommand();
|
||||
bool sendExposureCommand(int exposureTime);
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void errorOccurred(const QString &error);
|
||||
void dataReceived(const QByteArray &data);
|
||||
void imageReceived(const QImage &image, uint32_t blockId);
|
||||
void depthDataReceived(const QByteArray &depthData, uint32_t blockId);
|
||||
|
||||
private slots:
|
||||
void onReadyRead();
|
||||
void onError(QAbstractSocket::SocketError socketError);
|
||||
|
||||
private:
|
||||
QUdpSocket *m_controlSocket;
|
||||
QUdpSocket *m_dataSocket;
|
||||
GVSPParser *m_gvspParser;
|
||||
|
||||
QString m_cameraIp;
|
||||
int m_controlPort;
|
||||
int m_dataPort;
|
||||
bool m_isConnected;
|
||||
};
|
||||
|
||||
#endif // NETWORKMANAGER_H
|
||||
277
src/core/PointCloudProcessor.cpp
Normal file
277
src/core/PointCloudProcessor.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
#include "core/PointCloudProcessor.h"
|
||||
#include <QDebug>
|
||||
#include <vector>
|
||||
|
||||
PointCloudProcessor::PointCloudProcessor(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_fx(1432.8957f)
|
||||
, m_fy(1432.6590f)
|
||||
, m_cx(637.5117f)
|
||||
, m_cy(521.8720f)
|
||||
, m_zScale(0.2f)
|
||||
, m_imageWidth(1224)
|
||||
, m_imageHeight(1024)
|
||||
, m_totalPoints(1224 * 1024)
|
||||
, m_platform(nullptr)
|
||||
, m_device(nullptr)
|
||||
, m_context(nullptr)
|
||||
, m_queue(nullptr)
|
||||
, m_program(nullptr)
|
||||
, m_kernel(nullptr)
|
||||
, m_depthBuffer(nullptr)
|
||||
, m_xyzBuffer(nullptr)
|
||||
, m_clInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
PointCloudProcessor::~PointCloudProcessor()
|
||||
{
|
||||
cleanupOpenCL();
|
||||
}
|
||||
|
||||
void PointCloudProcessor::setCameraIntrinsics(float fx, float fy, float cx, float cy)
|
||||
{
|
||||
m_fx = fx;
|
||||
m_fy = fy;
|
||||
m_cx = cx;
|
||||
m_cy = cy;
|
||||
}
|
||||
|
||||
void PointCloudProcessor::setZScaleFactor(float scale)
|
||||
{
|
||||
m_zScale = scale;
|
||||
}
|
||||
|
||||
bool PointCloudProcessor::initializeOpenCL()
|
||||
{
|
||||
if (m_clInitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cl_int err;
|
||||
qDebug() << "[OpenCL] Starting initialization...";
|
||||
|
||||
// 1. 获取平台
|
||||
err = clGetPlatformIDs(1, &m_platform, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to get OpenCL platform");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 获取设备(优先GPU)
|
||||
err = clGetDeviceIDs(m_platform, CL_DEVICE_TYPE_GPU, 1, &m_device, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
qDebug() << "[OpenCL] GPU not available, using CPU";
|
||||
err = clGetDeviceIDs(m_platform, CL_DEVICE_TYPE_CPU, 1, &m_device, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to get OpenCL device");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 创建上下文
|
||||
m_context = clCreateContext(nullptr, 1, &m_device, nullptr, nullptr, &err);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to create OpenCL context");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 创建命令队列
|
||||
#if CL_TARGET_OPENCL_VERSION >= 200
|
||||
cl_queue_properties props[] = { CL_QUEUE_ON_DEVICE_DEFAULT, 0 };
|
||||
m_queue = clCreateCommandQueueWithProperties(m_context, m_device, props, &err);
|
||||
#else
|
||||
m_queue = clCreateCommandQueue(m_context, m_device, 0, &err);
|
||||
#endif
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to create command queue");
|
||||
cleanupOpenCL();
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "[OpenCL] Platform, device, context, and queue created successfully";
|
||||
|
||||
// 5. 创建并编译OpenCL程序
|
||||
const char* kernelSource =
|
||||
"__kernel void compute_xyz(__global const float* depth, "
|
||||
"__global float* xyz, int width, int height, "
|
||||
"float inv_fx, float inv_fy, float cx, float cy, float z_scale) { "
|
||||
"int idx = get_global_id(0); "
|
||||
"if (idx >= width * height) return; "
|
||||
"int y = idx / width; "
|
||||
"int x = idx % width; "
|
||||
"float z = depth[idx] * z_scale; "
|
||||
// 完全平面的圆柱投影:X和Y直接使用像素坐标,缩放到合适的范围
|
||||
"xyz[idx*3] = (x - cx) * 2.0f; " // X坐标,缩放系数2.0
|
||||
"xyz[idx*3+1] = (y - cy) * 2.0f; " // Y坐标,缩放系数2.0
|
||||
"xyz[idx*3+2] = z; "
|
||||
"}";
|
||||
|
||||
m_program = clCreateProgramWithSource(m_context, 1, &kernelSource, nullptr, &err);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to create OpenCL program");
|
||||
cleanupOpenCL();
|
||||
return false;
|
||||
}
|
||||
|
||||
err = clBuildProgram(m_program, 1, &m_device, nullptr, nullptr, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
char buildLog[4096];
|
||||
clGetProgramBuildInfo(m_program, m_device, CL_PROGRAM_BUILD_LOG, sizeof(buildLog), buildLog, nullptr);
|
||||
emit errorOccurred(QString("Failed to build OpenCL program: %1").arg(buildLog));
|
||||
cleanupOpenCL();
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "[OpenCL] Program compiled successfully";
|
||||
|
||||
// 6. 创建kernel
|
||||
m_kernel = clCreateKernel(m_program, "compute_xyz", &err);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to create OpenCL kernel");
|
||||
cleanupOpenCL();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 7. 创建缓冲区
|
||||
m_depthBuffer = clCreateBuffer(m_context, CL_MEM_READ_ONLY,
|
||||
sizeof(float) * m_totalPoints, nullptr, &err);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to create depth buffer");
|
||||
cleanupOpenCL();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_xyzBuffer = clCreateBuffer(m_context, CL_MEM_WRITE_ONLY,
|
||||
sizeof(float) * m_totalPoints * 3, nullptr, &err);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred("Failed to create XYZ buffer");
|
||||
cleanupOpenCL();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_clInitialized = true;
|
||||
qDebug() << "[OpenCL] Initialization complete";
|
||||
return true;
|
||||
}
|
||||
|
||||
void PointCloudProcessor::processDepthData(const QByteArray &depthData, uint32_t blockId)
|
||||
{
|
||||
if (!m_clInitialized) {
|
||||
if (!initializeOpenCL()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证数据大小
|
||||
if (depthData.size() != m_totalPoints * sizeof(int16_t)) {
|
||||
qDebug() << "[PointCloud] Invalid depth data size:" << depthData.size()
|
||||
<< "expected:" << (m_totalPoints * sizeof(int16_t));
|
||||
return;
|
||||
}
|
||||
|
||||
// 转换int16_t到float
|
||||
const int16_t* depthShort = reinterpret_cast<const int16_t*>(depthData.constData());
|
||||
std::vector<float> depthFloat(m_totalPoints);
|
||||
for (size_t i = 0; i < m_totalPoints; i++) {
|
||||
depthFloat[i] = static_cast<float>(depthShort[i]);
|
||||
}
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "[PointCloud] Processing block" << blockId << "with" << m_totalPoints << "points";
|
||||
|
||||
// 上传深度数据到GPU
|
||||
cl_int err = clEnqueueWriteBuffer(m_queue, m_depthBuffer, CL_TRUE, 0,
|
||||
sizeof(float) * m_totalPoints, depthFloat.data(),
|
||||
0, nullptr, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred(QString("Failed to upload depth data: %1").arg(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置kernel参数
|
||||
float inv_fx = 1.0f / m_fx;
|
||||
float inv_fy = 1.0f / m_fy;
|
||||
int width = m_imageWidth;
|
||||
int height = m_imageHeight;
|
||||
|
||||
clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &m_depthBuffer);
|
||||
clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &m_xyzBuffer);
|
||||
clSetKernelArg(m_kernel, 2, sizeof(int), &width);
|
||||
clSetKernelArg(m_kernel, 3, sizeof(int), &height);
|
||||
clSetKernelArg(m_kernel, 4, sizeof(float), &inv_fx);
|
||||
clSetKernelArg(m_kernel, 5, sizeof(float), &inv_fy);
|
||||
clSetKernelArg(m_kernel, 6, sizeof(float), &m_cx);
|
||||
clSetKernelArg(m_kernel, 7, sizeof(float), &m_cy);
|
||||
clSetKernelArg(m_kernel, 8, sizeof(float), &m_zScale);
|
||||
|
||||
// 执行kernel
|
||||
size_t local = 256;
|
||||
size_t global = ((m_totalPoints + local - 1) / local) * local;
|
||||
err = clEnqueueNDRangeKernel(m_queue, m_kernel, 1, nullptr,
|
||||
&global, &local, 0, nullptr, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred(QString("Failed to execute kernel: %1").arg(err));
|
||||
return;
|
||||
}
|
||||
|
||||
clFinish(m_queue);
|
||||
|
||||
// 读取结果
|
||||
std::vector<float> xyz(m_totalPoints * 3);
|
||||
err = clEnqueueReadBuffer(m_queue, m_xyzBuffer, CL_TRUE, 0,
|
||||
sizeof(float) * m_totalPoints * 3, xyz.data(),
|
||||
0, nullptr, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
emit errorOccurred(QString("Failed to read XYZ data: %1").arg(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建PCL点云
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
||||
cloud->width = m_imageWidth;
|
||||
cloud->height = m_imageHeight;
|
||||
cloud->is_dense = false;
|
||||
cloud->points.resize(m_totalPoints);
|
||||
|
||||
// 填充点云数据(过滤全零点)
|
||||
for (size_t i = 0; i < m_totalPoints; i++) {
|
||||
cloud->points[i].x = xyz[i * 3];
|
||||
cloud->points[i].y = xyz[i * 3 + 1];
|
||||
cloud->points[i].z = xyz[i * 3 + 2];
|
||||
}
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "[PointCloud] Block" << blockId << "processed successfully";
|
||||
emit pointCloudReady(cloud, blockId);
|
||||
}
|
||||
|
||||
void PointCloudProcessor::cleanupOpenCL()
|
||||
{
|
||||
if (m_depthBuffer) {
|
||||
clReleaseMemObject(m_depthBuffer);
|
||||
m_depthBuffer = nullptr;
|
||||
}
|
||||
if (m_xyzBuffer) {
|
||||
clReleaseMemObject(m_xyzBuffer);
|
||||
m_xyzBuffer = nullptr;
|
||||
}
|
||||
if (m_kernel) {
|
||||
clReleaseKernel(m_kernel);
|
||||
m_kernel = nullptr;
|
||||
}
|
||||
if (m_program) {
|
||||
clReleaseProgram(m_program);
|
||||
m_program = nullptr;
|
||||
}
|
||||
if (m_queue) {
|
||||
clReleaseCommandQueue(m_queue);
|
||||
m_queue = nullptr;
|
||||
}
|
||||
if (m_context) {
|
||||
clReleaseContext(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
m_clInitialized = false;
|
||||
qDebug() << "[OpenCL] Cleanup complete";
|
||||
}
|
||||
1138
src/gui/MainWindow.cpp
Normal file
1138
src/gui/MainWindow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
159
src/gui/MainWindow.h
Normal file
159
src/gui/MainWindow.h
Normal file
@@ -0,0 +1,159 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
#include <memory>
|
||||
#include <pcl/point_cloud.h>
|
||||
#include <pcl/point_types.h>
|
||||
|
||||
class ConfigManager;
|
||||
class NetworkManager;
|
||||
class DeviceScanner;
|
||||
class GVSPParser;
|
||||
class PointCloudProcessor;
|
||||
class PointCloudWidget;
|
||||
struct DeviceInfo;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class QSpinBox;
|
||||
class QLabel;
|
||||
class QImage;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
// 相机控制槽函数
|
||||
void onStartClicked();
|
||||
void onStopClicked();
|
||||
void onOnceClicked();
|
||||
void onExposureChanged(int value);
|
||||
void onCaptureClicked();
|
||||
void onBrowseSavePathClicked();
|
||||
|
||||
// 网络配置槽函数
|
||||
void onConnectClicked();
|
||||
void onIpAddressChanged(const QString &ip);
|
||||
void onControlPortChanged(int port);
|
||||
void onDataPortChanged(int port);
|
||||
void onNetworkConnected();
|
||||
void onNetworkDisconnected();
|
||||
void onDataReceived(const QByteArray &data);
|
||||
|
||||
// GVSP数据处理槽函数
|
||||
void onImageReceived(const QImage &image, uint32_t blockId);
|
||||
void onDepthDataReceived(const QByteArray &depthData, uint32_t blockId);
|
||||
void onPointCloudReady(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, uint32_t blockId);
|
||||
|
||||
// 设备扫描槽函数
|
||||
void onRefreshClicked();
|
||||
void onDeviceFound(const DeviceInfo &device);
|
||||
void onScanProgress(int current, int total);
|
||||
void onScanFinished(int devicesFound);
|
||||
void onDeviceSelected(QListWidgetItem *item);
|
||||
|
||||
// 定时器更新
|
||||
void updateUI();
|
||||
|
||||
// 日志槽函数
|
||||
void onClearLogClicked();
|
||||
void onSaveLogClicked();
|
||||
|
||||
protected:
|
||||
// 重写事件处理函数以监听系统主题变化
|
||||
void changeEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void setupConnections();
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
// 拍照辅助函数
|
||||
bool saveDepthImage(const QString &dir, const QString &baseName, const QString &format);
|
||||
bool savePointCloud(const QString &dir, const QString &baseName, const QString &format);
|
||||
void performBackgroundSave(const QString &saveDir, const QString &baseName,
|
||||
const QString &depthFormat, const QString &pointCloudFormat,
|
||||
const QImage &image, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud);
|
||||
|
||||
// 日志辅助函数
|
||||
void addLog(const QString &message, const QString &level = "INFO");
|
||||
void updateStatistics();
|
||||
|
||||
private:
|
||||
// 核心管理器
|
||||
std::unique_ptr<ConfigManager> m_configManager;
|
||||
std::unique_ptr<NetworkManager> m_networkManager;
|
||||
std::unique_ptr<DeviceScanner> m_deviceScanner;
|
||||
std::unique_ptr<PointCloudProcessor> m_pointCloudProcessor;
|
||||
|
||||
// UI更新定时器
|
||||
QTimer *m_updateTimer;
|
||||
|
||||
// 状态变量
|
||||
bool m_isConnected;
|
||||
bool m_autoSaveOnNextFrame;
|
||||
|
||||
// 当前帧数据(用于拍照)
|
||||
QImage m_currentImage;
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr m_currentPointCloud;
|
||||
uint32_t m_currentFrameId;
|
||||
|
||||
// UI控件指针
|
||||
class QWidget *m_centralWidget;
|
||||
class QSplitter *m_mainSplitter;
|
||||
class QWidget *m_controlPanel;
|
||||
class QLabel *m_imageDisplay;
|
||||
PointCloudWidget *m_pointCloudWidget;
|
||||
|
||||
// 按钮控件
|
||||
QPushButton *m_refreshBtn;
|
||||
QPushButton *m_connectBtn;
|
||||
QPushButton *m_startBtn;
|
||||
QPushButton *m_stopBtn;
|
||||
QPushButton *m_onceBtn;
|
||||
QPushButton *m_captureBtn;
|
||||
|
||||
// 输入控件
|
||||
QSlider *m_exposureSlider;
|
||||
QSpinBox *m_exposureSpinBox;
|
||||
QSpinBox *m_ctrlPortSpinBox;
|
||||
QSpinBox *m_dataPortSpinBox;
|
||||
|
||||
// 拍照参数控件
|
||||
class QLineEdit *m_savePathEdit;
|
||||
QPushButton *m_browseSavePathBtn;
|
||||
class QComboBox *m_depthFormatCombo;
|
||||
class QComboBox *m_pointCloudFormatCombo;
|
||||
|
||||
// 显示控件
|
||||
QLabel *m_statusLabel;
|
||||
QListWidget *m_deviceList;
|
||||
|
||||
// 统计信息控件
|
||||
QLabel *m_fpsLabel;
|
||||
QLabel *m_resolutionLabel;
|
||||
QLabel *m_queueLabel;
|
||||
|
||||
// 日志显示控件
|
||||
class QTextEdit *m_logDisplay;
|
||||
QPushButton *m_clearLogBtn;
|
||||
QPushButton *m_saveLogBtn;
|
||||
|
||||
// 统计数据
|
||||
QDateTime m_lastFrameTime;
|
||||
int m_frameCount;
|
||||
int m_totalFrameCount;
|
||||
double m_currentFps;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
9
src/gui/MainWindow_data_handler.cpp
Normal file
9
src/gui/MainWindow_data_handler.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
void MainWindow::onDataReceived(const QByteArray &data)
|
||||
{
|
||||
static int packetCount = 0;
|
||||
packetCount++;
|
||||
|
||||
if (packetCount % 100 == 0) {
|
||||
qDebug() << "Received" << packetCount << "packets, latest size:" << data.size() << "bytes";
|
||||
}
|
||||
}
|
||||
259
src/gui/PointCloudGLWidget.cpp
Normal file
259
src/gui/PointCloudGLWidget.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
#include "gui/PointCloudGLWidget.h"
|
||||
#include <QDebug>
|
||||
#include <cmath>
|
||||
#include <cfloat>
|
||||
|
||||
PointCloudGLWidget::PointCloudGLWidget(QWidget *parent)
|
||||
: QOpenGLWidget(parent)
|
||||
, m_program(nullptr)
|
||||
, m_vertexBuffer(nullptr)
|
||||
, m_vao(nullptr)
|
||||
, m_pointCount(0)
|
||||
, m_orthoSize(2000.0f) // 正交投影视野大小
|
||||
, m_rotationX(0.0f) // 从正面看(0度)
|
||||
, m_rotationY(0.0f) // 从正面看(0度)
|
||||
, m_translation(0.0f, 0.0f, 0.0f)
|
||||
, m_leftButtonPressed(false)
|
||||
, m_rightButtonPressed(false)
|
||||
{
|
||||
setMinimumSize(400, 400);
|
||||
}
|
||||
|
||||
PointCloudGLWidget::~PointCloudGLWidget()
|
||||
{
|
||||
makeCurrent();
|
||||
if (m_vao) {
|
||||
m_vao->destroy();
|
||||
delete m_vao;
|
||||
}
|
||||
if (m_vertexBuffer) {
|
||||
m_vertexBuffer->destroy();
|
||||
delete m_vertexBuffer;
|
||||
}
|
||||
if (m_program) {
|
||||
delete m_program;
|
||||
}
|
||||
doneCurrent();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::initializeGL()
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
|
||||
setupShaders();
|
||||
|
||||
// 创建VAO和VBO
|
||||
m_vao = new QOpenGLVertexArrayObject(this);
|
||||
m_vao->create();
|
||||
|
||||
m_vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
|
||||
m_vertexBuffer->create();
|
||||
|
||||
qDebug() << "[PointCloudGLWidget] OpenGL initialized";
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::setupShaders()
|
||||
{
|
||||
m_program = new QOpenGLShaderProgram(this);
|
||||
|
||||
// 顶点着色器
|
||||
const char *vertexShaderSource = R"(
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 position;
|
||||
uniform mat4 mvp;
|
||||
void main() {
|
||||
gl_Position = mvp * vec4(position, 1.0);
|
||||
gl_PointSize = 2.0;
|
||||
}
|
||||
)";
|
||||
|
||||
// 片段着色器
|
||||
const char *fragmentShaderSource = R"(
|
||||
#version 330 core
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) {
|
||||
qDebug() << "[PointCloudGLWidget] Vertex shader error:" << m_program->log();
|
||||
}
|
||||
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) {
|
||||
qDebug() << "[PointCloudGLWidget] Fragment shader error:" << m_program->log();
|
||||
}
|
||||
if (!m_program->link()) {
|
||||
qDebug() << "[PointCloudGLWidget] Shader link error:" << m_program->log();
|
||||
} else {
|
||||
qDebug() << "[PointCloudGLWidget] Shaders compiled and linked successfully";
|
||||
}
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::resizeGL(int w, int h)
|
||||
{
|
||||
m_projection.setToIdentity();
|
||||
|
||||
// 使用正交投影代替透视投影,避免"喷射状"效果
|
||||
float aspect = float(w) / float(h);
|
||||
m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect,
|
||||
-m_orthoSize, m_orthoSize,
|
||||
-50000.0f, 50000.0f); // 近平面和远平面
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::paintGL()
|
||||
{
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
if (m_pointCount == 0 || !m_program) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 每帧重新计算正交投影矩阵(确保使用最新的m_orthoSize)
|
||||
m_projection.setToIdentity();
|
||||
float aspect = float(width()) / float(height());
|
||||
m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect,
|
||||
-m_orthoSize, m_orthoSize,
|
||||
-50000.0f, 50000.0f);
|
||||
|
||||
// 设置view矩阵
|
||||
m_view.setToIdentity();
|
||||
m_view.rotate(m_rotationX, 1.0f, 0.0f, 0.0f);
|
||||
m_view.rotate(m_rotationY, 0.0f, 1.0f, 0.0f);
|
||||
m_view.translate(m_translation);
|
||||
|
||||
// 设置model矩阵
|
||||
m_model.setToIdentity();
|
||||
|
||||
// 计算MVP矩阵
|
||||
QMatrix4x4 mvp = m_projection * m_view * m_model;
|
||||
|
||||
// 绑定shader和设置uniform
|
||||
m_program->bind();
|
||||
m_program->setUniformValue("mvp", mvp);
|
||||
|
||||
// 绑定VAO和绘制
|
||||
m_vao->bind();
|
||||
glDrawArrays(GL_POINTS, 0, m_pointCount);
|
||||
m_vao->release();
|
||||
|
||||
m_program->release();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
m_lastMousePos = event->pos();
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_leftButtonPressed = true;
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
m_rightButtonPressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QPoint delta = event->pos() - m_lastMousePos;
|
||||
m_lastMousePos = event->pos();
|
||||
|
||||
if (m_leftButtonPressed) {
|
||||
// 左键:旋转
|
||||
m_rotationX += delta.y() * 0.5f;
|
||||
m_rotationY += delta.x() * 0.5f;
|
||||
update();
|
||||
} else if (m_rightButtonPressed) {
|
||||
// 右键:平移(根据正交投影视野大小调整平移速度)
|
||||
float scale = m_orthoSize * 0.002f;
|
||||
m_translation.setX(m_translation.x() + delta.x() * scale);
|
||||
m_translation.setY(m_translation.y() - delta.y() * scale);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_leftButtonPressed = false;
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
m_rightButtonPressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
// 滚轮:缩放(调整正交投影视野大小)
|
||||
float delta = event->angleDelta().y() / 120.0f;
|
||||
m_orthoSize *= (1.0f - delta * 0.1f);
|
||||
m_orthoSize = qMax(100.0f, qMin(m_orthoSize, 10000.0f)); // 范围:100-10000
|
||||
|
||||
update(); // 触发重绘,paintGL会使用新的m_orthoSize
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
|
||||
{
|
||||
if (!cloud) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤全零点并转换为顶点数组
|
||||
m_vertices.clear();
|
||||
float minX = FLT_MAX, maxX = -FLT_MAX;
|
||||
float minY = FLT_MAX, maxY = -FLT_MAX;
|
||||
float minZ = FLT_MAX, maxZ = -FLT_MAX;
|
||||
|
||||
for (const auto& point : cloud->points) {
|
||||
if (point.z > 0.01f) {
|
||||
m_vertices.push_back(point.x);
|
||||
m_vertices.push_back(point.y);
|
||||
m_vertices.push_back(point.z);
|
||||
|
||||
// 统计坐标范围
|
||||
if (point.x < minX) minX = point.x;
|
||||
if (point.x > maxX) maxX = point.x;
|
||||
if (point.y < minY) minY = point.y;
|
||||
if (point.y > maxY) maxY = point.y;
|
||||
if (point.z < minZ) minZ = point.z;
|
||||
if (point.z > maxZ) maxZ = point.z;
|
||||
}
|
||||
}
|
||||
|
||||
m_pointCount = m_vertices.size() / 3;
|
||||
|
||||
// 添加调试日志
|
||||
static int updateCount = 0;
|
||||
if (updateCount < 3 || updateCount % 100 == 0) {
|
||||
qDebug() << "[PointCloudGLWidget] Update" << updateCount << "- Points:" << m_pointCount
|
||||
<< "Total cloud size:" << cloud->size();
|
||||
qDebug() << " X range:" << minX << "to" << maxX;
|
||||
qDebug() << " Y range:" << minY << "to" << maxY;
|
||||
qDebug() << " Z range:" << minZ << "to" << maxZ;
|
||||
}
|
||||
updateCount++;
|
||||
|
||||
updateBuffers();
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::updateBuffers()
|
||||
{
|
||||
if (m_vertices.empty() || !m_vao || !m_vertexBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
makeCurrent();
|
||||
|
||||
m_vao->bind();
|
||||
m_vertexBuffer->bind();
|
||||
m_vertexBuffer->allocate(m_vertices.data(), m_vertices.size() * sizeof(float));
|
||||
|
||||
// 设置顶点属性指针
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
|
||||
|
||||
m_vertexBuffer->release();
|
||||
m_vao->release();
|
||||
|
||||
doneCurrent();
|
||||
}
|
||||
56
src/gui/PointCloudWidget.cpp
Normal file
56
src/gui/PointCloudWidget.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "gui/PointCloudWidget.h"
|
||||
#include "gui/PointCloudGLWidget.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QDebug>
|
||||
|
||||
PointCloudWidget::PointCloudWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_statusLabel(nullptr)
|
||||
, m_glWidget(nullptr)
|
||||
{
|
||||
// 创建布局
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(5);
|
||||
|
||||
// 创建状态标签
|
||||
m_statusLabel = new QLabel("点云显示 (等待数据...)", this);
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_statusLabel->setStyleSheet("QLabel { background-color: #1C1C1C; color: white; font-size: 12px; padding: 5px; }");
|
||||
m_statusLabel->setMaximumHeight(30);
|
||||
layout->addWidget(m_statusLabel);
|
||||
|
||||
// 创建OpenGL点云显示widget
|
||||
m_glWidget = new PointCloudGLWidget(this);
|
||||
layout->addWidget(m_glWidget);
|
||||
|
||||
setLayout(layout);
|
||||
|
||||
qDebug() << "[PointCloudWidget] Embedded OpenGL widget created";
|
||||
}
|
||||
|
||||
PointCloudWidget::~PointCloudWidget()
|
||||
{
|
||||
// OpenGL widget会自动清理
|
||||
}
|
||||
|
||||
void PointCloudWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
|
||||
{
|
||||
if (!cloud || !m_glWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 统计有效点数
|
||||
int validPoints = 0;
|
||||
for (const auto& point : cloud->points) {
|
||||
if (point.z > 0.01f) {
|
||||
validPoints++;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新状态标签
|
||||
m_statusLabel->setText(QString("点云显示 | 有效点数: %1").arg(validPoints));
|
||||
|
||||
// 更新OpenGL显示
|
||||
m_glWidget->updatePointCloud(cloud);
|
||||
}
|
||||
58
src/main.cpp
Normal file
58
src/main.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include "gui/MainWindow.h"
|
||||
#include "core/Logger.h"
|
||||
|
||||
// Custom message handler to redirect qDebug output to Logger
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
Logger *logger = Logger::instance();
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
logger->debug(msg);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
logger->info(msg);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
logger->warning(msg);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
case QtFatalMsg:
|
||||
logger->error(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 设置应用程序信息
|
||||
app.setOrganizationName("UpperControl");
|
||||
app.setApplicationName("UpperControl GUI");
|
||||
app.setApplicationVersion("1.0.0");
|
||||
|
||||
// 初始化Logger(在可执行文件同目录下)
|
||||
QString logPath = QCoreApplication::applicationDirPath() + "/d330viewer.log";
|
||||
Logger::instance()->setLogFile(logPath);
|
||||
Logger::instance()->setMaxLines(10000); // 保留最新10000行
|
||||
|
||||
// 安装消息处理器
|
||||
qInstallMessageHandler(messageHandler);
|
||||
|
||||
qDebug() << "D330Viewer started";
|
||||
qDebug() << "Log file:" << logPath;
|
||||
|
||||
// 创建并显示主窗口
|
||||
MainWindow mainWindow;
|
||||
mainWindow.show();
|
||||
|
||||
int result = app.exec();
|
||||
|
||||
qDebug() << "D330Viewer exiting";
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user