AutoLISP: The Complete Guide — Chapter 10 / 16

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 _D instead 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 CMDECHO before 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 altered
  • ANGDIR (angle direction): counterclockwise or clockwise? Your angle calculation may produce an inverted result
  • ANGBASE (base angle): angle 0 does not necessarily point east
  • AUNITS (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 command goes 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 command creates an entry in the undo history, which can be problematic if your program executes 50 commands — the user will need to press Ctrl+Z 50 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-s by default. Only fall back to command if you need pause for 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, ANGBASE have 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:

  1. Read its data with entget
  2. Modify the association list with subst
  3. 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.


Helping hand Need an AutoCAD (AutoLISP, ObjectARX, .NET, VBA) development? Contact me for a free quote.