von Cedric Mans, Lea Harz, Lucas Enzmann, Maike Herkenrath, Maleen Ahrens, Sönke Roos
Unser Projekt „Optimales Puppenhaus“ hat die dreidimensionale Generierung eines Hauses gemäß den Anforderungen des Nutzers (Baugrundfläche etc.) und gewisser Optimalitätskriterien zum Ziel. Als optimal wird beispielsweise die Ausrichtung der Fenster für einen vorteilhaften Lichteinfall, die Verhältnisse der Raummaße gemäß des goldenen Schnittes, aber auch ganz banale Dinge1) wie die Position von Schränken mit einem ausreichenden Abstand zueinander, sodass der eine Schrank nicht die Öffnung der Tür des anderen behindert, angesehen. Ziel des Projekts ist der 3D-Druck eines nach unseren Maßstäben optimalen kleinen Puppenhauses.
Unser Projekt hat sich zum Ziel gesetzt den Grundriss einer Wohnung nach unterschiedlichen Kriterien zu optimieren. Die Natur dieser Kriterien waren uns zuerst unklar. Wir hatten unterschiedliche Ideen, wussten aber nicht welche technisch realisierbar waren. Die Ergebnisse sollten als 3D-Modell dargestellt werden um anschließend von einem 3D-Drucker realisiert werden zu können.
In den ersten Wochen haben wir uns unterschiedliche Teilziele definiert, wovon sich aber die meisten als unrealistisch herausgestellt haben. Schlussendlich haben wir uns die drei dimensionale Visualisierung eines nach unterschiedlichen Kriterien optimierten Raumes als Endziel gesetzt. Als Zwischenziele haben wir uns die einzelnen Objekte vorgenommen, aus welche ein Raum sich zusammensetzt.
Nach einigen Wochen haben wir unsere größere Gruppe von sechs Personen, in zwei Untergruppen aufgeteilt. Die Triangulierungs-Gruppe war verantwortlich für die Konvertion eines generierten 3D-Objektes, in ein geometrisch vergleichbares Objekt, bestehend ausschließlich aus der Zusammensetzung flacher Dreiecke. Dadurch werden überflüssige Trennwände entfernt, wodurch es möglich wird das Objekt mittels eines 3D-Druckers auszudrucken.
Die andere Gruppe ist Verantwortlich für die Visualisierung und den Optimierungs-Algorithmus. Die Visualisierung besteht aus einer 3-Dimensionalen Darstellung der Wände welches mittels dem Modul „Vpython“ ermöglicht wird. Der Optimierungs-Algorithmus soll die Maße eines Raumes unter dem Einfluss unterschiedlicher Bedingungen und Einschränkungen berechnen. Für die Optimierung benutzen wird die „fmin_cobyla“ Funktion aus der „scipy.optimize“ Bibliothek. Für weitere mathematsiche Operationen benutzen wir das Modul „numpy“.
Visualisierung
Um einen Raum 3-Dimensional darzustellen, haben wir die einzelnen Bestandteile eines Raumen in unabhängige Objekte eingeteilt. Das einfachste Objekt ist das „Wall-Objekt“ aus dem kompliziertere Objekte wie das „Fensterwand-Objekt“ und das „Türwand-Objekt“ zusammengesetzt werden.
Wall-Objekt
from __future__ import division from __future__ import print_function from addons import dist # Distance between two points function from visual import * display(background=(0, 0, 0)) # Black Background class Wall(object): standard_wall_height = 3.0 standard_wall_thickness = 0.2 def __init__(self, position, ax, length, cl=(1,1,1)): self.position = position self.ax = ax self.cl = cl self.length = length self.box = box(pos = position, size = (length, self.standard_wall_height, self.standard_wall_thickness), color = cl, axis = ax, opacity = 0.5) self.points = [ (self.position[0] + 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[2] + 0.5 * self.standard_wall_thickness), (self.position[0] + 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[2] - 0.5 * self.standard_wall_thickness), (self.position[0] - 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[2] - 0.5 * self.standard_wall_thickness), (self.position[0] - 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[2] + 0.5 * self.standard_wall_thickness), (self.position[0] + 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[2] + 0.5 * self.standard_wall_thickness), (self.position[0] + 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[2] - 0.5 * self.standard_wall_thickness), (self.position[0] - 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[2] - 0.5 * self.standard_wall_thickness), (self.position[0] - 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[2] + 0.5 * self.standard_wall_thickness) ] if self.ax == (0,0,1): self.points = [ (self.position[2] + 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[0] + 0.5 * self.standard_wall_thickness), (self.position[2] + 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[0] - 0.5 * self.standard_wall_thickness), (self.position[2] - 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[0] - 0.5 * self.standard_wall_thickness), (self.position[2] - 0.5 * self.length, self.position[1] - 0.5 * self.standard_wall_height, self.position[0] + 0.5 * self.standard_wall_thickness), (self.position[2] + 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[0] + 0.5 * self.standard_wall_thickness), (self.position[2] + 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[0] - 0.5 * self.standard_wall_thickness), (self.position[2] - 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[0] - 0.5 * self.standard_wall_thickness), (self.position[2] - 0.5 * self.length, self.position[1] + 0.5 * self.standard_wall_height, self.position[0] + 0.5 * self.standard_wall_thickness)] for i in range(len(self.points)): self.points[i]= (self.points[i][2], self.points[i][1],self.points[i][0]) def push(self, vector): index = 0 for point in self.points: self.points[index] = (point[0] + vector[0], point[1] + vector[1], point[2] + vector[2]) index += 1 self.box.pos = (self.position[0] + vector[0], self.position[1] + vector[1], self.position[2] + vector[2]) def rotate(self, degree): index = 0 for point in self.points: self.points[index] = (self.box.pos[0]+(point[0]-self.box.pos[0]) * math.cos(radians(degree)) - (point[2]-self.box.pos[2]) * math.sin(radians(degree)), point[1], self.box.pos[2]+(point[0]-self.box.pos[0]) * math.sin(radians(degree)) + (point[2]-self.box.pos[2]) * math.cos(radians(degree))) index += 1 self.box.rotate(angle=radians(degree),axis=(0,1,0),pos=self.position) def getPoints(self): return self.points
Ein Wall-Objekt ist im Prinzip nichts anderes als ein flacher Quader, welche sich die Koordinaten seiner acht Eckpunkte merkt. Beim initiieren eines Wall-Objektes muss ein Mittelpunkt, eine Achse und eine Länge der Wand angegeben werden. Mithilfe dieser Informationen können die entsprechenden Eckpunkte ausgerechnet werden. Darüberhinaus besitzt jedes Wall-Objekt auch noch die Methoden push(), rotate() und getPoints(). Die push() und rotate() Methoden bewegen das Objekt im Raum und berechnet die neue Positionen der Eckpunkte. Dadurch ist sich jedes Wall-Objekt seinen eigenen Punkten zu jeder Zeit und in jeder Position bewusst. Die getPoints() Methode erlaubte uns die Koordinaten jeder der Eckpunkte abzurufen.
Tuerwand-Objekt
from __future__ import division from __future__ import print_function from wall import Wall from visual import * from errors import * display(background=(0, 0, 0)) # Black Background class Tuerwand(object): # Liste welche alle waende speichert walls = [] standard_wall_height = Wall.standard_wall_height def __init__(self, position, axis, xlength, color = color.white, ratio = 0.3): self.position = position self.axis = axis self.xlength = xlength self.color = color self.ratio = ratio self.structure_wall() def getPoints(self): return [wall.getPoints() for wall in self.walls] def structure_wall(self): door_width = 1.0 door_height = 2.0 remaining_length = self.xlength - door_width l_length = self.ratio * (remaining_length / (self.ratio + 1)) r_length = remaining_length - l_length # Kreation der Wand im Falle Paralell zur x-Achse if self.axis == (1, 0, 0): self.position = (self.position[0], self.position[1], self.position[2]) pos_1 = (self.position[0] + ((-self.xlength / 2) + (l_length / 2)), self.position[1], self.position[2]) pos_2 = (pos_1[0] + (0.5 * l_length) + 0.5 * door_width, pos_1[1] + (-self.standard_wall_height/2 + door_height + 0.5 * (self.standard_wall_height - door_height)), pos_1[2]) pos_3 = (pos_2[0] + 0.5 * door_width + 0.5 * r_length, pos_1[1], pos_2[2]) self.walls.append(Wall(pos_1, self.axis, l_length, self.color)) Wall.standard_wall_height = Wall.standard_wall_height - door_height self.walls.append(Wall(pos_2, self.axis, door_width, self.color)) Wall.standard_wall_height = 3.0 self.walls.append(Wall(pos_3, self.axis, r_length, self.color)) # Kration des Raumes im Falle parallel zur z-Achse elif self.axis == (0, 0, 1): self.position = (self.position[0], self.position[1], self.position[2]) pos_1 = (self.position[0], self.position[1], self.position[2] + ((-self.xlength / 2) + (l_length / 2))) pos_2 = (pos_1[0], pos_1[1] + (-self.standard_wall_height/2 + door_height + 0.5 * (self.standard_wall_height - door_height)), pos_1[2] + (0.5 * l_length) + 0.5 * door_width) pos_3 = (pos_1[0], pos_1[1], pos_2[2] + 0.5 * door_width + 0.5 * r_length) self.walls.append(Wall(pos_1, self.axis, l_length, self.color)) Wall.standard_wall_height = Wall.standard_wall_height - door_height self.walls.append(Wall(pos_2, self.axis, door_width, self.color)) Wall.standard_wall_height = 3.0 self.walls.append(Wall(pos_3, self.axis, r_length, self.color)) else: raise AxisError("Illegal Axis; Only X and Y Axis with the value 1 supported")
Unser Tuerwand-Objekt wird aus drei Wall-Objekten zusammengesetzt. Da jedes Tuerwand Objekt aus Wall-Objekten zusammengesetzt ist, kann man über die „Wall.getPoints()“ Methode, auch alle Koordinaten der Eckpunkte des Tuerwand-Objektes abrufen. Beim innizieeren eines Tuerwand-Objektes muss die Position, die Achse und die Länge angegeben werden. Falls man die Position der Türen beeinflussen will, gibt es die Möglichkeit ein „ratio“ Parameter anzugeben. Der ratio-parameter gibt an, in welchem Verhältnis die Wand links von der Tür, zu der Wand rechts von der Tür steht. Würde man zum Beispiel eine Tür in der Mitte der Wand haben wollen, so müsste man ein ratio von 1 angeben. Wir haben uns für ein standard-ratio von 0.3 entschieden, für eine möglichst natürlich wirkende Türposition. Die methode structure_wall() ist verantwortlich für das kreeiren der einzelnen Wände in den korrekten Positionen und setzt das gesamte Tuerwand-Objekt zusammen.
Fenster-Objekt
from __future__ import division from __future__ import print_function from visual import * from wall import Wall display(background=(0, 0, 0)) class Fensterwand(object): standard_wall_height = Wall.standard_wall_height walls = [] def __init__(self, length, position, axis, window_size = 1/5, color = color.white, ratio = 1): self.length = length self.ratio = ratio self.position = position self.axis = axis self.window_size = window_size self.color = color self.structure_wall() def getPoints(self): return [wall.getPoints() for wall in self.walls] def structure_wall(self): fhr = 1 #Fensterhoeheratio fb = (Wall.standard_wall_height - fhr) / 2 #Fensterbankhoehe window_width = self.window_size * self.length window_height = (1/2) * self.standard_wall_height - fb remaining_length = self.length - window_width l_length = self.ratio * (remaining_length / (self.ratio + 1)) r_length = remaining_length - l_length if self.axis == (1, 0, 0): # Bestimmung der Positionen self.pos_1 = (self.position[0] - 0.5 * window_width - 0.5 * l_length, self.position[1], self.position[2]) self.pos_3 = (self.position[0] + 0.5 * window_width + 0.5 * r_length, self.position[1], self.position[2]) self.pos_2 = (self.position[0], self.position[1] - 0.5 * fhr - 0.5 * fb, self.position[2]) self.pos_4 = (self.position[0], self.position[1] + 0.5 * fhr + 0.5 * fb, self.position[2]) self.pos_5 = self.position # Kreeiren der Waende mit gleicher Hoehe self.walls.append(Wall(self.pos_1, self.axis, l_length, self.color)) # Kreeiren der Waende mit gleicher Hoehe self.walls.append(Wall(self.pos_3, self.axis, r_length, self.color)) Wall.standard_wall_height = fb # Kreeiren der Waende mit einer hoehe abweichend vom Standard self.walls.append(Wall(self.pos_2, self.axis, window_width, self.color)) # Kreeiren der Waende mit einer hoehe abweichend vom Standard self.walls.append(Wall(self.pos_4, self.axis, window_width, self.color)) Wall.standard_wall_height = 3.0 elif self.axis == (0, 0, 1): # Bestimmung der Positionen self.pos_1 = (self.position[0], self.position[1], self.position[2] - 0.5 * window_width - 0.5 * l_length) self.pos_3 = (self.position[0], self.position[1], self.position[2] + 0.5 * window_width + 0.5 * r_length) self.pos_2 = (self.position[0], self.position[1] - 0.5 * fhr - 0.5 * fb, self.position[2]) self.pos_4 = (self.position[0], self.position[1] + 0.5 * fhr + 0.5 * fb, self.position[2]) self.pos_5 = self.position # Kreeiren der Waende mit gleicher Hoehe self.walls.append(Wall(self.pos_1, self.axis, l_length, self.color)) # Kreeiren der Waende mit gleicher Hoehe self.walls.append(Wall(self.pos_3, self.axis, r_length, self.color)) Wall.standard_wall_height = fb # Kreeiren der Waende mit einer hoehe abweichend vom Standard self.walls.append(Wall(self.pos_2, self.axis, window_width, self.color)) # Kreeiren der Waende mit einer hoehe abweichend vom Standard self.walls.append(Wall(self.pos_4, self.axis, window_width, self.color)) Wall.standard_wall_height = 3.0 else: print("Error: Invalid axis")
Ähnlich wie bei dem Tuerwand-Objekt, wird das Fensterwand-Objekt aus 4 unterschiedlichen Wall-Objekten zusammengesetzt. Beim initiieren eines Tuerwand-Objektes muss die Länge, Position und die Achse angegeben werden. Optional kann auch noch die Größe des Fensters wie auch seine Position mittel des ratio-Parameters angepasst werden.
Raum-Objekt
from tuerwand import Tuerwand from fensterwand import Fensterwand from errors import * from visual import * from sys import exit display(background=(0, 0, 0)) # Black Background class Room(object): def __init__(self, position=(0,0,0), xlength=10, zlength=5, door_pos=0, window_pos=0, color=color.white, door_ratio=0.3): self.position = vector(position) self.xlength = xlength self.zlength = zlength self.color = color self.door_pos = door_pos self.window_pos = window_pos self.door_ratio = door_ratio self.structure_room() def getPoints(self): return [wall.getPoints() for wall in [self.walls[key] for key in self.walls]] walls = {} def structure_room(self): self.zlength = self.zlength - Wall.standard_wall_thickness # Checks if fensterwand pos is equal to tuerwand pos if self.door_pos == self.window_pos and self.door_pos != 0: print("Error: Window position is equal to Door position") exit(0) # Marking the middle point sphere(radius = 0.3, color = color.red, pos = self.position) # Spawn parallel z-walls for i in range(2): self.walls[i] = Wall(self.position, (1, 0, 0), self.xlength - Wall.standard_wall_thickness, self.color) # Spawn parallel x-walls for i in range(2, 4): self.walls[i] = Wall(self.position, (1, 0, 0), self.zlength + Wall.standard_wall_thickness, self.color) # Push Parallel to x-axis for key in range(1, 3): self.walls[key - 1].push((0,0,(-1)**key * (0.5 * self.zlength))) # Push Parallel to z-axis for key in range(3, 5): self.walls[key - 1].push(((-1)**key * (0.5 * self.xlength),0,0)) self.walls[key - 1].rotate(90) # Checks if a Door as been spawned door = False # Door parallel to x-axis for door_p in range(1, 3): if self.door_pos == door_p: temp_length = self.walls[door_p - 1].length pos_temp_wall = self.walls[door_p - 1].box.pos self.walls[door_p - 1].box.visible = False del self.walls[door_p - 1].box self.walls[door_p - 1] = Tuerwand(position=pos_temp_wall,axis=(1, 0, 0),xlength=temp_length,color=self.color,ratio=self.door_ratio) door = True break # Door parallel to z-axis for door_p in range(3, 5): if self.door_pos == door_p: temp_length = self.walls[door_p - 1].length pos_temp_wall = self.walls[door_p - 1].box.pos self.walls[door_p - 1].box.visible = False del self.walls[door_p - 1].box self.walls[door_p - 1] = Tuerwand(position=pos_temp_wall,axis=(0, 0, 1),xlength=temp_length, color=self.color, ratio=self.door_ratio) door = True break if not door and self.door_pos != 0: print("Error: No valid door parameter") # Checks if Windows have been spawned windows = False # Winows parallel to x-axis for window_p in range(1, 3): if self.window_pos == window_p: temp_length = self.walls[window_p - 1].length pos_temp_wall = self.walls[window_p - 1].box.pos self.walls[window_p - 1].box.visible = False del self.walls[window_p - 1].box self.walls[window_p - 1] = Fensterwand(length=temp_length, position=pos_temp_wall, axis=(1,0,0)) windows = True break # Winows parallel to z-axis for window_p in range(3, 5): if self.window_pos == window_p: temp_length = self.walls[window_p - 1].length pos_temp_wall = self.walls[window_p - 1].box.pos self.walls[window_p - 1].box.visible = False del self.walls[window_p - 1].box self.walls[window_p - 1] = Fensterwand(length=temp_length, position=pos_temp_wall, axis=(0,0,1)) windows = True break if not windows and self.window_pos != 0: print("Error: No valid window parameter")
Nachdem die Grundbausteine eines Raumes etabliert sind, werden sie im Raum-Objekt zusammengefasst. Beim initiieren eines Raum-Objekts muss eine x-Länge des Raumes, eine y-Länge des Raumes und jeweils eine Angabe welche Wand ein Tüerwand-Objekt sein soll bzw. ein Fensterwand-Objekt. Die structure_room() Methode kreeirt die jeweiligen Objekt-Bausteine in den entsprechenden Positionen.
Die Wall.getPoints() Methode dient als Schnittstelle zwischen der Visualisierungs Gruppe und der Triangulisierungs Gruppe.
Main-Visualisierung
Nachdem wir uns auf ein Layout für die Grundrisse der Wohnung geeinigt haben, bemerkten wir schnell das unsere ursprüngliche Idee jeden einzelnen Raum als eigenes Raum-Objekt zu initiieren, ineffizient und unpraktisch war. Angenommen alle 5 Räume (4 Räume + Gang) würden als eigene Raum-Objekte passend im Koordinatensystem nebeneinander initialisiert werden, so würde in bestimmten Fällen mehr als eine Wand die selben Räume trennen, wodurch Wände unnötig überlappen. Deshalb entschieden wir uns den Raum aus den einzelnen Raum-Bausteinen zusammenzusetzen. So besteht ein Raum mit zwei Fenstern und einer Tür aus zwei Fensterwand-Objekte, ein Tuerwand-Objekt und ein Wall-Objekt.
Die getStructure() Funktion, ruft mithilfe eine Moduls namens „subprocesses32“ den Algorithmus auf (siehe unten) welche eine Datenstruktur liefert, in der die optimierten Maße der Wohnung gespeichert sind. Diese wird anschließend ausgelesen in der structureBuilding Funktion und die einzelnen Wände werden entsprechend initialisiert.
Mithilfe der „Pickle“-Bibliothek werden alle relevanten Punkte der Wohnung zusammengefasst in eine „points.pickle“ Datei. Die Datenstruktur, „points.pickle“, bestehend aus den Koordinaten aller Eckpunkte, wird anschließend von dem Triangulisierungs-Algorithmus triangulisiert.
from visual import * from raum import Room from wall import Wall from tuerwand import Tuerwand from fensterwand import Fensterwand import subprocess32 as sp import ast from pickle import Pickler def getStructure(): unparseObject = sp.check_output("python ../Algorithmus/main.py", shell=True) parsedObject = "" relevant = False for char in unparseObject: if char == '}': parsedObject += char relevant = False break if char == '{': relevant = True if relevant: parsedObject += char return parsedObject points = [] def StructureBuilding(data): Y_HEIGHT = 0.5 * Wall.standard_wall_height x_len = data[1][0][0] + data[2][0][0] z_len = data[0][0][1] + data[1][0][1] mp = (0.5 * x_len, Y_HEIGHT, 0.5 * z_len) aussenwaende = [] aw_0_0_mp = (0.5 * data[0][0][0] + 0.25 * Wall.standard_wall_thickness, Y_HEIGHT, 0) aussenwaende.append(Fensterwand(length=data[0][0][0] - 0.5 * Wall.standard_wall_thickness, position=aw_0_0_mp, axis=(1,0,0), window_size=1/2)) points.append(aussenwaende[0].getPoints()) aw_1_mp = (0, Y_HEIGHT, 0.5 * z_len) aussenwaende.append(Tuerwand(position=aw_1_mp, axis=(0,0,1), xlength=z_len + Wall.standard_wall_thickness, ratio=50)) points.append(aussenwaende[1].getPoints()) aw_2_mp = (0.5 * x_len, Y_HEIGHT, z_len) aussenwaende.append(Wall(position=aw_2_mp,ax=(1,0,0),length=x_len - Wall.standard_wall_thickness)) points.append(aussenwaende[2].getPoints()) aw_3_1_mp = (x_len, Y_HEIGHT, data[2][0][1] + 0.5 * data[2][0][1]) aussenwaende.append(Fensterwand(data[2][0][1] + Wall.standard_wall_thickness, aw_3_1_mp, axis = (0,0,1), window_size=1/2)) points.append(aussenwaende[3].getPoints()) aw_3_0_mp = (x_len, Y_HEIGHT, 0.5 * data[3][0][1]) aussenwaende.append(Fensterwand(data[3][0][1] + Wall.standard_wall_thickness, aw_3_0_mp, axis=(0,0,1), window_size=1/2)) points.append(aussenwaende[4].getPoints()) aw_0_2_mp = (data[1][0][0] + 0.5 * data[3][0][0], Y_HEIGHT, 0) aussenwaende.append(Fensterwand(length=data[3][0][0], position=aw_0_2_mp, axis=(1,0,0), window_size=1/2)) points.append(aussenwaende[5].getPoints()) aw_0_1_mp = (data[0][0][0] + 0.5 * data[4][0][0], Y_HEIGHT, 0) aussenwaende.append(Fensterwand(data[4][0][0], aw_0_1_mp, axis=(1,0,0), window_size=1/2)) points.append(aussenwaende[6].getPoints()) wall_2_0_mp = (0.5 * data[0][0][0], Y_HEIGHT, data[0][0][1]) wall_2_0 = Tuerwand(position=wall_2_0_mp,axis=(1,0,0),xlength=data[0][0][0] - Wall.standard_wall_thickness, ratio=1) points.append(wall_2_0.getPoints()) wall_2_1_mp = (data[0][0][0] + 0.5 * data[4][0][0], Y_HEIGHT, data[0][0][1]) wall_2_1 = Tuerwand(position=wall_2_1_mp,axis=(1,0,0),xlength=data[4][0][0] - Wall.standard_wall_thickness, ratio=1) points.append(wall_2_1.getPoints()) wall_3_mp = (data[1][0][0] + 0.5 * data[2][0][0], Y_HEIGHT, data[3][0][1]) wall_3 = Tuerwand(position=wall_3_mp,axis=(1,0,0),xlength=data[3][0][0] - Wall.standard_wall_thickness) points.append(wall_3.getPoints()) wall_1_mp = (data[0][0][0], Y_HEIGHT, data[0][0][1] * 0.5 + 0.5 * Wall.standard_wall_thickness) wall_1 = Wall(position=wall_1_mp,ax=(1,0,0),length=data[0][0][1]) wall_1.rotate(90) points.append(wall_1.getPoints()) wall_4_0_mp = (data[1][0][0], Y_HEIGHT, 0.5 * data[4][0][1] + 0.5 * Wall.standard_wall_thickness) wall_4_0 = Wall(position=wall_4_0_mp, ax=(1,0,0), length=data[4][0][1]) wall_4_0.rotate(90) points.append(wall_4_0.getPoints()) wall_4_1_mp = (data[1][0][0], Y_HEIGHT, data[4][0][1] + 0.5 * data[1][0][1]) wall_4_1 = Tuerwand(position=wall_4_1_mp,axis=(0,0,1),xlength=data[1][0][1] - Wall.standard_wall_thickness) points.append(wall_4_1.getPoints()) Wall.standard_wall_height = 0.2 Wall.standard_wall_thickness = z_len + 0.2 boden_pos = (mp[0], -0.1, mp[2]) boden = Wall(position=boden_pos, ax=(1,0,0), length=x_len + 0.2) Wall.standard_wall_height = 3.0 Wall.standard_wall_thickness = 0.2 StructureBuilding(ast.literal_eval(getStructure())) with open("points.pickle", 'w') as f: p = Pickler(f) p.dump(points)
Für die Optimierung bedienten wir uns der „scipy.optimize“ Bibliothek in der die Funktion „fmin_cobyla“ beinhaltet ist. fmin_cobyla minimisiert den Wert einer Funktion mittels Einschränkungen und Linearer Approxmiation (Constrained Optimization By Linear Approximation - COBYLA).
from __future__ import print_function from scipy.optimize import fmin_cobyla import numpy as np def constrains(): cons = [] eps = 0 GRUND_X = 10.0 GRUND_Y = 5.0 cons.append(lambda v:v[0] - eps) #(x0, y0, x1, y1, x2, y2, x3, y3, x4, y4) cons.append(lambda v:v[1] - eps) #(0, 1, 2 , 3, 4, 5, 6, 7, 8, 9) cons.append(lambda v:v[2] - eps) cons.append(lambda v:v[3] - eps) cons.append(lambda v:v[4] - eps) cons.append(lambda v:v[5] - eps) cons.append(lambda v:v[6] - eps) cons.append(lambda v:v[7] - eps) cons.append(lambda v:v[8] - eps) cons.append(lambda v:v[9] - eps) cons.append(lambda v: -(v[1] + v[3] - GRUND_Y)) cons.append(lambda v: -(v[9] + v[3] - GRUND_Y)) cons.append(lambda v: -(v[7] + v[5] - GRUND_Y)) cons.append(lambda v: -(v[0] + v[8] + v[6] - GRUND_X)) cons.append(lambda v: -(v[0] + v[8] + v[4] - GRUND_X)) cons.append(lambda v: -(v[2] + v[4] - GRUND_X)) cons.append(lambda v: -(v[3] - 1.25)) return cons def flaeche_0(v): return v[0] * v[1] def flaeche_1(v): return v[2] * v[3] def flaeche_2(v): return v[4] * v[5] def flaeche_3(v): return v[6] * v[7] def flaeche_4(v): return v[8] * v[9] def quadratizitaet_0(v): return ((v[0] * v[1]) / (v[0]*v[0] + v[1]*v[1])) def quadratizitaet_1(v): return ((v[2] * v[3]) / (v[2]*v[2] + v[3]*v[3])) def quadratizitaet_2(v): return ((v[4] * v[5]) / (v[4]*v[4] + v[5]*v[5])) def quadratizitaet_3(v): return ((v[6] * v[7]) / (v[6]*v[6] + v[7]*v[7])) def quadratizitaet_4(v): return ((v[8] * v[9]) / (v[8]*v[8] + v[9]*v[9])) def goldener_schnitt_0(v): g = 1 / ((1 + np.sqrt(5)) / 2) return np.power(((v[0] / v[1]) - g), 2) * np.power((v[1] / v[0]) - g, 2) def goldener_schnitt_1(v): g = 1 / ((1 + np.sqrt(5)) / 2) return np.power(((v[2] / v[3]) - g), 2) * np.power((v[3] / v[2]) - g, 2) def goldener_schnitt_2(v): g = 1 / ((1 + np.sqrt(5)) / 2) return np.power(((v[4] / v[5]) - g), 2) * np.power((v[5] / v[4]) - g, 2) def goldener_schnitt_3(v): g = 1 / ((1 + np.sqrt(5)) / 2) return np.power(((v[6] / v[7]) - g), 2) * np.power((v[7] / v[6]) - g, 2) def goldener_schnitt_4(v): g = 1 / ((1 + np.sqrt(5)) / 2) return np.power(((v[8] / v[9]) - g), 2) * np.power((v[9] / v[8]) - g, 2) def flaeche_ins(v): return -np.prod([flaeche_0(v), flaeche_1(v), flaeche_2(v), flaeche_3(v), flaeche_4(v)]) def quadratizitaet_ins(v): return -np.prod([quadratizitaet_0(v), quadratizitaet_1(v), quadratizitaet_2(v), quadratizitaet_3(v), quadratizitaet_4(v)]) def goldener_schnitt_ins(v): return -np.prod([goldener_schnitt_0(v), goldener_schnitt_1(v), goldener_schnitt_2(v), goldener_schnitt_3(v), goldener_schnitt_4(v)]) def target(v): w1 = 1.0/10.0 * 1 # Flaeche w2 = 10.0/10.0 * 0 # Quadratizitaet // * 0 dar widerspruechlich mit Goldener Schnitt w3 = 9.0/10.0 * 1 # Goldener Schnitt return (w1 * flaeche_ins(v) + w2 * quadratizitaet_ins(v) + w3 * goldener_schnitt_ins(v)) def RaumAlgo(): cons = constrains() return fmin_cobyla(target,[2.1]*10,cons)
fmin_cobyla benötigt eine Hauptfunktion welche minimiert wird und Bedingungen welche die Hauptfunktion entsprechend einschränkt.
Die Funktion „constraints()“ liefert eine Liste aller einschränkenden Bedingungen. In der constraints() Funktion wird, unter anderem, dass Intervall festgelegt in der fmin_cobyla optimieren darf. Zum Beispiel darf keiner der Räume vertikal größer werden als die Vertikal angebene Grundfläche oder der Gang darf nicht kleiner werden als die Gang Mindestgröße, bei uns festgelegt auf 1.25 Einheiten.
Unsere Hauptfunktion „target()“ wird zusammengesetzt aus drei Optimierungskriterien bezogen auf jeden einzelnen Raum des Grundrisses. Die Optimierungskriterien bestehen aus der Größtmöglichen Fläche (Maximal mögliche Größe für jeden Raum), bestmöglichste Quadratizität (Bestmögliches Quadratisches Seitenverhältnis des Raumes) und dem goldenen Schnitt (Seitenverhältnis des goldenen Schnittes). Für jeden Raum existiert eine Funktion welche jedes dieser Kriterien für den entsprechenden Raum berechnet. Diese werden anschließend in separaten Funktionen zusammengefasst und in der Hauptfunktion „target()“ mit anpassbarem Gewicht, der „RaumAlgo()“ Funktion übergeben. „RaumAlgo()“ ruft den Optimierungs Algorithmus fmin_cobyla auf und gibt dessen Ergebnisse zurück.
Die Datei „main.py“ in dem Algorithmus Verzeichniss dient dem Programm als Kontaktstelle zwischen dem Algorithmus und der Visualisierung.
from __future__ import print_function import numpy from algo import RaumAlgo import copy struktur = { 0 : [(1, 2), (4, 3)], 1 : [(4, 0), (0,4), (2, 3)], 2 : [(3, 0), (4, 1), (1, 1)], 3 : [(4, 1), (2, 2)], 4 : [(0, 1), (1, 2), (2, 3), (3, 3)] } def getListFromNumpyArray(): raumAlgoListe = list(RaumAlgo()) filtered = [] for char in raumAlgoListe: if type(char) is numpy.float64: filtered.append(char) return filtered def createStructure(): structure = copy.deepcopy(struktur) data_raw = getListFromNumpyArray() data = [(data_raw[0], data_raw[1]), (data_raw[2], data_raw[3]), (data_raw[4], data_raw[5]), (data_raw[6], data_raw[7]), (data_raw[8], data_raw[9])] for room in range(5): structure[room].insert(0, data[room]) return structure print(createStructure())
Endergebniss
Ruft man die Datei „main.py“ in dem Visualisierungs Algorithmus auf, so erhält man eine Optimierte Visualisierung des Grundrisses.