Menu class - multiple instances in panel (or way to pass a parameter) ??

Last thread for the day… promise.

I have a recurring menu in one of my scripts:
http://www.fourmadmen.com/blender/b25/lm_b25_proto_7.jpg

It is drawn 20 times in the panel. The menu itelf is a class class LayerOptionsMenu(bpy.types.Menu). The menu is composed of a seris of operators and a copule of sub menu classes. Once you get to a menu item that “does” something it is an operator.

Now I CAN pass a value to a property within the operator. For example: layout.operator(HelloWorld.bl_idname, text = “Bounding Box”, icon=“BBOX”).layer = 1
But to get it to the operators it first must be passed to the instance of the menu class. And the “.layer=1” property assignment does not work for menu classes.

So…

The menu is called in draw with menu(LayerOptionsMenu.bl_idname, text=str(i+1)), how can I inform the instance of the class that it is working with Layer 1, or 2, or whatever? The LayerOptionsMenu.bl_idname is a string that is used to lookup the operator. Each menu for each layer is the same instance.

So, short of creating 20 distinct classes for these is there ANY way anyone can think of to tell the menu “You are for Layer 5” (or whatever number)?

???

One solution can be a global variable instanced in draw() panel

The instance is not instantiated directly within draw(). The menu() function (within draw()) is passed a String and the Blender internal code finds the registered class that has a bl_idname value that matches the passed in String.

So it’s Blender doing the search and instantiation based on the passed String.

Unless, that is, I am misunderstanding what you are suggesting.

yoo havve let say 20 menus so in your class you shold be able to determine which one it is !
and i guess you have one menu / layer
identify the menu and your done

unless you mean something else here!

you could also organise 2 menus only but that would change totaly your GUI
i mean one menu for layer and another one for the var / layer!

salutations

I agree, I should be able to. But I’m not, which is where the question comes in. I see no way to pass to the menu function any variable that could be used to identify which layer it is supposed to operate with.

I can set a property with the layer number in the menu operators once the menu draw function has been fired but I have to get the layer number to the menu function first.

if you mean during the init phase of the class
i’ m looking for a solution for this too!

did you tried with scene defined properties
should be available at least in the Draw or exec part of your class
after your selected a menu ?

salutations

menu() is not passed a property, just a String that is used to look-up and instantiate an instance of a class.

With operator I can do this .operator(…).somePropertyDefinedInOperator = 42

…but with menu you can not because .menu(…) returns NoneType.

you say that you are using the same instance of the class!
sorry dont follow you on this feature

i mean if you make and instance then it should be an independant object created from the class!

is there a copy available of you script so we can look at it ?

i work on 2 structure of menu sub menu right now

the one like in solid shape script and the other one from the pie menu
which are not done the same way at all!

but may be your using anohter way to make menu/sub menu here !

salutations

I am saying I am given the same instance of a class. I do not actualy create the instance in my code… Blender does that.

Which would be fine if I could define AND set a property within that class like I can with operator(). I have an operator instance that is used by more than one menu item. But with the operator I can define a Property within the class and set it in the call to operator(). With menu() I can define the property but can not set it at runtime.

without a piece of code it’s hard to understand.
Anywhere this is for global


import bpy
from bpy.props import*
        
genprop = 0 
        
