0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Blenderのリグやボーンのスケールを適用した時の不具合対策

Last updated at Posted at 2025-04-17

Blenderで他からボーンアニメーションのついたオブジェクトをインポートした場合に
スケールが1でない状態で読み込んでしまったもののスケールを適用した場合に
image.png
意図しない場所に吹っ飛んでいったり
image.png

Rigify等のリグシステムでスケールがかかった状態で作業してしまって
image.png
スケールを適用して「しっちゃかめっちゃか」になったことがあるかと思います。

これは Blenderのアニメーションはオブジェクトそのものに格納されているのではなく
Actionという別のデータを利用していて
オブジェクトスケールの「適用」ではそのデータやコンストレイントの設定が 変更されないために起こります

こういった不具合の対策について書き留めておきたいと思います

ボーンアニメーションスケールの適用スクリプト

Blenderにはアニメーションできるものが色々ありますが
選択したアーマチュアのスケールを適用した上で そのアーマチュアに付いていたアニメーションのスケールを調整するスクリプトを例示してみます

apply_armatureobject_scaling.py
import bpy
import mathutils
obj = bpy.context.active_object
if obj.type == 'ARMATURE':
    # アーマチュアオブジェクトのトランスフォームを取得
    amt_scale = obj.matrix_local.to_scale()
    ##====================トランスフォームの適用========================##
    # 子を取得
    children = obj.children
    bpy.ops.object.select_all(action='DESELECT')
    obj.select_set(True)
    for child in children:
        child.select_set(True)
    # 選択オブジェクトのスケールを適用
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    bpy.ops.object.select_all(action='DESELECT')
    obj.select_set(True)

    ##====================アニメーションデータを調整========================##
    # スケールのみを考慮したマトリクスを作成
    mat_amt_scale = mathutils.Matrix.LocRotScale(None, None, amt_scale)
    # 位置のアニメーションデータを調整
    ## アニメーションを取得
    animation_data = obj.animation_data
    if  hasattr(animation_data,"action"):
        fcurves = animation_data.action.fcurves
        for curve in fcurves:
            if curve.data_path[-8:] == 'location':
                # array_indexで各データパスのチャンネルのインデックスが取得できる
                scale = mathutils.Vector((1.0, amt_scale[curve.array_index]))
                # 移動のキーフレームの値をスケーリングに合わせた値に設定
                for point in  curve.keyframe_points:
                    point.co = point.co * scale
                    point.handle_left = point.handle_left *scale
                    point.handle_right = point.handle_right *scale

適用したいアーマチュアオブジェクトをアクティブにした状態で実行するスクリプトです
スクリプトの前半は
アーマーチュアと子になるオブジェクトを選択してオブジェクトメニューの適用>スケールを実行したのと同じ動作
後半は
スケールを適用したことで ボーンの移動量が合わなくなる点の調整です

(このスクリプトではNLAを使ったりしたアニメーションには対応していません)

rigifyでスケールを適用した時の不具合対応

ボーンの移動の問題を解消した後にも
Rigify等のリグでは骨が伸び縮みした状態になるかと思います
image.png
これは ストレッチコンストレイントに記録されているボーンの長さがスケール適用前の値のままなのが原因のようです
手動で調整する場合には「元の長さ」の欄の横にある×マークをクリックすることで現在の値にリセットされるようですが
全てを調整するのは大変ですのでこれもスクリプト化してみました

stretchto_reset.py
import bpy
# アクティブオブジェクトのストレッチコンストレイントを調整
obj = bpy.context.active_object
if obj.type == 'ARMATURE':
    for pose_bone in  obj.pose.bones:
        for constraint in pose_bone.constraints:
            if constraint.type == 'STRETCH_TO':
                # ストレッチの長さを初期化
                constraint.rest_length = pose_bone.bone.length

ファイル内にあるアクションに スケーリング操作を適用するスクリプト

apply_action_scaling.py
import bpy
import mathutils
# 適用するスケール
apply_scale = [0.01, 0.01, 0.01]
# リストに書いたアクションを処理対象に
actions_name = ['Action','Action.001','idleCrouch','idleCrouch_in','idleCrouch_out','Stay']
# ファイル内にあるアクション全てを対象にする場合
# actions = bpy.data.actions

actions = [bpy.data.actions[name] for name in actions_name if(name in bpy.data.actions.keys()) ]
amt_scale = mathutils.Vector(apply_scale)
for action in actions:
    fcurves = action.fcurves
    for curve in fcurves:
        if curve.data_path[-8:] == 'location':
            # array_indexで各データパスのチャンネルのインデックスが取得できる
            scale = mathutils.Vector((1.0, amt_scale[curve.array_index]))
            # 移動のキーフレームの値をスケーリングに合わせた値に設定
            for point in curve.keyframe_points:
                point.co = point.co *scale
                point.handle_left = point.handle_left *scale
                point.handle_right = point.handle_right *scale

対象にするアクションの一覧を書き出す準備に
ファイルの中にあるアクション一覧をCSVでクリップボードに書き出すスクリプト

import bpy
actions = ["'%s'" % a.name for action in bpy.data.actions]
bpy.context.window_manager.clipboard = ','.join(actions)

ちなみにこれはPythonコンソールでこんな感じに短縮表記できます

C.window_manager.clipboard = ','.join(["'%s'" % a.name for a in D.actions])

使うのは一回の作業では一回きりなのでこの方が楽かも?


オブジェクトのスケールをつけたままアニメーションを付けてしまった事例は
私がゲームやアニメでの業務に関わっていた中でも頻繁に遭遇しているものです
何かの手助けになれば幸いです

おまけ

スケールが適用されていないオブジェクトを探すスクリプト
(ファイル内のスケール1でないオブジェクトをコンソールにプリント)

check_scale.py
import mathutils 
for obj in bpy.data.objects:
    normal_seze = mathutils.Vector((1.0, 1.0, 1.0))
    if obj.scale != normal_seze:
        if obj.name[0:3] != "WGT":
            print(obj.name)

"WGT"で始まるオブジェクトを対象外にしています

スケールを適用するスクリプトの別バージョン

apply_armatureobject_scaling2.py
import bpy
import mathutils
obj = bpy.context.active_object
if obj.type == 'ARMATURE':
    # アーマチュアオブジェクトのトランスフォームを取得
    amt_scale = obj.matrix_local.to_scale()
    # スケールのみを考慮したマトリクスを作成
    mat_amt_scale = mathutils.Matrix.LocRotScale(None, None, amt_scale)
    # オブジェクトのスケールをボーンに適用
    obj.data.transform(mat_amt_scale)
    # 情報の更新で一旦エディットモードに
    bpy.ops.object.editmode_toggle()
    bpy.ops.object.editmode_toggle()
    
    # 位置のアニメーションデータを調整
    ## アニメーションを取得
    animation_data = obj.animation_data
    if  hasattr(animation_data,"action"):
        fcurves = animation_data.action.fcurves
        for curve in fcurves:
            if curve.data_path[-8:] == 'location':
                # array_indexで各データパスのチャンネルのインデックスが取得できる
                scale = mathutils.Vector((1.0, amt_scale[curve.array_index]))
                # 移動のキーフレームの値をスケーリングに合わせた値に設定
                for point in  curve.keyframe_points:
                    point.co = point.co * scale
                    point.handle_left = point.handle_left *scale
                    point.handle_right = point.handle_right *scale

    # 子になっているオブジェクトのスケールを全て1に
    # 子を取得
    children = obj.children
    for child in obj.children:
        mat_scale_child = mathutils.Matrix.LocRotScale(None, None, child.matrix_local.to_scale()) @ mat_amt_scale
        child.data.transform(mat_scale_child)
        new_loc = child.matrix_basis.to_translation() @ mat_amt_scale
        child.location = new_loc
        child.scale = (1 ,1, 1)
    obj.scale = (1 ,1, 1)

基本的には先のスクリプトと同じ挙動ですが 少し書き方が異なっているバージョンです

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?