From 9107ccbaa35ce944ffb32dd3f90d3f31ddb9e530 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Tue, 28 Jan 2025 11:30:59 +0300 Subject: [PATCH] Added blender scripts --- assets/blender/scripts/export_clothes.py | 371 +++ assets/blender/scripts/export_models.py | 278 ++ assets/blender/scripts/import_vrm.py | 237 ++ assets/blender/scripts/mixamo/define.py | 1 + .../scripts/mixamo/definitions/naming.py | 14 + .../mixamo/import_mixamo_root_motion.py | 459 +++ assets/blender/scripts/mixamo/lib/addon.py | 15 + .../blender/scripts/mixamo/lib/animation.py | 203 ++ assets/blender/scripts/mixamo/lib/armature.py | 29 + .../blender/scripts/mixamo/lib/bones_data.py | 36 + .../blender/scripts/mixamo/lib/bones_edit.py | 19 + .../blender/scripts/mixamo/lib/bones_pose.py | 113 + .../blender/scripts/mixamo/lib/constraints.py | 19 + assets/blender/scripts/mixamo/lib/context.py | 10 + assets/blender/scripts/mixamo/lib/cs.blend | Bin 0 -> 1062988 bytes .../scripts/mixamo/lib/custom_props.py | 60 + assets/blender/scripts/mixamo/lib/drivers.py | 23 + .../blender/scripts/mixamo/lib/maths_geo.py | 151 + assets/blender/scripts/mixamo/lib/mixamo.py | 39 + assets/blender/scripts/mixamo/lib/objects.py | 81 + assets/blender/scripts/mixamo/lib/version.py | 35 + assets/blender/scripts/mixamo/mixamo_rig.py | 2835 +++++++++++++++++ assets/blender/scripts/mixamo/mixamoroot.py | 387 +++ assets/blender/scripts/mixamo/utils.py | 18 + .../blender/scripts/vrm/VroidMixamoRename.py | 188 ++ assets/blender/scripts/vrm/__init__.py | 3 + assets/blender/scripts/vrm/rename.py | 135 + 27 files changed, 5759 insertions(+) create mode 100644 assets/blender/scripts/export_clothes.py create mode 100644 assets/blender/scripts/export_models.py create mode 100644 assets/blender/scripts/import_vrm.py create mode 100644 assets/blender/scripts/mixamo/define.py create mode 100644 assets/blender/scripts/mixamo/definitions/naming.py create mode 100644 assets/blender/scripts/mixamo/import_mixamo_root_motion.py create mode 100644 assets/blender/scripts/mixamo/lib/addon.py create mode 100644 assets/blender/scripts/mixamo/lib/animation.py create mode 100644 assets/blender/scripts/mixamo/lib/armature.py create mode 100644 assets/blender/scripts/mixamo/lib/bones_data.py create mode 100644 assets/blender/scripts/mixamo/lib/bones_edit.py create mode 100644 assets/blender/scripts/mixamo/lib/bones_pose.py create mode 100644 assets/blender/scripts/mixamo/lib/constraints.py create mode 100644 assets/blender/scripts/mixamo/lib/context.py create mode 100644 assets/blender/scripts/mixamo/lib/cs.blend create mode 100644 assets/blender/scripts/mixamo/lib/custom_props.py create mode 100644 assets/blender/scripts/mixamo/lib/drivers.py create mode 100644 assets/blender/scripts/mixamo/lib/maths_geo.py create mode 100644 assets/blender/scripts/mixamo/lib/mixamo.py create mode 100644 assets/blender/scripts/mixamo/lib/objects.py create mode 100644 assets/blender/scripts/mixamo/lib/version.py create mode 100644 assets/blender/scripts/mixamo/mixamo_rig.py create mode 100644 assets/blender/scripts/mixamo/mixamoroot.py create mode 100644 assets/blender/scripts/mixamo/utils.py create mode 100644 assets/blender/scripts/vrm/VroidMixamoRename.py create mode 100644 assets/blender/scripts/vrm/__init__.py create mode 100644 assets/blender/scripts/vrm/rename.py 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_models.py b/assets/blender/scripts/export_models.py new file mode 100644 index 0000000..81caf9f --- /dev/null +++ b/assets/blender/scripts/export_models.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python + +import os, time +import bpy +from math import pi +import glob +import shutil +from mathutils import Vector, Matrix +from math import radians, pi + +class ExportMappingFemale: + blend_path = "assets/blender/" + "vrm-vroid-normal-female.blend" + gltf_path = "godot/character-data/vroid-normal-female.npc" + 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 = "godot/character-data/vroid-normal-male.npc" + 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}) + +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()]: +#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)) + + bpy.ops.export_scene.gltf(filepath=mapping.gltf_path.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, + 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_normal=True, + export_lights=False) + shutil.move(mapping.gltf_path.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/import_vrm.py b/assets/blender/scripts/import_vrm.py new file mode 100644 index 0000000..c6acdda --- /dev/null +++ b/assets/blender/scripts/import_vrm.py @@ -0,0 +1,237 @@ +#!/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 + +basepath = os.getcwd() +class VRMDataFemale: + path = "jane2-dress.vrm" + outfile = "vrm-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" + 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 + +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(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) + #import_mixamo_root_motion(bpy.context, anim_path, True, True, False, False, False, 'COPY_HIPS', True, True, True) + #get_anim(anim_path) + 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 +# create_root_bone(anim_obj) +# hips_to_root(anim_obj) + 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) + +# bpy.ops.import_scene.fbx(filepath=anim_path, +# directory='', filter_glob='*.fbx', +# files=None, ui_tab='MAIN', +# use_manual_orientation=False, +# global_scale=1.0, bake_space_transform=False, +# use_custom_normals=True, colors_type='SRGB', +# use_image_search=True, use_alpha_decals=False, +# decal_offset=0.0, use_anim=True, anim_offset=1.0, +# use_subsurf=False, use_custom_props=True, +# use_custom_props_enum_as_string=True, +# ignore_leaf_bones=False, force_connect_children=False, +# automatic_bone_orientation=False, primary_bone_axis='Y', +# secondary_bone_axis='X', use_prepost_rot=True, +# axis_forward='-Z', axis_up='Y') +# bpy.context.object.name = anim +# bpy.context.object.animation_data.action.name = anim +# _zero_out(main_armature) + 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/mixamo/define.py b/assets/blender/scripts/mixamo/define.py new file mode 100644 index 0000000..70af10e --- /dev/null +++ b/assets/blender/scripts/mixamo/define.py @@ -0,0 +1 @@ +from .definitions.naming import * \ No newline at end of file diff --git a/assets/blender/scripts/mixamo/definitions/naming.py b/assets/blender/scripts/mixamo/definitions/naming.py new file mode 100644 index 0000000..8c0b204 --- /dev/null +++ b/assets/blender/scripts/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/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/lib/addon.py b/assets/blender/scripts/mixamo/lib/addon.py new file mode 100644 index 0000000..1116e62 --- /dev/null +++ b/assets/blender/scripts/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/lib/animation.py b/assets/blender/scripts/mixamo/lib/animation.py new file mode 100644 index 0000000..95d0864 --- /dev/null +++ b/assets/blender/scripts/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/lib/armature.py b/assets/blender/scripts/mixamo/lib/armature.py new file mode 100644 index 0000000..5897e9b --- /dev/null +++ b/assets/blender/scripts/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/lib/bones_data.py b/assets/blender/scripts/mixamo/lib/bones_data.py new file mode 100644 index 0000000..3afac6f --- /dev/null +++ b/assets/blender/scripts/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/lib/bones_edit.py b/assets/blender/scripts/mixamo/lib/bones_edit.py new file mode 100644 index 0000000..4a03525 --- /dev/null +++ b/assets/blender/scripts/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/lib/bones_pose.py b/assets/blender/scripts/mixamo/lib/bones_pose.py new file mode 100644 index 0000000..d38362b --- /dev/null +++ b/assets/blender/scripts/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/lib/constraints.py b/assets/blender/scripts/mixamo/lib/constraints.py new file mode 100644 index 0000000..3111787 --- /dev/null +++ b/assets/blender/scripts/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/lib/context.py b/assets/blender/scripts/mixamo/lib/context.py new file mode 100644 index 0000000..a3718bf --- /dev/null +++ b/assets/blender/scripts/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/lib/cs.blend b/assets/blender/scripts/mixamo/lib/cs.blend new file mode 100644 index 0000000000000000000000000000000000000000..04431e7dbd58587fa87554ab30d69e9dcef7d47c GIT binary patch literal 1062988 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

