EnumProperty / DropDownList, not changing visually selected Item?


(Matt) #1

Hello!

I’ve been slowly learning Python, and now trying to create a custom Operator/Class.

I have a EnumProperty/Item List that appears when I run the Operator on an Object. It shows the DropDown/List at the botttom left / operator section, as expected.

But the issue is, when I choose an item, I can execute the Function just fine/On Update, but the visual item does not change on the UI. Meaning, when I click the List, select another entry, it runs the proper code, but the visual item selected never changes. It’s always stuck at the initial/default one??

I’ve seen some examples of doing this multiple ways. But I’m not sure what is the best way.
But, my script will only work/should work on Meshes, so I don’t think I have to add my Enum to the Scene, but possibly? In the Register FN?

Any ideas?

Thank you!

DropDownList_Issue.m4v (102.7 KB)

Edit: So it seems ilke my Execute FN under the Draw, is overriding it/not letting it get set properly. Or at least, I can set my operator in there, and it will then change.


(Matt) #2

Another issue I am noticing now as well, which is maybe similar to above. When I tried to add a Modifier in the Execute FN via the DropDownList, or create a new Object, it doesn’t work. It seems like any Object Update/Creation is blocked till the main execute is finished…

Edit: Yeah, that’s exactly it. I can create a new Plane Object in the main def Execute, which happens AFTER the Update FN has run.

Edit #2:

So I did get it to work now, but seems hacky. I basically set the state in a variable from my Main Class, inside/during the DropDown FN, and do some other processing. Then when it returns back to my main FN / Execute, I SET the DropDown State, based on that variable. So then the UI is correct and everything gets processed…

Basically:

image

image


(Matt) #3

Ran into another issue, when trying to just Add a simple button to my operator panel, calling a Def/Function.

Took like an hour or so to get it to work. I’m still not entirely sure why it’s so finicky. I definitely have alot to learn about Python/Blender yet. I kept getting errors about invalid bl_idname, with my new class.

I saw some example about calling the ID “button.name”. So I tried that, and finally got it working. Or so it seems. At least the button shows up/executes it’s own Def/FN on Click.


(AFWS) #4

Posting pictures is not gonna do a lot of good. It’s better to post your code, so people can run it and try to see your issues.


(Matt) #5

Yeah, I could post some code.

I’m trying to figure out how these bpy.types.Operators work… It seems like, from what I said initially, it’s blocking certain things from happening, while that’s active, which makes sense I guess…

But, what do I do/how do I make like this window below (Translate), that comes up, after you move, and lets you change settings and whatnot? That must not be an operator in the same way I’m using then?

image

image

Maybe i need to change the way I’m doing some things… Basically I want to setup some initial Boolean Modifier stuff, then after let the user change settings/see it update, and be able to commit/cancel .

But, with my current method, it seems like I’m always getting blocked/operations are not wanting to stick, unless I force it during the Execute FN inside the operator.

I was trying to add a UIList now, and it shows up/I can populate it initially. But when I try to Click/Select anything, it runs my main Execute FN in the operator/and blocks the UI from updating/items being selected. I cannot also add/remove items either. Unless I do so, from a copy of the UI , in the Text Editor area.

Here’s a Video showing what happens… Basically the Operator version CAN update, but just not directly… unless , again, I force somehow in the Execute area. But I’m not sure how to handle clicking the List Items/Updating that way then too, if I have too…

bpy.types.Operator_UI_Blocking.m4v (1.3 MB)


(AFWS) #6

See if this helps any. You need to create the properties inside your operator.

import bpy


class SimpleOperator(bpy.types.Operator):
    """Resize Object"""
    bl_idname = "object.simple_operator"
    bl_label = "Resize Object Operator"
    bl_options = {'REGISTER', 'UNDO'}

#Properties are created inside operator
    RESIZE_X = bpy.props.FloatProperty(name="Resize X", description="Resize the object along the X axis", default=1.0) 
    RESIZE_Y = bpy.props.FloatProperty(name="Resize Y", description="Resize the object along the Y axis", default=1.0)
    RESIZE_Z = bpy.props.FloatProperty(name="Resize Z", description="Resize the object along the Z axis", default=1.0)

        
    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def draw(self, context):
        layout = self.layout
        
        col = layout.column(align=True)
        col.label("Resize:")
        col.separator()
                        
        col.prop(self, "RESIZE_X", text='X')
        col.prop(self, "RESIZE_Y", text='Y')
        col.prop(self, "RESIZE_Z", text='Z')
        
                
    def execute(self, context):
        
        bpy.ops.transform.resize(value=(self.RESIZE_X, self.RESIZE_Y, self.RESIZE_Z))
        
        return {'FINISHED'}


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

    def draw(self, context):
        layout = self.layout
        
        row = layout.row()
        row.operator("object.simple_operator")


