Making custom layers visible

I have several ideas for addons that could best be configured via per-face data. But users need to be able to manipulate that data. I can see making a (modal) operator which lets them set the value for the selected faces, but cannot figure out how to give the user any useful feedback.

Basically, how would one recreate the “Edge Crease” operator in python, inclusive of the “show_edge_crease” overlay.

If I get you right, you want some kind of overlay inside the 3d view in editmode to give the user interactive feedback?

You could do that with a modal operator and a draw handler. See the examples in the Text editor.

In order to draw lines etc. on top of everything else (X-ray style), you’ll have to use POST_PIXEL for the draw handler and convert 3d coordinates to 2d pixel coordinates: from bpy_extras.view3d_utils import location_3d_to_region_2d

Ok, that’s definitely put me on the right track. I’d not see draw_handler_add before.

Seeing as it’s mostly undocumented, I did some source spelunkying to see how it works, the legal regionsyou can call draw_handler_add with are WINDOW, HEADER, CHANNELS, TEMPORARY, UI, TOOLS, TOOL_PROPS, PREVIEW, and the legal draw modes are POST_PIXEL, POST_VIEW, PRE_VIEW. The 3 draw modes are invoked here and here, each with a call to ED_region_draw_cb_draw.

Anyway, I’d much rather have the lines appear with occlusion, like edge crease does. It looks like PRE_VIEW is the only option for sharing the depth buffer. Will have to experiment a bit.

Am I right in saying other Python script writers would be interested in how to do this? Or is it just me?

Oh, I see you already know all this. And I must have been wrong about PRE vs POST as the “Math Vis” plugin uses POSTand seems to have occlusion working. I should be able to get most of what I want by copying that plugin.

Yeah, there’s unfortunately little documentation. The templates found in Blender’s text editor were my primary source.

PRE_VIEW is really beneath everything else (kind of the opposite of X-ray), POST_VIEW draws in actual 3D space.

You might also want to check out this addon:
http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/3D_interaction/Index_Visualiser

Ok, I’ve basically got this now. I’ve written a sample script to color edges red/green/blue to demonstrate how to do it, if anyone else is interested.


bl_info = {
    "name": "Color Edges",
    "description": "Sample script for editing and displaying customlayer data.",
    "author": "Adam Newgas",
    "version": (1, 0, 0),
    "blender": (2, 74, 0),
    "location": "",
    "warning": "",
    "wiki_url": "",
    "category": ""}

import bpy
import bgl
import blf
import bmesh
import itertools
from bgl import  GL_LINES, GL_FLOAT

# Custom layer definitions

def get_edgecolor_layer(bm, create=False):
    r = bm.edges.layers.int.get("edgecolor")
    if r is None and create:
        r = bm.edges.layers.int.new("edgecolor")
    return r


color_enum = [("NONE","None","", 0),
              ("RED", "Red","Colors the given edge red", 1),
              ("GREEN", "Green","Colors the given edge green", 2),
              ("BLUE", "Blue","Colors the given edge blue", 3),
              ]

color_enum_to_int = {item[0]: item[3] for item in color_enum}

colors = [None, [1,0,0], [0,1,0],[0,0,1]]

# Drawing code

callback_handle = [None]

def tag_redraw_all_view3d():
    context = bpy.context

    # Py cant access notifers
    for window in context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for region in area.regions:
                    if region.type == 'WINDOW':
                        region.tag_redraw()


def enable_color_draw_callback():
    if callback_handle[0]:
        return

    handle_view = bpy.types.SpaceView3D.draw_handler_add(draw_callback_view, (), 'WINDOW', 'POST_VIEW')
    callback_handle[0] = handle_view

    tag_redraw_all_view3d()


def disable_color_draw_callback():
    if not callback_handle[0]:
        return

    handle_view, = callback_handle
    bpy.types.SpaceView3D.draw_handler_remove(handle_view, 'WINDOW')
    callback_handle[0] = None

    tag_redraw_all_view3d()


