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?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

シェイプキーのドライバボーンを一括作成するスクリプト

Last updated at Posted at 2024-07-09

Blenderには他のオブジェクトやボーンの移動量に合わせて他のオブジェクトの設定値を変更するドライバー という機能があります。

例えばボーンのポーズモードで トランスフォームのX位置の数値で右クリックして
メニューの中にある「新規ドライバーとしてコピー」を押して
シェイプキーの値の欄を右クリックして「ドライバーを貼り付け」を実行すると
ボーンのポーズモードでのX移動に合わせてシェイプキーの値が変化するドライバが作成できます
image.png
24070901.gif

シェイプキーはキャラクターの表情付け等に便利で こうやってドライバで制御するようにすればボーンアニメーションと同時に扱えます
ですが全てを設定するのはとても煩雑です

ボーンのコントローラーを一括して作成するスクリプトを作成してみました
image.png
メッシュを選択した状態で実行するとメッシュに設定されたシェイプキーから
シェイプキーを制御するドライバを設定したボーンとボーン名と目盛りのカスタムシェイプを作成します。

y_make_shapekey_controler.py
import bpy
import zlib
import base64
import json
# 文字メッシュのデータ
mesh_dic_zip = {
"A":"eJyLjo421FEwjNVRANFmINoUQcPETaB8MG2ko2AOok1ANIgBV2Gso2AKos2gOpFUgsXNoSYD+QZQE41iY2MBnaQdVw==",
"C":"eJxNjMkNADAIw1ZhAB4cPYZB7L9GiZRHX0YmclW5ymkVMMBQueAiN//jnd7po3GU0czCwGRplsny/ksH5e4HuOEbkg==",
"B":"eJxFjcENACEIBFuhAB4qKloLof82jk023mvYsAwRsVRGqsRU6WBlY57MC+zMnb2is+fs7cSATcPQaB402m+0d1F0fjg0XZVD8wU3PmZ+zvsl+g==",
"D":"eJwtzMsNAEEIAtBWKIDDfHS2GEP/bawknh5KtKqC+ERUEs/umdttY+z+yMHNcljetIe49hIxFzkfUtIPbIgWZw==",
"E":"eJyLjo421VEwj9VRiDZE0CYg2gRKA/mGINoURIMY0QZQEaCMEYg20lEwhtIwnaaxsbEAwWkUrg==",
"F":"eJyLjo421VEwj9VRiDZE0CYg2gRKA/mGsSBGtAGIBRExAtFGOgrGUNokNjYWAJgRET8=",
"G":"eJxNzcsNwDAIA9BVGIBDfiTtLIj91yiOrKSnhwCDuzeVFSo+aFWZtMGcV863xn46YKeGHAokC4pyL/T/Zl6yk0gnPy+VB774FPEBjiEifg==",
"H":"eJyLjo421FEwjNVRANEmUNocRJtC+aYIvmEsiAFSYQBiGCF0GoNoEyhtrKNgGhsbCwDATBSu",
"I":"eJyLjo420lEwj9VRiDaG0iYIviGUD6aNQDSIEW2oo2AAFwHSQL4xVKUxVKdpbGwsAMHEFK4=",
"J":"eJyLjo421FEwjNVRiDbQUTAC0cZQvgmUD6TNoeLmsSAGSIcBsg4jHQVjqAoTqA7T2NhYAL4CFKg=",
"M":"eJyLjo421VEwjNVRANHmINpYR8EURBtC+YYgeRAj2gCqEihiBKKNdBSMoTpMYmNjAZgXEUE=",
"K":"eJyLjo421FEwj9VRANEmUNoQRJtCxYG0EZQ2jAUxog2gKgyhMkDaGNkEEx0F09jYWADAOBSr",
"L":"eJyLjo421FEwj9VRANGGINoESpvqKBjFghjRBlARQ5AIkDbSUTCOjY0FAKPlDcw=",
"N":"eJyLjo421VEwj9VRANGGINoQyjcE8UEMEMsAKmIEoo10FIxjY2MBpVcN0g==",
"O":"eJxFzcsNACAIA9BVGICDqOgwDfuvoTVNPD1C+QCYbrvc0GW6LdnpzUP5M9QPzrFA+xONjr85dGnS5ZZ060PwY9UBm+gdTQ==",
"P":"eJxNzLsNADEMAtBVGIAiHycZxvL+ayScKK56IAtnZidOERn29XAP9y4XsewuBV2aQiOGHMS033L+F8+tT1UX6QoZ3A==",
"Q":"eJwtjckNADAIw1ZhAB49oHQXxP5rlKh5OUEGMnOq7FLJpbLA7oc9QCOdc6dv9LvPQsiB9DcHuGkYN9o08Kg4GLzYvPwcVfUAhbggxg==",
"R":"eJxFzcENACAIA8BVGICHiIq7EPZfQ5s04XUlgZKZpmKlAhcNuDhvlU0nNXq4F4WAy4Ewu9Ghs8m7qS+/oXLhxYeqB4VjIMc=",
"3":"eJxNjrsNADEIxVZhAIqQf2ZB7L/GhcjFVUY8MLh7V9mh4hUOlQVn8uYj2eDtd9jIK/uPRt/Sk4UXFWOiYKpstr9xwsXlzSeHz+yKDmqziPgAnFUpzg==",
"5":"eJxFzdkNACAIA9BVGIAP8d6lYf81tEmNX4+UC8Bw2+mG+E7a5e0P2ZRXWmUwZ4HiFkrexNtoutDp1MWlD9ttZeYBjNsfFA==",
"X":"eJw1jMsNADAIQldxAA9V+xnGsP8aLQk9PVCgu8Mt4EYWWW6LXPLvfuQ/AxT8DIrUQqrxOJVMLW4AFyA5GBw=",
"Y":"eJyLjo421lEwjNVRANGmINpUR8EcRBuCaBADxDIAMYygKoF849jYWAClSw3V",
"0":"eJxdjMkJADEMA1tRAX7EzrW9GPffRiIQBPY1kscoM6ehlyGHIcjbt/pHurzrHvoLegaaxtCe+S8O9UUug5PbMMnO5aoDjPIfCw==",
"6":"eJxNzbkNADAIA8BVGIAiQL5dEPuvESy5SHXIRpCZU2WVShpdKpMG7N6hU2PebhgqtzAgGdwcbJwX4r+8+ak1eOhVOVX1AITgIL4=",
"Z":"eJyLjo421FEwj9VRiDaF0kC+IYg2A9EgBkjEACpjBKKNdBSMY2NjAaVIDdM=",
"V":"eJyLjo421VEwj9VRANEmINpYR8EQRBtC+YYgeRAj2gAhYwSijXQUjKE6TGJjYwGYgxFD",
"U":"eJwtjMkJADAMw1bxAHnkaOgwIfuv0Rr8kkDGM9OGXAN5yWMIMsVQD3YKzSmu8pdFluHooXf3AcBFFKs=",
"W":"eJyLjo421VEwj9VRANGGINpYR8EYRBtC+YYgeRAj2gAhYgSijaAqgTpMYmNjAZerET8=",
"T":"eJyLjo421VEwj9VRiDZG0IYg2hDEBzGiDRAiRlDaODY2FgCmIQ3X",
"S":"eJxFjcENwDAIA1dhAB6BhqSZBbH/Go0jR32dDejIzFDxUsmuYuDuD3sHnTSVIAfnk3eT3bj3QkBqCI0f/DdfBj8e46DpJdc1bsGqqg/uNiYZ",
"8":"eJxFjssRwCAIRFuhAA4uxl8tDv23ETDr5LS8EZ67924q1VX2o2KZwQ+5kXumkUEG98B7433wIE96Jj3Dc8iNkkPhhdFQabyN+t/gGAdNU2VlLhWcH8utFGYcNcKJ7zGsMBZBdfcXino1Mw==",
"9":"eJxdjcsNACEIRFuhAA6Kom4thP7bWCaZg/H0yHyYiDAVT5VwsqsscoPlH3CSTr04waFizFniiKbSqbQ7UR/G3VzP4mbvcOHDYuYPrLUkQw==",
"1":"eJyLjo421FEwj9VRiDbSUbCA0kYg2hBKG4NoEAMkYoAsA1RpDKJNwCpiAZbLETo=",
"m":"eJyLjo421VEwitVRANGmINpER8EMRBtD+UYIvjGINoTyDUH6QIxoAx0FQ7gIRIcxVIcJskkwk810FMxjY2MBuu0blw==",
"n":"eJxFyLENACAMA8FVPIALSALLRN5/DXAD1b2+uxcRIuyyRWw7vyFHD2K+cw0ibRIl6QCYSBFC",
"c":"eJwtjcERACEMAluhAB4azV0xmfTfhuLwWiYsk6oK4muitjmJNJd4+3D/mL6nPAWZQ2HYCBuL2F6mF+/DT8zuPrjcG5A=",
"e":"eJxFjcsNwDAIQ1dhAA4JhXxmQey/RnFlqadngw2ZOVWiVNJUFtjewSDbP9wb6GRwHuw7+hBoDIjBpP0XnMlg8/u4VDZ4mGte8KrMqnoBpT0kOA==",
"b":"eJyLjo421FGwiNVRANGmINoESgP5RlA+mDbVUTCG0iaxIEa0gY6CIbJKQ6gKY5AKJJOAOsxAtBlIXWwsAOezGdo=",
"d":"eJxFjMENACAIA1dhAB6CYtyFdP81tEkTX1cart0dbhNu5CLTLcn6LPWl+4CBxmAY+kwtTS0tGY9bZgC45nQZ1w==",
"g":"eJxFjcsJQEEIA1uxAA+r7q8Xsf821kAe7zQJDElmusoulTSVBS72poNTxdgDdLL9SVoh5KDZxgCDC0HzW9h8Ov/z4dOtqgeDHSC5",
"a":"eJxVjbkNwEAMw1bRACrOvi+7GN5/jUSAmlQ0QEKuqk3MJsQlLiLFNMM+7b9uuzviFHXUIMLlsEmXvw/hhfDCIa54iae7X4S0IMA=",
"f":"eJyLjo420lEwi9VRiDZG0EZQ2hxEm0LFTXQULKB8i1gQI9pQR8EAxDCE6gDSxlCVhlATTOEmxMYCACZJGDE=",
"t":"eJyLjo420lEwi9VRiDZG0MZQ2gJEm0LFTXQUjGJBjGhDHQUDEMMQJAKhjaEqDEE00ETT2NhYAMMmFLQ=",
"j":"eJyLjo421lEwjNVRiDaC0iY6CkZQ2gxEA+XNQbQhSBzEiDZA6DCA0sZQlSZQlaaxsbEAvvwUpg==",
"k":"eJyLjo420lGwiNVRANEmUNoIRJvqKJhBaaNYECPaQEfBEMQwhKoA0sZQ2iQ2NhYAmBERQA==",
"l":"eJyLjo421lGwiNVRANHGINpER8EoFsSINtBRMAQxDMEisQDpiQpo",
"o":"eJwtjccNADAIA1dhAB4pQIax2H+NYMmvw8gFwHa77YbjdsjRKV1kiKl/yh/088By20os8qop5JxEkKWGp8ZZet39AZnYHU0=",
"p":"eJxNjcsNADAIQldxAA/96jDE/deoNjTp6QkBBNBVVqhgqcxiaiM7/au3yiZvflS+DjiT6bRi41I2x98wLrwPmfOIOJqaHUk=",
"q":"eJxFzMkNADEIA8BWXACPBHIUg+i/jcSRV/sahGxnphuiDNkNg87fLhd1eXOTBns8+Gk8tsGVDPmWhhrf0tJyM+yqOptZHUw=",
"r":"eJyLjo420lEwjdVRANFmUNoIRBtD+SYgGsSINtRRMAAxDKAqgLQxVKVJbGwsAJezEUA=",
"h":"eJyLjo421VEwitVRANGmINpER8EMRBtDaUOouCFUHZC2iAUxog10FAyRZYx0FIyhOk2gJiFMjI0FACN3GCY=",
"s":"eJw1jMERACEIA1uhAB6KyhWTof82Tp31FTJZVlJ3i3LTJJfboM+TQW5u0ZM96xxqbh0iIJ5hYLyGxPDdz/oBuAwblQ==",
"v":"eJyLjo421VEwjdVRANFmINpYR8EIRBtCxQ1B4iAGiGUAYhhAVRjpKBhDdZjExsYCAJidEUM=",
"u":"eJyLjo421VEwitVRANFmINoIyjfUUTCG0maxIAaIZQBiGEBVGEFVGOsomMTGxgIAlzYRPQ==",
"w":"eJyLjo420VEwitVRiDbVUTAD0cY6CiYg2ggqbggSBzFALAMQwwAqA1RhDNcRGwsAl5URPw==",
"y":"eJxNjMkNADAIw1ZhAB4tvYaJsv8aBSmV+jIgbADhNuiG7jaLS3tyi1Gcbl1/j5s1oOmSpVYcMkLFNNdfTPOQvLbdG4k=",
"x":"eJyLjo421VEwitVRiDbWUTAB0YZQPpA2A9GmIBrEAIkYoKkwBtEmOgqGsbGxAJcwET0=",
"z":"eJyLjo421FEwi9VRiDaF0kC+EZRvFAtigEQMkGWMdBSMY2NjAaUHDdI=",
"i":"eJyLjo421lEwitVRANFmINoIRIMY0QY6CoYghiFIRWwsAOklCmc=",
"4":"eJyLjo421FEwjdVRANEmINpUR8ECSsP4RrEgRrSBjoIhiGGko2AAoo1BMhCdxlC+SWxsLAAnMBL4",
"2":"eJwtjMkRACAIA1uhAB6CovbC0H8bEievHJDNzKVySyVD5UCd2Zi739D+C/YO3VAYNANm8NKEyYWRsLj4hANi1QO/+xuk",
"7":"eJyLjo421FGwiNVRANHmINoUyjfWUTCB0kaxIEa0gY6CIYhhpKNgAKWN4SpjYwGZLBFG",
".":"eJyLjo421lEwidVRiDbRUTCOBTGiDXQUDGNjYwFkPQb8",
",":"eJyLjo421lEwidVRiDbSUTCKBTGiDXQUDGNjYwFkDQb5",
"/":"eJyLjo421FEwitVRiDbVUbCIBTGiDXQUDGNjYwFkNgb+",
"?":"eJxNjcsNwDAIQ1fxABxKAvnMgth/jYbIlXp6yMZ2RKhgpiCaYBWdHNQPB3UvdoGRrWgCZb5nHfFQUX5c55c0Njmbv6XJ5cOdmS+XviKV",
"&":"eJxFjssNwCAMQ1fxAD6Q8CnMErH/Gm2QUU+PBOs5EeHEs4moxBRHsmlvREt2oiYH4WJX7swuWubyEYUw/RSZb/IaXRziaZwyL11kn2ipysrfsV+YcynA",
"'":"eJyLjo420VGwiNVRiDbWUTCLBTGiDXQUDGNjYwFkxwcD",
"+":"eJyLjo421lEwjdVRANHGUNocRJtCxQ1BNIgRbaCjYAhiGOkoGIBoA6gOIG0SGxsLAJhLET8=",
"-":"eJyLjo421VEwjdVRiDYE0SBGtIGOgmFsbCwAZG4G/g==",
"*":"eJxFy8ENgFAIA9BVGIADiIi7kO6/hjZp8k+vtGF3y63hRksObfUpW3uefXQXGDbckuFyCxr6+L1ly0eOfAF8iRke+Q==",
"Null":"eJyLjo421VGwiNVRANFGINoQyjcE8UGMaAMdBUMQwwCqwkhHwRhEG4PEY2MBGWwPiQ==",
"_":"eJyLjo421VEwitVRiDYE0SAGiGUQGxsLAGQFBvg=",
"!":"eJyLjo420lEwjtVRiDbWUTAC0SY6CoZQvkUsiBFtqKNgAGIYIWQMY2NjAaRNDc8=",
"#":"eJxNTcERwDAIWsUBfKg1aTqL5/5rNObo2RcgAhHhTDOZCr3QWq/CAW3tn7v23aAdOUPOs0gIkxYROBeT4FPQMP7Ldzec3IK/9YMllY9oZr7nAStl",
")":"eJyLjo421lGwiNVRiDbRUTCH0sYgGihuFAtiRBvqKBiAGIYgEbhMLACmRA3Y",
"(":"eJyLjo420VGwiNVRiDbWUTCH0sYgGihuFAtiRBvqKBiAGIYgEYgKoEwsAKZEDdg=",
"@":"eJxdjssNwDAIQ1dhAA51/pklYv81CshRpZ5ecMhzzjldpZlKcAYb58v6zYNzsqj037vFPAnmzsq8cL9YHM6jAt48NBeaKjcvs3HQOPkjsGmp7OB2X4oQ5lTDd5BSeAtSB+9BM7MXduc3Ig==",
"<":"eJyLjo420VEwj9VRiDbUUTAB0UC+YSyIARIxADGMwCKxAOlYCmY=",
">":"eJyLjo420lGwiNVRiDbVUTAF0UC+USyIEW2oo2AAFTGMjY0FAOmyCmk=",
"^":"eJyLjo421FEwi9VRiDbWUbAA0aYgPogBkjEAMYx0FAxjY2MB6fgKbg==",
}

