これまでは移行先のSims4人物モデルと、移行元のVRoidサンプル人物モデルをインポートした後に、約60個のリグ(ボーン)の位置合わせを手動で行っていたが、これらは全て自動化できた。
事前調査
Python は一度も使ったことがないので類例調査から始める。
- 一番近い内容の参考記事は How to align an armature bone to a bone from another armature だが、これはポーズモードでのサンプルである。
- Bone Locations - Python Support - Blender Artists Community では、個々のボーンに(グローバルな)座標はなく、親ボーンまたはアーマチュアからの相対となっているから注意しろとだけ書かれている。
- scripting - Move bone to a certain location python - Blender Stack Exchange では、編集モードでボーンの位置を他のオブジェクトの位置に合わせることに挑戦している。あるボーンの位置を、別メッシュの位置に移動させるサンプルがある。
少なくともオブジェクトモデル位は頭に入れておかないと作れなそうだ。
公式の API マニュアルもざっくり読んだ。
まずはポーズモードでのボーン移動を試作する
まずはポーズモードでのボーン移動サンプルコード How to align an armature bone to a bone from another armature を改造し、本当にボーンが移動するか確認する。
いま自分の Blender ファイルには、2つのボーン(リグ)がインポートされている
- 左の点々のようなリグは、Sims4人物モデル由来のボーン(リグ)
アーマチュアオブジェクトであり名前は rig - 右の八角柱のボーンは、VRoid人物モデル由来のボーン
アーマチュアオブジェクトであり名前は Armature ※ややこしくてすまん
Sims4の点々リグのどれかの座標に、VRoidの八角柱ボーンを移動させてみる
- ポーズモードでのボーン移動サンプルコードをコピペ
import bpyarmature_to = bpy.data.objects['Armature']armature_from = bpy.data.objects['Armature.001']armature_from.matrix_world = armature_to.matrix_world - 移動させるのは、VRoid由来の巨大八角柱の太ももボーンが分かりやすそうなので、編集モードに切り替えてマウスで選択
- ボーンプロパティから名前 J_Bip_L_UpperLeg をコピーし、armature_from の方の bones の配列インデックス番号 0 を名前指定に差し替え
- 同様に、rmature_to の方の 0 も Sims4由来の右手リグ名 b__R_Hand__ に置換
- 3Dビューポートの表示をポーズモードに切り替えて実行
ポーズモードで、太ももボーンは移動した。
しかしこれは、あくまでもポーズモードなので、単なるアニメーション。
アーマチュアを構成する個々のボーンの構造自体が変わったわけではない。
VRoid由来のアーマチュアが倒れて地面にキスをしたのは謎。
編集モードでのボーン移動を試作する
まず最初に、
- VRoid人物モデル由来のボーン
アーマチュアオブジェクトであり名前は Armature ※ややこしくてすまん
を選択して、編集モードに切り替えておく。ポーズモードのままでは、編集モードでしか利かない API は全く動作しないことに注意。
まず Python コンソールで、VRoid由来の巨大八角柱の太ももボーンをピンポイントで指定する方法の確認をする。
色々と調べた結果、pose を data に書き換えればよいと分かった。
>>> bpy.data.objects['Armature'].data.bones['J_Bip_L_UpperLeg']
bpy.data.armatures['Armature'].bones["J_Bip_L_UpperLeg"]
エコーバックされ、編集モードでボーンを特定する方法が分かった。
誤りだったが、当初の考えは以下のとおり。
ベタだが、ポーズモードでのボーン移動サンプルコードと同様、移動先の座標を求めてから、移動させるボーンの座標に代入するつもりでいた。
- 移動先の座標値を先に求めるため、Sims4由来の右手リグ b__R_Hand__ のヘッドの座標を取得する。
- リグ(ボーン)のヘッド座標 head_local は、アーマチュア rig 内でのローカル座標という仕様なので、グローバル座標に正規化する。
- 正規化された移動先の座標値(グローバル座標)を、VRoid由来の巨大八角柱の太ももボーン J_Bip_L_UpperLeg が属するアーマチュア Armature(※ややこしくてすまん)内でのローカル座標へと変換する。
- VRoid由来の巨大八角柱の太ももボーン J_Bip_L_UpperLeg のヘッドの座標に、上記で求めたローカル座標を代入する。
事前調査した位置合わせ挑戦記事の回答 Answer に座標変換のやり方が書いてあったが、グローバルからローカルへの変換の説明だけだったので追加調査した。
- すると幾つかの記事で matrix_world でローカル座標に変換する例があった。
- しかしポーズモードならば普通に使える matrix_world や matrix といった演算子 API は、編集モードのボーンに対しては使えなかった。
- これら演算子が適用できるのは、あくまでも対象がオブジェクトの場合であり、個々のボーンはオブジェクトではないので、適用できないというオチ。
数時間調べた結果、ローカル座標のまま代入するだけでよかった。
2つのアーマチュアオブジェクトの座標を共に原点(0,0,0)に揃えてあれば、ローカル座標とグローバル座標を変換せずとも、座標値は同じになる。よって:
bpy.data.armatures['Armature'].edit_bones['J_Bip_L_UpperLeg'].head.xzy = bpy.data.armatures['rig'].edit_bones.data.bones['b__R_Hand__'].head_local
で想定したとおりの動作となった。
この結論に至るまでに、様々な罠にはまった。
- 2つのアーマチュアのローカル座標系の座標軸が実は違っていて、そのまんま代入したら Y軸とZ軸が入れ替わった
× head.xyz
〇 head.xzy のように Y/Zを入れ替えながら代入 - さらには Sims4ローカル座標系のZ軸の符号が逆だったので、マイナス1を乗じてYに格納する必要があった
- 当初 bpy.data.armatures でなく bpy.data.objects を使っており、何となく data が余計な気がしてウッカリ省きエラーを連発
× bpy.data.objects['rig'].bones['b__R_Hand__'].head
〇 bpy.data.objects['rig'].data.bones['b__R_Hand__'].head
〇 bpy.data.armatures['rig'].bones['b__R_Hand__'].head - 老眼による、座標値 Vector のテスト出力の読み違え
5.063229764346033e-07 を 5より大きい数値と読み違え、なぜ身長 5mの位置にリグ(ボーン)があるのかと狼狽した。e-07 なので 0.0000005 なのだが。 - 当初 edit_bones でなくbones を使い、座標を参照はできたが代入できず
〇参照 bpy.data.objects['rig'].data.bones['b__R_Hand__'].head で Vector が返る
× 代入 bpy.data.objects['rig'].data.bones['b__R_Hand__'].head = bpy.data.objects['rig'].data.bones['b__R_Hand__'].head で Vector 代入できない - 現在選択しているアーマチュアを混同すると、以前に動作したハズのスクリプトが一見エラー
bpy.data.armatures['Armature'].edit_bones['J_Bip_L_UpperLeg'].head.xzy = bpy.data.armatures['rig'].edit_bones.data.bones['b__L_Hand__'].head_local
〇 bpy.context.active_bone が bpy.data.armatures['Armature']...EditBone のとき
× bpy.context.active_bone が bpy.data.armatures['rig']...EditBone のとき - 同様に、編集モードではなくオブジェクトモードで実行した場合、以前に動作したハズのスクリプトが一見エラー
- 何となく現在選択していないアーマチュア rig の方の edit_bones でも data.bones が省ける気がしてウッカリ省きエラー
〇 bpy.data.armatures['Armature'].edit_bones['J_Bip_L_UpperLeg'].head.xzy = bpy.data.armatures['rig'].edit_bones.data.bones['b__L_Hand__'].head_local
× bpy.data.armatures['Armature'].edit_bones['J_Bip_L_UpperLeg'].head.xzy = bpy.data.armatures['rig'].edit_bones['b__L_Hand__'].head_local
〇 bpy.data.armatures['rig'].edit_bones.data.bones['b__R_Hand__'].head
× bpy.data.armatures['rig'].edit_bones['b__R_Hand__'].head
編集モードでのボーン移動スクリプトを作成する
移動させたい全てのボーンの名前と、移動先にしたい全てのリグの名前を表にする。
以前に目視かつ手動で位置合わせしていたボーン(リグ)の名前を特定して、そのとき何のボーンを何のリグに合わせたかを思い出しながら、対応表をつくる。
Excel 表の式でコードを増殖
外道?
残念ながら、これだけではうまくいかなかった
- x座標は合っているが、y座標とz座標があわないなど
- Sims4ローカル座標系のZ軸の符号が逆だったので、マイナス1を乗じてYに格納する必要があった
- 首/視線/指のつま先など、それ以上先には別のリグ(ボーン)が無いものは、テール tail の位置決めが必要だった。
- ヘッド head の座標に固定値を足すなどして位置決め
力技で問題点を解決して、全自動で位置合わせを行えるようにした
Excel 表で命令文を試作しては、Python コンソールでテストした。
- 位置合わせしたい(移動させたい)ボーンのヘッド座標に、移行先リグのヘッド座標を代入するだけの処理は、Excel 表で自動生成したもので十分だった。
- 首/視線/指のつま先など、それ以上先には別のリグ(ボーン)が無いものについてのテール tail の位置決めについては、自分でそれらしい仕様を決めて実装した。
これまで約60個のリグ(ボーン)の位置合わせを手動で行っていたが、全て自動化できた。
位置決めするにあたり自作したチートシート
首/視線あたりの座標設定をどのようにするか考えるため作ったシート
端っこのボーンのヘッド head やテール tail をどの辺に移動させるかを考えるさいに、VRoid と Sims4 の対応関係を整理しておく必要があった。
Sims4人物モデルに設定されたリグ(ボーン)の場所を確認するために作ったシート
Blender でアーマチュアを切り替えたり、モード切り替えたりするのが面倒だった。
紙ボーンの配置や名称が男女で違うことに気づき、マッピングを整理した図
せめて番号くらいは合わせておいてくれるといいのだが。
後日の調査で、髪・乳・衣服などのボーンは VRM の標準の範囲ではないらしく、モデルごとにバラツキがあったり、親ボーンを設定していない場合があるなど、骨格設定の自動化が難しい事項と分かったが、スクリプトからは削除せずにそのまま残している。
編集モードでのボーン移動スクリプト
GitHub - tombi-aburage/Sims4VRMAvatar : RelocateVRMBones2Sims4Rigs.py