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

Last Update: 2023.3.1

Blender 2.7

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

Blender 2.7

Last Update: 2023.3.1

2-10. BlenderのUIを制御する③

2-8節 から続けてきたBlenderのUI制御の解説ですが、本節ではその締めくくりとしてBlenderが提供する特殊なUIをアドオンから呼び出す方法について説明します。 本節では、ファイルブラウザなどの利用頻度の高いUIから検索ボックスなどのあまり使われないUIまで一通り説明します。

作成するアドオンの仕様

アドオンを作成する

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

import bpy
from bpy.props import IntProperty, FloatProperty, EnumProperty
from bpy.props import FloatVectorProperty, StringProperty


bl_info = {
    "name": "サンプル2-10: BlenderのUIを制御するアドオン3",
    "author": "Nutti",
    "version": (2, 0),
    "blender": (2, 75, 0),
    "location": "3Dビュー > ツールシェルフ",
    "description": "BlenderのUIを制御するアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "User Interface"
}


class ShowPopupMessage(bpy.types.Operator):

    bl_idname = "object.show_popup_message"
    bl_label = "ポップアップメッセージ"
    bl_description = "ポップアップメッセージ"
    bl_options = {'REGISTER', 'UNDO'}

    # execute() メソッドがないと、やり直し未対応の文字が出力される
    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # ポップアップメッセージ表示
        return wm.invoke_popup(self, width=200, height=100)

    # ポップアップメッセージに表示する内容
    def draw(self, context):
        layout = self.layout
        layout.label("メッセージ")


class ShowDialogMenu(bpy.types.Operator):

    bl_idname = "object.show_dialog_menu"
    bl_label = "ダイアログメニュー"
    bl_description = "ダイアログメニュー"
    bl_options = {'REGISTER', 'UNDO'}

    prop_int = IntProperty(
        name="ダイアログプロパティ 1",
        description="ダイアログプロパティ 1",
        default=100,
        min=0,
        max=255
    )
    prop_float = FloatProperty(
        name="ダイアログプロパティ 2",
        description="ダイアログプロパティ 2",
        default=0.75,
        min=0.0,
        max=1.0
    )
    prop_enum = EnumProperty(
        name="ダイアログプロパティ 3",
        description="ダイアログプロパティ 3",
        items=[
            ('ITEM_1', "項目 1", "項目 1"),
            ('ITEM_2', "項目 2", "項目 2"),
            ('ITEM_3', "項目 3", "項目 3")
        ],
        default='ITEM_1'
    )
    prop_floatv = FloatVectorProperty(
        name="ダイアログプロパティ 4",
        description="ダイアログプロパティ 4",
        subtype='COLOR_GAMMA',
        default=(1.0, 1.0, 1.0),
        min=0.0,
        max=1.0
    )

    def execute(self, context):
        self.report(
            {'INFO'},
            "サンプル2-10: [1] %d, [2] %f, [3] %s, [4] (%f, %f, %f)"
            % (self.prop_int, self.prop_float, self.prop_enum,
               self.prop_floatv[0], self.prop_floatv[1],
               self.prop_floatv[2])
        )

        return {'FINISHED'}

    def invoke(self, context, event):
        scene = context.scene
        wm = context.window_manager

        self.prop_int = scene.cm_prop_int
        self.prop_float = scene.cm_prop_float
        self.prop_enum = scene.cm_prop_enum
        self.prop_floatv = scene.cm_prop_floatv

        # ダイアログメニュー呼び出し
        return wm.invoke_props_dialog(self)


class ShowFileBrowser(bpy.types.Operator):

    bl_idname = "object.show_file_browser"
    bl_label = "ファイルブラウザ"
    bl_description = "ファイルブラウザ"
    bl_options = {'REGISTER', 'UNDO'}

    filepath = StringProperty(subtype="FILE_PATH")
    filename = StringProperty()
    directory = StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        self.report(
            {'INFO'},
            "サンプル2-10: [FilePath] %s, [FileName] %s, [Directory] %s"
            % (self.filepath, self.filename, self.directory)
        )
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # ファイルブラウザ表示
        wm.fileselect_add(self)

        return {'RUNNING_MODAL'}


class ShowConfirmPopup(bpy.types.Operator):

    bl_idname = "object.show_confirm_popup"
    bl_label = "確認ポップアップ"
    bl_description = "確認ポップアップ"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        self.report({'INFO'}, "サンプル2-10: 確認ポップアップボタンをクリックしました")
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # 確認メッセージ表示
        return wm.invoke_confirm(self, event)


