Mixamo2 / utils.py
jasongzy's picture
🐞 fix: misc
8080e63
raw
history blame
10.1 kB
import math
import os
import sys
import bpy
import numpy as np
from bpy.types import Action, Armature, Mesh, Object
# isort: split
import bmesh
import mathutils
class HiddenPrints:
def __init__(self, enable=True, suppress_err=False):
self.enable = enable
self.suppress_err = suppress_err
def __enter__(self):
if not self.enable:
return
self._original_stdout = sys.stdout
sys.stdout = open(os.devnull, "w")
if self.suppress_err:
self._original_stderr = sys.stderr
sys.stderr = open(os.devnull, "w")
def __exit__(self, exc_type, exc_val, exc_tb):
if not self.enable:
return
sys.stdout.close()
sys.stdout = self._original_stdout
if self.suppress_err:
sys.stderr.close()
sys.stderr = self._original_stderr
USE_WORLD_COORDINATES = False
class Mode:
def __init__(self, mode_name="EDIT", active_obj: Object = None):
self.mode = mode_name
self.active = active_obj
self.pre_active = None
self.pre_mode = "OBJECT"
def __enter__(self):
self.pre_active = bpy.context.view_layer.objects.active
if self.pre_active is not None:
self.pre_mode = bpy.context.object.mode
bpy.context.view_layer.objects.active = self.active
bpy.ops.object.mode_set(mode=self.mode)
return self.active
def __exit__(self, exc_type, exc_val, exc_tb):
bpy.ops.object.mode_set(mode=self.pre_mode)
bpy.context.view_layer.objects.active = self.pre_active
def reset():
bpy.ops.wm.read_factory_settings(use_empty=True)
def update():
bpy.context.view_layer.update()
bpy.context.scene.update_tag()
for obj in bpy.context.scene.objects:
# obj.hide_render = obj.hide_render
obj.update_tag()
def remove_all(delete_actions=True):
for obj in bpy.data.objects.values():
bpy.data.objects.remove(obj, do_unlink=True)
bpy.ops.outliner.orphans_purge(do_recursive=True)
if delete_actions:
for action in bpy.data.actions:
bpy.data.actions.remove(action, do_unlink=True)
def remove_empty():
childless_empties = [e for e in bpy.data.objects if e.type.startswith("EMPTY") and not e.children]
bpy.data.batch_remove(childless_empties)
def remove_collection(coll_name: str):
if coll_name not in bpy.data.collections:
return
coll = bpy.data.collections[coll_name]
for c in coll.children:
remove_collection(c)
bpy.data.collections.remove(coll, do_unlink=True)
def load_file(filepath: str, *args, **kwargs) -> "list[Object]":
old_objs = set(bpy.context.scene.objects)
if filepath.endswith(".glb"):
bpy.ops.import_scene.gltf(filepath=filepath, *args, **kwargs)
elif filepath.endswith(".fbx"):
bpy.ops.import_scene.fbx(filepath=filepath, *args, **kwargs)
elif filepath.endswith(".obj"):
bpy.ops.wm.obj_import(filepath=filepath, *args, **kwargs)
elif filepath.endswith(".ply"):
bpy.ops.wm.ply_import(filepath=filepath, *args, **kwargs)
else:
raise RuntimeError(f"Invalid input file: {filepath}")
imported_objs = set(bpy.context.scene.objects) - old_objs
imported_objs = sorted(imported_objs, key=lambda x: x.name)
print("Imported:", imported_objs)
return imported_objs
def select_all():
bpy.ops.object.select_all(action="SELECT")
def deselect():
bpy.ops.object.select_all(action="DESELECT")
def select_objs(obj_list: "list[Object]" = None, deselect_first=False):
if not obj_list:
obj_list = bpy.context.scene.objects
if deselect_first:
deselect()
for obj in obj_list:
obj.select_set(True)
def select_mesh(obj_list: "list[Object]" = None, all=True, deselect_first=False):
if not obj_list:
obj_list = bpy.context.scene.objects
if deselect_first:
deselect()
for obj in obj_list:
if obj.type == "MESH":
if all:
obj.select_set(True)
else:
break
class Select:
"""
Deselecting before and after selecting the specified objects.
"""
def __init__(self, objs: "Object | list[Object]" = None):
self.objs = (objs,) if isinstance(objs, Object) else objs
self.objs: "tuple[Object]" = tuple(self.objs)
def __enter__(self):
select_objs(self.objs, deselect_first=True)
return self.objs
def __exit__(self, exc_type, exc_val, exc_tb):
deselect()
def get_type_objs(obj_list: "list[Object]" = None, type="MESH", sort=True) -> "list[Object]":
if not obj_list:
obj_list = bpy.context.scene.objects
type_obj_list = [obj for obj in obj_list if obj.type == type]
if sort:
type_obj_list = sorted(type_obj_list, key=lambda x: x.name)
return type_obj_list
def get_all_mesh_obj(obj_list: "list[Object]" = None):
return get_type_objs(obj_list, "MESH")
def get_all_armature_obj(obj_list: "list[Object]" = None):
return get_type_objs(obj_list, "ARMATURE")
def get_armature_obj(obj_list: "list[Object]" = None) -> Object:
if not obj_list:
obj_list = bpy.context.scene.objects
for obj in obj_list:
if obj.type == "ARMATURE":
return obj
def get_rest_bones(armature_obj: Object):
if armature_obj is None:
return None, None, None
rest_bones = []
rest_bones_tail = []
bones_idx_dict: "dict[str, int]" = {}
armature_data: Armature = armature_obj.data
for i, bone in enumerate(armature_data.bones):
pos = bone.head_local
pos_tail = bone.tail_local
if USE_WORLD_COORDINATES:
pos = armature_obj.matrix_world @ pos
pos_tail = armature_obj.matrix_world @ pos_tail
rest_bones.append(pos)
rest_bones_tail.append(pos_tail)
bones_idx_dict[bone.name] = i
rest_bones = np.stack(rest_bones, axis=0)
rest_bones_tail = np.stack(rest_bones_tail, axis=0)
return rest_bones, rest_bones_tail, bones_idx_dict
def transfer_weights(source_bone_name: str, target_bone_name: str, mesh_obj_list: "list[Object]"):
if isinstance(mesh_obj_list, Object):
mesh_obj_list = [mesh_obj_list]
for obj in mesh_obj_list:
source_group = obj.vertex_groups.get(source_bone_name)
if source_group is None:
return
source_i = source_group.index
target_group = obj.vertex_groups.get(target_bone_name)
if target_group is None:
target_group = obj.vertex_groups.new(name=target_bone_name)
for v in obj.data.vertices:
for g in v.groups:
if g.group == source_i:
target_group.add((v.index,), g.weight, "ADD")
obj.vertex_groups.remove(source_group)
def remove_empty_vgroups(mesh_obj_list: "list[Object]"):
if isinstance(mesh_obj_list, Object):
mesh_obj_list = [mesh_obj_list]
for obj in mesh_obj_list:
vertex_groups = obj.vertex_groups
groups = {r: None for r in range(len(vertex_groups))}
for vert in obj.data.vertices:
for vg in vert.groups:
i = vg.group
if i in groups:
del groups[i]
lis = list(groups)
lis.sort(reverse=True)
for i in lis:
vertex_groups.remove(vertex_groups[i])
def set_action(armature_obj: Object, action: Action):
if not armature_obj.animation_data:
armature_obj.animation_data_create()
armature_obj.animation_data.action = action
return armature_obj
def mesh_quads2tris(obj_list: "list[Object]" = None):
if not obj_list:
obj_list = bpy.context.scene.objects
for obj in obj_list:
if obj.type == "MESH":
with Mode("EDIT", obj):
bpy.ops.mesh.quads_convert_to_tris(quad_method="BEAUTY", ngon_method="BEAUTY")
def get_enabled_addons() -> "list[str]":
return [x.module for x in bpy.context.preferences.addons]
def enable_arp(armature_obj: Object, addon_path=os.path.join(os.path.dirname(__file__), "auto_rig_pro")):
import sys
assert os.path.isfile(os.path.join(addon_path, "__init__.py")), "Auto-Rig Pro not found"
dirname, addon_name = os.path.split(addon_path)
# if addon_name in get_enabled_addons():
# return
sys.path.insert(0, dirname)
with Mode("POSE", armature_obj):
# import addon_utils
# addon_utils.enable(addon_name)
bpy.ops.preferences.addon_enable(module=addon_name)
def retarget(source_armature: Object, target_armature: Object, inplace=False):
enable_arp(target_armature)
scn = bpy.context.scene
scn.source_rig = source_armature.name
if inplace:
scn.arp_retarget_in_place = True
scn.target_rig = target_armature.name
bpy.ops.arp.auto_scale()
bpy.ops.arp.build_bones_list()
hips = scn.bones_map_v2["mixamorig:Hips"]
scn.bones_map_index = list(scn.bones_map_v2).index(hips)
hips.set_as_root = True
bpy.ops.arp.retarget()
return target_armature
def load_mixamo_anim(char_file: str, anim_file: str, do_retarget=False, inplace=False, to_tris=False):
char_objs = load_file(char_file) if isinstance(char_file, str) else char_file
char_armature = get_armature_obj(char_objs)
anim_objs = load_file(anim_file)
anim_armature = get_armature_obj(anim_objs)
print(anim_armature)
print(anim_armature.animation_data)
assert anim_armature.animation_data is not None and len(bpy.data.actions) > 0, f"Animation not found in {anim_file}"
set_action(char_armature, anim_armature.animation_data.action)
if do_retarget:
retarget(anim_armature, char_armature, inplace=inplace)
for action in bpy.data.actions:
if action is not char_armature.animation_data.action:
bpy.data.actions.remove(action, do_unlink=True)
for obj in anim_objs:
bpy.data.objects.remove(obj, do_unlink=True)
if to_tris:
mesh_quads2tris(char_objs)
return char_objs