Merge pull request #119 from 989onan/PoseMode
fix bad armature merging issues
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
|
||||
*.pyc
|
||||
.vscode/settings.json
|
||||
core/preferences.json
|
||||
|
||||
+5
-6
@@ -455,12 +455,6 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
default=""
|
||||
)
|
||||
|
||||
merge_all_bones: BoolProperty(
|
||||
name=t('MergeArmature.merge_all'),
|
||||
description=t('MergeArmature.merge_all_desc'),
|
||||
default=True
|
||||
)
|
||||
|
||||
apply_transforms: BoolProperty(
|
||||
name=t('MergeArmature.apply_transforms'),
|
||||
description=t('MergeArmature.apply_transforms_desc'),
|
||||
@@ -529,7 +523,10 @@ def register() -> None:
|
||||
"""Register the Avatar Toolkit property group"""
|
||||
logger.info("Registering Avatar Toolkit properties")
|
||||
try:
|
||||
bpy.utils.register_class(ZeroWeightBoneItem)
|
||||
bpy.utils.register_class(AvatarToolkitSceneProperties)
|
||||
|
||||
|
||||
except ValueError:
|
||||
# Class already registered, we can continue
|
||||
pass
|
||||
@@ -544,7 +541,9 @@ def unregister() -> None:
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
bpy.utils.unregister_class(ZeroWeightBoneItem)
|
||||
bpy.utils.unregister_class(AvatarToolkitSceneProperties)
|
||||
|
||||
except RuntimeError:
|
||||
pass
|
||||
logger.debug("Properties unregistered successfully")
|
||||
|
||||
@@ -2,7 +2,7 @@ import bpy
|
||||
import numpy as np
|
||||
from typing import List, Optional, Dict, Set, Tuple, Any
|
||||
from bpy.types import Context, Object, Operator, ArmatureModifier, EditBone, VertexGroup, Mesh, ShapeKey
|
||||
|
||||
from ...core.dictionaries import bone_names
|
||||
from ...core.logging_setup import logger
|
||||
from ...core.translations import t
|
||||
from ...core.common import (
|
||||
@@ -10,7 +10,8 @@ from ...core.common import (
|
||||
fix_zero_length_bones,
|
||||
clear_unused_data_blocks,
|
||||
join_mesh_objects,
|
||||
remove_unused_shapekeys
|
||||
remove_unused_shapekeys,
|
||||
simplify_bonename
|
||||
)
|
||||
|
||||
class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
|
||||
@@ -52,7 +53,6 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
|
||||
wm.progress_update(80)
|
||||
|
||||
# Get settings from scene properties
|
||||
merge_all_bones: bool = context.scene.avatar_toolkit.merge_all_bones
|
||||
join_meshes: bool = context.scene.avatar_toolkit.join_meshes
|
||||
|
||||
# Merge armatures
|
||||
@@ -60,7 +60,6 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
|
||||
base_armature_name,
|
||||
merge_armature_name,
|
||||
mesh_only=False,
|
||||
merge_all_bones=merge_all_bones,
|
||||
join_meshes=join_meshes,
|
||||
operator=self
|
||||
)
|
||||
@@ -100,16 +99,12 @@ def validate_parents_and_transforms(merge_armature: Object, base_armature: Objec
|
||||
base_parent: Optional[Object] = base_armature.parent
|
||||
|
||||
if merge_parent or base_parent:
|
||||
if context.scene.merge_all_bones:
|
||||
for armature, parent in [(merge_armature, merge_parent), (base_armature, base_parent)]:
|
||||
if parent:
|
||||
if not is_transform_clean(parent):
|
||||
logger.error("Parent transforms are not clean")
|
||||
return False
|
||||
bpy.data.objects.remove(parent, do_unlink=True)
|
||||
else:
|
||||
logger.error("Parent relationships need fixing")
|
||||
return False
|
||||
for armature, parent in [(merge_armature, merge_parent), (base_armature, base_parent)]:
|
||||
if parent:
|
||||
if not is_transform_clean(parent):
|
||||
logger.error("Parent transforms are not clean")
|
||||
return False
|
||||
bpy.data.objects.remove(parent, do_unlink=True)
|
||||
return True
|
||||
|
||||
def is_transform_clean(obj: Object) -> bool:
|
||||
@@ -135,7 +130,6 @@ def merge_armatures(
|
||||
base_armature_name: str,
|
||||
merge_armature_name: str,
|
||||
mesh_only: bool,
|
||||
merge_all_bones: bool = False,
|
||||
join_meshes: bool = False,
|
||||
operator: Optional[Operator] = None
|
||||
) -> None:
|
||||
@@ -174,25 +168,50 @@ def merge_armatures(
|
||||
|
||||
# Store original parent relationships
|
||||
original_parents: Dict[str, Optional[str]] = {}
|
||||
for bone in merge_armature.data.bones:
|
||||
merge_armature_data: bpy.types.Armature = merge_armature.data
|
||||
for bone in merge_armature_data.bones:
|
||||
original_parents[bone.name] = bone.parent.name if bone.parent else None
|
||||
|
||||
#create reverse lookup
|
||||
reverse_bone_lookup = {}
|
||||
for preferred_name, name_list in bone_names.items():
|
||||
for name in name_list:
|
||||
reverse_bone_lookup[name] = preferred_name
|
||||
|
||||
# Get base bone names
|
||||
base_bone_names: Set[str] = {bone.name for bone in base_armature.data.bones}
|
||||
|
||||
base_armature_standards: Dict[str,Optional[str]] = {}
|
||||
for bone in base_bone_names:
|
||||
if simplify_bonename(bone) in reverse_bone_lookup:
|
||||
base_armature_standards[reverse_bone_lookup[simplify_bonename(bone)]] = bone
|
||||
|
||||
# Switch to edit mode on merge armature and rename bones
|
||||
bpy.context.view_layer.objects.active = merge_armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# Handle bone renaming based on merge_all_bones setting
|
||||
for bone in merge_armature.data.edit_bones:
|
||||
if not merge_all_bones:
|
||||
# Only rename bones that don't exist in base armature
|
||||
if bone.name not in base_bone_names:
|
||||
bone.name += '.merge'
|
||||
# Handle bone renaming/removing to target armature.
|
||||
bone_names_source: list[str] = [bone.name for bone in merge_armature_data.edit_bones]
|
||||
for bone in bone_names_source:
|
||||
bone_name = bone
|
||||
if bone_name not in base_bone_names: #not auto mergable to original
|
||||
|
||||
if simplify_bonename(bone_name) in reverse_bone_lookup: #if is a standard bone through standard translation.
|
||||
if reverse_bone_lookup[simplify_bonename(bone_name)] in base_armature_standards: #if this bone equals for example, "hips", does a bone that should be "hips" exist on our target armature?
|
||||
#if so, rename this bone to that one
|
||||
merge_armature_data.edit_bones[bone_name].name = base_armature_standards[reverse_bone_lookup[simplify_bonename(bone_name)]]
|
||||
bone_name = merge_armature_data.edit_bones[bone_name].name
|
||||
#adjust original parents list to point to the new name.
|
||||
for child_bone in merge_armature_data.edit_bones[bone_name]:
|
||||
original_parents[child_bone.name] = bone_name
|
||||
#then remove so it doesn't clash when merged.
|
||||
merge_armature_data.edit_bones.remove(merge_armature_data.edit_bones[bone_name])
|
||||
continue
|
||||
|
||||
#if it really doesn't have a counter part, just don't bother.
|
||||
else:
|
||||
# Rename all bones from merge armature
|
||||
bone.name += '.merge'
|
||||
merge_armature_data.edit_bones.remove(merge_armature_data.edit_bones[bone_name])
|
||||
|
||||
|
||||
# Return to object mode
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
@@ -204,14 +223,15 @@ def merge_armatures(
|
||||
bpy.context.view_layer.objects.active = base_armature
|
||||
bpy.ops.object.join()
|
||||
|
||||
base_armature_data: bpy.types.Armature = base_armature.data
|
||||
|
||||
# Restore parent relationships
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
for bone in base_armature.data.edit_bones:
|
||||
base_name: str = bone.name.replace('.merge', '')
|
||||
if base_name in original_parents:
|
||||
parent_name: Optional[str] = original_parents[base_name]
|
||||
for bone in base_armature_data.edit_bones:
|
||||
if bone.name in original_parents:
|
||||
parent_name: Optional[str] = original_parents[bone.name]
|
||||
if parent_name:
|
||||
parent_bone: Optional[EditBone] = base_armature.data.edit_bones.get(parent_name)
|
||||
parent_bone: Optional[EditBone] = base_armature_data.edit_bones.get(parent_name)
|
||||
if parent_bone:
|
||||
bone.parent = parent_bone
|
||||
|
||||
@@ -250,11 +270,6 @@ def merge_armatures(
|
||||
|
||||
# Remove any remaining .merge bones
|
||||
bpy.context.view_layer.objects.active = base_armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
edit_bones: List[EditBone] = base_armature.data.edit_bones
|
||||
bones_to_remove: List[EditBone] = [bone for bone in edit_bones if bone.name.endswith('.merge')]
|
||||
for bone in bones_to_remove:
|
||||
edit_bones.remove(bone)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# Final cleanup
|
||||
@@ -298,8 +313,7 @@ def adjust_merge_armature_transforms(
|
||||
def detect_bones_to_merge(
|
||||
base_edit_bones: bpy.types.ArmatureEditBones,
|
||||
merge_edit_bones: bpy.types.ArmatureEditBones,
|
||||
tolerance: float,
|
||||
merge_all_bones: bool
|
||||
tolerance: float
|
||||
) -> List[str]:
|
||||
"""Detect corresponding bones between base and merge armatures using smart detection and position tolerance"""
|
||||
bones_to_merge: List[str] = []
|
||||
@@ -314,7 +328,7 @@ def detect_bones_to_merge(
|
||||
merge_bone_position: np.ndarray = np.array(merge_bone.head)
|
||||
found_match: bool = False
|
||||
|
||||
if merge_all_bones and merge_bone.name in base_bones_positions:
|
||||
if merge_bone.name in base_bones_positions:
|
||||
# If merging same bones by name
|
||||
bones_to_merge.append(merge_bone.name)
|
||||
found_match = True
|
||||
|
||||
@@ -155,7 +155,6 @@ class AvatarToolKit_PT_CustomPanel(Panel):
|
||||
|
||||
# Group related options together
|
||||
transform_col: UILayout = col.column(align=True)
|
||||
transform_col.prop(toolkit, "merge_all_bones")
|
||||
transform_col.prop(toolkit, "apply_transforms")
|
||||
|
||||
col.separator(factor=0.5)
|
||||
|
||||
Reference in New Issue
Block a user