More Turtle Graphics

A basic class implementation for turtle graphics in autocad –

The basic idea of turtle graphics is that the pen has a location and a direction. The instruction to draw a line only needs distance. The ending point is the new current location. To translate distance at angle from a given point to coordinates the Sin and Cosine are used.

The public class variables are x1, y1 – the current location, the beginning of the line to be drawn, and x2, y2, the calculated end points. Heading is a private double. It is private so that the class can always keep the angle heading between 0 <= heading < 360 no matter how many cumulative turns. A boolean PEN variable allows a PENUP or PENDOWN state.

Turtle1.FD 6 draws a line 6 units (assuming PEN is DOWN). Turtle.Left 45 and Turtle.Right 45 turn heading to the left or right 45 degrees or any number. Input to the user is in degrees. The class module converts to radians in private.

some example code using the turtle class –


Sub turtle_demo_A()
connect_acad

Dim turtle1 As CTurtle
Set turtle1 = New CTurtle

Debug.Print turtle1.x1
Debug.Print turtle1.y1
Debug.Print turtle1.pen
'x1 and y1 are zero
'pen is true
'these are class defaults

Debug.Print turtle1.heading
'access to private variable pheading is thru LET and GET HEADING
'class_initialize default is 90

turtle1.heading = 540
Debug.Print turtle1.heading
'property Let heading (double)
'seems like an argument but you call LET with assignment
'property let heading sets and controls value between 0 <= pheading < 360
'heading is 180

turtle1.heading = turtle1.heading + 180
Debug.Print turtle1.heading
'heading is 0
'just like A = A + 3
'right side of equation is GET and left side is LET

turtle1.heading = turtle1.heading - 45
Debug.Print turtle1.heading
'heading is 315

turtle1.left 90
Debug.Print turtle1.heading
'heading is 45

turtle1.right 90
Debug.Print turtle1.heading
'heading is 315

turtle1.heading = turtle1.heading + 180
Debug.Print turtle1.heading
'heading is 135

turtle1.fd 12
'line is 45 to left

End Sub

all this boils down to –


Sub turtle_demo_B()
connect_acad

Dim turtle1 As CTurtle
Set turtle1 = New CTurtle

turtle1.heading = 45
turtle1.x1 = 2
turtle1.y1 = 3

turtle1.fd 12
End Sub

The Class Module code for the turtle to draw a line contains the trigonometry to calculate the point at dist and angle. It draws the line using the familiar AddLine method with EndPoints as an array of 3 doubles, xyz. This is a 2D implementation, Z is always zero for now but does not have to be.


Public Sub fd(dist As Double)
'assumes x1 y1 and heading
x2 = x1 + dist * Cos(ang2rad(pheading))
y2 = y1 + dist * Sin(ang2rad(pheading))

If pen Then
Call drawline(x1, y1, x2, y2)
End If

'updates to new position
x1 = x2
y1 = y2
End Sub

Sub drawline(x1 As Double, y1 As Double, x2 As Double, y2 As Double)
'internal sub to draw line
Dim acadline As acadline
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

Set acadline = acadDoc.ModelSpace.AddLine(pt1, pt2)
Update
End Sub

Drawing the same line in code with no class implementation –


Sub turtle_demo_C()
connect_acad

Dim acadline As acadline
Dim pt1(0 To 2) As Double
Dim pt2(0 To 2) As Double

Dim x1 As Double, y1 As Double
Dim x2 As Double, y2 As Double

Dim ang As Double
Dim dist As Double

x1 = 2
y1 = 3
ang = 45
dist = 12

x2 = x1 + dist * Cos(ang2rad(ang))
y2 = y1 + dist * Sin(ang2rad(ang))

pt1(0) = x1: pt1(1) = y1: pt1(2) = 0
pt2(0) = x2: pt2(1) = y2: pt2(2) = 0

Set acadline = acadDoc.ModelSpace.AddLine(pt1, pt2)
acadApp.Update
End Sub

Instead of doing the calculations, Autocad provides the Utility PolarPoint to do the trig. Polarpoint returns an array. Autodesk help uses a Variant to capture it, but a dynamic array works fine (see pt2).


Sub turtle_demo_D()
connect_acad

