Скрипты Python в Blender: автоматизация рутинных операций и создание параметрических моделей

Раздел: Автоматизация -> 3D-моделирование

Введение в скрипты 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 при необходимости повторяемости.

Скрипт Python для Blender - comments

En
Blender python script (python)