diff --git a/.gitignore b/.gitignore index 5375c90..f80edf1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ build-vscode/* characters/* resources/buildings/* assets/blender/scripts/*.blend -assets/blender/scripts/__pycache__/* +__pycache__/ *.blend[1-9] diff --git a/assets/blender/scripts/export_buildings.py b/assets/blender/scripts/export_buildings.py new file mode 100644 index 0000000..e9efe97 --- /dev/null +++ b/assets/blender/scripts/export_buildings.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +import os, sys, time +import bpy +from math import pi +import glob +import shutil +from mathutils import Vector, Matrix +from math import radians, pi + +argv = sys.argv +argv = argv[argv.index("--") + 1:] + +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") + +gltf_file = argv[0] +print("Exporting to " + gltf_file) +basepath = os.getcwd() +# bpy.ops.export_scene.gltf(filepath="", check_existing=True, +# export_import_convert_lighting_mode='SPEC', gltf_export_id="", +# export_format='GLB', ui_tab='GENERAL', export_copyright="", export_image_format='AUTO', +# export_texture_dir="", export_jpeg_quality=75, export_keep_originals=False, +# export_texcoords=True, export_normals=True, export_draco_mesh_compression_enable=False, +# export_draco_mesh_compression_level=6, export_draco_position_quantization=14, +# export_draco_normal_quantization=10, export_draco_texcoord_quantization=12, +# export_draco_color_quantization=10, export_draco_generic_quantization=12, export_tangents=False, +# export_materials='EXPORT', export_original_specular=False, export_colors=True, +# export_attributes=False, use_mesh_edges=False, use_mesh_vertices=False, export_cameras=False, +# use_selection=False, use_visible=False, use_renderable=False, +# use_active_collection_with_nested=True, use_active_collection=False, use_active_scene=False, +# export_extras=False, export_yup=True, export_apply=False, export_animations=True, +# export_frame_range=False, export_frame_step=1, export_force_sampling=True, export_animation_mode='ACTIONS', +# export_nla_strips_merged_animation_name="Animation", export_def_bones=False, +# export_hierarchy_flatten_bones=False, export_optimize_animation_size=True, +# export_optimize_animation_keep_anim_armature=True, export_optimize_animation_keep_anim_object=False, +# export_negative_frame='SLIDE', export_anim_slide_to_zero=False, export_bake_animation=False, +# export_anim_single_armature=True, export_reset_pose_bones=True, export_current_frame=False, +# export_rest_position_armature=True, export_anim_scene_split_object=True, export_skins=True, +# export_all_influences=False, export_morph=True, export_morph_normal=True, +# export_morph_tangent=False, export_morph_animation=True, export_morph_reset_sk_data=True, +# export_lights=False, export_nla_strips=True, will_save_settings=False, filter_glob="*.glb") + +bpy.ops.export_scene.gltf(filepath=gltf_file, + use_selection=False, + check_existing=False, + export_format='GLB', + export_texture_dir='textures', export_texcoords=True, + export_normals=True, + export_tangents=True, + export_materials='EXPORT', + export_colors=True, + use_mesh_edges=False, + use_mesh_vertices=False, + export_cameras=False, + use_visible=False, + use_renderable=False, + export_yup=True, + export_apply=True, + export_animations=True, + export_force_sampling=True, + export_def_bones=False, + export_current_frame=False, + export_morph=True, + export_morph_animation=False, + export_morph_normal=True, + export_morph_tangent=True, + export_lights=False, + export_skins=True) + +bpy.ops.wm.read_homefile(use_empty=True) +time.sleep(2) +bpy.ops.wm.quit_blender() diff --git a/assets/blender/scripts/export_clothes.py b/assets/blender/scripts/export_clothes.py new file mode 100644 index 0000000..e53cedc --- /dev/null +++ b/assets/blender/scripts/export_clothes.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python + +import os, time +import bpy +from math import pi +import glob +from mathutils import Vector, Matrix +from math import radians, pi +import json + +class ExportMappingFemale: + char_blend_path = "assets/blender/" + "vroid-normal-female.blend" + cloth_blend_paths = ["assets/blender/female-coat2.blend"] + gltf_path = "godot/clothes/female-coat.gltf" + inner_path = "Object" + objs = ["skeleton", "body", "privates", "ref-bodyskirt-noimp", "ref-bodydress-noimp", "ref-bodycap-noimp", "ref-bodyshoes-noimp", "ref-dress-noimp", "ref-topskirt-noimp", "ref-skirt4-noimp", "ref-skirt3-noimp"] + objs_remove = [] + armature_name = "skeleton" + outfile = "tmp-female-cloth.blend" + default_action = 'default' + sex = "female" + def __init__(self): + self.files = [] + for fobj in self.objs: + self.files.append({"name": fobj}) + +class ExportMappingMale: + char_blend_path = "assets/blender/vroid1-man-animate.blend" + cloth_blend_paths = ["assets/blender/male-coat2.blend"] + gltf_path = "godot/clothes/male-coat.gltf" + inner_path = "Object" + objs = ["pxy", "body", "ref-topbottom-noimp", "ref-bodytopbottom-noimp"] + objs_remove = [] + armature_name = "pxy" + outfile = "tmp-male-cloth.blend" + default_action = 'default' + sex = "male" + def __init__(self): + self.files = [] + for fobj in self.objs: + self.files.append({"name": fobj}) + + +basepath = os.getcwd() +def check_bone(bname): + ok = True + baddie = ["ctrl_", "mch_", "MCH_"] + for bd in baddie: + if bname.startswith(bd): + ok = False + break + return ok + +def clamp_angle_deg(angle, min_angle_deg, max_angle_deg): + min_angle = radians(min_angle_deg) + max_angle = radians(max_angle_deg) + if angle < min_angle: + angle = min_angle + if angle > max_angle: + angle = max_angle + return angle +def angle_to_linear(angle, divider): + if angle < 0.0: + return angle / divider + else: + return 0.0 +def angle_to_linear_x(bone, angle): + skel = bpy.data.objects["skeleton_orig"] + left_base = "ctrl_base_upperleg.L.001" + right_base = "ctrl_base_upperleg.R.001" + base = "" + if base == "": + for e in ["_R", ".R"]: + if bone.name.endswith(e): + base = right_base + break + if base == "": + for e in ["_L", ".L"]: + if bone.name.endswith(e): + base = left_base + break + if base == "": + for e in ["_R.", ".R."]: + if bone.name.find(e) >= 0: + base = right_base + break + if base == "": + for e in ["_L.", ".L."]: + if bone.name.find(e) >= 0: + base = left_base + break + mul = skel.pose.bones[base]["to_linear_x_base"] + offset = skel.pose.bones[base]["angle_offset"] +# print("bone: ", bone.name, "base: ", base, "mul: ", mul) +# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset) + print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle) + return (angle + offset) * mul +def extra_linear(angle, offset): + ret = 0.0 + offt = offset * angle * 2.0 / -radians(-90) + if angle * 2.0 < -radians(65): + if angle * 2.0 > -radians(65): + ret += offset + else: + ret += offt + return ret + +def prepare_armature(mapping): + print("Preparing...") + bpy.ops.object.armature_add(enter_editmode=False) + new_armature = bpy.context.object + orig_armature = bpy.data.objects[mapping.armature_name] + armature_name = orig_armature.name + orig_armature.name = orig_armature.name + "_orig" + new_armature.name = armature_name + queue = [] + if new_armature.animation_data is None: + new_armature.animation_data_create() + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='EDIT') + for b in new_armature.data.edit_bones: + new_armature.data.edit_bones.remove(b) + bpy.context.view_layer.objects.active = orig_armature + bpy.ops.object.mode_set(mode='EDIT') + for b in orig_armature.data.edit_bones: + print(b.name) + if b.parent is None: + queue.append(b.name) + print("Copying bones...") + while len(queue) > 0: + item = queue.pop(0) + print(item) + itemb = orig_armature.data.edit_bones[item] + if not itemb.use_deform and not check_bone(item): + continue + for cb in orig_armature.data.edit_bones: + if cb.parent == itemb: + queue.append(cb.name) + nb = new_armature.data.edit_bones.new(item) + nb.name = item + nb.head = itemb.head + nb.tail = itemb.tail + nb.matrix = itemb.matrix + nb.use_deform = itemb.use_deform + if itemb.parent is not None: + ok = True + pname = itemb.parent.name + while not check_bone(pname): + bparent = itemb.parent.parent + if bparent is None: + ok = False + break + pname = bparent.name + if ok: + nb.parent = new_armature.data.edit_bones[itemb.parent.name] + else: + nb.parent = None + else: + nb.parent = None + nb.use_connect = itemb.use_connect +# drivers_data = new_armature.animation_data.drivers + print("Creating constraints...") + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='OBJECT') + bpy.context.view_layer.objects.active = orig_armature + bpy.ops.object.mode_set(mode='OBJECT') + for b in new_armature.pose.bones: + print(b.name) + c = b.constraints.new(type='COPY_TRANSFORMS') + c.target = orig_armature + c.subtarget = b.name + for obj in bpy.data.objects: + if obj.parent == orig_armature: + obj.parent = new_armature + for mod in obj.modifiers: + if mod.type == 'ARMATURE': + mod.object = new_armature + print("Baking actions...") + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='POSE') + for track in orig_armature.animation_data.nla_tracks: + print(track.name) + for s in track.strips: + action = s.action + print(action.name) + orig_armature.animation_data.action = action + new_armature.animation_data.action = None + bpy.context.view_layer.objects.active = new_armature + firstFrame = int(s.action_frame_start) + lastFrame = int(s.action_frame_end) + bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'}) + aname = orig_armature.animation_data.action.name + orig_armature.animation_data.action.name = "bake_" + aname + new_armature.animation_data.action.name = aname + track = new_armature.animation_data.nla_tracks.new() + track.name = aname + track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action) + track.mute = True + track.lock = True + print("Removing constraints...") + for b in new_armature.pose.bones: + for c in b.constraints: + b.constraints.remove(c) + new_armature.animation_data.action = bpy.data.actions[mapping.default_action] + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='OBJECT') + + +obj_data = {} +for mapping in [ExportMappingFemale(), ExportMappingMale()]: + print("Initializing...") + bpy.ops.wm.read_homefile(use_empty=True) + print("Preparing driver setup...") + bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg + bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear + bpy.app.driver_namespace["extra_linear"] = extra_linear + bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x + print("Driver setup done...") + + bpy.ops.wm.append( + filepath=os.path.join(mapping.char_blend_path, mapping.inner_path), + directory=os.path.join(mapping.char_blend_path, mapping.inner_path), + files=mapping.files) + for obj in bpy.data.objects: + if obj.name.startswith("bone-"): + bpy.data.objects.remove(obj) + print("Append character done...") + prepare_armature(mapping) + print("Armature done...") + print("Removing original armature and actions...") + orig_arm = bpy.data.objects[mapping.armature_name + '_orig'] + bpy.data.objects.remove(orig_arm) + for act in bpy.data.actions: + if act.name.startswith("bake_"): + act.name = act.name + "-noimp" + for act in bpy.data.actions: + if act.name.startswith("bake_"): + bpy.data.actions.remove(act) + print("Removing original armature and actions done...") + + for filepath in mapping.cloth_blend_paths: + with bpy.data.libraries.load(filepath) as (data_from, data_to): + data_to.objects = [ob for ob in data_from.objects if not ob.endswith('noimp')] + for obj in data_to.objects: + if obj.type == 'MESH': + bpy.context.scene.collection.objects.link(obj) + obj.parent = bpy.data.objects[mapping.armature_name] + arm_mod = obj.modifiers.new('armature', 'ARMATURE') + arm_mod.object = bpy.data.objects[mapping.armature_name] + for vg in obj.vertex_groups: + obj.vertex_groups.remove(vg) + bpy.ops.object.select_all(action='DESELECT') + bpy.data.objects[obj.name].select_set(True) + src_name = "body" + params = "" + otype = "nearest" + distance = 0.1 + if "src_obj" in obj: + obname = obj["src_obj"] + if obname.find(";") >= 0: + src_name, params = obname.split(";"); + vpar, vdist = params.split("=") + otype = vpar + distance = float(vdist) + else: + src_name = obname + if src_name not in bpy.data.objects: + src_name = "ref-" + src_name + "-noimp" + keywords = [] + keywords.append(mapping.sex) + if "slot" in obj: + keywords.append(obj["slot"]) + if "keywords" in obj: + kw = obj["keywords"].split(",") + for xtk in kw: + keywords.append(xtk.strip()) + if "state" in obj: + keywords.append(obj["state"].strip()) + else: + if obj.name.endswith("-damaged"): + keywords.append("damaged") + elif obj.name.endswith("-revealing"): + keywords.append("revealing") + else: + keywords.append("normal") + if "speciality" in obj: + keywords.append(obj["speciality"].strip()) + else: + keywords.append("common") + keywords.append(obj.name.replace("-damaged", "").replace("-revealing", "")) + obj_data[obj.name] = {} + obj_data[obj.name]["keywords"] = keywords + sobj = src_name + vert_mapping = "NEAREST" + use_distance = True + bpy.data.objects[src_name].select_set(True) + bpy.context.view_layer.objects.active = bpy.data.objects[obj.name] + bpy.ops.paint.weight_paint_toggle() + if otype == "nearest": + vert_mapping = "NEAREST" + elif otype == "poly": + vert_mapping = "POLYINTERP_NEAREST" + if distance <= 0.0001: + use_distance = False + bpy.ops.object.data_transfer(use_reverse_transfer=True, + data_type='VGROUP_WEIGHTS', use_create=True, + vert_mapping = vert_mapping, + layers_select_src='NAME', + max_distance = distance, + use_max_distance = use_distance, + layers_select_dst='ALL') + bpy.ops.paint.weight_paint_toggle() + print("Append clothes done...") + for obj in bpy.data.objects: + if obj.name.endswith("noimp"): + bpy.data.objects.remove(obj) + elif obj.name == "body": + obj.name = "body-noimp" + else: + bpy.context.view_layer.objects.active = obj + for modifier in obj.modifiers: + if modifier.type != 'ARMATURE': + bpy.ops.object.modifier_apply(modifier = modifier.name) + + +# print("Removing original armature and actions...") +# orig_arm = bpy.data.objects[mapping.armature_name + '_orig'] +# bpy.data.objects.remove(orig_arm) +# for act in bpy.data.actions: +# if act.name.startswith("bake_"): +# bpy.data.actions.remove(act) +# for obj in bpy.data.objects: +# if obj.type == 'MESH': +# if not obj.name in mapping.objs and obj.parent is None: +# if not obj.name.endswith("-noimp"): +# obj.name = obj.name + "-noimp" + for obj in bpy.data.objects: + if obj.name in mapping.objs_remove: + bpy.data.objects.remove(obj) + + bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile)) + + bpy.ops.export_scene.gltf(filepath=mapping.gltf_path, + use_selection=False, + check_existing=False, + export_format='GLTF_SEPARATE', + export_texture_dir='textures', export_texcoords=True, + export_normals=True, + export_tangents=False, + export_materials='EXPORT', + export_colors=True, + use_mesh_edges=False, + use_mesh_vertices=False, + export_cameras=False, + use_visible=False, + use_renderable=False, + export_yup=True, + export_animations=False, + export_force_sampling=True, + export_def_bones=False, + export_current_frame=False, + export_morph=True, + export_morph_normal=True, + export_lights=False) + +with open(basepath + "/godot/clothes/clothes.json", "w") as write_file: + json.dump(obj_data, write_file, indent=8) + +bpy.ops.wm.read_homefile(use_empty=True) +time.sleep(2) +bpy.ops.wm.quit_blender() diff --git a/assets/blender/scripts/export_for_modelling.py b/assets/blender/scripts/export_for_modelling.py new file mode 100644 index 0000000..b33c877 --- /dev/null +++ b/assets/blender/scripts/export_for_modelling.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +import os, sys, time +import bpy +from math import pi +import glob +import shutil +from mathutils import Vector, Matrix +from math import radians, pi +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") +from vrm import rename +from mixamo import mixamo_rig +from mixamo.lib.armature import * +#from mixamo.mixamoroot import import_armature +#from mixamo.import_mixamo_root_motion import import_mixamo_root_motion + +from settings import VRMDataFemale, VRMDataMale, basepath + +imports = [VRMDataFemale(), VRMDataMale()] + + +class mkrig: + ik_arms = True + ik_legs = True + def __init__(self): + mixamo_rig._make_rig(self) + mixamo_rig.remove_temp_objects() + mixamo_rig.clean_scene() +def get_anim(filepath, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False): +# current_context = bpy.context.area.ui_type + old_objs = set(bpy.context.scene.objects) + try: + import_armature(filepath, root_bone_name, hip_bone_name, remove_prefix, name_prefix, insert_root, delete_armatures) + imported_objects = set(bpy.context.scene.objects) - old_objs + if delete_armatures and num_files > 1: + deleteArmature(imported_objects) + except Exception as e: + log.error("[Mixamo Root] ERROR get_all_anims raised %s when processing %s" % (str(e), filepath)) +# bpy.context.area.ui_type = current_context + bpy.context.scene.frame_start = 0 + bpy.ops.object.mode_set(mode='OBJECT') +def create_root_bone(anim_obj): + bpy.context.view_layer.objects.active = anim_obj + bpy.ops.object.mode_set(mode='EDIT') + root_bone = anim_obj.data.edit_bones.new("Root") + root_bone.tail.y = 0.5 + anim_obj.data.edit_bones["mixamorig:Hips"].parent = anim_obj.data.edit_bones["Root"] + bpy.ops.object.mode_set(mode='OBJECT') +def hips_to_root(anim_obj): + for fc in anim_obj.animation_data.action.fcurves: + if "mixamorig:Hips" in fc.data_path: + if "location" in fc.data_path: + print(fc, fc.data_path, fc.array_index) + if fc.array_index in [0, 2]: + fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root") +# if fc.array_index == 2: +# for kp in fc.keyframe_points: +# kp.co.z = min(0, abs(kp.co.z)) +# hip_curves = [fc for fc in anim_obj.animation_data.action.fcurves if hip_bone_name in fc.data_path and fc.data_path.startswith('location')] +# print(hip_curves) +def hips_to_root(anim_obj): + for fc in anim_obj.animation_data.action.fcurves: + if "mixamorig:Hips" in fc.data_path: + if "location" in fc.data_path: + print(fc, fc.data_path, fc.array_index) + if fc.array_index in [0, 2]: + fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root") +def hips_to_root2(anim_obj): + for fc in anim_obj.animation_data.action.fcurves: + if "Ctrl_Hips" in fc.data_path: + if "location" in fc.data_path: + print(fc, fc.data_path, fc.array_index) + if fc.array_index in [0, 2]: + data_path = fc.data_path[:] +# fc.data_path = data_path.replace("Ctrl_Hips", "Ctrl_Master") + fcr = anim_obj.animation_data.action.fcurves.new(data_path = data_path.replace("Ctrl_Hips", "Root"), index = fc.array_index) + keys = [0] * (2 * len(fc.keyframe_points)) + fcr.keyframe_points.add(len(fc.keyframe_points)) + fc.keyframe_points.foreach_get("co", keys) + fcr.keyframe_points.foreach_set("co", keys) + print("ROOT") +# fc.data_path = data_path.replace("Ctrl_Hips", "Root") +# for fcr in anim_obj.animation_data.action.fcurves: + # if "Root" in fcr.data_path: +# fcr.array_index = fc.array_index +# print("ROOT") + +for imp in imports: + bpy.ops.wm.read_homefile(use_empty=True) + for o in bpy.data.objects: + bpy.data.objects.remove(o) + print(basepath + "/assets/vroid/" + imp.path) + result = bpy.ops.import_scene.vrm(filepath=(basepath + "/assets/vroid/" + imp.path)) + if result != {"FINISHED"}: + raise Exception(f"Failed to import vrm: {result}") + armature_count = 0 + for obj in bpy.data.objects: + if (obj.type == "ARMATURE"): + armature_count += 1 + if armature_count > 1: + raise Exception("Bad scene data") + main_armature = None + for obj in bpy.data.objects: + if (obj.type == "ARMATURE"): + main_armature = obj + break + if main_armature == None: + raise Exception("Bad scene data") + print("Renaming...") + main_armature.select_set(True) + bpy.context.view_layer.objects.active = main_armature + rename.rename_bones(main_armature) + main_armature.select_set(False) + main_armature.name = imp.armature_name + main_armature["VRM_IMPORTED_NAME"] = "female" + if main_armature.animation_data is None: + main_armature.animation_data_create() + + main_armature.select_set(False) + bpy.context.view_layer.objects.active = None + bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/" + imp.editfile)) diff --git a/assets/blender/scripts/export_models.py b/assets/blender/scripts/export_models.py new file mode 100644 index 0000000..948e5d9 --- /dev/null +++ b/assets/blender/scripts/export_models.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python + +import os, sys, time +import bpy +from math import pi +import glob +import shutil +from mathutils import Vector, Matrix +from math import radians, pi + +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") +from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited + +basepath = os.getcwd() +def check_bone(bname): + ok = True + baddie = ["ctrl_", "mch_", "MCH_", "Ctrl_", "Mch_"] + for bd in baddie: + if bname.startswith(bd): + ok = False + break + return ok + +def prepare_armature(mapping): + print("Preparing...") + bpy.ops.object.armature_add(enter_editmode=False) + new_armature = bpy.context.object + orig_armature = bpy.data.objects[mapping.armature_name] + armature_name = orig_armature.name + orig_armature.name = orig_armature.name + "_orig" + new_armature.name = armature_name + queue = [] + if new_armature.animation_data is None: + new_armature.animation_data_create() + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='EDIT') + for b in new_armature.data.edit_bones: + new_armature.data.edit_bones.remove(b) + bpy.context.view_layer.objects.active = orig_armature + bpy.ops.object.mode_set(mode='EDIT') + for b in orig_armature.data.edit_bones: + print(b.name) + if b.parent is None: + queue.append(b.name) + print("Copying bones...") + while len(queue) > 0: + item = queue.pop(0) + print(item) + itemb = orig_armature.data.edit_bones[item] + if not itemb.use_deform and not check_bone(item): + continue + for cb in orig_armature.data.edit_bones: + if cb.parent == itemb: + queue.append(cb.name) + nb = new_armature.data.edit_bones.new(item) + nb.name = item + nb.head = itemb.head + nb.tail = itemb.tail + nb.matrix = itemb.matrix + nb.use_deform = itemb.use_deform + if itemb.parent is not None: + ok = True + pname = itemb.parent.name + while not check_bone(pname): + bparent = itemb.parent.parent + if bparent is None: + ok = False + break + pname = bparent.name + if ok: + nb.parent = new_armature.data.edit_bones[itemb.parent.name] + else: + nb.parent = None + else: + nb.parent = None + nb.use_connect = itemb.use_connect +# drivers_data = new_armature.animation_data.drivers + print("Creating constraints...") + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='OBJECT') + bpy.context.view_layer.objects.active = orig_armature + bpy.ops.object.mode_set(mode='OBJECT') + for b in new_armature.pose.bones: + print(b.name) + c = b.constraints.new(type='COPY_TRANSFORMS') + c.target = orig_armature + c.subtarget = b.name + for obj in bpy.data.objects: + if obj.parent == orig_armature: + obj.parent = new_armature + for mod in obj.modifiers: + if mod.type == 'ARMATURE': + mod.object = new_armature + print("Baking actions...") + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='POSE') + for track in orig_armature.animation_data.nla_tracks: + print(track.name) + for s in track.strips: + action = s.action + print(action.name) + orig_armature.animation_data.action = action + new_armature.animation_data.action = None + bpy.context.view_layer.objects.active = new_armature + firstFrame = int(s.action_frame_start) + lastFrame = int(s.action_frame_end) + bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'}) + aname = orig_armature.animation_data.action.name + orig_armature.animation_data.action.name = "bake_" + aname + new_armature.animation_data.action.name = aname + track = new_armature.animation_data.nla_tracks.new() + track.name = aname + track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action) + track.mute = True + track.lock = True + print("Removing constraints...") + for b in new_armature.pose.bones: + for c in b.constraints: + b.constraints.remove(c) + new_armature.animation_data.action = bpy.data.actions[mapping.default_action] + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='OBJECT') + +# track = new_armature.animation_data.nla_tracks.new() +# track.name = action.name + + +def clamp_angle_deg(angle, min_angle_deg, max_angle_deg): + min_angle = radians(min_angle_deg) + max_angle = radians(max_angle_deg) + if angle < min_angle: + angle = min_angle + if angle > max_angle: + angle = max_angle + return angle +def angle_to_linear(angle, divider): + if angle < 0.0: + return angle / divider + else: + return 0.0 +def angle_to_linear_x(bone, angle): + skel = bpy.data.objects["skeleton_orig"] + left_base = "ctrl_base_upperleg.L.001" + right_base = "ctrl_base_upperleg.R.001" + base = "" + if base == "": + for e in ["_R", ".R"]: + if bone.name.endswith(e): + base = right_base + break + if base == "": + for e in ["_L", ".L"]: + if bone.name.endswith(e): + base = left_base + break + if base == "": + for e in ["_R.", ".R."]: + if bone.name.find(e) >= 0: + base = right_base + break + if base == "": + for e in ["_L.", ".L."]: + if bone.name.find(e) >= 0: + base = left_base + break + mul = skel.pose.bones[base]["to_linear_x_base"] + offset = skel.pose.bones[base]["angle_offset"] +# print("bone: ", bone.name, "base: ", base, "mul: ", mul) +# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset) + print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle) + return (angle + offset) * mul +def extra_linear(angle, offset): + ret = 0.0 + offt = offset * angle * 2.0 / -radians(-90) + if angle * 2.0 < -radians(65): + if angle * 2.0 > -radians(65): + ret += offset + else: + ret += offt + return ret + +for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited()]: + if not os.path.exists(mapping.blend_path): + continue +#for mapping in [ExportMappingFemale()]: + print("Initializing...") + bpy.ops.wm.read_homefile(use_empty=True) + print("Preparing driver setup...") + bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg + bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear + bpy.app.driver_namespace["extra_linear"] = extra_linear + bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x + print("Driver setup done...") + + bpy.ops.wm.append( + filepath=os.path.join(mapping.blend_path, mapping.inner_path), + directory=os.path.join(mapping.blend_path, mapping.inner_path), + files=mapping.files) + print("Append done...") + + prepare_armature(mapping) + print("Armature done...") + + print("Remove junk...") + for ob in bpy.data.objects: + if ob.name.startswith("cs_"): + bpy.data.objects.remove(ob) + elif ob.name.startswith("Face") and ob.name != "Face": + bpy.data.objects.remove(ob) + + print("Removing original armature and actions...") + orig_arm = bpy.data.objects[mapping.armature_name + '_orig'] + bpy.data.objects.remove(orig_arm) + for act in bpy.data.actions: + if act.name.startswith("bake_"): + act.name = act.name + "-noimp" + for act in bpy.data.actions: + if act.name.startswith("bake_"): + bpy.data.actions.remove(act) + for act in bpy.data.actions: + if act.name.startswith("bake_"): + bpy.data.actions.remove(act) + for obj in bpy.data.objects: + if obj.type == 'MESH': + if not obj.name in mapping.objs and obj.parent is None: + if not obj.name.endswith("-noimp"): + obj.name = obj.name + "-noimp" + bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile)) + + os.makedirs(os.path.dirname(mapping.gltf_path), exist_ok=True) + bpy.ops.export_scene.gltf(filepath=mapping.gltf_path, + use_selection=False, + check_existing=False, +# export_format='GLTF_SEPARATE', + export_format='GLB', + export_texture_dir='textures', export_texcoords=True, + export_animation_mode='NLA_TRACKS', + export_normals=True, + export_tangents=True, + export_materials='EXPORT', +# export_all_vertex_colors=True, +# colors_type='SRGB', +# export_vertex_colors=True, + export_colors=True, + use_mesh_edges=False, + use_mesh_vertices=False, + export_cameras=False, + use_visible=False, + use_renderable=False, + export_yup=True, + export_animations=True, + export_force_sampling=True, + export_def_bones=False, + export_current_frame=False, + export_morph=True, + export_morph_animation=False, + export_morph_normal=True, + export_morph_tangent=True, + export_lights=False, + export_skins=True) + +bpy.ops.wm.read_homefile(use_empty=True) +time.sleep(2) +bpy.ops.wm.quit_blender() diff --git a/assets/blender/scripts/export_ogre_scene.py b/assets/blender/scripts/export_ogre_scene.py new file mode 100644 index 0000000..35e0623 --- /dev/null +++ b/assets/blender/scripts/export_ogre_scene.py @@ -0,0 +1,289 @@ +import os, sys, time +import bpy + +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") + +from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape + +basepath = os.getcwd() +def check_bone(bname): + ok = True + baddie = ["ctrl_", "mch_", "MCH_", "Ctrl_", "Mch_"] + for bd in baddie: + if bname.startswith(bd): + ok = False + break + return ok + +def prepare_armature(mapping): + print("Preparing...") + bpy.ops.object.armature_add(enter_editmode=False) + new_armature = bpy.context.object + orig_armature = bpy.data.objects[mapping.armature_name] + armature_name = orig_armature.name + orig_armature.name = orig_armature.name + "_orig" + new_armature.name = armature_name + queue = [] + if new_armature.animation_data is None: + new_armature.animation_data_create() + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='EDIT') + for b in new_armature.data.edit_bones: + new_armature.data.edit_bones.remove(b) + bpy.context.view_layer.objects.active = orig_armature + bpy.ops.object.mode_set(mode='EDIT') + for b in orig_armature.data.edit_bones: + print(b.name) + if b.parent is None: + queue.append(b.name) + print("Copying bones...") + while len(queue) > 0: + item = queue.pop(0) + print(item) + itemb = orig_armature.data.edit_bones[item] + if not itemb.use_deform and not check_bone(item): + continue + for cb in orig_armature.data.edit_bones: + if cb.parent == itemb: + queue.append(cb.name) + nb = new_armature.data.edit_bones.new(item) + nb.name = item + nb.head = itemb.head + nb.tail = itemb.tail + nb.matrix = itemb.matrix + nb.use_deform = itemb.use_deform + if itemb.parent is not None: + ok = True + pname = itemb.parent.name + while not check_bone(pname): + bparent = itemb.parent.parent + if bparent is None: + ok = False + break + pname = bparent.name + if ok: + nb.parent = new_armature.data.edit_bones[itemb.parent.name] + else: + nb.parent = None + else: + nb.parent = None + nb.use_connect = itemb.use_connect +# drivers_data = new_armature.animation_data.drivers + print("Creating constraints...") + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='OBJECT') + bpy.context.view_layer.objects.active = orig_armature + bpy.ops.object.mode_set(mode='OBJECT') + for b in new_armature.pose.bones: + print(b.name) + c = b.constraints.new(type='COPY_TRANSFORMS') + c.target = orig_armature + c.subtarget = b.name + for obj in bpy.data.objects: + if obj.parent == orig_armature: + obj.parent = new_armature + for mod in obj.modifiers: + if mod.type == 'ARMATURE': + mod.object = new_armature + print("Baking actions...") + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='POSE') + for track in orig_armature.animation_data.nla_tracks: + print(track.name) + for s in track.strips: + action = s.action + print(action.name) + orig_armature.animation_data.action = action + new_armature.animation_data.action = None + bpy.context.view_layer.objects.active = new_armature + firstFrame = int(s.action_frame_start) + lastFrame = int(s.action_frame_end) + bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'}) + aname = orig_armature.animation_data.action.name + orig_armature.animation_data.action.name = "bake_" + aname + new_armature.animation_data.action.name = aname + track = new_armature.animation_data.nla_tracks.new() + track.name = aname + track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action) + track.mute = True + track.lock = True + print("Removing constraints...") + for b in new_armature.pose.bones: + for c in b.constraints: + b.constraints.remove(c) + new_armature.animation_data.action = bpy.data.actions[mapping.default_action] + bpy.context.view_layer.objects.active = new_armature + bpy.ops.object.mode_set(mode='OBJECT') + +# track = new_armature.animation_data.nla_tracks.new() +# track.name = action.name + + +def clamp_angle_deg(angle, min_angle_deg, max_angle_deg): + min_angle = radians(min_angle_deg) + max_angle = radians(max_angle_deg) + if angle < min_angle: + angle = min_angle + if angle > max_angle: + angle = max_angle + return angle +def angle_to_linear(angle, divider): + if angle < 0.0: + return angle / divider + else: + return 0.0 +def angle_to_linear_x(bone, angle): + skel = bpy.data.objects["skeleton_orig"] + left_base = "ctrl_base_upperleg.L.001" + right_base = "ctrl_base_upperleg.R.001" + base = "" + if base == "": + for e in ["_R", ".R"]: + if bone.name.endswith(e): + base = right_base + break + if base == "": + for e in ["_L", ".L"]: + if bone.name.endswith(e): + base = left_base + break + if base == "": + for e in ["_R.", ".R."]: + if bone.name.find(e) >= 0: + base = right_base + break + if base == "": + for e in ["_L.", ".L."]: + if bone.name.find(e) >= 0: + base = left_base + break + mul = skel.pose.bones[base]["to_linear_x_base"] + offset = skel.pose.bones[base]["angle_offset"] +# print("bone: ", bone.name, "base: ", base, "mul: ", mul) +# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset) + print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle) + return (angle + offset) * mul +def extra_linear(angle, offset): + ret = 0.0 + offt = offset * angle * 2.0 / -radians(-90) + if angle * 2.0 < -radians(65): + if angle * 2.0 > -radians(65): + ret += offset + else: + ret += offt + return ret + +for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape()]: +#for mapping in [ExportMappingFemale()]: + print("Initializing...") + bpy.ops.wm.read_homefile(use_empty=True) + print("Preparing driver setup...") + bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg + bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear + bpy.app.driver_namespace["extra_linear"] = extra_linear + bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x + print("Driver setup done...") + + bpy.ops.wm.append( + filepath=os.path.join(mapping.blend_path, mapping.inner_path), + directory=os.path.join(mapping.blend_path, mapping.inner_path), + files=mapping.files) + print("Append done...") + + prepare_armature(mapping) + print("Armature done...") + + print("Remove junk...") + for ob in bpy.data.objects: + if ob.name.startswith("cs_"): + bpy.data.objects.remove(ob) + elif ob.name.startswith("Face") and ob.name != "Face": + bpy.data.objects.remove(ob) + + print("Removing original armature and actions...") + orig_arm = bpy.data.objects[mapping.armature_name + '_orig'] + bpy.data.objects.remove(orig_arm) + for act in bpy.data.actions: + if act.name.startswith("bake_"): + act.name = act.name + "-noimp" + for act in bpy.data.actions: + if act.name.startswith("bake_"): + bpy.data.actions.remove(act) + for act in bpy.data.actions: + if act.name.startswith("bake_"): + bpy.data.actions.remove(act) + for obj in bpy.data.objects: + if obj.type == 'MESH': + if not obj.name in mapping.objs and obj.parent is None: + if not obj.name.endswith("-noimp"): + obj.name = obj.name + "-noimp" + name_prefix = os.path.basename(mapping.ogre_scene).replace(".scene", "") + for o in bpy.data.objects: + if o.type == 'MESH' or o.type == 'ARMATURE': + if o.name.startswith(name_prefix): + continue + o.name = name_prefix + "_" + o.name.strip() + for o in bpy.data.meshes: + if o.name.startswith(name_prefix): + continue + o.name = name_prefix + "_" + o.name + o.name = o.name.replace("(merged)", "").strip() + for m in bpy.data.materials: + if m.name.startswith(name_prefix): + continue + m.name = name_prefix + "_" + m.name + m.name = m.name.replace("(merged)", "").strip() + anon = 0 + for m in bpy.data.images: + if m.name.startswith(name_prefix) and m.filepath.startswith(name_prefix): + continue + if m.name == "": + m.name = "anonymous_" + str(anon) + anon = anon + 1 + if m.filepath == "": + m.filepath = "anonymous_" + str(anon) + ".png" + anon = anon + 1 + if not m.name.startswith(name_prefix): + m.name = name_prefix + "_" + m.name + m.name = m.name.replace("(merged)", "").strip() + if not m.filepath.startswith(name_prefix): + m.filepath = m.filepath.replace("(merged)", "_").strip() + m.filepath = m.filepath.replace(" ", "_") + m.filepath = name_prefix + "_" + os.path.basename(m.filepath) + bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile)) + bpy.ops.ogre.export(filepath=mapping.ogre_scene, EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='1') + +# bpy.ops.export_scene.gltf(filepath=mapping.gltf_path.replace(".npcshape", ".gltf").replace(".npc", ".gltf"), +# use_selection=False, +# check_existing=False, +# export_format='GLTF_SEPARATE', +# export_texture_dir='textures', export_texcoords=True, +# export_normals=True, +# export_tangents=True, +# export_materials='EXPORT', +## export_all_vertex_colors=True, +## colors_type='SRGB', +## export_vertex_colors=True, +# export_colors=True, +# use_mesh_edges=False, +# use_mesh_vertices=False, +# export_cameras=False, +# use_visible=False, +# use_renderable=False, +# export_yup=True, +# export_animations=True, +# export_force_sampling=True, +# export_def_bones=False, +# export_current_frame=False, +# export_morph=True, +# export_morph_animation=False, +# export_morph_normal=True, +# export_morph_tangent=True, +# export_lights=False, +# export_skins=True) +# shutil.move(mapping.gltf_path.replace(".npcshape", ".gltf").replace(".npc", ".gltf"), mapping.gltf_path) + + +bpy.ops.wm.read_homefile(use_empty=True) +time.sleep(2) +bpy.ops.wm.quit_blender() diff --git a/assets/blender/scripts/geometry/tris2quads.py b/assets/blender/scripts/geometry/tris2quads.py new file mode 100644 index 0000000..45a1498 --- /dev/null +++ b/assets/blender/scripts/geometry/tris2quads.py @@ -0,0 +1,14 @@ +import bmesh +import bpy +import subprocess +import sys + +def tris2quads(obj): + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.object.mode_set(mode='OBJECT') + obj.select_set(False) + bpy.context.view_layer.objects.active = None diff --git a/assets/blender/scripts/import_vrm.py b/assets/blender/scripts/import_vrm.py new file mode 100644 index 0000000..4dc948a --- /dev/null +++ b/assets/blender/scripts/import_vrm.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python + +import os, sys, time +import bpy +from math import pi +import glob +import shutil +from mathutils import Vector, Matrix +from math import radians, pi +if bpy.app.version[0] == 3: + sys.path.insert(0, os.getcwd() + "/assets/blender/scripts/mixamo/3.6") +if bpy.app.version[0] == 4: + sys.path.insert(0, os.getcwd() + "/assets/blender/scripts/mixamo/4.3") +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") +from vrm import rename +if bpy.app.version[0] == 3: + from mixamo import mixamo_rig +from mixamo.lib.armature import * +from settings import VRMDataFemale, VRMDataMale, VRMDataMaleBabyShape, basepath +from geometry import tris2quads + +imports = [VRMDataFemale(), VRMDataMale(), VRMDataMaleBabyShape()] + +class mkrig: + ik_arms = True + ik_legs = True + def __init__(self): + mixamo_rig._make_rig(self) + mixamo_rig.remove_temp_objects() + mixamo_rig.clean_scene() +def get_anim(filepath, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False): + old_objs = set(bpy.context.scene.objects) + try: + import_armature(filepath, root_bone_name, hip_bone_name, remove_prefix, name_prefix, insert_root, delete_armatures) + imported_objects = set(bpy.context.scene.objects) - old_objs + if delete_armatures and num_files > 1: + deleteArmature(imported_objects) + except Exception as e: + log.error("[Mixamo Root] ERROR get_all_anims raised %s when processing %s" % (str(e), filepath)) + bpy.context.scene.frame_start = 0 + bpy.ops.object.mode_set(mode='OBJECT') +def create_root_bone(anim_obj): + bpy.context.view_layer.objects.active = anim_obj + bpy.ops.object.mode_set(mode='EDIT') + root_bone = anim_obj.data.edit_bones.new("Root") + root_bone.tail.y = 0.5 + anim_obj.data.edit_bones["mixamorig:Hips"].parent = anim_obj.data.edit_bones["Root"] + bpy.ops.object.mode_set(mode='OBJECT') +def hips_to_root(anim_obj): + for fc in anim_obj.animation_data.action.fcurves: + if "mixamorig:Hips" in fc.data_path: + if "location" in fc.data_path: + print(fc, fc.data_path, fc.array_index) + if fc.array_index in [0, 2]: + fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root") +def hips_to_root(anim_obj): + for fc in anim_obj.animation_data.action.fcurves: + if "mixamorig:Hips" in fc.data_path: + if "location" in fc.data_path: + print(fc, fc.data_path, fc.array_index) + if fc.array_index in [0, 2]: + fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root") +def hips_to_root2(anim_obj): + for fc in anim_obj.animation_data.action.fcurves: + if "Ctrl_Hips" in fc.data_path: + if "location" in fc.data_path: + print(fc, fc.data_path, fc.array_index) + if fc.array_index in [0, 2]: + data_path = fc.data_path[:] + fcr = anim_obj.animation_data.action.fcurves.new(data_path = data_path.replace("Ctrl_Hips", "Root"), index = fc.array_index) + keys = [0] * (2 * len(fc.keyframe_points)) + fcr.keyframe_points.add(len(fc.keyframe_points)) + fc.keyframe_points.foreach_get("co", keys) + fcr.keyframe_points.foreach_set("co", keys) + print("ROOT") + +for imp in imports: + bpy.ops.wm.read_homefile(use_empty=True) + for o in bpy.data.objects: + bpy.data.objects.remove(o) + print(basepath + "/assets/vroid/" + imp.path) + result = bpy.ops.import_scene.vrm(filepath=(basepath + "/assets/vroid/" + imp.path)) + if result != {"FINISHED"}: + raise Exception(f"Failed to import vrm: {result}") + for o in bpy.data.objects: + if o.type == 'MESH': + tris2quads.tris2quads(o) + armature_count = 0 + for obj in bpy.data.objects: + if (obj.type == "ARMATURE"): + armature_count += 1 + if armature_count > 1: + raise Exception("Bad scene data") + main_armature = None + for obj in bpy.data.objects: + if (obj.type == "ARMATURE"): + main_armature = obj + break + if main_armature == None: + raise Exception("Bad scene data") + print("Renaming...") + main_armature.select_set(True) + bpy.context.view_layer.objects.active = main_armature + rename.rename_bones(main_armature) + main_armature.select_set(False) + main_armature.name = imp.armature_name + main_armature["VRM_IMPORTED_NAME"] = "female" + if main_armature.animation_data is None: + main_armature.animation_data_create() + main_armature.select_set(True) + bpy.context.view_layer.objects.active = main_armature + mkrig() + main_armature.select_set(False) + bpy.context.view_layer.objects.active = None + + for anim in imp.mixamo_animations: + anim_path = imp.mixamo_animation_path + anim + ".fbx" + print("Load BVH..." + anim_path) + old_objs = set(bpy.context.scene.objects) + bpy.ops.import_scene.fbx(filepath=anim_path, global_scale=imp.fbx_scale) + imported_objects = set(bpy.context.scene.objects) - old_objs + for anim_obj in imported_objects: + if anim_obj == main_armature: + continue + if "VRM_IMPORTED_NAME" in anim_obj: + continue + if main_armature == anim_obj or anim_obj.type != "ARMATURE": + continue + if anim_obj.animation_data == None: + print("Bad object: " + anim_obj.name + " " + anim_obj.type) + bpy.data.objects.remove(anim_obj) + continue + if anim_obj.animation_data.action == None: + print("Bad object: " + anim_obj.name + " " + anim_obj.type) + bpy.data.objects.remove(anim_obj) + continue + print("importing: " + anim_obj.animation_data.action.name) + mixamo_rig._import_anim(anim_obj, main_armature, True) + print("===" + main_armature.animation_data.action.name) + main_armature.animation_data.action.name = anim + hips_to_root2(main_armature) + track = main_armature.animation_data.nla_tracks.new() + action = main_armature.animation_data.action + track.name = action.name + track.strips.new(action.name, int(action.frame_range[0]), action) + track.mute = True + track.lock = True + bpy.data.objects.remove(anim_obj) + imported_objects.clear() + imported_objects = set(bpy.context.scene.objects) - old_objs + for anim_obj in imported_objects: + bpy.data.objects.remove(anim_obj) + + main_armature.animation_data.action = bpy.data.actions.new(name="default") + default_action = main_armature.animation_data.action + bpy.context.view_layer.objects.active = main_armature + main_armature.select_set(True) + layers = enable_all_armature_layers() + bpy.ops.object.mode_set(mode='POSE') + for b in main_armature.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] + bpy.ops.object.mode_set(mode='OBJECT') + + bpy.ops.object.mode_set(mode='POSE') + for b in main_armature.data.bones: + if b.name.find("Ctrl_") < 0 and b.name.find("ctrl_") < 0: + continue + main_armature.pose.bones[b.name].matrix_basis.identity() + main_armature.pose.bones[b.name].keyframe_insert(data_path='location', frame=1) + rotation_mode = main_armature.pose.bones[b.name].rotation_mode + if rotation_mode == "QUATERNION": + main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_quaternion', frame=1) + elif rotation_mode == "AXIS_ANGLE": + main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_axis_angle', frame=1) + else: + main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_euler', frame=1) + main_armature.pose.bones[b.name].keyframe_insert(data_path='scale', frame=1) + bpy.ops.object.mode_set(mode='OBJECT') + track = main_armature.animation_data.nla_tracks.new() + action = main_armature.animation_data.action + track.name = action.name + track.strips.new(action.name, int(action.frame_range[0]), action) + track.mute = True + track.lock = True + restore_armature_layers(layers) + main_armature.animation_data.action = default_action + + main_armature.select_set(False) + bpy.context.view_layer.objects.active = None + bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/" + imp.outfile)) + diff --git a/assets/blender/scripts/install_addons.py b/assets/blender/scripts/install_addons.py new file mode 100644 index 0000000..0537970 --- /dev/null +++ b/assets/blender/scripts/install_addons.py @@ -0,0 +1,14 @@ +import os +import bpy +path_to_script_dir = os.getcwd() + "/assets/blender/scripts/addons/" + str(bpy.app.version[0]) + "." + str(bpy.app.version[1]) +file_list = sorted(os.listdir(path_to_script_dir)) +script_list = [item for item in file_list if item.endswith('.zip')] +for file in file_list: + path_to_file = os.path.join(path_to_script_dir, file) + bpy.ops.preferences.addon_install(overwrite=True, target='DEFAULT', filepath=path_to_file, filter_folder=True, filter_python=False, filter_glob="*.py;*.zip") +enableTheseAddons = ["VRM_Addon_for_Blender-release"] +for string in enableTheseAddons: + name = enableTheseAddons + bpy.ops.preferences.addon_enable(module = string) +bpy.ops.wm.save_userpref() + diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/__init__.py b/assets/blender/scripts/mixamo/3.6/mixamo/__init__.py new file mode 100644 index 0000000..883d026 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/__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/assets/blender/scripts/mixamo/3.6/mixamo/define.py b/assets/blender/scripts/mixamo/3.6/mixamo/define.py new file mode 100644 index 0000000..70af10e --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/define.py @@ -0,0 +1 @@ +from .definitions.naming import * \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/definitions/naming.py b/assets/blender/scripts/mixamo/3.6/mixamo/definitions/naming.py new file mode 100644 index 0000000..eba88d2 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/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/assets/blender/scripts/mixamo/3.6/mixamo/lib/addon.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/addon.py new file mode 100644 index 0000000..05bfa9f --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/addon.py @@ -0,0 +1,15 @@ +import bpy, sys, linecache, ast + +def get_error_message(): + exc_type, exc_obj, tb = sys.exc_info() + f = tb.tb_frame + lineno = tb.tb_lineno + filename = f.f_code.co_filename + linecache.checkcache(filename) + line = linecache.getline(filename, lineno, f.f_globals) + error_message = 'Error in ({}\nLine {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj) + return error_message + + +def get_addon_preferences(): + return bpy.context.preferences.addons[__package__].preferences \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/animation.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/animation.py new file mode 100644 index 0000000..ab5203b --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/animation.py @@ -0,0 +1,203 @@ +import bpy +from .maths_geo import * +from .bones_pose import * +from .version import * + +def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True, bake_object=False, ik_data=None): + scn = bpy.context.scene + obj_data = [] + bones_data = [] + armature = bpy.data.objects.get(bpy.context.active_object.name) + + def get_bones_matrix(): + matrix = {} + for pbone in armature.pose.bones: + if only_selected and not pbone.bone.select: + continue + + bmat = pbone.matrix + + # IK poles + if pbone.name.startswith("Ctrl_ArmPole") or pbone.name.startswith("Ctrl_LegPole"): + b1 = b2 = None + src_arm = ik_data["src_arm"] + type = "" + if "Leg" in pbone.name: + type = "Leg" + elif "Arm" in pbone.name: + type = "Arm" + + name_split = pbone.name.split('_') + side = name_split[len(name_split)-1] + b1_name = ik_data[type+side][0] + b2_name = ik_data[type+side][1] + b1 = src_arm.pose.bones.get(b1_name) + b2 = src_arm.pose.bones.get(b2_name) + + _axis = None + if type == "Leg": + _axis = (b1.z_axis*0.5) + (b2.z_axis*0.5)#b1.z_axis# + elif type == "Arm": + if side == "Left": + _axis = b2.x_axis + elif side == "Right": + _axis = -b2.x_axis + + pole_pos = get_ik_pole_pos(b1, b2, method=2, axis=_axis) + #pole_pos = b2.head + (b2.z_axis.normalized() * (b2.tail-b2.head).magnitude) + bmat = Matrix.Translation(pole_pos) + + # Child Of constraints are preserved after baking + # need to compensate the matrix with the Child Of transformation + child_of_cns = pbone.constraints.get("Child Of") + if child_of_cns: + if child_of_cns.influence == 1.0 and child_of_cns.mute == False: + bmat = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ bmat + + matrix[pbone.name] = armature.convert_space(pose_bone=pbone, matrix=bmat, from_space="POSE", to_space="LOCAL") + + return matrix + + def get_obj_matrix(): + parent = armature.parent + matrix = armature.matrix_world + if parent: + return parent.matrix_world.inverted_safe() @ matrix + else: + return matrix.copy() + + # store matrices + current_frame = scn.frame_current + + for f in range(int(frame_start), int(frame_end+1)): + scn.frame_set(f) + bpy.context.view_layer.update() + + if bake_bones: + bones_data.append((f, get_bones_matrix())) + if bake_object: + obj_data.append((f, get_obj_matrix())) + + # set new action + action = bpy.data.actions.new("Action") + anim_data = armature.animation_data_create() + anim_data.action = action + + def store_keyframe(bn, prop_type, fc_array_index, fra, val): + fc_data_path = 'pose.bones["' + bn + '"].' + prop_type + fc_key = (fc_data_path, fc_array_index) + if not keyframes.get(fc_key): + keyframes[fc_key] = [] + keyframes[fc_key].extend((fra, val)) + + + # set transforms and store keyframes + if bake_bones: + for pb in armature.pose.bones: + if only_selected and not pb.bone.select: + continue + + euler_prev = None + quat_prev = None + keyframes = {} + + for (f, matrix) in bones_data: + pb.matrix_basis = matrix[pb.name].copy() + + for arr_idx, value in enumerate(pb.location): + store_keyframe(pb.name, "location", arr_idx, f, value) + + rotation_mode = pb.rotation_mode + + if rotation_mode == 'QUATERNION': + if quat_prev is not None: + quat = pb.rotation_quaternion.copy() + quat.make_compatible(quat_prev) + pb.rotation_quaternion = quat + quat_prev = quat + del quat + else: + quat_prev = pb.rotation_quaternion.copy() + + for arr_idx, value in enumerate(pb.rotation_quaternion): + store_keyframe(pb.name, "rotation_quaternion", arr_idx, f, value) + + elif rotation_mode == 'AXIS_ANGLE': + for arr_idx, value in enumerate(pb.rotation_axis_angle): + store_keyframe(pb.name, "rotation_axis_angle", arr_idx, f, value) + + else: # euler, XYZ, ZXY etc + if euler_prev is not None: + euler = pb.rotation_euler.copy() + euler.make_compatible(euler_prev) + pb.rotation_euler = euler + euler_prev = euler + del euler + else: + euler_prev = pb.rotation_euler.copy() + + for arr_idx, value in enumerate(pb.rotation_euler): + store_keyframe(pb.name, "rotation_euler", arr_idx, f, value) + + for arr_idx, value in enumerate(pb.scale): + store_keyframe(pb.name, "scale", arr_idx, f, value) + + + # Add keyframes + for fc_key, key_values in keyframes.items(): + data_path, index = fc_key + fcurve = action.fcurves.find(data_path=data_path, index=index) + if fcurve == None: + fcurve = action.fcurves.new(data_path, index=index, action_group=pb.name) + + num_keys = len(key_values) // 2 + fcurve.keyframe_points.add(num_keys) + fcurve.keyframe_points.foreach_set('co', key_values) + + if blender_version._float >= 290:# internal error when doing so with Blender 2.83, only for Blender 2.90 and higher + linear_enum_value = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value + fcurve.keyframe_points.foreach_set('interpolation', (linear_enum_value,) * num_keys) + else: + for kf in fcurve.keyframe_points: + kf.interpolation = 'LINEAR' + + + if bake_object: + euler_prev = None + quat_prev = None + + for (f, matrix) in obj_data: + name = "Action Bake" + armature.matrix_basis = matrix + + armature.keyframe_insert("location", index=-1, frame=f, group=name) + + rotation_mode = armature.rotation_mode + if rotation_mode == 'QUATERNION': + if quat_prev is not None: + quat = armature.rotation_quaternion.copy() + quat.make_compatible(quat_prev) + armature.rotation_quaternion = quat + quat_prev = quat + del quat + else: + quat_prev = armature.rotation_quaternion.copy() + armature.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name) + elif rotation_mode == 'AXIS_ANGLE': + armature.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name) + else: # euler, XYZ, ZXY etc + if euler_prev is not None: + euler = armature.rotation_euler.copy() + euler.make_compatible(euler_prev) + armature.rotation_euler = euler + euler_prev = euler + del euler + else: + euler_prev = armature.rotation_euler.copy() + armature.keyframe_insert("rotation_euler", index=-1, frame=f, group=name) + + armature.keyframe_insert("scale", index=-1, frame=f, group=name) + + + # restore current frame + scn.frame_set(current_frame) diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/armature.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/armature.py new file mode 100644 index 0000000..1661eb1 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/armature.py @@ -0,0 +1,19 @@ +import bpy + +def restore_armature_layers(layers_select): + # restore the armature layers visibility + for i in range(0, 32): + bpy.context.active_object.data.layers[i] = layers_select[i] + + +def enable_all_armature_layers(): + # enable all layers + # and return the list of each layer visibility + _layers = bpy.context.active_object.data.layers + layers_select = [] + for i in range(0, 32): + layers_select.append(_layers[i]) + for i in range(0, 32): + bpy.context.active_object.data.layers[i] = True + + return layers_select \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_data.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_data.py new file mode 100644 index 0000000..ef8de53 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_data.py @@ -0,0 +1,17 @@ +import bpy + +def get_data_bone(name): + return bpy.context.active_object.data.bones.get(name) + + +def set_bone_layer(databone, layer_idx, multi=False): + if databone == None: + return + + databone.layers[layer_idx] = True + if multi: + return + + for i, lay in enumerate(databone.layers): + if i != layer_idx: + databone.layers[i] = False \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_edit.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_edit.py new file mode 100644 index 0000000..aa14526 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_edit.py @@ -0,0 +1,19 @@ +import bpy + +def get_edit_bone(name): + return bpy.context.object.data.edit_bones.get(name) + + +def copy_bone_transforms(bone1, bone2): + # copy editbone bone1 transforms to bone 2 + bone2.head = bone1.head.copy() + bone2.tail = bone1.tail.copy() + bone2.roll = bone1.roll + + +def create_edit_bone(bone_name, deform=False): + b = get_edit_bone(bone_name) + if b == None: + b = bpy.context.active_object.data.edit_bones.new(bone_name) + b.use_deform = deform + return b \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_pose.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_pose.py new file mode 100644 index 0000000..c20cc59 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_pose.py @@ -0,0 +1,104 @@ +import bpy +from .objects import * +from .version import * + +def get_custom_shape_scale(pbone, uniform=True): + if blender_version._float >= 300: + if uniform: + # uniform scale + val = 0 + for i in range(0,3): + val += pbone.custom_shape_scale_xyz[i] + return val/3 + # array scale + else: + return pbone.custom_shape_scale_xyz + # pre-Blender 3.0 + else: + return pbone.custom_shape_scale + + +def get_selected_pbone_name(): + try: + return bpy.context.selected_pose_bones[0].name#.active_pose_bone.name + except: + return + + +def get_pose_bone(name): + return bpy.context.active_object.pose.bones.get(name) + + +def lock_pbone_transform(pbone, type, list): + for i in list: + if type == "location": + pbone.lock_location[i] = True + elif type == "rotation": + pbone.lock_rotation[i] = True + elif type == "scale": + pbone.lock_scale[i] = True + + +def set_bone_custom_shape(pbone, cs_name): + cs = get_object(cs_name) + if cs == None: + append_cs(cs_name) + cs = get_object(cs_name) + + pbone.custom_shape = cs + + +def set_bone_color_group(obj, pb, grp_name): + # mixamo required color + orange = (0.969, 0.565, 0.208) + orange_light = (0.957, 0.659, 0.416) + blue_dark = (0.447, 0.682, 1.0) + blue_light = (0.365, 0.851, 1.0) + + # base color + green = (0.0, 1.0, 0.0) + red = (1.0, 0.0, 0.0) + blue = (0.0, 0.9, 1.0) + + grp_color_master = orange_light + grp_color_neck = orange_light + grp_color_root_master = orange + grp_color_head = orange + grp_color_body_mid = green + grp_color_body_left = blue_dark + grp_color_body_right = blue_light + + grp = obj.pose.bone_groups.get(grp_name) + if grp == None: + grp = obj.pose.bone_groups.new(name=grp_name) + grp.color_set = 'CUSTOM' + + grp_color = None + if grp_name == "body_mid": + grp_color = grp_color_body_mid + elif grp_name == "body_left": + grp_color = grp_color_body_left + elif grp_name == "body_right": + grp_color = grp_color_body_right + elif grp_name == "master": + grp_color = grp_color_master + elif grp_name == "neck": + grp_color = grp_color_head + elif grp_name == "head": + grp_color = grp_color_neck + elif grp_name == "root_master": + grp_color = grp_color_root_master + + # set normal color + grp.colors.normal = grp_color + + # set select color/active color + for col_idx in range(0,3): + grp.colors.select[col_idx] = grp_color[col_idx] + 0.2 + grp.colors.active[col_idx] = grp_color[col_idx] + 0.4 + + pb.bone_group = grp + + +def update_transform(): + bpy.ops.transform.rotate(value=0, orient_axis='Z', orient_type='VIEW', orient_matrix=((0.0, 0.0, 0), (0, 0.0, 0.0), (0.0, 0.0, 0.0)), orient_matrix_type='VIEW', mirror=False) \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/constraints.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/constraints.py new file mode 100644 index 0000000..56d9229 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/constraints.py @@ -0,0 +1,19 @@ +import bpy + +def add_copy_transf(p_bone, tgt, subtgt): + cns_transf = p_bone.constraints.get("Copy Transforms") + if cns_transf == None: + cns_transf = p_bone.constraints.new("COPY_TRANSFORMS") + cns_transf.name = "Copy Transforms" + cns_transf.target = tgt + cns_transf.subtarget = subtgt + + +def set_constraint_inverse_matrix(cns): + # set the inverse matrix of Child Of constraint + tar_obj = cns.target + subtarget_pbone = tar_obj.pose.bones.get(cns.subtarget) + if subtarget_pbone: + #cns.inverse_matrix = tar_obj.matrix_world.inverted() @ subtarget_pbone.matrix_basis.inverted() + print("reset child of cns", cns.name, cns.subtarget) + cns.inverse_matrix = subtarget_pbone.bone.matrix_local.to_4x4().inverted() \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/context.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/context.py new file mode 100644 index 0000000..86617d2 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/context.py @@ -0,0 +1,10 @@ +import bpy + +def get_current_mode(): + return bpy.context.mode + + +def restore_current_mode(current_mode): + if current_mode == 'EDIT_ARMATURE': + current_mode = 'EDIT' + bpy.ops.object.mode_set(mode=current_mode) \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/cs.blend b/assets/blender/scripts/mixamo/3.6/mixamo/lib/cs.blend new file mode 100644 index 0000000..b2f5dcd --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/cs.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d25ab3ff7b8497e945bb7a153ae80c22a99aecc7b46ab454c386daaba5dd33a2 +size 1062988 diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/custom_props.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/custom_props.py new file mode 100644 index 0000000..30ef526 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/custom_props.py @@ -0,0 +1,60 @@ +import bpy +from .version import blender_version + + +def get_prop_setting(node, prop_name, setting): + if blender_version._float >= 300: + return node.id_properties_ui(prop_name).as_dict()[setting] + else: + return node['_RNA_UI'][prop_name][setting] + + +def set_prop_setting(node, prop_name, setting, value): + if blender_version._float >= 300: + ui_data = node.id_properties_ui(prop_name) + if setting == 'default': + ui_data.update(default=value) + elif setting == 'min': + ui_data.update(min=value) + elif setting == 'max': + ui_data.update(max=value) + elif setting == 'soft_min': + ui_data.update(soft_min=value) + elif setting == 'soft_max': + ui_data.update(soft_max=value) + elif setting == 'description': + ui_data.update(description=value) + + else: + if not "_RNA_UI" in node.keys(): + node["_RNA_UI"] = {} + node['_RNA_UI'][prop_name][setting] = value + + +def create_custom_prop(node=None, prop_name="", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="", soft_min=None, soft_max=None, default=None): + if soft_min == None: + soft_min = prop_min + if soft_max == None: + soft_max = prop_max + + if blender_version._float < 300: + if not "_RNA_UI" in node.keys(): + node["_RNA_UI"] = {} + + node[prop_name] = prop_val + + if default == None: + default = prop_val + + if blender_version._float < 300: + node["_RNA_UI"][prop_name] = {'use_soft_limits':True, 'min': prop_min, 'max': prop_max, 'description': prop_description, 'soft_min':soft_min, 'soft_max':soft_max, 'default':default} + else: + set_prop_setting(node, prop_name, 'min', prop_min) + set_prop_setting(node, prop_name, 'max', prop_max) + set_prop_setting(node, prop_name, 'description', prop_description) + set_prop_setting(node, prop_name, 'soft_min', soft_min) + set_prop_setting(node, prop_name, 'soft_max', soft_max) + set_prop_setting(node, prop_name, 'default', default) + + # set as overridable + node.property_overridable_library_set('["'+prop_name+'"]', True) \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/drivers.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/drivers.py new file mode 100644 index 0000000..9374dce --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/drivers.py @@ -0,0 +1,23 @@ +import bpy + +def add_driver_to_prop(obj, dr_dp, tar_dp, array_idx=-1, exp="var"): + if obj.animation_data == None: + obj.animation_data_create() + + drivers_list = obj.animation_data.drivers + dr = drivers_list.find(dr_dp, index=array_idx) + + if dr == None: + dr = obj.driver_add(dr_dp, array_idx) + + dr.driver.expression = exp + + var = dr.driver.variables.get('var') + + if var == None: + var = dr.driver.variables.new() + + var.type = 'SINGLE_PROP' + var.name = 'var' + var.targets[0].id = obj + var.targets[0].data_path = tar_dp \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/maths_geo.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/maths_geo.py new file mode 100644 index 0000000..ccd00e1 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/maths_geo.py @@ -0,0 +1,151 @@ +from math import * +from mathutils import * + + +def mat3_to_vec_roll(mat): + vec = mat.col[1] + vecmat = vec_roll_to_mat3(mat.col[1], 0) + vecmatinv = vecmat.inverted() + rollmat = vecmatinv @ mat + roll = atan2(rollmat[0][2], rollmat[2][2]) + return roll + + +def vec_roll_to_mat3(vec, roll): + target = Vector((0, 0.1, 0)) + nor = vec.normalized() + axis = target.cross(nor) + if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix + axis.normalize() + theta = target.angle(nor) + bMatrix = Matrix.Rotation(theta, 3, axis) + else: + updown = 1 if target.dot(nor) > 0 else -1 + bMatrix = Matrix.Scale(updown, 3) + bMatrix[2][2] = 1.0 + + rMatrix = Matrix.Rotation(roll, 3, nor) + mat = rMatrix @ bMatrix + return mat + + +def align_bone_x_axis(edit_bone, new_x_axis): + new_x_axis = new_x_axis.cross(edit_bone.y_axis) + new_x_axis.normalize() + dot = max(-1.0, min(1.0, edit_bone.z_axis.dot(new_x_axis))) + angle = acos(dot) + edit_bone.roll += angle + dot1 = edit_bone.z_axis.dot(new_x_axis) + edit_bone.roll -= angle * 2.0 + dot2 = edit_bone.z_axis.dot(new_x_axis) + if dot1 > dot2: + edit_bone.roll += angle * 2.0 + + +def align_bone_z_axis(edit_bone, new_z_axis): + new_z_axis = -(new_z_axis.cross(edit_bone.y_axis)) + new_z_axis.normalize() + dot = max(-1.0, min(1.0, edit_bone.x_axis.dot(new_z_axis))) + angle = acos(dot) + edit_bone.roll += angle + dot1 = edit_bone.x_axis.dot(new_z_axis) + edit_bone.roll -= angle * 2.0 + dot2 = edit_bone.x_axis.dot(new_z_axis) + if dot1 > dot2: + edit_bone.roll += angle * 2.0 + + +def signed_angle(u, v, normal): + nor = normal.normalized() + a = u.angle(v) + + c = u.cross(v) + + if c.magnitude == 0.0: + c = u.normalized().cross(v) + if c.magnitude == 0.0: + return 0.0 + + if c.angle(nor) < 1: + a = -a + return a + + +def project_point_onto_plane(q, p, n): + n = n.normalized() + return q - ((q - p).dot(n)) * n + + +def get_pole_angle(base_bone, ik_bone, pole_location): + pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head) + projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head) + return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head) + + +def get_pose_matrix_in_other_space(mat, pose_bone): + rest = pose_bone.bone.matrix_local.copy() + rest_inv = rest.inverted() + + if pose_bone.parent and pose_bone.bone.use_inherit_rotation: + par_mat = pose_bone.parent.matrix.copy() + par_inv = par_mat.inverted() + par_rest = pose_bone.parent.bone.matrix_local.copy() + + else: + par_mat = Matrix() + par_inv = Matrix() + par_rest = Matrix() + + smat = rest_inv @ (par_rest @ (par_inv @ mat)) + + return smat + + +def get_ik_pole_pos(b1, b2, method=1, axis=None): + + if method == 1: + # IK pole position based on real IK bones vector + plane_normal = (b1.head - b2.tail) + midpoint = (b1.head + b2.tail) * 0.5 + prepole_dir = b2.head - midpoint#prepole_fk.tail - prepole_fk.head + pole_pos = b2.head + prepole_dir.normalized()# * 4 + pole_pos = project_point_onto_plane(pole_pos, b2.head, plane_normal) + pole_pos = b2.head + ((pole_pos - b2.head).normalized() * (b2.head - b1.head).magnitude * 1.7) + + elif method == 2: + # IK pole position based on bone2 Z axis vector + pole_pos = b2.head + (axis.normalized() * (b2.tail-b2.head).magnitude) + + return pole_pos + + +def rotate_point(point, angle, origin, axis): + rot_mat = Matrix.Rotation(angle, 4, axis.normalized()) + # rotate in world origin space + offset_vec = -origin + offset_knee = point + offset_vec + # rotate + rotated_point = rot_mat @ offset_knee + # bring back to original space + rotated_point = rotated_point -offset_vec + return rotated_point + + +def dot_product(x, y): + return sum([x[i] * y[i] for i in range(len(x))]) + + +def norm(x): + return sqrt(dot_product(x, x)) + + +def normalize(x): + return [x[i] / norm(x) for i in range(len(x))] + + +def project_vector_onto_plane(x, n): + d = dot_product(x, n) / norm(n) + p = [d * normalize(n)[i] for i in range(len(n))] + vec_list = [x[i] - p[i] for i in range(len(x))] + return Vector((vec_list[0], vec_list[1], vec_list[2])) + \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/mixamo.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/mixamo.py new file mode 100644 index 0000000..4a531dc --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/mixamo.py @@ -0,0 +1,39 @@ +import bpy +from .bones_pose import * +from .bones_data import * +from ..definitions import naming + +def get_mixamo_prefix(): + p = "" + rig = bpy.context.active_object + + if 'mixamo_prefix' in rig.data.keys(): + p = rig.data["mixamo_prefix"] + + else: + for dbone in rig.data.bones: + if dbone.name.startswith("mixamorig") and ':' in dbone.name: + p = dbone.name.split(':')[0]+':' + break + + try: + rig.data["mixamo_prefix"] = p + except:# context error + pass + + return p + + +def get_mix_name(name, use_prefix): + if not use_prefix: + return name + else: + p = get_mixamo_prefix() + return p+name + + +def get_bone_side(bone_name): + if bone_name.endswith("_Left"): + return "Left" + elif bone_name.endswith("_Right"): + return "Right" \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/objects.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/objects.py new file mode 100644 index 0000000..dd9ff07 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/objects.py @@ -0,0 +1,81 @@ +import bpy, os + +def delete_object(obj): + bpy.data.objects.remove(obj, do_unlink=True) + + +def duplicate_object(): + try: + bpy.ops.object.duplicate(linked=False, mode='TRANSLATION') + except: + bpy.ops.object.duplicate('TRANSLATION', False) + + +def get_object(name): + return bpy.data.objects.get(name) + + +def set_active_object(object_name): + bpy.context.view_layer.objects.active = bpy.data.objects[object_name] + bpy.data.objects[object_name].select_set(state=True) + + +def hide_object(obj_to_set): + obj_to_set.hide_set(True) + obj_to_set.hide_viewport = True + + +def is_object_hidden(obj_to_get): + if obj_to_get.hide_get() == False and obj_to_get.hide_viewport == False: + return False + else: + return True + + +def append_cs(names=[]): + context = bpy.context + scene = context.scene + addon_directory = os.path.dirname(os.path.abspath(__file__)) + filepath = addon_directory + "\cs.blend" + + # load the objects data in file + with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to): + data_to.objects = [name for name in data_from.objects if name in names] + + # Add the objects in the scene + for obj in data_to.objects: + if obj: + # link in collec + scene.collection.objects.link(obj) + + cs_grp = bpy.data.objects.get("cs_grp") + if cs_grp == None: + cs_grp = bpy.data.objects.new(name="cs_grp", object_data=None) + bpy.context.collection.objects.link(cs_grp) + cs_grp.location = [0,0,0] + cs_grp.rotation_euler = [0,0,0] + cs_grp.scale = [1,1,1] + + # parent the custom shape + obj.parent = cs_grp + + # assign to new collection + assigned_collections = [] + for collec in cs_grp.users_collection: + try: + collec.objects.link(obj) + assigned_collections.append(collec) + except:# already in collection + pass + + if len(assigned_collections): + # remove previous collections + for i in obj.users_collection: + if not i in assigned_collections: + i.objects.unlink(obj) + # and the scene collection + try: + scene.collection.objects.unlink(obj) + except: + pass + \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/lib/version.py b/assets/blender/scripts/mixamo/3.6/mixamo/lib/version.py new file mode 100644 index 0000000..9f3e80f --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/lib/version.py @@ -0,0 +1,35 @@ +import bpy + +class ARP_blender_version: + _string = bpy.app.version_string + blender_v = bpy.app.version + _float = blender_v[0]*100+blender_v[1]+blender_v[2]*0.01 + _char = bpy.app.version_char + +blender_version = ARP_blender_version() + + +def convert_drivers_cs_to_xyz(armature): + # Blender 3.0 requires Vector3 custom_shape_scale values + # convert single uniform driver to vector3 array drivers + drivers_armature = [i for i in armature.animation_data.drivers] + + for dr in drivers_armature: + if 'custom_shape_scale' in dr.data_path: + if not 'custom_shape_scale_xyz' in dr.data_path: + for i in range(0, 3): + new_dr = armature.animation_data.drivers.from_existing(src_driver=dr) + new_dr.data_path = new_dr.data_path.replace('custom_shape_scale', 'custom_shape_scale_xyz') + new_dr.array_index = i + new_dr.driver.expression += ''# update hack + + armature.driver_remove(dr.data_path, dr.array_index) + + print("Converted custom shape scale drivers to xyz") + + +def get_custom_shape_scale_prop_name(): + if blender_version._float >= 300: + return 'custom_shape_scale_xyz' + else: + return 'custom_shape_scale' \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig.py b/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig.py new file mode 100644 index 0000000..9736b7c --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig.py @@ -0,0 +1,2764 @@ +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" + bones_map["Root"] = "Ctrl_Master" + + 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() diff --git a/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_functions.py b/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_functions.py new file mode 100644 index 0000000..e9d491b --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/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/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_prefs.py b/assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_prefs.py new file mode 100644 index 0000000..679091e --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/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/assets/blender/scripts/mixamo/3.6/mixamo/utils.py b/assets/blender/scripts/mixamo/3.6/mixamo/utils.py new file mode 100644 index 0000000..829a721 --- /dev/null +++ b/assets/blender/scripts/mixamo/3.6/mixamo/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 diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/define.py b/assets/blender/scripts/mixamo/4.3/mixamo/define.py new file mode 100644 index 0000000..70af10e --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/define.py @@ -0,0 +1 @@ +from .definitions.naming import * \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/definitions/naming.py b/assets/blender/scripts/mixamo/4.3/mixamo/definitions/naming.py new file mode 100644 index 0000000..8c0b204 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/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/assets/blender/scripts/mixamo/4.3/mixamo/lib/addon.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/addon.py new file mode 100644 index 0000000..1116e62 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/addon.py @@ -0,0 +1,15 @@ +import bpy, sys, linecache, ast + +def get_error_message(): + exc_type, exc_obj, tb = sys.exc_info() + f = tb.tb_frame + lineno = tb.tb_lineno + filename = f.f_code.co_filename + linecache.checkcache(filename) + line = linecache.getline(filename, lineno, f.f_globals) + error_message = 'Error in ({}\nLine {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj) + return error_message + + +def get_addon_preferences(): + return bpy.context.preferences.addons[__package__].preferences \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/animation.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/animation.py new file mode 100644 index 0000000..95d0864 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/animation.py @@ -0,0 +1,203 @@ +import bpy +from .maths_geo import * +from .bones_pose import * +from .version import * + +def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True, bake_object=False, ik_data=None): + scn = bpy.context.scene + obj_data = [] + bones_data = [] + armature = bpy.data.objects.get(bpy.context.active_object.name) + + def get_bones_matrix(): + matrix = {} + for pbone in armature.pose.bones: + if only_selected and not pbone.bone.select: + continue + + bmat = pbone.matrix + + # IK poles + if pbone.name.startswith("Ctrl_ArmPole") or pbone.name.startswith("Ctrl_LegPole"): + b1 = b2 = None + src_arm = ik_data["src_arm"] + type = "" + if "Leg" in pbone.name: + type = "Leg" + elif "Arm" in pbone.name: + type = "Arm" + + name_split = pbone.name.split('_') + side = name_split[len(name_split)-1] + b1_name = ik_data[type+side][0] + b2_name = ik_data[type+side][1] + b1 = src_arm.pose.bones.get(b1_name) + b2 = src_arm.pose.bones.get(b2_name) + + _axis = None + if type == "Leg": + _axis = (b1.z_axis*0.5) + (b2.z_axis*0.5)#b1.z_axis# + elif type == "Arm": + if side == "Left": + _axis = b2.x_axis + elif side == "Right": + _axis = -b2.x_axis + + pole_pos = get_ik_pole_pos(b1, b2, method=2, axis=_axis) + #pole_pos = b2.head + (b2.z_axis.normalized() * (b2.tail-b2.head).magnitude) + bmat = Matrix.Translation(pole_pos) + + # Child Of constraints are preserved after baking + # need to compensate the matrix with the Child Of transformation + child_of_cns = pbone.constraints.get("Child Of") + if child_of_cns: + if child_of_cns.influence == 1.0 and child_of_cns.mute == False: + bmat = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ bmat + + matrix[pbone.name] = armature.convert_space(pose_bone=pbone, matrix=bmat, from_space="POSE", to_space="LOCAL") + + return matrix + + def get_obj_matrix(): + parent = armature.parent + matrix = armature.matrix_world + if parent: + return parent.matrix_world.inverted_safe() @ matrix + else: + return matrix.copy() + + # store matrices + current_frame = scn.frame_current + + for f in range(int(frame_start), int(frame_end+1)): + scn.frame_set(f) + bpy.context.view_layer.update() + + if bake_bones: + bones_data.append((f, get_bones_matrix())) + if bake_object: + obj_data.append((f, get_obj_matrix())) + + # set new action + action = bpy.data.actions.new("Action") + anim_data = armature.animation_data_create() + anim_data.action = action + + def store_keyframe(bn, prop_type, fc_array_index, fra, val): + fc_data_path = 'pose.bones["' + bn + '"].' + prop_type + fc_key = (fc_data_path, fc_array_index) + if not keyframes.get(fc_key): + keyframes[fc_key] = [] + keyframes[fc_key].extend((fra, val)) + + + # set transforms and store keyframes + if bake_bones: + for pb in armature.pose.bones: + if only_selected and not pb.bone.select: + continue + + euler_prev = None + quat_prev = None + keyframes = {} + + for (f, matrix) in bones_data: + pb.matrix_basis = matrix[pb.name].copy() + + for arr_idx, value in enumerate(pb.location): + store_keyframe(pb.name, "location", arr_idx, f, value) + + rotation_mode = pb.rotation_mode + + if rotation_mode == 'QUATERNION': + if quat_prev is not None: + quat = pb.rotation_quaternion.copy() + quat.make_compatible(quat_prev) + pb.rotation_quaternion = quat + quat_prev = quat + del quat + else: + quat_prev = pb.rotation_quaternion.copy() + + for arr_idx, value in enumerate(pb.rotation_quaternion): + store_keyframe(pb.name, "rotation_quaternion", arr_idx, f, value) + + elif rotation_mode == 'AXIS_ANGLE': + for arr_idx, value in enumerate(pb.rotation_axis_angle): + store_keyframe(pb.name, "rotation_axis_angle", arr_idx, f, value) + + else: # euler, XYZ, ZXY etc + if euler_prev is not None: + euler = pb.rotation_euler.copy() + euler.make_compatible(euler_prev) + pb.rotation_euler = euler + euler_prev = euler + del euler + else: + euler_prev = pb.rotation_euler.copy() + + for arr_idx, value in enumerate(pb.rotation_euler): + store_keyframe(pb.name, "rotation_euler", arr_idx, f, value) + + for arr_idx, value in enumerate(pb.scale): + store_keyframe(pb.name, "scale", arr_idx, f, value) + + + # Add keyframes + for fc_key, key_values in keyframes.items(): + data_path, index = fc_key + fcurve = action.fcurves.find(data_path=data_path, index=index) + if fcurve == None: + fcurve = action.fcurves.new(data_path, index=index, action_group=pb.name) + + num_keys = len(key_values) // 2 + fcurve.keyframe_points.add(num_keys) + fcurve.keyframe_points.foreach_set('co', key_values) + + if blender_version._float >= 290:# internal error when doing so with Blender 2.83, only for Blender 2.90 and higher + linear_enum_value = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value + fcurve.keyframe_points.foreach_set('interpolation', (linear_enum_value,) * num_keys) + else: + for kf in fcurve.keyframe_points: + kf.interpolation = 'LINEAR' + + + if bake_object: + euler_prev = None + quat_prev = None + + for (f, matrix) in obj_data: + name = "Action Bake" + armature.matrix_basis = matrix + + armature.keyframe_insert("location", index=-1, frame=f, group=name) + + rotation_mode = armature.rotation_mode + if rotation_mode == 'QUATERNION': + if quat_prev is not None: + quat = armature.rotation_quaternion.copy() + quat.make_compatible(quat_prev) + armature.rotation_quaternion = quat + quat_prev = quat + del quat + else: + quat_prev = armature.rotation_quaternion.copy() + armature.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name) + elif rotation_mode == 'AXIS_ANGLE': + armature.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name) + else: # euler, XYZ, ZXY etc + if euler_prev is not None: + euler = armature.rotation_euler.copy() + euler.make_compatible(euler_prev) + armature.rotation_euler = euler + euler_prev = euler + del euler + else: + euler_prev = armature.rotation_euler.copy() + armature.keyframe_insert("rotation_euler", index=-1, frame=f, group=name) + + armature.keyframe_insert("scale", index=-1, frame=f, group=name) + + + # restore current frame + scn.frame_set(current_frame) diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/armature.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/armature.py new file mode 100644 index 0000000..5897e9b --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/armature.py @@ -0,0 +1,29 @@ +import bpy + +def restore_armature_layers(layers_select): + # restore the armature layers visibility + # ~ for i in range(0, 32): + # ~ bpy.context.active_object.data.layers[i] = layers_select[i] + for c in bpy.context.active_object.data.collections: + if c.name in layers_select: + c.is_visible = layers_select[c.name] + else: + c.is_visible = False + + +def enable_all_armature_layers(): + # enable all layers + # and return the list of each layer visibility + # ~ _layers = bpy.context.active_object.data.layers + # ~ layers_select = [] + # ~ for i in range(0, 32): + # ~ layers_select.append(_layers[i]) + # ~ for i in range(0, 32): + # ~ bpy.context.active_object.data.layers[i] = True + + layers_select = {} + for c in bpy.context.active_object.data.collections: + layers_select[c.name] = c.is_visible + c.is_visible = True + + return layers_select diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_data.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_data.py new file mode 100644 index 0000000..3afac6f --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_data.py @@ -0,0 +1,36 @@ +import bpy + +def get_data_bone(name): + return bpy.context.active_object.data.bones.get(name) + + +def set_bone_collection(armt, databone, coll_name, multi=False): + if databone is None: + return + + armt = armt.data + + coll = None + for c in armt.collections: + if c.name == coll_name: + coll = c + break + + if coll is None: + coll = armt.collections.new(coll_name) + + colls_to_remove_from = None + if not multi: + colls_to_remove_from = [c for c in databone.collections] + + r = coll.assign(databone) + + if colls_to_remove_from is not None: + for c in colls_to_remove_from: + c.unassign(databone) + + # ~ databone.layers[layer_idx] = True + + # ~ for i, lay in enumerate(databone.layers): + # ~ if i != layer_idx: + # ~ databone.layers[i] = False diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_edit.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_edit.py new file mode 100644 index 0000000..4a03525 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_edit.py @@ -0,0 +1,19 @@ +import bpy + +def get_edit_bone(name): + return bpy.context.object.data.edit_bones.get(name) + + +def copy_bone_transforms(bone1, bone2): + # copy editbone bone1 transforms to bone 2 + bone2.head = bone1.head.copy() + bone2.tail = bone1.tail.copy() + bone2.roll = bone1.roll + + +def create_edit_bone(bone_name, deform=False): + b = get_edit_bone(bone_name) + if b == None: + b = bpy.context.active_object.data.edit_bones.new(bone_name) + b.use_deform = deform + return b \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_pose.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_pose.py new file mode 100644 index 0000000..d38362b --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_pose.py @@ -0,0 +1,113 @@ +import bpy +from .objects import * +from .version import * + +def get_custom_shape_scale(pbone, uniform=True): + if blender_version._float >= 300: + if uniform: + # uniform scale + val = 0 + for i in range(0,3): + val += pbone.custom_shape_scale_xyz[i] + return val/3 + # array scale + else: + return pbone.custom_shape_scale_xyz + # pre-Blender 3.0 + else: + return pbone.custom_shape_scale + + +def get_selected_pbone_name(): + try: + return bpy.context.selected_pose_bones[0].name#.active_pose_bone.name + except: + return + + +def get_pose_bone(name): + return bpy.context.active_object.pose.bones.get(name) + + +def lock_pbone_transform(pbone, type, list): + for i in list: + if type == "location": + pbone.lock_location[i] = True + elif type == "rotation": + pbone.lock_rotation[i] = True + elif type == "scale": + pbone.lock_scale[i] = True + + +def set_bone_custom_shape(pbone, cs_name): + cs = get_object(cs_name) + if cs == None: + append_cs(cs_name) + cs = get_object(cs_name) + + pbone.custom_shape = cs + + +def set_bone_color_group(obj, pb, grp_name): + # mixamo required color + orange = (0.969, 0.565, 0.208) + orange_light = (0.957, 0.659, 0.416) + blue_dark = (0.447, 0.682, 1.0) + blue_light = (0.365, 0.851, 1.0) + + # base color + green = (0.0, 1.0, 0.0) + red = (1.0, 0.0, 0.0) + blue = (0.0, 0.9, 1.0) + + grp_color_master = orange_light + grp_color_neck = orange_light + grp_color_root_master = orange + grp_color_head = orange + grp_color_body_mid = green + grp_color_body_left = blue_dark + grp_color_body_right = blue_light + + # ~ grp = obj.data.collections.get(grp_name) + # ~ grp = obj.pose.bone_groups.get(grp_name) + # ~ if grp == None: + # ~ grp = obj.data.collections.new(grp_name) + # ~ grp = obj.pose.bone_groups.new(name=grp_name) + # ~ grp.color_set = 'CUSTOM' + + grp_color = None + if grp_name == "body_mid": + grp_color = grp_color_body_mid + elif grp_name == "body_left": + grp_color = grp_color_body_left + elif grp_name == "body_right": + grp_color = grp_color_body_right + elif grp_name == "master": + grp_color = grp_color_master + elif grp_name == "neck": + grp_color = grp_color_head + elif grp_name == "head": + grp_color = grp_color_neck + elif grp_name == "root_master": + grp_color = grp_color_root_master + + # set normal color + # ~ grp.colors.normal = grp_color + + # set select color/active color + # ~ for col_idx in range(0,3): + # ~ grp.colors.select[col_idx] = grp_color[col_idx] + 0.2 + # ~ grp.colors.active[col_idx] = grp_color[col_idx] + 0.4 + + # ~ r = grp.assign(pb) + # ~ pb.bone_group = grp + + pb.color.palette = 'CUSTOM' + pb.color.custom.normal = grp_color + for col_idx in range(0,3): + pb.color.custom.select[col_idx] = grp_color[col_idx] + 0.2 + pb.color.custom.active[col_idx] = grp_color[col_idx] + 0.4 + + +def update_transform(): + bpy.ops.transform.rotate(value=0, orient_axis='Z', orient_type='VIEW', orient_matrix=((0.0, 0.0, 0), (0, 0.0, 0.0), (0.0, 0.0, 0.0)), orient_matrix_type='VIEW', mirror=False) diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/constraints.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/constraints.py new file mode 100644 index 0000000..3111787 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/constraints.py @@ -0,0 +1,19 @@ +import bpy + +def add_copy_transf(p_bone, tgt, subtgt): + cns_transf = p_bone.constraints.get("Copy Transforms") + if cns_transf == None: + cns_transf = p_bone.constraints.new("COPY_TRANSFORMS") + cns_transf.name = "Copy Transforms" + cns_transf.target = tgt + cns_transf.subtarget = subtgt + + +def set_constraint_inverse_matrix(cns): + # set the inverse matrix of Child Of constraint + tar_obj = cns.target + subtarget_pbone = tar_obj.pose.bones.get(cns.subtarget) + if subtarget_pbone: + #cns.inverse_matrix = tar_obj.matrix_world.inverted() @ subtarget_pbone.matrix_basis.inverted() + print("reset child of cns", cns.name, cns.subtarget) + cns.inverse_matrix = subtarget_pbone.bone.matrix_local.to_4x4().inverted() \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/context.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/context.py new file mode 100644 index 0000000..a3718bf --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/context.py @@ -0,0 +1,10 @@ +import bpy + +def get_current_mode(): + return bpy.context.mode + + +def restore_current_mode(current_mode): + if current_mode == 'EDIT_ARMATURE': + current_mode = 'EDIT' + bpy.ops.object.mode_set(mode=current_mode) \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/cs.blend b/assets/blender/scripts/mixamo/4.3/mixamo/lib/cs.blend new file mode 100644 index 0000000..b2f5dcd --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/cs.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d25ab3ff7b8497e945bb7a153ae80c22a99aecc7b46ab454c386daaba5dd33a2 +size 1062988 diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/custom_props.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/custom_props.py new file mode 100644 index 0000000..794ea37 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/custom_props.py @@ -0,0 +1,60 @@ +import bpy +from .version import blender_version + + +def get_prop_setting(node, prop_name, setting): + if blender_version._float >= 300: + return node.id_properties_ui(prop_name).as_dict()[setting] + else: + return node['_RNA_UI'][prop_name][setting] + + +def set_prop_setting(node, prop_name, setting, value): + if blender_version._float >= 300: + ui_data = node.id_properties_ui(prop_name) + if setting == 'default': + ui_data.update(default=value) + elif setting == 'min': + ui_data.update(min=value) + elif setting == 'max': + ui_data.update(max=value) + elif setting == 'soft_min': + ui_data.update(soft_min=value) + elif setting == 'soft_max': + ui_data.update(soft_max=value) + elif setting == 'description': + ui_data.update(description=value) + + else: + if not "_RNA_UI" in node.keys(): + node["_RNA_UI"] = {} + node['_RNA_UI'][prop_name][setting] = value + + +def create_custom_prop(node=None, prop_name="", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="", soft_min=None, soft_max=None, default=None): + if soft_min == None: + soft_min = prop_min + if soft_max == None: + soft_max = prop_max + + if blender_version._float < 300: + if not "_RNA_UI" in node.keys(): + node["_RNA_UI"] = {} + + node[prop_name] = prop_val + + if default == None: + default = prop_val + + if blender_version._float < 300: + node["_RNA_UI"][prop_name] = {'use_soft_limits':True, 'min': prop_min, 'max': prop_max, 'description': prop_description, 'soft_min':soft_min, 'soft_max':soft_max, 'default':default} + else: + set_prop_setting(node, prop_name, 'min', prop_min) + set_prop_setting(node, prop_name, 'max', prop_max) + set_prop_setting(node, prop_name, 'description', prop_description) + set_prop_setting(node, prop_name, 'soft_min', soft_min) + set_prop_setting(node, prop_name, 'soft_max', soft_max) + set_prop_setting(node, prop_name, 'default', default) + + # set as overridable + node.property_overridable_library_set('["'+prop_name+'"]', True) \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/drivers.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/drivers.py new file mode 100644 index 0000000..31b8bda --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/drivers.py @@ -0,0 +1,23 @@ +import bpy + +def add_driver_to_prop(obj, dr_dp, tar_dp, array_idx=-1, exp="var"): + if obj.animation_data == None: + obj.animation_data_create() + + drivers_list = obj.animation_data.drivers + dr = drivers_list.find(dr_dp, index=array_idx) + + if dr == None: + dr = obj.driver_add(dr_dp, array_idx) + + dr.driver.expression = exp + + var = dr.driver.variables.get('var') + + if var == None: + var = dr.driver.variables.new() + + var.type = 'SINGLE_PROP' + var.name = 'var' + var.targets[0].id = obj + var.targets[0].data_path = tar_dp \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/maths_geo.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/maths_geo.py new file mode 100644 index 0000000..6219111 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/maths_geo.py @@ -0,0 +1,151 @@ +from math import * +from mathutils import * + + +def mat3_to_vec_roll(mat): + vec = mat.col[1] + vecmat = vec_roll_to_mat3(mat.col[1], 0) + vecmatinv = vecmat.inverted() + rollmat = vecmatinv @ mat + roll = atan2(rollmat[0][2], rollmat[2][2]) + return roll + + +def vec_roll_to_mat3(vec, roll): + target = Vector((0, 0.1, 0)) + nor = vec.normalized() + axis = target.cross(nor) + if axis.dot(axis) > 0.0000000001: # this seems to be the problem for some bones, no idea how to fix + axis.normalize() + theta = target.angle(nor) + bMatrix = Matrix.Rotation(theta, 3, axis) + else: + updown = 1 if target.dot(nor) > 0 else -1 + bMatrix = Matrix.Scale(updown, 3) + bMatrix[2][2] = 1.0 + + rMatrix = Matrix.Rotation(roll, 3, nor) + mat = rMatrix @ bMatrix + return mat + + +def align_bone_x_axis(edit_bone, new_x_axis): + new_x_axis = new_x_axis.cross(edit_bone.y_axis) + new_x_axis.normalize() + dot = max(-1.0, min(1.0, edit_bone.z_axis.dot(new_x_axis))) + angle = acos(dot) + edit_bone.roll += angle + dot1 = edit_bone.z_axis.dot(new_x_axis) + edit_bone.roll -= angle * 2.0 + dot2 = edit_bone.z_axis.dot(new_x_axis) + if dot1 > dot2: + edit_bone.roll += angle * 2.0 + + +def align_bone_z_axis(edit_bone, new_z_axis): + new_z_axis = -(new_z_axis.cross(edit_bone.y_axis)) + new_z_axis.normalize() + dot = max(-1.0, min(1.0, edit_bone.x_axis.dot(new_z_axis))) + angle = acos(dot) + edit_bone.roll += angle + dot1 = edit_bone.x_axis.dot(new_z_axis) + edit_bone.roll -= angle * 2.0 + dot2 = edit_bone.x_axis.dot(new_z_axis) + if dot1 > dot2: + edit_bone.roll += angle * 2.0 + + +def signed_angle(u, v, normal): + nor = normal.normalized() + a = u.angle(v) + + c = u.cross(v) + + if c.magnitude == 0.0: + c = u.normalized().cross(v) + if c.magnitude == 0.0: + return 0.0 + + if c.angle(nor) < 1: + a = -a + return a + + +def project_point_onto_plane(q, p, n): + n = n.normalized() + return q - ((q - p).dot(n)) * n + + +def get_pole_angle(base_bone, ik_bone, pole_location): + pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head) + projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head) + return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head) + + +def get_pose_matrix_in_other_space(mat, pose_bone): + rest = pose_bone.bone.matrix_local.copy() + rest_inv = rest.inverted() + + if pose_bone.parent and pose_bone.bone.use_inherit_rotation: + par_mat = pose_bone.parent.matrix.copy() + par_inv = par_mat.inverted() + par_rest = pose_bone.parent.bone.matrix_local.copy() + + else: + par_mat = Matrix() + par_inv = Matrix() + par_rest = Matrix() + + smat = rest_inv @ (par_rest @ (par_inv @ mat)) + + return smat + + +def get_ik_pole_pos(b1, b2, method=1, axis=None): + + if method == 1: + # IK pole position based on real IK bones vector + plane_normal = (b1.head - b2.tail) + midpoint = (b1.head + b2.tail) * 0.5 + prepole_dir = b2.head - midpoint#prepole_fk.tail - prepole_fk.head + pole_pos = b2.head + prepole_dir.normalized()# * 4 + pole_pos = project_point_onto_plane(pole_pos, b2.head, plane_normal) + pole_pos = b2.head + ((pole_pos - b2.head).normalized() * (b2.head - b1.head).magnitude * 1.7) + + elif method == 2: + # IK pole position based on bone2 Z axis vector + pole_pos = b2.head + (axis.normalized() * (b2.tail-b2.head).magnitude) + + return pole_pos + + +def rotate_point(point, angle, origin, axis): + rot_mat = Matrix.Rotation(angle, 4, axis.normalized()) + # rotate in world origin space + offset_vec = -origin + offset_knee = point + offset_vec + # rotate + rotated_point = rot_mat @ offset_knee + # bring back to original space + rotated_point = rotated_point -offset_vec + return rotated_point + + +def dot_product(x, y): + return sum([x[i] * y[i] for i in range(len(x))]) + + +def norm(x): + return sqrt(dot_product(x, x)) + + +def normalize(x): + return [x[i] / norm(x) for i in range(len(x))] + + +def project_vector_onto_plane(x, n): + d = dot_product(x, n) / norm(n) + p = [d * normalize(n)[i] for i in range(len(n))] + vec_list = [x[i] - p[i] for i in range(len(x))] + return Vector((vec_list[0], vec_list[1], vec_list[2])) + \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/mixamo.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/mixamo.py new file mode 100644 index 0000000..0644bc5 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/mixamo.py @@ -0,0 +1,39 @@ +import bpy +from .bones_pose import * +from .bones_data import * +from ..definitions import naming + +def get_mixamo_prefix(): + p = "" + rig = bpy.context.active_object + + if 'mixamo_prefix' in rig.data.keys(): + p = rig.data["mixamo_prefix"] + + else: + for dbone in rig.data.bones: + if dbone.name.startswith("mixamorig") and ':' in dbone.name: + p = dbone.name.split(':')[0]+':' + break + + try: + rig.data["mixamo_prefix"] = p + except:# context error + pass + + return p + + +def get_mix_name(name, use_prefix): + if not use_prefix: + return name + else: + p = get_mixamo_prefix() + return p+name + + +def get_bone_side(bone_name): + if bone_name.endswith("_Left"): + return "Left" + elif bone_name.endswith("_Right"): + return "Right" \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/objects.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/objects.py new file mode 100644 index 0000000..00e4248 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/objects.py @@ -0,0 +1,81 @@ +import bpy, os + +def delete_object(obj): + bpy.data.objects.remove(obj, do_unlink=True) + + +def duplicate_object(): + try: + bpy.ops.object.duplicate(linked=False, mode='TRANSLATION') + except: + bpy.ops.object.duplicate('TRANSLATION', False) + + +def get_object(name): + return bpy.data.objects.get(name) + + +def set_active_object(object_name): + bpy.context.view_layer.objects.active = bpy.data.objects[object_name] + bpy.data.objects[object_name].select_set(state=True) + + +def hide_object(obj_to_set): + obj_to_set.hide_set(True) + obj_to_set.hide_viewport = True + + +def is_object_hidden(obj_to_get): + if obj_to_get.hide_get() == False and obj_to_get.hide_viewport == False: + return False + else: + return True + + +def append_cs(names=[]): + context = bpy.context + scene = context.scene + addon_directory = os.path.dirname(os.path.abspath(__file__)) + filepath = addon_directory + "\cs.blend" + + # load the objects data in file + with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to): + data_to.objects = [name for name in data_from.objects if name in names] + + # Add the objects in the scene + for obj in data_to.objects: + if obj: + # link in collec + scene.collection.objects.link(obj) + + cs_grp = bpy.data.objects.get("cs_grp") + if cs_grp == None: + cs_grp = bpy.data.objects.new(name="cs_grp", object_data=None) + bpy.context.collection.objects.link(cs_grp) + cs_grp.location = [0,0,0] + cs_grp.rotation_euler = [0,0,0] + cs_grp.scale = [1,1,1] + + # parent the custom shape + obj.parent = cs_grp + + # assign to new collection + assigned_collections = [] + for collec in cs_grp.users_collection: + try: + collec.objects.link(obj) + assigned_collections.append(collec) + except:# already in collection + pass + + if len(assigned_collections): + # remove previous collections + for i in obj.users_collection: + if not i in assigned_collections: + i.objects.unlink(obj) + # and the scene collection + try: + scene.collection.objects.unlink(obj) + except: + pass + \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/lib/version.py b/assets/blender/scripts/mixamo/4.3/mixamo/lib/version.py new file mode 100644 index 0000000..0d331d4 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/lib/version.py @@ -0,0 +1,35 @@ +import bpy + +class ARP_blender_version: + _string = bpy.app.version_string + blender_v = bpy.app.version + _float = blender_v[0]*100+blender_v[1]+blender_v[2]*0.01 + #_char = bpy.app.version_string + +blender_version = ARP_blender_version() + + +def convert_drivers_cs_to_xyz(armature): + # Blender 3.0 requires Vector3 custom_shape_scale values + # convert single uniform driver to vector3 array drivers + drivers_armature = [i for i in armature.animation_data.drivers] + + for dr in drivers_armature: + if 'custom_shape_scale' in dr.data_path: + if not 'custom_shape_scale_xyz' in dr.data_path: + for i in range(0, 3): + new_dr = armature.animation_data.drivers.from_existing(src_driver=dr) + new_dr.data_path = new_dr.data_path.replace('custom_shape_scale', 'custom_shape_scale_xyz') + new_dr.array_index = i + new_dr.driver.expression += ''# update hack + + armature.driver_remove(dr.data_path, dr.array_index) + + print("Converted custom shape scale drivers to xyz") + + +def get_custom_shape_scale_prop_name(): + if blender_version._float >= 300: + return 'custom_shape_scale_xyz' + else: + return 'custom_shape_scale' diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/mixamo_rig.py b/assets/blender/scripts/mixamo/4.3/mixamo/mixamo_rig.py new file mode 100644 index 0000000..97a5619 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/mixamo_rig.py @@ -0,0 +1,2835 @@ +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] + + for c in bpy.context.active_object.data.collections: + if c.name in ('CTRL'): + c.is_visible = True + else: + c.is_visible = False + + +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) + + coll_mix_name = "DEF" + coll_ctrl_name = "CTRL" + coll_intern_name = "MCH" + + # ~ _ = rig.data.collections.new(coll_0_name) + # ~ for eb in rig.data.edit_bones: + # ~ set_bone_collection(rig, eb, coll_0_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_collection(rig, c_master, coll_ctrl_name) + # ~ 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_collection(rig, b, coll_mix_name) + # ~ 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_collection(rig, c_hips, coll_ctrl_name) + # ~ 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_collection(rig, c_hips_free, coll_ctrl_name) + # ~ 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_collection(rig, hips_free_helper, coll_intern_name) + # ~ 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) + set_bone_collection(rig, c_spine, coll_ctrl_name) + 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_collection(rig, c_spine1, coll_ctrl_name) + # ~ 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_collection(rig, c_spine2, coll_ctrl_name) + # ~ 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_collection(rig, b, coll_mix_name) + # ~ 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) + set_bone_collection(rig, c_neck, coll_ctrl_name) + 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) + set_bone_collection(rig, c_head, coll_ctrl_name) + 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_collection(rig, b, coll_mix_name) + # ~ 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_collection(rig, thigh_ik, coll_intern_name) + # ~ 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) + set_bone_collection(rig, c_thigh_fk, coll_ctrl_name) + 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) + set_bone_collection(rig, calf_ik, coll_intern_name) + + # 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_collection(rig, c_calf_fk, coll_ctrl_name) + # ~ 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) + set_bone_collection(rig, c_foot_fk, coll_ctrl_name) + 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_collection(rig, foot_fk, coll_intern_name) + # ~ 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_collection(rig, c_foot_ik, coll_ctrl_name) + # ~ 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_collection(rig, foot_ik, coll_intern_name) + # ~ 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_collection(rig, foot_snap, coll_intern_name) + # ~ 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) + set_bone_collection(rig, foot_ik_target, coll_intern_name) + + # 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) + set_bone_collection(rig, heel_out, coll_intern_name) + + # 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) + set_bone_collection(rig, heel_in, coll_intern_name) + + # 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) + set_bone_collection(rig, heel_mid, coll_intern_name) + + 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) + set_bone_collection(rig, toes_end, coll_intern_name) + + # 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) + set_bone_collection(rig, toes_end_01, coll_intern_name) + + # 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) + set_bone_collection(rig, c_foot_01, coll_ctrl_name) + 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) + set_bone_collection(rig, foot_01_pole, coll_intern_name) + + # 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) + set_bone_collection(rig, c_toe_ik, coll_ctrl_name) + 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) + set_bone_collection(rig, toe_track, coll_intern_name) + + # 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) + set_bone_collection(rig, toe_01_ik, coll_intern_name) + + # 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) + set_bone_collection(rig, toe_02, coll_intern_name) + + # 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) + set_bone_collection(rig, c_toe_fk, coll_ctrl_name) + 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) + set_bone_collection(rig, c_foot_roll_cursor, coll_ctrl_name) + 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) + set_bone_collection(rig, c_pole_ik, coll_ctrl_name) + 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) + set_bone_collection(rig, c_finger, coll_ctrl_name) + 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) + set_bone_collection(rig, b, coll_mix_name) + + # 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) + set_bone_collection(rig, c_shoulder, coll_ctrl_name) + 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) + set_bone_collection(rig, arm_ik, coll_intern_name) + + # 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) + set_bone_collection(rig, c_arm_fk, coll_ctrl_name) + 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) + set_bone_collection(rig, forearm_ik, coll_intern_name) + + # 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) + set_bone_collection(rig, c_forearm_fk, coll_ctrl_name) + 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) + set_bone_collection(rig, c_pole_ik, coll_ctrl_name) + 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) + set_bone_collection(rig, c_hand_ik, coll_ctrl_name) + 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) + set_bone_collection(rig, c_hand_fk, coll_ctrl_name) + 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 is None: + print("No animation data, exit bake") + return + + if rig.animation_data.nla_tracks is 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 is None: + fs = strip.frame_start + if fe is None: + fe = strip.frame_end + + if strip.frame_start < fs: + fs = strip.frame_start + if strip.frame_end > fe: + fe = strip.frame_end + + if fs is None or fe is None: + print("No NLA tracks found, exit") + return + + # get active action frame range + act = rig.animation_data.action + if act is not None: + 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...") + c0 = rig.data.collections.get("CTRL") + if c0 is not None: + for b in c0.bones: + pb = rig.pose.bones.get(b.name) + if pb is not None: + rig.data.bones.active = pb.bone + pb.bone.select = True + + # ~ for pbone in rig.pose.bones: + # ~ if pbone.bone.layers[0]: + # ~ rig.data.bones.active = pbone.bone + # ~ pbone.bone.select = True + + fs, fe = int(fs), int(fe) + + 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 bpy.context.view_layer.objects.active == None: + set_active_object(tar_arm.name) + 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" + bones_map["Root"] = "Ctrl_Master" +# bones_map["Root"] = "Root" + + 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 + if "Root" in src_name: + print("CONSTRAINT", src_bone, tar_bone) + + 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() diff --git a/assets/blender/scripts/mixamo/4.3/mixamo/utils.py b/assets/blender/scripts/mixamo/4.3/mixamo/utils.py new file mode 100644 index 0000000..2b64849 --- /dev/null +++ b/assets/blender/scripts/mixamo/4.3/mixamo/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 diff --git a/assets/blender/scripts/mixamo/import_mixamo_root_motion.py b/assets/blender/scripts/mixamo/import_mixamo_root_motion.py new file mode 100644 index 0000000..520b393 --- /dev/null +++ b/assets/blender/scripts/mixamo/import_mixamo_root_motion.py @@ -0,0 +1,459 @@ +import bpy +import os + +from bpy_extras.io_utils import ImportHelper +from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty +from bpy.types import Operator, Panel +from mathutils import Vector + + +class ImportMixamo(): + def __init__(self, hips_name:str): + """ init variables """ + self.obj = bpy.context.active_object + self.intensity = self.obj.scale + self.action = self.obj.animation_data.action + + for curve in self.obj.animation_data.action.fcurves: + if curve.data_path == f'pose.bones["{bpy.utils.escape_identifier(hips_name)}"].location': + if curve.array_index == 0: + self.curve_x = curve + elif curve.array_index == 1: + self.curve_y = curve + elif curve.array_index == 2: + self.curve_z = curve + + def rename_action(self, file_path:str): + """ rename action, new name use file name """ + self.action.name = os.path.basename(file_path).split('.')[0] + return {'FINISHED'} + + def remove_prefix_name(self, prefix_name:str): + """ remove prefix name from the bone name""" + bones = self.obj.pose.bones + for bone in bones: + if bone.name.startswith(prefix_name): + bone.name = bone.name.replace(prefix_name, "") + return {'FINISHED'} + + def delete_armature(self, armature_name:str): + """ delete , delete cihld objects """ + if self.obj.name.startswith(armature_name + '.00'): + bpy.ops.object.select_hierarchy(direction='CHILD', extend=True) + bpy.ops.object.delete() + return {'FINISHED'} + + def apply_all_transform(self): + """ apply all transform to the object """ + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + return {'FINISHED'} + + def scale_bone_action_intensity(self): + """ Scale the action intensity of the bones, fix animation """ + ## x keyframes + for kf in self.curve_x.keyframe_points: + kf.co.y *= self.intensity.x + ## y keyframes + for kf in self.curve_y.keyframe_points: + kf.co.y *= self.intensity.y + ## z keyframs + for kf in self.curve_z.keyframe_points: + kf.co.y *= self.intensity.z + return {'FINISHED'} + + def set_parent(self, child_bone_name:str, parent_bone_name:str): + """ set parent of the root bone """ + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bpy.context.scene.frame_set(1) + self.obj.data.edit_bones[child_bone_name].parent = self.obj.data.edit_bones[parent_bone_name] + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + return {'FINISHED'} + +class BakeMethod(): + """ calculate the height of the root motion """ + def __init__(self, hips_name:str, method:str, bake_x:bool, bake_y:bool, bake_z:bool): + self.obj = bpy.context.active_object + self.hips_name = hips_name + self.bake_x = bake_x + self.bake_y = bake_y + self.bake_z = bake_z + self.method = method + + for curve in self.obj.animation_data.action.fcurves: + if curve.data_path == f'pose.bones["{bpy.utils.escape_identifier(hips_name)}"].location': + if curve.array_index == 0: + self.curve_x = curve + + self.frames = [] + for kf in self.curve_x.keyframe_points: + detail_frame = [int(kf.co.x), float("0." + str(kf.co.x).split('.')[1])] + self.frames.append(detail_frame) + + def get_location_in_world(self, bone_name:str) -> Vector: + return self.obj.matrix_world @ self.obj.pose.bones[bone_name].head + + def copy_for_hips(self): + """ copy for hips bone location in world """ + vectors, root_vectors = [], [] + for f in self.frames: + bpy.context.scene.frame_set(f[0], subframe=f[1]) + vectors.append(self.get_location_in_world(bone_name=self.hips_name)) + root_vectors = [Vector((v.x * self.bake_x, + v.y * self.bake_y, + v.z * self.bake_z + )) for v in vectors] + hips_vectors = [vectors[i] - root_vectors[i] for i in range(len(vectors))] + first_point = vectors[0] + root_vectors = [Vector((v.x - first_point.x * self.bake_x, + v.y - first_point.y * self.bake_y, + v.z - first_point.z * self.bake_z, + )) for v in root_vectors] + hips_vectors = [Vector((v.x + first_point.x * self.bake_x, + v.y + first_point.y * self.bake_y, + v.z + first_point.z * self.bake_z, + )) for v in hips_vectors] + return root_vectors, hips_vectors + + def main_bone(self): + """ get main bone y_loc min_value (World Coordinate System)""" + vectors, height_ls = [], [] + for f in self.frames: + bpy.context.scene.frame_set(f[0], subframe=f[1]) + vectors.append(self.get_location_in_world(bone_name=self.hips_name)) + ## get main bone lowest height + headtop = self.obj.pose.bones["mixamorig:HeadTop_End"] + lefthand = self.obj.pose.bones["mixamorig:LeftHand"] + righthand = self.obj.pose.bones["mixamorig:RightHand"] + spline = self.obj.pose.bones["mixamorig:Spine"] + lefttoe = self.obj.pose.bones["mixamorig:LeftToe_End"] + righttoe = self.obj.pose.bones["mixamorig:RightToe_End"] + height = min(headtop.head[2], lefthand.head[2], righthand.head[2], + spline.head[2], lefttoe.head[2], righttoe.head[2]) + height_ls.append(height) + root_vectors = [Vector((vectors[i].x * self.bake_x, + vectors[i].y * self.bake_y, + height_ls[i] * self.bake_z + )) for i in range(len(vectors))] + hips_vectors = [vectors[i] - root_vectors[i] for i in range(len(vectors))] + ## root_on_floor x/z + first_point = vectors[0] + root_vectors = [Vector((v.x - first_point.x * self.bake_x, + v.y - first_point.y * self.bake_y, + v.z )) for v in root_vectors] + hips_vectors = [Vector((v.x + first_point.x * self.bake_x, + v.y + first_point.y * self.bake_y, + v.z )) for v in hips_vectors] + return root_vectors, hips_vectors + + def bound_box(self): + """ get bound box center """ + vectors, root_vectors, hips_vectors, lowest_points = [], [], [], [] + for f in self.frames: + bpy.context.scene.frame_set(f[0], subframe=f[1]) + bound_box_loc = [b[:] for b in self.obj.bound_box] + low_point = Vector((min(x) for x in (list(zip(*bound_box_loc))))) + vectors.append(self.get_location_in_world(bone_name=self.hips_name)) + lowest_points.append(low_point) + root_vectors = [Vector((vectors[i].x * self.bake_x, + vectors[i].y * self.bake_y, + lowest_points[i].z * self.bake_z + )) for i in range(len(vectors))] + hips_vectors = [vectors[i] - root_vectors[i] for i in range(len(vectors))] + ## root_on_floor x / z + first_point = root_vectors[0] + root_vectors = [Vector((v.x - first_point.x * self.bake_x, + v.y - first_point.y * self.bake_y, + v.z )) for v in root_vectors] + hips_vectors = [Vector((v.x + first_point.x * self.bake_x, + v.y + first_point.y * self.bake_y, + v.z )) for v in hips_vectors] + return root_vectors, hips_vectors + + def run(self): + match self.method: + case "COPY_HIPS": + return self.copy_for_hips() + case "MAIN_BONE": + return self.main_bone() + case "BOUND_BOX": + return self.bound_box() + + +class RootMotion(): + def __init__(self, hips_name:str): + self.obj = bpy.context.active_object + for curve in self.obj.animation_data.action.fcurves: + if curve.data_path == f'pose.bones["{bpy.utils.escape_identifier(hips_name)}"].location': + if curve.array_index == 0: + self.curve_x = curve + elif curve.array_index == 1: + self.curve_y = curve + elif curve.array_index == 2: + self.curve_z = curve + + ## get frames + self.frames = [] ## -> [ whole_frame=float_value ] + for kf in self.curve_x.keyframe_points: + self.frames.append(kf.co.x) + + def add_root(self, root_name:str): + """ add root bone""" + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + # create bone && set bone tail + root = self.obj.data.edit_bones.new(root_name) + root.head = (0.0, 0.0, 0.0) + root.tail = (0.0, 0.0, 0.3) + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + return {'FINISHED'} + + def vectors_world2local(self, bone_name, vectors) -> list: + """ mapping coordinate system; world to local """ + local_bone = self.obj.pose.bones[bone_name] + local_vectors = [self.obj.matrix_world.inverted() @ local_bone.bone.matrix_local.inverted() @ v + for v in vectors] + return local_vectors + + def bake_keyframes(self, bone_name, vectors): + """ bake root motion keyframes """ + bone=self.obj.pose.bones[bone_name] + local_vectors = self.vectors_world2local(bone_name, vectors) + for i, f in enumerate(self.frames): + bone.location = local_vectors[i] + bone.keyframe_insert(data_path='location', frame=f, group=bone_name) + return {'FINISHED'} + + def edit_keyframes(self, bone_name, vectors): + """ edit hips bone keyframe points""" + vectors = self.vectors_world2local(bone_name=bone_name, vectors=vectors) + for i, kf in enumerate(self.curve_x.keyframe_points): + kf.co.y = vectors[i].x + for i, kf in enumerate(self.curve_y.keyframe_points): + kf.co.y = vectors[i].y + for i, kf in enumerate(self.curve_z.keyframe_points): + kf.co.y = vectors[i].z + return {'FINISHED'} + + +def import_mixamo_root_motion(context, file_path: str, is_apply_transform: bool, + is_rename_action: bool, is_remove_prefix: bool, + is_delete_armature: bool, is_add_root: bool, + method: str, bake_x: bool, bake_y: bool, bake_z: bool): + """ main - batch """ + ## Parameters + root_name = "Root" + hips_name = "mixamorig:Hips" + prefix_name = "mixamorig:" + armature_name = "Armature" + try: + bpy.ops.import_scene.fbx(filepath=file_path) ## import fbx file + + ## class instance + importer = ImportMixamo(hips_name=hips_name) + bake_method = BakeMethod(hips_name=hips_name, method=method, + bake_x=bake_x, bake_y=bake_y, bake_z=bake_z) + root_motion = RootMotion(hips_name=hips_name) + + ## apply transform and fix animation + if is_apply_transform: + importer.scale_bone_action_intensity() + importer.apply_all_transform() + ## get vectors for bone + if is_add_root and (bake_x, bake_y, bake_z): + root_vectors, hips_vectors = bake_method.run() + ## add root bone + if is_add_root: + root_motion.add_root(root_name=root_name) + # ## bake root motion keyframes + if is_add_root and (bake_x, bake_y, bake_z): + root_motion.bake_keyframes(bone_name=root_name, vectors=root_vectors) + root_motion.edit_keyframes(bone_name=hips_name, vectors=hips_vectors) + # ## set parent + if is_add_root: + importer.set_parent(child_bone_name=hips_name, parent_bone_name=root_name) + # ## rename action + if is_rename_action: + importer.rename_action(file_path=file_path) + # ## remove prefix + if is_remove_prefix: + importer.remove_prefix_name(prefix_name=prefix_name) + # ## delete armature + if is_delete_armature: + importer.delete_armature(armature_name=armature_name) + context.scene.frame_set(1) ## set frame to 1 + except Exception as e: + print(e) + return {'FINISHED'} + + +## ImportHelper +class BatchImport(Operator, ImportHelper): + """ Batch import """ + bl_idname = "import_mixamo.root_motion" + bl_label = "Import Mixamo *.Fbx" + bl_options = {'PRESET'} + + # ImportHelper mix-in class uses this. + filename_ext = ".fbx" + + files: CollectionProperty( + type=bpy.types.OperatorFileListElement, + options={'HIDDEN', 'SKIP_SAVE'}, + ) # type: ignore + + directory: StringProperty( + subtype='DIR_PATH' + ) # type: ignore + + filter_glob: StringProperty( + default="*.fbx", + options={'HIDDEN'}, + maxlen=255, + ) # type: ignore + + # List of operator properties + is_apply_transforms: BoolProperty( + name="Apply Transform", + description="Recommended to keep it checked and fix animation, apply all transforms, if unchecked, root motion will have unpredictable results", + default=True, + ) # type: ignore + + is_add_root: BoolProperty( + name="Add Root Bone", + description="Add the root bone and set as parent, Root Motion use this bone to bake keyframes, if unchecked, Root Motion will not work.", + default=True, + ) # type: ignore + + is_rename_action: BoolProperty( + name="Rename Action", + description="Rename the name of the action animation using the filename", + default=True, + ) # type: ignore + + is_remove_prefix: BoolProperty( + name="Remove prefix", + description="Remove prefix names from all bones ", + default=True, + ) # type: ignore + + is_delete_armature: BoolProperty( + name="Remove Armature", + description="Remove object ", + default=True, + ) # type: ignore + + method: EnumProperty( + name="Method", + description="Root-Motion -> Bake keyframes: Height bake method", + items=( + ('COPY_HIPS', "Copy", "Copy from hips bone transform in wrold space"), + ('MAIN_BONE', "Bone", "Copy hips bone X/Y, get lowest bone height as Z"), + ('BOUND_BOX', "Bound box", "Copy hips bone X/Y, get Bound Box lowest as Z"), + ), + default='COPY_HIPS', + ) # type: ignore + + bake_x: BoolProperty( + name="X", + description="Baking to the Root Bone", + default=True, + ) # type: ignore + + bake_y: BoolProperty( + name="Y", + description="Baking to the Root Bone", + default=True, + ) # type: ignore + + bake_z: BoolProperty( + name="Z", + description="Baking to the Root Bone - Height", + default=False, + ) # type: ignore + + def execute(self, context): + for file in self.files: + file_path = os.path.join(self.directory, file.name) + import_mixamo_root_motion(context, file_path=file_path, + is_apply_transform=self.is_apply_transforms, + is_rename_action=self.is_rename_action, + is_remove_prefix=self.is_remove_prefix, + is_delete_armature=self.is_delete_armature, + is_add_root=self.is_add_root, + method=self.method, bake_x=self.bake_x, + bake_y=self.bake_y, bake_z=self.bake_z) + return {'FINISHED'} + + def draw(self, context): + pass + +## Panel: import setings +class IMPORT_PT_base_settings(Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Import Settings" + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + return operator.bl_idname == "IMPORT_MIXAMO_OT_root_motion" + + def draw(self, context): + layout = self.layout + sfile = context.space_data + operator = sfile.active_operator + column = layout.column(align=True) + column.prop(operator, 'is_apply_transforms', icon='CON_TRANSFORM') + column.prop(operator, 'is_add_root', icon='GROUP_BONE') + column.prop(operator, 'is_remove_prefix', icon='BONE_DATA') + column.prop(operator, 'is_rename_action', icon='ACTION') + column.prop(operator, 'is_delete_armature', icon='TRASH') + +## Panel: root motion settings +class IMPORT_PT_bake_settings(Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Root Motion" + bl_parent_id = "IMPORT_PT_base_settings" + bl_options = {'HEADER_LAYOUT_EXPAND'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + return operator.bl_idname == "IMPORT_MIXAMO_OT_root_motion" + + def draw(self, context): + layout = self.layout + sfile = context.space_data + operator = sfile.active_operator + + layout.prop(operator, 'method') + + row = layout.row(align=True) + row.prop(operator, 'bake_x', icon='KEYFRAME_HLT') + row.prop(operator, 'bake_y', icon='KEYFRAME_HLT') + row.prop(operator, 'bake_z', icon='KEYFRAME_HLT') + + +def menu_func_import(self, context): + self.layout.operator(BatchImport.bl_idname, text="Mixamo fbx(folder/*.fbx)") + +# Register and add to the "file selector" menu (required to use F3 search "Text Import Operator" for quick access). +def register(): + bpy.utils.register_class(BatchImport) + bpy.utils.register_class(IMPORT_PT_base_settings) + bpy.utils.register_class(IMPORT_PT_bake_settings) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + +def unregister(): + bpy.utils.unregister_class(BatchImport) + bpy.utils.unregister_class(IMPORT_PT_bake_settings) + bpy.utils.unregister_class(IMPORT_PT_base_settings) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + +if __name__ == "__main__": + register() + # test call + bpy.ops.import_mixamo.root_motion('INVOKE_DEFAULT') +# unregister() diff --git a/assets/blender/scripts/mixamo/mixamoroot.py b/assets/blender/scripts/mixamo/mixamoroot.py new file mode 100644 index 0000000..285001d --- /dev/null +++ b/assets/blender/scripts/mixamo/mixamoroot.py @@ -0,0 +1,387 @@ +# -*- coding: utf-8 -*- + +''' + Copyright (C) 2022 Richard Perry + Copyright (C) Average Godot Enjoyer (Johngoss725) + + 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 3 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, see . + + Note that Johngoss725's original contributions were published under a + Creative Commons 1.0 Universal License (CC0-1.0) located at + . +''' + +# Original Script Created By: Average Godot Enjoyer (Johngoss725) +# Bone Renaming Modifications, File Handling, And Addon By: Richard Perry +import bpy +import os +import logging +from pathlib import Path + + +log = logging.getLogger(__name__) + +# in future remove_prefix should be renamed to rename prefix and a target prefix should be specifiable via ui +def fixBones(remove_prefix=False, name_prefix="mixamorig:"): + bpy.ops.object.mode_set(mode = 'OBJECT') + + if not bpy.ops.object: + log.warning('[Mixamo Root] Could not find amature object, please select the armature') + + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.context.object.show_in_front = True + + if remove_prefix: + for rig in bpy.context.selected_objects: + if rig.type == 'ARMATURE': + for mesh in rig.children: + for vg in mesh.vertex_groups: + new_name = vg.name + new_name = new_name.replace(name_prefix,"") + rig.pose.bones[vg.name].name = new_name + vg.name = new_name + for bone in rig.pose.bones: + bone.name = bone.name.replace(name_prefix,"") + for action in bpy.data.actions: + fc = action.fcurves + for f in fc: + f.data_path = f.data_path.replace(name_prefix,"") + +def scaleAll(): + bpy.ops.object.mode_set(mode='OBJECT') + + prev_context=bpy.context.area.type + + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.pose.select_all(action='SELECT') + bpy.context.area.type = 'GRAPH_EDITOR' + bpy.context.space_data.dopesheet.filter_text = "Location" + bpy.context.space_data.pivot_point = 'CURSOR' + bpy.context.space_data.dopesheet.use_filter_invert = False + + bpy.ops.anim.channels_select_all(action='SELECT') + + bpy.ops.transform.resize(value=(1, 0.01, 1), orient_type='GLOBAL', + orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), + orient_matrix_type='GLOBAL', + constraint_axis=(False, True, False), + mirror=True, use_proportional_edit=False, + proportional_edit_falloff='SMOOTH', + proportional_size=1, + use_proportional_connected=False, + use_proportional_projected=False) + + +def copyHips(root_bone_name="Root", hip_bone_name="mixamorig:Hips", name_prefix="mixamorig:"): + bpy.context.area.ui_type = 'FCURVES' + #SELECT OUR ROOT MOTION BONE + bpy.ops.pose.select_all(action='DESELECT') + bpy.context.object.pose.bones[name_prefix + root_bone_name].bone.select = True + # SET FRAME TO ZERO + bpy.ops.graph.cursor_set(frame=0.0, value=0.0) + #ADD NEW KEYFRAME + bpy.ops.anim.keyframe_insert_menu(type='Location') + #SELECT ONLY HIPS AND LOCTAIUON GRAPH DATA + bpy.ops.pose.select_all(action='DESELECT') + bpy.context.object.pose.bones[hip_bone_name].bone.select = True + bpy.context.area.ui_type = 'DOPESHEET' + bpy.context.space_data.dopesheet.filter_text = "Location" + bpy.context.area.ui_type = 'FCURVES' + #COPY THE LOCATION VALUES OF THE HIPS AND DELETE THEM + bpy.ops.graph.copy() + bpy.ops.graph.select_all(action='DESELECT') + + myFcurves = bpy.context.object.animation_data.action.fcurves + + for i in myFcurves: + hip_bone_fcvurve = 'pose.bones["'+hip_bone_name+'"].location' + if str(i.data_path)==hip_bone_fcvurve: + myFcurves.remove(i) + + bpy.ops.pose.select_all(action='DESELECT') + bpy.context.object.pose.bones[name_prefix + root_bone_name].bone.select = True + bpy.ops.graph.paste() + + bpy.context.area.ui_type = 'VIEW_3D' + bpy.ops.object.mode_set(mode='OBJECT') + +def fix_bones_nla(remove_prefix=False, name_prefix="mixamorig:"): + bpy.ops.object.mode_set(mode = 'OBJECT') + + if not bpy.ops.object: + log.warning('[Mixamo Root] Could not find amature object, please select the armature') + + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.context.object.show_in_front = True + +def scale_all_nla(armature): + bpy.ops.object.mode_set(mode='OBJECT') + + # prev_context=bpy.context.area.type + + for track in [x for x in armature.animation_data.nla_tracks]: + bpy.context.active_nla_track = track + for strip in track.strips: + bpy.context.active_nla_strip = strip + print(bpy.context.active_nla_strip) + + + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.pose.select_all(action='SELECT') + bpy.context.area.type = 'GRAPH_EDITOR' + bpy.context.space_data.dopesheet.filter_text = "Location" + bpy.context.space_data.pivot_point = 'CURSOR' + bpy.context.space_data.dopesheet.use_filter_invert = False + + bpy.ops.anim.channels_select_all(action='SELECT') + + bpy.ops.transform.resize(value=(1, 0.01, 1), orient_type='GLOBAL', + orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), + orient_matrix_type='GLOBAL', + constraint_axis=(False, True, False), + mirror=True, use_proportional_edit=False, + proportional_edit_falloff='SMOOTH', + proportional_size=1, + use_proportional_connected=False, + use_proportional_projected=False) + +def copy_hips_nla(root_bone_name="Root", hip_bone_name="mixamorig:Hips", name_prefix="mixamorig:"): + hip_bone_name="Ctrl_Hips" + bpy.ops.object.mode_set(mode='POSE') + previous_context = bpy.context.area.ui_type + bpy.ops.pose.select_all(action='DESELECT') + while False: + #SELECT OUR ROOT MOTION BONE + # bpy.context.object.pose.bones[name_prefix + root_bone_name].bone.select = True + + # bpy.ops.nla.tweakmode_enter() + # bpy.context.area.ui_type = 'FCURVES' + + # # SET FRAME TO ZERO + # bpy.ops.graph.cursor_set(frame=0.0, value=0.0) + # #ADD NEW KEYFRAME + # bpy.ops.anim.keyframe_insert_menu(type='Location') + # #SELECT ONLY HIPS AND LOCTAIUON GRAPH DATA + # bpy.ops.pose.select_all(action='DESELECT') + # bpy.context.object.pose.bones[hip_bone_name].bone.select = True + # bpy.context.area.ui_type = 'DOPESHEET' + # bpy.context.space_data.dopesheet.filter_text = "Location" + # bpy.context.area.ui_type = 'FCURVES' + # #COPY THE LOCATION VALUES OF THE HIPS AND DELETE THEM + # bpy.ops.graph.copy() + # bpy.ops.graph.select_all(action='DESELECT') + + # myFcurves = bpy.context.object.animation_data.action.fcurves + + # for i in myFcurves: + # hip_bone_fcvurve = 'pose.bones["'+hip_bone_name+'"].location' + # if str(i.data_path)==hip_bone_fcvurve: + # myFcurves.remove(i) + + # bpy.ops.pose.select_all(action='DESELECT') + # bpy.context.object.pose.bones[name_prefix + root_bone_name].bone.select = True + # bpy.ops.graph.paste() + + # for animation data in object + # for + pass + + for track in bpy.context.object.animation_data.nla_tracks: + bpy.context.object.animation_data.nla_tracks.active = track + for strip in track.strips: + bpy.context.object.pose.bones[name_prefix + root_bone_name].bone.select = True + bpy.context.area.ui_type = 'NLA_EDITOR' + bpy.ops.nla.tweakmode_enter() + bpy.context.area.ui_type = 'FCURVES' + hip_curves = [fc for fc in strip.fcurves if hip_bone_name in fc.data_path and fc.data_path.startswith('location')] + + # Copy Hips to root + ## Insert keyframe for root bone + start_frame = strip.action.frame_range[0] + # frame sets the x axis cursor (determines the frame, and value the y axis cursor, which is the amplitude of the curve) + bpy.ops.graph.cursor_set(frame=start_frame, value=0.0) + bpy.ops.anim.keyframe_insert_menu(type='Location') + bpy.ops.pose.select_all(action='DESELECT') + + ## Copy Location fcruves + bpy.context.object.pose.bones[hip_bone_name].bone.select = True + bpy.context.area.ui_type = 'DOPESHEET' + bpy.context.space_data.dopesheet.filter_text = "Location" + bpy.context.area.ui_type = 'FCURVES' + bpy.ops.graph.copy() + bpy.ops.graph.select_all(action='DESELECT') + + ## We want to delete the hips locations + allFcurves = strip.fcurves + for fc in hip_curves: + allFcurves.remove(fc) + + ## Paste location fcurves to the root bone + bpy.ops.pose.select_all(action='DESELECT') + bpy.context.object.pose.bones[name_prefix + root_bone_name].bone.select = True + bpy.ops.graph.paste() + + + loc_fcurves = [fc for fc in strip.fcurves if root_bone_name in fc.data_path and fc.data_path.startswith('location')] + + # Update Root Bone + # set z of root to min 0 (not negative). + for fc in loc_fcurves: + # Z axis location curve + if fc.array_index == 2: + for kp in fc.keyframe_points: + kp.co.z = min(0, abs(kp.co.z)) + + # Delete rotation curves for x(0) and y(1) axis. Should we delet Z rotation too? + # rot_fcurves = [fc for fc in strip.fcurves if root_bone_name in fc.data_path and fc.data_path.startswith('rotation') and (fc.array_index == 0 or fc.array_index == 1)] + # for fc in rot_fcurves: + # strip.fcurves.remove(fc) + # while(rot_fcurves): + # fc = rot_fcurves.pop() + # strip.fcurves.remove(fc) + bpy.context.area.ui_type = 'NLA_EDITOR' + bpy.ops.nla.tweakmode_exit() + bpy.context.area.ui_type = previous_context + bpy.ops.object.mode_set(mode='OBJECT') + +def deleteArmature(imported_objects=set()): + armature = None + if bpy.context.selected_objects: + armature = bpy.context.selected_objects[0] + if imported_objects == set(): + log.warning("[Mixamo Root] No armature imported, nothing to delete") + else: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for obj in imported_objects: + bpy.data.objects[obj.name].select_set(True) + + bpy.ops.object.delete(use_global=False, confirm=False) + if bpy.context.selected_objects: + bpy.context.view_layer.objects.active = armature + +def import_armature(filepath, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False): + old_objs = set(bpy.context.scene.objects) + if insert_root: + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.ops.import_scene.fbx(filepath = filepath)#, automatic_bone_orientation=True) + else: + bpy.ops.import_scene.fbx(filepath = filepath)#, automatic_bone_orientation=True) + + imported_objects = set(bpy.context.scene.objects) - old_objs + imported_actions = [x.animation_data.action for x in imported_objects if x.animation_data] + print("[Mixamo Root] Now importing: " + str(filepath)) + imported_actions[0].name = Path(filepath).resolve().stem # Only reads the first animation associated with an imported armature + + if insert_root: + add_root_bone(root_bone_name, hip_bone_name, remove_prefix, name_prefix) + + +def add_root_bone(root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:"): + armature = bpy.context.selected_objects[0] + bpy.ops.object.mode_set(mode='EDIT') + + root_bone = armature.data.edit_bones.new(name_prefix + root_bone_name) + root_bone.tail.y = 30 + + armature.data.edit_bones[hip_bone_name].parent = armature.data.edit_bones[name_prefix + root_bone_name] + bpy.ops.object.mode_set(mode='OBJECT') + + fixBones(remove_prefix=remove_prefix, name_prefix=name_prefix) + scaleAll() + copyHips(root_bone_name=root_bone_name, hip_bone_name=hip_bone_name, name_prefix=name_prefix) + +def add_root_bone_nla(root_bone_name="Root", hip_bone_name="mixamorig:Hips", name_prefix="mixamorig:"):#remove_prefix=False, name_prefix="mixamorig:"): + armature = bpy.context.selected_objects[0] + bpy.ops.object.mode_set(mode='EDIT') + + # Add root bone to edit bones + root_bone = armature.data.edit_bones.new(name_prefix + root_bone_name) + root_bone.tail.z = .25 + + armature.data.edit_bones[hip_bone_name].parent = armature.data.edit_bones[name_prefix + root_bone_name] + bpy.ops.object.mode_set(mode='OBJECT') + + # fix_bones_nla(remove_prefix=remove_prefix, name_prefix=name_prefix) + # scale_all_nla() + copy_hips_nla(root_bone_name=root_bone_name, hip_bone_name=hip_bone_name, name_prefix=name_prefix) + +def push(obj, action, track_name=None, start_frame=0): + # Simulate push : + # * add a track + # * add an action on track + # * lock & mute the track + # * remove active action from object + tracks = obj.animation_data.nla_tracks + new_track = tracks.new(prev=None) + if track_name: + new_track.name = track_name + strip = new_track.strips.new(action.name, start_frame, action) + obj.animation_data.action = None + +def get_all_anims(source_dir, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False): + files = os.listdir(source_dir) + num_files = len(files) + current_context = bpy.context.area.ui_type + old_objs = set(bpy.context.scene.objects) + + for file in files: + print("file: " + str(file)) + try: + filepath = source_dir+"/"+file + import_armature(filepath, root_bone_name, hip_bone_name, remove_prefix, name_prefix, insert_root, delete_armatures) + imported_objects = set(bpy.context.scene.objects) - old_objs + if delete_armatures and num_files > 1: + deleteArmature(imported_objects) + num_files -= 1 + + + except Exception as e: + log.error("[Mixamo Root] ERROR get_all_anims raised %s when processing %s" % (str(e), file)) + return -1 + bpy.context.area.ui_type = current_context + bpy.context.scene.frame_start = 0 + bpy.ops.object.mode_set(mode='OBJECT') + +def apply_all_anims(delete_applied_armatures=False, control_rig=None, push_nla=False): + if control_rig and control_rig.type == 'ARMATURE': + bpy.ops.object.mode_set(mode='OBJECT') + + imported_objects = set(bpy.context.scene.objects) + imported_armatures = [x for x in imported_objects if x.type == 'ARMATURE' and x.name != control_rig.name] + + for obj in imported_armatures: + action_name = obj.animation_data.action.name + bpy.context.scene.mix_source_armature = obj + bpy.context.view_layer.objects.active = control_rig + + bpy.ops.mr.import_anim_to_rig() + + bpy.context.view_layer.objects.active = control_rig + selected_action = control_rig.animation_data.action + selected_action.name = 'ctrl_' + action_name + # created_actions.append(selected_action) + + if push_nla: + push(control_rig, selected_action, None, int(selected_action.frame_start)) + + if delete_applied_armatures: + bpy.context.view_layer.objects.active = control_rig + deleteArmature(set([obj])) + + +if __name__ == "__main__": + dir_path = "" # If using script in place please set this before running. + get_all_anims(dir_path) + print("[Mixamo Root] Run as plugin, or copy script in text editor while setting parameter defaults.") diff --git a/assets/blender/scripts/separate_materials.py b/assets/blender/scripts/separate_materials.py new file mode 100644 index 0000000..04b073b --- /dev/null +++ b/assets/blender/scripts/separate_materials.py @@ -0,0 +1,59 @@ +import bpy, sys, os +import bmesh +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") +from settings import basepath + +def split_mesh_by_material(obj): + """Splits a mesh object into separate objects based on material assignments.""" + + if obj.type != 'MESH': + return + + mesh = obj.data + bm = bmesh.new() + bm.from_mesh(mesh) + + material_indices = {} + for face in bm.faces: + material_index = face.material_index + if material_index not in material_indices: + material_indices[material_index] = [] + material_indices[material_index].append(face) + + for material_index, faces in material_indices.items(): + new_bm = bmesh.new() + for face in faces: + new_bm.faces.new([new_bm.verts.new(v.co) for v in face.verts]) + + new_mesh = bpy.data.meshes.new(f"{obj.name}_mat_{material_index}") + new_bm.to_mesh(new_mesh) + new_bm.free() + + new_obj = bpy.data.objects.new(f"{obj.name}_mat_{material_index}", new_mesh) + new_obj.data.materials.append(mesh.materials[material_index]) + bpy.context.collection.objects.link(new_obj) + +def append_object(filepath, object_names): + """Appends an object from a specified Blender file. + + Args: + filepath (str): The full path to the source .blend file. + object_name (str): The name of the object to append. + """ + with bpy.data.libraries.load(filepath) as (data_from, data_to): + data_to.objects = object_names + + if data_to.objects: + for obj in data_to.objects: + bpy.context.collection.objects.link(obj) + else: + print(f"Object '{object_name}' not found in '{filepath}'.") + +bpy.ops.wm.read_homefile(use_empty=True) +for o in bpy.data.objects: + bpy.data.objects.remove(o) + +append_object(basepath + "/assets/blender/" + "vrm-vroid-normal-male.blend", ["Body", "male"]) + +bpy.ops.wm.save_as_mainfile(filepath=basepath + "/" + "tmp.blend") + diff --git a/assets/blender/scripts/settings.py b/assets/blender/scripts/settings.py new file mode 100644 index 0000000..a4e876a --- /dev/null +++ b/assets/blender/scripts/settings.py @@ -0,0 +1,83 @@ +import os, sys +basepath = os.getcwd() +class VRMDataFemale: + path = "jane2-dress.vrm" + outfile = "vrm-vroid-normal-female.blend" + editfile = "modelling-vroid-normal-female.blend" + armature_name = "female" + mixamo_animation_path = basepath + "/assets/blender/mixamo/female/" + mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn"] + fbx_scale = 0.89 +class VRMDataMale: + path = "buch1.vrm" + outfile = "vrm-vroid-normal-male.blend" + editfile = "modelling-vroid-normal-male.blend" + armature_name = "male" + mixamo_animation_path = basepath + "/assets/blender/mixamo/male/" + mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn"] + fbx_scale = 1.0 +class VRMDataMaleBabyShape: + path = "buch1-chibi.vrm" + outfile = "shapes/male/vrm-vroid-normal-male-chibi.blend" + editfile = "modelling-shape-male-chibi.blend" + armature_name = "male" + mixamo_animation_path = basepath + "/assets/blender/mixamo/male/" + mixamo_animations = [] + fbx_scale = 1.0 + +class ExportMappingFemale: + blend_path = "assets/blender/" + "vrm-vroid-normal-female.blend" + gltf_path = "assets/blender/" + "characters/female/vroid-normal-female.gltf" + ogre_scene = "characters/female/vroid-normal-female.scene" + inner_path = "Object" + objs = ["female", "Body", "Hair", "Face"] + armature_name = "female" + outfile = "tmp-female.blend" + default_action = 'default' + def __init__(self): + self.files = [] + for fobj in self.objs: + self.files.append({"name": fobj}) + +class ExportMappingMale: + blend_path = "assets/blender/" + "vrm-vroid-normal-male.blend" + gltf_path = "assets/blender/" + "characters/male/vroid-normal-male.gltf" + ogre_scene = "characters/male/vroid-normal-male.scene" + inner_path = "Object" + objs = ["male", "Body", "Hair", "Face"] + armature_name = "male" + outfile = "tmp-male.blend" + default_action = 'default' + def __init__(self): + self.files = [] + for fobj in self.objs: + self.files.append({"name": fobj}) + +class ExportMappingMaleEdited: + blend_path = "assets/blender/" + "edited-normal-male.blend" + gltf_path = "characters/male/normal-male.gltf" + ogre_scene = "characters/male/vroid-normal-male.scene" + inner_path = "Object" + objs = ["male", "Body", "Hair", "Face", "BackHair", "Tops", "Bottoms", "Shoes", "Accessory"] + armature_name = "male" + outfile = "tmp-male.blend" + default_action = 'default' + def __init__(self): + self.files = [] + for fobj in self.objs: + self.files.append({"name": fobj}) + +class ExportMappingMaleBabyShape: + blend_path = "assets/blender/shapes/male/" + "vrm-vroid-normal-male-chibi.blend" + gltf_path = "assets/blender/" + "characters/shapes/male/chibi/vroid-normal-male-chibi.gltf" + ogre_scene = "characters/shapes/male/chibi/vroid-normal-male-chibi.scene" + inner_path = "Object" + objs = ["male", "Body", "Hair", "Face"] + armature_name = "male" + outfile = "tmp-male-chibi.blend" + default_action = 'default' + def __init__(self): + self.files = [] + for fobj in self.objs: + self.files.append({"name": fobj}) + diff --git a/assets/blender/scripts/test.py b/assets/blender/scripts/test.py new file mode 100644 index 0000000..3fd730d --- /dev/null +++ b/assets/blender/scripts/test.py @@ -0,0 +1,7 @@ +import os, sys, time +import bpy + +sys.path.insert(0, os.getcwd() + "/assets/blender/scripts") + +help(bpy.ops.ogre.export) + diff --git a/assets/blender/scripts/vrm/VroidMixamoRename.py b/assets/blender/scripts/vrm/VroidMixamoRename.py new file mode 100644 index 0000000..cfaa554 --- /dev/null +++ b/assets/blender/scripts/vrm/VroidMixamoRename.py @@ -0,0 +1,188 @@ +bl_info = { + "name": "Vroid To Mixamo Rename", + "author": "Artificial Light", + "version": (1, 0), + "blender": (2, 80, 0), + "location": "View3D > N", + "description": "Renames Vrm importer bones to mixamo add-on and changes the bone roll so that Mixamo add-on works with Vroid models", + "warning": "", + "doc_url": "", + "category": "", +} + + +import bpy +from bpy.types import (Panel,Operator) +import math + +class Button(bpy.types.Operator): + """Make Sure to Select the Armature""" + bl_idname = "rnm.1" + bl_label = "RENAME AND ROLL" + + + + def execute(self, context): + context = bpy.context + obj = context.object + + namelist = [ + ("J_Bip_C_Chest", "mixamorig:Spine1"), + ("J_Bip_C_UpperChest","mixamorig:Spine2"), + ("J_Bip_C_Head","mixamorig:Head"), + ("J_Bip_C_Neck","mixamorig:Neck"), + ("J_Bip_C_Spine","mixamorig:Spine"), + ("J_Bip_C_Hips","mixamorig:Hips"), + ("J_Bip_L_UpperLeg","mixamorig:LeftUpLeg"), + ("J_Bip_R_UpperLeg","mixamorig:RightUpLeg"), + ("J_Bip_L_LowerLeg","mixamorig:LeftLeg"), + ("J_Bip_R_LowerLeg","mixamorig:RightLeg"), + ("J_Bip_R_Foot","mixamorig:RightFoot"), + ("J_Bip_L_Foot","mixamorig:LeftFoot"), + ("J_Bip_R_ToeBase","mixamorig:RightToeBase"), + ("J_Bip_L_ToeBase","mixamorig:LeftToeBase"), + ("J_Bip_L_Shoulder","mixamorig:LeftShoulder"), + ("J_Bip_R_Shoulder","mixamorig:RightShoulder"), + ("J_Bip_L_UpperArm","mixamorig:LeftArm"), + ("J_Bip_R_UpperArm","mixamorig:RightArm"), + ("J_Bip_L_LowerArm","mixamorig:LeftForeArm"), + ("J_Bip_R_LowerArm","mixamorig:RightForeArm"), + ("J_Bip_L_Hand","mixamorig:LeftHand"), + ("J_Bip_R_Hand","mixamorig:RightHand"), + + ("J_Bip_L_Thumb1","mixamorig:LeftHandThumb1"), + ("J_Bip_R_Thumb1","mixamorig:RightHandThumb1"), + ("J_Bip_L_Thumb2","mixamorig:LeftHandThumb2"), + ("J_Bip_R_Thumb2","mixamorig:RightHandThumb2"), + ("J_Bip_L_Thumb3","mixamorig:LeftHandThumb3"), + ("J_Bip_R_Thumb3","mixamorig:RightHandThumb3"), + + ("J_Bip_L_Index1","mixamorig:LeftHandIndex1"), + ("J_Bip_R_Index1","mixamorig:RightHandIndex1"), + ("J_Bip_L_Index2","mixamorig:LeftHandIndex2"), + ("J_Bip_R_Index2","mixamorig:RightHandIndex2"), + ("J_Bip_L_Index3","mixamorig:LeftHandIndex3"), + ("J_Bip_R_Index3","mixamorig:RightHandIndex3"), + + ("J_Bip_L_Middle1","mixamorig:LeftHandMiddle1"), + ("J_Bip_R_Middle1","mixamorig:RightHandMiddle1"), + ("J_Bip_L_Middle2","mixamorig:LeftHandMiddle2"), + ("J_Bip_R_Middle2","mixamorig:RightHandMiddle2"), + ("J_Bip_L_Middle3","mixamorig:LeftHandMiddle3"), + ("J_Bip_R_Middle3","mixamorig:RightHandMiddle3"), + + ("J_Bip_L_Ring1","mixamorig:LeftHandRing1"), + ("J_Bip_R_Ring1","mixamorig:RightHandRing1"), + ("J_Bip_L_Ring2","mixamorig:LeftHandRing2"), + ("J_Bip_R_Ring2","mixamorig:RightHandRing2"), + ("J_Bip_L_Ring3","mixamorig:LeftHandRing3"), + ("J_Bip_R_Ring3","mixamorig:RightHandRing3"), + + ("J_Bip_L_Little1","mixamorig:LeftHandPinky1"), + ("J_Bip_R_Little1","mixamorig:RightHandPinky1"), + ("J_Bip_L_Little2","mixamorig:LeftHandPinky2"), + ("J_Bip_R_Little2","mixamorig:RightHandPinky2"), + ("J_Bip_L_Little3","mixamorig:LeftHandPinky3"), + ("J_Bip_R_Little3","mixamorig:RightHandPinky3"), + + ("J_Adj_L_FaceEye","Eye.L"), + ("J_Adj_R_FaceEye","Eye.R"), + ("J_Sec_L_Bust1","Bust1.L"), + ("J_Sec_R_Bust1","Bust1.R"), + ("J_Sec_L_Bust2","Bust2.L"), + ("J_Sec_R_Bust2","Bust2.R"), + + ] + + for name, newname in namelist: + # get the pose bone with name + pb = obj.pose.bones.get(name) + # continue if no bone of that name + if pb is None: + continue + # rename + pb.name = newname + + bpy.ops.object.mode_set(mode = 'EDIT') + bpy.context.object.data.edit_bones['mixamorig:LeftShoulder'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftForeArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHand'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightShoulder'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightForeArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHand'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftUpLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightUpLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftFoot'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftToeBase'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightFoot'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightToeBase'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandThumb1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandThumb2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandThumb3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandThumb1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandThumb2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandThumb3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandIndex1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandIndex2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandIndex3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandIndex1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandIndex2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandIndex3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandMiddle1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandMiddle2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandMiddle3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandMiddle1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandMiddle2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandMiddle3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandRing1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandRing2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandRing3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandRing1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandRing2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandRing3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandPinky1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandPinky2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandPinky3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandPinky1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandPinky2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandPinky3'].roll=+math.radians(180) + + + + bpy.ops.object.mode_set(mode='OBJECT') + return {'FINISHED'} + +class Panel(bpy.types.Panel): + bl_label = "Rename Vroid Bones for Mixamo and Change Bone Roll" + bl_idname = "OBJECT_PT_Rename" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Rename Vroid Bones and change Roll" + + def draw(self, context): + layout = self.layout + + obj = context.object + + row = layout.row() + row.operator(Button.bl_idname,text="RENAME AND ROLL", icon='MENU_PANEL') + +from bpy.utils import register_class, unregister_class +_classes=[ + Button, + Panel +] +def register(): + for cls in _classes: + register_class(cls) + +def unregister(): + + for cls in _classes: + unregister_class(cls) +if __name__ == "__main__": + register() diff --git a/assets/blender/scripts/vrm/__init__.py b/assets/blender/scripts/vrm/__init__.py new file mode 100644 index 0000000..26c568c --- /dev/null +++ b/assets/blender/scripts/vrm/__init__.py @@ -0,0 +1,3 @@ + +# empty + diff --git a/assets/blender/scripts/vrm/rename.py b/assets/blender/scripts/vrm/rename.py new file mode 100644 index 0000000..9ad609a --- /dev/null +++ b/assets/blender/scripts/vrm/rename.py @@ -0,0 +1,135 @@ +import bpy +import math + +def rename_bones(obj): + namelist = [ + ("J_Bip_C_Chest", "mixamorig:Spine1"), + ("J_Bip_C_UpperChest","mixamorig:Spine2"), + ("J_Bip_C_Head","mixamorig:Head"), + ("J_Bip_C_Neck","mixamorig:Neck"), + ("J_Bip_C_Spine","mixamorig:Spine"), + ("J_Bip_C_Hips","mixamorig:Hips"), + ("J_Bip_L_UpperLeg","mixamorig:LeftUpLeg"), + ("J_Bip_R_UpperLeg","mixamorig:RightUpLeg"), + ("J_Bip_L_LowerLeg","mixamorig:LeftLeg"), + ("J_Bip_R_LowerLeg","mixamorig:RightLeg"), + ("J_Bip_R_Foot","mixamorig:RightFoot"), + ("J_Bip_L_Foot","mixamorig:LeftFoot"), + ("J_Bip_R_ToeBase","mixamorig:RightToeBase"), + ("J_Bip_L_ToeBase","mixamorig:LeftToeBase"), + ("J_Bip_L_Shoulder","mixamorig:LeftShoulder"), + ("J_Bip_R_Shoulder","mixamorig:RightShoulder"), + ("J_Bip_L_UpperArm","mixamorig:LeftArm"), + ("J_Bip_R_UpperArm","mixamorig:RightArm"), + ("J_Bip_L_LowerArm","mixamorig:LeftForeArm"), + ("J_Bip_R_LowerArm","mixamorig:RightForeArm"), + ("J_Bip_L_Hand","mixamorig:LeftHand"), + ("J_Bip_R_Hand","mixamorig:RightHand"), + + ("J_Bip_L_Thumb1","mixamorig:LeftHandThumb1"), + ("J_Bip_R_Thumb1","mixamorig:RightHandThumb1"), + ("J_Bip_L_Thumb2","mixamorig:LeftHandThumb2"), + ("J_Bip_R_Thumb2","mixamorig:RightHandThumb2"), + ("J_Bip_L_Thumb3","mixamorig:LeftHandThumb3"), + ("J_Bip_R_Thumb3","mixamorig:RightHandThumb3"), + + ("J_Bip_L_Index1","mixamorig:LeftHandIndex1"), + ("J_Bip_R_Index1","mixamorig:RightHandIndex1"), + ("J_Bip_L_Index2","mixamorig:LeftHandIndex2"), + ("J_Bip_R_Index2","mixamorig:RightHandIndex2"), + ("J_Bip_L_Index3","mixamorig:LeftHandIndex3"), + ("J_Bip_R_Index3","mixamorig:RightHandIndex3"), + + ("J_Bip_L_Middle1","mixamorig:LeftHandMiddle1"), + ("J_Bip_R_Middle1","mixamorig:RightHandMiddle1"), + ("J_Bip_L_Middle2","mixamorig:LeftHandMiddle2"), + ("J_Bip_R_Middle2","mixamorig:RightHandMiddle2"), + ("J_Bip_L_Middle3","mixamorig:LeftHandMiddle3"), + ("J_Bip_R_Middle3","mixamorig:RightHandMiddle3"), + + ("J_Bip_L_Ring1","mixamorig:LeftHandRing1"), + ("J_Bip_R_Ring1","mixamorig:RightHandRing1"), + ("J_Bip_L_Ring2","mixamorig:LeftHandRing2"), + ("J_Bip_R_Ring2","mixamorig:RightHandRing2"), + ("J_Bip_L_Ring3","mixamorig:LeftHandRing3"), + ("J_Bip_R_Ring3","mixamorig:RightHandRing3"), + + ("J_Bip_L_Little1","mixamorig:LeftHandPinky1"), + ("J_Bip_R_Little1","mixamorig:RightHandPinky1"), + ("J_Bip_L_Little2","mixamorig:LeftHandPinky2"), + ("J_Bip_R_Little2","mixamorig:RightHandPinky2"), + ("J_Bip_L_Little3","mixamorig:LeftHandPinky3"), + ("J_Bip_R_Little3","mixamorig:RightHandPinky3"), + + ("J_Adj_L_FaceEye","Eye.L"), + ("J_Adj_R_FaceEye","Eye.R"), + ("J_Sec_L_Bust1","Bust1.L"), + ("J_Sec_R_Bust1","Bust1.R"), + ("J_Sec_L_Bust2","Bust2.L"), + ("J_Sec_R_Bust2","Bust2.R"), + + ] + + for name, newname in namelist: + # get the pose bone with name + pb = obj.pose.bones.get(name) + # continue if no bone of that name + if pb is None: + continue + # rename + pb.name = newname + + bpy.ops.object.mode_set(mode = 'EDIT') + bpy.context.object.data.edit_bones['mixamorig:LeftShoulder'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftForeArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHand'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightShoulder'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightForeArm'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHand'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftUpLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightUpLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightLeg'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftFoot'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftToeBase'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightFoot'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightToeBase'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandThumb1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandThumb2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandThumb3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandThumb1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandThumb2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandThumb3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandIndex1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandIndex2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandIndex3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandIndex1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandIndex2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandIndex3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandMiddle1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandMiddle2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandMiddle3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandMiddle1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandMiddle2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandMiddle3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandRing1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandRing2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandRing3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandRing1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandRing2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandRing3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandPinky1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandPinky2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:LeftHandPinky3'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandPinky1'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandPinky2'].roll=+math.radians(180) + bpy.context.object.data.edit_bones['mixamorig:RightHandPinky3'].roll=+math.radians(180) + + + + bpy.ops.object.mode_set(mode='OBJECT') + return {'FINISHED'} + +