作者归档:admin

播放器

一,VLC

VLC 是一款自由、开源的跨平台多媒体播放器及框架,可播放大多数多媒体文件,以及 DVD、音频 CD、VCD 及各类流媒体协议。

1,主页地址

https://www.videolan.org/

2,源码地址

https://github.com/videolan/vlc

二,QtAV

基于Qt和FFmpeg的跨平台高性能音视频播放框架。

1,主页地址

https://www.qtav.org

2,源码地址

https://github.com/wang-bin/QtAV

三,smplayer

1,主页地址

https://www.smplayer.info

2,源码地址

https://github.com/smplayer-dev/smplayer

Qt 相关资源下载地址

1,Qt在线安装工具

目前都是采用在线安装的方式,前提是需要注册。

https://download.qt.io/official_releases/online_installers

2,QtCreator

IDE单独下载地址。

https://download.qt.io/archive/qtcreator

3,vs插件

windows下使用vs开发,可以安装这个插件。

https://download.qt.io/archive/vsaddin

4,arm环境下安装Qt开发环境

arm下比如ubuntu环境,可直接用apt 指令安装,但是目前仓库中的最新版本时5.9。
如下是把能装的都装了,如果空间有限,请自行裁剪。

sudo apt-get install qtbase5-dev qtbase5-dev-tools qtchooser qt5-qmake qtcreator
sudo apt-get install qtbase5-examples qtbase5-doc-html qtdeclarative5-dev qml-module-qtquick-controls2 qml-module-qtquick-extras qml-module-qt-labs-platform
sudo apt-get install qtmultimedia5-dev  libqt5multimedia5-plugins qml-module-qtquick-virtualkeyboard  libqt5svg5* qtbase5-private-dev qml-module-qt-labs-folderlistmodel
sudo apt-get install qml-module-qt-labs-folderlistmodel qml-module-qtquick* qtquickcontrols5-* qml-module-qtquick2

5,vs最新版
https://visualstudio.microsoft.com/zh-hans/downloads/

QML 中的状态

状态描述了当前用户界面样子,QML中一个状态定义了一组属性的改变,并且会在一定条件下被触发。

假设有这么一个场景,红黄绿三个灯,用一个按钮,点击后依次切换三个灯亮起。使用QWidget的思路去实现就是在按钮click对应的槽函数中,依次获取三个button的指针,然后改变其颜色,这样也能实现,但是不够优雅。QML的思路是,全局定义一组状态,然后每个状态来控制具体的属性,使用时只要切换不同状态就可以了,后续修改的话,只需要修改这个全局状态就行,并且三个按钮集中暴露在这组状态中。

一,定义三个灯

//三个灯的按钮
Row{
anchors.centerIn: parent
spacing: 20
//红灯
Rectangle{
id:red
width: 50
height: 50
radius: 50
color: "red"
}

//黄灯
Rectangle{
id:yellow
width: 50
height: 50
radius: 50
color: "yellow"
}

//绿灯
Rectangle{
id:green
width: 50
height: 50
radius: 50
color: "green"
}
}

二,定义一组状态,每个状态控制 三个灯具体的属性

//定义一组状态 规定每个状态下 的属性
Item{
id:root
state:"red"
states: [
State {
//红灯状态 红灯亮 其它灭
name: "red"
PropertyChanges {target: red;color:"red"}
PropertyChanges {target: yellow;color:"gray"}
PropertyChanges {target: green;color:"gray"}
},
State {
name: "yellow"
PropertyChanges {target: yellow;color:"yellow"}
PropertyChanges {target: red;color:"gray"}
PropertyChanges {target: green;color:"gray"}
},
State {
name: "green"
PropertyChanges {target: green;color:"green"}
PropertyChanges {target: red;color:"gray"}
PropertyChanges {target: yellow;color:"gray"}
}
]
}

三,切换时 只需要指定 当前状态是 谁即可。

//依次切换三个状态
Button{
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("切换")
onClicked: {
if(root.state=="red")
root.state="yellow"
else if(root.state=="yellow")
root.state="green"
else
root.state="red"
}
}

四,效果

五,过渡

我们还可以在状态切换的时候,加上过渡的动画,让其看起来更丝滑。过渡规定了从状态A到状态B切换时,某个动画的变化。比如下边状态红灯 到黄灯时,红灯和黄灯颜色持续500毫秒。

六,代码

https://gitcode.com/keiler20181/QMLState.git

QML 不同风格和主题的切换

Quick程序提供了方便的用于切换不同风格和主题的配置文件,如果没有设计稿,又想界面没那么丑,那么可以用这套配置,让应用看起来相对专业一点。

一,在 qrc 资源文件中添加 qtquickcontrols2.conf 文件。

二,编辑这个文件。

qt 共提供5种不同的风格。

1,Default Style:
默认风格,其外观和行为会随着操作系统和系统主题的变化而变化。它保持了跨平台的一致性,并且对大多数常见控件提供了自然的外观。

2,Material Style:
受 Google Material Design 影响的风格,主要用于移动应用程序。它强调了阴影、动画和响应式布局,具有现代感和生动感。

3,Universal Style:
致力于提供一种在不同操作系统上具有一致外观的风格。它适合那些希望应用程序在不同平台上保持相似外观的开发者。

4,Imagine Style:
一种受到 macOS 的设计风格启发的风格,它提供了类似 macOS 风格的外观和交互体验。

5,Fusion Style:
基于 Qt Widgets 的 Fusion 主题,提供了一种类似传统桌面应用程序的外观和行为。

每种风格还可以设置不同的属性。

1. accent,color类型,表示重点色,默认是Pink

2. primary,color类型,表示优选色,默认是Indigo

3. backbround,color类型,表示背景色,默认由主题指定(light或者dark)

4. elevation,int类型,表示海拔高度,值越大,阴影越深,该值与具体控件相关

5. foreground,color类型,表示前景色,默认值由主题指定(light或者dark)

6. theme,枚举类型,表示主题,默认是Light,也可修改为Dark

三,看下效果

1,fusion

2,Universal

3,Material

4,Imagine

5,默认风格

6,Dark主题

四,代码

https://gitcode.com/keiler20181/QMLStyle.git

C#通过Qt使用VTK

需求:

一个项目,界面是C# 开发的,但是业务上有三维可视化的需求,VTK基于C#的绑定版本需要收费,并且资料很少。因此将VTK嵌入到Qt里,并封装成一个dll,通过接口提供给C#访问。

实现:

一,Qt程序的配置

这里用到了一第三方库(https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate),它可以将Qt的窗口给C#使用。

1,首先看pro文件,主要是dll编译配置,和第三方库引用及VTK的依赖库。

2,main.cpp

#include "widget.h"
#include <QApplication>

#include <windows.h>
#include <qmfcapp.h>
#include <qwinwidget.h>

// int main(int argc, char *argv[])
// {
// QApplication a(argc, argv);
// Widget w;
// w.show();
// return a.exec();
// }

BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpvReserved*/ )
{
static bool ownApplication = FALSE;

if ( dwReason == DLL_PROCESS_ATTACH )
ownApplication = QMfcApp::pluginInstance( hInstance );
if ( dwReason == DLL_PROCESS_DETACH && ownApplication )
delete qApp;

return TRUE;
}

QWinWidget *win=nullptr;
extern "C" __declspec(dllexport) bool initWindow( HWND parent )
{
if(parent==nullptr)
return false;
win = new QWinWidget(parent);
Widget *widget = new Widget(win);
widget->show();
win->move(0,0);
win->show();

return TRUE;
}

extern "C" __declspec(dllexport) bool destroyWindow()
{
if(win!=0){
win->close();
delete win;
}

return TRUE;
}

