简介:Revit 2019.2官方二次开发案例包是一份面向Revit开发者的高密度学习资源,共收录147个Autodesk官方示例及完整源代码。这些案例围绕Revit API展开,覆盖C#、VB.NET等语言,涉及基本元素操作、参数读写与关联、视图及图形控制、事件响应、外部工作流集成、自定义UI面板等主题,既适合初学者按例入门,也适合进阶开发者对照最佳实践排查性能与兼容性问题。资源包采用zip压缩,大小约256.85MB,内含2000个文件,以1105个.cs源文件、186个.addin插件配置、186个.rtf说明文档、167个.csproj工程文件、176个.resx资源文件为主,另含.rvt/.rfa样例模型、.xaml界面及少量.dll依赖,结构清晰,可在Visual Studio中直接打开运行。目前已有4470人学习浏览。通过逐个案例研读,开发者可掌握从创建墙、楼板、柱等构件到自定义命令面板、协同工作流集成的完整路径,同时理解常见API陷阱与优化策略,对实际项目开发极具参考价值。
1. 为什么 Revit 2019.2 二次开发官方案例值得啃:147 例不是教程,是一本问题词典
很多人学 Revit 2019.2 二次开发,最难受的不是看不懂 API 文档,而是对着 .NET 类库翻来翻去,翻到第二天还是不知道怎么写出一段能在 Revit 里跑起来的代码。这套官方案例加源代码,147 个例子,在我眼里不是“从入门到精通”的教程,而是一本按功能分区的问题词典——你想做族参数批量写入,就先找一个参数读写案例;你想做元素筛选,就先找一个 FilteredElementCollector 案例。它解决的不是“API 是什么”的问题,而是“我这个功能该从哪个入口开始”的问题。适合读它的人很明确:刚接触 Revit API 的开发者、要在设计院或施工企业里做提效工具的人,以及那些已经会写一点脚本但想用 C# 做出完整插件的从业者。
2. 跑通第一个外部命令:环境配置与最小可运行代码
2.1 先理解 Revit 二次开发的三个“黑匣子”概念
我见过不少新手栽在同一个地方:急于写功能,却没有先理解 Revit 插件运行时的三个基础概念。这三个概念不是纯理论,它们直接决定了你的代码能不能被 Revit 加载、能不能修改文档。
第一个是外部命令(ExternalCommand)。Revit 不会自动执行你程序集里的任意方法,它只认实现 IExternalCommand 接口的类。当你点击按钮或通过外部工具触发时,Revit 会实例化这个类并调用 Execute 方法。Execute 方法有三个参数:commandData 携带当前文档和 UI 上下文,message 用于向外传递错误信息,elements 用于高亮出错元素。
第二个是事务(Transaction)。Revit 的文档模型不允许你在没有事务保护的情况下直接修改任何元素。这是它和普通 CRUD 应用最大的差异——每一次创建、删除、改参数,都必须包在一个 Transaction 里。事务控制不好,就会出现“Cannot modify document”这种拦住一半开发者的报错。
第三个是 Document 与 Element 模型。Document 代表当前项目文件,所有墙、门、窗、标注都是 Element。每个 Element 携带类型参数和实例参数,参数的值通过 Parameter 对象访问。理解了这三个概念,你读 147 例里的任何一段代码,都不会觉得是在看天书。
2.2 配置 Visual Studio 工程:三处关键设置
我一般用 Visual Studio 创建一个 .NET Framework 类库项目,而不是控制台项目。类库没有入口点,Revit 以加载 DLL 的方式调用它。目标框架建议设为 .NET Framework 4.7 或 4.7.2,具体看本机 Revit 2019.2 安装的运行时版本,太高的框架在低版本 Revit 上会加载失败。
创建好项目后,在解决方案资源管理器里右键“添加引用”,浏览到 Revit 2019.2 的安装目录,通常路径类似 Program Files 下的 Autodesk 目录,找到 RevitAPI.dll 和 RevitAPIUI.dll 并添加。这两个程序集分别包含基础 API 与 UI 扩展类,缺一个都编译不过。选中这两个引用,把“复制本地”设为 False,避免编译后把几百兆的 API DLL 拷到输出目录。
代码里需要用到外部命令特性,所以要引入 Autodesk.Revit.Attributes 命名空间。这是最容易漏的一步——很多人写好了 Execute 方法,却忘了给类标 [Transaction(TransactionMode.Manual)] 特性,加载后按钮直接灰置。
2.3 最小可运行命令:让 Revit 里长出一面墙
我建议把第一个例子的目标定得足够小:在当前项目中标高上创建一段 3 米长的直墙,并写入一条注释参数。这段代码虽然只有 60 行左右,但把事务、元素收集器、几何创建、参数写入四条主线全部串了一遍。
using System; using System.Linq; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; namespace HelloWall { [Transaction(TransactionMode.Manual)] public class HelloWallCmd : IExternalCommand { public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements) { // 拿到当前激活的文档 Document doc = commandData.Application.ActiveUIDocument.Document; using (Transaction tx = new Transaction(doc, "创建示例墙")) { tx.Start(); // 找一个非幕墙的墙类型,避免默认取到幕墙导致创建失败 WallType wallType = new FilteredElementCollector(doc) .OfClass(typeof(WallType)) .Cast<WallType>() .FirstOrDefault(wt => !wt.IsCurtain); if (wallType == null) { message = "当前项目中没有可用的非幕墙墙类型"; tx.RollBack(); return Result.Failed; } // 取第一个标高作为墙的底部参照 Level level = new FilteredElementCollector(doc) .OfClass(typeof(Level)) .Cast<Level>() .FirstOrDefault(); // 定义一条从 (0,0,0) 到 (3,0,0) 的水平线段,单位是英尺 Line line = Line.CreateBound(new XYZ(0, 0, 0), new XYZ(3, 0, 0)); // 创建墙,高度 3.0 英尺,偏移 0,不翻转,非结构墙 Wall wall = Wall.Create( doc, line, wallType.Id, level.Id, 3.0, 0.0, false, false); // 写入实例注释参数,注意用 BuiltInParameter 而不是参数名字符串 Parameter comment = wall.get_Parameter( BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS); if (comment != null && !comment.IsReadOnly) { comment.Set("由 HelloWall 命令生成"); } tx.Commit(); } return Result.Succeeded; } } }这段代码有几个参数需要说明。TransactionMode.Manual 表示事务由代码显式管理,这是绝大多数写命令场景的默认选项;Line.CreateBound 的两个端点坐标单位是英尺,所以 3.0 对应 3 英尺,想生成 3 米就把端点改成 (10, 0, 0) 或自己除以 0.3048;Wall.Create 的第七、八个参数分别是翻转朝向和结构属性,业务里常按实际需要配置。注释参数写入用 get_Parameter 加 BuiltInParameter 枚举,不要用 LookupParameter("注释"),因为中英文版本项目参数名不一致,枚举跨语言稳定。
2.4 用 AddInManager 加载并调试
代码编译通过不代表万事大吉,还要让 Revit 认识这个程序集。常见做法有两种:直接用 AddInManager 临时加载,或者写 .addin 文件做持久部署。调试阶段我用前者。
在 Revit 2019.2 的“附加模块”选项卡下找到“外部工具”,打开 AddInManager。加载刚才编译出来的 HelloWall.dll,这时命令列表里会出现 HelloWallCmd。选中它点 Run,Revit 会执行 Execute 方法。如果代码里设置了断点,需要以“附加到进程”的方式让 Visual Studio 连接到 Revit.exe 进程,然后触发命令。这个调试链路我建议一上来就练熟,后续读官方案例时,你会频繁改代码、重新编译、再加载验证。
3. 147 例怎么读:按模块分类建立自己的学习路径
3.1 先按 API 功能域给 147 例分组
我不建议从第一个例子顺序读到最后一个。官方案例通常按功能域组织,但你自己心里也要有一张地图。拿到这套案例后,我会先把案例题目扫一遍,按下表归类打标签。
| 功能域 | 典型案例方向 | 你需要它的场景 |
|---|---|---|
| 文档与项目 | 打开、保存、版本信息、多文档管理 | 做文件批处理工具 |
| 元素创建 | 墙、梁、柱、楼板、族的创建 | 参数化建模工具 |
| 参数管理 | 实例参数、类型参数、共享参数读写 | 批量改标注、数据导出 |
| 元素筛选 | FilteredElementCollector、参数过滤器 | 统计某个区域的门窗数量 |
| 视图与图纸 | 视图创建、视图范围、图纸导出 | 自动出图、批量导出 PDF |
| 标注与文字 | 尺寸标注、文字注释创建与修改 | 一键标注工具 |
| 族与族类型 | 族管理器、类型属性切换 | 族库批量替换与升级 |
| 链接与导入导出 | CAD、IFC 链接、外部数据交换 | 模型与外部系统数据同步 |
分类的作用在下周就会体现出来。你手上的项目如果需要批量修改门标记字体,直接翻到“标注与文字”这一类,而不是从第一个例子重新开始找。147 例看着数量吓人,按功能域拆开,每个域只有十几例,心理压力小很多,学习效率也高。
3.2 每个案例的“五步读法”
我读官方案例从不大段通读,而是固定走五步:先找入口类,再看事务范围,然后锁定核心 API 调用,接着查参数单位,最后在最小案例里复现。
第一步找入口类。看这个案例是外部命令还是外部应用,入口类写在哪个文件。第二步看事务范围,重点关注事务的 Start 和 Commit 之间包住了哪些操作,这能告诉你哪些逻辑被 Revit 视为“同一次修改”。第三步锁定核心调用,比如创建楼板的 Floor.Create、读取几何的 get_Geometry,这些是你要迁移到自己工具里的关键方法。第四步查参数单位,尺寸到底是英尺还是毫米,角度是弧度还是度,这步不看清楚,代码一跑尺寸全错。第五步复现,我会把案例代码复制到一个干净类库工程里,删掉与核心无关的边角逻辑,跑通一次再改造。
第五步最重要,也最容易被跳过。光看代码你会觉得“哦原来是用这个方法”,但只有真正编译运行过一次,你才会发现引用缺失、命名空间漏引、API 版本差异这些没人写在文档里的问题。
3.3 从官方案例到自己的项目:改写时保持“最小差异”原则
案例读懂了,下一步是把它搬到真实项目里。我在这条路上吃过亏,一开始总是顺手把案例里的类名、变量名、甚至业务流程一起改成自己项目的风格,结果一改就错,跑不通也分不清是改错了还是原代码就有问题。
现在我的习惯是“最小差异”改写。第一步只改数据来源:把案例里写死的集合改成从当前文档筛选出来的真实元素。第二步只改输出行为:把案例里创建单个元素改成循环创建。第三步才改界面和交互逻辑。每一步改完都编译运行一次,确认通过再进入下一步。这样 147 例里的任何一段优质代码,都能在半小时内变成一个符合自己需求的小工具,而不是变成一段改乱的、无法定位问题的代码。
4. 事务、参数与元素筛选:官方案例里最值得深挖的三类代码
4.1 事务与回滚:拒绝 “Cannot modify document”
官方案例里出现频率最高的结构就是 Transaction。我见过一段被多人转发的代码,事务 Start 之后没有 try/catch,没有 RollBack,Commit 失败后整个执行过程卡住,后续操作全部失效。读案例时你要特别留意它的异常分支处理方式。
using (Transaction tx = new Transaction(doc, "批量修改注释")) { tx.Start(); try { // 这里放元素修改逻辑,比如遍历墙集合写入注释 foreach (Wall wall in walls) { Parameter p = wall.get_Parameter( BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS); p?.Set("批量处理结果"); } tx.Commit(); } catch (Exception ex) { tx.RollBack(); TaskDialog.Show("批量处理失败", ex.Message); return Result.Failed; } }这段代码里有三个值得注意的地方。using 块保证了事务对象最终被释放,即使 Commit 抛出异常也不会把文档锁住。catch 里主动调用 RollBack,把已修改但未提交的操作全部撤销,避免脏数据留在文档里。return Result.Failed 让 Revit 知道命令没有正常完成,配合 TaskDialog 弹出错误信息,开发期排查问题会直观很多。
还要提醒一点:不要在事务 Start 之前做耗时操作,也不要把耗时操作堆在事务里。Revit 对事务有运行时长感知,超过一定时间会弹出交互提示或直接中断。常见做法是把数据收集、计算放到事务外,事务内只做元素的修改和参数写入。
4.2 参数读写与单位换算:读出来是 3,实际是 3 英尺
官方案例里参数读写通常配合单位换算出现,因为 Revit 所有内部数值都使用英尺体系。长度用英尺,面积用平方英尺,角度用弧度。直接读出来的数字,如果不做换算就输出,结果会让人一头雾水。
// 读取墙的宽度,注意此参数属于类型参数 Parameter widthParam = wall.WallType.get_Parameter( BuiltInParameter.WALL_ATTR_WIDTH_PARAM); double widthInFeet = widthParam.AsDouble(); // 换算出毫米值,1 英尺 = 304.8 mm double widthInMm = widthInFeet * 304.8; // 读取实例参数:墙的注释 Parameter commentParam = wall.get_Parameter( BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS); string comment = commentParam.AsString();这里最容易被坑的是获取参数的入口。墙宽是类型参数,要从 wall.WallType 取;注释是实例参数,从 wall 实例本身取。很多新手直接 wall.get_Parameter(BuiltInParameter.WALL_ATTR_WIDTH_PARAM),会发现返回值不为 null 但读出来总是不对,或干脆返回 null,就是因为没区分类型参数与实例参数。
单位换算我建议直接乘 304.8 或者用单位转换工具类。框架版本不同,单位换算 API 名称变化较大,直接乘常数是稳定且可读的做法。角度参数同理,读取后乘 180 再除 Math.PI,转成度数再展示。
4.3 用 FilteredElementCollector 筛出你需要的那面墙
147 例里筛选元素的代码会反复出现,因为绝大多数业务第一步都是“找到符合条件的元素”。FilteredElementCollector 是官方推荐的筛选入口,比遍历整个文档高效得多。
// 收集当前文档中所有非幕墙的墙,且宽度大于 200mm FilteredElementCollector collector = new FilteredElementCollector(doc); ICollection<Wall> walls = collector .OfClass(typeof(Wall)) .Cast<Wall>() .Where(wall => { if (wall.WallType.IsCurtain) return false; double width = wall.WallType.get_Parameter( BuiltInParameter.WALL_ATTR_WIDTH_PARAM).AsDouble(); return width > 0.2 / 0.3048; // 0.2 米转英尺 }) .ToList();代码里两个细节值得解释。OfClass(typeof(Wall)) 把收集范围限定为墙,避免把门窗、家具全部筛进来;在过滤条件里把 0.2 米先除以 0.3048 转成英尺,再做比较,避免阈值错一个数量级。
筛选完成后你还经常需要按位置、楼层、房间再去重过滤,常见的做法是在 Where 里补充对元素位置参数的判断,或者把收集结果按 Level 属性分组。这部分逻辑官方案例里不一定给全,但筛选骨架是同一套。建议把这段收集与过滤的代码沉淀成一个工具方法,所有工具类共用,后续维护只改一处。
5. 避坑:Revit 二次开发最常见的 5 个翻车现场与排查方法
5.1 外部命令按钮灰置,点下去没反应
现象:用 AddInManager 加载 DLL 之后,命令列表里能看到类名,但按钮是灰色的,点击没有任何反馈。
原因:外部命令类没有正确实现 IExternalCommand 接口,或者类没有被标记 [Transaction(TransactionMode.Manual)] 特性。Revit 在加载时检查接口与特性,不符合要求的命令直接不可用。
解决:打开类定义确认继承 IExternalCommand,且 Execute 方法签名完全一致。常见错误是把 Execute 写成了 ExecuteInternal,或把参数顺序调换。加上 Transaction 特性后重新编译加载,按钮即恢复可选。
5.2 运行时报错 “Cannot modify document”
现象:命令执行到一半,抛出 Autodesk.Revit.Exceptions 相关异常,提示文档无法修改。
原因:在事务外调用了 Set、Create、Delete 等修改方法。很多新手不知道 Revit 的文档保护机制,在 Transaction 之前就尝试改参数。
解决:把修改逻辑包进事务,参考上面 4.1 的代码结构。还要注意事务提交后不能再对元素做修改,如果循环里有多个阶段的修改,就不要过早调用 Commit。
5.3 get_Parameter 返回 null
现象:代码写好了 wall.get_Parameter(BuiltInParameter.WALL_ATTR_WIDTH_PARAM),但返回值是 null,或读取出来的数值永远是 0。
原因:最有可能的两种情况——把类型参数当实例参数读,或把实例参数当类型参数读。墙宽属于类型参数,必须从 WallType 上读取;注释属于实例参数,要从 Wall 实例上读取。
解决:先用 Revit Lookup 工具查看目标参数到底挂在 Element 还是 ElementType 节点下,再选择对应的 get_Parameter 入口。同时确认 BuiltInParameter 枚举名称与真实参数对应,避免用错枚举。
5.4 调试器里正常,打包 Release 后插件崩溃
现象:Debug 编译的 DLL 在 AddInManager 里运行正常,切换成 Release 并部署到其他机器后,加载时直接报错或闪退。
原因:多数情况是引用丢失或版本不匹配。Release 输出目录里的 RevitAPI.dll 被误设为“复制本地”,或者引用了比 2019.2 更高版本的 API 程序集。
解决:把 RevitAPI.dll 与 RevitAPIUI.dll 的“复制本地”设为 False,确认引用版本与目标 Revit 一致。部署时报错时,打开 Windows 事件查看器,在应用程序日志里能看到加载失败的程序集名称,按名称去修正。
5.5 事务提交失败后整个 Revit 卡住
现象:批量修改数百个元素时,中途某个元素参数写入失败,事务既没提交也没回滚,Revit 界面处于无响应状态。
原因:代码里只有 Commit,没有事务结束分支。Revit 不允许一个事务处于中间状态,没有 Commit 也没有 RollBack 的事务会让文档一直处于锁定状态。
解决:用 try/catch/finally 或 4.1 所示的结构保证事务必然结束。业务逻辑复杂时,可以把一个大事务拆成多个小事务,每处理若干元素提交一次,既能减少失败影响范围,也能避免单个事务运行时间过长被 Revit 交互中断。
6. 从 147 例走向自己的工具库:外部应用与验证方法
6.1 把单一命令升级为 Ribbon 工具集
单个外部命令适合开发和调试,真正给团队使用时,要把它做成带按钮面板的外部应用。实现 IExternalApplication 接口,在 OnStartup 里创建选项卡和面板。
[Transaction(TransactionMode.Manual)] public class App : IExternalApplication { public Result OnStartup(UIControlledApplication application) { string tabName = "我的工具"; application.CreateRibbonTab(tabName); RibbonPanel panel = application.CreateRibbonPanel(tabName, "批处理"); PushButtonData buttonData = new PushButtonData( "BatchComment", // 按钮内部标识,需唯一 "批量注释", // 按钮显示名称 typeof(App).Assembly.Location, // 程序集路径 typeof(HelloWallCmd).FullName); // 命令类全名 buttonData.ToolTip = "给选中的墙批量写入注释参数"; panel.AddItem(buttonData); return Result.Succeeded; } public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } }PushButtonData 的第三个参数如果是 typeof(App).Assembly.Location,每次编译路径变化就要重写按钮绑定;更稳妥的做法是写死 DLL 绝对路径,或使用相对部署路径。OnStartup 里创建面板前先检查同名面板是否已存在,否则多次加载会重复创建按钮。
6.2 用 .addin 文件完成部署
AddInManager 只适合开发期。给同事分发插件时,把命令或应用注册到 .addin 文件里,让 Revit 启动时自动加载。
<?xml version="1.0" encoding="utf-8"?> <AddIn Type="Command"> <Name>BatchWallTool</Name> <Assembly>BatchWallTool.dll</Assembly> <FullClassName>BatchWallTool.HelloWallCmd</FullClassName> <AddInId>8F4E1A2B-1234-4A1E-9B6C-0A1B2C3D4E5F</AddInId> <VendorId>MyCompany</VendorId> <VendorDescription>My Development Tools</VendorDescription> </AddIn>注意 .addin 文件必须放在 Revit 的 AddIns 目录下,不同版本目录不同,不要混放。AddInId 是一个固定的 GUID,一台机器上的插件要有独立 ID,重复会导致 Revit 启动时报加载冲突。部署时我习惯把 DLL 依赖一起放进统一点目录,不要让插件依赖调试输出目录。
6.3 用 Revit Lookup 验证你的参数与元素结构
写代码时最常见的问题是“我到底有没有写对参数”。我强烈建议在开发机上安装 Revit Lookup 工具。它能以树形结构展示当前选中元素的所有属性,包括类型参数、实例参数、共享参数,以及它们的存储值。
在验证插件行为时,我一般先手动选中一个元素,用 Lookup 查看它的内置参数名和参数入口;再去代码里对照 BuiltInParameter 枚举,确认读写方向正确。整个过程能排除一半以上“参数为什么读出来不对”的疑惑。Lookup 不能告诉你代码逻辑哪里错了,但它能确认数据源本身长什么样。
6.4 验证习惯与收束
开发完一个工具,我习惯做三遍验证:第一遍用 Revit 自带的族文件或最小项目跑通功能,第二遍用一个接近真实规模的模型做批量压力测试,第三遍把插件部署到另一台没有开发环境的机器上做纯净环境验证。前两遍能发现大部分逻辑与事务问题,第三遍专门用来发现问题 DLL 依赖和版本不匹配。
这 147 例官方案例真正的价值,不是让你记住每一个 API 调用,而是让你知道“这类问题官方是怎么下手的”。我的做法是把它当成案头工具书,写插件时先翻同类案例,确认官方推荐写法,再进入改写。希望这个过程能帮你少走弯路,早日从抄案例过渡到写自己的工具库。
本文还有配套的精品资源,点击获取