getting selection from a menu

So im writing my own custom browser inside Blender just to streamline our company asset pipeline a bit.
Im wanting to be able to query a selection made in a custom menu.

Anyone have any ideas?

Currently the UI looks like this:


The way it works is you select which asset “category” you want (Prop, Environment, Character etc) this populates the asset menu below based on its folder contents (Table01, Chair01, Chair02 etc)
And then this populates version menu below that, (Table01_Model, Table01_Rig, Table01_Final etc)

The populating works fine because i can call on an EnumProperty using the get() function it has. But im not sure about Menu’s and cant seem to find much on it.

If i ran the whole thing on EnumProperty (as in the first property ‘Category’) that would work but having heaps of issues repopulating the items of an EnumProperty and heaps of people say it cant be done in Blender currently.

Any ideas?

p.s. please dont hate on me for using Open, Import and Reference instead of Open, Append and Link. Its for my Maya buddies who im trying to convert into the light :stuck_out_tongue:

ok maybe i should be using an enum property…just as i post i then find a link that might help with dynamic enumPropertys :stuck_out_tongue:
I’m sure there was another reason why i didnt want to go down this way…hmmm any ideas are more than welcome though!

The value of the Enum Property is the currently selected item.

Here is how I setup mine.


    
    def list_update(self, context):
         # Your update code here.
         pass
    
    def list_populate(self, context):
        # Your populate code here.
        result = [('ADD','Add','Adition'),('SUB','Sub','Subtract'),('MULT','Mult','Multiply'),('DIV','Div','Divide')]
        return result
        
    function_type = bpy.props.EnumProperty(items=list_populate, description="A list of supported math functions.", update=list_update)
 

So when you reference the enum, it’s value is the result. If I were to print function_type it’s value might be ‘ADD’.

Run this script in text editor and see the bottom of the object tab, and also the system console to see the property value printed there:

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

        layout.prop(context.scene, "my_prop")

def update_cb(self, context):
    # self is bpy.context.scene
    print("Selected:", self.my_prop)

def items_cb(self, context):
    l = []
    for ob in context.scene.objects:
        # Could use arbitrary conditions here
        #if ob.name.startswith("C"):
        
        l.append((ob.name, ob.name, ob.type))
    return l

def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.my_prop = bpy.props.EnumProperty(items=items_cb, update=update_cb)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.my_prop


if __name__ == "__main__":
    register()


It’s a dynamically populated EnumProperty with an update-callback. It appears as dropdown menu by default, but could also be shown expanded:

 layout.prop(context.scene, "my_prop", expand=True)

The value of the enumprop is whatever you use here:

  l.append((ob.name, ob.name, ob.type))

(the first ob.name!)

Cheers! thanks peeps :slight_smile:

Will have a go with these samples. Thanks Codemanx i think it was one of your enumproperty scripts i found before.

Does anyone know how to call a selection in a Menu? Or is a menu merely a list of buttons so that it executes rather than maintains a selection as with the enumprop?

If a menu isn’t an EnumProp added via .prop to a panel, then each entry is an operator

layout.operator(“object.select_all”)

So on click, that operator is executed.

You can also add the same operator multiple times to a menu (populate it in the draw() method), use different labels and settings and later identify what was clicked using one of these settings (e.g. an index)

import bpy

class SimpleOperator(bpy.types.Operator):
    bl_label = "Simple Operator"
    bl_idname = "object.simple_operator"
    
    index = bpy.props.IntProperty()
    
    def execute(self, context):
        # Order shouldn't have changed between menu draw() and op execute(),
        # we should use the object name in this case I guess...
        ob = context.scene.objects[self.index]
        self.report({'INFO'}, "Clicked %i (%s)" % (self.index, ob.name))
        return {'FINISHED'}

class SimpleCustomMenu(bpy.types.Menu):
    bl_label = "Simple Custom Menu"
    bl_idname = "OBJECT_MT_simple_custom_menu"

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

        for i, ob in enumerate(context.scene.objects):
            props = layout.operator(SimpleOperator.bl_idname, text=ob.name)
            props.index = i


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


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

if __name__ == "__main__":
    register()

    # The menu can also be called from scripts
    bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)


i seem to be able to get the dynamic menus/enumProps working fine but it depends on what’s triggering them it seems. What im needing is for when i change one menu/enumProp the other one updates depending on what the first one changes to.

This is the code so far. Sorry it probably wont run because it relies on another class and its a bit bitsy at the moment. This runs no errors but the 2nd enumProp doesnt update…:frowning:


import bpy
import os
from bpy.props import *
import px_project as pxp

asset_categories=[('CHARACTERS','Characters','Characters folder'),
('PROPS','Props','Props folder'),
      ('ENVIRONMENTS','Environments','Environments folder'),
      ('MATTES','Mattes','Mattes folder'),
      ]

         
