背景
因工作需要,想尝试一下从autoCAD读取图纸数据,并直接在Tekla Structures建模。之前有使用过VS2010,用vb和c#开发过一点程序,这次想继续用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的模板。
新建了一个工程,里面已经添加了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。这个小问题就不纠结了,这个目标算是完成了。
这部分的工程代码可在此处下载。
暂告一段落
后面的开发研究就不具备普遍性了,所以文章就暂告一段落,一些工程代码随后上传。
更多推荐
从0开始——CAD与Tekla开发入门
发布评论