"""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))