diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 9d8a89dc55b..a1786e9fabf 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4791,6 +4791,34 @@ def _template_uv_select(*, type, value, select_passthrough, legacy): return items +def _template_mask_select(*, type, value, select_passthrough, legacy): + + # See: `use_tweak_select_passthrough` doc-string. + if select_passthrough and (value in {'CLICK', 'RELEASE'}): + select_passthrough = False + + items = [ + ("mask.select", {"type": type, "value": value}, + {"properties": [ + *((("deselect_all", True),) if not legacy else ()), + *((("select_passthrough", True),) if select_passthrough else ()), + ]}), + ("mask.select", {"type": type, "value": value, "shift": True}, + {"properties": [("toggle", True)]}), + ] + + if select_passthrough: + # Add an additional click item to de-select all other items, + # needed so pass-through is able to de-select other items. + items.append(( + "mask.select", + {"type": type, "value": 'CLICK'}, + {"properties": [("deselect_all", True)]}, + )) + + return items + + def _template_sequencer_generic_select(*, type, value, legacy): return [( "sequencer.select", @@ -6946,6 +6974,148 @@ def km_image_editor_tool_uv_scale(params): ) +# ------------------------------------------------------------------------------ +# Tool System (Mask Editor) + +def km_image_editor_tool_mask_cursor(params): + return ( + "Image Editor Tool: Mask, Cursor", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("mask.cursor_set", {"type": params.tool_mouse, "value": 'PRESS'}, None), + # Don't use `tool_maybe_tweak_event` since it conflicts with `PRESS` that places the cursor. + ("transform.translate", params.tool_tweak_event, + {"properties": [("release_confirm", True), ("cursor_transform", True)]}), + ]}, + ) + + +def km_image_editor_tool_mask_select(params, *, fallback): + return ( + _fallback_id("Image Editor Tool: Mask, Tweak", fallback), + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + *([] if (fallback and (params.select_mouse == 'RIGHTMOUSE')) else _template_items_tool_select( + params, "mask.select", "mask.cursor_set", fallback=fallback)), + *([] if params.use_fallback_tool_select_handled else + _template_mask_select( + type=params.select_mouse, + value=params.select_mouse_value, + select_passthrough=params.use_tweak_select_passthrough, + legacy=params.legacy, + )), + ]}, + ) + + +def km_image_editor_tool_mask_select_box(params, *, fallback): + return ( + _fallback_id("Image Editor Tool: Mask, Select Box", fallback), + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( + "mask.select_box", + # Don't use `tool_maybe_tweak_event`, see comment for this slot. + **(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else + params.tool_tweak_event))), + ]}, + ) + + +def km_image_editor_tool_mask_select_circle(params, *, fallback): + return ( + _fallback_id("Image Editor Tool: Mask, Select Circle", fallback), + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( + "mask.select_circle", + **(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else + {"type": params.tool_mouse, "value": 'PRESS'}), + properties=[("wait_for_input", False)])), + ]}, + ) + + +def km_image_editor_tool_mask_select_lasso(params, *, fallback): + return ( + _fallback_id("Image Editor Tool: Mask, Select Lasso", fallback), + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + + {"items": [ + *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( + "mask.select_lasso", + **(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else + params.tool_tweak_event))), + ]}, + ) + + +def km_image_editor_tool_mask_move(params): + return ( + "Image Editor Tool: Mask, Move", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("transform.translate", {**params.tool_maybe_tweak_event, **params.tool_modifier}, + {"properties": [("release_confirm", True)]}), + ]}, + ) + + +def km_image_editor_tool_mask_rotate(params): + return ( + "Image Editor Tool: Mask, Rotate", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("transform.rotate", {**params.tool_maybe_tweak_event, **params.tool_modifier}, + {"properties": [("release_confirm", True)]}), + ]}, + ) + + +def km_image_editor_tool_mask_scale(params): + return ( + "Image Editor Tool: Mask, Scale", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("transform.resize", {**params.tool_maybe_tweak_event, **params.tool_modifier}, + {"properties": [("release_confirm", True)]}), + ]}, + ) + + +def km_image_editor_tool_mask_transform(params): + return ( + "Image Editor Tool: Mask, Transform", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("transform.resize", {**params.tool_maybe_tweak_event, **params.tool_modifier}, + {"properties": [("release_confirm", True)]}), + ]}, + ) + + +def km_image_editor_tool_mask_primitive_square(params): + return ( + "Image Editor Tool: Mask, Box", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("mask.primitive_square_add", {"type": 'LEFTMOUSE', "value": 'PRESS'}, + {"properties": []}), + ]}, + ) + + +def km_image_editor_tool_mask_primitive_circle(params): + return ( + "Image Editor Tool: Mask, Circle", + {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + ("mask.primitive_circle_add", {"type": 'LEFTMOUSE', "value": 'PRESS'}, + {"properties": []}), + ]}, + ) + + # ------------------------------------------------------------------------------ # Tool System (Node Editor) @@ -8506,6 +8676,17 @@ def generate_keymaps(params=None): km_image_editor_tool_uv_move(params), km_image_editor_tool_uv_rotate(params), km_image_editor_tool_uv_scale(params), + km_image_editor_tool_mask_cursor(params), + *(km_image_editor_tool_mask_select(params, fallback=fallback) for fallback in (False, True)), + *(km_image_editor_tool_mask_select_box(params, fallback=fallback) for fallback in (False, True)), + *(km_image_editor_tool_mask_select_circle(params, fallback=fallback) for fallback in (False, True)), + *(km_image_editor_tool_mask_select_lasso(params, fallback=fallback) for fallback in (False, True)), + km_image_editor_tool_mask_move(params), + km_image_editor_tool_mask_rotate(params), + km_image_editor_tool_mask_scale(params), + km_image_editor_tool_mask_transform(params), + km_image_editor_tool_mask_primitive_circle(params), + km_image_editor_tool_mask_primitive_square(params), *(km_node_editor_tool_select(params, fallback=fallback) for fallback in (False, True)), *(km_node_editor_tool_select_box(params, fallback=fallback) for fallback in (False, True)), *(km_node_editor_tool_select_lasso(params, fallback=fallback) for fallback in (False, True)), diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index fc454b82b34..dbc0153426f 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2467,6 +2467,164 @@ class _defs_image_generic: ) +class _defs_image_mask_transform: + + @ToolDef.from_fn + def translate(): + return dict( + idname="builtin.move", + label="Move", + icon="ops.transform.translate", + widget="IMAGE_GGT_gizmo2d_translate", + operator="transform.translate", + keymap="Image Editor Tool: Mask, Move" + ) + + @ToolDef.from_fn + def rotate(): + return dict( + idname="builtin.rotate", + label="Rotate", + icon="ops.transform.rotate", + widget="IMAGE_GGT_gizmo2d_rotate", + operator="transform.rotate", + keymap="Image Editor Tool: Mask, Rotate", + ) + + @ToolDef.from_fn + def scale(): + return dict( + idname="builtin.scale", + label="Scale", + icon="ops.transform.resize", + widget="IMAGE_GGT_gizmo2d_resize", + operator="transform.resize", + keymap="Image Editor Tool: Mask, Scale", + ) + + @ToolDef.from_fn + def transform(): + return dict( + idname="builtin.transform", + label="Transform", + description=( + "Supports any combination of grab, rotate, and scale at once" + ), + icon="ops.transform.transform", + widget="IMAGE_GGT_gizmo2d", + # No keymap default action, only for gizmo! + ) + + +class _defs_image_mask_select: + + @ToolDef.from_fn + def select(): + return dict( + idname="builtin.select", + label="Tweak", + icon="ops.generic.select", + widget=None, + keymap=(), + ) + + @ToolDef.from_fn + def box(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("mask.select_box") + row = layout.row() + row.use_property_split = False + row.prop(props, "mode", text="", expand=True, icon_only=True) + + return dict( + idname="builtin.select_box", + label="Select Box", + icon="ops.generic.select_box", + widget=None, + keymap=(), + draw_settings=draw_settings, + ) + + @ToolDef.from_fn + def lasso(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("mask.select_lasso") + row = layout.row() + row.use_property_split = False + row.prop(props, "mode", text="", expand=True, icon_only=True) + + return dict( + idname="builtin.select_lasso", + label="Select Lasso", + icon="ops.generic.select_lasso", + widget=None, + keymap=(), + draw_settings=draw_settings, + ) + + @ToolDef.from_fn + def circle(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("mask.select_circle") + row = layout.row() + row.use_property_split = False + row.prop(props, "mode", text="", expand=True, icon_only=True) + layout.prop(props, "radius") + + def draw_cursor(_context, tool, xy): + from gpu_extras.presets import draw_circle_2d + props = tool.operator_properties("mask.select_circle") + radius = props.radius + draw_circle_2d(xy, (1.0,) * 4, radius, segments=32) + + return dict( + idname="builtin.select_circle", + label="Select Circle", + icon="ops.generic.select_circle", + widget=None, + keymap=(), + draw_settings=draw_settings, + draw_cursor=draw_cursor, + ) + + +class _defs_image_mask_primitive: + + @ToolDef.from_fn + def box(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("mask.primitive_square_add") + layout.prop(props, "size") + layout.prop(props, "location") + + return dict( + idname="builtin.box", + label="Box", + icon="ops.gpencil.primitive_box", + cursor='CROSSHAIR', + draw_settings=draw_settings, + widget=None, + keymap="Image Editor Tool: Mask, Box", + ) + + @ToolDef.from_fn + def circle(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("mask.primitive_circle_add") + layout.prop(props, "size") + layout.prop(props, "location") + + return dict( + idname="builtin.circle", + label="Circle", + icon="ops.gpencil.primitive_circle", + cursor='CROSSHAIR', + draw_settings=draw_settings, + widget=None, + keymap="Image Editor Tool: Mask, Circle", + ) + + class _defs_image_uv_transform: @ToolDef.from_fn @@ -3121,6 +3279,29 @@ class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel): ), ) + _tools_mask_transform = ( + _defs_image_mask_transform.translate, + _defs_image_mask_transform.rotate, + _defs_image_mask_transform.scale, + _defs_image_mask_transform.transform, + ) + + _tools_mask_select = ( + ( + _defs_image_mask_select.select, + _defs_image_mask_select.box, + _defs_image_mask_select.circle, + _defs_image_mask_select.lasso, + ), + ) + + _tools_mask_primitive = ( + ( + _defs_image_mask_primitive.circle, + _defs_image_mask_primitive.box, + ), + ) + _tools_annotate = ( ( _defs_annotate.scribble, @@ -3156,7 +3337,15 @@ class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_image_uv_sculpt.pinch, ], 'MASK': [ + *_tools_mask_select, + _defs_image_generic.cursor, None, + *_tools_mask_transform, + None, + *_tools_annotate, + None, + # TODO: Make interactive placement before adding primitive tools + # *_tools_mask_primitive, ], 'PAINT': [ _brush_tool, diff --git a/source/blender/editors/include/ED_image.hh b/source/blender/editors/include/ED_image.hh index b97fa3a7de2..1f37315a6cb 100644 --- a/source/blender/editors/include/ED_image.hh +++ b/source/blender/editors/include/ED_image.hh @@ -112,6 +112,7 @@ bool ED_image_slot_cycle(Image *image, int direction); bool ED_space_image_show_render(const SpaceImage *sima); bool ED_space_image_show_paint(const SpaceImage *sima); +bool ED_space_image_show_mask(const SpaceImage *sima); bool ED_space_image_show_uvedit(const SpaceImage *sima, Object *obedit); bool ED_space_image_paint_curve(const bContext *C); diff --git a/source/blender/editors/include/ED_mask.hh b/source/blender/editors/include/ED_mask.hh index c1b33df3e26..5763fc388be 100644 --- a/source/blender/editors/include/ED_mask.hh +++ b/source/blender/editors/include/ED_mask.hh @@ -90,6 +90,9 @@ bool ED_mask_selected_minmax(const bContext *C, float max[2], bool handles_as_control_point); +void ED_mask_center_from_pivot_ex( + const bContext *C, ScrArea *area, float r_center[2], char mode, bool *r_has_select); + /* `mask_draw.cc` */ /** diff --git a/source/blender/editors/include/ED_uvedit.hh b/source/blender/editors/include/ED_uvedit.hh index 2fcd011de73..8e225aba179 100644 --- a/source/blender/editors/include/ED_uvedit.hh +++ b/source/blender/editors/include/ED_uvedit.hh @@ -64,7 +64,7 @@ bool ED_uvedit_center_multi(const Scene *scene, float r_cent[2], char mode); -bool ED_uvedit_center_from_pivot_ex(SpaceImage *sima, +bool ED_uvedit_center_from_pivot_ex(const SpaceImage *sima, Scene *scene, ViewLayer *view_layer, float r_center[2], diff --git a/source/blender/editors/mask/mask_query.cc b/source/blender/editors/mask/mask_query.cc index 30415967875..7e7daff4555 100644 --- a/source/blender/editors/mask/mask_query.cc +++ b/source/blender/editors/mask/mask_query.cc @@ -673,6 +673,25 @@ bool ED_mask_selected_minmax(const bContext *C, return ok; } +void ED_mask_center_from_pivot_ex( + const bContext *C, ScrArea *area, float r_center[2], char mode, bool *r_has_select) +{ + float min[2], max[2]; + const bool mask_selected = ED_mask_selected_minmax(C, min, max, false); + + switch (mode) { + case V3D_AROUND_CURSOR: + ED_mask_cursor_location_get(area, r_center); + break; + default: + mid_v2_v2v2(r_center, min, max); + break; + } + if (r_has_select != nullptr) { + *r_has_select = mask_selected; + } +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/space_image/image_edit.cc b/source/blender/editors/space_image/image_edit.cc index 8038e7176de..12e3a77479c 100644 --- a/source/blender/editors/space_image/image_edit.cc +++ b/source/blender/editors/space_image/image_edit.cc @@ -458,6 +458,15 @@ bool ED_space_image_show_paint(const SpaceImage *sima) return (sima->mode == SI_MODE_PAINT); } +bool ED_space_image_show_mask(const SpaceImage *sima) +{ + if (ED_space_image_show_render(sima)) { + return false; + } + + return (sima->mode == SI_MODE_MASK); +} + bool ED_space_image_show_uvedit(const SpaceImage *sima, Object *obedit) { if (sima) { diff --git a/source/blender/editors/space_image/space_image.cc b/source/blender/editors/space_image/space_image.cc index 094f4d72159..c0563f02189 100644 --- a/source/blender/editors/space_image/space_image.cc +++ b/source/blender/editors/space_image/space_image.cc @@ -805,6 +805,11 @@ static void image_main_region_listener(const wmRegionListenerParams *params) } WM_gizmomap_tag_refresh(region->runtime->gizmo_map); break; + case NC_MASK: + if (ELEM(wmn->data, ND_DATA, ND_SELECT)) { + WM_gizmomap_tag_refresh(region->runtime->gizmo_map); + } + break; case NC_MATERIAL: if (wmn->data == ND_SHADING_LINKS) { SpaceImage *sima = static_cast(area->spacedata.first); diff --git a/source/blender/editors/transform/transform_gizmo_2d.cc b/source/blender/editors/transform/transform_gizmo_2d.cc index 4b1dc56cbd2..3686cfbeafc 100644 --- a/source/blender/editors/transform/transform_gizmo_2d.cc +++ b/source/blender/editors/transform/transform_gizmo_2d.cc @@ -39,6 +39,7 @@ #include "ED_gizmo_library.hh" #include "ED_gizmo_utils.hh" #include "ED_image.hh" +#include "ED_mask.hh" #include "ED_screen.hh" #include "ED_uvedit.hh" @@ -81,7 +82,7 @@ static bool gizmo2d_generic_poll(const bContext *C, wmGizmoGroupType *gzgt) case SPACE_IMAGE: { const SpaceImage *sima = static_cast(area->spacedata.first); Object *obedit = CTX_data_edit_object(C); - if (!ED_space_image_show_uvedit(sima, obedit)) { + if (!(ED_space_image_show_uvedit(sima, obedit) || ED_space_image_show_mask(sima))) { return false; } break; @@ -240,12 +241,27 @@ static bool gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min ScrArea *area = CTX_wm_area(C); bool has_select = false; if (area->spacetype == SPACE_IMAGE) { - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - Vector objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( - scene, view_layer, nullptr); - if (ED_uvedit_minmax_multi(scene, objects, r_min, r_max)) { - has_select = true; + const SpaceImage *sima = static_cast(area->spacedata.first); + switch (sima->mode) { + case SI_MODE_UV: { + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Vector objects = + BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( + scene, view_layer, nullptr); + if (ED_uvedit_minmax_multi(scene, objects, r_min, r_max)) { + has_select = true; + } + break; + } + case SI_MODE_MASK: { + if (ED_mask_selected_minmax(C, r_min, r_max, false)) { + has_select = true; + } + break; + } + default: + break; } } else if (area->spacetype == SPACE_SEQ) { @@ -373,9 +389,19 @@ static bool gizmo2d_calc_transform_pivot(const bContext *C, float r_pivot[2]) bool has_select = false; if (area->spacetype == SPACE_IMAGE) { - SpaceImage *sima = static_cast(area->spacedata.first); + const SpaceImage *sima = static_cast(area->spacedata.first); ViewLayer *view_layer = CTX_data_view_layer(C); - ED_uvedit_center_from_pivot_ex(sima, scene, view_layer, r_pivot, sima->around, &has_select); + switch (sima->mode) { + case SI_MODE_UV: + ED_uvedit_center_from_pivot_ex( + sima, scene, view_layer, r_pivot, sima->around, &has_select); + break; + case SI_MODE_MASK: + ED_mask_center_from_pivot_ex(C, area, r_pivot, sima->around, &has_select); + break; + default: + break; + } } else if (area->spacetype == SPACE_SEQ) { SpaceSeq *sseq = static_cast(area->spacedata.first); diff --git a/source/blender/editors/uvedit/uvedit_ops.cc b/source/blender/editors/uvedit/uvedit_ops.cc index 21ca815fe87..6c5b5aa3899 100644 --- a/source/blender/editors/uvedit/uvedit_ops.cc +++ b/source/blender/editors/uvedit/uvedit_ops.cc @@ -309,7 +309,7 @@ bool ED_uvedit_center_multi(const Scene *scene, return changed; } -bool ED_uvedit_center_from_pivot_ex(SpaceImage *sima, +bool ED_uvedit_center_from_pivot_ex(const SpaceImage *sima, Scene *scene, ViewLayer *view_layer, float r_center[2], diff --git a/source/blender/windowmanager/intern/wm_toolsystem.cc b/source/blender/windowmanager/intern/wm_toolsystem.cc index b3908907066..7bb73f367be 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.cc +++ b/source/blender/windowmanager/intern/wm_toolsystem.cc @@ -728,7 +728,7 @@ static bool toolsystem_key_ensure_check(const bToolKey *tkey) case SPACE_VIEW3D: return true; case SPACE_IMAGE: - if (ELEM(tkey->mode, SI_MODE_PAINT, SI_MODE_UV, SI_MODE_VIEW)) { + if (ELEM(tkey->mode, SI_MODE_PAINT, SI_MODE_UV, SI_MODE_VIEW, SI_MODE_MASK)) { return true; } break; @@ -1140,6 +1140,8 @@ static const char *toolsystem_default_tool(const bToolKey *tkey) return "builtin.brush"; case SI_MODE_VIEW: return "builtin.sample"; + case SI_MODE_MASK: + return "builtin.select_box"; } break; case SPACE_NODE: {