Re-design of submodules used in blender.git

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
This commit is contained in:
Sergey Sharybin
2023-02-21 16:39:58 +01:00
committed by Sergey Sharybin
parent 3e721195b0
commit 03806d0b67
570 changed files with 2039093 additions and 211 deletions

View File

@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
__all__ = (
"io",
"keymap_from_toolbar",
"keymap_hierarchy",
)

View File

@@ -0,0 +1,306 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# -----------------------------------------------------------------------------
# Export Functions
__all__ = (
"_init_properties_from_data", # Shared with gizmo default property initialization.
"keyconfig_export_as_data",
"keyconfig_import_from_data",
"keyconfig_init_from_data",
"keyconfig_merge",
"keymap_init_from_data",
)
def indent(levels):
return levels * " "
def round_float_32(f):
from struct import pack, unpack
return unpack("f", pack("f", f))[0]
def repr_f32(f):
f_round = round_float_32(f)
f_str = repr(f)
f_str_frac = f_str.partition(".")[2]
if not f_str_frac:
return f_str
for i in range(1, len(f_str_frac)):
f_test = round(f, i)
f_test_round = round_float_32(f_test)
if f_test_round == f_round:
return "%.*f" % (i, f_test)
return f_str
def kmi_args_as_data(kmi):
s = [
f"\"type\": '{kmi.type}'",
f"\"value\": '{kmi.value}'"
]
if kmi.any:
s.append("\"any\": True")
else:
for attr in ("shift", "ctrl", "alt", "oskey"):
if mod := getattr(kmi, attr):
s.append(f"\"{attr:s}\": " + ("-1" if mod == -1 else "True"))
if (mod := kmi.key_modifier) and (mod != 'NONE'):
s.append(f"\"key_modifier\": '{mod:s}'")
if (direction := kmi.direction) and (direction != 'ANY'):
s.append(f"\"direction\": '{direction:s}'")
if kmi.repeat:
if (
(kmi.map_type == 'KEYBOARD' and kmi.value in {'PRESS', 'ANY'}) or
(kmi.map_type == 'TEXTINPUT')
):
s.append("\"repeat\": True")
return "{" + ", ".join(s) + "}"
def _kmi_properties_to_lines_recursive(level, properties, lines):
from bpy.types import OperatorProperties
def string_value(value):
if isinstance(value, (str, bool, int, set)):
return repr(value)
elif isinstance(value, float):
return repr_f32(value)
elif getattr(value, '__len__', False):
return repr(tuple(value))
raise Exception(f"Export key configuration: can't write {value!r}")
for pname in properties.bl_rna.properties.keys():
if pname != "rna_type":
value = getattr(properties, pname)
if isinstance(value, OperatorProperties):
lines_test = []
_kmi_properties_to_lines_recursive(level + 2, value, lines_test)
if lines_test:
lines.append(f"(")
lines.append(f"\"{pname}\",\n")
lines.append(f"{indent(level + 3)}" "[")
lines.extend(lines_test)
lines.append("],\n")
lines.append(f"{indent(level + 3)}" "),\n" f"{indent(level + 2)}")
del lines_test
elif properties.is_property_set(pname):
value = string_value(value)
lines.append((f"(\"{pname}\", {value:s}),\n" f"{indent(level + 2)}"))
def _kmi_properties_to_lines(level, kmi_props, lines):
if kmi_props is None:
return
lines_test = [f"\"properties\":\n" f"{indent(level + 1)}" "["]
_kmi_properties_to_lines_recursive(level, kmi_props, lines_test)
if len(lines_test) > 1:
lines_test.append("],\n")
lines.extend(lines_test)
def _kmi_attrs_or_none(level, kmi):
lines = []
_kmi_properties_to_lines(level + 1, kmi.properties, lines)
if kmi.active is False:
lines.append(f"{indent(level)}\"active\":" "False,\n")
if not lines:
return None
return "".join(lines)
def keyconfig_export_as_data(wm, kc, filepath, *, all_keymaps=False):
# Alternate format
# Generate a list of keymaps to export:
#
# First add all user_modified keymaps (found in keyconfigs.user.keymaps list),
# then add all remaining keymaps from the currently active custom keyconfig.
#
# Sort the resulting list according to top context name,
# while this isn't essential, it makes comparing keymaps simpler.
#
# This will create a final list of keymaps that can be used as a "diff" against
# the default blender keyconfig, recreating the current setup from a fresh blender
# without needing to export keymaps which haven't been edited.
class FakeKeyConfig:
keymaps = []
edited_kc = FakeKeyConfig()
for km in wm.keyconfigs.user.keymaps:
if all_keymaps or km.is_user_modified:
edited_kc.keymaps.append(km)
# merge edited keymaps with non-default keyconfig, if it exists
if kc != wm.keyconfigs.default:
export_keymaps = keyconfig_merge(edited_kc, kc)
else:
export_keymaps = keyconfig_merge(edited_kc, edited_kc)
# Sort the keymap list by top context name before exporting,
# not essential, just convenient to order them predictably.
export_keymaps.sort(key=lambda k: k[0].name)
with open(filepath, "w", encoding="utf-8") as fh:
fw = fh.write
# Use the file version since it includes the sub-version
# which we can bump multiple times between releases.
from bpy.app import version_file
fw(f"keyconfig_version = {version_file!r}\n")
del version_file
fw("keyconfig_data = \\\n[")
for km, _kc_x in export_keymaps:
km = km.active()
fw("(")
fw(f"\"{km.name:s}\",\n")
fw(f"{indent(2)}" "{")
fw(f"\"space_type\": '{km.space_type:s}'")
fw(f", \"region_type\": '{km.region_type:s}'")
# We can detect from the kind of items.
if km.is_modal:
fw(", \"modal\": True")
fw("},\n")
fw(f"{indent(2)}" "{")
is_modal = km.is_modal
fw(f"\"items\":\n")
fw(f"{indent(3)}[")
for kmi in km.keymap_items:
if is_modal:
kmi_id = kmi.propvalue
else:
kmi_id = kmi.idname
fw(f"(")
kmi_args = kmi_args_as_data(kmi)
kmi_data = _kmi_attrs_or_none(4, kmi)
fw(f"\"{kmi_id:s}\"")
if kmi_data is None:
fw(f", ")
else:
fw(",\n" f"{indent(5)}")
fw(kmi_args)
if kmi_data is None:
fw(", None),\n")
else:
fw(",\n")
fw(f"{indent(5)}" "{")
fw(kmi_data)
fw(f"{indent(6)}")
fw("},\n" f"{indent(5)}")
fw("),\n")
fw(f"{indent(4)}")
fw("],\n" f"{indent(3)}")
fw("},\n" f"{indent(2)}")
fw("),\n" f"{indent(1)}")
fw("]\n")
fw("\n\n")
fw("if __name__ == \"__main__\":\n")
# We could remove this in the future, as loading new key-maps in older Blender versions
# makes less and less sense as Blender changes.
fw(" # Only add keywords that are supported.\n")
fw(" from bpy.app import version as blender_version\n")
fw(" keywords = {}\n")
fw(" if blender_version >= (2, 92, 0):\n")
fw(" keywords[\"keyconfig_version\"] = keyconfig_version\n")
fw(" import os\n")
fw(" from bl_keymap_utils.io import keyconfig_import_from_data\n")
fw(" keyconfig_import_from_data(\n")
fw(" os.path.splitext(os.path.basename(__file__))[0],\n")
fw(" keyconfig_data,\n")
fw(" **keywords,\n")
fw(" )\n")
# -----------------------------------------------------------------------------
# Import Functions
#
# NOTE: unlike export, this runs on startup.
# Take care making changes that could impact performance.
def _init_properties_from_data(base_props, base_value):
assert type(base_value) is list
for attr, value in base_value:
if type(value) is list:
base_props.property_unset(attr)
props = getattr(base_props, attr)
_init_properties_from_data(props, value)
else:
try:
setattr(base_props, attr, value)
except AttributeError:
print(f"Warning: property '{attr}' not found in item '{base_props.__class__.__name__}'")
except Exception as ex:
print(f"Warning: {ex!r}")
def keymap_init_from_data(km, km_items, is_modal=False):
new_fn = getattr(km.keymap_items, "new_modal" if is_modal else "new")
for (kmi_idname, kmi_args, kmi_data) in km_items:
kmi = new_fn(kmi_idname, **kmi_args)
if kmi_data is not None:
if not kmi_data.get("active", True):
kmi.active = False
kmi_props_data = kmi_data.get("properties", None)
if kmi_props_data is not None:
kmi_props = kmi.properties
assert type(kmi_props_data) is list
_init_properties_from_data(kmi_props, kmi_props_data)
def keyconfig_init_from_data(kc, keyconfig_data):
# Load data in the format defined above.
#
# Runs at load time, keep this fast!
for (km_name, km_args, km_content) in keyconfig_data:
km = kc.keymaps.new(km_name, **km_args)
km_items = km_content["items"]
# Check here instead of inside 'keymap_init_from_data'
# because we want to allow both tuple & list types in that case.
#
# For full keymaps, ensure these are always lists to allow for extending them
# in a generic way that doesn't have to check for the type each time.
assert type(km_items) is list
keymap_init_from_data(km, km_items, is_modal=km_args.get("modal", False))
def keyconfig_import_from_data(name, keyconfig_data, *, keyconfig_version=(0, 0, 0)):
# Load data in the format defined above.
#
# Runs at load time, keep this fast!
import bpy
wm = bpy.context.window_manager
kc = wm.keyconfigs.new(name)
if keyconfig_version is not None:
from .versioning import keyconfig_update
keyconfig_data = keyconfig_update(keyconfig_data, keyconfig_version)
keyconfig_init_from_data(kc, keyconfig_data)
return kc
# -----------------------------------------------------------------------------
# Utility Functions
def keyconfig_merge(kc1, kc2):
""" note: kc1 takes priority over kc2
"""
kc1_names = {km.name for km in kc1.keymaps}
merged_keymaps = [(km, kc1) for km in kc1.keymaps]
if kc1 != kc2:
merged_keymaps.extend(
(km, kc2)
for km in kc2.keymaps
if km.name not in kc1_names
)
return merged_keymaps

