作ったモーションの腕や足の角度や位置を後から変えられるようにしたい

# 溢れるキーフレームたちが問題

Blenderでモーション作っている時にこう思った。

:::note info
『後から手足の角度だけ加算したい。調整したい。』
:::

だけど、
BVHファイル（モーキャプなどから持ってくるときの形式）などをリターゲットしたモーションファイルを開くと、勝手に作られたキーフレームが邪魔で編集するべきキーフレームがうずもれてしまい、実質編集不可じゃないですか。

![スクリーンショット 2025-06-04 145818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/7224a56c-e6b5-4de9-af08-46e81b8411e2.png)


:::note alert
（腕を上下させるだけのモーションなのに…いらないキーフレームがたくさん:rage:）
:::



![スクリーンショット 2025-06-04 150044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/d5307b99-a269-487d-98cb-4ab5f87172f4.png)
＊完成後


（ほら、後からちょっと腕曲げたいだけやねん:tired_face:）

というわけでやっていきます。
全体の手順は以下の通りです。

:::note info

①スクリプトでモーション調整用の骨（以下ハンドル骨）を生やす

:::

:::note info

②生やしたハンドル骨にMMDボーンツールの回転の追加を適用

:::



# ①スクリプトでハンドル骨を生やす



↓　①調整用のハンドル骨を生やすためのスクリプト

:::note info
追加したいハンドル骨と、その親をどれにするかはご自身のモデルとご相談の上、書き換えてご使用ください。

:::





（detailsをクリックしてね）



<details><summary>details</summary>



```
import bpy
from mathutils import Vector

# Armatureオブジェクトを取得
armature = bpy.data.objects.get("Armature")  # アーマチュア名を正確に確認
if not armature:
    print("Armature.002 が見つかりません")
    exit()

# 編集モードに切り替え
bpy.context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='EDIT')

# ボーン情報（親にするボーン名, 追加するハンドル骨の名前を自由に決める, 生成するtail座標（ローカル））
bone_data = [


    ("Root", "RotHip", (0, 0.2, 0)),
    ("J_Bip_C_Spine", "RotChest", (0, 0.2, 0)),
    ("J_Bip_C_UpperChest", "RotNeck", (0, 0.2, 0)),
    ("J_Bip_C_Neck", "RotHead", (0, 0.2, 0)),
    ("J_Bip_C_Hips", "RotUpperLeg_L", (0, 0.2, 0)),  # ここを特殊処理
    ("J_Bip_UpperLeg_L", "RotLowerLeg_L", (0, 0.2, 0)),
    ("J_Bip_LowerLeg_L", "RotFoot_L", (0, 0.2, 0)),
    ("J_Bip_Shoulder_L", "RotUpperArm_L", (0, 0.2, 0)),
    ("J_Bip_UpperArm_L", "RotLowerArm_L", (0, 0.2, 0)),
    ("J_Bip_C_Hips", "RotUpperLeg_R", (0, 0.2, 0)),  # ここを特殊処理
    ("J_Bip_UpperLeg_R", "RotLowerLeg_R", (0, 0.2, 0)),
    ("J_Bip_LowerLeg_R", "RotFoot_R", (0, 0.2, 0)),
    ("J_Bip_Shoulder_R", "RotUpperArm_R", (0, 0.2, 0)),
    ("J_Bip_UpperArm_R", "RotLowerArm_R", (0, 0.2, 0)),
]

# Armatureデータを取得
armature_data = armature.data

for parent_name, bone_name, local_tail in bone_data:
    new_bone = armature_data.edit_bones.new(bone_name)
    
    # 親ボーンを取得
    parent_bone = armature_data.edit_bones.get(parent_name)
    if parent_bone:
        # 特定のボーンのみ head を親ボーンの head に設定
        if bone_name == "RotUpperLeg_L":
            new_bone.head = armature_data.edit_bones["J_Bip_UpperLeg_L"].head
        elif bone_name == "RotUpperLeg_R":
            new_bone.head = armature_data.edit_bones["J_Bip_UpperLeg_R"].head
        else:
            new_bone.head = parent_bone.tail  # 通常は親ボーンの tail
        
        # tail は head + local_tail の位置に設定
        new_bone.tail = new_bone.head + Vector(local_tail)

        # 親ボーンを設定
        new_bone.parent = parent_bone
        print(f"ボーン '{bone_name}' を '{parent_name}' にペアレントしました")

    else:
        print(f"親ボーン '{parent_name}' が見つかりません")

# 編集モードを終了し、オブジェクトモードに戻る
bpy.ops.object.mode_set(mode='OBJECT')

print("親ボーンのtail位置または特定のhead位置から子ボーンを生成しました！")
```


</details>


:::note warn
コードの中にある追加したいハンドル骨は、『後で動かしたい骨』を親にしないことを推奨。挙動がおかしいので。

例えば、
左前腕のボーンを後々ハンドルで動きを調整したい場合、そのハンドルの親は上腕のボーンにします。

:::
上のとモデルが違いますが、こいつはモーションをリターゲットする元のモデルです。手順に違いはないのでスルーしてください
![スクリーンショット 2025-06-04 153858.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/9cb89875-4ee8-4f6f-a87e-9b2e22130055.png)


Y軸方向にまっすぐなハンドル骨を生やしてみた。だいたい親骨のテール位置。

太もものハンドル骨の生成位置だけは自身の骨のヘッド位置にしておいた。見やすいからね。

今更ですけがVRMやMMDのアドオンを実装する説明は省きます。ご了承を