Dim acadline As acadline
Dim pt1(0 To 2) As Double
Dim pt2() As Double

Dim x1 As Double, y1 As Double

Dim ang As Double
Dim dist As Double

x1 = 0
y1 = 0
ang = 45
dist = 12

'these are commented out
'x2 = x1 + dist * Cos(ang2rad(ang))
'y2 = y1 + dist * Sin(ang2rad(ang))

pt1(0) = x1: pt1(1) = y1: pt1(2) = 0
pt2 = acadDoc.Utility.PolarPoint(pt1, ang2rad(ang), dist)

Set acadline = acadDoc.ModelSpace.AddLine(pt1, pt2)
acadApp.Update
End Sub

We can further simplify this process with a function to populate point arrays. Every Point can be declared as a dynamic array.


Function Pt(x As Double, y As Double, z As Double) As Double()
Dim pnt(0 To 2) As Double
pnt(0) = x: pnt(1) = y: pnt(2) = z
Pt = pnt
End Function

Sub turtle_demo_E()
connect_acad

Dim acadline As acadline
Dim pt1() As Double
Dim pt2() As Double

Dim ang As Double
Dim dist As Double

ang = 45
dist = 12

pt1 = Pt(2, 3, 0)
pt2 = acadDoc.Utility.PolarPoint(pt1, ang2rad(ang), dist)

Set acadline = acadDoc.ModelSpace.AddLine(pt1, pt2)
acadApp.Update
End Sub

we can streamline a little bit more with a dedicated Line sub-routine. now lets compare the turtle and more conventional autocad methods. Each will draw a line and turn before drawing the next. Using dynamic arrays, what was pt2 can become pt1 with a simple assignment. That is not possible with the conventional static array where points are declared as – Dim PT1 (0 to 2) as Double.


Sub turtle_demo_F()
connect_acad

Dim turtle1 As CTurtle
Set turtle1 = New CTurtle

turtle1.heading = 30
turtle1.x1 = 1
turtle1.y1 = 2

turtle1.fd 12
turtle1.left 30
turtle1.fd 12

Dim pt1() As Double
Dim pt2() As Double

Dim ang As Double
Dim dist As Double

ang = 45
dist = 12

pt1 = Pt(1, 2, 0)
pt2 = acadDoc.Utility.PolarPoint(pt1, ang2rad(ang), dist)
line1 pt1, pt2

pt1 = pt2
pt2 = acadDoc.Utility.PolarPoint(pt1, ang2rad(ang + 30), dist)
line1 pt1, pt2

acadApp.Update
End Sub

Turtle graphics has geometry implications, start here, go forward, turn and repeat. Turtle graphics is local with a simple interface and limited command set. Coordinate graphics is a global grid, but its interface can also be simplified. The two approaches might be able to work together.

A random star generator –


Sub turtle_demo_6()
init_turtle
Dim dblsize As Double
Dim inc As Integer

For inc = 1 To 480

dblsize = rnddbl(0, 360)
turtle1.heading = dblsize

dblsize = rnddbl(0, 1024)
turtle1.x1 = dblsize

dblsize = rnddbl(512, 1024)
turtle1.y1 = dblsize

dblsize = rnddbl(2, 17)
star_5 dblsize

Next inc

End Sub

Sub star_5(dblsize As Double)

For i = 1 To 5
turtle1.fd dblsize
turtle1.right 144
Next i

End Sub

Function rnddbl(upr As Double, lwr As Double) As Double
' Randomize
' better results without Randomize

rnddbl = CDbl((upr - lwr + 1) * Rnd + lwr)
End Function

I cannot get the language tag VB to stick anymore. Thats why the code doesnt stand out from the text. WordPress challenges me.

Advertisements

Vector Class Tweaks

I have been working on Vector class VBA code to draw Autocad lines as vectors. The first version separated 2D and 3D vectors into two different classes. One reason to do this is the vector angle is figured differently. Another is I wanted to use a 2D or a 3D arrow head. Many math books, analytic geometry or calculus, introduce them in two different chapters. The goal is to make VBA vector algebra notation as similar as possible to math notation. Or at least readable. Now I have combined the classes, just ignoring any difficulty, to eliminate duplicated code, while i use and tweak. I also removed the addition and scalar multiplication from the class module to make the parameter list complete and not a function of one of the vectors (tho for mult this was not necessary, it will be useful for dot product). A work in progress. I will post code when i am satisfied with it.