View File

@@ -0,0 +1,436 @@
# 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

View File

@@ -0,0 +1,229 @@
# SPDX-License-Identifier: GPL-2.0-or-later
__all__ = (
"generate",
)
def _km_expand_from_toolsystem(space_type, context_mode):
def _fn():
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
for cls in ToolSelectPanelHelper.__subclasses__():
if cls.bl_space_type == space_type:
return cls.keymap_ui_hierarchy(context_mode)
raise Exception("keymap not found")
return _fn
def _km_hierarchy_iter_recursive(items):
for sub in items:
if callable(sub):
yield from sub()
else:
yield (*sub[:3], list(_km_hierarchy_iter_recursive(sub[3])))
def generate():
return list(_km_hierarchy_iter_recursive(_km_hierarchy))
# bpy.type.KeyMap: (km.name, km.space_type, km.region_type, [...])
# ('Script', 'EMPTY', 'WINDOW', []),
# Access via 'km_hierarchy'.
_km_hierarchy = [
('Window', 'EMPTY', 'WINDOW', []), # file save, window change, exit
('Screen', 'EMPTY', 'WINDOW', [ # full screen, undo, screenshot
('Screen Editing', 'EMPTY', 'WINDOW', []), # re-sizing, action corners
('Region Context Menu', 'EMPTY', 'WINDOW', []), # header/footer/navigation_bar stuff (per region)
]),
('View2D', 'EMPTY', 'WINDOW', []), # view 2d navigation (per region)
('View2D Buttons List', 'EMPTY', 'WINDOW', []), # view 2d with buttons navigation
('User Interface', 'EMPTY', 'WINDOW', []),
('3D View', 'VIEW_3D', 'WINDOW', [ # view 3d navigation and generic stuff (select, transform)
('Object Mode', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'OBJECT'),
]),
('Mesh', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_MESH'),
]),
('Curve', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_CURVE'),
]),
('Curves', 'EMPTY', 'WINDOW', []),
('Armature', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_ARMATURE'),
]),
('Metaball', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_METABALL'),
]),
('Lattice', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_LATTICE'),
]),
('Font', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_TEXT'),
]),
('Pose', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'POSE'),
]),
('Vertex Paint', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'PAINT_VERTEX'),
]),
('Weight Paint', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'PAINT_WEIGHT'),
]),
('Paint Vertex Selection (Weight, Vertex)', 'EMPTY', 'WINDOW', []),
('Paint Face Mask (Weight, Vertex, Texture)', 'EMPTY', 'WINDOW', []),
# image and view3d
('Image Paint', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'PAINT_TEXTURE'),
]),
('Sculpt', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'SCULPT'),
]),
('Sculpt Curves', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'CURVES_SCULPT'),
]),
('Particle', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', 'PARTICLE'),
]),
('Knife Tool Modal Map', 'EMPTY', 'WINDOW', []),
('Custom Normals Modal Map', 'EMPTY', 'WINDOW', []),
('Bevel Modal Map', 'EMPTY', 'WINDOW', []),
('Paint Stroke Modal', 'EMPTY', 'WINDOW', []),
('Sculpt Expand Modal', 'EMPTY', 'WINDOW', []),
('Paint Curve', 'EMPTY', 'WINDOW', []),
('Curve Pen Modal Map', 'EMPTY', 'WINDOW', []),
('Object Non-modal', 'EMPTY', 'WINDOW', []), # mode change
('View3D Placement Modal', 'EMPTY', 'WINDOW', []),
('View3D Walk Modal', 'EMPTY', 'WINDOW', []),
('View3D Fly Modal', 'EMPTY', 'WINDOW', []),
('View3D Rotate Modal', 'EMPTY', 'WINDOW', []),
('View3D Move Modal', 'EMPTY', 'WINDOW', []),
('View3D Zoom Modal', 'EMPTY', 'WINDOW', []),
('View3D Dolly Modal', 'EMPTY', 'WINDOW', []),
# toolbar and properties
('3D View Generic', 'VIEW_3D', 'WINDOW', [
_km_expand_from_toolsystem('VIEW_3D', None),
]),
]),
('Graph Editor', 'GRAPH_EDITOR', 'WINDOW', [
('Graph Editor Generic', 'GRAPH_EDITOR', 'WINDOW', []),
]),
('Dopesheet', 'DOPESHEET_EDITOR', 'WINDOW', [
('Dopesheet Generic', 'DOPESHEET_EDITOR', 'WINDOW', []),
]),
('NLA Editor', 'NLA_EDITOR', 'WINDOW', [
('NLA Channels', 'NLA_EDITOR', 'WINDOW', []),
('NLA Generic', 'NLA_EDITOR', 'WINDOW', []),
]),
('Timeline', 'TIMELINE', 'WINDOW', []),
('Image', 'IMAGE_EDITOR', 'WINDOW', [
# Image (reverse order, UVEdit before Image).
('UV Editor', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('IMAGE_EDITOR', 'UV'),
]),
('UV Sculpt', 'EMPTY', 'WINDOW', []),
# Image and view3d.
('Image Paint', 'EMPTY', 'WINDOW', [
_km_expand_from_toolsystem('IMAGE_EDITOR', 'PAINT'),
]),
('Image View', 'IMAGE_EDITOR', 'WINDOW', [
_km_expand_from_toolsystem('IMAGE_EDITOR', 'VIEW'),
]),
('Image Generic', 'IMAGE_EDITOR', 'WINDOW', [
_km_expand_from_toolsystem('IMAGE_EDITOR', None),
]),
]),
('Outliner', 'OUTLINER', 'WINDOW', []),
('Node Editor', 'NODE_EDITOR', 'WINDOW', [
('Node Generic', 'NODE_EDITOR', 'WINDOW', []),
]),
('SequencerCommon', 'SEQUENCE_EDITOR', 'WINDOW', [
('Sequencer', 'SEQUENCE_EDITOR', 'WINDOW', [
_km_expand_from_toolsystem('SEQUENCE_EDITOR', 'SEQUENCER'),
]),
('SequencerPreview', 'SEQUENCE_EDITOR', 'WINDOW', [
_km_expand_from_toolsystem('SEQUENCE_EDITOR', 'PREVIEW'),
]),
]),
('File Browser', 'FILE_BROWSER', 'WINDOW', [
('File Browser Main', 'FILE_BROWSER', 'WINDOW', []),
('File Browser Buttons', 'FILE_BROWSER', 'WINDOW', []),
]),
('Info', 'INFO', 'WINDOW', []),
('Property Editor', 'PROPERTIES', 'WINDOW', []), # align context menu
('Text', 'TEXT_EDITOR', 'WINDOW', [
('Text Generic', 'TEXT_EDITOR', 'WINDOW', []),
]),
('Console', 'CONSOLE', 'WINDOW', []),
('Clip', 'CLIP_EDITOR', 'WINDOW', [
('Clip Editor', 'CLIP_EDITOR', 'WINDOW', []),
('Clip Graph Editor', 'CLIP_EDITOR', 'WINDOW', []),
('Clip Dopesheet Editor', 'CLIP_EDITOR', 'WINDOW', []),
]),
('Grease Pencil', 'EMPTY', 'WINDOW', [ # grease pencil stuff (per region)
('Grease Pencil Stroke Curve Edit Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Edit Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint (Draw brush)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint (Fill)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint (Erase)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint (Tint)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Smooth)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Thickness)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Strength)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Grab)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Push)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Twist)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Pinch)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Randomize)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Sculpt (Clone)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Weight Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Weight (Draw)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Vertex Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Vertex (Draw)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Vertex (Blur)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Vertex (Average)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Vertex (Smear)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Vertex (Replace)', 'EMPTY', 'WINDOW', []),
]),
('Mask Editing', 'EMPTY', 'WINDOW', []),
('Frames', 'EMPTY', 'WINDOW', []), # frame navigation (per region)
('Markers', 'EMPTY', 'WINDOW', []), # markers (per region)
('Animation', 'EMPTY', 'WINDOW', []), # frame change on click, preview range (per region)
('Animation Channels', 'EMPTY', 'WINDOW', []),
('View3D Gesture Circle', 'EMPTY', 'WINDOW', []),
('Gesture Straight Line', 'EMPTY', 'WINDOW', []),
('Gesture Zoom Border', 'EMPTY', 'WINDOW', []),
('Gesture Box', 'EMPTY', 'WINDOW', []),
('Standard Modal Map', 'EMPTY', 'WINDOW', []),
('Transform Modal Map', 'EMPTY', 'WINDOW', []),
('Eyedropper Modal Map', 'EMPTY', 'WINDOW', []),
('Eyedropper ColorRamp PointSampling Map', 'EMPTY', 'WINDOW', []),
]

