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
-
Erweitern Sie den Reaktor dahingehend, dass alle Mansfens
automatisch auf dem Layer 'Viewports' erzeugt werden.
-
Erweitern Sie den Reaktor dahingehend, dass alle INSERTs
(Blockreferenzen) automatisch auf dem Layer 'Inserts' erzeugt
werden.
-
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.
-
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.
Lösungen