趣味プログラマによるOSS開発日誌

趣味で作っているOSSソフトウェアの紹介や関連技術の紹介、楽曲製作、Webデザイン勉強状況を紹介します。

プラグインでサブメニューを作成する方法

Blenderプラグインを公開してから、機能を追加して欲しいとかバグを修正して欲しいとか要望をたくさんいただいているわけですが、使ってくれている人が多いことが分かりうれしい反面、対応が大変ですね。

これらの要望に対応していく中でプラグイン内でツリー状のメニュー(右端に三角マークが出ていて、選択するとサブメニューが出るようなもの)を作成する必要があり、試行錯誤の末ようやく理解できた気がするのでまとめてみようと思います。

通常メニュー

通常のメニュー(選択したら処理が実行されるもの)の作り方ですが、これはプラグインの作成方法でも紹介しました。
※一部省略しています。

# オペレータ「Hoge」の処理を実施するクラス
class Menu(bpy.types.Operator):
    
    bl_idname = "uv.hoge"               # ID名
    bl_label = "Hoge Menu"              # メニューに表示される文字列
    bl_description = "Hoge Piyo"        # メニューに表示される説明文

    # 実際にプラグインが処理を実施する処理
    def execute(self, context):
        return {'FINISHED'}             # 成功した場合はFINISHEDを返す

# オペレータ「Hoge」を登録する関数
def menu_func(self, context):
    self.layout.operator(Menu.bl_idname)     # 登録したいクラスの「bl_idname」を指定

# プラグインをインストールしたときの処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.append(menu_func)

ここまでは問題ないと思います。
続いて、ツリー上のメニューを作成する方法を示します。

ツリー状のメニュー

最初に簡単なサンプルコードを示します。
表示されるメニューの構成は、「Hoge Menu」-「Hoge Sub Menu」となります。

# サブメニュー
class SubMenu(bpy.types.Operator):
    bl_idname = "uv.hoge_sub"
    bl_label = "Sub Hoge Menu"
    out_text = bpy.props.StringProperty()

    def execute(self, context):
        self.report({'INFO'}, self.out_text)
        return {'FINISHED'}

# メインメニュー
class Menu(bpy.types.Menu):
    bl_idname = "uv.hoge"
    bl_label = "Hoge Menu"
    bl_description = "Hoge Piyo"

    def draw(self, context):
        layout = self.layout
        # サブメニューの登録+出力文字列の登録
        layout.operator(SubMenu.bl_idname).out_text = "Sub Hoge Menu"

# Editモードにて「U」を押したときに表示されるメニューに登録(bpy.types.VIEW3D_MT_uv_map.append(menu_func)から呼ばれる)
def menu_func(self, context):
    self.layout.separator()            # メニューにセパレータを登録
    self.layout.menu(Menu.bl_idname)

# 登録
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.append(menu_func)

解説

  • サブメニュー用クラス

サブクラスのクラスは基本的に通常のメニューを作る時のクラスと変わらず、bpy.types.Operatorクラスを継承します。
ここでは確認として、サブメニューにout_textと呼ばれる変数を追加し、メニューが実行されたときにout_textが出力されるようにします。

# サブメニュー
class SubMenu(bpy.types.Operator):
    bl_idname = "uv.hoge_sub"
    bl_label = "Sub Hoge Menu"
    out_text = bpy.props.StringProperty()

    def execute(self, context):
        self.report({'INFO'}, self.out_text)
        return {'FINISHED'}
  • メインメニュー用クラス

メインメニューではツリー上のメニューを作成する必要がありますので、bpy.types.Menuクラスを継承しています。
さらにメインメニューは選択された時の動作を考える必要がなく、executeメソッドは不要です。
その代わりにメニューを表示するためのdrawメソッドを追加する必要があります。
drawメソッド内部ではlayout.operatorの引数に、bpy.types.Operatorのサブクラスのbl_idnameを代入して実行することでサブメニューを登録し、かつ登録されたサブメニューのメンバ変数out_textに文字列を代入しています。