View File

@@ -0,0 +1,48 @@
# SPDX-License-Identifier: GPL-2.0-or-later
def keyconfig_data_oskey_from_ctrl(keyconfig_data_src, *, filter_fn=None):
keyconfig_data_dst = []
for km_name, km_parms, km_items_data_src in keyconfig_data_src:
km_items_data_dst = km_items_data_src.copy()
items_dst = []
km_items_data_dst["items"] = items_dst
for item_src in km_items_data_src["items"]:
item_op, item_event, item_prop = item_src
if "ctrl" in item_event:
if filter_fn is None or filter_fn(item_event):
item_event = item_event.copy()
item_event["oskey"] = item_event["ctrl"]
del item_event["ctrl"]
items_dst.append((item_op, item_event, item_prop))
items_dst.append(item_src)
keyconfig_data_dst.append((km_name, km_parms, km_items_data_dst))
return keyconfig_data_dst
def keyconfig_data_oskey_from_ctrl_for_macos(keyconfig_data_src):
"""Use for apple since Cmd is typically used in-place of Ctrl."""
def filter_fn(item_event):
if item_event.get("ctrl"):
event_type = item_event["type"]
# Ctrl-{Key}
if (event_type in {
'H',
'M',
'SPACE',
'W',
'ACCENT_GRAVE',
'PERIOD',
'TAB',
}):
if (not item_event.get("alt")) and (not item_event.get("shift")):
return False
# Ctrl-Alt-{Key}
if (event_type in {
'Q',
}):
if item_event.get("alt") and (not item_event.get("shift")):
return False
return True
return keyconfig_data_oskey_from_ctrl(keyconfig_data_src, filter_fn=filter_fn)

