四.SolidWorks 开发之Linq初探

SolidWorks开发之Linq初探

一.何为Linq

语言集成查询(英语:Language Integrated Query,缩写:LINQ),是微软的一项技术,新增一种自然查询的SQL语法到 Dot NET Framework的编程语言中,当前可支持C#以及Visual Basic .NET语言.

二.查询SolidWorks对象

在SolidWorks开发中我们经常会遇到遍历特征,遍历零部件的需求,此时使用Linq便会如虎添翼.当然也不局限于集合查询,结合C#的委托机制,我们也可以将一些特定的代码模式抽象出自己的查询操作组件.

三.使用实例

3.1 在遍历特征中使用

SolidWorks开发中我们经常会遇到特征遍历,如下图所示,是一个SolidWorks装配体的特征树.红色线条圈出来的是我们需要获取的特征.

下面,我们将使用Linq查询出这三个初始基准面,一个坐标原点,和一个插件中的自定义宏特征.

feat.png

1. 获取初始参考基准面

SolidWorks零件和装配体中有三个初始基准面和一个原点作为初始参考.
我们经常会需要这三个参考执行一些操作.因为这几个参考会有一个默认命名,所以有时候会直接使用名字来获取特征,但面对用户使用了其他语言的模板或者修改了名字容易造成错误,比较好的方法是遍历所有特征,前三个类型为基准面的特征为初始基准面.

/// <summary>
/// 获取初始基准面
/// 前视基准面
/// 上视基准面
/// 右视基准面
/// </summary>
/// <returns></returns>
public IEnumerable<IFeature> 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<IFeature> GetFeaturesEx(this              IFeatureManager featMgr,bool TopOnly = false)
        {
            var objarray = featMgr.GetFeatures(TopOnly) as object[];
            var feats = objarray.Cast<IFeature>();
            if (feats != null)
            {
                return feats;
            }
            else
            {
                throw new FeatMgrFeatsNotFoundException("无特征集返回");
            }
        }
  • FeatureTypeName.RefPlane 为一个定义好的常量
public class FeatureTypeName
{
    /// <summary>
    /// 基准面
    /// interface: IRefPlane
    /// </summary>
    public const string RefPlane = "RefPlane";

    
    /// <summary>
    /// 原点坐标系
    /// interface: None
    /// </summary>
    public const string OrignSys = "OriginProfileFeature";
}

2. 获取原点

  • 同样的我们可以遍历出原点
        /// <summary>
        /// 获取原点 
        /// </summary>
        /// <returns></returns>
        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<IModelDoc2> action)
        {
            var docs = (swApp.GetDocuments() as Object[]).Cast<IModelDoc2>();

            if (docs  == null)
            {
                return;
            }

            foreach (var item in docs)
            {
                action?.Invoke(item);
            }
        }

同样可以封装一个使用打开模型的方法

        /// <summary>
        /// 对活动文档进行操作
        /// </summary>
        /// <param name="swApp"></param>
        /// <param name="action"></param>
        public static void UsingActiveDoc(this SldWorks swApp, Action<IModelDoc2> 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")));
        }
  • 此例中使用了如下对特征条件进行筛选的扩展方法
        /// <summary>
        /// 获取特定的特征
        /// </summary>
        /// <param name="comp"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public static IList<Feature> TakeCompTopFeaturesWhile(this Component2 comp, Func<Feature,bool> func)
        {
            List<Feature> features = new List<Feature>();
            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<Body2> 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<Component2> 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<ModelDoc2> 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<SldWorksFixture>
    {

        private readonly IActiveProjectService _activeProjectService;
        private readonly ISolidWorksAddin _addin;

        private ModelDoc2 activeDoc;
        /// <summary>
        /// 构造函数,需要注入依赖项
        /// </summary>
        /// <param name="addin"></param>
        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开发人员提供帮助.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容