3,Widget 写法,可以参考这篇文章(http://qthello.com/index.php/2024/04/11/vtk/)或者看仓库上的源码。

二,C#端

1,引用dll 并调用接口

using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp
{
public partial class Form1 : Form
{
[DllImport("VTKNet.dll", EntryPoint = "initWindow", CharSet = CharSet.Ansi)]
public extern static bool initWindow(IntPtr parent);

[DllImport("VTKNet.dll", EntryPoint = "destroyWindow", CharSet = CharSet.Ansi)]
public extern static bool destroyWindow();

public Form1()
{
InitializeComponent();
//打开窗口
initWindow(this.Handle);
}
}
}

2,exe路径下 需要把自己的dll ,Qt的dll 以及VTK的dll 全部放进去。

效果:

代码:

https://gitcode.com/keiler20181/QtNetVTK.git

windows下利用NSIS制作安装包

经过程序员抓耳挠腮的开发,应用终于开发完毕了,那接下来无论发布给客户还是给测试,最好的方式时打一个安装包,直接给release文件夹,就显得不太专业了。

程序的打包分为两步:

1,将所以依赖文件收集全,确保其它非开发机器运行时,不会缺少库。

2,用第三方打包软件,制作安装包。

一,收集依赖文件

1,我们以一个QML程序为例,新建一个pkg/v1.0.0/bin文件夹,并将 release版本的pkgTest.exe拷贝到这个bin文件夹下。

2,找到对应版本的Qt命令行工具。

3,执行windeployqt.exe 执行,自动收集依赖文件。

  • windeployqt.exe 为打包工具
  • –qmldir 是自己的qml文件所在的目录,如果QWidget程序,这个参数 及后面的路径就不需要了。这个也告诉我们,我们的目录结构最好将所有自定义的qml文件放到一起,便于打包。
  • 自定义qml文件所在的路径
  • pkgTest.exe 我们的主程序

4,这个指令只能收集一部分程序,执行后自己双击一下主程序(pkgTest.exe),看还缺啥,手动拷贝一下。最可靠的办法是找一台非开发机器验证。

二,打包工具打包

我习惯使用NSIS+HM NIS EDIT 这两个工具进行打包,原因就是颗粒度更细,可以控制环境变量、自启动等。并且官网提供了大量的插件,设置上更加的自由。

1,安装打包工具 NSIS ,这个是一个基础的库( https://nsis.sourceforge.io/Download

2,安装编辑器,这是配套使用的一个编辑器。只用上边那个也可以,只是加上这个更方便(https://hmne.sourceforge.net/

3,使用。我们第一步可以建立一个向导,按照向导,一步一步操作。

安装语言可以自己选择,一个或者多个都可以。

选择目录这一步很关键,把自带的两个删掉,然后指定我们的主程序的父目录,也就是bin文件夹。

之后下一步,下一步,选择将脚本保存,便于后期修改。

最后就是编译脚本,生成安装包。

安装包生成到了脚本所在目录,双击可以自己验证下。

三,NSIS 指令

NSIS 官网提供了大量的插件(https://nsis.sourceforge.io/Category:Plugins),自己可以根据业务需要去下载,并且它本身也有大量的指令,可以支持更高的定制化需求。

1,静默安装。 比如安装其他依赖的exe,不想弹出来提示框,让其他在后台默默执行。

SilentInstall silent 
SilentUninstall silent
比如 下边的代码,将Video.exe 在后台执行
SilentInstall silent 
SilentUninstall silent 
Section "MainSection" SEC01
  SetOutPath "$INSTDIR"
  SetOverwrite try
  File "path_1.0.1\Video.exe"
SectionEnd

2,安装后运行指定程序

Function .onInstSuccess
ExecShell "" "$INSTDIR\Video.exe"
FunctionEnd

3,安装后 开机自启。其实就是写注册表

WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run""Video" "$INSTDIR\Video.exe" ; 开机自启

DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Video" ;卸载开机启动

4,杀死某个进程。 可以用在安装时,确保当前程序已经被关掉。

需要下载插件:https://www.cnblogs.com/chechen/p/11125353.html

Function .onInit
KillProcDLL::KillProc "Video.exe" ;杀进程
Sleep 1000
FunctionEnd

某些进程杀不掉 用这个:

Function .onInit
ExecCmd::exec '"taskkill" /F /IM hiclass.exe /T'
FunctionEnd

5,管理员运行

WriteRegStr HKCU "Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" "$INSTDIR\Video.exe" "RUNASADMIN" ;管理员运行 

6,安装vc环境

; 安装VC环境
Function .onInstSuccess
Push $R0
ClearErrors
ReadRegStr $R0 HKLM "SOFTWARE\Classes\Installer\Dependencies\{e2803110-78b3-4664-a479-3611a381656a}" "Version"

; 检测含有vc的注册表信息是否存在
IfErrors 0 VSRedistInstalled
Exec "$INSTDIR\vcredist_msvc2015_x86.exe /q" ;若不存在,执行静默安装
StrCpy $R0 "-1"
;MessageBox MB_OK $R0

VSRedistInstalled:
;MessageBox MB_OK "已安装"
pop $R0
; Delete "$INSTDIR\vcredist_msvc2015_x86."
FunctionEnd

7,检测是否是xp系统

需要安装插件: https://blog.csdn.net/xt2zsun/article/details/79379098

#检测 Windows 7
var isWindowsXP
Section
Version::IsWindowsXP
#获取值
Pop $isWindowsXP
${if} $isWindowsXP == "1"
MessageBox MB_OK "暂不支持XP系统,请升级到Win7及以上!"
Quit
${EndIf}
SectionEnd

8,右键属性 添加信息

VIProductVersion "${PRODUCT_VERSION}" ;版本号,格式为 X.X.X.X (若使用则本条必须)
VIAddVersionKey /LANG=2052 "ProductName" "我的程序" ;产品名称
VIAddVersionKey /LANG=2052 "Comments" "这是我写的一个测试程序" ;备注
VIAddVersionKey /LANG=2052 "CompanyName" "我的公司名称" ;公司名称
VIAddVersionKey /LANG=2052 "LegalTrademarks" "Test Application is a trademark of Fake company" ;合法商标
VIAddVersionKey /LANG=2052 "LegalCopyright" "Copyright (c) 2019 我的公司版权" ;合法版权
VIAddVersionKey /LANG=2052 "FileDescription" "我的文件描述" ;文件描述(标准信息)
VIAddVersionKey /LANG=2052 "FileVersion" "${PRODUCT_VERSION}" ;文件版本
VIAddVersionKey /LANG=2052 "ProductVersion" "${PRODUCT_VERSION}" ;产品版本

9,判断文件是否存在,用于检验插件是否安装

;存在执行 第三行,不存在执行第二行  不存在+1 行
IfFileExists "C:\Program Files (x86)\WinPcap\rpcapd.exe" +3 +2
MessageBox MB_OK "已安装" ;占位 无实际作用
ExecWait '"$INSTDIR\WinPcap_4_1_3.exe" '

10,删除及创建快捷方式

Delete "$DESKTOP\快乐魔法狮.lnk"
SetOutPath "$INSTDIR"
CreateShortCut "$DESKTOP\快乐魔法狮.lnk" "$INSTDIR\hidteacher.exe"

11,创建环境变量。

EnVar::DeleteValue "vi_algorthmCall_tool_root"
EnVar::AddValue "vi_algorthmCall_tool_root" "$INSTDIR"

利用FCL实现更加精准的碰撞检测

一,需求

利用OSG结合FCL实现实现精准的碰撞检测。

二,效果

三,分析

我们看如下这张图,碰撞的逻辑就是,在一个三维场景中,构造一个实体,比如下边的BoxA,然后在物理引擎比如bullet中,或者专用的碰撞检测库中也构造一个对应的实体,比如BoxB。之后在BoxA位姿改变时后,将BoB的位姿也做相应的更新。之后发生碰撞时,物理引擎或者FCL就会给出信号。而这个场景,可以是VTK或者OSG。而碰撞检测可以用Bullet也可以用FCL。

之前用bullet做个尝试,基本的图形能满足需求,比如球,盒子,但是项目中涉及到点云的碰撞,而bullet中处理点云,没有找到好的处理方式。但是FCL可以将点云转变成fcl中对应的实体,因此最终选择了FCL进行碰撞检测,这里列出FCL中大概的步骤。

1,FCL中构造实体。这里构造了一个 盒子。

auto box_geometry = std::make_shared<fcl::Boxf>(w, d, h);
auto ob = new fcl::CollisionObjectf(box_geometry);

2,更新FCL实体的位姿矩阵。

void FCLManager::updateTrans(const std::string &name, const fcl::Transform3f &trans)
{
fcl::CollisionObjectf *ob=getCollisionObject(name);
if(ob){
ob->setTransform(trans);
}
}

//OSG 矩阵 需要进行转换 才能给到FCL使用
//osg 矩阵转fcl矩阵
osg::Vec3 osgTrans = mt.getTrans(); // 获取平移分量
osg::Quat osgQuat = mt.getRotate(); // 获取旋转分量

fcl::Quaternionf rotation(osgQuat.w(), osgQuat.x(), osgQuat.y(), osgQuat.z());
fcl::Vector3f translation(osgTrans.x(), osgTrans.y(), osgTrans.z());
fcl::Transform3f fclTrans=fcl::Transform3f::Identity();
fclTrans.translation() = translation;
fclTrans.linear()=rotation.toRotationMatrix();
FCLManager::getInstance()->updateTrans(this->getName(),fclTrans);

3,碰撞检测

我是检测机器人和其它障碍物的碰撞,这里把机器人关节放到一个集合中,把其它障碍物放到另一个集合中

 
bool FCLManager::detectCollision()
{
fcl::CollisionRequestf request;
fcl::CollisionResultf result;
for(auto &ob1:jointMap){
for(auto &ob2:obstacleMap){
collide(ob1.second, ob2.second, request, result);
if(result.isCollision()){
return true;
}
}
}
return false;
}

4,FCL支持三角面检测。因此我们在FCL中构造对应实体的时候,可以直接用三角面。这样不管OSG中构造的时盒子还是球,还是导入的stl,对应FCL中都是统一用三角面处理。

void FCLManager::addTriMesh(const std::string &name, osg::Node *node)
{
fcl::CollisionObjectf *obj = createNodeCollisionObject(node);
obstacleMap.emplace(name,obj);
}

fcl::CollisionObjectf *FCLManager::createNodeCollisionObject(osg::Node *node)
{
MyComputeTriMeshVisitor visitor;
node->accept( visitor );
osg::Vec3Array* vertices = visitor.getTriMesh();

typedef fcl::BVHModel<fcl::OBBRSSf> Model;
Model* model = new Model();
std::shared_ptr<fcl::CollisionGeometryf> m1_ptr(model);
model->beginModel();
osg::Vec3 p1, p2, p3;
for( size_t i = 0; i + 2 < vertices->size(); i += 3 )
{
p1 = vertices->at( i );
p2 = vertices->at( i + 1 );
p3 = vertices->at( i + 2 );

fcl::Vector3<float> pp1{p1.x(),p1.y(),p1.z()};
fcl::Vector3<float> pp2{p2.x(),p2.y(),p2.z()};
fcl::Vector3<float> pp3{p3.x(),p3.y(),p3.z()};

model->addTriangle(pp1, pp2, pp3);
}
model->endModel();
model->computeLocalAABB();

return new fcl::CollisionObjectf(m1_ptr);
}

5,点云的碰撞。点云的碰撞 使用了一种叫做八叉树的算法。首先将点云转成pcl的点云 格式,然后可以直接构造出fcl实体,这也是选用FCL的原因。

fcl::CollisionObjectf* FCLManager::createPointCloudCollisionObject(const pcl::PointCloud<pcl::PointXYZ>::Ptr pointcloud_ptr, const octomap::point3d &origin_3d)
{
// octomap octree settings
const double resolution = 0.01;
const double prob_hit = 0.9;
const double prob_miss = 0.1;
const double clamping_thres_min = 0.12;
const double clamping_thres_max = 0.98;

std::shared_ptr<octomap::OcTree> octomap_octree = std::make_shared<octomap::OcTree>(resolution);
octomap_octree->setProbHit(prob_hit);
octomap_octree->setProbMiss(prob_miss);
octomap_octree->setClampingThresMin(clamping_thres_min);
octomap_octree->setClampingThresMax(clamping_thres_max);

octomap::KeySet free_cells;
octomap::KeySet occupied_cells;

#if defined(_OPENMP)
#pragma omp parallel
#endif
{
#if defined(_OPENMP)
auto thread_id = omp_get_thread_num();
auto thread_num = omp_get_num_threads();
#else
int thread_id = 0;
int thread_num = 1;
#endif
int start_idx = static_cast<int>(pointcloud_ptr->size() / thread_num) * thread_id;
int end_idx = static_cast<int>(pointcloud_ptr->size() / thread_num) * (thread_id + 1);
if (thread_id == thread_num - 1)
{
end_idx = pointcloud_ptr->size();
}

octomap::KeySet local_free_cells;
octomap::KeySet local_occupied_cells;

for (auto i = start_idx; i < end_idx; i++)
{
octomap::point3d point((*pointcloud_ptr)[i].x, (*pointcloud_ptr)[i].y, (*pointcloud_ptr)[i].z);
octomap::KeyRay key_ray;
if (octomap_octree->computeRayKeys(origin_3d, point, key_ray))
{
local_free_cells.insert(key_ray.begin(), key_ray.end());
}

octomap::OcTreeKey tree_key;
if (octomap_octree->coordToKeyChecked(point, tree_key))
{
local_occupied_cells.insert(tree_key);
}
}

#if defined(_OPENMP)
#pragma omp critical
#endif
{
free_cells.insert(local_free_cells.begin(), local_free_cells.end());
occupied_cells.insert(local_occupied_cells.begin(), local_occupied_cells.end());
}
}

// free cells only if not occupied in this cloud
for (auto it = free_cells.begin(); it != free_cells.end(); ++it)
{
if (occupied_cells.find(*it) == occupied_cells.end())
{
octomap_octree->updateNode(*it, false);
}
}

// occupied cells
for (auto it = occupied_cells.begin(); it != occupied_cells.end(); ++it)
{
octomap_octree->updateNode(*it, true);
}

auto fcl_octree = std::make_shared<fcl::OcTree<float>>(octomap_octree);
std::shared_ptr<fcl::CollisionGeometryf> fcl_geometry = fcl_octree;
return new fcl::CollisionObjectf(fcl_geometry);
}

四,总结

OSG结合FCL实现碰撞的检测,首先将OSG中的Node 转换成FCL中的三角面。然后将点云用上述方式进行转换。最后调用result.isCollision()函数 来判断两个实体是否发生碰撞。

https://github.com/flexible-collision-library/fcl

QML 调用C++ 单列对象

一些对象是全局唯一的,特别适合使用单列模式,比如网络,数据库,或者跟设备相关的功能,如串口,plc等,因此在QML中访问C++的单列,就很有必要。

一,C++单例

以一个相机操作为例,这里只列出只要功能。

class ImageHandle : public QThread
{
Q_OBJECT

public:
//单例模式的声明
static ImageHandle* instance(){
static ImageHandle manager;
return &manager;
}

//给QML调用的接口
Q_INVOKABLE void startPreview();

private:
explicit ImageHandle(QObject *parent = nullptr);
~ImageHandle();

};

二,main.cpp 中注册单列,这样才能在QML中使用。

一共两步,一步定义一个函数(configureProvider),还有一步是利用qmlRegisterSingletonType注册

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QIcon>
#include "comm/imageHandle.h"

//单例的使用
static QObject *configureProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return ImageHandle::instance();
}

int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QGuiApplication::setAttribute(Qt::AA_Use96Dpi);

QGuiApplication app(argc, argv);

//单例的使用
qmlRegisterSingletonType<ImageHandle>("ImageHandle",1,0,"ImageHandle",configureProvider);

QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);

return app.exec();
}

三,QML中使用

import 导入后 就可以直接使用了。

QML与C++交互

QML写界面,业务逻辑使用C++,既能快速的开发界面也能利用C++的强大生态,这是目前比较被认可的方式,那就涉及到QML与C++对象的交互。

我们以登录例子来说明,页面点击登录,将信息传递到c++ http对象进行密码的验证,然后返回登录结果。

一,调用C++中的函数

1,普通C++类

#ifndef HTTPHANDLER_H
#define HTTPHANDLER_H

#include <QObject>
class HTTPHandler:public QObject{
Q_OBJECT
public:
HTTPHandler(QObject* parent=0):QObject(parent){

}
//登录接口 验证用户名 和密码
Q_INVOKABLE bool login(QString name,QString pwd){
if(name=="admin"&&pwd=="123"){
return true;
}else{
return false;
}
}
};
#endif // HTTPHANDLER_H

2,注册C++ 类

main.cpp注册此 C++ 类型,这样QML中就能使用了。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "HTTPHandler.h"

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);

