# FastCAE-AppDesigner **Repository Path**: shundongyang/fastcae-app-designer ## Basic Information - **Project Name**: FastCAE-AppDesigner - **Description**: FastCAE-AppDesigner - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 8 - **Created**: 2025-01-21 - **Last Updated**: 2025-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 FastCAE 的APP设计器 # 编译 软件和`VTK`、`OCC`库强相关,本版本在Qt5.14.2 VS2017 X64下编译,`OCC`和`VTK`下提供编译好的dll文件和lib文件,如果非此环境,需要重新编译`VTK`和`OCC` # 逻辑编辑器 逻辑编辑器负责组织描述逻辑,以工作流为基础,fastcae-app-designer的工作流以插件模式加载入程序中,主要涉及以下两个基础个模块(模块顺序为依赖顺序):`FCPlugin`、`FCNode`,同时`Plugin\FCUtilNodePlugin`为fastcae-app-designer的基础节点插件,其他的插件编写可参考此插件编写。 # 插件 ## FCPlugin插件管理模块 `FCPlugin`模块是为了提供一个统一且简单的插件管理功能,此模块提供了插件的基类`FCAbstractPlugin`,作为fastcae-app-designer的插件,只要继承此类,即可实现插件。 同时,`FCPlugin`模块提供了所有插件管理的单例:`FCPluginManager`,这是一个单例,可以用来加载和卸载插件,下面是通过此类加载插件的例子: ```cpp //加载插件 FCPluginManager& plugin = FCPluginManager::instance(); if (!plugin.isLoaded()) { plugin.load(); } //获取插件 QList plugins = plugin.getPluginOptions(); for (int i = 0; i < plugins.size(); ++i) { FCPluginOption opt = plugins[i]; if (!opt.isValid()) { continue; } FCAbstractPlugin *p = opt.plugin(); //开始通过dynamic_cast判断插件的具体类型 if (FCAbstractNodePlugin *np = dynamic_cast(p)) { //说明是节点插件 ... qDebug() << tr("succeed load plugin ") << np->getName(); } } ``` `FCPluginOption`维护着每个插件的内容,内部包含了此插件(dll)的一些基本信息 ## 编写一个FCPlugin插件 要实现一个插件,必须在lib中实现两个函数,和实现一个插件类,以`FCUtilNodePlugin`为例,具体可参见`src\Plugin\FCUtilNodePlugin` - 首先继承`FCAbstractNodePlugin`实现自己的插件类,这里为了进一步抽象,定义了节点插件的基类`FCAbstractNodePlugin`,`FCUtilNodePlugin`继承于`FCAbstractNodePlugin` ```cpp class FCNODE_API FCAbstractNodePlugin : public FCAbstractPlugin { public: FCAbstractNodePlugin(); virtual ~FCAbstractNodePlugin(); ... }; class FCUtilNodePlugin : public FCAbstractNodePlugin { public: FCUtilNodePlugin(); ~FCUtilNodePlugin(); }; ``` `FCUtilNodePlugin`里将实现具体的功能,这里不描述 - 导出函数`plugin_create`和`plugin_destory`的实现 在实现了`FCUtilNodePlugin`类后,还需要实现两个C标准的导出函数,`plugin_create`和`plugin_destory`: ```cpp extern "C" FCUTILNODEPLUGIN_API FCAbstractPlugin *plugin_create(); extern "C" FCUTILNODEPLUGIN_API void plugin_destory(FCAbstractPlugin *p); ``` 这两个函数是关键,插件系统在加载时需压根据c函数名来查找这两个函数 这两个函数的功能很简单, 就是生成插件,和删除插件,具体实现如下: ```cpp FCAbstractPlugin *plugin_create() { return (new FCUtilNodePlugin()); } void plugin_destory(FCAbstractPlugin *p) { delete p; } ``` 这样,一个插件就能正常运行。 # 工作流 工作流主要的模块为`FCNode`,这个模块为工作流的核心和最基本的抽象,此模块主要的几个类为: - 工作流的基本逻辑类: `FCWorkFlow`,`FCAbstractNode`,`FCAbstractNodeFactory` - 工作流的显示类: `FCAbstractNodeGraphicsItem`,`FCAbstractNodeLinkGraphicsItem`,`FCAbstractNodeWidget`,`FCNodeGraphicsScene`,`FCNodeGraphicsView` 工作流基于插件模式编写,后续用户可基于`FCNode`模块实现自己的逻辑,具体可参见`src\Plugin\FCUtilNodePlugin` ## 工作流的思路 工作流由节点组成,节点间传递数据,每个节点可以运行用户定义的逻辑,一个工作流就是多个节点以及节点间的关系描述,节点有输入和输出,节点的输出可流入到下一个节点中,作为下个节点的输入。 每个节点的数据流出时,会收集输入数据,进行逻辑运算,再输出数据。 因此工作流的关键就是每个节点,每个节点可以理解为编程过程中的函数,节点的输入就是函数的传参,节点的输出就类似于函数的返回。 ## 节点 节点作为工作流的关键,`FCNode`模块的关键类就是`FCAbstractNode`,工作流都面向这个基类,具体功能的节点由用户自己继承`FCAbstractNode`来实现,例如`src\Plugin\FCUtilNodePlugin\FCVariantValueNode.h` `FCAbstractNode`描述了节点的抽象信息,包括输入,输出,节点的连接,如果以一个函数为例,要通过一个节点描述一个类似函数的功能,这个节点应该具备如下要素: - 输入 - 输出 - 逻辑 因此fastcae-app-designer的节点定义如下: ![img](doc/PIC/节点说明示意图.png) 这个节点表示了一个加法函数 ```py def add(a,b): return a+b ``` 它有两个输入端a和b,他有一个输出端,由于函数的`return`是不需要具体描述的,但节点的输出要为其定义名称,因为后面节点的传递过程需要用到上个节点的输出,为了能知道哪个输出,所以输出也要定义名称。 因此节点的抽象类定义了输入参数名和输出参数名,对应两个接口函数:`getInputKeys`和`getOutputKeys` ```cpp //获取所有输入的参数名 virtual QList getInputKeys() const = 0; //获取所有输出的参数名 virtual QList getOutputKeys() const = 0; ``` 要实现一个节点,必须指定这个节点输入什么,输出什么,当然,可以没有输入,也可以没有输出。 ## 节点的生成 上面说了节点的抽象,实际一个工作流是由多个节点组成,而fastcae-app-designer是由插件组成,如何知道程序有哪些节点,这里参考程序开发的`import` 节点的生成由节点工厂实现,节点工厂可以理解为一个程序包,程序包里有多个函数,节点工厂正是这样一个程序包。 `FCAbstractNodeFactory`类就是节点的抽象工厂,此类关键的函数是`create`接口,工厂将通过`FCNodeMetaData`来生成一个节点 ```cpp //工厂函数,创建一个FCAbstractNode,工厂不持有FCAbstractNode的管理权 virtual FCAbstractNode *create(const FCNodeMetaData& meta) = 0; ``` `FCNodeMetaData`是节点的基本信息,其关键函数是: ```cpp QString getNodePrototype() const; ``` `FCNodeMetaData`通过NodePrototype来决定`new`哪个节点。 一个程序可以`import`多个包,因此工作流也应该有多个节点工厂,汇总到一个"集团"中,"集团"再生成节点 这个"集团"就是`FCWorkFlow`类,这个类汇总了所有工厂,其关键函数如下: ```cpp //注册工厂 void registFactory(FCAbstractNodeFactory *factory); //创建节点,会触发信号nodeCreated,FCWorkFlow保留节点的内存管理权 FCAbstractNodeSmtPtr createNode(const FCNodeMetaData& md); ``` "集团"汇总了所有工厂,节点通过"集团"创建,程序启动过程,先加载插件,插件生成工厂,工厂在集团中注册,最后再通过集团生成节点,具体流程如下: ![img](doc/PIC/节点工厂注册到工作流.png) 在程序启动完成后,所有节点工厂到注册到`FCWorkFlow`中,通过`FCWorkFlow`即可生成节点。 ## 节点的渲染 节点的渲染基于Qt的`Graphics View Framework`,通过Graphics View来实现渲染,整个工作流作为一个画布(QGraphicsScene),为此`FCNode`模块提供了`FCNodeGraphicsScene`,`FCNodeGraphicsView`和`FCAbstractNodeGraphicsItem`来实现 `FCAbstractNodeGraphicsItem`对应一个`FCAbstractNode`,实际是通过`FCAbstractNode`的`createGraphicsItem`接口来生成`FCAbstractNodeGraphicsItem` 因此,要实例化一个节点还需要实现接口函数`FCAbstractNode::createGraphicsItem` ```cpp //节点对应的item显示接口,所有node都需要提供一个供前端的显示接口 virtual FCAbstractNodeGraphicsItem *createGraphicsItem() = 0; ``` 这个接口返回`FCAbstractNode`对应的GraphicsItem,`FCNode`模块把节点的GraphicsItem抽象为`FCAbstractNodeGraphicsItem`,`FCAbstractNodeGraphicsItem`实现了节点的基本渲染,结合`FCNodeGraphicsScene`和`FCAbstractNodeLinkGraphicsItem`实现节点输入输出的连接工作。 对于用户来说,用户操作节点实现了节点之间的关系连接,这个要反应到逻辑层,逻辑层既为`FCAbstractNode`,因此,前端看到的连线动作,逻辑端实际是调用`FCAbstractNode`的`linkTo`函数, ```cpp //建立连接,如果基础的对象需要校验,可继承此函数 virtual bool linkTo(const QString& outpt, Pointer toItem, const QString& inpt); ``` 在前端是通过`FCAbstractNodeLinkGraphicsItem`实现两个`FCAbstractNodeGraphicsItem`的连接,用户基本不需要关注`FCAbstractNodeLinkGraphicsItem`,用户只需要关心节点的连接点的生成。 用户实现自己的节点需要继承`paint`和`boundingRect`函数 ```cpp //绘图 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); //绘图相关 QRectF boundingRect() const; ``` `FCAbstractNodeGraphicsItem`已经默认实现了输入输出点的绘制,如果需要自定义,可以重写`paintLinkPoint`函数 ``` //绘制某个连接点 virtual void paintLinkPoint(const FCNodeLinkPoint& pl, QPainter *painter); ``` `FCNode`模块提供了标准的节点渲染类`FCStandardNodeGraphicsItem`,此类实现了节点的名字显示和输入输出的显示。 ### 响应Node事件 节点存在一些动作,如被传入参数等操作时,应该反映到GraphicsItem执行某些绘图操作,这时可以通过重写`FCAbstractNodeGraphicsItem::nodeAction`函数来实现 ```cpp virtual void nodeAction(int action, const QVariant& v); ``` action动作参见`FCAbstractNode::NodeAction`枚举 ### 标准节点渲染:FCStandardNodeGraphicsItem 标准节点渲染渲染了`FCAbstractNode`的输入端,输出端,和节点名,其效果如下: ![img](doc/PIC/节点连接示意.gif) 上面演示了两种节点,一个节点只有输出,一个节点有3个输入,2个输出。 标准渲染节点具备如下特性 - 标准节点由矩形绘制 - 标准节点内部显示节点的名字 - 输入端位于左侧,垂直均匀分布,以空心方块代表 - 输出端位于右侧,垂直均匀分布,以实心方块代表 - 点击输出端进入连线模式,鼠标点击对应的输入端实现节点与节点之间的关系建立 - 连线过程点击鼠标右键取消连接状态 - 选择连接线能看到连接点的名字 ## 节点的操作 用户对节点的操作,例如赋予初始值等等需要用过界面实现,这时需要提供一个窗口给用户,`FCNode`模块提供了`FCAbstractNodeWidget`用于触发窗口事件。 `FCAbstractNodeWidget`由`FCAbstractNodeGraphicsItem`产生,核心函数位于为`FCAbstractNodeGraphicsItem`的两个成员函数: ```cpp //获取节点对应的窗口,一般保存节点的设置 FCAbstractNodeWidget *getNodeWidget() const; void setNodeWidget(FCAbstractNodeWidget *p); ``` 在示例程序`src\FCMethodEditor`中,用户点击节点后,会把节点对应的窗口显示在dockwidget中,如下图所示 ![img](doc/PIC/节点设置窗口.gif) 上例中在点击节点item后,调用节点的`FCAbstractNodeGraphicsItem::getNodeWidget`函数获取窗口,并设置到dockwidget中。 因此,在实例化`FCAbstractNodeGraphicsItem`过程中,一个重要事项是设置节点窗口 以`FCVariantValueNodeGraphicsItem`为例,其构造函数如下: ``` FCVariantValueNodeGraphicsItem::FCVariantValueNodeGraphicsItem(FCVariantValueNode *n, QGraphicsItem *p) : FCStandardNodeGraphicsItem(n, p) { FCAbstractNodeWidget *w = new FCVariantValueNodeWidget(this); setNodeWidget(w); } ``` `setNodeWidget`设置了节点的窗口,使得主窗口可以获取一个窗口指针显示,并对节点进行设置 节点窗口实现了对节点的操作,设置参数进节点中 ``` void FCVariantValueNodeWidget::apply() { QString name = ui->lineEditName->text(); QString vstr = ui->lineEditValueText->text(); QString typestr = ui->comboBoxType->currentText(); if ("string" == typestr) { typestr = "QString"; }else if ("point" == typestr) { typestr = "QPoint"; }else if ("pointF" == typestr) { typestr = "QPointF"; }else if ("time" == typestr) { typestr = "QTime"; }else if ("datetime" == typestr) { typestr = "QDateTime"; }else if ("size" == typestr) { typestr = "QSize"; }else if ("color" == typestr) { typestr = "QColor"; } QVariant v = FCDataPackage::stringToVariant(vstr, typestr); qDebug() << "typestr:"<labelInfo->setText(tr("variant is invalid!")); return; } m_data.setValueItem("value", FCDataItem(v, name)); ui->labelInfo->setText(tr("")); FCAbstractNode *n = getNode(); if (n) { n->input("_value", m_data.copy()); } } void FCVariantValueNodeWidget::on_comboBoxType_currentIndexChanged(const QString& arg1) { Q_UNUSED(arg1); apply(); } void FCVariantValueNodeWidget::on_lineEditValueText_editingFinished() { apply(); } ```