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

Last Update: 2019.4.2

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

3-1節 ではマウスのイベントを扱う方法を説明しました。 マウスのイベントを扱うことができるようになると、キーボードのイベントも扱いたいと思いませんか? そこで本節では、キーボードのイベントを扱う方法を説明します。

作成するアドオンの仕様

キーボードのイベントを扱う方法を理解するため、本節で作成するアドオンは次のようなキーボードの入力情報を利用した機能を持つものとします。

キー 処理
[X] X軸正方向に平行移動
[Shift] + [X] X軸負方向に平行移動
[Y] Y軸正方向に平行移動
[Shift] + [Y] Y軸負方向に平行移動
[Z] Z軸正方向に平行移動
[Shift] + [Z] Z軸負方向に平行移動

アドオンを作成する

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

import bpy
from bpy.props import BoolProperty, PointerProperty
from mathutils import Vector


bl_info = {
    "name": "サンプル3-2: キーボードのキー入力に応じてオブジェクトを並進移動させる",
    "author": "Nutti",
    "version": (2, 0),
    "blender": (2, 75, 0),
    "location": "3Dビュー > プロパティパネル > オブジェクト並進移動",
    "description": "キーボードからの入力に応じてオブジェクトを並進移動させるアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}


# プロパティ
class TOM_Properties(bpy.types.PropertyGroup):

    running = BoolProperty(
        name="オブジェクト並進移動モード中",
        description="オブジェクト並進移動モード中か?",
        default=False
    )


# オブジェクト並進移動モード時の処理
class TranslateObjectMode(bpy.types.Operator):

    bl_idname = "object.translate_object_mode"
    bl_label = "オブジェクト並進移動モード"
    bl_description = "オブジェクト並進移動モードへ移行します"

    def modal(self, context, event):
        props = context.scene.tom_props

        # 3Dビューの画面を更新
        if context.area:
            context.area.tag_redraw()

        # キーボードのQキーが押された場合は、オブジェクト並進移動モードを終了
        if event.type == 'Q' and event.value == 'PRESS':
            props.running = False
            print("サンプル3-2: 通常モードへ移行しました。")
            return {'FINISHED'}

        if event.value == 'PRESS':
            value = Vector((0.0, 0.0, 0.0))
            if event.type == 'X':
                value.x = 1.0 if not event.shift else -1.0
            if event.type == 'Y':
                value.y = 1.0 if not event.shift else -1.0
            if event.type == 'Z':
                value.z = 1.0 if not event.shift else -1.0
            # 選択中のオブジェクトを並進移動する
            bpy.ops.transform.translate(value=value)

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        props = context.scene.tom_props
        if context.area.type == 'VIEW_3D':
            # 開始ボタンが押された時の処理
            if props.running is False:
                props.running = True
                # modal処理クラスを追加
                context.window_manager.modal_handler_add(self)
                print("サンプル3-2: オブジェクト並進移動モードへ移行しました。")
                return {'RUNNING_MODAL'}
        else:
            return {'CANCELLED'}


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

    bl_label = "オブジェクト並進移動モード"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        props = context.scene.tom_props
        # 開始/停止ボタンを追加
        if props.running is False:
            layout.operator(
                TranslateObjectMode.bl_idname, text="開始", icon="PLAY"
            )
        else:
            layout.operator(
                TranslateObjectMode.bl_idname, text="終了", icon="PAUSE"
            )


def register():
    bpy.utils.register_module(__name__)
    sc = bpy.types.Scene
    sc.tom_props = PointerProperty(
        name="プロパティ",
        description="本アドオンで利用するプロパティ一覧",
        type=TOM_Properties
    )
    print("サンプル3-2: アドオン「サンプル3-2」が有効化されました。")


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


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

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

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

プロパティパネルを表示し、項目 [オブジェクト並進移動モード] が追加されていることを確認します。

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

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

1 [3Dビュー] エリアのプロパティパネル上の項目 [オブジェクト並進移動モード] に配置されている [開始] ボタンをクリックすると、オブジェクト並進移動モードに移行します。この時、コンソールウィンドウに次のメッセージが出力されます。

サンプル3-2: オブジェクト並進移動モードへ移行しました。
2 オブジェクト並進移動モードではキーボードのキーの組み合わせによりオブジェクトを並進移動することができます。
3 キーボードの [Q] キーを押すことで、オブジェクト並進移動モードが終了します。オブジェクト並進移動モードを終了した時に、コンソールウィンドウに次のメッセージが表示されます。
サンプル3-2: 通常モードへ移行しました。

アドオンを無効化する

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

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

ソースコードの解説

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

アドオン内で利用するプロパティを定義する

3-1節 に引き続き、本節のサンプルでも複数のクラス間でデータを共有します。 プロパティの定義方法については 3-1節 の説明を参照することとし、ここではサンプルで定義しているプロパティの一覧を示します。

プロパティ 意味
running オブジェクト並進移動モード中のときに、値が True となる

modalメソッド

3-1節 では、面の削除処理中であることとモーダルモード中であることが対応していましたが、本節のサンプルではオブジェクト並進移動モードがモーダルモードに対応します。 以降本節では、モーダルモードと書かれていたらオブジェクト並進移動モードとして読み進めて問題ありません。

modal メソッドの最初の処理である [3Dビュー] エリアの更新処理までは、3-1節 と同様です。 本節では、[3Dビュー] エリアの更新処理までの説明をせずに、モーダルモードの終了処理から説明します。

モーダルモード終了処理

本節のサンプルでは、キーボードの [Q] キーを押すとモーダルモードを終了します。 この処理を実現するため、キーボードのキーイベントが発生した時に呼び出される modal メソッドの引数 event のイベント情報を利用します。 [Q] キーが押された時に、モーダルモードを終了するためのコードを次に示します。

        # キーボードのQキーが押された場合は、オブジェクト並進移動モードを終了
        if event.type == 'Q' and event.value == 'PRESS':
            props.running = False
            print("サンプル3-2: 通常モードへ移行しました。")
            return {'FINISHED'}

3-1節 でも説明しましたが、キーイベント発生時の変数 event には、次のようなメンバ変数が格納されています。

メンバ変数 意味
type 発生したイベントの識別子
value イベントの値

キーボードの [Q] キーが押されたとき、event.type には Qevent.valuePRESS が保存されます。 本節のサンプルではこのことを利用し、event.typeQevent.valuePRESS のときに、props.runningFalse に設定したあと {'FINISHED'} を返すことでモーダルモードを終了します。

オブジェクト並進移動モード時のキー入力状態の確認

オブジェクト並進移動モードでは、利用するキーの状態を確認してオブジェクトを並進移動する必要があります。 次に示すコードにより、キーの入力を確認してオブジェクトの移動量を設定します。

        if event.value == 'PRESS':
            value = Vector((0.0, 0.0, 0.0))
            if event.type == 'X':
                value.x = 1.0 if not event.shift else -1.0
            if event.type == 'Y':
                value.y = 1.0 if not event.shift else -1.0
            if event.type == 'Z':
                value.z = 1.0 if not event.shift else -1.0

基本的にはモーダルモード終了処理と同様に、イベント情報 event を利用します。 ただし、[Shift] キーを押した時に反対方向へ並進移動させる必要があるため、[Shift] キーが押されていることを判定する必要があります。

[Shift] キーが押されたか否かを判定するためには、event.shift 変数を参照します。 [Shift] キーが押されていると event.shiftTrue が設定されるため、event.shiftTrueFalse かで、移動量を 1.0 または -1.0 に設定します。

これで並進移動量を求める処理が完成しましたが、[Shift] キーが押されていることを判定するために event.shift 変数を利用することに疑問をもたれた方もいるかもしれません。 event.type には、'LEFT_SHIFT''RIGHT_SHIFT'といった [Shift] キーのイベントを扱うイベント情報が存在します。 しかし、本節のサンプルではあえて event.shift 変数を利用しています。

event.type の代わりになぜ event.shift 変数を利用する必要があるのでしょうか? それは、[Shift] キーを押したり離したりしたときしか modal メソッドが呼ばれず、1回の modal メソッドの呼び出しで1つのキーのイベントしか扱うことができないためです。 例えば、[Shift] キー + [X] キーを押す場合、プログラムからは [Shift] キーを押した後に [X] キーを押したようにみえるため、[Shift] キーのイベントが発生した後に [X] キーのイベントが発生することになります。 仮に event.type を用いて [Shift] キーが押されている状態を判定しようとすると、独自に「Shiftキーが押されている」状態を判断するための処理を作る必要があります。 このように、無駄な処理が増えてしまうことから本節のサンプルでは event.shift 変数を用いて [Shift] キーの情報を取得しています。

このように、キーが押されていることを判定するための変数は [Shift] キー以外にもあります。 次にその変数の一覧を示します。

変数 キー
event.alt [Alt]
event.ctrl [Ctrl]
event.oskey (Macの) [Command]
event.shift [Shift]
キーボードのキーを一定時間以上押し続けると、キーを連続して押したり離したりした状態になります。 これは一般的にキーリピートと呼ばれているもので、Blenderでもキーリピートによる連続したイベントを受け取ることができます。
例えば本節のサンプルでは、Xキーを一定時間押しっぱなしにすると、選択したオブジェクトがX軸の正方向へ連続して平行移動します。

オブジェクトの並進移動

キー入力から求めた移動量をもとに、オブジェクトを並進移動します。

            # 選択中のオブジェクトを並進移動する
            bpy.ops.transform.translate(value=value)

オブジェクトの並進移動は、bpy.ops.transform.translate 関数で行います。 引数 value に並進移動量を Vector クラスのインスタンスとして渡すことで、選択中のオブジェクトを並進移動します。 bpy.ops.transform.translate 関数には他にもいろいろな引数を渡すことができますが、ここでは割愛します。

modalメソッドの戻り値

3-1節 で紹介したサンプルの modal メソッドでは {'PASS_THROUGH'} を返していましたが、本節のサンプルでは {'RUNNING_MODAL'} で返しています。 これは特殊オブジェクトモード時にキーを押した時にBlender本体の処理へイベントが伝搬しないようにするためです。 仮に modal メソッドの戻り値を {'RUNNING_MODAL'} から {'PASS_THROUGH'} に変更してしまうと、Blenderにもキーイベントが発生してしまい、期待しない動作を引き起こしてしまいます。 例えば、Blenderが標準でオブジェクトの削除機能に割り当てている [X] キーを押した場合、Blenderの削除機能が起動してしまいます。

まとめ

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

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

ポイント