class SimpleOperatorOne(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatorone"
    bl_label = "Simple Object Operator 1"
    bl_options = {'REGISTER', 'UNDO'}
    
    global genprop 
    prop_1 = IntProperty(name='prop1')
    def execute(self, context):
        self.properties.prop_1 = genprop
        print('SimpleOperatorOne...',self.properties.prop_1 )
        return {'FINISHED'}

class SimpleOperatorTwo(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatortwo"
    bl_label = "Simple Object Operator 2"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        print('nothing')
        return {'FINISHED'}


class VIEW3D_MT_master(bpy.types.Menu):
    bl_idname='master'
    bl_label = "Master Material Menu"
    global genprop 
    
    def draw(self, context):
        layout = self.layout
        
        row=layout.operator("simple_operatorone",text='one', icon='ZOOMIN')
        
        row=layout.operator("simple_operatortwo",text='two',  icon='HAND')
        
        print('VIEW3D_MT_master...',genprop)

        
class menuPanel(bpy.types.Panel):
    bl_idname = "menuPanel"
    bl_label = "Simple Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context):
        global genprop
        layout = self.layout
        
        genprop = 10
        layout.menu(VIEW3D_MT_master.bl_idname, text='test')
        
        
def register():
    bpy.types.register(SimpleOperatorOne)
    bpy.types.register(SimpleOperatorTwo)
    
    bpy.types.register(VIEW3D_MT_master)
    
    bpy.types.register(menuPanel)

def unregister():
    bpy.types.unregister(SimpleOperatorOne)
    bpy.types.unregister(SimpleOperatorTwo)
    
    bpy.types.unregister(VIEW3D_MT_master)
    
    bpy.types.unregister(menuPanel)

if __name__ == "__main__":
    register()


Yes, thank you for taking the time to type that up.

I have modified if for my issue/question. The more I noodle on this the more I see it as lest of an instance issue and more of an issue of what do calls to menu() return – None – versue what calls to operator() return – instance of opertor.

Although within the above rules I am open to a solution. Thanks!

Here your code modified and commented by me


import bpy
from bpy.props import*
        
genprop = 0 
        
class SimpleOperatorOne(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatorone"
    bl_label = "Simple Object Operator 1"
    bl_options = {'REGISTER', 'UNDO'}
    
    layer = IntProperty(name='layer') ##FourMadMen## Can access this because call to operator() returns instance of operator
    
    def execute(self, context):
        print('SimpleOperatorOne... operating on layer ',self.properties.layer ) ##FourMadMen## Value of this was sent during call to operator()
        return {'FINISHED'}

class SimpleOperatorTwo(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatortwo"
    bl_label = "Simple Object Operator 2"
    bl_options = {'REGISTER', 'UNDO'}

    layer = IntProperty(name='layer') ##FourMadMen## Can access this because call to operator() returns instance of operator

    def execute(self, context):
        print('SimpleOperatorTwo... operating on layer ',self.properties.layer ) ##FourMadMen## Value of this was sent during call to operator()
        return {'FINISHED'}


class VIEW3D_MT_master(bpy.types.Menu):
    bl_idname='master'
    bl_label = "Master Material Menu"
    global genprop 
    
    layer = IntProperty(name='layer') ##FourMadMen## CAN'T access this because call to menu() returns None
    
    def draw(self, context):
        global genprop

        layout = self.layout
        
        ##FourMadMen## Would like to do the following but no way to set this from the menuPall menu() call (see below)
        #
        #layout.operator("simple_operatorone",text='operation 1', icon='ZOOMIN').layer = layer
        #layout.operator("simple_operatortwo",text='operation 2',  icon='HAND').layer = layer
        #
        ####
        
        layer = genprop ##FourMadMen## When this is drawn value of gen prop for all menus in panel will be 2
        layout.operator("simple_operatorone",text='operation 1', icon='ZOOMIN').layer = layer
        layout.operator("simple_operatortwo",text='operation 2',  icon='HAND').layer = layer
        
        #print('VIEW3D_MT_master...',genprop)

        
class menuPanel(bpy.types.Panel):
    bl_idname = "menuPanel"
    bl_label = "Simple Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context):
        global genprop
        layout = self.layout
        
        for i in range(1,3):
            genprop = i
            layout.menu(VIEW3D_MT_master.bl_idname, text='menu for layer '+str(i))
        
        ###### FourMadMen asks ## Any way to pass value of 'i' to the menu() call?
        ###### You can with operator() (see .operator() calls above) but not menu.
        
def register():
    bpy.types.register(SimpleOperatorOne)
    bpy.types.register(SimpleOperatorTwo)
    
    bpy.types.register(VIEW3D_MT_master)
    
    bpy.types.register(menuPanel)

def unregister():
    bpy.types.unregister(SimpleOperatorOne)
    bpy.types.unregister(SimpleOperatorTwo)
    
    bpy.types.unregister(VIEW3D_MT_master)
    
    bpy.types.unregister(menuPanel)

if __name__ == "__main__":
    register()

one thing here did you try to change the name layer to another name
cuase i think layer is a predefined word in API

so cannot be use as such may be!
API get confuse may be with the real layer number or other things!

salutations

@FourMadMen
Better with a piece of code :slight_smile:
changed genprop to an array


import bpy
from bpy.props import*
        
genprop = []
        
class SimpleOperatorOne(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatorone"
    bl_label = "Simple Object Operator 1"
    bl_options = {'REGISTER', 'UNDO'}
    
    layer = IntProperty(name='layer') ##FourMadMen## Can access this because call to operator() returns instance of operator
    
    def execute(self, context):
        print('SimpleOperatorOne... operating on layer ',self.properties.layer ) ##FourMadMen## Value of this was sent during call to operator()
        return {'FINISHED'}

class SimpleOperatorTwo(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatortwo"
    bl_label = "Simple Object Operator 2"
    bl_options = {'REGISTER', 'UNDO'}

    layer = IntProperty(name='layer') ##FourMadMen## Can access this because call to operator() returns instance of operator

    def execute(self, context):
        print('SimpleOperatorTwo... operating on layer ',self.properties.layer ) ##FourMadMen## Value of this was sent during call to operator()
        return {'FINISHED'}


class VIEW3D_MT_master(bpy.types.Menu):
    bl_idname='master'
    bl_label = "Master Material Menu"
    global genprop 
    
    #not necessary - bruno
    #layer = IntProperty(name='layer') ##FourMadMen## CAN'T access this because call to menu() returns None
    
    def draw(self, context):
        #not necessary is not here that you set genprop. Here you only access genprop - bruno
        #global genprop

        layout = self.layout
        
        ##FourMadMen## Would like to do the following but no way to set this from the menuPall menu() call (see below)
        #
        #layout.operator("simple_operatorone",text='operation 1', icon='ZOOMIN').layer = layer
        #layout.operator("simple_operatortwo",text='operation 2',  icon='HAND').layer = layer
        #
        ####
        #I use directly the global variable array  "genprop"
        #layer = genprop ##FourMadMen## When this is drawn value of gen prop for all menus in panel will be 2
        layout.operator("simple_operatorone",text='operation 1', icon='ZOOMIN').layer = genprop[0]
        layout.operator("simple_operatortwo",text='operation 2',  icon='HAND').layer = genprop[1]
        
        #print('VIEW3D_MT_master...',genprop)

        
class menuPanel(bpy.types.Panel):
    bl_idname = "menuPanel"
    bl_label = "Simple Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context):
        global genprop
        layout = self.layout
        
        for i in range(1,3):
            genprop.append(i)
            layout.menu(VIEW3D_MT_master.bl_idname, text='menu for layer '+str(i))
        
        ###### FourMadMen asks ## Any way to pass value of 'i' to the menu() call?
        ###### You can with operator() (see .operator() calls above) but not menu.
        
def register():
    bpy.types.register(SimpleOperatorOne)
    bpy.types.register(SimpleOperatorTwo)
    
    bpy.types.register(VIEW3D_MT_master)
    
    bpy.types.register(menuPanel)

def unregister():
    bpy.types.unregister(SimpleOperatorOne)
    bpy.types.unregister(SimpleOperatorTwo)
    
    bpy.types.unregister(VIEW3D_MT_master)
    
    bpy.types.unregister(menuPanel)

if __name__ == "__main__":
    register()

You have a misunderstanding. I think you are associating the number of menu items I used in the example with the number of operators. They are not related.

Within menu draw (where layer is the same number for each operator call):


layout.operator("simple_operatorone",text='operation 1', icon='ZOOMIN').layer = layer
layout.operator("simple_operatortwo",text='operation 2',  icon='HAND').layer = layer

So if you click the first menu control and and choose operation 1 then that will perform operation 1 on layer 1
If you click the first menu control and choose operation 2 then that will perform operation 2 on layer 1

…coninuing with that…

If you click the second menu control and choose operation 1 then that will perform operation 1 on layer 2
If you click the second menu control and choose operation 2 then that will perform operation 2 on layer 2

…and extending beyone that…

If you click the Nth (3rd, 4th, 5th, etc.) menu control and choose operation 2 then that will perform operation 2 on layer N
If you click the Nth (3rd, 4th, 5th, etc) menu control and choose operation 2 then that will perform operation 2 on layer N

And at the risk of adding code that does not work. But the following code demonstrates the spirit of what I want and although this code does not work perhaps there is an alternative way to set the property within the menu class.

First the snippet of what I’d LIKE to do (again operators can but anyway…)


 for i in range(0, 20):
            layout.menu(VIEW3D_MT_master.bl_idname, text='menu for layer '+str(i)).layer(i)

But you can’t set the layer proper of the menu class like you can with the operator class because the menu() call returns ‘None’ and the operator call returns an instance of the class.


import bpy
from bpy.props import*
        
class SimpleOperatorOne(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatorone"
    bl_label = "Simple Object Operator 1"
    bl_options = {'REGISTER', 'UNDO'}
    
    layer = IntProperty(name='layer') ##FourMadMen## Can access this because call to operator() returns instance of operator
    
    def execute(self, context):
        print('SimpleOperatorOne... operating on layer ',self.properties.layer ) ##FourMadMen## Value of this was sent during call to operator()
        return {'FINISHED'}

class SimpleOperatorTwo(bpy.types.Operator):
    ''''''
    bl_idname = "simple_operatortwo"
    bl_label = "Simple Object Operator 2"
    bl_options = {'REGISTER', 'UNDO'}

    layer = IntProperty(name='layer') ##FourMadMen## Can access this because call to operator() returns instance of operator

    def execute(self, context):
        print('SimpleOperatorTwo... operating on layer ',self.properties.layer ) ##FourMadMen## Value of this was sent during call to operator()
        return {'FINISHED'}


class VIEW3D_MT_master(bpy.types.Menu):
    bl_idname='master'
    bl_label = "Master Material Menu"
    
    layer = IntProperty(name='layer') ##FourMadMen## CAN'T access this because call to menu() returns None
    
    def draw(self, context):
        layout = self.layout
        
        layout.operator("simple_operatorone",text='operation 1', icon='ZOOMIN').layer = layer
        layout.operator("simple_operatortwo",text='operation 2',  icon='HAND').layer = layer

        
class menuPanel(bpy.types.Panel):
    bl_idname = "menuPanel"
    bl_label = "Simple Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    
    def draw(self, context):
        layout = self.layout
        
        for i in range(0, 20):
            layout.menu(VIEW3D_MT_master.bl_idname, text='menu for layer '+str(i)).layer(i)
        
        ###### FourMadMen asks ## Any way to pass value of 'i' to the menu() call?
        ###### You can with operator() (see .operator() calls above) but not menu.
        
def register():
    bpy.types.register(SimpleOperatorOne)
    bpy.types.register(SimpleOperatorTwo)
    
    bpy.types.register(VIEW3D_MT_master)
    
    bpy.types.register(menuPanel)

def unregister():
    bpy.types.unregister(SimpleOperatorOne)
    bpy.types.unregister(SimpleOperatorTwo)
    
    bpy.types.unregister(VIEW3D_MT_master)
    
    bpy.types.unregister(menuPanel)

if __name__ == "__main__":
    register()

ok sorry lost what was the goal here !

so you want to identify the menu selected and the item selected in that menu ?

you could add a class count in the class to know which one was last selected may be !
and this var is readable anywhere so you could use it

there is one thing i don’t get it

when the operator is exec
it should create a button in new panel but it’s not making one every time
or may be it is the same one at same location
like not appending button but simply replacing it !

any toughs on this ?

salutations

i fond another menu example

using an operator

and you an print the seleted item from this menu

but in your i don’t know how you can do that ?

any idea how to print the selected item in yuor added menus?

Thanks

The execute function of the operator is called when the menu item selected is an operator so the print() could go in there. Do you have a url for this new example? Or is it in Blender somewhere?

it’s an example i got a while ago

here it is

here i added a second menu for the fun
but cannot tell which menu it is ?




import bpy
from bpy.props import *

#For the non-operator menus, define their values like this:
bpy.types.Scene.EnumProperty(attr='method', name='Method', items=(
    ('1', 'One', 'The first item'),
    ('2', 'Two', 'The second item'),
    ('3', 'Three', 'The third item')), default='1')

#These are stored in the .blend



#For the operator menu this variable just stores the last operation called

last_method = 'None'

class myPanel(bpy.types.Panel):#This is the panel
    '''Panel'''
    bl_label = "Menu Examples"				#	Called this
    bl_space_type = "VIEW_3D"				#	in 3D view
    bl_region_type = "TOOLS"				#	in tool shelf

    def draw(self, context):
        global last_method					#make this global to find the last operation

        layout = self.layout

        col = layout.column()
        col.label(' A menu that executes operators:', 'UI')
        col.operator_menu_enum('view_3d.my_operator', 'method', 'Operate')				#	Here the operator menu is displayed
																						#	'view_3d.my_operator' is the bl_idname of the operator called
        
        col.label(' Last: ' + last_method, 'QUESTION')
        
        
        
        col.separator();col.separator();col.separator()
        
        col.label(' A menu that executes operators:', 'UI')
        col.operator_menu_enum('view_3d.my_operator', 'method', 'Operate')				#	Here the operator menu is displayed
																						#	'view_3d.my_operator' is the bl_idname of the operator called



class myOperator(bpy.types.Operator):				#	When an option in the operator menu is clicked, this is called
    '''Operator'''
    bl_idname = 'view_3d.my_operator'
    bl_label = 'Operator'
    
    #Define possible operations
    method = EnumProperty(items=(
        ('1', 'One', 'The first item'),
        ('2', 'Two', 'The second item'),
        ('3', 'Three', 'The third item')))

    def execute(self, context):
    
        global last_method							#	Make global so we can later draw it in the panel

        last_method = self.properties.method		#	Store the choosen operation
        return {'FINISHED'}
        
        
        
        
        
        

def register():
    bpy.types.register(myOperator)
    bpy.types.register(myPanel)

def unregister():
    bpy.types.unregister(myOperator)
    bpy.types.unregister(myPanel)

if __name__ == "__main__":
    register()




it’s an intersting way of doing it
but you need to add menu and use different operator i guess
to catch the menu unless there is another way ?

Thanks

No, that just uses a global to tell which menu option was chosen. I can already tell which option is chosen. Thanks anyway.