LISP

Developing solutions can be a challenge and on this page notes are maintained - maybe they can help you!

A table of contents...

IT Architecture

Less technical, more IT Architecture based.

Advanced LISP subjects

Dive deeper in LISP.

If you use AutoCAD or BricsCAD, you probably want to optimize work flows. Customizing is a packet of actions to do this and it often involves creating a user interface that fit your needs and automating drawing tasks in a smart way. If you want to dive into that, you'll have a long road ahead of you. However, that shouldn't keep you from doing so. Why? Because there is so much written on the net to help you, with relative small actions you can achieve great results and, we are here to help you.

There is much to gain but managers are in general hard to convince to put money in customizing. Keep them informed and show them how much easier and faster CAD-challenges can be solved, you may get their support. Keep them involved.




Knowledge Base

Documentation Tools

Manuals

Platform independent software, open-source:

Screen casts

See http://www.vanderworp.org/Screen-casting

Programming Tools

Concepts

BricsCAD and AutoCAD and dealing with AutoLoader

Read this page: BricsCAD_and_AutoCAD_and_dealing_with_AutoLoader.

Start sequence AutoCAD and BricsCAD

What happens, and in what order, when you start your CAD program? The answer - a bit outdated - can be found here: http://www.blog.cadnauseam.com/2008/09/01/what-is-loaded-at-autocad-startup-and-when/, look at the list.

LISP start files

You want your CAD program to do things at start or during opening a document.

When\Vendor

BricsCAD

AutoCAD

At program start

on_start.lsp

acad.lsp

At document loading

on_doc_load.lsp

acaddoc.lsp

Read the previous paragraph for more options, but these work fine. If menu's are used you may find menuname.mnl also a proper construction to use. In AutoCAD, TRUSTEDPATHS can complicate things. If these files do not exist you can make them and put them in the search path. First find out if and where they live by pasting something like this on the command line:

(findfile "on_start.lsp")

If these files don't exist you can create a directory where you put them and append it to the "Support File Search Path" or put them in %appdata% like:

C:\\Users\\%username%\\AppData\\Roaming\\Bricsys\\BricsCAD\\V17x64\\en_US\\Support

Running commands at start up

Regular CAD commands should be run as a final step. Otherwise they can interfere with other things that are about to start or have been started during initializing the program. The construction for this is using (S::STARTUP).

The problem with all startup files like (S::STARTUP), acad.lsp, acaddoc.lsp is that there is only one valid file and it may be used or created by other app. A nice programming technique is checking for existence, checking if it contains the right code and - if not - append the right code. For example, CADchUP contains these measures.

As far as (S::STARTUP) is concerned you may want to take a look at http://www.cadtutor.net/forum/showthread.php?26336-S-STARTUP-Function to see how code is appended by using a defun-q construction. Alternative: https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2016/ENU/AutoCAD-Customization/files/GUID-FDB4038D-1620-4A56-8824-D37729D42520-htm.html. An example of this, in on_doc_load.lsp (BricsCAD) or acaddoc.lsp AutoCAD):

; Your other LISP code

; Putting commands in a list, yes, this is a list.
(defun-q finalstepcommands ()
  (command "._ribbonclose")
  (command "._-layer" "m" "0" "")
  (command "._filetabclose")
  (command "._taskbar" "1")
)

; Appending this list to to special STARTUP list
(setq S::STARTUP (append S::STARTUP finalstepcommands))

; More LISP code ...

As an alternative you can also start your program with a /b switch. This looks like cadprogram.exe /b startup.scr, where startup.scr can contain commands, but also LISP.

vla-get vla-put

You can control much with this combo. The question is: How to use it? Reading this is not enough, paste code on the command line and study it (F2)...

If you paste:

(setq ss (vlax-get-acad-object))

... you get an object - assigned to variable ss. Next you want to know the properties of ss:

(vlax-dump-object ss)

You see the list of properties and one of the entries is "Preferences". Let's see what it offers, if you paste each line...

(setq ss (vla-get-preferences (vlax-get-acad-object)))
(vlax-dump-object ss)

... you get an object files. So the next step:

(setq ss (vla-get-files (vla-get-preferences (vlax-get-acad-object))))
(vlax-dump-object ss)

Now we have all files settings. As long is there is not a (RO) text (Read Only), we can change it.

For example, we have all plotter configurations on a server drive. The current locations are:

(vla-get-printerconfigpath ss)
(vla-get-printerdescpath ss)
(vla-get-printerstylesheetpath ss)

We assign a different location:

(vla-put-printerconfigpath ss "x:\\cad_users\\19.1\\plotters")
(vla-put-printerdescpath ss "x:\\cad_users\\19.1\\plotters\\pmpfiles")
(vla-put-printerstylesheetpath ss "x:\\cad_users\\19.1\\plotters\\plotstyles")

Cool?

In summary: You start with (vlax-get-acad-object) for building a selection set, making it complexer as described, where (vlax-dump-object ss) is your friend. If you know the properties by using "get", you can change them with "put".

Easy Peacy!

UCS and trans

See trans

Calling LISP from within LISP

If you have code that you want to reuse, there is the load construction like:

(load filename [onfailure])

This evaluates filename. Another less common way is to define your command like (defun c:runthis ()...) and call it from within another LISP function like:

(princ "\nYour code to run... ")
(c:runthis)
(princ "\nDone. ")

You can consider this construction as an addition to error handling as described at http://www.afralisp.net/autolisp/tutorials/error-trapping.php

LISP, DRY and WET and (defun ...)

This one goes a bit deeper, but don't let it keep you from reading. It contains hard to find information on the net but it comes in really helpful at times.

Q&D, WET and DRY

I found it funny to understand the abbreviations of DRY and WET. When you start coding, you want a solution and Q&D, Quick and Dirty is no problem, "It works!"

But on the way you start noticing you are repeating yourself, "I've done that before, where was it?" Next you paste a code block, adapt it and and again you solved a challenge. This is known as WET or Write Everything Twice. The negative conotation is easy to explain: If a code block needs a change it is not easy to find all locations where you used that code block.

In time you start to understand LISP better, how every piece of code between parentheses returns a value that you can use as input for another piece of code between parentheses.

Time to work smarter.

Functions (defun ...) play a central role in this article.

(defun create-flowers ()
    (remove-roots-and-return-flower
        (reap-and-return-harvested-plant
            (drop-seed-and-return-plant "tulip")
        )
    )
)
(defun create-carrots ()
    (remove-stem-and-return-root
        (reap-and-return-harvested-plant
            (drop-seed-and-return-plant "ordinary-carrot")
        )
    )
)

How about creating a universal function, instead of copying and pasting code from file to file? (reap-and-return-harvested-plant arguments) is a good example.

This is where DRY comes in. DRY stands for Don't Repeat Yourself. Much of the example code is the same. Yet, their final output, carrots and flowers is very different. By defining (reap-and-return-harvested-plant plant) and (drop-seed-and-return-plant seed-type), where plant and seed-type are the arguments, DRY conditions are met and writing becomes easier, better and more efficient.

Functions, (defun ...) in general.

Shall we dive in the subject a little deeper? You can even automate the creation of functions, quite exiting! Do yourself a favour and paste code on the command line to better understand what happens.

(defun c:candc (/)
    (princ "\nCola and Chips!")
    (princ)
)

As one paste line:

(defun c:candc (/) (princ "\nCola and Chips!") (princ))

Test by entering new command "CANDC". You get the message. Nothing new, "c:" is the reason you can use it on the command line and (princ) suppresses the previous returned values.

Now:

(type c:candc)

SUBR (subroutine) is returned, not SYM(bol), SUBR is a data type. To erase the subroutine:

(setq c:candc nil)

Clean slate, symbol c:candc seized to exist, read on, it gets interesting from this point.

Functions with variable names, meet "set" and "read"

Create a variable, symbol "function-name":

(setq function-name "candc")
(type function-name)

Create a function:

(defun function-candc (/) (princ "\nCola and Chips!") (princ))
(type function-candc)

Here comes the magic:

(setq function-symbol (read (strcat "c:" function-name)))
(set function-symbol function-candc)

