mirror of
https://github.com/blender/blender-addons-contrib.git
synced 2025-07-21 23:47:35 +00:00
523 lines
19 KiB
Python
523 lines
19 KiB
Python
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
|
|
bl_info = {
|
|
"name": "Show Vertex Groups/Weights",
|
|
"author": "Jason van Gumster (Fweeb), Bartius Crouch, CoDEmanX",
|
|
"version": (0, 7, 2),
|
|
"blender": (2, 80, 4),
|
|
"location": "3D View > Properties Region > Show Weights",
|
|
"description": "Finds the vertex groups of a selected vertex "
|
|
"and displays the corresponding weights",
|
|
"warning": "",
|
|
"doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
|
"Scripts/Modeling/Show_Vertex_Group_Weights",
|
|
"tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
|
|
"category": "Mesh",
|
|
}
|
|
|
|
#TODO - Add button for selecting vertices with no groups
|
|
|
|
|
|
import bpy, bmesh, bgl, blf, mathutils
|
|
|
|
|
|
# Borrowed/Modified from Bart Crouch's old Index Visualizer add-on
|
|
def calc_callback(self, context):
|
|
#polling
|
|
if context.mode != "EDIT_MESH" or len(context.active_object.vertex_groups) == 0:
|
|
return
|
|
|
|
# get color info from theme
|
|
acol = context.preferences.themes[0].view_3d.editmesh_active
|
|
tcol = (acol[0] * 0.85, acol[1] * 0.85, acol[2] * 0.85)
|
|
|
|
# get screen information
|
|
mid_x = context.region.width / 2.0
|
|
mid_y = context.region.height / 2.0
|
|
width = context.region.width
|
|
height = context.region.height
|
|
|
|
# get matrices
|
|
view_mat = context.space_data.region_3d.perspective_matrix
|
|
ob_mat = context.active_object.matrix_world
|
|
total_mat = view_mat @ ob_mat
|
|
|
|
# calculate location info
|
|
texts = []
|
|
locs = []
|
|
weights = []
|
|
me = context.active_object.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
|
|
verts_max = context.scene.show_vgroups_weights_limit
|
|
verts_used = []
|
|
|
|
for elem in reversed(bm.select_history):
|
|
if not isinstance(elem, bmesh.types.BMVert): #or elem.hide:
|
|
continue
|
|
if bm.select_history.active == elem:
|
|
locs.append([acol[0], acol[1], acol[2], elem.index, elem.co.to_4d()])
|
|
else:
|
|
locs.append([tcol[0], tcol[1], tcol[2], elem.index, elem.co.to_4d()])
|
|
dvert = elem[dvert_lay]
|
|
for vgroup in context.active_object.vertex_groups:
|
|
if vgroup.index in dvert.keys():
|
|
weights += [elem.index, vgroup.index, dvert[vgroup.index]]
|
|
verts_used.append(elem)
|
|
verts_max -= 1
|
|
if verts_max <= 0:
|
|
break
|
|
|
|
for v in bm.verts:
|
|
if v.select and v not in verts_used: #XXX Should check v.hide here, but it doesn't work
|
|
if isinstance(bm.select_history.active, bmesh.types.BMVert) and bm.select_history.active.index == v.index:
|
|
locs.append([acol[0], acol[1], acol[2], v.index, v.co.to_4d()])
|
|
else:
|
|
locs.append([tcol[0], tcol[1], tcol[2], v.index, v.co.to_4d()])
|
|
dvert = v[dvert_lay]
|
|
for vgroup in context.active_object.vertex_groups:
|
|
if vgroup.index in dvert.keys():
|
|
weights += [v.index, vgroup.index, dvert[vgroup.index]]
|
|
verts_max -= 1
|
|
if verts_max <= 0:
|
|
break
|
|
|
|
|
|
for loc in locs:
|
|
vec = total_mat @ loc[4] # order is important
|
|
# dehomogenise
|
|
vec = mathutils.Vector((vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]))
|
|
x = int(mid_x + vec[0] * width / 2.0)
|
|
y = int(mid_y + vec[1] * height / 2.0)
|
|
texts += [loc[0], loc[1], loc[2], loc[3], x, y, 0]
|
|
|
|
# store as ID property in mesh
|
|
context.active_object.data["show_vgroup_verts"] = texts
|
|
context.active_object.data["show_vgroup_weights"] = weights
|
|
|
|
|
|
# draw in 3d-view
|
|
def draw_callback(self, context):
|
|
# polling
|
|
if context.mode != "EDIT_MESH" or len(context.active_object.vertex_groups) == 0:
|
|
return
|
|
# retrieving ID property data
|
|
try:
|
|
texts = context.active_object.data["show_vgroup_verts"]
|
|
weights = context.active_object.data["show_vgroup_weights"]
|
|
except:
|
|
return
|
|
if not texts:
|
|
return
|
|
|
|
bm = bmesh.from_edit_mesh(context.active_object.data)
|
|
|
|
if bm.select_mode == {'VERT'} and bm.select_history.active is not None:
|
|
active_vert = bm.select_history.active
|
|
else:
|
|
active_vert = None
|
|
|
|
# draw
|
|
blf.size(0, 13, 72)
|
|
blf.enable(0, blf.SHADOW)
|
|
blf.shadow(0, 3, 0.0, 0.0, 0.0, 1.0)
|
|
blf.shadow_offset(0, 2, -2)
|
|
for i in range(0, len(texts), 7):
|
|
bgl.glColor3f(texts[i], texts[i+1], texts[i+2])
|
|
blf.position(0, texts[i+4], texts[i+5], texts[i+6])
|
|
blf.draw(0, "Vertex " + str(int(texts[i+3])) + ":")
|
|
font_y = texts[i+5]
|
|
group_name = ""
|
|
for j in range(0, len(weights), 3):
|
|
if int(weights[j]) == int(texts[i+3]):
|
|
font_y -= 13
|
|
blf.position(0, texts[i+4] + 10, font_y, texts[i+6])
|
|
for group in context.active_object.vertex_groups:
|
|
if group.index == int(weights[j+1]):
|
|
group_name = group.name
|
|
break
|
|
blf.draw(0, group_name + ": %.3f" % weights[j+2])
|
|
if group_name == "":
|
|
font_y -= 13
|
|
blf.position(0, texts[i+4] + 10, font_y, texts[i+6])
|
|
blf.draw(0, "No Groups")
|
|
|
|
# restore defaults
|
|
blf.disable(0, blf.SHADOW)
|
|
|
|
|
|
# operator
|
|
class ShowVGroupWeights(bpy.types.Operator):
|
|
bl_idname = "view3d.show_vgroup_weights"
|
|
bl_label = "Show Vertex Group Weights"
|
|
bl_description = "Toggle the display of the vertex groups and weights for selected vertices"
|
|
|
|
_handle_calc = None
|
|
_handle_draw = None
|
|
|
|
@staticmethod
|
|
def handle_add(self, context):
|
|
ShowVGroupWeights._handle_calc = bpy.types.SpaceView3D.draw_handler_add(
|
|
calc_callback, (self, context), 'WINDOW', 'POST_VIEW')
|
|
ShowVGroupWeights._handle_draw = bpy.types.SpaceView3D.draw_handler_add(
|
|
draw_callback, (self, context), 'WINDOW', 'POST_PIXEL')
|
|
|
|
@staticmethod
|
|
def handle_remove():
|
|
if ShowVGroupWeights._handle_calc is not None:
|
|
bpy.types.SpaceView3D.draw_handler_remove(ShowVGroupWeights._handle_calc, 'WINDOW')
|
|
if ShowVGroupWeights._handle_draw is not None:
|
|
bpy.types.SpaceView3D.draw_handler_remove(ShowVGroupWeights._handle_draw, 'WINDOW')
|
|
ShowVGroupWeights._handle_calc = None
|
|
ShowVGroupWeights._handle_draw = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
if context.area.type == 'VIEW_3D':
|
|
if not context.scene.show_vgroups_weights:
|
|
# operator is called and not running, start everything
|
|
ShowVGroupWeights.handle_add(self, context)
|
|
context.scene.show_vgroups_weights = True
|
|
else:
|
|
# operator is called again, stop displaying
|
|
ShowVGroupWeights.handle_remove()
|
|
context.scene.show_vgroups_weights = False
|
|
clear_properties(full=False)
|
|
context.area.tag_redraw()
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "View3D not found, can't run operator")
|
|
return {'CANCELLED'}
|
|
|
|
class VGroupsWeights(bpy.types.PropertyGroup):
|
|
vgroup: bpy.props.IntProperty()
|
|
weight: bpy.props.FloatProperty(min=0.0, max=1.0)
|
|
|
|
class AssignVertexWeight(bpy.types.Operator):
|
|
bl_idname = "mesh.vertex_group_assign"
|
|
bl_label = "Assign Weights"
|
|
bl_description = "Assign weights for all of the groups on a specific vertex"
|
|
|
|
index: bpy.props.IntProperty()
|
|
|
|
vgroup_weights: bpy.props.CollectionProperty(
|
|
description="Vertex Group Weights",
|
|
type=VGroupsWeights)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
me = context.active_object.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
weights = {}
|
|
for item in self.vgroup_weights:
|
|
weights[item.vgroup] = item.weight
|
|
|
|
for v in bm.verts:
|
|
if v.index == self.index:
|
|
dvert = v[dvert_lay]
|
|
for vgroup in dvert.keys():
|
|
dvert[vgroup] = weights[vgroup]
|
|
break
|
|
context.area.tag_redraw()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class RemoveFromVertexGroup(bpy.types.Operator):
|
|
bl_idname = "mesh.vertex_group_remove"
|
|
bl_label = "Remove Vertex from Group"
|
|
bl_description = "Remove a specific vertex from a specific vertex group"
|
|
|
|
#XXX abusing vector props here a bit; the first element is the vert index and the second is the group index
|
|
vert_and_group: bpy.props.IntVectorProperty(name = "Vertex and Group to remove", size = 2)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
ob = context.active_object
|
|
me = ob.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
|
|
# Save current selection
|
|
selected_verts = []
|
|
for v in bm.verts:
|
|
if v.select is True:
|
|
selected_verts.append(v.index)
|
|
if v.index != self.vert_and_group[0]:
|
|
v.select = False
|
|
|
|
ob.vertex_groups.active_index = self.vert_and_group[1]
|
|
bpy.ops.object.vertex_group_remove_from()
|
|
|
|
# Re-select vertices
|
|
for v in bm.verts:
|
|
if v.index in selected_verts:
|
|
v.select = True
|
|
|
|
#XXX Hacky, but there's no other way to update the UI panels
|
|
bpy.ops.object.editmode_toggle()
|
|
bpy.ops.object.editmode_toggle()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class AddToVertexGroup(bpy.types.Operator):
|
|
bl_idname = "mesh.vertex_group_add"
|
|
bl_label = "Add Vertex to Group"
|
|
bl_description = "Add a specific vertex to a specific vertex group"
|
|
|
|
def avail_vgroups(self, context):
|
|
if context is None:
|
|
return []
|
|
ob = context.active_object
|
|
bm = bmesh.from_edit_mesh(ob.data)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
items = []
|
|
self.vertex = bm.select_history.active.index
|
|
|
|
dvert = bm.select_history.active[dvert_lay]
|
|
|
|
#XXX since we need an identifier here, user won't be able to add a vgroup with that name ('-1')
|
|
#XXX could check against vgroup names and find an unused name, but it's a rare case after all.
|
|
items.append(("-1", "New Vertex Group", "Add a new vertex group to the active object", -1))
|
|
|
|
for i in ob.vertex_groups:
|
|
if i.index not in dvert.keys():
|
|
items.append((i.name, i.name, str(i.index), i.index))
|
|
|
|
return items
|
|
|
|
vertex: bpy.props.IntProperty()
|
|
available_vgroups: bpy.props.EnumProperty(items=avail_vgroups, name="Available Groups")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
ob = context.active_object
|
|
me = ob.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
#print(self.available_vgroups)
|
|
|
|
# Save current selection
|
|
selected_verts = []
|
|
for v in bm.verts:
|
|
if v.select is True:
|
|
selected_verts.append(v.index)
|
|
if v.index != self.vertex:
|
|
v.select = False
|
|
|
|
weight = context.tool_settings.vertex_group_weight
|
|
context.tool_settings.vertex_group_weight = 1.0
|
|
if self.available_vgroups == "-1":
|
|
bpy.ops.object.vertex_group_assign(new=True) #XXX Assumes self.vertex is the active vertex
|
|
else:
|
|
bpy.ops.object.vertex_group_set_active(group = self.available_vgroups)
|
|
bpy.ops.object.vertex_group_assign() #XXX Assumes self.vertex is the active vertex
|
|
context.tool_settings.vertex_group_weight = weight
|
|
|
|
# Re-select vertices
|
|
for v in bm.verts:
|
|
if v.index in selected_verts:
|
|
v.select = True
|
|
|
|
#XXX Hacky, but there's no other way to update the UI panels
|
|
bpy.ops.object.editmode_toggle()
|
|
bpy.ops.object.editmode_toggle()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class MESH_PT_ShowWeights(bpy.types.Panel):
|
|
bl_label = "Show Weights"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
bl_category = "Edit"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'EDIT_MESH'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
ob = context.active_object
|
|
me = ob.data
|
|
bm = bmesh.from_edit_mesh(me)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
|
|
row = layout.row(align=True)
|
|
|
|
# text = "Show" if not context.scene.show_vgroups_weights else "Hide"
|
|
# row.operator(ShowVGroupWeights.bl_idname, text=text)
|
|
# row.prop(context.scene, "show_vgroups_weights_limit")
|
|
|
|
if len(ob.vertex_groups) > 0:
|
|
# Active vertex
|
|
active_vert = bm.select_history.active
|
|
sub = layout.box()
|
|
col = sub.column(align = True)
|
|
if bm.select_mode == {'VERT'} and active_vert is not None:
|
|
col.label(text = "Active Vertex")
|
|
row = col.row()
|
|
row.label(text = "Vertex " + str(active_vert.index) + ":")
|
|
row.operator_menu_enum("mesh.vertex_group_add", "available_vgroups", text = "Add Group", icon = 'GROUP_VERTEX')
|
|
has_groups = False
|
|
vgroup_weights = []
|
|
|
|
for i in me.vertices:
|
|
if i.index == active_vert.index:
|
|
vgroup_weights_index = i.index
|
|
for j in range(len(i.groups)):
|
|
for k in ob.vertex_groups:
|
|
if k.index == i.groups[j].group:
|
|
has_groups = True
|
|
split = col.split(factor = 0.90, align = True)
|
|
vgroup_weights.append((k.index, i.groups[j].weight))
|
|
row = split.row(align = True)
|
|
row.prop(i.groups[j], "weight", text = k.name, slider = True, emboss = not k.lock_weight)
|
|
row = split.row(align = True)
|
|
row.operator("mesh.vertex_group_remove", text="", icon='X').vert_and_group = (i.index, k.index)
|
|
|
|
if not has_groups:
|
|
col.label(text = " No Groups")
|
|
else:
|
|
props = col.operator("mesh.vertex_group_assign")
|
|
props.index = vgroup_weights_index
|
|
|
|
for vgroup, weight in vgroup_weights:
|
|
item = props.vgroup_weights.add()
|
|
item.vgroup = vgroup
|
|
item.weight = weight
|
|
|
|
layout.separator()
|
|
else:
|
|
col.label(text = "No Active Vertex")
|
|
layout.prop(context.window_manager, "show_vgroups_show_all", toggle = True)
|
|
# All selected vertices (except for the active vertex)
|
|
if context.window_manager.show_vgroups_show_all:
|
|
for v in bm.verts:
|
|
if v.select:
|
|
if active_vert is not None and v.index == active_vert.index:
|
|
continue
|
|
sub = layout.box()
|
|
col = sub.column(align = True)
|
|
col.label(text = "Vertex " + str(v.index) + ":")
|
|
has_groups = False
|
|
vgroup_weights = []
|
|
for i in me.vertices:
|
|
if i.index == v.index:
|
|
vgroup_weights_index = i.index
|
|
for j in range(len(i.groups)):
|
|
for k in ob.vertex_groups:
|
|
if k.index == i.groups[j].group:
|
|
has_groups = True
|
|
split = col.split(factor = 0.90, align = True)
|
|
vgroup_weights.append((k.index, i.groups[j].weight))
|
|
row = split.row(align = True)
|
|
row.prop(i.groups[j], "weight", text = k.name, slider = True, emboss = not k.lock_weight)
|
|
row = split.row(align = True)
|
|
row.operator("mesh.vertex_group_remove", text="", icon='X').vert_and_group = (i.index, k.index)
|
|
if not has_groups:
|
|
col.label(text = " No Groups")
|
|
else:
|
|
props = col.operator("mesh.vertex_group_assign")
|
|
props.index = vgroup_weights_index
|
|
|
|
for vgroup, weight in vgroup_weights:
|
|
item = props.vgroup_weights.add()
|
|
item.vgroup = vgroup
|
|
item.weight = weight
|
|
|
|
else:
|
|
layout.label(text = "No Groups")
|
|
|
|
|
|
def create_properties():
|
|
bpy.types.WindowManager.show_vgroups_show_all = bpy.props.BoolProperty(
|
|
name = "Show All Selected Vertices",
|
|
description = "Show all vertices with vertex groups assigned to them",
|
|
default=False)
|
|
|
|
bpy.types.Mesh.assign_vgroup = bpy.props.StringProperty()
|
|
|
|
bpy.types.Scene.show_vgroups_weights = bpy.props.BoolProperty(
|
|
name="Show Vertex Groups/Weights",
|
|
default=False)
|
|
|
|
bpy.types.Scene.show_vgroups_weights_limit = bpy.props.IntProperty(
|
|
name="Limit",
|
|
description="Maximum number of weight overlays to draw",
|
|
default=20,
|
|
min=1,
|
|
max=1000,
|
|
soft_max=100)
|
|
|
|
# removal of ID-properties when script is disabled
|
|
def clear_properties(full=True):
|
|
|
|
if bpy.context.active_object is not None:
|
|
me = bpy.context.active_object.data
|
|
|
|
if hasattr(me, "show_vgroup_verts"):
|
|
del me["show_vgroup_verts"]
|
|
if hasattr(me, "show_vgroup_weights"):
|
|
del me["show_vgroup_weights"]
|
|
|
|
if full:
|
|
del bpy.types.WindowManager.show_vgroups_show_all
|
|
del bpy.types.Mesh.assign_vgroup
|
|
del bpy.types.Scene.show_vgroups_weights
|
|
del bpy.types.Scene.show_vgroups_weights_limit
|
|
|
|
|
|
classes = (
|
|
ShowVGroupWeights,
|
|
VGroupsWeights,
|
|
AssignVertexWeight,
|
|
RemoveFromVertexGroup,
|
|
AddToVertexGroup,
|
|
MESH_PT_ShowWeights
|
|
)
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
create_properties()
|
|
|
|
def unregister():
|
|
ShowVGroupWeights.handle_remove()
|
|
clear_properties()
|
|
|
|
for cls in classes:
|
|
bpy.utils.unregister_class(cls)
|
|
|
|
if __name__ == "__main__":
|
|
register()
|