简介:这是面向机械设计工程师和CAD二次开发者的C#自动化标注示例,用于解决SOLIDWORKS工程图手动标注耗时、易错的问题。资源共115个文件,压缩包仅1.9MB,包含C#源码工程(sln、csproj)、工程图/零件/装配模板(drwdot、prtdot、asmdot)及运行所需的dll和界面图标png等,便于直接编译或对照学习。示例基于.NET Framework 4.8与SOLIDWORKS 2022 SP5.0,通过SDK编程调用API实现尺寸标注、公差标注和注释添加等关键操作,并附有相关配置与说明文件,能帮助读者快速搭建自己的标注自动化插件,减少重复劳动、保证标注一致性。已有583人浏览学习,适合需要提升出图效率的研发设计人员参考实践。
1. C# 给 SOLIDWORKS 工程图做标注自动化:这套示例先帮你把最麻烦的选边和插尺寸打通
现实里,工程图标注自动化最难的不是写循环,而是搞清 SOLIDWORKS 把「尺寸」「注释」「形位公差」藏在了哪些接口背后。这套基于 C# 的 SOLIDWORKS 工程图标注自动化示例,目标就是把工程图文档里重复的标注工作,比如补尺寸、加粗糙度、插形位公差,封装成能直接调用的函数。它把连接 SOLIDWORKS、打开图纸、遍历视图、选择边线、插入标注的完整链路拆成了独立步骤,非常适合懂制图规范、会一点 C#、但没系统看过 SOLIDWORKS API 的工程师。我拿到这类项目会先只盯两个点:怎么选中目标边线,怎么把尺寸插进去并且不弹出输入框。这两步通了,整个自动化就立住了一大半。
2. 先拆对象模型:哪些标注能自动化,哪些该留给模板
2.1 工程图标注背后对应的 COM 对象:DIM、GTOL、粗糙度与注释
SOLIDWORKS 的 API 把工程图里的标注都包装成 COM 对象,但每种对象的创建入口完全不同。尺寸对应IDimension,注释对应IAnnotation,形位公差对应IGtol,表面粗糙度对应ISurfaceFinish。这四类接口都挂在 Document 对象下面,可创建方式不是一个统一的InsertAnnotation就能覆盖的。
从自动化难度来看,普通的线性尺寸和直径尺寸最简单,因为它的位置和方向都能用坐标描述;注释次之,麻烦在引线锚点和文本换行;形位公差和粗糙度难度最高,因为除了放置坐标,还要设置符号类型、公差值、基准标识、复合框,参数一多就容易出边界问题。实际工程里我见过不少半成品插件,能把尺寸刷出来,但粗糙度符号位置完全不挨着边线,最后还得人工一个个拖回去。
| 标注类型 | 主要接口 | 创建难度 | 典型参数 |
|---|---|---|---|
| 线性/直径尺寸 | IDimension | 低 | 位置、方向、延伸线 |
| 注释 | IAnnotation | 中 | 文本、样式、附着点 |
| 形位公差 | IGtol | 高 | 符号、公差、基准、框体 |
| 表面粗糙度 | ISurfaceFinish | 高 | 符号类型、数值、方向 |
这套示例帮你省掉的最重要工作量,就是把这四类对象的参数整理成了 C# 的可读结构,而不是让你对着 API 文档猜数值。
2.2 示例项目的代码组织方式:拿到源码先看哪几个文件
一个正经的 C# 工程图自动化项目,不会把所有逻辑塞进一个Program.cs。拿到这套示例时,我建议先看入口和控制类,不要一头扎进底层 API 封装里。
| 文件/类 | 职责 | 阅读优先级 |
|---|---|---|
Program.cs | 程序入口,演示单张图纸标注流程 | 最高 |
SwConnector.cs | 连接 SOLIDWORKS 进程、打开文档 | 高 |
DrawingWalker.cs | 遍历图纸和视图,返回视图列表 | 高 |
AnnotationHelper.cs | 封装尺寸、注释、形位公差、粗糙度插入 | 中 |
BatchRunner.cs | 批量处理多张工程图,管理日志 | 低(后期再用) |
我的习惯是先看Program.cs里有几行核心调用,再看AnnotationHelper里有没有处理「输入尺寸值」弹窗。如果示例里没处理弹窗,你第一轮跑多半会卡在某个图纸上。
2.3 边界判断:模板能解决的不要写脚本
这套示例能自动插入标注,不代表所有标注都应该由脚本完成。标题栏、技术要求、图框这些固定内容,应该用图纸模板或者属性卡驱动,脚本去生成纯属浪费维护成本。真正值得自动化的,是那些随模型几何变化的尺寸、批量重复的孔标注、以及必须对应到具体边线的粗糙度和形位公差。
另一个边界是「关联性」。脚本插入的尺寸如果只是画在图纸上、和模型几何没有关联,那就是死尺寸,模型一改它不会跟着动,这在交付图纸时是重大隐患。所以每次插入尺寸后,要确认返回的对象确实属于视图下的模型尺寸,而不是注释型尺寸。这也是判断一个标注自动化示例能不能用的核心标准。
3. 搭建环境与连接处理:C# 拿到 SOLIDWORKS 文档对象才算开始
3.1 引用 Interop DLL:版本匹配与“嵌入互操作类型”开关
SOLIDWORKS 二次开发第一坑永远是引用。示例大概率也是从SOLIDWORKS.Interop.sldworks.dll和SOLIDWORKS.Interop.swconst.dll开始。这两个 DLL 在 SOLIDWORKS 安装目录下的api\redist文件夹里,不同主版本对应的 CLSID 基本兼容,但接口新增的方法会不同。我建议用你本机版本对应的 DLL,不要从网上随意拷一个。
在 Visual Studio 里添加引用后,必须把引用属性里的“嵌入互操作类型”改成False。如果保持默认的True,COM 接口封送时可能出现类型不匹配,典型表现是运行时能连上 SOLIDWORKS,但调用具体接口时抛InvalidCastException。平台目标也建议直接设为 x64,和你的 SOLIDWORKS 位数保持一致,避免 AnyCPU 在 64 位环境里出现奇怪的封送问题。
3.2 连接已运行的 SOLIDWORKS:先用 GetActiveObject,失败再启动新实例
工程图自动化最稳妥的连接方式是复用已经打开的 SOLIDWORKS 实例,这样能看到运行过程,也方便调试。获取当前实例需要借助 OLE 的GetActiveObject。示例里一般封装成这样的函数:
[DllImport("ole32.dll")] static extern int GetActiveObject(string progId, out object obj); private static SldWorks ConnectOrStartSw() { object oleObj = null; try { GetActiveObject("SldWorks.Application", out oleObj); } catch { oleObj = null; } SldWorks swApp = oleObj as SldWorks; if (swApp == null) { Type swType = Type.GetTypeFromProgID("SldWorks.Application"); swApp = (SldWorks)Activator.CreateInstance(swType); swApp.Visible = true; } return swApp; }这段代码的逻辑很直接:先尝试拿到已存在的 COM 对象,没有就创建一个新实例。注意Activator.CreateInstance之后把Visible设为true,否则程序可能在后台无界面启动,遇到弹窗时你根本看不到,误以为卡死。实际批处理阶段一般会反过来,把Visible设为false减少窗口资源占用。
3.3 打开工程图:OpenDoc6 与静默模式参数
拿到SldWorks对象后,下一步是打开.slddrw文件。标准做法是OpenDoc6,它的返回值是一个ModelDoc2对象,可以向下转型成DrawingDoc。这里最容易翻车的是第四个参数,也就是打开选项里的静默模式。
int error = 0; int warning = 0; ModelDoc2 model = swApp.OpenDoc6( filePath, (int)swDocumentTypes_e.swDocDRAWING, (int)swOpenDocOptions_e.swOpenDocOptions_Silent, "", ref error, ref warning); if (model == null) { Console.WriteLine($"打开失败: {filePath}, error={error}, warning={warning}"); return null; } DrawingDoc drawing = (DrawingDoc)model;参数说明:swDocDRAWING告诉 API 这是工程图类型;swOpenDocOptions_Silent表示静默打开,不弹任何对话框。如果没有加这个选项,当图纸引用了缺失的模型文件时,SOLIDWORKS 会弹出一个模态对话框,批处理脚本很可能会卡在那里等用户点确认。error和warning一定要传变量进去,打开失败后能看到具体错误码。比如error=2通常和文件格式或版本有关,error=11一般是文件被占用。
3.4 遍历图纸和视图:GetViews 返回的 object[] 要小心处理
工程图可以有多张图纸,每张图纸里又有多个视图。SOLIDWORKS API 里有个比较反直觉的设计:DrawingDoc.GetViews()返回的是object[],里面每个元素其实是View对象。直接强转容易出问题,尤其是数组里偶尔混入空引用。
private static List<View> GetAllViews(DrawingDoc drawing) { List<View> views = new List<View>(); int sheetCount = drawing.GetSheetCount(); for (int i = 0; i < sheetCount; i++) { drawing.SetCurrentSheet(i); object[] rawViews = drawing.GetViews(); if (rawViews == null) continue; foreach (object rawView in rawViews) { View view = rawView as View; if (view == null || string.IsNullOrEmpty(view.GetName2())) continue; views.Add(view); } } return views; }这里先SetCurrentSheet(i)切到第 i 张图纸,再取视图,否则你拿到的视图列表可能只是第一张图纸的。GetName2()是获取视图名称的常见方法,不同版本名称略有差异,示例里一般会用这个来判断是不是目标视图。过滤逻辑很关键:图纸里有些辅助视图、剖面视图是自动生成的,你不一定想给它们插标注。
环境搭建完成后,大部分问题集中在 COM 对象释放上。这里有个血泪经验:不要到处调用Marshal.ReleaseComObject,在循环里频繁释放同一个对象,很容易触发 Access Violation。这一点在后面的避坑章节展开。
4. 核心标注实现:尺寸、注释、粗糙度与形位公差的插入顺序
4.1 选边和选面:所有标注都依赖 SelectByID2
在工程图里插标注,SOLIDWORKS 的套路和手动操作一样:先选中目标边线或面,再执行标注命令。API 对应的是ModelDocExtension.SelectByID2。
bool SelectEdge(DrawingDoc drawing, string viewName, string edgeName) { string fullName = $"{edgeName}@{viewName}"; ModelDoc2 model = (ModelDoc2)drawing; return model.Extension.SelectByID2( fullName, "EDGE", 0, 0, 0, false, 0, null, 0); }参数拆解:第一个参数是选择对象的完整名称,格式通常是Edge<1>@视图名;第二个参数"EDGE"表示选择类型,也可以是"FACE"、"VERTEX"、"DIMENSION";后面三个坐标参数 0, 0, 0 是选择位置的近似坐标,名称足够精确时这三个值无所谓。倒数第四个参数false表示不增加新的选择条目,也就是先清空再选。返回值是bool,很多新手不看这个返回值,结果后续插入尺寸时操作的是空的选集,命令静默失败。
注意:选择时务必确认视图没有被折叠或者隐藏。如果视图不可见,SelectByID2大概率返回false。遇到这种情况,先遍历视图判断view.Visible属性。
4.2 插入线性尺寸:CreateDimension2 的坐标与方向参数
选好边线后,插入智能尺寸最常见的 API 是DrawingDoc.CreateDimension2。示例里通常封装成这样:
internal static bool InsertLinearDimension( DrawingDoc drawing, string viewName, string edgeName, double textX, double textY, double textZ) { if (!SelectEdge(drawing, viewName, edgeName)) return false; Feature dimFeature = drawing.CreateDimension2( textX, textY, textZ, 0, 0, 1, 0, 0, 0); if (dimFeature == null) { Console.WriteLine($"尺寸插入失败: {edgeName}"); return false; } IDimension dim = dimFeature.GetSpecificFeature2() as IDimension; return dim != null; }CreateDimension2的前三个参数是尺寸文字放置位置的世界坐标,单位是米,注意不是图面单位,这是最常见的位置漂移根源。中间三个参数是尺寸法向方向,0,0,1表示垂直于工程图平面,一般不变。最后三个参数是尺寸方向向量,传全零表示让 SOLIDWORKS 根据所选边线自动判断方向。
不同 SOLIDWORKS 版本对CreateDimension2的签名略有差异。如果编译不过,打开对象浏览器看一下当前 Interop 版本的定义,参数少一个或顺序变一下都很正常。核心思想不变:位置坐标用米,方向和延伸线尽量交给 API 自动判断,让 SOLIDWORKS 去套用当前图纸样式的尺寸属性。
4.3 注释、粗糙度与形位公差:以宏录制为基准参数来源
尺寸之外的三类标注,API 调用参数非常繁琐,我不建议对着文档硬啃。最快的方法是打开 SOLIDWORKS 宏录制,手动插入一个粗糙度符号或者形位公差,停止录制后看生成的 VBA 调用。这套示例的底层多半也是这么反推出来的。
注释的插入相对简单,可以用InsertAnnotation2:
IAnnotation note = model.InsertAnnotation2( "技术要求:未注圆角 R1", // 文本 0, // 样式 0, // 标志 0, // 固定角度 0.05, 0.05, 0.0); // 位置,单位米形位公差和粗糙度就不适合在这里贴完整代码,因为不同版本方法名从InsertGtol1到InsertGtol3都有。我的习惯是:用宏录制抓出本机可用的调用,再把参数改成从配置文件读取。示例项目里一般也会提供类似AnnotationHelper.InsertGtol(...)的封装,内部参数列表很长,但调用方只需要传符号、公差值、基准和坐标。
4.4 按视图批量标注:循环中提前关掉弹窗和输入框
批量给多个视图插尺寸时,最让人崩溃的不是代码逻辑,而是 SOLIDWORKS 每到插入尺寸就会弹出“修改尺寸值”对话框。手动操作时这个对话框很方便,脚本自动化时就是灾难,经常有几百张图跑着跑着停在某个尺寸输入框上等人点确认。
解决方案是在程序开始前关闭尺寸输入相关设置:
swApp.SetUserPreferenceToggle( (int)swUserPreferenceToggle_e.swInputDimValOnCreate, false);这行代码的意思是:创建尺寸时不弹出输入值对话框,直接用默认值。跑完整个流程再把它恢复成true。另一处要关的是“显示草图尺寸”之类和标注无关的弹窗,它们同样会阻断批处理。
循环遍历视图时,还要注意视图方向。工程图里剖面视图、局部放大图的方向可能和主视图不一致,如果示例的尺寸方向自动判断逻辑不够健壮,插入的尺寸线方向会歪。所以批量前一定要先打印视图名称和视图类型,人工过一遍哪些视图不该被自动标注。
5. 避坑与排查:标注自动化最容易翻车的 5 个现场
5.1 现象:标注位置随机漂移,甚至跑到图纸外面
现象:脚本跑完,尺寸文字没有靠近边线,有的跑到图框外,有的堆叠在一起。
原因:最常见的是单位不匹配。CreateDimension2的坐标单位是米,而图纸里的视觉坐标往往用毫米;另外视图在图纸上的位置有平移,脚本如果直接用了模型坐标,没有换算成图纸坐标,尺寸就会随视图比例和位置整体偏移。
解决:先统一单位。从drawing.GetCurrentSheet()拿到图纸属性,确认图纸单位是毫米,再在封装层把输入坐标除以 1000 转成米。位置换算公式用视图的Position加视图比例系数,不要自己瞎猜。跑完一张图立刻打开检查一个尺寸位置,确认后写个单元测试锁住这个换算关系,后续改代码也不会破坏。
5.2 现象:API 插入的尺寸值不对,或出现过定义标注
现象:尺寸插进去了,但值显示的是错的,比如直径标注成了半径;或者文档出现红色过定义提示。
原因:选择边线时选到了隐藏线、构造线或参考边,SOLIDWORKS 按错误的几何计算尺寸值。更常见的是原图纸上已经有相同边线的尺寸,脚本没检查直接再插一条,导致重复标注和过定义。
解决:插入前先做一次“重复检测”。遍历当前视图已有的IDimension,读它的GetReferenceEntity拿关联边线,和本次要选的边线名比对。另外在选择参数里加上false的“选择隐藏边线”选项,SOLIDWORKS 的SelectByID2有几个标志位可以控制排除隐藏边线。不要试图用删除再插入来解决,那样会破坏原图纸已经调好的制图规范。
5.3 现象:调用崩溃,报 Access Violation(C0000005)
现象:程序运行到某个标注插入时直接崩溃,事件查看器里能看到Access violation reading location 0x...,错误码通常带c0000005。
原因:绝大多数情况是 COM 对象生命周期管理过头。很多人学了一招Marshal.ReleaseComObject,就在循环里对同一个View或Dimension反复释放,结果指针变成悬挂指针,下一轮循环再去访问就崩了。另一个诱因是把“嵌入互操作类型”设为True,导致接口封送后的运行时 RCW 被提前回收。
解决:不要到处手动释放 COM 对象。把获取到的SldWorks、DrawingDoc、View交给 .NET 的垃圾回收器,或者只在合适的时机用一次Marshal.FinalReleaseComObject。设置完引用后检查 Interop DLL 的“嵌入互操作类型”确实是False。如果崩溃发生在极快速连续操作时,可以在每张图纸处理完后调用GC.Collect()和GC.WaitForPendingFinalizers(),但这只能算临时手段,根治还是控制持有的 COM 引用数量。
5.4 现象:SOLIDWORKS 崩或提示“可用窗口资源极低”
现象:批量跑到几十张图纸后,SOLIDWORKS 变慢,鼠标转圈,然后提示“可用窗口资源极低”,再继续就未响应。
原因:工程图自动化过程中,每次选择、插入标注都可能创建新的 COM 对象和 UI 窗口句柄。如果脚本没有在每张图纸处理完后释放文档,甚至没有关闭文档,撑到后面把系统的 User32 窗口资源耗尽。另一个常见原因是反复开关文档却不真正关闭,导致内存中积累大量图纸对象。
解决:每处理完一张图纸,执行CloseDoc把图纸关掉,让 SOLIDWORKS 释放图形资源。批处理时把swApp.Visible设为false,减少窗口句柄开销。如果仍然出现资源低,检查是不是有宏或插件在跑后台任务,把系统里不相关的 SOLIDWORKS 实例全关掉,只留一个进程。还有个小技巧:循环里的SelectionManager要清空选中状态,卡在选集缓存里的对象不会自动释放。
5.5 现象:批处理很快就结束,但工程图一张都没改
现象:日志显示每张图纸都“处理成功”,但打开文件一看,该插的标注一个都没有,或者只有第一张改了。
原因:最常见的是OpenDoc6的静默模式打开的是已有实例里已经打开着的同名文档,脚本拿到的ModelDoc2指向的是缓存里的旧版本文档,插入操作被 SOLIDWORKS 当成只读或者被文档锁定,API 直接返回空对象但脚本没检查继续跑。另一个原因是SelectByID2选边失败静默返回false,封装函数直接吐了个false但上层没判断。
解决:在每个关键步骤后检查返回值并写入日志。尤其是SelectByID2失败时,立刻输出当前视图名和边线名,方便定位是哪张图纸的哪个视图出了问题。打开文档前用GetOpenDoc2查一下文件是否已在内存中,是的话先CloseDoc再重开。最后,批处理里加一个“预期标注数”的校验,处理完统计实际标注数,和预期对不上就标记为失败,而不是只记一句“成功”。
6. 把示例改造成批处理脚本:日志、验证和可回滚的流程
这套示例最大的价值不是单张图纸演示,而是它能被扩展成批处理工具。我的改造思路是先做一次 dry-run,再跑真正的批量任务。
string[] files = Directory.GetFiles(inputFolder, "*.slddrw"); foreach (string file in files) { var result = ProcessDrawing(file, dryRun: true); Console.WriteLine($"{Path.GetFileName(file)} -> {result.Status}"); }dryRun模式下打开图纸、遍历视图、执行所有SelectByID2,但CreateDimension2之前只打印坐标和边线名,不真正插入。这轮跑完,我至少能确认所有视图名、边线名都是对的,避免在正式批量时造出一堆错误标注再一张张撤销。正式批量时加一个配置文件控制参数,比如尺寸样式、粗糙度数值、形位公差基准,不要把这些硬编码在 C# 里。保存策略上,永远不要直接用原文件保存。处理完后先另存到临时目录,人工抽检两张再决定要不要覆盖原图。换句话说,把“后悔药”做在流程里,而不是依赖 SOLIDWORKS 的撤销栈。
从那以后,我每次给工程图跑自动化前,都强制自己先走一遍单张图纸的 dry-run,确认方向和位置没问题再放开批量,这个习惯已经帮我挡掉了至少三波返工事故。这个示例项目最适合的用法,正是把这个验证流程和你的图纸规范融合到一起。希望帮到你。
本文还有配套的精品资源,点击获取