Source code for fieldview.view

"""View-control helpers for interactive or batch FieldView sessions.

By default the functions in this module operate on the current graphics
window.  Use ``window=`` to target a specific 1-based window number.

This module is responsible for coarse view operations such as centering,
fitting, cardinal or isometric alignment, and perspective or overlay toggles.
Use it together with :mod:`fieldview.layout` for pane selection and
:mod:`fieldview.camera` for lower-level navigation and camera-state control.

Example:

.. code-block:: python

    >>> import os
    >>> import fieldview as fv
    >>> data_dir = os.path.join(fv.home, "examples", "f18")
    >>> ds = fv.data.load_plot3d(
    ...     os.path.join(data_dir, "f18i9b_g_bin"),
    ...     os.path.join(data_dir, "f18i9b_q_bin"),
    ... )
    >>> fv.view.fit()
    >>> fv.view.align("+z")
"""

from __future__ import annotations

from collections.abc import Sequence
from typing import cast

from ._core_utils import _core_call, _normalize_window, _require_core_func
from .data import PerspectiveState, _build_perspective_state
from .exceptions import InvalidArgumentError


def _normalize_direction(direction: str | Sequence[str]) -> str | tuple[str, str, str]:
    if isinstance(direction, str):
        lowered = direction.strip().lower()
        if lowered not in {"+x", "-x", "+y", "-y", "+z", "-z"}:
            raise InvalidArgumentError(
                "direction must be one of +x, -x, +y, -y, +z, or -z"
            )
        return lowered

    if isinstance(direction, Sequence) and not isinstance(
        direction, (bytes, bytearray, str)
    ):
        values = tuple(str(item).strip().lower() for item in direction)
        if len(values) != 3:
            raise InvalidArgumentError(
                "direction must contain exactly three axis strings for isometric align"
            )
        valid = {"+x", "-x", "+y", "-y", "+z", "-z"}
        if any(item not in valid for item in values):
            raise InvalidArgumentError(
                "direction entries must be axis strings like +x or -z"
            )
        return values[0], values[1], values[2]

    raise InvalidArgumentError(
        "direction must be a string or a sequence of three strings"
    )


[docs] def center( x: float | None = None, y: float | None = None, z: float | None = None, *, window: int | None = None, ) -> None: """Center the view. With no coordinates, centers automatically on the current scene contents. With ``x``, ``y``, and ``z`` provided, translates the current view so the specified world-space point appears at the window center. This operation preserves the current viewing orientation and camera offset, so both eye and target may move together. ``window=None`` targets the current window. Args: x: X coordinate of the world-space point to place at the screen center. Provide all of ``x``, ``y``, and ``z`` together, or omit all three for automatic centering. y: Y coordinate of the world-space point to place at the screen center. z: Z coordinate of the world-space point to place at the screen center. window: Optional 1-based window number. When omitted, the current graphics window is used. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.center() >>> fv.view.center(0.0, 0.0, 0.0) """ func = _require_core_func("center") normalized_window = _normalize_window(window) coords = (x, y, z) if all(value is None for value in coords): _core_call(func, normalized_window) return if any(value is None for value in coords): raise InvalidArgumentError("x, y, and z must be provided together") _core_call( func, float(cast(float, x)), float(cast(float, y)), float(cast(float, z)), normalized_window, )
[docs] def fit(*, window: int | None = None) -> None: """Fit the current scene contents to the view. ``window=None`` targets the current window. Args: window: Optional 1-based window number. When omitted, the current graphics window is used. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.fit() """ func = _require_core_func("fit") _core_call(func, _normalize_window(window))
[docs] def reset(*, window: int | None = None) -> None: """Reset world transforms for the selected window. This matches the UI reset action for the world transform. ``window=None`` targets the current window. Args: window: Optional 1-based window number. When omitted, the current graphics window is used. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.reset() """ func = _require_core_func("reset") _core_call(func, _normalize_window(window))
[docs] def align(direction: str | Sequence[str], *, window: int | None = None) -> None: """Align the view to a cardinal or isometric direction. ``window=None`` targets the current window. Args: direction: Either one of ``+x``, ``-x``, ``+y``, ``-y``, ``+z``, ``-z`` for a cardinal view, or a sequence of three such strings for an isometric orientation. window: Optional 1-based window number. When omitted, the current graphics window is used. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.align("+x") >>> fv.view.align(("+x", "+y", "+z")) """ func = _require_core_func("align") _core_call(func, _normalize_direction(direction), _normalize_window(window))
[docs] def set_perspective( enabled: bool, angle: float | None = None, *, window: int | None = None ) -> None: """Enable or disable perspective for a window. ``window=None`` targets the current window. Args: enabled: Whether perspective projection should be enabled. angle: Optional perspective angle in degrees. When omitted, the host keeps the current perspective angle. window: Optional 1-based window number. When omitted, the current graphics window is used. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.set_perspective(True, angle=30.0) """ if not isinstance(enabled, bool): raise InvalidArgumentError("enabled must be a boolean") if angle is not None: angle = float(angle) func = _require_core_func("set_perspective") _core_call(func, enabled, angle, _normalize_window(window))
[docs] def get_perspective(*, window: int | None = None) -> PerspectiveState: """Return the current perspective state for a window. ``window=None`` targets the current window. Args: window: Optional 1-based window number. When omitted, the current graphics window is used. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.set_perspective(True, angle=30.0) >>> state = fv.view.get_perspective() """ func = _require_core_func("get_perspective") return _build_perspective_state( cast(dict[str, object], _core_call(func, _normalize_window(window))) )
[docs] def set_outline(enabled: bool) -> None: """Enable or disable dataset outlines for the current scene. Args: enabled: Whether dataset outlines should be visible. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.set_outline(True) """ if not isinstance(enabled, bool): raise InvalidArgumentError("enabled must be a boolean") func = _require_core_func("set_outline") _core_call(func, enabled)
[docs] def get_outline() -> bool: """Return whether dataset outlines are currently visible. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> visible = fv.view.get_outline() """ func = _require_core_func("get_outline") return bool(_core_call(func))
[docs] def set_axis_markers(enabled: bool) -> None: """Enable or disable the global axis-marker overlay. Args: enabled: Whether the global axis-marker overlay should be visible. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> fv.view.set_axis_markers(True) """ if not isinstance(enabled, bool): raise InvalidArgumentError("enabled must be a boolean") func = _require_core_func("set_axis_markers") _core_call(func, enabled)
[docs] def get_axis_markers() -> bool: """Return whether the global axis-marker overlay is visible. Example: .. code-block:: python >>> import os >>> import fieldview as fv >>> data_dir = os.path.join(fv.home, "examples", "f18") >>> ds = fv.data.load_plot3d( ... os.path.join(data_dir, "f18i9b_g_bin"), ... os.path.join(data_dir, "f18i9b_q_bin"), ... ) >>> visible = fv.view.get_axis_markers() """ func = _require_core_func("get_axis_markers") return bool(_core_call(func))