How to make a custom Toolbar button have a custom function?

I cobbled together a UI panel menu to change object modes, I would like the buttons to be in the Tools palette on the left instead but I don’t see a way to make custom buttons that do custom functions instead of just changing modes



# colbed together from template and tweaks from
# https://blender.stackexchange.com/questions/167862/how-to-create-a-button-on-the-n-panel
# would like to get the buttons in one area instead of multiple functions
# but maybe thats how python works
# 
# would also like to put this into teh left hand tools section instead
# but for now it works

import bpy

class Change_Mode_Edit(bpy.types.Operator):
# class Change_Mode_Edit(bpy.types.Operator, mode_name='OBJECT'):
    """Tooltip"""
    bl_idname = "object.edit"
    bl_label = "Edit"

    # @classmethod
    # def poll(cls, context):
    #     obj = context.active_object
    #     return (obj is not None and obj.type == 'MESH')

    def execute(self, context):
        # Your code here wow!
        bpy.ops.object.mode_set(mode='EDIT')
        # bpy.ops.object.mode_set(mode=mode_name)
        return {'FINISHED'}

class Change_Mode_Object(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.object"
    bl_label = "Object"

    def execute(self, context):
        bpy.ops.object.mode_set(mode='OBJECT')
        return {'FINISHED'}

class Change_Mode_Vertex_Paint(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.vertexpaint"
    bl_label = "VertexPaint"

    def execute(self, context):
        bpy.ops.object.mode_set(mode='VERTEX_PAINT')
        return {'FINISHED'}

class Change_Mode_Vertex_Sculpt(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.sculpt"
    bl_label = "Sculpt"

    def execute(self, context):
        bpy.ops.object.mode_set(mode='SCULPT')
        return {'FINISHED'}

class Change_Mode_Texture_Paint(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.texturepaint"
    bl_label = "Texture Paint"

    def execute(self, context):
        bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
        return {'FINISHED'}
        
class Change_Mode_Weight_Paint(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.weightpaint"
    bl_label = "Weight Paint"

    def execute(self, context):
        bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
        return {'FINISHED'}



class Objects_Modes_Swapper_Z:
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    # bl_region_type = "TOOLS"
    bl_category = "Modes"
    bl_options = {"DEFAULT_CLOSED"}

    # def draw(self, context):
    #     layout = self.layout
    #     layout.label(text="This is the main panel.")
    #     row = layout.row()
        # row.operator("mesh.primitive_cube_add")
        # bpy.ops.object.mode_set(mode='EDIT')
        # bpy.ops.mesh.wireframe(use_boundary=True, use_even_offset=True, use_relative_offset=False, use_replace=True, thickness=0.01, offset=0.01, use_crease=False, crease_weight=0.01)
        


class HELLO_PT_World1(Objects_Modes_Swapper_Z, bpy.types.Panel):
    bl_idname = "HELLO_PT_World1"
    bl_label = "Panel 1"

    def draw(self, context):
        layout = self.layout
        layout.label(text="Object modes")
        row = layout.row()
        # row.operator("mesh.primitive_cube_add")
        # would like to simply use this function but its not working yet
        # row.operator(Change_Mode_Edit.bl_idname, 'EDIT')
        row.operator(Change_Mode_Object.bl_idname)
        row = layout.row()
        row.operator(Change_Mode_Edit.bl_idname)
        row = layout.row()
        row.operator(Change_Mode_Vertex_Paint.bl_idname)
        row = layout.row()
        row.operator(Change_Mode_Vertex_Sculpt.bl_idname)
        row = layout.row()
        row.operator(Change_Mode_Texture_Paint.bl_idname)
        row = layout.row()
        row.operator(Change_Mode_Weight_Paint.bl_idname)


def register():
    bpy.utils.register_class(HELLO_PT_World1)
    bpy.utils.register_class(Change_Mode_Object)
    bpy.utils.register_class(Change_Mode_Edit)
    bpy.utils.register_class(Change_Mode_Vertex_Paint)
    bpy.utils.register_class(Change_Mode_Vertex_Sculpt)
    bpy.utils.register_class(Change_Mode_Texture_Paint)
    bpy.utils.register_class(Change_Mode_Weight_Paint)
 
 
def unregister():
    bpy.utils.unregister_class(HELLO_PT_World1)
    bpy.utils.unregister_class(Change_Mode_Object)
    bpy.utils.unregister_class(Change_Mode_Edit)
    bpy.utils.unregister_class(Change_Mode_Vertex_Paint)
    bpy.utils.unregister_class(Change_Mode_Vertex_Sculpt)
    bpy.utils.unregister_class(Change_Mode_Texture_Paint)
    bpy.utils.unregister_class(Change_Mode_Weight_Paint)

if __name__ == "__main__":
    register()

this is the default template blender provides
I assume I need a custom somehow in the “view3d.select_circle”
I changed all to “view3d.move” and that did that
but where does “view3d” come from???

# This example adds an object mode tool to the toolbar.
# This is just the circle-select and lasso tools tool.
import bpy
from bpy.types import WorkSpaceTool


class MyTool(WorkSpaceTool):
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'OBJECT'

    # The prefix of the idname should be your add-on name.
    bl_idname = "my_template.my_circle_select"
    bl_label = "My Circle Select"
    bl_description = (
        "This is a tooltip\n"
        "with multiple lines"
    )
    bl_icon = "ops.generic.select_circle"
    bl_widget = None
    bl_keymap = (
        ("view3d.select_circle", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": [("wait_for_input", False)]}),
        ("view3d.select_circle", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
         {"properties": [("mode", 'SUB'), ("wait_for_input", False)]}),
    )

    def draw_settings(context, layout, tool):
        props = tool.operator_properties("view3d.select_circle")
        layout.prop(props, "mode")
        layout.prop(props, "radius")


class MyOtherTool(WorkSpaceTool):
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'OBJECT'

    bl_idname = "my_template.my_other_select"
    bl_label = "My Lasso Tool Select"
    bl_description = (
        "This is a tooltip\n"
        "with multiple lines"
    )
    bl_icon = "ops.generic.select_lasso"
    bl_widget = None
    bl_keymap = (
        ("view3d.select_lasso", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
        ("view3d.select_lasso", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
         {"properties": [("mode", 'SUB')]}),
    )

    def draw_settings(context, layout, tool):
        props = tool.operator_properties("view3d.select_lasso")
        layout.prop(props, "mode")


def register():
    bpy.utils.register_tool(MyTool, after={"builtin.scale_cage"}, separator=True, group=True)
    bpy.utils.register_tool(MyOtherTool, after={MyTool.bl_idname})


def unregister():
    bpy.utils.unregister_tool(MyTool)
    bpy.utils.unregister_tool(MyOtherTool)


if __name__ == "__main__":
    register()

and the attempt

# This example adds an object mode tool to the toolbar.
# This is just the circle-select and lasso tools tool.
import bpy
from bpy.types import WorkSpaceTool

class Change_Mode_Edit(bpy.types.Operator):
# class Change_Mode_Edit(bpy.types.Operator, mode_name='OBJECT'):
    """Tooltip"""
    bl_idname = "object.edit"
    bl_label = "Edit"

    # @classmethod
    # def poll(cls, context):
    #     obj = context.active_object
    #     return (obj is not None and obj.type == 'MESH')

    def execute(self, context):
        # Your code here wow!
        bpy.ops.object.mode_set(mode='EDIT')
        # bpy.ops.object.mode_set(mode=mode_name)
        return {'FINISHED'}


class MyTool(WorkSpaceTool):
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'OBJECT'

    # The prefix of the idname should be your add-on name.
    bl_idname = "my_template2.my_circle_select2"
    bl_label = "My Circle Select"
    bl_description = (
        "This is a tooltip\n"
        "with multiple lines"
    )
    bl_icon = "ops.generic.select_circle"
    bl_widget = None
    bl_keymap = (
        ("object.move", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": [("wait_for_input", False)]}),
        ("object.move", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
         {"properties": [("mode", 'SUB'), ("wait_for_input", False)]}),
    )

    def draw_settings(context, layout, tool):
        props = tool.operator_properties("object.move")
        layout.prop(props, "mode")
        layout.prop(props, "radius")



def register():
    bpy.utils.register_tool(MyTool, after={"builtin.scale_cage"}, separator=True, group=True)
    bpy.utils.register_class(Change_Mode_Edit)


def unregister():
    bpy.utils.unregister_tool(MyTool)
    bpy.utils.unregister_class(Change_Mode_Edit)


if __name__ == "__main__":
    register()

Hi bro , did you find a solution for this .

if anyone reading this now , can they please explain how to add our custom tool to the toolbar ?

If you just want to add a super simple button without a bunch of complicated stuff, here’s the simplest possible version:

# Adds super simple button to left-hand toolbar in 3D view to go into edit mode
# per forum post from a while ago. This is far simpler and easy to understand 
# than the one provided by Blender. As a result of the simplicity, it does not
# have the super cool draw formatting. It works just fine though.


import bpy


class ButtonsPanel(bpy.types.Panel):
    bl_label = "Tools"  # Not visible per bl_option to hide header below.
    bl_idname = "my.custom_panel_thing"  # This doesn't really do much in simpler contexts.
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'  # Make it go to the left, not the right, which would be 'UI'.
    bl_options = {'HIDE_HEADER'}  # Avoid drawing an annoying extra box around it.

    def draw(self, context):
        layout = self.layout
        column = layout.column()  # Setting this stuff up so we can size it to be square like the others.
        row = column.row()
        row.scale_y = 2  # So it is at least the right size, if nothing else.
        row.scale_x = 2  # So it is at least the right size, if nothing else.
        row.operator("my.change_to_edit_mode", icon='EDITMODE_HLT', text="")
        
    
class ChangeToEditModeOperator(bpy.types.Operator):
    bl_idname = "my.change_to_edit_mode"
    bl_label = "Switch to Edit Mode"
    bl_description = "Change to edit mode"

    def execute(self, context):
        active_object = context.active_object  # We can skip the bpy.context part since it's provided in the line above.
        if active_object.type == 'MESH':  # This avoids an error when Edit mode is not available for current object.
            bpy.ops.object.mode_set(mode='EDIT') # Set mode.
        return {'FINISHED'}


def register():
    bpy.utils.register_class(ButtonsPanel)
    bpy.utils.register_class(ChangeToEditModeOperator)
    
    
def unregister():
    bpy.utils.unregister_class(ButtonsPanel)
    bpy.utils.unregister_class(ChangeToEditModeOperator)
    
    
if __name__ == "__main__":  # Automatically call register function when we run from text editor.
    register()
    

That won’t use the fancy background draw method stuff that makes it look super cool and different from normal panel buttons, but it adds a button to the left hand toolbar without all the complicated, confusing stuffage.

1 Like