"""API description for managing plotting theme parameters in pyvista. Examples -------- Apply a built-in theme >>> import pyvista as pv >>> pv.set_plot_theme('default') >>> pv.set_plot_theme('document') >>> pv.set_plot_theme('dark') >>> pv.set_plot_theme('paraview') Load a theme into pyvista >>> from pyvista.plotting.themes import DocumentTheme >>> theme = DocumentTheme() >>> theme.save('my_theme.json') # doctest:+SKIP >>> loaded_theme = pv.load_theme('my_theme.json') # doctest:+SKIP Create a custom theme from the default theme and load it into pyvista. >>> my_theme = DocumentTheme() >>> my_theme.font.size = 20 >>> my_theme.font.title_size = 40 >>> my_theme.cmap = 'jet' >>> pv.global_theme.load_theme(my_theme) >>> pv.global_theme.font.size 20 """ from __future__ import annotations from enum import Enum from itertools import chain import json import os import pathlib from pathlib import Path from typing import TYPE_CHECKING from typing import Any import warnings import pyvista # noqa: TC001 from pyvista.core.utilities.misc import _check_range from .colors import Color from .colors import get_cmap_safe from .colors import get_cycler from .opts import InterpolationType from .tools import parse_font_family if TYPE_CHECKING: from collections.abc import Callable from pyvista.core._typing_core import VectorLike from pyvista.jupyter import JupyterBackendOptions from ._typing import ColorLike from ._typing import ColormapOptions def _set_plot_theme_from_env() -> None: """Set plot theme from an environment variable.""" if 'PYVISTA_PLOT_THEME' in os.environ: try: theme = os.environ['PYVISTA_PLOT_THEME'] set_plot_theme(theme.lower()) except ValueError: allowed = ', '.join([item.name for item in _NATIVE_THEMES]) warnings.warn( f'\n\nInvalid PYVISTA_PLOT_THEME environment variable "{theme}". ' f'Should be one of the following: {allowed}', ) def load_theme(filename): """Load a theme from a file. Parameters ---------- filename : str Theme file. Must be json. Returns ------- pyvista.Theme The loaded theme. Examples -------- >>> import pyvista as pv >>> from pyvista.plotting.themes import DocumentTheme >>> theme = DocumentTheme() >>> theme.save('my_theme.json') # doctest:+SKIP >>> loaded_theme = pv.load_theme('my_theme.json') # doctest:+SKIP """ with Path(filename).open() as f: theme_dict = json.load(f) return Theme.from_dict(theme_dict) def set_plot_theme(theme): """Set the plotting parameters to a predefined theme using a string. Parameters ---------- theme : str Theme name. Either ``'default'``, ``'document'``, ``'dark'``, or ``'paraview'``. Examples -------- Set to the default theme. >>> import pyvista as pv >>> pv.set_plot_theme('default') Set to the document theme. >>> pv.set_plot_theme('document') Set to the dark theme. >>> pv.set_plot_theme('dark') Set to the ParaView theme. >>> pv.set_plot_theme('paraview') """ import pyvista # noqa: PLC0415 if isinstance(theme, str): theme = theme.lower() try: new_theme_type = _NATIVE_THEMES[theme].value except KeyError: msg = f"Theme {theme} not found in PyVista's native themes." raise ValueError(msg) pyvista.global_theme.load_theme(new_theme_type()) elif isinstance(theme, Theme): pyvista.global_theme.load_theme(theme) else: msg = ( f'Expected a ``pyvista.plotting.themes.Theme`` or ``str``, not {type(theme).__name__}' ) raise TypeError(msg) # Mostly from https://stackoverflow.com/questions/56579348/how-can-i-force-subclasses-to-have-slots class _ForceSlots(type): """Metaclass to force classes and subclasses to have __slots__.""" @classmethod def __prepare__(cls, name, bases, **kwargs): # type: ignore[override] super_prepared = super().__prepare__(cls, name, bases, **kwargs) # type: ignore[arg-type, call-arg, misc] super_prepared['__slots__'] = () return super_prepared class _ThemeConfig(metaclass=_ForceSlots): """Provide common methods for theme configuration classes.""" __slots__: list[str] = [] @classmethod def from_dict(cls, dict_): """Create from a dictionary.""" inst = cls() for key, value in dict_.items(): attr = getattr(inst, key) if hasattr(attr, 'from_dict'): setattr(inst, key, attr.from_dict(value)) else: setattr(inst, key, value) return inst def to_dict(self) -> dict[str, Any]: """Return theme config parameters as a dictionary. Returns ------- dict This theme parameter represented as a dictionary. """ # remove the first underscore in each entry dict_ = {} for key in self._all__slots__(): value = getattr(self, key) key_ = key[1:] if hasattr(value, 'to_dict'): dict_[key_] = value.to_dict() else: dict_[key_] = value return dict_ def __eq__(self, other) -> bool: if not isinstance(other, _ThemeConfig): return False for attr_name in other._all__slots__(): attr = getattr(self, attr_name) other_attr = getattr(other, attr_name) if ( isinstance(attr, (tuple, list)) and tuple(attr) != tuple(other_attr) ) or not attr == other_attr: return False return True __hash__ = None # type: ignore[assignment] # https://github.com/pyvista/pyvista/pull/7671 def __getitem__(self, key): """Get a value via a key. Implemented here for backwards compatibility. """ return getattr(self, key) def __setitem__(self, key, value): """Set a value via a key. Implemented here for backwards compatibility. """ setattr(self, key, value) @classmethod def _all__slots__(cls): """Get all slots including parent classes.""" mro = cls.mro() return tuple(chain.from_iterable(c.__slots__ for c in mro if c is not object)) # type: ignore[attr-defined] class _LightingConfig(_ThemeConfig): """PyVista lighting configuration. This will control the lighting interpolation type, parameters, and Physically Based Rendering (PBR) options Examples -------- Set global PBR parameters. >>> import pyvista as pv >>> pv.global_theme.lighting_params.interpolation = 'pbr' >>> pv.global_theme.lighting_params.metallic = 0.5 >>> pv.global_theme.lighting_params.roughness = 0.25 """ __slots__ = [ '_ambient', '_diffuse', '_emissive', '_interpolation', '_metallic', '_roughness', '_specular', '_specular_power', ] def __init__(self): self._interpolation = InterpolationType.FLAT.value self._metallic = 0.0 self._roughness = 0.5 self._ambient = 0.0 self._diffuse = 1.0 self._specular = 0.0 self._specular_power = 100.0 self._emissive = False @property def interpolation(self) -> InterpolationType: # numpydoc ignore=RT01 """Return or set the default interpolation type. See :class:`pyvista.plotting.opts.InterpolationType`. Options are: * ``'Phong'`` * ``'Flat'`` * ``'Physically based rendering'`` This is stored as an integer value of the ``InterpolationType`` so that the theme can be JSON-serializable. Examples -------- >>> import pyvista as pv >>> pv.global_theme.lighting_params.interpolation = 'Phong' >>> pv.global_theme.lighting_params.interpolation """ return InterpolationType.from_any(self._interpolation) @interpolation.setter def interpolation( self, interpolation: str | int | InterpolationType, ): self._interpolation = InterpolationType.from_any(interpolation).value @property def metallic(self) -> float: # numpydoc ignore=RT01 """Return or set the metallic value. This requires that the interpolation be set to ``'Physically based rendering'``. Must be between 0 and 1. Examples -------- Set the global metallic value used in physically based rendering to ``0.5``. >>> import pyvista as pv >>> pv.global_theme.lighting_params.metallic = 0.5 >>> pv.global_theme.lighting_params.metallic 0.5 """ return self._metallic @metallic.setter def metallic(self, metallic: float): _check_range(metallic, (0, 1), 'metallic') self._metallic = metallic @property def roughness(self) -> float: # numpydoc ignore=RT01 """Return or set the roughness value. This value has to be between 0 (glossy) and 1 (rough). A glossy material has reflections and a high specular part. This parameter is only used by PBR interpolation. Examples -------- Set the global roughness value used in physically based rendering to ``0.25``. >>> import pyvista as pv >>> pv.global_theme.lighting_params.roughness = 0.25 >>> pv.global_theme.lighting_params.roughness 0.25 """ return self._roughness @roughness.setter def roughness(self, roughness: float): _check_range(roughness, (0, 1), 'roughness') self._roughness = roughness @property def ambient(self) -> float: # numpydoc ignore=RT01 """Return or set the ambient value. When lighting is enabled, this is the amount of light in the range of 0 to 1 that reaches the actor when not directed at the light source emitted from the viewer. Examples -------- Set the global ambient lighting value to ``0.2``. >>> import pyvista as pv >>> pv.global_theme.lighting_params.ambient = 0.2 >>> pv.global_theme.lighting_params.ambient 0.2 """ return self._ambient @ambient.setter def ambient(self, ambient: float): _check_range(ambient, (0, 1), 'ambient') self._ambient = ambient @property def diffuse(self) -> float: # numpydoc ignore=RT01 """Return or set the diffuse value. This is the scattering of light by reflection or transmission. Diffuse reflection results when light strikes an irregular surface such as a frosted window or the surface of a frosted or coated light bulb. Must be between 0 and 1. Examples -------- Set the global diffuse lighting value to ``0.5``. >>> import pyvista as pv >>> pv.global_theme.lighting_params.diffuse = 0.5 >>> pv.global_theme.lighting_params.diffuse 0.5 """ return self._diffuse @diffuse.setter def diffuse(self, diffuse: float): _check_range(diffuse, (0, 1), 'diffuse') self._diffuse = diffuse @property def specular(self) -> float: # numpydoc ignore=RT01 """Return or set the specular value. Specular lighting simulates the bright spot of a light that appears on shiny objects. Must be between 0 and 1. Examples -------- Set the global specular value to ``0.1``. >>> import pyvista as pv >>> pv.global_theme.lighting_params.specular = 0.1 >>> pv.global_theme.lighting_params.specular 0.1 """ return self._specular @specular.setter def specular(self, specular: float): _check_range(specular, (0, 1), 'specular') self._specular = specular @property def specular_power(self) -> float: # numpydoc ignore=RT01 """Return or set the specular power value. Must be between 0.0 and 128.0. Examples -------- Set the global specular power value to ``50``. >>> import pyvista as pv >>> pv.global_theme.lighting_params.specular_power = 50 >>> pv.global_theme.lighting_params.specular_power 50 """ return self._specular_power @specular_power.setter def specular_power(self, specular_power: float): _check_range(specular_power, (0, 128), 'specular_power') self._specular_power = specular_power @property def emissive(self) -> bool: # numpydoc ignore=RT01 """Return or set if emissive is used with point Gaussian style. Examples -------- Globally enable emissive lighting when using the point Gaussian style. >>> import pyvista as pv >>> pv.global_theme.lighting_params.emissive = True >>> pv.global_theme.lighting_params.emissive True """ return self._emissive @emissive.setter def emissive(self, emissive: bool): self._emissive = bool(emissive) class _DepthPeelingConfig(_ThemeConfig): """PyVista depth peeling configuration. Examples -------- Set global depth peeling parameters. >>> import pyvista as pv >>> pv.global_theme.depth_peeling.number_of_peels = 1 >>> pv.global_theme.depth_peeling.occlusion_ratio = 0.0 >>> pv.global_theme.depth_peeling.enabled = True """ __slots__ = ['_enabled', '_number_of_peels', '_occlusion_ratio'] def __init__(self): self._number_of_peels = 4 self._occlusion_ratio = 0.0 self._enabled = False @property def number_of_peels(self) -> int: # numpydoc ignore=RT01 """Return or set the number of peels in depth peeling. Examples -------- >>> import pyvista as pv >>> pv.global_theme.depth_peeling.number_of_peels = 1 """ return self._number_of_peels @number_of_peels.setter def number_of_peels(self, number_of_peels: int): self._number_of_peels = int(number_of_peels) @property def occlusion_ratio(self) -> float: # numpydoc ignore=RT01 """Return or set the occlusion ratio in depth peeling. Examples -------- >>> import pyvista as pv >>> pv.global_theme.depth_peeling.occlusion_ratio = 0.0 """ return self._occlusion_ratio @occlusion_ratio.setter def occlusion_ratio(self, occlusion_ratio: float): self._occlusion_ratio = float(occlusion_ratio) @property def enabled(self) -> bool: # numpydoc ignore=RT01 """Return or set if depth peeling is enabled. Examples -------- >>> import pyvista as pv >>> pv.global_theme.depth_peeling.enabled = True """ return self._enabled @enabled.setter def enabled(self, enabled: bool): self._enabled = bool(enabled) def __repr__(self): txt = [''] parm = { 'Number': 'number_of_peels', 'Occlusion ratio': 'occlusion_ratio', 'Enabled': 'enabled', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<21}: {setting}') return '\n'.join(txt) class _SilhouetteConfig(_ThemeConfig): """PyVista silhouette configuration. Examples -------- Set global silhouette parameters. >>> import pyvista as pv >>> pv.global_theme.silhouette.enabled = True >>> pv.global_theme.silhouette.color = 'grey' >>> pv.global_theme.silhouette.line_width = 2 >>> pv.global_theme.silhouette.feature_angle = 20 """ __slots__ = [ '_color', '_decimate', '_enabled', '_feature_angle', '_line_width', '_opacity', ] def __init__(self): self._color = Color('black') self._line_width = 2 self._opacity = 1.0 self._feature_angle = None self._decimate = None self._enabled = False @property def enabled(self) -> bool: # numpydoc ignore=RT01 """Return or set whether silhouette is on or off.""" return self._enabled @enabled.setter def enabled(self, enabled: bool): self._enabled = bool(enabled) @property def color(self) -> Color: # numpydoc ignore=RT01 """Return or set the silhouette color. Examples -------- >>> import pyvista as pv >>> pv.global_theme.silhouette.color = 'red' """ return self._color @color.setter def color(self, color: ColorLike): self._color = Color(color) @property def line_width(self) -> float: # numpydoc ignore=RT01 """Return or set the silhouette line width. Examples -------- >>> import pyvista as pv >>> pv.global_theme.silhouette.line_width = 2.0 """ return self._line_width @line_width.setter def line_width(self, line_width: float): self._line_width = float(line_width) # type: ignore[assignment] @property def opacity(self) -> float: # numpydoc ignore=RT01 """Return or set the silhouette opacity. Examples -------- >>> import pyvista as pv >>> pv.global_theme.silhouette.opacity = 1.0 """ return self._opacity @opacity.setter def opacity(self, opacity: float): _check_range(opacity, (0, 1), 'opacity') self._opacity = float(opacity) @property def feature_angle(self) -> float | None: # numpydoc ignore=RT01 """Return or set the silhouette feature angle. Examples -------- >>> import pyvista as pv >>> pv.global_theme.silhouette.feature_angle = 20.0 """ return self._feature_angle @feature_angle.setter def feature_angle(self, feature_angle: float | None): self._feature_angle = feature_angle @property def decimate(self) -> float: # numpydoc ignore=RT01 """Return or set the amount to decimate the silhouette. Parameter must be between 0 and 1. Examples -------- >>> import pyvista as pv >>> pv.global_theme.silhouette.decimate = 0.9 """ return self._decimate # type: ignore[return-value] @decimate.setter def decimate(self, decimate: float | None): if decimate is None: self._decimate = None else: _check_range(decimate, (0, 1), 'decimate') self._decimate = float(decimate) def __repr__(self): txt = [''] parm = { 'Color': 'color', 'Line width': 'line_width', 'Opacity': 'opacity', 'Feature angle': 'feature_angle', 'Decimate': 'decimate', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<21}: {setting}') return '\n'.join(txt) class _ColorbarConfig(_ThemeConfig): """PyVista colorbar configuration. Examples -------- Set the colorbar width. >>> import pyvista as pv >>> pv.global_theme.colorbar_horizontal.width = 0.2 """ __slots__ = ['_height', '_position_x', '_position_y', '_width'] def __init__(self): self._width = None self._height = None self._position_x = None self._position_y = None @property def width(self) -> float: # numpydoc ignore=RT01 """Return or set colorbar width. Examples -------- >>> import pyvista as pv >>> pv.global_theme.colorbar_horizontal.width = 0.2 """ return self._width # type: ignore[return-value] @width.setter def width(self, width: float): self._width = float(width) @property def height(self) -> float: # numpydoc ignore=RT01 """Return or set colorbar height. Examples -------- >>> import pyvista as pv >>> pv.global_theme.colorbar_horizontal.height = 0.2 """ return self._height # type: ignore[return-value] @height.setter def height(self, height: float): self._height = float(height) @property def position_x(self) -> float: # numpydoc ignore=RT01 """Return or set colorbar x position. Examples -------- >>> import pyvista as pv >>> pv.global_theme.colorbar_horizontal.position_x = 0.2 """ return self._position_x # type: ignore[return-value] @position_x.setter def position_x(self, position_x: float): self._position_x = float(position_x) @property def position_y(self) -> float: # numpydoc ignore=RT01 """Return or set colorbar y position. Examples -------- >>> import pyvista as pv >>> pv.global_theme.colorbar_horizontal.position_y = 0.2 """ return self._position_y # type: ignore[return-value] @position_y.setter def position_y(self, position_y: float): self._position_y = float(position_y) def __repr__(self): txt = [''] parm = { 'Width': 'width', 'Height': 'height', 'X Position': 'position_x', 'Y Position': 'position_y', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<21}: {setting}') return '\n'.join(txt) class _AxesConfig(_ThemeConfig): """PyVista axes configuration. Examples -------- Show the default axes configuration values. >>> import pyvista as pv >>> pv.global_theme.axes.x_color Color(name='tomato', hex='#ff6347ff', opacity=255) >>> pv.global_theme.axes.y_color Color(name='seagreen', hex='#2e8b57ff', opacity=255) >>> pv.global_theme.axes.z_color Color(name='blue', hex='#0000ffff', opacity=255) >>> pv.global_theme.axes.box False >>> pv.global_theme.axes.show True Set the x-axis color to black. >>> pv.global_theme.axes.x_color = 'black' Show the axes orientation widget by default. >>> pv.global_theme.axes.show = True Use the :func:`axes orientation box ` as the orientation widget. >>> pv.global_theme.axes.box = True """ __slots__ = ['_box', '_show', '_x_color', '_y_color', '_z_color'] def __init__(self): self._x_color = Color('tomato') self._y_color = Color('seagreen') self._z_color = Color('mediumblue') self._box = False self._show = True def __repr__(self): txt = ['Axes configuration'] parm = { 'X Color': 'x_color', 'Y Color': 'y_color', 'Z Color': 'z_color', 'Use Box': 'box', 'Show': 'show', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<21}: {setting}') return '\n'.join(txt) @property def x_color(self) -> Color: # numpydoc ignore=RT01 """Return or set x-axis color. Examples -------- Get the default x-axis color >>> import pyvista as pv >>> pv.global_theme.axes.x_color Color(name='tomato', hex='#ff6347ff', opacity=255) Change the default color. >>> pv.global_theme.axes.x_color = 'red' """ return self._x_color @x_color.setter def x_color(self, color: ColorLike): self._x_color = Color(color) @property def y_color(self) -> Color: # numpydoc ignore=RT01 """Return or set y-axis color. Examples -------- Get the default y-axis color >>> import pyvista as pv >>> pv.global_theme.axes.y_color Color(name='seagreen', hex='#2e8b57ff', opacity=255) Change the default color. >>> pv.global_theme.axes.y_color = 'green' """ return self._y_color @y_color.setter def y_color(self, color: ColorLike): self._y_color = Color(color) @property def z_color(self) -> Color: # numpydoc ignore=RT01 """Return or set z-axis color. Examples -------- Get the default z-axis color >>> import pyvista as pv >>> pv.global_theme.axes.z_color Color(name='blue', hex='#0000ffff', opacity=255) Change the default color. >>> pv.global_theme.axes.z_color = 'purple' """ return self._z_color @z_color.setter def z_color(self, color: ColorLike): self._z_color = Color(color) @property def box(self) -> bool: # numpydoc ignore=RT01 """Use a box axes orientation widget. If ``True``, Use the :func:`axes orientation box ` instead of the :class:`pyvista.AxesActor` as the orientation widget for plots. Examples -------- >>> import pyvista as pv >>> pv.global_theme.axes.box = True """ return self._box @box.setter def box(self, box: bool): self._box = bool(box) @property def show(self) -> bool: # numpydoc ignore=RT01 """Show or hide the axes actor. Examples -------- Hide the axes by default. >>> import pyvista as pv >>> pv.global_theme.axes.show = False """ return self._show @show.setter def show(self, show: bool): self._show = bool(show) class _Font(_ThemeConfig): """PyVista plotter font configuration. Examples -------- Set the default font family to 'arial'. Must be either 'arial', 'courier', or 'times'. >>> import pyvista as pv >>> pv.global_theme.font.family = 'arial' Set the default font size to 20. >>> pv.global_theme.font.size = 20 Set the default title size to 40 >>> pv.global_theme.font.title_size = 40 Set the default label size to 10 >>> pv.global_theme.font.label_size = 10 Set the default text color to 'grey' >>> pv.global_theme.font.color = 'grey' Set the string formatter used to format numerical data to '%.6e' >>> pv.global_theme.font.fmt = '%.6e' """ __slots__ = ['_color', '_family', '_fmt', '_label_size', '_size', '_title_size'] def __init__(self): self._family = 'arial' self._size = 12 self._title_size = None self._label_size = None self._color = Color('white') self._fmt = None def __repr__(self): txt = [''] parm = { 'Family': 'family', 'Size': 'size', 'Title size': 'title_size', 'Label size': 'label_size', 'Color': 'color', 'Float format': 'fmt', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<21}: {setting}') return '\n'.join(txt) @property def family(self) -> str: # numpydoc ignore=RT01 """Return or set the font family. Must be one of the following: * ``"arial"`` * ``"courier"`` * ``"times"`` Examples -------- Set the default global font family to 'courier'. >>> import pyvista as pv >>> pv.global_theme.font.family = 'courier' """ return self._family @family.setter def family(self, family: str): parse_font_family(family) # check valid font self._family = family @property def size(self) -> int: # numpydoc ignore=RT01 """Return or set the font size. Examples -------- >>> import pyvista as pv >>> pv.global_theme.font.size = 20 """ return self._size @size.setter def size(self, size: int): self._size = int(size) @property def title_size(self) -> int: # numpydoc ignore=RT01 """Return or set the title size. If ``None``, then VTK uses ``UnconstrainedFontSizeOn`` for titles. Examples -------- >>> import pyvista as pv >>> pv.global_theme.font.title_size = 20 """ return self._title_size # type: ignore[return-value] @title_size.setter def title_size(self, title_size: int | None): if title_size is None: self._title_size = None else: self._title_size = int(title_size) @property def label_size(self) -> int: # numpydoc ignore=RT01 """Return or set the label size. If ``None``, then VTK uses ``UnconstrainedFontSizeOn`` for labels. Examples -------- >>> import pyvista as pv >>> pv.global_theme.font.label_size = 20 """ return self._label_size # type: ignore[return-value] @label_size.setter def label_size(self, label_size: int | None): if label_size is None: self._label_size = None else: self._label_size = int(label_size) @property def color(self) -> Color: # numpydoc ignore=RT01 """Return or set the font color. Examples -------- >>> import pyvista as pv >>> pv.global_theme.font.color = 'black' """ return self._color @color.setter def color(self, color: ColorLike): self._color = Color(color) @property def fmt(self) -> str: # numpydoc ignore=RT01 """Return or set the string formatter used to format numerical data. Examples -------- Set the string formatter used to format numerical data to '%.6e'. >>> import pyvista as pv >>> pv.global_theme.font.fmt = '%.6e' """ return self._fmt # type: ignore[return-value] @fmt.setter def fmt(self, fmt: str): self._fmt = fmt class _SliderStyleConfig(_ThemeConfig): """PyVista configuration for a single slider style.""" __slots__ = [ '_cap_length', '_cap_opacity', '_cap_width', '_name', '_slider_color', '_slider_length', '_slider_width', '_tube_color', '_tube_width', ] def __init__(self): """Initialize the slider style configuration.""" self._name = None self._slider_length = None self._slider_width = None self._slider_color = None self._tube_width = None self._tube_color = None self._cap_opacity = None self._cap_length = None self._cap_width = None @property def name(self) -> str: # numpydoc ignore=RT01 """Return the name of the slider style configuration.""" return self._name # type: ignore[return-value] @name.setter def name(self, name: str): self._name = name @property def cap_width(self) -> float: # numpydoc ignore=RT01 """Return or set the cap width. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.cap_width = 0.02 """ return self._cap_width # type: ignore[return-value] @cap_width.setter def cap_width(self, cap_width: float): self._cap_width = float(cap_width) @property def cap_length(self) -> float: # numpydoc ignore=RT01 """Return or set the cap length. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.cap_length = 0.01 """ return self._cap_length # type: ignore[return-value] @cap_length.setter def cap_length(self, cap_length: float): self._cap_length = float(cap_length) @property def cap_opacity(self) -> float: # numpydoc ignore=RT01 """Return or set the cap opacity. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.cap_opacity = 1.0 """ return self._cap_opacity # type: ignore[return-value] @cap_opacity.setter def cap_opacity(self, cap_opacity: float): _check_range(cap_opacity, (0, 1), 'cap_opacity') self._cap_opacity = float(cap_opacity) @property def tube_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the tube color. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.tube_color = 'black' """ return self._tube_color # type: ignore[return-value] @tube_color.setter def tube_color(self, tube_color: ColorLike): self._tube_color = Color(tube_color) @property def tube_width(self) -> float: # numpydoc ignore=RT01 """Return or set the tube_width. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.tube_width = 0.005 """ return self._tube_width # type: ignore[return-value] @tube_width.setter def tube_width(self, tube_width: float): self._tube_width = float(tube_width) @property def slider_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the slider color. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.slider_color = 'grey' """ return self._slider_color # type: ignore[return-value] @slider_color.setter def slider_color(self, slider_color: ColorLike): self._slider_color = Color(slider_color) @property def slider_width(self) -> float: # numpydoc ignore=RT01 """Return or set the slider width. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.slider_width = 0.04 """ return self._slider_width # type: ignore[return-value] @slider_width.setter def slider_width(self, slider_width: float): self._slider_width = float(slider_width) @property def slider_length(self) -> float: # numpydoc ignore=RT01 """Return or set the slider_length. Examples -------- >>> import pyvista as pv >>> pv.global_theme.slider_styles.modern.slider_length = 0.02 """ return self._slider_length # type: ignore[return-value] @slider_length.setter def slider_length(self, slider_length: float): self._slider_length = float(slider_length) def __repr__(self): txt = [''] parm = { 'Slider length': 'slider_length', 'Slider width': 'slider_width', 'Slider color': 'slider_color', 'Tube width': 'tube_width', 'Tube color': 'tube_color', 'Cap opacity': 'cap_opacity', 'Cap length': 'cap_length', 'Cap width': 'cap_width', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<17}: {setting}') return '\n'.join(txt) class _SliderConfig(_ThemeConfig): """PyVista configuration encompassing all slider styles. Examples -------- Set the classic slider configuration. >>> import pyvista as pv >>> slider_styles = pv.global_theme.slider_styles >>> slider_styles.classic.slider_length = 0.02 >>> slider_styles.classic.slider_width = 0.04 >>> slider_styles.classic.slider_color = (0.5, 0.5, 0.5) >>> slider_styles.classic.tube_width = 0.005 >>> slider_styles.classic.tube_color = (1.0, 1.0, 1.0) >>> slider_styles.classic.cap_opacity = 1 >>> slider_styles.classic.cap_length = 0.01 >>> slider_styles.classic.cap_width = 0.02 Set the modern slider configuration. >>> slider_styles.modern.slider_length = 0.02 >>> slider_styles.modern.slider_width = 0.04 >>> slider_styles.modern.slider_color = (0.43, 0.44, 0.45) >>> slider_styles.modern.tube_width = 0.04 >>> slider_styles.modern.tube_color = (0.69, 0.70, 0.709) >>> slider_styles.modern.cap_opacity = 0 >>> slider_styles.modern.cap_length = 0.01 >>> slider_styles.modern.cap_width = 0.02 """ __slots__ = ['_classic', '_modern'] def __init__(self): """Initialize the slider configuration.""" self._classic = _SliderStyleConfig() self._classic.name = 'classic' self._classic.slider_length = 0.02 self._classic.slider_width = 0.04 self._classic.slider_color = 'gray' self._classic.tube_width = 0.005 self._classic.tube_color = 'white' self._classic.cap_opacity = 1 self._classic.cap_length = 0.01 self._classic.cap_width = 0.02 self._modern = _SliderStyleConfig() self._modern.name = 'modern' self._modern.slider_length = 0.02 self._modern.slider_width = 0.04 self._modern.slider_color = (110, 113, 117) self._modern.tube_width = 0.04 self._modern.tube_color = (178, 179, 181) self._modern.cap_opacity = 0 self._modern.cap_length = 0.01 self._modern.cap_width = 0.02 @property def classic(self) -> _SliderStyleConfig: # numpydoc ignore=RT01 """Return the Classic slider configuration.""" return self._classic @classic.setter def classic(self, config: _SliderStyleConfig): if not isinstance(config, _SliderStyleConfig): msg = 'Configuration type must be `_SliderStyleConfig`' # type: ignore[unreachable] raise TypeError(msg) self._classic = config @property def modern(self) -> _SliderStyleConfig: # numpydoc ignore=RT01 """Return the Modern slider configuration.""" return self._modern @modern.setter def modern(self, config: _SliderStyleConfig): if not isinstance(config, _SliderStyleConfig): msg = 'Configuration type must be `_SliderStyleConfig`' # type: ignore[unreachable] raise TypeError(msg) self._modern = config def __repr__(self): txt = [''] parm = { 'Classic': 'classic', 'Modern': 'modern', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f' {name:<21}: {setting}') return '\n'.join(txt) def __iter__(self): for style in [self._classic, self._modern]: yield style.name class _TrameConfig(_ThemeConfig): """PyVista Trame configuration. Examples -------- Set global trame view parameters. >>> import pyvista as pv >>> pv.global_theme.trame.interactive_ratio = 2 >>> pv.global_theme.trame.still_ratio = 2 """ __slots__ = [ '_default_mode', '_interactive_ratio', '_jupyter_extension_available', '_jupyter_extension_enabled', '_jupyter_server_name', '_jupyter_server_port', '_server_proxy_enabled', '_server_proxy_prefix', '_still_ratio', ] def __init__(self): self._interactive_ratio = 1 self._still_ratio = 1 self._jupyter_server_name = 'pyvista-jupyter' self._jupyter_server_port = 0 self._server_proxy_enabled = 'PYVISTA_TRAME_SERVER_PROXY_PREFIX' in os.environ # default for ``jupyter-server-proxy`` service = os.environ.get('JUPYTERHUB_SERVICE_PREFIX', '') prefix = os.environ.get('PYVISTA_TRAME_SERVER_PROXY_PREFIX', '/proxy/') if service and not prefix.startswith('http'): # pragma: no cover self._server_proxy_prefix = str(Path(service) / prefix.lstrip('/')).rstrip('/') + '/' self._server_proxy_enabled = True else: self._server_proxy_prefix = prefix self._jupyter_extension_available = 'TRAME_JUPYTER_WWW' in os.environ self._jupyter_extension_enabled = ( self._jupyter_extension_available and not self._server_proxy_enabled ) # if set, jupyter_mode overwrites defaults jupyter_mode = os.environ.get('PYVISTA_TRAME_JUPYTER_MODE') if jupyter_mode == 'extension' and self._jupyter_extension_available: # pragma: no cover self._server_proxy_enabled = False self._jupyter_extension_enabled = True elif jupyter_mode == 'proxy' and self._server_proxy_enabled: # pragma: no cover self._jupyter_extension_enabled = False elif jupyter_mode == 'native': # pragma: no cover self._jupyter_extension_enabled = False self._server_proxy_enabled = False self._default_mode = 'trame' @property def interactive_ratio(self) -> float: # numpydoc ignore=RT01 """Return or set the interactive ratio for PyVista Trame views. Examples -------- >>> import pyvista as pv >>> pv.global_theme.trame.interactive_ratio = 2 """ return self._interactive_ratio @interactive_ratio.setter def interactive_ratio(self, interactive_ratio: float): self._interactive_ratio = interactive_ratio # type: ignore[assignment] @property def still_ratio(self) -> float: # numpydoc ignore=RT01 """Return or set the still ratio for PyVista Trame views. Examples -------- >>> import pyvista as pv >>> pv.global_theme.trame.still_ratio = 2 """ return self._still_ratio @still_ratio.setter def still_ratio(self, still_ratio: float): self._still_ratio = still_ratio # type: ignore[assignment] @property def jupyter_server_name(self): # numpydoc ignore=RT01 """Return or set the trame server name PyVista uses in Jupyter. This defaults to ``'pyvista-jupyter'``. This must be set before running :func:`pyvista.set_jupyter_backend` to ensure a server of this name is launched. Most users should not need to modify this. """ return self._jupyter_server_name @jupyter_server_name.setter def jupyter_server_name(self, name: str): self._jupyter_server_name = name @property def jupyter_server_port(self) -> int: # numpydoc ignore=RT01 """Return or set the port for the Trame Jupyter server.""" return self._jupyter_server_port @jupyter_server_port.setter def jupyter_server_port(self, port: int): self._jupyter_server_port = port @property def server_proxy_enabled(self) -> bool: # numpydoc ignore=RT01 """Return or set if use of relative URLs is enabled for the Jupyter interface.""" return self._server_proxy_enabled @server_proxy_enabled.setter def server_proxy_enabled(self, enabled: bool): if enabled and self.jupyter_extension_enabled: warnings.warn('Enabling server_proxy will disable jupyter_extension') self._jupyter_extension_enabled = False self._server_proxy_enabled = bool(enabled) @property def server_proxy_prefix(self): # numpydoc ignore=RT01 """Return or set URL prefix when using relative URLs with the Jupyter interface.""" return self._server_proxy_prefix @server_proxy_prefix.setter def server_proxy_prefix(self, prefix: str): self._server_proxy_prefix = prefix @property def jupyter_extension_available(self) -> bool: # numpydoc ignore=RT01 """Return whether the trame_jupyter_extension is detected.""" return self._jupyter_extension_available @jupyter_extension_available.setter def jupyter_extension_available(self, _available: bool): warnings.warn( 'The jupyter_extension_available flag is read only and is automatically detected.', ) @property def jupyter_extension_enabled(self) -> bool: # numpydoc ignore=RT01 """Return or set whether to use the trame_jupyter_extension to communicate with clients.""" return self._jupyter_extension_enabled @jupyter_extension_enabled.setter def jupyter_extension_enabled(self, enabled: bool): if enabled and not self.jupyter_extension_available: msg = 'The trame_jupyter_extension is not available' raise ValueError(msg) if enabled and self.server_proxy_enabled: warnings.warn('Enabling jupyter_extension will disable server_proxy') self._server_proxy_enabled = False self._jupyter_extension_enabled = bool(enabled) @property def default_mode(self): # numpydoc ignore=RT01 """Return or set the default mode of the Trame backend. * ``'trame'``: Uses a view that can switch between client and server rendering modes. * ``'server'``: Uses a view that is purely server rendering. * ``'client'``: Uses a view that is purely client rendering (generally safe without a virtual frame buffer) """ return self._default_mode @default_mode.setter def default_mode(self, mode: str): self._default_mode = mode class _CameraConfig(_ThemeConfig): """PyVista camera configuration. Examples -------- Set global camera parameters. >>> import pyvista as pv >>> pv.global_theme.camera.position = [1.0, 1.0, 1.0] >>> pv.global_theme.camera.viewup = [0.0, 0.0, 1.0] """ __slots__ = [ '_parallel_projection', '_parallel_scale', '_position', '_viewup', ] def __init__(self): self._position = [1.0, 1.0, 1.0] self._viewup = [0.0, 0.0, 1.0] self._parallel_projection = False self._parallel_scale = 1.0 @property def position(self) -> VectorLike[float]: # numpydoc ignore=RT01 """Return or set the camera position. Examples -------- Set camera position. >>> import pyvista as pv >>> pv.global_theme.camera.position = [1.0, 1.0, 1.0] """ return self._position @position.setter def position(self, position: VectorLike[float]): self._position = position # type: ignore[assignment] @property def viewup(self) -> VectorLike[float]: # numpydoc ignore=RT01 """Return or set the camera viewup. Examples -------- Set camera viewup. >>> import pyvista as pv >>> pv.global_theme.camera.viewup = [0.0, 0.0, 1.0] """ return self._viewup @viewup.setter def viewup(self, viewup: VectorLike[float]): self._viewup = viewup # type: ignore[assignment] @property def parallel_projection(self) -> bool: # numpydoc ignore=RT01 """Return or set parallel projection mode. Examples -------- Enable parallel projection. >>> import pyvista as pv >>> pv.global_theme.camera.parallel_projection = True """ return self._parallel_projection @parallel_projection.setter def parallel_projection(self, value: bool) -> None: self._parallel_projection = value @property def parallel_scale(self) -> bool: # numpydoc ignore=RT01 """Return or set parallel scale. Examples -------- Set parallel scale. >>> import pyvista as pv >>> pv.global_theme.camera.parallel_scale = 2.0 """ return self._parallel_scale # type: ignore[return-value] @parallel_scale.setter def parallel_scale(self, value: bool) -> None: self._parallel_scale = value class Theme(_ThemeConfig): """Base VTK theme. Examples -------- Change the global default background color to white. >>> import pyvista as pv >>> pv.global_theme.color = 'white' Show edges by default. >>> pv.global_theme.show_edges = True Create a new theme from the default theme and apply it globally. >>> from pyvista.plotting.themes import DocumentTheme >>> my_theme = DocumentTheme() >>> my_theme.color = 'red' >>> my_theme.background = 'white' >>> pv.global_theme.load_theme(my_theme) """ __slots__ = [ '_above_range_color', '_allow_empty_mesh', '_anti_aliasing', '_auto_close', '_axes', '_background', '_before_close_callback', '_before_close_callback', '_below_range_color', '_camera', '_cmap', '_color', '_color_cycler', '_colorbar_horizontal', '_colorbar_orientation', '_colorbar_vertical', '_depth_peeling', '_edge_color', '_edge_opacity', '_enable_camera_orientation_widget', '_floor_color', '_font', '_full_screen', '_hidden_line_removal', '_image_scale', '_interactive', '_interpolate_before_map', '_jupyter_backend', '_lighting', '_lighting_params', '_line_width', '_logo_file', '_multi_rendering_splitting_position', '_multi_samples', '_name', '_nan_color', '_notebook', '_opacity', '_outline_color', '_point_size', '_render_lines_as_tubes', '_render_points_as_spheres', '_resample_environment_texture', '_return_cpos', '_sharp_edges_feature_angle', '_show_edges', '_show_scalar_bar', '_show_vertices', '_silhouette', '_slider_styles', '_smooth_shading', '_split_sharp_edges', '_title', '_trame', '_transparent_background', '_volume_mapper', '_window_size', ] def __init__(self): """Initialize the theme.""" self._name = 'default' self._background = Color([0.3, 0.3, 0.3]) self._full_screen = False self._camera = _CameraConfig() self._notebook = None self._window_size = [1024, 768] self._image_scale = 1 self._font = _Font() self._cmap: ColormapOptions = 'viridis' self._color = Color('white') self._color_cycler = None self._nan_color = Color('darkgray') self._above_range_color = Color('grey') self._below_range_color = Color('grey') self._edge_color = Color('black') self._line_width = 1.0 self._point_size = 5.0 self._outline_color = Color('white') self._floor_color = Color('gray') self._colorbar_orientation = 'horizontal' self._colorbar_horizontal = _ColorbarConfig() self._colorbar_horizontal.width = 0.6 self._colorbar_horizontal.height = 0.08 self._colorbar_horizontal.position_x = 0.35 self._colorbar_horizontal.position_y = 0.05 self._colorbar_vertical = _ColorbarConfig() self._colorbar_vertical.width = 0.08 self._colorbar_vertical.height = 0.45 self._colorbar_vertical.position_x = 0.9 self._colorbar_vertical.position_y = 0.02 self._show_scalar_bar = True self._show_edges = False self._show_vertices = False self._lighting = True self._interactive = False self._render_points_as_spheres = False self._render_lines_as_tubes = False self._transparent_background = False self._title = 'PyVista' self._axes = _AxesConfig() self._split_sharp_edges = False self._sharp_edges_feature_angle = 30.0 self._before_close_callback = None self._allow_empty_mesh = False # Grab system flag for anti-aliasing # Use a default value of 8 multi-samples as this is default for VTK try: self._multi_samples = int(os.environ.get('PYVISTA_MULTI_SAMPLES', '8')) except ValueError: # pragma: no cover self._multi_samples = 8 # Grab system flag for auto-closing self._auto_close = os.environ.get('PYVISTA_AUTO_CLOSE', '').lower() != 'false' self._jupyter_backend: JupyterBackendOptions = ( os.environ.get('PYVISTA_JUPYTER_BACKEND', 'trame') # type: ignore[assignment] ) self._trame = _TrameConfig() self._multi_rendering_splitting_position = None self._volume_mapper = 'smart' self._smooth_shading = False self._depth_peeling = _DepthPeelingConfig() self._silhouette = _SilhouetteConfig() self._slider_styles = _SliderConfig() self._return_cpos = True self._hidden_line_removal = False self._anti_aliasing = 'msaa' self._enable_camera_orientation_widget = False self._lighting_params = _LightingConfig() self._interpolate_before_map = True self._opacity = 1.0 self._edge_opacity = 1.0 self._logo_file = None self._resample_environment_texture: bool | float = False @property def hidden_line_removal(self) -> bool: # numpydoc ignore=RT01 """Return or set hidden line removal. Wireframe geometry will be drawn using hidden line removal if the rendering engine supports it. See Also -------- pyvista.Plotter.enable_hidden_line_removal Examples -------- Enable hidden line removal. >>> import pyvista as pv >>> pv.global_theme.hidden_line_removal = True >>> pv.global_theme.hidden_line_removal True """ return self._hidden_line_removal @hidden_line_removal.setter def hidden_line_removal(self, value: bool): self._hidden_line_removal = value @property def interpolate_before_map(self) -> bool: # numpydoc ignore=RT01 """Return or set whether to interpolate colors before mapping. If the ``interpolate_before_map`` is turned off, the color mapping occurs at polygon points and colors are interpolated, which is generally less accurate whereas if the ``interpolate_before_map`` is on (the default), then the scalars will be interpolated across the topology of the dataset which is more accurate. See also :ref:`interpolate_before_map_example`. Examples -------- Enable hidden line removal. >>> import pyvista as pv Load a cylinder which has cells with a wide spread >>> cyl = pv.Cylinder(direction=(0, 0, 1), height=2).elevation() Common display argument to make sure all else is constant >>> dargs = dict(scalars='Elevation', cmap='rainbow', show_edges=True) >>> p = pv.Plotter(shape=(1, 2)) >>> _ = p.add_mesh( ... cyl, ... interpolate_before_map=False, ... scalar_bar_args={'title': 'Elevation - interpolated'}, ... **dargs, ... ) >>> p.subplot(0, 1) >>> _ = p.add_mesh( ... cyl, ... interpolate_before_map=True, ... scalar_bar_args={'title': 'Elevation - interpolated'}, ... **dargs, ... ) >>> p.link_views() >>> p.camera_position = [ ... (-1.67, -5.10, 2.06), ... (0.0, 0.0, 0.0), ... (0.00, 0.37, 0.93), ... ] >>> p.show() # doctest: +SKIP """ return self._interpolate_before_map @interpolate_before_map.setter def interpolate_before_map(self, value: bool): self._interpolate_before_map = value @property def opacity(self) -> float: # numpydoc ignore=RT01 """Return or set the opacity. Examples -------- >>> import pyvista as pv >>> pv.global_theme.opacity = 0.5 """ return self._opacity @opacity.setter def opacity(self, opacity: float): _check_range(opacity, (0, 1), 'opacity') self._opacity = float(opacity) @property def edge_opacity(self) -> float: # numpydoc ignore=RT01 """Return or set the edges opacity. .. note:: `edge_opacity` uses ``SetEdgeOpacity`` as the underlying method which requires VTK version 9.3 or higher. If ``SetEdgeOpacity`` is not available, `edge_opacity` is set to 1. Examples -------- >>> import pyvista as pv >>> pv.global_theme.edge_opacity = 0.5 """ return self._edge_opacity @edge_opacity.setter def edge_opacity(self, edge_opacity: float): _check_range(edge_opacity, (0, 1), 'edge_opacity') self._edge_opacity = float(edge_opacity) @property def above_range_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default above range color. Examples -------- Set the above range color to red. >>> import pyvista as pv >>> pv.global_theme.above_range_color = 'r' >>> pv.global_theme.above_range_color Color(name='red', hex='#ff0000ff', opacity=255) """ return self._above_range_color @above_range_color.setter def above_range_color(self, value: ColorLike): self._above_range_color = Color(value) @property def below_range_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default below range color. Examples -------- Set the below range color to blue. >>> import pyvista as pv >>> pv.global_theme.below_range_color = 'b' >>> pv.global_theme.below_range_color Color(name='blue', hex='#0000ffff', opacity=255) """ return self._below_range_color @below_range_color.setter def below_range_color(self, value: ColorLike): self._below_range_color = Color(value) @property def return_cpos(self) -> bool: # numpydoc ignore=RT01 """Return or set the default behavior of returning the camera position. Examples -------- Disable returning camera position by ``show`` and ``plot`` methods. >>> import pyvista as pv >>> pv.global_theme.return_cpos = False """ return self._return_cpos @return_cpos.setter def return_cpos(self, value: bool): self._return_cpos = value @property def background(self) -> Color: # numpydoc ignore=RT01 """Return or set the default background color of pyvista plots. Examples -------- Set the default global background of all plots to white. >>> import pyvista as pv >>> pv.global_theme.background = 'white' """ return self._background @background.setter def background(self, new_background: ColorLike) -> None: self._background = Color(new_background) @property def jupyter_backend( self, ) -> JupyterBackendOptions: # numpydoc ignore=RT01 """Return or set the jupyter notebook plotting backend. Jupyter backend to use when plotting. Must be one of the following: * ``'static'`` : Display a single static image within the Jupyterlab environment. Still requires that a virtual framebuffer be set up when displaying on a headless server, but does not require any additional modules to be installed. * ``'client'`` : Export/serialize the scene graph to be rendered with VTK.js client-side through ``trame``. Requires ``trame`` and ``jupyter-server-proxy`` to be installed. * ``'server'``: Render remotely and stream the resulting VTK images back to the client using ``trame``. This replaces the ``'ipyvtklink'`` backend with better performance. Supports the most VTK features, but suffers from minor lag due to remote rendering. Requires that a virtual framebuffer be set up when displaying on a headless server. Must have at least ``trame`` and ``jupyter-server-proxy`` installed for cloud/remote Jupyter instances. This mode is also aliased by ``'trame'``. * ``'trame'``: The full Trame-based backend that combines both ``'server'`` and ``'client'`` into one backend. This requires a virtual frame buffer. * ``'html'``: The ``'client'`` backend, but able to be embedded. * ``'none'`` : Do not display any plots within jupyterlab, instead display using dedicated VTK render windows. This will generate nothing on headless servers even with a virtual framebuffer. Examples -------- Just show static images. >>> pv.set_jupyter_backend('static') # doctest:+SKIP Disable all plotting within JupyterLab and display using a standard desktop VTK render window. >>> pv.set_jupyter_backend(None) # doctest:+SKIP """ return self._jupyter_backend @jupyter_backend.setter def jupyter_backend(self, backend: str): from pyvista.jupyter import _validate_jupyter_backend # noqa: PLC0415 self._jupyter_backend = _validate_jupyter_backend(backend) @property def trame(self) -> _TrameConfig: # numpydoc ignore=RT01 """Return or set the default trame parameters.""" return self._trame @trame.setter def trame(self, config: _TrameConfig): if not isinstance(config, _TrameConfig): msg = 'Configuration type must be `_TrameConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._trame = config @property def auto_close(self) -> bool: # numpydoc ignore=RT01 """Automatically close the figures when finished plotting. .. DANGER:: Set to ``False`` with extreme caution. Examples -------- >>> import pyvista as pv >>> pv.global_theme.auto_close = False """ return self._auto_close @auto_close.setter def auto_close(self, value: bool): self._auto_close = value @property def full_screen(self) -> bool: # numpydoc ignore=RT01 """Return if figures are shown in full screen. Examples -------- Set windows to be full screen by default. >>> import pyvista as pv >>> pv.global_theme.full_screen = True """ return self._full_screen @full_screen.setter def full_screen(self, value: bool): self._full_screen = value @property def enable_camera_orientation_widget(self) -> bool: # numpydoc ignore=RT01 """Enable the camera orientation widget in all plotters. Examples -------- >>> import pyvista as pv >>> pv.global_theme.enable_camera_orientation_widget = True >>> pv.global_theme.enable_camera_orientation_widget True """ return self._enable_camera_orientation_widget @enable_camera_orientation_widget.setter def enable_camera_orientation_widget(self, value: bool): self._enable_camera_orientation_widget = value @property def camera(self): # numpydoc ignore=RT01 """Return or set the default camera position. Examples -------- Set both the position and viewup of the camera. >>> import pyvista as pv >>> pv.global_theme.camera.position = [1.0, 1.0, 1.0] >>> pv.global_theme.camera.viewup = [0.0, 0.0, 1.0] """ return self._camera @camera.setter def camera(self, camera): if isinstance(camera, dict): self._camera = _CameraConfig.from_dict(camera) elif isinstance(camera, _CameraConfig): self._camera = camera else: msg = f'camera value must either be a `dict` or a `_CameraConfig`, got {type(camera)}' raise TypeError(msg) @property def notebook(self) -> bool | None: # numpydoc ignore=RT01 """Return or set the state of notebook plotting. Setting this to ``True`` always enables notebook plotting, while setting it to ``False`` disables plotting even when plotting within a jupyter notebook and plots externally. Examples -------- Disable all jupyter notebook plotting. >>> import pyvista as pv >>> pv.global_theme.notebook = False """ return self._notebook @notebook.setter def notebook(self, value: bool | None): self._notebook = value @property def window_size(self) -> list[int]: # numpydoc ignore=RT01 """Return or set the default render window size. Examples -------- Set window size to ``[400, 400]``. >>> import pyvista as pv >>> pv.global_theme.window_size = [400, 400] """ return self._window_size @window_size.setter def window_size(self, window_size: list[int]): if len(window_size) != 2: msg = 'Expected a length 2 iterable for ``window_size``.' raise ValueError(msg) # ensure positive size if window_size[0] < 0 or window_size[1] < 0: msg = 'Window size must be a positive value.' raise ValueError(msg) self._window_size = window_size @property def image_scale(self) -> int: # numpydoc ignore=RT01 """Return or set the default image scale factor.""" return self._image_scale @image_scale.setter def image_scale(self, value: int): value = int(value) if value < 1: msg = 'Scale factor must be a positive integer.' raise ValueError(msg) self._image_scale = int(value) @property def font(self) -> _Font: # numpydoc ignore=RT01 """Return or set the default font size, family, and/or color. Examples -------- Set the default font family to 'arial'. Must be either 'arial', 'courier', or 'times'. >>> import pyvista as pv >>> pv.global_theme.font.family = 'arial' Set the default font size to 20. >>> pv.global_theme.font.size = 20 Set the default title size to 40. >>> pv.global_theme.font.title_size = 40 Set the default label size to 10. >>> pv.global_theme.font.label_size = 10 Set the default text color to 'grey'. >>> pv.global_theme.font.color = 'grey' String formatter used to format numerical data to '%.6e'. >>> pv.global_theme.font.fmt = '%.6e' """ return self._font @font.setter def font(self, config: _Font): if not isinstance(config, _Font): msg = 'Configuration type must be `_Font`.' # type: ignore[unreachable] raise TypeError(msg) self._font = config @property def cmap(self) -> ColormapOptions: # numpydoc ignore=RT01 """Return or set the default colormap of pyvista. See :ref:`named_colormaps` for supported colormaps. Only applicable when displaying ``scalars``. You can also specify a list of colors to override an existing colormap with a custom one. For example, to create a three color colormap you might specify ``['green', 'red', 'blue']`` Examples -------- Set the default global colormap to 'jet'. >>> import pyvista as pv >>> pv.global_theme.cmap = 'jet' """ return self._cmap @cmap.setter def cmap(self, cmap: ColormapOptions): get_cmap_safe(cmap) # for validation self._cmap = cmap @property def color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default color of meshes in pyvista. Used for meshes without ``scalars``. When setting, the value must be either a string, rgb list, or hex color string. For example: * ``color='white'`` * ``color='w'`` * ``color=[1.0, 1.0, 1.0]`` * ``color='#FFFFFF'`` Examples -------- Set the default mesh color to 'red'. >>> import pyvista as pv >>> pv.global_theme.color = 'red' """ return self._color @color.setter def color(self, color: ColorLike): self._color = Color(color) @property def color_cycler(self): # numpydoc ignore=RT01 """Return or set the default color cycler used to color meshes. This color cycler is iterated over by each renderer to sequentially color datasets when displaying them through ``add_mesh``. When setting, the value must be either a list of color-like objects, or a cycler of color-like objects. If the value passed is a single string, it must be one of: * ``'default'`` - Use the default color cycler (matches matplotlib's default) * ``'matplotlib`` - Dynamically get matplotlib's current theme's color cycler. * ``'all'`` - Cycle through all available colors in ``pyvista.plotting.colors.hexcolors`` Setting to ``None`` will disable the use of the color cycler. Examples -------- Set the default color cycler to iterate through red, green, and blue. >>> import pyvista as pv >>> pv.global_theme.color_cycler = ['red', 'green', 'blue'] >>> pl = pv.Plotter() >>> _ = pl.add_mesh(pv.Cone(center=(0, 0, 0))) # red >>> _ = pl.add_mesh(pv.Cube(center=(1, 0, 0))) # green >>> _ = pl.add_mesh(pv.Sphere(center=(1, 1, 0))) # blue >>> _ = pl.add_mesh(pv.Cylinder(center=(0, 1, 0))) # red again >>> pl.show() # doctest: +SKIP """ return self._color_cycler @color_cycler.setter def color_cycler(self, color_cycler): self._color_cycler = get_cycler(color_cycler) @property def nan_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default NaN color. This color is used to plot all NaN values. Examples -------- >>> import pyvista as pv >>> pv.global_theme.nan_color = 'darkgray' """ return self._nan_color @nan_color.setter def nan_color(self, nan_color: ColorLike): self._nan_color = Color(nan_color) @property def edge_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default edge color. Examples -------- Set the global edge color to 'blue'. >>> import pyvista as pv >>> pv.global_theme.edge_color = 'blue' """ return self._edge_color @edge_color.setter def edge_color(self, edge_color: ColorLike): self._edge_color = Color(edge_color) @property def line_width(self) -> float: # numpydoc ignore=RT01 """Return or set the default line width. Examples -------- >>> import pyvista as pv >>> pv.global_theme.line_width = 2.0 """ return self._line_width @line_width.setter def line_width(self, line_width: float): self._line_width = float(line_width) @property def point_size(self) -> float: # numpydoc ignore=RT01 """Return or set the default point size. Examples -------- >>> import pyvista as pv >>> pv.global_theme.line_width = 10.0 """ return self._point_size @point_size.setter def point_size(self, point_size: float): self._point_size = float(point_size) @property def outline_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default outline color. Examples -------- >>> import pyvista as pv >>> pv.global_theme.outline_color = 'white' """ return self._outline_color @outline_color.setter def outline_color(self, outline_color: ColorLike): self._outline_color = Color(outline_color) @property def floor_color(self) -> Color: # numpydoc ignore=RT01 """Return or set the default floor color. Examples -------- >>> import pyvista as pv >>> pv.global_theme.floor_color = 'black' """ return self._floor_color @floor_color.setter def floor_color(self, floor_color: ColorLike): self._floor_color = Color(floor_color) @property def colorbar_orientation(self) -> str: # numpydoc ignore=RT01 """Return or set the default colorbar orientation. Must be either ``'vertical'`` or ``'horizontal'``. Examples -------- >>> import pyvista as pv >>> pv.global_theme.colorbar_orientation = 'horizontal' """ return self._colorbar_orientation @colorbar_orientation.setter def colorbar_orientation(self, colorbar_orientation: str): if colorbar_orientation not in ['vertical', 'horizontal']: msg = 'Colorbar orientation must be either "vertical" or "horizontal"' raise ValueError(msg) self._colorbar_orientation = colorbar_orientation @property def colorbar_horizontal(self) -> _ColorbarConfig: # numpydoc ignore=RT01 """Return or set the default parameters of a horizontal colorbar. Examples -------- Set the default horizontal colorbar width to 0.6. >>> import pyvista as pv >>> pv.global_theme.colorbar_horizontal.width = 0.6 Set the default horizontal colorbar height to 0.2. >>> pv.global_theme.colorbar_horizontal.height = 0.2 """ return self._colorbar_horizontal @colorbar_horizontal.setter def colorbar_horizontal(self, config: _ColorbarConfig): if not isinstance(config, _ColorbarConfig): msg = 'Configuration type must be `_ColorbarConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._colorbar_horizontal = config @property def colorbar_vertical(self) -> _ColorbarConfig: # numpydoc ignore=RT01 """Return or set the default parameters of a vertical colorbar. Examples -------- Set the default colorbar width to 0.45. >>> import pyvista as pv >>> pv.global_theme.colorbar_vertical.width = 0.45 Set the default colorbar height to 0.8. >>> import pyvista as pv >>> pv.global_theme.colorbar_vertical.height = 0.8 """ return self._colorbar_vertical @colorbar_vertical.setter def colorbar_vertical(self, config: _ColorbarConfig): if not isinstance(config, _ColorbarConfig): msg = 'Configuration type must be `_ColorbarConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._colorbar_vertical = config @property def show_scalar_bar(self) -> bool: # numpydoc ignore=RT01 """Return or set the default color bar visibility. Examples -------- Show the scalar bar by default when scalars are available. >>> import pyvista as pv >>> pv.global_theme.show_scalar_bar = True """ return self._show_scalar_bar @show_scalar_bar.setter def show_scalar_bar(self, show_scalar_bar: bool): self._show_scalar_bar = bool(show_scalar_bar) @property def show_edges(self) -> bool: # numpydoc ignore=RT01 """Return or set the default edge visibility. Examples -------- Show edges globally by default. >>> import pyvista as pv >>> pv.global_theme.show_edges = True """ return self._show_edges @show_edges.setter def show_edges(self, show_edges: bool): self._show_edges = bool(show_edges) @property def show_vertices(self) -> bool: # numpydoc ignore=RT01 """Return or set the default vertex visibility. Examples -------- Show vertices globally by default. >>> import pyvista as pv >>> pv.global_theme.show_vertices = True """ return self._show_vertices @show_vertices.setter def show_vertices(self, show_vertices: bool): self._show_vertices = bool(show_vertices) @property def lighting(self) -> bool: # numpydoc ignore=RT01 """Return or set the default ``lighting``. Examples -------- Disable lighting globally. >>> import pyvista as pv >>> pv.global_theme.lighting = False """ return self._lighting @lighting.setter def lighting(self, lighting: bool): self._lighting = lighting @property def interactive(self) -> bool: # numpydoc ignore=RT01 """Return or set the default ``interactive`` parameter. Examples -------- Make all plots non-interactive globally. >>> import pyvista as pv >>> pv.global_theme.interactive = False """ return self._interactive @interactive.setter def interactive(self, interactive: bool): self._interactive = bool(interactive) @property def render_points_as_spheres(self) -> bool: # numpydoc ignore=RT01 """Return or set the default ``render_points_as_spheres`` parameter. Examples -------- Render points as spheres by default globally. >>> import pyvista as pv >>> pv.global_theme.render_points_as_spheres = True """ return self._render_points_as_spheres @render_points_as_spheres.setter def render_points_as_spheres(self, render_points_as_spheres: bool): self._render_points_as_spheres = bool(render_points_as_spheres) @property def render_lines_as_tubes(self) -> bool: # numpydoc ignore=RT01 """Return or set the default ``render_lines_as_tubes`` parameter. Examples -------- Render points as spheres by default globally. >>> import pyvista as pv >>> pv.global_theme.render_lines_as_tubes = True """ return self._render_lines_as_tubes @render_lines_as_tubes.setter def render_lines_as_tubes(self, render_lines_as_tubes: bool): self._render_lines_as_tubes = bool(render_lines_as_tubes) @property def transparent_background(self) -> bool: # numpydoc ignore=RT01 """Return or set the default ``transparent_background`` parameter. Examples -------- Set transparent_background globally to ``True``. >>> import pyvista as pv >>> pv.global_theme.transparent_background = True """ return self._transparent_background @transparent_background.setter def transparent_background(self, transparent_background: bool): self._transparent_background = transparent_background @property def title(self) -> str: # numpydoc ignore=RT01 """Return or set the default ``title`` parameter. This is the VTK render window title. Examples -------- Set title globally to 'plot'. >>> import pyvista as pv >>> pv.global_theme.title = 'plot' """ return self._title @title.setter def title(self, title: str): self._title = title @property def anti_aliasing(self) -> str | None: # numpydoc ignore=RT01 """Enable or disable anti-aliasing. Should be either ``"ssaa"``, ``"msaa"``, ``"fxaa"``, or ``None``. Examples -------- Use super-sampling anti-aliasing in the global theme. >>> import pyvista as pv >>> pv.global_theme.anti_aliasing = 'ssaa' >>> pv.global_theme.anti_aliasing 'ssaa' Disable anti-aliasing in the global theme. >>> import pyvista as pv >>> pv.global_theme.anti_aliasing = None See :ref:`anti_aliasing_example` for more information regarding anti-aliasing. """ return self._anti_aliasing @anti_aliasing.setter def anti_aliasing(self, anti_aliasing: str | None): if isinstance(anti_aliasing, str): if anti_aliasing not in ['ssaa', 'msaa', 'fxaa']: msg = 'anti_aliasing must be either "ssaa", "msaa", or "fxaa"' raise ValueError(msg) elif anti_aliasing is not None: msg = 'anti_aliasing must be either "ssaa", "msaa", "fxaa", or None' # type: ignore[unreachable] raise TypeError(msg) self._anti_aliasing = anti_aliasing # type: ignore[assignment] @property def multi_samples(self) -> int: # numpydoc ignore=RT01 """Return or set the default ``multi_samples`` parameter. Set the number of multisamples to used with hardware anti_aliasing. This is only used when :attr:`anti_aliasing ` is set to ``"msaa"``. Examples -------- Set the default number of multisamples to 2 and enable ``"msaa"`` >>> import pyvista as pv >>> pv.global_theme.anti_aliasing = 'msaa' >>> pv.global_theme.multi_samples = 2 """ return self._multi_samples @multi_samples.setter def multi_samples(self, multi_samples: int): self._multi_samples = int(multi_samples) @property def multi_rendering_splitting_position(self) -> float: # numpydoc ignore=RT01 """Return or set the default ``multi_rendering_splitting_position`` parameter. Examples -------- Set multi_rendering_splitting_position globally to 0.5 (the middle of the window). >>> import pyvista as pv >>> pv.global_theme.multi_rendering_splitting_position = 0.5 """ return self._multi_rendering_splitting_position # type: ignore[return-value] @multi_rendering_splitting_position.setter def multi_rendering_splitting_position( self, multi_rendering_splitting_position: float, ): self._multi_rendering_splitting_position = multi_rendering_splitting_position @property def volume_mapper(self) -> str: # numpydoc ignore=RT01 """Return or set the default ``volume_mapper`` parameter. Must be one of the following strings, which are mapped to the following VTK volume mappers. * ``'fixed_point'`` : :vtk:`vtkFixedPointVolumeRayCastMapper` * ``'gpu'`` : :vtk:`vtkGPUVolumeRayCastMapper` * ``'open_gl'`` : :vtk:`vtkOpenGLGPUVolumeRayCastMapper` * ``'smart'`` : :vtk:`vtkSmartVolumeMapper` Examples -------- Set default volume mapper globally to 'gpu'. >>> import pyvista as pv >>> pv.global_theme.volume_mapper = 'gpu' """ return self._volume_mapper @volume_mapper.setter def volume_mapper(self, mapper: str): mappers = ['fixed_point', 'gpu', 'open_gl', 'smart'] if mapper not in mappers: msg = ( f'Mapper ({mapper}) unknown. Available volume mappers ' f'include:\n {", ".join(mappers)}' ) raise ValueError(msg) self._volume_mapper = mapper @property def smooth_shading(self) -> bool: # numpydoc ignore=RT01 """Return or set the default ``smooth_shading`` parameter. Examples -------- Set the global smooth_shading parameter default to ``True``. >>> import pyvista as pv >>> pv.global_theme.smooth_shading = True """ return self._smooth_shading @smooth_shading.setter def smooth_shading(self, smooth_shading: bool): self._smooth_shading = bool(smooth_shading) @property def depth_peeling(self) -> _DepthPeelingConfig: # numpydoc ignore=RT01 """Return or set the default depth peeling parameters. Examples -------- Set the global depth_peeling parameter default to be enabled with 8 peels. >>> import pyvista as pv >>> pv.global_theme.depth_peeling.number_of_peels = 8 >>> pv.global_theme.depth_peeling.occlusion_ratio = 0.0 >>> pv.global_theme.depth_peeling.enabled = True """ return self._depth_peeling @depth_peeling.setter def depth_peeling(self, config: _DepthPeelingConfig): if not isinstance(config, _DepthPeelingConfig): msg = 'Configuration type must be `_DepthPeelingConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._depth_peeling = config @property def silhouette(self) -> _SilhouetteConfig: # numpydoc ignore=RT01 """Return or set the default ``silhouette`` configuration. Examples -------- Set parameters of the silhouette. >>> import pyvista as pv >>> pv.global_theme.silhouette.color = 'grey' >>> pv.global_theme.silhouette.line_width = 2.0 >>> pv.global_theme.silhouette.feature_angle = 20 """ return self._silhouette @silhouette.setter def silhouette(self, config: _SilhouetteConfig): if not isinstance(config, _SilhouetteConfig): msg = 'Configuration type must be `_SilhouetteConfig`' # type: ignore[unreachable] raise TypeError(msg) self._silhouette = config @property def slider_styles(self) -> _SliderConfig: # numpydoc ignore=RT01 """Return the default slider style configurations.""" return self._slider_styles @slider_styles.setter def slider_styles(self, config: _SliderConfig): if not isinstance(config, _SliderConfig): msg = 'Configuration type must be `_SliderConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._slider_styles = config @property def axes(self) -> _AxesConfig: # numpydoc ignore=RT01 """Return or set the default ``axes`` configuration. Examples -------- Set the x-axis color to black. >>> import pyvista as pv >>> pv.global_theme.axes.x_color = 'black' Show the axes orientation widget by default. >>> pv.global_theme.axes.show = True Use the :func:`axes orientation box ` as the orientation widget. >>> pv.global_theme.axes.box = True """ return self._axes @axes.setter def axes(self, config: _AxesConfig): if not isinstance(config, _AxesConfig): msg = 'Configuration type must be `_AxesConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._axes = config @property def before_close_callback( self, ) -> Callable[[pyvista.Plotter], None]: # numpydoc ignore=RT01 """Return the default before_close_callback function for Plotter.""" return self._before_close_callback # type: ignore[return-value] @before_close_callback.setter def before_close_callback( self, value: Callable[[pyvista.Plotter], None], ): self._before_close_callback = value @property def allow_empty_mesh(self) -> bool: # numpydoc ignore=RT01 """Return or set whether to allow plotting empty meshes. Examples -------- Enable plotting of empty meshes. >>> import pyvista as pv >>> pv.global_theme.allow_empty_mesh = True Now add an empty mesh to a plotter >>> pl = pv.Plotter() >>> _ = pl.add_mesh(pv.PolyData()) >>> pl.show() # doctest: +SKIP """ return self._allow_empty_mesh @allow_empty_mesh.setter def allow_empty_mesh(self, allow_empty_mesh: bool): self._allow_empty_mesh = bool(allow_empty_mesh) def restore_defaults(self): """Restore the theme defaults. Examples -------- >>> import pyvista as pv >>> pv.global_theme.restore_defaults() """ self.__init__() # type: ignore[misc] def __repr__(self): """User friendly representation of the current theme.""" txt = [f'{self.name.capitalize()} Theme'] txt.append('-' * len(txt[0])) parm = { 'Background': 'background', 'Jupyter backend': 'jupyter_backend', 'Full screen': 'full_screen', 'Window size': 'window_size', 'Camera': 'camera', 'Notebook': 'notebook', 'Font': 'font', 'Auto close': 'auto_close', 'Colormap': 'cmap', 'Color': 'color', 'Color Cycler': 'color_cycler', 'NaN color': 'nan_color', 'Edge color': 'edge_color', 'Outline color': 'outline_color', 'Floor color': 'floor_color', 'Colorbar orientation': 'colorbar_orientation', 'Colorbar - horizontal': 'colorbar_horizontal', 'Colorbar - vertical': 'colorbar_vertical', 'Show scalar bar': 'show_scalar_bar', 'Show edges': 'show_edges', 'Lighting': 'lighting', 'Interactive': 'interactive', 'Render points as spheres': 'render_points_as_spheres', 'Transparent Background': 'transparent_background', 'Title': 'title', 'Axes': 'axes', 'Multi-samples': 'multi_samples', 'Multi-renderer Split Pos': 'multi_rendering_splitting_position', 'Volume mapper': 'volume_mapper', 'Smooth shading': 'smooth_shading', 'Depth peeling': 'depth_peeling', 'Silhouette': 'silhouette', 'Slider Styles': 'slider_styles', 'Return Camera Position': 'return_cpos', 'Hidden Line Removal': 'hidden_line_removal', 'Anti-Aliasing': '_anti_aliasing', 'Split sharp edges': '_split_sharp_edges', 'Sharp edges feat. angle': '_sharp_edges_feature_angle', 'Before close callback': '_before_close_callback', } for name, attr in parm.items(): setting = getattr(self, attr) txt.append(f'{name:<25}: {setting}') return '\n'.join(txt) @property def name(self) -> str: # numpydoc ignore=RT01 """Return or set the name of the theme.""" return self._name @name.setter def name(self, name: str): self._name = name def load_theme(self, theme: str | Theme) -> None: """Overwrite the current theme with a theme. Parameters ---------- theme : pyvista.plotting.themes.Theme Theme to use to overwrite this theme. Examples -------- Create a custom theme from the default theme and load it into the global theme of pyvista. >>> import pyvista as pv >>> from pyvista.plotting.themes import DocumentTheme >>> my_theme = DocumentTheme() >>> my_theme.font.size = 20 >>> my_theme.font.title_size = 40 >>> my_theme.cmap = 'jet' >>> pv.global_theme.load_theme(my_theme) >>> pv.global_theme.font.size 20 Create a custom theme from the dark theme and load it into pyvista. >>> from pyvista.plotting.themes import DarkTheme >>> my_theme = DarkTheme() >>> my_theme.show_edges = True >>> pv.global_theme.load_theme(my_theme) >>> pv.global_theme.show_edges True """ if isinstance(theme, str): theme = load_theme(theme) if not isinstance(theme, Theme): msg = '``theme`` must be a pyvista theme like ``pyvista.plotting.themes.Theme``.' raise TypeError(msg) for attr_name in Theme.__slots__: setattr(self, attr_name, getattr(theme, attr_name)) def save(self, filename: str) -> None: """Serialize this theme to a json file. ``before_close_callback`` is non-serializable and is omitted. Parameters ---------- filename : str Path to save the theme to. Should end in ``'.json'``. Examples -------- Export and then load back in a theme. >>> import pyvista as pv >>> theme = pv.themes.DocumentTheme() >>> theme.background = 'white' >>> theme.save('my_theme.json') # doctest:+SKIP >>> loaded_theme = pv.load_theme('my_theme.json') # doctest:+SKIP """ data = self.to_dict() # functions are not serializable del data['before_close_callback'] with Path(filename).open('w') as f: json.dump(data, f) @property def split_sharp_edges(self) -> bool: # numpydoc ignore=RT01 """Set or return splitting sharp edges. See :ref:`shading_example` for an example showing split sharp edges. Examples -------- Enable the splitting of sharp edges globally. >>> import pyvista as pv >>> pv.global_theme.split_sharp_edges = True >>> pv.global_theme.split_sharp_edges True Disable the splitting of sharp edges globally. >>> import pyvista as pv >>> pv.global_theme.split_sharp_edges = False >>> pv.global_theme.split_sharp_edges False """ return self._split_sharp_edges @split_sharp_edges.setter def split_sharp_edges(self, value: bool): self._split_sharp_edges = value @property def sharp_edges_feature_angle(self) -> float: # numpydoc ignore=RT01 """Set or return the angle of the sharp edges feature angle. See :ref:`shading_example` for an example showing split sharp edges. Examples -------- Change the sharp edges feature angle to 45 degrees. >>> import pyvista as pv >>> pv.global_theme.sharp_edges_feature_angle = 45.0 >>> pv.global_theme.sharp_edges_feature_angle 45.0 """ return self._sharp_edges_feature_angle @sharp_edges_feature_angle.setter def sharp_edges_feature_angle(self, value: float): self._sharp_edges_feature_angle = float(value) @property def lighting_params(self) -> _LightingConfig: # numpydoc ignore=RT01 """Return or set the default lighting configuration.""" return self._lighting_params @lighting_params.setter def lighting_params(self, config: _LightingConfig): if not isinstance(config, _LightingConfig): msg = 'Configuration type must be `_LightingConfig`.' # type: ignore[unreachable] raise TypeError(msg) self._lighting_params = config @property def resample_environment_texture(self) -> bool | float: # numpydoc ignore=RT01 """Set or return resampling environment texture. Resample the environment texture when using :meth:`~pyvista.Plotter.set_environment_texture`. Set this to a float to set the sampling rate explicitly or set to ``True`` to downsample the texture to 1/16th of its original resolution. Downsampling the texture can substantially improve performance for some environments, e.g. headless setups or if GPU support is limited. .. versionadded:: 0.45 Examples -------- Enable resampling the environment texture globally. >>> import pyvista as pv >>> pv.global_theme.resample_environment_texture = True >>> pv.global_theme.resample_environment_texture True Disable the resampling the environment texture. >>> import pyvista as pv >>> pv.global_theme.resample_environment_texture = False >>> pv.global_theme.resample_environment_texture False """ return self._resample_environment_texture @resample_environment_texture.setter def resample_environment_texture(self, value: bool | float): self._resample_environment_texture = value @property def logo_file(self) -> str | None: # numpydoc ignore=RT01 """Return or set the logo file. .. note:: :func:`pyvista.Plotter.add_logo_widget` will default to PyVista's logo if this is unset. Examples -------- Set the logo file to a custom logo. >>> import pyvista as pv >>> from pyvista import examples >>> logo_file = examples.download_file('vtk.png') >>> pv.global_theme.logo_file = logo_file Now the logo will be used by default for :func:`pyvista.Plotter.add_logo_widget`. >>> pl = pv.Plotter() >>> _ = pl.add_logo_widget() >>> _ = pl.add_mesh(pv.Sphere(), show_edges=True) >>> pl.show() """ return self._logo_file @logo_file.setter def logo_file(self, logo_file: str | pathlib.Path | None): if logo_file is None: path = None else: if not pathlib.Path(logo_file).exists(): msg = f'Logo file ({logo_file}) not found.' raise FileNotFoundError(msg) path = str(logo_file) self._logo_file = path class DarkTheme(Theme): """Dark mode theme. Black background, "viridis" colormap, tan meshes, white (hidden) edges. Examples -------- Make the dark theme the global default. >>> import pyvista as pv >>> from pyvista import themes >>> pv.set_plot_theme(themes.DarkTheme()) Alternatively, set via a string. >>> pv.set_plot_theme('dark') """ def __init__(self): """Initialize the theme.""" super().__init__() self.name = 'dark' self.background = 'black' self.cmap = 'viridis' self.font.color = 'white' self.show_edges = False self.color = 'lightblue' self.outline_color = 'white' self.edge_color = 'white' self.axes.x_color = 'tomato' self.axes.y_color = 'seagreen' self.axes.z_color = 'blue' class ParaViewTheme(Theme): """A paraview-like theme. Examples -------- Make the paraview-like theme the global default. >>> import pyvista as pv >>> from pyvista import themes >>> pv.set_plot_theme(themes.ParaViewTheme()) Alternatively, set via a string. >>> pv.set_plot_theme('paraview') """ def __init__(self): """Initialize theme.""" super().__init__() self.name = 'paraview' self.background = 'paraview' self.cmap = 'coolwarm' self.font.family = 'arial' self.font.label_size = 16 self.font.color = 'white' self.show_edges = False self.color = 'white' self.outline_color = 'white' self.edge_color = 'black' self.axes.x_color = 'tomato' self.axes.y_color = 'gold' self.axes.z_color = 'green' class DocumentTheme(Theme): """A document theme well suited for papers and presentations. This theme uses: * A white background * Black fonts * The "viridis" colormap * disables edges for surface plots * Hidden edge removal Best used for presentations, papers, etc. Examples -------- Make the document theme the global default. >>> import pyvista as pv >>> from pyvista import themes >>> pv.set_plot_theme(themes.DocumentTheme()) Alternatively, set via a string. >>> pv.set_plot_theme('document') """ def __init__(self): """Initialize the theme.""" super().__init__() self.name = 'document' self.background = 'white' self.cmap = 'viridis' self.font.size = 18 self.font.title_size = 18 self.font.label_size = 18 self.font.color = 'black' self.show_edges = False self.color = 'lightblue' self.outline_color = 'black' self.edge_color = 'black' self.axes.x_color = 'tomato' self.axes.y_color = 'seagreen' self.axes.z_color = 'blue' class DocumentProTheme(DocumentTheme): """A more professional document theme. This theme extends the base document theme with: * Default color cycling * Rendering points as spheres * MSAA anti aliassing * Depth peeling """ def __init__(self): """Initialize the theme.""" super().__init__() self.name = 'document_pro' self.anti_aliasing = 'ssaa' self.color_cycler = get_cycler('default') self.render_points_as_spheres = True self.multi_samples = 8 self.depth_peeling.number_of_peels = 4 self.depth_peeling.occlusion_ratio = 0.0 self.depth_peeling.enabled = True class _DocumentBuildTheme(DocumentTheme): """Theme used for building the documentation.""" def __init__(self): """Initialize the theme.""" super().__init__() self.name = 'document_build' self.window_size = [1024, 768] self.font.size = 22 self.font.label_size = 22 self.font.title_size = 22 self.return_cpos = False self.resample_environment_texture = True class _TestingTheme(Theme): """Low resolution testing theme for ``pytest``. Necessary for image regression. Xvfb doesn't support multi-sampling, it's disabled for consistency between desktops and remote testing. Also disables ``return_cpos`` to make it easier for us to write examples without returning camera positions. Resampling is also enabled for environment textures since this can be very slow without a GPU. """ def __init__(self): super().__init__() self.name = 'testing' self.multi_samples = 1 self.window_size = [400, 400] self.axes.show = False self.return_cpos = False self.resample_environment_texture = True class _NATIVE_THEMES(Enum): # noqa: N801 """Global built-in themes available to PyVista.""" paraview = ParaViewTheme document = DocumentTheme document_pro = DocumentProTheme document_build = _DocumentBuildTheme dark = DarkTheme default = document testing = _TestingTheme vtk = Theme