How to create mesh through the Blender Python API
To add custom mesh to the scene through the Blender Python API we need to do the following:
Open the “Text Editor” window.
Import the main Blender Python API module.
Any mesh consists of vertices, edges, and faces. Let’s make data blocks for them.
Our simple mesh will consist only of a single vertex. So let’s fill only the vertices data block, setting the vertex coordinates.
Next, make the mesh structure with the “new_mesh” name,
and fill it from the data blocks.
We created the mesh, but it couldn’t be added to the scene as raw. Only objects could be added to the scene. Let’s make an object with the “new_object” name and link it with the created mesh.
We created the object. But there is more to do. We need a collection in which we will add the created object. Let’s make a new collection with the “new_collection” name and place it into the master scene collection.
Now we can add our object to the scene, placing it into our collection.
After this code execution, by pressing the “Run Script” button, we will add a mesh with a single vertex to the scene.
When you login first time using a Social Login button, we collect your account public profile information shared by Social Login provider, based on your privacy settings. We also get your email address to automatically create an account for you in our website. Once your account is created, you’ll be logged-in to this account.
When you login first time using a Social Login button, we collect your account public profile information shared by Social Login provider, based on your privacy settings. We also get your email address to automatically create an account for you in our website. Once your account is created, you’ll be logged-in to this account.
Меши с Python & Blender: двумерная сетка
Привет! Понадобилось процедурно генерировать сложную модель, и пока я копал, как это делается, нашёл несколько статей от Diego Gangl, cg артиста и разработчика Блендера. Они славные для новичка, понимающего в моделировании, и не умеющего в код. Это перевод одной из них. Неточности и ошибки автора я поместил под спойлеры.
Процедурная генерация мешей даёт уйму возможностей. Можно сделать модель, чьё состояние зависит от событий в реальном мире, заняться генеративным артом, моделировать формы, основанные на матфункциях, или даже создавать контент для игр. Блендер — прекрасный выбор инструмента. Это комбайн для моделирования и анимации, и у него есть жирный и хорошо документированный Python API.
Важная заметка: сохраняйтесь чаще, особенно ковыряясь со скриптами!
Начнём-с
Меш и объект для Блендера — разные вещи. Взаимосвязь такая: создаём меш → привязываем к объекту → привязываем объект к сцене.
Стартанём с импорта bpy и пары переменных.
import bpy # Настройки name = 'Gridtastic' rows = 5 columns = 10
Переменная name используется и для объекта, и для меша. Переменные rows и columns будут определять координаты вертексов меша. Дальше настроим добавление меша и объекта. Создадим меш, потом объект с привязкой к нему меша , потом привяжем объект к сцене. Сразу создадим пустышки для вертексов и полигонов, и чуть позже накидаем в них данных.
verts = [] faces = [] # Создаём меш mesh = bpy.data.meshes.new(name) mesh.from_pydata(verts, [], faces) # Создаём объект и привязываем к сцене obj = bpy.data.objects.new(name, mesh) bpy.scene.collection.objects.link(obj) # Выделяем объект bpy.context.view_layer.objects.active = obj obj.select = True
ничего не произойдёт, кроме печального AttributeError: module ‘bpy’ has no attribute ‘scene’. Опечатка автора статьи в том, что строка привязки должна выглядеть так: bpy.context.scene.collection.objects.link(obj)
Кроме того, выделение объекта в версиях старше 2.79 реализовано так: obj.select_set(True)
Наиболее интересна функция from_pydata() . Она и создаёт меш, исходя из трёх списков: вершин, рёбер и граней. Подробнее об этой функции в родной документации.
Сетка из вершин
Резонно начать с первого вертекса. Каждый вертекс описывается тремя координатами: по X, Y, Z осям. Поскольку мы делаем двумерную сетку, координата Z будет всегда равна нулю. Первый вертекс расположим в нулевых координатах сцены, они же нули глобальных координат. Иначе говоря, в координатах (0, 0, 0). По сути, вертекс это кортеж из трёх чисел с плавающей точкой. Перепишем список с вертексами таким образом:
Попробуйте запустить скрипт, и вы увидите одинокую точку. Она даже редактируема. Пора сделать ряд точечек. Нам потребуется цикл, накидывающий столько вершин, сколько колонок мы обозначили.
verts = [(x, 0, 0) for x in range(columns)]
Поскольку range() возвращает целые числа, кооордината X каждой вершины будет по сути номером колонки. Что означает, что ширина колонки равна одному блендер-юниту . Снова прогнав скрипт, мы увидим десять вертексов в ряд. До сетки из вертексов осталась капля: добавить строчки. Сделаем их так же циклом:
verts = [(x, y, 0) for x in range(columns) for y in range(rows)]
Победа! Налицо сетка из вертексов.
Пора создать полигоны, но сначала осознаем, как это работает.
Полигон
У каждого вертекса есть индекс. Как только рождается новая вершинка, так ей тут же присваивают порядковый номер. Но самая первая будет с индексом 0.
Полигон — кортеж из индексов вертексов. Для формирования полигона их может быть от трёх до бесконечности. Кроме того, эти индексы — натуральные числа. Если вставить дробное значение, Блендер не крашнется, но округлит его. Ну, а поскольку мы хотим четырёхугольные полигоны, для каждого полигона нам потребуется четыре индекса вертексов. Каких? Ну, можно прикинуть на глаз, но есть более клёвый вариант: включить режим отладки. Откройте питон-консоль Блендера, и вбейте туда команду:
примечание про версии Блендеров
Код ниже — для Блендра до версии 2.8. Ежели у вас Блендер в диапазоне 2.8-3, нужная галочка лежит тут: Edit → Preferences → Interface → Display → Developer Extras. Теперь во Viewport Displays (в Edit Mode) появится чекбокс Indices.
Я надеюсь, что если вы и почерпнёте что-то из этого туториала, то это будет режим отладки. Это лучшее, что может с вами произойти, пока вы пишете скрипт или даже пилите аддон. Для просмотра индексов вершин свежесозданной сетки, перейдите в режим редактирования объекта и выделите нужную вершину. В N-панели во вкладке Mesh Display Panel поставьте галочку на Indices, чекбокс появится в колонке Edge Info. Если этого чекбокса нет, вероятно, режим отладки выключен. Теперь все выделенные вершины будут показывать свои индексы.
Сосредоточимся на первом полигоне. Его образуют вертексы с индексами 0, 1, 5 и 6. Попробуем:
Запускаем скрипт, и видим странную картину: как будто бы мы соединили неправильные вертексы.
На самом деле вертексы правильные, но порядок записи некорректен. Дело в том, что индексы в кортеже должны располагаться начиная с нижнего левого угла, и против часовой стрелки:
примечания про порядок обхода вершин
Первое: абсолютно неважно, с какой вершины начнётся обход, лишь бы по кругу.
Второе: обход по часовой или против часовой влияет на направление нормали полигона, но никак не влияет на его построение.
То есть на самом деле нужна была такая последовательность: 0, 5, 6, 1. Исправим строчку кода, и снова запустим скрипт:
Вот теперь хорошечно. Когда возникают такие проблемы, попробуйте поменять местами индексы в первой или второй паре. А теперь веселье: надо понять, как составить список вершин для ряда из полигонов. Если присмотреться, увидим такую закономерность:
- все индексы прибавляют по пять пять по оси X
- первый индекс равен нулю, второй на единицу больше
Первый индекс каждого полигона — номер столбца, умноженный на количество колонок. Второй смещается на единицу относительно первого, так что просто добавим её. Накидаем код и проверим выхлоп:
for x in range(columns - 1): print(x * rows) print((x + 1) * rows)
Почему у количества колонок отнимается единица? Потому что на десять вертексов приходится девять рёбер, то есть нам нужно девять пар чисел.
Третий и четвертый индексы будут равны (x + 1) * rows + 1 и x * rows + 1 соответственно. Добавим единицу к X перед умножением, чтобы сместить индекс во вторую строчку.
Цикл, который выведет наборы индексов для каждого полигона в строке:
for x in range(columns - 1): print('first:', x * rows) print('second:', (x + 1) * rows) print('third:', (x + 1) * rows + 1) print('fourth:', x * rows + 1) print('---')
Делаем меш
Со всеми этими знаниями мы можем приступить к созданию первого ряда полигонов. Но прежде завернём кусок кода для полигонов в функцию, для большего удобства и читаемости. Кроме того, я докинул создание полигонов построчно. На каждый следующий ряд индексы возрастают на единицу, так что просто добавляем номер ряда в конце.
def face(column, row): """ Создаём полигон """ return (column* rows + row, (column + 1) * rows + row, (column + 1) * rows + 1 + row, column * rows + 1 + row)
Добавим в код полигонов эту функцию, как мы сделали это с кодом вертексов:
faces = [face(x, y) for x in range(columns - 1) for y in range(rows - 1)]
Отнимаем у количетсва строк единицу по тем же причинам, что и отнимали у колонок. Запустим скрипт и возрадуемся.
Вот и всё! Только что вы своими руками написали скрипт, генерирующий двумерную сетку. Дальше ещё немного хитростей.
Масштабирование
Мы можем регулировать количество вертексов, составляющих сетку, но размер ячейки всегда будет равен одному блендер-юниту. Поправим же это.
Достаточно простого умножения координат на число, определяющее масштаб. Добавим переменную для этого числа. Можно вписать её прям к вертексам, но будет лучше сделать отдельную функцию.
size = 1 def vert(column, row): """ Создаём точку """ return (column * size, row * size, 0) verts = [vert(x, y) for x in range(columns) for y in range(rows)]
Попробуем поменять значение size на что-нибудь другое, и посмотрим, что получится.
Финальный код
import bpy # Настройки name = 'Gridtastic' rows = 5 columns = 10 size = 1 # Функции def vert(column, row): """ Создаём точку """ return (column * size, row * size, 0) def face(column, row): """ Создаём полигон """ return (column* rows + row, (column + 1) * rows + row, (column + 1) * rows + 1 + row, column * rows + 1 + row) # Циклы для списка координат и вертексов verts = [vert(x, y) for x in range(columns) for y in range(rows)] faces = [face(x, y) for x in range(columns - 1) for y in range(rows - 1)] # Создаём меш mesh = bpy.data.meshes.new(name) mesh.from_pydata(verts, [], faces) # Создаём объект и привязвыем к сцене obj = bpy.data.objects.new(name, mesh) bpy.context.scene.collection.objects.link(obj) # Выделяем объект bpy.context.view_layer.objects.active = obj obj.select = True
Заключение
Надеюсь, вам понравилось! Это самый простой пример создания меша, что я смог придумать, и дальше ещё много интересных штук, которые можно сделать. Вот несколько несложных вещей, на которых вы можете потренироваться:
- разбить коэффициент масштабирования по осям;
- добавить сетке смещение, чтобы она начиналась не в нулевых координатах;
- красиво запаковать это в функции (или классы)
В следующем туториале перепрыгнем в трёхмерное пространство и сделаем куб.
Оригинал статьи (автор не прикрутил к сайту сертификат, браузер может ругаться.)
- Меши с Python & Blender: кубы и матрицы
- Меши с Python & Blender: икосферы
- Меши с Python & Blender: скруглённый куб
- Меши с Python & Blender: круги и цилиндры