Context Sensitive Menu in Blender?

I would like to create a Mesh Specials context menu. For example, in edit mode, select a face then press w key for face specials menu, or select an edge press w to open edge specials and so on. I don’t like using Ctrl E/F/V for edge, face and vertex menus every time. I think only one dynamic menu can do the same thing. How can I do that? (An example script would be awesome.)

Thanks

Maybe the right question is “Is this possible with python or not?” :eyebrowlift2:

My principal answer is “YES”, meaning “it should be possible”… But what if a vertex and an edge are selected in the same time? The script is to respond by a “CANCEL”-message???

Something like the dynamic space bar menu? You can test for specific conditions in the draw method of the menu class and output the menu accordingly.

This time another sub-menu can be created like in Dynamic Spacebar Menu. Or menu appears only for the last selected component. Maya is capable of doing this, why not Blender?
(was a Maya user before. Sorry. :))

In scripts/startup/bl_ui/space_view3d.py it’s these babies


        layout.separator()

        layout.menu("VIEW3D_MT_edit_mesh_vertices")
        layout.menu("VIEW3D_MT_edit_mesh_edges")
        layout.menu("VIEW3D_MT_edit_mesh_faces")
        layout.menu("VIEW3D_MT_edit_mesh_normals")

easy enough to check the current selection mode too, this sets them. checking is similar


# vertex
bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(True, False, False)")
# edge
bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False, True, False)")
# face
bpy.ops.wm.context_set_value(data_path="tool_settings.mesh_select_mode", value="(False, False, True)")

this would check the current state, while in context


# if is face select mode
bpy.context.tool_settings.mesh_select_mode[:] == (False, False, True)
>> true

i’ll give it a swing lateron demohero, unless someone beats me to it.

Thank you zeffii.

I wish I had enough python knowledge. Using this in scripts/startup/bl_ui/space_view3d.py file would be great.

Btw, Blender already has context sensitive menus in Object mode. Select a camera and press w for example. When you select a lamp object, w specials menu changes for lamp properties. I think dynamic menus are very important for reducing keyboard shortcuts. Much more practical.

@ yeah, the principle of providing a context sensitive menu for the last selected item sounds fair… although it may not be exactly what the user thinks in the moment… but partially, it is a problem of the user(s) :wink:

Heres an example, youll need to assign to a key to execute in different contexts.


import bpy


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

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

        if context.space_data.type == 'VIEW_3D':
            if context.mode == 'EDIT_MESH':
                layout.operator("wm.open_mainfile")
                layout.operator("wm.save_as_mainfile")
            elif context.mode == 'OBJECT':
                layout.operator("object.shade_smooth")
        elif context.space_data.type == 'IMAGE_EDITOR':
            layout.operator("image.new")
        else:
            layout.label("No Context!")
                


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


def unregister():
    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)

Thank you very much for this example Campbell.

But I don’t know how to assign a hotkey. How can I do that? Using keymap editor? Couldn’t find the operator named Simple Custom Menu?!

The following keymaps the menu to the “SPACE” bar

import bpy


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

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

        if context.space_data.type == 'VIEW_3D':
            if context.mode == 'EDIT':
                layout.operator("wm.open_mainfile")
                layout.operator("wm.save_as_mainfile")
            elif context.mode == 'OBJECT':
                layout.operator("object.shade_smooth")
        elif context.space_data.type == 'IMAGE_EDITOR':
            layout.operator("image.new")
        else:
            layout.label("No Context!")
                


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

    km = bpy.context.window_manager.keyconfigs.active.keymaps['3D View']
    kmi = km.keymap_items.new('wm.call_menu', 'SPACE', 'PRESS')
    kmi.properties.name = "OBJECT_MT_simple_custom_menu"    
    
    
    


def unregister():
    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)

To map it to j for instance change to

kmi = km.keymap_items.new('wm.call_menu', 'J', 'PRESS')

Thanks for your help @batFINGER and Meta-Androcto. This is awesome. :yes:

Updated the previous script.

  • uses spacebar
  • has vert/edge/face modes.