//注册类型
qmlRegisterType<HTTPHandler>("HTTPHandler", 1, 0, "HTTPHandler");


QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);

return app.exec();
}

3,调用C++对象中的函数

只要是C++中 通过Q_INVOKABLE关键词声明的public函数,QML中都能访问。


import QtQuick 2.15
import QtQuick.Controls 1.4
//导入C++ 对象
import HTTPHandler 1.0

Rectangle{
width: 200
height: 200

//相当于 实列化一个C++对象
HTTPHandler{
id:httpHandler
}

signal loginOk()
Row{
anchors.centerIn: parent
TextField{
id:name
}
TextField{
id:pwd
}
}
Button{
anchors.bottom: parent.bottom
text: qsTr("登录")
onClicked: {
//调用C++对象中的函数
if(httpHandler.login(name.text,pwd.text)){
loginOk()
}
}
}
}

二,响应C++中的信号

上述方式相当于同步的方式调用C++中的函数,还可以异步的响应C++中的信号,相当于QML中的槽与C++中的信号进行绑定。

1,C++类

#ifndef HTTPHANDLER_H
#define HTTPHANDLER_H

#include <QObject>
class HTTPHandler:public QObject{
Q_OBJECT
public:
HTTPHandler(QObject* parent=0):QObject(parent){

}

Q_INVOKABLE void login(QString name,QString pwd){
if(name=="admin"&&pwd=="123"){
//验证成功后 激发信号
emit loginSuccess();
}
}

signals:
//登录成功信号
void loginSuccess();
};
#endif // HTTPHANDLER_H

