Thursday, May 28, 2009

AutoLISP variable scope and "local globals"

I've been programming with AutoLISP for around 15 years, but I never realized there is more to variable scope in AutoLISP/Visual LISP than a simple local vs. global scope dichotomy.

As a primer to those who are unfamiliar with variable scope, here is a little example you can try at the AutoCAD command line. I've intersperse the example with my comments that have semi-colons preceding them:


;;; AutoLISP variable scope tutorial
;;; Copyleft 2009 GNU GPL style by Thomas Gail Haws
;;;
;;; Type these code snippets at the AutoCAD command line
;;; to observe the effect of variable scope.
;;;
;;; First see what the TGH symbol (variable) returns.
Command: !tgh
nil
;;; Set tgh to 0
Command: (setq tgh 0)
0
;;; Write a function that sets tgh to 1
Command: (defun tgh1 () (setq tgh 1))
TGH1
;;; See that writing the function didn't change tgh
Command: !tgh
nil
;;; Invoke the function tgh1
Command: (tgh1)
1
;;; See that invoking the function changed tgh
Command: !tgh
1
;;; Write another function that changes tgh,
;;; but make it a LOCAL version of tgh that is changed.
Command: (defun tgh2 (/ tgh) (setq tgh 2))
TGH2
;;; See that defining the function didn't change
;;; the "outside" (global) version of tgh
Command: !tgh
1
;;; Invoke the tgh2 function.
;;; See it returns 2 because the last thing it did was
;;; set ITS LOCAL VERSION of tgh to 2
Command: (tgh2)
2
;;; See that invoking the tgh2 function didn't change
;;; the "outside" (GLOBAL) version of tgh.
Command: !tgh
1


What I discovered this year is that a variable that's global to function A can be local to a function B that calls A. In other words, if you don't make an "in-house" version of a variable in one function A, another function B that calls A can still make A work with B's local version of the variable. And that B/A version is not available to or visible to anything outside of B.

Here's an analogy. It is a lovely spring day in the town of Session. The flowers are in bloom. The East and the West family live in three bedroom houses, each with a brothers room, a sisters room, and a parents room. If we want to know the air particulate load at any location around the East or the West house, we need to know whether the location has its own inside (local) air, or instead follows and modifies the outside (global) air. For simplicity, there are no open windows--only open doors--, and particulate loads disperse and equalize immediately. Let's jump right into the AutoLISP code, with the story continuing as comments indicated by lines that start with semi-colons (;)


(defun Story ()
;;; To simplify reporting and seting pload, make two little functions first.
(defun princpload (location)
(princ (strcat "\nCurrent particulate load in " location " is "))
(princ pload)
(princ ".")
)
(defun setqpload (location)
(princ "\n")
(princpload location)
(setq pload (getint (strcat "\nNew particulate load in " location ": ")))
(princpload location)
(princ)
)
;;; Session has an ambient springtime air with a particulate load of 100.
(setq pload 100)
;;;Write a function to report and change the pload in Session.
(defun Session () (setqpload "Session"))
;;; The East family leaves their outside doors wide open.
;;; So their pload matches and changes the Session pload.
;;; We put the sisters and brothers rooms inside the house.
(defun East ()
(setqpload "East")
(EastSisters)
(setqpload "East")
(EastBrothers)
(setqpload "East")
)
;;; The West family keeps all their outside doors closed.
;;; So their pload is a local "inside" value independent of Session's pload.
;;; We make their pload local by adding in it the West function arguments list
;;; after the / forward slash.
;;; We put the sisters and brothers rooms inside the house.
(defun West (/ pload)
(setqpload "West")
(WestBrothers)
(setqpload "West")
(WestSisters)
(setqpload "West")
)
;;; All brothers have allergies, so their doors are always closed.
;;; We make their ploads local.
(defun EastBrothers (/ pload) (setqpload "East brothers"))
(defun WestBrothers (/ pload) (setqpload "West brothers"))
;;; All sisters care nothing about particulates, so their doors are open.
;;; We leave their ploads global.
(defun EastSisters () (setqpload "East sisters"))
(defun WestSisters () (setqpload "West sisters"))
)
(Story)


