How do you make a list of object dropdowns then iterate through the list?

I’m working on an addon. I’d like to make a list that shows two dropdown fields with each field allowing you to select an object and a vertex group respectively.
These fields appear on a row. More rows can be added by clicking a button.
This panel appears only when you select a mesh object so the vertex groups and assigned objects will be specific to each mesh object in the scene.

The idea is, this list allows you to assign a high poly mesh to the vertex group (which belongs to a low poly mesh). A translation can then be applied to each vertex group in that list, essentially moving the vertex group as if it was a object. Since we know which mesh is “assigned” to each group, the same translation can be applied said high poly meshes.

This allows us to explode and recombine the mesh for baking purposes in one click.

Anyway, I know a bit of Python but my main issue is understanding how it works in Blender. This is what I have so far for my panel:

class ExplodedBake(bpy.types.Panel):    """Explodes a selection, bakes a number of maps, and un-explodes it"""
    bl_label = "Exploded Bake"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        ob = context.object


        split = layout.split()
        #column one
        col = split.column()
        col.label(text="Object:")
        ##### add object group picker here


        
        #column two
        col = split.column()
        col.label(text="Vertex Group:")
        row = col.row(align=True)
        ##### add vertex group picker here
        
        layout.operator("button.explode", text="Explode!")

As you can see I’m not sure how to add the object and vertex group picker. How would you make a field that allows you to pick certain things (in this case vertex group and mesh object)?
Also, assuming we have multiple rows of fields, how would you iterate through them to get the objects and vertex groups they represent?

Thanks

Here is a quick example how to add a picker:

import bpy


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

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

        obj = context.object

        layout.prop_search(obj, "group_name", bpy.data, "groups")
        layout.prop_search(obj, "vgroup_name", obj, "vertex_groups")

def group_name_cb(self, context):
    print("Group changed to", self.group_name)

def vgroup_name_cb(context, scene):
    print("Vertex Group changed to", self.vgroup_name_cb)
    
def register():
    bpy.types.Object.group_name = bpy.props.StringProperty(name="Group", update=group_name_cb)
    bpy.types.Object.vgroup_name = bpy.props.StringProperty(name="Vertex Group", update=vgroup_name_cb)
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Object.group_name
    del bpy.types.Object.vgroup_name


if __name__ == "__main__":
    register()


You can react on changes in the callback functions.

Thanks, I think I’ve seen this before but couldnt figure out how to use it. Looking back at the docs it makes more sense.
I have an idea about how to create and iterate through a list of dropdown lists but I may be back later if I can’t figure it out (and also if I do so people wondering the same thing as me can get an answer)

I’m back. I’ve been trying to dynamically create the fields however currently, when a field is clicked and set to an item, all the fields of the same type change. How do I make it make each subsequent field unique?

mainArray = []

class ExplodedBake(bpy.types.Panel):
	"""Explodes a selection, bakes a number of maps, and un-explodes it"""
	bl_label = "Exploded Bake"
	bl_space_type = 'VIEW_3D'
	bl_region_type = 'TOOLS'
		
	def draw(self, context):
		layout = self.layout
		scene = context.scene
		obj = context.object
		rowNo = 5;
		split = layout.split()
		col = split.column()
		col.label(text="Object:")
		col2 = split.column()
		col2.label(text="Vertex Group:")
		for x in range(0,rowNo):
			a = col.prop_search(obj, "name", bpy.data, "objects")
			b = col2.prop_search(obj, "vgroup_name", obj, "vertex_groups")
			mainArray.append({"object": a, "vertex_group": b})
			print(mainDict)	 

I know the way I’m attempting to store the information is probably horribly wrong but that’s another issue (any ideas how to get the data out of the field and into an array?).

Oh no, you don’t want to do that. You don’t change things in the draw event, you display things. Take that mainArray and turn it into an EnumProperty and place the population code in the list_populate def.


    def list_update(self, context):
            self.update()
    
    def list_populate(self, context):
        result = [('ABS','Abs','Absoulte Power'),('SIN','Sin','Sine'),('COS','Cos','Cosine'),('DEG','Deg','Degrees'),('RAD','Rad','Radians')]
        return result
    
    # Your population code instead.
    def list_populate(self, context):
        result = []
        rowNo = 5
        for x in range(0,rowNo):
            a = bpy.data.objects.get(self.name)  #Assumes this code is running in the object context.
            b = a.vertex_groups.get("vgroup_name")
            result.append({"object": a, "vertex_group": b})
        return result

    function_type = bpy.props.EnumProperty(
        items=list_populate, 
        description="A list of supported math functions.", 
        update=list_update)

Then in your draw event display the enum property.


    def draw_buttons(self, context, layout):
        layout.prop(self, "function_type",text='Func')

