從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,程式是直接在
新建了一個工程,裡面已經添加了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。這個小問題就不糾結了,這個目標算是完成了。
這部分的工程程式碼可在此處下載。
暫告一段落
後面的開發研究就不具備普遍性了,所以文章就暫告一段落,一些工程程式碼隨後上傳。