Source code for linkforge.blender.adapters.context

"""Abstraction layer for Blender host environment.

This module defines the IBlenderContext protocol, which decouples LinkForge
from direct bpy dependencies, enabling pure unit testing and multi-platform
support.
"""

from __future__ import annotations

import typing
from collections.abc import Iterable
from typing import Any, runtime_checkable


[docs] @runtime_checkable class IBlenderContext(typing.Protocol): """Protocol defining the necessary Blender environment access.""" @property def scene(self) -> Any: """Active Blender scene.""" ... @property def data(self) -> Any: """Access to bpy.data.""" ... @property def ops(self) -> Any: """Access to bpy.ops.""" ... @property def view_layer(self) -> Any: """Active Blender view layer.""" ... @property def active_object(self) -> Any | None: """Currently active Blender object.""" ... @property def preferences(self) -> Any: """Blender user preferences.""" ... @property def window_manager(self) -> Any: """Blender window manager.""" ...
[docs] def get_objects(self) -> Iterable[Any]: """Retrieve all objects relevant for the current context.""" ...
[docs] def get_active_object(self) -> Any | None: """Retrieve the currently active object.""" ...
class BlenderContext: """Real-world implementation of IBlenderContext using live bpy.""" def __init__(self, bpy_instance: Any = None): """Initialize with a specific bpy instance (defaults to global bpy).""" import bpy self._global_bpy = bpy if bpy_instance is None: self._bpy = bpy else: self._bpy = bpy_instance @property def _ctx(self) -> Any: """Internal helper to get the active context object.""" # If we are holding the bpy module, return bpy.context # Otherwise, assume we are holding a context object directly if hasattr(self._bpy, "context"): return self._bpy.context return self._bpy @property def scene(self) -> Any: """Return the active scene from context.""" return self._ctx.scene @property def data(self) -> Any: """Return the data block.""" if hasattr(self._bpy, "data"): return self._bpy.data return self._global_bpy.data @property def ops(self) -> Any: """Return the operators block.""" if hasattr(self._bpy, "ops"): return self._bpy.ops return self._global_bpy.ops @property def view_layer(self) -> Any: """Return the active view layer.""" return self._ctx.view_layer @property def active_object(self) -> Any | None: """Return the active object from context.""" return self._ctx.active_object @property def preferences(self) -> Any: """Return the user preferences.""" return self._ctx.preferences @property def window_manager(self) -> Any: """Return the window manager.""" return self._ctx.window_manager def get_objects(self) -> Iterable[Any]: """Return all objects in the current scene.""" return typing.cast(Iterable[Any], self.data.objects) def get_active_object(self) -> Any | None: """Return the active object from context.""" return self.active_object