Как стать автором
Обновить

Делаем addon для Blender

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров5.4K

Люблю Blender. Он мощный, а в последних версиях красивый! А ещё он бесплатный. И даже становится удобнее. Но если посмотреть на то сколько вопросов в интернете про то, как сделать что-то вполне тривиальное, начинаешь понимать, что до реального удобства там ещё далеко. С последними версиями эти вопросы становятся менее актуальными, потому что часть этих проблем уже кто-то решил за вас, написав аддон, который и только ждет что бы его активировали!

Например, работать в некоторых сценариях без LoopTools - это боль. Без него из существующих вертексов красивый круг сделать руками - нереально, из коробки тоже. А вот Circle даст то, что доктор прописал. Да, можно, конечно, нарисовать отдельно круг и потом ручками его в свой меш повертексно вмержить...

LoopTools во всей красе
LoopTools во всей красе

Gstrech позволяет без лишних танцев с бубном выровнять текущие вертексы в красивую ровную линию. А если нужно потом их красиво разложить, то вот вам Space. В общем классные штучки, вот вам ссылка на документацию. Она на английском, язык менять бесполезно, вот вам котик в качестве утешения =^__^=.

И всё было хорошо, пока мне не понадобилось сделать не круг, а сегмент круга. Назовем это аркой. Тут опять, даже с аддонами, надо городить огород.

И тут я вспоминаю свою первую (и единственную попытку) портировать Blender на Emscripten, хотя бы как вьювер в браузере. Официальная отмазка команды в том, что блендер использует python и потому... Вот что потому я так и не понял. Есть же Brython (который питон в браузере), а значит!

А не важно, что на самом деле это значит - статья не об этом. В Blender есть Python! Аддоны пишутся на нём по большинству. А значит я могу накатать лично мне нужный функционал. Есть правда некоторые тяжеловесные вещи, которые из питона сделать вам не дадут - только с/с++, о чем в документации написано, но мои хотелки туда влезут с головой.

Аддон стадия 0

Открываем вкладочку Scripting в Blender и делаем то, что нам нужно руками. Сначала надо покурить пару раз документацию. Если у вас ничего не получилось в интерактивном режиме, то можно обратиться на профильные форумы, там объяснят что к чему. Подводных камней тут целый океан.

Аддон стадия 0.5

Всё в той же вкладочке Scripting загружаем готовый шаблон для аддона. Тут есть очень много из того, что нам может понадобится. Лично я эту часть поменял на: открыть готовый аддон, который ближе по функционалу, чем темплейт. Что делает этот базовый темплейт: добавляет объект правой кнопкой мыши в режиме Object, а мне нужно редактировать позиции вертексов в режиме Edit. Но, как короткая база, очень даже ничего.

Базовый тэмплейт блендера
Базовый тэмплейт блендера

Аддон стадия 1.0

Что бы аддон добавился в Blender нужно чтобы была точка входа и выхода.

# registering and menu integration
def register():
    pass

# unregistering and removing menus
def unregister():
    pass

if __name__ == "__main__":
    register()

Вот вам минимальный аддон, ничего не делает, но уже добавляется. Ах да, ещё bl_info было бы неплохо заполнить. Поигравшись с ним, можно увидеть, как всё это воспринимает блендер.

Устанавливается аддон через Edit/Preferences/Add-ons/Install. Зацикливаться на этом не буду, есть куча инструкций. Только галочку после установки поставить не забудьте, иначе аддон добавлен, но не активирован. Удалять через те же пункты. Инструкций как это сделать полно.

Итак, на этой стадии, у нас есть бесполезный аддон, который ничего вообще не делается, кроме как устанавливается. Сделаем заглушку для нашего будущего функционала.

import bpy
from bpy.types import Operator

bl_info = {
    "name": "Make arch тулза моя",
    "author": "автор я",
    "version": (0, 1, 0),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu/ Make arch",
    "warning": "",
    "description": "Make arch tool",
    "doc_url": "",
    "category": "Mesh",
}

class MeshToolsMakeArch(Operator):
    bl_idname = "mesh.make_arch"
    bl_label = "Make arch"
    bl_description = "Shape selected vertices into an arch shape with center in 3d cursor"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        ob = context.active_object
        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')

    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        return self.execute(context)


def menu_func(self, context):
    self.layout.operator("mesh.make_arch")