class ShowPropertyPopup(bpy.types.Operator):

    bl_idname = "object.show_property_popup"
    bl_label = "プロパティ付きポップアップ"
    bl_description = "プロパティ付きポップアップ"
    bl_options = {'REGISTER', 'UNDO'}

    prop_int = IntProperty(
        name="プロパティ 1",
        description="プロパティ 1",
        default=100,
        min=0,
        max=255
    )
    prop_float = FloatProperty(
        name="プロパティ 2",
        description="プロパティ 2",
        default=0.75,
        min=0.0,
        max=1.0
    )
    prop_enum = EnumProperty(
        name="プロパティ 3",
        description="プロパティ 3",
        items=[
            ('ITEM_1', "項目 1", "項目 1"),
            ('ITEM_2', "項目 2", "項目 2"),
            ('ITEM_3', "項目 3", "項目 3")
        ],
        default='ITEM_1'
    )
    prop_floatv = FloatVectorProperty(
        name="プロパティ 4",
        description="プロパティ 4",
        subtype='COLOR_GAMMA',
        default=(1.0, 1.0, 1.0),
        min=0.0,
        max=1.0
    )

    def execute(self, context):
        self.report(
            {'INFO'},
            "サンプル2-10: [1] %d, [2] %f, [3] %s, [4] (%f, %f, %f)"
            % (self.prop_int, self.prop_float, self.prop_enum,
               self.prop_floatv[0], self.prop_floatv[1],
               self.prop_floatv[2])
        )
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # プロパティ付きポップアップ表示
        return wm.invoke_props_popup(self, event)


class ShowSearchPopup(bpy.types.Operator):

    bl_idname = "object.show_search_popup"
    bl_label = "検索ウィンドウ付きポップアップ"
    bl_description = "検索ウィンドウ付きポップアップ"
    bl_options = {'REGISTER', 'UNDO'}
    bl_property = "item"

    item = EnumProperty(
        name="配置位置",
        description="複製したオブジェクトの配置位置",
        items=[
            ('ITEM_1', '項目1', '項目1'),
            ('ITEM_2', '項目2', '項目2'),
            ('ITEM_3', '項目3', '項目3')
        ],
        default='ITEM_1'
    )

    def execute(self, context):
        self.report({'INFO'}, "サンプル2-10: %s を選択しました" % self.item)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # 検索ウィンドウ付きポップアップ表示
        wm.invoke_search_popup(self)

        # {'FINISHED'} を返す必要がある
        return {'FINISHED'}


# ツールシェルフに「カスタムメニュー」タブを追加
class VIEW3D_PT_CustomMenu(bpy.types.Panel):

    bl_label = "カスタムメニュー"          # タブに表示される文字列
    bl_space_type = 'VIEW_3D'           # メニューを表示するエリア
    bl_region_type = 'TOOLS'            # メニューを表示するリージョン
    bl_category = "カスタムメニュー"       # タブを開いたメニューのヘッダーに表示される文字列
    bl_context = "objectmode"           # パネルを表示するコンテキスト

    # 本クラスの処理が実行可能かを判定する
    @classmethod
    def poll(cls, context):
        # オブジェクトが選択されている時のみメニューを表示させる
        for o in bpy.data.objects:
            if o.select:
                return True
        return False

    # ヘッダーのカスタマイズ
    def draw_header(self, context):
        layout = self.layout
        layout.label(text="", icon='PLUGIN')

    # メニューの描画処理
    def draw(self, context):
        layout = self.layout

        # ポップアップメッセージを表示する
        layout.label(text="ポップアップメッセージを表示する:")
        layout.operator(ShowPopupMessage.bl_idname)

        layout.separator()

        # ダイアログメニューを表示する
        layout.label(text="ダイアログメニューを表示する:")
        layout.operator(ShowDialogMenu.bl_idname)

        layout.separator()

        # ファイルブラウザを表示する
        layout.label(text="ファイルブラウザを表示する:")
        layout.operator(ShowFileBrowser.bl_idname)

        layout.separator()

        # 確認ポップアップを表示する
        layout.label(text="確認ポップアップを表示する:")
        layout.operator(ShowConfirmPopup.bl_idname)

        layout.separator()

        # プロパティ付きポップアップを表示する
        layout.label(text="プロパティ付きポップアップを表示する:")
        layout.operator(ShowPropertyPopup.bl_idname)

        layout.separator()

        # 検索ポップアップを表示する
        layout.label(text="検索ポップアップを表示する:")
        layout.operator(ShowSearchPopup.bl_idname)