import bpy


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

    def draw(self, context):
        layout = self.layout
        if context.space_data.type == 'VIEW_3D':
            if context.mode == 'EDIT_MESH':
                layout.operator("wm.open_mainfile")
                layout.operator("wm.save_as_mainfile")

                layout.separator()

                props = layout.operator("wm.context_set_value", text="Vertex Mode")
                props.data_path = "tool_settings.mesh_select_mode"
                props.value="(True, False, False)"
    
                props = layout.operator("wm.context_set_value", text="Edge Mode")
                props.data_path = "tool_settings.mesh_select_mode"
                props.value="(False, True, False)"
    
                props = layout.operator("wm.context_set_value", text="Face Mode")
                props.data_path = "tool_settings.mesh_select_mode"
                props.value="(False, False, True)"
                
            elif context.mode == 'OBJECT':
                layout.operator("object.shade_smooth")
        elif context.space_data.type == 'IMAGE_EDITOR':
            layout.operator("image.new")
        else:

            layout.label("No Context!")
                


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

    km = bpy.context.window_manager.keyconfigs.active.keymaps['3D View']
    kmi = km.keymap_items.new('wm.call_menu', 'SPACE', 'PRESS')
    kmi.properties.name = "OBJECT_MT_simple_custom_menu"    
    
    
    


def unregister():
    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)

a remix ( Using J key) :



import bpy


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

    def draw(self, context):
        layout = self.layout
        if context.space_data.type == 'VIEW_3D':
            if context.mode == 'EDIT_MESH':

                layout.separator()

                # if vertex mode
                if bpy.context.tool_settings.mesh_select_mode[:] == (True, False, False):
                    layout.menu("VIEW3D_MT_edit_mesh_vertices")
        
                # if edge mode
                if bpy.context.tool_settings.mesh_select_mode[:] == (False, True, False):
                    layout.menu("VIEW3D_MT_edit_mesh_edges")
        
                # if face mode
                if bpy.context.tool_settings.mesh_select_mode[:] == (False, False, True):
                    layout.menu("VIEW3D_MT_edit_mesh_faces")

    
                
            elif context.mode == 'OBJECT':
                layout.label("object mode")
        elif context.space_data.type == 'IMAGE_EDITOR':
            layout.label("No Context! image editor")
        else:

            layout.label("No Context!")
                


def register():
    bpy.utils.register_class(SimpleCustomMenu)
  
    km = bpy.context.window_manager.keyconfigs.active.keymaps['3D View']
    kmi = km.keymap_items.new('wm.call_menu', 'J', 'PRESS')
    kmi.properties.name = "OBJECT_MT_simple_custom_menu"    

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


if __name__ == "__main__":
    register()

or, if you use a variety of modes, sometimes combined like ‘faces+edges’ then consider


import bpy


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

    def draw(self, context):
        layout = self.layout
        if context.space_data.type == 'VIEW_3D':
            if context.mode == 'EDIT_MESH':

                layout.separator()
                
                current_mesh_mode = context.tool_settings.mesh_select_mode[:]

                # allows multiple, simultaneous modes, f.ex edges+faces 
                # if vertex mode on
                if current_mesh_mode[0]:
                    layout.menu("VIEW3D_MT_edit_mesh_vertices")
        
                # if vertex mode on
                if current_mesh_mode[1]:
                    layout.menu("VIEW3D_MT_edit_mesh_edges")
        
                # if vertex mode on
                if current_mesh_mode[2]:
                    layout.menu("VIEW3D_MT_edit_mesh_faces")

    
                
            elif context.mode == 'OBJECT':
                layout.label("object mode")
        elif context.space_data.type == 'IMAGE_EDITOR':
            layout.label("No Context! image editor")
        else:

            layout.label("No Context!")
                


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

    km = bpy.context.window_manager.keyconfigs.active.keymaps['3D View']
    kmi = km.keymap_items.new('wm.call_menu', 'J', 'PRESS')
    kmi.properties.name = "OBJECT_MT_simple_custom_menu"    
    

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


if __name__ == "__main__":
    register()

This must be a dream. Thank you soooo much for all your help. :yes: