QT自定义控件之多发酵罐并联工艺(源码开源)

一、前言

在工控、生物制药仿真场景中,组态可视化界面是上位机软件非常核心的一部分。市面上成熟的组态软件固然强大,但定制化成本高,很多项目需要我们基于 Qt 自主绘制工艺流程图,实现管道流体流动、罐体液位仿真、状态闪烁告警、实时数据监控以及视图缩放平移全套交互。

本次项目基于C++ + Qt + QPainter从零实现生物发酵工艺流程仿真界面,没有使用第三方组态组件,背景图+动画+流体效果等完成。本文就来拆解这套工业仿真界面背后的实现思路,给做 Qt 工控可视化的开发者提供参考。

技术栈:C++、Qt、QPainter、QTimer、坐标矩阵变换

二、作品展示

先来上作品进行展示

PixPin_2026-08-30_13-29-05

 

PixPin_2026-08-30_16-12-04

 

本项目实现多发酵罐并联工艺组态仿真界面,完整还原培养基罐、补料罐、发酵罐 A、收集母组件、成品罐整套设备管线。 界面具备完整工业仿真能力:管道内介质沿着管路做流动动画;各个罐体实时仿真液体存储高度;所有仪表面板实时刷新温度、PH、含氧量、压力等工艺参数;同时支持画布整体放大、缩小、拖拽平移,放大后管线、罐体、文本仪表不会出现错乱。整体效果如截图所示,所有动画全部在 QWidget 的paintEvent中渲染。

界面元素:

1、设备:各类 立式储罐、发酵反应釜、阀门压力表、仪表显示面板

2、管线:多色工艺管道,不同颜色区分介质类型

3、动画:管道流体流动、罐体液位升降

4、数据:温度、PH、压力、含氧量动态监控面板

5、交互:画布缩放、拖拽,仿真启停控制

亮点展示:

亮点 1:管道流水的紧密贴合机制
工业组态最容易翻车的就是管道流体动画。简单直线管道还好处理,一旦遇到直角转弯、多路分叉、管道汇流,直接绘制线段做流动粒子会出现转弯处流体断裂、错位,视觉上流体 “穿墙”,不符合真实管路走向。

亮点 2:各种原料罐的液体存储仿真机制
界面包含多种罐体:培养基罐、补料罐、发酵罐、成品罐,罐体外形各不相同(圆筒、带封头反应釜),需要仿真罐内不同高度的液体,同时上层保留气体空间。

亮点 3:动态监控面板,实时刷新工艺参数
界面多处悬浮仪表面板,展示温度、PH、含氧量、压力,数据跟随仿真流程实时变化。

亮点 4:放大缩小自如 —— 画布视图变换机制
整套工艺图纸元件繁多,用户需要可以滚轮缩放、鼠标拖拽平移整个工艺画布,缩放之后,罐体、管道、文字、告警灯全部同步缩放,坐标不能错乱。

整套上位机组态仿真没有依赖任何第三方绘图库,纯粹依靠 C+++Qt 的 QPainter 完成复杂工业界面。

管道流体的核心在于基于QPainterPath路径采样,保证拐角分叉流体连续;

罐体液体仿真重点使用裁剪路径,适配异形反应釜;

动态监控按需刷新,降低 CPU 占用;

QPainter 做工业组态虽然需要手写大量绘图代码,但是自由度极高,可以完全定制工艺界面。适合做中小型仿真上位机,不需要引入重型组态 SDK。在后续迭代中还可以继续拓展:鼠标点击阀门开关、导出工艺截图、数据曲线趋势图等功能。

三、实现流程

1、主窗口结构

主窗口MainWindow,右上角放置普通 QPushButton【开始】按钮;中心放置我们自定义的绘图控件CustomWidget(继承自QWidget

int display[4] = {14,15,16,17};
QString state[3]  = {"停止","运行中..","保修中.."};
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    timer = new QTimer(this);  // 创建定时器对象
    static int count = 8;
    static bool flag = 0;
    static int colorflag = 0;
            connect(timer,&QTimer::timeout,
                       [=]()
               {
                  flag = !flag;
                  int randomNumber = QRandomGenerator::global()->bounded(100); // 生成一个0到99之间的随机数(包括0和99)
                  ui->widget->Set_TextChangeEvent(count,QString::number(randomNumber));
                  count++;
                  if(count>13)
                  {
                      count = 8;
                  }
                  //传感器控制
//                  ui->widget->Set_TextChangeEvent(count + 4,QString::number(randomNumber));
                  int valueRandomNumber = QRandomGenerator::global()->bounded(8); // 生成一个0到99之间的随机数(包括0和99)
                  ui->widget->Set_TextChangeEvent(valueRandomNumber,QString::number(randomNumber * 1.1,'f',1));
               }
               );
}

  

2、核心代码

本期的核心代码主要在CustomWidget.cpp中

部分代码展示:

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
# pragma execution_character_set("utf-8")
#endif
void rotateAndPaintRect(QPainter *painter, const QRectF &rect, int angle,bool isRound = false,int rx = 0,int ry = 0)
{
qreal cx = 0;
qreal cy = 0;
cx = rect.center().x();
cy = rect.center().y();
painter->save();
painter->translate(cx ,cy);
painter->rotate(angle);
QPointF drawOffset = rect.topLeft() - QPointF(cx,cy);
painter->drawRect(drawOffset.x(), drawOffset.y(), rect.width(), rect.height());
painter->restore();
}
void DrawText(QPainter &painter,const QRectF &rect,QString str,double angle,int size,QString fontFamily,int textAlignment)
{
QRectF rotatedRect(-rect.width()/2, -rect.height()/2, rect.width(), rect.height());
qreal cx = rect.x() + rect.width() / 2;
qreal cy = rect.y() + rect.height() / 2;
QFont font = QFont(fontFamily, size);
painter.setFont(font);
painter.save();
painter.translate(cx, cy);
painter.scale(1, -1);
painter.rotate(-angle);//旋转角度
painter.drawText(rotatedRect,textAlignment,str);
//恢复上面保存的状态
painter.restore();
}
void DrawImage(QPainter &painter, QImage &image, const QRectF &rect,double angle)
{
QRectF rotatedRect(-rect.width()/2, -rect.height()/2, rect.width(), rect.height());
painter.drawImage(rotatedRect, image);
}

 

四、源码开放

本期源码开放,有需要的朋友可以去某某v,“Q态王”,~

 

posted on 2026-08-31 11:14  Littlehero_121  阅读(9)  评论(0)    收藏  举报

导航