# ②生やしたハンドルにMMDボーンツールの回転の追加を適用

では後々ハンドルを使って微調整したい骨のプロパティを弄っていきましょう。
![スクリーンショット 2025-06-04 151348.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/d3de43bd-6058-4b8a-82b9-2c70ef2205fb.png)

例：左前腕の骨にハンドル骨の回転を加算するために、さっき作った『RotLowerArm_L』を割り当てたところ。
これをスクリプトでサクッとやりたい。

（もろちん手動でやっていっても構いません）

②生やしたハンドル骨にMMDボーンツールの回転の追加を適用
以下スクリプト（detailsをクリックしてね）
<details><summary>details</summary>

```

import bpy

# Armatureオブジェクトを取得
armature = bpy.data.objects["Armature"]  # Armatureの名前を適宜変更してください

# 対象のボーンと値のボーンのペアをリストで指定
bone_pairs = [

    ("J_Bip_C_Hips", "RotHip"),
    ("J_Bip_C_Chest", "RotChest"),
    ("J_Bip_C_Neck", "RotNeck"),
    ("J_Bip_C_Head","RotHead"),
    
("J_Bip_UpperLeg_L","RotUpperLeg_L"),
("J_Bip_LowerLeg_L","RotLowerLeg_L"),
("J_Bip_Foot_L","RotFoot_L"),
("J_Bip_UpperArm_L","RotUpperArm_L"),
("J_Bip_LowerArm_L","RotLowerArm_L"),


("J_Bip_UpperLeg_R","RotUpperLeg_R"),
("J_Bip_LowerLeg_R","RotLowerLeg_R"),
("J_Bip_Foot_R","RotFoot_R"),
("J_Bip_UpperArm_R","RotUpperArm_R"),
("J_Bip_LowerArm_R","RotLowerArm_R"),



#("",""),
#("",""),
#("",""),
]

# MMDのボーンデータを取得
mmd_root = armature.mmd_root

for bone_name, target_bone_name in bone_pairs:
    bone = armature.pose.bones.get(bone_name)
    target_bone = armature.pose.bones.get(target_bone_name)

    if bone and target_bone:
        # additionalTransformBoneの設定
        bone.mmd_bone.additional_transform_bone = target_bone.name
        bone.mmd_bone.additional_transform_degree = 1.0  # 影響度（必要に応じて変更）

        # AdditionalRotationをオンにする
        bone.mmd_bone.has_additional_rotation = True

        print(f"Set additionalTransformBone: {bone_name} -> {target_bone_name}")
        print(f"Enabled AdditionalRotation for {bone_name}")
    else:
        print(f"ボーンが見つかりません: {bone_name} または {target_bone_name}")


```

</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/1996ef0a-5c7a-4583-ac5b-ea9a2db432b8.png)

スクリプトを実行しただけでは反映されなかったので、『回転＋』の項目をオフ⇒オンと付けなおしたら無事反映された。


![スクリーンショット 2025-06-04 150003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/a4dd93d4-47eb-4789-b6d5-a3f0696674ef.png)

先ほどのおさげの女の子モデルで作った『バンザイ』のモーションをリターゲットして、腕はハンドルで曲げている。

![スクリーンショット 2025-06-04 150044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105235/e365554d-f6ec-4504-b056-29cac0a4b2e6.png)

キーフレームに注目。

:::note alert

リターゲットするときは項目からハンドル骨は外しておくように。

:::



そうすればハンドル骨に無用なキーフレームは生成されません。

つまり今回のやったことを一言でまとめるならば

:::note info
リターゲットに影響しないボーンを新たに作り、そいつの回転具合を後からモーションに加算する
:::

ってことですな。


# おまけ）作ったハンドル骨にコンストレイトを適用する

<details><summary>details</summary>

```


import bpy

# Armatureオブジェクトを取得
armature = bpy.data.objects.get("Armature")  # アーマチュア名を正確に確認
if not armature:
    print("Armature.002 が見つかりません")
    exit()

# ボーンのリスト（制限を適用するボーン名）
bone_names = [
    "RotHip", "RotChest", "RotNeck", "RotHead",
    "RotUpperLeg_L", "RotLowerLeg_L", "RotFoot_L",
    "RotUpperArm_L", "RotLowerArm_L",
    "RotUpperLeg_R", "RotLowerLeg_R", "RotFoot_R",
    "RotUpperArm_R", "RotLowerArm_R"
]

# ポーズモードに切り替え
bpy.context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='POSE')

for bone_name in bone_names:
    bone = armature.pose.bones.get(bone_name)
    if bone:
        # 回転制限コンストレイントを追加
        constraint = bone.constraints.get("Rotation Limit")
        if not constraint:
            constraint = bone.constraints.new(type='LIMIT_ROTATION')
        
        # X・Y軸を完全固定（0度に設定）
        constraint.use_limit_x = True
        constraint.use_limit_y = True
        constraint.use_limit_z = False  # Z軸は制限なし
        constraint.min_x = 0
        constraint.max_x = 0
        constraint.min_y = 0
        constraint.max_y = 0

        # オーナーをカスタム空間に設定(ここはお好みで)
        constraint.owner_space = 'CUSTOM'

        print(f"ボーン '{bone_name}' に回転制限を適用しました")
    else:
        print(f"ボーン '{bone_name}' が見つかりません")

print("全てのボーンに回転制限コンストレイントを適用しました！")

```

</details>



以上、初心者なりに考えたモーションの修正方法です。