Add Easy_Weight
to Addons
#47
@ -12,19 +12,39 @@ class MESH_MT_vertex_group_batch_delete(bpy.types.Menu):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("object.vertex_group_remove", text="Delete All Groups").all = True
|
||||
layout.operator("object.vertex_group_remove", text="Delete All Unlocked Groups").all_unlocked = True
|
||||
layout.operator(DeleteEmptyDeformGroups.bl_idname)
|
||||
layout.operator(DeleteUnselectedDeformGroups.bl_idname)
|
||||
layout.operator(DeleteUnusedVertexGroups.bl_idname)
|
||||
layout.operator(
|
||||
"object.vertex_group_remove",
|
||||
text="Delete All Groups",
|
||||
icon='TRASH'
|
||||
).all = True
|
||||
layout.operator(
|
||||
"object.vertex_group_remove",
|
||||
text="Delete All Unlocked Groups",
|
||||
icon='UNLOCKED'
|
||||
).all_unlocked = True
|
||||
layout.separator()
|
||||
layout.operator(DeleteEmptyDeformGroups.bl_idname, icon='GROUP_BONE')
|
||||
layout.operator(DeleteUnusedVertexGroups.bl_idname, icon='BRUSH_DATA')
|
||||
layout.operator(DeleteUnselectedDeformGroups.bl_idname, icon='RESTRICT_SELECT_ON')
|
||||
|
||||
class MESH_MT_vertex_group_mirror(bpy.types.Menu):
|
||||
bl_label = "Mirror"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("object.vertex_group_mirror", icon='ARROW_LEFTRIGHT').use_topology = False
|
||||
layout.operator("object.vertex_group_mirror", text="Mirror Vertex Group (Topology)").use_topology = True
|
||||
# TODO: Add an operator that duplicates and mirrors active group
|
||||
# TODO: Add an operator that duplicates and mirrors groups of all selected pose bones, or simply all groups.
|
||||
# All this functionality could be merged into a single operator with a pop-up that asks you if you want proximity or topology based mirroring for the active group, all groups, or selected pose bones.
|
||||
layout.operator(
|
||||
"object.vertex_group_mirror",
|
||||
text="Mirror Active Group (Proximity)",
|
||||
icon='AUTOMERGE_OFF'
|
||||
).use_topology = False
|
||||
layout.operator(
|
||||
"object.vertex_group_mirror",
|
||||
text="Mirror Active Group (Topology)",
|
||||
icon='AUTOMERGE_ON'
|
||||
).use_topology = True
|
||||
|
||||
class MESH_MT_vertex_group_sort(bpy.types.Menu):
|
||||
bl_label = "Sort"
|
||||
@ -34,12 +54,12 @@ class MESH_MT_vertex_group_sort(bpy.types.Menu):
|
||||
layout.operator(
|
||||
"object.vertex_group_sort",
|
||||
icon='SORTALPHA',
|
||||
text="Sort by Name",
|
||||
text="By Name",
|
||||
).sort_type = 'NAME'
|
||||
layout.operator(
|
||||
"object.vertex_group_sort",
|
||||
icon='BONE_DATA',
|
||||
text="Sort by Bone Hierarchy",
|
||||
text="By Bone Hierarchy",
|
||||
).sort_type = 'BONE_HIERARCHY'
|
||||
|
||||
class MESH_MT_vertex_group_copy(bpy.types.Menu):
|
||||
@ -48,9 +68,12 @@ class MESH_MT_vertex_group_copy(bpy.types.Menu):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("object.vertex_group_copy", icon='DUPLICATE')
|
||||
layout.operator("object.vertex_group_copy_to_linked")
|
||||
layout.operator("object.vertex_group_copy_to_selected")
|
||||
# TODO: This isn't grayed out when there's no active group.
|
||||
# TODO: Maybe for things that use the active group, we should put the name of the group in the button text? Makes it harder to search tho perhaps. Not even sure if menu search supports dynamic menu text?
|
||||
layout.operator("object.vertex_group_copy", icon='DUPLICATE', text="Duplicate Group")
|
||||
layout.separator()
|
||||
layout.operator("object.vertex_group_copy_to_linked", text="Synchronize Groups on All Instances", icon='LINKED')
|
||||
layout.operator("object.vertex_group_copy_to_selected", text="Synchronize Groups on Selected", icon = 'RESTRICT_SELECT_OFF')
|
||||
|
||||
class MESH_MT_vertex_group_lock(bpy.types.Menu):
|
||||
bl_label = "Batch Lock"
|
||||
@ -62,7 +85,7 @@ class MESH_MT_vertex_group_lock(bpy.types.Menu):
|
||||
props.action, props.mask = 'LOCK', 'ALL'
|
||||
props = layout.operator("object.vertex_group_lock", icon='UNLOCKED', text="Unlock All")
|
||||
props.action, props.mask = 'UNLOCK', 'ALL'
|
||||
props = layout.operator("object.vertex_group_lock", text="Invert All Locks")
|
||||
props = layout.operator("object.vertex_group_lock", icon='UV_SYNC_SELECT', text="Invert All Locks")
|
||||
props.action, props.mask = 'INVERT', 'ALL'
|
||||
|
||||
class MESH_MT_vertex_group_weight(bpy.types.Menu):
|
||||
@ -73,26 +96,38 @@ class MESH_MT_vertex_group_weight(bpy.types.Menu):
|
||||
|
||||
layout.operator(
|
||||
"object.vertex_group_remove_from",
|
||||
icon='X',
|
||||
icon='MESH_DATA',
|
||||
text="Remove Selected Verts from All Groups",
|
||||
).use_all_groups = True
|
||||
layout.operator("object.vertex_group_clean", text="Clean 0 Weights from All Groups").group_select_mode = 'ALL'
|
||||
layout.operator(
|
||||
"object.vertex_group_clean",
|
||||
icon='BRUSH_DATA',
|
||||
text="Clean 0 Weights from All Groups"
|
||||
).group_select_mode = 'ALL'
|
||||
layout.separator()
|
||||
layout.operator("object.vertex_group_remove_from", text="Remove All Verts from Selected Group").use_all_verts = True
|
||||
layout.operator(
|
||||
"object.vertex_group_remove_from",
|
||||
icon='TRASH',
|
||||
text="Remove All Verts from Selected Group"
|
||||
).use_all_verts = True
|
||||
|
||||
def draw_misc(self, context):
|
||||
layout = self.layout
|
||||
layout.operator(FocusDeformBones.bl_idname)
|
||||
layout.operator(CreateMirrorGroups.bl_idname)
|
||||
layout.operator(FocusDeformBones.bl_idname, icon='ZOOM_IN')
|
||||
layout.operator(CreateMirrorGroups.bl_idname, icon='MOD_MIRROR')
|
||||
# TODO: Add an operator called "Smart Cleanup" that creates missing mirror groups,
|
||||
# Cleans 0 weights,
|
||||
# Deletes unused deforming groups,
|
||||
# and deletes unused non-deforming groups.
|
||||
|
||||
def draw_vertex_group_menu(self, context):
|
||||
layout = self.layout
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_batch_delete')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_mirror')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_sort')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_copy')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_lock')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_weight')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_batch_delete', icon='TRASH')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_mirror', icon='ARROW_LEFTRIGHT')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_sort', icon='SORTALPHA')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_copy', icon='DUPLICATE')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_lock', icon='LOCKED')
|
||||
layout.row().menu(menu='MESH_MT_vertex_group_weight', icon='MOD_VERTEX_WEIGHT')
|
||||
|
||||
classes = [
|
||||
MESH_MT_vertex_group_batch_delete,
|
||||
|
@ -26,6 +26,19 @@ def get_deforming_vgroups(mesh_ob) -> List[bpy.types.VertexGroup]:
|
||||
def get_empty_deforming_vgroups(mesh_ob) -> List[bpy.types.VertexGroup]:
|
||||
deforming_vgroups = get_deforming_vgroups(mesh_ob)
|
||||
empty_deforming_groups = [vg for vg in deforming_vgroups if not vgroup_has_weight(mesh_ob, vg)]
|
||||
|
||||
# Always account for Mirror modifier:
|
||||
if not 'MIRROR' in [m.type for m in mesh_ob.modifiers]:
|
||||
return empty_deforming_groups
|
||||
|
||||
# A group is not considered empty if it is the opposite of a non-empty group.
|
||||
for empty_vg in empty_deforming_groups[:]:
|
||||
opposite_vg = mesh_ob.vertex_groups.get(flip_name(empty_vg.name))
|
||||
if not opposite_vg:
|
||||
continue
|
||||
if opposite_vg not in empty_deforming_groups:
|
||||
empty_deforming_groups.remove(empty_vg)
|
||||
|
||||
return empty_deforming_groups
|
||||
|
||||
def get_non_deforming_vgroups(mesh_ob) -> set:
|
||||
@ -38,7 +51,6 @@ def get_vgroup_weight_on_vert(vgroup, vert_idx) -> float:
|
||||
# only only way to ask Blender if a vertex is assigned to a vertex group.
|
||||
try:
|
||||
w = vgroup.weight(vert_idx)
|
||||
if w > 0:
|
||||
return w
|
||||
except RuntimeError:
|
||||
return -1
|
||||
@ -51,7 +63,7 @@ def vgroup_has_weight(mesh_ob, vgroup) -> bool:
|
||||
|
||||
|
||||
class DeleteEmptyDeformGroups(bpy.types.Operator):
|
||||
"""Delete vertex groups which are associated to deforming bones but don't have any weights."""
|
||||
"""Delete vertex groups which are associated to deforming bones but don't have any weights"""
|
||||
bl_idname = "object.delete_empty_deform_vgroups"
|
||||
bl_label = "Delete Empty Deform Groups"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
@ -64,7 +76,12 @@ class DeleteEmptyDeformGroups(bpy.types.Operator):
|
||||
return all((ob_is_mesh, obj.vertex_groups, ob_has_arm_mod))
|
||||
|
||||
def execute(self, context):
|
||||
delete_vgroups(context.object, get_empty_deforming_vgroups(context.object))
|
||||
empty_groups = get_empty_deforming_vgroups(context.object)
|
||||
num_groups = len(empty_groups)
|
||||
print(f"Deleting empty deform groups:")
|
||||
print(" " + "\n ".join([vg.name for vg in empty_groups]))
|
||||
self.report({'INFO'}, f"Deleted {num_groups} empty deform groups.")
|
||||
delete_vgroups(context.object, empty_groups)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@ -76,15 +93,21 @@ class WeightPaintOperator(bpy.types.Operator):
|
||||
return all((context.mode=='PAINT_WEIGHT', obj, rig, obj.vertex_groups))
|
||||
|
||||
class DeleteUnselectedDeformGroups(WeightPaintOperator):
|
||||
"""Delete deforming vertex groups that do not belong to any of the selected pose bones."""
|
||||
"""Delete deforming vertex groups that do not correspond to any selected pose bone"""
|
||||
bl_idname = "object.delete_unselected_deform_vgroups"
|
||||
bl_label = "Delete Unselected Deform Groups"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
deforming_groups = get_deforming_vgroups(context.object)
|
||||
unselected_def_groups = [vg for vg in deforming_groups if vg.name not in context.selected_pose_bones]
|
||||
selected_bone_names = [b.name for b in context.selected_pose_bones]
|
||||
unselected_def_groups = [vg for vg in deforming_groups if vg.name not in selected_bone_names]
|
||||
|
||||
print(f"Deleting unselected deform groups:")
|
||||
deleted_names = [vg.name for vg in unselected_def_groups]
|
||||
print(" " + "\n ".join(deleted_names))
|
||||
delete_vgroups(context.object, unselected_def_groups)
|
||||
self.report({'INFO'}, f"Deleted {len(deleted_names)} unselected deform groups.")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@ -107,7 +130,7 @@ def reveal_bone(bone, select=True):
|
||||
bone.select = True
|
||||
|
||||
class FocusDeformBones(WeightPaintOperator):
|
||||
"""Reveal the layers of, unhide, and select the bones of all deforming vertex groups."""
|
||||
"""Reveal the layers of, unhide, and select the bones of all deforming vertex groups"""
|
||||
bl_idname = "object.focus_deform_vgroups"
|
||||
bl_label = "Focus Deform Groups"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
@ -150,7 +173,7 @@ def get_shape_key_mask_vgroups(mesh_ob) -> List[bpy.types.VertexGroup]:
|
||||
if vg and vg.name not in mask_vgroups:
|
||||
mask_vgroups.append(vg)
|
||||
|
||||
def delete_unused_vgroups(mesh_ob):
|
||||
def delete_unused_vgroups(mesh_ob) -> List[str]:
|
||||
non_deform_vgroups = get_non_deforming_vgroups(mesh_ob)
|
||||
used_vgroups = []
|
||||
|
||||
@ -167,13 +190,16 @@ def delete_unused_vgroups(mesh_ob):
|
||||
# Constraints: TODO. This is a pretty rare case, and will require checking through the entire blend file.
|
||||
|
||||
groups_to_delete = set(non_deform_vgroups) - set(used_vgroups)
|
||||
names = [vg.name for vg in groups_to_delete]
|
||||
print(f"Deleting unused non-deform groups:")
|
||||
print(" " + "\n ".join(names))
|
||||
delete_vgroups(mesh_ob, groups_to_delete)
|
||||
|
||||
return names
|
||||
|
||||
class DeleteUnusedVertexGroups(bpy.types.Operator):
|
||||
"""Delete non-deforming vertex groups which are not used by any modifiers, shape keys or constraints."""
|
||||
"""Delete non-deforming vertex groups which are not used by any modifiers, shape keys or constraints"""
|
||||
bl_idname = "object.delete_unused_vgroups"
|
||||
bl_label = "Delete Unused Groups"
|
||||
bl_label = "Delete Unused Non-Deform Groups"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
@ -184,12 +210,14 @@ class DeleteUnusedVertexGroups(bpy.types.Operator):
|
||||
return all((ob_is_mesh, ob_has_groups))
|
||||
|
||||
def execute(self, context):
|
||||
delete_unused_vgroups(context.object)
|
||||
deleted_names = delete_unused_vgroups(context.object)
|
||||
|
||||
self.report({'INFO'}, f"Deleted {len(deleted_names)} unused non-deform groups.")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CreateMirrorGroups(bpy.types.Operator):
|
||||
"""Create missing Left/Right vertex groups to ensure correct behaviour of Mirror modifier."""
|
||||
"""Create missing Left/Right vertex groups to ensure correct behaviour of Mirror modifier"""
|
||||
bl_idname = "object.ensure_mirror_vgroups"
|
||||
bl_label = "Ensure Mirror Groups"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
@ -205,11 +233,19 @@ class CreateMirrorGroups(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
deforming_groups = get_deforming_vgroups(obj)
|
||||
new_counter = 0
|
||||
print("Creating missing Mirror groups:")
|
||||
for vg in deforming_groups:
|
||||
flipped_name = flip_name(vg.name)
|
||||
if flipped_name == vg.name:
|
||||
continue
|
||||
if flipped_name in obj.vertex_groups:
|
||||
continue
|
||||
obj.vertex_groups.new(name=flipped_name)
|
||||
print(" "+flipped_name)
|
||||
new_counter += 1
|
||||
|
||||
self.report({'INFO'}, f"Created {new_counter} missing groups")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user