# registering and menu integration
def register():
    bpy.utils.register_class(MeshToolsMakeArch)
    bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)

# unregistering and removing menus
def unregister():
    bpy.utils.unregister_class(MeshToolsMakeArch)
    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)

if __name__ == "__main__":
    register()

Теперь появился код, который в режиме Edit по нажатию правой кнопкой мыши, покажет нам кнопку "Make arch". Которая ничего не делает.

Кнопочка есть, осталось прикрутить действие на её нажатие
Кнопочка есть, осталось прикрутить действие на её нажатие

Разберемся в коде выше, что за что отвечает.

bl_info - метаданные модуля. Будет показано во вкладке add-ons.

MeshToolsMakeArch(Operator) - класс который будет реализовывать действие, которое мы хотим. На нем остановимся по подробнее.

Начнем с самого начала. Наш класс наследник bpy.types.Operator, потому что я собираюсь проводить операцию над вертексами.

bl_idname - имя нашего действия. Лучше бы ему быть уникальным. Можно не прописывать, тогда автоматически подтянет имя класса. Понадобится в функции регистрации меню, где будет непосредственно добавляться оператор, который этот самый bl_idname и будет использовать. В моем случае это menu_func и register/unregister, в которых он используется.

bl_label - в нашем случае это имя кнопки.

bl_description - описание, думаю подробности тут не нужны.

bl_options - зачем тут нужен Register, я так и не понял. Но вот без 'Undo' действие в истории не появится и Undo/Redo не подлежит (по крайней мере так утверждает документация).

Функция poll - нужна для проверки, можно ли вообще этот оператор в текущих условиях выполнить.

Функция execute - без неё можно обойтись, но рекомендуется, чтобы она всё таки была. Это позволит использовать оператор в макросах и в других Blender скриптах. Должен вернуть по окончанию состояние успешности: {'FINISHED'} или {'CANCELED'}. Что даст понять, нужно ли это действие ревертить в будущем или нет.

У меня пока на её месте заглушка, но всё действия, я буду делать именно в это функции.

Функция invoke - вот она и будет вызываться, если будем взаимодействовать как типичный пользователь, то бишь мышью. Можно прямо как в примере выше, возвращать вызов return self.execute(context). У invoke, чуть по больше контекста о вызывающей стороне (например, координаты мыши), чем у execute, так что в неё можно сделать дополнительные песни и пляски, для более подходящего вызова execute.

С нашим классом мы закончили. Дальше остается функция меню, которая в текущем виде, просто возвращает оператор (что означает в данном случае - кнопку). А в функции register/unregister соответственно наши созданные функции добавляются или удаляются в контекст блендера.

Аддон стадия 1.0

Время писать нормальную функцию execute. Писать и объяснять прямо то, что мне нужно для выравнивания по арке не буду, там будет сплошная математика. Остановимся на аспектах Blender. Поэтому, вместо тяжелой математики, вот вам код, который подвинет ваши вертексы по оси Y на 2 (я знаю, что 2 это много, зато очень наглядно).

    def execute(self, context):
        mode = bpy.context.active_object.mode
        bpy.ops.object.mode_set(mode='OBJECT')
        selectedVerts = [v for v in bpy.context.active_object.data.vertices if v.select]
        print ("selected {} vertexes", len(selectedVerts))
        if len(selectedVerts) < 2:
            self.report({'WARNING'}, "too few points, should be at least 2")
            return {'CANCELLED'}
        for v in  selectedVerts:
            v.co.y += 2
        bpy.ops.object.mode_set(mode=mode)
        return{'FINISHED'}

Первым делом запоминаю текущий режим, который в самом конце, я собираюсь вернуть обратно. Почему это важно? Потому что следующей строчкой, я его поменяю, на режим Object. Почему это важно? Потому, что я собираюсь двигать вертексы, а в режим Edit они двигаться отказываются. А вернуть обратно пользователя в начальный режим, было бы не плохо. Это раздражает переключаться туда-сюда режимы руками после действия.

На счет почему не двигаются вертексы

Как подсказал @Mixail_Soluyanov в режиме Edit надо двигать уже bmesh. Подробности по ссылке

# This example assumes we have a mesh object selected

import bpy
import bmesh

# Get the active mesh
me = bpy.context.object.data

# Get a BMesh representation
bm = bmesh.new()   # create an empty BMesh
bm.from_mesh(me)   # fill it in from a Mesh


# Modify the BMesh, can do anything here...
for v in bm.verts:
    v.co.x += 1.0


# Finish up, write the bmesh back to the mesh
bm.to_mesh(me)
bm.free()  # free and prevent further access

Следующей строчкой, я беру текущий активный объект, и выгребаю из него, все выделенные вертексы. Я точно знаю, что объект меш, потому что у меня написан правильный poll. А так бы пришлось городить всякие конструкции на этом этапе.

print... Что бы увидеть результат принта, нужно открыть системную консоль Window/Toggle System Console. Иначе результат принта не увидеть.

Валидация входящих данных. Обратите внимание на self.report. Вот это уже пользователь увидит. Есть 3 вида репорта {'INFO'}, {'WARNING'} и {'ERROR'}. Думаю смысла их описывать нет. Удобство в том, что текст в них пользователь увидит. Приятный бонус, что варнинг на панели состояний будет оранжевым, а эрор красным.

Пример WARNING
Пример WARNING

В случае с неуспешной валидацией выходим с результатомCANCELLED. Потому что мы ничего не поменяли. И в истории действие светить не надо. На счет ничего не поменяли. Заметили ошибку? Перед возвратом CANCELLED, нет возвращения начального состояния. Так что пользователь выйдет в режим Object. Что бы такого не было, можно заюзать декоратор (поищите python decorator, кто не знает).

Дальше цикл, в котором все выделенные вертексы, двигаются на 2 по оси y.

Ну и возвращаем FINISHED теперь эту операцию можно отменить через UNDO.

Аддон готов.

Аддон - хочу подменю как у LoopTools

Допустим нужно два действия и группировка, а кнопки уже в нем. Что ж не проблема. Надо добавить класс, который будет отвечать за подменю, и всё что в нём, и не забыть зарегистрировать его и второй класс оператора.

#класс отвечающий за под меню
#без _MT_ в имени всё будет работать, но BLENDER ругается при регистрации
class VIEW3D_MT_SUBMENU_Menu(Menu):
    bl_label = "SUBMENU LABEL HERE"

    def draw(self, context):
        layout = self.layout
        #добавляем bl_idname для операторов
        layout.operator("mesh.YOUR_OPERATOR_1")
        layout.operator("mesh.YOUR_OPERATOR_2")


# запоминаем классы для регистрации
classes = (
    VIEW3D_MT_SUBMENU_Menu,
    YOUR_OPERATOR_CLASS_1,
    YOUR_OPERATOR_CLASS_2
)


#функция меню теперь возвращает новый класс.
#который вернет наши две кнопки с операциями
def menu_func(self, context):
    self.layout.menu("VIEW3D_MT_SUBMENU_Menu")
    self.layout.separator() # а это подчеркивание после нашего подменю. можно и убрать

    
# новый регистр
def register():
    # регистрируем классы в цикле
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)


# новый анрегистр
def unregister():
    # удаляем регистраци классов в цикле
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)

Заключение

Теперь у вас на руках код полноценного аддона. В моём случае написание аддона с нуля заняло изрядное количество времени. Но у меня вышло побороть Blender и заставить точки подвинуться куда мне нужно. Стоит обратить внимание, что в Blender есть две сущности мешей: mesh и bmesh. Они немного разные, и немного для разных целей, и немного в разных режимах доступны, и немного... Ну вы поняли, подводные камни, тут могут быть на любом этапе. Тестируйте код во вкладке Scripting - меньше потом голова будет болеть.

Кроме того, есть какие-то волшебные штуки, чтобы аддон нормально тестировать и дебажить, но мне было лень тратить столько времени просто что бы их настроить. Само написание аддона у меня написание заняло меньше времени, чем попытки настроить внятный дебаг.

Ребята в Blender молодцы, отсутствие и неудобство некоторых вещей, они компенсировали, внятным API для собственно их создания для тех кто хочет и может. Правда API не такой уже и внятный, а тех, кто одновременно может и хочет, далеко не так много.

Скачать полный код аддонов можно тут там же демонстрация как это работает. Да, там 2 аддона, первый с двумя операторами и подменю, второй просто с кнопкой сделать арку.

Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+11
Комментарии11

Публикации

Истории

Работа

Python разработчик
108 вакансий
Data Scientist
69 вакансий

Ближайшие события