Add Easy_Weight
to Addons
#47
195
rogue_weights.py
195
rogue_weights.py
@ -17,6 +17,40 @@ All functionality can be found in the Sidebar->EasyWeight->Weight Islands panel.
|
|||||||
# TODO:
|
# TODO:
|
||||||
# UIList: Filtering options, explanations as to what the numbers mean. Maybe a warning for Calculate Islands operator when the mesh has a lot of verts or vgroups.
|
# UIList: Filtering options, explanations as to what the numbers mean. Maybe a warning for Calculate Islands operator when the mesh has a lot of verts or vgroups.
|
||||||
|
|
||||||
|
class VertIndex(PropertyGroup):
|
||||||
|
index: IntProperty()
|
||||||
|
|
||||||
|
class WeightIsland(PropertyGroup):
|
||||||
|
vert_indicies: CollectionProperty(type=VertIndex) # TODO: Is this really needed?? Why can't a CollectionProperty(type=IntProperty) be fine??
|
||||||
|
|
||||||
|
class IslandGroup(PropertyGroup):
|
||||||
|
name: StringProperty() # Name of the vertex group this set of island is associated with
|
||||||
|
islands: CollectionProperty(type=WeightIsland)
|
||||||
|
num_expected_islands: IntProperty(
|
||||||
|
name="Expected Islands",
|
||||||
|
default=1,
|
||||||
|
min=1,
|
||||||
|
description="Number of weight islands that have been marked as the expected amount by the user. If the real amount differs from this value, a warning appears"
|
||||||
|
)
|
||||||
|
index: IntProperty()
|
||||||
|
|
||||||
|
def update_vgroup_islands(mesh, vgroup, vert_index_map, island_groups, island_group=None) -> IslandGroup:
|
||||||
|
islands = get_islands_of_vgroup(mesh, vgroup, vert_index_map)
|
||||||
|
|
||||||
|
if not island_group:
|
||||||
|
island_group = island_groups.add()
|
||||||
|
island_group.index = len(island_groups)-1
|
||||||
|
island_group.name = vgroup.name
|
||||||
|
else:
|
||||||
|
island_group.islands.clear()
|
||||||
|
for island in islands:
|
||||||
|
island_storage = island_group.islands.add()
|
||||||
|
for v_idx in island:
|
||||||
|
v_idx_storage = island_storage.vert_indicies.add()
|
||||||
|
v_idx_storage.index = v_idx
|
||||||
|
|
||||||
|
return island_group
|
||||||
|
|
||||||
def build_vert_index_map(mesh) -> dict:
|
def build_vert_index_map(mesh) -> dict:
|
||||||
"""Build a dictionary of vertex indicies pointing to a list of other vertex indicies that the vertex is connected to by an edge."""
|
"""Build a dictionary of vertex indicies pointing to a list of other vertex indicies that the vertex is connected to by an edge."""
|
||||||
|
|
||||||
@ -81,45 +115,11 @@ def get_islands_of_vgroup(mesh: Mesh, vgroup: VertexGroup, vert_index_map: dict)
|
|||||||
islands.append(island)
|
islands.append(island)
|
||||||
return islands
|
return islands
|
||||||
|
|
||||||
def update_vgroup_islands(mesh, vgroup, vert_index_map, island_groups, island_group=None) -> IslandGroup:
|
|
||||||
islands = get_islands_of_vgroup(mesh, vgroup, vert_index_map)
|
|
||||||
|
|
||||||
if not island_group:
|
|
||||||
island_group = island_groups.add()
|
|
||||||
island_group.index = len(island_groups)-1
|
|
||||||
island_group.name = vgroup.name
|
|
||||||
else:
|
|
||||||
island_group.islands.clear()
|
|
||||||
for island in islands:
|
|
||||||
island_storage = island_group.islands.add()
|
|
||||||
for v_idx in island:
|
|
||||||
v_idx_storage = island_storage.vert_indicies.add()
|
|
||||||
v_idx_storage.index = v_idx
|
|
||||||
|
|
||||||
return island_group
|
|
||||||
|
|
||||||
def select_vertices(mesh: Mesh, vert_indicies: List[int]):
|
def select_vertices(mesh: Mesh, vert_indicies: List[int]):
|
||||||
assert bpy.context.mode != 'EDIT_MESH', "Object must not be in edit mode, otherwise vertex selection doesn't work!"
|
assert bpy.context.mode != 'EDIT_MESH', "Object must not be in edit mode, otherwise vertex selection doesn't work!"
|
||||||
for vi in vert_indicies:
|
for vi in vert_indicies:
|
||||||
mesh.vertices[vi].select = True
|
mesh.vertices[vi].select = True
|
||||||
|
|
||||||
class VertIndex(PropertyGroup):
|
|
||||||
index: IntProperty()
|
|
||||||
|
|
||||||
class WeightIsland(PropertyGroup):
|
|
||||||
vert_indicies: CollectionProperty(type=VertIndex) # TODO: Is this really needed?? Why can't a CollectionProperty(type=IntProperty) be fine??
|
|
||||||
|
|
||||||
class IslandGroup(PropertyGroup):
|
|
||||||
name: StringProperty() # Name of the vertex group this set of island is associated with
|
|
||||||
islands: CollectionProperty(type=WeightIsland)
|
|
||||||
num_expected_islands: IntProperty(
|
|
||||||
name="Expected Islands",
|
|
||||||
default=1,
|
|
||||||
min=1,
|
|
||||||
description="Number of weight islands that have been marked as the expected amount by the user. If the real amount differs from this value, a warning appears"
|
|
||||||
)
|
|
||||||
index: IntProperty()
|
|
||||||
|
|
||||||
class MarkIslandsAsOkay(Operator):
|
class MarkIslandsAsOkay(Operator):
|
||||||
"""Mark this number of vertex islands to be the intended amount. Vertex group will be hidden from the list until this number changes"""
|
"""Mark this number of vertex islands to be the intended amount. Vertex group will be hidden from the list until this number changes"""
|
||||||
bl_idname = "object.set_expected_island_count"
|
bl_idname = "object.set_expected_island_count"
|
||||||
@ -286,6 +286,83 @@ class CalculateWeightIslands(Operator):
|
|||||||
bpy.ops.object.mode_set(mode=org_mode)
|
bpy.ops.object.mode_set(mode=org_mode)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class EASYWEIGHT_UL_weight_island_groups(UIList):
|
||||||
|
@staticmethod
|
||||||
|
def draw_header(layout):
|
||||||
|
row = layout.row()
|
||||||
|
split1 = row.split(factor=0.5)
|
||||||
|
row1 = split1.row()
|
||||||
|
row1.label(text="Vertex Group")
|
||||||
|
row1.alignment = 'RIGHT'
|
||||||
|
row1.label(text="|")
|
||||||
|
row2 = split1.row()
|
||||||
|
row2.label(text="Islands")
|
||||||
|
|
||||||
|
def filter_items(self, context, data, propname):
|
||||||
|
flt_flags = []
|
||||||
|
flt_neworder = []
|
||||||
|
list_items = getattr(data, propname)
|
||||||
|
|
||||||
|
island_groups = getattr(data, propname)
|
||||||
|
|
||||||
|
helper_funcs = bpy.types.UI_UL_list
|
||||||
|
|
||||||
|
if self.filter_name:
|
||||||
|
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, island_groups, "name",
|
||||||
|
reverse=self.use_filter_sort_reverse)
|
||||||
|
|
||||||
|
if not flt_flags:
|
||||||
|
flt_flags = [self.bitflag_filter_item] * len(island_groups)
|
||||||
|
|
||||||
|
if self.use_filter_invert:
|
||||||
|
for idx, flag in enumerate(flt_flags):
|
||||||
|
flt_flags[idx] = 0 if flag else self.bitflag_filter_item
|
||||||
|
|
||||||
|
for idx, island_group in enumerate(island_groups):
|
||||||
|
if len(island_group.islands) < 1:
|
||||||
|
# Filter island groups with only 1 or 0 islands in them
|
||||||
|
flt_flags[idx] = 0
|
||||||
|
elif len(island_group.islands) == island_group.num_expected_islands:
|
||||||
|
# Filter island groups with the expected number of islands in them
|
||||||
|
flt_flags[idx] = 0
|
||||||
|
|
||||||
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
def draw_filter(self, context, layout):
|
||||||
|
# Nothing much to say here, it's usual UI code...
|
||||||
|
main_row = layout.row()
|
||||||
|
row = main_row.row(align=True)
|
||||||
|
|
||||||
|
row.prop(self, 'filter_name', text="")
|
||||||
|
row.prop(self, 'use_filter_invert', toggle=True, text="", icon='ARROW_LEFTRIGHT')
|
||||||
|
|
||||||
|
row = main_row.row(align=True)
|
||||||
|
row.use_property_split=True
|
||||||
|
row.use_property_decorate=False
|
||||||
|
row.prop(self, 'use_filter_sort_alpha', toggle=True, text="")
|
||||||
|
row.prop(self, 'use_filter_sort_reverse', toggle=True, text="", icon='SORT_ASC')
|
||||||
|
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||||
|
island_group = item
|
||||||
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||||
|
icon = 'ERROR'
|
||||||
|
num_islands = len(island_group.islands)
|
||||||
|
if num_islands == island_group.num_expected_islands:
|
||||||
|
icon = 'CHECKMARK'
|
||||||
|
row = layout.row()
|
||||||
|
split = row.split(factor=0.5)
|
||||||
|
row1 = split.row()
|
||||||
|
row1.label(text=island_group.name)
|
||||||
|
row1.alignment = 'RIGHT'
|
||||||
|
row1.label(text="|")
|
||||||
|
row2 = split.row()
|
||||||
|
row2.label(text=str(num_islands), icon=icon)
|
||||||
|
op = row2.operator(FocusSmallestIsland.bl_idname, text="", icon='VIEWZOOM').vgroup = island_group.name
|
||||||
|
row2.operator(MarkIslandsAsOkay.bl_idname, text="", icon='CHECKMARK').vgroup = island_group.name
|
||||||
|
# TODO: Operator to mark current number of islands as the expected amount
|
||||||
|
elif self.layout_type in {'GRID'}:
|
||||||
|
pass
|
||||||
|
|
||||||
class EASYWEIGHT_PT_WeightIslands(Panel):
|
class EASYWEIGHT_PT_WeightIslands(Panel):
|
||||||
"""Panel with utilities for detecting rogue weights."""
|
"""Panel with utilities for detecting rogue weights."""
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
@ -306,8 +383,9 @@ class EASYWEIGHT_PT_WeightIslands(Panel):
|
|||||||
if len(island_groups)==0: return
|
if len(island_groups)==0: return
|
||||||
active_weight_islands = obj.island_groups[obj.active_islands_index]
|
active_weight_islands = obj.island_groups[obj.active_islands_index]
|
||||||
|
|
||||||
row = layout.row()
|
EASYWEIGHT_UL_weight_island_groups.draw_header(layout)
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
row.template_list(
|
row.template_list(
|
||||||
'EASYWEIGHT_UL_weight_island_groups',
|
'EASYWEIGHT_UL_weight_island_groups',
|
||||||
'',
|
'',
|
||||||
@ -317,53 +395,6 @@ class EASYWEIGHT_PT_WeightIslands(Panel):
|
|||||||
'active_islands_index',
|
'active_islands_index',
|
||||||
)
|
)
|
||||||
|
|
||||||
class EASYWEIGHT_UL_weight_island_groups(UIList):
|
|
||||||
def filter_items(self, context, data, propname):
|
|
||||||
flt_flags = []
|
|
||||||
flt_neworder = []
|
|
||||||
list_items = getattr(data, propname)
|
|
||||||
|
|
||||||
island_groups = getattr(data, propname)
|
|
||||||
|
|
||||||
helper_funcs = bpy.types.UI_UL_list
|
|
||||||
|
|
||||||
if self.filter_name:
|
|
||||||
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, island_groups, "name",
|
|
||||||
reverse=self.use_filter_name_reverse)
|
|
||||||
|
|
||||||
if not flt_flags:
|
|
||||||
flt_flags = [self.bitflag_filter_item] * len(island_groups)
|
|
||||||
|
|
||||||
if self.use_filter_invert:
|
|
||||||
for idx, flag in enumerate(flt_flags):
|
|
||||||
flt_flags[idx] = 0 if flag else self.bitflag_filter_item
|
|
||||||
|
|
||||||
for idx, island_group in enumerate(island_groups):
|
|
||||||
if len(island_group.islands) < 1:
|
|
||||||
# Filter island groups with only 1 or 0 islands in them
|
|
||||||
flt_flags[idx] = 0
|
|
||||||
elif len(island_group.islands) == island_group.num_expected_islands:
|
|
||||||
# Filter island groups with the expected number of islands in them
|
|
||||||
flt_flags[idx] = 0
|
|
||||||
|
|
||||||
return flt_flags, flt_neworder
|
|
||||||
|
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
|
||||||
island_group = item
|
|
||||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
|
||||||
icon = 'ERROR'
|
|
||||||
num_islands = len(island_group.islands)
|
|
||||||
if num_islands == island_group.num_expected_islands:
|
|
||||||
icon = 'CHECKMARK'
|
|
||||||
row = layout.row()
|
|
||||||
row.label(text=island_group.name)
|
|
||||||
row.label(text=str(num_islands), icon=icon)
|
|
||||||
op = row.operator(FocusSmallestIsland.bl_idname, text="", icon='VIEWZOOM').vgroup = island_group.name
|
|
||||||
row.operator(MarkIslandsAsOkay.bl_idname, text="", icon='CHECKMARK').vgroup = island_group.name
|
|
||||||
# TODO: Operator to mark current number of islands as the expected amount
|
|
||||||
elif self.layout_type in {'GRID'}:
|
|
||||||
pass
|
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
VertIndex,
|
VertIndex,
|
||||||
WeightIsland,
|
WeightIsland,
|
||||||
|
Loading…
Reference in New Issue
Block a user