197 lines
6.2 KiB
Python
197 lines
6.2 KiB
Python
"""Manages the registration of Blender classes, including delayed registrations that require access to Python dependencies.
|
|
|
|
Attributes:
|
|
BL_KEYMAP: Addon-specific keymap used to register operator hotkeys. REG__CLASSES: Currently registered Blender classes.
|
|
REG__KEYMAP_ITEMS: Currently registered Blender keymap items.
|
|
DELAYED_REGISTRATIONS: Currently pending registration operations, which can be realized with `run_delayed_registration()`.
|
|
EVENT__DEPS_SATISFIED: A constant representing a semantic choice of key for `DELAYED_REGISTRATIONS`.
|
|
"""
|
|
|
|
import typing as typ
|
|
from pathlib import Path
|
|
|
|
import bpy
|
|
|
|
from .nodeps.utils import simple_logger
|
|
|
|
log = simple_logger.get(__name__)
|
|
|
|
# TODO: More types for these things!
|
|
DelayedRegKey: typ.TypeAlias = str
|
|
BLClass: typ.TypeAlias = (
|
|
bpy.types.Panel
|
|
| bpy.types.UIList
|
|
| bpy.types.Menu
|
|
| bpy.types.Header
|
|
| bpy.types.Operator
|
|
| bpy.types.KeyingSetInfo
|
|
| bpy.types.RenderEngine
|
|
| bpy.types.AssetShelf
|
|
| bpy.types.FileHandler
|
|
)
|
|
BLKeymapItem: typ.TypeAlias = typ.Any ## TODO: Better Type
|
|
KeymapItemDef: typ.TypeAlias = typ.Any ## TODO: Better Type
|
|
|
|
####################
|
|
# - Globals
|
|
####################
|
|
BL_KEYMAP: bpy.types.KeyMap | None = None
|
|
|
|
REG__CLASSES: list[BLClass] = []
|
|
REG__KEYMAP_ITEMS: list[BLKeymapItem] = []
|
|
|
|
DELAYED_REGISTRATIONS: dict[DelayedRegKey, typ.Callable[[Path], None]] = {}
|
|
|
|
####################
|
|
# - Delayed Registration Keys
|
|
####################
|
|
EVENT__DEPS_SATISFIED: DelayedRegKey = 'on_deps_satisfied'
|
|
|
|
|
|
####################
|
|
# - Class Registration
|
|
####################
|
|
def register_classes(bl_register: list[BLClass]) -> None:
|
|
"""Registers a Blender class, allowing it to hook into relevant Blender features.
|
|
|
|
Caches registered classes in the module global `REG__CLASSES`.
|
|
|
|
Parameters:
|
|
bl_register: List of Blender classes to register.
|
|
"""
|
|
log.info('Registering %s Classes', len(bl_register))
|
|
for cls in bl_register:
|
|
if cls.bl_idname in REG__CLASSES:
|
|
msg = f'Skipping register of {cls.bl_idname}'
|
|
log.info(msg)
|
|
continue
|
|
|
|
log.debug(
|
|
'Registering Class %s',
|
|
repr(cls),
|
|
)
|
|
bpy.utils.register_class(cls)
|
|
REG__CLASSES.append(cls)
|
|
|
|
|
|
def unregister_classes() -> None:
|
|
"""Unregisters all previously registered Blender classes.
|
|
|
|
All previously registered Blender classes can be found in the module global variable `REG__CLASSES`.
|
|
"""
|
|
log.info('Unregistering %s Classes', len(REG__CLASSES))
|
|
for cls in reversed(REG__CLASSES):
|
|
log.debug(
|
|
'Unregistering Class %s',
|
|
repr(cls),
|
|
)
|
|
bpy.utils.unregister_class(cls)
|
|
|
|
REG__CLASSES.clear()
|
|
|
|
|
|
####################
|
|
# - Keymap Registration
|
|
####################
|
|
def register_keymap_items(keymap_item_defs: list[dict]):
|
|
# Lazy-Load BL_NODE_KEYMAP
|
|
global BL_KEYMAP # noqa: PLW0603
|
|
if BL_KEYMAP is None:
|
|
BL_KEYMAP = bpy.context.window_manager.keyconfigs.addon.keymaps.new(
|
|
name='Node Editor',
|
|
space_type='NODE_EDITOR',
|
|
)
|
|
log.info(
|
|
'Registered Keymap %s',
|
|
str(BL_KEYMAP),
|
|
)
|
|
|
|
# Register Keymaps
|
|
log.info('Registering %s Keymap Items', len(keymap_item_defs))
|
|
for keymap_item_def in keymap_item_defs:
|
|
keymap_item = BL_KEYMAP.keymap_items.new(
|
|
*keymap_item_def['_'],
|
|
ctrl=keymap_item_def['ctrl'],
|
|
shift=keymap_item_def['shift'],
|
|
alt=keymap_item_def['alt'],
|
|
)
|
|
log.debug(
|
|
'Registered Keymap Item %s with spec %s',
|
|
repr(keymap_item),
|
|
keymap_item_def,
|
|
)
|
|
REG__KEYMAP_ITEMS.append(keymap_item)
|
|
|
|
|
|
def unregister_keymap_items():
|
|
global BL_KEYMAP # noqa: PLW0603
|
|
|
|
# Unregister Keymaps
|
|
log.info('Unregistering %s Keymap Items', len(REG__KEYMAP_ITEMS))
|
|
for keymap_item in reversed(REG__KEYMAP_ITEMS):
|
|
log.debug(
|
|
'Unregistered Keymap Item %s',
|
|
repr(keymap_item),
|
|
)
|
|
BL_KEYMAP.keymap_items.remove(keymap_item)
|
|
|
|
# Lazy-Unload BL_NODE_KEYMAP
|
|
if BL_KEYMAP is not None:
|
|
log.info(
|
|
'Unregistered Keymap %s',
|
|
repr(BL_KEYMAP),
|
|
)
|
|
REG__KEYMAP_ITEMS.clear()
|
|
BL_KEYMAP = None
|
|
|
|
|
|
####################
|
|
# - Delayed Registration Semantics
|
|
####################
|
|
def delay_registration(
|
|
delayed_reg_key: DelayedRegKey,
|
|
classes_cb: typ.Callable[[Path], list[BLClass]],
|
|
keymap_item_defs_cb: typ.Callable[[Path], list[KeymapItemDef]],
|
|
) -> None:
|
|
"""Delays the registration of Blender classes that depend on certain Python dependencies, for which neither the location nor validity is yet known.
|
|
|
|
The function that registers is stored in the module global `DELAYED_REGISTRATIONS`, indexed by `delayed_reg_key`.
|
|
Once the PyDeps location and validity is determined, `run_delayed_registration()` can be used as a shorthand for accessing `DELAYED_REGISTRATIONS[delayed_reg_key]`.
|
|
|
|
Parameters:
|
|
delayed_reg_key: The identifier with which to index the registration callback.
|
|
Module-level constants like `EVENT__DEPS_SATISFIED` are a good choice.
|
|
classes_cb: A function that takes a `sys.path`-compatible path to Python dependencies needed by the Blender classes in question, and returns a list of Blender classes to import.
|
|
`register_classes()` will be used to actually register the returned Blender classes.
|
|
keymap_item_defs_cb: Similar, except for addon keymap items.
|
|
|
|
Returns:
|
|
A function that takes a `sys.path`-compatible path to the Python dependencies needed to import the given Blender classes.
|
|
"""
|
|
if delayed_reg_key in DELAYED_REGISTRATIONS:
|
|
msg = f'Already delayed a registration with key {delayed_reg_key}'
|
|
raise ValueError(msg)
|
|
|
|
def register_cb(path_pydeps: Path):
|
|
log.info(
|
|
'Running Delayed Registration (key %s) with PyDeps: %s',
|
|
delayed_reg_key,
|
|
path_pydeps,
|
|
)
|
|
register_classes(classes_cb(path_pydeps))
|
|
register_keymap_items(keymap_item_defs_cb(path_pydeps))
|
|
|
|
DELAYED_REGISTRATIONS[delayed_reg_key] = register_cb
|
|
|
|
|
|
def run_delayed_registration(delayed_reg_key: DelayedRegKey, path_pydeps: Path) -> None:
|
|
"""Run a delayed registration, by using `delayed_reg_key` to lookup the correct path, passing `path_pydeps` to the registration.
|
|
|
|
Parameters:
|
|
delayed_reg_key: The identifier with which to index the registration callback.
|
|
Must match the parameter with which the delayed registration was first declared.
|
|
path_pydeps: The `sys.path`-compatible path to the Python dependencies that the classes need to have available in order to register.
|
|
"""
|
|
register_cb = DELAYED_REGISTRATIONS.pop(delayed_reg_key)
|
|
register_cb(path_pydeps)
|