Source code for vifpara.views.view_object

import paraview.simple as pv
from typing import List, Optional

from ..base import Layout, Case
from ..logging import logger


class IViewObject:
    def __init__(self, case: Optional[Case], width: int, height: int, show_orientation_axis: bool = False):
        """
        Base class for all objects that contain a ParaView render view.

        This class initializes:
          - A render view
          - Default dimensions
          - A display handle (set after rendering)
          - Optional orientation axis visibility
          - A list of modifier callbacks applied before rendering

        :param Case case: The loaded visualization case of the project.
        :param int width: The width of the view in pixels.
        :param int height: The height of the view in pixels.
        :param bool show_orientation_axis: Whether the ParaView orientation axis
            should be displayed in this view.
        :return: None
        """
        self._case = case
        self._width = width
        self._height = height
        self._render_view = pv.CreateRenderView()
        self._display: pv.Show = None
        self._blocks: Optional[List[str]] = None
        self._modifier_callback_list: list = []
        self._show_orientation_axis: bool = show_orientation_axis

    def delete_view(self):
        """
        Delete the underlying render view.

        This method removes the associated ParaView render view from the
        visualization pipeline and logs that the view should no longer be used.

        :return: None
        """
        logger.info(f"Deleting view {self}. View should not be used anymore.")
        pv.Delete(self._render_view)

    def get_case(self) -> Case:
        """
        Return the loaded case this view is based on.

        :return: The loaded case of the view object.
        :rtype: Case
        """
        return self._case

    def get_width(self) -> int:
        """
        Return the width of the view in pixels.

        If the internally stored width is less than or equal to zero, a warning
        is logged and the width is set to a failsafe value of ``10`` to prevent
        division-by-zero issues during rendering.

        :return: The width of the view in pixels.
        :rtype: int
        """
        if self._width <= 0:
            logger.warning(
                f"View {self} has width of {self._width}. "
                "Will set width to 10px pre rendering."
            )
            self._width = 10  # Failsafe to avoid divisions by 0 in case of empty view
        return self._width

    def get_height(self) -> int:
        """
        Return the height of the view in pixels.

        If the internally stored height is less than or equal to zero, a warning
        is logged and the height is set to a failsafe value of ``10`` to prevent
        division-by-zero issues during rendering.

        :return: The height of the view in pixels.
        :rtype: int
        """
        if self._height <= 0:
            logger.warning(
                f"View {self} has height of {self._height}. "
                "Will set height to 10px pre rendering."
            )
            self._height = 10  # Failsafe to avoid divisions by 0 in case of empty view
        return self._height

    def set_blocks(self, blocks: List[str]):
        """
        Set the list of blocks to be displayed in the view.

        This method assigns the provided list of block identifiers to the internal
        ``_blocks`` attribute, determining which blocks are shown in the rendered
        output.

        :param List[str] blocks: A list of block names or identifiers.
        :return: None
        """
        self._blocks = blocks

    def render(self, layout: Layout, row: int = 0, col: int = 0):
        """
        Render the view into the given layout and apply modifiers.

        This method performs the following steps in order:
        1. Validates that ``layout`` is a ``Layout`` instance.
        2. Renders the view's own content via ``_render_inside`` at the specified grid
        position (``row``, ``col``).
        3. Toggles the orientation axes visibility on the underlying render view,
        if available.
        4. Applies all attached render modifiers via ``_render_modifiers``.
        5. If both a display and block selectors are present, applies the block selection
        to the display.

        :param Layout layout: The layout grid where the view should be placed.
        :param int row: Target row index in the layout grid. Defaults to ``0``.
        :param int col: Target column index in the layout grid. Defaults to ``0``.
        :raises TypeError: If ``layout`` is not an instance of ``Layout``.
        :return: None
        """
        if not isinstance(layout, Layout):
            raise TypeError(
                f"Provided a {type(layout).__name__} instead of a layout to a TextView. "
                "Please provide a Layout object."
            )
        self._render_inside(layout, row, col)
        try:
            self._render_view.OrientationAxesVisibility = 1 if self._show_orientation_axis else 0
        except AttributeError:
            pass
        self._render_modifiers()

        if self._display is not None and self._blocks is not None:
            self._display.BlockSelectors = self._blocks

    def get_render_object(self):
        """
        Return the underlying render object.

        This base implementation does not define a render object and logs an
        error indicating that subclasses should override this method.

        :return: Always returns ``None`` in the base class.
        """
        logger.error(f"Render Object not implemented for {self}.")
        return None


    def get_render_view(self):
        """
        Return the underlying ParaView render view.

        This method provides access to the internal ``_render_view`` attribute,
        which represents the ParaView ``RenderView`` used by the view for
        visualization.

        :return: The associated ParaView render view.
        """
        return self._render_view

    def add_modifier_callback(self, callback):
        """
        Used by view object modifiers to add their render callback to the view object.
        """
        self._modifier_callback_list.append(callback)

    def _render_inside(self, layout: Layout, row: int, col: int):
        """
        Interface method to override. Called by the render() method.
        """
        logger.error(f"Rendering not implemented for {self}.")

    def _render_modifiers(self):
        """
        Iterates over all attached modifiers and renders them one by one.
        """
        for callback in self._modifier_callback_list:
            callback(self, self._display)