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