- 积分
- 24557
- 明经币
- 个
- 注册时间
- 2004-3-17
- 在线时间
- 小时
- 威望
-
- 金钱
- 个
- 贡献
-
- 激情
-
|
本帖最后由 作者 于 2009-5-19 15:02:55 编辑
原帖:http://through-the-interface.typepad.com/through_the_interface/commands/
一、AutoCAD的命令编程技术
August 11, 2006
Techniques for calling AutoCAD commands programmatically
It's quite common to want to call commands from one or other of AutoCAD's programming environments. While it's cleanest (from a purist's perspective) to use an API to perform the task you want, the quickest way - and the one which will appeal to the pragmatists (and the lazy) among us is often to call a sequence of commands. And there are, of course, a few places in AutoCAD where APIs have not been exposed, so scripting commands is the only way to achieve what you want.
Let's take the simple example of adding a line. Here's what you'd do from the different environments from a low-level API perspective:
LISP - create an association list representing the entity and then (entmake) it
VBA (or COM) - get the ModelSpace object from the active drawing and call AddLine (the COM API is probably the simplest in that respect)
.NET and ObjectARX - open the block table and then the model-space block table record, create a new line object and append it to the model-space (and to the transaction, if you're using them), closing the various objects along the way
Having first started coding for AutoCAD with LISP (for R10), I know that the simplest way to do what you want from that environment is to call the LINE command, passing in the start and end points:
(command "_LINE" "0,0,0" "100,100,0" "")
LISP is great in that respect: as you're not able to define native commands (only LISP) functions, it's perfectly acceptable to use it to script commands to do what you want, rather than rely on low-level APIs.
ObjectARX in particular has potential issues with respect to defining native commands calling commands, as AutoCAD is only "re-entrant" up to 4 levels. Without going into specifics, it's basically best to avoid calling commands using acedCommand() from ObjectARX, unless the command is registered as a LISP function using acedDefun().
While you do have to be careful when calling commands from VBA or ObjectARX, there are a few options available to you.
ObjectARX
ads_queueexpr()
This old favourite is intended to be used from acrxEntryPoint() to execute a sequence of commands after (s::startup) has been called from LISP (as you are not able to use acedCommand() from this context)
You need to declare it yourself (extern "C" void ads_queueexpr( ACHAR *);) before use
It has been unsupported as long as I can remember, but is widely-used and mentioned in the Tips & Techniques section of the ObjectARX Developer's Guide
AcApDocManager::sendStringToExecute()
This function has the advantage of a few more options being available as arguments, mainly around where to execute the string (which document, and whether it be activated), and whether to echo the string to the command-line
::SendMessage()
This is a standard Win32 platform API and so can, in effect, be used to drive AutoCAD from an external client. It uses a structure to pass the string that is often a bit tricky to set up (it became a migration issue when we switched to Unicode, for example)
IAcadDocument::SendCommand()
This COM method is the only way (other than acedCommand() or acedCmd()) to execute a command synchronously from AutoCAD (and even then it may not be completely synchronous if requiring user input)
acedCommand()
This is the ObjectARX equivalent to (command), and is genuinely synchronous. Unfortunately (as mentioned earlier) there are issues with using it directly from a natively-registered command, so I'd recommend only using it from acedDefun()-registered commands (see the ObjectARX documentation and the below sample for more details)
VBA (some of which also applies to VB)
ThisDrawing.SendCommand
This is the same as IAcadDocument::SendCommand() from C++
SendKeys
This is just a simple technique to send key-strokes to the command-line
SendMessage
This is just the Win32 API mentioned above, but declared and called from VB(A)
So, now for some sample code...
ObjectARX sample code
The first can be dropped into an ObjectARX Wizard-defined project (I used Visual Studio 2005 and ObjectARX 2007). You'll need to make sure "COM-client" is selected and you name your project "SendingCommands" (or you search and replace to change the name in the below code to something you prefer).
The code creates points along a line (from 0,0 to 5,5), using different techniques to send the command to AutoCAD. I would, of course, use the proper ObjectARX APIs to do this (creating an AcDbPoint etc.) - I just used this as an example of a command that could be sent.
It creates the first point on load, using ads_queueexpr(), and defines commands (TEST1, TEST2, TEST3 and TEST4) for the subsequent tests (the last being an acedDefun()-registered command).- #include "StdAfx.h"
- #include "resource.h"
- #define szRDS _RXST("Adsk")
- extern "C" void ads_queueexpr( ACHAR *);
- //----- ObjectARX EntryPoint
- class CSendingCommandsApp : public AcRxArxApp {
- public:
- CSendingCommandsApp () : AcRxArxApp () {}
- virtual AcRx::AppRetCode On_kInitAppMsg (void *pkt) {
- // You *must* call On_kInitAppMsg here
- AcRx::AppRetCode retCode =AcRxArxApp::On_kInitAppMsg (pkt) ;
- return (retCode) ;
- }
- virtual AcRx::AppRetCode On_kUnloadAppMsg (void *pkt) {
- // You *must* call On_kUnloadAppMsg here
- AcRx::AppRetCode retCode =AcRxArxApp::On_kUnloadAppMsg (pkt) ;
- return (retCode) ;
- }
- virtual AcRx::AppRetCode On_kLoadDwgMsg(void * pkt) {
- AcRx::AppRetCode retCode =AcRxArxApp::On_kLoadDwgMsg (pkt) ;
- ads_queueexpr( _T("(command"_POINT" "1,1,0")") );
- return (retCode) ;
- }
- virtual void RegisterServerComponents () {
- }
- public:
- // - AdskSendingCommands._SendStringToExecTest command (do not rename)
- static void AdskSendingCommands_SendStringToExecTest(void)
- {
- acDocManager->sendStringToExecute(curDoc(), _T("_POINT 2,2,0 "));
- }
- static void SendCmdToAcad(ACHAR *cmd)
- {
- COPYDATASTRUCT cmdMsg;
- cmdMsg.dwData = (DWORD)1;
- cmdMsg.cbData = (DWORD)(_tcslen(cmd) + 1) * sizeof(ACHAR);
- cmdMsg.lpData = cmd;
- SendMessage(adsw_acadMainWnd(), WM_COPYDATA, NULL, (LPARAM)&cmdMsg);
- }
- // - AdskSendingCommands._SendMessageTest command (do not rename)
- static void AdskSendingCommands_SendMessageTest(void)
- {
- SendCmdToAcad(_T("_POINT 3,3,0 "));
- }
- // - AdskSendingCommands._SendCommandTest command (do not rename)
- static void AdskSendingCommands_SendCommandTest(void)
- {
- try {
- IAcadApplicationPtr pApp = acedGetIDispatch(TRUE);
- IAcadDocumentPtr pDoc;
- pApp->get_ActiveDocument(&pDoc);
- pDoc->SendCommand( _T("_POINT 4,4,0 ") );
- }
- catch(_com_error& e) {
- acutPrintf(_T("\nCOM error: %s"), (ACHAR*)e.Description());
- }
- }
- // ----- ads_test4 symbol (do not rename)
- static int ads_test4(void)
- {
- acedCommand(RTSTR, _T("_POINT"), RTSTR,_T("5,5,0"), RTNONE);
- acedRetVoid();
- return (RSRSLT);
- }
- } ;
- // ----------------------------------------------------------
- ACED_ARXCOMMAND_ENTRY_AUTO(CSendingCommandsApp, AdskSendingCommands, _SendStringToExecTest, TEST1, ACRX_CMD_TRANSPARENT, NULL)
- ACED_ARXCOMMAND_ENTRY_AUTO(CSendingCommandsApp, AdskSendingCommands, _SendMessageTest, TEST2, ACRX_CMD_TRANSPARENT, NULL)
- ACED_ARXCOMMAND_ENTRY_AUTO(CSendingCommandsApp, AdskSendingCommands, _SendCommandTest, TEST3, ACRX_CMD_TRANSPARENT, NULL)
- ACED_ADSCOMMAND_ENTRY_AUTO(CSendingCommandsApp, test4, true)
- // ----------------------------------------------------------
- IMPLEMENT_ARX_ENTRYPOINT(CSendingCommandsApp)
VBA sample code
This sample defines VBA macros that create points from 6,6 to 8,8, using techniques that mirror the ones shown in the ObjectARX sample.- Option Explicit OnPrivate Const WM_COPYDATA = &H4A
- Private Type COPYDATASTRUCT
- dwData As Long
- cbData As Long
- lpData As String
- End Type
- Private Declare Function SendMessage Lib "user32" Alias _
- "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal _
- wParam As Long, ByVal lParam As Any) As Long
- Public Sub SendMessageToAutoCAD(ByVal message As String)
- Dim data As COPYDATASTRUCT
- Dim str As String
- str = StrConv(message, vbUnicode) 'converts to Unicode
- data.dwData = 1
- data.lpData = str
- data.cbData = (Len(str) + 2)
- SendMessage(ThisDrawing.Application.hwnd, WM_COPYDATA, 0, data)
- End Sub
- Public Sub Test4()
- ThisDrawing.SendCommand("_point 6,6,0 ")
- End Sub
- Public Sub Test5()
- SendKeys("_point 7,7,0 ")
- End Sub
- Public Sub Test6()
- SendMessageToAutoCAD("_point 8,8,0 ")
- End Sub
|
|