3
$\begingroup$

Vertex Weight: Numerical Overlay in Weight Paint Mode

Is there a possibility to overlay the weights of the active Vertex Group as numbers in the viewport? It would make for a much more precise readout than the color gradients, especially when fiddling with minor adjustments.

Here is the code that I was able to come up with hikariztw's help, the only thing missing is to make it work in Weight Paint Mode. It works in Edit mode, for now.

I was thinking of adding this to the Overlays Panel, since it is basically a variation on the Mesh Analysis Overlays, like Edge Length, Edge Angle or Face Area. Alas, it seems you cannot touch those, as they are written in C.

Index Visualizer 0.1

import bpy
import bgl
import blf
import mathutils
import bmesh

bl_info = {
    'name': 'Index Visualiser (BMesh)',
    'author': 'Bartius Crouch, CoDEmanX, hikariztw',
    'version': (2, 6, 15),
    'blender': (2, 80, 0),
    'location': 'View3D > Properties panel > Mesh Display tab (edit-mode)',
    'warning': '',  # used for warning icon and text in addons panel
    'description': 'Display the indices of vertices, edges and faces '
                   'in the 3d-view',
    'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'
                'Scripts/3D_interaction/Index_Visualiser',
    'tracker_url': 'http://projects.blender.org/tracker/index.php?'
                   'func=detail&aid=21557',
    'category': '3D View'}


# calculate locations and store them as ID property in the mesh

def draw_callback_px(self, context):
    # polling

    if context.mode != "EDIT_MESH" and context.mode != "PAINT_WEIGHT":
        return

    # get screen information

    region = context.region
    mid_x = region.width / 2
    mid_y = region.height / 2
    width = region.width
    height = region.height

    # get matrices

    obj = context.active_object
    view_mat = context.space_data.region_3d.perspective_matrix
    ob_mat = obj.matrix_world
    total_mat = view_mat @ ob_mat

    blf.size(0, 13, 72)

    def draw_index(r, g, b, index, center):

        vec = total_mat @ center  # 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)
        y = int(mid_y + vec[1] * height / 2)

        # bgl.glColorMask(1,1,1,1)
        blf.position(0, x, y, 0)
        if isinstance(index, float):
            blf.draw(0, '{:.2f}'.format(index))
        else:
            blf.draw(0, str(index))

    scene = context.scene
    me = obj.data
    if context.mode == "EDIT_MESH":
        bm = bmesh.from_edit_mesh(me)
    else:
        bm = type('null', (), {'verts': [], 'edges': [], 'faces': []})

    if scene.live_mode:
        me.update()

    if scene.display_vert_index:
        for v in bm.verts:
            if not v.hide and \
                    (v.select or not scene.display_sel_only):
                # CoDEmanx: bm.verts.index_update()?

                draw_index(1.0, 1.0, 1.0, v.index, v.co.to_4d())

    if scene.display_weight:
        vg = context.object.vertex_groups[0]

        for v in bm.verts:
            try:
                weight = vg.weight(v.index)
                draw_index(1.0, 1.0, 1.0, weight, v.co.to_4d())
            except Exception as e:
                continue

    if scene.display_edge_index:
        for e in bm.edges:
            if not e.hide and \
                    (e.select or not scene.display_sel_only):
                v1 = e.verts[0].co
                v2 = e.verts[1].co
                loc = v1 + ((v2 - v1) / 2)
                draw_index(1.0, 1.0, 0.0, e.index, loc.to_4d())
    if scene.display_face_index:
        for f in bm.faces:
            if not f.hide and \
                    (f.select or not scene.display_sel_only):
                draw_index(1.0, 0.0, 0.5, f.index,
                           f.calc_center_median().to_4d())


# operator

