✨ feat: load from pre-animated files
Browse files- .gitmodules +3 -0
- apply_animation.py +38 -0
- auto_rig_pro +1 -0
- utils.py +65 -0
.gitmodules
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[submodule "auto_rig_pro"]
|
| 2 |
+
path = auto_rig_pro
|
| 3 |
+
url = https://github.com/jasongzy/auto_rig_pro
|
apply_animation.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from glob import glob
|
| 3 |
+
|
| 4 |
+
from tqdm import tqdm
|
| 5 |
+
|
| 6 |
+
from utils import HiddenPrints, bpy, load_mixamo_anim, remove_all, reset, select_objs, update
|
| 7 |
+
|
| 8 |
+
if __name__ == "__main__":
|
| 9 |
+
character_dir = "./character_vroid_refined"
|
| 10 |
+
animation_dir = "./animation"
|
| 11 |
+
output_dir = "./animated_vroid"
|
| 12 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 13 |
+
|
| 14 |
+
reset()
|
| 15 |
+
character_list = sorted(glob(os.path.join(character_dir, "*.fbx")))
|
| 16 |
+
assert character_list
|
| 17 |
+
animation_list = sorted(glob(os.path.join(animation_dir, "*.fbx")))
|
| 18 |
+
assert animation_list
|
| 19 |
+
for char_file in tqdm(character_list, dynamic_ncols=True, desc="Character"):
|
| 20 |
+
for anim_file in tqdm(animation_list, dynamic_ncols=True, leave=False, desc="Animation"):
|
| 21 |
+
with HiddenPrints():
|
| 22 |
+
remove_all()
|
| 23 |
+
objs = load_mixamo_anim(char_file, anim_file, do_retarget=True, inplace=False)
|
| 24 |
+
update()
|
| 25 |
+
get_base_name = lambda s: os.path.splitext(os.path.basename(s))[0]
|
| 26 |
+
output_filename = f"{get_base_name(char_file)}-{get_base_name(anim_file)}"
|
| 27 |
+
# bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_dir, f"{output_filename}.blend"))
|
| 28 |
+
select_objs(objs, deselect_first=True)
|
| 29 |
+
bpy.ops.export_scene.fbx(
|
| 30 |
+
filepath=os.path.join(output_dir, f"{output_filename}.fbx"),
|
| 31 |
+
check_existing=False,
|
| 32 |
+
use_selection=True,
|
| 33 |
+
use_triangles=True,
|
| 34 |
+
add_leaf_bones=False,
|
| 35 |
+
bake_anim=True,
|
| 36 |
+
# path_mode="COPY",
|
| 37 |
+
# embed_textures=True,
|
| 38 |
+
)
|
auto_rig_pro
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Subproject commit 614be17f80cda54f58f378b7bd28f4b4c1ba4ec5
|
utils.py
CHANGED
|
@@ -242,6 +242,13 @@ def remove_empty_vgroups(mesh_obj_list: "list[Object]"):
|
|
| 242 |
vertex_groups.remove(vertex_groups[i])
|
| 243 |
|
| 244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
def mesh_quads2tris(obj_list: "list[Object]" = None):
|
| 246 |
if not obj_list:
|
| 247 |
obj_list = bpy.context.scene.objects
|
|
@@ -249,3 +256,61 @@ def mesh_quads2tris(obj_list: "list[Object]" = None):
|
|
| 249 |
if obj.type == "MESH":
|
| 250 |
with Mode("EDIT", obj):
|
| 251 |
bpy.ops.mesh.quads_convert_to_tris(quad_method="BEAUTY", ngon_method="BEAUTY")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
vertex_groups.remove(vertex_groups[i])
|
| 243 |
|
| 244 |
|
| 245 |
+
def set_action(armature_obj: Object, action: Action):
|
| 246 |
+
if not armature_obj.animation_data:
|
| 247 |
+
armature_obj.animation_data_create()
|
| 248 |
+
armature_obj.animation_data.action = action
|
| 249 |
+
return armature_obj
|
| 250 |
+
|
| 251 |
+
|
| 252 |
def mesh_quads2tris(obj_list: "list[Object]" = None):
|
| 253 |
if not obj_list:
|
| 254 |
obj_list = bpy.context.scene.objects
|
|
|
|
| 256 |
if obj.type == "MESH":
|
| 257 |
with Mode("EDIT", obj):
|
| 258 |
bpy.ops.mesh.quads_convert_to_tris(quad_method="BEAUTY", ngon_method="BEAUTY")
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def get_enabled_addons() -> "list[str]":
|
| 262 |
+
return [x.module for x in bpy.context.preferences.addons]
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def enable_arp(armature_obj: Object, addon_path=os.path.join(os.path.dirname(__file__), "auto_rig_pro")):
|
| 266 |
+
import sys
|
| 267 |
+
|
| 268 |
+
assert os.path.isfile(os.path.join(addon_path, "__init__.py")), "Auto-Rig Pro not found"
|
| 269 |
+
dirname, addon_name = os.path.split(addon_path)
|
| 270 |
+
if addon_name in get_enabled_addons():
|
| 271 |
+
return
|
| 272 |
+
sys.path.insert(0, dirname)
|
| 273 |
+
with Mode("POSE", armature_obj):
|
| 274 |
+
# import addon_utils
|
| 275 |
+
# addon_utils.enable(addon_name)
|
| 276 |
+
bpy.ops.preferences.addon_enable(module=addon_name)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def retarget(source_armature: Object, target_armature: Object, inplace=False):
|
| 280 |
+
enable_arp(target_armature)
|
| 281 |
+
scn = bpy.context.scene
|
| 282 |
+
scn.source_rig = source_armature.name
|
| 283 |
+
if inplace:
|
| 284 |
+
scn.arp_retarget_in_place = True
|
| 285 |
+
scn.target_rig = target_armature.name
|
| 286 |
+
bpy.ops.arp.auto_scale()
|
| 287 |
+
bpy.ops.arp.build_bones_list()
|
| 288 |
+
hips = scn.bones_map_v2["mixamorig:Hips"]
|
| 289 |
+
scn.bones_map_index = list(scn.bones_map_v2).index(hips)
|
| 290 |
+
hips.set_as_root = True
|
| 291 |
+
bpy.ops.arp.retarget()
|
| 292 |
+
return target_armature
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def load_mixamo_anim(char_file: str, anim_file: str, do_retarget=False, inplace=False, to_tris=False):
|
| 296 |
+
char_objs = load_file(char_file) if isinstance(char_file, str) else char_file
|
| 297 |
+
char_armature = get_armature_obj(char_objs)
|
| 298 |
+
|
| 299 |
+
anim_objs = load_file(anim_file)
|
| 300 |
+
anim_armature = get_armature_obj(anim_objs)
|
| 301 |
+
print(anim_armature)
|
| 302 |
+
print(anim_armature.animation_data)
|
| 303 |
+
assert anim_armature.animation_data is not None and len(bpy.data.actions) > 0, f"Animation not found in {anim_file}"
|
| 304 |
+
|
| 305 |
+
set_action(char_armature, anim_armature.animation_data.action)
|
| 306 |
+
if do_retarget:
|
| 307 |
+
retarget(anim_armature, char_armature, inplace=inplace)
|
| 308 |
+
for action in bpy.data.actions:
|
| 309 |
+
if action is not char_armature.animation_data.action:
|
| 310 |
+
bpy.data.actions.remove(action, do_unlink=True)
|
| 311 |
+
for obj in anim_objs:
|
| 312 |
+
bpy.data.objects.remove(obj, do_unlink=True)
|
| 313 |
+
|
| 314 |
+
if to_tris:
|
| 315 |
+
mesh_quads2tris(char_objs)
|
| 316 |
+
return char_objs
|