子猫チビ太とイラストの成長ブログ♪

子猫チビ太とイラスト練習の記録♪最近は3DCGとハンドメイドしています~

シェイプキーの自動化


さて、できるだけ自動化してシェイプキーをつくっていきます。

1.0~12フレームに13個の表情をautorigのポーズで作成

 

フレーム 発音キー
0 Basis
1 sil
2 PP
3 kk
4 CH
5 SS
6 DD
7 nn
8 aa
9 E
10 ih
11 oh
12 ou

chat GPTさんに作ってもらったスクリプトです。

よかったら自己責任でお試しください。

 

2.顔のオブジェクトを選択してスクリプトを実行

 

 

# === JP12 口形ベイク:選択メッシュ → 発音名シェイプキー ===
# 使い方:
# 1) 発音名とフレームを用意(マーカー名=発音名があればそのフレームを優先)
# 2) 対象メッシュ(body / teeth / tongue など)を選択
# 3) OBJECTモードで実行

import bpy

# ===== 設定 =====
ORDER = ["sil","PP","kk","CH","SS","DD","nn","aa","E","ih","oh","ou"]  # 作るシェイプキー名
BASIS_FRAME = 0                                  # 参照用。Basis自体は変更しない
USE_TIMELINE_MARKERS_IF_MATCH = True             # 同名マーカーがあればそのフレームを優先
START_FRAME = 1                                   # マーカーが無い場合の開始フレーム
STEP = 1                                          # 同上:何フレームごとに並べるか(1=連番1..N)
SNAP_BASIS_TO_FRAME = False                       # TrueにするとBasis形状をBASIS_FRAMEの見た目に置換(要注意)
# =================

def build_frame_map(order):
    base = {name: START_FRAME + i*STEP for i, name in enumerate(order)}
    if USE_TIMELINE_MARKERS_IF_MATCH:
        markers = {m.name: m.frame for m in bpy.context.scene.timeline_markers}
        for k in base.keys():
            if k in markers:
                base[k] = markers[k]
    return base

def ensure_keys(obj, names):
    """Basisと必要キーを作成・初期化"""
    if obj.data.shape_keys is None:
        obj.shape_key_add(name="Basis", from_mix=False)
    kb = obj.data.shape_keys.key_blocks
    if kb[0].name != "Basis":
        kb[0].name = "Basis"
    obj.data.shape_keys.use_relative = True
    # 必要キーを準備
    for n in names:
        if n not in kb:
            obj.shape_key_add(name=n, from_mix=False)
        kb[n].value = 0.0
        kb[n].mute = False
        kb[n].relative_key = kb["Basis"]
    return kb

def mesh_coords_from_evaluated(obj):
    """モディファイア/アーマチュア適用後の頂点座標(オブジェクト空間)"""
    deps = bpy.context.evaluated_depsgraph_get()
    eval_obj = obj.evaluated_get(deps)
    tmp = eval_obj.to_mesh()
    vcount = len(tmp.vertices)
    coords = [c for v in tmp.vertices for c in v.co]
    eval_obj.to_mesh_clear()
    return coords, vcount

def assign_coords_to_key(obj, key_name, coords, vcount):
    kb = obj.data.shape_keys.key_blocks[key_name]
    if len(kb.data) != vcount:
        raise RuntimeError(
            f"{obj.name}: 頂点数不一致(Subdivision/Remesh等でトポロジが変化していませんか?一時OFFに)"
        )
    kb.value = 1.0
    kb.data.foreach_set("co", coords)
    kb.value = 0.0

def bake_pose_to_shape(obj, key_name, frame):
    scn = bpy.context.scene
    scn.frame_set(frame)
    coords, vcount = mesh_coords_from_evaluated(obj)
    assign_coords_to_key(obj, key_name, coords, vcount)

def snap_basis_to_frame(obj, frame):
    scn = bpy.context.scene
    scn.frame_set(frame)
    coords, vcount = mesh_coords_from_evaluated(obj)
    kb = obj.data.shape_keys.key_blocks["Basis"]
    if len(kb.data) != vcount:
        raise RuntimeError(f"{obj.name}: Basisに割当不可(頂点数不一致)")
    kb.data.foreach_set("co", coords)