class IndexVisualiser(bpy.types.Operator):
    bl_idname = "view3d.index_visualiser"
    bl_label = "Index Visualiser"
    bl_description = "Toggle the visualisation of indices"

    _handle = None

    @classmethod
    def poll(cls, context):
        return context.mode == "EDIT_MESH" or context.mode == "PAINT_WEIGHT"

    def modal(self, context, event):
        if context.area:
            context.area.tag_redraw()

        # removal of callbacks when operator is called again

        if context.scene.display_indices == -1:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.scene.display_indices = 0
            return {"CANCELLED"}

        return {"PASS_THROUGH"}

    def invoke(self, context, event):
        if context.area.type == "VIEW_3D":
            if context.scene.display_indices < 1:
                # operator is called for the first time, start everything

                context.scene.display_indices = 1
                self._handle = bpy.types.SpaceView3D.draw_handler_add(
                    draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
                context.window_manager.modal_handler_add(self)
                return {"RUNNING_MODAL"}
            else:
                # operator is called again, stop displaying

                context.scene.display_indices = -1
                return {'RUNNING_MODAL'}
        else:
            self.report({"WARNING"}, "View3D not found, can't run operator")
            return {"CANCELLED"}


# defining the panel

def menu_func(self, context):
    self.layout.separator()
    col = self.layout.column(align=True)
    col.operator(IndexVisualiser.bl_idname, text="Visualize indices")
    row = col.row(align=True)
    row.active = ((context.mode == "EDIT_MESH" or context.mode == "PAINT_WEIGHT") and
                  context.scene.display_indices == 1)
    row.prop(context.scene, "display_vert_index", toggle=True)
    row.prop(context.scene, "display_weight", toggle=True)
    row.prop(context.scene, "display_edge_index", toggle=True)
    row.prop(context.scene, "display_face_index", toggle=True)
    row = col.row(align=True)
    row.active = context.mode == "EDIT_MESH" and \
        context.scene.display_indices == 1
    row.prop(context.scene, "display_sel_only")
    # row.prop(context.scene, "live_mode")


def register_properties():
    bpy.types.Scene.display_indices = bpy.props.IntProperty(
        name="Display indices",
        default=0)
    # context.scene.display_indices = 0

    bpy.types.Scene.display_sel_only = bpy.props.BoolProperty(
        name="Selected only",
        description="Only display indices of selected vertices/edges/faces",
        default=True)
    bpy.types.Scene.display_vert_index = bpy.props.BoolProperty(
        name="Vertices",
        description="Display vertex indices", default=False)
    bpy.types.Scene.display_weight = bpy.props.BoolProperty(
        name="Weight",
        description="Display vertex weight", default=False)
    bpy.types.Scene.display_edge_index = bpy.props.BoolProperty(
        name="Edges",
        description="Display edge indices")
    bpy.types.Scene.display_face_index = bpy.props.BoolProperty(
        name="Faces",
        description="Display face indices")
    bpy.types.Scene.live_mode = bpy.props.BoolProperty(
        name="Live",
        description="Toggle live update of the selection, can be slow",
        default=False)


def unregister_properties():
    del bpy.types.Scene.display_indices
    del bpy.types.Scene.display_sel_only
    del bpy.types.Scene.display_vert_index
    del bpy.types.Scene.display_weight
    del bpy.types.Scene.display_edge_index
    del bpy.types.Scene.display_face_index
    del bpy.types.Scene.live_mode


def register():
    register_properties()
    bpy.utils.register_class(IndexVisualiser)
    bpy.types.VIEW3D_PT_view3d_properties.append(menu_func)


def unregister():
    bpy.utils.unregister_class(IndexVisualiser)
    unregister_properties()
    bpy.types.VIEW3D_PT_view3d_properties.remove(menu_func)


if __name__ == "__main__":
    register()
$\endgroup$
6
  • 1
    $\begingroup$ It is possible to use a gl_shader which drawing text beside the vertices to handle those weight number. But it will be a lot of work in code. $\endgroup$
    – HikariTW
    May 5, 2019 at 14:54
  • $\begingroup$ Is a gl_shader related to a GLSL Shader? Having trouble reading up on it. $\endgroup$
    – Ben
    May 5, 2019 at 15:12
  • 1
    $\begingroup$ Yes, I mean OpenGL shader with API in Blender 2.8: wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/… $\endgroup$
    – HikariTW
    May 5, 2019 at 15:25
  • $\begingroup$ I just remembered that there was an add-on to display the Vertex Indices on top of the Mesh. Maybe I can hack something together with this. Or somebody else with some actual experience in python. developer.blender.org/F11709 $\endgroup$
    – Ben
    May 7, 2019 at 7:51
  • $\begingroup$ Are you trying to turn it into Blender 2.8 add-on? I might look into it a bit. $\endgroup$
    – HikariTW
    May 7, 2019 at 8:15

1 Answer 1

2
$\begingroup$

This code is convert into Blender 2.8 for now, in any 3D view mode, you can find the option in 3DVIEW properties panel. If the object you selected have a vertex group, it will reveal those vertices with their weight (round to 0.01 for readability)

You should start work on the bgl and blf module for more tuning. That's what the original coder use.

bl_info = {
    'name': 'Weight Visualiser',
    'author': 'Bartius Crouch, CoDEmanX, hikariztw',
    'version': (0, 0, 1),
    'blender': (2, 80, 0),
    'location': 'View3D > Properties panel > Mesh Display tab (edit-mode)',
    'category': '3D View'}


import bpy
import bgl
import blf
import mathutils


# calculate locations and store them as ID property in the mesh

def draw_callback_px(self, context):
    # polling

    # if context.mode != "EDIT_MESH" and context.mode != "PAINT_WEIGHT":
    #     return

    # get screen information

    region = context.region
    mid_x = region.width / 2
    mid_y = region.height / 2
    width = region.width
    height = 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

    blf.size(0, 13, 72)

    def draw_index(r, g, b, index, center):

        vec = total_mat @ center # 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)
        y = int(mid_y + vec[1] * height / 2)

        # bgl.glColorMask(1,1,1,1)
        blf.position(0, x, y, 0)
        if isinstance(index,float):
            blf.draw(0, '{:.2f}'.format(index))
        else:
            blf.draw(0, str(index))


    scene = context.scene
    me = context.active_object.data

    if scene.live_mode:
        me.update()

    if scene.display_weight:
        vg = context.object.vertex_groups.active

        for v in me.vertices:
            try:
                weight = vg.weight(v.index)
                draw_index(1.0, 1.0, 1.0, weight, v.co.to_4d())
            except Exception as e:
                continue


# operator

class WeightVisualiser(bpy.types.Operator):
    bl_idname = "view3d.weight_visualiser"
    bl_label = "Weight Visualiser"
    bl_description = "Toggle the visualisation of Weight"

    _handle = None

    @classmethod
    def poll(cls, context):
        return True

    def modal(self, context, event):
        if context.area:
            context.area.tag_redraw()

        # removal of callbacks when operator is called again

        if context.scene.display_weight_run == -1:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.scene.display_weight_run = 0
            return {"CANCELLED"}

        return {"PASS_THROUGH"}

    def invoke(self, context, event):
        if context.area.type == "VIEW_3D":
            if context.scene.display_weight_run != 1:
                # operator is called for the first time, start everything

                context.scene.display_weight_run = 1
                self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px,
                    (self, context), 'WINDOW', 'POST_PIXEL')
                context.window_manager.modal_handler_add(self)
                return {"RUNNING_MODAL"}
            else:
                # operator is called again, stop displaying

                context.scene.display_weight_run = -1
                return {'RUNNING_MODAL'}
        else:
            self.report({"WARNING"}, "View3D not found, can't run operator")
            return {"CANCELLED"}