2,响应C++信号

import QtQuick 2.15
import QtQuick.Controls 1.4
import HTTPHandler 1.0

Rectangle{
width: 200
height: 200

//相当于 实列化一个C++对象
HTTPHandler{
id:httpHandler
//绑定C++信号
onLoginSuccess: {
loginOk()
}
}

signal loginOk()
Row{
anchors.centerIn: parent
TextField{
id:name
}
TextField{
id:pwd
}
}
Button{
anchors.bottom: parent.bottom
text: qsTr("登录")
onClicked: {
//调用C++对象中的函数
httpHandler.login(name.text,pwd.text)

}
}
}

三,绑定C++中的属性

还可以直接在C++定义属性,然后QML绑定此属性,适合实时的传递一些状态数据。

1,C++类

#ifndef HTTPHANDLER_H
#define HTTPHANDLER_H

#include <QObject>
class HTTPHandler:public QObject{
Q_OBJECT
//注册属性
Q_PROPERTY(QString status READ getStatus WRITE setStatus NOTIFY statusChanged FINAL)

public:
HTTPHandler(QObject* parent=0):QObject(parent){

}

Q_INVOKABLE bool login(QString name,QString pwd){
if(name=="admin"&&pwd=="123"){
return true;
}else{
//设置状态信息
setStatus("pwd or name error");
}
}

QString getStatus() const;
void setStatus(const QString &newStatus);

signals:
void statusChanged();

private:
//状态信息
QString status;
};

inline QString HTTPHandler::getStatus() const
{
return status;
}

inline void HTTPHandler::setStatus(const QString &newStatus)
{
if (status == newStatus)
return;
status = newStatus;
emit statusChanged();
}


#endif // HTTPHANDLER_H

2,绑定属性

import QtQuick 2.15
import QtQuick.Controls 1.4
import HTTPHandler 1.0

Rectangle{
width: 200
height: 200

//相当于 实列化一个C++对象
HTTPHandler{
id:httpHandler
}

signal loginOk()
Row{
anchors.centerIn: parent
TextField{
id:name
}
TextField{
id:pwd
}
}

//定义一个文本框 直接绑定C++的属性
Text {
anchors.top: parent.top
text: httpHandler.status
}
Button{
anchors.bottom: parent.bottom
text: qsTr("登录")
onClicked: {
//调用C++对象中的函数
if(httpHandler.login(name.text,pwd.text)){
loginOk()
}
}
}
}

3,看下效果

点击登录 ,如果密码或用户名错误会将C++的状态信息,实时的显示到左上角的QML Text控件中。

QML中的页面切换方式

整理下QML中页面切换的两种方式,这里以常见的登录为例,分为两个页面。登录页和主页,软件起来首先呈现登录页,点击登录进入主页,点击退出 返回到登录页

一,隐藏的方式

1.效果

2.定义两个页面(登录页,和首页),并定义登录成功和返回信号。当点击登录时,激发登录成功信号,点击退出时 激发返回信号。

3.main.qml中对两个信号进行处理。分别隐藏和显示不同的页面。

二,动态加载的方式

用Loader占位,然后动态加载组件。类似先定义一个指针,然后根据需要指向不同的内存。

三,SwipeView方式

这种相当于把页面都放到了一个容器里,然后滑动切换。

四,总结

1与3类似。当页面构造时,如果需要同时开辟很大的内存,此时建议用1,3的方式,提前把内存开辟好,这样页面页会加载的快一些。

自定义Switch

一,需求
QML中实现自定义的Switch。
二,效果


三,实现

这个要用以前的版本。

import QtQuick.Controls 1.4 as PreControl
import QtQuick.Controls.Styles 1.4
PreControl.Switch{
id:lightSwidch
height: 26
anchors.verticalCenter: parent.verticalCenter
style: SwitchStyle {
groove: Rectangle {
width: 57
height: 26
radius: 13
color: lightSwidch.checked?"#3F64EB":"#414850"
}
handle: Rectangle{
width: 26
height: 26
radius: 13
color: "#FAFCFF"
}
}
onCheckedChanged: {

}
}

自定义 Slider

一,需求
QML中实现自定义的Slider。

二,效果

三,实现

import QtQuick 2.12
import QtQuick.Controls 2.15

Item {
id: root
property int startval: 0
property int endval: 100
property int sliderWidth: 200

implicitHeight: control.height + label.height
implicitWidth: sliderWidth

Rectangle {
color: Qt.rgba(0,0,0,0)
width: root.width
height: root.height
}
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "#FFFFFF"
}

// background: Rectangle {
// y: (control.height - height) / 2
// height: 4
// radius: 2
// color: "green"

// Rectangle {
// width: control.visualPosition * parent.width
// height: parent.height
// color: "red"
// radius: 2
// }
// }
}

Label {
id: label
width: 20
height: 20
text: control.value
font.pixelSize: 15
color: "#FFFFFF"
x: handleId.x + control.x
y: handleId.y - 10
Rectangle {
color: Qt.rgba(0,0,0,0)
anchors.fill: parent
opacity: 0.3
}
}
}

四,使用

QML实现 无边框 可移动 可缩放窗体

一,需求

利用QML实现无边框窗体,要求可移动,可缩放。

二,效果

三,实现

//主窗体
import QtQuick 2.12
import QtQuick.Controls 2.3
import QtQuick.Window 2.3

import "./widget"

Window {
id: window
visible: true
flags: Qt.FramelessWindowHint|Qt.Window
width: 1024
height: 768
color: "#0F1013"
property int bw: 3

//改变鼠标形状
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: {
const p = Qt.point(mouseX, mouseY)
const b = bw + 10
if (p.x < b && p.y < b) return Qt.SizeFDiagCursor
if (p.x >= width - b && p.y >= height - b) return Qt.SizeFDiagCursor
if (p.x >= width - b && p.y < b) return Qt.SizeBDiagCursor
if (p.x < b && p.y >= height - b) return Qt.SizeBDiagCursor
if (p.x < b || p.x >= width - b) return Qt.SizeHorCursor
if (p.y < b || p.y >= height - b) return Qt.SizeVerCursor
}
acceptedButtons: Qt.NoButton
}

DragHandler {
id: resizeHandler
grabPermissions: TapHandler.TakeOverForbidden
target: null
onActiveChanged: if (active) {
const p = resizeHandler.centroid.position
const b = bw + 10
let e = 0;
if (p.x < b) { e |= Qt.LeftEdge }
if (p.x >= width - b) { e |= Qt.RightEdge }
if (p.y < b) { e |= Qt.TopEdge }
if (p.y >= height - b) { e |= Qt.BottomEdge }
window.startSystemResize(e);
}
}

MyToolBar{
window:window
}
}

//工具栏
import QtQuick 2.12
import QtQuick.Controls 2.3
import QtQuick.Window 2.3