# プロパティの初期化
def init_props():
    scene = bpy.types.Scene
    scene.cm_prop_int = IntProperty(
        name="Prop 1",
        description="Integer Property",
        default=100,
        min=0,
        max=255
    )
    scene.cm_prop_float = FloatProperty(
        name="Prop 2",
        description="Float Property",
        default=0.75,
        min=0.0,
        max=1.0
    )
    scene.cm_prop_enum = EnumProperty(
        name="Prop 3",
        description="Enum Property",
        items=[
            ('ITEM_1', "項目 1", "項目 1"),
            ('ITEM_2', "項目 2", "項目 2"),
            ('ITEM_3', "項目 3", "項目 3")
        ],
        default='ITEM_1'
    )
    scene.cm_prop_floatv = FloatVectorProperty(
        name="Prop 4",
        description="Float Vector Property",
        subtype='COLOR_GAMMA',
        default=(1.0, 1.0, 1.0),
        min=0.0,
        max=1.0
    )


# プロパティを削除
def clear_props():
    scene = bpy.types.Scene
    del scene.cm_prop_int
    del scene.cm_prop_float
    del scene.cm_prop_enum
    del scene.cm_prop_floatv


def register():
    bpy.utils.register_module(__name__)
    init_props()
    print("サンプル2-10: アドオン「サンプル2-10」が有効化されました。")


def unregister():
    bpy.utils.unregister_module(__name__)
    clear_props()
    print("サンプル2-10: アドオン「サンプル2-10」が無効化されました。")


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

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

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

そして、[3Dビュー] エリアのツール・シェルフにタブ [カスタムメニュー] が追加されます。

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

[3Dビュー] エリアのツール・シェルフのタブ [カスタムメニュー] をクリックすると、カスタムメニューのメニューが表示されます。

[カスタムメニュー] タブに設置されたボタンをクリックし、動作を確認します。

ポップアップメッセージボタン

[ポップアップメッセージ] ボタンを押すと、ポップアップメッセージが表示されます。

1 タブ [カスタムメニュー] の [ポップアップメッセージ] ボタンをクリックします。
ポップアップメッセージボタン 手順1
2 クリックした場所にポップメッセージが表示されます。
ポップアップメッセージボタン 手順2

ダイアログメニューボタン

[ダイアログメニュー] ボタンを押すと、4つのプロパティと [OK] ボタン付きのダイアログメニューが表示されます。

1 タブ [カスタムメニュー] の [ダイアログメニュー] ボタンをクリックします。
2 クリックした場所にダイアログメニューが開きます。
3 ダイアログメニュー上のプロパティは変更することができます。
4 [OK] ボタンを押すとスクリプト実行ログに以下のメッセージが表示されます。

サンプル2-10: [1] (ダイアログプロパティ 1の値), [2] (ダイアログプロパティ 2の値), [3] (ダイアログプロパティ 3の識別子), [4] (ダイアログプロパティ 4の値)

ファイルブラウザボタン

[ファイルブラウザ] ボタンを押すと、ファイルブラウザが表示されます。

1 タブ [カスタムメニュー] の [ファイルブラウザ] ボタンをクリックします。
2 ファイルブラウザが開きます。
3 適当にファイルを開くと、スクリプト実行ログに以下のメッセージが表示されます。
サンプル2-10: [FilePath] (開いたファイルのファイルパス), [FileName] (開いたファイルのファイル名), [Directory] (開いたファイルが置かれたディレクトリ)

確認ポップアップボタン

[確認ポップアップ] ボタンを押すと、操作を実行するか中断するかを問うポップアップが表示されます。

1 タブ [カスタムメニュー] の [確認ポップアップ] ボタンをクリックします。
確認ポップアップボタン 手順1
2 操作を実行するか中断するかを問うポップアップが表示されるので、[確認ポップアップ] をクリックします。
確認ポップアップボタン 手順2
3 スクリプト実行ログに以下のメッセージが表示されます。仮に、[確認ポップアップ] をクリックしなかった場合は、処理が中断されます。
サンプル2-10: 確認ポップアップボタンをクリックしました

プロパティ付きポップアップボタン

