3D-Modell jedes beliebigen Grundstücks in NRW (Software)

Martin Vogel ⌂ @, Dortmund / Bochum, Donnerstag, 16.03.2017, 15:14 (vor 586 Tagen)

Die Qualität der seit Januar 2017 frei verfügbaren Geländehöhendaten für NRW kann einem den Atem stocken lassen. Hier werfen wir einen Blick auf das Gelände der in einen ehemaligen Steinbruch an der Volme hineingebauten Stadthalle Hagen. Visualisiert wurden die aufbereiteten Geländedaten mit dem kostenlosen CAD-Programm FreeCAD. Die horizontale Auflösung beträgt einen Meter, die vertikale Auflösung der Höhendaten beträgt sogar nur einen Zentimeter:

[image]
Datenquelle: https://www.opengeodata.nrw.de/produkte/geobasis/dgm/dgm1/ ; Land NRW (2017) ; Datenlizenz Deutschland - Namensnennung - Version 2.0 (http://www.govdata.de/dl-de/by-2-0)

Auf den Servern des Opengeodata-Projektes gibt es sogar noch höher aufgelöste Daten, diese sind aber etwas unhandlicher zu verarbeiten, da sie nicht in einem festen Raster, sondern als ungeordnete Punktewolke mit etwa 4 Punkten pro Quadratmeter vorliegen.

Es ist nun für jedermann ganz einfach, sich ein 3D-Modell seines Grundstücks oder eines beliebigen anderen Geländeausschnittes selber zu erstellen. Sämtliche benötigte Software dazu ist kostenlos verfügbar. Bis zur 3D-druckfähigen Geländemodelldatei sind es nur wenige Schritte:

1. Von der Webseite https://www.opengeodata.nrw.de/produkte/geobasis/dgm/dgm1/ die Dateien für die gewünschten Ortschaften herunterladen.
Die ZIP-Dateien sind im Schnitt ein paar hundert Megabyte groß.

2. Diese ZIP-Dateien in einen beliebigen Ordner entpacken.
Es werden jeweils dutzende XYZ-Dateien angelegt, jede ist 124 MB groß und enthält 4 Millionen Höhenwerte einer 2000x2000 m² großen Geländekachel.

Wer in die XYZ-Dateien hineinschaut, stellt fest, dass die Koordinaten in einem für Nichtvermesser recht ungewöhnlichen Format vorliegen. Dieses Datenformat mit dem etwas sperrigen Namen „EPSG:25832 ETRS89/UTM 32.Zone“ lässt sich aber in gewöhnliche GPS-Koordinaten mit Längen- und Breitengrad umrechnen. Ebenso können bekannte Geokoordinaten in das EPSG-Format konvertiert werden.

3. Zur Umrechnung verwenden wir ein selbstgeschriebenes Python-3-Programm, welches das Python-Modul "utm" von Tobias Bieniek verwendet. Laden Sie die folgende ZIP-Datei herunter, entpacken Sie sie und starten Sie das Programm "Gelaendemodell.py".

[Update: Das Programm wurde aktualisiert, siehe Antwort auf diesen Beitrag!]

Falls Sie Python 3 noch nicht installiert haben, finden Sie es auf https://www.python.org/downloads/ – im Moment ist Version 3.6.0 aktuell.

4. Das Programm führt Sie in einem Dialog schrittweise zum gewünschten Ergebnis. Sie benötigen lediglich noch die Koordinaten von zwei Punkten, mit denen Sie ein Rechteck definieren, für welches unser Python-Programm das Geländemodell erzeugen soll. Diese Koordinaten können Sie direkt aus Google Maps oder einem anderen Kartendienst kopieren oder abschreiben. Für die Stadthalle Hagen oben waren das zum Beispiel die Punkte „51.350383, 7.482606“ und „51.356366, 7.491436“. Welche Eckpunkte des Rechtecks Sie aus der Karte abgreifen, ist egal, solange diese nur diagonal gegenüberliegen und sich innerhalb von Nordrheinwestfalen befinden.

5. Nach Angabe der gewünschten Auflösung legt das Programm drei Dateien neu an: eine XYZ-Datei enthält einfach nur eine Liste aller gefundenen Höhendaten des ausgewählten Bereichs, eine SCR-Datei enthält Befehle für die CAD-Programme AutoCAD und BricsCAD zur Erstellung eines Volumenkörpers, über den Sie sehr simpel Massenberechnungen zu Aushub und Auftrag anstellen können und schließlich eine Stereolithographiedatei im STL-Format, aus der Sie mit einem 3D-Drucker ein greifbares Modell erschaffen können. Die Schrittweite für die Auflösung sollte anfangs auf einen recht hohen Wert eingestellt werden, um die erzeugten Dateien klein und damit schnell bearbeitbar zu halten. Ein 10-Meter-Raster oder sogar ein 100-Meter-Raster zu verwenden, ist meistens gar keine schlechte Idee. Mehr als vielleicht 1000 Punkte pro Grundstück werden oft gar nicht benötigt.

Zum Ansehen und Weiterbearbeiten der STL-Datei empfiehlt sich das kostenlose Programm FreeCAD, in dessen erstaunlich mächtigen Funktionsumfang man sich allerdings erst einarbeiten muss. Kurzanleitung: STL-Datei öffnen, Inhalt mit dem Lupen-Icon bildfüllend darstellen, Verschieben der Ansicht mit der mittleren Maustaste, Drehen mit zusätzlichem Drücken der linken Maustaste (Bedienungsanleitung).

Viel Spaß damit![image]

--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums

Heute schon programmiert? Einführung in Python 3 (PDF)

Tags:
GIS, NRW, DGM, Geländemodell, Geodaten, Python 3

Das Einhornkänguruh im Landschaftsschutzgebiet

Martin Vogel ⌂ @, Dortmund / Bochum, Freitag, 17.03.2017, 13:33 (vor 585 Tagen) @ Martin Vogel

Durch Zufall entdeckt: Würde man das Landschaftsschutzgebiet hinter der BO fluten, hätte der neue See die Form eines Einhornkänguruhs :-)

[image]

Das Geländemodell im Bild hat eine Auflösung von 10 Metern, daher sind die Dreiecke, aus denen die Oberfläche geformt ist, noch recht gut erkennbar.

… kommt zufällig gerade jemand günstig an 220 Meter Spundwand?

--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums

Heute schon programmiert? Einführung in Python 3 (PDF)

Aktualisierte Version des Geländemodellierers

Martin Vogel ⌂ @, Dortmund / Bochum, Dienstag, 27.03.2018, 13:24 (vor 210 Tagen) @ Martin Vogel

Das Python-Programm zur Erstellung von 3D-CAD-Geländemodellen wurde nach der ersten Veröffentlichung noch um einige Funktionen erweitert. So kann man nun beispielsweise die Höhenauflösung, die standardmäßig 1 cm beträgt, auf einen gröberen Wert einstellen, um architektonische Höhenschichtenmodelle zu erstellen.

[image]
Der Kaisberg in Hagen-Vorhalle als 10-Meter-Quader-Modell
Datenquelle: https://www.opengeodata.nrw.de/produkte/geobasis/dgm/dgm1/ ; Land NRW (2017) ; Datenlizenz Deutschland - Namensnennung - Version 2.0 (http://www.govdata.de/dl-de/by-2-0)

