[Blender] Blenderスクリプトでマウスやキーボードのイベントを扱う方法

Blenderのプラグインを作っているとマウスやキーボードからのイベントを扱って、よりインタラクティブ性の高い機能を提供したくなると思います。
ObjectモードでSキーを押した時に、マウスの移動だけでオブジェクトのサイズを変更する機能は、まさしくマウスからのイベントを扱っている例で、操作感が良いですよね。

そこで今回は、Blenderのスクリプトでマウスやキーボードのイベントを扱う方法を、サンプルを交えて紹介します。

# サンプル

今回もサンプルを最初に示します。
このサンプルは、マウスの位置に応じてオブジェクトを拡大・縮小することが出来ます。
以下のイベントを扱い、Blenderにおける入力イベントのパターンが一通り分かるようにしました。

* マウスを移動した時
* マウスのクリックした時
* キーボードのキーを押した時

## ソースコード

```py:mouse_keyboard_event_test.py
import bpy

bl_info = {
    "name" : "Mouse/Keyboard event test",
    "author" : "Nutti",
    "version" : (0,1),
    "blender" : (2, 7, 0),
    "location" : "UV > Mouse/Keyboard event test",
    "description" : "Mouse/Keyboard event test.",
    "warning" : "",
    "wiki_url" : "",
    "tracker_url" : "",
    "category" : "UV"
}

running = False     # イベントの取得中であればTrue

class MouseKeyboardEventTest(bpy.types.Operator):
    """Mouse/Keyboard event test."""
    
    bl_idname = "uv.mouse_keyboard_event_test"
    bl_label = "Mouse/Keyboard event test"
    bl_description = "Mouse/Keyboard event test"
    bl_options = {'REGISTER', 'UNDO'}
    
    obj_scale = 1.0     # オブジェクトの拡大率
    orig_x = 0.0        # ボタンを押したときのX座標
    orig_scale = []     # ボタンを押したときのオブジェクトの拡大率

    def modal(self, context, event):
        global running
    
        # 動作していない場合は終了
        if running is False:
            return {'PASS_THROUGH'}
        
        # オブジェクトへ拡大率を適用
        active_obj = bpy.context.active_object
        active_obj.scale[0] = self.orig_scale[0] * self.obj_scale
        active_obj.scale[1] = self.orig_scale[1] * self.obj_scale
        active_obj.scale[2] = self.orig_scale[2] * self.obj_scale
        
        #############################################
        # マウス/キーボードのイベントを扱う処理
        #############################################
        
        # マウス移動 - マウスのX座標をもとにオブジェクトの拡大率を設定
        if event.type == 'MOUSEMOVE':
            factor = 1.0 / 100.0
            self.obj_scale = 1.0 + (event.mouse_x - self.orig_x) * factor
            return {'RUNNING_MODAL'}
        # スペースキーを押した時 - 拡大率をボタンを押した時に戻す
        elif event.type == 'SPACE':
            if event.value == 'PRESS':
                self.orig_x = event.mouse_x
                return {'RUNNING_MODAL'}
        # 右クリックを押した時 - 変更した拡大率を確定して操作を終了
        if event.type == 'RIGHTMOUSE':
            if event.value == 'PRESS':
                running = False
                return {'FINISHED'}
        
        return {'RUNNING_MODAL'}
    
    def invoke(self, context, event):
        global running
        
        if context.area.type == 'VIEW_3D':
            # 動作していない状態でボタンを押されたら、各イベントを受け付ける
            if running is False:
                running = True
                # 初期状態の設定
                self.obj_scale = 1.0
                self.orig_x = event.mouse_x
                active_obj = bpy.context.active_object
                self.orig_scale = [
                    active_obj.scale[0],
                    active_obj.scale[1],
                    active_obj.scale[2]
                ]
                # modalハンドラの設定
                context.window_manager.modal_handler_add(self)
                return {'RUNNING_MODAL'}
        return {'CANCELLED'}
        
# 'N'キーを押したときにVIEW 3Dの右側に表示されるメニューにボタンを設置
class OBJECT_PT_MKET(bpy.types.Panel):
    bl_label = "Mouse/Keyboard Event Test"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    def draw(self, context):
        sc = context.scene
        layout = self.layout
        # ボタンの配置
        layout.operator(MouseKeyboardEventTest.bl_idname, text="Start", icon="PLAY")


def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)


if __name__ == "__main__":
    register()

```

## 使い方

