1. 程式人生 > >從0開始——CAD與Tekla開發入門

從0開始——CAD與Tekla開發入門

背景

因工作需要,想嘗試一下從autoCAD讀取圖紙資料,並直接在Tekla Structures建模。之前有使用過VS2010,用vb.net和c#.net開發過一點程式,這次想繼續用VS2010進行開發。由於將要從0開始學習CAD和Tekla兩款軟體的二次開發,等完成後,對其他人也有些參考價值,所以本文將記錄下整個的摸索過程。
目前的大致思路是:
- 通過CAD外掛讀取資料
- 儲存原始資料
- 原始資料經過計算後,生成模型引數資料
- 通過Tekla外掛將模型繪製出來

嘗試過程

2016.08.23

第1步目標:外部程式呼叫Tekla的API,在模型裡繪製簡單圖形(已實現)

目前已實現,稍後補上。

第2步目標:外部程式呼叫autoCAD的API,在圖紙上繪製簡單圖形(已實現)

經過搜尋,發現CAD二次開發主分為:VBA、Lisp、ObjectARX和.Net。從我的開發經歷看,.Net應該是最滿足專案需求的——操作體驗與保密,所以首先嚐試.Net的方案。
我用的是autoCAD2014,找到這篇文章AutoCAD .NET API二次開發學習指南
去到autoCAD官網的資料區,裡面有各種文件和安裝程式,可惜沒有找到中文網頁。
之前搜尋ObjectARX時,有下載2014版的SDK,是在新浪部落格找到的,連結也是官網的。
從資料區看,autoCAD2014支援VS2010和VS2012,看來運氣還不錯。
安裝完ObjectARX SDK,確切地說是解壓了之後,在VS2010裡並沒有出現ObjectARX的模板。於是按照學習指南的步驟,又安裝了.NET Wizards,程式是直接在

官網下載的。在安裝.NET Wizards之前,我還安裝了ObjectARX Wizards,同樣是在官網找到的。ObjectARX Wizards安裝了之後,在VS2010裡C++下出現了AutoCAD的模板,安裝.NET Wizards之後,才在C#下出AutoCAD的模板。
C#下的新模板
新建了一個工程,裡面已經添加了autoCAD的一些引用,直接編譯,沒有報錯。
這裡寫圖片描述
生成的是.dll檔案,不能直接執行,我又另外新建了一個普通工程TestModel,想新增引用,看能不能編譯通過。因為dll的外掛裡是空的,我想起了之前在github下載的一個cad 外掛工程,當時已經編譯通過生成了dll檔案。於是,我用它來測試引用,順便看看能不能操作autoCAD。
我先是隻添加了dll的引用和using宣告,編譯通過,但執行時報錯:”未能載入檔案或程式集“Acdbmgd.dll”或它的某一個依賴項。找不到指定的模組。“。新增對Acdbmgd.dll等幾個檔案的引用,還是報錯。
這裡寫圖片描述

還是看文件吧,下載了AutoCAD 2014 .Net Training,裡面有個PPT,英文的。看了幾頁,驚呆了!居然是用netload載入dll到autoCAD!試了一下,順利地運行了一個畫直線的命令,果然是開啟的方式不對。可是,這種用法,真的不符合我的預期啊!
這裡寫圖片描述
但我不死心,繼續找。終於找到《外部.NET程式與AutoCAD互動》,在TestModel中添加了“AutoCAD 2014 Type Library”引用,但新增”using Autodesk.AutoCAD.Interop;“時提示錯誤。搜尋“”,找到一個帖子,以下一個回答。

暈,還沒有人回答你噢,我來說一下吧,首先第一步你要新增兩個COM引用:
AutoCAD 2010 Type Library //我的機子裡裝的是CAD2010版
AutoCAD/ObiectDBX Common 18.0 Type Library
把這兩個引用的名稱空間引進來
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
//就可以寫程式碼了
AcadApplication CadApp; //定義一個CAD應用程式對像
AcadDocument CadDoc; //定義一個CAD文件
AcadModelSpace CadSpace; //這是CAD的模型空間
//然後就先一個CAD應用程式
CadApp = new AcadApplication();
CadApp.Visible = true; //如果你想把CAD顯示到前臺來,就設為true
CadDoc = CadApp.ActiveDocument; //獲取CAD當前活動的文件
CadSpace = (AcadModelSpace)CadDoc.ModelSpace; //獲得名稱空間
double[] c = new double[6]; //我隨便定義一個要畫線的點陣列,記得點這個數組裡是按XYZ座標順序存放的噢
c[0] = 34; c[1] = 98; c[2] = 67; c[3] = 956; c[4] = 655; c[5] = 322; //隨便輸的
CadSpace.Add3DPoly(c); //我這裡畫的是三維多段線,你要畫其它的線型的話,到找一下對應的方法即可。
別忘了記得給分噢!

但還是提示錯誤,我用Everything搜尋“Autodesk.AutoCAD.Interop.dll”,在ObjectARX SDK裡找到了這個檔案,有32位、64位兩種,還看到了Autodesk.AutoCAD.Interop.Common.dll,我引用了64位的這兩個檔案,錯誤消除了。直接編譯執行,成功打開了一個新autoCAD程序,畫出了一條線。
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

在《外部.NET程式與AutoCAD互動》找到呼叫已開啟autoCAD的程式碼,修改後如下,也成功地在autoCAD裡畫了一條線。後來發現,只新增兩個dll,不新增COM引用,也是可以的。

        private void button2_Click(object sender, EventArgs e)
        {
            const string progID = "AutoCAD.Application.19.1"; 
            AcadApplication acApp = null; 
            try 
            { 
                acApp = (AcadApplication)Marshal.GetActiveObject(progID); 
            }catch{
                try { 
                    Type acType = Type.GetTypeFromProgID(progID); 
                    acApp = (AcadApplication)Activator.CreateInstance(acType, true); 
                } catch { 
                    MessageBox.Show("Cannot create object of type \"" + progID + "\""); 
                } 
            }
            if (acApp != null){ 
                // By the time this is reached AutoCAD is fully  
                // functional and can be interacted with through code   
                acApp.Visible = true;
                AcadDocument CadDoc;  //定義一個CAD文件
                AcadModelSpace CadSpace;  //這是CAD的模型空間
                //然後就先一個CAD應用程式
                acApp.Visible = true;  //如果你想把CAD顯示到前臺來,就設為true
                CadDoc = acApp.ActiveDocument;  //獲取CAD當前活動的文件
                CadSpace = (AcadModelSpace)CadDoc.ModelSpace; //獲得名稱空間
                double[] c = new double[6];  //我隨便定義一個要畫線的點陣列,記得點這個數組裡是按XYZ座標順序存放的噢
                c[0] = 34; c[1] = 98; c[2] = 67; c[3] = 956; c[4] = 655; c[5] = 322;  //隨便輸的
                CadSpace.Add3DPoly(c);
                MessageBox.Show("hello");
            }
        }

現在能啟動autoCAD了,後面就是想辦法把外掛dll載入進去。

第3步目標:外部程式呼叫autoCAD .Net API(未實現)

第2步完成的,其實是通過COM方式操作的autoCAD。《外部.NET程式與AutoCAD互動》雖然寫了怎樣載入.Net程式集來呼叫.Net API,但一直出現應用程式載入失敗。看來事情並不是那麼簡單。COM方式操作autoCAD難以得到返回值,而我的目標是讀取資料。
看來這條路暫時不通,調整目標:用acad2014.lsp自動載入dll外掛,通過外掛與外部程式通訊。

C:\Program Files\Autodesk\AutoCAD 2014\Support\acad2014.lsp
(COMMAND "NetLoad" "D:\\test.dll")

