はじめてのBlenderアドオン開発
Last Update: 2023.3.1
Blender 2.8~3.0
はじめてのBlenderアドオン開発
Blender 2.8~3.0
Last Update: 2023.3.1
2-4. サブメニューを作成する
ここまで紹介したアドオンは、1階層のメニューに対して項目を追加するだけでしたが、サブメニュー(マウスオーバーすると展開されるメニュー)を作成して2階層以上のメニューを作ることもできます。 例えば、[3Dビューポート] スペースのメニュー [追加] > [メッシュ] は、親メニュー [追加] の下に子メニュー [メッシュ] がある2階層のメニューとなっています。 本節では、2-3節 のサンプルアドオンを改良し、並進移動するオブジェクトをメニューから選択できるようにします。 そして、このサンプルアドオンを説明しながら、多階層のメニューを作成する方法を解説します。
1-5節 を参考にして次に示すソースコードを入力し、ファイル名を sample_2-4.py
で保存してください。
import bpy
from bpy.props import StringProperty, FloatProperty, EnumProperty
bl_info = {
"name": "サンプル 2-4: オブジェクトを並進移動するアドオン③",
"author": "ぬっち(Nutti)",
"version": (3, 0),
"blender": (2, 80, 0),
"location": "3Dビューポート > オブジェクト",
"description": "オブジェクトを並進移動するサンプルアドオン",
"warning": "",
"support": "TESTING",
"doc_url": "",
"tracker_url": "",
"category": "Object"
}
# オブジェクトを並進移動するオペレータ
class SAMPLE24_OT_TranslateObject(bpy.types.Operator):
bl_idname = "object.sample24_translate_object"
bl_label = "並進移動"
bl_description = "オブジェクトを並進移動します"
bl_options = {'REGISTER', 'UNDO'}
axis: EnumProperty(
name="移動軸",
description="移動軸を設定します",
default='X',
items=[
('X', "X軸", "X軸に沿って並進移動します"),
('Y', "Y軸", "Y軸に沿って並進移動します"),
('Z', "Z軸", "Z軸に沿って並進移動します"),
]
)
amount: FloatProperty(
name="移動量",
description="移動量を設定します",
default=1.0,
)
obj_name: StringProperty(options={'HIDDEN'})
def execute(self, context):
# 並進移動するオブジェクトを取得
obj = bpy.data.objects[self.obj_name]
if self.axis == 'X':
obj.location[0] += self.amount
elif self.axis == 'Y':
obj.location[1] += self.amount
elif self.axis == 'Z':
obj.location[2] += self.amount
self.report({'INFO'}, "サンプル 2-4: 『{}』を{}軸方向へ {} 並進移動しました。"
.format(obj.name, self.axis, self.amount))
print("サンプル 2-4: オペレータ『{}』が実行されました。".format(self.bl_idname))
return {'FINISHED'}
# メインメニュー
class SAMPLE24_MT_TranslateObject(bpy.types.Menu):
bl_idname = "SAMPLE24_MT_TranslateObject"
bl_label = "オブジェクトの並進移動"
bl_description = "オブジェクトを並進移動します"
def draw(self, context):
layout = self.layout
# サブメニューの登録
# bpy.data.objects:オブジェクト一覧
for o in bpy.data.objects:
if o.type == 'MESH':
ops = layout.operator(
SAMPLE24_OT_TranslateObject.bl_idname, text=o.name
)
ops.obj_name = o.name
def menu_fn(self, context):
self.layout.separator()
self.layout.menu(SAMPLE24_MT_TranslateObject.bl_idname)
classes = [
SAMPLE24_OT_TranslateObject,
SAMPLE24_MT_TranslateObject,
]
def register():
for c in classes:
bpy.utils.register_class(c)
bpy.types.VIEW3D_MT_object.append(menu_fn)
print("サンプル 2-4: アドオン『サンプル 2-4』が有効化されました。")
def unregister():
bpy.types.VIEW3D_MT_object.remove(menu_fn)
for c in classes:
bpy.utils.unregister_class(c)
print("サンプル 2-4: アドオン『サンプル 2-4』が無効化されました。")
if __name__ == "__main__":
register()
1-5節 を参考にして作成したアドオンを有効化すると、コンソールウィンドウに次の文字列が出力されます。
サンプル 2-4: アドオン『サンプル 2-4』が有効化されました。
アドオンを有効化したあと、[3Dビューポート] スペースのメニュー [オブジェクト] > [オブジェクトの並進移動] にサブメニューが追加されていることを確認します。 サブメニューには、[3Dビューポート] スペースに存在するメッシュ型のオブジェクト名の項目が追加されています。 なお、Blenderを日本語化している場合、自動翻訳により「Cube」は「立方体」と表示されるため、注意してください。
1 | [3Dビューポート] スペースのメニュー [オブジェクト] > [オブジェクトの並進移動] から、並進移動するオブジェクトの名前を選んで実行すると、選択したオブジェクトが並進移動します。 |
2 | 2-3節 と同様、並進移動時の移動軸・移動量を、オペレータプロパティから変更できます。 |
1-5節 を参考にして有効化したアドオンを無効化すると、コンソールウィンドウに次の文字列が出力されます。
サンプル 2-4: アドオン『サンプル 2-4』が無効化されました。
ソースコードの大部分は 2-3節 からの流用です。 ここでは、新規で追加した部分について解説します。
サブメニューを追加するためには、bpy.types.Menu
クラスを継承した メニュークラス を作成する必要があります。
# メインメニュー
class SAMPLE24_MT_TranslateObject(bpy.types.Menu):
bl_idname = "SAMPLE24_MT_TranslateObject"
bl_label = "オブジェクトの並進移動"
bl_description = "オブジェクトを並進移動します"
def draw(self, context):
layout = self.layout
# サブメニューの登録
# bpy.data.objects:オブジェクト一覧
for o in bpy.data.objects:
if o.type == 'MESH':
ops = layout.operator(
SAMPLE24_OT_TranslateObject.bl_idname, text=o.name
)
ops.obj_name = o.name
オペレータクラスと同様、メニュークラスにはクラス変数 bl_idname
, bl_label
, bl_description
を定義する必要がありますが、bl_options
を定義する必要はありません。 ただし bl_idname
は、メニュークラスのクラス名と同一の文字列を指定する必要があります。 メニュークラスのクラス名は、フォーマットXXX_MT_YYY
にしたがう必要があります。 XXX
は英大文字から始まる英字/数字/アンダースコア(_
)から構成される文字列、YYY
は英字/数字/アンダースコア(_
)から構成される文字列です。
また、メニュークラスでは、メニューの描画に必要な draw
メソッドを実装する必要があります。 メニューが表示されるたびに draw
メソッドが呼ばれ、次の引数が渡されてきます。
引数 | 型 | 値の説明 |
---|---|---|
self |
メニュークラス | メニュークラスのインスタンス |
context |
bpy.types.Context |
draw メソッドが呼ばれたときのコンテキスト |
サブメニューへの項目追加は self.layout.operator
メソッドで行います。 本節のサンプルアドオンでは、[3Dビューポート] スペース上の全てのオブジェクト名をメニュー項目に追加するため、self.layout.operator
メソッドの第1引数にオペレータクラスのクラス変数 bl_idname
を指定し、引数 text
にオブジェクト名を指定しています。
オペレータクラス SAMPLE24_OT_TranslateObject
は、並進移動するオブジェクトをオブジェクト名で判定するため、オペレータクラスのクラス変数 obj_name
にオブジェクト名を代入します。 obj_name
は StringProperty
クラスとして定義します。 なお、引数 options
に値 {'HIDDEN'}
を渡していますが、これはオペレータプロパティとしてユーザに変更を許したくないプロパティに対して設定します。 これにより obj_name
がオペレータプロパティとして表示されなくなります。
obj_name: StringProperty(options={'HIDDEN'})
オペレータクラスの execute
メソッドでは、クラス変数 obj_name
に代入されたオブジェクト名を用いて、並進移動の対象となるオブジェクトを取得して並進移動させています。 なお、詳細を省略した execute
メソッドの具体的な処理については、ソースコードのコメントに処理内容を細かく記載しているため、確認してみてください。
最後に、[3Dビューポート] スペースのメニュー [オブジェクト] にメニューを追加します。
def menu_fn(self, context):
self.layout.separator()
self.layout.menu(SAMPLE24_MT_TranslateObject.bl_idname)
オペレータをメニューに追加するときは、self.layout.operator
メソッドを利用していました。 しかし、サブメニューをメニューに追加する場合は、self.layout.menu
メソッドを利用する必要があります。 self.layout.menu
メソッドにメニュークラス SAMPLE24_MT_TranslateObject
クラスのクラス変数 bl_idname
を引数として渡すことで、サブメニューをメニューに追加できます。
サブメニューにさらにサブメニュー(サブサブメニュー)を追加するなど、3階層以上のメニューを作成することもできます。
次のコードは、先ほど作成したサンプルアドオンのメニューとサブメニューの間に、新たなメニューとして [オブジェクトの並進移動(サブメニュー)] を追加する処理です。
import bpy
from bpy.props import StringProperty, FloatProperty, EnumProperty
bl_info = {
"name": "サンプル 2-4: オブジェクトを並進移動するアドオン③",
"author": "ぬっち(Nutti)",
"version": (3, 0),
"blender": (2, 80, 0),
"location": "3Dビューポート > オブジェクト",
"description": "オブジェクトを並進移動するサンプルアドオン",
"warning": "",
"support": "TESTING",
"doc_url": "",
"tracker_url": "",
"category": "Object"
}
# オブジェクトを並進移動するオペレータ
class SAMPLE24_OT_TranslateObject(bpy.types.Operator):
bl_idname = "object.sample24_translate_object"
bl_label = "並進移動"
bl_description = "オブジェクトを並進移動します"
bl_options = {'REGISTER', 'UNDO'}
axis: EnumProperty(
name="移動軸",
description="移動軸を設定します",
default='X',
items=[
('X', "X軸", "X軸に沿って並進移動します"),
('Y', "Y軸", "Y軸に沿って並進移動します"),
('Z', "Z軸", "Z軸に沿って並進移動します"),
]
)
amount: FloatProperty(
name="移動量",
description="移動量を設定します",
default=1.0,
)
obj_name: StringProperty(options={'HIDDEN'})
def execute(self, context):
# 並進移動するオブジェクトを取得
obj = bpy.data.objects[self.obj_name]
if self.axis == 'X':
obj.location[0] += self.amount
elif self.axis == 'Y':
obj.location[1] += self.amount
elif self.axis == 'Z':
obj.location[2] += self.amount
self.report({'INFO'}, "サンプル 2-4: 『{}』を{}軸方向へ {} 並進移動しました。"
.format(obj.name, self.axis, self.amount))
print("サンプル 2-4: オペレータ『{}』が実行されました。".format(self.bl_idname))
return {'FINISHED'}
# サブメニュー
class SAMPLE24_MT_TranslateObjectSub(bpy.types.Menu):
bl_idname = "SAMPLE24_MT_TranslateObjectSub"
bl_label = "オブジェクトの並進移動(サブメニュー)"
bl_description = "オブジェクトを並進移動します(サブメニュー)"
def draw(self, context):
layout = self.layout
# サブサブメニューの登録
# bpy.data.objects:オブジェクト一覧
for o in bpy.data.objects:
if o.type == 'MESH':
ops = layout.operator(
SAMPLE24_OT_TranslateObject.bl_idname, text=o.name
)
ops.obj_name = o.name
# メインメニュー
class SAMPLE24_MT_TranslateObject(bpy.types.Menu):
bl_idname = "SAMPLE24_MT_TranslateObject"
bl_label = "オブジェクトの並進移動"
bl_description = "オブジェクトを並進移動します"
def draw(self, context):
layout = self.layout
# サブメニューの登録
layout.menu(SAMPLE24_MT_TranslateObjectSub.bl_idname)
def menu_fn(self, context):
self.layout.separator()
self.layout.menu(SAMPLE24_MT_TranslateObject.bl_idname)
classes = [
SAMPLE24_OT_TranslateObject,
SAMPLE24_MT_TranslateObject,
SAMPLE24_MT_TranslateObjectSub,
]
def register():
for c in classes:
bpy.utils.register_class(c)
bpy.types.VIEW3D_MT_object.append(menu_fn)
print("サンプル 2-4: アドオン『サンプル 2-4』が有効化されました。")
def unregister():
bpy.types.VIEW3D_MT_object.remove(menu_fn)
for c in classes:
bpy.utils.unregister_class(c)
print("サンプル 2-4: アドオン『サンプル 2-4』が無効化されました。")
if __name__ == "__main__":
register()
アドオンを作成して有効化すると、図のように3階層のメニューが作成されていることが確認できます。
コードを見るとわかると思いますが、3階層のメニューは2階層のメニューを作成したときの応用です。
# サブメニュー
class SAMPLE24_MT_TranslateObjectSub(bpy.types.Menu):
bl_idname = "SAMPLE24_MT_TranslateObjectSub"
bl_label = "オブジェクトの並進移動(サブメニュー)"
bl_description = "オブジェクトを並進移動します(サブメニュー)"
def draw(self, context):
layout = self.layout
# サブサブメニューの登録
# bpy.data.objects:オブジェクト一覧
for o in bpy.data.objects:
if o.type == 'MESH':
ops = layout.operator(
SAMPLE24_OT_TranslateObject.bl_idname, text=o.name
)
ops.obj_name = o.name
# メインメニュー
class SAMPLE24_MT_TranslateObject(bpy.types.Menu):
bl_idname = "SAMPLE24_MT_TranslateObject"
bl_label = "オブジェクトの並進移動"
bl_description = "オブジェクトを並進移動します"
def draw(self, context):
layout = self.layout
# サブメニューの登録
layout.menu(SAMPLE24_MT_TranslateObjectSub.bl_idname)
メニュークラス SAMPLE24_MT_TranslateObject
では、サブサブメニュー用に作成したメニュークラス SAMPLE24_MT_TranslateObjectSub
のクラス変数 bl_idname
を、self.layout.menu
メソッドに渡しています。 そして、SAMPLE24_MT_TranslateObjectSub
クラスの中でオペレータを登録することで、3階層のメニューを作成しています。
同様の手順を踏むことで、4階層、5階層、・・・とメニューの階層を増やすことができます。
2-3節 で紹介したサンプルアドオンを改造し、並進移動するオブジェクトをサブメニューから選択できるようにしました。
サブメニューを用いることで、本節のサンプルアドオンのように処理対象を選択できるようにしたり、機能ごとにメニュー項目を整理できるようになります。 ぜひここでサブメニューの作り方を習得し、わかりやすいUI作りに活かしましょう。
bpy.types.Menu
クラスを継承して作成するbl_idname
を引数にして self.layout.menu
メソッドを呼び出し、そのメニュークラスの draw
メソッド内で、オペレータクラスのクラス変数 bl_idname
を self.layout.operation
メソッドの引数に指定することで、サブメニューを作成できるself.layout.menu
メソッドを活用することで、3階層以上のメニューを作成できる