Modifier option window in Blender Python

Hi guys,

So after having a go at a Blender Cookie tutorial about making a custom toolbar script, I decided to have a go at making my own version of his “QuickToolbar”.

After some searching in the blender scripts for calling the Enum Menu of the Modifiers, I’ve come across a predicament.

I want to try an call an options window in the 3D space when I’ve selected a modifier, just like it does in the modifiers tab.

For example ( I’ve mapped the custom bar to Shift + Q ) when I press Shift + Q and then select the Mirror Modifier from the modifiers menu, a second window appears giving me the available options like enabling clipping, what axis it should be put on etc…

Also, on a slightly different note. How do I go about putting the smooth and flat into their own submenu? just like the modifier menu?

Any Ideas?

This is what I have at the moment:


# Adding a modifier menu ------------------------------------------------------------
col.operator_menu_enum ( "object.modifier_add", type", icon = "MODIFIER" )

# Adding a smooth/flat shader -------------------------------------------------------
col = layout.column ( align = True )
layout.separator()
row = col.row()
row.operator ( "object.shade_smooth", "Smooth Shading", icon = 'SOLID' )
row = col.row()
row.operator ( "object.shade_flat", "Flat Shading", icon = 'MESH_UVSPHERE' )

Blender is not designed to allow this, but look yourself:


import bpy


class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"
    bl_options = {'REGISTER', 'UNDO'}
    
    @classmethod
    def poll(cls, context):
        return (context.active_object is not None and
                context.active_object.type == 'MESH')    


    def execute(self, context):
        return {'FINISHED'}
    
    def draw(self, context):
        layout = self.layout
        ob = context.object
        md_self = bpy.types.DATA_PT_modifiers
        for md in ob.modifiers:
            box = layout.template_modifier(md)
            if box:
                # match enum type to our functions, avoids a lookup table.
                getattr(md_self, md.type)(md_self, box, ob, md)
        
    def invoke(self, context, event):
        return context.window_manager.invoke_props_popup(self, event)




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




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




if __name__ == "__main__":
    register()


    # test call
    bpy.ops.object.simple_operator('INVOKE_DEFAULT')



I’m completely new to python so could you try to explain what that’s doing please?

it’s an operator that opens a popup. The popup uses a copy of the modifier panel draw code (accessible via the registered RNA type) without instancing that panel class. It is a working solution, but drawing panel UI widgets in a popup isn’t nice - the popup’s background color shines through the widgets making some labels and especially dropdown fields up to unreadable. Furthermore, if you change the order of modifiers in the popup, they change in modifier panel, but not in popup. There may be more issues i didn’t discover yet.

So where do I put that into my script then?

It’s just a demo, you can paste and run it in blender’s text editor. If you want to use it in your script, copy’n’paste the operator and call it in your code (or add the operator to a panel). Make sure you have it register.

So here’s what I have at the moment:


bl_info = {
        "name": "Custom Modifier Menu",
        "category": "3D View",
        "author": "Jamie Edwards",
        "version": ( 0, 1 ),
        "description": "A Custom Menu containing Modifiers and Shaders alike."
        }
import bpy


class SimpleOperator ( bpy.types.Operator ):
    """Tooltip"""
    bl_idname = "object.active_modifiers"
    bl_label = "Active Modifiers:"
    bl_options = { 'REGISTER', 'UNDO' }
    
    @classmethod
    def poll ( cls, context ):
        return ( context.active_object is not None and
            context.active_object.type == 'MESH' )
    
    def execute ( self, context ):
        return { 'FINISHED' }
    
    def draw ( self, context ):
        layout = self.layout
        ob = context.object
        md_self = bpy.types.DATA_PT_modifiers
        for md in ob.modifiers:
            box = layout.template_modifier ( md )
            if box:
                # match enum type to our functions, avoid a lookup table.
                getattr ( md_self, md.type ) ( md_self, box, ob, md )
    
    def invoke ( self, context, event ):
        return context.window_manager.invoke_props_popup ( self, event )
                


class customMenu ( bpy.types.Menu ):
    bl_label = "Custom Modifier Menu"
    bl_idname = "view3D.custom_modifier-menu"
    
    def draw ( self, context ):
        layout = self.layout
        col = layout.column ( align = True )
        row = col.row()
        
        # Adding a modifier menu----------------------------------------------
        col.operator_menu_enum ( "object.modifier_add", "type", icon="MODIFIER" )
        
        # Adding a smooth/flat shader -----------------------------------------
        col = layout.column ( align = True )
        layout.separator()
        row = col.row()
        row.operator ( "object.shade_smooth", "Smooth Shading", icon='SOLID' )
        row = col.row()
        row.operator ( "object.shade_flat", "Flat Shading", icon='MESH_UVSPHERE' )
        
        # Adding existing Modifiers ( if any )
        col = layout.column ( align = True )
        layout.separator()
        row = col.row()
        row.operator ( "object.active_modifiers" )


# Register it ---------------------------------------------      
def register():
    bpy.utils.register_class ( customMenu )
    bpy.utils.register_class ( SimpleOperator )
    bpy.ops.wm.call_menu ( name = customMenu.bl_idname )
    
# Unregister it -------------------------------------------    
def unregister():
    bpy.utils.unregister_class ( customMenu )
    bpy.utils.unregister_class ( SimpleOperator )
    
if __name__ == "__main__":
    register()

And when I press “Run Script” the label I’ve given it which is “Active Modifiers:” shows, but none of my actual active modifiers show up ( and I have one mirror and one subsurf going ), yet when I copy and paste your demo they do show.

Any ideas?

import bpy



class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Edit Modifier"
    bl_options = {'REGISTER', 'UNDO'}
    
    def modifiers_cb(self, context):
        return [(md.name, md.name, '') for md in context.object.modifiers]
    
    modifier = bpy.props.EnumProperty(items=modifiers_cb)
    
    @classmethod
    def poll(cls, context):
        return (context.active_object is not None and
                context.active_object.type == 'MESH')    




    def execute(self, context):
        return {'FINISHED'}
    
    def draw(self, context):
        layout = self.layout
        ob = context.object
        md = ob.modifiers.get(self.modifier)
        
        if md is None:
            return {'CANCELLED'}
        
        md_self = bpy.types.DATA_PT_modifiers
        box = layout.template_modifier(md)
        
        if box:
            # match enum type to our functions, avoids a lookup table.
            getattr(md_self, md.type)(md_self, box, ob, md)
        
    def invoke(self, context, event):
        return context.window_manager.invoke_props_popup(self, event)


class customMenu ( bpy.types.Menu ):
    bl_label = "Custom Modifier Menu"
    bl_idname = "view3d.modifier_menu"
    
    def draw ( self, context ):
        layout = self.layout
        
        # Adding a modifier menu----------------------------------------------
        layout.operator_menu_enum ( "object.modifier_add", "type", icon="ZOOMIN" )
        layout.separator()
        
        # Adding a smooth/flat shader -----------------------------------------
        layout.operator ( "object.shade_smooth", "Smooth Shading", icon='SOLID' )
        layout.operator ( "object.shade_flat", "Flat Shading", icon='MESH_UVSPHERE' )
        layout.separator()
        
        # Adding existing Modifiers ( if any )
        row = layout.row()
        row.operator_context = 'INVOKE_DEFAULT'
        row.operator_menu_enum("object.simple_operator", "modifier", icon="MODIFIER")




def register():
    bpy.utils.register_module(__name__)


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


if __name__ == "__main__":
    register()


    # test call
    bpy.ops.wm.call_menu(name="view3d.modifier_menu")

BUT: it doesn’t redraw when i would have to (see e.g. Bevel modifier, Limit method - changing type doesn’t show the dynamic fields below), and operator buttons don’t work well (only once afaik). If you click subdivide for multires mod, the ranges for preview/render wo’t update etc.

When I try to install it as an addon, I get the following error:


Traceback ( most recent call last ):
  File "/usr/share/blender/2.67/scripts/startup/bl_operators/wm.py", line 1779, in execute
    addons_old = {mod.__name__ for mod in addon_utils.modiles ( addon_utils.addons_fake_modules)}
  File "/usr/share/blender/2.67/scripts/modules/addon_utils.py", line 188, in modules
    mod_list.sort(key=lambda mod:(mod.bl_info["category"],
  File "/usr/share/blender/2.67/scripts/modules/addon_utils.py", line 188, in <lambda>
    mod_list.sort(key=lambda mod:(mod.bl_info["category"],
KeyError:'category'
location:<unknown location>:-1

I pretty much copied the code you gave me but added the following onto the top:


bl_info = {
        "name": "Custom Modifier Menu",
        "category": "3D View",
        "author": "Jamie Edwards",
        "version": ( 0, 1 ),
        "description": "A Custom Menu containing Modifiers and Shaders alike."
        }

and that is just above the “import bpy” part?

you need to invoke the menu somehow, e.g. add a menu item to an existing panel with the custom menu as submenu or something like that.

what about my problem with post #9?

Oh, didn’t see your post #9. As the error message says, you need a ‘category’ key in the bl_info structure. Make sure you add all required fields to it.

But I have…