Die STL-Dateien für den 3D-Druck werden nun auch im Binärformat geschrieben, sind dadurch nur noch halb so groß und werden schneller verarbeitet.
Die Unterseite des Modells ist nun nicht mehr fest auf Meeresspiegelhöhe eingestellt, sondern liegt (auf glatte 10 Meter gerundet) etwas unterhalb der geringsten Geländehöhe. Wussten Sie, dass es Punkte in NRW gibt, die fast 300 Meter unterhalb des Meerespiegels liegen? Werfen Sie mal einen Blick in den Tagebau Hambacher Forst, wo einmal der Ort Etzweiler lag: -298,99 m!
Für die Weiterverarbeitung mit CAD-Programmen gibt es nun auch einen direkten DXF-Export. Leider sind in der DXF-Datei aus skurrilen Lizenzierungsgründen keine 3D-Volumenkörper enthalten, sondern nur die umhüllende Fläche. Nach dem Laden sind daher noch zwei Bearbeitungsschritte nötig. Zuerst müssen mit dem AutoCAD/BricsCAD-Befehl REGION alle 3D-Flächen in Regionen umgewandelt werden. Sobald sich das CAD-Programm davon erholt hat, können Sie mit dem Befehl DMHEFTEN den von den Regionen umhüllten Volumenkörper erstellen lassen. Kochen Sie sich in der Zeit, die dazu benötigt wird, ruhig einen Tee. Oder zwei.
In manchen ländlichen Gegenden ist es gar nicht so einfach, herauszufinden, in welchen ZIP-Dateien des NRW-Geodatenservers die benötigten Geländekacheln zu finden sind. Das Programm ermittelt nun anhand eines Kataloges die Namen der maximal noch herunterzuladenden Archivdateien. Da sich die abgedeckten Bereiche überschneiden, genügen manchmal auch weniger Downloads.

