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

Last Update: 2023.3.1

Blender 2.8~3.0

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

Blender 2.8~3.0

Last Update: 2023.3.1

3-2. キーボードのイベントを扱う

3-1節 では、マウスのイベントを扱う方法を説明しました。 本節はこの流れで、キーボードのイベントを扱う方法を説明します。

作成するアドオンの仕様

キーボードのイベントを扱う方法を理解するため、次のような機能を備えるサンプルアドオンを紹介します。

アドオンを作成する

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

import bpy


bl_info = {
    "name": "サンプル 3-2: 入力したキーを表示するアドオン",
    "author": "ぬっち(Nutti)",
    "version": (3, 0),
    "blender": (2, 80, 0),
    "location": "3Dビューポート > Sidebar > サンプル 3-2",
    "description": "入力したキーのテキストオブジェクトを表示するアドオン",
    "warning": "",
    "support": "TESTING",
    "doc_url": "",
    "tracker_url": "",
    "category": "Object"
}


# 大文字のアルファベットリスト
ALPHABET_LIST = [chr(i) for i in range(65, 65 + 26)]


# 入力したキーのテキストオブジェクトを表示するオペレータ
class SAMPLE32_OT_ShowInputKey(bpy.types.Operator):

    bl_idname = "object.sample32_show_input_key"
    bl_label = "入力キーを表示"
    bl_description = "入力したキーをテキストオブジェクトとして表示します"

    # Trueの場合は、キーを入力したときに入力したキーに対する
    # テキストオブジェクトが表示される(Trueの場合は、モーダルモード中である)
    __running = False
    # テキストオブジェクトの名前
    __text_object_name = None

    # モーダルモード中はTrueを返す
    @classmethod
    def is_running(cls):
        return cls.__running

    def modal(self, context, event):
        op_cls = SAMPLE32_OT_ShowInputKey

        # エリアを再描画
        if context.area:
            context.area.tag_redraw()

        # パネル [入力キーのテキストオブジェクト表示] のボタン [終了] を
        # 押したときに、モーダルモードを終了
        if not self.is_running():
            # テキストオブジェクトを削除
            if op_cls.__text_object_name in bpy.data.objects:
                bpy.data.objects.remove(bpy.data.objects[op_cls.__text_object_name])
            op_cls.__text_object_name = None
            return {'FINISHED'}

        input_key = event.type
        # 大文字のアルファベット以外はすべてイベントを無視する
        if input_key not in ALPHABET_LIST:
            return {'PASS_THROUGH'}

        if op_cls.__text_object_name in bpy.data.objects:
            bpy.data.objects[op_cls.__text_object_name].data.body = input_key

        return {'RUNNING_MODAL'}

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

        if context.area.type == 'VIEW_3D':
            # [開始] ボタンが押された時の処理
            if not op_cls.is_running():
                # テキストオブジェクト作成
                bpy.ops.object.text_add(location=(0.0, 0.0, 0.0), radius=2.0)
                op_cls.__text_object_name = context.active_object.name
                bpy.data.objects[op_cls.__text_object_name].data.body = ""
                # モーダルモードを開始
                context.window_manager.modal_handler_add(self)
                op_cls.__running = True
                print("サンプル 3-2: 入力キーの表示処理を開始しました。")
                return {'RUNNING_MODAL'}
            # [終了] ボタンが押された時の処理
            else:
                op_cls.__running = False
                print("サンプル 3-2: 入力キーの表示処理を終了しました。")
                return {'FINISHED'}
        else:
            return {'CANCELLED'}


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

    bl_label = "入力キーを表示"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "サンプル 3-2"
    bl_context = "objectmode"

    def draw(self, context):
        op_cls = SAMPLE32_OT_ShowInputKey

        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")


classes = [
    SAMPLE32_OT_ShowInputKey,
    SAMPLE32_PT_ShowInputKey,
]


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


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


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

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

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

Sidebarを表示し、タブ [サンプル 3-2] が追加されていることを確認します。

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

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

1 [3Dビューポート] スペースのSidebarの > [サンプル 3-2] > [入力キーを表示] に配置されている [開始] ボタンをクリックすると、入力キーを表示するモードに移行します。このとき、コンソールウィンドウに次のメッセージが出力されます。

サンプル 3-2: 入力キーの表示処理を開始しました。
2 入力キーを表示するモードでは、入力したキーボードのキーをテキストオブジェクトとして表示します。
3 [3Dビューポート] スペースのSidebarの [サンプル 3-2] > [入力キーを表示] に配置されている [終了] ボタンをクリックすると、入力キーを表示するモードが終了します。また、コンソールウィンドウに次のメッセージが表示されます。
サンプル 3-2: 入力キーの表示処理を終了しました。

アドオンを無効化する

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

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

ソースコードの解説

ソースコードを見ると、変数名などの細かい部分やオペレータクラスの invoke メソッドと modal メソッドの処理を除いて 3-1節 で説明した内容とほとんど同じであることがわかります。 このため本節では、オペレータクラス SAMPLE32_OT_ShowInputKeyinvoke メソッドと modal メソッドについてのみ説明します。

3-1節 では、オブジェクトが回転中であることとモーダルモード中であることが対応していましたが、本節のサンプルアドオンでは、入力キーの表示中であることがモーダルモード中に対応します。 以降本節では、モーダルモードと書かれていたら、入力キーの表示中であるとして読み進めて問題ありません。

invokeメソッド

オペレータクラス SAMPLE32_OT_ShowInputKeyinvoke メソッドでは、モーダルモード中ではない場合(クラスメソッド is_runningFalse を返した場合)に、bpy.ops.object.text_add 関数を呼び出して、キーを表示するためのテキストオブジェクトを作成します。 このとき、テキストオブジェクトで表示するテキストは空文字列とします。 また、modal メソッドからテキストオブジェクトにアクセスするために、クラス変数 __text_object_name に作成したテキストオブジェクトのオブジェクト名を保存しておきます。

modalメソッド

modal メソッドの最初の処理である [3Dビューポート] スペースの更新処理は、3-1節 と同様です。 ここでは、それ以降の処理について説明します。

モーダルモード終了処理

クラスメソッド is_runningFalse を返すとき、モーダルモードを終了させます。 このとき、モーダルモード開始時に作成したテキストオブジェクトを削除する必要があります。 クラス変数 __text_object_name に保存しておいた、テキストオブジェクトのオブジェクト名を利用し、削除対象のオブジェクトを bpy.data.objects から削除します。 bpy.data.objects.remove メソッドの引数に、削除対象のオブジェクトのオブジェクト名を指定することで、オブジェクトを削除できます。 オブジェクト削除後、modal メソッドは {'FINISHED'} を返して、モーダルモードを終了します。

# パネル [入力キーのテキストオブジェクト表示] のボタン [終了] を
# 押したときに、モーダルモードを終了
if not self.is_running():
    # テキストオブジェクトを削除
    if op_cls.__text_object_name in bpy.data.objects:
        bpy.data.objects.remove(bpy.data.objects[op_cls.__text_object_name])
    op_cls.__text_object_name = None
    return {'FINISHED'}

テキストオブジェクトのテキスト更新処理

入力されたキーに従って、テキストオブジェクトのテキストを更新します。 テキストオブジェクトのテキストを更新するためには、オブジェクトのメンバ変数 data.body に、表示するテキストを設定する必要があります。

入力されたキーは、イベント情報である引数 event のメンバ変数 type に識別子として保存されている(3-1節 参照)ため、これを利用します。 ALPHABET_LIST は、大文字のアルファベット 'A' から 'Z' までが保存されたリストとなっていて、event.type がリスト内のいずれかの要素に一致する場合に、テキストオブジェクトのテキストを更新しています。 もし、リスト中のいずれの要素にも一致しない場合、modal メソッドは {'PASS_THROUGH'} を返してしまうため、テキストオブジェクトを更新しません。

input_key = event.type
# 大文字のアルファベット以外はすべてイベントを無視する
if input_key not in ALPHABET_LIST:
    return {'PASS_THROUGH'}

if op_cls.__text_object_name in bpy.data.objects:
    bpy.data.objects[op_cls.__text_object_name].data.body = input_key

なお、大文字のアルファベットのリストは、次のコードで作成できます。

# 大文字のアルファベットリスト
ALPHABET_LIST = [chr(i) for i in range(65, 65 + 26)]

まとめ

本節では、キーボードのキーイベントを扱う方法を紹介しました。 3-1節 のサンプルアドオンと比較すると、modal メソッドまたは invoke メソッドの引数に渡されてくるイベント情報を用いる点で、マウスのイベントとキーボードのイベントの扱い方が、ほとんど同じであることが理解できたかと思います。

ユーザからの入力イベントを扱い、インタラクティブ性の高い機能を提供することは、これまでに紹介してきた execute メソッドを単に実行するだけの処理と比べて、処理が複雑になりがちでバグも発生しやすくなります。 しかし、キーボードやマウスのイベントを適切に扱うことができるようになると、アドオンで実現できることが広がるため、より高度なアドオンを作ることができるようになるでしょう。

ポイント