# Specific method for parametric drawing programs

The hard part of coding parametric drawing program whether in lisp or VBA is managing the large number of points. The program turns into many lines of hard to read data apparently randomly named. A sketch has to be made with points labeled and equations or formulas entered. It all might make sense during the coding, but probably won’t a few weeks later when a change has to be made even if the sketch(s) is found. It won’t be obvious how the points are calculated or why lines are drawn from pt7 to pt21 to pt3. There is no one right way but I have recently worked on both lisp and vba programs and have some specific but not comprehensive suggestions. This is a special theory for creating the xy data but not a general theory for the entire program.

There are two basic ways to manage your drawing subroutine. It can accept points or xy data. Try both ways. Both methods need xy data.

In Lisp I use Visual Lisp objects rather than the “command” method. The object method can draw directly in to a block definition, and it can directly change the layer property. It requires a point object, but that can be created and passed as a parameter or the xy data can be passed and the point created in the subroutine.

For lisp I made a point creation routine and passed points to the subroutine which runs the Addline method.

`(defun pt ( x y ) (vlax-3d-point x y 0))`

In a very simple box example this gets called as

`(setq pt1 (pt 0 0) pt2 (pt L 0) pt3 (pt L W) pt4 (pt 0 W))`

Then the line routine would be

```(defun linep (pt1 pt2 obj lyr / lineobj) (setq lineobj (vla-AddLine obj pt1 pt2)) (vla-put-layer lineobj lyr) )```

And be called as

`(linep pt1 pt2 ms "hidden")`

Or you could pass xy data

```(defun line (x1 y1 x2 y2 obj lyr / pt1 pt2 lineobj) (setq pt1 (vlax-3d-point x1 y1 0) pt2 (vlax-3d-point x2 y2 0)) (setq lineobj (vla-AddLine obj pt1 pt2)) (vla-put-layer lineobj lyr) )```

In VBA every variable has to be declared previous to use, so you might lean towards passing xy data. Assume you want to draw a notched rectangle and make it a polyline. You make a sub specifically for this purpose. After setting the xy data coordinates, any six vertex closed polyline can be drawn with

`Call p6_box(x1, y1, x2, y1, x2, y2, x3, y2, x3, y3, x1, y3)`

``` Sub p6_box(p1 As Double, p2 As Double, p3 As Double, p4 As Double, p5 As Double, p6 As Double, _ p7 As Double, p8 As Double, p9 As Double, p10 As Double, p11 As Double, p12 As Double) ```

``` Dim objent As AcadLWPolyline Dim pt(0 To 11) As Double pt(0) = p1: pt(1) = p2 pt(2) = p3: pt(3) = p4 pt(4) = p5: pt(5) = p6 pt(6) = p7: pt(7) = p8 pt(8) = p9: pt(9) = p10 pt(10) = p11: pt(11) = p12 Set objent = acadDoc.ModelSpace.AddLightWeightPolyline(pt) objent.Closed = True Set obj_Acad_Entity = objent End Sub```

This makes no sense without a sketch but the sub p6_box can draw any closed polyline with 6 points configured any way you need it. Our notched box is L X W with an A X B notch, drawn with the lower left corner at 0,0. There are 3 X coordinates and 3 Y coordinates.
X1=0 , X2=L-B , X3=L
Y1=0 , Y2=A , Y3=W

You can turn this box around any way you wish, move the notch to the middle, put a hole in the middle. Just label xy coordinates as needed in order from the origin. This is how you organize your xy data without duplication in a straightforward way. Sometimes its convenient to also label points, sometimes its not required, but the xy data must always be figured from the parameters as the first step.

In VBA we would probably draw in counterclockwise order.

`Call p6_box(x1, y1, x2, y1, x2, y2, x3, y2, x3, y3, x1, y3)`

Now it should make sense. The xydata starts at the origin. Subroutines can be written so declared point variables are not required, or required. If you have a lot of sub-routines, just declare your x1, x2, etc as public to avoid re-declaring.

In programming 101 they strongly suggest that your subroutines be simple and single purpose. Just about every autocad parametric program I have seen or written has been a mess at the actual geometry creation level. For instance in this example, the parameters A and B, L and W may need to have complicated formulas behind them. Put those upstream of the actual sub-routine that draws the geometry. Make the geometry creation as simple as possible. Pass the actual parameters if possible, do not develop them. Interface is top down thinking, but geometry is bottom up.
Such as

```Sub draw_notch_box(W As Double, L As Double, A As Double, B As Double) x1 = 0 x2 = L - B x3 = L y1 = 0 y2 = A y3 = W Call p6_box(x1, y1, x2, y1, x2, y2, x3, y2, x3, y3, x1, y3) End Sub```

You will be able to read that next year if you remember that xy data starts at the origin.

A non-trivial VBA parametric project requires some structure to re-use standard techniques and a guide drawing to be made with user parameters identified that specifies the point values to be drawn. This sample project is a sheet metal frame that would be used to cap a hole. There are only two parts, top/bottom and side, but they are drawn both in front assembly and laid out flat before bending, so there are 4 basic drawing sub-routines. The drawing subroutines create geometry. Their only other job is to initialize bounding box variables so the calling program has the option to move into assembly, block, and/or rotate.

The parts are always drawn with lower left corner at or near 0,0. The master drawing is created manually before the programming is started. If carefully done, and revised until it works as needed, it will make the programming much easier. Give each project its own module. It solves naming issues if each project is given a simple project ID, then prefix all the subs with the ID. The draw subs are at the bottom and the controlling program at the top. The draw subs are completed first, all in a similar manner. Again it makes programming much simpler if all the controlling sketches are laid out consistently and thoroughly. The only variables passed are the user variables. Calculations are made in the drawing sub. The sketches should be consistent – variable D1 in one sketch is the same value D1 in another sketch.

```
Sub B1_chan_top_bent(A As Double, D As Double, E As Double)
Dim pt As Variant
Dim A1 As Double, A2 As Double
A1 = A + D + D
A2 = A + D

'bounding box pt_ll and pt_ur are global vars
Call initpt(pt_ll, -1, -1, 0)
Call initpt(pt_ur, A1 + 2, D + 2, 0)

pt = Array(0, E, D, E, D, 0, _
A2, 0, A2, E, A1, E, _
A1, D, 0, D)
Call draw_array(pt)

Call line(D, E, A2, E)

'each drawing sub re-sets current layer to 0
Update
End Sub

Sub B1_chan_side_bent(B As Double, D As Double, E As Double)
Dim pt As Variant
Dim B1 As Double
B1 = B + E + E

'bounding box
Call initpt(pt_ll, -1, -1, 0)
Call initpt(pt_ur, B1 + 2, D + 2, 0)

pt = Array(0, 0, B1, 0, B1, D, 0, D)
Call draw_array(pt)

Call line(0, E, B1, E)

Update
End Sub

Sub B1_chan_top_flat(A As Double, C As Double, D As Double, E As Double)
Dim pt As Variant
Dim A1 As Double, A2 As Double, C1 As Double, C2 As Double, D1 As Double
A1 = A + D + D
A2 = A + D
D1 = D - E
C1 = C + D1
C2 = C + D1 + D1

'bounding box
Call initpt(pt_ll, -1, -1, 0)
Call initpt(pt_ur, A1 + 2, C2 + 2, 0)

pt = Array(0, 0, A1, 0, A1, D1, _
A2, D1, A2, C1, A1, C1, _
A1, C2, 0, C2, 0, C1, _
D, C1, D, D1, 0, D1)
Call draw_array(pt)

Call line(D, D1, A2, D1)
Call line(D, C1, A2, C1)

Update
End Sub

Sub B1_chan_side_flat(B As Double, C As Double, D As Double, E As Double)
Dim pt As Variant
Dim B1 As Double, C1 As Double, C2 As Double, D1 As Double
B1 = B + E + E
D1 = D - E
C1 = C + D1
C2 = C + D1 + D1

'bounding box
Call initpt(pt_ll, -1, -1, 0)
Call initpt(pt_ur, B1 + 2, C2 + 2, 0)

'outside box
pt = Array(0, 0, B1, 0, B1, C2, 0, C2)
Call draw_array(pt)

Call line(0, D1, B1, D1)
Call line(0, C1, B1, C1)

Update
End Sub
```