# メインメニュー
class Menu(bpy.types.Menu):
    bl_idname = "uv.hoge"
    bl_label = "Hoge Menu"
    bl_description = "Hoge Piyo"

    def draw(self, context):
        layout = self.layout
        # サブメニューの登録+出力文字列の登録
        layout.operator(SubMenu.bl_idname).out_text = "Sub Hoge Menu"
  • 登録

登録の部分は通常の部分とほとんど変わりませんが、bpy.types.VIEW3D_MT_uv_map.appendの引数に指定した関数から呼ばれるmenu_funcに修正を入れています。
サブメニューの作成には関係ありませんが、self.layout.separator関数を実行することで、メニューにセパレートを入れることが出来ます。
これにより他のメニューと明確に区別でき、プラグインをインストールしたことにより追加されたメニューを分かりやすくします。
self.layout.menu関数の引数にbpy.types.Menuの派生クラスのbl_idnameを渡すことで、ツリー上のメニューを登録することが出来ます。

# Editモードにて「U」を押したときに表示されるメニューに登録(bpy.types.VIEW3D_MT_uv_map.append(menu_func)から呼ばれる)
def menu_func(self, context):
    self.layout.separator()            # メニューにセパレータを登録
    self.layout.menu(Menu.bl_idname)

# 登録
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.append(menu_func)

サンプルプラグイン

上記で示したものについて、動作するサンプルプラグインのコードを示します。

# Blender内部のデータ構造にアクセスするために必要
import bpy

# プラグインに関する情報
bl_info = {
    "name" : "Hoge Plugin",             # プラグイン名
    "author" : "Piyo",                  # 作者
    "version" : (0,1),                  # プラグインのバージョン
    "blender" : (2, 6, 5),              # プラグインが動作するBlenderのバージョン
    "location" : "UV Mapping > Hoge",   # Blender内部でのプラグインの位置づけ
    "description" : "Hoge Fuga Piyo",   # プラグインの説明
    "warning" : "",
    "wiki_url" : "",                    # プラグインの説明が存在するWikiページのURL
    "tracker_url" : "",                 # Blender Developer OrgのスレッドURL
    "category" : "UV"                   # プラグインのカテゴリ名
}

# サブメニュー
class SubMenu(bpy.types.Operator):
    bl_idname = "uv.hoge_sub"
    bl_label = "Sub Hoge Menu"
    out_text = bpy.props.StringProperty()

    def execute(self, context):
        self.report({'INFO'}, self.out_text)
        return {'FINISHED'}

# メインメニュー
class Menu(bpy.types.Menu):
    bl_idname = "uv.hoge"
    bl_label = "Hoge Menu"
    bl_description = "Hoge Piyo"

    def draw(self, context):
        layout = self.layout
        # サブメニューの登録+出力文字列の登録
        layout.operator(SubMenu.bl_idname).out_text = "Sub Hoge Menu"

# Editモードにて「U」を押したときに表示されるメニューに登録(bpy.types.VIEW3D_MT_uv_map.append(menu_func)から呼ばれる)
def menu_func(self, context):
    self.layout.separator()            # メニューにセパレータを登録
    self.layout.menu(Menu.bl_idname)

# 登録
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.append(menu_func)

# プラグインをアンインストールしたときの処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.remove(menu_func)

# メイン関数
if __name__ == "__main__":
    register()

サンプルプラグインの実行方法

上記のサンプルプラグインの実行方法を以下に示します。
プラグインのインストール手順は、BlenderWikiページに書かれています。
http://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Extensions/Python/Add-Ons

  1. 「File」-「User Preferences」を選択
  2. 「Addons」タブを選択
  3. 「Install from File...」ボタンを押す
  4. インストールしたいプラグインを選択
  5. インストールされたプラグインの横のチェックボックスをオン
  6. 「Edit Mode」で「U」を押す
  7. Hoge Menu」がツリー状のメニューとなっていることを確認する
  8. Hoge Menu」-「Sub Hoge Menu」をクリックすると処理が実行される(ログに「Sub Hoge Menu」と表示される)

実行結果

  • ツリー状のメニューの表示

  • 「Sub Hoge Menu」の選択結果

[2014.11.23追記]
Qiitaに本内容を投稿しました。
http://qiita.com/nutti/items/3f75f34adab99a805a35