"""
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