Now let's consider two scenarios, one with the brother rooms and a second with the sister rooms.

First consider the brother rooms. It should be simply intuitive that if a dust bag explodes in a brother room, it will affect only the particulate load in that room, and if the Session particulate load drops, it will affect neither brother room. Now load (or paste) the code above into a drawing session and then follow the steps below to see how the code behaves at the AutoCAD command prompt in the brothers scenario:


Command: (Session)

Current particulate load in Session is 100.
New particulate load in Session: 100
Current particulate load in Session is 100.

Command: (East)

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Current particulate load in East sisters is 100.
New particulate load in East sisters: 100
Current particulate load in East sisters is 100.

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Current particulate load in East brothers is nil.
New particulate load in East brothers: 500
Current particulate load in East brothers is 500.

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Command: (West)

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West brothers is nil.
New particulate load in West brothers: 600
Current particulate load in West brothers is 600.

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West sisters is nil.
New particulate load in West sisters:
Current particulate load in West sisters is nil.

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.


While their functions were running, the brother room particulate loads were independently higher than 100. But be aware that now the functions have ended, the independent loads have also gone away.

For scenario 2, consider what happens if a smaller dust bag explodes in the sister rooms. Each house (exclusive of its brother room) now has a particulate load to match its sister room. And the town of Session now has a particulate load to match which sister room? Let's see.


Command: (Session)

Current particulate load in Session is 100.
New particulate load in Session: 100
Current particulate load in Session is 100.

Command: (East)

Current particulate load in East is 100.
New particulate load in East: 100
Current particulate load in East is 100.

Current particulate load in East sisters is 100.
New particulate load in East sisters: 200
Current particulate load in East sisters is 200.

Current particulate load in East is 200.
New particulate load in East: 200
Current particulate load in East is 200.

Current particulate load in East brothers is nil.
New particulate load in East brothers:
Current particulate load in East brothers is nil.

Current particulate load in East is 200.
New particulate load in East: 200
Current particulate load in East is 200.

Command: (West)

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West brothers is nil.
New particulate load in West brothers:
Current particulate load in West brothers is nil.

Current particulate load in West is nil.
New particulate load in West:
Current particulate load in West is nil.

Current particulate load in West sisters is nil.
New particulate load in West sisters: 300
Current particulate load in West sisters is 300.

Current particulate load in West is 300.
New particulate load in West: 300
Current particulate load in West is 300.

Command: (Session)

Current particulate load in Session is 200.
New particulate load in Session: 200
Current particulate load in Session is 200.


Answer: The Session particulate load now matches the East sister room (and the East house) particulate load. This is because both the East sister room door and the East house doors are open.

The bottom line: Even though the West sisters have their door open and are using a global pload variable, the fact that the West house doors are closed keeps the West sisters from using the Session pload variable. They are using the "house" global pload variable. Since the WestSisters function is called by the West function that has pload declared as a local symbol, the WestSister function's global scope for pload can't get outside the West function.

Tuesday, May 19, 2009

This Little Piggy Went to Market Barbershop Quartet song

In 1991 when I was a student at Brigham Young University, I sang with a barbershop quartet called The Minutemen. My favorite of all the songs we sang was This Little Piggy Went to Market, as sung by the Bluegrass Student Union international champion quartet.

I recently had a chance to teach This Little Piggy Went to Market to a quartet from church for a talent show. Our score was very hard to read, and the song was absent from any internet searching, so I subsequently typed the song into Melody Assistant and Musescore, rearranging some troublesome rhythms.

Here are the lyrics:

I remember many dear old love songs, rhapsodies and symphonies galore,
But a melody my mother sang to me still haunts me. How I always will adore

This little piggy went to market. This little piggy stayed home.
This little piggy had roast beef. This little piggy had none.

