はじめてのBlenderアドオン開発
Last Update: 2023.3.1
Blender 2.8~3.0
はじめてのBlenderアドオン開発
Blender 2.8~3.0
Last Update: 2023.3.1
3-5. blfモジュールを使ってテキストを描画する
3-4節 では、gpuモジュールを利用して図形を描画する方法を説明しました。 しかし、gpuモジュールには、テキストを描画するためのAPIを用意していません。 このため、テキストを描画するためには、本節で紹介するblfモジュールを利用する必要があります。
blfモジュールを使ってテキストを描画する方法を理解するために、次の機能を備えるアドオンを作成します。
1-5節 を参考にして次に示すソースコードを入力し、ファイル名 sample_3-5.py
として保存してください。
import datetime
import bpy
import blf
bl_info = {
"name": "サンプル 3-5: 日時を表示するアドオン②",
"author": "ぬっち(Nutti)",
"version": (3, 0),
"blender": (2, 80, 0),
"location": "3Dビューポート > Sidebar > サンプル 3-5",
"description": "現在の日時を表示するアドオン",
"warning": "",
"support": "TESTING",
"doc_url": "",
"tracker_url": "",
"category": "3D View"
}
# リージョン情報の取得
def get_region(context, area_type, region_type):
region = None
area = None
# 指定されたエリアの情報を取得する
for a in context.screen.areas:
if a.type == area_type:
area = a
break
else:
return None
# 指定されたリージョンの情報を取得する
for r in area.regions:
if r.type == region_type:
region = r
break
return region
# 日時を表示するオペレータ
class SAMPLE35_OT_ShowDatetime(bpy.types.Operator):
bl_idname = "object.sample35_show_datetime"
bl_label = "日時を表示"
bl_description = "日時を表示します"
# 描画ハンドラ
__handle = None
@classmethod
def is_running(cls):
# 描画中は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):
# リージョンの幅を取得するため、描画先のリージョンを得る
region = get_region(context, 'VIEW_3D', 'WINDOW')
# 描画先のリージョンへテキストを描画
# 注意:本来ならタイマを併用して再描画しないと日時が更新されないが、
# 説明簡略化のためにタイマを利用していない。
# このため表示の不自然さを避けるため、時間以下の情報を表示していない。
if region is not None:
blf.color(0, 1.0, 1.0, 1.0, 1.0)
blf.size(0, 30, 72)
blf.position(0, 100.0, region.height - 120.0, 0)
date_str = datetime.datetime.now().strftime("%Y.%m.%d")
blf.draw(0, date_str)
def invoke(self, context, event):
op_cls = SAMPLE35_OT_ShowDatetime
if context.area.type == 'VIEW_3D':
# [開始] ボタンが押された時の処理
if not op_cls.is_running():
self.__handle_add(context)
print("サンプル 3-5: 日時の表示処理を開始しました。")
# [終了] ボタンが押された時の処理
else:
self.__handle_remove(context)
print("サンプル 3-5: 日時の表示処理を終了しました。")
# エリアを再描画
if context.area:
context.area.tag_redraw()
return {'FINISHED'}
else:
return {'CANCELLED'}
# UI
class SAMPLE35_PT_ShowDatetime(bpy.types.Panel):
bl_label = "日時を表示"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "サンプル 3-5"
bl_context = "objectmode"
def draw(self, context):
op_cls = SAMPLE35_OT_ShowDatetime
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")
classes = [
SAMPLE35_OT_ShowDatetime,
SAMPLE35_PT_ShowDatetime,
]
def register():
for c in classes:
bpy.utils.register_class(c)
print("サンプル 3-5: アドオン『サンプル 3-5』が有効化されました。")
def unregister():
for c in classes:
bpy.utils.unregister_class(c)
print("サンプル 3-5: アドオン『サンプル 3-5』が無効化されました。")
if __name__ == "__main__":
register()
1-5節 を参考にして作成したアドオンを有効化すると、コンソールウィンドウに次の文字列が出力されます。
サンプル 3-5: アドオン『サンプル 3-5』が有効化されました。
[3Dビューポート] スペースのSidebarを表示し、パネル [サンプル 3-5] > [日時を表示] が追加されていることを確認します。
有効化したアドオンの機能を使い、動作を確認します。
1 | [3Dビューポート] スペースのSidebarに配置されたパネル [サンプル 3-5] > [日時を表示] より、[開始] ボタンをクリックします。 |
2 | [3Dビューポート] スペースに、年月日のテキストが表示されます。 |
3 | パネル [サンプル 3-5] > [日時を表示] に配置された [終了] ボタンをクリックすると、テキストが描画されなくなります。 |
1-5節 を参考にして有効化したアドオンを無効化すると、コンソールウィンドウに次の文字列が出力されます。
サンプル 3-5: アドオン『サンプル 3-5』が無効化されました。
本節のサンプルアドオンのソースコードを見ると、描画関数の登録をはじめとして 3-4節 のサンプルアドオンのソースコードと似ています。 図形を描画する処理とテキストを描画する処理が異なることを除けば、gpuモジュールを使った図形描画とほぼ同じ手順で、blfモジュールを使ってテキストを描画できることがわかります。 そこで本節では、blfモジュールによるテキストの描画処理を中心に説明し、重複する部分については説明を省略します。
テキストを描画するために利用するAPIは、blfモジュールをインポートすることによって利用できるようになります。
import blf
3-4節 のサンプルアドオンと同様、テキストを描画するためには、描画関数を登録する必要があります。 描画関数の登録は、クラスメソッド __handle_add
で行っています。 具体的な引数の型については、3-4節 と同じであるため、説明は省略します。
@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'
)
サンプルアドオンでは、描画関数が SAMPLE35_OT_ShowDatetime.__draw
スタティックメソッド、描画するリージョンが WINDOW
であることから、第1引数に RenderText.__render
、第3引数に WINDOW
を指定します。 第2引数には、コンテキストを渡します。
描画関数である SAMPLE35_OT_ShowDatetime.__draw
スタティックメソッドは、描画先のリージョンが更新されるたびに呼ばれます。 SAMPLE35_OT_ShowDatetime.__draw
スタティックメソッドは、描画先のリージョン情報を get_region
関数で取得したあと、blfモジュールのAPIを使ってテキストを描画します。
リージョンの座標値は、リージョンの左下が (x, y) = (0, 0) となり、右方向がX軸正方向、左方向がY軸正方向となります。
本節のサンプルアドオンでは、[ウィンドウ] リージョンの左上の座標にテキストを表示する必要があります。 しかし、リージョンの左上の座標値は環境によって変化するため、単純に数値をそのまま入力して左上にテキストが表示されるように調整しただけでは、リージョンのサイズを変更したときに、テキストを正しい位置に表示できません。 このため、リージョンの左上の座標値を常に取得し、取得した座標値からの差分値を利用します。
さて、[ウィンドウ] リージョンの左上の座標を取得するためには、リージョン情報を取得する必要があります。 本節のサンプルアドオンでは、次の引数を受け取る get_region
関数を呼び出して、リージョン情報を取得します。
引数 | 型 | 意味 |
---|---|---|
context |
bpy.types.Context |
コンテキスト |
area_type |
str |
取得対象のリージョンが属するエリアが持つスペース |
region_type |
str |
取得対象のリージョン |
引数 area_type
や region_type
に指定する値は、それぞれ 2-7節 で説明した、パネルクラスのクラス変数 bl_space_type
と bl_region_type
に指定する値と同じです。
get_region
関数のソースコードを次に示します。
# リージョン情報の取得
def get_region(context, area_type, region_type):
region = None
area = None
# 指定されたエリアの情報を取得する
for a in context.screen.areas:
if a.type == area_type:
area = a
break
else:
return None
# 指定されたリージョンの情報を取得する
for r in area.regions:
if r.type == region_type:
region = r
break
return region
Blender上で開いている全てのエリア情報は、context.screen.areas
に保存されているため、get_region
関数の引数に指定したスペースのタイプ area_type
と、エリア情報のメンバ変数 type
が一致することを確認することで、目的のエリア情報を取得できます。 本節のサンプルアドオンでは、get_region
関数の引数 area_type
に 'VIEW_3D'
が指定されているため、[3Dビューポート] スペースを持つエリア情報を取得できます。
取得したエリア情報のメンバ変数 regions
から、エリアを構成する全てのリージョン情報を取得できます。 エリア情報と同様に、リージョン情報のメンバ変数 type
と get_region
関数の引数 region_type
が一致すれば、目的のリージョン情報を取得できます。 本節のサンプルアドオンでは、get_region
関数の引数 region_type
に 'WINDOW'
が指定されているため、[ウィンドウ] リージョンのリージョン情報を取得できます。
[ウィンドウ] リージョンのリージョン情報は、get_region
関数の戻り値 region
に保存されています。 リージョン情報から、テキストの描画先となる座標値を求めるための情報を取得します。
本節のサンプルアドオンで必要となる情報は、[ウィンドウ] リージョンの左上の座標値です。 リージョンの左下の座標値が (x, y) = (0, 0) であることから、左上の座標は (x, y) = (0, リージョンの高さ) となります。 リージョン情報 region
は、リージョンの高さや幅の情報を持ち、region.height
でリージョンの高さを、region.width
でリージョンの幅を取得できます。 このため、リージョンの左上の座標は (x, y) = (0, region.height) となります。 同様に、右上の座標は (x, y) = (region.width, region.height)、右下の座標は (x, y) = (region.width, 0) となります。
SAMPLE35_OT_ShowDatetime.__draw
関数では、テキストを描画するために、blfモジュールの関数を4つ呼び出しています。
blf.color(0, 1.0, 1.0, 1.0, 1.0)
blf.size(0, 30, 72)
blf.position(0, 100.0, region.height - 120.0, 0)
date_str = datetime.datetime.now().strftime("%Y.%m.%d")
blf.draw(0, date_str)
1つ目の blf.color
関数は、フォントの色を指定する関数で、次に示す引数を指定します。
引数 | 型 | 意味 |
---|---|---|
第1引数 | int |
フォントID(デフォルトのフォントを指定する場合は、0 を指定) |
第2引数 | float |
フォントの色(赤) |
第3引数 | float |
フォントの色(緑) |
第3引数 | float |
フォントの色(青) |
第3引数 | float |
フォントの色(アルファ) |
2つ目の blf.size
関数は、フォントサイズを指定する関数で、次に示す引数を指定します。
引数 | 型 | 意味 |
---|---|---|
第1引数 | int |
フォントID(デフォルトのフォントを指定する場合は、0 を指定) |
第2引数 | int |
フォントサイズ |
第3引数 | int |
DPI |
次に blf.position
関数を使って、テキストを描画する位置を指定します。 blf.position
関数には、次に示す引数を指定します。
引数 | 型 | 意味 |
---|---|---|
第1引数 | int |
フォントID(デフォルトのフォントを指定する場合は、0 を指定) |
第2引数 | float |
描画座標(X座標) |
第3引数 | float |
描画座標(Y座標) |
第4引数 | float |
描画座標(Z座標) |
最後に、次に示す引数を blf.draw
関数に渡して呼び出し、引数に指定されたテキストを描画します。
引数 | 型 | 意味 |
---|---|---|
第1引数 | int |
フォントID(デフォルトのフォントを使う場合は、0 を指定) |
第2引数 | str |
描画するテキストの文字列 |
0
が割り当てられています。 デフォルトのフォント以外のフォントに変えたい場合は、blf.load
関数を使ってフォントを読み込み、フォントIDに blf.load
の戻り値を指定することで、読み込んだフォントを使ってテキストを描画できます。
3-4節 と同様に、不要になった描画関数は登録解除する必要があります。
@classmethod
def __handle_remove(cls, context):
if cls.is_running():
# 描画関数の登録を解除
bpy.types.SpaceView3D.draw_handler_remove(
cls.__handle, 'WINDOW'
)
cls.__handle = None
blfモジュールを用いて、任意のテキストをBlender上に表示する方法を説明しました。 blfモジュールを利用するためには、描画関数の登録など面倒な部分がありますが、Blenderが提供する既存のUIにテキストを表示するよりも、描画方法に自由度があります。
Blenderのアドオンの中には、gpuモジュールとblfモジュールとを組み合わせることで、少し変わったUIを構築しているアドオンがあるため、いろいろなアドオンを使ってソースコードを見ながら、使い方を学んでいくとよいと思います。 例えば、クリックしたマウスのボタンや押したキーボードのキーを表示するアドオン『Screencast Keys』は、gpuモジュールやblfモジュールを使っているため、これらのモジュールの使い方を学ぶための取っ掛かりとして、参考になると思います。
bpy.types.XXX.draw_handler_add
(XXX:描画するスペースにより変わる)メソッドを呼び出して、描画関数を登録する必要があるbpy.types.XXX.draw_handler_remove
メソッドを呼び出して、登録を解除する必要があるcontext.screen.areas
から取得できる