From BlenderWiki

Jump to: navigation, search

This page shows how to intercept every draw call in blenders python ui which can be used for some UI tricks.

First heres an example of how to do just that.

Tested to work with Blender 2.70

Intercept UI draw/poll

This is a simple example of how to intercept poll and draw functions, without making any behavioral changes.

classes = ["Panel", "Menu", "Header"]
 
 
def draw_override(func_orig, self_real, context):
    print("override draw:", self_real)
 
    ret = func_orig(self_real, context)
    return ret
 
 
def poll_override(func_orig, cls, context):
    print("override poll:", func_orig.__self__)
 
    ret = func_orig(context)
 
    return ret
 
import bpy
 
 
for cls_name in classes:
    cls = getattr(bpy.types, cls_name)
 
    for subcls in cls.__subclasses__():
        if "draw" in subcls.__dict__:  # dont want to get parents draw()
 
            def replace_draw():
                # function also serves to hold draw_orig in a local namespace
                draw_orig = subcls.draw
                def draw(self, context):
                    return draw_override(draw_orig, self, context)
                subcls.draw = draw
 
            replace_draw()
 
        if "poll" in subcls.__dict__:  # dont want to get parents poll()
            def replace_poll():
                # function also serves to hold poll_orig in a local namespace
                poll_orig = subcls.poll
                def poll(cls, context):
                    return poll_override(poll_orig, cls, context)
                subcls.poll = classmethod(poll)
 
            replace_poll()

Override layout & functions

This is the extended from the above code but extended to subclass the layout type used within draw().

In this example operators and properties are filtered out based on their names, but all sorts of things are possible with this - modifying args to functions, changing text etc.

classes = ["Panel", "Menu", "Header"]
 
 
import bpy
 
UILayout = bpy.types.UILayout
 
op_blacklist = [
    "render.render",
    "object.modifier_add",
    "object.forcefield_toggle",
    ]
 
prop_blacklist = [
    "Object.location",
    "Object.scale",
    "Object.rotation_euler",
    "RenderSettings.display_mode",
    ]
 
 
def filter_operator(op_id):
    if op_id in op_blacklist:
        return False
    return True
 
 
def filter_prop(data, prop):
    prop_id = "%s.%s" % (data.__class__.__name__, prop)
    if prop_id in prop_blacklist:
        return False
    return True
 
 
class OperatorProperties_FAKE:
    pass
 
 
class UILayout_FAKE(bpy.types.UILayout):
    __slots__ = ()
 
    def __getattribute__(self, attr):
        # ensure we always pass down UILayout_FAKE instances
        if attr in ("row", "split", "column", "box", "column_flow"):
            real_func = UILayout.__getattribute__(self, attr)
 
            def dummy_func(*args, **kw):
                print("    wrapped", attr)
                ret = real_func(*args, **kw)
                return UILayout_FAKE(ret)
            return dummy_func
 
        elif attr in ("operator", "operator_menu_enum", "operator_enum"):
            real_func = UILayout.__getattribute__(self, attr)
 
            def dummy_func(*args, **kw):
                print("    wrapped", attr)
                if filter_operator(args[0]):
                    ret = real_func(*args, **kw)
                else:
                    # UILayout.__getattribute__(self, "label")()
                    # may need to be set
                    ret = OperatorProperties_FAKE()
                return ret
            return dummy_func
        elif attr in ("prop", "prop_enum"):
            real_func = UILayout.__getattribute__(self, attr)
 
            def dummy_func(*args, **kw):
                print("    wrapped", attr)
                if filter_prop(args[0], args[1]):
                    ret = real_func(*args, **kw)
                else:
                    ret = None
                return ret
            return dummy_func
        else:
            return UILayout.__getattribute__(self, attr)
 
        print(self, attr)
 
    def operator(*args, **kw):
        print("OP")
        return super().operator(*args, **kw)
 
 
def draw_override(func_orig, self_real, context):
    if 1:
        class Wrapper(self_real.__class__):
            def __getattribute__(self, attr):
                if attr == "layout":
                    ret = self_real.layout
                    return UILayout_FAKE(ret)
                else:
                    return super().__getattr__(self, attr)
 
            @property
            def layout(self):
                ret = self_real.layout
                print("wrapped")
                return ret
        print(1)
        self_wrap = Wrapper(self_real)
        ret = func_orig(self_wrap, context)
    else:
        # simple, no wrapping
        ret = func_orig(self_wrap, context)
 
    return ret
 
 
def poll_override(func_orig, context):
 
    ret = func_orig(context)
 
    return ret
 
 
for cls_name in classes:
    cls = getattr(bpy.types, cls_name)
 
    for subcls in cls.__subclasses__():
        if "draw" in subcls.__dict__:  # dont want to get parents draw()
 
            def replace_draw():
                # function also serves to hold draw_old in a local namespace
                draw_orig = subcls.draw
 
                def draw(self, context):
                    return draw_override(draw_orig, self, context)
                subcls.draw = draw
 
            replace_draw()
 
        if "poll" in subcls.__dict__:  # dont want to get parents poll()
            def replace_poll():
                # function also serves to hold draw_old in a local namespace
                poll_orig = subcls.poll
 
                def poll(context):
                    return poll_override(poll_orig, context)
                subcls.poll = classmethod(poll)
 
            replace_poll()