Ein paar Worte vorabHome   Letzte MeldungenNews   Index der Kapitel und der besprochenen FunktionenIndex   Wer ich bin, warum ich diese Seiten mache, KontaktImpressum   Ich freue mich über jeden Eintrag im Gästebuch!Gästebuch   Einige Links zu anderen AutoLisp-SeitenLinks   Copyrights und DisclaimerRechts
Hier können die kompletten Seiten als ZIP-File heruntergeladen werden!

Einführung zum Programmieren mit VisualLisp Welcome to...
Das ActiveX-Objektmodell - Grundlage der vl-Programmierung in AutoCAD Das Objekt
Die vla-Funktionen: Viel ActiveX - wenig Dokumentation Knielang
Funktionen für den schnellen Zugriff in VisualLisp Breiter Gürtel
Variants - der Gummi-Datentyp von VBA Damenhandtasche
Collections - VB-Sammelbehälter in VisualLisp Kommste mit rauf?
Das Auffangen von Fehlern in VisualLisp Plumps
Berechnen von Schnittpunkten zwischen Entities mit ActiveX Windschnittig!
Ein erster, einfacher Reaktor, der viel Arbeit sparen kann Faulheit
Importieren von Views aus einer geschlossenen Zeichnung mit DBX Deutsche Bahn


Zum Einsteiger-Tutorial

Zu den Seiten für Fortgeschrittene

Meine Private HP mit Fotos, Gedichten, Musik und Postkartenversand

Mein Online-Lexikon der Fotografie

Mein völlig abgedrehtes Reisebüro










Die Reaktoren in AutoCAD sind das, was man in anderen Programmiersprachen als 'Eventhandler' kennt. Das heisst, dass es sich hier um Programme bzw. Funktionen handelt, die immer dann ausgeführt werden, wenn ein bestimmtes Ereignis eintritt. Im Falle eines Command-Reaktors, den wir hier kennenlernen werden, wird der Programmcode immer dann ausgeführt, wenn AutoCAD einen der AutoCAD-Befehle ausführt. Das ist ja recht häufig der Fall, ausserdem gibt es keine Möglichkeit, den Programmcode nur an einen bestimmten Befehl zu binden.

Natürlich bekommt unsere aufgerufene Funktion mitgeteilt, welcher Befehl gerade ausgeführt wird. Handelt es sich dabei um einen Befehl, den wir weiterverarbeiten wollen, wird aus unserer Reaktorfunktion heraus eine weitere Funktion aufgerufen, die die Verarbeitung übernimmt. Soll keine Verarbeitung stattfinden, wird nichts weiter aufgerufen und die Funktion beendet.

Als ein Beispiel soll hier folgendes dienen: Wenn man beim Arbeiten in einer Zeichnung bemasst, sollte man tunlichst einen eigenen Layer für die Bemassungen verwenden - oder besser gleich mehrere, damit Bemassungen für verschiedene Zeichnungsmassstäbe voneinander trennbar bleiben. Man kann dann sehr schön einen, mehrere oder alle Bemassungslayer wegfrieren usw.

In der Praxis ist es aber aufwändig und mühsam, jedes Mal vor einer Bemassung den entsprechenden Layer zu setzen: oft wird es vergessen. Je nachdem, wie die Bemassungsfarben eingestellt sind, sieht man den Fehler sofort oder aber überhaupt nicht. Ziel der Übung ist es, den Layerwechsel zu automatisieren und dafür zu sorgen, dass Bemassungen immer automatisch auf dem richtigen Layer liegen - im Alltag eines Zeichners lässt sich so eine Menge Arbeit einsparen.

Ähnlich verhält es sich mit Schraffuren: Auch sie sollten auf einem eigenen Layer liegen, damit man sie problemlos zwischenzeitig wegfrieren kann. Auch hier wird unsere Reaktor-Funktion für Ordnung sorgen. Damit lässt sich die Aufgabe der Reaktor-Funktion klar definieren: Sie muss, feststellen, ob einer der AutoCAD-Bemassungsbefehle oder der Schraffurbefehl aktiv ist und an entsprechende Unterfunktionen delegieren. Bei allen anderen Befehlen soll nichts passieren.

