Source code for colour_visuals.visual

"""
Visual Utilities
================

Define the visual utilities.
"""

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from contextlib import contextmanager

import pygfx as gfx
from colour.colorimetry import (
    MSDS_CMFS,
    SDS_ILLUMINANTS,
    MultiSpectralDistributions,
    SpectralDistribution,
)
from colour.hints import (
    Any,
    ArrayLike,
    Generator,
    Literal,
    LiteralColourspaceModel,
    LiteralRGBColourspace,
    Sequence,
    Type,
    cast,
)
from colour.models import (
    COLOURSPACE_MODELS,
    RGB_Colourspace,
    RGB_COLOURSPACE_sRGB,
)
from colour.plotting import (
    METHODS_CHROMATICITY_DIAGRAM,
    filter_cmfs,
    filter_illuminants,
    filter_RGB_colourspaces,
)
from colour.utilities import first_item, validate_method

__author__ = "Colour Developers"
__copyright__ = "Copyright 2023 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "visual_property",
    "Visual",
    "MixinPropertyCMFS",
    "MixinPropertyColour",
    "MixinPropertyColourspace",
    "MixinPropertyIlluminant",
    "MixinPropertyKwargs",
    "MixinPropertyTypeMaterial",
    "MixinPropertyMethod",
    "MixinPropertyModel",
    "MixinPropertyOpacity",
    "MixinPropertySamples",
    "MixinPropertySegments",
    "MixinPropertySize",
    "MixinPropertyThickness",
    "MixinPropertyWireframe",
]


