Wir haben
(entmake ...) ja bereits kennengelernt, allerdings
nur an einem kurzen Beispiel. In diesem Kapitel werden wir uns etwas
näher mit dieser Funktion befassen. Zunächst jedoch eine grundsätzliche
Überlegung vorweg:
Über
(command ...) und
(entmake ...) haben wir zwei
sehr verschiedene Wege, wie wir Geometrie-Elemente erzeugen können.
Jeder dieser Wege hat seine Stärken und Schwächen. Die Vorteile des
(command)-Verfahrens liegen in seiner Einfachheit. Fast jeder
AutoLisp-Programmierer ist ja auch mit der interaktiven Arbeit mit
AutoCAD vertraut und kennt die AutoCAD-Befehle gut, wobei dieses
Wissen natürlich ausgeprägter ist, wenn man mit AutoCAD eher an der
Kommandozeile orientiert arbeitet.
Das Erzeugen von Geometrie mit
(command) ist also oft nichts
weiter als ein schriftliches Fixieren der gewohnten Abläufe. Mit
(entmake) Geometrie zu erzeugen ist etwas komplexer und dadurch
manchmal auch fehleranfälliger. Dafür hat
(entmake) aber den
Geschwindigkeitsvorteil klar auf seiner Seite. Wenn es um das
Generieren von sehr viel Geometrieelementen und um Geschwindigkeit
geht, sollte man also
(entmake) eindeutig vorziehen.
Gar nicht teilen kann ich aber die öfters vertretene Ansicht, dass
man
(command ...) lieber überhaupt nicht verwenden sollte.
Dafür sehe ich keinen Grund - im Gegenteil: Wenn in einem Programm
mal ein Layer erzeugt werden soll oder etwas in der Art, dann spricht
alles für
(command). Der Nachteil in der Geschwindigkeit
macht sich bei ein oder zwei Aufrufen überhaupt nicht bemerkbar.
Den bekommen wir erst zu spüren, wenn wir z.B. ein Lochblech zeichnen.
Es gibt viele Fälle, in denen ein Geometrie-Element mit einem kurzen
(command)-Aufruf schnell erzeugt ist, wo aber der Weg über
(entmake) statt einer Zeile auch mal eine Bildschirmseite
füllt. Dazu ein kleines Beispiel - Wir wollen nichts weiter erzeugen
als einen Kreis, das Problem ist nur, dass wir nicht den Mittelpunkt
des Kreises sowie den Radius haben, sondern nur 3 Punkte, die auf
dem Kreis liegen. Mit command sieht das so aus:
(command "_circle" "_3p" p1 p2 p3)
Das war's auch schon - mehr Code brauchen wir nicht. Aber wie können
wir das gleiche Resultat mit
(entmake) erzielen? Es werden
Mittelpunkt und Radius erwartet, weil AutoCAD Kreise nun mal in
diesem Format speichert - unabhängig davon, wie sie erzeugt wurden.
Das bedeutet, dass wir erst einmal aus den 3 Punkten den Mittelpunkt
und den Radius berechnen müssen. Und wie machen wir das? Wir sind
darauf angewiesen, dafür eine Hilfsfunktion zu definieren, da wir
bei Verzeicht auf
(command) auch auf manchen Teil des in
AutoCAD eingebauten Rechenkomforts verzichten müssen.
Die hinter der Hilfsfunktion stehende Überlegung ist folgende: Wir
denken uns zwei Geraden zwischen jeweils 2 der drei auf dem Umfang
liegenden Punkte und errichten jeweils auf dem Mittelpunkt dieser
Geraden eine Mittelsenkrechte. Da, wo diese Mittelsenkrechten sich
schneiden, liegt der Kreismittelpunkt. Um den Schnittpunkt zu berechnen,
benutzen wir die Funktion
(inters), mit der wir uns aber ein
neues Problem einhandeln: Diese Funktion hat schlicht und einfach
einen Fehler, den AutoDesk wohl nicht mehr zu beheben gedenkt.
(inters) bekommt als Argumente vier Punkte, wobei die beiden
ersten die erste Gerade definieren, die beiden letzten Punkte stellen
die andere Gerade dar. Dann erwartet
(inters) als fünftes
Argument noch ein
nil oder
T, mit dem festgelegt wird,
ob auch ein 'virtueller' Schnittpunkt als Ergebnis kommen darf, oder
ob gewünscht ist, dass der Schnittpunkt nicht auf der gedachten
Verlängerung liegen darf. Anders als bei allen anderen Lisp-Funktionen
reicht es aber nicht, wie z.B. bei
(strcase str) bzw.
(strcase str 'T), dass ein
nil nur de facto
vorliegt, d.h. dass das Argument nicht übergeben wird - nein, bei
(inters) muss eine leere Liste explizit übergeben werden.
Unterlässt man dies, gibt
(inters) immer
nil zurück.
Dann wird auch erstmals
(polar) verwendet, eine Funktion mit
drei Argumenten: einem Punkt/Vektor, einem Winkel und einem Abstand.
Berechnet wird ein Vektor bestehend aus Abstand und Winkel, und
gegenüber dem Zeichnungsursprung um das erste Argument verschoben.
(polar) hat zwar keinen Bug wie
(inters), es ist
aber gewöhnungsbedürftig, dass hier erst der Winkel und dann der
Abstand angegeben werden müssen - das widerspricht allen Gewohnheiten
und auch der Reihenfolge bei Polarkoordinaten in AutoCAD.
Nun also der Code der Hilfsfunktion:
(defun center-3p(p1 p2 p3 / )
(apply'inters
(append
(apply'append
(mapcar
'(lambda(a b / tmp)
(list
(setq tmp
(mapcar
'(lambda(c / )(/ c 2.0))
(mapcar'+ a b)
)
)
(polar tmp(+(angle a b)(/ pi 2))1)
)
)
(list p1 p2)
(list p2 p3)
)
)
(list nil)
)
)
)
Zugegeben, diese Funktion ist (für ein Einsteiger-Tutorial) nicht
ganz einfach nachzuvollziehen. Ein paar Hinweise, wie immer von
innen nach aussen zu sehen: Der innerste
(mapcar'+..)-Ausdruck
addiert die beiden Punkte, das darumliegende
(mapcar'(lambda...))
berechnet den Mittelpunkt der Strecke P1P2 (im zweiten Durchgang P2P3).
Der Mittelpunkt der Strecke wird in
tmp zwischengespeichert, damit
er für
(polar...) wiederverwendet werden kann. Der
(polar)-Aufruf berechnet den Einheits-Normalvektor zu der
jeweiligen Strecke (daher die Länge 1, es kann aber auch jede andere
Zahl > 0 eingesetzt werden, ohne dass sich am Ergebnis etwas ändert).
Der Ausdruck
(mapcar'(lambda(a b ...))) schliesslich wendet
die Berechnung einmal auf P1P2 und dann auf P2P3 an. Mit
(apply'append...) wird das Ergebnis zu einer linearen Liste
geglättet, an deren Ende noch das unselige
nil geklebt werden
muss, um dem Fehler in
(inters) Genüge zu tun. Das Ganze wird
zum Schluss mit
(apply'inters...) an
(inters) zur
Berechnung des Schnittpunktes übergeben.
Bevor wir nun aber wieder zu
(entmake) zurückkehren können,
müssen wir uns noch mit dem möglichen Fall befassen, dass die drei
Punkte auf einer Geraden liegen. Dieser Fall muss abgefangen werden,
und zwar unabhängig davon, ob wir den Kreis mit
(entmake)
oder
(command) erzeugen wollen! Liegen die drei Punkte auf
einer geraden, ist ein Kreis nicht möglich - ein
(command)-Aufruf
würde zu einem Programmabbruch führen, da der Befehl 'Kreis' nicht
erwartungsgemäss ausgeführt wird. Und auch mit
(entmake)
bleiben wir stecken, da unsere Hilfsfunktion
nil zurückgibt:
(center-3p '(0 0) '(1 0) '(3 1))
=> (0.5 3.5)
(center-3p '(0 0) '(1 0) '(3 0))
=> nil
Diese Erkenntnis relativiert aber auch wieder unsere Annahmen, was
die Kreiserzeugung mit
(command) angeht: So einfach, wie oben
angegeben, dürfen wir die Code-Zeile
(command "_circle" "_3p" p1 p2 p3)
in einem Programm überhaupt nicht verwenden! Wir können nicht sicher
sein, ob AutoCAD den Befehl ausführt oder mit *Ungültig* abbricht.
Da wir uns nun schon ausgiebig mit geometrischen Berechnungen befasst
haben, schlage ich hier zur Abwechslung einen anderen Ansatz vor:
Wir speichern vor dem
(command)-Aufruf das letzte Element
der Datenbank und überprüfen hinterher, ob
(entlast) einen
anderen ENAME zurückgibt. Nur dann ist sicher, dass ein Kreis erzeugt
wurde.
Wir stellen jetzt noch einmal gegenüber: Mit
(command) ist also
der folgende Code notwendig, um einen Kreis in der besprochenen
Drei-Punkte-Form zu erzeugen:
...
(setq lastent(entlast))
(command "_circle" "_3p" p1 p2 p3)
(if(= lastent(entlast))
... ; Hier muss der aufgetretene
... ; Fehler behandelt werden!
)
...
Ein mit
(entmake) erzeugter Kreis würde so aussehen
(dass die Hilfsfunktion geladen sein muss, ist klar!):
...
(if(setq mp(center-3p p1 p2 p3))
(entmake
(list
'(0 . "Circle")
(cons 10 mp)
(cons 40(distance mp p1))
)
)
(progn
... ; Hier muss der aufgetretene
... ; Fehler behandelt werden!
)
)
...
Im Rahmen unseres Vergleiches zwischen Geometrie-Erzeugung
mit
(command) und
(entmake) muss aber noch ein
weiterer Aspekt erörtert werden: das Zeitverhalten zwischen
den beiden Methoden ist unterschiedlich, wie sehr, das bleibt
noch zu klären.
(command) ist im Vergleich zu
(entmake) langsamer, das wurde eingangs bereits kurz
angesprochen. Das letzte Beispiel in diesem Kapitel demonstriert
(wieder in zwei Versionen) die Zeitunterschiede.
Wir legen zunächst ein paar Punkte für ein Gebilde fest, das
wir mit Linien zeichnen. Den Zeichenvorgang wiederholen wir
ingesamt 10000 mal (im Raster von 100 x 100). Die Zeit kann
über eine Uhr abgestoppt werden. Wer mehr über das Abstoppen
von Funktions-Laufzeiten wissen möchte, kann auf den Seiten
für Fortgeschrittene unter
(elapsed...)
mehr erfahren. Bevor Sie den Beispiel-Code ausführen, sollten
Sie die Systemvariable CMDECHO auf den Wert 0 setzen!
(setvar "cmdecho" 0)
(defun timetest-command( / steps pl ix iy ofs)
(setq steps 100)
(setq ofs'(2 2))
(setq pl
'( (0 0)(1 0)(1 1)(0.5 1.5)
(0 1)(0 0)(1 1)(0 1)(1 0)
)
)
(setq px '(0 0) py '(0 0))
(setq ix 0 iy 0)
(repeat steps
(repeat steps
(command "_line")
(mapcar 'command
(mapcar
'(lambda(p)
(list
(+(car p)(* ix(car ofs)))
(+(cadr p)(* iy(cadr ofs)))
)
)
pl
)
)
(command "")
(setq ix(1+ ix))
)
(setq iy(1+ iy)ix 0)
)
)
(defun timetest-entmake( / steps pl ix iy ofs tmp)
(setq steps 100)
(setq ofs'(2 2))
(setq pl
'( (0 0)(1 0)(1 1)(0.5 1.5)
(0 1)(0 0)(1 1)(0 1)(1 0)
)
)
(setq px '(0 0) py '(0 0))
(setq ix 0 iy 0)
(repeat steps
(repeat steps
(setq tmp
(mapcar
'(lambda(p)
(list
(+(car p)(* ix(car ofs)))
(+(cadr p)(* iy(cadr ofs)))
)
)
pl
)
)
(repeat(1-(length pl))
(entmake
(list
'(0 . "LINE")
(cons 10(car tmp))
(cons 11(car(setq tmp(cdr tmp))))
)
)
)
(setq ix(1+ ix))
)
(setq iy(1+ iy)ix 0)
)
)
Was bringt der Vergeich nun wirklich ans Tageslicht? Ein Unterschied
wird beim Aufruf der Funktionen sichtbar: Bei der
(entmake)-Version
bleibt der Bildschirm schwarz, solange die Funktion läuft - bei der
(command)-Version wird jede Linie sofort auf dem Bildschirm
wiedergegeben. Das kostet einfach Rechenzeit! Und der Vergleich in Zahlen
sagt uns, dass zwischen 1 min 38 sek (
(entmake)-Methode) und 3 min 58 sek
(
(command)-Methode) zwar ein Unterschied besteht - aber es liegen keine
Welten dazwischen - immerhin werden da 80000 Entities erzeugt, und der
Unterschied ist kleiner als Faktor 3!
Wirkliche Unterschiede entstehen da, wo Rechenzeiten im Verhältnis zur
Datenmenge mehr als linear ansteigen, z.B. wenn große Datenlisten mit
(append liste(list datum)) erweitert werden statt
mit
(cons datum liste). Hier können Faktoren entstehen
von 1:100 oder 1:1000 oder sogar 1:10000, je nach Datenvolumen. Ach ja -
die Zahlen wurden gemessen auf einem betagten AMD K6-2/450 mit 320 MB
Arbeitsspeicher - auf einer neuen 2GHz-CAD-Workstation dürften sie
wesentlich kürzer sein. Den wesentlichen Unterschied sollte man allerdings
nie vergessen: Ein schlechter gedanklicher Ansatz für eine zu lösende
Aufgabe schafft mehr Unterschiede im Zeitverhalten als der tatsächlich
bestehende Unterschied zwischen einem alten 386er und einem Pentium IV,
und so besehen gibt es auch im Zeitverhalten zwischen den beiden Methoden
keinen Unterschied.