From 55235c7032c59ab51480a302171b26f8a34c7ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sofus=20Albert=20H=C3=B8gsbro=20Rose?= Date: Wed, 2 Oct 2024 11:24:48 +0200 Subject: [PATCH] feat: Added handler bubble-up logic. --- oscillode/__init__.py | 20 ++++- oscillode/assets/__init__.py | 17 +++- oscillode/assets/geonodes.py | 6 +- oscillode/contracts/__init__.py | 2 + oscillode/contracts/bl_handlers.py | 122 +++++++++++++++++++++++++++++ oscillode/registration.py | 32 ++++++++ pyproject.toml | 6 ++ 7 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 oscillode/contracts/bl_handlers.py diff --git a/oscillode/__init__.py b/oscillode/__init__.py index caa112f..98cbea7 100644 --- a/oscillode/__init__.py +++ b/oscillode/__init__.py @@ -16,7 +16,10 @@ """A visual DSL for electromagnetic simulation design and analysis implemented as a Blender node editor.""" -# from . import assets, node_trees, operators, preferences, registration +from functools import reduce + +# from . import node_trees, operators, preferences, registration +from . import assets, preferences, registration from . import contracts as ct from .utils import logger @@ -32,6 +35,17 @@ BL_REGISTER: list[ct.BLClass] = [ # *node_trees.BL_REGISTER, ] +BL_HANDLERS: ct.BLHandlers = reduce( + lambda a, b: a + b, + [ + assets.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, @@ -64,6 +78,7 @@ def register() -> None: log.info('Registering Addon: %s', ct.addon.NAME) registration.register_classes(BL_REGISTER) + registration.register_handlers(BL_HANDLERS) registration.register_hotkeys(BL_HOTKEYS) log.info('Finished Registration of Addon: %s', ct.addon.NAME) @@ -83,7 +98,8 @@ def unregister() -> None: """ log.info('Starting %s Unregister', ct.addon.NAME) - registration.unregister_classes() registration.unregister_hotkeys() + registration.unregister_handlers() + registration.unregister_classes() log.info('Finished %s Unregister', ct.addon.NAME) diff --git a/oscillode/assets/__init__.py b/oscillode/assets/__init__.py index 2ba4717..d4c2f1d 100644 --- a/oscillode/assets/__init__.py +++ b/oscillode/assets/__init__.py @@ -14,17 +14,30 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from functools import reduce + +import oscillode.contracts as ct + from . import geonodes -BL_REGISTER = [ +BL_REGISTER: list[ct.BLClass] = [ *geonodes.BL_REGISTER, ] -BL_HOTKEYS = [ +BL_HANDLERS: ct.BLHandlers = reduce( + lambda a, b: a + b, + [ + geonodes.BL_HANDLERS, + ], + ct.BLHandlers(), +) + +BL_HOTKEYS: list[ct.KeymapItemDef] = [ *geonodes.BL_HOTKEYS, ] __all__ = [ 'BL_REGISTER', + 'BL_HANDLERS', 'BL_HOTKEYS', ] diff --git a/oscillode/assets/geonodes.py b/oscillode/assets/geonodes.py index fb7ac37..e6fbce7 100644 --- a/oscillode/assets/geonodes.py +++ b/oscillode/assets/geonodes.py @@ -579,11 +579,11 @@ def initialize_asset_libraries(_: bpy.types.Scene): ) -bpy.app.handlers.load_pre.append(initialize_asset_libraries) - BL_REGISTER = [ NodeAssetPanel, GeoNodesToStructureNode, ] -BL_HOTKEYS = [] +BL_HANDLERS: ct.BLHandlers = ct.BLHandlers(load_pre=(initialize_asset_libraries,)) + +BL_HOTKEYS: list[ct.KeymapItemDef] = [] diff --git a/oscillode/contracts/__init__.py b/oscillode/contracts/__init__.py index 620cd55..58f65d4 100644 --- a/oscillode/contracts/__init__.py +++ b/oscillode/contracts/__init__.py @@ -39,6 +39,7 @@ from .bl import ( PropName, SocketName, ) +from .bl_handlers import BLHandlers from .icons import Icon from .mobj_types import ManagedObjType from .node_tree_types import ( @@ -74,6 +75,7 @@ __all__ = [ 'PresetName', 'PropName', 'SocketName', + 'BLHandlers', 'Icon', 'BLInstance', 'InstanceID', diff --git a/oscillode/contracts/bl_handlers.py b/oscillode/contracts/bl_handlers.py new file mode 100644 index 0000000..914a580 --- /dev/null +++ b/oscillode/contracts/bl_handlers.py @@ -0,0 +1,122 @@ +# 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 . + +"""Declares types for working with `bpy.app.handlers` callbacks.""" + +import typing as typ + +import bpy +import pydantic as pyd + +from oscillode.utils.staticproperty import staticproperty + +BLHandler = typ.Callable[[], None] +BLHandlerWithFile = typ.Callable[[str], None] +BLHandlerWithRenderStats = typ.Callable[[typ.Any], None] + + +class BLHandlers(pyd.BaseModel): + """Contains lists of handlers associated with this addon.""" + + animation_playback_post: tuple[BLHandler, ...] = () + animation_playback_pre: tuple[BLHandler, ...] = () + annotation_post: tuple[BLHandler, ...] = () + annotation_pre: tuple[BLHandler, ...] = () + composite_cancel: tuple[BLHandler, ...] = () + composite_post: tuple[BLHandler, ...] = () + composite_pre: tuple[BLHandler, ...] = () + depsgraph_update_post: tuple[BLHandler, ...] = () + depsgraph_update_pre: tuple[BLHandler, ...] = () + frame_change_post: tuple[BLHandler, ...] = () + frame_change_pre: tuple[BLHandler, ...] = () + load_factory_preferences_post: tuple[BLHandler, ...] = () + load_factory_startup_post: tuple[BLHandler, ...] = () + load_post: tuple[BLHandlerWithFile, ...] = () + load_post_fail: tuple[BLHandlerWithFile, ...] = () + load_pre: tuple[BLHandlerWithFile, ...] = () + object_bake_cancel: tuple[BLHandler, ...] = () + object_bake_complete: tuple[BLHandler, ...] = () + object_bake_pre: tuple[BLHandler, ...] = () + redo_post: tuple[BLHandler, ...] = () + redo_pre: tuple[BLHandler, ...] = () + render_cancel: tuple[BLHandler, ...] = () + render_complete: tuple[BLHandler, ...] = () + render_init: tuple[BLHandler, ...] = () + render_post: tuple[BLHandler, ...] = () + render_pre: tuple[BLHandler, ...] = () + render_stats: tuple[BLHandler, ...] = () + + #################### + # - Properties + #################### + @staticproperty # type: ignore[arg-type] + def hander_categories() -> tuple[str, ...]: # type: ignore[misc] + """Returns an immutable string sequence of handler categories.""" + return ( + 'animation_playback_post', + 'animation_playback_pre', + 'annotation_post', + 'annotation_pre', + 'composite_cancel', + 'composite_post', + 'composite_pre', + 'depsgraph_update_post', + 'depsgraph_update_pre', + 'frame_change_post', + 'frame_change_pre', + 'load_factory_preferences_post', + 'load_factory_startup_post', + 'load_post', + 'load_post_fail', + 'load_pre', + 'object_bake_cancel', + 'object_bake_complete', + 'object_bake_pre', + 'redo_post', + 'redo_pre', + 'render_cancel', + 'render_complete', + 'render_init', + 'render_post', + 'render_pre', + 'render_stats', + ) + + #################### + # - Merging + #################### + def __add__(self, other: typ.Self) -> typ.Self: + """Concatenate the handlers of two `BLHandlers` objects.""" + return BLHandlers( + **{ + hndl_cat: getattr(self, hndl_cat) + getattr(self, hndl_cat) + for hndl_cat in self.handler_categories + } + ) + + def register(self) -> None: + """Registers all handlers declared by-category.""" + for handler_category in BLHandlers.handler_categories: + for handler in getattr(self, handler_category): + getattr(bpy.app.handlers, handler_category).append(handler) + + def unregister(self) -> None: + """Unregisters only this addon's handlers from bpy.app.handlers.""" + for handler_category in BLHandlers.handler_categories: + for handler in getattr(self, handler_category): + bpy_handlers = getattr(bpy.app.handlers, handler_category) + if handler in bpy_handlers: + bpy_handlers.remove(handler) diff --git a/oscillode/registration.py b/oscillode/registration.py index f1fd812..b12e4f5 100644 --- a/oscillode/registration.py +++ b/oscillode/registration.py @@ -33,9 +33,12 @@ log = logger.get(__name__) # - Globals #################### _REGISTERED_CLASSES: list[ct.BLClass] = [] + _ADDON_KEYMAP: bpy.types.KeyMap | None = None _REGISTERED_HOTKEYS: list[ct.BLKeymapItem] = [] +_REGISTERED_HANDLERS: ct.BLHandlers | None = None + #################### # - Class Registration @@ -74,6 +77,35 @@ def unregister_classes() -> None: _REGISTERED_CLASSES.clear() +#################### +# - Handler Registration +#################### +def register_handlers(bl_handlers: ct.BLHandlers) -> None: + """Register the given Blender handlers.""" + global _REGISTERED_HANDLERS # noqa: PLW0603 + + log.info('Registering BLHandlers') ## TODO: More information + if _REGISTERED_HANDLERS is None: + bl_handlers.register() + _REGISTERED_HANDLERS = bl_handlers + + msg = 'There are already BLHandlers registered; they must be unregistered before a new set can be registered.' + raise ValueError(msg) + + +def unregister_handlers() -> None: + """Unregister this addon's registered Blender handlers.""" + global _REGISTERED_HANDLERS # noqa: PLW0603 + + log.info('Unregistering BLHandlers') ## TODO: More information + if _REGISTERED_HANDLERS is not None: + _REGISTERED_HANDLERS.register() + _REGISTERED_HANDLERS = None + + msg = 'There are no BLHandlers registered; therefore, there is nothing to register.' + raise ValueError(msg) + + #################### # - Keymap Registration #################### diff --git a/pyproject.toml b/pyproject.toml index 69f2446..be42db3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -193,6 +193,12 @@ ignore = [ "E501", # Let Formatter Worry about Line Length ] +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + "D100", # It's okay to not have module-level docstrings in test modules. + "D104", # Same for packages. +] + #################### # - Tooling: Ruff Sublinters ####################