Intro
Случайно увидел у MatSys старый пост с таким вот примером:
Мне захотелось развить эту тему и немного поэкспериментировать. А заодно попрактиковать Python и поделиться с другими дизайнерами пройденным путем. Тем, кто работает в Grasshopper, возможно, это пригодится.☺
Start
Для начала воспроизведем базовый функционал.
Нам необходимо получить все вариации комбинаций линий в заданном объекте.
Создадим объект:
Переходим к интересному, нам нужно научиться перебирать все возможные комбинации для заданного набора внутри этого объекта. Под набором я подразумеваю количество линий, которые мы должны одновременно отобразить.
В случае с “набором” из одной линии все элементарно, возможных комбинаций существует столько, сколько у нас существует линий. В данном случае это 12 комбинаций.
Наглядности ради:
Это сделано простым перебором элементов в списке из 12 линий с помощью List item. Но такой подход нам вряд ли поможет решить основную задачу.
Как же нам перебрать все возможные сочетания без повторений для всех возможных наборов в данном объекте?
Очевидно, что наборов у нас всего 12: одна линия в один момент времени, две линии в один момент времени, три линии в один момент времени …и так до 12 линий в один момент времени. С последним, впрочем, итак все ясно — для набора из 12 линий существует всего 1 сочетание— показать все эти линии одновременно ☺
Но как же вычислить количество комбинаций для других наборов?
Есть вот такая жуткая для глаза дизайнера формула, которая способна это сделать (не бойтесь, нам на самом деле не обязательно все это считать, но для понимания это полезно):
На самом деле, не все так страшно, n — это количество всех элементов, в нашем случае 12. k — это количество элементов в заданном наборе, для которого мы хотим вычислить количество сочетаний без повторений.
Мы хотим узнать результаты для наборов от 1 до 12 линий. Соответственно, нам нужно подставить в формулу вместо k числа от 1 до 12.
Моя цель в этих упражнениях рассмотреть как можно использовать Python в Grasshopper, поэтому посчитаем количество сочетаний с его помощью:
Чудесно! Но что с этим делать? ☺
На самом деле, как я уже говорил, нам не нужно вручную считать сочетания. В Python есть модуль itertools, который создан специально для таких задач. Давайте разберем как он работает.
Возьмем наш список линий и подадим его на вход Python компоненту:
Получим все возможные 12 комбинаций для набора, состоящего всего из одной линии с помощью itertools:
Ок. Давайте сделаем так, чтобы мы могли наглядно увидеть результаты для всех комбинаций в заданных наборах.
import itertoolsсombinations = list(itertools.combinations(lines,set))print len(combinations)a = combinations[index]
Теперь с этим можно делать все что угодно.
Patterns
С одним объектом все просто, но что если мы хотим сделать паттерн из кучи таких объектов?
Предположим, у нас есть сетка из шестигранников, и мы хотим в каждой из ячеек отрисовать случайную комбинацию из всех возможных сочетаний.
Если огрубить, алгоритм примерно такой:
- Берем объект из списка. (объект — набор из 12 линий)
- Определяем из скольки линий будет состоять набор для данного элемента (случайное число от 1 до 12)
- Генерируем все комбинации для заданного набора
- Определяем какую комбинацию из полученных мы хотим показать. (случайное число из количества всех возможных комбинаций для данного набора)
- Отображаем полученную комбинацию на экране
- Переходим к следующему объекту
Для начала создадим дерево из объектов.
Теперь нужно запихнуть наше дерево в Python компонент и научиться выбирать ветки с объектами. Ветки выбираются с помощью команды Branch.
object.Branch(branch_index)
Объекты мы выбирать научились. Теперь мы можем взять каждый из объектов по очереди и передать его в скрипт, чтобы скрипт произвел с ним всякие штуки и показывал нам результат.
Создадим класс Generator(), который будет содержать в себе нужные нам данные, а также уметь что-то делать.
Входящими данными для класса будут наши линии, назовем их input_hexagon, а также количество линий в наборе, для которого мы будем искать комбинации, обзовем эту переменную input_type.
сlass Generator(): def __init__(self, input_hexagon, input_type): self.type = input_type
self.hexagon = input_hexagon
self.combinations = list(itertools.combinations(self.hexagon, self.type))
self.length = len(self.combinations)
В классах есть конструктор — инициализирующая функция, которая всегда выполняется при создании объекта этого класса.
Так как при создании класса мы говорим ему с каким объектом работать и какой набор нас интересует, ничего не мешает посчитать комбинации непосредственно в конструкторе, а также хранить в нем переменную с количеством комбинаций для заданного набора. Она пригодится нам позже.
Наш класс умеет считать комбинации для заданного объекта, хранящего линии и указанного набора. Но он не умеет показывать результат! Пора его научить это делать.
Добавляем в класс функцию, которая принимает в качестве аргумента индекс комбинации из списка всех возможных комбинаций, которые мы посчитали в конструкторе.
def display_combo(self, combo_index):
self.combo = self.combinations[combo_index]
return self.combo
Давайте проверим как работает наш класс. Для этого сначала создадим объект класса и скормим ему наш объект из линий, например, первый объект в дереве (его индекс равен 0), а также набор, для которого посчитаем комбинации.
g = Generator(hexagons.Branch(0),1)
Отобразим результат, вызвав функцию класса display_combo(), сказав какую комбинацию из найденных показать. Пускай это будет первая комбинация из списка (индекс 0).
a = g.display_combo(0)
Сейчас мы вручную задали тип набора (1) и вручную задали индекс для комбинации, которую нужно отобразить (0)
Но мы хотели задавать их случайным образом. Поэтому импортируем модуль random и создадим две переменные.
import random as r #импортируем модуль
Получаем случайный тип набора от 1 до 12 линий в наборе.
random_type = r.randint(1,12) #cлучайное число от 1 до 12
Еще нам нужно случайное число для отображения комбинаций.
Мы помним, что для каждого набора количество комбинаций отличается. Именно поэтому наш конструктор класса хранит в себе переменную, содержащую значение длины списка всех комбинаций для заданного набора. Добраться до этой переменной можно так: g.length
Соответственно, раз мы знаем количество комбинаций, мы должны выбирать случайное число в этом диапазоне.
random_combo = r.randint(0, g.length-1)
Вот итоговый код. Цикл прокрутит наш генератор для каждого объекта из входящего дерева и выдаст результат:
import itertools
import random as rсlass Generator():
def __init__(self, input_hexagon, input_type): self.type = input_type
self.hexagon = input_hexagon
self.combinations = list(itertools.combinations(self.hexagon, self.type))
self.length = len(self.combinations) def display_combo(self, combo_index):
self.combo = self.combinations[combo_index]
return self.combohexagons_amount = hexagons.BranchCount #сколько всего объектов
my_list = [] #создаем пустой лист куда будем добавлять результатыfor i in range(hexagons_amount):
hexagon_new = hexagons.Branch(i) #берем объекты по очереди
random_type = r.randint(1,12) #генерируем случайный тип g = Generator(hexagon_new,random_type)#создаем объект класса random_combo = r.randint(0,g.length-1) #случайное комбо my_list.extend(g.display_combo(random_combo)) #добавляем в списокa = my_list #показываем результат
Если немножко поиграться, можно заставить наш скрипт работать не со случайными значениями, а со вполне осознанными.
В конце концов, наша вариативность позволяет использовать в качестве входящих данных, определяющих наборы, что угодно, например, изображение:
Если развивать тему, то данный подход не ограничен исключительно регулярными гексагональными сетками. На вход можно подавать различную геометрию.
Результат в виде векторных файлов не единственная причина, чтобы использовать Grasshopper для такого рода вещей. Ведь мы можем использовать тот же самый генератор, но в трехмерном пространстве.
В качестве примера я решил показать что-нибудь простое, например, генерацию ваз ☺