❄️

Blenderで日本語入力を実装するには

2021/07/31に公開

追記(2022/01/13)
進捗のようなものです.
この動画から,さらに進めて入力自体はできるようになりました.
あとは,エリア外にカーソルが移動したときにIMEの無効化,および,カーソルが戻ってきたときのIMEの有効化を実装する必要があります.
https://www.youtube.com/watch?v=cTHzajpJD0Y


Blenderの日本語入力を改善したいと思っています.しかし,まとまった時間がとれないので考え方だけまとめておきます.これを見て取り組んでくれる方がいると嬉しいです.

Blenderの日本語入力(IME)サポートの現状

2021/07/31時点の Widnows/Mac での日本語入力のサポート状況です.

  • UIのテキストボタン source/blender/editors/interface/
  • テキストオブジェクト source/blender/editors/curve/
  • テキストエディタ source/blender/editors/space_text/
  • Pythonコンソール source/blender/editors/space_console/

(テキストオブジェクトは少しずつ実装していくかもしれません)

Linuxでは処理が別なので,全部サポートされています.

UIのテキストボタンで実装されているIME関連の処理と同様の処理を実装することで他の場所でも日本語入力ができるようになります.

準備

Blenderの開発環境を準備します.
基本的に公式Wikiの通りに進めていくとよいかと思います.

Building Blender - Blender Developer Wiki

今後,記事を作るかもしれません.

IME関連の処理は#WITH_INPUT_IMEマクロを探す

ソースコード中の#WITH_INPUT_IMEマクロによって,ビルド時にIMEサポートを切り替えることができます(デフォルトで有効).このマクロを探すことで,IME関連の処理を簡単に見つけることができます.
ただし,ソースコードにIME処理を追加したときは,CMakeLists.txtにWITH_INPUT_IMEマクロに関する記述が必要です.

IME用語

  • コンポジション文字列
    • 変換中の文字列
  • 変換結果の文字列
    • Enterを押して変換を確定した文字列

例えば,と入力したい場合,次のように推移していきます.

(コンポジション文字列) あ → あm → あめ → 飴 → 雨
<Enter>
(変換結果の文字列) 雨

BlenderではwmIMEData構造体にIME関連の情報が格納されています.

typedef struct wmIMEData {
  size_t result_len, composite_len; // 各文字列の長さ(utf-8なのでascii1文字は1,日本語1文字は3)
  char *str_result; // 変換結果の文字列(utf-8)
  char *str_composite; // コンポジション文字列(utf-8)
  int cursor_pos; // IME変換中のカーソル位置(utf-8換算で0が先頭.日本語であれば3の倍数)
  int sel_start; // IME変換中の選択した文字列の範囲(utf-8換算で0が先頭)
  int sel_end; // 雨が|降る|ように (||で囲まれた範囲の場合 sel_start==6, sel_end==12)
  bool is_ime_composing; // IME変換中かどうかを表すフラグ
} wmIMEData;

実装

IMEの有効化と無効化

  1. テキスト入力を開始するときに,wm_window_IME_beginを呼び出し,IMEを有効にします.
  2. テキスト入力を終了するときに,wm_window_IME_endを呼び出し,IMEを無効にします.

テキストボタンでは次の関数でこの処理が行われます.

処理 関数
テキスト入力開始 ui_textedit_begin
IME有効化 ui_textedit_ime_begin
テキスト入力終了 ui_textedit_end
IME無効化 ui_textedit_ime_end

IME関連のイベント処理

テキスト入力中にIME関連のイベントを処理します.

  • WM_IME_COMPOSITE_START
    • 変換開始(選択範囲の削除)
  • WM_IME_COMPOSITE_EVENT
    • 変換中(変換が確定した結果文字列の挿入)
  • WM_IME_COMPOSITE_END
    • 変換終了(特に何もしない)

テキストボタンではui_do_but_textedit関数内でこの処理が行われます.

コンポジション文字列の描画

変換中のコンポジション文字列を描画します.

テキストボタンではwidget_draw_text関数内でこの処理が行われます.

:::message info
コンポジション文字列もテキストデータの中に挿入する場合は,この処理は必要ありません.その代わり,変換を確定して結果文字列を挿入する際に,コンポジション文字列を削除する必要があります.
:::

下線の描画

コンポジション文字列を他と区別するために,文字列に下線を描画します.また,コンポジション文字列の中から変換する範囲を選択できるので,そこだけ他の下線と区別するために,太い下線を描画します.

テキストボタンではwidget_draw_text_ime_underline関数がこの処理を行います.

IMEウィンドウの移動

IMEの変換ウィンドウの位置を指定するために,wm_window_IME_beginを呼び出します.

テキストボタンではui_but_ime_reposition関数がこの処理を行います.

補足

キーボード入力イベント

キーボード入力イベントは次の順に処理されます.

OS → GHOST → BlenderのWindow Manager → Blenderの各種エリア

例: テキストボタン
OS(Windows/Mac) → GHOST → Window Manager → テキストボタン(UI)

テキストボタンの処理と同様の処理を実装することで,他のエリアで日本語を入力することができます.

GHOSTとは?
General Handy Operating System Toolkitの略で,OS固有の処理を抽象化したライブラリです.Blenderは各OSのAPIではなく,GHOSTのAPIを利用します.
参考: Source/File Structure - Blender Developer Wiki
ソースコード: /intern/ghost/

