Added blender scripts
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,5 +4,5 @@ build-vscode/*
|
||||
characters/*
|
||||
resources/buildings/*
|
||||
assets/blender/scripts/*.blend
|
||||
assets/blender/scripts/__pycache__/*
|
||||
__pycache__/
|
||||
*.blend[1-9]
|
||||
|
||||
72
assets/blender/scripts/export_buildings.py
Normal file
72
assets/blender/scripts/export_buildings.py
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, time
|
||||
import bpy
|
||||
from math import pi
|
||||
import glob
|
||||
import shutil
|
||||
from mathutils import Vector, Matrix
|
||||
from math import radians, pi
|
||||
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
|
||||
gltf_file = argv[0]
|
||||
print("Exporting to " + gltf_file)
|
||||
basepath = os.getcwd()
|
||||
# bpy.ops.export_scene.gltf(filepath="", check_existing=True,
|
||||
# export_import_convert_lighting_mode='SPEC', gltf_export_id="",
|
||||
# export_format='GLB', ui_tab='GENERAL', export_copyright="", export_image_format='AUTO',
|
||||
# export_texture_dir="", export_jpeg_quality=75, export_keep_originals=False,
|
||||
# export_texcoords=True, export_normals=True, export_draco_mesh_compression_enable=False,
|
||||
# export_draco_mesh_compression_level=6, export_draco_position_quantization=14,
|
||||
# export_draco_normal_quantization=10, export_draco_texcoord_quantization=12,
|
||||
# export_draco_color_quantization=10, export_draco_generic_quantization=12, export_tangents=False,
|
||||
# export_materials='EXPORT', export_original_specular=False, export_colors=True,
|
||||
# export_attributes=False, use_mesh_edges=False, use_mesh_vertices=False, export_cameras=False,
|
||||
# use_selection=False, use_visible=False, use_renderable=False,
|
||||
# use_active_collection_with_nested=True, use_active_collection=False, use_active_scene=False,
|
||||
# export_extras=False, export_yup=True, export_apply=False, export_animations=True,
|
||||
# export_frame_range=False, export_frame_step=1, export_force_sampling=True, export_animation_mode='ACTIONS',
|
||||
# export_nla_strips_merged_animation_name="Animation", export_def_bones=False,
|
||||
# export_hierarchy_flatten_bones=False, export_optimize_animation_size=True,
|
||||
# export_optimize_animation_keep_anim_armature=True, export_optimize_animation_keep_anim_object=False,
|
||||
# export_negative_frame='SLIDE', export_anim_slide_to_zero=False, export_bake_animation=False,
|
||||
# export_anim_single_armature=True, export_reset_pose_bones=True, export_current_frame=False,
|
||||
# export_rest_position_armature=True, export_anim_scene_split_object=True, export_skins=True,
|
||||
# export_all_influences=False, export_morph=True, export_morph_normal=True,
|
||||
# export_morph_tangent=False, export_morph_animation=True, export_morph_reset_sk_data=True,
|
||||
# export_lights=False, export_nla_strips=True, will_save_settings=False, filter_glob="*.glb")
|
||||
|
||||
bpy.ops.export_scene.gltf(filepath=gltf_file,
|
||||
use_selection=False,
|
||||
check_existing=False,
|
||||
export_format='GLB',
|
||||
export_texture_dir='textures', export_texcoords=True,
|
||||
export_normals=True,
|
||||
export_tangents=True,
|
||||
export_materials='EXPORT',
|
||||
export_colors=True,
|
||||
use_mesh_edges=False,
|
||||
use_mesh_vertices=False,
|
||||
export_cameras=False,
|
||||
use_visible=False,
|
||||
use_renderable=False,
|
||||
export_yup=True,
|
||||
export_apply=True,
|
||||
export_animations=True,
|
||||
export_force_sampling=True,
|
||||
export_def_bones=False,
|
||||
export_current_frame=False,
|
||||
export_morph=True,
|
||||
export_morph_animation=False,
|
||||
export_morph_normal=True,
|
||||
export_morph_tangent=True,
|
||||
export_lights=False,
|
||||
export_skins=True)
|
||||
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
time.sleep(2)
|
||||
bpy.ops.wm.quit_blender()
|
||||
371
assets/blender/scripts/export_clothes.py
Normal file
371
assets/blender/scripts/export_clothes.py
Normal file
@@ -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()
|
||||
121
assets/blender/scripts/export_for_modelling.py
Normal file
121
assets/blender/scripts/export_for_modelling.py
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, time
|
||||
import bpy
|
||||
from math import pi
|
||||
import glob
|
||||
import shutil
|
||||
from mathutils import Vector, Matrix
|
||||
from math import radians, pi
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
from vrm import rename
|
||||
from mixamo import mixamo_rig
|
||||
from mixamo.lib.armature import *
|
||||
#from mixamo.mixamoroot import import_armature
|
||||
#from mixamo.import_mixamo_root_motion import import_mixamo_root_motion
|
||||
|
||||
from settings import VRMDataFemale, VRMDataMale, basepath
|
||||
|
||||
imports = [VRMDataFemale(), VRMDataMale()]
|
||||
|
||||
|
||||
class mkrig:
|
||||
ik_arms = True
|
||||
ik_legs = True
|
||||
def __init__(self):
|
||||
mixamo_rig._make_rig(self)
|
||||
mixamo_rig.remove_temp_objects()
|
||||
mixamo_rig.clean_scene()
|
||||
def get_anim(filepath, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False):
|
||||
# current_context = bpy.context.area.ui_type
|
||||
old_objs = set(bpy.context.scene.objects)
|
||||
try:
|
||||
import_armature(filepath, root_bone_name, hip_bone_name, remove_prefix, name_prefix, insert_root, delete_armatures)
|
||||
imported_objects = set(bpy.context.scene.objects) - old_objs
|
||||
if delete_armatures and num_files > 1:
|
||||
deleteArmature(imported_objects)
|
||||
except Exception as e:
|
||||
log.error("[Mixamo Root] ERROR get_all_anims raised %s when processing %s" % (str(e), filepath))
|
||||
# bpy.context.area.ui_type = current_context
|
||||
bpy.context.scene.frame_start = 0
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
def create_root_bone(anim_obj):
|
||||
bpy.context.view_layer.objects.active = anim_obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
root_bone = anim_obj.data.edit_bones.new("Root")
|
||||
root_bone.tail.y = 0.5
|
||||
anim_obj.data.edit_bones["mixamorig:Hips"].parent = anim_obj.data.edit_bones["Root"]
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
def hips_to_root(anim_obj):
|
||||
for fc in anim_obj.animation_data.action.fcurves:
|
||||
if "mixamorig:Hips" in fc.data_path:
|
||||
if "location" in fc.data_path:
|
||||
print(fc, fc.data_path, fc.array_index)
|
||||
if fc.array_index in [0, 2]:
|
||||
fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root")
|
||||
# if fc.array_index == 2:
|
||||
# for kp in fc.keyframe_points:
|
||||
# kp.co.z = min(0, abs(kp.co.z))
|
||||
# hip_curves = [fc for fc in anim_obj.animation_data.action.fcurves if hip_bone_name in fc.data_path and fc.data_path.startswith('location')]
|
||||
# print(hip_curves)
|
||||
def hips_to_root(anim_obj):
|
||||
for fc in anim_obj.animation_data.action.fcurves:
|
||||
if "mixamorig:Hips" in fc.data_path:
|
||||
if "location" in fc.data_path:
|
||||
print(fc, fc.data_path, fc.array_index)
|
||||
if fc.array_index in [0, 2]:
|
||||
fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root")
|
||||
def hips_to_root2(anim_obj):
|
||||
for fc in anim_obj.animation_data.action.fcurves:
|
||||
if "Ctrl_Hips" in fc.data_path:
|
||||
if "location" in fc.data_path:
|
||||
print(fc, fc.data_path, fc.array_index)
|
||||
if fc.array_index in [0, 2]:
|
||||
data_path = fc.data_path[:]
|
||||
# fc.data_path = data_path.replace("Ctrl_Hips", "Ctrl_Master")
|
||||
fcr = anim_obj.animation_data.action.fcurves.new(data_path = data_path.replace("Ctrl_Hips", "Root"), index = fc.array_index)
|
||||
keys = [0] * (2 * len(fc.keyframe_points))
|
||||
fcr.keyframe_points.add(len(fc.keyframe_points))
|
||||
fc.keyframe_points.foreach_get("co", keys)
|
||||
fcr.keyframe_points.foreach_set("co", keys)
|
||||
print("ROOT")
|
||||
# fc.data_path = data_path.replace("Ctrl_Hips", "Root")
|
||||
# for fcr in anim_obj.animation_data.action.fcurves:
|
||||
# if "Root" in fcr.data_path:
|
||||
# fcr.array_index = fc.array_index
|
||||
# print("ROOT")
|
||||
|
||||
for imp in imports:
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
for o in bpy.data.objects:
|
||||
bpy.data.objects.remove(o)
|
||||
print(basepath + "/assets/vroid/" + imp.path)
|
||||
result = bpy.ops.import_scene.vrm(filepath=(basepath + "/assets/vroid/" + imp.path))
|
||||
if result != {"FINISHED"}:
|
||||
raise Exception(f"Failed to import vrm: {result}")
|
||||
armature_count = 0
|
||||
for obj in bpy.data.objects:
|
||||
if (obj.type == "ARMATURE"):
|
||||
armature_count += 1
|
||||
if armature_count > 1:
|
||||
raise Exception("Bad scene data")
|
||||
main_armature = None
|
||||
for obj in bpy.data.objects:
|
||||
if (obj.type == "ARMATURE"):
|
||||
main_armature = obj
|
||||
break
|
||||
if main_armature == None:
|
||||
raise Exception("Bad scene data")
|
||||
print("Renaming...")
|
||||
main_armature.select_set(True)
|
||||
bpy.context.view_layer.objects.active = main_armature
|
||||
rename.rename_bones(main_armature)
|
||||
main_armature.select_set(False)
|
||||
main_armature.name = imp.armature_name
|
||||
main_armature["VRM_IMPORTED_NAME"] = "female"
|
||||
if main_armature.animation_data is None:
|
||||
main_armature.animation_data_create()
|
||||
|
||||
main_armature.select_set(False)
|
||||
bpy.context.view_layer.objects.active = None
|
||||
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/" + imp.editfile))
|
||||
264
assets/blender/scripts/export_models.py
Normal file
264
assets/blender/scripts/export_models.py
Normal file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, time
|
||||
import bpy
|
||||
from math import pi
|
||||
import glob
|
||||
import shutil
|
||||
from mathutils import Vector, Matrix
|
||||
from math import radians, pi
|
||||
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited
|
||||
|
||||
basepath = os.getcwd()
|
||||
def check_bone(bname):
|
||||
ok = True
|
||||
baddie = ["ctrl_", "mch_", "MCH_", "Ctrl_", "Mch_"]
|
||||
for bd in baddie:
|
||||
if bname.startswith(bd):
|
||||
ok = False
|
||||
break
|
||||
return ok
|
||||
|
||||
def prepare_armature(mapping):
|
||||
print("Preparing...")
|
||||
bpy.ops.object.armature_add(enter_editmode=False)
|
||||
new_armature = bpy.context.object
|
||||
orig_armature = bpy.data.objects[mapping.armature_name]
|
||||
armature_name = orig_armature.name
|
||||
orig_armature.name = orig_armature.name + "_orig"
|
||||
new_armature.name = armature_name
|
||||
queue = []
|
||||
if new_armature.animation_data is None:
|
||||
new_armature.animation_data_create()
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
for b in new_armature.data.edit_bones:
|
||||
new_armature.data.edit_bones.remove(b)
|
||||
bpy.context.view_layer.objects.active = orig_armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
for b in orig_armature.data.edit_bones:
|
||||
print(b.name)
|
||||
if b.parent is None:
|
||||
queue.append(b.name)
|
||||
print("Copying bones...")
|
||||
while len(queue) > 0:
|
||||
item = queue.pop(0)
|
||||
print(item)
|
||||
itemb = orig_armature.data.edit_bones[item]
|
||||
if not itemb.use_deform and not check_bone(item):
|
||||
continue
|
||||
for cb in orig_armature.data.edit_bones:
|
||||
if cb.parent == itemb:
|
||||
queue.append(cb.name)
|
||||
nb = new_armature.data.edit_bones.new(item)
|
||||
nb.name = item
|
||||
nb.head = itemb.head
|
||||
nb.tail = itemb.tail
|
||||
nb.matrix = itemb.matrix
|
||||
nb.use_deform = itemb.use_deform
|
||||
if itemb.parent is not None:
|
||||
ok = True
|
||||
pname = itemb.parent.name
|
||||
while not check_bone(pname):
|
||||
bparent = itemb.parent.parent
|
||||
if bparent is None:
|
||||
ok = False
|
||||
break
|
||||
pname = bparent.name
|
||||
if ok:
|
||||
nb.parent = new_armature.data.edit_bones[itemb.parent.name]
|
||||
else:
|
||||
nb.parent = None
|
||||
else:
|
||||
nb.parent = None
|
||||
nb.use_connect = itemb.use_connect
|
||||
# drivers_data = new_armature.animation_data.drivers
|
||||
print("Creating constraints...")
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.context.view_layer.objects.active = orig_armature
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
for b in new_armature.pose.bones:
|
||||
print(b.name)
|
||||
c = b.constraints.new(type='COPY_TRANSFORMS')
|
||||
c.target = orig_armature
|
||||
c.subtarget = b.name
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == orig_armature:
|
||||
obj.parent = new_armature
|
||||
for mod in obj.modifiers:
|
||||
if mod.type == 'ARMATURE':
|
||||
mod.object = new_armature
|
||||
print("Baking actions...")
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
for track in orig_armature.animation_data.nla_tracks:
|
||||
print(track.name)
|
||||
for s in track.strips:
|
||||
action = s.action
|
||||
print(action.name)
|
||||
orig_armature.animation_data.action = action
|
||||
new_armature.animation_data.action = None
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
firstFrame = int(s.action_frame_start)
|
||||
lastFrame = int(s.action_frame_end)
|
||||
bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'})
|
||||
aname = orig_armature.animation_data.action.name
|
||||
orig_armature.animation_data.action.name = "bake_" + aname
|
||||
new_armature.animation_data.action.name = aname
|
||||
track = new_armature.animation_data.nla_tracks.new()
|
||||
track.name = aname
|
||||
track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action)
|
||||
track.mute = True
|
||||
track.lock = True
|
||||
print("Removing constraints...")
|
||||
for b in new_armature.pose.bones:
|
||||
for c in b.constraints:
|
||||
b.constraints.remove(c)
|
||||
new_armature.animation_data.action = bpy.data.actions[mapping.default_action]
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# track = new_armature.animation_data.nla_tracks.new()
|
||||
# track.name = action.name
|
||||
|
||||
|
||||
def clamp_angle_deg(angle, min_angle_deg, max_angle_deg):
|
||||
min_angle = radians(min_angle_deg)
|
||||
max_angle = radians(max_angle_deg)
|
||||
if angle < min_angle:
|
||||
angle = min_angle
|
||||
if angle > max_angle:
|
||||
angle = max_angle
|
||||
return angle
|
||||
def angle_to_linear(angle, divider):
|
||||
if angle < 0.0:
|
||||
return angle / divider
|
||||
else:
|
||||
return 0.0
|
||||
def angle_to_linear_x(bone, angle):
|
||||
skel = bpy.data.objects["skeleton_orig"]
|
||||
left_base = "ctrl_base_upperleg.L.001"
|
||||
right_base = "ctrl_base_upperleg.R.001"
|
||||
base = ""
|
||||
if base == "":
|
||||
for e in ["_R", ".R"]:
|
||||
if bone.name.endswith(e):
|
||||
base = right_base
|
||||
break
|
||||
if base == "":
|
||||
for e in ["_L", ".L"]:
|
||||
if bone.name.endswith(e):
|
||||
base = left_base
|
||||
break
|
||||
if base == "":
|
||||
for e in ["_R.", ".R."]:
|
||||
if bone.name.find(e) >= 0:
|
||||
base = right_base
|
||||
break
|
||||
if base == "":
|
||||
for e in ["_L.", ".L."]:
|
||||
if bone.name.find(e) >= 0:
|
||||
base = left_base
|
||||
break
|
||||
mul = skel.pose.bones[base]["to_linear_x_base"]
|
||||
offset = skel.pose.bones[base]["angle_offset"]
|
||||
# print("bone: ", bone.name, "base: ", base, "mul: ", mul)
|
||||
# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset)
|
||||
print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle)
|
||||
return (angle + offset) * mul
|
||||
def extra_linear(angle, offset):
|
||||
ret = 0.0
|
||||
offt = offset * angle * 2.0 / -radians(-90)
|
||||
if angle * 2.0 < -radians(65):
|
||||
if angle * 2.0 > -radians(65):
|
||||
ret += offset
|
||||
else:
|
||||
ret += offt
|
||||
return ret
|
||||
|
||||
for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited()]:
|
||||
if not os.path.exists(mapping.blend_path):
|
||||
continue
|
||||
#for mapping in [ExportMappingFemale()]:
|
||||
print("Initializing...")
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
print("Preparing driver setup...")
|
||||
bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg
|
||||
bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear
|
||||
bpy.app.driver_namespace["extra_linear"] = extra_linear
|
||||
bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x
|
||||
print("Driver setup done...")
|
||||
|
||||
bpy.ops.wm.append(
|
||||
filepath=os.path.join(mapping.blend_path, mapping.inner_path),
|
||||
directory=os.path.join(mapping.blend_path, mapping.inner_path),
|
||||
files=mapping.files)
|
||||
print("Append done...")
|
||||
|
||||
prepare_armature(mapping)
|
||||
print("Armature done...")
|
||||
|
||||
print("Remove junk...")
|
||||
for ob in bpy.data.objects:
|
||||
if ob.name.startswith("cs_"):
|
||||
bpy.data.objects.remove(ob)
|
||||
elif ob.name.startswith("Face") and ob.name != "Face":
|
||||
bpy.data.objects.remove(ob)
|
||||
|
||||
print("Removing original armature and actions...")
|
||||
orig_arm = bpy.data.objects[mapping.armature_name + '_orig']
|
||||
bpy.data.objects.remove(orig_arm)
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("bake_"):
|
||||
act.name = act.name + "-noimp"
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("bake_"):
|
||||
bpy.data.actions.remove(act)
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("bake_"):
|
||||
bpy.data.actions.remove(act)
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH':
|
||||
if not obj.name in mapping.objs and obj.parent is None:
|
||||
if not obj.name.endswith("-noimp"):
|
||||
obj.name = obj.name + "-noimp"
|
||||
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile))
|
||||
|
||||
os.makedirs(os.path.dirname(mapping.gltf_path), exist_ok=True)
|
||||
bpy.ops.export_scene.gltf(filepath=mapping.gltf_path,
|
||||
use_selection=False,
|
||||
check_existing=False,
|
||||
# export_format='GLTF_SEPARATE',
|
||||
export_format='GLB',
|
||||
export_texture_dir='textures', export_texcoords=True,
|
||||
export_animation_mode='NLA_TRACKS',
|
||||
export_normals=True,
|
||||
export_tangents=True,
|
||||
export_materials='EXPORT',
|
||||
# export_all_vertex_colors=True,
|
||||
# colors_type='SRGB',
|
||||
# export_vertex_colors=True,
|
||||
export_colors=True,
|
||||
use_mesh_edges=False,
|
||||
use_mesh_vertices=False,
|
||||
export_cameras=False,
|
||||
use_visible=False,
|
||||
use_renderable=False,
|
||||
export_yup=True,
|
||||
export_animations=True,
|
||||
export_force_sampling=True,
|
||||
export_def_bones=False,
|
||||
export_current_frame=False,
|
||||
export_morph=True,
|
||||
export_morph_animation=False,
|
||||
export_morph_normal=True,
|
||||
export_morph_tangent=True,
|
||||
export_lights=False,
|
||||
export_skins=True)
|
||||
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
time.sleep(2)
|
||||
bpy.ops.wm.quit_blender()
|
||||
289
assets/blender/scripts/export_ogre_scene.py
Normal file
289
assets/blender/scripts/export_ogre_scene.py
Normal file
@@ -0,0 +1,289 @@
|
||||
import os, sys, time
|
||||
import bpy
|
||||
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
|
||||
from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape
|
||||
|
||||
basepath = os.getcwd()
|
||||
def check_bone(bname):
|
||||
ok = True
|
||||
baddie = ["ctrl_", "mch_", "MCH_", "Ctrl_", "Mch_"]
|
||||
for bd in baddie:
|
||||
if bname.startswith(bd):
|
||||
ok = False
|
||||
break
|
||||
return ok
|
||||
|
||||
def prepare_armature(mapping):
|
||||
print("Preparing...")
|
||||
bpy.ops.object.armature_add(enter_editmode=False)
|
||||
new_armature = bpy.context.object
|
||||
orig_armature = bpy.data.objects[mapping.armature_name]
|
||||
armature_name = orig_armature.name
|
||||
orig_armature.name = orig_armature.name + "_orig"
|
||||
new_armature.name = armature_name
|
||||
queue = []
|
||||
if new_armature.animation_data is None:
|
||||
new_armature.animation_data_create()
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
for b in new_armature.data.edit_bones:
|
||||
new_armature.data.edit_bones.remove(b)
|
||||
bpy.context.view_layer.objects.active = orig_armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
for b in orig_armature.data.edit_bones:
|
||||
print(b.name)
|
||||
if b.parent is None:
|
||||
queue.append(b.name)
|
||||
print("Copying bones...")
|
||||
while len(queue) > 0:
|
||||
item = queue.pop(0)
|
||||
print(item)
|
||||
itemb = orig_armature.data.edit_bones[item]
|
||||
if not itemb.use_deform and not check_bone(item):
|
||||
continue
|
||||
for cb in orig_armature.data.edit_bones:
|
||||
if cb.parent == itemb:
|
||||
queue.append(cb.name)
|
||||
nb = new_armature.data.edit_bones.new(item)
|
||||
nb.name = item
|
||||
nb.head = itemb.head
|
||||
nb.tail = itemb.tail
|
||||
nb.matrix = itemb.matrix
|
||||
nb.use_deform = itemb.use_deform
|
||||
if itemb.parent is not None:
|
||||
ok = True
|
||||
pname = itemb.parent.name
|
||||
while not check_bone(pname):
|
||||
bparent = itemb.parent.parent
|
||||
if bparent is None:
|
||||
ok = False
|
||||
break
|
||||
pname = bparent.name
|
||||
if ok:
|
||||
nb.parent = new_armature.data.edit_bones[itemb.parent.name]
|
||||
else:
|
||||
nb.parent = None
|
||||
else:
|
||||
nb.parent = None
|
||||
nb.use_connect = itemb.use_connect
|
||||
# drivers_data = new_armature.animation_data.drivers
|
||||
print("Creating constraints...")
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.context.view_layer.objects.active = orig_armature
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
for b in new_armature.pose.bones:
|
||||
print(b.name)
|
||||
c = b.constraints.new(type='COPY_TRANSFORMS')
|
||||
c.target = orig_armature
|
||||
c.subtarget = b.name
|
||||
for obj in bpy.data.objects:
|
||||
if obj.parent == orig_armature:
|
||||
obj.parent = new_armature
|
||||
for mod in obj.modifiers:
|
||||
if mod.type == 'ARMATURE':
|
||||
mod.object = new_armature
|
||||
print("Baking actions...")
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
for track in orig_armature.animation_data.nla_tracks:
|
||||
print(track.name)
|
||||
for s in track.strips:
|
||||
action = s.action
|
||||
print(action.name)
|
||||
orig_armature.animation_data.action = action
|
||||
new_armature.animation_data.action = None
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
firstFrame = int(s.action_frame_start)
|
||||
lastFrame = int(s.action_frame_end)
|
||||
bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'})
|
||||
aname = orig_armature.animation_data.action.name
|
||||
orig_armature.animation_data.action.name = "bake_" + aname
|
||||
new_armature.animation_data.action.name = aname
|
||||
track = new_armature.animation_data.nla_tracks.new()
|
||||
track.name = aname
|
||||
track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action)
|
||||
track.mute = True
|
||||
track.lock = True
|
||||
print("Removing constraints...")
|
||||
for b in new_armature.pose.bones:
|
||||
for c in b.constraints:
|
||||
b.constraints.remove(c)
|
||||
new_armature.animation_data.action = bpy.data.actions[mapping.default_action]
|
||||
bpy.context.view_layer.objects.active = new_armature
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# track = new_armature.animation_data.nla_tracks.new()
|
||||
# track.name = action.name
|
||||
|
||||
|
||||
def clamp_angle_deg(angle, min_angle_deg, max_angle_deg):
|
||||
min_angle = radians(min_angle_deg)
|
||||
max_angle = radians(max_angle_deg)
|
||||
if angle < min_angle:
|
||||
angle = min_angle
|
||||
if angle > max_angle:
|
||||
angle = max_angle
|
||||
return angle
|
||||
def angle_to_linear(angle, divider):
|
||||
if angle < 0.0:
|
||||
return angle / divider
|
||||
else:
|
||||
return 0.0
|
||||
def angle_to_linear_x(bone, angle):
|
||||
skel = bpy.data.objects["skeleton_orig"]
|
||||
left_base = "ctrl_base_upperleg.L.001"
|
||||
right_base = "ctrl_base_upperleg.R.001"
|
||||
base = ""
|
||||
if base == "":
|
||||
for e in ["_R", ".R"]:
|
||||
if bone.name.endswith(e):
|
||||
base = right_base
|
||||
break
|
||||
if base == "":
|
||||
for e in ["_L", ".L"]:
|
||||
if bone.name.endswith(e):
|
||||
base = left_base
|
||||
break
|
||||
if base == "":
|
||||
for e in ["_R.", ".R."]:
|
||||
if bone.name.find(e) >= 0:
|
||||
base = right_base
|
||||
break
|
||||
if base == "":
|
||||
for e in ["_L.", ".L."]:
|
||||
if bone.name.find(e) >= 0:
|
||||
base = left_base
|
||||
break
|
||||
mul = skel.pose.bones[base]["to_linear_x_base"]
|
||||
offset = skel.pose.bones[base]["angle_offset"]
|
||||
# print("bone: ", bone.name, "base: ", base, "mul: ", mul)
|
||||
# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset)
|
||||
print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle)
|
||||
return (angle + offset) * mul
|
||||
def extra_linear(angle, offset):
|
||||
ret = 0.0
|
||||
offt = offset * angle * 2.0 / -radians(-90)
|
||||
if angle * 2.0 < -radians(65):
|
||||
if angle * 2.0 > -radians(65):
|
||||
ret += offset
|
||||
else:
|
||||
ret += offt
|
||||
return ret
|
||||
|
||||
for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape()]:
|
||||
#for mapping in [ExportMappingFemale()]:
|
||||
print("Initializing...")
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
print("Preparing driver setup...")
|
||||
bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg
|
||||
bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear
|
||||
bpy.app.driver_namespace["extra_linear"] = extra_linear
|
||||
bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x
|
||||
print("Driver setup done...")
|
||||
|
||||
bpy.ops.wm.append(
|
||||
filepath=os.path.join(mapping.blend_path, mapping.inner_path),
|
||||
directory=os.path.join(mapping.blend_path, mapping.inner_path),
|
||||
files=mapping.files)
|
||||
print("Append done...")
|
||||
|
||||
prepare_armature(mapping)
|
||||
print("Armature done...")
|
||||
|
||||
print("Remove junk...")
|
||||
for ob in bpy.data.objects:
|
||||
if ob.name.startswith("cs_"):
|
||||
bpy.data.objects.remove(ob)
|
||||
elif ob.name.startswith("Face") and ob.name != "Face":
|
||||
bpy.data.objects.remove(ob)
|
||||
|
||||
print("Removing original armature and actions...")
|
||||
orig_arm = bpy.data.objects[mapping.armature_name + '_orig']
|
||||
bpy.data.objects.remove(orig_arm)
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("bake_"):
|
||||
act.name = act.name + "-noimp"
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("bake_"):
|
||||
bpy.data.actions.remove(act)
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("bake_"):
|
||||
bpy.data.actions.remove(act)
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH':
|
||||
if not obj.name in mapping.objs and obj.parent is None:
|
||||
if not obj.name.endswith("-noimp"):
|
||||
obj.name = obj.name + "-noimp"
|
||||
name_prefix = os.path.basename(mapping.ogre_scene).replace(".scene", "")
|
||||
for o in bpy.data.objects:
|
||||
if o.type == 'MESH' or o.type == 'ARMATURE':
|
||||
if o.name.startswith(name_prefix):
|
||||
continue
|
||||
o.name = name_prefix + "_" + o.name.strip()
|
||||
for o in bpy.data.meshes:
|
||||
if o.name.startswith(name_prefix):
|
||||
continue
|
||||
o.name = name_prefix + "_" + o.name
|
||||
o.name = o.name.replace("(merged)", "").strip()
|
||||
for m in bpy.data.materials:
|
||||
if m.name.startswith(name_prefix):
|
||||
continue
|
||||
m.name = name_prefix + "_" + m.name
|
||||
m.name = m.name.replace("(merged)", "").strip()
|
||||
anon = 0
|
||||
for m in bpy.data.images:
|
||||
if m.name.startswith(name_prefix) and m.filepath.startswith(name_prefix):
|
||||
continue
|
||||
if m.name == "":
|
||||
m.name = "anonymous_" + str(anon)
|
||||
anon = anon + 1
|
||||
if m.filepath == "":
|
||||
m.filepath = "anonymous_" + str(anon) + ".png"
|
||||
anon = anon + 1
|
||||
if not m.name.startswith(name_prefix):
|
||||
m.name = name_prefix + "_" + m.name
|
||||
m.name = m.name.replace("(merged)", "").strip()
|
||||
if not m.filepath.startswith(name_prefix):
|
||||
m.filepath = m.filepath.replace("(merged)", "_").strip()
|
||||
m.filepath = m.filepath.replace(" ", "_")
|
||||
m.filepath = name_prefix + "_" + os.path.basename(m.filepath)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile))
|
||||
bpy.ops.ogre.export(filepath=mapping.ogre_scene, EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='1')
|
||||
|
||||
# bpy.ops.export_scene.gltf(filepath=mapping.gltf_path.replace(".npcshape", ".gltf").replace(".npc", ".gltf"),
|
||||
# use_selection=False,
|
||||
# check_existing=False,
|
||||
# export_format='GLTF_SEPARATE',
|
||||
# export_texture_dir='textures', export_texcoords=True,
|
||||
# export_normals=True,
|
||||
# export_tangents=True,
|
||||
# export_materials='EXPORT',
|
||||
## export_all_vertex_colors=True,
|
||||
## colors_type='SRGB',
|
||||
## export_vertex_colors=True,
|
||||
# export_colors=True,
|
||||
# use_mesh_edges=False,
|
||||
# use_mesh_vertices=False,
|
||||
# export_cameras=False,
|
||||
# use_visible=False,
|
||||
# use_renderable=False,
|
||||
# export_yup=True,
|
||||
# export_animations=True,
|
||||
# export_force_sampling=True,
|
||||
# export_def_bones=False,
|
||||
# export_current_frame=False,
|
||||
# export_morph=True,
|
||||
# export_morph_animation=False,
|
||||
# export_morph_normal=True,
|
||||
# export_morph_tangent=True,
|
||||
# export_lights=False,
|
||||
# export_skins=True)
|
||||
# shutil.move(mapping.gltf_path.replace(".npcshape", ".gltf").replace(".npc", ".gltf"), mapping.gltf_path)
|
||||
|
||||
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
time.sleep(2)
|
||||
bpy.ops.wm.quit_blender()
|
||||
14
assets/blender/scripts/geometry/tris2quads.py
Normal file
14
assets/blender/scripts/geometry/tris2quads.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import bmesh
|
||||
import bpy
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def tris2quads(obj):
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.tris_convert_to_quads()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
obj.select_set(False)
|
||||
bpy.context.view_layer.objects.active = None
|
||||
194
assets/blender/scripts/import_vrm.py
Normal file
194
assets/blender/scripts/import_vrm.py
Normal file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, time
|
||||
import bpy
|
||||
from math import pi
|
||||
import glob
|
||||
import shutil
|
||||
from mathutils import Vector, Matrix
|
||||
from math import radians, pi
|
||||
if bpy.app.version[0] == 3:
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts/mixamo/3.6")
|
||||
if bpy.app.version[0] == 4:
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts/mixamo/4.3")
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
from vrm import rename
|
||||
if bpy.app.version[0] == 3:
|
||||
from mixamo import mixamo_rig
|
||||
from mixamo.lib.armature import *
|
||||
from settings import VRMDataFemale, VRMDataMale, VRMDataMaleBabyShape, basepath
|
||||
from geometry import tris2quads
|
||||
|
||||
imports = [VRMDataFemale(), VRMDataMale(), VRMDataMaleBabyShape()]
|
||||
|
||||
class mkrig:
|
||||
ik_arms = True
|
||||
ik_legs = True
|
||||
def __init__(self):
|
||||
mixamo_rig._make_rig(self)
|
||||
mixamo_rig.remove_temp_objects()
|
||||
mixamo_rig.clean_scene()
|
||||
def get_anim(filepath, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False):
|
||||
old_objs = set(bpy.context.scene.objects)
|
||||
try:
|
||||
import_armature(filepath, root_bone_name, hip_bone_name, remove_prefix, name_prefix, insert_root, delete_armatures)
|
||||
imported_objects = set(bpy.context.scene.objects) - old_objs
|
||||
if delete_armatures and num_files > 1:
|
||||
deleteArmature(imported_objects)
|
||||
except Exception as e:
|
||||
log.error("[Mixamo Root] ERROR get_all_anims raised %s when processing %s" % (str(e), filepath))
|
||||
bpy.context.scene.frame_start = 0
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
def create_root_bone(anim_obj):
|
||||
bpy.context.view_layer.objects.active = anim_obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
root_bone = anim_obj.data.edit_bones.new("Root")
|
||||
root_bone.tail.y = 0.5
|
||||
anim_obj.data.edit_bones["mixamorig:Hips"].parent = anim_obj.data.edit_bones["Root"]
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
def hips_to_root(anim_obj):
|
||||
for fc in anim_obj.animation_data.action.fcurves:
|
||||
if "mixamorig:Hips" in fc.data_path:
|
||||
if "location" in fc.data_path:
|
||||
print(fc, fc.data_path, fc.array_index)
|
||||
if fc.array_index in [0, 2]:
|
||||
fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root")
|
||||
def hips_to_root(anim_obj):
|
||||
for fc in anim_obj.animation_data.action.fcurves:
|
||||
if "mixamorig:Hips" in fc.data_path:
|
||||
if "location" in fc.data_path:
|
||||
print(fc, fc.data_path, fc.array_index)
|
||||
if fc.array_index in [0, 2]:
|
||||
fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root")
|
||||
def hips_to_root2(anim_obj):
|
||||
for fc in anim_obj.animation_data.action.fcurves:
|
||||
if "Ctrl_Hips" in fc.data_path:
|
||||
if "location" in fc.data_path:
|
||||
print(fc, fc.data_path, fc.array_index)
|
||||
if fc.array_index in [0, 2]:
|
||||
data_path = fc.data_path[:]
|
||||
fcr = anim_obj.animation_data.action.fcurves.new(data_path = data_path.replace("Ctrl_Hips", "Root"), index = fc.array_index)
|
||||
keys = [0] * (2 * len(fc.keyframe_points))
|
||||
fcr.keyframe_points.add(len(fc.keyframe_points))
|
||||
fc.keyframe_points.foreach_get("co", keys)
|
||||
fcr.keyframe_points.foreach_set("co", keys)
|
||||
print("ROOT")
|
||||
|
||||
for imp in imports:
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
for o in bpy.data.objects:
|
||||
bpy.data.objects.remove(o)
|
||||
print(basepath + "/assets/vroid/" + imp.path)
|
||||
result = bpy.ops.import_scene.vrm(filepath=(basepath + "/assets/vroid/" + imp.path))
|
||||
if result != {"FINISHED"}:
|
||||
raise Exception(f"Failed to import vrm: {result}")
|
||||
for o in bpy.data.objects:
|
||||
if o.type == 'MESH':
|
||||
tris2quads.tris2quads(o)
|
||||
armature_count = 0
|
||||
for obj in bpy.data.objects:
|
||||
if (obj.type == "ARMATURE"):
|
||||
armature_count += 1
|
||||
if armature_count > 1:
|
||||
raise Exception("Bad scene data")
|
||||
main_armature = None
|
||||
for obj in bpy.data.objects:
|
||||
if (obj.type == "ARMATURE"):
|
||||
main_armature = obj
|
||||
break
|
||||
if main_armature == None:
|
||||
raise Exception("Bad scene data")
|
||||
print("Renaming...")
|
||||
main_armature.select_set(True)
|
||||
bpy.context.view_layer.objects.active = main_armature
|
||||
rename.rename_bones(main_armature)
|
||||
main_armature.select_set(False)
|
||||
main_armature.name = imp.armature_name
|
||||
main_armature["VRM_IMPORTED_NAME"] = "female"
|
||||
if main_armature.animation_data is None:
|
||||
main_armature.animation_data_create()
|
||||
main_armature.select_set(True)
|
||||
bpy.context.view_layer.objects.active = main_armature
|
||||
mkrig()
|
||||
main_armature.select_set(False)
|
||||
bpy.context.view_layer.objects.active = None
|
||||
|
||||
for anim in imp.mixamo_animations:
|
||||
anim_path = imp.mixamo_animation_path + anim + ".fbx"
|
||||
print("Load BVH..." + anim_path)
|
||||
old_objs = set(bpy.context.scene.objects)
|
||||
bpy.ops.import_scene.fbx(filepath=anim_path, global_scale=imp.fbx_scale)
|
||||
imported_objects = set(bpy.context.scene.objects) - old_objs
|
||||
for anim_obj in imported_objects:
|
||||
if anim_obj == main_armature:
|
||||
continue
|
||||
if "VRM_IMPORTED_NAME" in anim_obj:
|
||||
continue
|
||||
if main_armature == anim_obj or anim_obj.type != "ARMATURE":
|
||||
continue
|
||||
if anim_obj.animation_data == None:
|
||||
print("Bad object: " + anim_obj.name + " " + anim_obj.type)
|
||||
bpy.data.objects.remove(anim_obj)
|
||||
continue
|
||||
if anim_obj.animation_data.action == None:
|
||||
print("Bad object: " + anim_obj.name + " " + anim_obj.type)
|
||||
bpy.data.objects.remove(anim_obj)
|
||||
continue
|
||||
print("importing: " + anim_obj.animation_data.action.name)
|
||||
mixamo_rig._import_anim(anim_obj, main_armature, True)
|
||||
print("===" + main_armature.animation_data.action.name)
|
||||
main_armature.animation_data.action.name = anim
|
||||
hips_to_root2(main_armature)
|
||||
track = main_armature.animation_data.nla_tracks.new()
|
||||
action = main_armature.animation_data.action
|
||||
track.name = action.name
|
||||
track.strips.new(action.name, int(action.frame_range[0]), action)
|
||||
track.mute = True
|
||||
track.lock = True
|
||||
bpy.data.objects.remove(anim_obj)
|
||||
imported_objects.clear()
|
||||
imported_objects = set(bpy.context.scene.objects) - old_objs
|
||||
for anim_obj in imported_objects:
|
||||
bpy.data.objects.remove(anim_obj)
|
||||
|
||||
main_armature.animation_data.action = bpy.data.actions.new(name="default")
|
||||
default_action = main_armature.animation_data.action
|
||||
bpy.context.view_layer.objects.active = main_armature
|
||||
main_armature.select_set(True)
|
||||
layers = enable_all_armature_layers()
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
for b in main_armature.pose.bones:
|
||||
b.location = [0,0,0]
|
||||
b.rotation_euler = [0,0,0]
|
||||
b.rotation_quaternion = [1,0,0,0]
|
||||
b.scale = [1,1,1]
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
for b in main_armature.data.bones:
|
||||
if b.name.find("Ctrl_") < 0 and b.name.find("ctrl_") < 0:
|
||||
continue
|
||||
main_armature.pose.bones[b.name].matrix_basis.identity()
|
||||
main_armature.pose.bones[b.name].keyframe_insert(data_path='location', frame=1)
|
||||
rotation_mode = main_armature.pose.bones[b.name].rotation_mode
|
||||
if rotation_mode == "QUATERNION":
|
||||
main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_quaternion', frame=1)
|
||||
elif rotation_mode == "AXIS_ANGLE":
|
||||
main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_axis_angle', frame=1)
|
||||
else:
|
||||
main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_euler', frame=1)
|
||||
main_armature.pose.bones[b.name].keyframe_insert(data_path='scale', frame=1)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
track = main_armature.animation_data.nla_tracks.new()
|
||||
action = main_armature.animation_data.action
|
||||
track.name = action.name
|
||||
track.strips.new(action.name, int(action.frame_range[0]), action)
|
||||
track.mute = True
|
||||
track.lock = True
|
||||
restore_armature_layers(layers)
|
||||
main_armature.animation_data.action = default_action
|
||||
|
||||
main_armature.select_set(False)
|
||||
bpy.context.view_layer.objects.active = None
|
||||
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/" + imp.outfile))
|
||||
|
||||
14
assets/blender/scripts/install_addons.py
Normal file
14
assets/blender/scripts/install_addons.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import os
|
||||
import bpy
|
||||
path_to_script_dir = os.getcwd() + "/assets/blender/scripts/addons/" + str(bpy.app.version[0]) + "." + str(bpy.app.version[1])
|
||||
file_list = sorted(os.listdir(path_to_script_dir))
|
||||
script_list = [item for item in file_list if item.endswith('.zip')]
|
||||
for file in file_list:
|
||||
path_to_file = os.path.join(path_to_script_dir, file)
|
||||
bpy.ops.preferences.addon_install(overwrite=True, target='DEFAULT', filepath=path_to_file, filter_folder=True, filter_python=False, filter_glob="*.py;*.zip")
|
||||
enableTheseAddons = ["VRM_Addon_for_Blender-release"]
|
||||
for string in enableTheseAddons:
|
||||
name = enableTheseAddons
|
||||
bpy.ops.preferences.addon_enable(module = string)
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
62
assets/blender/scripts/mixamo/3.6/mixamo/__init__.py
Normal file
62
assets/blender/scripts/mixamo/3.6/mixamo/__init__.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
|
||||
|
||||
bl_info = {
|
||||
"name": "Mixamo Rig",
|
||||
"author": "Mixamo",
|
||||
"version": (1, 11, 11),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "3D View > Mixamo> Control Rig",
|
||||
"description": "Generate a control rig from the selected Mixamo Fbx skeleton",
|
||||
"category": "Animation"}
|
||||
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
if "mixamo_rig_prefs" in locals():
|
||||
importlib.reload(mixamo_rig_prefs)
|
||||
if "mixamo_rig" in locals():
|
||||
importlib.reload(mixamo_rig)
|
||||
if "mixamo_rig_functions" in locals():
|
||||
importlib.reload(mixamo_rig_functions)
|
||||
if "utils" in locals():
|
||||
importlib.reload(utils)
|
||||
if "animation" in locals():
|
||||
importlib.reload(animation)
|
||||
|
||||
|
||||
import bpy
|
||||
from . import mixamo_rig_prefs
|
||||
from . import mixamo_rig
|
||||
from . import mixamo_rig_functions
|
||||
from . import utils
|
||||
|
||||
def register():
|
||||
mixamo_rig_prefs.register()
|
||||
mixamo_rig.register()
|
||||
mixamo_rig_functions.register()
|
||||
|
||||
def unregister():
|
||||
mixamo_rig_prefs.unregister()
|
||||
mixamo_rig.unregister()
|
||||
mixamo_rig_functions.unregister()
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
1
assets/blender/scripts/mixamo/3.6/mixamo/define.py
Normal file
1
assets/blender/scripts/mixamo/3.6/mixamo/define.py
Normal file
@@ -0,0 +1 @@
|
||||
from .definitions.naming import *
|
||||
@@ -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"]
|
||||
15
assets/blender/scripts/mixamo/3.6/mixamo/lib/addon.py
Normal file
15
assets/blender/scripts/mixamo/3.6/mixamo/lib/addon.py
Normal file
@@ -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
|
||||
203
assets/blender/scripts/mixamo/3.6/mixamo/lib/animation.py
Normal file
203
assets/blender/scripts/mixamo/3.6/mixamo/lib/animation.py
Normal file
@@ -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)
|
||||
19
assets/blender/scripts/mixamo/3.6/mixamo/lib/armature.py
Normal file
19
assets/blender/scripts/mixamo/3.6/mixamo/lib/armature.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
|
||||
def restore_armature_layers(layers_select):
|
||||
# restore the armature layers visibility
|
||||
for i in range(0, 32):
|
||||
bpy.context.active_object.data.layers[i] = layers_select[i]
|
||||
|
||||
|
||||
def enable_all_armature_layers():
|
||||
# enable all layers
|
||||
# and return the list of each layer visibility
|
||||
_layers = bpy.context.active_object.data.layers
|
||||
layers_select = []
|
||||
for i in range(0, 32):
|
||||
layers_select.append(_layers[i])
|
||||
for i in range(0, 32):
|
||||
bpy.context.active_object.data.layers[i] = True
|
||||
|
||||
return layers_select
|
||||
17
assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_data.py
Normal file
17
assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_data.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import bpy
|
||||
|
||||
def get_data_bone(name):
|
||||
return bpy.context.active_object.data.bones.get(name)
|
||||
|
||||
|
||||
def set_bone_layer(databone, layer_idx, multi=False):
|
||||
if databone == None:
|
||||
return
|
||||
|
||||
databone.layers[layer_idx] = True
|
||||
if multi:
|
||||
return
|
||||
|
||||
for i, lay in enumerate(databone.layers):
|
||||
if i != layer_idx:
|
||||
databone.layers[i] = False
|
||||
19
assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_edit.py
Normal file
19
assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_edit.py
Normal file
@@ -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
|
||||
104
assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_pose.py
Normal file
104
assets/blender/scripts/mixamo/3.6/mixamo/lib/bones_pose.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import bpy
|
||||
from .objects import *
|
||||
from .version import *
|
||||
|
||||
def get_custom_shape_scale(pbone, uniform=True):
|
||||
if blender_version._float >= 300:
|
||||
if uniform:
|
||||
# uniform scale
|
||||
val = 0
|
||||
for i in range(0,3):
|
||||
val += pbone.custom_shape_scale_xyz[i]
|
||||
return val/3
|
||||
# array scale
|
||||
else:
|
||||
return pbone.custom_shape_scale_xyz
|
||||
# pre-Blender 3.0
|
||||
else:
|
||||
return pbone.custom_shape_scale
|
||||
|
||||
|
||||
def get_selected_pbone_name():
|
||||
try:
|
||||
return bpy.context.selected_pose_bones[0].name#.active_pose_bone.name
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
def get_pose_bone(name):
|
||||
return bpy.context.active_object.pose.bones.get(name)
|
||||
|
||||
|
||||
def lock_pbone_transform(pbone, type, list):
|
||||
for i in list:
|
||||
if type == "location":
|
||||
pbone.lock_location[i] = True
|
||||
elif type == "rotation":
|
||||
pbone.lock_rotation[i] = True
|
||||
elif type == "scale":
|
||||
pbone.lock_scale[i] = True
|
||||
|
||||
|
||||
def set_bone_custom_shape(pbone, cs_name):
|
||||
cs = get_object(cs_name)
|
||||
if cs == None:
|
||||
append_cs(cs_name)
|
||||
cs = get_object(cs_name)
|
||||
|
||||
pbone.custom_shape = cs
|
||||
|
||||
|
||||
def set_bone_color_group(obj, pb, grp_name):
|
||||
# mixamo required color
|
||||
orange = (0.969, 0.565, 0.208)
|
||||
orange_light = (0.957, 0.659, 0.416)
|
||||
blue_dark = (0.447, 0.682, 1.0)
|
||||
blue_light = (0.365, 0.851, 1.0)
|
||||
|
||||
# base color
|
||||
green = (0.0, 1.0, 0.0)
|
||||
red = (1.0, 0.0, 0.0)
|
||||
blue = (0.0, 0.9, 1.0)
|
||||
|
||||
grp_color_master = orange_light
|
||||
grp_color_neck = orange_light
|
||||
grp_color_root_master = orange
|
||||
grp_color_head = orange
|
||||
grp_color_body_mid = green
|
||||
grp_color_body_left = blue_dark
|
||||
grp_color_body_right = blue_light
|
||||
|
||||
grp = obj.pose.bone_groups.get(grp_name)
|
||||
if grp == None:
|
||||
grp = obj.pose.bone_groups.new(name=grp_name)
|
||||
grp.color_set = 'CUSTOM'
|
||||
|
||||
grp_color = None
|
||||
if grp_name == "body_mid":
|
||||
grp_color = grp_color_body_mid
|
||||
elif grp_name == "body_left":
|
||||
grp_color = grp_color_body_left
|
||||
elif grp_name == "body_right":
|
||||
grp_color = grp_color_body_right
|
||||
elif grp_name == "master":
|
||||
grp_color = grp_color_master
|
||||
elif grp_name == "neck":
|
||||
grp_color = grp_color_head
|
||||
elif grp_name == "head":
|
||||
grp_color = grp_color_neck
|
||||
elif grp_name == "root_master":
|
||||
grp_color = grp_color_root_master
|
||||
|
||||
# set normal color
|
||||
grp.colors.normal = grp_color
|
||||
|
||||
# set select color/active color
|
||||
for col_idx in range(0,3):
|
||||
grp.colors.select[col_idx] = grp_color[col_idx] + 0.2
|
||||
grp.colors.active[col_idx] = grp_color[col_idx] + 0.4
|
||||
|
||||
pb.bone_group = grp
|
||||
|
||||
|
||||
def update_transform():
|
||||
bpy.ops.transform.rotate(value=0, orient_axis='Z', orient_type='VIEW', orient_matrix=((0.0, 0.0, 0), (0, 0.0, 0.0), (0.0, 0.0, 0.0)), orient_matrix_type='VIEW', mirror=False)
|
||||
19
assets/blender/scripts/mixamo/3.6/mixamo/lib/constraints.py
Normal file
19
assets/blender/scripts/mixamo/3.6/mixamo/lib/constraints.py
Normal file
@@ -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()
|
||||
10
assets/blender/scripts/mixamo/3.6/mixamo/lib/context.py
Normal file
10
assets/blender/scripts/mixamo/3.6/mixamo/lib/context.py
Normal file
@@ -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)
|
||||
BIN
assets/blender/scripts/mixamo/3.6/mixamo/lib/cs.blend
(Stored with Git LFS)
Normal file
BIN
assets/blender/scripts/mixamo/3.6/mixamo/lib/cs.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
60
assets/blender/scripts/mixamo/3.6/mixamo/lib/custom_props.py
Normal file
60
assets/blender/scripts/mixamo/3.6/mixamo/lib/custom_props.py
Normal file
@@ -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)
|
||||
23
assets/blender/scripts/mixamo/3.6/mixamo/lib/drivers.py
Normal file
23
assets/blender/scripts/mixamo/3.6/mixamo/lib/drivers.py
Normal file
@@ -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
|
||||
151
assets/blender/scripts/mixamo/3.6/mixamo/lib/maths_geo.py
Normal file
151
assets/blender/scripts/mixamo/3.6/mixamo/lib/maths_geo.py
Normal file
@@ -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]))
|
||||
|
||||
39
assets/blender/scripts/mixamo/3.6/mixamo/lib/mixamo.py
Normal file
39
assets/blender/scripts/mixamo/3.6/mixamo/lib/mixamo.py
Normal file
@@ -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"
|
||||
81
assets/blender/scripts/mixamo/3.6/mixamo/lib/objects.py
Normal file
81
assets/blender/scripts/mixamo/3.6/mixamo/lib/objects.py
Normal file
@@ -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
|
||||
|
||||
35
assets/blender/scripts/mixamo/3.6/mixamo/lib/version.py
Normal file
35
assets/blender/scripts/mixamo/3.6/mixamo/lib/version.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import bpy
|
||||
|
||||
class ARP_blender_version:
|
||||
_string = bpy.app.version_string
|
||||
blender_v = bpy.app.version
|
||||
_float = blender_v[0]*100+blender_v[1]+blender_v[2]*0.01
|
||||
_char = bpy.app.version_char
|
||||
|
||||
blender_version = ARP_blender_version()
|
||||
|
||||
|
||||
def convert_drivers_cs_to_xyz(armature):
|
||||
# Blender 3.0 requires Vector3 custom_shape_scale values
|
||||
# convert single uniform driver to vector3 array drivers
|
||||
drivers_armature = [i for i in armature.animation_data.drivers]
|
||||
|
||||
for dr in drivers_armature:
|
||||
if 'custom_shape_scale' in dr.data_path:
|
||||
if not 'custom_shape_scale_xyz' in dr.data_path:
|
||||
for i in range(0, 3):
|
||||
new_dr = armature.animation_data.drivers.from_existing(src_driver=dr)
|
||||
new_dr.data_path = new_dr.data_path.replace('custom_shape_scale', 'custom_shape_scale_xyz')
|
||||
new_dr.array_index = i
|
||||
new_dr.driver.expression += ''# update hack
|
||||
|
||||
armature.driver_remove(dr.data_path, dr.array_index)
|
||||
|
||||
print("Converted custom shape scale drivers to xyz")
|
||||
|
||||
|
||||
def get_custom_shape_scale_prop_name():
|
||||
if blender_version._float >= 300:
|
||||
return 'custom_shape_scale_xyz'
|
||||
else:
|
||||
return 'custom_shape_scale'
|
||||
2764
assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig.py
Normal file
2764
assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig.py
Normal file
File diff suppressed because it is too large
Load Diff
1116
assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_functions.py
Normal file
1116
assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_functions.py
Normal file
File diff suppressed because it is too large
Load Diff
31
assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_prefs.py
Normal file
31
assets/blender/scripts/mixamo/3.6/mixamo/mixamo_rig_prefs.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import bpy
|
||||
|
||||
def update_all_tab_names(self, context):
|
||||
try:
|
||||
from . import mixamo_rig
|
||||
mixamo_rig.update_mixamo_tab()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class MR_MT_addon_preferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
mixamo_tab_name : bpy.props.StringProperty(name="Interface Tab", description="Name of the tab to display the interface in", default="Mixamo", update=update_all_tab_names)
|
||||
|
||||
def draw(self, context):
|
||||
col = self.layout.column(align=True)
|
||||
col.prop(self, "mixamo_tab_name", text="Interface Tab")
|
||||
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
|
||||
try:
|
||||
register_class(MR_MT_addon_preferences)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
unregister_class(MR_MT_addon_preferences)
|
||||
18
assets/blender/scripts/mixamo/3.6/mixamo/utils.py
Normal file
18
assets/blender/scripts/mixamo/3.6/mixamo/utils.py
Normal file
@@ -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 *
|
||||
1
assets/blender/scripts/mixamo/4.3/mixamo/define.py
Normal file
1
assets/blender/scripts/mixamo/4.3/mixamo/define.py
Normal file
@@ -0,0 +1 @@
|
||||
from .definitions.naming import *
|
||||
@@ -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"]
|
||||
15
assets/blender/scripts/mixamo/4.3/mixamo/lib/addon.py
Normal file
15
assets/blender/scripts/mixamo/4.3/mixamo/lib/addon.py
Normal file
@@ -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
|
||||
203
assets/blender/scripts/mixamo/4.3/mixamo/lib/animation.py
Normal file
203
assets/blender/scripts/mixamo/4.3/mixamo/lib/animation.py
Normal file
@@ -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)
|
||||
29
assets/blender/scripts/mixamo/4.3/mixamo/lib/armature.py
Normal file
29
assets/blender/scripts/mixamo/4.3/mixamo/lib/armature.py
Normal file
@@ -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
|
||||
36
assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_data.py
Normal file
36
assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_data.py
Normal file
@@ -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
|
||||
19
assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_edit.py
Normal file
19
assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_edit.py
Normal file
@@ -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
|
||||
113
assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_pose.py
Normal file
113
assets/blender/scripts/mixamo/4.3/mixamo/lib/bones_pose.py
Normal file
@@ -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)
|
||||
19
assets/blender/scripts/mixamo/4.3/mixamo/lib/constraints.py
Normal file
19
assets/blender/scripts/mixamo/4.3/mixamo/lib/constraints.py
Normal file
@@ -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()
|
||||
10
assets/blender/scripts/mixamo/4.3/mixamo/lib/context.py
Normal file
10
assets/blender/scripts/mixamo/4.3/mixamo/lib/context.py
Normal file
@@ -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)
|
||||
BIN
assets/blender/scripts/mixamo/4.3/mixamo/lib/cs.blend
(Stored with Git LFS)
Normal file
BIN
assets/blender/scripts/mixamo/4.3/mixamo/lib/cs.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
60
assets/blender/scripts/mixamo/4.3/mixamo/lib/custom_props.py
Normal file
60
assets/blender/scripts/mixamo/4.3/mixamo/lib/custom_props.py
Normal file
@@ -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)
|
||||
23
assets/blender/scripts/mixamo/4.3/mixamo/lib/drivers.py
Normal file
23
assets/blender/scripts/mixamo/4.3/mixamo/lib/drivers.py
Normal file
@@ -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
|
||||
151
assets/blender/scripts/mixamo/4.3/mixamo/lib/maths_geo.py
Normal file
151
assets/blender/scripts/mixamo/4.3/mixamo/lib/maths_geo.py
Normal file
@@ -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]))
|
||||
|
||||
39
assets/blender/scripts/mixamo/4.3/mixamo/lib/mixamo.py
Normal file
39
assets/blender/scripts/mixamo/4.3/mixamo/lib/mixamo.py
Normal file
@@ -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"
|
||||
81
assets/blender/scripts/mixamo/4.3/mixamo/lib/objects.py
Normal file
81
assets/blender/scripts/mixamo/4.3/mixamo/lib/objects.py
Normal file
@@ -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
|
||||
|
||||
35
assets/blender/scripts/mixamo/4.3/mixamo/lib/version.py
Normal file
35
assets/blender/scripts/mixamo/4.3/mixamo/lib/version.py
Normal file
@@ -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'
|
||||
2835
assets/blender/scripts/mixamo/4.3/mixamo/mixamo_rig.py
Normal file
2835
assets/blender/scripts/mixamo/4.3/mixamo/mixamo_rig.py
Normal file
File diff suppressed because it is too large
Load Diff
18
assets/blender/scripts/mixamo/4.3/mixamo/utils.py
Normal file
18
assets/blender/scripts/mixamo/4.3/mixamo/utils.py
Normal file
@@ -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 *
|
||||
459
assets/blender/scripts/mixamo/import_mixamo_root_motion.py
Normal file
459
assets/blender/scripts/mixamo/import_mixamo_root_motion.py
Normal file
@@ -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 <Armature.00*>, 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 <mixamorig:>",
|
||||
default=True,
|
||||
) # type: ignore
|
||||
|
||||
is_delete_armature: BoolProperty(
|
||||
name="Remove Armature",
|
||||
description="Remove object <Armature.00*>",
|
||||
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 <X Location> to the Root Bone",
|
||||
default=True,
|
||||
) # type: ignore
|
||||
|
||||
bake_y: BoolProperty(
|
||||
name="Y",
|
||||
description="Baking <Y Location> to the Root Bone",
|
||||
default=True,
|
||||
) # type: ignore
|
||||
|
||||
bake_z: BoolProperty(
|
||||
name="Z",
|
||||
description="Baking <Z Location> 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()
|
||||
387
assets/blender/scripts/mixamo/mixamoroot.py
Normal file
387
assets/blender/scripts/mixamo/mixamoroot.py
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Note that Johngoss725's original contributions were published under a
|
||||
Creative Commons 1.0 Universal License (CC0-1.0) located at
|
||||
<https://github.com/Johngoss725/Mixamo-To-Godot>.
|
||||
'''
|
||||
|
||||
# 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.")
|
||||
59
assets/blender/scripts/separate_materials.py
Normal file
59
assets/blender/scripts/separate_materials.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import bpy, sys, os
|
||||
import bmesh
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
from settings import basepath
|
||||
|
||||
def split_mesh_by_material(obj):
|
||||
"""Splits a mesh object into separate objects based on material assignments."""
|
||||
|
||||
if obj.type != 'MESH':
|
||||
return
|
||||
|
||||
mesh = obj.data
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
|
||||
material_indices = {}
|
||||
for face in bm.faces:
|
||||
material_index = face.material_index
|
||||
if material_index not in material_indices:
|
||||
material_indices[material_index] = []
|
||||
material_indices[material_index].append(face)
|
||||
|
||||
for material_index, faces in material_indices.items():
|
||||
new_bm = bmesh.new()
|
||||
for face in faces:
|
||||
new_bm.faces.new([new_bm.verts.new(v.co) for v in face.verts])
|
||||
|
||||
new_mesh = bpy.data.meshes.new(f"{obj.name}_mat_{material_index}")
|
||||
new_bm.to_mesh(new_mesh)
|
||||
new_bm.free()
|
||||
|
||||
new_obj = bpy.data.objects.new(f"{obj.name}_mat_{material_index}", new_mesh)
|
||||
new_obj.data.materials.append(mesh.materials[material_index])
|
||||
bpy.context.collection.objects.link(new_obj)
|
||||
|
||||
def append_object(filepath, object_names):
|
||||
"""Appends an object from a specified Blender file.
|
||||
|
||||
Args:
|
||||
filepath (str): The full path to the source .blend file.
|
||||
object_name (str): The name of the object to append.
|
||||
"""
|
||||
with bpy.data.libraries.load(filepath) as (data_from, data_to):
|
||||
data_to.objects = object_names
|
||||
|
||||
if data_to.objects:
|
||||
for obj in data_to.objects:
|
||||
bpy.context.collection.objects.link(obj)
|
||||
else:
|
||||
print(f"Object '{object_name}' not found in '{filepath}'.")
|
||||
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
for o in bpy.data.objects:
|
||||
bpy.data.objects.remove(o)
|
||||
|
||||
append_object(basepath + "/assets/blender/" + "vrm-vroid-normal-male.blend", ["Body", "male"])
|
||||
|
||||
bpy.ops.wm.save_as_mainfile(filepath=basepath + "/" + "tmp.blend")
|
||||
|
||||
83
assets/blender/scripts/settings.py
Normal file
83
assets/blender/scripts/settings.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import os, sys
|
||||
basepath = os.getcwd()
|
||||
class VRMDataFemale:
|
||||
path = "jane2-dress.vrm"
|
||||
outfile = "vrm-vroid-normal-female.blend"
|
||||
editfile = "modelling-vroid-normal-female.blend"
|
||||
armature_name = "female"
|
||||
mixamo_animation_path = basepath + "/assets/blender/mixamo/female/"
|
||||
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn"]
|
||||
fbx_scale = 0.89
|
||||
class VRMDataMale:
|
||||
path = "buch1.vrm"
|
||||
outfile = "vrm-vroid-normal-male.blend"
|
||||
editfile = "modelling-vroid-normal-male.blend"
|
||||
armature_name = "male"
|
||||
mixamo_animation_path = basepath + "/assets/blender/mixamo/male/"
|
||||
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn"]
|
||||
fbx_scale = 1.0
|
||||
class VRMDataMaleBabyShape:
|
||||
path = "buch1-chibi.vrm"
|
||||
outfile = "shapes/male/vrm-vroid-normal-male-chibi.blend"
|
||||
editfile = "modelling-shape-male-chibi.blend"
|
||||
armature_name = "male"
|
||||
mixamo_animation_path = basepath + "/assets/blender/mixamo/male/"
|
||||
mixamo_animations = []
|
||||
fbx_scale = 1.0
|
||||
|
||||
class ExportMappingFemale:
|
||||
blend_path = "assets/blender/" + "vrm-vroid-normal-female.blend"
|
||||
gltf_path = "assets/blender/" + "characters/female/vroid-normal-female.gltf"
|
||||
ogre_scene = "characters/female/vroid-normal-female.scene"
|
||||
inner_path = "Object"
|
||||
objs = ["female", "Body", "Hair", "Face"]
|
||||
armature_name = "female"
|
||||
outfile = "tmp-female.blend"
|
||||
default_action = 'default'
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingMale:
|
||||
blend_path = "assets/blender/" + "vrm-vroid-normal-male.blend"
|
||||
gltf_path = "assets/blender/" + "characters/male/vroid-normal-male.gltf"
|
||||
ogre_scene = "characters/male/vroid-normal-male.scene"
|
||||
inner_path = "Object"
|
||||
objs = ["male", "Body", "Hair", "Face"]
|
||||
armature_name = "male"
|
||||
outfile = "tmp-male.blend"
|
||||
default_action = 'default'
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingMaleEdited:
|
||||
blend_path = "assets/blender/" + "edited-normal-male.blend"
|
||||
gltf_path = "characters/male/normal-male.gltf"
|
||||
ogre_scene = "characters/male/vroid-normal-male.scene"
|
||||
inner_path = "Object"
|
||||
objs = ["male", "Body", "Hair", "Face", "BackHair", "Tops", "Bottoms", "Shoes", "Accessory"]
|
||||
armature_name = "male"
|
||||
outfile = "tmp-male.blend"
|
||||
default_action = 'default'
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingMaleBabyShape:
|
||||
blend_path = "assets/blender/shapes/male/" + "vrm-vroid-normal-male-chibi.blend"
|
||||
gltf_path = "assets/blender/" + "characters/shapes/male/chibi/vroid-normal-male-chibi.gltf"
|
||||
ogre_scene = "characters/shapes/male/chibi/vroid-normal-male-chibi.scene"
|
||||
inner_path = "Object"
|
||||
objs = ["male", "Body", "Hair", "Face"]
|
||||
armature_name = "male"
|
||||
outfile = "tmp-male-chibi.blend"
|
||||
default_action = 'default'
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
7
assets/blender/scripts/test.py
Normal file
7
assets/blender/scripts/test.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import os, sys, time
|
||||
import bpy
|
||||
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
|
||||
help(bpy.ops.ogre.export)
|
||||
|
||||
188
assets/blender/scripts/vrm/VroidMixamoRename.py
Normal file
188
assets/blender/scripts/vrm/VroidMixamoRename.py
Normal file
@@ -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()
|
||||
3
assets/blender/scripts/vrm/__init__.py
Normal file
3
assets/blender/scripts/vrm/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# empty
|
||||
|
||||
135
assets/blender/scripts/vrm/rename.py
Normal file
135
assets/blender/scripts/vrm/rename.py
Normal file
@@ -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'}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user