Manche Dinge beim Programmieren in AutoCAD mit AutoLisp
können über Kurz oder Lang doch recht langweilig werden.
Dazu gehört z.B. der Zugriff auf Gruppencodes in Elementdaten.
Nicht nur, dass man die DXF-Codes im Kopf haben muss! Es
ist auch die immerwiederkehrende Anwendung der Kombination
(cdr(assoc dxfcode ...)), die uns langweilig
wird.
Da liegt also Automatisierungsbedarf vor. Funktionen zu
schreiben, die den Code ein bisschen straffen, ist kein
Kunststück. Der Code ist alles andere als komplex, wir
brauchen nur eine Zeile. Die nachfolgende Funktion extrahiert
z.B. den Layer aus einer Datenliste:
(defun get-layer(data / )
(cdr(assoc 8 data))
)
Ein wenig komfortabler wird das Ganze, wenn wir das zu
übergebende Argument etwas 'aufweichen' - schliesslich
brauchen wir so eine Funktion manchmal für einen Elementnamen,
manchmal für eine Datenliste, die wir schon haben. Wir
fügen also noch eine Zeile ein:
(defun get-layer(data / )
(if(=(type data)'ENAME)(setq data(entget data)))
(cdr(assoc 8 data))
)
Jetzt können wir die Funktion so oder so aufrufen, das macht
sie ungleich praktischer in der Anwendung:
(get-layer(car(entsel))) => "0"
(get-layer(entget(entlast))) => "0"
Schön und gut, jetzt haben wir eine Funktion, die uns den
Layer extrahiert. Es bedarf wenig Fantasie, um zu erkennen,
dass weitere Funktionen für die anderen Gruppencodes fast
genauso aussehen würden - lediglich der Name und der Gruppencode
wären anders. Ein Muster für diese Funktions-Familie könnte
so aussehen:
(defun <funktionsname>(data / )
(if(=(type data)'ENAME)(setq data(entget data)))
(cdr(assoc <gruppencode> data))
)
Jetzt wäre es ganz einfach, z.B. mit einem Texteditor die
spitzen Klammern durch die entsprechenden Inhalte zu ersetzen.
Aber das kriegen wir doch auch mit Lisp hin! Was wir brauchen,
ist eine Funktion, die diese Schablone benutzt und ausserdem
eine Liste mit den Namen sowie Gruppencodes als Argument erhält,
und die dann unsere gewünschten Funktionen sozusagen in
Nullzeit produziert.
Die spitzen Klammern sind übrigens in Symbolnamen in AutoLisp
durchaus erlaubt. Irgendeine Form von besonderer Bedeutung
habe sie da nicht. Wir verwenden sie hier einzig und allein der
Optik wegen!
Die Sache ist ganz einfach zu realisieren, wenn wir auf die
Funktion
(mapin ...) zurückgreifen, der hier ein eigenes
Kapitel gewidmet ist. Wer dieses Kapitel noch nicht kennt,
sollte dies wohl zuerst einmal nachholen.
mapin durchsucht
jedenfalls beliebig tief verschachtelte Listen und wendet
einen lambda-ausdruck auf jedes Atom in der Liste an. Das ist
genau das, was wir brauchen, um die Platzhalter durch Inhalte
zu ersetzen. So sieht unser Funktionsgenerator für eine Funktion
zunächst aus:
(defun mk-dxf-ass-func(name gruppencode / )
(eval
(mapin
'(lambda(ein-atom / )
(cond
( (= ein-atom '<funktionsname>)
(read(strcat "get-" name))
)
( (= ein-atom '<gruppencode>)gruppencode)
(1 ein-atom)
)
)
'(defun <funktionsname>(data / )
(if(=(type data)'ENAME)
(setq data(entget data))
)
(cdr(assoc <gruppencode> data))
)
)
)
)
Wenn wir ihn mit den entsprechenden Argumenten aufrufen,
produziert dieser Generator die entsprechenden Funktionen:
(mk-dxf-ass-func "layer" 8) => (get-layer)
(get-layer(entlast)) => "0"
(mapcar
'(lambda(defpaar / )
(apply 'mk-dxf-ass-func defpaar)
)
'( (6 . "Linientyp")
(8 . "Layer")
(62 . "Farbe")
(48 . "LTFaktor")
(60 . "Unsichtbar")
(67 . "Bereich")
)
)
Die letzte Anwendung von
mapcar produziert gleich
Funktionen in Serie, und wir können alle diese Funktionen
sofort benutzen.
Jetzt haben wir also die Funktionen zum Auslesen von
Gruppencodes im Griff. Manchmal brauchen wir aber auch
solche, die mit
(entmod) einen bestimmten Guppencode
verändern - ein roter Kreis soll z.B. grün werden!
Wir gehen jetzt noch einen Schritt weiter und schreiben
eine ganz allgemeine Funktion, die eine Schablone sowie
die einzusetzenden Inhalte als Argumente erhält:
(defun subst-in-template(template val-pairs / )
(mapin
'(lambda(atm / )
(if(assoc atm val-pairs)
(cdr(assoc atm val-pairs))
atm
)
)
template
)
)
Die Arbeitsweise ist hier anders: Diese Funktion ersetzt
nur in beliebigen Schablonen und kann eine generierte
neue Funktionsdefinition zurückgeben, aber auch eine
Datenliste! Evaluiert wird nichts. Unser Layer-Beispiel:
(subst-in-template
'(defun <funktionsname>(data / )
(if(=(type data)'ENAME)(setq data(entget data)))
(cdr(assoc <gruppencode> data))
)
'( (<funktionsname> . get-layer)
(<gruppencode> . 8)
)
)
=> (DEFUN GET-LAYER (DATA /) (IF (= (TYPE DATA)
(QUOTE ENAME)) (SETQ DATA (ENTGET DATA)))
(CDR (ASSOC 8 DATA)))
In diesem Beispiel wird also nur der Code für
get-layer
geliefert, wollen wir die Funktion benutzen, müssen wir noch
ein
(eval ...) drumherumsetzen. Die Funktion
subst-in-template ist aber ungleich flexibler als unsere
bisherigen Ansätze. Für die gewünschten Funktionen zum
Ändern mit
(entmod ...) setzen wir einfach eine andere
Schablone ein, die wir im nächsten Beispiel zur Abwechslung
einmal vorab extern definieren:
(setq set-template
'(defun <funktionsname>(data / )
(if(=(type data)'ENAME)(setq data(entget data)))
(entmod
(if(assoc <gruppencode> data)
(subst
(cons <gruppencode> val)
(assoc <gruppencode> data)
data
)
(append
(list(cons <gruppencode> val))
data
)
)
)
)
)
Nun wenden wir eine eine etwas komplexere lambda-expression
an, um in einem Rutsch gleich mehrere entmod-Funktionen zu
erzeugen und auch sofort benutzbar zu machen. Ein Hinweis:
Der Name mit abschliessendem Ausrufungszeichen
soll darauf hinweisen, dass es sich hier um reine
Nebeneffekt-Funktionen, also destruktive Funktionen handelt.
Zurückgegeben wird das Ergebnis von
(entmod ...), das
evtl. zur Überprüfung verwendet werden kann.
(defun mk-dxf-set-funcs( / )
(mapcar
'(lambda(funcdata / )
(eval
(subst-in-template set-template funcdata)
)
)
'( ( (<funktionsname> . set-layer!)
(<gruppencode> . 8)
)
( (<funktionsname> . set-linientyp!)
(<gruppencode> . 6)
)
( (<funktionsname> . set-farbe!)
(<gruppencode> . 62)
)
)
)
)
Wie man sieht, ist es gar nicht so schwer, Funktionen
automatisch zu generieren. Durch die Verwendung solcher
templates können wir uns in Zukunft einfach eine
Menge Code einsparen.