3D Vector Angle

The angle of a 3D vector is the angle from the vector directly to each of the 3 positive axes. The angles are named alpha (x-axis), beta(y-axis) and gamma(z-axis). They are always positive and between 0 and 180 inclusive. They are called direction angles.

The arrowhead is inserted at rotation angle 0 at the endpoint of the vector then rotated. 2D rotation rotates around a point that is actually an axis normal (perpendicular) to the current UCS. The easiest way to rotate the arrowhead into position is to make a UCS that includes the plane of the alpha (or beta or gamma) angle between the vector and the axis. if the vector does not have a startpoint at the origin, a line parallel to the axis at the startpoint of the vector is used. after the ucs is made, the arrowhead is inserted and rotated by the alpha angle. the same procedure works for the other axes and direction angles.

how can the arrowhead be moved into position with only one rotation? If the vector is located by two angles, the third angle can be calculated. Imagine standing up a vector and dimensioning two angles, the other angle is fixed. the first rotation is the rotation of the ucs, the second angle is the alpha and final rotation needed.

The VBA UCS method uses 3 points, a new origin, a point on the postive new x-axis and a point on the positive new y-axis. Its very easy to work with but it required 3 points that form a right angle. Draw a line from the end of the vector perpendicular to the axis. that is the projection of the vector on the axis and the new origin point on the axis. another point any distance in the direction of the positive axis is the second point. The new y-axis point is the endpoint of the vector.

 '3D Vectors
   set_wcs
  
     Set blockrefObj = acadDoc.ModelSpace.InsertBlock(endpt2, blkname, 0.375, 0.375, 0.375, 0)
     
     Call set_ucs(px2, py1, pz1, _
                  px2 + 1, py1, pz1, _
                  px2, py2, pz2, _
                  "UCS_alpha")
      
         blockrefObj.Rotate endpt2, alpha
 
      'these are just for reference
       Call set_ucs(px1, py2, pz1, _
                  px1, py2 + 1, pz1, _
                  px2, py2, pz2, _
                  "UCS_beta")

       Call set_ucs(px1, py1, pz2, _
                  px1, py1, pz2 + 1, _
                  px2, py2, pz2, _
                  "UCS_gamma")
   set_wcs
  acadApp.Update
 


Sub set_ucs(x1 As Double, y1 As Double, z1 As Double, _
            x2 As Double, y2 As Double, z2 As Double, _
            x3 As Double, y3 As Double, z3 As Double, strname As String)
   'pt1 is origin, pt2 is xaxis, pt3 is yaxis
    
    Dim ucsObj As AcadUCS
    Dim originPt(0 To 2) As Double
    Dim xAxisPt(0 To 2) As Double
    Dim yAxisPt(0 To 2) As Double
    
   originPt(0) = x1: originPt(1) = y1: originPt(2) = z1
   xAxisPt(0) = x2: xAxisPt(1) = y2: xAxisPt(2) = z2
   yAxisPt(0) = x3: yAxisPt(1) = y3: yAxisPt(2) = z3

   Set ucsObj = acadDoc.UserCoordinateSystems.Add(originPt, xAxisPt, yAxisPt, strname)
    acadDoc.ActiveUCS = ucsObj

 End Sub

3D Vector Class

Two vectors can be added. Three vectors can be added. Three vectors at right angles on the axes can be resolved as components of any 3D vector.


Sub test_3d_vector3()
Call connect_acad

Dim vs As C3DVector
Dim vt As C3DVector
Dim vu As C3DVector
Dim vv As C3DVector

Set vs = New C3DVector
vs.pts_xyz 0, 0, 0, 3, 0, 0

Set vt = New C3DVector
vt.pts_xyz 0, 0, 0, 0, 4, 0

Set vu = New C3DVector
vu.pts_xyz 0, 0, 0, 0, 0, 5

vs.draw "s"
vt.draw "t"
vu.draw "u"

Set vv = v3D_3_add(vs, vt, vu)
vv.draw "s + t + u"