DebugビルドとReleaseビルド

makeでReleaseビルドができて,make debugでDebugビルドができます.

ここからは,個人的な使い分けですが...
Releaseビルドの方が体感早い気がするので,printf書いてReleaseビルドで確認した方が早い時もあります.
バグが起きたり,動作がわからなくなったりしたら,Debugビルドをして,デバッガを使ったりします.

自分はninjaとccacheを使っているのでコマンドはmake ccache ninjamake debug ccache ninjaになります.
比較していないのですが,体感ビルド時間が早いような気がします.


テキストオブジェクトに実装するには

IMEの有効化と無効化

こんな感じでしょうか.
何も入力できませんが,変換ウィンドウだけ表示されると思います.

https://github.com/hzuika/blender/commit/94ce267462f2383bfef3a7c971476e59b03c2c1c

テキストオブジェクトであれば,編集モードに入ったときがテキスト入力の開始で,オブジェクトモードになったときが,テキスト入力の終了になるでしょう.
編集モード関連の処理はobject_edit.cにあるので,テキストオブジェクトを表すOB_FONTで検索すると見つけられると思います.

wm_window_IME_beginwm_window_IME_endの引数にwmWindowが必要で,これはwmWindow *CTX_wm_window(const bContext *C)を使用して取得します.
つまり,Cがあるところでないと実行できません.

UIやTabキーを使って,編集モードに入る処理と出る処理は次の関数で実行されるようですね.
static int editmode_toggle_exec(bContext *C, wmOperator *op)

  • bool ED_object_editmode_enter(bContext *C, int flag)かと思いましたが,この関数はどこにも参照されていないようです.
  • bool ED_object_editmode_exit(bContext *C, int flag)object_batch_delete_hierarchy_fnでしか参照されていないので,削除処理でしか使わないみたいです.

INE関連のイベント処理

こんな感じでしょうか.
変換中は文字列が表示されず,変換ウィンドウの位置もおかしくなっていますが,変換結果の文字列は入力できると思います.

https://github.com/hzuika/blender/commit/f904fd8d5ec1661ce9a9b66af346d45575a511ee

テキストオブジェクトの入力処理はeditfont.cのinsert_text_invoke内で行われます.

insert_text_execはキーボード入力では呼ばれないようです.

しかし,そのままではWM_IME_CONPOSITE_*イベントがinvoke関数まで来ないようです..
wm_eventmatch関数内でIMEイベントも含めるように処理を追加します.

IMEウィンドウの移動

カーソルの下にウィンドウを表示します.
テキストオブジェクトのカーソル(長方形)の4点のローカル座標(x, y)はEditFonttextcurs[0][8]に左下から反時計回りに格納されています.

カーソル移動と入力の可不可の判定

テキストオブジェクトの編集モード中に入力可能なカーソル位置についてまとめます.

3D Viewportからウィンドウの外にカーソルを移動するとき,カーソルアイコンが変化します.
十 → マウスカーソル → 左右矢印(ウィンドウの拡大) → マウスカーソル(ウィンドウの外)
このとき,キーボード入力の可不可は次のようになります.
可能 → 可能 → 不可能 → 不可能

3D Viewportからツールバーにカーソルを移動したときのカーソルアイコンの変化は

  • 右から左に移動した場合
    十 → マウスカーソル → 左右矢印(ツールバーの移動) → マウスカーソル
    このとき,キーボード入力の可不可は
    可能 → 可能(ショートカット優先) → 可能(ショートカット優先) → 可能(ショートカット優先)
  • 下から上に移動した場合
    十 → マウスカーソル
    このとき,キーボード入力の可不可は
    可能 → 可能(ショートカット優先)

ただし,どちらも最初にマウスカーソルが出た時点でTキーを押すと,入力されず,ショートカットでツールバーが非表示になります.

3D Viewport → 非表示ツールバーアイコンへの移動
十 → 左右矢印
可能 → 可能
このときは,Tキーも常に入力され,ショートカットは起動しません.

3D Viewport → ヘッダーへの移動
十 → マウスカーソル
可能 → 不可能

3D Viewport → ギズモへの移動
十 → マウスカーソル(or 専用アイコン)
可能 → 可能

3D Viewport → サイドバーへの移動
十 → マウスカーソル
可能 → 可能(ショートカット優先)

サイドバー → 別エリア
マウスカーソル → 左右矢印(エリアの拡大) → マウスカーソル(別エリア)
可能(ショートカット優先) → 可能(ショートカット優先) → 不可能

3D Viewport → 別エリア
十 → マウスカーソル → 左右矢印(エリアの拡大) → マウスカーソル(別エリア)
可能 → 可能 → 可能 → 不可能

別エリア → 3D Viewport
マウスカーソル(別エリア) → 左右矢印(エリアの拡大) → マウスカーソル(エリア内) → 十
不可能 → 不可能 → 可能 → 可能

3D Viewport → ポップアップダイアログ(名称がわかりません)
十 → マウスカーソル
可能 → 可能(ショートカット優先)

他のウィンドウがアクティブな状態でマウスカーソルを移動させるとアイコンが変わります.
しかし,たとえカーソルが3D Viewport上でもキーボードの入力は不可能です(アクティブなウィンドウに入力される).
ウィンドウを切り替えたり,マウスでクリックしたりすることで,ウィンドウをアクティブにすると,キーボード入力ができます.

GitHubで編集を提案

Discussion