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!

Berechnen von arithmetischen Ausdrücken in der Kommandozeile Sitz!Platz!Fass!
Das Verschachteln von Ausdrücken Alte Schachtel!
Das Speichern von Werten in Variablen Gebunkert
Verhindern der Evaluation mit Quote Bergbäche
Erzeugen von einfachen Listen in AutoLisp Brot,Eier,Käse
Einfache Funktionen zur Listenbearbeitung ...um die Wurst
Funktionen für den Zugriff auf Listenelemente Was ein Salat!
Über Haupt- und Nebeneffekte von Funktionen Schwer schuften
Das Definieren von eigenen Funktionen in AutoLisp Ostfriesischer...
Lokale Variablen und Funktionsargumente in AutoLisp Kondome!
Das Laden von Programmdateien in AutoLisp Banküberfall
Verzweigung in Funktionen aufgrund von Entscheidungen Wenn das Wort...
Zusammenfassen von Entscheidungen mit den Logik-Funktionen Ins Schweinderl
Mehrfach-Verzweigungen in AutoLisp mit Cond 3. Strasse links
Schleifen zum Steuern des Ablaufs in AutoLisp-Funktionen Wie im Fernsehen
Testfunktionen zum Steuern von Schleifen in AutoLisp Schwanger?
Gleichheitstests als Schleifenkriterium in AutoLisp Noch gleicher?
Zeichneketten-Bearbeitung in AutoLisp Rauchzeichen
Funktionen zur Konvertierung von Datentypen in AutoLisp Wasser zu Wein
Komplexere Funktionen für die Bearbeitung von Listen in AutoLisp Nicht arbeiten...
Das Erzeugen von anonymen Funktionen mit lambda Schwarze Kutte
Das Bearbeiten von Listenelementen mit foreach Jedem das Seine
Erzeugen und Verwenden von Assoziationslisten in AutoLisp Beim Psychiater
Zugriff auf Geometriedaten und Erzeugen von Geometrieelementen Ententanz
Der Umgang mit Auswahlsätzen in AutoLisp Jung gefreit, ...
Auswahl von AutoCAD-Zeichnungsgeometrie mit ssget Raffgierig!
Verändern von Zeichnungs-Geometrie mit entmod Flickschusterei
Das Erzeugen von Geometrie mit entmake Houdini
Über Programmierstile in AutoLisp, Teil 1 Emma
Über Programmierstile in AutoLisp, Teil 2 Sti(e)lblüten
Über Programmierstile in AutoLisp, Teil 3 Eingewickelt
Über Programmierstile in AutoLisp, Teil 4 Doofe Frisur?


Zum den Seiten für Fortgeschrittene

Zu den ActiveX-Seiten

Meine Private HP mit Fotos, Gedichten, Musik und Postkartenversand

Mein Online-Lexikon der Fotografie

Mein völlig abgedrehtes Reisebüro










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.