# Create UI slider for each member in a custom vertex layer (floats)

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?

• 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. – Polosson Aug 30 '15 at 10:06
• @zeffii No, every new vertex would have its own layer property to be editable in the panel – Swoorup Aug 30 '15 at 13:30

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="")

"""Tooltip"""

@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'.")
return

wm.mesh_vert_layer.verts.clear()
for v in bm.verts:
if v.select:
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.name = "Vertex #%i" % v.index
item.index = v.index
item.value = v[l]
return {'FINISHED'}

"""Tooltip"""

@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)

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

wm = bpy.context.window_manager

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

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

km.keymap_items.remove(kmi)

if __name__ == "__main__":
register()

• 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. – Swoorup Aug 30 '15 at 14:13
• 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). – CodeManX Aug 30 '15 at 14:39
• 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!!! – Swoorup Aug 30 '15 at 15:22