692e200ffe
save startup blend for animation tab & whatnot
266 lines
8.1 KiB
Python
266 lines
8.1 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass, field
|
|
|
|
import bpy
|
|
import rna_keymap_ui
|
|
|
|
|
|
bk_logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class KeyMapItemDef:
|
|
"""Description of a keymap item we want to register."""
|
|
|
|
idname: str
|
|
type: str
|
|
value: str
|
|
shift: bool = False
|
|
ctrl: bool = False
|
|
alt: bool = False
|
|
oskey: bool = False
|
|
key_modifier: str = "NONE"
|
|
properties: dict[str, object] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class KeyMapDef:
|
|
"""Description of a keymap with its items."""
|
|
|
|
name: str
|
|
space_type: str
|
|
region_type: str = "WINDOW"
|
|
items: list[KeyMapItemDef] = field(default_factory=list)
|
|
|
|
|
|
# Default bindings we ship. Users can edit these in Preferences.
|
|
DEFAULT_KEYMAP_ITEMS: list[KeyMapItemDef] = [
|
|
KeyMapItemDef(
|
|
idname="view3d.run_assetbar_fix_context",
|
|
type="SEMI_COLON",
|
|
value="PRESS",
|
|
properties={"keep_running": False, "do_search": False},
|
|
),
|
|
KeyMapItemDef(
|
|
idname="wm.blenderkit_menu_rating_upload",
|
|
type="R",
|
|
value="PRESS",
|
|
),
|
|
]
|
|
|
|
DEFAULT_KEYMAPS: list[KeyMapDef] = [
|
|
# Register into the standard "Window" keymap so Blender shows it in the main tree.
|
|
KeyMapDef(
|
|
name="Window", # must be windows otherwise blender will not show it in the default keymap
|
|
space_type="EMPTY",
|
|
region_type="WINDOW",
|
|
items=DEFAULT_KEYMAP_ITEMS,
|
|
),
|
|
]
|
|
|
|
# Store only the keymap items we create so we can clean them up without touching user overrides.
|
|
_registered_keymaps: list[
|
|
tuple[bpy.types.KeyConfig, bpy.types.KeyMap, bpy.types.KeyMapItem]
|
|
] = []
|
|
|
|
|
|
def _keymap_has_item(km: bpy.types.KeyMap, idname: str) -> bpy.types.KeyMapItem | None:
|
|
for item in km.keymap_items:
|
|
if item.idname == idname:
|
|
return item
|
|
return None
|
|
|
|
|
|
def _find_in_keyconfig(
|
|
keyconfig: bpy.types.KeyConfig, idname: str
|
|
) -> bpy.types.KeyMapItem | None:
|
|
for km in keyconfig.keymaps:
|
|
kmi = _keymap_has_item(km, idname)
|
|
if kmi:
|
|
return kmi
|
|
return None
|
|
|
|
|
|
def register_keymaps(custom_keymaps: list[KeyMapDef] | None = None) -> None:
|
|
"""Register keymaps for the add-on.
|
|
|
|
Args:
|
|
custom_keymaps: Optional iterable of KeyMapDef if callers want to override defaults.
|
|
"""
|
|
|
|
wm = bpy.context.window_manager
|
|
if not wm:
|
|
bk_logger.warning("Unable to register keymaps: no window manager available")
|
|
return
|
|
kc_addon = wm.keyconfigs.addon
|
|
kc_user = wm.keyconfigs.user
|
|
if not kc_addon:
|
|
bk_logger.warning("Unable to register keymaps: no add-on keyconfig available")
|
|
return
|
|
bk_logger.debug("Registering keymaps for BlenderKit add-on")
|
|
|
|
keymaps = list(custom_keymaps) if custom_keymaps is not None else DEFAULT_KEYMAPS
|
|
|
|
for km_def in keymaps:
|
|
# If the user already has a custom binding in their keyconfig, don't recreate it.
|
|
if kc_user and _find_in_keyconfig(kc_user, km_def.items[0].idname):
|
|
bk_logger.debug(
|
|
f"User keyconfig already has binding for {km_def.items[0].idname}; leaving user override intact"
|
|
)
|
|
continue
|
|
|
|
km = kc_addon.keymaps.find(
|
|
km_def.name, space_type=km_def.space_type, region_type=km_def.region_type
|
|
)
|
|
if km is None:
|
|
bk_logger.debug(
|
|
f"Keymap {km_def.name} not found in {kc_addon.name}, creating new one"
|
|
)
|
|
km = kc_addon.keymaps.new(
|
|
name=km_def.name,
|
|
space_type=km_def.space_type,
|
|
region_type=km_def.region_type,
|
|
)
|
|
|
|
for item_def in km_def.items:
|
|
if _keymap_has_item(km, item_def.idname):
|
|
bk_logger.debug(
|
|
f"Keymap {km_def.name} in {kc_addon.name} already has item {item_def.idname}, skipping"
|
|
)
|
|
continue
|
|
bk_logger.debug(
|
|
f"Adding keymap item {item_def.idname} to keymap {km_def.name} in {kc_addon.name}"
|
|
)
|
|
kmi = km.keymap_items.new(
|
|
idname=item_def.idname,
|
|
type=item_def.type,
|
|
value=item_def.value,
|
|
shift=item_def.shift,
|
|
ctrl=item_def.ctrl,
|
|
alt=item_def.alt,
|
|
oskey=item_def.oskey,
|
|
key_modifier=item_def.key_modifier,
|
|
)
|
|
for prop_name, prop_value in item_def.properties.items():
|
|
bk_logger.debug(
|
|
f"Setting property {prop_name}={prop_value} on keymap item {item_def.idname} in {kc_addon.name}"
|
|
)
|
|
setattr(kmi.properties, prop_name, prop_value)
|
|
_registered_keymaps.append((kc_addon, km, kmi))
|
|
|
|
|
|
def unregister_keymaps() -> None:
|
|
if not _registered_keymaps:
|
|
return
|
|
|
|
for kc, km, kmi in _registered_keymaps:
|
|
try:
|
|
km.keymap_items.remove(kmi)
|
|
except RuntimeError:
|
|
# Already removed by user; ignore.
|
|
pass
|
|
_registered_keymaps.clear()
|
|
|
|
|
|
def get_keymap_item(idname: str) -> bpy.types.KeyMapItem | None:
|
|
"""Return the current keymap item for the given operator.
|
|
|
|
Prefers the user's key configuration (where edits are stored) and falls back to the
|
|
add-on keyconfig defaults.
|
|
"""
|
|
|
|
wm = bpy.context.window_manager
|
|
if not wm:
|
|
return None
|
|
|
|
for cfg in (wm.keyconfigs.user, wm.keyconfigs.addon):
|
|
if cfg:
|
|
kmi = _find_in_keyconfig(cfg, idname)
|
|
if kmi:
|
|
return kmi
|
|
return None
|
|
|
|
|
|
def format_keymap_item(kmi: bpy.types.KeyMapItem) -> str:
|
|
"""Return a human readable shortcut label for a KeyMapItem."""
|
|
|
|
parts = []
|
|
if kmi.ctrl:
|
|
parts.append("Ctrl")
|
|
if kmi.alt:
|
|
parts.append("Alt")
|
|
if kmi.shift:
|
|
parts.append("Shift")
|
|
if kmi.oskey:
|
|
parts.append("Cmd")
|
|
if kmi.key_modifier and kmi.key_modifier != "NONE":
|
|
parts.append(kmi.key_modifier.replace("_", " ").title())
|
|
parts.append(kmi.type.replace("_", " ").title())
|
|
return "+".join(parts)
|
|
|
|
|
|
def get_shortcut_label(idname: str, fallback: str = "") -> str:
|
|
"""Return a formatted shortcut string for the operator if available."""
|
|
|
|
kmi = get_keymap_item(idname)
|
|
if not kmi:
|
|
return fallback
|
|
return format_keymap_item(kmi)
|
|
|
|
|
|
def _find_km_and_kmi(
|
|
keyconfig: bpy.types.KeyConfig, idname: str
|
|
) -> tuple[bpy.types.KeyMap, bpy.types.KeyMapItem] | None:
|
|
if not keyconfig:
|
|
return None
|
|
for km in keyconfig.keymaps:
|
|
kmi = _keymap_has_item(km, idname)
|
|
if kmi:
|
|
return km, kmi
|
|
return None
|
|
|
|
|
|
def draw_keymap(self, context):
|
|
layout = self.layout
|
|
wm = context.window_manager
|
|
kc_addon = wm.keyconfigs.addon
|
|
kc_user = wm.keyconfigs.user
|
|
if not kc_addon:
|
|
return
|
|
|
|
box = layout.box()
|
|
box.label(text="BlenderKit Keymaps")
|
|
|
|
for item_def in DEFAULT_KEYMAP_ITEMS:
|
|
# Prefer user override if available, otherwise show addon default.
|
|
entry = _find_km_and_kmi(kc_user, item_def.idname) if kc_user else None
|
|
source_kc = kc_user if entry else kc_addon
|
|
km, kmi = (
|
|
entry
|
|
if entry
|
|
else _find_km_and_kmi(kc_addon, item_def.idname) or (None, None)
|
|
)
|
|
if not km or not kmi:
|
|
continue
|
|
rna_keymap_ui.draw_kmi([], source_kc, km, kmi, box, 0)
|