Работа с геометрией в Python

Автор: Danil Nagy. Перевод: Дмитрий Булка. Оригинал: https://medium.com/generative-design/working-with-geometry-in-python-a256de7bb1b1

Основной язык Python очень ограничен в функциональности, будучи ограниченным базовой алгеброй, структурами управления потоками и функциями манипулирования данными, с которыми мы работали в предыдущих трех разделах. Это делается намеренно, чтобы сохранить основной язык Python как можно более легким и быстрым. Чтобы расширить функциональность Python до более продвинутых применений, мы полагаемся на ряд внешних библиотек, которые определяют объекты и методы, полезные для конкретных задач. Чтобы использовать специализированные функции этих библиотек, мы должны сначала убедиться, что они установлены на нашем компьютере, а затем нам нужно импортировать их в наш сценарий. Некоторые очень распространенные библиотеки, такие как random, предустановлены вместе с Python, но все равно должны быть импортированы в каждый скрипт, в котором мы хотим его использовать. Другие библиотеки, такие как numpy, должны быть установлены отдельно, прежде чем их можно будет импортировать в наши скрипты. Чтобы мы могли работать с геометрией в Python, Rhino и Grasshopper предоставляют несколько предустановленных библиотек, которые мы рассмотрим в этом разделе.

Работа с библиотеками

Три основные библиотеки, предоставляемые Rhino / Grasshopper, которые позволяют Python получать доступ к геометрическим типам данных и операциям:

  1. ghpythonlib.components — позволяет напрямую ссылаться на компоненты Grasshopper в коде Python
  2. Rhino.Geometry — позволяет получить доступ ко всем командам и типам данных в основной библиотеке Rhinocommon'а
  3. rhinoscriptsyntax — это обертка над Rhino. Библиотека геометрии, которая предоставляет аналогичные команды и функциональные возможности Rhinoscript

Примечание:библиотека ghpythonlib доступна только через нод GHPython, начиная с версии 0.6.0.3, поэтому, если вы используете более старую версию плагина, эта библиотека может быть недоступна. Вы можете скачать последнюю версию с сайта http://www.food4rhino.com/app/ghpython.

Давайте посмотрим, как каждый из них работает на простом примере. Мы создадим простой скрипт Python, который возьмет точку в качестве входных данных и создаст круг с этой точкой в качестве центра и радиусом 2. Определение Grasshopper состоит из нода Python с одним входом и одним выходом, нода Pt, подключенного к входу, и нода Panel, подключенного к выходу, чтобы мы могли видеть результаты.

Ноды Pt, Python и Panel на Холсте

Дважды щелкните на ноде Python, чтобы открыть редактор сценариев Python. Сначала давайте посмотрим, как точка входа представлена в Python. Мы можем увидеть это, напечатав входное значение и используя функцию Python type () для печати его типа. Введите эти строки в сценарий и нажмите кнопку "Тест", чтобы увидеть результаты:

print x

print type(x)

Вы должны увидеть нечто в этом роде:

ac7825d2-24b0-4225-b52a-f04a3daa0849

<тип 'Guid'>

Подождите секунду, разве мы не ввели точку — почему мы получаем этот "Guid"?

По умолчанию любая геометрия, вводимая в нод Python, вводится не как сама геометрия, а как ссылка на геометрию в среде Grasshopper. Эта ссылка хранится в уникальном идентификационном номере геометрии, текстовой строке, называемой "Guid". Это может создать проблемы для большинства геометрических функций, которые ожидают фактических геометрических данных вместо ссылки "Guid".

Мы можем исправить это, сообщив Python, какие именно данные мы вводим, используя "подсказки типа". Чтобы задать тип входных данных, щелкните правой кнопкой мыши на входе " x " нода Python, перейдите в раздел "Type hint (подсказка типа)" и выберите пункт Point3d (3D-Точка). Меню "Type hint" показывает все типы геометрии, поддерживаемые Grasshopper. Когда мы указываем, что входные данные имеют тип Point3d, Python автоматически преобразует ссылку Guid в фактическую геометрию Точки, чтобы мы могли использовать ее с геометрическими функциями в нашем скрипте.

Type hint (подсказка типа)

Теперь, когда мы правильно определили тип геометрии, которую мы вводим, мы можем начать строить наш простой скрипт. Мы начнем с импорта компонентов библиотеки ghpythonlib.components. Удалите предыдущие две строки и введите текст на первой строке:

import ghpythonlib.components as ghcomp

Это позволит импортировать часть компонентов основной библиотеки ghpythonlib в наш скрипт, чтобы мы могли использовать его методы для работы с геометрией. Мы используем синтаксис import ... as ..., чтобы дать библиотеке более короткое ключевое слово, которое сэкономит нам ввод текста и сделает скрипт чище. Теперь, когда мы используем библиотеку, мы можем ссылаться на нее с помощью "ghcomp", а не вводить полный "ghpythonlib.componentsс" каждый раз.

