前一段时间在做CAD二次开发。不管是诚恳地说还是不诚恳地说,做出来的东西都很坑爹,不值一提。现在那个任务已经暂告一个段落了,所以写篇博客把值得总结得东西记录下来。下面的两个技巧都是困惑了很长时间才解决的,应当略有一些价值。


第一个技巧是启动CAD的方法。CAD二次开发的技术貌似有很多种,我不太熟悉所以不一一列举。我是在 .net平台下用C#开发的,采用的方法是用代码创建一个CAD实例并启动,效果等同于直接运行CAD可执行程序。之后可以用代码往里面发送命令。这个做法貌似十分非主流。由于CAD二次开发的方法很多,在网上查到的资料比较混乱,所以我花了很长时间才成功启动CAD。以下是方法。要声明一点,由于这件事已经过去几个月了,所以我不能完全保证下面这个方法有没有遗漏什么细节。有遗漏的话请指出,谢谢!

首先当然是添加引用(如果没有装CAD的话,第一步的第一步是先安装CAD)。我这里添加的引用有三个,分别是AutoCADAXDBLibMPolygonComlib。我忘了是不是有哪一条可以删除了?

然后using下面两条

using Autodesk.AutoCAD.Interop;

using Autodesk.AutoCAD.Interop.Common;

然后在窗体所在的命名空间里写这么一行

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000016-0000-0000-C000-000000000046")]


然后在这个命名空间里写一个接口

public interface IMessageFilter

    {

        [PreserveSig]

        int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);

        [PreserveSig]

        int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);

        [PreserveSig]

        int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);

    }

然后在窗体类里写上下面这些东西

[DllImport("ole32.dll")]

static extern int CoRegisterMessageFilter(IMessageFilter lpMessageFilter, out IMessageFilter lplpMessageFilter);

int IMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)

{

    return 0;

}

int IMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)

{

    return 1000;

}

int IMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)

{

    return 1;

}

然后在窗体的构造函数里加上两行,把构造函数弄成下面这样

public Form1()

{

    InitializeComponent();

    IMessageFilter oldFilter;

    CoRegisterMessageFilter(this, out oldFilter);

}

以上内容除了加引用以外我都不知道是干什么用的,不过经过实验以上内容缺了哪一行都不能启动CAD

接下来的步骤就比较清晰了。

AcadApplication pCAD;

System.Type oType = System.Type.GetTypeFromProgID("AutoCAD.Application");

pCAD = System.Activator.CreateInstance(oType,true) as AcadApplication;

pCAD.Visible = true;

pCAD.Documents.Open("CAD文件的路径"null, null);

CAD就成功启动了。


第二个技巧是用代码往CAD里发送命令行。说起来很惭愧,有很多操作,比如合并多段线,我没有找到相应的接口。解决方法之一是读取两条多段线的坐标,新画一条合并后的多段线并删除两个旧的多段线,但是这个做法太恶心了。于是我找到了一个方法,用代码往CAD里发送命令行。下面是一句合并多段线的代码。

pCAD.ActiveDocument.SendCommand("pe\n(handent\"" pObject2.Handle "\")\nj\n(handent\"" pObject1.Handle "\")\n\n(command)\n");

里面这个命令行等价于下面这个操作。

首先,在CAD里输入命令pe,回车,进入多段线操作。

然后,如果是在CAD

里直接操作的话,这个时候应该用鼠标选择一个实体。但是代码不能实现这个操作,所以要用代码指定一个实体。例如(handent"EX21")是指定了编号为EX21的实体。这个实体用代码表示是"(handent\"" pObject2.Handle "\")"pObject2.Handle是实体的编号(或者叫句柄?)。然后回车,即选中这个实体。需要注意的是"EX21"需要双引号,所以要用转义字符\"往字符串里嵌入双引号。经过实验,如果在CAD界面里直接输入(handent"EX21")并回车,同样可以选中编号为EX21的实体。

然后,往CAD里输入命令j,即选择合并多段线命令。

然后再用代码选中另外一个实体,比如"XE78"号实体。这相当于在CAD中输入(handent"XE78")并回车。

然后再回车,合并两条多段线。

输入(command),回车,退出多段线操作命令。在CAD中,这个操作可以通过点击esc键实现,但是用代码的话只能输入(command)并回车。

用代码发送命令行跟直接往CAD里输入命令行是一样的,上面那句代码执行后CAD的命令行窗口里会显示相应的命令。上面这个例子说明了命令行的构造规则,其他命令可以按照这个规则构造。同时这导致一个问题,即代码执行完毕前千万不要点击CAD窗口,否则点击操作可能被视为一个选择实体的操作,从而干扰命令行的执行。例如,如果系统认为点击操作是在"pe\n"之后、"(handent\"" pObject2.Handle "\")"之前发生的,那么被合并的第一条多段线不是pObject2,而是单击点中的那条多段线。这件事困扰了我很久,因为用局部数据进行测试没有问题,但是对全部数据进行处理的时候却总会出错,而且每次出的错都不一样。后来我才发觉出错的原因。