はじめてのBlenderアドオン開発

Last Update: 2023.3.1

Blender 2.8~3.0

はじめてのBlenderアドオン開発

Blender 2.8~3.0

Last Update: 2023.3.1

3-7. プリファレンスを活用する

プリファレンスには、アドオンごとに提供されるアドオンの設定情報があります。 ユーザはこの設定情報から、アドオンが提供する設定内容を変更できます。 アドオン開発者は、アドオンの機能に割り当てるキーボードのキーやUIのフォントなど、ユーザに設定させたい情報をプリファレンスに配置できます。 プリファレンスを活用することで、Sidebarをはじめとした既存のUIの邪魔にならないように、アドオンの設定を変更するUIを提供できます。

プリファレンス

プリファレンスにおけるアドオン設定情報

プリファレンス とは、トップバーの [編集] > [プリファレンス...] から開くことのできるウィンドウを指します。 これまでの節では、プリファレンスを使ってBlenderのUIを日本語化したり、アドオンの有効/無効化を行ったりしてきました。 これらに加えて、アドオンの設定情報を配置できる場所が、プリファレンスに存在します。 ユーザは、プリファレンスに配置されたアドオンの設定を行うためのUIから、ユーザの好みに合わせてアドオンの設定を行います。

プリファレンス上にアドオンの設定情報を配置する方法を説明する前に、アドオンの設定情報が配置される場所を知っておく必要があります。 ここでは、筆者が作成したアドオン『Magic UV』を使って確認します。

1 トップバーの [編集] > [プリファレンス...] を実行して表示される プリファレンス で、[アドオン] タブを選択し、『Magic UV』を有効化した状態で左の矢印をクリックして、アドオンの詳細情報を表示します。
2 [プリファレンス:] と書かれているところが、本節で説明するアドオンの設定情報の配置先になります。

プリファレンスに追加すべきアドオン設定

これまで、アドオンの設定を変更するUIの配置場所として、Sidebarやオペレータプロパティを紹介しましたが、本節では新たに、プリファレンスと呼ばれる配置先が登場しました。 頻繁に変更しないアドオンの設定情報をプリファレンスに配置することで、SidebarやオペレータプロパティのUIが複雑になることを避けることができます。

プリファレンスに配置する情報として、適切だと考えられるものを次に示します。

本節のサンプルアドオンでは、上から2番目の用途で、プリファレンスにアドオンの設定を変更するUIを配置します。

作成するアドオンの仕様

本節では、3-5節 で紹介したサンプルアドオンを改造します。

アドオンを作成する

1-5節 を参考にして次のソースコードを入力し、ファイル名 sample_3-7.py として保存してください。

import datetime

import bpy
import blf
from bpy.props import IntProperty, IntVectorProperty


bl_info = {
    "name": "サンプル 3-7: 日時を表示するアドオン③",
    "author": "ぬっち(Nutti)",
    "version": (3, 0),
    "blender": (2, 80, 0),
    "location": "3Dビューポート > Sidebar > サンプル 3-7",
    "description": "現在の日時を表示するアドオン(プリファレンス利用版)",
    "warning": "",
    "support": "TESTING",
    "doc_url": "",
    "tracker_url": "",
    "category": "3D View"
}


# リージョン情報の取得
def get_region(context, area_type, region_type):
    region = None
    area = None

    # 指定されたエリアの情報を取得する
    for a in context.screen.areas:
        if a.type == area_type:
            area = a
            break
    else:
        return None
    # 指定されたリージョンの情報を取得する
    for r in area.regions:
        if r.type == region_type:
            region = r
            break

    return region


