简介:SolidWorks二次开发模板是一套面向机械设计人员与C#开发者的SDK入门工程,帮助读者在SolidWorks环境中快速搭建COM Add-in插件、定制菜单工具栏并实现命令与事件响应。压缩包共99个文件,约10.24MB,以cs源码、dll类库、csproj与sln工程文件为主,辅以resx/resources界面资源、bmp图标、xml与txt配置说明,结构上包含SwCSharpAddin1插件项目、Windows Forms界面以及属性页处理等模块。已有730人学习下载,适合作为二次开发练手与项目脚手架。模板覆盖插件初始化、命令注册、事件处理、界面定制和SolidWorks操作封装等典型环节,读者可据此理解ISldWorks、ISwCAddin等接口的调用方式,并在此基础上扩展建模、文件操作或数据交换功能,减少从零搭建工程与调试接口的时间。
1. SolidWorks 二次开发模板不是教程,而是已经处理好 COM 常见问题的项目骨架
SolidWorks 二次开发模板不是教你建模的教程,它是一套已经把 COM 连接、文档打开关闭、自定义属性读写、工程图批量出图这几件事封装好的项目骨架。我最早接批量改图号的活,一百多张图纸手工改到凌晨,改到第十几张还把两个图号弄反了,那次之后才开始认真拆这类模板。它适合两类人:VBA 宏写到几百行开始莫名其妙崩的,以及想把宏升级成能装到别人机器上的插件的。下面按骨架里有什么、连接层怎么管、工具层怎么改、哪里最容易出问题这个顺序拆。
2. 骨架里装了什么:三条技术路线与四层目录
拿到一份二次开发模板,第一件事不是打开 Program.cs 看代码,而是先判它走的是哪条技术路线。路线选错,后面写多少业务代码都是白费:一个只做一次性批量导出的活,硬做成 Add-in 插件,光注册表就能折腾两天;反过来,一个要给全组十几个人天天点的工具,做成控制台 exe,每次都得让人先开 SolidWorks 再双击,用不了一周就没人用了。这一章先把三条路线的边界划清楚,再看模板为什么这么分层。
2.1 三条路线的取舍:宏、插件、外部进程
| 形态 | 入口 | 部署成本 | 适合的活 | 模板里的角色 |
|---|---|---|---|---|
| VBA 宏 .swp | SolidWorks 内部运行 | 拷文件即可 | 一次性脚本、录制动作改出来的小工具 | 只留参考实现,不进主工程 |
| .NET Add-in | 随 SolidWorks 启动加载 | RegAsm 注册 + 两处注册表 | 团队常用工具、需要菜单或任务面板 | 独立 Host 工程,共用业务类库 |
| 外部进程 exe | 双击或计划任务 | 拷目录 + 运行时 | 批量无人值守、和别的系统对接 | 主骨架 |
VBA 宏的优势是零部署,录一遍操作就能改,缺点也很明显:没有像样的调试器,版本管理基本靠改文件名加日期,业务一复杂就是几千行的单文件,谁接手谁骂。它适合验证「这个操作 API 到底能不能做」,不适合长期维护。
Add-in 的优势是用户无感,打开 SolidWorks 菜单就在那儿。代价是部署链最长:程序集要 RegAsm 注册 COM,注册表里要写插件描述和启动项,32 位和 64 位注册表还得分清。我见过最常见的情况是开发机上跑得好好的,换台机器菜单就是不出现,最后发现是注册到了 32 位视图里。
外部进程 exe 是我现在默认选的形态。它通过 COM 连到 SolidWorks,不依赖插件加载机制,日志、配置、异常都能自己掌控,还能挂到计划任务上半夜跑。它要额外处理两件事:SolidWorks 已经开着的时候要连上去而不是再起一个,以及批量跑完要把进程收拾干净。这两件事正好是模板连接层解决的问题。
模板的取舍是:主骨架用外部进程,业务代码全部放在一个独立的类库里,Add-in 只做一层薄壳去调同一个类库。这样同一个功能,白天能用插件点,晚上能用命令行批量跑,业务逻辑只写一份。
2.2 四层目录:按改动频率切,不按技术名词切
看一个模板好不好用,我一般先看它的目录是怎么切的。按 Models、Services、Helpers 这种技术名词切的,业务一多就会散;按改动频率切的,接手的人一眼知道哪儿能动手。这套骨架大致是这么分的:
| 目录 | 职责 | 一年改几次 |
|---|---|---|
| Connection | 连接、可见性、文档开关、COM 释放 | 一两次 |
| Utils | 单位换算、选择、属性读写、特征遍历 | 三五次 |
| Jobs | 一个类一个活:批量出图、BOM 导出、属性同步 | 几乎每周 |
| Host | 控制台入口或 Add-in 入口,只做参数解析和日志初始化 | 一两次 |
| Config | 路径、模板名、图纸格式、日志级别 | 每次上线前 |
这么切的好处很直接:改业务不动连接层,就不需要重新验证「进程关不关得掉」这类底层问题。反过来,如果连接代码和业务代码混在一个文件里,某天你只是想加一个自定义属性,却要顺带重测整条链路,这就是给自己找麻烦。
2.3 环境准备与第一次跑通
前置条件只有三条,但每条都能卡住人。第一,机器上必须装 SolidWorks,因为 .NET 工程要引用的两个 Interop 程序集就来自安装目录下的 api\redist 子目录,具体后缀各版本不一样,按你本机的实际路径来。第二,工程平台目标要设成 x64,SolidWorks 是 64 位进程,AnyCPU 在某些宿主里会以 32 位启动,连上去直接报类未注册。第三,引用的两个程序集把「嵌入互操作类型」关掉,否则编译器会生成一份内嵌的类型副本,跨程序集传 ISldWorks 的时候两边类型对不上,报的错还特别绕。
// Connection/SldWorksSession.cs using SolidWorks.Interop.sldworks; using System; using System.Runtime.InteropServices; public sealed class SldWorksSession : IDisposable { public ISldWorks App { get; private set; } private readonly bool _owned; // 实例是不是我们自己起的,决定 Dispose 时敢不敢动它 public SldWorksSession(bool startIfMissing = true, bool visible = true) { App = TryGetRunning(); _owned = App == null; if (_owned) { if (!startIfMissing) throw new InvalidOperationException("没有找到正在运行的实例,且不允许新建"); // ProgID 是唯一稳定的入口,别去拼注册表路径 var t = Type.GetTypeFromProgID("SldWorks.Application"); if (t == null) throw new InvalidOperationException("本机未注册,检查是否安装了对应版本"); App = (ISldWorks)Activator.CreateInstance(t); } App.Visible = visible; // 无人值守时设 false,批量出图能快一截 App.CommandInProgress = true; // 挡住用户中途点界面造成的重入 } private static ISldWorks TryGetRunning() { try { return (ISldWorks)Marshal.GetActiveObject("SldWorks.Application"); } catch (COMException) { return null; } // 没开着就是没开着,不当错误往上抛 } public void Dispose() { App.CommandInProgress = false; // 只关自己起的实例;用户开着的那个不许动 if (_owned) { try { App.ExitApp(); } catch { } } Com.Release(App); App = null; } }这段代码有三个参数值得说清楚。startIfMissing 控制「没有实例时要不要自己起一个」,批量任务一般设 true,而挂在别人机器上跟着界面走的小工具要设 false,避免用户以为只是打开一个工具,结果后台多出一个看不见的进程。visible 直接决定性能,隐藏窗口跑批量比可见快,但代价是出错时看不到任何提示,只能靠日志。CommandInProgress 是最容易漏的一个,它挡住用户在跑批过程中点界面,能省掉一大半「跑着跑着就不动了」的报障。
需要留意的是 Marshal.GetActiveObject 只在 .NET Framework 上现成可用,更新的运行时里这个方法被拿掉了,得自己写 P/Invoke 或者干脆改走 Add-in 形态。这是选路线时的硬约束,不是写法问题。
3. 连接层:把 ISldWorks 拿到手,再干净地还回去
业务代码写错,最多是结果不对;连接层写错,是进程泄漏、文件锁死、下一批任务直接打不开文档。这部分代码量最少,但值得反复看。我一般的判断标准是:跑完一千个文件,任务管理器里不该多出任何一个残留进程,工程目录下不该多出任何 .lck 或者临时文件。
3.1 早绑定与晚绑定:性能之外还有部署问题
早绑定就是引用 Interop 程序集,编译期就能检查方法名和参数类型,调用走的是 vtable,速度快,代码也有智能提示。代价是编译机必须能找到这两个程序集,而且换了 SolidWorks 大版本、引用路径变了,就得重新引一次。有些人图省事把程序集拷进工程目录,过半年自己都忘了这份是从哪个版本拷出来的,编译能过、运行报类型转换失败,排查起来极其难受。
晚绑定是用 dynamic 或者 Type.InvokeMember,运行时按名字找方法,不需要引用,代价是所有错误都推迟到运行时才暴露,拼错一个字母也得跑一遍才知道。它真正的价值在于「同一份程序要在多个版本上跑」,这时候用早绑定就得为每个版本编一份。
模板的做法是早绑定加一层隔离:业务层不直接接触 ISldWorks,所有调用经过一个接口包装。这样以后真要切晚绑定,只动一个文件,业务代码一行不改。
3.2 打开、关闭、退出:三种语义别混着用
先看代码,注释里写了每个地方为什么这么写。
public IModelDoc2 Open(string path, bool silent = true) { // 按文件内容判类型,别根据扩展名猜:.sldprt 也可能被存成别的 int docType = App.GetDocumentType(path); if (docType == 0) throw new IOException($"无法识别文档类型:{path}"); int errors = 0, warnings = 0; int options = silent ? (int)swOpenDocOptions_e.swOpenDocOptions_Silent : 0; var doc = App.OpenDoc6(path, docType, options, "", ref errors, ref warnings); if (doc == null) throw new IOException($"打开失败:{Path.GetFileName(path)} errors={errors} warnings={warnings}"); return doc; } public void Close(IModelDoc2 doc) { if (doc == null) return; // CloseDoc 认的是标题不是路径;同名文档并存时会关错,先取标题再关 string title = doc.GetTitle(); try { App.CloseDoc(title); } catch (COMException) { } }OpenDoc6 的两个 ref 参数是排查的唯一线索,成功的时候它们也有值,比如警告里会告诉你引用的子装配找不到。我习惯把 errors 和 warnings 连同文件名一起写进日志,出问题的时候能直接定位是哪个引用断了,而不是只知道「有一批打不开」。
swOpenDocOptions_Silent 抑制的是打开过程中的提示框,比如缺失引用、轻化、大型装配模式的选择。批量场景必须开,否则跑到某个缺零件的装配就弹个框卡在那里等人点,半夜没人点,任务就一直挂着。
CloseDoc 的参数是文档标题不是完整路径,这是个很常见的坑。SolidWorks 允许多开同名文档(来自不同目录),这时候按标题关就会关错。稳一点的做法是关之前判断 App.GetDocumentCount 或者干脆在批量任务里保证一次只开一个文档。
退出语义有三种,别混:只关文档、退出应用、只释放引用不管进程。模板的规矩是「谁开的谁负责关」,Session 里记了 _owned 标志,只关自己起的实例。这条规矩能避免一个非常烦人的投诉:用户开着 SolidWorks 做设计,你跑个工具把人家的界面关了。
3.3 COM 引用计数:为什么你的进程关不掉
这是二次开发里最经典的一类问题。把变量赋成 null 不等于释放 COM 对象,运行时可调用包装器(RCW)还替你持着一个引用,引用计数没归零,进程就一直挂着。更麻烦的是遍历的时候每个 Feature、每个 Component 都是一个新的 RCW,漏一个就漏一个。
// Utils/Com.cs public static class Com { public static void Release(params object[] objs) { foreach (var o in objs) { if (o != null && Marshal.IsComObject(o)) { // 已经释放过的再放会抛异常,这里吞掉是合理的 try { Marshal.ReleaseComObject(o); } catch (InvalidComObjectException) { } } } } // 只在进程级收尾时调:一次 GC 几十毫秒,放循环里跑几千次就是几分钟 public static void FullCleanup() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } }释放顺序要跟获取顺序反过来:先放文档里的特征、视图、注释,再放文档,最后放 App。顺序反了最轻是抛异常,重一点是某个对象还挂在已释放的父对象上,后面再访问直接崩在不确定的位置。
FullCleanup 千万别放进循环。我见过有人为了「确保不泄漏」,每处理一个文件就调一次,一千个文件跑下来多花七八分钟,还以为是 SolidWorks 本身慢。正确的做法是整个批次结束、进程退出前调一次就够。
3.4 事件订阅:回调线程和重入
事件用 C# 的 += 写法最省事,底层是连接点,编译器帮你生成了包装。但有两个约束得记住:回调在 SolidWorks 的主线程上执行,你在里面开 Task 去调 API 是无效的,会撞上 RPC_E_CALL_REJECTED;回调里也别弹模态框,一弹整个 SolidWorks 就等在那儿。
// 事件必须成对解绑,否则退出时对象被事件源持着,进程收不掉 App.ActiveModelDocChangeNotify += OnDocChanged; App.DocumentSaveNotify += OnDocSaved; private int OnDocChanged() { // 返回值必须是 0,非 0 会打断 SolidWorks 自己的流程 _log.Info($"活动文档切换:{App.ActiveDoc?.GetTitle()}"); return 0; }返回值这点很多人不知道,看到签名返回 int 就随便 return 了,结果 SolidWorks 行为变得很怪,成了难查的黑匣子。事件处理函数一律 return 0,除非你确实清楚自己在打断什么流程。
4. 工具层:单位、选择、属性、出图,四处最常改的代码
工具层是业务代码真正落地的地方。一个批量出图任务,表面上只有「打开、改属性、导出、关闭」四步,实际写起来八成的时间花在:坐标单位不对选不中实体、属性改名没覆盖、导出参数没设对只出了一张图纸。这一章把四块最常改的代码拆开,每块都给出可抄的写法和对应的参数解释。
4.1 单位:内部只认米,界面显示毫米
API 里所有长度入参出参都是米,界面上看到的是毫米,这中间的换算错了不会报错,只会「选不中东西」。这是最消耗时间的一类 bug:代码没抛异常,日志也没错误,就是 SelectByID2 一直返回 false。
public static class Units { public const double MmPerMeter = 1000.0; public static double MmToM(double mm) => mm / MmPerMeter; // 表格里的 mm 进 API 前必须换 public static double MToMm(double m) => m * MmPerMeter; // API 吐出来的坐标显示给人看时换 // 只用来打日志和做校验,不参与几何运算 public static string Describe(IModelDoc2 doc) { // 用 GetUnits 拿单位制,别去比对字符串:"MM" 在本地化版本里会变成别的 return ((swUnitSystem_e)doc.GetUnits()).ToString(); } }除了坐标,还有两处容易忘:工程图里图纸尺寸、视图位置、注释坐标都是米;装配里配合的偏移值也是米。从 Excel 读配置表的时候,在边界处统一换一次,别让毫米值往业务层深处流,流到第三层就没人记得这一列到底是毫米还是米。
顺便说一个批量场景的必设项:把「新建尺寸时弹出输入框」关掉。加尺寸、改尺寸的过程中弹一次框,无人值守就死在那儿了。这个开关是用户偏好里的 Toggle,会话初始化时设一次即可,具体枚举名按你本机版本的 Interop 来查,别硬背。
4.2 选择与遍历:SelectByID2 的几个隐藏参数
SelectByID2 的参数表看起来长,实际常用的就三个:坐标(米)、Append(是否累加选择)、Mark(标记值)。Mark 在装配配合、批量选面的时候特别有用,用不同的标记值把「要配合的面」和「参考面」分开,后面按标记筛选,比按顺序取稳得多。
它返回 false 不抛异常,所以每个调用点都要判。排查顺序建议固定成三步:先确认坐标单位,再确认目标实体没被压缩或隐藏,最后确认当前文档是活动文档(不是的话先激活)。
public static IEnumerable<Feature> WalkFeatures(IModelDoc2 doc) { var feat = doc.FirstFeature() as Feature; while (feat != null) { yield return feat; // 子特征递归,模板里限 8 层:复杂装配里无限递归会把栈打爆 var sub = feat.GetFirstSubFeature() as Feature; int depth = 0; while (sub != null && depth++ < 8) { yield return sub; sub = sub.GetNextSubFeature() as Feature; } feat = feat.GetNextFeature() as Feature; } }遍历用迭代器写起来最舒服,但有个副作用得清楚:yield 出去的对象被调用方持着,枚举过程中别去 CloseDoc 或者切活动文档,否则下一次 MoveNext 访问的就是已经失效的对象。要么先 ToList 把需要的信息摘出来,要么在枚举期间保证文档不变。
4.3 自定义属性:Add3 会静默失败,Get4 有两个返回值
属性这块的坑集中在两个方法上。Add3 如果同名属性已存在而你没传覆盖选项,它不报错也不改值,你的批量更新就这么静默地什么都没干。Get4 有两个 out 参数,一个是原始文本,一个是解析后的值,图纸里用 $PRP 链接的必须看后者。
public static void SetProp(IModelDoc2 doc, string name, string value, string config = "") { // config 传空串表示文件级属性,传配置名表示配置特定属性 var cpm = doc.Extension.CustomPropertyManager[config]; cpm.Add3(name, (int)swCustomInfoType_e.swCustomInfoText, value, (int)swCustomPropertyAddOption_e.swCustomPropertyReplaceValue); // 这个选项才覆盖 } public static string GetProp(IModelDoc2 doc, string name, string config = "") { var cpm = doc.Extension.CustomPropertyManager[config]; string raw, resolved; cpm.Get4(name, false, out raw, out resolved); // false = 不解析 $PRP 引用 return resolved; // 链接求值后的结果才等于图纸上看到的 }文件级属性和配置特定属性的区别,在出图场景里影响很大。同一个零件多个配置,图号如果放在文件级,所有配置的图纸都会印同一个图号;放配置特定才能一配置一图号。改完属性必须重建一次,图纸里的 $PRP 链接才会刷新,不然导出的 PDF 上还是旧图号,而且这个过程不报错。
4.4 工程图批量出图:图号替换 + PDF 导出
这块是整套模板里最实用的部分,也是坑最密集的部分。核心流程是:读配置表拿到「文件路径 → 图号」的对应关系,逐个打开、写属性、重建、导出、关闭。中间任何一步失败都不该中断整批,所以每个文件单独 try/catch,失败的记进待重跑清单。
public void RunBatch(IEnumerable<(string Path, string DrawingNo)> jobs, string outDir) { Directory.CreateDirectory(outDir); var failed = new List<string>(); foreach (var job in jobs) { IModelDoc2 doc = null; try { doc = _session.Open(job.Path, silent: true); // 1) 先改属性再重建,顺序反了图纸上还是旧值 Tools.SetProp(doc, "图号", job.DrawingNo); doc.ForceRebuild3(false); // 2) 导出。ExportData 传 null 表示沿用上一次的 PDF 打印设置 string target = Path.Combine(outDir, Path.GetFileNameWithoutExtension(job.Path) + ".pdf"); int errors = 0, warnings = 0; bool ok = doc.Extension.SaveAs(target, (int)swSaveAsVersion_e.swSaveAsCurrentVersion, (int)swSaveAsOptions_e.swSaveAsOptions_Silent, null, ref errors, ref warnings); if (!ok) throw new IOException($"导出失败 errors={errors} warnings={warnings}"); } catch (Exception ex) { failed.Add($"{job.Path}\t{ex.Message}"); _log.Error(job.Path, ex); } finally { _session.Close(doc); // 关文档不保存,原始文件保持干净 } } File.WriteAllLines(Path.Combine(outDir, "failed.txt"), failed); }ExportData 传 null 这一条要特别说明。它沿用上一次的 PDF 导出设置,包括图纸范围(当前图纸还是所有图纸)、线宽、是否黑白。这意味着在一台新机器上第一次跑,可能每份 PDF 只出了一张图纸,而且不报错。稳妥的做法是先手工导一次把设置定下来,或者构造导出数据对象显式指定范围。
ForceRebuild3 的参数是「是否强制重建所有配置」,批量出图一般传 false,只重建当前配置。传 true 在大装配上能把时间翻好几倍,而图纸上的引用其实只需要当前配置更新。
finally 里关文档而不是保存,是刻意的:原始文件不该被批量任务改动,属性变更只存在于内存中。如果业务确实需要把图号写回文件,另开一个任务专门做保存,别混在出图流程里,否则中途失败会留下改了一半的文件。
5. 避坑与排查:六条出问题记录
下面这些是按「现象、原因、解决」整理的,都是我在实际项目里遇上过的。新手照着手册写代码通常不会碰上全部,但只要做过两个真实项目,至少会撞上其中三条。
5.1 编译期与引用期
第一条,现象是编译报「找不到类型 ISldWorks」或者两个程序集版本冲突。原因通常是引用的 Interop 程序集来自另一台机器或者另一个大版本的安装目录,也可能是不小心把「嵌入互操作类型」打开了,编译器内嵌了一份类型副本,跨程序集传递时两边类型被认为不同,报错信息还很绕。解决方式很固定:直接引用本机安装目录下的那两个程序集,嵌入互操作类型设为 false,复制到本地设为 false,让运行时用安装目录里那一份,避免版本不匹配。
第二条,现象是在自己机器上一切正常,换台机器一运行就报「Class not registered」或者十六进制的 0x80040154。原因要么是目标机没装 SolidWorks,要么是装了但 ProgID 解析不到,还有一种是宿主进程以 32 位启动去连 64 位的组件。解决方式是在连接之前先用 Type.GetTypeFromProgID 判空,给一句人能看懂的话,另外把平台目标明确设成 x64,别留 AnyCPU 在路上埋雷。
5.2 运行期
第三条,现象是跑完一批任务,任务管理器里躺着一堆没有窗口的 SolidWorks 进程。原因几乎一定是 RCW 没放干净,尤其是遍历组件、遍历视图的时候漏了释放。解决方式是所有 COM 对象集中走一个释放工具方法,按获取的逆序释放,整个批次结束时做一次完整回收,绝不在循环里调。
第四条,现象是 SelectByID2 一直返回 false,代码不抛异常。三个原因按概率排:坐标传的是毫米而不是米;目标实体被压缩或隐藏;当前活动文档不是你以为的那个。解决方式是坐标在边界处统一换算,选中前先清空选择集并确认活动文档,判断失败时把坐标、文档标题、实体名一起打进日志,下一次就不用猜了。
第五条,现象是中文环境下按名字找特征失败,同一段代码在英文版上没问题。原因是代码里写死了「凸台-拉伸1」这种本地化名称。解决方式是改用特征类型判断,用 GetTypeName2 拿到的类型名不随语言变,配合选择类型常量做匹配,或者干脆按创建顺序和草图基准面位置来定位。
5.3 部署与权限
第六条,现象是 Add-in 注册成功,注册表也写了,SolidWorks 里就是看不到菜单。常见原因有三个:在 64 位机器上用了 Framework 目录下的 RegAsm 注册到 32 位视图;插件启动项的值被写成 0;或者用户之前在插件对话框里手动取消勾选过,覆盖写在当前用户下。解决方式是确认用的是 Framework64 那份注册工具,检查机器级和用户级两处注册表,并让用户在插件对话框里重新勾选一次。
| 症状 | 先看什么 | 再看什么 |
|---|---|---|
| 进程残留 | 循环里的 COM 对象释放 | 事件订阅是否解绑 |
| 选中失败 | 坐标单位 | 实体是否压缩、文档是否活动 |
| 属性没生效 | Add3 是否传了覆盖选项 | 是否漏了重建 |
| PDF 只有一张图 | 上次导出设置 | 是否显式传了导出数据 |
| 插件菜单不出现 | RegAsm 位数 | 两处注册表 |
还有一类不是异常的问题值得单独提:RPC_E_CALL_REJECTED,十六进制 0x80010001。它的意思是 SolidWorks 正在忙,你的调用被拒绝。批量任务里偶尔出现是正常的,正确的处理是捕获这个错误码后等两百毫秒重试,重试三次还不行再放弃。很多人没见过这个码,直接当成未知异常往上抛,结果整批中断。
6. 进阶:把模板改成自己的脚手架并跑通冒烟用例
模板到手只是起点,真正省时间的是把它改成自己的习惯。我一般做三件事:配置外置、失败可续跑、改完必跑冒烟。
配置外置最直接。路径、图纸格式、属性名映射、日志级别全部挪到一个 json 里,代码里只留默认值兜底。这样同一份程序,A 项目用一套配置,B 项目换一个文件就行,不用重新编译。
{ "session": { "visible": false, "startIfMissing": true }, "batch": { "inputCsv": "D:/work/jobs.csv", "outputDir": "D:/work/pdf", "checkpoint": "D:/work/done.txt", "rebuildBeforeExport": true }, "props": { "drawingNo": "图号", "material": "材料" } }checkpoint 那个字段是为了断点续跑。每成功一个文件就往里追加一行,下次启动先读进来做差集。批量任务跑到第七百个崩掉是常事,没有续跑机制就得从头再来一遍,几百个文件的打开重建能耗掉一整个下午。
第二件事是加冒烟用例。我习惯固定三个样本文件:一个简单零件图、一个带子装配的装配图、一个多配置零件。每次改完代码,先跑这三个,检查四件事:PDF 页数对不对、文件大小不为零、图号属性和配置表一致、任务管理器里没有残留进程。这四个检查加起来不到两分钟,能挡掉绝大多数低级错误。
// 冒烟检查的核心就一句:失败必须显式暴露,不能靠肉眼看 var result = _session.RunBatch(smokeJobs, smokeOut); if (result.Failed.Count > 0) throw new Exception("冒烟未通过:" + string.Join(";", result.Failed <p> <a href="https://download.csdn.net/download/qq_36724994/10015438" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>