AutoLISP : le guide complet — Chapitre 15 / 16

Les reactors

Dans le chapitre précédent, nous avons vu comment lier des entités entre elles par leurs handles. Cela ouvre une question naturelle : peut-on réagir automatiquement quand une entité liée est modifiée ou supprimée ? C'est exactement le rôle des reactors.

Un reactor est un mécanisme qui surveille des événements dans AutoCAD et exécute une fonction de rappel (callback) quand l'événement se produit. Si vous connaissez les événements en C# (par exemple button.Click += OnClick) ou les event listeners en JavaScript, c'est le même concept : on s'abonne à un événement et on fournit une fonction qui sera appelée quand il se produit.

Types de reactors

AutoLISP propose plusieurs catégories de reactors via les fonctions vlr-* (Visual LISP Reactors) :

Type Fonction de création Surveille...
Objet vlr-object-reactor Les modifications d'entités spécifiques
Base de données vlr-dwg-reactor L'ouverture, la fermeture, la sauvegarde du dessin
Éditeur vlr-editor-reactor Les commandes exécutées par l'utilisateur
Commande vlr-command-reactor Le début et la fin de commandes spécifiques

Reactor d'objet : surveiller une entité

Le reactor d'objet est le plus courant. Il surveille une ou plusieurs entités et déclenche une fonction quand elles sont modifiées :

(defun notifier-modification (proprietaire reactor liste-parametres)
  (princ (strcat "\nL'entité " (vl-princ-to-string proprietaire) " a été modifiée !"))
  (princ)
)

;; Créer un cercle
(entmake
  (list
    '(0 . "CIRCLE")
    '(10 50.0 50.0 0.0)
    '(40 . 25.0)
  )
)

;; Attacher un reactor à ce cercle
(setq mon-reactor
  (vlr-object-reactor
    (list (vlax-ename->vla-object (entlast)))  ; Liste d'entités à surveiller
    "Surveillance cercle"                        ; Description (données utilisateur)
    '((:vlr-modified . notifier-modification))   ; Liste d'événements et callbacks
  )
)

Désormais, chaque fois que le cercle est déplacé, redimensionné ou modifié de quelque façon que ce soit, le message s'affiche.

La signature des callbacks

Chaque fonction de rappel reçoit trois arguments :

  1. proprietaire — l'objet VLA (pas un ename) de l'entité qui a déclenché l'événement
  2. reactor — le reactor lui-même, utile pour accéder aux données utilisateur ou se supprimer
  3. liste-parametres — une liste d'informations supplémentaires sur l'événement (souvent nil)

Pour convertir l'objet VLA en ename (nécessaire pour entget), utilisez vlax-vla-object->ename :

(defun mon-callback (proprietaire reactor liste-parametres / entite donnees)
  (setq entite (vlax-vla-object->ename proprietaire))
  (setq donnees (entget entite))
  ;; Traiter les données...
  (princ)
)

Attention : la fonction de rappel d'un reactor d'objet ne doit jamais modifier l'entité surveillée directement dans le callback :vlr-modified. Cela provoquerait une boucle infinie (la modification déclenche le reactor, qui modifie l'entité, ce qui déclenche le reactor...). Si vous devez modifier une entité en réponse à un événement, utilisez le callback :vlr-objectClosed.

Événements disponibles pour les reactors d'objet

Événement Se déclenche quand...
:vlr-modified L'entité est modifiée
:vlr-erased L'entité est supprimée
:vlr-copied L'entité est copiée
:vlr-objectClosed L'entité est fermée après modification (moment sûr pour modifier)

La distinction entre :vlr-modified et :vlr-objectClosed est importante. Le premier se déclenche pendant la modification (l'entité est encore « ouverte » en écriture), tandis que le second se déclenche après (l'entité est « fermée » et ses données sont à jour). C'est dans :vlr-objectClosed que vous pouvez lire les nouvelles valeurs et modifier d'autres entités en toute sécurité.

Reactor de base de données : surveiller le dessin

Les reactors de base de données surveillent les événements au niveau du fichier DWG :

(defun avant-sauvegarde (reactor arguments)
  (princ "\nSauvegarde en cours — vérification des données...")
  ;; Ici, vous pourriez vérifier l'intégrité des liens entre entités
  (princ)
)

(defun apres-sauvegarde (reactor arguments)
  (princ "\nSauvegarde terminée.")
  (princ)
)

(vlr-dwg-reactor
  nil
  '((:vlr-beginSave . avant-sauvegarde)
    (:vlr-saveComplete . apres-sauvegarde))
)

Événements disponibles pour les reactors de dessin

Événement Se déclenche quand...
:vlr-beginSave Le dessin va être sauvegardé
:vlr-saveComplete La sauvegarde est terminée
:vlr-beginClose Le dessin va être fermé
:vlr-databaseToBeDestroyed La base de données va être détruite

Reactor de commande : réagir aux commandes

Les reactors de commande se déclenchent quand l'utilisateur (ou un programme) exécute une commande AutoCAD :

(defun surveiller-effacement (reactor arguments)
  (if (= (car arguments) "ERASE")
    (princ "\nAttention : des entités vont être supprimées !")
  )
  (princ)
)

(vlr-command-reactor
  nil
  '((:vlr-commandWillStart . surveiller-effacement))
)

Événements disponibles pour les reactors de commande

Événement Se déclenche quand...
:vlr-commandWillStart Une commande est sur le point de démarrer
:vlr-commandEnded Une commande vient de se terminer
:vlr-commandCancelled Une commande a été annulée (Échap)
:vlr-commandFailed Une commande a échoué

Le premier argument de la liste arguments contient le nom de la commande en majuscules (par exemple "ERASE", "MOVE", "COPY").

