Metaprogramming with AutoCAD - Part 1

A recent comment on one of my F# articles got me thinking about this topic (thanks, Thomas! :-), so I thought I’d write a few posts on it. Next week is AU, and the week after that I’m attending a training class in Boston, so posts may be a little sparse over the coming weeks.

Metaprogramming  – according to the definition on Wikipedia – is the act of writing code that writes or manipulates other programs (or itself). But what is it really all about? The vast majority of programmers are actually metaprogramming without realizing it has such a fancy name.

To help understand metaprogramming, we’re going to focus on two ways of categorizing the various types of metaprogramming activity. Metaprogramming is usually either static or dynamic and homogeneous or heterogeneous (there are other classifications, but we’re not going to worry about those in this article).

  • Static = at compile-time
  • Dynamic = at runtime
  • Homogeneous = the same output language is used as on the input
  • Heterogeneous = a different output language is used to the input language

The most obvious form of metaprogramming is to create machine-code using a compiler (or even an interpreter) for a high-level language. This is a static, heterogeneous act of metaprogramming (although using an interpreter would presumably make this dynamic). Here are a few more interesting examples of metaprogramming I’ve used myself:

  • C++ templates or pre-processor macros to generate lower-level code at compile-time
    • Static (compile-time) and heterogeneous (the generation language is different from the output language)
  • Generation of a series of LISP expressions that are evaluated at runtime
    • Dynamic (runtime) and homogeneous (the generation and output languages are the same)
  • Programmatic creation (perhaps using LISP, C#, VB(A) or C++) of an AutoCAD script that is then executed
    • Dynamic and heterogeneous
  • Composing a SQL statement on-the-fly and using it to query a database
    • Dynamic and heterogeneous

The focus of this series of posts is on dynamic metaprogramming, which allows the modification of code at runtime (rather than compile-time). A further, somewhat more complex, example of dynamic metaprogramming is to redefine functions at runtime (something that’s possible with LISP, for instance), which allows applications to evolve, such as when developing expert systems that “learn” over time.

LISP was really one of the early programming environments that enabled metaprogramming, primarily through its ability to evaluate expression using (eval) and to redefine functions at runtime using (defun). This has been immensely valuable to AutoLISP programmers over the years. When AutoLISP was first introduced it was purely an interpreted language, so dynamic metaprogramming was provided pretty much automatically. In order for metaprogramming to work after the introduction of Visual LISP (which is fundamentally a compiled environment, albeit to an intermediate language), a runtime component supporting dynamic compilation was needed and provided. Very little change was needed in AutoLISP code, although in some rare cases (defun-q) now needs to be used, if it’s important to provide access to the internal representation of functions.

We’ll see this is a common thread for dynamic metaprogramming: by definition you either need to be working in an interpreted environment or will need to have a runtime component available that supports some kind of compilation (probably JIT). Visual LISP provides this, as does VBA and .NET (via the CLR).

Back to AutoLISP: one very common activity is to interpret a string using (read) and then call (eval) on it. The string may have been stored in a drawing, a text file, an external database, or generated on-the-fly. For example:

Command: (eval (read "(* 5 (getvar \"ZOOMFACTOR\"))"))

300

VBA also has native support for dynamic metaprogramming via the Eval() function:

Eval "MsgBox ThisDrawing.Name"

VB6 doesn't have direct support for Eval(), but it seems you can make use of it either by embedding a Script Control or by calling across to the VBA runtime (Googling "VB6 Eval" returned a number of options). I don't know whether it's possible to evaluate and make use of AutoCAD-specific variables - such as ThisDrawing - when using these techniques, however.

Metaprogramming with .NET is not quite so automatic, but is altogether possible, as I’ll show in my next post.

November 19, 2007 in AutoCAD, AutoCAD .NET, Visual Basic & VBA, Visual LISP | Permalink | Comments (6) | TrackBack

Free webcast: "AutoCAD: .NET for LISP Programmers"

On October 18th we're delivering a free AutoCAD development-oriented webcast entitled "AutoCAD: .NET for LISP Programmers".

Check here for our overall API schedule, or go here to register directly. These sessions are typically held at the end of the working day in Europe (5pm CEST) which is the beginning of the day on the West Coast of the US (8am PDT).

As usual, I'll post a link to the recording, once it's available.

October 12, 2007 in AutoCAD, AutoCAD .NET, Training, Visual LISP | Permalink | Comments (5) | TrackBack

Loading the right version of an ObjectARX module into 32- or 64-bit AutoCAD

This question has come in from a number of developers...

How can I tell when my application is running inside a 64-bit version of AutoCAD?

As mentioned in this previous post, AutoCAD 2008 installs as native 64-bit binaries on a supported 64-bit OS, just as 32-bit binaries get installed on a supported 32-bit OS.

A minor complication is that certain of our AutoCAD-based products do not yet have native 64-bit versions. Our Engineering teams are working on this, but in the meantime, your application might well be working inside a 32-bit Autodesk product on a 64-bit OS.

So how do we know whether we're on a 32- or 64-bit platform (i.e. AutoCAD)?

The ideal would be to have a simple system variable (just like we have ACADVER for the version of AutoCAD), which can be queried from any environment. Unfortunately this has not (as yet) been provided, so we have to look for another approach, for now.

The good news is that .NET applications generally shouldn't care - the same binary will work on both 32- and 64-bit platforms. The same for LISP, but then people often use LISP loaders to load ObjectARX modules - which most certainly do care - so my feeling is that this problem will most commonly be faced from LISP.

Before talking about getting the information from LISP, let's talk a little about ObjectARX, first. ObjectARX modules are built specifically as 32- or 64-bit versions. The version you load will depend on the host executable (the AutoCAD platform) you're working in, not on the OS. A 32-bit module will simply not load in a 64-bit version AutoCAD and vice-versa. The way most professional developers make sure the right version of their module is loaded, is to set up demand-loading keys appropriately from their installers, which AutoCAD uses to locate the appropriate modules and load them.

A module doesn't usually need to know whether it is 32- or 64-bit (and with polymorphic types in ObjectARX you should be able to build both versions off the same source code). That said - you might want to enable certain memory-intensive operations from your 64-bit modules but not from your 32-bit versions (for example), so one way is simply to declare a pointer and check its size (thanks to Gopinath Taget, from our DevTech team in San Rafael, for proposing this solution):

Adesk::IntPtr ptr;

int ptrSize = sizeof( ptr );

If ptrSize is 4, then you're in a 32-bit module - if ptrSize is 8, you're in a 64-bit module.

This could clearly also be exposed as a LISP-callable function (using acedDefun()), which is a solution for people who create their own ObjectARX modules but clearly not viable for people who don't.

So now back to our common scenario of people using LISP to load the correct version of an ObjectARX module: in the absence of a handy system variable, what do we do?

I thought about this for a while, and booted around some strange ideas such as using COM from LISP to query file attributes from AutoCAD binaries (yeech), and eventually decided that the best approach was simply to try to load a module, and if it fails, try a 64-bit specific name.

Here's the technique - you would use this function as a replacement for (arxload "myapp"):

(defun myarxload (fn / fn64)

  ;(princ (strcat "\nLoading " fn))

  (if

    (vl-catch-all-error-p

      (vl-catch-all-apply 'arxload (list fn))

    )

    (progn

      (setq fn64 (strcat fn "x64"))

      ;(princ (strcat "\nLoading " fn64))

      (if (findfile fn64)

        (arxload fn64)

      )

    )

  )

  (princ)

)

This code assumes a few things...

  • We pass in the module name without the extension (as we append "x64" to the filename)
  • We have used "x64" as a suffix for 64-bit versions of our modules (e.g. "AdskMyAppx64.arx")
    • I'm not aware of any convention for this... we simply use the same module names inside AutoCAD (which removes the need for code such as this, in any case)

I'd be very interested to hear the experiences and suggestions of readers of this blog on the subject. This topic has come up a few times and perhaps more of you have comments that would help.

April 20, 2007 in AutoCAD, AutoCAD .NET, ObjectARX, Visual LISP | Permalink | Comments (8) | TrackBack

Protecting intellectual property in AutoCAD application modules

This is an interesting topic – and one that I’m far from being expert in – so it would be great if readers could submit comments with additional information.

Intellectual property protection is a major concern for software developers, and issues that are seen today with .NET languages have been troubling AutoCAD developers since the introduction of AutoLISP.

So, what are these issues?

As a professional software developer, if you ship source-code to your customers there is substantial risk of it being borrowed or stolen for use in other unlicensed situations. This is true if you ship the actual source code used to build your modules, or if the “compiled” modules are actually not fully compiled, but (for example) stored in a CPU-independent, intermediate language that can quite easily be decompiled and have source code reconstituted or reverse-engineered (albeit without comments and usually without the original symbol names).

ObjectARX

This is much less of an issue with languages that are compiled to CPU-specific executable or machine code, such as for ObjectARX (and before it, ADS): the output from a C++ compiler does not, for instance, included any source code, unless you choose to ship debugging-related files such as PDBs (files which include information about function prototypes – information that is considered by some software providers as intellectual property in and of itself).

LISP

Intellectual property protection was historically a big issue with AutoLISP: in its original incarnation (i.e. before the integration of Visual LISP) LISP code was always interpreted by AutoCAD. Interpreted languages do not have a compilation phase – the code is “made sense of” by the interpreter at runtime. There were, however, things that could be done to obfuscate and protect the code before distribution. Most developers used these two tools on their code before shipping it:

The Kelvinator – this nifty tool did a few things to obfuscate AutoLISP code:

Removed unnecessary whitespace

Stripped out comments

Mangled “internal” symbols (those not exposed as external variables or function names)

The Kelvinator was apparently written by Kelvin R. Throop, along with a number of other AutoLISP utilities. I wasn't around at the time (it was written in the late 80s and I joined in the mid 90s), so without ever having met Kelvin I do wonder whether he really exists, or whether this is just another use of the pseudonym used in a number of Science Fiction stories over the 20 or so years preceding the Kelvinator's development. This theory seems to be confirmed by this memo in the Autodesk File on John Walker's Fourmilab site. But if the real Kelvin R. Throop could step forward (or if someone else could confirm his existence or identity), then I'd willingly present my humble apologies. :-)

In terms of its functionality the Kelvinator was really quite effective: the only step that could be effectively reversed was the whitespace removal – by any LISP pretty printer. Getting meaningful comments & function/variable names back was nigh on impossible (although you could work out what certain variables were used for in a particular algorithm pretty easily).

Protect.exe

This tool used very lightweight encryption to stop LISP files from being read by plain ASCII tools. From memory, I think a bit was reversed in each ASCII character – it was that level of encryption – which also meant that unprotection tools were also available (including one of the “c” versions of R13, if I remember correctly, which would print protected LISP as plain text to the AutoCAD text window :-).

Most professional developers at the time would Kelvinate and then Protect their LISP code before distributing it. Other options were to use LISP compilation – an early compiler was available pre-R13 versions, and Vital LISP (which later became Visual LISP) provided compilation capabilities.

Visual LISP changed the game for LISP developers: it allowed them to properly compile LISP into a protected format. Visual LISP supports two modes of compilation: each LISP file can be compiled to a format called FAS, which can be loaded directly into AutoCAD, and these can in turn be packaged into VLX modules. Originally Visual LISP allowed compilation to ARX, but these were essentially copies of the VL runtime with the compiled code stored as a resource, and clearly it’s more efficient to not force distribution of the runtime when it's in any case a standard component.

Over the years I've not come across anyone raising concerns about the security of the FAS or VLX formats, which leads me to believe they're a "secure" way to protect and distribute LISP modules (which ultimately means the effort required to make sense of the underlying logic of the application would be too high for it to be considered a useful practice for those developers unscrupulous enough to otherwise attempt it).

VB6

The VB6 IDE allows compilation to executable code – albeit code that requires a runtime component – and, once again, to the best of my knowledge this code is considered "secure” (see above note).

VBA

The VBA component embedded in AutoCAD allows password protection of DVB files (see the Protection tab on the Project Properties dialog in the VBA IDE). This password protection uses standard encryption to lock away source code from prying eyes. I understand that cracking tools are available for protected VBA modules, but there is no secret back-door – my team occasionally gets asked to help access source code stored in protected DVB files (as the person working on the code left the company without telling anyone the password for the module – or at least that’s what we’re told :-). Ultimately there’s nothing we can do to help in these cases, unfortunately. The cracking tools that are available seem to work on a brute force principle – they will cycle through possible passwords until one works – so if you want to really protect your code I’d recommend using a nice, long password.

.NET

So – life is basically good for LISP, ObjectARX, VBA and VB6 applications… what about .NET?

One of the major concerns shared by many .NET developers is around code security. Managed code gets compiled into assemblies that contain Microsoft Intermediate Language (MSIL) - basically CPU-independent instructions - in addition to metadata describing types, members and references to code in other assemblies. At runtime the MSIL gets converted to CPU-specific instructions by a just-in-time (JIT) compiler.

These assemblies are quite easy to disassemble into something resembling the original source code (albeit – once again – without comments or meaningful variable names, but internal function names and types get maintained).

Just as the Kelvinator was available for AutoLISP, there are obfuscation tools available for .NET languages. Visual Studio ships with one called the Dotfuscator Community Edition: this tools works on .NET assemblies, reducing the ease with which someone could reverse-engineer the source code.

At a basic level the tool strips namespace and member names - replacing them with symbols such as "a", "b", "c", ... - but it can only do so much to hide the logic of the code's execution, especially if there is no great dependency on functions and sub-routines (the more linear the code, the easier it is to understand, in my brief experience of analysing obfuscated code). That said, the Dotfuscator tool does have a number of options I haven't explored in depth, so it may well provide additional, helpful capabilities.

From taking a cursory look into what information is available by default in .NET assemblies, it's become clear to me that this should be an area of concern for anyone serious about protecting their source code investment. I strongly recommend that any professional software developer working with .NET spend time investigating obfuscation technologies for .NET assemblies. This guide, although a little dated, does seem to provide a fairly good background to .NET obfuscation, as well as presenting a number of different vendors' offerings.

In my next post I'm going to take a look at the Reflector tool - a very useful tool that can be used to disassemble .NET assemblies - and discuss how it might be used to help further one's understanding of .NET development.

January 16, 2007 in AutoCAD, AutoCAD .NET, ObjectARX, Visual Basic & VBA, Visual LISP | Permalink | Comments (10) | TrackBack

Linking Circles, Part 5: Animating the snake

As promised in the last post, here's some old LISP code I used to demo the original circle linking application. I've changed it slightly to not only move the snake in 2D, now the Z-value of the lead object is set to be a fraction of the object's Y-value. This won't actually change what's seen in a standard overhead view... if you want to revert to 2D-only movement of the snake, simply remove "zval" from the two calls to the MOVE command.

I should also add that while the snake will move through 3D by default, I didn't change the code that prompts the user to select the screen area: as you drag across to the second point, it "rubber-bands" in screen coordinates, even if the point that gets selected is returned in the coordinates of the current UCS. As this is primarily a test-bed, I didn't see the point in devoting time to address this quirk - the snake still moves, which is the main thing.

The performance will, of course, depend on your system and on the length and composition of the snake (whether the snake is made up of circles or spheres with materials being display realistically, for instance).

The LISP code depends on getting the circle's centre point (getting the value of group-code 10), so while it will work on all sorts of objects, it won't work on 3D solids. So you should create a chain with a circle at the head for this to work properly.

The file is available here for download.

Here's the LISP code:

    1 ; Create a chain of linked circles, by either using LINK

    2 ; on a number of CIRCLEs or AUTOLINK.

    3 ; Type SNAKE, select the head of the chain, and then enter

    4 ; a distance for the simulation to run over (this version

    5 ; also assumes that 0,0 is at the bottom left of the screen)

    6 ;

    7 ; The function plotted is just a sine wave,

    8 ; using the full height of the screen

    9 ;

   10 ; Written by Kean Walmsley 26/03/96, for messing about

   11 ; purposes only

   12 ;

   13 ; Updated 1/12/06, adding support for 3D, and formatting

   14 ; text to fit the width of the Through_the_Interface blog

   15

   16 (defun asdk_snake (en pt1 pt2 screens moves waves

   17                 / ocmd oblp undoctl undoon undoone

   18                   undoall screenno maxdeg distance vhgt en

   19                   xval yvalybase xinc xmax xbot pt1 pt2

   20                   )

   21   (setq ocmd    (getvar "CMDECHO")

   22         oblp    (getvar "BLIPMODE")

   23         undoctl  (getvar "UNDOCTL")

   24         undoon  (= 1 (logand 1 undoctl))

   25         undoone  (= 2 (logand 2 undoctl))

   26         undoall  (= 4 (logand 4 undoctl))

   27   )

   28   (setvar "CMDECHO" 0)

   29   (setvar "BLIPMODE" 0)

   30   (if undoon

   31     (command "_.UNDO" "_CONTROL" "_NONE")

   32   )

   33   (setq screenno 0

   34         maxdeg  (* pi waves)

   35         distance (abs (- (car pt1) (car pt2)))

   36         vhgt    (abs (- (cadr pt1) (cadr pt2)))

   37         ybase    (/ (+ (cadr pt1) (cadr pt2)) 2)

   38         xbot    (if (< (car pt1) (car pt2))

   39                   (car pt1)

   40                   (car pt2)

   41                 )

   42         xmax    (+ distance xbot)

   43         xinc    (/ distance moves)

   44         xval    xbot

   45         yval    (+ ybase (* (sin (* maxdeg

   46                                     (/ (- xval xbot)

   47                                         (- xmax xbot)

   48                                     )

   49                                   )

   50                               )

   51                               (/ vhgt 2.0)

   52                           )

   53                 )

   54         zval    (/ yval 4)

   55   )

   56   (while (< screenno screens)

   57     (while (< xval xmax)

   58       (command "_.MOVE" en ""

   59               (cdr (assoc 10 (entget en)))

   60               (list xval yval zval)

   61       )

   62       (setq xval (+ xval xinc)

   63             yval (+ ybase (* (sin (* maxdeg

   64                                     (/ (- xval xbot)

   65                                         (- xmax xbot)

   66                                     )

   67                                   )

   68                             )

   69                             (/ vhgt 2.0)

   70                           )

   71                 )

   72             zval (/ yval 4)

   73       )

   74     )

   75     (setq screenno (1+ screenno))

   76     (while (and (>= xval xbot) (< screenno screens))

   77       (command "_.MOVE" en ""

   78               (cdr (assoc 10 (entget en)))

   79               (list xval yval zval)

   80       )

   81       (setq xval (- xval xinc)

   82             yval (- ybase (* (sin (* maxdeg

   83                                     (/ (- xval xbot)

   84                                         (- xmax xbot)

   85                                     )

   86                                   )

   87                             )

   88                             (/ vhgt 2.0)

   89                           )

   90                 )

   91             zval (/ yval 4)

   92       )

   93     )

   94     (setq screenno (1+ screenno))

   95   )

   96   (if undoone

   97     (command "_.UNDO" "_ONE")

   98     (if undoall

   99       (command "_.UNDO" "_ALL")

  100     )

  101   )

  102   (setvar "CMDECHO" ocmd)

  103   (setvar "BLIPMODE" oblp)

  104   (terpri)

  105 )

  106

  107 (defun C:SNAKE (/ es pt1 pt2 screens moves waves)

  108   (if (setq es (entsel "\nSelect the snake's head: "))

  109     (if

  110       (setq pt1

  111         (getpoint "\nSelect first corner of screen area: ")

  112       )

  113       (if

  114         (setq pt2

  115               (getcorner

  116                 pt1

  117                 "\nSelect second corner of screen area: "

  118               )

  119         )

  120         (progn

  121           (if (not asdk_screens) (setq asdk_screens 2))

  122           (if (not asdk_moves)  (setq asdk_moves  100))

  123           (if (not asdk_waves)  (setq asdk_waves  4))

  124           (if

  125             (not

  126               (setq screens

  127                     (getint

  128                       (strcat

  129                         "\nEnter number of times"

  130                         " to cross screen <"

  131                         (itoa asdk_screens)

  132                         ">: "

  133                       )

  134                     )

  135               )

  136             )

  137             (setq screens asdk_screens)

  138             (setq asdk_screens screens)

  139           )

  140           (if

  141             (not

  142               (setq moves

  143                   (getint

  144                     (strcat

  145                       "\nEnter number of moves"

  146                       " to cross screen <"

  147                       (itoa asdk_moves)

  148                       ">: "

  149                     )

  150                   )

  151               )

  152             )

  153             (setq moves asdk_moves)

  154             (setq asdk_moves moves)

  155           )

  156           (if

  157             (not

  158               (setq waves

  159                     (getint

  160                       (strcat

  161                         "\nEnter number of waves for snake <"

  162                         (itoa asdk_waves)

  163                         ">: "

  164                       )

  165                     )

  166               )

  167             )

  168             (setq waves asdk_waves)

  169             (setq asdk_waves waves)

  170           )

  171           (asdk_snake (car es) pt1 pt2 screens moves waves)

  172         )

  173       )

  174     )

  175   )

  176   (princ)

  177 )

December 6, 2006 in AutoCAD, AutoCAD .NET, Visual LISP | Permalink | Comments (0) | TrackBack

Controlling interactive polyline creation - Part 1

I received this interesting question through by email over the weekend:

“How can I ask AutoCAD to let the user to draw a Polyline (just like the user pushed Polyline button) and then just after finishing drawing get the ObjectID of that entity? Is it possible?”

This is a fun one, as there are a number of different approaches to take. I’m going to outline (or just go ahead and implement, depending on the complexity) the various possibilities – taking the first two today and the others in (a) subsequent post(s).

The idea is to define our own command, say MYPOLY, and make sure we’re left with execution control – and the object ID/entity name – after the user has defined a polyline in the drawing window.

There are two basic ways to solve this problem, and each of these has two variants. The initial (and major) choice is whether to let the standard AutoCAD PLINE command provide the user-interface for creating the polyline. Doing so is certainly simpler, assuming you want the user to have access to all the polyline options. That said, you may actually prefer to limit the user’s options (for example, not to allow width or arc segments), in which case the approach to implement the UI yourself would be better suited.

So, now to tackle the first two options...

From the MYPOLY command, we want to call the PLINE command. Once the command has completed, we want to make sure our code is being executed, which will allow us to get the polyline's object ID.

This is where we get our next choice: how to find out when the PLINE command has ended. The first option (and the one typically used from Visual LISP for this type of task) is to loop until the command is finished, checking either CMDACTIVE or CMDNAMES. This is important, as polylines can have an arbitrary number of vertices, so we don’t know exactly how long the command will take to complete (in terms of how many “pauses” the command will have, requesting a point selection from the user).

Here’s how I’d do this in LISP (the technique is published on the ADN site in this DevNote: Waiting for (command) to finish in AutoLISP):

(defun C:MYPOLY()

  (command "_.PLINE")

  (while (= (getvar "CMDNAMES") "PLINE")

    (command pause)

  )

  (princ "\nEntity name of polyline: ")

  (princ (entlast))

  (princ)

)

And here's what happens when we execute this code:

Command: mypoly

_.PLINE

Specify start point:

Current line-width is 0.0000

Specify next point or [Arc/Halfwidth/Length/Undo/Width]:

Specify next point or [Arc/Close/Halfwidth/Length/Undo/Width]:

Specify next point or [Arc/Close/Halfwidth/Length/Undo/Width]: a

Specify endpoint of arc or

[Angle/CEnter/CLose/Direction/Halfwidth/Line/Radius/Second pt/Undo/Width]:

Specify endpoint of arc or

[Angle/CEnter/CLose/Direction/Halfwidth/Line/Radius/Second pt/Undo/Width]:


Command:

Entity name of polyline: <Entity name: 7ef90048>

The second option to wait for the command to complete is to register a callback handling the CommandEnded() event.

Here’s some C# code showing this approach:

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;


namespace MyPlineApp

{

  public class MyPlineCmds : IExtensionApplication

  {

    // Flag used to check whether it's our command

    // that launched PLINE

    private static bool myCommandStarted = false;


    public void Initialize()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      doc.CommandEnded += new

        CommandEventHandler(

          plineCommandEnded

        );

    }


    public void Terminate()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      doc.CommandEnded -= new

        CommandEventHandler(

          plineCommandEnded

        );

    }


    [CommandMethod("MYPOLY")]

    public void MyPoly()

    {

      // Set the flag and launch PLINE

      myCommandStarted = true;

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      doc.SendStringToExecute("_PLINE ",false,false,false);

    }


    private void plineCommandEnded(

      object sender,

      CommandEventArgs e)

    {

      if (myCommandStarted

        && e.GlobalCommandName.ToUpper() == "PLINE")

      {

        // We're just performing a simple check, so OK..

        // We could launch a follow-on command, if needed

        Document doc =

          Application.DocumentManager.MdiActiveDocument;

        PromptSelectionResult lastRes =

          doc.Editor.SelectLast();

        if (lastRes.Value != null

          && lastRes.Value.Count == 1)

        {

          doc.Editor.WriteMessage(

            "\nPolyline entity is: "

            + lastRes.Value[0].ObjectId

          );

        }

        myCommandStarted = false;

      }

    }

  }

}

