Source code for ladybug_vtk.polydata

"""A VTK Polydata object with additional methods."""

import vtk
from typing import Dict, List, Tuple

from ladybug_display.visualization import VisualizationData, \
    LegendParameters, DataTypeBase

from .metadata import PolyDataMetaData
from .writer import write_to_folder, write_to_file, VTKWriters


[docs] class PolyData(vtk.vtkPolyData): """A thin wrapper around vtk.vtkPolyData. See here for more information: https://vtk.org/doc/nightly/html/classvtkPolyData.html#details A PolyData object holds the geometry information in addition to several layers of data. All these data are aligned with the geometry. For instance, you can use a PolyData to represent a sensor grid and then add data for irradiance and daylight factor values to it. A PolyData can be exported to a VTK object directly but in most cases you should use the DisplayPolyData object to group the PolyData and set their display attributes. """ def __init__(self) -> None: super().__init__() self._data: Dict[str, PolyDataMetaData] = {} self._color_by = '' @ property def data(self) -> Dict[str, PolyDataMetaData]: """Get data for this Polydata. The keys are the name for each data and the values are the visualization metadata. """ return self._data
[docs] def add_visualization_data( self, data: VisualizationData, matching_method: str = 'faces' ): """Add visualization data to this polyData. Args: data: A visualization data object. matching_method: Either faces or vertices. Use faces if one value is assigned per each face and vertices if one value is assigned per each vertex. Default is faces. """ per_face = False if matching_method == 'vertices' else True name = self._get_dataset_name(data) if per_face: assert self.GetNumberOfCells() == len(data.values), \ f'The length of input values for "{name}" ({len(data.values)}) does ' \ f'not match the number of polydata cells ({self.GetNumberOfCells()}).' \ ' Try to match the number of data or use the `add_data` method directly.' return self.add_data( data.values, name, per_face=per_face, legend_parameters=data.legend_parameters, unit=data.unit, data_type=data.data_type )
[docs] def add_data( self, data: List, name: str, *, per_face: bool = True, legend_parameters: LegendParameters = None, data_type: DataTypeBase = None, unit: str = None ): """Add a list of data to a vtkPolyData. Data can be added to cells or points. By default the data will be added to cells. Args: data: A list of values. The length of the data should match the length of DataCells or DataPoints in Polydata. name: Name of data (e.g. Useful Daylight Autonomy.) per_face: A Boolean to indicate if the data is per cell or per point. In most cases except for sensor points that are loaded as sensors the data are provided per cell. legend_parameters: An Optional LegendParameters object to override default parameters of the legend. None indicates that default legend parameters will be used. (Default: None). data_type: Optional DataType from the ladybug datatype subpackage (ie. Temperature()) , which will be used to assign default legend properties. If None, the legend associated with this object will contain no units unless a unit below is specified. (Default: None). unit: Optional text string for the units of the values. (ie. "C"). If None or empty, the default units of the data_type will be used. If no data type is specified in this case, this will simply be an empty string. (Default: None). """ assert name not in self._data, \ f'A data by name "{name}" already exist. Try a different name.' if per_face: assert self.GetNumberOfCells() == len(data), \ f'The length of input values for "{name}" ({len(data)}) does ' \ f'not match the number of polydata cells ({self.GetNumberOfCells()}).' else: assert self.GetNumberOfPoints() == len(data), \ f'The length of input values for "{name}" ({len(data)}) does ' \ f'not match the number of polydata points ({self.GetNumberOfPoints()}).' if isinstance(data[0], (list, tuple)): values = self._resolve_array_type(data[0][0]) values.SetNumberOfComponents(len(data[0])) values.SetNumberOfTuples(len(data)) iterator = True else: values = self._resolve_array_type(data[0]) iterator = False if name: values.SetName(name) if iterator: for d in data: values.InsertNextValue(*d) else: for d in data: values.InsertNextValue(d) if per_face: self.GetCellData().AddArray(values) else: self.GetPointData().AddArray(values) self.Modified() self._data[name] = PolyDataMetaData(legend_parameters, data_type, unit, per_face)
@property def color_by(self) -> str: return self._color_by @color_by.setter def color_by(self, name: str) -> None: """Set the name for active data that should be used to color PolyData.""" assert name in self._data, \ f'{name} is not a valid data for this PolyData. Available ' \ f'data are: {list(self._data.keys())}' cell = self._data[name].per_face if cell: self.GetCellData().SetActiveScalars(name) else: self.GetPointData().SetActiveScalars(name) self.Modified() self._color_by = name @ staticmethod def _resolve_array_type(data): if isinstance(data, float): return vtk.vtkFloatArray() elif isinstance(data, int): return vtk.vtkIntArray() elif isinstance(data, str): return vtk.vtkStringArray() else: raise ValueError(f'Unsupported input data type: {type(data)}') @staticmethod def _get_dataset_name(data_set: VisualizationData): if data_set.data_type: ds_name = data_set.data_type.name elif data_set.legend_parameters and data_set.legend_parameters.title: ds_name = data_set.legend_parameters.title else: ds_name = 'Data' return ds_name
[docs] def to_vtk(self, target_folder, name, writer: VTKWriters = VTKWriters.binary): """Write to a VTK file. The file extension will be set to vtk for ASCII format and vtp for binary format. """ return write_to_file(self, target_folder, name, writer)
[docs] def to_folder(self, target_folder='.'): """Write data to a folder with a JSON meta file. This method generates a folder that includes a JSON meta file along with all the binary arrays written as standalone binary files. The generated format can be used by vtk.js using the reader below https://kitware.github.io/vtk-js/examples/HttpDataSetReader.html Args: target_folder: Path to target folder. Default: . """ return write_to_folder(self, target_folder)
def __repr__(self) -> Tuple[str]: return (f'PolyData: #{len(self.data)}')