筑纬建筑科技ZHUWEI

告别繁琐配置!将AI大模型无缝接入Revit,对话即建模!

公众号同步 · 筑纬建筑科技 · 2026-09-30


 

本文转载至帮特数字化工坊,原作者:帮特数字化工坊。点击阅读原文:转载目的在于传递更多信息,如有侵权请联系删除。


如何将AI大模型(DeepSeek)接入Revit中?

你想让 Revit 自动干活,要么写一堆 C# 插件,每个功能写一个按钮,用户还得记着点哪个;要么搞一套 MCP Server,配端口、配协议、配环境变量,门槛高到劝退一半人。

我自己也试过 MCP 方案,配来配去实在烦。后来换了个思路:直接在 Revit 里嵌一个聊天框,填个 API Key 就能用大模型驱动 Revit。不用配 MCP,不用开端口,进程内直接调用。

这个框架我断断续续写了有一阵子,从最早的 MCP 架构一路改到现在的进程内调用方案。整体跑下来感觉设计还挺顺的,今天就跟大家聊聊这套架构是怎么做的。

图片

AI聊天界面窗口

1 这个插件能干什么

一句话概括:在 Revit 右侧嵌一个 AI 聊天面板,你用自然语言提问,AI 一边回答问题,一边直接操作 Revit 模型。

举几个实际的例子:

「统计当前模型的构件数量」——AI 调用 analyze_model_statistics 命令,返回一张 Markdown 表格,告诉你墙多少、门多少、管多少。

「只显示名字带 FFU 的设备」——AI 调用 operate_element 命令,直接在视图里隔离匹配构件。

「导出房间数据到 Excel」——AI 调用 export_room_data,文件就生成好了。

整个过程你不需要点任何按钮,不需要记命令名,用大白话说就行。

最大的便利:不用配 MCP

传统方案要在本机起一个 MCP Server,走 TCP 协议通信。这个插件把命令集直接加载到 Revit 进程里,通过 CommandBus 进程内调用。用户唯一要做的就是打开设置窗口,选一个大模型,填上 API Key。

没有端口配置,没有协议握手,没有环境变量。

支持的大模型

插件内置了 10 个模型预设,还支持自定义添加任何 OpenAI 兼容接口:

模型
API 地址
DeepSeek Chat / 推理
api.deepseek.com/v1
通义千问 Plus / Max / Turbo
dashscope.aliyuncs.com
智谱 GLM-4 / GLM-4 Flash
open.bigmodel.cn
Kimi (moonshot-v1-128k)
api.moonshot.cn/v1
OpenAI GPT-4o / mini
api.openai.com/v1

命令覆盖范围

插件注册了 100 多条 Revit 命令,覆盖建筑、结构、MEP、注释、视图管理、图纸、明细表、材质、工作集、族、导出等全流程。常用的比如:

  • • 查询类:get_current_view_elements、get_selected_elements、get_element_parameters、analyze_model_statistics、get_material_quantities、get_warnings
  • • 创建类:create_room、create_level、create_grid、create_floor、create_sheet、create_schedule、create_view
  • • 修改类:set_element_parameters、operate_element、color_splash、modify_element、change_element_type
  • • 删除类:delete_element、purge_unused、wipe_empty_tags
  • • 导出类:export_room_data、export_to_excel、batch_export、export_families

还有一键开洞、管道轴测图、碰撞检测大屏、房间净高分析、支吊架生成等专业功能,都封装成了 AI 可调用的命令。

而且插件兼容 Revit 2020 到 2027,低版本自动过滤掉不支持的命令,不会报错。

2 技术实现:架构与设计思路

整体架构

项目拆成两个工程:

RevitAiChat (主插件)
├── Application.cs          ← Revit 入口,注册面板和按钮
├── UI/ChatPanel.xaml       ← 聊天界面
├── Services/ChatClient.cs  ← 和大模型通信的大脑
├── Services/CommandBus.cs  ← 进程内命令总线
└── Execution/             ← 命令注册和事件管理

RevitMCPCommandSet (命令集)
├── Commands/               ← 100+ 命令定义
├── Services/               ← 对应的事件处理器
└── Models/                 ← 参数和数据模型

RevitAiChat 负责 UI、大模型通信和命令调度;RevitMCPCommandSet 负责具体的 Revit API 操作。两个工程在同一个进程里运行,通过 CommandBus 直接调用,不走网络。

核心运行流程