Библиотека ghpythonlib.components содержит методы, которые копируют поведение каждого нода в Grasshopper. Каждый метод ожидает то же количество и тип входных данных, что и его эквивалент нода Grasshopper, и возвращает те же выходные данные. Если нод имеет более одного выхода, то возвращаемым будет список, длина которого равна количеству выходов.

Давайте используем библиотечный метод Circle (Окружность)чтобы создать круг, основанный на центральной точке и радиусе, точно так же, как нод "Circle" в Grasshopper. На следующей строке введите:

a = ghcomp.Circle(x, 2)

Как и ожидалось, это создает круг радиусом 2, центрированный на точке, подаваемой на вход x.

Circle

Теперь давайте сделаем то же самое с библиотекой Rhino.Geometry. Ниже первой строки импорта введите:

import Rhino.Geometry as rh

Эта строка импортирует часть Geometry основной бибилиотеки Rhino и присваивает ей ключевое слово "rh". Теперь мы можем изменить строку кода, создающую окружность на:

a = rh.Circle(x, 2)

Если вы запустите скрипт, то увидите, что результат будет точно таким же — окружность с центром в нашей входной точке радиусом 2.

Наконец, давайте рассмотрим тот же пример с использованием библиотеки rhinoscriptsyntax. Мы можем импортировать библиотеку, набрав текст:

import rhinoscriptsyntax as rs

И воспользоваться библиотечным методом .AddCircle(), позволяющим создать новый круг на основе нашей входной точки и радиуса.

Теперь мы видели три различных библиотеки, которые позволяют вам работать с геометрией в Python, так какой же из них вы должны пользоваться? В конечном счете, все три делают в основном одно и то же и создают одну и ту же точную геометрию, но каждая из них имеет определенные преимущества и ограничения:

Одна из главных трудностей при начале работы с такими библиотеками, как rhinoscriptsyntax или Rhino.Geometry — это знание точного синтаксиса каждого доступного геометрического метода, того, что нужно передать для входных данных, и того, что возвращается, чтобы получить ожидаемое от каждого из них. В Grasshopper вы можете легко увидеть, какие ноды доступны вам, просматривая параметры на панели инструментов. Вы также можете увидеть ожидаемые входы и выходы каждого нода, посмотрев на порты нода. С кодом это немного сложнее, потому что нет графического интерфейса ни для одного из методов. Итак, как мы узнаем, какие методы доступны и как их использовать?

Одна из главных трудностей при начале работы с такими библиотеками, как rhinoscriptsyntax или Rhino.Geometry — это знание точного синтаксиса каждого доступного геометрического метода, того, что нужно передать для входных данных, и того, что возвращается, чтобы получить ожидаемое от каждого из них. В Grasshopper вы можете легко увидеть, какие ноды доступны вам, просматривая параметры на панели инструментов. Вы также можете увидеть ожидаемые входы и выходы каждого узла, посмотрев на порты узла. С кодом это немного сложнее, потому что нет графического интерфейса ни для одного из методов. Итак, как мы узнаем, какие методы доступны и как их использовать?

Лучший способ—это поиск по документации каждой библиотеки, которая содержит полное описание каждого объекта, реализуемого библиотекой, и ее методов. Вы можете найти документацию по этим двум библиотекам здесь:

На практике поиск по полным наборам документации может быть трудным и запутанным для тех, кто только начинает работу, поэтому редактор сценариев Python предоставляет два инструмента, которые облегчают поиск доступных методов в библиотеке и их использование.

Во-первых, это функция автозаполнения, которая дает вам подсказки о том, какие методы доступны в библиотеке при вводе кода. Возможно, вы уже заметили это, когда писали строки выше. Давайте наберем строку

a = rh.Circle(x, 2)

снова символ за символом, чтобы увидеть, как это работает. Помните, что "rh" — это ключевое слово, представляющее Библиотеку Rhino.Geometry, а символ "." — это способ доступа Python к методам объекта или библиотеки. Как только вы наберете тексте символ "." появится окно со списком всех методов, доступных в этой библиотеке. Это также будет работать, если у вас есть экземпляр объекта и где вы пытаетесь получить доступ к его методам. По мере продолжения набора текста всплывающий список будет автоматически прокручиваться вниз до той части, которую вы вводите, и выделять наилучший метод сопоставления. Как только вы увидите выделенный метод, вы можете нажать кнопку "Enter" или дважды щелкнуть по имени, чтобы ввести имя метода в скрипт.

Метод

Следуя за именем метода, вы создаете набор скобок, в которые передаете входные данные метода. Как только вы введете первую скобку "(", окно Python автоматически загрузит документацию этого метода в окна результатов, которые расскажут вам, какие входные данные ожидает метод и какие выходные данные он генерирует.

