ff23d23cfc
I decided to go through each function and UI section one by one, improving and overhauling things. Each function and section is going to be fully tested and not rushed out. This is the best way to catch things, but also include the code base as much as possible.
160 lines
6.1 KiB
Python
160 lines
6.1 KiB
Python
import bpy
|
|
import numpy as np
|
|
from bpy.types import Context, Object
|
|
from typing import Optional, Tuple, List, Set
|
|
from ..core.translations import t
|
|
from ..core.dictionaries import bone_names
|
|
|
|
def get_active_armature(context: bpy.types.Context) -> Optional[bpy.types.Object]:
|
|
"""Get the currently selected armature from Avatar Toolkit properties"""
|
|
armature_name = context.scene.avatar_toolkit.active_armature
|
|
if armature_name and armature_name != 'NONE':
|
|
return bpy.data.objects.get(armature_name)
|
|
return None
|
|
|
|
def set_active_armature(context: bpy.types.Context, armature: bpy.types.Object) -> None:
|
|
"""Set the active armature for Avatar Toolkit operations"""
|
|
context.scene.avatar_toolkit.active_armature = armature
|
|
|
|
def get_armature_list(self=None, context: bpy.types.Context = None) -> List[Tuple[str, str, str]]:
|
|
"""Get list of all armature objects in the scene"""
|
|
if context is None:
|
|
context = bpy.context
|
|
armatures = [(obj.name, obj.name, "") for obj in context.scene.objects if obj.type == 'ARMATURE']
|
|
if not armatures:
|
|
return [('NONE', t("Armature.validation.no_armature"), '')]
|
|
return armatures
|
|
|
|
def validate_armature(armature: bpy.types.Object) -> Tuple[bool, str]:
|
|
"""
|
|
Validate if the selected object is a proper armature and has required bones
|
|
Returns tuple of (is_valid, message)
|
|
"""
|
|
if not armature:
|
|
return False, t("Armature.validation.no_armature")
|
|
if armature.type != 'ARMATURE':
|
|
return False, t("Armature.validation.not_armature")
|
|
if not armature.data.bones:
|
|
return False, t("Armature.validation.no_bones")
|
|
|
|
essential_bones: Set[str] = {'hips', 'spine', 'chest', 'neck', 'head'}
|
|
found_bones: Set[str] = {bone.name.lower() for bone in armature.data.bones}
|
|
|
|
for bone in essential_bones:
|
|
if not any(alt_name in found_bones for alt_name in bone_names[bone]):
|
|
return False, t("Armature.validation.missing_bone", bone=bone)
|
|
|
|
return True, t("QuickAccess.valid_armature")
|
|
|
|
def auto_select_single_armature(context: bpy.types.Context) -> None:
|
|
"""Automatically select armature if only one exists in scene"""
|
|
armatures = get_armature_list(context)
|
|
if len(armatures) == 1:
|
|
set_active_armature(context, armatures[0])
|
|
|
|
def clear_default_objects() -> None:
|
|
"""Removes default Blender objects (cube, light, camera)"""
|
|
default_names: Set[str] = {'Cube', 'Light', 'Camera'}
|
|
for obj in bpy.data.objects:
|
|
if obj.name.split('.')[0] in default_names:
|
|
bpy.data.objects.remove(obj, do_unlink=True)
|
|
|
|
def get_armature_stats(armature: bpy.types.Object) -> dict:
|
|
"""Get statistics about the armature"""
|
|
return {
|
|
'bone_count': len(armature.data.bones),
|
|
'has_pose': bool(armature.pose),
|
|
'visible': not armature.hide_viewport,
|
|
'name': armature.name
|
|
}
|
|
|
|
def get_all_meshes(context: Context) -> List[Object]:
|
|
armature = get_active_armature(context)
|
|
if armature:
|
|
return [obj for obj in bpy.data.objects if obj.type == 'MESH' and obj.parent == armature]
|
|
return []
|
|
|
|
def apply_pose_as_rest(context: Context, armature_obj: Object, meshes: List[Object]) -> bool:
|
|
for mesh_obj in meshes:
|
|
if not mesh_obj.data:
|
|
continue
|
|
if mesh_obj.data.shape_keys and mesh_obj.data.shape_keys.key_blocks:
|
|
if len(mesh_obj.data.shape_keys.key_blocks) == 1:
|
|
basis = mesh_obj.data.shape_keys.key_blocks[0]
|
|
basis_name = basis.name
|
|
mesh_obj.shape_key_remove(basis)
|
|
apply_armature_to_mesh(armature_obj, mesh_obj)
|
|
mesh_obj.shape_key_add(name=basis_name)
|
|
else:
|
|
apply_armature_to_mesh_with_shapekeys(armature_obj, mesh_obj, context)
|
|
else:
|
|
apply_armature_to_mesh(armature_obj, mesh_obj)
|
|
|
|
bpy.ops.object.mode_set(mode='POSE')
|
|
bpy.ops.pose.armature_apply(selected=False)
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
return True
|
|
|
|
def apply_armature_to_mesh(armature_obj: Object, mesh_obj: Object) -> None:
|
|
armature_mod = mesh_obj.modifiers.new('PoseToRest', 'ARMATURE')
|
|
armature_mod.object = armature_obj
|
|
|
|
if bpy.app.version >= (3, 5):
|
|
mesh_obj.modifiers.move(mesh_obj.modifiers.find(armature_mod.name), 0)
|
|
else:
|
|
for _ in range(len(mesh_obj.modifiers) - 1):
|
|
bpy.ops.object.modifier_move_up(modifier=armature_mod.name)
|
|
|
|
with bpy.context.temp_override(object=mesh_obj):
|
|
bpy.ops.object.modifier_apply(modifier=armature_mod.name)
|
|
|
|
def apply_armature_to_mesh_with_shapekeys(armature_obj: Object, mesh_obj: Object, context: Context) -> None:
|
|
old_active_index = mesh_obj.active_shape_key_index
|
|
old_show_only = mesh_obj.show_only_shape_key
|
|
mesh_obj.show_only_shape_key = True
|
|
|
|
shape_keys = mesh_obj.data.shape_keys.key_blocks
|
|
vertex_groups = []
|
|
mutes = []
|
|
|
|
for sk in shape_keys:
|
|
vertex_groups.append(sk.vertex_group)
|
|
sk.vertex_group = ''
|
|
mutes.append(sk.mute)
|
|
sk.mute = False
|
|
|
|
disabled_mods = []
|
|
for mod in mesh_obj.modifiers:
|
|
if mod.show_viewport:
|
|
mod.show_viewport = False
|
|
disabled_mods.append(mod)
|
|
|
|
arm_mod = mesh_obj.modifiers.new('PoseToRest', 'ARMATURE')
|
|
arm_mod.object = armature_obj
|
|
|
|
co_length = len(mesh_obj.data.vertices) * 3
|
|
eval_cos = np.empty(co_length, dtype=np.single)
|
|
|
|
for i, shape_key in enumerate(shape_keys):
|
|
mesh_obj.active_shape_key_index = i
|
|
|
|
depsgraph = context.evaluated_depsgraph_get()
|
|
eval_mesh = mesh_obj.evaluated_get(depsgraph)
|
|
eval_mesh.data.vertices.foreach_get('co', eval_cos)
|
|
|
|
shape_key.data.foreach_set('co', eval_cos)
|
|
if i == 0:
|
|
mesh_obj.data.vertices.foreach_set('co', eval_cos)
|
|
|
|
for mod in disabled_mods:
|
|
mod.show_viewport = True
|
|
|
|
mesh_obj.modifiers.remove(arm_mod)
|
|
|
|
for sk, vg, mute in zip(shape_keys, vertex_groups, mutes):
|
|
sk.vertex_group = vg
|
|
sk.mute = mute
|
|
|
|
mesh_obj.active_shape_key_index = old_active_index
|
|
mesh_obj.show_only_shape_key = old_show_only
|