Skip to content

Instantly share code, notes, and snippets.

@green224
Last active August 22, 2020 10:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save green224/693d68ce500949e3b428434c7ce7cd97 to your computer and use it in GitHub Desktop.
Save green224/693d68ce500949e3b428434c7ce7cd97 to your computer and use it in GitHub Desktop.
BlenderでボーンをSwing-Twist分解するリギングを行うためのアドオン
"""
振り(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