2
$\begingroup$

So I'm still something of a beginner and I have a feeling my problem deals with my understanding of how python works. I would like to effectively have vertex groups that are separate from the built-in vertex groups. It would make my life a little easier and having them all combined would complicate things (such as armatures and what not). I can almost get it! But for some reason, I can't figure out how to get an array to work inside of a PropertGroup the way StringProperty, IntProperty, etc. do. Instead of each object holding their own arrays (or even individual selections they are all the same or I have to do a huge array (see below). Anyone know how to do this or get around it? Thanks!

import bpy, bmesh
from bpy.props import CollectionProperty, IntProperty
from bpy.types import PropertyGroup, UIList, Operator, Panel

class ListItem(PropertyGroup):
    """Group of properties representing an item in the list."""

   name = StringProperty(
           name="Name",
           description="A name for this item",
           default='Untitled')

    vGroup = []


class MY_UL_List(UIList):
    """Demo UIList."""

    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):

        # We could write some code to decide which icon to use here...
        custom_icon = 'OBJECT_DATAMODE'

        # Make sure your code supports all 3 layout types
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.label(item.name, icon = custom_icon)

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



class LIST_OT_NewItem(Operator):
    """Add a new item to the list."""

    bl_idname = "my_list.new_item"
    bl_label = "Add a new item"

    def execute(self, context):
        scene = bpy.context.scene
        obj = context.object
        bm = bmesh.from_edit_mesh(obj.data)
        ind = obj.list_index
        listItem = obj.my_list
        slct = []
        #Add new item to my_list
       obj.my_list.add()
        #Set new item as active in list
        obj.list_index = len(listItem)-1
        #Gather selected verts
        for v in bm.verts:
            if v.select:
                slct.append(v.index)
        #Add verts to currently selected Item in list
        listItem[ind].vGroup.append(slct)

        return{'FINISHED'}


class LIST_OT_DeleteItem(Operator):
    """Delete the selected item from the list."""

    bl_idname = "my_list.delete_item"
    bl_label = "Delete"

    @classmethod
    def poll(cls, context):
        return context.object.my_list

    def execute(self, context):
        obj = context.object
        my_list = obj.my_list
        index = obj.list_index

        my_list[index].vGroup.pop(index)
        my_list.remove(index)
        index = min(max(0, index - 1), len(my_list) - 1)
        obj.list_index = len(my_list)-1

        return{'FINISHED'}

class Load(Operator):
    """Load the selected item from the list."""

    bl_idname = "my_list.load"
    bl_label = "Load"

    @classmethod
    def poll(cls, context):
        return context.object.my_list

    def execute(self, context):
        scene = bpy.context.scene
        obj = context.object
        index = obj.list_index
        mesh = obj.data
        listItem = obj.my_list
        bm = bmesh.from_edit_mesh(mesh)
        #Get verts from array and select them
        for v in listItem[index].vGroup[index]:
            print('Loaded Vertex: ',v)
            bm.verts[v].select = True
            #add to selection history
            bm.select_history.add(bm.verts[v])
        mesh.update()

        return{'FINISHED'}


class PT_ListExample(Panel):
    """Demo panel for UI list Tutorial."""
    bl_label = "Saved Selections"
    bl_space_type = 'VIEW_3D'
    bl_category = "My Stuff"
    bl_region_type = 'TOOLS'
    bl_idname = "SCENE_PT_LIST_DEMO"

    @classmethod
    def poll(cls, context):
        if context.object.mode == 'EDIT':
            return True

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        obj = context.object
        ind = obj.list_index

        row = layout.row()
        row.template_list("MY_UL_List", "The_List", obj, "my_list", obj, "list_index")

        row = layout.row()
        row.operator('my_list.new_item', text='Add')
        row.operator('my_list.delete_item')
        row.operator('my_list.load')

        if ind >= 0 and obj.my_list:
            item = obj.my_list[ind]

            row = layout.row()
            row.prop(item, "name")
            layout.label(text=str(item.vGroup[ind]))
            #layout.label(text=item.vGroup)

