Wie man 2D-Zeichnungen auf einen Zylindermantel aufprägt (Software)
Die DWG-CAD-Programme BricsCAD und AutoCAD besitzen recht eindrucksvolle Modellierungsfähigkeiten. Reichen die eingebauten Befehle nicht aus, lassen sich in der Programmiersprache LISP neue Funktionen dazuschreiben. In den letzten 33 Jahren entstanden so allerorten tausende kleine und große CAD-Hilfswerkzeuge.
Leider ist die uralte Sprache LISP zwar ziemlich leistungsfähig, aber sie ist schon etwas speziell mit ihrer Syntax voller Klammerausdrücke. Ihre fremdartige polnische Notation trägt wohl auch mit dazu bei, dass die meisten CAD-Anwenderinnen und -anwender noch nie eine LISP-Routine selber geschrieben haben.
Ich fragte mich, ob es wohl möglich sei, auch mit einem Python-Programm CAD-Daten aus einer bestehenden Zeichnung zu lesen. Da es weder in AutoCAD noch in BricsCAD eine Programmierschnittstelle für Python gibt, müsste die Datenübergabe allein über Textdateien erfolgen.
Um Daten aus dem CAD-Programm heraus zu bekommen, hilft der Befehl DATENEXTRAKT, der eine CSV-Datei mit den Geometriedaten ausgewählter Zeichnungsobjekte erzeugt. Diese CSV-Datei kann von Python problemlos gelesen werden.
Der Rückweg ist etwas kniffliger, da Python ja wegen der fehlenden Programmierschnittstelle keine Möglichkeit hat, unmittelbar vorhandene Objekteigenschaften abzufragen. Alles, was Python ausgibt, muss praktisch „im Blindflug“ vom CAD-Programm ausgeführt werden. Zu diesem Zweck erzeugt das in diesem Beitrag vorgestellte Pythonprogramm eine SCR-Datei, in der alle Zeichenbefehle genau so stehen, wie sie ohne Maus über die Tastatur eingetippt werden müssten. Sinnvollerweise geschieht das in einer neuen, leeren Zeichnung.
Um beispielsweise das Haus vom Nikolaus bildschirmfüllend darzustellen, würde man folgendes eintippen:
LINIE 1,1 1,3 2,4 3,3 1,3 3,1 1,1 3,3 3,1 ↲
ZOOM G↲
Das Zeichen ↲ steht dabei für das Drücken der Eingabetaste auf der Tastatur. In Python wird das entsprechende Steuerzeichen durch die Ersatzdarstellung \n in die SCR-Datei eingefügt.
Damit unser Pythonprogramm nicht allzu trivial wird, bekommt es die Aufgabe, eine zweidimensionale Zeichnung auf einem Zylindermantel abzubilden. Die Beispielzeichnung unten besteht aus 12 geschlossenen Polylinien. Die Bögen sind als Splines konstruiert worden und verwandeln sich dadurch beim Verbinden mit dem Rest der Polylinie zu einer Kette kurzer Linienstücke. Kreisbogenstücke würden dagegen durch ein einzelnes Linienstück ersetzt.
Exportiert man die Geometrie aus BricsCAD mittels DATENEXTRAKT in eine CSV-Datei, so kann man auswählen, welche Objektarten und -eigenschaften exportiert werden sollen. Wir beschränken uns bei der Objektart auf „Polylinien-Scheitelpunkte“ und bei den Eigenschaften auf „Position X“, „Position Y“ und „Scheitelpunkt“. Das Häkchen vor „kombiniere gleiche Zeilen“ darf nicht gesetzt sein.
Die „extrahierte“ CSV-Datei besteht aus ein paar hundert Zeilen mit den Koordinaten aller Scheitelpunkte der ausgewählten Polylinien.
Anzahl;Name;Position X;Position Y;Scheitelpunkt
1;Polylinien Scheitelpunkt;8.527978;0.410224;1
1;Polylinien Scheitelpunkt;7.976107;0.410224;2
1;Polylinien Scheitelpunkt;7.976107;1.279974;3
1;Polylinien Scheitelpunkt;7.979059;1.328747;4
1;Polylinien Scheitelpunkt;7.987645;1.377615;5
1;Polylinien Scheitelpunkt;8.001463;1.426065;6
1;Polylinien Scheitelpunkt;8.020111;1.473588;7
Der Zähler in der letzten Spalte ist die Nummer des Scheitelpunkts in der jeweiligen Polylinie. Er beginnt bei jeder neuen Polylinie wieder bei Eins. Wir verwenden ihn, um herauszufinden, zu welcher Polylinie ein Scheitelpunkt gehört.
Unser Programm hat nun die Aufgabe, aus dieser CSV-Datei die X-Y-Koordinaten in 3D-Zylinderkoordinaten umzurechnen und die CAD-Befehle zu erzeugen, die diese Geometrie auf einen Zylindermantel abbilden. Diese werden in eine Datei geschrieben und mit dem Befehl SCRIPT in AutoCAD oder BricsCAD ausgeführt.
Das Ergebnis sieht in unserem Beispiel so aus:
Mit dem CAD-Befehl AUFPRÄGEN lassen sich auf dem Zylindermantel liegende Flächen erzeugen, die dann beispielsweise verstärkt oder zurückgesetzt werden können. Wenn wir unserem Zylinder eine Wandstärke zuweisen, sieht das Ergebnis der Modellierung so aus:
Damit die erzeugte Geometrie überhaupt korrekt aufgeprägt werden kann, muss sie exakt auf dem Zylindermantel liegen. Die Verbindungslinien zwischen den Scheitelpunkten sind daher stets Ellipsenbögen, die jeweils auf einer Ebene liegen, welche den Zylinder schneidet. Einzige Ausnahme sind senkrechte Linien, die weiterhin geradlinig bleiben.
Als Kuriosum ist zu beachten, dass weder AutoCAD noch BricsCAD Ellipsenbögen im Raum kennen. Um einen Ellipsenbogen zu zeichnen, muss zuerst eine Zeichenebene durch ein Benutzerkoordinatensystem definiert werden, auf der dann in einem zweiten Schritt ein Ellipsenbogen vom Startpunkt zum Endpunkt gezeichnet werden kann.
Das Programm kann auch dazu verwendet werden, Schrift auf Zylindermäntel aufzuprägen. Der Text muss lediglich vorher mit dem Befehl TXTAUFL in Polylinien umgewandelt werden. Je nach Font ist nach der Umwandlung ein Zwischenschritt notwendig, in dem möglicherweise vorhandene doppelte Umrisslinien gelöscht werden.
from math import sin, cos, pi, hypot from tkinter import Tk from tkinter.filedialog import askopenfilename import sys ## Version 5 vom 10. April 2019 print(""" Dieses Python-Programm erzeugt ein BricsCAD-Script, um geradlinige Teilstücke von 2D-Polylinien als Ellipsenbögen auf einem Zylindermantel abzubilden. ### Anleitung ### Schritt 1: Die später auf dem Zylindermantel anzuordnenden Elemente müssen als 2D-Polylinien in der x-y-Ebene vorliegen. Bei mittels TXTAUFL aus Texten erzeugten Objekten müssen ggf. doppelte Polylinien gelöscht werden. Schritt 2: In BricsCAD mittels DATENEXTRAKT eine CSV-Datei mit den Scheitel- punktdaten der gewünschten Polylinien erstellen. Es müssen mindestens die Eigenschaften "Position X", "Position Y" und "Scheitelpunkt" ausgewählt werden. Gleiche Elemente dürfen nicht zusammengefasst werden. Als Trennzeichen ist das Semikolon zu wählen. Schritt 3: Diese CSV-Datei laden. """) Tk().withdraw() csvname = askopenfilename(title = "CSV-Datei wählen:", filetypes = (("CSV-Dateien","*.csv"), ("Alle Dateien","*.*"))) if not csvname: print("Keine CSV-Datei ausgewählt. Programm wird abgebrochen.") sys.exit() with open(csvname) as csv: Zeilen = [Z.strip() for Z in csv] # Kopfzeile der CSV-Datei Z = Zeilen[0].split(";") # Führt diese alle benötigten Spalten auf? try: xSpalte = Z.index("Position X") ySpalte = Z.index("Position Y") sSpalte = Z.index("Scheitelpunkt") except ValueError: print(""" CSV-Formatfehler! In der Kopfzeile der CSV-Datei müssen die Spaltenüberschriften "Position X", "Position Y" und "Scheitelpunkt" aufgeführt sein. Programm wird abgebrochen. """) sys.exit() # Liste aller Polylinien in der CSV-Datei PL = [] # Grenzen des erkannten Bereichs xmin = ymin = float("inf") xmax = ymax = float("-inf") # Alle Zeilen der CSV-Datei auswerten for Zeile in Zeilen[1:]: zs = Zeile.split(";") if "Scheitelpunkt;" in Zeile: # Nummer des Scheitelpunkts sp = zs[sSpalte] # Beginn einer neuen Polylinie? if sp == "1": PL.append([]) # x- und y-Ordinaten holen x = float(zs[xSpalte]) y = float(zs[ySpalte]) # Grenzen ggf. erweitern xmin = min(xmin, x) ymin = min(ymin, y) xmax = max(xmax, x) ymax = max(ymax, y) # Scheitelpunkt an zuletzt begonnene Polylinie anhängen, # doppelte Punkte überspringen if not PL[-1] or (x,y) != PL[-1][-1]: PL[-1].append((x,y)) print(len(PL),"Polylinien gefunden.\n") print(f"x-Bereich {xmin:.2f} bis {xmax:.2f}") print(f"y-Bereich {ymin:.2f} bis {ymax:.2f}") breite = xmax - xmin höhe = ymax - ymin # Radius für 360° Umwicklung rmin = breite/pi/2 print(f"\nKleinster sinnvoller Radius: {rmin:.2f}") r = float(input("Gewählter Radius r = ")) # Zylinderumfang u = 2*pi*r def zk(r,x,y): """Berechnet Zylinderkoordinaten""" z = y-ymin w = 2*pi*(x-xmin)/u x = r*cos(w) y = r*sin(w) return x, y, z # Die Scriptdatei heißt wie die CSV-Datei scrname = csvname[:-4]+".scr" print(f"\nSchreibe Skriptdatei „{scrname}“ …") with open(scrname,"w") as scr: # Aus Geschwindigkeitsgründen: visueller Stil "2D-Drahtmodell" scr.write("-VIS A 2D\n") # Definierende Geometrie immer löschen scr.write("DELOBJ 2\n") # Schrägansicht scr.write("APUNKT 1,1,1\n") # Zoom auf interessanten Bereich scr.write(f"ZOOM F {-r},{-r} {r},{r}\n") # Für alle Polylinien in der Liste: for P in PL: # Erster und letzter Punkt müssen übereinstimmen if P[0] != P[-1]: P.append(P[0]) # Für alle Teilstücke in der Polylinie: for i in range(len(P)-1): # Die beiden Endpunkte auf den Zylindermantel projizieren x1z, y1z ,z1z = zk(r, P[i][0], P[i][1]) x2z, y2z ,z2z = zk(r, P[i+1][0], P[i+1][1]) # Mittlerer Punkt der Linie auf dem Zylindermantel xmz, ymz, zmz = zk(r, (P[i][0]+P[i+1][0])/2, (P[i][1]+P[i+1][1])/2) # Sehnenlänge im Grundriss dx = x2z-x1z dy = y2z-y1z dh = hypot(dx, dy) # Höhenunterschied zwischen Start- und Endpunkt dz = z2z-z1z # Sehnenlänge im Raum ds = hypot(dh, dz) # Um Nulldivisionen zu vermeiden, müssen Linien # parallel zur Zylinderachse gesondert betrachtet werden: if not dh: # Senkrechte Linie scr.write("LINIE " f"{x1z},{y1z},{z1z} " f"{x2z},{y2z},{z2z} \n") else: # Ellipsenbogen # Radius der langen Halbachse rl = -ds/dh * r # Radius bis zur Sehnenmitte rs = hypot((x1z+x2z)/2,(y1z+y2z)/2) # Ellipsenbögen können nur # im ebenen BKS gezeichnet werden. scr.write(f"BKS 0,0,{zmz} " f"{xmz},{ymz},{zmz} " f"{x2z},{y2z},{z2z}\n") # Wir sammeln alle Punkte ein scr.write("ELLIPSE B " f"0,{-rl} " f"0,{rl} " f"{r},0 " f"{rs},{-ds/2} " f"{rs},{ds/2}\n" "BKS W\n") # Alle Ellipsen zu "Splines" verbinden scr.write("VERBINDEN alle \n") # Zum Schluss noch den dazugehörigen Zylinder bauen … scr.write(f"ZYLINDER 0,0,{-höhe/4} {r} {höhe*1.5}\n") # … und alle Splines auf den Zylindermantel aufprägen scr.write("AUFPRÄG L ") for P in PL: scr.write("alle J ") scr.write("\n") print(""" Schritt 4: In BricsCAD in einer leeren Zeichnung mittels SCRIPT die gerade erzeugte Scriptdatei ausführen lassen. Die Umrisse werden vom Skript mittels AUFPRÄGEN auf die Zylinderwand aufgeprägt. Die dadurch erzeugten Teilflächen der Zylinderwand können mittels DMVERSTÄRKEN erhaben oder vertieft ausmodelliert werden. """) input("[Enter]")
--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums
Bücher:
CAD mit BricsCAD
Bauinformatik mit Python