context = bpy.context
def shepekey_control_bones(key_blocks):
    # シェイプキー名でコントロールボーンを作成
    # 各ボーン間の距離
    shape_offset = 0.1
    # ボーンの長さ
    bone_length = 0.05
    # 一覧の開始位置
    start_pos = [1.0 ,0 ,1.5]
    # アーマチュアの作成
    armature_data = bpy.data.armatures.new("shape_contrl")
    armature_obj = bpy.data.objects.new('shape_contrl', armature_data)
    context.scene.collection.objects.link(armature_obj)
    bpy.context.view_layer.objects.active = armature_obj
    bpy.ops.object.mode_set(mode = 'EDIT')
    edit_bones = armature_data.edit_bones
    contrl_bones_name = []
    for i,key in enumerate(key_blocks):
        if key.name == "Basis": continue
        # コントロールボーン
        bone_1 = edit_bones.new( key.name )
        bone_1.head = [start_pos[0], start_pos[1], start_pos[2] -i *shape_offset +bone_length]        
        bone_1.tail = [start_pos[0], start_pos[1], start_pos[2] -i *shape_offset]
        bone_1.use_deform = False
        contrl_bones_name.append( key.name )
        # コントロールのシェイプのボーン
        bone_2 = edit_bones.new( "WG_" +key.name )
        bone_2.head = [start_pos[0], start_pos[1], start_pos[2] -i *shape_offset]
        bone_2.tail = [start_pos[0], start_pos[1], start_pos[2] -i *shape_offset +bone_length]
        bone_2.use_deform = False
    bpy.ops.object.mode_set(mode = 'OBJECT')
    for bone_name in contrl_bones_name:
        # シェイプの設定
        wg_name = "WG_" +bone_name
        wg_bone = armature_obj.pose.bones[wg_name]
        wg_obj = criate_scaleshape(bone_name)
        # シェイプボーンの移動をロック
        wg_bone.custom_shape = wg_obj
        wg_bone.lock_location = [True, True, True]
        wg_bone.lock_rotation = [True, True, True]
        wg_bone.lock_scale = [True, True, True]
        # コントロールボーンの移動をロック
        ct_bone = armature_obj.pose.bones[ bone_name ]
        ct_bone.lock_location = [False, True, True]
        ct_bone.lock_rotation = [True, True, True]
        ct_bone.lock_scale = [True, True, True]
    return(armature_obj)

