🦾

[UE5] Control Rig をPythonから使う

2022/10/19に公開約10,100字

Pythonでコントロールリグを云々する時のメモです。


アセットの取得

プロジェクト内のアセットを取得するとき

unreal.EditorAssetLibrary.load_asset( asset_path )

asset_path にはいろいろな書き方があてはめられます。

asset_path = "/Script/ControlRigDeveloper.ControlRigBlueprint'/Game/Characters/Mannequins/Rigs/CR_Mannequin_Body.CR_Mannequin_Body'"
asset_path = "/Game/Characters/Mannequins/Rigs/CR_Mannequin_Body.CR_Mannequin_Body"
asset_path = "/Game/Characters/Mannequins/Rigs/CR_Mannequin_Body"

コンテンツブラウザで選択中のアセットを取得するとき

sel_obj = unreal.EditorUtilityLibrary.get_selected_assets()[0]

開いている コントロールリグBP を取得

cr_bp = unreal.ControlRigBlueprint.get_currently_open_rig_blueprints()[0]

コントロールリグのプレビューメッシュ関連

プレビューメッシュの取得

cr_bp.get_preview_mesh()

プレビューメッシュを割り当てる

sk_mesh = unreal.EditorAssetLibrary.load_asset( sk_asset_path )
cr_bp.set_preview_mesh(sk_mesh)

コントロールリグ・グラフの取得

graph = cr_bp.get_model()

コントロールリグ・グラフのノードの取得

グラフのノードを何でもいいので取得

graph.get_nodes()[0]
# <Object '/Game/Characters/Mannequins/Rigs/CR_Mannequin_Body.CR_Mannequin_Body:RigVMModel.RigUnit_BeginExecution_1' (0x00000A8658230480) Class 'RigVMUnitNode'>

ノード名から取得

graph.find_node('RigUnit_BeginExecution_1')

選択中のノード

node_name = graph.get_select_nodes()[0]
# LogPython: Name("RigUnit_BeginExecution_1")

get_select_nodes でノードが取れればいいんですが、ノードではなくノード名が得られるので、
それをfind_nodeに渡します。

graph.find_node(graph.get_select_nodes()[0])
# <Object '/Game/Characters/Mannequins/Rigs/CR_Mannequin_Body.CR_Mannequin_Body:RigVMModel.RigUnit_BeginExecution_1' (0x00000A8658230480) Class 'RigVMUnitNode'>

ノードを調べる

ノードのID

node145 = graph.get_nodes()[145]
node145.get_node_index()
# 145

ノード名

node.get_name()
# 'RigUnit_BeginExecution_1'

node.get_node_title()
# 'Forwards Solve'

ノードの位置
シーン内での位置(トランスフォーム)ではなく、ノードツリー上の位置です。XYで受け取れます。

node.get_position()
# <Struct 'Vector2D' (0x0000024875CA7620) {x: -8224.000000, y: -11360.000000}>

ノードのパス

node.get_path_name()
# '/Game/Characters/Mannequins/Rigs/CR_Mannequin_Body.CR_Mannequin_Body:RigVMModel.RigUnit_BeginExecution_1'

なにに使えるかいまいちわかりません。もしかしたらノードではなくピン側で使う想定のものかも。

ドキュメントでノードを調べる

Pythonドキュメントを「RigVM」で検索し、
検索結果を「node」で検索すると各種ノードについて調べられます。

https://docs.unrealengine.com/5.0/en-US/PythonAPI/search.html?q=RigVM

Sequenceノードについて

5.1から旧Sequenceノードが非推奨になり、実行ピンが可変長になった新しいSequenceノードが登場しています。

内部的には、前者は「RigUnit_SequenceExecution」、後者は「RigUnit_SequenceAggregate」というようです。

リグ階層を取得

hierarchy = cr_bp.hierarchy

階層内のすべての要素を取得

リグ階層での「ボーン」「コントローラ」「Null」といった要素をまとめて「key」と呼びます。アニメーション作業中に打つキーと紛らわしい感じがしますが別物です。

hierarchy.get_all_keys()
# <Array object at 0x0000024875C9A4F0>