These can be called in any order. In this project i call the bent pieces first. I block them, then move them to an assembly position, using an arbitrary location (K, K). The side piece is drawn horizontal, so it is first rotated and moved about the same point. Again these points are located first on the sketches. The parts are blocked with a sub that accepts the bounding box coordinates, a name and a point for insert point. That routine works with any similar project. It creates a selection set using the bounding box, creates the block with name and insert point, adds the selection set entities to the block, erases the original entities and finally inserts the block. It assigns the block to a global var so the calling program can move it. These routines are in their own module shared by multiple projects.

```Sub make_ss_blk(pt_ll() As Double, pt_ur() As Double, strblkname As String, pt_insert() As Double)
'creates new ss, adds items to it with a crossing window
'creates new block, adds ss to blk with counting loop
'deletes original entities and inserts the block
'creates an object reference to the block objpersistent for moving/rotating

Dim i As Integer

objss.Select acSelectionSetCrossing, pt_ll, pt_ur

' make_blk(objss, strblkname, pt_insert)
On Error Resume Next
objBlock.Delete
On Error GoTo 0

ReDim Obj(0 To objss.Count - 1) As AcadObject
'Copy each object selected in the block
For i = 0 To objss.Count - 1
Set Obj(i) = objss(i)
Next i

objss.Erase
objss.Delete

Set objpersistent = acadDoc.ModelSpace.InsertBlock(pt_insert, strblkname, 1, 1, 1, 0)

End Sub

On Error Resume Next
If "" = strname Then
Exit Sub
End If

objss.Delete
If objss Is Nothing Then
MsgBox "unable to add " & strname
Else
End If
End Sub
```

The main calling program will vary depending on interface. Here is the basic one with variable values called out near the top.

```
Option Explicit
'ProjID B1
'Project Name - Inside Cap Channel
'ProjDesc - to cap existing hole
'a b c d e

Public Const Pi As Double = 3.14159265359
Public Const halfPI = Pi / 2

Public pt0(0 To 2) As Double
Public pt_ll(0 To 2) As Double
Public pt_ur(0 To 2) As Double

Sub B1_draw_inside_cap_chan_assy()

Dim pt1(0 To 2) As Double
Dim pt2(0 To 2) As Double
Dim K As Double
K = 50
Call init_part

'assy vars
Dim A As Double, B As Double, C As Double, D As Double, E As Double
'A is X, B is Y, C is ID THK, D is BENT FLG, E is GA

A = 24
B = 36
C = 4.125
D = 2.5
E = 0.125

Call B1_chan_top_bent(A, D, E)
Call make_ss_blk(pt_ll, pt_ur, "B1_chan_top", pt0)
Call initpt(pt1, A / 2 + D, 0, 0)
Call initpt(pt2, K, K + B / 2, 0)
objpersistent.Move pt1, pt2

Call B1_chan_side_bent(B, D, E)
Call make_ss_blk(pt_ll, pt_ur, "B1_chan_side", pt0)
Call initpt(pt1, B / 2 + E, 0, 0)
Call initpt(pt2, K + A / 2, K, 0)
objpersistent.Rotate pt1, -halfPI
objpersistent.Move pt1, pt2

Call B1_chan_top_flat(A, C, D, E)
objss.Select acSelectionSetWindow, pt_ll, pt_ur

Call initpt(pt2, 0, 48, 0)
For Each objent In objss
objent.Move pt0, pt2
Next

Call B1_chan_side_flat(B, C, D, E)
objss.Select acSelectionSetWindow, pt_ll, pt_ur

Call initpt(pt2, 0, 24, 0)
For Each objent In objss
objent.Move pt0, pt2
Next

Call initpt(pt2, K, K, 0)
Call cap_chan_sect(C + (2 * E), D, E)
objpersistent.Move pt0, pt2

Update
End Sub
```

The section view is a straightforward drawing sub

```Sub cap_chan_sect(C As Double, D As Double, E As Double)
Dim pt As Variant
Call initpt(pt_ll, -1, -1, 0)
Call initpt(pt_ur, D + 2, C + 2, 0)

pt = Array(0, 0, D, 0, D, E, E, E, E, C - E, D, C - E, D, C, 0, C)
Call draw_array(pt)

Update
End Sub
```

The init-part sub is simply because i like to be able to run programs in a completely new blank drawing. it creates layers, but also makes the initial connection between excel VBA code and autocad.

