SolidWorks开发之Linq初探
一.何为Linq
语言集成查询(英语:Language Integrated Query,缩写:LINQ),是微软的一项技术,新增一种自然查询的SQL语法到 Dot NET Framework的编程语言中,当前可支持C#以及Visual Basic .NET语言.
- 更多介绍请查看MSDN 中的介绍 https://docs.microsoft.com/zh-cn/dotnet/csharp/linq/
二.查询SolidWorks对象
在SolidWorks开发中我们经常会遇到遍历特征,遍历零部件的需求,此时使用Linq便会如虎添翼.当然也不局限于集合查询,结合C#的委托机制,我们也可以将一些特定的代码模式抽象出自己的查询操作组件.
三.使用实例
3.1 在遍历特征中使用
SolidWorks开发中我们经常会遇到特征遍历,如下图所示,是一个SolidWorks装配体的特征树.红色线条圈出来的是我们需要获取的特征.
下面,我们将使用Linq查询出这三个初始基准面,一个坐标原点,和一个插件中的自定义宏特征.
1. 获取初始参考基准面
SolidWorks零件和装配体中有三个初始基准面和一个原点作为初始参考.
我们经常会需要这三个参考执行一些操作.因为这几个参考会有一个默认命名,所以有时候会直接使用名字来获取特征,但面对用户使用了其他语言的模板或者修改了名字容易造成错误,比较好的方法是遍历所有特征,前三个类型为基准面的特征为初始基准面.
///
/// 获取初始基准面
/// 前视基准面
/// 上视基准面
/// 右视基准面
///
///
public IEnumerable GetWorldFeat(ModelDoc2 doc)
{
return doc.FeatureManager.GetFeaturesEx(true).
Where(p => p.GetTypeName2() == FeatureTypeName.RefPlane).
Select(p => p).Take(3);
}
- 其中 GetFeaturesEx(true) 是一个对GetFeatures() 方法的封装.
public static IEnumerable GetFeaturesEx(this IFeatureManager featMgr,bool TopOnly = false)
{
var objarray = featMgr.GetFeatures(TopOnly) as object[];
var feats = objarray.Cast();
if (feats != null)
{
return feats;
}
else
{
throw new FeatMgrFeatsNotFoundException("无特征集返回");
}
}
- FeatureTypeName.RefPlane 为一个定义好的常量
public class FeatureTypeName
{
///
/// 基准面
/// interface: IRefPlane
///
public const string RefPlane = "RefPlane";
///
/// 原点坐标系
/// interface: None
///
public const string OrignSys = "OriginProfileFeature";
}
2. 获取原点
- 同样的我们可以遍历出原点
///
/// 获取原点
///
///
public IFeature GetOrign(ModelDoc2 doc)
{
return doc.FeatureManager.GetFeaturesEx(true).
Where(f => f.GetTypeName2() == FeatureTypeName.OrignSys).
Select(p => p).FirstOrDefault();
}
3. 获取特定类型特征
在上图特征树最底部还有一个宏特征SPVD设计数据1,现在我们我们需求是在创建此种类型的自定义特征之前判断有没有,存在的话我们就不新建此特征而是编辑它.此时我们依然可以用Linq很简单的遍历处理.
private IFeature GetDesignData(ModelDoc2 doc)
{
if (doc == null)
{
return null;
}
//获取所有特征
var feats = doc.FeatureManager.GetFeaturesEx(true);
//找出类型为宏特征的特征
var MacroFeats = feats?.Where(p => p.GetTypeName2() == FeatureTypeName.MacroFeature).Select(p => p);
//找出类型为SPVD压力热器设计数据的特征
return MacroFeats?.
Where(mf => ((MacroFeatureData)mf.GetDefinition()).GetProgId()
== "XCHF.SPVD.swAddin.AddinService.SPVDProjectFeature").
Select(p => p).FirstOrDefault();
}
- 接下来将我们找到的自定义宏特征调整为编辑状态即可,如图
3.2 自定义委托的使用
- Linq方法本质上为委托作为参数的方法.在SolidWorks开发中我们也可以定义一些委托参数的方法来简便开发工作.
1.对文档操作
- 假设我们要对所有在SolidWorks中打开的模型进行统一操作,当然遍历所有打开文档然后执行操作即可,但我们也可以将此种模式封装为一个方法.
在下面的代码为将所有文档另存为一个新文档
SwApp.UsingOpenDoc(d => d?.SaveAsSilent(SaveAsFilePath, true));
我们只需要封装一个如下扩展方法,以后遍历文档便可以直接使用.
public static void UsingOpenDoc(this SldWorks swApp, Action action)
{
var docs = (swApp.GetDocuments() as Object[]).Cast();
if (docs == null)
{
return;
}
foreach (var item in docs)
{
action?.Invoke(item);
}
}
同样可以封装一个使用打开模型的方法
///
/// 对活动文档进行操作
///
///
///
public static void UsingActiveDoc(this SldWorks swApp, Action action)
{
if (swApp.ActiveDoc != null)
{
action?.Invoke((IModelDoc2)swApp.ActiveDoc);
}
}
2.对组件(Component2)进行操纵
- 获取组件中特定的特征
public void GetFeatsTest(AssemblyDoc AssDoc)
{
//获取所有组件
Component2[] swAllComp = (AssDoc.GetComponents(false) as object[]) as Component2[];
//获取组件中名字为AsmFace,AsmFace1,AsmFace2的特征
var feats = swAllComp.Select(p => p.TakeCompTopFeaturesWhile(f =>
f.GetTypeName2() == FeatureTypeName.RefPlane &&
(f.Name == "AsmFace" || f.Name == "AsmFace1" || f.Name == "AsmFace2")));
}
- 此例中使用了如下对特征条件进行筛选的扩展方法
///
/// 获取特定的特征
///
///
///
///
public static IList TakeCompTopFeaturesWhile(this Component2 comp, Func func)
{
List features = new List();
var feat = comp.FirstFeature();
while (feat != null)
{
if (func != null && func(feat))
{
features.Add(feat);
}
feat = feat.GetNextFeature() as Feature;
}
return features;
}
- 同样的,我封装了对实体操作,对子组件操作,和使用组件的ModelDoc2作一些操作的扩展方法
public static void UsingAllBody(this Component2 comp,Action action,swBodyType_e bodyType_E,bool VisbleOnly = false)
{
var bodys = comp.GetBodies2((int)bodyType_E) as Body2[];
foreach (var item in bodys)
{
if (VisbleOnly && item.Visible)
{
action(item);
}
else
{
action(item);
}
}
}
public static void UsingChildren(this Component2 comp,Action action)
{
var child = comp.GetChildren() as Component2[];
if (child != null)
{
foreach (var item in child)
{
action(item);
}
}
}
public static void UsingCompModelDoc2(this Component2 comp,Action action)
{
ModelDoc2 swModel = comp.GetModelDoc2() as ModelDoc2;
if (swModel != null)
{
action(swModel);
}
}
3.对方程式和自定义属性的查询修改操作
- Linq查询,获取所有全局变量的变量名字
var doc = _addin.SwApp.ActiveDoc as ModelDoc2;
var equ = doc.GetEquationMgr().GetAllEqu().
Where(p => p.GlobalVariable).Select(p => p.VarName);
- 获取包含日期的自定义属性
var doc = _addin.SwApp.ActiveDoc as ModelDoc2;
var dateProerty = doc.Extension.CustomPropertyManager[""].GetAllProperty()
?.Where(p => p.Value.Contains("日期"))?.Select(p => p.Name);
- 此处使用了对属性管理器和方程式管理器的扩展方法,由于只是展示可行性和代码比较多,此处便不贴出了.
四.调试和单元测试
1.Linq表达式调试
- Linq无疑为我们的代码提升了可读性,使我们的代码简介易读.有得必有失,简洁易读的背后是程序的可调试性被大大减弱了.但是,OzCode为我们提供对Linq表达式的调试支持.
Linq调试插件 OzCode
- 在Visual Studio扩展中搜索OzCode即可
- 从下图可以直接看到,程序遍历出了12个特征,在Where时便过滤出我们需要的三个基准面.
- OzCode提供了很多调试帮助,还有快速附加到程序,这对调试SolidWorks很有帮助,大家可以自行探索.
2.单元测试
1. 为什么要单元测试
- 单元测试可以有效地测试某个程序模块的行为,更可以重复大量测试.
2. 开发SolidWorks时进行单元测试
- 与SolidWorks通信的特性,决定了SolidWorks单元测试的繁琐与多数情况下的意义不大.但是你如果要开发一个长期维护的程序或者系统的插件,还是建议进行一定程度的单元测试.下面演示了一个简单的SolidWorks单元测试.
3.对示例代码写单元测试
此处使用了XUnit测试框架对SolidWorks测试
1.使用一个类夹具提供SolidWorks上下文,此处定义了一个SolidWorksFixture来启动SolidWorks,然后获取SolidWorks上下文并注入到我们的测试类中.如下所示,我们在构造函数中接收了SolidWorksFixture中提供的ISolidWorksAddin接口,关于具体实现,由于此处只是分享而且代码过长,便不在贴出.
在构造函数中程序等待SolidWorks启动完成,然后新建一个测试文档,以便接下来的方法来运行测试
[Collection("ActiveProjectService")]
public class ActiveProjectServiceTests:IClassFixture
{
private readonly IActiveProjectService _activeProjectService;
private readonly ISolidWorksAddin _addin;
private ModelDoc2 activeDoc;
///
/// 构造函数,需要注入依赖项
///
///
public ActiveProjectServiceTests(SldWorksFixture addin)
{
_addin = addin;
//等待SolidWorks启动完成
while (!addin.SwApp.StartupProcessCompleted)
{
Thread.Sleep(500);
}
if (addin != null)
{
activeDoc = addin.SwApp.CreateDocument(false, swDocumentTypes_e.swDocASSEMBLY);
}
if (_activeProjectService == null)
{
_activeProjectService = new ActiveProjectService(addin);
}
}
}
- 2.使用一个测试方法测试获取初始基准面,此处只是验证了长度,因为还有其他方法验证特征是否是需要的特征.
[Fact(DisplayName ="获取所有世界基准面")]
public void GetWorldFeatTest()
{
var feats = _activeProjectService.GetWorldFeat();
feats.ToList().ForEach(f =>
Debug.Print(f.Name)
);
Assert.True(feats.ToArray().Length == 3);
}
- 3.其他方法的单元测试类似,只需要在类中增加测试方法,SolidWorksFixture需要阅读一下XUnit文档,如果你有SolidWorks开发经验,初步实现并不繁琐.如果你实现了一个依赖注入的SolidWorks框架,
便可以更优雅的对容器内依赖项测试.
五.总结
- 以上便为SolidWorks中Linq使用的简单尝试和总结,希望能对SolidWorks开发人员提供帮助.