ToolBar {
height: 50
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
x:bw
y:bw

property var window: null
//关闭 最大化 最小化按钮
function toggleMaximized(mode) {
if(mode===0){
if (window.visibility === Window.Maximized) {
window.showNormal();
} else {
window.showMaximized();
}
}else if(mode===1){
window.showMinimized()
}else if(mode===2){
window.close()
}else if(mode===4){
return window.visibility
}

}

Rectangle{
id:rectBk
anchors.fill: parent
color: "#1F2025"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property point clickPos: "0,0"
onPressed: {
clickPos = Qt.point(mouse.x, mouse.y)
}
onDoubleClicked: {
toggleMaximized(0)
}
onPositionChanged: {
//鼠标偏移量
var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)

window.setX(window.x+delta.x)
window.setY(window.y+delta.y)
}
}
}

Row{
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 0
Rectangle{
width: 50
height: 50
color: Qt.rgba(0,0,0,0)
Image {
anchors.centerIn: parent
id: min
source: "qrc:/image/icon/minus.png"
width: 26
height: 26
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
onClicked: {
toggleMaximized(1)
}
onEntered: {
parent.color = "#454B57"
}

onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}
}

Rectangle{
width: 50
height: 50
color: Qt.rgba(0,0,0,0)
Image {
id: max
source: (toggleMaximized(4) === Window.Maximized)?"qrc:/image/icon/maximize.png":"qrc:/image/icon/maximize2.png"
anchors.centerIn: parent
width: 26
height: 26
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("max")
toggleMaximized(0)
}
onEntered: {
parent.color = "#454B57"
}
onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}
}

Rectangle{
width: 50
height: 50
color: Qt.rgba(0,0,0,0)
Image {
id: close
source: "qrc:/image/icon/close.png"
anchors.centerIn: parent
width: 26
height: 26
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
onClicked: {
toggleMaximized(2)
}
onEntered: {
parent.color = "#454B57"
}

onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}
}

}

//标题
Text {
id:title
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 20
color: "#FFFFFF"
font.pixelSize: 24
text: qsTr("影像检测仪")
}

Menu {
id:settingMenu
x:setting.x
y:setting.y+setting.height+1
width: 120
Action {
text: qsTr("创建项目")
onTriggered: console.log("创建项目")

}
Action {
text: qsTr("编辑项目")
onTriggered: console.log("编辑项目")
}

delegate: MenuBarItem {
id: settingMenuItem
height: 40
width: 120

contentItem: Text {
text: settingMenuItem.text
font.pixelSize: 16
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}

background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Qt.rgba(0,0,0,0)
MouseArea{
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: {
settingMenu.close()
settingMenuItem.action.trigger()
}
onEntered: {
parent.color = "#454B57"
}

onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}
}
}
background: Rectangle {
implicitWidth: 150
implicitHeight: 40
color: "#1F2025"
}
}

//设置
Rectangle{
id:setting
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left:title.right
anchors.leftMargin: 100
color: Qt.rgba(0,0,0,0)
width: 100

Row{
anchors.centerIn: parent
spacing: 2
Text {
color: "#FFFFFF"
font.pixelSize: 20
text: qsTr("设置")
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
Image {
width: 32
height: 32
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/image/icon/down.png"
}
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: {
settingMenu.open()
}
onEntered: {
parent.color = "#454B57"
}

onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}

}

//关于菜单
Menu {
id:aboutMenu
x:about.x
y:about.y+about.height+1
width: 120
Action {
text: qsTr("关于这个")
onTriggered: console.log("关于这个")
}
Action {
text: qsTr("关于那个")
onTriggered: console.log("关于那个")
}

delegate: MenuBarItem {
id: aboutMenuItem
height: 40
width: 120

contentItem: Text {
text: aboutMenuItem.text
font.pixelSize: 16
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}

background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Qt.rgba(0,0,0,0)
MouseArea{
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: {
aboutMenu.close()
aboutMenuItem.action.trigger()
}
onEntered: {
parent.color = "#454B57"
}

onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}

}

}
background: Rectangle {
implicitWidth: 150
implicitHeight: 40
color: "#1F2025"
}
}
//关于
Rectangle{
id:about
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left:setting.right
color: Qt.rgba(0,0,0,0)
width: 100

Row{
anchors.centerIn: parent
spacing: 2
Text {
color: "#FFFFFF"
font.pixelSize: 20
text: qsTr("关于")
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
Image {
width: 32
height: 32
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/image/icon/down.png"
}
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: {
aboutMenu.open()
}
onEntered: {
parent.color = "#454B57"
}

onExited: {
parent.color = Qt.rgba(0,0,0,0)
}
}

}
}

利用Qt自带的媒体模块播放mp4文件

一. 需求

利用Qt自带的媒体模块,播放mp4等媒体文件。

二. 实现

  • pro文件修改
QT       += core gui multimedia  multimediawidgets
  • ui中放一个QWidget,并提升为QVideoWidget.
  • widget.h 文件。
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include <QMediaPlayer>
#include <QVideoWidget>
#include <QMediaPlaylist>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_fileBtn_clicked();

private:
    Ui::Widget *ui;
    QMediaPlayer* mediaPlayer;
    QMediaPlaylist* mediaPlayList;
};
#endif // WIDGET_H
  • widget.cpp 文件
#include "widget.h"
#include "ui_widget.h"

#include <QFileDialog>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    mediaPlayer = new QMediaPlayer(this);
    mediaPlayList = new QMediaPlaylist(mediaPlayer);
    mediaPlayer->setVideoOutput(ui->widget);
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_fileBtn_clicked()
{
    QString file = QFileDialog::getOpenFileName(this,"open file",".","*.*");
    if(file.isEmpty())
        return;

    mediaPlayList->clear();
    mediaPlayList->addMedia(QUrl::fromLocalFile(file));
    mediaPlayList->setCurrentIndex(0);
    mediaPlayer->setPlaylist(mediaPlayList);
    mediaPlayer->play();
}
  • 涉及到媒体,就涉及到编解码,所以就需要安装编解码器,直接安装这个即可:

https://www.codecguide.com/download_kl.htm

不然会出现如下错误:  DirectShowPlayerService::doRender: Unresolved error code 0x80040266 

三. 效果

利用VTK 包围盒实现简单的碰撞检测

一. 需求

利用VTK自带的包围盒实现简单的碰撞检测。

二. 实现

