zoukankan      html  css  js  c++  java
  • 【.NET架构】BIM软件架构01:Revit插件产品架构梳理

    一、前言

           BIM:Building Information Modeling 建筑信息模型,就是将建筑的相关信息附着于模型中,以管理该建筑在设计、算量、施工、运维全生命周期的情况。创建模型的主要主流软件有Autodesk(欧特克)的Revit、Bentley的microstation、达索的CATIA(曾被我国在80、90年代用于制造战斗机,比较高端)。我所在的公司是从事BIM软件研发,隶属于一家上市企业。

            我在两个部门工作过。第一个部门是算量软件部门,专门制作基于Revit的算量插件(桌面端)。第二个部门是BIM数据平台部门,专门为大型企业制作BIM施工管控平台(Web及移动App端)。刚研究生毕业即进入公司,在两年整的工作中主要负责WPF(DevExpress)、识别算法、网络通信编程(Remoting、WebService、WCF、Socket)以及Web开发。

            架构的改造也是我工作的一部分,站在公司老司机的肩膀上还是很爽的。那么,我就从桌面端软件开始吧!

    二、架构简图

           制作Revit插件必须要引用2个dll:RevitAPI和RevitAPIUI,每年根据Autodesk发布Revit产品进行迭代,支持C#、Managed C++及VB编程。

           该解决方案共含有15个C#项目,红色模块都需要引用Revit的dll,灰色部分不用引用。编译完之后,在C:ProgramDataAutodeskRevitAddins201X添加相应的.addin文件,并指向Product.App的位置即可,然后运行Revit的软件即可使用插件。

    <?xml version="1.0" encoding="utf-8"?>
    <RevitAddIns>
      <AddIn Type="Application">
        <Name>Product</Name>
        <Assembly>X:PathProduct.App.dll</Assembly>
        <ClientId>a21827a2-6610-445b-a625-8d9f4a52f218</ClientId>
        <FullClassName>Product.App.ExternalApplication</FullClassName>
        <VendorId>BIMproduct</VendorId>
        <VendorDescription>BIMproduct www.product.com</VendorDescription>
      </AddIn>
    </RevitAddIns>

    三、架构解读

    1. App201X

    该项目是Revit插件的入口,主要由ExternalApplicationExternalCommand组成。需要引用其它的项目。该项目的ExternalApplication需要实现一个Revit接口:IExternalApplication。

    ExternalApplication在OnStartup函数中可以做一些初始化工作,比如初始化依赖反转容器、创建Ribbon按钮、初始化载入一些第三方dll、产品的授权检测等等。

    namespace Product.App
    {
        public class ExternalApplication:IExternalApplication
        {
    
            public  Result OnStartup(UIControlledApplication application)
            {
                try
                {
    #if !REVIT2014
                    CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("zh-CN");
    #endif
                    Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
                    Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
    
                    // Refer to: https://stackoverflow.com/questions/4041197/how-to-set-and-change-the-culture-in-wpf
                    // 使用“System.Windows.Documents.TextElement”以和QS或其他插件中的“FrameworkElement”区别
                    FrameworkElement.LanguageProperty.OverrideMetadata(
                        typeof(System.Windows.Documents.TextElement),
                        new FrameworkPropertyMetadata(
                              XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
                }
                catch (Exception ex)
                {
                    if (ex is System.ArgumentException)
                    {
                        // 有可能“System.Windows.Documents.TextElement”已被注册过. Do nothing
                    }
                    else
                    {
                        return Result.Succeeded;
                    }
                }
    
                try
                {
                    //Initialize
                }
                catch (Exception ex)
                {
                    return Result.Succeeded;
                }
                return Result.Succeeded;
            }
    
           
            public Result OnShutdown(UIControlledApplication application)
            {
                try
                {
                    //shoutdown 
                }
                catch (Exception)
                {
                    // do nothing...
                }
                return Result.Succeeded;
            }
        }
    }

    ExternalCommand主要负责插件上的按钮,以“关于”按钮为例:

        [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
        [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
        public class AboutCommand : ExternalCommandBase
        {
            protected override Result RunImpls(ExternalCommandData commandData, ref string message, ElementSet elements)
            {
                AboutWnd aboutWnd = new AboutWnd();
                // 出现异常时,关闭窗体
                try
                {
                    aboutWnd.ShowDialog();
                }
                finally
                {
                    aboutWnd.Close();
                }
                return Result.Succeeded;
            }
    
            public override bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
            {
                return true;
            }
    
            public override bool IsProductLicenseValid()
            {
                return true;
            }
        }

    可以看到该命令并没有直接实现IExternalCommand, IExternalCommandAvailability两个接口。而是再封装一个抽象基类,让抽象基类实现Revit的这俩个接口。然后相关的Command再继承这个抽象基类。为什么这么做是因为抽象基类中还可以做一些检查授权等等的事情。

    namespace Product.App
    {
        public abstract class ExternalCommandBase:IExternalCommand, IExternalCommandAvailability
        {
            protected static bool _allowMultiRun = false;
            protected static bool _isRunning = false;
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="commandData"></param>
            /// <param name="message"></param>
            /// <param name="elements"></param>
            /// <returns></returns>
            public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
            {
                if(!_allowMultiRun && _isRunning)
                {
                    return Result.Cancelled;
                }
    
                if (!IsProductLicenseValid())
                {
                    Container.Resolve<IMessageBox>().ShowInfo("授权失败,无法使用!");
                    return Result.Cancelled;
                }
    
                _isRunning = true;
    
                try
                {                
                    string msg = $"run command '{this.GetType().Name}'";
                    Container.Resolve<ILog>().Info(msg);
                    var res = RunImpls(commandData, ref message, elements);
                    _isRunning = false;
                    return res;
                }
                catch (Autodesk.Revit.Exceptions.OperationCanceledException)
                {
                    _isRunning = false;
                    // generally, it's select cancel exception
                    return Result.Cancelled;
                }
                catch (Exception ex)
                {
                    _isRunning = false;
                    if (ex.Message== "The active view is non-graphical and does not support capture of the focus for pick operations.")
                    {
                        Container.Resolve<IMessageBox>().ShowWarning("当前焦点不在视图区域,请将焦点停留在某个视图中。");
                        return Result.Failed;
                    }
                    string errorMsg = $"exception in command '{this.GetType().Name}': {ex}";
                    Container.Resolve<ILog>().Error(errorMsg, ex);
                    message = ex.Message;
                    return Result.Failed;
                }
            }
    
            protected string GetProductName()
            {
                return Container.Resolve<IApplication>().ProductLicenseName;
            }
    
            /// <summary>
            /// Implements this method to provide implementation for Execute
            /// </summary>
            /// <param name="commandData"></param>
            /// <param name="message"></param>
            /// <param name="elements"></param>
            /// <returns></returns>
            protected abstract Result RunImpls(ExternalCommandData commandData, ref string message, ElementSet elements);
    
            public virtual bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
            {
                return false;
            }
    
            public virtual bool IsProductLicenseValid()
            {
    #if DEBUG
                return true;
    #else
                return LicenseService.IsRegistered(GetProductName(), Container.Resolve<IApplication>().LicenseServerURL);
    #endif
            }
        }
    }

     

    2. CommonCommon.RevitExt201X

    Common项目主要定义基本的数据接口和定义依赖反转容器,注意该项目中并没有引用Revit的dll,说明其中的数据定义是不依赖于Revit的。

    与Revit有关的数据定义都放在Common.RevitExt201X,比如自定义的DockablePanel接口(继承IDockablePaneProvider)。

    3. UI、UI.Common、UI.RevitExt201X

    UI项目主要用于制作点击命令后产生的界面UI,该项目采用MVVM框架进行界面实现,文件夹梳理可如下:

    UI.Common主要是用于制作可复用的自定义控件、进度条、常用Converter;

    UI.RevitExt201X主要是用于控制DockablePanel和控制Revit构件显示效果,其依赖于Revit。

    4. Resource、Unity4Net、Report201X

    Resource项目用于多语言版本;

    Unity4Net项目用于对Windows的一些操作进行封装;

    Report201X项目用于输出报表。报表因为其有一定的业务逻辑,并不是一个单纯的UI界面汇总,所以单独做成一个project会比较好。

    5. RevitAPIUtil201X

    该项目用于对rvt中的构件进行直接的API操作,但不含有任何的业务逻辑。比如获取构件的若干面,获取长度等等。

    6. Core201X

    该项目用于核心业务逻辑。当用户在UI界面中点击某按钮,其执行的核心在该项目中。

    7. Foundation、Foundation.RevitExt201X

    Foundation项目用于进行基本的常数定义,全局定义,授权检查配置等等。

    Foundation.RevitExt201XApp201X息息相关,App201X中的ExternalApplication的OnStartup中的相关初始化逻辑比较多,将核心的部分抽出放在该项目中。

    8. DBManager

    该项目用于对数据库进行操作,Revit插件如果只用自带的revit数据缓存是不能满足现实需求的,需要使用小型数据库,推荐使用SqlLite数据库。

    9. ProjectManager

    因为算量软件的第一步是工程设置,之后计算的正确性都需要正确的工程设置,所以将工程设置单独抽出而不是放在Core201X中。

    四、结尾

         在今年一月,我被划入数据平台部门,暂且离开了桌面插件部门。进入数据平台部门后,对Web端及手机App端有了一些了解,并提出了相应的架构改造计划,之后几篇将叙述我对Web工程架构的一些心得。

  • 相关阅读:
    C++之路进阶——codevs2439(降雨量)
    C++之路进阶——codevs2933(诗人小G)
    C++之路进阶——bzoj2879(美食节)
    C++之路进阶——bzoj1934(善意的投票)
    C++之路进阶——bzoj3876(支线剧情)
    C++之路进阶——codevs1281(Xn数列)
    八数码难题
    道路游戏
    细胞分裂
    最长链
  • 原文地址:https://www.cnblogs.com/lovecsharp094/p/8719401.html
Copyright © 2011-2022 走看看