糖包拆线 & 查看点云软件增加姿态,线条的显示
This commit is contained in:
parent
99881767bb
commit
1641a0e029
@ -135,17 +135,22 @@ INCLUDEPATH += ../../../SDK/Device/gl_linelaser_sdk/include
|
||||
|
||||
win32:CONFIG(release, debug|release): {
|
||||
LIBS += -L$$PWD/../../../SDK/Device/VzNLSDK/Windows/x64/Release
|
||||
LIBS += -lVzKernel -lVzNLDetect -lVzNLGraphics
|
||||
# gl_linelaser_sdk
|
||||
LIBS += -L$$PWD/../../../SDK/Device/gl_linelaser_sdk/x64
|
||||
LIBS += -lgl_linelaser_sdk
|
||||
}
|
||||
else:win32:CONFIG(debug, debug|release): {
|
||||
LIBS += -L$$PWD/../../../SDK/Device/VzNLSDK/Windows/x64/Debug
|
||||
LIBS += -lVzKerneld -lVzNLDetectd -lVzNLGraphicsd
|
||||
# gl_linelaser_sdk
|
||||
LIBS += -L$$PWD/../../../SDK/Device/gl_linelaser_sdk/x64
|
||||
LIBS += -lgl_linelaser_sdk
|
||||
}
|
||||
else:unix:!macx: {
|
||||
LIBS += -L$$PWD/../../../SDK/Device/VzNLSDK/Arm/aarch64
|
||||
LIBS += -lVzEyeSecurityLoader-shared -lVzKernel -lVzNLDetect -lVzNLGraphics
|
||||
|
||||
# gl_linelaser_sdk for Linux aarch64
|
||||
LIBS += -L$$PWD/../../../SDK/Device/gl_linelaser_sdk/aarch64_linux -lgl_linelaser_sdk
|
||||
}
|
||||
|
||||
@ -146,6 +146,15 @@ protected:
|
||||
*/
|
||||
int CreateDevice(IVrEyeDevice** ppDevice) override;
|
||||
|
||||
/**
|
||||
* @brief Modbus写寄存器回调(重写虚函数)
|
||||
* 实现协议:
|
||||
* - 地址0: 请求启动相机扫描
|
||||
* - 地址2: 检测完成状态(成功1,失败2)- 只读,由系统自动更新
|
||||
* - 地址4: 检测成功后自动输出所有xyzu数据(每条拆线8个寄存器)
|
||||
*/
|
||||
void OnModbusWriteCallback(uint16_t startAddress, const uint16_t* data, uint16_t count) override;
|
||||
|
||||
private:
|
||||
// TCP服务器相关方法
|
||||
int InitTcpServer(int nPort);
|
||||
@ -188,6 +197,10 @@ private:
|
||||
|
||||
// 手眼标定矩阵列表(从独立文件加载,暂时保留在Presenter中)
|
||||
std::vector<CalibMatrix> m_clibMatrixList;
|
||||
|
||||
// ModbusTCP协议相关
|
||||
DetectionResult m_lastDetectionResult; // 最新的检测结果
|
||||
std::mutex m_modbusResultMutex; // 保护检测结果的互斥锁
|
||||
};
|
||||
|
||||
#endif // BAGTHREADPOSITIONPRESENTER_H
|
||||
|
||||
@ -252,6 +252,14 @@ int BagThreadPositionPresenter::ProcessAlgoDetection(std::vector<std::pair<EVzRe
|
||||
if (GetStatusCallback<IYBagThreadPositionStatus>()) {
|
||||
if (auto pStatus = GetStatusCallback<IYBagThreadPositionStatus>()) pStatus->OnStatusUpdate("检测处理器未初始化");
|
||||
}
|
||||
|
||||
// 更新ModbusTCP寄存器:地址2 = 2(检测失败)
|
||||
if (IsModbusServerRunning()) {
|
||||
uint16_t statusValue = 2; // 失败
|
||||
WriteModbusRegisters(2, &statusValue, 1);
|
||||
LOG_INFO("ModbusTCP: 检测失败(处理器未初始化),地址2写入2\n");
|
||||
}
|
||||
|
||||
return ERR_CODE(DEV_NOT_FIND);
|
||||
}
|
||||
|
||||
@ -277,6 +285,16 @@ int BagThreadPositionPresenter::ProcessAlgoDetection(std::vector<std::pair<EVzRe
|
||||
}
|
||||
|
||||
LOG_INFO("[Algo Thread] sx_bagThreadMeasure detected %zu objects time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec());
|
||||
|
||||
// 如果检测失败,更新ModbusTCP状态
|
||||
if (nRet != SUCCESS) {
|
||||
if (IsModbusServerRunning()) {
|
||||
uint16_t statusValue = 2; // 失败
|
||||
WriteModbusRegisters(2, &statusValue, 1);
|
||||
LOG_INFO("ModbusTCP: 检测失败,地址2写入2\n");
|
||||
}
|
||||
}
|
||||
|
||||
ERR_CODE_RETURN(nRet);
|
||||
|
||||
// 8. 通知UI检测结果
|
||||
@ -285,6 +303,69 @@ int BagThreadPositionPresenter::ProcessAlgoDetection(std::vector<std::pair<EVzRe
|
||||
pStatus->OnDetectionResult(detectionResult);
|
||||
}
|
||||
|
||||
// 保存检测结果用于ModbusTCP输出
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_modbusResultMutex);
|
||||
m_lastDetectionResult = detectionResult;
|
||||
}
|
||||
|
||||
// 更新ModbusTCP寄存器:地址2 = 1(检测成功),并输出xyzu数据到地址4
|
||||
if (IsModbusServerRunning()) {
|
||||
uint16_t statusValue = 1; // 成功
|
||||
WriteModbusRegisters(2, &statusValue, 1);
|
||||
LOG_INFO("ModbusTCP: 检测成功,地址2写入1\n");
|
||||
|
||||
// 直接输出xyzu数据到地址4
|
||||
if (!detectionResult.threadInfoList.empty()) {
|
||||
size_t threadCount = detectionResult.threadInfoList.size();
|
||||
std::vector<uint16_t> outputData;
|
||||
outputData.reserve(threadCount * 8);
|
||||
|
||||
LOG_INFO("ModbusTCP: 准备输出 %zu 条拆线的xyzu数据\n", threadCount);
|
||||
|
||||
// 将每个拆线的 x, y, z, u 转换为寄存器数据
|
||||
for (const auto& thread : detectionResult.threadInfoList) {
|
||||
// x (centerX)
|
||||
float x = static_cast<float>(thread.centerX);
|
||||
uint16_t* xPtr = reinterpret_cast<uint16_t*>(&x);
|
||||
outputData.push_back(xPtr[0]);
|
||||
outputData.push_back(xPtr[1]);
|
||||
|
||||
// y (centerY)
|
||||
float y = static_cast<float>(thread.centerY);
|
||||
uint16_t* yPtr = reinterpret_cast<uint16_t*>(&y);
|
||||
outputData.push_back(yPtr[0]);
|
||||
outputData.push_back(yPtr[1]);
|
||||
|
||||
// z (centerZ)
|
||||
float z = static_cast<float>(thread.centerZ);
|
||||
uint16_t* zPtr = reinterpret_cast<uint16_t*>(&z);
|
||||
outputData.push_back(zPtr[0]);
|
||||
outputData.push_back(zPtr[1]);
|
||||
|
||||
// u (rotateAngle)
|
||||
float u = static_cast<float>(thread.rotateAngle);
|
||||
uint16_t* uPtr = reinterpret_cast<uint16_t*>(&u);
|
||||
outputData.push_back(uPtr[0]);
|
||||
outputData.push_back(uPtr[1]);
|
||||
|
||||
LOG_DEBUG("ModbusTCP: 拆线数据: x=%.3f, y=%.3f, z=%.3f, u=%.3f\n",
|
||||
thread.centerX, thread.centerY, thread.centerZ, thread.rotateAngle);
|
||||
}
|
||||
|
||||
// 从地址4开始写入数据
|
||||
if (!outputData.empty()) {
|
||||
int ret = WriteModbusRegisters(4, outputData.data(), static_cast<uint16_t>(outputData.size()));
|
||||
if (ret == 0) {
|
||||
LOG_INFO("ModbusTCP: 成功输出 %zu 条拆线的xyzu数据到寄存器(地址4起,共%zu个寄存器)\n",
|
||||
threadCount, outputData.size());
|
||||
} else {
|
||||
LOG_ERROR("ModbusTCP: 输出xyzu数据失败,错误码: %d\n", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
QString statusMsg = QString("检测完成,发现%1条拆线").arg(detectionResult.positions.size() / 2);
|
||||
if (auto pStatus = GetStatusCallback<IYBagThreadPositionStatus>()) pStatus->OnStatusUpdate(statusMsg.toStdString());
|
||||
@ -695,3 +776,38 @@ int BagThreadPositionPresenter::CreateDevice(IVrEyeDevice** ppDevice)
|
||||
LOG_ERROR("[BagThreadPositionPresenter] Failed to create GlLineLaser device, error: %d\n", nRet);
|
||||
return ERR_CODE(DEV_OPEN_ERR);
|
||||
}
|
||||
|
||||
// ============ ModbusTCP 协议实现 ============
|
||||
|
||||
void BagThreadPositionPresenter::OnModbusWriteCallback(uint16_t startAddress, const uint16_t* data, uint16_t count)
|
||||
{
|
||||
if (!data || count == 0) {
|
||||
LOG_WARNING("[ModbusTCP] 无效的写入参数\n");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("[ModbusTCP] 收到写寄存器请求: 地址=%d, 数量=%d, 值=%d\n", startAddress, count, data[0]);
|
||||
|
||||
// 地址0: 请求启动相机扫描
|
||||
if (startAddress == 0) {
|
||||
if (data[0] == 1) {
|
||||
LOG_INFO("[ModbusTCP] 收到启动扫描请求\n");
|
||||
|
||||
// 重置检测状态寄存器(地址2)为0
|
||||
uint16_t statusValue = 0;
|
||||
WriteModbusRegisters(2, &statusValue, 1);
|
||||
|
||||
// 触发检测
|
||||
bool success = TriggerDetection(m_currentCameraIndex);
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR("[ModbusTCP] 启动扫描失败\n");
|
||||
// 写入失败状态到地址2
|
||||
statusValue = 2; // 失败
|
||||
WriteModbusRegisters(2, &statusValue, 1);
|
||||
} else {
|
||||
LOG_INFO("[ModbusTCP] 扫描已启动\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
101
App/BagThreadPosition/MODBUS_PROTOCOL.md
Normal file
101
App/BagThreadPosition/MODBUS_PROTOCOL.md
Normal file
@ -0,0 +1,101 @@
|
||||
# 糖包拆线 ModbusTCP 协议说明
|
||||
|
||||
**应用名称**: BagThreadPosition(糖包拆线)
|
||||
**版本**: v1.0
|
||||
**日期**: 2026-02-07
|
||||
|
||||
## 概述
|
||||
|
||||
糖包拆线应用作为 ModbusTCP 服务端,使用标准 ModbusTCP 协议(端口 502)与外部设备(客户端)通信,实现相机扫描控制和检测结果输出。
|
||||
|
||||
### 基本信息
|
||||
|
||||
- **服务端**: 糖包拆线应用(BagThreadPosition)
|
||||
- **客户端**: PLC、机器人控制器或其他支持 ModbusTCP 的设备
|
||||
- **协议**: ModbusTCP(基于 TCP/IP 的 Modbus 协议)
|
||||
- **端口**: 502
|
||||
- **功能码**: 0x03(读保持寄存器)、0x10(写多个保持寄存器)
|
||||
|
||||
## 寄存器地址映射
|
||||
|
||||
### 控制寄存器
|
||||
|
||||
| 地址 | 功能 | 读/写 | 数据类型 | 说明 |
|
||||
|------|------|-------|----------|------|
|
||||
| 0 | 启动扫描 | 写 | uint16 | 写入1启动相机扫描 |
|
||||
| 2 | 检测状态 | 读 | uint16 | 0=未完成, 1=成功, 2=失败 |
|
||||
|
||||
### 数据寄存器
|
||||
|
||||
| 地址 | 功能 | 读/写 | 数据类型 | 说明 |
|
||||
|------|------|-------|----------|------|
|
||||
| 4+ | xyzu数据 | 读 | float[] | 拆线位置数据(每条拆线8个寄存器) |
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 客户端操作步骤
|
||||
|
||||
### 1. 启动扫描
|
||||
|
||||
客户端写入寄存器[0] = 1
|
||||
|
||||
- 服务端启动相机扫描
|
||||
- 寄存器[2]被重置为0(检测中)
|
||||
|
||||
### 2. 等待检测完成
|
||||
|
||||
客户端循环读取寄存器[2],直到值不为0
|
||||
|
||||
- 值为1:检测成功,xyzu数据已自动写入寄存器[4]开始的位置
|
||||
- 值为2:检测失败
|
||||
|
||||
### 3. 读取检测结果
|
||||
|
||||
当寄存器[2] = 1(成功)时,客户端直接从寄存器[4]开始读取 xyzu 数据。
|
||||
|
||||
## 数据格式
|
||||
|
||||
### 寄存器布局
|
||||
|
||||
每条拆线占用 8 个寄存器(4个float,每个float占2个寄存器):
|
||||
|
||||
```
|
||||
寄存器[4+i*8+0:1] -> x (centerX, float)
|
||||
寄存器[4+i*8+2:3] -> y (centerY, float)
|
||||
寄存器[4+i*8+4:5] -> z (centerZ, float)
|
||||
寄存器[4+i*8+6:7] -> u (rotateAngle, float)
|
||||
```
|
||||
|
||||
其中 i 为拆线索引(从0开始)。
|
||||
|
||||
### xyzu 含义
|
||||
|
||||
- **x (centerX)**: 拆线端部中心点的X坐标(毫米)
|
||||
- **y (centerY)**: 拆线端部中心点的Y坐标(毫米)
|
||||
- **z (centerZ)**: 拆线端部中心点的Z坐标(毫米)
|
||||
- **u (rotateAngle)**: 拆线旋转角度(度,范围 -30° ~ 30°)
|
||||
|
||||
### Float 数据格式
|
||||
|
||||
每个 float 值占用 2 个 uint16 寄存器(IEEE 754 单精度浮点数):
|
||||
- 低位寄存器:float 的低16位
|
||||
- 高位寄存器:float 的高16位
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **服务端**: 糖包拆线应用启动后自动开启 ModbusTCP 服务端,监听端口 502
|
||||
2. **客户端**: 支持标准 ModbusTCP 协议的设备均可连接(PLC、机器人控制器等)
|
||||
3. **数据顺序**: 拆线数据按检测顺序排列
|
||||
4. **自动输出**: 检测成功后,服务端自动将 xyzu 数据写入寄存器[4]开始的位置,客户端无需额外请求
|
||||
5. **字节序**: Float 数据使用小端字节序(Little Endian)
|
||||
6. **连接数**: 支持多个客户端同时连接,但建议单客户端操作
|
||||
7. **超时处理**: 建议客户端设置合理的超时时间(如30秒)等待检测完成
|
||||
|
||||
## 错误处理
|
||||
|
||||
如果寄存器[2]返回2(失败),可能的原因:
|
||||
- 相机未连接
|
||||
- 检测算法失败
|
||||
- 检测处理器未初始化
|
||||
|
||||
建议客户端在失败后重新启动扫描或通知操作人员检查设备状态。
|
||||
@ -53,10 +53,10 @@ public:
|
||||
|
||||
/**
|
||||
* @brief 初始化TCP服务器
|
||||
* @param port TCP端口号,默认5020
|
||||
* @param port TCP端口号
|
||||
* @return 0-成功,其他-错误码
|
||||
*/
|
||||
int Initialize(uint16_t port = 5020);
|
||||
int Initialize(uint16_t port = 6800);
|
||||
|
||||
/**
|
||||
* @brief 反初始化,停止服务
|
||||
|
||||
@ -624,11 +624,19 @@ void BasePresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLas
|
||||
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
|
||||
lineData.p3DPoint = new SVzNL3DPosition[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p3DPoint) {
|
||||
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
|
||||
if(pLaserLinePoint->p3DPoint){
|
||||
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p3DPoint, 0, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
lineData.p2DPoint = new SVzNL2DPosition[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p2DPoint) {
|
||||
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
|
||||
if (lineData.p2DPoint){
|
||||
if(pLaserLinePoint->p2DPoint) {
|
||||
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p2DPoint, 0, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eDataType == keResultDataType_PointXYZRGBA) {
|
||||
@ -636,11 +644,19 @@ void BasePresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLas
|
||||
if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) {
|
||||
lineData.p3DPoint = new SVzNLPointXYZRGBA[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p3DPoint) {
|
||||
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
|
||||
if(pLaserLinePoint->p3DPoint){
|
||||
memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p3DPoint, 0, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
lineData.p2DPoint = new SVzNL2DLRPoint[pLaserLinePoint->nPointCount];
|
||||
if (lineData.p2DPoint) {
|
||||
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
|
||||
if(pLaserLinePoint->p2DPoint) {
|
||||
memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
|
||||
} else {
|
||||
memset(lineData.p2DPoint, 0, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -708,7 +724,7 @@ void BasePresenter::_StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, voi
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 相机一直重联
|
||||
void BasePresenter::StartCameraReconnectTimer()
|
||||
{
|
||||
LOG_DEBUG("[BasePresenter] StartCameraReconnectTimer called\n");
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
// 静态实例指针,供SDK回调访问
|
||||
CGlLineLaserDevice* CGlLineLaserDevice::s_pInstance = nullptr;
|
||||
|
||||
CGlLineLaserDevice::CGlLineLaserDevice()
|
||||
: m_nDeviceId(0)
|
||||
, m_bDeviceOpen(false)
|
||||
@ -15,6 +18,7 @@ CGlLineLaserDevice::CGlLineLaserDevice()
|
||||
, m_nBatchLines(200)
|
||||
{
|
||||
memset(&m_modelInfo, 0, sizeof(GLX8_2_ModelInfo));
|
||||
s_pInstance = this;
|
||||
}
|
||||
|
||||
CGlLineLaserDevice::~CGlLineLaserDevice()
|
||||
@ -22,11 +26,13 @@ CGlLineLaserDevice::~CGlLineLaserDevice()
|
||||
if (m_bDeviceOpen) {
|
||||
CloseDevice();
|
||||
}
|
||||
if (s_pInstance == this) {
|
||||
s_pInstance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int CGlLineLaserDevice::InitDevice()
|
||||
{
|
||||
// 初始化 gl_linelaser_sdk
|
||||
int ret = GLX8_2_Initialize();
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("GLX8_2_Initialize failed: %d\n", ret);
|
||||
@ -46,7 +52,6 @@ int CGlLineLaserDevice::SetStatusCallback(VzNL_OnNotifyStatusCBEx fNotify, void
|
||||
|
||||
int CGlLineLaserDevice::OpenDevice(const char* sIP, bool bRGBD, bool bSwing, bool bFillLaser)
|
||||
{
|
||||
// gl_linelaser_sdk 不支持 RGBD 和摆动模式,忽略这些参数
|
||||
(void)bRGBD;
|
||||
(void)bSwing;
|
||||
(void)bFillLaser;
|
||||
@ -56,14 +61,12 @@ int CGlLineLaserDevice::OpenDevice(const char* sIP, bool bRGBD, bool bSwing, boo
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// 解析IP地址
|
||||
GLX8_2_ETHERNET_CONFIG ethConfig;
|
||||
memset(ðConfig, 0, sizeof(ethConfig));
|
||||
|
||||
|
||||
if (sIP && strlen(sIP) > 0) {
|
||||
LOG_DEBUG("open IP address format: %s\n", sIP);
|
||||
// 解析IP字符串 "x.x.x.x"
|
||||
int ip[4];
|
||||
if (sscanf(sIP, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]) == 4) {
|
||||
ethConfig.abyIpAddress[0] = (unsigned char)ip[0];
|
||||
@ -84,7 +87,6 @@ int CGlLineLaserDevice::OpenDevice(const char* sIP, bool bRGBD, bool bSwing, boo
|
||||
return ERR_CODE(DEV_NOT_FIND);
|
||||
}
|
||||
|
||||
// 使用第一个找到的设备
|
||||
memcpy(ðConfig, &pDevices[0], sizeof(GLX8_2_ETHERNET_CONFIG));
|
||||
char ipStr[32];
|
||||
sprintf(ipStr, "%d.%d.%d.%d", ethConfig.abyIpAddress[0], ethConfig.abyIpAddress[1], ethConfig.abyIpAddress[2], ethConfig.abyIpAddress[3]);
|
||||
@ -95,7 +97,7 @@ int CGlLineLaserDevice::OpenDevice(const char* sIP, bool bRGBD, bool bSwing, boo
|
||||
// 打开设备
|
||||
int ret = GLX8_2_EthernetOpen(m_nDeviceId, ðConfig);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("GLX8_2_Ethernet Open failed: %d\n", ret);
|
||||
LOG_ERROR("GLX8_2_EthernetOpen failed: %d\n", ret);
|
||||
return ERR_CODE(DEV_OPEN_ERR);
|
||||
}
|
||||
|
||||
@ -114,14 +116,186 @@ int CGlLineLaserDevice::OpenDevice(const char* sIP, bool bRGBD, bool bSwing, boo
|
||||
m_modelInfo.Model, m_nProfileWidth, m_dXPitch, m_dYPitch);
|
||||
}
|
||||
|
||||
// 分配数据缓存
|
||||
m_profileBuffer.resize(static_cast<size_t>(m_nProfileWidth) * m_nBatchLines);
|
||||
m_intensityBuffer.resize(static_cast<size_t>(m_nProfileWidth) * m_nBatchLines);
|
||||
m_positionBuffer.resize(m_nProfileWidth);
|
||||
// 注册SDK批处理回调(批处理参数在设备端软件预先配置)
|
||||
ret = RegisterBatchCallback();
|
||||
if (ret != SUCCESS) {
|
||||
LOG_ERROR("RegisterBatchCallback failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Device initialized successfully (callback mode)\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// 配置批处理模式
|
||||
int CGlLineLaserDevice::ConfigureBatchMode()
|
||||
{
|
||||
LOG_DEBUG("Configuring batch mode...\n");
|
||||
|
||||
char inval[4];
|
||||
uint32_t ival;
|
||||
|
||||
// 1. 开启批处理测量
|
||||
ival = 1;
|
||||
memcpy(inval, &ival, 4);
|
||||
int ret = GLX8_2_SetSetting(m_nDeviceId, 1, 0, 0, 3, nullptr, inval, 4);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("Failed to enable batch mode: %d\n", ret);
|
||||
return ERR_CODE(DEV_CTRL_ERR);
|
||||
}
|
||||
LOG_DEBUG("Batch mode enabled\n");
|
||||
|
||||
// 2. 设置批处理数量
|
||||
ival = m_nBatchLines;
|
||||
memcpy(inval, &ival, 4);
|
||||
ret = GLX8_2_SetSetting(m_nDeviceId, 1, 0, 0, 0x0a, nullptr, inval, 4);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("Failed to set batch lines: %d\n", ret);
|
||||
return ERR_CODE(DEV_CTRL_ERR);
|
||||
}
|
||||
LOG_DEBUG("Batch lines set to %d\n", m_nBatchLines);
|
||||
|
||||
// 3. 设置带亮度输出
|
||||
ival = 1;
|
||||
memcpy(inval, &ival, 4);
|
||||
ret = GLX8_2_SetSetting(m_nDeviceId, 1, 2, 0, 0x0b, nullptr, inval, 4);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("Failed to enable intensity output: %d\n", ret);
|
||||
return ERR_CODE(DEV_CTRL_ERR);
|
||||
}
|
||||
LOG_DEBUG("Intensity output enabled\n");
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// 注册SDK批处理回调
|
||||
int CGlLineLaserDevice::RegisterBatchCallback()
|
||||
{
|
||||
s_pInstance = this;
|
||||
|
||||
int ret = GLX8_2_SetBatchOneTimeDataHandler(m_nDeviceId, BatchOneTimeCallback);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("GLX8_2_SetBatchOneTimeDataHandler failed: %d\n", ret);
|
||||
return ERR_CODE(DEV_CTRL_ERR);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Batch callback registered\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// SDK批处理回调(静态函数,由SDK线程调用)
|
||||
void CGlLineLaserDevice::BatchOneTimeCallback(const GLX8_2_STR_CALLBACK_INFO* info, const GLX8_2_Data DataObj)
|
||||
{
|
||||
if (s_pInstance == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_pInstance->m_bDetecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_pInstance->ProcessBatchData(info, DataObj);
|
||||
}
|
||||
|
||||
// 处理一次批处理回调数据
|
||||
void CGlLineLaserDevice::ProcessBatchData(const GLX8_2_STR_CALLBACK_INFO* info, const GLX8_2_Data DataObj)
|
||||
{
|
||||
if (info == nullptr || DataObj == nullptr) {
|
||||
LOG_ERROR("ProcessBatchData: null parameter\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查批处理状态
|
||||
if (info->returnStatus != 0) {
|
||||
LOG_WARNING("Batch returnStatus: %d\n", info->returnStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
int batchCount = info->BatchPoints;
|
||||
int width = info->xPoints;
|
||||
|
||||
if (batchCount <= 0 || width <= 0) {
|
||||
LOG_WARNING("Invalid batch data: batchCount=%d, width=%d\n", batchCount, width);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Received batch: %d lines, width: %d, startEncoder: %d, batchTimes: %d\n", batchCount, width, info->startEncoder, info->BatchTimes);
|
||||
|
||||
// 使用回调info中的实时参数
|
||||
double xPitch = info->xPixth;
|
||||
if (xPitch <= 0) {
|
||||
xPitch = m_dXPitch; // 回退到设备初始化时获取的值
|
||||
}
|
||||
|
||||
// 从SDK获取数据指针(SDK内部管理内存,无需自己分配)
|
||||
int32_t* profileData = GLX8_2_GetBatchProfilePoint(DataObj, 0);
|
||||
uint32_t* encoderData = GLX8_2_GetBatchEncoderPoint(DataObj, 0);
|
||||
|
||||
if (profileData == nullptr) {
|
||||
LOG_ERROR("GLX8_2_GetBatchProfilePoint returned null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 局部位置缓存
|
||||
std::vector<SVzNL3DPosition> positionBuffer(width);
|
||||
|
||||
// 逐行处理数据并回调给上层
|
||||
for (int lineIdx = 0; lineIdx < batchCount; lineIdx++) {
|
||||
if (!m_bDetecting) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算当前行在批处理数据中的偏移
|
||||
const int32_t* lineProfile = profileData + static_cast<size_t>(lineIdx) * width;
|
||||
|
||||
// 转换为xyz坐标
|
||||
double yOffset = static_cast<double>(m_ullFrameIndex) * m_dYPitch;
|
||||
for (int i = 0; i < width; i++) {
|
||||
SVzNL3DPosition& pos = positionBuffer[i];
|
||||
pos.nPointIdx = i;
|
||||
pos.pt3D.x = (static_cast<double>(i) - static_cast<double>(width) / 2.0) * xPitch;
|
||||
pos.pt3D.y = yOffset;
|
||||
|
||||
int32_t rawZ = lineProfile[i];
|
||||
if (rawZ == 0x7FFFFFFF || rawZ < -100000000) {
|
||||
pos.pt3D.z = 0.0;
|
||||
} else {
|
||||
pos.pt3D.z = static_cast<double>(rawZ) * 0.00001; // 0.01um -> mm
|
||||
}
|
||||
}
|
||||
|
||||
// 填充 SVzLaserLineData 结构
|
||||
SVzLaserLineData laserLineData;
|
||||
memset(&laserLineData, 0, sizeof(SVzLaserLineData));
|
||||
|
||||
laserLineData.p3DPoint = positionBuffer.data();
|
||||
laserLineData.p2DPoint = nullptr;
|
||||
laserLineData.nPointCount = width;
|
||||
laserLineData.dTotleOffset = yOffset;
|
||||
laserLineData.dStep = m_dYPitch;
|
||||
laserLineData.llFrameIdx = m_ullFrameIndex;
|
||||
laserLineData.llTimeStamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
laserLineData.nEncodeNo = encoderData ? encoderData[lineIdx] : 0;
|
||||
laserLineData.fSwingAngle = 0.0f;
|
||||
laserLineData.bEndOnceScan = (lineIdx == batchCount - 1) ? VzTrue : VzFalse;
|
||||
|
||||
// 回调给上层应用
|
||||
if (m_pDetectCallback) {
|
||||
m_pDetectCallback(m_eDataType, &laserLineData, m_pDetectCallbackParam);
|
||||
}
|
||||
|
||||
m_ullFrameIndex++;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Processed %d lines, total frames: %llu\n", batchCount, m_ullFrameIndex);
|
||||
|
||||
// 通知上层本次批处理数据处理完成
|
||||
if (m_pStatusCallback) {
|
||||
m_pStatusCallback(keDeviceWorkStatus_Device_Swing_Finish, nullptr, 0, m_pStatusCallbackParam);
|
||||
}
|
||||
}
|
||||
|
||||
int CGlLineLaserDevice::GetVersion(SVzNLVersionInfo& sVersionInfo)
|
||||
{
|
||||
memset(&sVersionInfo, 0, sizeof(SVzNLVersionInfo));
|
||||
@ -131,7 +305,6 @@ int CGlLineLaserDevice::GetVersion(SVzNLVersionInfo& sVersionInfo)
|
||||
strncpy(sVersionInfo.szSDKVersion, sdkVersion, VZNL_VERSION_LENGTH - 1);
|
||||
}
|
||||
|
||||
// 填充其他版本信息
|
||||
strncpy(sVersionInfo.szAppVersion, "GlLineLaser", VZNL_VERSION_LENGTH - 1);
|
||||
|
||||
return SUCCESS;
|
||||
@ -141,12 +314,10 @@ int CGlLineLaserDevice::GetDevInfo(SVzNLEyeDeviceInfoEx& sDeviceInfo)
|
||||
{
|
||||
memset(&sDeviceInfo, 0, sizeof(SVzNLEyeDeviceInfoEx));
|
||||
|
||||
// 填充设备信息
|
||||
strncpy(sDeviceInfo.sEyeCBInfo.byServerIP, m_strDeviceIP.c_str(), VZNL_SDK_NETWORK_IPv4_LENGTH - 1);
|
||||
strncpy(sDeviceInfo.sEyeCBInfo.szDeviceName, m_modelInfo.Model, VZNL_DEVICE_NAME_LENGTH - 1);
|
||||
strncpy(sDeviceInfo.sEyeCBInfo.szDeviceID, m_modelInfo.HeaderSerial, VZNL_GUID_LENGTH - 1);
|
||||
|
||||
// 设置分辨率
|
||||
sDeviceInfo.sVideoRes.nFrameWidth = m_nProfileWidth;
|
||||
sDeviceInfo.sVideoRes.nFrameHeight = m_nBatchLines;
|
||||
|
||||
@ -191,14 +362,19 @@ int CGlLineLaserDevice::StartDetect(VzNL_AutoOutputLaserLineExCB fCallFunc, EVzR
|
||||
m_pDetectCallback = fCallFunc;
|
||||
m_pDetectCallbackParam = param;
|
||||
m_eDataType = eDataType;
|
||||
m_bStopDetect = false;
|
||||
m_ullFrameIndex = 0;
|
||||
|
||||
// 启动数据采集线程(主动轮询模式)
|
||||
m_bDetecting = true;
|
||||
m_detectThread = std::thread(&CGlLineLaserDevice::DetectThreadFunc, this);
|
||||
|
||||
LOG_DEBUG("Detection started\n");
|
||||
// 启动回调模式批处理(0=立即开始)
|
||||
int ret = GLX8_2_StartMeasureWithCallback(m_nDeviceId, 0);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("GLX8_2_StartMeasureWithCallback failed: %d\n", ret);
|
||||
m_bDetecting = false;
|
||||
return ERR_CODE(DEV_CTRL_ERR);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Detection started (callback mode)\n");
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
@ -214,7 +390,7 @@ int CGlLineLaserDevice::StopDetect()
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
m_bStopDetect = true;
|
||||
m_bDetecting = false;
|
||||
|
||||
// 停止批处理
|
||||
int ret = GLX8_2_StopMeasure(m_nDeviceId);
|
||||
@ -222,13 +398,6 @@ int CGlLineLaserDevice::StopDetect()
|
||||
LOG_ERROR("GLX8_2_StopMeasure failed: %d\n", ret);
|
||||
}
|
||||
|
||||
// 等待线程结束
|
||||
if (m_detectThread.joinable()) {
|
||||
m_detectThread.join();
|
||||
}
|
||||
|
||||
m_bDetecting = false;
|
||||
|
||||
// 通知状态变化
|
||||
if (m_pStatusCallback) {
|
||||
m_pStatusCallback(keDeviceWorkStatus_Device_Swing_Finish, nullptr, 0, m_pStatusCallbackParam);
|
||||
@ -240,163 +409,12 @@ int CGlLineLaserDevice::StopDetect()
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// 数据采集线程函数(主动轮询模式,参考 test_deal_atch_datas)
|
||||
void CGlLineLaserDevice::DetectThreadFunc()
|
||||
{
|
||||
LOG_DEBUG("Detect thread started\n");
|
||||
|
||||
while (!m_bStopDetect) {
|
||||
// 启动批处理(参考 test_deal_atch_datas)
|
||||
int ret = GLX8_2_StartMeasure(m_nDeviceId, 5000); // 5秒超时
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("GLX8_2_StartMeasure failed: %d\n", ret);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 等待一小段时间让设备准备好
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// 获取批处理数据(参考 test_batch_datas)
|
||||
GetBatchData();
|
||||
|
||||
// 批处理完成后等待一段时间再开始下一次
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
LOG_DEBUG("Detect thread stopped\n");
|
||||
}
|
||||
|
||||
// 获取批处理数据(参考 test_batch_datas 的流程)
|
||||
void CGlLineLaserDevice::GetBatchData()
|
||||
{
|
||||
GLX8_2_STR_CALLBACK_INFO info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
|
||||
const int maxBatchLines = m_nBatchLines;
|
||||
|
||||
// 确保缓存足够大
|
||||
size_t totalPoints = static_cast<size_t>(m_nProfileWidth) * maxBatchLines;
|
||||
if (m_profileBuffer.size() < totalPoints) {
|
||||
m_profileBuffer.resize(totalPoints);
|
||||
}
|
||||
if (m_intensityBuffer.size() < totalPoints) {
|
||||
m_intensityBuffer.resize(totalPoints);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> encoderBuffer(maxBatchLines);
|
||||
|
||||
// 使用 GLX8_2_ReceiveDataAuto 顺序获取批处理数据
|
||||
int ret = GLX8_2_ReceiveDataAuto(m_nDeviceId, &info,
|
||||
m_profileBuffer.data(),
|
||||
m_intensityBuffer.data(),
|
||||
encoderBuffer.data());
|
||||
|
||||
if (ret != 0) {
|
||||
LOG_WARNING("GLX8_2_ReceiveDataAuto failed: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
int batchCount = info.BatchPoints;
|
||||
int width = info.xPoints;
|
||||
|
||||
if (batchCount <= 0 || width <= 0) {
|
||||
LOG_WARNING("Invalid batch data: batchCount=%d, width=%d\n", batchCount, width);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Received batch: %d lines, width: %d, startEncoder: %d\n",
|
||||
batchCount, width, info.startEncoder);
|
||||
|
||||
// 确保位置缓存足够大
|
||||
if (m_positionBuffer.size() < static_cast<size_t>(width)) {
|
||||
m_positionBuffer.resize(width);
|
||||
}
|
||||
|
||||
// 逐行处理数据并回调(转换为xyz点云数据)
|
||||
for (int lineIdx = 0; lineIdx < batchCount; lineIdx++) {
|
||||
if (m_bStopDetect) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算当前行在缓存中的偏移
|
||||
const int32_t* lineProfile = m_profileBuffer.data() + static_cast<size_t>(lineIdx) * width;
|
||||
|
||||
// 转换为xyz坐标
|
||||
ConvertProfileToPosition(lineProfile, width, lineIdx);
|
||||
|
||||
// 填充 SVzLaserLineData 结构
|
||||
SVzLaserLineData laserLineData;
|
||||
memset(&laserLineData, 0, sizeof(SVzLaserLineData));
|
||||
|
||||
laserLineData.p3DPoint = m_positionBuffer.data();
|
||||
laserLineData.p2DPoint = nullptr; // 线激光没有2D数据
|
||||
laserLineData.nPointCount = width;
|
||||
laserLineData.dTotleOffset = static_cast<double>(m_ullFrameIndex) * m_dYPitch;
|
||||
laserLineData.dStep = m_dYPitch;
|
||||
laserLineData.llFrameIdx = m_ullFrameIndex;
|
||||
laserLineData.llTimeStamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
laserLineData.nEncodeNo = encoderBuffer[lineIdx]; // 使用实际的编码器值
|
||||
laserLineData.fSwingAngle = 0.0f; // 线激光没有摆动角度
|
||||
laserLineData.bEndOnceScan = (lineIdx == batchCount - 1) ? VzTrue : VzFalse;
|
||||
|
||||
// 回调给上层应用
|
||||
if (m_pDetectCallback) {
|
||||
m_pDetectCallback(m_eDataType, &laserLineData, m_pDetectCallbackParam);
|
||||
}
|
||||
|
||||
m_ullFrameIndex++;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Processed %d lines, total frames: %llu\n", batchCount, m_ullFrameIndex);
|
||||
}
|
||||
|
||||
// 将轮廓数据转换为位置数据
|
||||
void CGlLineLaserDevice::ConvertProfileToPosition(const int32_t* profileData, int count, int lineIndex)
|
||||
{
|
||||
// lineIndex 参数保留用于未来扩展,当前使用 m_ullFrameIndex 计算全局偏移
|
||||
(void)lineIndex;
|
||||
|
||||
// 确保缓存足够大
|
||||
if (m_positionBuffer.size() < static_cast<size_t>(count)) {
|
||||
m_positionBuffer.resize(count);
|
||||
}
|
||||
|
||||
// 计算Y偏移(基于全局帧索引)
|
||||
double yOffset = static_cast<double>(m_ullFrameIndex) * m_dYPitch;
|
||||
|
||||
// 转换每个点
|
||||
for (int i = 0; i < count; i++) {
|
||||
SVzNL3DPosition& pos = m_positionBuffer[i];
|
||||
|
||||
pos.nPointIdx = i;
|
||||
|
||||
// X 坐标:根据点索引和X间距计算
|
||||
// X范围居中:从 -width/2 * xPitch 到 width/2 * xPitch
|
||||
pos.pt3D.x = (static_cast<double>(i) - static_cast<double>(count) / 2.0) * m_dXPitch;
|
||||
|
||||
// Y 坐标:扫描方向偏移
|
||||
pos.pt3D.y = yOffset;
|
||||
|
||||
// Z 坐标:高度值,gl_linelaser_sdk 单位是 0.01um,转换为 mm
|
||||
// 无效值通常是最大值或特定值,这里假设 0x7FFFFFFF 或负值为无效
|
||||
int32_t rawZ = profileData[i];
|
||||
if (rawZ == 0x7FFFFFFF || rawZ < -100000000) {
|
||||
pos.pt3D.z = 0.0; // 无效值设为0
|
||||
} else {
|
||||
pos.pt3D.z = static_cast<double>(rawZ) * 0.00001; // 0.01um -> mm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ ROI相关(线激光不支持)============
|
||||
|
||||
int CGlLineLaserDevice::SetDetectROI(SVzNLRect& leftROI, SVzNLRect& rightROI)
|
||||
{
|
||||
(void)leftROI;
|
||||
(void)rightROI;
|
||||
// 线激光不支持ROI设置
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -407,19 +425,17 @@ int CGlLineLaserDevice::GetDetectROI(SVzNLRect& leftROI, SVzNLRect& rightROI)
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// ============ 曝光/增益相关(线激光通过SDK设置)============
|
||||
// ============ 曝光/增益相关 ============
|
||||
|
||||
int CGlLineLaserDevice::SetEyeExpose(unsigned int& exposeTime)
|
||||
{
|
||||
(void)exposeTime;
|
||||
// gl_linelaser_sdk 通过 GLX8_2_SetSetting 设置曝光
|
||||
// 暂不实现详细接口
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int CGlLineLaserDevice::GetEyeExpose(unsigned int& exposeTime)
|
||||
{
|
||||
exposeTime = 1000; // 默认值
|
||||
exposeTime = 1000;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -431,7 +447,7 @@ int CGlLineLaserDevice::SetEyeGain(unsigned int& gain)
|
||||
|
||||
int CGlLineLaserDevice::GetEyeGain(unsigned int& gain)
|
||||
{
|
||||
gain = 100; // 默认值
|
||||
gain = 100;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -440,13 +456,12 @@ int CGlLineLaserDevice::GetEyeGain(unsigned int& gain)
|
||||
int CGlLineLaserDevice::SetFrame(int& frame)
|
||||
{
|
||||
(void)frame;
|
||||
// 线激光帧率由硬件决定
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int CGlLineLaserDevice::GetFrame(int& frame)
|
||||
{
|
||||
frame = 100; // 默认帧率
|
||||
frame = 100;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -454,7 +469,7 @@ int CGlLineLaserDevice::GetFrame(int& frame)
|
||||
|
||||
bool CGlLineLaserDevice::IsSupport()
|
||||
{
|
||||
return false; // 不支持RGBD
|
||||
return false;
|
||||
}
|
||||
|
||||
int CGlLineLaserDevice::SetRGBDExposeThres(float& value)
|
||||
@ -520,7 +535,6 @@ int CGlLineLaserDevice::SetWorkRange(double& dMin, double& dMax)
|
||||
|
||||
int CGlLineLaserDevice::GetWorkRange(double& dMin, double& dMax)
|
||||
{
|
||||
// 从设备信息获取工作范围
|
||||
dMin = m_modelInfo.zRangmin;
|
||||
dMax = m_modelInfo.zRangmax;
|
||||
return SUCCESS;
|
||||
@ -558,11 +572,6 @@ int CGlLineLaserDevice::SetBatchLines(unsigned int batchLines)
|
||||
return ERR_CODE(DEV_ARG_INVAILD);
|
||||
}
|
||||
m_nBatchLines = batchLines;
|
||||
|
||||
// 重新分配缓存
|
||||
m_profileBuffer.resize(static_cast<size_t>(m_nProfileWidth) * m_nBatchLines);
|
||||
m_intensityBuffer.resize(static_cast<size_t>(m_nProfileWidth) * m_nBatchLines);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -602,8 +611,6 @@ int CGlLineLaserDevice::GetMeasureRange(double& xMin, double& xMax, double& zMin
|
||||
}
|
||||
|
||||
// ============ 工厂方法实现 ============
|
||||
// 注意:IVrEyeDevice::CreateObject 在 VrEyeDevice 中实现,创建 VzNLSDK 设备
|
||||
// GlLineLaserDevice 只提供 IGlLineLaserDevice::CreateGlLineLaserObject 工厂方法
|
||||
|
||||
int IGlLineLaserDevice::CreateGlLineLaserObject(IGlLineLaserDevice** ppDevice)
|
||||
{
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
#include "IGlLineLaserDevice.h"
|
||||
#include "phoskey_ss.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
@ -15,6 +12,15 @@
|
||||
* @brief CGlLineLaserDevice 实现类
|
||||
*
|
||||
* 将 gl_linelaser_sdk (phoskey_ss) 的接口适配到 IVrEyeDevice 接口。
|
||||
* 使用 SDK 回调模式(GLX8_2_SetBatchOneTimeDataHandler)获取批处理数据。
|
||||
*
|
||||
* 完整闭环流程:
|
||||
* OpenDevice → 配置批处理参数 + 注册SDK回调
|
||||
* StartDetect → GLX8_2_StartMeasureWithCallback 启动采集
|
||||
* SDK回调 → BatchOneTimeCallback → ProcessBatchData → 逐行转换并回调上层
|
||||
* StopDetect → GLX8_2_StopMeasure 停止采集
|
||||
* CloseDevice → GLX8_2_CommClose 关闭设备
|
||||
*
|
||||
* 数据转换:gl_linelaser_sdk 的 int32_t 轮廓数据(单位0.01um)转换为
|
||||
* SVzLaserLineData 的 SVzNL3DPosition 格式(单位mm)。
|
||||
*/
|
||||
@ -100,7 +106,7 @@ private:
|
||||
// 批处理参数
|
||||
unsigned int m_nBatchLines = 200; // 批处理行数
|
||||
|
||||
// 回调相关
|
||||
// 上层回调相关
|
||||
VzNL_AutoOutputLaserLineExCB m_pDetectCallback = nullptr;
|
||||
void* m_pDetectCallbackParam = nullptr;
|
||||
EVzResultDataType m_eDataType = keResultDataType_Position;
|
||||
@ -108,42 +114,40 @@ private:
|
||||
VzNL_OnNotifyStatusCBEx m_pStatusCallback = nullptr;
|
||||
void* m_pStatusCallbackParam = nullptr;
|
||||
|
||||
// 数据采集线程
|
||||
std::thread m_detectThread;
|
||||
// 检测状态
|
||||
std::atomic<bool> m_bDetecting{false};
|
||||
std::atomic<bool> m_bStopDetect{false};
|
||||
std::mutex m_detectMutex;
|
||||
std::condition_variable m_detectCondition;
|
||||
|
||||
// 数据缓存
|
||||
std::vector<int32_t> m_profileBuffer; // 轮廓数据缓存
|
||||
std::vector<uint8_t> m_intensityBuffer; // 亮度数据缓存
|
||||
std::vector<SVzNL3DPosition> m_positionBuffer; // 转换后的位置数据
|
||||
|
||||
// 帧计数
|
||||
unsigned long long m_ullFrameIndex = 0;
|
||||
unsigned long long m_ullTimeStamp = 0;
|
||||
|
||||
// 内部方法
|
||||
|
||||
/**
|
||||
* @brief 数据采集线程函数(主动轮询模式,参考 test_deal_atch_datas)
|
||||
* @brief 配置批处理模式
|
||||
*/
|
||||
void DetectThreadFunc();
|
||||
int ConfigureBatchMode();
|
||||
|
||||
/**
|
||||
* @brief 获取批处理数据(参考 test_batch_datas 的流程)
|
||||
* @brief 注册SDK批处理回调
|
||||
*/
|
||||
void GetBatchData();
|
||||
int RegisterBatchCallback();
|
||||
|
||||
/**
|
||||
* @brief 将 gl_linelaser_sdk 的轮廓数据转换为 SVzNL3DPosition
|
||||
* @param profileData 轮廓数据(int32_t,单位0.01um)
|
||||
* @param count 数据点数
|
||||
* @param lineIndex 当前行索引
|
||||
* @return 转换后的位置数据数组
|
||||
* @brief SDK 批处理回调函数(静态,由SDK线程调用)
|
||||
* @param info 批处理信息
|
||||
* @param DataObj 批处理数据对象,用于提取轮廓/亮度/编码器
|
||||
*/
|
||||
void ConvertProfileToPosition(const int32_t* profileData, int count, int lineIndex);
|
||||
static void BatchOneTimeCallback(const GLX8_2_STR_CALLBACK_INFO* info, const GLX8_2_Data DataObj);
|
||||
|
||||
/**
|
||||
* @brief 处理一次批处理回调数据,逐行转换并回调给上层
|
||||
* @param info 批处理信息
|
||||
* @param DataObj 批处理数据对象
|
||||
*/
|
||||
void ProcessBatchData(const GLX8_2_STR_CALLBACK_INFO* info, const GLX8_2_Data DataObj);
|
||||
|
||||
// 静态回调需要通过实例指针访问成员,保存this指针供回调使用
|
||||
static CGlLineLaserDevice* s_pInstance;
|
||||
};
|
||||
|
||||
#endif // CGLLINELASERDEVICE_H
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 可用的 App 列表
|
||||
AVAILABLE_APPS="GrabBag BeltTearing LapWeld Workpiece ParticleSize BinocularMark WorkpieceProject TunnelChannel WheelMeasure ScrewPosition WorkpieceHole"
|
||||
AVAILABLE_APPS="GrabBag BeltTearing LapWeld Workpiece ParticleSize BinocularMark WorkpieceProject TunnelChannel WheelMeasure ScrewPosition WorkpieceHole BagThreadPosition"
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
|
||||
410
GrabBagPrj/pkg_bagthreadposition.sh
Normal file
410
GrabBagPrj/pkg_bagthreadposition.sh
Normal file
@ -0,0 +1,410 @@
|
||||
#!/bin/bash
|
||||
|
||||
#BagThreadPosition 版本配置
|
||||
PKG_NAME="BagThreadPosition"
|
||||
PKG_ARCH="arm64"
|
||||
|
||||
# 从Version.h文件中读取版本信息
|
||||
VERSION_FILE="../App/BagThreadPosition/BagThreadPositionApp/Version.h"
|
||||
|
||||
if [ -f "${VERSION_FILE}" ]; then
|
||||
# 读取版本号(从 BAGTHREADPOSITION_VERSION_STRING 中提取)
|
||||
PKG_VERSION=$(grep '#define BAGTHREADPOSITION_VERSION_STRING' ${VERSION_FILE} | sed 's/.*"\(.*\)".*/\1/')
|
||||
# 读取构建号(从 BAGTHREADPOSITION_BUILD_STRING 中提取)
|
||||
BUILD_NUMBER=$(grep '#define BAGTHREADPOSITION_BUILD_STRING' ${VERSION_FILE} | sed 's/.*"\(.*\)".*/\1/')
|
||||
|
||||
echo "从 ${VERSION_FILE} 读取版本信息:"
|
||||
echo " 版本号: ${PKG_VERSION}"
|
||||
echo " 构建号: ${BUILD_NUMBER}"
|
||||
|
||||
# 如果读取失败,使用默认值
|
||||
if [ -z "${PKG_VERSION}" ]; then
|
||||
PKG_VERSION="1.0.0"
|
||||
echo "警告: 无法读取版本号,使用默认值: ${PKG_VERSION}"
|
||||
fi
|
||||
|
||||
if [ -z "${BUILD_NUMBER}" ]; then
|
||||
BUILD_NUMBER="1"
|
||||
echo "警告: 无法读取构建号,使用默认值: ${BUILD_NUMBER}"
|
||||
fi
|
||||
else
|
||||
# Version.h文件不存在时的默认值
|
||||
PKG_VERSION="1.0.0"
|
||||
BUILD_NUMBER="1"
|
||||
echo "警告: ${VERSION_FILE} 文件不存在,使用默认版本信息"
|
||||
echo " 版本号: ${PKG_VERSION}"
|
||||
echo " 构建号: ${BUILD_NUMBER}"
|
||||
fi
|
||||
|
||||
PKG_PATH=$HOME/BagThreadPositionPkg
|
||||
CODE_PATH=../
|
||||
RELEASE_PATH=../Publish
|
||||
|
||||
echo "=========================================="
|
||||
echo "开始打包 BagThreadPosition 应用程序 v${PKG_VERSION}..."
|
||||
echo "=========================================="
|
||||
|
||||
echo "清理所有旧的打包目录..."
|
||||
rm -rf $HOME/*Pkg
|
||||
|
||||
#QT depend
|
||||
QT_PKG_PATH=/opt/firefly_qt5.15_arm64_20.04
|
||||
QT_LIB_PATH=/opt/sysroot/firefly-arm64-sysroot-20.04/lib/aarch64-linux-gnu
|
||||
|
||||
echo "创建打包目录结构..."
|
||||
mkdir -p ${PKG_PATH}/DEBIAN
|
||||
mkdir -p ${PKG_PATH}/etc/profile.d
|
||||
mkdir -p ${PKG_PATH}/etc/xdg/autostart
|
||||
mkdir -p ${PKG_PATH}/opt/sysroot/lib
|
||||
mkdir -p ${PKG_PATH}/usr/local/bin
|
||||
mkdir -p ${PKG_PATH}/usr/lib
|
||||
mkdir -p ${PKG_PATH}/usr/share/applications
|
||||
mkdir -p ${PKG_PATH}/usr/share/pixmaps
|
||||
|
||||
echo "复制 Qt 运行时环境..."
|
||||
cp -rfd ${QT_PKG_PATH}/ext ${PKG_PATH}/opt/firefly_qt5.15
|
||||
cp ${QT_PKG_PATH}/target_qtEnv.sh ${PKG_PATH}/etc/profile.d/
|
||||
|
||||
# 复制 Qt 库文件
|
||||
for libfile in ${QT_LIB_PATH}/*.so*; do
|
||||
# 获取文件名用于比较
|
||||
filename=$(basename "$libfile")
|
||||
|
||||
# 跳过 LLVM、flite、clang 和 X11 相关库文件
|
||||
if [[ "$filename" == *icu* ]]; then
|
||||
# 复制其他库文件,保持符号链接
|
||||
cp -rfd "$libfile" ${PKG_PATH}/opt/sysroot/lib/
|
||||
continue
|
||||
fi
|
||||
done
|
||||
|
||||
echo "复制依赖库文件..."
|
||||
#depend
|
||||
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_core*.so* ${PKG_PATH}/usr/lib/
|
||||
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_imgproc*.so* ${PKG_PATH}/usr/lib/
|
||||
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_highgui*.so* ${PKG_PATH}/usr/lib/
|
||||
|
||||
cp ${CODE_PATH}/AppAlgo/bagThreadPositioning/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
|
||||
cp ${CODE_PATH}/SDK/Device/VzNLSDK/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
|
||||
cp ${CODE_PATH}/SDK/Device/gl_linelaser_sdk/aarch64_linux/*.so ${PKG_PATH}/usr/lib/
|
||||
|
||||
echo "复制应用程序主文件..."
|
||||
#APP
|
||||
cp ${CODE_PATH}/GrabBagPrj/buildarm/App/BagThreadPosition/BagThreadPositionApp/BagThreadPositionApp ${PKG_PATH}/usr/local/bin/
|
||||
|
||||
echo "复制应用程序图标..."
|
||||
#LOGO
|
||||
cp ${CODE_PATH}/App/BagThreadPosition/BagThreadPositionApp/resource/logo.png ${PKG_PATH}/usr/share/pixmaps/bagthreadposition.png
|
||||
|
||||
echo "生成桌面自启动配置文件..."
|
||||
#desktop autostart configuration
|
||||
AUTOSTART_PATH=${PKG_PATH}/etc/xdg/autostart/bagthreadposition.desktop
|
||||
cat > ${AUTOSTART_PATH} << EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=BagThreadPosition
|
||||
Comment=BagThreadPosition Application Auto Start
|
||||
Exec=/usr/local/bin/BagThreadPositionApp
|
||||
Icon=/usr/share/pixmaps/bagthreadposition.png
|
||||
Hidden=false
|
||||
NoDisplay=false
|
||||
X-GNOME-Autostart-enabled=true
|
||||
AutostartCondition=GNOME3 unless-session gnome
|
||||
EOF
|
||||
|
||||
echo "生成 control 文件..."
|
||||
#control
|
||||
CONTROL_PATH=${PKG_PATH}/DEBIAN/control
|
||||
echo "Package: ${PKG_NAME}" > ${CONTROL_PATH}
|
||||
echo "Version: ${PKG_VERSION}" >> ${CONTROL_PATH}
|
||||
echo "Section: bagthreadpositionapp" >> ${CONTROL_PATH}
|
||||
echo "Architecture: ${PKG_ARCH}" >> ${CONTROL_PATH}
|
||||
echo "Priority: optional" >> ${CONTROL_PATH}
|
||||
echo "Maintainer: BagThreadPosition Team <support@bagthreadposition.com>" >> ${CONTROL_PATH}
|
||||
echo "Description: bagthreadposition app" >> ${CONTROL_PATH}
|
||||
|
||||
|
||||
|
||||
echo "生成安装后脚本..."
|
||||
#postinst install exec script
|
||||
POSTINST_PATH=${PKG_PATH}/DEBIAN/postinst
|
||||
cat > ${POSTINST_PATH} << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
echo "配置 BagThreadPosition 应用程序..."
|
||||
|
||||
# 创建应用注册目录和文件
|
||||
REGISTRY_DIR="/var/lib/vr-apps"
|
||||
REGISTRY_FILE="${REGISTRY_DIR}/installed-apps.list"
|
||||
APP_NAME="BagThreadPosition"
|
||||
|
||||
mkdir -p "${REGISTRY_DIR}"
|
||||
touch "${REGISTRY_FILE}"
|
||||
|
||||
# 检查是否是首次安装VR应用
|
||||
FIRST_INSTALL=false
|
||||
if [ ! -s "${REGISTRY_FILE}" ]; then
|
||||
FIRST_INSTALL=true
|
||||
echo "检测到首次安装VR应用,将安装公共资源..."
|
||||
fi
|
||||
|
||||
# 设置库文件路径(每个应用独立配置)
|
||||
echo "/usr/lib" > /etc/ld.so.conf.d/bagthreadposition.conf
|
||||
echo "/opt/sysroot/lib/" >> /etc/ld.so.conf.d/bagthreadposition.conf
|
||||
|
||||
# 如果不是首次安装,公共库已经配置过,只需要更新
|
||||
if [ "${FIRST_INSTALL}" = true ]; then
|
||||
echo "配置公共库路径..."
|
||||
ldconfig
|
||||
else
|
||||
echo "公共库已存在,仅更新配置..."
|
||||
ldconfig
|
||||
fi
|
||||
|
||||
# 确保应用程序可执行
|
||||
chmod +x /usr/local/bin/BagThreadPositionApp
|
||||
|
||||
# 配置端口映射 502 -> 5020
|
||||
echo "配置端口映射 502 -> 5020..."
|
||||
|
||||
# 检查并创建systemd服务确保重启后规则生效
|
||||
if [ ! -f /etc/systemd/system/vr-port-mapping.service ]; then
|
||||
echo "创建端口映射服务..."
|
||||
cat > /etc/systemd/system/vr-port-mapping.service << 'PORTEOF'
|
||||
[Unit]
|
||||
Description=VR Common Port Mapping Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c 'iptables-legacy -t nat -A PREROUTING -p tcp -m tcp --dport 502 -j REDIRECT --to-port 5020'
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
PORTEOF
|
||||
|
||||
systemctl enable vr-port-mapping.service
|
||||
systemctl start vr-port-mapping.service
|
||||
else
|
||||
echo "端口映射服务已存在,跳过创建..."
|
||||
# 确保服务处于启用状态
|
||||
systemctl enable vr-port-mapping.service 2>/dev/null || true
|
||||
systemctl start vr-port-mapping.service 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 检查iptables-legacy规则是否已存在
|
||||
if ! iptables-legacy -t nat -C PREROUTING -p tcp -m tcp --dport 502 -j REDIRECT --to-port 5020 2>/dev/null; then
|
||||
echo "添加iptables-legacy端口映射规则..."
|
||||
iptables-legacy -t nat -A PREROUTING -p tcp -m tcp --dport 502 -j REDIRECT --to-port 5020
|
||||
else
|
||||
echo "iptables-legacy规则已存在,跳过添加..."
|
||||
fi
|
||||
|
||||
# 检查并创建当前用户的桌面快捷方式
|
||||
echo "检查当前用户的桌面快捷方式..."
|
||||
|
||||
# 获取当前执行安装的用户信息
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
# 如果是通过sudo执行的,获取真实用户
|
||||
current_user="$SUDO_USER"
|
||||
current_home=$(getent passwd "$current_user" | cut -d: -f6)
|
||||
else
|
||||
# 直接执行的情况
|
||||
current_user=$(whoami)
|
||||
current_home="$HOME"
|
||||
fi
|
||||
|
||||
echo "当前用户: $current_user"
|
||||
echo "用户主目录: $current_home"
|
||||
|
||||
# 检查多种可能的桌面目录名称
|
||||
desktop_dirs=("Desktop" "桌面" "desktop")
|
||||
desktop_dir=""
|
||||
desktop_shortcut=""
|
||||
|
||||
for dir_name in "${desktop_dirs[@]}"; do
|
||||
potential_dir="$current_home/$dir_name"
|
||||
if [ -d "$potential_dir" ]; then
|
||||
desktop_dir="$potential_dir"
|
||||
desktop_shortcut="$desktop_dir/bagthreadposition.desktop"
|
||||
echo "找到桌面目录: $desktop_dir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查是否找到桌面目录
|
||||
if [ -n "$desktop_dir" ]; then
|
||||
# 检查桌面上是否已有快捷方式
|
||||
if [ ! -f "$desktop_shortcut" ]; then
|
||||
echo "为当前用户创建桌面快捷方式..."
|
||||
# 复制桌面文件到用户桌面
|
||||
cp /usr/share/applications/bagthreadposition.desktop "$desktop_shortcut"
|
||||
# 设置正确的所有者和权限
|
||||
chown $current_user:$current_user "$desktop_shortcut" 2>/dev/null || true
|
||||
chmod 755 "$desktop_shortcut"
|
||||
echo "已创建桌面快捷方式: $desktop_shortcut"
|
||||
else
|
||||
echo "桌面快捷方式已存在,跳过创建"
|
||||
fi
|
||||
else
|
||||
echo "当前用户没有找到桌面目录(Desktop/桌面/desktop),跳过桌面快捷方式创建"
|
||||
fi
|
||||
|
||||
# 注册应用到系统
|
||||
if ! grep -q "^${APP_NAME}$" "${REGISTRY_FILE}"; then
|
||||
echo "${APP_NAME}" >> "${REGISTRY_FILE}"
|
||||
echo "应用 ${APP_NAME} 已注册到系统"
|
||||
else
|
||||
echo "应用 ${APP_NAME} 已经注册,跳过..."
|
||||
fi
|
||||
|
||||
echo "BagThreadPosition 应用程序安装完成!"
|
||||
echo "应用程序将在用户登录桌面后自动启动。"
|
||||
echo "端口映射已配置:502 -> 5020"
|
||||
echo "桌面快捷方式已创建(如果用户有Desktop目录)"
|
||||
echo "如需立即启动,请运行: /usr/local/bin/BagThreadPositionApp"
|
||||
echo "如需禁用自启动,请删除文件: ~/.config/autostart/bagthreadposition.desktop"
|
||||
echo "已安装的VR应用: $(cat ${REGISTRY_FILE} | tr '\n' ' ')"
|
||||
EOF
|
||||
|
||||
chmod +x ${POSTINST_PATH}
|
||||
|
||||
echo "生成卸载脚本..."
|
||||
#postrm uninstall exec script
|
||||
POSTRM_PATH=${PKG_PATH}/DEBIAN/postrm
|
||||
cat > ${POSTRM_PATH} << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
echo "卸载 BagThreadPosition 应用程序..."
|
||||
|
||||
# 应用注册信息
|
||||
REGISTRY_DIR="/var/lib/vr-apps"
|
||||
REGISTRY_FILE="${REGISTRY_DIR}/installed-apps.list"
|
||||
APP_NAME="BagThreadPosition"
|
||||
|
||||
# 从注册表中移除应用
|
||||
if [ -f "${REGISTRY_FILE}" ]; then
|
||||
sed -i "/^${APP_NAME}$/d" "${REGISTRY_FILE}"
|
||||
echo "应用 ${APP_NAME} 已从系统注册表中移除"
|
||||
fi
|
||||
|
||||
# 清理应用专属的库文件配置
|
||||
rm -f /etc/ld.so.conf.d/bagthreadposition.conf
|
||||
|
||||
# 检查是否还有其他VR应用在使用公共资源
|
||||
REMAINING_APPS=0
|
||||
if [ -f "${REGISTRY_FILE}" ]; then
|
||||
REMAINING_APPS=$(grep -c . "${REGISTRY_FILE}" 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
echo "系统中剩余的VR应用数量: ${REMAINING_APPS}"
|
||||
|
||||
# 如果没有其他应用了,清理公共资源
|
||||
if [ "${REMAINING_APPS}" -eq 0 ]; then
|
||||
echo "没有其他VR应用,开始清理公共资源..."
|
||||
|
||||
# 清理端口映射配置
|
||||
echo "清理端口映射配置..."
|
||||
systemctl stop vr-port-mapping.service 2>/dev/null || true
|
||||
systemctl disable vr-port-mapping.service 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/vr-port-mapping.service
|
||||
|
||||
# 清理iptables-legacy规则
|
||||
iptables-legacy -t nat -D PREROUTING -p tcp -m tcp --dport 502 -j REDIRECT --to-port 5020 2>/dev/null || true
|
||||
|
||||
# 清理注册目录
|
||||
rm -rf "${REGISTRY_DIR}"
|
||||
|
||||
echo "公共资源已清理"
|
||||
else
|
||||
echo "系统中还有 ${REMAINING_APPS} 个VR应用在运行,保留公共资源"
|
||||
if [ -f "${REGISTRY_FILE}" ]; then
|
||||
echo "剩余应用: $(cat ${REGISTRY_FILE} | tr '\n' ' ')"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 更新库缓存
|
||||
ldconfig
|
||||
|
||||
# 重新加载systemd
|
||||
systemctl daemon-reload
|
||||
|
||||
# 清理当前用户的桌面快捷方式
|
||||
echo "清理当前用户的桌面快捷方式..."
|
||||
|
||||
# 获取当前执行卸载的用户信息
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
# 如果是通过sudo执行的,获取真实用户
|
||||
current_user="$SUDO_USER"
|
||||
current_home=$(getent passwd "$current_user" | cut -d: -f6)
|
||||
else
|
||||
# 直接执行的情况
|
||||
current_user=$(whoami)
|
||||
current_home="$HOME"
|
||||
fi
|
||||
|
||||
# 检查多种可能的桌面目录名称并清理快捷方式
|
||||
desktop_dirs=("Desktop" "桌面" "desktop")
|
||||
shortcut_found=false
|
||||
|
||||
for dir_name in "${desktop_dirs[@]}"; do
|
||||
desktop_shortcut="$current_home/$dir_name/bagthreadposition.desktop"
|
||||
if [ -f "$desktop_shortcut" ]; then
|
||||
echo "删除当前用户的桌面快捷方式: $desktop_shortcut"
|
||||
rm -f "$desktop_shortcut"
|
||||
echo "已删除桌面快捷方式: $desktop_shortcut"
|
||||
shortcut_found=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$shortcut_found" = false ]; then
|
||||
echo "当前用户没有找到桌面快捷方式,跳过清理"
|
||||
fi
|
||||
|
||||
echo "BagThreadPosition 应用程序卸载完成!"
|
||||
if [ "${REMAINING_APPS}" -eq 0 ]; then
|
||||
echo "所有VR应用已卸载,公共资源已清理"
|
||||
else
|
||||
echo "公共资源已保留供其他VR应用使用"
|
||||
fi
|
||||
echo "桌面快捷方式已清理"
|
||||
echo "如需彻底清理自启动配置,请手动删除: ~/.config/autostart/bagthreadposition.desktop"
|
||||
EOF
|
||||
|
||||
chmod +x ${POSTRM_PATH}
|
||||
|
||||
echo "生成桌面快捷方式..."
|
||||
#desktop
|
||||
DESKTOP_PATH=${PKG_PATH}/usr/share/applications/bagthreadposition.desktop
|
||||
echo "[Desktop Entry]" > ${DESKTOP_PATH}
|
||||
echo "Version=${PKG_VERSION}" >> ${DESKTOP_PATH}
|
||||
echo "Name=BagThreadPosition" >> ${DESKTOP_PATH}
|
||||
echo "Type=Application" >> ${DESKTOP_PATH}
|
||||
echo "Comment=BagThreadPosition App" >> ${DESKTOP_PATH}
|
||||
echo "Terminal=false" >> ${DESKTOP_PATH}
|
||||
echo "Exec=/usr/local/bin/BagThreadPositionApp" >> ${DESKTOP_PATH}
|
||||
echo "Icon=/usr/share/pixmaps/bagthreadposition.png" >> ${DESKTOP_PATH}
|
||||
echo "Categories=Development;" >> ${DESKTOP_PATH}
|
||||
echo "GenericName=BagThreadPosition App" >> ${DESKTOP_PATH}
|
||||
echo "Keywords=bagthreadposition;app;" >> ${DESKTOP_PATH}
|
||||
echo "StartupNotify=true" >> ${DESKTOP_PATH}
|
||||
|
||||
echo "设置文件权限..."
|
||||
# 设置usr目录权限(不包括DEBIAN)
|
||||
chmod -R 755 ${PKG_PATH}/usr
|
||||
chmod -R 755 ${PKG_PATH}/etc
|
||||
chmod -R 755 ${PKG_PATH}/opt
|
||||
|
||||
echo "开始构建 DEB 包..."
|
||||
# 生成带时间戳和构建号的包文件名
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
DEB_FILENAME="${RELEASE_PATH}/${PKG_NAME}_${PKG_VERSION}_${BUILD_NUMBER}_${PKG_ARCH}_${TIMESTAMP}.deb"
|
||||
|
||||
fakeroot dpkg -b ${PKG_PATH} ${DEB_FILENAME}
|
||||
|
||||
echo "=========================================="
|
||||
echo "打包完成!"
|
||||
echo "生成的包文件: ${DEB_FILENAME}"
|
||||
echo "文件大小: $(ls -lh ${DEB_FILENAME} | awk '{print $5}')"
|
||||
echo "=========================================="
|
||||
@ -85,7 +85,7 @@ cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_imgproc*.so* ${PKG_PATH}/u
|
||||
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_highgui*.so* ${PKG_PATH}/usr/lib/
|
||||
|
||||
cp ${CODE_PATH}/AppAlgo/workpieceHolePositioning/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
|
||||
cp ${CODE_PATH}/SDK/Device/VzNLSDK/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
|
||||
cp ${CODE_PATH}/SDK/Device/VzNLSDK/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
|
||||
|
||||
echo "复制应用程序主文件..."
|
||||
#APP
|
||||
|
||||
@ -43,6 +43,16 @@ private slots:
|
||||
*/
|
||||
void onOpenFile();
|
||||
|
||||
/**
|
||||
* @brief 打开线段文件
|
||||
*/
|
||||
void onOpenSegmentFile();
|
||||
|
||||
/**
|
||||
* @brief 打开姿态点文件
|
||||
*/
|
||||
void onOpenPoseFile();
|
||||
|
||||
/**
|
||||
* @brief 清除所有点云
|
||||
*/
|
||||
@ -98,6 +108,26 @@ private slots:
|
||||
*/
|
||||
void onLinePointTableClicked(int row, int column);
|
||||
|
||||
/**
|
||||
* @brief 显示点1的姿态
|
||||
*/
|
||||
void onShowPose1();
|
||||
|
||||
/**
|
||||
* @brief 显示点2的姿态
|
||||
*/
|
||||
void onShowPose2();
|
||||
|
||||
/**
|
||||
* @brief 显示输入的线段
|
||||
*/
|
||||
void onShowInputLine();
|
||||
|
||||
/**
|
||||
* @brief 清除输入的线段
|
||||
*/
|
||||
void onClearInputLine();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 初始化界面
|
||||
@ -124,6 +154,16 @@ private:
|
||||
*/
|
||||
QGroupBox* createMeasureGroup();
|
||||
|
||||
/**
|
||||
* @brief 创建选点测距页面
|
||||
*/
|
||||
QWidget* createMeasurePage();
|
||||
|
||||
/**
|
||||
* @brief 创建选线页面
|
||||
*/
|
||||
QWidget* createLinePage();
|
||||
|
||||
/**
|
||||
* @brief 创建选线拟合组
|
||||
*/
|
||||
@ -157,6 +197,8 @@ private:
|
||||
|
||||
// 文件操作控件
|
||||
QPushButton* m_btnOpenFile;
|
||||
QPushButton* m_btnOpenSegment;
|
||||
QPushButton* m_btnOpenPose;
|
||||
QPushButton* m_btnClearAll;
|
||||
QPushButton* m_btnResetView;
|
||||
|
||||
@ -167,6 +209,18 @@ private:
|
||||
QLabel* m_lblPoint2;
|
||||
QLabel* m_lblDistance;
|
||||
|
||||
// 点1姿态输入控件
|
||||
QLineEdit* m_editRx1;
|
||||
QLineEdit* m_editRy1;
|
||||
QLineEdit* m_editRz1;
|
||||
QPushButton* m_btnShowPose1;
|
||||
|
||||
// 点2姿态输入控件
|
||||
QLineEdit* m_editRx2;
|
||||
QLineEdit* m_editRy2;
|
||||
QLineEdit* m_editRz2;
|
||||
QPushButton* m_btnShowPose2;
|
||||
|
||||
// 选线拟合控件
|
||||
QPushButton* m_btnClearLine;
|
||||
QPushButton* m_btnShowLinePoints;
|
||||
@ -177,6 +231,16 @@ private:
|
||||
QLabel* m_lblLineIndex;
|
||||
QLabel* m_lblLinePointCount;
|
||||
|
||||
// 输入线段控件
|
||||
QLineEdit* m_editLineX1;
|
||||
QLineEdit* m_editLineY1;
|
||||
QLineEdit* m_editLineZ1;
|
||||
QLineEdit* m_editLineX2;
|
||||
QLineEdit* m_editLineY2;
|
||||
QLineEdit* m_editLineZ2;
|
||||
QPushButton* m_btnShowLine;
|
||||
QPushButton* m_btnClearLine2;
|
||||
|
||||
// 点云列表
|
||||
QListWidget* m_cloudList;
|
||||
|
||||
|
||||
@ -65,6 +65,34 @@ struct SelectedLineInfo
|
||||
SelectedLineInfo() : valid(false), cloudIndex(-1), lineIndex(-1), pointIndex(-1), pointCount(0), mode(LineSelectMode::Vertical) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 线段数据
|
||||
*/
|
||||
struct LineSegment
|
||||
{
|
||||
float x1, y1, z1; // 起点
|
||||
float x2, y2, z2; // 终点
|
||||
float r, g, b; // 颜色 (0-1)
|
||||
|
||||
LineSegment() : x1(0), y1(0), z1(0), x2(0), y2(0), z2(0), r(1), g(1), b(1) {}
|
||||
LineSegment(float _x1, float _y1, float _z1, float _x2, float _y2, float _z2, float _r = 1.0f, float _g = 1.0f, float _b = 1.0f)
|
||||
: x1(_x1), y1(_y1), z1(_z1), x2(_x2), y2(_y2), z2(_z2), r(_r), g(_g), b(_b) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 姿态点数据
|
||||
*/
|
||||
struct PosePoint
|
||||
{
|
||||
float x, y, z; // 位置
|
||||
float rx, ry, rz; // 欧拉角(度)
|
||||
float scale; // 坐标系大小
|
||||
|
||||
PosePoint() : x(0), y(0), z(0), rx(0), ry(0), rz(0), scale(10.0f) {}
|
||||
PosePoint(float _x, float _y, float _z, float _rx, float _ry, float _rz, float _scale = 10.0f)
|
||||
: x(_x), y(_y), z(_z), rx(_rx), ry(_ry), rz(_rz), scale(_scale) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 点云 OpenGL 渲染控件
|
||||
*/
|
||||
@ -145,6 +173,26 @@ public:
|
||||
*/
|
||||
size_t getCloudCount() const { return m_pointClouds.size(); }
|
||||
|
||||
/**
|
||||
* @brief 添加线段
|
||||
*/
|
||||
void addLineSegments(const QVector<LineSegment>& segments);
|
||||
|
||||
/**
|
||||
* @brief 清除所有线段
|
||||
*/
|
||||
void clearLineSegments();
|
||||
|
||||
/**
|
||||
* @brief 添加姿态点
|
||||
*/
|
||||
void addPosePoints(const QVector<PosePoint>& poses);
|
||||
|
||||
/**
|
||||
* @brief 清除所有姿态点
|
||||
*/
|
||||
void clearPosePoints();
|
||||
|
||||
signals:
|
||||
void pointSelected(const SelectedPointInfo& point);
|
||||
void twoPointsSelected(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance);
|
||||
@ -168,6 +216,8 @@ private:
|
||||
void drawMeasurementLine();
|
||||
void drawAxis();
|
||||
void drawSelectedLine(); // 绘制选中的线
|
||||
void drawLineSegments(); // 绘制线段
|
||||
void drawPosePoints(); // 绘制姿态点
|
||||
|
||||
struct PointCloudData
|
||||
{
|
||||
@ -216,6 +266,10 @@ private:
|
||||
|
||||
int m_colorIndex; // 颜色轮换索引
|
||||
static const int COLOR_COUNT = 7; // 可用颜色数量
|
||||
|
||||
// 线段和姿态点数据
|
||||
QVector<LineSegment> m_lineSegments;
|
||||
QVector<PosePoint> m_posePoints;
|
||||
};
|
||||
|
||||
#endif // POINT_CLOUD_GL_WIDGET_H
|
||||
|
||||
@ -5,6 +5,12 @@
|
||||
#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)
|
||||
@ -81,7 +87,7 @@ QWidget* CloudViewMainWindow::createViewerArea()
|
||||
QWidget* CloudViewMainWindow::createControlPanel()
|
||||
{
|
||||
QWidget* widget = new QWidget(this);
|
||||
widget->setMaximumWidth(300);
|
||||
widget->setMaximumWidth(400); // 加宽到400
|
||||
QVBoxLayout* layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(5, 5, 5, 5);
|
||||
layout->setSpacing(10);
|
||||
@ -89,11 +95,11 @@ QWidget* CloudViewMainWindow::createControlPanel()
|
||||
// 文件操作组
|
||||
layout->addWidget(createFileGroup());
|
||||
|
||||
// 选点测距组
|
||||
layout->addWidget(createMeasureGroup());
|
||||
|
||||
// 选线拟合组
|
||||
layout->addWidget(createLineGroup());
|
||||
// 创建 Tab 控件
|
||||
QTabWidget* tabWidget = new QTabWidget(widget);
|
||||
tabWidget->addTab(createMeasurePage(), "选点测距");
|
||||
tabWidget->addTab(createLinePage(), "选线");
|
||||
layout->addWidget(tabWidget);
|
||||
|
||||
// 点云列表组
|
||||
layout->addWidget(createCloudListGroup());
|
||||
@ -107,14 +113,22 @@ QWidget* CloudViewMainWindow::createControlPanel()
|
||||
QGroupBox* CloudViewMainWindow::createFileGroup()
|
||||
{
|
||||
QGroupBox* group = new QGroupBox("文件操作", this);
|
||||
group->setMaximumWidth(300);
|
||||
group->setMaximumWidth(400);
|
||||
QVBoxLayout* layout = new QVBoxLayout(group);
|
||||
|
||||
m_btnOpenFile = new QPushButton("打开文件", 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);
|
||||
@ -126,10 +140,98 @@ QGroupBox* CloudViewMainWindow::createFileGroup()
|
||||
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(300);
|
||||
group->setMaximumWidth(400);
|
||||
QVBoxLayout* layout = new QVBoxLayout(group);
|
||||
|
||||
// 操作说明
|
||||
@ -156,23 +258,91 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup()
|
||||
connect(m_btnClearPoints, &QPushButton::clicked, this, &CloudViewMainWindow::onClearSelectedPoints);
|
||||
layout->addWidget(m_btnClearPoints);
|
||||
|
||||
// 点1信息
|
||||
QHBoxLayout* point1Layout = new QHBoxLayout();
|
||||
// 分隔线
|
||||
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);
|
||||
point1Layout->addWidget(lblPoint1Title);
|
||||
point1Layout->addWidget(m_lblPoint1, 1);
|
||||
layout->addLayout(point1Layout);
|
||||
layout->addWidget(m_lblPoint1);
|
||||
|
||||
// 点2信息
|
||||
QHBoxLayout* point2Layout = new QHBoxLayout();
|
||||
// 点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);
|
||||
point2Layout->addWidget(lblPoint2Title);
|
||||
point2Layout->addWidget(m_lblPoint2, 1);
|
||||
layout->addLayout(point2Layout);
|
||||
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();
|
||||
@ -189,7 +359,7 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup()
|
||||
QGroupBox* CloudViewMainWindow::createLineGroup()
|
||||
{
|
||||
QGroupBox* group = new QGroupBox("选线", this);
|
||||
group->setMaximumWidth(300);
|
||||
group->setMaximumWidth(400);
|
||||
QVBoxLayout* layout = new QVBoxLayout(group);
|
||||
|
||||
// 操作说明
|
||||
@ -250,7 +420,7 @@ QGroupBox* CloudViewMainWindow::createLineGroup()
|
||||
QGroupBox* CloudViewMainWindow::createCloudListGroup()
|
||||
{
|
||||
QGroupBox* group = new QGroupBox("点云列表", this);
|
||||
group->setMaximumWidth(300);
|
||||
group->setMaximumWidth(400);
|
||||
QVBoxLayout* layout = new QVBoxLayout(group);
|
||||
|
||||
m_cloudList = new QListWidget(group);
|
||||
@ -317,6 +487,168 @@ void CloudViewMainWindow::onOpenFile()
|
||||
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();
|
||||
@ -335,7 +667,7 @@ void CloudViewMainWindow::onClearAll()
|
||||
m_lblLineIndex->setText("--");
|
||||
m_lblLinePointCount->setText("--");
|
||||
|
||||
statusBar()->showMessage("已清除所有点云");
|
||||
statusBar()->showMessage("已清除所有数据");
|
||||
}
|
||||
|
||||
void CloudViewMainWindow::onResetView()
|
||||
@ -347,6 +679,7 @@ void CloudViewMainWindow::onResetView()
|
||||
void CloudViewMainWindow::onClearSelectedPoints()
|
||||
{
|
||||
m_glWidget->clearSelectedPoints();
|
||||
m_glWidget->clearPosePoints(); // 清除选点时也清除姿态
|
||||
m_lblPoint1->setText("--");
|
||||
m_lblPoint2->setText("--");
|
||||
m_lblDistance->setText("--");
|
||||
@ -359,6 +692,9 @@ void CloudViewMainWindow::onPointSelected(const SelectedPointInfo& point)
|
||||
return;
|
||||
}
|
||||
|
||||
// 选择新点时清除之前的姿态显示
|
||||
m_glWidget->clearPosePoints();
|
||||
|
||||
updateSelectedPointsDisplay();
|
||||
|
||||
// 状态栏显示:坐标、线号、索引号
|
||||
@ -705,3 +1041,205 @@ void CloudViewMainWindow::onLinePointTableClicked(int row, int column)
|
||||
}
|
||||
}
|
||||
|
||||
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("已清除输入的线段");
|
||||
}
|
||||
|
||||
|
||||
@ -116,6 +116,8 @@ void PointCloudGLWidget::paintGL()
|
||||
drawSelectedPoints();
|
||||
drawMeasurementLine();
|
||||
drawSelectedLine();
|
||||
drawLineSegments();
|
||||
drawPosePoints();
|
||||
|
||||
// 最后绘制坐标系指示器(覆盖在所有内容之上)
|
||||
drawAxis();
|
||||
@ -248,6 +250,8 @@ void PointCloudGLWidget::clearPointClouds()
|
||||
m_pointClouds.clear();
|
||||
m_selectedPoints.clear();
|
||||
m_selectedLine = SelectedLineInfo();
|
||||
m_lineSegments.clear();
|
||||
m_posePoints.clear();
|
||||
m_minBound = QVector3D(-50, -50, -50);
|
||||
m_maxBound = QVector3D(50, 50, 50);
|
||||
m_center = QVector3D(0, 0, 0);
|
||||
@ -978,3 +982,91 @@ void PointCloudGLWidget::replaceFirstCloud(const PointCloudXYZ& cloud, const QSt
|
||||
resetView();
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::addLineSegments(const QVector<LineSegment>& segments)
|
||||
{
|
||||
m_lineSegments.append(segments);
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::clearLineSegments()
|
||||
{
|
||||
m_lineSegments.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::addPosePoints(const QVector<PosePoint>& poses)
|
||||
{
|
||||
m_posePoints.append(poses);
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::clearPosePoints()
|
||||
{
|
||||
m_posePoints.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::drawLineSegments()
|
||||
{
|
||||
if (m_lineSegments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
glLineWidth(2.0f);
|
||||
glBegin(GL_LINES);
|
||||
|
||||
for (const auto& seg : m_lineSegments) {
|
||||
glColor3f(seg.r, seg.g, seg.b);
|
||||
glVertex3f(seg.x1, seg.y1, seg.z1);
|
||||
glVertex3f(seg.x2, seg.y2, seg.z2);
|
||||
}
|
||||
|
||||
glEnd();
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::drawPosePoints()
|
||||
{
|
||||
if (m_posePoints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
glLineWidth(2.0f);
|
||||
|
||||
for (const auto& pose : m_posePoints) {
|
||||
glPushMatrix();
|
||||
|
||||
// 移动到姿态点位置
|
||||
glTranslatef(pose.x, pose.y, pose.z);
|
||||
|
||||
// 应用欧拉角旋转(ZYX顺序)
|
||||
glRotatef(pose.rz, 0, 0, 1); // Z轴旋转
|
||||
glRotatef(pose.ry, 0, 1, 0); // Y轴旋转
|
||||
glRotatef(pose.rx, 1, 0, 0); // X轴旋转
|
||||
|
||||
// 绘制坐标系
|
||||
glBegin(GL_LINES);
|
||||
|
||||
// X轴 - 红色
|
||||
glColor3f(1.0f, 0.0f, 0.0f);
|
||||
glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
glVertex3f(pose.scale, 0.0f, 0.0f);
|
||||
|
||||
// Y轴 - 绿色
|
||||
glColor3f(0.0f, 1.0f, 0.0f);
|
||||
glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
glVertex3f(0.0f, pose.scale, 0.0f);
|
||||
|
||||
// Z轴 - 蓝色
|
||||
glColor3f(0.0f, 0.0f, 1.0f);
|
||||
glVertex3f(0.0f, 0.0f, 0.0f);
|
||||
glVertex3f(0.0f, 0.0f, pose.scale);
|
||||
|
||||
glEnd();
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
17
Tools/CloudView/poses.txt
Normal file
17
Tools/CloudView/poses.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# 姿态点示例
|
||||
# 格式:{x,y,z}-{r,p,y}
|
||||
|
||||
# 原点
|
||||
{0,0,0}-{0,0,0}
|
||||
|
||||
# X轴上
|
||||
{100,0,0}-{0,0,45}
|
||||
|
||||
# Y轴上
|
||||
{0,100,0}-{0,0,90}
|
||||
|
||||
# Z轴上
|
||||
{0,0,100}-{0,45,0}
|
||||
|
||||
# 对角线
|
||||
{50,50,50}-{30,30,30}
|
||||
23
Tools/CloudView/segments.txt
Normal file
23
Tools/CloudView/segments.txt
Normal file
@ -0,0 +1,23 @@
|
||||
# 线段示例
|
||||
# 格式:{x,y,z}-{x,y,z}
|
||||
|
||||
# 坐标轴
|
||||
{0,0,0}-{100,0,0}
|
||||
{0,0,0}-{0,100,0}
|
||||
{0,0,0}-{0,0,100}
|
||||
|
||||
# 立方体
|
||||
{50,50,50}-{150,50,50}
|
||||
{150,50,50}-{150,150,50}
|
||||
{150,150,50}-{50,150,50}
|
||||
{50,150,50}-{50,50,50}
|
||||
|
||||
{50,50,150}-{150,50,150}
|
||||
{150,50,150}-{150,150,150}
|
||||
{150,150,150}-{50,150,150}
|
||||
{50,150,150}-{50,50,150}
|
||||
|
||||
{50,50,50}-{50,50,150}
|
||||
{150,50,50}-{150,50,150}
|
||||
{150,150,50}-{150,150,150}
|
||||
{50,150,50}-{50,150,150}
|
||||
Loading…
x
Reference in New Issue
Block a user