
做 SolidWorks 二次开发的朋友十有八九都会遇到自定义属性Custom Properties的读写需求。这个需求在 PDM 集成、BOM 导出、模型自动校验、工程图模板映射这些场景里几乎天天出现而CustomPropertyManager就是官方提供的关键 API 之一。今天要聊的GetType2方法专门用来在读取属性值的同时拿到属性的类型信息很多朋友在批量处理模型属性时遇到“日期变成数字”、“是/否被当成字符串”这类问题根源就是没有正确区分属性类型。这篇文章就把这个 API 从签名到实战、从单个文档到批量处理的完整玩法拆开讲一遍适合正在用 C# 写 SolidWorks 插件或者脚本的开发者参考。1. 先搞清楚为什么要读属性类型1.1 自定义属性就是三维模型的“户口本”SolidWorks 里的自定义属性本质上是一组挂在模型文件上的键值对存在于“文件 → 属性 → 自定义”这个对话框里。你可以把它当成模型的“户口本”记录编号、名称、材料、重量、设计者、日期、审核状态这些元数据。工程图标题栏的填写、BOM 表的自动生成、PDM 系统的字段映射底层数据源基本都来自这些属性。对开发者来说这个“户口本”是二次开发中最稳定的数据入口。模型几何形状千变万化但属性结构是可控的。很多企业会规定一套属性模板比如“图号”“名称”“材料”“重量”“版本”“审批人”然后要求所有模型都必须填齐。二次开发里最常见的需求就是批量扫描一批零件把属性读出来检查漏填项或者汇总到 Excel 里。这时候你面对的每一个字段不一定都是纯文本。1.2 类型信息在自动化流程里的角色属性值看起来都是字符串但 SolidWorks 在设计时给属性定义了类型。创建属性时界面里可以选择文本、数字、日期、是/否。这就像一个表单里既有普通输入框、下拉框、日期选择器也有复选框你不能用处理文本的方式一视同仁。如果没有类型信息你会遇到几个很实际的问题。第一数字属性取出后在 C# 里本质是double但如果你统一走ToString()再拼接或存库就会受到系统区域设置影响出现小数点变逗号这种问题。第二日期属性在底层是日期类型直接ToString()得到的是带时分秒的格式跟 Excel 里的日期格式对不上。第三是/否属性读出后的值在不同版本里可能是true/false或“是/否”/“Y/N”不做类型判断去转换就会出错。GetType2就是官方提供的“一眼看清属性到底属于哪种类型”的方法拿到类型之后你再决定怎么转换、怎么校验、怎么输出整个流程才谈得上可靠。2. 认识 CustomPropertyManager 和 GetType22.1 属性管理器怎么拿到手在 SolidWorks API 里CustomPropertyManager并不是一个你 new 出来的对象而是通过文档对象获取的。不同文档零件、装配体、工程图都有自己的属性管理器同一个文档里不同配置也有各自的属性管理器。// 连接 SolidWorks 应用 SldWorks.sldworks swApp (SldWorks.sldworks)Marshal.GetActiveObject(SldWorks.Application); // 取当前激活文档 SldWorks.ModelDoc2 swDoc (SldWorks.ModelDoc2)swApp.ActiveDoc; // 获取模型特定Model Specific属性的管理器配置名传空字符串 SldWorks.ICustomPropertyManager propMgr swDoc.Extension.CustomPropertyManager[]; if (propMgr null) { // 处理异常部分文档类型可能拿不到属性管理器 }注意这里CustomPropertyManager后面跟了一个中括号它本质上是一个带字符串索引的属性代表模型级属性。如果你传入具体的配置名称比如默认拿到的就是该配置特有的属性集合。工程图、装配体、零件的行为一致这一点极大方便了批量脚本的编写。2.2 GetType2 的签名与参数解析GetType2的官方签名如下bool GetType2(string Name, out object Value, out swCustomPropertyType_e Type);Name属性名即你要查询的那个键。Value输出参数属性当前值以object形式返回。如果属性不存在这个值可能是null。Type输出参数返回属性的类型类型是枚举swCustomPropertyType_e。返回值是bool表示这次查询是否成功。属性存在时通常返回true属性不存在或发生异常时返回false。实战中我建议把返回值当作“属性是否查得到”的第一判断条件不要只根据Value是否为null来判断因为一个合法的空字符串属性Value也可能是空。这里有必要提一个容易被忽略的细节SolidWorks 还有另一个方法GetType签名是bool GetType(string Name, out swCustomPropertyType_e Type)。它只能输出类型不能输出值。所以如果你既要值又要类型就得调用两次方法。GetType2相当于把这两个操作合并成一次 COM 调用减少跨进程消耗在高频遍历大批模型时性能差别是肉眼可见的。最简单的经验就是能在一次 API 调用里解决的事别拆成两次。2.3 swCustomPropertyType_e 到底有哪些值swCustomPropertyType_e是官方定义的自定义属性类型枚举常见取值如下枚举名称业务含义底层 C# 中的常见形态swCustomPropertyTypeUnknown未知类型无法识别或表达式未解析可能是字符串或 nullswCustomPropertyTypeString文本类型最常用的默认类型stringswCustomPropertyTypeNumber数字类型doubleswCustomPropertyTypeDate日期类型DateTimeswCustomPropertyTypeYesNo是/否类型bool不同 SolidWorks 版本对枚举值的定义可能略有扩展但绝大多数场景就是这五种。编写代码时不要硬编码数值永远用枚举名做判断否则升级版本后很容易踩坑。3. C# 实操获取属性类型的完整示例3.1 创建测试项目与引用准备在 Visual Studio 里我建议创建一个控制台应用目标框架根据你的 SolidWorks 版本选择。传统做法是.NET Framework 4.8加SolidWorks.Interop.sldworks的 COM 引用新版也可以用.NET 6/8配合 NuGet 上的互操作包但考虑到很多企业环境还是老版本我下面的示例按 .NET Framework 风格写这样兼容性最好。添加引用后在代码文件顶部引入命名空间using SolidWorks.Interop.sldworks; using System; using System.Collections.Generic; using System.Runtime.InteropServices;需要说明的是SldWorks.sldworks这个接口类型在 COM 互操作里很常用如果你用的是SldWorks.dll里的互操作类型ModelDoc2和ICustomPropertyManager都在同一个命名空间里。3.2 遍历所有属性并输出名称、值、类型一个完整的读取函数是这样写的public static void DumpAllCustomProperties(ModelDoc2 doc) { if (doc null) return; ICustomPropertyManager propMgr doc.Extension.CustomPropertyManager[]; if (propMgr null) { Console.WriteLine(获取属性管理器失败); return; } Liststring names; bool gotNames propMgr.GetCustomPropertyNames(out names); if (!gotNames || names null || names.Count 0) { Console.WriteLine(文档没有任何自定义属性); return; } foreach (string name in names) { object value; swCustomPropertyType_e propType; bool ok propMgr.GetType2(name, out value, out propType); if (ok) { Console.WriteLine($属性名: {name} | 类型: {propType} | 值: {value}); } else { Console.WriteLine($属性名: {name} | 查询失败属性可能已删除或不可访问); } } }这段逻辑看起来简单但有几个地方需要解释。第一属性名的获取必须走GetCustomPropertyNames返回的列表不要自己去猜属性名因为 SolidWorks 的属性名是区分大小写的手写名字一旦大小写不一致就会查不到。第二如果属性的值是中文或者含特殊字符控制台输出可能乱码这属于控制台编码问题不是 API 的问题。第三属性顺序不保证与界面显示一致需要排序的话自己处理。3.3 按类型做分支处理的经典写法拿到类型之后最推荐的写法就是直接switch分支把三种最容易出问题的类型单独处理switch (propType) { case swCustomPropertyType_e.swCustomPropertyTypeString: HandleString(name, value?.ToString() ?? ); break; case swCustomPropertyType_e.swCustomPropertyTypeNumber: if (value is double dValue) { // 数字格式统一用 InvariantCulture避免中文环境小数点变逗号 string numText dValue.ToString(0.####, System.Globalization.CultureInfo.InvariantCulture); HandleNumber(name, numText); } break; case swCustomPropertyType_e.swCustomPropertyTypeDate: if (value is DateTime dtValue) { string dateText dtValue.ToString(yyyy-MM-dd); HandleDate(name, dateText); } break; case swCustomPropertyType_e.swCustomPropertyTypeYesNo: // value 可能是 bool也可能是字符串兼容处理 bool boolValue false; if (value is bool b) { boolValue b; } else if (value ! null) { bool.TryParse(value.ToString(), out boolValue); } HandleYesNo(name, boolValue); break; default: // 未知类型按字符串兜底 HandleUnknown(name, value?.ToString() ?? ); break; }有朋友可能会问value是object直接ToString()不就行了吗不行这正是很多报错的来源。日期类型直接ToString()输出的是类似2024/1/5 0:00:00的格式存进数据库或 Excel 后很难看数字类型在高精度要求下直接转字符串也容易丢精度。类型分支处理的价值就在这里每种类型用最合适的方式进行格式化。3.4 链接属性和表达式怎么处理SolidWorks 里有一种特殊属性值是表达式形式比如SW-文件名称(SW-File Name)或者质量。这类属性在界面显示的是求值结果但底层存的是表达式。用GetType2获取时Value返回的通常是解析后的当前值类型多半是字符串。如果你需要拿到原始的表达式文本用Get6方法string expression; bool ok propMgr.Get6(propName, out expression, out bool evaluatedValue, out swCustomPropertyType_e type);这里expression返回未解析的表达式evaluatedValue返回解析后的当前值。在做属性模板迁移、跨文档复制属性时必须用Get6拿表达式否则你会把求值结果当成属性源文本存过去结果就变成一堆失去联动能力的死值。这个坑我见过不少人在做“批量重置属性”时踩过。4. 批量处理与工程应用4.1 遍历装配体里所有零件的通用写法单文档读取只是热身真正有生产力的是批量处理。最常见的需求是从一个装配体出发遍历其所有子零件和子装配读取每个文件的属性。这里给出一个可以落地的思路public static void TraverseComponents(AssemblyDoc swAssy, HashSetstring visitedFiles) { object[] components (object[])swAssy.GetComponents(false); if (components null) return; foreach (object compObj in components) { Component2 comp (Component2)compObj; string path comp.GetPathName(); if (string.IsNullOrEmpty(path)) continue; // 去重避免同一个件被多处引用时重复处理 if (!visitedFiles.Add(path)) continue; // 只读方式打开模型 ModelDoc2 childDoc OpenModelReadOnly(path); if (childDoc ! null) { DumpAllCustomProperties(childDoc); ReleaseComObject(childDoc); } // 如果该组件是子装配递归进去 if (comp.GetModelDoc2() is AssemblyDoc childAssy) { TraverseComponents(childAssy, visitedFiles); } } }打开模型时注意使用只读模式避免干扰用户正在编辑的文档。参考做法是用IOpenDoc2或OpenDoc6并传swOpenDocOptions_Silent和只读标志。批量遍历时还要注意内存每个打开过的文档都要释放 COM 引用否则处理几百个模型后 SolidWorks 可能明显卡顿。4.2 属性规范校验与自动补全拿到类型和值之后可以做的事情就开始变多了。比如做属性规范校验如果属性类型是数字但值解析失败说明模型里这个字段填了非法内容需要标记出来。如果属性类型是日期但日期格式不是预期格式可以自动转换为标准格式。如果是/否类型要求必须为明确的是或否不允许空值。字符串类型可以检查是否包含非法字符、是否超长。这类校验在制造业企业里非常实用。下游 ERP 系统对物料编码、名称有严格的格式约束上流模型里属性填得不规范下游就会报错或者生成脏数据。用 C# 写一个校验脚本挂在菜单上让设计人员提交前自查能省掉大量返工。4.3 导出 Excel BOM 时的类型转换在生成 BOM 场景里属性类型更是直接影响输出质量。重量、面积是数字要保留合适的小数位数日期是日期要在 Excel 里显示成日期单元格格式是/否可以映射成“是/否”而不是true/false。我的做法是把每个属性统一封装成一个自定义类public class CustomPropInfo { public string Name { get; set; } public object RawValue { get; set; } public swCustomPropertyType_e Type { get; set; } public string ToDisplayString() { switch (Type) { case swCustomPropertyType_e.swCustomPropertyTypeNumber: return Convert.ToDouble(RawValue).ToString(0.##, System.Globalization.CultureInfo.InvariantCulture); case swCustomPropertyType_e.swCustomPropertyTypeDate: return Convert.ToDateTime(RawValue).ToString(yyyy-MM-dd); case swCustomPropertyType_e.swCustomPropertyTypeYesNo: return Convert.ToBoolean(RawValue) ? 是 : 否; default: return RawValue?.ToString() ?? ; } } }这样你在后续无论是写 Excel、写文本还是推送到数据库都只调用ToDisplayString()格式统一且可维护。先读取到一个Dictionarystring, CustomPropInfo再统一处理的套路比“读一个属性立即写一个属性”的散装逻辑清晰得多。5. 踩坑实录与排查指南5.1 属性名区分大小写和空格第一个高频问题明明模型里能看到“零件号”这个属性代码里GetType2(零件号, ...)就是返回false。排查半天发现属性名实际是“零件号 ”带尾部空格或者大小写不一样。SolidWorks 的属性名是敏感的推荐的做法是永远用GetCustomPropertyNames返回的真实名称去查询不要手敲。如果你要判断某个固定属性是否存在可以先对名称列表做一次不区分大小写的匹配找到真实名称后再操作。5.2 日期和数字类型转换失败有朋友反馈value is DateTime这个判断根本不成立日期属性拿到的 value 其实是一个字符串。这种情况通常不是 API 的问题而是这个属性根本就不是真正的日期类型只是创建者用文本格式存了一个日期字符串。届时要靠类型枚举值来判断如果swCustomPropertyTypeDate都没成立那它就是字符串按字符串解析就好。反过来如果类型是日期但value解析失败多半是系统区域设置和创建时的格式不一致用DateTime.TryParse加多种格式兜底是更稳妥的做法。数字类型同理。注意有些版本的 SolidWorks 在特定区域设置下double值的ToString()会产生科学计数法输出 1E05 这种你不想看到的结果。统一用ToString(0.####, CultureInfo.InvariantCulture)可以规避。5.3 COM 对象释放与进程稳定这是二次开发老生常谈的问题但每次都要强调。SolidWorks 的 API 是 COM 接口ModelDoc2、ICustomPropertyManager、AssemblyDoc都是 COM 对象。批量脚本跑完后如果不释放可能会让 SolidWorks 进程内存暴涨。释放的标准姿势是private static void ReleaseComObject(object obj) { if (obj ! null Marshal.IsComObject(obj)) { Marshal.ReleaseComObject(obj); } }但注意Marshal.ReleaseComObject在循环里频繁调用也可能导致过度释放的问题。我的个人习惯是只对“高成本对象”做释放比如文档对象、应用对象对于属性管理器这类轻量对象不用太较真防止在复杂循环里释放过头引发莫名其妙的异常。更安全的做法是把打开文档、读取属性、关闭文档封装成一个独立方法保证每个文档在方法内使用try/finally释放。5.4 配置特定属性带来的优先级混淆一个零件如果有多个配置模型特定属性和配置特定属性是两套不同的集合。CustomPropertyManager[]拿到的只包含模型级属性而你在属性对话框的“配置特定”标签页里看到的内容不一定在这个集合里。需要读取配置特定属性时先获取配置名称列表Liststring configNames; swDoc.GetConfigurationNames(out configNames); foreach (string configName in configNames) { ICustomPropertyManager cfgMgr swDoc.Extension.CustomPropertyManager[configName]; if (cfgMgr null) continue; // 读取该配置的属性 }实际业务中“属性到底存在哪一层”经常引发文书混乱。我的建议是开发前先定好规则图号、名称等需全局一致的放模型特定属性与配置有关的重量、材料放配置特定属性。代码里要做兼容逻辑优先读配置特定属性读不到再回退到模型特定属性这样用户体验最好。5.5 GetType2 判定未知类型时的处理当属性值是表达式且表达式无法求值比如引用了不存在的自定义属性时GetType2的类型可能返回swCustomPropertyTypeUnknown。这种情况别硬编码处理建议当成字符串兜底同时把属性值原样输出方便排查。如果你在做数据导出遇到Unknown类型时要专门打日志因为它往往意味着模型里存在失联的链接表达式。6. 从属性类型到完整自动化流程的扩展思路GetType2只是属性读写工具箱里的一把扳手但当你理解了属性类型在整个数据链路中的位置后能做的事情会多很多。目前我在实际项目中用属性类型做了几件事第一是用类型信息动态生成属性录入界面字符串类型给文本框、数字类型给数值输入框、是/否类型给复选框、日期类型给日期选择器设计人员录入时不容易填错。第二是基于属性类型做迁移工具从旧模板属性映射到新模板属性期间严格按照类型匹配转换避免把日期字段映射到文本字段造成数据含义丢失。第三是把属性读取封装成 Websocket 或 HTTP 接口供上游系统按需拉取模型信息此时类型信息直接映射成 JSON Schema 的字段类型下游不用再做无意义的猜测。如果你正在规划自己的 SolidWorks 二次开发框架我的建议是把“属性读取”模块做成独立的基础服务统一走GetType2获取类型对外输出结构化对象。这样无论你后面要接 PDM、做 BOM、做图纸校验都不用再关心底层 SolidWorks API 的细节所有调用方拿到的都是“名称 值 类型”的干净数据。做到这一步你的二次开发才算真正站在了可复用的地基上而不是每个新需求都要重新从GetType2开始摸一遍。