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 ActiveX-Schnittstelle bietet für (fast) jedes geometrische Objekt, das in der Geometriedatenbank von AutoCAD vorkommen kann, die Methode IntersectWith an, mit deren Hilfe Schnittpunkte mit einem zweiten Geometrie-Objekt berechnet werden können. Ausnahmen sind hier nur das Papierbereichs-Ansichtsfenster PViewPort und das Polygon-Mesh, für diese beiden Entities existiert die Methode nicht.

In diesem Kapitel geht es darum, eine (recht kleine) Funktion für den Allgemeingebrauch in AutoLisp zu schaffen, die sich diese ActiveX-Methode zunutze macht und die Schnittpunkt zwischen beliebigen Entities berechnet, z.B. zwischen zwei Kreisen oder zwischen einem Bogen und einer Linie. Aber auch das mühsame Berechnen der Schnittpunkte zweier Polylinien oder Splines nimmt sie uns ab.

Alle verwendeten ActiveX-Aufrufe (sprich: vlax-...) werden innerhalb der Funktion gekapselt. Nach Aussen hin verhält sich die Funktion also wie eine 'ganz normale' Lisp-Funktion, und um sie zu verwenden, muss man eigentlich auch keine Ahnung von ActiveX haben - aber trotzdem wird es nichtschaden, wenn man auch versteht, was da vor sich geht.

ActiveX und AutoLisp sind von den Datentypen her völlig inkompatibel. Daher müssen die AutoLisp-Entitynamen zunächst einmal in den Datentyp ActiveX-Object konvertiert werden. Die Funktion (vlax-ename->vla-object <ename>) erledigt das. Dann haben wir zwei Möglichkeiten, die sich geringfügig in der Schreibweise unterscheiden: Wir können mit (vlax-invoke-method ...) die Methode auf die beiden Objekte anwenden, oder wir benutzen die Kurzschreibweise (vla-IntersectWith ...). Das Problem bei der kurzen Schreibweise ist, dass nirgendwo in der AutoLisp-Hilfe dokumentiert ist, welche Funktionen da überhaupt existieren. Es sind zwar weit über 1000 solcher Kurzfunktionen vorhanden, aber es existiert nicht einmal eine Aufstellung darüber.

Da der Schreibaufwand mit (vlax-invoke-method) eigentlich auch nur ein paar Buchstaben mehr beträgt, bleiben wir also lieber bei dieser Version. Mit dieser Funktion kann jede definierte Methode eines Objekts aufgerufen werden. Falls nicht klar sein sollte, was eine Methode überhaupt ist, hier eine ganz kurze Erklärung: In AutoLisp kennen wir (bisher) nur Variablen und Funktionen, die nicht an ein bestimmtes Objekt gebunden sind, sondern 'frei im Raum stehen' und auch aus jedem Kontext heraus abrufbar sind. In der objektorientierten Programmierung nennt man die zu einem Objekt gehörenden Funktionen Methoden, und die zu einem Objekt gehörenden Variablen nennt man Properties.

Ein kurzes gedankliches Beispiel dazu: Der Umfang eines Kreises muss sicherlich anders berechnet werden als der Umfang eines Rechtecks. In klassischem Autolisp würden wir uns dafür zwei verschiedene Funktionen definieren:
(defun kreisumfang(...))
(defun rechteckumfang(...))
                  
Wir müssten dann bei der Anwendung der Funktionen auch darauf achten, dass es nichtzu Verwechslungen kommt. In den objektorientierten Sprachen geht man einen anderen Weg. Natürlich gibt es auch hier zwei verschiedene Funktionen, aber beide haben den Namen umfang. Die eine wird den Kreis-Objekten zugeordnet, die andere den Rechteck-Objekten. In den meisten Sprachen ist der Punkt der Operator für die Zuordnung: KreisObjekt.Umfang() bzw. RechteckObjekt.Umfang() wären also die adäquaten Schreibweisen (die Klammern hinter der Methode sind in C++, Java usw. ein Zeichen dafür, dass es hier um eine Methode und nicht um Properties geht. Kreis.Radius würde also ohne Klammern geschrieben, da es hier um eine gespeicherte Variable geht, nicht um eine Methode, die etwas berechnet).

In AutoLisp/VisualLisp ist aber der Punkt für die 'dotted pairs' zuständig, und ausserdem würde ein solcher Operator das ganze Konzept von Lisp durcheinander bringen - Lisp ist nun mal per se keine objektorientierte Sprache, und die Lisp-Syntax weicht von Sprachen wie C++ oder Java sowieso gewaltig ab. Wir müssen in VisualLisp also damit leben, dass es keinen solchen Operator gibt - die Zusammenhänge müssen auch ohne Operator deutlich gemacht werden.

Statt (ein-entity.IntersectWith anderes.entity) müssen wir also zu (vlax-invoke-method ...) greifen. Der Name der Methode wird als Symbol übergeben, und derganze Aufruf sieht nun so aus:
(vlax-invoke-method
  (vlax-ename->vla-object ent1)
  'IntersectWith
  (vlax-ename->vla-object ent2)
  acExtendNone
)
                  
