Blender関連の雑多なメモ類
アドオン作成の手引っぽいもの:とりあえず
- 基本の基本:[Blender] Blenderプラグインの作り方
- 基本:「はじめてのBlenderアドオン開発 (Blender 2.8版)」
- TomoG氏の: Blenderでアドオンの作り方
- 忘却野氏の: アドオンの作り方・構造の解説
- dskjal氏の: Blender 2.80 で UI のスクリプトを書く
ぬっち氏の「Blenderプラグインの作り方」の出来が良すぎて、これの2.8対応版があればそれで十分な感じはある
◇ メニューの追加登録候補について(本体はまとめに移動)
- ユーザー設定 (
USERPREF
) はオペレーター関連 (_OT_
) だけPREFERENCE
に分離されている - メニューの
append・prepend・remove
は、scripts/modules/bpy_types.py
で定義されているので、(たぶん)変更可能- → 同名のメニューを自作メニューで置き換えるとかできるかも?
-
append
はこんな感じ@classmethod def append(cls, draw_func): """ Append a draw function to this menu, takes the same arguments as the menus draw function """ draw_funcs = cls._dyn_ui_initialize() cls._dyn_owner_apply(draw_func) draw_funcs.append(draw_func)
- ちなみに、
context.copy()
もこのファイルで定義されていた
オペレーターやクラスのプロパティの情報を取得したい
bpy.types.SubsurfModifier
のboundary_smooth
のPRESERVE_CORNERS
の説明文や、 bpy.ops.object.data_transfer
のvert_mapping
のEDGEINTERP_NEAREST
の説明文を流用したいが、いちいち API Document をコピペするのはちょっと... というとき
◇ types.Hoge
がある場合
struct = bpy.types.SubsurfModifier.bl_rna #.bl_rna で bpy_struct を取得
prop = struct.properties['boundary_smooth'] # properties["名前"] でプロパティを取得
text = prop.enum_items['PRESERVE_CORNERS'].description #Enum の場合はこんな感じ
◇ types.Hoge
がない場合(オペレーター)
struct = bpy.ops.object.data_transfer.get_rna_type() #.get_rna_type() で bpy_struct を取得
prop = struct.properties['vert_mapping'] # あとは types.Hoge と同じ
text = prop.enum_items['EDGEINTERP_NEAREST'].description
参考:How can I access view3d.select_circle radius value?
同様にしてstruct.functions
でメソッドが取れた はず
コピペでは駄目な理由:「同じテキスト(のはず)なのに、なぜか違う訳になる」という現象
例(2.91で発生、2.92は未確認)
class AddonPreferences(bpy.types.AddonPreferences):
bl_idname = __name__
def draw(self, context):
txt_solid=bpy.types.UILayout.enum_item_name(context.active_object, 'display_type', 'SOLID')
self.layout.label(text=txt_solid) #これは「ソリッド」と翻訳される
self.layout.label(text="Solid") #これも「ソリッド」と翻訳される
self.layout.operator("何かのオペレーター", text=txt_solid) #これは「ソリッド」と翻訳される
self.layout.operator("何かのオペレーター", text="Solid") #これは「立体」と翻訳される
翻訳 context のパラメータ設定が違うのかな?
◇ Blender のスクリーンショット作成機能とか
- python のテストではアドオンの機能テストはできるけどパネル表示のテストはできないよね問題の解決に使えないか?
- と思ったけどやっぱ無理? invoke_default → スクショ → キャンセルってできるんだろうか
- UI というか各エディターの基本状態の画像一覧をこれで自動的に作りたい
- あと mouse_gesture でやってるやつ
- これはスクリーンショット機能使ってなかったかも
Blender のスクリーンショット作成機能では、UIは映らないっぽい 勘違いだった
- エディターの基本状態の画像 → できる
- invoke_default してスクリーンショット → ボタン経由では可能、draw での自動実行は不可能
Calling operator "bpy.ops.screen.screenshot" error, can't modify blend data in this state (drawing/rendering)
これに限らず、「invoke でポップアップ出す → 処理を自動で実行する」のはどうすればいいんだろ
- app.tymer ?
◇ property の add() というか公式の PropertyGroup Example のやつ
-
bpy.types.PropertyGroup
を継承したプロパティグループのクラスMySet
を定義
-
使い方その1:
どこか.prop = bpy.props.PointerProperty(type=MySet)
とする- プロパティグループが1つ設定された状態
- →
どこか.prop.name
のようにMySet
の各attrの name を使って個別プロパティを取得する - これが公式の Exapmle の上のやつ
-
使い方その2:
どこか.prop = bpy.props.CollectionProperty(type=MySet)
とする- コレクションプロパティが1つ設定された状態 = 設定時点では 枠 だけできて、中身のグループはない
-
len(どこか.prop)=0
であり、真偽判定ではFalse
として扱われる - ただしリストというわけではなく、
どこか.prop == []
→False
-
-
どっか.hoge.add()
を行うとコレクションの中にMySet
のインスタンス?が追加される-
add()
は引数を取らない → コレクションの中身はアクセスのための固有の識別子?を持てない -
\Rightarrow どっか.hoge
の各要素はどっか.hoge[0]
のようにインデックスで取得する - あるいは
x for x in どっか.hoge
のようにfor
で回してあれこれする
-
-
add()
は追加したインスタンスを返すので、item = どっか.hoge.add()
としておけばitem.name
のように個別プロパティを取得できる - これが公式の Example の下のやつ
- コレクションプロパティが1つ設定された状態 = 設定時点では 枠 だけできて、中身のグループはない
要するに:どっか.hoge.add()
・・・初期設定の何かのプロパティでコレクションを埋めている
→ そのあとにx for x in どっか.hoge
があり、そこで実際のプロパティの値を設定しているはず
CollectionProperty.add()
に関する doc の記述が見つからないんだけど、存在しないんだっけ...?
class OBJECT_PT_context_object(ObjectButtonsPanel, Panel):
bl_label = ""
bl_options = {'HIDE_HEADER'}
def draw(self, context):
layout = self.layout
space = context.space_data
if space.use_pin_id:
layout.template_ID(space, "pin_id")
else:
row = layout.row()
row.template_ID(context.view_layer.objects, "active", filter='AVAILABLE')
- 通常時は
bpy.context.view_layer.objects.active
が判定に使用される (AVAILABLE
?)- ちゃんと Global Conetxt の要素(
view_layer
)を使用している - = (定義?上は) Local な
active_object
の有無には依存していない
- ちゃんと Global Conetxt の要素(
- ピン留めされたときは
bpy.context.space_data.pin_id
にあるid
が判定に使用される- パネル表示処理?上では
context.object
は重要でない - ピン留めは
pin_id
に『何』を投げているのか?- そもそも投げていない =
bpy.context.view_layer.objects.active
をpin_id
にセット の可能性が高そう
- そもそも投げていない =
- パネル表示処理?上では
◇ bpy.types.UILayout.context_pointer_set
以下のようにして使う (ここの真似)
class MyMenu(bpy.types.Menu):
bl_label = "My Menu"
bl_idname = "どこか.my_menu"
...
def draw(self, context):
row = self.layout.row()
row.context_pointer_set("my_camera", camera) #row() や column() に使う
row.menu(何か別のメニュー.bl_idname)
row.operator(何かのオペレーター.bl_idname)
C のコード
void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *ptr)
{
uiBlock *block = layout->root->block;
layout->context = CTX_store_add(&block->contexts, name, ptr);
}
- 上記の場合、
row
にセットされたmenu
やoperator
において、context.my_camera
が使用可能になる - あくまでも
row
の中に入らないと有効にならないようで、上記のMyMenu
内でrow
をいじってもcontext.my_camera
は使えない - 基本は
menu
で使うもののようで、operator
内でcontext.my_camera
を呼ぶとよくわからない挙動- 直接
execute
が動くもの:context.my_camera
が使える -
draw
するもの:最初のdraw
実行時は使えるが、再度draw
するとcontext.my_camera
がなくなっていてAttributeError
になる -
draw
してOKボタン等でexecute
するもの:execute
するときにはcontext.my_camera
がなくなっていてAttributeError
になる -
invoke
してexcute
するもの:invoke
時にはcontext.my_camera
は存在するが、excute
時にはなくなっている
- 直接
- そもそもオペレーターは普通に
oprator().camera = camera
すれば良い
で、書いてて思ったけど、これbpy.types.Menu
がプロパティを持てない問題への対処法なのでは?
- 上のリンクも "How to create submenus with dynamic content?" への解答として
context_pointer_set
が提示されているわけだし -
オペレーターのように\Rightarrow .menu(サブメニュー).content = target
ができないので、代替としてcontext_pointer_set("content", target)
を行う と考えると綺麗
ぬっち氏の「BLENDER INTERNAL」の記述 (p.68) によると、モーダルモード移行時に context の再取得(というか再設定?)が行われている。
-
invoke()
や最初のdraw()
では実行時に取得した context が利用され、その後はモード移行時に取得した context が利用される挙動と思われる。- モード移行時の context には UIlayout は含まれていないため、そこで付加された custom context も失われてしまう...のか? ほんとに?
別ウィンドウでテキストエディタを出し、出力を貼り付けるオペレーター
def execute(self, context):
if len(context.window_manager.windows) == 1:
bpy.ops.screen.userpref_show()
text_area = context.window_manager.windows[-1].screen.areas[-1]
text_area.type = 'TEXT_EDITOR'
if "temp_text" in bpy.data.texts:
text_area.spaces[0].text = bpy.data.texts["temp_text"]
else:
text_area.spaces[0].text = bpy.data.texts.new(name="temp_text")
pre_clipboard = context.window_manager.clipboard
override = context.copy()
context.window_manager.clipboard = "\n".join(dir(bpy.types)) # textで見たい情報
override['area'] = text_area
bpy.ops.text.select_all(override)
bpy.ops.text.paste(override)
bpy.ops.text.jump(override, line=1)
return {'FINISHED'}
- 別にクリップボードを介する必要はなくて、
text_area.spaces[0].text.write("\n".join(dir(bpy.types)))
すればいいだけなんだけどね
bl_rna_get_subclass_py
:クラス名からクラスオブジェクトを取得する 参考
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
areas = context.window_manager.windows[0].screen.areas
for a in areas:
if a.type == "PROPERTIES":
sp_data = a.spaces[0]
def myEmpty(data):
myEmpty.space_data = sp_data
return None
myEmpty(sp_data) # ↓ "_PT_"なら Panel のように、引数と合わせる
clas = bpy.types.Panel.bl_rna_get_subclass_py('PROPERTIES_PT_navigation_bar')
clas.draw(self, myEmpty) # draw 内で attr として xx.space_data が呼ばれる 辞書では駄目
もうちょっとちゃんとした手順というかbl_rna_get_subclass_py
を使う流れ
# my_addon.py の中でオペレータを定義
# class MyOprator(bpy.types.Operator):
# bl_idname = "wm.my_operator"
op_struct = bpy.ops.wm.my_operator.get_rna_type() # get_rna_type() で bpy_struct を取得
# op_struct: <bpy_struct, Struct("WM_OT_my_operator") as 0x.... >
# ↓
op_cls_in_types = op_struct.__class__ # __clas__ で Blender 内部でのクラス?を取得
# op_cls_in_types: <class 'bpy.types.WM_OT_my_operator' >
# ↓
op_cls_name = op_cls_in_types.__name__ # __name__ で クラス名を str として取得
# op_cls_name: WM_OT_my_operator
# ↓
op_class = bpy.types.Operator.bl_rna_get_subclass_py(op_cls_name)
# op_class: <class 'my_addon.MyOperator' >
op_class まで到達すると、AttributeError
を出さずに、op_class.xx
のようにクラス変数にアクセスしたり、op_class.draw()
のようにメソッドを呼んだりできる
ユーザーが追加したオペレータでは上記の手順でクラスオブジェクトの取得に成功できる
しかし、デフォルトのオペレータでは name は取れても bl_rna_get_subclass_py(name)
は None
になってしまう
めも
- Topbar の
space_data
は それ用のサブクラスがなく Space クラスがそのまま使われている -
PoseBone.bone == 正しいBone
→True
BUTPoseBone.bone is 正しいBone
→False
- 座標に関連する複数のプロパティ
-
matrix
: Final 4x4 matrix after constraints and drivers are applied (object space) -
matrix_basis
: Alternative access to location/scale/rotation relative to the parent and own rest bone -
matrix_channel
: 4x4 matrix, before constraints - とりあえず、ポーズボーンが変化していない(レスト位置のまま)のときは
- PoseBone の
matrix
≒ EditBone のmatrix
- PoseBone の
matrix_basis
→ 単位行列 - PoseBone の
matrix_channel
→ ?
- PoseBone の
-
◇ context.object
と context.active_object
の違いの話
公式:context.object
はボタン/パネル用だよ (超意訳)
↓
プロパティエディタ(Button Cont.)が context.object
を持つのは理解できる
では、なぜプロパティエディタの外、つまり Screen Cont. にも context.object
があるのか?
= 3Dビューでのcontext.object
と context.active_object
の、想定されている使い分けとは?
↓
3Dビューにもパネルはある・・・Screen Cont. のcontext.object
は、サイドバーなどで利用するためでは?
↓
予想:__PT__
系は .object
(もしかしたら__MT__
系も) 、__OT__
系は.active_object
を使うのが公式の作法
space_view3d.py の各クラスでの `context.object` /` active_object` の使い分け
クラス名 | context.object | context.active_object |
---|---|---|
VIEW3D_HT_tool_header | def draw_mode_settings | |
_draw_tool_settings_context_mode: | def PAINT_GPENCIL | |
VIEW3D_HT_header | def draw | |
VIEW3D_MT_editor_menus | def draw | |
VIEW3D_MT_transform_armature | def draw | |
TOPBAR_MT_edit_curve_add | def draw | |
VIEW3D_MT_object | def draw | |
VIEW3D_MT_object_context_menu | def draw | |
VIEW3D_MT_hook | def draw | |
VIEW3D_MT_vertex_group | def draw | |
VIEW3D_MT_gpencil_vertex_group | def draw | |
VIEW3D_MT_pose_group | def draw | |
VIEW3D_MT_edit_mesh_context_menu | def draw | |
VIEW3D_MT_edit_mesh_extrude | def draw | |
VIEW3D_MT_edit_font_kerning | def draw | |
VIEW3D_MT_assign_material | def draw | |
VIEW3D_MT_gpencil_copy_layer | def draw | |
VIEW3D_MT_gpencil_animation | def poll | |
VIEW3D_MT_pivot_pie | def draw | |
VIEW3D_PT_snapping | def draw | |
VIEW3D_PT_overlay_gpencil_options | def draw | |
VIEW3D_PT_context_properties | def _active_context_member | |
VIEW3D_PT_gpencil_draw_context_menu | def draw | |
TOPBAR_PT_gpencil_materials | def poll | |
TOPBAR_PT_gpencil_vertexcolor | def poll | |
せめて公式は統一してくれよ・・・ |
どっちかだけで良くない?
static eContextResult screen_ctx_active_object(const bContext *C, bContextDataResult *result)
{
wmWindow *win = CTX_wm_window(C);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
Object *obact = view_layer->basact ? view_layer->basact->object : NULL;
if (obact) {
CTX_data_id_pointer_set(result, &obact->id);
}
return CTX_RESULT_OK;
}
static eContextResult screen_ctx_object(const bContext *C, bContextDataResult *result)
{
wmWindow *win = CTX_wm_window(C);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
Object *obact = view_layer->basact ? view_layer->basact->object : NULL;
if (obact) {
CTX_data_id_pointer_set(result, &obact->id);
}
return CTX_RESULT_OK;
}
poll 関連
if (WM_operator_poll_context((bContext *)C, ot, context) == false) {
const char *msg = CTX_wm_operator_poll_msg_get(C);
PyErr_Format(PyExc_RuntimeError,
"Operator bpy.ops.%.200s.poll() %.200s",
opname,
msg ? msg : "failed, context is incorrect");
CTX_wm_operator_poll_msg_set(C, NULL); /* better set to NULL else it could be used again */
error_val = -1;
}
/* sets up the new context and calls 'wm_operator_invoke()' with poll_only */
bool WM_operator_poll_context(bContext *C, wmOperatorType *ot, short context)
{
return wm_operator_call_internal(C, ot, NULL, NULL, context, true, NULL);
}
API Doc. の UILayout のページに、icon 用のスクロールが導入された
良かった・・・公式も問題だと認識していたんだね・・・
こんな感じ(画像) ↓
ページ全体のスクロールバーの大きさを比べてみた感じ、2.91版の3分の2ぐらいになった?
ドライバーを使ったコンストレイントのオーナー空間の制御
というか Enum Property の制御
Enum Property はインデックスを使うことで eunm_items の要素を取得できる
なので、ポーズボーン.constraints["Limit Rotation"].owner_space
にドライバーを設定し、
0~3 の整数値を取るようにすれば、オーナー空間の設定を変更するスイッチっぽいものが作れる
ただし、ボーン以外では eunm_items が"World" と "Local" に減る関係なのか、
- Blender 内の表示:[ワールド、ポーズ、親付きローカル、ローカル]
- 実際の並び:[World, Local, Pose, Local_with_parent][1]
のように見た目と中身が違う
とりあえず、画像のようにインデックスの配列を作って対処することにした
-
'CUSTOM'がどう処理されているのかは不明 ↩︎
directly terminate a modal operator through the API 問題
あるいは、 invoke したポップアップを script 経由で閉じることはできるか 問題
いまのところの結論: ○「閉じるだけ」 ×「閉じて再度開く」
わかってること:
-
ctypes.windll.user32.keybd_event(0x1B)
Esc キーイベントを起こす
→ 一応閉じれたはず
オペレーター自体が止まってしまうので、もう一度自動的に invoke することができない\Leftrightarrow - invoke の中では、オペレーターの invoke はスキップされる
→ コンテクストメニューでは ポップアップがでない問題
※ なぜか menu を経由するとポップアップが出るので、invoke → menu → invoke は行けるかも? -
bpy.types.Window.event_simulate
?
ここのコメントによると、↓で、debug 用の機能らしく、add-on には向いていないとのことYou can start Blender with the CLI argument
--enable-event-simulate
which allows you to simulate events throughbpy.context.window.event_simulate("ESC", "PRESS")
-
invoke_xx
系を return 以外で呼ぶと、ポップアップ自体は出る
→ modal モードに入ってタイマーを使って呼ぶと、draw 内容が変更されたポップアップが出せる
すでに出ているポップアップが閉じるわけではない\Leftrightarrow -
invoke_xx
系を return 以外で呼ぶと、閉じる処理がうまく行かないっぽい
→ ポップアップを出したあとになにかすると、 ACCESS_VIOLATION でクラッシュする
結局、↓ なんだよね・・・
-
invoke でモーダルモード:
何かしらの処理の自動実行ができるが、ポップアップをうまく扱えない -
invoke でポップアップ:
ポップアップを扱えるが、モーダルモードではないらしく処理の自動実行が不可
-
モーダル入る → ポップアップ出す → スクリーンショット取って保存 → esc イベントで停止
↑CANCELLED になっても外部への保存は巻き戻されない気がするし、いける?
esc まではできそうだが、そのあと何かすると Blender が落ちるので、だめ -
プロパティに update を設定して prop で表示させると、常に update が呼ばれ続ける
今確かめたら、再発しない・・・なぜ?
→ update の中でスクショ、いける?
__init__()
アドオンで登録した class の - AddonPreferences では、有効化/無効化 の切り替えでは呼ばれない
→ 設定関係で 初期化する場合は、register()
で行う必要があるっぽい - オペレーターでは、呼ばれた際に、invoke より先に行われる
と思っているけど、上の結果を見ると勘違いな気がしてきた
プロパティやドライバーの話
- オペレーターや AddonPref. で設定した XxProperty 系は、
IDTYPE_FLAGS_NO_ANIMDATA
によってアニメーションが禁止されている
→ なので、ドライバーを設定できない(driver_add
するとnot animatable
エラーが出る)
※ 確かめていないが、たぶんパネルも同じ
疑問:
スクリプトで設定したユーザー定義プロパティが、カスタムプロパティに表示されたりされなかったりするのはなぜか。 というか、そもそも「表示される」ものなのか。
-
カスタムプロパティの表示は
rna_prop_ui.py
で定義されていて、例えばobject.items()
の返り値を元に表示が作られる
→ 中身は (str, IDPropertyGroup) のリスト
この形式のプロパティでは、参照するデータが同じだがタイプが違うもの(というか IDProp. が指している本体?)をカスタムプロパティの持ち主が持っている\Leftrightarrow
= つまり、object["cycles"]
もobject.cycles
もOK
※ 結局のポインタ値 (data) は同じだが「入れ物」は違うので、同列に扱ってはいけない気はする -
しかし、ユーザーが定義したカスタムプロパティは (str, float)であり、rna_idprop らしいがここで完結している
= つまり、object["prop"]
はあるが、object.prop
はない -
dskjalさんの説明にあるように、値が変更されるとユーザー定義プロパティが突然カスタムプロパティに表示される
→ IDPropertyGroup の設定がユーザー定義プロパティの読み込みより早いので、値の更新か何かで IDPropertyGroup が再設定されるまでは反映されない?
ユーザー定義のプロパティにドライバーを設定する
def execute(self, context):
driver = bpy.data.objects["何か"].driver_add("プロパティ名").driver
driver.type = 'SCRIPTED'
driver.expression = "var + 1"
if not driver.variables:
dr_var = driver.variables.new()
else:
dr_var = driver.variables[0]
dr_var.type = 'TRANSFORMS'
tg = dr_var.targets[0]
tg.id = bpy.data.objects["ターゲットの名前"]
tg.transform_type = 'LOC_Z'
いまさら知った description の動的な設定
class SpaceSwitch(bpy.types.Operator):
...
ident : StringProperty(name="enum identifier", default="")
desc_dic = {i.identifier: i.description
for i in bpy.types.Constraint.bl_rna.properties["owner_space"].enum_items}
@classmethod
def description(cls, context, properties):
key = properties.ident # 'WORLD' / 'POSE' / 'LOCAL_WITH_PARENT' / 'LOCAL'
return bpy.app.translations.pgettext_tip(cls.desc_dic[key])
- 2.91 では operator の text では label とは異なる翻訳辞書らしく、訳されないことがある
→bpy.app.translations.pgettext_tip
で明示的に翻訳を適用 - 通常のプロパティは
properties.xxx
でアクセスし、クラス変数はcls.yyy
でアクセスする
bpy.ops.view2d.scroll_down:プロパティエディタなどで scroll down を行う(が、いまいち下がりきらない)
フェイクユーザーの話
このスクラップやそのリンク先を読んでいて、せっかくなので C を見てみた
コード
/* $324 */
void id_fake_user_set(ID *id)
{
if (id && !(id->flag & LIB_FAKEUSER)) {
id->flag |= LIB_FAKEUSER;
id_us_plus(id);
}
}
/* ↓ id_us_plus() の中で呼ばれているものの1つ*/
/* $203 */
void id_lib_extern(ID *id)
{
if (id && ID_IS_LINKED(id)) {
BLI_assert(BKE_idtype_idcode_is_linkable(GS(id->name)));
if (id->tag & LIB_TAG_INDIRECT) {
id->tag &= ~LIB_TAG_INDIRECT;
id->flag &= ~LIB_INDIRECT_WEAK_LINK;
id->tag |= LIB_TAG_EXTERN;
id->lib->parent = NULL;
}
}
}
/**
* LIB_... flags report on status of the data-block this ID belongs to
* (persistent, saved to and read from .blend).
*/
short flag;
/**
* LIB_TAG_... tags (runtime only, cleared at read time).
*/
int tag;
ぱっと見た感じ、ID.flag と ID.tag の値の変更が中心に見える
どっちかを参照して保存するデータを決めているんだろうか?
"the data-block this ID belongs to" でデータブロックと ID が別扱いなのがわかんない
- 「struct → data = ID → library」って構造っぽい
- append の説明 "Append from a Library .blend file" を見ると、「library = .blend file」?
- で、ディレクトリ内の個別の要素がデータブロック?
/* $273 */
typedef struct ID {
...
struct Library *lib;
...
} ID;
/* $348 */
typedef struct Library {
ID id;
struct FileData *filedata;
...
} Library;
循環参照っぽいんだけど・・・ どっちが底なんだ
久々に blender 触ったら、3.2 でEVT_TWEAK
が削除されていた...
Tweak events have been removed from KeyMapItem.type. (EVT_TWEAK_L, EVT_TWEAK_M, EVT_TWEAK_R)
Instead of special types, regular mouse button types should be used (LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE) with the KeyMapItem.value set to CLICK_DRAG. Directional dragging can be specified by setting KeyMapItem.direction.
以前のように event だけ取得して後で direction を解釈して... 的な処理はできなさそう
普通にできた。というか公式のKeyMapItem.direction
のほうが判定が厳しすぎて使いづらい