```Sub init_part()
Call initpt(pt0, 0, 0, 0)
Call newlayer("UP", 4, acLnWtByLwDefault, "Continuous")
Call newlayer("Down", 6, acLnWtByLwDefault, "Hidden")
Call newlayer("Hidden", 6, acLnWtByLwDefault, "Hidden")
End Sub
```

Anything you can draw manually with specificity and clearly defined variables you can automate. It is only slightly more effort to automate dimensioning. Practice will refine your technique.

# Autocad VBA Parametrics – 2 – Polyline Method

The autocad lightweightpolyline is the method of choice for drawing parametric plane figures if it is an option. But assembling an array of 30 or so points can be tedious. Here is a sub i wrote several months ago, and to be honest, it is a complete mystery now. the only way i can work on it is to plot it out and start listing points. So i am going to generalize the method and come up with an easier standard procedure for drawing parametric polyline objects.

```
Sub orig_OS_skin(A As Double, B As Double)
Dim pt(1 To 36) As Double

pt(1) = 0.21875: pt(2) = A + 0.5 - 1
pt(3) = 0.21875: pt(4) = A + 0.5
pt(5) = 0: pt(6) = A + 0.5
pt(7) = 0: pt(8) = 0
pt(9) = B + 0.5 - 1.25: pt(10) = 0
pt(11) = B + 0.5 - 1.0625: pt(12) = 0.1875
pt(13) = B + 0.5: pt(14) = 0.1875
pt(15) = B + 0.5: pt(16) = 0.40625
pt(17) = B + 0.5 - 0.875: pt(18) = 0.40625
pt(19) = B + 0.5 - 0.875: pt(20) = 0.34375
pt(21) = B + 0.5 - 0.0625: pt(22) = 0.34375
pt(23) = B + 0.5 - 0.0625: pt(24) = 0.25
pt(25) = B + 0.5 - 1.08839: pt(26) = 0.25
pt(27) = B + 0.5 - 1.27589: pt(28) = 0.0625
pt(29) = 0.0625: pt(30) = 0.0625
pt(31) = 0.0625: pt(32) = A + 0.5 - 0.0625
pt(33) = 0.15625: pt(34) = A + 0.5 - 0.0625
pt(35) = 0.15625: pt(36) = A + 0.5 - 1
objent.Closed = True
objent.Update

Set objpersistent = objent
End Sub
```

the first task is to draw a single polyline object. You cannot automate an object if you cannot draw it. if you have drawn it 100 times you will do a better job automating it than if you have barely drawn it. Just like plotting a curve, place it as conveniently as possible at 0,0. Visualize it sitting on the xy axis. Keep your parametric variables as convenient as possible. Start listing points by moving in the direction of the positive X axis. sweep around the screen in a counterclockwise direction as a standard procedure. Locate the first point at 0,0 if possible. The second point should be to the right. Use excel to list just the X values. it is simpler to separate the listings of X and Y values and measure or calculate them separately. Work in the parametric calculations as you go. Make dimensions and measurements on the drawing as needed. After you have gone all around the object listing the X values, do the same thing for Y. This is the key step. The program listing above that dimensions an array (1 to 36) for 18 points then loads a value into each index location is the brute force normal straightforward approach. We can instead create a polyline wrapper program. The array function only requires the data to be separated by commas. the Polyline method will not use that array directly, but we can create a transfer method in the wrapper. the wrapper will also measure the length of the array. Create a new sub and paste the values just entered into the spreadsheet into the sub.

```Sub new_poly_draw(A As Double, B As Double)
Dim pt As Variant
pt = array(

Paste here

Call draw_array(pt)
End Sub
```
```Sub new_poly_draw(A As Double, B As Double)
Dim pt As Variant
pt = array(

0   0
B-1.25  0
B-1.0625    0.1875
B 0.1875
B 0.40625
B-.875  0.40625
B-.875  0.34375
B-.0625 0.34375
B-.0625 0.25
B-1.0884    0.25
B-1.2759    0.0625
0.0625  0.0625
0.0625  A-.0625
0.15625 A-.0625
0.15625 A-1
0.21875 A-1
0.21875 A
0   A

Call draw_array(pt)
End Sub
```

now put commas between values and put your line continuations wherever you want.

```Sub new_poly_draw(A As Double, B As Double)
Dim pt As Variant

pt = Array(0, 0, B - 1.25, 0, B - 1.0625, 0.1875, _
B, 0.1875, B, 0.40625, B - 0.875, 0.40625, B - 0.875, 0.34375, _
B - 0.0625, 0.34375, B - 0.0625, 0.25, B - 1.0884, 0.25, B - 1.2759, 0.0625, _
0.0625, 0.0625, 0.0625, A - 0.0625, 0.15625, A - 0.0625, 0.15625, A - 1, _
0.21875, A - 1, 0.21875, A, 0, A)

Call draw_array(pt)
End Sub
```

and you are done, because you have this wrapper to run it

```Sub draw_array(pt As Variant)
Dim pt2() As Double
Dim i As Integer
Dim lower As Integer, upper As Integer
lower = LBound(pt)
upper = UBound(pt)

ReDim pt2(lower To upper)
For i = lower To upper
pt2(i) = pt(i)
Next i

objent.Closed = True
objent.Update

Set objpersistent = objent

End Sub
```

objpersistent is a public variable that allows you to move this piece into position in the calling program.

# Autocad VBA Parametrics – 1 – Wrapper functions

Sometimes it takes a long time to make something simple. Creating wrapper subroutines makes large parametric programs cleaner and easier to read, allowing them to get larger and more useful. If you straightforwardly code each routine containing all the details it uses, you soon reach a point where the program is too complex looking to modify. Wrapper routines delegate the details of the drawing and make the flow of the calling programs easier to follow.

The ADDLINE method in Autocad VBA requires an array of 3 doubles for each endpoint of the line.

```Dim lineobj as AcadLine
Dim pt1(0 To 2) As Double
Dim pt2(0 To 2) As Double
pt1(0) = 2: pt1(1) = 3: pt1(2) = 0
pt2(0) = 40: pt2(1) = 50: pt2(2) = 0
```

By contrast the same thing can be done in autolisp with one line of code.

(command “line” (list 2 3) (list 4 5) “”)

Autocad VBA does not allow any shortcuts. Every line drawn has to use a dimensioned named assigned array of 3 doubles.

An Autocad VBA parametric drawing program would quickly require too many points to be practical. Wrapper functions are the solution. I am using the term in an informal way to indicate wrapping a VBA Autocad method to make it easier to use. Here is a simple line wrapper and how it is called.

call line(2, 3, 40, 50)

``` Sub line(x1 As Double, y1 As Double, x2 As Double, y2 As Double)
Dim pt1(0 To 2) As Double
Dim pt2(0 To 2) As Double
pt1(0) = x1: pt1(1) = y1: pt1(2) = 0
pt2(0) = x2: pt2(1) = y2: pt2(2) = 0
End Sub
```

Every Autocad VBA object is a candidate for one or more wrappers. The point command in autocad is not used very often in design (its very useful in the graphing calculator), but it could be easily simplified.
Call draw_point(1,3,0)

```Sub draw_point(x1 As Double, y1 As Double, z1 As Double)
Dim pt1(0 To 2) As Double
pt1(0) = x1: pt1(1) = y1: pt1(2) = z1
End Sub
```

An array of 3 doubles is Autocad VBA’s normal way of specifying a point location used by dozens of different objects. We can make a wrapper to aid making this array. When passing arrays as arguments to a subroutine they are always passed by reference – any changes made to the array in the called program are reflected in the calling program. Unfortunately we cannot get the wrapper to dimension the array for us, but we can simplify the values assignment a little. It does help when a lot of points are being set up.

Dim pt2(0 to 2) as Double
Call initpt(pt2, 2, 4, 0)

```Sub initpt(ByRef ptn() As Double, val1 As Double, val2 As Double, val3 As Double)
ptn(0) = val1: ptn(1) = val2: ptn(2) = val3
End Sub
```

The ADDLIGHTWEIGHTPOLYLINE method requires a single array of doubles, one value for each x and y. A line with two points would require an array with 4 values.

```Dim plineobj As AcadLWPolyline
Dim pt(1 To 4) As Double
pt(1) = 2: pt(2) = 3: pt(3) = 40: pt(4) = 50
```

The LightWeightPolyline method can be wrapped in a box routine. Box is drawn from lower left counterclockwise. The last segment is made with the closed property. In this case a layer is specified. It could be left out of the routine or made optional.
Call mbox(0, 0, L, W, “Hidden”)

