☰
FreeCAD MCP 源码拆解:GUI 线程分发机制,让 RPC 安全驱动 CAD 主线程
2026/10/2 18:26:51 网站建设 项目流程

FreeCAD MCP 源码拆解:GUI 线程分发机制,让 RPC 安全驱动 CAD 主线程

【免费下载链接】freecad-mcpFreeCAD MCP(Model Context Protocol) server项目地址: https://gitcode.com/gh_mirrors/fr/freecad-mcp

FreeCAD MCP 是一个让 AI(如 Claude)通过 MCP 协议远程控制 FreeCAD 的服务端项目。它的核心难题在于:RPC 请求运行在独立线程,而 FreeCAD 的文档树和 3D 视图只允许主线程(GUI 线程)访问。本文拆解gui_dispatch模块的 GUI 线程分发机制——它如何让跨线程调用既安全又不卡死界面。

为什么必须有 GUI 线程分发?

FreeCAD 基于 Qt,其文档对象(Document)、场景图(Coin3D)都不是线程安全的:

  • 在后台线程直接调用obj.Shape = ...、doc.recompute()、doc.addObject()会与 GUI 线程产生竞态,轻则状态错乱,重则事件循环彻底卡死(wedge),RPC 服务器从此不再响应。
  • 而 XML-RPC 服务器本身跑在 daemon 线程里(见 rpc_server.py 中的start_rpc_server),天然与 GUI 线程分离。

所以 FreeCAD MCP 的设计原则是:RPC 线程只负责收请求,所有触碰 FreeCAD 的操作必须"搬运"到 GUI 线程执行。承担这项搬运工作的是 addon/FreeCADMCP/rpc_server/gui_dispatch.py。

整体架构:一个队列 + 一条 Qt 信号

核心数据结构非常简洁:

组件位置职责
_rpc_request_queue模块级queue.QueueFIFO 任务队列,存放待搬运的闭包
_WakeSignal继承QObject的信号桥RPC 线程emit,GUI 线程收到信号后立即处理
process_gui_tasks()500ms 心跳 + 信号唤醒在 GUI 线程中批量消费队列
dispatch_to_gui()RPC 线程调用把任务包一层、入队、唤醒 GUI、同步等结果

数据流大致如下:

  1. 入队:dispatch_to_gui(task, timeout)把task包装成_wrapped()(负责计时、记录健康状态、把返回值塞进专属响应队列),放入_rpc_request_queue。
  2. 唤醒:通过_WakeSignal.wake()发出 Qt 信号。由于连接方式是QueuedConnection,槽函数一定会在 GUI 线程的事件循环中执行——这是跨线程调度的安全通道。500ms 的QTimer.singleShot心跳则作为兜底,防止信号丢失。
  3. 执行:process_gui_tasks()在 GUI 线程中循环消费队列,逐个执行任务,期间把鼠标换成等待光标、状态栏显示 "MCP: processing…",让用户直观感知 AI 正在操作。

一个细节:_WakeSignal必须在 GUI 线程创建(init_waker),从 RPC 线程 emit 却是安全的——这正是 Qt 信号机制跨线程的典型用法。

每个调用一条"专属响应队列"

dispatch_to_gui的返回值传递方式是本文值得学习的第一个设计点:每次调用新建一个queue.Queue(maxsize=1)作为响应队列,而不是所有调用共用一个全局队列。

好处很直接:A 调用超时后,它遗留的"迟到响应"不可能污染 B 调用的结果。配合threading.Event(任务开始信号)和一把状态锁,超时、完成、取消三种情况之间的竞态都被显式处理——例如完成与超时同时发生时,代码会先尝试get_nowait()捞一次结果再判定超时。

两级超时预算:排队时间不算执行账

GUI 线程一次只执行一个任务,后来的请求要 FIFO 排队。如果简单地"从入队开始计时",排在慢任务后面的调用会被误判超时。因此dispatch_to_gui把时间拆成两段:

  • queue_timeout(排队预算):从入队到任务真正开始的等待时间,超时会取消任务,但不会把分发系统标记为卡死。
  • timeout(执行预算):从任务在 GUI 线程实际启动开始计时。

这带来一个实际收益:两个并发的execute_code调用各自拥有完整的执行预算,第二个不会因为第一个慢而被冤枉成"卡死"。对应的客户端侧,freecad_client.py 会把 socket 超时放宽到2 × timeout + 30s,保证两级预算都有足够余量。完整说明见 docs/execution.md 的 "GUI dispatch timeouts" 一节。

