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
import bpy
import bpy, sys
from bpy.types import Operator, Mesh, VertexGroup, MeshVertex, Object
import itertools
import bmesh
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]:
"""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)
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!
@ -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
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
vertex group and optinally, has a specified selection state."""
for v in mesh.vertices:
if is_selected != None and v.select != is_selected:
if v.index in excluded_indicies:
continue
for g in v.groups:
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."""
islands = []
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:
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)
return islands
@ -309,7 +310,7 @@ def select_vertices(mesh: Mesh, vert_indicies: List[int]):
mesh.vertices[vi].select = True
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_label = "Focus Rogue Weights"
bl_options = {'REGISTER', 'UNDO'}
@ -326,10 +327,10 @@ class FocusRogueDeformingWeights(WeightPaintOperator):
obj['skip_groups'] = l
@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."""
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']:
continue
obj.vertex_groups.active_index = vgroup.index
@ -338,6 +339,8 @@ class FocusRogueDeformingWeights(WeightPaintOperator):
if len(islands) > 1:
return vgroup, islands
return None, None
def execute(self, context):
rig = context.pose_object
@ -347,35 +350,35 @@ class FocusRogueDeformingWeights(WeightPaintOperator):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='VERT')
bpy.ops.mesh.reveal()
bpy.ops.mesh.select_all(action='DESELECT')
mesh = obj.data
vert_index_map = build_vert_index_map(mesh)
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:
# Select islands except the one with the most verts.
largest_island = max(islands, key=len)
for island in islands:
if island == largest_island: continue
select_vertices(mesh, island)
# Select the smallest island.
select_vertices(mesh, min(islands, key=len))
# Support the case where the user chooses not to fix the rogue weights: Perhaps they are intentional
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.')
mesh.use_paint_mask_vertex = True
if rig:
rig.select_set(True)
mesh.use_paint_mask_vertex = True
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
else:
if rig:
rig.select_set(True)
obj.vertex_groups.active_index = org_vg_idx
bpy.ops.object.mode_set(mode=org_mode)
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']
else:
self.report({'INFO'}, "No rogue weights found!")