2026a

# 批量仿真 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 主程序插件异步关闭已完成。
  1. 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:依赖的插件
  2. MwBatchSimDialog

    MwBatchSimDialog 是批量仿真插件的主界面,由选择参数、编辑参数、批量仿真三个部分组成。

  3. MwParamSelectDialog

    MwParamSelectDialog 为批量仿真参数选择对话框,内部初始化一棵 MwParamSelectTree 批量仿真参数选择树。

    void MwParamSelectDialog::SetupUi()
    {
       QVBoxLayout *vbox_layout = new QVBoxLayout(this);
       paramTree = new MwParamSelectTree(classMgr, curKey, this);
       vbox_layout->addWidget(paramTree);
    }
    
  4. 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,实现了参数编辑功能。单击表格右上角的加号和减号分别调用 AddDataSetRemoveDataSet接口,更新参数的数据集。添加数据集时参数值为参数在模型中的默认值,默认值的获取通过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 可查看到开发的插件功能。