1, VTK的碰撞检测原理就是 求物体的包围盒,然后检测包围盒与线、面、或者另一个包围盒是否相交。包围盒主要包括球体、轴对齐包围盒(AABB)有向包围盒(OBB)凸包(Convex Hull),参考(VTK笔记——轴对齐包围盒(AABB) – 灰信网(软件开发博客聚合)

2,VTK自带了一个vtkCollisionDetectionFilter过滤器,来计算碰撞检测,此过滤器利用的就是OBB包围盒,使用简单,只需提供两个物体的数据集和矩阵即可。

3,实现的时候 利用一个结构体,来定义角色,矩阵和包围盒。这样便于计算。

struct  SimulateActor
{
    using Ptr =  QSharedPointer<SimulateActor>;
    vtkSmartPointer<vtkActor> Actor;
    vtkSmartPointer<vtkTransform> Trans;
    vtkSmartPointer<vtkPolyData> PolyData;
    vtkSmartPointer<vtkCollisionDetectionFilter> Collide;
};

4,先初始化之后所有的物体。然后利用两个循环来依次判断每两个物体是否发生碰撞。

5,不足

(1),此过滤器只能检测两个物体是否发生碰撞,无奈当前只能通过两个循环来进行遍历。

(2),没有找到如何将多个actor组合成一个,然后进行碰撞检测的方法,如果能组合成一个效率会提高很多。

(3),目前六个部件,与50个障碍物进行碰撞检测。检测一次的时间大约50ms。

VTK 可移动三维坐标轴

一. 需求

利用VTK实现一个可移动,可旋转的三维坐标轴。并通过回调获取其位姿。

二. 效果

三. 步骤

  1. 绘制轴和圈
vtkMovableAxesRepresentation::vtkMovableAxesRepresentation()
{
  // The initial state
  this->InteractionState = vtkMovableAxesRepresentation::Outside;
 
  // Handle size is in pixels for this widget
  this->HandleSize = 5.0;
 
  // Control orientation of normals
  this->InsideOut = 0;
 
  // Set up the initial properties
  this->CreateDefaultProperties();
 
  // Define the point coordinates
  double bounds[6];
  bounds[0] = -0.5;
  bounds[1] = 0.5;
  bounds[2] = -0.5;
  bounds[3] = 0.5;
  bounds[4] = -0.5;
  bounds[5] = 0.5;
  // Points 8-14 are down by PositionHandles();
  this->BoundingBox = vtkBox::New();
  this->PlaceWidget(bounds);
 
  //画 圆
  this->AxisCircel = new vtkActor* [3];
  this->AxisPolygonSource = new vtkRegularPolygonSource* [3];
  this->AxisCircleMapper = new vtkPolyDataMapper*[3];
  for(auto i=0;i<3;i++){
      this->AxisCircel[i]=vtkActor::New();
      this->AxisPolygonSource[i] = vtkRegularPolygonSource::New();
      this->AxisCircleMapper[i] = vtkPolyDataMapper::New();
 
      this->AxisPolygonSource[i]->SetNumberOfSides(100);    //多边形边数
      this->AxisPolygonSource[i]->SetRadius(g_circleRadius);       //半径
      this->AxisPolygonSource[i]->SetCenter(0, 0, 0);      //圆心
 
      this->AxisCircleMapper[i]->SetInputConnection(this->AxisPolygonSource[i]->GetOutputPort());
      this->AxisCircel[i]->SetMapper(this->AxisCircleMapper[i]);
      this->AxisCircel[i]->GetProperty()->SetOpacity(1);    //透明度
      this->AxisCircel[i]->GetProperty()->SetLineWidth(g_circleLineWidth);
      this->AxisCircel[i]->GetProperty()->SetRepresentationToWireframe();//图形不填充,只要边框
      this->AxisCircel[i]->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);    //颜色
 
 
      vtkNew<vtkMatrix4x4> initMatrix;
      this->AxisCircel[i]->SetUserMatrix(initMatrix);
  }
  //圆形拾取
  this->CircelPicker = vtkCellPicker::New();
  this->CircelPicker->SetTolerance(0.001);
  for(int i=0;i<3;i++){
      this->CircelPicker->AddPickList(this->AxisCircel[i]);
  }
  this->CircelPicker->PickFromListOn();
  this->CurrentCircel=nullptr;
 
  //轴
  this->HandleAxis = new vtkAssembly* [3];
  for (int i=0; i<3; i++)
  {
      vtkNew<vtkLineSource> lineSource;
      vtkNew<vtkPolyDataMapper> lineMapper;
      vtkNew<vtkActor> lineActor;
 
      lineMapper->SetInputConnection(lineSource->GetOutputPort());
      lineActor->SetMapper(lineMapper);
 
      double point1[3];
      double point2[3];
      if(i==0){//x轴
          point1[0]=-g_axisLength;
          point1[1]=0;
          point1[2]=0;
 
          point2[0]=g_axisLength;
          point2[1]=0;
          point2[2]=0;
      }else if(i==1){//y轴
          point1[0]=0;
          point1[1]=-g_axisLength;
          point1[2]=0;
 
          point2[0]=0;
          point2[1]=g_axisLength;
          point2[2]=0;
      }else if(i==2){//z轴
          point1[0]=0;
          point1[1]=0;
          point1[2]=-g_axisLength;
 
          point2[0]=0;
          point2[1]=0;
          point2[2]=g_axisLength;
      }
      lineSource->SetPoint1(point1);
      lineSource->SetPoint2(point2);
      lineActor->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);
      lineActor->GetProperty()->SetLineWidth(g_axisLineWidth);
 
      //箭头
      vtkNew<vtkConeSource> coneSource1;
      vtkNew<vtkPolyDataMapper> coneMapper1;
      vtkNew<vtkActor> coneActor1;
      coneSource1->SetHeight( 0.2 );
      coneSource1->SetRadius( 0.04 );
      coneSource1->SetResolution( 10 );
      coneMapper1->SetInputConnection(coneSource1->GetOutputPort());
      coneActor1->SetMapper(coneMapper1);
 
      coneActor1->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);
      coneSource1->SetCenter(point2);
 
      if(i==0){//x轴
 
      }else if(i==1){//y轴
          coneActor1->SetOrigin(point2);
          coneActor1->RotateZ(90);
      }else if(i==2){//z轴
          coneActor1->SetOrigin(point2);
          coneActor1->RotateY(-90);
      }
      //
      vtkNew<vtkConeSource> coneSource2;
      vtkNew<vtkPolyDataMapper> coneMapper2;
      vtkNew<vtkActor> coneActor2;
      coneSource2->SetHeight( 0.2 );
      coneSource2->SetRadius( 0.04 );
      coneSource2->SetResolution( 30 );
      coneMapper2->SetInputConnection(coneSource2->GetOutputPort());
      coneActor2->SetMapper(coneMapper2);
 
      coneActor2->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);
      coneSource2->SetCenter(point1);
 
      if(i==0){//x轴
          coneActor2->SetOrigin(point1);
          coneActor2->RotateY(180);
 
          this->AxisPolygonSource[i]->SetNormal(1,0,0);
      }else if(i==1){//y轴
          coneActor2->SetOrigin(point1);
          coneActor2->RotateZ(-90);
 
          this->AxisPolygonSource[i]->SetNormal(0,1,0);
      }else if(i==2){//z轴
          coneActor2->SetOrigin(point1);
          coneActor2->RotateY(90);
 
          this->AxisPolygonSource[i]->SetNormal(0,0,1);
      }
 
      this->HandleAxis[i] = vtkAssembly::New();
      this->HandleAxis[i]->AddPart(lineActor);
      this->HandleAxis[i]->AddPart(coneActor1);
      this->HandleAxis[i]->AddPart(coneActor2);
 
      vtkNew<vtkMatrix4x4> initMatrix;
      this->HandleAxis[i]->SetUserMatrix(initMatrix);
  }
 
  //坐标轴拾取
  this->AxisPicker = vtkCellPicker::New();
  this->AxisPicker->SetTolerance(0.001);
  for (int i=0; i<3; i++)
  {
      this->AxisPicker->AddPickList(this->HandleAxis[i]);
  }
  this->AxisPicker->PickFromListOn();
  //
 
  // Internal data members for performance
  this->Transform = vtkTransform::New();
  this->PlanePoints = vtkPoints::New(VTK_DOUBLE);
  this->PlanePoints->SetNumberOfPoints(6);
  this->PlaneNormals = vtkDoubleArray::New();
  this->PlaneNormals->SetNumberOfComponents(3);
  this->PlaneNormals->SetNumberOfTuples(6);
  this->Matrix = vtkMatrix4x4::New();
 
}

2. 实现轴和圈的互动,以旋转为例。

void vtkMovableAxesRepresentation::Rotate(int X, int Y, double *p1, double *p2, double *vpn, int singleAxis)
{
    for(int i=0;i<3;i++){
        vtkMatrix4x4 *origin_matrixCircle = this->AxisCircel[i]->GetUserMatrix();
        vtkNew<vtkMatrix4x4> result_matrixCircle;
        RotateByMatrix(origin_matrixCircle,p1,p2,singleAxis,result_matrixCircle);
        this->AxisCircel[i]->SetUserMatrix(result_matrixCircle);
 
        vtkMatrix4x4 *origin_matrixAxis = this->HandleAxis[i]->GetUserMatrix();
        vtkNew<vtkMatrix4x4> result_matrixAxis;
        RotateByMatrix(origin_matrixAxis,p1,p2,singleAxis,result_matrixAxis);
        this->HandleAxis[i]->SetUserMatrix(result_matrixAxis);
    }
    this->Modified();
    this->BuildRepresentation();
}
void vtkMovableAxesRepresentation::RotateByMatrix(vtkMatrix4x4 *origin_matrix, double *p1, double *p2,int direction,vtkMatrix4x4* result_matrix)
{
    // 将鼠标位置移动到自身坐标系下,求两次鼠标位置向量在投影平面的夹角
    vtkNew<vtkTransform> trans;
    trans->SetMatrix(origin_matrix);
    double pos_t1[4] { p1[0], p1[1], p1[2], 1 };
    double pos_t2[4] { p2[0], p2[1], p2[2], 1 };
    vtkNew<vtkMatrix4x4> posture_inv;
    vtkMatrix4x4::Invert(origin_matrix, posture_inv);
    auto pos_t = posture_inv->MultiplyDoublePoint(pos_t1);
    double v1[3] = { pos_t[0], pos_t[1], pos_t[2] };
    pos_t = posture_inv->MultiplyDoublePoint(pos_t2);
    double v2[3] = { pos_t[0], pos_t[1], pos_t[2] };
    double normal[3];
 
    if(direction==0){
        normal[0] = 1;
        normal[1] = 0;
        normal[2] = 0;
 
    }else if(direction==1){
        normal[0] = 0;
        normal[1] = 1;
        normal[2] = 0;
    }else if(direction==2){
        normal[0] = 0;
        normal[1] = 0;
        normal[2] = 1;
    }
    double projection1[3], projection2[3];
    GetPlaneProjection(normal, v1, projection1);
    GetPlaneProjection(normal, v2, projection2);
    vtkMath::Normalize(projection1);
    vtkMath::Normalize(projection2);
    double axis[3];
    vtkMath::Cross(projection1, projection2, axis);
    double radians = acos(vtkMath::Dot(projection1, projection2));
    double degrees = vtkMath::DegreesFromRadians(radians);
    trans->RotateWXYZ(degrees, axis);
    result_matrix->DeepCopy(trans->GetMatrix());
}