[プロパティ付きポップアップ] ボタンを押すと、4つのプロパティがポップアップで表示されます。

1 タブ [カスタムメニュー] の [プロパティ付きポップアップ] ボタンをクリックします。
2 プロパティを変更するたびに、[スクリプト実行ログ] に以下のメッセージが表示されます。

サンプル2-10: [1] (プロパティ 1の値), [2] (プロパティ 2の値), [3] (プロパティ 3の識別子), [4] (プロパティ 4の値)

検索ウィンドウ付きポップアップボタン

[検索ウィンドウ付きポップアップ] ボタンを押すと、検索ウィンドウがポップアップで表示されます。

1 タブ [カスタムメニュー] の [検索ウィンドウ付きポップアップ] ボタンをクリックします。
2 検索ウィンドウ付きポップアップが表示されます。
3 [項目1]・[項目2]・[項目3]の中から検索することができます。
4 項目を確定するとスクリプト実行ログに以下のメッセージが表示されます。
サンプル2-10: (確定した項目の識別子) を選択しました

アドオンを無効化する

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

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

ソースコードの解説

ポップアップメッセージを表示する

ポップアップメッセージを表示する方法について説明します。

ポップアップメッセージを表示するためのオペレータクラスを、以下に示します。

class ShowPopupMessage(bpy.types.Operator):

    bl_idname = "object.show_popup_message"
    bl_label = "ポップアップメッセージ"
    bl_description = "ポップアップメッセージ"
    bl_options = {'REGISTER', 'UNDO'}

    # execute() メソッドがないと、やり直し未対応の文字が出力される
    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # ポップアップメッセージ表示
        return wm.invoke_popup(self, width=200, height=100)

    # ポップアップメッセージに表示する内容
    def draw(self, context):
        layout = self.layout
        layout.label("メッセージ")

ポップアップメッセージの表示はボタンを押したときに呼ばれる invoke メソッドで行っています。

invoke メソッドは、処理が実行された時に呼ばれるメソッドです。 execute メソッドも処理が実行された時に呼ばれますが、execute メソッドの引数にはなかったイベント event を受け取る点が異なります。 3-1節 でも説明しますが、引数 event には invoke メソッドが呼ばれた時のマウスの位置や発生したキーイベントなどの情報が含まれています。 また、invokeexecute が2つ定義されていた場合、メニューの項目を選択した時やボタンを押したなどのUIから操作を行うと invoke が優先的に呼ばれます。 一方、2-2節 で説明した bpy.ops.<オペレーションクラスのbl_idname> を実行すると execute が呼び出されます。

このようにユーザからの入力を積極的に使いたい場合や、invoke メソッドで前処理を行った後に bpy.ops.<オペレーションクラスのbl_idname> を実行じて excute を呼び出したい場合に invoke メソッドを利用します。

本節のサンプルでは、invoke メソッド内で wm.invoke_popup 関数を次に示す引数を指定して実行しています。

引数
第1引数 オペレータクラス
width 整数
height 整数

wm.invoke_popup 関数により表示されるポップアップのUIは、draw メソッドで定義します。 本節のサンプルでは、メッセージ と書かれたラベルを表示しています。

wm.invoke_popup 関数の戻り値は {'RUNNING_MODAL'} ですが、本節では説明を省略します。 ポップアップメッセージを表示する時には、wm.invoke_popup 関数を invoke メソッドの戻り値に指定すればよいということだけを覚えておきましょう。

[ポップアップメッセージ] ボタンを配置する処理を以下に示します。

# ポップアップメッセージを表示する
layout.label(text="ポップアップメッセージを表示する:")
layout.operator(ShowPopupMessage.bl_idname)

ダイアログメニューを表示する

ポップアップメッセージの応用として、ポップアップからプロパティを入力できるダイアログメニューを表示することもできます。 ダイアログメニューを表示するためには、context.window_manager.invoke_props_dialog 関数を使用します。

ダイアログメニューを表示するオペレータクラスを以下に示します。