```Sub mbox(x1 As Double, y1 As Double, x2 As Double, y2 As Double, strlayer As String)
Dim pt(1 To 8) As Double
pt(1) = x1: pt(2) = y1
pt(3) = x2: pt(4) = y1
pt(5) = x2: pt(6) = y2
pt(7) = x1: pt(8) = y2
objent.Closed = True
objent.layer = strlayer
End Sub
```

Rectangular boxes show up a lot in any design. Any plane figure with a known number of vertexes could be hard coded as above. If you have a parametric application that often uses a notched rectangle you would use a polyline wrapper expecting 6 points. Here is how the hardcoded 6 point polyline sub is coded. You can see the 12 inputs are starting to get tedious. This 6 pointed figure has 12 inputs and the 4 pointed figure above has 4 inputs because this figure does not have to have rectangular angles, it simply draws 6 points. the box above is assumed to be square with the coordinate system.

```Sub test_p6()
Dim L As Double, W As Double, A As Double, B As Double
L = 72
W = 24
A = 12
B = 18
Call p6_box(0, 0, L - B, 0, L - B, A, L, A, L, W, 0, W)
End Sub

Sub p6_box(p1 As Double, p2 As Double, p3 As Double, p4 As Double, p5 As Double, p6 As Double, _
p7 As Double, p8 As Double, p9 As Double, p10 As Double, p11 As Double, p12 As Double)

Dim pt(1 To 12) As Double
pt(1) = p1: pt(2) = p2
pt(3) = p3: pt(4) = p4
pt(5) = p5: pt(6) = p6
pt(7) = p7: pt(8) = p8
pt(9) = p9: pt(10) = p10
pt(11) = p11: pt(12) = p12
objent.Closed = True
End Sub
```

The straightforward method to draw complex polyline figures would initially be coded all in one sub. The array would be dimensioned then loaded with values and immediately be given to the AddPoly method. This is a closed figure with 16 points.

``` Dim objent As AcadLWPolyline
Dim pt(1 To 32) As Double
pt(1) = 1: pt(2) = 0.21875
pt(3) = 0: pt(4) = 0.21875
pt(5) = 0: pt(6) = 0
pt(7) = W - 1.25: pt(8) = 0
pt(9) = W - 1.0625: pt(10) = 0.1875
pt(11) = W: pt(12) = 0.1875
pt(13) = W: pt(14) = 0.40625
pt(15) = W - 0.875: pt(16) = 0.40625
pt(17) = W - 0.875: pt(18) = 0.34375
pt(19) = W - 0.0625: pt(20) = 0.34375
pt(21) = W - 0.0625: pt(22) = 0.25
pt(23) = W - 1.08839: pt(24) = 0.25
pt(25) = W - 1.27589: pt(26) = 0.0625
pt(27) = 0.0625: pt(28) = 0.0625
pt(29) = 0.0625: pt(30) = 0.15625
pt(31) = 1: pt(32) = 0.15625
objent.Closed = True
```

A general method can be devised using a generic wrapper that tests the length of the array passed. However the poly method only accepts an array of doubles, and there is no way to conveniently construct an array of doubles except by declaring the index numbers of each value as above. The array function is easier to construct, but it only works with a variant declared. That is what we will use to construct the point list, then convert it in the wrapper, which can accept an array of any size.

```Sub test_draw_array()
Dim W As Double
W = 24
Dim pt As Variant
pt = Array(1, 0.21875, 0, 0.21875, _
0, 0, W - 1.25, 0, _
W - 1.0625, 0.1875, W, 0.1875, _
W, 0.40625, W - 0.875, 0.40625, _
W - 0.875, 0.34375, W - 0.0625, 0.34375, _
W - 0.0625, 0.25, W - 1.08839, 0.25, _
W - 1.27589, 0.0625, 0.0625, 0.0625, _
0.0625, 0.15625, 1, 0.15625)
Call draw_array(pt)
End Sub

Sub draw_array(pt As Variant)
Dim pt2() As Double
Dim i As Integer
Dim lower As Integer, upper As Integer
lower = LBound(pt)
upper = UBound(pt)

ReDim pt2(lower To upper)
For i = lower To upper
pt2(i) = pt(i)
Next i