[Edit Linked Library] Removed no-longer-relevant comment
[blender-addons-contrib.git] / mesh_show_vgroup_weights.py
1 # ***** BEGIN GPL LICENSE BLOCK *****
2 #
3 #
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.
8 #
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.
13 #
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.
17 #
18 # ***** END GPL LICENCE BLOCK *****
19
20 # <pep8 compliant> (Thanks to CodemanX on IRC)
21
22 bl_info = {
23     "name": "Show Vertex Groups/Weights",
24     "author": "Jason van Gumster (Fweeb), Bartius Crouch, CoDEmanX",
25     "version": (0, 7, 2),
26     "blender": (2, 65, 4),
27     "location": "3D View > Properties Region > Show Weights",
28     "description": "Finds the vertex groups of a selected vertex and displays the corresponding weights",
29     "warning": "",
30     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
31         "Scripts/Modeling/Show_Vertex_Group_Weights",
32     "tracker_url": "http://projects.blender.org/tracker/index.php?"\
33         "func=detail&aid=30609",
34     "category": "Mesh"}
35
36 #TODO - Add button for selecting vertices with no groups
37
38
39 import bpy, bmesh, bgl, blf, mathutils
40
41
42 # Borrowed/Modified from Bart Crouch's old Index Visualizer add-on
43 def calc_callback(self, context):
44     #polling
45     if context.mode != "EDIT_MESH" or len(context.active_object.vertex_groups) == 0:
46         return
47
48     # get color info from theme
49     acol = context.user_preferences.themes[0].view_3d.editmesh_active
50     tcol = (acol[0] * 0.85, acol[1] * 0.85, acol[2] * 0.85)
51
52     # get screen information
53     mid_x = context.region.width / 2.0
54     mid_y = context.region.height / 2.0
55     width = context.region.width
56     height = context.region.height
57
58     # get matrices
59     view_mat = context.space_data.region_3d.perspective_matrix
60     ob_mat = context.active_object.matrix_world
61     total_mat = view_mat * ob_mat
62
63     # calculate location info
64     texts = []
65     locs = []
66     weights  = []
67     me = context.active_object.data
68     bm = bmesh.from_edit_mesh(me)
69     dvert_lay = bm.verts.layers.deform.active
70
71     verts_max = context.scene.show_vgroups_weights_limit
72     verts_used = []
73
74     for elem in reversed(bm.select_history):
75         if not isinstance(elem, bmesh.types.BMVert): #or elem.hide:
76             continue
77         if bm.select_history.active == elem:
78             locs.append([acol[0], acol[1], acol[2], elem.index, elem.co.to_4d()])
79         else:
80             locs.append([tcol[0], tcol[1], tcol[2], elem.index, elem.co.to_4d()])
81         dvert = elem[dvert_lay]
82         for vgroup in context.active_object.vertex_groups:
83             if vgroup.index in dvert.keys():
84                 weights += [elem.index, vgroup.index, dvert[vgroup.index]]
85         verts_used.append(elem)
86         verts_max -= 1
87         if verts_max <= 0:
88             break
89
90     for v in bm.verts:
91         if v.select and v not in verts_used: #XXX Should check v.hide here, but it doesn't work
92             if isinstance(bm.select_history.active, bmesh.types.BMVert) and bm.select_history.active.index == v.index:
93                 locs.append([acol[0], acol[1], acol[2], v.index, v.co.to_4d()])
94             else:
95                 locs.append([tcol[0], tcol[1], tcol[2], v.index, v.co.to_4d()])
96             dvert = v[dvert_lay]
97             for vgroup in context.active_object.vertex_groups:
98                 if vgroup.index in dvert.keys():
99                     weights += [v.index, vgroup.index, dvert[vgroup.index]]
100             verts_max -= 1
101             if verts_max <= 0:
102                 break
103
104
105     for loc in locs:
106         vec = total_mat * loc[4] # order is important
107         # dehomogenise
108         vec = mathutils.Vector((vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]))
109         x = int(mid_x + vec[0] * width / 2.0)
110         y = int(mid_y + vec[1] * height / 2.0)
111         texts += [loc[0], loc[1], loc[2], loc[3], x, y, 0]
112
113     # store as ID property in mesh
114     context.active_object.data["show_vgroup_verts"] = texts
115     context.active_object.data["show_vgroup_weights"] = weights
116
117
118 # draw in 3d-view
119 def draw_callback(self, context):
120     # polling
121     if context.mode != "EDIT_MESH" or len(context.active_object.vertex_groups) == 0:
122         return
123     # retrieving ID property data
124     try:
125         texts = context.active_object.data["show_vgroup_verts"]
126         weights = context.active_object.data["show_vgroup_weights"]
127     except:
128         return
129     if not texts:
130         return
131
132     bm = bmesh.from_edit_mesh(context.active_object.data)
133
134     if bm.select_mode == {'VERT'} and bm.select_history.active is not None:
135         active_vert = bm.select_history.active
136     else:
137         active_vert = None
138
139     # draw
140     blf.size(0, 13, 72)
141     blf.enable(0, blf.SHADOW)
142     blf.shadow(0, 3, 0.0, 0.0, 0.0, 1.0)
143     blf.shadow_offset(0, 2, -2)
144     for i in range(0, len(texts), 7):
145         bgl.glColor3f(texts[i], texts[i+1], texts[i+2])
146         blf.position(0, texts[i+4], texts[i+5], texts[i+6])
147         blf.draw(0, "Vertex " + str(int(texts[i+3])) + ":")
148         font_y = texts[i+5]
149         group_name = ""
150         for j in range(0, len(weights), 3):
151             if int(weights[j]) == int(texts[i+3]):
152                 font_y -= 13
153                 blf.position(0, texts[i+4] + 10, font_y, texts[i+6])
154                 for group in context.active_object.vertex_groups:
155                     if group.index == int(weights[j+1]):
156                         group_name = group.name
157                         break
158                 blf.draw(0, group_name + ": %.3f" % weights[j+2])
159         if group_name == "":
160             font_y -= 13
161             blf.position(0, texts[i+4] + 10, font_y, texts[i+6])
162             blf.draw(0, "No Groups")
163
164     # restore defaults
165     blf.disable(0, blf.SHADOW)
166
167
168 # operator
169 class ShowVGroupWeights(bpy.types.Operator):
170     bl_idname = "view3d.show_vgroup_weights"
171     bl_label = "Show Vertex Group Weights"
172     bl_description = "Toggle the display of the vertex groups and weights for selected vertices"
173
174     _handle_calc = None
175     _handle_draw = None
176
177     @staticmethod
178     def handle_add(self, context):
179         ShowVGroupWeights._handle_calc = bpy.types.SpaceView3D.draw_handler_add(
180             calc_callback, (self, context), 'WINDOW', 'POST_VIEW')
181         ShowVGroupWeights._handle_draw = bpy.types.SpaceView3D.draw_handler_add(
182             draw_callback, (self, context), 'WINDOW', 'POST_PIXEL')
183
184     @staticmethod
185     def handle_remove():
186         if ShowVGroupWeights._handle_calc is not None:
187             bpy.types.SpaceView3D.draw_handler_remove(ShowVGroupWeights._handle_calc, 'WINDOW')
188         if ShowVGroupWeights._handle_draw is not None:
189             bpy.types.SpaceView3D.draw_handler_remove(ShowVGroupWeights._handle_draw, 'WINDOW')
190         ShowVGroupWeights._handle_calc = None
191         ShowVGroupWeights._handle_draw = None
192
193     @classmethod
194     def poll(cls, context):
195         return context.mode == 'EDIT_MESH'
196
197     def execute(self, context):
198         if context.area.type == 'VIEW_3D':
199             if not context.scene.show_vgroups_weights:
200                 # operator is called and not running, start everything
201                 ShowVGroupWeights.handle_add(self, context)
202                 context.scene.show_vgroups_weights = True
203             else:
204                 # operator is called again, stop displaying
205                 ShowVGroupWeights.handle_remove()
206                 context.scene.show_vgroups_weights = False
207                 clear_properties(full=False)
208             context.area.tag_redraw()
209             return {'FINISHED'}
210         else:
211             self.report({'WARNING'}, "View3D not found, can't run operator")
212             return {'CANCELLED'}
213
214 class VGroupsWeights(bpy.types.PropertyGroup):
215     vgroup = bpy.props.IntProperty()
216     weight = bpy.props.FloatProperty(min=0.0, max=1.0)
217
218 class AssignVertexWeight(bpy.types.Operator):
219     bl_idname = "mesh.vertex_group_assign"
220     bl_label = "Assign Weights"
221     bl_description = "Assign weights for all of the groups on a specific vertex"
222
223     index = bpy.props.IntProperty()
224
225     vgroup_weights = bpy.props.CollectionProperty(
226         description="Vertex Group Weights",
227         type=VGroupsWeights)
228
229     @classmethod
230     def poll(cls, context):
231         return context.mode == 'EDIT_MESH'
232
233     def execute(self, context):
234         me = context.active_object.data
235         bm = bmesh.from_edit_mesh(me)
236         dvert_lay = bm.verts.layers.deform.active
237         weights = {}
238         for item in self.vgroup_weights:
239             weights[item.vgroup] = item.weight
240
241         for v in bm.verts:
242             if v.index == self.index:
243                 dvert = v[dvert_lay]
244                 for vgroup in dvert.keys():
245                     dvert[vgroup] = weights[vgroup]
246                 break
247         context.area.tag_redraw()
248         return {'FINISHED'}
249
250
251 class RemoveFromVertexGroup(bpy.types.Operator):
252     bl_idname = "mesh.vertex_group_remove"
253     bl_label = "Remove Vertex from Group"
254     bl_description = "Remove a specific vertex from a specific vertex group"
255
256     #XXX abusing vector props here a bit; the first element is the vert index and the second is the group index
257     vert_and_group = bpy.props.IntVectorProperty(name = "Vertex and Group to remove", size = 2)
258
259     @classmethod
260     def poll(cls, context):
261         return context.mode == 'EDIT_MESH'
262
263     def execute(self, context):
264         ob = context.active_object
265         me = ob.data
266         bm = bmesh.from_edit_mesh(me)
267
268         # Save current selection
269         selected_verts = []
270         for v in bm.verts:
271             if v.select is True:
272                 selected_verts.append(v.index)
273                 if v.index != self.vert_and_group[0]:
274                     v.select = False
275
276         ob.vertex_groups.active_index = self.vert_and_group[1]
277         bpy.ops.object.vertex_group_remove_from()
278
279         # Re-select vertices
280         for v in bm.verts:
281             if v.index in selected_verts:
282                 v.select = True
283
284         #XXX Hacky, but there's no other way to update the UI panels
285         bpy.ops.object.editmode_toggle()
286         bpy.ops.object.editmode_toggle()
287         return {'FINISHED'}
288
289
290 class AddToVertexGroup(bpy.types.Operator):
291     bl_idname = "mesh.vertex_group_add"
292     bl_label = "Add Vertex to Group"
293     bl_description = "Add a specific vertex to a specific vertex group"
294
295     def avail_vgroups(self, context):
296         ob = context.active_object
297         bm = bmesh.from_edit_mesh(ob.data)
298         dvert_lay = bm.verts.layers.deform.active
299         items = []
300         self.vertex = bm.select_history.active.index
301
302         dvert = bm.select_history.active[dvert_lay]
303
304         #XXX since we need an identifier here, user won't be able to add a vgroup with that name ('-1')
305         #XXX could check against vgroup names and find an unused name, but it's a rare case after all.
306         items.append(("-1", "New Vertex Group", "Add a new vertex group to the active object", -1))
307
308         for i in ob.vertex_groups:
309             if i.index not in dvert.keys():
310                 items.append((i.name, i.name, str(i.index), i.index))
311
312         return items
313
314     vertex = bpy.props.IntProperty()
315     available_vgroups = bpy.props.EnumProperty(items=avail_vgroups, name="Available Groups")
316
317     @classmethod
318     def poll(cls, context):
319         return context.mode == 'EDIT_MESH'
320
321     def execute(self, context):
322         ob = context.active_object
323         me = ob.data
324         bm = bmesh.from_edit_mesh(me)
325         #print(self.available_vgroups)
326
327         # Save current selection
328         selected_verts = []
329         for v in bm.verts:
330             if v.select is True:
331                 selected_verts.append(v.index)
332                 if v.index != self.vertex:
333                     v.select = False
334
335         weight = context.tool_settings.vertex_group_weight
336         context.tool_settings.vertex_group_weight = 1.0
337         if self.available_vgroups == "-1":
338             bpy.ops.object.vertex_group_assign(new=True) #XXX Assumes self.vertex is the active vertex
339         else:
340             bpy.ops.object.vertex_group_set_active(group = self.available_vgroups)
341             bpy.ops.object.vertex_group_assign() #XXX Assumes self.vertex is the active vertex
342         context.tool_settings.vertex_group_weight = weight
343
344         # Re-select vertices
345         for v in bm.verts:
346             if v.index in selected_verts:
347                 v.select = True
348
349         #XXX Hacky, but there's no other way to update the UI panels
350         bpy.ops.object.editmode_toggle()
351         bpy.ops.object.editmode_toggle()
352         return {'FINISHED'}
353
354
355 class PanelShowWeights(bpy.types.Panel):
356     bl_label = "Show Weights"
357     bl_space_type = "VIEW_3D"
358     bl_region_type = "UI"
359     bl_options = {'DEFAULT_CLOSED'}
360
361     @classmethod
362     def poll(cls, context):
363         return context.mode == 'EDIT_MESH'
364
365     def draw(self, context):
366         layout = self.layout
367         ob = context.active_object
368         me = ob.data
369         bm = bmesh.from_edit_mesh(me)
370         dvert_lay = bm.verts.layers.deform.active
371
372         row = layout.row(align=True)
373
374         text = "Show" if not context.scene.show_vgroups_weights else "Hide"
375         row.operator(ShowVGroupWeights.bl_idname, text=text)
376         row.prop(context.scene, "show_vgroups_weights_limit")
377
378         if len(ob.vertex_groups) > 0:
379             # Active vertex
380             active_vert = bm.select_history.active
381             sub = layout.box()
382             col = sub.column(align = True)
383             if bm.select_mode == {'VERT'} and active_vert is not None:
384                 col.label(text = "Active Vertex")
385                 row = col.row()
386                 row.label(text = "Vertex " + str(active_vert.index) + ":")
387                 row.operator_menu_enum("mesh.vertex_group_add", "available_vgroups", text = "Add Group", icon = 'GROUP_VERTEX')
388                 has_groups = False
389                 vgroup_weights = []
390
391                 for i in me.vertices:
392                     if i.index == active_vert.index:
393                         vgroup_weights_index = i.index
394                         for j in range(len(i.groups)):
395                             for k in ob.vertex_groups:
396                                 if k.index == i.groups[j].group:
397                                     has_groups = True
398                                     split = col.split(percentage = 0.90, align = True)
399                                     vgroup_weights.append((k.index, i.groups[j].weight))
400                                     row = split.row(align = True)
401                                     row.prop(i.groups[j], "weight", text = k.name, slider = True, emboss = not k.lock_weight)
402                                     row = split.row(align = True)
403                                     row.operator("mesh.vertex_group_remove", text="", icon='X').vert_and_group = (i.index, k.index)
404
405                 if not has_groups:
406                     col.label(text = "    No Groups")
407                 else:
408                     props = col.operator("mesh.vertex_group_assign")
409                     props.index = vgroup_weights_index
410
411                     for vgroup, weight in vgroup_weights:
412                         item = props.vgroup_weights.add()
413                         item.vgroup = vgroup
414                         item.weight = weight
415
416                 layout.separator()
417             else:
418                 col.label(text = "No Active Vertex")
419             layout.prop(context.window_manager, "show_vgroups_show_all", toggle = True)
420             # All selected vertices (except for the active vertex)
421             if context.window_manager.show_vgroups_show_all:
422                 for v in bm.verts:
423                     if v.select:
424                         if active_vert is not None and v.index == active_vert.index:
425                             continue
426                         sub = layout.box()
427                         col = sub.column(align = True)
428                         col.label(text = "Vertex " + str(v.index) + ":")
429                         has_groups = False
430                         vgroup_weights = []
431                         for i in me.vertices:
432                             if i.index == v.index:
433                                 vgroup_weights_index = i.index
434                                 for j in range(len(i.groups)):
435                                     for k in ob.vertex_groups:
436                                         if k.index == i.groups[j].group:
437                                             has_groups = True
438                                             split = col.split(percentage = 0.90, align = True)
439                                             vgroup_weights.append((k.index, i.groups[j].weight))
440                                             row = split.row(align = True)
441                                             row.prop(i.groups[j], "weight", text = k.name, slider = True, emboss = not k.lock_weight)
442                                             row = split.row(align = True)
443                                             row.operator("mesh.vertex_group_remove", text="", icon='X').vert_and_group = (i.index, k.index)
444                         if not has_groups:
445                             col.label(text = "    No Groups")
446                         else:
447                             props = col.operator("mesh.vertex_group_assign")
448                             props.index = vgroup_weights_index
449
450                             for vgroup, weight in vgroup_weights:
451                                 item = props.vgroup_weights.add()
452                                 item.vgroup = vgroup
453                                 item.weight = weight
454
455         else:
456             layout.label(text = "No Groups")
457
458
459 def create_properties():
460     bpy.types.WindowManager.show_vgroups_show_all = bpy.props.BoolProperty(
461         name = "Show All Selected Vertices",
462         description = "Show all vertices with vertex groups assigned to them",
463         default=False)
464
465     bpy.types.Mesh.assign_vgroup = bpy.props.StringProperty()
466
467     bpy.types.Scene.show_vgroups_weights = bpy.props.BoolProperty(
468         name="Show Vertex Groups/Weights",
469         default=False)
470
471     bpy.types.Scene.show_vgroups_weights_limit = bpy.props.IntProperty(
472         name="Limit",
473         description="Maximum number of weight overlays to draw",
474         default=20,
475         min=1,
476         max=1000,
477         soft_max=100)
478
479 # removal of ID-properties when script is disabled
480 def clear_properties(full=True):
481
482     if bpy.context.active_object is not None:
483         me = bpy.context.active_object.data
484
485         if hasattr(me, "show_vgroup_verts"):
486             del me["show_vgroup_verts"]
487         if hasattr(me, "show_vgroup_weights"):
488             del me["show_vgroup_weights"]
489
490     if full:
491         del bpy.types.WindowManager.show_vgroups_show_all
492         del bpy.types.Mesh.assign_vgroup
493         del bpy.types.Scene.show_vgroups_weights
494         del bpy.types.Scene.show_vgroups_weights_limit
495
496 def register():
497     bpy.utils.register_module(__name__)
498     create_properties()
499
500 def unregister():
501     ShowVGroupWeights.handle_remove()
502     bpy.utils.unregister_module(__name__)
503     clear_properties()
504
505 if __name__ == "__main__":
506     register()