从用户输入一句话,到 Revit 执行操作,整个链路是这样的:

  1. 1. 用户在聊天框输入「统计构件数量」
  2. 2. ChatPanel 调用 ChatClient.SendAsync()
  3. 3. ChatClient 拼装 system prompt + Revit 上下文快照 + 工具列表
  4. 4. HTTP POST 到大模型 /chat/completions,流式接收
  5. 5. 模型返回 tool_calls:调用 analyze_model_statistics
  6. 6. CommandBus.ExecuteAsync() 在进程内执行命令
  7. 7. 命令通过 ExternalEvent 在 Revit API 线程执行
  8. 8. 结果 JSON 回传给模型,模型生成最终 Markdown 表格
  9. 9. 聊天界面流式渲染最终回复

这里有个关键设计:整个链路最多循环 12 轮。因为模型一次可能只调一个工具,拿到结果后还需要再思考、再调下一个工具,最终把所有结果汇总成回复。

2.1 插件是怎么加载进 Revit 的

Revit 启动时会扫描 %AppData%\Autodesk\Revit\Addins\年份\*.addin 文件,发现插件清单后加载 DLL,创建实现 IExternalApplication 的类,调用 OnStartup。

核心加载逻辑在 Application.cs 里:

// Application.cs — Revit 插件入口
publicclassApplication : IExternalApplication
{
    // 静态字段钉住面板,防止被 GC 回收
    internalstatic ChatPaneProvider PaneProvider;

    public Result OnStartup(UIControlledApplication app)
    {
        // 第一步:注册右侧停靠聊天面板
        PaneProvider = new ChatPaneProvider();
        app.RegisterDockablePane(
            ChatPaneProvider.PaneId,
            "Revit AI 助手",
            PaneProvider);

        // 第二步:挂 Idling 事件,持续刷新当前文档上下文
        app.Idling += OnIdling;
        app.ControlledApplication.ApplicationInitialized 
            += OnApplicationInitialized;

        // 第三步:在功能区画一个「AI 助手」按钮
        var button = new PushButtonData(
            "ID_REVIT_AI_CHAT",
            "AI 助手",
            assemblyPath,
            typeof(ToggleChatPaneCommand).FullName);
        panel.AddItem(button);

        return Result.Succeeded;
    }

    private void OnApplicationInitialized(object sender, var e)
    {
        var uiApp = new UIApplication(sender as Application);
        RevitContext.UiApplication = uiApp;
        // 关键:在 Revit 完全初始化后加载命令集
        CommandBus.Initialize(uiApp);
        uiApp.GetDockablePane(ChatPaneProvider.PaneId);
    }
}

有个细节值得注意:PaneProvider 必须用静态字段持有。如果写成局部变量,Revit 可能在某个时刻把它垃圾回收掉,下次点按钮就报「pane has not been created yet」。这是 Revit 插件开发一个经典的坑。

另一个设计是 CommandBus.Initialize 不在 OnStartup 里直接调,而是等到 ApplicationInitialized 事件。因为 OnStartup 时 Revit 还没完全加载好,直接初始化会失败。Idling 事件里还有个兜底逻辑——如果命令还没就绪,每次空闲再试一次。

2.2 聊天框是怎么访问大模型的

ChatClient 是整个插件的大脑。它负责和大模型通信,用的是 OpenAI 兼容的 /chat/completions 接口,支持流式输出(SSE)和工具调用(function calling)。

核心方法 SendAsync 的逻辑其实是一个最多 12 轮的循环:

// ChatClient.cs — 核心通信循环
public async Task<string> SendAsync(
    string userText, AppSettings settings,
    Action<string> onStatus, Action<string> onPartial
)

{
    // 1. 拼 system prompt + Revit 上下文快照
    string context = RevitContext.BuildSnapshot();
    _history.Add(new JObject {
        ["role"] = "user",
        ["content"] = context + "\n\n用户问题:\n" + userText
    });

    // 2. 加载工具列表(从 tools.json 过滤已注册命令)
    JArray tools = LoadTools();

    // 3. 最多 12 轮循环
    for (int round = 0; round < 12; round++)
    {
        // 流式调用大模型
        StreamRoundResult result = await StreamChatAsync(
            settings, tools, chunk => {
                visible.Append(chunk);
                onPartial?.Invoke(visible.ToString()); // 打字机效果
            }, _cts.Token);

        // 没有工具调用 = 最终答案,返回
        if (result.ToolCalls == null || result.ToolCalls.Count == 0)
            return visible.ToString();

        // 有工具调用 = 逐个执行,结果塞回历史
        foreach (var call in result.ToolCalls)
        {
            string toolName = call["function"]["name"].ToString();
            JObject args = JObject.Parse(call["function"]["arguments"].ToString());

            onStatus?.Invoke("正在执行 " + toolName + "…");

            // 关键:进程内直接调用 CommandBus
            string result = await CommandBus.ExecuteAsync(toolName, args);

            // 工具结果加回对话历史,模型下一轮能看到
            _history.Add(new JObject {
                ["role"] = "tool",
                ["tool_call_id"] = call["id"].ToString(),
                ["content"] = result
            });
        }
        // 循环回到第3步,模型带着工具结果继续生成
    }
}

