├── .gitignore ├── define.py ├── lib ├── cs.blend ├── context.py ├── addon.py ├── bones_edit.py ├── drivers.py ├── constraints.py ├── bones_data.py ├── armature.py ├── mixamo.py ├── version.py ├── custom_props.py ├── objects.py ├── bones_pose.py ├── maths_geo.py └── animation.py ├── README.md ├── utils.py ├── mixamo_rig_prefs.py ├── definitions └── naming.py ├── __init__.py ├── blender_manifest.toml ├── mixamo_rig_functions.py └── mixamo_rig.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .idea/ -------------------------------------------------------------------------------- /define.py: -------------------------------------------------------------------------------- 1 | from .definitions.naming import * -------------------------------------------------------------------------------- /lib/cs.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlenderBoi/mixamo_blender/HEAD/lib/cs.blend -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | this repo is retired, please use the addon from the link below 2 | 3 | 4 | https://extensions.blender.org/add-ons/mixamo-rig/ -------------------------------------------------------------------------------- /lib/context.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def get_current_mode(): 4 | return bpy.context.mode 5 | 6 | 7 | def restore_current_mode(current_mode): 8 | if current_mode == 'EDIT_ARMATURE': 9 | current_mode = 'EDIT' 10 | bpy.ops.object.mode_set(mode=current_mode) -------------------------------------------------------------------------------- /lib/addon.py: -------------------------------------------------------------------------------- 1 | import bpy, sys, linecache, ast 2 | 3 | def get_error_message(): 4 | exc_type, exc_obj, tb = sys.exc_info() 5 | f = tb.tb_frame 6 | lineno = tb.tb_lineno 7 | filename = f.f_code.co_filename 8 | linecache.checkcache(filename) 9 | line = linecache.getline(filename, lineno, f.f_globals) 10 | error_message = 'Error in ({}\nLine {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj) 11 | return error_message 12 | 13 | 14 | def get_addon_preferences(): 15 | return bpy.context.preferences.addons[__package__].preferences -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import bpy, os 2 | from math import * 3 | from mathutils import * 4 | from bpy.types import Panel, UIList 5 | from .lib.objects import * 6 | from .lib.bones_data import * 7 | from .lib.bones_edit import * 8 | from .lib.bones_pose import * 9 | from .lib.context import * 10 | from .lib.addon import * 11 | from .lib.mixamo import * 12 | from .lib.armature import * 13 | from .lib.constraints import * 14 | from .lib.animation import * 15 | from .lib.maths_geo import * 16 | from .lib.drivers import * 17 | from .lib.custom_props import * 18 | from .lib.version import * -------------------------------------------------------------------------------- /lib/bones_edit.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def get_edit_bone(name): 4 | return bpy.context.object.data.edit_bones.get(name) 5 | 6 | 7 | def copy_bone_transforms(bone1, bone2): 8 | # copy editbone bone1 transforms to bone 2 9 | bone2.head = bone1.head.copy() 10 | bone2.tail = bone1.tail.copy() 11 | bone2.roll = bone1.roll 12 | 13 | 14 | def create_edit_bone(bone_name, deform=False): 15 | b = get_edit_bone(bone_name) 16 | if b == None: 17 | b = bpy.context.active_object.data.edit_bones.new(bone_name) 18 | b.use_deform = deform 19 | return b -------------------------------------------------------------------------------- /lib/drivers.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def add_driver_to_prop(obj, dr_dp, tar_dp, array_idx=-1, exp="var"): 4 | if obj.animation_data == None: 5 | obj.animation_data_create() 6 | 7 | drivers_list = obj.animation_data.drivers 8 | dr = drivers_list.find(dr_dp, index=array_idx) 9 | 10 | if dr == None: 11 | dr = obj.driver_add(dr_dp, array_idx) 12 | 13 | dr.driver.expression = exp 14 | 15 | var = dr.driver.variables.get('var') 16 | 17 | if var == None: 18 | var = dr.driver.variables.new() 19 | 20 | var.type = 'SINGLE_PROP' 21 | var.name = 'var' 22 | var.targets[0].id = obj 23 | var.targets[0].data_path = tar_dp -------------------------------------------------------------------------------- /lib/constraints.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def add_copy_transf(p_bone, tgt, subtgt): 4 | cns_transf = p_bone.constraints.get("Copy Transforms") 5 | if cns_transf == None: 6 | cns_transf = p_bone.constraints.new("COPY_TRANSFORMS") 7 | cns_transf.name = "Copy Transforms" 8 | cns_transf.target = tgt 9 | cns_transf.subtarget = subtgt 10 | 11 | 12 | def set_constraint_inverse_matrix(cns): 13 | # set the inverse matrix of Child Of constraint 14 | tar_obj = cns.target 15 | subtarget_pbone = tar_obj.pose.bones.get(cns.subtarget) 16 | if subtarget_pbone: 17 | #cns.inverse_matrix = tar_obj.matrix_world.inverted() @ subtarget_pbone.matrix_basis.inverted() 18 | print("reset child of cns", cns.name, cns.subtarget) 19 | cns.inverse_matrix = subtarget_pbone.bone.matrix_local.to_4x4().inverted() -------------------------------------------------------------------------------- /mixamo_rig_prefs.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def update_all_tab_names(self, context): 4 | try: 5 | from . import mixamo_rig 6 | mixamo_rig.update_mixamo_tab() 7 | except: 8 | pass 9 | 10 | 11 | class MR_MT_addon_preferences(bpy.types.AddonPreferences): 12 | bl_idname = __package__ 13 | mixamo_tab_name : bpy.props.StringProperty(name="Interface Tab", description="Name of the tab to display the interface in", default="Mixamo", update=update_all_tab_names) 14 | 15 | def draw(self, context): 16 | col = self.layout.column(align=True) 17 | col.prop(self, "mixamo_tab_name", text="Interface Tab") 18 | 19 | 20 | def register(): 21 | from bpy.utils import register_class 22 | 23 | try: 24 | register_class(MR_MT_addon_preferences) 25 | except: 26 | pass 27 | 28 | 29 | def unregister(): 30 | from bpy.utils import unregister_class 31 | unregister_class(MR_MT_addon_preferences) 32 | -------------------------------------------------------------------------------- /lib/bones_data.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def get_data_bone(name): 4 | return bpy.context.active_object.data.bones.get(name) 5 | 6 | 7 | def set_bone_collection(armt, databone, coll_name, multi=False): 8 | if databone is None: 9 | return 10 | 11 | armt = armt.data 12 | 13 | coll = None 14 | for c in armt.collections: 15 | if c.name == coll_name: 16 | coll = c 17 | break 18 | 19 | if coll is None: 20 | coll = armt.collections.new(coll_name) 21 | 22 | colls_to_remove_from = None 23 | if not multi: 24 | colls_to_remove_from = [c for c in databone.collections] 25 | 26 | r = coll.assign(databone) 27 | 28 | if colls_to_remove_from is not None: 29 | for c in colls_to_remove_from: 30 | c.unassign(databone) 31 | 32 | # ~ databone.layers[layer_idx] = True 33 | 34 | # ~ for i, lay in enumerate(databone.layers): 35 | # ~ if i != layer_idx: 36 | # ~ databone.layers[i] = False 37 | -------------------------------------------------------------------------------- /lib/armature.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def restore_armature_layers(layers_select): 4 | # restore the armature layers visibility 5 | # ~ for i in range(0, 32): 6 | # ~ bpy.context.active_object.data.layers[i] = layers_select[i] 7 | for c in bpy.context.active_object.data.collections: 8 | if c.name in layers_select: 9 | c.is_visible = layers_select[c.name] 10 | else: 11 | c.is_visible = False 12 | 13 | 14 | def enable_all_armature_layers(): 15 | # enable all layers 16 | # and return the list of each layer visibility 17 | # ~ _layers = bpy.context.active_object.data.layers 18 | # ~ layers_select = [] 19 | # ~ for i in range(0, 32): 20 | # ~ layers_select.append(_layers[i]) 21 | # ~ for i in range(0, 32): 22 | # ~ bpy.context.active_object.data.layers[i] = True 23 | 24 | layers_select = {} 25 | for c in bpy.context.active_object.data.collections: 26 | layers_select[c.name] = c.is_visible 27 | c.is_visible = True 28 | 29 | return layers_select 30 | -------------------------------------------------------------------------------- /lib/mixamo.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .bones_pose import * 3 | from .bones_data import * 4 | from ..definitions import naming 5 | 6 | def get_mixamo_prefix(): 7 | p = "" 8 | rig = bpy.context.active_object 9 | 10 | if 'mixamo_prefix' in rig.data.keys(): 11 | p = rig.data["mixamo_prefix"] 12 | 13 | else: 14 | for dbone in rig.data.bones: 15 | if dbone.name.startswith("mixamorig") and ':' in dbone.name: 16 | p = dbone.name.split(':')[0]+':' 17 | break 18 | 19 | try: 20 | rig.data["mixamo_prefix"] = p 21 | except:# context error 22 | pass 23 | 24 | return p 25 | 26 | 27 | def get_mix_name(name, use_prefix): 28 | if not use_prefix: 29 | return name 30 | else: 31 | p = get_mixamo_prefix() 32 | return p+name 33 | 34 | 35 | def get_bone_side(bone_name): 36 | if bone_name.endswith("_Left"): 37 | return "Left" 38 | elif bone_name.endswith("_Right"): 39 | return "Right" -------------------------------------------------------------------------------- /lib/version.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ARP_blender_version: 4 | _string = bpy.app.version_string 5 | blender_v = bpy.app.version 6 | _float = blender_v[0]*100+blender_v[1]+blender_v[2]*0.01 7 | #_char = bpy.app.version_string 8 | 9 | blender_version = ARP_blender_version() 10 | 11 | 12 | def convert_drivers_cs_to_xyz(armature): 13 | # Blender 3.0 requires Vector3 custom_shape_scale values 14 | # convert single uniform driver to vector3 array drivers 15 | drivers_armature = [i for i in armature.animation_data.drivers] 16 | 17 | for dr in drivers_armature: 18 | if 'custom_shape_scale' in dr.data_path: 19 | if not 'custom_shape_scale_xyz' in dr.data_path: 20 | for i in range(0, 3): 21 | new_dr = armature.animation_data.drivers.from_existing(src_driver=dr) 22 | new_dr.data_path = new_dr.data_path.replace('custom_shape_scale', 'custom_shape_scale_xyz') 23 | new_dr.array_index = i 24 | new_dr.driver.expression += ''# update hack 25 | 26 | armature.driver_remove(dr.data_path, dr.array_index) 27 | 28 | print("Converted custom shape scale drivers to xyz") 29 | 30 | 31 | def get_custom_shape_scale_prop_name(): 32 | if blender_version._float >= 300: 33 | return 'custom_shape_scale_xyz' 34 | else: 35 | return 'custom_shape_scale' 36 | -------------------------------------------------------------------------------- /definitions/naming.py: -------------------------------------------------------------------------------- 1 | # control rig 2 | c_prefix = "Ctrl_" 3 | master_rig_names = {"master":"Master"} 4 | spine_rig_names = {"pelvis":"Hips", "spine1":"Spine", "spine2":"Spine1", "spine3":"Spine2", "hips_free":"Hips_Free", "hips_free_helper":"Hips_Free_Helper"} 5 | head_rig_names = {"neck":"Neck", "head":"Head"} 6 | leg_rig_names = {"thigh_ik":"UpLeg_IK", "thigh_fk":"UpLeg_FK", "calf_ik":"Leg_IK", "calf_fk":"Leg_FK", "foot_fk":"Foot_FK", "foot_ik":"Foot_IK", "foot_snap":"Foot_Snap", "foot_ik_target":"Foot_IK_target", "foot_01":"Foot_01", "foot_01_pole":"Foot_01_Pole", "heel_out":"FootHeelOut", "heel_in":"FootHeelIn", "heel_mid":"FootHeelMid", "toes_end":"ToeEnd", "toes_end_01":"ToeEnd_01", "toes_ik":"Toe_IK", "toes_track":"ToeTrack", "toes_01_ik":"Toe01_IK", "toes_02":"Toe02", "toes_fk":"Toe_FK", "foot_roll_cursor":"FootRoll_Cursor", "pole_ik":"LegPole_IK"} 7 | arm_rig_names = {"shoulder":"Shoulder", "arm_ik":"Arm_IK", "arm_fk":"Arm_FK", "forearm_ik":"ForeArm_IK", "forearm_fk":"ForeArm_FK", "pole_ik":"ArmPole_IK", "hand_ik":"Hand_IK", "hand_fk":"Hand_FK"} 8 | 9 | # mixamo bone names 10 | spine_names = {"pelvis":"Hips", "spine1":"Spine", "spine2":"Spine1", "spine3":"Spine2"} 11 | head_names = {"neck":"Neck", "head":"Head", "head_end":"HeadTop_End"} 12 | leg_names = {"thigh":"UpLeg", "calf":"Leg", "foot":"Foot", "toes":"ToeBase", "toes_end":"Toe_End"} 13 | arm_names = {"shoulder":"Shoulder", "arm":"Arm", "forearm":"ForeArm", "hand":"Hand"} 14 | fingers_type = ["Thumb", "Index", "Middle", "Ring", "Pinky"] -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN GPL LICENSE BLOCK ***** 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU General Public License 6 | # as published by the Free Software Foundation; either version 2 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | # ***** END GPL LICENCE BLOCK ***** 19 | 20 | 21 | bl_info = { 22 | "name": "Mixamo Rig for Blender", 23 | "author": "Mixamo, Xin, TinkerBoi", 24 | "version": (1, 1, 2), 25 | "blender": (4, 0, 0), 26 | "location": "3D View > Mixamo> Control Rig", 27 | "description": "Generate a control rig from the selected Mixamo Fbx skeleton", 28 | "category": "Animation", 29 | "doc_url": "https://github.com/BlenderBoi/mixamo_blender", 30 | "tracker_url": "https://github.com/BlenderBoi/mixamo_blender" 31 | } 32 | 33 | 34 | if "bpy" in locals(): 35 | import importlib 36 | if "mixamo_rig_prefs" in locals(): 37 | importlib.reload(mixamo_rig_prefs) 38 | if "mixamo_rig" in locals(): 39 | importlib.reload(mixamo_rig) 40 | if "mixamo_rig_functions" in locals(): 41 | importlib.reload(mixamo_rig_functions) 42 | if "utils" in locals(): 43 | importlib.reload(utils) 44 | if "animation" in locals(): 45 | importlib.reload(animation) 46 | 47 | 48 | import bpy 49 | from . import mixamo_rig_prefs 50 | from . import mixamo_rig 51 | from . import mixamo_rig_functions 52 | from . import utils 53 | 54 | def register(): 55 | mixamo_rig_prefs.register() 56 | mixamo_rig.register() 57 | mixamo_rig_functions.register() 58 | 59 | def unregister(): 60 | mixamo_rig_prefs.unregister() 61 | mixamo_rig.unregister() 62 | mixamo_rig_functions.unregister() 63 | 64 | if __name__ == "__main__": 65 | register() 66 | -------------------------------------------------------------------------------- /lib/custom_props.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .version import blender_version 3 | 4 | 5 | def get_prop_setting(node, prop_name, setting): 6 | if blender_version._float >= 300: 7 | return node.id_properties_ui(prop_name).as_dict()[setting] 8 | else: 9 | return node['_RNA_UI'][prop_name][setting] 10 | 11 | 12 | def set_prop_setting(node, prop_name, setting, value): 13 | if blender_version._float >= 300: 14 | ui_data = node.id_properties_ui(prop_name) 15 | if setting == 'default': 16 | ui_data.update(default=value) 17 | elif setting == 'min': 18 | ui_data.update(min=value) 19 | elif setting == 'max': 20 | ui_data.update(max=value) 21 | elif setting == 'soft_min': 22 | ui_data.update(soft_min=value) 23 | elif setting == 'soft_max': 24 | ui_data.update(soft_max=value) 25 | elif setting == 'description': 26 | ui_data.update(description=value) 27 | 28 | else: 29 | if not "_RNA_UI" in node.keys(): 30 | node["_RNA_UI"] = {} 31 | node['_RNA_UI'][prop_name][setting] = value 32 | 33 | 34 | def create_custom_prop(node=None, prop_name="", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="", soft_min=None, soft_max=None, default=None): 35 | if soft_min == None: 36 | soft_min = prop_min 37 | if soft_max == None: 38 | soft_max = prop_max 39 | 40 | if blender_version._float < 300: 41 | if not "_RNA_UI" in node.keys(): 42 | node["_RNA_UI"] = {} 43 | 44 | node[prop_name] = prop_val 45 | 46 | if default == None: 47 | default = prop_val 48 | 49 | if blender_version._float < 300: 50 | node["_RNA_UI"][prop_name] = {'use_soft_limits':True, 'min': prop_min, 'max': prop_max, 'description': prop_description, 'soft_min':soft_min, 'soft_max':soft_max, 'default':default} 51 | else: 52 | set_prop_setting(node, prop_name, 'min', prop_min) 53 | set_prop_setting(node, prop_name, 'max', prop_max) 54 | set_prop_setting(node, prop_name, 'description', prop_description) 55 | set_prop_setting(node, prop_name, 'soft_min', soft_min) 56 | set_prop_setting(node, prop_name, 'soft_max', soft_max) 57 | set_prop_setting(node, prop_name, 'default', default) 58 | 59 | # set as overridable 60 | node.property_overridable_library_set('["'+prop_name+'"]', True) -------------------------------------------------------------------------------- /blender_manifest.toml: -------------------------------------------------------------------------------- 1 | 2 | schema_version = "1.0.0" 3 | id = "mixamo" 4 | version = "1.1.1" 5 | name = "Mixamo Rig for Blender" 6 | tagline = "Generate a control rig from the selected Mixamo Fbx skeleton" 7 | maintainer = "Mixamo, Xin, TinkerBoi " 8 | type = "add-on" 9 | 10 | website = "https://blendermarket.com/creators/tinkerboi" 11 | tags = ["Rigging"] 12 | blender_version_min = "4.0.0" 13 | # blender_version_max = "4.2.0" 14 | 15 | # License conforming to https://spdx.org/licenses/ (use "SPDX: prefix) 16 | # https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html 17 | license = [ 18 | "SPDX:GPL-2.0-or-later", 19 | ] 20 | # Optional: required by some licenses. 21 | # copyright = [ 22 | # "2002-2024 Developer Name", 23 | # "1998 Company Name", 24 | # ] 25 | 26 | # Optional list of supported platforms. If omitted, the extension will be available in all operating systems. 27 | # platforms = ["windows-x64", "macos-arm64", "linux-x64"] 28 | # Other supported platforms: "windows-arm64", "macos-x64" 29 | 30 | # Optional: bundle 3rd party Python modules. 31 | # https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html 32 | # wheels = [ 33 | # "./wheels/hexdump-3.3-py3-none-any.whl", 34 | # "./wheels/jsmin-3.0.1-py3-none-any.whl", 35 | # ] 36 | 37 | # # Optional: add-ons can list which resources they will require: 38 | # # * files (for access of any filesystem operations) 39 | # # * network (for internet access) 40 | # # * clipboard (to read and/or write the system clipboard) 41 | # # * camera (to capture photos and videos) 42 | # # * microphone (to capture audio) 43 | # # 44 | # # If using network, remember to also check `bpy.app.online_access` 45 | # # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access 46 | # # 47 | # # For each permission it is important to also specify the reason why it is required. 48 | # # Keep this a single short sentence without a period (.) at the end. 49 | # # For longer explanations use the documentation or detail page. 50 | # 51 | # [permissions] 52 | # network = "Need to sync motion-capture data to server" 53 | # files = "Import/export FBX from/to disk" 54 | # clipboard = "Copy and paste bone transforms" 55 | 56 | # Optional: build settings. 57 | # https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build 58 | # [build] 59 | # paths_exclude_pattern = [ 60 | # "__pycache__/", 61 | # "/.git/", 62 | # "/*.zip", 63 | # ] 64 | -------------------------------------------------------------------------------- /lib/objects.py: -------------------------------------------------------------------------------- 1 | import bpy, os 2 | 3 | def delete_object(obj): 4 | bpy.data.objects.remove(obj, do_unlink=True) 5 | 6 | 7 | def duplicate_object(): 8 | try: 9 | bpy.ops.object.duplicate(linked=False, mode='TRANSLATION') 10 | except: 11 | bpy.ops.object.duplicate('TRANSLATION', False) 12 | 13 | 14 | def get_object(name): 15 | return bpy.data.objects.get(name) 16 | 17 | 18 | def set_active_object(object_name): 19 | bpy.context.view_layer.objects.active = bpy.data.objects[object_name] 20 | bpy.data.objects[object_name].select_set(state=True) 21 | 22 | 23 | def hide_object(obj_to_set): 24 | obj_to_set.hide_set(True) 25 | obj_to_set.hide_viewport = True 26 | 27 | 28 | def is_object_hidden(obj_to_get): 29 | if obj_to_get.hide_get() == False and obj_to_get.hide_viewport == False: 30 | return False 31 | else: 32 | return True 33 | 34 | 35 | def append_cs(names=[]): 36 | context = bpy.context 37 | scene = context.scene 38 | addon_directory = os.path.dirname(os.path.abspath(__file__)) 39 | filepath = addon_directory + "\cs.blend" 40 | 41 | # load the objects data in file 42 | with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to): 43 | data_to.objects = [name for name in data_from.objects if name in names] 44 | 45 | # Add the objects in the scene 46 | for obj in data_to.objects: 47 | if obj: 48 | # link in collec 49 | scene.collection.objects.link(obj) 50 | 51 | cs_grp = bpy.data.objects.get("cs_grp") 52 | if cs_grp == None: 53 | cs_grp = bpy.data.objects.new(name="cs_grp", object_data=None) 54 | bpy.context.collection.objects.link(cs_grp) 55 | cs_grp.location = [0,0,0] 56 | cs_grp.rotation_euler = [0,0,0] 57 | cs_grp.scale = [1,1,1] 58 | 59 | # parent the custom shape 60 | obj.parent = cs_grp 61 | 62 | # assign to new collection 63 | assigned_collections = [] 64 | for collec in cs_grp.users_collection: 65 | try: 66 | collec.objects.link(obj) 67 | assigned_collections.append(collec) 68 | except:# already in collection 69 | pass 70 | 71 | if len(assigned_collections): 72 | # remove previous collections 73 | for i in obj.users_collection: 74 | if not i in assigned_collections: 75 | i.objects.unlink(obj) 76 | # and the scene collection 77 | try: 78 | scene.collection.objects.unlink(obj) 79 | except: 80 | pass 81 | -------------------------------------------------------------------------------- /lib/bones_pose.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .objects import * 3 | from .version import * 4 | 5 | def get_custom_shape_scale(pbone, uniform=True): 6 | if blender_version._float >= 300: 7 | if uniform: 8 | # uniform scale 9 | val = 0 10 | for i in range(0,3): 11 | val += pbone.custom_shape_scale_xyz[i] 12 | return val/3 13 | # array scale 14 | else: 15 | return pbone.custom_shape_scale_xyz 16 | # pre-Blender 3.0 17 | else: 18 | return pbone.custom_shape_scale 19 | 20 | 21 | def get_selected_pbone_name(): 22 | try: 23 | return bpy.context.selected_pose_bones[0].name#.active_pose_bone.name 24 | except: 25 | return 26 | 27 | 28 | def get_pose_bone(name): 29 | return bpy.context.active_object.pose.bones.get(name) 30 | 31 | 32 | def lock_pbone_transform(pbone, type, list): 33 | for i in list: 34 | if type == "location": 35 | pbone.lock_location[i] = True 36 | elif type == "rotation": 37 | pbone.lock_rotation[i] = True 38 | elif type == "scale": 39 | pbone.lock_scale[i] = True 40 | 41 | 42 | def set_bone_custom_shape(pbone, cs_name): 43 | cs = get_object(cs_name) 44 | if cs == None: 45 | append_cs(cs_name) 46 | cs = get_object(cs_name) 47 | 48 | pbone.custom_shape = cs 49 | 50 | 51 | def set_bone_color_group(obj, pb, grp_name): 52 | # mixamo required color 53 | orange = (0.969, 0.565, 0.208) 54 | orange_light = (0.957, 0.659, 0.416) 55 | blue_dark = (0.447, 0.682, 1.0) 56 | blue_light = (0.365, 0.851, 1.0) 57 | 58 | # base color 59 | green = (0.0, 1.0, 0.0) 60 | red = (1.0, 0.0, 0.0) 61 | blue = (0.0, 0.9, 1.0) 62 | 63 | grp_color_master = orange_light 64 | grp_color_neck = orange_light 65 | grp_color_root_master = orange 66 | grp_color_head = orange 67 | grp_color_body_mid = green 68 | grp_color_body_left = blue_dark 69 | grp_color_body_right = blue_light 70 | 71 | # ~ grp = obj.data.collections.get(grp_name) 72 | # ~ grp = obj.pose.bone_groups.get(grp_name) 73 | # ~ if grp == None: 74 | # ~ grp = obj.data.collections.new(grp_name) 75 | # ~ grp = obj.pose.bone_groups.new(name=grp_name) 76 | # ~ grp.color_set = 'CUSTOM' 77 | 78 | grp_color = None 79 | if grp_name == "body_mid": 80 | grp_color = grp_color_body_mid 81 | elif grp_name == "body_left": 82 | grp_color = grp_color_body_left 83 | elif grp_name == "body_right": 84 | grp_color = grp_color_body_right 85 | elif grp_name == "master": 86 | grp_color = grp_color_master 87 | elif grp_name == "neck": 88 | grp_color = grp_color_head 89 | elif grp_name == "head": 90 | grp_color = grp_color_neck 91 | elif grp_name == "root_master": 92 | grp_color = grp_color_root_master 93 | 94 | # set normal color 95 | # ~ grp.colors.normal = grp_color 96 | 97 | # set select color/active color 98 | # ~ for col_idx in range(0,3): 99 | # ~ grp.colors.select[col_idx] = grp_color[col_idx] + 0.2 100 | # ~ grp.colors.active[col_idx] = grp_color[col_idx] + 0.4 101 | 102 | # ~ r = grp.assign(pb) 103 | # ~ pb.bone_group = grp 104 | 105 | pb.color.palette = 'CUSTOM' 106 | pb.color.custom.normal = grp_color 107 | for col_idx in range(0,3): 108 | pb.color.custom.select[col_idx] = grp_color[col_idx] + 0.2 109 | pb.color.custom.active[col_idx] = grp_color[col_idx] + 0.4 110 | 111 | 112 | def update_transform(): 113 | bpy.ops.transform.rotate(value=0, orient_axis='Z', orient_type='VIEW', orient_matrix=((0.0, 0.0, 0), (0, 0.0, 0.0), (0.0, 0.0, 0.0)), orient_matrix_type='VIEW', mirror=False) 114 | -------------------------------------------------------------------------------- /lib/maths_geo.py: -------------------------------------------------------------------------------- 1 | from math import * 2 | from mathutils import * 3 | 4 | 5 | def mat3_to_vec_roll(mat): 6 | vec = mat.col[1] 7 | vecmat = vec_roll_to_mat3(mat.col[1], 0) 8 | vecmatinv = vecmat.inverted() 9 | rollmat = vecmatinv @ mat 10 | roll = atan2(rollmat[0][2], rollmat[2][2]) 11 | return roll 12 | 13 | 14 | def vec_roll_to_mat3(vec, roll): 15 | target = Vector((0, 0.1, 0)) 16 | nor = vec.normalized() 17 | axis = target.cross(nor) 18 | if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix 19 | axis.normalize() 20 | theta = target.angle(nor) 21 | bMatrix = Matrix.Rotation(theta, 3, axis) 22 | else: 23 | updown = 1 if target.dot(nor) > 0 else -1 24 | bMatrix = Matrix.Scale(updown, 3) 25 | bMatrix[2][2] = 1.0 26 | 27 | rMatrix = Matrix.Rotation(roll, 3, nor) 28 | mat = rMatrix @ bMatrix 29 | return mat 30 | 31 | 32 | def align_bone_x_axis(edit_bone, new_x_axis): 33 | new_x_axis = new_x_axis.cross(edit_bone.y_axis) 34 | new_x_axis.normalize() 35 | dot = max(-1.0, min(1.0, edit_bone.z_axis.dot(new_x_axis))) 36 | angle = acos(dot) 37 | edit_bone.roll += angle 38 | dot1 = edit_bone.z_axis.dot(new_x_axis) 39 | edit_bone.roll -= angle * 2.0 40 | dot2 = edit_bone.z_axis.dot(new_x_axis) 41 | if dot1 > dot2: 42 | edit_bone.roll += angle * 2.0 43 | 44 | 45 | def align_bone_z_axis(edit_bone, new_z_axis): 46 | new_z_axis = -(new_z_axis.cross(edit_bone.y_axis)) 47 | new_z_axis.normalize() 48 | dot = max(-1.0, min(1.0, edit_bone.x_axis.dot(new_z_axis))) 49 | angle = acos(dot) 50 | edit_bone.roll += angle 51 | dot1 = edit_bone.x_axis.dot(new_z_axis) 52 | edit_bone.roll -= angle * 2.0 53 | dot2 = edit_bone.x_axis.dot(new_z_axis) 54 | if dot1 > dot2: 55 | edit_bone.roll += angle * 2.0 56 | 57 | 58 | def signed_angle(u, v, normal): 59 | nor = normal.normalized() 60 | a = u.angle(v) 61 | 62 | c = u.cross(v) 63 | 64 | if c.magnitude == 0.0: 65 | c = u.normalized().cross(v) 66 | if c.magnitude == 0.0: 67 | return 0.0 68 | 69 | if c.angle(nor) < 1: 70 | a = -a 71 | return a 72 | 73 | 74 | def project_point_onto_plane(q, p, n): 75 | n = n.normalized() 76 | return q - ((q - p).dot(n)) * n 77 | 78 | 79 | def get_pole_angle(base_bone, ik_bone, pole_location): 80 | pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head) 81 | projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head) 82 | return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head) 83 | 84 | 85 | def get_pose_matrix_in_other_space(mat, pose_bone): 86 | rest = pose_bone.bone.matrix_local.copy() 87 | rest_inv = rest.inverted() 88 | 89 | if pose_bone.parent and pose_bone.bone.use_inherit_rotation: 90 | par_mat = pose_bone.parent.matrix.copy() 91 | par_inv = par_mat.inverted() 92 | par_rest = pose_bone.parent.bone.matrix_local.copy() 93 | 94 | else: 95 | par_mat = Matrix() 96 | par_inv = Matrix() 97 | par_rest = Matrix() 98 | 99 | smat = rest_inv @ (par_rest @ (par_inv @ mat)) 100 | 101 | return smat 102 | 103 | 104 | def get_ik_pole_pos(b1, b2, method=1, axis=None): 105 | 106 | if method == 1: 107 | # IK pole position based on real IK bones vector 108 | plane_normal = (b1.head - b2.tail) 109 | midpoint = (b1.head + b2.tail) * 0.5 110 | prepole_dir = b2.head - midpoint#prepole_fk.tail - prepole_fk.head 111 | pole_pos = b2.head + prepole_dir.normalized()# * 4 112 | pole_pos = project_point_onto_plane(pole_pos, b2.head, plane_normal) 113 | pole_pos = b2.head + ((pole_pos - b2.head).normalized() * (b2.head - b1.head).magnitude * 1.7) 114 | 115 | elif method == 2: 116 | # IK pole position based on bone2 Z axis vector 117 | pole_pos = b2.head + (axis.normalized() * (b2.tail-b2.head).magnitude) 118 | 119 | return pole_pos 120 | 121 | 122 | def rotate_point(point, angle, origin, axis): 123 | rot_mat = Matrix.Rotation(angle, 4, axis.normalized()) 124 | # rotate in world origin space 125 | offset_vec = -origin 126 | offset_knee = point + offset_vec 127 | # rotate 128 | rotated_point = rot_mat @ offset_knee 129 | # bring back to original space 130 | rotated_point = rotated_point -offset_vec 131 | return rotated_point 132 | 133 | 134 | def dot_product(x, y): 135 | return sum([x[i] * y[i] for i in range(len(x))]) 136 | 137 | 138 | def norm(x): 139 | return sqrt(dot_product(x, x)) 140 | 141 | 142 | def normalize(x): 143 | return [x[i] / norm(x) for i in range(len(x))] 144 | 145 | 146 | def project_vector_onto_plane(x, n): 147 | d = dot_product(x, n) / norm(n) 148 | p = [d * normalize(n)[i] for i in range(len(n))] 149 | vec_list = [x[i] - p[i] for i in range(len(x))] 150 | return Vector((vec_list[0], vec_list[1], vec_list[2])) 151 | -------------------------------------------------------------------------------- /lib/animation.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .maths_geo import * 3 | from .bones_pose import * 4 | from .version import * 5 | 6 | def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True, bake_object=False, ik_data=None): 7 | scn = bpy.context.scene 8 | obj_data = [] 9 | bones_data = [] 10 | armature = bpy.data.objects.get(bpy.context.active_object.name) 11 | 12 | def get_bones_matrix(): 13 | matrix = {} 14 | for pbone in armature.pose.bones: 15 | if only_selected and not pbone.bone.select: 16 | continue 17 | 18 | bmat = pbone.matrix 19 | 20 | # IK poles 21 | if pbone.name.startswith("Ctrl_ArmPole") or pbone.name.startswith("Ctrl_LegPole"): 22 | b1 = b2 = None 23 | src_arm = ik_data["src_arm"] 24 | type = "" 25 | if "Leg" in pbone.name: 26 | type = "Leg" 27 | elif "Arm" in pbone.name: 28 | type = "Arm" 29 | 30 | name_split = pbone.name.split('_') 31 | side = name_split[len(name_split)-1] 32 | b1_name = ik_data[type+side][0] 33 | b2_name = ik_data[type+side][1] 34 | b1 = src_arm.pose.bones.get(b1_name) 35 | b2 = src_arm.pose.bones.get(b2_name) 36 | 37 | _axis = None 38 | if type == "Leg": 39 | _axis = (b1.z_axis*0.5) + (b2.z_axis*0.5)#b1.z_axis# 40 | elif type == "Arm": 41 | if side == "Left": 42 | _axis = b2.x_axis 43 | elif side == "Right": 44 | _axis = -b2.x_axis 45 | 46 | pole_pos = get_ik_pole_pos(b1, b2, method=2, axis=_axis) 47 | #pole_pos = b2.head + (b2.z_axis.normalized() * (b2.tail-b2.head).magnitude) 48 | bmat = Matrix.Translation(pole_pos) 49 | 50 | # Child Of constraints are preserved after baking 51 | # need to compensate the matrix with the Child Of transformation 52 | child_of_cns = pbone.constraints.get("Child Of") 53 | if child_of_cns: 54 | if child_of_cns.influence == 1.0 and child_of_cns.mute == False: 55 | bmat = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ bmat 56 | 57 | matrix[pbone.name] = armature.convert_space(pose_bone=pbone, matrix=bmat, from_space="POSE", to_space="LOCAL") 58 | 59 | return matrix 60 | 61 | def get_obj_matrix(): 62 | parent = armature.parent 63 | matrix = armature.matrix_world 64 | if parent: 65 | return parent.matrix_world.inverted_safe() @ matrix 66 | else: 67 | return matrix.copy() 68 | 69 | # store matrices 70 | current_frame = scn.frame_current 71 | 72 | for f in range(int(frame_start), int(frame_end+1)): 73 | scn.frame_set(f) 74 | bpy.context.view_layer.update() 75 | 76 | if bake_bones: 77 | bones_data.append((f, get_bones_matrix())) 78 | if bake_object: 79 | obj_data.append((f, get_obj_matrix())) 80 | 81 | # set new action 82 | action = bpy.data.actions.new("Action") 83 | anim_data = armature.animation_data_create() 84 | anim_data.action = action 85 | 86 | def store_keyframe(bn, prop_type, fc_array_index, fra, val): 87 | fc_data_path = 'pose.bones["' + bn + '"].' + prop_type 88 | fc_key = (fc_data_path, fc_array_index) 89 | if not keyframes.get(fc_key): 90 | keyframes[fc_key] = [] 91 | keyframes[fc_key].extend((fra, val)) 92 | 93 | 94 | # set transforms and store keyframes 95 | if bake_bones: 96 | for pb in armature.pose.bones: 97 | if only_selected and not pb.bone.select: 98 | continue 99 | 100 | euler_prev = None 101 | quat_prev = None 102 | keyframes = {} 103 | 104 | for (f, matrix) in bones_data: 105 | pb.matrix_basis = matrix[pb.name].copy() 106 | 107 | for arr_idx, value in enumerate(pb.location): 108 | store_keyframe(pb.name, "location", arr_idx, f, value) 109 | 110 | rotation_mode = pb.rotation_mode 111 | 112 | if rotation_mode == 'QUATERNION': 113 | if quat_prev is not None: 114 | quat = pb.rotation_quaternion.copy() 115 | quat.make_compatible(quat_prev) 116 | pb.rotation_quaternion = quat 117 | quat_prev = quat 118 | del quat 119 | else: 120 | quat_prev = pb.rotation_quaternion.copy() 121 | 122 | for arr_idx, value in enumerate(pb.rotation_quaternion): 123 | store_keyframe(pb.name, "rotation_quaternion", arr_idx, f, value) 124 | 125 | elif rotation_mode == 'AXIS_ANGLE': 126 | for arr_idx, value in enumerate(pb.rotation_axis_angle): 127 | store_keyframe(pb.name, "rotation_axis_angle", arr_idx, f, value) 128 | 129 | else: # euler, XYZ, ZXY etc 130 | if euler_prev is not None: 131 | euler = pb.rotation_euler.copy() 132 | euler.make_compatible(euler_prev) 133 | pb.rotation_euler = euler 134 | euler_prev = euler 135 | del euler 136 | else: 137 | euler_prev = pb.rotation_euler.copy() 138 | 139 | for arr_idx, value in enumerate(pb.rotation_euler): 140 | store_keyframe(pb.name, "rotation_euler", arr_idx, f, value) 141 | 142 | for arr_idx, value in enumerate(pb.scale): 143 | store_keyframe(pb.name, "scale", arr_idx, f, value) 144 | 145 | 146 | # Add keyframes 147 | for fc_key, key_values in keyframes.items(): 148 | data_path, index = fc_key 149 | if blender_version._float <= 403: 150 | fcurve = action.fcurves.find(data_path=data_path, index=index) 151 | else: 152 | fcurve = action.fcurve_ensure_for_datablock( 153 | armature, # Target data-block 154 | data_path, 155 | index=index 156 | ) 157 | if fcurve == None: 158 | fcurve = action.fcurves.new(data_path, index=index, action_group=pb.name) 159 | 160 | num_keys = len(key_values) // 2 161 | fcurve.keyframe_points.add(num_keys) 162 | fcurve.keyframe_points.foreach_set('co', key_values) 163 | 164 | if blender_version._float >= 290:# internal error when doing so with Blender 2.83, only for Blender 2.90 and higher 165 | linear_enum_value = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value 166 | fcurve.keyframe_points.foreach_set('interpolation', (linear_enum_value,) * num_keys) 167 | else: 168 | for kf in fcurve.keyframe_points: 169 | kf.interpolation = 'LINEAR' 170 | 171 | 172 | if bake_object: 173 | euler_prev = None 174 | quat_prev = None 175 | 176 | for (f, matrix) in obj_data: 177 | name = "Action Bake" 178 | armature.matrix_basis = matrix 179 | 180 | armature.keyframe_insert("location", index=-1, frame=f, group=name) 181 | 182 | rotation_mode = armature.rotation_mode 183 | if rotation_mode == 'QUATERNION': 184 | if quat_prev is not None: 185 | quat = armature.rotation_quaternion.copy() 186 | quat.make_compatible(quat_prev) 187 | armature.rotation_quaternion = quat 188 | quat_prev = quat 189 | del quat 190 | else: 191 | quat_prev = armature.rotation_quaternion.copy() 192 | armature.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name) 193 | elif rotation_mode == 'AXIS_ANGLE': 194 | armature.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name) 195 | else: # euler, XYZ, ZXY etc 196 | if euler_prev is not None: 197 | euler = armature.rotation_euler.copy() 198 | euler.make_compatible(euler_prev) 199 | armature.rotation_euler = euler 200 | euler_prev = euler 201 | del euler 202 | else: 203 | euler_prev = armature.rotation_euler.copy() 204 | armature.keyframe_insert("rotation_euler", index=-1, frame=f, group=name) 205 | 206 | armature.keyframe_insert("scale", index=-1, frame=f, group=name) 207 | 208 | 209 | # restore current frame 210 | scn.frame_set(current_frame) 211 | -------------------------------------------------------------------------------- /mixamo_rig_functions.py: -------------------------------------------------------------------------------- 1 | import bpy, os 2 | from mathutils import * 3 | from math import * 4 | from bpy.app.handlers import persistent 5 | from operator import itemgetter 6 | from .utils import * 7 | from .define import * 8 | 9 | fk_leg = [c_prefix+leg_rig_names["thigh_fk"], c_prefix+leg_rig_names["calf_fk"], c_prefix+leg_rig_names["foot_fk"], c_prefix+leg_rig_names["toes_fk"]] 10 | ik_leg = [leg_rig_names["thigh_ik"], leg_rig_names["calf_ik"], c_prefix+leg_rig_names["foot_ik"], c_prefix+leg_rig_names["pole_ik"], c_prefix+leg_rig_names["toes_ik"], c_prefix+leg_rig_names["foot_01"], c_prefix+leg_rig_names["foot_roll_cursor"], leg_rig_names["foot_snap"]] 11 | fk_arm = [c_prefix+arm_rig_names["arm_fk"], c_prefix+arm_rig_names["forearm_fk"], c_prefix+arm_rig_names["hand_fk"]] 12 | ik_arm = [arm_rig_names["arm_ik"], arm_rig_names["forearm_ik"], c_prefix+arm_rig_names["hand_ik"], c_prefix+arm_rig_names["pole_ik"]] 13 | 14 | ################## OPERATOR CLASSES ################### 15 | 16 | class MR_OT_arm_bake_fk_to_ik(bpy.types.Operator): 17 | """Snaps and bake an FK to an IK arm over a specified frame range""" 18 | 19 | bl_idname = "pose.mr_bake_arm_fk_to_ik" 20 | bl_label = "Snap an FK to IK arm over a specified frame range" 21 | bl_options = {'UNDO'} 22 | 23 | side : bpy.props.StringProperty(name="bone side") 24 | frame_start : bpy.props.IntProperty(name="Frame start", default=0) 25 | frame_end : bpy.props.IntProperty(name="Frame end", default=10) 26 | 27 | @classmethod 28 | def poll(cls, context): 29 | return (context.active_object != None and context.mode == 'POSE') 30 | 31 | def draw(self, context): 32 | layout = self.layout 33 | layout.prop(self, 'frame_start', text='Frame Start') 34 | layout.prop(self, 'frame_end', text='Frame End') 35 | 36 | def invoke(self, context, event): 37 | action = context.active_object.animation_data.action 38 | self.frame_start, self.frame_end = int(action.frame_range[0]), int(action.frame_range[1]) 39 | wm = context.window_manager 40 | return wm.invoke_props_dialog(self, width=400) 41 | 42 | def execute(self, context): 43 | use_global_undo = context.preferences.edit.use_global_undo 44 | context.preferences.edit.use_global_undo = False 45 | # save current autokey state 46 | auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto 47 | # set auto key to True 48 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = True 49 | 50 | try: 51 | bname = get_selected_pbone_name() 52 | self.side = get_bone_side(bname) 53 | bake_fk_to_ik_arm(self) 54 | finally: 55 | context.preferences.edit.use_global_undo = use_global_undo 56 | # restore autokey state 57 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state 58 | 59 | return {'FINISHED'} 60 | 61 | 62 | class MR_OT_arm_fk_to_ik(bpy.types.Operator): 63 | """Snaps an FK arm to an IK arm""" 64 | 65 | bl_idname = "pose.mr_arm_fk_to_ik_" 66 | bl_label = "Snap FK arm to IK" 67 | bl_options = {'UNDO'} 68 | 69 | side : bpy.props.StringProperty(name="bone side") 70 | 71 | @classmethod 72 | def poll(cls, context): 73 | return (context.active_object != None and context.mode == 'POSE') 74 | 75 | def execute(self, context): 76 | use_global_undo = context.preferences.edit.use_global_undo 77 | context.preferences.edit.use_global_undo = False 78 | 79 | try: 80 | bname = get_selected_pbone_name() 81 | self.side = get_bone_side(bname) 82 | 83 | fk_to_ik_arm(self) 84 | 85 | finally: 86 | context.preferences.edit.use_global_undo = use_global_undo 87 | 88 | return {'FINISHED'} 89 | 90 | 91 | class MR_OT_arm_bake_ik_to_fk(bpy.types.Operator): 92 | """Snaps and bake an IK to an FK arm over a specified frame range""" 93 | 94 | bl_idname = "pose.mr_bake_arm_ik_to_fk" 95 | bl_label = "Snap an IK to FK arm over a specified frame range" 96 | bl_options = {'UNDO'} 97 | 98 | side : bpy.props.StringProperty(name="bone side") 99 | frame_start : bpy.props.IntProperty(name="Frame start", default=0) 100 | frame_end : bpy.props.IntProperty(name="Frame end", default=10) 101 | 102 | @classmethod 103 | def poll(cls, context): 104 | return (context.active_object != None and context.mode == 'POSE') 105 | 106 | def draw(self, context): 107 | layout = self.layout 108 | layout.prop(self, 'frame_start', text='Frame Start') 109 | layout.prop(self, 'frame_end', text='Frame End') 110 | 111 | def invoke(self, context, event): 112 | wm = context.window_manager 113 | return wm.invoke_props_dialog(self, width=400) 114 | 115 | def execute(self, context): 116 | use_global_undo = context.preferences.edit.use_global_undo 117 | context.preferences.edit.use_global_undo = False 118 | # save current autokey state 119 | auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto 120 | # set auto key to True 121 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = True 122 | 123 | try: 124 | bname = get_selected_pbone_name() 125 | self.side = get_bone_side(bname) 126 | 127 | bake_ik_to_fk_arm(self) 128 | finally: 129 | context.preferences.edit.use_global_undo = use_global_undo 130 | # restore autokey state 131 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state 132 | 133 | return {'FINISHED'} 134 | 135 | 136 | class MR_OT_arm_ik_to_fk(bpy.types.Operator): 137 | """Snaps an IK arm to an FK arm""" 138 | 139 | bl_idname = "pose.mr_arm_ik_to_fk_" 140 | bl_label = "Snap IK arm to FK" 141 | bl_options = {'UNDO'} 142 | 143 | side : bpy.props.StringProperty(name="bone side") 144 | 145 | @classmethod 146 | def poll(cls, context): 147 | return (context.active_object != None and context.mode == 'POSE') 148 | 149 | def execute(self, context): 150 | use_global_undo = context.preferences.edit.use_global_undo 151 | context.preferences.edit.use_global_undo = False 152 | 153 | try: 154 | bname = get_selected_pbone_name() 155 | self.side = get_bone_side(bname) 156 | 157 | ik_to_fk_arm(self) 158 | 159 | finally: 160 | context.preferences.edit.use_global_undo = use_global_undo 161 | return {'FINISHED'} 162 | 163 | 164 | class MR_OT_switch_snap_anim(bpy.types.Operator): 165 | """Switch and snap IK-FK over multiple frames""" 166 | 167 | bl_idname = "pose.mr_switch_snap_anim" 168 | bl_label = "Switch and Snap IK FK anim" 169 | bl_options = {'UNDO'} 170 | 171 | rig = None 172 | side : bpy.props.StringProperty(name="bone side", default="") 173 | _side = "" 174 | prefix: bpy.props.StringProperty(name="", default="") 175 | type : bpy.props.StringProperty(name="type", default="") 176 | 177 | frame_start : bpy.props.IntProperty(name="Frame start", default=0) 178 | frame_end : bpy.props.IntProperty(name="Frame end", default=10) 179 | has_action = False 180 | 181 | 182 | @classmethod 183 | def poll(cls, context): 184 | return (context.active_object != None and context.mode == 'POSE') 185 | 186 | 187 | def draw(self, context): 188 | layout = self.layout 189 | if self.has_action: 190 | layout.prop(self, 'frame_start', text='Frame Start') 191 | layout.prop(self, 'frame_end', text='Frame End') 192 | else: 193 | layout.label(text="This rig is not animated!") 194 | 195 | 196 | def invoke(self, context, event): 197 | try: 198 | action = context.active_object.animation_data.action 199 | if action: 200 | self.has_action = True 201 | except: 202 | pass 203 | 204 | if self.has_action: 205 | self.frame_start, self.frame_end = int(action.frame_range[0]), int(action.frame_range[1]) 206 | 207 | wm = context.window_manager 208 | return wm.invoke_props_dialog(self, width=400) 209 | 210 | 211 | def execute(self, context): 212 | if self.has_action == False: 213 | return {'FINISHED'} 214 | 215 | try: 216 | scn = context.scene 217 | # save current autokey state 218 | auto_key_state = scn.tool_settings.use_keyframe_insert_auto 219 | # set auto key to True 220 | scn.tool_settings.use_keyframe_insert_auto = True 221 | # save current frame 222 | cur_frame = scn.frame_current 223 | 224 | self.rig = context.active_object 225 | bname = get_selected_pbone_name() 226 | self.side = get_bone_side(bname) 227 | self._side = '_'+self.side 228 | self.prefix = get_mixamo_prefix() 229 | 230 | 231 | if is_selected(fk_leg, bname) or is_selected(ik_leg, bname): 232 | self.type = "LEG" 233 | elif is_selected(fk_arm, bname) or is_selected(ik_arm, bname): 234 | self.type = "ARM" 235 | 236 | if self.type == "ARM": 237 | c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+self._side)#self.prefix+self.side+'Hand') 238 | if c_hand_ik['ik_fk_switch'] < 0.5: 239 | bake_fk_to_ik_arm(self) 240 | else: 241 | bake_ik_to_fk_arm(self) 242 | 243 | elif self.type == "LEG": 244 | c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+self._side)#get_pose_bone(self.prefix+self.side+'Foot') 245 | if c_foot_ik['ik_fk_switch'] < 0.5: 246 | bake_fk_to_ik_leg(self) 247 | else: 248 | print("Bake IK to FK leg") 249 | bake_ik_to_fk_leg(self) 250 | 251 | 252 | finally: 253 | # restore autokey state 254 | scn.tool_settings.use_keyframe_insert_auto = auto_key_state 255 | # restore frame 256 | scn.frame_set(cur_frame) 257 | 258 | return {'FINISHED'} 259 | 260 | 261 | class MR_OT_switch_snap(bpy.types.Operator): 262 | """Switch and snap IK-FK for the current frame""" 263 | 264 | bl_idname = "pose.mr_switch_snap" 265 | bl_label = "Switch and Snap IK FK" 266 | bl_options = {'UNDO'} 267 | 268 | rig = None 269 | side : bpy.props.StringProperty(name="bone side", default="") 270 | _side = "" 271 | prefix: bpy.props.StringProperty(name="", default="") 272 | type : bpy.props.StringProperty(name="type", default="") 273 | 274 | @classmethod 275 | def poll(cls, context): 276 | return (context.active_object != None and context.mode == 'POSE') 277 | 278 | def execute(self, context): 279 | use_global_undo = context.preferences.edit.use_global_undo 280 | context.preferences.edit.use_global_undo = False 281 | 282 | try: 283 | self.rig = context.active_object 284 | bname = get_selected_pbone_name() 285 | self.side = get_bone_side(bname) 286 | self._side = '_'+self.side 287 | self.prefix = get_mixamo_prefix() 288 | 289 | if is_selected(fk_leg, bname) or is_selected(ik_leg, bname): 290 | self.type = "LEG" 291 | elif is_selected(fk_arm, bname) or is_selected(ik_arm, bname): 292 | self.type = "ARM" 293 | 294 | 295 | if self.type == "ARM": 296 | #base_hand = get_pose_bone(self.prefix+self.side+'Hand') 297 | c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+self._side)#self.prefix+self.side+'Hand') 298 | if c_hand_ik['ik_fk_switch'] < 0.5: 299 | fk_to_ik_arm(self) 300 | else: 301 | ik_to_fk_arm(self) 302 | 303 | elif self.type == "LEG": 304 | #base_foot = get_pose_bone(self.prefix+self.side+'Foot') 305 | c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+self._side)#get_pose_bone(self.prefix+self.side+'Foot') 306 | if c_foot_ik['ik_fk_switch'] < 0.5: 307 | fk_to_ik_leg(self) 308 | else: 309 | ik_to_fk_leg(self) 310 | 311 | 312 | finally: 313 | context.preferences.edit.use_global_undo = use_global_undo 314 | 315 | return {'FINISHED'} 316 | 317 | 318 | class MR_OT_leg_bake_fk_to_ik(bpy.types.Operator): 319 | """Snaps and bake an FK leg to an IK leg over a specified frame range""" 320 | 321 | bl_idname = "pose.mr_bake_leg_fk_to_ik" 322 | bl_label = "Snap an FK to IK leg over a specified frame range" 323 | bl_options = {'UNDO'} 324 | 325 | side : bpy.props.StringProperty(name="bone side") 326 | _side = "" 327 | prefix = "" 328 | frame_start : bpy.props.IntProperty(name="Frame start", default=0) 329 | frame_end : bpy.props.IntProperty(name="Frame end", default=10) 330 | rig = None 331 | 332 | @classmethod 333 | def poll(cls, context): 334 | return (context.active_object != None and context.mode == 'POSE') 335 | 336 | def draw(self, context): 337 | layout = self.layout 338 | layout.prop(self, 'frame_start', text='Frame Start') 339 | layout.prop(self, 'frame_end', text='Frame End') 340 | 341 | def invoke(self, context, event): 342 | wm = context.window_manager 343 | return wm.invoke_props_dialog(self, width=400) 344 | 345 | def execute(self, context): 346 | use_global_undo = context.preferences.edit.use_global_undo 347 | context.preferences.edit.use_global_undo = False 348 | # save current autokey state 349 | auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto 350 | # set auto key to True 351 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = True 352 | 353 | try: 354 | self.rig = context.active_object 355 | bname = get_selected_pbone_name() 356 | self.side = get_bone_side(bname) 357 | self._side = '_'+self.side 358 | self.prefix = get_mixamo_prefix() 359 | 360 | bake_fk_to_ik_leg(self) 361 | finally: 362 | context.preferences.edit.use_global_undo = use_global_undo 363 | # restore autokey state 364 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state 365 | 366 | return {'FINISHED'} 367 | 368 | 369 | class MR_OT_leg_fk_to_ik(bpy.types.Operator): 370 | """Snaps an FK leg to an IK leg""" 371 | 372 | bl_idname = "pose.mr_leg_fk_to_ik_" 373 | bl_label = "Snap FK leg to IK" 374 | bl_options = {'UNDO'} 375 | 376 | side : bpy.props.StringProperty(name="bone side") 377 | rig = None 378 | _side = "" 379 | prefix = "" 380 | 381 | @classmethod 382 | def poll(cls, context): 383 | return (context.active_object != None and context.mode == 'POSE') 384 | 385 | def execute(self, context): 386 | use_global_undo = context.preferences.edit.use_global_undo 387 | context.preferences.edit.use_global_undo = False 388 | 389 | try: 390 | self.rig = context.active_object 391 | bname = get_selected_pbone_name() 392 | self.side = get_bone_side(bname) 393 | self._side = '_'+self.side 394 | self.prefix = get_mixamo_prefix() 395 | 396 | fk_to_ik_leg(self) 397 | 398 | finally: 399 | context.preferences.edit.use_global_undo = use_global_undo 400 | return {'FINISHED'} 401 | 402 | 403 | class MR_OT_leg_bake_ik_to_fk(bpy.types.Operator): 404 | """Snaps and bake an IK leg to an FK leg over a specified frame range""" 405 | 406 | bl_idname = "pose.mr_bake_leg_ik_to_fk" 407 | bl_label = "Snap an IK to FK leg over a specified frame range" 408 | bl_options = {'UNDO'} 409 | 410 | side : bpy.props.StringProperty(name="bone side") 411 | frame_start : bpy.props.IntProperty(name="Frame start", default=0) 412 | frame_end : bpy.props.IntProperty(name="Frame end", default=10) 413 | rig = None 414 | _side = "" 415 | prefix = "" 416 | 417 | 418 | @classmethod 419 | def poll(cls, context): 420 | return (context.active_object != None and context.mode == 'POSE') 421 | 422 | def draw(self, context): 423 | layout = self.layout 424 | layout.prop(self, 'frame_start', text='Frame Start') 425 | layout.prop(self, 'frame_end', text='Frame End') 426 | 427 | def invoke(self, context, event): 428 | wm = context.window_manager 429 | return wm.invoke_props_dialog(self, width=400) 430 | 431 | def execute(self, context): 432 | use_global_undo = context.preferences.edit.use_global_undo 433 | context.preferences.edit.use_global_undo = False 434 | # save current autokey state 435 | auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto 436 | # set auto key to True 437 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = True 438 | 439 | try: 440 | self.rig = context.active_object 441 | bname = get_selected_pbone_name() 442 | self.side = get_bone_side(bname) 443 | self._side = '_'+self.side 444 | self.prefix = get_mixamo_prefix() 445 | 446 | bake_ik_to_fk_leg(self) 447 | 448 | finally: 449 | context.preferences.edit.use_global_undo = use_global_undo 450 | # restore autokey state 451 | bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state 452 | 453 | return {'FINISHED'} 454 | 455 | 456 | class MR_OT_leg_ik_to_fk(bpy.types.Operator): 457 | """Snaps an IK leg to an FK leg""" 458 | 459 | bl_idname = "pose.mr_leg_ik_to_fk_" 460 | bl_label = "Snap IK leg to FK" 461 | bl_options = {'UNDO'} 462 | 463 | side : bpy.props.StringProperty(name="bone side") 464 | rig = None 465 | _side = "" 466 | prefix = "" 467 | 468 | @classmethod 469 | def poll(cls, context): 470 | return (context.active_object != None and context.mode == 'POSE') 471 | 472 | def execute(self, context): 473 | use_global_undo = context.preferences.edit.use_global_undo 474 | context.preferences.edit.use_global_undo = False 475 | try: 476 | self.rig = context.active_object 477 | bname = get_selected_pbone_name() 478 | self.side = get_bone_side(bname) 479 | self._side = '_'+self.side 480 | self.prefix = get_mixamo_prefix() 481 | 482 | ik_to_fk_leg(self) 483 | finally: 484 | context.preferences.edit.use_global_undo = use_global_undo 485 | 486 | return {'FINISHED'} 487 | 488 | 489 | 490 | ################## FUNCTIONS ################## 491 | 492 | def set_pose_rotation(pose_bone, mat): 493 | q = mat.to_quaternion() 494 | 495 | if pose_bone.rotation_mode == 'QUATERNION': 496 | pose_bone.rotation_quaternion = q 497 | elif pose_bone.rotation_mode == 'AXIS_ANGLE': 498 | pose_bone.rotation_axis_angle[0] = q.angle 499 | pose_bone.rotation_axis_angle[1] = q.axis[0] 500 | pose_bone.rotation_axis_angle[2] = q.axis[1] 501 | pose_bone.rotation_axis_angle[3] = q.axis[2] 502 | else: 503 | pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode) 504 | 505 | 506 | def snap_pos(pose_bone, target_bone): 507 | # Snap a bone to another bone. Supports child of constraints and parent. 508 | 509 | # if the pose_bone has direct parent 510 | if pose_bone.parent: 511 | # apply double time because of dependecy lag 512 | pose_bone.matrix = target_bone.matrix 513 | update_transform() 514 | # second apply 515 | pose_bone.matrix = target_bone.matrix 516 | else: 517 | # is there a child of constraint attached? 518 | child_of_cns = None 519 | if len(pose_bone.constraints) > 0: 520 | all_child_of_cns = [i for i in pose_bone.constraints if i.type == "CHILD_OF" and i.influence == 1.0 and i.mute == False and i.target] 521 | if len(all_child_of_cns) > 0: 522 | child_of_cns = all_child_of_cns[0]# in case of multiple child of constraints enabled, use only the first for now 523 | 524 | if child_of_cns != None: 525 | if child_of_cns.subtarget != "" and get_pose_bone(child_of_cns.subtarget): 526 | # apply double time because of dependecy lag 527 | pose_bone.matrix = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ target_bone.matrix 528 | update_transform() 529 | pose_bone.matrix = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ target_bone.matrix 530 | else: 531 | pose_bone.matrix = target_bone.matrix 532 | 533 | else: 534 | pose_bone.matrix = target_bone.matrix 535 | 536 | 537 | def snap_pos_matrix(pose_bone, target_bone_matrix): 538 | # Snap a bone to another bone. Supports child of constraints and parent. 539 | 540 | # if the pose_bone has direct parent 541 | if pose_bone.parent: 542 | pose_bone.matrix = target_bone_matrix.copy() 543 | update_transform() 544 | else: 545 | # is there a child of constraint attached? 546 | child_of_cns = None 547 | if len(pose_bone.constraints) > 0: 548 | all_child_of_cns = [i for i in pose_bone.constraints if i.type == "CHILD_OF" and i.influence == 1.0 and i.mute == False and i.target] 549 | if len(all_child_of_cns) > 0: 550 | child_of_cns = all_child_of_cns[0]# in case of multiple child of constraints enabled, use only the first for now 551 | 552 | if child_of_cns != None: 553 | if child_of_cns.subtarget != "" and get_pose_bone(child_of_cns.subtarget): 554 | pose_bone.matrix = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ target_bone_matrix 555 | update_transform() 556 | else: 557 | pose_bone.matrix = target_bone_matrix.copy() 558 | 559 | else: 560 | pose_bone.matrix = target_bone_matrix.copy() 561 | 562 | 563 | def snap_rot(pose_bone, target_bone): 564 | method = 1 565 | 566 | if method == 1: 567 | mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) 568 | set_pose_rotation(pose_bone, mat) 569 | #bpy.ops.object.mode_set(mode='OBJECT') 570 | #bpy.ops.object.mode_set(mode='POSE') 571 | bpy.context.view_layer.update() 572 | elif method == 2: 573 | loc, scale = pose_bone.location.copy(), pose_bone.scale.copy() 574 | pose_bone.matrix = target_bone.matrix 575 | pose_bone.location, pose_bone.scale = loc, scale 576 | bpy.context.view_layer.update() 577 | 578 | 579 | def bake_fk_to_ik_arm(self): 580 | for f in range(self.frame_start, self.frame_end +1): 581 | bpy.context.scene.frame_set(f) 582 | print("baking frame", f) 583 | fk_to_ik_arm(self) 584 | 585 | 586 | def fk_to_ik_arm(self): 587 | rig = self.rig 588 | side = self.side 589 | _side = self._side 590 | prefix = self.prefix 591 | 592 | arm_fk = rig.pose.bones[fk_arm[0] + _side] 593 | forearm_fk = rig.pose.bones[fk_arm[1] + _side] 594 | hand_fk = rig.pose.bones[fk_arm[2] + _side] 595 | 596 | arm_ik = rig.pose.bones[ik_arm[0] + _side] 597 | forearm_ik = rig.pose.bones[ik_arm[1] + _side] 598 | hand_ik = rig.pose.bones[ik_arm[2] + _side] 599 | pole = rig.pose.bones[ik_arm[3] + _side] 600 | 601 | #Snap rot 602 | snap_rot(arm_fk, arm_ik) 603 | snap_rot(forearm_fk, forearm_ik) 604 | snap_rot(hand_fk, hand_ik) 605 | 606 | #Snap scale 607 | hand_fk.scale =hand_ik.scale 608 | 609 | #rot debug 610 | forearm_fk.rotation_euler[0]=0 611 | forearm_fk.rotation_euler[1]=0 612 | 613 | #switch 614 | #base_hand = get_pose_bone(prefix+side+'Hand') 615 | c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+_side) 616 | c_hand_ik['ik_fk_switch'] = 1.0 617 | 618 | #udpate view 619 | bpy.context.view_layer.update() 620 | 621 | #insert key if autokey enable 622 | if bpy.context.scene.tool_settings.use_keyframe_insert_auto: 623 | #fk chain 624 | c_hand_ik.keyframe_insert(data_path='["ik_fk_switch"]') 625 | hand_fk.keyframe_insert(data_path="scale") 626 | hand_fk.keyframe_insert(data_path="rotation_euler") 627 | arm_fk.keyframe_insert(data_path="rotation_euler") 628 | forearm_fk.keyframe_insert(data_path="rotation_euler") 629 | 630 | #ik chain 631 | hand_ik.keyframe_insert(data_path="location") 632 | hand_ik.keyframe_insert(data_path="rotation_euler") 633 | hand_ik.keyframe_insert(data_path="scale") 634 | pole.keyframe_insert(data_path="location") 635 | 636 | # change FK to IK hand selection, if selected 637 | if hand_ik.bone.select: 638 | hand_fk.bone.select = True 639 | hand_ik.bone.select = False 640 | 641 | 642 | def bake_ik_to_fk_arm(self): 643 | for f in range(self.frame_start, self.frame_end +1): 644 | bpy.context.scene.frame_set(f) 645 | print("baking frame", f) 646 | 647 | ik_to_fk_arm(self) 648 | 649 | 650 | def ik_to_fk_arm(self): 651 | rig = self.rig 652 | side = self.side 653 | _side = self._side 654 | prefix = self.prefix 655 | 656 | arm_fk = rig.pose.bones[fk_arm[0] + _side] 657 | forearm_fk = rig.pose.bones[fk_arm[1] + _side] 658 | hand_fk = rig.pose.bones[fk_arm[2] + _side] 659 | 660 | arm_ik = rig.pose.bones[ik_arm[0] + _side] 661 | forearm_ik = rig.pose.bones[ik_arm[1] + _side] 662 | hand_ik = rig.pose.bones[ik_arm[2] + _side] 663 | pole_ik = rig.pose.bones[ik_arm[3] + _side] 664 | 665 | # Snap 666 | # constraint support 667 | constraint = None 668 | bparent_name = "" 669 | parent_type = "" 670 | valid_constraint = True 671 | 672 | # Snap Hand 673 | if len(hand_ik.constraints) > 0: 674 | for c in hand_ik.constraints: 675 | if not c.mute and c.influence > 0.5 and c.type == 'CHILD_OF': 676 | if c.target: 677 | #if bone 678 | if c.target.type == 'ARMATURE': 679 | bparent_name = c.subtarget 680 | parent_type = "bone" 681 | constraint = c 682 | #if object 683 | else: 684 | bparent_name = c.target.name 685 | parent_type = "object" 686 | constraint = c 687 | 688 | 689 | if constraint != None: 690 | if parent_type == "bone": 691 | if bparent_name == "": 692 | valid_constraint = False 693 | 694 | if constraint and valid_constraint: 695 | if parent_type == "bone": 696 | bone_parent = get_pose_bone(bparent_name) 697 | hand_ik.matrix = bone_parent.matrix_channel.inverted() @ hand_fk.matrix 698 | if parent_type == "object": 699 | bone_parent = bpy.data.objects[bparent_name] 700 | obj_par = bpy.data.objects[bparent_name] 701 | hand_ik.matrix = constraint.inverse_matrix.inverted() @ obj_par.matrix_world.inverted() @ hand_fk.matrix 702 | else: 703 | hand_ik.matrix = hand_fk.matrix 704 | 705 | # Snap Pole 706 | _axis = forearm_fk.x_axis if side == "Left" else -forearm_fk.x_axis 707 | pole_pos = get_ik_pole_pos(arm_fk, forearm_fk, method=2, axis=_axis) 708 | pole_mat = Matrix.Translation(pole_pos) 709 | snap_pos_matrix(pole_ik, pole_mat) 710 | 711 | # Switch 712 | c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+_side) 713 | #base_hand = get_pose_bone(prefix+side+'Hand') 714 | c_hand_ik['ik_fk_switch'] = 0.0 715 | 716 | # update 717 | update_transform() 718 | 719 | #insert key if autokey enable 720 | if bpy.context.scene.tool_settings.use_keyframe_insert_auto: 721 | #ik chain 722 | c_hand_ik.keyframe_insert(data_path='["ik_fk_switch"]') 723 | hand_ik.keyframe_insert(data_path="location") 724 | hand_ik.keyframe_insert(data_path="rotation_euler") 725 | hand_ik.keyframe_insert(data_path="scale") 726 | pole_ik.keyframe_insert(data_path="location") 727 | 728 | #fk chain 729 | hand_fk.keyframe_insert(data_path="location") 730 | hand_fk.keyframe_insert(data_path="rotation_euler") 731 | hand_fk.keyframe_insert(data_path="scale") 732 | arm_fk.keyframe_insert(data_path="rotation_euler") 733 | forearm_fk.keyframe_insert(data_path="rotation_euler") 734 | 735 | # change FK to IK hand selection, if selected 736 | if hand_fk.bone.select: 737 | hand_fk.bone.select = False 738 | hand_ik.bone.select = True 739 | 740 | 741 | def bake_fk_to_ik_leg(self): 742 | for f in range(self.frame_start, self.frame_end +1): 743 | bpy.context.scene.frame_set(f) 744 | print("baking frame", f) 745 | 746 | fk_to_ik_leg(self) 747 | 748 | 749 | def fk_to_ik_leg(self): 750 | rig = self.rig 751 | side = self.side 752 | _side = self._side 753 | prefix = self.prefix 754 | 755 | thigh_fk = rig.pose.bones[fk_leg[0] + _side] 756 | leg_fk = rig.pose.bones[fk_leg[1] + _side] 757 | foot_fk = rig.pose.bones[fk_leg[2] + _side] 758 | toes_fk = rig.pose.bones[fk_leg[3] + _side] 759 | 760 | thigh_ik = rig.pose.bones[ik_leg[0] + _side] 761 | leg_ik = rig.pose.bones[ik_leg[1] + _side] 762 | foot_ik = rig.pose.bones[ik_leg[2] + _side] 763 | pole_ik = rig.pose.bones[ik_leg[3] + _side] 764 | toes_ik = rig.pose.bones[ik_leg[4] + _side] 765 | foot_01_ik = rig.pose.bones[ik_leg[5] + _side] 766 | foot_roll_ik = rig.pose.bones[ik_leg[6] + _side] 767 | foot_snap_ik = rig.pose.bones[ik_leg[7] + _side] 768 | 769 | # Thigh snap 770 | snap_rot(thigh_fk, thigh_ik) 771 | #thigh_fk.matrix = thigh_ik.matrix.copy() 772 | 773 | # Leg snap 774 | snap_rot(leg_fk, leg_ik) 775 | 776 | # Foot snap 777 | snap_rot(foot_fk, foot_snap_ik) 778 | foot_fk.scale =foot_ik.scale 779 | 780 | # Toes snap 781 | snap_rot(toes_fk, toes_ik) 782 | toes_fk.scale = toes_ik.scale 783 | 784 | # rotation fix 785 | leg_fk.rotation_euler[1] = 0.0 786 | leg_fk.rotation_euler[2] = 0.0 787 | 788 | # switch prop value 789 | c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+_side) 790 | #base_foot = get_pose_bone(prefix+side+'Foot') 791 | c_foot_ik['ik_fk_switch'] = 1.0 792 | 793 | # udpate hack 794 | bpy.context.view_layer.update() 795 | 796 | #if bpy.context.scene.frame_current == 2: 797 | # print(br) 798 | 799 | #insert key if autokey enable 800 | if bpy.context.scene.tool_settings.use_keyframe_insert_auto: 801 | #fk chain 802 | c_foot_ik.keyframe_insert(data_path='["ik_fk_switch"]') 803 | thigh_fk.keyframe_insert(data_path="rotation_euler") 804 | leg_fk.keyframe_insert(data_path="rotation_euler") 805 | foot_fk.keyframe_insert(data_path="rotation_euler") 806 | foot_fk.keyframe_insert(data_path="scale") 807 | toes_fk.keyframe_insert(data_path="rotation_euler") 808 | toes_fk.keyframe_insert(data_path="scale") 809 | 810 | #ik chain 811 | foot_ik.keyframe_insert(data_path="location") 812 | foot_ik.keyframe_insert(data_path="rotation_euler") 813 | foot_ik.keyframe_insert(data_path="scale") 814 | foot_01_ik.keyframe_insert(data_path="rotation_euler") 815 | foot_roll_ik.keyframe_insert(data_path="location") 816 | toes_ik.keyframe_insert(data_path="rotation_euler") 817 | toes_ik.keyframe_insert(data_path="scale") 818 | pole_ik.keyframe_insert(data_path="location") 819 | 820 | # change IK to FK foot selection, if selected 821 | if foot_ik.bone.select: 822 | foot_fk.bone.select = True 823 | foot_ik.bone.select = False 824 | 825 | 826 | def bake_ik_to_fk_leg(self): 827 | for f in range(self.frame_start, self.frame_end +1): 828 | bpy.context.scene.frame_set(f) 829 | print("baking frame", f) 830 | 831 | ik_to_fk_leg(self) 832 | 833 | 834 | def ik_to_fk_leg(self): 835 | rig = self.rig 836 | side = self.side 837 | _side = self._side 838 | prefix = self.prefix 839 | 840 | thigh_fk = rig.pose.bones[fk_leg[0] + _side] 841 | leg_fk = rig.pose.bones[fk_leg[1] + _side] 842 | foot_fk = rig.pose.bones[fk_leg[2] + _side] 843 | toes_fk = rig.pose.bones[fk_leg[3] + _side] 844 | 845 | thigh_ik = rig.pose.bones[ik_leg[0] + _side] 846 | calf_ik = rig.pose.bones[ik_leg[1] + _side] 847 | foot_ik = rig.pose.bones[ik_leg[2] + _side] 848 | pole_ik = rig.pose.bones[ik_leg[3] + _side] 849 | toes_ik = rig.pose.bones[ik_leg[4] + _side] 850 | foot_01_ik = rig.pose.bones[ik_leg[5] + _side] 851 | foot_roll_ik = rig.pose.bones[ik_leg[6] + _side] 852 | 853 | 854 | # reset IK foot_01 and foot_roll 855 | foot_01_ik.rotation_euler = [0,0,0] 856 | foot_roll_ik.location[0] = 0.0 857 | foot_roll_ik.location[2] = 0.0 858 | 859 | # Snap toes 860 | toes_ik.rotation_euler = toes_fk.rotation_euler.copy() 861 | toes_ik.scale = toes_fk.scale.copy() 862 | 863 | # Child Of constraint or parent cases 864 | constraint = None 865 | bparent_name = "" 866 | parent_type = "" 867 | valid_constraint = True 868 | 869 | if len(foot_ik.constraints) > 0: 870 | for c in foot_ik.constraints: 871 | if not c.mute and c.influence > 0.5 and c.type == 'CHILD_OF': 872 | if c.target: 873 | #if bone 874 | if c.target.type == 'ARMATURE': 875 | bparent_name = c.subtarget 876 | parent_type = "bone" 877 | constraint = c 878 | #if object 879 | else: 880 | bparent_name = c.target.name 881 | parent_type = "object" 882 | constraint = c 883 | 884 | if constraint != None: 885 | if parent_type == "bone": 886 | if bparent_name == "": 887 | valid_constraint = False 888 | 889 | # Snap Foot 890 | if constraint and valid_constraint: 891 | if parent_type == "bone": 892 | bone_parent = rig.pose.bones[bparent_name] 893 | foot_ik.matrix = bone_parent.matrix_channel.inverted() @ foot_fk.matrix 894 | if parent_type == "object": 895 | ob = bpy.data.objects[bparent_name] 896 | foot_ik.matrix = constraint.inverse_matrix.inverted() @ ob.matrix_world.inverted() @ foot_fk.matrix 897 | 898 | else: 899 | foot_ik.matrix = foot_fk.matrix 900 | 901 | # update 902 | bpy.context.view_layer.update() 903 | 904 | # Snap Pole 905 | pole_pos = get_ik_pole_pos(thigh_fk, leg_fk, method=2, axis=leg_fk.z_axis) 906 | pole_mat = Matrix.Translation(pole_pos) 907 | snap_pos_matrix(pole_ik, pole_mat) 908 | 909 | update_transform() 910 | 911 | # switch 912 | c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+_side) 913 | #base_foot = get_pose_bone(prefix+side+'Foot') 914 | c_foot_ik['ik_fk_switch'] = 0.0 915 | 916 | update_transform() 917 | 918 | #insert key if autokey enable 919 | if bpy.context.scene.tool_settings.use_keyframe_insert_auto: 920 | #ik chain 921 | c_foot_ik.keyframe_insert(data_path='["ik_fk_switch"]') 922 | foot_01_ik.keyframe_insert(data_path="rotation_euler") 923 | foot_roll_ik.keyframe_insert(data_path="location") 924 | foot_ik.keyframe_insert(data_path="location") 925 | foot_ik.keyframe_insert(data_path="rotation_euler") 926 | foot_ik.keyframe_insert(data_path="scale") 927 | toes_ik.keyframe_insert(data_path="rotation_euler") 928 | toes_ik.keyframe_insert(data_path="scale") 929 | pole_ik.keyframe_insert(data_path="location") 930 | 931 | #fk chain 932 | thigh_fk.keyframe_insert(data_path="rotation_euler") 933 | leg_fk.keyframe_insert(data_path="rotation_euler") 934 | foot_fk.keyframe_insert(data_path="rotation_euler") 935 | foot_fk.keyframe_insert(data_path="scale") 936 | toes_fk.keyframe_insert(data_path="rotation_euler") 937 | toes_fk.keyframe_insert(data_path="scale") 938 | 939 | # change IK to FK foot selection, if selected 940 | if foot_fk.bone.select: 941 | foot_fk.bone.select = False 942 | foot_ik.bone.select = True 943 | 944 | 945 | def get_active_child_of_cns(bone): 946 | constraint = None 947 | bparent_name = "" 948 | parent_type = "" 949 | valid_constraint = True 950 | 951 | if len(bone.constraints) > 0: 952 | for c in bone.constraints: 953 | if not c.mute and c.influence > 0.5 and c.type == 'CHILD_OF': 954 | if c.target: 955 | if c.target.type == 'ARMATURE':# bone 956 | bparent_name = c.subtarget 957 | parent_type = "bone" 958 | constraint = c 959 | else:# object 960 | bparent_name = c.target.name 961 | parent_type = "object" 962 | constraint = c 963 | 964 | if constraint: 965 | if parent_type == "bone": 966 | if bparent_name == "": 967 | valid_constraint = False 968 | 969 | return constraint, bparent_name, parent_type, valid_constraint 970 | 971 | 972 | def is_selected(names, selected_bone_name, startswith=False): 973 | side = "" 974 | if get_bone_side(selected_bone_name) != None: 975 | side = get_bone_side(selected_bone_name) 976 | 977 | _side = "_"+side 978 | 979 | if startswith == False: 980 | if type(names) == list: 981 | for name in names: 982 | if not "." in name[-2:]: 983 | if name + _side == selected_bone_name: 984 | return True 985 | else: 986 | if name[-2:] == ".x": 987 | if name[:-2] + _side == selected_bone_name: 988 | return True 989 | elif names == selected_bone_name: 990 | return True 991 | else:#startswith 992 | if type(names) == list: 993 | for name in names: 994 | if selected_bone_name.startswith(name): 995 | return True 996 | else: 997 | return selected_bone_name.startswith(names) 998 | return False 999 | 1000 | 1001 | def is_selected_prop(pbone, prop_name): 1002 | if pbone.bone.keys(): 1003 | if prop_name in pbone.bone.keys(): 1004 | return True 1005 | 1006 | 1007 | ################## User Interface ################## 1008 | class MR_PT_rig_ui(bpy.types.Panel): 1009 | bl_space_type = 'VIEW_3D' 1010 | bl_region_type = 'UI' 1011 | bl_category = "Tool" 1012 | bl_label = "Mixamo Rig Settings" 1013 | bl_idname = "MR_PT_rig_ui" 1014 | 1015 | @classmethod 1016 | def poll(self, context): 1017 | if context.mode != 'POSE': 1018 | return False 1019 | return True 1020 | 1021 | 1022 | def draw(self, context): 1023 | layout = self.layout 1024 | scn = bpy.context.scene 1025 | rig = context.active_object 1026 | 1027 | if rig == None: 1028 | return 1029 | if rig.type != "ARMATURE": 1030 | return 1031 | 1032 | # check if a Mixamo ctrl rig is selected 1033 | if len(rig.data.keys()): 1034 | if not 'mr_control_rig' in rig.data.keys(): 1035 | return 1036 | else: 1037 | return 1038 | 1039 | 1040 | pose_bones = rig.pose.bones 1041 | 1042 | try: 1043 | active_bone = context.selected_pose_bones[0]#context.active_pose_bone 1044 | selected_bone_name = active_bone.name 1045 | except: 1046 | return 1047 | 1048 | side = get_bone_side(selected_bone_name) 1049 | prefix = get_mixamo_prefix() 1050 | 1051 | # Leg 1052 | if (is_selected(fk_leg, selected_bone_name) or is_selected(ik_leg, selected_bone_name)): 1053 | # IK-FK Switch 1054 | col = layout.column(align=True) 1055 | #foot_base = get_pose_bone(prefix+side.title()+'Foot') 1056 | c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+'_'+side.title()) 1057 | col.prop(c_foot_ik, '["ik_fk_switch"]', text="IK-FK Switch", slider=True) 1058 | col.operator(MR_OT_switch_snap.bl_idname, text="Snap Frame IK/FK") 1059 | col.operator(MR_OT_switch_snap_anim.bl_idname, text="Snap Anim IK-FK") 1060 | 1061 | 1062 | # Arm 1063 | if is_selected(fk_arm, selected_bone_name) or is_selected(ik_arm, selected_bone_name): 1064 | # IK-FK Switch 1065 | col = layout.column(align=True) 1066 | #hand_base = get_pose_bone(prefix+side.title()+'Hand') 1067 | c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+'_'+side.title()) 1068 | col.prop(c_hand_ik, '["ik_fk_switch"]', text="IK-FK Switch", slider=True) 1069 | col.operator(MR_OT_switch_snap.bl_idname, text="Snap Frame IK-FK") 1070 | col.operator(MR_OT_switch_snap_anim.bl_idname, text="Snap Anim IK-FK") 1071 | 1072 | 1073 | 1074 | ################## REGISTER ################## 1075 | classes = ( 1076 | MR_OT_arm_bake_fk_to_ik, 1077 | MR_OT_arm_fk_to_ik, 1078 | MR_OT_arm_bake_ik_to_fk, 1079 | MR_OT_arm_ik_to_fk, 1080 | MR_OT_switch_snap, 1081 | MR_OT_leg_fk_to_ik, 1082 | MR_OT_leg_bake_fk_to_ik, 1083 | MR_OT_leg_ik_to_fk, 1084 | MR_OT_leg_bake_ik_to_fk, 1085 | MR_PT_rig_ui, 1086 | MR_OT_switch_snap_anim) 1087 | 1088 | 1089 | def update_mixamo_tab(): 1090 | try: 1091 | bpy.utils.unregister_class(MR_PT_rig_ui) 1092 | except: 1093 | pass 1094 | 1095 | MR_PT_rig_ui.bl_category = bpy.context.preferences.addons[__package__].preferences.mixamo_tab_name 1096 | bpy.utils.register_class(MR_PT_rig_ui) 1097 | 1098 | 1099 | def register(): 1100 | from bpy.utils import register_class 1101 | 1102 | for cls in classes: 1103 | register_class(cls) 1104 | 1105 | update_mixamo_tab() 1106 | 1107 | bpy.types.Scene.mix_show_ik_fk_advanced = bpy.props.BoolProperty(name="Show IK-FK operators", description="Show IK-FK manual operators", default=False) 1108 | 1109 | 1110 | def unregister(): 1111 | from bpy.utils import unregister_class 1112 | 1113 | for cls in classes: 1114 | unregister_class(cls) 1115 | 1116 | del bpy.types.Scene.mix_show_ik_fk_advanced 1117 | -------------------------------------------------------------------------------- /mixamo_rig.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from math import * 3 | from mathutils import * 4 | from bpy.types import Panel 5 | from .utils import * 6 | from .define import * 7 | 8 | 9 | # OPERATOR CLASSES 10 | ################## 11 | class MR_OT_update(bpy.types.Operator): 12 | """Update old control rig to Blender 3.0""" 13 | 14 | bl_idname = "mr.update" 15 | bl_label = "update" 16 | bl_options = {'UNDO'} 17 | 18 | @classmethod 19 | def poll(cls, context): 20 | if context.active_object: 21 | if context.active_object.type == "ARMATURE": 22 | return "mr_control_rig" in context.active_object.data.keys() 23 | 24 | 25 | 26 | def execute(self, context): 27 | try: 28 | _update(self) 29 | finally: 30 | pass 31 | 32 | return {'FINISHED'} 33 | 34 | 35 | 36 | class MR_OT_exportGLTF(bpy.types.Operator): 37 | """Export to GLTF format""" 38 | 39 | bl_idname = "mr.export_gltf" 40 | bl_label = "export_gltf" 41 | bl_options = {'UNDO'} 42 | 43 | @classmethod 44 | def poll(cls, context): 45 | if context.active_object: 46 | if context.active_object.type == "ARMATURE": 47 | return True 48 | 49 | 50 | def execute(self, context): 51 | try: 52 | bpy.ops.export_scene.gltf() 53 | finally: 54 | pass 55 | 56 | return {'FINISHED'} 57 | 58 | 59 | class MR_OT_apply_shape(bpy.types.Operator): 60 | """Apply the selected shape""" 61 | 62 | bl_idname = "mr.apply_shape" 63 | bl_label = "apply_shape" 64 | bl_options = {'UNDO'} 65 | 66 | @classmethod 67 | def poll(cls, context): 68 | if context.active_object: 69 | if context.mode == 'EDIT_MESH': 70 | if 'cs_user' in context.active_object.name: 71 | return True 72 | 73 | def execute(self, context): 74 | use_global_undo = context.preferences.edit.use_global_undo 75 | context.preferences.edit.use_global_undo = False 76 | 77 | try: 78 | _apply_shape() 79 | finally: 80 | context.preferences.edit.use_global_undo = use_global_undo 81 | return {'FINISHED'} 82 | 83 | 84 | class MR_OT_edit_custom_shape(bpy.types.Operator): 85 | """Edit the selected bone shape""" 86 | 87 | bl_idname = "mr.edit_custom_shape" 88 | bl_label = "edit_custom_shape" 89 | bl_options = {'UNDO'} 90 | 91 | @classmethod 92 | def poll(cls, context): 93 | if context.mode == 'POSE': 94 | if bpy.context.active_pose_bone: 95 | return True 96 | 97 | def execute(self, context): 98 | try: 99 | cs = bpy.context.active_pose_bone.custom_shape 100 | if cs: 101 | _edit_custom_shape() 102 | else: 103 | self.report({"ERROR"}, "No custom shapes set for this bone.") 104 | 105 | finally: 106 | pass 107 | 108 | return {'FINISHED'} 109 | 110 | 111 | class MR_OT_make_rig(bpy.types.Operator): 112 | """Generate a control rig from the selected Mixamo skeleton""" 113 | 114 | bl_idname = "mr.make_rig" 115 | bl_label = "Create control rig from selected armature" 116 | bl_options = {'UNDO'} 117 | 118 | bake_anim: bpy.props.BoolProperty(name="Bake Anim", description="Bake animation to the control bones", default=True) 119 | ik_arms: bpy.props.BoolProperty(name="IK Hands", description="Use IK for arm bones, otherwise use FK (can be toggled later using the rig properties)", default=True) 120 | ik_legs: bpy.props.BoolProperty(name="IK Legs", description="Use IK for leg bones, otherwise use FK (can be toggled later using the rig properties)", default=True) 121 | animated_armature = None 122 | 123 | @classmethod 124 | def poll(cls, context): 125 | if context.active_object: 126 | if context.active_object.type == "ARMATURE": 127 | if "mr_control_rig" not in context.active_object.data.keys(): 128 | return True 129 | return False 130 | 131 | 132 | def invoke(self, context, event): 133 | wm = context.window_manager 134 | return wm.invoke_props_dialog(self, width=450) 135 | 136 | 137 | def draw(self, context): 138 | layout = self.layout 139 | layout.prop(self, 'bake_anim', text="Apply Animation") 140 | layout.prop(self, 'ik_arms', text="IK Arms") 141 | layout.prop(self, 'ik_legs', text="IK Legs") 142 | 143 | 144 | def execute(self, context): 145 | debug = False 146 | # ~ layer_select = [] 147 | 148 | try: 149 | # only select the armature 150 | arm = get_object(context.active_object.name) 151 | bpy.ops.object.mode_set(mode='OBJECT') 152 | bpy.ops.object.select_all(action='DESELECT') 153 | set_active_object(arm.name) 154 | 155 | # enable all armature layers 156 | layer_select = enable_all_armature_layers() 157 | 158 | # animation import: initial steps 159 | if self.bake_anim: 160 | if "mr_control_rig" not in arm.data.keys():# only if the control rig is not already built 161 | # duplicate current skeleton 162 | duplicate_object() 163 | copy_name = arm.name+"_TEMPANIM" 164 | self.animated_armature = get_object(bpy.context.active_object.name) 165 | self.animated_armature.name = copy_name 166 | self.animated_armature["mix_to_del"] = True 167 | 168 | bpy.ops.object.mode_set(mode='OBJECT') 169 | bpy.ops.object.select_all(action='DESELECT') 170 | set_active_object(arm.name) 171 | 172 | # set to rest pose, clear animation 173 | _zero_out() 174 | 175 | # build control rig 176 | _make_rig(self) 177 | 178 | if blender_version._float < 291: 179 | # Child Of constraints inverse matrix must be set manually in Blender versions < 2.91 180 | print("Set inverse ChildOf") 181 | _reset_inverse_constraints() 182 | 183 | # animation import: retarget 184 | if self.bake_anim and self.animated_armature: 185 | _import_anim(self.animated_armature, arm) 186 | 187 | print(blender_version._float) 188 | if blender_version._float >= 404: 189 | arm.animation_data.action_slot = arm.animation_data.action_suitable_slots[0] 190 | # set KeyingSet 191 | ks = context.scene.keying_sets_all 192 | try: 193 | ks.active = ks["Location & Rotation"] 194 | except:# doesn't exist in older Blender versions 195 | pass 196 | 197 | 198 | finally: 199 | bpy.ops.object.mode_set(mode='OBJECT') 200 | bpy.ops.object.select_all(action='DESELECT') 201 | set_active_object(arm.name) 202 | 203 | if debug == False: 204 | restore_armature_layers(layer_select) 205 | remove_retarget_cns(bpy.context.active_object) 206 | remove_temp_objects() 207 | clean_scene() 208 | 209 | 210 | self.report({"INFO"}, "Control Rig Done!") 211 | 212 | return {'FINISHED'} 213 | 214 | 215 | class MR_OT_zero_out(bpy.types.Operator): 216 | """Delete all keys and set every bones to (0,0,0) rotation""" 217 | 218 | bl_idname = "mr.zero_out" 219 | bl_label = "zero_out" 220 | bl_options = {'UNDO'} 221 | 222 | @classmethod 223 | def poll(cls, context): 224 | if context.active_object: 225 | return context.active_object.type == "ARMATURE" 226 | return False 227 | 228 | 229 | def execute(self, context): 230 | scn = bpy.context.scene 231 | 232 | 233 | try: 234 | _zero_out() 235 | 236 | finally: 237 | print("") 238 | 239 | 240 | return {'FINISHED'} 241 | 242 | 243 | class MR_OT_bake_anim(bpy.types.Operator): 244 | """Merge all animation layers (see NLA editor) into a single layer""" 245 | 246 | bl_idname = "mr.bake_anim" 247 | bl_label = "bake_anim" 248 | bl_options = {'UNDO'} 249 | 250 | 251 | @classmethod 252 | def poll(cls, context): 253 | if context.active_object: 254 | return context.active_object.type == "ARMATURE" 255 | return False 256 | 257 | 258 | def execute(self, context): 259 | scn = bpy.context.scene 260 | 261 | try: 262 | _bake_anim(self) 263 | 264 | finally: 265 | pass 266 | 267 | 268 | return {'FINISHED'} 269 | 270 | 271 | class MR_OT_import_anim(bpy.types.Operator): 272 | """Import an animation file (FBX) of the same character to the control rig""" 273 | 274 | bl_idname = "mr.import_anim_to_rig" 275 | bl_label = "import_anim_to_rig" 276 | bl_options = {'UNDO'} 277 | 278 | 279 | @classmethod 280 | def poll(cls, context): 281 | if context.active_object: 282 | if context.active_object.type == "ARMATURE": 283 | if "mr_control_rig" in context.active_object.data.keys(): 284 | return True 285 | return False 286 | 287 | 288 | def execute(self, context): 289 | scn = bpy.context.scene 290 | debug = False 291 | error = False 292 | layer_select = [] 293 | 294 | if scn.mix_source_armature == None: 295 | self.report({'ERROR'}, "Source armature must be set") 296 | return {'FINISHED'} 297 | 298 | 299 | try: 300 | layer_select = enable_all_armature_layers() 301 | #tar_arm = scn.mix_target_armature 302 | tar_arm = get_object(bpy.context.active_object.name) 303 | #src_arm = [i for i in bpy.context.selected_objects if i != tar_arm][0] 304 | src_arm = scn.mix_source_armature 305 | print("Source", src_arm.name) 306 | print("Target", tar_arm.name) 307 | 308 | _import_anim(src_arm, tar_arm, import_only=True) 309 | 310 | if blender_version._float >= 404: 311 | tar_arm.animation_data.action_slot = tar_arm.animation_data.action_suitable_slots[0] 312 | 313 | 314 | #except: 315 | # error = True 316 | # print("Error") 317 | 318 | finally: 319 | if debug == False: 320 | restore_armature_layers(layer_select) 321 | remove_retarget_cns(bpy.context.active_object) 322 | 323 | if scn.mix_source_armature: 324 | try: 325 | remove_retarget_cns(mix_source_armature) 326 | except: 327 | pass 328 | 329 | remove_temp_objects() 330 | 331 | self.report({"INFO"}, "Animation imported") 332 | 333 | 334 | return {'FINISHED'} 335 | 336 | 337 | 338 | 339 | # OPERATOR FUNCTIONS 340 | ##################### 341 | 342 | def _apply_shape(): 343 | bpy.ops.object.mode_set(mode='OBJECT') 344 | obj = bpy.context.active_object 345 | obj_name = obj.name 346 | shape = bpy.data.objects.get(obj_name) 347 | delete_obj = False 348 | 349 | cs_grp = get_object('cs_grp') 350 | if cs_grp: 351 | shape.parent = bpy.data.objects['cs_grp'] 352 | 353 | mr_armature_name = None 354 | mr_armature = None 355 | 356 | if len(shape.keys()) > 0: 357 | for key in shape.keys(): 358 | if 'delete' in shape.keys(): 359 | delete_obj = True 360 | if 'mr_armature' in key: 361 | mr_armature_name = shape['mr_armature'] 362 | mr_armature = bpy.data.objects.get(mr_armature_name) 363 | 364 | if delete_obj: 365 | bpy.ops.object.delete(use_global=False) 366 | else: 367 | # assign to collection 368 | if mr_armature: 369 | if len(mr_armature.users_collection) > 0: 370 | for collec in mr_armature.users_collection: 371 | if len(collec.name.split('_')) == 1: 372 | continue 373 | if collec.name.split('_')[1] == "rig" or collec.name.split('_')[1] == "grp": 374 | cs_collec = bpy.data.collections.get(collec.name.split('_')[0] + '_cs') 375 | if cs_collec: 376 | # remove from root collection 377 | if bpy.context.scene.collection.objects.get(shape.name): 378 | bpy.context.scene.collection.objects.unlink(shape) 379 | # remove from other collections 380 | for other_collec in shape.users_collection: 381 | other_collec.objects.unlink(shape) 382 | # assign to cs collection 383 | cs_collec.objects.link(shape) 384 | print("assigned to collec", cs_collec.name) 385 | else: 386 | print("cs collec not found") 387 | else: 388 | print("rig collec not found") 389 | 390 | else: 391 | print("Armature has no collection") 392 | else: 393 | print("Armature not set") 394 | 395 | # hide shape 396 | try: 397 | hide_object(shape) 398 | except: # weird error 'StructRNA of type Object has been removed' 399 | print("Error, could not hide shape") 400 | pass 401 | 402 | if mr_armature: 403 | set_active_object(mr_armature.name) 404 | bpy.ops.object.mode_set(mode='POSE') 405 | 406 | 407 | def _edit_custom_shape(): 408 | bone = bpy.context.active_pose_bone 409 | rig_name = bpy.context.active_object.name 410 | rig = get_object(rig_name) 411 | 412 | cs = bone.custom_shape 413 | cs_mesh = cs.data 414 | 415 | bpy.ops.object.posemode_toggle() 416 | 417 | # make sure the active collection is not hidden, otherwise we can't access the newly created object data 418 | active_collec = bpy.context.layer_collection 419 | if not active_collec.is_visible: 420 | for col in rig.users_collection: 421 | layer_col = search_layer_collection(bpy.context.view_layer.layer_collection, col.name) 422 | if layer_col.hide_viewport == False and col.hide_viewport == False: 423 | bpy.context.view_layer.active_layer_collection = layer_col 424 | break 425 | 426 | # create new mesh data 427 | bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, location=(-0, 0, 0.0), rotation=(0.0, 0.0, 0.0)) 428 | 429 | mesh_obj = bpy.context.active_object 430 | mesh_obj.name = 'cs_user_' + bone.name 431 | 432 | if cs.name == "cs_user_" + bone.name:# make a mesh instance if it's a already edited 433 | mesh_obj.data = cs_mesh 434 | mesh_obj['delete'] = 1.0 435 | else: # else create new object data 436 | mesh_obj.data = cs_mesh.copy() 437 | mesh_obj.data.name = mesh_obj.name 438 | bone.custom_shape = mesh_obj 439 | 440 | # store the current armature name in a custom prop 441 | mesh_obj['mr_armature'] = rig_name 442 | 443 | if bone.custom_shape_transform: 444 | bone_transf = bone.custom_shape_transform 445 | mesh_obj.matrix_world = rig.matrix_world @ bone_transf.matrix 446 | else: 447 | mesh_obj.matrix_world = rig.matrix_world @ bone.matrix 448 | 449 | mesh_obj.scale *= get_custom_shape_scale(bone) 450 | mesh_obj.scale *= bone.length 451 | 452 | bpy.ops.object.mode_set(mode='EDIT') 453 | 454 | 455 | def clean_scene(): 456 | # hide cs_grp 457 | cs_grp = get_object("cs_grp") 458 | if cs_grp: 459 | for c in cs_grp.children: 460 | hide_object(c) 461 | hide_object(cs_grp) 462 | 463 | # Display layer 0 and 1 only 464 | # ~ layers = bpy.context.active_object.data.layers 465 | # ~ layers[0] = True 466 | # ~ for i in range(0, 32): 467 | # ~ layers[i] = i in [0] 468 | 469 | for c in bpy.context.active_object.data.collections: 470 | if c.name in ('CTRL'): 471 | c.is_visible = True 472 | else: 473 | c.is_visible = False 474 | 475 | 476 | def init_armature_transforms(rig): 477 | bpy.ops.object.mode_set(mode='OBJECT') 478 | bpy.ops.object.select_all(action='DESELECT') 479 | set_active_object(rig.name) 480 | bpy.ops.object.mode_set(mode='OBJECT') 481 | 482 | # first unparent children meshes (init scale messed up children scale in Blender 2.8) 483 | child_par_dict = {} 484 | for child in bpy.data.objects[rig.name].children: 485 | bone_parent = None 486 | if child.parent_type == "BONE": 487 | bone_parent = child.parent_bone 488 | child_par_dict[child.name] = bone_parent 489 | child_mat = child.matrix_world.copy() 490 | child.parent = None 491 | bpy.context.evaluated_depsgraph_get().update() 492 | child.matrix_world = child_mat 493 | 494 | # apply armature transforms 495 | bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) 496 | bpy.context.evaluated_depsgraph_get().update() 497 | 498 | # restore armature children 499 | for child_name in child_par_dict: 500 | child = bpy.data.objects.get(child_name) 501 | child_mat = child.matrix_world.copy() 502 | child.parent = bpy.data.objects[rig.name] 503 | if child_par_dict[child_name] != None:# bone parent 504 | child.parent_type = "BONE" 505 | child.parent_bone = child_par_dict[child_name] 506 | 507 | bpy.context.evaluated_depsgraph_get().update() 508 | child.matrix_world = child_mat 509 | 510 | 511 | def _reset_inverse_constraints(): 512 | bpy.ops.object.mode_set(mode='POSE') 513 | 514 | rig_name = bpy.context.active_object.name 515 | rig = get_object(rig_name) 516 | 517 | for pb in rig.pose.bones: 518 | if len(pb.constraints): 519 | for cns in pb.constraints: 520 | if cns.type == 'CHILD_OF': 521 | set_constraint_inverse_matrix(cns) 522 | 523 | 524 | bpy.ops.object.mode_set(mode='OBJECT') 525 | 526 | 527 | 528 | def _update(self): 529 | if blender_version._float >= 300: 530 | convert_drivers_cs_to_xyz(bpy.context.active_object) 531 | 532 | 533 | def _make_rig(self): 534 | print("\nBuilding control rig...") 535 | 536 | scn = bpy.context.scene 537 | rig_name = bpy.context.active_object.name 538 | rig = get_object(rig_name) 539 | 540 | coll_mix_name = "DEF" 541 | coll_ctrl_name = "CTRL" 542 | coll_intern_name = "MCH" 543 | 544 | # ~ _ = rig.data.collections.new(coll_0_name) 545 | # ~ for eb in rig.data.edit_bones: 546 | # ~ set_bone_collection(rig, eb, coll_0_name) 547 | 548 | # ~ layer_mix_idx = 1 549 | # ~ layer_ctrl_idx = 0 550 | # ~ layer_intern_idx = 8 551 | use_name_prefix = True 552 | 553 | c_master_name = c_prefix+master_rig_names["master"] 554 | 555 | # Init transforms 556 | init_armature_transforms(rig) 557 | 558 | 559 | def add_master(): 560 | print(" Add Master") 561 | 562 | # -- Edit -- 563 | bpy.ops.object.mode_set(mode='EDIT') 564 | 565 | 566 | # Create bones 567 | c_master = create_edit_bone(c_master_name) 568 | c_master.head = [0, 0, 0] 569 | c_master.tail = [0, 0, 0.05 * rig.dimensions[2]] 570 | c_master.roll = 0.01 571 | set_bone_collection(rig, c_master, coll_ctrl_name) 572 | # ~ set_bone_layer(c_master, layer_ctrl_idx) 573 | c_master["mixamo_ctrl"] = 1# tag as controller bone 574 | 575 | 576 | # -- Pose -- 577 | bpy.ops.object.mode_set(mode='POSE') 578 | 579 | 580 | c_master_pb = get_pose_bone(c_master_name) 581 | 582 | # set custom shapes 583 | set_bone_custom_shape(c_master_pb, "cs_master") 584 | 585 | # set rotation mode 586 | c_master_pb.rotation_mode = "XYZ" 587 | 588 | # set color group 589 | set_bone_color_group(rig, c_master_pb, "master") 590 | 591 | 592 | def add_spine(): 593 | print(" Add Spine") 594 | 595 | # -- Edit -- 596 | bpy.ops.object.mode_set(mode='EDIT') 597 | 598 | 599 | # Create bones 600 | hips_name = get_mix_name(spine_names["pelvis"], use_name_prefix) 601 | spine_name = get_mix_name(spine_names["spine1"], use_name_prefix) 602 | spine1_name = get_mix_name(spine_names["spine2"], use_name_prefix) 603 | spine2_name = get_mix_name(spine_names["spine3"], use_name_prefix) 604 | 605 | hips = get_edit_bone(hips_name) 606 | spine = get_edit_bone(spine_name) 607 | spine1 = get_edit_bone(spine1_name) 608 | spine2 = get_edit_bone(spine2_name) 609 | 610 | if not hips or not spine or not spine1 or not spine2: 611 | print(" Spine bones are missing, skip spine") 612 | return 613 | 614 | for b in [hips, spine, spine1, spine2]: 615 | set_bone_collection(rig, b, coll_mix_name) 616 | # ~ set_bone_layer(b, layer_mix_idx) 617 | 618 | # Hips Ctrl 619 | c_hips_name = c_prefix+spine_rig_names["pelvis"] 620 | c_hips = create_edit_bone(c_hips_name) 621 | copy_bone_transforms(hips, c_hips) 622 | c_hips.parent = get_edit_bone(c_prefix+master_rig_names["master"]) 623 | set_bone_collection(rig, c_hips, coll_ctrl_name) 624 | # ~ set_bone_layer(c_hips, layer_ctrl_idx) 625 | c_hips["mixamo_ctrl"] = 1# tag as controller bone 626 | 627 | 628 | # Free Hips Ctrl 629 | c_hips_free_name = c_prefix+spine_rig_names["hips_free"] 630 | c_hips_free = create_edit_bone(c_hips_free_name) 631 | c_hips_free.head = hips.tail.copy() 632 | c_hips_free.tail = hips.head.copy() 633 | align_bone_x_axis(c_hips_free, hips.x_axis) 634 | c_hips_free["mixamo_ctrl"] = 1# tag as controller bone 635 | 636 | c_hips_free.parent = c_hips 637 | set_bone_collection(rig, c_hips_free, coll_ctrl_name) 638 | # ~ set_bone_layer(c_hips_free, layer_ctrl_idx) 639 | 640 | # Free Hips helper 641 | hips_free_h_name = spine_rig_names["hips_free_helper"] 642 | hips_free_helper = create_edit_bone(hips_free_h_name) 643 | copy_bone_transforms(hips, hips_free_helper) 644 | hips_free_helper.parent = c_hips_free 645 | set_bone_collection(rig, hips_free_helper, coll_intern_name) 646 | # ~ set_bone_layer(hips_free_helper, layer_intern_idx) 647 | 648 | # Spine Ctrl 649 | c_spine_name = c_prefix+spine_rig_names["spine1"] 650 | c_spine = create_edit_bone(c_spine_name) 651 | copy_bone_transforms(spine, c_spine) 652 | c_spine.parent = c_hips 653 | # ~ set_bone_layer(c_spine, layer_ctrl_idx) 654 | set_bone_collection(rig, c_spine, coll_ctrl_name) 655 | c_spine["mixamo_ctrl"] = 1# tag as controller bone 656 | 657 | # Spine1 Ctrl 658 | c_spine1_name = c_prefix+spine_rig_names["spine2"] 659 | c_spine1 = create_edit_bone(c_spine1_name) 660 | copy_bone_transforms(spine1, c_spine1) 661 | c_spine1.parent = c_spine 662 | set_bone_collection(rig, c_spine1, coll_ctrl_name) 663 | # ~ set_bone_layer(c_spine1, layer_ctrl_idx) 664 | c_spine1["mixamo_ctrl"] = 1# tag as controller bone 665 | 666 | # Spine2 Ctrl 667 | c_spine2_name = c_prefix+spine_rig_names["spine3"] 668 | c_spine2 = create_edit_bone(c_spine2_name) 669 | copy_bone_transforms(spine2, c_spine2) 670 | c_spine2.parent = c_spine1 671 | set_bone_collection(rig, c_spine2, coll_ctrl_name) 672 | # ~ set_bone_layer(c_spine2, layer_ctrl_idx) 673 | c_spine2["mixamo_ctrl"] = 1# tag as controller bone 674 | 675 | 676 | # -- Pose -- 677 | bpy.ops.object.mode_set(mode='POSE') 678 | 679 | 680 | c_hips_pb = get_pose_bone(c_hips_name) 681 | hips_helper_pb = get_pose_bone(hips_free_h_name) 682 | c_hips_free_pb = get_pose_bone(c_hips_free_name) 683 | c_spine_pb = get_pose_bone(c_spine_name) 684 | c_spine1_pb = get_pose_bone(c_spine1_name) 685 | c_spine2_pb = get_pose_bone(c_spine2_name) 686 | 687 | # set custom shapes 688 | set_bone_custom_shape(c_hips_pb, "cs_square_2") 689 | set_bone_custom_shape(c_hips_free_pb, "cs_hips") 690 | set_bone_custom_shape(c_spine_pb, "cs_circle") 691 | set_bone_custom_shape(c_spine1_pb, "cs_circle") 692 | set_bone_custom_shape(c_spine2_pb, "cs_circle") 693 | 694 | # set rotation mode 695 | c_hips_pb.rotation_mode = "XYZ" 696 | c_hips_free_pb.rotation_mode = "XYZ" 697 | c_spine_pb.rotation_mode = "XYZ" 698 | c_spine1_pb.rotation_mode = "XYZ" 699 | c_spine2_pb.rotation_mode = "XYZ" 700 | 701 | # set color group 702 | set_bone_color_group(rig, c_hips_pb, "root_master") 703 | set_bone_color_group(rig, c_hips_free_pb, "body_mid") 704 | set_bone_color_group(rig, c_spine_pb, "body_mid") 705 | set_bone_color_group(rig, c_spine1_pb, "body_mid") 706 | set_bone_color_group(rig, c_spine2_pb, "body_mid") 707 | 708 | # constraints 709 | # Hips 710 | mixamo_spine_pb = get_pose_bone(hips_name) 711 | cns = mixamo_spine_pb.constraints.get("Copy Transforms") 712 | if cns == None: 713 | cns = mixamo_spine_pb.constraints.new("COPY_TRANSFORMS") 714 | cns.name = "Copy Transforms" 715 | cns.target = rig 716 | cns.subtarget = hips_free_h_name 717 | 718 | # Spine 719 | spine_bone_matches = {"1": c_spine_name, "2":c_spine1_name, "3":c_spine2_name} 720 | for str_idx in spine_bone_matches: 721 | c_name = spine_bone_matches[str_idx] 722 | mixamo_bname = get_mix_name(spine_names["spine"+str_idx], use_name_prefix) 723 | mixamo_spine_pb = get_pose_bone(mixamo_bname) 724 | cns = mixamo_spine_pb.constraints.get("Copy Transforms") 725 | if cns == None: 726 | cns = mixamo_spine_pb.constraints.new("COPY_TRANSFORMS") 727 | cns.name = "Copy Transforms" 728 | cns.target = rig 729 | cns.subtarget = c_name 730 | 731 | 732 | def add_head(): 733 | print(" Add Head") 734 | 735 | # -- Edit -- 736 | bpy.ops.object.mode_set(mode='EDIT') 737 | 738 | 739 | # Create bones 740 | neck_name = get_mix_name(head_names["neck"], use_name_prefix) 741 | head_name = get_mix_name(head_names["head"], use_name_prefix) 742 | head_end_name = get_mix_name(head_names["head_end"], use_name_prefix) 743 | 744 | neck = get_edit_bone(neck_name) 745 | head = get_edit_bone(head_name) 746 | head_end = get_edit_bone(head_end_name) 747 | 748 | if not neck or not head: 749 | print(" Head or neck bones are missing, skip head") 750 | return 751 | 752 | for b in [neck, head, head_end]: 753 | set_bone_collection(rig, b, coll_mix_name) 754 | # ~ set_bone_layer(b, layer_mix_idx) 755 | 756 | # Neck Ctrl 757 | c_neck_name = c_prefix+head_rig_names["neck"] 758 | c_neck = create_edit_bone(c_neck_name) 759 | copy_bone_transforms(neck, c_neck) 760 | c_neck.parent = get_edit_bone(c_prefix+spine_rig_names["spine3"]) 761 | # ~ set_bone_layer(c_neck, layer_ctrl_idx) 762 | set_bone_collection(rig, c_neck, coll_ctrl_name) 763 | c_neck["mixamo_ctrl"] = 1# tag as controller bone 764 | 765 | # Head Ctrl 766 | c_head_name = c_prefix+head_rig_names["head"] 767 | c_head = create_edit_bone(c_head_name) 768 | copy_bone_transforms(head, c_head) 769 | c_head.parent = c_neck 770 | # ~ set_bone_layer(c_head, layer_ctrl_idx) 771 | set_bone_collection(rig, c_head, coll_ctrl_name) 772 | c_head["mixamo_ctrl"] = 1# tag as controller bone 773 | 774 | # -- Pose -- 775 | bpy.ops.object.mode_set(mode='POSE') 776 | 777 | 778 | c_neck_pb = get_pose_bone(c_neck_name) 779 | c_head_pb = get_pose_bone(c_head_name) 780 | 781 | # set custom shapes 782 | set_bone_custom_shape(c_neck_pb, "cs_neck") 783 | set_bone_custom_shape(c_head_pb, "cs_head") 784 | 785 | # set rotation mode 786 | c_neck_pb.rotation_mode = "XYZ" 787 | c_head_pb.rotation_mode = "XYZ" 788 | 789 | # set color group 790 | set_bone_color_group(rig, c_neck_pb, "neck") 791 | set_bone_color_group(rig, c_head_pb, "head") 792 | 793 | # constraints 794 | # Neck 795 | neck_pb = get_pose_bone(neck_name) 796 | head_pb = get_pose_bone(head_name) 797 | 798 | add_copy_transf(neck_pb, rig, c_neck_name) 799 | add_copy_transf(head_pb, rig, c_head_name) 800 | 801 | 802 | def add_leg(side): 803 | print(" Add Leg", side) 804 | 805 | _side = "_"+side 806 | thigh_name = get_mix_name(side+leg_names["thigh"], use_name_prefix) 807 | calf_name = get_mix_name(side+leg_names["calf"], use_name_prefix) 808 | foot_name = get_mix_name(side+leg_names["foot"], use_name_prefix) 809 | toe_name = get_mix_name(side+leg_names["toes"], use_name_prefix) 810 | toe_end_name = get_mix_name(side+leg_names["toes_end"], use_name_prefix) 811 | 812 | # -- Edit -- 813 | bpy.ops.object.mode_set(mode='EDIT') 814 | 815 | 816 | thigh = get_edit_bone(thigh_name) 817 | calf = get_edit_bone(calf_name) 818 | foot = get_edit_bone(foot_name) 819 | toe = get_edit_bone(toe_name) 820 | toe_end = get_edit_bone(toe_end_name) 821 | 822 | hips = get_edit_bone(get_mix_name(spine_names["pelvis"], use_name_prefix)) 823 | c_hips_free_name = c_prefix+spine_rig_names["hips_free"] 824 | c_hips_free = get_edit_bone(c_hips_free_name) 825 | 826 | if not thigh or not calf or not foot or not toe: 827 | print(" Leg bones are missing, skip leg: "+side) 828 | return 829 | 830 | # Set Mixamo bones in layer 831 | for b in [thigh, calf, foot, toe, toe_end]: 832 | set_bone_collection(rig, b, coll_mix_name) 833 | # ~ set_bone_layer(b, layer_mix_idx) 834 | 835 | # Create bones 836 | # correct straight leg angle, need minimum 0.1 degrees for IK constraints to work 837 | def get_leg_angle(): 838 | #return degrees(thigh.y_axis.angle(calf.y_axis)) 839 | vec1 = calf.head - thigh.head 840 | vec2 = foot.head - calf.head 841 | return degrees(vec1.angle(vec2)) 842 | 843 | leg_angle = get_leg_angle() 844 | 845 | if leg_angle < 0.1: 846 | print(" ! Straight leg bones, angle = "+str(leg_angle)) 847 | max_iter = 10000 848 | i = 0 849 | 850 | while leg_angle < 0.1 and i < max_iter: 851 | 852 | dir = ((thigh.z_axis + calf.z_axis)*0.5).normalized() 853 | calf.head += dir * (calf.tail-calf.head).magnitude * 0.0001 854 | leg_angle = get_leg_angle() 855 | i += 1 856 | 857 | print(" corrected leg angle: "+str(leg_angle)) 858 | 859 | 860 | # Thigh IK 861 | thigh_ik_name = leg_rig_names["thigh_ik"]+_side 862 | thigh_ik = create_edit_bone(thigh_ik_name) 863 | copy_bone_transforms(thigh, thigh_ik) 864 | 865 | # auto-align knee position with global Y axis to ensure IK pole vector is physically correct 866 | leg_axis = calf.tail - thigh.head 867 | leg_midpoint = (thigh.head + calf.tail) * 0.5 868 | 869 | #cur_vec = calf.head - leg_midpoint 870 | #cur_vec[2] = 0.0 871 | #global_y_vec = Vector((0, -1, 0)) 872 | 873 | dir = calf.head - leg_midpoint 874 | cur_vec = project_vector_onto_plane(dir, leg_axis) 875 | global_y_vec = project_vector_onto_plane(Vector((0, -1, 0)), leg_axis) 876 | 877 | signed_cur_angle = signed_angle(cur_vec, global_y_vec, leg_axis) 878 | print(" IK base angle:", degrees(signed_cur_angle)) 879 | 880 | # rotate 881 | rotated_point = rotate_point(calf.head.copy(), -signed_cur_angle, leg_midpoint, leg_axis) 882 | 883 | # (check) 884 | dir = rotated_point - leg_midpoint 885 | cur_vec = project_vector_onto_plane(dir, leg_axis) 886 | signed_cur_angle = signed_angle(cur_vec, global_y_vec, leg_axis) 887 | print(" IK corrected angle:", degrees(signed_cur_angle)) 888 | 889 | thigh_ik.tail = rotated_point 890 | 891 | thigh_ik.parent = c_hips_free 892 | set_bone_collection(rig, thigh_ik, coll_intern_name) 893 | # ~ set_bone_layer(thigh_ik, layer_intern_idx) 894 | 895 | # Thigh FK Ctrl 896 | c_thigh_fk_name = c_prefix+leg_rig_names["thigh_fk"]+_side 897 | c_thigh_fk = create_edit_bone(c_thigh_fk_name) 898 | copy_bone_transforms(thigh_ik, c_thigh_fk) 899 | c_thigh_fk.parent = c_hips_free 900 | # ~ set_bone_layer(c_thigh_fk, layer_ctrl_idx) 901 | set_bone_collection(rig, c_thigh_fk, coll_ctrl_name) 902 | c_thigh_fk["mixamo_ctrl"] = 1# tag as controller bone 903 | 904 | # Calf IK 905 | calf_ik_name = leg_rig_names["calf_ik"]+_side 906 | 907 | # check if bone exist to avoid undesired transformation when running the function multiple time 908 | calf_ik_exist = get_edit_bone(calf_ik_name) 909 | 910 | calf_ik = create_edit_bone(calf_ik_name) 911 | if calf_ik_exist == None: 912 | copy_bone_transforms(calf, calf_ik) 913 | calf_ik.head = thigh_ik.tail.copy() 914 | calf_ik.tail = foot.head.copy() 915 | calf_ik.parent = thigh_ik 916 | calf_ik.use_connect = True 917 | # ~ set_bone_layer(calf_ik, layer_intern_idx) 918 | set_bone_collection(rig, calf_ik, coll_intern_name) 919 | 920 | # align thigh and calf IK roll 921 | # align calf_ik local Z 922 | align_bone_z_axis(calf_ik, (calf_ik.head-leg_midpoint)) 923 | # align thigh_ik on calf_ik 924 | align_bone_z_axis(thigh_ik, calf_ik.z_axis) 925 | # copy thigh_ik to c_thigh_fk 926 | copy_bone_transforms(thigh_ik, c_thigh_fk) 927 | 928 | 929 | # Calf FK Ctrl 930 | c_calf_fk_name = c_prefix+leg_rig_names["calf_fk"]+_side 931 | c_calf_fk = create_edit_bone(c_calf_fk_name) 932 | copy_bone_transforms(calf_ik, c_calf_fk) 933 | c_calf_fk.parent = c_thigh_fk 934 | set_bone_collection(rig, c_calf_fk, coll_ctrl_name) 935 | # ~ set_bone_layer(c_calf_fk, layer_ctrl_idx) 936 | c_calf_fk["mixamo_ctrl"] = 1# tag as controller bone 937 | 938 | # Foot FK Ctrl 939 | c_foot_fk_name = c_prefix+leg_rig_names["foot_fk"]+_side 940 | c_foot_fk = create_edit_bone(c_foot_fk_name) 941 | copy_bone_transforms(foot, c_foot_fk) 942 | c_foot_fk.tail[2] = foot.head[2] 943 | align_bone_z_axis(c_foot_fk, Vector((0,0,1))) 944 | c_foot_fk.parent = c_calf_fk 945 | # ~ set_bone_layer(c_foot_fk, layer_ctrl_idx) 946 | set_bone_collection(rig, c_foot_fk, coll_ctrl_name) 947 | c_foot_fk["mixamo_ctrl"] = 1# tag as controller bone 948 | 949 | # Foot FK 950 | foot_fk_name = leg_rig_names["foot_fk"]+_side 951 | foot_fk = create_edit_bone(foot_fk_name) 952 | copy_bone_transforms(foot, foot_fk) 953 | foot_fk.parent = c_foot_fk 954 | set_bone_collection(rig, foot_fk, coll_intern_name) 955 | # ~ set_bone_layer(foot_fk, layer_intern_idx) 956 | 957 | # Foot IK Ctrl 958 | c_foot_ik_name = c_prefix+leg_rig_names["foot_ik"]+_side 959 | c_foot_ik = create_edit_bone(c_foot_ik_name) 960 | copy_bone_transforms(foot, c_foot_ik) 961 | c_foot_ik.tail[2] = foot.head[2] 962 | align_bone_z_axis(c_foot_ik, Vector((0,0,1))) 963 | set_bone_collection(rig, c_foot_ik, coll_ctrl_name) 964 | # ~ set_bone_layer(c_foot_ik, layer_ctrl_idx) 965 | c_foot_ik["mixamo_ctrl"] = 1# tag as controller bone 966 | 967 | # Foot IK 968 | foot_ik_name = leg_rig_names["foot_ik"]+_side 969 | foot_ik = create_edit_bone(foot_ik_name) 970 | copy_bone_transforms(foot, foot_ik) 971 | foot_ik.parent = c_foot_ik 972 | set_bone_collection(rig, foot_ik, coll_intern_name) 973 | # ~ set_bone_layer(foot_ik, layer_intern_idx) 974 | 975 | # Foot Snap 976 | foot_snap_name = leg_rig_names["foot_snap"]+_side 977 | foot_snap = create_edit_bone(foot_snap_name) 978 | copy_bone_transforms(c_foot_ik, foot_snap) 979 | foot_snap.parent = foot_ik 980 | set_bone_collection(rig, foot_snap, coll_intern_name) 981 | # ~ set_bone_layer(foot_snap, layer_intern_idx) 982 | 983 | # Foot IK target 984 | foot_ik_target_name = leg_rig_names["foot_ik_target"]+_side 985 | foot_ik_target = create_edit_bone(foot_ik_target_name) 986 | foot_ik_target.head = foot_ik.head.copy() 987 | foot_vec = (foot.tail - foot.head) 988 | foot_ik_target.tail = foot_ik_target.head - (foot_vec*0.25) 989 | align_bone_z_axis(foot_ik_target, Vector((0,0,1))) 990 | # parent set below (c_foot_01) 991 | # ~ set_bone_layer(foot_ik_target, layer_intern_idx) 992 | set_bone_collection(rig, foot_ik_target, coll_intern_name) 993 | 994 | # Foot Heel Out 995 | heel_out_name = leg_rig_names["heel_out"]+_side 996 | heel_out = create_edit_bone(heel_out_name) 997 | heel_out.head, heel_out.tail = Vector((0,0,0)), Vector((0,0,1)) 998 | heel_out.parent = c_foot_ik 999 | # ~ set_bone_layer(heel_out, layer_intern_idx) 1000 | set_bone_collection(rig, heel_out, coll_intern_name) 1001 | 1002 | # Foot Heel In 1003 | heel_in_name = leg_rig_names["heel_in"]+_side 1004 | heel_in = create_edit_bone(heel_in_name) 1005 | heel_in.head, heel_in.tail = Vector((0,0,0)), Vector((0,0,1)) 1006 | heel_in.parent = heel_out 1007 | # ~ set_bone_layer(heel_in, layer_intern_idx) 1008 | set_bone_collection(rig, heel_in, coll_intern_name) 1009 | 1010 | # Foot Heel Mid 1011 | heel_mid_name = leg_rig_names["heel_mid"]+_side 1012 | heel_mid = create_edit_bone(heel_mid_name) 1013 | heel_mid.head, heel_mid.tail = Vector((0,0,0)), Vector((0,0,1)) 1014 | heel_mid.parent = heel_in 1015 | # ~ set_bone_layer(heel_mid, layer_intern_idx) 1016 | set_bone_collection(rig, heel_mid, coll_intern_name) 1017 | 1018 | heel_mid.head[0], heel_mid.head[1], heel_mid.head[2] = foot.head[0], foot.head[1], foot.tail[2] 1019 | heel_mid.tail = foot.tail.copy() 1020 | heel_mid.tail[2] = heel_mid.head[2] 1021 | heel_mid.tail = heel_mid.head + (heel_mid.tail-heel_mid.head)*0.5 1022 | align_bone_x_axis(heel_mid, foot.x_axis) 1023 | 1024 | copy_bone_transforms(heel_mid, heel_in) 1025 | # use the foot x axis to determine "inside" vector, make sure it's pointing in the right direction for right and left side 1026 | fac = 1 1027 | if side == "Right": 1028 | fac = -1 1029 | 1030 | heel_in.head += foot.x_axis.normalized() * foot.length*0.3 * fac 1031 | heel_in.tail += foot.x_axis.normalized() * foot.length*0.3 * fac 1032 | 1033 | copy_bone_transforms(heel_mid, heel_out) 1034 | heel_out.head += foot.x_axis.normalized() * foot.length*0.3 * -fac 1035 | heel_out.tail += foot.x_axis.normalized() * foot.length*0.3 * -fac 1036 | 1037 | # Toe End 1038 | toes_end_name = leg_rig_names["toes_end"]+_side 1039 | toes_end = create_edit_bone(toes_end_name) 1040 | copy_bone_transforms(toe, toes_end) 1041 | toe_vec = (toes_end.tail-toes_end.head) 1042 | toes_end.tail += toe_vec 1043 | toes_end.head += toe_vec 1044 | toes_end.parent = heel_mid 1045 | # ~ set_bone_layer(toes_end, layer_intern_idx) 1046 | set_bone_collection(rig, toes_end, coll_intern_name) 1047 | 1048 | # Toe End 01 1049 | toes_end_01_name = leg_rig_names["toes_end_01"]+_side 1050 | toes_end_01 = create_edit_bone(toes_end_01_name) 1051 | copy_bone_transforms(toes_end, toes_end_01) 1052 | vec = toes_end_01.tail -toes_end_01.head 1053 | toes_end_01.tail = toes_end_01.head + (vec*0.5) 1054 | toes_end_01.parent = toes_end 1055 | # ~ set_bone_layer(toes_end_01, layer_intern_idx) 1056 | set_bone_collection(rig, toes_end_01, coll_intern_name) 1057 | 1058 | # Foot 01 Ctrl 1059 | c_foot_01_name = c_prefix+leg_rig_names["foot_01"]+_side 1060 | c_foot_01 = create_edit_bone(c_foot_01_name) 1061 | copy_bone_transforms(foot, c_foot_01) 1062 | c_foot_01_vec = c_foot_01.tail - c_foot_01.head 1063 | c_foot_01.tail += c_foot_01_vec 1064 | c_foot_01.head += c_foot_01_vec 1065 | c_foot_01.parent = toes_end 1066 | # ~ set_bone_layer(c_foot_01, layer_ctrl_idx) 1067 | set_bone_collection(rig, c_foot_01, coll_ctrl_name) 1068 | c_foot_01["mixamo_ctrl"] = 1# tag as controller bone 1069 | 1070 | # Foot_ik_target parent 1071 | foot_ik_target.parent = c_foot_01 1072 | 1073 | # Foot 01 Pole 1074 | foot_01_pole_name = leg_rig_names["foot_01_pole"]+_side 1075 | foot_01_pole = create_edit_bone(foot_01_pole_name) 1076 | foot_01_pole.head = c_foot_01.head + (c_foot_01.z_axis * 0.05 * c_foot_01.length * 40) 1077 | foot_01_pole.tail = foot_01_pole.head + (c_foot_01.z_axis * 0.05 * c_foot_01.length * 40) 1078 | foot_01_pole.roll = radians(180) 1079 | foot_01_pole.parent = c_foot_01 1080 | # ~ set_bone_layer(foot_01_pole, layer_intern_idx) 1081 | set_bone_collection(rig, foot_01_pole, coll_intern_name) 1082 | 1083 | # Toe IK Ctrl 1084 | c_toe_ik_name = c_prefix+leg_rig_names["toes_ik"]+_side 1085 | c_toe_ik = create_edit_bone(c_toe_ik_name) 1086 | copy_bone_transforms(toe, c_toe_ik) 1087 | c_toe_ik.parent = toes_end 1088 | # ~ set_bone_layer(c_toe_ik, layer_ctrl_idx) 1089 | set_bone_collection(rig, c_toe_ik, coll_ctrl_name) 1090 | c_toe_ik["mixamo_ctrl"] = 1# tag as controller bone 1091 | 1092 | # Toe Track 1093 | toe_track_name = leg_rig_names["toes_track"]+_side 1094 | toe_track = create_edit_bone(toe_track_name) 1095 | copy_bone_transforms(toe, toe_track) 1096 | toe_track.parent = foot_ik 1097 | # ~ set_bone_layer(toe_track, layer_intern_idx) 1098 | set_bone_collection(rig, toe_track, coll_intern_name) 1099 | 1100 | # Toe_01 IK 1101 | toe_01_ik_name = leg_rig_names["toes_01_ik"]+_side 1102 | toe_01_ik = create_edit_bone(toe_01_ik_name) 1103 | copy_bone_transforms(toe, toe_01_ik) 1104 | toe_01_ik.tail = toe_01_ik.head + (toe_01_ik.tail-toe_01_ik.head)*0.5 1105 | toe_01_ik.parent = toe_track 1106 | # ~ set_bone_layer(toe_01_ik, layer_intern_idx) 1107 | set_bone_collection(rig, toe_01_ik, coll_intern_name) 1108 | 1109 | # Toe_02 1110 | toe_02_name = leg_rig_names["toes_02"]+_side 1111 | toe_02 = create_edit_bone(toe_02_name) 1112 | copy_bone_transforms(toe, toe_02) 1113 | toe_02.head = toe_02.head + (toe_02.tail-toe_02.head)*0.5 1114 | toe_02.parent = toe_01_ik 1115 | # ~ set_bone_layer(toe_02, layer_intern_idx) 1116 | set_bone_collection(rig, toe_02, coll_intern_name) 1117 | 1118 | # Toe FK Ctrl 1119 | c_toe_fk_name = c_prefix+leg_rig_names["toes_fk"]+_side 1120 | c_toe_fk = create_edit_bone(c_toe_fk_name) 1121 | copy_bone_transforms(toe, c_toe_fk) 1122 | c_toe_fk.parent = foot_fk 1123 | # ~ set_bone_layer(c_toe_fk, layer_ctrl_idx) 1124 | set_bone_collection(rig, c_toe_fk, coll_ctrl_name) 1125 | c_toe_fk["mixamo_ctrl"] = 1# tag as controller bone 1126 | 1127 | # Foot Roll Cursor Ctrl 1128 | c_foot_roll_cursor_name = c_prefix+leg_rig_names["foot_roll_cursor"]+_side 1129 | c_foot_roll_cursor = create_edit_bone(c_foot_roll_cursor_name) 1130 | copy_bone_transforms(c_foot_ik, c_foot_roll_cursor) 1131 | vec = c_foot_roll_cursor.tail - c_foot_roll_cursor.head 1132 | dist = 1.2 1133 | c_foot_roll_cursor.head -= vec*dist 1134 | c_foot_roll_cursor.tail -= vec*dist 1135 | c_foot_roll_cursor.parent = c_foot_ik 1136 | # ~ set_bone_layer(c_foot_roll_cursor, layer_ctrl_idx) 1137 | set_bone_collection(rig, c_foot_roll_cursor, coll_ctrl_name) 1138 | c_foot_roll_cursor["mixamo_ctrl"] = 1# tag as controller bone 1139 | 1140 | # Pole IK Ctrl 1141 | c_pole_ik_name = c_prefix+leg_rig_names["pole_ik"]+_side 1142 | c_pole_ik = create_edit_bone(c_pole_ik_name) 1143 | # ~ set_bone_layer(c_pole_ik, layer_ctrl_idx) 1144 | set_bone_collection(rig, c_pole_ik, coll_ctrl_name) 1145 | c_pole_ik["mixamo_ctrl"] = 1# tag as controller bone 1146 | 1147 | plane_normal = (thigh_ik.head - calf_ik.tail) 1148 | prepole_dir = calf_ik.head - leg_midpoint 1149 | pole_pos = calf_ik.head + prepole_dir.normalized() 1150 | pole_pos = project_point_onto_plane(pole_pos, calf_ik.head, plane_normal) 1151 | pole_pos = calf_ik.head + ((pole_pos - calf_ik.head).normalized() * (calf_ik.head - thigh.head).magnitude * 1.7) 1152 | 1153 | c_pole_ik.head = pole_pos 1154 | c_pole_ik.tail = [c_pole_ik.head[0], c_pole_ik.head[1], c_pole_ik.head[2] + (0.165 * thigh_ik.length * 2)] 1155 | 1156 | ik_pole_angle = get_pole_angle(thigh_ik, calf_ik, c_pole_ik.head) 1157 | 1158 | 1159 | # -- Pose -- 1160 | bpy.ops.object.mode_set(mode='POSE') 1161 | 1162 | # Add constraints to control/mechanic bones 1163 | 1164 | # Calf IK 1165 | calf_ik_pb = get_pose_bone(calf_ik_name) 1166 | 1167 | cns_name = "IK" 1168 | ik_cns = calf_ik_pb.constraints.get(cns_name) 1169 | if ik_cns == None: 1170 | ik_cns = calf_ik_pb.constraints.new("IK") 1171 | ik_cns.name = cns_name 1172 | ik_cns.target = rig 1173 | ik_cns.subtarget = foot_ik_target_name 1174 | ik_cns.pole_target = rig 1175 | ik_cns.pole_subtarget = c_pole_ik_name 1176 | ik_cns.pole_angle = ik_pole_angle 1177 | ik_cns.chain_count = 2 1178 | ik_cns.use_tail = True 1179 | ik_cns.use_stretch = False 1180 | 1181 | calf_ik_pb.lock_ik_y = True 1182 | calf_ik_pb.lock_ik_z = True 1183 | 1184 | 1185 | # Foot IK 1186 | foot_ik_pb = get_pose_bone(foot_ik_name) 1187 | 1188 | cns_name = "Copy Location" 1189 | copy_loc_cns = foot_ik_pb.constraints.get(cns_name) 1190 | if copy_loc_cns == None: 1191 | copy_loc_cns = foot_ik_pb.constraints.new("COPY_LOCATION") 1192 | copy_loc_cns.name = cns_name 1193 | copy_loc_cns.target = rig 1194 | copy_loc_cns.subtarget = calf_ik_name 1195 | copy_loc_cns.head_tail = 1.0 1196 | 1197 | cns_name = "TrackTo" 1198 | cns = foot_ik_pb.constraints.get(cns_name) 1199 | if cns == None: 1200 | cns = foot_ik_pb.constraints.new("TRACK_TO") 1201 | cns.name = cns_name 1202 | cns.target = rig 1203 | cns.subtarget = c_foot_01_name 1204 | cns.head_tail = 0.0 1205 | cns.track_axis = "TRACK_Y" 1206 | cns.up_axis = "UP_Z" 1207 | cns.use_target_z = True 1208 | 1209 | cns_name = "Locked Track" 1210 | cns = foot_ik_pb.constraints.get(cns_name) 1211 | if cns == None: 1212 | cns = foot_ik_pb.constraints.new("LOCKED_TRACK") 1213 | cns.name = cns_name 1214 | cns.target = rig 1215 | cns.subtarget = foot_01_pole_name 1216 | cns.head_tail = 0.0 1217 | cns.track_axis = "TRACK_Z" 1218 | cns.lock_axis = "LOCK_Y" 1219 | 1220 | cns_name = "Copy Scale" 1221 | cns = foot_ik_pb.constraints.get(cns_name) 1222 | if cns == None: 1223 | cns = foot_ik_pb.constraints.new("COPY_SCALE") 1224 | cns.name = cns_name 1225 | cns.target = rig 1226 | cns.subtarget = c_foot_ik_name 1227 | 1228 | # Foot Ctrl IK 1229 | c_foot_ik_pb = get_pose_bone(c_foot_ik_name) 1230 | 1231 | cns_name = "Child Of" 1232 | cns = c_foot_ik_pb.constraints.get(cns_name) 1233 | if cns == None: 1234 | cns = c_foot_ik_pb.constraints.new("CHILD_OF") 1235 | cns.name = cns_name 1236 | cns.target = rig 1237 | cns.subtarget = "Ctrl_Master" 1238 | 1239 | 1240 | # Pole IK 1241 | c_pole_ik_pb = get_pose_bone(c_pole_ik_name) 1242 | 1243 | cns_name = "Child Of" 1244 | child_cns = c_pole_ik_pb.constraints.get(cns_name) 1245 | if child_cns == None: 1246 | child_cns = c_pole_ik_pb.constraints.new("CHILD_OF") 1247 | child_cns.name = cns_name 1248 | child_cns.target = rig 1249 | child_cns.subtarget = c_foot_ik_name 1250 | 1251 | cns_power = 8 1252 | 1253 | # Toe End 1254 | toes_end_pb = get_pose_bone(toes_end_name) 1255 | len = toes_end_pb.length * cns_power 1256 | 1257 | cns_name = "Transformation" 1258 | cns = toes_end_pb.constraints.get(cns_name) 1259 | if cns == None: 1260 | cns = toes_end_pb.constraints.new("TRANSFORM") 1261 | cns.name = cns_name 1262 | cns.target = rig 1263 | cns.subtarget = c_foot_roll_cursor_name 1264 | cns.use_motion_extrapolate = True 1265 | cns.target_space = cns.owner_space ="LOCAL" 1266 | cns.map_from = "LOCATION" 1267 | cns.from_min_z = 0.5 * len 1268 | cns.from_max_z = -0.5 * len 1269 | cns.map_to = "ROTATION" 1270 | cns.map_to_x_from = "Z" 1271 | cns.map_to_z_from = "X" 1272 | cns.to_min_x_rot = -2.61 1273 | cns.to_max_x_rot = 2.61 1274 | cns.mix_mode_rot = "ADD" 1275 | 1276 | cns_name = "Limit Rotation" 1277 | cns = toes_end_pb.constraints.get(cns_name) 1278 | if cns == None: 1279 | cns = toes_end_pb.constraints.new("LIMIT_ROTATION") 1280 | cns.name = cns_name 1281 | cns.owner_space ="LOCAL" 1282 | cns.use_limit_x = True 1283 | cns.min_x = -2*pi 1284 | cns.max_x = 0.0 1285 | 1286 | # Toe 01 ik 1287 | toe_01_ik_pb = get_pose_bone(toe_01_ik_name) 1288 | 1289 | cns_name = "Copy Transforms" 1290 | cns = toe_01_ik_pb.constraints.get(cns_name) 1291 | if cns == None: 1292 | cns = toe_01_ik_pb.constraints.new("COPY_TRANSFORMS") 1293 | cns.name = cns_name 1294 | cns.target = rig 1295 | cns.subtarget = c_toe_ik_name 1296 | cns.mix_mode = "REPLACE" 1297 | cns.target_space = cns.owner_space = "WORLD" 1298 | 1299 | # Toe 02 1300 | toe_02_pb = get_pose_bone(toe_02_name) 1301 | 1302 | cns_name = "Copy CopyRotation" 1303 | cns = toe_02_pb.constraints.get(cns_name) 1304 | if cns == None: 1305 | cns = toe_02_pb.constraints.new("COPY_ROTATION") 1306 | cns.name = cns_name 1307 | cns.target = rig 1308 | cns.subtarget = c_toe_ik_name 1309 | cns.mix_mode = "REPLACE" 1310 | cns.target_space = cns.owner_space = "WORLD" 1311 | 1312 | # Toe Track 1313 | toe_track_pb = get_pose_bone(toe_track_name) 1314 | 1315 | cns_name = "TrackTo" 1316 | cns = toe_track_pb.constraints.get(cns_name) 1317 | if cns == None: 1318 | cns = toe_track_pb.constraints.new("TRACK_TO") 1319 | cns.name = cns_name 1320 | cns.target = rig 1321 | cns.subtarget = toes_end_01_name 1322 | cns.head_tail = 0.0 1323 | cns.track_axis = 'TRACK_Y' 1324 | cns.up_axis = "UP_Z" 1325 | cns.use_target_z = True 1326 | 1327 | # Heel Mid 1328 | heel_mid_pb = get_pose_bone(heel_mid_name) 1329 | len = heel_mid_pb.length * cns_power 1330 | 1331 | cns_name = "Transformation" 1332 | cns = heel_mid_pb.constraints.get(cns_name) 1333 | if cns == None: 1334 | cns = heel_mid_pb.constraints.new("TRANSFORM") 1335 | cns.name = cns_name 1336 | cns.target = rig 1337 | cns.subtarget = c_foot_roll_cursor_name 1338 | cns.owner_space = cns.target_space = "LOCAL" 1339 | cns.map_from = "LOCATION" 1340 | cns.from_min_z = -0.25 * len 1341 | cns.from_max_z = 0.25 * len 1342 | cns.map_to = "ROTATION" 1343 | cns.map_to_x_from = "Z" 1344 | cns.map_to_y_from = "X" 1345 | cns.map_to_z_from = "Y" 1346 | cns.to_min_x_rot = radians(100) 1347 | cns.to_max_x_rot = -radians(100) 1348 | cns.mix_mode_rot = 'ADD' 1349 | 1350 | cns_name = "Limit Rotation" 1351 | cns = heel_mid_pb.constraints.get(cns_name) 1352 | if cns == None: 1353 | cns = heel_mid_pb.constraints.new("LIMIT_ROTATION") 1354 | cns.name = cns_name 1355 | cns.use_limit_x = True 1356 | cns.min_x = radians(0) 1357 | cns.max_x = radians(360) 1358 | cns.owner_space = "LOCAL" 1359 | 1360 | # Heel In 1361 | heel_in_pb = get_pose_bone(heel_in_name) 1362 | len = heel_in_pb.length * cns_power 1363 | 1364 | cns_name = "Transformation" 1365 | cns = heel_in_pb.constraints.get(cns_name) 1366 | if cns == None: 1367 | cns = heel_in_pb.constraints.new("TRANSFORM") 1368 | cns.name = cns_name 1369 | cns.target = rig 1370 | cns.subtarget = c_foot_roll_cursor_name 1371 | cns.owner_space = cns.target_space = "LOCAL" 1372 | cns.map_from = "LOCATION" 1373 | cns.from_min_x = -0.25 * len 1374 | cns.from_max_x = 0.25 * len 1375 | cns.map_to = "ROTATION" 1376 | cns.map_to_x_from = "Z" 1377 | cns.map_to_y_from = "X" 1378 | cns.map_to_z_from = "Y" 1379 | cns.to_min_y_rot = -radians(100) 1380 | cns.to_max_y_rot = radians(100) 1381 | cns.mix_mode_rot = 'ADD' 1382 | 1383 | cns_name = "Limit Rotation" 1384 | cns = heel_in_pb.constraints.get(cns_name) 1385 | if cns == None: 1386 | cns = heel_in_pb.constraints.new("LIMIT_ROTATION") 1387 | cns.name = cns_name 1388 | cns.use_limit_y = True 1389 | 1390 | if side == "Left": 1391 | cns.min_y = 0.0 1392 | cns.max_y = radians(90) 1393 | elif side == "Right": 1394 | cns.min_y = radians(-90) 1395 | cns.max_y = radians(0.0) 1396 | 1397 | cns.owner_space = "LOCAL" 1398 | 1399 | # Heel Out 1400 | heel_out_pb = get_pose_bone(heel_out_name) 1401 | len = heel_out_pb.length * cns_power 1402 | 1403 | cns_name = "Transformation" 1404 | cns = heel_out_pb.constraints.get(cns_name) 1405 | if cns == None: 1406 | cns = heel_out_pb.constraints.new("TRANSFORM") 1407 | cns.name = cns_name 1408 | cns.target = rig 1409 | cns.subtarget = c_foot_roll_cursor_name 1410 | cns.owner_space = cns.target_space = "LOCAL" 1411 | cns.map_from = "LOCATION" 1412 | cns.from_min_x = -0.25 * len 1413 | cns.from_max_x = 0.25 * len 1414 | cns.map_to = "ROTATION" 1415 | cns.map_to_x_from = "Z" 1416 | cns.map_to_y_from = "X" 1417 | cns.map_to_z_from = "Y" 1418 | cns.to_min_y_rot = -radians(100) 1419 | cns.to_max_y_rot = radians(100) 1420 | cns.mix_mode_rot = 'ADD' 1421 | 1422 | cns_name = "Limit Rotation" 1423 | cns = heel_out_pb.constraints.get(cns_name) 1424 | if cns == None: 1425 | cns = heel_out_pb.constraints.new("LIMIT_ROTATION") 1426 | cns.name = cns_name 1427 | cns.use_limit_y = True 1428 | 1429 | if side == "Left": 1430 | cns.min_y = radians(-90) 1431 | cns.max_y = radians(0.0) 1432 | elif side == "Right": 1433 | cns.min_y = radians(0.0) 1434 | cns.max_y = radians(90) 1435 | 1436 | cns.owner_space = "LOCAL" 1437 | 1438 | 1439 | # Add constraints to Mixamo bones 1440 | foot_pb = get_pose_bone(foot_name) 1441 | thigh_pb = get_pose_bone(thigh_name) 1442 | 1443 | # IK-FK switch property 1444 | if "ik_fk_switch" not in c_foot_ik_pb.keys(): 1445 | create_custom_prop(node=c_foot_ik_pb, prop_name="ik_fk_switch", prop_val=0.0, prop_min=0.0, prop_max=1.0, prop_description="IK-FK switch value") 1446 | 1447 | c_foot_ik_pb["ik_fk_switch"] = 0.0 if self.ik_legs else 1.0 1448 | 1449 | # Thigh 1450 | cns_name = "IK_follow" 1451 | cns_ik = thigh_pb.constraints.get(cns_name) 1452 | if cns_ik == None: 1453 | cns_ik = thigh_pb.constraints.new("COPY_TRANSFORMS") 1454 | cns_ik.name = cns_name 1455 | cns_ik.target = rig 1456 | cns_ik.subtarget = thigh_ik_name 1457 | cns_ik.influence = 1.0 1458 | 1459 | cns_name = "FK_follow" 1460 | cns_fk = thigh_pb.constraints.get(cns_name) 1461 | if cns_fk == None: 1462 | cns_fk = thigh_pb.constraints.new("COPY_TRANSFORMS") 1463 | cns_fk.name = cns_name 1464 | cns_fk.target = rig 1465 | cns_fk.subtarget = c_thigh_fk_name 1466 | cns_fk.influence = 0.0 1467 | 1468 | add_driver_to_prop(rig, 'pose.bones["'+thigh_name+'"].constraints["'+cns_name+'"].influence', 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1469 | 1470 | # Calf 1471 | calf_pb = get_pose_bone(calf_name) 1472 | 1473 | cns_name = "IK_follow" 1474 | cns_ik = calf_pb.constraints.get(cns_name) 1475 | if cns_ik == None: 1476 | cns_ik = calf_pb.constraints.new("COPY_TRANSFORMS") 1477 | cns_ik.name = cns_name 1478 | cns_ik.target = rig 1479 | cns_ik.subtarget = calf_ik_name 1480 | cns_ik.influence = 1.0 1481 | 1482 | cns_name = "FK_follow" 1483 | cns_fk = calf_pb.constraints.get(cns_name) 1484 | if cns_fk == None: 1485 | cns_fk = calf_pb.constraints.new("COPY_TRANSFORMS") 1486 | cns_fk.name = cns_name 1487 | cns_fk.target = rig 1488 | cns_fk.subtarget = c_calf_fk_name 1489 | cns_fk.influence = 0.0 1490 | 1491 | add_driver_to_prop(rig, 'pose.bones["'+calf_name+'"].constraints["'+cns_name+'"].influence', 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1492 | 1493 | # Foot 1494 | cns_name = "IK_follow" 1495 | cns_ik = foot_pb.constraints.get(cns_name) 1496 | if cns_ik == None: 1497 | cns_ik = foot_pb.constraints.new("COPY_TRANSFORMS") 1498 | cns_ik.name = cns_name 1499 | cns_ik.target = rig 1500 | cns_ik.subtarget = foot_ik_name 1501 | cns_ik.influence = 1.0 1502 | 1503 | cns_name = "FK_follow" 1504 | cns_fk = foot_pb.constraints.get(cns_name) 1505 | if cns_fk == None: 1506 | cns_fk = foot_pb.constraints.new("COPY_TRANSFORMS") 1507 | cns_fk.name = cns_name 1508 | cns_fk.target = rig 1509 | cns_fk.subtarget = foot_fk_name 1510 | cns_fk.influence = 0.0 1511 | 1512 | add_driver_to_prop(rig, 'pose.bones["'+foot_name+'"].constraints["'+cns_name+'"].influence', 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1513 | 1514 | 1515 | # Toe 1516 | toe_pb = get_pose_bone(toe_name) 1517 | 1518 | cns_name = "IK_Rot_follow" 1519 | cns_ik_rot = toe_pb.constraints.get(cns_name) 1520 | if cns_ik_rot == None: 1521 | cns_ik_rot = toe_pb.constraints.new("COPY_ROTATION") 1522 | cns_ik_rot.name = cns_name 1523 | cns_ik_rot.target = rig 1524 | cns_ik_rot.subtarget = c_toe_ik_name 1525 | cns_ik_rot.influence = 1.0 1526 | 1527 | cns_name = "IK_Scale_follow" 1528 | cns_ik_scale = toe_pb.constraints.get(cns_name) 1529 | if cns_ik_scale == None: 1530 | cns_ik_scale = toe_pb.constraints.new("COPY_SCALE") 1531 | cns_ik_scale.name = cns_name 1532 | cns_ik_scale.target = rig 1533 | cns_ik_scale.subtarget = c_toe_ik_name 1534 | cns_ik_scale.influence = 1.0 1535 | 1536 | cns_name_fk_rot = "FK_Rot_follow" 1537 | cns_fk_rot = toe_pb.constraints.get(cns_name_fk_rot) 1538 | if cns_fk_rot == None: 1539 | cns_fk_rot = toe_pb.constraints.new("COPY_ROTATION") 1540 | cns_fk_rot.name = cns_name_fk_rot 1541 | cns_fk_rot.target = rig 1542 | cns_fk_rot.subtarget = c_toe_fk_name 1543 | cns_fk_rot.influence = 1.0 1544 | 1545 | cns_name_fk_scale = "FK_Scale_follow" 1546 | cns_fk_scale = toe_pb.constraints.get(cns_name_fk_scale) 1547 | if cns_fk_scale == None: 1548 | cns_fk_scale = toe_pb.constraints.new("COPY_SCALE") 1549 | cns_fk_scale.name = cns_name_fk_scale 1550 | cns_fk_scale.target = rig 1551 | cns_fk_scale.subtarget = c_toe_fk_name 1552 | cns_fk_scale.influence = 1.0 1553 | 1554 | add_driver_to_prop(rig, 'pose.bones["'+toe_name+'"].constraints["'+cns_name_fk_rot+'"].influence', 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1555 | add_driver_to_prop(rig, 'pose.bones["'+toe_name+'"].constraints["'+cns_name_fk_scale+'"].influence', 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1556 | 1557 | 1558 | c_foot_01_pb = get_pose_bone(c_foot_01_name) 1559 | c_foot_roll_cursor_pb = get_pose_bone(c_foot_roll_cursor_name) 1560 | c_thigh_fk_pb = get_pose_bone(c_thigh_fk_name) 1561 | c_calf_fk_pb = get_pose_bone(c_calf_fk_name) 1562 | c_foot_fk_pb = get_pose_bone(c_foot_fk_name) 1563 | c_toe_ik_pb = get_pose_bone(c_toe_ik_name) 1564 | c_toe_fk_pb = get_pose_bone(c_toe_fk_name) 1565 | 1566 | 1567 | # Set transforms locks 1568 | lock_pbone_transform(c_foot_roll_cursor_pb, "location", [1]) 1569 | lock_pbone_transform(c_foot_roll_cursor_pb, "rotation", [0,1,2]) 1570 | lock_pbone_transform(c_foot_roll_cursor_pb, "scale", [0,1,2]) 1571 | 1572 | lock_pbone_transform(c_foot_01_pb, "location", [0,1,2]) 1573 | lock_pbone_transform(c_foot_01_pb, "rotation", [1,2]) 1574 | lock_pbone_transform(c_foot_01_pb, "scale", [0,1,2]) 1575 | 1576 | lock_pbone_transform(c_foot_fk_pb, "location", [0,1,2]) 1577 | 1578 | lock_pbone_transform(c_pole_ik_pb, "rotation", [0,1,2]) 1579 | lock_pbone_transform(c_pole_ik_pb, "scale", [0,1,2]) 1580 | 1581 | lock_pbone_transform(c_thigh_fk_pb, "location", [0,1,2]) 1582 | lock_pbone_transform(c_calf_fk_pb, "location", [0,1,2]) 1583 | 1584 | 1585 | c_pbones_list = [c_foot_ik_pb, c_pole_ik_pb, c_foot_01_pb, c_foot_roll_cursor_pb, c_thigh_fk_pb, c_calf_fk_pb, c_foot_fk_pb, c_toe_fk_pb, c_toe_ik_pb] 1586 | 1587 | # Set custom shapes 1588 | set_bone_custom_shape(c_thigh_fk_pb, "cs_thigh_fk") 1589 | set_bone_custom_shape(c_calf_fk_pb, "cs_calf_fk") 1590 | set_bone_custom_shape(c_foot_ik_pb, "cs_foot") 1591 | set_bone_custom_shape(c_foot_fk_pb, "cs_foot") 1592 | set_bone_custom_shape(c_pole_ik_pb, "cs_sphere_012") 1593 | set_bone_custom_shape(c_foot_roll_cursor_pb, "cs_foot_roll") 1594 | set_bone_custom_shape(c_foot_01_pb, "cs_foot_01") 1595 | set_bone_custom_shape(c_toe_fk_pb, "cs_toe") 1596 | set_bone_custom_shape(c_toe_ik_pb, "cs_toe") 1597 | 1598 | # set custom shape drivers 1599 | ik_controls_names = [c_foot_ik_name, c_foot_01_name, c_toe_ik_name, c_foot_roll_cursor_name, c_pole_ik_name] 1600 | 1601 | arr_ids = [-1] 1602 | if blender_version._float >= 300: 1603 | arr_ids = [0, 1, 2] 1604 | 1605 | for n in ik_controls_names: 1606 | dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() 1607 | tar_dp = 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]' 1608 | for arr_id in arr_ids: 1609 | add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="1-var") 1610 | 1611 | fk_controls_names = [c_foot_fk_name, c_thigh_fk_name, c_calf_fk_name, c_toe_fk_name] 1612 | 1613 | for n in fk_controls_names: 1614 | dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() 1615 | tar_dp = 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]' 1616 | for arr_id in arr_ids: 1617 | add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="var") 1618 | 1619 | 1620 | for pb in c_pbones_list: 1621 | # set rotation euler 1622 | pb.rotation_mode = "XYZ" 1623 | # set color group 1624 | set_bone_color_group(rig, pb, "body"+_side.lower()) 1625 | 1626 | 1627 | def add_arm(side): 1628 | print(" Add Arm", side) 1629 | _side = "_"+side 1630 | shoulder_name = get_mix_name(side+arm_names["shoulder"], use_name_prefix) 1631 | arm_name = get_mix_name(side+arm_names["arm"], use_name_prefix) 1632 | forearm_name = get_mix_name(side+arm_names["forearm"], use_name_prefix) 1633 | hand_name = get_mix_name(side+arm_names["hand"], use_name_prefix) 1634 | 1635 | 1636 | # -- Edit -- 1637 | bpy.ops.object.mode_set(mode='EDIT') 1638 | 1639 | 1640 | shoulder = get_edit_bone(shoulder_name) 1641 | arm = get_edit_bone(arm_name) 1642 | forearm = get_edit_bone(forearm_name) 1643 | hand = get_edit_bone(hand_name) 1644 | 1645 | 1646 | if not shoulder or not arm or not forearm or not hand: 1647 | print(" Arm bones are missing, skip arm: "+side) 1648 | return 1649 | 1650 | 1651 | # Create bones 1652 | # Fingers 1653 | fingers_names = [] 1654 | c_fingers_names = [] 1655 | fingers = [] 1656 | finger_leaves = [] 1657 | 1658 | for fname in fingers_type: 1659 | for i in range(1, 4): 1660 | finger_name = get_mix_name(side+"Hand"+fname+str(i), use_name_prefix) 1661 | finger = get_edit_bone(finger_name) 1662 | if finger == None: 1663 | continue 1664 | 1665 | fingers_names.append(finger_name) 1666 | fingers.append(finger) 1667 | c_finger_name = c_prefix+fname+str(i)+_side 1668 | c_fingers_names.append(c_finger_name) 1669 | c_finger = create_edit_bone(c_finger_name) 1670 | copy_bone_transforms(finger, c_finger) 1671 | # ~ set_bone_layer(c_finger, 0) 1672 | set_bone_collection(rig, c_finger, coll_ctrl_name) 1673 | c_finger["mixamo_ctrl"] = 1# tag as controller bone 1674 | 1675 | if i == 1: 1676 | c_finger.parent = hand 1677 | else: 1678 | prev_finger_name = c_prefix+fname+str(i-1)+_side 1679 | prev_finger = get_edit_bone(prev_finger_name) 1680 | c_finger.parent = prev_finger 1681 | 1682 | # fingers "leaves"/tip bones 1683 | for fname in fingers_type: 1684 | finger_name = get_mix_name(side+"Hand"+fname+"4", use_name_prefix) 1685 | finger_leaf = get_edit_bone(finger_name) 1686 | finger_leaves.append(finger_leaf) 1687 | 1688 | # Set Mixamo bones in layer 1689 | for b in [shoulder, arm, forearm, hand] + fingers + finger_leaves: 1690 | # ~ set_bone_layer(b, layer_mix_idx) 1691 | set_bone_collection(rig, b, coll_mix_name) 1692 | 1693 | # Shoulder Ctrl 1694 | c_shoulder_name = c_prefix+arm_rig_names["shoulder"]+_side 1695 | c_shoulder = create_edit_bone(c_shoulder_name) 1696 | copy_bone_transforms(shoulder, c_shoulder) 1697 | c_shoulder.parent = get_edit_bone(c_prefix+spine_rig_names["spine3"]) 1698 | # ~ set_bone_layer(c_shoulder, layer_ctrl_idx) 1699 | set_bone_collection(rig, c_shoulder, coll_ctrl_name) 1700 | c_shoulder["mixamo_ctrl"] = 1# tag as controller bone 1701 | 1702 | # Arm IK 1703 | arm_ik_name = arm_rig_names["arm_ik"]+_side 1704 | arm_ik = create_edit_bone(arm_ik_name) 1705 | copy_bone_transforms(arm, arm_ik) 1706 | 1707 | # correct straight arms angle, need minimum 0.1 degrees for IK constraints to work 1708 | angle_min = 0.1 1709 | 1710 | def get_arm_angle(): 1711 | #return degrees(arm.y_axis.angle(forearm.y_axis)) 1712 | vec1 = forearm.head - arm.head 1713 | vec2 = hand.head - forearm.head 1714 | return degrees(vec1.angle(vec2)) 1715 | 1716 | arm_angle = get_arm_angle() 1717 | 1718 | if arm_angle < angle_min: 1719 | print(" ! Straight arm bones, angle = "+str(arm_angle)) 1720 | 1721 | max_iter = 10000 1722 | i = 0 1723 | 1724 | while arm_angle < angle_min and i < max_iter: 1725 | 1726 | dir = ((arm.x_axis + forearm.x_axis)*0.5).normalized() 1727 | if side == "Right": 1728 | dir *= -1 1729 | 1730 | forearm.head += dir * (forearm.tail-forearm.head).magnitude * 0.0001 1731 | arm_angle = get_arm_angle() 1732 | i += 1 1733 | 1734 | print(" corrected arm angle: "+str(arm_angle)) 1735 | 1736 | # auto-align knee position with global Y axis to ensure IK pole vector is physically correct 1737 | arm_axis = forearm.tail - arm.head 1738 | arm_midpoint = (arm.head + forearm.tail) * 0.5 1739 | #cur_vec = forearm.head - arm_midpoint 1740 | #cur_vec[0] = 0.0 1741 | #global_y_vec = Vector((0, 1, 0)) 1742 | 1743 | dir = forearm.head - arm_midpoint 1744 | cur_vec = project_vector_onto_plane(dir, arm_axis) 1745 | global_y_vec = project_vector_onto_plane(Vector((0, 1, 0)), arm_axis) 1746 | signed_cur_angle = signed_angle(cur_vec, global_y_vec, arm_axis) 1747 | print(" IK correc angle:", degrees(signed_cur_angle)) 1748 | 1749 | # rotate 1750 | rotated_point = rotate_point(forearm.head.copy(), -signed_cur_angle, arm_midpoint, arm_axis) 1751 | """ 1752 | rot_mat = Matrix.Rotation(-signed_cur_angle, 4, arm_axis.normalized()) 1753 | # rotate in world origin space 1754 | offset_vec = -arm_midpoint 1755 | offset_elbow = forearm.head + offset_vec 1756 | # rotate 1757 | rotated_point = rot_mat @ offset_elbow 1758 | # bring back to original space 1759 | rotated_point = rotated_point -offset_vec 1760 | """ 1761 | 1762 | # (check) 1763 | dir = rotated_point - arm_midpoint 1764 | cur_vec = project_vector_onto_plane(dir, arm_axis) 1765 | signed_cur_angle = signed_angle(cur_vec, global_y_vec, arm_axis) 1766 | print(" IK corrected angle:", degrees(signed_cur_angle)) 1767 | 1768 | arm_ik.tail = rotated_point 1769 | 1770 | arm_ik.parent = c_shoulder 1771 | # ~ set_bone_layer(arm_ik, layer_intern_idx) 1772 | set_bone_collection(rig, arm_ik, coll_intern_name) 1773 | 1774 | # Arm FK Ctrl 1775 | c_arm_fk_name = c_prefix+arm_rig_names["arm_fk"]+_side 1776 | c_arm_fk = create_edit_bone(c_arm_fk_name) 1777 | c_arm_fk.parent = get_edit_bone(c_prefix+spine_rig_names["spine3"]) 1778 | copy_bone_transforms(arm_ik, c_arm_fk) 1779 | # ~ set_bone_layer(c_arm_fk, layer_ctrl_idx) 1780 | set_bone_collection(rig, c_arm_fk, coll_ctrl_name) 1781 | c_arm_fk["mixamo_ctrl"] = 1# tag as controller bone 1782 | 1783 | 1784 | # ForeArm IK 1785 | forearm_ik_name = arm_rig_names["forearm_ik"]+_side 1786 | forearm_ik = create_edit_bone(forearm_ik_name) 1787 | copy_bone_transforms(forearm, forearm_ik) 1788 | forearm_ik.head = arm_ik.tail.copy() 1789 | forearm_ik.tail = hand.head.copy() 1790 | forearm_ik.parent = arm_ik 1791 | # ~ set_bone_layer(forearm_ik, layer_intern_idx) 1792 | set_bone_collection(rig, forearm_ik, coll_intern_name) 1793 | 1794 | # align arm and forearm IK roll 1795 | # align forearm_ik local Z 1796 | align_bone_x_axis(forearm_ik, (forearm_ik.head-arm_midpoint)) 1797 | # align arm_ik on forearm_ik 1798 | align_bone_x_axis(arm_ik, forearm_ik.x_axis) 1799 | # copy arm_ik to c_arm_fk 1800 | copy_bone_transforms(arm_ik, c_arm_fk) 1801 | 1802 | if side == "Right": 1803 | forearm_ik.roll += radians(180) 1804 | arm_ik.roll += radians(180) 1805 | c_arm_fk.roll += radians(180) 1806 | 1807 | # Forearm FK Ctrl 1808 | c_forearm_fk_name = c_prefix+arm_rig_names["forearm_fk"]+_side 1809 | c_forearm_fk = create_edit_bone(c_forearm_fk_name) 1810 | copy_bone_transforms(forearm_ik, c_forearm_fk) 1811 | c_forearm_fk.parent = c_arm_fk 1812 | # ~ set_bone_layer(c_forearm_fk, layer_ctrl_idx) 1813 | set_bone_collection(rig, c_forearm_fk, coll_ctrl_name) 1814 | c_forearm_fk["mixamo_ctrl"] = 1# tag as controller bone 1815 | 1816 | 1817 | # Pole IK Ctrl 1818 | c_pole_ik_name = c_prefix+arm_rig_names["pole_ik"]+_side 1819 | c_pole_ik = create_edit_bone(c_pole_ik_name) 1820 | # ~ set_bone_layer(c_pole_ik, layer_ctrl_idx) 1821 | set_bone_collection(rig, c_pole_ik, coll_ctrl_name) 1822 | c_pole_ik["mixamo_ctrl"] = 1# tag as controller bone 1823 | 1824 | arm_midpoint = (arm_ik.head + forearm_ik.tail) * 0.5 1825 | 1826 | plane_normal = (arm_ik.head - forearm_ik.tail) 1827 | prepole_dir = forearm_ik.head - arm_midpoint 1828 | pole_pos = forearm_ik.head + prepole_dir.normalized() 1829 | pole_pos = project_point_onto_plane(pole_pos, forearm_ik.head, plane_normal) 1830 | pole_pos = forearm_ik.head + ((pole_pos - forearm_ik.head).normalized() * (forearm_ik.head - arm.head).magnitude * 1.0) 1831 | 1832 | c_pole_ik.head = pole_pos 1833 | c_pole_ik.tail = [c_pole_ik.head[0], c_pole_ik.head[1], c_pole_ik.head[2] + (0.165 * arm_ik.length * 4)] 1834 | 1835 | ik_pole_angle = get_pole_angle(arm_ik, forearm_ik, c_pole_ik.head) 1836 | 1837 | # Hand IK Ctrl 1838 | c_hand_ik_name = c_prefix + arm_rig_names["hand_ik"]+_side 1839 | c_hand_ik = create_edit_bone(c_hand_ik_name) 1840 | # ~ set_bone_layer(c_hand_ik, layer_ctrl_idx) 1841 | set_bone_collection(rig, c_hand_ik, coll_ctrl_name) 1842 | copy_bone_transforms(hand, c_hand_ik) 1843 | c_hand_ik["mixamo_ctrl"] = 1# tag as controller bone 1844 | 1845 | # Hand FK Ctrl 1846 | c_hand_fk_name = c_prefix+arm_rig_names["hand_fk"]+_side 1847 | c_hand_fk = create_edit_bone(c_hand_fk_name) 1848 | copy_bone_transforms(hand, c_hand_fk) 1849 | c_hand_fk.parent = c_forearm_fk 1850 | # ~ set_bone_layer(c_hand_fk, layer_ctrl_idx) 1851 | set_bone_collection(rig, c_hand_fk, coll_ctrl_name) 1852 | c_hand_fk["mixamo_ctrl"] = 1# tag as controller bone 1853 | 1854 | # ---- Pose ---- 1855 | bpy.ops.object.mode_set(mode='POSE') 1856 | 1857 | 1858 | # Add constraints to control/mechanic bones 1859 | c_shoulder_pb = get_pose_bone(c_shoulder_name) 1860 | shoulder_pb = get_pose_bone(shoulder_name) 1861 | c_arm_fk_pb = get_pose_bone(c_arm_fk_name) 1862 | forearm_ik_pb = get_pose_bone(forearm_ik_name) 1863 | c_pole_ik_pb = get_pose_bone(c_pole_ik_name) 1864 | c_hand_ik_pb = get_pose_bone(c_hand_ik_name) 1865 | 1866 | 1867 | # Arm FK Ctrl 1868 | cns_name = "Copy Location" 1869 | cns = c_arm_fk_pb.constraints.get(cns_name) 1870 | if cns == None: 1871 | cns = c_arm_fk_pb.constraints.new("COPY_LOCATION") 1872 | cns.name = cns_name 1873 | cns.head_tail = 1.0 1874 | cns.target = rig 1875 | cns.subtarget = c_shoulder_name 1876 | 1877 | # Forearm IK 1878 | cns_name = "IK" 1879 | ik_cns = forearm_ik_pb.constraints.get(cns_name) 1880 | if ik_cns == None: 1881 | ik_cns = forearm_ik_pb.constraints.new("IK") 1882 | ik_cns.name = cns_name 1883 | ik_cns.target = rig 1884 | ik_cns.subtarget = c_hand_ik_name 1885 | ik_cns.pole_target = rig 1886 | ik_cns.pole_subtarget = c_pole_ik_name 1887 | ik_cns.pole_angle = 0.0 1888 | if side == "Right": 1889 | ik_cns.pole_angle = radians(180) 1890 | ik_cns.chain_count = 2 1891 | ik_cns.use_tail = True 1892 | ik_cns.use_stretch = False 1893 | 1894 | forearm_ik_pb.lock_ik_y = True 1895 | forearm_ik_pb.lock_ik_x = True 1896 | 1897 | 1898 | # Pole IK Ctrl 1899 | cns_name = "Child Of" 1900 | cns = c_pole_ik_pb.constraints.get(cns_name) 1901 | if cns == None: 1902 | cns = c_pole_ik_pb.constraints.new("CHILD_OF") 1903 | cns.name = cns_name 1904 | cns.target = rig 1905 | cns.subtarget = c_prefix+spine_rig_names["pelvis"] 1906 | 1907 | 1908 | # Hand IK Ctrl 1909 | cns_name = "Child Of" 1910 | cns = c_hand_ik_pb.constraints.get(cns_name) 1911 | if cns == None: 1912 | cns = c_hand_ik_pb.constraints.new("CHILD_OF") 1913 | cns.name = cns_name 1914 | cns.target = rig 1915 | cns.subtarget = c_master_name 1916 | 1917 | 1918 | # Add constraints to Mixamo bones 1919 | hand_pb = get_pose_bone(hand_name) 1920 | 1921 | # Fingers 1922 | for i, fname in enumerate(c_fingers_names): 1923 | c_finger_pb = get_pose_bone(fname) 1924 | finger_pb = get_pose_bone(fingers_names[i]) 1925 | add_copy_transf(finger_pb, rig, c_finger_pb.name) 1926 | 1927 | 1928 | # Shoulder 1929 | add_copy_transf(shoulder_pb, rig, c_shoulder_pb.name) 1930 | 1931 | 1932 | # IK-FK switch property 1933 | if "ik_fk_switch" not in c_hand_ik_pb.keys(): 1934 | create_custom_prop(node=c_hand_ik_pb, prop_name="ik_fk_switch", prop_val=0.0, prop_min=0.0, prop_max=1.0, prop_description="IK-FK switch value") 1935 | 1936 | c_hand_ik_pb["ik_fk_switch"] = 0.0 if self.ik_arms else 1.0 1937 | 1938 | 1939 | # Arm 1940 | arm_pb = get_pose_bone(arm_name) 1941 | 1942 | cns_ik_name = "IK_follow" 1943 | cns_ik = arm_pb.constraints.get(cns_ik_name) 1944 | if cns_ik == None: 1945 | cns_ik = arm_pb.constraints.new("COPY_TRANSFORMS") 1946 | cns_ik.name = cns_ik_name 1947 | cns_ik.target = rig 1948 | cns_ik.subtarget = arm_ik_name 1949 | cns_ik.influence = 1.0 1950 | 1951 | cns_fk_name = "FK_Follow" 1952 | cns_fk = arm_pb.constraints.get(cns_fk_name) 1953 | if cns_fk == None: 1954 | cns_fk = arm_pb.constraints.new("COPY_TRANSFORMS") 1955 | cns_fk.name = cns_fk_name 1956 | cns_fk.target = rig 1957 | cns_fk.subtarget = c_arm_fk_name 1958 | cns_fk.influence = 0.0 1959 | 1960 | add_driver_to_prop(rig, 'pose.bones["'+arm_name+'"].constraints["'+cns_fk_name+'"].influence', 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1961 | 1962 | 1963 | # ForeArm 1964 | forearm_pb = get_pose_bone(forearm_name) 1965 | 1966 | cns_ik_name = "IK_follow" 1967 | cns_ik = forearm_pb.constraints.get(cns_ik_name) 1968 | if cns_ik == None: 1969 | cns_ik = forearm_pb.constraints.new("COPY_TRANSFORMS") 1970 | cns_ik.name = cns_ik_name 1971 | cns_ik.target = rig 1972 | cns_ik.subtarget = forearm_ik_name 1973 | cns_ik.influence = 1.0 1974 | 1975 | cns_fk_name = "FK_Follow" 1976 | cns_fk = forearm_pb.constraints.get(cns_fk_name) 1977 | if cns_fk == None: 1978 | cns_fk = forearm_pb.constraints.new("COPY_TRANSFORMS") 1979 | cns_fk.name = cns_fk_name 1980 | cns_fk.target = rig 1981 | cns_fk.subtarget = c_forearm_fk_name 1982 | cns_fk.influence = 0.0 1983 | 1984 | add_driver_to_prop(rig, 'pose.bones["'+forearm_name+'"].constraints["'+cns_fk_name+'"].influence', 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 1985 | 1986 | c_arm_fk_pb = get_pose_bone(c_arm_fk_name) 1987 | c_forearm_fk_pb = get_pose_bone(c_forearm_fk_name) 1988 | 1989 | lock_pbone_transform(c_forearm_fk_pb, "location", [0,1,2]) 1990 | 1991 | # Hand 1992 | cns_ik_name = "IK_follow" 1993 | cns_ik = hand_pb.constraints.get(cns_ik_name) 1994 | if cns_ik == None: 1995 | cns_ik = hand_pb.constraints.new("COPY_ROTATION") 1996 | cns_ik.name = cns_ik_name 1997 | cns_ik.target = rig 1998 | cns_ik.subtarget = c_hand_ik_name 1999 | cns_ik.influence = 1.0 2000 | 2001 | cns_fk_name = "FK_Follow" 2002 | cns_fk = hand_pb.constraints.get(cns_fk_name) 2003 | if cns_fk == None: 2004 | cns_fk = hand_pb.constraints.new("COPY_ROTATION") 2005 | cns_fk.name = cns_fk_name 2006 | cns_fk.target = rig 2007 | cns_fk.subtarget = c_hand_fk_name 2008 | cns_fk.influence = 0.0 2009 | 2010 | add_driver_to_prop(rig, 'pose.bones["'+hand_name+'"].constraints["'+cns_fk_name+'"].influence', 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]', array_idx=-1, exp="var") 2011 | 2012 | c_hand_fk_pb = get_pose_bone(c_hand_fk_name) 2013 | lock_pbone_transform(c_hand_fk_pb, "location", [0,1,2]) 2014 | 2015 | # Set custom shapes 2016 | c_hand_ik_pb = get_pose_bone(c_hand_ik_name) 2017 | set_bone_custom_shape(c_shoulder_pb, "cs_shoulder_"+side.lower()) 2018 | set_bone_custom_shape(c_arm_fk_pb, "cs_arm_fk") 2019 | set_bone_custom_shape(c_forearm_fk_pb, "cs_forearm_fk") 2020 | set_bone_custom_shape(c_pole_ik_pb, "cs_sphere_012") 2021 | set_bone_custom_shape(c_hand_fk_pb, "cs_hand") 2022 | set_bone_custom_shape(c_hand_ik_pb, "cs_hand") 2023 | 2024 | c_fingers_pb = [] 2025 | 2026 | for fname in c_fingers_names: 2027 | finger_pb = get_pose_bone(fname) 2028 | c_fingers_pb.append(finger_pb) 2029 | set_bone_custom_shape(finger_pb, "cs_circle_025") 2030 | 2031 | c_pbones_list = [c_shoulder_pb, c_arm_fk_pb, c_forearm_fk_pb, c_pole_ik_pb, c_hand_fk_pb, c_hand_ik_pb] + c_fingers_pb 2032 | 2033 | 2034 | # set custom shape drivers 2035 | ik_controls_names = [c_pole_ik_name, c_hand_ik_name] 2036 | 2037 | arr_ids = [-1] 2038 | if blender_version._float >= 300: 2039 | arr_ids = [0, 1, 2] 2040 | 2041 | for n in ik_controls_names: 2042 | dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() 2043 | tar_dp = 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]' 2044 | for arr_id in arr_ids: 2045 | add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="1-var") 2046 | 2047 | fk_controls_names = [c_arm_fk_name, c_forearm_fk_name, c_hand_fk_name] 2048 | 2049 | for n in fk_controls_names: 2050 | dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() 2051 | tar_dp = 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]' 2052 | for arr_id in arr_ids: 2053 | add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="var") 2054 | 2055 | 2056 | for pb in c_pbones_list: 2057 | # set rotation euler 2058 | pb.rotation_mode = "XYZ" 2059 | # set color group 2060 | set_bone_color_group(rig, pb, "body"+_side.lower()) 2061 | 2062 | 2063 | add_master() 2064 | add_spine() 2065 | add_head() 2066 | add_arm("Left") 2067 | add_arm("Right") 2068 | add_leg("Left") 2069 | add_leg("Right") 2070 | 2071 | # tag the armature with a custom prop to specify the control rig is built 2072 | rig.data["mr_control_rig"] = True 2073 | 2074 | 2075 | def _zero_out(): 2076 | print("\nZeroing out...") 2077 | scn = bpy.context.scene 2078 | arm = bpy.data.objects.get(bpy.context.active_object.name) 2079 | 2080 | print(" Clear anim") 2081 | # Clear animation data 2082 | action = None 2083 | if arm.animation_data: 2084 | if arm.animation_data.action: 2085 | action = arm.animation_data.action 2086 | 2087 | if action: 2088 | while len(action.fcurves): 2089 | action.fcurves.remove(action.fcurves[0]) 2090 | 2091 | print(" Clear pose") 2092 | # Reset pose 2093 | bpy.ops.object.mode_set(mode='POSE') 2094 | 2095 | for b in arm.pose.bones: 2096 | b.location = [0,0,0] 2097 | b.rotation_euler = [0,0,0] 2098 | b.rotation_quaternion = [1,0,0,0] 2099 | b.scale = [1,1,1] 2100 | 2101 | print("Zeroed out.") 2102 | 2103 | 2104 | def _bake_anim(self): 2105 | scn = bpy.context.scene 2106 | 2107 | # get min-max frame range 2108 | rig = bpy.context.active_object 2109 | 2110 | if rig.animation_data is None: 2111 | print("No animation data, exit bake") 2112 | return 2113 | 2114 | if rig.animation_data.nla_tracks is None: 2115 | print("No NLA tracks found, exit bake") 2116 | return 2117 | 2118 | tracks = rig.animation_data.nla_tracks 2119 | 2120 | fs = None 2121 | fe = None 2122 | 2123 | # from NLA tracks 2124 | for track in tracks: 2125 | for strip in track.strips: 2126 | if fs is None: 2127 | fs = strip.frame_start 2128 | if fe is None: 2129 | fe = strip.frame_end 2130 | 2131 | if strip.frame_start < fs: 2132 | fs = strip.frame_start 2133 | if strip.frame_end > fe: 2134 | fe = strip.frame_end 2135 | 2136 | if fs is None or fe is None: 2137 | print("No NLA tracks found, exit") 2138 | return 2139 | 2140 | # get active action frame range 2141 | act = rig.animation_data.action 2142 | if act is not None: 2143 | if act.frame_range[0] < fs: 2144 | fs = act.frame_range[0] 2145 | if act.frame_range[1] > fe: 2146 | fe = act.frame_range[1] 2147 | 2148 | # select only controllers bones 2149 | bpy.ops.object.mode_set(mode='POSE') 2150 | bpy.ops.pose.select_all(action='DESELECT') 2151 | 2152 | found_ctrl = False 2153 | for pbone in rig.pose.bones: 2154 | if "mixamo_ctrl" in pbone.bone.keys(): 2155 | rig.data.bones.active = pbone.bone 2156 | pbone.bone.select = True 2157 | found_ctrl = True 2158 | 2159 | if not found_ctrl:# backward compatibility, use layer 0 instead 2160 | print("Ctrl bones not tagged, search in layer 0 instead...") 2161 | c0 = rig.data.collections.get("CTRL") 2162 | if c0 is not None: 2163 | for b in c0.bones: 2164 | pb = rig.pose.bones.get(b.name) 2165 | if pb is not None: 2166 | rig.data.bones.active = pb.bone 2167 | pb.bone.select = True 2168 | 2169 | # ~ for pbone in rig.pose.bones: 2170 | # ~ if pbone.bone.layers[0]: 2171 | # ~ rig.data.bones.active = pbone.bone 2172 | # ~ pbone.bone.select = True 2173 | 2174 | fs, fe = int(fs), int(fe) 2175 | 2176 | scn.frame_set(fs) 2177 | bpy.context.view_layer.update() 2178 | 2179 | # bake NLA strips 2180 | print("Baking, frame start:", fs, ",frame end", fe) 2181 | bpy.ops.nla.bake(frame_start=fs, frame_end=fe, step=1, only_selected=True, visual_keying=False, 2182 | clear_constraints=False, clear_parents=False, use_current_action=False, 2183 | clean_curves=False, bake_types={'POSE'}) 2184 | 2185 | # remove tracks 2186 | while len(tracks): 2187 | rig.animation_data.nla_tracks.remove(tracks[0]) 2188 | 2189 | 2190 | def redefine_source_rest_pose(src_arm, tar_arm): 2191 | print(" Redefining source rest pose...") 2192 | 2193 | scn = bpy.context.scene 2194 | 2195 | src_arm_loc = src_arm.location.copy() 2196 | src_arm.location = [0,0,0] 2197 | fr_range = src_arm.animation_data.action.frame_range 2198 | fr_start = int(fr_range[0]) 2199 | fr_end = int(fr_range[1]) 2200 | 2201 | # duplicate source armature 2202 | bpy.ops.object.mode_set(mode='OBJECT') 2203 | bpy.ops.object.select_all(action='DESELECT') 2204 | set_active_object(src_arm.name) 2205 | bpy.ops.object.mode_set(mode='OBJECT') 2206 | duplicate_object() 2207 | src_arm_dupli = get_object(bpy.context.active_object.name) 2208 | src_arm_dupli["mix_to_del"] = True 2209 | 2210 | 2211 | """ 2212 | # Store bone matrices 2213 | bpy.ops.object.mode_set(mode='OBJECT') 2214 | bpy.ops.object.select_all(action='DESELECT') 2215 | set_active_object(src_arm.name) 2216 | bpy.ops.object.mode_set(mode='POSE') 2217 | 2218 | bones_data = [] 2219 | 2220 | for f in range(fr_start, fr_end+1): 2221 | print("Frame", f) 2222 | scn.frame_set(f) 2223 | bpy.context.view_layer.update() 2224 | 2225 | bones_matrices = {} 2226 | 2227 | for pbone in src_arm.pose.bones: 2228 | bones_matrices[pbone.name] = pbone.matrix.copy() 2229 | #bones_matrices[pbone.name] = src_arm.convert_space(pose_bone=pbone, matrix=pbone.matrix, from_space="POSE", to_space="LOCAL") 2230 | 2231 | 2232 | bones_data.append((f, bones_matrices)) 2233 | """ 2234 | 2235 | # Store target bones rest transforms 2236 | bpy.ops.object.mode_set(mode='OBJECT') 2237 | bpy.ops.object.select_all(action='DESELECT') 2238 | set_active_object(tar_arm.name) 2239 | bpy.ops.object.mode_set(mode='EDIT') 2240 | 2241 | rest_bones = {} 2242 | 2243 | for ebone in tar_arm.data.edit_bones: 2244 | rest_bones[ebone.name] = ebone.head.copy(), ebone.tail.copy(), vec_roll_to_mat3(ebone.y_axis, ebone.roll) 2245 | 2246 | # Apply source bones rest transforms 2247 | print(" Set rest pose...") 2248 | bpy.ops.object.mode_set(mode='OBJECT') 2249 | bpy.ops.object.select_all(action='DESELECT') 2250 | set_active_object(src_arm.name) 2251 | bpy.ops.object.mode_set(mode='EDIT') 2252 | 2253 | for bname in rest_bones: 2254 | ebone = get_edit_bone(bname) 2255 | 2256 | if ebone == None: 2257 | #print("Warning, bone not found on source armature:", bname) 2258 | continue 2259 | 2260 | head, tail, mat3 = rest_bones[bname] 2261 | ebone.head, ebone.tail, ebone.roll = src_arm.matrix_world.inverted() @ head, src_arm.matrix_world.inverted() @ tail, mat3_to_vec_roll(src_arm.matrix_world.inverted().to_3x3() @ mat3) 2262 | 2263 | 2264 | # Add constraints 2265 | bpy.ops.object.mode_set(mode='POSE') 2266 | 2267 | for pb in src_arm.pose.bones: 2268 | cns = pb.constraints.new("COPY_TRANSFORMS") 2269 | cns.name = "temp" 2270 | cns.target = src_arm_dupli 2271 | cns.subtarget = pb.name 2272 | 2273 | # Restore animation 2274 | print("Restore animation...") 2275 | bake_anim(frame_start=fr_start, frame_end=fr_end, only_selected=False, bake_bones=True, bake_object=False) 2276 | 2277 | # Restore location 2278 | src_arm.location = src_arm_loc 2279 | 2280 | # Delete temp data 2281 | # constraints 2282 | for pb in src_arm.pose.bones: 2283 | if len(pb.constraints): 2284 | cns = pb.constraints.get("temp") 2285 | if cns: 2286 | pb.constraints.remove(cns) 2287 | 2288 | # src_arm_dupli 2289 | delete_object(src_arm_dupli) 2290 | 2291 | print(" Source armature rest pose redefined.") 2292 | 2293 | 2294 | def _import_anim(src_arm, tar_arm, import_only=False): 2295 | print("\nImporting animation...") 2296 | scn = bpy.context.scene 2297 | 2298 | if src_arm.animation_data == None: 2299 | print(" No action found on the source armature") 2300 | return 2301 | 2302 | if src_arm.animation_data.action == None: 2303 | print(" No action found on the source armature") 2304 | return 2305 | 2306 | if len(src_arm.animation_data.action.fcurves) == 0: 2307 | print(" No keyframes to import") 2308 | return 2309 | 2310 | use_name_prefix = True 2311 | 2312 | # Redefine source armature rest pose if importing only animation, since 2313 | # Mixamo Fbx may have different rest pose when the Fbx file contains only animation data 2314 | if import_only: 2315 | redefine_source_rest_pose(src_arm, tar_arm) 2316 | 2317 | bpy.ops.object.mode_set(mode='OBJECT') 2318 | bpy.ops.object.select_all(action='DESELECT') 2319 | set_active_object(tar_arm.name) 2320 | bpy.ops.object.mode_set(mode='POSE') 2321 | 2322 | hand_left_name = get_mix_name("LeftHand", use_name_prefix) 2323 | hand_right_name = get_mix_name("RightHand", use_name_prefix) 2324 | foot_left_name = get_mix_name("LeftFoot", use_name_prefix) 2325 | foot_right_name = get_mix_name("RightFoot", use_name_prefix) 2326 | 2327 | hand_left_pb = get_pose_bone(hand_left_name) 2328 | c_hand_ik_left_pb = get_pose_bone(c_prefix + arm_rig_names["hand_ik"]+"_Left") 2329 | hand_right_pb = get_pose_bone(hand_right_name) 2330 | c_hand_ik_right_pb = get_pose_bone(c_prefix + arm_rig_names["hand_ik"]+"_Right") 2331 | foot_left_pb = get_pose_bone(foot_left_name) 2332 | c_foot_ik_left_pb = get_pose_bone(c_prefix + leg_rig_names["foot_ik"]+"_Left") 2333 | foot_right_pb = get_pose_bone(foot_right_name) 2334 | c_foot_ik_right_pb = get_pose_bone(c_prefix + leg_rig_names["foot_ik"]+"_Right") 2335 | 2336 | arm_left_kinematic = "IK" if c_hand_ik_left_pb["ik_fk_switch"] < 0.5 else "FK" 2337 | arm_right_kinematic = "IK" if c_hand_ik_right_pb["ik_fk_switch"] < 0.5 else "FK" 2338 | leg_left_kinematic = "IK" if c_foot_ik_left_pb["ik_fk_switch"] < 0.5 else "FK" 2339 | leg_right_kinematic = "IK" if c_foot_ik_right_pb["ik_fk_switch"] < 0.5 else "FK" 2340 | 2341 | 2342 | # Set bones mapping for retargetting 2343 | bones_map = {} 2344 | 2345 | bones_map[get_mix_name("Hips", use_name_prefix)] = c_prefix+"Hips" 2346 | bones_map[get_mix_name("Spine", use_name_prefix)] = c_prefix+"Spine" 2347 | bones_map[get_mix_name("Spine1", use_name_prefix)] = c_prefix+"Spine1" 2348 | bones_map[get_mix_name("Spine2", use_name_prefix)] = c_prefix+"Spine2" 2349 | bones_map[get_mix_name("Neck", use_name_prefix)] = c_prefix+"Neck" 2350 | bones_map[get_mix_name("Head", use_name_prefix)] = c_prefix+"Head" 2351 | bones_map[get_mix_name("LeftShoulder", use_name_prefix)] = c_prefix+"Shoulder_Left" 2352 | bones_map[get_mix_name("RightShoulder", use_name_prefix)] = c_prefix+"Shoulder_Right" 2353 | 2354 | # Arm 2355 | if arm_left_kinematic == "FK": 2356 | bones_map[get_mix_name("LeftArm", use_name_prefix)] = c_prefix+"Arm_FK_Left" 2357 | bones_map[get_mix_name("LeftForeArm", use_name_prefix)] = c_prefix+"ForeArm_FK_Left" 2358 | bones_map[get_mix_name("LeftHand", use_name_prefix)] = c_prefix+"Hand_FK_Left" 2359 | elif arm_left_kinematic == "IK": 2360 | bones_map[c_prefix+"Hand_IK_Left"] = c_prefix+"Hand_IK_Left" 2361 | 2362 | if arm_right_kinematic == "FK": 2363 | bones_map[get_mix_name("RightArm", use_name_prefix)] = c_prefix+"Arm_FK_Right" 2364 | bones_map[get_mix_name("RightForeArm", use_name_prefix)] = c_prefix+"ForeArm_FK_Right" 2365 | bones_map[get_mix_name("RightHand", use_name_prefix)] = c_prefix+"Hand_FK_Right" 2366 | elif arm_right_kinematic == "IK": 2367 | bones_map[c_prefix+"Hand_IK_Right"] = c_prefix+"Hand_IK_Right" 2368 | 2369 | # Fingers 2370 | bones_map[get_mix_name("LeftHandThumb1", use_name_prefix)] = c_prefix+"Thumb1_Left" 2371 | bones_map[get_mix_name("LeftHandThumb2", use_name_prefix)] = c_prefix+"Thumb2_Left" 2372 | bones_map[get_mix_name("LeftHandThumb3", use_name_prefix)] = c_prefix+"Thumb3_Left" 2373 | bones_map[get_mix_name("LeftHandIndex1", use_name_prefix)] = c_prefix+"Index1_Left" 2374 | bones_map[get_mix_name("LeftHandIndex2", use_name_prefix)] = c_prefix+"Index2_Left" 2375 | bones_map[get_mix_name("LeftHandIndex3", use_name_prefix)] = c_prefix+"Index3_Left" 2376 | bones_map[get_mix_name("LeftHandMiddle1", use_name_prefix)] = c_prefix+"Middle1_Left" 2377 | bones_map[get_mix_name("LeftHandMiddle2", use_name_prefix)] = c_prefix+"Middle2_Left" 2378 | bones_map[get_mix_name("LeftHandMiddle3", use_name_prefix)] = c_prefix+"Middle3_Left" 2379 | bones_map[get_mix_name("LeftHandRing1", use_name_prefix)] = c_prefix+"Ring1_Left" 2380 | bones_map[get_mix_name("LeftHandRing2", use_name_prefix)] = c_prefix+"Ring2_Left" 2381 | bones_map[get_mix_name("LeftHandRing3", use_name_prefix)] = c_prefix+"Ring3_Left" 2382 | bones_map[get_mix_name("LeftHandPinky1", use_name_prefix)] = c_prefix+"Pinky1_Left" 2383 | bones_map[get_mix_name("LeftHandPinky2", use_name_prefix)] = c_prefix+"Pinky2_Left" 2384 | bones_map[get_mix_name("LeftHandPinky3", use_name_prefix)] = c_prefix+"Pinky3_Left" 2385 | bones_map[get_mix_name("RightHandThumb1", use_name_prefix)] = c_prefix+"Thumb1_Right" 2386 | bones_map[get_mix_name("RightHandThumb2", use_name_prefix)] = c_prefix+"Thumb2_Right" 2387 | bones_map[get_mix_name("RightHandThumb3", use_name_prefix)] = c_prefix+"Thumb3_Right" 2388 | bones_map[get_mix_name("RightHandIndex1", use_name_prefix)] = c_prefix+"Index1_Right" 2389 | bones_map[get_mix_name("RightHandIndex2", use_name_prefix)] = c_prefix+"Index2_Right" 2390 | bones_map[get_mix_name("RightHandIndex3", use_name_prefix)] = c_prefix+"Index3_Right" 2391 | bones_map[get_mix_name("RightHandMiddle1", use_name_prefix)] = c_prefix+"Middle1_Right" 2392 | bones_map[get_mix_name("RightHandMiddle2", use_name_prefix)] = c_prefix+"Middle2_Right" 2393 | bones_map[get_mix_name("RightHandMiddle3", use_name_prefix)] = c_prefix+"Middle3_Right" 2394 | bones_map[get_mix_name("RightHandRing1", use_name_prefix)] = c_prefix+"Ring1_Right" 2395 | bones_map[get_mix_name("RightHandRing2", use_name_prefix)] = c_prefix+"Ring2_Right" 2396 | bones_map[get_mix_name("RightHandRing3", use_name_prefix)] = c_prefix+"Ring3_Right" 2397 | bones_map[get_mix_name("RightHandPinky1", use_name_prefix)] = c_prefix+"Pinky1_Right" 2398 | bones_map[get_mix_name("RightHandPinky2", use_name_prefix)] = c_prefix+"Pinky2_Right" 2399 | bones_map[get_mix_name("RightHandPinky3", use_name_prefix)] = c_prefix+"Pinky3_Right" 2400 | 2401 | if leg_left_kinematic == "FK": 2402 | bones_map[get_mix_name("LeftUpLeg", use_name_prefix)] = c_prefix+"UpLeg_FK_Left" 2403 | bones_map[get_mix_name("LeftLeg", use_name_prefix)] = c_prefix+"Leg_FK_Left" 2404 | bones_map[c_prefix+"Foot_FK_Left"] = c_prefix+"Foot_FK_Left" 2405 | bones_map[get_mix_name("LeftToeBase", use_name_prefix)] = c_prefix+"Toe_FK_Left" 2406 | elif leg_left_kinematic == "IK": 2407 | bones_map[c_prefix+"Foot_IK_Left"] = c_prefix+"Foot_IK_Left" 2408 | bones_map[get_mix_name("LeftToeBase", use_name_prefix)] = c_prefix+"Toe_IK_Left" 2409 | 2410 | if leg_right_kinematic == "FK": 2411 | bones_map[get_mix_name("RightUpLeg", use_name_prefix)] = c_prefix+"UpLeg_FK_Right" 2412 | bones_map[get_mix_name("RightLeg", use_name_prefix)] = c_prefix+"Leg_FK_Right" 2413 | bones_map[c_prefix+"Foot_FK_Right"] = c_prefix+"Foot_FK_Right" 2414 | bones_map[get_mix_name("RightToeBase", use_name_prefix)] = c_prefix+"Toe_FK_Right" 2415 | elif leg_right_kinematic == "IK": 2416 | bones_map[c_prefix+"Foot_IK_Right"] = c_prefix+"Foot_IK_Right" 2417 | bones_map[get_mix_name("RightToeBase", use_name_prefix)] = c_prefix+"Toe_IK_Right" 2418 | 2419 | 2420 | 2421 | action = None 2422 | if src_arm.animation_data == None: 2423 | print(" No action found on the source armature") 2424 | if src_arm.animation_data.action == None: 2425 | print(" No action found on the source armature") 2426 | 2427 | # Work on a source armature duplicate 2428 | bpy.ops.object.mode_set(mode='OBJECT') 2429 | bpy.ops.object.select_all(action='DESELECT') 2430 | set_active_object(src_arm.name) 2431 | 2432 | duplicate_object() 2433 | src_arm_copy_name = src_arm.name+"_COPY" 2434 | bpy.context.active_object.name = src_arm_copy_name 2435 | src_arm = get_object(src_arm_copy_name) 2436 | src_arm["mix_to_del"] = True 2437 | 2438 | # Get anim data 2439 | action = src_arm.animation_data.action 2440 | # src_arm.animation_data.action_slot 2441 | 2442 | fr_start = int(action.frame_range[0]) 2443 | fr_end = int(action.frame_range[1]) 2444 | 2445 | 2446 | bpy.ops.object.mode_set(mode='OBJECT') 2447 | bpy.ops.object.select_all(action='DESELECT') 2448 | set_active_object(tar_arm.name) 2449 | 2450 | # Store bones data from target armature 2451 | bpy.ops.object.mode_set(mode='EDIT') 2452 | 2453 | ctrl_matrices = {} 2454 | ik_bones_data = {} 2455 | 2456 | kinematics = {"HandLeft":["Hand", arm_left_kinematic,"Left"], "HandRight":["Hand", arm_right_kinematic, "Right"], "FootLeft":["Foot", leg_left_kinematic, "Left"], "FootRight":["Foot", leg_right_kinematic, "Right"]} 2457 | for b in kinematics: 2458 | type, kin_mode, side = kinematics[b] 2459 | ctrl_name = c_prefix+type+'_'+kin_mode+'_'+side 2460 | ctrl_ebone = get_edit_bone(ctrl_name) 2461 | mix_bone_name = get_mix_name(side+type, use_name_prefix) 2462 | 2463 | ctrl_matrices[ctrl_name] = ctrl_ebone.matrix.copy(), mix_bone_name 2464 | 2465 | # store corrected ik bones 2466 | if kin_mode == "IK": 2467 | ik_bones = {} 2468 | ik_chain = [] 2469 | 2470 | if type == "Foot": 2471 | ik_chain = ["UpLeg_IK_"+side, "Leg_IK_"+side] 2472 | elif type == "Hand": 2473 | ik_chain = ["Arm_IK_"+side, "ForeArm_IK_"+side] 2474 | 2475 | ik1 = get_edit_bone(ik_chain[0]) 2476 | ik2 = get_edit_bone(ik_chain[1]) 2477 | 2478 | ik_bones["ik1"] = ik1.name, ik1.head.copy(), ik1.tail.copy(), ik1.roll 2479 | ik_bones["ik2"] = ik2.name, ik2.head.copy(), ik2.tail.copy(), ik2.roll 2480 | ik_bones_data[b] = type, side, ik_bones 2481 | 2482 | 2483 | # Init source armature rotation and scale 2484 | bpy.ops.object.mode_set(mode='OBJECT') 2485 | bpy.ops.object.select_all(action='DESELECT') 2486 | 2487 | set_active_object(src_arm.name) 2488 | 2489 | scale_fac = src_arm.scale[0] 2490 | bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) 2491 | for fc in action.fcurves: 2492 | dp = fc.data_path 2493 | if dp.startswith('pose.bones') and dp.endswith(".location"): 2494 | for k in fc.keyframe_points: 2495 | k.co[1] *= scale_fac 2496 | 2497 | 2498 | bpy.ops.object.mode_set(mode='EDIT') 2499 | 2500 | # Add helper source bones 2501 | # add feet bones helpers 2502 | for name in ctrl_matrices: 2503 | foot_ebone = create_edit_bone(name) 2504 | foot_ebone.head, foot_ebone.tail = [0,0,0], [0,0,0.1] 2505 | foot_ebone.matrix = ctrl_matrices[name][0] 2506 | foot_ebone.parent = get_edit_bone(ctrl_matrices[name][1]) 2507 | 2508 | # add IK bones helpers 2509 | for b in ik_bones_data: 2510 | type, side, ik_bones = ik_bones_data[b] 2511 | for bone_type in ik_bones: 2512 | bname, bhead, btail, broll = ik_bones[bone_type] 2513 | ebone = create_edit_bone(bname) 2514 | ebone.head, ebone.tail, ebone.roll = bhead, btail, broll 2515 | 2516 | # set parents 2517 | for b in ik_bones_data: 2518 | type, side, ik_bones = ik_bones_data[b] 2519 | ik2_name = ik_bones["ik2"][0] 2520 | ik2 = get_edit_bone(ik2_name) 2521 | 2522 | # set constraints 2523 | bpy.ops.object.mode_set(mode='POSE') 2524 | 2525 | bake_ik_data = {"src_arm":src_arm} 2526 | 2527 | for b in ik_bones_data: 2528 | type, side, ik_bones = ik_bones_data[b] 2529 | b1_name = ik_bones["ik1"][0] 2530 | b2_name = ik_bones["ik2"][0] 2531 | b1_pb = get_pose_bone(b1_name) 2532 | b2_pb = get_pose_bone(b2_name) 2533 | 2534 | chain = [] 2535 | if type == "Foot": 2536 | chain = [get_mix_name(side+"UpLeg", use_name_prefix), get_mix_name(side+"Leg", use_name_prefix)] 2537 | bake_ik_data["Leg"+side] = chain 2538 | 2539 | elif type == "Hand": 2540 | chain = [get_mix_name(side+"Arm", use_name_prefix), get_mix_name(side+"ForeArm", use_name_prefix)] 2541 | bake_ik_data["Arm"+side] = chain 2542 | 2543 | cns = b1_pb.constraints.new("COPY_TRANSFORMS") 2544 | cns.name = "Copy Transforms" 2545 | cns.target = src_arm 2546 | cns.subtarget = chain[0] 2547 | 2548 | cns = b2_pb.constraints.new("COPY_TRANSFORMS") 2549 | cns.name = "Copy Transforms" 2550 | cns.target = src_arm 2551 | cns.subtarget = chain[1] 2552 | 2553 | # Retarget 2554 | retarget_method = 2 2555 | 2556 | # Method 1: Direct matrix retargetting (slower) 2557 | if retarget_method == 1: 2558 | for fr in range(fr_start, fr_end+1): 2559 | print(" frame", fr) 2560 | scn.frame_set(fr) 2561 | bpy.context.view_layer.update() 2562 | 2563 | for src_name in bones_map: 2564 | tar_name = bones_map[src_name] 2565 | src_bone = src_arm.pose.bones.get(src_name) 2566 | tar_bone = tar_arm.pose.bones.get(tar_name) 2567 | 2568 | if "Foot" in src_name: 2569 | tar_mix_bone = tar_arm.pose.bones.get(src_name) 2570 | #print(" tar_mix_bone", tar_mix_bone.name) 2571 | offset_mat = tar_bone.matrix @ tar_mix_bone.matrix.inverted() 2572 | tar_bone.matrix = offset_mat @ src_bone.matrix.copy() 2573 | else: 2574 | tar_bone.matrix = src_bone.matrix.copy() 2575 | 2576 | if "Hips" not in src_name: 2577 | tar_bone.location = [0,0,0] 2578 | 2579 | bpy.context.view_layer.update()# Not ideal, slow performances 2580 | 2581 | 2582 | # Method 2: Constrained retargetting (faster) 2583 | elif retarget_method == 2: 2584 | bpy.ops.object.mode_set(mode='OBJECT') 2585 | bpy.ops.object.select_all(action='DESELECT') 2586 | set_active_object(tar_arm.name) 2587 | bpy.ops.object.mode_set(mode='POSE') 2588 | bpy.ops.pose.select_all(action='DESELECT') 2589 | 2590 | 2591 | # add constraints 2592 | for src_name in bones_map: 2593 | tar_name = bones_map[src_name] 2594 | src_bone = src_arm.pose.bones.get(src_name) 2595 | tar_bone = tar_arm.pose.bones.get(tar_name) 2596 | 2597 | if src_bone == None: 2598 | #print("SKIP BONE", src_name) 2599 | continue 2600 | if tar_bone == None: 2601 | #print("SKIP BONE", tar_name) 2602 | continue 2603 | 2604 | cns_name = "Copy Rotation_retarget" 2605 | cns = tar_bone.constraints.new('COPY_ROTATION') 2606 | cns.name = cns_name 2607 | cns.target = src_arm 2608 | cns.subtarget = src_name 2609 | 2610 | if "Hips" in src_name: 2611 | cns_name = "Copy Location_retarget" 2612 | cns = tar_bone.constraints.new('COPY_LOCATION') 2613 | cns.name = cns_name 2614 | cns.target = src_arm 2615 | cns.subtarget = src_name 2616 | cns.owner_space = cns.target_space = "LOCAL" 2617 | 2618 | # Foot IK, Hand IK 2619 | if (leg_left_kinematic == "IK" and "Foot_IK_Left" in src_name) or (leg_right_kinematic == "IK" and "Foot_IK_Right" in src_name) or (arm_left_kinematic == "IK" and "Hand_IK_Left" in src_name) or (arm_right_kinematic == "IK" and "Hand_IK_Right" in src_name): 2620 | #print(" set IK remap constraints", src_name) 2621 | cns_name = "Copy Location_retarget" 2622 | cns = tar_bone.constraints.new('COPY_LOCATION') 2623 | cns.name = cns_name 2624 | cns.target = src_arm 2625 | cns.subtarget = src_name 2626 | cns.target_space = cns.owner_space = "POSE" 2627 | 2628 | # select IK poles 2629 | _side = "_Left" if "Left" in src_name else "_Right" 2630 | ik_pole_name = "" 2631 | if "Hand" in src_name: 2632 | ik_pole_name = c_prefix+arm_rig_names["pole_ik"]+_side 2633 | elif "Foot" in src_name: 2634 | ik_pole_name = c_prefix+leg_rig_names["pole_ik"]+_side 2635 | 2636 | ik_pole_ctrl = get_pose_bone(ik_pole_name) 2637 | tar_arm.data.bones.active = ik_pole_ctrl.bone 2638 | ik_pole_ctrl.bone.select = True 2639 | 2640 | 2641 | # select 2642 | tar_arm.data.bones.active = tar_bone.bone 2643 | tar_bone.bone.select = True 2644 | 2645 | bpy.context.view_layer.update() 2646 | 2647 | # bake 2648 | bake_anim(frame_start=fr_start, frame_end=fr_end, only_selected=True, bake_bones=True, bake_object=False, ik_data=bake_ik_data) 2649 | 2650 | bpy.ops.object.mode_set(mode='OBJECT') 2651 | set_active_object(src_arm.name) 2652 | set_active_object(tar_arm.name) 2653 | print("Animation imported.") 2654 | 2655 | 2656 | def remove_retarget_cns(armature): 2657 | #print("Removing constraints...") 2658 | for pb in armature.pose.bones: 2659 | if len(pb.constraints): 2660 | for cns in pb.constraints: 2661 | if cns.name.endswith("_retarget") or cns.name == "temp": 2662 | pb.constraints.remove(cns) 2663 | 2664 | 2665 | def remove_temp_objects(): 2666 | for obj in bpy.data.objects: 2667 | if "mix_to_del" in obj.keys(): 2668 | delete_object(obj) 2669 | 2670 | 2671 | def update_mixamo_tab(): 2672 | try: 2673 | bpy.utils.unregister_class(MR_PT_MenuMain) 2674 | bpy.utils.unregister_class(MR_PT_MenuRig) 2675 | bpy.utils.unregister_class(MR_PT_MenuAnim) 2676 | bpy.utils.unregister_class(MR_PT_MenuExport) 2677 | bpy.utils.unregister_class(MR_PT_MenuUpdate) 2678 | except: 2679 | pass 2680 | 2681 | MixamoRigPanel.bl_category = bpy.context.preferences.addons[__package__].preferences.mixamo_tab_name 2682 | bpy.utils.register_class(MR_PT_MenuMain) 2683 | bpy.utils.register_class(MR_PT_MenuRig) 2684 | bpy.utils.register_class(MR_PT_MenuAnim) 2685 | bpy.utils.register_class(MR_PT_MenuExport) 2686 | bpy.utils.register_class(MR_PT_MenuUpdate) 2687 | 2688 | ########### UI PANELS ################### 2689 | class MixamoRigPanel: 2690 | bl_space_type = 'VIEW_3D' 2691 | bl_region_type = 'UI' 2692 | bl_category = "Mixamo" 2693 | 2694 | 2695 | class MR_PT_MenuMain(Panel, MixamoRigPanel): 2696 | bl_label = "Mixamo Control Rig" 2697 | 2698 | def draw(self, context): 2699 | scn = context.scene 2700 | 2701 | layt = self.layout 2702 | layt.use_property_split = True 2703 | layt.use_property_decorate = False 2704 | 2705 | #col = layt.column(align=True) 2706 | #col.scale_y = 1.3 2707 | #col.prop_search(scn, "mix_source_armature", scn, "objects", text="Skeleton") 2708 | arm_name = "None" 2709 | 2710 | if context.active_object != None: 2711 | if context.active_object.type == "ARMATURE": 2712 | arm_name = context.active_object.name 2713 | 2714 | layt.label(text="Character: "+arm_name) 2715 | 2716 | 2717 | class MR_PT_MenuRig(Panel, MixamoRigPanel): 2718 | bl_label = "Control Rig" 2719 | bl_parent_id = "MR_PT_MenuMain" 2720 | 2721 | def draw(self, context): 2722 | layt = self.layout 2723 | layt.use_property_split = True 2724 | layt.use_property_decorate = False 2725 | 2726 | obj = context.active_object 2727 | scn = context.scene 2728 | 2729 | """ 2730 | has_rigged = False 2731 | if obj: 2732 | if obj.type == "ARMATURE": 2733 | if len(obj.data.keys()): 2734 | if "mr_data" in obj.data.keys(): 2735 | has_rigged = True 2736 | """ 2737 | 2738 | col = layt.column(align=True) 2739 | col.scale_y = 1.3 2740 | 2741 | col.operator(MR_OT_make_rig.bl_idname, text="Create Control Rig") 2742 | col.operator(MR_OT_zero_out.bl_idname, text="Zero Out Rig") 2743 | 2744 | col = layt.column(align=True) 2745 | col.separator() 2746 | 2747 | if bpy.context.mode != 'EDIT_MESH': 2748 | col.operator(MR_OT_edit_custom_shape.bl_idname, text="Edit Control Shape") 2749 | else: 2750 | col.operator(MR_OT_apply_shape.bl_idname, text="Apply Control Shape") 2751 | 2752 | 2753 | class MR_PT_MenuAnim(Panel, MixamoRigPanel): 2754 | bl_label = "Animation" 2755 | bl_parent_id = "MR_PT_MenuMain" 2756 | 2757 | def draw(self, context): 2758 | layt = self.layout 2759 | layt.use_property_split = True 2760 | layt.use_property_decorate = False# No animation. 2761 | scn = context.scene 2762 | layt.use_property_split = True 2763 | layt.use_property_decorate = False 2764 | 2765 | col = layt.column(align=True) 2766 | col.scale_y = 1 2767 | #col.prop_search(scn, "mix_target_armature", scn, "objects", text="Control Rig") 2768 | col.label(text="Source Skeleton:") 2769 | col.prop_search(scn, "mix_source_armature", scn, "objects", text="") 2770 | col.separator() 2771 | 2772 | col = layt.column(align=True) 2773 | col.scale_y = 1.3 2774 | col.operator(MR_OT_import_anim.bl_idname, text="Apply Animation to Control Rig") 2775 | 2776 | col = layt.column(align=True) 2777 | col.scale_y = 1.3 2778 | col.operator(MR_OT_bake_anim.bl_idname, text="Bake Animation") 2779 | 2780 | 2781 | class MR_PT_MenuUpdate(Panel, MixamoRigPanel): 2782 | bl_label = "Update" 2783 | bl_parent_id = "MR_PT_MenuMain" 2784 | 2785 | def draw(self, context): 2786 | layt = self.layout 2787 | layt.operator(MR_OT_update.bl_idname, text="Update Control Rig") 2788 | 2789 | 2790 | class MR_PT_MenuExport(Panel, MixamoRigPanel): 2791 | bl_label = "Export" 2792 | bl_parent_id = "MR_PT_MenuMain" 2793 | 2794 | def draw(self, context): 2795 | layt = self.layout 2796 | layt.operator('export_scene.gltf', text="GLTF Export...")#MR_OT_exportGLTF.bl_idname 2797 | 2798 | 2799 | ########### REGISTER ################## 2800 | classes = ( 2801 | MR_PT_MenuMain, 2802 | MR_PT_MenuRig, 2803 | MR_PT_MenuAnim, 2804 | MR_PT_MenuExport, 2805 | MR_PT_MenuUpdate, 2806 | MR_OT_make_rig, 2807 | MR_OT_zero_out, 2808 | MR_OT_bake_anim, 2809 | MR_OT_import_anim, 2810 | MR_OT_edit_custom_shape, 2811 | MR_OT_apply_shape, 2812 | MR_OT_exportGLTF, 2813 | MR_OT_update 2814 | ) 2815 | 2816 | 2817 | def register(): 2818 | from bpy.utils import register_class 2819 | for cls in classes: 2820 | register_class(cls) 2821 | 2822 | update_mixamo_tab() 2823 | 2824 | bpy.types.Scene.mix_source_armature = bpy.props.PointerProperty(type=bpy.types.Object) 2825 | bpy.types.Scene.mix_target_armature = bpy.props.PointerProperty(type=bpy.types.Object) 2826 | 2827 | 2828 | def unregister(): 2829 | from bpy.utils import unregister_class 2830 | for cls in reversed(classes): 2831 | unregister_class(cls) 2832 | 2833 | del bpy.types.Scene.mix_source_armature 2834 | del bpy.types.Scene.mix_target_armature 2835 | 2836 | if __name__ == "__main__": 2837 | register() 2838 | --------------------------------------------------------------------------------