def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.utils.register_class(SimpleOperator)

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    bpy.utils.unregister_class(SimpleOperator)
    

if __name__ == "__main__":
    register()

and of course you can use the object scale.

    def execute(self, context):
        
        ACT_OBJ = bpy.context.active_object
        
        ACT_OBJ.scale = (self.RESIZE_X, self.RESIZE_Y, self.RESIZE_Z)
        
        return {'FINISHED'}

(Matt) #7

Aaaaaaah! Yeah! :smiley: Thank you!
One more question, but I might be able to figure it out.

Can I define a empty Variable/Array in the main operator class, then set it on Execute, then access that from another Def in the script?

Would I set those to same as what you had, with the RESIZE_X / etc? As Props?

Like:

class SimpleOperator(bpy.types.Operator):
	"""Resize Object"""
	bl_idname = "object.simple_operator_example"
	bl_label = "Simple Operator Example"
	bl_options = {'REGISTER', 'UNDO'}

	#Properties are created inside operator
	RESIZE_X = bpy.props.FloatProperty(name="Resize X", description="Resize the object along the X axis", default=1.0) 
	RESIZE_Y = bpy.props.FloatProperty(name="Resize Y", description="Resize the object along the Y axis", default=1.0)
	RESIZE_Z = bpy.props.FloatProperty(name="Resize Z", description="Resize the object along the Z axis", default=1.0)

   #THIS
	OVERLAP_OBJECTS= []

Then in here do like:

def execute(self, context):
		
		ACT_OBJ = bpy.context.active_object
		ACT_OBJ.scale = (self.RESIZE_X, self.RESIZE_Y, self.RESIZE_Z)
		
		OVERLAP_OBJECTS = [some scene objects]

Maybe as Enum?

OVERLAP_OBJECTS =  bpy.props.EnumProperty(stuff in here?)

Basically I’m trying to Perform some initial stuff, as metioned before I think, and then add those objects to an array/to the UIList. Then after that, let the user add/remove items from the list, ideally/change other options in there.

I was trying to use this one example/integrate that. Or something similar.

But instead of Materials, it would be using Objects.

# -------------------------------------------------------------------
#   Collection
# -------------------------------------------------------------------
class CUSTOM_objectCollection(PropertyGroup):
	#name = StringProperty() -> Instantiated by default
	material = PointerProperty(
		name="Object",
		type=bpy.types.Object)

image


(Matt) #8

I got all my stuff figured out now. Also decided to move to a popup dialog instead of operator as it was before. But I also learned now why I was having issues with values not updating / getting stuck. :grinning:

Although I do have 1 more question. Does anyone have an example of how to use prop_search, but supply an initial object / string, then filter all scene objects by Mesh type, and let me use the eye dropper to pick stuff as well?

I think I need to use pointer properly and collection maybe. But can’t seem to get it to work…


(AFWS) #9

I just learned something myself. It looks like the “poll” for the pointer property is something new for 2.79.

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

		row = layout.row()
		row.prop_search(context.scene, "my_object_search", context.scene, "objects", text='Object Search')


def filter_mesh_objects(self, object):
	return object.type == 'MESH'


def register():
	bpy.utils.register_class(HelloWorldPanel)
	
	bpy.types.Scene.my_object_search = bpy.props.PointerProperty(
		type=bpy.types.Object,
		poll=filter_mesh_objects
	)


def unregister():
	bpy.utils.unregister_class(HelloWorldPanel)

	del bpy.types.Scene.my_object_search


if __name__ == "__main__":
	register()

(LuxCore Addon Developer) #10

Thanks for the tip, I instantly put it to use.
Side note: The poll function does not prevent the user to set a “forbidden” object via the eye dropper tool.


(Matt) #11

Yeah, you can pick a Lamp, even though it’s not showing up otherwise.

This is very close to what I want though! I’m not sure if it’s possible to pick an object initially? Well, set it automatically then the user can change after that?

Edit: Also, I think to get the picked Object/Data, you have to add update, in the PointerProperty Registration? But, when I tried that, and I say print(self.object), in the function, it always lists the same object…


(AFWS) #12

@B.Y.O.B and @Kickflipkid687, seems like someone posted that issue here ,but it doesn’t say how to solve it. From reading the last comment from mont29, it looks like you use the “update” somehow.

https://developer.blender.org/T52426

I posted this on Blender Developer Talk. This has started to drive me nuts, so maybe we can get a answer, LOL.


(Matt) #13

Ah, thanks! Yeah, we will see.

I thought i was using a regular Pointer Property, but I guess in my case, I am using a CollectionProperty now, since I have to dynamically add items to a UIList/each have unique entries/data.

Then I store custom settings in each Property/Group Item. Maybe that won’t work with the EyeDropper/setup.

