Added blender scripts

This commit is contained in:
2025-01-28 11:30:59 +03:00
parent d34369512b
commit 9107ccbaa3
27 changed files with 5759 additions and 0 deletions

View File

@@ -0,0 +1 @@
from .definitions.naming import *

View File

@@ -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"]

View 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()

View 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

View 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)

View 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

View 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

View 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

View 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)

View 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()

View 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)

Binary file not shown.

View 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)

View 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

View 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]))

View 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"

View 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

View 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'

File diff suppressed because it is too large Load Diff

View 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.")

View 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 *