hierarchy.get_all_keys()[0]
# <Struct 'RigElementKey' (0x00000A868D8C94D0) {type: Bone, name: "root"}>

ボーン・コントローラ・Null以外にどういったタイプがあるかはRigElementTypeで確認できます。
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigElementType.html

選択中のキーを取得

hierarchy.get_selected_keys()[0]
# <Struct 'RigElementKey' (0x00000A86B0E1DEE0) {type: Bone, name: "root"}>

リグ階層を操作する

リグ階層のためのコントローラーオブジェクトを取得して、そこから操作を開始します。
(ここでいうコントローラーは、リグ要素としてのコントローラーとは違うPythonオブジェクトとしてのコントローラーです)

hierarchy_controller = hierarchy.get_controller()
# or
hierarchy_controller = cr_bp.get_hierarchy_controller()

hierarchy_controller.add_bone('root', '', unreal.Transform(location=[0.000000,0.000000,0.000000],rotation=[0.000000,0.000000,-0.000000],scale=[1.000000,1.000000,1.000000]), False, unreal.RigBoneType.IMPORTED)

hierarchy_controller.add_control('global_ctrl', '', control_settings_global_ctrl, unreal.RigHierarchy.make_control_value_from_euler_transform(unreal.EulerTransform(location=[0.000000,0.000000,0.000000],rotation=[0.000000,0.000000,0.000000],scale=[1.000000,1.000000,1.000000])))

hierarchy_controller.add_null('head_fk_space', unreal.RigElementKey(type=unreal.RigElementType.NULL, name='chest_space'), unreal.Transform(location=[40.598420,3.662945,0.000029],rotation=[-90.000000,90.000000,-80.547132],scale=[1.000000,1.000000,1.000000]), False)

add_bone
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigHierarchyController.html?highlight=add_bone#unreal.RigHierarchyController.add_bone

add_control
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigHierarchyController.html?highlight=add_control#unreal.RigHierarchyController.add_control

add_null
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigHierarchyController.html?highlight=add_null#unreal.RigHierarchyController.add_null

さらに、RigControlは作成時にはコントロールセッティングを与える必要があります。
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigControlSettings.html

example
	control_settings_global_ctrl = unreal.RigControlSettings()
	control_settings_global_ctrl.animation_type = unreal.RigControlAnimationType.ANIMATION_CONTROL
	control_settings_global_ctrl.control_type = unreal.RigControlType.EULER_TRANSFORM
	control_settings_global_ctrl.display_name = 'None'
	control_settings_global_ctrl.draw_limits = True
	control_settings_global_ctrl.shape_color = unreal.LinearColor(1.000000, 0.964687, 0.000000, 1.000000)
	control_settings_global_ctrl.shape_name = 'Hexagon_Thin'
	control_settings_global_ctrl.shape_visible = True
	control_settings_global_ctrl.is_transient_control = False
	control_settings_global_ctrl.limit_enabled = [unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False), unreal.RigControlLimitEnabled(False, False)]
	control_settings_global_ctrl.minimum_value = unreal.RigHierarchy.make_control_value_from_euler_transform(unreal.EulerTransform(location=[0.000000,0.000000,0.000000],rotation=[0.000000,0.000000,0.000000],scale=[1.000000,1.000000,1.000000]))
	control_settings_global_ctrl.maximum_value = unreal.RigHierarchy.make_control_value_from_euler_transform(unreal.EulerTransform(location=[0.000000,0.000000,0.000000],rotation=[0.000000,0.000000,0.000000],scale=[1.000000,1.000000,1.000000]))
	control_settings_global_ctrl.primary_axis = unreal.RigControlAxis.X
	hierarchy_controller.add_control('global_ctrl', '', control_settings_global_ctrl, unreal.RigHierarchy.make_control_value_from_euler_transform(unreal.EulerTransform(location=[0.000000,0.000000,0.000000],rotation=[0.000000,0.000000,0.000000],scale=[1.000000,1.000000,1.000000])))

選択中のキーからコントロールセッティングを取得するにはこう

