Messing with Layer Names (Harry's Code Class Newsletter)25 Mar, 2008
Today's lesson demonstrates how to create an object-oriented Visual LISP routine that processes multiple AutoCAD drawings.
In Cadalyst's Hot Tip Harry-Requests Discussion Forum recently, a thread about converting layer names in drawings raised some ideas I'd like to examine further. AutoCAD contains the tools to rename layers manually; however, this user hoped to automate the task so he could convert a large number of drawings flowing in from external sources. I had the notion that this was a case in which the typical AutoLISP approach of mimicking manual procedures by wrapping LISP code around commands might not be the best solution.
Examining the Usual Approach
To begin, let's look at what is involved in manually converting layer names in AutoCAD. For each drawing in the library to be converted, the first step would be to open the drawing and look at the existing layers. Using the Layer dialog box, it is pretty easy to change names. When the dialog box process completes, all associated entities are on the newly named layer.
The problem is that we can't drive an AutoCAD dialog box using VBA nor Visual LISP, so we need to look at the "-" command option. Using a hyphen (-) in front of a command name tells AutoCAD you'd like to run the command line version of the command rather than the dialog box version. Command line versions can be driven using VBA and Visual LISP programming. Problem solved, right? Well, take a look at the results of the Layer command test using the hyphen.
Current layer: "0"
Enter an option
Apparently the rename functionality found in the dialog box is not part of the standard Layer command. Instead, we can conclude that another command handles renaming layers (as well as other named objects in AutoCAD): Rename.
A Layer by any Other Name
So now we turn our attention to the Rename command. We can rename layers by repeated calls to the -Rename command (hyphen required in the code).
It's pretty easy to use the -Rename command to write a routine that renames a group of layers. Create a nested list of layer names (("old" "new")...) at the start of the function. The routine will use this list in a loop, running the -Rename command over and over.
(defun C:RENLYRS (/ LYRS LYR)
(setq LYRS (list (list "LAYER1" "L1") (list "LAYER2" "L2")))
(foreach LYR LYRS
(if (tblsearch "LAYER" (car LYR))
(command "-RENAME" "LA" (car LYR) (cadr LYR)))))
This function uses the tblsearch subr to make sure the layer is known in the layer table before the -Rename command is issued. If the layer is not in the drawing, an error can result, causing the command sequence to get confused.
As you can see in this example, the old school AutoLISP methods for accessing table data are quite powerful. They allow quick and easy access to the table data sequentially or by using the name of an entry, as in this case. Coupled with the AutoCAD command system, the table access can pretty much accomplish most application requirements.
But I Have More than One Drawing!
However, this old school approach can only work on your currently active drawing and requires some rethinking if you want to run a routine on a group of drawings. The old school solution is to use a script file (SCR), which is a text-based file of an AutoCAD command. Using a script, one can program AutoCAD to open each drawing, then load and run the LISP code. The following lines demonstrate what might be found in a script file that opens and processes two drawings named DEMO1 and DEMO2.
This approach works well as a one-time solution to process a limited number of drawings. It may not work well for a recurring operation because it requires that you explicitly name each drawing in the script file itself. That means that before you can process a group of drawings, you first must edit the script file contents to specify file names. A few iterations of that process will have you looking for a better solution.
Objects to the Rescue
The solution we seek can be found using objects. In Visual LISP, objects present the opportunity to work with more than one drawing at a time. Now, in this case, the hitch is that the drawings need to be open and in a quiescent state -- that is, ready for a command to be run on each. But opening a set of drawings to be processed and then running a single Visual LISP macro is a lot easier than maintaining and updating a script file every time you need to do the layer renaming process.
To use Visual LISP to reach out from the current drawing and into another open drawing, you must use the object access tools. Only through the object relationships can you clearly define which drawing and which object inside the drawing you are referencing. Naturally, there will be a few more lines of code involved -- just not as many as you might imagine.
Object access in Visual LISP starts by including (vl-load-com) somewhere in your LISP code. This subr must be evaluated before you can access any of the VLAX functions in Visual LISP. Normally I put (vl-load-com) in my LISP files outside any (defun) function definitions so that it is evaluated when the LISP file is loaded. Once that subr is evaluated, the entire host of Visual LISP VLAX function are available, and repeated calls to (vl-load-com) are simply ignored.
Now we are ready to start accessing objects. To begin, we need to gather all the open drawings. This is accomplished by getting the documents collection object inside the AutoCAD program object. The following expression results in a collection of the open documents:
(setq dwgsObj (vla-get-documents (vlax-get-acad-object)))
A collection in Visual LISP terms is similar to a selection set or list. It is a group of similar objects, such as documents, layers, or blocks. You could also think of a collection as a special list of objects, much like a selection set is a special list of entity names. As such, there are several tools in Visual LISP specifically for processing collections. Looping through a collection, processing each member object one at a time, can be accomplished using (vlax-for). The (vlax-for) subr is a very simple sequential loop. Each member in a collection is placed in a local symbol for processing inside the loop. Consider the following Visual LISP code that uses the dwgsObj value established above:.
(vlax-for dwg dwgsObj (print (vla-get-name dwg)))
The above expression will loop through each open drawing and print the name of each drawing. The loop processes each drawing object in the dwgsObj collection, then prints the value of the name property in the drawing. Cool, huh?
We can do a lot with the object for each drawing, such as accessing the layers collection and making modifications. So let's take a look at a function that works with a nested list of layer names (("old" "new")...) to change names, just like our earlier example. The difference now is that we want this version to work with every open drawing.
(defun C:EXAMPLE3 (/ LYRS LYR)
(setq LYRS '(("LAYER1" "L1") ("LAYER2" "L2") ("LAYER3" "L3"))
dwgObj (vla-get-documents (vlax-get-acad-object))
(vlax-for Dwg dwgObj
(setq LYRObj (vla-get-layers DWG))
(foreach LYR LYRS
(setq LD (vl-catch-all-apply 'vla-item (list LYRObj (car LYR))))
(if (not (vl-catch-all-error-p LD))
(vla-put-name LYRObj (cadr LYR)))
The function employs a nested loop, with the outermost loop based on the open drawings collection. Inside the (vlax-for) loop, the layer object of open drawing is obtained and stored with LYRObj. Now the inner loop starts by iterating through each of the members of the LYRS list. The LYRS list contains the old and new names. We are to locate the old names, and when found, change them to the new.
To read inside the layer collection object given the layer name, we use (vla-item). The problem is that if the layer name is not in the collection, we will get an automation error that terminates the function. To stop the error exit, we can use the Visual LISP error catch, as demonstrated in the code. (vl-catch-all-apply) is a subr that runs a function with parameters.
When all goes well, we'll get the same result as when running the function directly. Should an error occur, the catch returns an error object instead of ending the evaluation. We test for the error object using the predicate subr (vl-catch-all-error-p). This predicate results in "true" when the object is an error object. The predicate result is "nil" if the symbol does not point to an error object. In this case, the (vla-item) subr will result in either a layer object or an error object set in LD.
Once we are certain that we have a layer object and not an error object, we can put the new name in place. The inner loop then iterates, going to the next layer name set. Once the layer list is complete, the drawings collection-based loop iterates by working with the next drawing.
You can expand this example to do other manipulations with the objects in the open drawing. That is the joy of programming. Because we used objects to accomplish the task, the application can be scaled upward to run on a multitude of open drawings. That is the primary difference between the old school AutoLISP approach and the newer Visual LISP object-oriented approach. You can scale up object-related code much more easily. That's something to consider the next time you have to decide whether or not to use an object-oriented approach in your programming.
Until next time, keep on programmin'.