# 日時を表示するオペレータ
class SAMPLE37_OT_ShowDatetime(bpy.types.Operator):

    bl_idname = "object.sample37_show_datetime"
    bl_label = "日時を表示"
    bl_description = "日時を表示します"

    # 描画ハンドラ
    __handle = None

    @classmethod
    def is_running(cls):
        # 描画中はTrue
        return True if cls.__handle else False

    @classmethod
    def __handle_add(cls, context):
        if not cls.is_running():
            # 描画関数の登録
            cls.__handle = bpy.types.SpaceView3D.draw_handler_add(
                cls.__draw, (context, ), 'WINDOW', 'POST_PIXEL'
            )

    @classmethod
    def __handle_remove(cls, context):
        if cls.is_running():
            # 描画関数の登録を解除
            bpy.types.SpaceView3D.draw_handler_remove(
                cls.__handle, 'WINDOW'
            )
            cls.__handle = None

    @classmethod
    def __draw(cls, context):
        prefs = context.preferences.addons[__name__].preferences

        # リージョンの幅を取得するため、描画先のリージョンを得る
        region = get_region(context, 'VIEW_3D', 'WINDOW')

        # 描画先のリージョンへテキストを描画
        if region is not None:
            blf.color(0, 1.0, 1.0, 1.0, 1.0)
            blf.size(0, prefs.font_size, 72)
            blf.position(0, prefs.position[0],
                         region.height - prefs.position[1], 0)
            date_str = datetime.datetime.now().strftime("%Y.%m.%d")
            blf.draw(0, date_str)

    def invoke(self, context, event):
        op_cls = SAMPLE37_OT_ShowDatetime

        if context.area.type == 'VIEW_3D':
            # [開始] ボタンが押された時の処理
            if not op_cls.is_running():
                self.__handle_add(context)
                print("サンプル 3-7: 日時の表示処理を開始しました。")
            # [終了] ボタンが押された時の処理
            else:
                self.__handle_remove(context)
                print("サンプル 3-7: 日時の表示処理を終了しました。")
            # エリアを再描画
            if context.area:
                context.area.tag_redraw()
            return {'FINISHED'}
        else:
            return {'CANCELLED'}


# UI
class SAMPLE37_PT_ShowDatetime(bpy.types.Panel):

    bl_label = "日時を表示"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "サンプル 3-7"
    bl_context = "objectmode"

    def draw(self, context):
        op_cls = SAMPLE37_OT_ShowDatetime

        layout = self.layout
        # [開始] / [停止] ボタンを追加
        if not op_cls.is_running():
            layout.operator(op_cls.bl_idname, text="開始", icon="PLAY")
        else:
            layout.operator(op_cls.bl_idname, text="終了", icon="PAUSE")


# プリファレンスのアドオン設定情報
class SAMPLE37_Preferences(bpy.types.AddonPreferences):
    bl_idname = __name__

    position: IntVectorProperty(
        name="位置",
        description="描画位置",
        size=2,
        min=0,
        default=(100, 120),
        subtype='TRANSLATION'
    )
    font_size: IntProperty(
        name="フォントサイズ",
        description="描画フォントサイズ",
        min=10,
        max=100,
        default=30,
    )

    def draw(self, context):
        layout = self.layout

        sp = layout.split(factor=0.3)
        col = sp.column()
        col.prop(self, "position")

        sp = sp.split(factor=0.4)
        col = sp.column()
        col.label(text="フォントサイズ:")
        col.prop(self, "font_size", text="")


classes = [
    SAMPLE37_OT_ShowDatetime,
    SAMPLE37_PT_ShowDatetime,
    SAMPLE37_Preferences,
]


def register():
    for c in classes:
        bpy.utils.register_class(c)
    print("サンプル 3-7: アドオン『サンプル 3-7』が有効化されました。")


def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)
    print("サンプル 3-7: アドオン『サンプル 3-7』が無効化されました。")


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

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

サンプル 3-7: アドオン『サンプル 3-7』が有効化されました。

アドオンを有効化し、アドオンの設定情報が表示されることを確認します。

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

有効化したアドオンの機能を使い、動作を確認します。

1 プリファレンスのアドオン設定情報から、フォントサイズや描画位置を変更します。
2 3-5節 の機能によって描画されるテキストが、1で設定した内容にしたがって変化することを確認します。

アドオンを無効化する

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

サンプル 3-7: アドオン『サンプル 3-7』が無効化されました。

ソースコードの解説

本節のサンプルアドオンは、3-5節 のサンプルアドオンを改造したものです。 このため、プリファレンスにアドオンの設定情報を追加し、アドオンの動作に反映させる方法のみを説明します。 その他の処理については、3-5節 を参照してください。

アドオン設定情報クラスの定義