How I recall my dear old mother putting me to bed.
She tucked me in and said to her cute little sleepy head,

This little piggy was a bad little piggy who cried all the way home.
Years have passed, but it's still my favorite poem.

I dream and pray someday I'll say to a cute little baby of my own,
This little piggy went to market and this little piggy stayed home.

This is a Minuteman performance I put at Youtube


This is a stereo midi file that's really good for learning the parts

This is a pdf file of the written score

Note: There's a difference between the midi and the pdf in that I flipped the Tenor and the Lead on the last two measures. I believe the midi is better. The Lead should stay on the melody all the way to the end.

Friday, May 15, 2009

AutoCAD Civil 3D 2010 Selecting feature lines, osnaps, and starting elevation

At Hubbard Engineering, when we want to draw a feature line over background geometry, continuing an existing feature line, what we do is use osnaps to select the endpoint of the existing feature line. But AutoCAD Civil invariably snaps to the underlying background geometry.

We found a solution, but first, here are some non-working solutions: We tried (out of desperation) adjusting the OSNAPZ variable (1 ignores Z in osnaps, 0 uses Z as out of the box). That doesn't work. We tried tweaking DRAWORDER. That doesn't work. We tried turning down VIEWRES to make arcs clunky. That works, but only for arcs, not lines.

We currently have two competing solutions:

1. Put the feature lines on a layer that has a complex linetype you can pick. On the fly, we used our ------X--------X-------X------- fence linetype. This works very well in all cases except short arcs and lines. We may define another "feature line" linetype that repeats more often.

2. Use our custom OFI (OFF ISOLATE, LAYISO) command to turn off all layers except the feature line layer. Then after starting the feature line, use the layer palette to turn on all layers again before continuing the feature line. This is relatively quick and painless if you select all layers using CTRL+A and then click any light bulb in the ON/OFF column.

At least those two solutions are keeping us moving forward happily.

Follow-up July 17, 2009

3. Osnap to a Spot Elevation label (I use the Center osnap) at the point in question.

AutoCAD Civil 3D 2010 Feature Line productivity without toolbar

2017 Update: This is still an important part of my $120/hr Civil 3D efficiency. The Ribbon still is too much of a drag for highly repetitive commands. And the replacement of interruptive mouse mileage with keyboard muscle memory makes this superior to the old toolbar buttons.

Rick Graham at Engineered Efficiency posted about the demise of the Feature line toolbar in AutoCAD Civil 2010 (registration required). Sadly, the post didn't offer much of a solution for the productivity hit that Autodesk perpetrated on us (site design engineers) with the decision they made to remove the Feature Line toolbar and "replace" it with a context sensitive ribbon panel.

I don't know AutoCAD Civil and version 2010 in particular well enough to make any final pronouncements about its out-of-the-box suitability for feature line productivity. But I have to admit at Hubbard Engineering--even though AutoCAD Civil has won our hearts in the past 6 months--we have failed to discover how to love the Feature Line editing interface of 2010.

So when all else fails, customize!

So we did customize. We are very pleased to finally have broken down and created Quick Keys for feature line editing. Where we were tearing our hair out, we are now smiling. Here is a printable list (copy to your word processor) of the quick keys we created. Let me know what you think. Share and share alike.

Contents:
-Printable Quick Keys list
-Quick Keys list with commentary
-PGP aliases code
-LISP code
-PGP installation instructions

Feature Line Quick Keys

General

FD
DrawFeatureLine

FAB
FeatureAddAsBreakline (to a surface)

FCO, FC
CreateFeatureLines (from objects)

Elevations

FGE
GradingElevEditor (table view)

FEI
InsertElevPoint

FHP
InsertFeatureHighLowPoint (solve intersection)

FRL
RaiseLowerFeatures

FQ, Q
QuickEditFeatureElevs

FE
EditFeatureElevs

FG
SetFeatureGrade (straight grade)

FR
RaiselowerFeaturesByRef

FV
SetFeatureRefElev ("Vertex" elevations)

FA
AdjacentFeatureElevsByRef

