Added blender scripts
This commit is contained in:
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.")
|
||||
Reference in New Issue
Block a user