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:
@@ -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'}
|
||||||
|
|||||||
Reference in New Issue
Block a user