3
$\begingroup$

I have a layer of vertex properties that I would like to expose through a Blender UI panel, so I can edit them. Through examples I learnt that you can expose the property and have it displayed and editable using props in the layout.

For example to display the cursor I could do

layout.column().prop(view, "cursor_location", text="Location")

But I can't do the same at all for selected list of vertices.

I have the following code.

class path_mesh_viewer_panel(bpy.types.Panel):
    bl_label = "Path Node"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

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

    def draw(self, context):
        layout = self.layout
        ob = bpy.context.active_object
        me = ob.data
        bm = bmesh.from_edit_mesh(me)

        node_width_key = bm.verts.layers.float['node_width']

        bm.verts.ensure_lookup_table()
        bm.edges.ensure_lookup_table()

        if bm.select_mode != {'VERT'}:
            layout.label("Vertex select only", icon = 'INFO')
            return

        selected = [vert.index for vert in bm.verts if vert.select]
        if not selected:
            layout.label("Nothing selected", icon = 'INFO')
        else:
            for i in selected:
                # I would like to have UI element here so I can edit the vertex layers
                layout.column().prop(bm.verts[i][node_width_key], "", text =     'INFO')

The last line immediately complains and gives out the following error in the System Console.

Traceback (most recent call last):
File "E:\paths-visualizer\blenderview.py", line 94, in draw
layout.column().prop(bm.verts[i][node_area_id], "", text = 'INFO')
TypeError: UILayout.prop(): error with argument 1, "data" -  Function.data expected a AnyType type, not int

location: <unknown location>:-1

Why am I getting the error? I have declared the vertex layer in code that creates the mesh, I can access it fine but not in the UI. Is there any way to do it?

$\endgroup$
2
  • 1
    $\begingroup$ I think you'd better use a CollectionProperty, and then display a UILayout.prop_enum in your panel. I believe Layout props() first argument must be a bpy.types.prop name you declared before. $\endgroup$
    – Polosson
    Aug 30, 2015 at 10:06
  • $\begingroup$ @zeffii No, every new vertex would have its own layer property to be editable in the panel $\endgroup$
    – Swoorup
    Aug 30, 2015 at 13:30

1 Answer 1

4
$\begingroup$

Layout.prop() supports bpy.props and ID properties only. You can't use it for custom data layers such as bm.verts.layers of the Bmesh module.

You would have to create a (temporary) set of properties for (selected) bm.verts in order to display their custom data. Because there are no proper callbacks to detect changes to the mesh, the only way I see is to use brute force: re-fill a CollectionProperty on every panel redraw with as many elements as there are (selected) vertices. These elements can be displayed without problems, but it's highly inefficient.

It would be much better to have a button the user needs to press to update the template_list() with selected vertices, so that it doesn't happen all the time (i.e. 20x per second when moving the mouse over the panel).

import bpy
import bmesh

class MeshVertLayerList(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.label(item.name)
            layout.prop(item, "value", text="", emboss=False)

        elif self.layout_type in {'GRID'}:
            layout.alignment = 'CENTER'
            layout.label(text="")


class MESH_OT_vert_layer_add(bpy.types.Operator):
    """Tooltip"""
    bl_label = "Add Vertex Layer"
    bl_idname = "mesh.vert_layer_add"

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.mode == 'EDIT')

    def execute(self, context):
        bm = bmesh.from_edit_mesh(context.object.data)
        try:
            bm.verts.layers.float['test']
        except KeyError:
            bm.verts.layers.float.new('test')
        else:
            return {'CANCELLED'}

        return {'FINISHED'}


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "data"

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.mode == 'EDIT')

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

        ob = context.object
        me = ob.data
        bm = bmesh.from_edit_mesh(me)

        try:
            l = bm.verts.layers.float['test']
        except KeyError:
            layout.label("No custom data layer 'test'.")
            layout.operator("mesh.vert_layer_add")
            return

        wm.mesh_vert_layer.verts.clear()
        for v in bm.verts:
            if v.select:
                item = wm.mesh_vert_layer.verts.add()
                item.name = "Vertex #%i" % v.index
                item.index = v.index
                item.value = v[l]

        layout.template_list("MeshVertLayerList", "", wm.mesh_vert_layer, "verts", wm.mesh_vert_layer, "index")


def upd(self, context):
    try:
        me = context.object.data
        bm = bmesh.from_edit_mesh(me)
        bm.verts[self.index][bm.verts.layers.float['test']] = self.value
        bmesh.update_edit_mesh(me, tessface=False, destructive=False)
    except:
        pass

class MeshVertLayerVert(bpy.types.PropertyGroup):
    value = bpy.props.FloatProperty(update=upd)
    index = bpy.props.IntProperty()


class MeshVertLayer(bpy.types.PropertyGroup):
    index = bpy.props.IntProperty()
    verts = bpy.props.CollectionProperty(type=MeshVertLayerVert)