When the end user changes the property the list will repopulate and the draw will automatically fire and display the new selection.

I think I understand what you’ve done with how it populates the enum property. I’m having trouble running the code though.

It says “rna_uiItemR: property not found: ExplodedBake.function_type”

Here’s how it looks currently:

class ExplodedBake(bpy.types.Panel):	"""Explodes a selection, bakes a number of maps, and un-explodes it"""
	bl_label = "Exploded Bake"
	bl_space_type = 'VIEW_3D'
	bl_region_type = 'TOOLS'


	def list_update(self, context):
		self.update()


	# Your population code instead.
	def list_populate(self, context):
		result = []
		rowNo = 5
		for x in range(0,rowNo):
			a = bpy.data.objects.get(self.name)  #Assumes this code is running in the object context.
			b = a.vertex_groups.get("vgroup_name")
			result.append({"object": a, "vertex_group": b})
		return result


	function_type = bpy.props.EnumProperty(
		items=list_populate, 
		description="Explode list", 
		update=list_update)


	def draw(self, context):
		layout = self.layout
		scene = context.scene
		obj = context.object
		rowNo = 5;
		split = layout.split()
		col = split.column()
		col.label(text="Object:")
		col2 = split.column()
		col2.label(text="Vertex Group:")
		# for x in range(0,rowNo):
		# 	a = col.prop_search(obj, "name", bpy.data, "objects")
		# 	b = col2.prop_search(obj, "vgroup_name", obj, "vertex_groups")
		# 	mainArray.append({"object": a, "vertex_group": b})
		# 	print(mainArray)


		layout.prop(self, "function_type",text='Func')
		layout.operator("button.explode", text="Explode!")	   



Is that the use of double quotes on the bl_label? Try single quotes to match the other bl_ definitions. Also you need an import bpy.

It’s still not working :confused: Is there a reason to use one type of quote over the other?

Here is the full code which includes the imports (Ignore the other stuff):

import bpy
from bpy.types import Operator  
from bpy.props import EnumProperty


global exploded
exploded = False
objData = [] #creates new array to store initial positions
vertexGroups = [{"group0":[0,0,0]},{"group1":[0,1,1]},{"group2":[0,2,3]}]


mainArray = []


class ExplodedBake(bpy.types.Panel):
    """Explodes a selection, bakes a number of maps, and un-explodes it"""
    bl_label = 'Exploded Bake'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'


    def list_update(self, context):
        self.update()


    # Your population code instead.
    def list_populate(self, context):
        result = []
        rowNo = 5
        for x in range(0,rowNo):
            a = bpy.data.objects.get(self.name)  #Assumes this code is running in the object context.
            b = a.vertex_groups.get("vgroup_name")
            result.append({"object": a, "vertex_group": b})
        return result


    function_type = bpy.props.EnumProperty(
        items=list_populate, 
        description="A list of supported math functions.", 
        update=list_update)




    def draw(self, context):
        layout = self.layout
        scene = context.scene
        obj = context.object
        rowNo = 5;
        split = layout.split()
        col = split.column()
        col.label(text="Object:")
        col2 = split.column()
        col2.label(text="Vertex Group:")
        # for x in range(0,rowNo):
        #     a = col.prop_search(obj, "name", bpy.data, "objects")
        #     b = col2.prop_search(obj, "vgroup_name", obj, "vertex_groups")
        #     mainArray.append({"object": a, "vertex_group": b})
        #     print(mainArray)


        layout.prop(self, "function_type",text='Func')
        layout.operator("button.explode", text='Explode!')       




class buttonExplode(bpy.types.Operator):
    bl_idname = 'button.explode'
    bl_label = 'Explode!'
    #bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        global objData
        objData = [] #clears array because we only want the current selection
        # If there ARE objects selected then act on all objects
        objects = bpy.context.selected_objects
        if objects != [] and exploded == False:
            exploded = True
            self.report({'INFO'}, 'Exploded!')  
            distanceMove = 0


            index = -1
            for index,obj in enumerate(objects):
                if obj.type == 'MESH': 
                    objData.append(obj.location) 
            print([objData])
            
            #explodes objects
            for obj in objects:
                if obj.type == 'MESH': 
                    distanceMove += 5
                    print(obj.name, obj)
                    print(distanceMove)
                    obj.location.x += distanceMove
                    
            
        elif objects == []:
            print("select sometjhing")
            self.report({'INFO'}, "Select one or more objects")
            
        
        return {'FINISHED'}




# (un-)register entire module, so you don't need to add every class here...
def register():
    bpy.types.Object.name = bpy.props.StringProperty(name="")
    bpy.types.Object.vgroup_name = bpy.props.StringProperty(name="")
    bpy.utils.register_module(__name__)




