Training

Visual LISP: Adding Defaults to Prompts

In my experience, nothing makes AutoCAD® users happier than a custom application (or macro or function) that saves them time and increases their productivity. That usually means the application provides an easy-to-use, intuitive interface and point-and-click usability. Create its opposite—a custom application that requires a lot of data entry, or one that does not take advantage of all the features available in a typical AutoCAD command—and not only will you never hear the end of it, your application won't be widely adopted. That's because Autodesk has set a high standard for its own development efforts and for those of third-party developers that create custom add-ons for its software. But why not meet that challenge—even if you are just writing code and creating macros for your own use. Why not figure out how to make your custom functions flow in the same manner as a spiffy built-in AutoCAD routine? This tidbit explores how to program default values into your input prompts so that they look and function just like normal AutoCAD commands.

Preparation Is the Key

Adding defaults to your programs is not difficult but it does involve a little thinking ahead and just a bit more coding. The first problem you have to solve is where the default comes from and where it will be stored when the function is not running. The default values can be carried in global symbols, or they can be stored in the drawing, for example, in a system variable or attached to an entity.

Each approach has its advantages and disadvantages. Choose the one that complements the nature of the application you're developing and its data. To illustrate, a simple routine like the one covered in this article uses the AutoCAD system variables to retrieve the last circle radius and offset values used. On the other hand, if you're developing a more complex application that is carrying design data, you might go with the Visual LISP™ dictionary data-storage options or save the variable data as extended data attached to an entity object.

In any case, keep in mind that the time you save in using defaults in your program more than compensates for the time you spend planning how to use them. Obviously, the more complex the application, the more complex the data warehousing might be. Always consider how the function is to store data between runs before programming even a single line of code. That way when you need to stash away a variable, you already know how you're going to do it.

Building the Expression

Once you know where your default values are coming from, how do you use them in your programs? Essentially you use the existing Visual LISP input expressions such as GETINT, GETREAL, GETDIST, and so forth and modify the prompts to include the default value. In most cases you'll modify prompts using the STRCAT (string concatenation) and a data-conversion subroutine such as ITOA (integer to ASCII) or RTOS (real to string). After the input expression the resulting value is immediately tested to see if input was actually supplied. If so, the input value replaces the saved variable value and the program continues to run.

The following code snippet illustrates this basic process. A variable called R1 is a distance value and is to be used as the default in a GETDIST expression asking for an inner radius.

(SETQ Tmp (GETDIST (STRCAT "\nInner radius <" (RTOS R1) ">: ")))
(if Tmp (setq R1 Tmp))

The first expression, the modified GETDIST, prompts the operator to supply the radius value. When run, the current value in R1 is displayed between the less than and greater than symbols—< and >—so that the string may appear as "Inner radius <1.000>: " when R1 has a value of one. After the GETDIST expression evaluates, the value in the temporary holding variable named Tmp is tested to see if it contains a value. If so, R1 is given that value.

This method generally works for all the basic data types. The only difference will be the conversion subroutine you use for the different data types. Table 1 lists the various data types and potential input and conversion subroutines. Also reference the Visual LISP online help system using the data type names in Table 1 for more help with building conversion subroutines. The real number and angular conversion subroutines have variable arguments that will allow you to control the conversion to a string.

Data Type

Input Subr

Conversion Subr

Integer GETINT ITOA
Real GETREAL RTOS
Distance GETDIST, GETREAL DISTOS, RTOS
Angle GETANGLE, GETORIENT ANGTOS, RTOS
String GETSTRING None

Deploying defaults in your custom function enhances user acceptance. It is much easier for users to press the Enter key as a list of options and dialog boxes fly by with values already seeded properly. Even so, dialog boxes, universal as they are, may not always be called for. In many situations command line-based input presents the better and more effective solution. Deciding which method to use will depend on context of the data relative to the application.

Example Program Function

The example program Listing 1 draws two concentric circles given the center point, inner circle radius, and thickness or distance between the circles. The default radius of the inner circle is obtained from the AutoCAD system variable CIRCLERAD; the default offset value, from OFFSETDIST. By using the existing AutoCAD default values you maintain a sense of continuity with previous commands the operator has issued.

Listing 1 (zip - 1Kb)

A loop is started using the WHILE expression. Normally, this expression is immediately followed by a predicate or testing expression. In this case a PROGN houses multiple expressions, and the last expression result will be the result of the PROGN for the testing component of the WHILE expression. Nesting expressions in this manner is one of the stronger points of writing programs using the Visual LISP language but does make for some interesting reading when you look back at forgotten or new programs.

Inside the PROGN the GETPOINT expression is primed using INITGET. This allows for the input of keywords that are defined as a string with spaces between each option. The next step is the GETDIST subroutine with a STRCAT to combine strings with the default value.

A standard organizing principle of AutoCAD command strings is to have the prompt followed by optional input values inside square brackets with slashes between them, followed by the default value inside the less than and greater than brackets. It's tremendously helpful to use prompts in that manner. When a function is running, the user can right-click in the graphics area, which opens a shortcut menu containing the options in the prompt text plus an option to cancel (which acts like the Enter key). You don't have to program the shortcut menu. It is created for you by AutoCAD based on the text in the prompt. AutoCAD users appreciate this contextual help. You can provide it by following the "unwritten" standards you see in typical AutoCAD command strings.

Back to the program. If the operator enters one of the keyword options or selects a point, the GETPOINT subroutine returns a nonnil result. That satisfies the WHILE test predicate enclosed in the PROGN and triggers the inner part of the WHILE loop. In this area of the code the value from the input is tested to see if it is a list and thereby a data point first. If it is a data point, then two circles are created. If not, the value is tested for a match with one of the keyword string options. Spend some time studying these sections of code in Listing 1 since they contain more examples of using defaults in the input of values.

Conclusion

Creating a function or macro or application that works like a built-in AutoCAD routine works all but guarantees happy end-users. (Of course that applies no matter what software ends up executing your program.) That is the magic of good programming. As a fellow magician once remarked in the laboratory, "Everything I learned about good programming I learned in elementary school. It's all about getting along with everyone else."

Keep on programmin'.