# defining the panel

def menu_func(self, context):
    self.layout.separator()
    col = self.layout.column(align=True)
    col.operator(WeightVisualiser.bl_idname, text="Visualize Weight")
    row = col.row(align=True)
    # row.active = ((context.mode=="EDIT_MESH" or context.mode=="PAINT_WEIGHT") and \
        # context.scene.display_weight_run==1)
    row.prop(context.scene, "display_weight", toggle=True)
    row = col.row(align=True)
    row.active = context.scene.display_weight_run == 1
    row.prop(context.scene, "display_sel_only")
    #row.prop(context.scene, "live_mode")



def register_properties():
    bpy.types.Scene.display_weight_run = bpy.props.IntProperty(
        name="Display Weight",
        default=0)
    bpy.types.Scene.display_sel_only = bpy.props.BoolProperty(
        name="Selected only",
        description="Only display indices of selected vertices/edges/faces",
        default=True)
    bpy.types.Scene.display_weight = bpy.props.BoolProperty(
        name="Weight",
        description="Display vertex weight", default=True)
    bpy.types.Scene.live_mode = bpy.props.BoolProperty(
        name="Live",
        description="Toggle live update of the selection, can be slow",
        default=False)

def unregister_properties():
    del bpy.types.Scene.display_weight_run
    del bpy.types.Scene.display_sel_only
    del bpy.types.Scene.display_weight
    del bpy.types.Scene.live_mode


def register():
    register_properties()
    bpy.utils.register_class(WeightVisualiser)
    bpy.types.VIEW3D_PT_view3d_properties.append(menu_func)


def unregister():
    bpy.utils.unregister_class(WeightVisualiser)
    unregister_properties()
    bpy.types.VIEW3D_PT_view3d_properties.remove(menu_func)


if __name__ == "__main__":
    register()

It will be more easier if not adding bmesh for vertices index. So, here you go. Also thanks for the original coder to deal with math problem.

$\endgroup$
6
  • 2
    $\begingroup$ vg = context.object.vertex_groups.active Recommend if you are going to edit another addon, take out the wiki and tracker urls in bl_info dictionary. $\endgroup$
    – batFINGER
    May 7, 2019 at 10:51
  • $\begingroup$ Nicely structured, thank you! I (with some help) made some progress on the script, Vertex Weight Numbers now show up in Edit Mode. Can't seem to get them over to Weight Paint Mode, though. I will add the code to the question. $\endgroup$
    – Ben
    May 8, 2019 at 6:26
  • $\begingroup$ I came back and see the code, It was quite easy to overwrite the original one, I update the code, you can try it within any VIEW3D mode (include weight paint). If the code work, please consider to mark as accepted answer. $\endgroup$
    – HikariTW
    May 8, 2019 at 7:10
  • $\begingroup$ Great job! I will refine the panel layout a bit and try to get the "selected only"-function to work also, but this version is already working perfectly fine. Thank you again for your support! $\endgroup$
    – Ben
    May 8, 2019 at 15:42
  • $\begingroup$ The issue is that the numbers are displayed as if the armature was in the rest pose. So when you move bones in pose mode, to actually check your weight painting - the overlayed numbers are totally misplaced and useless. $\endgroup$
    – user920391
    May 26, 2020 at 22:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .