Part Two - Spiral


I will break down the program into sections and, in turn, analyze each. Depending on feedback received in the forum, I will add more details to these columns.

Analyze Code

Portions of the column showing code will be in green.


Define Main Function

(defun C:SPIRAL (/ #DIAMETER #PITCH)
...

The new SPIRAL command is defined with the defun function. The C: in front of the function name makes it available from the command line without entering the lisp interpreter.

The second part of the line is a list of variable names that need to be kept local to the program. My personal convention is to use a "#" sign as the first character of every variable name that I know should be kept local. Another convention is to indent one character for each open parenthesis. That makes it easier to track the nested levels that will have to be closed.

When first testing and debugging a lisp program, I often leave many of the variables out of this list so that they are easy to inspect at the command line. Notice that the #ANGLE-DELTA variable is absent. After running the program, you can check what it's value is:

Command: !#ANGLE-DELTA
0.392699

Try it with a variable that was in the variable list:

Command: !#PITCH
nil

Since #PITCH is in the local variable list, it's value is only accessible while the program is running. So we see nil instead.


Get User Input

...
 (setq #DIAMETER (getreal "\nDiameter: "))
 (setq #PITCH (getreal "\nPitch: "))
 (setq #COILS (getint "\nNumber of coils: "))
 (setq #POINT-0 (getpoint "\nCenterpoint: "))
...

Since the program requires input from the user, we use several getXXX type functions. getreal for real numbers, getint for integers (whole numbers), and getpoint for an XYZ point value.

>(setq #DIAMETER (getreal "\nDiameter: "))
This line sets the #DIAMETER variable to the value that the user enters in response to the getreal prompt. It is a "real" number so it can be any decimal value. #DIAMETER represents the diameter of the coils that will be drawn. The "\n" at the start of the prompt string causes a new line to be started.

>(setq #PITCH (getreal "\nPitch: "))
Sets the #PITCH variable to the users supplied value. It too, is a real number. #PITCH represents the vertical distance between coils.

>(setq #COILS (getint "\nNumber of coils: "))
Sets the #PITCH variable to a user supplied integer. The number of coils could actually be a decimal number (real) but I chose to use a whole number (integer) in order to simplify other portions of the program. A real number would have allowed the last coil to be a partial coil which would have to be dealt with as a special case.

>(setq #POINT-0 (getpoint "\nCenterpoint: "))
Sets the #POINT-0 variable to the user supplied point. The getpoint function allows the user the either enter point coordinates at the keyboard or pick a point in the AutoCAD drawing. #POINT-0 will be the starting point for the spiral.

If the user does not enter a value for any one of the prompts, the program will fail when it tries to draw the spiral. We can fix this potential problem later.


Set Other Variables

...
 (setq #RESOLUTION 16)
 (setq #RADIUS (* #DIAMETER 0.5))
 (setq #ANGLE-DELTA (/ (* 2 pi) #RESOLUTION))
 (setq #ANGLE 0.0)
 (setq #SEGMENTS (* #COILS #RESOLUTION))
 (setq #Z-DELTA (/ #PITCH #RESOLUTION))
 (setq #Z 0.0)
 (setq #LIST nil)
...

The program does some preliminary calculations and sets some other variables before it can generate the list of points to draw the spiral.

>(setq #RESOLUTION 16)
The #RESOLUTION variable is used to determine how many points make up each coil. You could set this to another value or prompt the user for a value, but a value of 16 should be fine for most cases. A higher resolution increases the accuracy of the circular coils.

>(setq #RADIUS (* #DIAMETER 0.5))
Since the radius value will be more convenient to use in the calculations, the #DIAMETER value is multiplied by 0.5 to yield the value stored in the #RADIUS variable.

>(setq #ANGLE-DELTA (/ (* 2 pi) #RESOLUTION))
The #ANGLE-DELTA variable it used to store the incremental angle between each point around the perimeter of the coils. Angles in lisp are stored in radians, so pi is used to calculate the angle between each point.

>(setq #ANGLE 0.0)
#ANGLE is the starting angle for the first coil in the spiral.

>(setq #SEGMENTS (* #COILS #RESOLUTION))
#SEGMENTS is the total number of segments or that make up the spiral.

>(setq #Z-DELTA (/ #PITCH #RESOLUTION))
#Z-DELTA is the incremental distance between each point in the Z axis as we move around the coils. It is the distance between coils or #PITCH, divided by the number of point in each coil or #RESOLUTION.

>(setq #Z 0.0)
#Z is the starting elevation above #POINT-0 for the first coil in the spiral.

>(setq #LIST nil)
Since the program does not yet have the complete list of local variables called out, I had to set #LIST to nil. That is because I will be adding to #LIST with the cons function and I need to start with a blank list. Later, I will add #LIST to the local variables so that it will be nil when the program starts. At that time, this line will not be needed.


Generate List of Points

...
 (repeat #SEGMENTS
  (setq #POINT-1 (polar #POINT-0 #ANGLE #RADIUS))
  (setq #POINT-1 (list (car #POINT-1)
                       (cadr #POINT-1)
                       (+ (caddr #POINT-1) #Z)))
  (setq #LIST (cons #POINT-1 #LIST))
  (setq #ANGLE (+ #ANGLE #ANGLE-DELTA))
  (setq #Z (+ #Z #Z-DELTA))
  )
...

The program steps around the coils and increases the elevation as it builds a list of points.

>(repeat #SEGMENTS
This line starts the repeat function. Everyting that comes before the closing ")" will be repeated the number of times specified by #SEGMENTS.

>>(setq #POINT-1 (polar #POINT-0 #ANGLE #RADIUS))
For each pass through the repeat function, the polar coordinates of the next point on the coil is calculated.

>>(setq #POINT-1 (list (car #POINT-1) (cadr #POINT-1) (+ (caddr #POINT-1) #Z)))
The #Z value is added to the Z coordinate of the XYZ point.

>>(setq #LIST (cons #POINT-1 #LIST))
The point value is added to a list of points. When the first point is added, the cons function creates the list since one does not yet exist. Subsequent points are added to the same list.

>>(setq #ANGLE (+ #ANGLE #ANGLE-DELTA))
The #ANGLE variable is incremented by #ANGLE-DELTA in preparation for calculation of the next point.

>>(setq #Z (+ #Z #Z-DELTA))
The #Z elevation variable is incremented by #Z-DELTA in preparation for calculation of the next point.


Draw Spiral Spline

...
 (setq #LIST (reverse #LIST))
 (command "_.spline")
 (foreach #POINT #LIST (command #POINT))
 (command "" "" "")
 )

The program uses the list of points to draw the spiral spline.

>(setq #LIST (reverse #LIST))
Since the cons function builds the list by adding to the front, I reversed the list to make it the correct order. I did this just to have the spiral draw from bottom to top. The end result would look the same either way.

>(command "_.spline")
This line starts the spline command.

>(foreach #POINT #LIST (command #POINT))
Each point in the list is fed to the command interpreter which is still expecting point inputs for the spline command that was started in the previous line of code.

>(command "" "" "")
The first "" instructs the spline command to end it's request for more points. The next two "" are in response to the spline commands request for start and end tangency points. The program merely accepts the defaults. The tangency points should really be calculated to a much higher precision than what the defaults give.


You can run the AutoCAD spline command to see the sequence that it uses.

Part Three will detail the modifications needed to have accurate tangency points. I'll also start cleaning the code up a little bit.


1  2  3  4  5  6