def unzip_mesh_str(text):
    # 圧縮メッシュデータの展開
    bin = base64.b64decode(text)
    mesh_str = zlib.decompress(bin).decode()
    return json.loads(mesh_str)

def criate_scaleshape(str):
    # シェイプボーン用の目盛り用シェイプデータの作成
    mesh = bpy.data.meshes.new(str)
    obj = bpy.data.objects.new(str, mesh)
    # 文字の大きさ
    scale = 0.15
    # スケール部分のデータ
    vertices = [[-0.1, 0.2, 0.0], [-0.1, 0.5, 0.0], [0.0, 0.5, 0.0], [0.0, 0.2, 0.0], 
               [5.0, 0.2, 0.0], [5.0, 0.5, 0.0], [5.1, 0.5, 0.0], [5.1, 0.2, 0.0], [2.5, 0.1, 0.0], [2.5, 0.4, 0.0]]
    edges = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [8, 9]]
    vertex_count = 10
    # シェイプキー名の文字列データの作成
    for i,s in enumerate(str):
        if s in mesh_dic_zip:
            (v_list, e_list) =  unzip_mesh_str(mesh_dic_zip[s])
        else:
            (v_list, e_list) = unzip_mesh_str(mesh_dic["Null"])
        offset = 5 *i *scale +5.4
        vertices += [(float(v[0]) *scale + offset, float(v[1] *scale), 0) for v in v_list]
        edges += [[e[0] +vertex_count, e[1] +vertex_count]  for e in e_list]
        vertex_count += len(v_list)
    # メッシュの作成
    mesh.from_pydata(vertices, edges,[])
    mesh.update()
    return(obj)

