はじめてのBlenderアドオン開発 (Blender 2.7版)

Last Update: 2019.4.2

2-7. アドオンのソースコードを
複数ファイルへ分割する

アドオンへ機能を追加し続けると、やがてソースコードのサイズが肥大化していきます。 ソースコードが肥大化することで管理が大変になってきたら、ソースコードを複数ファイルに分割することを検討しましょう。 本節では、アドオンのソースコードを複数ファイルへ分割する方法を紹介します。

ソースコードを複数ファイルへ分割するメリット

前節までに紹介してきたサンプルのソースコードは、単一のファイルで構成されていました。 これまでのサンプルは非常に単純な機能しか持たないアドオンでしたので、ソースコードの行数も比較的少なく、1つのファイルでも問題なく扱うことができました。 しかし本格的なアドオンは、本書で紹介しているアドオンよりも複雑な処理になる傾向が高いです。 複雑でコードの規模が大きい処理の全てを1つのソースコードに書くと、後でソースコードを修正したい時に修正箇所を見つけるのが大変です。

一般的に物事を分割して考えることは、複雑なことを理解する時の助けになります。 アドオンの開発においても同じことが言え、ソースコードを複数のファイルに分割することで管理しやすくなります。 例えば、後からソースコードを修正する時に1つのファイルで構成されているとファイル全体を見る必要があるのに対し、ファイルが適切に分割されていれば関係のないファイルのコードを見る必要がなくなるため、目的の修正箇所を早く見つけることができるようになります。

作成するアドオンの仕様

アドオンを作成する

1-2節 のBlenderアドオン用フォルダに、ディレクトリ名 sample_2_7 のディレクトリを作成します。 1-5節 を参考にして以下のソースコードを入力し、作成したディレクトリの下にファイル名をそれぞれ __init__.py , enlarge_object.py , reduce_object.py として保存してください。

__init__.py

bl_info = {
    "name": "サンプル2-7: オブジェクトを拡大・縮小するアドオン(ファイル分割版)",
    "author": "Nutti",
    "version": (2, 0),
    "blender": (2, 75, 0),
    "location": "3Dビュー > オブジェクト",
    "description": "オブジェクトを拡大・縮小するサンプルアドオン(ファイル分割版)",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}


if "bpy" in locals():
    import imp
    imp.reload(enlarge_object)
    imp.reload(reduce_object)
else:
    from . import enlarge_object
    from . import reduce_object


import bpy


# メニューを構築する関数
def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(enlarge_object.EnlargeObject.bl_idname)
    self.layout.operator(reduce_object.ReduceObject.bl_idname)


# アドオン有効化時の処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_object.append(menu_fn)
    print("サンプル 2-7: アドオン「サンプル 2-7」が有効化されました。")


# アドオン無効化時の処理
def unregister():
    bpy.types.VIEW3D_MT_object.remove(menu_fn)
    bpy.utils.unregister_module(__name__)
    print("サンプル 2-7: アドオン「サンプル 2-7」が無効化されました。")


# メイン処理
if __name__ == "__main__":
    register()

enlarge_object.py

import bpy


# オブジェクトを拡大するオペレーション
class EnlargeObject(bpy.types.Operator):

    bl_idname = "object.enlarge_object"
    bl_label = "選択オブジェクトの拡大"
    bl_description = "選択中のオブジェクトを拡大します"
    bl_options = {'REGISTER', 'UNDO'}

    # メニューを実行した時に呼ばれる関数
    def execute(self, context):
        active_obj = context.active_object
        active_obj.scale = active_obj.scale * 2.0
        self.report({'INFO'}, "サンプル 2-7: 「%s」を2倍に拡大しました。" % (active_obj.name))
        print("サンプル 2-7: オペレーション「%s」が実行されました。" % (self.bl_idname))

        return {'FINISHED'}

reduce_object.py

import bpy


# オブジェクトを縮小するオペレーション
class ReduceObject(bpy.types.Operator):

    bl_idname = "object.reduce_object"
    bl_label = "選択オブジェクトの縮小"
    bl_description = "選択中のオブジェクトを縮小します"
    bl_options = {'REGISTER', 'UNDO'}

    # メニューを実行した時に呼ばれる関数
    def execute(self, context):
        active_obj = context.active_object
        active_obj.scale = active_obj.scale * 0.5
        self.report(
            {'INFO'},
            "サンプル 2-7: 「%s」を1/2倍に縮小しました。" % (active_obj.name)
        )
        print("サンプル 2-7: オペレーション「%s」が実行されました。" % (self.bl_idname))

        return {'FINISHED'}

アドオンを使用する

アドオンを有効化する

1-5節 を参考に作成したアドオンを有効化すると、コンソールウィンドウに以下の文字列が出力されます。

サンプル2-7: アドオン「サンプル2-7」が有効化されました。

アドオンの機能を使用する

アドオンの機能は 2-2節 で紹介したサンプルと同じ機能ですので、ここでは説明を省きます。

アドオンを無効化する

1-5節 を参考にして有効化したアドオンを無効化すると、コンソールウィンドウに以下の文字列が出力されます。

サンプル2-7: アドオン「サンプル2-7」が無効化されました。

ソースコードの解説

モジュールとパッケージ

ソースコードの解説に入る前に、Pythonのモジュールとパッケージの概念について理解しておく必要があります。

Pythonでは、 クラスや関数、変数が定義された1つのスクリプトファイル をモジュールといいます。 ソースコード内でモジュールをインポートすることによって、モジュールで定義したクラスや関数、変数が使用可能となります。

