乐于分享
好东西不私藏

说实话,AI写代码真的会翻车:但这个Revit插件框架我跑通了

说实话,AI写代码真的会翻车:但这个Revit插件框架我跑通了

AI写代码真的会翻车

说实话,AI写代码真的会翻车:但这个Revit插件框架我跑通了

2026-05-02 | Revit二次开发 | 实操复盘


一、开篇:AI写代码,快是真快,翻车也是真翻

说实话,用AI写代码这事儿,我一开始是拒绝的。

但用着用着就上头了——需求一说,代码就出来了,看着还挺像那么回事。直到我开始做这个 Revit插件加载框架

前前后后踩了8个坑,有些是AI”自信地写错”,有些是我自己没把关好。但最终,这个框架我跑通了,而且跑得还不错。

这篇文章,就是把这个过程完整地记下来——不是教你怎么用AI写代码,而是教你怎么跟AI一起,把事情做对


二、背景:我要解决什么问题?

做Revit二次开发的同学,应该都懂这个痛点:

每次改菜单、加按钮,都要重新编译、重新启动Revit。

一个插件做下来,光重启Revit就能耗掉半天。尤其是当你在做 AI辅助开发 的时候——让AI写DLL,然后你要不断测试,每次都要重启,这个效率简直低到令人发指。

所以我给自己提了一个需求:

做一个Revit插件框架,满足:

  1. JSON配置驱动菜单——改菜单不用改代码,改JSON就行
  2. DLL热重载——新DLL覆盖后,不用重启Revit,点个按钮重新加载
  3. AI只需要写DLL——框架固定,AI只写业务插件,不动框架代码

目标很清晰,看起来也不复杂。但做起来……


三、方案设计:核心思路

最终跑通的方案,核心就这几个模块:

RevitPluginLoader/
├── App.cs                    # Revit入口(IExternalApplication)
├── UI/
│   ├── RibbonBuilder.cs     # 读取JSON → 动态构建Ribbon菜单
│   ├── DynamicDispatchers.cs # 预定义20个命令类(Cmd000~Cmd019)
│   ├── ButtonConfigRegistry.cs # 配置注册表(索引→ButtonConfig映射)
│   └── ReloadCommand.cs     # 手动重载命令
├── Core/
│   └── PluginExecutor.cs    # DLL动态加载 + 执行命令
└── Models/
    ├── MenuConfig.cs        # JSON配置模型
    └── ButtonConfig.cs      # 按钮配置模型

核心设计决策

1. 为什么用”预定义20个命令类”,而不是动态生成?

Revit的IExternalCommand必须通过AddInManifest注册,或者用Reflection.Emit动态生成类型。前者太死板,后者在Revit环境里各种权限/沙箱问题。

最终方案:预定义20个命令类(Cmd000~Cmd019),JSON配置里指定每个按钮对应哪个Cmd,运行时通过ButtonConfigRegistry找到对应的DLL和类,用PluginExecutor加载并执行。

2. JSON配置长什么样?

{
"Tabs": [{
"Name""MyPlugin",
"Panels": [{
"Name""工具",
"Buttons": [
        {
"Name""Hello",
"DllPath""D:\\0_LiteSoft\\Appdomain\\SamplePlugin\\bin\\Debug\\SamplePlugin.dll",
"ClassName""SamplePlugin.HelloCommand",
"DispatcherIndex"0
        }
      ]
    }]
  }]
}

3. 热重载怎么实现?

点击”Reload”按钮 → 重新读取JSON → 更新ButtonConfigRegistry → 下次点击按钮时用新的DLL。

(严格来说,当前版本是”手动重载配置”,DLL文件锁定问题还在优化中。但至少菜单不用重启Revit就能改了。)


四、踩坑记录:8个坑,个个都是时间杀手

这部分是全文最有价值的——这些坑,AI不会主动告诉你,文档里也不会写


坑1:csproj里引用了不存在的文件

AI生成代码的时候,很乐观地往csproj里加文件引用。比如它用了一个PluginCommandDispatcher.cs,但这个文件根本没生成。

现象: 编译直接报错,找不到源文件。

解决: 手动打开csproj,把不存在的文件引用删掉,把实际有的文件(ButtonConfigRegistry.csDynamicDispatchers.cs)加进去。

教训: AI生成的csproj,一定要人工核对文件列表。


坑2:所有命令类都必须加[Transaction]特性

Revit API要求:**每个实现IExternalCommand的类,必须声明[Transaction(TransactionMode.Manual)]**(或其他TransactionMode)。

AI生成DynamicDispatchers.cs的时候,给20个Cmd类都忘了加这个特性。

现象: Revit加载后,点击按钮直接报错,不给你任何有用的提示。

解决: 给所有Cmd000~Cmd019都加上:

[Transaction(TransactionMode.Manual)]
publicclassCmd000 : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, refstring message, ElementSet elements)
    {
// ...
    }
}

教训: Revit API的[Transaction]特性,AI经常会漏。每次新增命令类,都要检查。


坑3:System.Windows.Forms在Revit里用不了

AI给ReloadCommand.cs里用了MessageBox.Show()——这是System.Windows.Forms里的,Revit的AddIn环境根本不支持。

