十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Revit二次开发:DWGExportOptions批量导出图纸的完整实践

Revit二次开发:DWGExportOptions批量导出图纸的完整实践 简介一套基于Revit API的DWG导出功能示例工程定位为Revit二次开发入门到进阶的实用参考面向需要批量出图或定制导出选项的BIM开发者。通过C#编写外部应用演示DWGExportOptions在视图导出、图层/单位等参数上的设置方法可在Visual Studio中打开调试或参照代码改造为自己的出图插件。压缩包共81个文件大小279KB包含45个.cs源文件、resx/resources资源文件、项目解决方案sln/csproj、Revit插件注册addin文件以及依赖dll结构清晰便于直接阅读与二次开发。压缩包内包含ReadMe说明与主窗体/命令代码方便对照理解外部应用加载到Revit的流程。已有921人学习下载对有Revit二次开发基础、希望提升出图自动化水平的开发者有实际借鉴价值。1. 拿到这个工程先看什么一个被低估的 Revit 图纸导出例子用过 Revit 的都知道手动 File → Export → CAD Formats → DWG对话框里十几个选项卡图层映射、单位、线型、导出范围每次项目出图设置一遍下个版本又忘记。更麻烦的是一栋楼几十张图纸导完还要逐个检查文件名、视图范围、图层是否干净。这个名为“导出图纸.rar”的压缩包就是一个 Revit 外部应用IExternalApplication的完整示例围绕 ImportExport 解决方案展开核心代码集中在 Unit.cs、MainData.cs、MainForm.cs 和 Export Command.cs 里直接操作 DWGExportOptions 控制图纸导出的颗粒度。本文要做的是把这个工程拆开从 addin 清单到命令注册从 DWGExportOptions 每个选项的取舍到批量导出的落地最后停在两个容易被忽略的细节上合并视图的图层命名策略以及多图纸导出时的常量缓存问题。这套代码适合两类人第一类是刚接触 Revit API、想知道外部应用如何挂载到 Revit 并组织 UI 的开发者第二类是已经在用 Document.Export 但被导出设置坑过的工程师——你会在这里看到完整的参数配置路径以及 classhljs-keywordforeach 循环里千万不要做的事。2. DWGExportOptions 与外部应用Revit 二次开发的第一个分水岭很多第一次写 Revit 二次开发的人会把“外部命令”IExternalCommand和“外部应用”IExternalApplication搞混。前者的生命周期是“点击按钮 → 执行 → 退出”Revit 不保留任何状态后者的生命周期跟随 Revit 主程序你可以在 OnStartup 里注册自定义 Ribbon 面板、绑定事件甚至加载窗体。这个项目里ImportExport.sln 的启动方式走的是后者所以你在 MainForm 里看到的批量导出面板本质上是 Revit 主线程托管的一个 WinForms 窗口它通过 Application 对象拿到当前文档再交给 Export 命令去执行。2.1 addin 清单里写了什么决定了你调试时能不能打断点外部应用不像外部命令那样可以靠 Add-In Manager 临时加载它必须在 manifest 文件里声明AddIn TypeApplication。这个资源包里的 ImportExport.addin 值得逐行读?xml version1.0 encodingutf-8? RevitAddIns AddIn TypeApplication NameHows3n ImportExport/Name AssemblyImportExport.dll/Assembly FullClassNameImportExport.App/FullClassName ClientId3d8f2c14-7a21-4e9a-9f57-9b3c1d2e4f6a/ClientId VendorIdHows3n/VendorId VendorDescriptionRevit Drawing Export Tool/VendorDescription /AddIn /RevitAddIns注意这里的 FullClassName 是 ImportExport.App不是 Export Command.cs 里的类。外部应用需要实现 IExternalApplication 接口并在 OnStartup 里返回 Result.SucceededRevit 才能识别。很多新手把 addin 指向了命令类运行后按钮不出现多半是这个原因。插入表清单节点作用调试期要改什么Assembly指向编译后的 dll 绝对路径改 bin/Debug 或 bin/Release 路径FullClassName必须是实现 IExternalApplication 的类全名不要写成 IExternalCommand 的类ClientId每个应用独立 GUID用于加载识别复制粘贴会导致两个应用冲突VendorId显示在“附加模块”里的厂商名自由命名建议用开发者代号2.2 OnStartup 里创建 Ribbon 按钮MainForm 从哪被唤起的这套代码没有用 PushButton 的 Longlist 参数玩花样而是直接在 OnStartup 里创建了一个 RibbonPanel把 MainForm 的打开动作绑到按钮上。这里有个容易被忽略的点MainForm 必须在 Revit 的 UI 线程创建否则 WinForms 控件会抛出跨线程异常。public class App : IExternalApplication { public Result OnStartup(UIControlledApplication application) { RibbonPanel panel application.CreateRibbonPanel(Hows3n Tools); PushButtonData btnData new PushButtonData( ExportDrawings, 导出图纸, typeof(App).Assembly.Location, ImportExport.ExportCommand); PushButton button panel.AddItem(btnData) as PushButton; button.ToolTip 批量导出当前项目的图纸为 DWG; button.LargeImage IconResource.GetBitmap(); return Result.Succeeded; } public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } }注意按钮指向的类名是 ImportExport.ExportCommand这是 IExternalCommand 的实现它的 Execute 方法里 new 出 MainForm 并调用 ShowDialog()。这种“外部应用做壳 外部命令做执行体”的组合是 Revit 二次开发里的标准分层壳负责注册 UI命令负责拿到文档和事务权限。Execute 里的 Document 参数来自 Revit 自动注入不需要你自己找。2.3 MainData.cs 存在的意义把选择和导出解耦MainData 这个名字听起来像数据层实际功能是承载“被勾选的图纸视图列表”。MainForm 的 DataGridView 绑定的不是 Revit 的 ViewSheet 集合而是 MainData 转换后的列表。好处是 UI 层只需要处理显示字段图纸编号、名称、是否勾选导出命令只认 List 两边互不干扰。我一般会在 MainData 里加一个 IsSelected 属性和一个 ExportName 字段后者用来规避重名文件——后面第 4 章会展开讲。3. 参数矩阵与批量导出把 DWGExportOptions 的每一个旋钮都拧到位3.1 Export 方法重载与视图收集策略Revit API 里 Document.Export 有两个核心重载一个接受 ViewSet一个接受 IList 。后者在 2019 之后的版本里更推荐因为它可以直接用 FilteredElementCollector 收集图纸后一次性导出。注意这个项目的做法public bool ExportSheets(Document doc, ListElementId sheetIds, string outputDir) { if (sheetIds null || sheetIds.Count 0) return false; DWGExportOptions dwgOptions new DWGExportOptions { ExportUnits ExportUnit.Millimeters, LayerOptions DWGExportLayerOptions.ExportAll, MergeViews false, ExportTarget DWGExportTarget.View, HideReferencePlanes true, HideScopeBoxes true, ExportingAreas false, UsePublishSettings false }; using (Transaction trans new Transaction(doc, ExportDWG)) { trans.Start(); bool exportResult doc.Export(outputDir, sheetIds, dwgOptions); trans.Commit(); return exportResult; } }这段代码里有几个参数值得拆开。ExportUnits 设为 Millimeters是避免导出后在 AutoCAD 里出现 1:1 的模型单位变成英寸的经典问题。LayerOptions.ExportAll 会把所有图元按图层映射表平铺导出如果你的项目里用了发布设置Publish Settings可以让 UsePublishSettings 保持 true 来复刻“打印发布”的图层组合但当前代码主动关掉它我猜是为了绕过发布设置中某些视图不可导出导致的中断。MergeViewsfalse 的选择很多余不这个参数决定了多张图纸是合并进同一个 DWG 文件还是每张图纸单独一个文件。批量导出场景下合并视图会导致图层名带上图纸编号前缀反而增加下游处理难度。单独导出才是图纸交付的常态。3.2 导出前必须过滤掉哪些视图视图专用过滤器不是所有 ViewSheet 都能导出 DWG。不可打印的草图视图、依赖视图Dependent View、以及被设置为“不生成图纸”的明细表视图都会在 Export 时抛异常。MainData 收集图纸时要做一次类型过滤var sheetCollector new FilteredElementCollector(doc) .OfClass(typeof(ViewSheet)); ListElementId exportableSheets new ListElementId(); foreach (ViewSheet sheet in sheetCollector) { if (!sheet.IsTemplate sheet.CanBePrinted) { bool hasInvisibleDependency sheet.GetAllDependentViews() .CastView() .Any(depView !depView.CanBePrinted); if (!hasInvisibleDependency) { exportableSheets.Add(sheet.Id); } } }GetAllDependentViews 这个 API 是 2021 版本加入的如果你是 2020 及以下版本需要改用sheet.GetViewSpecificElementsForExport()来间接判断但那样拿不到依赖视图的打印权限。CanBePrinted属性虽然名义上控制打印实际上 Export 内部也用它判断视图是否具备光栅化条件。如果你的项目里某张图纸在“打印”里是灰色不可选的那么它导出的 DWG 也会失败所以这里用 CanBePrinted 作为统一入口是对的。3.3 从 Export Command.cs 里反推执行顺序Export Command.cs 是这个解决方案里真正干活的类它的大致流程是Execute 拿到当前 Document → new MainForm 并把 MainData 填充进 DataGridView → 用户勾选图纸 → 点击“导出”按钮 → 后台线程或主线程调用 ExportSheets。一个值得学习的小细节是Execute 里会先检查当前视图是否处于“编辑族”或“编辑组”模式用 doc.IsFamilyDocument 判断——如果正在族编辑器里所有外部命令都会失败要弹窗提醒。在实际项目的批量导出里我建议把导出过程放到 TaskDialog.Show 一个模态进度框里用doc.Application.Application.ProgressChanged事件刷新导出进度。Revit 的 Document.Export 本身是同步阻塞的你在按钮事件里直接调用UI 线程会卡死看起来像程序未响应。这个 Demo 里没有做异步但你在正式工程里最好给 MainForm 加一个 BackgroundWorker或者至少加一句this.Cursor Cursors.WaitCursor来提示用户。4. MainForm 与视图导出流程从点击按钮到文件落盘的距离4.1 DataGridView 绑定与文件重名策略MainForm.cs 和 MainForm.Designer.cs 分别处理逻辑和控件布局。在 Designer 里你会发现一个 DataGridView列分别是“选择”、“图纸编号”、“图纸名称”、“比例”、“导出名称”。这里最容易踩坑的是如果直接绑定 ViewSheet 对象为 DataSource那么勾选状态会绑定到不可写的属性导致刷新后勾选丢失。MainData 的存在就是从 ViewSheet 映射出一个可写 DTO核心代码如下public class DrawingData { public ElementId SheetId { get; set; } public bool IsSelected { get; set; } public string SheetNumber { get; set; } public string SheetName { get; set; } public string ExportFileName { get; set; } public DrawingData(ViewSheet sheet) { SheetId sheet.Id; IsSelected false; SheetNumber sheet.SheetNumber; SheetName sheet.Name; ExportFileName ${sheet.SheetNumber}_{sheet.Name}; } }ExportFileName 属性允许手动修改是为了处理重名图纸。项目里会有一种常见情况两个不同专业或楼层的图纸编号相同比如 A-101 在结构图和建筑图里都出现。如果直接按 SheetNumber 命名后导出的文件会覆盖先导出的。所以导出按钮的逻辑要做一次校验var duplicateNames drawingList .Where(d d.IsSelected) .GroupBy(d d.ExportFileName) .Where(g g.Count() 1) .Select(g g.Key); if (duplicateNames.Any()) { TaskDialog.Show(重名检测, 以下导出名称存在重复 string.Join(, , duplicateNames) 请修改后再导出。); return; }这里用到 LINQ 的 GroupBy 做分组检测是数据量小场景下最高效的写法。如果图纸数量超过 500 张建议改用 HashSet 在循环里做 Add 返回值判断避免两次迭代。4.2 循环导出与文件是否存在处理核心导出循环里有一个很多人会犯的错误对每一张图纸分别调用一次 doc.Export。如果图纸是 80 张等于让 Revit 在内部为每张图纸重新初始化导出环境——DWGExportOptions 的图层映射表、字体映射文件、配置文件全都要重新读取耗时呈线性放大。正确做法是把所有 ElementId 合成一个 List 一次传入。但如果必须为每张图纸设置不同的 DWGExportOptions例如不同图纸用不同单位或图层策略那么循环调用后的一个副作用就是文件覆盖第二次导出同一视图会直接覆盖同名文件不会报错。foreach (DrawingData drawing in selectedDrawings) { string filePath Path.Combine(outputDir, drawing.ExportFileName .dwg); if (File.Exists(filePath)) { File.Delete(filePath); // 显式删除确保导出的是最新内容 } ElementId id drawing.SheetId; doc.Export(outputDir, new ListElementId { id }, dwgOptions); }上面的File.Delete在多数情况下多余因为 Doc.Export 会覆盖但如果文件被 AutoCAD 打开导出会失败且 Revit 只静默返回 false不会弹窗。先检查文件是否被占用如果有则直接提示用户关闭try { using (FileStream fs new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { // 占位文件可写则继续 } } catch (IOException) { TaskDialog.Show(文件被占用, filePath 正被其他程序使用请关闭后重试。); continue; }这段代码用 FileStream 以独占方式打开文件如果失败说明被 AutoCAD 或 DWG TrueView 占用。注意 catch 里不能用return因为上下文还在批量导出循环中你要的是“跳过这张继续下一张”用一个 continue 保持流程不中断。4.3 导出后的校验DWG 文件到底是否生成完整很多二次开发导出工具没做校验步骤但这正是这个压缩包里值得深挖的部分。MainForm 的导出按钮在拿到 doc.Export 返回值后还做了一个额外动作用 FileInfo 检查产生文件的字节数和最后写入时间确保文件不为 0 字节。FileInfo fi new FileInfo(filePath); if (!fi.Exists || fi.Length 1024) { failedList.Add(drawing.SheetNumber); } else { // 用 AutoCAD 的 DWG 头校验读取前 6 个字节应包含 AC10 byte[] header new byte[6]; using (FileStream fs fi.OpenRead()) { fs.Read(header, 0, 6); } string asciiHeader Encoding.ASCII.GetString(header); if (!asciiHeader.StartsWith(AC)) { failedList.Add(drawing.SheetNumber); } }DWG 文件的前 6 个字节是版本标识如 AC1015AutoCAD 2000、AC1027AutoCAD 2013、AC1032AutoCAD 2018。Revit 导出的 DWG 根据你设置的 DWG 版本属性不同而变但“AC”前缀是固定的。这个校验方式的额外好处是如果导出出来的文件是个 0 字节的空壳或者被安全软件拦截但没有报错这里会拦下一道。5. 从 ModelText 到图框拆分的验证手段导出前做一次“试导”和“图层瘦身”我在多个项目上得出的一个保守建议是批量导出前用项目中最复杂的图纸做一次单张试导。这个“试导”不需要额外写代码直接复现 Export Command 里那个new ListElementId { id }的逻辑只导出第一张带图框的图。试导的验证目标有三个图框图层是否映射成功、字体是否替换成 SHX、导出时间是否在可接受的范围内。如果试导的 DWG 在 AutoCAD 里打开看到图框的图层是“SHEET-TITLE”而不是“0”层说明图层映射配置正确。如果某个中文字体变成了问号那问题通常出在 ExportFontMapping 表而不是代码。图层瘦身这个技巧在《DWGExportOptions 参数矩阵》章节没有展开——LayerOptions DWGExportLayerOptions.ExportAll会把所有图元按图层导出但这样得到的 DWG 会出现结构专业里几百个图层的情况下游工程师要关很久。如果你要把这套工具用于实际交付我建议这样调整把收集到的视图先做一次视图过滤只保留ViewType.ThreeDimensional和ViewType.DrawingSheet类型的视图然后在导出参数里加一行ExportAs3DView true或者ExportTarget DWGExportTarget.View并为每个视图单独设置一个配合视图Associated View。这样图层的数量会锐减因为只有可见且与视图关联的元素会被导出。在实战里一个更实用的技巧利用DWGExportOptions的SetLayers方法手动指定每个 Revit 类别映射到哪个 AutoCAD 图层方法是DWGExportOptions dwgOptions new DWGExportOptions(); dwgOptions.SetLayers(new DictionaryElementId, string { { new ElementId(BuiltInCategory.OST_Walls), A-WALL }, { new ElementId(BuiltInCategory.OST_Doors), A-DOOR }, { new ElementId(BuiltInCategory.OST_Windows), A-WINDOW } });注意这里SetLayers的键是ElementId需要按 BuiltInCategory 取得不是 Category 名称字符串。Revit 内部会把 Category 转成对应的 ElementId这个方式等效于修改“图层映射/导出设置”对话框里的“按类别替换”选项。我实测过在 2022 和 2023 两个版本里行为一致但 2020 及更早版本存在时要确认是否支持。图框拆分的场景往往容易被忽略Revit 导出的 DWG 中图框是作为一个块整体导出的如果甲方要求把图框的每条线都拆成独立图元或者要求图框在 0 层而标题栏在 G_TTL 层那么单靠 DWGExportOptions 做不到。需要在导出后用 AutoCAD 的 .NET API 或 ObjectDBX 再跑一遍图块炸开Explode脚本或者在 Revit 侧用过滤器把图框族TitleBlock的实体复制到新建的 Detail Line 再导出。这个 Demo 没有包含这个深度但值得提醒的是DWGExportOptions 里的ExportOfViews和MergeViews组合不当会直接影响图框拆分的复杂度。MergeViewsTrue 时所有视图统一进一张图纸图框块拆开的时间代价也是乘 N 的。最后一个落地的验证手段是导出后不直接在 AutoCAD 里打开而是用 Teigha 或 ODA File Converter 做一次批量版本转换把 DWG 转成 DXF再用文本检索工具搜A-WALL和A-DOOR木有出现在 LAYER 表里确认图层映射生效。这样可以在程序层面自动验证导出质量不必每个 DWG 都人工打开检查。本文还有配套的精品资源点击获取
返回列表