I’m currently working on an import/export script for Blender 2.69 and I need to be able to assign a couple of properties to each face/polygon. This isn’t actually my biggest problem since I’ve figgured this out:
bpy.types.MeshPolygon.my_prop = True
My biggest problem is that I want to be able to select a face and then being able to set my_prop for this face to True/False through the UI in the tools panel. This works great for objects, like this:
import bpy
from bpy.props import *
bpy.types.Object.my_prop = BoolProperty(default = True)
class Test(bpy.types.Panel):
bl_label = "Test"
bl_idname = "test_panel"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
def draw(self, context):
row = self.layout.row()
row.prop(context.object, "my_prop", text = "My prop")
bpy.utils.register_class(Test)
I basically want the exact same thing, except for faces/polygons instead of objects. Is it possible?
I’ve been reading about bmesh and I’ve got no clue on how to use it. It doesn’t make sense to make a bmesh from a mesh, and then copy the bmesh data back into the mesh. What’s up with that? If you’ve got any example code regarding bmesh and custom layers, that would be very helpful! I can’t seem find much info about it on the internet.
Why the code below work? It says “property not found: MeshPolygon.my_prop” in the console, but I’ve obviously set my_prop.
import bpy
from bpy.props import *
bpy.types.MeshPolygon.my_prop = 'test'
class Test(bpy.types.Panel):
bl_label = "Test"
bl_idname = "test_panel"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
@classmethod
def poll(self, context):
return context.object is not None and context.object.type == 'MESH' and context.mode == 'EDIT_MESH'
def draw(self, context):
row = self.layout.row()
row.prop(context.object.data.polygons[0], "my_prop")
bpy.utils.register_class(Test)
This API gives access the blenders internal mesh editing api, featuring geometry connectivity data and access to editing operations such as split, separate, collapse and dissolve.
The features exposed closely follow the C API, giving python access to the functions used by blenders own mesh editing tools.
For an overview of BMesh data types and how they reference each other see: BMesh Design Document .
If you access a mesh in editmode via bmesh.from_edit_mesh(), there is no conversion between mesh and bmesh (mesh is actually a bmesh, but in the context of modelling system, bmesh module is a python module to access bmesh functions and data).
bpy.types.MeshPolygon is not any of the supported types:
Custom properties can be added to any subclass of an ID, Bone and PoseBone.
Here’s a minimal comparison between standard API vs bmesh module custom data access:
import bpy
import bmesh
from bpy import data as D
from bpy import context as C
f = C.object.data.polygon_layers_float.new("my flop")
f.data[0].value = 1
print(f.data[0].value) # 1.0
print(f.data[1].value # 0.0), the default
bm = bmesh.new()
bm.from_mesh(C.object.data)
l = bm.faces.layers.float['my flop'] # layer created via standard API above
print(bm.faces[0][l]) # 1.0
print(bm.faces[1][l]) # 1.0
# create int layer in bmesh module mannor
li = bm.faces.layers.int.new("custom_int_layer")
bm.faces[5][li] = 44
print(bm.faces[5][li]) # 44
print(bm.faces[6][li]) # 0, the default
Thanks, but I still can’t make it visible in the user interface. Well, I’ve successfully made it visible in the UI, but I can’t set the value. This is what I’ve come up with so far:
In order to make it possible to set the “property”, I need to use row.prop() instead of row.label(). How do I do this? I mean, row.prop() expect one AnyType(?) and one string.
bm properties are not bpy.props properties, you can’t add the former to the UI.
Here’s a working solution, but i really discourage it, since it redraws the list on every scene update (~20 times per second?).
See the data tab in mesh edit mode, and select one or multiple faces:
import bpy
import bmesh
class MeshFaceLayerList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
#ob = data
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_face_layer_add(bpy.types.Operator):
"""Tooltip"""
bl_label = "Add Face Layer"
bl_idname = "mesh.face_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.faces.layers.float['test']
except KeyError:
bm.faces.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.faces.layers.float['test']
except KeyError:
layout.label("No custom data layer 'test'.")
layout.operator("mesh.face_layer_add")
return
wm.mesh_face_layer.faces.clear()
for f in bm.faces:
if f.select:
item = wm.mesh_face_layer.faces.add()
item.name = "Face #%i" % f.index
item.index = f.index
item.value = f[l]
layout.template_list("MeshFaceLayerList", "", wm.mesh_face_layer, "faces", wm.mesh_face_layer, "index")
def upd(self, context):
try:
me = context.object.data
bm = bmesh.from_edit_mesh(me)
bm.faces[self.index][bm.faces.layers.float['test']] = self.value
bmesh.update_edit_mesh(me, tessface=False, destructive=False)
except:
pass
class MeshFaceLayerFace(bpy.types.PropertyGroup):
value = bpy.props.FloatProperty(update=upd)
index = bpy.props.IntProperty()
class MeshFaceLayer(bpy.types.PropertyGroup):
index = bpy.props.IntProperty()
faces = bpy.props.CollectionProperty(type=MeshFaceLayerFace)
def register():
bpy.utils.register_module(__name__)
bpy.types.WindowManager.mesh_face_layer = bpy.props.PointerProperty(type=MeshFaceLayer, options={'SKIP_SAVE'})
def unregister():
bpy.utils.unregister_module(__name__)
del bpy.types.WindowManager.mesh_face_layer
if __name__ == "__main__":
register()
That’s something similar to what I want. I actually don’t really need a list of each selected face, I might have been a little unclear about that. Anyway, your script helped me a lot, it made me think outside the box a little. I succesfully made a script that seems to work rather well. Here it is:
import bpy
import bmesh
from bpy.props import *
class FaceProperties(bpy.types.PropertyGroup):
index = IntProperty(default = -1)
my_prop = BoolProperty(default = True)
bpy.utils.register_class(FaceProperties)
bpy.types.WindowManager.current_face = PointerProperty(type = FaceProperties)
class FacePropertiesPanel(bpy.types.Panel):
bl_label = "Face properties"
bl_idname = "face_properties"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
@classmethod
def poll(cls, context):
return context.mode == 'EDIT_MESH'
def draw(self, context):
me = context.object.data
bm = bmesh.from_edit_mesh(me)
try:
my_prop_layer = bm.faces.layers.int['my_prop']
except:
my_prop_layer = bm.faces.layers.int.new('my_prop')
for face in bm.faces:
face[my_prop_layer] = 1
if bm.faces.active.index != context.window_manager.current_face.index:
context.window_manager.current_face.index = bm.faces.active.index
context.window_manager.current_face.my_prop = False if bm.faces.active[my_prop_layer] is 0 else True
# Sets the transparency, my_prop and doublesided for the current face.
bm.faces.active[my_prop_layer] = 0 if context.window_manager.current_face.my_prop is False else 1
# Updates the mesh
bmesh.update_edit_mesh(me)
# Draws the panel.
row = self.layout.row()
row.label('Current face: '+str(context.window_manager.current_face.index))
row = self.layout.row()
row.prop(context.window_manager.current_face, "my_prop", text = "My prop")
bpy.utils.register_class(FacePropertiesPanel)
Please, let me know if there’s an easier way of accomplishing this. I’m pretty sure there is. I like clean, straight-forward code which does exactly what you want in just a few lines so this looks a bit messy to me.
Right now you’ll need to select each face, one by one, and set the properties. I’ll make an operator which let’s you apply the properties to all selected faces, this will probably be rather easy.
there’s no easier solution IMO, I tried Mesh.polygon_layers_int to get rid of bmesh from / to mesh, but it works in object mode only. So should be fine!