アドオンを複数ファイル構成にする際の注意点をいくつか紹介する。
アドオンファイルは単一ファイル構成だとファイルの取り扱いが楽だが、膨大な行になってくると管理が大変になる。 ファイルを複数に分けると管理が楽になる。
メインのファイル以外の構成ファイルは、サブモジュールという模様。
もくじ
最初に読み込まれるファイル名は __init__.py にする
最初に読み込まれるファイルの名前は、__init__.py にする。
これがアドオンを読み込む際に最初に読み込まれるので、このファイルにサブモジュールのインポートなどを書く。
分類する
ファイルの種類ごとにフォルダー分類する。
明確に分類できるならした方がよいが、自分の扱いやすさも考慮すること。
自分は主に下記のようなフォルダー分類をしている。
- operators
- オペレーター全般。短くopとも。
- ui
- UI・メニューに関するもの。
- utils
- アドオンの様々な所で使われる細かな関数。
サブモジュールをインポートする
__init__.pyファイルの先頭付近で、サブモジュールをインポートする。
■ 例 下記のようなファイル構成の場合
- __init__.py
- hoge.py
- boo.py
- operatorsフォルダー
- op_moge.py
- op_goo.py
from .hoge import * from .boo import * from .operators.op_moge import * from .operators.op_goo import *
- . が階層を表す。
- 最初の . は、__init__.py ファイル直下の意味。
- * は、サブモジュール内すべての要素を読み込む。
- 注意点として、* だと全ての関数がその関数名のまま読み込まれるため、同名の関数があるとごっちゃになってしまう。
- 正しい書き方では、これを使わずに必要な関数のみをインポートした方がきれい。自分は面倒なのでやっていない。
サブモジュールは再読み込みできない
複数ファイル構成のアドオンは、設定 → アドオン → 再読み込み や、アドオンのチェックボックスのオンオフだけでは、サブモジュールを再読み込みできない。
そのため、毎回Blenderを再起動する必要がある。
デバッグ作業が非常に手間になってしまう。
アドオン再読み込みでサブモジュールも更新するようにするには、アドオン側に読み込み方法を付け加える必要がある。
解決法 - 既にサブモジュールがロードされていれば、再読み込みさせる
アドオン内にimportlib.reload() を作っておき、Blenderで[script.reload] のオペレーターを実行すると、再読み込みすることができる。
importlib.reload() は、サブモジュールがロードされていれば再ロードする設定する、という読み込み方にする。
- 画面一番左上のBlenderアイコン > システム > スクリプトを再読み込み
スクリプト
if 'bpy' in locals(): from importlib import reload import sys for k, v in list(sys.modules.items()): if k.startswith('addon_folder_name.'): reload(v)
ルート階層の__init__.pyファイル内に上記のコードを書く。
"addon_folder_name."の箇所を、"〇〇."のようにそのアドオンのフォルダー名に変えること。
旧コード
こちらは古い方法だが一応残しておく。
if "bpy" in locals(): import importlib reloadable_modules = [ # リストに読み込むものをまとめる "keymap", "op_main", "ui_panel", "property", ] for module in reloadable_modules: # リスト内のものがすでにあれば、reloadを発動する if module in locals(): importlib.reload(locals()[module]) 初回時に普通に読み込む from .keymap import * from .op_main import * from .ui_panel import * from .property import *
旧コードの注意点
このコードでは、深いフォルダー階層のファイルまでロードすることができない。
ルートの__init__.pyと、各フォルダー内の__init__.py内にこれを入れること。
再読み込み実行時にエラーが出る場合
1つのアドオンでエラーが出ると、全てのアドオンの再読み込みが失敗する。
問題が出たら1つ1つ修正すること。
修正して再読み込みすれば、他のアドオンも読み込むことができる。
特定ファイルが読み込めない場合
特定ファイルに何らかの原因があり、読み込み失敗する場合がある。
この問題があるかどうかを確認するには、class読み込みを一旦全てコメントアウトし、半分ずつサブモジュール読み込みをコメントアウトして原因を探る。
参考
Pythonモジュールがインポートされると、ソースファイルが変更されたときにインタープリターによって(つまり、自動的に)再ロードされることはありません。
Blenderには、F8ショートカット(デフォルトのキーマップ内)によってトリガーされる、すべてのpyスクリプトをリロードするオペレーターがあります。
これで、登録済みのアドオン(およびスタートアップ/ UIスクリプト)のみがリロードされます。アドオンに__init__.pyアドオンファイルでそのような構成を使用して、自分でリロードを処理する必要があるサブモジュールがいくつか含まれている場合:
BlenderはPythonスクリプトの変更を無視します - stackovernet
https://blender.stackovernet.com/ja/q/6802
アドオン設定が参照できない
アドオン設定のプロパティを参照する時に、一般的には下記のようにするが、これをサブモジュールでやろうとするとエラーを吐いてしまう。
bpy.context.preferences.preferences.addons[__name__].preferences
ここでの__name__では、アドオンのメインファイルの名を欲しているのだが、うまくいかない。
__name__は、その実行時のファイルのファイル名を返す。
単一ファイル構造ならそれで上手く行くが、フォルダー構造だと、__name__がメインのフォルダ以外のファイル名を返してしまうため、うまくいかない。
解決法1 __package__ を使う
そのため、__name__の代わりに__package__を使う。
AddonPreferences クラス内での bl_idnameを、__package__にすること。
class HOGE_MT_preferences(bpy.types.AddonPreferences): bl_idname = __package__
bpy.context.preferences.preferences.addons[__package__].preferences
解決法2 name.partition(“.”)[0]
下記のようにする。
こちらはAddonPreferences クラス内で、 __name__から変える必要はない。
bpy.context.preferences.preferences.addons[__name__.partition(".")[0]].preferences