Процедурная генерация великолепна! В этом уроке мы рассмотрим создание меша с использованием блендеровского Python API.
Создание мешей программными способами открывает множество возможностей. Вы можете создавать параметрические объекты, которые отвечают размерам реального мира, генеративное искусство, формы на основе математических формул или даже процедурный контент для игр. Blender — отличный выбор для такого рода работ, поскольку он сочетает в себе полномасштабный набор инструментов моделирования и анимации с мощным (и достаточно хорошо документированным) Python API.
В этой серии мы рассмотрим создание нескольких примитивов и некоторых базовых преобразований, а также рассмотрим некоторые ухищрения, которые упростят процесс разработки дополнений. Я предполагаю, что вы уже знаете Python на базовом уровне и достаточно знакомы с Blender. Поэтому мы пропустим введение в мир 3D и сосредоточимся на создании простой плоской сетки.
Настройка
В системе данных Blender существует различие между данными меша и объектами в сцене. Мы должны добавить меш и связать его с объектом, а затем связать этот объект со сценой, прежде чем мы сможем увидеть какие-либо результаты.
Начнем с импорта bpy (сюрприз!) и создания некоторых переменных.
import bpy # Settings name = 'Gridtastic' rows = 5 columns = 10 |
В переменной Name будет храниться имя меша и объекта одновременно. В то время как переменные rows и columns будут контролировать количество вершин нашей сетки.
Далее займемся созданием меша и объекта. Первым делом мы должны добавить блоки данных меша, затем объект, который будет их использовать и в завершении связать их с текущей сценой. Я также добавил несколько пустых списков для вершин и граней. Мы будем их заполнять немного позже.
verts = [] faces = [] # Create Mesh Datablock mesh = bpy.data.meshes.new(name) mesh.from_pydata(verts, [], faces) # Create Object and link to scene obj = bpy.data.objects.new(name, mesh) bpy.context.scene.objects.link(obj) # Select the object bpy.context.scene.objects.active = obj obj.select = True |
Наиболее интересная часть это from_pydata(). Эта функция создает меш из трех списков: вершины, ребра и грани. Обратите внимание, что если мы указываем грани, то ребра указывать уже не нужно. Проверьте документацию по данной функции.
Попробуйте запустить скрипт сейчас. Вы увидите, что добавлен новый объект, но геометрии не видно (поскольку мы еще не добавили ее). Удалите этот объект и давайте двинемся дальше, чтобы узнать, как же создать 2D сетку.
Сетка вершин
Начнем с добавления одной вершины. Вершины представляются тремя координатами (X, Y и Z). Наша сетки будет двухмерной, поэтому мы будем беспокоиться лишь об X и Y, Z у нас всегда будет равно 0. Мы поместим вершину в центр сцены, которая также является центром глобальных координат. Другими словами, ее координаты равны (0, 0, 0). Каждая вершина является кортежем из 3 чисел с плавающей запятой, поэтому измените список следующим образом:
verts = [(0, 0, 0)] |
Запустите скрипт снова и вы увидите одинокую точку. Теперь вы можете перейти в режим редактирования и поиграть с ней. Давайте двинемся дальше и создадим ряд вершин. Для этого нам понадобится цикл, который добавит столько вершин, сколько столбцов мы установили в переменную columns. Это очень легко сделать с помощью выражения:
verts = [(x, 0, 0) for x in range(columns)] |
range () возвращает целые числа, поэтому координата вершины по оси X будет их номером столбца. Это означает, что ширина каждого столбца будет 1 Blender единица (или 1 метр). Запустите скрипт снова. Вы увидите 10 вершин, выстроенных по оси X. Чтобы завершить создание сетки, нам нужно больше одной строки. Мы можем легко расширить список для добавления строк:
verts = [(x, y, 0) for x in range(columns) for y in range(rows)] |
Теперь мы видим сетку вершин во всей ее красе.
Мы можем приступать к созданию граней, но сначала нам нужно понять, как это делать.
Разбираемся с гранями
Каждая вершина, которую мы добавили имеет свой индекс. Индексы устанавливаются в том порядке, в котором мы создаем наши вершины, поэтому у первой вершины индекс 0, у второй 1 и т.д.
Чтобы создать грань, нам нужно добавить кортеж индексов вершин в список граней. Этот кортеж может состоять из 3 (треугольник), 4 (четырехугольник) или большего (многоугольник) количества индексов. Кстати, это все целые числа. Поскольку мы будем создавать четырехугольные грани, нам необходимо будет указать 4 индекса для каждой из них. Но как? Вы можете попытаться угадать их, но есть лучший способ. Мы можем включить режим отладки в Blender. Откройте консоль Python в Blender и введите следующее:
bpy.app.debug = True |
Это самая полезная настройка, которую вы можете использовать для создания мешей и даже для разработки аддонов. Чтобы увидеть индексы вершин, выберите сетку двумерных вершин и перейдите в режим редактирования. Откройте панель свойств и активируйте опцию Indices в меню Mesh Display. Если вы не видите опцию, возможно, вы еще не включили режим отладки. Теперь любая выбранная вершина покажет ее индекс, поэтому выберите их все, чтобы увидеть их индексы.
Давайте сосредоточимся на первой грани. Как видите, она состоит из вершин 0, 1, 5 и 6. Попробуем сделать одну грань с этими индексами:
faces = [(0, 1, 5, 6)] |
Попробуйте запустить скрипт еще раз и… подождите, что-то не так! Похоже, мы связали неправильные вершины.
Ну, мы соединили правильные вершины, но в неправильном порядке. Да, при создании граней необходимо соблюдать порядок: против часовой стрелки, начиная с нижней левой вершины.
Поэтому порядок для грани будет 0, 5, 6, 1. Исправьте это и снова запустите скрипт.
Теперь мы в деле. Каждый раз, когда вы видите подобные проблемы, попробуйте поменять первый или два последних набора индексов между собой. Хорошо, это то место, где все становится интересным. Нам нужно выяснить, как вычислить все индексы, чтобы создать целый ряд граней. Если мы внимательно посмотрим на индексы вершин, мы увидим закономерность:
- Все индексы увеличиваются на 5 по оси X. Их количество равно количеству столбцов.
- Первый индекс начинается с 0, второй на 1 больше.
Мы можем вычислить первый индекс в цикле, умножив текущий столбец на строку. Поскольку второй смещен на 1, нам просто нужно добавить 1, чтобы получить его.
Попробуйте вывести данные значения в консоли, предварительно определив переменные columns и rows:
for x in range(columns - 1): print(x * rows) print(x * rows + 1) |
Возможно, вам интересно, почему мы перебираем столбцы — 1. У нас есть 10 столбцов вершин, но они создают только 9 столбцов граней. Последний столбец ни с чем не соединяется.
Третий и четвертый индексы (x + 1) * rows + 1 и (x + 1) * rows соответственно. Мы добавляем 1 к X до умножения, чтобы установить индекс в следующую строку.
Вот цикл для вывода всех индексов:
for x in range(columns - 1): print('first:', x * rows) print('second:', x * rows + 1) print('third:', (x + 1) * rows + 1) print('fourth:', (x + 1) * rows) print('---') |
Создание сетки
Вооружившись всеми этими знаниями, мы можем построить первый ряд граней. Но прежде чем мы дойдем до этого, давайте выделим код граней в его собственную функцию, чтобы мы могли сохранить код в чистоте и порядке. Я также добавил возможность для создания граней в любой строке. Строки увеличивают индексы на 1, поэтому мы можем просто добавить номер строки в конце.
def face(column, row): """ Create a single face """ return (column * rows + row, column * rows + 1 + row, (column + 1) * rows + 1 + row, (column + 1) * rows + row) |
Создадим выражение, подобное тому, что мы делали с вершинами:
faces = [face(x, y) for x in range(columns - 1) for y in range(rows - 1)] |
Мы используем строки — 1 по той же причине, что и столбцы. Запустите скрипт и созерцайте.
Вот теперь сетка завершена. Вы создали скрипт, который может создавать 2D-сетки! Похлопайте себя по плечу и продолжайте читать, чтобы немного улучшить и расширить наш скрипт.
Масштабирование
Мы можем контролировать количество вершин нашей сетки, но квадраты всегда равны 1 BU (или 1 метр). Давайте изменим это.
Все, что нам нужно сделать, это умножить координаты X и Y на коэффициент масштаба. Начните с добавления переменной размера (size). Мы можем добавить это непосредственно к выражению verts, но опять же, будет лучше и чище, если мы сделаем это в своей собственной функции.
size = 1 def vert(column, row): """ Create a single vert """ return (column * size, row * size, 0) verts = [vert(x, y) for x in range(columns) for y in range(rows)] |
Попробуйте установить размер во что-то отличное от 1 и проверьте сетку.
Финальный код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import bpy # Settings name = 'Gridtastic' rows = 5 columns = 10 size = 1 # Utility functions def vert(column, row): """ Create a single vert """ return (column * size, row * size, 0) def face(column, row): """ Create a single face """ return (column* rows + row, column * rows + 1 + row, (column + 1) * rows + 1 + row, (column + 1) * rows + row) # Looping to create the grid 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)] # Create Mesh Datablock mesh = bpy.data.meshes.new(name) mesh.from_pydata(verts, [], faces) # Create Object and link to scene obj = bpy.data.objects.new(name, mesh) bpy.context.scene.objects.link(obj) # Select the object bpy.context.scene.objects.active = obj obj.select = True |
Завершение
Надеюсь, вам понравилось это введение в создание мешей на Python. Это только самый простой пример и есть много других интересных вещей. Вот несколько простых вещей, которые вы можете попытаться реализовать самостоятельно:
- Используйте два масштабирующих фактора: для X и Y.
- Добавьте смещение, чтобы сетка начиналась не с координат (0, 0).
- Изолируйте все это в свою собственную функцию (или класс).
Оставайтесь с нами и в следующем уроке, в котором мы, наконец, перейдем к 3D с кубиками.
Начну наверное программирование изучать))
Это точно
с английского языка начни ,
Интересно,но где это может быть использовано?Я просто, наверное,что-то не догнал
[q]»Создание мешей программными способами открывает множество возможностей. Вы можете создавать параметрические объекты, которые отвечают размерам реального мира, генеративное искусство, формы на основе математических формул или даже процедурный контент для игр. «[/q]
Да, в самом начале не догнали ?
насколько я обознан ,изменять обьекты (сетки)невозможно в bge ,к примеру удальть вершины или добавить ,хз
А вы «обознаны» о том, что BGE умер давным давно, а недавно разработчики нашли его труп и официально похоронили? 🈂
P.S. Игры в BGE никто не создавал и уже не будет. Это были инди-попытки, но не более.
я не вкурсе ,будет обидно если они сайт с API фунуциями — уничтожат ( Инди попытки ,всеравно как называть ,люди сидят за компом ,игроделы, и делают в свое удовольствие вещи , игродел должен быть -человеком оркестором ,все уметь ,знать , тянуть проект одному ,это тяжело ,наверное по этой причине ,спрос упал до минимума на bge ,и его разработчики похоронили как вы пишете ,мне бы побольше знаний в програмировании узнать как они компилировали код ,что в него входит ,его структура ,наверное не обойтись бес языка С :(
Круто!!!?
Артём, спасибо, интересный урок! Я много раз пересмотрел видео, где ты создаёшь свой addon, сделал кучу попыток сделать что-то посложнее, но так и не удалось. Например чтобы можно играя значениями в своём меню, можно было изменять кол-во, масштаб (размеры) объекта в сцене. Очень бы хотелось разобраться в этом, надеюсь это не последний урок.
Спасибо за материал!
Весьма занятно, но базы в уме нет. По этому, очень сложно оперировать какими то переменными и функциями, не зная примитивов программирования на Python. А времени особо на обучения нет.
Тут надо понимать, ты либо моделишь, либо кодишь. Творишь или считаешь))
Спасибо за труды.
Не понятено (из понятного), почему говорится:
«при создании граней необходимо соблюдать порядок: против часовой стрелки, начиная с нижней левой вершины.»
Но при этом, вершины всё равно соединяются ПО ЧАСОВОЙ стрелке.
Верно подметили! Это автор ошибся, а я, такой внимательный, переписал с ошибкой.
В таком случае у нас нормаль смотрит в другую сторону. А если делать как положено, то и результат соответственный.
Кстати, при таком порядке: [b]faces = [(1, 0, 5, 6)][/b] результат такой же, как при: [b]faces = [(0, 5, 6, 1)][/b].
Я предположил, что с практической стороны могло бы вероятно от нормали завесить, или смотря с какой стороны смотреть.. Но это всё же туториал, должно быть как положено))
Сейчас не вспомню название проги. Но она генерировала «типа салюта» и файлы которые можно было редактировать в иконке отображали змейку. Так что вариантов использования очень даже много.
Хороший урок, вот тоже интересный урок по программированию (только на английском) https://www.youtube.com/watch?v=SctkCRZwrg8
Gnu не нашёл, но зато зато у Suzanne появился такой друг:
Исходник аддона: http://rextester.com/AWXVQ15732
Спасибо Вам Артем!
>> Используйте два масштабирующих фактора: для X и Y.
Это упражнение я сделал: http://rextester.com/EPJ29209
size_x = 1
size_y = 0.5
# Utility functions
def vert(column, row):
«»» Create a single vert «»»
return (column * size_x, row * size_y, 0)
Кому интересно, на основании урока собрал скрипт для генерации правильной шестиугольной (сотовой) сетки. Не могу нормально приложить скрипт к комментарию, так что вот картинка, на коленке собрал.
P.S. Как нормально отправить код в комменты?
Обычно люди кидают как обычный текст, и все 🈂
Как вы знаете, питон требователен к структуре отступов, которые в комментарии обрезаются, поэтому скрипт работать не будет.
Все, кому этот скрипт пригодится, сами их вернут на место 🈸
P.S. Во время написания коммента, если нажать на карандашик, появится тег
Закиньте, пожалуйста, куда-нибудь сюда:
1) http://rextester.com/
2) https://ideone.com/
3) https://github.com/
4) и т.д.
Инетерсно посмотреть на код.
[code]
import bpy
from math import sqrt;
bpy.app.debug = True
name = ‘HexPlane’
rows = 20
cols = 10
len = 0.1
verts = []
faces = []
for row in range(rows+1):
subRow1 = []
for col in range(cols):
x = len/2 + col*len*3
y = row*len*sqrt(3)
subRow1.extend([(x,y,0),(x+len,y,0)])
verts.extend(subRow1)
subRow2 = []
for col in range(cols):
x = col*len*3
y = (row+0.5)*sqrt(3)*len
subRow2.extend([(x,y,0),(x+len*2,y,0)])
if row == rows-1+1:
subRow2 = subRow2[1:-1]
verts.extend(subRow2)
for row in range(rows+1-1):
for col in range(cols):
face1 = (cols*4*row+col*2,
cols*4*row+col*2+1,
cols*2+cols*row*4+col*2+1,
cols*4*row+col*2+cols*4+1,
cols*4*row+col*2+cols*4,
cols*2+cols*row*4+col*2)
faces.append(face1)
face2 = (cols*4*row+col*2+cols*4+1,
cols*2+cols*row*4+col*2+1,
cols*2+cols*row*4+col*2+2,
cols*4*row+col*2+cols*4+2,
cols*2+cols*row*4+col*2+2+cols*4,
cols*2+cols*row*4+col*2+1+cols*4)
face3 = (cols*4*row+col*2+cols*4+1,
cols*2+cols*row*4+col*2+1,
cols*2+cols*row*4+col*2+2,
cols*4*row+col*2+cols*4+2,
cols*2+cols*row*4+col*2+2+cols*4-1,
cols*2+cols*row*4+col*2+1+cols*4-1)
if col < cols-1: if row != rows-1: faces.append(face2) else: faces.append(face3) # Create Mesh Datablock mesh = bpy.data.meshes.new(name) mesh.from_pydata(verts, [], faces) # Create Object and link to scene obj = bpy.data.objects.new(name, mesh) bpy.context.scene.objects.link(obj) # Select the object bpy.context.scene.objects.active = obj obj.select = True [/code] Спасибо, очень удобные страницы, про первые 2 не знал. На всякий случай, отдельная ссылка на код: http://rextester.com/NSCM77930
Если кому-то захочется вынести функции face() и vert() в отдельный файл, например, пусть его название будет helper.py, то нужно сделать следующее:
[code]import bpy
import sys
import os
# Получаем путь к директории с .blend файлом.
# В этой дирктории здесь лежат наши скрипты.
dir = os.path.dirname(bpy.data.filepath)
# Проверяем есть ли полученная директория в
# списке подключённых директорий. Если нет,
# то добавляем.
if not dir in sys.path:
sys.path.append(dir)
# Импортируем наш модуль.
import helper
# Перезагружаем модуль. Это нужно если используется
# внешний редактор для написания кода.
# Я использую редактор VSCode — очень удобно.
# Для перезагрузки запускаемого скрипта в Blender’e
# нужно в Blender’е навести на скрипт и нажать Alt+R+R.
import importlib
importlib.reload(helper)
# Примечение. Все скрипты открывать в Blender’е не нужно, а
# нужно подключить только основной, который будет запускаться.
# Импортируем имена функций из нашего модуля helper.
from helper import face, vert[/code]
Круто!!!?
Интересно,но где это может быть использовано?Я просто, наверное,что-то не догнал
[q]»Создание мешей программными способами открывает множество возможностей. Вы можете создавать параметрические объекты, которые отвечают размерам реального мира, генеративное искусство, формы на основе математических формул или даже процедурный контент для игр. «[/q]
Да, в самом начале не догнали ?
насколько я обознан ,изменять обьекты (сетки)невозможно в bge ,к примеру удальть вершины или добавить ,хз
А вы «обознаны» о том, что BGE умер давным давно, а недавно разработчики нашли его труп и официально похоронили? 🈂
P.S. Игры в BGE никто не создавал и уже не будет. Это были инди-попытки, но не более.
я не вкурсе ,будет обидно если они сайт с API фунуциями — уничтожат ( Инди попытки ,всеравно как называть ,люди сидят за компом ,игроделы, и делают в свое удовольствие вещи , игродел должен быть -человеком оркестором ,все уметь ,знать , тянуть проект одному ,это тяжело ,наверное по этой причине ,спрос упал до минимума на bge ,и его разработчики похоронили как вы пишете ,мне бы побольше знаний в програмировании узнать как они компилировали код ,что в него входит ,его структура ,наверное не обойтись бес языка С :(
Спасибо за материал!
Весьма занятно, но базы в уме нет. По этому, очень сложно оперировать какими то переменными и функциями, не зная примитивов программирования на Python. А времени особо на обучения нет.
Тут надо понимать, ты либо моделишь, либо кодишь. Творишь или считаешь))
Спасибо за труды.
Не понятено (из понятного), почему говорится:
«при создании граней необходимо соблюдать порядок: против часовой стрелки, начиная с нижней левой вершины.»
Но при этом, вершины всё равно соединяются ПО ЧАСОВОЙ стрелке.
Верно подметили! Это автор ошибся, а я, такой внимательный, переписал с ошибкой.
В таком случае у нас нормаль смотрит в другую сторону. А если делать как положено, то и результат соответственный.
Кстати, при таком порядке: [b]faces = [(1, 0, 5, 6)][/b] результат такой же, как при: [b]faces = [(0, 5, 6, 1)][/b].
Я предположил, что с практической стороны могло бы вероятно от нормали завесить, или смотря с какой стороны смотреть.. Но это всё же туториал, должно быть как положено))
Начну наверное программирование изучать))
Это точно
с английского языка начни ,
Сейчас не вспомню название проги. Но она генерировала «типа салюта» и файлы которые можно было редактировать в иконке отображали змейку. Так что вариантов использования очень даже много.
Если кому-то захочется вынести функции face() и vert() в отдельный файл, например, пусть его название будет helper.py, то нужно сделать следующее:
[code]import bpy
import sys
import os
# Получаем путь к директории с .blend файлом.
# В этой дирктории здесь лежат наши скрипты.
dir = os.path.dirname(bpy.data.filepath)
# Проверяем есть ли полученная директория в
# списке подключённых директорий. Если нет,
# то добавляем.
if not dir in sys.path:
sys.path.append(dir)
# Импортируем наш модуль.
import helper
# Перезагружаем модуль. Это нужно если используется
# внешний редактор для написания кода.
# Я использую редактор VSCode — очень удобно.
# Для перезагрузки запускаемого скрипта в Blender’e
# нужно в Blender’е навести на скрипт и нажать Alt+R+R.
import importlib
importlib.reload(helper)
# Примечение. Все скрипты открывать в Blender’е не нужно, а
# нужно подключить только основной, который будет запускаться.
# Импортируем имена функций из нашего модуля helper.
from helper import face, vert[/code]
>> Используйте два масштабирующих фактора: для X и Y.
Это упражнение я сделал: http://rextester.com/EPJ29209
size_x = 1
size_y = 0.5
# Utility functions
def vert(column, row):
«»» Create a single vert «»»
return (column * size_x, row * size_y, 0)
Артём, спасибо, интересный урок! Я много раз пересмотрел видео, где ты создаёшь свой addon, сделал кучу попыток сделать что-то посложнее, но так и не удалось. Например чтобы можно играя значениями в своём меню, можно было изменять кол-во, масштаб (размеры) объекта в сцене. Очень бы хотелось разобраться в этом, надеюсь это не последний урок.
Хороший урок, вот тоже интересный урок по программированию (только на английском) https://www.youtube.com/watch?v=SctkCRZwrg8
Gnu не нашёл, но зато зато у Suzanne появился такой друг:
Исходник аддона: http://rextester.com/AWXVQ15732