Википедию цитировать не буду. Почему я выбрал купол в качестве дома?
- При равном объеме площадь поверхности сферы будет меньше, чем у любой другой формы. Это положительно влияет как на материалоемкость, так и на энергозатраты при эксплуатации.
- Мне нравится как выглядит сфера.
- Это интересный инженерный проект, в каком-то смысле даже вызов. Это сложно, трудно и потому весело!
Как это геодезические сферы устроены вообще? С первого взгляда кажется, что это какое-то переплетение рёбер и уловить систему сложно. В этой заметке попробуем разобраться.
В основе таких конструкций лежит икосаэдр или октаэдр. В общем правильный многогранник.
В моем случае это был именно икосаэдр и чаще используют его. Далее берем одну грань и заменяем ее на несколько треугольников, вершины которых лежат на сфере, центр которой совпадает с центром икосаэдра. Звучит не слишком складно. Отвлечемся.
Есть замечательный калькулятор www.acidome.ru который позволяет в реальном времени покрутить геодезик. Берем в качестве основы icosahedron, ставим частоту 1, часть сферы 1/1.
Это и есть наш основной икосаэдр. Частота это на сколько частей мы разобьем каждое ребро икосаэдра. Ставим 3,4, 5 и ничего становится непонятно. Переключаем в режим кровли и ищем пятиугольники. В тех местах, где у нас вершина икосаэдра — будет пятиугольник. Между тремя пятиугольниками грань икосаэдра.
Если внимательно смотреть на геодезик и знать, что искать (обычно пятиугольник), то становится видна регулярность структуры. На Биосфере в Монреале при должном усердии можно найти пятиугольники и посчитать частоту. Частота у нас равна количеству ребер между двумя пятиугольниками.
Сами “большие” треугольники, с вершинами на вершинах икосаэдра также имеют структуру. На acidome в режиме кровли это видно по цвету. Треугольники расположены симметрично относительно центра “большого” треугольника. Количество их типов меньше общего числа треугольников. В случае с частотой 5 уникальных треугольников 9.
В процессе проектирования дома я столкнулся с задачей постройки сферы в Dynamo. Это такой инструмент, который позволяет научить Autosedk Revit работать со сложными формами. Такая среда визуального программирования.
Погуглив я даже нашел скетч, который в Dynamo строил геодезическую сферу. Сферу то он строил, да не ту.
Дело вот в чем. Когда мы берем одно ребро икосаэдра и делим его на мелкие треугольники — сделать это можно несколькими способами. В acidome за это отвечает переключатель “метод разбиения”.
Найденный скетч строил сферу методом равных хорд. Что это значит? Мы берем большой треугольник икосаэдра, каждое его ребро делим на нужное нам количество частей, соединяем точки на ребрах между собой и получаем плоскую сетку из треугольников. Затем эту сетку мы проецируем на сферу. Все бы хорошо, но сами эти треугольники достаточно сильно отличаются по размеру. Центральный больше всех. Оно и понятно, центр “большого” треугольника у нас на максимальном расстоянии от сферы. Это плохо, так как в этом случае сложнее оптимизировать расход материалов. Будет больше отходов.
Другой метод разбиения (равными дугами) предполагает, что мы строим поверх “большого” треугольника дуги и уже их делим на равные части. Подход отличается, простой проекцией не обойтись.
Скетч не подходил. Я попытался его исправить и в итоге мне пришлось нырнуть в это дело с головой.
Как оказалось помимо визуальной среды Dynamo имеет встроенный Python. С этим языком я ранее не сталкивался, но где наша не пропадала? В конце концов это просто инструмент.
Дальше будут кусочки кода, прошу обратить внимание, что это мой hello world в python, а целью было не построить максимально эффективное и производительное решение, а построить нужную сферу.
Метод равных дуг.
Берем одну из граней икосаэдра и из углов этого треугольника строим дуги.
for k, edge in enumerate(curves):
# Строим дугу
arc = Arc.ByCenterPointStartPointEndPoint(sphere_center, edge.EndPoint, edge.StartPoint)
# Точки углов сохраняем, они нам пригодятся
result_points.append(edge.EndPoint)
result_points.append(edge.StartPoint)
# Иногда дугу строит "вокруг", тогда меняем местами начало и конец
if arc.SweepAngle > 90:
arc = Arc.ByCenterPointStartPointEndPoint(center_point, edge.StartPoint, edge.EndPoint)
# Делим дугу на равные части
arc_points = Arc.PointsAtEqualSegmentLength(arc, n)
else: arc_points = list(reversed(Arc.PointsAtEqualSegmentLength(arc, n)))
# Сохраняем и эти точки
for p in arc_points:
result_points.append(p)
Затем дуги делим на равные части и соединяем точки на дугах новыми дугами. У всех дуг один центр — центр сферы. Точки соединяем не все со всеми, а одноименные. На картинке оно выглядит попроще, чем в коде.
for edge_index, point_list in enumerate(points):
edge_arcs = []
for point_index, point in enumerate(point_list):
next_edge_index = edge_index + 1
if len(points) == next_edge_index:
next_edge_index = 0
end_point_index = n - point_index - 2
arc = Arc.ByCenterPointStartPointEndPoint(center_point, points[next_edge_index][end_point_index], point)
if arc.SweepAngle > 90:
arc = Arc.ByCenterPointStartPointEndPoint(center_point, point, points[next_edge_index][end_point_index])
arc_points_count = n - point_index - 1;
pp = Arc.PointsAtEqualSegmentLength(arc, arc_points_count)
for po in pp:
on_arc_points.append(po)
edge_arcs.append(arc)
edges_arcs.append(edge_arcs)
Опа, а дуги то не пересекаются! Не слишком беглое гугление вывело меня на книгу, которая подтвердила мои предположения о том, что нужно в качестве вершины ребра геодезика использовать центр треугольника, образованного пересечением дуг. Также курил исходники acidome, но не помню нашел ли там этому подтверждение. Помню, что было интересно.
Центры надо как-то найти. Это центр треугольника и это не сложно, но нужно было понять где же у нас в ворохе точек эти треугольники. Мне показалось самым простым вариантом соединять ближайшие друг к другу точки.
for point in on_arc_points:
distance = []
# Считаем расстояние от каждой точки до других.
for p2 in on_arc_points:
distance.append(point.DistanceTo(p2))
distance.sort()
# Берем три ближайшие
three_points = []
for p2 in on_arc_points:
if point.DistanceTo(p2) <= distance[2]:
three_points.append(p2);
# Строим треугольник
poly = Polygon.ByPoints(three_points)
# Берем его центр. Эту точку складываем к тем, что собирали ранее
result_points.append(poly.Center())
Теперь нам нужно соединить между собой собранные на разных этапах точки, которые и являются вершинами ребер геодезической сферы. На картинке эти точки видно хорошо, но вот когда они в массиве — все сложнее. Было несколько вариантов, но так как задача была с наименьшими трудозатратами получить рабочий скрипт, вышло вот это:
# Дальше какая-то магия с поиском какие точки из облака у нас составляют треугольники, которые грани геосферы
points = dict()
for i, point in enumerate(projected):
points[i] = dict()
points[i]['point'] = point
points[i]['id'] = i
points[i]['distance'] = dict()
for c, p2 in enumerate(projected):
points[i]['distance'][c] = point.DistanceTo(p2)
max_dist = 0
i = 0
for i, point in points.items():
max_distance = max(point['distance'].values())
if max_distance > max_dist:
root_point = i
max_dist = max_distance
row = dict()
row[root_point] = points[root_point]
del points[root_point]
surfaces = []
while len(row):
#for x in range(0, 2):
next_row = dict()
for id, item in row.items():
point = closest_point(points, id)
if point is not None:
tmp = points[point]
del points[point]
point2 = closest_point(points, id)
points[point] = tmp
if point2 is not None:
surfaces.append(Surface.ByPerimeterPoints([item['point'], points[point]['point'], points[point2]['point']]))
next_row[point] = points[point]
next_row[point2] = points[point2]
for id, item in next_row.items():
point = closest_point(row, id)
if point is not None:
tmp = row[point]
del row[point]
point2 = closest_point(row, id)
row[point] = tmp
if point2 is not None: surfaces.append(Surface.ByPerimeterPoints([row[point]['point'], row[point2]['point'], item['point']]))
row.clear()
for id, po in next_row.items():
if po['id'] in points:
del points[po['id']]
if po['id'] not in row:
row[po['id']] = po
face_triangles = surfaces
Сегмент готов. Наверное существует какой-то правильный путь для решения этой задачи, но я проложил свой.
Дальше сегмент разворачивается, несколько раз копируется копируется и получается полная сфера. Вот один из поворотов:
v = Vector.ByTwoPoints(sphere_center, curves[0].StartPoint)
for face_triangle in face_triangles:
geodesic_sphere.append(Geometry.Rotate(face_triangle, sphere_center, v, 72))
Скриптик вышел страшненький, я его пару раз переписывал, так как были проблемы с экспортом в Revit. Думал, что проблемы с построением. В итоге на форуме Dynamo индус подсказал украинцу и все удалось!
Теперь можно строить сферу любой частоты и любого диаметра. Сравнение размеров с результатами acidome показало, что все сходится с высокой точностью. Повторяемость это хорошо.
Также я занялся оптимизацией размеров с целью минимизации обрезков. Так как все размеры были у меня на руках это было не так трудно. В итоге радиус сферы получился 5,65 метров при частоте 5. Такие размеры позволяют мне достаточно эффективно использовать материалы шириной 125 см. Такую ширину имеют листы OSB, листового металла, утеплителя, гипсокартона. При хорошей оптимизации количество обрезков минимально. Наилучших результатов можно добиться путем расчета раскладок треугольников на материале, но этим я не занимался.
Дальше было проще, так как Revit съел сложную форму и позволил с ней работать примерно с тем же успехом, что и с квадратно-параллельной.
Конечно, трудности на этом не закончились, но это уже совсем другая история.