Last active
August 22, 2020 10:43
-
-
Save green224/693d68ce500949e3b428434c7ce7cd97 to your computer and use it in GitHub Desktop.
BlenderでボーンをSwing-Twist分解するリギングを行うためのアドオン
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
振り(swing)と捻じれ(twist)にボーンを分解してリグを組むための | |
機能を追加するアドオン。 | |
Swing-Twist分解は、腕や足などのボーンによく使用されるが、 | |
デフォルトの機能のみではこれをリギングできない。 | |
・CopyRotationでEulerXZ/Yに分解する | |
→ Swing-Twistではないので、角度が大きくなると破綻する | |
・DampedTrack & CopyRotation | |
→ 一見再現できているように見えるが、実行順の影響か | |
ワンテンポ回転の反映が遅れる | |
このアドオンでは、ちゃんとSwing-Twist分解した結果を | |
Driverから直接指定することで、Swing-Twistを実現する。 | |
・・・が、結局1フレーム反映が遅れる | |
使い方 | |
1.ポーズモードで、ハンドリング用ボーン・Swingボーン・Twistボーンの三つを選択した状態にする | |
2.ツールシェルフからSTBタブを選択 | |
3.Src/Swing/Twistの項目に、候補となるボーン名が表示されているので、 | |
問題が無ければExecuteボタンを押す。 | |
""" | |
import bpy | |
import os | |
import subprocess | |
import bmesh | |
import mathutils | |
import math | |
from bpy_extras.io_utils import ImportHelper | |
from bpy.app.handlers import persistent | |
from bpy.types import ( | |
Operator, | |
Panel, | |
PropertyGroup, | |
OperatorFileListElement, | |
) | |
from bpy.props import ( | |
BoolProperty, | |
PointerProperty, | |
StringProperty, | |
CollectionProperty, | |
FloatProperty, | |
EnumProperty, | |
IntProperty, | |
) | |
# プラグインに関する情報 | |
bl_info = { | |
"name" : "Swing Twist Bone", | |
"author" : "Shu", | |
"version" : (0,4), | |
'blender': (2, 80, 0), | |
"location": "View3D > Toolshelf", | |
"description" : "Add the swing twist bone module", | |
"warning" : "", | |
"wiki_url" : "", | |
"tracker_url" : "", | |
"category" : "3D View" | |
} | |
#------------------------------------------------------- | |
# 現在選択しているボーンから、srcBone,swingBone,twistBone候補を抜き出して返す | |
def getSelectedSTBones(): | |
srcBone = None | |
swingBone = None | |
twistBone = None | |
selectedPoseBones = bpy.context.selected_pose_bones | |
for bone in selectedPoseBones: | |
if bone.parent is not None and bone.parent in selectedPoseBones: | |
twistBone = bone | |
for bone in selectedPoseBones: | |
if bone.parent is not None and not (bone.parent in selectedPoseBones): | |
if twistBone is not None and twistBone.parent == bone: | |
swingBone = bone | |
else: | |
srcBone = bone | |
return srcBone, swingBone, twistBone | |
class Swing_Twist_Bone(Operator): | |
bl_idname = "object.swing_twist_bone" | |
bl_label = "SwingTwistBone" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
srcBone,swingBone,twistBone = getSelectedSTBones() | |
swingBone.rotation_mode = 'QUATERNION' | |
twistBone.rotation_mode = 'XYZ' | |
paramStr = '("' + bpy.context.active_object.name + '","' + srcBone.name + '")' | |
# Locationのドライバーを設定 | |
d = swingBone.driver_add("location",0).driver | |
d.expression = "getBoneLocX" + paramStr | |
d = swingBone.driver_add("location",1).driver | |
d.expression = "getBoneLocY" + paramStr | |
d = swingBone.driver_add("location",2).driver | |
d.expression = "getBoneLocZ" + paramStr | |
# Scaleのドライバーを設定 | |
d = swingBone.driver_add("scale",0).driver | |
d.expression = "getBoneSclX" + paramStr | |
d = swingBone.driver_add("scale",1).driver | |
d.expression = "getBoneSclY" + paramStr | |
d = swingBone.driver_add("scale",2).driver | |
d.expression = "getBoneSclZ" + paramStr | |
# Swingのドライバーを設定 | |
d = swingBone.driver_add("rotation_quaternion",0).driver | |
d.expression = "getBoneSwingW" + paramStr | |
d = swingBone.driver_add("rotation_quaternion",1).driver | |
d.expression = "getBoneSwingX" + paramStr | |
d = swingBone.driver_add("rotation_quaternion",2).driver | |
d.expression = "getBoneSwingY" + paramStr | |
d = swingBone.driver_add("rotation_quaternion",3).driver | |
d.expression = "getBoneSwingZ" + paramStr | |
# Twistのドライバーを設定 | |
d = twistBone.driver_add("rotation_euler",1).driver | |
d.expression = "getBoneTwist" + paramStr | |
return {'FINISHED'} | |
# 現在選択しているボーンを、とりあえずSwingBoneとしてのDriverを設定する機能。 | |
# 命名規則は HDL_ボーン名が元になるというので固定。 | |
class Quick_Make_Swing_Bone(Operator): | |
bl_idname = "object.quick_make_swing_bone" | |
bl_label = "QuickMakeSwingBone" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
srcBone,swingBone,twistBone = getSelectedSTBones() | |
swingBone = srcBone | |
swingBone.rotation_mode = 'QUATERNION' | |
paramStr = '("' + bpy.context.active_object.name + '","HDL_"+self.name)' | |
d = swingBone.driver_add("rotation_quaternion",0).driver | |
d.expression = "getBoneSwingW" + paramStr | |
d.use_self = True | |
d = swingBone.driver_add("rotation_quaternion",1).driver | |
d.expression = "getBoneSwingX" + paramStr | |
d.use_self = True | |
d = swingBone.driver_add("rotation_quaternion",2).driver | |
d.expression = "getBoneSwingY" + paramStr | |
d.use_self = True | |
d = swingBone.driver_add("rotation_quaternion",3).driver | |
d.expression = "getBoneSwingZ" + paramStr | |
d.use_self = True | |
return {'FINISHED'} | |
class VIEW3D_PT_Swing_Twist_Bone_panel(Panel): | |
bl_label = "Swing Twist Bone" | |
bl_space_type = "VIEW_3D" | |
bl_region_type = "UI" | |
bl_category = "STB" | |
@classmethod | |
def poll(self, context): | |
return (context.object is not None and (context.object.mode == 'POSE') and (context.active_object.type == 'ARMATURE')) | |
def draw(self, context): | |
layout = self.layout | |
if context.object is not None: | |
srcBone,swingBone,twistBone = getSelectedSTBones() | |
column = layout.column() | |
column.label(text="src : " + ("" if srcBone is None else srcBone.name)) | |
column.label(text="swing : " + ("" if swingBone is None else swingBone.name)) | |
column.label(text="twist : " + ("" if twistBone is None else twistBone.name)) | |
row = column.row() | |
row.enabled = srcBone is not None and swingBone is not None and twistBone is not None | |
row.operator("object.swing_twist_bone", text="Execute") | |
layout.separator() | |
column = layout.column() | |
column.label(text="quick make swing bone") | |
column.label(text="tgt : " + ("" if srcBone is None else srcBone.name)) | |
row = column.row() | |
row.enabled = srcBone is not None | |
row.operator("object.quick_make_swing_bone", text="Execute") | |
layout.separator() | |
else: | |
row = layout.row() | |
row.label(text=" ") | |
classes = ( | |
VIEW3D_PT_Swing_Twist_Bone_panel, | |
Swing_Twist_Bone, | |
Quick_Make_Swing_Bone, | |
) | |
#------------------------------------------------------- | |
# Constraint適応後の最終的なローカル変換マトリクスを得る | |
def getLocalMtx(amtName, boneName): | |
amt = bpy.data.objects[amtName] | |
# rotation_quaternionなどでは、Constraint適応後の角度が取れないため、 | |
# 全適応後のmatrixを直接参照して、そこからボーン回転量を算出する | |
mtxPP = amt.pose.bones[boneName].parent.matrix.copy() | |
mtxPD = amt.data.bones[boneName].parent.matrix_local.copy() | |
mtxPP.invert() | |
mtxPD.invert() | |
mtxP = mtxPP @ amt.pose.bones[boneName].matrix.copy() | |
mtxD = mtxPD @ amt.data.bones[boneName].matrix_local.copy() | |
mtxD.invert() | |
mtx = mtxD @ mtxP | |
return mtx | |
def getBoneSwingTwist(amtName, boneName, rate): | |
mtx = getLocalMtx(amtName, boneName) | |
rot = mtx.to_quaternion() | |
rot = mathutils.Quaternion().slerp(rot, rate) | |
return rot.to_swing_twist("Y") | |
def getBoneLocX(amtName, boneName): | |
return getLocalMtx(amtName, boneName).to_translation().x | |
def getBoneLocY(amtName, boneName): | |
return getLocalMtx(amtName, boneName).to_translation().y | |
def getBoneLocZ(amtName, boneName): | |
return getLocalMtx(amtName, boneName).to_translation().z | |
def getBoneSclX(amtName, boneName): | |
return getLocalMtx(amtName, boneName).to_scale().x | |
def getBoneSclY(amtName, boneName): | |
return getLocalMtx(amtName, boneName).to_scale().y | |
def getBoneSclZ(amtName, boneName): | |
return getLocalMtx(amtName, boneName).to_scale().z | |
def getBoneSwingX(amtName, boneName): | |
return getBoneSwingTwist(amtName, boneName, 1)[0].x | |
def getBoneSwingY(amtName, boneName): | |
return getBoneSwingTwist(amtName, boneName, 1)[0].y | |
def getBoneSwingZ(amtName, boneName): | |
return getBoneSwingTwist(amtName, boneName, 1)[0].z | |
def getBoneSwingW(amtName, boneName): | |
return getBoneSwingTwist(amtName, boneName, 1)[0].w | |
def getBoneTwist(amtName, boneName): | |
return getBoneSwingTwist(amtName, boneName, 1)[1] | |
def getBoneSwingX2(amtName, boneName, rate): | |
return getBoneSwingTwist(amtName, boneName, rate)[0].x | |
def getBoneSwingY2(amtName, boneName, rate): | |
return getBoneSwingTwist(amtName, boneName, rate)[0].y | |
def getBoneSwingZ2(amtName, boneName, rate): | |
return getBoneSwingTwist(amtName, boneName, rate)[0].z | |
def getBoneSwingW2(amtName, boneName, rate): | |
return getBoneSwingTwist(amtName, boneName, rate)[0].w | |
def getBoneTwist2(amtName, boneName, rate): | |
return getBoneSwingTwist(amtName, boneName, rate)[1] | |
@persistent | |
def load_handler(dummy): | |
# Add variable defined in this script into the drivers namespace. | |
bpy.app.driver_namespace["getBoneLocX"] = getBoneLocX | |
bpy.app.driver_namespace["getBoneLocY"] = getBoneLocY | |
bpy.app.driver_namespace["getBoneLocZ"] = getBoneLocZ | |
bpy.app.driver_namespace["getBoneSclX"] = getBoneSclX | |
bpy.app.driver_namespace["getBoneSclY"] = getBoneSclY | |
bpy.app.driver_namespace["getBoneSclZ"] = getBoneSclZ | |
bpy.app.driver_namespace["getBoneSwingX"] = getBoneSwingX | |
bpy.app.driver_namespace["getBoneSwingY"] = getBoneSwingY | |
bpy.app.driver_namespace["getBoneSwingZ"] = getBoneSwingZ | |
bpy.app.driver_namespace["getBoneSwingW"] = getBoneSwingW | |
bpy.app.driver_namespace["getBoneTwist"] = getBoneTwist | |
bpy.app.driver_namespace["getBoneSwingX2"] = getBoneSwingX2 | |
bpy.app.driver_namespace["getBoneSwingY2"] = getBoneSwingY2 | |
bpy.app.driver_namespace["getBoneSwingZ2"] = getBoneSwingZ2 | |
bpy.app.driver_namespace["getBoneSwingW2"] = getBoneSwingW2 | |
bpy.app.driver_namespace["getBoneTwist2"] = getBoneTwist2 | |
#------------------------------------------------------- | |
# プラグインをインストールしたときの処理 | |
def register(): | |
for cls in classes: | |
bpy.utils.register_class(cls) | |
bpy.app.handlers.load_post.append(load_handler) | |
# プラグインをアンインストールしたときの処理 | |
def unregister(): | |
for cls in reversed(classes): | |
bpy.utils.unregister_class(cls) | |
bpy.app.handlers.load_post.remove(load_handler) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment