1
$\begingroup$

I've added a checkbox to the custom Blender UI Panel with a function assigned to it which is being executed on every checkbox update. The idea of that function is to extend Blender Top Menu with the custom Operator's button when the checkbox is checked and remove this button from the Menu when it is unchecked.

The first part - extending menus with the custom operators - is described in the current API documentation, and it works fine like this:

def menu_draw(self, context):
    self.layout.operator("my_render.operator")

bpy.types.TOPBAR_MT_render.append(menu_draw)

But I can't find a way to remove this button from the menu once it has been added. It seems that it should be something like:

bpy.types.TOPBAR_MT_render.remove(menu_draw)   

and in the documentation of this remove method it is said that it should remove the drawing function from the menu - and somehow it even works: I mean it executes and raises no Errors - but nothing happens, the button remains in the menu.

Is it a bug of this .remove() method for Menu classes or am I doing something wrong and it is supposed to work in some other way?

Of course I could just write two different TOPBAR_MT_render Menu classes (the first one as a copy of the original one and another one as its new custom version), place them in two different modules and then unregister existing -> import proper one -> register imported every time the checkbox is changed, but as far as it is possible I would like to avoid it.

I'm using Blender 2.83.3.

Thanks for the help in advance!

UPD Thanks everyone, I've found a mistake! Your comments have really helped to figure out where was the issue. I placed an activating function inside the Operator class and tried to append/remove it right from inside of it, so every time the Operator was called, another instance of this function was used - that's why the previous one could not be removed from the TOPBAR_MR_render. Now I moved the activating function ttr_draw() outside the operator class and everything works perfect. Here's the code I had before:

import bpy
from bpy.props import BoolProperty, FloatProperty, PointerProperty
from bpy.utils import register_class, unregister_class

class TTR_TopMenuExtend(bpy.types.Operator):
    bl_idname = 'ttr.top'
    bl_label = 'Extend Top Menu'
    
    def execute(self, context):
        
        def ttr_draw(self, context): # This function should be moved out of this class
            layout = self.layout
            layout.operator("ttr.operator", icon='RENDER_ANIMATION')
            
        if context.scene.ttr and context.scene.ttr.activate:
            bpy.types.TOPBAR_MT_render.append(ttr_draw)
        else:
            bpy.types.TOPBAR_MT_render.remove(ttr_draw)
        return {'FINISHED'}

def ttr_activate(self, context):
    '''Activating function'''
    bpy.ops.ttr.top()

class TTR(bpy.types.PropertyGroup):
    '''Properties Group for UI Panel'''
    
    activate : BoolProperty(
        name="",
        description="Enable",
        default=False,
        update=ttr_activate
    )
    speed : FloatProperty(
        name="Speed",
        description="Speed",
        default=100.0,
        min=0,
        soft_max=300.0,
        subtype = "FACTOR"
    )

class TTR_PT_panel(bpy.types.Panel):
    '''Create UI Panel in the render properties window'''
    bl_label = "My TTR Add-on"
    bl_idname = "RENDER_PT_ttr"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_options = {'DEFAULT_CLOSED'}
    bl_context = "render"
    bl_category = 'My TTR Add-on'
    
    def draw_header(self, context):
        layout = self.layout
        scene = context.scene
        props = scene.ttr
        
        col = layout.column()
        col.prop(props, "activate")
        
    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True
        scene = context.scene
        props = scene.ttr
        layout.active = props.activate
        col = layout.column()
        col.prop(props, "speed")

class TTR_Operator(bpy.types.Operator):
    '''Custom Operator to be called from the Top Menu'''
    bl_idname = "ttr.operator"
    bl_label = "My Custom Operator"
    
    def execute(self, context):
        return {'FINISHED'}

classes = [
    TTR_TopMenuExtend,
    TTR,
    TTR_Operator,
    TTR_PT_panel
]    

def ttr_register():
    for cl in classes:
        register_class(cl)

def ttr_unregister():
    for cl in classes:
        unregister_class(cl)
   
if __name__ == '__main__':
    ttr_register()    
    bpy.types.Scene.ttr = PointerProperty(type=TTR)
$\endgroup$
4
  • 2
    $\begingroup$ This should work. Could you show a minimum working example where this issue occurs? As long as menu_draw still refers to the same function the API should be able to remove it. $\endgroup$
    – Robert Gützkow
    Aug 7 '20 at 21:31
  • 2
    $\begingroup$ Possibly related blender.stackexchange.com/questions/131290/… and blender.stackexchange.com/questions/134364/… $\endgroup$
    – batFINGER
    Aug 7 '20 at 22:38
  • 1
    $\begingroup$ You would have to unregister/reload scripts. Consider that you can just swap out the item by another one in draw() based on any condition (if that helps in this case). $\endgroup$
    – brockmann
    Aug 7 '20 at 23:14
  • 1
    $\begingroup$ Thanks everyone, it really helped! I've found a mistake in the code: I should not put an activating function inside the appending/removing Operator class (see the update in the question). The problem is solved! $\endgroup$ Aug 8 '20 at 6:41

Your Answer

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

Browse other questions tagged or ask your own question.