class ShowDialogMenu(bpy.types.Operator):

    bl_idname = "object.show_dialog_menu"
    bl_label = "ダイアログメニュー"
    bl_description = "ダイアログメニュー"
    bl_options = {'REGISTER', 'UNDO'}

    prop_int = IntProperty(
        name="ダイアログプロパティ 1",
        description="ダイアログプロパティ 1",
        default=100,
        min=0,
        max=255
    )
    prop_float = FloatProperty(
        name="ダイアログプロパティ 2",
        description="ダイアログプロパティ 2",
        default=0.75,
        min=0.0,
        max=1.0
    )
    prop_enum = EnumProperty(
        name="ダイアログプロパティ 3",
        description="ダイアログプロパティ 3",
        items=[
            ('ITEM_1', "項目 1", "項目 1"),
            ('ITEM_2', "項目 2", "項目 2"),
            ('ITEM_3', "項目 3", "項目 3")
        ],
        default='ITEM_1'
    )
    prop_floatv = FloatVectorProperty(
        name="ダイアログプロパティ 4",
        description="ダイアログプロパティ 4",
        subtype='COLOR_GAMMA',
        default=(1.0, 1.0, 1.0),
        min=0.0,
        max=1.0
    )

    def execute(self, context):
        self.report(
            {'INFO'},
            "サンプル2-10: [1] %d, [2] %f, [3] %s, [4] (%f, %f, %f)"
            % (self.prop_int, self.prop_float, self.prop_enum,
               self.prop_floatv[0], self.prop_floatv[1],
               self.prop_floatv[2])
        )

        return {'FINISHED'}

    def invoke(self, context, event):
        scene = context.scene
        wm = context.window_manager

        self.prop_int = scene.cm_prop_int
        self.prop_float = scene.cm_prop_float
        self.prop_enum = scene.cm_prop_enum
        self.prop_floatv = scene.cm_prop_floatv

        # ダイアログメニュー呼び出し
        return wm.invoke_props_dialog(self)

ShowDialogMenu クラスには4つのプロパティクラスの変数が宣言されていて、ダイアログメニューではこれらのプロパティを表示します。 ダイアログメニューの表示は wm.invoke_props_dialog 関数で行います。 引数には、ダイアログメニューに表示するプロパティクラスの変数を持つオペレータクラスのインスタンスを渡します。

wm.invoke_props_dialog 関数の引数を以下に示します。

引数
第1引数 オペレータクラスのインスタンス
width 整数
height 整数

wm.invoke_props_dialog 関数の戻り値は、ポップアップメッセージと同様に {'RUNNING_MODAL'} ですが、本節では説明を省略します。 ダイアログメニューを表示する時には、wm.invoke_props_dialog 関数を invoke メソッドの戻り値に指定すればよいということだけを覚えておきましょう。

ダイアログメニューに表示された [OK] ボタンを押すと、execute メソッドが実行されます。 execute メソッドでは、ダイアログメニューのプロパティに指定した値をスクリプト実行ログに出力します。 ダイアログメニューで指定したプロパティの値でアドオンの処理を実行したいときに活用しましょう。

[ダイアログメニュー] ボタンを配置する処理を以下に示します。

# ダイアログメニューを表示する
layout.label(text="ダイアログメニューを表示する:")
layout.operator(ShowDialogMenu.bl_idname)

ファイルブラウザを表示する

ファイルを開いたり保存したりする時など、Blender標準の機能を使用した場合でも、ファイルを選択するためのファイルブラウザを表示する機能が存在します。 アドオンからファイルブラウザを表示するためには、context.window_manager.fileselect_add 関数を利用します。

本節のサンプルでは、以下のコードでファイルブラウザを表示しています。

class ShowFileBrowser(bpy.types.Operator):

    bl_idname = "object.show_file_browser"
    bl_label = "ファイルブラウザ"
    bl_description = "ファイルブラウザ"
    bl_options = {'REGISTER', 'UNDO'}

    filepath = StringProperty(subtype="FILE_PATH")
    filename = StringProperty()
    directory = StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        self.report(
            {'INFO'},
            "サンプル2-10: [FilePath] %s, [FileName] %s, [Directory] %s"
            % (self.filepath, self.filename, self.directory)
        )
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # ファイルブラウザ表示
        wm.fileselect_add(self)

        return {'RUNNING_MODAL'}

ファイルブラウザを表示するためには、invoke メソッド内で wm.fileselect_add 関数を呼ぶ必要があります。 引数には、ファイルブラウザ内でファイルを確定した時に実行される execute メソッドが定義されたオペレータクラスのインスタンスを指定します。 invoke メソッドの戻り値は、{'RUNNING_MODAL'} にする必要があります。