And here's what happens when we execute this code:

Command: mypoly

Command:

Specify start point:

Current line-width is 0.0000

Specify next point or [Arc/Halfwidth/Length/Undo/Width]:

Specify next point or [Arc/Close/Halfwidth/Length/Undo/Width]:

Specify next point or [Arc/Close/Halfwidth/Length/Undo/Width]: a

Specify endpoint of arc or

[Angle/CEnter/CLose/Direction/Halfwidth/Line/Radius/Second pt/Undo/Width]:


Polyline entity is: (2130247840)

That's where I'll stop it there for now… the other two options I want to look at both revolve around defining your own user-interface. The first will simply collect a sequence of points from the user using GetPoint(), the second uses a Jig to do the same thing (I haven’t yet decided whether to actually implement this last one or not – we’ll see how much time I have later in the week).

November 8, 2006 in AutoCAD, AutoCAD .NET, Visual LISP | Permalink | Comments (2) | TrackBack

Passing arguments to .NET-defined commands

Another case of planets aligning: two different people have asked me this question in two days...

It's quite common for commands to require users to provide additional input during execution. This information might be passed in via a script or a (command) from LISP, or it may simply be typed manually or pasted in by the user.

The fact is, though, that commands don't actually take arguments. It may seem like they do, but they don't. What they do is ask for user input using dialogs or the command-line.

