Add Bone Tools

- Added merge to active to tools section for bones
- Added merge to individual parents, which merges each selected bone to their parent bone
This commit is contained in:
989onan
2024-09-06 12:32:50 -04:00
parent c10b51e32c
commit dc2b6e46ce
4 changed files with 130 additions and 2 deletions
+22
View File
@@ -262,3 +262,25 @@ def update_progress(self, context, message):
def finish_progress(context): def finish_progress(context):
context.window_manager.progress_end() context.window_manager.progress_end()
context.area.header_text_set(None) context.area.header_text_set(None)
def transfer_vertex_weights(context: Context, obj: bpy.types.Object, source_group: str, target_group: str, delete_source_group: bool = True) -> bool:
modifier: bpy.types.VertexWeightMixModifier = obj.modifiers.new(name="merge_weights",type="VERTEX_WEIGHT_MIX")
modifier.mix_set = 'B'
modifier.vertex_group_a = target_group
modifier.vertex_group_b = source_group
modifier.mask_constant = 1.0
bpy.ops.object.mode_set(mode='OBJECT')
prev_obj: bpy.types.Object = context.view_layer.objects.active
context.view_layer.objects.active = obj
bpy.ops.object.modifier_apply(modifier=modifier.name)
if delete_source_group:
obj.vertex_groups.remove(obj.vertex_groups.get(source_group))
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
context.view_layer.objects.active = prev_obj
return True
+95 -1
View File
@@ -1,6 +1,6 @@
import bpy import bpy
from ..core import common from ..core import common
from bpy.types import Operator, Context, Mesh, Armature, EditBone, PoseBone from bpy.types import Operator, Context, Mesh, Armature, EditBone
from ..core.register import register_wrap from ..core.register import register_wrap
from .translations import t from .translations import t
@@ -62,3 +62,97 @@ class AvatarToolkit_RemoveZeroWeightBones(Operator):
self.report({'INFO'}, t("Tools.remove_zero_weight_bones.success")) self.report({'INFO'}, t("Tools.remove_zero_weight_bones.success"))
return {'FINISHED'} return {'FINISHED'}
@register_wrap
class AvatarToolkit_OT_MergeBonesToActive(Operator):
bl_idname = "avatar_toolkit.merge_bones_to_active"
bl_label = t("Tools.merge_bones_to_active.label")
bl_description = t("Tools.merge_bones_to_active.desc")
bl_options = {'REGISTER', 'UNDO'}
delete_old: bpy.props.BoolProperty(name=t("Tools.merge_bones_to_active.delete_old.label"), description=t("Tools.merge_bones_to_active.delete_old.desc"), default=False)
@classmethod
def poll(cls, context: Context) -> bool:
if common.get_selected_armature(context) is not None:
if common.get_selected_armature(context) == context.view_layer.objects.active:
if context.mode == "POSE":
return len(context.selected_pose_bones) > 1
elif context.mode == "EDIT_ARMATURE":
return len(context.selected_bones) > 1
return False
def execute(cls, context: Context) -> set[str]:
prev_mode: str = "EDIT"
if context.mode == "POSE":
prev_mode = "POSE"
#get active bone and a list of all other selected bones
bpy.ops.object.mode_set(mode='EDIT')
target_bone: str = context.active_bone.name
armature_data: Armature = context.view_layer.objects.active.data
bones: list[str] = [i.name for i in context.selected_bones]
bones.remove(target_bone)
for obj in common.get_all_meshes(context):
for bone in bones:
bone_name: str = armature_data.edit_bones[bone].name
common.transfer_vertex_weights(context=context,obj=obj,source_group=bone_name,target_group=armature_data.edit_bones[target_bone].name)
bpy.ops.object.mode_set(mode='EDIT')
for bone in bones:
if cls.delete_old:
for bone_child in armature_data.edit_bones[bone].children:
bone_child.parent = armature_data.edit_bones[bone].parent
armature_data.edit_bones.remove(armature_data.edit_bones[bone])
bpy.ops.object.mode_set(mode=prev_mode)
return {'FINISHED'}
@register_wrap
class AvatarToolkit_OT_MergeBonesToParents(Operator):
bl_idname = "avatar_toolkit.merge_bones_to_parents"
bl_label = t("Tools.merge_bones_to_parents.label")
bl_description = t("Tools.merge_bones_to_parents.desc")
bl_options = {'REGISTER', 'UNDO'}
delete_old: bpy.props.BoolProperty(name=t("Tools.merge_bones_to_parents.delete_old.label"), description=t("Tools.merge_bones_to_parents.delete_old.desc"), default=False)
@classmethod
def poll(cls, context: Context) -> bool:
if common.get_selected_armature(context) is not None:
if common.get_selected_armature(context) == context.view_layer.objects.active:
if context.mode == "POSE":
return len(context.selected_pose_bones) > 0
elif context.mode == "EDIT_ARMATURE":
return len(context.selected_bones) > 0
return False
def execute(cls, context: Context) -> set[str]:
prev_mode: str = "EDIT"
if context.mode == "POSE":
prev_mode = "POSE"
#get active bone and a list of all other selected bones
bpy.ops.object.mode_set(mode='EDIT')
armature_data: Armature = context.view_layer.objects.active.data
for obj in common.get_all_meshes(context):
for bone in [i.name for i in context.selected_bones]:
if armature_data.edit_bones[bone].parent != None:
bone_name: str = armature_data.edit_bones[bone].name
common.transfer_vertex_weights(context=context,obj=obj,source_group=bone_name,target_group=armature_data.edit_bones[bone].parent.name)
bpy.ops.object.mode_set(mode='EDIT')
for bone in [i.name for i in context.selected_bones]:
if cls.delete_old:
for bone_child in armature_data.edit_bones[bone].children:
bone_child.parent = armature_data.edit_bones[bone].parent
armature_data.edit_bones.remove(armature_data.edit_bones[bone])
bpy.ops.object.mode_set(mode=prev_mode)
return {'FINISHED'}
+8
View File
@@ -135,6 +135,14 @@
"Tools.remove_zero_weight_bones.success": "Zero weight bones removed successfully", "Tools.remove_zero_weight_bones.success": "Zero weight bones removed successfully",
"Tools.remove_zero_weight_bones.label": "Remove Zero Weight Bones", "Tools.remove_zero_weight_bones.label": "Remove Zero Weight Bones",
"Tools.remove_zero_weight_bones.desc": "Remove bones from the armature that have weights less than threshold.", "Tools.remove_zero_weight_bones.desc": "Remove bones from the armature that have weights less than threshold.",
"Tools.merge_bones_to_active.delete_old.desc": "Remove old bones when merging.",
"Tools.merge_bones_to_active.delete_old.label": "Remove Old Bones",
"Tools.merge_bones_to_active.desc": "Merge selected bones to active bone (selected in bright blue or orange).",
"Tools.merge_bones_to_active.label": "Merge Bones to Active",
"Tools.merge_bones_to_parents.delete_old.desc": "Remove old bones when merging.",
"Tools.merge_bones_to_parents.delete_old.label": "Remove Old Bones",
"Tools.merge_bones_to_parents.desc": "Merges every bone in the selection to each of their parents.",
"Tools.merge_bones_to_parents.label": "Merge Bones to Individual Parents",
"Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold", "Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold",
"Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed", "Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed",
"VisemePanel.create_visemes": "Create Visemes", "VisemePanel.create_visemes": "Create Visemes",
+5 -1
View File
@@ -7,7 +7,7 @@ from ..functions.translations import t
from ..core.common import get_selected_armature from ..core.common import get_selected_armature
from ..functions.seperate_by import SeparateByMaterials, SeparateByLooseParts from ..functions.seperate_by import SeparateByMaterials, SeparateByLooseParts
from ..functions.additional_tools import ApplyTransforms from ..functions.additional_tools import ApplyTransforms
from ..functions.armature_modifying import AvatarToolkit_RemoveZeroWeightBones from ..functions.armature_modifying import AvatarToolkit_RemoveZeroWeightBones, AvatarToolkit_OT_MergeBonesToActive, AvatarToolkit_OT_MergeBonesToParents
@register_wrap @register_wrap
class AvatarToolkitToolsPanel(bpy.types.Panel): class AvatarToolkitToolsPanel(bpy.types.Panel):
@@ -39,6 +39,10 @@ class AvatarToolkitToolsPanel(bpy.types.Panel):
row.operator(SeparateByLooseParts.bl_idname, text=t("Tools.separate_by_loose_parts.label"), icon='OUTLINER_OB_MESH') row.operator(SeparateByLooseParts.bl_idname, text=t("Tools.separate_by_loose_parts.label"), icon='OUTLINER_OB_MESH')
row = layout.row(align=True) row = layout.row(align=True)
row.operator(ApplyTransforms.bl_idname, text=t("Tools.apply_transforms.label"), icon='OBJECT_ORIGIN') row.operator(ApplyTransforms.bl_idname, text=t("Tools.apply_transforms.label"), icon='OBJECT_ORIGIN')
row = layout.row(align=True)
row.operator(AvatarToolkit_RemoveZeroWeightBones.bl_idname, text=t("Tools.remove_zero_weight_bones.label"), icon='BONE_DATA') row.operator(AvatarToolkit_RemoveZeroWeightBones.bl_idname, text=t("Tools.remove_zero_weight_bones.label"), icon='BONE_DATA')
row = layout.row(align=True)
row.operator(AvatarToolkit_OT_MergeBonesToActive.bl_idname, text=t("Tools.merge_bones_to_active.label"), icon='BONE_DATA')
row.operator(AvatarToolkit_OT_MergeBonesToParents.bl_idname, text=t("Tools.merge_bones_to_parents.label"), icon='BONE_DATA')
else: else:
layout.label(text=t("Tools.select_armature"), icon='ERROR') layout.label(text=t("Tools.select_armature"), icon='ERROR')