1238 lines
41 KiB
Python
1238 lines
41 KiB
Python
|
|
"""Module containing pyvista implementation of :vtk:`vtkLight`."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from enum import IntEnum
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
# imports here rather than in _vtk to avoid circular imports
|
||
|
|
try:
|
||
|
|
from vtkmodules.vtkCommonMath import vtkMatrix4x4
|
||
|
|
from vtkmodules.vtkRenderingCore import vtkLight
|
||
|
|
from vtkmodules.vtkRenderingCore import vtkLightActor
|
||
|
|
except ImportError: # pragma: no cover
|
||
|
|
from vtk import vtkLight # type: ignore[no-redef]
|
||
|
|
from vtk import vtkLightActor # type: ignore[no-redef]
|
||
|
|
from vtk import vtkMatrix4x4 # type: ignore[no-redef]
|
||
|
|
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
|
||
|
|
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||
|
|
from pyvista.core import _validation
|
||
|
|
from pyvista.core._vtk_core import DisableVtkSnakeCase
|
||
|
|
from pyvista.core.utilities.arrays import vtkmatrix_from_array
|
||
|
|
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||
|
|
|
||
|
|
from .colors import Color
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from pyvista.core._typing_core import TransformLike
|
||
|
|
|
||
|
|
from ._typing import ColorLike
|
||
|
|
|
||
|
|
|
||
|
|
class LightType(IntEnum):
|
||
|
|
"""An enumeration for the light types."""
|
||
|
|
|
||
|
|
HEADLIGHT = 1
|
||
|
|
CAMERA_LIGHT = 2
|
||
|
|
SCENE_LIGHT = 3
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
"""Pretty name for a light type."""
|
||
|
|
return self.name.replace('_', ' ').title()
|
||
|
|
|
||
|
|
|
||
|
|
class Light(_NoNewAttrMixin, DisableVtkSnakeCase, vtkLight):
|
||
|
|
"""Light class.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
position : sequence[float], optional
|
||
|
|
The position of the light. The interpretation of the position
|
||
|
|
depends on the type of the light and whether the light has a
|
||
|
|
transformation matrix. See also the :py:attr:`position`
|
||
|
|
property.
|
||
|
|
|
||
|
|
focal_point : sequence[float], optional
|
||
|
|
The focal point of the light. The interpretation of the focal
|
||
|
|
point depends on the type of the light and whether the light
|
||
|
|
has a transformation matrix. See also the
|
||
|
|
:py:attr:`focal_point` property.
|
||
|
|
|
||
|
|
color : ColorLike, optional
|
||
|
|
The color of the light. The ambient, diffuse and specular
|
||
|
|
colors will all be set to this color on creation.
|
||
|
|
|
||
|
|
light_type : str | int, default: 'scene light'
|
||
|
|
The type of the light. If a string, one of ``'headlight'``,
|
||
|
|
``'camera light'`` or ``'scene light'``. If an int, one of 1,
|
||
|
|
2 or 3, respectively. The class constants ``Light.HEADLIGHT``,
|
||
|
|
``Light.CAMERA_LIGHT`` and ``Light.SCENE_LIGHT`` are also
|
||
|
|
available, respectively.
|
||
|
|
|
||
|
|
- A headlight is attached to the camera, looking at its
|
||
|
|
focal point along the axis of the camera.
|
||
|
|
|
||
|
|
- A camera light also moves with the camera, but it can
|
||
|
|
occupy a general position with respect to it.
|
||
|
|
|
||
|
|
- A scene light is stationary with respect to the scene,
|
||
|
|
as it does not follow the camera. This is the default.
|
||
|
|
|
||
|
|
intensity : float, optional
|
||
|
|
The brightness of the light (between 0 and 1).
|
||
|
|
|
||
|
|
positional : bool, optional
|
||
|
|
Set if the light is positional.
|
||
|
|
|
||
|
|
The default is a directional light, i.e. an infinitely distant
|
||
|
|
point source. A positional light with a cone angle of at least
|
||
|
|
90 degrees acts like a spherical point source. A positional
|
||
|
|
light with a cone angle that is less than 90 degrees is known
|
||
|
|
as a spotlight.
|
||
|
|
|
||
|
|
cone_angle : float, optional
|
||
|
|
Cone angle of a positional light in degrees.
|
||
|
|
|
||
|
|
show_actor : bool, default: False
|
||
|
|
Show an actor for a spotlight that depicts the geometry of the
|
||
|
|
beam.
|
||
|
|
|
||
|
|
exponent : float, optional
|
||
|
|
The exponent of the cosine used for spotlights. See also the
|
||
|
|
:py:attr:`exponent` property.
|
||
|
|
|
||
|
|
shadow_attenuation : float, optional
|
||
|
|
The value of shadow attenuation.
|
||
|
|
|
||
|
|
By default a light will be completely blocked when in shadow.
|
||
|
|
By setting this value to less than 1.0 you can control how
|
||
|
|
much light is attenuated when in shadow. Note that changing
|
||
|
|
the :py:attr:`attenuation_values` of the light can make it pass
|
||
|
|
through objects even if its shadow attenuation is 1.
|
||
|
|
|
||
|
|
attenuation_values : sequence, optional
|
||
|
|
Quadratic attenuation constants.
|
||
|
|
|
||
|
|
The values are a 3-length sequence which specifies the constant,
|
||
|
|
linear and quadratic constants in this order. These parameters
|
||
|
|
only have an effect for positional lights.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light at (10, 10, 10) and set its diffuse color to red.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light(position=(10, 10, 10))
|
||
|
|
>>> light.diffuse_color = 1.0, 0.0, 0.0
|
||
|
|
|
||
|
|
Create a positional light at (0, 0, 3) with a cone angle of
|
||
|
|
30, exponent of 20, and a visible actor.
|
||
|
|
|
||
|
|
>>> light = pv.Light(
|
||
|
|
... position=(0, 0, 3),
|
||
|
|
... show_actor=True,
|
||
|
|
... positional=True,
|
||
|
|
... cone_angle=30,
|
||
|
|
... exponent=20,
|
||
|
|
... )
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
# pull in light type enum values as class constants
|
||
|
|
HEADLIGHT = LightType.HEADLIGHT
|
||
|
|
CAMERA_LIGHT = LightType.CAMERA_LIGHT
|
||
|
|
SCENE_LIGHT = LightType.SCENE_LIGHT
|
||
|
|
|
||
|
|
@_deprecate_positional_args
|
||
|
|
def __init__( # noqa: PLR0917
|
||
|
|
self,
|
||
|
|
position=None,
|
||
|
|
focal_point=None,
|
||
|
|
color=None,
|
||
|
|
light_type='scene light',
|
||
|
|
intensity=None,
|
||
|
|
positional=None,
|
||
|
|
cone_angle=None,
|
||
|
|
show_actor=False, # noqa: FBT002
|
||
|
|
exponent=None,
|
||
|
|
shadow_attenuation=None,
|
||
|
|
attenuation_values=None,
|
||
|
|
):
|
||
|
|
"""Initialize the light."""
|
||
|
|
super().__init__()
|
||
|
|
self._renderers = []
|
||
|
|
self.actor = None
|
||
|
|
|
||
|
|
if position is not None:
|
||
|
|
self.position = position
|
||
|
|
if focal_point is not None:
|
||
|
|
self.focal_point = focal_point
|
||
|
|
|
||
|
|
if color is not None:
|
||
|
|
self.ambient_color = color
|
||
|
|
self.diffuse_color = color
|
||
|
|
self.specular_color = color
|
||
|
|
|
||
|
|
if isinstance(light_type, str):
|
||
|
|
# be forgiving: ignore spaces and case
|
||
|
|
light_type_orig = light_type
|
||
|
|
type_normalized = light_type.replace(' ', '').lower()
|
||
|
|
mapping = {
|
||
|
|
'headlight': LightType.HEADLIGHT,
|
||
|
|
'cameralight': LightType.CAMERA_LIGHT,
|
||
|
|
'scenelight': LightType.SCENE_LIGHT,
|
||
|
|
}
|
||
|
|
try:
|
||
|
|
light_type = mapping[type_normalized]
|
||
|
|
except KeyError:
|
||
|
|
light_keys = ', '.join(mapping)
|
||
|
|
msg = (
|
||
|
|
f'Invalid light_type "{light_type_orig}"\n'
|
||
|
|
f'Choose from one of the following: {light_keys}'
|
||
|
|
)
|
||
|
|
raise ValueError(msg) from None
|
||
|
|
elif not isinstance(light_type, int):
|
||
|
|
msg = f'Parameter light_type must be int or str, not {type(light_type).__name__}.'
|
||
|
|
raise TypeError(msg)
|
||
|
|
# LightType is an int subclass
|
||
|
|
|
||
|
|
self.light_type = light_type
|
||
|
|
|
||
|
|
if intensity is not None:
|
||
|
|
self.intensity = intensity
|
||
|
|
|
||
|
|
if cone_angle is not None:
|
||
|
|
self.cone_angle = cone_angle
|
||
|
|
|
||
|
|
if exponent is not None:
|
||
|
|
self.exponent = exponent
|
||
|
|
|
||
|
|
if positional is not None:
|
||
|
|
self.positional = positional
|
||
|
|
|
||
|
|
if shadow_attenuation is not None:
|
||
|
|
self.shadow_attenuation = shadow_attenuation
|
||
|
|
|
||
|
|
if attenuation_values is not None:
|
||
|
|
self.attenuation_values = attenuation_values
|
||
|
|
|
||
|
|
# Add the light actor
|
||
|
|
self.actor = vtkLightActor()
|
||
|
|
self.actor.SetLight(self)
|
||
|
|
self.actor.SetVisibility(show_actor)
|
||
|
|
|
||
|
|
def __repr__(self) -> str:
|
||
|
|
"""Print a repr specifying the id of the light and its light type."""
|
||
|
|
return f'<{self.__class__.__name__} ({self.light_type}) at {hex(id(self))}>'
|
||
|
|
|
||
|
|
def __eq__(self, other) -> bool:
|
||
|
|
"""Compare whether the relevant attributes of two lights are equal."""
|
||
|
|
# attributes which are native python types and thus implement __eq__
|
||
|
|
native_attrs = [
|
||
|
|
'light_type',
|
||
|
|
'position',
|
||
|
|
'focal_point',
|
||
|
|
'ambient_color',
|
||
|
|
'diffuse_color',
|
||
|
|
'specular_color',
|
||
|
|
'intensity',
|
||
|
|
'on',
|
||
|
|
'positional',
|
||
|
|
'exponent',
|
||
|
|
'cone_angle',
|
||
|
|
'attenuation_values',
|
||
|
|
'shadow_attenuation',
|
||
|
|
]
|
||
|
|
for attr in native_attrs:
|
||
|
|
if getattr(self, attr) != getattr(other, attr):
|
||
|
|
return False
|
||
|
|
|
||
|
|
# check transformation matrix element by element (if it exists)
|
||
|
|
this_trans = self.transform_matrix
|
||
|
|
that_trans = other.transform_matrix
|
||
|
|
trans_count = sum(1 for trans in [this_trans, that_trans] if trans is not None)
|
||
|
|
if trans_count == 1:
|
||
|
|
# either but not both are None
|
||
|
|
return False
|
||
|
|
if trans_count == 2:
|
||
|
|
for i in range(4):
|
||
|
|
for j in range(4):
|
||
|
|
if this_trans.GetElement(i, j) != that_trans.GetElement(i, j):
|
||
|
|
return False
|
||
|
|
return True
|
||
|
|
|
||
|
|
__hash__ = None # type: ignore[assignment] # https://github.com/pyvista/pyvista/pull/7671
|
||
|
|
|
||
|
|
def __del__(self):
|
||
|
|
"""Clean up when the light is being destroyed."""
|
||
|
|
self.actor = None
|
||
|
|
self._renderers.clear()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def shadow_attenuation(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the value of shadow attenuation.
|
||
|
|
|
||
|
|
By default a light will be completely blocked when in shadow.
|
||
|
|
By setting this value to less than 1.0 you can control how
|
||
|
|
much light is attenuated when in shadow. Note that changing
|
||
|
|
the :py:attr:`attenuation_values` of the light can make it pass
|
||
|
|
through objects even if its shadow attenuation is 1.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Set the shadow attenuation to 0.5
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.shadow_attenuation = 0.5
|
||
|
|
>>> light.shadow_attenuation
|
||
|
|
0.5
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetShadowAttenuation()
|
||
|
|
|
||
|
|
@shadow_attenuation.setter
|
||
|
|
def shadow_attenuation(self, value):
|
||
|
|
self.SetShadowAttenuation(value)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def ambient_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the ambient color of the light.
|
||
|
|
|
||
|
|
When setting, the color must be a 3-length sequence or a string.
|
||
|
|
For example:
|
||
|
|
|
||
|
|
* ``color='white'``
|
||
|
|
* ``color='w'``
|
||
|
|
* ``color=[1.0, 1.0, 1.0]``
|
||
|
|
* ``color='#FFFFFF'``
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light and set its ambient color to red.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.ambient_color = 'red'
|
||
|
|
>>> light.ambient_color
|
||
|
|
Color(name='red', hex='#ff0000ff', opacity=255)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return Color(self.GetAmbientColor())
|
||
|
|
|
||
|
|
@ambient_color.setter
|
||
|
|
def ambient_color(self, color: ColorLike):
|
||
|
|
self.SetAmbientColor(Color(color).float_rgb)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def diffuse_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the diffuse color of the light.
|
||
|
|
|
||
|
|
When setting, the color must be a 3-length sequence or a string.
|
||
|
|
For example:
|
||
|
|
|
||
|
|
* ``color='white'``
|
||
|
|
* ``color='w'``
|
||
|
|
* ``color=[1.0, 1.0, 1.0]``
|
||
|
|
* ``color='#FFFFFF'``
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light and set its diffuse color to blue.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.diffuse_color = (0.0, 0.0, 1.0)
|
||
|
|
>>> light.diffuse_color
|
||
|
|
Color(name='blue', hex='#0000ffff', opacity=255)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return Color(self.GetDiffuseColor())
|
||
|
|
|
||
|
|
@diffuse_color.setter
|
||
|
|
def diffuse_color(self, color: ColorLike):
|
||
|
|
self.SetDiffuseColor(Color(color).float_rgb)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def specular_color(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the specular color of the light.
|
||
|
|
|
||
|
|
When setting, the color must be a 3-length sequence or a string.
|
||
|
|
For example:
|
||
|
|
|
||
|
|
* ``color='white'``
|
||
|
|
* ``color='w'``
|
||
|
|
* ``color=[1.0, 1.0, 1.0]``
|
||
|
|
* ``color='#FFFFFF'``
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light and set its specular color to bright green.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.specular_color = '#00FF00'
|
||
|
|
>>> light.specular_color
|
||
|
|
Color(name='lime', hex='#00ff00ff', opacity=255)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return Color(self.GetSpecularColor())
|
||
|
|
|
||
|
|
@specular_color.setter
|
||
|
|
def specular_color(self, color: ColorLike):
|
||
|
|
self.SetSpecularColor(Color(color).float_rgb)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def position(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the position of the light.
|
||
|
|
|
||
|
|
Note: the position is defined in the coordinate space
|
||
|
|
indicated by the light's transformation matrix (if it
|
||
|
|
exists). To get the light's world space position, use the
|
||
|
|
(read-only) :py:attr:`world_position` property.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light positioned at ``(10, 10, 10)`` after
|
||
|
|
initialization, and note how the position is unaffected by a
|
||
|
|
non-trivial transform matrix.
|
||
|
|
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.position = (10, 10, 10)
|
||
|
|
>>> # set a "random" transformation matrix
|
||
|
|
>>> light.transform_matrix = np.arange(4 * 4).reshape(4, 4)
|
||
|
|
>>> light.position
|
||
|
|
(10.0, 10.0, 10.0)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetPosition()
|
||
|
|
|
||
|
|
@position.setter
|
||
|
|
def position(self, pos):
|
||
|
|
self.SetPosition(pos)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def world_position(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the world space position of the light.
|
||
|
|
|
||
|
|
The world space position is the :py:attr:`position` property
|
||
|
|
transformed by the light's transform matrix if it exists. The
|
||
|
|
value of this read-only property corresponds to the
|
||
|
|
:vtk:`vtkLight.GetTransformedPosition` method.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light with a transformation matrix that corresponds to a
|
||
|
|
90-degree rotation around the z-axis and a shift by (0, 0, -1), and
|
||
|
|
check that the light's position transforms as expected.
|
||
|
|
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light(position=(1, 0, 3))
|
||
|
|
>>> trans = np.zeros((4, 4))
|
||
|
|
>>> trans[:-1, :-1] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]
|
||
|
|
>>> trans[:-1, -1] = [0, 0, -1]
|
||
|
|
>>> light.transform_matrix = trans
|
||
|
|
>>> light.position
|
||
|
|
(1.0, 0.0, 3.0)
|
||
|
|
>>> light.world_position
|
||
|
|
(0.0, 1.0, 2.0)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTransformedPosition()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def focal_point(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the focal point of the light.
|
||
|
|
|
||
|
|
Note: the focal point is defined in the coordinate space
|
||
|
|
indicated by the light's transformation matrix (if it
|
||
|
|
exists). To get the light's world space focal point, use the
|
||
|
|
(read-only) :py:attr:`world_focal_point` property.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light at (10, 10, 10) shining at (0, 0, 1).
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light(position=(10, 10, 10))
|
||
|
|
>>> light.focal_point = (0, 0, 1)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetFocalPoint()
|
||
|
|
|
||
|
|
@focal_point.setter
|
||
|
|
def focal_point(self, pos):
|
||
|
|
self.SetFocalPoint(pos)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def world_focal_point(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the world space focal point of the light.
|
||
|
|
|
||
|
|
The world space focal point is the :py:attr:`focal_point`
|
||
|
|
property transformed by the light's transform matrix if it
|
||
|
|
exists. The value of this read-only property corresponds to
|
||
|
|
the :vtk:`vtkLight.GetTransformedFocalPoint` method.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light with a transformation matrix that corresponds
|
||
|
|
to a 90-degree rotation around the z-axis and a shift by (0,
|
||
|
|
0, -1), and check that the light's focal point transforms as
|
||
|
|
expected.
|
||
|
|
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light(focal_point=(1, 0, 3))
|
||
|
|
>>> trans = np.zeros((4, 4))
|
||
|
|
>>> trans[:-1, :-1] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]
|
||
|
|
>>> trans[:-1, -1] = [0, 0, -1]
|
||
|
|
>>> light.transform_matrix = trans
|
||
|
|
>>> light.focal_point
|
||
|
|
(1.0, 0.0, 3.0)
|
||
|
|
>>> light.world_focal_point
|
||
|
|
(0.0, 1.0, 2.0)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTransformedFocalPoint()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def intensity(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the brightness of the light (between 0 and 1).
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Light the two sides of a cube with lights of different
|
||
|
|
brightness.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter(lighting='none')
|
||
|
|
>>> _ = plotter.add_mesh(pv.Cube(), color='cyan')
|
||
|
|
>>> light_bright = pv.Light(position=(3, 0, 0), light_type='scene light')
|
||
|
|
>>> light_dim = pv.Light(position=(0, 3, 0), light_type='scene light')
|
||
|
|
>>> light_dim.intensity = 0.5
|
||
|
|
>>> for light in light_bright, light_dim:
|
||
|
|
... light.positional = True
|
||
|
|
... plotter.add_light(light)
|
||
|
|
>>> plotter.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetIntensity()
|
||
|
|
|
||
|
|
@intensity.setter
|
||
|
|
def intensity(self, intensity):
|
||
|
|
self.SetIntensity(intensity)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def on(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set whether the light is on.
|
||
|
|
|
||
|
|
This corresponds to the Switch state of the :vtk:`vtkLight` class.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light, check if it's on by default, and turn it off.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.on
|
||
|
|
True
|
||
|
|
>>> light.on = False
|
||
|
|
|
||
|
|
"""
|
||
|
|
return bool(self.GetSwitch())
|
||
|
|
|
||
|
|
@on.setter
|
||
|
|
def on(self, state):
|
||
|
|
self.SetSwitch(state)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def positional(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set whether the light is positional.
|
||
|
|
|
||
|
|
The default is a directional light, i.e. an infinitely distant
|
||
|
|
point source. A positional light with a cone angle of at least
|
||
|
|
90 degrees acts like a spherical point source. A positional
|
||
|
|
light with a cone angle that is less than 90 degrees is known
|
||
|
|
as a spotlight.
|
||
|
|
|
||
|
|
Attenuation and cone angles are only used for positional
|
||
|
|
lights. The :py:attr:`exponent` property is only used for
|
||
|
|
spotlights. Positional lights with a cone angle of at least
|
||
|
|
90 degrees don't show angular dependence of their beams, but
|
||
|
|
they display attenuation.
|
||
|
|
|
||
|
|
If the light is changed to directional, its actor (if previously
|
||
|
|
shown) is automatically hidden.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a spotlight shining on the origin.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light(position=(1, 1, 1))
|
||
|
|
>>> light.positional = True
|
||
|
|
>>> light.cone_angle = 30
|
||
|
|
|
||
|
|
"""
|
||
|
|
return bool(self.GetPositional())
|
||
|
|
|
||
|
|
@positional.setter
|
||
|
|
def positional(self, state):
|
||
|
|
if not state:
|
||
|
|
self.hide_actor()
|
||
|
|
self.SetPositional(state)
|
||
|
|
self._check_actor()
|
||
|
|
|
||
|
|
def _check_actor(self):
|
||
|
|
"""Check if the light actor should be added or removed from attached renderers.
|
||
|
|
|
||
|
|
This should be called whenever positional state or cone angle
|
||
|
|
are changed.
|
||
|
|
|
||
|
|
"""
|
||
|
|
if self.actor is None:
|
||
|
|
# only should occur on __init__
|
||
|
|
return
|
||
|
|
|
||
|
|
actor_state = self.cone_angle < 90 and self.positional
|
||
|
|
actor_name = self.actor.GetAddressAsString('')
|
||
|
|
|
||
|
|
# add or remove the actor from the renderer
|
||
|
|
for renderer in self._renderers:
|
||
|
|
if actor_state:
|
||
|
|
if actor_name not in renderer.actors:
|
||
|
|
renderer.add_actor(self.actor, render=False)
|
||
|
|
elif actor_name in renderer.actors:
|
||
|
|
renderer.remove_actor(self.actor, render=False)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def exponent(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the exponent of the cosine used for spotlights.
|
||
|
|
|
||
|
|
With a spotlight (a positional light with cone angle less than
|
||
|
|
90 degrees) the shape of the light beam within the light cone
|
||
|
|
varies with the angle from the light's axis, and the variation
|
||
|
|
of the intensity depends as the cosine of this angle raised to
|
||
|
|
an exponent, which is 1 by default. Increasing the exponent
|
||
|
|
makes the beam sharper (more focused around the axis),
|
||
|
|
decreasing it spreads the beam out.
|
||
|
|
|
||
|
|
Note that since the angular dependence defined by this
|
||
|
|
property and the truncation performed by the
|
||
|
|
:py:attr:`cone_angle` are independent, for spotlights with
|
||
|
|
narrow beams (small :py:attr:`cone_angle`) it is harder to see
|
||
|
|
the angular variation of the intensity, and a lot higher
|
||
|
|
exponent might be necessary to visibly impact the angular
|
||
|
|
distribution of the beam.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Plot three planes lit by three spotlights with exponents of 1,
|
||
|
|
2 and 5. The one with the lowest exponent has the broadest
|
||
|
|
beam.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter(lighting='none')
|
||
|
|
>>> for offset, exponent in zip([0, 1.5, 3], [1, 2, 5]):
|
||
|
|
... _ = plotter.add_mesh(pv.Plane(center=(offset, 0, 0)), color='white')
|
||
|
|
... light = pv.Light(
|
||
|
|
... position=(offset, 0, 0.1),
|
||
|
|
... focal_point=(offset, 0, 0),
|
||
|
|
... )
|
||
|
|
... light.exponent = exponent
|
||
|
|
... light.positional = True
|
||
|
|
... light.cone_angle = 80
|
||
|
|
... plotter.add_light(light)
|
||
|
|
>>> plotter.view_xy()
|
||
|
|
>>> plotter.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetExponent()
|
||
|
|
|
||
|
|
@exponent.setter
|
||
|
|
def exponent(self, exp):
|
||
|
|
self.SetExponent(exp)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def cone_angle(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the cone angle of a positional light.
|
||
|
|
|
||
|
|
The angle is in degrees and is measured between the axis of
|
||
|
|
the cone and an extremal ray of the cone. A value smaller than
|
||
|
|
90 has spot lighting effects, anything equal to and above 90
|
||
|
|
is just a positional light, i.e. a spherical point source.
|
||
|
|
|
||
|
|
Regarding the angular distribution of the light, the cone
|
||
|
|
angle merely truncates the beam, the shape of which is defined
|
||
|
|
by the :py:attr:`exponent`. If the cone angle is at least 90
|
||
|
|
degrees then there is no angular dependence.
|
||
|
|
|
||
|
|
If the light's cone angle is increased to 90 degrees or above,
|
||
|
|
its actor (if previously shown) is automatically hidden.
|
||
|
|
|
||
|
|
See Also
|
||
|
|
--------
|
||
|
|
:ref:`beam_shape_example`
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Plot three planes lit by three spotlights with varying cone
|
||
|
|
angles. Use a large exponent to cause a visible angular
|
||
|
|
variation of the intensity of the beams.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter(lighting='none')
|
||
|
|
>>> for offset, angle in zip([0, 1.5, 3], [70, 30, 20]):
|
||
|
|
... _ = plotter.add_mesh(pv.Plane(center=(offset, 0, 0)), color='white')
|
||
|
|
... light = pv.Light(position=(offset, 0, 1), focal_point=(offset, 0, 0))
|
||
|
|
... light.exponent = 15
|
||
|
|
... light.positional = True
|
||
|
|
... light.cone_angle = angle
|
||
|
|
... plotter.add_light(light)
|
||
|
|
>>> plotter.view_xy()
|
||
|
|
>>> plotter.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetConeAngle()
|
||
|
|
|
||
|
|
@cone_angle.setter
|
||
|
|
def cone_angle(self, angle):
|
||
|
|
if angle >= 90:
|
||
|
|
self.hide_actor()
|
||
|
|
self.SetConeAngle(angle)
|
||
|
|
self._check_actor()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def attenuation_values(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the quadratic attenuation constants.
|
||
|
|
|
||
|
|
The values are 3-length sequences which specify the constant,
|
||
|
|
linear and quadratic constants in this order. These parameters
|
||
|
|
only have an effect for positional lights.
|
||
|
|
|
||
|
|
Attenuation refers to the dampening of a beam of light as it
|
||
|
|
gets further away from the point source. The three constants
|
||
|
|
describe three different profiles for dampening with
|
||
|
|
distance. A larger attenuation constant corresponds to more
|
||
|
|
rapid decay with distance.
|
||
|
|
|
||
|
|
See Also
|
||
|
|
--------
|
||
|
|
:ref:`attenuation_example`
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Plot three cubes lit by two lights with different attenuation
|
||
|
|
profiles. The blue light has slower linear attenuation, the
|
||
|
|
green one has quadratic attenuation that makes it decay
|
||
|
|
faster. Note that there are no shadow effects included so each
|
||
|
|
box gets lit by both lights.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter(lighting='none')
|
||
|
|
>>> for offset in 1, 2.5, 4:
|
||
|
|
... _ = plotter.add_mesh(
|
||
|
|
... pv.Cube(center=(offset, offset, 0)), color='white'
|
||
|
|
... )
|
||
|
|
>>> colors = ['b', 'g']
|
||
|
|
>>> all_attenuations = [(0, 0.1, 0), (0, 0, 0.1)]
|
||
|
|
>>> centers = [(0, 1, 0), (1, 0, 0)]
|
||
|
|
>>> for color, attenuation_constants, center in zip(
|
||
|
|
... colors, all_attenuations, centers
|
||
|
|
... ):
|
||
|
|
... light = pv.Light(position=center, color=color)
|
||
|
|
... light.focal_point = (1 + center[0], 1 + center[1], 0)
|
||
|
|
... light.cone_angle = 90
|
||
|
|
... light.positional = True
|
||
|
|
... light.attenuation_values = attenuation_constants
|
||
|
|
... plotter.add_light(light)
|
||
|
|
>>> plotter.view_vector((-1, -1, 1))
|
||
|
|
>>> plotter.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetAttenuationValues()
|
||
|
|
|
||
|
|
@attenuation_values.setter
|
||
|
|
def attenuation_values(self, values):
|
||
|
|
self.SetAttenuationValues(values)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def transform_matrix(self): # numpydoc ignore=RT01
|
||
|
|
"""Return (if any) or set the transformation matrix of the light.
|
||
|
|
|
||
|
|
The transformation matrix is ``None`` by default, and it is
|
||
|
|
stored as a :vtk:`vtkMatrix4x4` object when set. If set, the
|
||
|
|
light's parameters (position and focal point) are transformed
|
||
|
|
by the matrix before being rendered. See also the
|
||
|
|
:py:attr:`world_position` and :py:attr:`world_focal_point`
|
||
|
|
read-only properties that can differ from :py:attr:`position`
|
||
|
|
and :py:attr:`focal_point`, respectively.
|
||
|
|
|
||
|
|
The 4-by-4 transformation matrix is a tool to encode a general
|
||
|
|
linear transformation and a translation (an affine
|
||
|
|
transform). The 3-by-3 principal submatrix (the top left
|
||
|
|
corner of the matrix) encodes a three-dimensional linear
|
||
|
|
transformation (e.g. some rotation around the origin). The top
|
||
|
|
three elements in the last column of the matrix encode a
|
||
|
|
three-dimensional translation. The last row of the matrix is
|
||
|
|
redundant.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light with a transformation matrix that corresponds
|
||
|
|
to a 90-degree rotation around the z-axis and a shift by (0,
|
||
|
|
0, -1), and check that the light's position transforms as
|
||
|
|
expected.
|
||
|
|
|
||
|
|
>>> import numpy as np
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light(position=(1, 0, 3))
|
||
|
|
>>> trans = np.zeros((4, 4))
|
||
|
|
>>> trans[:-1, :-1] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]]
|
||
|
|
>>> trans[:-1, -1] = [0, 0, -1]
|
||
|
|
>>> light.transform_matrix = trans
|
||
|
|
>>> light.position
|
||
|
|
(1.0, 0.0, 3.0)
|
||
|
|
>>> light.world_position
|
||
|
|
(0.0, 1.0, 2.0)
|
||
|
|
|
||
|
|
"""
|
||
|
|
return self.GetTransformMatrix()
|
||
|
|
|
||
|
|
@transform_matrix.setter
|
||
|
|
def transform_matrix(self, matrix: TransformLike):
|
||
|
|
if matrix is None or isinstance(matrix, vtkMatrix4x4):
|
||
|
|
self.SetTransformMatrix(matrix)
|
||
|
|
else:
|
||
|
|
trans = _validation.validate_transform4x4(matrix)
|
||
|
|
self.SetTransformMatrix(vtkmatrix_from_array(trans))
|
||
|
|
|
||
|
|
@property
|
||
|
|
def light_type(self): # numpydoc ignore=RT01
|
||
|
|
"""Return or set the light type.
|
||
|
|
|
||
|
|
The default light type is a scene light which lives in world
|
||
|
|
coordinate space.
|
||
|
|
|
||
|
|
A headlight is attached to the camera and always points at the
|
||
|
|
camera's focal point.
|
||
|
|
|
||
|
|
A camera light also moves with the camera, but it can have an
|
||
|
|
arbitrary relative position to the camera. Camera lights are
|
||
|
|
defined in a coordinate space where the camera is located at
|
||
|
|
(0, 0, 1), looking towards (0, 0, 0) at a distance of 1, with
|
||
|
|
up being (0, 1, 0). Camera lights use the transform matrix to
|
||
|
|
establish this space, i.e. they have a fixed :py:attr:`position`
|
||
|
|
with respect to the camera, and moving the camera only
|
||
|
|
affects the :py:attr:`world_position` via changes in the
|
||
|
|
:py:attr:`transform_matrix` (and the same goes for the focal
|
||
|
|
point).
|
||
|
|
|
||
|
|
The property returns class constant values from an enum:
|
||
|
|
|
||
|
|
- ``Light.HEADLIGHT == 1``
|
||
|
|
- ``Light.CAMERA_LIGHT == 2``
|
||
|
|
- ``Light.SCENE_LIGHT == 3``
|
||
|
|
|
||
|
|
If setting the value, either an integer code or a class constant enum
|
||
|
|
value must be used.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Check the type of lights for the first two lights of the default
|
||
|
|
light kit of plotters.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter()
|
||
|
|
>>> lights = plotter.renderer.lights[:2]
|
||
|
|
>>> [light.light_type for light in lights]
|
||
|
|
[<LightType.HEADLIGHT: 1>, <LightType.CAMERA_LIGHT: 2>]
|
||
|
|
|
||
|
|
Change the light type of the default light kit's headlight to a scene light.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter()
|
||
|
|
>>> lights = plotter.renderer.lights[:2]
|
||
|
|
>>> lights[0].light_type = pv.Light.SCENE_LIGHT
|
||
|
|
>>> [light.light_type for light in lights]
|
||
|
|
[<LightType.SCENE_LIGHT: 3>, <LightType.CAMERA_LIGHT: 2>]
|
||
|
|
|
||
|
|
"""
|
||
|
|
return LightType(self.GetLightType())
|
||
|
|
|
||
|
|
@light_type.setter
|
||
|
|
def light_type(self, ltype):
|
||
|
|
if not isinstance(ltype, int):
|
||
|
|
# note that LightType is an int subclass
|
||
|
|
msg = f'Light type must be an integer subclass instance, got {ltype} instead.'
|
||
|
|
raise TypeError(msg)
|
||
|
|
self.SetLightType(ltype)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_headlight(self): # numpydoc ignore=RT01
|
||
|
|
"""Return whether the light is a headlight.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Verify that the first light of the default light kit is a headlight.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter()
|
||
|
|
>>> lights = plotter.renderer.lights
|
||
|
|
>>> [light.is_headlight for light in lights]
|
||
|
|
[True, False, False, False, False]
|
||
|
|
|
||
|
|
"""
|
||
|
|
return bool(self.LightTypeIsHeadlight())
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_camera_light(self): # numpydoc ignore=RT01
|
||
|
|
"""Return whether the light is a camera light.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Verify that four out of five lights of the default light kit
|
||
|
|
are camera lights.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter()
|
||
|
|
>>> lights = plotter.renderer.lights
|
||
|
|
>>> [light.is_camera_light for light in lights]
|
||
|
|
[False, True, True, True, True]
|
||
|
|
|
||
|
|
"""
|
||
|
|
return bool(self.LightTypeIsCameraLight())
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_scene_light(self): # numpydoc ignore=RT01
|
||
|
|
"""Return whether the light is a scene light.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Verify that none of the lights of the default light kit are
|
||
|
|
scene lights.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter()
|
||
|
|
>>> lights = plotter.renderer.lights
|
||
|
|
>>> [light.is_scene_light for light in lights]
|
||
|
|
[False, False, False, False, False]
|
||
|
|
|
||
|
|
"""
|
||
|
|
return bool(self.LightTypeIsSceneLight())
|
||
|
|
|
||
|
|
def switch_on(self):
|
||
|
|
"""Switch on the light.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light, switch it off and switch it back on again.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.on = False
|
||
|
|
>>> light.switch_on()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.SwitchOn()
|
||
|
|
|
||
|
|
def switch_off(self):
|
||
|
|
"""Switch off the light.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light and switch it off.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.switch_off()
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.SwitchOff()
|
||
|
|
|
||
|
|
def set_direction_angle(self, elev, azim):
|
||
|
|
"""Set the position and focal point of a directional light.
|
||
|
|
|
||
|
|
The light is switched to directional (non-positional). The
|
||
|
|
focal point is set to the origin. The position is defined in
|
||
|
|
terms of an elevation and an azimuthal angle, both in degrees.
|
||
|
|
|
||
|
|
Note that the equivalent :vtk:`vtkLight.SetDirectionAngle` method
|
||
|
|
uses a surprising coordinate system where the (x', y', z') axes of
|
||
|
|
the method correspond to the (z, x, y) axes of the renderer.
|
||
|
|
This method reimplements the functionality in a way that ``elev``
|
||
|
|
is the conventional elevation and ``azim`` is the conventional azimuth.
|
||
|
|
In particular:
|
||
|
|
|
||
|
|
* ``elev = 0``, ``azim = 0`` is the +x direction
|
||
|
|
* ``elev = 0``, ``azim = 90`` is the +y direction
|
||
|
|
* ``elev = 90``, ``azim = 0`` is the +z direction
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
elev : float
|
||
|
|
The elevation of the directional light.
|
||
|
|
|
||
|
|
azim : float
|
||
|
|
The azimuthal angle of the directional light.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light that shines on the origin from a 30-degree
|
||
|
|
elevation in the xz plane.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.set_direction_angle(30, 0)
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.positional = False
|
||
|
|
self.focal_point = (0, 0, 0)
|
||
|
|
theta = np.radians(90 - elev)
|
||
|
|
phi = np.radians(azim)
|
||
|
|
self.position = (np.sin(theta) * np.cos(phi), np.sin(theta) * np.sin(phi), np.cos(theta))
|
||
|
|
|
||
|
|
@_deprecate_positional_args
|
||
|
|
def copy(self, deep=True): # noqa: FBT002
|
||
|
|
"""Return a shallow or a deep copy of the light.
|
||
|
|
|
||
|
|
The only mutable attribute of :class:`pyvista.Light` is the
|
||
|
|
transformation matrix (if it exists). Thus asking for a
|
||
|
|
shallow copy merely implies that the returned light and the
|
||
|
|
original share the transformation matrix instance.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
deep : bool, default: True
|
||
|
|
Whether to return a deep copy rather than a shallow
|
||
|
|
one.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
pyvista.Light
|
||
|
|
Copied light.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a light and check that it shares a transformation
|
||
|
|
matrix with its shallow copy.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.transform_matrix = [
|
||
|
|
... [1, 0, 0, 0],
|
||
|
|
... [0, 1, 0, 0],
|
||
|
|
... [0, 0, 1, 0],
|
||
|
|
... [0, 0, 0, 1],
|
||
|
|
... ]
|
||
|
|
>>> shallow_copied = light.copy(deep=False)
|
||
|
|
>>> shallow_copied == light
|
||
|
|
True
|
||
|
|
>>> shallow_copied.transform_matrix is light.transform_matrix
|
||
|
|
True
|
||
|
|
|
||
|
|
"""
|
||
|
|
immutable_attrs = [
|
||
|
|
'light_type',
|
||
|
|
'position',
|
||
|
|
'focal_point',
|
||
|
|
'ambient_color',
|
||
|
|
'diffuse_color',
|
||
|
|
'specular_color',
|
||
|
|
'intensity',
|
||
|
|
'on',
|
||
|
|
'positional',
|
||
|
|
'exponent',
|
||
|
|
'cone_angle',
|
||
|
|
'attenuation_values',
|
||
|
|
'shadow_attenuation',
|
||
|
|
]
|
||
|
|
new_light = Light()
|
||
|
|
|
||
|
|
for attr in immutable_attrs:
|
||
|
|
value = getattr(self, attr)
|
||
|
|
setattr(new_light, attr, value)
|
||
|
|
|
||
|
|
if deep and self.transform_matrix is not None:
|
||
|
|
new_light.transform_matrix = vtkMatrix4x4()
|
||
|
|
new_light.transform_matrix.DeepCopy(self.transform_matrix)
|
||
|
|
else:
|
||
|
|
new_light.transform_matrix = self.transform_matrix
|
||
|
|
|
||
|
|
# light actors are private, always copy, but copy visibility state as well
|
||
|
|
new_light.actor.SetVisibility(self.actor.GetVisibility()) # type: ignore[union-attr]
|
||
|
|
|
||
|
|
return new_light
|
||
|
|
|
||
|
|
def set_headlight(self):
|
||
|
|
"""Set the light to be a headlight.
|
||
|
|
|
||
|
|
Headlights are fixed to the camera and always point to the
|
||
|
|
focal point of the camera. Calling this method will reset the
|
||
|
|
light's transformation matrix.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.set_headlight()
|
||
|
|
>>> light.light_type
|
||
|
|
<LightType.HEADLIGHT: 1>
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.SetLightTypeToHeadlight()
|
||
|
|
|
||
|
|
def set_camera_light(self):
|
||
|
|
"""Set the light to be a camera light.
|
||
|
|
|
||
|
|
A camera light moves with the camera, but it can have an
|
||
|
|
arbitrary relative position to the camera. Camera lights are
|
||
|
|
defined in a coordinate space where the camera is located at
|
||
|
|
(0, 0, 1), looking towards (0, 0, 0) at a distance of 1, with
|
||
|
|
up being (0, 1, 0). Camera lights use the transformation
|
||
|
|
matrix to establish this space. Calling this method will
|
||
|
|
reset the light's transformation matrix.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.set_camera_light()
|
||
|
|
>>> light.light_type
|
||
|
|
<LightType.CAMERA_LIGHT: 2>
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.SetLightTypeToCameraLight()
|
||
|
|
|
||
|
|
def set_scene_light(self):
|
||
|
|
"""Set the light to be a scene light.
|
||
|
|
|
||
|
|
Scene lights are stationary with respect to the scene.
|
||
|
|
Calling this method will reset the light's transformation
|
||
|
|
matrix.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.set_scene_light()
|
||
|
|
>>> light.light_type
|
||
|
|
<LightType.SCENE_LIGHT: 3>
|
||
|
|
|
||
|
|
"""
|
||
|
|
self.SetLightTypeToSceneLight()
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def from_vtk(cls, vtk_light):
|
||
|
|
"""Create a light from a :vtk:`vtkLight`, resulting in a copy.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
vtk_light : :vtk:`vtkLight`
|
||
|
|
The :vtk:`vtkLight` to be copied.
|
||
|
|
|
||
|
|
Returns
|
||
|
|
-------
|
||
|
|
pyvista.Light
|
||
|
|
Wrapped light.
|
||
|
|
|
||
|
|
"""
|
||
|
|
if not isinstance(vtk_light, vtkLight):
|
||
|
|
msg = f'Expected vtk.vtkLight object, got {type(vtk_light).__name__} instead.'
|
||
|
|
raise TypeError(msg)
|
||
|
|
|
||
|
|
light = cls()
|
||
|
|
light.light_type = vtk_light.GetLightType() # resets transformation matrix
|
||
|
|
light.position = vtk_light.GetPosition()
|
||
|
|
light.focal_point = vtk_light.GetFocalPoint()
|
||
|
|
light.ambient_color = vtk_light.GetAmbientColor()
|
||
|
|
light.diffuse_color = vtk_light.GetDiffuseColor()
|
||
|
|
light.specular_color = vtk_light.GetSpecularColor()
|
||
|
|
light.intensity = vtk_light.GetIntensity()
|
||
|
|
light.on = vtk_light.GetSwitch()
|
||
|
|
light.positional = vtk_light.GetPositional()
|
||
|
|
light.exponent = vtk_light.GetExponent()
|
||
|
|
light.cone_angle = vtk_light.GetConeAngle()
|
||
|
|
light.attenuation_values = vtk_light.GetAttenuationValues()
|
||
|
|
trans = vtk_light.GetTransformMatrix()
|
||
|
|
light.transform_matrix = trans
|
||
|
|
light.shadow_attenuation = vtk_light.GetShadowAttenuation()
|
||
|
|
|
||
|
|
return light
|
||
|
|
|
||
|
|
def show_actor(self):
|
||
|
|
"""Show an actor for a spotlight that depicts the geometry of the beam.
|
||
|
|
|
||
|
|
For a directional light or a positional light with
|
||
|
|
:py:attr:`cone_angle` of at least 90 degrees the method
|
||
|
|
doesn't do anything. If the light is changed so that it
|
||
|
|
becomes a spotlight, this method has to be called again for
|
||
|
|
the actor to show. To hide the actor see :func:`hide_actor`.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
Create a scene containing a cube lit with a cyan spotlight and
|
||
|
|
visualize the light using an actor.
|
||
|
|
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> plotter = pv.Plotter()
|
||
|
|
>>> _ = plotter.add_mesh(pv.Cube(), color='white')
|
||
|
|
>>> for light in plotter.renderer.lights:
|
||
|
|
... light.intensity /= 5
|
||
|
|
>>> spotlight = pv.Light(position=(-1, 1, 1), color='cyan')
|
||
|
|
>>> spotlight.positional = True
|
||
|
|
>>> spotlight.cone_angle = 20
|
||
|
|
>>> spotlight.intensity = 10
|
||
|
|
>>> spotlight.exponent = 40
|
||
|
|
>>> spotlight.show_actor()
|
||
|
|
>>> plotter.add_light(spotlight)
|
||
|
|
>>> plotter.show()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if not self.positional or self.cone_angle >= 90:
|
||
|
|
return
|
||
|
|
self.actor.VisibilityOn() # type: ignore[union-attr]
|
||
|
|
|
||
|
|
def hide_actor(self):
|
||
|
|
"""Hide the actor for a positional light that depicts the geometry of the beam.
|
||
|
|
|
||
|
|
For a directional light the function doesn't do anything.
|
||
|
|
|
||
|
|
Examples
|
||
|
|
--------
|
||
|
|
>>> import pyvista as pv
|
||
|
|
>>> light = pv.Light()
|
||
|
|
>>> light.hide_actor()
|
||
|
|
|
||
|
|
"""
|
||
|
|
if not self.positional:
|
||
|
|
return
|
||
|
|
self.actor.VisibilityOff() # type: ignore[union-attr]
|
||
|
|
|
||
|
|
@property
|
||
|
|
def renderers(self): # numpydoc ignore=RT01
|
||
|
|
"""Return the renderers associated with this light."""
|
||
|
|
return self._renderers
|
||
|
|
|
||
|
|
def add_renderer(self, renderer):
|
||
|
|
"""Attach a renderer to this light.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
renderer : :vtk:`vtkRenderer`
|
||
|
|
Renderer.
|
||
|
|
|
||
|
|
"""
|
||
|
|
# quick check to avoid adding twice
|
||
|
|
if renderer not in self.renderers:
|
||
|
|
self.renderers.append(renderer)
|
||
|
|
|
||
|
|
# verify that the renderer has the light actor if applicable
|
||
|
|
self._check_actor()
|