Blenderのgpuモジュールの描画を試してみる

Blenderでアドオンを作成する場合
APIが用意されていないUIを描画するためにはgpuモジュールを使って画面描画をすることになります

自分のアドオンに利用するために　最小限の機能を試したのでメモです

## ３Dビューのカーソル位置に四角を描画
![24090701.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151493/0d6ffc30-ce50-5fe0-8176-5b2f94c97c76.gif)

``` py
import bpy
import gpu
from gpu_extras.batch import batch_for_shader


def draw_function(self. context):
    x, y = self.mouse_path[-1]
    data = {"pos": [
        (x-5, y-5), (x+5, y-5),
        (x+5, y+5), (x-5, y+5)]}

    indices = ([0,1],[1,2],[2,3],[3,0])
    
    shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    batch = batch_for_shader(shader, 'LINES', data, indices=indices)
    # シェーダのパラメータ設定
    color = [0.5, 1.0, 1.0, 1.0]
    shader.bind()
    shader.uniform_float("color", color)
    # 描画
    batch.draw(shader)

class DrawOperatorRectOnMouse(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"
    
    @classmethod
    def poll(cls, context):
        return context.active_object is not None 
    
    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type == 'MOUSEMOVE':
            self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))

        elif event.type == 'LEFTMOUSE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

 
    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # 描画関数の登録
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_function, (context,), 'WINDOW', 'POST_PIXEL')

            self.mouse_path = []
            
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

def register():
    bpy.utils.register_class(DrawOperatorRectOnMouse)


def unregister():
    bpy.utils.unregister_class(DrawOperatorRectOnMouse)


if __name__ == "__main__":
    register() 
```

## ドラッグ範囲に四角を描く
![24090702.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151493/f234608d-27f3-376d-1bff-e94530b973e6.gif)
実行後　ドラッグ中に最初のクリック位置からカーソル位置まで四角を描く動作
（黄色い丸はキャプチャソフトの機能でクリック時に表示されるもの）
マウスのプレス状態を保持する属性を追加することでドラッグを判別するようです
コメントアウトしている部分は最初にマウスを放した時に操作が確定する動作になります
```py
import bpy
import gpu
from gpu_extras.batch import batch_for_shader


def draw_function(self, context):
    x_0, y_0 = self.mouse_path[0]
    x, y = self.mouse_path[-1]
    data = {"pos": [(x_0, y_0), (x, y_0),(x, y), (x_0, y)]}

    indices = ([0,1],[1,2],[2,3],[3,0])
    
    shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    batch = batch_for_shader(shader, 'LINES', data, indices=indices)
    # シェーダのパラメータ設定
    color = [0.5, 1.0, 1.0, 1.0]
    shader.bind()
    shader.uniform_float("color", color)
    # 描画
    batch.draw(shader)

class DrawOperatorRectOnMouse(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"
    
    @classmethod
    def poll(cls, context):
        return context.active_object is not None 
    
    def modal(self, context, event):
        context.area.tag_redraw()
        if event.type == 'MOUSEMOVE' and self.left_press:
            self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))

        elif event.type == 'LEFTMOUSE':
            self.left_press = event.value == 'PRESS'
            # if event.value == 'RELEASE':
            #     self.left_press = None
            #     bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            #     return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            self.left_press = None
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}
        return {'RUNNING_MODAL'}

 
    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # 描画関数の登録
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_function, (self, context,), 'WINDOW', 'POST_PIXEL')

            self.mouse_path = []
            self.left_press = False #マウスが押されているかのフラグ
            
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

def register():
    bpy.utils.register_class(DrawOperatorRectOnMouse)


def unregister():
    bpy.utils.unregister_class(DrawOperatorRectOnMouse)


if __name__ == "__main__":
    register() 
```


## 画像エディタ上で　コーナードラッグ可能な四角を描画
画像エディタ（uvエディタも扱いは同じ）で同様の描画ができるので試作
画像を基準に初期座標を設定してドラッグ可能な状態にしています
![24090703.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151493/744a922b-22c1-ce5d-456a-af01472d9c41.gif)



```py
import bpy
import gpu
from gpu_extras.batch import batch_for_shader

def draw_function(self, context):
    x, y = self.mouse_path[-1]
    pos = self.rect_pos
    threshold = 30
    for i,p in enumerate(pos):
        if abs(p[0] -x) < threshold and abs(p[1] -y) < threshold:
                pos[i] = [x, y]   

    data = {"pos": pos }
    indices = ([0,1],[1,2],[2,3],[3,0])
    shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    batch = batch_for_shader(shader, 'LINES', data, indices=indices)
    # シェーダのパラメータ設定
    color = [0.5, 1.0, 1.0, 1.0]
    shader.bind()
    shader.uniform_float("color", color)
    # 描画
    batch.draw(shader)

class DrawOperatorRectOnMouse(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "imageedit.modal_operator"
    bl_label = "Simple Modal IMAGE_EDITOR Operator"
    
    @classmethod
    def poll(cls, context):
        return context.active_object is not None 
    
    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type == 'MOUSEMOVE' and self.left_press:
            self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))

        elif event.type == 'LEFTMOUSE':
            self.left_press = event.value == 'PRESS'

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            self.left_press = None
            bpy.types.SpaceImageEditor.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

 
    def invoke(self, context, event):
        if context.area.type == 'IMAGE_EDITOR':

            # 描画関数の登録
            self._handle = bpy.types.SpaceImageEditor.draw_handler_add(draw_function, (self, context,), 'WINDOW', 'POST_PIXEL')

            self.mouse_path = []
            self.left_press = False #マウスが押されているかのフラグ
            # ２Dビューの座標（UV）をリージョンの座標に変換
            self.rect_pos = [context.region.view2d.view_to_region(x,y) for x,y in [[0.0, 0.0],[1.0, 0.0], [1.0, 1.0],[0.0, 1.0]]]
            
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

def register():
    bpy.utils.register_class(DrawOperatorRectOnMouse)


def unregister():
    bpy.utils.unregister_class(DrawOperatorRectOnMouse)


if __name__ == "__main__":
    register() 
```

自分でもまだ写経状態で使えるものか分からないですが
何かの参考になれば