Скобка

В случае метода .Circle() вы можете видеть, что он на самом деле поддерживает множество различных комбинаций входных данных. В Python это называется "перезагрузкой" (overloading) метода и позволяет одному методу делать разные вещи на основе различных комбинаций входных данных. В этом случае он позволяет нам создавать окружности несколькими различными способами, такими как: на основе центра и радиуса или на основе 3 точек. Перезагрузка — еще одно преимущество использования библиотеки Rhino.Geometry над библиотекой ghpythonlib.components. Вместо того чтобы запоминать 7 различных нодов для создания кругов в Grasshopper, у нас есть один метод .Circle(), который может делать круги по-разному, основываясь на тех входных данных, которые мы ему даем.


Работа с наборами данных

До сих пор мы видели, как мы можем определить геометрию в Python на основе одного входного сигнала. Но что делать, если мы хотим работать с входными данными, содержащими несколько значений? Давайте расширим наше определение, чтобы увидеть, как это работает.

Вместо одной точки мы теперь создадим сетку точек, используя два компонента Series для создания двух списков по 5 значений в каждом, и подключим эти списки как компоненты x и y нода Pt. Чтобы получить сетку, нам нужно привить один из списков чисел, что мы можем сделать наикратчайшим путём, щелкнув правой кнопкой мыши на выходе второго нода Series (Серия) и выбрав Graft (Привить Дерево данных). Мы также сгладим результирующий набор точек, щелкнув правой кнопкой мыши на выходе нода Pt (Точка) и выбрав пункт Flatten (Обрубить Дерево данных). Давайте также уменьшим радиус окружности до 0.4, чтобы нам было легче видеть круги.

Flatten

Существует два основных способа обработки многозначных входных данных с помощью нода Python. По умолчанию нод Python действует так же, как и любой другой нод в Grasshopper. Это означает, что при подключении составного потока данных к любому входу узла нод будет выполняться один раз для каждого значения в потоке. Вот как сейчас работает наш нод. Вы можете видеть, что в скрипте мы ссылаемся только на одну точку, и нод фактически выполняется 25 раз, чтобы создать круг для каждой точки в потоке.

Второй способ обработки составных потоков данных — это перенос их непосредственно в нод Python в виде списка. В этом случае нод будет выполняться только один раз, и вы можете работать с различными значениями непосредственно в скрипте. Чтобы включить этот способ ввода, вам нужно щелкнуть правой кнопкой мыши на имени ввода в ноде Python и выбрать List Access (Подача Списком). Теперь все 25 точек занесены в наш скрипт в виде списка Python, поэтому нам нужно изменить наш код, чтобы сделать цикл по всем точкам и создать круг для каждой из них. Мы также должны определить вывод " а " как список и использовать метод .append(), который позволяет добавлять круги в этот список по мере их создания.

a = []

for pt in x:

a.append(rh.Circle(pt, 0.4))

List Access

Итак, какую из этих стратегий следует использовать при работе с составными потоками данных?

Для простых задач, где вы хотите иметь дело только с одним значением за один раз, часто проще использовать режим Item Access (Подача Элементом) и позволить Grasshopper обрабатывать циклы. Однако для более сложных задач часто проще привести данные сразу в режим List Access (Подача Списком) и использовать циклы и условные обозначения Python для непосредственного обращения с ними. Часто это позволяет обойти сложную древовидную структуру данных Grasshopper, которая может быть запутанной и неинтуитивной для сложных задач, связанных со сложными структурами данных.

Однако что делать, если наши данные уже находятся в древовидной структуре в Grasshopper, и нам нужно поддерживать эту структуру в нашем скрипте? Можем ли мы импортировать дерево данных непосредственно в наш скрипт Python и работать с ним там?


Работа с Деревьями данных в Python

Работа с древовидной структурой данных Grasshopper в Python добавляет дополнительную сложность, и ее следует избегать, если это вообще возможно. Если нам нужно работать только с одним значением в дереве одновременно, мы можем использовать режим Item Access (Подача Элементом), и Grasshopper будет поддерживать ту же самую структуру дерева данных в выходных данных (вы можете попробовать это, вернувшись к нашей реализации Item Access выше и избавившись от ярлыка Flatten (Срубить Дерево данных) в выходных данных Pt). Вы также можете срубить деревья перед вводом их в Python и использовать режим List Access (Подача Списком) для работы с ними непосредственно в виде списков Python, как мы делали выше.

