AutoLISP: The Complete Guide — Chapter 15 / 16

Reactors

In the previous chapter, we saw how to link entities together through their handles. This naturally raises a question: can we automatically react when a linked entity is modified or deleted? That is exactly what reactors are for.

A reactor is a mechanism that monitors events in AutoCAD and executes a callback function when the event occurs. If you are familiar with events in C# (for example button.Click += OnClick) or event listeners in JavaScript, it is the same concept: you subscribe to an event and provide a function that will be called when it fires.

Types of reactors

AutoLISP offers several categories of reactors through the vlr-* (Visual LISP Reactors) functions:

Type Creation function Monitors...
Object vlr-object-reactor Modifications to specific entities
Database vlr-dwg-reactor Opening, closing, and saving of the drawing
Editor vlr-editor-reactor Commands executed by the user
Command vlr-command-reactor The start and end of specific commands

Object reactor: monitoring an entity

The object reactor is the most common. It monitors one or more entities and triggers a function when they are modified:

(defun notify-modification (owner reactor parameter-list)
  (princ (strcat "\nEntity " (vl-princ-to-string owner) " has been modified!"))
  (princ)
)

;; Create a circle
(entmake
  (list
    '(0 . "CIRCLE")
    '(10 50.0 50.0 0.0)
    '(40 . 25.0)
  )
)

;; Attach a reactor to this circle
(setq my-reactor
  (vlr-object-reactor
    (list (vlax-ename->vla-object (entlast)))  ; List of entities to monitor
    "Circle monitoring"                          ; Description (user data)
    '((:vlr-modified . notify-modification))     ; List of events and callbacks
  )
)

From now on, every time the circle is moved, resized, or modified in any way, the message is displayed.

Callback signature

Each callback function receives three arguments:

  1. owner — the VLA object (not an ename) of the entity that triggered the event
  2. reactor — the reactor itself, useful for accessing user data or removing itself
  3. parameter-list — a list of additional information about the event (often nil)

To convert the VLA object to an ename (required for entget), use vlax-vla-object->ename:

(defun my-callback (owner reactor parameter-list / entity data)
  (setq entity (vlax-vla-object->ename owner))
  (setq data (entget entity))
  ;; Process the data...
  (princ)
)

Warning: the callback function of an object reactor must never modify the monitored entity directly in the :vlr-modified callback. This would cause an infinite loop (the modification triggers the reactor, which modifies the entity, which triggers the reactor...). If you need to modify an entity in response to an event, use the :vlr-objectClosed callback.

Available events for object reactors

Event Fires when...
:vlr-modified The entity is modified
:vlr-erased The entity is deleted
:vlr-copied The entity is copied
:vlr-objectClosed The entity is closed after modification (safe time to make changes)

The distinction between :vlr-modified and :vlr-objectClosed is important. The former fires during the modification (the entity is still "open" for writing), while the latter fires after (the entity is "closed" and its data is up to date). It is in :vlr-objectClosed that you can safely read the new values and modify other entities.

Database reactor: monitoring the drawing

Database reactors monitor events at the DWG file level:

(defun before-save (reactor arguments)
  (princ "\nSave in progress — verifying data...")
  ;; Here you could check the integrity of links between entities
  (princ)
)

(defun after-save (reactor arguments)
  (princ "\nSave complete.")
  (princ)
)

(vlr-dwg-reactor
  nil
  '((:vlr-beginSave . before-save)
    (:vlr-saveComplete . after-save))
)

Available events for drawing reactors

Event Fires when...
:vlr-beginSave The drawing is about to be saved
:vlr-saveComplete The save is complete
:vlr-beginClose The drawing is about to be closed
:vlr-databaseToBeDestroyed The database is about to be destroyed

Command reactor: responding to commands

Command reactors fire when the user (or a program) executes an AutoCAD command:

(defun monitor-erase (reactor arguments)
  (if (= (car arguments) "ERASE")
    (princ "\nWarning: entities are about to be deleted!")
  )
  (princ)
)

(vlr-command-reactor
  nil
  '((:vlr-commandWillStart . monitor-erase))
)

Available events for command reactors

Event Fires when...
:vlr-commandWillStart A command is about to start
:vlr-commandEnded A command has just finished
:vlr-commandCancelled A command has been cancelled (Escape)
:vlr-commandFailed A command has failed

The first element in the arguments list contains the command name in uppercase (for example "ERASE", "MOVE", "COPY").