Gérer les reactors

Supprimer un reactor

Les reactors consomment des ressources et peuvent ralentir AutoCAD si vous en créez trop. Pensez à les supprimer quand ils ne sont plus nécessaires :

;; Supprimer un reactor spécifique
(vlr-remove mon-reactor)

Lister les reactors actifs

;; Lister tous les reactors actifs, groupés par type
(vlr-reactors)

Accéder aux données utilisateur

Le deuxième argument de vlr-object-reactor (la chaîne "Surveillance cercle" dans notre exemple) est un champ libre. Vous pouvez y stocker n'importe quelle valeur AutoLISP — un handle, une liste, un nom de configuration. Pour le relire :

(vlr-data mon-reactor)  ; → "Surveillance cercle"

C'est très utile pour transmettre du contexte à vos callbacks sans utiliser de variables globales.

Exemple pratique : maintenir un lien entre entités

Combinons les handles (vus au chapitre précédent) et les reactors pour maintenir un lien entre un cercle et un texte qui affiche son rayon. Quand l'utilisateur modifie le cercle, le texte se met à jour automatiquement :

(defun mettre-a-jour-texte (proprietaire reactor liste-parametres /
                             entite-cercle donnees-cercle rayon
                             handle-texte entite-texte donnees-texte)
  ;; Récupérer le rayon du cercle (après fermeture, donc les données sont à jour)
  (setq entite-cercle (vlax-vla-object->ename proprietaire))
  (setq donnees-cercle (entget entite-cercle))
  (setq rayon (cdr (assoc 40 donnees-cercle)))

  ;; Retrouver le texte lié via le handle dans les xdata
  (setq donnees-cercle (entget entite-cercle '("WIIP_LIEN")))
  (setq handle-texte (cdr (assoc 1005 (cdadr (assoc -3 donnees-cercle)))))

  (if handle-texte
    (progn
      (setq entite-texte (handent handle-texte))
      (if entite-texte
        (progn
          ;; Mettre à jour le contenu du texte
          (setq donnees-texte (entget entite-texte))
          (setq donnees-texte
            (subst
              (cons 1 (strcat "R=" (rtos rayon 2 2)))
              (assoc 1 donnees-texte)
              donnees-texte
            )
          )
          (entmod donnees-texte)
          (entupd entite-texte)
        )
      )
    )
  )
  (princ)
)

(defun c:creer-cercle-avec-etiquette (/ centre rayon entite-cercle donnees-cercle
                                        handle-cercle entite-texte donnees-texte
                                        handle-texte)
  (regapp "WIIP_LIEN")

  ;; Créer le cercle
  (setq centre (getpoint "\nCentre du cercle : "))
  (setq rayon (getreal "\nRayon : "))

  (entmake
    (list
      '(0 . "CIRCLE")
      (cons 10 (append centre '(0.0)))
      (cons 40 rayon)
    )
  )
  (setq entite-cercle (entlast))
  (setq handle-cercle (cdr (assoc 5 (entget entite-cercle))))

  ;; Créer le texte d'étiquette
  (entmake
    (list
      '(0 . "TEXT")
      (cons 10 (list (+ (car centre) rayon 5.0) (cadr centre) 0.0))
      (cons 40 (* rayon 0.3))
      (cons 1 (strcat "R=" (rtos rayon 2 2)))
    )
  )
  (setq entite-texte (entlast))
  (setq handle-texte (cdr (assoc 5 (entget entite-texte))))

  ;; Lier le cercle au texte via xdata
  (setq donnees-cercle (entget entite-cercle))
  (setq donnees-cercle
    (append donnees-cercle
      (list
        (list -3
          (list "WIIP_LIEN"
            (cons 1005 handle-texte)
          )
        )
      )
    )
  )
  (entmod donnees-cercle)

  ;; Attacher un reactor au cercle
  (vlr-object-reactor
    (list (vlax-ename->vla-object entite-cercle))
    "Mise à jour étiquette rayon"
    '((:vlr-objectClosed . mettre-a-jour-texte))
  )

  (princ "\nCercle créé avec étiquette liée.")
  (princ)
)

Quand l'utilisateur modifie le rayon du cercle (par étirement, mise à l'échelle, etc.), le texte se met à jour automatiquement. Notez l'utilisation de :vlr-objectClosed et non :vlr-modified — c'est le moment sûr pour lire les nouvelles données du cercle et modifier le texte.

Résumé

Concept Fonction / Syntaxe Description
Reactor d'objet vlr-object-reactor Surveille les modifications d'entités spécifiques
Reactor de dessin vlr-dwg-reactor Surveille les événements du fichier DWG
Reactor de commande vlr-command-reactor Surveille les commandes exécutées
Supprimer un reactor vlr-remove Libère les ressources du reactor
Lister les reactors vlr-reactors Retourne tous les reactors actifs
Données utilisateur vlr-data Lit le champ libre du reactor
VLA → ename vlax-vla-object->ename Convertit un objet VLA en ename pour entget
Événement clé Quand l'utiliser
:vlr-modified Pour être notifié d'un changement (lecture seule)
:vlr-objectClosed Pour modifier d'autres entités en réponse au changement
:vlr-erased Pour nettoyer les liens quand une entité est supprimée
:vlr-commandWillStart Pour intercepter une commande avant qu'elle ne s'exécute

Dans le prochain chapitre, nous verrons comment communiquer avec des applications externes comme Microsoft Excel grâce à l'interopérabilité ActiveX.


Coup de pouce Besoin d'un développement AutoCAD (AutoLISP, ObjectARX, .NET, VBA) ? Contactez-moi pour un devis gratuit.