# 批量仿真 APP
本文介绍如何批量仿真 APP。
# 案例介绍
批量仿真插件围绕模型试验,提供了模型参数修改,批量仿真和结果批量显示功能。学生可以通过选择想要试验的参数,对参数试验数据进行批量编辑,并应用该数据完成批量仿真,显示结果,比较不同模型参数对试验结果的影响情况。
批量仿真插件如图所示,插件的使用流程是首先启动 MWORKS,打开需要批量仿真的模型,至少翻译一次之后,在工具栏中单击批量仿真按钮,弹出批量仿真配置窗口。
首先单击选择参数,在弹出的面板中选择需要批量仿真的参数集合,然后单击“+”添加数据集,接着修改数据集并单击开始批量仿真按钮,待进度条到达 100% 后,单击加载结果按钮,将批量仿真的结果加载到 MWORKS 结果面板。
# APP 开发
以下将从总体的框架搭建到细节的功能实现讲解使用 SDK 开发 MWORKS 插件的方法。
# 搭建框架
SDK 提供插件基类 MwPlugin,插件的初始化和析构都基于 MwPlugin 进行,进行插件开发时需要继承 MwPlugin 并至少实现它的所有纯虚函数,MwPlugin 类如下图所示:
class EXTENSIONSYSTEM_EXPORT MwPlugin : public QObject
{
Q_OBJECT
public:
enum ShutdownFlag {
SynchronousShutdown,
AsynchronousShutdown
};
MwPlugin();
~MwPlugin();
virtual bool initialize(QString *errorString) = 0;
virtual void extensionsInitialized() = 0;
virtual bool delayedInitialize() { return false; }
virtual ShutdownFlag aboutToShutdown() { return SynchronousShutdown; }
signals:
void asynchronousShutdownFinished();
};
- initialize:该接口用于初始化插件内部状态,在加载插件并创建 MwPlugin 实例后被 MWORKS 调用。该接口按照插件注册顺序依次调用,并且保证被依赖插件初始化先于依赖插件初始化(插件依赖在 json 文件的 Dependencies 条目配置);
- extensionsInitialized:所有插件的 initialize 执行完毕后,会按照插件注册顺序依次执行 extensionsInitialized,并且保证被依赖插件的 extensionsInitialized 先于依赖插件执行(插件依赖在 json 文件的Dependencies条目配置);
- delayedInitialize:所有插件的 extensionsInitialized 执行完毕后,会按照插件注册顺序依次执行 delayedInitialize,并且保证被依赖插件的 delayedInitialize 先于依赖插件执行(插件依赖在 json 文件的 Dependencies 条目配置);
- aboutToShutdown:该接口用于断开与其他插件的连接并优化关闭。如果插件需要将实际关闭延迟一段时间,则插件可以从该接口返回
MwPlugin::AsynchronousShutdown,MWORKS 将在收到所有延迟关闭插件的 asynchronousShutdownFinished 信号之前保持主线程事件循环运行; - asynchronousShutdownFinished:用于插件异步关闭时,通知 MWORKS 主程序插件异步关闭已完成。
MwBatchSimPlugin
MwBatchSimPlugin 继承 MwPlugin,实现了 initialize 和 extensionsInitialized 两个纯虚函数,在 initialize 中将批量仿真按钮添加到 MWORKS 工具栏中,extensionsInitialized 的实现为空。
bool MwBatchSimPlugin::initialize(QString *errorString) { MwWorkbench *work_bench = MwAppPtr->GetWorkbench(); work_bench->AddActionToToolBar(actionBatchSim); return true; } void MwBatchSimPlugin::extensionsInitialized() { }单击批量仿真按钮时,会触发槽函数 SlotShowBatchSimDlg,槽函数中获取 MWORKS 当前模型键值并依赖模型编译结果构造 MwSimData 对象,以此创建批量仿真对话框 MwBatchSimDialog。MWORKS 相关的对象均借由 MwAppPtr 获取,它的定义位于 MwAPP(MWORKS 应用程序对象)中。
void MwBatchSimPlugin::SlotShowBatchSimDlg() { if (batchSimDlg == nullptr) { MwMoGraphicsViewController *mo_controller = MwAppPtr->GetWorkbench()->GetMoGVController(); MoKey cur_key = mo_controller->GetCurrentClassKey(); if (cur_key == 0) { return; } MwClassManager *class_mgr = MwAppPtr->GetClassManager(); //打开对话框前先编译一次模型 std::string sim_path = class_mgr->GetCoreOption()->SimResultPath(); std::wstring mo_name = QString::fromStdString(class_mgr->GetMoHandler()->GetElementName(cur_key)).toStdWString(); std::wstring result_dir = QString::fromStdString(sim_path).toStdWString() + L"/" + mo_name; std::wstring data_path = result_dir + L"/Result.msr"; baseSimData = new MwSimData(mo_name, data_path, result_dir); if (!baseSimData->InitializeSimInst()) { return; } batchSimDlg = new MwBatchSimDialog(MwAppPtr->GetClassManager(), cur_key, baseSimData, MwAppPtr->GetWorkbench()); } batchSimDlg->show(); }MwBatchSimPlugin 头文件中还需要注册 MWORKS 插件元数据类型,并配置对应的 json 插件描述文件。
Q_PLUGIN_METADATA(IID MwAppId FILE "mw_batch_sim.json")在
mw_batch_sim_plugin.cpp所在目录下新建mw_batch_sim.json文件并进行配置。{ "Name": "Batch Simulation", "Version": "1.0", "CompatVersion": "1.0", "Vendor": "苏州同元", "Copyright": "(c) 2010-2023", "License": [], "Category": "SDK", "Description": "实现带有批量仿真功能的MWORKS.Sysplorer插件。", "Description_en": "batch simulation plugin for MWORKS.Sysplorer.", "Options": [], "Url": "http://www.tongyuan.cc", "Dependencies": [] }- Name:插件名称
- Version:插件版本
- CompatVersion:插件兼容版本
- Vendor:供应商
- Copyright:版权
- License:许可
- Category:类别
- Description:描述
- Description_en:英文描述
- Options:选项
- Url:网址
- Dependencies:依赖的插件
MwBatchSimDialog
MwBatchSimDialog 是批量仿真插件的主界面,由选择参数、编辑参数、批量仿真三个部分组成。
MwParamSelectDialog
MwParamSelectDialog 为批量仿真参数选择对话框,内部初始化一棵 MwParamSelectTree 批量仿真参数选择树。
void MwParamSelectDialog::SetupUi() { QVBoxLayout *vbox_layout = new QVBoxLayout(this); paramTree = new MwParamSelectTree(classMgr, curKey, this); vbox_layout->addWidget(paramTree); }MwParamEditTable
MwParamEditTable 继承 QTableWidget,负责显示和编辑批量仿真参数。
# 功能实现
# 选择参数
MwBatchSimDialog 通过接口SlotSelectParam弹出选择参数对话框,接口先检查是否之前已经有选择过参数,如果有则读取之前的参数列表来初始化,最后弹出 MwParamSelectDialog 批量参数选择对话框。
void MwBatchSimDialog::SlotSelectParam()
{
MwParamSelectDialog param_select_dlg(classMgr, curKey, this);
QStringList param_name_list;
paramTable->GetParamNameList(param_name_list);
param_select_dlg.InitParamSelectTreeCheckState(param_name_list);
if (param_select_dlg.exec() == QDialog::Accepted)
{
param_name_list.clear();
param_select_dlg.GetSelectedParamNameList(param_name_list);
paramTable->UpdateParamEditTable(param_name_list);
}
}
MwParamSelectDialog 中初始化 MwParamSelectTree。
void MwParamSelectDialog::SetupUi()
{
QVBoxLayout *vbox_layout = new QVBoxLayout(this);
paramTree = new MwParamSelectTree(classMgr, curKey, this);
vbox_layout->addWidget(paramTree);
}
MwParamSelectTree 中的 InitParamSelectTree 接口负责初始化批量仿真参数选择树。
void MwParamSelectTree::InitParamSelectTree()
{
//获取顶层参数信息
std::vector<MwParamInfo> top_param_lst;
if (!classMgr->GetMoHandler()->GetParamInfoEx(curKey, MwStrVector(), &top_param_lst))
{
return;
}
for (auto top_param_info : top_param_lst)
{
QTreeWidgetItem *top_item = CreateParamItem("", &top_param_info);
this->addTopLevelItem(top_item);
}
//获取嵌套组件信息
std::vector<MoKey> comp_keys;
classMgr->GetMoHandler()->GetNestedComponentKeys(curKey, comp_keys);
for (auto nested_comp_key : comp_keys)
{
QTreeWidgetItem *comp_item = CreateCompItem(nested_comp_key);
//获取嵌套组件下的参数信息
std::vector<MwParamInfo> nested_param_lst;
std::string nested_comp_name = classMgr->GetMoHandler()->GetElementName(nested_comp_key);
MwStrVector comp_nodes;
comp_nodes.push_back(nested_comp_name);
if (classMgr->GetMoHandler()->GetParamInfoEx(curKey, comp_nodes, &nested_param_lst))
{
if (!nested_param_lst.empty())
{
this->addTopLevelItem(comp_item);
}
for (auto nested_param_info : nested_param_lst)
{
QTreeWidgetItem *nested_param_item = CreateParamItem(QString::fromStdString(nested_comp_name), &nested_param_info);
comp_item->addChild(nested_param_item);
}
}
}
}
首先通过 MwMoHandler 的 GetParamInfoEx 获取顶层模型的参数列表,接着通过 MwMoHandler 的 GetNestedCompKeys 获取顶层模型下的嵌套组件,最后再使用一次GetParamInfoEx获取嵌套组件的参数列表,完成参数树的初始化。
# 编辑参数
MwParamEditTable 继承 QTableWidget,实现了参数编辑功能。单击表格右上角的加号和减号分别调用 AddDataSet和RemoveDataSet接口,更新参数的数据集。添加数据集时参数值为参数在模型中的默认值,默认值的获取通过UpdateParamDefaultValue实现。
void MwParamEditTable::UpdateParamDefaultValue()
{
for (auto param_name : paramList)
{
QStringList param_nodes = param_name.split(".");
MwStrList param_nodes_lst;
for (auto param_node_name : param_nodes)
{
param_nodes_lst.push_back(param_node_name.toStdString());
}
QString param_value = QString::fromStdString(classMgr->GetMoHandler()->GetParamValue(curKey, param_nodes_lst));
paramDefaultVal.append(param_value);
}
}
该接口通过参数名节点列表,调用 MwMoHandler 的GetParamValue接口获取参数默认值。
# 批量仿真
MwBatchSimDialog 中的接口SlotBatchSim负责启动批量仿真,接口中从 MwParamEditTable 中获取选择的批量仿真参数和数据集作为批量仿真的输入,提供给 MwBatchSimControl 来启动批量仿真。
void MwBatchSimDialog::SlotBatchSim()
{
std::wstring sim_path = classMgr->GetCoreOption()->SimResultPath();
batchSimCtrl->RebindBaseData(baseSimData, sim_path);
QStringList param_name_list;
QList<QStringList> param_val_list;
paramTable->GetParamNameList(param_name_list);
paramTable->GetParamDataSetList(param_val_list);
batchSimCtrl->SetBatchSimDataSrc(param_name_list, param_val_list);
progressBar->setRange(0, 100);
progressBar->setValue(0);
this->setEnabled(false);
batchSimCtrl->StartBatchSim();
}
MwBatchSimControl 中通过创建多个 MwSimControl 实现并发仿真,启动若干 MwBatchSimThread 线程并为每个线程分配一个 MwSimControl 对象。
void MwBatchSimControl::StartBatchSim()
{
ReSet();
for (int i = 0; i < simConcurrency; ++i)
{
MwBatchSimThread *batch_sim_thread = new MwBatchSimThread(this);
connect(batch_sim_thread, &QThread::finished, batch_sim_thread, &QThread::deleteLater);
batch_sim_thread->start();
}
}
每个 MwSimControl 线程执行完当前的仿真任务后会互斥地从 MwBatchSimControl 中获取下一个需要仿真的数据集,然后开始下一次仿真,直到所有数据集被全部仿真完毕后批量仿真结束。
void MwBatchSimThread::run()
{
MwSimData *clone_data = nullptr;
bool is_success = false;
batchSimCtrl->Lock();
is_success = CreateCloneDataAndMakeDir(clone_data);
batchSimCtrl->UnLock();
while (is_success)
{
simCtrl->RebindSimData(clone_data);
simCtrl->StartSimulate(MwSimControl::Sim_ContinueMode, MwSimControl::Sim_Batch);
semSim->acquire();
batchSimCtrl->Lock();
is_success = CreateCloneDataAndMakeDir(clone_data);
batchSimCtrl->UnLock();
}
}
每次启动一次仿真后,线程内的同步通过信号量完成,确保在单次仿真完成后再取下一个数据集仿真。
# 加载结果
MwBatchSimDialog 中的 SlotLoadResult接口实现将批量仿真结果加载到 MWORKS 结果面板的功能,接口通过调用 MwWorkbench 的OpenSimResult接口实现。
void MwBatchSimDialog::SlotLoadResult()
{
QString sim_path = QString::fromStdWString(classMgr->GetCoreOption()->SimResultPath());
int data_set_size = batchSimCtrl->GetDataSetSize();
for (int i = 0; i < data_set_size; ++i)
{
QString inst_path = sim_path + QString("/Result-%1/Result.msr").arg(i + 1);
MwAppPtr->GetWorkbench()->OpenSimResult(inst_path);
}
}
# APP 测试
为保证 APP 能够正常完整运行,在打包和编译代码期间可对 APP 进行测试保证 APP 功能完整。
# 编译调试测试
嵌入式生成的输出文件为 dll 库,dll 库需要依赖于 exe 才可进行调试,这里通过将程序中配置 exe 调试路径对编译的 dll 库进行调试,配置方法如下:
右击项目>属性>调试>命令/命令参数传入 mworks.exe 的路径,这里使用 SDK 下自带的mworks.exe进行调试。
直接单击运行程序可以直接打开软件进行调试。
Release 调试仍需配置属性,具体配置教程及调试请参考编译调试测试。
# 功能测试
APP 开发完成后,按照相应的软件测试方法,对照功能点功能列表进行详细的功能测试,确保软件在功能上完整,保证用户的体验。
# 集成测试
将开发完成的 dll 放入不同 Sysplorer 版本软件对应的插件目录Bin/Addins,并测试能否在 Sysplorer 软件中启动该工具,若启动成功则测试通过。
# APP 打包及发布
APP 打包即遵循具体 APP 开发环境的要求,打包好的 APP 程序需独立可运行,不需再另外安装软件或执行其他的操作。将嵌入式生成的 dll 库,放入SDK安装目录/bin/win_msvc2017x64/Release/Addins目录下的新建文件夹 MwBatchSimPlugin(文件夹名称任意)中即可进行运行。
进入/bin/win_msvc2017x64/Release/mworks.exe运行 Sysplorer 软件,可查看到放入的插件。
将 dll 和文件夹打包,放入任意 Sysplorer 的 bin 目录下的 Addins 文件夹下,然后运行 Sysplorer 可查看到开发的插件功能。