We've created ourselves a new command "CANDC" in just a few words, all based on variables! And we did not even use (defun ...)! Read this line twice.

Analyzing with "type", from inside to outside:

Because "function-candc" is SUBR, "set" takes care that SYM "function-symbol""c:candc" becomes SUBR too.

One caveat: We need (vlax-add-cmd ...) to get it working:

(vlax-add-cmd function-name function-symbol)

Think about it, this really offers opportunities to do some smart programming.

Loading functions the smarter way

The following command draws a leader without text. It lives in "arrow.lsp"

(defun *error* (msg)
        (setvar "cmdecho" cmdecho-org)
        (princ)
)

(defun arrow (/ cmdecho-org pt1 pt2)
        (setq cmdecho-org (getvar "cmdecho"))
        (setq pt1 (getpoint "\nSpecify arrow start point: "))
        (setq pt2 (getpoint pt1 "\nSpecify trailing end point: "))
        (setvar "cmdecho" 0)
        (command "._leader" "_non" pt1 "_non" pt2 "" "" "_none")
        (setvar "cmdecho" cmdecho-org)
        (princ)
)

Please note that "c:" is not available, it is just a plain function that is not available on the command line - yet.

We have a bunch of these programs to make life easier in one folder, but for now, focus is on "arrow".

To keep things simple, entries are made in...

You probably like a pointer there with (load "my-functions") in a file "my-functions.lsp", but we'll forget that for now.

The entry becomes:

(defun c:arrow (/ arrow) (load "arrow") (arrow))

A function is defined with name "c:arrow" and it claims almost no memory, it is just that single line of characters, nothing more. The opposite is what a lot of people do, just load tons of complete LISP files in memory, slowing down performance.

Loading of file "arrow.lsp" is only done when someone enters command ARROW. And unloading file "arrow.lsp", after finishing command ARROW, is taken care for by statement "(/ arrow)". Since atom "arrow" is not atom "c:arrow", the latter stays in memory, i.e. the one line of code. Proof can be found by running (atoms-family 1). After decades, it is still a charming way to treat your computer and yourself well.

You have more than one function, so your list of entries becomes something like:

(defun c:arrow (/ arrow) (load "arrow") (arrow))
(defun c:my-line (/ my-line) (load "my-line") (my-line))
(defun c:my-circle (/ my-circle) (load "my-circle") (my-circle))
(defun c:my-arc (/ my-arc) (load "my-arc") (my-arc))
(princ)

A variant that can be considered for often used commands:

(defun c:arrow () (if (not arrow) (load "arrow")) (arrow))

Once loaded from file, it remains in memory and does not have to be loaded again.

Putting it all together

How can we combine all previous knowledge into a final solution? Here is the plan:

LISP and tests

Functions like "while", "if" and "cond" require tests.

As long as the test evaluates to T or true, the test is passed and code beyond is executed.

I've seen many variants of tests, some very exotic, some not much used, some logical. The thing with LISP is that you can program with accents on readability at the cost of amount of code or with accents on efficient code at the cost of readability. This does matter, you want others to be able to understand your code while staying efficient. Conditional testing or logical testing is an example of many ways to achieve the same.

Assume:

(setq var-nil nil)
(setq var-string "string")
(setq var-real 0.123)
(setq var-true t)
(setq var-list (list 1.2 'a "A"))

Now a test:

(if (/= nil var-list)
  (princ "T")
  (princ "nil")
)

This construction is very tempting and readable at least. However the most simple test is just writing down only the atom name as in:

(if var-list
  (princ "T")
  (princ "nil")
)

This construction is often forgotten (at least by me) and can be combined with "and" like in:

(if (and var-list var-true)
  (princ "T")
  (princ "nil")
)

Now you have written a function with arguments. Problem: The last expression is not what you want as return output for your function. Almost not documented on the net but very logical: Create a variable with the return value and, as the last statement, do (eval return-value-variable). Not limited to T or nil.

Take a look at the following list for some more examples.

All these test expressions evaluate to T:

ATOM-NAME-NOT-NIL
(= var-nil nil)
(/= var-nil "string")
(= var-string "string")
(/= var-string "hello")
(= var-real 0.123)
(equal var-real 0.123)
(eq var-string (setq var-same-object var-string))
(= var-whatever)
(= var-nil)
(< 1 2)
(< var-nil var-real)
(< var-nil var-string)
(> "b" "a")
(<= 8 8.0)
(< 1 2 4.0)
(setq var-true t)
(setq var-true (not nil))
(not (equal var-real 0.234))
(not nil)
(not (not (not nil)))
(not var-nil)
(not var-not-defined)
(and)
(and (not var-nil))
(and var-string)
(and var-string (not var-nil))
(and var-string (not var-nil) (setq var-new "some-value"))
(eval (not nil))
(eval var-true)
(listp var-list)
(or var-string)
(or var-string var-nil)
(vl-every '/= var-list (cdr var-list))
(vl-some '/= var-list (cdr var-list))
(wcmatch var-string "str*")

Nice set of examples.

Some additional remarks:

LISP and groups

How can we handle named and unnamed groups?

If you want to do operations within a group you can use what is documented here: http://adndevblog.typepad.com/autocad/2012/12/how-to-add-a-group-in-a-selection-set-from-an-autolisp-function.html. A cached version:

(defun selgrp (grpname)
   ;; grpname is the group name, it accepts
   ;; unnamed groupnames, such as *A1
   (setq grp (dictsearch (namedobjdict) "ACAD_GROUP"))
   (setq a1 (dictsearch (cdr (assoc -1 grp)) grpname))
   (setq ss (ssadd))
   (while (/= (assoc 340 a1) nil)
      (setq ent (assoc 340 a1))
      (setq ss (ssadd (cdr ent) ss))
      (setq a1 (subst (cons 0 "") ent a1))
   )
   ss
)

LISP and selection sets

If you think about LISP and structures in list format, a selection set could be a list of multiple entities with their definitions. However, when multiple selection sets exist, they can overlap which, on its own, can be considered stupid and a waste. So selection sets are a bit special in a way that they are nothing more than a list of entity names. I must confess I have no proof of the latter but functions like (entget) form a strong indication. By the way, selections sets have often an abbreviation like "ss". This also means that selection sets have a kind of formatting. Consider this example:

(setq ss nil) ; ss equals nil
(setq ss (ssadd)) ; ss is formatted as selection set, but contains no entities
(setq ss (ssget)) ; ss can be supplied with entities. Paste examples on command line for illustration.

The concept:

So it is important to understand that you can not do something like adding a selection set 2 to set 1:

(setq ss-1 (+ ss-1 ss-2))

To achieve this, your construction can be:

(setq n 0)
(repeat (sslength ss-2)
    (ssadd (ssname ss-2 n) ss-1)
    (setq n (1+ n))
)

In its base, this solution is okay. However, you should be aware of an important thing:

Intermezzo:

ss-1 is nothing more than a pointer to a selection set with a name. In the example above, ss-1 is changed by adding members from ss-2. If you don't want ss-1 to be changed, you are tempted to use another symbol, like (setq ss-union ss-1) and use ss-union to add entities. Oops, ss-union becomes just another pointer to the same selection set. If you change ss-union by adding entities, ss-1 will change too without any notice!

Solution: Create a new selection set first: (setq ss-union (ssadd)) and next do the repeat routine for both ss-1 and ss-2. One pitfall less. This is why this statement is used directly under (defun...) in the working exmples below.

From time to time you have code adding entities and then you want to do something with only the added entities. What are those entities? Time for tricks: Before your code runs:

(setq ss-pre (ssget "x"))

All entities in the drawing are added to "ss-pre". After your code added more entities, you do:

(setq ss-post (ssget "x"))

And again there is a selection set with all entities. Think about subtracting a solid from another solid. If you do ss-post minus ss-pre, you have all newly added entities. So we need the difference in a new selection set and do post editing like grouping and moving.

You are probably aware of three Boolean operations when working with solids: Union, Subtract and Intersect. Let's take two selection sets. Entities are just a number in a list:

ss1 = (0 1 2 3)
ss2 = (2 3 4 5)

And the corresponding Boolean operations:

Union: ss1 + ss2 = (0 1 2 3 4 5)
Subtract: ss1 - ss2 = (0 1), ss2 - ss1 = (4 5)
Intersect: ss1 ∩ ss2 = (2 3)

A bit more practical, for Boolean "Union" we have function "ssadd", for Boolean "subtract" we have "ssdel". What do we have for Boolean "Intersect"?

How about this, and Boolean again:

Intersect: ss1 ∩ ss2 = (ss1 + ss2) - (ss1 - ss2) - (ss2 - ss1)

So if you do a Union of ss1 and ss2 and next you Subtract the Subtraction of ss1 and ss2 and finally Subtract the Subtraction of ss2 and ss1, you have the intersecting entities. Are you still there? I have been developing my thoughts while writing, hope I didn't oversee anything. But it means you can do all Booleans with only (ssadd) and (ssdel).

About selection sets:

Here are functions for "Union" and "Subtract".

File sel-union.lsp:

(defun sel-union (ss-base ss-add / ss-base ss-add n ss-union)
        (setq ss-union (ssadd))
        (if (= 'pickset (type ss-base))
                (progn
                        (setq n 0)
                        (repeat (sslength ss-base)
                                (ssadd (ssname ss-base n) ss-union)
                                (setq n (1+ n))
                        )
                )
        )
        (if (= 'pickset (type ss-add))
                (progn
                        (setq n 0)
                        (repeat (sslength ss-add)
                                (ssadd (ssname ss-add n) ss-union)
                                (setq n (1+ n))
                        )
                )
        )
        (eval ss-union)
)

And file sel-subtract.lsp:

(defun sel-subtract (ss-base ss-min / ss-base ss-min n ss-subtract)
        (setq ss-subtract (ssadd))
        (if (= 'pickset (type ss-base))
                (progn
                        (setq n 0)
                        (repeat (sslength ss-base)
                                (ssadd (ssname ss-base n) ss-subtract)
                                (setq n (1+ n))
                        )
                )
        )
        (if (= 'pickset (type ss-min))
                (progn
                        (setq n 0)
                        (repeat (sslength ss-min)
                                (ssdel (ssname ss-min n) ss-subtract)
                                (setq n (1+ n))
                        )
                )
        )
        (eval ss-subtract)
)

Where do we end with "Intersection"? We had this earlier:

Intersect: ss1 ∩ ss2 = (ss1 + ss2) - (ss1 - ss2) - (ss2 - ss1)

You can use the functions to get the intersection. However, it is a bit over complex. Better write a new function (sel-intersection ss-1 ss-2) based on using (ssmemb).

Last, but not least, file sel-intersect.lsp:

(defun sel-intersect (ss-base-1 ss-base-2 / ss-base-1 ss-base-2 n ss-base-1-new ss-intersect ent-name)
        (setq ss-base-1-new (ssadd) ss-intersect (ssadd))
        (if (= 'pickset (type ss-base-1))
                (progn
                        (setq n 0)
                        (repeat (sslength ss-base-1)
                                (ssadd (ssname ss-base-1 n) ss-base-1-new)
                                (setq n (1+ n))
                        )
                )
        )
        (if (= 'pickset (type ss-base-2))
                (progn
                        (setq n 0)
                        (repeat (sslength ss-base-2)
                                (setq ent-name (ssmemb (ssname ss-base-2 n) ss-base-1-new))
                                (if (/= nil ent-name)
                                        (ssadd ent-name ss-intersect)
                                )
                                (setq n (1+ n))
                        )
                )
        )
        (eval ss-intersect)
)

You want these functions in your toolbox! On the command line it is a bit harder but for illustrating it is fine. Files in your File Support Search Path? I use the BricsCAD prompt, which is a ":".

: (load "sel-union") < Load the Union function
SEL-UNION < Return value
: (setq ss1 (ssget)) < Create selection set ss1
Select entities: < Used a Crossing here
Opposite Corner:
Entities in set: 3 < 3 entities selected
Select entities: < And an empty return
<Selection set: 0000000024E454A0>
: (setq ss2 (ssget)) < Same for selection set ss2
Select entities:
Opposite Corner:
Entities in set: 3
Select entities:
<Selection set: 00000000248B6180> < And the second set
: MOVE < Check with a move command
Select entities to move: (sel-union ss1 ss2)
<Selection set: 0000000024E454A0> < Run our function
Select entities to move: < And so on...
6 found.
Entities in set: 6
Select entities to move:
Enter base point [Displacement] <Displacement>:0,0
Enter second point <Use base point as displacement>:0,0

There is good reading stuff:

Settings in LISP file

You probably do record settings like this:

(setq ortho_org (getvar "orthomode"))

... and more as needed. Then you code your program and change ORTHOMODE as needed:

(setvar "orthomode" 1)

However, a user should get all changed settings back after your code is finished. So you end with:

(setvar "orthomode" ortho_org)

If you do it properly, you put that line of code in your error handler too, in particular because many people press Esc too frequently.

A Consideration

For a small LISP program there is nothing wrong with this approach. However, for a big and interactive program it is not always the right way. Consider a program that interactively asks for input and finally comes up with a result. Somewhere during the proces a user switches on OSNAP. Can you imagine how user feels after finishing the command? "Hey, I thought I turned Osnap on, #@#$".

In this case we should respect the user and add code on a local base, not on a general base. To give an example:

(setq pt1 (getpoint "\nSpecify first point of line: "))
(setq pt2 (getpoint "\nSpecify last point of line: "))
(command "_.line" pt1 pt2 "")

But running object snap mode can spoil it. The last line could become:

(command "_.line" "_non" pt1 "_non" pt2 "")

Problem solved... Unless the command is an impressive list like:

(command "_.pline" pt2 "_w" "0.0" "0.0" "_a" "_d" hkd2 pt4 "_l" pt5 "_a" pt6 "_l" pt7 "_a" pt8 "_l" pt9 "_a" pt11 "_l" pt13 "_a" "_d" hkd3 pt15 "_l" pt16 "_a" pt17 "_l" pt18 "_a" pt19 "_l" pt20 "_a" pt22 "_l" "_cl")

In such a case you can consider making two functions, (commandpre) and (commandpost), where (commandpre) stores object snap mode and turns it off and (commandpost) restores it. Read on how to use boole to achieve this.

More on OSMODE

OSMODE or object snap mode. This is a number, a bitsum of many settings:

0 None
1 Endpoint
2 Midpoint
4 Center
8 Node
16 Quadrant
32 Intersection
64 Insertion
128 Perpendicular
256 Tangent
512 Nearest
1024 Quick
2048 Apparent Intersection
4096 Extension
8192 Parallel

For example, value 511 (green "P" button in CADchUP) is a bitsum of 1+2+4+8+16+32+64+128+256=511

You could use a construction as illustrated with ORTHOMODE, but you will find out it doesn't work perfectly. Why? Because OSNAP on and off is also included in the code.

16384 Object Snap on or off, F3 (on: subtract value, off: add value)

To illustrate: Do OSMODE 1. OSMODE is set to "endpoint", value 1 (check). Press F3 for turning off object snap. This is not the same as setting it to value 0, it is just a switch to turn off or turn on the setting of OSMODE (read this twice). Do OSMODE and see that it changed to 1+16384=16385.

So you want to turn osnap on and simply substract 16384? That works for 16385, but not if the value is unknown and set by the user, for example 1-16384 is a bad idea. So we need a different solution. We use a function called boole for truth tables.

Turning OSNAP off:

(setvar "osmode" (boole 7 (getvar "osmode") 16384))

Turning OSNAP on:

(setvar "osmode" (boole 2 (getvar "osmode") 16384))

Toggle OSNAP:

(setvar "osmode" (boole 6 (getvar "osmode") 16384))

Selection

Selecting objects and points. The pointer is square, dimensions (width and height) are in pixels:

All in all, it is easy to set all values for selection squares to values using LISP. Tuning these values to users needs is preventing them from missing clicks or wrong object clicks. Importance of setting this to optimal values is often underestimated.

LISP (laatst bewerkt op 2019-03-13 11:32:24 door WiebeVanDerWorp)