有几个设计细节值得展开说。

第一,system prompt 是动态拼的。 每次请求都会重新构建,包含当前 Revit 上下文(文档名、视图名、选中构件数)和已注册命令数量。这样模型知道用户在哪个文件里工作,不会瞎编。

当前 Revit 上下文:

  • • 文档:项目A.rvt
  • • 视图:Floor Plan - Level 1
  • • 视图类型:FloorPlan
  • • 比例:1:100
  • • 当前选择构件数:0

第二,流式输出用 SSE 解析。 大模型返回的是 data: {JSON} 格式的逐行数据。ChatClient 逐行解析,每来一段 token 就回调 onPartial,界面通过一个 50ms 的 DispatcherTimer 定时刷新,实现打字机效果。

第三,工具调用的参数是分片到达的。 流式返回时,tool_calls 的 function.arguments 不是一次性给全的,而是切成很多小段。ChatClient 用一个 Dictionary<int, JObject> 按 index 累加拼接,等流结束后才拿到完整的参数 JSON。

第四,工具列表是动态过滤的。 LoadTools() 读取 tools.json 里预定义的工具 schema,再用 CommandBus.RegisteredNames 过滤——只有当前 Revit 版本实际注册成功的命令才会发给模型。Revit 2020 不会看到 create_floor(那个 API 2022 才有),模型也就不会尝试调用它。

还有一个细节:温度设成了 0.2。BIM 操作需要确定性,不能让模型太有创造力。

2.3 聊天界面和命令集怎么联动的

这是整个架构最核心的部分。ChatClient 拿到模型的 tool_calls 后,怎么在 Revit 里执行?答案就是 CommandBus。

CommandBus 是一个静态类,替代了原来基于 TCP 的 MCP 架构。以前是 ChatClient → McpBridge → localhost:8080 → MCP Server → CommandSet,现在是 ChatClient → CommandBus → IRevitCommand → ExternalEvent → Revit API。全程在一个进程里,没有网络开销。

初始化和执行的核心代码:

// CommandBus.cs — 进程内命令总线
publicstaticclassCommandBus
{
    privatestatic RevitCommandRegistry _registry;
    privatestaticreadonlyobject ExecuteLock = new();

    // 初始化:反射加载 CommandSet DLL 里的所有命令
    public static void Initialize(UIApplication uiApp)
    {
        // 读 commandRegistry.json 命令清单
        var config = new ConfigurationManager(_logger);
        config.LoadConfiguration();

        _registry = new RevitCommandRegistry();
        var manager = new CommandManager(
            _registry, _logger, config, uiApp);

        // 反射扫描 DLL,实例化所有 IRevitCommand 实现
        manager.LoadCommands();
    }

    // 执行:在后台线程调用,避免卡 UI
    public static Task<string> ExecuteAsync(
        string method, JObject parameters
)

    {
        return Task.Run(() => Execute(method, parameters));
    }

    public static string Execute(
        string method, JObject parameters
)

    {
        // 检查「操作 Revit」开关是否打开
        if (!RevitOperateGate.Enabled)
            return"已关闭操作 Revit,命令未执行";

        // 串行锁:同一时间只执行一条命令
        Monitor.TryEnter(ExecuteLock, TimeSpan.FromMinutes(3));

        try {
            // 从注册表查找命令
            if (!_registry.TryGetCommand(method, outvar command))
                return"找不到命令:" + method;

            // 执行命令,返回 JSON 结果
            object result = command.Execute(
                parameters, Guid.NewGuid().ToString("N"));

            return JsonConvert.SerializeObject(result);
        }
        finally { Monitor.Exit(ExecuteLock); }
    }
}

几个关键设计:

串行锁。 Revit API 不是线程安全的,同一时间只能有一个线程在操作模型。CommandBus 用 Monitor.TryEnter 做了一个 3 分钟超时的串行锁,保证命令排队执行。

后台线程执行。 ExecuteAsync 用 Task.Run 在线程池上跑。因为命令内部会 Wait 一个 ExternalEvent,如果在 UI 线程上跑就会死锁。

