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

Last Update: 2019.4.2

2-9. BlenderのUIを制御する②

2-8節 に引き続き、BlenderのUIを制御する方法を説明します。 本節ではボタンの配置やメニューなどのUI部品の配置方法に加え、UI部品の整列方法についても説明します。 また本節のサンプルは、アドオンで利用可能なアイコンの一覧を表示する機能もありますので、アドオンでアイコンを使うことを考えている方は、どのようなアイコンが使えるかを確認しておくと良いかもしれません。

作成するアドオンの仕様

アドオンを作成する

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

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


bl_info = {
    "name": "サンプル2-9: BlenderのUIを制御するアドオン2",
    "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 NullOperation(bpy.types.Operator):

    bl_idname = "object.null_operation"
    bl_label = "NOP"
    bl_description = "何もしない"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        return {'FINISHED'}


class NullOperationMenu(bpy.types.Menu):

    bl_idname = "object.null_operation_menu"
    bl_label = "NOP メニュー"
    bl_description = "何もしない処理を複数持つメニュー"

    def draw(self, context):
        layout = self.layout
        # メニュー項目の追加
        for i in range(3):
            layout.operator(NullOperation.bl_idname, text=("項目 %d" % (i)))


class ShowAllIcons(bpy.types.Operator):

    bl_idname = "object.show_all_icons"
    bl_label = "利用可能なアイコンをすべて表示"
    bl_description = "利用可能なアイコンをすべて表示"
    bl_options = {'REGISTER', 'UNDO'}

    num_column = IntProperty(
        name="一行に表示するアイコン数",
        description="一行に表示するアイコン数",
        default=2,
        min=1,
        max=5
    )

    # オプションのUI
    def draw(self, context):
        layout = self.layout

        layout.prop(self, "num_column")

        layout.separator()

        # 利用可能なアイコンをすべて表示
        layout.label(text="利用可能なアイコン一覧:")
        icon = bpy.types.UILayout.bl_rna.functions['prop'].parameters['icon']
        for i, key in enumerate(icon.enum_items.keys()):
            if i % self.num_column == 0:
                row = layout.row()
            row.label(text=key, icon=key)

    def execute(self, context):
        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
        scene = context.scene

        # ボタンを追加
        layout.label(text="ボタンを追加する:")
        layout.operator(NullOperation.bl_idname, text="ボタン1")
        layout.operator(NullOperation.bl_idname, text="ボタン2", emboss=False)

        # 上下の間隔を空ける
        layout.separator()

        # メニューを追加
        layout.label(text="メニューを追加する:")
        layout.menu(NullOperationMenu.bl_idname, text="メニュー")

        layout.separator()

        # プロパティを追加
        layout.label(text="プロパティを追加する:")
        layout.prop(scene, "cm_prop_int", text="プロパティ 1")
        layout.prop(scene, "cm_prop_float", text="プロパティ 2")
        layout.prop(scene, "cm_prop_enum", text="プロパティ 3")
        layout.prop(scene, "cm_prop_floatv", text="プロパティ 4")

        layout.separator()

        # 一行に並べる(アライメント無)
        layout.label(text="一行に並べる(アライメント無):")
        row = layout.row(align=False)
        for i in range(3):
            row.operator(NullOperation.bl_idname, text=("列 %d" % (i)))

        layout.separator()

        # 一行に並べる(アライメント有)
        layout.label(text="一行に並べる(アライメント有):")
        row = layout.row(align=True)
        for i in range(3):
            row.operator(NullOperation.bl_idname, text=("列 %d" % (i)))

        layout.separator()

        # 一列に並べる(アライメント無)
        layout.label(text="一列に並べる(アライメント無):")
        column = layout.column(align=False)
        for i in range(3):
            column.operator(NullOperation.bl_idname, text=("行 %d" % (i)))

        layout.separator()

        # 一列に並べる(アライメント有)
        layout.label(text="一列に並べる(アライメント有):")
        column = layout.column(align=True)
        for i in range(3):
            column.operator(NullOperation.bl_idname, text=("行 %d" % (i)))

        layout.separator()

        # 複数列に配置する
        layout.label(text="複数列に配置する:")
        column = layout.column(align=True)
        row = column.row(align=True)
        row.operator(NullOperation.bl_idname, text="列 1, 行 1")
        row.operator(NullOperation.bl_idname, text="列 2, 行 1")
        row = column.row(align=True)
        row.operator(NullOperation.bl_idname, text="列 1, 行 2")
        row.operator(NullOperation.bl_idname, text="列 2, 行 2")

        layout.separator()

        # 領域を分割する
        layout.label(text="領域を分割する:")
        split = layout.split(percentage=0.3)
        column = split.column(align=True)
        column.label(text="領域1:")
        column.operator(NullOperation.bl_idname, text="行 1")
        column.operator(NullOperation.bl_idname, text="行 2")
        split = split.split(percentage=0.7)
        column = split.column()
        column.label(text="領域2:")
        column.operator(NullOperation.bl_idname, text="行 1")
        column.operator(NullOperation.bl_idname, text="行 2")
        split = split.split(percentage=1.0)
        column = split.column(align=False)
        column.label(text="領域3:")
        column.operator(NullOperation.bl_idname, text="行 1")
        column.operator(NullOperation.bl_idname, text="行 2")

        layout.separator()

        # 横幅を自動的に拡大する
        layout.label(text="横幅を自動的に拡大する:")
        row = layout.row()
        row.alignment = 'EXPAND'
        row.operator(NullOperation.bl_idname, text="列 1")
        row.operator(NullOperation.bl_idname, text="列 2")

        layout.separator()

        # 左寄せする
        layout.label(text="左寄せする:")
        row = layout.row()
        row.alignment = 'LEFT'
        row.operator(NullOperation.bl_idname, text="列 1")
        row.operator(NullOperation.bl_idname, text="列 2")

        layout.separator()

        # 右寄せする
        layout.label(text="右寄せする:")
        row = layout.row()
        row.alignment = 'RIGHT'
        row.operator(NullOperation.bl_idname, text="列 1")
        row.operator(NullOperation.bl_idname, text="列 2")

        layout.separator()

        # グループ化する
        layout.label(text="グループ化する:")
        row = layout.row()
        box = row.box()
        box_row = box.row()
        box_column = box_row.column()
        box_column.operator(NullOperation.bl_idname, text="行 1, 列 1")
        box_column.separator()
        box_column.operator(NullOperation.bl_idname, text="行 2, 列 1")
        box_row.separator()
        box_column = box_row.column()
        box_column.operator(NullOperation.bl_idname, text="行 1, 列 2")
        box_column.separator()
        box_column.operator(NullOperation.bl_idname, text="行 2, 列 2")

        layout.separator()

        # プロパティのUIをカスタマイズする+アイコン一覧を表示する
        layout.label(text="プロパティのUIをカスタマイズする")
        layout.operator(ShowAllIcons.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-9: アドオン「サンプル2-9」が有効化されました。")


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


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

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

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

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

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

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

[カスタムメニュー] に表示されたボタンやメニューなどをクリックしたり選択したりできますが、[利用可能なアイコンをすべて表示] ボタンをクリックした時を除いて基本的に何も起こりません。

利用可能なアイコンをすべて表示ボタン

[利用可能なアイコンをすべて表示] ボタンをクリックすると、アドオンで利用可能なアイコンの一覧がツール・シェルフに表示されます。

1 タブ [カスタムメニュー] の [利用可能なアイコンをすべて表示] ボタンをクリックします。
2 ツール・シェルフのオプションに、アドオンから利用可能なアイコン一覧と、それぞれのアイコンを表示するためのキーコード(識別子)が表示されます。
3 [一行に表示するアイコン数] の値を変えることで、一行に表示するアイコンの数を変更することができます。

アドオンを無効化する

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

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

ソースコードの解説

本節のサンプルプログラムは説明するUIの種類が多いためソースコードの規模は大きいです。 幸いなことに各UIを構築するソースコードは短く、かつUIごとに区切って説明しますので、規模が大きさに惑わされずに少しずつ理解していきましょう。

メニューを構築する

タブのメニューを構築するためには、draw メソッドを定義する必要があります。 draw メソッドの引数などの詳細については、2-5節 を参考にしてください。

本節のサンプルの draw メソッドのコードは非常に長いので、それぞれのUIごとに説明していきます。

ボタンを追加する

本節のサンプルでは、以下の処理により2種類のボタン(標準のボタンと文字列の周りの装飾が消えたボタン)を追加しています。

        # ボタンを追加
        layout.label(text="ボタンを追加する:")
        layout.operator(NullOperation.bl_idname, text="ボタン1")
        layout.operator(NullOperation.bl_idname, text="ボタン2", emboss=False)

ボタンは layout.operator 関数で追加することができ、以下の引数を指定します。 ボタンを押すと、layout.operator 関数の第1引数に指定したオペレータクラスの bl_idname を持つオペレータクラスの処理が実行されます。

引数 値の意味
第1引数 オペレータクラスの bl_idname
text ボタンに表示する文字列
icon ボタンに表示するアイコン
emboss False の場合、文字列の周りの装飾が消える

メニューを追加する

メニューを追加する処理の前に layout.separator 関数を呼ぶことで、上下のスペースを空けることができます。 ドロップダウンメニューでも layout.separator が使われていましたが、ドロップダウンの時の動作については、2-1節 を参考にしてください。

本節のサンプルでは以下の処理により、メニューを追加しています。

        # メニューを追加
        layout.label(text="メニューを追加する:")
        layout.menu(NullOperationMenu.bl_idname, text="メニュー")

2-5節 で説明したサブメニューを追加するための関数 layout.menu により、メニューを追加します。 追加したメニューは、セレクトボックスのUIとなります。 表示されるメニュー名は、デフォルトで第1引数に指定したメニュークラスの bl_label が表示されますが、text 引数に表示したい文字列を指定することで変更することができます。

プロパティを追加する

アドオンの機能実行時のパラメータなどを、ユーザ指定するためのプロパティを追加します。

プロパティを定義する

プロパティを追加するためには、プロパティの定義を行う必要があります。

プロパティの定義は、アドオン有効化時に register 関数から呼び出される init_props 関数で行います。 プロパティは、bpy.types.Scene に変数を追加することで定義できます。

# プロパティの初期化
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
    )

プロパティを削除する

アドオンを無効する時には、bpy.types.Scene に追加したプロパティのグループを削除する必要があります。 プロパティを削除せずにアドオンを無効すると、プロパティのデータがメモリに残ったままとなり無駄にメモリを消費してしまう ので、忘れずに削除するようにしましょう。

本節のサンプルでは、unregister 関数から呼び出される clear_props 関数により、定義したプロパティを削除しています。

# プロパティを削除
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

プロパティを変更するためのUIを構築する

定義したプロパティをユーザが変更するためのUIを表示するためには、layout.prop 関数を使います。 layout.prop 関数の引数を以下に示します。

引数 意味
第1引数 プロパティを持つオブジェクト
第2引数 プロパティ変数名
第3引数(text 表示文字列

本節のサンプルでは bpy.types.Scene にプロパティを登録したため、context.scene を第1引数に指定します。 第2引数には、bpy.types.Scene に登録したプロパティ変数名を文字列で指定します。

        # プロパティを追加
        layout.label(text="プロパティを追加する:")
        layout.prop(scene, "cm_prop_int", text="プロパティ 1")
        layout.prop(scene, "cm_prop_float", text="プロパティ 2")
        layout.prop(scene, "cm_prop_enum", text="プロパティ 3")
        layout.prop(scene, "cm_prop_floatv", text="プロパティ 4")

ボタンを一行に並べる

layout.operator 関数を用いると、横幅が100%のボタンが配置されます。 このため、単純に layout.operator を複数回実行すると実行した回数分、縦方向にボタンが配置されてしまいます。

ボタンを横に並べるためには layout.row 関数を使って行成分を取得し、取得した行成分に対して operator 関数を使ってボタンを配置する必要があります。 本節のサンプルでは、以下のようにして3つのボタンを一行に並べています。

        # 一行に並べる(アライメント無)
        layout.label(text="一行に並べる(アライメント無):")
        row = layout.row(align=False)
        for i in range(3):
            row.operator(NullOperation.bl_idname, text=("列 %d" % (i)))

なお、layout.row 関数の引数に align=False を指定すると、ボタンとボタンの間に隙間が空くようにして配置されるようになります。 一方、以下のコードのように、align=True を指定すると、この隙間がなくなります。

        # 一行に並べる(アライメント有)
        layout.label(text="一行に並べる(アライメント有):")
        row = layout.row(align=True)
        for i in range(3):
            row.operator(NullOperation.bl_idname, text=("列 %d" % (i)))

なお、operator の代わりに label 関数、prop 関数や menu 関数を使うことによって、ラベル、プロパティやメニューを一行に並べて配置することができます。

ボタンを一列に並べる

layout.operator 関数を複数回実行することでボタンを一列に配置することができますが、隙間が広く気に入らない方もいると思います。 隙間を縮めた状態でボタンを縦に並べるためには、layout.column 関数を使って列成分を取得し、取得した列成分に対して operator 関数を使ってボタンを配置します。

本節のサンプルでは、以下のように3つのボタンを一列に並べています。

        # 一列に並べる(アライメント無)
        layout.label(text="一列に並べる(アライメント無):")
        column = layout.column(align=False)
        for i in range(3):
            column.operator(NullOperation.bl_idname, text=("行 %d" % (i)))

ボタン間の隙間を無くすために align=True を指定できる点は、layout.row 関数と同様です。

        # 一列に並べる(アライメント有)
        layout.label(text="一列に並べる(アライメント有):")
        column = layout.column(align=True)
        for i in range(3):
            column.operator(NullOperation.bl_idname, text=("行 %d" % (i)))

なお、operator 関数の代わりに label 関数、prop 関数や menu 関数を使うことによって、ラベル、プロパティやメニューを一列に並べて配置することができます。

ボタンを複数列に配置する

layout.column 関数や layout.row 関数で取得した行成分や列成分に対してさらに行成分や列成分を取得することで、より複雑なボタンの配置を実現することができます。

本節のサンプルでは、以下のようにして2行2列にボタンを配置しています。

        # 複数列に配置する
        layout.label(text="複数列に配置する:")
        column = layout.column(align=True)
        row = column.row(align=True)
        row.operator(NullOperation.bl_idname, text="列 1, 行 1")
        row.operator(NullOperation.bl_idname, text="列 2, 行 1")
        row = column.row(align=True)
        row.operator(NullOperation.bl_idname, text="列 1, 行 2")
        row.operator(NullOperation.bl_idname, text="列 2, 行 2")

領域を分割する

layout.row 関数を用いて行成分を取得することで一行にボタンを配置することができますが、ボタンはすべて等幅になっていました。 ボタンの横幅を変えたい時は、layout.split 関数を用いて領域を分割します。

layout.split 関数の引数 percentage に値を指定することで、領域の横幅を決めることができます。 引数 percentage の値は浮動小数点数で指定し、1.0 で横幅100%、0.0 で横幅0%となります。 例えば、ツール・シェルフの横幅に対して70%の横幅を持つ領域を分割する場合は、layout.split(percentage=0.7) とします。

本節のサンプルでは、以下のような処理で領域を3分割しています。

        # 領域を分割する
        layout.label(text="領域を分割する:")
        split = layout.split(percentage=0.3)
        column = split.column(align=True)
        column.label(text="領域1:")
        column.operator(NullOperation.bl_idname, text="行 1")
        column.operator(NullOperation.bl_idname, text="行 2")
        split = split.split(percentage=0.7)
        column = split.column()
        column.label(text="領域2:")
        column.operator(NullOperation.bl_idname, text="行 1")
        column.operator(NullOperation.bl_idname, text="行 2")
        split = split.split(percentage=1.0)
        column = split.column(align=False)
        column.label(text="領域3:")
        column.operator(NullOperation.bl_idname, text="行 1")
        column.operator(NullOperation.bl_idname, text="行 2")

分割後の各領域では、縦並びにボタンを2つ表示しています。

layout.split 関数により分割した領域の変数 split に対して split.split 関数を実行することで、2つ以上の領域に分割することができます。 なお、split.split 関数に指定する引数 percentage について注意が必要です。 最初の領域分割で layout.split 関数を実行する時は、引数に指定した percentage がツール・シェルフの横幅に対する割合を示しますが、2回目の領域分割で split.split 関数を実行した時は、layout.split で分割した残りの領域、つまり本節のサンプルではツール・シェルフの横幅 70% の領域に対する割合を指定します。 同様に3回目の領域分割では、2回目に分割した残りの領域に対する割合を指定します。 従って、ツール・シェルフに対する横幅はそれぞれ、領域1で 30%、領域2で 70% × 0.7 = 49% 、領域3で 70% × 0.3 = 21% となります。

ボタンの横幅を自動的に拡大する

layout.operator 関数を使ってボタンを配置すると、ボタンの横幅が自動的に領域全体へ拡大されますが、ボタンの横幅を明示的に領域全体に拡大する方法もあります。

本節のサンプルでは、以下のように row.alignmentEXPAND を設定し、明示的にボタンの横幅を自動的に拡大しています。

        # 横幅を自動的に拡大する
        layout.label(text="横幅を自動的に拡大する:")
        row = layout.row()
        row.alignment = 'EXPAND'
        row.operator(NullOperation.bl_idname, text="列 1")
        row.operator(NullOperation.bl_idname, text="列 2")

ボタンを右寄せ・左寄せ配置にする

ボタンの横幅を自動的に拡大せず、右や左に寄せて配置することもできます。

本節のサンプルでは、以下のように row.alignmentLEFT を設定し、ボタンを左寄せ配置しています。

        # 左寄せする
        layout.label(text="左寄せする:")
        row = layout.row()
        row.alignment = 'LEFT'
        row.operator(NullOperation.bl_idname, text="列 1")
        row.operator(NullOperation.bl_idname, text="列 2")

また、row.alignmentRIGHT を設定することで右寄せ配置も可能です。

        # 右寄せする
        layout.label(text="右寄せする:")
        row = layout.row()
        row.alignment = 'RIGHT'
        row.operator(NullOperation.bl_idname, text="列 1")
        row.operator(NullOperation.bl_idname, text="列 2")

グループ化する

複数のUI部品をグループ化することもできます。 layout.box 関数の戻り値に対して operator 関数や menu 関数などを実行することで、作成したUIがグループ化されます。

本節のサンプルでは、以下のコードにより4つのボタンをグループ化しています。グループ内のUIは、通常のUIと同様のコードで構築することができます。

        # グループ化する
        layout.label(text="グループ化する:")
        row = layout.row()
        box = row.box()
        box_row = box.row()
        box_column = box_row.column()
        box_column.operator(NullOperation.bl_idname, text="行 1, 列 1")
        box_column.separator()
        box_column.operator(NullOperation.bl_idname, text="行 2, 列 1")
        box_row.separator()
        box_column = box_row.column()
        box_column.operator(NullOperation.bl_idname, text="行 1, 列 2")
        box_column.separator()
        box_column.operator(NullOperation.bl_idname, text="行 2, 列 2")

オプションの UI をカスタマイズする

2-3節 で説明したツール・シェルフのオプションのUIもカスタマイズすることができます。

オプションのUIをカスタマイズするために本節のサンプルでは、オペレータクラス ShowAllIcons を作成しています。 ShowAllIcons クラスは、Pythonから利用できるすべてのアイコンをツール・シェルフのオプションに表示する処理を定義しています。

オプションのUIをカスタマイズする処理を以下に示します。

    # オプションのUI
    def draw(self, context):
        layout = self.layout

        layout.prop(self, "num_column")

        layout.separator()

        # 利用可能なアイコンをすべて表示
        layout.label(text="利用可能なアイコン一覧:")
        icon = bpy.types.UILayout.bl_rna.functions['prop'].parameters['icon']
        for i, key in enumerate(icon.enum_items.keys()):
            if i % self.num_column == 0:
                row = layout.row()
            row.label(text=key, icon=key)

オプションのUIの構築は、オペレータクラスの draw メソッドで行います。 メソッドで定義している処理は、メニュークラスやパネルクラスで定義する draw メソッドと同じように、self.layout を通して行います。

利用可能なアイコンの識別子一覧は、bpy.types.UILayout.bl_rna.functions['prop'].parameters['icon'].enum_items.keys により取得できます。 取得したアイコンの識別子を、row.label 関数の引数 icon に指定することで、アイコンを表示することができます。 本節のサンプルでは、今後アドオンを作る人がアイコンを作る時の参考になるように、アイコンと識別子の対応関係がわかるようにしています。 このため、引数 text にアイコンの識別子を代入して表示しています。 また見やすさを考慮し、一行に表示可能なアイコンの数をオプションから指定することができます。ぜひ活用してください。

最後に、以下のコードにより、[利用可能なアイコンをすべて表示] ボタンを配置します。

        # プロパティのUIをカスタマイズする+アイコン一覧を表示する
        layout.label(text="プロパティのUIをカスタマイズする")
        layout.operator(ShowAllIcons.bl_idname)

まとめ

本節では、ボタンやメニューなどのUI部品の配置方法について説明しました。

Blenderに限らず、UIはボタンやメニューなど数多くの部品により構成されるため、一度に多くのAPIが登場し混乱された方も多いと思います。 しかし、個々の部品を制御するためのソースコードは数行~数十行の規模で、それぞれのソースコードも似たようなパターンであるため、ここまで読み進めてきた方であれば理解できると思います。 またUIの制御に限っては、分量が多い代わりに常に知っておくべきことが少ないため、本節の内容をすべて理解するよりも、必要な時のみ本節を参照しても問題はありません。

本節で紹介したUI部品とUI部品を追加するためのAPIの対応関係を、次にまとめます。

UI API
間隔をあける layout.separator
ラベル layout.label
ボタン layout.operator
メニュー(セレクトボックス) layout.menu
プロパティ layout.prop
行成分取得(アライメント無) layout.row
layout.row(align=False)
行成分取得(アライメント有) layout.row(align=True)
列成分取得(アライメント無) layout.column
layout.column(align=False)
列成分取得(アライメント有) layout.column(align=True)
UI部品の横幅を自動的に拡大 row.alignment = 'EXPAND'
UI部品を左寄せ row.alignment = 'LEFT'
UI部品を右寄せ row.alignment = 'RIGHT'
領域を分割 layout.split
グループ化 layout.box

ポイント