5798 lines
242 KiB
Python
5798 lines
242 KiB
Python
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
|
|
|
|
import bpy
|
|
#from . import Rigger_Toolbox
|
|
from mathutils import Matrix
|
|
import math
|
|
import mathutils
|
|
import numpy as np
|
|
import random
|
|
from . import Tools
|
|
import inspect
|
|
# import time
|
|
|
|
def draw_wgt(scale, bone, shape = 'sphere'):
|
|
if shape == 'BONE':
|
|
return None
|
|
if shape == 'ORIGINAL':
|
|
|
|
return bone.custom_shape
|
|
shape_obj_name = 'WGTB_' + bone.name
|
|
shape_name = 'WGTB_' + shape.lower() + '_' + bone.name
|
|
|
|
#check if the object and shape already exists
|
|
if shape_obj_name in bpy.data.objects:
|
|
shape_obj = bpy.data.objects[shape_obj_name]
|
|
#check if the shape exists
|
|
if shape_obj.data.name == shape_name:
|
|
return shape_obj
|
|
elif shape_name in bpy.data.meshes:
|
|
mesh = bpy.data.meshes[shape_name]
|
|
shape_obj.data = mesh
|
|
return shape_obj
|
|
else:
|
|
mesh = bpy.data.meshes.new(shape_name)
|
|
shape_obj.data = mesh
|
|
else:
|
|
if shape_name in bpy.data.meshes:
|
|
bpy.data.meshes.remove(bpy.data.meshes[shape_name])
|
|
mesh = bpy.data.meshes.new(shape_name)
|
|
shape_obj = bpy.data.objects.new(shape_obj_name, mesh)
|
|
|
|
#coordinates of the different widget shape
|
|
arrows = {'vertices': [(0.0, 0.0, -1.1981923897508295e-08), (0.0, 0.0, 0.40204665064811707), (0.0, 0.40204665064811707, -1.1981923897508295e-08), (0.40204665064811707, 0.0, -1.1981923897508295e-08), (-0.008419156074523926, 0.008419156074523926, 0.39362743496894836), (-0.008419156074523926, 0.008419156074523926, 0.4104657769203186), (-0.008419185876846313, -0.008419156074523926, 0.39362743496894836), (-0.008419156074523926, -0.008419156074523926, 0.4104657769203186), (0.008419156074523926, 0.008419156074523926, 0.39362743496894836), (0.008419156074523926, 0.008419156074523926, 0.4104657769203186), (0.008419156074523926, -0.008419156074523926, 0.39362743496894836), (0.008419156074523926, -0.008419156074523926, 0.4104657769203186), (0.39362743496894836, 0.008419156074523926, -0.008419156074523926), (0.39362743496894836, 0.008419156074523926, 0.008419156074523926), (0.39362743496894836, -0.008419156074523926, -0.008419156074523926), (0.39362743496894836, -0.008419156074523926, 0.008419156074523926), (0.4104657769203186, 0.008419156074523926, -0.008419156074523926), (0.4104657769203186, 0.008419156074523926, 0.008419156074523926), (0.4104657769203186, -0.008419156074523926, -0.008419156074523926), (0.4104657769203186, -0.008419156074523926, 0.008419156074523926), (-0.008419156074523926, 0.4104657769203186, -0.008419167250394821), (-0.008419156074523926, 0.4104657769203186, 0.008419143967330456), (-0.008419156074523926, 0.39362743496894836, -0.008419167250394821), (-0.008419156074523926, 0.39362743496894836, 0.008419143967330456), (0.008419156074523926, 0.4104657769203186, -0.008419167250394821), (0.008419156074523926, 0.4104657769203186, 0.008419143967330456), (0.008419156074523926, 0.39362743496894836, -0.008419167250394821), (0.008419156074523926, 0.39362743496894836, 0.008419143967330456)], 'edges': [[2, 0], [0, 1], [0, 3], [6, 4], [4, 5], [5, 7], [7, 6], [10, 6], [7, 11], [11, 10], [8, 10], [11, 9], [9, 8], [4, 8], [9, 5], [14, 12], [12, 13], [13, 15], [15, 14], [18, 14], [15, 19], [19, 18], [16, 18], [19, 17], [17, 16], [12, 16], [17, 13], [22, 20], [20, 21], [21, 23], [23, 22], [26, 22], [23, 27], [27, 26], [24, 26], [27, 25], [25, 24], [20, 24], [25, 21]], 'faces': []}
|
|
plain_axes = {'vertices': [(0.0, 0.0, 0.40140801668167114), (0.0, 0.0, -0.40140801668167114), (0.40140801668167114, 0.0, 0.0), (-0.40140801668167114, 0.0, 0.0), (0.0, -0.40140801668167114, 0.0), (0.0, 0.40140801668167114, 0.0), (0.0, 0.0, 0.0)], 'edges': [[6, 5], [4, 6], [6, 1], [6, 3], [0, 6], [2, 6]], 'faces': []}
|
|
single_arrow = {'vertices': [(0.0, 1.3104262563956581e-08, 0.2997905910015106), (0.0, 0.0, 0.0), (0.011538410559296608, 0.013322020880877972, 0.29990124702453613), (0.011538410559296608, -0.013321994803845882, 0.29990124702453613), (0.0, 1.7448547495746425e-08, 0.3991762399673462), (-0.011538410559296608, 0.013322020880877972, 0.29990124702453613), (-0.011538410559296608, -0.013321994803845882, 0.29990124702453613)], 'edges': [[0, 1], [2, 4], [2, 3], [5, 2], [3, 6], [5, 6], [6, 4], [4, 5], [4, 3]], 'faces': []}
|
|
circle = {'vertices': [(1.3969838619232178e-09, -1.7606295088512525e-08, -0.4027850925922394), (-0.1541391760110855, -1.6266094249317575e-08, -0.37212488055229187), (-0.2848120629787445, -1.2449531183733598e-08, -0.2848120629787445), (-0.37212488055229187, -6.737637558984488e-09, -0.1541391760110855), (-0.4027850925922394, 5.551115123125783e-17, 0.0), (-0.37212488055229187, 6.737637558984488e-09, 0.1541391760110855), (-0.2848120629787445, 1.2449531183733598e-08, 0.2848120629787445), (-0.1541391760110855, 1.6266094249317575e-08, 0.37212488055229187), (1.3969838619232178e-09, 1.7606295088512525e-08, 0.4027850925922394), (0.1541391760110855, 1.6266094249317575e-08, 0.37212488055229187), (0.2848120629787445, 1.2449531183733598e-08, 0.2848120629787445), (0.37212488055229187, 6.737637558984488e-09, 0.1541391760110855), (0.4027850925922394, 5.551115123125783e-17, 0.0), (0.37212488055229187, -6.737637558984488e-09, -0.1541391760110855), (0.2848120629787445, -1.2449531183733598e-08, -0.2848120629787445), (0.1541391760110855, -1.6266094249317575e-08, -0.37212488055229187)], 'edges': [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4], [6, 5], [7, 6], [8, 7], [9, 8], [10, 9], [11, 10], [12, 11], [13, 12], [14, 13], [15, 14], [0, 15]], 'faces': []}
|
|
cube = {'vertices': [(-0.4013901352882385, -0.4013901352882385, -0.4013901352882385), (-0.4013901352882385, -0.4013901352882385, 0.4013901352882385), (-0.4013901352882385, 0.4013901352882385, 0.4013901352882385), (-0.4013901352882385, 0.4013901352882385, -0.4013901352882385), (0.4013901352882385, -0.4013901352882385, -0.4013901352882385), (0.4013901352882385, -0.4013901352882385, 0.4013901352882385), (0.4013901352882385, 0.4013901352882385, 0.4013901352882385), (0.4013901352882385, 0.4013901352882385, -0.4013901352882385)], 'edges': [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]], 'faces': []}
|
|
sphere = {'vertices': [(0.0, 0.40137654542922974, 0.0), (-0.10388384014368057, 0.3876999318599701, 0.0), (-0.20068827271461487, 0.34760206937789917, 0.0), (-0.2838158905506134, 0.2838158905506134, 0.0), (-0.34760230779647827, 0.20068813860416412, 0.0), (-0.3876999318599701, 0.10388384014368057, 0.0), (-0.40137654542922974, 3.030309159157696e-08, 0.0), (-0.3876999318599701, -0.10388379544019699, 0.0), (-0.34760230779647827, -0.20068813860416412, 0.0), (-0.2838161289691925, -0.2838158905506134, 0.0), (-0.20068827271461487, -0.34760206937789917, 0.0), (-0.10388395935297012, -0.3876999318599701, 0.0), (-1.5630175198566576e-07, -0.40137654542922974, 0.0), (0.10388367623090744, -0.3876999318599701, 0.0), (0.20068803429603577, -0.34760230779647827, 0.0), (0.2838158905506134, -0.2838161289691925, 0.0), (0.34760206937789917, -0.20068837702274323, 0.0), (0.387699693441391, -0.10388403385877609, 0.0), (0.40137654542922974, -1.8660485068267008e-07, 0.0), (0.3876999318599701, 0.10388367623090744, 0.0), (0.34760230779647827, 0.20068803429603577, 0.0), (0.2838161289691925, 0.2838158905506134, 0.0), (0.20068851113319397, 0.34760206937789917, 0.0), (0.10388415306806564, 0.387699693441391, 0.0), (0.0, 2.990487502074757e-08, 0.40137654542922974), (-0.10388384014368057, 2.990487502074757e-08, 0.3876999318599701), (-0.20068827271461487, 2.990487502074757e-08, 0.34760206937789917), (-0.2838158905506134, 2.990487502074757e-08, 0.2838158905506134), (-0.34760230779647827, 1.4952437510373784e-08, 0.20068813860416412), (-0.3876999318599701, 7.476218755186892e-09, 0.10388384014368057), (-0.40137654542922974, 3.564938905328222e-15, 3.030309159157696e-08), (-0.3876999318599701, -7.476218755186892e-09, -0.10388379544019699), (-0.34760230779647827, -1.4952437510373784e-08, -0.20068813860416412), (-0.2838161289691925, -2.990487502074757e-08, -0.2838158905506134), (-0.20068827271461487, -2.990487502074757e-08, -0.34760206937789917), (-0.10388395935297012, -2.990487502074757e-08, -0.3876999318599701), (-1.5630175198566576e-07, -2.990487502074757e-08, -0.40137654542922974), (0.10388367623090744, -2.990487502074757e-08, -0.3876999318599701), (0.20068803429603577, -2.990487502074757e-08, -0.34760230779647827), (0.2838158905506134, -2.990487502074757e-08, -0.2838161289691925), (0.34760206937789917, -1.4952437510373784e-08, -0.20068837702274323), (0.387699693441391, -7.476218755186892e-09, -0.10388403385877609), (0.40137654542922974, -1.425975562131289e-14, -1.8660485068267008e-07), (0.3876999318599701, 7.476218755186892e-09, 0.10388367623090744), (0.34760230779647827, 1.4952437510373784e-08, 0.20068803429603577), (0.2838161289691925, 2.990487502074757e-08, 0.2838158905506134), (0.20068851113319397, 2.990487502074757e-08, 0.34760206937789917), (0.10388415306806564, 2.990487502074757e-08, 0.387699693441391), (-2.990487502074757e-08, 1.782469452664111e-15, 0.40137654542922974), (-3.738109199957762e-08, -0.10388384014368057, 0.3876999318599701), (-4.485731253112135e-08, -0.20068827271461487, 0.34760206937789917), (-5.980975004149514e-08, -0.2838158905506134, 0.2838158905506134), (-2.990487502074757e-08, -0.34760230779647827, 0.20068813860416412), (-2.990487502074757e-08, -0.3876999318599701, 0.10388384014368057), (-2.990487502074757e-08, -0.40137654542922974, 3.030309159157696e-08), (-2.990487502074757e-08, -0.3876999318599701, -0.10388379544019699), (0.0, -0.34760230779647827, -0.20068813860416412), (0.0, -0.2838161289691925, -0.2838158905506134), (1.4952437510373784e-08, -0.20068827271461487, -0.34760206937789917), (2.2428656265560676e-08, -0.10388395935297012, -0.3876999318599701), (2.990486080989285e-08, -1.5630175198566576e-07, -0.40137654542922974), (3.738109199957762e-08, 0.10388367623090744, -0.3876999318599701), (4.485731253112135e-08, 0.20068803429603577, -0.34760230779647827), (5.980975004149514e-08, 0.2838158905506134, -0.2838161289691925), (2.990487502074757e-08, 0.34760206937789917, -0.20068837702274323), (2.990487502074757e-08, 0.387699693441391, -0.10388403385877609), (2.990487502074757e-08, 0.40137654542922974, -1.8660485068267008e-07), (2.990487502074757e-08, 0.3876999318599701, 0.10388367623090744), (0.0, 0.34760230779647827, 0.20068803429603577), (0.0, 0.2838161289691925, 0.2838158905506134), (-1.4952437510373784e-08, 0.20068851113319397, 0.34760206937789917), (-2.2428656265560676e-08, 0.10388415306806564, 0.387699693441391), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)], 'edges': [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13], [13, 14], [14, 15], [15, 16], [16, 17], [17, 18], [18, 19], [19, 20], [20, 21], [21, 22], [22, 23], [0, 23], [24, 25], [25, 26], [26, 27], [27, 28], [28, 29], [29, 30], [30, 31], [31, 32], [32, 33], [33, 34], [34, 35], [35, 36], [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 42], [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [24, 47], [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54], [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 60], [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66], [66, 67], [67, 68], [68, 69], [69, 70], [70, 71], [48, 71]], 'faces': []}
|
|
cone = {'vertices': [(-3.0009825735533013e-09, 0.402785062789917, 1.7763568394002505e-15), (0.2848120331764221, 0.2848120331764221, 0.0), (0.402785062789917, 0.0, 0.0), (0.2848120331764221, -0.2848120331764221, 0.0), (-3.0009825735533013e-09, -0.402785062789917, -1.7763568394002505e-15), (-0.2848120331764221, -0.2848120331764221, 0.0), (-0.402785062789917, 0.0, 0.0), (-0.2848120331764221, 0.2848120331764221, 0.0), (-3.0009825735533013e-09, -3.552713678800501e-15, 0.805570125579834)], 'edges': [[0, 1], [0, 8], [8, 1], [1, 2], [8, 2], [2, 3], [8, 3], [3, 4], [8, 4], [4, 5], [8, 5], [5, 6], [8, 6], [6, 7], [8, 7], [7, 0]], 'faces': []}
|
|
|
|
shapes = {'arrows':arrows, 'plain_axes':plain_axes,'single_arrow':single_arrow, 'cube':cube, 'sphere' : sphere , 'circle' : circle, 'cone' : cone}
|
|
shape = shape.lower()
|
|
|
|
mesh.from_pydata(np.array(shapes[shape]['vertices'])*scale , shapes[shape]['edges'], shapes[shape]['faces'])
|
|
shape_obj['WGT_TempCtrl'] = True
|
|
return shape_obj
|
|
|
|
def original_shape_properties(ctrl, posebone, size):
|
|
'''copy all the properties from the original bone shape'''
|
|
for prop in dir(posebone):
|
|
if ctrl.btc.setup == 'POLE' and prop == 'custom_shape_transform':
|
|
continue
|
|
#iterate only over custom shape properties
|
|
if 'custom_shape' not in prop:
|
|
continue
|
|
if not hasattr(ctrl, prop):
|
|
continue
|
|
value = getattr(posebone, prop)
|
|
if 'scale' in prop:
|
|
value = tuple(value * size)
|
|
setattr(ctrl, prop, value)
|
|
|
|
def get_ctrls_from_selection(scene, ids, controllers, id_type = 'org_id'):
|
|
'''choose which bones are relevant based on the enum selection properties'''
|
|
|
|
#if setup_ids is in globals then it is running the operator and should be using selected
|
|
select_filter = 'SELECTED' if 'setup_ids' in globals() else scene.btc.target
|
|
ctrls = []
|
|
|
|
#get the relevant bones
|
|
for rig in controllers:
|
|
if rig.type == 'ARMATURE':
|
|
for ctrl in rig.pose.bones:
|
|
#Filter out Helper bones
|
|
if ctrl.btc.org in {'MCH', 'ORG'}:
|
|
continue
|
|
id = getattr(ctrl.btc, id_type)
|
|
#filter the bones using btc.target
|
|
if select_filter == 'SELECTED':
|
|
if id not in ids:
|
|
continue
|
|
else:
|
|
if not id:
|
|
continue
|
|
ctrls.append(ctrl)
|
|
|
|
elif rig.type == 'EMPTY':
|
|
id = getattr(rig.animtoolbox, 'setup_id')
|
|
# filter the bones using btc.target
|
|
if select_filter == 'SELECTED':
|
|
if id not in ids:
|
|
continue
|
|
else:
|
|
if not id:
|
|
continue
|
|
ctrls.append(rig)
|
|
|
|
return ctrls
|
|
|
|
def tempctrl_shapesize(self, context):
|
|
'''multiple the size of the controller shapes'''
|
|
|
|
scene = context.scene
|
|
org_ids = get_bone_ids(context, id = 'org_id')
|
|
setup_ids = get_bone_ids(context, id = 'setup_id')
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
ctrls = get_ctrls_from_selection(scene, setup_ids, controllers, id_type = 'setup_id')
|
|
ctrl_org_bones = map_ctrl_org_bones(org_ids, controllers, controlled_objs, id_type = 'org_id')
|
|
|
|
if any([ctrl.type == 'EMPTY' for ctrl in controllers]):
|
|
empty_org_bones = map_ctrl_org_bones(setup_ids, controllers, controlled_objs, id_type = 'setup_id')
|
|
|
|
for ctrl in ctrls:
|
|
if ctrl.id_data.type == 'ARMATURE':
|
|
scale = mathutils.Vector([self.shape_size]*3)
|
|
|
|
if ctrl in ctrl_org_bones:
|
|
org_bone = ctrl_org_bones[ctrl]
|
|
if ctrl.custom_shape == org_bone.custom_shape:
|
|
#Multiple the scale with the scale from the original bone
|
|
scale = mathutils.Vector(np.array(org_bone.custom_shape_scale_xyz) * tuple([self.shape_size]*3))
|
|
|
|
#child bones that have the same bone length get half of the size
|
|
if ctrl.btc.child and round(ctrl.id_data.data.bones[ctrl.name].length, 2) == round(ctrl.id_data.data.bones[ctrl.parent.name].length, 2):
|
|
scale *= 0.5
|
|
ctrl.custom_shape_scale_xyz = scale
|
|
|
|
elif ctrl.id_data.type == 'EMPTY':
|
|
org_bone = empty_org_bones[ctrl]
|
|
ctrl.empty_display_size = self.shape_size * org_bone.length * 0.5
|
|
#if it's a child then make it smaller
|
|
if ctrl.animtoolbox.child : ctrl.empty_display_size *= 0.5
|
|
|
|
def tempctrl_shape_type(self, context):
|
|
'''change the display type of the ctrl bone'''
|
|
scene = context.scene
|
|
|
|
setup_ids = get_bone_ids(context, id = 'setup_id')
|
|
org_ids = get_bone_ids(context, id = 'org_id')
|
|
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
ctrls = get_ctrls_from_selection(scene, setup_ids, controllers, id_type = 'setup_id')#
|
|
|
|
if not ctrls:
|
|
return
|
|
#map bone and ctrls
|
|
ctrl_org_bones = map_ctrl_org_bones(setup_ids, controllers, controlled_objs, id_type = 'setup_id')
|
|
|
|
if not len(ctrl_org_bones.values()):
|
|
return
|
|
|
|
length_avg = sum([ctrl.id_data.data.bones[ctrl.name].length for ctrl in ctrl_org_bones.values()]) / len(ctrl_org_bones.values())
|
|
|
|
for ctrl in ctrls:
|
|
if ctrl.id_data.type == 'EMPTY':
|
|
ctrl.empty_display_type = self.shape_type
|
|
continue
|
|
|
|
if self.shape_type == 'ORIGINAL':
|
|
if ctrl not in ctrl_org_bones:
|
|
continue
|
|
org_bone = ctrl_org_bones[ctrl]
|
|
original_shape_properties(ctrl, org_bone , self.shape_size)
|
|
|
|
if ctrl.btc.setup == 'POLE':
|
|
continue
|
|
if org_bone.custom_shape_transform is not None and ctrl.custom_shape_transform is None:
|
|
#check if the transform bone shape already exists
|
|
transform_bonename = org_bone.custom_shape_transform.name
|
|
if transform_bonename in ctrl.id_data.pose.bones:
|
|
ctrl.custom_shape_transform = ctrl.id_data.pose.bones[transform_bonename]
|
|
continue
|
|
#add an extra transform bone, if it doesnt exist
|
|
rig = ctrl.id_data
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
boneshape = add_ctrl_bone(rig, org_bone.custom_shape_transform, rig.data.edit_bones[ctrl.name].parent, '')
|
|
bone_name = boneshape.name
|
|
add_bone_to_collection(rig, boneshape)
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
posebone = rig.pose.bones[bone_name]
|
|
constraint_add(posebone, org_bone.custom_shape_transform, 'COPY_TRANSFORMS')
|
|
ctrl.custom_shape_transform = posebone
|
|
else:
|
|
# org_obj = ctrl.id_data.animtoolbox.controlled
|
|
length = (length_avg / ctrl.length)*0.4 + 0.4
|
|
# ctrl.custom_shape = draw_wgt(length, ctrl_org_bones[ctrl], shape = self.shape_type)
|
|
ctrl.custom_shape = draw_wgt(length, ctrl, shape = self.shape_type)
|
|
# ctrl.use_custom_shape_bone_size = False
|
|
ctrl.custom_shape_scale_xyz = tuple([self.shape_size]*3)
|
|
|
|
ctrl.custom_shape_transform = None
|
|
|
|
#If it's a child then assign the parent shape
|
|
for ctrl in ctrls:
|
|
if ctrl.id_data.type == 'EMPTY':
|
|
continue
|
|
if not ctrl.parent and not ctrl.btc.child:
|
|
continue
|
|
if ctrl.parent in ctrls and ctrl.btc.org_id == ctrl.parent.btc.org_id:
|
|
ctrl.custom_shape = ctrl.parent.custom_shape
|
|
ctrl.custom_shape_transform = ctrl.parent.custom_shape_transform
|
|
|
|
#Get the scale from the parent, if they are the same length then get half of it
|
|
scale = ctrl.parent.custom_shape_scale_xyz.copy()
|
|
rig = ctrl.id_data
|
|
if round(rig.data.bones[ctrl.name].length, 2) == round(rig.data.bones[ctrl.parent.name].length, 2):
|
|
scale *= 0.5
|
|
ctrl.custom_shape_scale_xyz = scale
|
|
|
|
def map_ctrl_org_bones(ids, controllers, controlled_objs, id_type = 'setup_id'):
|
|
'''returns a dictionary of the ctrls and original bones'''
|
|
|
|
ctrl_org_bones = dict()
|
|
ids_org_bone = dict()
|
|
#get the original bones and their ids in a dictionary
|
|
for org_rig in controlled_objs:
|
|
ids_org_bone = {getattr(bone.btc, id_type) : bone for bone in org_rig.pose.bones if getattr(bone.btc, id_type) in ids}
|
|
|
|
for ctrl_rig in controllers:
|
|
if ctrl_rig.type == 'ARMATURE':
|
|
for ctrl_bone in ctrl_rig.pose.bones:
|
|
id = getattr(ctrl_bone.btc, id_type)
|
|
if id not in ids:
|
|
continue
|
|
if id not in ids_org_bone:
|
|
continue
|
|
ctrl_org_bones.update({ctrl_bone : ids_org_bone[id]})
|
|
|
|
elif ctrl_rig.type == 'EMPTY':
|
|
id = getattr(ctrl_rig.animtoolbox, 'setup_id')
|
|
if id not in ids:
|
|
continue
|
|
if id not in ids_org_bone:
|
|
continue
|
|
ctrl_org_bones.update({ctrl_rig : ids_org_bone[id]})
|
|
|
|
return ctrl_org_bones
|
|
|
|
def get_bone_color_sets(context, self):
|
|
# Get the current theme
|
|
theme = bpy.context.preferences.themes[0]
|
|
|
|
# Get all bone color sets from the theme
|
|
color_sets = []
|
|
for i, color_set in enumerate(theme.bone_color_sets, start=1):
|
|
if color_set.normal: # Check if the color set is not empty
|
|
i_str = str(i).zfill(2)
|
|
color_icon_name = "COLORSET_{}_VEC".format(i_str)
|
|
color_set_name = "THEME"+i_str
|
|
text = i_str + ' - Theme Color Set'
|
|
color_sets.append((color_set_name, text, text, color_icon_name, i))
|
|
|
|
return color_sets
|
|
|
|
def update_bone_color(context, self):
|
|
|
|
context = bpy.context
|
|
scene = context.scene
|
|
color_set = scene.btc.color_set
|
|
org_ids = get_bone_ids(context, id = 'org_id')
|
|
setup_ids = get_bone_ids(context, id = 'setup_id')
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
|
|
ctrls = get_ctrls_from_selection(scene, setup_ids, controllers, id_type = 'setup_id')
|
|
|
|
# ctrl_org_bones = map_ctrl_org_bones(setup_ids, controllers, controlled_objs, id_type = 'setup_id')
|
|
for ctrl in ctrls:
|
|
if ctrl.id_data.type == 'EMPTY':
|
|
continue
|
|
ctrl.color.palette = color_set
|
|
|
|
def add_twist_to_track(source, target, constraints):
|
|
|
|
if target.id_data.type == 'ARMATURE':
|
|
#Adding an extra copy rotation for the twist axis
|
|
for rot_bone in target.id_data.pose.bones:
|
|
#Find the org bone to use for local rotation for the twist
|
|
if rot_bone.btc.org == 'ORG' and rot_bone.btc.setup_id == target.btc.setup_id:
|
|
break
|
|
constraint_add(rot_bone, target, 'COPY_ROTATION')
|
|
rot_con = constraint_add(source, rot_bone, 'COPY_ROTATION')
|
|
rot_con.target_space = 'LOCAL'
|
|
rot_con.owner_space = 'LOCAL'
|
|
else:
|
|
rot_con = constraint_add(source, target, 'COPY_ROTATION')
|
|
|
|
constraints.append(rot_con)
|
|
|
|
return rot_con
|
|
|
|
def constraints_filter(self, source, target, con_type = 'COPY_TRANSFORMS'):
|
|
'''add constraints but with attribute filter'''
|
|
constraints = []
|
|
attr_dict = {'x' : 0, 'y' : 1, 'z' : 2}
|
|
|
|
#update all the properties and constraint for the twist before adding track constraints
|
|
if con_type == 'DAMPED_TRACK':
|
|
# twist = False if con_type == 'COPY_TRANSFORMS' else self.twist
|
|
self.filter_location = (False, False, False)
|
|
self.filter_scale = (False, False, False)
|
|
|
|
if self.twist:
|
|
rot_con = add_twist_to_track(source, target, constraints)
|
|
|
|
#Adding Track constraint without any filters
|
|
all_attributes = list(self.filter_location) + list(self.filter_rotation) + list(self.filter_scale)
|
|
if not any(all_attributes) : # or con_type == 'TRACK_TO'
|
|
con = constraint_add(source, target, con_type)
|
|
constraints.append(con)
|
|
return constraints
|
|
|
|
con = None
|
|
|
|
#Adding Constraints with filtered attributes
|
|
if con_type == 'COPY_TRANSFORMS':
|
|
filter_con_type = {'filter_rotation': 'COPY_ROTATION', 'filter_scale' : 'COPY_SCALE', 'filter_location' : 'COPY_LOCATION'}
|
|
for filter, con_type in filter_con_type.items():
|
|
#check if anything is turned on for this attribute
|
|
filter_transform = getattr(self, filter)
|
|
if all(filter_transform):
|
|
continue
|
|
con = constraint_add(source, target, con_type)
|
|
for attr, i in attr_dict.items():
|
|
setattr(con, 'use_' + attr, not filter_transform[i])
|
|
constraints.append(con)
|
|
else:
|
|
#In Case Track to is using Filters
|
|
if any(list(self.filter_rotation)):
|
|
for attr, i in attr_dict.items():
|
|
#Exclude axes that are not part of the twist
|
|
if self.twist and attr != self.axis[1].lower():
|
|
setattr(rot_con,'use_' + attr, False)
|
|
|
|
if self.filter_rotation[i]:
|
|
continue
|
|
#if it's the same axis of the direction axis then skip it
|
|
if attr.upper() in self.axis:
|
|
continue
|
|
|
|
con = constraint_add(source, target, 'LOCKED_TRACK')
|
|
con.lock_axis = ('LOCK_' + attr).upper()
|
|
constraints.append(con)
|
|
|
|
return constraints
|
|
|
|
def constraint_add(source, target, con_type = 'COPY_TRANSFORMS'):
|
|
'''Add a constraint with the source and target, available both to bones and empties'''
|
|
|
|
constraint = source.constraints.new(con_type)
|
|
|
|
if target:
|
|
if hasattr(target, 'bone'):
|
|
constraint.target = target.id_data
|
|
constraint.subtarget = target.name
|
|
else:
|
|
constraint.target = target
|
|
|
|
#Add name suffix if it's not on a control rig object
|
|
# if 'bate' not in constraint.id_data.keys():
|
|
constraint.name = con_type.capitalize() + '_to_Ctrl'
|
|
|
|
return constraint
|
|
|
|
def constraint_ctrl_to_bone(ctrl, bone, bake_loc, con_type, constraints = []):
|
|
constraint = constraint_add(ctrl, bone, con_type)
|
|
if bake_loc == 'TAIL':
|
|
constraint.head_tail = 1
|
|
|
|
constraints.append(constraint)
|
|
|
|
return constraints
|
|
|
|
def constraint_org_to_ctrl(self, ctrls, selected):
|
|
if not self.con_back:
|
|
return
|
|
# con_type = 'DAMPED_TRACK' if self.bake_loc == 'TRACK' else 'COPY_TRANSFORMS'
|
|
if self.bake_loc == 'TRACK':
|
|
# con_type = 'TRACK_TO' if self.twist and 'Y' in self.axis else 'DAMPED_TRACK'
|
|
con_type = 'DAMPED_TRACK'
|
|
else:
|
|
con_type = 'COPY_TRANSFORMS'
|
|
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
if ctrl.btc.setup == 'ROOT': #dont need to add constrain for the root bone
|
|
continue
|
|
if self.child and child_names: #switch to the child controling the original bone
|
|
child_name = child_names[ctrls.index(ctrl)]
|
|
child_ctrl = rig.pose.bones[child_name]
|
|
ctrl = child_ctrl
|
|
|
|
# add constraints but with attribute filter
|
|
constraints = constraints_filter(self, bone, ctrl, con_type)
|
|
if not constraints:
|
|
continue
|
|
for constraint in constraints:
|
|
if constraint.type in {'DAMPED_TRACK', 'TRACK_TO', 'LOCKED_TRACK'}:
|
|
direction = 'NEGATIVE_' if '-' == self.axis[0] else ''
|
|
constraint.track_axis = 'TRACK_' + direction + self.axis[1]
|
|
|
|
add_enabled_driver(bone.id_data, ctrl.id_data, constraint)
|
|
bone.bone.select = False
|
|
|
|
def create_scene_collection(obj):
|
|
#create a collection
|
|
if 'Ctrl_Helpers' not in bpy.data.collections:
|
|
collection = bpy.data.collections.new('Ctrl_Helpers')
|
|
bpy.context.scene.collection.children.link(collection)
|
|
else:
|
|
collection = bpy.data.collections['Ctrl_Helpers']
|
|
if obj not in collection.objects.values():
|
|
collection.objects.link(obj)
|
|
|
|
return collection
|
|
|
|
def register_ctrl(controller, controlled):
|
|
|
|
ctrl_items = bpy.context.scene.btc.ctrl_items
|
|
#check if it's already registered
|
|
controllers = [item.controller for item in ctrl_items]
|
|
if controller in controllers:
|
|
return
|
|
#register the controller in the scene
|
|
ctrl_item = ctrl_items.add()
|
|
ctrl_item.controller = controller
|
|
ctrl_item.controlled = controlled
|
|
|
|
def find_empty_ctrl(context, posebone):
|
|
#Get all the empty ctrls
|
|
empty_ctrls = [ctrl.controller for ctrl in context.scene.btc.ctrl_items if ctrl.controller.type == 'EMPTY']
|
|
for empty in empty_ctrls:
|
|
if posebone.btc.setup_id == empty.animtoolbox.setup_id:
|
|
return empty
|
|
|
|
def add_empty(context, source=None, nameextension = 'Ctrl_'):
|
|
|
|
scene = context.scene
|
|
btc = scene.btc
|
|
|
|
if source:
|
|
empty = bpy.data.objects.new(nameextension + source.name, None)
|
|
else:
|
|
#If it's not assigned from bone or object then create a root at cursor
|
|
empty = bpy.data.objects.new('Root', None)
|
|
source = scene.cursor
|
|
|
|
empty.empty_display_size = btc.shape_size * 0.5
|
|
if hasattr(source, 'length'):
|
|
empty.empty_display_size *= source.length
|
|
empty.empty_display_type = btc.shape_type
|
|
#bpy.context.btc.ctrl_items.controller = empty
|
|
|
|
#for a bone or cursor assign matrix, for empty matrix world
|
|
if hasattr(source, 'matrix'):
|
|
empty.matrix_world = source.matrix.to_4x4()
|
|
elif hasattr(source, 'matrix_world'):
|
|
empty.matrix_world = source.matrix_world.to_4x4()
|
|
|
|
temp_col = create_scene_collection(empty)
|
|
|
|
return empty
|
|
|
|
def add_control_empties(self, context, bones):
|
|
#Add an empty to the scene
|
|
empties = []
|
|
root_empty = None
|
|
setup = 'EMPTY' if self.bake_loc == 'COPY' else 'TRACK_TO_EMPTY'
|
|
setup_ids = set()
|
|
|
|
for bone in bones:
|
|
empty = add_empty(context, bone)
|
|
|
|
#add setup id
|
|
setup_id = add_id(empty, setup)
|
|
setup_ids.add(setup_id)
|
|
bone.btc.setup_id = empty.animtoolbox.setup_id = setup_id
|
|
bone.btc.setup = setup
|
|
|
|
#define controller and controlled
|
|
empty.animtoolbox.controlled = bone.id_data
|
|
register_ctrl(empty, bone.id_data)
|
|
|
|
if root_empty:
|
|
empty.parent = root_empty
|
|
|
|
empties.append(empty)
|
|
|
|
return empties, root_empty, setup_ids
|
|
|
|
def ctrls_orientation(self, ctrls, child_names):
|
|
if not self.orientation:
|
|
return
|
|
|
|
for i, ctrl in enumerate(ctrls):
|
|
ctrl_edit = rig.data.edit_bones[ctrl.name]
|
|
loc, rot, scale = ctrl_edit.matrix.decompose()
|
|
new_rot = mathutils.Euler((0, 0, 0), 'XYZ').to_matrix()
|
|
ctrl_edit.matrix = Matrix.LocRotScale(loc, new_rot, scale)
|
|
|
|
if not self.child:
|
|
continue
|
|
child_edit = rig.data.edit_bones[child_names[i]]
|
|
loc, rot, scale = child_edit.matrix.decompose()
|
|
child_edit.matrix = Matrix.LocRotScale(loc, new_rot, scale)
|
|
|
|
def get_bone_matrix(bone):
|
|
'''check for type of matrix, bone or object'''
|
|
if hasattr(bone, 'matrix_local'):
|
|
matrix = bone.matrix_local
|
|
#Relevant for edit bones
|
|
elif hasattr(bone, 'matrix'):
|
|
matrix = bone.matrix
|
|
#Relevant for object empties
|
|
elif hasattr(bone, 'matrix_world'):
|
|
matrix = bone.matrix_world
|
|
|
|
# if type(bone.id_data) == bpy.types.Object:
|
|
# matrix = bone.id_data.matrix_world @ matrix
|
|
|
|
return matrix
|
|
|
|
def add_ctrl_bone(rig, bone, parent_bone, nameextension = 'Ctrl_', bake_loc = 'HEAD', length_factor = 1, orient_world = False):
|
|
ctrl_bone = rig.data.edit_bones.new(nameextension + bone.name)
|
|
ctrl_bone.parent = parent_bone
|
|
|
|
if bone is None:
|
|
#sctrl_bone.matrix_world = Matrix()
|
|
ctrl_bone.matrix = Matrix()
|
|
else:
|
|
#get the source bone
|
|
bone_rig = bone.id_data
|
|
#check that the bone id data is object and not armature
|
|
if type(bone_rig) == bpy.types.Object:
|
|
bone_rig = bone_rig.data
|
|
if bone.name in bone_rig.edit_bones:
|
|
bone = bone_rig.edit_bones[bone.name]
|
|
#in case the bone still doesn't exist in the edit bones then get it from data.bones,
|
|
# usually when the object is linked
|
|
elif bone.name in bone_rig.bones:
|
|
bone = bone_rig.bones[bone.name]
|
|
|
|
matrix = get_bone_matrix(bone)
|
|
|
|
loc, rot, scale = matrix.decompose()
|
|
# Create a rotation matrix to srotate 90 degrees around the X-axis (bone pointing upwards)
|
|
if orient_world:
|
|
# rot = mathutils.Euler((90 * (3.14159 / 180), 0, 0), 'XYZ').to_matrix()
|
|
rot = mathutils.Euler((0, 0, 0), 'XYZ').to_matrix()
|
|
|
|
#decompose and compose a new matrix at the tail location
|
|
if bake_loc == 'TAIL':
|
|
loc = bone.tail_local if hasattr(bone, 'tail_local') else bone.tail
|
|
|
|
matrix = Matrix.LocRotScale(loc, rot, scale)
|
|
|
|
ctrl_bone.length = bone.length * length_factor
|
|
ctrl_bone.matrix = matrix
|
|
|
|
return ctrl_bone
|
|
|
|
def add_ctrl_rig(bones):
|
|
'''Adding the control rig bones'''
|
|
org_obj = bones[0].id_data
|
|
rigname = 'Controller_' + org_obj.name
|
|
|
|
#check if the rig object exists, if not add a new one
|
|
if rigname not in bpy.data.objects:
|
|
armature = bpy.data.armatures.new('Controller_' + org_obj.data.name)
|
|
rig = bpy.data.objects.new(rigname, object_data = armature)
|
|
else:
|
|
rig = bpy.data.objects[rigname]
|
|
|
|
col = create_scene_collection(rig)
|
|
#stamp the rig with the addon module stamp
|
|
#rig['bate'] = True
|
|
rig.animtoolbox.controlled = org_obj
|
|
org_obj.animtoolbox.controller = rig
|
|
bpy.context.view_layer.objects.active = rig
|
|
rig.show_in_front = bpy.context.preferences.addons[__package__].preferences.in_front
|
|
#register the controller and what is being controlled in the scene btc properties
|
|
register_ctrl(rig, org_obj)
|
|
|
|
return rig, col
|
|
|
|
def move_root_editbone(root_bone):
|
|
scene = bpy.context.scene
|
|
btc = scene.btc
|
|
|
|
#if There's no object applied as a reference for the root then use the cursor
|
|
if not btc.root_object:
|
|
root_bone.matrix = Matrix.Identity(4)
|
|
root_bone.matrix.translation = scene.cursor.matrix.translation
|
|
root_bone.length = 1
|
|
return
|
|
|
|
#get the matrix from the original bone that is used as a root
|
|
if btc.root_object.type == 'ARMATURE' and btc.root_bone in btc.root_object.data.bones:
|
|
root_bone.head = btc.root_object.data.bones[btc.root_bone].head
|
|
root_bone.tail = btc.root_object.data.bones[btc.root_bone].tail
|
|
root_bone.matrix = btc.root_object.data.bones[btc.root_bone].matrix_local
|
|
root_bone.length = btc.root_object.data.bones[btc.root_bone].length
|
|
|
|
elif btc.root_object:
|
|
root_bone.length = 1
|
|
root_bone.matrix = btc.root_object.matrix_world
|
|
|
|
def add_root_bone(rig):
|
|
scene = bpy.context.scene
|
|
btc = scene.btc
|
|
if not btc.root:
|
|
return None
|
|
|
|
#return only the root bone, otherwise it will return empty
|
|
root_bone = None
|
|
root_org_bone = None
|
|
found_root = False
|
|
|
|
#check if the root bone is already in the rig, and if yes then return
|
|
for posebone in rig.pose.bones:
|
|
if posebone.btc.setup != 'ROOT':
|
|
continue
|
|
|
|
#if nothing is used as a ref for the root then use the existing root
|
|
if not btc.root_object:
|
|
found_root = True
|
|
root_bone = rig.data.edit_bones[posebone.name]
|
|
break
|
|
|
|
if btc.root_bone == '':
|
|
#if the current root is in the same matrix of the ref object then use it
|
|
if btc.root_object.matrix_world != posebone.matrix:
|
|
continue
|
|
else:
|
|
org_id = posebone.btc.org_id
|
|
if org_id != btc.root_object.pose.bones[btc.root_bone].btc.org_id:
|
|
continue
|
|
|
|
found_root = True
|
|
root_bone = rig.data.edit_bones[posebone.name]
|
|
root_org_bone = posebone
|
|
|
|
#add a new root bone
|
|
if not root_bone:
|
|
root_bone = rig.data.edit_bones.new(btc.root_bone)
|
|
root_bone.name = 'Temp_Root'
|
|
root_bone.select = True
|
|
|
|
if not found_root:
|
|
move_root_editbone(root_bone)
|
|
|
|
if not btc.root_bone:
|
|
root_bone.select = False
|
|
add_bone_to_collection(rig, root_bone, col_name = 'Root', visible = True)
|
|
|
|
|
|
return root_bone
|
|
|
|
def add_control_bones(self, bones, rig, root_ctrl, child = False, orient_world = False, prefix = 'Ctrl_'):
|
|
|
|
'''Adding all the control bones, ctrl childs and override transform bones during edit mode'''
|
|
for bone in bones:
|
|
bone.id_data.data.bones[bone.name].select = False
|
|
#deselect the original rig in case it's linked
|
|
if rig.mode != 'EDIT':
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
ctrls = []
|
|
child_ctrls = []
|
|
mch_bones = []
|
|
|
|
#root_bone = add_root_bone(rig)
|
|
for bone in bones:
|
|
ctrl_bone = add_ctrl_bone(rig, bone, root_ctrl, prefix, 'HEAD', orient_world = orient_world)
|
|
ctrls.append(ctrl_bone)
|
|
# add_bone_to_collection(rig, ctrl_bone, col_name = 'Org', visible = True)
|
|
|
|
#add bone in case it's using transform from another bone
|
|
if self.shape_type == 'ORIGINAL' and bone.custom_shape_transform is not None:
|
|
bone_shape_transform = add_ctrl_bone(rig, bone.custom_shape_transform, root_ctrl, '', self.bake_loc)
|
|
add_bone_to_collection(rig, bone_shape_transform)
|
|
|
|
#add child controls
|
|
if child:
|
|
child_ctrl = add_ctrl_bone(rig, bone, ctrl_bone, 'Child_' + prefix, 'HEAD', 0.5, orient_world = orient_world)
|
|
child_ctrls.append(child_ctrl)
|
|
add_bone_to_collection(rig, child_ctrl, col_name = 'Child Ctrls', visible = True)
|
|
|
|
ctrl_bone.select = True
|
|
|
|
return ctrls, child_ctrls
|
|
|
|
def add_ctrl_group(rig):
|
|
#Add the bone group if doesn't exist
|
|
bone_groups = rig.pose.bone_groups
|
|
if 'Ctrl Bones' not in bone_groups:
|
|
ctrl_group = bone_groups.new(name = 'Ctrl Bones')
|
|
ctrl_group.color_set = bpy.context.scene.btc.color_set
|
|
# ctrl_group.color_set = 'THEME09'
|
|
else:
|
|
ctrl_group = bone_groups['Ctrl Bones']
|
|
|
|
return ctrl_group
|
|
|
|
def add_bone_setup_id(bone, child = False, setup = 'NONE', org ='CTRL', setup_id = 0, org_id = 0):
|
|
#add the properties to the bone
|
|
# if setup_id and bone.btc.setup_id and setup_id != bone.btc.setup_id:
|
|
# return
|
|
bone.btc.setup_id = setup_id
|
|
bone.btc.org_id = org_id
|
|
bone.btc.setup = setup
|
|
bone.btc.org = org
|
|
bone.btc.child = child
|
|
|
|
|
|
def add_id(rig, setup, org = False):
|
|
'''check if the id already exists, add and return it'''
|
|
#btc = context.scene.btc
|
|
id = random.randint(10**8, (10**9)-1)
|
|
while str(id) in rig.animtoolbox.ctrl_setups:
|
|
id = random.randint(10**8, (10**9)-1)
|
|
if not org:
|
|
id_setup = rig.animtoolbox.ctrl_setups.add()
|
|
id_setup.name = str(id)
|
|
id_setup.setup = setup
|
|
|
|
return id
|
|
|
|
class BtcSelections(bpy.types.Operator):
|
|
"""Select all the bones that are constrained to the empty"""
|
|
bl_idname = "anim.btc_selections"
|
|
bl_label = "Selections"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
# @classmethod
|
|
# def poll(cls, context):
|
|
# return context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
obj = context.object
|
|
scene = context.scene
|
|
objs = [(item.controller, item.controlled) for item in scene.btc.ctrl_items]
|
|
if len(objs) < 1:
|
|
return {'CANCELLED'}
|
|
|
|
scene_ctrl_objs, scene_constrained_objs = zip(*objs)
|
|
scene_constrained_objs = set(scene_constrained_objs)
|
|
|
|
ctrl_objs = set()
|
|
ctrl_bones = set()
|
|
constrained_rigs = set()
|
|
for obj in context.selected_objects:
|
|
if scene.btc.selection in {'RELATIVE_CTRLS', 'CONTROLLERS'}:
|
|
if obj in scene_constrained_objs and obj.type == 'ARMATURE':
|
|
#First collect the selected constrained bones and deselect them
|
|
for bone in obj.data.bones:
|
|
if not bone.select and scene.btc.selection == 'RELATIVE_CTRLS':
|
|
continue
|
|
for con in obj.pose.bones[bone.name].constraints:
|
|
if not '_to_Ctrl' in con.name:
|
|
continue
|
|
ctrl_objs.update({con.target})
|
|
ctrl_bones.update({con.subtarget})
|
|
bone.select = False
|
|
obj.select_set(False)
|
|
|
|
#if current selection is a controller then assign it as well
|
|
elif obj in scene_ctrl_objs:
|
|
ctrl_objs.update({obj})
|
|
if scene.btc.selection == 'RELATIVE_CTRLS' and obj.type == 'ARMATURE':
|
|
for bone in obj.data.bones:
|
|
if bone.select:
|
|
ctrl_bones.update({bone.name})
|
|
|
|
elif scene.btc.selection in {'RELATIVE_CONSTRAINED', 'CONSTRAINED'}:
|
|
if obj in scene_ctrl_objs:
|
|
#First collect the control empties or bones and deselect them
|
|
ctrl_objs.update({obj})
|
|
if obj.type == 'ARMATURE':
|
|
for bone in obj.data.bones:
|
|
if bone.select:
|
|
ctrl_bones.update({bone.name})
|
|
bone.select = False
|
|
obj.select_set(False)
|
|
|
|
constrained_rigs.update({obj.animtoolbox.controlled})
|
|
|
|
#if current selection is a controlled rig then assign it as well
|
|
elif obj in scene_constrained_objs:
|
|
constrained_rigs.update({obj})
|
|
|
|
if scene.btc.selection in {'RELATIVE_CTRLS', 'CONTROLLERS'}:
|
|
#select the relative objects and bones
|
|
for ctrl_obj in ctrl_objs:
|
|
if ctrl_obj is None:
|
|
continue
|
|
ctrl_obj.select_set(True)
|
|
context.view_layer.objects.active = ctrl_obj
|
|
if ctrl_obj.type != 'ARMATURE':
|
|
continue
|
|
#select the bones if it's a control rig
|
|
if context.mode != 'POSE':
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
for ctrl_bone in ctrl_obj.data.bones:
|
|
if scene.btc.selection == 'RELATIVE_CTRLS':
|
|
ctrl_bone.select = True if ctrl_bone.name in ctrl_bones else False
|
|
elif scene.btc.selection == 'CONTROLLERS':
|
|
ctrl_bone.select = True
|
|
|
|
#Reset active bone in selection order to use context.selected_pose_bone[-1].bone
|
|
if context.selected_pose_bones[-1].name in ctrl_obj.pose.bones:
|
|
ctrl_obj.data.bones.active = context.selected_pose_bones[-1].bone
|
|
|
|
elif scene.btc.selection in {'RELATIVE_CONSTRAINED', 'CONSTRAINED'}:
|
|
for constrained_rig in constrained_rigs:
|
|
#iterate over the constrained bones
|
|
constrained_rig.select_set(True)
|
|
context.view_layer.objects.active = constrained_rig
|
|
if constrained_rig.mode != 'POSE':
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
for bone in constrained_rig.data.bones:
|
|
for con in constrained_rig.pose.bones[bone.name].constraints:
|
|
if not '_to_Ctrl' in con.name:
|
|
continue
|
|
if con.target in ctrl_objs and scene.btc.selection == 'RELATIVE_CONSTRAINED':
|
|
#bone.select = True if not con.subtarget or con.subtarget in ctrl_bones else False
|
|
if not con.subtarget or con.subtarget in ctrl_bones:
|
|
bone.select = True
|
|
elif scene.btc.selection == 'CONSTRAINED':
|
|
bone.select = True
|
|
constrained_rig.data.bones.active = bone
|
|
else:
|
|
bone.select = False
|
|
|
|
if bone.select:
|
|
constrained_rig.data.bones.active = bone
|
|
|
|
if hasattr(Tools, 'bone_selection'):
|
|
#Reset selection order in order to use context.selected_pose_bone[-1].bone
|
|
Tools.bone_selection = list(context.selected_pose_bones)
|
|
Tools.obj_selection = list(context.selected_objects)
|
|
# Tools.selection_order(self, context)
|
|
return {'FINISHED'}
|
|
|
|
def copy_modifiers(modifier):
|
|
#Copy the attributes of the modifier
|
|
mod_attr = {}
|
|
for key in dir(modifier): #add all the attributes into a dictionary
|
|
value = getattr(modifier, key)
|
|
mod_attr.update({key: value})
|
|
#mod_list.append(attr)
|
|
|
|
return mod_attr
|
|
|
|
def paste_modifiers(fcu, mod_attr):
|
|
#for mod in mod_list:
|
|
if mod_attr['type'] == 'CYCLES' and len(fcu.modifiers): #can add cycle modifier only as the first modifier
|
|
return
|
|
new_mod = fcu.modifiers.new(mod_attr['type'])
|
|
# if new_mod is None:
|
|
# return
|
|
for attr, value in mod_attr.items():
|
|
if type(value) is float or type(value) is int or type(value) is bool:
|
|
if not new_mod.is_property_readonly(attr):
|
|
setattr(new_mod, attr, value)
|
|
|
|
def update_range_type(self, context):
|
|
#get the default for custom frame range from the current scene frame range
|
|
if self.bake_range_type != 'CUSTOM':
|
|
return
|
|
if self.bake_range[1]:
|
|
return
|
|
self.bake_range = (context.scene.frame_start, context.scene.frame_end)
|
|
|
|
def update_bake_range(self, context):
|
|
#Make sure the start frame is not bigger or equal to the end frame
|
|
if self.bake_range_type != 'CUSTOM':
|
|
return
|
|
if self.bake_range[0] >= self.bake_range[1]:
|
|
self['bake_range'][1] = self.bake_range[0] + 1
|
|
|
|
def get_framerange(fcu = None):
|
|
#get the start and end frames
|
|
scene = bpy.context.scene
|
|
bake_range_type = scene.btc.bake_range_type
|
|
|
|
#check that there is something in the action otherwise use the scene frame range
|
|
frame_end = fcu.id_data.frame_range[1] if fcu else None
|
|
|
|
if bake_range_type == 'KEYFRAMES' and fcu and frame_end:
|
|
if len(fcu.keyframe_points) < 2:
|
|
action = fcu.id_data
|
|
range = action.frame_range[1] - action.frame_range[0]
|
|
if range > 5:
|
|
frame_start = round(action.frame_range[0])
|
|
frame_end = round(action.frame_range[1]+1)
|
|
else:
|
|
#In case the action is very short, because maybe it's just adjustment layer then get the scene frame range
|
|
frame_start = scene.frame_start
|
|
frame_end = scene.frame_end+1
|
|
else:
|
|
#if it is not smartbake or if it's only 2 frames
|
|
frame_start = round(fcu.range()[0])
|
|
frame_end = round(fcu.range()[1]+1)
|
|
|
|
elif bake_range_type == 'CUSTOM':
|
|
frame_start = scene.btc.bake_range[0]
|
|
frame_end = scene.btc.bake_range[1]+1
|
|
|
|
else:# bake_range_type == 'SCENE' or if fcu is none
|
|
frame_start = scene.frame_start
|
|
frame_end = scene.frame_end+1
|
|
|
|
return frame_start, frame_end
|
|
|
|
class smartfcu:
|
|
def __init__(self, fcu, posebone, sb = True, ik = False):
|
|
self.posebone = posebone
|
|
self.org_obj = posebone.id_data
|
|
self.fcu = fcu
|
|
if fcu:
|
|
self.data_path = fcu.data_path
|
|
self.index = fcu.array_index
|
|
self.group = fcu.group.name if fcu.group else posebone.name
|
|
self.extrapolation = fcu.extrapolation
|
|
else:
|
|
self.extrapolation = 'CONSTANT'
|
|
|
|
self.modifiers = []
|
|
self.interpolations = dict()
|
|
self.handle_left_types = dict()
|
|
self.handle_right_types = dict()
|
|
self.frames = []
|
|
self.all_frames = []
|
|
|
|
if ik:
|
|
return
|
|
#get the start and end frames
|
|
scene = bpy.context.scene
|
|
self.bake_range_type = scene.btc.bake_range_type
|
|
self.frame_start, self.frame_end = get_framerange(fcu)
|
|
|
|
self.update_frames(fcu, sb)
|
|
|
|
def update_frames(self, fcu, sb):
|
|
'''update the fcurve frames from all the actions '''
|
|
|
|
frame_start, frame_end = get_framerange(fcu)
|
|
#In case of multiple actions(layers) it will get the min and max of their frame ranges
|
|
if hasattr(self, 'frame_start'):
|
|
self.frame_start = min(self.frame_start, frame_start)
|
|
self.frame_end = max(self.frame_end, frame_end)
|
|
else:
|
|
self.frame_start = frame_start
|
|
self.frame_end = frame_end
|
|
|
|
#Get all the frames either smartbake or not
|
|
if sb and self.bake_range_type == 'KEYFRAMES' and fcu: #and (len(fcu.keyframe_points) > 2 or len(self.frames) > 2)
|
|
self.frames += [round(keyframe.co[0], 2) for keyframe in fcu.keyframe_points]
|
|
#Filter out the frames that are out of range
|
|
self.frames += list(filter(lambda frame: self.frame_start <= frame <= self.frame_end, self.frames))
|
|
|
|
#bake on every frame if there is no smartbake or not enough keyframes
|
|
else:
|
|
self.frames += [frame for frame in range( self.frame_start, self.frame_end)]
|
|
|
|
self.frames = sorted(set(self.frames))
|
|
|
|
def add_interpolations(self, ctrl_fcu = None):
|
|
|
|
keyframe_points = [keyframe for keyframe in self.fcu.keyframe_points]
|
|
#if there are keyframes from the ctrl fcurve then add them to the interpolations
|
|
if ctrl_fcu:
|
|
keyframe_points += [keyframe for keyframe in ctrl_fcu.keyframe_points]
|
|
if len(keyframe_points):
|
|
#updating and not just getting a new dict in case of baking both from origin and from the ctrls
|
|
self.interpolations.update({round(keyframe.co[0], 2) : keyframe.interpolation for keyframe in keyframe_points})
|
|
self.handle_left_types.update({round(keyframe.co[0], 2) : keyframe.handle_left_type for keyframe in keyframe_points})
|
|
self.handle_right_types.update({round(keyframe.co[0], 2) : keyframe.handle_right_type for keyframe in keyframe_points})
|
|
else:
|
|
self.interpolations = {frame : 'BEZIER' for frame in self.frames}
|
|
self.handle_left_types = self.handle_right_types = {frame : 'FREE' for frame in self.frames}
|
|
|
|
def add_inbetweens(self):
|
|
self.inbetweens = []
|
|
for i, frame in enumerate(self.frames[:-1]):
|
|
if (self.frames[i+1] - frame) <= 1:
|
|
continue
|
|
self.inbetweens.append(round(frame + (self.frames[i+1] - frame)*1/3, 2))
|
|
self.inbetweens.append(round(frame + (self.frames[i+1] - frame)*2/3, 2))
|
|
self.inbetweens.sort()
|
|
self.all_frames += sorted(self.frames + self.inbetweens)
|
|
|
|
def add_no_inbetweens(self):
|
|
self.all_frames = self.frames
|
|
self.inbetweens = []
|
|
#give only linear interpolation to all the keyframes
|
|
self.interpolations = {frame : 'LINEAR' for frame in self.frames}
|
|
|
|
def copy(self, smartfcurve, copy_fcurves = True):
|
|
'''Copying the smartfcurve attribute from another one'''
|
|
self.posebone = smartfcurve.posebone
|
|
if hasattr(smartfcurve, 'ctrlbone'):
|
|
self.ctrlbone = smartfcurve.ctrlbone
|
|
|
|
if copy_fcurves:
|
|
self.fcu = smartfcurve.fcu
|
|
self.data_path = smartfcurve.data_path
|
|
self.index = smartfcurve.index
|
|
|
|
if hasattr(smartfcurve, 'group'):
|
|
self.group = smartfcurve.group
|
|
self.frames = smartfcurve.frames
|
|
self.all_frames = smartfcurve.all_frames
|
|
self.modifiers = smartfcurve.modifiers
|
|
self.frame_start = smartfcurve.frame_start
|
|
self.frame_end = smartfcurve.frame_end
|
|
if hasattr(smartfcurve, 'inbetweens'):
|
|
self.inbetweens = smartfcurve.inbetweens
|
|
if hasattr(smartfcurve, 'interpolations'):
|
|
self.interpolations = smartfcurve.interpolations
|
|
if hasattr(smartfcurve, 'handle_left_types'):
|
|
self.handle_left_types = smartfcurve.handle_left_types
|
|
if hasattr(smartfcurve, 'handle_right_types'):
|
|
self.handle_right_types = smartfcurve.handle_right_types
|
|
|
|
def update_fcurves(self, fcu, data_path):
|
|
'''update the fcurves, for example after adding a new one'''
|
|
self.fcu = fcu
|
|
self.group = fcu.group.name
|
|
self.data_path = data_path
|
|
|
|
def get_bone_fcurves(posebone):
|
|
'''get all the fcurves of a bone'''
|
|
transform_fcus = dict()
|
|
actions = set()
|
|
#get the keyframes
|
|
bone_path = posebone.path_from_id()
|
|
obj = posebone.id_data
|
|
transformations = ["rotation_quaternion","rotation_euler", "rotation_axis_angle", "location", "scale"]
|
|
|
|
#Get all the actions from all the layers of the bone
|
|
get_bone_layers_actions(posebone, actions)
|
|
|
|
if obj.animation_data.action:
|
|
actions.add(obj.animation_data.action)
|
|
|
|
|
|
if not actions:
|
|
return transform_fcus
|
|
|
|
for action in actions:
|
|
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
|
|
|
for transform in transformations:
|
|
if transform == 'rotation_quaternion' or transform == 'rotation_axis_angle':
|
|
array_len = 4
|
|
else:
|
|
array_len = 3
|
|
#check if it's a vector or just one string
|
|
for i in range(array_len):
|
|
fcu = fcurves.find(data_path = bone_path + '.' + transform, index = i)
|
|
if fcu is None:
|
|
continue
|
|
|
|
if transform not in transform_fcus:
|
|
transform_fcus.update({transform : [fcu]})
|
|
else:
|
|
transform_fcus[transform].append(fcu)
|
|
|
|
return transform_fcus
|
|
|
|
def add_bone_fcurves(posebone, transform_fcus):
|
|
'''add new fcurves to a bone'''
|
|
|
|
#get the keyframes
|
|
bone_path = posebone.path_from_id()
|
|
obj = posebone.id_data
|
|
|
|
if posebone.rotation_mode == 'QUATERNION':
|
|
rotation = 'rotation_quaternion'
|
|
elif posebone.rotation_mode == 'AXIS_ANGLE':
|
|
rotation = 'rotation_axis_angle'
|
|
else:
|
|
rotation = 'rotation_euler'
|
|
|
|
transformations = ['location', rotation]
|
|
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
|
|
|
for transform in transformations:
|
|
if transform in transform_fcus:
|
|
continue
|
|
if transform == 'rotation_quaternion' or transform == 'rotation_axis_angle':
|
|
array_len = 4
|
|
else:
|
|
array_len = 3
|
|
|
|
for i in range(array_len):
|
|
fcu = fcurves.new(data_path = bone_path + '.' + transform, index = i)
|
|
Tools.add_group_to_fcurve(obj, fcu, posebone.name)
|
|
# bone_fcurves.append(fcu)
|
|
if transform not in transform_fcus:
|
|
transform_fcus.update({transform : [fcu]})
|
|
else:
|
|
transform_fcus[transform].append(fcu)
|
|
|
|
return transform_fcus
|
|
|
|
def get_transforms_array(posebone, track_filter = False):
|
|
'''return a dictionary with all the transforms and array of the bone depending on its rotation mode'''
|
|
|
|
|
|
setup = posebone.btc.setup if type(posebone) == bpy.types.PoseBone else None
|
|
transforms = {"location" : 3, "scale" : 3} if setup not in {'TRACK_TO', 'TRACK_TO_EMPTY'} or not track_filter else dict()
|
|
|
|
if len(posebone.rotation_mode) == 3:
|
|
transforms.update({'rotation_euler' : 3})
|
|
else:
|
|
transforms.update({'rotation_' + posebone.rotation_mode.lower() : 4})
|
|
|
|
return transforms
|
|
|
|
def smartbake_to_ctrls(context, self, posebones, ctrls, ctrl_rig = None, distance = False):
|
|
#Iterating over posebones
|
|
#Getting all the fcurves from the posebones from all the layer actions
|
|
#Iterating over the posebone fcurves
|
|
#Adding missing fcurves
|
|
#Iterating over the ctrlbones
|
|
#Converting posebone datapath to ctrlbone datapath
|
|
#Creating smartfcurve from the fcurve
|
|
#Merging smartcurves
|
|
#Merging to smartframes
|
|
|
|
scene = context.scene
|
|
frame_current = scene.frame_current
|
|
smartfcus = dict()
|
|
smartframes = set()
|
|
# merged_smartfcus = dict()
|
|
#baked_ctrls = []
|
|
#Store all the fcurves data and the frame count
|
|
for posebone in posebones:
|
|
#Boolean to check if an fcurve was found
|
|
org_rig = posebone.id_data
|
|
# id = posebone.btc.setup_id
|
|
path = posebone.path_from_id()
|
|
if not ctrl_rig:
|
|
ctrl_rig = org_rig.animtoolbox.controller
|
|
|
|
if ctrl_rig:
|
|
if not hasattr(ctrl_rig, 'animation_data'):
|
|
continue
|
|
if ctrl_rig.animation_data is None:
|
|
continue
|
|
if ctrl_rig.animation_data.action is None:
|
|
continue
|
|
|
|
transforms = get_transforms_array(posebone)
|
|
actions = set()
|
|
get_bone_layers_actions(posebone, actions)
|
|
if posebone.id_data.animation_data.action:
|
|
actions.add(posebone.id_data.animation_data.action)
|
|
|
|
#getting all the fcurves from all the actions
|
|
fcus = (fcu for action in actions for fcu in Tools.get_fcurves_channelbag(org_rig, action))
|
|
|
|
#find the ctrl bone
|
|
for ctrl_bone in ctrls: #ctrl_rig.pose.bones
|
|
if type(ctrl_bone) == bpy.types.PoseBone:
|
|
if ctrl_bone.btc.org_id == posebone.btc.org_id:
|
|
break
|
|
else:
|
|
#in case it's an empty then check the setup id of the object
|
|
if ctrl_bone.animtoolbox.setup_id == posebone.btc.setup_id:
|
|
break
|
|
|
|
ctrl_rig = ctrl_bone.id_data
|
|
|
|
#######Get smartfcurves from the current fcurves#########
|
|
for fcu in fcus:
|
|
if path not in fcu.data_path:
|
|
continue
|
|
#check that it's a really a transform channel
|
|
if not any(fcu.data_path.endswith(transform) for transform in transforms.keys()):
|
|
continue
|
|
|
|
#if it's using euler and has too many array coming from quaternion, then skip
|
|
#fixes a bug with the last index in case there is both euler and quaternion and we bake from euler to quaternion
|
|
if 'rotation' in fcu.data_path:
|
|
if len(ctrl_bone.rotation_mode) == 3 and fcu.array_index > 2:
|
|
continue
|
|
|
|
smartfcurve = get_smartfcurve(fcu, smartfcus, posebone, fcu.data_path, index = None, sb = self.smartbake)
|
|
|
|
add_missing_ctrl_smartfcurves(self, ctrl_bone, posebone, smartfcus)
|
|
|
|
#####Assign all the properties to the smartfcurves and change from posebone to ctrl bone######
|
|
for smartfcurve in smartfcus.values():
|
|
if posebone != smartfcurve.posebone:
|
|
continue
|
|
|
|
if type(ctrl_bone) == bpy.types.PoseBone:
|
|
#change smartfcurve attributes from original to ctrl bone
|
|
ctrl_path = ctrl_bone.path_from_id()
|
|
new_data_path = smartfcurve.data_path.replace(path, ctrl_path)
|
|
else:
|
|
#in case it's an empty, keep only the transform
|
|
new_data_path = smartfcurve.data_path.replace(path + '.', '')
|
|
|
|
new_data_path = replace_rot_mode(new_data_path, ctrl_bone)
|
|
|
|
smartfcurve.ctrlbone = ctrl_bone
|
|
smartfcurve.data_path = new_data_path
|
|
smartfcurve.posebone = posebone
|
|
smartfcurve.group = ctrl_bone.name
|
|
|
|
#using distance for relative controls
|
|
if distance:
|
|
smartfcurve.distance = Matrix.Identity(4)
|
|
if self.bake_loc == 'TRACK':
|
|
ctrl_bone_matrix = ctrl_bone.matrix if type(ctrl_bone) == bpy.types.PoseBone else ctrl_bone.matrix_world
|
|
smartfcurve.distance = (self.org_obj.matrix_world @ posebone.matrix).inverted() @ ctrl_bone_matrix #@ ctrl_bone.id_data.matrix_world
|
|
|
|
#getting the action or channelbag for adding new fcurves
|
|
fcurves_container = Tools.get_fcurves_container(ctrl_rig, ctrl_rig.animation_data.action)
|
|
fcu_new = smartbake_add_fcurve(fcurves_container, new_data_path, smartfcurve.index, smartfcurve.group)
|
|
smartfcurve.update_fcurves(fcu_new, new_data_path)
|
|
|
|
if Tools.filter_properties(self, fcu_new):
|
|
fcu_new.select = False
|
|
fcu_new.hide = True
|
|
|
|
smartframes = get_smartframes(scene, smartfcus)
|
|
smartframes = sorted(smartframes)
|
|
|
|
smartbake_write_keyframes(scene, smartframes, smartfcus, bake_ctrls = True)
|
|
|
|
add_interpolations(smartfcus)
|
|
scene.frame_set(frame_current)
|
|
|
|
def add_missing_ctrl_smartfcurves(self, ctrl_bone, posebone, smartfcus):
|
|
'''Add Transform smartfcurves that are missing from the Temp Ctrls Used in smartbake_to_ctrls'''
|
|
|
|
if posebone.btc.setup == 'ROOT':
|
|
return
|
|
|
|
#Get the transform of the controller in case it's a different rotation order
|
|
ctrl_transforms = get_transforms_array(ctrl_bone)
|
|
path = posebone.path_from_id()
|
|
#####Find the MISSING transforms and get them also smartfcurves###
|
|
for transform, arrays in ctrl_transforms.items():
|
|
for array in range(arrays):
|
|
data_path = path + '.' + transform
|
|
smartfcurve = smartfcus.get((data_path, array))
|
|
if smartfcurve:
|
|
continue
|
|
if 'rotation' in transform:
|
|
if len(ctrl_bone.rotation_mode) == 3 and array > 2:
|
|
continue
|
|
|
|
smartfcurve = smartfcu(None, posebone, self.smartbake)
|
|
smartfcurve.data_path, smartfcurve.index = data_path, array
|
|
find_smartfcus_to_copy(smartfcurve, smartfcus)
|
|
smartfcus.update({(data_path, array) : smartfcurve})
|
|
|
|
def find_smartfcus_to_copy(smartfcurve, smartfcus):
|
|
'''Find an fcurve to copy keyframes from. Either from a different array or transform'''
|
|
data_path = smartfcurve.data_path
|
|
|
|
#Get the keyframes from another array in the same transform
|
|
for i in range(4):
|
|
if smartfcurve.index == i:
|
|
continue
|
|
smartfcu_copy = smartfcus.get((data_path, i))
|
|
if not smartfcu_copy:
|
|
continue
|
|
smartfcurve.copy(smartfcu_copy, copy_fcurves = False)
|
|
if smartfcurve.frames:
|
|
return
|
|
|
|
#Get keyframes from another transform channel
|
|
transforms = get_transforms_array(smartfcurve.posebone)
|
|
path = smartfcurve.data_path.split('"].')[0]
|
|
org_transform = smartfcurve.data_path.split('"].')[1]
|
|
|
|
for transform, array_index in transforms.items():
|
|
if transform == org_transform:
|
|
continue
|
|
data_path_copy = path + '"].' + transform
|
|
|
|
for i in range(array_index):
|
|
smartfcu_copy = smartfcus.get((data_path_copy, i))
|
|
if not smartfcu_copy:
|
|
continue
|
|
|
|
smartfcurve.copy(smartfcu_copy, copy_fcurves = False)
|
|
if smartfcurve.frames:
|
|
return
|
|
|
|
def get_bone_layers_actions(bone, actions):
|
|
'''get the actions from all layers'''
|
|
if not bpy.context.scene.btc.bake_layers:
|
|
return actions
|
|
|
|
obj = bone.id_data
|
|
if not obj.animation_data:
|
|
return actions
|
|
if not obj.animation_data.use_nla:
|
|
return actions
|
|
for track in obj.animation_data.nla_tracks:
|
|
if track.mute:
|
|
continue
|
|
for strip in track.strips:
|
|
if strip.mute:
|
|
continue
|
|
if strip.action in actions:
|
|
continue
|
|
actions.add(strip.action)
|
|
|
|
return actions
|
|
|
|
def smartbake_to_ik(context, posebones, chain, sb):
|
|
|
|
scene = context.scene
|
|
frame_current = scene.frame_current
|
|
ctrls = chain.ctrls_to_bake
|
|
smartfcus = dict()
|
|
smartframes = set()
|
|
bone_path_ids = [bone.path_from_id() for bone in posebones if bone.btc.setup_id]
|
|
actions = set()
|
|
#Using this dict to get actions per object/slot
|
|
obj_actions = dict()
|
|
|
|
for bone in chain.org_bones:
|
|
# if scene.btc.bake_layers:
|
|
get_bone_layers_actions(bone, actions)
|
|
obj = bone.id_data
|
|
if obj.animation_data.action:
|
|
actions.add(obj.animation_data.action)
|
|
obj_actions = ({obj : actions})
|
|
|
|
#Store all the fcurves data and the frame count
|
|
#It is going to use the smartframes from the overall frames
|
|
frames_start = []
|
|
frames_end = []
|
|
for obj, action in obj_actions.items():
|
|
for action in actions:
|
|
if action is None:
|
|
continue
|
|
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
|
for fcu in fcurves:
|
|
#check if the fcurve is in the original bones
|
|
path = fcu.data_path.split('"].')[0] + ('"]')
|
|
if path not in bone_path_ids:
|
|
continue
|
|
frames = np.zeros(len(fcu.keyframe_points)*2)
|
|
fcu.keyframe_points.foreach_get('co', frames)
|
|
frames = frames[::2]
|
|
smartframes.update(frames)
|
|
frame_start, frame_end = get_framerange(fcu)
|
|
frames_start.append(frame_start)
|
|
frames_end.append(frame_end)
|
|
if scene.btc.bake_range_type != 'KEYFRAMES':
|
|
smartframes.update([frame_start, frame_end])
|
|
|
|
smartframes = sorted([round(frame, 2) for frame in smartframes
|
|
if min(frames_start) <= frame < max(frames_end)])
|
|
if not sb and smartframes:
|
|
smartframes = [frame for frame in range(int(min(smartframes)), int(max(smartframes))+1)]
|
|
|
|
#create the fcurves and add them to smartfcurves
|
|
for ctrl in ctrls:
|
|
ctrl_action = ctrl.id_data.animation_data.action
|
|
path = ctrl.path_from_id()
|
|
if ctrl.rotation_mode == 'QUATERNION':
|
|
rot = 'rotation_quaternion'
|
|
elif ctrl.rotation_mode == 'AXIS_ANGLE':
|
|
rot = 'rotation_axis_angle'
|
|
else:
|
|
rot = 'rotation_euler'
|
|
for transform in ['location', rot, 'scale']:
|
|
#add the array length, depends on the transform
|
|
array_len = 4 if transform in ['rotation_quaternion', 'rotation_axis_angle'] else 3
|
|
for array_index in range(array_len):
|
|
data_path = path + '.' + transform
|
|
fcurves = Tools.get_fcurves_channelbag(ctrl.id_data, ctrl_action)
|
|
try:
|
|
fcu = fcurves.new(data_path = data_path, index = array_index)
|
|
Tools.add_group_to_fcurve(ctrl.id_data, fcu, ctrl.name)
|
|
except RuntimeError:
|
|
#in case it was already existing because of previous setup, find it and clean it
|
|
fcu = fcurves.find(data_path, index = array_index)
|
|
if fcu is None:
|
|
continue
|
|
fcu.keyframe_points.clear()
|
|
|
|
smartfcurve = smartfcu(fcu, ctrl, sb, ik = True)
|
|
smartfcurve.frames = smartframes
|
|
smartfcurve.ctrlbone = ctrl
|
|
smartfcurve.add_inbetweens()
|
|
smartfcurve.add_interpolations()
|
|
smartfcus.update({(fcu.data_path, fcu.array_index) : smartfcurve})
|
|
|
|
# smartframes = sorted([round(frame, 2) for frame in smartframes])
|
|
smartbake_write_keyframes(scene, smartframes, smartfcus, chain, bake_ctrls = True)
|
|
|
|
add_interpolations(smartfcus)
|
|
scene.frame_set(frame_current)
|
|
|
|
def deselect_all_fcurves(fcurves):
|
|
#hide and unselect all the fcurve channels
|
|
array_true = [True] * len(fcurves)
|
|
array_false = [False] * len(fcurves)
|
|
fcurves.foreach_set('hide', array_true)
|
|
fcurves.foreach_set('select', array_false)
|
|
|
|
def get_enabled_keyframes(ctrl_rig, action_frames_remove):
|
|
'''Get all the frames where Temp Ctrls are turned off'''
|
|
|
|
action = ctrl_rig.animation_data.action
|
|
|
|
if action in action_frames_remove:
|
|
return action_frames_remove[action]
|
|
|
|
fcurves = Tools.get_fcurves_channelbag(ctrl_rig, action)
|
|
enable_fcu = fcurves.find('animtoolbox.ctrls_enabled')
|
|
if not enable_fcu:
|
|
return None
|
|
|
|
action_frame_range = action.frame_range
|
|
frames_remove = []
|
|
|
|
keyframes = np.zeros(len(enable_fcu.keyframe_points)*2)
|
|
enable_fcu.keyframe_points.foreach_get('co', keyframes)
|
|
frames = keyframes[::2]
|
|
values = keyframes[1::2]
|
|
|
|
if values[0] == 0:
|
|
frames_remove += list(range(round(action_frame_range[0]), round(frames[0])))
|
|
|
|
for i in range(1, len(frames)):
|
|
if values[i-1] == 0 and values[i] == 1:
|
|
frames_remove += list(range(round(frames[i-1]), round(frames[i])))
|
|
|
|
if values[-1] == 0:
|
|
frames_remove += list(range(round(frames[-1]), round(action_frame_range[1])))
|
|
|
|
action_frames_remove.update({action : frames_remove})
|
|
|
|
return frames_remove
|
|
|
|
|
|
def replace_rot_mode(origin_data_path, posebone):
|
|
#use bone rotation mode
|
|
transform = origin_data_path.split('"].')[-1]
|
|
if 'rotation' in transform:
|
|
rot_type = 'euler' if posebone.rotation_mode not in {'QUATERNION', 'AXIS_ANGLE'} else posebone.rotation_mode.lower()
|
|
origin_data_path = origin_data_path.replace(transform, 'rotation_' + rot_type)
|
|
return origin_data_path
|
|
|
|
|
|
def filter_transform(transform, array_index):
|
|
#Skipping transforms that are excluded in the filter
|
|
atb = bpy.context.scene.animtoolbox
|
|
if transform == 'rotation_euler':
|
|
# it's a 4 array by default so move 1 array forward for rotation euler
|
|
array_index +=1
|
|
|
|
if 'rotation' in transform:
|
|
transform = transform[:8]
|
|
filter_transform = getattr(atb, 'filter_' + transform)
|
|
|
|
return True if filter_transform[array_index] else False
|
|
|
|
def add_missing_fcurves(action, posebone, smartfcus):
|
|
#Get a dictionary with the transformations and arrays
|
|
|
|
transforms = get_transforms_array(posebone, track_filter = True)
|
|
obj = posebone.id_data
|
|
fcu_container = Tools.get_fcurves_container(obj, action)
|
|
if not fcu_container:
|
|
return
|
|
|
|
for transform, index_len in transforms.items():
|
|
data_path = posebone.path_from_id() + '.' + transform
|
|
|
|
for array_index in range(index_len):
|
|
if filter_transform(transform, array_index):
|
|
continue
|
|
|
|
smartfcurve = smartfcus.get((data_path, array_index))
|
|
if smartfcurve:
|
|
continue
|
|
|
|
fcu = fcu_container.fcurves.new(data_path, index = array_index)
|
|
group = fcu_container.groups.new(posebone.name) if posebone.name not in fcu_container.groups else fcu_container.groups[posebone.name]
|
|
fcu.group = group
|
|
|
|
smartfcurve = get_smartfcurve(fcu, smartfcus, posebone, fcu.data_path, fcu.array_index, True)
|
|
|
|
# if not smartfcurve.frames:
|
|
# #Need to check if this is running twice
|
|
# find_smartfcus_to_copy(smartfcurve, smartfcus)
|
|
|
|
smartfcus.update({(data_path, array_index) : smartfcurve})
|
|
|
|
|
|
def smartbake_add_fcurve(fcu_container , data_path, array_index, groupname):
|
|
'''Add a new fcurve to an action if it's still not existing'''
|
|
|
|
fcu = fcu_container.fcurves.find(data_path, index = array_index)
|
|
|
|
if not fcu:
|
|
fcu = fcu_container.fcurves.new(data_path, index = array_index)
|
|
#get the group
|
|
group = fcu_container.groups.new(groupname) if groupname not in fcu_container.groups else fcu_container.groups[groupname]
|
|
fcu.group = group
|
|
|
|
return fcu
|
|
|
|
|
|
def get_smartfcurve(fcu, smartfcus, posebone, data_path, index = None, sb = True):
|
|
|
|
array_index = fcu.array_index if fcu else index
|
|
|
|
#check if it has any transformation
|
|
if data_path.split('.')[-1] not in ['location', 'rotation_euler', 'rotation_quaternion', 'scale']:
|
|
return None
|
|
|
|
#if the smartfcurve exist then update it, else create one
|
|
if (data_path, array_index) in smartfcus:
|
|
smartfcurve = smartfcus[(data_path, array_index)]
|
|
#merge frames with previous frames
|
|
smartfcurve.update_frames(fcu, sb)
|
|
else:
|
|
smartfcurve = smartfcu(fcu, posebone, sb)
|
|
smartfcus.update({(data_path, array_index) : smartfcurve})
|
|
smartfcurve.data_path = data_path
|
|
smartfcurve.index = array_index
|
|
|
|
#add the modifiers
|
|
if fcu:
|
|
for mod in fcu.modifiers:
|
|
smartfcurve.modifiers.append(mod)
|
|
|
|
return smartfcurve
|
|
|
|
def get_smartframes(scene, smartfcus):
|
|
'''add inbetweens to the smartfcurves and add to the overall smartframes using this after all the layers have been iterated'''
|
|
|
|
# #add in betweens and interpolations
|
|
smartbake = scene.btc.smartbake
|
|
smartframes = set()
|
|
|
|
def get_frames(smartfcurve):
|
|
if scene.btc.bake_range_type == 'KEYFRAMES':
|
|
|
|
#if there are still no frames applied then take it from another related smartfcurve
|
|
if not smartfcurve.frames:
|
|
find_smartfcus_to_copy(smartfcurve, smartfcus)
|
|
|
|
#check after merging all the layers if thereäs still more then two keyframes
|
|
if len(smartfcurve.frames) > 2 and smartbake:
|
|
smartfcurve.add_interpolations()
|
|
smartfcurve.add_inbetweens()
|
|
return smartfcurve
|
|
else:
|
|
#if there is still not enough keyframes then just use the whole frame range
|
|
smartfcurve.frames = [frame for frame in range(smartfcurve.frame_start, smartfcurve.frame_end)]
|
|
|
|
smartfcurve.add_no_inbetweens()
|
|
|
|
return smartfcurve
|
|
|
|
for smartfcurve in smartfcus.values():
|
|
get_frames(smartfcurve)
|
|
|
|
#Add to the overall smartframes
|
|
smartframes = set(smartfcurve.all_frames).union(smartframes)
|
|
|
|
return smartframes
|
|
|
|
def remove_unnecessey_keyframes(smartfcurve):
|
|
'''remove unnecessey keyframes that were created on extra index when using keyframe insert, usually on quaternions'''
|
|
keyframes = smartfcurve.fcu.keyframe_points
|
|
if len(keyframes) == len(smartfcurve.all_frames):
|
|
return
|
|
i = 0
|
|
btc = bpy.context.scene.btc
|
|
frame_start, frame_end = btc.bake_range
|
|
|
|
while i < len(keyframes):
|
|
key = keyframes[i]
|
|
|
|
#if it's custom frame range then skip frames that are not included
|
|
|
|
# if btc.bake_range_type == 'CUSTOM':
|
|
if not frame_start < key.co[0] < frame_end and btc.bake_range_type == 'CUSTOM':
|
|
i += 1
|
|
continue
|
|
|
|
if round(key.co[0], 2) not in smartfcurve.all_frames:
|
|
keyframes.remove(key)
|
|
else:
|
|
i += 1
|
|
|
|
def get_ctrl_matrix(bone, fcu, smartfcurve, chain, bones_matrices):
|
|
'''Set the matrix of a bone when writing keyframes to the bake.'''
|
|
|
|
org_obj_mat = smartfcurve.org_obj.matrix_world
|
|
if type(bone) == bpy.types.PoseBone:
|
|
# if fcu.data_path in data_paths:
|
|
# return True
|
|
if bone in bones_matrices:
|
|
return True
|
|
|
|
if bone.btc.setup == 'POLE' and chain:
|
|
if not 'location' in fcu.data_path:
|
|
return False
|
|
# bone.matrix.translation = find_pole_vector(chain.base_bone, chain.ik_tip, bone).translation
|
|
matrix = find_pole_vector(chain.base_bone, chain.ik_tip, bone)
|
|
bones_matrices.update({bone : matrix.copy()})
|
|
return True
|
|
|
|
if hasattr(smartfcurve, 'distance'):
|
|
#If it is using track constraint then get the distance, This is currently being used always with worldspace controls
|
|
matrix = org_obj_mat @ smartfcurve.posebone.matrix @ smartfcurve.distance #
|
|
else:
|
|
matrix = get_bone_matrix(bone)
|
|
|
|
matrix = Tools.reverse_childof_constraint(bone, matrix)
|
|
|
|
#Add the bone and matrix only to bones that are constrained
|
|
bones_matrices.update({bone : matrix.copy()})
|
|
|
|
#In case of empties
|
|
if type(bone) != bpy.types.PoseBone:
|
|
bone.matrix_world = matrix
|
|
|
|
|
|
return True
|
|
|
|
def euler_filter_context_override():
|
|
context = bpy.context
|
|
window = context.window
|
|
screen = context.screen
|
|
#Storing the first area in the screen
|
|
old_area = screen.areas[0].type
|
|
area = screen.areas[0]
|
|
area.type = 'GRAPH_EDITOR'
|
|
region = area.regions[1]
|
|
|
|
with context.temp_override(window=window, area=area, region=region):
|
|
bpy.ops.graph.euler_filter()
|
|
|
|
#restoring the old area
|
|
screen.areas[0].type = old_area
|
|
|
|
|
|
def get_constrained_bones(smartfcus):
|
|
constrained = set()
|
|
# conbones_matrix = dict()
|
|
for smartfcurve in smartfcus.values():
|
|
bone = smartfcurve.posebone
|
|
for con in bone.constraints:
|
|
if con.type == 'CHILD_OF':
|
|
continue
|
|
if not hasattr(con, 'target'):
|
|
continue
|
|
if not con.target:
|
|
continue
|
|
if con.target == bone.id_data.animtoolbox.controller:
|
|
continue
|
|
#in case of empties that are not assigned as a controller
|
|
if con.target.animtoolbox.controlled == bone.id_data:
|
|
continue
|
|
constrained.add(bone)
|
|
|
|
return constrained
|
|
|
|
def get_con_influence(constrained):
|
|
#This is done separatly so that we don't run view_layer.update() everytime for each bone
|
|
|
|
if not constrained:
|
|
return None
|
|
con_influence = dict()
|
|
#Turn off influence from tempctrls to check the other constraints offset
|
|
for bone in constrained:
|
|
for con in bone.constraints:
|
|
if not hasattr(con, 'target'):
|
|
continue
|
|
if con.target is None:
|
|
continue
|
|
if con.target.animtoolbox.controlled == bone.id_data:
|
|
con_influence.update({con : con.influence})
|
|
con.influence = 0
|
|
|
|
return con_influence
|
|
|
|
def set_ctrl_matrix(bones, ctrls):
|
|
'''Assigning matrix, in case the original armature has no action'''
|
|
constrained = set()
|
|
bones_matrices = dict()
|
|
for bone, ctrl in zip(bones, ctrls):
|
|
if bone.btc.setup =='ROOT':
|
|
continue
|
|
matrix_copied = Tools.reverse_childof_constraint(ctrl, bone.matrix.copy(), constrained)
|
|
bones_matrices.update({ctrl : matrix_copied})
|
|
|
|
bones_matrices = Tools.reorder_bones_matrices(bones_matrices, constrained)
|
|
Tools.paste_bones_matrices(bones_matrices, constrained)
|
|
|
|
def smartbake_write_keyframes(scene, smartframes, smartfcus, chain = None, bake_ctrls = False, posebones = set()):
|
|
#Store the matrix of a bone in case it needs an extra iteration because of constraints
|
|
#Need to update the object as well in case of using empties
|
|
|
|
constrained = get_constrained_bones(smartfcus)
|
|
|
|
def get_parents_matrices(smartfcurve, bones_matrices):
|
|
#get matrix also for parent bones
|
|
if not hasattr(bone, 'parent_recursive'):
|
|
return
|
|
|
|
parent_posebones = set(posebones).intersection(set(bone.parent_recursive))
|
|
if not parent_posebones:
|
|
return
|
|
|
|
for p_bone in parent_posebones:
|
|
if p_bone in bones_matrices.keys():
|
|
continue
|
|
get_ctrl_matrix(p_bone, smartfcurve.fcu, smartfcurve, chain, bones_matrices)
|
|
|
|
def set_parents_matrices(pasted_bones, bones_matrices):
|
|
#get matrix also for parent bones
|
|
if not hasattr(bone, 'parent_recursive'):
|
|
return
|
|
|
|
#Get all the parent bones that are also included in this calculation. If they don't have a keyframe,
|
|
#setting the matrix only on the child bone can cause wrong the wrong matrix
|
|
parent_posebones = (bone for bone in reversed(bone.parent_recursive) if bone in posebones and bone not in pasted_bones)
|
|
|
|
#Pasting the matrix first to all the parent bones
|
|
if parent_posebones:
|
|
for p_bone in parent_posebones:
|
|
Tools.paste_bone_matrix(p_bone, bones_matrices[p_bone], constrained, x_filter = False)
|
|
pasted_bones.add(p_bone)
|
|
|
|
bone_fcus = dict()
|
|
|
|
wm = bpy.context.window_manager
|
|
wm.progress_begin(0, len(smartframes))
|
|
|
|
for frame in smartframes:
|
|
scene.frame_set(frame = int(frame), subframe = round(frame % 1, 2))
|
|
|
|
#assign the matrix only once for each bone
|
|
bones_matrices = dict()
|
|
|
|
for smartfcurve in smartfcus.values():
|
|
if frame not in smartfcurve.all_frames:
|
|
continue
|
|
fcu = smartfcurve.fcu
|
|
#Write the keys either to a ctrl bone or to the original pose bones
|
|
bone = smartfcurve.ctrlbone if bake_ctrls else smartfcurve.posebone
|
|
|
|
obj = bone.id_data
|
|
#add the keyframes to all the fcurves in the end of the iteration
|
|
if get_ctrl_matrix(bone, fcu, smartfcurve, chain, bones_matrices):
|
|
|
|
#get matrix also for parent bones
|
|
get_parents_matrices(smartfcurve, bones_matrices)
|
|
|
|
if bone in bone_fcus:
|
|
bone_fcus[bone].add(fcu)
|
|
else:
|
|
bone_fcus.update({bone : {fcu}})
|
|
|
|
#re-order first with constrained bones
|
|
bones_matrices = Tools.reorder_bones_matrices(bones_matrices, constrained)
|
|
|
|
#Using this set to avoid pasting the matrix multiple times to the same bone
|
|
pasted_bones = set()
|
|
|
|
#Store the influence and set them to 0 before calculating the matrix without the constraint (to get the offset)
|
|
con_influence = get_con_influence(constrained)
|
|
|
|
#Paste the matrix to the bones, and add keyframes to them
|
|
for bone, matrix in bones_matrices.items():
|
|
if bone not in bone_fcus:
|
|
continue
|
|
obj = bone.id_data
|
|
set_parents_matrices(pasted_bones, bones_matrices)
|
|
|
|
#Paste the matrix to the bone itself before entering the keyframe
|
|
if bone not in pasted_bones:
|
|
if type(bone) != bpy.types.PoseBone:
|
|
bone.matrix_world = matrix
|
|
else:
|
|
Tools.paste_bone_matrix(bone, matrix, constrained, x_filter = False)
|
|
# bone.matrix = matrix
|
|
pasted_bones.add(bone)
|
|
|
|
#Add keyframes to all the fcurves of the bone
|
|
for fcu in bone_fcus[bone]:
|
|
groupname = fcu.group.name if fcu.group else ""
|
|
obj.keyframe_insert(fcu.data_path, index = fcu.array_index , frame = frame, group = groupname) #
|
|
|
|
#Turn the influence of the constraints back on
|
|
if con_influence:
|
|
for con, influence in con_influence.items():
|
|
con.influence = influence
|
|
|
|
wm.progress_update(smartframes.index(frame))
|
|
|
|
#####add interpolations#####
|
|
for smartfcurve in smartfcus.values():
|
|
|
|
remove_unnecessey_keyframes(smartfcurve)
|
|
|
|
#Copy Paste the modifiers from the ctrl rig
|
|
if smartfcurve.modifiers:
|
|
for mod in smartfcurve.modifiers:
|
|
mod_attr = copy_modifiers(mod)
|
|
paste_modifiers(smartfcurve.fcu, mod_attr)
|
|
|
|
#apply the saved extrapolation
|
|
smartfcurve.fcu.extrapolation = smartfcurve.extrapolation
|
|
|
|
# keyframes = smartfcurve.fcu.keyframe_points
|
|
if not smartfcurve.interpolations:
|
|
continue
|
|
|
|
for key in smartfcurve.fcu.keyframe_points:
|
|
#skip inbetween keyframes
|
|
frame = round(key.co[0], 2)
|
|
if frame in smartfcurve.inbetweens:
|
|
continue
|
|
if frame not in smartfcurve.interpolations:
|
|
continue
|
|
|
|
key.interpolation = smartfcurve.interpolations[frame]
|
|
if key.interpolation != 'BEZIER':
|
|
continue
|
|
key.handle_left_type = smartfcurve.handle_left_types[frame]
|
|
key.handle_right_type = smartfcurve.handle_right_types[frame]
|
|
|
|
wm.progress_end()
|
|
|
|
def add_interpolations(smartfcus):
|
|
#turn inbetween keyframes values to handles
|
|
for smartfcurve in smartfcus.values():
|
|
fcu = smartfcurve.fcu
|
|
keys = fcu.keyframe_points
|
|
|
|
i = 0
|
|
while i < len(keys)-3:
|
|
frame = round(keys[i].co[0], 2)
|
|
if frame not in smartfcurve.interpolations:
|
|
i +=1
|
|
continue
|
|
|
|
#skip inbetweens
|
|
if frame in smartfcurve.inbetweens:
|
|
i +=1
|
|
continue
|
|
|
|
if smartfcurve.interpolations[frame] != 'BEZIER':
|
|
i +=1
|
|
continue
|
|
#keyframes
|
|
P0 = keys[i].co[1]
|
|
P3 = keys[i+3].co[1]
|
|
|
|
#inbetweens
|
|
P1 = keys[i+1].co[1]
|
|
P2 = keys[i+2].co[1]
|
|
|
|
P1_frame = keys[i+1].co[0]
|
|
P2_frame = keys[i+2].co[0]
|
|
|
|
cp1 = (1/6)*( -5*P0 + 18*P1 - 9*P2 + 2*P3)
|
|
cp2 = (1/6)*( 2*P0 - 9*P1 +18*P2 - 5*P3)
|
|
|
|
keys[i].handle_right_type = 'FREE'
|
|
keys[i+3].handle_left_type = 'FREE'
|
|
|
|
keys[i].handle_right = [P1_frame, cp1]
|
|
keys[i+3].handle_left = [P2_frame, cp2]
|
|
|
|
i += 3
|
|
|
|
# removing the inbetween keyframes
|
|
i = 0
|
|
|
|
while i < len(keys):
|
|
key = keys[i]
|
|
if round(key.co[0], 2) in smartfcurve.inbetweens:
|
|
keys.remove(key)
|
|
else:
|
|
i += 1
|
|
|
|
# fcu.update()
|
|
|
|
def count_fcurve_ranges(obj, posebones):
|
|
|
|
#get all the paths of the bones
|
|
bone_path_ids = [bone.path_from_id() for bone in posebones if bone.btc.setup_id]
|
|
|
|
frame_range = set()
|
|
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
|
for fcu in fcurves:
|
|
path = fcu.data_path.split('"].')[0] + ('"]')
|
|
if path not in bone_path_ids:
|
|
continue
|
|
frame_range.add(round(fcu.range()[0], 2))
|
|
frame_range.add(round(fcu.range()[1], 2))
|
|
return sorted(frame_range)
|
|
|
|
# class ConvertRotatinMode(bpy.types.Operator):
|
|
# """Bake all the bones with constraints to the empties"""
|
|
# bl_idname = "anim.convert_rotation_mode"
|
|
# bl_label = "Convert Rotation Mode"
|
|
# bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
# # @classmethod
|
|
# # def poll(cls, context):
|
|
# # return context.object.type == 'ARMATURE'
|
|
|
|
# def execute(self, context):
|
|
# #obj = context.object
|
|
# scene = context.scene
|
|
# selected_bones = context.selected_pose_bones
|
|
|
|
# return {'FINISHED'}
|
|
def check_for_actions(obj):
|
|
'''checking if any action exist in the object, either active action or in the nla'''
|
|
if not obj:
|
|
return False
|
|
if obj.animation_data.action:
|
|
return True
|
|
#if there is no action assigned and no use of nla then quit
|
|
if not obj.animation_data.use_nla:
|
|
return False
|
|
for track in obj.animation_data.nla_tracks:
|
|
for strip in track.strips:
|
|
if strip.action:
|
|
return True
|
|
|
|
return False
|
|
|
|
class BakeTempCtrls(bpy.types.Operator):
|
|
"""Bake all the bones with constraints to the Temp Ctrls"""
|
|
bl_idname = "anim.bake_temp_ctrls"
|
|
bl_label = "Bake Temp Ctrls"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
# @classmethod
|
|
# def poll(cls, context):
|
|
# return context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
# sec = time.time()
|
|
self.report({'INFO'}, 'Baked Temp Ctrls')
|
|
scene = context.scene
|
|
ids = get_bone_ids(context)
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
|
|
#in case the controllers are empties
|
|
for ctrl in controllers:
|
|
if not ctrl.animtoolbox.setup_id:
|
|
continue
|
|
ids.add(ctrl.animtoolbox.setup_id)
|
|
# controllers_remove_child(ctrl, controllers) <-- ?
|
|
|
|
if not ids:
|
|
return {'CANCELLED'}
|
|
|
|
temp_col = find_temp_ctrl_collection(scene, controllers)
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(temp_col, context, controlled_objs)
|
|
|
|
#Select only the bones with the constraints
|
|
for obj in controlled_objs:
|
|
if obj.type != 'ARMATURE':
|
|
continue
|
|
|
|
#Relevant only for temp ctrls and not empties
|
|
if obj.animtoolbox.controller:
|
|
if not check_for_actions(obj.animtoolbox.controller):
|
|
continue
|
|
|
|
obj.select_set(True)
|
|
context.view_layer.objects.active = obj
|
|
obj_vis, obj_hide = unhide_org(obj)
|
|
if obj.mode != 'POSE':
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
baked_bones = []
|
|
#get all the bones that should be baked via id
|
|
for posebone in obj.pose.bones:
|
|
#Disabling root from bake
|
|
if posebone.btc.setup == 'ROOT':
|
|
continue
|
|
#check if selected if it has any of selected ids
|
|
if scene.btc.target == 'SELECTED' and posebone.btc.setup_id in ids:
|
|
baked_bones.append(posebone)
|
|
#if not only selected check if it has any setup_id
|
|
elif scene.btc.target != 'SELECTED' and posebone.btc.setup_id:
|
|
baked_bones.append(posebone)
|
|
|
|
#ctrl_rig = obj.animtoolbox.controller
|
|
if not obj.animation_data:
|
|
obj.animation_data_create()
|
|
if not obj.animation_data.action:
|
|
action = bpy.data.actions.new(obj.name)
|
|
obj.animation_data.action = action
|
|
obj.animation_data.action_slot = Tools.add_action_slot(obj, action)
|
|
|
|
else:
|
|
action = obj.animation_data.action
|
|
|
|
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
|
deselect_all_fcurves(fcurves)
|
|
|
|
smartbake(context, action, baked_bones, ids)
|
|
|
|
if obj_vis: obj.hide_set(True)
|
|
if obj_hide: obj.hide_viewport = True
|
|
|
|
include_collections(included_collections, hidden_objs, temp_col, controlled_objs)
|
|
|
|
if not scene.btc.clean_constraints:
|
|
return {'FINISHED'}
|
|
|
|
remove_constraints(context, ids, controllers, controlled_objs)
|
|
|
|
if not scene.btc.clean_ctrls:
|
|
return {'FINISHED'}
|
|
|
|
remove_tempctrls(context, self, controllers, controlled_objs, ids)
|
|
# print('\nseconds\n', time.time() - sec)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def smartbake(context, org_action, posebones, ids):
|
|
'''Smart Bake back to the original controls'''
|
|
|
|
scene = context.scene
|
|
frame_current = scene.frame_current
|
|
smartfcus = dict()
|
|
smartframes = set()
|
|
action_frames_remove = dict()
|
|
#Use this to check if an fcurve exist also in the original action (and not just on a layer)
|
|
org_action_fcus = set()
|
|
transformations = ["rotation_quaternion","rotation_euler", "rotation_axis_angle", "location", "scale"]
|
|
|
|
#Store all the fcurves data and the frame count
|
|
for posebone in posebones:
|
|
#Boolean to check if an fcurve was found
|
|
setup_id = posebone.btc.setup_id
|
|
#if the bone is not part of the setup ids then skip
|
|
if setup_id not in ids:
|
|
continue
|
|
# find_fcu = False
|
|
path = posebone.path_from_id()
|
|
|
|
if 'EMPTY' in posebone.btc.setup:
|
|
ctrl_rig = find_empty_ctrl(context, posebone)
|
|
else:
|
|
ctrl_rig = posebone.id_data.animtoolbox.controller
|
|
|
|
actions = {org_action}
|
|
get_bone_layers_actions(posebone, actions)
|
|
#Get smartfcurves from the Original Bones
|
|
for action in actions:
|
|
fcurves = Tools.get_fcurves_channelbag(posebone.id_data, action)
|
|
for fcu in fcurves:
|
|
if path not in fcu.data_path:
|
|
continue
|
|
#apply the bake only to the transformation channels
|
|
if fcu.data_path.split('"].')[-1] not in transformations:
|
|
continue
|
|
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
|
continue
|
|
if posebone.btc.setup in {'TRACK_TO', 'TRACK_TO_EMPTY'} and 'rotation' not in fcu.data_path:
|
|
continue
|
|
if scene.btc.from_origin:
|
|
smartfcurve = get_smartfcurve(fcu, smartfcus, posebone, fcu.data_path, fcu.array_index, scene.btc.smartbake)
|
|
|
|
if smartfcurve is None:
|
|
continue
|
|
#if it's not the original action then create a new fcurve in org_action where it's baked to and assign it to smartfcurve
|
|
#Usefull especially when working inside a new empty layer
|
|
if action != org_action and (fcu.data_path, fcu.array_index) not in org_action_fcus:
|
|
fcurves_container = Tools.get_fcurves_container(posebone.id_data, org_action)
|
|
if fcurves_container is None:
|
|
fcurves_container = Tools.add_channelbag(posebone.id_data, org_action)
|
|
# check if the fcurve exists in the original action, and if not then create one
|
|
org_fcu = smartbake_add_fcurve(fcurves_container, fcu.data_path, fcu.array_index, fcu.group.name)
|
|
smartfcurve.fcu = org_fcu
|
|
#Add frames from other smartfcurves, need to check if I need to move this after the first iteration
|
|
org_action_fcus.add((org_fcu.data_path, org_fcu.array_index))
|
|
|
|
#clean the fcurve in case there are issues with the fcurve like double keyframes
|
|
# if action == org_action:
|
|
# fcurves_remove_keys(scene.btc, fcu)
|
|
|
|
fcu.select = True
|
|
fcu.hide = False
|
|
|
|
if ctrl_rig is None:
|
|
continue
|
|
|
|
frames_remove = get_enabled_keyframes(ctrl_rig, action_frames_remove)
|
|
|
|
#Getting the Fcurve container, either action or channelbag
|
|
fcurves_container = Tools.get_fcurves_container(posebone.id_data, org_action)
|
|
if not fcurves_container:
|
|
fcurves_container = Tools.add_channelbag(posebone.id_data, org_action)
|
|
|
|
if scene.btc.from_ctrl:
|
|
if not ctrl_rig.animation_data.action:
|
|
new_action = bpy.data.actions.new(name = 'Temp_Ctrls_' + action.name)
|
|
ctrl_rig.animation_data.action = new_action
|
|
Tools.add_slot_to_animdata(ctrl_rig.animation_data)
|
|
|
|
ctrl_actions = {ctrl_rig.animation_data.action}
|
|
if ctrl_rig.type == 'ARMATURE':
|
|
for ctrl_bone in ctrl_rig.pose.bones:
|
|
if ctrl_bone.btc.setup_id != setup_id:
|
|
continue
|
|
ctrl_path = ctrl_bone.path_from_id()
|
|
get_bone_layers_actions(ctrl_bone, ctrl_actions)
|
|
else:
|
|
#for empties get the layers from the empty ctrl
|
|
get_bone_layers_actions(ctrl_rig, ctrl_actions)
|
|
ctrl_path = ''
|
|
|
|
for ctrl_action in ctrl_actions:
|
|
#iterate over ctrl rig fcurves
|
|
fcurves = Tools.get_fcurves_channelbag(ctrl_rig, ctrl_action)
|
|
for ctrl_fcu in fcurves:
|
|
#find the fcurve
|
|
if ctrl_path not in ctrl_fcu.data_path:
|
|
continue
|
|
if 'TRACK_TO' in posebone.btc.setup and not 'rotation' in ctrl_fcu.data_path:
|
|
continue
|
|
#bake only transformation channels
|
|
if ctrl_fcu.data_path.split('"].')[-1] not in transformations:
|
|
continue
|
|
|
|
#change the path to the original bone path
|
|
#Get the original path either using bones or empties
|
|
origin_data_path = ctrl_fcu.data_path.replace(ctrl_path, path) if ctrl_path else path + '.' + ctrl_fcu.data_path
|
|
|
|
origin_data_path = replace_rot_mode(origin_data_path, posebone)
|
|
|
|
#if it's using euler and has too many array coming from quaternion, then skip
|
|
if ctrl_fcu.array_index > 2 and origin_data_path.split('"].')[-1] == 'rotation_euler':
|
|
continue
|
|
|
|
#fcurve was not found, get the keyframes from the ctrl bone
|
|
#update the smartfcurve frames from the controlers
|
|
smartfcurve = smartfcus.get((origin_data_path, ctrl_fcu.array_index))
|
|
if smartfcurve:
|
|
smartfcurve.update_frames(ctrl_fcu, scene.btc.smartbake)
|
|
else:
|
|
#Add the fcurve if it's in the ctrl but not in the original aaction
|
|
fcu = smartbake_add_fcurve(fcurves_container, origin_data_path, ctrl_fcu.array_index, posebone.name)
|
|
smartfcurve = get_smartfcurve(ctrl_fcu, smartfcus, posebone, origin_data_path, ctrl_fcu.array_index, scene.btc.smartbake)
|
|
if smartfcurve : smartfcurve.fcu = fcu
|
|
|
|
#getting the group name from posebone instead of the ctrl group name
|
|
smartfcurve.group = posebone.name
|
|
|
|
if (origin_data_path, ctrl_fcu.array_index) not in smartfcus:
|
|
continue
|
|
|
|
#get only the modifiers from the controlers to the existing fcurves
|
|
smartfcurve = smartfcus[(origin_data_path, ctrl_fcu.array_index)]
|
|
for mod in ctrl_fcu.modifiers:
|
|
smartfcurve.modifiers.append(mod)
|
|
|
|
#Remove frames to not include in the bake, in case of custom frame range
|
|
if frames_remove and smartfcurve:
|
|
smartframes = {frame for frame in smartframes if round(frame) not in frames_remove}
|
|
smartfcurve.frames = [frame for frame in smartfcurve.frames if round(frame) not in frames_remove]
|
|
smartfcurve.interpolations = {frame : interpolation for frame, interpolation in smartfcurve.interpolations.items() if round(frame) not in frames_remove}
|
|
|
|
#need to add the fcurves
|
|
add_missing_fcurves(org_action, posebone, smartfcus)
|
|
|
|
smartframes = get_smartframes(scene, smartfcus)
|
|
smartframes = sorted(smartframes)
|
|
|
|
#write all the keyframes
|
|
smartbake_write_keyframes(scene, smartframes, smartfcus, posebones = posebones)
|
|
|
|
add_interpolations(smartfcus)
|
|
|
|
scene.frame_set(frame_current)
|
|
|
|
def remove_tempctrls(context, self, controllers, controlled_objs, ids):
|
|
'''reusing these sequence of rebake and remove ctrls both for cleanup and during bake'''
|
|
|
|
scene = context.scene
|
|
if scene.btc.target == 'SELECTED':
|
|
self.smartbake = context.scene.btc.smartbake
|
|
for rig in controllers:
|
|
if rig.type != 'ARMATURE':
|
|
continue
|
|
|
|
#Hide non relevant collections for a faster bake
|
|
temp_col = find_temp_ctrl_collection(scene, controllers)
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(temp_col, context, controlled_objs)
|
|
|
|
rebake_bones, root_bone = get_rebake_bones(rig, ids)
|
|
|
|
if context.scene.btc.rebake_to_org:
|
|
rebake_connection_to_org(self, context, ids, rebake_bones, root_bone)
|
|
else:
|
|
rebake_connection_ctrls(self, context, rig, rebake_bones, root_bone, ids = ids)
|
|
|
|
include_collections(included_collections, hidden_objs, temp_col, controlled_objs)
|
|
|
|
# rebake_connection_to_org(self, context, controllers, ids)
|
|
remove_selected(context, ids, controllers)
|
|
else:
|
|
remove_ctrls(context)
|
|
remove_collection()
|
|
|
|
switch_to_original_obj(context, controlled_objs)
|
|
|
|
def remove_collection():
|
|
for col in bpy.data.collections:
|
|
if 'Ctrl_Helpers' in col.name and not len(col.objects):
|
|
bpy.data.collections.remove(col)
|
|
|
|
def remove_empty(btc, empty, ctrl_rig_bones):
|
|
'''Remove worldspace empty, and it's action or slot and registration from ctrl items'''
|
|
|
|
action = None
|
|
if empty.animation_data:
|
|
action = empty.animation_data.action
|
|
|
|
empty_index = next((i for i, item in enumerate(btc.ctrl_items) if empty == item.controller), None)
|
|
if empty_index != None:
|
|
btc.ctrl_items.remove(empty_index)
|
|
|
|
if empty.children:
|
|
child_index = next((i for i, item in enumerate(btc.ctrl_items) if empty.children[0] == item.controller), None)
|
|
btc.ctrl_items.remove(child_index)
|
|
bpy.data.objects.remove(empty.children[0])
|
|
|
|
ctrl_rig_bones.pop(empty)
|
|
bpy.data.objects.remove(empty)
|
|
|
|
if action:
|
|
if hasattr(action, 'slots'):
|
|
Tools.remove_empty_slots(action)
|
|
if len(action.slots):
|
|
return
|
|
bpy.data.actions.remove(action)
|
|
|
|
def get_relative_selected_objects(context):
|
|
#get the selected object from the bones or empties
|
|
selected_objects = set()
|
|
if context.selected_pose_bones:
|
|
selected_objects = {bone.id_data for bone in context.selected_pose_bones}
|
|
if context.selected_objects:
|
|
#get selected objects with empties
|
|
selected_objects = selected_objects.union({obj for obj in context.selected_objects if obj.animtoolbox.setup_id})
|
|
for obj in selected_objects.copy():
|
|
if obj.animtoolbox.controller:
|
|
selected_objects.add(obj.animtoolbox.controller)
|
|
elif obj.animtoolbox.controlled:
|
|
selected_objects.add(obj.animtoolbox.controlled)
|
|
|
|
for item in context.scene.btc.ctrl_items:
|
|
if item.controlled in selected_objects:
|
|
selected_objects.add(item.controller)
|
|
if item.controller in selected_objects:
|
|
selected_objects.add(item.controlled)
|
|
|
|
return selected_objects
|
|
|
|
def remove_ctrls(context, selected = None):
|
|
scene = context.scene
|
|
i = 0
|
|
selected_objects = get_relative_selected_objects(context)
|
|
|
|
#for ctrl in bpy.data.objects:
|
|
while i < len(scene.btc.ctrl_items):
|
|
ctrl_item = scene.btc.ctrl_items[i]
|
|
if selected:
|
|
if ctrl_item.controller != selected:
|
|
i += 1
|
|
continue
|
|
if ctrl_item.controller is None:
|
|
i += 1
|
|
continue
|
|
|
|
if scene.btc.target == 'RELATIVE':
|
|
if ctrl_item.controller not in selected_objects and ctrl_item.controlled not in selected_objects:
|
|
i += 1
|
|
continue
|
|
|
|
ctrl_rig = ctrl_item.controller
|
|
if ctrl_rig is not None:
|
|
org_rig = ctrl_item.controlled
|
|
all_ids = ctrl_rig.animtoolbox.ctrl_setups.keys()
|
|
|
|
#remove the controller from the scene btc list
|
|
scene.btc.ctrl_items.remove(i)
|
|
if ctrl_rig is None:
|
|
continue
|
|
|
|
#remove the related action
|
|
if ctrl_rig.animation_data is not None:
|
|
if ctrl_rig.animation_data.action is not None:
|
|
bpy.data.actions.remove(ctrl_rig.animation_data.action)
|
|
|
|
if ctrl_rig.type == 'ARMATURE':
|
|
bpy.data.armatures.remove(ctrl_rig.data)
|
|
else:
|
|
bpy.data.objects.remove(ctrl_rig)
|
|
|
|
#Reset all the org_ids from the original objects
|
|
if org_rig is None:
|
|
continue
|
|
if org_rig.type != 'ARMATURE':
|
|
continue
|
|
|
|
for bone in org_rig.pose.bones:
|
|
if bone is None:
|
|
continue
|
|
if str(bone.btc.org_id) in all_ids:
|
|
bone.btc.org_id = 0
|
|
if str(bone.btc.setup_id) in all_ids:
|
|
bone.btc.setup_id = 0
|
|
bone.btc.setup = 'NONE'
|
|
bone.btc.org = 'NONE'
|
|
|
|
def remove_constraints(context, ids, controllers, controlled_objs):
|
|
|
|
#add children to controllers
|
|
ctrls = list(controllers) + [ctrl.children[0] for ctrl in controllers if ctrl.children]
|
|
|
|
scene = context.scene
|
|
|
|
for obj in controlled_objs:
|
|
if obj.type != 'ARMATURE':
|
|
continue
|
|
for bone in obj.pose.bones:
|
|
if scene.btc.target == 'SELECTED':
|
|
if bone.btc.setup_id not in ids:
|
|
continue
|
|
else:
|
|
#check if the bone has anything assigned to it
|
|
if not bone.btc.setup_id:
|
|
continue
|
|
|
|
#reset the setup_id
|
|
bone.btc.setup_id = 0
|
|
bone.btc.org_id = 0
|
|
bone.btc.org = bone.btc.setup ='NONE'
|
|
|
|
for con in bone.constraints:
|
|
if not hasattr(con, 'target'):
|
|
continue
|
|
if con.target not in ctrls:
|
|
continue
|
|
con_path = con.path_from_id()
|
|
#Removing target because of a bug in Blender 4.4, otherwise it crashes when switching mode
|
|
con.target = None
|
|
bone.constraints.remove(con)
|
|
#select the original bone
|
|
obj.data.bones[bone.name].select = True
|
|
#remove any fcurve channels of the constraints
|
|
if obj.animation_data is None:
|
|
continue
|
|
if obj.animation_data.action is None:
|
|
continue
|
|
|
|
#remove fcurves related to the constraint
|
|
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
|
con_fcu = fcurves.find(con_path)
|
|
if con_fcu is not None:
|
|
fcurves.remove(con_fcu)
|
|
|
|
# remove drivers connected to the constraint
|
|
for drv in obj.animation_data.drivers:
|
|
if con_path in drv.data_path:
|
|
obj.animation_data.drivers.remove(drv)
|
|
|
|
def add_empty_controller(obj, controllers):
|
|
'''add the related empty controllers such as a parent or a child controller'''
|
|
if obj.type != 'EMPTY':
|
|
return controllers.add(obj)
|
|
if obj.animtoolbox.child:
|
|
if obj.animtoolbox.setup_id == obj.parent.animtoolbox.setup_id:
|
|
return controllers.add(obj.parent)
|
|
elif obj.children:
|
|
for child in obj.children:
|
|
if not child.animtoolbox.child:
|
|
continue
|
|
if child.animtoolbox.setup_id == obj.animtoolbox.setup_id:
|
|
return controllers.add(child)
|
|
|
|
return controllers.add(obj)
|
|
|
|
def get_controllers_controlled(context):
|
|
#get all the selected control and controlled rigs
|
|
#if it's during invoke and using setup ids then use it always as selected
|
|
select_filter = 'SELECTED' if 'setup_ids' in globals() else context.scene.btc.target
|
|
|
|
controllers = set()
|
|
controlled_objs = set()
|
|
scene = context.scene
|
|
if select_filter in ['RELATIVE', 'SELECTED']:
|
|
|
|
clean_empty_ctrl_items(context.scene)
|
|
selected_objects = get_relative_selected_objects(context) if select_filter =='RELATIVE' else context.selected_objects
|
|
# selected_objects = context.selected_objects if len(context.selected_objects) else [context.object]
|
|
for obj in selected_objects:
|
|
if obj is None:
|
|
continue
|
|
if obj.animtoolbox.controller:
|
|
#skip the root empty controller
|
|
if obj.animtoolbox.controller.animtoolbox.root:
|
|
continue
|
|
#if it has a controller then it's controlled object
|
|
controlled_objs.add(obj)
|
|
controllers.add(obj.animtoolbox.controller)
|
|
add_empty_controller(obj.animtoolbox.controller, controllers)
|
|
elif obj.animtoolbox.controlled:
|
|
#skip the root empty controller
|
|
if obj.animtoolbox.root:
|
|
continue
|
|
controlled_objs.add(obj.animtoolbox.controlled)
|
|
controllers.add(obj)
|
|
#check if it's a child empty selected, then add also it's parent
|
|
add_empty_controller(obj, controllers)
|
|
|
|
if not context.selected_pose_bones:
|
|
return controllers, controlled_objs
|
|
|
|
#in case it is using EMPTIES and the bones are selected. then check for related controls using setup ids
|
|
for bone in context.selected_pose_bones:
|
|
if bone.btc.setup not in {'EMPTY', 'TRACK_TO_EMPTY'} or not bone.btc.setup_id:
|
|
continue
|
|
id = bone.btc.setup_id
|
|
for ctrl_item in scene.btc.ctrl_items:
|
|
if not ctrl_item.controller:
|
|
continue
|
|
if ctrl_item.controlled not in selected_objects:
|
|
continue
|
|
if id == ctrl_item.controller.animtoolbox.setup_id:
|
|
add_empty_controller(ctrl_item.controller, controllers)
|
|
controlled_objs.add(ctrl_item.controlled)
|
|
else:
|
|
#collect all the controlled objects
|
|
controlled_objs = {item.controlled for item in scene.btc.ctrl_items if item.controlled}
|
|
controllers = {item.controller for item in scene.btc.ctrl_items if item.controller}
|
|
|
|
#In case controlled objs are not found and there are still bones with ids
|
|
#The add the object of this bones to reset them
|
|
if not controlled_objs and context.selected_pose_bones:
|
|
for bone in context.selected_pose_bones:
|
|
if bone.btc.setup_id or bone.btc.org_id:
|
|
controlled_objs.add(bone.id_data)
|
|
|
|
return controllers, controlled_objs
|
|
|
|
def unhide_org(obj):
|
|
#Make sure the original armature is not hidden
|
|
obj_vis = obj.hide_get()
|
|
obj_hide = obj.hide_viewport
|
|
if obj_vis or obj_hide:
|
|
obj.hide_set(False)
|
|
obj.hide_viewport = False
|
|
|
|
return obj_vis, obj_hide
|
|
|
|
def switch_to_original_obj(context, objs):
|
|
'''Switching back to the original object posemode after cleanup or bake'''
|
|
for obj in objs:
|
|
if obj is None:
|
|
continue
|
|
if obj not in context.view_layer.objects.values():
|
|
continue
|
|
unhide_org(obj)
|
|
obj.select_set(True)
|
|
context.view_layer.objects.active = obj
|
|
|
|
if obj.animtoolbox.controller:
|
|
controller_obj = obj.animtoolbox.controller
|
|
obj_vis, obj_hide = unhide_org(controller_obj)
|
|
if obj_vis or obj_hide:
|
|
if context.scene.animtoolbox.isolate_pose_mode:
|
|
context.scene.animtoolbox.isolate_pose_mode = False
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
def clean_empty_ctrl_items(scene):
|
|
'''If there are ctrl items that are missing controller or controlled then remove them'''
|
|
i=0
|
|
while i < len(scene.btc.ctrl_items):
|
|
ctrl_item = scene.btc.ctrl_items[i]
|
|
if not ctrl_item.controller or not ctrl_item.controlled:
|
|
scene.btc.ctrl_items.remove(i)
|
|
else:
|
|
i += 1
|
|
|
|
def remove_selected(context, ids, controllers):
|
|
'''Remove selected temp ctrls'''
|
|
btc = context.scene.btc
|
|
#dictionary of all the ctrl rigs with a list of the selected bones for converting to edit bones
|
|
ctrl_rig_bones = {ctrl_item.controller : set() for ctrl_item in btc.ctrl_items}
|
|
rig = None
|
|
|
|
selected = context.selected_pose_bones if context.selected_pose_bones else []
|
|
|
|
#Get all the empty controls to remove the root control in case it is the only control left
|
|
clean_empty_ctrl_items(context.scene)
|
|
empty_ctrls = [ctrl.controller for ctrl in context.scene.btc.ctrl_items if ctrl.controller.type == 'EMPTY' and not ctrl.controller.animtoolbox.child]
|
|
|
|
for rig in controllers:
|
|
#check if the rig is actually an empty with setup id
|
|
if rig is None:
|
|
continue
|
|
if rig.animtoolbox.setup_id in ids and rig.type =='EMPTY': #hasattr(rig, 'animtoolbox') and
|
|
remove_empty(btc, rig, ctrl_rig_bones)
|
|
if rig in empty_ctrls:
|
|
empty_ctrls.remove(rig)
|
|
|
|
#check if only the root is left
|
|
if len(empty_ctrls) == 1:
|
|
if empty_ctrls[0] is None:
|
|
continue
|
|
if empty_ctrls[0].animtoolbox.root:
|
|
remove_empty_item(context, empty_ctrls[0])
|
|
remove_collection()
|
|
continue
|
|
|
|
#get the rest of the selected ctrl bones and ids
|
|
for posebone in rig.pose.bones:
|
|
if posebone.btc.setup_id not in ids and posebone not in selected:
|
|
continue
|
|
ids.add(posebone.btc.setup_id)
|
|
ids.add(posebone.btc.org_id)
|
|
ctrl_rig_bones[rig].add(posebone.name)
|
|
|
|
#check if there is still controllers (in case they were already removed)
|
|
if not any(len(value) > 0 for value in ctrl_rig_bones.values()):
|
|
return {'CANCELLED'}
|
|
|
|
if rig is None:
|
|
return {'CANCELLED'}
|
|
|
|
#assign the last control rig as active object for going to edit mode
|
|
if rig in context.view_layer.objects.values():
|
|
context.view_layer.objects.active = rig
|
|
|
|
#remove channels of the control bones from the action
|
|
for rig, bonenames in ctrl_rig_bones.items():
|
|
# if rig.type != 'ARMATURE':
|
|
# continue
|
|
if rig not in context.view_layer.objects.values():
|
|
continue
|
|
# rig.select_set(True)
|
|
if not rig.animation_data:
|
|
continue
|
|
if not rig.animation_data.action:
|
|
continue
|
|
|
|
fcurves = Tools.get_fcurves_channelbag(rig, rig.animation_data.action)
|
|
for fcu in fcurves:
|
|
if len(fcu.data_path.split('"')) < 2:
|
|
continue
|
|
if fcu.data_path.split('"')[1] in bonenames:
|
|
fcurves.remove(fcu)
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
#remove the bones in edit mode
|
|
for rig in controllers:
|
|
for bone_name in ctrl_rig_bones[rig]:
|
|
if bone_name not in rig.data.edit_bones:
|
|
continue
|
|
edit_bone = rig.data.edit_bones[bone_name]
|
|
rig.data.edit_bones.remove(edit_bone)
|
|
|
|
# if the rig is empty or has only root left then remove it completly
|
|
if not len(rig.data.edit_bones) or (len(rig.data.edit_bones) == 1 and rig.pose.bones[0].btc.setup == 'ROOT'):
|
|
remove_ctrls(context, rig)
|
|
remove_collection()
|
|
continue
|
|
|
|
#remove the ctrl setups from the object
|
|
for id in ids:
|
|
remove_id(rig, id)
|
|
|
|
#Switch controller back to posemode
|
|
if context.active_object in controllers:
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
def find_existing_ids(bones):
|
|
ids = {bone.btc.setup_id for bone in bones}
|
|
return ids
|
|
|
|
def clean_bone_before_setup(context, bones):
|
|
'''Cleaning old setups before creating new setup on a bone'''
|
|
ids = {bone.btc.setup_id for bone in bones}
|
|
if not any(ids):
|
|
return False
|
|
|
|
if context.preferences.addons[__package__].preferences.clear_setup:
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
remove_constraints(context, ids, controllers, controlled_objs)
|
|
remove_selected(context, ids, controllers)
|
|
return False
|
|
else:
|
|
#Return True to just cancel the operator instead of clearing the old setup
|
|
return True
|
|
|
|
def remove_id(rig, id):
|
|
'''remove the ids from the ctrl setups'''
|
|
#find the index
|
|
index = rig.animtoolbox.ctrl_setups.find(str(id))
|
|
if index is None:
|
|
return
|
|
rig.animtoolbox.ctrl_setups.remove(index)
|
|
|
|
|
|
def get_bone_ids(context, id = 'setup_id'):
|
|
|
|
select_filter = 'SELECTED' if 'setup_ids' in globals() else context.scene.btc.target
|
|
|
|
scene = context.scene
|
|
ids = set()
|
|
|
|
if select_filter == 'ALL':
|
|
#get ids from All the controller rigs and bones
|
|
for obj in scene.objects:
|
|
if not obj.animtoolbox.controlled and obj not in context.selected_objects:
|
|
#still checking selected objects in case animtoolbox.controlled is empty
|
|
continue
|
|
if obj.type == 'ARMATURE':
|
|
for bone in obj.pose.bones:
|
|
if getattr(bone.btc, id):
|
|
ids.add(getattr(bone.btc, id))
|
|
elif obj.type == 'EMPTY' and id == 'setup_id':
|
|
if getattr(obj.animtoolbox, id):
|
|
ids.add(getattr(obj.animtoolbox, id))
|
|
|
|
return ids
|
|
|
|
if not context.selected_objects:
|
|
return ids
|
|
|
|
selected_objects = get_relative_selected_objects(context)
|
|
if select_filter == 'RELATIVE':
|
|
clean_empty_ctrl_items(context.scene)
|
|
#get all controlled or controller objects
|
|
clean_empty_ctrl_items(context.scene)
|
|
objs = [obj for item in scene.btc.ctrl_items for obj in (item.controlled, item.controller)]
|
|
#get all bones from the object that are selected and are ctrl or controlled
|
|
#ids = {getattr(bone.btc, id) for obj in objs if obj in selected_objects for bone in obj.pose.bones if getattr(bone.btc, id)}
|
|
ids = set()
|
|
for obj in objs:
|
|
if obj not in selected_objects:
|
|
continue
|
|
if obj is None:
|
|
continue
|
|
if obj.type =='ARMATURE':
|
|
for bone in obj.pose.bones:
|
|
if getattr(bone.btc, id):
|
|
ids.add(getattr(bone.btc, id))
|
|
elif getattr(obj.animtoolbox, id):
|
|
ids.add(getattr(obj.animtoolbox, id))
|
|
|
|
return ids
|
|
|
|
if select_filter == 'SELECTED':
|
|
|
|
if context.selected_pose_bones: #context.object.type == 'ARMATURE' and
|
|
ids = {getattr(bone.btc, id) for bone in context.selected_pose_bones if getattr(bone.btc, id)}
|
|
else:
|
|
ids = {getattr(obj.animtoolbox, 'setup_id') for obj in selected_objects if obj and getattr(obj.animtoolbox, 'setup_id')}
|
|
return ids
|
|
|
|
def rebake_connection_ctrls(self, context, obj, rebake_bones, parent, root_name = None, base_parent_name = None, ids = None):
|
|
#reconnecting to a new parent while constrained in world space
|
|
|
|
if not rebake_bones:
|
|
return
|
|
bones_ctrl = dict()
|
|
restore_selection = set()
|
|
|
|
#deselect objects and keep only controllers, to be able to go into edit mode when the org object is linked
|
|
for obj in context.selected_objects:
|
|
if obj.animtoolbox.controller:
|
|
#select the object back after finished rebaking
|
|
if obj.select_get: restore_selection.add(obj)
|
|
obj.select_set(False)
|
|
elif obj.animtoolbox.controlled:
|
|
context.view_layer.objects.active = obj
|
|
obj.select_set(True)
|
|
|
|
#if there is no active controller selected, then get from the controlled.
|
|
if not context.view_layer.objects.active and restore_selection:
|
|
controlled = next(iter(restore_selection))
|
|
ctrl_obj = controlled.animtoolbox.controller
|
|
if not ctrl_obj:
|
|
return
|
|
context.view_layer.objects.active = ctrl_obj
|
|
ctrl_obj.select_set(True)
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
#Bones in this case are the original Temp ctrls, adding a duplicate as a new ctrl
|
|
for bone in rebake_bones:
|
|
obj = bone.id_data
|
|
edit_bone = obj.data.edit_bones[bone.name]
|
|
# TEMPIK connection has an exception because of the linking mch bone
|
|
if bone.btc.org == 'MCH' and bone.btc.setup == 'TEMPIK' and parent:
|
|
edit_bone.parent = obj.data.edit_bones[parent.name]
|
|
continue
|
|
if obj.pose.bones[bone.name].btc.child:
|
|
continue
|
|
ctrl = add_ctrl_bone(obj, edit_bone, None)
|
|
bones_ctrl.update({bone : ctrl.name})
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
if not bones_ctrl:
|
|
for obj in restore_selection:
|
|
obj.select_set(True)
|
|
return
|
|
|
|
#constraining the duplicate ctrl to the original ctrl to copy animation in worldspace
|
|
for bone, ctrlname in bones_ctrl.items():
|
|
ctrl = obj.pose.bones[ctrlname]
|
|
constraint_add(ctrl, bone)
|
|
#Adding all the ids for the smartbake to work
|
|
ctrl.btc.org_id = bone.btc.org_id
|
|
ctrl.btc.setup_id = bone.btc.setup_id
|
|
ctrl.rotation_mode = bone.rotation_mode
|
|
bones_ctrl.update({bone : ctrl})
|
|
|
|
bones, ctrls = zip(*bones_ctrl.items())
|
|
|
|
#Bake the new temporary ctrls of the connection ctrl
|
|
|
|
smartbake_to_ctrls(context, self, bones, ctrls, obj)
|
|
constraints_clear(ctrls)
|
|
|
|
#setup the new parent for the original connection bone
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
if parent:
|
|
parent_editbone = obj.data.edit_bones[parent.name]
|
|
|
|
if root_name:
|
|
#assign root to the base bone
|
|
if root_name in obj.data.edit_bones:
|
|
root_bone = obj.data.edit_bones[root_name]
|
|
|
|
#remove parent from base bone
|
|
if base_parent_name:
|
|
if base_parent_name in obj.data.edit_bones:
|
|
edit_bone = obj.data.edit_bones[base_parent_name]
|
|
edit_bone.parent = root_bone
|
|
|
|
for bone in bones:
|
|
edit_bone = obj.data.edit_bones[bone.name]
|
|
|
|
if parent:
|
|
#if the root parent is being removed
|
|
if ids and parent.btc.setup_id in ids:
|
|
edit_bone.parent = None
|
|
continue
|
|
if edit_bone == parent_editbone:
|
|
continue
|
|
edit_bone.parent = parent_editbone
|
|
else:
|
|
edit_bone.parent = None
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
#constrain back to the temporary temporary ctrls
|
|
for bone, ctrl in bones_ctrl.items():
|
|
constraint_add(bone, ctrl)
|
|
#Adding all the ids for the smartbake to work
|
|
|
|
smartbake_to_ctrls(context, self, ctrls, bones, obj)
|
|
#remove only the constraints with the duplicated ctrls included inside
|
|
for bone, ctrl in zip(bones, ctrls):
|
|
for con in bone.constraints:
|
|
if ctrl.id_data == con.target and ctrl.name == con.subtarget:
|
|
bone.constraints.remove(con)
|
|
|
|
# constraints_clear(ctrls)
|
|
|
|
remove_tempctrls_channels(obj, ctrls)
|
|
|
|
#removing the temporary ctrls
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
for ctrl in ctrls:
|
|
edit_ctrl = obj.data.edit_bones[ctrl.name]
|
|
obj.data.edit_bones.remove(edit_ctrl)
|
|
|
|
if obj.animtoolbox.controlled:
|
|
obj.animtoolbox.controlled.select_set(True)
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
#restoring unselected objects
|
|
for obj in restore_selection:
|
|
obj.select_set(True)
|
|
|
|
return
|
|
|
|
def remove_tempctrls_channels(obj, ctrls):
|
|
|
|
#remove extra channels from the temporary ctrls before removing them
|
|
if not obj.animation_data:
|
|
return
|
|
if not obj.animation_data.action:
|
|
return
|
|
|
|
action = obj.animation_data.action
|
|
ctrl_paths = [ctrl.path_from_id() for ctrl in ctrls]
|
|
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
|
for fcu in fcurves:
|
|
path = fcu.data_path.split('"].')[0] + ('"]')
|
|
if path in ctrl_paths:
|
|
fcurves.remove(fcu)
|
|
|
|
def get_rebake_bones(rig, ids):
|
|
#get all the bones with connection (Ctrls and their children)
|
|
rebake_bones = []
|
|
rebake_org_ids = set()
|
|
root_bone = None
|
|
|
|
# for rig in controllers:
|
|
for ctrlbone in rig.pose.bones:
|
|
if ctrlbone.btc.setup == 'ROOT':
|
|
root_bone = ctrlbone
|
|
if ctrlbone.btc.setup_id in ids:
|
|
continue
|
|
if not ctrlbone.btc.setup_id:
|
|
continue
|
|
if not ctrlbone.parent:
|
|
continue
|
|
if ctrlbone.parent.btc.setup_id in ids:
|
|
rebake_bones.append(ctrlbone)
|
|
rebake_org_ids.add(ctrlbone.btc.org_id)
|
|
|
|
# get child bones with the same org_id
|
|
for child in ctrlbone.children:
|
|
if child.btc.org_id == ctrlbone.btc.org_id:
|
|
rebake_bones.append(child)
|
|
|
|
return rebake_bones, root_bone
|
|
|
|
def rebake_connection_to_org(self, context, ids, rebake_bones, root_bone):
|
|
#checking for bones that need to be baked in case they are connected to a removed chain
|
|
|
|
#get the bone that is directly connected
|
|
# get_rebake_bones(controllers, ids)
|
|
if not rebake_bones:
|
|
return
|
|
|
|
self.smartbake = context.scene.btc.smartbake
|
|
#Relink rebaked bones to the world or root
|
|
for ctrlbone in rebake_bones:
|
|
if not ctrlbone.parent:
|
|
continue
|
|
if ctrlbone.parent.btc.setup_id == ctrlbone.btc.setup_id:
|
|
continue
|
|
rig = ctrlbone.id_data
|
|
if context.mode != 'EDIT':
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
if root_bone:
|
|
root_editbone = rig.data.edit_bones[root_bone.name]
|
|
|
|
ctrl_editbone = rig.data.edit_bones[ctrlbone.name]
|
|
if root_bone:
|
|
ctrl_editbone.parent = root_editbone if root_bone.btc.setup_id not in ids else None
|
|
|
|
|
|
if context.mode != 'POSE':
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
#relations between bones that are contrained to bones
|
|
con_bones_to_ctrls = {}
|
|
#Relations between ctrls that are baked from a certain bone
|
|
bake_bones_to_ctrl = {}
|
|
#Find the relative bones and constrain the ctrls back to the original armature bones
|
|
|
|
for ctrlbone in rebake_bones:
|
|
org_id = ctrlbone.btc.org_id
|
|
rig = ctrlbone.id_data
|
|
org_obj = rig.animtoolbox.controlled
|
|
found_org_bone = False
|
|
#find the original bone from the original armature
|
|
for org_bone in org_obj.pose.bones:
|
|
if org_bone.btc.org_id == org_id:
|
|
found_org_bone = True
|
|
break
|
|
if not found_org_bone:
|
|
continue
|
|
|
|
#remove constraints from the original bone
|
|
for con in org_bone.constraints:
|
|
if con.subtarget == ctrlbone.name and con.target == rig:
|
|
org_bone.constraints.remove(con)
|
|
con_bones_to_ctrls.update({org_bone : ctrlbone})
|
|
|
|
#constrain the control back to the bone
|
|
if ctrlbone.btc.setup == 'TEMPIK':
|
|
if ctrlbone.btc.org == 'MCH':
|
|
con = constraint_add(ctrlbone, org_bone)
|
|
|
|
#for flipped fk constrain the org bones to the original armature bones
|
|
elif ctrlbone.btc.setup == 'TEMPFK_FLIP':
|
|
if ctrlbone.btc.org == 'ORG':
|
|
con = constraint_add(ctrlbone, org_bone)
|
|
else:
|
|
if not ctrlbone.btc.child:
|
|
con = constraint_add(ctrlbone, org_bone)
|
|
bake_bones_to_ctrl.update({org_bone : ctrlbone})
|
|
|
|
if not con_bones_to_ctrls:
|
|
return
|
|
smartbake_to_ctrls(context, self, bake_bones_to_ctrl.keys(), bake_bones_to_ctrl.values())
|
|
|
|
# remove constraint from the ctrls and constraint the original bones back
|
|
for ctrlbone in bake_bones_to_ctrl.values():
|
|
for con in ctrlbone.constraints:
|
|
ctrlbone.constraints.remove(con)
|
|
for org_bone, ctrlbone in con_bones_to_ctrls.items():
|
|
con = constraint_add(org_bone, ctrlbone)
|
|
|
|
def controllers_remove_child(ctrl, controllers):
|
|
#If both child and parent are inside then keep only the parent
|
|
if not ctrl.parent:
|
|
return controllers
|
|
if ctrl.parent.animtoolbox.root:
|
|
return controllers
|
|
if ctrl.parent in controllers:
|
|
controllers.remove(ctrl)
|
|
|
|
return controllers
|
|
|
|
class Cleanup(bpy.types.Operator):
|
|
"""Select all the bones that are constrained to the empty"""
|
|
bl_idname = "anim.remove_bones_constraints"
|
|
bl_label = "Remove_Constrains_from_Bones"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
scene = context.scene
|
|
|
|
ids = get_bone_ids(context)
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
|
|
if not scene.btc.clean_constraints:
|
|
return {'FINISHED'}
|
|
#in case the controllers are empties add their ids and if the root is selected
|
|
for ctrl in list(controllers):
|
|
if not ctrl.animtoolbox.setup_id:
|
|
continue
|
|
ids.add(ctrl.animtoolbox.setup_id)
|
|
controllers_remove_child(ctrl, controllers)
|
|
remove_constraints(context, ids, controllers, controlled_objs)
|
|
|
|
if not scene.btc.clean_ctrls:
|
|
return {'FINISHED'}
|
|
|
|
if not ids:
|
|
return {'CANCELLED'}
|
|
|
|
remove_tempctrls(context, self, controllers, controlled_objs, ids)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def ctrl_shape_items(self, context):
|
|
'''Add all the display items for bones and empties'''
|
|
shapes = ['Cube','Sphere','Circle','Arrows','Plain_Axes','Single_Arrow','Cone']
|
|
if context.scene.btc.ctrl_type == 'BONE':
|
|
shapes.insert(0, 'Original')
|
|
shapes.append('Bone')
|
|
#create an item similiar to ('CIRCLE', 'Circle', 'Circle display type', 1)
|
|
items = [(shape.upper(), shape, shape + ' display type', i) for i, shape in enumerate(shapes)]
|
|
|
|
return items
|
|
|
|
def add_enabled_driver(obj, rig, con):
|
|
|
|
datapath = con.path_from_id() + '.enabled'
|
|
|
|
if not obj.animation_data:
|
|
obj.animation_data_create()
|
|
|
|
#adding the driver
|
|
drv = obj.animation_data.drivers.find(datapath)
|
|
|
|
if drv is None:
|
|
drv = obj.animation_data.drivers.new(datapath)
|
|
|
|
#If variables exiwst already from before then use it
|
|
if len(drv.driver.variables):
|
|
var = drv.driver.variables[0]
|
|
else:
|
|
var = drv.driver.variables.new()
|
|
var.name = con.name
|
|
var.targets[0].id = rig
|
|
var.targets[0].data_path = 'animtoolbox.ctrls_enabled'
|
|
|
|
return drv
|
|
|
|
class UnlinkChains(bpy.types.Operator):
|
|
"""Unparent Temp Ctrl chains while keeping the original animation"""
|
|
bl_idname = "anim.unlink_temp_chains"
|
|
bl_label = "Unlink_Temp_Chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) and context.object.mode == 'POSE'
|
|
|
|
def execute(self, context):
|
|
# obj = context.object
|
|
clean_empty_ctrl_items(context.scene)
|
|
controllers = {item.controller for item in context.scene.btc.ctrl_items if item.controller}
|
|
controlled_objs = {item.controlled for item in context.scene.btc.ctrl_items if item.controlled}
|
|
if not controllers:
|
|
return {'CANCELLED'}
|
|
self.smartbake = True
|
|
|
|
#get all the setup ids from the selected bones
|
|
setup_ids = {getattr(bone.btc, 'setup_id') for bone in context.selected_pose_bones}
|
|
|
|
#create a dictionary with the setup ids and a list of the bone for each chain
|
|
id_chains = dict()
|
|
for obj in controllers:
|
|
if obj is None:
|
|
continue
|
|
controlled_selected = obj.animtoolbox.controlled.select_get()
|
|
for bone in obj.pose.bones:
|
|
setup_id = bone.btc.setup_id
|
|
if bone.id_data not in controllers:
|
|
continue
|
|
if setup_id not in setup_ids:
|
|
continue
|
|
if bone.btc.setup == 'POLE':
|
|
continue
|
|
if bone.btc.org == 'MCH' and bone.btc.setup != 'TEMPIK':
|
|
continue
|
|
if bone.btc.org == 'ORG':
|
|
continue
|
|
if setup_id not in id_chains:
|
|
id_chains.update({setup_id : [bone]})
|
|
else:
|
|
id_chains[setup_id].append(bone)
|
|
|
|
if not len(id_chains):
|
|
return {'CANCELLED'}
|
|
|
|
temp_col = find_temp_ctrl_collection(context.scene, controllers)
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(temp_col, context, controlled_objs)
|
|
|
|
#get the bones that need to be parented
|
|
bones_to_link = []
|
|
for setup_id, bones in id_chains.items():
|
|
|
|
hierarchies = get_hierarchies(bones)
|
|
hierarchy = [bone for hierarchy in hierarchies for bone in hierarchy]
|
|
|
|
if hierarchy[-1].parent is None:
|
|
continue
|
|
if not hierarchy[-1].parent.btc.setup_id:
|
|
continue
|
|
bones_to_link.append(hierarchy[-1])
|
|
|
|
rebake_connection_ctrls(self, context, obj, bones_to_link, None)
|
|
|
|
if obj.animtoolbox.controlled:
|
|
obj.animtoolbox.controlled.select_set(controlled_selected)
|
|
|
|
include_collections(included_collections, hidden_objs, temp_col, controlled_objs)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class LinkTempChains(bpy.types.Operator):
|
|
"""Connect different chains of temp ctrls connected with each other while keeping the original animation"""
|
|
bl_idname = "anim.link_temp_chains"
|
|
bl_label = "Link_Temp_Chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) and context.object.mode == 'POSE'
|
|
|
|
def execute(self, context):
|
|
# obj = context.object
|
|
clean_empty_ctrl_items(context.scene)
|
|
controllers = {item.controller for item in context.scene.btc.ctrl_items if item.controller}
|
|
controlled_objs = {item.controlled for item in context.scene.btc.ctrl_items if item.controlled}
|
|
if not controllers:
|
|
return {'CANCELLED'}
|
|
self.smartbake = True
|
|
|
|
temp_col = find_temp_ctrl_collection(context.scene, controllers)
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(temp_col, context, controlled_objs)
|
|
|
|
#get all the setup ids from the selected bones
|
|
setup_ids = {getattr(bone.btc, 'setup_id') for bone in context.selected_pose_bones}
|
|
|
|
root_bone_name = None
|
|
|
|
#create a dictionary with the setup ids and a list of the bone for each chain
|
|
id_chains = dict()
|
|
for obj in controllers:
|
|
if obj is None:
|
|
continue
|
|
controlled_selected = obj.animtoolbox.controlled.select_get()
|
|
for bone in obj.pose.bones:
|
|
setup_id = bone.btc.setup_id
|
|
if bone.id_data not in controllers:
|
|
continue
|
|
if bone.btc.setup == 'ROOT':
|
|
root_bone_name = bone.name
|
|
continue
|
|
if setup_id not in setup_ids:
|
|
continue
|
|
if bone.btc.setup == 'POLE':
|
|
continue
|
|
if bone.btc.org == 'MCH' and bone.btc.setup != 'TEMPIK':
|
|
continue
|
|
if bone.btc.org == 'ORG':
|
|
continue
|
|
if setup_id not in id_chains:
|
|
id_chains.update({setup_id : [bone]})
|
|
else:
|
|
id_chains[setup_id].append(bone)
|
|
|
|
if len(id_chains) < 2:
|
|
include_collections(included_collections, hidden_objs, self.temp_col, controlled_objs)
|
|
return {'CANCELLED'}
|
|
|
|
#Get the parent of the active chain
|
|
active_bone = obj.data.bones.active.name
|
|
parent_posebone = obj.pose.bones[active_bone]
|
|
parent_chain_id = parent_posebone.btc.setup_id
|
|
parent_chain = id_chains[parent_chain_id]
|
|
hierarchies = get_hierarchies(parent_chain)
|
|
# hierarchy = [bone for hierarchy in hierarchies for bone in hierarchy]
|
|
|
|
if parent_posebone.btc.setup == 'TEMPIK' and context.scene.btc.link_to == 'BASE':
|
|
#get hierarchy only from Mechanical bones in case of TempIK
|
|
hierarchy = [bone for hierarchy in hierarchies for bone in hierarchy if bone.btc.org =='MCH']
|
|
else:
|
|
#normally get the hierarchy only from the ctrl bones
|
|
hierarchy = [bone for hierarchy in hierarchies for bone in hierarchy if bone.btc.org =='CTRL']
|
|
|
|
#parent chains to the head or the tip of
|
|
parent_bone = hierarchy[0] if context.scene.btc.link_to == 'TIP' else hierarchy[-1]
|
|
if parent_bone.name not in obj.pose.bones:
|
|
continue
|
|
|
|
#get the bones that need to be parented
|
|
bones_to_link = []
|
|
base_parent_name = None
|
|
|
|
for setup_id, bones in id_chains.items():
|
|
|
|
hierarchies = get_hierarchies(bones)
|
|
hierarchy = [bone for hierarchy in hierarchies for bone in hierarchy]
|
|
|
|
if setup_id == parent_chain_id:
|
|
if not hierarchy[-1].parent:
|
|
continue
|
|
#
|
|
if hierarchy[-1].parent.btc.setup_id not in id_chains:
|
|
continue
|
|
base_parent_name = hierarchy[-1].name
|
|
|
|
#Get the base bone of the hierarchy, unless it's ik setup than get the IK Ctrl
|
|
if hierarchy[-1].btc.setup == 'TEMPIK':
|
|
bone_to_link = hierarchy[-1] if hierarchy[-1].btc.org == 'CTRL' else hierarchy[0]
|
|
else:
|
|
bone_to_link = hierarchy[-1]
|
|
|
|
bones_to_link.append(bone_to_link)
|
|
|
|
rebake_connection_ctrls(self, context, obj, bones_to_link, parent_bone, root_bone_name, base_parent_name)
|
|
|
|
if obj.animtoolbox.controlled:
|
|
obj.animtoolbox.controlled.select_set(controlled_selected)
|
|
|
|
include_collections(included_collections, hidden_objs, temp_col, controlled_objs)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def target_bone_position(self, context):
|
|
'''get the bone position for track constraint depending on the Axis'''
|
|
global rig, ctrls, child_names, selected
|
|
if not 'ctrls' in globals():
|
|
return
|
|
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
if child_names:
|
|
#get the child before changing the ctrl to edit bone
|
|
childname = child_names[ctrls.index(ctrl)]
|
|
|
|
if rig.mode == 'EDIT':
|
|
ctrl = rig.data.edit_bones[ctrl.name]
|
|
org_rig = bone.id_data
|
|
if bone.name in org_rig.data.edit_bones:
|
|
bone = org_rig.data.edit_bones[bone.name]
|
|
else:
|
|
bone = org_rig.data.bones[bone.name]
|
|
|
|
matrix = get_bone_matrix(bone)
|
|
if type(bone.id_data) == bpy.types.Object:
|
|
matrix = bone.id_data.matrix_world @ matrix
|
|
ctrl.matrix = matrix
|
|
|
|
if self.bake_loc != 'TRACK':
|
|
continue
|
|
dir = -1 if self.axis[0] == '-' else 1
|
|
axis = self.axis[1]
|
|
axes = {'X' : 0, 'Y' : 1, 'Z' :2}
|
|
value = [0, 0, 0]
|
|
value[axes[axis]] = ctrl.length * self.offset * dir
|
|
local_translation = mathutils.Vector(value)
|
|
if rig.mode == 'EDIT':
|
|
world_translation = ctrl.matrix.to_3x3() @ local_translation
|
|
ctrl.tail += world_translation
|
|
ctrl.head += world_translation
|
|
elif rig.mode == 'POSE':
|
|
world_translation = ctrl.matrix_basis.to_3x3() @ local_translation
|
|
ctrl.location += world_translation
|
|
|
|
if not child_names:
|
|
continue
|
|
#apply the position to the children
|
|
child = rig.data.edit_bones[childname] if rig.mode == 'EDIT' else rig.pose.bones[childname]
|
|
child.matrix = ctrl.matrix
|
|
|
|
def remove_child_ctrls(rig, child_names):
|
|
if not child_names:
|
|
return
|
|
for child_name in child_names:
|
|
child = rig.data.edit_bones[child_name]
|
|
rig.data.edit_bones.remove(child)
|
|
return []
|
|
|
|
def child_prop_edit(self):
|
|
global ctrls, child_names, rig
|
|
|
|
child_ctrls = []
|
|
|
|
if not self.child:# and child_names:
|
|
#Remove child ctrls
|
|
child_names = remove_child_ctrls(rig, child_names)
|
|
return child_ctrls
|
|
|
|
#Add child ctrls
|
|
child_names = []
|
|
for ctrl_posebone in ctrls:
|
|
ctrl = rig.data.edit_bones[ctrl_posebone.name]
|
|
child_ctrl = add_ctrl_bone(rig, ctrl, ctrl, 'Child_', 'HEAD', 0.5, orient_world = self.orientation)
|
|
child_ctrl.matrix = ctrl.matrix
|
|
child_ctrls.append(child_ctrl)
|
|
add_bone_to_collection(rig, child_ctrl, col_name = 'Child Ctrls', visible = True)
|
|
child_names.append(child_ctrl.name)
|
|
|
|
return child_ctrls
|
|
|
|
def child_prop_pose(context):
|
|
#If there are new child bones then assign them the setup id and the shapes
|
|
global ctrls, child_names, rig
|
|
|
|
if not child_names:
|
|
return
|
|
for child_name, ctrl in zip(child_names, ctrls):
|
|
child = rig.pose.bones[child_name]
|
|
add_bone_setup_id(child, True, ctrl.btc.setup, 'CTRL', ctrl.btc.setup_id, ctrl.btc.org_id)
|
|
child.rotation_mode = ctrl.rotation_mode
|
|
if bpy.app.version < (4, 0, 0):
|
|
child.bone_group = ctrl.bone_group
|
|
else:
|
|
child.color.palette = context.scene.btc.color_set
|
|
|
|
tempctrl_shape_type(context.scene.btc, context)
|
|
|
|
def child_prop(self, context):
|
|
'''Add or remove child ctrls using the property in the ui panel'''
|
|
global ctrls, child_names, rig
|
|
# rig = self.rig
|
|
|
|
if rig.mode != 'EDIT':
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
child_ctrls = child_prop_edit(self)
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
child_prop_pose(context)
|
|
|
|
def flip_prop(self, context):
|
|
|
|
global ctrls, selected, rig, child_names
|
|
|
|
if self.flip:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
for ctrl in ctrls:
|
|
ctrl_edit = rig.data.edit_bones[ctrl.name]
|
|
flip_editbone(self, ctrl_edit)
|
|
if self.child:
|
|
child_name = child_names[ctrls.index(ctrl)]
|
|
#Flip only the head since the tail is in the middle
|
|
child_edit = rig.data.edit_bones[child_name]
|
|
child_edit.head = ctrl_edit.head
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
flip_posebone(context, ctrl, bone)
|
|
|
|
else:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
ctrl_edit = rig.data.edit_bones[ctrl.name]
|
|
bone_matrix = get_bone_matrix(bone.bone)
|
|
# bone_edit = bone.id_data.data.edit_bones[bone.name]
|
|
ctrl_edit.matrix = bone_matrix
|
|
if self.child:
|
|
child_name = child_names[ctrls.index(ctrl)]
|
|
#Flip only the head since the tail is in the middle
|
|
child_edit = rig.data.edit_bones[child_name]
|
|
child_edit.head = ctrl_edit.head
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
ctrl.matrix = bone.id_data.matrix_world @ bone.matrix
|
|
|
|
class TempFK(bpy.types.Operator):
|
|
"""Copy and bake the animation to a temporary FK chain"""
|
|
bl_idname = "anim.bake_to_temp_fk"
|
|
bl_label = "Bake_To_Temp_FK"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bake: bpy.props.BoolProperty(name = 'Bake Temp Ctrls', description = "Bakes Temp Ctrls, keeps them constrained when unchecked", default = True)
|
|
smartbake: bpy.props.BoolProperty(name = 'Smart Bake', description = "Keeps keyframes count from selection and recalculate interpolation", default = False)
|
|
bake_loc: bpy.props.EnumProperty(name = 'Type', description="Select what type of constraints will be used", default = 'COPY',
|
|
items = [('COPY', 'Copy Transforms','Bake to the bones heads', 0), ('TRACK', 'Track Target', 'Track the bone to an axis', 1)])
|
|
child: bpy.props.BoolProperty(name = 'Add Extra Child Ctrls', description = "Add an child control for an overlay control", default = False, update = child_prop)
|
|
con_back: bpy.props.BoolProperty(name = 'Constrain selected to Temp Ctrls', description = "Constraint the selected bones back to the Temp controls", default = True)
|
|
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = False)
|
|
flip: bpy.props.BoolProperty(name = 'Flip Temp Ctrls', description = "Flipping chain bones when using reversed hierarchy", default = False, update = flip_prop)
|
|
|
|
rotation_mode: bpy.props.EnumProperty(name = 'Rotation Mode', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
|
|
items = [('PRESERVE', 'Preserve','Keep Rotation Order from the original selected bones', 0),
|
|
('QUATERNION', 'Quaternion','Quaternion Rotation Order - No Gimbal Lock', 1),
|
|
('XYZ', 'XYZ', 'XYZ Rotation Order', 2), ('XZY', 'XZY','XZY Rotation Order', 3),
|
|
('YXZ', 'YXZ','YXZ Rotation Order', 4), ('YZX', 'YZX', 'YZX Rotation Order', 5),
|
|
('ZXY', 'ZXY', 'ZXY Rotation Order', 6), ('ZYX', 'ZYX', 'ZYX Rotation Order', 7),
|
|
('AXIS_ANGLE', 'AXIS_ANGLE', 'Axis Angle Rotation Order', 8)])
|
|
|
|
filter_bool: bpy.props.BoolProperty(name = 'Filter Properties', description = "Extend all the filter elements", default = False)
|
|
filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(False, False, False), size = 3, options={'HIDDEN'})
|
|
filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(False, False, False), size = 3, options={'HIDDEN'})
|
|
filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(False, False, False), size = 3, options={'HIDDEN'})
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) and context.object.mode == 'POSE'
|
|
|
|
def invoke(self, context, event):
|
|
obj = context.object
|
|
self.org_obj = obj
|
|
if obj.type != 'ARMATURE':
|
|
self.report({'ERROR'},'Currently works only with Armature objects')
|
|
return {'CANCELLED'}
|
|
|
|
if obj.animtoolbox.controlled:
|
|
self.report({'ERROR'},'Selected object is already a control rig')
|
|
return {'CANCELLED'}
|
|
|
|
global ctrls, child_names, selected, root_name, rig, setup_ids
|
|
context.scene.btc.ctrl_type = 'BONE'
|
|
|
|
selected = context.selected_pose_bones if obj.mode == 'POSE' else []
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
if clean_bone_before_setup(context, selected):
|
|
#Clear setup is turned off, then cancelling this operator
|
|
self.report({'ERROR'},'Selected bones are already connected to temp ctrls. Clear current ctrls before continuing')
|
|
return {'CANCELLED'}
|
|
|
|
self.bone_selection = Tools.bone_selection.copy()
|
|
|
|
#check if bones need to flipped
|
|
flip = False
|
|
for i, bone in enumerate(self.bone_selection[:-1]):
|
|
bone_next = self.bone_selection[i+1]
|
|
# head_dist = mathutils.Vector([abs(axis) for axis in (bone.bone.head - bone_next.bone.tail)])
|
|
# tail_dist = mathutils.Vector([abs(axis) for axis in (bone.bone.tail - bone_next.bone.head)])
|
|
head_dist = (bone.bone.head_local - bone_next.bone.tail_local).length
|
|
tail_dist = (bone.bone.tail_local - bone_next.bone.head_local).length
|
|
|
|
if head_dist < tail_dist:
|
|
flip = True
|
|
break
|
|
|
|
root_name = None
|
|
|
|
#if there are no display shapes then use the cube as the default
|
|
if not any([bone.custom_shape for bone in selected]):
|
|
# context.scene.btc.shape_type = 'CUBE' <--- is running tempctrl_shape_type too early
|
|
context.scene.btc['shape_type'] = 1
|
|
|
|
wm = context.window_manager
|
|
|
|
bake_layers_default(obj, context.scene)
|
|
get_org_action(obj, self)
|
|
|
|
########################################
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
|
|
self.shape_type = context.scene.btc.shape_type
|
|
self.shape_size = context.scene.btc.shape_size
|
|
root_ctrl = None
|
|
|
|
rig, self.temp_col = add_ctrl_rig(selected)
|
|
#self.rig = rig
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
root_update_edit()
|
|
|
|
ctrls, child_ctrls = add_control_bones(self, selected, rig, None, self.child)
|
|
ctrl_names = [ctrl.name for ctrl in ctrls]
|
|
child_names = [child.name for child in child_ctrls]
|
|
|
|
for ctrl in ctrls:
|
|
add_bone_to_collection(rig, ctrl, col_name = 'FK Ctrls', visible = True)
|
|
if self.child:
|
|
child_ctrl = child_ctrls[ctrls.index(ctrl)]
|
|
#Flip only the head since the tail is in the middle
|
|
child_ctrl.head = ctrl.head
|
|
|
|
ctrl_names = [ctrl.name for ctrl in ctrls]
|
|
child_names = [child.name for child in child_ctrls]
|
|
|
|
# root_update(context.scene.btc, context)
|
|
|
|
###################################
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
ctrls = [rig.pose.bones[name] for name in ctrl_names]
|
|
child_ctrls = [rig.pose.bones[name] for name in child_names]
|
|
|
|
ctrl_group = add_ctrl_group(rig) if bpy.app.version < (4, 0, 0) else None
|
|
|
|
setup = 'TEMPFK'
|
|
setup_ids = set()
|
|
#assign id to root control and the position
|
|
|
|
if context.scene.btc.root:
|
|
root_update_pose(context.scene.btc, context)
|
|
|
|
setup_id = add_id(rig, setup)
|
|
setup_ids.add(setup_id)
|
|
|
|
assign_setup_ids(self, context, ctrls, selected, child_ctrls, setup, setup_id, ctrl_group)
|
|
|
|
self.flip = flip
|
|
# flip_prop(self, context)
|
|
|
|
tempctrl_shape_type(self, context)
|
|
|
|
if not self.org_action:
|
|
self.orientation = False
|
|
return wm.invoke_props_dialog(self, width = 200)
|
|
|
|
return wm.invoke_props_dialog(self, width = 250)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
btc = context.scene.btc
|
|
|
|
#Frame Range
|
|
split = layout.split(factor=0.5, align = True)
|
|
split.prop(btc, 'bake_range_type', text = '')
|
|
split.prop(btc, 'bake_layers', text = 'Include All Layers')
|
|
if btc.bake_range_type == 'CUSTOM':
|
|
row = layout.row()
|
|
row.prop(btc, 'bake_range', text = '')
|
|
layout.separator()
|
|
|
|
row = layout.row()
|
|
row.prop(btc, 'shape_size', text = 'Size')
|
|
row.prop(btc, 'shape_type', text = '')
|
|
|
|
layout.separator()
|
|
|
|
row = layout.row()
|
|
row.label(text = 'Rotation Mode:')
|
|
row.prop(self, 'rotation_mode', text = '')
|
|
layout.separator()
|
|
col = layout.column()
|
|
col.prop(self, 'smartbake')
|
|
if not self.flip:
|
|
col.prop(self, 'orientation')
|
|
col.prop(self, 'flip')
|
|
split = layout.split(factor = 0.9)
|
|
split.prop(self, 'con_back')
|
|
split.prop(self, 'filter_bool', icon = 'FILTER')
|
|
if self.filter_bool:
|
|
split = layout.split(factor = 0.75)
|
|
split.label(text = "Filter Properties")
|
|
split.label(text = "X Y Z")
|
|
Tools.filter_draw_ui(self, self, titel = False)
|
|
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text = 'Add Hierarchy:')
|
|
row = layout.row()
|
|
row.prop(btc, 'root', text = 'Root Ctrl')
|
|
row.prop(self, 'child', text = 'Extra Child Ctrl')
|
|
if btc.root:
|
|
# row = box.row(align = True)
|
|
row = layout.row()
|
|
row.prop(btc, 'root_object', text = '')
|
|
if hasattr(btc.root_object, 'type'):
|
|
if btc.root_object.type == 'ARMATURE':
|
|
# row = box.row(align = True)
|
|
row = layout.row()
|
|
row.prop_search(btc, 'root_bone', btc.root_object.pose, 'bones', text = '')
|
|
|
|
def execute(self, context):
|
|
# sec = time.time()
|
|
|
|
global ctrls, child_names, selected, root_name, rig, setup_ids
|
|
|
|
objs = {bone.id_data for bone in selected}
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(self.temp_col, context, objs)
|
|
|
|
if 'ctrls' not in globals():
|
|
self.report({'INFO'},'Not available. Please update the Temp Ctrls from the addon panel')
|
|
return {'CANCELLED'}
|
|
|
|
#create dictionary between selected bones and the temp ctrls
|
|
selected_ctrls = dict(zip(selected, ctrls))
|
|
|
|
if not context.active_object:
|
|
context.view_layer.objects.active = rig
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
# root_ctrl = rig.data.edit_bones[root_name] if root_name else None
|
|
root_ctrl = parent_ctrls_to_root(root_name, rig, ctrls)
|
|
if not self.flip:
|
|
ctrls_orientation(self, ctrls, child_names)
|
|
|
|
#setup hierarchies
|
|
self.create_hierarchy(selected, selected_ctrls, rig)
|
|
|
|
#adding helper bones
|
|
if self.flip:
|
|
orgs, rest = add_control_bones(self, selected, rig, root_ctrl, False, prefix = 'ORG_')
|
|
mchs, rest = add_control_bones(self, selected, rig, root_ctrl, False, prefix = 'MCH_')
|
|
|
|
for ctrl, mch, org in zip(ctrls, mchs, orgs):
|
|
add_bone_to_collection(rig, mch, col_name = 'MCH', visible = False)
|
|
add_bone_to_collection(rig, org, col_name = 'ORG', visible = False)
|
|
flip_editbone(self, mch)
|
|
mch.parent = org
|
|
|
|
org_names = [org.name for org in orgs]
|
|
mch_names = [mch.name for mch in mchs]
|
|
|
|
##################################
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
con_type = 'COPY_TRANSFORMS'
|
|
|
|
#Assigning rotation modes
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
ctrl.rotation_mode = bone.rotation_mode if self.rotation_mode == 'PRESERVE' else self.rotation_mode
|
|
|
|
#apply the rotation mode to the childs
|
|
if child_names:
|
|
for ctrl, childname in zip(ctrls, child_names):
|
|
rig.pose.bones[childname].rotation_mode = ctrl.rotation_mode
|
|
|
|
#if it's not a flipped fk then just bake and finish
|
|
if not self.flip:
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
constraint_add(ctrl, bone, con_type)
|
|
|
|
root_ctrl, root_org = add_root_to_bake(self, context, rig, root_name, ctrls, selected)
|
|
|
|
action = create_anim_data_action(rig, self.org_obj)
|
|
if action is not None:
|
|
smartbake_to_ctrls(context, self, selected, ctrls)
|
|
else:
|
|
set_ctrl_matrix(selected, ctrls)
|
|
|
|
constraints_clear(ctrls)
|
|
custom_range_switch(context.scene, action, rig)
|
|
#if it's using root ctrl reference then remove them from ctrls and selected, to not include anymore in the bake
|
|
if root_org:
|
|
ctrls.remove(root_ctrl)
|
|
selected.remove(root_org)
|
|
|
|
constraint_org_to_ctrl(self, ctrls, selected)
|
|
|
|
include_collections(included_collections, hidden_objs, self.temp_col, objs)
|
|
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return {'FINISHED'}
|
|
|
|
orgs = [rig.pose.bones[name] for name in org_names]
|
|
mchs = [rig.pose.bones[name] for name in mch_names]
|
|
|
|
for ctrl, mch, org, bone in zip(ctrls, mchs, orgs, selected):
|
|
#copy the ids to the helper bones
|
|
mch.btc.org_id = org.btc.org_id = ctrl.btc.org_id
|
|
mch.btc.setup_id = org.btc.setup_id = ctrl.btc.setup_id
|
|
ctrl.btc.org = 'CTRL'
|
|
org.btc.org = 'ORG'
|
|
mch.btc.org = 'MCH'
|
|
ctrl.btc.setup = org.btc.setup = mch.btc.setup = 'TEMPFK_FLIP'
|
|
ctrl.color.palette = context.scene.btc.color_set
|
|
org.custom_shape = None
|
|
|
|
#constraining the ctrls to the flipped mechanical bones that are parented to the org bones
|
|
constraint_add(org, bone, con_type)
|
|
constraint_add(ctrl, mch, con_type)
|
|
|
|
#add the root to a copy of the ctrls and selected
|
|
root_ctrl, root_org = add_root_to_bake(self, context, rig, root_name, ctrls, selected)
|
|
|
|
action = create_anim_data_action(rig, self.org_obj)
|
|
|
|
self.bake_loc = 'COPY'
|
|
if action is not None:
|
|
smartbake_to_ctrls(context, self, selected, ctrls)
|
|
else:
|
|
#If there is no action then just set the bones matrices
|
|
set_ctrl_matrix(selected, ctrls)
|
|
|
|
constraints_clear(ctrls)
|
|
constraints_clear(orgs)
|
|
|
|
custom_range_switch(context.scene, action, rig)
|
|
#if it's using root ctrl reference then remove them from ctrls and selected
|
|
if root_org:
|
|
ctrls.remove(root_ctrl)
|
|
selected.remove(root_org)
|
|
|
|
#reseting values of the org
|
|
for org in orgs:
|
|
org.location = (0,0,0)
|
|
org.rotation_euler = (0.0, 0.0, 0.0)
|
|
org.rotation_quaternion = (1.0, 0.0, 0.0, 0.0)
|
|
org.scale = (1,1,1)
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
child_ctrls = [rig.data.edit_bones[name] for name in child_names]
|
|
#switch to the child controling the original bone and reparenting the original to the child ctrl
|
|
|
|
#In this case the hierarchy and relations with the org bones
|
|
for ctrl, org, bone in zip(ctrls, orgs, selected):
|
|
ctrl_edit = rig.data.edit_bones[ctrl.name]
|
|
org_edit = rig.data.edit_bones[org.name]
|
|
if self.child and child_ctrls:
|
|
ctrl_edit = child_ctrls[ctrls.index(ctrl)]
|
|
org_edit.parent = ctrl_edit
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
include_collections(included_collections, hidden_objs, self.temp_col, objs)
|
|
|
|
if not self.con_back:
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return {'FINISHED'}
|
|
|
|
#constraint the original bones to the org bones
|
|
for org, bone in zip(orgs, selected):
|
|
if org.btc.setup == 'ROOT': #dont need to add constrain for the root bone
|
|
continue
|
|
# add constraints but with attribute filter
|
|
constraints_filter(self, bone, org, con_type)
|
|
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return {'FINISHED'}
|
|
|
|
def cancel(self, context):
|
|
cancel_tempctrls(context)
|
|
global ctrls, child_names, selected, root_name, rig, setup_ids
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return None
|
|
|
|
def create_hierarchy(self, bone_selection, selected_ctrls, rig):
|
|
#Create hierachy from the selection order
|
|
selection_order = bone_selection if self.flip else list(reversed(bone_selection))
|
|
|
|
for i, bone in enumerate(selection_order[:-1]):
|
|
if bone not in selected_ctrls:
|
|
continue
|
|
#use selected ctrls dictionary to find the ctrl
|
|
ctrl = selected_ctrls[bone]
|
|
# add_bone_to_collection(rig, ctrl, col_name = 'FK Ctrls', visible = True)
|
|
parent_bone = selection_order[i+1]
|
|
parent_ctrl = selected_ctrls[parent_bone]
|
|
if self.child:
|
|
child = selected_ctrls[parent_bone].children[0]
|
|
parent_ctrl = child
|
|
rig.data.edit_bones[ctrl.name].parent = rig.data.edit_bones[parent_ctrl.name]
|
|
|
|
def flip_editbone(self, bone):
|
|
if not self.flip:
|
|
return
|
|
bone_head, bone_tail = bone.head.copy(), bone.tail.copy()
|
|
bone.tail, bone.head = bone_head, bone_tail
|
|
|
|
def flip_posebone(context, ctrl, bone):
|
|
org_obj_mat = bone.id_data.matrix_world
|
|
ctrl.matrix = org_obj_mat @ bone.matrix
|
|
context.view_layer.update()
|
|
new_loc = ctrl.tail
|
|
rotation_matrix = mathutils.Matrix.Rotation(math.pi, 4, 'Y')
|
|
new_matrix = ctrl.matrix_basis @ rotation_matrix
|
|
ctrl.matrix_basis = new_matrix
|
|
ctrl.matrix.translation = new_loc
|
|
|
|
def fcurves_remove_bones(rig, bones):
|
|
bone_paths = [bone.path_from_id() for bone in bones]
|
|
fcurves = Tools.get_fcurves_channelbag(rig, rig.animation_data.action)
|
|
for fcu in fcurves:
|
|
path = fcu.data_path.split('"].')[0] + ('"]')
|
|
if path in bone_paths:
|
|
fcurves.remove(fcu)
|
|
|
|
def reset_root_bone_ref(self):
|
|
if not self.root_object:
|
|
return True
|
|
if not hasattr(self.root_object, 'type'):
|
|
return True
|
|
if self.root_object.type != 'ARMATURE':
|
|
return True
|
|
if self.root_bone not in self.root_object.data.bones:
|
|
return True
|
|
|
|
return False
|
|
|
|
def root_update_edit():
|
|
'''Breaking down properties during edit mode and pose mode
|
|
to be able to use it during invoke without switching so often'''
|
|
global root_name, rig
|
|
|
|
if not root_name:
|
|
root_ctrl = add_root_bone(rig)
|
|
if root_ctrl:
|
|
root_name = root_ctrl.name
|
|
# move_root_editbone(root_ctrl)
|
|
else:
|
|
root_ctrl= rig.data.edit_bones[root_name]
|
|
|
|
return root_name
|
|
|
|
def root_update_pose(self, context):
|
|
global root_name, rig, setup_ids
|
|
if reset_root_bone_ref(self):
|
|
self['root_bone'] = ''
|
|
|
|
if root_name not in rig.pose.bones:
|
|
return
|
|
|
|
root_ctrl = rig.pose.bones[root_name]
|
|
obj = self.root_object
|
|
#check if root ctrl already exists and connected to other bones
|
|
if len(root_ctrl.children):
|
|
return
|
|
#selecting the bone for the shape to apply to it as well
|
|
root_ctrl.bone.select = True
|
|
|
|
#if it doesn't have already setup id then add it in order to get the shape
|
|
if not root_ctrl.btc.setup_id:
|
|
root_id = assign_id_root(self, rig, root_ctrl)
|
|
setup_ids.add(root_id)
|
|
root_ctrl.color.palette = context.scene.btc.color_set
|
|
|
|
if not obj:
|
|
#get the cursor position also in pose mode
|
|
root_ctrl.matrix = Matrix.Identity(4)
|
|
root_ctrl.matrix.translation = context.scene.cursor.matrix.translation
|
|
return
|
|
|
|
if obj.type == 'ARMATURE' and self.root_bone in obj.data.bones:
|
|
#get the bone ref position
|
|
ref_bone = obj.pose.bones[self.root_bone]
|
|
root_ctrl.matrix = obj.matrix_world @ ref_bone.matrix
|
|
ref_bone.btc.setup_id = root_ctrl.btc.setup_id
|
|
context.view_layer.update()
|
|
else:
|
|
#get the object position
|
|
root_ctrl.matrix = obj.matrix_world
|
|
|
|
def root_update(self, context):
|
|
'''checks if the previous selected bone is included in the current selected object'''
|
|
global ctrls, root_name, rig
|
|
|
|
if not self.root:
|
|
return
|
|
|
|
#For empties run a different function
|
|
if ctrls[0].id_data.type == 'EMPTY':
|
|
empty_root_update(self, context)
|
|
return
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
if root_name:
|
|
remove_root_ctrl()
|
|
#switching to pose mode to regsiter the bone
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
root_name = None
|
|
root_update_edit()
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
root_update_pose(self, context)
|
|
|
|
tempctrl_shape_type(self, context)
|
|
|
|
def remove_root_ctrl():
|
|
'''remove the root bone and the ids connected to it'''
|
|
global rig, root_name
|
|
if root_name not in rig.data.edit_bones:
|
|
return
|
|
if rig.data.edit_bones[root_name].children:
|
|
return
|
|
root_id = rig.pose.bones[root_name].btc.setup_id
|
|
remove_id(rig, rig.pose.bones[root_name].btc.setup_id)
|
|
|
|
#remove the id from the original bone if it exist
|
|
root_org = next((bone for bone in rig.animtoolbox.controlled.pose.bones if bone.btc.setup_id == root_id), None)
|
|
if root_org:
|
|
root_org.btc.setup_id = 0
|
|
|
|
rig.data.edit_bones.remove(rig.data.edit_bones[root_name])
|
|
|
|
def root_prop(self, context):
|
|
#Add or remove the root control using the property self.root
|
|
global ctrls, child_names, root_name, rig
|
|
|
|
#For empties run a different function
|
|
if ctrls[0].id_data.type == 'EMPTY':
|
|
empty_root_prop(self, context)
|
|
return
|
|
|
|
#Adding or finiding a root bone
|
|
if self.root:
|
|
root_update(self, context)
|
|
tempctrl_shape_type(self, context)
|
|
# root_ctrl.color.palette = context.scene.btc.color_set
|
|
return
|
|
|
|
#elif not self.root then remove the root bone
|
|
if context.mode != 'EDIT':
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
# if not root_name:
|
|
# root_name = next((bone.name for bone in rig.pose.bones if bone.btc.setup == 'ROOT'), None)
|
|
# else:
|
|
if root_name:
|
|
root_ctrl = rig.data.edit_bones[root_name]
|
|
|
|
if not root_ctrl:
|
|
root_name = None
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
return
|
|
|
|
#if no one is using the root, then REMOVE it
|
|
if root_name:
|
|
remove_root_ctrl()
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
root_name = None
|
|
|
|
def assign_id_root(self, rig, root_ctrl):
|
|
root_id = add_id(rig, 'ROOT')
|
|
add_bone_setup_id(root_ctrl, False, 'ROOT', 'CTRL', setup_id = root_id)
|
|
root_ctrl.btc.setup = 'ROOT'
|
|
|
|
#add the org id to the root bone and the original bone
|
|
if self.root_bone == '':
|
|
return root_id
|
|
|
|
org_id = 0
|
|
org_bone = self.root_object.pose.bones[self.root_bone]
|
|
if org_bone.btc.org_id:
|
|
org_id = org_bone.btc.org_id
|
|
else:
|
|
org_id = add_id(rig, 'NONE', True)
|
|
|
|
org_bone.btc.org_id = root_ctrl.btc.org_id = org_id
|
|
org_bone.btc.setup = root_ctrl.btc.setup
|
|
org_bone.btc.org = root_ctrl.btc.org
|
|
|
|
return root_id
|
|
|
|
def copy_setup_properties(target, source):
|
|
'''Copy all the setup properties between bones'''
|
|
target.btc.setup = source.btc.setup
|
|
target.btc.setup_id = source.btc.setup_id
|
|
target.btc.org_id = source.btc.org_id
|
|
target.btc.org = source.btc.org
|
|
target.rotation_mode = source.rotation_mode
|
|
target.matrix = source.matrix
|
|
|
|
def assign_setup_ids(self, context, ctrls, selected, child_ctrls, setup, setup_id, ctrl_group):
|
|
'''Assign Ids and properties in Pose mode'''
|
|
|
|
#Assign group, color, matrix and setup ids in pose mode
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
obj_mat = bone.id_data.matrix_world
|
|
ctrl.matrix = obj_mat @ bone.matrix
|
|
#assign the controller to a group
|
|
if bpy.app.version < (4, 0, 0):
|
|
ctrl.bone_group = ctrl_group
|
|
else:
|
|
ctrl.color.palette = context.scene.btc.color_set
|
|
# rig.data.bones[ctrl.name].color.palette = context.scene.btc.color_set
|
|
add_bone_to_collection(rig, ctrl, col_name = 'World Space', visible = True)
|
|
|
|
org_id = add_id(rig, setup)
|
|
#Only WorldSpace controls need a new setup id on every iteration
|
|
# (since every iteration is a new chain)
|
|
if setup in {'WORLDSPACE', 'TRACK_TO'}:
|
|
setup_id = add_id(rig, setup)
|
|
|
|
add_bone_setup_id(ctrl, False, setup, 'CTRL', setup_id , org_id)
|
|
add_bone_setup_id(bone, False, setup, 'NONE', setup_id, org_id)
|
|
|
|
if self.child and child_ctrls:
|
|
child_ctrl = child_ctrls[ctrls.index(ctrl)]
|
|
# child_ctrl = ctrl.children[0]
|
|
if bpy.app.version < (4, 0, 0):
|
|
child_ctrl.bone_group = ctrl_group
|
|
else:
|
|
child_ctrl.color.palette = context.scene.btc.color_set
|
|
# rig.data.bones[child_ctrl.name].color.palette = context.scene.btc.color_set
|
|
|
|
add_bone_setup_id(child_ctrl, True, setup, 'CTRL', setup_id, org_id)
|
|
|
|
#get custom shape and assign the matrix
|
|
if bone.custom_shape_transform and self.shape_type == 'ORIGINAL':
|
|
ot_bone = bone.custom_shape_transform.name
|
|
#Get the original override transform bone
|
|
target = bone.id_data.pose.bones[ot_bone]
|
|
ctrl.custom_shape_transform = target
|
|
ctrl.custom_shape_transform.btc.setup_id = ctrl.btc.setup_id
|
|
ctrl.custom_shape_transform.matrix = obj_mat @ bone.custom_shape_transform.matrix
|
|
|
|
|
|
|
|
def parent_ctrls_to_root(root_name, rig, ctrls):
|
|
'parent the bones to root ctrl'
|
|
if not root_name:
|
|
return None
|
|
if root_name not in rig.data.edit_bones:
|
|
return None
|
|
root_ctrl = rig.data.edit_bones[root_name]
|
|
for ctrl in ctrls:
|
|
rig.data.edit_bones[ctrl.name].parent = root_ctrl
|
|
return root_ctrl
|
|
|
|
def bake_layers_default(obj, scene):
|
|
#Turn bake layers automatically if there is no active action but the layers are turned on
|
|
if not obj.animation_data:
|
|
return
|
|
if not obj.animation_data.use_nla:
|
|
return
|
|
strips = [strip for track in obj.animation_data.nla_tracks for strip in track.strips]
|
|
if obj.animation_data.action is None and len(strips):
|
|
scene.btc.bake_layers = True
|
|
|
|
#check if there is an offset, then use scene frame range by default
|
|
# for strip in strips:
|
|
# start_offset = strip.action.frame_range[0] - strip.frame_start
|
|
# strip_offset = (strip.action_frame_start - strip.frame_start - start_offset) * strip.scale + start_offset
|
|
# offset = strip.action_frame_start * strip.scale - strip.action_frame_start * strip.scale + strip_offset
|
|
# if offset:
|
|
# scene.btc.bake_range_type = 'SCENE'
|
|
# break
|
|
|
|
def find_temp_ctrl_collection(scene, controllers):
|
|
'''Find the Temp Ctrl collection'''
|
|
for ctrl in controllers:
|
|
for temp_col in scene.collection.children:
|
|
if ctrl in temp_col.objects.values():
|
|
return temp_col
|
|
return None
|
|
|
|
|
|
def exclude_collections(temp_col, context, objs):
|
|
'''Excluding all collections during bake except
|
|
for the control rig and controlled object collection.
|
|
Controlled object is moved temporarly to temp ctrl collection during bake'''
|
|
|
|
if not temp_col:
|
|
return
|
|
#set of collection that were still turned on
|
|
included_collections = set()
|
|
scene_collections = context.scene.collection.children
|
|
view_collections = context.view_layer.layer_collection.children
|
|
|
|
#Add the controlled object to the temp ctrl collection
|
|
for obj in objs:
|
|
if obj not in temp_col.objects.values():
|
|
temp_col.objects.link(obj)
|
|
|
|
#iterating parallel to scene and view collection because of different options
|
|
for scene_col, view_col in zip(scene_collections, view_collections):
|
|
#if it's temp ctrl, or temp ctrls are child collection then skip to avoid disable it
|
|
if scene_col == temp_col or temp_col in scene_col.children_recursive:
|
|
view_col.exclude = False
|
|
continue
|
|
|
|
elif not view_col.exclude:
|
|
included_collections.add(view_col)
|
|
view_col.exclude = True
|
|
|
|
return included_collections
|
|
|
|
def include_collections(included_collections, hidden_objs, temp_col, org_objs):
|
|
'''Resume collections to initial setup after bake'''
|
|
if not temp_col:
|
|
return
|
|
|
|
for col in included_collections:
|
|
col.exclude = False
|
|
for obj in hidden_objs:
|
|
if not obj.hide_viewport:
|
|
obj.hide_set(True)
|
|
|
|
#controlled object was included in the temp ctrl collection and now is being unlinked
|
|
for obj in org_objs:
|
|
if obj in temp_col.objects.values():
|
|
temp_col.objects.unlink(obj)
|
|
|
|
#In case the object is not available in any collection anymore then link it to the main collection
|
|
bpy.context.view_layer.update()
|
|
if obj not in bpy.context.view_layer.objects.values():
|
|
bpy.context.scene.collection.objects.link(obj)
|
|
|
|
def filter_draw_ui(self):
|
|
layout = self.layout
|
|
|
|
box = layout.box()
|
|
row = box.row()
|
|
|
|
if self.bake_loc != 'TRACK':
|
|
row.label(text = 'Location')
|
|
row.prop(self, 'filter_location', text = '')
|
|
row = box.row()
|
|
row.label(text = 'Rotation')
|
|
row.prop(self, 'filter_rotation', text = '')
|
|
row = box.row()
|
|
if self.bake_loc != 'TRACK':
|
|
row.label(text = 'Scale')
|
|
row.prop(self, 'filter_scale', text = '')
|
|
|
|
class WorldSpaceProperties:
|
|
'''Shared properties between worldspace controls'''
|
|
|
|
bake: bpy.props.BoolProperty(name = 'Bake Temp Ctrls', description = "Bakes Temp Ctrls, keeps them constrained when unchecked", default = True)
|
|
smartbake: bpy.props.BoolProperty(name = 'Smart Bake', description = "Keeps keyframes count from selection and recalculate interpolation", default = False)
|
|
|
|
twist: bpy.props.BoolProperty(name = 'Include Twist', description = "Use Track To constraint to include the twist rotation", default = True)
|
|
|
|
con_back: bpy.props.BoolProperty(name = 'Constrain selected to Temp Ctrls', description = "Constraint the selected bones back to the Temp controls", default = True)
|
|
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True)
|
|
rotation_mode: bpy.props.EnumProperty(name = 'Rotation Mode', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
|
|
items = [('PRESERVE', 'Preserve','Keep Rotation Order from the original selected bones', 0),
|
|
('QUATERNION', 'Quaternion','Quaternion Rotation Order - No Gimbal Lock', 1),
|
|
('XYZ', 'XYZ', 'XYZ Rotation Order', 2), ('XZY', 'XZY','XZY Rotation Order', 3),
|
|
('YXZ', 'YXZ','YXZ Rotation Order', 4), ('YZX', 'YZX', 'YZX Rotation Order', 5),
|
|
('ZXY', 'ZXY', 'ZXY Rotation Order', 6), ('ZYX', 'ZYX', 'ZYX Rotation Order', 7),
|
|
('AXIS_ANGLE', 'AXIS_ANGLE', 'Axis Angle Rotation Order', 8)])
|
|
|
|
filter_bool: bpy.props.BoolProperty(name = 'Filter Properties', description = "Extend all the filter elements", default = False)
|
|
filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(False, False, False), size = 3, options={'HIDDEN'})
|
|
filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(False, False, False), size = 3, options={'HIDDEN'})
|
|
filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(False, False, False), size = 3, options={'HIDDEN'})
|
|
|
|
|
|
|
|
class WorldSpaceCtrls(bpy.types.Operator, WorldSpaceProperties):
|
|
|
|
"""Copy and bake the animation to temporary controls"""
|
|
bl_idname = "anim.bake_to_ctrl"
|
|
bl_label = "Bake_To_Controls"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bake_loc: bpy.props.EnumProperty(name = 'Type', description="Select what type of constraints will be used", update = target_bone_position,
|
|
items = [('COPY', 'Copy Transforms','Bake to the bones heads', 0), ('TRACK', 'Track Target', 'Track the bone to an axis', 1)])
|
|
axis: bpy.props.EnumProperty(name = 'Axis', description="Which direction of the original bone the target should point", update = target_bone_position, default = 1,
|
|
items = [('+X', '+X','Target Bone the +X Axis', 0), ('+Y', '+Y', 'Target Bone the +Y Axis', 1), ('+Z', '+Z', 'Target Bone the +Z Axis', 2),
|
|
('-X', '-X','Target Bone the -X Axis', 3), ('-Y', '-Y', 'Target Bone the -Y Axis', 4), ('-Z', '-Z', 'Target Bone the -Z Axis', 5)])
|
|
offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = target_bone_position)
|
|
child: bpy.props.BoolProperty(name = 'Add Extra Child Ctrls', description = "Add an child control for an overlay control", default = False, update = child_prop)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) and context.object.mode == 'POSE'
|
|
|
|
def invoke(self, context, event):
|
|
|
|
obj = context.object
|
|
self.org_obj = obj
|
|
scene = context.scene
|
|
if obj.type != 'ARMATURE':
|
|
self.report({'ERROR'},'Currently works only with Armature objects')
|
|
return {'CANCELLED'}
|
|
|
|
if obj.animtoolbox.controlled:
|
|
self.report({'ERROR'},'Selected object is already a control rig')
|
|
return {'CANCELLED'}
|
|
|
|
# if any(bone.btc.setup_id for bone in context.selected_pose_bones):
|
|
# self.report({'ERROR'},'Selected bones already have a chain applied to them')
|
|
# return {'CANCELLED'}
|
|
|
|
global ctrls, child_names, selected, root_name, rig, setup_ids
|
|
context.scene.btc.ctrl_type = 'BONE'
|
|
selected = context.selected_pose_bones if obj.mode == 'POSE' else []
|
|
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
|
|
if clean_bone_before_setup(context, selected):
|
|
#Clear setup is turned off, then cancelling this operator
|
|
self.report({'ERROR'},'The selected bones are already connected to temp ctrls. Clean the current ctrls before proceeding')
|
|
return {'CANCELLED'}
|
|
|
|
setup_ids = set()
|
|
#if there are no display shapes then use the cube as the default
|
|
if not any([bone.custom_shape for bone in selected]):
|
|
# scene.btc.shape_type = 'CUBE'
|
|
scene.btc['shape_type'] = 1
|
|
|
|
root_name = None
|
|
self.message = None
|
|
if any([posebone.bone.use_connect for posebone in selected]):
|
|
# if 'textwrap' not in locals():
|
|
# import textwrap
|
|
self.bake_loc = 'TRACK'
|
|
self.message = 'Some of the bones are connected, using Type: Track by default.'
|
|
|
|
bake_layers_default(obj, scene)
|
|
|
|
wm = context.window_manager
|
|
|
|
get_org_action(obj, self)
|
|
|
|
self.shape_type = scene.btc.shape_type
|
|
self.shape_size = scene.btc.shape_size
|
|
root_ctrl = None
|
|
#getting the rig and its collection
|
|
rig, self.temp_col = add_ctrl_rig(selected)
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
root_ctrl = add_root_bone(rig)
|
|
if root_ctrl:
|
|
root_name = root_ctrl.name
|
|
|
|
ctrls, child_ctrls = add_control_bones(self, selected, rig, None, self.child)
|
|
ctrl_names = [ctrl.name for ctrl in ctrls]
|
|
child_names = [child.name for child in child_ctrls]
|
|
|
|
# root_update(context.scene.btc, context)
|
|
if scene.btc.root:
|
|
root_name = root_update_edit()
|
|
###################################
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
if scene.btc.root:
|
|
root_update_pose(scene.btc, context)
|
|
|
|
ctrls = [rig.pose.bones[name] for name in ctrl_names]
|
|
child_ctrls = [rig.pose.bones[name] for name in child_names]
|
|
|
|
ctrl_group = add_ctrl_group(rig) if bpy.app.version < (4, 0, 0) else None
|
|
|
|
setup = 'WORLDSPACE' if self.bake_loc == 'COPY' else 'TRACK_TO'
|
|
|
|
setup_id = None
|
|
assign_setup_ids(self, context, ctrls, selected, child_ctrls, setup, setup_id, ctrl_group)
|
|
|
|
tempctrl_shape_type(self, context)
|
|
|
|
target_bone_position(self, context)
|
|
|
|
return wm.invoke_props_dialog(self, width = 250)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
btc = context.scene.btc
|
|
|
|
#Frame Range
|
|
split = layout.split(factor=0.5, align = True)
|
|
split.prop(btc, 'bake_range_type', text = '')
|
|
split.prop(btc, 'bake_layers', text = 'Include All Layers')
|
|
if btc.bake_range_type == 'CUSTOM':
|
|
row = layout.row()
|
|
row.prop(btc, 'bake_range', text = '')
|
|
layout.separator()
|
|
|
|
row = layout.row()
|
|
row.prop(btc, 'shape_size', text = 'Size')
|
|
row.prop(btc, 'shape_type', text = '')
|
|
|
|
layout.separator()
|
|
if context.object.mode == 'POSE':
|
|
if self.message:
|
|
box = layout.box()
|
|
col = box.column(align = True)
|
|
col.alignment = 'CENTER'
|
|
col.label(text = self.message[:33])
|
|
col.label(text = self.message[33:])
|
|
|
|
row = layout.row()
|
|
row.label(text = 'Rotation Mode:')
|
|
row.prop(self, 'rotation_mode', text = '')
|
|
|
|
split = layout.split(factor = 0.4)
|
|
split.label(text = 'Type: ')
|
|
split.prop(self, 'bake_loc', text = '')
|
|
if self.bake_loc == 'TRACK':
|
|
|
|
split = layout.split(factor = 0.4)
|
|
split.label(text = 'Axis: ')
|
|
split.prop(self, 'axis', text = '')
|
|
split.prop(self, 'offset', text = '')
|
|
|
|
col = layout.column()
|
|
col.prop(self, 'twist', text = 'Include Twist')
|
|
|
|
layout.separator()
|
|
col = layout.column()
|
|
col.prop(self, 'smartbake')
|
|
col.prop(self, 'orientation')
|
|
split = layout.split(factor = 0.9)
|
|
split.prop(self, 'con_back')
|
|
split.prop(self, 'filter_bool', icon = 'FILTER')
|
|
if self.filter_bool:
|
|
split = layout.split(factor = 0.75)
|
|
split.label(text = "Filter Properties")
|
|
split.label(text = "X Y Z")
|
|
filter_draw_ui(self)
|
|
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text = 'Add Hierarchy:')
|
|
row = layout.row()
|
|
row.prop(btc, 'root', text = 'Root Ctrl')
|
|
row.prop(self, 'child', text = 'Extra Child Ctrl')
|
|
if btc.root:
|
|
# row = box.row(align = True)
|
|
row = layout.row()
|
|
row.prop(btc, 'root_object', text = '')
|
|
if hasattr(btc.root_object, 'type'):
|
|
if btc.root_object.type == 'ARMATURE':
|
|
# row = box.row(align = True)
|
|
row = layout.row()
|
|
row.prop_search(btc, 'root_bone', btc.root_object.pose, 'bones', text = '')
|
|
|
|
def execute(self, context):
|
|
# sec = time.time()
|
|
|
|
# obj = context.object
|
|
global ctrls, child_names, selected, root_name, rig, setup_ids
|
|
|
|
if 'ctrls' not in globals():
|
|
self.report({'INFO'},'Not available. Please update the Temp Ctrls from the addon panel')
|
|
return {'CANCELLED'}
|
|
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
|
|
objs = {bone.id_data for bone in selected}
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(self.temp_col, context, objs)
|
|
|
|
#store the matrices for the target ctrls before changing their position in edit mode
|
|
ctrl_matrices = dict()
|
|
for ctrl in ctrls:
|
|
ctrl_matrices.update({ctrl: ctrl.matrix.copy()})
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
parent_ctrls_to_root(root_name, rig, ctrls)
|
|
if self.bake_loc == 'TRACK':
|
|
target_bone_position(self, context)
|
|
|
|
ctrls_orientation(self, ctrls, child_names)
|
|
|
|
mch_names = add_twist_helpers(self, selected, root_name)
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
mch_bones = [rig.pose.bones[name] for name in mch_names]
|
|
|
|
setup = 'WORLDSPACE' if self.bake_loc == 'COPY' else 'TRACK_TO'
|
|
|
|
#Add the constraints to the ctrls
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
#update the setup in case it was changed to
|
|
ctrl.btc.setup = bone.btc.setup = setup
|
|
|
|
#Rotation mode works only in Pose mode, Needs to be in execute and not invoke
|
|
ctrl.rotation_mode = bone.rotation_mode if self.rotation_mode == 'PRESERVE' else self.rotation_mode
|
|
|
|
ctrl.matrix = ctrl_matrices[ctrl]
|
|
|
|
#add constraint to the custom shape bones
|
|
if ctrl.custom_shape_transform is None:
|
|
continue
|
|
if bone.custom_shape_transform and self.shape_type == 'ORIGINAL':
|
|
ot_bone = bone.custom_shape_transform.name
|
|
#Get the original override transform bone
|
|
target = bone.id_data.pose.bones[ot_bone]
|
|
ctrl.custom_shape_transform.btc.setup_id = ctrl.btc.setup_id
|
|
constraint_add(ctrl.custom_shape_transform, target, 'COPY_TRANSFORMS')
|
|
|
|
#apply the rotation mode to the childs
|
|
if child_names:
|
|
for ctrl, childname in zip(ctrls, child_names):
|
|
rig.pose.bones[childname].rotation_mode = ctrl.rotation_mode
|
|
|
|
context.view_layer.update()
|
|
|
|
setup_twist_helpers(mch_bones, selected, ctrls)
|
|
|
|
#constrain the root bone
|
|
root_ctrl, root_org = add_root_to_bake(self, context, rig, root_name, ctrls, selected)
|
|
|
|
action = create_anim_data_action(rig, self.org_obj)
|
|
|
|
if action is not None:
|
|
smartbake_to_ctrls(context, self, selected, ctrls, distance = True)
|
|
|
|
constraints_clear(ctrls)
|
|
|
|
include_collections(included_collections, hidden_objs, self.temp_col, objs)
|
|
|
|
#Constrains the selected bones back to the controls
|
|
if not self.con_back:
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return {'FINISHED'}
|
|
|
|
#if it's using root ctrl reference then remove them from ctrls and selected
|
|
if root_org:
|
|
ctrls.remove(root_ctrl)
|
|
selected.remove(root_org)
|
|
|
|
constraint_org_to_ctrl(self, ctrls, selected)
|
|
|
|
rig.select_set(True)
|
|
context.view_layer.objects.active = rig
|
|
rig.data.bones.active = rig.data.bones[ctrl.name]
|
|
if root_name:
|
|
if root_name in rig.data.bones:
|
|
rig.data.bones[root_name].select = False
|
|
|
|
custom_range_switch(context.scene, action, rig)
|
|
|
|
if hasattr(Tools, 'bone_selection'):
|
|
#Reset selection order in order to use context.selected_pose_bone[-1].bone
|
|
Tools.bone_selection = list(context.selected_pose_bones)
|
|
Tools.obj_selection = list(context.selected_objects)
|
|
# print('\nseconds\n', time.time() - sec)
|
|
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return {'FINISHED'}
|
|
|
|
def cancel(self, context):
|
|
cancel_tempctrls(context)
|
|
global ctrls, child_names, selected, root_name, rig, setup_ids
|
|
del ctrls, child_names, selected, root_name, rig, setup_ids
|
|
return None
|
|
|
|
def cancel_tempctrls(context):
|
|
global rig, setup_ids, root_name, selected
|
|
#remove control rigs and empties
|
|
controllers, controlled_objs = get_controllers_controlled(context)
|
|
remove_selected(context, setup_ids, controllers)
|
|
remove_org_bone_id(selected, setup_ids)
|
|
# remove_ctrls(context)
|
|
remove_collection()
|
|
switch_to_original_obj(context, controlled_objs)
|
|
|
|
'''
|
|
def ToggleSwitch(context, toggle):
|
|
|
|
scene = context.scene
|
|
controlled_objs = set([item.controlled for item in scene.btc.ctrl_items if item.controlled])
|
|
controllers = set([item.controller for item in scene.btc.ctrl_items if item.controller])
|
|
ids = get_bone_ids(context)
|
|
|
|
for obj in controlled_objs:
|
|
for bone in obj.pose.bones:
|
|
if bone.btc.setup_id not in ids:
|
|
continue
|
|
for con in bone.constraints:
|
|
if not hasattr(con, 'target'):
|
|
continue
|
|
if con.target not in controllers:
|
|
continue
|
|
con.enabled = toggle
|
|
'''
|
|
|
|
def custom_range_switch(scene, action, rig):
|
|
'''Adding on and off keyframes to start and end of Custom Frame Range'''
|
|
if scene.btc.bake_range_type != 'CUSTOM':
|
|
return
|
|
frame_start, frame_end = scene.btc.bake_range
|
|
fcurves = Tools.get_fcurves_channelbag(rig, action)
|
|
fcu = fcurves.find(data_path = 'animtoolbox.ctrls_enabled', index = 0)
|
|
if fcu is None:
|
|
fcu = fcurves.new('animtoolbox.ctrls_enabled')
|
|
fcu.keyframe_points.add(4)
|
|
|
|
fcu.keyframe_points[-1].co = (frame_start-1, 0)
|
|
fcu.keyframe_points[-2].co = (frame_start, 1)
|
|
fcu.keyframe_points[-3].co = (frame_end, 1)
|
|
fcu.keyframe_points[-4].co = (frame_end+1, 0)
|
|
|
|
fcu.update()
|
|
|
|
def empty_child_prop(self, context):
|
|
'''Add or remove EMPTY child ctrls using the property in the ui panel'''
|
|
global ctrls, child_ctrls
|
|
|
|
if self.child:
|
|
for ctrl in ctrls:
|
|
child_empty = add_empty(context, ctrl, 'Child_')
|
|
child_empty.parent = ctrl
|
|
child_empty.matrix_parent_inverse = ctrl.matrix_world.inverted()
|
|
child_empty.empty_display_size = ctrl.empty_display_size * 0.5
|
|
child_empty.animtoolbox.setup_id = ctrl.animtoolbox.setup_id
|
|
child_empty.animtoolbox.child = True
|
|
|
|
#define controller and controlled
|
|
child_empty.animtoolbox.controlled = ctrl.animtoolbox.controlled
|
|
register_ctrl(child_empty, ctrl.animtoolbox.controlled)
|
|
|
|
child_ctrls.append(child_empty)
|
|
|
|
elif child_ctrls:
|
|
for child in child_ctrls:
|
|
remove_empty_item(context, child)
|
|
|
|
child_ctrls = []
|
|
|
|
def empty_root_update(self, context):
|
|
global root_ctrl
|
|
|
|
if not self.root_object:
|
|
#get the cursor position also in pose mode
|
|
root_ctrl.matrix_world = Matrix.Identity(4)
|
|
root_ctrl.matrix_world.translation = context.scene.cursor.matrix.translation
|
|
return
|
|
|
|
if self.root_bone in self.root_object.data.bones:
|
|
#get the bone ref position
|
|
ref_bone = self.root_object.pose.bones[self.root_bone]
|
|
root_ctrl.matrix_world = ref_bone.matrix
|
|
ref_bone.btc.setup_id = root_ctrl.animtoolbox.setup_id
|
|
context.view_layer.update()
|
|
else:
|
|
#get the object position
|
|
root_ctrl.matrix_world = self.root_object.matrix_world
|
|
|
|
def empty_root_prop(self, context):
|
|
'''add or remove root ctrl for worldspace empties'''
|
|
global ctrls, root_ctrl
|
|
|
|
setup = 'EMPTY'
|
|
btc = context.scene.btc
|
|
obj = context.object
|
|
root = None
|
|
|
|
if btc.root:
|
|
if btc.root_object:
|
|
if btc.root_object.type == 'ARMATURE' and btc.root_bone:
|
|
root = btc.root_object.pose.bones[btc.root_bone]
|
|
else:
|
|
root = btc.root_object
|
|
|
|
root_ctrl = add_empty(context, root, nameextension = '')
|
|
root_ctrl.empty_display_size *= 2
|
|
root_ctrl.animtoolbox.controlled = obj
|
|
root_ctrl.animtoolbox.root = True
|
|
register_ctrl(root_ctrl, obj)
|
|
|
|
if btc.root_bone:
|
|
root_ctrl.select_set(True)
|
|
|
|
setup_id = add_id(root_ctrl, setup)
|
|
setup_ids.add(setup_id)
|
|
root_ctrl.animtoolbox.setup_id = setup_id
|
|
|
|
if root:
|
|
if type(root) == bpy.types.PoseBone:
|
|
root.btc.setup_id = setup_id
|
|
root.btc.setup = setup
|
|
|
|
elif root_ctrl:
|
|
remove_empty_item(context, root_ctrl)
|
|
root_ctrl = None
|
|
|
|
def target_empty_position(self, context):
|
|
global ctrls, child_ctrls, selected
|
|
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
|
|
matrix = get_bone_matrix(bone)
|
|
ctrl.matrix_world = matrix
|
|
|
|
if self.bake_loc != 'TRACK':
|
|
continue
|
|
dir = -1 if self.axis[0] == '-' else 1
|
|
axis = self.axis[1]
|
|
axes = {'X' : 0, 'Y' : 1, 'Z' :2}
|
|
value = [0, 0, 0]
|
|
value[axes[axis]] = bone.length * self.offset * dir
|
|
local_translation = mathutils.Vector(value)
|
|
bone_local_translation = bone.matrix.to_3x3() @ local_translation
|
|
ctrl.matrix_world.translation = bone.matrix.translation + bone_local_translation
|
|
|
|
def remove_empty_item(context, empty):
|
|
'''remove empty from objects and from ctrl items'''
|
|
btc = context.scene.btc
|
|
|
|
empty_index = next((i for i, item in enumerate(btc.ctrl_items) if empty == item.controller), None)
|
|
if empty_index != None:
|
|
btc.ctrl_items.remove(empty_index)
|
|
bpy.data.objects.remove(empty)
|
|
|
|
class BakeToEmpties(bpy.types.Operator, WorldSpaceProperties):
|
|
"""Copy and bake the animation to temporary controls"""
|
|
bl_idname = "anim.bake_to_empties"
|
|
bl_label = "Bake_To_Empties"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bake_loc: bpy.props.EnumProperty(name = 'Type', description="Select what type of constraints will be used", update = target_empty_position,
|
|
items = [('COPY', 'Copy Transforms','Bake to the bones heads', 0), ('TRACK', 'Track Target', 'Track the bone to an axis', 1)])
|
|
axis: bpy.props.EnumProperty(name = 'Axis', description="Which direction of the original bone the target should point", update = target_empty_position, default = 1,
|
|
items = [('+X', '+X','Target Bone the +X Axis', 0), ('+Y', '+Y', 'Target Bone the +Y Axis', 1), ('+Z', '+Z', 'Target Bone the +Z Axis', 2),
|
|
('-X', '-X','Target Bone the -X Axis', 3), ('-Y', '-Y', 'Target Bone the -Y Axis', 4), ('-Z', '-Z', 'Target Bone the -Z Axis', 5)])
|
|
offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = target_empty_position)
|
|
child: bpy.props.BoolProperty(name = 'Extra Child', description = "Add an child control for an overlay control", default = False, update = empty_child_prop)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) #and context.object.mode == 'POSE'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row()
|
|
btc = context.scene.btc
|
|
row.prop(btc, 'shape_size', text = 'Size')
|
|
row.prop(btc, 'shape_type', text = '')
|
|
|
|
layout.separator()
|
|
|
|
row = layout.row()
|
|
row.label(text = 'Rotation Mode:')
|
|
row.prop(self, 'rotation_mode', text = '')
|
|
|
|
split = layout.split(factor = 0.4)
|
|
split.label(text = 'Type: ')
|
|
|
|
split.prop(self, 'bake_loc', text = '')
|
|
if self.bake_loc == 'TRACK':
|
|
split = layout.split(factor = 0.4)
|
|
split.label(text = 'Axis: ')
|
|
split.prop(self, 'axis', text = '')
|
|
split.prop(self, 'offset', text = '')
|
|
|
|
col = layout.column()
|
|
col.prop(self, 'twist', text = 'Include Twist')
|
|
|
|
layout.separator()
|
|
col = layout.column()
|
|
col.prop(self, 'smartbake')
|
|
split = layout.split(factor = 0.9)
|
|
split.prop(self, 'con_back')
|
|
split.prop(self, 'filter_bool', icon = 'FILTER')
|
|
if self.filter_bool:
|
|
split = layout.split(factor = 0.65)
|
|
split.label(text = "Filter Properties")
|
|
split.label(text = "X Y Z")
|
|
Tools.filter_draw_ui(self, self, titel = False)
|
|
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text = 'Add Hierarchy:')
|
|
row = layout.row()
|
|
row.prop(btc, 'root', text = 'Root Ctrl')
|
|
row.prop(self, 'child')
|
|
if btc.root:
|
|
row = layout.row()
|
|
row.prop(btc, 'root_object', text = '')
|
|
if hasattr(btc.root_object, 'type'):
|
|
if btc.root_object.type == 'ARMATURE':
|
|
row = layout.row()
|
|
row.prop_search(btc, 'root_bone', context.scene.btc.root_object.pose, 'bones', text = '')
|
|
|
|
def invoke(self, context, event):
|
|
obj = context.object
|
|
self.org_obj = obj
|
|
if obj.type != 'ARMATURE':
|
|
self.report({'ERROR'},'Currently works only with Armature objects')
|
|
return {'CANCELLED'}
|
|
|
|
if obj.animtoolbox.controlled:
|
|
self.report({'ERROR'},'Selected object is already a control rig')
|
|
return {'CANCELLED'}
|
|
|
|
global ctrls, selected, root_ctrl, setup_ids, child_ctrls
|
|
|
|
context.scene.btc.ctrl_type = 'EMPTY'
|
|
selected = context.selected_pose_bones if obj.mode == 'POSE' else context.selected_objects
|
|
child_ctrls = []
|
|
|
|
bake_layers_default(obj, context.scene)
|
|
get_org_action(obj, self)
|
|
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
if clean_bone_before_setup(context, selected):
|
|
#Clear setup is turned off, then cancelling this operator
|
|
self.report({'ERROR'},'Selected bones are already connected to temp ctrls. Clean the current ctrls before proceeding')
|
|
return {'CANCELLED'}
|
|
if not context.active_object:
|
|
#After cleaning the object is not active and set to object mode
|
|
context.view_layer.objects.active = obj
|
|
bpy.ops.object.mode_set(mode = 'OBJECT')
|
|
|
|
setup_ids = set()
|
|
|
|
ctrls, root_ctrl, setup_ids = add_control_empties(self, context, selected)
|
|
self.temp_col = find_temp_ctrl_collection(context.scene, ctrls)
|
|
|
|
target_empty_position(self, context)
|
|
empty_root_prop(self, context)
|
|
empty_child_prop(self, context)
|
|
|
|
# tempctrl_shape_type(self, context)
|
|
|
|
wm = context.window_manager
|
|
#launch the dialogue box
|
|
if not self.org_action:
|
|
self.orientation = False
|
|
# return wm.invoke_props_dialog(self, width = 200)
|
|
|
|
return wm.invoke_props_dialog(self, width = 200)
|
|
|
|
def execute(self, context):
|
|
global ctrls, child_ctrls, selected, root_ctrl, rig, setup_ids
|
|
|
|
if 'ctrls' not in globals():
|
|
self.report({'INFO'},'Not available. Please update the Temp Ctrls from the addon panel')
|
|
return {'CANCELLED'}
|
|
|
|
objs = {bone.id_data for bone in selected}
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(self.temp_col, context, objs)
|
|
|
|
con_type = 'COPY_TRANSFORMS'
|
|
obj = self.org_obj
|
|
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
# constraints = constraint_ctrl_to_bone(ctrl, bone, self.bake_loc, con_type)
|
|
if root_ctrl:
|
|
matrix = ctrl.matrix_world.copy()
|
|
ctrl.parent = root_ctrl
|
|
ctrl.matrix_world = matrix
|
|
ctrl.select_set(True)
|
|
|
|
#apply Rotation Mode
|
|
ctrl.rotation_mode = bone.rotation_mode if self.rotation_mode == 'PRESERVE' else self.rotation_mode
|
|
|
|
#constrain the root bone
|
|
if root_ctrl and context.scene.btc.root_bone:
|
|
root_ref = obj.pose.bones[context.scene.btc.root_bone]
|
|
root_contsraint = constraint_add(root_ctrl, root_ref)
|
|
#Assign the root setup to the original bone
|
|
root_ref.btc.setup = 'ROOT'
|
|
|
|
bpy.ops.object.mode_set(mode = 'OBJECT')
|
|
|
|
obj.select_set(False)
|
|
|
|
action = None
|
|
for ctrl in ctrls:
|
|
action = create_anim_data_action(ctrl, self.org_obj, action)
|
|
|
|
if action:
|
|
smartbake_to_ctrls(context, self, selected, ctrls, distance = True)
|
|
|
|
include_collections(included_collections, hidden_objs, self.temp_col, objs)
|
|
|
|
if not self.con_back:
|
|
del ctrls, selected, root_ctrl, setup_ids, child_ctrls
|
|
return {'FINISHED'}
|
|
|
|
#select which constraints are going to be added
|
|
con_type = 'DAMPED_TRACK' if self.bake_loc == 'TRACK' else 'COPY_TRANSFORMS'
|
|
#for empty, emptychild, bone in zip(empties, child_empties, selected_bones):
|
|
for ctrl, bone in zip(ctrls, selected):
|
|
#assigning also the setup
|
|
if self.bake_loc == 'TRACK':
|
|
ctrl.animtoolbox.setup = bone.btc.setup = 'TRACK_TO_EMPTY'
|
|
#switch to the child controling the original bone
|
|
if self.child:
|
|
ctrl = ctrl.children[0]
|
|
if self.bake_loc == 'TRACK' : ctrl.animtoolbox.setup = 'TRACK_TO_EMPTY'
|
|
|
|
constraints = constraints_filter(self, bone, ctrl, con_type)
|
|
for constraint in constraints:
|
|
if constraint.type == 'DAMPED_TRACK':
|
|
direction = 'NEGATIVE_' if '-' == self.axis[0] else ''
|
|
constraint.track_axis = 'TRACK_' + direction + self.axis[1]
|
|
|
|
add_enabled_driver(bone.id_data, bone.id_data, constraint)
|
|
|
|
context.view_layer.objects.active = ctrl
|
|
del ctrls, selected, root_ctrl, setup_ids, child_ctrls
|
|
return {'FINISHED'}
|
|
|
|
def cancel(self, context):
|
|
global ctrls, selected, root_ctrl, setup_ids, child_ctrls
|
|
|
|
btc = context.scene.btc
|
|
controllers = [item.controller for item in btc.ctrl_items]
|
|
for ctrl in ctrls:
|
|
btc.ctrl_items.remove(controllers.index(ctrl))
|
|
bpy.data.objects.remove(ctrl)
|
|
if child_ctrls:
|
|
for child in child_ctrls:
|
|
bpy.data.objects.remove(child)
|
|
|
|
#remove all the setup ids
|
|
remove_org_bone_id(self.org_obj.pose.bones, setup_ids)
|
|
|
|
if root_ctrl:
|
|
remove_empty_item(context, root_ctrl)
|
|
|
|
remove_collection()
|
|
del ctrls, selected, root_ctrl, setup_ids, child_ctrls
|
|
return None
|
|
|
|
def remove_org_bone_id(bones, setup_ids):
|
|
'''removing the ids from the original bones'''
|
|
for bone in bones:
|
|
if bone.btc.setup_id not in setup_ids:
|
|
continue
|
|
remove_id(bone.id_data, bone.btc.setup_id)
|
|
bone.btc.setup_id = 0
|
|
bone.btc.org_id = 0
|
|
bone.btc.setup = bone.btc.org = 'NONE'
|
|
|
|
class AddEmpties(bpy.types.Operator):
|
|
"""Add Empties in the location of the selected bones"""
|
|
bl_idname = "anim.empties_to_bones"
|
|
bl_label = "Add_Empties"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
|
|
obj = context.object
|
|
if obj.type != 'ARMATURE':
|
|
self.report({'ERROR'},'Currently works only with Armature objects')
|
|
return {'CANCELLED'}
|
|
|
|
context.scene.btc.ctrl_type = 'EMPTY'
|
|
selected = context.selected_pose_bones if obj.mode == 'POSE' else context.selected_objects
|
|
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
|
|
add_control_empties(self, context, selected)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def add_bone_to_collection(rig, bone, col_name = 'MCH', visible = False):
|
|
'''Add bone to a collection'''
|
|
|
|
if bpy.app.version < (4, 0, 0):
|
|
return
|
|
|
|
collections = rig.data.collections
|
|
if col_name in collections:
|
|
col = rig.data.collections[col_name]
|
|
else:
|
|
col = rig.data.collections.new(col_name)
|
|
col.assign(bone)
|
|
col.is_visible = visible
|
|
|
|
def switch_layers(rig, bone, layer_indexes):
|
|
'''switch bone layers'''
|
|
if bpy.app.version >= (4, 0, 0):
|
|
return
|
|
bone.layers = [True if i in layer_indexes else False for i in range(32)]
|
|
|
|
def get_children(root, selected_bones):
|
|
'''Get all the childrens within a selection chain'''
|
|
children = []
|
|
for child in root.children_recursive:
|
|
if child not in selected_bones:
|
|
continue
|
|
parents = set(child.parent_recursive[:child.parent_index(root)])
|
|
if not parents.issubset(selected_bones):
|
|
continue
|
|
children.append(child)
|
|
return set(children)
|
|
|
|
def get_hierarchies(selected_bones):
|
|
'''Get the all the hierarchial chains from all the selected bones'''
|
|
|
|
# selected_bones = set(bpy.context.selected_pose_bones)
|
|
selected_bones = set(selected_bones)
|
|
root_bones = [bone for bone in selected_bones if bone.parent not in selected_bones]
|
|
hierarchies = []
|
|
for root in root_bones:
|
|
end_chains = []
|
|
#check for separate selections with children recursive
|
|
children = get_children(root, selected_bones)
|
|
#if there is no end chains, in case of just one bone then append the root as a chain
|
|
if not children:
|
|
hierarchies.append([root])
|
|
continue
|
|
|
|
#find the end chain from all the childrens
|
|
for child in children:
|
|
grandchildren = get_children(child, selected_bones)
|
|
#check if the children of the children are still part of the hierarchy or the end of the chain
|
|
if len(grandchildren) and grandchildren.issubset(children):
|
|
continue
|
|
end_chains.append(child)
|
|
end_chains = set(end_chains)
|
|
|
|
#start from the end of the chain and go up until the root
|
|
for end_chain in end_chains:
|
|
chain = [end_chain]
|
|
bone = end_chain
|
|
while bone.parent in selected_bones:
|
|
chain.append(bone.parent)
|
|
bone = bone.parent
|
|
#make sure the last parent bone is the root bone of the chain
|
|
if chain[-1] == root:
|
|
hierarchies.append(chain)
|
|
return hierarchies
|
|
|
|
#### Pole Angle Functions ####
|
|
def signed_angle(vector_u, vector_v, normal):
|
|
# Normal specifies orientation
|
|
if vector_v == mathutils.Vector((0, 0, 0)):
|
|
#put some random number, because 0 will give an error for the angle
|
|
vector_v = mathutils.Vector((0, -2, 0))
|
|
angle = vector_u.angle(vector_v)
|
|
if vector_u.cross(vector_v).angle(normal) < 1:
|
|
angle = -angle
|
|
return angle
|
|
|
|
def get_pole_angle(base_bone, ik_bone, pole_location):
|
|
pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head)
|
|
projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head)
|
|
if projected_pole_axis == mathutils.Vector((0,0,0)):
|
|
return None
|
|
return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head)
|
|
|
|
def create_anim_data_action(rig, obj, action = None):
|
|
#creating and adding the action
|
|
if obj.animation_data is None:
|
|
return
|
|
if rig.animation_data is not None:
|
|
if rig.animation_data.action is not None:
|
|
return rig.animation_data.action
|
|
|
|
name = obj.name
|
|
|
|
actions = set()
|
|
if obj.animation_data.action:
|
|
name = obj.animation_data.action.name
|
|
actions.add(obj.animation_data.action)
|
|
get_bone_layers_actions(obj, actions)
|
|
|
|
if not len(actions):
|
|
return None
|
|
|
|
anim_data = rig.animation_data_create() if rig.animation_data is None else rig.animation_data
|
|
|
|
#Check if we are using the same action with a new slot or a new action
|
|
if action is None or not hasattr(anim_data, 'action_slot'):
|
|
action = bpy.data.actions.new(name = 'Temp_Ctrls_' + name)
|
|
|
|
anim_data.action = action
|
|
slot = Tools.add_slot_to_animdata(anim_data)
|
|
|
|
if slot:
|
|
layer = action.layers.new('Temp_Ctrls') if not len(action.layers) else action.layers[0]
|
|
|
|
strip = layer.strips.new() if not len(layer.strips) else layer.strips[0]
|
|
strip.channelbags.new(slot)
|
|
|
|
return action
|
|
|
|
def constraints_clear(ctrls):
|
|
#remove constraints from the temp controls
|
|
for ctrl in ctrls:
|
|
for con in ctrl.constraints:
|
|
ctrl.constraints.remove(con)
|
|
|
|
|
|
def find_pole_vector(base_bone, ik_tip, pole_bone):
|
|
# Get the vector bisecting each FK control (object space)
|
|
PV_normal = ((ik_tip.vector.normalized() + base_bone.vector.normalized() * -1)).normalized()
|
|
offset = bpy.context.scene.btc.pole_offset
|
|
# We push the pole control in the opposite direction of the FK bisecting vector (object space)
|
|
PV_matrix_loc = ik_tip.matrix.to_translation() + (PV_normal * -1) * offset
|
|
PV_matrix = Matrix.LocRotScale(PV_matrix_loc, pole_bone.matrix.to_quaternion(), None)
|
|
return PV_matrix
|
|
|
|
|
|
def pole_prop_edit(self, context):
|
|
global rig, ik_chains, child_names, ctrls
|
|
btc = context.scene.btc
|
|
for chain in ik_chains:
|
|
#add pole target only for more then 2 bones
|
|
if btc.pole_target:
|
|
add_pole_bone(self, context, rig, chain)
|
|
else:
|
|
add_bone_to_collection(rig, rig.data.edit_bones[chain.base_bone.name], col_name = 'IK Ctrls', visible = True)
|
|
#remove the pole bone and switch to base bone
|
|
if not hasattr(chain, 'pole_bone'):
|
|
continue
|
|
rig.data.edit_bones.remove(rig.data.edit_bones[chain.pole_bone_name])
|
|
#remove from ctrls and append base bone instead
|
|
if chain.pole_bone in ctrls:
|
|
ctrls.remove(chain.pole_bone)
|
|
ctrls.append(chain.base_bone)
|
|
del chain.pole_bone, chain.pole_bone_name
|
|
|
|
#Remove child ctrls
|
|
if btc.child:
|
|
child_names = remove_child_ctrls(rig, child_names)
|
|
|
|
def pole_prop_pose(self, context):
|
|
global ik_chains, ctrls, child_names
|
|
btc = context.scene.btc
|
|
for chain in ik_chains:
|
|
if not btc.pole_target:
|
|
chain.base_bone.btc.org = 'CTRL'
|
|
|
|
if not hasattr(chain, 'pole_bone'):
|
|
continue
|
|
|
|
del chain.pole_bone
|
|
if hasattr(chain,'pole_bone_name'):
|
|
chain.pole_bone_name
|
|
continue
|
|
|
|
pole_posebone_update(chain, ctrls)
|
|
if chain.base_bone in ctrls:
|
|
ctrls.remove(chain.base_bone)
|
|
|
|
def pole_prop(self, context):
|
|
global rig, ik_chains, ctrls, child_names
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
pole_prop_edit(self, context)
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
pole_prop_pose(self, context)
|
|
|
|
if self.child:
|
|
child_prop(self, context)
|
|
|
|
tempctrl_shape_type(context.scene.btc, context)
|
|
|
|
# update_bone_color(context.scene.btc, self)
|
|
|
|
def pole_posebone_update(chain, ctrls):
|
|
'''update pole bone properties in the pole prop and add ik prop'''
|
|
if not hasattr(chain, 'pole_bone_name'):
|
|
return
|
|
chain.pole_bone = rig.pose.bones[chain.pole_bone_name]
|
|
add_bone_setup_id(chain.pole_bone , False, 'POLE', 'CTRL', setup_id = chain.setup_id, org_id = add_id(rig, 'CTRL', True))
|
|
chain.pole_bone.matrix = find_pole_vector(chain.base_bone, chain.ik_tip, chain.pole_bone)
|
|
# chain.pole_bone.scale = chain.ik_ctrl.scale
|
|
chain.pole_bone.color.palette = bpy.context.scene.btc.color_set
|
|
|
|
if chain.pole_bone not in ctrls:
|
|
ctrls.append(chain.pole_bone)
|
|
|
|
def add_pole_bone(self, context, rig, chain):
|
|
'''Add the pole bone if it's possible'''
|
|
btc = context.scene.btc
|
|
base_bone = rig.data.edit_bones[chain.base_bone_name]
|
|
ik_tip = rig.data.edit_bones[chain.ik_tip_name]
|
|
|
|
#If the angle between the bones is 0 then move a bit in the Y direction to be able to get a pole vector
|
|
if not base_bone.vector.angle(ik_tip.vector):
|
|
base_bone.tail += mathutils.Vector((0, 0.001, 0))
|
|
ik_tip.head += mathutils.Vector((0, 0.001, 0))
|
|
|
|
pole_matrix = find_pole_vector(base_bone, ik_tip, ik_tip)
|
|
|
|
#pole matrix orientation to world by default
|
|
loc, rot, scale = pole_matrix.decompose()
|
|
new_rot = mathutils.Euler((0, 0, 0), 'XYZ')
|
|
pole_matrix = Matrix.LocRotScale(loc, new_rot, scale)
|
|
|
|
#In case of a flat IK skip adding a pole bone and use base bone instead
|
|
if pole_matrix.translation == chain.ik_tip.matrix.translation:
|
|
self.report({'ERROR'}, "Can't add Pole Ctrl because of a flat IK chain")
|
|
chain.pole_bone = None
|
|
add_bone_to_collection(rig, chain.base_bone, col_name = 'IK Ctrls', visible = True)
|
|
btc['pole_target'] = False
|
|
return
|
|
|
|
pole_bone_name = chain.org_bones[-2].name + '_Pole' if len(chain.org_bones) > 1 else chain.org_bones[0].name + '_Pole'
|
|
chain.pole_bone = rig.data.edit_bones.new(pole_bone_name)
|
|
|
|
#get always the original length when it's using ik_ctrl posebone or editbone
|
|
length = chain.ik_ctrl.bone.length if hasattr(chain.ik_ctrl, 'bone') else chain.ik_ctrl.length
|
|
chain.pole_bone.length = length
|
|
chain.pole_bone.matrix = pole_matrix
|
|
chain.pole_angle = get_pole_angle(base_bone, ik_tip, pole_matrix.translation)
|
|
|
|
chain.pole_bone_name = chain.pole_bone.name
|
|
chain.pole_bone.select = True
|
|
|
|
switch_layers(rig, base_bone, [31])
|
|
add_bone_to_collection(rig, base_bone)
|
|
rig.data.collections['IK Ctrls'].unassign(base_bone)
|
|
add_bone_to_collection(rig, chain.pole_bone, col_name = 'IK Ctrls', visible = True)
|
|
|
|
def pole_offset(self, context):
|
|
global ik_chains, ctrls, child_names
|
|
|
|
for chain in ik_chains:
|
|
if not hasattr(chain, 'pole_bone_name'):
|
|
continue
|
|
chain.pole_bone = rig.pose.bones[chain.pole_bone_name]
|
|
chain.pole_bone.matrix = find_pole_vector(chain.base_bone, chain.ik_tip, chain.pole_bone)
|
|
# chain.pole_bone.scale = chain.ik_ctrl.scale
|
|
|
|
|
|
def add_ik_prop_edit(self, context):
|
|
global rig, ik_chains, ctrls, child_names, root_name
|
|
btc = context.scene.btc
|
|
root_ctrl = None
|
|
if root_name:
|
|
if root_name in rig.data.edit_bones:
|
|
root_ctrl = rig.data.edit_bones[root_name]
|
|
|
|
for chain in ik_chains:
|
|
if btc.add_ik_ctrl:
|
|
#Add an extra ik ctrl bone to the tip of the chain
|
|
chain.ik_tip = chain.ctrls[0]
|
|
chain.ik_tip_name = chain.ik_tip.name
|
|
ik_tip_edit = rig.data.edit_bones[chain.ik_tip_name]
|
|
chain.ik_ctrl = rig.data.edit_bones.new(chain.org_bones[0].name + '_IK_Ctrl')
|
|
chain.ik_ctrl.head = ik_tip_edit.tail
|
|
chain.ik_ctrl.length = ik_tip_edit.length * 0.5
|
|
chain.ik_ctrl.align_orientation(ik_tip_edit)
|
|
chain.ik_ctrl.parent = root_ctrl
|
|
chain.ik_ctrl.select = True
|
|
chain.ik_ctrl_name = chain.ik_ctrl.name
|
|
else:
|
|
#remove the extra ik control
|
|
if chain.ik_ctrl not in chain.ctrls:
|
|
ctrls.remove(chain.ik_ctrl)
|
|
|
|
rig.data.edit_bones.remove(rig.data.edit_bones[chain.ik_ctrl_name])
|
|
chain.ik_ctrl = chain.ctrls[0]
|
|
chain.ik_tip = chain.ctrls[1] if len(chain.ctrls) > 1 else chain.ctrls[0]
|
|
ctrls.append(chain.ik_ctrl)
|
|
chain.ik_ctrl_name = chain.ik_ctrl.name
|
|
chain.ik_tip_name = chain.ik_tip.name
|
|
|
|
#remove the pole bone
|
|
if btc.pole_target and hasattr(chain, 'pole_bone_name'):
|
|
#removing and adding the pole_target without calling the whole pole prop update
|
|
ctrls.remove(chain.pole_bone)
|
|
rig.data.edit_bones.remove(rig.data.edit_bones[chain.pole_bone_name])
|
|
add_pole_bone(self, context, rig, chain)
|
|
|
|
def add_ik_prop_pose(self, context):
|
|
btc = context.scene.btc
|
|
|
|
for chain in ik_chains:
|
|
if btc.add_ik_ctrl:
|
|
#assign the ik ctrl to the ctrls
|
|
chain.ik_ctrl = rig.pose.bones[chain.ik_ctrl_name]
|
|
if chain.ik_ctrl not in ctrls:
|
|
ctrls.append(chain.ik_ctrl)
|
|
if chain.ik_tip in ctrls:
|
|
ctrls.remove(chain.ik_tip)
|
|
add_bone_setup_id(chain.ik_ctrl, False, 'TEMPIK', 'CTRL', setup_id = chain.ik_tip.btc.setup_id, org_id = chain.ik_tip.btc.org_id)
|
|
|
|
rig.data.collections['IK Ctrls'].unassign(chain.ik_tip)
|
|
add_bone_to_collection(rig, chain.ik_tip)
|
|
|
|
chain.ik_tip.btc.org = 'ORG'
|
|
chain.ik_ctrl.btc.org = 'CTRL'
|
|
chain.ik_tip.custom_shape = None
|
|
chain.ik_tip.color.palette = 'DEFAULT'
|
|
chain.ik_ctrl.matrix = chain.ik_tip.matrix
|
|
context.view_layer.update()
|
|
chain.ik_ctrl.matrix.translation = chain.ik_tip.tail
|
|
else:
|
|
chain.ik_ctrl.btc.org = 'CTRL'
|
|
|
|
chain.ik_ctrl.color.palette = context.scene.btc.color_set
|
|
if btc.pole_target and hasattr(chain, 'pole_bone_name'):
|
|
pole_posebone_update(chain, ctrls)
|
|
|
|
add_bone_to_collection(rig, chain.ik_ctrl, col_name = 'IK Ctrls', visible = True)
|
|
|
|
def add_ik_prop(self, context):
|
|
'''add an extra ik bone, normally for chains with only 2 bones selected'''
|
|
global rig, ik_chains, ctrls, child_names
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
add_ik_prop_edit(self, context)
|
|
|
|
#Remove child ctrls
|
|
if self.child:
|
|
child_names = remove_child_ctrls(rig, child_names)
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
add_ik_prop_pose(self, context)
|
|
|
|
if self.child:
|
|
child_prop(self, context)
|
|
|
|
tempctrl_shape_type(context.scene.btc, context)
|
|
# update_bone_color(context.scene.btc, self)
|
|
|
|
class TempIK(bpy.types.Operator):
|
|
"""Copy and bake the animation to temporary controls"""
|
|
bl_idname = "anim.bake_to_temp_ik"
|
|
bl_label = "Bake_To_Temp_IK"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
# frame_start: bpy.props.IntProperty(name = 'Start', description="The start frame for the bake")
|
|
# frame_end: bpy.props.IntProperty(name = 'End', description="The end frame for the bake")
|
|
bake_loc: bpy.props.EnumProperty(name = 'Bake Location', description="Select which point on the bone to bake to", items = [('HEAD', 'Heads','Bake to the bones heads', 0), ('TAIL', 'Tails', 'Bake to the bones tails', 1)])
|
|
con_back: bpy.props.BoolProperty(name = 'Constraint bones to Ctrls', description = "Constraint the selected bones back to the controls", default = True)
|
|
|
|
use_hierarchies: bpy.props.BoolProperty(name = 'Keep Separate Hierarchies', description = "Use all the different hierarchies included in the selection", default = False)
|
|
|
|
smartbake: bpy.props.BoolProperty(name = 'Smart Bake', description = "Keeps keyframes count from selection and recalculate interpolation", default = False)
|
|
|
|
rotation_mode: bpy.props.EnumProperty(name = 'Rotation Mode', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
|
|
items = [('PRESERVE', 'Preserve','Keep Rotation Order from the original selected bones', 0),
|
|
('QUATERNION', 'Quaternion','Quaternion Rotation Order - No Gimbal Lock', 1),
|
|
('XYZ', 'XYZ', 'XYZ Rotation Order', 2), ('XZY', 'XZY','XZY Rotation Order', 3),
|
|
('YXZ', 'YXZ','YXZ Rotation Order', 4), ('YZX', 'YZX', 'YZX Rotation Order', 5),
|
|
('ZXY', 'ZXY', 'ZXY Rotation Order', 6), ('ZYX', 'ZYX', 'ZYX Rotation Order', 7),
|
|
('AXIS_ANGLE', 'AXIS_ANGLE', 'Axis Angle Rotation Order', 8)])
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) and context.object.mode == 'POSE'
|
|
|
|
def invoke(self, context, event):
|
|
# sec = time.time()
|
|
obj = context.object
|
|
|
|
if obj.type != 'ARMATURE':
|
|
self.report({'ERROR'},'Currently works only with Armature objects')
|
|
return {'CANCELLED'}
|
|
|
|
if obj.animtoolbox.controlled:
|
|
self.report({'ERROR'},'Selected object is already a control rig')
|
|
return {'CANCELLED'}
|
|
|
|
global ik_chains, child_names, ctrls, root_name, rig, setup_ids, selected
|
|
context.scene.btc.ctrl_type = 'BONE'
|
|
|
|
selected = context.selected_pose_bones if obj.mode == 'POSE' else []
|
|
if not selected:
|
|
return {'CANCELLED'}
|
|
|
|
if clean_bone_before_setup(context, selected):
|
|
#Clear setup is turned off, then cancelling this operator
|
|
self.report({'ERROR'},'Selected bones are already connected to temp ctrls. Clean the current ctrls before proceeding')
|
|
return {'CANCELLED'}
|
|
|
|
btc = context.scene.btc
|
|
#if there are no display shapes then use the cube as the default
|
|
if not any([bone.custom_shape for bone in selected]):
|
|
# btc.shape_type = 'CUBE'
|
|
btc['shape_type'] = 1
|
|
|
|
self.hierarchies = get_hierarchies(selected)
|
|
#check if there is multiple hierarchies that are also longer then one bone
|
|
self.use_hierarchies = any(len(hierarchy) > 1 for hierarchy in self.hierarchies) and len(self.hierarchies) > 1 and len(selected) > 3
|
|
|
|
# If less then two bones add ik ctrl checked by default
|
|
if self.use_hierarchies:
|
|
add_ik_ctrl = any(len(hierarchy) < 3 for hierarchy in self.hierarchies)
|
|
else:
|
|
add_ik_ctrl = len(selected) < 3
|
|
|
|
bake_layers_default(obj, context.scene)
|
|
########################
|
|
|
|
get_org_action(obj, self)
|
|
self.org_obj = obj
|
|
self.shape_type = btc.shape_type
|
|
self.shape_size = btc.shape_size
|
|
|
|
root_name = None
|
|
root_ctrl = None
|
|
#add separate hierachies within the selection or one entire hierarchy from the selection
|
|
hierarchies = self.hierarchies if self.use_hierarchies else [list(reversed(selected))]
|
|
|
|
ik_chains = []
|
|
rig, self.temp_col = add_ctrl_rig(selected)
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
for hierarchy in hierarchies:
|
|
ctrls, child_ctrls = add_control_bones(self, hierarchy, rig, root_ctrl) #, orient_world = self.orientation
|
|
child_names = []
|
|
# for ctrl in ctrls:
|
|
# add_bone_to_collection(rig, ctrl, col_name = 'ORG', visible = True)
|
|
chain = IK_Chain(hierarchy, ctrls, root_ctrl)
|
|
|
|
#if only one bone is selected in the chain then add an extra control automatically
|
|
if len(hierarchy) == 1:
|
|
add_ik_ctrl = True
|
|
|
|
#setup t tail to head in edit mode
|
|
for i, ctrl in enumerate(ctrls[:-1]):
|
|
ctrls[i+1].tail = ctrl.head
|
|
context.view_layer.update()
|
|
|
|
switch_layers(rig, chain.ik_tip, [31])
|
|
add_bone_to_collection(rig, chain.ik_tip)
|
|
add_bone_to_collection(rig, chain.ik_ctrl, col_name = 'IK Ctrls', visible = True)
|
|
|
|
# if bpy.app.version >= (4, 0, 0):
|
|
# rig.data.collections['ORG'].is_visible = False
|
|
|
|
ik_chains.append(chain)
|
|
|
|
btc['add_ik_ctrl'] = add_ik_ctrl
|
|
if add_ik_ctrl:
|
|
add_ik_prop_edit(self, context)
|
|
|
|
pole_prop_edit(self, context)
|
|
|
|
#updating the ctrls (with either base bone or pole)before adding the children
|
|
ctrl_names = []
|
|
for chain in ik_chains:
|
|
ctrl_names += [chain.ik_ctrl.name, chain.base_bone.name] if not btc.pole_target else [chain.ik_ctrl.name, chain.pole_bone.name]
|
|
# ctrl_names = [ctrl.name for ctrl in ctrls]
|
|
|
|
if btc.root:
|
|
root_name = root_update_edit()
|
|
##################################
|
|
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
setup_ids = set()
|
|
|
|
if btc.root:
|
|
root_update_pose(btc, context)
|
|
|
|
#converting ctrls to posebones
|
|
ctrls = [rig.pose.bones[ctrlname] for ctrlname in ctrl_names]
|
|
|
|
for chain in ik_chains:
|
|
if bpy.app.version < (4, 0, 0) : ctrl_group = add_ctrl_group(rig)
|
|
setup_id = add_id(rig, 'TEMPIK')
|
|
setup = 'TEMPIK'
|
|
chain.setup_id = setup_id
|
|
|
|
# org_id = add_id(rig, 'TEMPIK')
|
|
ctrl_group = add_ctrl_group(rig) if bpy.app.version < (4, 0, 0) else None
|
|
chain.ctrl_group = ctrl_group
|
|
|
|
chain.bonenames_to_posebones(rig)
|
|
|
|
context.view_layer.update()
|
|
#setup the IDs and current frame matrix
|
|
org_id = add_id(rig, 'TEMPIK', True)
|
|
for ctrl, bone in zip(reversed(chain.ctrls), reversed(chain.org_bones)):
|
|
#Assign ID and setup to the ctrls
|
|
add_bone_setup_id(ctrl, False, setup, 'ORG', setup_id, org_id)
|
|
add_bone_setup_id(bone, False, 'NONE','NONE', setup_id, org_id)
|
|
|
|
# #assign the matrix
|
|
ctrl.matrix = bone.id_data.matrix_world @ bone.matrix
|
|
ctrl.color.palette = btc.color_set
|
|
|
|
add_bone_setup_id(chain.ik_ctrl, False, setup, 'CTRL', setup_id, org_id = chain.ik_tip.btc.org_id)
|
|
|
|
|
|
if add_ik_ctrl:
|
|
add_ik_prop_pose(self, context)
|
|
context.view_layer.update()
|
|
pole_prop_pose(self, context)
|
|
|
|
# child_prop_pose(context)
|
|
if context.scene.btc.child:
|
|
child_prop(context.scene.btc, context)
|
|
|
|
self.ik_chains = ik_chains
|
|
tempctrl_shape_type(self, context)
|
|
wm = context.window_manager
|
|
# print('\nseconds\n', time.time() - sec)
|
|
return wm.invoke_props_dialog(self, width = 200)
|
|
|
|
def execute(self, context):
|
|
# obj = context.object
|
|
global ik_chains, child_names, ctrls, root_name, rig, setup_ids, selected
|
|
if 'ctrls' not in globals():
|
|
self.report({'INFO'},'Not available. Please update the Temp Ctrls from the addon panel')
|
|
return {'CANCELLED'}
|
|
btc = context.scene.btc
|
|
controlled_objs = {item.controlled for item in context.scene.btc.ctrl_items if item.controlled}
|
|
hidden_objs = {obj for obj in context.view_layer.objects if obj and obj.hide_get()}
|
|
included_collections = exclude_collections(self.temp_col, context, controlled_objs)
|
|
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
#Add root control to all ctrls and parent bones
|
|
bones_to_root = ctrls + [chain.parent for chain in ik_chains if hasattr(chain, 'parent')]
|
|
root_ctrl = parent_ctrls_to_root(root_name, rig, bones_to_root)
|
|
|
|
child_names_to_orient = None
|
|
if child_names:
|
|
#creating ctrl child relation dictionary
|
|
ctrls_childs = {ctrl.name : childname for ctrl, childname in zip(ctrls, child_names)}
|
|
|
|
#Only the IK Ctrl gets the World orientation
|
|
#Exclude base bone from orientation, pole bone is anyway always world orientation
|
|
child_names_to_orient = [ctrls_childs[chain.ik_ctrl.name] for chain in ik_chains]
|
|
|
|
#update to world orientation if necessery
|
|
ctrls_to_orient = [chain.ik_ctrl for chain in ik_chains]
|
|
ctrls_orientation(btc, ctrls_to_orient, child_names_to_orient)
|
|
|
|
#Create the IK chain parenting
|
|
for chain in ik_chains:
|
|
|
|
if chain.org_bones[-1].parent:
|
|
chain_parent = add_ctrl_bone(rig, chain.org_bones[-1].parent, root_ctrl, nameextension = '')
|
|
chain.parent = chain_parent
|
|
chain.parent_name = chain.parent.name
|
|
#parent the base bone to the chain parent
|
|
rig.data.edit_bones[chain.base_bone_name].parent = chain.parent
|
|
switch_layers(rig, chain.parent, [31])
|
|
add_bone_to_collection(rig, chain.parent)
|
|
else:
|
|
chain.parent = None
|
|
|
|
#adding the original bones again as reference bones in case they are not orientated in IK setup direction
|
|
chain.ref_org_names = []
|
|
for ctrl, org in zip(chain.ctrls, chain.org_bones):
|
|
#Adding the reference bones that are aligned with org bones and parenting them to the ctrl bones of the IK
|
|
if child_names and ctrl.name in ctrls_childs:
|
|
parent_org = rig.data.edit_bones[ctrls_childs[ctrl.name]]
|
|
else:
|
|
parent_org = rig.data.edit_bones[ctrl.name]
|
|
ref_org = add_ctrl_bone(rig, org, parent_org , 'ORG_', 'HEAD', orient_world = False) #rig.data.edit_bones[ctrl.name]
|
|
add_bone_to_collection(rig, ref_org, col_name = 'ORG', visible = False)
|
|
chain.ref_org_names.append(ref_org.name)
|
|
|
|
for i, ctrl in enumerate(chain.ctrls[:-1]):
|
|
#skip parenting for the first ik control bone
|
|
if ctrl == chain.base_bone and hasattr(chain, 'parent'):
|
|
rig.data.edit_bones[ctrl.name].parent = rig.data.edit_bones[chain.ctrls[i+1].name]
|
|
continue
|
|
if ctrl == chain.ik_ctrl:
|
|
continue
|
|
#get the parent from the next bone but in edit bones
|
|
rig.data.edit_bones[ctrl.name].parent = rig.data.edit_bones[chain.ctrls[i+1].name]
|
|
|
|
#updating the polebone position with the offset
|
|
if btc.pole_target and hasattr(chain, 'pole_bone_name'):
|
|
pole_bone = rig.data.edit_bones[chain.pole_bone_name]
|
|
base_bone = rig.data.edit_bones[chain.base_bone_name]
|
|
ik_tip = rig.data.edit_bones[chain.ik_tip_name]
|
|
pole_bone.matrix = find_pole_vector(base_bone, ik_tip, pole_bone)
|
|
if child_names:
|
|
pole_child = rig.data.edit_bones[ctrls_childs[pole_bone.name]]
|
|
#somehow assigning the matrix here didnt work so I use head and tail
|
|
pole_length = pole_child.tail - pole_child.head
|
|
pole_child.head = pole_bone.head
|
|
pole_child.tail = pole_bone.head + pole_length
|
|
|
|
# ik_chains = self.ik_chains
|
|
constraints = []
|
|
################################## Switching to Pose Mode ##################################
|
|
|
|
con_type = 'COPY_TRANSFORMS'
|
|
bpy.ops.object.mode_set(mode = 'POSE')
|
|
|
|
#Iterate over all the ctrl bones in pose mode
|
|
for chain in ik_chains:
|
|
chain.bonenames_to_posebones(rig)
|
|
chain.ctrls_to_bake = [chain.ik_ctrl, chain.base_bone] if not btc.pole_target else [chain.ik_ctrl, chain.pole_bone, chain.base_bone]
|
|
|
|
setup_id = chain.ik_ctrl.btc.setup_id
|
|
#Org ID is not very important and relevant for Temp IK
|
|
org_id = chain.ik_ctrl.btc.org_id
|
|
|
|
#assigning the bone setup
|
|
if chain.parent:
|
|
org_obj = chain.ik_ctrl.id_data
|
|
org_id = add_id(rig, 'TEMPIK', True)
|
|
add_bone_setup_id(chain.parent, False, 'TEMPIK', 'MCH', setup_id, org_id)
|
|
chain.parent.matrix = org_obj.matrix_world @ chain.org_bones[-1].parent.matrix
|
|
|
|
if child_names:
|
|
chain.assign_child_ctrls(rig, ctrls_childs)
|
|
|
|
#constrain all the ctrls to the original bones
|
|
for ctrl, bone in zip(reversed(chain.ctrls), reversed(chain.org_bones)):
|
|
|
|
#reset all bones pose position
|
|
ctrl.location.zero()
|
|
ctrl.rotation_euler.zero()
|
|
ctrl.rotation_quaternion.identity()
|
|
ctrl.scale = bone.scale
|
|
|
|
#Rotation mode works only in Pose mode
|
|
ctrl.rotation_mode = bone.rotation_mode if self.rotation_mode == 'PRESERVE' else self.rotation_mode
|
|
|
|
#switch to the child controling the original bone
|
|
if btc.child and ctrl == chain.ik_ctrl:
|
|
chain.ik_child_ctrl.rotation_mode = ctrl.rotation_mode
|
|
|
|
constraint_add(ctrl, bone, con_type)
|
|
|
|
if ctrl.btc.setup == 'POLE':
|
|
continue
|
|
|
|
# #constraint custom_shape_transform to original rig bone
|
|
if bone.custom_shape_transform and self.shape_type == 'ORIGINAL':
|
|
ot_bone = bone.custom_shape_transform.name
|
|
ctrl.custom_shape_transform = ctrl.id_data.pose.bones[ot_bone]
|
|
#Get the original override transform bone
|
|
target = bone.id_data.pose.bones[ot_bone]
|
|
ctrl.custom_shape_transform.btc.setup_id = ctrl.btc.setup_id
|
|
constraint_add(ctrl.custom_shape_transform, target, con_type)
|
|
|
|
#if an ik control was added, constrain it and include in the chain class
|
|
if btc.add_ik_ctrl:
|
|
constraint_ctrl_to_bone(chain.ik_ctrl, chain.ctrls[0], 'TAIL', con_type, constraints)
|
|
|
|
if chain.parent:
|
|
constraint_ctrl_to_bone(chain.parent, chain.org_bones[-1].parent, self.bake_loc, con_type, constraints)
|
|
|
|
root_to_bake = False
|
|
if root_name:
|
|
root_ctrl = rig.pose.bones[root_name]
|
|
#if the root is constrained then add it to the bake
|
|
root_to_bake = constrain_root(context, rig, self.org_obj, root_ctrl)
|
|
|
|
action = create_anim_data_action(rig, self.org_obj)
|
|
|
|
if root_to_bake:
|
|
org_root = btc.root_object.pose.bones[btc.root_bone]
|
|
if action is not None:
|
|
smartbake_to_ctrls(context, self, [org_root], [root_ctrl])
|
|
constraints_clear([root_ctrl])
|
|
|
|
for chain in ik_chains:
|
|
if action is not None:
|
|
smartbake_to_ik(context, selected, chain, self.smartbake)
|
|
#remove the constraints from the temp ctrls
|
|
constraints_clear(set(chain.ctrls_to_bake + chain.ctrls))
|
|
|
|
custom_range_switch(context.scene, action, rig)
|
|
# constraining the original bones to the temp ik control bones
|
|
for chain in ik_chains:
|
|
#add the ik constraint to the ctrls
|
|
ik_con = constraint_add(chain.ik_tip, chain.ik_ctrl if not btc.child else chain.ik_child_ctrl, 'IK')
|
|
|
|
ik_con.chain_count = chain.length(btc.add_ik_ctrl)
|
|
#setup the ik constraint
|
|
if btc.pole_target and len(chain.ctrls) > 1:
|
|
ik_con.pole_target = rig
|
|
ik_con.pole_subtarget = chain.pole_bone_name if not btc.child else chain.pole_child_ctrl_name
|
|
if chain.pole_angle:
|
|
ik_con.pole_angle = chain.pole_angle
|
|
|
|
#Need to refresh evaluation so that the ref org bones match the org bones
|
|
context.view_layer.update()
|
|
for ctrl, bone, ref_org in zip(chain.ctrls, chain.org_bones, chain.ref_orgs):
|
|
#Aligning the reference bones to the original bones and assigning them to the chain id
|
|
ref_org.matrix = bone.id_data.matrix_world @ bone.matrix.copy()
|
|
add_bone_setup_id(ref_org, False, 'TEMPIK', 'ORG', bone.btc.setup_id, bone.btc.org_id)
|
|
|
|
#switch to the child controling the original bone
|
|
if btc.child and ctrl == chain.ik_ctrl:
|
|
ctrl = chain.ik_child_ctrl
|
|
|
|
#add the transform constraints to the original bones to match the temp ik
|
|
constraint = constraint_add(bone, ref_org, con_type)
|
|
add_enabled_driver(bone.id_data, ctrl.id_data, constraint)
|
|
include_collections(included_collections, hidden_objs, self.temp_col, controlled_objs)
|
|
|
|
del ik_chains, child_names, ctrls, root_name, rig, setup_ids, selected
|
|
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
btc = context.scene.btc
|
|
|
|
#Frame Range
|
|
split = layout.split(factor=0.5, align = True)
|
|
split.prop(btc, 'bake_range_type', text = '')
|
|
split.prop(btc, 'bake_layers', text = 'Include All Layers')
|
|
if btc.bake_range_type == 'CUSTOM':
|
|
row = layout.row()
|
|
row.prop(btc, 'bake_range', text = '')
|
|
layout.separator()
|
|
|
|
row = layout.row()
|
|
row.prop(context.scene.btc, 'shape_size', text = 'Size')
|
|
row.prop(context.scene.btc, 'shape_type', text = '')
|
|
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text = 'Rotation Mode:')
|
|
row.prop(self, 'rotation_mode', text = '')
|
|
|
|
layout.separator()
|
|
col = layout.column()
|
|
col.prop(self, 'smartbake')
|
|
col.prop(btc, 'add_ik_ctrl')
|
|
split = layout.split(factor=0.58, align = True)
|
|
split.prop(btc, 'pole_target')
|
|
if btc.pole_target:
|
|
split.prop(btc, 'pole_offset', text = '')
|
|
|
|
col.prop(btc, 'orientation')
|
|
# col.prop(self, 'use_hierarchies')
|
|
#col.prop(self, 'con_back')
|
|
#row = layout.row()
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text = 'Add Hierarchy:')
|
|
row = layout.row()
|
|
row.prop(context.scene.btc, 'root', text = 'Root Ctrl')
|
|
row.prop(btc, 'child', text = 'Extra Child Ctrl')
|
|
if context.scene.btc.root:
|
|
# row = box.row(align = True)
|
|
row = layout.row()
|
|
row.prop(context.scene.btc, 'root_object', text = '')
|
|
if hasattr(context.scene.btc.root_object, 'type'):
|
|
if context.scene.btc.root_object.type == 'ARMATURE':
|
|
# row = box.row(align = True)
|
|
row = layout.row()
|
|
row.prop_search(context.scene.btc, 'root_bone', context.scene.btc.root_object.pose, 'bones', text = '')
|
|
|
|
def cancel(self, context):
|
|
cancel_tempctrls(context)
|
|
|
|
global ik_chains, child_names, ctrls, root_name, rig, setup_ids, selected
|
|
del ik_chains, child_names, ctrls, root_name, rig, setup_ids, selected
|
|
return None
|
|
|
|
def get_org_action(obj, self):
|
|
'''get the original action into self.org_action'''
|
|
self.org_action = None
|
|
if obj.animation_data is not None:
|
|
if obj.animation_data.action:
|
|
self.org_action = obj.animation_data.action
|
|
|
|
def add_twist_helpers(self, selected, root_name):
|
|
'''In EDIT MODE adding helper MCH and ORG bones for twisting world space controls'''
|
|
|
|
#First check if we need twist at all
|
|
#Cancel twist if using only copy transforms
|
|
if self.bake_loc == 'COPY' : self.twist = False
|
|
|
|
attr_dict = {'x' : 0, 'y' : 1, 'z' : 2}
|
|
index = attr_dict[self.axis[1].lower()]
|
|
if self.filter_rotation[index]:
|
|
self.twist = False
|
|
self.filter_rotation[index] = False
|
|
|
|
####Adding track helpers in EDIT mode###
|
|
if self.bake_loc != 'TRACK' or not self.twist:
|
|
return []
|
|
|
|
mch_bones = []
|
|
#Making sure there is no double parent
|
|
mch_parents = dict()
|
|
#Get the root in case there is no parent
|
|
edit_root = None if not root_name else rig.data.edit_bones[root_name]
|
|
#In case there is no parent add the root
|
|
mch_parent = edit_root
|
|
|
|
for bone in selected:
|
|
if bone.parent:
|
|
if bone.parent in mch_parents:
|
|
mch_parent = mch_parents[bone.parent]
|
|
else:
|
|
mch_parent = add_ctrl_bone(rig, bone.parent, edit_root, 'MCH_', 'HEAD', orient_world = False)
|
|
mch_parents.update({bone.parent : mch_parent})
|
|
|
|
add_bone_to_collection(rig, mch_parent, col_name = 'MCH', visible = False)
|
|
|
|
mch_bone = add_ctrl_bone(rig, bone, mch_parent, 'ORG_ROT_', 'HEAD', orient_world = False)
|
|
add_bone_to_collection(rig, mch_bone, col_name = 'MCH', visible = False)
|
|
mch_bones.append(mch_bone)
|
|
|
|
return [bone.name for bone in mch_bones]
|
|
|
|
def setup_twist_helpers(mch_bones, selected, ctrls):
|
|
'''In Pose Mode Setup the twist helper bones with constraints'''
|
|
if not mch_bones:
|
|
return
|
|
|
|
for mch, bone in zip(mch_bones, selected):
|
|
copy_setup_properties(mch, bone)
|
|
mch.btc.org = 'ORG'
|
|
# constraint_add(mch, bone, 'COPY_LOCATION')
|
|
# ctrls.append(mch)
|
|
|
|
#Constraint the parent of the helper bone to the original parent of the bone
|
|
if mch.parent:
|
|
copy_setup_properties(mch.parent, bone)
|
|
mch.parent.btc.org = 'MCH'
|
|
constraint_add(mch.parent, bone.parent, 'COPY_TRANSFORMS')
|
|
|
|
def add_root_to_bake(self, context, rig, root_name, ctrls, selected):
|
|
if not root_name:
|
|
return None, None
|
|
root_ctrl = rig.pose.bones[root_name]
|
|
if constrain_root(context, rig, self.org_obj, root_ctrl):
|
|
root_org = self.org_obj.pose.bones[context.scene.btc.root_bone]
|
|
ctrls.insert(0, root_ctrl)
|
|
selected.insert(0, root_org)
|
|
else:
|
|
return None, None
|
|
|
|
return root_ctrl, root_org
|
|
|
|
def constrain_root(context, rig, obj, root_ctrl):
|
|
#make sure there is not already animation data on the rig
|
|
if context.scene.btc.root_bone not in obj.pose.bones:
|
|
return False
|
|
action = rig.animation_data.action if rig.animation_data is not None else None
|
|
|
|
if action:
|
|
root_fcus = get_bone_fcurves(root_ctrl)
|
|
if root_fcus:
|
|
return False
|
|
root_org = obj.pose.bones[context.scene.btc.root_bone]
|
|
#constrain the root bone to the referenced root bone
|
|
constraint_add(root_ctrl, root_org)
|
|
|
|
return True
|
|
|
|
class IK_Chain:
|
|
|
|
def __init__(self, hierarchy, ctrls, root):
|
|
# self.ctrls = ctrls
|
|
self.org_bones = hierarchy
|
|
self.base_bone = ctrls[-1]
|
|
self.ik_tip = ctrls[1] if len(ctrls) > 1 else ctrls[0]
|
|
self.ik_ctrl = ctrls[0]
|
|
#need to check if I still have any use for chain.root_ctrl
|
|
self.root_ctrl = root
|
|
self.ctrls = ctrls
|
|
|
|
#Add all the names for converting to between edit and pose bones
|
|
self.ctrl_names = [ctrl.name for ctrl in ctrls]
|
|
if self.root_ctrl:
|
|
self.root_name = self.root_ctrl.name
|
|
self.ik_ctrl_name = self.ik_ctrl.name
|
|
self.base_bone_name = self.base_bone.name
|
|
self.ik_tip_name = self.ik_tip.name
|
|
|
|
def length(self, added_ik_ctrl):
|
|
return len(self.ctrls)-1 if not added_ik_ctrl else len(self.ctrls)
|
|
|
|
def bonenames_to_posebones(self, rig):
|
|
'''converting bone names to pose bones'''
|
|
self.ik_ctrl = rig.pose.bones[self.ik_ctrl_name]
|
|
self.base_bone = rig.pose.bones[self.base_bone_name]
|
|
self.ik_tip = rig.pose.bones[self.ik_tip_name]
|
|
if hasattr(self, 'parent_name'):
|
|
self.parent = rig.pose.bones[self.parent_name]
|
|
if hasattr(self, 'root_name'):
|
|
self.root_ctrl = rig.pose.bones[self.root_name]
|
|
if hasattr(self, 'pole_bone_name'):
|
|
self.pole_bone = rig.pose.bones[self.pole_bone_name]
|
|
# if hasattr(self, 'pole_bone_ref_name'):
|
|
# self.pole_bone_ref = rig.pose.bones[self.pole_bone_ref_name]
|
|
if hasattr(self, 'ik_child_ctrl_name'):
|
|
self.ik_child_ctrl = rig.pose.bones[self.ik_child_ctrl_name]
|
|
if hasattr(self, 'pole_child_ctrl_name'):
|
|
self.pole_child_ctrl = rig.pose.bones[self.pole_child_ctrl_name]
|
|
if hasattr(self, 'base_child_name'):
|
|
self.base_child_ctrl = rig.pose.bones[self.base_child_name]
|
|
if hasattr(self, 'ref_org_names'):
|
|
self.ref_orgs = [rig.pose.bones[name] for name in self.ref_org_names if type(name) == str]
|
|
|
|
self.ctrls = [rig.pose.bones[name] for name in self.ctrl_names if type(name) == str]
|
|
|
|
def assign_child_ctrls(self, rig, ctrls_childs):
|
|
'''assign all the child bones to the chain class'''
|
|
posebones = rig.pose.bones
|
|
btc = bpy.context.scene.btc
|
|
if self.ik_ctrl.name in ctrls_childs:
|
|
self.ik_child_ctrl = posebones[ctrls_childs[self.ik_ctrl.name]]
|
|
self.ik_child_ctrl_name = ctrls_childs[self.ik_ctrl.name]
|
|
if self.base_bone.name in ctrls_childs:
|
|
self.base_child_ctrl = posebones[ctrls_childs[self.base_bone.name]]
|
|
self.base_child_name = ctrls_childs[self.base_bone.name]
|
|
if not hasattr(self, 'pole_bone'):
|
|
return
|
|
if self.pole_bone.name in ctrls_childs:
|
|
self.pole_child_ctrl = posebones[ctrls_childs[self.pole_bone.name]]
|
|
self.pole_child_ctrl_name = ctrls_childs[self.pole_bone.name]
|
|
|
|
classes = (WorldSpaceCtrls, TempFK, TempIK, BakeToEmpties, AddEmpties, LinkTempChains, UnlinkChains,
|
|
BtcSelections, Cleanup, BakeTempCtrls)
|
|
|
|
def register():
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|
|
|
|
def unregister():
|
|
from bpy.utils import unregister_class
|
|
for cls in classes:
|
|
unregister_class(cls)
|