キーボードのキーイベントを扱う

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

作成するアドオンの仕様

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

  • 3Dビュー エリアのプロパティパネルの項目 オブジェクト並進移動モード から、オブジェクト並進移動モードを開始するためのボタンを配置する
  • キーボードのキー Q が押された時に、オブジェクト並進移動モードを終了する
  • オブジェクト並進移動モード中は、以下のキーボードのキー入力に応じて、オブジェクトを並進移動させる
キー 処理
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」が有効化されました。
プロパティパネルを表示し、項目 オブジェクト並進移動モード が追加されていることを確認します。 3-2節 アドオン有効化

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

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

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

2
オブジェクト並進移動モードではキーボードのキーの組み合わせによりオブジェクトを並進移動することができます。 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() メソッドを単に実行するだけの処理と比べて、処理が複雑になりがちでバグも発生しやすくなります。しかし、キーボードやマウスのイベントを適切に扱うことができるようになると、アドオンで実現できることが広がるため、より高度なアドオンを作ることができるようになるでしょう。

ポイント

  • modal() メソッドや invoke() メソッドの引数に渡されてくるイベント情報を用いることにより、キーボードのキーイベントを扱うことができる
  • 一部のキー(Alt キー、Ctrl キー、Command キー、Shift キー)には、押されている状態を判定するためのイベント情報が個別に存在する
  • ユーザからの入力イベントを扱うことで、インタラクティブな機能を提供できる反面、機能を実現するための処理が複雑化する傾向がある