5
$\begingroup$

I would like to get the list of operators, and their order, in a menu. For example I need to get that list for the file menu (bpy.types.INFO_MT_file).

The idea is to add an element in a specific point of the menu, which is not possible with the append or prepend function. But if I can get the list of operators, I can rebuild an entire menu and insert my element where I want, that will do the trick.

So far I can do it with my plugins (overwriting the INFO_MT_file Class). But if another plugin do the same thing as I'm doing, we will destroy each other's layout. So any way to get a menu's children ?

Cheers !

$\endgroup$
5
$\begingroup$

Inserting layout elements in between is not supported and I wouldn't recommend to hack such a feature in with Python.

You can get the filename of the file from which a draw function originates, granted it is a python script:

bpy.types.INFO_MT_file.draw.__code__.co_filename

... then check the code of that file.

Here is an example how to "inject" code:

bl_info = {
    "name": "Menu Insert In-Between",
    "author": "CoDEmanX",
    "version": (1, 0),
    "blender": (2, 65, 0),
    "location": "File menu"}

import bpy

def main():
    insert_after = 'layout.menu("INFO_MT_file_open_recent"'
    insert_code = '    layout.operator("object.select_all", text="Some OT")\n'
    bpy_type = "INFO_MT_file"
    bpy_type_class = getattr(bpy.types, bpy_type)
    #module = bpy_type_class.__module__

    filepath = bpy_type_class.draw.__code__.co_filename
    if filepath == "<string>":
        print("Aborting, modifications are active")
        return
    try:
        file = open(filepath, "r")
        lines = file.readlines()
    except:
        print("%s couldn't be accessed, aborting." % filepath)
        return

    line_start = bpy_type_class.draw.__code__.co_firstlineno - 1

    for i in range(line_start, len(lines)):
        line = lines[i]
        if not line[0].isspace() and line.lstrip()[0] not in ("#", "\n", "\r"):
            break

    line_end = i

    # Unindent draw func by one level, since it won't sit inside a class
    lines = [l[4:] for l in lines[line_start:line_end]]

    for i, line in enumerate(lines, 1):
        if insert_after in line:
            print("FOUND INSERT LINE")
            lines.insert(i, insert_code)
            break
    else:
        print("COULDN'T FIND INSERTION POINT")
        return

    # Debug output
    #f = open("D:\\s.txt", "w").writelines(lines)

    l = {}
    exec("".join(lines), {}, l)
    print(l)

    #bpy_type_class.draw.__code__ = code_object # Doesn't work, since a single func is not a module

    bpy_type_class.draw = l['draw'] # exec defined our custom draw() func!

def register():
    print("register menu inbetween adder")
    main()

def unregister():
    print("unregister menu inbetween adder")

if __name__ == "__main__":
    register()

But the problem remains: if a second addon tries to do the same, it will revert what the first did, unless you write a system that solely manages adding stuff.

It is also possible to replace the UILayout type to intercept every draw operation, which also allows to "remove" stuff like operators or properties from interface. The problem is worse here however: if a second script tries to replace the UILayout type, the entire layout system will crash and all elements disappear from UI unless they are drawn by C code alone.

You can find an implementation here:

http://wiki.blender.org/index.php?title=Dev:2.5/Py/Scripts/Cookbook/Code_snippets/Interface&oldid=226944#Advanced_Topics

$\endgroup$
  • $\begingroup$ Interesting approach with the lecture of the source file, thanks for sharing ! Yes the issue of another addon doing the same thing can be tricky to handle. GUI questions really needs a powerup in Blender. $\endgroup$ – Flavio Jul 2 '14 at 15:00
  • $\begingroup$ Hey StackExchange users : As the answer is "it's not supported, but here is a work around", should I mark the question as answered, or should I leave it open ? $\endgroup$ – Flavio Jul 2 '14 at 15:03
  • $\begingroup$ That kind of question should go to meta.blender.stackexchange.com, but yeah, you should accept the answer as it is the current solution / valid answer. Once this changes, you may accept another (new) answer. $\endgroup$ – CoDEmanX Jul 2 '14 at 20:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.