ファイルブラウザで確定したファイルの情報を保存するために、クラス変数 filepath , filename , directory を宣言しています。 ファイルブラウザからファイルの情報を受け取るためには、本節のサンプルと同じ変数名にする 必要があることに注意が必要です。 なお、filepathdirectory は、プロパティクラス StringProperty の引数 subtype にファイルパスを格納するプロパティであることを示す FILE_PATH を指定する必要があります。

ファイルブラウザでファイルを確定すると execute メソッドが呼ばれ、確定したファイルパス・ファイル名・ファイルが置かれたディレクトリをスクリプト実行ログに表示します。

最後に、[ファイルブラウザ] ボタンを配置する処理を以下に示します。

# ファイルブラウザを表示する
layout.label(text="ファイルブラウザを表示する:")
layout.operator(ShowFileBrowser.bl_idname)

実行確認のポップアップを表示する

Blenderの機能の中には、実行する前に処理を実行するか中断するかを確認するためのポップアップを表示する機能があります。 例えば、[情報] エリアのメニュー [ファイル] > [スタートアップファイルを保存] は、実行確認のポップアップを表示する例の1つです。

実行確認のポップアップは、context.window_manager.invoke_confirm 関数により表示することができます。

本節のサンプルでは、以下のコードで実行確認のポップアップを表示しています。

class ShowConfirmPopup(bpy.types.Operator):

    bl_idname = "object.show_confirm_popup"
    bl_label = "確認ポップアップ"
    bl_description = "確認ポップアップ"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        self.report({'INFO'}, "サンプル2-10: 確認ポップアップボタンをクリックしました")
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # 確認メッセージ表示
        return wm.invoke_confirm(self, event)

実行確認のポップアップは、invoke メソッド内から wm.invoke_confirm 関数を呼び出して表示しています。

vm.invoke_confirm 関数の引数を以下に示します。

引数 意味
第1引数 実行確認ポップアップにて処理の実行を決定したときに呼び出される execute メソッドが定義されたオペレータクラスのインスタンス
第2引数 invoke メソッドの引数 event を指定

[確認ポップアップ] ボタンを配置する処理を以下に示します。

# 確認ポップアップを表示する
layout.label(text="確認ポップアップを表示する:")
layout.operator(ShowConfirmPopup.bl_idname)

プロパティ付きポップアップを表示する

値を変更することができる、プロパティ付きポップアップを作ることもできます。 実行結果を見るとダイアログメニューと同じ動きに見えますが、ダイアログメニューは [OK] ボタンを押すまで処理が実行されないのに対し、プロパティ付きポップアップではプロパティを変更するたびに処理が実行されます

本節のサンプルでは、以下のようにしてプロパティ付きポップアップを表示しています。

class ShowPropertyPopup(bpy.types.Operator):

    bl_idname = "object.show_property_popup"
    bl_label = "プロパティ付きポップアップ"
    bl_description = "プロパティ付きポップアップ"
    bl_options = {'REGISTER', 'UNDO'}

    prop_int = IntProperty(
        name="プロパティ 1",
        description="プロパティ 1",
        default=100,
        min=0,
        max=255
    )
    prop_float = FloatProperty(
        name="プロパティ 2",
        description="プロパティ 2",
        default=0.75,
        min=0.0,
        max=1.0
    )
    prop_enum = EnumProperty(
        name="プロパティ 3",
        description="プロパティ 3",
        items=[
            ('ITEM_1', "項目 1", "項目 1"),
            ('ITEM_2', "項目 2", "項目 2"),
            ('ITEM_3', "項目 3", "項目 3")
        ],
        default='ITEM_1'
    )
    prop_floatv = FloatVectorProperty(
        name="プロパティ 4",
        description="プロパティ 4",
        subtype='COLOR_GAMMA',
        default=(1.0, 1.0, 1.0),
        min=0.0,
        max=1.0
    )

    def execute(self, context):
        self.report(
            {'INFO'},
            "サンプル2-10: [1] %d, [2] %f, [3] %s, [4] (%f, %f, %f)"
            % (self.prop_int, self.prop_float, self.prop_enum,
               self.prop_floatv[0], self.prop_floatv[1],
               self.prop_floatv[2])
        )
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # プロパティ付きポップアップ表示
        return wm.invoke_props_popup(self, event)

プロパティ付きポップアップは、invoke メソッド内から context.window_manager.invoke_props_popup 関数を実行することで表示することができます。 プロパティを変更すると execute メソッドが実行され、 現在のプロパティの値がスクリプト実行ログに表示されます。

wm.invoke_props_popup 関数には、以下に示す引数を指定します。