スクリプトのインストールの方法は以下に従って行ってください。
[Blender Wiki](http://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Extensions/Python/Add-Ons)

View 3Dで'N'キーを押した時に右側に表示されるメニューの「Mouse/Keyboard Event Test」にある「Start」ボタンをクリックすると、以下の操作を受け付けるようになります。

|操作|オブジェクトへの動作|
|---|---|
|マウス移動|ボタンをクリックした場所からマウスを画面右方向に動かすと選択されたオブジェクトが拡大され、画面左方向に動かすと縮小されます。|
|スペースキー|ボタンをクリックした時のオブジェクトの大きさに戻します。|
|右クリック|変更したオブジェクトの大きさを確定し、操作を終了します。|


# サンプルの解説

Blenderスクリプトの基本的な解説は以下の記事で紹介しているので、ここでは新しく追加された要素の解説に絞ります。
[\[Blender\] Blenderプラグインの作り方](http://qiita.com/nutti/items/a836391723bd28cd3e4c)

## ボタンの登録

View 3Dで'N'キーを押した時に表示される右側のメニューにボタンを追加します。
ボタンの登録については本記事の説明の範囲を超えてしまうので、今のところはこんなもんなんだと思ってください。

```py
# 'N'キーを押したときにVIEW 3Dの右側に表示されるメニューにボタンを設置
class OBJECT_PT_MKET(bpy.types.Panel):
    bl_label = "Mouse/Keyboard Event Test"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    def draw(self, context):
        # ボタンの配置
        # ...
```

## invokeメソッド

**クラスが呼びだされた時に呼ばれるメソッド** です。
今回のサンプルでは、ボタンをクリックした時に呼ばれます。

```py
    def invoke(self, context, event):
        global running
        
        if context.area.type == 'VIEW_3D':
            # 動作していない状態でボタンを押されたら、各イベントを受け付ける
            if running is False:
                running = True

                # 初期状態の設定
                # ....

                # modalハンドラの設定
                context.window_manager.modal_handler_add(self)
                return {'RUNNING_MODAL'}
        return {'CANCELLED'}
```

ボタンをクリックした後マウスのイベントを受け付けるように、 `context.window_manager.modal_handler_add` 関数にハンドラ( `modal` メソッドを持つオブジェクト)を指定しています。
ここでは `invoke` メソッドと `modal` メソッドをクラスが同時に持つため、 `self` を指定します。さらに各種初期状態の設定も行っています。

ここでハンドラを設定すると **以降はmodalメソッドが呼ばれ続けてしまうため**、停止時にイベントが発生してもオブジェクトへの操作を無くすために `running` 変数を用意しています。


## modalメソッド

`invoke` メソッドで `context.window_manager.modal_handler_add` 関数によりハンドラを登録しておくと、**マウスやキーボードからのイベントが発生した時に呼ばれる** ようになります。

```py
    def modal(self, context, event):
        global running

        # 動作していない場合は終了
        if running is False:
            return {'PASS_THROUGH'}

        # ....
        
        # マウス移動 - マウスのX座標をもとにオブジェクトの拡大率を設定
        if event.type == 'MOUSEMOVE':
            factor = 1.0 / 100.0
            self.obj_scale = 1.0 + (event.mouse_x - self.orig_x) * factor
            return {'RUNNING_MODAL'}
        # スペースキーを押した時 - 拡大率をボタンを押した時に戻す
        elif event.type == 'SPACE':
            if event.value == 'PRESS':
                self.orig_x = event.mouse_x
                return {'RUNNING_MODAL'}
        # 右クリックを押した時 - 変更した拡大率を確定して操作を終了
        if event.type == 'RIGHTMOUSE':
            if event.value == 'PRESS':
                running = False
                return {'FINISHED'}
        
        return {'RUNNING_MODAL'}
```

`modal` メソッドではそれぞれのイベントに対して、以下のように処理しています。

|イベント|処理|
|---|---|
|マウス移動|現在位置を取得し、現在位置と初期位置の横方向の差を計算してオブジェクトの拡大率を設定します。<br>その後、`{'RUNNING_MODAL'}`を返して処理を継続します。|
|右クリック|`{'FINISHED'}`を返し、処理を終えます。|
|スペースキー|オブジェクトの拡大率を初期値に変更します。<br>その後、`{'RUNNING_MODAL'}`を返して処理を継続します。|
|その他|`{'RUNNING_MODAL'}`を返して処理を継続します。|

# 終わりに

Blenderのスクリプトで、マウスやキーボードからのイベントを扱う方法を紹介しました。
キーボードやマウスのイベントを知ることで、 **スクリプトで実現できることが増える** と思いますので、使ってみてはいかがでしょうか？

# 参考情報

* [Operator(bpy_struct)](http://www.blender.org/api/blender_python_api_2_57_release/bpy.types.Operator.html)