Скрипты Python в Blender: автоматизация рутинных операций и создание параметрических моделей
Введение в скрипты Python для Blender
Blender предоставляет мощный Python API, позволяющий автоматизировать практически любые действия в 3D-редакторе. С помощью скриптов можно создавать объекты, управлять материалами, запускать рендер, импортировать/экспортировать данные и даже разрабатывать собственные инструменты. В этой статье на примерах разберём основные подходы и варианты решений для автоматизации 3D-моделирования.
Основное решение: скрипт для генерации массива объектов по спирали
Как создать последовательность объектов, расположенных по спирали, одним запуском скрипта?
Рассмотрим классический пример – генерация колонны из кубов, закрученных по спирали. Скрипт использует bpy и mathutils.
import bpy
import math
def create_spiral(count=30, radius=2, height=5, turns=2):
# Удаляем все объекты перед созданием (опционально)
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for i in range(count):
# Вычисляем угол и высоту
angle = (i / count) * turns * 2 * math.pi
z = (i / count) * height
x = radius * math.cos(angle)
y = radius * math.sin(angle)
# Создаём куб и перемещаем
bpy.ops.mesh.primitive_cube_add(size=0.2, location=(x, y, z))
# Поворачиваем объект относительно центра спирали
obj = bpy.context.active_object
obj.rotation_euler.z = angle
create_spiral()Blender python script (скрипт python для blender)
Пояснение шагов:
- Импортируем bpy (основной модуль Blender) и math для тригонометрии.
- Функция create_spiral принимает параметры: количество элементов, радиус, высоту, количество витков.
- В цикле вычисляем координаты (x,y,z) и угол поворота для каждого куба.
- Используем bpy.ops.mesh.primitive_cube_add для создания объекта и сразу задаём его положение через location.
- После создания объект становится активным, поэтому можем изменить его поворот (rotation_euler.z).
Типичные проблемы и их решение
- Ошибка: AttributeError: module 'bpy' has no attribute 'ops' – скрипт запущен не внутри Blender. Решение: выполнять код в текстовом редакторе Blender (Scripting workspace) или через bpy только после импорта модуля bpy внутри Blender.
- Проблема производительности – при большом количестве объектов (count > 1000) операция может зависнуть. Решение: использовать bpy.ops.object.join после цикла для объединения в один меш, либо применять BMesh для создания геометрии напрямую.
- Непредвиденное вращение – если не сбросить поворот перед циклом, накопление может дать искажения. Решение: перед циклом установить bpy.context.scene.cursor.location = (0,0,0) и bpy.context.scene.cursor.rotation_euler = (0,0,0).
Вариант 1: Автоматический импорт/экспорт файлов
Как автоматически импортировать папку с .obj файлами и конвертировать их в .fbx?
Скрипт перебирает все файлы в указанной папке, импортирует каждый и экспортирует в другой формат.
import bpy
import os
def batch_convert(input_folder, output_format='fbx'):
# Очищаем сцену перед импортом
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for filename in os.listdir(input_folder):
if filename.endswith('.obj'):
path = os.path.join(input_folder, filename)
# Импорт
bpy.ops.import_scene.obj(filepath=path)
# Экспорт с тем же именем, но другим расширением
out_path = os.path.join(input_folder, filename.replace('.obj', '.' + output_format))
if output_format == 'fbx':
bpy.ops.export_scene.fbx(filepath=out_path)
elif output_format == 'stl':
bpy.ops.export_mesh.stl(filepath=out_path)
# Удаляем импортированные объекты для следующего файла
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
batch_convert('C:/my_folder/')Проблемы и их решение
- Путь с кириллицей – Blender может не найти файл. Решение: использовать только латиницу в путях или преобразовывать строку в байты с кодировкой UTF-8.
- Конфликт имён при экспорте – если уже есть файл с тем же именем, экспорт может выдавать ошибку. Решение: проверять существование файла и добавлять суффикс (например, _1).
- Импорт без материалов – если .obj содержит материалы, они могут не импортироваться. Решение: перед импортом включить настройку bpy.ops.import_scene.obj(filepath=path, use_image_search=True).
Вариант 2: Параметрическая модель гайки
Как создать модель гайки с заданным диаметром, высотой и количеством граней?
Используем BMesh для построения геометрии напрямую (быстрее, чем через ops).
import bpy
import bmesh
import math
def create_nut(outer_radius=1, inner_radius=0.6, height=0.8, sides=6):
# Создаём новый mesh и bmesh
mesh = bpy.data.meshes.new('Nut')
bm = bmesh.new()
# Создаём верхнюю и нижнюю окружности с отверстием
# Используем bmesh.ops.create_circle для наружного контура
outer_verts = bmesh.ops.create_circle(bm, cap_ends=False, diameter=outer_radius*2, segments=sides)
# Смещаем по Z
for v in outer_verts['verts']:
v.co.z = height
# Создаём нижнюю окружность
bmesh.ops.create_circle(bm, cap_ends=False, diameter=outer_radius*2, segments=sides)
# Соединяем – полный код требует построения граней между кольцами
# (для сокращения опущено, но общая идея: создание цилиндрической поверхности)
bm.to_mesh(mesh)
bm.free()
# Добавляем объект в сцену
obj = bpy.data.objects.new('Nut', mesh)
bpy.context.collection.objects.link(obj)
create_nut()Типичные ошибки
- Отсутствие граней – после создания вершин нужно вручную строить полигоны. Решение: использовать bmesh.ops.bridge_loops для соединения двух колец.
- Повреждённая топология – неверный порядок вершин. Решение: проверять индексы при создании граней с помощью bm.faces.new(verts).
Вариант 3: Управление материалами через скрипт
Как назначить случайный цвет каждому из выделенных объектов?
import bpy
import random
def random_materials(obj_list):
for obj in obj_list:
# Создаём новый материал
mat_name = f"Random_{random.randint(1000,9999)}"
mat = bpy.data.materials.new(mat_name)
mat.use_nodes = True
# Доступ к нод-дереву
bsdf = mat.node_tree.nodes['Principled BSDF']
bsdf.inputs[0].default_value = (random.random(), random.random(), random.random(), 1)
# Назначаем материал объекту
if obj.data.materials:
obj.data.materials[0] = mat
else:
obj.data.materials.append(mat)
# Использование: выделите объекты в сцене и запустите
random_materials(bpy.context.selected_objects)Проблемы
- Материал не отображается в viewport – нужно включить отображение материалов. Решение: bpy.context.area.ui_type = 'VIEW_3D' и включить режим Material Preview.
- Дублирование материалов – каждый вызов создаёт новый материал без удаления старого. Решение: перед назначением удалять все старые материалы объекта: obj.data.materials.clear().
Вариант 4: Автоматизация анимации ключевыми кадрами
Как анимировать вращение объекта на 360 градусов за 50 кадров с помощью скрипта?
import bpy
def animate_rotation(obj, frames=50):
# Устанавливаем начальный кадр
bpy.context.scene.frame_start = 0
bpy.context.scene.frame_end = frames
# Начальный поворот (0 градусов)
obj.rotation_euler.z = 0
obj.keyframe_insert(data_path="rotation_euler", index=2, frame=0)
# Конечный поворот (360 градусов = 2*pi)
import math
obj.rotation_euler.z = 2 * math.pi
obj.keyframe_insert(data_path="rotation_euler", index=2, frame=frames)
# Пример
cube = bpy.data.objects['Cube']
animate_rotation(cube, 100)Типичные ошибки
- Ключевой кадр не создаётся – если объект не имеет анимации, нужно сначала задать начальное значение до вставки. Решение: явно указать значение перед keyframe_insert.
- Анимация прыгает – если интерполяция задана как линейная, может быть рывок на 360->0. Решение: использовать Bezier или установить цикл в кривых.
Вариант 5: Использование Geometry Nodes из скрипта
Как программно добавить модификатор Geometry Nodes на объект и задать параметры?
import bpy
def add_gn_modifier(obj, node_group_name, params):
# Создаём модификатор
mod = obj.modifiers.new(name="GN_Mod", type='NODES')
# Назначаем группу нод (должна существовать в blend-файле)
ng = bpy.data.node_groups[node_group_name]
mod.node_group = ng
# Изменяем параметры (пример: Input_1 типа Float)
mod['Input_1'] = params.get('value', 1.0)
# Предполагаем, что есть группа нод 'MyGroup' с входом 'Input_1'
add_gn_modifier(bpy.context.active_object, 'MyGroup', {'value': 0.5})Проблемы
- Модификатор не применяется – если группа нод использует атрибуты, которые не существуют у объекта. Решение: предварительно создать атрибуты (например, obj.data.attributes.new).
- Неправильное имя входа – имена входов в нодах могут отличаться от ожидаемых. Решение: вывести список mod.node_group.interface.items_tree для отладки.
Заключение: Скрипты Python в Blender открывают широкие возможности для автоматизации – от простых манипуляций до сложных генеративных моделей. Главное – понимать структуру API и всегда тестировать на резервной копии проекта. Используя приведённые примеры, можно быстро адаптировать их под свои нужды, не тратя время на рутинные операции.
Расширенные примеры скриптов для Blender
Пример 1: Параметрическая лестница с перилами
Скрипт создаёт лестницу из ступеней, автоматически расставляя их с заданным шагом и высотой, и добавляет перила из трубок.
import bpy
import math
def create_stairs(steps=10, step_width=0.3, step_height=0.2, step_depth=0.4, rail_height=1):
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for i in range(steps):
# Ступень
bpy.ops.mesh.primitive_cube_add(size=1, location=(i*step_depth, 0, i*step_height))
cube = bpy.context.active_object
cube.scale = (step_depth/2, step_width/2, step_height/2)
# Применяем масштаб, чтобы дальше работать с правильными размерами
bpy.ops.object.transform_apply(scale=True)
# Перила: две трубы по бокам
for side in [-1, 1]:
# Стойки
for i in range(steps):
bpy.ops.mesh.primitive_cylinder_add(radius=0.02, depth=rail_height, location=(i*step_depth, side*step_width/1.8, i*step_height + rail_height/2))
# Перила (поручень) – цилиндр, согнутый не будем, просто прямая труба
bpy.ops.mesh.primitive_cylinder_add(radius=0.03, depth=steps*step_depth, location=((steps-1)*step_depth/2, side*step_width/1.8, steps*step_height))
railing = bpy.context.active_object
railing.rotation_euler.x = math.pi/2
create_stairs()Результат: в сцене появляется лестница из 10 ступеней и перила. Объекты можно дополнительно модифицировать.
Пояснение:
- Цилиндры для стоек создаются в каждой точке ступени, высота rail_height.
- Поручень – один длинный цилиндр, повёрнутый на 90 градусов, чтобы лежал вдоль лестницы.
- Применение масштаба (transform_apply) необходимо, чтобы координаты вершин соответствовали визуальному размеру (иначе при повороте поручня масштаб исказится).
Возможные проблемы
- Поручень может выходить за край последней ступени – нужно корректировать глубину цилиндра: depth = (steps-1)*step_depth + step_depth.
- Стойки перил могут накладываться на ступени – стоит поднять их основание на высоту ступени, что уже сделано через i*step_height.
Пример 2: Пакетный рендер с разными настройками
Скрипт автоматически меняет камеру, фон или материалы и последовательно рендерит изображения в папку.
import bpy
import os
def batch_render(output_dir, camera_list, material_list):
for cam_name in camera_list:
# Устанавливаем активную камеру
cam = bpy.data.objects[cam_name]
bpy.context.scene.camera = cam
for mat_name in material_list:
# Назначаем материал выделенному объекту (например, 'Cube')
obj = bpy.data.objects['Cube']
mat = bpy.data.materials.get(mat_name)
if mat:
obj.data.materials[0] = mat
else:
print(f'Материал {mat_name} не найден')
# Настройки рендера
bpy.context.scene.render.filepath = os.path.join(output_dir, f'{cam_name}_{mat_name}.png')
bpy.ops.render.render(write_still=True)
batch_render('C:/renders', ['Camera.001', 'Camera.002'], ['Red', 'Blue'])Результат: в папке renders появятся файлы: Camera.001_Red.png, Camera.001_Blue.png, Camera.002_Red.png, Camera.002_Blue.png.
Пояснение:
- Скрипт перебирает все комбинации камер и материалов из списков.
- Для каждого сочетания устанавливает камеру, меняет материал на объекте 'Cube', задаёт путь и запускает рендер.
- Обратите внимание, что материал должен быть заранее создан в файле.
Типичные ошибки
- Ошибка KeyError – если объекта с именем 'Cube' нет. Решение: предварительно проверить существование через bpy.data.objects.get('Cube').
- Рендер не начинается – если не настроен рендер-движок (Cycles vs Eevee). Решение: установить bpy.context.scene.render.engine = 'CYCLES' перед рендером.
Пример 3: Очистка и оптимизация меша
Скрипт удаляет двойные вершины, неиспользуемые рёбра и пересчитывает нормали для всех выбранных объектов.
import bpy
def clean_mesh_objects(obj_list):
for obj in obj_list:
if obj.type != 'MESH':
continue
# Переходим в режим редактирования
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# Удаление двойных вершин
bpy.ops.mesh.remove_doubles(threshold=0.0001)
# Удаление неиспользуемых рёбер (связанные с пустыми гранями)
bpy.ops.mesh.delete_loose()
# Пересчёт нормалей
bpy.ops.mesh.normals_make_consistent(inside=False)
# Возвращаемся в объектный режим
bpy.ops.object.mode_set(mode='OBJECT')
clean_mesh_objects(bpy.context.selected_objects)Результат: у каждого выбранного меша удалены лишние вершины, исправлены нормали.
Пояснение:
- Операции редактирования доступны только в режиме EDIT. После завершения нужно вернуться в OBJECT.
- threshold в remove_doubles – расстояние, в пределах которого вершины считаются дубликатами. Для больших моделей можно увеличить до 0.001.
Проблемы
- Ошибка 'Operator bpy.ops.mesh.remove_doubles.poll() failed' – если объект не находится в режиме редактирования или не выбран ни один элемент. Решение: перед операциями убедиться, что bpy.context.active_object соответствует объекту, и вызвать bpy.ops.mesh.select_all(action='SELECT').
- Нормали переворачиваются неверно – параметр inside указывает направление. Если модель импортирована с перевёрнутыми нормалями, стоит установить inside=True.
Пример 4: Создание сплайнового пути с помощью кривых Безье
Скрипт генерирует кривую Безье по точкам из списка – полезно для создания траекторий анимации или дорог.
import bpy
import mathutils
def create_bezier_path(points):
# Создаём пустую кривую
curve_data = bpy.data.curves.new(name='Path', type='CURVE')
curve_data.dimensions = '3D'
spline = curve_data.splines.new('BEZIER')
spline.bezier_points.add(len(points)-1)
for i, loc in enumerate(points):
bp = spline.bezier_points[i]
bp.co = mathutils.Vector(loc)
# Задаём автоматические ручки (handle_type='AUTO')
bp.handle_left_type = 'AUTO'
bp.handle_right_type = 'AUTO'
obj = bpy.data.objects.new('BezierCurve', curve_data)
bpy.context.collection.objects.link(obj)
# Пример: точки на спирали
ts = [(math.sin(t), math.cos(t), t*0.5) for t in [i*0.5 for i in range(50)]]
create_bezier_path(ts)Результат: в сцене появляется сглаженная кривая, проходящая через заданные точки.
Пояснение:
- Создаётся объект кривой с типом BEZIER. Количество точек задаётся через add(len-1), так как первая точка уже есть.
- Ручки (handles) ставятся в автоматический режим для плавности. Можно задать вручную, передавая вторую и третью точки как handle_left и handle_right.
- Кривая может использоваться как путь для камеры или объекта с модификатором Follow Path.
Типичные ошибки
- Кривая выглядит ломаной – если не задать тип ручек или установить 'VECTOR'. Решение: для плавности использовать 'AUTO' или 'ALIGNED'.
- Точки не совпадают с ожидаемыми – если не передан mathutils.Vector (координаты должны быть кортежем из трёх чисел). Решение: всегда приводить к vector: mathutils.Vector((x,y,z)).
Пример 5: Геометрический шум (вертексная деформация)
Скрипт добавляет случайный шум к координатам вершин выбранного меша – для создания неровных поверхностей.
import bpy
import random
import math
def vertex_noise(obj, strength=0.1, seed=0):
random.seed(seed)
mesh = obj.data
# Работаем с вершинами напрямую
for v in mesh.vertices:
noise = (random.random() - 0.5) * 2 * strength
v.co.x += noise
v.co.y += noise * 0.5 # дополнительное искажение
v.co.z += noise * 0.3
# Обновляем mesh
mesh.update()
# Применяем к активному объекту
vertex_noise(bpy.context.active_object, strength=0.2, seed=42)Результат: вершины объекта смещены случайным образом, поверхность становится «шумной».
Пояснение:
- Используется random.seed для воспроизводимости.
- Изменения происходят непосредственно в mesh.vertices, после чего нужно вызвать mesh.update() для перерисовки.
- Для больших объектов (миллионы вершин) такой подход может быть медленным; лучше использовать модификатор Displace с текстурой.
Проблемы
- Изменения не сохраняются – если не вызван mesh.update(). Решение: обязательно обновлять mesh после цикла.
- Шум применяется к каждой вершине по-разному – если не задан seed, каждый запуск даст разный результат. Решение: фиксировать seed при необходимости повторяемости.