diff --git a/__init__.py b/__init__.py index c4fea96..8b6fdf7 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,3 @@ -bl_info = { - "name": "GP Tool Wheel", - "author": "Sietse Brouwer", - "version": (1, 0, 5), - "blender": (3, 0, 0), - "description": "Extended pie menu for selecting Grease Pencil tools quickly.", - "doc_url": "https://github.com/SietseB/GP-Tool-Wheel", - "tracker_url": "https://github.com/SietseB/GP-Tool-Wheel/issues", - "category": "3D View" -} - - if 'bpy' in locals(): import importlib importlib.reload(preferences) @@ -36,6 +24,9 @@ def addon_init(): # Assign hotkey to tool wheel operator preferences.assign_hotkey_to_tool_wheel() + # Add brush asset context menu item + preferences.add_brush_asset_context_menu_item() + # Load tool icons tool_data.tool_data.get_tool_icon_textures() @@ -50,6 +41,7 @@ def register(): bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_AssignHotkey) bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_SavePrefDefinition) bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_LoadPrefDefinition) + bpy.utils.register_class(preferences.GPENCIL_OT_link_brush_to_gp_tool_wheel) bpy.utils.register_class(tool_wheel_operator.GPENCIL_OT_tool_wheel) # Delayed inits @@ -65,11 +57,15 @@ def unregister(): bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_AssignHotkey) bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_SavePrefDefinition) bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_LoadPrefDefinition) + bpy.utils.unregister_class(preferences.GPENCIL_OT_link_brush_to_gp_tool_wheel) bpy.utils.unregister_class(tool_wheel_operator.GPENCIL_OT_tool_wheel) # Remove hotkey preferences.remove_hotkey_of_tool_wheel() + # Remove brush asset context menu item + preferences.remove_brush_asset_context_menu_item() + if __name__ == "__main__": register() diff --git a/__init__extension.py b/__init__extension.py new file mode 100644 index 0000000..8b6fdf7 --- /dev/null +++ b/__init__extension.py @@ -0,0 +1,71 @@ +if 'bpy' in locals(): + import importlib + importlib.reload(preferences) + importlib.reload(tool_wheel_operator) + importlib.reload(tool_data) +else: + from . import preferences + from . import tool_wheel_operator + from . import tool_data + +import bpy + + +# Inits +def addon_init(): + # Blender ready to assign hotkey? + if bpy.context.window_manager.keyconfigs.active is None: + # Keep interval timer alive + return 0.2 + + # Set default preferences (when needed) + preferences.set_default_preferences() + + # Assign hotkey to tool wheel operator + preferences.assign_hotkey_to_tool_wheel() + + # Add brush asset context menu item + preferences.add_brush_asset_context_menu_item() + + # Load tool icons + tool_data.tool_data.get_tool_icon_textures() + + +# Addon registration +def register(): + bpy.utils.register_class(preferences.GPToolWheel_PG_tool) + bpy.utils.register_class(preferences.GPToolWheel_PG_mode_order) + bpy.utils.register_class(preferences.GPToolWheelPreferences) + bpy.utils.register_class(preferences.GPTOOLWHEEL_UL_ModeList) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_MoveItem) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_AssignHotkey) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_SavePrefDefinition) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_LoadPrefDefinition) + bpy.utils.register_class(preferences.GPENCIL_OT_link_brush_to_gp_tool_wheel) + bpy.utils.register_class(tool_wheel_operator.GPENCIL_OT_tool_wheel) + + # Delayed inits + bpy.app.timers.register(addon_init, first_interval=0.2) + + +def unregister(): + bpy.utils.unregister_class(preferences.GPToolWheel_PG_tool) + bpy.utils.unregister_class(preferences.GPToolWheel_PG_mode_order) + bpy.utils.unregister_class(preferences.GPToolWheelPreferences) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_UL_ModeList) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_MoveItem) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_AssignHotkey) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_SavePrefDefinition) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_LoadPrefDefinition) + bpy.utils.unregister_class(preferences.GPENCIL_OT_link_brush_to_gp_tool_wheel) + bpy.utils.unregister_class(tool_wheel_operator.GPENCIL_OT_tool_wheel) + + # Remove hotkey + preferences.remove_hotkey_of_tool_wheel() + + # Remove brush asset context menu item + preferences.remove_brush_asset_context_menu_item() + + +if __name__ == "__main__": + register() diff --git a/__init__legacy.py b/__init__legacy.py new file mode 100644 index 0000000..ecdd2e9 --- /dev/null +++ b/__init__legacy.py @@ -0,0 +1,83 @@ +bl_info = { + "name": "GP Tool Wheel", + "author": "Sietse Brouwer", + "version": (1, 0, 6), + "blender": (3, 0, 0), + "description": "Extended pie menu for selecting Grease Pencil tools quickly.", + "doc_url": "https://github.com/SietseB/GP-Tool-Wheel", + "tracker_url": "https://github.com/SietseB/GP-Tool-Wheel/issues", + "category": "3D View" +} + + +if 'bpy' in locals(): + import importlib + importlib.reload(preferences) + importlib.reload(tool_wheel_operator) + importlib.reload(tool_data) +else: + from . import preferences + from . import tool_wheel_operator + from . import tool_data + +import bpy + + +# Inits +def addon_init(): + # Blender ready to assign hotkey? + if bpy.context.window_manager.keyconfigs.active is None: + # Keep interval timer alive + return 0.2 + + # Set default preferences (when needed) + preferences.set_default_preferences() + + # Assign hotkey to tool wheel operator + preferences.assign_hotkey_to_tool_wheel() + + # Add brush asset context menu item + preferences.add_brush_asset_context_menu_item() + + # Load tool icons + tool_data.tool_data.get_tool_icon_textures() + + +# Addon registration +def register(): + bpy.utils.register_class(preferences.GPToolWheel_PG_tool) + bpy.utils.register_class(preferences.GPToolWheel_PG_mode_order) + bpy.utils.register_class(preferences.GPToolWheelPreferences) + bpy.utils.register_class(preferences.GPTOOLWHEEL_UL_ModeList) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_MoveItem) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_AssignHotkey) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_SavePrefDefinition) + bpy.utils.register_class(preferences.GPTOOLWHEEL_OT_LoadPrefDefinition) + bpy.utils.register_class(preferences.GPENCIL_OT_link_brush_to_gp_tool_wheel) + bpy.utils.register_class(tool_wheel_operator.GPENCIL_OT_tool_wheel) + + # Delayed inits + bpy.app.timers.register(addon_init, first_interval=0.2) + + +def unregister(): + bpy.utils.unregister_class(preferences.GPToolWheel_PG_tool) + bpy.utils.unregister_class(preferences.GPToolWheel_PG_mode_order) + bpy.utils.unregister_class(preferences.GPToolWheelPreferences) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_UL_ModeList) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_MoveItem) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_AssignHotkey) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_SavePrefDefinition) + bpy.utils.unregister_class(preferences.GPTOOLWHEEL_OT_LoadPrefDefinition) + bpy.utils.unregister_class(preferences.GPENCIL_OT_link_brush_to_gp_tool_wheel) + bpy.utils.unregister_class(tool_wheel_operator.GPENCIL_OT_tool_wheel) + + # Remove hotkey + preferences.remove_hotkey_of_tool_wheel() + + # Remove brush asset context menu item + preferences.remove_brush_asset_context_menu_item() + + +if __name__ == "__main__": + register() diff --git a/blender_manifest.toml b/blender_manifest.toml index e45e6bc..c43b3f0 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -3,7 +3,7 @@ schema_version = "1.0.0" # Example of manifest file for a Blender extension # Change the values according to your extension id = "grease_pencil_tool_wheel" -version = "1.0.5" +version = "1.0.6" name = "GP Tool Wheel" tagline = "Extended pie menu for selecting Grease Pencil tools quickly" maintainer = "Sietse Brouwer " diff --git a/docs/images/link_brush_to_tool_1.jpg b/docs/images/link_brush_to_tool_1.jpg new file mode 100644 index 0000000..47880f0 Binary files /dev/null and b/docs/images/link_brush_to_tool_1.jpg differ diff --git a/docs/images/link_brush_to_tool_2.jpg b/docs/images/link_brush_to_tool_2.jpg new file mode 100644 index 0000000..2437ab9 Binary files /dev/null and b/docs/images/link_brush_to_tool_2.jpg differ diff --git a/preferences.py b/preferences.py index 25b7737..78d6848 100644 --- a/preferences.py +++ b/preferences.py @@ -8,7 +8,7 @@ import bpy from bpy_extras.io_utils import ExportHelper, ImportHelper -from bpy.props import BoolProperty, CollectionProperty, IntProperty, StringProperty +from bpy.props import BoolProperty, CollectionProperty, IntProperty, StringProperty, EnumProperty from bpy.types import AddonPreferences, Operator, PropertyGroup, UIList from .tool_data import tool_data as td @@ -158,7 +158,8 @@ def execute(self, context): prefs = context.preferences.addons[__package__].preferences tools = [] for tool in prefs.tools: - tools.append((tool.mode, tool.tool_index, tool.enabled)) + tools.append((tool.mode, tool.tool_index, tool.enabled, + tool.asset_lib_type, tool.asset_lib_id, tool.asset_id)) mode_order = [] for mode in prefs.mode_order: mode_order.append((mode.name, mode.order, mode.mode)) @@ -214,7 +215,13 @@ def execute(self, context): prefs.tools.clear() for tool in data['tools']: pref = prefs.tools.add() - pref.mode, pref.tool_index, pref.enabled = tool + if len(tool) == 3: + # Before version 4.3 + pref.mode, pref.tool_index, pref.enabled = tool + else: + # From version 4.3 on + pref.mode, pref.tool_index, pref.enabled, pref.asset_lib_type, pref.asset_lib_id, pref.asset_id = tool + prefs.mode_order.clear() for mode in data['mode_order']: pref = prefs.mode_order.add() @@ -236,6 +243,9 @@ def execute(self, context): # Set text in operator button td.key_button_text = kmi.to_string() + # Fill empty preferences with default values + set_default_preferences() + context.preferences.is_dirty = True self.report({'INFO'}, f'Preference Definition loaded from {self.filepath}') @@ -260,6 +270,9 @@ def on_pref_change(self, context): mode: StringProperty() tool_index: IntProperty() enabled: BoolProperty(update=on_pref_change) + asset_lib_type: StringProperty(update=on_pref_change) + asset_lib_id: StringProperty(update=on_pref_change) + asset_id: StringProperty(update=on_pref_change) # GP Tool Wheel preferences @@ -284,8 +297,20 @@ class GPToolWheelPreferences(AddonPreferences): def draw(self, _): layout = self.layout td.get_active_modes_and_tools() + use_brush_assets = (bpy.app.version >= (4, 3, 0)) + + # Save preference definition + box = layout.box() + col = box.column(align=True) + row = col.row() + row.operator('gp_tool_wheel.save_pref_definition') + row.operator('gp_tool_wheel.load_pref_definition') + col.separator(factor=1.5) + col.label(text='You can save and load the GP Tool Wheel preferences for backup purposes or', icon='FILE_TICK') + col.label(text='for distribution to other Blender installations.') # Keyboard shortcut + layout.separator(factor=0) box = layout.box() row = box.row() row.label(text='Keyboard shortcut for GP Tool Wheel:') @@ -337,18 +362,51 @@ def draw(self, _): grid = box.grid_flow(row_major=True, columns=3, even_columns=True) col = grid.column() col.label(text=td.tools_per_mode[mode]['name']) - for tool in td.tools_per_mode[mode]['tools']: - col.prop(tool['pref'], 'enabled', text=tool['name']) + for index, tool in enumerate(td.tools_per_mode[mode]['tools']): + pref = get_tool_preference(mode, index) + name = tool['as_asset']['name'] if use_brush_assets and 'as_asset' in tool and 'name' in tool['as_asset'] \ + else tool['name'] + col.prop(pref, 'enabled', text=name) + + # Brush assets + if bpy.app.version < (4, 3, 0): + return - # Save preference definition - layout.separator(factor=0) box = layout.box() + box.label(text='Brush Assets') col = box.column(align=True) - row = col.row() - row.operator('gp_tool_wheel.save_pref_definition') - row.operator('gp_tool_wheel.load_pref_definition') - col.separator(factor=1.5) - col.label(text='You can save and load the GP Tool Wheel preferences for distribution to other Blender installations.') + col.separator(factor=1) + col.label(text='Tip: instead of making manual changes here, you can right-click on a brush in', icon='BRUSHES_ALL') + col.label(text="the brush asset shelf and choose 'Set as Tool in GP Tool Wheel...'.") + + col = box.column() + col.separator(factor=1.0) + col.label(text='Draw Mode') + sub = col.column() + + tool = td.tools_per_mode['draw']['tools'][td.tint_tool_index] + row = sub.row().split(factor=0.25) + row.label(text='') + row.column().label(text=tool['name']) + pref = get_tool_preference('draw', td.tint_tool_index) + sub.prop(pref, 'asset_lib_type', text='Library Type') + sub.prop(pref, 'asset_lib_id', text='Library') + sub.prop(pref, 'asset_id', text='Asset') + + col = box.column() + col.separator(factor=4) + col.label(text='Sculpt Mode') + sub = col.column() + + for index, tool in enumerate(td.tools_per_mode['sculpt']['tools']): + pref = get_tool_preference('sculpt', index) + row = sub.row().split(factor=0.25) + row.label(text='') + row.column().label(text=tool['name']) + sub.prop(pref, 'asset_lib_type', text='Library Type') + sub.prop(pref, 'asset_lib_id', text='Library') + sub.prop(pref, 'asset_id', text='Asset') + sub.separator(factor=1.0) # When not set already, set default tool preferences @@ -366,6 +424,17 @@ def set_default_preferences(): pref.tool_index = i pref.enabled = tool['default'] + # When brush asset data is not set, add it + for mode in td.modes: + for i, tool in enumerate(td.tools_per_mode[mode]['tools']): + if 'as_asset' in tool and tool['as_asset']['tool'] == '': + pref = get_tool_preference(mode, i) + asset = tool['as_asset'] + if pref.asset_lib_type == '': + pref.asset_lib_type = asset['asset_library_type'] + pref.asset_lib_id = asset['asset_library_identifier'] + pref.asset_id = asset['relative_asset_identifier'] + # When there is no mode order, add it mode_order = addon_prefs.mode_order if not mode_order: @@ -385,6 +454,15 @@ def set_default_preferences(): addon_prefs.kmi_oskey = False +# Get tool preference (by mode and tool index) +def get_tool_preference(mode, index): + prefs = bpy.context.preferences.addons[__package__].preferences + for pref in prefs.tools: + if pref.mode == mode and pref.tool_index == index: + return pref + return None + + # Get the tool preferences def get_tool_preferences(): # Get tool preferences @@ -401,6 +479,12 @@ def get_tool_preferences(): tool['enabled'] = pref.enabled tool['pref'] = pref + if 'as_asset' in tool and tool['as_asset']['tool'] == '' and pref.asset_id: + asset = tool['as_asset'] + asset['asset_library_type'] = pref.asset_lib_type + asset['asset_library_identifier'] = pref.asset_lib_id + asset['relative_asset_identifier'] = pref.asset_id + # Check for new tools, not yet set in list for mode in td.modes: for i, tool in enumerate(td.tools_per_mode[mode]['tools']): @@ -412,12 +496,135 @@ def get_tool_preferences(): tool['enabled'] = pref.enabled tool['pref'] = pref + if 'as_asset' in tool and tool['as_asset']['tool'] == '' and pref.asset_id: + asset = tool['as_asset'] + asset['asset_library_type'] = pref.asset_lib_type + asset['asset_library_identifier'] = pref.asset_lib_id + asset['relative_asset_identifier'] = pref.asset_id + # Get mode order td.mode_order = [] for pref in prefs.mode_order: td.mode_order.append(pref.order) +class GPENCIL_OT_link_brush_to_gp_tool_wheel(Operator): + '''Link current brush asset to a tool in the GP Tool Wheel''' + bl_idname = "gpencil.link_brush_to_gp_tool_wheel" + bl_label = "Set Brush in GP Tool Wheel" + bl_options = {'REGISTER', 'UNDO'} + + brush_items_draw: EnumProperty(items=[('0', 'Tint', '')], name='Tool') + brush_items_sculpt: EnumProperty(items=[ + ('0', 'Smooth', ''), + ('1', 'Thickness', ''), + ('2', 'Strength', ''), + ('3', 'Randomize', ''), + ('4', 'Grab', ''), + ('5', 'Pull', ''), + ('6', 'Twist', ''), + ('7', 'Pinch', ''), + ('8', 'Clone', ''), + ], name='Tool') + + sculpt_tools = ['smooth', 'thickness', 'strength', 'randomize', 'grab', 'pull', 'twist', 'pinch', 'clone'] + brush_name = '' + + @classmethod + def poll(cls, context): + if context.mode not in ['PAINT_GREASE_PENCIL', 'SCULPT_GREASE_PENCIL']: + return False + + brush_asset = None + if context.mode == 'PAINT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_paint.brush_asset_reference + elif context.mode == 'SCULPT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_sculpt_paint.brush_asset_reference + return brush_asset is not None + + def invoke(self, context, _): + # Get brush asset name (part after last / in relative_asset_identifier) + brush_asset = None + if context.mode == 'PAINT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_paint.brush_asset_reference + elif context.mode == 'SCULPT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_sculpt_paint.brush_asset_reference + brush_name = brush_asset.relative_asset_identifier + brush_name = brush_name[brush_name.rfind('/') + 1:] + self.brush_name = brush_name + + # And make a tool guess, based on the brush name + if context.mode == 'SCULPT_GREASE_PENCIL': + brush_name_parts = brush_name.lower().split() + for part in brush_name_parts: + if part and part in self.sculpt_tools: + tool_index = self.sculpt_tools.index(part) + self.brush_items_sculpt = str(tool_index) + break + + # Invoke dialog + return context.window_manager.invoke_props_dialog(self, width=240) + + def draw(self, context): + layout = self.layout + layout.label(text=f"Use brush '{self.brush_name}' for tool:") + if context.mode == 'PAINT_GREASE_PENCIL': + layout.props_enum(self, 'brush_items_draw') + if context.mode == 'SCULPT_GREASE_PENCIL': + layout.props_enum(self, 'brush_items_sculpt') + layout.label(text="in the GP Tool Wheel.") + + def execute(self, context): + wheel_tool_asset = None + mode = '' + index = 0 + if context.mode == 'PAINT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_paint.brush_asset_reference + wheel_tool_asset = td.tools_per_mode['draw']['tools'][td.tint_tool_index]['as_asset'] + mode = 'draw' + index = td.tint_tool_index + if context.mode == 'SCULPT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_sculpt_paint.brush_asset_reference + wheel_tool_asset = td.tools_per_mode['sculpt']['tools'][int(self.brush_items_sculpt)]['as_asset'] + mode = 'sculpt' + index = int(self.brush_items_sculpt) + + if wheel_tool_asset is not None: + wheel_tool_asset['asset_library_type'] = brush_asset.asset_library_type + wheel_tool_asset['asset_library_identifier'] = brush_asset.asset_library_identifier + wheel_tool_asset['relative_asset_identifier'] = brush_asset.relative_asset_identifier + + # Update tool in addon preferences + pref = get_tool_preference(mode, index) + pref.asset_lib_type = brush_asset.asset_library_type + pref.asset_lib_id = brush_asset.asset_library_identifier + pref.asset_id = brush_asset.relative_asset_identifier + context.preferences.is_dirty = True + + return {'FINISHED'} + + +def brush_asset_context_menu_draw(self, _): + layout = self.layout + layout.separator() + layout.operator("gpencil.link_brush_to_gp_tool_wheel", text="Set as Tool in GP Tool Wheel...") + + +# Add operator to brush asset context menu for linking a brush asset to a tool in the tool wheel +def add_brush_asset_context_menu_item(): + if bpy.app.version >= (4, 3, 0): + if hasattr(bpy.types.VIEW3D_MT_brush_context_menu.draw, '_draw_funcs'): + if brush_asset_context_menu_draw in bpy.types.VIEW3D_MT_brush_context_menu.draw._draw_funcs: + return + bpy.types.VIEW3D_MT_brush_context_menu.append(brush_asset_context_menu_draw) + + +# Remove operator from brush asset context menu +def remove_brush_asset_context_menu_item(): + if bpy.app.version >= (4, 3, 0): + bpy.types.VIEW3D_MT_brush_context_menu.remove(brush_asset_context_menu_draw) + + # Get show hints preference settings def get_show_hints(): return bpy.context.preferences.addons[__package__].preferences.show_hints diff --git a/readme.md b/readme.md index ddbf671..d5d3323 100644 --- a/readme.md +++ b/readme.md @@ -23,11 +23,12 @@ You can customize your wheel in the add-on preferences. Up to Blender 4.1, go to `Edit` > `Preferences...` > `Add-ons` and look for `3D View: GP Tool Wheel`. -For Blender 4.2 and higher, go to `Edit` > `Preferences...` > `Extensions` and look for `GP Tool Wheel`. +For Blender 4.2 and higher, go to `Edit` > `Preferences...` > `Add-ons` and look for `GP Tool Wheel`. Click on the arrow on the left. -Here you can compose your ideal, tailor made wheel. Perhaps you want to change it in a kind of 'Quick favorites' menu, with only the modes and tools you often use. Or assign Tab as shortcut, replacing the default pie menu. +Here you can compose your ideal, tailor made wheel. Perhaps you want to change it in a kind of 'Quick favorites' menu, with only the modes and tools you often use. +Or assign Tab as shortcut, replacing the default pie menu. > **Circle of GP life:** defining the order of modes @@ -44,10 +45,31 @@ Here you can compose your ideal, tailor made wheel. Perhaps you want to change i > ![Save and load preferences to/from definition file](docs/images/gp_tool_wheel_preferences_3.png) +## Link brush assets (new in Blender 4.3) + +Blender 4.3 ships with the new Brush Asset Shelf, with default brushes and the option to define custom brushes yourself. +For Grease Pencil, the Brush Shelf is active in Draw and Sculpt Mode. +By default, GP Tool Wheel uses the _Essential_ brush assets that ship with Blender. + +But you can assign a custom brush to a tool in the wheel. Right-click on the brush and choose _Set as Tool in GP Tool Wheel..._ + +![Brush asset context menu with link brush to GP Tool Wheel](docs/images/link_brush_to_tool_1.jpg) + +In the dialog that follows, select the tool in the GP Tool Wheel and click OK. + +![Link brush to a tool in the GP Tool Wheel](docs/images/link_brush_to_tool_2.jpg) + +Now this brush will be activated when you click on it in the wheel. + + ## Installation GP Tool Wheel is suited for Blender 3.0 and higher. -Up to Blender 4.1, installation of the add-on is done in the usual Blender way: +For Blender 4.2 and higher, the add-on is [available as an extension](https://extensions.blender.org/add-ons/grease-pencil-tool-wheel/) at the Blender Extension platform. +Click on the big blue 'Get Add-on' button and follow the instructions. + + +Up to Blender 4.1 (or when you don't want to use the Extension platform), installation of the add-on is done in the usual Blender 'legacy' way: - Download [the latest release](https://github.com/SietseB/GP-Tool-Wheel/releases). (Make sure it is a zip file, not automatically unzipped.) - In Blender, go to `Edit` > `Preferences...` > `Add-ons`. Click on `Install...` and select the zip file. - When the stars are in your favour, the add-on appears. Activate it. @@ -59,7 +81,10 @@ Up to Blender 4.1, installation of the add-on is done in the usual Blender way: ## Changelog -- v1.0.5 - 2024-05-23 +- v1.0.6 – 2024-11-01 + - Adapted for Blender 4.3, with the new Brush Asset Shelf + - New feature: Assign a custom brush in the Asset Shelf to a tool in the GP Tool Wheel +- v1.0.5 – 2024-05-23 - Now uses UI scale: bigger wheel when UI scale is higher - Support for Grease Pencil v3 - Save and load preferences to a custom file path diff --git a/tool_data.py b/tool_data.py index b0f90d5..2ce42f8 100644 --- a/tool_data.py +++ b/tool_data.py @@ -40,6 +40,17 @@ def __init__(self): [0, 1, 2, 3, 5], [0, 1, 2, 3, 4, 5], ] + # Non-draw brush assets in Draw mode + self.non_draw_assets = [ + '/Fill', + '/Tint', + '/Eraser', + ] + # Tool index for Draw tool + self.draw_tool_index = 0 + self.tint_tool_index = 3 + + # Available tools per mode self.tools_per_mode = {} s = self.tools_per_mode s['weight'] = { @@ -49,11 +60,36 @@ def __init__(self): 'modev3': 'WEIGHT_GREASE_PENCIL', 'active_tools': [], 'tools': [ - {'name': 'Weight', 'tool': 'builtin_brush.Weight', 'icon': 'weight_paint_weight', 'default': True}, - {'name': 'Blur', 'tool': 'builtin_brush.Blur', 'icon': 'vertex_paint_blur', 'default': True}, - {'name': 'Average', 'tool': 'builtin_brush.Average', 'icon': 'vertex_paint_average', 'default': True}, - {'name': 'Smear', 'tool': 'builtin_brush.Smear', 'icon': 'vertex_paint_smear', 'default': True}, - {'name': 'Gradient', 'tool': 'builtin.gradient', 'icon': 'weight_paint_gradient', 'default': False}, + {'name': 'Weight', 'tool': 'builtin_brush.Weight', 'icon': 'weight_paint_weight', 'default': True, 'as_asset': { + 'tool': 'builtin.brush', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Blur', 'tool': 'builtin_brush.Blur', 'icon': 'vertex_paint_blur', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.blur', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Average', 'tool': 'builtin_brush.Average', 'icon': 'vertex_paint_average', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.average', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Smear', 'tool': 'builtin_brush.Smear', 'icon': 'vertex_paint_smear', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.smear', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Gradient', 'tool': 'builtin_brush.Gradient', 'icon': 'weight_paint_gradient', 'default': False, 'as_asset': { + 'tool': 'builtin_brush.gradient', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, ] } s['draw'] = { @@ -63,19 +99,85 @@ def __init__(self): 'modev3': 'PAINT_GREASE_PENCIL', 'active_tools': [], 'tools': [ - {'name': 'Draw', 'tool': 'builtin_brush.Draw', 'icon': 'draw_draw', 'default': True}, - {'name': 'Fill', 'tool': 'builtin_brush.Fill', 'icon': 'draw_fill', 'default': True}, - {'name': 'Erase', 'tool': 'builtin_brush.Erase', 'icon': 'draw_erase', 'default': True}, - {'name': 'Tint', 'tool': 'builtin_brush.Tint', 'icon': 'draw_tint', 'default': True}, - {'name': 'Cutter', 'tool': 'builtin.cutter', 'icon': 'draw_cutter', 'default': True}, - {'name': 'Eyedropper', 'tool': 'builtin.eyedropper', 'icon': 'draw_eyedropper', 'default': True}, - {'name': 'Line', 'tool': 'builtin.line', 'icon': 'draw_line', 'default': True}, - {'name': 'Polyline', 'tool': 'builtin.polyline', 'icon': 'draw_polyline', 'default': True}, - {'name': 'Arc', 'tool': 'builtin.arc', 'icon': 'draw_arc', 'default': True}, - {'name': 'Curve', 'tool': 'builtin.curve', 'icon': 'draw_curve', 'default': True}, - {'name': 'Box', 'tool': 'builtin.box', 'icon': 'draw_box', 'default': True}, - {'name': 'Circle', 'tool': 'builtin.circle', 'icon': 'draw_circle', 'default': True}, - {'name': 'Interpolate', 'tool': 'builtin.interpolate', 'icon': 'edit_interpolate', 'default': False}, + {'name': 'Draw', 'tool': 'builtin_brush.Draw', 'icon': 'draw_draw', 'default': True, 'as_asset': { + 'tool': 'builtin.brush', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_draw.blend/Brush/Pencil' + }}, + {'name': 'Fill', 'tool': 'builtin_brush.Fill', 'icon': 'draw_fill', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.Fill', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Erase', 'tool': 'builtin_brush.Erase', 'icon': 'draw_erase', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.Erase', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Tint', 'tool': 'builtin_brush.Tint', 'icon': 'draw_tint', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_draw.blend/Brush/Tint' + }}, + {'name': 'Cutter', 'tool': 'builtin.cutter', 'icon': 'draw_cutter', 'default': True, 'as_asset': { + 'tool': 'builtin.trim', + 'name': 'Trim', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Eyedropper', 'tool': 'builtin.eyedropper', 'icon': 'draw_eyedropper', 'default': True, 'as_asset': { + 'tool': 'builtin.eyedropper', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Line', 'tool': 'builtin.line', 'icon': 'draw_line', 'default': True, 'as_asset': { + 'tool': 'builtin.line', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Polyline', 'tool': 'builtin.polyline', 'icon': 'draw_polyline', 'default': True, 'as_asset': { + 'tool': 'builtin.polyline', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Arc', 'tool': 'builtin.arc', 'icon': 'draw_arc', 'default': True, 'as_asset': { + 'tool': 'builtin.arc', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Curve', 'tool': 'builtin.curve', 'icon': 'draw_curve', 'default': True, 'as_asset': { + 'tool': 'builtin.curve', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Box', 'tool': 'builtin.box', 'icon': 'draw_box', 'default': True, 'as_asset': { + 'tool': 'builtin.box', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Circle', 'tool': 'builtin.circle', 'icon': 'draw_circle', 'default': True, 'as_asset': { + 'tool': 'builtin.circle', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Interpolate', 'tool': 'builtin.interpolate', 'icon': 'edit_interpolate', 'default': False, 'as_asset': { + 'tool': 'builtin.interpolate', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, ] } s['vertex'] = { @@ -85,11 +187,36 @@ def __init__(self): 'modev3': 'VERTEX_GREASE_PENCIL', 'active_tools': [], 'tools': [ - {'name': 'Draw', 'tool': 'builtin_brush.Draw', 'icon': 'vertex_paint_draw', 'default': True}, - {'name': 'Blur', 'tool': 'builtin_brush.Blur', 'icon': 'vertex_paint_blur', 'default': True}, - {'name': 'Average', 'tool': 'builtin_brush.Average', 'icon': 'vertex_paint_average', 'default': True}, - {'name': 'Smear', 'tool': 'builtin_brush.Smear', 'icon': 'vertex_paint_smear', 'default': True}, - {'name': 'Replace', 'tool': 'builtin_brush.Replace', 'icon': 'vertex_paint_replace', 'default': True}, + {'name': 'Draw', 'tool': 'builtin_brush.Draw', 'icon': 'vertex_paint_draw', 'default': True, 'as_asset': { + 'tool': 'builtin.brush', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Blur', 'tool': 'builtin_brush.Blur', 'icon': 'vertex_paint_blur', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.blur', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Average', 'tool': 'builtin_brush.Average', 'icon': 'vertex_paint_average', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.average', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Smear', 'tool': 'builtin_brush.Smear', 'icon': 'vertex_paint_smear', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.smear', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, + {'name': 'Replace', 'tool': 'builtin_brush.Replace', 'icon': 'vertex_paint_replace', 'default': True, 'as_asset': { + 'tool': 'builtin_brush.replace', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, ] } s['edit'] = { @@ -109,7 +236,14 @@ def __init__(self): {'name': 'Bend', 'tool': 'builtin.bend', 'icon': 'edit_bend', 'default': False}, {'name': 'Shear', 'tool': 'builtin.shear', 'icon': 'edit_shear', 'default': False}, {'name': 'Transform Fill', 'tool': 'builtin.transform_fill', - 'icon': 'edit_transform_fill', 'default': False}, + 'icon': 'edit_transform_fill', 'default': False, 'as_asset': { + 'tool': 'builtin.texture_gradient', + 'name': 'Gradient', + 'icon': 'weight_paint_gradient', + 'asset_library_type': '', + 'asset_library_identifier': '', + 'relative_asset_identifier': '' + }}, {'name': 'Interpolate', 'tool': 'builtin.interpolate', 'icon': 'edit_interpolate', 'default': True}, ] } @@ -120,15 +254,61 @@ def __init__(self): 'modev3': 'SCULPT_GREASE_PENCIL', 'active_tools': [], 'tools': [ - {'name': 'Smooth', 'tool': 'builtin_brush.Smooth', 'icon': 'sculpt_smooth', 'default': True}, - {'name': 'Thickness', 'tool': 'builtin_brush.Thickness', 'icon': 'sculpt_thickness', 'default': True}, - {'name': 'Strength', 'tool': 'builtin_brush.Strength', 'icon': 'sculpt_strength', 'default': True}, - {'name': 'Randomize', 'tool': 'builtin_brush.Randomize', 'icon': 'sculpt_randomize', 'default': True}, - {'name': 'Grab', 'tool': 'builtin_brush.Grab', 'icon': 'sculpt_grab', 'default': True}, - {'name': 'Push', 'tool': 'builtin_brush.Push', 'icon': 'sculpt_push', 'default': True}, - {'name': 'Twist', 'tool': 'builtin_brush.Twist', 'icon': 'sculpt_twist', 'default': False}, - {'name': 'Pinch', 'tool': 'builtin_brush.Pinch', 'icon': 'sculpt_pinch', 'default': False}, - {'name': 'Clone', 'tool': 'builtin_brush.Clone', 'icon': 'sculpt_clone', 'default': True}, + {'name': 'Smooth', 'tool': 'builtin_brush.Smooth', 'icon': 'sculpt_smooth', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Smooth' + }}, + {'name': 'Thickness', 'tool': 'builtin_brush.Thickness', 'icon': 'sculpt_thickness', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Thickness' + }}, + {'name': 'Strength', 'tool': 'builtin_brush.Strength', 'icon': 'sculpt_strength', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Strength' + }}, + {'name': 'Randomize', 'tool': 'builtin_brush.Randomize', 'icon': 'sculpt_randomize', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Randomize' + }}, + {'name': 'Grab', 'tool': 'builtin_brush.Grab', 'icon': 'sculpt_grab', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Grab' + }}, + {'name': 'Push', 'tool': 'builtin_brush.Push', 'icon': 'sculpt_push', 'default': True, 'as_asset': { + 'tool': '', + 'name': 'Pull', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Pull' + }}, + {'name': 'Twist', 'tool': 'builtin_brush.Twist', 'icon': 'sculpt_twist', 'default': False, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Twist' + }}, + {'name': 'Pinch', 'tool': 'builtin_brush.Pinch', 'icon': 'sculpt_pinch', 'default': False, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Pinch' + }}, + {'name': 'Clone', 'tool': 'builtin_brush.Clone', 'icon': 'sculpt_clone', 'default': True, 'as_asset': { + 'tool': '', + 'asset_library_type': 'ESSENTIALS', + 'asset_library_identifier': '', + 'relative_asset_identifier': 'brushes/essentials_brushes-gp_sculpt.blend/Brush/Clone' + }}, ] } s['object'] = { diff --git a/tool_wheel_draw.py b/tool_wheel_draw.py index b9c4c9b..10caf35 100644 --- a/tool_wheel_draw.py +++ b/tool_wheel_draw.py @@ -182,6 +182,15 @@ def prepare(self, event, area, context): if region.alignment == 'RIGHT' and region.width > right_padding: right_padding = region.width + # Get height of tool header and brush asset shelf + top_padding = 2 + bottom_padding = 2 + for region in area.regions: + if region.type in ['HEADER', 'TOOL_HEADER']: + top_padding += region.height + if region.type in ['ASSET_SHELF', 'ASSET_SHELF_HEADER']: + bottom_padding += region.height + # Make sure bounding box is within area bounds dx = 0 dy = 0 @@ -190,11 +199,11 @@ def prepare(self, event, area, context): max_x += dx if max_x > area.width - right_padding: dx = area.width - right_padding - max_x - if min_y < 3: - dy = 3 - min_y + if min_y < bottom_padding: + dy = bottom_padding - min_y max_y += dy - if max_y > area.height - 54: - dy = area.height - 54 - max_y + if max_y > area.height - top_padding: + dy = area.height - top_padding - max_y # Adjust xy of boxes if not within area bounds if dx != 0 or dy != 0: @@ -344,6 +353,7 @@ def draw(self, context): ipad = ToolButton.BUTTON_IMG_PADDING * ui_scale gpu.state.blend_set('ALPHA') self.active_mode = '' + use_brush_assets = (bpy.app.version >= (4, 3, 0)) # Get active mode, based on angle of mouse in the wheel dx = self.mouse_x - self.center_x @@ -367,7 +377,8 @@ def draw(self, context): active_box = box # Draw center wheel - draw_texture_2d(td.textures['inner_wheel'], (self.center_x - 24, self.center_y - 24), 48, 48) + draw_texture_2d(td.textures['inner_wheel'], (self.center_x - 24 * ui_scale, + self.center_y - 24 * ui_scale), 48 * ui_scale, 48 * ui_scale) # Iterate boxes (modes) active_tool = -1 @@ -399,7 +410,10 @@ def draw(self, context): gpu.matrix.pop() # Draw tool icon - texture = td.textures[tool['icon']] + icon = tool['icon'] + if use_brush_assets and 'as_asset' in tool and 'icon' in tool['as_asset']: + icon = tool['as_asset']['icon'] + texture = td.textures[icon] x = button.x + ipad y = button.y - button.h - ipad draw_texture_2d(texture, (x, y), button.w, button.h) @@ -429,34 +443,34 @@ def draw(self, context): # Draw dot on inner wheel if significant_angle: angle = math.radians(angle) - dx = self.center_x + math.cos(angle) * 19 - 4 - dy = self.center_y + math.sin(angle) * 19 - 4 - draw_texture_2d(td.textures['active_dot'], (dx, dy), 8, 8) + dx = self.center_x + math.cos(angle) * 19 * ui_scale - 4 * ui_scale + dy = self.center_y + math.sin(angle) * 19 * ui_scale - 4 * ui_scale + draw_texture_2d(td.textures['active_dot'], (dx, dy), 8 * ui_scale, 8 * ui_scale) # Draw dot on active box if active_box is not None: box = active_box match box.index: case 0: - dx = box.x + box.w + 2 - dy = box.y - box.h + 14 + dx = box.x + box.w + 2 * ui_scale + dy = box.y - box.h + 14 * ui_scale case 1: - dx = box.x + box.w * 0.5 - 5 - dy = box.y - box.h - 12 + dx = box.x + box.w * 0.5 - 5 * ui_scale + dy = box.y - box.h - 12 * ui_scale case 2: - dx = box.x - 12 - dy = box.y - box.h + 14 + dx = box.x - 12 * ui_scale + dy = box.y - box.h + 14 * ui_scale case 3: - dx = box.x - 12 - dy = box.y - 24 + dx = box.x - 12 * ui_scale + dy = box.y - 24 * ui_scale case 4: - dx = box.x + box.w * 0.5 - 5 - dy = box.y + 2 + dx = box.x + box.w * 0.5 - 5 * ui_scale + dy = box.y + 2 * ui_scale case 5: - dx = box.x + box.w + 2 - dy = box.y - 24 + dx = box.x + box.w + 2 * ui_scale + dy = box.y - 24 * ui_scale - draw_texture_2d(td.textures['active_dot'], (dx, dy), 10, 10) + draw_texture_2d(td.textures['active_dot'], (dx, dy), 10 * ui_scale, 10 * ui_scale) # Draw active mode or tool name as hint # Note: this must be done last, because blf messes with the alpha state @@ -477,7 +491,11 @@ def draw(self, context): if self.active_tool == -1: hint = td.tools_per_mode[self.active_mode]['name'] else: - hint = td.tools_per_mode[self.active_mode]['tools'][self.active_tool]['name'] + tool = td.tools_per_mode[self.active_mode]['tools'][self.active_tool] + if use_brush_assets and 'as_asset' in tool and 'name' in tool['as_asset']: + hint = tool['as_asset']['name'] + else: + hint = tool['name'] tw, _ = blf.dimensions(0, hint) tx = self.center_x - tw * 0.5 blf.color(0, self.text_color[0], self.text_color[1], self.text_color[2], 0.8) diff --git a/tool_wheel_operator.py b/tool_wheel_operator.py index aeb5567..49d0eb9 100644 --- a/tool_wheel_operator.py +++ b/tool_wheel_operator.py @@ -13,7 +13,7 @@ class GPENCIL_OT_tool_wheel(Operator): _draw_handle = None _brush_sizes = [0, 0, 0, 0, 0] - _unprojected_radius = [0, 0, 0, 0, 0] + _unprojected_radius = [0.0, 0.0, 0.0, 0.0, 0.0] tool_wheel = tool_wheel_draw.ToolWheel() @classmethod @@ -34,10 +34,32 @@ def switch_mode_and_tool(self, context, new_mode, new_tool): # Get Grease Pencil version is_gp_legacy = context.object.type == 'GPENCIL' - # Get selected mode + # From 4.3 on, use brush assets + use_brush_assets = (bpy.app.version >= (4, 3, 0)) + brush_asset = None + + # Get selected mode. mode = td.tools_per_mode[new_mode] - # Switch to mode + # Remember last used draw brush + if use_brush_assets and context.mode == 'PAINT_GREASE_PENCIL': + brush_asset = context.tool_settings.gpencil_paint.brush_asset_reference + if brush_asset is not None: + # When the brush asset is a fill, tint or eraser, we don't store it. + # We only want to remember real draw brushes. + is_draw_brush = True + for non_draw_brush in td.non_draw_assets: + if brush_asset.relative_asset_identifier.find(non_draw_brush) != -1: + is_draw_brush = False + break + + if is_draw_brush: + # Store the draw brush asset + td.tools_per_mode['draw']['tools'][td.draw_tool_index]['as_asset']['asset_library_type'] = brush_asset.asset_library_type + td.tools_per_mode['draw']['tools'][td.draw_tool_index]['as_asset']['asset_library_identifier'] = brush_asset.asset_library_identifier + td.tools_per_mode['draw']['tools'][td.draw_tool_index]['as_asset']['relative_asset_identifier'] = brush_asset.relative_asset_identifier + + # Switch to mode. if is_gp_legacy and mode['mode'] != context.mode: match(new_mode): case 'object': @@ -59,39 +81,69 @@ def switch_mode_and_tool(self, context, new_mode, new_tool): case 'edit': bpy.ops.object.mode_set(mode='EDIT') case 'sculpt': - bpy.ops.object.mode_set(mode='SCULPT_GPENCIL') + bpy.ops.object.mode_set(mode='SCULPT_GREASE_PENCIL') case 'draw': - bpy.ops.object.mode_set(mode='PAINT_GPENCIL') + bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL') case 'weight': - bpy.ops.object.mode_set(mode='WEIGHT_GPENCIL') + bpy.ops.object.mode_set(mode='WEIGHT_GREASE_PENCIL') case 'vertex': - bpy.ops.object.mode_set(mode='VERTEX_GPENCIL') + bpy.ops.object.mode_set(mode='VERTEX_GREASE_PENCIL') elif new_tool == -1: return {'CANCELLED'} - # Switch to tool - if new_tool != -1: - tool = td.tools_per_mode[new_mode]['tools'][new_tool]['tool'] - # Handle 'add' tools - if tool.startswith('add.'): - match tool: - case 'add.empty': - bpy.ops.object.empty_add(radius=0.1) - case 'add.bone': - bpy.ops.object.armature_add(radius=0.4) - case 'add.gp.stroke': - if is_gp_legacy: - bpy.ops.object.gpencil_add(type='STROKE') - else: - bpy.ops.object.grease_pencil_add(type='STROKE') - case 'add.gp.empty': - if is_gp_legacy: - bpy.ops.object.gpencil_add(type='EMPTY') - else: - bpy.ops.object.grease_pencil_add(type='EMPTY') - else: - # Switch to tool + # Get selected tool + if new_tool == -1: + return {'FINISHED'} + + tool = mode['tools'][new_tool]['tool'] + tool_as_asset = None + if use_brush_assets and 'as_asset' in mode['tools'][new_tool]: + tool_as_asset = mode['tools'][new_tool]['as_asset'] + + # Handle 'add' tools + if tool.startswith('add.'): + match tool: + case 'add.empty': + bpy.ops.object.empty_add(radius=0.1) + case 'add.bone': + bpy.ops.object.armature_add(radius=0.4) + case 'add.gp.stroke': + if is_gp_legacy: + bpy.ops.object.gpencil_add(type='STROKE') + else: + bpy.ops.object.grease_pencil_add(type='STROKE') + case 'add.gp.empty': + if is_gp_legacy: + bpy.ops.object.gpencil_add(type='EMPTY') + else: + bpy.ops.object.grease_pencil_add(type='EMPTY') + else: + # Switch to tool + if tool_as_asset is None: bpy.ops.wm.tool_set_by_id(name=tool) + else: + # From 4.3 on, use brush assets for drawing and sculpting and tools for all the others + use_asset = False if tool_as_asset['tool'] else True + + # Edge case: when switching from from a non-draw brush to the Draw tool, + # use the previously stored draw brush asset. + if new_mode == 'draw' and mode['tools'][new_tool]['name'] == 'Draw': + brush_asset = context.tool_settings.gpencil_paint.brush_asset_reference + if brush_asset is not None: + for non_draw_brush in td.non_draw_assets: + if brush_asset.relative_asset_identifier.find(non_draw_brush) != -1: + use_asset = True + break + + # Switch to the new tool or brush asset + if use_asset: + # Set brush asset + bpy.ops.brush.asset_activate(asset_library_type=tool_as_asset['asset_library_type'], + asset_library_identifier=tool_as_asset['asset_library_identifier'], + relative_asset_identifier=tool_as_asset['relative_asset_identifier']) + else: + # Set tool + bpy.ops.wm.tool_set_by_id(name=tool_as_asset['tool']) return {'FINISHED'}