UI: Move Extensions repositories popover to header
[blender-addons-contrib.git] / mesh_show_vgroup_weights.py
blobca603d11a77d00841c774979d6ad9facc03d98ba
1 # ***** BEGIN GPL LICENSE BLOCK *****
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ***** END GPL LICENCE BLOCK *****
20 bl_info = {
21 "name": "Show Vertex Groups/Weights",
22 "author": "Jason van Gumster (Fweeb), Bartius Crouch, CoDEmanX",
23 "version": (0, 7, 2),
24 "blender": (2, 80, 4),
25 "location": "3D View > Properties Region > Show Weights",
26 "description": "Finds the vertex groups of a selected vertex "
27 "and displays the corresponding weights",
28 "warning": "",
29 "doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
30 "Scripts/Modeling/Show_Vertex_Group_Weights",
31 "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
32 "category": "Mesh",
35 #TODO - Add button for selecting vertices with no groups
38 import bpy, bmesh, bgl, blf, mathutils
41 # Borrowed/Modified from Bart Crouch's old Index Visualizer add-on
42 def calc_callback(self, context):
43 #polling
44 if context.mode != "EDIT_MESH" or len(context.active_object.vertex_groups) == 0:
45 return
47 # get color info from theme
48 acol = context.preferences.themes[0].view_3d.editmesh_active
49 tcol = (acol[0] * 0.85, acol[1] * 0.85, acol[2] * 0.85)
51 # get screen information
52 mid_x = context.region.width / 2.0
53 mid_y = context.region.height / 2.0
54 width = context.region.width
55 height = context.region.height
57 # get matrices
58 view_mat = context.space_data.region_3d.perspective_matrix
59 ob_mat = context.active_object.matrix_world
60 total_mat = view_mat @ ob_mat
62 # calculate location info
63 texts = []
64 locs = []
65 weights = []
66 me = context.active_object.data
67 bm = bmesh.from_edit_mesh(me)
68 dvert_lay = bm.verts.layers.deform.active
70 verts_max = context.scene.show_vgroups_weights_limit
71 verts_used = []
73 for elem in reversed(bm.select_history):
74 if not isinstance(elem, bmesh.types.BMVert): #or elem.hide:
75 continue
76 if bm.select_history.active == elem:
77 locs.append([acol[0], acol[1], acol[2], elem.index, elem.co.to_4d()])
78 else:
79 locs.append([tcol[0], tcol[1], tcol[2], elem.index, elem.co.to_4d()])
80 dvert = elem[dvert_lay]
81 for vgroup in context.active_object.vertex_groups:
82 if vgroup.index in dvert.keys():
83 weights += [elem.index, vgroup.index, dvert[vgroup.index]]
84 verts_used.append(elem)
85 verts_max -= 1
86 if verts_max <= 0:
87 break
89 for v in bm.verts:
90 if v.select and v not in verts_used: #XXX Should check v.hide here, but it doesn't work
91 if isinstance(bm.select_history.active, bmesh.types.BMVert) and bm.select_history.active.index == v.index:
92 locs.append([acol[0], acol[1], acol[2], v.index, v.co.to_4d()])
93 else:
94 locs.append([tcol[0], tcol[1], tcol[2], v.index, v.co.to_4d()])
95 dvert = v[dvert_lay]
96 for vgroup in context.active_object.vertex_groups:
97 if vgroup.index in dvert.keys():
98 weights += [v.index, vgroup.index, dvert[vgroup.index]]
99 verts_max -= 1
100 if verts_max <= 0:
101 break
104 for loc in locs:
105 vec = total_mat @ loc[4] # order is important
106 # dehomogenise
107 vec = mathutils.Vector((vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]))
108 x = int(mid_x + vec[0] * width / 2.0)
109 y = int(mid_y + vec[1] * height / 2.0)
110 texts += [loc[0], loc[1], loc[2], loc[3], x, y, 0]
112 # store as ID property in mesh
113 context.active_object.data["show_vgroup_verts"] = texts
114 context.active_object.data["show_vgroup_weights"] = weights
117 # draw in 3d-view
118 def draw_callback(self, context):
119 # polling
120 if context.mode != "EDIT_MESH" or len(context.active_object.vertex_groups) == 0:
121 return
122 # retrieving ID property data
123 try:
124 texts = context.active_object.data["show_vgroup_verts"]
125 weights = context.active_object.data["show_vgroup_weights"]
126 except:
127 return
128 if not texts:
129 return
131 bm = bmesh.from_edit_mesh(context.active_object.data)
133 if bm.select_mode == {'VERT'} and bm.select_history.active is not None:
134 active_vert = bm.select_history.active
135 else:
136 active_vert = None
138 # draw
139 blf.size(0, 13, 72)
140 blf.enable(0, blf.SHADOW)
141 blf.shadow(0, 3, 0.0, 0.0, 0.0, 1.0)
142 blf.shadow_offset(0, 2, -2)
143 for i in range(0, len(texts), 7):
144 bgl.glColor3f(texts[i], texts[i+1], texts[i+2])
145 blf.position(0, texts[i+4], texts[i+5], texts[i+6])
146 blf.draw(0, "Vertex " + str(int(texts[i+3])) + ":")
147 font_y = texts[i+5]
148 group_name = ""
149 for j in range(0, len(weights), 3):
150 if int(weights[j]) == int(texts[i+3]):
151 font_y -= 13
152 blf.position(0, texts[i+4] + 10, font_y, texts[i+6])
153 for group in context.active_object.vertex_groups:
154 if group.index == int(weights[j+1]):
155 group_name = group.name
156 break
157 blf.draw(0, group_name + ": %.3f" % weights[j+2])
158 if group_name == "":
159 font_y -= 13
160 blf.position(0, texts[i+4] + 10, font_y, texts[i+6])
161 blf.draw(0, "No Groups")
163 # restore defaults
164 blf.disable(0, blf.SHADOW)
167 # operator
168 class ShowVGroupWeights(bpy.types.Operator):
169 bl_idname = "view3d.show_vgroup_weights"
170 bl_label = "Show Vertex Group Weights"
171 bl_description = "Toggle the display of the vertex groups and weights for selected vertices"
173 _handle_calc = None
174 _handle_draw = None
176 @staticmethod
177 def handle_add(self, context):
178 ShowVGroupWeights._handle_calc = bpy.types.SpaceView3D.draw_handler_add(
179 calc_callback, (self, context), 'WINDOW', 'POST_VIEW')
180 ShowVGroupWeights._handle_draw = bpy.types.SpaceView3D.draw_handler_add(
181 draw_callback, (self, context), 'WINDOW', 'POST_PIXEL')
183 @staticmethod
184 def handle_remove():
185 if ShowVGroupWeights._handle_calc is not None:
186 bpy.types.SpaceView3D.draw_handler_remove(ShowVGroupWeights._handle_calc, 'WINDOW')
187 if ShowVGroupWeights._handle_draw is not None:
188 bpy.types.SpaceView3D.draw_handler_remove(ShowVGroupWeights._handle_draw, 'WINDOW')
189 ShowVGroupWeights._handle_calc = None
190 ShowVGroupWeights._handle_draw = None
192 @classmethod
193 def poll(cls, context):
194 return context.mode == 'EDIT_MESH'
196 def execute(self, context):
197 if context.area.type == 'VIEW_3D':
198 if not context.scene.show_vgroups_weights:
199 # operator is called and not running, start everything
200 ShowVGroupWeights.handle_add(self, context)
201 context.scene.show_vgroups_weights = True
202 else:
203 # operator is called again, stop displaying
204 ShowVGroupWeights.handle_remove()
205 context.scene.show_vgroups_weights = False
206 clear_properties(full=False)
207 context.area.tag_redraw()
208 return {'FINISHED'}
209 else:
210 self.report({'WARNING'}, "View3D not found, can't run operator")
211 return {'CANCELLED'}
213 class VGroupsWeights(bpy.types.PropertyGroup):
214 vgroup: bpy.props.IntProperty()
215 weight: bpy.props.FloatProperty(min=0.0, max=1.0)
217 class AssignVertexWeight(bpy.types.Operator):
218 bl_idname = "mesh.vertex_group_assign"
219 bl_label = "Assign Weights"
220 bl_description = "Assign weights for all of the groups on a specific vertex"
222 index: bpy.props.IntProperty()
224 vgroup_weights: bpy.props.CollectionProperty(
225 description="Vertex Group Weights",
226 type=VGroupsWeights)
228 @classmethod
229 def poll(cls, context):
230 return context.mode == 'EDIT_MESH'
232 def execute(self, context):
233 me = context.active_object.data
234 bm = bmesh.from_edit_mesh(me)
235 dvert_lay = bm.verts.layers.deform.active
236 weights = {}
237 for item in self.vgroup_weights:
238 weights[item.vgroup] = item.weight
240 for v in bm.verts:
241 if v.index == self.index:
242 dvert = v[dvert_lay]
243 for vgroup in dvert.keys():
244 dvert[vgroup] = weights[vgroup]
245 break
246 context.area.tag_redraw()
247 return {'FINISHED'}
250 class RemoveFromVertexGroup(bpy.types.Operator):
251 bl_idname = "mesh.vertex_group_remove"
252 bl_label = "Remove Vertex from Group"
253 bl_description = "Remove a specific vertex from a specific vertex group"
255 #XXX abusing vector props here a bit; the first element is the vert index and the second is the group index
256 vert_and_group: bpy.props.IntVectorProperty(name = "Vertex and Group to remove", size = 2)
258 @classmethod
259 def poll(cls, context):
260 return context.mode == 'EDIT_MESH'
262 def execute(self, context):
263 ob = context.active_object
264 me = ob.data
265 bm = bmesh.from_edit_mesh(me)
267 # Save current selection
268 selected_verts = []
269 for v in bm.verts:
270 if v.select is True:
271 selected_verts.append(v.index)
272 if v.index != self.vert_and_group[0]:
273 v.select = False
275 ob.vertex_groups.active_index = self.vert_and_group[1]
276 bpy.ops.object.vertex_group_remove_from()
278 # Re-select vertices
279 for v in bm.verts:
280 if v.index in selected_verts:
281 v.select = True
283 #XXX Hacky, but there's no other way to update the UI panels
284 bpy.ops.object.editmode_toggle()
285 bpy.ops.object.editmode_toggle()
286 return {'FINISHED'}
289 class AddToVertexGroup(bpy.types.Operator):
290 bl_idname = "mesh.vertex_group_add"
291 bl_label = "Add Vertex to Group"
292 bl_description = "Add a specific vertex to a specific vertex group"
294 def avail_vgroups(self, context):
295 if context is None:
296 return []
297 ob = context.active_object
298 bm = bmesh.from_edit_mesh(ob.data)
299 dvert_lay = bm.verts.layers.deform.active
300 items = []
301 self.vertex = bm.select_history.active.index
303 dvert = bm.select_history.active[dvert_lay]
305 #XXX since we need an identifier here, user won't be able to add a vgroup with that name ('-1')
306 #XXX could check against vgroup names and find an unused name, but it's a rare case after all.
307 items.append(("-1", "New Vertex Group", "Add a new vertex group to the active object", -1))
309 for i in ob.vertex_groups:
310 if i.index not in dvert.keys():
311 items.append((i.name, i.name, str(i.index), i.index))
313 return items
315 vertex: bpy.props.IntProperty()
316 available_vgroups: bpy.props.EnumProperty(items=avail_vgroups, name="Available Groups")
318 @classmethod
319 def poll(cls, context):
320 return context.mode == 'EDIT_MESH'
322 def execute(self, context):
323 ob = context.active_object
324 me = ob.data
325 bm = bmesh.from_edit_mesh(me)
326 #print(self.available_vgroups)
328 # Save current selection
329 selected_verts = []
330 for v in bm.verts:
331 if v.select is True:
332 selected_verts.append(v.index)
333 if v.index != self.vertex:
334 v.select = False
336 weight = context.tool_settings.vertex_group_weight
337 context.tool_settings.vertex_group_weight = 1.0
338 if self.available_vgroups == "-1":
339 bpy.ops.object.vertex_group_assign(new=True) #XXX Assumes self.vertex is the active vertex
340 else:
341 bpy.ops.object.vertex_group_set_active(group = self.available_vgroups)
342 bpy.ops.object.vertex_group_assign() #XXX Assumes self.vertex is the active vertex
343 context.tool_settings.vertex_group_weight = weight
345 # Re-select vertices
346 for v in bm.verts:
347 if v.index in selected_verts:
348 v.select = True
350 #XXX Hacky, but there's no other way to update the UI panels
351 bpy.ops.object.editmode_toggle()
352 bpy.ops.object.editmode_toggle()
353 return {'FINISHED'}
356 class MESH_PT_ShowWeights(bpy.types.Panel):
357 bl_label = "Show Weights"
358 bl_space_type = "VIEW_3D"
359 bl_region_type = "UI"
360 bl_category = "Edit"
361 bl_options = {'DEFAULT_CLOSED'}
363 @classmethod
364 def poll(cls, context):
365 return context.mode == 'EDIT_MESH'
367 def draw(self, context):
368 layout = self.layout
369 ob = context.active_object
370 me = ob.data
371 bm = bmesh.from_edit_mesh(me)
372 dvert_lay = bm.verts.layers.deform.active
374 row = layout.row(align=True)
376 # text = "Show" if not context.scene.show_vgroups_weights else "Hide"
377 # row.operator(ShowVGroupWeights.bl_idname, text=text)
378 # row.prop(context.scene, "show_vgroups_weights_limit")
380 if len(ob.vertex_groups) > 0:
381 # Active vertex
382 active_vert = bm.select_history.active
383 sub = layout.box()
384 col = sub.column(align = True)
385 if bm.select_mode == {'VERT'} and active_vert is not None:
386 col.label(text = "Active Vertex")
387 row = col.row()
388 row.label(text = "Vertex " + str(active_vert.index) + ":")
389 row.operator_menu_enum("mesh.vertex_group_add", "available_vgroups", text = "Add Group", icon = 'GROUP_VERTEX')
390 has_groups = False
391 vgroup_weights = []
393 for i in me.vertices:
394 if i.index == active_vert.index:
395 vgroup_weights_index = i.index
396 for j in range(len(i.groups)):
397 for k in ob.vertex_groups:
398 if k.index == i.groups[j].group:
399 has_groups = True
400 split = col.split(factor = 0.90, align = True)
401 vgroup_weights.append((k.index, i.groups[j].weight))
402 row = split.row(align = True)
403 row.prop(i.groups[j], "weight", text = k.name, slider = True, emboss = not k.lock_weight)
404 row = split.row(align = True)
405 row.operator("mesh.vertex_group_remove", text="", icon='X').vert_and_group = (i.index, k.index)
407 if not has_groups:
408 col.label(text = " No Groups")
409 else:
410 props = col.operator("mesh.vertex_group_assign")
411 props.index = vgroup_weights_index
413 for vgroup, weight in vgroup_weights:
414 item = props.vgroup_weights.add()
415 item.vgroup = vgroup
416 item.weight = weight
418 layout.separator()
419 else:
420 col.label(text = "No Active Vertex")
421 layout.prop(context.window_manager, "show_vgroups_show_all", toggle = True)
422 # All selected vertices (except for the active vertex)
423 if context.window_manager.show_vgroups_show_all:
424 for v in bm.verts:
425 if v.select:
426 if active_vert is not None and v.index == active_vert.index:
427 continue
428 sub = layout.box()
429 col = sub.column(align = True)
430 col.label(text = "Vertex " + str(v.index) + ":")
431 has_groups = False
432 vgroup_weights = []
433 for i in me.vertices:
434 if i.index == v.index:
435 vgroup_weights_index = i.index
436 for j in range(len(i.groups)):
437 for k in ob.vertex_groups:
438 if k.index == i.groups[j].group:
439 has_groups = True
440 split = col.split(factor = 0.90, align = True)
441 vgroup_weights.append((k.index, i.groups[j].weight))
442 row = split.row(align = True)
443 row.prop(i.groups[j], "weight", text = k.name, slider = True, emboss = not k.lock_weight)
444 row = split.row(align = True)
445 row.operator("mesh.vertex_group_remove", text="", icon='X').vert_and_group = (i.index, k.index)
446 if not has_groups:
447 col.label(text = " No Groups")
448 else:
449 props = col.operator("mesh.vertex_group_assign")
450 props.index = vgroup_weights_index
452 for vgroup, weight in vgroup_weights:
453 item = props.vgroup_weights.add()
454 item.vgroup = vgroup
455 item.weight = weight
457 else:
458 layout.label(text = "No Groups")
461 def create_properties():
462 bpy.types.WindowManager.show_vgroups_show_all = bpy.props.BoolProperty(
463 name = "Show All Selected Vertices",
464 description = "Show all vertices with vertex groups assigned to them",
465 default=False)
467 bpy.types.Mesh.assign_vgroup = bpy.props.StringProperty()
469 bpy.types.Scene.show_vgroups_weights = bpy.props.BoolProperty(
470 name="Show Vertex Groups/Weights",
471 default=False)
473 bpy.types.Scene.show_vgroups_weights_limit = bpy.props.IntProperty(
474 name="Limit",
475 description="Maximum number of weight overlays to draw",
476 default=20,
477 min=1,
478 max=1000,
479 soft_max=100)
481 # removal of ID-properties when script is disabled
482 def clear_properties(full=True):
484 if bpy.context.active_object is not None:
485 me = bpy.context.active_object.data
487 if hasattr(me, "show_vgroup_verts"):
488 del me["show_vgroup_verts"]
489 if hasattr(me, "show_vgroup_weights"):
490 del me["show_vgroup_weights"]
492 if full:
493 del bpy.types.WindowManager.show_vgroups_show_all
494 del bpy.types.Mesh.assign_vgroup
495 del bpy.types.Scene.show_vgroups_weights
496 del bpy.types.Scene.show_vgroups_weights_limit
499 classes = (
500 ShowVGroupWeights,
501 VGroupsWeights,
502 AssignVertexWeight,
503 RemoveFromVertexGroup,
504 AddToVertexGroup,
505 MESH_PT_ShowWeights
508 def register():
509 for cls in classes:
510 bpy.utils.register_class(cls)
512 create_properties()
514 def unregister():
515 ShowVGroupWeights.handle_remove()
516 clear_properties()
518 for cls in classes:
519 bpy.utils.unregister_class(cls)
521 if __name__ == "__main__":
522 register()