acadApp.Update
End Sub


Sub test_3d_vector5()
Call connect_acad

Dim i As C3DVector
Dim j As C3DVector
Dim k As C3DVector

Set i = New C3DVector
i.pts_xyz 0, 0, 0, 1, 0, 0

Set j = New C3DVector
j.pts_xyz 0, 0, 0, 0, 1, 0

Set k = New C3DVector
k.pts_xyz 0, 0, 0, 0, 0, 1

i.draw "i"
j.draw "j"
k.draw "k"

Dim rx As C3DVector
Dim ry As C3DVector
Dim rz As C3DVector

Set rx = v3D_scalar(3, i)
Set ry = v3D_scalar(4, j)
Set rz = v3D_scalar(5, k)

Dim resultant As C3DVector
Set resultant = v3D_3_add(rx, ry, rz)

rx.draw "rx"
ry.draw "ry"
rz.draw "rz"
resultant.draw "resultant"

acadApp.Update
End Sub

Vector Class in Autocad VBA

Vectors have length, direction and angle. In engineering and science, vectors can be moved tip to tail to add forces and create a resultant vector. Vectors are equal if magnitude and direction are equal. Position is arbitrary. 2D vectors can fully describe with one (x,y) pair. When the vector starts at (0,0), the end point is also the delta x and delta y.

The autocad line already has everything it needs to be a vector. The direction is saved in separate variables for start and end point. The acadline object in VBA has all the properties required of a vector – StartPoint, EndPoint, Length, Angle and Delta. Delta is the vector. Delta is returned as a 3 place array of doubles, exactly the same as StartPoint and EndPoint, but it’s not a point, it’s (x2-x1, y2-y1, z2-z1). We save it to a variable as if it were a point. We use it to do vector algebra – the algebra of lines.

An object vector is given a variable name, such as U, V, W, S or T, usually lower case and bold, and specified to be equal to the delta values.

angle brackets (this wordpress is html and it will not allow angle brackets for any purpose other than the one it has in its mind) are used to show (x,y) (imagine angle brackets) is not the same thing as (x,y).

Vectors are added by adding their delta values. if u = (ux,uy) and t = (tx,ty) then u + t = (ux+tx, uy+ty). Vectors can also be subtracted and scaled. The angle and length they make can be calculated from the delta. Vectors of different length and angle are easily added to form a new vector with new length and new direction.

A great deal of engineering mechanics and physics vector work can be done just by drawing lines of appropriate length and direction and moving the vectors tip to tail, measuring the resultants.

The parallelogram rule states that if two vectors are to be added, draw them as adjacent sides of a parellogram, the diagonal is the sum. If 3 or more vectors to be added, they are moved one after the other and the resultant is start point to finish.

The math of adding, subtracting and scaling vectors is simple. Creating a vector class in VBA allows the formation, properties and rules of calculations to be formalized. This is a first simple draft.

Vectors in different locations with the same length and direction are declared equal. Math is done on vectors regardless of position. If a vector start point is at 0,0, that is declared to be standard position. In order to physically draw the vector in autocad, we have to have a position. So we may want to specify vectors assuming the start point is 0,0 with length and angle parameters. Or we may want to specify with a single point value as the endpoint. Other vectors we may want to specify the start point and parameters. That gives us 5 possible ways to specify a vector. The first two assume 0,0 as the start point. Using (x1,y1) as start point, and (x2,y2) as endpoint –

vector1 (x2,y2)
vector2 (Length, Theta)
vector3 (x1,y1, x2,y2)
vector4 (x1,y1, Length, Theta)
vector5 (x1,y1,x_delta, y_delta)

In VBA classes, the variables used may be hidden from the user, while the names of property subs are the interface. We use the good names for properties and reference names for the variables. We want (i think) to save all required data even though it is redundant. The angle and length can be calculated from the point values, and vice versa, so they have to be consistent.

property names – variables
3 place array of doubles
pt1 – startpt1
pt2 – endpt2
delta – pdelta

simple doubles
angle – pangle
length – plength
delta_x – pdelta_x
delta_y – pdelta_y
x1 – px1
x2 – px2
y1 – py1
y2 – py2

Properties are implemented with Get (return of value from variable) and Let (assignment of value to variable) statements. The easiest way to start them is to use the VBA editor pulldown under Insert Procedure and click Property. It will add each. We dont really need Let statements yet in this first implementation. Variables are assigned when the vector is first initialized in a single sub procedure. (I am going to post screenshots while this is still in a state of flux.)

The angle of the vector can be calculated from Tangent of delta y over delta x, but tangent has a period of 180 degrees and vectors rotate from 0 to 360, not including 360, so i wrote a simple program to test in each quadrant.

When the vector is specified with length and angle, the initialization sub can be shortened, so it is not redundant. (SLA here stands for Start, Length, Angle)

The vector.draw method –
This arrow is not integral. It is a 1-unit long block inserted at the end point of the line at a scale that looks good using the angle of the vector. The vector line is full length of course, so on-screen calculations work. The arrow can be erased. A switch could be added in a more polished program to arrow or not arrow. The optional parameter adds a text label at the midpoint of the vector.

This is all we need to get started with vectors. The calculation programs are external to the class.

Here is a test demo and the autocad output.

The math is simple, but we will not be able to use VBA symbols “+ – *” for vector addition, subtraction and multiplication. Those are reserved. Subtraction is accomplished by using a -1 scalar factor and adding the negative vector. Functions return a vector object. We need to set up a vector variable to receive the result.

another interesting application of the parallelogram rule is that one diagonal is the sum, the other is the difference.

Autocad Point Variable

(Edit – this is an important post, to emphasize, autocad vba help always declares point values as a static 3 place array (0 to 2) of doubles. A better way is to always declare the variable as a dynamic array and fill them with a function. The function code is the takeaway. Look at the first two PT examples. The first sub PT1 uses standard method static (0 to 2) array. This is an improvement over manually filling points and many of my older posts use this to clean up the code. The second function PT uses a static array internally but returns a dynamic array. Look at the assignments for each method. Using a function is cleaner. With dynamic arrays you can duplicate points by assignment. At the end is a discussion of autocad method polarpoint (typical of many methods) that returns an array value that cannot be received by a static array so autocad help uses a variant. A dynamic array works just as well and is my preferred method for all point values.)

All of Autocad graphics methods use a 3 place array of doubles for the coordinates.

The simplest way to sub this is by passing the static array for the sub to fill data.

Dim PtA(0 To 2) As Double
Call pt1(ptA, 1, 2, 0)

Sub pt1(ByRef pnt() As Double, x As Double, y As Double, z As Double)
pnt(0) = x: pnt(1) = y: pnt(2) = z
End Sub

This changes ptA in the calling program. Another way (a better way) is to make a function and pass the array back to a receiving variable.
This requires a dynamic array.

Dim ptA() As Double
ptA = Pt(1, 2, 0)

Function Pt(x As Double, y As Double, z As Double) As Double()
Dim pnt(0 To 2) As Double
pnt(0) = x: pnt(1) = y: pnt(2) = z
Pt = pnt
End Function

This is a better way to deal with point values.

We can even get away with this – no variables at all.

Set lineAB = acadDoc.ModelSpace.AddLine(Pt(1, 4, 0), Pt(9, 1, 0))

Dynamic arrays can accept point values. A lot of objects in autocad return point values. Autocad VBA help always uses a single variant to hold the array, because their usual point is a static array and you cannot assign to that. The same thing can be done by just dispensing with the static array all the time.

For example – the utility polarpoint method returns a point value. A variant has to be declared to receive it.

from help –
RetVal = object.PolarPoint(Point, Angle, Distance)
Point Type: Variant (three-element array of doubles)
Return Value Type: Variant (three-element array of doubles)

example from help –
Dim polarPnt As Variant
Dim basePnt(0 To 2) As Double
polarPnt = ThisDrawing.Utility.PolarPoint(basePnt, angle, distance)
Set lineObj = ThisDrawing.ModelSpace.AddLine(basePnt, polarPnt)

PolarPoint returns an array. You cannot assign to a static array. Dynamic arrays can assign. If the declaration is to a dynamic array with the correct type Double it works. There is no reason I can think of to ever declare a point except as a dynamic array.

Dim polarPnt() As Double