class PxAssetLoader(bpy.types.Operator):bl_idname = "wm.asset_loader"
      bl_label = "Asset Loader"

     category_selection = "Props"      
    
    def set_category_selection(self, value):
          
[INDENT=2]self.category_selection = value[/INDENT]
         
    def get_category_selection(self):
          
[INDENT=2]return (self.category_selection)[/INDENT]
     
   def execute(self, context):
          
[INDENT=2]print('LOADED FILE')
          return {'FINISHED'}[/INDENT]
     
    # Runs when asset Loader has been selected from the Project Menu
    def invoke(self, context, event):
          
[INDENT=2]return context.window_manager.invoke_props_dialog(self, width=300)[/INDENT]
     
    def draw(self, context):
          
[INDENT=2]layout = self.layout  
          box = layout.box()
          col = box.column()
          col.label("Asset")
          col.prop(context.scene, "Category")
          col.separator()
          col.prop(context.scene, "Asset")
          col.separator()
          #col.prop(self, "version_list_property")
          col.separator()
          col.separator()
[/INDENT]
 # -----------------------------------------------------------------------------------------


asset_loader = PxAssetLoader
project_path = "/media/Podserver/01_Internal/00_RnD/CG_TestProject/Production/" 

def create_asset_List(dir_path, target_folder):file_list = list()
      file_names = list()
      i=0
      for filename in os.listdir(dir_path + target_folder):
           
[INDENT=2]file_names.append(filename)[/INDENT]
       for each_file in file_names:
           
[INDENT=2]file_list.append(((each_file).upper(), each_file, str(i)))
           i=i+1
[/INDENT]
       return(file_list)

                 
def get_category(self):return self['Category']

     
def set_category(self, value):self['Category'] = value

     
def get_asset(self):return self['Asset']

     
def set_asset(self, value):self['Asset'] = value

     
def update_category_property(self, context):# get selection index, use index to select asset type and split to get selection
    category_index = get_category(self)
    category_type = asset_categories[category_index]
    selection = str(category_type).split("'")
    print(selection[3])
    asset_loader.set_category_selection(asset_loader,selection[3])

     
def update_asset_property(self, context):print(asset_loader.get_category_selection(asset_loader)) # test

     
    
bpy.types.Scene.Category = bpy.props.EnumProperty(items=asset_categories, get=get_category, set=set_category, update=update_category_property)

bpy.types.Scene.Asset = bpy.props.EnumProperty(items=create_asset_List(project_path, asset_loader.get_category_selection(asset_loader)), get=get_asset, set=set_asset, update=update_asset_property)

bpy.utils.register_class(PxAssetLoader)

what the…how the hek do you paste it in code mode? lol

Ok ive got my create_asset_list method set to (self, context) and thats printing out the right results…just not updating the enum prop for some reason…

def create_asset_List(self,context):
file_list = list()
file_names = list()
i=0
category = asset_loader.get_category_selection(asset_loader)
for filename in os.listdir(project_path + category):

[INDENT=2]file_names.append(filename)[/INDENT]
for each_file in file_names:

[INDENT=2]file_list.append(((each_file).upper(), each_file, str(i)))
i=i+1[/INDENT]
print(file_list)
return(file_list)

Great. Thanks CodeManx. Adding the IntProperty to the operator seems to do the job. Oh well i guess im going back to menus!

if anyone figures out how to visually update the enumProperty that would be great. It seems it was definitley running the create_asset_list method every time i ran the mouse over it actually. But just re drawing.

use code-tags:

[ CODE]just without the spaces in front of the C![/ CODE]

Just realized I did not post an example here, but on pasteall.org and had no local copy… But recovered the link from IRC log, so here it is (one enum updates the other):

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

        layout.prop(context.scene, "prop1")
        layout.prop(context.scene, "prop2")

def items_prop1(self, context):
    types = {ob.type: ob.type for ob in context.scene.objects}
    return [(t, t, "") for t in types]
    
def items_prop2(self, context):
    return [(ob.name, ob.name, "") for ob in context.scene.objects if ob.type == context.scene.prop1]
    
def update_prop1(self, context):
    print("Prop1 changed to", context.scene.prop1)
    
def update_prop2(self, context):
    print("Prop2 changed to", context.scene.prop2)
    

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.types.Scene.prop1 = bpy.props.EnumProperty(items=items_prop1, update=update_prop1)
    bpy.types.Scene.prop2 = bpy.props.EnumProperty(items=items_prop2, update=update_prop2)
    
def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    del bpy.types.Scene.prop1
    del bpy.types.Scene.prop2


if __name__ == "__main__":
    register()


Ok so again it still seems to be updating internally at least. But its just not redrawing. The wierd thing is when i select the asset it says ive selected a sub-file of the categeroy selected. even though on the UI its something else!?!?
This script should run once the project_path is set to any folder on your computer.


import bpy
import os
from bpy.props import *