Here are a few tips on how to support passing of information into your commands...

Define a version of your command that asks for input via the command-line

It's very easy to define beautiful UIs in .NET applications. You absolutely should do so. But it's also helpful to provide an alternative that can be called via the command-line and from scripts. I'd suggest a couple of things here for your commands:

  • Define a standard version (e.g. "MYCOMMAND") and a command-line version ("-MYCOMMAND"). It's common to prefix command-line versions of commands in AutoCAD with a hyphen (see -PURGE vs. PURGE, for instance).
  • An alternative is to check for the values of FILEDIA and CMDDIA system variables - see the AutoCAD documentation on these commands to understand what effect they're meant to have on commands.

When implementing a command-line version of your command, you simply use the standard user input functions (GetXXX()) available in the Autodesk.AutoCAD.EditorInput namespace. Here's some VB.NET code showing this:

    <CommandMethod("TST")> _

    Public Sub Test()

        Dim ed As Editor

        ed = Application.DocumentManager.MdiActiveDocument.Editor

        Dim name As PromptResult = ed.GetString(vbCr + "Enter name: ")

        ed.WriteMessage("You entered: " + name.StringResult)

    End Sub

When it's run, you get this (typed text in red):

Command: tst

Enter name: Hello

You entered: Hello

Command:

By the way, I tend to put a vbCr (or "\n" in ObjectARX) in front of prompts being used in GetXXX() functions, as it's also possible to terminate text input with a space in AutoCAD: this means that even is a space is entered to launch the command, the prompt string displays on a new line.

Define a LISP version of your command

The <LispFunction> attribute allows you to declare .NET functions as LISP-callable. A very good technique is to separate the guts of your command into a separate function that is then called both by your command (after it has asked the user for the necessary input) and by the LISP-registered function, which unpackages the arguments passed into it.

To understand more about how to implement LISP-callable functions in .NET, see this previous post.

September 1, 2006 in AutoCAD, AutoCAD .NET, Commands, Visual LISP | Permalink | Comments (5) | TrackBack

Calling DIMARC from Visual LISP

This is an interesting little problem that came in from Japan last week. Basically the issue is understanding how to call the DIMARC command programmatically from LISP using (command).

The DIMARC command was introduced in AutoCAD 2006 to dimension the length of arcs or polyline arc segments. What's interesting about this problem is that it highlights different aspects of entity selection in LISP.

Firstly, let's take a look at the DIMARC command (I've put the keyboard-entered text in red below):

Command: DIMARC

Select arc or polyline arc segment:

Specify arc length dimension location, or [Mtext/Text/Angle/Partial/Leader]:

Dimension text = 10.6887

The first prompt is for the arc or polyline arc segment, the second is for the dimension location (we're not going to worry about using other options).

The classic way to select an entity is to use (entsel):

Command: (entsel)

Select object: (<Entity name: 7ef8e050> (22.1694 32.2269 0.0))

This returns a list containing both the entity-name and the point picked when selecting the entity. This is going to be particularly useful in this problem, as the DIMARC command needs to know where the point was picked: this is because it has to support picking of a "complex" entity, as it works not only on arcs but on arc segments of polylines. If you use the classic technique of using (car)stripping off the point, to just pass in the entity name, it fails:

Command: DIMARC

Select arc or polyline arc segment: (car (entsel))

Select object: <Entity name: 7ef8e050>

Command:

Whereas just passing the results of (entsel) works fine:

Command: DIMARC

Select arc or polyline arc segment: (entsel)

Select object: (<Entity name: 7ef8e050> (22.2696 32.2269 0.0))

Specify arc length dimension location, or [Mtext/Text/Angle/Partial/Leader]:

Dimension text = 24.3844

So it's clear we need to keep the point in there. So we know what we have to do to select arc entities - we can pass in the results of (entsel).

Polyline entities are trickier. You can pass in the entity name, with or without the point, and the command won't work:

Command: DIMARC

Select arc or polyline arc segment: (entsel)

Select object: (<Entity name: 7ef8e058> (51.0773 29.5726 0.0))

Object selected is not an arc or polyline arc segment.

Select arc or polyline arc segment:

But we can pass in the point picked by (entsel), and let the DIMARC command do the work of determining whether the segment picked was actually an arc or not:

Command: DIMARC

Select arc or polyline arc segment: (cadr (entsel))

Select object: (52.6305 29.5726 0.0)

Specify arc length dimension location, or [Mtext/Text/Angle/Partial/Leader]:

Dimension text = 9.4106

So now we're ready to put it all together.

[ Note: The below example does not take into consideration the user cancelling from (getpoint) or (entsel) - that is left as an exercise for the reader. ]

(defun c:myDimArc(/ en pt ed)

  (setq en (entsel "\nSelect arc or polyline arc segment: ")

        pt (getpoint "\nSpecify arc length dimension location: ")

        ed (entget (car en))

  )

  ; If the object selected is a 2D polyline (old or new)

  ; then pass the point instead of the entity name

  (if (member (cdr (assoc 0 ed)) '("POLYLINE" "LWPOLYLINE"))

    (setq en (cadr en))

  )

  (command "_.DIMARC" en pt)

  (princ)

)

This is just an example of how you need to call the DIMARC command programmatically - with this technique you clearly lose the visual feedback from the dragging of the entity during selection (what we call the "jig" in ObjectARX). And this is ultimately an unrealistic example - you're more likely to need to call DIMARC automatically for a set of entities in a drawing than just define a simple command to request user input and pass it through.

One thing this example does demonstrate is that different commands have different needs in terms of entity selection information (some just need a selection set or an entity name, some need more that than).

August 28, 2006 in AutoCAD, Commands, Dimensions, Visual LISP | Permalink | Comments (5) | TrackBack

Handling Automation errors in Visual LISP

Since Visual LISP was introduced, developers have taken advantage of its ability to call COM Automation interfaces (whether AutoCAD's or other applications'). The addition of this functionality to the LISP platform created many new development possibilities - previously you were able to call through to ObjectARX applications defining LISP functions, but enabling Automation access from LISP suddenly allowed developers to access any other application adopting the COM standard its API, such as Microsoft Excel.

A quick note on error handling in LISP...

Traditionally LISP applications have defined their own (*error*) function to trap errors during execution. During this function they often report the value of ERRNO - used by AutoCAD to tell the LISP app what kind of error has occurred - which in turn can help the user or developer pin down the cause of the problem. This is fine, but this kind of global error-trap doesn't make it easy to resume execution after the error.

When using Automation interfaces things are different. Automation clients generally need to trap exceptions as they occur, rather than defining a global error-handling function. Visual LISP enables this with a very helpful function named (vl-catch-all-apply).

(vl-catch-all-apply) is like the (apply) function, in that it takes a symbol representing the function name to be called as the first argument, followed by the various arguments to be passed to that function in the form of a list. (vl-catch-all-apply) executes the function call, and does its best to trap any errors that occur during it. The main difference between the function signatures of (apply) and (vl-catch-all-apply) is with the return value, which will either be the return value of the function call, if all works well, or an error object that can then be queried for additional information.

Let's take a simple example that doesn't involve Automation, which I've basically stolen from the Visual LISP online help. The following code asks the user to enter two numbers using my favourite LISP function, (getreal) :-), and then tries to perform a division. We check result of the division with (vl-catch-all-error-p), to see whether it succeeded or not: if it didn't we then use (vl-catch-all-error-message) to get an error string telling us what happened.

(defun c:div (/ first second result)

  (setq first (getreal "\nEnter the first number: ")

        second (getreal "\nEnter the second number: ")

        result (vl-catch-all-apply '/ (list first second))

  )

  (if (vl-catch-all-error-p result)

    (princ (strcat "\nCaught an exception: "

                   (vl-catch-all-error-message result)

           )

    )

    (princ (strcat "\nSuccess - the result is "

                   (rtos result)

           )

    )

  )

  (princ)

)

Here's what happens when you run the command:

Command: div

Enter the first number: 50

Enter the second number: 2

Success - the result is 25.0000

Command: div

Enter the first number: 50

Enter the second number: 0

Caught an exception: divide by zero

So how does this technique apply to Automation calls? Let's take a look at another piece of code, this time calling a function to check the interference between two solids. This code defines a CHECKINT command that asks for two solids to be selected. Assuming two solids are selected, it will call the CheckInterference Automation method, specifying that any resulting intersection should be created as its own solid.

A lot of work was done to enhance the solids capabilities in AutoCAD 2007, so although this function works find in 2007, in prior releases it would often return modelling errors. This code allows it to fail gracefully even in prior releases.

; Helper function to check whether an entity is a solid

(defun is-solid-p (ename)

  (= (cdr (assoc 0 (entget ename))) "3DSOLID")

)

; The CHECKINT command

(defun c:checkint (/ first second e1 e2 result)

  (vl-load-com)

  (setq first  (entsel "\nSelect the first solid: ")

        second (entsel "\nSelect the second solid: ")

  )

  (if (and first

          second

          (is-solid-p (setq e1 (car first)))

          (is-solid-p (setq e2 (car second)))

      )

    (progn

      (setq result (vl-catch-all-apply

                      'vla-CheckInterference

                      (list (vlax-ename->vla-object e1)

                            (vlax-ename->vla-object e2)

                            :vlax-true

                      )

                  )

      )

      (if (vl-catch-all-error-p result)

        (princ (strcat "\nCaught an exception: "

                      (vl-catch-all-error-message result)

               )

        )

        (progn

          (princ "\nSuccess!")

          ; Highlight the newly created intersection solid

          (vla-Highlight result :vlax-true)

          (vlax-release-object result)

       )

      )

    )

    (princ "\nMust select two solids.")

  )

  (princ)

)

August 16, 2006 in AutoCAD, Visual LISP | Permalink | Comments (0) | TrackBack

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 decla