Interacting with the Drawing
Now that we know how to gather information from the user, let's move on to the next step: interacting with the AutoCAD drawing. Creating lines, circles, modifying existing entities, automating plan production — this is where everything comes together. AutoLISP offers two fundamentally different approaches to achieve this.
Approach 1: the command function
The command function lets you execute AutoCAD commands from your code, exactly as if you were typing them at the keyboard:
(command "_LINE" "0,0" "100,0" "")
This expression draws a line from point (0,0) to point (100,0). The empty string "" simulates pressing Enter to end the command.
Using international names
AutoCAD commands have names translated according to the installation language. In French, the command to draw a line is LIGNE, in English it's LINE. To make your code work regardless of the language, prefix the command name with an underscore _:
(command "_LINE" "0,0" "100,0" "") ; Works in all languages
(command "LIGNE" "0,0" "100,0" "") ; Only works in French
Likewise, command options should be prefixed with _:
(command "_CIRCLE" "0,0" "_Diameter" 50) ; "Diameter" option in international form
Golden rule: always prefix command and option names with
_to ensure portability of your programs.
Best practice: in the command line, you often type the shortcut for an option (for example
_Dinstead of_Diameter). In a program, prefer the full option name: it makes the code much more readable and self-documenting. Saving a few characters is not worth the loss of clarity when re-reading the code six months later.
Passing points as lists
Rather than using strings like "0,0", you can pass coordinate lists, which is cleaner and allows the use of variables:
(setq start-point '(0 0))
(setq end-point '(100 50))
(command "_LINE" start-point end-point "")
Example: drawing a rectangle
(defun c:rect-simple (/ point1 width height point2 point3 point4)
(setq point1 (getpoint "\nLower-left corner: "))
(setq width (getreal "\nWidth: "))
(setq height (getreal "\nHeight: "))
(setq point2 (list (+ (car point1) width) (cadr point1)))
(setq point3 (list (+ (car point1) width) (+ (cadr point1) height)))
(setq point4 (list (car point1) (+ (cadr point1) height)))
(command "_LINE" point1 point2 point3 point4 "_Close")
(princ)
)
Pitfalls of command
The command approach is intuitive — it reproduces what you would do manually. But it comes with several pitfalls you need to be aware of.
Command echo
By default, each command executed by command is displayed in the command line, as if the user typed it. In a script that executes dozens of commands, this slows down execution and clutters the display. You need to disable the echo with the CMDECHO system variable:
(defun c:my-drawing (/ old-echo)
;; Save and disable echo
(setq old-echo (getvar "CMDECHO"))
(setvar "CMDECHO" 0)
;; Your commands here
(command "_LINE" "0,0" "100,0" "")
(command "_CIRCLE" "50,25" 10)
;; Restore echo
(setvar "CMDECHO" old-echo)
(princ)
)
Best practice: always save the original value of
CMDECHObefore modifying it, and restore it at the end of your function. If your program crashes before restoration, the echo will remain disabled for the user.
Dependency on system variables
This is the most insidious pitfall. AutoCAD commands rely on the current system variables. Your program may behave differently depending on the user's configuration:
OSMODE(object snap): if the user has enabled midpoint snap, your points may be unintentionally alteredANGDIR(angle direction): counterclockwise or clockwise? Your angle calculation may produce an inverted resultANGBASE(base angle): angle 0 does not necessarily point eastAUNITS(angle units): degrees, radians, gradians?CLAYER(current layer): the entity will be created on the active layer, not necessarily the one you expect
To write a reliable program, you must save and force the critical system variables:
(defun c:reliable-drawing (/ old-osmode old-cmdecho)
;; Save variables
(setq old-osmode (getvar "OSMODE"))
(setq old-cmdecho (getvar "CMDECHO"))
;; Force settings
(setvar "CMDECHO" 0)
(setvar "OSMODE" 0) ; Disable object snaps
;; Draw safely
(command "_LINE" '(0 0) '(100 0) "")
;; Restore variables
(setvar "OSMODE" old-osmode)
(setvar "CMDECHO" old-cmdecho)
(princ)
)
This save/restore code is tedious, especially as the number of variables to manage grows. And if your program encounters an error before reaching the restoration, the variables will remain modified.
Other limitations
- Performance: each call to
commandgoes through AutoCAD's command interpreter, which is slower than direct entity manipulation - Regeneration: some commands trigger a drawing regeneration, which further slows execution
- Undo: each call to
commandcreates an entry in the undo history, which can be problematic if your program executes 50 commands — the user will need to pressCtrl+Z50 times to undo everything
The command-s variant
Since AutoCAD 2015, the command-s function advantageously replaces command in most cases. Its syntax is identical:
(command-s "_LINE" '(0 0) '(100 0) "")
The difference is subtle but important: command-s suspends LISP execution until the command is completely finished. With command, there are cases where LISP execution resumes before the command has actually completed — particularly with commands that display a dialog box or launch a sub-command. This can cause hard-to-diagnose bugs: your program continues while the previous command hasn't finished yet.
;; With command, the next line may execute before the block is inserted
(command "_INSERT" "MyBlock" '(0 0) 1.0 1.0 0.0)
(setq last-ent (entlast)) ; May not be the block!
;; With command-s, insertion is guaranteed to be complete
(command-s "_INSERT" "MyBlock" '(0 0) 1.0 1.0 0.0)
(setq last-ent (entlast)) ; This is definitely the block
There is, however, one restriction: command-s does not support the pause keyword (which allows the user to provide interactive input in the middle of a command). If you need pause, stick with command.
Best practice: use
command-sby default. Only fall back tocommandif you needpausefor interactive user input, or if your program targets an AutoCAD version prior to 2015.
Approach 2: entmake — creating entities directly
The entmake function creates an entity directly in the drawing database, bypassing the command interpreter. It takes an association list in DXF format as its argument:
(entmake
(list
'(0 . "LINE") ; Entity type
'(8 . "MyLayer") ; Layer
'(10 0.0 0.0 0.0) ; Start point (X Y Z)
'(11 100.0 0.0 0.0) ; End point (X Y Z)
)
)
This expression creates a line from point (0,0,0) to point (100,0,0) on the layer "MyLayer".
DXF codes
Each entity property is identified by a DXF code (Drawing Exchange Format). Here are the most common codes:
| Code | Property |
|---|---|
| 0 | Entity type ("LINE", "CIRCLE", "ARC", "TEXT", etc.) |
| 8 | Layer name |
| 10 | Primary point (start for a line, center for a circle) |
| 11 | Secondary point (end for a line) |
| 40 | Numeric value (radius for a circle, height for text) |
| 62 | Color number (1 = red, 2 = yellow, 3 = green, etc.) |
The complete list of DXF codes is available in the official Autodesk DXF reference.
Entity examples
A circle
(entmake
(list
'(0 . "CIRCLE")
'(8 . "Construction")
'(10 50.0 25.0 0.0) ; Center
'(40 . 10.0) ; Radius
'(62 . 1) ; Red color
)
)
A text entity
(entmake
(list
'(0 . "TEXT")
'(8 . "Annotations")
'(10 0.0 0.0 0.0) ; Insertion point
'(40 . 2.5) ; Text height
'(1 . "Hello AutoLISP") ; Text content
)
)
A point
(entmake
(list
'(0 . "POINT")
'(10 30.0 40.0 0.0)
)
)
Advantages of entmake
Compared to command, the entmake approach is:
- Independent of system variables:
OSMODE,ANGDIR,ANGBASEhave no influence. You explicitly specify each property — no unpleasant surprises - More performant: no round-trip through the command interpreter, no unnecessary regeneration
- More predictable: the layer, color, and linetype are explicitly defined in the DXF list, not inherited from the current context
- Clean undo: you can group multiple operations into a single undo block with
(command "_.UNDO" "_Begin")and(command "_.UNDO" "_End")
A comparative example
Drawing 100 random circles — let's compare the two approaches:
;; With command (slower, context-dependent)
(defun c:circles-command (/ old-osmode old-cmdecho index)
(setq old-osmode (getvar "OSMODE"))
(setq old-cmdecho (getvar "CMDECHO"))
(setvar "CMDECHO" 0)
(setvar "OSMODE" 0)
(setq index 0)
(repeat 100
(command "_CIRCLE"
(list (* (rem (* index 7) 100) 10.0) (* (rem (* index 13) 100) 10.0))
(+ 5.0 (rem (* index 3) 20))
)
(setq index (1+ index))
)
(setvar "OSMODE" old-osmode)
(setvar "CMDECHO" old-cmdecho)
(princ)
)
;; With entmake (faster, context-independent)
(defun c:circles-entmake (/ index)
(setq index 0)
(repeat 100
(entmake
(list
'(0 . "CIRCLE")
(cons 10 (list (* (rem (* index 7) 100) 10.0) (* (rem (* index 13) 100) 10.0) 0.0))
(cons 40 (+ 5.0 (rem (* index 3) 20)))
)
)
(setq index (1+ index))
)
(princ)
)
The entmake version is more concise, faster, and requires no system variable save/restore.
Reading and modifying entities: entget and entmod
Reading an entity with entget
The entget function returns the DXF association list of an existing entity:
;; Retrieve the last created entity
(setq data (entget (entlast)))
The result is an association list like the one you pass to entmake, enriched with additional information:
;; Example result for a line
((-1 . <Entity name: 7ff...>) ; Internal reference
(0 . "LINE") ; Type
(8 . "0") ; Layer
(10 0.0 0.0 0.0) ; Start point
(11 100.0 0.0 0.0) ; End point
...)
To extract a property, use assoc then cdr:
(cdr (assoc 0 data)) ; -> "LINE" (entity type)
(cdr (assoc 8 data)) ; -> "0" (layer)
(assoc 10 data) ; -> (10 0.0 0.0 0.0) (start point)
Modifying an entity with entmod
To modify an existing entity, follow three steps:
- Read its data with
entget - Modify the association list with
subst - Apply the changes with
entmod
;; Change the layer of the last entity
(setq entity (entlast))
(setq data (entget entity))
;; Replace the layer
(setq data
(subst
'(8 . "NewLayer") ; New value
(assoc 8 data) ; Old value
data ; Full list
)
)
;; Apply the modification
(entmod data)
The subst function replaces an occurrence in a list. Here, it replaces the pair (8 . "0") with (8 . "NewLayer").
Practical example: moving an entity
(defun c:move-last (/ entity data old-point new-point offset)
(setq offset (getpoint "\nDisplacement vector (click two points): "))
(setq entity (entlast))
(setq data (entget entity))
(setq old-point (assoc 10 data))
;; Calculate the new point
(setq new-point
(list 10
(+ (nth 1 old-point) (car offset))
(+ (nth 2 old-point) (cadr offset))
(+ (nth 3 old-point) (caddr offset))
)
)
;; Apply
(setq data (subst new-point old-point data))
(entmod data)
(princ)
)
Selecting entities
To work with existing entities, you need to select them:
entlast — the last created entity
(setq entity (entlast))
(entget entity) ; Read its data
entsel — ask the user to click
(setq selection (entsel "\nSelect an entity: "))
;; Returns a list: (entity-name selection-point)
(setq entity (car selection)) ; The entity name
(entget entity) ; Its DXF data
ssget — multiple selection
(setq ss (ssget)) ; Prompts the user for a selection
(sslength ss) ; Number of selected entities
(ssname ss 0) ; First entity in the set
(ssname ss 1) ; Second entity
Example: changing the color of a selection
(defun c:colorize (/ ss color index entity data)
(setq color (getint "\nColor number (1-255): "))
(setq ss (ssget))
(if ss
(progn
(setq index 0)
(repeat (sslength ss)
(setq entity (ssname ss index))
(setq data (entget entity))
(if (assoc 62 data)
;; Color already exists, replace it
(setq data (subst (cons 62 color) (assoc 62 data) data))
;; No color defined, add it after the layer
(setq data (append data (list (cons 62 color))))
)
(entmod data)
(setq index (1+ index))
)
(princ (strcat "\n" (itoa (sslength ss)) " entity(ies) modified."))
)
(princ "\nNo selection.")
)
(princ)
)
Which approach should you choose?
| Criterion | command |
entmake / entmod |
|---|---|---|
| Ease of learning | Simple, intuitive | Requires knowledge of DXF codes |
| Performance | Slow | Fast |
| Reliability | Depends on system variables | Context-independent |
| Portability | Language-sensitive (without _) |
Always portable |
| Undo | 1 entry per command | Full control |
| Ideal use case | Quick scripts, prototyping | Robust, distributed programs |
In summary: command is easier for getting started and for personal scripts. But as soon as your program needs to be reliable, performant, or shared with other users, prefer entmake and entmod. The extra effort of learning DXF codes pays off quickly.
In the next chapter, we will see how to read and write text files to export data or import coordinates.
Need an AutoCAD (AutoLISP, ObjectARX, .NET, VBA) development? Contact me for a free quote.