四.SolidWorks 开发之Linq初探

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查询出这三个初始基准面,一个坐标原点,和一个插件中的自定义宏特征.

feat.png

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即可
image.png
  • 从下图可以直接看到,程序遍历出了12个特征,在Where时便过滤出我们需要的三个基准面.
OzCode调试
  • 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开发人员提供帮助.

你可能感兴趣的:(四.SolidWorks 开发之Linq初探)