2016.08.24

第4步目標:用.Net外掛在圖紙上畫常用圖形、標註等(部分實現)

先看《AutoCAD 2014 .Net Training》裡的教程,把裡面的示例工程都認真看了一遍。從1到8,每個示例新增一點功能,發現它把常用的功能都演示了下:畫圓、畫塊、database、Jigs、PointMonitor、新增右鍵、新增窗體。可惜沒說Ribbon,我找到AutoCAD中程式建立Ribbon介面執行AutoCAD命令,本想直接新增到示例8,但引用老是有問題。然後,我用Wizard新建了個工程,引用全部選中,把程式碼複製進去,又新增System.Xaml引用和using Autodesk.Windows;終於編譯通過,創建出了一個空白Ribbon Tab。

using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;

using Autodesk.Windows;

        private const string MY_TAB_ID = "MY_TAB_ID";

        [CommandMethod("addMyRibbon")]
        public void createRibbon()
        {
            Autodesk.Windows.RibbonControl ribCntrl =
                      Autodesk.AutoCAD.Ribbon.RibbonServices.RibbonPaletteSet.RibbonControl;
            //can also be Autodesk.Windows.ComponentManager.Ribbon;     

            //add the tab
            RibbonTab ribTab = new RibbonTab();
            ribTab.Title = "My custom tab";
            ribTab.Id = MY_TAB_ID;
            ribCntrl.Tabs.Add(ribTab);

            //create and add both panels
            addPanel1(ribTab);
            addPanel2(ribTab);

            //set as active tab
            ribTab.IsActive = true;
        }

        private void addPanel2(RibbonTab ribTab)
        {
        }

        private void addPanel1(RibbonTab ribTab)
        {
            //throw new NotImplementedException();
        }

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
當我想繼續新增Panel和Button時,又出現引用問題了,有一個類的宣告老找不到。

2016.08.25

折騰了半天,發現是自定義的。。。補上之後,終於新增上按鈕了。

//新增按鈕的程式碼
        private void addPanel2(RibbonTab ribTab)
        {
            //create the panel source
            RibbonPanelSource ribPanelSource = new RibbonPanelSource();
            ribPanelSource.Title = "Edit Registry";

            //create the panel
            RibbonPanel ribPanel = new RibbonPanel();
            ribPanel.Source = ribPanelSource;
            ribTab.Panels.Add(ribPanel);

            //create button1
            RibbonButton ribButtonDrawCircle = new RibbonButton();
            ribButtonDrawCircle.Text = "My Draw Circle";
            ribButtonDrawCircle.ShowText = true;
            //pay attention to the SPACE after the command name
            ribButtonDrawCircle.CommandParameter = "DrawCircle ";
            ribButtonDrawCircle.CommandHandler = new AdskCommandHandler();
            ribPanelSource.Items.Add(ribButtonDrawCircle);
        }

        private void addPanel1(RibbonTab ribTab)
        {
            //throw new NotImplementedException();
        }
        [CommandMethod("DrawCircle")]
        public void DrawCircle()
        {
            //畫個圓,實現在此略去,這不是這篇blog的重點。
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
            PromptPointOptions getPointOptions = new PromptPointOptions("Pick Center Point : ");
            PromptPointResult getPointResult = ed.GetPoint(getPointOptions);
            if ((getPointResult.Status == PromptStatus.OK))
            {
                PromptDistanceOptions getRadiusOptions = new PromptDistanceOptions("Pick Radius : ");
                getRadiusOptions.BasePoint = getPointResult.Value;
                getRadiusOptions.UseBasePoint = true;
                PromptDoubleResult getRadiusResult = ed.GetDistance(getRadiusOptions);
                if ((getRadiusResult.Status == PromptStatus.OK))
                {
                    Database dwg = ed.Document.Database;
                    Transaction trans = dwg.TransactionManager.StartTransaction();
                    try
                    {
                        Circle circle = new Circle(getPointResult.Value, Vector3d.ZAxis, getRadiusResult.Value);
                        BlockTableRecord btr = (BlockTableRecord)trans.GetObject(dwg.CurrentSpaceId, OpenMode.ForWrite);
                        btr.AppendEntity(circle);
                        trans.AddNewlyCreatedDBObject(circle, true);
                        trans.Commit();
                    }
                    catch (System.Exception ex)
                    {
                        ed.WriteMessage("problem due to " + ex.Message);
                    }
                    finally
                    {
                        trans.Dispose();
                    }
                }
            }
        }