Die vordefinierte Konstante acExtendNone hat den Wert 0 und zeigt an, dass Schnittpunkte auf 'gedachten' Verlängerungen nicht gewünscht sind. Wenn es um geschlossene Entities wie Kreise geht, spielt diese Konstante natürlich keine Rolle.

Schön und gut, das war bisher wirklich nicht aufregend - aber verwerten können wir das Ergebnis leider noch nicht: Die Rückgabe ist ein Array aus Doubles - keine Liste! An den Datentyp 'Array' müssen wir uns nun herantasten, wobei nebenher auch noch zu prüfen ist, ob das Array etwa leer ist, da keine Schnittpunkte vorhanden sind. Ein nil können wir von einer Lisp-Funktion erwarten, nicht aber von ActiveX!

Um an das Array selbst heranzukommen, müssen wir noch einen Aufruf von (vlax-variant-value ...) drumherum legen:
(vlax-variant-value
  (vlax-invoke-method
    (vlax-ename->vla-object ent1)
    'IntersectWith
    (vlax-ename->vla-object ent2)
    acExtendNone
  )
)
                  
Für den Umgang mit solchen Arrays stellt uns VisualLisp den neuen Datentyp 'safearray' zur Verfügung - der Name enthält das Wort 'safe', weil diese Arrays gegen den Zugriff über die Dimensionen des Arrays hinaus abgesichert sind. Abstürze sind also bei falschen Zugriffen genauso wenig zu erwarten wie bei Zugriffen hinter das Ende einer Lisp-Liste. Ein solches 'safearray' kann mit (vlax-safearray->list) in eine Lisp-Liste umgewandelt werden.
(vlax-safearray->list
  (vlax-variant-value
    (vlax-invoke-method
      (vlax-ename->vla-object ent1)
      'IntersectWith
      (vlax-ename->vla-object ent2)
      acExtendNone
    )
  )
)
                  
Noch haben wir das Problem mit dem leeren Array nicht gelöst, und schon haben wir ein neues Problem am Hals: Die Umwandlung des Arrays in eine Liste funktioniert zwar irgendwie, gibt uns aber eine lineare Liste in der Form (x1 y1 z1 x2 y2 z2 x3 y3 ...) zurück. Die aus jeweils 3 Koordinaten bestehenden Punkte müssen wir uns also noch 'zusammenbasteln'. Dazu eine kleine Hilfsfunktion:
(defun gather(ls len / tmp rl i)
  (setq i 0)
  (foreach item ls
    (setq tmp(append tmp(list item)))
    (setq i(1+ i))
    (if(zerop(rem i len))
      (setq rl(cons tmp rl) tmp nil)
    )
  )
  (reverse rl)
)

(gather '(x1 y1 z1 x2 y2 z2 x3 y3 z3 ...))
  => ((x1 y1 z1)(x2 y2 z2)(x3 y3 z3) ...)
                  
Und um zu prüfen, ob wir ein leeres Array zurückbekommen haben, gibt es die Funktion (vlax-safearray-get-u-bound ...). Das 'u' steht hier für 'upper', also das Ende des Arrays. Wenn das Array leer ist, gibt uns diese Funktion den Wert -1 (Basic lässt grüssen!) zurück. Da die Arrays bis zu 16-dimensional sein können, muss noch eine 1 für die Dimension mitgegeben werden (keine Zählung ab 0 wie in den meisten anderen Sprachen, auch hier lässt Basic grüssen).

Damit haben wir nun alles zusammen, was wir für unsere Funktion benötigen: Hier die vollständige Implementation (natürlich muss die Hilfsfunktion (gather...) auch geladen sein. Der unvermeidliche Aufruf von (vl-load-com) muss übrigens sein, damit alle ActiveX-Funktionen und Konstanten geladen sind und zur Verfügung stehen.
(defun intersect(ent1 ent2 / ar)
  (vl-load-com)
  (setq ar
    (vlax-invoke-method
      (vlax-ename->vla-object ent1)
     'IntersectWith
      (vlax-ename->vla-object ent2)
      acExtendNone
    )
  )
  (if
    (/= -1
      (vlax-safearray-get-u-bound
        (vlax-variant-value ar)
        1
      )
    )
    (gather
      (vlax-safearray->list
        (vlax-variant-value ar)
      )
      3
    )
  )
)
                  
Und nun noch eine Testfunktion, mit der man zwei Objekte am Bildschirm wählen kann. Die Schnittpunkte werden als Punkte eingezeichnet - man sollte die Systemvariable PDMODE auf einen entsprechenden Wert setzen, damit das Resultat auch sichtbar wird. In dieser Testfunktion ist nichts mehr von ActiveX und (vlax-...) zu sehen. Wir können die neue Funktion benutzen wie das altbekannte (inters ...), nur kann sie ungleich mehr!
(defun c:intersect( / )
  (vl-load-com)
  (foreach p
    (intersect
      (car(entsel "\nErstes Objekt wählen: "))
      (car(entsel "\nZweites Objekt wählen: "))
    )
    (command"_point"p)
  )
)