Das nun folgende kurze Beispiel zeigt, wie eine Reaktorfunktion aussieht. Die verwendeten Variablen :vlr-commandWillStart und :vlr-commandEnded sind vordefiniert, der Doppelpunkt am Anfang der Namen hat keinerlei technische Bedeutung - die Namen wurden von AutoDesk einfach so festgelegt. Inhaltlich bedeutet diese Argumente, dass wir hier zwei Reaktorfunktionen anmelden: die eine wird aufgerufen, bevor AutoCAD einen Befehl ausführt, die andere kommt dann nach Beendigung des Befehls dran.

Beide Aufrufe von (vlr-command-reactor) haben übrigens ein erstes Argument, wobei im hier vorliegenden Fall nil übergeben wird. Dieses Argument ist für die Übergabe von Daten an den Reaktor vorgesehen. Da hier aber keine Daten notwendig sind, wird nichts übergeben.
; Callback-Funktion, die vom Reaktor
; vor der Ausführung eines Befehles
; aufgerufen wird
(defun cmd-start-callback(reaktor argumente / )
  (princ(strcat"START: "(car(ergumente))"\n"))
)

; Callback-Funktion, die vom Reaktor
; nach der Ausführung eines Befehles
; aufgerufen wird
(defun cmd-end-callback(reaktor argumente / )
  (princ(strcat"END: "(car(ergumente))"\n"))
)

; Funktion zum Setzen des Reaktors
(defun add-cmd-reactors( / )
  (vl-load-com)
  (vlr-command-reactor nil
    '( (:vlr-commandWillStart . cmd-start-callback))
  )
  (vlr-command-reactor nil
    '( (:vlr-commandEnded . cmd-end-callback))
  )
)
                  
Sobald dieser Code geladen und die Funktion (add-cmd-reactors) einmal ausgeführt ist, wird jeder von AutoCAD ausgeführte Befehl zweimal auf der Kommandozeile ausgegeben: Einmal vor und einmal nach der Ausführung. Bei diesem Beispiel ging es darum, erst einmal den generellen Weg aufzuzeigen, wie so ein Reaktor überhaupt angelegt wird. Bisher wird jeder AutoCAD-Befehl verarbeitet, eine Filterung nach speziellen Befehlen fand noch nicht statt.

Bevor wir uns aber der Filterung zuwenden, muss erst noch ein anderes Problem gelöst werden: Wenn die Funktion (add-cmd-reactors) noch einmal ausgeführt wird, werden zwei weitere Reaktoren aktiviert! Das sollte aber auf jeden Fall vermieden werden. Wir tun dies mit Hilfe einer globalen Vraiablen, deren Namen wir ordentlich in Sternchen setzen, damit man auch sieht, dass es eine globale Variable ist:
(defun add-cmd-reactors( / )
  (vl-load-com)
  (if(not *cmd-reactor-added*)
    (progn
      (vlr-command-reactor nil
       '( (:vlr-commandWillStart . cmd-start-callback))
      )
      (vlr-command-reactor nil
       '( (:vlr-commandEnded . cmd-end-callback))
      )
      (setq *cmd-reactor-added* 'T)
    )
  )
)
                  
Dieser Mechanismus verhindert nun, dass - auch bei mehrmaligem Ausführen von (add-cmd-reactors) - mehr als die notwendigen Reaktoren installiert werden. Diesen Sicherheitsmechanismus sollte man übrigens in allen Reaktor-Programmen benutzen.

Als nächstes können wir uns nun der Verteilung der Aufgaben zuwenden: Jede von einem Reaktor aufgerufene Funktion erhält zwei Argumente: Das erste Argument ist der Reaktor selbst, dieses Argument benötigen wir aber in unserem Beispiel nicht, weshalb wir es auch nicht auswerten. Das zweite Argument enthält den von AutoCAD ausgeführten Befehl - es ist aber zu beachten, dass es sich hier um eine Liste handelt. Das hat seinen praktischen Grund darin, dass alle durch einen Reaktor aufgerufenen Callback-Funktionen einen einheitlichen Aufbau haben müssen: Das zweite Argument ist immer eine Liste - daher müssen wir einfach den Befehlsnamen mit (car ...) daraus extrahieren.

Den Filter für die zu verarbeitenden Befehle können wir mit den klassischen Lispfunktionen (cond ...) und (member ...) verwirklichen und dann die entsprechenden Verarbeitungsroutinen daraus aufrufen:
(defun cmd-start-callback(reactor argslist / )
  (cond
    ( (member(car argslist)'("DIMLINEAR""DIMALIGNED"
                             "DIMRADIUS""DIMANGULAR"
                             "DIMBASELINE""DIMDIAMETER"
                             "DIMCONTINUE""QLEADER"))
      (dim-start-callback)
    )
    ( (=(car argslist)"BHATCH")
      (bhatch-start-callback)
    )
  )
)