现象: 编译报错:System.Windows.Forms命名空间不存在。

解决: 改用Revit自带的TaskDialog

TaskDialog.Show("提示""配置已重新加载");

教训: Revit插件里做UI交互,用TaskDialog,别用WinForms。


坑4:ToolTip还是Tooltip?拼写不一致

AI在MenuConfig.cs里用了Tooltip(小写p),但在RibbonBuilder.cs里读取的时候用的是ToolTip(大写P)。

现象: 编译不报错,但运行时按钮的提示文本全是空的,找半天找不到原因。

解决: 统一成ToolTip(Revit API的PushButtonData.ToolTip属性就是这个拼写)。

教训: JSON字段名和C#属性名,大小写敏感,一定要统一。


坑5:UIApplicationUIControlledApplication傻傻分不清

Revit有两个”应用”对象:

  • UIControlledApplication:插件启动时用(构建菜单)
  • UIApplication:命令执行时用(操作文档)

AI在ReloadCommand里试图把UIControlledApplication当成UIApplication来用,直接导致类型转换失败。

现象: 运行时异常:Unable to cast object of type 'UIControlledApplication' to type 'UIApplication'

解决: 重构ReloadCommand,不再试图重新调用RibbonBuilder.Build(),而是只更新配置注册表。

教训: 这两个类型,生命周期不同,用途不同,不能混用。


坑6:PluginDomain属性访问权限不够

AI在App.cs里写了PluginDomain { get; private set; },但后续在别的类里想给PluginDomain赋值,根本赋不了。

现象: 编译报错:属性set不可访问。

解决: 改成{ get; set; },或者提供一个InitializePluginDomain()方法。

教训: 框架里的”全局状态”,访问权限要提前规划好。


坑7:DLL被Revit进程锁定,无法覆盖

编译错误

这是最头疼的一个。框架加载了DLL之后,Revit进程就把DLL文件锁住了。你想重新编译、覆盖DLL?没门。

现象: 编译输出:”无法将文件'...SamplePlugin.dll'复制到'...SamplePlugin.dll'。文件正被另一进程使用。

解决: 用taskkill强制关闭所有Revit进程,重新编译,再启动Revit。

taskkill //F //IM Revit.exe

优化方向: 后续可以用AppDomain隔离加载DLL,实现真正的”热重载”不重启。当前版本先手动重启Revit。

教训:.NET FrameworkAssembly.LoadFrom()会锁定文件,这是设计如此。要实现热重载,必须用AppDomain或者Assembly.Load(byte[])


坑8:缺少using语句,AI”自以为”命名空间存在

这个坑最隐蔽。AI生成代码的时候,有时候会用某个类,但不加对应的using

比如PluginExecutor.cs里用了ElementSet,但没加using Autodesk.Revit.DB;

现象: 编译报错:找不到类型ElementSet

解决: 人工检查所有用到的Revit API类型,把对应的using补全。

教训: AI写C#代码,永远要人工核对using列表。


五、最终效果:跑通Hello World

经过这一轮修复,最终跑通了:

目录结构:

D:\0_LiteSoft\Appdomain\RevitPluginLoader\
├── src\RevitPluginLoader\          # 框架主体
├── src\SamplePlugin\               # 示例插件(AI只写这个)
└── config\menu.json                # 菜单配置

Hello World示例(SamplePlugin.HelloCommand):

[Transaction(TransactionMode.Manual)]
publicclassHelloCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, refstring message, ElementSet elements)
    {
        UIApplication uiApp = commandData.Application;
        TaskDialog.Show("Hello"$"当前文档:{uiApp.ActiveUIDocument.Document.Title}");
return Result.Succeeded;
    }
}

运行效果:

  1. 启动Revit → 自动加载框架 → 根据menu.json生成Ribbon菜单
  2. 点击”Hello”按钮 → 执行SamplePlugin.dll里的HelloCommand
  3. 弹出TaskDialog,显示当前文档标题 ✅
测试插件
重载命令

六、这个框架,适合谁用?

适合:

  • 正在用AI辅助写Revit插件的人
  • 受够了”改一行代码就要重启Revit”的人
  • 想做插件框架、但不想从零开始的人

不适合:

  • 纯小白(这个框架需要一定的C#和Revit API基础)
  • 生产环境(当前版本DLL热重载还没完全实现,需要手动重启Revit)

七、后续可以扩展的方向

  1. 真正的DLL热重载:用AppDomain隔离,实现不重启Revit就能加载新DLL
  2. 命令参数传递:JSON配置里支持传参数给命令
  3. 按钮图标:支持从配置文件指定图标路径
  4. 插件管理界面:做个WPF窗体,可视化配置菜单

八、总结

说实话,这篇文章不是什么”AI神技分享”,而是一个普通人用AI写代码的真实记录

AI能帮你提速,但它不会帮你负责。踩的8个坑,每一个都是”AI写了,你没发现,直到编译或运行时才爆”。

但反过来,如果没有AI,这个项目我从零写,可能要花3天。现在半天就跑通了。

工具是工具,人才是核心。AI不会取代你,但会用AI的人会取代你


如果你也在用AI做Revit二次开发,欢迎交流。框架源码我整理一下,后续发出来。