そして、複数のモジュールを1つにまとめたもの をパッケージといいます。 Pythonでパッケージを作成するためには、ディレクトリを作成し、その配下にファイル __init__.py とパッケージ化するモジュール一式を置きます。 Pythonで一般的にパッケージを作る場合は __init__.py は空のファイルでもよいですが、後で説明するようにBlenderでパッケージを作る場合は、特別な処理を記載する必要があります。

モジュール名はスクリプトのファイル名、パッケージ名は __init__.py とモジュール一式をまとめたディレクトリ名となります。 このことを踏まえると、本節のサンプルについてモジュールとパッケージの関係は、sample_2_7パッケージにはenlarge_objectモジュールとreduce_objectモジュールが含まれる と表すことができます。

__init__.pyとは

本節で作成したサンプルのソースコードは3つのファイルから構成されます。3つのファイルのうち最も重要なファイルは、__init__.py です。 __init__.py には、アドオン有効化時に実行される処理やモジュールの読み込み処理 を記載します。 (本節のサンプルでは、enlarge_object.py , reduce_object.py のモジュールを読み込む処理を記載します。)

一般的に __init__.py には、パッケージインポート時に呼び出される処理を記載することになっています。 register 関数や unregister 関数が定義され、これらの関数がアドオンの有効化/無効化時に呼び出されることから、アドオンの有効化はBlender上でのパッケージのインポートと同様であると考えることができます。

以降の解説では、__init__.py を中心に解説していきます。 enlarge_object.py , reduce_object.py2-2節 と同様のため、ここでは説明を省略します。

モジュールのインポート

モジュールのインポートは bpy モジュールを読み込んだ時と同様、import 指示により行います。

import enlarge_object
import reduce_object

しかし、上記のコードはアドオン有効化時に enlarge_objectreduce_object が見つからないとエラーになります。 これは、Blenderが enlarge_objectreduce_object の場所を知らないからです。

Blenderに enlarge_objectreduce_object の場所を知らせるために、以下のように書き換えます。

from . import enlarge_object
from . import reduce_object

from . を付け加えたことにより、 __init__.py が置かれたディレクトリ、つまり自身のパッケージの中から2つのモジュール enlarge_objectreduce_object を探すようになります。 上記のコードで、アドオン有効化時のエラーは出力されなくなりました。 しかし、もう1つやるべきことがあります。

1-5節 では、Blenderを再起動せずにBlenderの機能である『Reload Scripts』機能を用いて、アドオンをアップデートする方法を紹介しました。 1-4節 でも少し触れましたが、『Reload Scripts』機能を使っても複数のファイルから構成されるアドオンをアップデートできない場合があります。

結論から言うと、『Reload Scripts』機能を使った時に __init__.py の処理で、インポートしているモジュールの再読み込みに失敗していることが原因 です。 from . import ... を用いてモジュールの再読み込みができると思う方もいるかもしれませんが、既にインポートされているモジュールに対するインポート処理は無視されてしまいます。

この問題に対処するため、以下のようにインポート処理を書き換える必要があります。

if "bpy" in locals():
    import imp
    imp.reload(enlarge_object)
    imp.reload(reduce_object)
else:
    from . import enlarge_object
    from . import reduce_object


import bpy

上記のコードを簡単に説明します。 最初のif文では、bpy モジュールが既にインポートされているかを判定します。 インポートされていなければ、Blender起動後に初めてアドオンが読み込まれたと判定し、関連するモジュールをインポートします。

一方、bpy モジュールが既にインポートされていれば、Blender起動後の2回目以降の読み込みであると考えることができます。 この時は imp モジュールを用いて、既にインポートされているモジュールを再度読み込みます。 2回目以降の読み込みと判定されるのは、『Reload Scripts』機能によりアドオンが再度読み込まれた時になるため、これは正常に動作します。

なお、bpy モジュールのインポートは、モジュール読み込み処理の後でなければなりません。 もし bpy モジュールのインポートをモジュール読み込み処理の前に書いてしまうと、最初のif文で真と判定されてしまいます。

bl_info 変数の作成

ファイル __init__.py には、bl_info 変数を定義する必要があります。 bl_info については、2-1節 で既に説明しているので、ここでは説明を省略します。

アドオン有効化・無効化時の処理

アドオンの有効化・無効化時呼ばれる、register 関数と unregister 関数も __init__.py に記載する必要があります。

register 関数と unregister 関数も、2-1節 で説明した内容とほぼ同じですが、1点異なることがあります。

register 関数や unregister 関数の処理内で、bpy.types.VIEW3D_MT_object.append 関数の引数に渡す menu_fn 関数を見てください。 menu_fn 関数の中で self.layout.operator 関数が呼ばれているのは同じですが、引数のクラス名の前にモジュール名が追加されています。 bpy モジュールと同様に enlarge_object もモジュールであるため、モジュール内のクラスや関数などにアクセスするためにモジュール名が必要になります。

enlarge_object.py と reduce_object.py

enlarge_object.py には EnlargeObject クラス、reduce_object.py には ReduceObject クラスが記載されています。 コードの内容は、2-1節 と同じであるため、ここでは説明を省略します。

まとめ

アドオンのソースコードを複数ファイルに分割する方法を紹介しました。

ソースコードは規模が大きくなるにつれ管理しにくくなる傾向があるため、ある程度ソースコードの規模が大きくなってきたら複数のファイルに分割することを検討すべきです。 ソースコードを分割する目安は行数や機能数など様々ですが、筆者はアドオンを機能単位に分割するように心がけています。 これにより、機能に因んだファイル名をつけることができるようになります。 また、機能ごとにモジュールが分かれるため、特定の機能を修正したいときに目的のソースコードを素早く見つけることができます。

ポイント