Fix for #165 (Hopefully)

We now recursively deletes entire chains of empty bones but keep parents if the toggle is active. It does well in testing but i dont have models with a long chain of bones to test.
This commit is contained in:
Yusarina
2025-07-28 23:23:56 +01:00
parent 60ba1b363f
commit c830938dce
+37 -33
View File
@@ -186,7 +186,6 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
if not armature: if not armature:
return {'CANCELLED'} return {'CANCELLED'}
# Store initial transforms
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
initial_transforms: Dict[str, Dict[str, Any]] = {} initial_transforms: Dict[str, Dict[str, Any]] = {}
data_breaking = store_breaking_settings_armature(armature) data_breaking = store_breaking_settings_armature(armature)
@@ -200,56 +199,61 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
'parent': bone.parent.name if bone.parent else None 'parent': bone.parent.name if bone.parent else None
} }
# Get weighted bones # Get bones with any weight
weighted_bones: List[str] = [] weighted_bones: List[str] = []
meshes = get_all_meshes(context) meshes = get_all_meshes(context)
zero_weight_bones: List[str] = []
for mesh in meshes: for mesh in meshes:
mesh_data: Mesh = mesh.data for vertex in mesh.data.vertices:
for vertex in mesh_data.vertices:
for group in vertex.groups: for group in vertex.groups:
if group.weight > context.scene.avatar_toolkit.merge_weights_threshold: if group.weight > context.scene.avatar_toolkit.merge_weights_threshold:
weighted_bones.append(mesh.vertex_groups[group.group].name) vg = mesh.vertex_groups[group.group]
if vg.name not in weighted_bones:
weighted_bones.append(vg.name)
# Process bone removal armature_data = armature.data
bpy.ops.object.mode_set(mode='EDIT')
armature_data: Armature = armature.data
removed_count = 0 removed_count = 0
zero_weight_bones: List[str] = []
for bone in armature_data.edit_bones[:]: # Create a copy of the list def is_zero_weight_chain(bone, weighted_bones, preserve_check_fn):
if (bone.name not in weighted_bones and if bone.name in weighted_bones or preserve_check_fn(bone.name, context):
not self.should_preserve_bone(bone.name, context)): return False
return all(is_zero_weight_chain(child, weighted_bones, preserve_check_fn) for child in bone.children)
if context.scene.avatar_toolkit.list_only_mode: for bone in armature_data.edit_bones[:]:
zero_weight_bones.append(bone.name) if bone.name in weighted_bones or self.should_preserve_bone(bone.name, context):
continue continue
# Store children data if not is_zero_weight_chain(bone, weighted_bones, self.should_preserve_bone):
children = bone.children continue
children_data = {child.name: initial_transforms[child.name] for child in children}
# Reparent children if context.scene.avatar_toolkit.list_only_mode:
for child in children: zero_weight_bones.append(bone.name)
continue
# Traverse and collect the full empty chain
stack = [bone]
chain = []
while stack:
b = stack.pop()
chain.append(b)
stack.extend(b.children)
for b in reversed(chain): # Remove children before parents
for child in b.children:
child.use_connect = False child.use_connect = False
if bone.parent: if b.parent:
child.parent = bone.parent child.parent = b.parent
if b.name in armature_data.edit_bones:
# Remove bone armature_data.edit_bones.remove(b)
armature_data.edit_bones.remove(bone) removed_count += 1
removed_count += 1
# Restore children positions
for child_name, data in children_data.items():
if child_name in armature_data.edit_bones:
child = armature_data.edit_bones[child_name]
restore_bone_transforms(child, data)
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
if context.scene.avatar_toolkit.list_only_mode: if context.scene.avatar_toolkit.list_only_mode:
self.populate_bone_list(context, zero_weight_bones) self.populate_bone_list(context, zero_weight_bones)
return {'FINISHED'} return {'FINISHED'}
restore_breaking_settings_armature(armature, data_breaking) restore_breaking_settings_armature(armature, data_breaking)
self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count)) self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count))
return {'FINISHED'} return {'FINISHED'}