# ***** 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 import mathutils from mathutils import Matrix from mathutils import Vector import math 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') ctrls = get_ctrls_from_selection(scene, org_ids, controllers, id_type = 'org_id') if not ctrls: return #map bone and ctrls ctrl_org_bones = map_ctrl_org_bones(org_ids, controllers, controlled_objs, id_type = 'org_id') # if not len(ctrl_org_bones.values()): # return if len(ctrl_org_bones.values()): length_avg = sum([ctrl.id_data.data.bones[ctrl.name].length for ctrl in ctrl_org_bones.values()]) / len(ctrl_org_bones.values()) else: length_avg = 1 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 # Apply the offset shape only to ctrls that use the same transform of the org bone if ctrl.btc.setup in {'WORLDSPACE_CURSOR', 'TRACK_TO', 'POLE'}: ctrl.custom_shape_transform = None ctrl.custom_shape_translation = (0, 0, 0) ctrl.custom_shape_rotation_euler = (0, 0, 0) 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: # If it already exists then find it ctrl.custom_shape_transform = ctrl.id_data.pose.bones[transform_bonename] else: #add an extra transform bone, if it doesnt exist bpy.ops.object.mode_set(mode = 'EDIT') boneshape = add_ctrl_bone(rig, org_bone.custom_shape_transform, rig.data.edit_bones[ctrl.name].parent, '') transform_bonename = boneshape.name add_bone_to_collection(rig, boneshape) bpy.ops.object.mode_set(mode = 'POSE') rig = ctrl.id_data posebone = rig.pose.bones[transform_bonename] constraint_add(posebone, org_bone.custom_shape_transform, 'COPY_TRANSFORMS') ctrl.custom_shape_transform = posebone else: # scale = (length_avg / ctrl.length)*0.4 + 0.4 scale = 1 ctrl.custom_shape = draw_wgt(scale, ctrl, shape = self.shape_type) ctrl.custom_shape_scale_xyz = tuple([self.shape_size]*3) ctrl.custom_shape_transform = None ctrl.custom_shape_translation = (0, 0, 0) ctrl.custom_shape_rotation_euler = (0, 0, 0) #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} def no_filter_constraints(): #Adding Track constraint without any filters # Currently it's using always filter (separate constraints) on copy transforms if there is scale on source rig all_attributes = list(self.filter_location) + list(self.filter_rotation) + list(self.filter_scale) if any(all_attributes): # or con_type == 'TRACK_TO' return constraints if con_type == 'COPY_TRANSFORMS' and source.id_data.scale != Vector((1, 1, 1)): return constraints con = constraint_add(source, target, con_type) constraints.append(con) return constraints #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) constraints = no_filter_constraints() if constraints: return constraints con = None #Adding Constraints with filtered attributes if con_type == 'COPY_TRANSFORMS': filter_con_type = {'filter_location' : 'COPY_LOCATION', 'filter_rotation': 'COPY_ROTATION', 'filter_scale' : 'COPY_SCALE'} 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) # In case of scaled source rig, use local for scale constraint if con.type == 'COPY_SCALE': con.owner_space = con.target_space ='LOCAL' 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.ws_type == 'TRACK' else 'COPY_TRANSFORMS' if self.ws_type == '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 and ctrl.btc.org != 'ORG': #switch to the child controling the original bone # If the ctrls are org bones, then skip assigning to child bones 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) posebone_select(bone, False) def posebone_select(posebone: bpy.types.PoseBone, select: bool): '''Select or deselect the bone depending on the version''' if bpy.app.version >= (5, 0, 0): posebone.select = select else: posebone.bone.select = select 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.ws_type == '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) 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''' #Relevant for edit bones if hasattr(bone, 'matrix_local'): matrix = bone.matrix_local 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 get_relative_ws_matrix(bone, ctrl): # Get relative ws matrix obj_mat = bone.id_data.matrix_world distance = (bone.id_data.matrix_world @ bone.bone.matrix_local).inverted() @ ctrl.bone.matrix_local.copy() return obj_mat @ bone.matrix @ distance def add_ctrl_bone(rig, bone, parent_bone, nameextension = 'Ctrl_', length_factor = 1, orient_world = False): '''Add a new ctrl based on an existing bone or the cursor matrix''' ctrl_name = nameextension + bone.name if bone else nameextension + 'Empty' ctrl_bone = rig.data.edit_bones.new(ctrl_name) ctrl_bone.parent = parent_bone if bone is None: # ctrl_bone.matrix = Matrix() matrix = bpy.context.scene.cursor.matrix length = 1 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) length = bone.length 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() matrix = Matrix.LocRotScale(loc, rot, scale) ctrl_bone.length = length * length_factor if ctrl_bone.id_data == bone.id_data: ctrl_bone.matrix = matrix else: org_obj = rig.animtoolbox.controlled ctrl_bone.matrix = org_obj.matrix_world @ matrix # return ctrl_bone def add_ctrl_rig(bones): '''Adding the control rig bones''' if bones: org_obj = bones[0].id_data else: org_obj = bpy.context.object #In case the current object is already the control object, then assign it as the rig and quit if org_obj.animtoolbox.controlled: rig = org_obj col = create_scene_collection(rig) return rig, col 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: posebone_select(bone, False) #deselect the original rig in case it's linked if rig.mode != 'EDIT': bpy.ops.object.mode_set(mode = 'EDIT') ctrls = [] child_ctrls = [] #root_bone = add_root_bone(rig) for bone in bones: ctrl_bone = add_ctrl_bone(rig, bone, root_ctrl, prefix, 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, '') add_bone_to_collection(rig, bone_shape_transform) #add child controls if child: child_ctrl = add_ctrl_bone(rig, bone, ctrl_bone, 'Child_' + prefix, 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 = 'NONE', setup_id = None): '''check if the id already exists, add a unique setup_id or org_id and return it''' id_nr = random.randint(10**8, (10**9)-1) #Get all the existing ids either from all the ctrl setup_ids or all the org_ids if setup_id is None: # Getting the ids for the setup ids ids = rig.animtoolbox.ctrl_setups else: # Getting all the ids for the org ids to make sure the org id is unique through all setups ids = [org_id.name for setup in rig.animtoolbox.ctrl_setups for org_id in setup.org_ids] # Get the id number as long as it doesnt exist already while str(id_nr) in ids: id_nr = random.randint(10**8, (10**9)-1) # Registering the ids if setup != 'NONE' and setup_id is None: # In case new setup id was added then give it the description of the setup new_id = ids.add() new_id.setup = setup elif setup_id and str(setup_id) in rig.animtoolbox.ctrl_setups: # New org id added into the new setup (id) new_id = rig.animtoolbox.ctrl_setups[str(setup_id)].org_ids.add() new_id.name = str(id_nr) return id_nr 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 bones = obj.pose.bones if bpy.app.version >= (5, 0, 0) else obj.data.bones for bone in 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': bones = obj.pose.bones if bpy.app.version >= (5, 0, 0) else obj.data.bones for bone in 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': bones = obj.pose.bones if bpy.app.version >= (5, 0, 0) else obj.data.bones for bone in 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') bones = ctrl_obj.pose.bones if bpy.app.version >= (5, 0, 0) else ctrl_obj.data.bones for ctrl_bone in 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') bones = constrained_rig.pose.bones if bpy.app.version >= (5, 0, 0) else constrained_rig.data.bones for bone in 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 = constrained_rig.data.bones[bone.name] else: bone.select = False if bone.select: constrained_rig.data.bones.active = constrained_rig.data.bones[bone.name] 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(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 rotation = Tools.rot_mode_to_channel(posebone.rotation_mode) transformations = ['location', rotation] fcurves = Tools.get_fcurves(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 = True): #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, ctrl_bone in zip(posebones, ctrls): #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(org_rig, action)) ctrl_rig = ctrl_bone.id_data org_obj = self.org_obj if hasattr(self, 'org_obj') else ctrl_rig.animtoolbox.controlled #######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.ws_type in {'TRACK', 'CURSOR'}: ctrl_bone_matrix = ctrl_bone.matrix if type(ctrl_bone) == bpy.types.PoseBone else ctrl_bone.matrix_world smartfcurve.distance = (org_obj.matrix_world @ posebone.matrix).inverted() @ ctrl_bone_matrix #getting the action or channelbag for adding new fcurves channelbag = Tools.get_channelbag(ctrl_rig, ctrl_rig.animation_data.action) fcu_new = smartbake_add_fcurve(channelbag, 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 = [] range_type = scene.btc.bake_range_type if range_type == 'KEYFRAMES': for obj, action in obj_actions.items(): for action in actions: if action is None: continue fcurves = Tools.get_fcurves(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) 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)] # Get scene or custom frame range in case keys are not available, via get_framerange if range_type != 'KEYFRAMES' or not smartframes: frame_start, frame_end = get_framerange() smartframes = [frame for frame in range(frame_start, frame_end)] #create the fcurves and add them to smartfcurves for ctrl in ctrls: if ctrl == chain.ik_tip and ctrl != chain.ik_ctrl: continue # if chain.pole_bone and ctrl == chain.base_bone: # continue ctrl_action = ctrl.id_data.animation_data.action path = ctrl.path_from_id() rot = Tools.rot_mode_to_channel(ctrl.rotation_mode) # Get the original bone to get the distance from org_bone = None if ctrl in chain.ctrls: index = chain.ctrls.index(ctrl) org_bone = chain.org_bones[index] # If the IK Ctrl is not part of the chain (extra ctrl) # then make it relative to the IK tip org bone if ctrl == chain.ik_ctrl and chain.ik_ctrl not in chain.ctrls: index = chain.ctrls.index(chain.ik_tip) org_bone = chain.org_bones[index] 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(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 if org_bone: # Get the distance from the original bone org_obj = org_bone.id_data smartfcurve.distance = (org_obj.matrix_world @ org_bone.bone.matrix_local).inverted() @ ctrl.bone.matrix_local # org_obj.matrix_world smartfcurve.posebone = org_bone 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, 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(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_channelbag(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 try: 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) smartfcus.update({(data_path, array_index) : smartfcurve}) except RuntimeError: continue 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.''' 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: # Calculate pole target after other bones are calculated 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 org_obj = smartfcurve.posebone.id_data matrix = org_obj.matrix_world @ 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 store_ctrl_matrix(context, ctrls): #Storing ctrl matrices before re-parenting ctrls_matrices = dict() for ctrl in ctrls: ctrls_matrices[ctrl] = ctrl.matrix.copy() return ctrls_matrices def restore_ctrl_matrix(context, ctrl, ctrls_matrices): for ctrl, bone in zip(ctrls, selected): ctrl.matrix = ctrls_matrices[ctrl] context.view_layer.update() 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 # starttime = time.time() 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 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: #Get the rest of the bones to check if they are children of the current pasted bone bones = set(bones_matrices.keys()).difference(pasted_bones) Tools.paste_bone_matrix(p_bone, bones_matrices[p_bone], constrained, bones, x_filter = False) pasted_bones.add(p_bone) bone_fcus = dict() bones_prevrot = 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) if chain and chain.pole_bone is not None: # Adding the pole bone to the end of the list bones_matrices.update({chain.pole_bone : None}) bones_matrices[chain.pole_bone] = bones_matrices.pop(chain.pole_bone) #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: # In case of empties if type(bone) != bpy.types.PoseBone: bone.matrix_world = matrix else: # Get the rest of the bones to check if they are children and if we need viewlayer update bones = set(bones_matrices.keys()).difference(pasted_bones) # Calculating pole target matrix after other bones were calcluated if chain and bone == chain.pole_bone: base_bone = chain.base_bone ik_tip = chain.ik_tip angle = chain.base_bone.vector.angle(chain.ik_tip.vector) if not round(angle): # continue # If the angle is flat then use the position of the pole relative to the rest position base bone relative_matrix = base_bone.bone.matrix_local.inverted() @ chain.pole_bone.bone.matrix_local matrix = base_bone.matrix @ relative_matrix # print(frame, 'angle is 0 using base bone matrix') else: bpy.context.view_layer.update() matrix = find_pole_vector(base_bone, ik_tip, chain.pole_bone, chain.step) Tools.paste_bone_matrix(bone, matrix, constrained, bones, x_filter = False) pasted_bones.add(bone) #Compare the rotation with the previous frame to make sure it's not flipping Tools.compatible_rotation(bone, bones_prevrot) #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() # print('writing keyframes time 0.7.7', time.time() - starttime) 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(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(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(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: channelbag = Tools.get_channelbag(posebone.id_data, org_action) if channelbag is None: channelbag = 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(channelbag, 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 channelbag = Tools.get_channelbag(posebone.id_data, org_action) if not channelbag: channelbag = Tools.add_channelbag(posebone.id_data, org_action) if scene.btc.from_ctrl: ctrl_paths = set() 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': # Iterate to find the controller bone for ctrl_bone in ctrl_rig.pose.bones: if ctrl_bone.btc.setup_id != setup_id or ctrl_bone.btc.org != 'CTRL': 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 = '' ctrl_paths.add('') for ctrl_action in ctrl_actions: #iterate over ctrl rig fcurves fcurves = Tools.get_fcurves(ctrl_rig, ctrl_action) # for ctrl_path in ctrl_paths: 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 action fcu = smartbake_add_fcurve(channelbag, 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 scene.emp.motion_path and 'atb_mp_name' in ctrl_rig: scene.emp.motion_path = False 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 #This is done also during remove constraints, need to double check this.. 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 elif not bone.btc.setup_id: #check if the bone has anything assigned to it 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 posebone_select(bone, 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(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''' scene = context.scene btc = 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) if scene.emp.motion_path and 'atb_mp_name' in posebone: scene.emp.motion_path = False #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(rig, rig.animation_data.action) for fcu in fcurves: if fcu is None: continue 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 # print('rebake_connection_ctrls') 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, distance=False) 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, distance=False) #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(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 = [] 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) # get child bones with the same org_id for child in ctrlbone.children: if child.btc.org_id == ctrlbone.btc.org_id and child.btc.child: 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 setup_id = ctrlbone.btc.setup_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 and org_bone.btc.setup_id == setup_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(), distance=False) # 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 ids and scene.btc.target == 'SELECTED': return {'CANCELLED'} 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'} 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_types = {(bone.btc.setup_id, bone.btc.setup) 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: if bone.id_data not in controllers: continue setup_id_type = (bone.btc.setup_id, bone.btc.setup) if setup_id_type not in setup_ids_types: continue if bone.btc.org == 'MCH' and bone.btc.setup != 'TEMPIK': continue if bone.btc.org == 'ORG': continue if setup_id_type not in id_chains: id_chains.update({setup_id_type : [bone]}) else: id_chains[setup_id_type].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 bones in id_chains.values(): 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): 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'} #If bones that are not part of temp ctrls being linked then cancel the operation objs = {bone.id_data for bone in context.selected_pose_bones} if not objs.issubset(controllers): self.report({'INFO'},'Link Temp Chains works only with Temp Ctrls') 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 and setup type from the selected bones # Using both setup id and setup type in case of linking Pole Ctrl and Temp IK ctrl separatly setup_ids_types = {(bone.btc.setup_id, bone.btc.setup) 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: if bone.id_data not in controllers: continue setup_id_type = (bone.btc.setup_id, bone.btc.setup) if setup_id_type not in setup_ids_types: continue if bone.btc.org == 'MCH' and bone.btc.setup != 'TEMPIK': continue if bone.btc.org == 'ORG': continue if bone.btc.setup == 'ROOT': root_bone_name = bone.name if setup_id_type not in id_chains: id_chains.update({setup_id_type : [bone]}) else: id_chains[setup_id_type].append(bone) if len(id_chains) < 2: include_collections(included_collections, hidden_objs, 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_posebone.btc.setup) 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_type, bones in id_chains.items(): setup_id, setup = setup_id_type 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] # Connect only direct ik ctrl and not the child if bone_to_link.btc.child: bone_to_link = bone_to_link.parent 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)] org_rig = bone.id_data if rig.mode == 'EDIT': #Switch to edit bones using the posebone name ctrl = rig.data.edit_bones[ctrl.name] 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] if type(bone.id_data) == bpy.types.Object: matrix = get_relative_ws_matrix(bone, ctrl) ctrl.matrix = matrix # else: # matrix = get_bone_matrix(bone) # matrix = org_rig.matrix_world @ bone.matrix if self.ws_type == 'CURSOR': ctrl.matrix.translation = context.scene.cursor.matrix.translation if hasattr(ctrl, 'btc') : ctrl.btc.setup = 'WORLDSPACE_CURSOR' elif self.ws_type == 'COPY': if hasattr(ctrl, 'btc') : ctrl.btc.setup = 'WORLDSPACE' if self.ws_type != 'TRACK': continue if hasattr(ctrl, 'btc') : ctrl.btc.setup = 'TRACK_TO' 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 # tempctrl_shape_type(context.scene.btc, context) 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 # org_obj = rig.animtoolbox.controlled # print('org obj ', org_obj) #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_', 0.5, orient_world = self.orientation) # child_ctrl.matrix = ctrl.matrix # child_ctrl.length = ctrl.length * 0.5 # bpy.context.view_layer.update() 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] org_rig = bone.id_data bone_matrix = get_bone_matrix(bone.bone) bone_matrix = org_rig.matrix_world @ bone_matrix # bone_edit = bone.id_data.data.edit_bones[bone.name] ctrl_edit.length = bone.bone.length 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 ctrl.matrix = get_relative_ws_matrix(bone, ctrl) 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) ws_type: 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 self.con_back and clean_bone_before_setup(context, selected): self.con_back = False 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 = (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 self.flip = flip #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.flip: flip_editbone(self, ctrl) 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) # 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 if self.con_back and 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') self.cancel(context) 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) 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)) ctrls_matrices = store_ctrl_matrix(context, 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_') #adding the new bones to bone collections for ctrl, org in zip(ctrls, 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 and reassigning the matrix after re-parenting if self.flip: ctrls = list(reversed(ctrls)) selected = list(reversed(selected)) org_names = list(reversed(org_names)) child_names = list(reversed(child_names)) for ctrl, bone in zip(ctrls, selected): ctrl.rotation_mode = bone.rotation_mode if self.rotation_mode == 'PRESERVE' else self.rotation_mode ctrl.matrix = ctrls_matrices[ctrl] context.view_layer.update() #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: if self.con_back: for ctrl, bone in zip(ctrls, selected): copy_setup_properties(bone, ctrl) # 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, distance = True) 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'} ######################## In case of flipping ########################## orgs = [rig.pose.bones[name] for name in org_names] # mchs = [rig.pose.bones[name] for name in mch_names] for ctrl, org, bone in zip(ctrls, orgs, selected): #copy the ids to the helper bones org.btc.org_id = ctrl.btc.org_id org.btc.setup_id = ctrl.btc.setup_id org.rotation_mode = ctrl.rotation_mode ctrl.btc.org = 'CTRL' org.btc.org = 'ORG' # mch.btc.org = 'MCH' ctrl.btc.setup = org.btc.setup = 'TEMPFK_FLIP' ctrl.color.palette = context.scene.btc.color_set org.custom_shape = None copy_setup_properties(bone, ctrl) #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) # restore_ctrl_matrix(context, ctrl, ctrls_matrices) #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.ws_type = '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(orgs) constraints_clear(ctrls) 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) constraint_org_to_ctrl(self, orgs, selected) 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 ctrl.matrix = get_relative_ws_matrix(bone, ctrl) 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(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 posebone_select(root_ctrl, 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 #register the org id into the setup id new_org_id = rig.animtoolbox.ctrl_setups[str(root_id)].org_ids.add() new_org_id.name = str(org_id) else: org_id = add_id(rig, 'NONE', root_id) 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): ctrl.matrix = get_relative_ws_matrix(bone, ctrl) #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) #Only WorldSpace controls need a new setup id on every iteration # (since every iteration is a new chain) if setup in {'WORLDSPACE', 'TRACK_TO', 'WORLDSPACE_CURSOR'}: setup_id = add_id(rig, setup) # Check if the original bone already has an org id if yes use it, if not then create one if bone.btc.org_id: org_id = bone.btc.org_id #register the org id into the setup id new_org_id = rig.animtoolbox.ctrl_setups[str(setup_id)].org_ids.add() new_org_id.name = str(org_id) else: org_id = add_id(rig, 'NONE', setup_id) # Assign the new org_id to the new bone bone.btc.org_id = org_id 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': obj_mat = bone.id_data.matrix_world 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.ws_type != '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.ws_type != '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'} ws_type: bpy.props.EnumProperty(name = 'Type', description="Select what type of constraints will be used", update = target_bone_position, items = [('COPY', 'Copy Transforms','Bake the bones to standard world space ctrls', 0), ('TRACK', 'Track Target', 'Track the bones to ctrls on a specific axis', 1), ('CURSOR', 'Cursor Pivot', 'Bake the bones to cursor space pivot ctrl', 2)]) 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", min = 0, 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 self.con_back and clean_bone_before_setup(context, selected): self.con_back = False 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 #Connected bones get Track WS type by default since they can only rotate if any([posebone.bone.use_connect for posebone in selected]): self.ws_type = '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 self.setup_type = {'COPY' : 'WORLDSPACE', 'TRACK' : 'TRACK_TO', 'CURSOR' : 'WORLDSPACE_CURSOR'} setup = self.setup_type[self.ws_type] 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, 'ws_type', text = '') if self.ws_type == '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 self.con_back and 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') self.cancel(context) return {'CANCELLED'} 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') root_ctrl = parent_ctrls_to_root(root_name, rig, ctrls) if self.ws_type == 'TRACK': target_bone_position(self, context) elif self.ws_type == 'CURSOR': # In case of cursor pivot it's using extra org bones at the original bones position orgs, rest = add_control_bones(self, selected, rig, root_ctrl, False, prefix = 'ORG_') for ctrl, org in zip(ctrls, orgs): add_bone_to_collection(rig, org, col_name = 'ORG', visible = False) if self.child: i = ctrls.index(ctrl) #Assign the child ctrl bone as the parent of org org.parent = rig.data.edit_bones[child_names[i]] else: org.parent = rig.data.edit_bones[ctrl.name] org_names = [org.name for org in orgs] ctrls_orientation(self, ctrls, child_names) mch_names = add_twist_helpers(self, selected, root_name) ############################ POSE MODE ############################ bpy.ops.object.mode_set(mode = 'POSE') mch_bones = [rig.pose.bones[name] for name in mch_names] #Getting the setup based on the dict inside invoke function setup = self.setup_type[self.ws_type] #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 = 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] #Copy the setup ids to the original bone only if con back is checked if self.con_back: bone.btc.setup = setup bone.btc.setup_id = ctrl.btc.setup_id bone.btc.org_id = ctrl.btc.org_id #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() # Adding org bones in case of using Cursor pivot type if self.ws_type == 'CURSOR': orgs = setup_ws_cursor_posemode(org_names, ctrls, selected) setup_twist_helpers(mch_bones, selected, ctrls) #Add the root bone into the bake if necessery root_org = None if root_name and root_name in rig.pose.bones: root_ctrl = rig.pose.bones[root_name] if check_root_fcurves(context, rig, self.org_obj, root_ctrl): root_org = self.org_obj.pose.bones[context.scene.btc.root_bone] root_ctrl.rotation_mode = root_org.rotation_mode ctrls.insert(0, root_ctrl) selected.insert(0, root_org) action = create_anim_data_action(rig, self.org_obj) if action is not None: smartbake_to_ctrls(context, self, selected, ctrls, distance = True) 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) if self.ws_type == 'CURSOR': constraint_org_to_ctrl(self, orgs, selected) else: 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.pose.bones: posebone_select(rig.pose.bones[root_name], 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 setup_ws_cursor_posemode(org_names, ctrls, selected): orgs = [rig.pose.bones[name] for name in org_names] for ctrl, org, bone in zip(ctrls, orgs, selected): #copy the ids to the helper bones org.btc.org_id = ctrl.btc.org_id org.btc.setup_id = ctrl.btc.setup_id ctrl.btc.org = 'CTRL' org.btc.org = 'ORG' org.btc.setup = ctrl.btc.setup # ctrl.color.palette = context.scene.btc.color_set org.custom_shape = None org.matrix = bone.matrix return orgs 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) class AddEmptyCtrls(bpy.types.Operator): """Copy and bake the animation to temporary controls""" bl_idname = "anim.add_empty_ctrl" bl_label = "Add_Empty_Ctrl" bl_options = {'REGISTER', 'UNDO'} #Currently using this because of assign_setup_ids, can be removed later # 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 add_setup(self, ctrl, setup, color): setup_id = add_id(rig, setup) org_id = add_id(rig, 'NONE', setup_id) add_bone_setup_id(ctrl, False, setup, 'CTRL', setup_id, org_id) if bpy.app.version < (4, 0, 0): ctrl.bone_group = color else: ctrl.color.palette = color def execute(self, context): obj = context.object scene = context.scene if obj.type != 'ARMATURE': self.report({'ERROR'},'Currently works only with Armature objects') return {'CANCELLED'} global ctrls, child_names, selected, rig, setup_ids selected = context.selected_pose_bones if obj.mode == 'POSE' else [] setup_ids = set() self.shape_type = scene.btc.shape_type self.shape_size = scene.btc.shape_size # If there is no bone selected, make sure that the controlled rig is the active one in order to get the correct control rig if obj.animtoolbox.controlled: controlled_obj = obj.animtoolbox.controlled context.view_layer.objects.active = controlled_obj #getting the rig and its collection rig, self.temp_col = add_ctrl_rig(selected) bpy.ops.object.mode_set(mode = 'EDIT') if selected: ctrls, child_ctrls = add_control_bones(self, selected, rig, None, False) ctrl_names = [ctrl.name for ctrl in ctrls] else: #Adds an empty bone at the cursor position ctrl_bone = add_ctrl_bone(rig, None, None) ctrl_name = ctrl_bone.name ctrl_bone.select = True ################################### bpy.ops.object.mode_set(mode = 'POSE') #Add either a color group or color set, depends on the Blender version color = add_ctrl_group(rig) if bpy.app.version < (4, 0, 0) else context.scene.btc.color_set if selected: # Controls Based on Existing bones ctrls = [rig.pose.bones[name] for name in ctrl_names] for ctrl, bone in zip(ctrls, selected): matrix = get_bone_matrix(bone) matrix = bone.id_data.matrix_world @ matrix ctrl.matrix = matrix self.add_setup(ctrl, 'WORLDSPACE', color) else: # Control Based on Cursor ctrl = rig.pose.bones[ctrl_name] ctrl.matrix = scene.cursor.matrix.copy() self.add_setup(ctrl, 'WORLDSPACE_CURSOR', color) #deselect the original bones and set the new ctrls active for bone in selected: posebone_select(bone, False) rig.data.bones.active = rig.data.bones[ctrl.name] tempctrl_shape_type(context.scene.btc, context) return {'FINISHED'} 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(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.ws_type != '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'} ws_type: 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, 'ws_type', text = '') if self.ws_type == '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 self.con_back and clean_bone_before_setup(context, selected): self.con_back = False 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 self.con_back and 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') self.cancel(context) return {'CANCELLED'} 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) setup = 'EMPTY' if self.ws_type == 'COPY' else 'TRACK_TO_EMPTY' con_type = 'COPY_TRANSFORMS' obj = self.org_obj for ctrl, bone in zip(ctrls, selected): if self.con_back: bone.btc.setup_id = ctrl.animtoolbox.setup_id bone.btc.setup = setup 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.ws_type == '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.ws_type == '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.ws_type == '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'} self.ws_type = 'COPY' 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, step): # 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 * (step*1000) # 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 decompose_keep_scale(matrix, scale): loc, rot, old_scale = matrix.decompose() new_matrix = Matrix.LocRotScale(loc, rot, scale) return new_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 chain.pole_bone is None: 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) chain.pole_bone = chain.pole_bone_name = None # 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 if not chain.pole_bone: continue if not chain.pole_bone_name: continue chain.pole_bone = chain.pole_bone_name = None # 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 if context.mode != 'EDIT': 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 ik_ctrl_orientation(self, context): global rig, ik_chains, ctrls, child_names if context.mode != 'EDIT': org_context = context.mode bpy.ops.object.mode_set(mode = 'EDIT') rot_world = mathutils.Euler((0, 0, 0), 'XYZ').to_matrix() for chain in ik_chains: ik_ctrl = chain.ik_ctrl ik_ctrl_edit = rig.data.edit_bones[chain.ik_ctrl.name] if self.orientation: loc, rot, scale = ik_ctrl.matrix.decompose() loc_edit, rot, scale_edit = ik_ctrl_edit.matrix.decompose() ik_ctrl_edit.matrix = Matrix.LocRotScale(loc_edit, rot_world, scale_edit) elif chain.ik_ctrl in chain.ctrls: org_bone = chain.find_org_bone(chain.ik_ctrl) org_bone_edit = org_bone.id_data.data.edit_bones[org_bone.name] ik_ctrl_edit.matrix = org_bone.id_data.matrix_world @ org_bone_edit.matrix # chain.ik_ctrl.matrix = org_bone.matrix if not ik_ctrl.children: continue context.view_layer.update() for child in ik_ctrl.children: child.matrix = ik_ctrl_edit.matrix if org_context == 'POSE': bpy.ops.object.mode_set(mode = 'POSE') context.view_layer.update() for chain in ik_chains: ik_ctrl = chain.ik_ctrl # Getting the matrix in pose mode if chain.ik_ctrl in chain.ctrls: org_bone = chain.find_org_bone(chain.ik_ctrl) matrix = org_bone.matrix.copy() else: # For Extra IK Ctrl get the matrix from the tip bone org_bone = chain.find_org_bone(chain.ik_tip) matrix = org_bone.matrix.copy() matrix.translation = chain.ik_tip.tail chain.ik_ctrl.matrix = matrix if not ik_ctrl.children: continue for child in ik_ctrl.children: child.matrix = ik_ctrl.matrix 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 if not 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 = chain.ik_ctrl.btc.org_id) chain.pole_bone.matrix = find_pole_vector(chain.base_bone, chain.ik_tip, chain.pole_bone, chain.step) chain.pole_bone.color.palette = bpy.context.scene.btc.color_set if chain.pole_bone not in ctrls: ctrls.append(chain.pole_bone) def update_axis_prop(self, context): global rig, ik_chains, ctrls # if not self.flat: # return if context.mode != 'EDIT': bpy.ops.object.mode_set(mode = 'EDIT') for chain in ik_chains: base_bone = rig.data.edit_bones[chain.base_bone_name] ik_tip = rig.data.edit_bones[chain.ik_tip_name] # Check if the angle was already adjusted, then adjust it back to it's flat position if hasattr(chain, 'angle_direction'): base_bone.tail = base_bone.tail - chain.angle_direction ik_tip.head = ik_tip.head - chain.angle_direction update_chain_angle(context, chain, base_bone, ik_tip) if not hasattr(chain, 'pole_bone_name'): continue if not chain.pole_bone_name: continue pole_matrix = find_pole_vector(base_bone, ik_tip, ik_tip, chain.step) #pole matrix orientation to world by default loc, rot, scale = pole_matrix.decompose() new_rot = mathutils.Euler((0, 0, 0), 'XYZ') pole_bone = rig.data.edit_bones[chain.pole_bone_name] pole_bone.matrix = Matrix.LocRotScale(loc, new_rot, scale) # Update the angle value chain.pole_angle = get_pole_angle(base_bone, ik_tip, pole_matrix.translation) # pole_prop(self, context) if context.mode != 'POSE': bpy.ops.object.mode_set(mode = 'POSE') pole_posebone_update(chain, ctrls) # pole_prop_pose(self, context) # if hasattr(self, 'child') and self.child: # child_prop(self, context) def find_pole_direction(chain, ik_tip): if ik_tip not in chain.ctrls: return 1 # Getting the original bones in pose mode org_tip = chain.find_org_bone(ik_tip) tip_matrix = org_tip.id_data.matrix_world @ org_tip.bone.matrix_local @ org_tip.matrix_basis tip_direction = tip_matrix.to_3x3() @ Vector((0, 1, 0)) # Get only the Y axis direction value = tip_direction[1] return (value < 0) - (value > 0) # return 1 if value < 0 else -1 def get_step(obj): d = obj.dimensions # area = 2 * (d.x * d.y + d.x * d.z + d.y * d.z) # # Prevent log(0) # area = max(area, 1e-12) # scaled_area = math.sqrt(area) # # Log-scaled step # step = 10 ** math.floor(math.log10(scaled_area * 0.001)) step = ((d.x + d.y + d.z) / 3)*0.001 return step def update_chain_angle(context, chain, base_bone, ik_tip, step = 0.01): if not hasattr(chain, 'direction'): direction = find_pole_direction(chain, ik_tip) direction = chain.direction pole_axis = context.scene.btc.pole_axis if not direction and pole_axis == 'AUTO': context.scene.btc['pole_axis'] = 1 if pole_axis == 'AUTO': # Adding an angle to flat ik chains for better calculations chain.angle_direction = Vector((0, step * direction, 0)) base_bone.tail += chain.angle_direction ik_tip.head += chain.angle_direction else: # Adding angle to the flat ik based on the axis and direction input from the user axis = pole_axis[1].lower() direction = 1 if pole_axis[0] == '+' else -1 base_bone_tail = getattr(base_bone.tail, axis) value = base_bone_tail + direction * -step chain.angle_direction = Vector((0, 0, 0)) setattr(chain.angle_direction, axis, (direction * -step)) setattr(base_bone.tail, axis, value) setattr(ik_tip.head, axis, value) def add_pole_bone(self, context, rig, chain): '''Add the pole bone if it's possible''' 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 self.flat = False if not round(base_bone.vector.angle(ik_tip.vector), 1): self.flat = True chain.direction = find_pole_direction(chain, ik_tip) if chain.direction: context.scene.btc['pole_axis'] = 4 if not hasattr(chain, 'step'): chain.step = get_step(rig.animtoolbox.controlled) update_chain_angle(context, chain, base_bone, ik_tip, chain.step) pole_matrix = find_pole_vector(base_bone, ik_tip, ik_tip, chain.step) #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) 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 if not 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.step) 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 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 chain.pole_bone_name: #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") # ws_type: 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)]) bake_org: bpy.props.BoolProperty(name = 'Bake Org Bones', description = "Keeps original bones exactly as they were but slower bake", default = False) orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True, update = ik_ctrl_orientation) # axis: bpy.props.EnumProperty(name = 'Axis Direction', description="Which direction should the pole bone point", default = 6, update = update_axis_prop, # 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), # ('AUTO', 'Auto','Select pole bone direction based on the current pose', 6)]) @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') del selected 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() # return {'FINISHED'} 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] # Create a rotation matrix to srotate 90 degrees around the X-axis (bone pointing upwards) if self.orientation: loc, rot, scale = chain.ik_ctrl.matrix.decompose() # rot = mathutils.Euler((90 * (3.14159 / 180), 0, 0), 'XYZ').to_matrix() rot = mathutils.Euler((0, 0, 0), 'XYZ').to_matrix() chain.ik_ctrl.matrix = Matrix.LocRotScale(loc, rot, scale) 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 # Adding to the global setups ids setup_ids.add(setup_id) 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 for ctrl, bone in zip(reversed(chain.ctrls), reversed(chain.org_bones)): org_id = add_id(rig, 'NONE', setup_id) #Assign ID and setup to the ctrls add_bone_setup_id(ctrl, False, setup, 'MCH', setup_id, org_id) add_bone_setup_id(bone, False, 'NONE','NONE', setup_id, org_id) # #assign the matrix distance = (bone.id_data.matrix_world @ bone.bone.matrix_local).inverted() @ ctrl.bone.matrix_local.copy() ctrl.matrix = bone.id_data.matrix_world @ bone.matrix @ distance # context.view_layer.update() ctrl.color.palette = btc.color_set add_bone_setup_id(chain.ik_ctrl, False, setup, 'CTRL', setup_id, org_id = chain.ik_ctrl.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): # sec = time.time() 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) ################################## Switching to EDIT Mode ################################## 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) # pole_offset = context.scene.btc.pole_offset #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 to the original armature bones 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_', orient_world = False) 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 #get the parent from the next bone but in edit bones if ctrl == chain.ik_ctrl: continue 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 chain.pole_bone_name: #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, chain.step) 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.base_bone, chain.ik_tip, chain.ik_ctrl] if not btc.pole_target else [chain.base_bone, chain.ik_tip, chain.ik_ctrl, chain.pole_bone ] setup_id = chain.ik_ctrl.btc.setup_id # org_id = chain.ik_ctrl.btc.org_id #assigning the bone setup if chain.parent: org_obj = rig.animtoolbox.controlled org_id = add_id(rig, 'NONE', setup_id) add_bone_setup_id(chain.parent, False, 'TEMPIK', 'MCH', setup_id, org_id) parent_org = chain.org_bones[-1].parent distance = (org_obj.matrix_world @ parent_org.bone.matrix_local).inverted() @ chain.parent.bone.matrix_local.copy() chain.parent.matrix = org_obj.matrix_world @ parent_org.matrix @ distance context.view_layer.update() # Adding separate constraints to the parent bone, with scale on local in case the original object has a scale constraint_add(chain.parent, parent_org, con_type = 'COPY_LOCATION') constraint_add(chain.parent, parent_org, con_type = 'COPY_ROTATION') scale_con = constraint_add(chain.parent, parent_org, con_type = 'COPY_SCALE') scale_con.target_space = 'LOCAL' if child_names: chain.assign_child_ctrls(rig, ctrls_childs) #constrain all the ctrls to the original bones chain_bones = zip(*map(reversed, (chain.ctrls, chain.ref_orgs, chain.org_bones))) # bone_chains = zip(*map(reversed(chain.ctrls), reversed(chain.ref_orgs), reversed(chain.def_bones), reversed(chain.org_bones)) for ctrl, ref_org, bone in chain_bones: #zip(chain.ctrls, chain.ref_orgs, 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 ref_org.rotation_mode = ctrl.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 # testing without transform constraints # constraint_add(ctrl, bone, con_type) if self.bake_org: #Attaching the ref org bones to the original for extra precision constraint_add(ref_org, bone, con_type) if ctrl.btc.setup == 'POLE': continue # The distance between the two bones in rest pose distance = (bone.id_data.matrix_world @ bone.bone.matrix_local).inverted() @ ctrl.bone.matrix_local.copy() ctrl.matrix = bone.id_data.matrix_world @ bone.matrix @ distance # ctrl.matrix = bone.id_data.matrix_world @ ctrl.bone.matrix_local @ bone.matrix_basis # if chain.parent: # ctrl.matrix = bone.id_data.matrix_world @ ctrl.matrix @ chain.parent.matrix_basis.inverted() # #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) context.view_layer.update() #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, 'HEAD', 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') if chain.pole_bone: chain.pole_angle = get_pole_angle(chain.base_bone, chain.ik_tip, chain.pole_bone.matrix.translation) 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() #### Assigning the org id to the org ref bones and baking them to match the original bones#### for bone, ref_org in zip(chain.org_bones, chain.ref_orgs): add_bone_setup_id(ref_org, False, 'TEMPIK', 'ORG', bone.btc.setup_id, bone.btc.org_id) if not self.bake_org: #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() if self.bake_org: # Bake to the last layer of bones to preserve the original bones after the ik bake smartbake_to_ctrls(context, self, chain.org_bones, chain.ref_orgs, rig) constraints_clear(chain.ref_orgs) #add the transform constraints and driver to the original bones to match the temp ik for bone, ref_org in zip(chain.org_bones, chain.ref_orgs): constraint = constraint_add(bone, ref_org, con_type) add_enabled_driver(bone.id_data, rig, constraint) include_collections(included_collections, hidden_objs, self.temp_col, controlled_objs) del ik_chains, child_names, ctrls, root_name, rig, setup_ids, selected # print('\nseconds\n', time.time() - sec) 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(self, 'bake_org') col.prop(btc, 'add_ik_ctrl') col.prop(self, 'orientation') split = layout.split(factor=0.58, align = True) split.prop(btc, 'pole_target') if btc.pole_target: split.prop(btc, 'pole_offset', text = '') if hasattr(self, 'flat') and self.flat: box = layout.box() row = box.row() row.label(text = 'The chain is Flat! choose the pole') row = box.row() row.label(text = 'Direction') row.prop(btc, 'pole_axis', text ='') # 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.ws_type != 'TRACK': self.twist = False if not self.twist: return [] 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### 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_', 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_', 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): '''Checking if root needs to be added to the bake''' 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 check_root_fcurves(context, rig, obj, root_ctrl): '''Check if should be baked into root ctrl or not 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 ctrl already has animation then skip baking if root_fcus: return False # If the original root bone has no animation and nothing connected to it then skip root_bone = obj.pose.bones[context.scene.btc.root_bone] if not root_bone.parent and not get_bone_fcurves(root_bone): return False return True def constrain_root(context, rig, obj, root_ctrl): #make sure there is not already animation data on the rig if not check_root_fcurves(context, rig, obj, root_ctrl): 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 self.pole_bone = None self.pole_bone_name = None self.step = get_step(hierarchy[0].id_data) def find_org_bone(self, ctrlbone): index = self.ctrls.index(ctrlbone) org_bone = self.org_bones[index] return org_bone 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 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, AddEmptyCtrls, 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)