asset_categories=[
    ('CHARACTERS','Characters','Characters folder'),
    ('PROPS','Props','Props folder'),
    ('ENVIRONMENTS','Environments','Environments folder'),
    ('MATTES','Mattes','Mattes folder'),
    ]
    
project_path = "/media/Podserver/01_Internal/00_RnD/CG_TestProject/Production/" 
        
class PxAssetLoader(bpy.types.Operator):
    bl_idname = "wm.asset_loader"
    bl_label = "Asset Loader"
    
    openType_list_property = EnumProperty(
            name="Select",
            items=(('OPEN','Open','Open the asset'),
                    ('IMPORT','Import','Import the asset'),
                    ('REFERENCE','Reference','Reference the asset')),
            options={'ENUM_FLAG'},
            default={'OPEN'},
            )        
    
    def execute(self, context):
        print('LOADED FILE')
        return {'FINISHED'}
    
    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self, width=300)
    
    def draw(self, context):
        layout = self.layout  
        box = layout.box()
        col = box.column()
        col.label("Asset")
        col.prop(context.scene, "Category")
        col.prop(context.scene, "Asset")
        row = col.row()
        row.prop(self, "openType_list_property")
        
# -----------------------------------------------------------------------------------------

def asset_populate(self, context):
    return [(filename, filename, "") for filename in os.listdir(project_path + bpy.context.scene.Category)]
    
def update_category_property(self, context):
    print("updating Category..." + bpy.context.scene.Category)
    
def update_asset_property(self, context):
    print("updating Asset..." + bpy.context.scene.Asset)
    
def register():
    bpy.utils.register_class(PxAssetLoader)
    bpy.types.Scene.Category = bpy.props.EnumProperty(items=asset_categories, update=update_category_property)
    bpy.types.Scene.Asset = bpy.props.EnumProperty(items=asset_populate, update=update_asset_property)
    
def unregister():
    bpy.utils.unregister_class(PxAssetLoader)
    del bpy.types.Scene.Category
    del bpy.types.Scene.Asset

if __name__ == "__main__":
    register()
    
asset_loader = PxAssetLoader
bpy.ops.wm.asset_loader('INVOKE_DEFAULT')

finally! in the end i got it working with Menus. I can post the code up here for anyone if they want to have a look.

Something else i found interesting. When an enumProperty has options set to {‘ENUM_FLAG’} when calling the items i get: 1,2,4? But when used as a dropdown menu i get 0,1,2 as expected! Anyone know why?

Im having to hack it a bit by adding some conditional to look out for the 4 as i want to keep the ENUM_FLAG option for aesthetic reasons.

Well thank you very much CoDEmanX for the insight! :smiley: Company pipeline will run much smoother now :). Im sure ill be back on new post for more questions in future.

Asthetical reasons?
Hm… it’s actually a functional change. ENUM_FLAG will expand the enum and allow you to select multiple items (use shift+click).

Is that really what you want?
If you want the expanded style, use:

col.prop(context.scene, "Category", expand=True)

BTW: You can put the default name / label in the property registration, and use lower-case names for properties:

bpy.types.Scene.category = bpy.props.EnumProperty(
    name="Category",
    description="Some explanation for the tooltip here",
    items=asset_categories,
    update=update_category_property)

# ...

# Will show a default label text "Category" with upper case C!
col.prop(context.scene, "category", expand=True)

# You can overwrite it like:
col.prop(context.scene, "category", expand=True, text="New Label")

You cannot however leave out the property name.


col.prop(context.scene, "Category", expand=True, <b>text="Category"</b>)

@CoDEmanX, Given your penchant for correcting every small mistake I may make on this forum, I couldn’t let this one go.

@mafster.
The 1, 2, 4 etc is because you can have multiple selections and deeper in the code somewhere they are using a bitwise OR, or something similar is my guess.

lol @BatFINGER’s CoDEmanX remark :stuck_out_tongue:

Interesting thought regarding bitwise…

re Aesthetics, i (personally) think that Open, Import, Reference looks better expanded as three buttons. However i would not want people shift+clicking. So good point. And expanding it (minus the enumFlag option) has solved the strange indexing issue :stuck_out_tongue: Looks exactly the same. I actually stole the enumFlag process off cell-fracture panel however i noticed his enums acted a bit strange. Sweet, well asset loader all go now! cheers

Should i put a [solved] thingamee somewhere? If so how…

Doh! So true… corrected my post.
@mafster: you can use lowercase property names and still show a capitalized label using the either the name-property on prop registration, or by overwriting it in layout.prop(…, text=“NEW LABEL”)

Yeha, baby, give it to me! =D

The 1, 2, 4 etc is because you can have multiple selections and deeper in the code somewhere they are using a bitwise OR, or something similar is my guess.

How strange, when I tested it in 2.69 official, it returned a proper set of identifiers, no bitfield values. But in my own build from recent trunk, it just crashes as soon as I click an element??

Reported: https://developer.blender.org/T38489