"""A VTK scene."""
from __future__ import annotations
from typing import Dict, List, Tuple, Union
import vtk
from ._helper import _validate_input
from .actor import Actor
from .assistant import Assistant
from .camera import Camera
from .headless import try_headless
from .legend_parameter import LegendParameter
from .types import ImageTypes
from .text_actor import TextActor
[docs]class Scene:
"""Initialize a Scene object.
Vtk derives inspiration from a movie set in naming its objects. Imagine a scene
being prepared at a movie set. The scene has a background. It has a few actors, there
are assistants running around making sure everything is ready, and there are a few
cameras setup to capture the scene from different angles. A
scene in vtk is defined in the similar fashion. It has a background color, some
actors i.e. geometry objects from model, a few cameras around the scene, and
assistants equal the number of cameras setup in the scene.
Args:
background_color: A tuple of three integers that represent RGB values of the
color that you'd like to set as the background color. Defaults to white.
actors: A list of Actor objects to be added to the scene. Defaults to None.
cameras: A list of Camera objects to be added to the scene. Defaults to None.
text_actor: A TextActor object to be added to the scene. Defaults to None.
"""
def __init__(self, background_color: Tuple[int, int, int] = None,
actors: List[Actor] = None, cameras: List[Camera] = None,
text_actor: TextActor = None) -> None:
self.background_color = background_color
self.actors = actors or []
self.cameras = cameras or []
self.legend_parameters = {}
self.text_actor = text_actor
self._assistants = []
@property
def background_color(self) -> Tuple[int, int, int]:
"""background_color for the scene."""
return self._background_color
@background_color.setter
def background_color(self, val: Tuple[int, int, int]) -> None:
if not val:
colors = vtk.vtkNamedColors()
self._background_color = colors.GetColor3d("White")
elif _validate_input(val, [int]):
self._background_color = val
else:
raise ValueError(
'Background color is a tuple with three integers'
' representing R, G, and B values.'
)
@property
def actors(self) -> List[Actor]:
"""Actors in the scene."""
return self._actors
@actors.setter
def actors(self, val: List[Actor]) -> None:
"""Set actors for the scene."""
if not val:
self._actors = []
elif _validate_input(val, [Actor]):
self._actors = val
else:
raise ValueError(
'A list of Actor objects is expected.'
f' Instead got {val}.'
)
@property
def cameras(self) -> List[Camera]:
"""A list of honeybee-vtk cameras setup in the scene."""
return self._cameras
@cameras.setter
def cameras(self, val: List[Camera]) -> None:
"""Set cameras for the scene."""
if not val:
self._cameras = []
elif _validate_input(val, [Camera]):
self._cameras = val
else:
raise ValueError(
'A list of Camera objects is expected.'
f' Instead got {val}.'
)
@property
def text_actor(self) -> TextActor:
"""TextActor object in the scene."""
return self._text_actor
@text_actor.setter
def text_actor(self, val: TextActor) -> None:
"""Set the TextActor object in the scene."""
if not val:
self._text_actor = None
elif not isinstance(val, TextActor):
raise ValueError('TextActor object is expected.')
else:
self._text_actor = val
@property
def assistants(self) -> List[Assistant]:
"""A list of honeybee-vtk assistants working in the scene."""
return self._assistants
@property
def legend_parameters(self) -> Dict[str, LegendParameter]:
"""Legends in the scene that can be added to the images."""
for actor in self._actors:
self._legend_parameters[actor.legend_parameter.name] = actor.legend_parameter
return self._legend_parameters
@legend_parameters.setter
def legend_parameters(self, val: Dict[str, LegendParameter]) -> None:
"""Set legend parameters for the scene."""
self._legend_parameters = val
[docs] def legend_parameter(self, name: str) -> LegendParameter:
"""Get a legend parameter object by name.
Args:
name: A string for the name of the legend parameters you are looking for.
Returns:
A legend parameter object.
"""
if name not in self.legend_parameters.keys():
raise ValueError(
'No legend parameter found by that name in this scene. Make sure cameras'
' and actors a added to the scene. Legends in the scene are'
f' {tuple(self.legend_parameters.keys())}.'
)
return self.legend_parameters[name]
[docs] def add_cameras(self, val: Union[Camera, List[Camera]]) -> None:
"""Add a honeybee-vtk Camera objects to a Scene.
Args:
val: Either a list of Camera objects or a single Camera object.
"""
if isinstance(val, list) and _validate_input(val, [Camera]):
self._cameras += val
elif isinstance(val, Camera):
self._cameras.append(val)
else:
raise ValueError(
'Either a list of Camera objects or a Camera object is expected.'
f' Instead got {val}.'
)
[docs] def add_actors(self, val: Union[Actor, List[Actor]]) -> None:
"""add honeybee-vtk Actor objects to a Scene.
Args:
val: Either a list of Actors objects or a single Actor object.
"""
if isinstance(val, list) and _validate_input(val, [Actor]):
self._actors += val
elif isinstance(val, Actor):
self._actors.append(val)
else:
raise ValueError(
'Either a list of Actor objects or an Actor object is expected.'
f' Instead got {val}.'
)
[docs] def add_text_actor(self, val: TextActor) -> None:
"""Add a TextActor object to the scene."""
assert isinstance(val, TextActor), 'TextActor object is expected.'
self._text_actor = val
[docs] def update_scene(self) -> None:
"""Update the scene.
This method will use the latest cameras, actors, and visible legend parameters
to create assistant object.
"""
if self._cameras and self._actors:
if self.legend_parameters:
visible_legend_params = [
legend_param for legend_param in self.legend_parameters.values()
if not legend_param.hide_legend]
else:
visible_legend_params = []
self._assistants = [
Assistant(background_color=self._background_color, camera=camera,
actors=self._actors, legend_parameters=visible_legend_params,
text_actor=self._text_actor)
for camera in self._cameras]
else:
raise ValueError(
'Add cameras and actors to the scene first.'
)
@try_headless
def export_images(
self, folder: str, image_type: ImageTypes = ImageTypes.png, *,
image_scale: int = 1, image_width: int = 0, image_height: int = 0,
image_name: str = '', color_range: vtk.vtkLookupTable = None,
rgba: bool = False, show: bool = False, vtk_camera: vtk.vtkCamera = None,
extract_camera: bool = False) -> List[str]:
"""Export all the cameras in the scene as images.
Reference: https://kitware.github.io/vtk-examples/site/Python/IO/ImageWriter/
This method is able to export an image in '.png', '.jpg', '.ps', '.tiff', '.bmp',
and '.pnm' formats.
Parameters vtk_camera and extract_camera are mutually exclusive and these
parameters are only used by the to_grid_images method in the Model object.
Args:
folder: A valid path to where you'd like to write the images.
image_type: An ImageType object.
image_scale: An integer value as a scale factor. Defaults to 1.
image_width: An integer value that sets the width of image in pixels.
Defaults to 0, which will use default radinace view's horizontal angle.
image_height: An integer value that sets the height of image in pixels.
Defaults to 0, which will use default radinace view's vertical angle.
image_name: A text string that sets the name of the image. Defaults to ''.
color_range: A vtk lookup table object which can be obtained
from the color_range mehtod of the DataFieldInfo object.
Defaults to None.
rgba: A boolean value to set the type of buffer. A True value sets
an the background color to black. A False value uses the Scene object's
background color. Defaults to False.
show: A boolean value to decide if the the render window should pop up.
Defaults to False.
vtk_camera: A vtkCamera object. If specified, this vtkCamera will be used
to export images. Defaults to None.
extract_camera: A boolean value to if active camera from the renderer in the
assistant shall be extracted or not. Defaults to False.
Returns:
A list of text strings representing the paths to the exported images.
"""
self.update_scene()
if vtk_camera:
self._assistants[0].vtk_camera = vtk_camera
if extract_camera:
return self._assistants[0]._create_window()[2].GetActiveCamera()
return [assistant._export_image(
folder=folder, image_type=image_type, image_scale=image_scale,
image_width=image_width, image_height=image_height, image_name=image_name,
color_range=color_range, rgba=rgba, show=show)
for assistant in self._assistants]
[docs] def export_gltf(self, folder: str, name: str = 'Camera') -> str:
"""Export a scene to a glTF file.
Args:
folder: A valid path to where you'd like to write the gltf file.
name: Name of the gltf file as a text string.
Returns:
A text string representing the path to the gltf file.
"""
self.update_scene()
if self._assistants:
return self._assistants[0]._export_gltf(folder=folder, name=name)
else:
raise ValueError(
'At least one camera needs to be setup to export an image.'
)