commit 0e19f20200e24827f91e1100d297a9a0d6502648 Author: Aurelien Vaillant Date: Thu Aug 1 13:43:28 2024 +0200 Add basic depo with the mixamo addon diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c92e2c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,319 @@ +# Created by https://www.toptal.com/developers/gitignore/api/windows,pycharm,python,blender +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,pycharm,python,blender + +### Blender ### +# Ignore temporary Blender files. +*.blend[0-9]* + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows,pycharm,python,blender diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..883d026 --- /dev/null +++ b/__init__.py @@ -0,0 +1,62 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ***** END GPL LICENCE BLOCK ***** + + +bl_info = { + "name": "Mixamo Rig", + "author": "Mixamo", + "version": (1, 11, 11), + "blender": (2, 80, 0), + "location": "3D View > Mixamo> Control Rig", + "description": "Generate a control rig from the selected Mixamo Fbx skeleton", + "category": "Animation"} + + +if "bpy" in locals(): + import importlib + if "mixamo_rig_prefs" in locals(): + importlib.reload(mixamo_rig_prefs) + if "mixamo_rig" in locals(): + importlib.reload(mixamo_rig) + if "mixamo_rig_functions" in locals(): + importlib.reload(mixamo_rig_functions) + if "utils" in locals(): + importlib.reload(utils) + if "animation" in locals(): + importlib.reload(animation) + + +import bpy +from . import mixamo_rig_prefs +from . import mixamo_rig +from . import mixamo_rig_functions +from . import utils + +def register(): + mixamo_rig_prefs.register() + mixamo_rig.register() + mixamo_rig_functions.register() + +def unregister(): + mixamo_rig_prefs.unregister() + mixamo_rig.unregister() + mixamo_rig_functions.unregister() + +if __name__ == "__main__": + register() \ No newline at end of file diff --git a/define.py b/define.py new file mode 100644 index 0000000..70af10e --- /dev/null +++ b/define.py @@ -0,0 +1 @@ +from .definitions.naming import * \ No newline at end of file diff --git a/definitions/naming.py b/definitions/naming.py new file mode 100644 index 0000000..8c0b204 --- /dev/null +++ b/definitions/naming.py @@ -0,0 +1,14 @@ +# control rig +c_prefix = "Ctrl_" +master_rig_names = {"master":"Master"} +spine_rig_names = {"pelvis":"Hips", "spine1":"Spine", "spine2":"Spine1", "spine3":"Spine2", "hips_free":"Hips_Free", "hips_free_helper":"Hips_Free_Helper"} +head_rig_names = {"neck":"Neck", "head":"Head"} +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"} +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"} + +# mixamo bone names +spine_names = {"pelvis":"Hips", "spine1":"Spine", "spine2":"Spine1", "spine3":"Spine2"} +head_names = {"neck":"Neck", "head":"Head", "head_end":"HeadTop_End"} +leg_names = {"thigh":"UpLeg", "calf":"Leg", "foot":"Foot", "toes":"ToeBase", "toes_end":"Toe_End"} +arm_names = {"shoulder":"Shoulder", "arm":"Arm", "forearm":"ForeArm", "hand":"Hand"} +fingers_type = ["Thumb", "Index", "Middle", "Ring", "Pinky"] \ No newline at end of file diff --git a/mixamo_rig.py b/mixamo_rig.py new file mode 100644 index 0000000..57dec58 --- /dev/null +++ b/mixamo_rig.py @@ -0,0 +1,2763 @@ +import bpy, sys, linecache, ast +import math +from math import * +from mathutils import * +from bpy.types import Panel, UIList +from .utils import * +from .define import * + + +# OPERATOR CLASSES +################## +class MR_OT_update(bpy.types.Operator): + """Update old control rig to Blender 3.0""" + + bl_idname = "mr.update" + bl_label = "update" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + if context.active_object: + if context.active_object.type == "ARMATURE": + return "mr_control_rig" in context.active_object.data.keys() + + + + def execute(self, context): + try: + _update(self) + finally: + pass + + return {'FINISHED'} + + + +class MR_OT_exportGLTF(bpy.types.Operator): + """Export to GLTF format""" + + bl_idname = "mr.export_gltf" + bl_label = "export_gltf" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + if context.active_object: + if context.active_object.type == "ARMATURE": + return True + + + def execute(self, context): + try: + bpy.ops.export_scene.gltf() + finally: + pass + + return {'FINISHED'} + + +class MR_OT_apply_shape(bpy.types.Operator): + """Apply the selected shape""" + + bl_idname = "mr.apply_shape" + bl_label = "apply_shape" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + if context.active_object: + if context.mode == 'EDIT_MESH': + if 'cs_user' in context.active_object.name: + return True + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + + try: + _apply_shape() + finally: + context.preferences.edit.use_global_undo = use_global_undo + return {'FINISHED'} + + +class MR_OT_edit_custom_shape(bpy.types.Operator): + """Edit the selected bone shape""" + + bl_idname = "mr.edit_custom_shape" + bl_label = "edit_custom_shape" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + if context.mode == 'POSE': + if bpy.context.active_pose_bone: + return True + + def execute(self, context): + try: + cs = bpy.context.active_pose_bone.custom_shape + if cs: + _edit_custom_shape() + else: + self.report({"ERROR"}, "No custom shapes set for this bone.") + + finally: + pass + + return {'FINISHED'} + + +class MR_OT_make_rig(bpy.types.Operator): + """Generate a control rig from the selected Mixamo skeleton""" + + bl_idname = "mr.make_rig" + bl_label = "Create control rig from selected armature" + bl_options = {'UNDO'} + + bake_anim: bpy.props.BoolProperty(name="Bake Anim", description="Bake animation to the control bones", default=True) + 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) + 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) + animated_armature = None + + @classmethod + def poll(cls, context): + if context.active_object: + if context.active_object.type == "ARMATURE": + if not "mr_control_rig" in context.active_object.data.keys(): + return True + return False + + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=450) + + + def draw(self, context): + layout = self.layout + layout.prop(self, 'bake_anim', text="Apply Animation") + layout.prop(self, 'ik_arms', text="IK Arms") + layout.prop(self, 'ik_legs', text="IK Legs") + + + def execute(self, context): + debug = False + layer_select = [] + + try: + # only select the armature + arm = get_object(context.active_object.name) + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(arm.name) + + # enable all armature layers + layer_select = enable_all_armature_layers() + + # animation import: initial steps + if self.bake_anim: + if not "mr_control_rig" in arm.data.keys():# only if the control rig is not already built + # duplicate current skeleton + duplicate_object() + copy_name = arm.name+"_TEMPANIM" + self.animated_armature = get_object(bpy.context.active_object.name) + self.animated_armature.name = copy_name + self.animated_armature["mix_to_del"] = True + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(arm.name) + + # set to rest pose, clear animation + _zero_out() + + # build control rig + _make_rig(self) + + if blender_version._float < 291: + # Child Of constraints inverse matrix must be set manually in Blender versions < 2.91 + print("Set inverse ChildOf") + _reset_inverse_constraints() + + # animation import: retarget + if self.bake_anim and self.animated_armature: + _import_anim(self.animated_armature, arm) + + # set KeyingSet + ks = context.scene.keying_sets_all + try: + ks.active = ks["Location & Rotation"] + except:# doesn't exist in older Blender versions + pass + + + finally: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(arm.name) + + if debug == False: + restore_armature_layers(layer_select) + remove_retarget_cns(bpy.context.active_object) + remove_temp_objects() + clean_scene() + + + self.report({"INFO"}, "Control Rig Done!") + + return {'FINISHED'} + + +class MR_OT_zero_out(bpy.types.Operator): + """Delete all keys and set every bones to (0,0,0) rotation""" + + bl_idname = "mr.zero_out" + bl_label = "zero_out" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + if context.active_object: + return context.active_object.type == "ARMATURE" + return False + + + def execute(self, context): + scn = bpy.context.scene + + + try: + _zero_out() + + finally: + print("") + + + return {'FINISHED'} + + +class MR_OT_bake_anim(bpy.types.Operator): + """Merge all animation layers (see NLA editor) into a single layer""" + + bl_idname = "mr.bake_anim" + bl_label = "bake_anim" + bl_options = {'UNDO'} + + + @classmethod + def poll(cls, context): + if context.active_object: + return context.active_object.type == "ARMATURE" + return False + + + def execute(self, context): + scn = bpy.context.scene + + try: + _bake_anim(self) + + finally: + pass + + + return {'FINISHED'} + + +class MR_OT_import_anim(bpy.types.Operator): + """Import an animation file (FBX) of the same character to the control rig""" + + bl_idname = "mr.import_anim_to_rig" + bl_label = "import_anim_to_rig" + bl_options = {'UNDO'} + + + @classmethod + def poll(cls, context): + if context.active_object: + if context.active_object.type == "ARMATURE": + if "mr_control_rig" in context.active_object.data.keys(): + return True + return False + + + def execute(self, context): + scn = bpy.context.scene + debug = False + error = False + layer_select = [] + + if scn.mix_source_armature == None: + self.report({'ERROR'}, "Source armature must be set") + return {'FINISHED'} + + + try: + layer_select = enable_all_armature_layers() + #tar_arm = scn.mix_target_armature + tar_arm = get_object(bpy.context.active_object.name) + #src_arm = [i for i in bpy.context.selected_objects if i != tar_arm][0] + src_arm = scn.mix_source_armature + print("Source", src_arm.name) + print("Target", tar_arm.name) + + _import_anim(src_arm, tar_arm, import_only=True) + + #except: + # error = True + # print("Error") + + finally: + if debug == False: + restore_armature_layers(layer_select) + remove_retarget_cns(bpy.context.active_object) + + if scn.mix_source_armature: + try: + remove_retarget_cns(mix_source_armature) + except: + pass + + remove_temp_objects() + + self.report({"INFO"}, "Animation imported") + + + return {'FINISHED'} + + + + +# OPERATOR FUNCTIONS +##################### + +def _apply_shape(): + bpy.ops.object.mode_set(mode='OBJECT') + obj = bpy.context.active_object + obj_name = obj.name + shape = bpy.data.objects.get(obj_name) + delete_obj = False + + cs_grp = get_object('cs_grp') + if cs_grp: + shape.parent = bpy.data.objects['cs_grp'] + + mr_armature_name = None + mr_armature = None + + if len(shape.keys()) > 0: + for key in shape.keys(): + if 'delete' in shape.keys(): + delete_obj = True + if 'mr_armature' in key: + mr_armature_name = shape['mr_armature'] + mr_armature = bpy.data.objects.get(mr_armature_name) + + if delete_obj: + bpy.ops.object.delete(use_global=False) + else: + # assign to collection + if mr_armature: + if len(mr_armature.users_collection) > 0: + for collec in mr_armature.users_collection: + if len(collec.name.split('_')) == 1: + continue + if collec.name.split('_')[1] == "rig" or collec.name.split('_')[1] == "grp": + cs_collec = bpy.data.collections.get(collec.name.split('_')[0] + '_cs') + if cs_collec: + # remove from root collection + if bpy.context.scene.collection.objects.get(shape.name): + bpy.context.scene.collection.objects.unlink(shape) + # remove from other collections + for other_collec in shape.users_collection: + other_collec.objects.unlink(shape) + # assign to cs collection + cs_collec.objects.link(shape) + print("assigned to collec", cs_collec.name) + else: + print("cs collec not found") + else: + print("rig collec not found") + + else: + print("Armature has no collection") + else: + print("Armature not set") + + # hide shape + try: + hide_object(shape) + except: # weird error 'StructRNA of type Object has been removed' + print("Error, could not hide shape") + pass + + if mr_armature: + set_active_object(mr_armature.name) + bpy.ops.object.mode_set(mode='POSE') + + +def _edit_custom_shape(): + bone = bpy.context.active_pose_bone + rig_name = bpy.context.active_object.name + rig = get_object(rig_name) + + cs = bone.custom_shape + cs_mesh = cs.data + + bpy.ops.object.posemode_toggle() + + # make sure the active collection is not hidden, otherwise we can't access the newly created object data + active_collec = bpy.context.layer_collection + if not active_collec.is_visible: + for col in rig.users_collection: + layer_col = search_layer_collection(bpy.context.view_layer.layer_collection, col.name) + if layer_col.hide_viewport == False and col.hide_viewport == False: + bpy.context.view_layer.active_layer_collection = layer_col + break + + # create new mesh data + bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, location=(-0, 0, 0.0), rotation=(0.0, 0.0, 0.0)) + + mesh_obj = bpy.context.active_object + mesh_obj.name = 'cs_user_' + bone.name + + if cs.name == "cs_user_" + bone.name:# make a mesh instance if it's a already edited + mesh_obj.data = cs_mesh + mesh_obj['delete'] = 1.0 + else: # else create new object data + mesh_obj.data = cs_mesh.copy() + mesh_obj.data.name = mesh_obj.name + bone.custom_shape = mesh_obj + + # store the current armature name in a custom prop + mesh_obj['mr_armature'] = rig_name + + if bone.custom_shape_transform: + bone_transf = bone.custom_shape_transform + mesh_obj.matrix_world = rig.matrix_world @ bone_transf.matrix + else: + mesh_obj.matrix_world = rig.matrix_world @ bone.matrix + + mesh_obj.scale *= get_custom_shape_scale(bone) + mesh_obj.scale *= bone.length + + bpy.ops.object.mode_set(mode='EDIT') + + +def clean_scene(): + # hide cs_grp + cs_grp = get_object("cs_grp") + if cs_grp: + for c in cs_grp.children: + hide_object(c) + hide_object(cs_grp) + + # Display layer 0 and 1 only + layers = bpy.context.active_object.data.layers + layers[0] = True + for i in range(0, 32): + layers[i] = i in [0] + + +def init_armature_transforms(rig): + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(rig.name) + bpy.ops.object.mode_set(mode='OBJECT') + + # first unparent children meshes (init scale messed up children scale in Blender 2.8) + child_par_dict = {} + for child in bpy.data.objects[rig.name].children: + bone_parent = None + if child.parent_type == "BONE": + bone_parent = child.parent_bone + child_par_dict[child.name] = bone_parent + child_mat = child.matrix_world.copy() + child.parent = None + bpy.context.evaluated_depsgraph_get().update() + child.matrix_world = child_mat + + # apply armature transforms + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + bpy.context.evaluated_depsgraph_get().update() + + # restore armature children + for child_name in child_par_dict: + child = bpy.data.objects.get(child_name) + child_mat = child.matrix_world.copy() + child.parent = bpy.data.objects[rig.name] + if child_par_dict[child_name] != None:# bone parent + child.parent_type = "BONE" + child.parent_bone = child_par_dict[child_name] + + bpy.context.evaluated_depsgraph_get().update() + child.matrix_world = child_mat + + +def _reset_inverse_constraints(): + bpy.ops.object.mode_set(mode='POSE') + + rig_name = bpy.context.active_object.name + rig = get_object(rig_name) + + for pb in rig.pose.bones: + if len(pb.constraints): + for cns in pb.constraints: + if cns.type == 'CHILD_OF': + set_constraint_inverse_matrix(cns) + + + bpy.ops.object.mode_set(mode='OBJECT') + + + +def _update(self): + if blender_version._float >= 300: + convert_drivers_cs_to_xyz(bpy.context.active_object) + + +def _make_rig(self): + print("\nBuilding control rig...") + + scn = bpy.context.scene + rig_name = bpy.context.active_object.name + rig = get_object(rig_name) + + layer_mix_idx = 1 + layer_ctrl_idx = 0 + layer_intern_idx = 8 + use_name_prefix = True + + c_master_name = c_prefix+master_rig_names["master"] + + # Init transforms + init_armature_transforms(rig) + + + def add_master(): + print(" Add Master") + + # -- Edit -- + bpy.ops.object.mode_set(mode='EDIT') + + + # Create bones + c_master = create_edit_bone(c_master_name) + c_master.head = [0, 0, 0] + c_master.tail = [0, 0, 0.05 * rig.dimensions[2]] + c_master.roll = 0.01 + set_bone_layer(c_master, layer_ctrl_idx) + c_master["mixamo_ctrl"] = 1# tag as controller bone + + + # -- Pose -- + bpy.ops.object.mode_set(mode='POSE') + + + c_master_pb = get_pose_bone(c_master_name) + + # set custom shapes + set_bone_custom_shape(c_master_pb, "cs_master") + + # set rotation mode + c_master_pb.rotation_mode = "XYZ" + + # set color group + set_bone_color_group(rig, c_master_pb, "master") + + + def add_spine(): + print(" Add Spine") + + # -- Edit -- + bpy.ops.object.mode_set(mode='EDIT') + + + # Create bones + hips_name = get_mix_name(spine_names["pelvis"], use_name_prefix) + spine_name = get_mix_name(spine_names["spine1"], use_name_prefix) + spine1_name = get_mix_name(spine_names["spine2"], use_name_prefix) + spine2_name = get_mix_name(spine_names["spine3"], use_name_prefix) + + hips = get_edit_bone(hips_name) + spine = get_edit_bone(spine_name) + spine1 = get_edit_bone(spine1_name) + spine2 = get_edit_bone(spine2_name) + + if not hips or not spine or not spine1 or not spine2: + print(" Spine bones are missing, skip spine") + return + + for b in [hips, spine, spine1, spine2]: + set_bone_layer(b, layer_mix_idx) + + # Hips Ctrl + c_hips_name = c_prefix+spine_rig_names["pelvis"] + c_hips = create_edit_bone(c_hips_name) + copy_bone_transforms(hips, c_hips) + c_hips.parent = get_edit_bone(c_prefix+master_rig_names["master"]) + set_bone_layer(c_hips, layer_ctrl_idx) + c_hips["mixamo_ctrl"] = 1# tag as controller bone + + + # Free Hips Ctrl + c_hips_free_name = c_prefix+spine_rig_names["hips_free"] + c_hips_free = create_edit_bone(c_hips_free_name) + c_hips_free.head = hips.tail.copy() + c_hips_free.tail = hips.head.copy() + align_bone_x_axis(c_hips_free, hips.x_axis) + c_hips_free["mixamo_ctrl"] = 1# tag as controller bone + + c_hips_free.parent = c_hips + set_bone_layer(c_hips_free, layer_ctrl_idx) + + # Free Hips helper + hips_free_h_name = spine_rig_names["hips_free_helper"] + hips_free_helper = create_edit_bone(hips_free_h_name) + copy_bone_transforms(hips, hips_free_helper) + hips_free_helper.parent = c_hips_free + set_bone_layer(hips_free_helper, layer_intern_idx) + + # Spine Ctrl + c_spine_name = c_prefix+spine_rig_names["spine1"] + c_spine = create_edit_bone(c_spine_name) + copy_bone_transforms(spine, c_spine) + c_spine.parent = c_hips + set_bone_layer(c_spine, layer_ctrl_idx) + c_spine["mixamo_ctrl"] = 1# tag as controller bone + + # Spine1 Ctrl + c_spine1_name = c_prefix+spine_rig_names["spine2"] + c_spine1 = create_edit_bone(c_spine1_name) + copy_bone_transforms(spine1, c_spine1) + c_spine1.parent = c_spine + set_bone_layer(c_spine1, layer_ctrl_idx) + c_spine1["mixamo_ctrl"] = 1# tag as controller bone + + # Spine2 Ctrl + c_spine2_name = c_prefix+spine_rig_names["spine3"] + c_spine2 = create_edit_bone(c_spine2_name) + copy_bone_transforms(spine2, c_spine2) + c_spine2.parent = c_spine1 + set_bone_layer(c_spine2, layer_ctrl_idx) + c_spine2["mixamo_ctrl"] = 1# tag as controller bone + + + # -- Pose -- + bpy.ops.object.mode_set(mode='POSE') + + + c_hips_pb = get_pose_bone(c_hips_name) + hips_helper_pb = get_pose_bone(hips_free_h_name) + c_hips_free_pb = get_pose_bone(c_hips_free_name) + c_spine_pb = get_pose_bone(c_spine_name) + c_spine1_pb = get_pose_bone(c_spine1_name) + c_spine2_pb = get_pose_bone(c_spine2_name) + + # set custom shapes + set_bone_custom_shape(c_hips_pb, "cs_square_2") + set_bone_custom_shape(c_hips_free_pb, "cs_hips") + set_bone_custom_shape(c_spine_pb, "cs_circle") + set_bone_custom_shape(c_spine1_pb, "cs_circle") + set_bone_custom_shape(c_spine2_pb, "cs_circle") + + # set rotation mode + c_hips_pb.rotation_mode = "XYZ" + c_hips_free_pb.rotation_mode = "XYZ" + c_spine_pb.rotation_mode = "XYZ" + c_spine1_pb.rotation_mode = "XYZ" + c_spine2_pb.rotation_mode = "XYZ" + + # set color group + set_bone_color_group(rig, c_hips_pb, "root_master") + set_bone_color_group(rig, c_hips_free_pb, "body_mid") + set_bone_color_group(rig, c_spine_pb, "body_mid") + set_bone_color_group(rig, c_spine1_pb, "body_mid") + set_bone_color_group(rig, c_spine2_pb, "body_mid") + + # constraints + # Hips + mixamo_spine_pb = get_pose_bone(hips_name) + cns = mixamo_spine_pb.constraints.get("Copy Transforms") + if cns == None: + cns = mixamo_spine_pb.constraints.new("COPY_TRANSFORMS") + cns.name = "Copy Transforms" + cns.target = rig + cns.subtarget = hips_free_h_name + + # Spine + spine_bone_matches = {"1": c_spine_name, "2":c_spine1_name, "3":c_spine2_name} + for str_idx in spine_bone_matches: + c_name = spine_bone_matches[str_idx] + mixamo_bname = get_mix_name(spine_names["spine"+str_idx], use_name_prefix) + mixamo_spine_pb = get_pose_bone(mixamo_bname) + cns = mixamo_spine_pb.constraints.get("Copy Transforms") + if cns == None: + cns = mixamo_spine_pb.constraints.new("COPY_TRANSFORMS") + cns.name = "Copy Transforms" + cns.target = rig + cns.subtarget = c_name + + + def add_head(): + print(" Add Head") + + # -- Edit -- + bpy.ops.object.mode_set(mode='EDIT') + + + # Create bones + neck_name = get_mix_name(head_names["neck"], use_name_prefix) + head_name = get_mix_name(head_names["head"], use_name_prefix) + head_end_name = get_mix_name(head_names["head_end"], use_name_prefix) + + neck = get_edit_bone(neck_name) + head = get_edit_bone(head_name) + head_end = get_edit_bone(head_end_name) + + if not neck or not head: + print(" Head or neck bones are missing, skip head") + return + + for b in [neck, head, head_end]: + set_bone_layer(b, layer_mix_idx) + + # Neck Ctrl + c_neck_name = c_prefix+head_rig_names["neck"] + c_neck = create_edit_bone(c_neck_name) + copy_bone_transforms(neck, c_neck) + c_neck.parent = get_edit_bone(c_prefix+spine_rig_names["spine3"]) + set_bone_layer(c_neck, layer_ctrl_idx) + c_neck["mixamo_ctrl"] = 1# tag as controller bone + + # Head Ctrl + c_head_name = c_prefix+head_rig_names["head"] + c_head = create_edit_bone(c_head_name) + copy_bone_transforms(head, c_head) + c_head.parent = c_neck + set_bone_layer(c_head, layer_ctrl_idx) + c_head["mixamo_ctrl"] = 1# tag as controller bone + + # -- Pose -- + bpy.ops.object.mode_set(mode='POSE') + + + c_neck_pb = get_pose_bone(c_neck_name) + c_head_pb = get_pose_bone(c_head_name) + + # set custom shapes + set_bone_custom_shape(c_neck_pb, "cs_neck") + set_bone_custom_shape(c_head_pb, "cs_head") + + # set rotation mode + c_neck_pb.rotation_mode = "XYZ" + c_head_pb.rotation_mode = "XYZ" + + # set color group + set_bone_color_group(rig, c_neck_pb, "neck") + set_bone_color_group(rig, c_head_pb, "head") + + # constraints + # Neck + neck_pb = get_pose_bone(neck_name) + head_pb = get_pose_bone(head_name) + + add_copy_transf(neck_pb, rig, c_neck_name) + add_copy_transf(head_pb, rig, c_head_name) + + + def add_leg(side): + print(" Add Leg", side) + + _side = "_"+side + thigh_name = get_mix_name(side+leg_names["thigh"], use_name_prefix) + calf_name = get_mix_name(side+leg_names["calf"], use_name_prefix) + foot_name = get_mix_name(side+leg_names["foot"], use_name_prefix) + toe_name = get_mix_name(side+leg_names["toes"], use_name_prefix) + toe_end_name = get_mix_name(side+leg_names["toes_end"], use_name_prefix) + + # -- Edit -- + bpy.ops.object.mode_set(mode='EDIT') + + + thigh = get_edit_bone(thigh_name) + calf = get_edit_bone(calf_name) + foot = get_edit_bone(foot_name) + toe = get_edit_bone(toe_name) + toe_end = get_edit_bone(toe_end_name) + + hips = get_edit_bone(get_mix_name(spine_names["pelvis"], use_name_prefix)) + c_hips_free_name = c_prefix+spine_rig_names["hips_free"] + c_hips_free = get_edit_bone(c_hips_free_name) + + if not thigh or not calf or not foot or not toe: + print(" Leg bones are missing, skip leg: "+side) + return + + # Set Mixamo bones in layer + for b in [thigh, calf, foot, toe, toe_end]: + set_bone_layer(b, layer_mix_idx) + + # Create bones + # correct straight leg angle, need minimum 0.1 degrees for IK constraints to work + def get_leg_angle(): + #return degrees(thigh.y_axis.angle(calf.y_axis)) + vec1 = calf.head - thigh.head + vec2 = foot.head - calf.head + return degrees(vec1.angle(vec2)) + + leg_angle = get_leg_angle() + + if leg_angle < 0.1: + print(" ! Straight leg bones, angle = "+str(leg_angle)) + max_iter = 10000 + i = 0 + + while leg_angle < 0.1 and i < max_iter: + + dir = ((thigh.z_axis + calf.z_axis)*0.5).normalized() + calf.head += dir * (calf.tail-calf.head).magnitude * 0.0001 + leg_angle = get_leg_angle() + i += 1 + + print(" corrected leg angle: "+str(leg_angle)) + + + # Thigh IK + thigh_ik_name = leg_rig_names["thigh_ik"]+_side + thigh_ik = create_edit_bone(thigh_ik_name) + copy_bone_transforms(thigh, thigh_ik) + + # auto-align knee position with global Y axis to ensure IK pole vector is physically correct + leg_axis = calf.tail - thigh.head + leg_midpoint = (thigh.head + calf.tail) * 0.5 + + #cur_vec = calf.head - leg_midpoint + #cur_vec[2] = 0.0 + #global_y_vec = Vector((0, -1, 0)) + + dir = calf.head - leg_midpoint + cur_vec = project_vector_onto_plane(dir, leg_axis) + global_y_vec = project_vector_onto_plane(Vector((0, -1, 0)), leg_axis) + + signed_cur_angle = signed_angle(cur_vec, global_y_vec, leg_axis) + print(" IK base angle:", degrees(signed_cur_angle)) + + # rotate + rotated_point = rotate_point(calf.head.copy(), -signed_cur_angle, leg_midpoint, leg_axis) + + # (check) + dir = rotated_point - leg_midpoint + cur_vec = project_vector_onto_plane(dir, leg_axis) + signed_cur_angle = signed_angle(cur_vec, global_y_vec, leg_axis) + print(" IK corrected angle:", degrees(signed_cur_angle)) + + thigh_ik.tail = rotated_point + + thigh_ik.parent = c_hips_free + set_bone_layer(thigh_ik, layer_intern_idx) + + # Thigh FK Ctrl + c_thigh_fk_name = c_prefix+leg_rig_names["thigh_fk"]+_side + c_thigh_fk = create_edit_bone(c_thigh_fk_name) + copy_bone_transforms(thigh_ik, c_thigh_fk) + c_thigh_fk.parent = c_hips_free + set_bone_layer(c_thigh_fk, layer_ctrl_idx) + c_thigh_fk["mixamo_ctrl"] = 1# tag as controller bone + + # Calf IK + calf_ik_name = leg_rig_names["calf_ik"]+_side + + # check if bone exist to avoid undesired transformation when running the function multiple time + calf_ik_exist = get_edit_bone(calf_ik_name) + + calf_ik = create_edit_bone(calf_ik_name) + if calf_ik_exist == None: + copy_bone_transforms(calf, calf_ik) + calf_ik.head = thigh_ik.tail.copy() + calf_ik.tail = foot.head.copy() + calf_ik.parent = thigh_ik + calf_ik.use_connect = True + set_bone_layer(calf_ik, layer_intern_idx) + + + # align thigh and calf IK roll + # align calf_ik local Z + align_bone_z_axis(calf_ik, (calf_ik.head-leg_midpoint)) + # align thigh_ik on calf_ik + align_bone_z_axis(thigh_ik, calf_ik.z_axis) + # copy thigh_ik to c_thigh_fk + copy_bone_transforms(thigh_ik, c_thigh_fk) + + + # Calf FK Ctrl + c_calf_fk_name = c_prefix+leg_rig_names["calf_fk"]+_side + c_calf_fk = create_edit_bone(c_calf_fk_name) + copy_bone_transforms(calf_ik, c_calf_fk) + c_calf_fk.parent = c_thigh_fk + set_bone_layer(c_calf_fk, layer_ctrl_idx) + c_calf_fk["mixamo_ctrl"] = 1# tag as controller bone + + # Foot FK Ctrl + c_foot_fk_name = c_prefix+leg_rig_names["foot_fk"]+_side + c_foot_fk = create_edit_bone(c_foot_fk_name) + copy_bone_transforms(foot, c_foot_fk) + c_foot_fk.tail[2] = foot.head[2] + align_bone_z_axis(c_foot_fk, Vector((0,0,1))) + c_foot_fk.parent = c_calf_fk + set_bone_layer(c_foot_fk, layer_ctrl_idx) + c_foot_fk["mixamo_ctrl"] = 1# tag as controller bone + + # Foot FK + foot_fk_name = leg_rig_names["foot_fk"]+_side + foot_fk = create_edit_bone(foot_fk_name) + copy_bone_transforms(foot, foot_fk) + foot_fk.parent = c_foot_fk + set_bone_layer(foot_fk, layer_intern_idx) + + # Foot IK Ctrl + c_foot_ik_name = c_prefix+leg_rig_names["foot_ik"]+_side + c_foot_ik = create_edit_bone(c_foot_ik_name) + copy_bone_transforms(foot, c_foot_ik) + c_foot_ik.tail[2] = foot.head[2] + align_bone_z_axis(c_foot_ik, Vector((0,0,1))) + set_bone_layer(c_foot_ik, layer_ctrl_idx) + c_foot_ik["mixamo_ctrl"] = 1# tag as controller bone + + # Foot IK + foot_ik_name = leg_rig_names["foot_ik"]+_side + foot_ik = create_edit_bone(foot_ik_name) + copy_bone_transforms(foot, foot_ik) + foot_ik.parent = c_foot_ik + set_bone_layer(foot_ik, layer_intern_idx) + + # Foot Snap + foot_snap_name = leg_rig_names["foot_snap"]+_side + foot_snap = create_edit_bone(foot_snap_name) + copy_bone_transforms(c_foot_ik, foot_snap) + foot_snap.parent = foot_ik + set_bone_layer(foot_snap, layer_intern_idx) + + # Foot IK target + foot_ik_target_name = leg_rig_names["foot_ik_target"]+_side + foot_ik_target = create_edit_bone(foot_ik_target_name) + foot_ik_target.head = foot_ik.head.copy() + foot_vec = (foot.tail - foot.head) + foot_ik_target.tail = foot_ik_target.head - (foot_vec*0.25) + align_bone_z_axis(foot_ik_target, Vector((0,0,1))) + # parent set below (c_foot_01) + set_bone_layer(foot_ik_target, layer_intern_idx) + + # Foot Heel Out + heel_out_name = leg_rig_names["heel_out"]+_side + heel_out = create_edit_bone(heel_out_name) + heel_out.head, heel_out.tail = Vector((0,0,0)), Vector((0,0,1)) + heel_out.parent = c_foot_ik + set_bone_layer(heel_out, layer_intern_idx) + + # Foot Heel In + heel_in_name = leg_rig_names["heel_in"]+_side + heel_in = create_edit_bone(heel_in_name) + heel_in.head, heel_in.tail = Vector((0,0,0)), Vector((0,0,1)) + heel_in.parent = heel_out + set_bone_layer(heel_in, layer_intern_idx) + + # Foot Heel Mid + heel_mid_name = leg_rig_names["heel_mid"]+_side + heel_mid = create_edit_bone(heel_mid_name) + heel_mid.head, heel_mid.tail = Vector((0,0,0)), Vector((0,0,1)) + heel_mid.parent = heel_in + set_bone_layer(heel_mid, layer_intern_idx) + + heel_mid.head[0], heel_mid.head[1], heel_mid.head[2] = foot.head[0], foot.head[1], foot.tail[2] + heel_mid.tail = foot.tail.copy() + heel_mid.tail[2] = heel_mid.head[2] + heel_mid.tail = heel_mid.head + (heel_mid.tail-heel_mid.head)*0.5 + align_bone_x_axis(heel_mid, foot.x_axis) + + copy_bone_transforms(heel_mid, heel_in) + # use the foot x axis to determine "inside" vector, make sure it's pointing in the right direction for right and left side + fac = 1 + if side == "Right": + fac = -1 + + heel_in.head += foot.x_axis.normalized() * foot.length*0.3 * fac + heel_in.tail += foot.x_axis.normalized() * foot.length*0.3 * fac + + copy_bone_transforms(heel_mid, heel_out) + heel_out.head += foot.x_axis.normalized() * foot.length*0.3 * -fac + heel_out.tail += foot.x_axis.normalized() * foot.length*0.3 * -fac + + # Toe End + toes_end_name = leg_rig_names["toes_end"]+_side + toes_end = create_edit_bone(toes_end_name) + copy_bone_transforms(toe, toes_end) + toe_vec = (toes_end.tail-toes_end.head) + toes_end.tail += toe_vec + toes_end.head += toe_vec + toes_end.parent = heel_mid + set_bone_layer(toes_end, layer_intern_idx) + + # Toe End 01 + toes_end_01_name = leg_rig_names["toes_end_01"]+_side + toes_end_01 = create_edit_bone(toes_end_01_name) + copy_bone_transforms(toes_end, toes_end_01) + vec = toes_end_01.tail -toes_end_01.head + toes_end_01.tail = toes_end_01.head + (vec*0.5) + toes_end_01.parent = toes_end + set_bone_layer(toes_end_01, layer_intern_idx) + + # Foot 01 Ctrl + c_foot_01_name = c_prefix+leg_rig_names["foot_01"]+_side + c_foot_01 = create_edit_bone(c_foot_01_name) + copy_bone_transforms(foot, c_foot_01) + c_foot_01_vec = c_foot_01.tail - c_foot_01.head + c_foot_01.tail += c_foot_01_vec + c_foot_01.head += c_foot_01_vec + c_foot_01.parent = toes_end + set_bone_layer(c_foot_01, layer_ctrl_idx) + c_foot_01["mixamo_ctrl"] = 1# tag as controller bone + + # Foot_ik_target parent + foot_ik_target.parent = c_foot_01 + + # Foot 01 Pole + foot_01_pole_name = leg_rig_names["foot_01_pole"]+_side + foot_01_pole = create_edit_bone(foot_01_pole_name) + foot_01_pole.head = c_foot_01.head + (c_foot_01.z_axis * 0.05 * c_foot_01.length * 40) + foot_01_pole.tail = foot_01_pole.head + (c_foot_01.z_axis * 0.05 * c_foot_01.length * 40) + foot_01_pole.roll = radians(180) + foot_01_pole.parent = c_foot_01 + set_bone_layer(foot_01_pole, layer_intern_idx) + + # Toe IK Ctrl + c_toe_ik_name = c_prefix+leg_rig_names["toes_ik"]+_side + c_toe_ik = create_edit_bone(c_toe_ik_name) + copy_bone_transforms(toe, c_toe_ik) + c_toe_ik.parent = toes_end + set_bone_layer(c_toe_ik, layer_ctrl_idx) + c_toe_ik["mixamo_ctrl"] = 1# tag as controller bone + + # Toe Track + toe_track_name = leg_rig_names["toes_track"]+_side + toe_track = create_edit_bone(toe_track_name) + copy_bone_transforms(toe, toe_track) + toe_track.parent = foot_ik + set_bone_layer(toe_track, layer_intern_idx) + + # Toe_01 IK + toe_01_ik_name = leg_rig_names["toes_01_ik"]+_side + toe_01_ik = create_edit_bone(toe_01_ik_name) + copy_bone_transforms(toe, toe_01_ik) + toe_01_ik.tail = toe_01_ik.head + (toe_01_ik.tail-toe_01_ik.head)*0.5 + toe_01_ik.parent = toe_track + set_bone_layer(toe_01_ik, layer_intern_idx) + + # Toe_02 + toe_02_name = leg_rig_names["toes_02"]+_side + toe_02 = create_edit_bone(toe_02_name) + copy_bone_transforms(toe, toe_02) + toe_02.head = toe_02.head + (toe_02.tail-toe_02.head)*0.5 + toe_02.parent = toe_01_ik + set_bone_layer(toe_02, layer_intern_idx) + + # Toe FK Ctrl + c_toe_fk_name = c_prefix+leg_rig_names["toes_fk"]+_side + c_toe_fk = create_edit_bone(c_toe_fk_name) + copy_bone_transforms(toe, c_toe_fk) + c_toe_fk.parent = foot_fk + set_bone_layer(c_toe_fk, layer_ctrl_idx) + c_toe_fk["mixamo_ctrl"] = 1# tag as controller bone + + # Foot Roll Cursor Ctrl + c_foot_roll_cursor_name = c_prefix+leg_rig_names["foot_roll_cursor"]+_side + c_foot_roll_cursor = create_edit_bone(c_foot_roll_cursor_name) + copy_bone_transforms(c_foot_ik, c_foot_roll_cursor) + vec = c_foot_roll_cursor.tail - c_foot_roll_cursor.head + dist = 1.2 + c_foot_roll_cursor.head -= vec*dist + c_foot_roll_cursor.tail -= vec*dist + c_foot_roll_cursor.parent = c_foot_ik + set_bone_layer(c_foot_roll_cursor, layer_ctrl_idx) + c_foot_roll_cursor["mixamo_ctrl"] = 1# tag as controller bone + + # Pole IK Ctrl + c_pole_ik_name = c_prefix+leg_rig_names["pole_ik"]+_side + c_pole_ik = create_edit_bone(c_pole_ik_name) + set_bone_layer(c_pole_ik, layer_ctrl_idx) + c_pole_ik["mixamo_ctrl"] = 1# tag as controller bone + + plane_normal = (thigh_ik.head - calf_ik.tail) + prepole_dir = calf_ik.head - leg_midpoint + pole_pos = calf_ik.head + prepole_dir.normalized() + pole_pos = project_point_onto_plane(pole_pos, calf_ik.head, plane_normal) + pole_pos = calf_ik.head + ((pole_pos - calf_ik.head).normalized() * (calf_ik.head - thigh.head).magnitude * 1.7) + + c_pole_ik.head = pole_pos + 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)] + + ik_pole_angle = get_pole_angle(thigh_ik, calf_ik, c_pole_ik.head) + + + # -- Pose -- + bpy.ops.object.mode_set(mode='POSE') + + # Add constraints to control/mechanic bones + + # Calf IK + calf_ik_pb = get_pose_bone(calf_ik_name) + + cns_name = "IK" + ik_cns = calf_ik_pb.constraints.get(cns_name) + if ik_cns == None: + ik_cns = calf_ik_pb.constraints.new("IK") + ik_cns.name = cns_name + ik_cns.target = rig + ik_cns.subtarget = foot_ik_target_name + ik_cns.pole_target = rig + ik_cns.pole_subtarget = c_pole_ik_name + ik_cns.pole_angle = ik_pole_angle + ik_cns.chain_count = 2 + ik_cns.use_tail = True + ik_cns.use_stretch = False + + calf_ik_pb.lock_ik_y = True + calf_ik_pb.lock_ik_z = True + + + # Foot IK + foot_ik_pb = get_pose_bone(foot_ik_name) + + cns_name = "Copy Location" + copy_loc_cns = foot_ik_pb.constraints.get(cns_name) + if copy_loc_cns == None: + copy_loc_cns = foot_ik_pb.constraints.new("COPY_LOCATION") + copy_loc_cns.name = cns_name + copy_loc_cns.target = rig + copy_loc_cns.subtarget = calf_ik_name + copy_loc_cns.head_tail = 1.0 + + cns_name = "TrackTo" + cns = foot_ik_pb.constraints.get(cns_name) + if cns == None: + cns = foot_ik_pb.constraints.new("TRACK_TO") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_foot_01_name + cns.head_tail = 0.0 + cns.track_axis = "TRACK_Y" + cns.up_axis = "UP_Z" + cns.use_target_z = True + + cns_name = "Locked Track" + cns = foot_ik_pb.constraints.get(cns_name) + if cns == None: + cns = foot_ik_pb.constraints.new("LOCKED_TRACK") + cns.name = cns_name + cns.target = rig + cns.subtarget = foot_01_pole_name + cns.head_tail = 0.0 + cns.track_axis = "TRACK_Z" + cns.lock_axis = "LOCK_Y" + + cns_name = "Copy Scale" + cns = foot_ik_pb.constraints.get(cns_name) + if cns == None: + cns = foot_ik_pb.constraints.new("COPY_SCALE") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_foot_ik_name + + # Foot Ctrl IK + c_foot_ik_pb = get_pose_bone(c_foot_ik_name) + + cns_name = "Child Of" + cns = c_foot_ik_pb.constraints.get(cns_name) + if cns == None: + cns = c_foot_ik_pb.constraints.new("CHILD_OF") + cns.name = cns_name + cns.target = rig + cns.subtarget = "Ctrl_Master" + + + # Pole IK + c_pole_ik_pb = get_pose_bone(c_pole_ik_name) + + cns_name = "Child Of" + child_cns = c_pole_ik_pb.constraints.get(cns_name) + if child_cns == None: + child_cns = c_pole_ik_pb.constraints.new("CHILD_OF") + child_cns.name = cns_name + child_cns.target = rig + child_cns.subtarget = c_foot_ik_name + + cns_power = 8 + + # Toe End + toes_end_pb = get_pose_bone(toes_end_name) + len = toes_end_pb.length * cns_power + + cns_name = "Transformation" + cns = toes_end_pb.constraints.get(cns_name) + if cns == None: + cns = toes_end_pb.constraints.new("TRANSFORM") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_foot_roll_cursor_name + cns.use_motion_extrapolate = True + cns.target_space = cns.owner_space ="LOCAL" + cns.map_from = "LOCATION" + cns.from_min_z = 0.5 * len + cns.from_max_z = -0.5 * len + cns.map_to = "ROTATION" + cns.map_to_x_from = "Z" + cns.map_to_z_from = "X" + cns.to_min_x_rot = -2.61 + cns.to_max_x_rot = 2.61 + cns.mix_mode_rot = "ADD" + + cns_name = "Limit Rotation" + cns = toes_end_pb.constraints.get(cns_name) + if cns == None: + cns = toes_end_pb.constraints.new("LIMIT_ROTATION") + cns.name = cns_name + cns.owner_space ="LOCAL" + cns.use_limit_x = True + cns.min_x = -2*pi + cns.max_x = 0.0 + + # Toe 01 ik + toe_01_ik_pb = get_pose_bone(toe_01_ik_name) + + cns_name = "Copy Transforms" + cns = toe_01_ik_pb.constraints.get(cns_name) + if cns == None: + cns = toe_01_ik_pb.constraints.new("COPY_TRANSFORMS") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_toe_ik_name + cns.mix_mode = "REPLACE" + cns.target_space = cns.owner_space = "WORLD" + + # Toe 02 + toe_02_pb = get_pose_bone(toe_02_name) + + cns_name = "Copy CopyRotation" + cns = toe_02_pb.constraints.get(cns_name) + if cns == None: + cns = toe_02_pb.constraints.new("COPY_ROTATION") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_toe_ik_name + cns.mix_mode = "REPLACE" + cns.target_space = cns.owner_space = "WORLD" + + # Toe Track + toe_track_pb = get_pose_bone(toe_track_name) + + cns_name = "TrackTo" + cns = toe_track_pb.constraints.get(cns_name) + if cns == None: + cns = toe_track_pb.constraints.new("TRACK_TO") + cns.name = cns_name + cns.target = rig + cns.subtarget = toes_end_01_name + cns.head_tail = 0.0 + cns.track_axis = 'TRACK_Y' + cns.up_axis = "UP_Z" + cns.use_target_z = True + + # Heel Mid + heel_mid_pb = get_pose_bone(heel_mid_name) + len = heel_mid_pb.length * cns_power + + cns_name = "Transformation" + cns = heel_mid_pb.constraints.get(cns_name) + if cns == None: + cns = heel_mid_pb.constraints.new("TRANSFORM") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_foot_roll_cursor_name + cns.owner_space = cns.target_space = "LOCAL" + cns.map_from = "LOCATION" + cns.from_min_z = -0.25 * len + cns.from_max_z = 0.25 * len + cns.map_to = "ROTATION" + cns.map_to_x_from = "Z" + cns.map_to_y_from = "X" + cns.map_to_z_from = "Y" + cns.to_min_x_rot = radians(100) + cns.to_max_x_rot = -radians(100) + cns.mix_mode_rot = 'ADD' + + cns_name = "Limit Rotation" + cns = heel_mid_pb.constraints.get(cns_name) + if cns == None: + cns = heel_mid_pb.constraints.new("LIMIT_ROTATION") + cns.name = cns_name + cns.use_limit_x = True + cns.min_x = radians(0) + cns.max_x = radians(360) + cns.owner_space = "LOCAL" + + # Heel In + heel_in_pb = get_pose_bone(heel_in_name) + len = heel_in_pb.length * cns_power + + cns_name = "Transformation" + cns = heel_in_pb.constraints.get(cns_name) + if cns == None: + cns = heel_in_pb.constraints.new("TRANSFORM") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_foot_roll_cursor_name + cns.owner_space = cns.target_space = "LOCAL" + cns.map_from = "LOCATION" + cns.from_min_x = -0.25 * len + cns.from_max_x = 0.25 * len + cns.map_to = "ROTATION" + cns.map_to_x_from = "Z" + cns.map_to_y_from = "X" + cns.map_to_z_from = "Y" + cns.to_min_y_rot = -radians(100) + cns.to_max_y_rot = radians(100) + cns.mix_mode_rot = 'ADD' + + cns_name = "Limit Rotation" + cns = heel_in_pb.constraints.get(cns_name) + if cns == None: + cns = heel_in_pb.constraints.new("LIMIT_ROTATION") + cns.name = cns_name + cns.use_limit_y = True + + if side == "Left": + cns.min_y = 0.0 + cns.max_y = radians(90) + elif side == "Right": + cns.min_y = radians(-90) + cns.max_y = radians(0.0) + + cns.owner_space = "LOCAL" + + # Heel Out + heel_out_pb = get_pose_bone(heel_out_name) + len = heel_out_pb.length * cns_power + + cns_name = "Transformation" + cns = heel_out_pb.constraints.get(cns_name) + if cns == None: + cns = heel_out_pb.constraints.new("TRANSFORM") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_foot_roll_cursor_name + cns.owner_space = cns.target_space = "LOCAL" + cns.map_from = "LOCATION" + cns.from_min_x = -0.25 * len + cns.from_max_x = 0.25 * len + cns.map_to = "ROTATION" + cns.map_to_x_from = "Z" + cns.map_to_y_from = "X" + cns.map_to_z_from = "Y" + cns.to_min_y_rot = -radians(100) + cns.to_max_y_rot = radians(100) + cns.mix_mode_rot = 'ADD' + + cns_name = "Limit Rotation" + cns = heel_out_pb.constraints.get(cns_name) + if cns == None: + cns = heel_out_pb.constraints.new("LIMIT_ROTATION") + cns.name = cns_name + cns.use_limit_y = True + + if side == "Left": + cns.min_y = radians(-90) + cns.max_y = radians(0.0) + elif side == "Right": + cns.min_y = radians(0.0) + cns.max_y = radians(90) + + cns.owner_space = "LOCAL" + + + # Add constraints to Mixamo bones + foot_pb = get_pose_bone(foot_name) + thigh_pb = get_pose_bone(thigh_name) + + # IK-FK switch property + if not "ik_fk_switch" in c_foot_ik_pb.keys(): + 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") + + c_foot_ik_pb["ik_fk_switch"] = 0.0 if self.ik_legs else 1.0 + + # Thigh + cns_name = "IK_follow" + cns_ik = thigh_pb.constraints.get(cns_name) + if cns_ik == None: + cns_ik = thigh_pb.constraints.new("COPY_TRANSFORMS") + cns_ik.name = cns_name + cns_ik.target = rig + cns_ik.subtarget = thigh_ik_name + cns_ik.influence = 1.0 + + cns_name = "FK_follow" + cns_fk = thigh_pb.constraints.get(cns_name) + if cns_fk == None: + cns_fk = thigh_pb.constraints.new("COPY_TRANSFORMS") + cns_fk.name = cns_name + cns_fk.target = rig + cns_fk.subtarget = c_thigh_fk_name + cns_fk.influence = 0.0 + + 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") + + # Calf + calf_pb = get_pose_bone(calf_name) + + cns_name = "IK_follow" + cns_ik = calf_pb.constraints.get(cns_name) + if cns_ik == None: + cns_ik = calf_pb.constraints.new("COPY_TRANSFORMS") + cns_ik.name = cns_name + cns_ik.target = rig + cns_ik.subtarget = calf_ik_name + cns_ik.influence = 1.0 + + cns_name = "FK_follow" + cns_fk = calf_pb.constraints.get(cns_name) + if cns_fk == None: + cns_fk = calf_pb.constraints.new("COPY_TRANSFORMS") + cns_fk.name = cns_name + cns_fk.target = rig + cns_fk.subtarget = c_calf_fk_name + cns_fk.influence = 0.0 + + 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") + + # Foot + cns_name = "IK_follow" + cns_ik = foot_pb.constraints.get(cns_name) + if cns_ik == None: + cns_ik = foot_pb.constraints.new("COPY_TRANSFORMS") + cns_ik.name = cns_name + cns_ik.target = rig + cns_ik.subtarget = foot_ik_name + cns_ik.influence = 1.0 + + cns_name = "FK_follow" + cns_fk = foot_pb.constraints.get(cns_name) + if cns_fk == None: + cns_fk = foot_pb.constraints.new("COPY_TRANSFORMS") + cns_fk.name = cns_name + cns_fk.target = rig + cns_fk.subtarget = foot_fk_name + cns_fk.influence = 0.0 + + 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") + + + # Toe + toe_pb = get_pose_bone(toe_name) + + cns_name = "IK_Rot_follow" + cns_ik_rot = toe_pb.constraints.get(cns_name) + if cns_ik_rot == None: + cns_ik_rot = toe_pb.constraints.new("COPY_ROTATION") + cns_ik_rot.name = cns_name + cns_ik_rot.target = rig + cns_ik_rot.subtarget = c_toe_ik_name + cns_ik_rot.influence = 1.0 + + cns_name = "IK_Scale_follow" + cns_ik_scale = toe_pb.constraints.get(cns_name) + if cns_ik_scale == None: + cns_ik_scale = toe_pb.constraints.new("COPY_SCALE") + cns_ik_scale.name = cns_name + cns_ik_scale.target = rig + cns_ik_scale.subtarget = c_toe_ik_name + cns_ik_scale.influence = 1.0 + + cns_name_fk_rot = "FK_Rot_follow" + cns_fk_rot = toe_pb.constraints.get(cns_name_fk_rot) + if cns_fk_rot == None: + cns_fk_rot = toe_pb.constraints.new("COPY_ROTATION") + cns_fk_rot.name = cns_name_fk_rot + cns_fk_rot.target = rig + cns_fk_rot.subtarget = c_toe_fk_name + cns_fk_rot.influence = 1.0 + + cns_name_fk_scale = "FK_Scale_follow" + cns_fk_scale = toe_pb.constraints.get(cns_name_fk_scale) + if cns_fk_scale == None: + cns_fk_scale = toe_pb.constraints.new("COPY_SCALE") + cns_fk_scale.name = cns_name_fk_scale + cns_fk_scale.target = rig + cns_fk_scale.subtarget = c_toe_fk_name + cns_fk_scale.influence = 1.0 + + 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") + 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") + + + c_foot_01_pb = get_pose_bone(c_foot_01_name) + c_foot_roll_cursor_pb = get_pose_bone(c_foot_roll_cursor_name) + c_thigh_fk_pb = get_pose_bone(c_thigh_fk_name) + c_calf_fk_pb = get_pose_bone(c_calf_fk_name) + c_foot_fk_pb = get_pose_bone(c_foot_fk_name) + c_toe_ik_pb = get_pose_bone(c_toe_ik_name) + c_toe_fk_pb = get_pose_bone(c_toe_fk_name) + + + # Set transforms locks + lock_pbone_transform(c_foot_roll_cursor_pb, "location", [1]) + lock_pbone_transform(c_foot_roll_cursor_pb, "rotation", [0,1,2]) + lock_pbone_transform(c_foot_roll_cursor_pb, "scale", [0,1,2]) + + lock_pbone_transform(c_foot_01_pb, "location", [0,1,2]) + lock_pbone_transform(c_foot_01_pb, "rotation", [1,2]) + lock_pbone_transform(c_foot_01_pb, "scale", [0,1,2]) + + lock_pbone_transform(c_foot_fk_pb, "location", [0,1,2]) + + lock_pbone_transform(c_pole_ik_pb, "rotation", [0,1,2]) + lock_pbone_transform(c_pole_ik_pb, "scale", [0,1,2]) + + lock_pbone_transform(c_thigh_fk_pb, "location", [0,1,2]) + lock_pbone_transform(c_calf_fk_pb, "location", [0,1,2]) + + + 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] + + # Set custom shapes + set_bone_custom_shape(c_thigh_fk_pb, "cs_thigh_fk") + set_bone_custom_shape(c_calf_fk_pb, "cs_calf_fk") + set_bone_custom_shape(c_foot_ik_pb, "cs_foot") + set_bone_custom_shape(c_foot_fk_pb, "cs_foot") + set_bone_custom_shape(c_pole_ik_pb, "cs_sphere_012") + set_bone_custom_shape(c_foot_roll_cursor_pb, "cs_foot_roll") + set_bone_custom_shape(c_foot_01_pb, "cs_foot_01") + set_bone_custom_shape(c_toe_fk_pb, "cs_toe") + set_bone_custom_shape(c_toe_ik_pb, "cs_toe") + + # set custom shape drivers + ik_controls_names = [c_foot_ik_name, c_foot_01_name, c_toe_ik_name, c_foot_roll_cursor_name, c_pole_ik_name] + + arr_ids = [-1] + if blender_version._float >= 300: + arr_ids = [0, 1, 2] + + for n in ik_controls_names: + dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() + tar_dp = 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]' + for arr_id in arr_ids: + add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="1-var") + + fk_controls_names = [c_foot_fk_name, c_thigh_fk_name, c_calf_fk_name, c_toe_fk_name] + + for n in fk_controls_names: + dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() + tar_dp = 'pose.bones["'+c_foot_ik_name+'"]["ik_fk_switch"]' + for arr_id in arr_ids: + add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="var") + + + for pb in c_pbones_list: + # set rotation euler + pb.rotation_mode = "XYZ" + # set color group + set_bone_color_group(rig, pb, "body"+_side.lower()) + + + def add_arm(side): + print(" Add Arm", side) + _side = "_"+side + shoulder_name = get_mix_name(side+arm_names["shoulder"], use_name_prefix) + arm_name = get_mix_name(side+arm_names["arm"], use_name_prefix) + forearm_name = get_mix_name(side+arm_names["forearm"], use_name_prefix) + hand_name = get_mix_name(side+arm_names["hand"], use_name_prefix) + + + # -- Edit -- + bpy.ops.object.mode_set(mode='EDIT') + + + shoulder = get_edit_bone(shoulder_name) + arm = get_edit_bone(arm_name) + forearm = get_edit_bone(forearm_name) + hand = get_edit_bone(hand_name) + + + if not shoulder or not arm or not forearm or not hand: + print(" Arm bones are missing, skip arm: "+side) + return + + + # Create bones + # Fingers + fingers_names = [] + c_fingers_names = [] + fingers = [] + finger_leaves = [] + + for fname in fingers_type: + for i in range(1, 4): + finger_name = get_mix_name(side+"Hand"+fname+str(i), use_name_prefix) + finger = get_edit_bone(finger_name) + if finger == None: + continue + + fingers_names.append(finger_name) + fingers.append(finger) + c_finger_name = c_prefix+fname+str(i)+_side + c_fingers_names.append(c_finger_name) + c_finger = create_edit_bone(c_finger_name) + copy_bone_transforms(finger, c_finger) + set_bone_layer(c_finger, 0) + c_finger["mixamo_ctrl"] = 1# tag as controller bone + + if i == 1: + c_finger.parent = hand + else: + prev_finger_name = c_prefix+fname+str(i-1)+_side + prev_finger = get_edit_bone(prev_finger_name) + c_finger.parent = prev_finger + + # fingers "leaves"/tip bones + for fname in fingers_type: + finger_name = get_mix_name(side+"Hand"+fname+"4", use_name_prefix) + finger_leaf = get_edit_bone(finger_name) + finger_leaves.append(finger_leaf) + + # Set Mixamo bones in layer + for b in [shoulder, arm, forearm, hand] + fingers + finger_leaves: + set_bone_layer(b, layer_mix_idx) + + + # Shoulder Ctrl + c_shoulder_name = c_prefix+arm_rig_names["shoulder"]+_side + c_shoulder = create_edit_bone(c_shoulder_name) + copy_bone_transforms(shoulder, c_shoulder) + c_shoulder.parent = get_edit_bone(c_prefix+spine_rig_names["spine3"]) + set_bone_layer(c_shoulder, layer_ctrl_idx) + c_shoulder["mixamo_ctrl"] = 1# tag as controller bone + + # Arm IK + arm_ik_name = arm_rig_names["arm_ik"]+_side + arm_ik = create_edit_bone(arm_ik_name) + copy_bone_transforms(arm, arm_ik) + + # correct straight arms angle, need minimum 0.1 degrees for IK constraints to work + angle_min = 0.1 + + def get_arm_angle(): + #return degrees(arm.y_axis.angle(forearm.y_axis)) + vec1 = forearm.head - arm.head + vec2 = hand.head - forearm.head + return degrees(vec1.angle(vec2)) + + arm_angle = get_arm_angle() + + if arm_angle < angle_min: + print(" ! Straight arm bones, angle = "+str(arm_angle)) + + max_iter = 10000 + i = 0 + + while arm_angle < angle_min and i < max_iter: + + dir = ((arm.x_axis + forearm.x_axis)*0.5).normalized() + if side == "Right": + dir *= -1 + + forearm.head += dir * (forearm.tail-forearm.head).magnitude * 0.0001 + arm_angle = get_arm_angle() + i += 1 + + print(" corrected arm angle: "+str(arm_angle)) + + # auto-align knee position with global Y axis to ensure IK pole vector is physically correct + arm_axis = forearm.tail - arm.head + arm_midpoint = (arm.head + forearm.tail) * 0.5 + #cur_vec = forearm.head - arm_midpoint + #cur_vec[0] = 0.0 + #global_y_vec = Vector((0, 1, 0)) + + dir = forearm.head - arm_midpoint + cur_vec = project_vector_onto_plane(dir, arm_axis) + global_y_vec = project_vector_onto_plane(Vector((0, 1, 0)), arm_axis) + signed_cur_angle = signed_angle(cur_vec, global_y_vec, arm_axis) + print(" IK correc angle:", degrees(signed_cur_angle)) + + # rotate + rotated_point = rotate_point(forearm.head.copy(), -signed_cur_angle, arm_midpoint, arm_axis) + """ + rot_mat = Matrix.Rotation(-signed_cur_angle, 4, arm_axis.normalized()) + # rotate in world origin space + offset_vec = -arm_midpoint + offset_elbow = forearm.head + offset_vec + # rotate + rotated_point = rot_mat @ offset_elbow + # bring back to original space + rotated_point = rotated_point -offset_vec + """ + + # (check) + dir = rotated_point - arm_midpoint + cur_vec = project_vector_onto_plane(dir, arm_axis) + signed_cur_angle = signed_angle(cur_vec, global_y_vec, arm_axis) + print(" IK corrected angle:", degrees(signed_cur_angle)) + + arm_ik.tail = rotated_point + + arm_ik.parent = c_shoulder + set_bone_layer(arm_ik, layer_intern_idx) + + + # Arm FK Ctrl + c_arm_fk_name = c_prefix+arm_rig_names["arm_fk"]+_side + c_arm_fk = create_edit_bone(c_arm_fk_name) + c_arm_fk.parent = get_edit_bone(c_prefix+spine_rig_names["spine3"]) + copy_bone_transforms(arm_ik, c_arm_fk) + set_bone_layer(c_arm_fk, layer_ctrl_idx) + c_arm_fk["mixamo_ctrl"] = 1# tag as controller bone + + + # ForeArm IK + forearm_ik_name = arm_rig_names["forearm_ik"]+_side + forearm_ik = create_edit_bone(forearm_ik_name) + copy_bone_transforms(forearm, forearm_ik) + forearm_ik.head = arm_ik.tail.copy() + forearm_ik.tail = hand.head.copy() + forearm_ik.parent = arm_ik + set_bone_layer(forearm_ik, layer_intern_idx) + + + # align arm and forearm IK roll + # align forearm_ik local Z + align_bone_x_axis(forearm_ik, (forearm_ik.head-arm_midpoint)) + # align arm_ik on forearm_ik + align_bone_x_axis(arm_ik, forearm_ik.x_axis) + # copy arm_ik to c_arm_fk + copy_bone_transforms(arm_ik, c_arm_fk) + + if side == "Right": + forearm_ik.roll += radians(180) + arm_ik.roll += radians(180) + c_arm_fk.roll += radians(180) + + # Forearm FK Ctrl + c_forearm_fk_name = c_prefix+arm_rig_names["forearm_fk"]+_side + c_forearm_fk = create_edit_bone(c_forearm_fk_name) + copy_bone_transforms(forearm_ik, c_forearm_fk) + c_forearm_fk.parent = c_arm_fk + set_bone_layer(c_forearm_fk, layer_ctrl_idx) + c_forearm_fk["mixamo_ctrl"] = 1# tag as controller bone + + + # Pole IK Ctrl + c_pole_ik_name = c_prefix+arm_rig_names["pole_ik"]+_side + c_pole_ik = create_edit_bone(c_pole_ik_name) + set_bone_layer(c_pole_ik, layer_ctrl_idx) + c_pole_ik["mixamo_ctrl"] = 1# tag as controller bone + + arm_midpoint = (arm_ik.head + forearm_ik.tail) * 0.5 + + plane_normal = (arm_ik.head - forearm_ik.tail) + prepole_dir = forearm_ik.head - arm_midpoint + pole_pos = forearm_ik.head + prepole_dir.normalized() + pole_pos = project_point_onto_plane(pole_pos, forearm_ik.head, plane_normal) + pole_pos = forearm_ik.head + ((pole_pos - forearm_ik.head).normalized() * (forearm_ik.head - arm.head).magnitude * 1.0) + + c_pole_ik.head = pole_pos + 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)] + + ik_pole_angle = get_pole_angle(arm_ik, forearm_ik, c_pole_ik.head) + + # Hand IK Ctrl + c_hand_ik_name = c_prefix + arm_rig_names["hand_ik"]+_side + c_hand_ik = create_edit_bone(c_hand_ik_name) + set_bone_layer(c_hand_ik, layer_ctrl_idx) + copy_bone_transforms(hand, c_hand_ik) + c_hand_ik["mixamo_ctrl"] = 1# tag as controller bone + + # Hand FK Ctrl + c_hand_fk_name = c_prefix+arm_rig_names["hand_fk"]+_side + c_hand_fk = create_edit_bone(c_hand_fk_name) + copy_bone_transforms(hand, c_hand_fk) + c_hand_fk.parent = c_forearm_fk + set_bone_layer(c_hand_fk, layer_ctrl_idx) + c_hand_fk["mixamo_ctrl"] = 1# tag as controller bone + + # ---- Pose ---- + bpy.ops.object.mode_set(mode='POSE') + + + # Add constraints to control/mechanic bones + c_shoulder_pb = get_pose_bone(c_shoulder_name) + shoulder_pb = get_pose_bone(shoulder_name) + c_arm_fk_pb = get_pose_bone(c_arm_fk_name) + forearm_ik_pb = get_pose_bone(forearm_ik_name) + c_pole_ik_pb = get_pose_bone(c_pole_ik_name) + c_hand_ik_pb = get_pose_bone(c_hand_ik_name) + + + # Arm FK Ctrl + cns_name = "Copy Location" + cns = c_arm_fk_pb.constraints.get(cns_name) + if cns == None: + cns = c_arm_fk_pb.constraints.new("COPY_LOCATION") + cns.name = cns_name + cns.head_tail = 1.0 + cns.target = rig + cns.subtarget = c_shoulder_name + + # Forearm IK + cns_name = "IK" + ik_cns = forearm_ik_pb.constraints.get(cns_name) + if ik_cns == None: + ik_cns = forearm_ik_pb.constraints.new("IK") + ik_cns.name = cns_name + ik_cns.target = rig + ik_cns.subtarget = c_hand_ik_name + ik_cns.pole_target = rig + ik_cns.pole_subtarget = c_pole_ik_name + ik_cns.pole_angle = 0.0 + if side == "Right": + ik_cns.pole_angle = radians(180) + ik_cns.chain_count = 2 + ik_cns.use_tail = True + ik_cns.use_stretch = False + + forearm_ik_pb.lock_ik_y = True + forearm_ik_pb.lock_ik_x = True + + + # Pole IK Ctrl + cns_name = "Child Of" + cns = c_pole_ik_pb.constraints.get(cns_name) + if cns == None: + cns = c_pole_ik_pb.constraints.new("CHILD_OF") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_prefix+spine_rig_names["pelvis"] + + + # Hand IK Ctrl + cns_name = "Child Of" + cns = c_hand_ik_pb.constraints.get(cns_name) + if cns == None: + cns = c_hand_ik_pb.constraints.new("CHILD_OF") + cns.name = cns_name + cns.target = rig + cns.subtarget = c_master_name + + + # Add constraints to Mixamo bones + hand_pb = get_pose_bone(hand_name) + + # Fingers + for i, fname in enumerate(c_fingers_names): + c_finger_pb = get_pose_bone(fname) + finger_pb = get_pose_bone(fingers_names[i]) + add_copy_transf(finger_pb, rig, c_finger_pb.name) + + + # Shoulder + add_copy_transf(shoulder_pb, rig, c_shoulder_pb.name) + + + # IK-FK switch property + if not "ik_fk_switch" in c_hand_ik_pb.keys(): + 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") + + c_hand_ik_pb["ik_fk_switch"] = 0.0 if self.ik_arms else 1.0 + + + # Arm + arm_pb = get_pose_bone(arm_name) + + cns_ik_name = "IK_follow" + cns_ik = arm_pb.constraints.get(cns_ik_name) + if cns_ik == None: + cns_ik = arm_pb.constraints.new("COPY_TRANSFORMS") + cns_ik.name = cns_ik_name + cns_ik.target = rig + cns_ik.subtarget = arm_ik_name + cns_ik.influence = 1.0 + + cns_fk_name = "FK_Follow" + cns_fk = arm_pb.constraints.get(cns_fk_name) + if cns_fk == None: + cns_fk = arm_pb.constraints.new("COPY_TRANSFORMS") + cns_fk.name = cns_fk_name + cns_fk.target = rig + cns_fk.subtarget = c_arm_fk_name + cns_fk.influence = 0.0 + + 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") + + + # ForeArm + forearm_pb = get_pose_bone(forearm_name) + + cns_ik_name = "IK_follow" + cns_ik = forearm_pb.constraints.get(cns_ik_name) + if cns_ik == None: + cns_ik = forearm_pb.constraints.new("COPY_TRANSFORMS") + cns_ik.name = cns_ik_name + cns_ik.target = rig + cns_ik.subtarget = forearm_ik_name + cns_ik.influence = 1.0 + + cns_fk_name = "FK_Follow" + cns_fk = forearm_pb.constraints.get(cns_fk_name) + if cns_fk == None: + cns_fk = forearm_pb.constraints.new("COPY_TRANSFORMS") + cns_fk.name = cns_fk_name + cns_fk.target = rig + cns_fk.subtarget = c_forearm_fk_name + cns_fk.influence = 0.0 + + 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") + + c_arm_fk_pb = get_pose_bone(c_arm_fk_name) + c_forearm_fk_pb = get_pose_bone(c_forearm_fk_name) + + lock_pbone_transform(c_forearm_fk_pb, "location", [0,1,2]) + + # Hand + cns_ik_name = "IK_follow" + cns_ik = hand_pb.constraints.get(cns_ik_name) + if cns_ik == None: + cns_ik = hand_pb.constraints.new("COPY_ROTATION") + cns_ik.name = cns_ik_name + cns_ik.target = rig + cns_ik.subtarget = c_hand_ik_name + cns_ik.influence = 1.0 + + cns_fk_name = "FK_Follow" + cns_fk = hand_pb.constraints.get(cns_fk_name) + if cns_fk == None: + cns_fk = hand_pb.constraints.new("COPY_ROTATION") + cns_fk.name = cns_fk_name + cns_fk.target = rig + cns_fk.subtarget = c_hand_fk_name + cns_fk.influence = 0.0 + + 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") + + c_hand_fk_pb = get_pose_bone(c_hand_fk_name) + lock_pbone_transform(c_hand_fk_pb, "location", [0,1,2]) + + # Set custom shapes + c_hand_ik_pb = get_pose_bone(c_hand_ik_name) + set_bone_custom_shape(c_shoulder_pb, "cs_shoulder_"+side.lower()) + set_bone_custom_shape(c_arm_fk_pb, "cs_arm_fk") + set_bone_custom_shape(c_forearm_fk_pb, "cs_forearm_fk") + set_bone_custom_shape(c_pole_ik_pb, "cs_sphere_012") + set_bone_custom_shape(c_hand_fk_pb, "cs_hand") + set_bone_custom_shape(c_hand_ik_pb, "cs_hand") + + c_fingers_pb = [] + + for fname in c_fingers_names: + finger_pb = get_pose_bone(fname) + c_fingers_pb.append(finger_pb) + set_bone_custom_shape(finger_pb, "cs_circle_025") + + 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 + + + # set custom shape drivers + ik_controls_names = [c_pole_ik_name, c_hand_ik_name] + + arr_ids = [-1] + if blender_version._float >= 300: + arr_ids = [0, 1, 2] + + for n in ik_controls_names: + dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() + tar_dp = 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]' + for arr_id in arr_ids: + add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="1-var") + + fk_controls_names = [c_arm_fk_name, c_forearm_fk_name, c_hand_fk_name] + + for n in fk_controls_names: + dr_dp = 'pose.bones["'+n+'"].'+get_custom_shape_scale_prop_name() + tar_dp = 'pose.bones["'+c_hand_ik_name+'"]["ik_fk_switch"]' + for arr_id in arr_ids: + add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=arr_id, exp="var") + + + for pb in c_pbones_list: + # set rotation euler + pb.rotation_mode = "XYZ" + # set color group + set_bone_color_group(rig, pb, "body"+_side.lower()) + + + add_master() + add_spine() + add_head() + add_arm("Left") + add_arm("Right") + add_leg("Left") + add_leg("Right") + + # tag the armature with a custom prop to specify the control rig is built + rig.data["mr_control_rig"] = True + + +def _zero_out(): + print("\nZeroing out...") + scn = bpy.context.scene + arm = bpy.data.objects.get(bpy.context.active_object.name) + + print(" Clear anim") + # Clear animation data + action = None + if arm.animation_data: + if arm.animation_data.action: + action = arm.animation_data.action + + if action: + while len(action.fcurves): + action.fcurves.remove(action.fcurves[0]) + + print(" Clear pose") + # Reset pose + bpy.ops.object.mode_set(mode='POSE') + + for b in arm.pose.bones: + b.location = [0,0,0] + b.rotation_euler = [0,0,0] + b.rotation_quaternion = [1,0,0,0] + b.scale = [1,1,1] + + print("Zeroed out.") + + +def _bake_anim(self): + scn = bpy.context.scene + + # get min-max frame range + rig = bpy.context.active_object + + if rig.animation_data == None: + print("No animation data, exit bake") + return + + if rig.animation_data.nla_tracks == None: + print("No NLA tracks found, exit bake") + return + + tracks = rig.animation_data.nla_tracks + + fs = None + fe = None + + # from NLA tracks + for track in tracks: + for strip in track.strips: + if fs == None: + fs = strip.frame_start + if fe == None: + fe = strip.frame_end + + if strip.frame_start < fs: + fs = strip.frame_start + if strip.frame_end > fe: + fe = strip.frame_end + + + # get active action frame range + act = rig.animation_data.action + + if fs == None or fe == None: + print("No NLA tracks found, exit") + return + + if act.frame_range[0] < fs: + fs = act.frame_range[0] + if act.frame_range[1] > fe: + fe = act.frame_range[1] + + # select only controllers bones + bpy.ops.object.mode_set(mode='POSE') + + bpy.ops.pose.select_all(action='DESELECT') + + found_ctrl = False + for pbone in rig.pose.bones: + if "mixamo_ctrl" in pbone.bone.keys(): + rig.data.bones.active = pbone.bone + pbone.bone.select = True + found_ctrl = True + + if not found_ctrl:# backward compatibility, use layer 0 instead + print("Ctrl bones not tagged, search in layer 0 instead...") + for pbone in rig.pose.bones: + if pbone.bone.layers[0]: + rig.data.bones.active = pbone.bone + pbone.bone.select = True + + scn.frame_set(fs) + bpy.context.view_layer.update() + + # bake NLA strips + print("Baking, frame start:", fs, ",frame end", fe) + bpy.ops.nla.bake(frame_start=fs, frame_end=fe, step=1, only_selected=True, visual_keying=False, clear_constraints=False, clear_parents=False, use_current_action=False, clean_curves=False, bake_types={'POSE'}) + + # remove tracks + while len(tracks): + rig.animation_data.nla_tracks.remove(tracks[0]) + + +def redefine_source_rest_pose(src_arm, tar_arm): + print(" Redefining source rest pose...") + + scn = bpy.context.scene + + src_arm_loc = src_arm.location.copy() + src_arm.location = [0,0,0] + fr_range = src_arm.animation_data.action.frame_range + fr_start = int(fr_range[0]) + fr_end = int(fr_range[1]) + + # duplicate source armature + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(src_arm.name) + bpy.ops.object.mode_set(mode='OBJECT') + duplicate_object() + src_arm_dupli = get_object(bpy.context.active_object.name) + src_arm_dupli["mix_to_del"] = True + + + """ + # Store bone matrices + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(src_arm.name) + bpy.ops.object.mode_set(mode='POSE') + + bones_data = [] + + for f in range(fr_start, fr_end+1): + print("Frame", f) + scn.frame_set(f) + bpy.context.view_layer.update() + + bones_matrices = {} + + for pbone in src_arm.pose.bones: + bones_matrices[pbone.name] = pbone.matrix.copy() + #bones_matrices[pbone.name] = src_arm.convert_space(pose_bone=pbone, matrix=pbone.matrix, from_space="POSE", to_space="LOCAL") + + + bones_data.append((f, bones_matrices)) + """ + + # Store target bones rest transforms + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(tar_arm.name) + bpy.ops.object.mode_set(mode='EDIT') + + rest_bones = {} + + for ebone in tar_arm.data.edit_bones: + rest_bones[ebone.name] = ebone.head.copy(), ebone.tail.copy(), vec_roll_to_mat3(ebone.y_axis, ebone.roll) + + # Apply source bones rest transforms + print(" Set rest pose...") + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(src_arm.name) + bpy.ops.object.mode_set(mode='EDIT') + + for bname in rest_bones: + ebone = get_edit_bone(bname) + + if ebone == None: + #print("Warning, bone not found on source armature:", bname) + continue + + head, tail, mat3 = rest_bones[bname] + 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) + + + # Add constraints + bpy.ops.object.mode_set(mode='POSE') + + for pb in src_arm.pose.bones: + cns = pb.constraints.new("COPY_TRANSFORMS") + cns.name = "temp" + cns.target = src_arm_dupli + cns.subtarget = pb.name + + # Restore animation + print("Restore animation...") + bake_anim(frame_start=fr_start, frame_end=fr_end, only_selected=False, bake_bones=True, bake_object=False) + + # Restore location + src_arm.location = src_arm_loc + + # Delete temp data + # constraints + for pb in src_arm.pose.bones: + if len(pb.constraints): + cns = pb.constraints.get("temp") + if cns: + pb.constraints.remove(cns) + + # src_arm_dupli + delete_object(src_arm_dupli) + + print(" Source armature rest pose redefined.") + + +def _import_anim(src_arm, tar_arm, import_only=False): + print("\nImporting animation...") + scn = bpy.context.scene + + if src_arm.animation_data == None: + print(" No action found on the source armature") + return + + if src_arm.animation_data.action == None: + print(" No action found on the source armature") + return + + if len(src_arm.animation_data.action.fcurves) == 0: + print(" No keyframes to import") + return + + use_name_prefix = True + + # Redefine source armature rest pose if importing only animation, since + # Mixamo Fbx may have different rest pose when the Fbx file contains only animation data + if import_only: + redefine_source_rest_pose(src_arm, tar_arm) + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(tar_arm.name) + bpy.ops.object.mode_set(mode='POSE') + + hand_left_name = get_mix_name("LeftHand", use_name_prefix) + hand_right_name = get_mix_name("RightHand", use_name_prefix) + foot_left_name = get_mix_name("LeftFoot", use_name_prefix) + foot_right_name = get_mix_name("RightFoot", use_name_prefix) + + hand_left_pb = get_pose_bone(hand_left_name) + c_hand_ik_left_pb = get_pose_bone(c_prefix + arm_rig_names["hand_ik"]+"_Left") + hand_right_pb = get_pose_bone(hand_right_name) + c_hand_ik_right_pb = get_pose_bone(c_prefix + arm_rig_names["hand_ik"]+"_Right") + foot_left_pb = get_pose_bone(foot_left_name) + c_foot_ik_left_pb = get_pose_bone(c_prefix + leg_rig_names["foot_ik"]+"_Left") + foot_right_pb = get_pose_bone(foot_right_name) + c_foot_ik_right_pb = get_pose_bone(c_prefix + leg_rig_names["foot_ik"]+"_Right") + + arm_left_kinematic = "IK" if c_hand_ik_left_pb["ik_fk_switch"] < 0.5 else "FK" + arm_right_kinematic = "IK" if c_hand_ik_right_pb["ik_fk_switch"] < 0.5 else "FK" + leg_left_kinematic = "IK" if c_foot_ik_left_pb["ik_fk_switch"] < 0.5 else "FK" + leg_right_kinematic = "IK" if c_foot_ik_right_pb["ik_fk_switch"] < 0.5 else "FK" + + + # Set bones mapping for retargetting + bones_map = {} + + bones_map[get_mix_name("Hips", use_name_prefix)] = c_prefix+"Hips" + bones_map[get_mix_name("Spine", use_name_prefix)] = c_prefix+"Spine" + bones_map[get_mix_name("Spine1", use_name_prefix)] = c_prefix+"Spine1" + bones_map[get_mix_name("Spine2", use_name_prefix)] = c_prefix+"Spine2" + bones_map[get_mix_name("Neck", use_name_prefix)] = c_prefix+"Neck" + bones_map[get_mix_name("Head", use_name_prefix)] = c_prefix+"Head" + bones_map[get_mix_name("LeftShoulder", use_name_prefix)] = c_prefix+"Shoulder_Left" + bones_map[get_mix_name("RightShoulder", use_name_prefix)] = c_prefix+"Shoulder_Right" + + # Arm + if arm_left_kinematic == "FK": + bones_map[get_mix_name("LeftArm", use_name_prefix)] = c_prefix+"Arm_FK_Left" + bones_map[get_mix_name("LeftForeArm", use_name_prefix)] = c_prefix+"ForeArm_FK_Left" + bones_map[get_mix_name("LeftHand", use_name_prefix)] = c_prefix+"Hand_FK_Left" + elif arm_left_kinematic == "IK": + bones_map[c_prefix+"Hand_IK_Left"] = c_prefix+"Hand_IK_Left" + + if arm_right_kinematic == "FK": + bones_map[get_mix_name("RightArm", use_name_prefix)] = c_prefix+"Arm_FK_Right" + bones_map[get_mix_name("RightForeArm", use_name_prefix)] = c_prefix+"ForeArm_FK_Right" + bones_map[get_mix_name("RightHand", use_name_prefix)] = c_prefix+"Hand_FK_Right" + elif arm_right_kinematic == "IK": + bones_map[c_prefix+"Hand_IK_Right"] = c_prefix+"Hand_IK_Right" + + # Fingers + bones_map[get_mix_name("LeftHandThumb1", use_name_prefix)] = c_prefix+"Thumb1_Left" + bones_map[get_mix_name("LeftHandThumb2", use_name_prefix)] = c_prefix+"Thumb2_Left" + bones_map[get_mix_name("LeftHandThumb3", use_name_prefix)] = c_prefix+"Thumb3_Left" + bones_map[get_mix_name("LeftHandIndex1", use_name_prefix)] = c_prefix+"Index1_Left" + bones_map[get_mix_name("LeftHandIndex2", use_name_prefix)] = c_prefix+"Index2_Left" + bones_map[get_mix_name("LeftHandIndex3", use_name_prefix)] = c_prefix+"Index3_Left" + bones_map[get_mix_name("LeftHandMiddle1", use_name_prefix)] = c_prefix+"Middle1_Left" + bones_map[get_mix_name("LeftHandMiddle2", use_name_prefix)] = c_prefix+"Middle2_Left" + bones_map[get_mix_name("LeftHandMiddle3", use_name_prefix)] = c_prefix+"Middle3_Left" + bones_map[get_mix_name("LeftHandRing1", use_name_prefix)] = c_prefix+"Ring1_Left" + bones_map[get_mix_name("LeftHandRing2", use_name_prefix)] = c_prefix+"Ring2_Left" + bones_map[get_mix_name("LeftHandRing3", use_name_prefix)] = c_prefix+"Ring3_Left" + bones_map[get_mix_name("LeftHandPinky1", use_name_prefix)] = c_prefix+"Pinky1_Left" + bones_map[get_mix_name("LeftHandPinky2", use_name_prefix)] = c_prefix+"Pinky2_Left" + bones_map[get_mix_name("LeftHandPinky3", use_name_prefix)] = c_prefix+"Pinky3_Left" + bones_map[get_mix_name("RightHandThumb1", use_name_prefix)] = c_prefix+"Thumb1_Right" + bones_map[get_mix_name("RightHandThumb2", use_name_prefix)] = c_prefix+"Thumb2_Right" + bones_map[get_mix_name("RightHandThumb3", use_name_prefix)] = c_prefix+"Thumb3_Right" + bones_map[get_mix_name("RightHandIndex1", use_name_prefix)] = c_prefix+"Index1_Right" + bones_map[get_mix_name("RightHandIndex2", use_name_prefix)] = c_prefix+"Index2_Right" + bones_map[get_mix_name("RightHandIndex3", use_name_prefix)] = c_prefix+"Index3_Right" + bones_map[get_mix_name("RightHandMiddle1", use_name_prefix)] = c_prefix+"Middle1_Right" + bones_map[get_mix_name("RightHandMiddle2", use_name_prefix)] = c_prefix+"Middle2_Right" + bones_map[get_mix_name("RightHandMiddle3", use_name_prefix)] = c_prefix+"Middle3_Right" + bones_map[get_mix_name("RightHandRing1", use_name_prefix)] = c_prefix+"Ring1_Right" + bones_map[get_mix_name("RightHandRing2", use_name_prefix)] = c_prefix+"Ring2_Right" + bones_map[get_mix_name("RightHandRing3", use_name_prefix)] = c_prefix+"Ring3_Right" + bones_map[get_mix_name("RightHandPinky1", use_name_prefix)] = c_prefix+"Pinky1_Right" + bones_map[get_mix_name("RightHandPinky2", use_name_prefix)] = c_prefix+"Pinky2_Right" + bones_map[get_mix_name("RightHandPinky3", use_name_prefix)] = c_prefix+"Pinky3_Right" + + if leg_left_kinematic == "FK": + bones_map[get_mix_name("LeftUpLeg", use_name_prefix)] = c_prefix+"UpLeg_FK_Left" + bones_map[get_mix_name("LeftLeg", use_name_prefix)] = c_prefix+"Leg_FK_Left" + bones_map[c_prefix+"Foot_FK_Left"] = c_prefix+"Foot_FK_Left" + bones_map[get_mix_name("LeftToeBase", use_name_prefix)] = c_prefix+"Toe_FK_Left" + elif leg_left_kinematic == "IK": + bones_map[c_prefix+"Foot_IK_Left"] = c_prefix+"Foot_IK_Left" + bones_map[get_mix_name("LeftToeBase", use_name_prefix)] = c_prefix+"Toe_IK_Left" + + if leg_right_kinematic == "FK": + bones_map[get_mix_name("RightUpLeg", use_name_prefix)] = c_prefix+"UpLeg_FK_Right" + bones_map[get_mix_name("RightLeg", use_name_prefix)] = c_prefix+"Leg_FK_Right" + bones_map[c_prefix+"Foot_FK_Right"] = c_prefix+"Foot_FK_Right" + bones_map[get_mix_name("RightToeBase", use_name_prefix)] = c_prefix+"Toe_FK_Right" + elif leg_right_kinematic == "IK": + bones_map[c_prefix+"Foot_IK_Right"] = c_prefix+"Foot_IK_Right" + bones_map[get_mix_name("RightToeBase", use_name_prefix)] = c_prefix+"Toe_IK_Right" + + + + action = None + if src_arm.animation_data == None: + print(" No action found on the source armature") + if src_arm.animation_data.action == None: + print(" No action found on the source armature") + + # Work on a source armature duplicate + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(src_arm.name) + + duplicate_object() + src_arm_copy_name = src_arm.name+"_COPY" + bpy.context.active_object.name = src_arm_copy_name + src_arm = get_object(src_arm_copy_name) + src_arm["mix_to_del"] = True + + # Get anim data + action = src_arm.animation_data.action + fr_start = int(action.frame_range[0]) + fr_end = int(action.frame_range[1]) + + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(tar_arm.name) + + # Store bones data from target armature + bpy.ops.object.mode_set(mode='EDIT') + + ctrl_matrices = {} + ik_bones_data = {} + + 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"]} + for b in kinematics: + type, kin_mode, side = kinematics[b] + ctrl_name = c_prefix+type+'_'+kin_mode+'_'+side + ctrl_ebone = get_edit_bone(ctrl_name) + mix_bone_name = get_mix_name(side+type, use_name_prefix) + + ctrl_matrices[ctrl_name] = ctrl_ebone.matrix.copy(), mix_bone_name + + # store corrected ik bones + if kin_mode == "IK": + ik_bones = {} + ik_chain = [] + + if type == "Foot": + ik_chain = ["UpLeg_IK_"+side, "Leg_IK_"+side] + elif type == "Hand": + ik_chain = ["Arm_IK_"+side, "ForeArm_IK_"+side] + + ik1 = get_edit_bone(ik_chain[0]) + ik2 = get_edit_bone(ik_chain[1]) + + ik_bones["ik1"] = ik1.name, ik1.head.copy(), ik1.tail.copy(), ik1.roll + ik_bones["ik2"] = ik2.name, ik2.head.copy(), ik2.tail.copy(), ik2.roll + ik_bones_data[b] = type, side, ik_bones + + + # Init source armature rotation and scale + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + + set_active_object(src_arm.name) + + scale_fac = src_arm.scale[0] + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + for fc in action.fcurves: + dp = fc.data_path + if dp.startswith('pose.bones') and dp.endswith(".location"): + for k in fc.keyframe_points: + k.co[1] *= scale_fac + + + bpy.ops.object.mode_set(mode='EDIT') + + # Add helper source bones + # add feet bones helpers + for name in ctrl_matrices: + foot_ebone = create_edit_bone(name) + foot_ebone.head, foot_ebone.tail = [0,0,0], [0,0,0.1] + foot_ebone.matrix = ctrl_matrices[name][0] + foot_ebone.parent = get_edit_bone(ctrl_matrices[name][1]) + + # add IK bones helpers + for b in ik_bones_data: + type, side, ik_bones = ik_bones_data[b] + for bone_type in ik_bones: + bname, bhead, btail, broll = ik_bones[bone_type] + ebone = create_edit_bone(bname) + ebone.head, ebone.tail, ebone.roll = bhead, btail, broll + + # set parents + for b in ik_bones_data: + type, side, ik_bones = ik_bones_data[b] + ik2_name = ik_bones["ik2"][0] + ik2 = get_edit_bone(ik2_name) + + # set constraints + bpy.ops.object.mode_set(mode='POSE') + + bake_ik_data = {"src_arm":src_arm} + + for b in ik_bones_data: + type, side, ik_bones = ik_bones_data[b] + b1_name = ik_bones["ik1"][0] + b2_name = ik_bones["ik2"][0] + b1_pb = get_pose_bone(b1_name) + b2_pb = get_pose_bone(b2_name) + + chain = [] + if type == "Foot": + chain = [get_mix_name(side+"UpLeg", use_name_prefix), get_mix_name(side+"Leg", use_name_prefix)] + bake_ik_data["Leg"+side] = chain + + elif type == "Hand": + chain = [get_mix_name(side+"Arm", use_name_prefix), get_mix_name(side+"ForeArm", use_name_prefix)] + bake_ik_data["Arm"+side] = chain + + cns = b1_pb.constraints.new("COPY_TRANSFORMS") + cns.name = "Copy Transforms" + cns.target = src_arm + cns.subtarget = chain[0] + + cns = b2_pb.constraints.new("COPY_TRANSFORMS") + cns.name = "Copy Transforms" + cns.target = src_arm + cns.subtarget = chain[1] + + # Retarget + retarget_method = 2 + + # Method 1: Direct matrix retargetting (slower) + if retarget_method == 1: + for fr in range(fr_start, fr_end+1): + print(" frame", fr) + scn.frame_set(fr) + bpy.context.view_layer.update() + + for src_name in bones_map: + tar_name = bones_map[src_name] + src_bone = src_arm.pose.bones.get(src_name) + tar_bone = tar_arm.pose.bones.get(tar_name) + + if "Foot" in src_name: + tar_mix_bone = tar_arm.pose.bones.get(src_name) + #print(" tar_mix_bone", tar_mix_bone.name) + offset_mat = tar_bone.matrix @ tar_mix_bone.matrix.inverted() + tar_bone.matrix = offset_mat @ src_bone.matrix.copy() + else: + tar_bone.matrix = src_bone.matrix.copy() + + if not "Hips" in src_name: + tar_bone.location = [0,0,0] + + bpy.context.view_layer.update()# Not ideal, slow performances + + + # Method 2: Constrained retargetting (faster) + elif retarget_method == 2: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + set_active_object(tar_arm.name) + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.pose.select_all(action='DESELECT') + + + # add constraints + for src_name in bones_map: + tar_name = bones_map[src_name] + src_bone = src_arm.pose.bones.get(src_name) + tar_bone = tar_arm.pose.bones.get(tar_name) + + if src_bone == None: + #print("SKIP BONE", src_name) + continue + if tar_bone == None: + #print("SKIP BONE", tar_name) + continue + + cns_name = "Copy Rotation_retarget" + cns = tar_bone.constraints.new('COPY_ROTATION') + cns.name = cns_name + cns.target = src_arm + cns.subtarget = src_name + + if "Hips" in src_name: + cns_name = "Copy Location_retarget" + cns = tar_bone.constraints.new('COPY_LOCATION') + cns.name = cns_name + cns.target = src_arm + cns.subtarget = src_name + cns.owner_space = cns.target_space = "LOCAL" + + # Foot IK, Hand IK + 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): + #print(" set IK remap constraints", src_name) + cns_name = "Copy Location_retarget" + cns = tar_bone.constraints.new('COPY_LOCATION') + cns.name = cns_name + cns.target = src_arm + cns.subtarget = src_name + cns.target_space = cns.owner_space = "POSE" + + # select IK poles + _side = "_Left" if "Left" in src_name else "_Right" + ik_pole_name = "" + if "Hand" in src_name: + ik_pole_name = c_prefix+arm_rig_names["pole_ik"]+_side + elif "Foot" in src_name: + ik_pole_name = c_prefix+leg_rig_names["pole_ik"]+_side + + ik_pole_ctrl = get_pose_bone(ik_pole_name) + tar_arm.data.bones.active = ik_pole_ctrl.bone + ik_pole_ctrl.bone.select = True + + + # select + tar_arm.data.bones.active = tar_bone.bone + tar_bone.bone.select = True + + bpy.context.view_layer.update() + + # bake + bake_anim(frame_start=fr_start, frame_end=fr_end, only_selected=True, bake_bones=True, bake_object=False, ik_data=bake_ik_data) + + bpy.ops.object.mode_set(mode='OBJECT') + set_active_object(src_arm.name) + set_active_object(tar_arm.name) + print("Animation imported.") + + +def remove_retarget_cns(armature): + #print("Removing constraints...") + for pb in armature.pose.bones: + if len(pb.constraints): + for cns in pb.constraints: + if cns.name.endswith("_retarget") or cns.name == "temp": + pb.constraints.remove(cns) + + +def remove_temp_objects(): + for obj in bpy.data.objects: + if "mix_to_del" in obj.keys(): + delete_object(obj) + + +def update_mixamo_tab(): + try: + bpy.utils.unregister_class(MR_PT_MenuMain) + bpy.utils.unregister_class(MR_PT_MenuRig) + bpy.utils.unregister_class(MR_PT_MenuAnim) + bpy.utils.unregister_class(MR_PT_MenuExport) + bpy.utils.unregister_class(MR_PT_MenuUpdate) + except: + pass + + MixamoRigPanel.bl_category = bpy.context.preferences.addons[__package__].preferences.mixamo_tab_name + bpy.utils.register_class(MR_PT_MenuMain) + bpy.utils.register_class(MR_PT_MenuRig) + bpy.utils.register_class(MR_PT_MenuAnim) + bpy.utils.register_class(MR_PT_MenuExport) + bpy.utils.register_class(MR_PT_MenuUpdate) + +########### UI PANELS ################### +class MixamoRigPanel: + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Mixamo" + + +class MR_PT_MenuMain(Panel, MixamoRigPanel): + bl_label = "Mixamo Control Rig" + + def draw(self, context): + scn = context.scene + + layt = self.layout + layt.use_property_split = True + layt.use_property_decorate = False + + #col = layt.column(align=True) + #col.scale_y = 1.3 + #col.prop_search(scn, "mix_source_armature", scn, "objects", text="Skeleton") + arm_name = "None" + + if context.active_object != None: + if context.active_object.type == "ARMATURE": + arm_name = context.active_object.name + + layt.label(text="Character: "+arm_name) + + +class MR_PT_MenuRig(Panel, MixamoRigPanel): + bl_label = "Control Rig" + bl_parent_id = "MR_PT_MenuMain" + + def draw(self, context): + layt = self.layout + layt.use_property_split = True + layt.use_property_decorate = False + + obj = context.active_object + scn = context.scene + + """ + has_rigged = False + if obj: + if obj.type == "ARMATURE": + if len(obj.data.keys()): + if "mr_data" in obj.data.keys(): + has_rigged = True + """ + + col = layt.column(align=True) + col.scale_y = 1.3 + + col.operator(MR_OT_make_rig.bl_idname, text="Create Control Rig") + col.operator(MR_OT_zero_out.bl_idname, text="Zero Out Rig") + + col = layt.column(align=True) + col.separator() + + if bpy.context.mode != 'EDIT_MESH': + col.operator(MR_OT_edit_custom_shape.bl_idname, text="Edit Control Shape") + else: + col.operator(MR_OT_apply_shape.bl_idname, text="Apply Control Shape") + + +class MR_PT_MenuAnim(Panel, MixamoRigPanel): + bl_label = "Animation" + bl_parent_id = "MR_PT_MenuMain" + + def draw(self, context): + layt = self.layout + layt.use_property_split = True + layt.use_property_decorate = False# No animation. + scn = context.scene + layt.use_property_split = True + layt.use_property_decorate = False + + col = layt.column(align=True) + col.scale_y = 1 + #col.prop_search(scn, "mix_target_armature", scn, "objects", text="Control Rig") + col.label(text="Source Skeleton:") + col.prop_search(scn, "mix_source_armature", scn, "objects", text="") + col.separator() + + col = layt.column(align=True) + col.scale_y = 1.3 + col.operator(MR_OT_import_anim.bl_idname, text="Apply Animation to Control Rig") + + col = layt.column(align=True) + col.scale_y = 1.3 + col.operator(MR_OT_bake_anim.bl_idname, text="Bake Animation") + + +class MR_PT_MenuUpdate(Panel, MixamoRigPanel): + bl_label = "Update" + bl_parent_id = "MR_PT_MenuMain" + + def draw(self, context): + layt = self.layout + layt.operator(MR_OT_update.bl_idname, text="Update Control Rig") + + +class MR_PT_MenuExport(Panel, MixamoRigPanel): + bl_label = "Export" + bl_parent_id = "MR_PT_MenuMain" + + def draw(self, context): + layt = self.layout + layt.operator('export_scene.gltf', text="GLTF Export...")#MR_OT_exportGLTF.bl_idname + + +########### REGISTER ################## +classes = ( + MR_PT_MenuMain, + MR_PT_MenuRig, + MR_PT_MenuAnim, + MR_PT_MenuExport, + MR_PT_MenuUpdate, + MR_OT_make_rig, + MR_OT_zero_out, + MR_OT_bake_anim, + MR_OT_import_anim, + MR_OT_edit_custom_shape, + MR_OT_apply_shape, + MR_OT_exportGLTF, + MR_OT_update + ) + + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + + update_mixamo_tab() + + bpy.types.Scene.mix_source_armature = bpy.props.PointerProperty(type=bpy.types.Object) + bpy.types.Scene.mix_target_armature = bpy.props.PointerProperty(type=bpy.types.Object) + + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + + del bpy.types.Scene.mix_source_armature + del bpy.types.Scene.mix_target_armature + +if __name__ == "__main__": + register() \ No newline at end of file diff --git a/mixamo_rig_functions.py b/mixamo_rig_functions.py new file mode 100644 index 0000000..d899e17 --- /dev/null +++ b/mixamo_rig_functions.py @@ -0,0 +1,1116 @@ +import bpy, os +from mathutils import * +from math import * +from bpy.app.handlers import persistent +from operator import itemgetter +from .utils import * +from .define import * + +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"]] +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"]] +fk_arm = [c_prefix+arm_rig_names["arm_fk"], c_prefix+arm_rig_names["forearm_fk"], c_prefix+arm_rig_names["hand_fk"]] +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"]] + +################## OPERATOR CLASSES ################### + +class MR_OT_arm_bake_fk_to_ik(bpy.types.Operator): + """Snaps and bake an FK to an IK arm over a specified frame range""" + + bl_idname = "pose.mr_bake_arm_fk_to_ik" + bl_label = "Snap an FK to IK arm over a specified frame range" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + frame_start : bpy.props.IntProperty(name="Frame start", default=0) + frame_end : bpy.props.IntProperty(name="Frame end", default=10) + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def draw(self, context): + layout = self.layout + layout.prop(self, 'frame_start', text='Frame Start') + layout.prop(self, 'frame_end', text='Frame End') + + def invoke(self, context, event): + action = context.active_object.animation_data.action + self.frame_start, self.frame_end = action.frame_range[0], action.frame_range[1] + wm = context.window_manager + return wm.invoke_props_dialog(self, width=400) + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + # save current autokey state + auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto + # set auto key to True + bpy.context.scene.tool_settings.use_keyframe_insert_auto = True + + try: + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + bake_fk_to_ik_arm(self) + finally: + context.preferences.edit.use_global_undo = use_global_undo + # restore autokey state + bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state + + return {'FINISHED'} + + +class MR_OT_arm_fk_to_ik(bpy.types.Operator): + """Snaps an FK arm to an IK arm""" + + bl_idname = "pose.mr_arm_fk_to_ik_" + bl_label = "Snap FK arm to IK" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + + try: + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + + fk_to_ik_arm(self) + + finally: + context.preferences.edit.use_global_undo = use_global_undo + + return {'FINISHED'} + + +class MR_OT_arm_bake_ik_to_fk(bpy.types.Operator): + """Snaps and bake an IK to an FK arm over a specified frame range""" + + bl_idname = "pose.mr_bake_arm_ik_to_fk" + bl_label = "Snap an IK to FK arm over a specified frame range" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + frame_start : bpy.props.IntProperty(name="Frame start", default=0) + frame_end : bpy.props.IntProperty(name="Frame end", default=10) + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def draw(self, context): + layout = self.layout + layout.prop(self, 'frame_start', text='Frame Start') + layout.prop(self, 'frame_end', text='Frame End') + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=400) + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + # save current autokey state + auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto + # set auto key to True + bpy.context.scene.tool_settings.use_keyframe_insert_auto = True + + try: + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + + bake_ik_to_fk_arm(self) + finally: + context.preferences.edit.use_global_undo = use_global_undo + # restore autokey state + bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state + + return {'FINISHED'} + + +class MR_OT_arm_ik_to_fk(bpy.types.Operator): + """Snaps an IK arm to an FK arm""" + + bl_idname = "pose.mr_arm_ik_to_fk_" + bl_label = "Snap IK arm to FK" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + + try: + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + + ik_to_fk_arm(self) + + finally: + context.preferences.edit.use_global_undo = use_global_undo + return {'FINISHED'} + + +class MR_OT_switch_snap_anim(bpy.types.Operator): + """Switch and snap IK-FK over multiple frames""" + + bl_idname = "pose.mr_switch_snap_anim" + bl_label = "Switch and Snap IK FK anim" + bl_options = {'UNDO'} + + rig = None + side : bpy.props.StringProperty(name="bone side", default="") + _side = "" + prefix: bpy.props.StringProperty(name="", default="") + type : bpy.props.StringProperty(name="type", default="") + + frame_start : bpy.props.IntProperty(name="Frame start", default=0) + frame_end : bpy.props.IntProperty(name="Frame end", default=10) + has_action = False + + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + + def draw(self, context): + layout = self.layout + if self.has_action: + layout.prop(self, 'frame_start', text='Frame Start') + layout.prop(self, 'frame_end', text='Frame End') + else: + layout.label(text="This rig is not animated!") + + + def invoke(self, context, event): + try: + action = context.active_object.animation_data.action + if action: + self.has_action = True + except: + pass + + if self.has_action: + self.frame_start, self.frame_end = action.frame_range[0], action.frame_range[1] + + wm = context.window_manager + return wm.invoke_props_dialog(self, width=400) + + + def execute(self, context): + if self.has_action == False: + return {'FINISHED'} + + try: + scn = context.scene + # save current autokey state + auto_key_state = scn.tool_settings.use_keyframe_insert_auto + # set auto key to True + scn.tool_settings.use_keyframe_insert_auto = True + # save current frame + cur_frame = scn.frame_current + + self.rig = context.active_object + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + self._side = '_'+self.side + self.prefix = get_mixamo_prefix() + + + if is_selected(fk_leg, bname) or is_selected(ik_leg, bname): + self.type = "LEG" + elif is_selected(fk_arm, bname) or is_selected(ik_arm, bname): + self.type = "ARM" + + if self.type == "ARM": + c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+self._side)#self.prefix+self.side+'Hand') + if c_hand_ik['ik_fk_switch'] < 0.5: + bake_fk_to_ik_arm(self) + else: + bake_ik_to_fk_arm(self) + + elif self.type == "LEG": + c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+self._side)#get_pose_bone(self.prefix+self.side+'Foot') + if c_foot_ik['ik_fk_switch'] < 0.5: + bake_fk_to_ik_leg(self) + else: + print("Bake IK to FK leg") + bake_ik_to_fk_leg(self) + + + finally: + # restore autokey state + scn.tool_settings.use_keyframe_insert_auto = auto_key_state + # restore frame + scn.frame_set(cur_frame) + + return {'FINISHED'} + + +class MR_OT_switch_snap(bpy.types.Operator): + """Switch and snap IK-FK for the current frame""" + + bl_idname = "pose.mr_switch_snap" + bl_label = "Switch and Snap IK FK" + bl_options = {'UNDO'} + + rig = None + side : bpy.props.StringProperty(name="bone side", default="") + _side = "" + prefix: bpy.props.StringProperty(name="", default="") + type : bpy.props.StringProperty(name="type", default="") + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + + try: + self.rig = context.active_object + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + self._side = '_'+self.side + self.prefix = get_mixamo_prefix() + + if is_selected(fk_leg, bname) or is_selected(ik_leg, bname): + self.type = "LEG" + elif is_selected(fk_arm, bname) or is_selected(ik_arm, bname): + self.type = "ARM" + + + if self.type == "ARM": + #base_hand = get_pose_bone(self.prefix+self.side+'Hand') + c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+self._side)#self.prefix+self.side+'Hand') + if c_hand_ik['ik_fk_switch'] < 0.5: + fk_to_ik_arm(self) + else: + ik_to_fk_arm(self) + + elif self.type == "LEG": + #base_foot = get_pose_bone(self.prefix+self.side+'Foot') + c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+self._side)#get_pose_bone(self.prefix+self.side+'Foot') + if c_foot_ik['ik_fk_switch'] < 0.5: + fk_to_ik_leg(self) + else: + ik_to_fk_leg(self) + + + finally: + context.preferences.edit.use_global_undo = use_global_undo + + return {'FINISHED'} + + +class MR_OT_leg_bake_fk_to_ik(bpy.types.Operator): + """Snaps and bake an FK leg to an IK leg over a specified frame range""" + + bl_idname = "pose.mr_bake_leg_fk_to_ik" + bl_label = "Snap an FK to IK leg over a specified frame range" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + _side = "" + prefix = "" + frame_start : bpy.props.IntProperty(name="Frame start", default=0) + frame_end : bpy.props.IntProperty(name="Frame end", default=10) + rig = None + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def draw(self, context): + layout = self.layout + layout.prop(self, 'frame_start', text='Frame Start') + layout.prop(self, 'frame_end', text='Frame End') + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=400) + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + # save current autokey state + auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto + # set auto key to True + bpy.context.scene.tool_settings.use_keyframe_insert_auto = True + + try: + self.rig = context.active_object + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + self._side = '_'+self.side + self.prefix = get_mixamo_prefix() + + bake_fk_to_ik_leg(self) + finally: + context.preferences.edit.use_global_undo = use_global_undo + # restore autokey state + bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state + + return {'FINISHED'} + + +class MR_OT_leg_fk_to_ik(bpy.types.Operator): + """Snaps an FK leg to an IK leg""" + + bl_idname = "pose.mr_leg_fk_to_ik_" + bl_label = "Snap FK leg to IK" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + rig = None + _side = "" + prefix = "" + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + + try: + self.rig = context.active_object + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + self._side = '_'+self.side + self.prefix = get_mixamo_prefix() + + fk_to_ik_leg(self) + + finally: + context.preferences.edit.use_global_undo = use_global_undo + return {'FINISHED'} + + +class MR_OT_leg_bake_ik_to_fk(bpy.types.Operator): + """Snaps and bake an IK leg to an FK leg over a specified frame range""" + + bl_idname = "pose.mr_bake_leg_ik_to_fk" + bl_label = "Snap an IK to FK leg over a specified frame range" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + frame_start : bpy.props.IntProperty(name="Frame start", default=0) + frame_end : bpy.props.IntProperty(name="Frame end", default=10) + rig = None + _side = "" + prefix = "" + + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def draw(self, context): + layout = self.layout + layout.prop(self, 'frame_start', text='Frame Start') + layout.prop(self, 'frame_end', text='Frame End') + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=400) + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + # save current autokey state + auto_key_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto + # set auto key to True + bpy.context.scene.tool_settings.use_keyframe_insert_auto = True + + try: + self.rig = context.active_object + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + self._side = '_'+self.side + self.prefix = get_mixamo_prefix() + + bake_ik_to_fk_leg(self) + + finally: + context.preferences.edit.use_global_undo = use_global_undo + # restore autokey state + bpy.context.scene.tool_settings.use_keyframe_insert_auto = auto_key_state + + return {'FINISHED'} + + +class MR_OT_leg_ik_to_fk(bpy.types.Operator): + """Snaps an IK leg to an FK leg""" + + bl_idname = "pose.mr_leg_ik_to_fk_" + bl_label = "Snap IK leg to FK" + bl_options = {'UNDO'} + + side : bpy.props.StringProperty(name="bone side") + rig = None + _side = "" + prefix = "" + + @classmethod + def poll(cls, context): + return (context.active_object != None and context.mode == 'POSE') + + def execute(self, context): + use_global_undo = context.preferences.edit.use_global_undo + context.preferences.edit.use_global_undo = False + try: + self.rig = context.active_object + bname = get_selected_pbone_name() + self.side = get_bone_side(bname) + self._side = '_'+self.side + self.prefix = get_mixamo_prefix() + + ik_to_fk_leg(self) + finally: + context.preferences.edit.use_global_undo = use_global_undo + + return {'FINISHED'} + + + +################## FUNCTIONS ################## + +def set_pose_rotation(pose_bone, mat): + q = mat.to_quaternion() + + if pose_bone.rotation_mode == 'QUATERNION': + pose_bone.rotation_quaternion = q + elif pose_bone.rotation_mode == 'AXIS_ANGLE': + pose_bone.rotation_axis_angle[0] = q.angle + pose_bone.rotation_axis_angle[1] = q.axis[0] + pose_bone.rotation_axis_angle[2] = q.axis[1] + pose_bone.rotation_axis_angle[3] = q.axis[2] + else: + pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode) + + +def snap_pos(pose_bone, target_bone): + # Snap a bone to another bone. Supports child of constraints and parent. + + # if the pose_bone has direct parent + if pose_bone.parent: + # apply double time because of dependecy lag + pose_bone.matrix = target_bone.matrix + update_transform() + # second apply + pose_bone.matrix = target_bone.matrix + else: + # is there a child of constraint attached? + child_of_cns = None + if len(pose_bone.constraints) > 0: + 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] + if len(all_child_of_cns) > 0: + child_of_cns = all_child_of_cns[0]# in case of multiple child of constraints enabled, use only the first for now + + if child_of_cns != None: + if child_of_cns.subtarget != "" and get_pose_bone(child_of_cns.subtarget): + # apply double time because of dependecy lag + pose_bone.matrix = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ target_bone.matrix + update_transform() + pose_bone.matrix = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ target_bone.matrix + else: + pose_bone.matrix = target_bone.matrix + + else: + pose_bone.matrix = target_bone.matrix + + +def snap_pos_matrix(pose_bone, target_bone_matrix): + # Snap a bone to another bone. Supports child of constraints and parent. + + # if the pose_bone has direct parent + if pose_bone.parent: + pose_bone.matrix = target_bone_matrix.copy() + update_transform() + else: + # is there a child of constraint attached? + child_of_cns = None + if len(pose_bone.constraints) > 0: + 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] + if len(all_child_of_cns) > 0: + child_of_cns = all_child_of_cns[0]# in case of multiple child of constraints enabled, use only the first for now + + if child_of_cns != None: + if child_of_cns.subtarget != "" and get_pose_bone(child_of_cns.subtarget): + pose_bone.matrix = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ target_bone_matrix + update_transform() + else: + pose_bone.matrix = target_bone_matrix.copy() + + else: + pose_bone.matrix = target_bone_matrix.copy() + + +def snap_rot(pose_bone, target_bone): + method = 1 + + if method == 1: + mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) + set_pose_rotation(pose_bone, mat) + #bpy.ops.object.mode_set(mode='OBJECT') + #bpy.ops.object.mode_set(mode='POSE') + bpy.context.view_layer.update() + elif method == 2: + loc, scale = pose_bone.location.copy(), pose_bone.scale.copy() + pose_bone.matrix = target_bone.matrix + pose_bone.location, pose_bone.scale = loc, scale + bpy.context.view_layer.update() + + +def bake_fk_to_ik_arm(self): + for f in range(self.frame_start, self.frame_end +1): + bpy.context.scene.frame_set(f) + print("baking frame", f) + fk_to_ik_arm(self) + + +def fk_to_ik_arm(self): + rig = self.rig + side = self.side + _side = self._side + prefix = self.prefix + + arm_fk = rig.pose.bones[fk_arm[0] + _side] + forearm_fk = rig.pose.bones[fk_arm[1] + _side] + hand_fk = rig.pose.bones[fk_arm[2] + _side] + + arm_ik = rig.pose.bones[ik_arm[0] + _side] + forearm_ik = rig.pose.bones[ik_arm[1] + _side] + hand_ik = rig.pose.bones[ik_arm[2] + _side] + pole = rig.pose.bones[ik_arm[3] + _side] + + #Snap rot + snap_rot(arm_fk, arm_ik) + snap_rot(forearm_fk, forearm_ik) + snap_rot(hand_fk, hand_ik) + + #Snap scale + hand_fk.scale =hand_ik.scale + + #rot debug + forearm_fk.rotation_euler[0]=0 + forearm_fk.rotation_euler[1]=0 + + #switch + #base_hand = get_pose_bone(prefix+side+'Hand') + c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+_side) + c_hand_ik['ik_fk_switch'] = 1.0 + + #udpate view + bpy.context.view_layer.update() + + #insert key if autokey enable + if bpy.context.scene.tool_settings.use_keyframe_insert_auto: + #fk chain + c_hand_ik.keyframe_insert(data_path='["ik_fk_switch"]') + hand_fk.keyframe_insert(data_path="scale") + hand_fk.keyframe_insert(data_path="rotation_euler") + arm_fk.keyframe_insert(data_path="rotation_euler") + forearm_fk.keyframe_insert(data_path="rotation_euler") + + #ik chain + hand_ik.keyframe_insert(data_path="location") + hand_ik.keyframe_insert(data_path="rotation_euler") + hand_ik.keyframe_insert(data_path="scale") + pole.keyframe_insert(data_path="location") + + # change FK to IK hand selection, if selected + if hand_ik.bone.select: + hand_fk.bone.select = True + hand_ik.bone.select = False + + +def bake_ik_to_fk_arm(self): + for f in range(self.frame_start, self.frame_end +1): + bpy.context.scene.frame_set(f) + print("baking frame", f) + + ik_to_fk_arm(self) + + +def ik_to_fk_arm(self): + rig = self.rig + side = self.side + _side = self._side + prefix = self.prefix + + arm_fk = rig.pose.bones[fk_arm[0] + _side] + forearm_fk = rig.pose.bones[fk_arm[1] + _side] + hand_fk = rig.pose.bones[fk_arm[2] + _side] + + arm_ik = rig.pose.bones[ik_arm[0] + _side] + forearm_ik = rig.pose.bones[ik_arm[1] + _side] + hand_ik = rig.pose.bones[ik_arm[2] + _side] + pole_ik = rig.pose.bones[ik_arm[3] + _side] + + # Snap + # constraint support + constraint = None + bparent_name = "" + parent_type = "" + valid_constraint = True + + # Snap Hand + if len(hand_ik.constraints) > 0: + for c in hand_ik.constraints: + if not c.mute and c.influence > 0.5 and c.type == 'CHILD_OF': + if c.target: + #if bone + if c.target.type == 'ARMATURE': + bparent_name = c.subtarget + parent_type = "bone" + constraint = c + #if object + else: + bparent_name = c.target.name + parent_type = "object" + constraint = c + + + if constraint != None: + if parent_type == "bone": + if bparent_name == "": + valid_constraint = False + + if constraint and valid_constraint: + if parent_type == "bone": + bone_parent = get_pose_bone(bparent_name) + hand_ik.matrix = bone_parent.matrix_channel.inverted() @ hand_fk.matrix + if parent_type == "object": + bone_parent = bpy.data.objects[bparent_name] + obj_par = bpy.data.objects[bparent_name] + hand_ik.matrix = constraint.inverse_matrix.inverted() @ obj_par.matrix_world.inverted() @ hand_fk.matrix + else: + hand_ik.matrix = hand_fk.matrix + + # Snap Pole + _axis = forearm_fk.x_axis if side == "Left" else -forearm_fk.x_axis + pole_pos = get_ik_pole_pos(arm_fk, forearm_fk, method=2, axis=_axis) + pole_mat = Matrix.Translation(pole_pos) + snap_pos_matrix(pole_ik, pole_mat) + + # Switch + c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+_side) + #base_hand = get_pose_bone(prefix+side+'Hand') + c_hand_ik['ik_fk_switch'] = 0.0 + + # update + update_transform() + + #insert key if autokey enable + if bpy.context.scene.tool_settings.use_keyframe_insert_auto: + #ik chain + c_hand_ik.keyframe_insert(data_path='["ik_fk_switch"]') + hand_ik.keyframe_insert(data_path="location") + hand_ik.keyframe_insert(data_path="rotation_euler") + hand_ik.keyframe_insert(data_path="scale") + pole_ik.keyframe_insert(data_path="location") + + #fk chain + hand_fk.keyframe_insert(data_path="location") + hand_fk.keyframe_insert(data_path="rotation_euler") + hand_fk.keyframe_insert(data_path="scale") + arm_fk.keyframe_insert(data_path="rotation_euler") + forearm_fk.keyframe_insert(data_path="rotation_euler") + + # change FK to IK hand selection, if selected + if hand_fk.bone.select: + hand_fk.bone.select = False + hand_ik.bone.select = True + + +def bake_fk_to_ik_leg(self): + for f in range(self.frame_start, self.frame_end +1): + bpy.context.scene.frame_set(f) + print("baking frame", f) + + fk_to_ik_leg(self) + + +def fk_to_ik_leg(self): + rig = self.rig + side = self.side + _side = self._side + prefix = self.prefix + + thigh_fk = rig.pose.bones[fk_leg[0] + _side] + leg_fk = rig.pose.bones[fk_leg[1] + _side] + foot_fk = rig.pose.bones[fk_leg[2] + _side] + toes_fk = rig.pose.bones[fk_leg[3] + _side] + + thigh_ik = rig.pose.bones[ik_leg[0] + _side] + leg_ik = rig.pose.bones[ik_leg[1] + _side] + foot_ik = rig.pose.bones[ik_leg[2] + _side] + pole_ik = rig.pose.bones[ik_leg[3] + _side] + toes_ik = rig.pose.bones[ik_leg[4] + _side] + foot_01_ik = rig.pose.bones[ik_leg[5] + _side] + foot_roll_ik = rig.pose.bones[ik_leg[6] + _side] + foot_snap_ik = rig.pose.bones[ik_leg[7] + _side] + + # Thigh snap + snap_rot(thigh_fk, thigh_ik) + #thigh_fk.matrix = thigh_ik.matrix.copy() + + # Leg snap + snap_rot(leg_fk, leg_ik) + + # Foot snap + snap_rot(foot_fk, foot_snap_ik) + foot_fk.scale =foot_ik.scale + + # Toes snap + snap_rot(toes_fk, toes_ik) + toes_fk.scale = toes_ik.scale + + # rotation fix + leg_fk.rotation_euler[1] = 0.0 + leg_fk.rotation_euler[2] = 0.0 + + # switch prop value + c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+_side) + #base_foot = get_pose_bone(prefix+side+'Foot') + c_foot_ik['ik_fk_switch'] = 1.0 + + # udpate hack + bpy.context.view_layer.update() + + #if bpy.context.scene.frame_current == 2: + # print(br) + + #insert key if autokey enable + if bpy.context.scene.tool_settings.use_keyframe_insert_auto: + #fk chain + c_foot_ik.keyframe_insert(data_path='["ik_fk_switch"]') + thigh_fk.keyframe_insert(data_path="rotation_euler") + leg_fk.keyframe_insert(data_path="rotation_euler") + foot_fk.keyframe_insert(data_path="rotation_euler") + foot_fk.keyframe_insert(data_path="scale") + toes_fk.keyframe_insert(data_path="rotation_euler") + toes_fk.keyframe_insert(data_path="scale") + + #ik chain + foot_ik.keyframe_insert(data_path="location") + foot_ik.keyframe_insert(data_path="rotation_euler") + foot_ik.keyframe_insert(data_path="scale") + foot_01_ik.keyframe_insert(data_path="rotation_euler") + foot_roll_ik.keyframe_insert(data_path="location") + toes_ik.keyframe_insert(data_path="rotation_euler") + toes_ik.keyframe_insert(data_path="scale") + pole_ik.keyframe_insert(data_path="location") + + # change IK to FK foot selection, if selected + if foot_ik.bone.select: + foot_fk.bone.select = True + foot_ik.bone.select = False + + +def bake_ik_to_fk_leg(self): + for f in range(self.frame_start, self.frame_end +1): + bpy.context.scene.frame_set(f) + print("baking frame", f) + + ik_to_fk_leg(self) + + +def ik_to_fk_leg(self): + rig = self.rig + side = self.side + _side = self._side + prefix = self.prefix + + thigh_fk = rig.pose.bones[fk_leg[0] + _side] + leg_fk = rig.pose.bones[fk_leg[1] + _side] + foot_fk = rig.pose.bones[fk_leg[2] + _side] + toes_fk = rig.pose.bones[fk_leg[3] + _side] + + thigh_ik = rig.pose.bones[ik_leg[0] + _side] + calf_ik = rig.pose.bones[ik_leg[1] + _side] + foot_ik = rig.pose.bones[ik_leg[2] + _side] + pole_ik = rig.pose.bones[ik_leg[3] + _side] + toes_ik = rig.pose.bones[ik_leg[4] + _side] + foot_01_ik = rig.pose.bones[ik_leg[5] + _side] + foot_roll_ik = rig.pose.bones[ik_leg[6] + _side] + + + # reset IK foot_01 and foot_roll + foot_01_ik.rotation_euler = [0,0,0] + foot_roll_ik.location[0] = 0.0 + foot_roll_ik.location[2] = 0.0 + + # Snap toes + toes_ik.rotation_euler = toes_fk.rotation_euler.copy() + toes_ik.scale = toes_fk.scale.copy() + + # Child Of constraint or parent cases + constraint = None + bparent_name = "" + parent_type = "" + valid_constraint = True + + if len(foot_ik.constraints) > 0: + for c in foot_ik.constraints: + if not c.mute and c.influence > 0.5 and c.type == 'CHILD_OF': + if c.target: + #if bone + if c.target.type == 'ARMATURE': + bparent_name = c.subtarget + parent_type = "bone" + constraint = c + #if object + else: + bparent_name = c.target.name + parent_type = "object" + constraint = c + + if constraint != None: + if parent_type == "bone": + if bparent_name == "": + valid_constraint = False + + # Snap Foot + if constraint and valid_constraint: + if parent_type == "bone": + bone_parent = rig.pose.bones[bparent_name] + foot_ik.matrix = bone_parent.matrix_channel.inverted() @ foot_fk.matrix + if parent_type == "object": + ob = bpy.data.objects[bparent_name] + foot_ik.matrix = constraint.inverse_matrix.inverted() @ ob.matrix_world.inverted() @ foot_fk.matrix + + else: + foot_ik.matrix = foot_fk.matrix + + # update + bpy.context.view_layer.update() + + # Snap Pole + pole_pos = get_ik_pole_pos(thigh_fk, leg_fk, method=2, axis=leg_fk.z_axis) + pole_mat = Matrix.Translation(pole_pos) + snap_pos_matrix(pole_ik, pole_mat) + + update_transform() + + # switch + c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+_side) + #base_foot = get_pose_bone(prefix+side+'Foot') + c_foot_ik['ik_fk_switch'] = 0.0 + + update_transform() + + #insert key if autokey enable + if bpy.context.scene.tool_settings.use_keyframe_insert_auto: + #ik chain + c_foot_ik.keyframe_insert(data_path='["ik_fk_switch"]') + foot_01_ik.keyframe_insert(data_path="rotation_euler") + foot_roll_ik.keyframe_insert(data_path="location") + foot_ik.keyframe_insert(data_path="location") + foot_ik.keyframe_insert(data_path="rotation_euler") + foot_ik.keyframe_insert(data_path="scale") + toes_ik.keyframe_insert(data_path="rotation_euler") + toes_ik.keyframe_insert(data_path="scale") + pole_ik.keyframe_insert(data_path="location") + + #fk chain + thigh_fk.keyframe_insert(data_path="rotation_euler") + leg_fk.keyframe_insert(data_path="rotation_euler") + foot_fk.keyframe_insert(data_path="rotation_euler") + foot_fk.keyframe_insert(data_path="scale") + toes_fk.keyframe_insert(data_path="rotation_euler") + toes_fk.keyframe_insert(data_path="scale") + + # change IK to FK foot selection, if selected + if foot_fk.bone.select: + foot_fk.bone.select = False + foot_ik.bone.select = True + + +def get_active_child_of_cns(bone): + constraint = None + bparent_name = "" + parent_type = "" + valid_constraint = True + + if len(bone.constraints) > 0: + for c in bone.constraints: + if not c.mute and c.influence > 0.5 and c.type == 'CHILD_OF': + if c.target: + if c.target.type == 'ARMATURE':# bone + bparent_name = c.subtarget + parent_type = "bone" + constraint = c + else:# object + bparent_name = c.target.name + parent_type = "object" + constraint = c + + if constraint: + if parent_type == "bone": + if bparent_name == "": + valid_constraint = False + + return constraint, bparent_name, parent_type, valid_constraint + + +def is_selected(names, selected_bone_name, startswith=False): + side = "" + if get_bone_side(selected_bone_name) != None: + side = get_bone_side(selected_bone_name) + + _side = "_"+side + + if startswith == False: + if type(names) == list: + for name in names: + if not "." in name[-2:]: + if name + _side == selected_bone_name: + return True + else: + if name[-2:] == ".x": + if name[:-2] + _side == selected_bone_name: + return True + elif names == selected_bone_name: + return True + else:#startswith + if type(names) == list: + for name in names: + if selected_bone_name.startswith(name): + return True + else: + return selected_bone_name.startswith(names) + return False + + +def is_selected_prop(pbone, prop_name): + if pbone.bone.keys(): + if prop_name in pbone.bone.keys(): + return True + + +################## User Interface ################## +class MR_PT_rig_ui(bpy.types.Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Tool" + bl_label = "Mixamo Rig Settings" + bl_idname = "MR_PT_rig_ui" + + @classmethod + def poll(self, context): + if context.mode != 'POSE': + return False + return True + + + def draw(self, context): + layout = self.layout + scn = bpy.context.scene + rig = context.active_object + + if rig == None: + return + if rig.type != "ARMATURE": + return + + # check if a Mixamo ctrl rig is selected + if len(rig.data.keys()): + if not 'mr_control_rig' in rig.data.keys(): + return + else: + return + + + pose_bones = rig.pose.bones + + try: + active_bone = context.selected_pose_bones[0]#context.active_pose_bone + selected_bone_name = active_bone.name + except: + return + + side = get_bone_side(selected_bone_name) + prefix = get_mixamo_prefix() + + # Leg + if (is_selected(fk_leg, selected_bone_name) or is_selected(ik_leg, selected_bone_name)): + # IK-FK Switch + col = layout.column(align=True) + #foot_base = get_pose_bone(prefix+side.title()+'Foot') + c_foot_ik = get_pose_bone(c_prefix+leg_rig_names["foot_ik"]+'_'+side.title()) + col.prop(c_foot_ik, '["ik_fk_switch"]', text="IK-FK Switch", slider=True) + col.operator(MR_OT_switch_snap.bl_idname, text="Snap Frame IK/FK") + col.operator(MR_OT_switch_snap_anim.bl_idname, text="Snap Anim IK-FK") + + + # Arm + if is_selected(fk_arm, selected_bone_name) or is_selected(ik_arm, selected_bone_name): + # IK-FK Switch + col = layout.column(align=True) + #hand_base = get_pose_bone(prefix+side.title()+'Hand') + c_hand_ik = get_pose_bone(c_prefix+arm_rig_names["hand_ik"]+'_'+side.title()) + col.prop(c_hand_ik, '["ik_fk_switch"]', text="IK-FK Switch", slider=True) + col.operator(MR_OT_switch_snap.bl_idname, text="Snap Frame IK-FK") + col.operator(MR_OT_switch_snap_anim.bl_idname, text="Snap Anim IK-FK") + + + +################## REGISTER ################## +classes = ( + MR_OT_arm_bake_fk_to_ik, + MR_OT_arm_fk_to_ik, + MR_OT_arm_bake_ik_to_fk, + MR_OT_arm_ik_to_fk, + MR_OT_switch_snap, + MR_OT_leg_fk_to_ik, + MR_OT_leg_bake_fk_to_ik, + MR_OT_leg_ik_to_fk, + MR_OT_leg_bake_ik_to_fk, + MR_PT_rig_ui, + MR_OT_switch_snap_anim) + + +def update_mixamo_tab(): + try: + bpy.utils.unregister_class(MR_PT_rig_ui) + except: + pass + + MR_PT_rig_ui.bl_category = bpy.context.preferences.addons[__package__].preferences.mixamo_tab_name + bpy.utils.register_class(MR_PT_rig_ui) + + +def register(): + from bpy.utils import register_class + + for cls in classes: + register_class(cls) + + update_mixamo_tab() + + bpy.types.Scene.mix_show_ik_fk_advanced = bpy.props.BoolProperty(name="Show IK-FK operators", description="Show IK-FK manual operators", default=False) + + +def unregister(): + from bpy.utils import unregister_class + + for cls in classes: + unregister_class(cls) + + del bpy.types.Scene.mix_show_ik_fk_advanced diff --git a/mixamo_rig_prefs.py b/mixamo_rig_prefs.py new file mode 100644 index 0000000..9430972 --- /dev/null +++ b/mixamo_rig_prefs.py @@ -0,0 +1,31 @@ +import bpy + +def update_all_tab_names(self, context): + try: + from . import mixamo_rig + mixamo_rig.update_mixamo_tab() + except: + pass + + +class MR_MT_addon_preferences(bpy.types.AddonPreferences): + bl_idname = __package__ + 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) + + def draw(self, context): + col = self.layout.column(align=True) + col.prop(self, "mixamo_tab_name", text="Interface Tab") + + +def register(): + from bpy.utils import register_class + + try: + register_class(MR_MT_addon_preferences) + except: + pass + + +def unregister(): + from bpy.utils import unregister_class + unregister_class(MR_MT_addon_preferences) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0999c13 --- /dev/null +++ b/readme.md @@ -0,0 +1,5 @@ +[![Python 3.10.2](https://img.shields.io/badge/python-3.10.2-sucess.svg)](https://www.python.org/downloads/release/python-3102/) +![Static Badge](https://img.shields.io/badge/blender-4--2-orange?style=flat&logo=blender&logoColor=white&label=blender&labelColor=black&link=https%3A%2F%2Fwww.blender.org%2F) + +# Blender Mixamo +Forks from the blender add-on "Mixamo", [develop by Adobe](https://www.adobe.com/products/substance3d/plugins/mixamo-in-blender.html). \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..2b64849 --- /dev/null +++ b/utils.py @@ -0,0 +1,18 @@ +import bpy, os +from math import * +from mathutils import * +from bpy.types import Panel, UIList +from .lib.objects import * +from .lib.bones_data import * +from .lib.bones_edit import * +from .lib.bones_pose import * +from .lib.context import * +from .lib.addon import * +from .lib.mixamo import * +from .lib.armature import * +from .lib.constraints import * +from .lib.animation import * +from .lib.maths_geo import * +from .lib.drivers import * +from .lib.custom_props import * +from .lib.version import * \ No newline at end of file