Files
streaming_world/assets/blender/scripts/mixamo/mixamoroot.py
2025-01-28 11:30:59 +03:00

388 lines
16 KiB
Python

# -*- 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.")