FX
FeatureGradeExtensionByRef

FES
FeatureElevsFromSurf

Geometry

FPI, FI
InsertFeaturePI

FPD, FDD
DeleteFeaturePI

FB
BreakFeatures

FJ
JoinFeatures

FT
TrimFeatures

FO
OffsetFeature

FW
WeedFeatures
One more time with commentary:

Feature Line Quick Keys

General

FD
DrawFeatureLine. FD draws a Feature Line. But it's usually faster to draw lines and polylines and convert them to Feature Lines with FCO. Create without a style to enable layer-based control. Create with a style to enable elevation conflict priority. It's easier to FJ join them (to do a projection grading, for example) than to FB break them (to do an FRL, for example), so having fewer segments (shorter Feature Lines) generally works fine.

FAB
FeatureAddAsBreakline. FAB is how you make a surface out of Feature Lines unless you are using Grading Infill. Making a surface is the way you can evaluate your design via contours or 3D viewing. Having a surface also gives you the best way to label your design slopes and elevations. FAB was the chief or sole way I built a Finished Grade surface until I got comfortable with Grading Infill. I consider an FG surface as merely a throw-away draped object and the Feature Lines as gospel. The surface is always fully re-buildable by simply FAB-ing all Feature lines (or redoing Grading Infill). For a "smooth" surface, use shorter Distance (25') and Mid-ordinate distance (-.05') Supplementing factors; for precise warp control, use more Feature Lines. To prevent crossing breakline errors, use a mid-ordinate distance supplementing factor smaller than your smallest Feature Line offset (such as offset between top of face of curb and gutter flow-line.

FCO, FC
CreateFeatureLines (from objects). FCO is the fastest way to make Feature Lines, but sometimes FO (offset) may be a better practice (for geometric precision and avoiding surplus elevation points). Keep your work in bite-sized pieces by creating only as many Feature Lines at a time as you are ready to work with (start with what you know and add detail as you learn and progress), because you may find along the way you would rather use FO offset than FCO for some of your feature lines, and because adding large numbers of Feature Lines without personal attention is an invitation for troublesome, evil crossings and gaps.

Elevations

FGE
GradingElevEditor. FGE is the tabular view for editing feature line elevations. While FQ is the better default grading plan editor tool, FGE is a powerful Feature Line table view editor with tools that are not available in other commands. Only with FGE (though FG comes close) can you raise or lower as a group selected points on a Feature Line. FGE treats a feature line more like an alignment.

FEI
FEI inserts a vertical-only bend in a feature line. Elevation points are sometimes easier to insert than PI points because you don't have to attend to the horizontal alignment. But beware that if you extend, trim, or stretch a feature line, elevation points will move proportionally.

FED
DeleteElevPoint. FED is mostly a great cleanup tool for all the elevation points that appear without your permission or notice. Notice the All option for when it can help you.

FHP
InsertFeatureHighLowPoint. InsertFeatureHighLowPoint. FHP solves the location and elevation at which two slopes toward each other meet. FHP is a powerful little calculator for the rare occasions you need it. I use FHP to pinpoint the elevation and location where two grades intersect without resorting to a Projection Grading. Experiment with FHP so that you will remember it when it's your quickest solution.

FRL
RaiseLowerFeatures. Use FRL to raise an entire section of your site or a single Feature Line. A nice feature of FRL is that it works like Stretch, in that connecting Feature Lines have their slopes adjusted.

FQ, Q
QuickEditFeatureElevs. FQ is the fastest grading design, checking, and error-correction tool in the toolbox. Notice the Surface option for when you want to paste a point to a Surface. When FQ doesn't work/respond, it is because you have touching feature lines (only one of them can have “priority” or editability--the last one created or see the FD comments) or tiny feature line segments you have not discovered.

FE
EditFeatureElevs. FE is, with FQ and FGE, one of the three similar feature line editing tools. It was my first exposure to Feature Line editing, but I am not aware of any advantage it has over the other two other than backward similiarity. I have not touched it in years.

FG
SetFeatureGrade. FG is how you straight grade a feature line between any two points. The interface is a little confusing, and you may need to hover your cursor along a feature line to get a response, but you need to be good at using this tool so you can set ranges of vertices along a feature line all in one command.

FR
RaiseLowerFeaturesByRef. FR lets you change the elevation of selected Feature Lines (for example, a building pad or an entire parking lot) as calculated from a certain other point to any point in the set. For example, set this building 1 foot above this sidewalk point or set this parking lot so this driveway neck slopes 5% up from this valley gutter edge. In my work I have only used this tool when I need to make major adjustments to balance earthwork, but it sure comes in handy when I need it.

FV
SetFeatureRefElev ("Vertex" elevations). FV lets you set a Feature Line vertex (point) elevation as calculated from some other point.  It is a tedious command to use, but sometimes in a pinch it is faster than, say, adding an elevation point or PI and drawing a feature line.

FA
AdjacentFeatureElevsByRef. FA is like FO offset, but instead of making a new object, it projects the elevations of one Feature Line onto an adjacent Feature Line. If you use FA with sloppy geometry, you will have trouble with phantom elevation points. It is sometimes best to use FO or AutoCAD Offset to create adjacent parallel lines such as for curbs so that FA works seemlessly with them. When it's necessary to use FA for geometry you know is not parallel with exactly coincident endpoints, use either FQ and FED or FB and Extend to clean up the results.

FX
FeatureGradeExtensionByRef. FX lets you grade across a gap. In reality it is very similar to FV, but it suggests the continuation slope of the reference object, which allows for exactness. I don't think it works right prior to version 2011. I have rarely used this tool in the real world.

FES
FeatureElevsFromSurf. FES is the way to drape an existing Feature Line onto a surface. I use FES for quick and dirty daylight line design (to avoid the overhead of Projection Grading). I also use it to paste parking islands onto a pavement surface that has been defined by the major parking edges.

Geometry

FPI, FI
InsertFeaturePI. Use FPI to insert vertices and grade changes on Feature Lines.

FPD, FDD
DeleteFeaturePI. Use FPD to delete vertices from Feature Lines. PIs are represented as triangles vs. Elevation Point circles.

FB
BreakFeatures. Break a Feature Line. Works just like Break, but it's finicky to respond to the First option.

FJ
JoinFeatures. Join to a Feature Line. I mostly use this in preparation to do a FG (straight grade) or a Projection Grading.

FT
TrimFeatures. Trim Feature Lines.  Use standard Extend to extend them.

FO
OffsetFeature. Offset Feature Lines. FO is the best way to create additional curb or walk lines after you have established the controlling curb line. For most initial design purposes, use only edge of pavement and top back of curb at curbs; gutter and top face are normally only necessary for looks.

FW
WeedFeatures. FW is useful to simplify a daylight line whose elevations were assigned from a complicated surface, inserting intermediate grade break points (to follow the surface exactly). Weeding may be very wise before using such a Feature Line for Projection Grading.


We added these by putting the following section in our team lisp file:

;;; Feature Line Quick Keys
(defun c:fd () (command "_AeccDrawFeatureLine")(princ))
(defun c:fbl () (command "_AeccFeatureAddAsBreakline")(princ))
(defun c:fab () (command "_AeccFeatureAddAsBreakline")(princ));Add to Surface as Breaklines
(defun c:fco () (command "_AeccCreateFeatureLines")(princ));Create Feature Lines from Objects(defun c:fc () (command "_AeccCreateFeatureLines")(princ));Create Feature Lines from Objects;;; Elevations
(defun c:fge () (command "_AeccGradingElevEditor")(princ))
(defun c:fei () (command "_AeccInsertFeatureElevPoint")(princ))
(defun c:fed () (command "_AeccDeleteFeatureElevPoint")(princ))
(defun c:fhp () (command "_AeccInsertFeatureHighLowPoint")(princ))
(defun c:frl () (command "_AeccRaiseLowerFeatures")(princ))
(defun c:fq () (command "_AeccQuickEditFeatureElevs")(princ))
(defun c:q () (command "_AeccQuickEditFeatureElevs")(princ))(defun c:fe () (command "_AeccEditFeatureElevs")(princ))
(defun c:fg () (command "_AeccSetFeatureGrade")(princ))
(defun c:fr () (command "_AeccRaiselowerFeaturesByRef")(princ))
(defun c:fv () (command "_AeccSetFeatureRefElev")(princ));"Vertex" elevation
(defun c:fa () (command "_AeccAdjacentFeatureElevsByRef")(princ))
(defun c:fx () (command "_AeccFeatureGradeExtensionByRef")(princ))
(defun c:fes () (command "_AeccFeatureElevsFromSurf")(princ))
;;; Geometry
(defun c:fpi () (command "_AeccInsertFeaturePI")(princ))
(defun c:fi () (command "_AeccInsertFeaturePI")(princ))(defun c:fpd () (command "_AeccDeleteFeaturePI")(princ))(defun c:fdd () (command "_AeccDeleteFeaturePI")(princ))(defun c:fb () (command "_AeccBreakFeatures")(princ))
(defun c:fj () (command "_AeccJoinFeatures")(princ))
(defun c:ft () (command "_AeccTrimFeatures")(princ))
(defun c:fo () (command "_AeccOffsetFeature")(princ))
(defun c:fw () (command "_AeccWeedFeatures")(princ))
;;; End Feature Line Quick Keys

You could also add them one at a time as "PGP aliases" using the ALIASEDIT command or add them as a group to your ACAD.PGP file as instructed at the end of this post, like this:


FD, *DrawFeatureLine
FAB, *FeatureAddAsBreakline
FCO, *CreateFeatureLines

FC, *CreateFeatureLines
FGE, *GradingElevEditor
FEI, *InsertElevPoint
FED, *DeleteElevPoint
FHP, *InsertFeatureHighLowPoint
FRL, *RaiseLowerFeatures
FQ, *QuickEditFeatureElevs
FE, *EditFeatureElevs
FG, *SetFeatureGrade
FR, *RaiselowerFeaturesByRef
FV, *SetFeatureRefElev
FA, *AdjacentFeatureElevsByRef
FX, *FeatureGradeExtensionByRef
FES, *FeatureElevsFromSurf
FPI, *InsertFeaturePI

FI, *InsertFeaturePI
FPD, *DeleteFeaturePI
FDD, *DeleteFeaturePI
FB, *BreakFeatures
FJ, *JoinFeatures
FT, *TrimFeatures
FO, *OffsetFeature
FW, *WeedFeatures


Basic Installation Instructions



Install the "PGP Aliases" by pasting then into the end of your active ACAD.PGP file, as follows.

1. Paste the following line onto your AutoCAD command line to find the location of your active ACAD.PGP file.

(progn(princ(findfile "acad.pgp"))(princ))

2. Using the result of step one, browse to your ACAD.PGP file in Windows Explorer. Open the file by double-clicking it. If Windows needs you to choose a program from a list to open it, choose Notepad.

3. Use the CTRL+END keys to go to the end of the file. Then copy the pgp style aliases (eg. FD, *DrawFeatureLine) from this page and paste them into the very end of your ACAD.PGP. Don't worry; the ACAD.PGP file is designed to be easy for non-programmers to change without trouble.

4. Save and close the ACAD.PGP file. In AutoCAD at the command line, enter the REINIT command and select the PGP file.

5. Try your new quick keys.

Revised: 1 Sep 2017. Added optimized shortcuts Q, FC, FI, and FDD
Revised: 24 Sep 2010. Added command explanations.
Revised: 20 Sep 2010. Added basic loading instructions.
Revised: 9 Jul 2009. Added commands.
Revised: 18 Sep 2009. Corrected PGP syntax
Revised: 2 Apr 2021. Added references to Grading Infill and that I have not used FE in years.