操作开关。 聊天面板右上角有个「操作 Revit」按钮。关掉后 RevitOperateGate.Enabled 为 false,CommandBus 直接拒绝执行,ChatClient 也不把工具列表发给模型。用户可以放心聊天问问题,不用担心 AI 乱改图。

2.4 命令的编写模式

每条命令由两部分组成:一个 Command 类负责解析参数和调度,一个 EventHandler 类负责在 Revit API 线程上执行实际操作。这个模式解决了 Revit API 的线程限制问题——大模型调用在后台线程,但 Revit API 只能在主线程执行。

以最简单的 say_hello 命令为例:

// SayHelloCommand.cs — 命令入口
publicclassSayHelloCommand : ExternalEventCommandBase
{
    private SayHelloEventHandler _handler;

    publicoverridestring CommandName => "say_hello";

    public SayHelloCommand(UIApplication uiApp)
        : base(new SayHelloEventHandler(), uiApp)
 { }

    public override object Execute(JObject parameters, string requestId)
    {
        string message = parameters?["message"]?.ToString() ?? "Hello!";

        // 把参数传给 handler
        _handler.SetParameters(message);

        // 触发 ExternalEvent 并等待完成(15秒超时)
        if (RaiseAndWaitForCompletion(15000))
            returnnew { success = true, message = _handler.Result };
        else
            thrownew TimeoutException();
    }
}

// SayHelloEventHandler.cs — 实际执行
publicclassSayHelloEventHandler : IExternalEventHandler, IWaitableExternalEventHandler
{
    privatereadonly ManualResetEvent _resetEvent = new(false);

    publicstring Result { get; privateset; }

    public void SetParameters(string message)
    {
        Message = message;
        _resetEvent.Reset(); // 必须重置!
    }

    public bool WaitForCompletion(int timeout)
        => _resetEvent.WaitOne(timeout);

    public void Execute(UIApplication app)
    {
        try {
            Result = Message;
            TaskDialog.Show("Revit AI", Message);
        }
        finally {
            _resetEvent.Set(); // 通知等待方:完成了
        }
    }
}

这个模式是整个命令集的基础。每个命令都是这个套路:

  1. 1. Command 类继承 ExternalEventCommandBase,在 Execute 里解析 JObject 参数
  2. 2. 把参数传给 EventHandler,调用 RaiseAndWaitForCompletion
  3. 3. ExternalEvent 被 Revit 调度到主线程执行 EventHandler 的 Execute(UIApplication)
  4. 4. EventHandler 用 ManualResetEvent.Set() 通知完成,Command 拿到结果返回

这里有一个容易踩的坑:每次 SetParameters 时必须先 Reset()。否则上一次 Execute 留下的已触发信号会让 WaitOne 立刻返回,handler 根本没执行。

2.5 模型怎么知道有哪些命令可以调

大模型的 function calling 需要你告诉它有哪些工具可用,每个工具接受什么参数。这个插件用了一个 tools.json 文件来定义所有工具的 schema,格式就是 OpenAI 的 function calling 标准。

比如 create_room 命令的 schema 长这样:

{
  "type":"function",
"function":{
    "name":"create_room",
    "description":"在指定位置创建房间",
    "parameters":{
      "type":"object",
      "properties":{
        "data":{
          "type":"array",
          "items":{
            "type":"object",
            "properties":{
              "name":{"type":"string"},
              "number":{"type":"string"},
              "level":{"type":"number"}
            }
          }
        }
      },
      "required":["data"]
    }
}
}

项目里还有一个 JsonSchemaGenerator 工具类,可以从 C# 模型类自动生成 JSON Schema。不过实际使用中,tools.json 是预定义的,编译时按 Revit 版本生成不同的清单。

一个有意思的细节:ChatClient 在发送前会做一轮 schema 清洗。因为有些 schema 定义可能包含 PowerShell 风格的 @{type=object;...} 字符串(生成脚本的问题),这会让大模型接口直接返回 HTTP 400。清洗逻辑会把这些非法字符串替换成合法的 JSON Schema。

2.6 安全设计:API Key 和工作区隔离

插件的安全设计有两个值得说的点。

第一,API Key 用 Windows DPAPI 加密存储。 不走明文配置文件。ApiKeyVault 用 ProtectedData.Protect 加密,按「Revit 版本 + 用户 + 项目」维度隔离。换一份 .rvt 文件,API Key 不会带过去。配置文件 settings.json 里压根不存 Key,加载时会把误存的 Key 清掉。

第二,聊天记录按工作区隔离。 WorkspaceScope 用「Revit 版本 + 用户名 + 项目路径哈希」作为唯一标识。不同项目的聊天记录互不干扰。未保存的文档用 unsaved-{hash} 标记,另存为时能自动迁移。