def register():
    bpy.utils.register_module(__name__)
    bpy.types.WindowManager.mesh_vert_layer = bpy.props.PointerProperty(type=MeshVertLayer)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.WindowManager.mesh_vert_layer


if __name__ == "__main__":
    register()

As a side note: custom data "propagates" on mesh operations such as extrude. There's a patch in the making to disable it: https://developer.blender.org/T42865

edit: It's possible to bind an update operator to SELECTMOUSE RELEASE to update the CollectionProperty after a selection change. It will work for right-clicks only however. I'm not sure if it's possible to do the same for modal selection operators such as lasso.

import bpy
import bmesh

class MeshVertLayerList(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.label(item.name)
            layout.prop(item, "value", text="", emboss=False)

        elif self.layout_type in {'GRID'}:
            layout.alignment = 'CENTER'
            layout.label(text="")

class MESH_OT_vert_prop_update(bpy.types.Operator):
    """Tooltip"""
    bl_label = "Vertex Properties Update"
    bl_idname = "mesh.vert_prop_update"
    bl_options = {'INTERNAL'}

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.mode == 'EDIT')


    def execute(self, context):            
        ob = context.object
        me = ob.data
        wm = context.window_manager
        bm = bmesh.from_edit_mesh(me)

        try:
            l = bm.verts.layers.float['test']
        except KeyError:
            return {'CANCELLED'}

        wm.mesh_vert_layer.verts.clear()
        for v in bm.verts:
            if v.select:
                item = wm.mesh_vert_layer.verts.add()
                item.name = "Vertex #%i" % v.index
                item.index = v.index
                item.value = v[l]
        return {'FINISHED'}


class MESH_OT_vert_layer_add(bpy.types.Operator):
    """Tooltip"""
    bl_label = "Add Vertex Layer"
    bl_idname = "mesh.vert_layer_add"

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.mode == 'EDIT')

    def execute(self, context):
        bm = bmesh.from_edit_mesh(context.object.data)
        try:
            bm.verts.layers.float['test']
        except KeyError:
            bm.verts.layers.float.new('test')
        else:
            return {'CANCELLED'}

        return {'FINISHED'}


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "data"

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.mode == 'EDIT')

    def draw(self, context):
        layout = self.layout
        wm = context.window_manager
        layout.template_list("MeshVertLayerList", "", wm.mesh_vert_layer, "verts", wm.mesh_vert_layer, "index")


def upd(self, context):
    try:
        me = context.object.data
        bm = bmesh.from_edit_mesh(me)
        bm.verts[self.index][bm.verts.layers.float['test']] = self.value
        bmesh.update_edit_mesh(me, tessface=False, destructive=False)
    except:
        pass

class MeshVertLayerVert(bpy.types.PropertyGroup):
    value = bpy.props.FloatProperty(update=upd)
    index = bpy.props.IntProperty()


class MeshVertLayer(bpy.types.PropertyGroup):
    index = bpy.props.IntProperty()
    verts = bpy.props.CollectionProperty(type=MeshVertLayerVert)


addon_keymaps = []

def register():
    bpy.utils.register_module(__name__)
    bpy.types.WindowManager.mesh_vert_layer = bpy.props.PointerProperty(type=MeshVertLayer)

    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name='Mesh', space_type='EMPTY')

    kmi = km.keymap_items.new(MESH_OT_vert_prop_update.bl_idname, 'SELECTMOUSE', 'RELEASE', any=True)

    addon_keymaps.append((km, kmi))


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.WindowManager.mesh_vert_layer

    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()


if __name__ == "__main__":
    register()
$\endgroup$
3
  • $\begingroup$ Thanks very much. This is almost exactly what I needed. I tested this, However, it easily slows to a crawl when selecting large amount of vertices. Isn't it possible to hook this on every vertex select operation or mouse click event instead? I don't really mind the data "propagation" on mesh operations. $\endgroup$
    – Swoorup
    Aug 30, 2015 at 14:13
  • 2
    $\begingroup$ You can actually intercept the select event (on release) and run an update operator to refill the collection. You would need to do this for other operators such as Select All (A) as well. I have a feeling that this will not be possible for modal selection operators such as lasso or box select however. Mesh selection changes in general can not be determined, as I already said. Your best option is still a button to manually update the collection property (see my updated answer, you would basically remove the keymap stuff and add the update operator to the panel). $\endgroup$
    – CodeManX
    Aug 30, 2015 at 14:39
  • 1
    $\begingroup$ I am marking this as answer, I hope blender-dev community can do something about this. I am choosing to go with your first answer, however I am restricting the collection list only up to certain elements to keep under performance. Thanks again!!! $\endgroup$
    – Swoorup
    Aug 30, 2015 at 15:22

You must log in to answer this question.

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