fix: Registration of keymaps and associated errors.

uv-refactor
Sofus Albert Høgsbro Rose 2024-10-02 12:55:38 +02:00
parent 55235c7032
commit 383f7d9bfd
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
12 changed files with 329 additions and 122 deletions

View File

@ -18,8 +18,8 @@
from functools import reduce
# from . import node_trees, operators, preferences, registration
from . import assets, preferences, registration
# from . import node_trees
from . import assets, operators, preferences, registration
from . import contracts as ct
from .utils import logger
@ -30,8 +30,8 @@ log = logger.get(__name__)
# - Load and Register Addon
####################
BL_REGISTER: list[ct.BLClass] = [
# *operators.BL_REGISTER,
# *assets.BL_REGISTER,
*operators.BL_REGISTER,
*assets.BL_REGISTER,
# *node_trees.BL_REGISTER,
]
@ -39,20 +39,18 @@ BL_HANDLERS: ct.BLHandlers = reduce(
lambda a, b: a + b,
[
assets.BL_HANDLERS,
# *operators.BL_HANDLERS,
# *assets.BL_HANDLERS,
# *node_trees.BL_HANDLERS,
operators.BL_HANDLERS,
assets.BL_HANDLERS,
# node_trees.BL_HANDLERS,
],
ct.BLHandlers(),
)
BL_HOTKEYS: list[ct.KeymapItemDef] = [
# *operators.BL_HOTKEYS,
# *assets.BL_HOTKEYS,
BL_KEYMAP_ITEMS: list[ct.BLKeymapItem] = [
*assets.BL_KEYMAP_ITEMS,
*operators.BL_KEYMAP_ITEMS,
]
## TODO: BL_HANDLERS and BL_SOCKET_DEFS
####################
# - Registration
@ -79,7 +77,7 @@ def register() -> None:
registration.register_classes(BL_REGISTER)
registration.register_handlers(BL_HANDLERS)
registration.register_hotkeys(BL_HOTKEYS)
registration.register_keymaps(BL_KEYMAP_ITEMS)
log.info('Finished Registration of Addon: %s', ct.addon.NAME)
else:
@ -98,7 +96,7 @@ def unregister() -> None:
"""
log.info('Starting %s Unregister', ct.addon.NAME)
registration.unregister_hotkeys()
registration.unregister_keymaps()
registration.unregister_handlers()
registration.unregister_classes()

View File

@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Internal assets for use via the Python API and/or directly by the user as an asset library."""
from functools import reduce
import oscillode.contracts as ct
@ -32,8 +34,8 @@ BL_HANDLERS: ct.BLHandlers = reduce(
ct.BLHandlers(),
)
BL_HOTKEYS: list[ct.KeymapItemDef] = [
*geonodes.BL_HOTKEYS,
BL_KEYMAP_ITEMS: list[ct.BLKeymapItem] = [
*geonodes.BL_KEYMAP_ITEMS,
]
__all__ = [

View File

@ -31,17 +31,17 @@ log = logger.get(__name__)
####################
# GeoNodes Paths
## Internal
GN_INTERNAL_PATH = ct.addon.PATH_ASSETS / 'internal'
GN_INTERNAL_INPUTS_PATH = GN_INTERNAL_PATH / 'input'
GN_INTERNAL_SOURCES_PATH = GN_INTERNAL_PATH / 'source'
GN_INTERNAL_STRUCTURES_PATH = GN_INTERNAL_PATH / 'structure'
GN_INTERNAL_MONITORS_PATH = GN_INTERNAL_PATH / 'monitor'
GN_INTERNAL_SIMULATIONS_PATH = GN_INTERNAL_PATH / 'simulation'
GN_INTERNAL_PATH: Path = ct.addon.PATH_ASSETS / 'internal'
GN_INTERNAL_INPUTS_PATH: Path = GN_INTERNAL_PATH / 'input'
GN_INTERNAL_SOURCES_PATH: Path = GN_INTERNAL_PATH / 'source'
GN_INTERNAL_STRUCTURES_PATH: Path = GN_INTERNAL_PATH / 'structure'
GN_INTERNAL_MONITORS_PATH: Path = GN_INTERNAL_PATH / 'monitor'
GN_INTERNAL_SIMULATIONS_PATH: Path = GN_INTERNAL_PATH / 'simulation'
## Structures
GN_STRUCTURES_PATH = ct.addon.PATH_ASSETS / 'structures'
GN_STRUCTURES_PRIMITIVES_PATH = GN_STRUCTURES_PATH / 'primitives'
GN_STRUCTURES_ARRAYS_PATH = GN_STRUCTURES_PATH / 'arrays'
GN_STRUCTURES_PATH: Path = ct.addon.PATH_ASSETS / 'structures'
GN_STRUCTURES_PRIMITIVES_PATH: Path = GN_STRUCTURES_PATH / 'primitives'
GN_STRUCTURES_ARRAYS_PATH: Path = GN_STRUCTURES_PATH / 'arrays'
class GeoNodes(enum.StrEnum):
@ -473,10 +473,10 @@ class GeoNodesToStructureNode(bpy.types.Operator):
## 3. We compute it manually, to avoid the jank.
node_location = get_view_location(
editor_region,
[
(
event.mouse_x - editor_region.x,
event.mouse_y - editor_region.y,
],
),
context.preferences.system.ui_scale,
)
@ -525,8 +525,8 @@ ASSET_LIB_SPECS: dict[str, Path] = {
}
@bpy.app.handlers.persistent
def initialize_asset_libraries(_: bpy.types.Scene):
@bpy.app.handlers.persistent # type: ignore[misc]
def initialize_asset_libraries(_: bpy.types.Scene) -> None:
"""Before loading a `.blend` file, ensure that the WindowManager properties relied on by NodeAssetPanel are available.
- Several asset libraries, defined under the global `ASSET_LIB_SPECS`, are added/replaced such that the name:path map is respected.
@ -586,4 +586,4 @@ BL_REGISTER = [
BL_HANDLERS: ct.BLHandlers = ct.BLHandlers(load_pre=(initialize_asset_libraries,))
BL_HOTKEYS: list[ct.KeymapItemDef] = []
BL_KEYMAP_ITEMS: list[ct.BLKeymapItem] = []

View File

@ -22,24 +22,25 @@ from .bl import (
BLColorRGBA,
BLEnumElement,
BLEnumID,
BLEventType,
BLEventValue,
BLIcon,
BLIconSet,
BLIDStruct,
BLImportMethod,
BLKeymapItem,
BLModifierType,
BLNodeTreeInterfaceID,
BLOperatorStatus,
BLPropFlag,
BLRegionType,
BLSpaceType,
KeymapItemDef,
ManagedObjName,
PresetName,
PropName,
SocketName,
)
from .bl_handlers import BLHandlers
from .bl_keymap import BLKeymapItem
from .icons import Icon
from .mobj_types import ManagedObjType
from .node_tree_types import (
@ -58,6 +59,8 @@ __all__ = [
'BLColorRGBA',
'BLEnumElement',
'BLEnumID',
'BLEventType',
'BLEventValue',
'BLIcon',
'BLIconSet',
'BLIDStruct',

View File

@ -45,7 +45,7 @@ if 'RUNNING_BLEXT_TESTS' in os.environ:
PATH_ADDON_ROOT.parent / 'dev' / 'local'
) ## TODO: Consult init_settings
else:
PATH_CACHE: Path = Path(bpy.utils.extension_path_user(__package__, create=True))
PATH_CACHE: Path = Path(bpy.utils.extension_path_user(__package__, create=True)) # type: ignore[no-redef]
####################

View File

@ -95,7 +95,6 @@ BLIDStruct: typ.TypeAlias = (
| bpy.types.WorkSpace
| bpy.types.World
)
BLKeymapItem: typ.TypeAlias = typ.Any ## TODO: Better Type
BLPropFlag: typ.TypeAlias = typ.Literal[
'HIDDEN',
'SKIP_SAVE',
@ -112,6 +111,24 @@ BLColorRGBA = tuple[float, float, float, float]
####################
# - Operators
####################
BLRegionType: typ.TypeAlias = typ.Literal[
'WINDOW',
'HEADER',
'CHANNELS',
'TEMPORARY',
'UI',
'TOOLS',
'TOOL_PROPS',
'ASSET_SHELF',
'ASSET_SHELF_HEADER',
'PREVIEW',
'HUD',
'NAVIGATION_BAR',
'EXECUTE',
'FOOTER',
'TOOL_HEADER',
'XR',
]
BLSpaceType: typ.TypeAlias = typ.Literal[
'EMPTY',
'VIEW_3D',
@ -133,32 +150,151 @@ BLSpaceType: typ.TypeAlias = typ.Literal[
'SPREADSHEET',
'PREFERENCES',
]
BLRegionType: typ.TypeAlias = typ.Literal[
'WINDOW',
'HEADER',
'CHANNELS',
'TEMPORARY',
'UI',
'TOOLS',
'TOOL_PROPS',
'ASSET_SHELF',
'ASSET_SHELF_HEADER',
'PREVIEW',
'HUD',
'NAVIGATION_BAR',
'EXECUTE',
'FOOTER',
'TOOL_HEADER',
'XR',
]
BLOperatorStatus: typ.TypeAlias = set[
typ.Literal['RUNNING_MODAL', 'CANCELLED', 'FINISHED', 'PASS_THROUGH', 'INTERFACE']
]
####################
# - Operators
####################
## TODO: Write the rest in.
BLEventType: typ.TypeAlias = typ.Literal[
'NONE',
'LEFTMOUSE',
'MIDDLEMOUSE',
'RIGHTMOUSE',
'BUTTON4MOUSE',
'BUTTON5MOUSE',
'BUTTON6MOUSE',
'BUTTON7MOUSE',
'PEN',
'ERASOR',
'MOUSEMOVE',
'INBETWEEN_MOUSEMOVE',
'TRACKPADPAN',
'TRACKPADZOOM',
'MOUSEROTATE',
'MOUSESMARTZOOM',
'WHEELUPMOUSE',
'WHEELDOWNMOUSE',
'WHEELINMOUSE',
'WHEELOUTMOUSE',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'ZERO',
'ONE',
'TWO',
'THREE',
'FOUR',
'FIVE',
'SIX',
'SEVEN',
'EIGHT',
'NINE',
'LEFT_CTRL',
'LEFT_ALT',
'LEFT_SHIFT',
'RIGHT_ALT',
'RIGHT_CTRL',
'RIGHT_SHIFT',
'ESC',
'TAB',
'RET', ## Enter
'SPACE',
'LINE_FEED',
'BACK_SPACE',
'DEL',
'SEMI_COLON',
'PERIOD',
'COMMA',
'QUOTE',
'ACCENT_GRAVE',
'MINUS',
'PLUS',
'SLASH',
'BACK_SLASH',
'EQUAL',
'LEFT_BRACKET',
'RIGHT_BRACKET',
'LEFT_ARROW',
'DOWN_ARROW',
'RIGHT_ARROW',
'UP_ARROW',
'NUMPAD_0',
'NUMPAD_1',
'NUMPAD_2',
'NUMPAD_3',
'NUMPAD_4',
'NUMPAD_5',
'NUMPAD_6',
'NUMPAD_7',
'NUMPAD_8',
'NUMPAD_9',
'NUMPAD_PERIOD',
'NUMPAD_SLASH',
'NUMPAD_ASTERIX',
'NUMPAD_MINUS',
'NUMPAD_PLUS',
'NUMPAD_ENTER',
'F1',
'F2',
'F3',
'F4',
'F5',
'F6',
'F7',
'F8',
'F9',
'F10',
'F11',
'F12',
'PAUSE',
'INSERT',
'HOME',
'PAGE_UP',
'PAGE_DOWN',
'END',
'MEDIA_PLAY',
'MEDIA_STOP',
'MEDIA_FIRST',
'MEDIA_LAST',
]
BLEventValue: typ.TypeAlias = typ.Literal[
'ANY',
'PRESS',
'RELEASE',
'CLICK',
'DOUBLE_CLICK',
'CLICK_DRAG',
'NOTHING',
]
####################
# - Addon Types
####################
KeymapItemDef: typ.TypeAlias = typ.Any
ManagedObjName = str
####################

View File

@ -58,6 +58,9 @@ class BLHandlers(pyd.BaseModel):
render_post: tuple[BLHandler, ...] = ()
render_pre: tuple[BLHandler, ...] = ()
render_stats: tuple[BLHandler, ...] = ()
## TODO: Verify these type signatures.
## TODO: A validator to check that all handlers are decorated with bpy.app.handlers.persistent
####################
# - Properties

View File

@ -0,0 +1,60 @@
# oscillode
# Copyright (C) 2024 oscillode Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Declares types for working with `bpy.types.KeyMap`s."""
import bpy
import pydantic as pyd
from .bl import BLEventType, BLEventValue, BLSpaceType
from .operator_types import OperatorType
class BLKeymapItem(pyd.BaseModel):
"""Contains lists of handlers associated with this addon."""
operator: OperatorType
event_type: BLEventType
event_value: BLEventValue
shift: bool = False
ctrl: bool = False
alt: bool = False
key_modifier: BLEventType = 'NONE'
space_type: BLSpaceType = 'EMPTY'
def register(self, addon_keymap: bpy.types.KeyMap) -> bpy.types.KeymapItem:
"""Registers this hotkey with an addon keymap.
Raises:
ValueError: If the `space_type` constraint of the addon keymap does not match the `space_type` constraint of this `BLKeymapItem`.
"""
if self.space_type == addon_keymap.space_type:
addon_keymap.keymap_items.new(
self.operator,
self.event_type,
self.event_value,
shift=self.shift,
ctrl=self.ctrl,
alt=self.alt,
key_modifier=self.key_modifier,
)
msg = f'Addon keymap space type {addon_keymap.space_type} does not match space_type of BLKeymapItem to register: {self}'
raise ValueError(msg)
## TODO: Check if space_type matches?

View File

@ -312,6 +312,8 @@ class MaxwellSimTree(bpy.types.NodeTree):
default=True,
)
viewer_node_type: ct.NodeType = ct.NodeType.Viewer
####################
# - Init Methods
####################

View File

@ -16,11 +16,22 @@
"""Blender operators that ship with Oscillode."""
from functools import reduce
from oscillode import contracts as ct
from . import connect_viewer
BL_REGISTER = [
BL_REGISTER: list[ct.BLClass] = [
*connect_viewer.BL_REGISTER,
]
BL_HOTKEYS = [
*connect_viewer.BL_HOTKEYS,
BL_HANDLERS: ct.BLHandlers = reduce(
lambda a, b: a + b,
[
*connect_viewer.BL_HANDLERS,
],
ct.BLHandlers(),
)
BL_KEYMAP_ITEMS: list[ct.BLKeymapItem] = [
*connect_viewer.BL_KEYMAP_ITEMS,
]

View File

@ -19,7 +19,6 @@
import bpy
from oscillode import contracts as ct
from oscillode.node_trees.maxwell_sim_nodes.contracts import node_types
from oscillode.utils import logger
log = logger.get(__name__)
@ -79,16 +78,17 @@ class ConnectViewerNode(bpy.types.Operator):
selected_node = context.selected_nodes[0]
# Find Viewer Node...
viewer_node_type = node_tree.viewer_node_type
for node in node_tree.nodes:
# TODO: Support multiple viewer nodes.
if hasattr(node, 'node_type') and node.node_type is node_types.Viewer:
if hasattr(node, 'node_type') and node.node_type is viewer_node_type:
viewer_node = node
break
# ...OR: Create Viewer Node
else:
# TODO: Place viewer where it doesn't overlap other nodes.
viewer_node = node_tree.nodes.new(node_types.Viewer.value)
viewer_node = node_tree.nodes.new(viewer_node_type)
viewer_node.location.x = selected_node.location.x + 250
viewer_node.location.y = selected_node.location.y
selected_node.select = False
@ -112,19 +112,19 @@ class ConnectViewerNode(bpy.types.Operator):
####################
# - Blender Registration
####################
BL_REGISTER = [
BL_REGISTER: list[ct.BLClass] = [
ConnectViewerNode,
]
BL_HOTKEYS = [
{
'_': (
BL_HANDLERS: ct.BLHandlers = ct.BLHandlers()
BL_KEYMAP_ITEMS: list[ct.BLKeymapItem] = [
ct.BLKeymapItem(
ct.OperatorType.ConnectViewerNode,
'LEFTMOUSE',
'PRESS',
),
'ctrl': True,
'shift': True,
'alt': False,
},
event_type='LEFTMOUSE',
event_value='PRESS',
ctrl=True,
shift=True,
space_type='NODE_EDITOR',
)
]

View File

@ -17,9 +17,13 @@
"""Manages the registration of Blender classes, including delayed registrations that require access to Python dependencies.
Attributes:
_ADDON_KEYMAP: Addon-specific keymap used to register operator hotkeys.
REG__CLASSES: Currently registered Blender classes.
_REGISTERED_HOTKEYS: Currently registered Blender keymap items.
_REGISTERED_CLASSES: Blender classes currently registered by this addon.
_REGISTERED_KEYMAPS: Addon keymaps currently registered by this addon.
Each addon keymap is constrained to a single `space_type`, which is the key.
_REGISTERED_KEYMAP_ITEMS: Addon keymap items currently registered by this addon.
Each keymap item is paired to the keymap within which it is registered.
_Each keymap is guaranteed to also be found in `_REGISTERED_KEYMAPS`._
_REGISTERED_HANDLERS: Addon handlers currently registered by this addon.
"""
import bpy
@ -34,8 +38,8 @@ log = logger.get(__name__)
####################
_REGISTERED_CLASSES: list[ct.BLClass] = []
_ADDON_KEYMAP: bpy.types.KeyMap | None = None
_REGISTERED_HOTKEYS: list[ct.BLKeymapItem] = []
_REGISTERED_KEYMAPS: dict[ct.BLSpaceType, bpy.types.KeyMap] = {}
_REGISTERED_KEYMAP_ITEMS: list[tuple[bpy.types.KeyMap, bpy.types.KeymapItem]] = []
_REGISTERED_HANDLERS: ct.BLHandlers | None = None
@ -109,59 +113,47 @@ def unregister_handlers() -> None:
####################
# - Keymap Registration
####################
def register_hotkeys(hotkey_defs: list[dict]) -> None:
def register_keymaps(keymap_items: list[ct.BLKeymapItem]) -> None:
"""Registers a list of Blender hotkey definitions.
Parameters:
hotkey_defs: List of Blender hotkey definitions to register.
bl_keymap_items: List of Blender hotkey definitions to register.
"""
# Lazy-Load BL_NODE_KEYMAP
global _ADDON_KEYMAP # noqa: PLW0603
if _ADDON_KEYMAP is None:
_ADDON_KEYMAP = bpy.context.window_manager.keyconfigs.addon.keymaps.new(
name='Node Editor',
space_type='NODE_EDITOR',
)
log.info(
'Registered Addon Keymap (Base for Keymap Items): %s',
str(_ADDON_KEYMAP),
)
# Aggregate Requested Spaces of All Keymap Items
keymap_space_types: set[ct.BLSpaceType] = {
keymap_item.space_type for keymap_item in keymap_items
}
# Register Keymaps
log.info('Registering %s Keymap Items', len(hotkey_defs))
for keymap_item_def in hotkey_defs:
keymap_item = _ADDON_KEYMAP.keymap_items.new(
*keymap_item_def['_'],
ctrl=keymap_item_def['ctrl'],
shift=keymap_item_def['shift'],
alt=keymap_item_def['alt'],
# Create Addon Keymap per Requested Space
for keymap_space_type in keymap_space_types:
log.info('Registering %s Keymap', keymap_space_type)
bl_keymap = bpy.context.window_manager.keyconfigs.addon.keymaps.new(
name=f'{ct.addon.NAME} - {keymap_space_type}',
space_type=keymap_space_type,
)
log.debug(
'Registered Keymap Item %s with spec %s',
repr(keymap_item),
keymap_item_def,
)
_REGISTERED_HOTKEYS.append(keymap_item)
_REGISTERED_KEYMAPS[keymap_space_type] = bl_keymap
# Register Keymap Items in Correct Addon Keymap
for keymap_item in keymap_items:
log.info('Registering %s Keymap Item', keymap_item)
bl_keymap = _REGISTERED_KEYMAPS[keymap_item.space_type]
bl_keymap_item = keymap_item.register(bl_keymap)
_REGISTERED_KEYMAP_ITEMS.append((bl_keymap, bl_keymap_item))
def unregister_hotkeys() -> None:
"""Unregisters all Blender hotkeys associated with the addon."""
global _ADDON_KEYMAP # noqa: PLW0603
def unregister_keymaps() -> None:
"""Unregisters all Blender keymaps associated with the addon."""
# Unregister Keymap Items from Correct Addon Keymap
for bl_keymap, bl_keymap_item in reversed(_REGISTERED_KEYMAP_ITEMS):
log.info('Unregistering %s BL Keymap Item', bl_keymap_item)
bl_keymap.keymap_items.remove(bl_keymap_item)
# Unregister Keymaps
log.info('Unregistering %s Keymap Items', len(_REGISTERED_HOTKEYS))
for keymap_item in reversed(_REGISTERED_HOTKEYS):
log.debug(
'Unregistered Keymap Item %s',
repr(keymap_item),
)
_ADDON_KEYMAP.keymap_items.remove(keymap_item)
_REGISTERED_KEYMAP_ITEMS.clear()
# Lazy-Unload BL_NODE_KEYMAP
if _ADDON_KEYMAP is not None:
log.info(
'Unregistered Keymap %s',
repr(_ADDON_KEYMAP),
)
_REGISTERED_HOTKEYS.clear()
_ADDON_KEYMAP = None
# Delete Addon Keymaps
for bl_keymap in reversed(_REGISTERED_KEYMAPS.values()):
log.info('Unregistering %s Keymap', bl_keymap)
bpy.context.window_manager.keyconfigs.addon.keymaps.remove(bl_keymap)
_REGISTERED_KEYMAPS.clear()