Managing reactors

Removing a reactor

Reactors consume resources and can slow down AutoCAD if you create too many. Remember to remove them when they are no longer needed:

;; Remove a specific reactor
(vlr-remove my-reactor)

Listing active reactors

;; List all active reactors, grouped by type
(vlr-reactors)

Accessing user data

The second argument of vlr-object-reactor (the string "Circle monitoring" in our example) is a free-form field. You can store any AutoLISP value there — a handle, a list, a configuration name. To read it back:

(vlr-data my-reactor)  ; → "Circle monitoring"

This is very useful for passing context to your callbacks without using global variables.

Let us combine handles (seen in the previous chapter) and reactors to maintain a link between a circle and a text entity that displays its radius. When the user modifies the circle, the text updates automatically:

(defun update-text (owner reactor parameter-list /
                     circle-entity circle-data radius
                     text-handle text-entity text-data)
  ;; Get the circle's radius (after closing, so the data is up to date)
  (setq circle-entity (vlax-vla-object->ename owner))
  (setq circle-data (entget circle-entity))
  (setq radius (cdr (assoc 40 circle-data)))

  ;; Find the linked text via the handle in xdata
  (setq circle-data (entget circle-entity '("WIIP_LIEN")))
  (setq text-handle (cdr (assoc 1005 (cdadr (assoc -3 circle-data)))))

  (if text-handle
    (progn
      (setq text-entity (handent text-handle))
      (if text-entity
        (progn
          ;; Update the text content
          (setq text-data (entget text-entity))
          (setq text-data
            (subst
              (cons 1 (strcat "R=" (rtos radius 2 2)))
              (assoc 1 text-data)
              text-data
            )
          )
          (entmod text-data)
          (entupd text-entity)
        )
      )
    )
  )
  (princ)
)

(defun c:create-circle-with-label (/ center radius circle-entity circle-data
                                      circle-handle text-entity text-data
                                      text-handle)
  (regapp "WIIP_LIEN")

  ;; Create the circle
  (setq center (getpoint "\nCircle center: "))
  (setq radius (getreal "\nRadius: "))

  (entmake
    (list
      '(0 . "CIRCLE")
      (cons 10 (append center '(0.0)))
      (cons 40 radius)
    )
  )
  (setq circle-entity (entlast))
  (setq circle-handle (cdr (assoc 5 (entget circle-entity))))

  ;; Create the label text
  (entmake
    (list
      '(0 . "TEXT")
      (cons 10 (list (+ (car center) radius 5.0) (cadr center) 0.0))
      (cons 40 (* radius 0.3))
      (cons 1 (strcat "R=" (rtos radius 2 2)))
    )
  )
  (setq text-entity (entlast))
  (setq text-handle (cdr (assoc 5 (entget text-entity))))

  ;; Link the circle to the text via xdata
  (setq circle-data (entget circle-entity))
  (setq circle-data
    (append circle-data
      (list
        (list -3
          (list "WIIP_LIEN"
            (cons 1005 text-handle)
          )
        )
      )
    )
  )
  (entmod circle-data)

  ;; Attach a reactor to the circle
  (vlr-object-reactor
    (list (vlax-ename->vla-object circle-entity))
    "Radius label update"
    '((:vlr-objectClosed . update-text))
  )

  (princ "\nCircle created with linked label.")
  (princ)
)

When the user modifies the circle's radius (by stretching, scaling, etc.), the text updates automatically. Note the use of :vlr-objectClosed rather than :vlr-modified — this is the safe time to read the circle's new data and modify the text.

Summary

Concept Function / Syntax Description
Object reactor vlr-object-reactor Monitors modifications to specific entities
Drawing reactor vlr-dwg-reactor Monitors DWG file events
Command reactor vlr-command-reactor Monitors executed commands
Remove a reactor vlr-remove Frees the reactor's resources
List reactors vlr-reactors Returns all active reactors
User data vlr-data Reads the reactor's free-form field
VLA to ename vlax-vla-object->ename Converts a VLA object to an ename for entget
Key event When to use it
:vlr-modified To be notified of a change (read only)
:vlr-objectClosed To modify other entities in response to a change
:vlr-erased To clean up links when an entity is deleted
:vlr-commandWillStart To intercept a command before it executes

In the next chapter, we will see how to communicate with external applications like Microsoft Excel using ActiveX interoperability.


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