3,感谢网友的分享。

https://blog.csdn.net/a15005784320/article/details/122096432

西门子PLC snap7

一. 需求
集成snap7库,对西门子plc进行访问。
二. 步骤

  1. 下载snap7 库,之后取build/bin/win64 下的 dll 和lib 直接使用。
    https://sourceforge.net/projects/snap7/files/1.4.2/

2. 把examples/cpp 下的 snap7.h 和cpp 加到工程里。

3. 封装一个单例 来使用 这个库。

#ifndef PLCMANAGER_H
#define PLCMANAGER_H

#include <QObject>
#include <snap7.h>

class PLCManager : public QObject
{
    Q_OBJECT
public:
    static PLCManager& getInstance() {
        static PLCManager instance;
        return instance;
    }
    PLCManager(const PLCManager&) = delete;
    PLCManager& operator=(const PLCManager&) = delete;

    //连接plc
    int connect(QString ip);
    //设置转台角度
    int setTurntableAngle(float value);

private:
    explicit PLCManager(QObject *parent = nullptr);
    ~PLCManager();

signals:

private:
    std::shared_ptr<TS7Client> client;
    bool bConnected{false};
};

#endif // PLCMANAGER_H
#include "plcmanager.h"

PLCManager::PLCManager(QObject *parent)
    : QObject{parent}
{
    client = std::make_shared<TS7Client>();
}

PLCManager::~PLCManager()
{

}

int PLCManager::connect(QString ip)
{
    int ret = client->ConnectTo(ip.toStdString().data(),0,1);
    if(ret==0){
        bConnected=true;
    }else{
        bConnected=false;
    }
    return ret;
}

int PLCManager::setTurntableAngle(float value)
{
    if(!bConnected)
        return -1;

    //测试
    byte bufferWrite[1]={1};

    client->MBWrite(100,1,&bufferWrite);
}

4. 使用的时候 直接调用业务函数。

   PLCManager::getInstance().connect("127.0.0.1");
   PLCManager::getInstance().setTurntableAngle(0);

5. 用Hsl工具测试下,这里再M100 位置 写入了一个byte ,值为1。

6,常用读写

void PLCManager::writeInt(int DBNumber, int StartByte, short value)
{
    if(!bConnected)
        return;
    uint8_t  bytes[2] = {0};
    std::memcpy(bytes, &value, sizeof(value));
    std::reverse(bytes, bytes + sizeof(bytes));
    client->WriteArea(Area,DBNumber,StartByte,2,S7WLByte,bytes);
}

short PLCManager::readInt(int DBNumber, int StartByte)
{
    if(!bConnected)
        return -1;

    uint8_t buffRead[2];
    int ret = client->ReadArea(Area,DBNumber,StartByte,2,S7WLByte,&buffRead);
    if(ret==0){
        short intValue = static_cast<int>((buffRead[1] & 0xFF) |
                                          ((buffRead[0] & 0xFF) << 8));

        return intValue;
    }else{
        return -1;
    }
}

void PLCManager::writeFloat(int DBNumber, int StartByte, float value)
{
    if(!bConnected)
        return;

    uint8_t  bytes[4] = {0};
    std::memcpy(bytes, &value, sizeof(value));
    std::reverse(bytes, bytes + sizeof(bytes));

    client->WriteArea(Area,DBNumber,StartByte,4,S7WLByte,bytes);
}

float PLCManager::readFloat(int DBNumber, int StartByte)
{
    if(!bConnected)
        return -1;

    uint8_t buffRead[4];

    int ret = client->ReadArea(Area,DBNumber,StartByte, 4,S7WLByte,buffRead);
    if(ret==0){
        uint32_t value = (static_cast<uint32_t>(buffRead[0]) << 24) |
                         (static_cast<uint32_t>(buffRead[1]) << 16) |
                         (static_cast<uint32_t>(buffRead[2]) << 8) |
                         static_cast<uint32_t>(buffRead[3]);

        float floatValue;
        std::memcpy(&floatValue, &value, sizeof(float));
        return floatValue;
    }else{
        return -1;
    }

}

void PLCManager::writeBool(int DBNumber, int StartByte, int StartBit, bool value)
{
    if(!bConnected)
        return;

    byte res[1]{0};
    int ret = client->ReadArea(Area,DBNumber,StartByte,1,S7WLByte,&res);
    if(ret==0){
        byte result[1]{0};
        if(!value){
            unsigned char mask = ~(1 << StartBit);
            result[0] = res[0] & mask;
        }else{
            unsigned char mask = 1 << StartBit;
            result[0] = res[0] | mask;
        }
        client->WriteArea(Area, DBNumber, StartByte, 1, S7WLByte, &result);
    }
}

