ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SolidWorks二次开发:用GetType2读取自定义属性类型与解析状态,避免BOM导出脏数据

SolidWorks二次开发:用GetType2读取自定义属性类型与解析状态,避免BOM导出脏数据 刚开始做SolidWorks属性批量处理的时候我拿到手的第一件事就是闷头调GetValue2把属性值抽出来一股脑丢进Excel。结果很快被打脸导出表里出现了一堆“D1方程式_1”“质量零件1”这种字符串用户看着直接懵。后来我才意识到属性这个东西不能只读值——类型和解析状态同样重要。这也是我为什么专门去啃CustomPropertyManager.GetType2这个方法的原因。这篇东西就是把我在项目里用GetType2读取属性类型的完整思路、代码和踩坑记录整理出来给同样在做SolidWorks二次开发的兄弟们一个可以直接参考的写法。1. 为什么我最终选择了GetType2一次属性导出引发的思考1.1 只读值不读类型Excel导出全乱套先说一个我实际经历的场景。当时我在做一个物料清单导出插件目标是从装配体里把每个零部件的自定义属性抓出来生成物料清单。属性里有这么几样材料、重量、是否需要热处理、表面处理。刚开始我的方案特别朴素——拿到ICustomPropertyManager之后直接GetValue2把值取出来全部当string写入Excel。结果导出来的文件问题一大堆重量这一列虽然数据是“12.5”这种格式但Excel单元格是文本格式没法直接参与求和、排序、汇总用户点开数字列还想筛选“大于10的零件”压根筛不了。是否需要热处理用户在SolidWorks里用的类型是布尔值我导出来却是“True”“False”字符串跟公司ERP系统的导入模板对不上。最坑的是密度这种属性一旦用户在属性里填的是质量零件1 / 体积零件1这种方程式我用GetValue2拿到的就是这条原始表达式而不是解析出的数字。用户拿到Excel看到一串“xxx”直接说这个工具是坏的。这让我意识到一件事做属性处理类型信息是刚需不是锦上添花。GetValue2这类方法只负责给值但值背后的语义是数字、布尔、日期还是带量纲的长度/质量全靠GetType2来告诉你。1.2 GetType2相对老API解决了什么其实在GetType2之前SolidWorks API里就有GetType这个方法但它有个很明显的局限它只告诉你这个属性的类型不给你值。尤其是当属性被方程式、全局变量或者配置特定条件驱动时GetType根本无法让你判断“当前拿到的值到底能不能直接用”。GetType2则是一趟把信息拿全了它的输出参数里同时包含解析后的值、未解析的原始表达式、是否解析成功。相当于把“值是什么”和“值是否可信”一起交给你。这在做BOM导出、属性校验、ERP/PLM对接这类场景里能省掉很多脏活累活。我自己用过一段时间对比下来GetType2和GetValue2的配合是比较舒服的组合方法返回类型输出参数适用场景GetValuestring只有值老代码不推荐新项目用GetValue2string值、解析后值、未解析值只需要值不需要类型GetType类型只有类型旧方案信息量不足GetType2int(状态码)类型、解析值、未解析值、解析状态需要同时判断类型和值状态的场景所以我后来的项目里只要是做属性读取默认就走GetType2值也用它的ResolvedValue不再单独调GetValue2避免同一个属性被打开两次、万一中间状态变了还不好排查。2. 先拿到CustomPropertyManager模型、配置、图纸三种入口的差异2.1 模型级属性与配置特定属性的差异CustomPropertyManager的获取方式并不复杂就一行ICustomPropertyManager mgr doc.Extension.CustomPropertyManager[configName];关键就在这个configName参数上。很多人第一次写代码会下意识传一个空字符串进去拿到一个管理器就开始操作这其实是把“模型级属性”和“配置特定属性”搞混了。SolidWorks的自定义属性分两层模型级属性对应属性对话框里的“摘要信息”页签传入获取的是文档本身的自定义属性。这个属性所有配置都能看到相当于是“全局属性”。配置特定属性对应“配置特定”页签传入具体的配置名称例如默认或零件1获取的是该配置独有的属性。同一份文档不同配置可以有不同的属性值这在做系列化零件时非常常见。我举个实际例子一个标准件有了三个配置分别对应三种不同长度每个配置里都设置了长度属性值不一样。这时候如果你只拿CustomPropertyManager[]去读长度得到的不是任何一个配置的值而是模型级的那一份定义只有当你要读CustomPropertyManager[配置A]的时候才能拿到配置A对应的长度值。所以代码的第一步通常是先判断当前激活配置再获取对应的管理器IModelDoc2 doc (IModelDoc2)swApp.ActiveDoc; if (doc null) return; string activeConfigName doc.GetActiveConfiguration().Name; ICustomPropertyManager docMgr doc.Extension.CustomPropertyManager[]; ICustomPropertyManager configMgr doc.Extension.CustomPropertyManager[activeConfigName];提示doc.Extension.CustomPropertyManager[configName]在configName不存在时实测会返回null所以调用前一定要判空不然下一句就是莫名的空引用异常。获取到管理器之后再配合GetNames()拿到属性名数组就可以进入下一步读取了。2.2 图纸和装配体的读取注意事项零件的读取方式讲到这儿装配体其实也差不多——装配体本身也有模型级和配置特定的属性获取管理器的套路完全一样只是要注意装配体里往往涉及多个子零件你要读的是“装配体文档自己”的属性还是“装配体里某个零件的属性”。如果是后者得先通过doc.GetFeatureByPosition2或遍历IGetComponents拿到对应组件再获取组件的IModelDoc2最后走同样的Extension.CustomPropertyManager逻辑。工程图DrawingDoc这里要单独提醒一下图纸文件虽然也有CustomPropertyManager但图纸格式Sheet Format里的标题栏属性很多是依靠链接驱动的例如$PRPSHEET:材料这种写法。你通过CustomPropertyManager[]去增删改不一定能联动影响标题栏单元格。我做工程图出图工具时踩过一次这个坑后来对工程图的属性体系单独做了处理。如果你现在的需求只针对零件和装配体的属性读取直接把注意力放在模型级和配置级管理器上就够了。3. GetType2方法解剖返回值、五个参数和Resolved/Unresolved值的意义3.1 方法签名与参数说明GetType2的签名在C#里长这样int GetType2( string Name, out swCustomPropertyTypeOptions_e Type, out string ResolvedValue, out string UnresolvedValue, out bool WasResolved );逐个说下我实际使用时的理解Name属性名称就是这个属性在SolidWorks里显示的名字。Type属性类型返回的是swCustomPropertyTypeOptions_e枚举后面专门讲。ResolvedValue解析后的值。如果属性值本身只是一个普通字符串这个值就是原始文本如果属性值是由方程式或全局变量驱动的这里就是最终计算出来的值。UnresolvedValue未解析的原始值。属性里存的是D1方程式_1这里返回的就是这串表达式本身。WasResolved是否解析成功。这是个非常重要的标志位true表示ResolvedValue可以直接信任false表示解析失败了ResolvedValue大概率是空字符串或者错误提示。返回值int0表示调用成功非0表示失败。比如传入一个不存在的属性名返回值就是非0。我第一次看这个签名时“Resolved”和“Unresolved”两个词看得直发懵。后来用一个快递的比喻想通了你网购一个东西订单上写了“自提柜B区-07号”这是UnresolvedValue实际上快递员把包裹放到“东门快递站”短信通知你去取这个“东门快递站”就是ResolvedValue。WasResolved就是告诉你——短信有没有发给你包裹到底有没有放到一个你能去取的地方。3.2 WasResolved不是摆设什么时候会解析失败很多人在学习阶段会忽略WasResolved觉得自己只是取个值看ResolvedValue不就行了但实际项目里这个布尔值能帮你拦下大量脏数据。我遇到过的解析失败场景有这么几种属性值引用了被删除的全局变量。比如原来属性填的是Width后来用户把Width这个全局变量从方程式里删了属性窗口里直接显示成错误值。这时候WasResolved是false。方程式循环引用。工程图里经常有人把质量属性又连接到自己身上SolidWorks算不出来自然也谈不上解析成功。跨零件引用了不存在的属性。装配体的某个配置里一个子零件的材料属性被删了但父级装配体里有个属性还引用着它。就因为这些情况我后来写代码几乎形成了肌肉记忆从GetType2拿到值后先看WasResolved再决定用不用这个值。如果WasResolved是false宁可给用户弹一条告警也不要悄悄把空值写进数据库。对于BOM准确性要求比较高的场景静默写入错误数据比报错更可怕。4. swCustomPropertyTypeOptions_e枚举全解读4.1 枚举值对照表Type输出参数返回的是swCustomPropertyTypeOptions_e枚举定义在SolidWorks.Interop.swconst命名空间里。我按自己整理过的文档把常用类型列表如下枚举名数值含义swCustomPropertyTypeUnknown0未知类型swCustomPropertyTypeString30字符串/文本swCustomPropertyTypeNumber31数值swCustomPropertyTypeBoolean33布尔值swCustomPropertyTypeDate34日期swCustomPropertyTypeLength40长度swCustomPropertyTypeAngle42角度swCustomPropertyTypeArea44面积swCustomPropertyTypeVolume46体积swCustomPropertyTypeMass48质量swCustomPropertyTypeMassPerVolume50密度质量/体积swCustomPropertyTypeCurrency52货币swCustomPropertyTypeDimension54尺寸swCustomPropertyTypeNumberOfDigits56数字位数swCustomPropertyTypeUserDefined60用户自定义提示不同SolidWorks版本的枚举数值可能略有调整以上是常见版本的表现。如果你在自己环境里发现数字对不上以你引用的SolidWorks.Interop.swconst里实际的枚举定义为准。代码里不要用魔法数字直接写枚举名最稳妥。4.2 量纲类型的坑单位换算与类型转换这张表里最值得关注的是Length、Mass、Volume这一组量纲类型。它们本质上也是数字但在SolidWorks内部是“带单位”的数字。当你用GetType2拿ResolvedValue时它返回的是文档当前单位设置下的字符串值。比如文档长度单位是毫米那长度属性可能返回25如果文档单位是米返回的字符串可能是0.025。这里最典型的坑是两个文档的单位设置不一样同一批数据导出来数值含义完全不同。我之前做批量属性同步工具把A文档的长度属性读出来直接写到B文档的长度属性里结果B文档显示的长度差了1000倍就是因为A用毫米、B用米。后来我在工具里强制统一单位——要么全部用SI单位存取要么在读值之前先获取文档的单位设置做换算。另外在C#里处理这些量纲类型时我会“按类型分流”处理而不是一把梭全字符串。比如判断这个属性能不能当作数字参与计算就写一个辅助方法private bool IsNumericType(swCustomPropertyTypeOptions_e type) { return type swCustomPropertyTypeOptions_e.swCustomPropertyTypeNumber || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeLength || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeMass || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeVolume || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeArea || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeAngle || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeCurrency || type swCustomPropertyTypeOptions_e.swCustomPropertyTypeMassPerVolume; }这样在写导出逻辑时能很自然地决定每个单元格是输出文本格式还是数字格式也能避免把材料这种字符串属性误当成数字参与计算。5. 可直接抄的示例属性类型读取与校验工具5.1 遍历并打印全部属性的类型和解析状态最基本的用法就是把当前文档的所有属性遍历一遍把类型和值全部打出来方便肉眼核对数据。using SolidWorks.Interop.sldworks; using SolidWorks.Interop.swconst; public void DumpAllProperties(SldWorks swApp) { IModelDoc2 doc (IModelDoc2)swApp.ActiveDoc; if (doc null) { Console.WriteLine(当前没有打开的文档); return; } string activeConfig doc.GetActiveConfiguration().Name; ICustomPropertyManager docMgr doc.Extension.CustomPropertyManager[]; ICustomPropertyManager configMgr doc.Extension.CustomPropertyManager[activeConfig]; Console.WriteLine( 模型级属性 ); DumpManagerProperties(docMgr); Console.WriteLine($ 配置特定属性 [{activeConfig}] ); DumpManagerProperties(configMgr); } private void DumpManagerProperties(ICustomPropertyManager mgr) { if (mgr null) { Console.WriteLine( 管理器为空); return; } object namesObj mgr.GetNames(); if (namesObj null) { Console.WriteLine( 没有自定义属性); return; } string[] names (string[])namesObj; foreach (string name in names) { swCustomPropertyTypeOptions_e type; string resolvedValue; string unresolvedValue; bool wasResolved; int result mgr.GetType2(name, out type, out resolvedValue, out unresolvedValue, out wasResolved); if (result ! 0) { Console.WriteLine($ [{name}] 读取失败错误码{result}); continue; } Console.WriteLine($ [{name}]); Console.WriteLine($ 类型{type} ({(int)type})); Console.WriteLine($ 解析值{resolvedValue}); Console.WriteLine($ 未解析值{unresolvedValue}); Console.WriteLine($ 解析成功{wasResolved}); } }这段代码跑起来之后你会在控制台里看到类似这样的输出[材料] 类型swCustomPropertyTypeString (30) 解析值Q235B 未解析值Q235B 解析成功True [重量] 类型swCustomPropertyTypeMass (48) 解析值12.5 未解析值质量零件1 解析成功True看到没重量属性的未解析值是质量零件1但解析值是12.5。如果你用老GetValue不加区分Excel里就会出现那条公式。5.2 按类型校验Excel导入数据我在做属性同步工具时有一个场景是用户从Excel批量导入属性到SolidWorks导入前需要对每条数据做“目标属性类型”的校验。也就是先检查SolidWorks里这个属性是什么类型再判断Excel里给的值合不合法。比如目标是布尔类型的属性Excel里给个“12”这明显不对应该直接拦下来。对应的校验逻辑就长这样public bool ValidatePropertyValue( ICustomPropertyManager mgr, string propertyName, string incomingValue, out string errorMessage) { swCustomPropertyTypeOptions_e type; string resolvedValue; string unresolvedValue; bool wasResolved; int result mgr.GetType2(propertyName, out type, out resolvedValue, out unresolvedValue, out wasResolved); if (result ! 0) { errorMessage $目标属性 [{propertyName}] 不存在; return false; } switch (type) { case swCustomPropertyTypeOptions_e.swCustomPropertyTypeBoolean: bool boolValue; if (!bool.TryParse(incomingValue, out boolValue)) { errorMessage $属性 [{propertyName}] 是布尔类型Excel数据 [{incomingValue}] 无法转换为布尔值; return false; } break; case swCustomPropertyTypeOptions_e.swCustomPropertyTypeNumber: double numValue; if (!double.TryParse(incomingValue, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out numValue)) { errorMessage $属性 [{propertyName}] 是数值类型Excel数据 [{incomingValue}] 无法转换为数值; return false; } break; case swCustomPropertyTypeOptions_e.swCustomPropertyTypeDate: DateTime dateValue; if (!DateTime.TryParse(incomingValue, out dateValue)) { errorMessage $属性 [{propertyName}] 是日期类型Excel数据 [{incomingValue}] 不是合法日期; return false; } break; } errorMessage string.Empty; return true; }这里有一处细节double.TryParse第二参数我用了NumberStyles.Float第三参数用了InvariantCulture是因为不同操作系统的区域设置不一样有的国家用逗号当小数点比如12,5有的用句点12.5。为了让导入逻辑不因为用户电脑的时钟/区域设置而行为漂移统一用InvariantCulture解析最靠谱。5.3 一个通用Numeric解析辅助函数最后再分享一个在我项目里被反复调用的函数把任意属性值安全地转为double。它的核心逻辑很简单——先用GetType2确认类型是数值类再解析ResolvedValue。但额外做了一层保护如果WasResolved为false直接返回失败不上当。public bool TryGetNumericPropertyValue( ICustomPropertyManager mgr, string propertyName, out double value) { value 0; swCustomPropertyTypeOptions_e type; string resolvedValue; string unresolvedValue; bool wasResolved; int result mgr.GetType2(propertyName, out type, out resolvedValue, out unresolvedValue, out wasResolved); if (result ! 0) return false; if (!wasResolved) return false; if (!IsNumericType(type)) return false; return double.TryParse(resolvedValue, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out value); }这个函数在我处理装配体总重量、卷料长度合计等场景里帮了大忙。因为模型级与配置特定的属性都可能出现我把“判断存在 - 判断类型 - 判断解析状态 - 解析数值”这一整条链路收敛到了一处调用方不需要关心内部细节拿不到值就返回false拿到值就是一个干净的double。6. 实战踩坑记录6.1 属性名里的空格和全角字符有一次批量处理几十个零件发现部分零件的Material属性读取失败而另一些成功。排查到最后发现是用户在SolidWorks里创建属性时属性名带了一个肉眼看不出来的全角空格——“Material ”和“Material”看起来一模一样但API按名称精确匹配多了任何一个字符都查不到。所以我的建议是批量处理前先做一次属性名清洗或者至少要把所有属性名拉出来打印一遍用[]包住名字对比很容易看出有没有多余空格。如果是从Excel映射属性名最好在代码里做一次Trim()把首尾空格去掉再传给GetType2。6.2 配置特定属性在非激活配置下读取会失败前文提到过CustomPropertyManager[configName]里的configName一旦指定了某个配置就只认那个配置的属性集合。如果你用CustomPropertyManager[配置A]去读一个只在配置B定义的属性GetType2会直接返回非0错误码。这个坑在“遍历所有配置同步属性”的场景里特别容易踩。我的经验是不要只读当前激活配置而是先通过doc.GetConfigurationNames()把所有配置名取出来然后逐个配置建立CustomPropertyManager去处理。这样才对得上号。6.3 GetNames返回object空属性会返回nullGetNames()这个方法的返回值在C#里是个object虽然有经验的人知道里面装的是string[]但直接用(string[])mgr.GetNames()强转有个隐患当文档一个自定义属性都没有时GetNames()返回null强转直接抛NullReferenceException。我习惯是先判空再转换object namesObj mgr.GetNames(); if (namesObj null) { Console.WriteLine(该属性管理器下没有任何属性); return; } string[] names (string[])namesObj;这也是很多老代码里容易崩的隐藏点建议当成固定模板写进自己的工具类里。6.4 性能问题管理器提出来API调用留在循环里如果你要批量处理几百个零件、每个零件还有几十个属性你可能会写出这样的循环foreach (var part in parts) { foreach (var propName in propNames) { var mgr part.Extension.CustomPropertyManager[]; mgr.GetType2(propName, ...); // 每个属性都重新拿一次管理器 } }这种写法在零件数量少时问题不大一旦零件数上了五百速度就会明显慢下来因为每次Extension.CustomPropertyManager[]都是在COM层做一次属性管理器的实例化。正确做法是把CustomPropertyManager提到外层循环一个零件的所有属性共用同一个管理器实例foreach (var part in parts) { ICustomPropertyManager mgr part.Extension.CustomPropertyManager[]; if (mgr null) continue; foreach (var propName in propNames) { mgr.GetType2(propName, ...); } }我实测过这种改动可以把批处理时间缩短到原来的三分之一左右。另外SolidWorks API本身不是线程安全的别想着开多线程并行读属性老老实实单线程遍历否则COM层随时可能给你扔一个无法预料的崩溃。6.5 类型枚举别用魔法数字最后一点虽然听起来有点“洁癖”但在多人协作的项目里真的很重要判断属性类型时不要在代码里写if ((int)type 30)而是用swCustomPropertyTypeOptions_e.swCustomPropertyTypeString这种枚举名。原因有两个可读性。同事看代码的时候“30”这个数字没有任何语义他还得翻文档才知道是字符串类型而枚举名一眼就能看懂。兼容性。SolidWorks内部枚举值虽然我没有遇到过变化但在不同大版本之间不能100%保证数字永远不变。直接用枚举名编译器会帮你适配一旦有变化改引用即可不用全局搜魔法数字替换。我在项目里还额外加了一道防线在工具启动时把用到的枚举值全部打印出来做一次自动“体检”如果发现枚举数字和自己预期不符立即中止并提示升级API引用版本。多花十分钟省掉的是线上批量跑的时候数据全乱的风险。做属性读取这件事理论上一个GetType2就能全部解决但真正影响项目质量的往往是这些边界情况属性名脏字符、配置不匹配、解析失败、单位不统一、空引用。我自己被这几个坑反复教育之后才慢慢沉淀出“先校验再读取、按类型分流、管理器外提”这三条铁律。做SolidWorks二次开发技术难度往往是次要的对细节的把控才是决定工具能不能真正交付给用户使用的关键。
返回列表