はじめてのBlenderアドオン開発
Last Update: 2023.3.1
Blender 2.8~3.0
はじめてのBlenderアドオン開発
Blender 2.8~3.0
Last Update: 2023.3.1
3-4. gpuモジュールを使って図形描画する
2-7節 では、Blenderが提供するフレームワークの中で、ボタンやメニューなどのUIを構築する方法を説明しました。 しかし、アドオンの機能によっては、独自のUIを構築したほうがよい場合があります。 例えば、押したキーボードのキーやマウスのボタンを表示するアドオン『Screencast Keys』は、独自のUIを構築しているアドオンの1つです。 押したキーボードのキーをメニューなどに表示しても見づらいため、Blenderが提供している描画APIを使って、よりよいUIを構築しています。
本節のサンプルアドオンは、次のような機能を備えています。
1-5節 を参考にして次に示すソースコードを入力し、ファイル名 sample_3-4.py
として保存してください。
import math
import bpy
import gpu
from bpy.props import FloatProperty, FloatVectorProperty
from gpu_extras.batch import batch_for_shader
bl_info = {
"name": "サンプル 3-4: 星型の図形を描画するアドオン",
"author": "ぬっち(Nutti)",
"version": (3, 0),
"blender": (2, 80, 0),
"location": "3Dビューポート > Sidebar > サンプル 3-4",
"description": "星型の図形を描画するアドオン",
"warning": "",
"support": "TESTING",
"doc_url": "",
"tracker_url": "",
"category": "3D View"
}
# 星型の図形を描画するオペレータ
class SAMPLE34_OT_DrawStar(bpy.types.Operator):
bl_idname = "object.sample34_draw_star"
bl_label = "星型の図形を描画"
bl_description = "星型の図形を描画します"
# 描画ハンドラ
__handle = None
@classmethod
def is_running(cls):
# 描画ハンドラがNone以外のときは描画中であるため、Trueを返す
return True if cls.__handle else False
@classmethod
def __handle_add(cls, context):
if not cls.is_running():
# 描画関数の登録
cls.__handle = bpy.types.SpaceView3D.draw_handler_add(
cls.__draw, (context, ), 'WINDOW', 'POST_PIXEL'
)
@classmethod
def __handle_remove(cls, context):
if cls.is_running():
# 描画関数の登録を解除
bpy.types.SpaceView3D.draw_handler_remove(
cls.__handle, 'WINDOW'
)
cls.__handle = None
@classmethod
def __draw(cls, context):
sc = context.scene
# ビルトインのシェーダを取得
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
# 頂点データを作成
center = sc.sample34_center
radius = sc.sample34_size / 2.0
angle = 72 * math.pi / 180
data = {"pos": [
[center[0], center[1] + radius],
[center[0] + radius * math.sin(angle), center[1] + radius * math.cos(angle)],
[center[0] + radius * math.sin(2 * angle), center[1] + radius * math.cos(2 * angle)],
[center[0] - radius * math.sin(2 * angle), center[1] + radius * math.cos(2 * angle)],
[center[0] - radius * math.sin(angle), center[1] + radius * math.cos(angle)]
]}
# インデックスデータを作成
indices = [
[0, 2], [2, 4], [4, 1], [1, 3], [3, 0]
]
# バッチを作成
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)
def invoke(self, context, event):
op_cls = SAMPLE34_OT_DrawStar
if context.area.type == 'VIEW_3D':
# [開始] ボタンが押された時の処理
if not op_cls.is_running():
self.__handle_add(context)
print("サンプル 3-4: 星型の図形の描画処理を開始しました。")
# [終了] ボタンが押された時の処理
else:
self.__handle_remove(context)
print("サンプル 3-4: 星型の図形の描画処理を終了しました。")
# エリアを再描画
if context.area:
context.area.tag_redraw()
return {'FINISHED'}
else:
return {'CANCELLED'}
# UI
class SAMPLE34_PT_DrawStar(bpy.types.Panel):
bl_label = "星型の図形を表示"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "サンプル 3-4"
bl_context = "objectmode"
def draw(self, context):
sc = context.scene
op_cls = SAMPLE34_OT_DrawStar
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")
layout.separator()
layout.prop(sc, "sample34_center")
layout.prop(sc, "sample34_size")
def init_props():
sc = bpy.types.Scene
sc.sample34_center = FloatVectorProperty(
name="中心",
description="星の中心座標",
size=2,
min=0.0,
default=(200.0, 200.0)
)
sc.sample34_size = FloatProperty(
name="サイズ",
description="星の大きさ",
min=10.0,
default=200.0
)
def clear_props():
sc = bpy.types.Scene
del sc.sample34_center
del sc.sample34_size
classes = [
SAMPLE34_OT_DrawStar,
SAMPLE34_PT_DrawStar,
]
def register():
for c in classes:
bpy.utils.register_class(c)
init_props()
print("サンプル 3-4: アドオン『サンプル 3-4』が有効化されました。")
def unregister():
clear_props()
for c in classes:
bpy.utils.unregister_class(c)
print("サンプル 3-4: アドオン『サンプル 3-4』が無効化されました。")
if __name__ == "__main__":
register()
1-5節 を参考にして作成したアドオンを有効化すると、コンソールウィンドウに次に示す文字列が出力されます。
サンプル 3-4: アドオン『サンプル 3-4』が有効化されました。
Sidebarを表示し、パネル [サンプル 3-4] > [星型の図形を表示] が追加されていることを確認します。
有効化したアドオンの機能を使い、動作を確認します。
1 | [3Dビューポート] スペースのSidebarから、パネル [サンプル 3-4] > [星型の図形を表示] に配置されている [開始] ボタンをクリックします。 |
2 | [3Dビューポート] スペース上に三角形が表示されます。また、パネルには表示する図形の中心座標とサイズを変更するためのUIが表示されます。 |
3 | 図形の中心座標やサイズを変更すると、[3Dビューポート] スペース上に表示されている三角形も変形されます。 |
4 | パネル [サンプル 3-4] > [星型の図形を表示] に配置されている [終了] ボタンをクリックすると、図形が描画されなくなります。 |
1-5節 を参考にして有効化したアドオンを無効化すると、コンソールウィンドウに次の文字列が出力されます。
サンプル 3-4: アドオン『サンプル 3-4』が無効化されました。
本節では、Blenderが提供する図形描画のためのAPIについて説明します。
本節のサンプルアドオンは、図形を描画するために、gpuモジュールで提供されるAPIを利用しています。 このため、gpuモジュールをインポートする必要があります。
import gpu
本節のサンプルアドオンでは、複数のクラス間(SAMPLE34_OT_DrawStar
と SAMPLE34_PT_DrawStar
)で、プロパティクラスの変数を共有しています。 共有するプロパティクラスの変数を、次に示します。
変数 | 意味 |
---|---|
sample34_center |
星の中心座標 |
sample34_size |
星の大きさ |
プロパティクラスの変数は、それぞれ bpy.types.Scene
のメンバ変数として定義します。 以降、各変数には bpy.types.Scene
を通してアクセスできます。 例えば、プロパティ sample34_center
にアクセスする場合は、bpy.types.Scene.sample34_center
とします。 プロパティクラスの変数は、register
関数で呼ばれる init_props
関数で作成し、unregister
関数で呼ばれる clear_props
関数で削除しています。
def init_props():
sc = bpy.types.Scene
sc.sample34_center = FloatVectorProperty(
name="中心",
description="星の中心座標",
size=2,
min=0.0,
default=(200.0, 200.0)
)
sc.sample34_size = FloatProperty(
name="サイズ",
description="星の大きさ",
min=10.0,
default=200.0
)
def clear_props():
sc = bpy.types.Scene
del sc.sample34_center
del sc.sample34_size
classes = [
SAMPLE34_OT_DrawStar,
SAMPLE34_PT_DrawStar,
]
単純にgpuモジュールのAPIを呼び出しただけでは、図形を表示することはできません。 図形を描画するためには、描画関数 を登録し、描画関数内でgpuモジュールのAPIを呼び出す必要があります。
サンプルアドオンでは、[3Dビューポート] スペースに対して描画関数を登録する処理をクラスメソッド __handle_add
に定義し、invoke
メソッドの [開始] ボタンが押されたときに呼び出します。
@classmethod
def __handle_add(cls, context):
if not cls.is_running():
# 描画関数の登録
cls.__handle = bpy.types.SpaceView3D.draw_handler_add(
cls.__draw, (context, ), 'WINDOW', 'POST_PIXEL'
)
描画関数の登録は、スペース単位で行います。 例えば、[3Dビューポート] スペースに描画関数を登録したい場合は、bpy.types.SpaceView3D.draw_handler_add
メソッドを使って登録します。 ここで bpy.types.SpaceView3D
は、[3Dビューポート] スペースのスペース情報ですが、描画先のスペースによってこの部分の記述が変わります。 描画先のスペースの候補一覧を次に示します。 なお、同じスペースを持つエリアがウィンドウ内に複数存在した場合は、該当するエリアすべてに描画されます。
クラス | スペース |
---|---|
SpaceView3D |
[3Dビューポート] スペース |
SpaceImageEditor |
[画像エディター] スペース、[UVエディター] スペース |
SpaceNodeEditor |
[シェーダーエディター] スペース、[コンポジター] スペース、[テクスチャノードエディター] スペース |
SpaceSequenceEditor |
[ビデオシーケンサー] スペース |
SpaceClipEditor |
[動画クリップエディター] スペース |
SpaceDopeSheetEditor |
[ドープシート] スペース、[タイムライン] スペース |
SpaceGraphEditor |
[グラフエディター] スペース、[ドライバー] スペース |
SpaceNLA |
[ノンリニアアニメーション] スペース |
SpaceTextEditor |
[テキストエディター] スペース |
SpaceConsole |
[Pythonコンソール] スペース |
SpaceInfo |
[情報] スペース |
SpaceOutliner |
[アウトライナー] スペース |
SpaceProperties |
[プロパティ] スペース |
SpaceFileBrowser |
[ファイルブラウザー] スペース |
SpacePreferences |
[プリファレンス] スペース |
bpy.types.SpaceView3D.draw_handler_add
メソッドの引数には、次に示す引数を指定します。
引数 | 型 | 意味 |
---|---|---|
第1引数 | 描画関数(描画関数は、関数、クラスメソッド、またはスタティックメソッドのいずれか) | |
第2引数 | tuple |
描画関数で受け取る引数(タプル型) |
第3引数 | str |
描画先のリージョン |
第4引数 | str |
描画モード(深度バッファの扱いを指定。基本は POST_PIXEL を指定する) |
本節のサンプルアドオンでは、第1引数に SAMPLE34_OT_DrawStar.__draw
、第3引数に WINDOW
を指定します。 第2引数にはコンテキスト情報を渡し、描画関数内でこれらの値を利用します。
(context, )
を渡しているところに、違和感を感じるかもしれません。 単純に考えると、ここには context
のみを渡せばよさそうです。 しかし、実際に試した人はわかると思いますが、仮にここで第2引数に context
を指定すると、「Contextではなくタプル型の値を指定する必要があります」というエラーメッセージが表示されて、エラー終了してしまいます。 このため、サンプルアドオンでは、タプル型であることを明示するために (context, )
を指定しています。 なお、要素が1つの場合にタプル型であることを認識させるために、要素のあとにカンマ(,)を追加していることに注意してください。 (context)
のようにカンマがないと、context
と同じであると判断されてしまいます。
bpy.types.SpaceView3D.draw_handler_add
メソッドは、戻り値としてハンドルを返します。 ハンドルは、クラス変数 SAMPLE34_OT_DrawStar.__handle
に保存し、描画関数の登録解除時に利用します。
context.area.tag_redraw
メソッドを実行しています。
描画関数は、クラスメソッド SAMPLE34_OT_DrawStar.__draw
として定義します。 クラスメソッド SAMPLE34_OT_DrawStar.__draw
は、次の手順で星型の図形を描画します。
なお、サンプルアドオンでは、gpu_extrasモジュールを使って描画処理を簡略化しています。 次のようにして、gpu_extrasモジュールの batch.batch_for_shader
関数をインポートしていることに注意してください。
from gpu_extras.batch import batch_for_shader
シェーダを自作することも可能ですが、サンプルアドオンではビルトインのシェーダ '2D_UNIFORM_COLOR'
を利用しています。 シェーダ '2D_UNIFORM_COLOR'
は、1色で図形を塗りつぶすときに利用します。 ビルトインのシェーダは、gpu.shader.from_builtin
関数を呼び出して取得します。
# ビルトインのシェーダを取得
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
バッチを作るために必要な、頂点データやインデックスデータを作成します。 星型の図形になるように、頂点データやインデックスデータを作成していますが、星型図形の頂点の求め方に関しては、ここでは詳細を割愛します。
# 頂点データを作成
center = sc.sample34_center
radius = sc.sample34_size / 2.0
angle = 72 * math.pi / 180
data = {"pos": [
[center[0], center[1] + radius],
[center[0] + radius * math.sin(angle), center[1] + radius * math.cos(angle)],
[center[0] + radius * math.sin(2 * angle), center[1] + radius * math.cos(2 * angle)],
[center[0] - radius * math.sin(2 * angle), center[1] + radius * math.cos(2 * angle)],
[center[0] - radius * math.sin(angle), center[1] + radius * math.cos(angle)]
]}
# インデックスデータを作成
indices = [
[0, 2], [2, 4], [4, 1], [1, 3], [3, 0]
]
なお、クラスメソッド SAMPLE34_OT_DrawStar.__draw
に渡されてくる引数 context
はコンテキスト情報であり、context.scene
は bpy.types.Scene
と同じものであることに注意してください。 このため、bpy.types.Scene.sample34_center
として定義したプロパティクラスの変数は、context.scene.sample34_center
としてアクセスできます。
作成した、頂点データやインデックスデータをもとに、バッチを作成します。 バッチは、gpu_extraモジュールの batch_for_shader
関数を呼び出して作成します。
# バッチを作成
batch = batch_for_shader(shader, 'LINES', data, indices=indices)
batch_for_shader
関数は、次に示す引数を受け取ります。
引数 | 型 | 意味 |
---|---|---|
第1引数 | gpu.types.Shader |
シェーダ |
第2引数 | str |
描画図形の型 |
第3引数 | dict |
頂点データ |
第4引数 | list |
インデックスデータ |
サンプルアドオンでは、第2引数に 'LINES'
を渡して線分を表示しています。
シェーダにパラメータを渡します。 渡せるパラメータはシェーダによって異なりますが、サンプルアドオンで使用するシェーダには、図形を塗りつぶす色 "color"
をパラメータとして渡すことができます。 シェーダにパラメータを渡すために、shader.uniform_float
メソッドを呼び出しています。
# シェーダのパラメータ設定
color = [0.5, 1.0, 1.0, 1.0]
shader.bind()
shader.uniform_float("color", color)
サンプルアドオンでは (赤, 緑, 青, アルファ値) = (0.5, 1.0, 1.0, 1.0)
を渡すことで、描画色を水色に設定しています。
最後に、batch.draw
メソッドを呼び出して、図形を描画します。 引数にシェーダを渡していることに注意してください。
bpy.types.SpaceView3D.draw_handler_add
メソッドを使って登録した描画関数は、登録解除するまで呼ばれ続けます。 このため、不要になったとき(本節のサンプルアドオンでは、[終了] ボタンが押されたとき)に登録解除する必要があります。 描画関数を登録解除する処理を次に示します。
@classmethod
def __handle_remove(cls, context):
if cls.is_running():
# 描画関数の登録を解除
bpy.types.SpaceView3D.draw_handler_remove(
cls.__handle, 'WINDOW'
)
cls.__handle = None
描画関数の登録解除は、bpy.types.SpaceView3D.draw_handler_remove
メソッドを呼び出して行います。 描画関数を登録したときに使用した bpy.types.SpaceView3D.draw_handler_add
メソッドと同様、SpaceView3D
は、描画関数を登録解除する対象のスペースによって変更する必要があります。 bpy.types.SpaceView3D.draw_handler_remove
メソッドに指定する引数を次に示します。
引数 | 型 | 意味 |
---|---|---|
第1引数 | ハンドル(draw_handler_add メソッドの戻り値) |
|
第2引数 | str |
描画関数を登録したリージョン |
本節のサンプルアドオンでは、クラス変数 SAMPLE34_OT_DrawStar.__handle
にハンドルが保存されているため、クラス変数 SAMPLE34_OT_DrawStar.__handle
を第1引数に指定します。 第2引数は、描画関数の登録を解除するリージョンを指定しますが、本節のサンプルアドオンでは、 bpy.types.SpaceView3D.draw_handler_add
メソッドの第3引数に指定したリージョン WINDOW
を指定します。
これで描画関数の登録が解除されました。 登録解除後は、クラス変数 SAMPLE34_OT_DrawStar.__handle
に None
を代入してハンドルが無効であることを明示します。
gpuモジュールを使って、[3Dビューポート] スペースで図形を描画するための方法を説明しました。
本節で紹介したgpuモジュール、3-1節 と 3-2節 で説明したユーザからのイベントを扱う処理を組み合わせることで、Blenderの枠組みで実現可能なUIとは全く異なる、独自のUIを構築できます。
bpy.types.XXX.draw_handler_add
(XXX:描画対象のスペースにより変わる)メソッドを用いて、描画関数を登録する必要があるbpy.types.XXX.draw_handler_remove
メソッドを用いて、登録を解除する必要があるgpu.shader.from_builtin
関数を利用することで、Blenderのビルトインシェーダを取得できる