第三,危险操作要确认。 删除、清理这类命令调用 ConfirmationHelper.Confirm,弹 Revit 原生 TaskDialog 让用户确认。删 50 个构件之前会问你一句「About to delete 50 element(s). Continue?」。

3 写在最后

回过头来看,我觉得这个框架最值得说的不是某个具体技术点,而是设计思路:把大模型的 function calling 和 Revit 的 ExternalEvent 机制缝在一起,用一个进程内的 CommandBus 替代了外部 MCP Server。

对用户来说,门槛从「配端口、配协议、配环境变量」降到了「填一个 API Key」。对开发者来说,新增一个命令只需要写两个类(Command + EventHandler),不用改通信层。

当然也有改进空间。比如 send_code_to_revit 命令允许 AI 直接执行 C# 代码,能力很强但风险也高。system prompt 里虽然禁止用它做删除,但 prompt 约束不是硬约束。

另一个值得关注的趋势是:大模型的 function calling 能力越来越强,DeepSeek、通义千问这些国产模型都已经支持。这意味着类似架构不只适用于 Revit,任何有 API 的桌面软件都可以借鉴——AutoCAD、SketchUp、甚至 Excel。

以前我们说「AI 赋能工具」,更多是 AI 作为一个独立产品。而我做这个框架想表达的思路是:AI 作为已有工具的一个嵌入式大脑。不需要换工具,不需要改流程,在原来的工作环境里多了一个能听懂人话的助手。

这可能才是 AI 进入专业领域的正确姿势。






声明:本文观点仅代表作者观点 版权归筑纬科技所有

未经授权请勿转载欢迎转发到朋友圈

转载时请注明出处:筑纬科技


• END •




上海筑纬建筑科技有限公司是一家专业从事建筑设计综合优化、BIM设计咨询、装配式设计咨询的新型技术服务企业,为 高新技术企业 和 全国科技型中小企业 ,公司已建立完善的质量管理与标准化体系,先后通过ISO 9001 质量管理体系认证、ISO 14001 环境管理体系认证及ISO 45001 职业健康安全管理体系认证。由原设计院技术骨干、原地产公司高级管理人员、BIM工程师、装配式建筑工程师等共同组成,提供建筑工程综合设计优化和精细化审图、建筑BIM设计咨询及机电深化、装配式建筑设计咨询和深化的服务。


建筑设计优化业务方面,公司定位为房地产公司设计管理的延伸和补充,精耕民用建筑综合设计咨询及优化,秉承“专业创造价值,服务铸就品质”的理念,专注于客户及市场需求,全过程或根据业主要求的重要阶段对设计的经济性、合理性、安全性等进行把控,为客户节约无谓成本支出并显著提升图纸质量,达到“降本增效”的目标。


BIM设计咨询业务方面,我们和业内诸多大型优秀地产集团/设计院/总包单位开展合作,咨询服务的建筑产品涵盖了所有的业态。服务阶段涵盖了BIM顾问、设计阶段BIM、施工阶段BIM、正向BIM设计等。提供高品质住宅设计,精品地库设计等。通过数字化建造技术在设计阶段辅助设计师减少设计差错、优化设计质量,在施工阶段辅助施工单位节约成本、完成高质量的机电安装筹划;和业内主流软件服务商协作,帮助客户对建筑施工阶段进行管理;可将BIM模型运用到运维阶段(LOD500:模型精度达到运维级)。


装配式设计咨询业务方面,我们采用基于BIM软件平台实施PC构件深化设计服务,PC构件空间参数和配筋均可在BIM模型中联动修改和调整,提前规避解决所有钢筋碰撞问题。提供轻量化模型交付,所见即所得,便于各方提前进行具象化审核。可以按照工业4.0的要求提供数字化交付,提供BOM清单数据,提供所有PC构件外形尺寸、金属件统计数据、每一根钢筋的空间下料尺寸,运用物联网技术提供PC构件的生产、存储、运输和安装支持服务。相比业内传统的PC深化设计公司,筑纬针对特定建筑产品能为客户提供系统性技术解决方案和施工服务,尤其在物流重载仓库和工业上楼建筑领域。


公司拥有专家团队和具有大型甲级设计院从业背景的资深技术人员,专业技术人员均具有深厚的专业功底,均具有丰富的设计院和优化公司双重工作经验,技术水平及成本控制能力在同行业中处于领先地位。














—— 本文同步自微信公众号「筑纬建筑科技」,查看原文 ——