Creating a custom menu option

I am very close to creating an add on that will create a custom menu option but I can’t figure out how to call the custom code that I want to call.

The code below will run as an add on. It will put a menu option for “Clean Screen Layout” in the Window menu in the Info window.


bl_info = {
    'name': "Simple Carnival Menu Additions",
    'author': "Jeff Boller",
    'version': (1, 0, 0),
    'blender': (2, 6, 7),
    'api': 44136,
    'description': "Various menu additions for The Simple Carnival's stereoscopic toolkit.",
    'wiki_url': "3d.simplecarnival.com",
    'tracker_url': "",
    'category': "Object"}


import bpy

def clean_screen_layout(self, context):
    import bpy
    orig_screen = bpy.context.window.screen.name
    for screen in bpy.data.screens:
         if (len(screen.name) > 4):
             if (screen.name[-4:-3] == "."):
                 if (screen.name[-3].isdigit()):
                    self.report({'INFO'}, "Deleted screen '" + screen.name + "'")
                    bpy.ops.screen.delete({'screen': screen})
    bpy.context.window.screen = bpy.data.screens[orig_screen]


def menu_clean_screen_layout(self, context):
    bl_label = "Clean Screen Layout"
    bl_idname = "OBJECT_MT_clean_screen_layout"
    #self.layout.operator("wm.save_homefile")
    self.layout.separator()
    self.layout.operator("screen.area_split", text="Clean Screen Layout").direction = 'VERTICAL'


def register():
    bpy.types.INFO_MT_window.append(menu_clean_screen_layout)

if __name__ == "__main__":     register()



The problem is, I am trying to actually run the function clean_screen_layout() once the menu option is picked. Right now I have some bogus code in there (self.layout.operator(“screen.area_split”, text=“Clean Screen Layout”).direction = ‘VERTICAL’). Obviously. “screen.area_split” needs to be changed so it will call clean_screen_layout(). However, I can’t figure out the syntax on how to do it (nor can I find a similar example to imitate).

Any suggestions? Thanks!

I think you need to sort out the creation of the menu code first and leave the code on the actual work to be done later. There are templates in Blender, but I suggest you look at the CGCookie tutorial on making a simple popup menu that you can find at http://cgcookie.com/blender/2013/05/23/creating-custom-menu-python-scripting/

If you use that as a template then about the only thing you need to add is the class for the clean screen. That class will probably need to be registered as well.

What is the goal of the script? Split and un-split and view?

The goal is to call the function clean_screen_layout(), which deletes what I consider to be extraneous screen layouts (any layouts that end with a period followed by three numbers). I only had the split/unsplit code in there because I pulled that from another example I found on the forum and couldn’t figure out how to substitute my own function.

You could do it like this:

import bpy
import re


class SimpleOperator(bpy.types.Operator):
    """Delete screens which end with .###"""
    bl_idname = "screen.clear"
    bl_label = "Clear Screens"

    def execute(self, context):
        screen_name = context.screen.name
        for screen in bpy.data.screens:
            if re.match(".*\.\d{3}$", screen.name) is not None:
                bpy.ops.screen.delete({'window': context.window, 'screen': screen, 'region': None})
        screen = bpy.data.screens.get(screen_name)
        if screen is not None:
            context.window.screen = screen
        return {'FINISHED'}


def draw_func(self, context):
    layout = self.layout
    layout.separator()
    layout.operator(SimpleOperator.bl_idname)
    
def register():
    bpy.utils.register_class(SimpleOperator)
    bpy.types.INFO_HT_header.append(draw_func)

def unregister():
    bpy.utils.unregister_class(SimpleOperator)
    bpy.types.INFO_HT_header.remove(draw_func)


if __name__ == "__main__":
    register()


Thank you! That’s great. Is there some way to ensure that the button doesn’t slide off the screen? On a 1366x768 screen, if I select an object with a longish name in the outliner or in the scene, the button will slide off the screen, never to be seen again (until an object with a shorter name is selected).