classes = [ListItem,MY_UL_List,LIST_OT_NewItem,LIST_OT_DeleteItem,Load,PT_ListExample]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.Object.my_list = CollectionProperty(type = ListItem)
    bpy.types.Object.list_index = IntProperty(name = "Index for my_list", default = 0)


def unregister():

    del bpy.types.Object.my_list
    del bpy.types.Object.list_index

    for cls in classes:
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()
$\endgroup$

2 Answers 2

4
$\begingroup$

Mesh data layers

Would seriously consider not using a list of vertex indices to "emulate" vertex groups. As soon as the mesh is edited, eg a vertex is removed, or the indices are re-sorted, your lists will be misleading and of little to no use.

Instead we can set up a data layer on the bmesh vertices. There is no boolean type, so will use an integer layer where 1 is in the group 0 is not.

Setting up an int data layer on an edit bmesh

import bpy
import bmesh
obj = bpy.context.object
bm = bmesh.from_edit_mesh(obj.data)

my_id = (bm.verts.layers.int.get("id") or 
        bm.verts.layers.int.new('id'))
for v in bm.verts:
    v[my_id] = 1 if v.select else 0 # int(v.select) 

bmesh.update_edit_mesh(obj.data)

Retrieving a data layer from mesh in object mode.

import bpy
context = bpy.context
ob = context.object
me = ob.data

vlayer = me.vertex_layers_int.get("id")
if vlayer:
    vlist = [v.index for v in me.vertices if 
            vlayer.data[v.index].value]
    print(vlist)

Note: "id" here is just an arbitrary name, use your vgroup name

On properties

In your question code ListItem.vGroup is a class property. All instances of the class will share this, hence your problem in question. Below is a simple getter property, where self is the list item, and self.id_data is the blender object instance of the type that the collection property is defined on.

class ListItem(PropertyGroup):
    """Group of properties representing an item in the list."""

    name = StringProperty(
           name="Name",
           description="A name for this item",
           default='Untitled')
    @property
    def vgroup(self):
        print("ob = ", self.id_data, ", item = ", self.name)
        return ob.name

Defining a class property using bpy.props does all the setting up of a property for you. The getter / setter example shows somewhat how the properties are handled internally by saving to an ID property.

Putting it together

Added a little get_bmesh method to re-use edit bmesh from a global dictionary.

dic = {}
def get_bmesh(ob):
    bm = None
    if ob.mode == 'EDIT' and ob.type == 'MESH':
        bm = dic.setdefault(ob.name, bmesh.from_edit_mesh(ob.data))
    else:
        dic.clear()        
    return bm

class ListItem(PropertyGroup):
    """Group of properties representing an item in the list."""

    name = StringProperty(
           name="Name",
           description="A name for this item",
           default='Untitled')
    @property
    def vgroup(self):
        ob = self.id_data
        bm = get_bmesh(ob)
        id = self.name
        if bm:
            vlayer = bm.verts.layers.int.get(id)
            return [v.index for v in bm.verts 
                    if v[vlayer]]
        else:
            vlayer = me.vertex_layers_int.get(id)
            if vlayer:
                return [v.index for v in me.vertices if 
                        vlayer.data[v.index].value]

and adjust other operators accordingly, which I will leave in your capable hands.

$\endgroup$
0
1
$\begingroup$

The best approach I've come of with so far it to use a custom dictionary on the object where the keys are group names, and the values are arrays. This seems to work, but I would rather be able to extend bpy.types.Object with a new CollectionProperty with some kind of array type. (I am trying to implement face groups.)

Note that batFINGER's bmesh approach saves on the mesh, not the object. Native vertex groups also seem to work this way--selecting a vertex group for one object also selects the vertices of linked data--but the "Vertex Groups" panel in Properties > Data seems specific to the object.

I think I will try the bmesh approach but with additional attributes used to filter by object.

$\endgroup$

You must log in to answer this question.

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