Kean Walmsley


  • About the Author
    Kean on Google+

July 2014

Sun Mon Tue Wed Thu Fri Sat
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    








« Developing Autodesk Civil 3D applications with .NET | Main | Cancelling an active command in 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 On

Private 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

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e200d83435c19b53ef

Listed below are links to weblogs that reference Techniques for calling AutoCAD commands programmatically:

blog comments powered by Disqus

10 Random Posts