免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

SolidWorks二次开发:C#调用GetAll3高效读取自定义属性

SolidWorks二次开发:C#调用GetAll3高效读取自定义属性 干SolidWorks二次开发的朋友十有八九迟早会撞上“自定义属性”这道坎。你建一堆零件给它们填了图号、材质、供应商、重量图纸标题栏要自动带出来ERP/物料系统要把数据收走BOM要一键导出。程序总得有个入口把这些信息“捞”出来这个入口就是 CustomPropertyManager而真正干活干得痛快的就是 GetAll3 这个老接口。今天的主题很具体用 C# 调用 CustomPropertyManager.GetAll3把零件、装配体、工程图里的自定义属性一次性拿全。我不打算把官方文档抄一遍而是按我平时做项目的思路从属性存放逻辑、接口参数拆解、能直接跑的代码到那些文档里根本不会写的坑一步步讲清楚。无论你是在做插件还是写独立的小工具这套思路基本通用。1. 为什么单独拿自定义属性这么重要1.1 自定义属性的三层存放逻辑SolidWorks 里“自定义属性”这个词其实是个集合概念实操中至少分三种来源第一种是文档级别的模型属性就是你在属性选项卡里填的那些“图号”“名称”“材料”之类对应用户界面最常说的“自定义页签”第二种是配置特定属性Configuration Specific带配置的零件会在每个配置下面有一套独立属性同一个模型切换到不同配置属性值可能完全不同第三种是跟着文件走的“摘要信息”例如作者、关键字、创建日期严格说这属于 SummaryInfo但它也能通过属性管理器部分读到。新手最容易搞混的就是第二种。同一套代码在普通零件上跑得好好的换到多配置零件或装配体上读出来的属性数量就不对或者拿到的值是空的。原因往往是你只读了一个存储位置没读另一个。GetAll3 本身不会替你决定“所有属性”到底包含哪些位置它只负责把某个 CustomPropertyManager 里的数据倒出来。想拿全得先搞清楚要从哪个管理器拿或者说要循环拿几个管理器。从 API 模型看SolidWorks 给每个文档准备了至少两个 CustomPropertyManagerICustomPropertyManager[]对应文档自定义属性页签ICustomPropertyManager[配置名]对应配置特定属性页签。如果你用doc.Extension.CustomPropertyManager[null]或者直接传空字符串拿到的一般是文档级那一套。所以“获取所有属性”最稳妥的理解是遍历文档级管理器再遍历每个配置的管理器分别调用 GetAll 系列方法最后合并。1.2 老方案逐条读取的痛点早年的二次开发资料里很多示例是这么写的先用GetCustomPropertyNames拿到全部属性名再循环调用Get6或Get3一条一条把值、解析结果、类型分别取出来。这个逻辑没错但放到真实项目里很难受。假设某个产品模型挂了四五十条自定义属性一个BOM里有两三百个零件循环次数就是上万次。每次调用都是一个进程内 COM 调用虽然比跨进程的调用快但你在 SolidWorks 主线程里频繁敲 COM 对象会造成两件事第一整体耗时明显变长用户看到的是界面卡顿第二如果每个循环里你创建了中间 COM 对象而没有及时释放插件跑个半小时SolidWorks 就慢得像挂机严重的直接报“可用的窗口资源极低”然后崩溃。这也是为什么把属性读取集中到GetAll3一次调用里更合理——它把一批属性的名字、原始值、解析后的值、解析成功标记、属性类型、依赖关系一次性给你只做一次 COM 往返然后把数据放到普通 .NET 对象里处理。剩余的时间可以花在纯托管代码上跟 COM 基本不再纠缠。后面我会给代码完整实现。2. 认识 CustomPropertyManager 与 GetAll3 接口2.1 从哪个管理器拿“所有属性”先说结论没有哪个 API 能一个方法把文档里所有页签、所有配置的属性自动归拢成一个数组。SolidWorks 的接口设计是“一个管理器管一片区域”你只能主动去访问各个管理器。所以我在工具类里通常会封装一个方法接收IModelDoc2内部统一处理这些位置文档级doc.Extension.CustomPropertyManager[string.Empty]当前配置doc.Extension.CustomPropertyManager[doc.ConfigurationManager.ActiveConfiguration.Name]所有配置遍历doc.ConfigurationManager.GetConfigurationNames()对每个配置名各取一次如果需要读焊件切割清单属性那还得换到FeatureManager那边对应的属性管理器套路一样但入口不同很多项目只需要“当前配置文档级”因为标题栏、BOM 基本都是取当前配置状态。但如果你想做一个“属性对比工具”或者要处理多配置物料编码那就必须把配置遍历做全。GetCustomPropertyManager[]可能返回 null尤其是对某些新建但没保存过的临时文档。这种情况别硬调先判断一下再用容错逻辑返回空集合。另外一个常见误区图纸.slddrw也能读属性但图纸的自定义属性跟模型的属性不在同一套管理器下你要读图纸视图里显示的零件属性通常还是去读被参照的模型文档而不是直接读图纸。2.2 GetAll3 的参数到底在说什么直接看 C# 互操作下的方法签名int GetAll3( out string[] Names, out string[] Values, out string[] ResolvedValues, out bool[] WasResolved, out int[] Types, out string[] LinkToDependencies, int NumNames );参数含义一句话能说清Names是返回的属性名数组Values是属性存储的原始字符串ResolvedValues是“真正算出来的值”WasResolved标记解析是否成功Types是属性类型枚举值LinkToDependencies表示当前属性依赖了哪些其他属性NumNames则是你要求这次调用返回几个属性。最后一个参数最容易踩坑。它的值不是随便填的必须和你传入的Names数组长度一致。通常流程是先用GetCustomPropertyNames拿到全部名称和数量然后把这个数量原样透传给GetAll3。如果数量对不上方法返回非零错误码数组里可能只有部分数据。返回值也很直白0 表示成功S_OK非 0 都算失败。很多网上贴的代码只判断“返回值小于0失败”不完全对你最好判断! 0。就算返回成功也建议检查WasResolved因为解析失败不是接口错误它只是告诉你“有一条属性引用的其它属性没找到”。2.3 类型枚举与解析状态在业务里的实际含义Types数组里的值是swCustomPropertyType_e枚举我平时的判断依据记成一张表值枚举名含义典型例子0swCustomPropertyUnknown未知类型旧版本模型、外部工具写入的特殊值1swCustomPropertyText文本图号、名称、供应商2swCustomPropertyNumber数字重量、数量、单价3swCustomPropertyYesNo是/否是否外购、是否关键件4swCustomPropertyDate日期创建日期、审核日期这个类型信息在导出到 ERP 时非常有用。比如“重量”读出来原始值是0.35如果直接当字符串存到数据库后面做数值统计会很难看。有了类型枚举你可以在 DTO 里把数字类型的字段转成double把是/否转成布尔值。ResolvedValues和WasResolved是另一组值得关注的数组。SolidWorks 自定义属性支持“表达式”比如你在属性值里填重量、SW-质量零件1.SLDPRT、$PRP:图号界面显示的是计算后的结果但接口的Values里存的还是那段表达式字符串。高版本的 SolidWorks 会在界面帮你显示解析结果但程序读到的原始值并不一定是用户最终看到的值。这时候你就要以Values为准还是以ResolvedValues为准我的经验是分场景。如果你要回写或复制属性用原始Values因为一个表达式复制到另一个文档依赖关系还在如果你要做 BOM、报价、界面展示用ResolvedValues同时检查WasResolved。如果WasResolved为 false多半是引用的属性改名了或所在文档没打开你把它填到 BOM 里就是乱码或者空值。3. 实操用 C# 完整读取所有自定义属性3.1 环境准备引用 Interop 与进程位数动手写代码前先把环境配好。SolidWorks 二次开发常见的工程配置是 Visual Studio .NET Framework 4.7.2 或 4.8引用两个 COM 库SolidWorks.Interop.sldworks和SolidWorks.Interop.swconst。有些机器上还见到SolidWorks.Interop.swpublished那是插件向导用的做独立程序不一定需要。两个库的引用方式很简单在项目里右键“添加引用”切到 COM 页签找SolidWorks.Interop.sldworks系统会自动生成互操作程序集。如果你的 SolidWorks 版本比较新引用时会看到多个版本号选择和你目标机器一致的即可。注意生成出的互操作类型默认可能带“嵌入互操作类型”选项建议把它设成 false否则部署到没有对应主互操作程序集的机器上会出问题。进程位数这一条要反复强调SolidWorks 是 64 位进程。你如果写独立 EXE编译目标平台必须是 x64不能选 AnyCPU 靠系统决定否则在绝大多数机器上Marshal.GetActiveObject(SldWorks.Application)会连不上或报类型不匹配。插件项目也同理SolidWorks 2020 之后的加载项基本都要求 64 位。我不是说 .NET Core 一定不行但做产品我不敢赌。最稳的搭配是 .NET Framework 4.8 x64 Visual Studio 2022这套组合我用了好几年没在环境上翻过车。3.2 核心代码实现一次 GetAll3 取出全部属性下面这段代码是我项目里整理过的精简版可以直接用。它先处理文档级管理器再处理当前配置的管理器最后把结果合并到一个普通 DTO 列表里。重点是 GetAll3 的调用方式以及 finally 里释放 COM 对象。using System; using System.Collections.Generic; using System.Runtime.InteropServices; using SolidWorks.Interop.sldworks; using SolidWorks.Interop.swconst; public class CustomPropertyReader { public static ListCustomPropItem GetAll(IModelDoc2 doc) { var result new ListCustomPropItem(); if (doc null || doc.Extension null) return result; IModelDocExtension ext doc.Extension; // 1. 文档自定义属性页签 CustomPropertyManager docMgr ext.CustomPropertyManager[string.Empty]; ReadManager(docMgr, Document, result); // 2. 当前配置的配置特定属性页签 IConfiguration activeCfg doc.ConfigurationManager?.ActiveConfiguration; if (activeCfg ! null) { CustomPropertyManager cfgMgr ext.CustomPropertyManager[activeCfg.Name]; ReadManager(cfgMgr, Configuration: activeCfg.Name, result); } return result; } private static void ReadManager(CustomPropertyManager mgr, string scope, ListCustomPropItem result) { if (mgr null) return; try { string[] names; int count mgr.GetCustomPropertyNames(out names, true, true, true); if (count 0 || names null || names.Length 0) return; string[] values; string[] resolved; bool[] wasResolved; int[] types; string[] links; int hr mgr.GetAll3( out names, out values, out resolved, out wasResolved, out types, out links, names.Length); if (hr ! 0) return; for (int i 0; i names.Length; i) { result.Add(new CustomPropItem { Name names[i], Value values[i], ResolvedValue resolved[i], WasResolved wasResolved[i], Type (swCustomPropertyType_e)types[i], LinkDependencies links[i], Scope scope }); } } finally { Marshal.ReleaseComObject(mgr); } } } public class CustomPropItem { public string Name { get; set; } public string Value { get; set; } public string ResolvedValue { get; set; } public bool WasResolved { get; set; } public swCustomPropertyType_e Type { get; set; } public string LinkDependencies { get; set; } public string Scope { get; set; } public override string ToString() { return ${Scope} | {Name} {ResolvedValue} ({Type}); } }这段代码有几个隐含细节值得说明。GetCustomPropertyNames的三个布尔参数从左到右分别控制是否包含用户自定义属性、是否包含配置特定属性、是否包含模型内部属性。一般情况下直接传true, true, true最省事。不同 SolidWorks 版本的互操作程序集对这个方法生成的签名可能略有差异有的是out object需要你自己转成string[]int count mgr.GetCustomPropertyNames(out object objNames, true, true, true); string[] names objNames as string[];GetAll3传进去的names是GetCustomPropertyNames返回的那个数组顺序不需要你关心你只要保证数组长度等于最终要取的属性数量。如果接口返回非 0最常见的情况就是文档里属性数量在两次调用之间发生了变化。比如你有别的插件在后台改了属性或多配置切换触发了属性重建。这种情况下重试一次通常能过。3.3 调用步骤与返回值校验上面代码是核心但你直接跑起来之前还要套一个调用入口。如果你做的是插件走SldWorks的事件回调拿到IModelDoc2后调CustomPropertyReader.GetAll(doc)就行。如果你做的是独立工具连接方式一般是这样SldWorks swApp Marshal.GetActiveObject(SldWorks.Application) as SldWorks; IModelDoc2 doc swApp.ActiveDoc as IModelDoc2; var props CustomPropertyReader.GetAll(doc); foreach (CustomPropItem item in props) { Console.WriteLine(item.ToString()); }拿到列表后建议做两层校验。第一层判断总数看是不是和 SolidWorks 界面上“自定义属性”页签里看到的行数对得上。对不上先怀疑是不是忘了读配置特定那套。第二层检查WasResolved凡是 false 的单独列出来不要混进正常数据。返回值校验的完整姿势一般是返回 0成功数据可用返回非 0重试一次如果仍失败记录错误日志并跳过该文档names.Length 0这个管理器为空直接返回不算异常另外提醒一句你在外部工具里读取属性时模型可能还在加载或重建中。刚从 SolidWorks 打开一个大装配立刻去读属性偶尔会拿到空值。我的做法是循环等doc.IsRebuilt或者干脆等几百毫秒再读。对插件来说尽量在DocumentLoadNotify2事件里拿到文档后再做延时处理别在通知回调里同步跑重逻辑会卡界面。4. 实战中的坑与排查思路4.1 COM 对象泄漏窗口资源极低 / 越用越卡二次开发写多了你就会发现很多“SolidWorks 崩溃”“SolidWorks 越用越慢”的锅最终都指向 COM 对象没释放。用 GetAll3 这类批量接口时最常见的泄漏点是doc.Extension.CustomPropertyManager[...]返回的CustomPropertyManager对象。每调一次都创建一个新的 RCWRuntime Callable Wrapper你不释放SolidWorks 端对应的 COM 引用计数就一直在涨。症状很典型插件第一次运行速度正常第二次开始变卡多跑几十次后SolidWorks 状态栏开始提示“可用的窗口资源极低”甚至直接黑屏闪退。注意这跟内存占用高不一定完全对应更像 GDI 资源和 COM 引用没被回收。对策概括成三条用完立即Marshal.ReleaseComObject长循环里避免频繁创建新的IModelDocExtension引用释放之后不要再保存引用变量否则可能抛异常。上面代码里我把ReleaseComObject放在finally里就是为了防止中途异常导致泄漏。还有个容易忽略的事GetCustomPropertyNames返回的names数组虽然是托管数组但它内部元素有些是从 COM 层拷过来的字符串一般不需要逐个释放。真正需要你管的始终是这些管理器对象本身。4.2 Access Violation C0000005 与混乱的属性名有些朋友反馈在循环里批量读取一批零件后偶尔抛AccessViolationException (c0000005)。这个异常很吓人但根因通常是调用时机问题某个文档已经被关闭或释放了但代码里还握着它的IModelDoc2引用或者你错误地ReleaseComObject了一个其实不归你释放的对象。只要你在一个嵌入式插件里把CustomPropertyManager误释放了两遍SolidWorks 进程马上就提刀。另一个相对隐蔽的坑是属性名规范。SolidWorks 属性名在界面上不强制区分大小写但底层读取是以精确字符串匹配为准的。你界面上写着“零件号”程序里读零件号没问题如果从外部 Excel 导入了一批名字有的带空格有的全角冒号匹配就会失败。用 GetAll3 拿到名字后我建议立刻建立一个字典用Trim()后的命名作为 key后续所有业务逻辑都走这个字典避免一层层字符串比较。还有字符编码问题。绝大多数场景不需要你担心SolidWorks API 返回的是 Unicode。但如果你把属性值写进 CSV 文件后中文乱码那是导出环节没带 BOM 头不是 GetAll3 的锅别在这个接口上反复折腾。4.3 多配置与装配体场景的常见误区多配置模型最容易踩的坑就是“只读当前配置”。用户可能在界面切换到“配置A”但你要导出的 BOM 要求包含“配置B”。如果你只调一次CustomPropertyManager[activeConfigName]拿到的不是你想要的。解决办法上面说了遍历GetConfigurationNames()每个配置名都建一个管理器。装配体场景还有个相反的问题——你以为装配体文档里应该有很多属性结果读取后没几条。这很正常。装配体的自定义属性通常只存放装配特有的信息比如装配图号、总重量零件的属性在各自的零件文档里。如果你要做整机 BOM正确做法是先读装配体文档属性再遍历装配体里每个零件递归读取每个零件的属性而不是指望一次 GetAll3 把子零件的属性也捞出来。递归读取时注意避免重复打开同一个零件。用一个HashSetstring记录已经读过的文件路径可以省掉大量重复的 COM 调用。4.4 常见问题速查表现象可能原因处理思路GetAll3 返回非 0names 数组与真实属性数不一致重新调 GetCustomPropertyNames传入最新长度属性明明有读取列表里没有只读了文档级没读配置特定遍历配置名逐个读管理器值是表达式不是最终结果该属性是计算属性业务展示用 ResolvedValues回写用 ValuesWasResolved 为 false引用的属性不存在或变更单独记录人工核对运行多次后 SolidWorks 变慢COM 对象泄漏检查是否有管理器未 ReleaseComObject插件在文档关闭后仍读属性持有已销毁的 IModelDoc2在事件里清理缓存减少旧引用大装配递归读属性卡顿多次重复打开文档用文件路径缓存已读结果这个表你可以直接贴到团队文档里当排查手册。我项目里遇到问题时第一件事也是对着这几个方向排除80% 能定位。5. 从“能用”到“好用”工程化封装与扩展玩法5.1 用 DTO 统一属性模型项目一旦复杂你会发现属性读取只是最上游的一小步。后面要对接标题栏、BOM、ERP甚至要导入 Unity3D 做数字孪生到处都要用到属性数据。所以别把逻辑全堆在调用 GetAll3 的方法里最好定义一套 DTO也就是我在代码里写的CustomPropItem。这个 DTO 里可以再扩展几个字段来源文档路径、来源配置、最后修改时间、该属性是否来自表达式。有了这些你导出表格、写数据库、生成 JSON 都会变简单。比如导出 JSON 给 Unity3D 用直接序列化ListCustomPropItem就行如果在代码里看到了swCustomPropertyType_Number的类型顺手把ResolvedValue转成double再塞进数值字段比前端再字符串转数值省事得多。5.2 批量处理文档与缓存策略当你需要一次处理几十上百个 SolidWorks 文件时性能要从“单个文档调用”提升到“批处理”的层次。我的建议是三个策略。第一在内存里缓存属性结构。同一个标准件装配体里出现了十几次你不需要每次重新打开它读属性按文件路径缓存一次就够了。除非你确认文件在磁盘上被修改过否则缓存一直是有效的。第二把视图刷新和重建关掉。很多属性读取本身不依赖界面刷新你可以在进入批处理前把swApp.UserControl false或暂停屏幕刷新批量结束后再恢复。注意这块要做异常保护否则程序抛错后用户界面会处于冻结状态体验很差。第三用后台任务配合进度条。毕竟遍历几十个大装配纯主线程跑会卡死界面。SolidWorks 的 COM 对象线程模型是 STA独立工具里合理的方式是把批量任务丢到后台线程但用 Dispatcher 或锁保证对 SolidWorks 对象的调用始终回到使用这个 STA 线程的上下文。这里容易绕稳妥做法是控制任务粒度比如每处理一个文件就让进度条通知一次让用户觉得程序在动其实比纯粹追求并发更实用。5.3 从属性到业务系统BOM / ERP / 数据交换场景属性拿全之后后续能做的事非常多。最常见的是往 ERP 系统导物料主数据字段就是属性名值就是 ResolvedValues第二种是生成 Excel BOM把文档级、配置级属性摊平成一列列第三种是导出成中间文件给其它软件用比如 SolidWorks 模型要导入 Unity3D 时把属性导出成 JSON 挂在 GameObject 上比在三维软件里一个个手动填省事得多。我在实际项目里还做过一个“属性同步”小工具逻辑是从 Excel 读取一列图号批量打开对应模型读完所有属性再按规则回写几个固定字段。核心读取用的就是 GetAll3。你会发现这套 API 本身只是一个“水龙头”关键是你决定把水接到哪个桶。SolidWorks 生态里也有一批辅助插件比如有些人提到的大国工匠之类的工具本质上也是把这些属性读写、映射、翻译做成可视化界面。你要是自己动手做底层 API 还是这套区别只在于你愿意投入多少精力把业务流程做进去。自己写的好处是逻辑完全可控不需要被固定功能牵制。5.4 扩展从单文档到整机数据联动如果你有一天要管理整台设备的数据而不是只读一个零件思路可以再往上抽象一层。建立“项目-装配-零件-属性”的层级模型每个节点记录它的 CustomPropertyManager 入口属性值变更时按依赖关系逐级刷新。这听着复杂但基础读取部分依然是循环调用 GetAll3。换句话说把今天这篇讲的接口吃透就等于把数据联动的地基打好了。最后分享一点经验做这种 COM 接口开发我个人的习惯是先把输入输出用日志打清楚。让 GetAll3 跑一次把 names、values、resolvedValues、wasResolved、types 全部打印出来亲眼核对一遍比看十篇文档都管用。尤其是 wasResolved很多人忽略它直到 BOM 里出现一行莫名其妙的空值才回头查。先用小模型验证再上批量最后再做界面这样排错成本最低。GetAll3 不是最炫的接口但它绝对是 SolidWorks 二次开发里最值得花时间吃透的那一批。
返回列表