b+T!cIkpP+ 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 ZPxKf=U-o9|+bw<^9lEt|htP|D{{>(F`tkq( literal 0 HcmV?d00001 diff --git a/assets/blender/scripts/mixamo/lib/custom_props.py b/assets/blender/scripts/mixamo/lib/custom_props.py new file mode 100644 index 0000000..794ea37 --- /dev/null +++ b/assets/blender/scripts/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/lib/drivers.py b/assets/blender/scripts/mixamo/lib/drivers.py new file mode 100644 index 0000000..31b8bda --- /dev/null +++ b/assets/blender/scripts/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/lib/maths_geo.py b/assets/blender/scripts/mixamo/lib/maths_geo.py new file mode 100644 index 0000000..6219111 --- /dev/null +++ b/assets/blender/scripts/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/lib/mixamo.py b/assets/blender/scripts/mixamo/lib/mixamo.py new file mode 100644 index 0000000..0644bc5 --- /dev/null +++ b/assets/blender/scripts/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/lib/objects.py b/assets/blender/scripts/mixamo/lib/objects.py new file mode 100644 index 0000000..00e4248 --- /dev/null +++ b/assets/blender/scripts/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/lib/version.py b/assets/blender/scripts/mixamo/lib/version.py new file mode 100644 index 0000000..0d331d4 --- /dev/null +++ b/assets/blender/scripts/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/mixamo_rig.py b/assets/blender/scripts/mixamo/mixamo_rig.py new file mode 100644 index 0000000..97a5619 --- /dev/null +++ b/assets/blender/scripts/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/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/mixamo/utils.py b/assets/blender/scripts/mixamo/utils.py new file mode 100644 index 0000000..2b64849 --- /dev/null +++ b/assets/blender/scripts/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/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'} + +