MRが楽しい

MRやVRについて学習したことを書き残す

Blender 2.8のPython APIドキュメントを少しずつ読み解く Python APIの概要 その3

本日は Blender2.8 の調査枠です。
Blender 2.8 の Python API ドキュメントを少しずつ読みつつ試していきます。

Blender 2.8 Python API Documentation

以下のページを日本語訳しつつ実際に試しつつ記事を進めていきます。
docs.blender.org
docs.blender.org

今日は「Python APIの概要」の登録に関してです。
f:id:bluebirdofoz:20190922220841j:plain

登録

モジュール登録

起動時にロードされるBlenderモジュールにはregister()関数と、unregister()関数が必要です。
これらは、Blenderがコードから呼び出す唯一の関数です。それ以外は通常のPythonモジュールです。

シンプルなBlender / Pythonモジュールは以下のようになります。

import bpy

class SimpleOperator(bpy.types.Operator):
    """ See example above """

def register():
    bpy.utils.register_class(SimpleOperator)

def unregister():
    bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
    register()

これらの関数は通常、スクリプトで実行され、メニュー項目が追加される場合があります。
また、独自のツールのデータを設定する内部目的で使用することもできます。
しかし新しいブレンドファイルがロードされたときにはスクリプトが再実行されないため注意してください。

登録/登録解除の呼び出しが使用できるため、Blenderの実行中にアドオンを切り替えてスクリプトを再ロードすることができます。
登録呼び出しがスクリプトの本文に配置された場合、登録時にインポートが呼び出されます。
つまり、モジュールのインポートとそのクラスのBlenderへのロードが同時に行われます。

これは、スクリプトが別のモジュールからクラスをインポートするときに問題になります。
どのクラスがいつロードされるかを管理するのが難しくなります。

一例の最後の2行はテスト用です。

if __name__ == "__main__":
    register()

これにより、スクリプトテキストエディターで直接実行して、変更をテストできます。
このregister()呼び出しは、スクリプトがモジュールとしてインポートされた場合、実行されません。
f:id:bluebirdofoz:20190922220923j:plain

クラス登録

クラスをBlenderに登録すると、クラス定義がBlenderにロードされ、既存の機能とともに使用できるようになります。
このクラスがロードされると、クラスの元の名前ではなく bl_idname を使用して bpy.types からアクセスできます。

クラスをロードするとき、以下のチェックを実行します。
・すべての必要なプロパティと関数が見つかること
・プロパティが正しい型を持っていること
・関数が正しい数の引数を持っていること

クラス定義に問題がある場合は、登録時に発生します。以下に例を示します。

def execute(self, context, spam) のような関数の引数を使用すると、以下の例外が発生します

ValueError: expected Operator, SimpleOperator class "execute" function to have 2 args, found 3

f:id:bluebirdofoz:20190922220944j:plain

bl_idname = 1 を使用すると以下の例外が発生します。

TypeError: validating class error: Operator.bl_idname expected a string type, not int

f:id:bluebirdofoz:20190922221007j:plain

複数クラス

※ Blender2.8では本章で紹介する register_module は削除されている。
  代わりに bpy.utils.register_classes_factory を利用する。
wiki.blender.org

クラスをBlenderにロードする方法は、単純なケースでは bpy.utils.register_class を呼び出すだけで十分です。
しかし、クラスが多い場合やパッケージサブモジュールに独自のクラスがある場合は、全てを登録するのは面倒です。

より便利なロード/アンロードのために、以下の関数が存在します。
・bpy.utils.register_module(モジュール)
・bpy.utils.unregister_module(モジュール)

独自の演算子、パネルメニューなどの多くを定義するスクリプトは、次のように記述するだけです。

def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

内部的にBlenderは、登録可能なタイプのサブクラスを収集ます。
それらが定義されているモジュール毎にサブクラスを保存します。
モジュール名を bpy.utils.register_module に渡すことで、このモジュールとそのサブモジュールによって作成されたすべてのクラスを登録できます。

クラス間の依存関係

Blenderをカスタマイズする場合、独自の設定をグループ化する必要がある場合があります。
他のスクリプトと共存する必要があるためです。
これらのプロパティクラスをグループ化するには、グループ内のグループまたはグループ内のコレクションに対して、登録/登録解除の順序に気を付ける必要があります。

カスタムプロパティグループは、それ自体が登録が必要なクラスです。
カスタムエンジンのマテリアル設定を保存する例を以下に示します。

# Create new property
# bpy.data.materials[0].my_custom_props.my_float
import bpy

class MyMaterialProps(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()

def register():
    bpy.utils.register_class(MyMaterialProps)
    bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialProps)

def unregister():
    del bpy.types.Material.my_custom_props
    bpy.utils.unregister_class(MyMaterialProps)

if __name__ == "__main__":
    register()

f:id:bluebirdofoz:20190922221052j:plain

クラスは、プロパティで使用する前に登録する必要があります。登録しないと、エラーが発生します。

ValueError: bpy_struct "Material" registration error: my_custom_props could not register
# Create new property group with a sub property
# bpy.data.materials[0].my_custom_props.sub_group.my_float
import bpy

class MyMaterialSubProps(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()

class MyMaterialGroupProps(bpy.types.PropertyGroup):
    sub_group = bpy.props.PointerProperty(type=MyMaterialSubProps)

def register():
    bpy.utils.register_class(MyMaterialSubProps)
    bpy.utils.register_class(MyMaterialGroupProps)
    bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialGroupProps)

def unregister():
    del bpy.types.Material.my_custom_props
    bpy.utils.unregister_class(MyMaterialGroupProps)
    bpy.utils.unregister_class(MyMaterialSubProps)

if __name__ == "__main__":
    register()

最も低いクラスを最初に登録する必要があります。登録解除はその逆です。
f:id:bluebirdofoz:20190922221121j:plain

クラスの操作

プロパティは、Blenderの実行中に追加および削除できます。
通常、登録または登録解除で行いますが、特別な場合には、スクリプトの実行中にタイプを変更すると便利な場合があります。

以下に例を示します。

# add a new property to an existing type
bpy.types.Object.my_float = bpy.props.FloatProperty()
# remove
del bpy.types.Object.my_float

f:id:bluebirdofoz:20190922221736j:plain

自分で定義したPropertyGroupサブクラスに対しても同様に機能します。

class MyPropGroup(bpy.types.PropertyGroup):
    pass
MyPropGroup.my_float = bpy.props.FloatProperty()

これは以下と同等です

class MyPropGroup(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()
動的な定義済みクラス(高度)

レンダーマンシェーダー定義など、データの指定子がBlenderにない場合があります。
それらをタイプとして定義し、その場で削除することが役立つ場合があります。

for i in range(10):
    idname = "object.operator_%d" % i

    def func(self, context):
        print("Hello World", self.bl_idname)
        return {'FINISHED'}

    opclass = type("DynOp%d" % i,
                   (bpy.types.Operator, ),
                   {"bl_idname": idname, "bl_label": "Test", "execute": func},
                   )
    bpy.utils.register_class(opclass)

type() はクラスを定義するために呼び出されています。
これはPythonでクラスを作成するための代替構文であり、クラスを動的に構築するのに適しています。

以下に演算子を呼び出す例を示します。

>>> bpy.ops.object.operator_1()
Hello World OBJECT_OT_operator_1
{'FINISHED'}
>>> bpy.ops.object.operator_2()
Hello World OBJECT_OT_operator_2
{'FINISHED'}

f:id:bluebirdofoz:20190922221807j:plain