Однако, если вам абсолютно необходимо иметь дело со структурой дерева данных непосредственно в Python, вы можете сделать это, изменив входные данные на Tree Access (Подача Деревом данных) и приведя структуру дерева данных непосредственно в Python. Давайте посмотрим, как мы можем работать с этими данными, внося некоторые изменения в наш скрипт круга. Давайте уберем ярлык Flatten (Срубить Дерево данных) на выходе Pt нода и изменим вход x нода Python на режим Tree Access.

Tree Access

Это приведет центральные точки в наш скрипт в виде дерева данных с 5 ветвями по 5 точек в каждой. Данные теперь представлены в Python в виде специального типа, называемого "DataTree" (мы можем увидеть это, используя функцию type () в Python и печатая результаты).

print type(x)

Этот тип данных имеет несколько свойств и методов, которые позволяют работать с древовидной структурой и получать доступ к данным в различных ветвях.

Используя эти методы, мы можем модифицировать наш скрипт для работы непосредственно с данными дерева. Сначала мы создаем цикл для перебора всех ветвей дерева (мы используем range () для создания списка индексов от 0 до количества ветвей) и перебираем их с помощью переменной i.

for i in range(x.BranchCount):

Затем мы создаем второй цикл для итерации по каждой точке, хранящейся в каждой ветви. Помните, что переменная i повторяет индекс каждой ветви, поэтому мы можем использовать её x.Branch(i) для доступа к данным в каждой ветви по одному.

for pt in x.Branch(i):

Что делать, если мы также хотим вывести наши результаты в формате дерева данных? Опять же, это добавляет дополнительную сложность нашему скрипту, и её следует избегать, если это возможно. Но если нам действительно нужно это сделать, мы можем. В этом случае нам нужно фактически создать новый объект DataTree, который требует от нас импорта двух дополнительных объектов из основной библиотеки Grasshopper в наш скрипт. Мы можем импортировать их, написав эти две строки в верхней части нашего скрипта:

from Grasshopper import DataTree

from Grasshopper.Kernel.Data import GH_Path

Объект DataTree позволяет нам создавать новые переменные дерева данных, в то время как объект GH_Path позволяет нам создавать переменные пути, которые сообщают деревьям данных, где хранить данные. Оба этих объекта находятся в основной библиотеке Grasshopper Python и могут быть импортированы с помощью синтаксиса from ... import ... для импорта только тех конкретных объектов, которые нам нужны.

Обработка Дерева данных

Теперь нам нужно изменить вывод a, чтобы он работал как дерево данных, а не как базовый список Python. Сначала мы объявляем a как экземпляр объекта DataTree:

a = DataTree[object]()

Затем внутри первого цикла мы создаем новую переменную для представления пути к ветви, в которую мы поместим данные:

newPath = GH_Path(i)

Объект GH_Path может определять любой путь к дереву данных, принимая последовательность целочисленных значений. В этом случае мы передаем переменную i, которая хранит индекс каждой входящей ветви. Это фактически повторит структуру дерева входящих данных.

Наконец, мы используем метод дерева данных .Add(), который позволяет добавить каждый круг в дерево на основе указанного пути.

a.Add(rh.Circle(pt, .4), newPath)


Вычислительная геометрия в Python

Давайте закончим этот раздел, посмотрев, как мы можем использовать библиотеку Rhino.Geometry для выполнения вычислений на основе геометрии непосредственно в Python. Сначала давайте расширим наше определение, создав новый вход для узла Python, который принимает единственную точку, на которую ссылается документ Rhino. Не забудьте также изменить его Type hint на Point3d, чтобы Python преобразовал ее в точечную геометрию.

Теперь мы изменим наше определение окружности, чтобы вычислить радиус каждой окружности динамически на основе расстояния от ее центра до нашей опорной точки.

Аттрактор

Сначала мы пишем новую строку кода для вычисления радиуса.

radius = pt.DistanceTo(y) / 5.0

Мы используем метод .DistanceTo() центральной точки круга, хранящейся в переменной pt, чтобы вычислить расстояние до новой точки, хранящейся в переменной y, и разделить расстояние на 5.0, чтобы сделать все круги меньше. Затем мы изменим наше определение окружности, чтобы использовать этот радиус вместо предыдущего жестко закодированного:

a.Add(rh.Circle(pt, radius), newPath)

Это дает нашим кругам динамическую связь со ссылочной точкой, позволяя нам создавать различные паттерны, перемещая точку.

Различные паттерны

Этот пример показывает, как мы можем использовать библиотеку Rhino.Geometry для репликации всех возможностей Rhino и Grasshopper непосредственно с кодом Python. Хотя работа таким образом требует практики, она дает нам огромную степень контроля над нашей геометрией и позволяет нам описывать сложные проектные пространства за пределами того, что может быть непосредственно сделано в Grasshopper. В следующих нескольких разделах мы рассмотрим некоторые уникальные стратегии управления пространствами проектирования, которые возможны только при непосредственной реализации в коде.