From Idea to Code (Harry's Code Class Newsletter)
23 Jun, 2008Every computer program is unique, but the steps of every programming process are fundamentally the same.
Every computer program starts with a basic idea. The art of programming takes that idea and crafts it into code that solves a problem. Programs vary in complexity, but the fundamental process of crafting a program is often the same. This month, I'll walk you through the basic steps that take you from idea to code.
Step 1: Define the Idea
The programming example I'll use today initiated from a request posted in the thread "Need Modified Lengthen Command" in Cadalyst's Hot Tip Harry - Requests discussion forum. The user asked if anyone had already written code to perform the given task.
With the lengthen command, when you lengthen something (Delta option) you have to either specify a positive number (for increasing length) or a negative number (to decrease length).
What I am looking for is an option similar to that of the trim/extend commands where you hold down shift to change from trim mode to extend mode and vice versa. By choosing a positive number for the lengthen Delta command, you can increase length, and by holding down shift and clicking a line etc. you can decrease length by the same amount. ...
Step 2: Refine the Idea
Somehow that request slipped through the net, and no one on the forum answered. A few months passed, and, necessity being the mother of invention, the original author posted a follow-up that included a modified description of his request.
- Start the command - Specify (only positive) value to store for lengthen delta command
- Run the following prompt "Select entities to lengthen or <Shorten>"
(Shorten by hitting Enter or "S")
- The command would be fed a positive value unless enter or "S" is called, which would change the sign of the number to negative.
- If in "negative" mode (i.e., shorten) the following prompt would display: "Select entities to shorten or <lengthen>".
- Hitting enter or "L" would change the mode back to lengthen (positive value).
OK, now we are getting somewhere! This outline is very close to a potential LISP solution. All that remains is the step where you turn the nearly understandable prose into prefix notation gibberish. We call it programming.
Step 3: Convert to Pseudocode
But we still have to fine-tune this approach before it's complete. The next step is to bring the description closer to programming talk using a language known as pseudocode. Pseudocode combines natural language that most people can understand with some of the structure of the programming language, such as conditionals (if) and loops (repeat-until or while). In the case of AutoCAD command-based macros like the example we're using here, pseudocode will also contain the AutoCAD commands to be used.
When I use pseudocode in the programming process, I usually write the pseudocode as comments in the programming text editor -- in this case, the Visual LISP for AutoCAD Developer Environment (VLIDE). That's why you'll see the semicolons in front of each pseudocode statement. Here's how I would convert the example into pseudocode.
;Enter a length value defaulting to the last used value.
;While user either selects an entity or toggles the delta direction,
;;if the user entered a toggle option, change the delta value's sign
;;;otherwise run the LENGTHEN command sequence with delta option
;;;"LENGTHEN" "DE" <delta value> <entity-selected> ""
;Loop back to the while
Step 4: Convert to Code
Now the real fun begins: transforming the pseudocode into code that solves your problem. Let's dig in.
The first statement in the pseudocode sounds simple enough, and in most cases, it is. However, this is not a simple case. The value used by the Lengthen command is not a system variable that I could find. That means we have to establish our own default and then go forward.
(if (null DefaultLengthen)
(setq DefaultLengthen 1.0))
(setq TMP (getdist
(strcat "\nDefault NLENGTHEN value <"
(rtos (abs DefaultLengthen))
">: "))
DefaultLengthen (if TMP TMP (abs DefaultLengthen))
)
This complicated-looking code starts by checking to see if the symbol DefaultLengthen has a value. If not, it is set to 1.0 as our initial default. The next statement asks the operator to input a new distance value. The absolute value of the default value is shown as the simple choice. After the distance input completes, the value of TMP is tested to see if it contains anything. If it does, the new value is saved as the default value. Otherwise, the old default is used.
Continuing through the pseudocode, the next step starts a while loop based on user input. Although you can create a function for this purpose, you can also use the (PROGN) expression to group the expressions needed. The key is to make sure the last expression in the (PROGN) group performs the test. In this case, we want to accept an entity selection or the input of a string indicating a desire to switch between lengthen and shorten modes. That means using INITGET to prepare the input system and building a different string prompt for each loop. LISP is an amazing language -- the following snippet of code accomplishes all that!
(progn
(setq TMP (if (minusp DefaultLengthen) "Bigger" "Shorter"))
(initget 0 TMP)
(prompt (if (minusp DefaultLengthen)
"\nShorten active, "
"\nBigger active, "))
(setq TMP (entsel (strcat "Select entity or <" TMP ">: ")))
)
Inside the (PROGN) expression grouping, the first step is to set a temporary variable TMP to the string "Bigger" or "Shorter" depending on the sign of the default delta length. This value will be used in the (INITGET) and in the prompt for the entity selection as an input option. After the (INITGET) expression, a prompt initiates, telling the operator which mode is active.
Last, the (ENTSEL) entity selection expression is used to ask the operator to pick an entity. Because that is the last expression in the (PROGN) expression group, the result of the (ENTSEL) expression is the result of the (PROGN). The (PROGN) result is being used as the predicate of the (WHILE) expression. If the value is nil, then the loop is done. Thus, when the operator selects nothing and just presses Enter, the loop will end. Otherwise, the value of TMP will either contain a string or an entity reference.
Note that the terms "Bigger" and "Shorter" were used. "Longer" will not work because the (ENTSEL) expression will interpret the "L" character as meaning the "Last" entity drawn on the screen.
Inside the (WHILE) loop, we can now code the following expressions:
(cond
((= (type TMP) 'STR)
(setq DefaultLengthen (* -1.0 DefaultLengthen)))
(t
(command "_LENGTHEN" "DE" DefaultLengthen TMP "")
)
)
This conditional expression first tests to see if TMP is a string. If it is, the default delta length sign is flipped. Otherwise, the Lengthen command is run from inside a (COMMAND) expression using the default delta length and the entity reference, which must be the entity name plus pick point, exactly as returned from (ENTSEL).
Step 5: Wrap It Up
That's it. All that's left to do is string it all together and add a few more parentheses to close out the program.
Check out "Need Modified Lengthen Command" in the Hot Tip Harry - Requests forum for the complete discussion (and the revisions that ensue). Remember that the best macros and tools come from the desire to just make the commands require fewer picks, clicks, and questions.
Until next time, keep on programmin'.
Related Resources
As always, a wealth of Visual LISP and VBA programming basics is available in the Harry's Code Class newsletter archives on Cadalyst.com.