I need some help converting the Arc segment of a Polyline to GCode (using G02 or G03). By standard, the DXF stores the arc segment as start point, end point, and bulge. So I have these 3 variables. I know that the radius of the arc is 4.Atan(bulge), so I can figure the angle of the arc. For curves, the bulge value is a ratio of the distance from the arc to the chord divided by half the length of the chord. The bulge factor adds vertices to a polyline curve, creating an approximation of the curve using straight line segments. The length of these segments varies depending on the bulge factor and the degree of curvature.
Applications developed before AutoCAD R14 sometimes stumble when they encounterthe new objects of Release 14 and later. The most common problem stems from thechange in the polyline object inside AutoCAD. AutoCAD R14 introduced a new objectnamed a lightweight polyline that replaces the complex polyline object. When youcreate a drawing in Release 14 and use commands to create polylines, then you aremost likely creating lightweight polylines. From an AutoCAD user's perspective,the lightweight polyline is exactly like the older polyline. But from aprogrammer's perspective, they are very different. This month, we'll look atthese differences from a programmer's point of view-both in AutoLISP and in VBA.
Let's start with an explanation of exactly what a lightweight polyline is versusa traditional polyline. The primary difference is that a lightweight polyline isaddressed as a single entity object whereas the traditional polyline is asequence of entity objects. In a traditional polyline, you find a series ofvertex objects that define the attributes of each point. Each vertex has its ownentity ID and a full complement of information about that object. In mostpolylines, the information contained in each vertex is redundant. For example,the layer and linetype assignments rarely vary (and shouldn't!) between vertices.In fact, about the only information each vertex contains that is unique is the x,y and z points and bulge factor. In some cases, the width will vary betweenvertices as well.
Thus, a traditional polyline object takes up a lot more space than it might needin the majority of instances where polylines are used in AutoCAD drawings. Thatis where a lightweight polyline comes into play. A lightweight polyline containsonly the point, bulge and width information for each vertex. And, it is allcontained in a single entity. A lightweight polyline has a repeating set of groupcodes for these values at each vertex point. This is best seen in the examplethat follows. First, draw a simple rectangle.
Command: RECTANG
Specify first corner point or [Chamfer/Elevation/Fillet/ Thickness/Width]: 0,0
Specify other corner point: 1,1
Next, type in the expression (entget (entlast)) at the command prompt to revealthe contents of the lightweight polyline.
Listing 1. Entity List Resulting From (ENTGET (ENTLAST)) After Drawing aRectangle From 0,0 to 1,1 |
Listing 2. The List Points in a Lightweight Polyline |
Listing 3. Get the Nth Vertex From a Lightweight Polyline |
Listing 4. Convert a Lightweight to a Traditional Polyline |
Listing 5. VBA Utility to Convert Lightweight Polylines to TraditionalOnes |
Table 1. Standard Group Code Usage |
Each line in the listing contains a group code followed by the associated data.Note that the listing that appears on your monitor will be different because itwill all be run together. The group codes are integer numbers that help ourprograms to understand what the data that follows will be. These are standardgroup code assignments in AutoCAD that have been used not only in AutoLISP entitylists, but also within the DXF structure. Table 1 summarizes the generalizedgroup code assignments for quick reference.
Looking at group code assignments in Listing 1, notice that there are some groupcodes that repeat. In the lightweight polyline object, the group codes 10, 40, 41and 42 are repeated for each point that is part of the polyline. Group code 10 isthe data point itself. Group codes 40 and 41 are the starting and ending widthsof the polyline segment. The bulge factor is located with group code 42. If asegment does not have an arc, the bulge factor is zero. When there is an arc inthe segment, the value seen with group code 42 is the result of calculating thetangent value of one quarter of the included angle. When the angle is clockwise,the bulge factor is negative; when the angle is counterclockwise, the bulgefactor is greater than zero.
In a traditional polyline, the 10, 40, 41 and 42 group codes are seen inindividual vertex entity objects-you use (ENTNEXT) to step through the polyline,and each point is a separate entity list. This makes the extraction ofsegment-level information much easier, and it also provides a way to uniquelyidentify one segment from another using entity names.
The advantage of having unique group codes in an entity list seems to be lostwhen working with lightweight polylines. This is because there are multipleoccurrences of the 10, 40 and other codes. So, how does one work through such acomplicated list structure? (Repeated use of the (ASSOC) subr will only returnthe same values.)
The answer is to dig into the list and destroy it as you go. Of course, use atemporary copy of the list or save a copy before doing this as you will mostlikely want to use the list again. Chipping away at the front destroys the list.That is, we are going to remove the list members from the front, so that when wedo an (ASSOC) for one of the variables we want, it will be the next vertex andnot the one we've already looked at.
Reduce the List
The expression (setq EL (member (assoc 10 EL) EL)) reduces the entity list (EL)that contains the lightweight polyline. The (ASSOC 10 EL) portion returns thefirst group code 10 sublist. This list is then used with the (MEMBER) subr tolocate it in the list. When successful, (MEMBER) returns the located list memberalong with the remainder of the list following it. This has the effect of movingthe group code member to the front of the list. The values of the vertex can thenbe accessed as normal using the (ASSOC) expression. After the application is doneretrieving the vertex data, it must then take out the group code 10 so that thenext time around the next point is moved to the front.
Take a look at Listing 1. When the expression just shown is used with the listset to EL, it results in a list that starts at the first group code 10 sublist(just after the group code 39). After using the value of the point, the firstmember of EL is removed via the (CDR) subr. Thus, the next time the (MEMBER(ASSOC)) expression is employed on EL, the list is reduced further by taking offthe 40, 41 and 42 code members up to the next group code 10.
Armed with this expression, we can write a (WHILE) loop that reduces thelightweight polyline to the individual vertex members. The loop iterates as longas there are vertex points remaining. A simple loop that reports each vertexpoint is shown in Listing 2. The (SETQ) expression is used as the predicate testin the (WHILE) loop. When we have read through all the points in the lightweightpolyline, the loop terminates as the (ASSOC) subr returns nil, which causes a nilresult from the EL list because nil is found at the end of every normal list. The(WHILE) loop simply prints the value of group code 10, which is the point wherethe vertex is located. Because the (SETQ) expression sets the value resultingfrom (MEMBER) back into EL, EL destroys itself while the loop is running. Thelist is further reduced by using the (CDR) expression, which takes the head offand prepares the list for the next iteration. Since the entity list is aparameter to the function in Listing 2, the calling application still has anintact original of the list to use.
The problems with lightweight polylines are that they don't have uniqueidentities for each of the vertices, and they do not have z values associatedwith the point lists. The function in Listing 3 can help you solve the firstproblem. Given an integer that indicates offset position of the vertex desired,this function returns a mini-association list containing the point, bulge andwidth values for that index value.
Looking inside the function in Listing 3, you'll see a (WHILE) loop. The (WHILE)loop iterates as long as the N value is greater than zero and EL still has avalue. Inside the loop, the EL list value is reduced to the member starting withgroup code 10. Note that inside the (ASSOC) expression, the (CDR) is used toremove the previous group code 10 from the list. In the first iteration, the(CDR) removes the entity name, which is of no consequence to our programming. IfN is equal to zero at the end of the iterations, then the desired vertex has beenlocated. If N is zero, a portion of the entity list containing the points, widthsand bulge is then returned as a result of the function. If N is not zero, thefunction returns nil.
There are applications with which the lightweight polyline will not work well.This is especially true with applications being ported from previous versions ofAutoCAD or ones that need to work with the vertex-elements as individualselection picks. The answer to this problem is to convert all the lightweightpolylines in the drawing into traditional polylines as the application isrunning. The only drawback to this approach is that the Offset command may notwork well with the traditional polylines. It seems that the Offset commandprefers lightweight polylines only because it can be assured that they arecoplanar.
Converting Lightweight to Traditional
Listing 4 contains a function that converts a lightweight polyline into atraditional polyline. Because there are variable instances where traditionalpolylines are required, this function works with various parameter options. Thefunction will accept an entity list, an entity name or a selection pick (entityname plus pick point). The result of the function is the entity name of the newtraditional polyline-entity object. This utility function can be modified to meetother requirements as needed by your application, so let's look at the routine inmore detail.
The parameter EL is first tested to see if it is an entity name. If so, theentity name is converted to an entity list and stored back in EL. If EL is not anentity name but came from an (ENTSEL) type function, then the entity data list isretrieved and given the entity name from the selection pick list. Otherwise, ELremains intact as supplied, and it is tested to see if it is an entity listcontaining the entity type LWPOLYLINE.
Once the function knows for sure that the entity list is that of a lightweightpolyline, it continues by extracting the layer, z data and other headerinformation. The header data is stored in local variables for access later in theroutine. Traditional polylines involve multiple entity objects. They are startedwith the POLYLINE header entity, have a VERTEX entity for each point and areterminated by the SEQEND entity. In order to convert the lightweight polylineinto a traditional one, we must build each of the entity components.
The fastest and best-controlled way to create new entities with AutoLISP is viathe (ENTMAKE) subr. Once given the header data, the function proceeds to create apolyline header entity list storing it in EN. If the color and linetypeinformation in the lightweight polyline has been set, those values are added tothe entity list in EN so that our new object will have these same properties. The(ENTMAKE) subr is then presented with this entity list to create the newpolyline.
The next step is to create a prototype VERTEX entity list. Each vertex containsthe unique entries for the point list, bulge factor and widths. The prototypeentity list has these values initialized to zeros. We will substitute the uniquevalues for each instance.
Dxf Polyline Bulge
Next, the function starts into a (WHILE) loop that is very similar to the onesalready seen. Each group code 10 in the lightweight polyline is moved to thefront of EL and the loop iterates. When we have exhausted the points in EL, theloop finishes.
Inside the (WHILE) loop, the entity list EN is changed. EN has the VERTEXprototype entity list and the values for points, bulge factor and widths aresubstituted with the values from the EL entity list. The (ENTMAKE) subr is thenemployed to create the VERTEX entity object. Note that as the entity objectsequence for the traditional polyline is defined, it will not appear on thegraphics screen. The entity object will not be generated on the screen until itis terminated.
When we no longer have any points left in the EL data list, the entity objectsequence definition for the polyline is completed and (ENTMAKE) is used one lasttime to generate the SEQEND entity object. At this time, the polyline is drawn.The call to (ENTLAST) retrieves the entity name of the last entity created -ournew traditional polyline.
Some modifications exist that you may want to make to this lightweight polylineconversion utility. For instance, the original polyline can be placed on a hiddenlayer or removed altogether. You can also return the same type of information aswas provided to the entity. That is, if an entity name was supplied, an entityname is returned. If an entity selection pick was supplied, then one is returnedinstead of just the entity name (hint: use (NENTSELP) to build it after deletingthe original lightweight polyline).
The VBA Alternative
Now, let's look at the same problem, but this time let's make use of the VBAprogramming solution. VBA works with entity objects in a more object-orientedsense than AutoLISP. In AutoLISP, you use entity lists; in VBA, entity objects.From the VBA programmer's perspective, the lightweight and traditional polylineobjects are very much the same. In fact, a quick inspection of the properties andmethods associated with the two does not reveal any significant differences(except that the coordinates to the traditional style are three-dimensional whilethose for lightweight polylines are only two-dimensional).
Listing 5 contains the code for a function in VBA that takes an object presumedto be an AutoCAD polyline entity. An IF test is used to see if indeed the objectis a lightweight polyline. If so, it proceeds to get the coordinates. Thecoordinates of a polyline object are stored in a variant array-you supply avariant-type variable and AutoCAD VBA returns it with data in it. The data is anarray of double precision real numbers representing the 2D points. All the pointsare stored together in the array with the x values being found at positions 0, 2,4 and so forth; the y values are located at positions 1, 3, 5 and onwards. A FORloop can be used to step through the array, skipping every other one as we getthe x and y values for the points.
To create a polyline object, either a lightweight or a traditional, you mustbuild an array of doubles and then assign them to a variant variable. Variantspresent an elegant way to pass information between objects in VBA. From thesystems programmer's point of view, they are pointers with type definition dataattached.
The conversion function then creates a new array of doubles that are large enoughto contain the z values as well. To determine how large the new array must be,take the number of coordinate values in the 2D data array and multiply it by oneand a half. REDIM is then used to dimension the array to the variable sizerequired.
Next, the function takes the 2D data and puts it into the 3D data array. Eachcoordinate is stored in succession (the first point is located at positions 0, 1,2, and the next point can be found at positions 3 through 5 and so forth). TheFOR-NEXT loop uses the size of the coordinates variant array obtained from thelightweight polyline and steps through the array two at a time. Another counter(J) is used to keep track of the location in the 3D array we are creating.
After the FOR-NEXT loop, the coordinate array just defined is set to a variantvariable. This has the net effect of making the variant point at the array, whichis what is needed to create the new polyline in the Model Space of the currentdrawing. The properties of the lightweight polyline can now be copied to theproperties of the traditional polyline. The global properties such as color,linetype and layer are set from EN to EN2-the new polyline.
Another loop is then started that can transfer the properties for each vertex.The bulge factor and width properties are moved from EN to EN2 by using theGETWIDTH and GETBULGE methods. An index is supplied to the functions to obtainthe value at a specific vertex. The complementary functions are SETWIDTH and SETBULGE, and they set the appropriate values at each vertex.
When this function finishes, the new entity will be generated. Note that there isno closing or sequence-end definition that causes the graphic generation of theobject. If you need to force a regeneration of the object itself in your code,then the UPDATE method is employed.
Obtaining the specific values of a coordinate in VBA is much easier than inAutoLISP. The COORDINATE property is used with an index of the exact coordinatedesired. Coordinates are always returned and supplied to the COORDINATE method asan array of three double-precision real numbers. In the case of the lightweightpolyline, the z value is the same as the elevation or the UCS or it will be zero.For the traditional polyline type, the z value is the same as the vertex. Notethat when assigning the COORDINATE property of an entity object, you must supplya variant that is assigned to an array of three double-precision real numbers.And, when COORDINATE is used to obtain a value, the result is a variant data typethat points at a three-element array of doubles.
Choices
The manipulation of the entity data in VBA is substantially easier than inAutoLISP (as seen in this simple example). Although the notion of variants can besomewhat confusing at first, they are really nothing more than pointers tovariable memory locations as used by the VBA objects in AutoCAD. Their usagegreatly reduces the overhead needed to implement the types of interfaces desiredby programmers of VBA in AutoCAD.
So, what's your choice? In AutoCAD 2000, the VBA system is greatly improved.There is more power and a better set of tools for interfacing with the AutoCAD2000 environment. But at the same time, Visual LISP is now part of the standardAutoCAD package, and it provides a greatly enhanced version of AutoLISP thatrivals VBA in almost all regards. So, the decision is not easy as they are bothgreat choices. No matter what direction you go, remember to keep onprogrammin'.