(defun cmd-end-callback(reactor argslist / )
  (cond
    ( (member(car argslist)'("DIMLINEAR""DIMALIGNED"
                             "DIMRADIUS""DIMANGULAR"
                             "DIMBASELINE""DIMDIAMETER"
                             "DIMCONTINUE""QLEADER"))
      (dim-end-callback)
    )
    ( (=(car argslist)"BHATCH")
      (bhatch-end-callback)
    )
  )
)
                  
Nun müssen wir nur noch die Funktionen am Ende der Kette definieren, die die eigentliche Arbeit, nämlich das Umschalten der Layer, erledigen. Die jeweiligen Start-Funktionen speichern den aktuellen Layer ab (auch hier verwenden wir wieder eine globale Variable) und setzen dann den jeweils notwendigen Layer. Die End-Funktion stellt nach Beendigung des Befehls den alten Layerstand wieder her.

Zunächst die Funktionen für den Schraffur-Befehl - wir setzen hier exemplarisch den Layer "Schraffur". Natürlich kann der Name dieses Layers auch anders lauten - die Anpassung ist ja sicher kein Problem. Wie nicht anders zu erwarten war, wird hier aber nicht der gesamte Code in eine unübersichtliche Monsterfunktion gepackt. Wir definieren zuvor ein paar Hilfsfunktionen, die uns auch in anderen Zusammhängen noch gute Dienste leisten können.
; Macht einen Layer aktuell
(defun set-layer(name / )
  (vla-put-ActiveLayer
    (current-document)
    (vla-item(document-layers)name)
  )
)
                  
(set-layer) macht einen namentlich angegebenen Layer aktuell. Liegt der Layer als Vla-Object vor, verwendet man statt dessen den Ausdruck (vla-put-ActiveLayer (current-document) LayerObject), d.h. der Ausdruck (vla-item(document-layers)name) wandelt den Layer-Namen in ein Vla-Object um. Dies funktioniert aber nur, wenn der Layer tatsächlich existiert, also in der Layer-Collection enthalten ist. Wenn das nicht der Fall ist, bricht die Methode item mit einem Fehler ab!

Daher brauchen wir gleich noch eine Hilfsfunktion, die ebenfalls die Layer-Collection durchsucht, aber keinen Fehler verursacht, wenn der Layer nicht existiert. Hier wird einfach nil zurückgegeben:
; gibt das Vla-Object zu einem
; Layernamen zurück
(defun layer-object(name / result)
  (vlax-for layer(document-layers)
    (if
      (=(strcase name)
        (strcase(vla-get-name layer))
      )
      (setq result layer)
    )
  )
  result
)
                  
(layer-object name) gibt also das entsprechende vla-Object zurück, wenn der Layer existiert, oder nil, wenn nicht. Wir brauchen diese Funktion soäter, um zu entscheiden, ob ein Schraffur- oder Bemassungslayer angelegt werden muss oder nicht, bevor er aktuell gemacht wird.
; Erzeugt einen neuen Layer
(defun create-layer(name props / newlayer)
  (setq newlayer
    (vla-add (document-layers) name)
  )
  (foreach prop props
    (vlax-put-property
      newlayer (car prop)(cdr prop)
    )
  )
  newlayer
)
                  
Die Funktion (create-layer) dient zum Erzeugen eines neuen Layers. Sie bekommt den Namen als erstes Argument, dann folgt eine Liste (dotted pairs!) mit Paaren von Eigenschaften und Werten.
; Ermittelt den aktuellen Layer
(defun get-current-layer( / )
  (vla-get-activelayer
    (current-document)
  )
)
                  
Die sehr simple Funktion (get-current-layer) gibt uns den derzeit aktuellen Layer zurück. Wir benötigen ihn, um ihn in einer globalen Variablen zwischenzuspeichern, damit er nach Ausführung des Befehls wieder aktuell gemacht werden kann.
; Gibt die Layer-Collection zurück
(defun document-layers( / )
  (vla-get-layers
    (current-document)
  )
)
                  
(document-layers) ist eine ebenfalls sehr schlichte Hilfsfunktion, die uns den Zugriff auf die Layer-Collection der aktiven Zeichnung ohne viel Schreibarbeit ermöglicht. Als letzte Hilfsfunktion dann noch das hier:
; gibt die aktuelle Zeichnung zurück
(defun current-document( / )
  (vla-get-activedocument
    (vlax-get-acad-object)
  )
)
                  
Diese Funktion erspart uns einfach ein bisschen Schreibarbeit. Damit haben wir unsere Hilfsfunktionen beisammen und können uns den eigentlichen Callback-Funktionen (zweiten Grades) zuwenden. Zunächst einmal die Funktion für den Schraffur-Befehl:
; Vorher-Callback für Schraffuren
(defun bhatch-start-callback( / layer)
  (setq layer "S_Schraffuren")
  (if(not(layer-object layer))
    (create-layer layer '((Color . 140)))
  )
  (setq *previous-layer*(get-current-layer))
  (set-layer layer)
)
                  
Hie passiert nichts wirklich aufregendes. Es wird getestet, ob der Schraffur-Layer bereits vorhanden ist - wenn nicht, wird er eben angelegt. Und dann wird er - bevor der Schraffur-Befehl ausgeführt wird - aktuell gemacht.
; Nachher-Callback für Schraffuren
(defun bhatch-end-callback( / )
  (if *previous-layer*
    (progn
      (vla-put-activelayer
        (current-document)
        *previous-layer*
      )
      (setq *previous-layer* nil)
    )
  )
)
                  
Diese Funktion setzt nach der Ausführung des Schraffur-Befehls den aktuellen Layer wieder zurück. Dass vorher nachgeprüft wird, ob die globale Variable *previous-layer* wirklich existiert, ist nur eine zusätzliche Sicherung, die eventuelle Fehlerquellen abfangen soll.

Nun können wir uns den Callback-Funktionen für die Bemassungen zuwenden, wobei die Sache ein klein wenig komplizierter wird. Hier wird es natürlich in jeder Firma unterschiedliche Auffassungen geben, wie welche Bemassungen auf welchen Layern liegen sollten. In unserem Beispiel gehen wir davon aus, dass die Zeichnungs-Massstäbe zwischen 10:1 und 1:10 liegen können und dass für jeden Massstab ein eigener Bemassungslayer verwendet werden soll.

Um zu ermitteln, in welchem Massstab die anstehende Bemassung erfolgen soll, lesen wir die AutoCAD-Systemvariable DIMSCALE aus. Diese sollte dem gewünschten Massstab umgekehrt proportional sein, also zwischen 10.0 und 0.1 liegen.
; Vorher-Callback für Bemassungen
(defun dim-start-callback(reactor argslist /
                             scale str layer)
  (setq scale(getvar"dimscale"))
  (setq str
    (cond
      ( (>= scale 10.0 )  "1~10")
      ( (>= scale  5.0 )  "1~5")
      ( (>= scale  4.0 )  "1~4")
      ( (>= scale  3.0 )  "1~3")
      ( (>= scale  2.0 )  "1~2")
      ( (>= scale  1.0 )  "1~1")
      ( (>= scale  0.5 )  "2~1")
      ( (>= scale  0.33)  "3~1")
      ( (>= scale  0.25)  "4~1")
      ( (>= scale  0.20)  "5~1")
      ( (>= scale  0.10)  "10~1")
      ( 1 "1~XX")
    )
  )
  (setq layer(strcat "Bem_" str))
  (if(not(layer-object layer))
    (create-layer layer '((Color . 11)))
  )
  (setq *previous-layer*(get-current-layer))
  (set-layer layer)
)
                  
Diese Funktion ermittelt aus dem Inhalt der Systemvariablen DIMSCALE einen Layernamen wie z.B. "Bem_1~5" und schaut dann nach, ob es diesen Layer bereits gibt. Falls nicht, wird er angelegt. Auf jeden Fall landet die folgende Bemassung auf dem richtigen Layer. Die entsprechende End-Callback-Funktion ist übrigens genau die Gleiche wie bei den Schraffuren. Trotzdem verwenden wir hier eine eigene Funktion, um die Dinge klar und übersichtlich zu halten.
; Nachher-Callback für Bemassungen
(defun dim-end-callback( / )
  (if *previous-layer*
    (progn
      (vla-put-activelayer
        (current-document)
        *previous-layer*
      )
      (setq *previous-layer* nil)
    )
  )
)
                  
Abschliessend noch einmal der gesamte Code im Zusammenhang, um das Abkopieren vom Bildschirm zu erleichtern. Der Code muss in einer Lisp-Datei gespeichert werden, und solbald diese in AutoCAD geladen wird, ist unser Command-Reactor am Werk (der Aufruf von (add-cmd-reactors) wird beim Laden automatisch durchgeführt).
; Anfang der Datei

; Callback-Funktion, die vom Reaktor
; vor der Ausführung eines Befehles
; aufgerufen wird
(defun cmd-start-callback(reaktor argumente / )
  (princ(strcat"START: "(car(ergumente))"\n"))
)

; Callback-Funktion, die vom Reaktor
; nach der Ausführung eines Befehles
; aufgerufen wird
(defun cmd-end-callback(reaktor argumente / )
  (princ(strcat"END: "(car(ergumente))"\n"))
)

; Funktion zum Setzen des Reaktors
(defun add-cmd-reactors( / )
  (vl-load-com)
  (vlr-command-reactor nil
    '( (:vlr-commandWillStart . cmd-start-callback))
  )
  (vlr-command-reactor nil
    '( (:vlr-commandEnded . cmd-end-callback))
  )
)

; Funktion zum Setzen der Reaktoren
(defun add-cmd-reactors( / )
  (vl-load-com)
  (if(not *cmd-reactor-added*)
    (progn
      (vlr-command-reactor nil
       '( (:vlr-commandWillStart . cmd-start-callback))
      )
      (vlr-command-reactor nil
       '( (:vlr-commandEnded . cmd-end-callback))
      )
      (setq *cmd-reactor-added* 'T)
    )
  )
)

; allgemeine Cmd-Callback-Funktion
; (vor der Befehlsausführung)
(defun cmd-start-callback(reactor argslist / )
  (cond
    ( (member(car argslist)'("DIMLINEAR""DIMALIGNED"
                             "DIMRADIUS""DIMANGULAR"
                             "DIMBASELINE""DIMDIAMETER"
                             "DIMCONTINUE""QLEADER"))
      (dim-start-callback)
    )
    ( (=(car argslist)"BHATCH")
      (bhatch-start-callback)
    )
  )
)

; allgemeine Cmd-Callback-Funktion
; (nach der Befehlsausführung)
(defun cmd-end-callback(reactor argslist / )
  (cond
    ( (member(car argslist)'("DIMLINEAR""DIMALIGNED"
                             "DIMRADIUS""DIMANGULAR"
                             "DIMBASELINE""DIMDIAMETER"
                             "DIMCONTINUE""QLEADER"))
      (dim-end-callback)
    )
    ( (=(car argslist)"BHATCH")
      (bhatch-end-callback)
    )
  )
)

; Macht einen Layer aktuell
(defun set-layer(name / )
  (vla-put-ActiveLayer
    (current-document)
    (vla-item(document-layers)name)
  )
)

; gibt das Vla-Object zu einem
; Layernamen zurück
(defun layer-object(name / result)
  (vlax-for layer(document-layers)
    (if
      (=(strcase name)
        (strcase(vla-get-name layer))
      )
      (setq result layer)
    )
  )
  result
)

; Erzeugt einen neuen Layer
(defun create-layer(name props / newlayer)
  (setq newlayer
    (vla-add (document-layers) name)
  )
  (foreach prop props
    (vlax-put-property
      newlayer (car prop)(cdr prop)
    )
  )
  newlayer
)

; Ermittelt den aktuellen Layer
(defun get-current-layer( / )
  (vla-get-activelayer
    (current-document)
  )
)

; Gibt die Layer-Collection zurück
(defun document-layers( / )
  (vla-get-layers
    (current-document)
  )
)

; gibt die aktuelle Zeichnung zurück
(defun current-document( / )
  (vla-get-activedocument
    (vlax-get-acad-object)
  )
)

; Vorher-Callback für Schraffuren
(defun bhatch-start-callback( / layer)
  (setq layer "S_Schraffuren")
  (if(not(layer-object layer))
    (create-layer layer '((Color . 140)))
  )
  (setq *previous-layer*(get-current-layer))
  (set-layer layer)
)

; Nachher-Callback für Schraffuren
(defun bhatch-end-callback( / )
  (if *previous-layer*
    (progn
      (vla-put-activelayer
        (current-document)
        *previous-layer*
      )
      (setq *previous-layer* nil)
    )
  )
)

; Vorher-Callback für Bemassungen
(defun dim-start-callback(reactor argslist /
                            scale str layer)
  (setq scale(getvar"dimscale"))
  (setq str
    (cond
      ( (>= scale 10.0 )  "1~10")
      ( (>= scale  5.0 )  "1~5")
      ( (>= scale  4.0 )  "1~4")
      ( (>= scale  3.0 )  "1~3")
      ( (>= scale  2.0 )  "1~2")
      ( (>= scale  1.0 )  "1~1")
      ( (>= scale  0.5 )  "2~1")
      ( (>= scale  0.33)  "3~1")
      ( (>= scale  0.25)  "4~1")
      ( (>= scale  0.20)  "5~1")
      ( (>= scale  0.10)  "10~1")
      ( 1 "1~XX")
    )
  )
  (setq layer(strcat "Bem_" str))
  (if(not(layer-object layer))
    (create-layer layer '((Color . 11)))
  )
  (setq *previous-layer*(get-current-layer))
  (set-layer layer)
)

; Nachher-Callback für Bemassungen
(defun dim-end-callback( / )
  (if *previous-layer*
    (progn
      (vla-put-activelayer
        (current-document)
        *previous-layer*
      )
      (setq *previous-layer* nil)
    )
  )
)

; Sicherstellen, dass die ActiveX-
; Funktionen geladen sind
(vl-load-com)
; Reaktoren beim Laden starten
(add-cmd-reactors)

; Ende Datei
                  

Übungsaufgaben

  1. Erweitern Sie den Reaktor dahingehend, dass alle Mansfens automatisch auf dem Layer 'Viewports' erzeugt werden.

  2. Erweitern Sie den Reaktor dahingehend, dass alle INSERTs (Blockreferenzen) automatisch auf dem Layer 'Inserts' erzeugt werden.

  3. Erweitern Sie den Reaktor um einen Schalter, mit dem er an- und ausgestellt werden kann, z.B. um eine Schraffur zu erzeugen, die ausnahmsweise NICHT auf dem Vorgabelayer landen soll. Als Schalter soll eine globale Variable dienen, die jeweils abgefragt wird.

  4. Erweitern Sie den Reaktor dahingehend, dass bei Verwendung des Befehles BAND eine Alert-Box aufgeht, die den Benutzer darüber aufklärt, dass der Befehl BAND Ballast aus grauer AutoCAD-Vorzeit ist und dass alles, was BAND kann, mit dem Befehl POLYLINIE viel besser zu erledigen ist.

  5. Lösungen