def draw_callback_view():
    context = bpy.context
    if context.mode != "EDIT_MESH":
        return
    ob = context.active_object
    if ob is None:
        return
    if ob.type != "MESH":
        return
    if ob.mode != "EDIT":
        return
    if False:
        # Respects modifiers, but not hidden edges
        ob.update_from_editmode()
        bm = bmesh.new()
        bm.from_object(ob, context.scene)
    else:
        # Faster bmesh access, but doesn't respect modifiers
        bm = bmesh.from_edit_mesh(ob.data)
    layer = get_edgecolor_layer(bm)
    if layer is None:
        return
    bgl.glLineWidth(3.0)
    bgl.glPushMatrix()
    b = bgl.Buffer(GL_FLOAT, [16], list(itertools.chain(*ob.matrix_world.col)))
    bgl.glMultMatrixf(b)
    for edge in bm.edges:
        if edge.hide: continue
        color_int = edge[layer]
        if color_int == 0: continue
        bgl.glColor3f(*colors[color_int])
        bgl.glBegin(GL_LINES)
        for v in edge.verts:
            bgl.glVertex3f(*v.co)
        bgl.glEnd()
    bgl.glPointSize(1.0)
    bgl.glLineWidth(1.0)
    bgl.glPopMatrix()

def set_edge_color_visible_property(self, context):
    print("asdf",self.edge_color_visible)
    if self.edge_color_visible:
        enable_color_draw_callback()
    else:
        disable_color_draw_callback()



# Define operators

# A clone of WM_menu_invoke, which Blender doesn't expose to the UI
def menu_invoke(operator, context):
    def draw(self, context):
        self.layout.operator_context = "EXEC_REGION_WIN"
        self.layout.operator_enum(operator.bl_idname, "type")
    context.window_manager.popup_menu(draw, operator.bl_label)
    return {"INTERFACE"}

class ColorEdgesOperator(bpy.types.Operator):
    """Sets the edge color for selected edges"""
    bl_idname = "view3d.color_edges"
    bl_label = "Color Edges"
    bl_options = {'REGISTER', 'UNDO'}

    type = bpy.props.EnumProperty(items=color_enum,
                                  name="Color",
                                  description="Controls what color the edges will be colored")

    @classmethod
    def poll(cls, context):
        ob = context.active_object
        return ((ob is not None) and
                (ob.mode == "EDIT") and
                (ob.type == "MESH") and
                (context.mode == "EDIT_MESH"))

    def execute(self, context):
        obj = context.active_object
        bm = bmesh.from_edit_mesh(obj.data)
        layer = get_edgecolor_layer(bm, create=True)
        has_selected_edges = False
        for edge in bm.edges:
            if edge.select:
                edge[layer] = color_enum_to_int[self.type]
                has_selected_edges = True
        if not has_selected_edges:
            self.report({"WARNING"}, "No edges selected")
        bmesh.update_edit_mesh(obj.data, destructive=False)
        return {"FINISHED"}

    def invoke(self, context, event):
        return menu_invoke(self, context)

class ColorEdgesPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Color Edges Panel"
    bl_idname = "OBJECT_PT_color_edges"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout

        layout.prop(context.scene, "edge_color_visible")
        layout.operator("view3d.color_edges")

def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.edge_color_visible = (
        bpy.props.BoolProperty(name = "Edge Color Visible",
                               description="If true, custom edge colors will be displayed in Edit Mode",
                               default=False,
                               update=set_edge_color_visible_property))
    # Probably not a good a good idea for a real script
    # But I want to get people on their feet quickly
    enable_color_draw_callback()

def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.edge_color_visible
    disable_color_draw_callback()

if __name__ == "__main__":
    register()

There’s one bit I’m not happy with though:


    if False:
        # Respects modifiers, but not hidden edges
        ob.update_from_editmode()
        bm = bmesh.new()
        bm.from_object(ob, context.scene)
    else:
        # Faster bmesh access, but doesn't respect modifiers
        bm = bmesh.from_edit_mesh(ob.data)

Basically, I can get a bmesh straight from the edit data, or from the modifiers. But neither of the work quite right. The real Mark Seams behaviour follows only the modifiers you’ve enabled to be visible in edit mode, and respects hidden edges.