Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

Qt 与可视化 / Qt & Visualization

1. Qt 6 与科学可视化路线 / Qt 6 and Scientific Visualization Roadmap

2. 信号槽机制深度解析 / Qt Signals and Slots in Depth

3. 对象树与内存管理 / QObject Trees and Memory Management

4. CAE开发常用核心API实战 / Essential Qt APIs for CAE Development

5. Model/View架构与CAE数据处理 / Model-View Architecture and CAE Data Processing

6. 多线程与求解器集成 / Multithreading and Solver Integration

7. CAE软件架构实战模式 / Practical Architecture Patterns for CAE Software

8. 3D可视化与交互 / Interactive 3D Visualization

9. 实战案例:从零拆解一个FEA前后处理器 / Building an FEA Pre- and Post-Processor from Scratch

本页目录

CAE软件架构实战模式 / Practical Architecture Patterns for CAE Software ​

📅 创建时间:2026-07-13 🏷️ 标签:#Qt #软件架构 #插件系统 #CAE #设计模式 #QUndoStack 📚 前置知识:Model View Architecture、Plugin Architecture Patterns、Architecture


📋 本章目标 ​

  • 理解CAE软件的四层架构设计
  • 掌握基于QPluginLoader的CAE插件系统设计
  • 掌握QUndoStack实现撤销/重做的完整模式
  • 掌握Action/Command注册模式构建菜单和工具栏
  • 掌握Document-View多文档窗口架构
  • 掌握CAE软件的配置管理与国际化方案
  • 能够设计一个完整CAE软件的骨架架构

专题扩展 ​

  • 插件 ABI 与文档架构

第1部分:CAE分层架构 ​

1.1 四层架构模型 ​

┌─────────────────────────────────────────────────────────────┐
│               CAE 软件四层架构                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  第1层:用户界面层 (UI Layer)                         │   │
│  │  QMainWindow / QMdiArea / QDockWidget               │   │
│  │  QOpenGLWidget (3D视口) / QTreeView (零件树)         │   │
│  │  QTableView (结果表格) / QPropertyBrowser (属性面板) │   │
│  │  ★ 只负责展示和交互,不包含业务逻辑                    │
│  └─────────────────────────┬───────────────────────────┘   │
│              信号槽 (松耦合,单向依赖)                        │
│  ┌─────────────────────────▼───────────────────────────┐   │
│  │  第2层:业务逻辑层 (Business Logic)                   │   │
│  │  Document管理 (打开/保存/关闭项目)                     │   │
│  │  Command执行 (创建零件/施加载荷/划分网格)              │   │
│  │  SolverManager (启动/监控/取消求解)                   │   │
│  │  PluginManager (发现/加载/卸载插件)                   │   │
│  │  ★ 核心协调层,不依赖具体算法                          │
│  └─────────────────────────┬───────────────────────────┘   │
│              直接函数调用 (同步,高性能)                      │
│  ┌─────────────────────────▼───────────────────────────┐   │
│  │  第3层:核心算法层 (Core Algorithms)                  │   │
│  │  几何导入 (STEP/IGES/Nastran BDF解析)                │   │
│  │  网格生成 (Delaunay/Advancing Front/wrapper TetGen)  │   │
│  │  求解器接口 (QProcess wrapper + 输入输出解析)         │   │
│  │  结果后处理 (等值面/切面/流线/动画)                    │   │
│  │  ★ 纯计算,无GUI依赖                                 │
│  └─────────────────────────┬───────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────▼───────────────────────────┐   │
│  │  第4层:数据持久层 (Data Layer)                       │   │
│  │  QFile/QDataStream/QTextStream (二进制/文本I/O)      │   │
│  │  QSettings (项目配置) / JSON/XML/HDF5                │   │
│  │  ★ 只负责存储和读取,不关心数据含义                    │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  依赖方向: UI → Logic → Algorithm → Data (单向)             │
│           上层知道下层,下层不知道上层                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

1.2 层间通信机制 ​

cpp
// ===== UI <-> Logic:信号槽(松耦合)=====
// Logic层发信号,UI层连接监听
class MeshManager : public QObject {
    Q_OBJECT
signals:
    void meshLoaded(int nodeCount, int elementCount);
    void meshLoadFailed(const QString& error);
};

// ===== Logic <-> Algorithm:直接函数调用(高性能)=====
// Algorithm层提供纯C++接口
class TetMesher {
public:
    MeshResult tetrahedralize(const SurfaceMesh& input,
                              const MeshingParams& params);
};

// ===== 依赖注入:Logic持有Algorithm的接口指针 =====
class MeshManager : public QObject {
    std::unique_ptr<IMesher> m_mesher;  // 依赖接口,不依赖实现
public:
    void setMesher(std::unique_ptr<IMesher> mesher) {
        m_mesher = std::move(mesher);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

第2部分:插件系统设计 ​

2.1 CAE插件接口定义 ​

cpp
// ===== 所有CAE插件的基接口 =====
struct CaePluginInfo {
    QString name;
    QString version;
    QString description;
    QString author;
};

// ===== 导入器插件接口 =====
class IModelImporter {
public:
    virtual ~IModelImporter() = default;
    virtual CaePluginInfo pluginInfo() const = 0;
    virtual QStringList supportedExtensions() const = 0;
    virtual bool importFile(const QString& path, MeshData& out) = 0;
};

// 导出宏
#define CAE_IMPORTER_IID "com.fastcae.ImporterInterface/1.0"
Q_DECLARE_INTERFACE(IModelImporter, CAE_IMPORTER_IID)

// ===== 求解器插件接口 =====
class ISolver {
public:
    virtual ~ISolver() = default;
    virtual CaePluginInfo pluginInfo() const = 0;
    virtual QStringList inputFileExtensions() const = 0;
    virtual QProcess* createProcess(const SolverInput& input) = 0;
    virtual SolverResult parseOutput(const QString& outputPath) = 0;
};

#define CAE_SOLVER_IID "com.fastcae.SolverInterface/1.0"
Q_DECLARE_INTERFACE(ISolver, CAE_SOLVER_IID)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

2.2 插件实现(以导入器为例) ​

cpp
// ===== 插件项目中的实现 =====
class NastranBdfImporter : public QObject, public IModelImporter {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID CAE_IMPORTER_IID FILE "nastran_importer.json")
    Q_INTERFACES(IModelImporter)

public:
    CaePluginInfo pluginInfo() const override {
        return {"Nastran BDF Importer", "1.0",
                "Import Nastran bulk data files", "FastCAE Team"};
    }

    QStringList supportedExtensions() const override {
        return {"bdf", "dat", "nas"};
    }

    bool importFile(const QString& path, MeshData& out) override {
        // 解析Nastran BDF格式...
        return true;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

2.3 PluginManager——插件发现与加载 ​

cpp
class CaePluginManager : public QObject {
    Q_OBJECT
public:
    void loadAllPlugins(const QString& pluginDir) {
        QDir dir(pluginDir);
        for (const QString& file : dir.entryList(QDir::Files)) {
            QPluginLoader loader(dir.absoluteFilePath(file));
            QObject* instance = loader.instance();

            if (auto* importer = qobject_cast<IModelImporter*>(instance)) {
                m_importers.append(importer);
                qDebug() << "Loaded importer:" << importer->pluginInfo().name;
            }
            else if (auto* solver = qobject_cast<ISolver*>(instance)) {
                m_solvers.append(solver);
                qDebug() << "Loaded solver:" << solver->pluginInfo().name;
            }
        }
    }

    QList<IModelImporter*> importers() const { return m_importers; }
    QList<ISolver*> solvers() const { return m_solvers; }

private:
    QList<IModelImporter*> m_importers;
    QList<ISolver*> m_solvers;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

第3部分:命令模式与撤销/重做 ​

3.1 QUndoCommand基础 ​

cpp
// ===== 每个CAE操作封装为一个QUndoCommand =====
class CreatePartCommand : public QUndoCommand {
public:
    CreatePartCommand(const QString& name, PartTreeModel* model,
                      const QModelIndex& parent)
        : m_name(name), m_model(model), m_parent(parent)
    {
        setText(QString("Create Part '%1'").arg(name));
    }

    void undo() override {
        // 删除最后添加的Part
        int lastRow = m_model->rowCount(m_parent) - 1;
        m_model->removePart(lastRow, m_parent);
    }

    void redo() override {
        m_model->addPart(m_name, m_parent);
    }

private:
    QString m_name;
    PartTreeModel* m_model;
    QModelIndex m_parent;
};

// ===== 使用 =====
QUndoStack* undoStack = new QUndoStack(this);

// 创建操作并推入栈
undoStack->push(new CreatePartCommand("Wing", model, rootIndex));

// 撤销/重做通过快捷键
QAction* undoAction = undoStack->createUndoAction(this, "&Undo");
undoAction->setShortcut(QKeySequence::Undo);   // Ctrl+Z

QAction* redoAction = undoStack->createRedoAction(this, "&Redo");
redoAction->setShortcut(QKeySequence::Redo);   // Ctrl+Y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

3.2 宏命令——组合多个操作为一步 ​

cpp
// ===== 划分网格是一个"宏命令" =====
class MeshPartCommand : public QUndoCommand {
public:
    MeshPartCommand(Part* part, const MeshingParams& params)
        : QUndoCommand("Mesh Part")
    {
        // 子命令构成一个不可分割的整体
        new CreateMeshCommand(part, &m_meshData, this);   // child 1
        new SetMeshParamsCommand(&m_meshData, params, this); // child 2
        new GenerateMeshCommand(&m_meshData, this);          // child 3
    }
    // QUndoCommand的child自动管理:
    // undo时逆序undo每个child
    // redo时正序redo每个child
    // 只需要实现顶层命令
    void undo() override { /* QUndoCommand自动处理children */ }
    void redo() override { /* QUndoCommand自动处理children */ }

private:
    MeshData m_meshData;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

3.3 视图同步——undo/redo后通知所有视图 ​

cpp
// 通过信号通知视图刷新
class Document : public QObject {
    Q_OBJECT
public:
    void executeCommand(QUndoCommand* cmd) {
        m_undoStack.push(cmd);
        // undo/redo自动触发 indexChanged 信号
    }

signals:
    void dataChanged();  // 数据变更了,视图们刷新吧
};

// 在MainWindow中连接
connect(document, &Document::dataChanged, viewport3D,
        QOverload<>::of(&QOpenGLWidget::update));
connect(document, &Document::dataChanged, treeView,
        [this]() { partTreeModel->refresh(); });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第4部分:Action与菜单/工具栏 ​

4.1 集中注册模式 ​

cpp
class ActionManager : public QObject {
    Q_OBJECT
public:
    enum ActionId {
        File_New, File_Open, File_Save, File_SaveAs,
        Edit_Undo, Edit_Redo,
        Mesh_Tetrahedral, Mesh_Hexahedral,
        Solver_Run, Solver_Cancel,
        View_ZoomFit, View_Wireframe, View_Shaded
    };

    // ★ 集中注册所有Action
    void registerAllActions(QWidget* parent) {
        registerAction(File_New,  "&New",  QKeySequence::New,
                       parent, []{ /* new project */ });
        registerAction(File_Open, "&Open", QKeySequence::Open,
                       parent, []{ /* open project */ });
        // ... 注册所有action
    }

    QAction* action(ActionId id) const { return m_actions.value(id); }

private:
    QHash<ActionId, QAction*> m_actions;
};

// 插件也可以注册Action
void ImporterPlugin::registerActions(ActionManager* mgr) {
    mgr->registerAction(ActionManager::File_Import_Nastran,
        "Import &Nastran...", {}, this,
        [this]{ showImportDialog(); });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

4.2 动态菜单构建 ​

cpp
void MainWindow::buildMenuBar() {
    QMenu* fileMenu = menuBar()->addMenu("&File");
    fileMenu->addAction(m_actionMgr->action(ActionManager::File_New));
    fileMenu->addAction(m_actionMgr->action(ActionManager::File_Open));

    // 动态子菜单——所有已加载的导入器
    QMenu* importMenu = fileMenu->addMenu("&Import");
    for (IModelImporter* importer : m_pluginMgr->importers()) {
        QAction* act = importMenu->addAction(
            importer->pluginInfo().name);
        connect(act, &QAction::triggered, this, [importer, this]() {
            importWithPlugin(importer);
        });
    }

    fileMenu->addSeparator();
    fileMenu->addAction(m_actionMgr->action(ActionManager::File_Save));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第5部分:Document-View多文档 ​

5.1 QMdiArea多文档界面 ​

cpp
class MainWindow : public QMainWindow {
public:
    MainWindow() {
        m_mdiArea = new QMdiArea(this);
        setCentralWidget(m_mdiArea);
    }

    void openProject(const QString& path) {
        auto* doc = new ModelDocument(path);

        // 为这个文档创建一个子窗口
        auto* subWindow = new QMdiSubWindow();
        subWindow->setWidget(createViewport(doc));  // 3D视口
        subWindow->setWindowTitle(QFileInfo(path).fileName());

        m_mdiArea->addSubWindow(subWindow);
        subWindow->show();

        // Document拥有数据,SubWindow拥有视图
        // 关闭SubWindow → 检查Document是否dirty → 提示保存
        connect(subWindow, &QMdiSubWindow::aboutToActivate,
                this, &MainWindow::onDocumentActivated);
    }

private:
    QMdiArea* m_mdiArea;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

第6部分:配置与国际化 ​

6.1 分层配置系统 ​

cpp
// 三层配置:默认值 → 用户设置 → 项目设置
class ConfigManager {
public:
    QVariant value(const QString& key) const {
        // 优先级:项目 > 用户 > 默认
        if (m_projectSettings.contains(key))
            return m_projectSettings[key];
        if (m_userSettings.contains(key))
            return m_userSettings[key];
        return m_defaults.value(key);
    }

private:
    QSettings m_userSettings;                    // 用户级
    QVariantMap m_projectSettings;               // 项目级(存在工程文件中)
    static const QHash<QString, QVariant> m_defaults;  // 默认值
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

6.2 QTranslator国际化 ​

cpp
void MainWindow::switchLanguage(const QString& lang) {
    static QTranslator translator;

    qApp->removeTranslator(&translator);
    if (translator.load(QString("fastcae_%1.qm").arg(lang),
                         ":/translations")) {
        qApp->installTranslator(&translator);
    }
    // UI自动切换语言(所有 tr() 调用重新翻译)
}
1
2
3
4
5
6
7
8
9
10

核心总结 ​

总结1:四层架构的依赖方向 ​

UI → Logic → Algorithm → Data(单向!)
每一层只依赖下一层的接口,不知道具体实现
Logic是"总指挥":协调UI的请求,调用Algorithm,管理Data
1
2
3

总结2:QUndoStack的标准工作流 ​

用户操作 → new QUndoCommand → undoStack.push(cmd)
  → cmd->redo() 执行
  → undoStack indexChanged 信号
  → 所有View刷新

Ctrl+Z → undoStack.undo() → cmd->undo()
  → 反转操作 → View刷新
1
2
3
4
5
6
7

总结3:插件系统的关键接口 ​

CAE 插件可按 Importer/Exporter/Solver/Mesher/PostProcessor/Material 等角色分类。纯虚接口、Q_DECLARE_INTERFACE、QPluginLoader 和 qobject_cast 负责接口发现,但不自动构成长久稳定 ABI;Qt/编译器/CRT、IID/元数据版本、所有权和卸载协议必须另行约束,完整规则见插件工程知识区。


章节测试 ​

测试1:分层架构 ​

在四层架构中,SolverManager属于哪一层? A. UI层 B. 业务逻辑层 C. 核心算法层 D. 数据持久层

测试2:插件系统 ​

QPluginLoader加载插件后,如何判断它实现了哪个接口? A. 检查文件名 B. 调用instance()获取QObject*,然后用qobject_cast尝试转换 C. 读取metadata.json D. 检查Q_PLUGIN_METADATA宏

测试3:QUndoStack ​

QUndoCommand的子命令(child commands)被undo时,顺序是什么? A. 正序(先加的先undo) B. 逆序(后加的先undo) C. 同时undo D. 随机顺序


参考答案 ​

测试1答案 ​

答案:B。SolverManager负责启动/监控/取消求解——这是协调逻辑,不涉及具体求解算法(那是第3层的事),也不直接显示UI(那是第1层的事)。

测试2答案 ​

答案:B。loader.instance()返回QObject*,然后用qobject_cast<IModelImporter*>(instance)逐个尝试所有已知接口类型。非空即表示实现了该接口。

测试3答案 ​

答案:B。逆序——后加的先undo,确保状态回滚的一致性。这和QObject对象树删除children的顺序一致。


相关笔记 ​

  • Model View Architecture - Model/View(Document-View的基础)
  • Plugin Architecture Patterns - 通用插件设计模式
  • Industrial Plugin Case Study - FreeCAD/ParaView插件分析
  • Architecture - CAE软件架构详解

下一步学习 ​

  • [ ] 阅读 07 - 3D可视化与交互
  • [ ] 阅读 08 - 实战案例:FEA前后处理器
  • [ ] 在你项目中尝试实现QUndoStack的撤销/重做

学习状态:🟡 开始学习

CAE 依赖方向图 ​

text
UI widgets/controllers
        |
Qt Model/View adapters       Render adapters
        |                         |
Application commands/use cases --+
        |
Domain Document/entities/results
        |
Ports: storage / solver / importer / plugin
        |
Infrastructure implementations
1
2
3
4
5
6
7
8
9
10
11

依赖指向领域和用例,领域不包含 QWidget、线程池或 GPU handle。Qt signal 是外层通知机制,不应成为领域唯一调用协议。

修改事务 ​

text
UI intent -> Command validate
 -> prepare delta
 -> commit domain invariant
 -> revision/dirty/undo update
 -> publish model/render notifications
1
2
3
4
5

通知在提交后发出;失败前不留下半改状态。异步求解只读取 snapshot,结果按 revision 提交。

面试连续追问 ​

问:MVC/MVVM 名字为何不够指导 CAE 架构? 答:还需明确 Document 所有权、长任务、版本化文件、渲染资源和插件 ABI 的边界与关闭顺序。

问:Service Locator 有何问题? 答:依赖隐藏、测试顺序和全局生命周期不清;组合根显式注入更可审查。

问:领域对象应否继承 QObject? 答:不是绝对禁止,但会耦合线程/所有权/元对象;长期可移植领域通常保持普通 C++ 值,再由 Qt adapter 通知。

自测与答案 ​

  1. 谁拥有 Document? 答:应用/窗口级组合根明确拥有,views/controllers 观察或持受控引用。
  2. solver 如何避免改 GUI 状态? 答:读取不可变 snapshot,生成独立结果,GUI/应用线程验证 revision 后提交。
  3. 插件接口放在哪层? 答:基础设施边界,通过窄 port/capability 适配用例,不让插件任意摸内部对象图。

最后更新于:

Pager
上一篇6. 多线程与求解器集成 / Multithreading and Solver Integration
下一篇8. 3D可视化与交互 / Interactive 3D Visualization

持续记录,持续成长

Copyright © Tidenflow