def main():
    targets = [o for o in bpy.context.selected_objects if o.type == 'MESH']
    if not targets:
        print("メッシュを選択してから実行してください。"); return

    # 安全:オブジェクトモードに
    try:
        bpy.ops.object.mode_set(mode='OBJECT')
    except:
        pass

    scn = bpy.context.scene
    current_frame = scn.frame_current

    frame_for = build_frame_map(ORDER)

    for obj in targets:
        try:
            kb = ensure_keys(obj, ORDER)

            # 必要ならBasisを特定フレームの見た目へスナップ
            if SNAP_BASIS_TO_FRAME:
                snap_basis_to_frame(obj, BASIS_FRAME)

            # 各発音をベイク
            for name in ORDER:
                bake_pose_to_shape(obj, name, frame_for[name])

            # 仕上げ:値は全て0に戻す
            for k in obj.data.shape_keys.key_blocks:
                k.value = 0.0

            print(f"[OK] {obj.name}: {len(ORDER)}個の発音キーを作成/上書き")
        except Exception as e:
            print(f"[NG] {obj.name}: {e}")

    # 元のフレームへ
    scn.frame_set(current_frame)
    print("完了:選択メッシュに発音名シェイプキーをベイクしました。")

if __name__ == "__main__":
    main()

 

 

続いて複数オブジェクトにマスターのドライバーをいれて一括に動かすスクリプト

import bpy

# 無いキーを自動追加するか(True: 追加してリンク / False: 無いものはスキップ)
CREATE_MISSING = True

ctx = bpy.context
sel = [o for o in ctx.selected_objects if o.type == 'MESH']
if not sel:
    raise RuntimeError("メッシュを選択してから実行してください。")

master = ctx.view_layer.objects.active
if master not in sel or master.type != 'MESH':
    raise RuntimeError("アクティブにマスター(例: body のメッシュ)を置いて実行してください。")

if not master.data.shape_keys:
    raise RuntimeError("マスターにシェイプキーがありません。")

mkeys = master.data.shape_keys.key_blocks

def ensure_key(slave, name):
    """スレーブに name のキーが無ければ作る(必要なときだけ)"""
    sk = slave.data.shape_keys
    if sk is None:
        slave.shape_key_add(name="Basis", from_mix=False)
        sk = slave.data.shape_keys
    if name in sk.key_blocks:
        return sk.key_blocks[name]
    if CREATE_MISSING:
        return slave.shape_key_add(name=name, from_mix=False)
    return None

def link_one(slave, name):
    sk = slave.data.shape_keys
    kb = ensure_key(slave, name)
    if kb is None:
        return False

    # 既存ドライバを削除
    data_path = f'key_blocks["{name}"].value'
    try:
        slave.data.shape_keys.driver_remove(data_path)
    except Exception:
        pass  # 無ければOK

    # 新規ドライバを追加(Key データに対して)
    fcu = slave.data.shape_keys.driver_add(data_path)
    drv = fcu.driver
    drv.type = 'SCRIPTED'
    var = drv.variables.new()
    var.name = 'src'
    # マスター Object を参照し、その中の data.shape_keys の値を参照
    var.targets[0].id = master
    var.targets[0].data_path = f'data.shape_keys.key_blocks["{name}"].value'
    drv.expression = 'src'
    return True

count_ok = 0
count_skip = 0

for s in sel:
    if s == master:
        continue
    if not s.data.shape_keys and not CREATE_MISSING:
        print(f"[SKIP] {s.name}: シェイプキーが無い(CREATE_MISSING=False)")
        count_skip += 1
        continue
    # Basisは除外して、他のキーをリンク
    for name in mkeys.keys():
        if name == "Basis":
            continue
        if link_one(s, name):
            count_ok += 1
        else:
            count_skip += 1

print(f"Linked drivers: {count_ok}, skipped: {count_skip}")

 

これでずいぶん短縮できそうです♪