I'm trying to create my own WorkSpaceTool and give it a custom icon to distinguish it from the other tool icons. I've used bpy.utils.previews before to load icons for panel buttons - however, it doesn't seem to work for WorkSpaceTool - perhaps because WorkSpaceTool has it's icon set as a static class member.

Below is my attempt at assigning a custom icon based on bpy.utils.previews. Is there a way to get this to load an icon for my tool?

import bpy
import bpy.utils.previews
import os

preview_collections = {}

class SimpleOperator(bpy.types.Operator):
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    def __init__(self):
        print("construct SimpleOperator")

    def __del__(self):
        print("destruct SimpleOperator")

    def poll(cls, context):
        return context.active_object is not None

    def modal(self, context, event):
        print("modal evTyp:%s evVal:%s" % (str(event.type), str(event.value)))

        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
            # allow navigation
            return {'PASS_THROUGH'}
        elif event.type == 'MOUSEMOVE':
            return {'RUNNING_MODAL'}

        elif event.type == 'LEFTMOUSE':
            mouse_pos = (event.mouse_region_x, event.mouse_region_y)
            print("  pos %s" % str(mouse_pos))
            return {'RUNNING_MODAL'}

        elif event.type == 'RIGHTMOUSE':
            mouse_pos = (event.mouse_region_x, event.mouse_region_y)
            print("  pos %s" % str(mouse_pos))
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

#    def execute(self, context):
#        print("execute SimpleOperator")
#        return {'FINISHED'}

    def invoke(self, context, event):
        print("invoke evTyp:%s evVal:%s" % (str(event.type), str(event.value)))
        return {'RUNNING_MODAL'}


class MyTool(bpy.types.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 = preview_collections["main"]["uvBrush"].icon_id
#    bl_icon = "ops.generic.select_circle"
    bl_widget = None
    bl_keymap = (
        ("object.simple_operator", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": []}),

    def draw_settings(context, layout, tool):
#        pcol = preview_collections["main"]
#        self.bl_icon = pcol["uvBrush"].icon_id
        props = tool.operator_properties("view3d.select_circle")
        layout.prop(props, "mode")
        layout.prop(props, "radius")


def register():
    #Load icons
    icon_path = "../icons"
    if __name__ == "__main__":
        icon_path = "../../source/icons"
    icons_dir = os.path.join(os.path.dirname(__file__), icon_path)
#    print("icons dir: " + str(icons_dir))
    pcoll = bpy.utils.previews.new()
    pcoll.load("uvBrush", os.path.join(icons_dir, "uvBrush.png"), 'IMAGE')
    preview_collections["main"] = pcoll

    #Register tools
    bpy.utils.register_tool(MyTool, after={"builtin.scale_cage"}, separator=True, group=True)

def unregister():
    bpy.utils.unregister_tool(MyTool, after={"builtin.scale_cage"}, separator=True, group=True)

    #Unload icons
    for pcoll in preview_collections.values():

if __name__ == "__main__":

1 Answer 1


Unfortunately bpy.utils.previews does not work with WorkSpaceTool, you will have to create your icon as a binary blender file.

This is easiest done by first downloading the current blender icon resource file from SVN. Then use the most recent blender_icons_geom.py script to export the icons in the blender icon resource file by running the script from within Blender, the exported icons will either be next to the Blender executable or in the root of your home directory. You can then place the file in the resources of your addon and load it by giving bl_icon the direct path, excluding the .dat. If your icon file is called mytool.brush.grab.dat you will give the path as mytool.brush.grab.

import bpy
from pathlib import Path

class PaintArtistTool(bpy.types.WorkSpaceTool):
    bl_icon = (Path(__file__).parent / "icons" / "mytool.brush.grab").as_posix()

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .