Add Easy_Weight to Addons #47

Merged
Nick Alberelli merged 48 commits from feature/easy_weights into main 2023-05-17 22:13:57 +02:00
Showing only changes of commit f0c2100590 - Show all commits

View File

@ -1,7 +1,8 @@
from typing import List, Tuple from typing import List, Tuple
import bpy import bpy, sys
from bpy.types import Operator, Mesh, VertexGroup, MeshVertex, Object from bpy.types import Operator, Mesh, VertexGroup, MeshVertex, Object
import itertools
import bmesh import bmesh
from .utils.naming import flip_name from .utils.naming import flip_name
@ -268,10 +269,7 @@ def build_vert_index_map(mesh) -> dict:
def find_weight_island_vertices(mesh: Mesh, vert_idx: int, group_index: int, vert_idx_map: dict, island=[]) -> List[int]: def find_weight_island_vertices(mesh: Mesh, vert_idx: int, group_index: int, vert_idx_map: dict, island=[]) -> List[int]:
"""Recursively find all vertices that are connected to a vertex by edges, and are also in the same vertex group.""" """Recursively find all vertices that are connected to a vertex by edges, and are also in the same vertex group."""
# I am sick of bmesh, so you must build a vertex connection map with build_vert_index_map and pass it in here.
mesh.vertices[vert_idx].select = True
island.append(vert_idx) island.append(vert_idx)
for connected_vert_idx in vert_idx_map[vert_idx]: # For each edge connected to the vert for connected_vert_idx in vert_idx_map[vert_idx]: # For each edge connected to the vert
if connected_vert_idx in island: # Avoid infinite recursion! if connected_vert_idx in island: # Avoid infinite recursion!
@ -281,11 +279,11 @@ def find_weight_island_vertices(mesh: Mesh, vert_idx: int, group_index: int, ver
find_weight_island_vertices(mesh, connected_vert_idx, group_index, vert_idx_map, island) # Continue recursion find_weight_island_vertices(mesh, connected_vert_idx, group_index, vert_idx_map, island) # Continue recursion
return island return island
def find_any_vertex_in_group(mesh: Mesh, vgroup: VertexGroup, is_selected=None) -> MeshVertex: def find_any_vertex_in_group(mesh: Mesh, vgroup: VertexGroup, excluded_indicies=[]) -> MeshVertex:
"""Return the index of the first vertex we find which is part of the """Return the index of the first vertex we find which is part of the
vertex group and optinally, has a specified selection state.""" vertex group and optinally, has a specified selection state."""
for v in mesh.vertices: for v in mesh.vertices:
if is_selected != None and v.select != is_selected: if v.index in excluded_indicies:
continue continue
for g in v.groups: for g in v.groups:
if vgroup.index == g.group: if vgroup.index == g.group:
@ -296,10 +294,13 @@ def build_weight_islands_in_group(mesh: Mesh, vgroup: VertexGroup, vert_index_ma
"""Return a list of lists of vertex indicies: Weight islands within this vertex group.""" """Return a list of lists of vertex indicies: Weight islands within this vertex group."""
islands = [] islands = []
while True: while True:
any_unselected_vertex_in_group = find_any_vertex_in_group(mesh, vgroup, is_selected=False) flat_islands = set(itertools.chain.from_iterable(islands))
any_unselected_vertex_in_group = find_any_vertex_in_group(mesh, vgroup, excluded_indicies=flat_islands)
if not any_unselected_vertex_in_group: if not any_unselected_vertex_in_group:
break break
island = find_weight_island_vertices(mesh, any_unselected_vertex_in_group.index, vgroup.index, vert_index_map, []) sys.setrecursionlimit(len(mesh.vertices)) # TODO: I guess recursion is bad and we should avoid it here?
island = find_weight_island_vertices(mesh, any_unselected_vertex_in_group.index, vgroup.index, vert_index_map, island=[])
sys.setrecursionlimit(990)
islands.append(island) islands.append(island)
return islands return islands
@ -309,7 +310,7 @@ def select_vertices(mesh: Mesh, vert_indicies: List[int]):
mesh.vertices[vi].select = True mesh.vertices[vi].select = True
class FocusRogueDeformingWeights(WeightPaintOperator): class FocusRogueDeformingWeights(WeightPaintOperator):
"""While in weight paint mode, find and focus a deforming vertex group which consists of several islands. Automatic fixing is dangerous, so this operator does not remove weights automatically. Keep using this until no more rogue weights are found""" """While in weight paint mode, find and focus a deforming vertex group which consists of several islands. The smallest weight island is selected, but no weights are removed. Keep running this until no more rogue weights are found"""
bl_idname = "object.focus_rogue_weights" bl_idname = "object.focus_rogue_weights"
bl_label = "Focus Rogue Weights" bl_label = "Focus Rogue Weights"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -326,10 +327,10 @@ class FocusRogueDeformingWeights(WeightPaintOperator):
obj['skip_groups'] = l obj['skip_groups'] = l
@staticmethod @staticmethod
def find_vgroup_with_multiple_islands(obj: Object, vert_index_map: dict) -> Tuple[VertexGroup, List[List[int]]]: def find_rogue_deform_weights(obj: Object, vert_index_map: dict) -> Tuple[VertexGroup, List[List[int]]]:
"""Return the first vertex group we find that has multiple islands, as well as the islands.""" """Return the first vertex group we find that has multiple islands, as well as the islands."""
mesh = obj.data mesh = obj.data
for vgroup in obj.vertex_groups: for vgroup in get_deforming_vgroups(obj):
if 'skip_groups' in obj and vgroup.name in obj['skip_groups']: if 'skip_groups' in obj and vgroup.name in obj['skip_groups']:
continue continue
obj.vertex_groups.active_index = vgroup.index obj.vertex_groups.active_index = vgroup.index
@ -339,6 +340,8 @@ class FocusRogueDeformingWeights(WeightPaintOperator):
if len(islands) > 1: if len(islands) > 1:
return vgroup, islands return vgroup, islands
return None, None
def execute(self, context): def execute(self, context):
rig = context.pose_object rig = context.pose_object
obj = context.object obj = context.object
@ -347,35 +350,35 @@ class FocusRogueDeformingWeights(WeightPaintOperator):
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='VERT') bpy.ops.mesh.select_mode(type='VERT')
bpy.ops.mesh.reveal()
bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_all(action='DESELECT')
mesh = obj.data mesh = obj.data
vert_index_map = build_vert_index_map(mesh) vert_index_map = build_vert_index_map(mesh)
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
vgroup, islands = self.find_vgroup_with_multiple_islands(obj, vert_index_map) vgroup, islands = self.find_rogue_deform_weights(obj, vert_index_map)
if vgroup: if vgroup:
# Select islands except the one with the most verts. # Select the smallest island.
largest_island = max(islands, key=len) select_vertices(mesh, min(islands, key=len))
for island in islands:
if island == largest_island: continue
select_vertices(mesh, island)
# Support the case where the user chooses not to fix the rogue weights: Perhaps they are intentional # Support the case where the user chooses not to fix the rogue weights: Perhaps they are intentional
self.save_skip_group(obj, vgroup) self.save_skip_group(obj, vgroup)
self.report({'INFO'}, f'Found Vertex Group "{vgroup.name}" with {len(islands)} islands. Fix or ignore it, then keep running this operator to find the next group with rogue weights.') self.report({'INFO'}, f'Found Vertex Group "{vgroup.name}" with {len(islands)} islands. Fix or ignore it, then keep running this operator to find the next group with rogue weights.')
mesh.use_paint_mask_vertex = True
if rig: if rig:
rig.select_set(True) rig.select_set(True)
mesh.use_paint_mask_vertex = True
bpy.ops.object.mode_set(mode='WEIGHT_PAINT') bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
else: else:
if rig:
rig.select_set(True)
obj.vertex_groups.active_index = org_vg_idx obj.vertex_groups.active_index = org_vg_idx
bpy.ops.object.mode_set(mode=org_mode) bpy.ops.object.mode_set(mode=org_mode)
if 'skip_groups' in obj and len(obj['skip_groups']) > 0: if 'skip_groups' in obj and len(obj['skip_groups']) > 0:
self.report({'INFO'}, f"No rogue weights found, but {len(obj['skip_groups'])} were skipped. Run the operator again to cycle through groups with rogue weights.") self.report({'INFO'}, f"No rogue weights found, but {len(obj['skip_groups'])} were skipped. Keep running the operator to cycle through groups with multiple weight islands to make sure they are desired.")
del obj['skip_groups'] del obj['skip_groups']
else: else:
self.report({'INFO'}, "No rogue weights found!") self.report({'INFO'}, "No rogue weights found!")