03806d0b67
This commit implements described in the #104573. The goal is to fix the confusion of the submodule hashes change, which are not ideal for any of the supported git-module configuration (they are either always visible causing confusion, or silently staged and committed, also causing confusion). This commit replaces submodules with a checkout of addons and addons_contrib, covered by the .gitignore, and locale and developer tools are moved to the main repository. This also changes the paths: - /release/scripts are moved to the /scripts - /source/tools are moved to the /tools - /release/datafiles/locale is moved to /locale This is done to avoid conflicts when using bisect, and also allow buildbot to automatically "recover" wgen building older or newer branches/patches. Running `make update` will initialize the local checkout to the changed repository configuration. Another aspect of the change is that the make update will support Github style of remote organization (origin remote pointing to thy fork, upstream remote pointing to the upstream blender/blender.git). Pull Request #104755
437 lines
17 KiB
Python
437 lines
17 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# Dynamically create a keymap which is used by the popup toolbar
|
|
# for accelerator key access.
|
|
|
|
__all__ = (
|
|
"generate",
|
|
)
|
|
|
|
|
|
def generate(context, space_type, *, use_fallback_keys=True, use_reset=True):
|
|
"""
|
|
Keymap for popup toolbar, currently generated each time.
|
|
"""
|
|
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
|
|
|
|
def modifier_keywords_from_item(kmi):
|
|
kw = {}
|
|
for (attr, default) in (
|
|
("any", False),
|
|
("shift", False),
|
|
("ctrl", False),
|
|
("alt", False),
|
|
("oskey", False),
|
|
("key_modifier", 'NONE'),
|
|
):
|
|
val = getattr(kmi, attr)
|
|
if val != default:
|
|
kw[attr] = val
|
|
return kw
|
|
|
|
def dict_as_tuple(d):
|
|
return tuple((k, v) for (k, v) in sorted(d.items()))
|
|
|
|
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
|
|
|
|
items_all = [
|
|
# 0: tool
|
|
# 1: keymap item (direct access)
|
|
# 2: keymap item (newly calculated for toolbar)
|
|
[item, None, None]
|
|
for item in ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context))
|
|
if item is not None
|
|
]
|
|
items_all_id = {item_container[0].idname for item_container in items_all}
|
|
|
|
# Press the toolbar popup key again to set the default tool,
|
|
# this is useful because the select box tool is useful as a way
|
|
# to 'drop' currently active tools (it's basically a 'none' tool).
|
|
# so this allows us to quickly go back to a state that allows
|
|
# a shortcut based workflow (before the tool system was added).
|
|
use_tap_reset = use_reset
|
|
# TODO: support other tools for modes which don't use this tool.
|
|
tap_reset_tool = "builtin.cursor"
|
|
# Check the tool is available in the current context.
|
|
if tap_reset_tool not in items_all_id:
|
|
use_tap_reset = False
|
|
|
|
# Pie-menu style release to activate.
|
|
use_release_confirm = use_reset
|
|
|
|
# Generate items when no keys are mapped.
|
|
use_auto_keymap_alpha = False # Map manually in the default key-map.
|
|
use_auto_keymap_num = use_fallback_keys
|
|
|
|
# Temporary, only create so we can pass 'properties' to find_item_from_operator.
|
|
use_hack_properties = True
|
|
|
|
km_name_default = "Toolbar Popup"
|
|
km_name = km_name_default + " <temp>"
|
|
wm = context.window_manager
|
|
keyconf_user = wm.keyconfigs.user
|
|
keyconf_active = wm.keyconfigs.active
|
|
|
|
keymap = keyconf_active.keymaps.get(km_name)
|
|
if keymap is None:
|
|
keymap = keyconf_active.keymaps.new(km_name, space_type='EMPTY', region_type='TEMPORARY')
|
|
for kmi in keymap.keymap_items:
|
|
keymap.keymap_items.remove(kmi)
|
|
|
|
keymap_src = keyconf_user.keymaps.get(km_name_default)
|
|
if keymap_src is not None:
|
|
for kmi_src in keymap_src.keymap_items:
|
|
# Skip tools that aren't currently shown.
|
|
if (
|
|
(kmi_src.idname == "wm.tool_set_by_id") and
|
|
(kmi_src.properties.name not in items_all_id)
|
|
):
|
|
continue
|
|
keymap.keymap_items.new_from_item(kmi_src)
|
|
del keymap_src
|
|
del items_all_id
|
|
|
|
kmi_unique_args = set()
|
|
|
|
def kmi_unique_or_pass(kmi_args):
|
|
kmi_unique_len = len(kmi_unique_args)
|
|
kmi_unique_args.add(dict_as_tuple(kmi_args))
|
|
return kmi_unique_len != len(kmi_unique_args)
|
|
|
|
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
|
|
|
|
if use_hack_properties:
|
|
kmi_hack = keymap.keymap_items.new("wm.tool_set_by_id", 'NONE', 'PRESS')
|
|
kmi_hack_properties = kmi_hack.properties
|
|
kmi_hack.active = False
|
|
|
|
kmi_hack_brush_select = keymap.keymap_items.new("paint.brush_select", 'NONE', 'PRESS')
|
|
kmi_hack_brush_select_properties = kmi_hack_brush_select.properties
|
|
kmi_hack_brush_select.active = False
|
|
|
|
if use_release_confirm or use_tap_reset:
|
|
kmi_toolbar = wm.keyconfigs.find_item_from_operator(
|
|
idname="wm.toolbar",
|
|
)[1]
|
|
kmi_toolbar_type = None if not kmi_toolbar else kmi_toolbar.type
|
|
if use_tap_reset and kmi_toolbar_type is not None:
|
|
kmi_toolbar_args_type_only = {"type": kmi_toolbar_type}
|
|
kmi_toolbar_args = {**kmi_toolbar_args_type_only, **modifier_keywords_from_item(kmi_toolbar)}
|
|
else:
|
|
use_tap_reset = False
|
|
del kmi_toolbar
|
|
|
|
if use_tap_reset:
|
|
kmi_found = None
|
|
if use_hack_properties:
|
|
# First check for direct assignment, if this tool already has a key, no need to add a new one.
|
|
kmi_hack_properties.name = tap_reset_tool
|
|
kmi_found = wm.keyconfigs.find_item_from_operator(
|
|
idname="wm.tool_set_by_id",
|
|
context='INVOKE_REGION_WIN',
|
|
# properties={"name": item.idname},
|
|
properties=kmi_hack_properties,
|
|
include={'KEYBOARD'},
|
|
)[1]
|
|
if kmi_found:
|
|
use_tap_reset = False
|
|
del kmi_found
|
|
|
|
if use_tap_reset:
|
|
use_tap_reset = kmi_unique_or_pass(kmi_toolbar_args)
|
|
|
|
if use_tap_reset:
|
|
items_all[:] = [
|
|
item_container
|
|
for item_container in items_all
|
|
if item_container[0].idname != tap_reset_tool
|
|
]
|
|
|
|
# -----------------------
|
|
# Begin Keymap Generation
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Direct Tool Assignment & Brushes
|
|
|
|
for item_container in items_all:
|
|
item = item_container[0]
|
|
# Only check the first item in the tools key-map (a little arbitrary).
|
|
if use_hack_properties:
|
|
# First check for direct assignment.
|
|
kmi_hack_properties.name = item.idname
|
|
kmi_found = wm.keyconfigs.find_item_from_operator(
|
|
idname="wm.tool_set_by_id",
|
|
context='INVOKE_REGION_WIN',
|
|
# properties={"name": item.idname},
|
|
properties=kmi_hack_properties,
|
|
include={'KEYBOARD'},
|
|
)[1]
|
|
|
|
if kmi_found is None:
|
|
if item.data_block:
|
|
# PAINT_OT_brush_select
|
|
mode = context.active_object.mode
|
|
# See: BKE_paint_get_tool_prop_id_from_paintmode
|
|
if space_type == 'IMAGE_EDITOR':
|
|
if context.space_data.mode == 'PAINT':
|
|
attr = "image_tool"
|
|
else:
|
|
attr = None
|
|
elif space_type == 'VIEW_3D':
|
|
attr = {
|
|
'SCULPT': "sculpt_tool",
|
|
'VERTEX_PAINT': "vertex_tool",
|
|
'WEIGHT_PAINT': "weight_tool",
|
|
'TEXTURE_PAINT': "image_tool",
|
|
'PAINT_GPENCIL': "gpencil_tool",
|
|
'VERTEX_GPENCIL': "gpencil_vertex_tool",
|
|
'SCULPT_GPENCIL': "gpencil_sculpt_tool",
|
|
'WEIGHT_GPENCIL': "gpencil_weight_tool",
|
|
'SCULPT_CURVES': "curves_sculpt_tool",
|
|
}.get(mode, None)
|
|
else:
|
|
attr = None
|
|
|
|
if attr is not None:
|
|
setattr(kmi_hack_brush_select_properties, attr, item.data_block)
|
|
kmi_found = wm.keyconfigs.find_item_from_operator(
|
|
idname="paint.brush_select",
|
|
context='INVOKE_REGION_WIN',
|
|
properties=kmi_hack_brush_select_properties,
|
|
include={'KEYBOARD'},
|
|
)[1]
|
|
elif mode in {'EDIT', 'PARTICLE_EDIT', 'SCULPT_GPENCIL'}:
|
|
# Doesn't use brushes
|
|
pass
|
|
else:
|
|
print("Unsupported mode:", mode)
|
|
del mode, attr
|
|
|
|
else:
|
|
kmi_found = None
|
|
|
|
if kmi_found is not None:
|
|
pass
|
|
elif item.operator is not None:
|
|
kmi_found = wm.keyconfigs.find_item_from_operator(
|
|
idname=item.operator,
|
|
context='INVOKE_REGION_WIN',
|
|
include={'KEYBOARD'},
|
|
)[1]
|
|
elif item.keymap is not None:
|
|
km = keyconf_user.keymaps.get(item.keymap[0])
|
|
if km is None:
|
|
print("Keymap", repr(item.keymap[0]), "not found for tool", item.idname)
|
|
kmi_found = None
|
|
else:
|
|
kmi_first = km.keymap_items
|
|
kmi_first = kmi_first[0] if kmi_first else None
|
|
if kmi_first is not None:
|
|
kmi_found = wm.keyconfigs.find_item_from_operator(
|
|
idname=kmi_first.idname,
|
|
# properties=kmi_first.properties, # prevents matches, don't use.
|
|
context='INVOKE_REGION_WIN',
|
|
include={'KEYBOARD'},
|
|
)[1]
|
|
if kmi_found is None:
|
|
# We need non-keyboard events so keys with 'key_modifier' key is found.
|
|
kmi_found = wm.keyconfigs.find_item_from_operator(
|
|
idname=kmi_first.idname,
|
|
# properties=kmi_first.properties, # prevents matches, don't use.
|
|
context='INVOKE_REGION_WIN',
|
|
exclude={'KEYBOARD'},
|
|
)[1]
|
|
if kmi_found is not None:
|
|
if kmi_found.key_modifier == 'NONE':
|
|
kmi_found = None
|
|
else:
|
|
kmi_found = None
|
|
del kmi_first
|
|
del km
|
|
else:
|
|
kmi_found = None
|
|
item_container[1] = kmi_found
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Single Key Access
|
|
|
|
# More complex multi-pass test.
|
|
for item_container in items_all:
|
|
item, kmi_found = item_container[:2]
|
|
if kmi_found is None:
|
|
continue
|
|
kmi_found_type = kmi_found.type
|
|
|
|
# Only for single keys.
|
|
if (
|
|
(len(kmi_found_type) == 1) or
|
|
# When a tool is being activated instead of running an operator, just copy the shortcut.
|
|
(kmi_found.idname in {"wm.tool_set_by_id", "WM_OT_tool_set_by_id"})
|
|
):
|
|
kmi_args = {"type": kmi_found_type, **modifier_keywords_from_item(kmi_found)}
|
|
if kmi_unique_or_pass(kmi_args):
|
|
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
|
kmi.properties.name = item.idname
|
|
item_container[2] = kmi
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Single Key Modifier
|
|
#
|
|
#
|
|
# Test for key_modifier, where alpha key is used as a 'key_modifier'
|
|
# (grease pencil holding 'D' for example).
|
|
|
|
for item_container in items_all:
|
|
item, kmi_found, kmi_exist = item_container
|
|
if kmi_found is None or kmi_exist:
|
|
continue
|
|
|
|
kmi_found_type = kmi_found.type
|
|
if kmi_found_type in {
|
|
'LEFTMOUSE',
|
|
'RIGHTMOUSE',
|
|
'MIDDLEMOUSE',
|
|
'BUTTON4MOUSE',
|
|
'BUTTON5MOUSE',
|
|
'BUTTON6MOUSE',
|
|
'BUTTON7MOUSE',
|
|
}:
|
|
kmi_found_type = kmi_found.key_modifier
|
|
# excludes 'NONE'
|
|
if len(kmi_found_type) == 1:
|
|
kmi_args = {"type": kmi_found_type, **modifier_keywords_from_item(kmi_found)}
|
|
del kmi_args["key_modifier"]
|
|
if kmi_unique_or_pass(kmi_args):
|
|
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
|
kmi.properties.name = item.idname
|
|
item_container[2] = kmi
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Assign A-Z to Keys
|
|
#
|
|
# When the keys are free.
|
|
|
|
if use_auto_keymap_alpha:
|
|
# Map all unmapped keys to numbers,
|
|
# while this is a bit strange it means users will not confuse regular key bindings to ordered bindings.
|
|
|
|
# First map A-Z.
|
|
kmi_type_alpha_char = [chr(i) for i in range(65, 91)]
|
|
kmi_type_alpha_args = {c: {"type": c} for c in kmi_type_alpha_char}
|
|
kmi_type_alpha_args_tuple = {c: dict_as_tuple(kmi_type_alpha_args[c]) for c in kmi_type_alpha_char}
|
|
for item_container in items_all:
|
|
item, kmi_found, kmi_exist = item_container
|
|
if kmi_exist:
|
|
continue
|
|
kmi_type = item.label[0].upper()
|
|
kmi_tuple = kmi_type_alpha_args_tuple.get(kmi_type)
|
|
if kmi_tuple and kmi_tuple not in kmi_unique_args:
|
|
kmi_unique_args.add(kmi_tuple)
|
|
kmi = keymap.keymap_items.new(
|
|
idname="wm.tool_set_by_id",
|
|
value='PRESS',
|
|
**kmi_type_alpha_args[kmi_type],
|
|
)
|
|
kmi.properties.name = item.idname
|
|
item_container[2] = kmi
|
|
del kmi_type_alpha_char, kmi_type_alpha_args, kmi_type_alpha_args_tuple
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Assign Numbers to Keys
|
|
|
|
if use_auto_keymap_num:
|
|
# Free events (last used first).
|
|
kmi_type_auto = ('ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'ZERO')
|
|
# Map both numbers and num-pad.
|
|
kmi_type_dupe = {
|
|
'ONE': 'NUMPAD_1',
|
|
'TWO': 'NUMPAD_2',
|
|
'THREE': 'NUMPAD_3',
|
|
'FOUR': 'NUMPAD_4',
|
|
'FIVE': 'NUMPAD_5',
|
|
'SIX': 'NUMPAD_6',
|
|
'SEVEN': 'NUMPAD_7',
|
|
'EIGHT': 'NUMPAD_8',
|
|
'NINE': 'NUMPAD_9',
|
|
'ZERO': 'NUMPAD_0',
|
|
}
|
|
|
|
def iter_free_events():
|
|
for mod in ({}, {"shift": True}, {"ctrl": True}, {"alt": True}):
|
|
for e in kmi_type_auto:
|
|
yield (e, mod)
|
|
|
|
iter_events = iter(iter_free_events())
|
|
|
|
for item_container in items_all:
|
|
item, kmi_found, kmi_exist = item_container
|
|
if kmi_exist:
|
|
continue
|
|
kmi_args = None
|
|
while True:
|
|
key, mod = next(iter_events, (None, None))
|
|
if key is None:
|
|
break
|
|
kmi_args = {"type": key, **mod}
|
|
kmi_tuple = dict_as_tuple(kmi_args)
|
|
if kmi_tuple in kmi_unique_args:
|
|
kmi_args = None
|
|
else:
|
|
break
|
|
|
|
if kmi_args is not None:
|
|
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
|
kmi.properties.name = item.idname
|
|
item_container[2] = kmi
|
|
kmi_unique_args.add(kmi_tuple)
|
|
|
|
key = kmi_type_dupe.get(kmi_args["type"])
|
|
if key is not None:
|
|
kmi_args["type"] = key
|
|
kmi_tuple = dict_as_tuple(kmi_args)
|
|
if kmi_tuple not in kmi_unique_args:
|
|
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
|
kmi.properties.name = item.idname
|
|
kmi_unique_args.add(kmi_tuple)
|
|
|
|
# ---------------------
|
|
# End Keymap Generation
|
|
|
|
if use_hack_properties:
|
|
keymap.keymap_items.remove(kmi_hack)
|
|
keymap.keymap_items.remove(kmi_hack_brush_select)
|
|
|
|
# Keep last so we can try add a key without any modifiers
|
|
# in the case this toolbar was activated with modifiers.
|
|
if use_tap_reset:
|
|
if len(kmi_toolbar_args_type_only) == len(kmi_toolbar_args):
|
|
kmi_toolbar_args_available = kmi_toolbar_args
|
|
else:
|
|
# We have modifiers, see if we have a free key w/o modifiers.
|
|
kmi_toolbar_tuple = dict_as_tuple(kmi_toolbar_args_type_only)
|
|
if kmi_toolbar_tuple not in kmi_unique_args:
|
|
kmi_toolbar_args_available = kmi_toolbar_args_type_only
|
|
kmi_unique_args.add(kmi_toolbar_tuple)
|
|
else:
|
|
kmi_toolbar_args_available = kmi_toolbar_args
|
|
del kmi_toolbar_tuple
|
|
|
|
kmi = keymap.keymap_items.new(
|
|
"wm.tool_set_by_id",
|
|
value='DOUBLE_CLICK',
|
|
**kmi_toolbar_args_available,
|
|
)
|
|
kmi.properties.name = tap_reset_tool
|
|
|
|
if use_release_confirm and (kmi_toolbar_type is not None):
|
|
kmi = keymap.keymap_items.new(
|
|
"ui.button_execute",
|
|
type=kmi_toolbar_type,
|
|
value='RELEASE',
|
|
any=True,
|
|
)
|
|
kmi.properties.skip_depressed = True
|
|
|
|
wm.keyconfigs.update()
|
|
return keymap
|