Lv.的博客

UE4嵌入Qt5 三维可视化案例

 

该项目为中铁客户端(QT部分),采用QT5.7X64编译,适用于windows server 2016及以上系统。该项目主要包括:数据可视化、Socket通讯、内嵌ue4可执行程序三大方面
一:数据可视化

数据可视化即采用图形图表等第三方库对采集的数据进行动态展示,可以非常直观的查看传感器采集到的数据。这部分主要采用了两大图形图标库:QT自带的库:QtCharts和第三方库:QCustomPlot。

1.1:QtCharts

QtCharts由于是qt自带的使用也是比较广泛的图形展示库,本项目中将它封装在statisticalwindow类中。我们在使用它时应该注意三点

①在.pro文件中添加:

QT += charts;

②添加自己需要的头文件:

#include <QtCharts/QChartView>
#include <QtCharts/QPieSeries>
#include <QtCharts/QPieSlice>
//根据需要自行添加

③在代码前声明命名空间:

using namespace QtCharts;

示例代码:

void StatisticalWindow::Creatpie_chart()
{
//构建QPieSeries作为饼图为其添加3个切片QPieSlice数据源
QPieSeries *pieSeries = new QPieSeries();
//设置中心圆大小
pieSeries->setHoleSize(0.35);
//添加数据
QPieSlice *slice1=pieSeries->append(QString::fromLocal8Bit("正常个数92个"), 40);
slice1->setColor(QColor(0,226,255,150));//设置背景颜色
slice1->setLabelColor(QColor(0,226,255,255));//标签颜色
slice1->setBorderColor(QColor(255,58,93,90));//边框颜色
slice1->setLabelVisible(true);//设置标签可见
QPieSlice *slice2=pieSeries->append(QString::fromLocal8Bit("异常个数6个"), 6);
slice2->setLabelColor(QColor(0,226,255,255));
slice2->setColor(QColor(0,226,255,220));
slice2->setBorderColor(QColor(255,58,93,90));//边框颜色
slice2->setLabelVisible(true);
QPieSlice *slice3 = pieSeries->append(QString::fromLocal8Bit("危险个数4个"), 4);
//slice3->setExploded();//炸开
slice3->setLabelVisible(true);
slice3->setLabelColor(QColor(0,226,255,255));
slice3->setBorderColor(QColor(255,58,93,90));//边框颜色
slice3->setColor(QColor(255,58,93,230));
QChart *pieChart = new QChart();
pieChart->addSeries(pieSeries); // 将 series 添加至图表
pieChart->setTitle(QString::fromLocal8Bit("所有监测点数据统计"));
pieChart->setTitleFont(QFont(QString::fromLocal8Bit("所有监测点数据统计"),11));//字体大小
pieChart->setTitleBrush(QBrush(QColor(255,255,255,200)));
//pieChart->setGeometry(0,400,400,400);
pieChart->legend()->setAlignment(Qt::AlignBottom);
//pieChart->setTheme(QChart::ChartThemeBrownSand);
QString path1=QDir::currentPath()+"/imgs/1.png";//大表盘背景图
QPixmap pixmap(path1);//设定图片
ui->QchartviewPie_label->setPixmap(pixmap);
pieChart->setBackgroundBrush(QBrush(QColor(255,255,255,0)));//设置背景透明
//设置给控件显示(QchartviewPie是ui界面中拖入widget控件并将它提升为Qchartview类)
ui->QchartviewPie->setRenderHint(QPainter::Antialiasing);
pieChart->setSelected(true);
ui->QchartviewPie->setChart(pieChart);
}

示例效果:

 

1.2:QCustomPlot

QCustomPlot是目前QT公认为比较好用的第三方图形展示库,库里就只有qcustomplot.h和qcustomplot.cpp两个主要文件。本项目主要封装于showonepoint类中,以及mainwindow类也有使用。使用各类需要注意:

①:将下载好的QCustomPlot压缩包解压后的qcustomplot.h和qcustomplot.cpp两个文件放到自己项目的.pro同级目录,然后打开项目-添加现有文件,分别将这两个文件添加进自己的项目中。

②:在.pro文件中添加:

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport

③:执行qmark

示例代码:

//该函数代码不完整,主要展示图表细节设置代码
void MainWindow::ShowAllType()
{
//三大类的折线展示图
ui->AllTypeshow->addGraph();//添加线条
......
//设置线条
ui->AllTypeshow->graph(0)->setName(QString::fromLocal8Bit("风速平均值"));
ui->AllTypeshow->graph(0)->setPen(QPen(QColor(0,226,255,180)));//风速线颜色
ui->AllTypeshow->setBackground(QBrush(QColor(27, 27, 27,255)));//背景色
//设置坐标轴名称
//ui->AllTypeshow->yAxis->setSelectedTickLabelColor(QColor(0,226, 255,255));//设置单击选中后的坐标颜色
ui->AllTypeshow->xAxis->setLabel(QString::fromLocal8Bit("各类传感器实时数据"));//x标签
ui->AllTypeshow->xAxis->setLabelColor(QColor(255,255, 255,200));//x标签颜色
ui->AllTypeshow->xAxis->setTickLabelColor(QColor(0,226, 255,255));//x的数据颜色
ui->AllTypeshow->xAxis->setBasePen(QPen(QColor(0,226, 255,255)));//x轴基础线颜色
ui->AllTypeshow->xAxis->setSubTickPen(QPen(QColor(0,226, 255,255)));//x轴小刻度颜色
ui->AllTypeshow->yAxis->setLabelColor(QColor(255,255, 255,200));//Y标签
ui->AllTypeshow->yAxis->setTickLabelColor(QColor(0,226, 255,255));
ui->AllTypeshow->yAxis->setLabel(QString::fromLocal8Bit("实时平均值"));//y标签
ui->AllTypeshow->yAxis->setBasePen(QPen(QColor(0,226, 255,255)));
ui->AllTypeshow->yAxis->setSubTickPen(QPen(QColor(0,226, 255,255)));
ui->AllTypeshow->xAxis->grid()->setPen(QPen(QColor(0,226, 255,100)));//x栅格颜色
QPen qcustomx_grid_pen;//x轴网格线风格
qcustomx_grid_pen.setColor(QColor(0,226, 255,100));
qcustomx_grid_pen.setStyle(Qt::DotLine);
ui->AllTypeshow->xAxis->grid()->setPen(qcustomx_grid_pen);
QPen qcustomy_grid_pen;//y轴网格线风格
qcustomy_grid_pen.setColor(QColor(0,226, 255,100));
qcustomy_grid_pen.setStyle(Qt::DotLine);
ui->AllTypeshow->yAxis->grid()->setPen(qcustomy_grid_pen);
ui->AllTypeshow->xAxis-> setTicks(true); //不显示坐标轴
ui->AllTypeshow->xAxis->setRange(0, 30);
ui->AllTypeshow->yAxis->setRange(1, 80);
//启用拖拽事件等
ui->AllTypeshow->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend | QCP::iSelectPlottables);
ui->AllTypeshow->legend->setBrush(QBrush(QColor(0,226,255,100)));
ui->AllTypeshow->legend->setVisible(true);//设置图表的标注可见
//添加数据,这里演示y=30-35浮动数据
for (int i = 0; i<30; i++)
{
X_WD.append(i);
// Y_WD.append(qrand()%3+30);//温度数据取值在30-33浮动
Y_FS.append(qrand()%8+3);//风速在3-11浮动(单位:m/s)
Y_ZD.append(qrand()%10+50);//震动在50-60浮动(单位%)
Y_YL.append(qrand()%10+15);//应力在15-25浮动单位用kgf/mm²
Y_JS.append(qrand()%10+30);
}
//给三大类实时数据设置数据
ui->AllTypeshow->graph(0)->setData(X_WD, Y_FS);
ui->AllTypeshow->graph(1)->setData(X_WD, Y_ZD);//震动
ui->AllTypeshow->graph(2)->setData(X_WD, Y_YL);
ui->AllTypeshow->graph(3)->setData(X_WD, Y_JS);
}

示例效果:


二:Socket通讯

QT部分的通讯主要涉及到和服务端 、后台通讯以及ue4程序间的通讯,均采用socket通讯来完成:
2.1:服务端通讯

服务端通讯:主要 是获取传感器数据,接收多个客户端并满足高并发请求传感器数据。

接收客户端指令后建立任务队列,服务端根据队列发送命令到传感器以获取各个传感器的状态以及数据等,解析或计算后返回给客户端用于实施呈现在图形图表之中。

主要使用了 -------- 完成端口模型

示例代码:

void IO_CompletionPort::Init_CompletionPort()
{

this->getcomport();
//初始化完成端口
serialport = new CSerialPort();
//serialport->InitPort("COM2", 9600, true, NOPARITY, 8, ONESTOPBIT);


USES_CONVERSION;
char*comportss = T2A(this->comport);
serialport->InitPort(comportss, 9600, true, NOPARITY, 8, ONESTOPBIT);

//初始化串口连接对象
AllTCPClient.empty();
BShockCollect = false;
//shock采集是否打开
WSADATA wsaData;
//加载socket版本
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
Function->WriteLog(_T("初始化模块失败"));
return;
}
else
{
CString message1 = _T("监听串口:") + this->comport + _T("成功");
Function->WriteLog(message1);
Function->WriteLog(_T("初始化模块成功"));
}
//创建完成端口
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (CompletionPort == NULL)
{
Function->WriteLog(_T("创建完成端口失败"));
return;
}
else
{
Function->WriteLog(_T("创建完成端口成功"));
}
SYSTEM_INFO si;
GetSystemInfo(&si);
int m_nProcessors = si.dwNumberOfProcessors;
NumOfThread = m_nProcessors;
AllWorkThread = new HANDLE[NumOfThread];
for (int i = 0; i < NumOfThread; i++)
{
CWinThread* Thread = AfxBeginThread(WorkThread, this);
AllWorkThread[i] = Thread;
if (Thread)
{
CString str;
str.Format(_T("%d"), Thread->m_nThreadID);
Function->WriteLog(_T("创建工作线程ID=") + str + _T("成功"));
}
else
{
Function->WriteLog(_T("创建工作线程失败"));
return;
}
}
//根据CPU数量创建*2的工作组线程
ServerListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
//创建监听socket
struct sockaddr_in ServerAddress;
ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));
ServerAddress.sin_family = AF_INET;
ServerAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
ServerAddress.sin_port = htons(Port);
//绑定监听socket到端口
if (SOCKET_ERROR == bind(ServerListen, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress)))
{
Function->WriteLog(_T("绑定端口失败"));
}
if (listen(ServerListen, SOMAXCONN) == SOCKET_ERROR)
{
Function->WriteLog(_T("监听失败"));
}
else
{
//创建监听socket的数据
PPER_SOCKET_CONTEXT mainPerHandleData = (PPER_SOCKET_CONTEXT)GlobalAlloc(GPTR, sizeof(PER_SOCKET_CONTEXT));
if (mainPerHandleData == NULL)
{
Function->WriteLog(_T("申请监听IO数据失败"));
}
mainPerHandleData->m_Socket = ServerListen;
//将监听socket的数据绑定到完成端口
if (CreateIoCompletionPort((HANDLE)ServerListen, CompletionPort, (DWORD)mainPerHandleData, 0) == NULL)
{
Function->WriteLog(_T("创建监听套接字数据失败"));
}
//创建预接受的socket,并且把这个socket绑定到接受函数指针之上
GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes = 0;
SOCKET Accept = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (SOCKET_ERROR == WSAIoctl(Accept, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), &m_lpfnAcceptEx, sizeof(m_lpfnAcceptEx), &dwBytes, NULL, NULL))
{
CString E;
E.Format(_T("%d"), WSAGetLastError());
Function->WriteLog(_T("查找接受函数指针错误:") + E);
}
//将接受socket的数据也生成出来然后绑定到函数指针上面
PPER_IO_CONTEXT mainPerIoData = (PPER_IO_CONTEXT)GlobalAlloc(GPTR, sizeof(PER_IO_CONTEXT));
mainPerIoData->m_sockAccept = Accept;
mainPerIoData->dataLength = DATA_BUFSIZE;
RtlZeroMemory(&(mainPerIoData->m_Overlapped), sizeof(OVERLAPPED));
mainPerIoData->m_OpType = ACCEPT;
mainPerIoData->dataLength = DATA_BUFSIZE;
mainPerIoData->m_wsaBuf.buf = mainPerIoData->m_szBuffer;
mainPerIoData->m_wsaBuf.len = mainPerIoData->dataLength;
if (FALSE == m_lpfnAcceptEx(mainPerHandleData->m_Socket, mainPerIoData->m_sockAccept, &mainPerIoData->m_szBuffer, mainPerIoData->dataLength - ((sizeof(SOCKADDR_IN) + 16) * 2), sizeof(SOCKADDR_IN) + 16, sizeof(SOCKADDR_IN) + 16, &dwBytes, &(mainPerIoData->m_Overlapped)))
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
CString E;
E.Format(_T("%d"), WSAGetLastError());
Function->WriteLog(_T("投递接收请求错误:") + E);
}
}
}
}

2.2:ue4程序通讯

内嵌ue4程序通讯:主要是将qt作为服务端,在启动qt时初始化QTcpServer进行监听,,然后启动外部ue4程序等待其连接。

主要功能:

ue4发消息qt接消息,qt接收消息并解析后执行响应操作。(场景中传感器点击,切换当前展示的传感器等)
qt发消息,ue4程序接消息并解析:(响应解除警报,取消闪烁报警、实时更新传感器状态、警报报警等)

①在.pro中添加: QT += network

②在头文件添加

#include <QTcpServer>
#include <QTcpSocket>

②初始化监听、新连接、接收消息

//初始化socket进行监听,等待连接
void MainWindow::SocketListen()
{
server = new QTcpServer();
server->listen(QHostAddress::Any, Port);//监听端口Port(#define Port 9537)
connect(server, SIGNAL(newConnection()), this, SLOT(SocketConnect()));//当有一个新连接过来, 执行socketcontent
}
//新的Socket连接
void MainWindow::SocketConnect()
{
//初始化thissocket,得到这个连接
thissocket = server->nextPendingConnection();
if(thissocket)
{
connect(thissocket, SIGNAL(readyRead()), this, SLOT(SocketRecv())); //当接受到消息
}
}
//接受解析socket消息
void MainWindow::SocketRecv()
{
if(thissocket->isValid())
{
QByteArray arr=thissocket->readAll();//读取消息
QString data = arr;//QString::fromLocal8Bit(arr);将接收得到的unicode字符转为自己的utf8字符集,如果发送的已经转了那就直接接收
if(!data.isEmpty())
{
Execute_sockecommand(data);//根据命令执行操作响应
}
}
}


三:内嵌ue4可执行程序

该部分主要是将外部程序嵌入qt程序中,作为其一个子窗口进行展示。只要是通过qt启动外部程序,然后利用windows函数FindWindow()获得ue4窗口句柄handle,然后就是对这个handle的操作。

目前这个嵌入ue4exe的地方有点问题:一般做法是利用一个QWindow 来作为这个handle的代理,再利用QWidget在显示这个QWindow 已达到QT嵌入外部exe。

主要功能:

传感器位置点坐标转换,即需要解决现实场景->三维场景的传感器映射。(解决经纬度坐标->ue4的世界空间坐标)。
传感器动态配置,基于上方的坐标转换,在后台进行传感器添加后,通过读取sql数据库进行不同种类传感器的实时生成。(实际最后采用的http协议,由java后端提供接口,没有直接查询数据库)。
三维场景的效果:楼层分层透明、各个方位的视图展示、传感器报警快速定位,场景漫游模式等(ue4是很强大的三维引擎,只要产品经理给力可以设计很多的酷炫效果,已达到说服客户的目的哦)。

**********实际上这种方法对绝大多数应用程序来说都比较实用,但是对于ue4来说却存在焦点问题!尝试许久无果后,无奈之举只能直接对handle进行移动缩放让其跟随qt窗口进行运动!!!!!

**********最后设计成了四个window窗口,即上、右、下三个qt窗口和左边的ue4窗口。

①移动ue4exe窗口

MoveWindow(hwnWindow,startpoint.x(),startpoint.y(),1041,540,1);

②关闭时自身时结束ue4exe。(结束本应该写在~mainwindow()里面,这里直接截取QCloseEvent事件进行处理)

//关闭操作响应
void MainWindow::closeEvent(QCloseEvent *event)
{
switch( QMessageBox::information(this,QString::fromLocal8Bit("提示"),QString::fromLocal8Bit("你确定退出该软件?"),QString::fromLocal8Bit("确定"),QString::fromLocal8Bit("取消"),0,1))
{
case 0:
{
QString exename="UE4Game.exe";
exename="sensor.exe";
terminateMyexe(exename);//查找并结束exe
delete mypie;
break;
event->accept();//接受关闭消息
}
case 1:
default:
event->ignore();//忽略
break;
}
}


四:管理后台

该系统为满足客户要求的传感器动态化,需要解决进行传感器的动态配置和解析,即现场加装了一个风速传感器后,系统能够自动获取并展示到三维模型中去,实现实时的全动态加载和监控。

后台采用java开发。

主要功能:

基本的角色、账号等菜单、数据权限
动态添加、修改传感器信息,计算参数配置
数据统计等。

 

客户端整体效果:

该版本为了解决ue4焦点问题而新写的第二版本,无奈的采用了3个qt的window进行拼接,后面的同学如果采用这方式进行大屏展示,并解决了相关ue4的焦点问题。欢迎留言讨论,大家一起学习。
————————————————
版权声明:本文为CSDN博主「萤火1129」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_22824481/article/details/79616433

posted @ 2021-04-06 14:06  Avatarx  阅读(2474)  评论(0编辑  收藏  举报