bpy.types.Scene.autoboolListObj = bpy.props.CollectionProperty(type=autoBoolObjectProps)

prop_search(obj, "boolModObject",bpy.context.scene,"objects","","",False,'OBJECT_DATA')

class autoBoolObjectProps(bpy.types.PropertyGroup):
	name =  bpy.props.StringProperty(name = "",description = "Click to Type-In/Rename Boolean Modifier.",default = "",update=autoBool_UIList_Renamed)#bpy.props.StringProperty()
	boolModObject = bpy.props.StringProperty(update=autoBool_UIList_ModObject_Changed)
	index = bpy.props.IntProperty()   
	modToggle = bpy.props.BoolProperty(name = "",description = "Toggle this Boolean Modifier ON/OFF.",default = True,update=autoBool_UIList_ToggleModifier)
	modOperator = bpy.props.EnumProperty(name="", description = "Operator used to prodcuce the Boolean. (Difference,Intersect,Union,Cutout,Dupe/Extract)",default ='DIFFERENCE',items=autoBool_Operator_List,update=autoBool_UIList_Operator)

(Matt) #14

I figured out something else , that will work for what I’m doing I guess. It’s sorta doing same thing, twice in a way, but again like I said, I might not be able to use the other method, given my situation.

AutoBool_Overlap_10.m4v (2.7 MB)

But, I am adding a row.operator to my UIList/Item. I am feeding it the current Index of the UI Item, as it’s drawn. So then I can use that Index in the Operator, to figure out where to get/set my data in the other prop_search.

def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
right.operator('operator.autobool_searchmenu',icon='VIEWZOOM',text="").index = index

which the operator then is invoking a :

context.window_manager.invoke_search_popup(self)

My Operator for the Popup Search is like:

class AutoBool_ObjectSearch(bpy.types.Operator):
    bl_idname = "operator.autobool_searchmenu"
    bl_label = "Search Objects"
    bl_description = "Search/Select Scene Mesh to use for Boolean Operation."
    bl_options = {'REGISTER', 'INTERNAL'}
    bl_property = "enum"
	
    index = bpy.props.IntProperty(name="",default=0)

     def scene_items(self, context):
        return [(ob.name, ob.name, '') for ob in bpy.data.objects if ob.type == 'MESH']
		
    enum = bpy.props.EnumProperty(items=scene_items)

    def execute(self, context):
        #print(self.enum)  #GETS THE CHOSEN OBJECT ON SEARCH
		
        context.scene.autobool_custom_index = self.index
        idx = context.scene.autobool_custom_index
        item = context.scene.autoboolListObj[idx]
		
        if item.boolModObject != None:
            item.boolModObject = self.enum  #SET BOOL OBJ/MESH TO SELECTED ITEM IN SEARCH RESULT/ENUM
        return {"FINISHED"}

    def invoke(self, context, event):
        context.window_manager.invoke_search_popup(self)
        return {"FINISHED"}

(AFWS) #15

Looks like you went a different way ,but it ended up being a bug that is now fixed.


(Matt) #16

Oh ya? Fixed in daily build, or when would it show up?

I’d still maybe use it, with my other search method as well. :slight_smile:


(AFWS) #17

Yeah, daily build or build yourself. I would wait a day or two ,since it was just fixed today.

Edit:
Just tested on 2.8 and it works.


(Matt) #18

Cool, yeah, they fixed it!

I tried to use it though however, and the eye dropper wasn’t working. Maybe can’t do how I’d like.

Well and still has the issue of, not showing initial objects/can’t set initial entry, then let user change/pick after.
But, if I can’t use it in my case, it’s not the end of the world. More so since I got that other searching method working too. More of a nice to have now.

class autoBoolObjectProps(bpy.types.PropertyGroup):
	name =  bpy.props.StringProperty(name = "",description = "Click to Type-In/Rename Boolean Modifier.",default = "",update=autoBool_UIList_Renamed)#bpy.props.StringProperty()
	boolModObject = bpy.props.StringProperty(update=autoBool_UIList_ModObject_Changed)
	
	my_object_search = bpy.props.PointerProperty(
		type=bpy.types.Object,
		poll=filter_mesh_objects
	)
	
	index = bpy.props.IntProperty()   
	modToggle = bpy.props.BoolProperty(name = "",description = "Toggle this Boolean Modifier ON/OFF.",default = True,update=autoBool_UIList_ToggleModifier)
	modOperator = bpy.props.EnumProperty(name="", description = "Operator used to prodcuce the Boolean. (Difference,Intersect,Union,Cutout,Dupe/Extract)",default ='DIFFERENCE',items=autoBool_Operator_List,update=autoBool_UIList_Operator)
right.prop_search(obj, "my_object_search",bpy.context.scene,"objects","","",False,'OBJECT_DATA')