Object Manipulation in Visual LISP1 May, 2002 By: Bill Kramer
This month's topic comes from a reader who wrote, "I have a drawing with a lot of intersecting lines. I would like to be able to globally insert a block or circle at those intersections." This is a great request as it lets me demonstrate the powerful InsertWith method of the ActiveX interface and show how Visual LISP works with similar automation features typically manipulated in VBA.
At first glance this application seems simple--just look at a drawing and get all the intersections. However, the application involves several concepts that are not immediately obvious, and they can be roadblocks to the successful programming of the utility and others like it.
The first conceptual roadblock has to do with the very notion of seeing the intersections. Often people believe computers think in the same manner as humans. Humans can see that two lines appear to intersect in the drawing. In fact, we can see several intersections at once as we view the drawing and actually conceptualize what it represents. The computer does not see the drawing. The computer stores and regenerates the drawing on a display device. The computer merely manipulates the data to put a picture on the screen for you, the human operator, to see and comprehend. The computer does not store intersections, it stores end points--intersections are calculated from the end points.
Although the computer is much faster at solving rigorous mathematics than we are, it lacks the conceptual talent to understand what it sees. Consider the idea of an intersection of two lines in a drawing. Although your eye can see the intersection immediately, can you just as quickly tell me the exact coordinates of that intersection? Most people cannot immediately compute the coordinates of the intersection except in the simplest of cases. This is where the computer is superior; it can perform that computation quickly. But it cannot start the computation unless it is directed to the lines to obtain the numbers involved.
In order to solve the problem at hand--the location of all intersections in a selection set--our utility will loop through the selection set getting each object and then testing the object against every other object in the selection set for intersections. Tedious for you and me, but something the computer can do rather quickly and with surprisingly few instructions in the Visual LISP language. Listing 1 contains the code for a utility to find all the intersections in a selection set. (Note that you can download the code: cdnc5-02.LSP.)
|Table 1. Visual LISP Data Conversion for Active X Data Types|
|VLAX-ENTITY->VLA-OBJECT||converts entity name to object reference|
|VLAX-VLA-OBJECT->ENTITY||converts object reference to entity name|
|VLAX-MAKE-SAFEARRAY||creates a "safe array"|
|VLAX-MAKE-VARIANT||creates a "variant"|
|VLAX-VARIANT-TYPE||tests type of variant, codes response|
|VLAX-VARIANT-VALUE||gets variant value|
|VLAX-SAFEARRAY->LIST||converts safe array to a data list|
|VLAX-SAFEARRAY-TYPE||gets type of data in safe array|
|VLAX-SAFEARRAY-U-BOUND||gets upper bound of safe array, will be -1 if array is empty|
|VLAX-SAFEARRAY-GET-ELEMENT||gets an element of a safe array|
|VLAX-SAFEARRAY-PUT-ELEMENT||puts an element into a safe array|
|VLAX-SAFEARRAY-GET-DIM||gets the dimensional size of a safe array|
|VLAX-SAFEARRAY-FILL||puts data into a safe array|
|VLAX-3D-POINT||converts 3D data point to safe array|
The utility in Listing 1 has one argument supplied from the outside, a selection set of objects. We assume these objects are capable of running the intersection method available through the ActiveX interface of AutoCAD. Here we'll encounter the next conceptual roadblock as we try to see how to utilize the ActiveX methods and properties from Visual LISP. The difficulty lies in the fact that ActiveX makes use of the data types Visual LISP does not use, and vice versa. Thus, to take advantage of the ActiveX methods, we must translate the data between formats. This practice seems somewhat awkward at first glance, especially when the "Variant" data type is involved.Variants
A Variant is a container in Visual Basic that can point to any other type of data. Most often a variant is used to send data between functions where it is more economical to send the address of the object instead of a complete copy. This is one area where Visual LISP is different. In most cases Visual LISP sends a copy of something to a function. The original copy remains untouched while the function runs. Visual Basic commonly sends the address or location in memory of variables. As a result of this difference, we must convert data between Visual LISP and the ActiveX data types designed around the Visual Basic language specifics.
Visual LISP has several data conversion subrs available to convert data between Visual LISP and Visual Basic formats, as shown in Table 1. I strongly encourage you to read more about them in Visual LISP's online help. Do note that they all begin with VLAX, meaning you must run VL-LOAD-COM to load them into the Visual LISP environment.
|Listing 1. Find All Intersections Between Objects in the Selection Set SS|
|(defun get_all_inters_in_SS (SS /|
SSL ;length of SS
PTS ;returning list
aObj1 ;Object 1
aObj2 ;Object 2
N1 ;Loop counter
N2 ;Loop counter
(setq N1 0 ;index for outer loop
SSL (sslength SS))
; Outer loop, first through second to last
(while (< N1 (1- SSL))
; Get object 1, convert to VLA object type
(setq aObj1 (ssname SS N1)
aObj1 (vlax-ename->vla-object aObj1)
N2 (1+ N1)) ;index for inner loop
; Inner loop, go through remaining objects
(while (< N2 SSL)
; Get object 2, convert to VLA object
(setq aObj2 (ssname SS N2)
aObj2 (vlax-ename->vla-object aObj2)
; Find intersections of Objects
iPts (vla-intersectwith aObj1
; variant result
iPts (vlax-variant-value iPts))
; Variant array has values?
(if (> (vlax-safearray-get-u-bound iPts 1)
(progn ;array holds values, convert it
(setq iPts ;to a list.
;Loop through list constructing points
(while (> (length iPts) 0)
(setq Pts (cons (list (car iPts)
iPts (cdddr iPts)))))
(setq N2 (1+ N2))) ;inner loop end
(setq N1 (1+ N1))) ;outer loop end
Pts) ;return list of points found
The example function in Listing 1 takes advantage of a couple of the conversion subrs in order to exploit the AutoCAD object method named "IntersectWith." Specifically the function starts by converting entity names as retrieved from a selection set to AutoCAD object references. This step is accomplished using the VLAX-ENTITY->VLA-OBJECT subr. The intersection method returns a variant that points to a safe array. There are several subrs involved in the translation of the returning variant to a list we can use in Visual LISP. They are VLAX-VARIANT-VALUE, VLAX-GET-SAFEARRAY-U-BOUND, and VLAX-SAFEARRAY->LIST. In order to understand how these subrs are being utilized, let's take a closer look at what's going on in the function so we can get to the point at which it requires translation.
As the program loops through the selection set, two index values--N1 and N2--are maintained. N1 contains the index into the selection for the outside loop, and N2 is the index for the inside loop. As the second loop starts, N2 is set to one greater than N1. In this way each member of the selection will be checked for intersections with all other members of the set, but never against itself.Indexes and Entities
The index values obtain entity names from the selection set with SSNAME. The entity name is in turn converted to an object reference with VLAX-ENTITY->VLA-OBJECT. In the source code the process repeats as both the outer and inner loops start. The two objects are then used with the subr VLA-INTERSECTWITH, which isn't in the online help under that name. The only information available about the method is in the ActiveX help file under the name IntersectWith. IntersectWith is available for all AutoCAD objects related to the basic line and arc entity. It will compute the intersection point(s) between the objects and return a list of numbers representing the x, y, and z values of each point found. The list is returned in the form of a variant. This means Visual LISP will have to convert the variant to a data list.
VLA-INTERSECTWITH has three parameters. The two object references and a code flag indicating any special treatments to the objects. You can get the code flag values shown in Table 2 by looking at the VBA object browser for the acExtend constants.
|Table 2. IntersectWith Extend Option Codes|
|VBA Help Name||Value||Action|
|AcExtendNone||0||neither entity is extended for intersection|
|AcExtendThisEntity||1||first entity object is extended looking for intersections|
|AcExtendOtherEntity||2||second entity object is extended looking for intersections|
|AcExtendBoth||3||both objects are extended looking for intersections|
The IntersectWith method returns a list of numbers. From a Visual LISP point of view this would be great if it were really a list of points. However, the return values are not point lists but a list of numbers with all the points strung together in a continuous series of the x, y, and z values. For easier manipulation in Visual LISP, convert this list into sub lists of three real numbers representing points. It's simple in Visual LISP, as seen by the code in Listing 1.
After the VLA-INTERSECTWITH expression, the resulting value in IPTS is a variant. VLAX-VARIANT-VALUE returns the value associated with the variant reference. The variant value should be for a safe array of real numbers. A safe array is similar to a list in Visual LISP, except all the data must be of the exact same type. Thus, you can have a safe array of numbers or strings but not a mixture of the two data types. Given the reference to a safe array, we can now test the upper bound or limit of the array. It should be greater than zero if any intersection points are found. The test involving VLAX-GET-U-BOUND is required since calling VLAX-SAFEARRAY->LIST with an empty list results in an error.A List of Points
Once you have a list of numbers, you can then convert them into a list of points. That means taking three members from the front, packaging them into a list, and adding them to the resulting list of points in PTS. A WHILE loop, which runs as long as IPTS has values, follows the conversion of the safe array to a list of numbers.
At the end of the inner loop, the value of N2 is incremented. The inner loop iterates through all the selection set following the object pointed to by N1. When the last object has been tested for intersection, the inner loop completes and then N1 is incremented. The outer loop continues so long as N1 is less than the number of objects in the selection set minus one. When the outer loop completes, the values stored in the list PTS are returned as a result of the utility function.
What happens with the resulting points is up to the application. The original request was to insert circles or blocks at each point found. At the CADENCE magazine Web site you can find this utility routine along with some extra functions showing it in use. This version inserts point objects at each intersection and can be quickly converted to insert blocks or circles.
From a performance point of view there are a few items to be aware of when planning to use this utility. It is best suited for a small number of objects in a selection set. Given a couple dozen objects the utility runs very fast; however, for larger selection sets (several thousand) it will take longer to solve the intersections (time requirement increases in a non-linear fashion). Thus, you will be happiest with the performance of the utility in an interactive environment with a low number of test elements.
Was this utility useful? If you'd like to suggest another, please email it to Toolbox@autocode.com. Until next time, keep on programmin'!