bool PLCManager::readBool(int DBNumber, int StartByte, int StartBit)
{
    if(!bConnected)
        return -1;

    byte res[1]{0};
    //读一个字节 8位
    int ret = client->ReadArea(Area,DBNumber,StartByte,1,S7WLByte,&res);
    if(ret==0){
        //判断第StartBit位
        unsigned char mask = 1 << StartBit;
        unsigned char result = res[0] & mask;
        if(result!=0){
            return true;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

模块间通信

一,描述

Qt 通常使用信号和槽,进行模块间通信,但是绑定的时候,必须要有一个地方,同时知道信号和槽的所属对象,耦合性还是太紧密,这个库,只要按照规定的写法,就可以实现信号和槽的通信,并且不需要绑定,减少了一层耦合。

二,使用

  • A B两个类都需要包含头文件。#include PSEventController.h

  • A类中触发的地方,调用publish函数。

//比如按下按钮 ,需提供一个唯一的标识字符串,可自定义参数
PSEventController::publish(“addLine”,Q_ARG(bool,isChecked));

  • B类中 定义 on_psEvent_xxx 函数,并实现。

//此处的addLine 即为上述pulish 函数中的第一个参数,isChecked 为publis中的第二个参数
void on_psEvent_addLine(bool isChecked);

  • B类中初始化的时候 执行 subscribe 函数。

PSEventController::subscribe(this,addLine);

这样,A 和B 两个类 发布和订阅的唯一字符串标识符只要一致,A 在pubsh 的时候 B 就可以subscribe到,并且A 和B 完全耦合,相互不可见。

三,库 具体内容

//PSEventController.h
#ifndef PSEVENTCONTROLLER_H
#define PSEVENTCONTROLLER_H

#include <QObject>
#include <QReadWriteLock>
#include <QMap>
#include <QList>

#define METHOD_PREFIX on_psEvent_

class PSEventController : public QObject
{
    Q_OBJECT
public:
    static void unSubscribe(QObject* listener, const QByteArray& eventName);
    static bool subscribe(QObject* listener, const QByteArray& eventName);
    static bool publish(const QByteArray& eventName, Qt::ConnectionType connectionType,
        QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(),
        QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(),
        QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(),
        QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(),
        QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument());

    static inline bool publish(const QByteArray& eventName,
        QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(),
        QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(),
        QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(),
        QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(),
        QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument())
    {
        return publish(eventName, Qt::AutoConnection, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
    }

    static inline QString get_Errors()
    {
        return ps_LastError_;
    }

    static inline void clearEvents()
    {
        QWriteLocker locker(&ps_Lock_);
        psEvents_pool_.clear();
    }

    static inline QByteArray methodFormatting(const QByteArray& eventName) {
        return METHOD_PREFIX + eventName;
    }
private:
    static QMap<QByteArray, QList<QObject*>> psEvents_pool_;
    static QReadWriteLock ps_Lock_;

    static QString ps_LastError_;
};

#endif // PSEVENTCONTROLLER_H
//PSEventController.cpp
#include PSEventController.h
#include <QWriteLocker>

QMap<QByteArray, QList<QObject*>> PSEventController::psEvents_pool_;
QReadWriteLock PSEventController::ps_Lock_;
QString PSEventController::ps_LastError_;

void PSEventController::unSubscribe(QObject* listener, const QByteArray& eventName)
{
    QWriteLocker locker(&ps_Lock_);
    int index = -1;
    if (psEvents_pool_.contains(eventName) &&
        (index = psEvents_pool_[eventName].indexOf(listener)) >= 0 && index < psEvents_pool_[eventName].count())
        psEvents_pool_[eventName].takeAt(index);
}

bool PSEventController::subscribe(QObject* listener, const QByteArray& eventName)
{
    QWriteLocker locker(&ps_Lock_);
    if (psEvents_pool_.contains(eventName)) {
        if (-1 != psEvents_pool_[eventName].indexOf(listener)) {
            ps_LastError_ = QString(This object is subscribed to this eventName);
            return false;
        }
        psEvents_pool_[eventName].push_back(listener);
        return true;
    } else {
        psEvents_pool_.insert(eventName, { listener });
        return true;
    }
}

bool PSEventController::publish(const QByteArray& eventName, Qt::ConnectionType connectionType,
    QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3,
    QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7,
    QGenericArgument val8, QGenericArgument val9)
{
    QReadLocker locker(&ps_Lock_);
    if (!psEvents_pool_.contains(eventName)) {
        ps_LastError_ = QString(No objects subscribe to this eventName);
        return false;
    }
    auto methodName = methodFormatting(eventName);
    QStringList errors;
    for (auto listener : psEvents_pool_[eventName]) {
        if (!listener)
            continue;
        auto ret = QMetaObject::invokeMethod(listener, methodName, connectionType,
            val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
        if (!ret)
            errors.append(QString(%1:%2).arg(listener->metaObject()->className()).arg(listener->objectName()));
    }
    if (errors.isEmpty())
        return true;
    ps_LastError_ = QString(%1 execution failed:[\n).arg(QString(eventName));
    for (auto& err : errors)
        ps_LastError_ += QString(%1;\n).arg(err);
    ps_LastError_ += ]\n;
    return false;
}

mqtt

一, 需求
Qt使用mqtt协议与服务端进行通信。

二,使用

  1. 所有源码加入到工程里,我比较喜欢源码引用,这样在跨平台时,省去了路径的配置。
    https://github.com/emqx/qmqtt
  2. 写一个单例,建立连接,并在连接成功时,订阅 主题。
#ifndef MQTTMANAGERT_H
#define MQTTMANAGERT_H

#include <iostream>
#include <mqtt/qmqtt.h>

class MQTTManager:public QObject
{
    Q_OBJECT
public:
    static MQTTManager* instance();

    void setInTemperature(QString newInTemperature);

private:
    ~MQTTManager();
    MQTTManager();

public slots:
    void doConnected();
    void onSubscribed(const QString& topic);
    void doDisconnected();
    void doDataReceived(QMQTT::Message message);

signals:
    void inTemperatureChanged();

private:
    //mqtt客户端
    QMQTT::Client *client;
    static MQTTManager *manager;

    //室内温度
    QString inTemperature;

};
#endif // MQTTMANAGERT_H
#include "mqttManagert.h"
MQTTManager* MQTTManager::manager=nullptr;
const QString HOST= "127.0.0.1";

//温度和湿度
const QString TOPIC_IN_TEMPERATURE_HUMIDITY ="qmqtt/in_temperature_humidity";

MQTTManager::MQTTManager(){
    client = new QMQTT::Client(QHostAddress(HOST),1883,this);
    connect(client,&QMQTT::Client::connected,this,&MQTTManager::doConnected);
    connect(client,&QMQTT::Client::disconnected,this,&MQTTManager::doDisconnected);
    connect(client,&QMQTT::Client::received,this,&MQTTManager::doDataReceived);
    connect(client, &QMQTT::Client::subscribed, this, &MQTTManager::onSubscribed);

    client->connectToHost();
}

MQTTManager::~MQTTManager(){
    client->disconnected();
}

MQTTManager* MQTTManager::instance(){
    if(!manager){
        manager = new MQTTManager();
    }
    return manager;
}

void MQTTManager::doConnected(){
    qDebug()<<"doConnected ok";
    client->subscribe(TOPIC_IN_TEMPERATURE_HUMIDITY);
}
void MQTTManager::onSubscribed(const QString& topic)
{
    qDebug() << "onSubscribed " << topic;
}

//解析数据
void MQTTManager::doDataReceived(QMQTT::Message message){
    QString mes = QString(message.id())+" "+QString(message.qos())+" "+message.topic()+" "+message.payload()+"\n";
    if(message.topic()==TOPIC_IN_TEMPERATURE_HUMIDITY){
        QStringList dataList = QString(message.payload()).split(",");
        if(dataList.size()==2){
            setInTemperature(dataList[0]);
        }
    }
}

void MQTTManager::doDisconnected(){
    qDebug()<<"doDisconnected ok";
}

void MQTTManager::setInTemperature(QString newInTemperature)
{
    if (inTemperature == newInTemperature)
        return;
    inTemperature = newInTemperature;
    emit inTemperatureChanged();
}
  1. 发布主题,直接调用publish函数
    publish(const QMQTT::Message& message)

http

一,需求
Qt调用http接口,如果使用本身的network库需要做大量的工作,采用httplib这个库,则调用起来就特别优雅。

二,使用

  1. 这个库就是一个头文件,直接include进去即可。
    https://github.com/yhirose/cpp-httplib

  2. 看一个客户端列子。

    #include <httplib.h>
    #include <iostream>
    int main(void)
    {
    //定义客户端
    httplib::Client cli("localhost", 1234);
    
    //调用get接口
    if (auto res = cli.Get("/hi")) {
    if (res->status == StatusCode::OK_200) {
      std::cout << res->body << std::endl;
    }
    } else {
    auto err = res.error();
    std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
    }
    }

  3. 服务端写起来也很方便。

    #include <httplib.h>
    int main(void)
    {
    using namespace httplib;
    
    Server svr;
    
    svr.Get("/hi", [](const Request& req, Response& res) {
    res.set_content("Hello World!", "text/plain");
    });
    
    // Match the request path against a regular expression
    // and extract its captures
    svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
    auto numbers = req.matches[1];
    res.set_content(numbers, "text/plain");
    });
    
    // Capture the second segment of the request path as "id" path param
    svr.Get("/users/:id", [&](const Request& req, Response& res) {
    auto user_id = req.path_params.at("id");
    res.set_content(user_id, "text/plain");
    });
    
    // Extract values from HTTP headers and URL query params
    svr.Get("/body-header-param", [](const Request& req, Response& res) {
    if (req.has_header("Content-Length")) {
      auto val = req.get_header_value("Content-Length");
    }
    if (req.has_param("key")) {
      auto val = req.get_param_value("key");
    }
    res.set_content(req.body, "text/plain");
    });
    
    svr.Get("/stop", [&](const Request& req, Response& res) {
    svr.stop();
    });
    
    svr.listen("localhost", 1234);
    }
//客户端传图例子
QString file = QCoreApplication::applicationDirPath() + "/hf.jpg";
httplib::Client cli(ip, port);
cli.set_connection_timeout(0, 300000); // 300 milliseconds
cli.set_read_timeout(5, 0); // 5 seconds
cli.set_write_timeout(5, 0); // 5 seconds

std::ifstream sfile(file.toStdString().c_str(), std::ios::binary);
std::string imageData((std::istreambuf_iterator<char>(sfile)), std::istreambuf_iterator<char>());
httplib::MultipartFormDataItems items = {
{"file", imageData,  "hf.jpg", "application/octet-stream"},
};
auto res = cli.Post("/infer", items);
if (res&&res->status == 200)
{
		std::string body = res->body;
		std::cout << "上传成功: " << body;
}