
1. 为什么你会需要C#来操控图纸标注先从一个场景说起。我接过一个挺典型的活客户那边有几十张SolidWorks工程图需要把所有带有特定前缀的尺寸标注全部替换成新前缀同时把每张图纸的标注信息尺寸类型、名义值、公差、关联特征汇总成一个Excel清单。图纸数量一大手工改不仅容易漏而且时间上根本不允许。手动操作一张复杂图纸的标注检查加修改快则十分钟慢则半小时几十张图下来一整天就没了关键还容易出错。这时候就需要用程序来干了。用C#写SolidWorks标注处理程序本质上是通过SolidWorks的API接口和模型打交道。SolidWorks提供了一整套COM接口C#可以通过Interop程序集访问这些接口实现在外部程序中创建、读取、修改模型及工程图里的标注对象。和SolidWorks自带的宏VBA相比C#的优势在于可以方便地写复杂的业务逻辑比如对接数据库、做Excel导出、写正则匹配替换规则可以打包成独立工具分发部署到没有VBA开发环境的机器上也能跑而且调试体验比在宏编辑器里写VBA舒服很多。这里要先破除一个迷思很多人觉得“标注”就是图纸上那些数字和符号不值得用程序处理。但实际在制造企业里标注是设计信息的下游载体图号变更、标准件代号调整、公差体系切换都会牵动大量标注修改。标注信息的准确性和一致性直接影响下游工艺、加工和质检。所以围绕标注的批量化操作、信息提取、模板化生成其实是很有需求的一块自动化场景。这篇文章我会从环境准备、API对象模型、读取标注、批量修改标注、常见问题排查几个环节把我实际做过的C#加SolidWorks标注处理项目的思路和踩过的坑完整写出来。适合有C#基础、需要做SolidWorks二次开发的工程师或者被重复性标注工作折磨得想“偷懒”的结构设计师。2. 开发环境搭建与首次连接2.1 环境清单不止是装了SolidWorks就行要做C#二次开发本机环境有几个硬性要求装有SolidWorks最好是我这次用的2018 SP5及以上版本版本太老API对象模型差异会比较明显。Visual Studio 2019或2022社区版就行。.NET Framework 4.7.2或.NET 6/8看你的目标运行环境如果只是本机工具建议直接用.NET Framework 4.7.2省心。在项目中引用两个Interop程序集SolidWorks.Interop.sldworks.dll 和 SolidWorks.Interop.swconst.dll。这两个DLL一般位于SolidWorks安装目录下比如C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\api\redist。添加引用时直接浏览到这个路径选DLL即可。版本选择上有个实在的建议如果你们的SolidWorks是2020或2021就下载对应版本的Interop程序集。虽然高版本API基本向下兼容但低版本引用在高版本上会更稳妥反过来就偶尔会出现类型转换问题。SolidWorks安装目录里自带的DLL就是和本机版本最匹配的。2.2 用Interop还是用COM动态调用实际开发中有两种路线强类型Interop引用具体版本的Interop DLL代码里可以直接使用SldWorks、ModelDoc2等类型编译期就有智能提示代码可读性好。这是我推荐的方式。动态绑定通过ProgID创建对象不引用Interop DLL运行时再解析。这种方式部署时不用管版本但写代码时没有智能提示报错也晚非常适合“跑通验证”的场景不适合正式项目。我这里全程用Interop强类型方式。Interop还有一个好处Visual Studio对这类COM程序集支持得比较好会生成对应的互操作程序集遇到方法签名不明确时可以直接右键“转到定义”查看真实的参数约束。2.3 连接SolidWorks的代码骨架using SolidWorks.Interop.sldworks; using System; using System.Runtime.InteropServices; public class SwConnection : IDisposable { private SldWorks _swApp; public bool Connect(string progId SldWorks.Application) { // 先尝试获取已运行的实例 try { _swApp Marshal.GetActiveObject(progId) as SldWorks; } catch { // 没有运行则创建新实例 Type swType Type.GetTypeFromProgID(progId); _swApp Activator.CreateInstance(swType) as SldWorks; } if (_swApp null) return false; // 设为可见 _swApp.Visible true; return true; } public void Dispose() { if (_swApp ! null) { Marshal.FinalReleaseComObject(_swApp); _swApp null; } GC.Collect(); GC.WaitForPendingFinalizers(); } }这段代码有一个细节要提醒Marshal.GetActiveObject如果当前没启动SolidWorks会抛异常所以一定要用try-catch包住。Activator.CreateInstance创建实例时SolidWorks会起一个完整进程打开速度取决于机器性能建议开发时先把SolidWorks开着再跑程序。连接后别忘了主动释放COM对象。COM引用计数问题在C#里非常隐蔽你以为对象可以被GC回收了其实SolidWorks进程还占着大量内存。不释放干净的典型表现是程序跑几轮后SolidWorks越来越卡最后蹦出内存不足的报错。3. 标注对象模型搞懂层级才好下手3.1 从零件到工程图的完整链路SolidWorks中标注主要集中在工程图环境但模型的尺寸草图尺寸、特征尺寸也可以被程序访问。对象层级大致是SldWorks.Application → ModelDoc2 → Sheet → View → Dimension / Annotation这里有两个容易混淆的概念要理清Dimension尺寸标注比如直径标注、线性标注、角度标注在API里对应Dimension对象。Annotation注释对象包括文字注释Note、表面粗糙度符号、形位公差符号等。注释是一个更大的概念尺寸标注也属于注释的范畴。API里它们的关联关系是Dimension对象继承自IAnnotation也就是说一个尺寸本身也是一种注释。所以遍历时通过Annotation可以拿到所有标注类对象再判断其类型可以区分是尺寸、注释还是其他符号。先把这个关系弄清楚再写遍历代码否则容易陷入“为什么我拿到了关联尺寸却拿不到文字”的困惑。3.2 尺寸标注的命名规则SolidWorks里的每个尺寸都有一个唯一名称最常见的格式是D1草图1 D1凸台-拉伸1 D1工程图视图1前缀D就是尺寸的标识后面的符号后跟所属特征或视图名称。这个命名规则对程序特别重要因为很多API方法比如Parameter、SelectByID2都要靠名称字符串去定位对象。实际项目里我踩过一个坑尺寸名称中会有变体名称的情况。SolidWorks允许同一个特征里存在同名的草图这时尺寸名称会展示为类似D1草图1特征1的格式。遍历时如果直接用名字匹配容易漏掉这类“重名”尺寸。所以建议优先采用遍历集合的方式而不是用名称字符串去精确匹配。3.3 三种常用的获取标注方式方式一按名称获取尺寸ModelDoc2 swDoc _swApp.ActiveDoc; Dimension swDim swDoc.Parameter(D1草图1) as Dimension;方式二通过选择管理器获取选中对象ModelDoc2 swDoc _swApp.ActiveDoc; bool sel swDoc.Extension.SelectByID2( D1工程图视图1, DIMENSION, 0, 0, 0, false, 0, null, 0); if (sel) { SelectionMgr selMgr swDoc.SelectionManager; Dimension swDim selMgr.GetSelectedObject6(1) as Dimension; }方式三遍历视图内所有注解ModelDoc2 swDoc _swApp.ActiveDoc; View swView swDoc.GetFirstView(); while (swView ! null) { Annotation ann swView.GetFirstAnnotation(); swView swView.GetNextView(); }这三种方式各有适用场景按名称最直接但依赖命名唯一性按选择管理适合交互式工具遍历集合适合批量处理。下面实战部分主要用遍历方式最稳。4. 实战一遍历工程图并导出标注到Excel4.1 需求定义与方案设计这类需求很常见设计部门每季度要出全量图纸标注台账供工艺和采购核对。手工做法是打开一张图逐个查看标注复制信息到Excel百张图纸工作量巨大。我的实现思路是打开SolidWorks工程图不显示界面后台运行。遍历所有图纸Sheet。遍历每张图纸的所有视图View。遍历视图内的所有注解Annotation。按类型筛选出尺寸标注读取名称、值、公差、显示的文字等信息。写入Excel文件我用开源的NPOI避免装Office。4.2 核心代码遍历与读取public ListDimInfo CollectDimensions(IModelDoc2 doc) { var result new ListDimInfo(); int sheetCount doc.SheetCount; for (int i 1; i sheetCount; i) { Sheet sheet doc.GetSheet(i); doc.ActivateSheet(sheet.GetName()); View curView sheet.GetFirstView(); while (curView ! null) { Annotation ann curView.GetFirstAnnotation(); while (ann ! null) { int annType ann.GetType2(); if (annType (int)swAnnotationType_e.swAnnotationDimension) { Dimension dim ann as Dimension; if (dim ! null) { DimInfo info new DimInfo { SheetName sheet.GetName(), ViewName curView.GetName(), DimName dim.GetName(), DimText dim.GetText() }; result.Add(info); } } ann curView.GetNextAnnotation(ann); } curView curView.GetNextView(); } } return result; }这里有一个关键细节GetNextAnnotation的参数是当前Annotation对象。刚写这个遍历时我犯了个错误——只调GetFirstAnnotation忘记传参调用GetNextAnnotation(ann)结果程序死循环或者只能拿到第一个标注。视图遍历也一样GetNextView()必须有当前视图作为上下文。SolidWorks API这些遍历方法基本都是“先把当前对象传进去返回下一个”的模式和.NET集合的迭代器风格完全不同写的时候很容易转不过弯来。4.3 属性读取不只是文字尺寸标注值得读取的属性远不止显示文字。我在项目里主要读这几项属性API方法说明名称dim.GetName()如 D1草图1显示文字dim.GetText()实际显示的字符串含前缀后缀名义值dim.GetValue()尺寸数值double公差上下偏差dim.GetToleranceValues()返回上下偏差数组显示精度dim.GetDimensionText()相关小数位数信息关联特征dim.GetFeature()返回关联的特征对象有个坑要提醒GetText()返回的是“最终显示文本”SolidWorks在显示时会根据尺寸值和当前单位设置把数值格式化。如果你要拿原始表达式比如形如“D1草图1 0.5”的驱动表达式需要调用更底层的GetDimensionText()之类的方法。我实际遇到过一个场景程序要把所有标注的“显示文本”和“驱动表达式”分别导出结果发现GetText()返回“15”而驱动表达式其实是“D1草图3 0.2”查API文档查了一会儿才定位到正确方法。5. 实战二批量修改标注与自动添加注释5.1 批量修改标注前缀的完整案例我在开头提到的那个任务——把一批工程图中所有直径标注的前缀从旧代号改成新代号。实现逻辑其实不复杂public int ReplaceDimPrefix(IModelDoc2 doc, string oldPrefix, string newPrefix) { int count 0; foreach (var dim in GetDimensions(doc)) { string text dim.GetText(); if (text.Contains(oldPrefix)) { string newText text.Replace(oldPrefix, newPrefix); bool ok dim.SetText(newText); if (ok) count; } } return count; }SetText方法返回布尔值。这里要特别注意不是每次SetText都会成功。如果传入的字符串不符合当前系统的标注格式比如在直径标注里传了个没有前缀的数字SolidWorks可能会拒绝修改。我实测遇到的情况是SetText返回false但没有任何异常抛出来。所以一定要检查返回值否则你以为改成功了实际图纸纹丝不动。尺寸文字修改还有一个陷阱当你调用SetText后尺寸关联的3D模型可能也会跟着变因为尺寸文字本身可能承载了驱动语义尤其是引用方程式或全局变量的尺寸。如果要“只改显示、不动几何”最安全的方式是操作Annotation的显示属性而非直接改尺寸文字。这需要根据业务需求仔细判断。5.2 自动创建标注注释的实际操作除了改尺寸有时候还需要在图纸上自动添加文字注释比如在图纸角落写入“设计者XXX 审核YYY 日期ZZZ”。通过API添加注释的方式public bool AddNoteToSheet(ModelDoc2 doc, string sheetName, string noteText, double x, double y) { bool ok doc.ActivateSheet(sheetName); if (!ok) return false; // 在指定坐标插入文字注释 bool created doc.InsertNote(noteText, x, y); if (!created) return false; // 获取最后一个注释对象刚创建的 Note swNote doc.GetLastFeatureManagerData() as Note; return swNote ! null; }注意坐标系统SolidWorks的插入坐标默认是图纸坐标系单位是米没错API内部一律用SI单位哪怕你界面设置是毫米。我第一版程序在毫米单位下输入了10, 10结果注释跑到了图纸外找半天没找着。换算很简单需要处理的数值直接除以1000即可。关于定位注释更稳的做法是给注释添加“定位点”和“引线”而不是用固定坐标。因为工程图设计变量多固定坐标在不同比例的视图里显示位置不理想。SolidWorks的Note.AddLeader方法可以给注释添加引线到某个几何实体上这样注释跟着特征走位置更灵活。不过引线相关API在文档里描述不够详细需要用试验法摸索。我的建议是先用录制宏的方式记录一次手动添加注释的操作然后打开VBA编辑窗口查看生成的宏代码这是了解API调用最好的学习资料。5.3 标注样式的统一性维护另外一个实用场景是统一标注样式。团队协作中经常出现各人的标注字体、箭头样式、文字高度不一致。通过C#可以批量对齐这些属性标注文字高度dim.GetTextHeight()通过dim.SetTextHeight()调整单位是米。箭头类型遍历尺寸通过dim.ArrowHeadStyle相关属性调整。公差显示方式dim.GetDimTolerance/SetDimTolerance相关方法可以统一改为“双边公差”或“对称公差”。这些操作对API枚举值比如箭头样式用起来有点繁琐因为文档不直观。我实际做法是先用宏录制一遍找到枚举值的真实数值再在C#里用对应枚举硬编码或定义常量。比如箭头样式在swArrowheadStyle_e里取值有实心、空心、建筑制图等好几种录一遍宏就知道当前命令默认用的是哪个数值。6. 常见问题与排查技巧实录6.1 COM对象泄漏SolidWorks越来越卡这个问题我说过几乎是所有COM二次开发都绕不过去的坎。SolidWorks的API对象大多派生自COMC#里的GC不会自动释放COM引用必须手动调用Marshal.FinalReleaseComObject。实际经验是try { // 使用对象 } finally { Marshal.FinalReleaseComObject(dim); Marshal.FinalReleaseComObject(view); Marshal.FinalReleaseComObject(doc); }这个做法在遍历嵌套对象时要注意顺序先释放子对象再释放父对象。而且API文档明确提示不要对同一个COM指针调用FinalReleaseComObject两次否则会抛异常。还有一种更隐蔽的泄漏object类型的中间变量。很多API方法返回的是object你把它赋给具体类型后原来的object被丢弃了但COM引用计数还挂在那边。稳妥做法是用try-finally包住或者启动一个定时GC兜底。最实在的技巧是批量处理大量图纸时每处理完一张图纸就调用一次GC.Collect()配合GC.WaitForPendingFinalizers()虽然粗暴但有效。6.2 后台运行与界面刷新的取舍处理过程要不要显示SolidWorks界面长期是个双难选择显示界面速度慢但你能实时看到程序在干什么调试方便。后台运行_swApp.Visible false速度快出问题时难以定位。我的建议是开发阶段用Visible true真实批量跑时用false。另外隐藏界面时SolidWorks可能不刷新视图导致你读取的标注状态是“逻辑上的最新值”而不是“界面显示值”。补救办法是在关键操作后调用doc.ForceRebuild3(true)之类的重绘方式强制刷新。实际上在批处理流程中我遇到过一个罕见情况隐藏界面时GetCurrentSheet()返回的不是当前激活图纸因为界面没有实时同步激活状态。这个坑的解法是不要用GetCurrentSheet老老实实用GetSheet(i)按索引遍历。6.3 版本兼容性不同SolidWorks版本的API差异C#加SolidWorks二次开发的另一个常见烦恼是版本差异。SolidWorks的API在2016、2018、2020等大版本切换时部分方法的签名和枚举定义会变化。最典型的是swAnnotationType_e枚举的值在不同版本间有微小偏移导致在新版本上筛选类型时意外漏掉某些对象。应对办法有两条锁版本——只在目标版本的SolidWorks上运行反正是内部工具。类型泛化——不依赖具体枚举判断而是通过“对象是否能转换为Dimension”来筛选。后者的写法就算枚举变了程序也能正常工作if (ann is Dimension dim) { // 这里直接处理 Dimension }这个写法明显更稳健。6.4 程序偶发崩溃与异常处理批量运行时最怕的是跑到第37张图纸时程序崩溃前面36张白跑了。我的处理方案是把异常捕获放在单张图纸粒度for (int i 1; i doc.SheetCount; i) { try { ProcessSheet(doc, i); } catch (Exception ex) { Log($Sheet {i} 处理失败: {ex.Message}); continue; // 跳过继续 } }另外别忘了给AppDomain.CurrentDomain.UnhandledException挂一个全局处理器把栈信息写进日志。COM异常在CLR里往往被包装成COMException你需要读取ErrorCode和Source字段才能看到有效信息。我排查过一个问题表面报错在SetText实际根源是前面某次SelectByID2传入的参数类型不对对象根本没被选中后续操作全部建立在无效对象上。这就是“错误出现在下游根源在上游”的典型COM二次开发案例。6.5 调试技巧用好SolidWorks的宏录制器最后一个技巧是二次开发新人最容易忽略的先录宏再翻译成C#。在SolidWorks里打开“宏录制”工具→宏→录制/暂停手动操作一遍你要自动化的动作比如改一个标注文字、添加一个注释、设置一个公差录制出来的VBA代码就是API调用的标准答案。这个思路的价值在于录制宏学API的效率比翻文档高太多很多对象属性和方法在官方帮助文档里写得很晦涩但录制出来的VBA代码直白易懂。我基本已经把“录宏→转C#”当作SolidWorks二次开发的第一方法论。7. 我的一些实战心得做C#加SolidWorks标注自动化这个方向前后跑了不少项目最深的体会有几点。第一理解业务比理解API更重要。标注的自动化处理表面上是操作对象实际是要搞清楚图纸上的标注承担了什么信息角色——是工艺标识、尺寸公差还是表注文本。不同的信息角色修改的策略完全不一样。程序把“该改的漏了不该改的改了”比手工改错还要棘手。第二SolidWorks二次开发本质上是一种“讲故事”式的编程。你要先连接、再有文档、再选视图、再挑标注每一层的对象获取都依赖上一层的正确状态。任何一步出错后面全是空的。所以我的程序里每一步都做了严格的对象非空检查宁可多写几十行判断也不愿在运行时才看到“对象未引用”的报错。第三批量化标注处理特别适合做成“工具链”而不是单一脚本。从图纸批量打开、标注提取、Excel导出、错误标记、修改回写这个链路里的每一环都可以独立复用到其他任务上。我后来把核心的DimensionReader和NoteWriter类抽象出来新项目直接复用省下大量重复编码的时间。如果你也想做这类开发建议从最小的场景入手——先写一个能读取当前工程图里所有标注并输出Name和Text的小工具跑通了再逐步加功能。把宏录制、Interop引用、COM释放这几个基础问题先弄清楚后面自然就顺了。关于标注的自动化处理还有个延伸方向值得提一下与PLM/ERP系统对接。通过C#读取SolidWorks标注信息后直接写入制造执行系统的数据库实现设计到生产的“图纸标注数据化”。这个方向在很多企业里还处于空白但需求非常明确做出来价值不小。最后分享一个小技巧批量处理前先对单张图纸做一次完整走查确认你的程序在“标准图纸”上跑出来的结果和你手工核查的结果完全一致再上量。我在早期项目里就是跳过这个环节直接跑50张图纸后来发现遍历视图时漏了“图纸格式”里的注释导致一批标注台账少了几十行数据返工成本比预想高得多。磨刀不误砍柴工先验证再铺开这个习惯比任何API技巧都更值得养成。