引数 意味
第1引数 プロパティを変えた時に呼び出される execute メソッドが定義されたオペレータクラスのインスタンス
第2引数 invoke メソッドの引数 event を指定

なお、プロパティ付きポップアップで表示されたプロパティは、ツール・シェルフのオプションにも表示されているため、ポップアップが閉じてしまった場合でも他の操作を行わなわない限り変更することができます。

[プロパティ付きポップアップ] ボタンを配置する処理を以下に示します。

# プロパティ付きポップアップを表示する
layout.label(text="プロパティ付きポップアップを表示する:")
layout.operator(ShowPropertyPopup.bl_idname)

検索ウィンドウ付きポップアップを表示する

あらかじめ登録した項目の中から検索することができる、検索ウィンドウ付きのポップアップを表示することができます。 実際このUIがどのように役立つのか、筆者もよくわかっていませんが、BlenderのAPIとして用意されているので紹介します。

本節のサンプルでは、検索ウィンドウ付きポップアップを以下のコードにより表示します。

class ShowSearchPopup(bpy.types.Operator):

    bl_idname = "object.show_search_popup"
    bl_label = "検索ウィンドウ付きポップアップ"
    bl_description = "検索ウィンドウ付きポップアップ"
    bl_options = {'REGISTER', 'UNDO'}
    bl_property = "item"

    item = EnumProperty(
        name="配置位置",
        description="複製したオブジェクトの配置位置",
        items=[
            ('ITEM_1', '項目1', '項目1'),
            ('ITEM_2', '項目2', '項目2'),
            ('ITEM_3', '項目3', '項目3')
        ],
        default='ITEM_1'
    )

    def execute(self, context):
        self.report({'INFO'}, "サンプル2-10: %s を選択しました" % self.item)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # 検索ウィンドウ付きポップアップ表示
        wm.invoke_search_popup(self)

        # {'FINISHED'} を返す必要がある
        return {'FINISHED'}

検索ウィンドウ付きのポップアップを表示するためには、invoke メソッド内で context.window_manager.invoke_search_popup 関数を使います。 引数には、項目確定時に呼び出される execute メソッドが定義されたクラスのインスタンスを指定します。 なお invoke メソッドは {'FINISHED'} を返す必要があります。

項目を確定すると execute メソッドが呼び出され、選択した項目の識別子がスクリプト実行ログに表示されます。

検索ウィンドウで検索できる項目は、アドオン開発者が追加する 必要があります。 検索ウィンドウへ追加する項目リストを持つ変数は EnumProperty クラスの型で定義する必要があり、クラス変数 bl_property に定義した変数名を代入する必要があります。 本節のサンプルではクラス変数 item が項目リストであるため、bl_property="item" とします。

最後に、[検索ウィンドウ付きポップアップ] ボタンを配置する処理を以下に示します。

# 検索ポップアップを表示する
layout.label(text="検索ポップアップを表示する:")
layout.operator(ShowSearchPopup.bl_idname)

まとめ

本節では、Blenderが提供するUIをアドオンから呼び出す方法について説明しました。

2-9節 に引き続き本節のサンプルでも様々なAPIが登場しましたので、本節で紹介したUI関連のAPIをまとめておきます。

UI API
ポップアップメッセージ context.window_manager.invoke_popup
ダイアログメニュー context.window_manager.invoke_props_dialog
ファイルブラウザ context.window_manager.fileselect_add
確認ポップアップ context.window_manager.invoke_confirm
プロパティ付きポップアップ context.window_manager.invoke_props_popup
検索ウィンドウ付きポップアップ context.window_manager.invoke_search_popup

2-8節 から本節まで3節にわたりBlenderのUIを構築する方法を説明しましたが、わかりやすいUIを構築するためのポイントについては説明していません。 わかりやすいUIを構築するのはアドオンの開発とは異なり、明確な答えがありません。 他の方が開発されたアドオンのUIを参考にするだけでなく、世の中に公開されているWebページやアプリの画面などにもアンテナを常に張り巡らせ、自分で良いと思ったデザインを真似して吸収していく ことが、わかりやすいUIを構築するための早道であると思います。

本節で前編は終わりです。 ここまで読まれた方であれば、アドオンを作るだけでなくPythonで書かれているBlenderのUIも自由に変更することができるようになっていることでしょう。 後編では、より高度なアドオンを作りたい人向けの話題を取り上げます。

ポイント