При разработке аддонов модули должны быть максимально абстрагированные. По очень простой причине – функционал, созданный для текущего аддона, вполне вероятно понадобиться и в следующем аддоне, и возможно даже не в одном. На этапе реализации уже готового аддона, проблема доступа к подобным модулям с общим функционалом решается просто – все необходимые модули включаются в единый пакет и распространяются вместе. Однако на этапе разработки подобные модули гораздо удобнее хранить отдельно, не связывая их с каким-то определенным пакетом, а при необходимости импортировать нужные модули в нужный аддон.
Python, в соответствии с правилами импорта пакетов, предоставляет возможность подключать модули следующими способами:
- Прямым импортированием, если модуль расположен внутри текущего пакета.
Для того, чтобы модуль присутствовал в пакете, его нужно туда скопировать. Это самый простой и в тоже время самый неоптимальный вариант. Дублирование кода, которого всегда следует избегать, здесь максимально. В случае, если в скопированном модуле обнаружится ошибка, для исправления нужно отследить все сделанные копии. Если в одном аддоне модуль будет чем-либо усовершенствован – добавлен новый класс или метод, изменения опять же коснуться только текущей копии, и чтобы внести их во все остальные копии модуля, нужно будет приложить немало сил.
- Если модуль располагается за пределами пакета, его все равно можно импортировать, добавив путь к директории модуля в переменную окружения sys.path.
Таким образом импортируются модули в запускающий скрипт Blender для отладки многомодульных аддонов. Этот вариант лишен основного недостатка первого, но и у него есть свои минусы. Модуль хранится в отдельном месте в единственном экземпляре, все правки и дополнения затрагивают только его и автоматически становятся доступны во всех остальных аддонах, в которые модуль был импортирован. Проблем с дублированием кода нет. Однако в этом случае при создании релиза аддона, необходимо пройтись по всем подключенным таким образом модулям и скопировать их все в распространяемый пакет.
- Создать в пакете аддона символьную ссылку на необходимый модуль, чтобы обращаться к модулю через нее.
Самый удобный вариант, который лишен вышеперечисленных минусов. Python корректно работает с символьными ссылками и в Windows и в Linux. Дублирования кода нет – через символьную ссылку все правки и дополнения вносятся сразу в исходный модуль, никаких реальных копий не создается. В то же время при упаковке пакета в архив для создания распространяемого релиза, архиваторы корректно упаковывают не ссылку на модуль, а помещают в архив копию самого модуля.
Возьмем для примера простейший универсальный класс для работы с двухмерным вектором:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import math class Vector2d(): def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return "Vector2d({x},{y})".format(x = self.x, y = self.y) def add(self, vec2): if isinstance(vec2, Vector2d): return Vector2d(self.x + vec2.x, self.y + vec2.y) def subtract(self, vec2): if isinstance(vec2, Vector2d): return Vector2d(self.x - vec2.x, self.y - vec2.y) def length(self): return math.sqrt(self.x ** 2 + self.y ** 2) |
Его можно использовать во множестве аддонов, поэтому сохраним его в отдельный модуль Vector2d.py в директории d:\Python\Vector2d\.
Создадим отдельный аддон SampleProject, единственной задачей которого будет демонстрация подключения и использования модуля Vector2d.
- Создать директорию d:\Python\SampleProject\
- Создать в ней файл __init__.py
Для того, чтобы подключить к аддону ранее созданный модуль Vectro2d, создадим в директории SampleProject символьную ссылку на этот модуль.
В операционной системе Windows символьные ссылки создаются командой mklink (для Linux – систем нужно использовать команду ln) или с помощью файловых менеджеров, умеющих работать с символьными ссылками (таких как Far). Важное замечание – в операционной системе Windows символьные ссылки поддерживаются начиная с версии Vista. Пользователям старших систем придется использовать 1 или 2 метод работы с модулями.
Создадим командный файл link.cmd со следующей командой:
1 |
mklink "d:\Python\SampleProject\Vector2d.py" "d:\Python\Vector2d\Vector2d.py" |
После выполнения этого файла, в директории d:\Python\SampleProject будет создана символьная ссылка Vector2d.py, указывающая на модуль d:\Python\Vector2d\Vector2d.py.
Для проверки работы в файл __init__.py занесем универсальный код для многомодульных аддонов с небольшими изменениями. В первую очередь в список подключаемых модулей внесем Vector2d.
1 |
modulesNames = ['Vector2d'] |
В функцию register добавим несколько дополнительных строчек кода. Так как эта функция вызывается в момент активации аддона, добавленный код будет сразу же выполнен.
1 2 3 4 5 6 |
v1 = Vector2d.Vector2d(1, 0) v2 = Vector2d.Vector2d(2, 0) print('--------------------------') print(v1.add(v2)) print('--------------------------') |
Напомню, что это плохая практика, в реальном аддоне никакого дополнительного кода в __init__.py быть не должно. Данный код добавляется исключительно для демонстрации, что при использовании символьных ссылок “все работает”. Здесь создаются два вектора v1 и v2 и выводятся результат простейшего действия (сложения) с этими векторами.
Итоговое содержание файла __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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
bl_info = { 'name': 'SampleProject', 'category': 'All', 'version': (0, 0, 1), 'blender': (2, 78, 0) } modulesNames = ['Vector2d'] import sys import importlib modulesFullNames = [] for currentModuleName in modulesNames: if 'DEBUG_MODE' in sys.argv: modulesFullNames.append('{}'.format(currentModuleName)) else: modulesFullNames.append('{}.{}'.format(__name__, currentModuleName)) for currentModuleName in modulesFullNames: if currentModuleName in sys.modules: importlib.reload(sys.modules[currentModuleName]) else: globals()[currentModuleName] = importlib.import_module(currentModuleName) def register(): for currentModuleName in modulesFullNames: if currentModuleName in sys.modules: if hasattr(sys.modules[currentModuleName], 'register'): sys.modules[currentModuleName].register() v1 = Vector2d.Vector2d(1, 0) v2 = Vector2d.Vector2d(2, 0) print('--------------------------') print(v1.add(v2)) print('--------------------------') def unregister(): for currentModuleName in modulesFullNames: if currentModuleName in sys.modules: if hasattr(sys.modules[currentModuleName], 'unregister'): sys.modules[currentModuleName].unregister() if __name__ == "__main__": register() |
Для проверки работы, в отладочном скрипте Blender укажем директорию аддона:
1 |
filesDir = "d:\Python\SampleProject" |
и запустим его нажатием на кнопку Run Script. Открыв консольное окно System Console, можно увидеть результат сложения двух векторов.
Протестировав подключение модуля через символьную ссылку в режиме отладки, попробуем собрать релизный пакет. Для этого директорию d:\Python\SampleProject\ упакуем архиватором zip в архив. Если после этого открыть архив, можно заметить, что файл Vector2d.py упаковался не как символьная ссылка (размер 0 байт), а как реальный файл (размер 568 байт).
Проведем установку аддона из полученного архива.
После активации аддона в консоли System Console выведены результаты сложения векторов, что означает, что аддон корректно установлен и работает без ошибок.