If there isn’t a way to get around this, I would prefer to implement this like how I was originally trying to implement it – which was to save screen real estate and have an option under the Window menu in the Info panel. If you could fill in the piece of what I was missing there to call my custom function, that would be really helpful.

You can only append or prepend to existing draw functions, thus it’s possible to put the button as the left most element, even before the editor type selector.

Simple replace

 INFO_HT_header

by

 INFO_MT_window

to make it a menu entry.

Thanks – I got it working with the menu option. I did notice one thing, however. I wonder if this is a Blender bug (I’m using 2.72) or if there’s a way to work around this:

  • Create a new screen layout in a popout window by doing a shift+drag on one of the window splitters. This will create a “____.###” screen layout.
  • Leave that new window open, but go back to your original screen.
  • Run the clean screen layout code.
  • Click on the scene layout dropdown. Blender will crash.

This happens no matter if I use your way of deleting the screen layouts or if I use mine. If you do the above steps but close any .### windows that are in separate windows before running the clean screen code, Blender will not crash. Likewise, if the current screen layout is a .### window and you run the clean screen code, Blender will not crash. It’s only when a .### window is being shown in another Blender window.

How can I determine whether a screen layout is currently open in a Blender window? Mind you, I don’t want to check whether the screen layout is active in the current active Blender window that the code is being run from. I only want to check whether one of the other Blender windows has a .### screen layout. (If so, I won’t delete that layouts.)

All screens are in bpy.data.screens, the active one is referenced by every Window object, so should be something like

for window in bpy.context.window_manager.windows:
    print(window.screen.name)

You should report the crash issue to the bug tracker!

That provides all of the possible screen layouts in the blend file – not the screen layouts that are currently visible when Blender is open.

What I’m trying to find are the screen layouts for all of the other “pop out” windows that are currently open. (I’m not sure what Blender calls these “pop out” windows – they’re the separate windows that open when you hold down shift while dragging on a window splitter.)

My mistake. I’m not sure why I was seeing what I was seeing before, but that code does indeed work as intended.

The final code:


bl_info = {
    'name': "Simple Carnival Clean Screen Layouts",
    'author': "CoDEmanX, Jeff Boller",
    'version': (1, 0, 1),
    'blender': (2, 6, 7),
    'api': 44136,
    'location': "Info panel -> 'Window' menu option -> 'Clean Screen Layouts'",
    'description': "Deletes all screen layouts which end with .###",
    'wiki_url': "3d.simplecarnival.com",
    'tracker_url': "",
    'category': "Object"}


import bpy
import re


class SimpleOperator(bpy.types.Operator):
    """Delete screen layouts which end with .###"""
    bl_idname = "screen.clear"
    bl_label = "Clean Screen Layouts"


    def execute(self, context):
        screen_name = context.screen.name
        for screen in bpy.data.screens:
            if re.match(".*\.\d{3}$", screen.name) is not None:
                s = screen.name
                allowed_to_delete = 1
                # Make sure we don't have any windows that have this particular screen layout open. (Blender will crash.)
                for window in bpy.context.window_manager.windows:
                    if (window.screen.name == s):
                        self.report({'WARNING'}, "Cannot delete screen layout '" + window.screen.name + "' because it is open.")
                        allowed_to_delete = 0
                        break
                if (allowed_to_delete == 1):
                    bpy.ops.screen.delete({'window': context.window, 'screen': screen, 'region': None})
                    self.report({'INFO'}, "Deleted screen '" + s + "'")
        screen = bpy.data.screens.get(screen_name)
        if screen is not None:
            context.window.screen = screen
        return {'FINISHED'}

def draw_func(self, context):
    layout = self.layout
    layout.separator()
    layout.operator(SimpleOperator.bl_idname, text=SimpleOperator.bl_label)

def register():
    bpy.utils.register_class(SimpleOperator)
    bpy.types.INFO_MT_window.append(draw_func)

def unregister():
    bpy.utils.unregister_class(SimpleOperator)
    bpy.types.INFO_MT_header.remove(draw_func)


if __name__ == "__main__":
    register()