def set_bone_driver(key_blocks, armature_obj):
    # ボーンとシェイプキーの対応付け
    for key in key_blocks:
        if key.name in armature_obj.data.bones.keys():
            prop = key.driver_add("value")
            #スクリプトタイプのドライバ
            prop.driver.type =  'SCRIPTED'
            #ドライバ変数の作成
            var = prop.driver.variables.new()
            var.name = 'Var'
            var.type = 'TRANSFORMS'
            # ドライバのターゲットの設定
            var.targets[0].id = armature_obj
            var.targets[0].bone_target = key.name
            # ポーズモードのX軸移動で移動
            var.targets[0].transform_type = 'LOC_X'
            var.targets[0].transform_space = 'LOCAL_SPACE'
            # ドライバー式
            prop.driver.expression = 'Var*4'

def set_bone_limit_loc(key_blocks, armature_obj):
    # コントロールボーンに位置制限のコンストレイントの設定
    for key in key_blocks:
        if key.name in armature_obj.pose.bones.keys():
            pose_bone = armature_obj.pose.bones[ key.name ]
            constraint = pose_bone.constraints.new('LIMIT_LOCATION')
            constraint.use_min_x = True
            constraint.min_x = 0.0
            constraint.use_max_x = True
            constraint.max_x = 0.25
            constraint.use_transform_limit = True
            constraint.owner_space = 'LOCAL'


if context.active_object.type == 'MESH':
    mesh = context.active_object.data
    key_blocks = mesh.shape_keys.key_blocks
    # アーマチュアの作成
    armature_obj = shepekey_control_bones(key_blocks)
    # ドライバーの設定
    set_bone_driver(key_blocks, armature_obj)
    # コントロールボーンに位置制限のコンストレイントの設定
    set_bone_limit_loc(key_blocks, armature_obj)

スクリプトでは新しいアーマチュアオブジェクトで作成しますが
既存のボーンに統合しても ドライバの設定は引き継がれるようです

既存のドライバがある場合やボーンを作り直したい場合の処理を作成していませので
作り直す場合には既存のドライバは削除してください
ドライバエディタでKeyの項目名を選択して削除をすると 選択した項目名のドライバを削除できます
image.png

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?