Для отладочного запуска разрабатываемого многофайлового аддона в Blender удобно использовать следующую систему. Однако описанный поход обладает одним недостатком: импортируемые в файле __init__.py модули становятся доступны только после выполнения самого файла (после того, как отработает функция register() ). Это означает, что любые обращения к импортируемым модулям до их регистрации вызовут ошибку. В большинстве случаев это не критично, однако вызовет проблемы, если в аддоне например используется наследование от класса, описанного в импортируемом модуле т.к. описание классов в модуле обрабатывается раньше инициализации аддона.
Чтобы получить больше свободы при работе с импортируемыми модулями, для отладки аддона можно использовать другой принцип – не запускать аддон непосредственно из директории разработки, а инсталлировать его в Blender и проверять работу сразу “на чистовую”. Однако ручная переустановка аддона требует выполнения определенного набора действий, что излишне затрудняет подобную отладку. Решить данную проблему можно проводя переустановку аддона для тестов в автоматическом режиме.
Рассмотрим отладочный запуск на примере рассматриваемого ранее тестового аддона “addCube”, состоящего из трех модулей:
- __init__.py
- addCube.py
- addCubePanel.py
Модули “addCube” (добавление в сцену дефолтного куба) и “addCubePanel” (панель с кнопкой добавления куба в сцену) останутся без изменений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import bpy class addCubeSample(bpy.types.Operator): bl_idname = 'mesh.add_cube_sample' bl_label = 'Add Cube' bl_options = {"REGISTER", "UNDO"} def execute(self, context): bpy.ops.mesh.primitive_cube_add() return {"FINISHED"} def register() : bpy.utils.register_class(addCubeSample) def unregister() : bpy.utils.unregister_class(addCubeSample) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import bpy class addCubePanel(bpy.types.Panel): bl_idname = "panel.add_cube_panel" bl_label = "AddCube" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" bl_category = "Add Cube" def draw(self, context): self.layout.operator("mesh.add_cube_sample", icon='MESH_CUBE', text="Add Cube") def register() : bpy.utils.register_class(addCubePanel) def unregister() : bpy.utils.unregister_class(addCubePanel) |
Зато модуль инициализации аддона __init__.py заметно упрощается.
Так как пакет с модулями аддона теперь всегда будет располагаться в одном месте – в директории аддонов Blender, импортировать нужные для работы модули можно одной командой:
1 |
from . import имя_модуля |
То есть отпадает необходимость предварительного импортирования всех модулей пакета.
Для работы аддона необходимо импортировать только главные модули “addCube” и “addCubePanel”.
1 2 |
from . import addCube from . import addCubePanel |
Если (в более сложных аддонах) где-то во внутренних модулях требуется сделать нужный импорт, никакого предварительного импорта в файле __init__.py делать не требуется. Для импорта во внутреннем модуле достаточно все той же команды “from . import имя_модуля”.
В файле __init__.py помимо импорта главных модулей остаются только функции “register” и “unregister” для их инициализации и блок с описанием аддона.
Полный код файла __init__.py для тестового аддона имеет следующий вид:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
bl_info = { 'name': 'Test Multifile Addon', 'category': 'All', 'version': (0, 0, 1), 'blender': (2, 79, 0) } from . import addCube from . import addCubePanel def register(): addCube.register() addCubePanel.register() def unregister(): addCube.unregister() addCubePanel.unregister() if __name__ == "__main__": register() |
Модули аддона готовы, осталось модифицировать отладочный скрипт в Blender.
Откроем Blender, перейдем в окно Text Editor и создадим новый скрипт.
Для начала определим входные параметры: название директории (пакета) аддона, полный путь к директории разработки аддона и сами модули, которые нужны для работы аддона – все файлы с расширением “.py”. Если в аддон нужно включить какие-то дополнительные файлы, например “README.md” и “LICENSE”, укажем их отдельным списком:
1 2 3 4 5 6 |
addon_folder = 'addCube' source_path = 'd:/dev' files_mask = '*.py' add_files_names = ['README.md', 'LICENSE'] |
Соберем все необходимые файлы с полными путями в список “files”:
1 2 3 4 5 6 |
import glob import os addon_path = os.path.join(source_path, addon_folder) files = glob.glob(os.path.join(addon_path, files_mask)) + [os.path.join(addon_path, file) for file in add_files_names] |
В системной директории для временных файлов создадим свою временную папку. В ней мы подготовим файлы аддона к инсталляции в Blender. Использование конструкции “with” позволит автоматически подчистить все временные файлы и директории по окончании выполнения скрипта.
1 2 3 |
import tempfile with tempfile.TemporaryDirectory() as temp_dir: |
Во временную директорию скопируем все собранные ранее файлы. Копировать нужно в пакет, т.е. в поддиректорию с именем таким же, как имя аддона.
1 2 3 4 5 6 7 8 |
import shutil addon_folder_to_files = os.path.join(temp_dir, addon_folder, addon_folder) os.makedirs(addon_folder_to_files) for file in files: shutil.copy(file, addon_folder_to_files) |
Упакуем готовый пакет с файлами в zip-архив:
1 2 3 4 5 |
addon_folder_to_zip = os.path.join(temp_dir, addon_folder) shutil.make_archive(addon_folder_to_zip, 'zip', addon_folder_to_zip) addon_zip_path = addon_folder_to_zip + '.zip' |
В переменную “addon_zip_path” сохраняется полный путь к готовому zip-архиву, из которого будет проводиться дальнейшая установка аддона.
При разработке аддона, его код будет постоянно обновляться. Поэтому каждый запуск отладочного скрипта сначала должен удалить старую (установленную при предыдущем запуске) версию аддона.
1 2 3 4 |
import bpy bpy.ops.wm.addon_disable(module = addon_folder) bpy.ops.wm.addon_remove(module = addon_folder) |
Сначала аддон деактивируется, после чего удаляется из Blender. Однако импортированные в процессе работы аддона модули при этом из памяти не удаляются. Чтобы обновился весь код, их так же нужно удалить. Удаляем только те модули, которые загружены из пакета нашего аддона.
1 2 3 4 |
for module in list(sys.modules.keys()): if hasattr(sys.modules[module], '__package__'): if(sys.modules[module].__package__ == addon_folder): del sys.modules[module] |
Теперь можно заново установить аддон из временной директории:
1 2 |
bpy.ops.wm.addon_install(filepath = addon_zip_path, overwrite = True) bpy.ops.wm.addon_enable(module = addon_folder) |
Полный код скрипта, переустанавливающего аддон для отладочных запусков следующий:
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 40 41 42 43 |
import tempfile import os import shutil import glob import bpy import sys addon_folder = 'addCube' source_path = 'd:/dev' files_mask = '*.py' add_files_names = ['README.md', '.gitignore', 'LICENSE'] addon_path = os.path.join(source_path, addon_folder) files = glob.glob(os.path.join(addon_path, files_mask)) + [os.path.join(addon_path, file) for file in add_files_names] with tempfile.TemporaryDirectory() as temp_dir: addon_folder_to_files = os.path.join(temp_dir, addon_folder, addon_folder) os.makedirs(addon_folder_to_files) for file in files: shutil.copy(file, addon_folder_to_files) addon_folder_to_zip = os.path.join(temp_dir, addon_folder) shutil.make_archive(addon_folder_to_zip, 'zip', addon_folder_to_zip) addon_zip_path = addon_folder_to_zip + '.zip' bpy.ops.wm.addon_disable(module = addon_folder) bpy.ops.wm.addon_remove(module = addon_folder) for module in list(sys.modules.keys()): if hasattr(sys.modules[module], '__package__'): if(sys.modules[module].__package__ == addon_folder): del sys.modules[module] bpy.ops.wm.addon_install(filepath = addon_zip_path, overwrite = True) bpy.ops.wm.addon_enable(module = addon_folder) |
Теперь для того, чтобы полностью обновить код аддона в Blender достаточно всего лишь нажать кнопку “Run Script”.
Этот отладочный скрипт является универсальным, его можно использовать при разработке любого мультифайлового аддона, нужно только изменить директорию аддона и путь к директории разработки на новые.
Я правильно понимаю, что если аддон допустим будет содержать большие портативные либы, то каждый такой релоад будет гонять сотни мегабайт (а может и гигабайт) по винту?
И это может быть в теории еще и не очень быстрым?
Если да, то можно сделать какой-то игнор лист или что-то подобное?
У меня на гитхабе выложена чуть более продвинутая версия
https://github.com/Korchy/blender_dev_tools_int/blob/master/addon_reinst_multifile.py
в ней можно задавать масками через * и расширение, файлы и директории, которые нужно копировать при установке аддона.
Но если аддон требует чего-то и этого нет в его диретории, он не будет работать
Если аддон использует другие пакеты, лучшим вариантом будет установить эти пакеты в сам Блендер отдельно и тогда они не нужны будут в директории аддона и скорость обмена файлами повысится.
Не работало на версии 2.92.
Обновленная для 2.92 версия:
Спасибо!
… ты так скоро своё IDE для разработки дополнений напишешь …
Пока удается использовать чужие. Хотя мысль писать свою ide интересная )