def unregister():
    bpy.utils.unregister_module(__name__)




if __name__ == "__main__":
    register()

your list_populate() function returns a dict, but it has to return a sequence of sequences (tuple of tuples, list of lists…). Something like this:

((“ONE”, “One”, “First entry”), (“TWO”, “Two”, “Second entry”), …)

ONE and TWO are the keys, which will be stored in the property (the one which is selected as string), One and Two are the display names, and the other two are descriptions for the items.

Hmm, I must be doing something wrong because it’s still not working. I added a bl_info because I was getting a warning and thought that might be something to do with it but that didn’t help.
The data in the list will probably be different since at the moment its not doing entirely what I would want. At this point I’m just trying to get it to display something . I also tried just making a list of lists with just strings like you said CoDEmanX but that didn’t help either. The same error about the property not being found appears.
I’m a bit stumped.

import bpy#from bpy.types import Operator  
#from bpy.props import EnumProperty


global exploded
exploded = False
objData = [] #creates new array to store initial positions
#vertexGroups = [{"group0":[0,0,0]},{"group1":[0,1,1]},{"group2":[0,2,3]}]


mainArray = []


bl_info = {
	"name": "My Script",
	"description": "Single line explaining what this script exactly does.",
	"author": "Paul Le Henaff",
	"version": (1, 0),
	"blender": (2, 72, 0),
	"location": "Tool panel > Misc > Exploded Bake",
	"warning": "", # used for warning icon and text in addons panel
	"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
				"Scripts/My_Script",
	"category": "On mesh select"}


class ExplodedBake(bpy.types.Panel):
	"""Explodes a selection, bakes a number of maps, and un-explodes it"""
	bl_label = 'Exploded Bake'
	bl_space_type = 'VIEW_3D'
	bl_region_type = 'TOOLS'


	def list_update(self, context):
		self.update()


	# Your population code instead.
	
	def list_populate(self, context):
		result = []
		rowNo = 5
		for x in range(0,rowNo):
			a = bpy.data.objects.get(self.name)  #Assumes this code is running in the object context.
			b = a.vertex_groups.get("vgroup_name")
			#result.append({"object": a, "vertex_group": b})
			result.append((a, self.name, "Object"), (b, vgroup_name, "Vertex group"))
			print(result)


		return result


	function_type = bpy.props.EnumProperty(
		items=list_populate, 
		description="A list of supported math functions.", 
		update=list_update)




	def draw(self, context):
		layout = self.layout
		scene = context.scene
		obj = context.object
		rowNo = 5;
		split = layout.split()
		col = split.column()
		col.label(text="Object:")
		col2 = split.column()
		col2.label(text="Vertex Group:")
		# for x in range(0,rowNo):
		# 	a = col.prop_search(obj, "name", bpy.data, "objects")
		# 	b = col2.prop_search(obj, "vgroup_name", obj, "vertex_groups")
		# 	mainArray.append({"object": a, "vertex_group": b})
		# 	print(mainArray)


		layout.prop(self, "function_type",text='Func')
		layout.operator("button.explode", text='Explode!')	   




class buttonExplode(bpy.types.Operator):
	bl_idname = 'button.explode'
	bl_label = 'Explode!'
	#bl_options = {'REGISTER', 'UNDO'}
	
	def execute(self, context):
		global objData
		objData = [] #clears array because we only want the current selection
		# If there ARE objects selected then act on all objects
		objects = bpy.context.selected_objects
		if objects != [] and exploded == False:
			exploded = True
			self.report({'INFO'}, 'Exploded!')  
			distanceMove = 0


			index = -1
			for index,obj in enumerate(objects):
				if obj.type == 'MESH': 
					objData.append(obj.location) 
			print([objData])
			
			#explodes objects
			for obj in objects:
				if obj.type == 'MESH': 
					distanceMove += 5
					print(obj.name, obj)
					print(distanceMove)
					obj.location.x += distanceMove
					
			
		elif objects == []:
			print("select sometjhing")
			self.report({'INFO'}, "Select one or more objects")
			
		
		return {'FINISHED'}




# (un-)register entire module, so you don't need to add every class here...
def register():
	bpy.types.Object.name = bpy.props.StringProperty(name="")
	bpy.types.Object.vgroup_name = bpy.props.StringProperty(name="")
	bpy.utils.register_module(__name__)




def unregister():
	bpy.utils.unregister_module(__name__)




if __name__ == "__main__":
	register()



I think it is because you’re class is defined as a Panel type instead of an Operator type. Dynamic lists may not be supported under the Tool based context at this time. Therefore the Enum property is never really created and the draw event prop fails to detect it.

You can certainly use that population technique in the object based context, however.

Sorry for the late reply. Thanks for the lead. I’ll keep trying.