//以下是響應按鈕的自定義類
    class AdskCommandHandler : System.Windows.Input.ICommand
    {
        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            //is from Ribbon Button
            RibbonButton ribBtn = parameter as RibbonButton;
            if (ribBtn != null)
            {
                //execute the command 
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.SendStringToExecute((string)ribBtn.CommandParameter, true, false, true);
            }
        }
    }

這裡寫圖片描述
這一部分的工程程式碼到此處可下載

第5步目標:把ribbon、窗體、右鍵整合到同一個外掛工程(已實現)

2016.08.26
把Ribbon、palette、右鍵的程式碼整合到一起之後,可以通過命令來載入它們了。但為了方便,我想讓CAD啟動時,自動載入。

        void IExtensionApplication.Initialize()
        {
            AddContextMenu();
            createRibbon(); 
            palette();
         }

        void IExtensionApplication.Terminate()
        {
            RemoveContextMenu();            
        }

按理說,上面的程式碼就能讓CAD在啟動時載入右鍵、Ribbon、窗體了,但Dll載入卻出問題了。逐一測試後,發現是Ribbon載入有問題。在《 開啟cad如何自動載入ribbon選單》找到了答案,原來是要等待Ribbon啟動。原樣複製程式碼,卻提示“No overload for ‘ComponentManager_ItemInitialized’ matches delegate ‘System.EventHandler’”。還好最後找到另一篇《獲取Ribbon 選項卡(Tab)被點選的訊息》,添加了一個“< RibbonItemEventArgs >”,錯誤消除。

        void IExtensionApplication.Initialize()
        {
            AddContextMenu();
            Autodesk.Windows.ComponentManager.ItemInitialized += new EventHandler<RibbonItemEventArgs>(ComponentManager_ItemInitialized); 
            palette();
         }

        void IExtensionApplication.Terminate()
        {
            RemoveContextMenu();
        }
        void ComponentManager_ItemInitialized(object sender, RibbonItemEventArgs e)
        {
            if (Autodesk.Windows.ComponentManager.Ribbon != null)
            {
                createRibbon();
                Autodesk.Windows.ComponentManager.ItemInitialized -= new EventHandler<RibbonItemEventArgs>(ComponentManager_ItemInitialized);
            }
        }

這裡寫圖片描述
自動載入使用的是登錄檔法,為了不讓CAD提示警告,還把dll放在了CAD安裝目錄下。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R19.1\ACAD-D001\Applications\CadDataCapture]
"LOADCTRLS"=dword:0000000e
"LOADER"="C:\\Program Files\\Autodesk\\AutoCAD 2014\\CadDataCapture.dll"
"DESCRIPTION"="AutoCAD Data Capture"
"MANAGED"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R19.1\ACAD-D001\Applications\CadDataCapture\Commands]
"palette"="CadDataCapture.resources.dll#palette"

[HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R19.1\ACAD-D001\Applications\CadDataCapture\Groups]
"CadDataCapture_CMDS"="CadDataCapture_CMDS"

但還有一個問題,就是palette載入之後,重啟CAD,會有兩個palette。這個小問題就不糾結了,這個目標算是完成了。
這部分的工程程式碼可在此處下載

暫告一段落

後面的開發研究就不具備普遍性了,所以文章就暫告一段落,一些工程程式碼隨後上傳。