卡死检测:fail-fast 而不是无限等待

一个已在 GUI 线程运行的任务无法被安全取消。如果它超过执行预算,dispatch_health.py 中的DispatchHealth会把状态置为stuck:

  • 之后新来的 GUI 调用立即失败(返回GUI_DISPATCH_STUCK错误码),不再傻等;
  • 但已经排队的调用继续等待,等卡住的任务结束后照常执行;
  • 客户端可以随时用get_rpc_status查询当前是哪个操作在卡(该接口完全不经过 GUI 线程,所以卡死时也能诊断)。

测试文件 tests/test_gui_dispatch.py 用 Fake 的 Qt/FreeCAD 模块完整覆盖了这组行为:卡死后新调用秒拒、已排队调用在解除卡死后恢复、执行预算不从入队时刻计时等。

鼠标按钮守卫:不打断你的 3D 拖动

如果你正按住鼠标在视口里旋转模型,此时 AI 的建模任务插队执行,体验会很糟。process_gui_tasks每个 tick 先检查QApplication.mouseButtons():

  • 有按键按下 → 跳过本 tick,推迟到下个心跳;
  • 但推迟最多 5 秒(MOUSE_DEFER_MAX_S):Qt 偶发丢失"鼠标释放"事件(例如在窗口外松手),若无限推迟,RPC 服务器会永久停摆;
  • 超时后强制处理任务,并在 FreeCAD 控制台打印一次警告。

弹窗、右键菜单、模态对话框打开时同样会推迟分发,且每次推迟的原因(_last_defer)都会被记录——超时错误信息会直接告诉用户"GUI 线程没处理任务的原因:3D 导航拖动中",而不是抛出一个莫名的 timeout。

实战:启动 RPC 服务器并观察状态栏

安装 addon 后,在 FreeCAD 中切换到MCP Addon工作台(addon/FreeCADMCP/InitGui.py 定义),点击工具栏的Start RPC Server。启动逻辑在 rpc_server.py 的start_rpc_server()中:绑定端口(默认 9875)→init_waker()创建唤醒信号 → 启动 500ms 心跳 → 启动 RPC 线程。

注意状态栏底部的 "RPC Server started at 127.0.0.1:91757"——这就是分发桥已经就绪的信号。当 AI 提交任务时,你会看到鼠标变成等待光标、状态栏显示 "MCP: processing…";而如果你正在拖动 3D 视图,任务会自动礼让。

关键源码导读

想深入阅读,建议按这个顺序:

  1. addon/FreeCADMCP/rpc_server/gui_dispatch.py —— 模块顶部 30 行 docstring 就是一份 7 条的"健壮性保证清单",逐条对照源码看即可;
  2. addon/FreeCADMCP/rpc_server/dispatch_health.py —— 仅 100 行的健康状态机,healthy / busy / stuck三态;
  3. addon/FreeCADMCP/rpc_server/rpc_server.py —— 所有 RPC 处理器如何调用dispatch_to_gui,以及execute_code_async如何通过commit()助手把文档写操作交还 GUI 线程;
  4. tests/test_gui_dispatch.py —— 不依赖真实 FreeCAD 的纯单元测试,展示了如何 Fake 掉 Qt 来测试分发逻辑。

总结

FreeCAD MCP 的 GUI 线程分发机制用一个小模块解决了一个大工程问题:跨线程调用 CAD 主线程的完整生命周期管理。它的几个核心取舍值得借鉴:

  • 🧵信号 + 心跳双通道唤醒:兼顾实时性与可靠性;
  • 📬每调用独立响应队列:从结构上杜绝超时污染;
  • ⏱️排队/执行两段预算:慢任务不连累排队的请求;
  • 🛑stuck 状态 fail-fast:卡死时快速失败 + 可诊断,而非无限等待;
  • 🖱️有界鼠标守卫:礼让用户交互,但绝不被陈旧输入状态卡死。

对任何需要"后台线程安全驱动 GUI 应用"的项目(不限于 CAD),这套模式都是一个很好的参考模板。

【免费下载链接】freecad-mcpFreeCAD MCP(Model Context Protocol) server项目地址: https://gitcode.com/gh_mirrors/fr/freecad-mcp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询