Download des Python-Programms: Digitales Geländemodell.ZIP

  1. #!/usr/bin/env python3
  2.  
  3. import os
  4. import sys
  5. import webbrowser
  6. import struct
  7. from tkinter import Tk
  8. from tkinter.filedialog import askdirectory
  9. from math import sqrt, sin, cos, tan, radians, degrees, floor
  10.  
  11. print("""
  12. Dieses Python3-Skript erstellt Höhenmodelle für CAD und 3D-Druck.
  13.  
  14. Mit zwei Dezimalgradkoordinaten geben Sie die Eckpunkte einer rechteckigen
  15. Geländeoberfläche an, für die dieses Programm diverse 3D-Dateien erzeugt:
  16.  
  17. - ein Höhenmodell im STL-Format für 3D-Drucker,
  18. - eine DXF-Datei mit 3D-Flächen, die in Regionen umgewandelt und zu einem
  19. Volumenmodell geheftet werden können,
  20. - eine auf die Auswahl reduzierte XYZ-Datei und
  21. - diverse AutoCAD- bzw. BricsCAD-Befehlsskripte (SCR).
  22. """)
  23.  
  24. # Die CAD-Skripte enthalten Befehle zur Erzeugung von Prismenfeldern aus
  25. # 3D-Körpern, sodass Volumenbefehle angewendet werden können, sowie Befehle
  26. # zur Erzeugung von verschiedenen Oberflächenmodellen.
  27.  
  28. # Das aus Drei- und Vierecken zusammengesetzte Oberflächenmodell ist im
  29. # Ergebnis mit dem Inhalt der DXF-Datei identisch und lässt sich durch
  30. # Anwendung der beiden Befehle "Region" und "Heften" ebenfalls in ein
  31. # Volumenmodell umwandeln.
  32.  
  33. # Das Programm wurde für die öffentlichen Höhendaten für digitale
  34. # Geländemodelle in NRW geschrieben, lässt sich aber mit geringen Anpassungen
  35. # auch für andere DGM-Daten nutzen.
  36.  
  37. # Datenquelle:
  38. # http://www.bezreg-koeln.nrw.de/
  39. # brk_internet/geobasis/hoehenmodelle/gelaendemodelle/
  40. # bzw. https://www.opengeodata.nrw.de/produkte/
  41.  
  42. # Autor: Dipl.-Ing. Martin Vogel, Hochschule Bochum, 2017
  43.  
  44. # Lizenz: Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 Deutschland
  45. # (CC BY-SA 3.0 DE) https://creativecommons.org/licenses/by-sa/3.0/de/
  46.  
  47. # Version 11 vom 18. Juni 2017
  48. # Zu fehlenden Kacheln wird nun angegeben, in welchen Ortsdateien diese
  49. # zu finden sind. Dazu mussten alle 396 Dateien einmal heruntergeladen und
  50. # diese 269 Gigabyte ZIP-Dateien katalogisiert werden. -> Gelaendekatalog.csv
  51.  
  52. # Version 10 vom 15. Juni 2017
  53. # UTM-Koordinatenumrechnung jetzt ohne externes Modul und nach vereinfachtem
  54. # Algorithmus.
  55.  
  56. # Version 9 vom 13. Juni 2017
  57. # DXF-Export der Dreiecks- und Vierecksflächen aus Version 8.
  58.  
  59. # Version 8 vom 11. Juni 2017
  60. # Oberflächenmodell für CAD-Skript aus einzelnen Dreiecksflächen, wie in der
  61. # STL-Datei, Seitenflächen und Boden jedoch aus Vierecken.
  62.  
  63. # Version 7 vom 9. Juni 2017
  64. # Jetzt mit 3D-Netz, viel schneller als Volumenkörper im CAD-Programm.
  65. # Leider kann BricsCAD aus dieser Oberfläche keinen Volumenkörper heften.
  66.  
  67. # Version 6 vom 21. März 2017
  68. # Jetzt mit Log-Datei zur Garantie der Wiederholbarkeit.
  69.  
  70. # Version 5 vom 19. März 2017
  71. # Jetzt mit variabler Höhe der Unterseite. Es gibt in NRW tatsächlich Flächen,
  72. # die weit unterhalb des Meeresspiegels liegen. (Tagebau Etzweiler: -298,90m!)
  73.  
  74. # Version 4 vom 18. März 2017
  75. # Jetzt mit binärem STL-Export. Die Dateien sind nur noch halb so groß
  76. # und werden viel schneller gelesen als ASCII-STL-Dateien.
  77.  
  78. # Version 3 vom 17. März 2017
  79. # Einige mögliche Fehleingaben werden jetzt freundlich abgefangen.
  80. # Erweiterung um CAD-Skript für Dreiecksprismen, um den Minecraft-Effekt
  81. # loszuwerden.
  82.  
  83. # Version 2 vom 16. März 2017
  84. # Jetzt mit Eingabe der Höhenauflösung.
  85. # Außerdem werden nicht mehr alle gefundenen Punkte im Dictionary
  86. # abgelegt, sondern nur noch die später auch benötigten.
  87.  
  88. # Ideen für Erweiterungen:
  89. # - Grafische Oberfläche (mit Kontrolle der Kachelabdeckung) einbauen.
  90. # - Automatisches Nachladen fehlender Kacheln.
  91. # - In der DXF-Datei noch Werte für Zoom, Blickwinkel, Geokoordinaten,
  92. # Einheit Meter usw. eintragen.
  93. # - Klären, warum FreeCAD nur eine einzige 3D-Fläche der DXF-Datei darstellt.
  94.  
  95. def sysexit():
  96. input("\nProgramm wird beendet. [Enter]")
  97. sys.exit()
  98.  
  99. def log(s, sichtbar=True):
  100. "Bildschirmmeldung mit Protokollierung"
  101. with open(name+".log","a") as logfile:
  102. logfile.write("%s\n" % s)
  103. if sichtbar:
  104. print("\n%s" % s)
  105.  
  106. def utm(Bg,Lg):
  107. "Umrechnung von Breitengrad und Längengrad in UTM-Koordinaten"
  108. # http://www.ottmarlabonde.de/L1/UTMBeispielRechnung.htm
  109. # Literatur: A. Schödlbauer,
  110. # Rechenformeln und Rechenbeispiele zur Landesvermessung,
  111. # Teil 2, Herbert WichmannVerlag Karlsruhe
  112.  
  113. L = radians(Lg)
  114. B = radians(Bg)
  115. tB = tan(B)
  116. cB = cos(B)
  117.  
  118. # Halbachsen des WGS84-Ellipsoids:
  119. a = 6378137.0
  120. b = 6356752.314
  121.  
  122. # Radiusreduzierung
  123. mH = 0.9996
  124.  
  125. # c = a**2/b
  126. c = 6399593.626005325
  127.  
  128. # eq = (a**2-b**2)/b**2
  129. eq = 0.006739496819936062
  130.  
  131. # E0 = c*(1-3/4*eq+45/64*eq**2-175/256*eq**3+11025/16384*eq**4)
  132. E0 = 6367449.145759811
  133.  
  134. # E2 = c*(-3/8*eq+15/32*eq**2-525/1024*eq**3+2205/4096*eq**4)
  135. E2 = -16038.508797800609
  136.  
  137. # E4 = c*(15/256*eq**2-105/1024*eq**3+2205/16384*eq**4)
  138. E4 = 16.83262765753934
  139.  
  140. # E6 = c*(-35/3072*eq**3+315/12288*eq**4)
  141. E6 = -0.021980907677118407
  142.  
  143. LL = E0*B + E2*sin(2*B) + E4*sin(4*B) + E6*sin(6*B)
  144. x0 = mH*LL
  145.  
  146. LhL = 3 + 6*floor(degrees(L)/6)
  147. Zone = 30 + (3+LhL)/6
  148.  
  149. DL = L - radians(LhL)
  150.  
  151. etaq = eq*cB**2
  152. Nq = c/sqrt(1+etaq)
  153.  
  154. x2 = mH/2*Nq*tB*cB**2
  155. x4 = mH/24*Nq*tB*cB**4*(5-tB**2+9*etaq)
  156. x6 = mH/720*Nq*tB*cB**6*(61-58*tB**2+tB**4)
  157.  
  158. N = x0 + x2*DL**2 + x4*DL**4 + x6*DL**6
  159.  
  160. y1 = mH*Nq*cB
  161. y3 = mH/6*Nq*cB**3*(1-tB**2+etaq)
  162. y5 = mH/120*Nq*cB**5*(5-18*tB**2+tB**4+etaq*(14-58*tB**2))
  163.  
  164. E = y1*DL + y3*DL**3 + y5*DL**5 + 500000
  165.  
  166. return N, E, Zone
  167.  
  168.  
  169. # Schritt 1: Wie soll das Kind heißen?
  170. print("\nGeben Sie einen Basisnamen für die erzeugten Dateien an!\n"
  171. "Existierende Dateien mit diesem Namen und den Endungen .dxf, .scr,\n"
  172. ".xyz und .stl werden ohne Warnung überschrieben.\n")
  173.  
  174. name = input("Dateiname ohne Endung: ")
  175. log("Basisname: "+name, sichtbar=False)
  176.  
  177. # Schritt 2: Wo sind die Geodaten?
  178. print("Bitte wählen Sie den Ordner mit den ausgepackten XYZ-Dateien aus:")
  179. # Für den Dialog müssen wir Tk laden. Wir brauchen aber das Fenster nicht.
  180. Tk().withdraw()
  181. ordner = askdirectory(title="Bitte den gewünschten Ordner doppelklicken")
  182.  
  183. if not ordner:
  184. print("\nKein Ordner ausgewählt.")
  185. sysexit()
  186.  
  187. log("Gewählter Ordner: %s" % ordner)
  188.  
  189. # Schritt 3: Welches Gebiet wollen wir modellieren?
  190. print("\nGeben Sie zwei gegenüberliegende Eckpunkte des zu modellierenden\n"
  191. "Rechtecks an! Das Eingabeformat ist (Breite, Länge) in Dezimalgrad,\n"
  192. "also beispielsweise 51.335757,7.479087 – Sie können die Koordinaten\n"
  193. "direkt aus Google Maps oder Ihrem GPS-Gerät übernehmen.\n")
  194.  
  195. try:
  196. lat1, lon1 = eval(input("Erstes Eckpunktkoordinatenpaar: "))
  197. lat2, lon2 = eval(input("Gegenüberliegendes Koordinatenpaar: "))
  198. except:
  199. print("Es wurden keine zwei mit einem Komma getrennte Zahlen erkannt.")
  200. sysexit()
  201.  
  202. # Die Eingabe oben war beliebig, wir brauchen aber gleich die Südwestecke
  203. # unten links und die Nordostecke oben rechts:
  204. ul_lat = min(lat1, lat2)
  205. ul_lon = min(lon1, lon2)
  206. or_lat = max(lat1, lat2)
  207. or_lon = max(lon1, lon2)
  208. log("Geokoordinaten: %f,%f %f,%f" % (ul_lat, ul_lon, or_lat, or_lon),
  209. sichtbar=False)
  210.  
  211. # Umrechnung der Dezimalgradkoordinaten ins UTM-System; aus Längen- und
  212. # Breitengrad werden ein Nordwert, ein Ostwert und eine Zonennummer.
  213. n, e, zn = utm(ul_lat, ul_lon)
  214.  
  215. # Die Zonennummer ist in den Dateien, die unter opengeodata.nrw.de
  216. # heruntergeladen werden können, dem Ostwert vorangestellt.
  217. ul_e = int("%i%i"%(zn,e))
  218.  
  219. # Der Nordwert wird übernommen.
  220. ul_n = int(n)
  221.  
  222. # Das gleiche noch einmal für die Nordostecke unseres Rechtecks.
  223. n, e, zn = utm(or_lat, or_lon)
  224. or_e = int("%i%i"%(zn,e))
  225. or_n = int(n)
  226.  
  227. log("\nLage und Größe des ausgewählten Rechtecks:")
  228. log("UTM-Koordinaten: %i,%i %i,%i in Zone %i" % (ul_e,ul_n,or_e,or_n,zn))
  229. log("Ausdehnung Ost-West: %i m" % (or_e-ul_e))
  230. log("Ausdehnung Nord-Süd: %i m" % (or_n-ul_n))
  231. log("Fläche: %i m²" % ((or_e-ul_e)*(or_n-ul_n)))
  232.  
  233. # Liste der zu öffnenden XYZ-Dateien zusammenbauen:
  234. # Bei den NRW-Höhendaten kann man am Dateinamen erkennen, ob gesuchte
  235. # Punkte in ihnen enthalten sein könnten.
  236. # Der Dateiname gibt die untere linke Ecke einer 2000x2000-m²-Kachel an.
  237. # Beispiel für einen Dateinamen: dgm1_32368_5700_2_nw.xyz
  238. # Hier sind die Ostwerte 32368000 bis 32369999 und die Nordwerte
  239. # 5700000 bis n5701999 enthalten.
  240.  
  241. print("\nUntersuche Vollständigkeit der Höhendaten…")
  242. xyz_Liste = []
  243. e_min = 2 * (ul_e // 2000)
  244. e_max = 2 * (or_e // 2000)
  245. n_min = 2 * (ul_n // 2000)
  246. n_max = 2 * (or_n // 2000)
  247. for e in range(e_min,e_max+1,2):
  248. for n in range(n_min,n_max+1,2):
  249. xyz_Liste.append("dgm1_%i_%i_2_nw.xyz" % (e,n))
  250.  
  251. # Sind alle Dateien vorhanden?
  252. fehlende_zip=[]
  253. for xyz_Name in xyz_Liste:
  254. if not os.path.isfile(ordner+"/"+xyz_Name):
  255. print("Die XYZ-Datei %s fehlt!" % xyz_Name)
  256. # print("Sie finden Sie in folgenden ZIP-Archiven:")
  257. with open("Gelaendekatalog.csv") as csv:
  258. for Zeile in csv:
  259. if xyz_Name in Zeile[1:]:
  260. zip_name = Zeile.split()[0]
  261. # print(zip_name)
  262. if zip_name not in fehlende_zip:
  263. fehlende_zip.append(zip_name)
  264.  
  265. if fehlende_zip:
  266. print("\nBitte laden Sie zuerst die fehlenden ZIP-Archive herunter und\n"
  267. "entpacken Sie die darin enthaltenen XYZ-Dateien in den Ordner\n"
  268. "%s.\n" % ordner)
  269. print("In diesen Archiven können Sie die fehlenden Kacheln finden:\n")
  270. print("\n".join(fehlende_zip))
  271. print("\nDie Downloadseite\n"
  272. "https://www.opengeodata.nrw.de/produkte/geobasis/dgm/dgm1/\n"
  273. "wird nun im Webbrowser aufgerufen …")
  274. webbrowser.open("https://www.opengeodata.nrw.de/"
  275. "produkte/geobasis/dgm/dgm1/")
  276. sysexit()
  277.  
  278. print("… alle benötigten Dateien sind vorhanden.")
  279.  
  280. # Alle Dateien sind vorhanden, jetzt kümmern wir uns um die Modelldetails:
  281. print("\nDie horizontale Auflösung der Daten beträgt einen Meter, was bei\n"
  282. "größeren Flächen zu extremen Dateigrößen und Verarbeitungszeiten der\n"
  283. "erzeugten Modelle führen kann. Tipp: beginnen Sie mit ungefähr 1000\n"
  284. "Punkten auf der gesamten Fläche und erhöhen Sie die Auflösung in\n"
  285. "weiteren Durchläufen schrittweise.\n")
  286.  
  287. # Vorschlag für die horizontale Auflösung ermitteln
  288. kl0 = max(1,int(((or_e-ul_e)*(or_n-ul_n)/1000)**0.5))
  289. p0 = ((or_e-ul_e)//kl0) * ((or_n-ul_n)//kl0)
  290. print("Bei %i m Auflösung würden Sie beispielsweise %i Punkte erhalten.\n" % (
  291. kl0, p0))
  292.  
  293. try:
  294. kl = int(input("Geben Sie einen ganzzahligen Wert ein [m]: "))
  295. except:
  296. kl = 1
  297.  
  298. print("\nDie vertikale Auflösung der Daten beträgt einen Zentimeter. Das ist\n"
  299. "normalerweise in Ordnung. Für einen Höhenschichteneffekt wie bei\n"
  300. "architektonischen Geländemodellen kann dieser Wert auch auf 100 oder\n"
  301. "mehr geändert werden, je nach gewünschter Effektstärke.\n"
  302. "Nebeneffekt: Das CAD-Volumenmodell benötigt bei größeren Werten\n"
  303. "deutlich weniger Speicherplatz.\n")
  304.  
  305. try:
  306. kh = int(input("Geben Sie einen ganzzahligen Wert ein [cm]: "))
  307. except:
  308. kh = 1
  309.  
  310. xmax = or_e - (or_e-ul_e) % kl
  311. ymax = or_n - (or_n-ul_n) % kl
  312.  
  313. log("Horizontale Auflösung [m]: %i" % kl, sichtbar=False)
  314. log("Vertikale Auflösung [cm]: %i" % kh, sichtbar=False)
  315. log("\nAbstand der neuen Punkte: %i m" % kl)
  316. log("\nAusdehnung Ost-West: %i m" % (xmax-ul_e))
  317. log("Ausdehnung Nord-Süd: %i m" % (ymax-ul_n))
  318. fqm = (xmax-ul_e)*(ymax-ul_n)
  319. log("Fläche: %i m² bzw. %.3f km²" % (fqm,fqm/1e6))
  320.  
  321. # Alle gefundenen Höhenwerte werden zunächst in ein Dictionary
  322. # geschrieben, aus dem sie für die einzelnen Dateien wieder
  323. # ausgelesen werden.
  324.  
  325. D = {}
  326.  
  327. # Die Höhe der Unterseite ist nicht null, sondern orientiert sich
  328. # am tatsächlichen Gelände.
  329.  
  330. minh = float("inf")
  331. maxh = -float("inf")
  332.  
  333. # Schleife über alle zu verwendenden Eingabedateien
  334. for dateiname in xyz_Liste:
  335. with open(ordner+"/"+dateiname) as dgm:
  336. log("Verwende XYZ-Datei %s" % dateiname)
  337. for zeile in dgm:
  338. # Beispiel für eine Zeile aus Bochum:
  339. # 32372000.00 5706000.00 61.32
  340. try:
  341. x,y,h = zeile.split()
  342. x = int(float(x))
  343. y = int(float(y))
  344. h = float(h)
  345. except ValueError:
  346. print("Abbruch, falsches Format:",zeile)
  347. break
  348. # Koordinaten der Zeile im gesuchten Rechteck?
  349. if ul_e <= x <= or_e and ul_n <= y <= or_n:
  350. # Liegt der Punkt im ausgedünnten Horizontalraster?
  351. if not (x - ul_e) % kl and not (y - ul_n) % kl:
  352. # Punkt wird bei gewählter Auflösung verwendet
  353. if kh != 1:
  354. # Höhenwerte werden gerundet
  355. h = round(round(h*100/kh)*kh/100, 2)
  356. D[x,y] = h
  357. minh = min(h, minh)
  358. maxh = max(h, maxh)
  359.  
  360.  
  361. log("Kleinste gefundene Höhe: %.2f Meter" % minh)
  362. minh = 10 * floor(minh/10) - 10
  363. log("Setze Unterkante auf %.2f Meter." % minh)
  364.  
  365. # Alle Punkte als simple XYZ-Datei sichern
  366.  
  367. ausname = name+".xyz"
  368. log("Schreibe XYZ-Ausgabedatei: %s" % ausname)
  369.  
  370. with open(ausname,"w") as aus:
  371. for x in range(ul_e,or_e+1,kl):
  372. for y in range(ul_n,or_n+1,kl):
  373. aus.write("%i %i %.2f\n"%(x,y,D[(x,y)]))
  374.  
  375. # DXF-Export
  376.  
  377. ausname = name+".dxf"
  378. log("Schreibe DXF-Datei mit 3D-Flächen: %s" % ausname)
  379.  
  380. # Weil 3D-Solids in einem obskuren „Geheimformat“ gespeichert werden, hier
  381. # nur die umhüllende Fläche des mittels REGION und HEFTEN in einen SOLID
  382. # umzuwandelnden Geländekörpers.
  383.  
  384. with open(ausname,"w") as aus:
  385. aus.write("0\nSECTION\n2\nHEADER\n"
  386. "9\n$ACADVER\n1\nAC1006\n"
  387. "9\n$INSBASE\n10\n0.0\n20\n0.0\n30\n0.0\n"
  388. "9\n$EXTMIN\n10\n0.0\n20\n0.0\n"
  389. "9\n$EXTMAX\n10\n%f\n20\n%f\n" % (xmax-ul_e, ymax-ul_n) +
  390. "9\n$LIMMIN\n10\n0.0\n20\n0.0\n"
  391. "9\n$LIMMAX\n10\n%f\n20\n%f\n" % (xmax-ul_e, ymax-ul_n) +
  392. "0\nENDSEC\n"
  393. "0\nSECTION\n2\nENTITIES\n")
  394.  
  395. # Geländeoberfläche
  396. for x in range(ul_e,xmax+1-kl,kl):
  397. xu = x - ul_e
  398. for y in range(ul_n,ymax+1-kl,kl):
  399. yu = y - ul_n
  400. h1 = D[x,y]
  401. h2 = D[x+kl,y]
  402. h3 = D[x,y+kl]
  403. h4 = D[x+kl,y+kl]
  404. aus.write("0\n3DFACE\n"
  405. "10\n%i\n20\n%i\n30\n%s\n"%(xu, yu, h1) +
  406. "11\n%i\n21\n%i\n31\n%s\n"%(xu+kl, yu+kl, h4) +
  407. "12\n%i\n22\n%i\n32\n%s\n"%(xu+kl, yu, h2) +
  408. "13\n%i\n23\n%i\n33\n%s\n"%(xu+kl, yu, h2))
  409. aus.write("0\n3DFACE\n"
  410. "10\n%i\n20\n%i\n30\n%s\n"%(xu, yu, h1) +
  411. "11\n%i\n21\n%i\n31\n%s\n"%(xu, yu+kl, h3) +
  412. "12\n%i\n22\n%i\n32\n%s\n"%(xu+kl, yu+kl, h4) +
  413. "13\n%i\n23\n%i\n33\n%s\n"%(xu+kl, yu+kl, h4))
  414.  
  415.  
  416. # Unterseite
  417. aus.write("0\n3DFACE\n"
  418. "10\n%i\n20\n%i\n30\n%.2f\n"%(0,0, minh) +
  419. "11\n%i\n21\n%i\n31\n%.2f\n"%(xmax-ul_e, 0, minh) +
  420. "12\n%i\n22\n%i\n32\n%.2f\n"%(xmax-ul_e, ymax-ul_n, minh) +
  421. "13\n%i\n23\n%i\n33\n%.2f\n"%(0, ymax-ul_n, minh))
  422.  
  423. # Linke Wand
  424. for y in range(ul_n,ymax+1-kl,kl):
  425. yu = y-ul_n
  426. h1 = D[ul_e,y]
  427. h2 = D[ul_e,y+kl]
  428. aus.write("0\n3DFACE\n"
  429. "10\n0\n20\n%i\n30\n%.2f\n"%(yu, minh) +
  430. "11\n0\n21\n%i\n31\n%.2f\n"%(yu+kl, minh) +
  431. "12\n0\n22\n%i\n32\n%.2f\n"%(yu+kl, h2) +
  432. "13\n0\n23\n%i\n33\n%.2f\n"%(yu, h1))
  433.  
  434. # Rechte Wand
  435. for y in range(ul_n,ymax+1-kl,kl):
  436. xu = xmax-ul_e
  437. yu = y-ul_n
  438. h1 = D[xmax,y]
  439. h2 = D[xmax,y+kl]
  440. aus.write("0\n3DFACE\n"
  441. "10\n%i\n20\n%i\n30\n%.2f\n" % (xu, yu, minh) +
  442. "11\n%i\n21\n%i\n31\n%.2f\n" % (xu, yu, h1) +
  443. "12\n%i\n22\n%i\n32\n%.2f\n" % (xu, yu+kl, h2) +
  444. "13\n%i\n23\n%i\n33\n%.2f\n" % (xu, yu+kl, minh))
  445.  
  446. # Vordere Wand
  447. for x in range(ul_e,xmax+1-kl,kl):
  448. h1 = D[x,ul_n]
  449. h2 = D[x+kl,ul_n]
  450. xu = x - ul_e
  451. aus.write("0\n3DFACE\n"
  452. "10\n%i\n20\n0\n30\n%.2f\n" % (xu, minh) +
  453. "11\n%i\n21\n0\n31\n%.2f\n" % (xu, h1) +
  454. "12\n%i\n22\n0\n32\n%.2f\n" % (xu+kl, h2) +
  455. "13\n%i\n23\n0\n33\n%.2f\n" % (xu+kl, minh))
  456.  
  457. # Hintere Wand
  458. for x in range(ul_e,xmax+1-kl,kl):
  459. xu = x - ul_e
  460. yu = ymax - ul_n
  461. h1 = D[x,ymax]
  462. h2 = D[x+kl,ymax]
  463. aus.write("0\n3DFACE\n"
  464. "10\n%i\n20\n%i\n30\n%.2f\n"%(xu, yu, minh) +
  465. "11\n%i\n21\n%i\n31\n%.2f\n"%(xu+kl, yu, minh) +
  466. "12\n%i\n22\n%i\n32\n%.2f\n"%(xu+kl, yu, h2) +
  467. "13\n%i\n23\n%i\n33\n%.2f\n"%(xu, yu, h1))
  468.  
  469. aus.write("0\nENDSEC\n0\nEOF\n")
  470.  
  471.  
  472.  
  473. # Diverse Skripte für AutoCAD/BricsCAD
  474.  
  475. # Grundeinstellungen des Zeichnungseditors:
  476. def scr_intro():
  477. # Rückgängigmachen ausschalten
  478. # Koordinateneingabe hat Vorrang vor Objektfang,
  479. # Ansicht schräg von Südwesten,
  480. # Visueller Stil Drahtmodell (Geschwindigkeit!),
  481. # Zoom auf interessanten Quader
  482. return ("Zurück S K\n"
  483. "OSnapCoord 1\n"
  484. "APunkt -1,-2,2\n"
  485. "-Vis A Drahtmodell\n"
  486. "Zoom F 0,0,%f %i,%i,%f\n" % (minh, or_e-ul_e, or_n-ul_n, maxh))
  487.  
  488. # Einstellungen nach Skriptende:
  489. def scr_exit():
  490. # Rückgängigmachen einschalten
  491. # Zoom auf Grenzen
  492. return ("Zurück A\n"
  493. "Zoom G\n")
  494.  
  495.  
  496. ausname = name+".quadratprismen.scr"
  497. log("Schreibe CAD-Skriptdatei mit Quadratprismenfeld: %s " % ausname)
  498.  
  499. # Eigentlich wäre es universeller, anstelle der deutschsprachigen
  500. # Bezeichner englischsprachige Bezeichner mit vorangestelltem Unterstrich
  501. # zu verwenden. Dummerweise kommt BricsCAD damit nicht immer zurecht.
  502. with open(ausname,"w") as aus:
  503. aus.write(scr_intro())
  504.  
  505. for x in range(ul_e,or_e+1,kl):
  506. for y in range(ul_n,or_n+1,kl):
  507. aus.write("Quader %i,%i,%.2f %i,%i,%.2f\n" % (
  508. x-ul_e, y-ul_n, minh,
  509. x-ul_e+kl, y-ul_n+kl, D[(x,y)]))
  510.  
  511. aus.write(scr_exit())
  512.  
  513. ausname = name+".dreiecksprismen.scr"
  514. log("Schreibe CAD-Skriptdatei mit Dreiecksprismenfeld: %s" % ausname)
  515.  
  516. with open(ausname,"w") as aus:
  517. aus.write(scr_intro())
  518.  
  519. for x in range(ul_e,xmax+1-kl,kl):
  520. for y in range(ul_n,ymax+1-kl,kl):
  521. h1 = float(D[x,y])
  522. x1, y1 = x-ul_e, y-ul_n
  523. h2 = float(D[x+kl,y])
  524. x2, y2 = x1+kl, y1
  525. h3 = float(D[x+kl,y+kl])
  526. x3, y3 = x2, y1+kl
  527. h4 = float(D[x,y+kl])
  528. x4, y4 = x1, y3
  529. hmax = max(h1, h2, h3, h4)
  530. # Dreieck auf Nullebene zeichnen …
  531. aus.write("3DPoly %i,%i,%.2f %i,%i,%.2f %i,%i,%.2f S\n" % (
  532. x1,y1,minh, x2,y2,minh, x3,y3,minh))
  533. # … bis zum höchsten Punkt hochziehen …
  534. aus.write("_extrude L %f\n" % (hmax-minh))
  535. # … und oben schräg abschneiden.
  536. aus.write("Kappen L %i,%i,%f %i,%i,%f %i,%i,%f %i,%i,%f\n" %
  537. (x1,y1,h1, x2,y2,h2, x3,y3,h3, x1,y1,minh))
  538. # Und nochmal für das zweite Dreieck:
  539. aus.write("3DPoly %i,%i,%.2f %i,%i,%.2f %i,%i,%.2f S\n" % (
  540. x1,y1,minh, x4,y4,minh, x3,y3,minh))
  541. aus.write("_extrude L %f\n" % hmax)
  542. aus.write("Kappen L %i,%i,%f %i,%i,%f %i,%i,%f %i,%i,%f\n" %
  543. (x1,y1,h1, x4,y4,h4, x3,y3,h3, x1,y1,minh))
  544.  
  545. aus.write(scr_exit())
  546.  
  547. ausname = name+".mesh.scr"
  548. log("Schreibe CAD-Skriptdatei mit 3D-Netz (Mesh): %s " % ausname)
  549.  
  550. # Beim Quadratnetz hat BricsCAD die Einschränkung, dass es maximal
  551. # 256×256 Knoten haben darf. Daher muss hier ausgedünnt werden.
  552.  
  553. with open(ausname,"w") as aus:
  554. aus.write(scr_intro())
  555. n = 1
  556. klm = kl
  557. while (or_e-ul_e+1)//klm > 256 or (or_n-ul_n+1)//klm > 256:
  558. n += 1
  559. klm = kl * n
  560. if n>1:
  561. log("Verwende nur jeden %i. Punkt pro Richtung." % n)
  562.  
  563. # Gelände
  564. aus.write("3dnetz %i %i\n" % ((or_e-ul_e)//klm+1,(or_n-ul_n)//klm+1))
  565. for x in range(ul_e,or_e+1,klm):
  566. for y in range(ul_n,or_n+1,klm):
  567. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,D[(x,y)]))
  568. # Seitenflächen
  569. y = ul_n
  570. aus.write("3dnetz %i 2\n" % ((or_e-ul_e)//klm+1))
  571. for x in range(ul_e,or_e+1,klm):
  572. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,minh))
  573. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,D[(x,y)]))
  574. y = range(ul_n,or_n+1,klm)[-1]
  575. aus.write("3dnetz %i 2\n" % ((or_e-ul_e)//klm+1))
  576. for x in range(ul_e,or_e+1,klm):
  577. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,minh))
  578. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,D[(x,y)]))
  579. x = ul_e
  580. aus.write("3dnetz %i 2\n" % ((or_n-ul_n)//klm+1))
  581. for y in range(ul_n,or_n+1,klm):
  582. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,minh))
  583. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,D[(x,y)]))
  584. x = range(ul_e,or_e+1,klm)[-1]
  585. aus.write("3dnetz %i 2\n" % ((or_n-ul_n)//klm+1))
  586. for y in range(ul_n,or_n+1,klm):
  587. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,minh))
  588. aus.write("%i,%i,%f\n" % (x-ul_e,y-ul_n,D[(x,y)]))
  589. # Boden
  590. x_max = range(ul_e,or_e+1,klm)[-1]-ul_e
  591. y_max = range(ul_n,or_n+1,klm)[-1]-ul_n
  592. aus.write("3dfläche\n")
  593. aus.write("0,0,%f " % (minh))
  594. aus.write("0,%i,%f " % (y_max, minh))
  595. aus.write("%i,%i,%f " % (x_max, y_max, minh))
  596. aus.write("%i,0,%f \n" % (x_max, minh))
  597.  
  598. aus.write(scr_exit())
  599.  
  600. ausname = name+".3dflächen.scr"
  601. log("Schreibe CAD-Scriptdatei mit 3D-Flächen: %s" % ausname)
  602.  
  603. # Das 3D-Netz oben lässt sich in BricsCAD blöderweise nicht zu einem
  604. # Solid umformen. Hier wird daher nun derselbe Algorithmus verwendet,
  605. # der auch für STL-Dateien zum Einsatz kommt.
  606. # Für die Seiten und den Boden werden Vierecke verwendet.
  607.  
  608. with open(ausname,"w") as aus:
  609. aus.write(scr_intro())
  610.  
  611. # Geländeoberfläche
  612. for x in range(ul_e,xmax+1-kl,kl):
  613. xu = x - ul_e
  614. for y in range(ul_n,ymax+1-kl,kl):
  615. yu = y - ul_n
  616. h1 = D[x,y]
  617. h2 = D[x+kl,y]
  618. h3 = D[x,y+kl]
  619. h4 = D[x+kl,y+kl]
  620. aus.write("3dfläche\n"
  621. "%i,%i,%s\n"%(xu, yu, h1) +
  622. "%i,%i,%s\n"%(xu+kl, yu+kl, h4) +
  623. "%i,%i,%s\n\n\n"%(xu+kl, yu, h2))
  624. aus.write("3dfläche\n"
  625. "%i,%i,%s\n"%(xu, yu, h1) +
  626. "%i,%i,%s\n"%(xu, yu+kl, h3) +
  627. "%i,%i,%s\n\n\n"%(xu+kl, yu+kl, h4))
  628.  
  629.  
  630. # Unterseite
  631. aus.write("3dfläche\n"
  632. "%i,%i,%.2f\n"%(0,0, minh) +
  633. "%i,%i,%.2f\n"%(xmax-ul_e, 0, minh) +
  634. "%i,%i,%.2f\n"%(xmax-ul_e, ymax-ul_n, minh) +
  635. "%i,%i,%.2f\n\n"%(0, ymax-ul_n, minh))
  636.  
  637. # Linke Wand
  638. for y in range(ul_n,ymax+1-kl,kl):
  639. yu = y-ul_n
  640. h1 = D[ul_e,y]
  641. h2 = D[ul_e,y+kl]
  642. aus.write("3dfläche\n"
  643. "0,%i,%.2f\n"%(yu, minh) +
  644. "0,%i,%.2f\n"%(yu+kl, minh) +
  645. "0,%i,%.2f\n"%(yu+kl, h2) +
  646. "0,%i,%.2f\n\n"%(yu, h1))
  647.  
  648. # Rechte Wand
  649. for y in range(ul_n,ymax+1-kl,kl):
  650. xu = xmax-ul_e
  651. yu = y-ul_n
  652. h1 = D[xmax,y]
  653. h2 = D[xmax,y+kl]
  654. aus.write("3dfläche\n"
  655. "%i,%i,%.2f\n" % (xu, yu, minh) +
  656. "%i,%i,%.2f\n" % (xu, yu, h1) +
  657. "%i,%i,%.2f\n" % (xu, yu+kl, h2) +
  658. "%i,%i,%.2f\n\n" % (xu, yu+kl, minh))
  659.  
  660. # Vordere Wand
  661. for x in range(ul_e,xmax+1-kl,kl):
  662. h1 = D[x,ul_n]
  663. h2 = D[x+kl,ul_n]
  664. xu = x - ul_e
  665. aus.write("3dfläche\n"
  666. "%i,0,%.2f\n" % (xu, minh) +
  667. "%i,0,%.2f\n" % (xu, h1) +
  668. "%i,0,%.2f\n" % (xu+kl, h2) +
  669. "%i,0,%.2f\n\n" % (xu+kl, minh))
  670.  
  671. # Hintere Wand
  672. for x in range(ul_e,xmax+1-kl,kl):
  673. xu = x - ul_e
  674. yu = ymax - ul_n
  675. h1 = D[x,ymax]
  676. h2 = D[x+kl,ymax]
  677. aus.write("3dfläche\n"
  678. "%i,%i,%.2f\n"%(xu, yu, minh) +
  679. "%i,%i,%.2f\n"%(xu+kl, yu, minh) +
  680. "%i,%i,%.2f\n"%(xu+kl, yu, h2) +
  681. "%i,%i,%.2f\n\n"%(xu, yu, h1))
  682.  
  683. aus.write(scr_exit())
  684.  
  685. ausname = name+".ascii.stl"
  686. log("Schreibe STL-Datei für 3D-Druck (ASCII): %s" % ausname)
  687.  
  688. # Die Flächen umhüllen einen Quader, der unten auf Höhe minh aufliegt
  689. # und oben durch die Geländeoberfläche abgeschnitten wird.
  690.  
  691. with open(ausname,"w") as aus:
  692. aus.write("solid "+ausname+"\n")
  693.  
  694. # Geländeoberfläche
  695. for x in range(ul_e,xmax+1-kl,kl):
  696. xu = x - ul_e
  697. for y in range(ul_n,ymax+1-kl,kl):
  698. yu = y - ul_n
  699. h1 = D[x,y]
  700. h2 = D[x+kl,y]
  701. h3 = D[x,y+kl]
  702. h4 = D[x+kl,y+kl]
  703. aus.write("facet normal 0 0 1\n"
  704. "outer loop\n"
  705. "vertex %i %i %s\n"%(xu, yu, h1) +
  706. "vertex %i %i %s\n"%(xu+kl, yu+kl, h4) +
  707. "vertex %i %i %s\n"%(xu+kl, yu, h2) +
  708. "endloop\n"
  709. "endfacet\n")
  710. aus.write("facet normal 0 0 1\n"
  711. "outer loop\n"
  712. "vertex %i %i %s\n"%(xu, yu, h1) +
  713. "vertex %i %i %s\n"%(xu, yu+kl, h3) +
  714. "vertex %i %i %s\n"%(xu+kl, yu+kl, h4) +
  715. "endloop\n"
  716. "endfacet\n")
  717. # Was für ein Aufwand für zwei popelige Dreiecke!
  718.  
  719. # Die Unterseite und die Seitenflächen wiederholen im Moment
  720. # die Unterteilung der Oberseite. Dadurch hat die STL-Datei fast
  721. # doppelt so viele Dreiecke wie eigentlich nur nötig wären.
  722. # Durch geschickte Aufteilung könnte die Unterseite mit nur zwei
  723. # Dreiecken realisiert werden, wobei die Seitenflächen mit rund der
  724. # Hälfte der Dreiecke auskommen könnten. Bei großen Geländesteigungen
  725. # gibt es da aber ein paar Herausforderungen an den Algorithmus.
  726.  
  727. # Unterseite
  728. for x in range(ul_e,xmax+1-kl,kl):
  729. xu = x - ul_e
  730. for y in range(ul_n,ymax+1-kl,kl):
  731. yu = y - ul_n
  732. aus.write("facet normal 0 0 -1\n"
  733. "outer loop\n"
  734. "vertex %i %i %.2f\n"%(xu, yu, minh) +
  735. "vertex %i %i %.2f\n"%(xu+kl, yu, minh) +
  736. "vertex %i %i %.2f\n"%(xu+kl, yu+kl, minh) +
  737. "endloop\n"
  738. "endfacet\n")
  739. aus.write("facet normal 0 0 -1\n"
  740. "outer loop\n"
  741. "vertex %i %i %.2f\n"%(xu, yu, minh) +
  742. "vertex %i %i %.2f\n"%(xu+kl, yu+kl, minh) +
  743. "vertex %i %i %.2f\n"%(xu, yu+kl, minh) +
  744. "endloop\n"
  745. "endfacet\n")
  746.  
  747. # Linke Wand
  748. for y in range(ul_n,ymax+1-kl,kl):
  749. yu = y-ul_n
  750. h1 = D[ul_e,y]
  751. h2 = D[ul_e,y+kl]
  752. aus.write("facet normal -1 0 0\n"
  753. "outer loop\n"
  754. "vertex 0 %i %.2f\n"%(yu, minh) +
  755. "vertex 0 %i %.2f\n"%(yu+kl, h2) +
  756. "vertex 0 %i %.2f\n"%(yu, h1) +
  757. "endloop\n"
  758. "endfacet\n")
  759. aus.write("facet normal -1 0 0\n"
  760. "outer loop\n"
  761. "vertex 0 %i %.2f\n"%(yu, minh) +
  762. "vertex 0 %i %.2f\n"%(yu+kl, minh) +
  763. "vertex 0 %i %.2f\n"%(yu+kl, h2) +
  764. "endloop\n"
  765. "endfacet\n")
  766.  
  767. # Rechte Wand
  768. for y in range(ul_n,ymax+1-kl,kl):
  769. xu = xmax-ul_e
  770. yu = y-ul_n
  771. h1 = D[xmax,y]
  772. h2 = D[xmax,y+kl]
  773. aus.write("facet normal 1 0 0\n"
  774. "outer loop\n"
  775. "vertex %i %i %.2f\n" % (xu, yu, minh) +
  776. "vertex %i %i %.2f\n" % (xu, yu, h1) +
  777. "vertex %i %i %.2f\n" % (xu, yu+kl, h2) +
  778. "endloop\n"
  779. "endfacet\n")
  780. aus.write("facet normal 1 0 0\n"
  781. "outer loop\n"
  782. "vertex %i %i %.2f\n" % (xu, yu, minh) +
  783. "vertex %i %i %.2f\n" % (xu, yu+kl, h2) +
  784. "vertex %i %i %.2f\n" % (xu, yu+kl, minh) +
  785. "endloop\n"
  786. "endfacet\n")
  787.  
  788. # Vordere Wand
  789. for x in range(ul_e,xmax+1-kl,kl):
  790. h1 = D[x,ul_n]
  791. h2 = D[x+kl,ul_n]
  792. xu = x - ul_e
  793. aus.write("facet normal 0 -1 0\n"
  794. "outer loop\n"
  795. "vertex %i 0 %.2f\n" % (xu, minh) +
  796. "vertex %i 0 %.2f\n" % (xu, h1) +
  797. "vertex %i 0 %.2f\n" % (xu+kl, h2) +
  798. "endloop\n"
  799. "endfacet\n")
  800. aus.write("facet normal 0 -1 0\n"
  801. "outer loop\n"
  802. "vertex %i 0 %.2f\n" % (xu, minh) +
  803. "vertex %i 0 %.2f\n" % (xu+kl, h2) +
  804. "vertex %i 0 %.2f\n" % (xu+kl, minh) +
  805. "endloop\n"
  806. "endfacet\n")
  807.  
  808. # Hintere Wand
  809. for x in range(ul_e,xmax+1-kl,kl):
  810. xu = x - ul_e
  811. yu = ymax - ul_n
  812. h1 = D[x,ymax]
  813. h2 = D[x+kl,ymax]
  814. aus.write("facet normal 0 1 0\n"
  815. "outer loop\n"
  816. "vertex %i %i %.2f\n"%(xu, yu, minh) +
  817. "vertex %i %i %.2f\n"%(xu+kl, yu, h2) +
  818. "vertex %i %i %.2f\n"%(xu, yu, h1) +
  819. "endloop\n"
  820. "endfacet\n")
  821. aus.write("facet normal 0 1 0\n"
  822. "outer loop\n"
  823. "vertex %i %i %.2f\n"%(xu, yu, minh) +
  824. "vertex %i %i %.2f\n"%(xu+kl, yu, minh) +
  825. "vertex %i %i %.2f\n"%(xu+kl, yu, h2) +
  826. "endloop\n"
  827. "endfacet\n")
  828.  
  829. ausname = name+".binär.stl"
  830. log("Schreibe STL-Datei für 3D-Druck (binär): %s" % ausname)
  831.  
  832. with open(ausname,"wb") as aus:
  833. # 80 Bytes ungenutzter Header
  834. aus.write(b'\0' * 80)
  835.  
  836. # Wie viele Dreiecke hat das Modell insgesamt?
  837. nx = (xmax - ul_e) // kl
  838. ny = (ymax - ul_n) // kl
  839. ngesamt = 4 * nx*ny + 4 * nx + 4 * ny
  840. aus.write(struct.pack('<I', ngesamt))
  841.  
  842. # Geländeoberfläche
  843. for x in range(ul_e,xmax+1-kl,kl):
  844. xu = x - ul_e
  845. for y in range(ul_n,ymax+1-kl,kl):
  846. yu = y - ul_n
  847. h1 = D[x,y]
  848. h2 = D[x+kl,y]
  849. h3 = D[x,y+kl]
  850. h4 = D[x+kl,y+kl]
  851. aus.write(struct.pack("<12fh",
  852. 0, 0, 1,
  853. xu, yu, h1,
  854. xu+kl, yu+kl, h4,
  855. xu+kl, yu, h2,
  856. 0))
  857. aus.write(struct.pack("<12fh",
  858. 0, 0, 1,
  859. xu, yu, h1,
  860. xu, yu+kl, h3,
  861. xu+kl, yu+kl, h4,
  862. 0))
  863.  
  864. # Unterseite
  865. for x in range(ul_e,xmax+1-kl,kl):
  866. xu = x - ul_e
  867. for y in range(ul_n,ymax+1-kl,kl):
  868. yu = y - ul_n
  869. aus.write(struct.pack("<12fh",
  870. 0, 0, -1,
  871. xu, yu, minh,
  872. xu+kl, yu+kl, minh,
  873. xu+kl, yu, minh,
  874. 0))
  875. aus.write(struct.pack("<12fh",
  876. 0, 0, -1,
  877. xu, yu, minh,
  878. xu, yu+kl, minh,
  879. xu+kl, yu+kl, minh,
  880. 0))
  881.  
  882. # Linke Wand
  883. for y in range(ul_n,ymax+1-kl,kl):
  884. yu = y-ul_n
  885. h1 = D[ul_e,y]
  886. h2 = D[ul_e,y+kl]
  887. aus.write(struct.pack("<12fh",
  888. -1, 0, 0,
  889. 0, yu, minh,
  890. 0, yu+kl, h2,
  891. 0, yu, h1,
  892. 0))
  893. aus.write(struct.pack("<12fh",
  894. -1, 0, 0,
  895. 0, yu, minh,
  896. 0, yu+kl, minh,
  897. 0, yu+kl, h2,
  898. 0))
  899.  
  900. # Rechte Wand
  901. xu = xmax-ul_e
  902. for y in range(ul_n,ymax+1-kl,kl):
  903. yu = y-ul_n
  904. h1 = D[xmax,y]
  905. h2 = D[xmax,y+kl]
  906. aus.write(struct.pack("<12fh",
  907. 1, 0, 0,
  908. xu, yu, minh,
  909. xu, yu, h1,
  910. xu, yu+kl, h2,
  911. 0))
  912. aus.write(struct.pack("<12fh",
  913. 1, 0, 0,
  914. xu, yu, minh,
  915. xu, yu+kl, h2,
  916. xu, yu+kl, minh,
  917. 0))
  918.  
  919. # Vordere Wand
  920. for x in range(ul_e,xmax+1-kl,kl):
  921. h1 = D[x,ul_n]
  922. h2 = D[x+kl,ul_n]
  923. xu = x - ul_e
  924. aus.write(struct.pack("<12fh",
  925. 0, -1, 0,
  926. xu, 0, minh,
  927. xu, 0, h1,
  928. xu+kl, 0, h2,
  929. 0))
  930. aus.write(struct.pack("<12fh",
  931. 0, -1, 0,
  932. xu, 0, minh,
  933. xu+kl, 0, h2,
  934. xu+kl, 0, minh,
  935. 0))
  936.  
  937. # Hintere Wand
  938. yu = ymax - ul_n
  939. for x in range(ul_e,xmax+1-kl,kl):
  940. xu = x - ul_e
  941. h1 = D[x,ymax]
  942. h2 = D[x+kl,ymax]
  943. aus.write(struct.pack("<12fh",
  944. 0, 1, 0,
  945. xu, yu, minh,
  946. xu+kl, yu, h2,
  947. xu, yu, h1,
  948. 0))
  949. aus.write(struct.pack("<12fh",
  950. 0, 1, 0,
  951. xu, yu, minh,
  952. xu+kl, yu, minh,
  953. xu+kl, yu, h2,
  954. 0))
  955.  
  956. log("Programmlauf erfolgreich abgeschlossen.")
  957. print("Die Ausgabedateien können nun weiterverarbeitet werden.")
  958. sysexit()

--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums

Heute schon programmiert? Einführung in Python 3 (PDF)

Tags:
NRW, DGM, Geländemodell, Python 3

RSS-Feed dieser Diskussion
powered by my little forum