[docs] class visual_property(property): """ Define a :class:`property` sub-class calling the :class:`colour_visuals.Visual.update` method. """
[docs] def __set__(self, obj: Any, value: Any) -> None: """Reimplement the :class:`property.__set__` method.""" super().__set__(obj, value) obj.update()
[docs] class Visual(gfx.Group, metaclass=ABCMeta): """Define the base class for the visuals."""
[docs] def __init__(self) -> None: self._is_update_blocked = False super().__init__()
[docs] @contextmanager def block_update(self) -> Generator: """Define a context manager that blocks the visual updates.""" self._is_update_blocked = True yield self._is_update_blocked = False
[docs] @abstractmethod def update(self) -> None: """ Update the visual. Notes ----- - Must be reimplemented by sub-classes. """
[docs] class MixinPropertyCMFS: """ Define a mixin for a standard observer colour matching functions, default to the *CIE 1931 2 Degree Standard Observer*. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyCMFS.cmfs` """
[docs] def __init__(self) -> None: self._cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] super().__init__()
@visual_property def cmfs( self, ) -> MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]: """ Getter and setter property for the standard observer colour matching functions. Parameters ---------- value Value to set the standard observer colour matching functions with. Returns ------- :class:`colour.MultiSpectralDistributions` or :class:`str` or \ :class:`Sequence` Standard observer colour matching functions. """ return self._cmfs
[docs] @cmfs.setter def cmfs( self, value: ( MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str] ), ) -> None: """Setter for the **self.cmfs** property.""" self._cmfs = cast( "MultiSpectralDistributions", first_item(filter_cmfs(value).values()), )
[docs] class MixinPropertyColour: """ Define a mixin for a colour. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyColour.colour` """
[docs] def __init__(self) -> None: self._colour = None super().__init__()
@visual_property def colour(self) -> ArrayLike | None: """ Getter and setter property for the colour. Parameters ---------- value Value to set the colour with. Returns ------- ArrayLike or None Visual colour. """ return self._colour
[docs] @colour.setter def colour(self, value: ArrayLike | None) -> None: """Setter for the **self.colour** property.""" self._colour = value
[docs] class MixinPropertyColourspace: """ Define a mixin for a *RGB* colourspace. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyColour.colour` """
[docs] def __init__(self) -> None: self._colourspace = RGB_COLOURSPACE_sRGB super().__init__()
@visual_property def colourspace( self, ) -> ( RGB_Colourspace | LiteralRGBColourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] ): """ Getter and setter property for the *RGB* colourspace. Parameters ---------- value Value to set the *RGB* colourspace with. Returns ------- :class:`colour.RGB_Colourspace` or :class:`str` or :class:`Sequence` Colourspace. """ return self._colourspace
[docs] @colourspace.setter def colourspace( self, value: ( RGB_Colourspace | LiteralRGBColourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] ), ) -> None: """Setter for the **self.colourspace** property.""" self._colourspace = cast( "RGB_Colourspace", first_item(filter_RGB_colourspaces(value).values()), )
[docs] class MixinPropertyIlluminant: """ Define a mixin for an illuminant spectral distribution. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyIlluminant.illuminant` """
[docs] def __init__(self) -> None: self._illuminant = SDS_ILLUMINANTS["E"] super().__init__()
@visual_property def illuminant( self, ) -> SpectralDistribution | str | Sequence[SpectralDistribution | str]: """ Getter and setter property for the illuminant spectral distribution. Parameters ---------- value Value to set the illuminant spectral distribution with. Returns ------- :class:`colour.SpectralDistribution` or :class:`str` or \ :class:`Sequence` Illuminant spectral distribution. """ return self._illuminant
[docs] @illuminant.setter def illuminant( self, value: ( SpectralDistribution | str | Sequence[SpectralDistribution | str] ) = "E", ) -> None: """Setter for the **self.illuminant** property.""" self._illuminant = cast( "SpectralDistribution", first_item(filter_illuminants(value).values()), )
[docs] class MixinPropertyKwargs: """ Define a mixin for keyword arguments. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyKwargs.kwargs` """
[docs] def __init__(self) -> None: self._kwargs = {} super().__init__()
@visual_property def kwargs(self) -> dict: """ Getter and setter property for the keyword arguments. Parameters ---------- value Value to set keyword arguments with. Returns ------- :class:`dict` Keyword arguments. """ return self._kwargs
[docs] @kwargs.setter def kwargs(self, value: dict) -> None: """Setter for the **self.kwargs** property.""" self._kwargs = value
[docs] class MixinPropertyTypeMaterial: """ Define a mixin for a material type. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyTypeMaterial.type_material` """
[docs] def __init__(self) -> None: self._type_material = gfx.MeshBasicMaterial super().__init__()
@visual_property def type_material( self, ) -> Type[gfx.MeshAbstractMaterial]: """ Getter and setter property for the material type. Parameters ---------- value Value to set the material type with. Returns ------- :class:`gfx.MeshAbstractMaterial` Material type. """ return self._type_material
[docs] @type_material.setter def type_material(self, value: Type[gfx.MeshAbstractMaterial]) -> None: """Setter for the **self.material** property.""" self._type_material = value
[docs] class MixinPropertyMethod: """ Define a mixin for a *Chromaticity Diagram* method. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyMethod.method` """
[docs] def __init__(self) -> None: self._method = "CIE 1931" super().__init__()
@visual_property def method( self, ) -> Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str: """ Getter and setter property for the *Chromaticity Diagram* method. Parameters ---------- value Value to set the *Chromaticity Diagram* method with. Returns ------- :class:`str` *Chromaticity Diagram* method. """ return self._method
[docs] @method.setter def method( self, value: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str ) -> None: """Setter for the **self.method** property.""" self._method = validate_method(value, tuple(METHODS_CHROMATICITY_DIAGRAM))
[docs] class MixinPropertyModel: """ Define a mixin for a colourspace model. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyModel.model` """
[docs] def __init__(self) -> None: self._model = "CIE xyY" super().__init__()
@visual_property def model(self) -> LiteralColourspaceModel | str: """ Getter and setter property for the colourspace model. Parameters ---------- value Value to set the colourspace model with. Returns ------- :class:`str` Colourspace model. """ return self._model
[docs] @model.setter def model(self, value: LiteralColourspaceModel | str) -> None: """Setter for the **self.model** property.""" self._model = validate_method(value, tuple(COLOURSPACE_MODELS))
[docs] class MixinPropertyOpacity: """ Define a mixin for an opacity value. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyOpacity.opacity` """
[docs] def __init__(self) -> None: self._opacity = 1 super().__init__()
@visual_property def opacity(self) -> float: """ Getter and setter property for the opacity value. Parameters ---------- value Value to set the opacity value with. Returns ------- :class:`float` Visual opacity. """ return self._opacity
[docs] @opacity.setter def opacity(self, value: float) -> None: """Setter for the **self.opacity** property.""" self._opacity = value
[docs] class MixinPropertySamples: """ Define a mixin for a sample count. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertySamples.samples` """
[docs] def __init__(self) -> None: self._samples = 1 super().__init__()
@visual_property def samples(self) -> int: """ Getter and setter property for the sample count. Parameters ---------- value Value to set sample count with. Returns ------- :class:`int` Sample count. """ return self._samples
[docs] @samples.setter def samples(self, value: int) -> None: """Setter for the **self.samples** property.""" self._samples = value
[docs] class MixinPropertySegments: """ Define a mixin for a segment count. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertySegments.segments` """
[docs] def __init__(self) -> None: self._segments = 16 super().__init__()
@visual_property def segments(self) -> int: """ Getter and setter property for the segment count. Parameters ---------- value Value to set segment count with. Returns ------- :class:`int` Sample count. """ return self._segments
[docs] @segments.setter def segments(self, value: int) -> None: """Setter for the **self.segments** property.""" self._segments = value
[docs] class MixinPropertySize: """ Define a mixin for a size value. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertySize.size` """
[docs] def __init__(self) -> None: self._size = 1 super().__init__()
@visual_property def size(self) -> float: """ Getter and setter property for the size value. Parameters ---------- value Value to set size value with. Returns ------- :class:`int` Size value. """ return self._size
[docs] @size.setter def size(self, value: float) -> None: """Setter for the **self.size** property.""" self._size = value
[docs] class MixinPropertyThickness: """ Define a mixin for a thickness value. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyThickness.thickness` """
[docs] def __init__(self) -> None: self._thickness = 1 super().__init__()
@visual_property def thickness(self) -> float: """ Getter and setter property for the thickness value. Parameters ---------- value Value to set the thickness value with. Returns ------- :class:`float` Thickness value. """ return self._thickness
[docs] @thickness.setter def thickness(self, value: float) -> None: """Setter for the **self.thickness** property.""" self._thickness = value
[docs] class MixinPropertyWireframe: """ Define a mixin for a wireframe state. Attributes ---------- - :attr:`~colour_visuals.visual.MixinPropertyWireframe.wireframe` """
[docs] def __init__(self) -> None: self._wireframe = False super().__init__()
@visual_property def wireframe(self) -> bool: """ Getter and setter property for the wireframe state. Parameters ---------- value Value to set wireframe state with. Returns ------- :class:`bool` Wireframe state. """ return self._wireframe
[docs] @wireframe.setter def wireframe(self, value: bool) -> None: """Setter for the **self.wireframe** property.""" self._wireframe = value