プリファレンスにアドオンの設定情報を追加するためには、bpy.types.AddonPreferences クラスを継承したクラス(アドオン設定情報クラス と呼ぶことにします)を定義する必要があります。

# プリファレンスのアドオン設定情報
class SAMPLE37_Preferences(bpy.types.AddonPreferences):
    bl_idname = __name__

    position: IntVectorProperty(
        name="位置",
        description="描画位置",
        size=2,
        min=0,
        default=(100, 120),
        subtype='TRANSLATION'
    )
    font_size: IntProperty(
        name="フォントサイズ",
        description="描画フォントサイズ",
        min=10,
        max=100,
        default=30,
    )

    def draw(self, context):
        layout = self.layout

        sp = layout.split(factor=0.3)
        col = sp.column()
        col.prop(self, "position")

        sp = sp.split(factor=0.4)
        col = sp.column()
        col.label(text="フォントサイズ:")
        col.prop(self, "font_size", text="")

アドオン設定情報クラスのクラス変数 bl_idname には、プリファレンスに追加するアドオンの設定情報に対応した、パッケージ名やモジュール名を指定します。 アドオンのソースコードが単一のファイルで構成される場合は、自身のモジュール名を表す __name__ を指定します。 一方、アドオンのソースコードが複数のファイルで構成される場合は、パッケージ名を指定する必要がありますが、指定方法について注意する必要があります。 アドオン設定情報クラスが __init__.py に定義されている場合は、__name__ がパッケージ名を表すため __name__ を指定すれば問題ありません(__package__ を指定してもよいです)。 しかし、__init__.py 以外に定義されている場合は、パッケージ名を表す __package__ を指定する必要があります。

プリファレンスに追加するアドオンの設定情報は、プロパティクラスの変数としてクラス変数に定義します。 そして、アドオン設定情報クラスの draw メソッドに、UIを構築するための処理を定義します。 draw メソッドの書き方は、基本的にパネルクラスの draw メソッドの書き方と同じです。 しかし、row.prop メソッドなどのUIを追加する関数の第1引数には、プロパティクラスの変数を定義したインスタンスを指定する必要があることに注意が必要です。 サンプルアドオンでは、draw メソッドが呼び出されたインスタンスと、プロパティクラスの変数を定義したインスタンスが同じであることから、self を指定します。

ここまでの処理で、プリファレンスにアドオンの設定情報が追加できます。 続いて、プリファレンスに追加したアドオンの設定情報を、オペレータクラスの処理内で受け取る方法を説明します。

アドオンの設定情報へのアクセス

アドオンの設定情報へは、次のコードでアクセスできます。

prefs = context.preferences.addons[__name__].preferences

本節のサンプルアドオンのソースコードは、単一のファイルで構成されます。 このため、context.preferences.addons[__name__].preferences でアドオンの設定情報にアクセスできます。 アドオンが複数のファイルで構成されている場合は、前述のアドオン設定情報クラスと同様に bl_idname__name__ または __package__ を指定して、アドオンの設定情報にアクセスします。

これで、プリファレンスに追加されたアドオンの設定情報にアクセスできるようになりました。 アドオンの設定情報を使ってテキストを描画する処理を、次に示します。

blf.color(0, 1.0, 1.0, 1.0, 1.0)
blf.size(0, prefs.font_size, 72)
blf.position(0, prefs.position[0],
             region.height - prefs.position[1], 0)
date_str = datetime.datetime.now().strftime("%Y.%m.%d")
blf.draw(0, date_str)

まとめ

プリファレンスにアドオンの設定情報を配置し、設定された情報を取得する方法を紹介しました。 プリファレンスを活用することで、SidebarやオペレータプロパティのUIが煩雑になる問題を解消できます。 プリファレンスに配置する設定情報の選択基準を決めるのは難しいところがありますが、設定情報の変更の頻度が1つの基準になるかと思います。

本節までで、BlenderのUIフレームワークの中でUIを構築する方法が、何度か出てきました。 幸いなことに、対象がSidebarであるかプリファレンスであるかに関わらず、基本的には同じような方法でUIを構築できます。 一度覚えたことを他の場面でも活用できるのは、BlenderのUIが一貫した方法で構築されているからです。 なお、UIの構築については、2-7節 にて詳細に説明しているため、参考にしてください。

ポイント