hierarchy_controller.get_control_settings(hierarchy.get_selected_keys()[0])
# <Struct 'RigControlSettings' (0x00000A86CEDC7E00) {animation_type: AnimationControl, control_type: EulerTransform, display_name: "", primary_axis: X, limit_enabled: ((),(),(),(),(),(),(),(),()), draw_limits: True, minimum_value: {}, maximum_value: {}, shape_visible: True, shape_visibility: UserDefined, shape_name: "Hexagon_Thin", shape_color: {r: 1.000000, g: 0.964687, b: 0.000000, a: 1.000000}, is_transient_control: False, control_enum: None, customization: {available_spaces: , removed_spaces: }, driven_controls: , group_with_parent_control: False}>

ちなみにリグ要素のキーではない実体はこちらなようです

RigBone
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigBone.html
RigControl
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigControl.html
RigSpace
https://docs.unrealengine.com/5.0/en-US/PythonAPI/class/RigSpace.html

でもこれらを取得する方法がいまいちわかりませんでした。castも出来ない様子。

root_bone.cast(unreal.RigBone)
LogPython: Error: Traceback (most recent call last):
LogPython: Error:   File "<string>", line 1, in <module>
LogPython: Error: TypeError: RigElementKey: Cannot cast type 'RigBone' to 'RigElementKey'

リグコントローラーオブジェクトの取得

さきほど紹介した階層のコントローラーと同じく、Pythonオブジェクトとしてのリグコントローラーです。本名は「RigVMController
ノードの選択や追加、ピン接続、undoなど、コントロールリグを開いているときにエディタで行う操作を一通り実行できます。
コントロールしたいコントロールリグBPを取得するところから開始します。

cr_bp = unreal.ControlRigBlueprint.get_currently_open_rig_blueprints()[0]
# cr_bp = unreal.EditorAssetLibrary.load_asset( cr_bp_path )

crtl = unreal.ControlRigBlueprintLibrary.get_controller(cr_bp)
# or
func_lib = cr_bp.get_local_function_library()
crtl = cr_bp.get_controller(func_lib)

公式ドキュメントを確認する。

記事を書いた後で気づいたんですが、
https://docs.unrealengine.com/5.0/ja/control-rig-python-scripting-in-unreal-engine/
以前はなかったと思うんですがこんなページができていました。
最高です。
事前にこのページを見つけていたらこの記事は書かなかったです笑

特に最高な個所をいくつか抜粋します。

開いているコントロールリグをPythonスクリプトとして取得する


右上の「クラス設定」から「py」とでも検索し、
Pythonログ設定 > Python Log Settings > Commands
の「Pythonスクリプトのコピー」を実行します。

そして適宜エディタにペーストして下さい。

これで、いま開いているコントロールリグをPythonで作成するにはどうするか、その全貌が得られます。
たとえばUE5 Mannequinであれば10142行ほどのコードが得られました。
ある意味コントロールリグのアスキー版みたいな感じですね。
最高。

エディタでの操作ログをPythonスクリプトとして得る。


「メッセージログ」ウィンドウ を見てみると「コントロールリグのPythonログ」というカテゴリがあります。※「コントロールリグのログ」と紛らわしいので注意。
コントロールリグをエディタで操作すると、そのログがPythonスクリプトとして流れてきます。
例えば、左下のリグ階層で新規にNullを作った場合はこう、

hierarchy_controller.add_null('NewNull', unreal.RigElementKey(type=unreal.RigElementType.BONE, name='tip_r'), unreal.Transform(location=[0.000000,0.000000,0.000000],rotation=[0.000000,0.000000,-0.000000],scale=[1.000000,1.000000,1.000000]), False)

それをリネームしたらこう

hierarchy_controller.rename_element(unreal.RigElementKey(type=unreal.RigElementType.NULL, name='NewNull'), 'test_null')

ただし、左上のビューポート上でコントローラを操作したりすると、ぶわーっと同じようなログが流れますのでちょっと注意です。

ほか

ほかには、コントロールリグを開いたときにPythonを実行してもらう、みたいな仕込みもできるようです。
MayaでいうuserSetupとかPre Render MELみたいな感じですかね。
試していません。

Discussion

ログインするとコメントできます