View File

@@ -0,0 +1,63 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Update Blender version this key-map was written in:
#
# When the version is `(0, 0, 0)`, the key-map being loaded didn't contain any versioning information.
# This will older than `(2, 92, 0)`.
def keyconfig_update(keyconfig_data, keyconfig_version):
from bpy.app import version_file as blender_version
if keyconfig_version >= blender_version:
return keyconfig_data
# Version the key-map.
import copy
# Only copy once.
has_copy = False
# Default repeat to false.
if keyconfig_version <= (2, 92, 0):
if not has_copy:
keyconfig_data = copy.deepcopy(keyconfig_data)
has_copy = True
for _km_name, _km_parms, km_items_data in keyconfig_data:
for (_item_op, item_event, _item_prop) in km_items_data["items"]:
if item_event.get("value") == 'PRESS':
# Unfortunately we don't know the 'map_type' at this point.
# Setting repeat true on other kinds of events is harmless.
item_event["repeat"] = True
if keyconfig_version <= (3, 2, 5):
if not has_copy:
keyconfig_data = copy.deepcopy(keyconfig_data)
has_copy = True
for _km_name, _km_parms, km_items_data in keyconfig_data:
for (_item_op, item_event, _item_prop) in km_items_data["items"]:
if ty_new := {
'EVT_TWEAK_L': 'LEFTMOUSE',
'EVT_TWEAK_M': 'MIDDLEMOUSE',
'EVT_TWEAK_R': 'RIGHTMOUSE',
}.get(item_event.get("type")):
item_event["type"] = ty_new
if (value := item_event["value"]) != 'ANY':
item_event["direction"] = value
item_event["value"] = 'CLICK_DRAG'
if keyconfig_version <= (3, 2, 6):
if not has_copy:
keyconfig_data = copy.deepcopy(keyconfig_data)
has_copy = True
for _km_name, _km_parms, km_items_data in keyconfig_data:
for (_item_op, item_event, _item_prop) in km_items_data["items"]:
if ty_new := {
'NDOF_BUTTON_ESC': 'ESC',
'NDOF_BUTTON_ALT': 'LEFT_ALT',
'NDOF_BUTTON_SHIFT': 'LEFT_SHIFT',
'NDOF_BUTTON_CTRL': 'LEFT_CTRL',
}.get(item_event.get("type")):
item_event["type"] = ty_new
return keyconfig_data