Remove Zero Weights Improvements
- Added Options to preserve Parent Bones. - Added List mode only where the user can select the bones there want to remove. - Added Options to only target Deform bones only and non deform bones only. This is complete, the UI needs a little cleanup but I do this in a UI cleanup nearer Alpha 2.
This commit is contained in:
@@ -18,6 +18,13 @@ from .common import get_armature_list, get_active_armature, get_all_meshes
|
|||||||
from ..functions.visemes import VisemePreview
|
from ..functions.visemes import VisemePreview
|
||||||
from ..functions.eye_tracking import set_rotation
|
from ..functions.eye_tracking import set_rotation
|
||||||
|
|
||||||
|
class ZeroWeightBoneItem(PropertyGroup):
|
||||||
|
"""Property group for zero weight bone list items"""
|
||||||
|
name: StringProperty(name="Bone Name")
|
||||||
|
selected: BoolProperty(name="Selected", default=True)
|
||||||
|
has_children: BoolProperty(name="Has Children", default=False)
|
||||||
|
is_deform: BoolProperty(name="Is Deform Bone", default=False)
|
||||||
|
|
||||||
def update_validation_mode(self: PropertyGroup, context: Context) -> None:
|
def update_validation_mode(self: PropertyGroup, context: Context) -> None:
|
||||||
"""Updates validation mode and saves preference"""
|
"""Updates validation mode and saves preference"""
|
||||||
logger.info(f"Updating validation mode to: {self.validation_mode}")
|
logger.info(f"Updating validation mode to: {self.validation_mode}")
|
||||||
@@ -361,6 +368,40 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
|||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
preserve_parent_bones: BoolProperty(
|
||||||
|
name=t("Tools.preserve_parent_bones"),
|
||||||
|
description=t("Tools.preserve_parent_bones_desc"),
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
target_bone_type: EnumProperty(
|
||||||
|
name=t("Tools.target_bone_type"),
|
||||||
|
description=t("Tools.target_bone_type_desc"),
|
||||||
|
items=[
|
||||||
|
('ALL', t("Tools.target_all_bones"), ""),
|
||||||
|
('DEFORM', t("Tools.target_deform_bones"), ""),
|
||||||
|
('NON_DEFORM', t("Tools.target_non_deform_bones"), "")
|
||||||
|
],
|
||||||
|
default='ALL'
|
||||||
|
)
|
||||||
|
|
||||||
|
zero_weight_bones: CollectionProperty(
|
||||||
|
type=ZeroWeightBoneItem,
|
||||||
|
name="Zero Weight Bones",
|
||||||
|
description="List of bones with zero weights"
|
||||||
|
)
|
||||||
|
|
||||||
|
zero_weight_bones_index: IntProperty(
|
||||||
|
name="Zero Weight Bone Index",
|
||||||
|
default=0
|
||||||
|
)
|
||||||
|
|
||||||
|
list_only_mode: BoolProperty(
|
||||||
|
name=t("Tools.list_only_mode"),
|
||||||
|
description=t("Tools.list_only_mode_desc"),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
cleanup_shape_keys: BoolProperty(
|
cleanup_shape_keys: BoolProperty(
|
||||||
name=t('MergeArmature.cleanup_shape_keys'),
|
name=t('MergeArmature.cleanup_shape_keys'),
|
||||||
description=t('MergeArmature.cleanup_shape_keys_desc'),
|
description=t('MergeArmature.cleanup_shape_keys_desc'),
|
||||||
|
|||||||
@@ -134,17 +134,11 @@ class AvatarToolKit_OT_DeleteBoneConstraints(Operator):
|
|||||||
|
|
||||||
def execute(self, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
"""Execute the constraint removal operation"""
|
"""Execute the constraint removal operation"""
|
||||||
|
|
||||||
# Make sure we are in Object mode first or it will error
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
|
|
||||||
# Select armature and make it active before changing mode
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
armature.select_set(True)
|
armature.select_set(True)
|
||||||
context.view_layer.objects.active = armature
|
context.view_layer.objects.active = armature
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='POSE')
|
bpy.ops.object.mode_set(mode='POSE')
|
||||||
|
|
||||||
constraints_removed = 0
|
constraints_removed = 0
|
||||||
@@ -157,7 +151,6 @@ class AvatarToolKit_OT_DeleteBoneConstraints(Operator):
|
|||||||
self.report({'INFO'}, t("Tools.clean_constraints_success", count=constraints_removed))
|
self.report({'INFO'}, t("Tools.clean_constraints_success", count=constraints_removed))
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
|
class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
|
||||||
"""Operator to remove bones with no vertex weights"""
|
"""Operator to remove bones with no vertex weights"""
|
||||||
bl_idname = "avatar_toolkit.clean_weights"
|
bl_idname = "avatar_toolkit.clean_weights"
|
||||||
@@ -167,10 +160,37 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
|
|||||||
|
|
||||||
def should_preserve_bone(self, bone_name: str, context: Context) -> bool:
|
def should_preserve_bone(self, bone_name: str, context: Context) -> bool:
|
||||||
"""Check if bone should be preserved based on settings"""
|
"""Check if bone should be preserved based on settings"""
|
||||||
if context.scene.avatar_toolkit.merge_twist_bones:
|
toolkit = context.scene.avatar_toolkit
|
||||||
return "twist" in bone_name.lower()
|
bone = context.active_object.data.bones.get(bone_name)
|
||||||
|
|
||||||
|
if not bone:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if toolkit.preserve_parent_bones and bone.children:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if toolkit.target_bone_type == 'DEFORM' and not bone.use_deform:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if toolkit.target_bone_type == 'NON_DEFORM' and bone.use_deform:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def populate_bone_list(self, context: Context, zero_weight_bones: List[str]) -> None:
|
||||||
|
"""Populate the zero weight bones list"""
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
toolkit.zero_weight_bones.clear()
|
||||||
|
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
for bone_name in zero_weight_bones:
|
||||||
|
bone = armature.data.bones.get(bone_name)
|
||||||
|
if bone:
|
||||||
|
item = toolkit.zero_weight_bones.add()
|
||||||
|
item.name = bone_name
|
||||||
|
item.has_children = len(bone.children) > 0
|
||||||
|
item.is_deform = bone.use_deform
|
||||||
|
|
||||||
def execute(self, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
"""Execute the zero weight bone removal operation"""
|
"""Execute the zero weight bone removal operation"""
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
@@ -192,6 +212,7 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
|
|||||||
# Get weighted bones
|
# Get weighted bones
|
||||||
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
|
mesh_data: Mesh = mesh.data
|
||||||
@@ -209,6 +230,10 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
|
|||||||
if (bone.name not in weighted_bones and
|
if (bone.name not in weighted_bones and
|
||||||
not self.should_preserve_bone(bone.name, context)):
|
not self.should_preserve_bone(bone.name, context)):
|
||||||
|
|
||||||
|
if context.scene.avatar_toolkit.list_only_mode:
|
||||||
|
zero_weight_bones.append(bone.name)
|
||||||
|
continue
|
||||||
|
|
||||||
# Store children data
|
# Store children data
|
||||||
children = bone.children
|
children = bone.children
|
||||||
children_data = {child.name: initial_transforms[child.name] for child in children}
|
children_data = {child.name: initial_transforms[child.name] for child in children}
|
||||||
@@ -227,11 +252,38 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
|
|||||||
for child_name, data in children_data.items():
|
for child_name, data in children_data.items():
|
||||||
if child_name in armature_data.edit_bones:
|
if child_name in armature_data.edit_bones:
|
||||||
child = armature_data.edit_bones[child_name]
|
child = armature_data.edit_bones[child_name]
|
||||||
child.head = data['head']
|
restore_bone_transforms(child, data)
|
||||||
child.tail = data['tail']
|
|
||||||
child.roll = data['roll']
|
|
||||||
child.matrix = data['matrix']
|
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
if context.scene.avatar_toolkit.list_only_mode:
|
||||||
|
self.populate_bone_list(context, zero_weight_bones)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count))
|
self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class AvatarToolKit_OT_RemoveSelectedBones(Operator):
|
||||||
|
"""Operator to remove selected bones from the zero weight bones list"""
|
||||||
|
bl_idname = "avatar_toolkit.remove_selected_bones"
|
||||||
|
bl_label = t("Tools.remove_selected_bones")
|
||||||
|
bl_description = t("Tools.remove_selected_bones_desc")
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> set[str]:
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
|
selected_bones = [item.name for item in toolkit.zero_weight_bones
|
||||||
|
if item.selected]
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
for bone_name in selected_bones:
|
||||||
|
if bone_name in armature.data.edit_bones:
|
||||||
|
armature.data.edit_bones.remove(armature.data.edit_bones[bone_name])
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
toolkit.zero_weight_bones.clear()
|
||||||
|
|
||||||
|
self.report({'INFO'}, t("Tools.bones_removed", count=len(selected_bones)))
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@@ -149,6 +149,19 @@
|
|||||||
"Tools.merge_twist_bones_desc": "When checked, twist bones will be kept, even if there are zero-weight",
|
"Tools.merge_twist_bones_desc": "When checked, twist bones will be kept, even if there are zero-weight",
|
||||||
"Tools.clean_weights": "Remove Zero Weight Bones",
|
"Tools.clean_weights": "Remove Zero Weight Bones",
|
||||||
"Tools.clean_weights_desc": "Remove bones with no vertex weights",
|
"Tools.clean_weights_desc": "Remove bones with no vertex weights",
|
||||||
|
"Tools.preserve_parent_bones": "Preserve Parent Bones",
|
||||||
|
"Tools.preserve_parent_bones_desc": "Keep bones that have children even if they have no weights",
|
||||||
|
"Tools.target_bone_type": "Target Bone Type",
|
||||||
|
"Tools.target_bone_type_desc": "Filter which types of bones to process",
|
||||||
|
"Tools.target_all_bones": "All Bones",
|
||||||
|
"Tools.target_deform_bones": "Deform Bones Only",
|
||||||
|
"Tools.target_non_deform_bones": "Non-Deform Bones Only",
|
||||||
|
"Tools.list_only_mode": "List Mode Only",
|
||||||
|
"Tools.list_only_mode_desc": "List zero weight bones instead of removing them",
|
||||||
|
"Tools.zero_weight_bones_found": "Zero weight bones found: {bones}",
|
||||||
|
"Tools.remove_selected_bones": "Remove Selected Bones",
|
||||||
|
"Tools.remove_selected_bones_desc": "Remove selected zero weight bones from armature",
|
||||||
|
"Tools.bones_removed": "Removed {count} bones",
|
||||||
"Tools.clean_constraints": "Delete Bone Constraints",
|
"Tools.clean_constraints": "Delete Bone Constraints",
|
||||||
"Tools.clean_constraints_desc": "Remove all bone constraints from armature",
|
"Tools.clean_constraints_desc": "Remove all bone constraints from armature",
|
||||||
"Tools.clean_constraints_success": "Removed {count} bone constraints",
|
"Tools.clean_constraints_success": "Removed {count} bone constraints",
|
||||||
|
|||||||
@@ -149,6 +149,19 @@
|
|||||||
"Tools.merge_twist_bones_desc": "チェックすると、重みが0でもツイストボーンを保持します",
|
"Tools.merge_twist_bones_desc": "チェックすると、重みが0でもツイストボーンを保持します",
|
||||||
"Tools.clean_weights": "重みなしボーンを削除",
|
"Tools.clean_weights": "重みなしボーンを削除",
|
||||||
"Tools.clean_weights_desc": "頂点の重みがないボーンを削除",
|
"Tools.clean_weights_desc": "頂点の重みがないボーンを削除",
|
||||||
|
"Tools.preserve_parent_bones": "親ボーンを保持",
|
||||||
|
"Tools.preserve_parent_bones_desc": "ウェイトがなくても子ボーンを持つボーンを保持",
|
||||||
|
"Tools.target_bone_type": "対象ボーンタイプ",
|
||||||
|
"Tools.target_bone_type_desc": "処理するボーンタイプを選択",
|
||||||
|
"Tools.target_all_bones": "全てのボーン",
|
||||||
|
"Tools.target_deform_bones": "変形ボーンのみ",
|
||||||
|
"Tools.target_non_deform_bones": "非変形ボーンのみ",
|
||||||
|
"Tools.list_only_mode": "リストモードのみ",
|
||||||
|
"Tools.list_only_mode_desc": "ゼロウェイトボーンを削除せずにリスト表示",
|
||||||
|
"Tools.zero_weight_bones_found": "ゼロウェイトボーンが見つかりました: {bones}",
|
||||||
|
"Tools.remove_selected_bones": "選択したボーンを削除",
|
||||||
|
"Tools.remove_selected_bones_desc": "選択したゼロウェイトボーンをアーマチュアから削除",
|
||||||
|
"Tools.bones_removed": "{count}個のボーンを削除しました",
|
||||||
"Tools.clean_constraints": "ボーンのコンストレイントを削除",
|
"Tools.clean_constraints": "ボーンのコンストレイントを削除",
|
||||||
"Tools.clean_constraints_desc": "アーマチュアからすべてのボーンコンストレイントを削除",
|
"Tools.clean_constraints_desc": "アーマチュアからすべてのボーンコンストレイントを削除",
|
||||||
"Tools.clean_constraints_success": "{count}個のボーンコンストレイントを削除しました",
|
"Tools.clean_constraints_success": "{count}個のボーンコンストレイントを削除しました",
|
||||||
|
|||||||
@@ -149,6 +149,19 @@
|
|||||||
"Tools.merge_twist_bones_desc": "체크하면 가중치가 0이어도 트위스트 본 유지",
|
"Tools.merge_twist_bones_desc": "체크하면 가중치가 0이어도 트위스트 본 유지",
|
||||||
"Tools.clean_weights": "0 가중치 본 제거",
|
"Tools.clean_weights": "0 가중치 본 제거",
|
||||||
"Tools.clean_weights_desc": "버텍스 가중치가 없는 본 제거",
|
"Tools.clean_weights_desc": "버텍스 가중치가 없는 본 제거",
|
||||||
|
"Tools.preserve_parent_bones": "부모 본 보존",
|
||||||
|
"Tools.preserve_parent_bones_desc": "가중치가 없어도 자식 본이 있는 본 유지",
|
||||||
|
"Tools.target_bone_type": "대상 본 유형",
|
||||||
|
"Tools.target_bone_type_desc": "처리할 본 유형 필터링",
|
||||||
|
"Tools.target_all_bones": "모든 본",
|
||||||
|
"Tools.target_deform_bones": "변형 본만",
|
||||||
|
"Tools.target_non_deform_bones": "비변형 본만",
|
||||||
|
"Tools.list_only_mode": "목록 모드만",
|
||||||
|
"Tools.list_only_mode_desc": "제로 가중치 본을 제거하지 않고 목록으로 표시",
|
||||||
|
"Tools.zero_weight_bones_found": "제로 가중치 본 발견: {bones}",
|
||||||
|
"Tools.remove_selected_bones": "선택한 본 제거",
|
||||||
|
"Tools.remove_selected_bones_desc": "선택한 제로 가중치 본을 아마추어에서 제거",
|
||||||
|
"Tools.bones_removed": "{count}개의 본이 제거되었습니다",
|
||||||
"Tools.clean_constraints": "본 제약 조건 삭제",
|
"Tools.clean_constraints": "본 제약 조건 삭제",
|
||||||
"Tools.clean_constraints_desc": "아마추어에서 모든 본 제약 조건 제거",
|
"Tools.clean_constraints_desc": "아마추어에서 모든 본 제약 조건 제거",
|
||||||
"Tools.clean_constraints_success": "{count}개의 본 제약 조건 제거됨",
|
"Tools.clean_constraints_success": "{count}개의 본 제약 조건 제거됨",
|
||||||
|
|||||||
+30
-2
@@ -1,9 +1,21 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from bpy.types import Panel, Context, UILayout, Operator
|
from bpy.types import Panel, Context, UILayout, Operator, UIList
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
|
|
||||||
|
class AVATAR_TOOLKIT_UL_ZeroWeightBones(UIList):
|
||||||
|
"""UI List for displaying zero weight bones with selection options"""
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(item, "selected", text="")
|
||||||
|
row.label(text=item.name)
|
||||||
|
if item.has_children:
|
||||||
|
row.label(text="", icon='OUTLINER_OB_ARMATURE')
|
||||||
|
if item.is_deform:
|
||||||
|
row.label(text="", icon='MOD_ARMATURE')
|
||||||
|
|
||||||
class AvatarToolKit_PT_ToolsPanel(Panel):
|
class AvatarToolKit_PT_ToolsPanel(Panel):
|
||||||
"""Panel containing various tools for avatar customization and optimization"""
|
"""Panel containing various tools for avatar customization and optimization"""
|
||||||
bl_label: str = t("Tools.label")
|
bl_label: str = t("Tools.label")
|
||||||
@@ -18,6 +30,7 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
|||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the tools panel interface"""
|
"""Draw the tools panel interface"""
|
||||||
layout: UILayout = self.layout
|
layout: UILayout = self.layout
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# General Tools
|
# General Tools
|
||||||
tools_box: UILayout = layout.box()
|
tools_box: UILayout = layout.box()
|
||||||
@@ -45,7 +58,22 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
|||||||
# Weight Tools
|
# Weight Tools
|
||||||
weight_box: UILayout = bone_box.box()
|
weight_box: UILayout = bone_box.box()
|
||||||
col = weight_box.column(align=True)
|
col = weight_box.column(align=True)
|
||||||
col.prop(context.scene.avatar_toolkit, "merge_twist_bones", text=t("Tools.merge_twist_bones"))
|
col.prop(toolkit, "merge_twist_bones", text=t("Tools.merge_twist_bones"))
|
||||||
|
col.prop(toolkit, "preserve_parent_bones")
|
||||||
|
col.prop(toolkit, "target_bone_type")
|
||||||
|
col.prop(toolkit, "list_only_mode")
|
||||||
|
|
||||||
|
if toolkit.list_only_mode and len(toolkit.zero_weight_bones) > 0:
|
||||||
|
box = weight_box.box()
|
||||||
|
row = box.row()
|
||||||
|
row.template_list("AVATAR_TOOLKIT_UL_ZeroWeightBones", "",
|
||||||
|
toolkit, "zero_weight_bones",
|
||||||
|
toolkit, "zero_weight_bones_index")
|
||||||
|
|
||||||
|
col = box.column(align=True)
|
||||||
|
col.operator("avatar_toolkit.remove_selected_bones",
|
||||||
|
text=t("Tools.remove_selected_bones"))
|
||||||
|
|
||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
row.operator("avatar_toolkit.clean_weights", text=t("Tools.clean_weights"), icon='GROUP_BONE')
|
row.operator("avatar_toolkit.clean_weights", text=t("Tools.clean_weights"), icon='GROUP_BONE')
|
||||||
row.operator("avatar_toolkit.clean_constraints", text=t("Tools.clean_constraints"), icon='CONSTRAINT_BONE')
|
row.operator("avatar_toolkit.clean_constraints", text=t("Tools.clean_constraints"), icon='CONSTRAINT_BONE')
|
||||||
|
|||||||
Reference in New Issue
Block a user