From 5fe273b0a69720be091850cce24902c385ba0cbc Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Fri, 21 Jun 2024 12:29:22 +0530 Subject: [PATCH 01/22] build: Bump version to v0.11.dev0 --- pyproject.toml | 5 ++--- tests/test_metadata.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fa072a17..7cee3c2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] # Check https://python-poetry.org/docs/pyproject/ for all available sections name = "ansys-fluent-visualization" -version = "0.10.dev0" +version = "0.11.dev0" description = "A python wrapper for ansys Fluent visualization" license = "MIT" authors = ["ANSYS, Inc. "] @@ -25,11 +25,10 @@ packages = [ [tool.poetry.dependencies] python = ">=3.9,<4.0" importlib-metadata = {version = "^4.0", python = "<3.9"} -ansys-fluent-core = "~=0.21.dev1" +ansys-fluent-core = "~=0.22.dev0" vtk = ">=9.3.0.rc0" pyvista = ">=0.39.0" pyvistaqt = ">=0.7.0" -pyside6 = ">=6.2.3" matplotlib = ">=3.5.1" requests = "==2.31.0" diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4aea85c9..239d8b47 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -2,4 +2,4 @@ def test_pkg_version(): - assert __version__ == "0.10.dev0" + assert __version__ == "0.11.dev0" From b4df79ef98366defa8f2478d2e10abc835441abe Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Sun, 22 Sep 2024 11:44:55 +0530 Subject: [PATCH 02/22] feat: Plug in pyvista as an alternate plotter. --- src/ansys/fluent/visualization/__init__.py | 3 +- .../visualization/matplotlib/__init__.py | 6 - .../fluent/visualization/plotter/__init__.py | 6 + .../plotter/matplotlib/__init__.py | 0 .../{ => plotter}/matplotlib/plotter_defns.py | 0 .../plotter_objects.py} | 8 +- .../plotter_windows_manager.py} | 30 ++-- .../visualization/plotter/pyvista/__init__.py | 0 .../plotter/pyvista/plotter_defns.py | 166 ++++++++++++++++++ 9 files changed, 196 insertions(+), 23 deletions(-) delete mode 100644 src/ansys/fluent/visualization/matplotlib/__init__.py create mode 100644 src/ansys/fluent/visualization/plotter/__init__.py create mode 100644 src/ansys/fluent/visualization/plotter/matplotlib/__init__.py rename src/ansys/fluent/visualization/{ => plotter}/matplotlib/plotter_defns.py (100%) rename src/ansys/fluent/visualization/{matplotlib/matplot_objects.py => plotter/plotter_objects.py} (91%) rename src/ansys/fluent/visualization/{matplotlib/matplot_windows_manager.py => plotter/plotter_windows_manager.py} (93%) create mode 100644 src/ansys/fluent/visualization/plotter/pyvista/__init__.py create mode 100644 src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py diff --git a/src/ansys/fluent/visualization/__init__.py b/src/ansys/fluent/visualization/__init__.py index 1f7bd55f..209af9f7 100644 --- a/src/ansys/fluent/visualization/__init__.py +++ b/src/ansys/fluent/visualization/__init__.py @@ -7,6 +7,7 @@ _VERSION_INFO = None __version__ = importlib_metadata.version(__name__.replace(".", "-")) +PLOTTER = "matplotlib" def version_info() -> str: @@ -23,5 +24,5 @@ def version_info() -> str: from ansys.fluent.visualization._config import get_config, set_config # noqa: F401 -from ansys.fluent.visualization.matplotlib import Plots # noqa: F401 +from ansys.fluent.visualization.plotter import Plots # noqa: F401 from ansys.fluent.visualization.pyvista import Graphics # noqa: F401 diff --git a/src/ansys/fluent/visualization/matplotlib/__init__.py b/src/ansys/fluent/visualization/matplotlib/__init__.py deleted file mode 100644 index 49e3f46e..00000000 --- a/src/ansys/fluent/visualization/matplotlib/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""A package that provides interfacing Fluent with Matplotlib.""" - -from ansys.fluent.visualization.matplotlib.matplot_objects import Plots # noqa: F401 -from ansys.fluent.visualization.matplotlib.matplot_windows_manager import ( # noqa: F401 - matplot_windows_manager, -) diff --git a/src/ansys/fluent/visualization/plotter/__init__.py b/src/ansys/fluent/visualization/plotter/__init__.py new file mode 100644 index 00000000..c0bbf6a3 --- /dev/null +++ b/src/ansys/fluent/visualization/plotter/__init__.py @@ -0,0 +1,6 @@ +"""A package that provides interfacing Fluent with plotters (matplotlib or pyvista).""" + +from ansys.fluent.visualization.plotter.plotter_objects import Plots # noqa: F401 +from ansys.fluent.visualization.plotter.plotter_windows_manager import ( # noqa: F401 + plotter_windows_manager, +) diff --git a/src/ansys/fluent/visualization/plotter/matplotlib/__init__.py b/src/ansys/fluent/visualization/plotter/matplotlib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/ansys/fluent/visualization/matplotlib/plotter_defns.py b/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py similarity index 100% rename from src/ansys/fluent/visualization/matplotlib/plotter_defns.py rename to src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py diff --git a/src/ansys/fluent/visualization/matplotlib/matplot_objects.py b/src/ansys/fluent/visualization/plotter/plotter_objects.py similarity index 91% rename from src/ansys/fluent/visualization/matplotlib/matplot_objects.py rename to src/ansys/fluent/visualization/plotter/plotter_objects.py index 701e9e3b..219e9edf 100644 --- a/src/ansys/fluent/visualization/matplotlib/matplot_objects.py +++ b/src/ansys/fluent/visualization/plotter/plotter_objects.py @@ -13,8 +13,8 @@ Plots as PlotsContainer, ) -from ansys.fluent.visualization.matplotlib.matplot_windows_manager import ( - matplot_windows_manager, +from ansys.fluent.visualization.plotter.plotter_windows_manager import ( + plotter_windows_manager, ) @@ -65,7 +65,7 @@ def plot(self, window_id: Optional[str] = None): Window ID. If an ID is not specified, a unique ID is used. The default is ``None``. """ - matplot_windows_manager.plot(self, window_id) + plotter_windows_manager.plot(self, window_id) class MonitorPlot(MonitorDefn): @@ -100,4 +100,4 @@ def plot(self, window_id: Optional[str] = None): Window ID. If an ID is not specified, a unique ID is used. The default is ``None``. """ - matplot_windows_manager.plot(self, window_id) + plotter_windows_manager.plot(self, window_id) diff --git a/src/ansys/fluent/visualization/matplotlib/matplot_windows_manager.py b/src/ansys/fluent/visualization/plotter/plotter_windows_manager.py similarity index 93% rename from src/ansys/fluent/visualization/matplotlib/matplot_windows_manager.py rename to src/ansys/fluent/visualization/plotter/plotter_windows_manager.py index 68b870bf..8fdf7cca 100644 --- a/src/ansys/fluent/visualization/matplotlib/matplot_windows_manager.py +++ b/src/ansys/fluent/visualization/plotter/plotter_windows_manager.py @@ -1,4 +1,4 @@ -"""Module for matplotlib windows management.""" +"""Module for plotter windows management.""" import itertools import multiprocessing as mp @@ -13,8 +13,14 @@ ) from ansys.fluent.core.post_objects.singleton_meta import AbstractSingletonMeta -from ansys.fluent.visualization import get_config -from ansys.fluent.visualization.matplotlib.plotter_defns import Plotter, ProcessPlotter +from ansys.fluent.visualization import PLOTTER, get_config + +if PLOTTER == "matplotlib": + from ansys.fluent.visualization.plotter.matplotlib.plotter_defns import Plotter +else: + from ansys.fluent.visualization.plotter.pyvista.plotter_defns import Plotter + +from ansys.fluent.visualization.plotter.matplotlib.plotter_defns import ProcessPlotter from ansys.fluent.visualization.post_data_extractor import XYPlotDataExtractor from ansys.fluent.visualization.post_windows_manager import ( PostWindow, @@ -70,11 +76,11 @@ def close(self): pass -class MatplotWindow(PostWindow): - """Provides for managing Matplotlib windows.""" +class PlotterWindow(PostWindow): + """Provides for managing Plotter windows.""" def __init__(self, id: str, post_object: PlotDefn): - """Instantiate a Matplotlib window. + """Instantiate a plotter window. Parameters ---------- @@ -202,12 +208,12 @@ def __call__(self): self.plotter.plot(xy_data) -class MatplotWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): - """Provides for managing Matplotlib windows.""" +class PlotterWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): + """Provides for managing Plotter windows.""" def __init__(self): - """Instantiate a windows manager for matplotlib.""" - self._post_windows: Dict[str, MatplotWindow] = {} + """Instantiate a windows manager for the plotter.""" + self._post_windows: Dict[str, PlotterWindow] = {} def open_window(self, window_id: Optional[str] = None) -> str: """Open a new window. @@ -383,7 +389,7 @@ def _open_window(self, window_id: str) -> Union[Plotter, _ProcessPlotterHandle]: ): window.refresh = False else: - window = MatplotWindow(window_id, None) + window = PlotterWindow(window_id, None) self._post_windows[window_id] = window if in_notebook(): window.plotter() @@ -415,4 +421,4 @@ def _get_unique_window_id(self) -> str: return window_id -matplot_windows_manager = MatplotWindowsManager() +plotter_windows_manager = PlotterWindowsManager() diff --git a/src/ansys/fluent/visualization/plotter/pyvista/__init__.py b/src/ansys/fluent/visualization/plotter/pyvista/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py new file mode 100644 index 00000000..991729be --- /dev/null +++ b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py @@ -0,0 +1,166 @@ +from typing import List, Optional + +import numpy as np +import pyvista as pv + + +class Plotter: + """Class for pyvista chart 2D plotter.""" + + def __init__( + self, + window_id: str, + curves: Optional[List[str]] = None, + title: Optional[str] = "XY Plot", + xlabel: Optional[str] = "position", + ylabel: Optional[str] = "", + remote_process: Optional[bool] = False, + ): + """Instantiate a matplotlib plotter. + + Parameters + ---------- + window_id : str + Window id. + curves : List[str], optional + List of curves name. + title : str, optional + Plot title. + xlabel : str, optional + X axis label. + ylabel : str, optional + Y axis label. + figure : str, optional + Matplot lib figure. + axis : str, optional + Subplot indices. + remote_process: bool, optional + Is remote process. + """ + self._curves = [] if curves is None else curves + self._title = title + self._xlabel = xlabel + self._ylabel = ylabel + self._window_id = window_id + self._min_y = None + self._max_y = None + self._min_x = None + self._max_x = None + self._yscale = None + self._data = {} + self._closed = False + self._visible = False + self._remote_process = remote_process + self.chart = None + self.plotter = None + + def plot(self, data: dict) -> None: + """Draw plot in window. + + Parameters + ---------- + data : dict + Data to plot. Data consists the list of x and y + values for each curve. + """ + if not data: + return + for curve in data: + min_y_value = np.amin(data[curve]["yvalues"]) + max_y_value = np.amax(data[curve]["yvalues"]) + min_x_value = np.amin(data[curve]["xvalues"]) + max_x_value = np.amax(data[curve]["xvalues"]) + self._data[curve]["xvalues"] = data[curve]["xvalues"].tolist() + self._data[curve]["yvalues"] = data[curve]["yvalues"].tolist() + self._min_y = min(self._min_y, min_y_value) if self._min_y else min_y_value + self._max_y = max(self._max_y, max_y_value) if self._max_y else max_y_value + self._min_x = min(self._min_x, min_x_value) if self._min_x else min_x_value + self._max_x = max(self._max_x, max_x_value) if self._max_x else max_x_value + + if not self._remote_process: + self.plotter = pv.Plotter(title=f"PyFluent [{self._window_id}]") + self.chart = pv.Chart2D() + self.plotter.add_chart(self.chart) + self.chart.title = self._title + self.chart.x_label = self._xlabel or "" + self.chart.y_label = self._ylabel or "" + color_list = ["b", "r", "g", "c", "m", "y", "k"] + style_list = ["-", "--", "-.", "-.."] + for count, curve in enumerate(self._curves): + plot = self.chart.line( + self._data[curve]["xvalues"], + self._data[curve]["yvalues"], + width=2.5, + color=color_list[count % len(color_list)], + style=style_list[count % len(style_list)], + label=curve, + ) + + if self._max_x > self._min_x: + self.chart.x_range = [self._min_x, self._max_x] + y_range = self._max_y - self._min_y + if self._yscale == "log": + self.chart.y_axis.log_scale = True + y_range = 0 + self.chart.y_range = [self._min_y - y_range * 0.2, self._max_y + y_range * 0.2] + + if not self._visible: + self._visible = True + self.plotter.show() + + def close(self): + """Close window.""" + self.plotter.close() + self._closed = True + + def is_closed(self): + """Check if window is closed.""" + return self._closed + + def save_graphic(self, file_name: str): + """Save graphics. + + Parameters + ---------- + file_name : str + File name to save graphic. + """ + self.plotter.save_graphic(file_name) + + def set_properties(self, properties: dict): + """Set plot properties. + + Parameters + ---------- + properties : dict + Plot properties i.e. curves, title, xlabel and ylabel. + """ + self._curves = properties.get("curves", self._curves) + self._title = properties.get("title", self._title) + self._xlabel = properties.get("xlabel", self._xlabel) + self._ylabel = properties.get("ylabel", self._ylabel) + self._yscale = properties.get("yscale", self._yscale) + self._data = {} + self._min_y = None + self._max_y = None + self._min_x = None + self._max_x = None + self._reset() + + def __call__(self): + """Reset and show plot.""" + self._reset() + self._visible = True + self.plotter.show() + + # private methods + def _reset(self): + for curve_name in self._curves: + self._data[curve_name] = {} + self._data[curve_name]["xvalues"] = [] + self._data[curve_name]["yvalues"] = [] + if not self.plotter: + return + for curve_name in self._curves: + plot = self.chart.line([], []) + plot.label = curve_name From 5c3ca7fbbeb746cb2bd6a559fbc2bd6ed35d6fc8 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Sun, 22 Sep 2024 12:07:25 +0530 Subject: [PATCH 03/22] Update test. --- tests/test_post.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_post.py b/tests/test_post.py index 28d32575..324a06e8 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -6,9 +6,7 @@ import numpy as np import pytest -from ansys.fluent.visualization import get_config, set_config -from ansys.fluent.visualization.matplotlib import Plots -from ansys.fluent.visualization.pyvista import Graphics +from ansys.fluent.visualization import Graphics, Plots, get_config, set_config @pytest.fixture(autouse=True) From 39f22ecb70fa061a0c863457ff03cbded35ede6f Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Sun, 22 Sep 2024 12:17:39 +0530 Subject: [PATCH 04/22] Update docs. --- doc/source/api/visualization/index.rst | 2 +- doc/source/api/visualization/matplot_windows_manager.rst | 6 +++--- doc/source/api/visualization/monitorplot.rst | 2 +- doc/source/api/visualization/plots.rst | 4 ++-- doc/source/api/visualization/xyplot.rst | 2 +- doc/source/users_guide/index.rst | 4 ++-- src/ansys/fluent/visualization/plotter/plotter_objects.py | 8 ++++---- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/source/api/visualization/index.rst b/doc/source/api/visualization/index.rst index 5f1da46b..b9c01ec8 100644 --- a/doc/source/api/visualization/index.rst +++ b/doc/source/api/visualization/index.rst @@ -90,7 +90,7 @@ environment. Matplotlib is then used to plot data. .. code:: python # import module - from ansys.fluent.visualization.matplotlib import Plots + from ansys.fluent.visualization import Plots # get the plots object for the session plots_session1 = Plots(solver_session) diff --git a/doc/source/api/visualization/matplot_windows_manager.rst b/doc/source/api/visualization/matplot_windows_manager.rst index dcdabd69..f5a8ffe7 100644 --- a/doc/source/api/visualization/matplot_windows_manager.rst +++ b/doc/source/api/visualization/matplot_windows_manager.rst @@ -13,8 +13,8 @@ of every time step. .. code-block:: python - from ansys.fluent.visualization.matplotlib import Plots - from ansys.fluent.visualization.matplotlib import matplot_windows_manager + from ansys.fluent.visualization import Plots + from ansys.fluent.visualization.plotter import matplot_windows_manager plots_session = Plots(session) @@ -45,5 +45,5 @@ of every time step. cb_time_step_ended_id = session.events_manager.register_callback('TimestepEndedEvent', auto_refresh_plot) -.. autoclass:: ansys.fluent.visualization.matplotlib.matplot_windows_manager.MatplotWindowsManager +.. autoclass:: ansys.fluent.visualization.plotter.plotter_windows_manager.MatplotWindowsManager :members: \ No newline at end of file diff --git a/doc/source/api/visualization/monitorplot.rst b/doc/source/api/visualization/monitorplot.rst index 525f3dbc..f239d827 100644 --- a/doc/source/api/visualization/monitorplot.rst +++ b/doc/source/api/visualization/monitorplot.rst @@ -3,6 +3,6 @@ MonitorPlot =========== -.. autopostdoc:: ansys.fluent.visualization.matplotlib.matplot_objects.MonitorPlot +.. autopostdoc:: ansys.fluent.visualization.plotter.plotter_objects.MonitorPlot diff --git a/doc/source/api/visualization/plots.rst b/doc/source/api/visualization/plots.rst index 71fa9ac8..8377b64c 100644 --- a/doc/source/api/visualization/plots.rst +++ b/doc/source/api/visualization/plots.rst @@ -2,7 +2,7 @@ Plots ===== -.. autoclass:: ansys.fluent.visualization.matplotlib.matplot_objects.Plots +.. autoclass:: ansys.fluent.visualization.plotter.plotter_objects.Plots In the following example, a ``Plots`` object is instantiated with a Fluent session @@ -11,7 +11,7 @@ a monitor plot. .. code-block:: python - from ansys.fluent.visualization.matplotlib import Plots + from ansys.fluent.visualization import Plots plots_session = Plots(session) plot1 = plots_session.XYPlots["plot-1"] diff --git a/doc/source/api/visualization/xyplot.rst b/doc/source/api/visualization/xyplot.rst index 45f89f59..28ebe40b 100644 --- a/doc/source/api/visualization/xyplot.rst +++ b/doc/source/api/visualization/xyplot.rst @@ -3,7 +3,7 @@ XY plot ======= -.. autopostdoc:: ansys.fluent.visualization.matplotlib.matplot_objects.XYPlot +.. autopostdoc:: ansys.fluent.visualization.plotter.plotter_objects.XYPlot diff --git a/doc/source/users_guide/index.rst b/doc/source/users_guide/index.rst index 4d369955..35ac1689 100644 --- a/doc/source/users_guide/index.rst +++ b/doc/source/users_guide/index.rst @@ -20,8 +20,8 @@ This example shows how you can display a mesh: import ansys.fluent.core as pyfluent from ansys.fluent.core import examples from ansys.fluent.visualization import set_config - from ansys.fluent.visualization.matplotlib import Plots - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Plots + from ansys.fluent.visualization import Graphics from ansys.fluent.visualization.contour import Contour set_config(blocking=True, set_view_on_display="isometric") diff --git a/src/ansys/fluent/visualization/plotter/plotter_objects.py b/src/ansys/fluent/visualization/plotter/plotter_objects.py index 219e9edf..55476d94 100644 --- a/src/ansys/fluent/visualization/plotter/plotter_objects.py +++ b/src/ansys/fluent/visualization/plotter/plotter_objects.py @@ -46,9 +46,9 @@ class XYPlot(XYPlotDefn): .. code-block:: python - from ansys.fluent.visualization.matplotlib import Plots + from ansys.fluent.visualization import Plots - matplotlib_plots = Plots(session) + plots = Plots(session) plot1 = matplotlib_plots.XYPlots["plot-1"] plot1.surfaces_list = ['symmetry', 'wall'] plot1.y_axis_function = "temperature" @@ -82,9 +82,9 @@ class MonitorPlot(MonitorDefn): .. code-block:: python - from ansys.fluent.visualization.matplotlib import Plots + from ansys.fluent.visualization import Plots - matplotlib_plots = Plots(session) + plots = Plots(session) plot1 = matplotlib_plots.Monitors["plot-1"] plot1.monitor_set_name = 'residuals' plot1.plot("window-0") From 425e6b96a0dc67448bdcbfbebb61b2eee1004ed6 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Sun, 22 Sep 2024 12:26:41 +0530 Subject: [PATCH 05/22] Update docs. --- doc/source/api/visualization/matplot_windows_manager.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/api/visualization/matplot_windows_manager.rst b/doc/source/api/visualization/matplot_windows_manager.rst index f5a8ffe7..23658fdc 100644 --- a/doc/source/api/visualization/matplot_windows_manager.rst +++ b/doc/source/api/visualization/matplot_windows_manager.rst @@ -14,7 +14,7 @@ of every time step. .. code-block:: python from ansys.fluent.visualization import Plots - from ansys.fluent.visualization.plotter import matplot_windows_manager + from ansys.fluent.visualization.plotter import plotter_windows_manager plots_session = Plots(session) @@ -37,7 +37,7 @@ of every time step. # Create callback that refreshes window-1 and window-2. def auto_refresh_plot(session_id, event_info): - matplot_windows_manager.refresh_windows(session_id, ["window-1", "window-2"]) + plotter.refresh_windows(session_id, ["window-1", "window-2"]) #Register this callback with server events. cb_init_id = session.events_manager.register_callback('InitializedEvent', auto_refresh_plot) @@ -45,5 +45,5 @@ of every time step. cb_time_step_ended_id = session.events_manager.register_callback('TimestepEndedEvent', auto_refresh_plot) -.. autoclass:: ansys.fluent.visualization.plotter.plotter_windows_manager.MatplotWindowsManager +.. autoclass:: ansys.fluent.visualization.plotter.plotter_windows_manager.PlotterWindowsManager :members: \ No newline at end of file From 2faf80fbb6f86e8de9bbe52e0de9ce951549b38e Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 11:06:47 +0530 Subject: [PATCH 06/22] Add abstract class for plotters. --- .../plotter/abstract_plotter_defns.py | 51 +++++++++++++++++++ .../plotter/matplotlib/plotter_defns.py | 4 +- .../plotter/pyvista/plotter_defns.py | 6 ++- 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py diff --git a/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py b/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py new file mode 100644 index 00000000..22c92265 --- /dev/null +++ b/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py @@ -0,0 +1,51 @@ +"""Module providing matplotlib plotter functionality.""" + +from abc import ABC, abstractmethod + + +class AbstractPlotter(ABC): + """Abstract class for plotter.""" + + @abstractmethod + def plot(self, data: dict) -> None: + """Draw plot in window. + + Parameters + ---------- + data : dict + Data to plot. Data consists the list of x and y + values for each curve. + """ + pass + + @abstractmethod + def close(self): + """Close plotter window.""" + pass + + @abstractmethod + def is_closed(self): + """Check if the plotter window is closed.""" + pass + + @abstractmethod + def save_graphic(self, file_name: str): + """Save graphics to the specified file. + + Parameters + ---------- + file_name : str + File name to save graphic. + """ + pass + + @abstractmethod + def set_properties(self, properties: dict): + """Set plot properties. + + Parameters + ---------- + properties : dict + Plot properties i.e. curves, title, xlabel and ylabel. + """ + pass diff --git a/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py b/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py index 19cfc868..d39a0f2d 100644 --- a/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py +++ b/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py @@ -5,8 +5,10 @@ import matplotlib.pyplot as plt import numpy as np +from ansys.fluent.visualization.plotter.abstract_plotter_defns import AbstractPlotter -class Plotter: + +class Plotter(AbstractPlotter): """Class for matplotlib plotter.""" def __init__( diff --git a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py index 991729be..c7c5430d 100644 --- a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py +++ b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py @@ -3,8 +3,10 @@ import numpy as np import pyvista as pv +from ansys.fluent.visualization.plotter.abstract_plotter_defns import AbstractPlotter -class Plotter: + +class Plotter(AbstractPlotter): """Class for pyvista chart 2D plotter.""" def __init__( @@ -16,7 +18,7 @@ def __init__( ylabel: Optional[str] = "", remote_process: Optional[bool] = False, ): - """Instantiate a matplotlib plotter. + """Instantiate a pyvista chart 2D plotter. Parameters ---------- From 932c9ca16dc805efb58f7021660a6c2979d5bc79 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 11:55:09 +0530 Subject: [PATCH 07/22] Restructure pyvista -> graphic --- README.rst | 2 +- doc/source/api/visualization/contour.rst | 2 +- doc/source/api/visualization/graphics.rst | 4 ++-- doc/source/api/visualization/index.rst | 2 +- .../visualization/matplot_windows_manager.rst | 10 ++++---- doc/source/api/visualization/mesh.rst | 2 +- doc/source/api/visualization/pathlines.rst | 2 +- doc/source/api/visualization/plots.rst | 2 +- doc/source/api/visualization/post_objects.rst | 4 ++-- .../visualization/pyvista_windows_manager.rst | 16 ++++++------- doc/source/api/visualization/surface.rst | 2 +- doc/source/api/visualization/vector.rst | 2 +- src/ansys/fluent/visualization/__init__.py | 2 +- .../fluent/visualization/graphic/__init__.py | 6 +++++ .../graphic_objects.py} | 24 +++++++++---------- .../graphic/graphic_windows_manager.py | 12 ++++++++++ .../visualization/graphic/pyvista/__init__.py | 0 .../pyvista/graphic_defns.py} | 3 --- .../fluent/visualization/pyvista/__init__.py | 6 ----- 19 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 src/ansys/fluent/visualization/graphic/__init__.py rename src/ansys/fluent/visualization/{pyvista/pyvista_objects.py => graphic/graphic_objects.py} (90%) create mode 100644 src/ansys/fluent/visualization/graphic/graphic_windows_manager.py create mode 100644 src/ansys/fluent/visualization/graphic/pyvista/__init__.py rename src/ansys/fluent/visualization/{pyvista/pyvista_windows_manager.py => graphic/pyvista/graphic_defns.py} (99%) delete mode 100644 src/ansys/fluent/visualization/pyvista/__init__.py diff --git a/README.rst b/README.rst index f6624e0d..8e332f3a 100644 --- a/README.rst +++ b/README.rst @@ -101,7 +101,7 @@ the PyFluent-Visualization documentation. .. code:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics = Graphics(session=session) temperature_contour = graphics.Contours["contour-temperature"] temperature_contour.field = "temperature" diff --git a/doc/source/api/visualization/contour.rst b/doc/source/api/visualization/contour.rst index a271ad0c..11d0b5aa 100644 --- a/doc/source/api/visualization/contour.rst +++ b/doc/source/api/visualization/contour.rst @@ -3,4 +3,4 @@ Contour ======= -.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Contour +.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Contour diff --git a/doc/source/api/visualization/graphics.rst b/doc/source/api/visualization/graphics.rst index 94452ff3..461943c9 100644 --- a/doc/source/api/visualization/graphics.rst +++ b/doc/source/api/visualization/graphics.rst @@ -3,7 +3,7 @@ Graphics ======== -.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Graphics +.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Graphics In the following example, a ``Graphics`` object is instantiated with a Fluent session as its context. The ``Graphics`` object is used to create a mesh, @@ -11,7 +11,7 @@ contour, vector, and surface. The contour is then deleted. .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics_session = Graphics(session) #Create object diff --git a/doc/source/api/visualization/index.rst b/doc/source/api/visualization/index.rst index b9c01ec8..ce98d94a 100644 --- a/doc/source/api/visualization/index.rst +++ b/doc/source/api/visualization/index.rst @@ -32,7 +32,7 @@ environment. PyVista is then used to visualize the extracted data. .. code:: python # import module - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics # get the graphics objects for the session diff --git a/doc/source/api/visualization/matplot_windows_manager.rst b/doc/source/api/visualization/matplot_windows_manager.rst index 23658fdc..93f8d61c 100644 --- a/doc/source/api/visualization/matplot_windows_manager.rst +++ b/doc/source/api/visualization/matplot_windows_manager.rst @@ -1,9 +1,9 @@ -.. _ref_matplot_windows_manager: +.. _ref_plotter_windows_manager: -Matplotlib windows manager -========================== +Plotter windows manager +======================= -The ``MatplotWindowsManager`` class provides for managing and directly interacting +The ``PlotterWindowsManager`` class provides for managing and directly interacting with Matplotlib windows. By registering these methods with ``EventsManager``, you can update plots during run time. @@ -37,7 +37,7 @@ of every time step. # Create callback that refreshes window-1 and window-2. def auto_refresh_plot(session_id, event_info): - plotter.refresh_windows(session_id, ["window-1", "window-2"]) + plotter_windows_manager.refresh_windows(session_id, ["window-1", "window-2"]) #Register this callback with server events. cb_init_id = session.events_manager.register_callback('InitializedEvent', auto_refresh_plot) diff --git a/doc/source/api/visualization/mesh.rst b/doc/source/api/visualization/mesh.rst index aa665ba0..673ba2cd 100644 --- a/doc/source/api/visualization/mesh.rst +++ b/doc/source/api/visualization/mesh.rst @@ -3,6 +3,6 @@ Mesh ==== -.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Mesh +.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Mesh diff --git a/doc/source/api/visualization/pathlines.rst b/doc/source/api/visualization/pathlines.rst index 1bf530c0..88ad716c 100644 --- a/doc/source/api/visualization/pathlines.rst +++ b/doc/source/api/visualization/pathlines.rst @@ -3,4 +3,4 @@ Pathlines ========= -.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Pathlines +.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Pathlines diff --git a/doc/source/api/visualization/plots.rst b/doc/source/api/visualization/plots.rst index 8377b64c..5016ae9f 100644 --- a/doc/source/api/visualization/plots.rst +++ b/doc/source/api/visualization/plots.rst @@ -22,7 +22,7 @@ a monitor plot. #To plot data on local surface created in PyVista - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics pyvista_surface_provider = Graphics(session).Surfaces plots_session = Plots(session, pyvista_surface_provider) plot2 = plots_session.XYPlots["plot-2"] diff --git a/doc/source/api/visualization/post_objects.rst b/doc/source/api/visualization/post_objects.rst index 2d99b94a..0a78c2a8 100644 --- a/doc/source/api/visualization/post_objects.rst +++ b/doc/source/api/visualization/post_objects.rst @@ -6,8 +6,8 @@ graphics plots - pyvista_windows_manager - matplot_windows_manager + graphic_windows_manager + plotter_windows_manager diff --git a/doc/source/api/visualization/pyvista_windows_manager.rst b/doc/source/api/visualization/pyvista_windows_manager.rst index 06ca9da8..c48aad60 100644 --- a/doc/source/api/visualization/pyvista_windows_manager.rst +++ b/doc/source/api/visualization/pyvista_windows_manager.rst @@ -1,8 +1,8 @@ -.. _ref_pyvista_windows_manager: +.. _ref_graphic_windows_manager: -PyVista windows manager +Graphic windows manager ======================= -The ``PyVistaWindowsManager`` class provides for managing and directly interacting +The ``GraphicWindowsManager`` class provides for managing and directly interacting with PyVista windows. By registering these methods with ``EventsManager``, you can update graphics during run time and create animations. @@ -15,8 +15,8 @@ the end of every time step and creates an animation. .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics - from ansys.fluent.visualization.pyvista import pyvista_windows_manager + from ansys.fluent.visualization import Graphics + from ansys.fluent.visualization.graphic import graphic_windows_manager graphics_session = Graphics(session) @@ -30,7 +30,7 @@ the end of every time step and creates an animation. #Create callback that refreshes window-1. def auto_refresh_contour(session_id, event_info): - pyvista_windows_manager.refresh_windows(session_id, ["window-1"]) + graphic_windows_manager.refresh_windows(session_id, ["window-1"]) #Register this callback with server events. cb_init_id = session.events_manager.register_callback('InitializedEvent', auto_refresh_contour) @@ -38,8 +38,8 @@ the end of every time step and creates an animation. cb_time_step_ended_id = session.events_manager.register_callback('TimestepEndedEvent', auto_refresh_contour) #Create animation for window-1. - pyvista_windows_manager.animate_windows(session.id, ["window-1"]) + graphic_windows_manager.animate_windows(session.id, ["window-1"]) -.. autoclass:: ansys.fluent.visualization.pyvista.pyvista_windows_manager.PyVistaWindowsManager +.. autoclass:: ansys.fluent.visualization.graphic.graphic_windows_manager.GraphicWindowsManager :members: \ No newline at end of file diff --git a/doc/source/api/visualization/surface.rst b/doc/source/api/visualization/surface.rst index e95edcaf..88a1fcd9 100644 --- a/doc/source/api/visualization/surface.rst +++ b/doc/source/api/visualization/surface.rst @@ -4,6 +4,6 @@ Surface ======= -.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Surface +.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Surface diff --git a/doc/source/api/visualization/vector.rst b/doc/source/api/visualization/vector.rst index 42eb09ef..94f48193 100644 --- a/doc/source/api/visualization/vector.rst +++ b/doc/source/api/visualization/vector.rst @@ -3,4 +3,4 @@ Vector ====== -.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Vector \ No newline at end of file +.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Vector \ No newline at end of file diff --git a/src/ansys/fluent/visualization/__init__.py b/src/ansys/fluent/visualization/__init__.py index 209af9f7..87d4c331 100644 --- a/src/ansys/fluent/visualization/__init__.py +++ b/src/ansys/fluent/visualization/__init__.py @@ -24,5 +24,5 @@ def version_info() -> str: from ansys.fluent.visualization._config import get_config, set_config # noqa: F401 +from ansys.fluent.visualization.graphic import Graphics # noqa: F401 from ansys.fluent.visualization.plotter import Plots # noqa: F401 -from ansys.fluent.visualization.pyvista import Graphics # noqa: F401 diff --git a/src/ansys/fluent/visualization/graphic/__init__.py b/src/ansys/fluent/visualization/graphic/__init__.py new file mode 100644 index 00000000..a768162a --- /dev/null +++ b/src/ansys/fluent/visualization/graphic/__init__.py @@ -0,0 +1,6 @@ +"""A package that provides interfacing Fluent with graphic renderer (PyVista).""" + +from ansys.fluent.visualization.graphic.graphic_objects import Graphics # noqa: F401 +from ansys.fluent.visualization.graphic.graphic_windows_manager import ( # noqa: F401 + graphic_windows_manager, +) diff --git a/src/ansys/fluent/visualization/pyvista/pyvista_objects.py b/src/ansys/fluent/visualization/graphic/graphic_objects.py similarity index 90% rename from src/ansys/fluent/visualization/pyvista/pyvista_objects.py rename to src/ansys/fluent/visualization/graphic/graphic_objects.py index edc1238a..dd48b8ee 100644 --- a/src/ansys/fluent/visualization/pyvista/pyvista_objects.py +++ b/src/ansys/fluent/visualization/graphic/graphic_objects.py @@ -16,8 +16,8 @@ Graphics as GraphicsContainer, ) -from ansys.fluent.visualization.pyvista.pyvista_windows_manager import ( - pyvista_windows_manager, +from ansys.fluent.visualization.graphic.graphic_windows_manager import ( + graphic_windows_manager, ) @@ -49,7 +49,7 @@ class Mesh(MeshDefn): .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics_session = Graphics(session) mesh1 = graphics_session.Meshes["mesh-1"] @@ -71,7 +71,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - pyvista_windows_manager.plot( + graphic_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -81,7 +81,7 @@ class Pathlines(PathlinesDefn): .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics_session = Graphics(session) pathlines1 = graphics_session.Pathlines["pathlines-1"] @@ -103,7 +103,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - pyvista_windows_manager.plot( + graphic_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -122,7 +122,7 @@ class Surface(SurfaceDefn): .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics_session = Graphics(session) surface1 = graphics_session.Surfaces["surface-1"] @@ -146,7 +146,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - pyvista_windows_manager.plot( + graphic_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -165,7 +165,7 @@ class Contour(ContourDefn): .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics_session = Graphics(session) contour1 = graphics_session.Contours["contour-1"] @@ -187,7 +187,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - pyvista_windows_manager.plot( + graphic_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -206,7 +206,7 @@ class Vector(VectorDefn): .. code-block:: python - from ansys.fluent.visualization.pyvista import Graphics + from ansys.fluent.visualization import Graphics graphics_session = Graphics(session) vector1 = graphics_session.Vectors["vector-1"] @@ -229,6 +229,6 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - pyvista_windows_manager.plot( + graphic_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) diff --git a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py b/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py new file mode 100644 index 00000000..555c1692 --- /dev/null +++ b/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py @@ -0,0 +1,12 @@ +"""Module for graphic windows management.""" + +from ansys.fluent.visualization.graphic.pyvista.graphic_defns import ( + PyVistaWindowsManager, +) + + +class GraphicWindowsManager(PyVistaWindowsManager): + pass + + +graphic_windows_manager = GraphicWindowsManager() diff --git a/src/ansys/fluent/visualization/graphic/pyvista/__init__.py b/src/ansys/fluent/visualization/graphic/pyvista/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py b/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py similarity index 99% rename from src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py rename to src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py index b79ca53f..b361adfe 100644 --- a/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py +++ b/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py @@ -815,6 +815,3 @@ def _get_unique_window_id(self) -> str: window_id = f"window-{next(itr_count)}" if window_id not in self._post_windows: return window_id - - -pyvista_windows_manager = PyVistaWindowsManager() diff --git a/src/ansys/fluent/visualization/pyvista/__init__.py b/src/ansys/fluent/visualization/pyvista/__init__.py deleted file mode 100644 index 25d907dd..00000000 --- a/src/ansys/fluent/visualization/pyvista/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""A package that provides interfacing Fluent with PyVista.""" - -from ansys.fluent.visualization.pyvista.pyvista_objects import Graphics # noqa: F401 -from ansys.fluent.visualization.pyvista.pyvista_windows_manager import ( # noqa: F401 - pyvista_windows_manager, -) From 9ec22a278df499113875bd884270ec83a0d6a29f Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 12:01:51 +0530 Subject: [PATCH 08/22] Rename docs. --- .../{pyvista_windows_manager.rst => graphic_windows_manager.rst} | 0 .../{matplot_windows_manager.rst => plotter_windows_manager.rst} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename doc/source/api/visualization/{pyvista_windows_manager.rst => graphic_windows_manager.rst} (100%) rename doc/source/api/visualization/{matplot_windows_manager.rst => plotter_windows_manager.rst} (100%) diff --git a/doc/source/api/visualization/pyvista_windows_manager.rst b/doc/source/api/visualization/graphic_windows_manager.rst similarity index 100% rename from doc/source/api/visualization/pyvista_windows_manager.rst rename to doc/source/api/visualization/graphic_windows_manager.rst diff --git a/doc/source/api/visualization/matplot_windows_manager.rst b/doc/source/api/visualization/plotter_windows_manager.rst similarity index 100% rename from doc/source/api/visualization/matplot_windows_manager.rst rename to doc/source/api/visualization/plotter_windows_manager.rst From 56020e678cc78a624003664841de7dfa639e4e2d Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 12:07:01 +0530 Subject: [PATCH 09/22] Add docstring. --- .../fluent/visualization/graphic/graphic_windows_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py b/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py index 555c1692..e8ff2fc0 100644 --- a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py +++ b/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py @@ -6,6 +6,8 @@ class GraphicWindowsManager(PyVistaWindowsManager): + """Provides for managing graphic windows.""" + pass From cfda0f3108a8f5ded918d2541e1536a45fb243f4 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 19:43:27 +0530 Subject: [PATCH 10/22] Refactor graphics. --- .../fluent/visualization/graphic/__init__.py | 2 +- .../visualization/graphic/graphic_objects.py | 12 +- .../graphic/graphic_windows_manager.py | 767 ++++++++++++++++- .../graphic/pyvista/graphic_defns.py | 787 +----------------- 4 files changed, 791 insertions(+), 777 deletions(-) diff --git a/src/ansys/fluent/visualization/graphic/__init__.py b/src/ansys/fluent/visualization/graphic/__init__.py index a768162a..cfe59cff 100644 --- a/src/ansys/fluent/visualization/graphic/__init__.py +++ b/src/ansys/fluent/visualization/graphic/__init__.py @@ -2,5 +2,5 @@ from ansys.fluent.visualization.graphic.graphic_objects import Graphics # noqa: F401 from ansys.fluent.visualization.graphic.graphic_windows_manager import ( # noqa: F401 - graphic_windows_manager, + graphics_windows_manager, ) diff --git a/src/ansys/fluent/visualization/graphic/graphic_objects.py b/src/ansys/fluent/visualization/graphic/graphic_objects.py index dd48b8ee..9caa0408 100644 --- a/src/ansys/fluent/visualization/graphic/graphic_objects.py +++ b/src/ansys/fluent/visualization/graphic/graphic_objects.py @@ -17,7 +17,7 @@ ) from ansys.fluent.visualization.graphic.graphic_windows_manager import ( - graphic_windows_manager, + graphics_windows_manager, ) @@ -71,7 +71,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - graphic_windows_manager.plot( + graphics_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -103,7 +103,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - graphic_windows_manager.plot( + graphics_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -146,7 +146,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - graphic_windows_manager.plot( + graphics_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -187,7 +187,7 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - graphic_windows_manager.plot( + graphics_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) @@ -229,6 +229,6 @@ def display(self, window_id: Optional[str] = None, overlay: Optional[bool] = Fal Whether to overlay graphics over existing graphics. The default is ``False``. """ - graphic_windows_manager.plot( + graphics_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) diff --git a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py b/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py index e8ff2fc0..ba2b840b 100644 --- a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py +++ b/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py @@ -1,14 +1,769 @@ """Module for graphic windows management.""" -from ansys.fluent.visualization.graphic.pyvista.graphic_defns import ( - PyVistaWindowsManager, +"""Module for pyVista windows management.""" + +from enum import Enum +import itertools +import threading +from typing import Dict, List, Optional, Union + +from ansys.fluent.core.fluent_connection import FluentConnection +from ansys.fluent.core.post_objects.check_in_notebook import in_notebook +from ansys.fluent.core.post_objects.post_object_definitions import GraphicsDefn +from ansys.fluent.core.post_objects.singleton_meta import AbstractSingletonMeta +import numpy as np +import pyvista as pv +from pyvistaqt import BackgroundPlotter + +from ansys.fluent.visualization import get_config +from ansys.fluent.visualization.post_data_extractor import FieldDataExtractor +from ansys.fluent.visualization.post_windows_manager import ( + PostWindow, + PostWindowsManager, ) -class GraphicWindowsManager(PyVistaWindowsManager): - """Provides for managing graphic windows.""" +class FieldDataType(Enum): + """Provides surface data types.""" + + Meshes = 1 + Vectors = 2 + Contours = 3 + Pathlines = 4 + + +from ansys.fluent.visualization.graphic.pyvista.graphic_defns import Renderer + + +class GraphicsWindow(PostWindow): + """Provides for managing Graphics windows.""" + + def __init__(self, id: str, post_object: GraphicsDefn): + """Instantiate a Graphics window. + + Parameters + ---------- + id : str + Window ID. + post_object : GraphicsDefn + Object to draw. + """ + self.post_object: GraphicsDefn = post_object + self.id: str = id + self.renderer = Renderer(id, in_notebook(), get_config()["blocking"]) + self.overlay: bool = False + self.fetch_data: bool = False + self.show_window: bool = True + self.animate: bool = False + self.close: bool = False + self.refresh: bool = False + self.update: bool = False + self._visible: bool = False + self._data = {} + + def set_data(self, data_type: FieldDataType, data: Dict[int, Dict[str, np.array]]): + """Set data for graphics.""" + self._data[data_type] = data + + def fetch(self): + """Fetch data for graphics.""" + if not self.post_object: + return + obj = self.post_object + if obj.__class__.__name__ == "Mesh": + self._fetch_mesh(obj) + elif obj.__class__.__name__ == "Surface": + self._fetch_surface(obj) + elif obj.__class__.__name__ == "Contour": + self._fetch_contour(obj) + elif obj.__class__.__name__ == "Vector": + self._fetch_vector(obj) + elif obj.__class__.__name__ == "Pathlines": + self._fetch_pathlines(obj) + + def render(self): + """Render graphics.""" + if not self.post_object: + return + obj = self.post_object + + if not self.overlay: + self.renderer._clear_plotter(in_notebook()) + if obj.__class__.__name__ == "Mesh": + self._display_mesh(obj) + elif obj.__class__.__name__ == "Surface": + self._display_surface(obj) + elif obj.__class__.__name__ == "Contour": + self._display_contour(obj) + elif obj.__class__.__name__ == "Vector": + self._display_vector(obj) + elif obj.__class__.__name__ == "Pathlines": + self._display_pathlines(obj) + if self.animate: + self.renderer.write_frame() + self.renderer._set_camera(get_config()["set_view_on_display"]) + if not self._visible and self.show_window: + self.renderer.show() + self._visible = True + + def plot(self): + """Display graphics.""" + self.fetch() + self.render() + + # private methods + + def _fetch_vector(self, obj): + if self._data.get(FieldDataType.Vectors) is None or self.fetch_data: + self._data[FieldDataType.Vectors] = FieldDataExtractor(obj).fetch_data() + + def _display_vector(self, obj): + field_info = obj._api_helper.field_info() + vectors_of = obj.vectors_of() + # scalar bar properties + scalar_bar_args = self.renderer._scalar_bar_default_properties() + + field = obj.field() + field_unit = obj._api_helper.get_field_unit(field) + field = f"{field}\n[{field_unit}]" if field_unit else field + + for surface_id, mesh_data in self._data[FieldDataType.Vectors].items(): + if "vertices" not in mesh_data or "faces" not in mesh_data: + continue + mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 + mesh_data[vectors_of].shape = ( + mesh_data[vectors_of].size // 3, + 3, + ) + vector_scale = mesh_data["vector-scale"][0] + topology = "line" if mesh_data["faces"][0] == 2 else "face" + if topology == "line": + mesh = pv.PolyData( + mesh_data["vertices"], + lines=mesh_data["faces"], + ) + else: + mesh = pv.PolyData( + mesh_data["vertices"], + faces=mesh_data["faces"], + ) + mesh.cell_data["vectors"] = mesh_data[vectors_of] + scalar_field = mesh_data[obj.field()] + velocity_magnitude = np.linalg.norm(mesh_data[vectors_of], axis=1) + if obj.range.option() == "auto-range-off": + auto_range_off = obj.range.auto_range_off + range_ = [auto_range_off.minimum(), auto_range_off.maximum()] + if auto_range_off.clip_to_range(): + velocity_magnitude = np.ma.masked_outside( + velocity_magnitude, + auto_range_off.minimum(), + auto_range_off.maximum(), + ).filled(fill_value=0) + else: + auto_range_on = obj.range.auto_range_on + if auto_range_on.global_range(): + range_ = field_info.get_scalar_field_range(obj.field(), False) + else: + range_ = [np.min(scalar_field), np.max(scalar_field)] + + if obj.skip(): + vmag = np.zeros(velocity_magnitude.size) + vmag[:: obj.skip() + 1] = velocity_magnitude[:: obj.skip() + 1] + velocity_magnitude = vmag + mesh.cell_data["Velocity Magnitude"] = velocity_magnitude + mesh.cell_data[field] = scalar_field + glyphs = mesh.glyph( + orient="vectors", + scale="Velocity Magnitude", + factor=vector_scale * obj.scale(), + geom=pv.Arrow(), + ) + self.renderer.render( + glyphs, + scalars=field, + scalar_bar_args=scalar_bar_args, + clim=range_, + ) + if obj.show_edges(): + self.renderer.render(mesh, show_edges=True, color="white") + + def _fetch_pathlines(self, obj): + if self._data.get(FieldDataType.Pathlines) is None or self.fetch_data: + self._data[FieldDataType.Pathlines] = FieldDataExtractor(obj).fetch_data() + + def _display_pathlines(self, obj): + field = obj.field() + field_unit = obj._api_helper.get_field_unit(field) + field = f"{field}\n[{field_unit}]" if field_unit else field + + # scalar bar properties + scalar_bar_args = self.renderer._scalar_bar_default_properties() + + # loop over all meshes + for surface_id, surface_data in self._data[FieldDataType.Pathlines].items(): + if "vertices" not in surface_data or "lines" not in surface_data: + continue + surface_data["vertices"].shape = surface_data["vertices"].size // 3, 3 + + mesh = pv.PolyData( + surface_data["vertices"], + lines=surface_data["lines"], + ) + + mesh.point_data[field] = surface_data[obj.field()] + self.renderer.render( + mesh, + scalars=field, + scalar_bar_args=scalar_bar_args, + ) + + def _fetch_contour(self, obj): + if self._data.get(FieldDataType.Contours) is None or self.fetch_data: + self._data[FieldDataType.Contours] = FieldDataExtractor(obj).fetch_data() + + def _display_contour(self, obj): + # contour properties + field = obj.field() + field_unit = obj._api_helper.get_field_unit(field) + field = f"{field}\n[{field_unit}]" if field_unit else field + range_option = obj.range.option() + filled = obj.filled() + contour_lines = obj.contour_lines() + node_values = obj.node_values() + + # scalar bar properties + scalar_bar_args = self.renderer._scalar_bar_default_properties() + + # loop over all meshes + for surface_id, surface_data in self._data[FieldDataType.Contours].items(): + if "vertices" not in surface_data or "faces" not in surface_data: + continue + surface_data["vertices"].shape = surface_data["vertices"].size // 3, 3 + topology = "line" if surface_data["faces"][0] == 2 else "face" + if topology == "line": + mesh = pv.PolyData( + surface_data["vertices"], + lines=surface_data["faces"], + ) + else: + mesh = pv.PolyData( + surface_data["vertices"], + faces=surface_data["faces"], + ) + if node_values: + mesh.point_data[field] = surface_data[obj.field()] + else: + mesh.cell_data[field] = surface_data[obj.field()] + if range_option == "auto-range-off": + auto_range_off = obj.range.auto_range_off + if auto_range_off.clip_to_range(): + if np.min(mesh[field]) < auto_range_off.maximum(): + maximum_below = mesh.clip_scalar( + scalars=field, + value=auto_range_off.maximum(), + ) + if np.max(maximum_below[field]) > auto_range_off.minimum(): + minimum_above = maximum_below.clip_scalar( + scalars=field, + invert=False, + value=auto_range_off.minimum(), + ) + if filled: + self.renderer.render( + minimum_above, + scalars=field, + show_edges=obj.show_edges(), + scalar_bar_args=scalar_bar_args, + ) + + if (not filled or contour_lines) and ( + np.min(minimum_above[field]) + != np.max(minimum_above[field]) + ): + self.renderer.render( + minimum_above.contour(isosurfaces=20) + ) + else: + if filled: + self.renderer.render( + mesh, + clim=[ + auto_range_off.minimum(), + auto_range_off.maximum(), + ], + scalars=field, + show_edges=obj.show_edges(), + scalar_bar_args=scalar_bar_args, + ) + if (not filled or contour_lines) and ( + np.min(mesh[field]) != np.max(mesh[field]) + ): + self.renderer.render(mesh.contour(isosurfaces=20)) + else: + auto_range_on = obj.range.auto_range_on + if auto_range_on.global_range(): + if filled: + field_info = obj._api_helper.field_info() + self.renderer.render( + mesh, + clim=field_info.get_scalar_field_range(obj.field(), False), + scalars=field, + show_edges=obj.show_edges(), + scalar_bar_args=scalar_bar_args, + ) + if (not filled or contour_lines) and ( + np.min(mesh[field]) != np.max(mesh[field]) + ): + self.renderer.render(mesh.contour(isosurfaces=20)) + + else: + if filled: + self.renderer.render( + mesh, + scalars=field, + show_edges=obj.show_edges(), + scalar_bar_args=scalar_bar_args, + ) + if (not filled or contour_lines) and ( + np.min(mesh[field]) != np.max(mesh[field]) + ): + self.renderer.render(mesh.contour(isosurfaces=20)) + + def _fetch_surface(self, obj): + dummy_object = "dummy_object" + post_session = obj.get_root() + if ( + obj.definition.type() == "iso-surface" + and obj.definition.iso_surface.rendering() == "contour" + ): + contour = post_session.Contours[dummy_object] + contour.field = obj.definition.iso_surface.field() + contour.surfaces_list = [obj._name] + contour.show_edges = obj.show_edges() + contour.range.auto_range_on.global_range = True + contour.boundary_values = True + self._fetch_contour(contour) + del post_session.Contours[dummy_object] + else: + mesh = post_session.Meshes[dummy_object] + mesh.surfaces_list = [obj._name] + mesh.show_edges = obj.show_edges() + self._fetch_mesh(mesh) + del post_session.Meshes[dummy_object] + + def _display_surface(self, obj): + dummy_object = "dummy_object" + post_session = obj.get_root() + if ( + obj.definition.type() == "iso-surface" + and obj.definition.iso_surface.rendering() == "contour" + ): + contour = post_session.Contours[dummy_object] + contour.field = obj.definition.iso_surface.field() + contour.surfaces_list = [obj._name] + contour.show_edges = obj.show_edges() + contour.range.auto_range_on.global_range = True + contour.boundary_values = True + self._display_contour(contour) + del post_session.Contours[dummy_object] + else: + mesh = post_session.Meshes[dummy_object] + mesh.surfaces_list = [obj._name] + mesh.show_edges = obj.show_edges() + self._display_mesh(mesh) + del post_session.Meshes[dummy_object] + + def _fetch_mesh(self, obj): + if self._data.get(FieldDataType.Meshes) is None or self.fetch_data: + self._data[FieldDataType.Meshes] = FieldDataExtractor(obj).fetch_data() + + def _display_mesh(self, obj): + for surface_id, mesh_data in self._data[FieldDataType.Meshes].items(): + if "vertices" not in mesh_data or "faces" not in mesh_data: + continue + mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 + topology = "line" if mesh_data["faces"][0] == 2 else "face" + if topology == "line": + mesh = pv.PolyData( + mesh_data["vertices"], + lines=mesh_data["faces"], + ) + else: + mesh = pv.PolyData( + mesh_data["vertices"], + faces=mesh_data["faces"], + ) + color_size = len(self.renderer._colors) + color = list(self.renderer._colors.values())[surface_id % color_size] + self.renderer.render(mesh, show_edges=obj.show_edges(), color=color) + + def _get_refresh_for_plotter(self, window: "GraphicsWindow"): + def refresh(): + with GraphicsWindowsManager._condition: + plotter = window.renderer + if window.close: + window.animate = False + plotter.close() + return + if not window.update: + return + window.update = False + try: + window.plot() + finally: + GraphicsWindowsManager._condition.notify() + + return refresh + + +class GraphicsWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): + """Provides for managing Graphics windows.""" + + _condition = threading.Condition() + + def __init__(self): + """Instantiate ``GraphicsWindow`` for Graphics.""" + self._post_windows: Dict[str:GraphicsWindow] = {} + self._plotter_thread: threading.Thread = None + self._post_object: GraphicsDefn = None + self._window_id: Optional[str] = None + self._exit_thread: bool = False + self._app = None + + def get_window(self, window_id: str) -> GraphicsWindow: + """Get the Graphics window. + + Parameters + ---------- + window_id : str + Window ID. + + Returns + ------- + GraphicsWindow + Graphics window. + """ + with self._condition: + return self._post_windows.get(window_id, None) + + def get_plotter(self, window_id: str) -> Union[BackgroundPlotter, pv.Plotter]: + """Get the PyVista plotter. + + Parameters + ---------- + window_id : str + Window ID for the plotter. + + Returns + ------- + Union[BackgroundPlotter, pv.Plotter] + PyVista plotter. + """ + with self._condition: + return self._post_windows[window_id].renderer.plotter + + def open_window(self, window_id: Optional[str] = None) -> str: + """Open a new window. + + Parameters + ---------- + window_id : str, optional + ID for the new window. The default is ``None``, in which + case a unique ID is automatically assigned. + + Returns + ------- + str + ID for the new window. + """ + with self._condition: + if not window_id: + window_id = self._get_unique_window_id() + if in_notebook() or get_config()["blocking"]: + self._open_window_notebook(window_id) + else: + self._open_and_plot_console(None, window_id) + return window_id + + def set_object_for_window(self, object: GraphicsDefn, window_id: str) -> None: + """Associate a visualization object with a running window instance. + + Parameters + ---------- + object : GraphicsDefn + Post object to associate with a running window instance. + window_id : str + Window ID for the association. + + Raises + ------ + RuntimeError + If the window does not support the object. + """ + if not isinstance(object, GraphicsDefn): + raise RuntimeError("Object type currently not supported.") + with self._condition: + window = self._post_windows.get(window_id) + if window: + window.post_object = object + + def plot( + self, + object: GraphicsDefn, + window_id: Optional[str] = None, + fetch_data: Optional[bool] = False, + overlay: Optional[bool] = False, + ) -> None: + """Draw a plot. + + Parameters + ---------- + object: GraphicsDefn + Object to plot. + window_id : str, optional + Window ID for the plot. The default is ``None``, in which + case a unique ID is assigned. + fetch_data : bool, optional + Whether to fetch data. The default is ``False``. + overlay : bool, optional + Whether to overlay graphics over existing graphics. + The default is ``False``. + Raises + ------ + RuntimeError + If the window does not support the object. + """ + if not isinstance(object, GraphicsDefn): + raise RuntimeError("Object type currently not supported.") + with self._condition: + if not window_id: + window_id = self._get_unique_window_id() + if in_notebook() or get_config()["blocking"]: + self._plot_notebook(object, window_id, fetch_data, overlay) + else: + self._open_and_plot_console(object, window_id, fetch_data, overlay) + + def save_graphic( + self, + window_id: str, + format: str, + ) -> None: + """Save a graphic. + + Parameters + ---------- + window_id : str + Window ID for the graphic to save. + format : str + Graphic file format. Supported formats are SVG, EPS, PS, PDF, and TEX. + + Raises + ------ + ValueError + If the window does not support the specified format. + """ + with self._condition: + window = self._post_windows.get(window_id) + if window: + window.renderer.plotter.save_graphic(f"{window_id}.{format}") + + def refresh_windows( + self, + session_id: Optional[str] = "", + windows_id: Optional[List[str]] = [], + overlay: Optional[bool] = False, + ) -> None: + """Refresh windows. + + Parameters + ---------- + session_id : str, optional + Session ID for refreshing the windows that belong only to this + session. The default is ``""``, in which case the windows in all + sessions are refreshed. + windows_id : List[str], optional + IDs of the windows to refresh. The default is ``[]``, in which case + all windows are refreshed. + overlay : bool, Optional + Overlay graphics over existing graphics. + """ + with self._condition: + windows_id = self._get_windows_id(session_id, windows_id) + for window_id in windows_id: + window = self._post_windows.get(window_id) + if window: + window.refresh = True + self.plot(window.post_object, window.id, overlay=overlay) + + def animate_windows( + self, + session_id: Optional[str] = "", + windows_id: Optional[List[str]] = [], + ) -> None: + """Animate windows. + + Parameters + ---------- + session_id : str, optional + Session ID for animating the windows that belong only to this + session. The default is ``""``, in which case the windows in all + sessions are animated. + windows_id : List[str], optional + List of IDs for the windows to animate. The default is ``[]``, in which + case all windows are animated. + + Raises + ------ + NotImplementedError + If not implemented. + """ + with self._condition: + windows_id = self._get_windows_id(session_id, windows_id) + for window_id in windows_id: + window = self._post_windows.get(window_id) + if window: + window.animate = True + window.renderer.plotter.open_gif(f"{window.id}.gif") + + def close_windows( + self, + session_id: Optional[str] = "", + windows_id: Optional[List[str]] = [], + ) -> None: + """Close windows. + + Parameters + ---------- + session_id : str, optional + Session ID for closing the windows that belong only to this session. + The default is ``""``, in which case the windows in all sessions + are closed. + windows_id : List[str], optional + List of IDs for the windows to close. The default is ``[]``, in which + all windows are closed. + """ + with self._condition: + windows_id = self._get_windows_id(session_id, windows_id) + for window_id in windows_id: + window = self._post_windows.get(window_id) + if window: + if in_notebook() or get_config()["blocking"]: + window.renderer.plotter.close() + window.close = True + + # private methods + + def _display(self) -> None: + while True: + with self._condition: + if self._exit_thread: + break + if self._window_id: + window = self._post_windows.get(self._window_id) + plotter = window.renderer.plotter if window else None + animate = window.animate if window else False + if not plotter or plotter._closed: + window = GraphicsWindow(self._window_id, self._post_object) + plotter = window.renderer.plotter + self._app = plotter.app + plotter.add_callback( + window._get_refresh_for_plotter(window), + 100, + ) + window.post_object = self._post_object + window.fetch_data = self._fetch_data + window.overlay = self._overlay + window.animate = animate + window.update = True + self._post_windows[self._window_id] = window + self._post_object = None + self._window_id = None + self._app.processEvents() + with self._condition: + for window in self._post_windows.values(): + plotter = window.renderer.plotter + plotter.close() + plotter.app.quit() + self._post_windows.clear() + self._condition.notify() + + def _open_and_plot_console( + self, + obj: object, + window_id: str, + fetch_data: bool = False, + overlay: bool = False, + ) -> None: + if self._exit_thread: + return + with self._condition: + self._window_id = window_id + self._post_object = obj + self._fetch_data = fetch_data + self._overlay = overlay + + if not self._plotter_thread: + if FluentConnection._monitor_thread: + FluentConnection._monitor_thread.cbs.append(self._exit) + self._plotter_thread = threading.Thread(target=self._display, args=()) + self._plotter_thread.start() + + with self._condition: + self._condition.wait() + + def _open_window_notebook(self, window_id: str) -> pv.Plotter: + window = self._post_windows.get(window_id) + if window and not window.close and window.refresh: + window.refresh = False + else: + window = GraphicsWindow(window_id, None) + self._post_windows[window_id] = window + return window + + def _plot_notebook( + self, obj: object, window_id: str, fetch_data: bool, overlay: bool + ) -> None: + window = self._open_window_notebook(window_id) + window.post_object = obj + window.fetch_data = fetch_data + window.overlay = overlay + window.plot() + + def _get_windows_id( + self, + session_id: Optional[str] = "", + windows_id: Optional[List[str]] = [], + ) -> List[str]: + with self._condition: + return [ + window_id + for window_id in [ + window_id + for window_id, window in self._post_windows.items() + if not window.plotter._closed + and ( + not session_id + or session_id == window.post_object._api_helper.id() + ) + ] + if not windows_id or window_id in windows_id + ] + + def _exit(self) -> None: + if self._plotter_thread: + with self._condition: + self._exit_thread = True + self._condition.wait() + self._plotter_thread.join() + self._plotter_thread = None - pass + def _get_unique_window_id(self) -> str: + itr_count = itertools.count() + with self._condition: + while True: + window_id = f"window-{next(itr_count)}" + if window_id not in self._post_windows: + return window_id -graphic_windows_manager = GraphicWindowsManager() +graphics_windows_manager = GraphicsWindowsManager() diff --git a/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py b/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py index b361adfe..d1f1f791 100644 --- a/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py +++ b/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py @@ -1,65 +1,17 @@ """Module for pyVista windows management.""" -from enum import Enum -import itertools -import threading -from typing import Dict, List, Optional, Union - -from ansys.fluent.core.fluent_connection import FluentConnection -from ansys.fluent.core.post_objects.check_in_notebook import in_notebook -from ansys.fluent.core.post_objects.post_object_definitions import GraphicsDefn -from ansys.fluent.core.post_objects.singleton_meta import AbstractSingletonMeta -import numpy as np import pyvista as pv from pyvistaqt import BackgroundPlotter -from ansys.fluent.visualization import get_config -from ansys.fluent.visualization.post_data_extractor import FieldDataExtractor -from ansys.fluent.visualization.post_windows_manager import ( - PostWindow, - PostWindowsManager, -) - - -class FieldDataType(Enum): - """Provides surface data types.""" - - Meshes = 1 - Vectors = 2 - Contours = 3 - Pathlines = 4 - -class PyVistaWindow(PostWindow): - """Provides for managing PyVista windows.""" - - def __init__(self, id: str, post_object: GraphicsDefn): - """Instantiate a PyVista window. - - Parameters - ---------- - id : str - Window ID. - post_object : GraphicsDefn - Object to draw. - """ - self.post_object: GraphicsDefn = post_object - self.id: str = id +class Renderer: + def __init__(self, win_id: str, in_notebook: bool, non_interactive: bool): self.plotter: Union[BackgroundPlotter, pv.Plotter] = ( - pv.Plotter(title=f"PyFluent ({self.id})") - if in_notebook() or get_config()["blocking"] - else BackgroundPlotter(title=f"PyFluent ({self.id})") + pv.Plotter(title=f"PyFluent ({win_id})") + if in_notebook or non_interactive + else BackgroundPlotter(title=f"PyFluent ({win_id})") ) - self.overlay: bool = False - self.fetch_data: bool = False - self.show_window: bool = True - self.animate: bool = False - self.close: bool = False - self.refresh: bool = False - self.update: bool = False - self._visible: bool = False self._init_properties() - self._data = {} self._colors = { "red": [255, 0, 0], "lime": [0, 255, 0], @@ -80,75 +32,6 @@ def __init__(self, id: str, post_object: GraphicsDefn): "white": [255, 255, 255], } - def set_data(self, data_type: FieldDataType, data: Dict[int, Dict[str, np.array]]): - """Set data for graphics.""" - self._data[data_type] = data - - def fetch(self): - """Fetch data for graphics.""" - if not self.post_object: - return - obj = self.post_object - if obj.__class__.__name__ == "Mesh": - self._fetch_mesh(obj) - elif obj.__class__.__name__ == "Surface": - self._fetch_surface(obj) - elif obj.__class__.__name__ == "Contour": - self._fetch_contour(obj) - elif obj.__class__.__name__ == "Vector": - self._fetch_vector(obj) - elif obj.__class__.__name__ == "Pathlines": - self._fetch_pathlines(obj) - - def render(self): - """Render graphics.""" - if not self.post_object: - return - obj = self.post_object - plotter = self.plotter - camera = plotter.camera.copy() - if not self.overlay: - if in_notebook() and self.plotter.theme._jupyter_backend == "pythreejs": - plotter.remove_actor(plotter.renderer.actors.copy()) - else: - plotter.clear() - if obj.__class__.__name__ == "Mesh": - self._display_mesh(obj, plotter) - elif obj.__class__.__name__ == "Surface": - self._display_surface(obj, plotter) - elif obj.__class__.__name__ == "Contour": - self._display_contour(obj, plotter) - elif obj.__class__.__name__ == "Vector": - self._display_vector(obj, plotter) - elif obj.__class__.__name__ == "Pathlines": - self._display_pathlines(obj, plotter) - if self.animate: - plotter.write_frame() - view = get_config()["set_view_on_display"] - view_fun = { - "xy": plotter.view_xy, - "xz": plotter.view_xz, - "yx": plotter.view_yx, - "yz": plotter.view_yz, - "zx": plotter.view_zx, - "zy": plotter.view_zy, - "isometric": plotter.view_isometric, - }.get(view) - if view_fun: - view_fun() - else: - plotter.camera = camera.copy() - if not self._visible and self.show_window: - plotter.show() - self._visible = True - - def plot(self): - """Display graphics.""" - self.fetch() - self.render() - - # private methods - def _init_properties(self): self.plotter.theme.cmap = "jet" self.plotter.background_color = "white" @@ -166,652 +49,28 @@ def _scalar_bar_default_properties(self) -> dict: position_y=0.3, ) - def _fetch_vector(self, obj): - if self._data.get(FieldDataType.Vectors) is None or self.fetch_data: - self._data[FieldDataType.Vectors] = FieldDataExtractor(obj).fetch_data() - - def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - field_info = obj._api_helper.field_info() - vectors_of = obj.vectors_of() - # scalar bar properties - scalar_bar_args = self._scalar_bar_default_properties() - - field = obj.field() - field_unit = obj._api_helper.get_field_unit(field) - field = f"{field}\n[{field_unit}]" if field_unit else field - - for surface_id, mesh_data in self._data[FieldDataType.Vectors].items(): - if "vertices" not in mesh_data or "faces" not in mesh_data: - continue - mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 - mesh_data[vectors_of].shape = ( - mesh_data[vectors_of].size // 3, - 3, - ) - vector_scale = mesh_data["vector-scale"][0] - topology = "line" if mesh_data["faces"][0] == 2 else "face" - if topology == "line": - mesh = pv.PolyData( - mesh_data["vertices"], - lines=mesh_data["faces"], - ) - else: - mesh = pv.PolyData( - mesh_data["vertices"], - faces=mesh_data["faces"], - ) - mesh.cell_data["vectors"] = mesh_data[vectors_of] - scalar_field = mesh_data[obj.field()] - velocity_magnitude = np.linalg.norm(mesh_data[vectors_of], axis=1) - if obj.range.option() == "auto-range-off": - auto_range_off = obj.range.auto_range_off - range_ = [auto_range_off.minimum(), auto_range_off.maximum()] - if auto_range_off.clip_to_range(): - velocity_magnitude = np.ma.masked_outside( - velocity_magnitude, - auto_range_off.minimum(), - auto_range_off.maximum(), - ).filled(fill_value=0) - else: - auto_range_on = obj.range.auto_range_on - if auto_range_on.global_range(): - range_ = field_info.get_scalar_field_range(obj.field(), False) - else: - range_ = [np.min(scalar_field), np.max(scalar_field)] - - if obj.skip(): - vmag = np.zeros(velocity_magnitude.size) - vmag[:: obj.skip() + 1] = velocity_magnitude[:: obj.skip() + 1] - velocity_magnitude = vmag - mesh.cell_data["Velocity Magnitude"] = velocity_magnitude - mesh.cell_data[field] = scalar_field - glyphs = mesh.glyph( - orient="vectors", - scale="Velocity Magnitude", - factor=vector_scale * obj.scale(), - geom=pv.Arrow(), - ) - plotter.add_mesh( - glyphs, - scalars=field, - scalar_bar_args=scalar_bar_args, - clim=range_, - ) - if obj.show_edges(): - plotter.add_mesh(mesh, show_edges=True, color="white") - - def _fetch_pathlines(self, obj): - if self._data.get(FieldDataType.Pathlines) is None or self.fetch_data: - self._data[FieldDataType.Pathlines] = FieldDataExtractor(obj).fetch_data() - - def _display_pathlines(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - field = obj.field() - field_unit = obj._api_helper.get_field_unit(field) - field = f"{field}\n[{field_unit}]" if field_unit else field - - # scalar bar properties - scalar_bar_args = self._scalar_bar_default_properties() - - # loop over all meshes - for surface_id, surface_data in self._data[FieldDataType.Pathlines].items(): - if "vertices" not in surface_data or "lines" not in surface_data: - continue - surface_data["vertices"].shape = surface_data["vertices"].size // 3, 3 - - mesh = pv.PolyData( - surface_data["vertices"], - lines=surface_data["lines"], - ) - - mesh.point_data[field] = surface_data[obj.field()] - plotter.add_mesh( - mesh, - scalars=field, - scalar_bar_args=scalar_bar_args, - ) - - def _fetch_contour(self, obj): - if self._data.get(FieldDataType.Contours) is None or self.fetch_data: - self._data[FieldDataType.Contours] = FieldDataExtractor(obj).fetch_data() - - def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - # contour properties - field = obj.field() - field_unit = obj._api_helper.get_field_unit(field) - field = f"{field}\n[{field_unit}]" if field_unit else field - range_option = obj.range.option() - filled = obj.filled() - contour_lines = obj.contour_lines() - node_values = obj.node_values() - - # scalar bar properties - scalar_bar_args = self._scalar_bar_default_properties() - - # loop over all meshes - for surface_id, surface_data in self._data[FieldDataType.Contours].items(): - if "vertices" not in surface_data or "faces" not in surface_data: - continue - surface_data["vertices"].shape = surface_data["vertices"].size // 3, 3 - topology = "line" if surface_data["faces"][0] == 2 else "face" - if topology == "line": - mesh = pv.PolyData( - surface_data["vertices"], - lines=surface_data["faces"], - ) - else: - mesh = pv.PolyData( - surface_data["vertices"], - faces=surface_data["faces"], - ) - if node_values: - mesh.point_data[field] = surface_data[obj.field()] - else: - mesh.cell_data[field] = surface_data[obj.field()] - if range_option == "auto-range-off": - auto_range_off = obj.range.auto_range_off - if auto_range_off.clip_to_range(): - if np.min(mesh[field]) < auto_range_off.maximum(): - maximum_below = mesh.clip_scalar( - scalars=field, - value=auto_range_off.maximum(), - ) - if np.max(maximum_below[field]) > auto_range_off.minimum(): - minimum_above = maximum_below.clip_scalar( - scalars=field, - invert=False, - value=auto_range_off.minimum(), - ) - if filled: - plotter.add_mesh( - minimum_above, - scalars=field, - show_edges=obj.show_edges(), - scalar_bar_args=scalar_bar_args, - ) - - if (not filled or contour_lines) and ( - np.min(minimum_above[field]) - != np.max(minimum_above[field]) - ): - plotter.add_mesh(minimum_above.contour(isosurfaces=20)) - else: - if filled: - plotter.add_mesh( - mesh, - clim=[ - auto_range_off.minimum(), - auto_range_off.maximum(), - ], - scalars=field, - show_edges=obj.show_edges(), - scalar_bar_args=scalar_bar_args, - ) - if (not filled or contour_lines) and ( - np.min(mesh[field]) != np.max(mesh[field]) - ): - plotter.add_mesh(mesh.contour(isosurfaces=20)) - else: - auto_range_on = obj.range.auto_range_on - if auto_range_on.global_range(): - if filled: - field_info = obj._api_helper.field_info() - plotter.add_mesh( - mesh, - clim=field_info.get_scalar_field_range(obj.field(), False), - scalars=field, - show_edges=obj.show_edges(), - scalar_bar_args=scalar_bar_args, - ) - if (not filled or contour_lines) and ( - np.min(mesh[field]) != np.max(mesh[field]) - ): - plotter.add_mesh(mesh.contour(isosurfaces=20)) - - else: - if filled: - plotter.add_mesh( - mesh, - scalars=field, - show_edges=obj.show_edges(), - scalar_bar_args=scalar_bar_args, - ) - if (not filled or contour_lines) and ( - np.min(mesh[field]) != np.max(mesh[field]) - ): - plotter.add_mesh(mesh.contour(isosurfaces=20)) - - def _fetch_surface(self, obj): - dummy_object = "dummy_object" - post_session = obj.get_root() - if ( - obj.definition.type() == "iso-surface" - and obj.definition.iso_surface.rendering() == "contour" - ): - contour = post_session.Contours[dummy_object] - contour.field = obj.definition.iso_surface.field() - contour.surfaces_list = [obj._name] - contour.show_edges = obj.show_edges() - contour.range.auto_range_on.global_range = True - contour.boundary_values = True - self._fetch_contour(contour) - del post_session.Contours[dummy_object] + def _clear_plotter(self, in_notebook): + if in_notebook and self.plotter.theme._jupyter_backend == "pythreejs": + self.plotter.remove_actor(plotter.renderer.actors.copy()) else: - mesh = post_session.Meshes[dummy_object] - mesh.surfaces_list = [obj._name] - mesh.show_edges = obj.show_edges() - self._fetch_mesh(mesh) - del post_session.Meshes[dummy_object] - - def _display_surface(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - dummy_object = "dummy_object" - post_session = obj.get_root() - if ( - obj.definition.type() == "iso-surface" - and obj.definition.iso_surface.rendering() == "contour" - ): - contour = post_session.Contours[dummy_object] - contour.field = obj.definition.iso_surface.field() - contour.surfaces_list = [obj._name] - contour.show_edges = obj.show_edges() - contour.range.auto_range_on.global_range = True - contour.boundary_values = True - self._display_contour(contour, plotter) - del post_session.Contours[dummy_object] - else: - mesh = post_session.Meshes[dummy_object] - mesh.surfaces_list = [obj._name] - mesh.show_edges = obj.show_edges() - self._display_mesh(mesh, plotter) - del post_session.Meshes[dummy_object] - - def _fetch_mesh(self, obj): - if self._data.get(FieldDataType.Meshes) is None or self.fetch_data: - self._data[FieldDataType.Meshes] = FieldDataExtractor(obj).fetch_data() - - def _display_mesh(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - for surface_id, mesh_data in self._data[FieldDataType.Meshes].items(): - if "vertices" not in mesh_data or "faces" not in mesh_data: - continue - mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 - topology = "line" if mesh_data["faces"][0] == 2 else "face" - if topology == "line": - mesh = pv.PolyData( - mesh_data["vertices"], - lines=mesh_data["faces"], - ) - else: - mesh = pv.PolyData( - mesh_data["vertices"], - faces=mesh_data["faces"], - ) - color_size = len(self._colors.values()) - color = list(self._colors.values())[surface_id % color_size] - plotter.add_mesh(mesh, show_edges=obj.show_edges(), color=color) - - def _get_refresh_for_plotter(self, window: "PyVistaWindow"): - def refresh(): - with PyVistaWindowsManager._condition: - plotter = window.plotter - if window.close: - window.animate = False - plotter.close() - return - if not window.update: - return - window.update = False - try: - window.plot() - finally: - PyVistaWindowsManager._condition.notify() + self.plotter.clear() - return refresh - - -class PyVistaWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): - """Provides for managing PyVista windows.""" - - _condition = threading.Condition() - - def __init__(self): - """Instantiate ``PyVistaWindow`` for PyVista.""" - self._post_windows: Dict[str:PyVistaWindow] = {} - self._plotter_thread: threading.Thread = None - self._post_object: GraphicsDefn = None - self._window_id: Optional[str] = None - self._exit_thread: bool = False - self._app = None - - def get_window(self, window_id: str) -> PyVistaWindow: - """Get the PyVista window. - - Parameters - ---------- - window_id : str - Window ID. - - Returns - ------- - PyVistaWindow - PyVista window. - """ - with self._condition: - return self._post_windows.get(window_id, None) - - def get_plotter(self, window_id: str) -> Union[BackgroundPlotter, pv.Plotter]: - """Get the PyVista plotter. - - Parameters - ---------- - window_id : str - Window ID for the plotter. - - Returns - ------- - Union[BackgroundPlotter, pv.Plotter] - PyVista plotter. - """ - with self._condition: - return self._post_windows[window_id].plotter - - def open_window(self, window_id: Optional[str] = None) -> str: - """Open a new window. - - Parameters - ---------- - window_id : str, optional - ID for the new window. The default is ``None``, in which - case a unique ID is automatically assigned. - - Returns - ------- - str - ID for the new window. - """ - with self._condition: - if not window_id: - window_id = self._get_unique_window_id() - if in_notebook() or get_config()["blocking"]: - self._open_window_notebook(window_id) - else: - self._open_and_plot_console(None, window_id) - return window_id - - def set_object_for_window(self, object: GraphicsDefn, window_id: str) -> None: - """Associate a visualization object with a running window instance. - - Parameters - ---------- - object : GraphicsDefn - Post object to associate with a running window instance. - window_id : str - Window ID for the association. - - Raises - ------ - RuntimeError - If the window does not support the object. - """ - if not isinstance(object, GraphicsDefn): - raise RuntimeError("Object type currently not supported.") - with self._condition: - window = self._post_windows.get(window_id) - if window: - window.post_object = object - - def plot( - self, - object: GraphicsDefn, - window_id: Optional[str] = None, - fetch_data: Optional[bool] = False, - overlay: Optional[bool] = False, - ) -> None: - """Draw a plot. - - Parameters - ---------- - object: GraphicsDefn - Object to plot. - window_id : str, optional - Window ID for the plot. The default is ``None``, in which - case a unique ID is assigned. - fetch_data : bool, optional - Whether to fetch data. The default is ``False``. - overlay : bool, optional - Whether to overlay graphics over existing graphics. - The default is ``False``. - Raises - ------ - RuntimeError - If the window does not support the object. - """ - if not isinstance(object, GraphicsDefn): - raise RuntimeError("Object type currently not supported.") - with self._condition: - if not window_id: - window_id = self._get_unique_window_id() - if in_notebook() or get_config()["blocking"]: - self._plot_notebook(object, window_id, fetch_data, overlay) - else: - self._open_and_plot_console(object, window_id, fetch_data, overlay) - - def save_graphic( - self, - window_id: str, - format: str, - ) -> None: - """Save a graphic. - - Parameters - ---------- - window_id : str - Window ID for the graphic to save. - format : str - Graphic file format. Supported formats are SVG, EPS, PS, PDF, and TEX. - - Raises - ------ - ValueError - If the window does not support the specified format. - """ - with self._condition: - window = self._post_windows.get(window_id) - if window: - window.plotter.save_graphic(f"{window_id}.{format}") - - def refresh_windows( - self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], - overlay: Optional[bool] = False, - ) -> None: - """Refresh windows. - - Parameters - ---------- - session_id : str, optional - Session ID for refreshing the windows that belong only to this - session. The default is ``""``, in which case the windows in all - sessions are refreshed. - windows_id : List[str], optional - IDs of the windows to refresh. The default is ``[]``, in which case - all windows are refreshed. - overlay : bool, Optional - Overlay graphics over existing graphics. - """ - with self._condition: - windows_id = self._get_windows_id(session_id, windows_id) - for window_id in windows_id: - window = self._post_windows.get(window_id) - if window: - window.refresh = True - self.plot(window.post_object, window.id, overlay=overlay) - - def animate_windows( - self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], - ) -> None: - """Animate windows. - - Parameters - ---------- - session_id : str, optional - Session ID for animating the windows that belong only to this - session. The default is ``""``, in which case the windows in all - sessions are animated. - windows_id : List[str], optional - List of IDs for the windows to animate. The default is ``[]``, in which - case all windows are animated. - - Raises - ------ - NotImplementedError - If not implemented. - """ - with self._condition: - windows_id = self._get_windows_id(session_id, windows_id) - for window_id in windows_id: - window = self._post_windows.get(window_id) - if window: - window.animate = True - window.plotter.open_gif(f"{window.id}.gif") - - def close_windows( - self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], - ) -> None: - """Close windows. - - Parameters - ---------- - session_id : str, optional - Session ID for closing the windows that belong only to this session. - The default is ``""``, in which case the windows in all sessions - are closed. - windows_id : List[str], optional - List of IDs for the windows to close. The default is ``[]``, in which - all windows are closed. - """ - with self._condition: - windows_id = self._get_windows_id(session_id, windows_id) - for window_id in windows_id: - window = self._post_windows.get(window_id) - if window: - if in_notebook() or get_config()["blocking"]: - window.plotter.close() - window.close = True - - # private methods - - def _display(self) -> None: - while True: - with self._condition: - if self._exit_thread: - break - if self._window_id: - window = self._post_windows.get(self._window_id) - plotter = window.plotter if window else None - animate = window.animate if window else False - if not plotter or plotter._closed: - window = PyVistaWindow(self._window_id, self._post_object) - plotter = window.plotter - self._app = plotter.app - plotter.add_callback( - window._get_refresh_for_plotter(window), - 100, - ) - window.post_object = self._post_object - window.fetch_data = self._fetch_data - window.overlay = self._overlay - window.animate = animate - window.update = True - self._post_windows[self._window_id] = window - self._post_object = None - self._window_id = None - self._app.processEvents() - with self._condition: - for window in self._post_windows.values(): - plotter = window.plotter - plotter.close() - plotter.app.quit() - self._post_windows.clear() - self._condition.notify() - - def _open_and_plot_console( - self, - obj: object, - window_id: str, - fetch_data: bool = False, - overlay: bool = False, - ) -> None: - if self._exit_thread: - return - with self._condition: - self._window_id = window_id - self._post_object = obj - self._fetch_data = fetch_data - self._overlay = overlay - - if not self._plotter_thread: - if FluentConnection._monitor_thread: - FluentConnection._monitor_thread.cbs.append(self._exit) - self._plotter_thread = threading.Thread(target=self._display, args=()) - self._plotter_thread.start() - - with self._condition: - self._condition.wait() - - def _open_window_notebook(self, window_id: str) -> pv.Plotter: - window = self._post_windows.get(window_id) - if window and not window.close and window.refresh: - window.refresh = False + def _set_camera(self, view: str): + camera = self.plotter.camera.copy() + view_fun = getattr(self.plotter, f"view_{view}", None) + if view_fun: + view_fun() else: - window = PyVistaWindow(window_id, None) - self._post_windows[window_id] = window - return window + self.plotter.camera = camera.copy() - def _plot_notebook( - self, obj: object, window_id: str, fetch_data: bool, overlay: bool - ) -> None: - window = self._open_window_notebook(window_id) - window.post_object = obj - window.fetch_data = fetch_data - window.overlay = overlay - window.plot() + def write_frame(self): + self.plotter.write_frame() - def _get_windows_id( - self, - session_id: Optional[str] = "", - windows_id: Optional[List[str]] = [], - ) -> List[str]: - with self._condition: - return [ - window_id - for window_id in [ - window_id - for window_id, window in self._post_windows.items() - if not window.plotter._closed - and ( - not session_id - or session_id == window.post_object._api_helper.id() - ) - ] - if not windows_id or window_id in windows_id - ] + def show(self): + self.plotter.show() - def _exit(self) -> None: - if self._plotter_thread: - with self._condition: - self._exit_thread = True - self._condition.wait() - self._plotter_thread.join() - self._plotter_thread = None + def render(self, mesh, **kwargs): + self.plotter.add_mesh(mesh, **kwargs) - def _get_unique_window_id(self) -> str: - itr_count = itertools.count() - with self._condition: - while True: - window_id = f"window-{next(itr_count)}" - if window_id not in self._post_windows: - return window_id + def close(self): + self.plotter.close() From 413a2fdb7745e89be8f0565a1e180ded6cbf063e Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 20:04:10 +0530 Subject: [PATCH 11/22] Refactor. --- doc/source/api/visualization/contour.rst | 2 +- doc/source/api/visualization/graphics.rst | 2 +- ...indows_manager.rst => graphics_windows_manager.rst} | 10 +++++----- doc/source/api/visualization/mesh.rst | 2 +- doc/source/api/visualization/pathlines.rst | 2 +- doc/source/api/visualization/post_objects.rst | 2 +- doc/source/api/visualization/surface.rst | 2 +- doc/source/api/visualization/vector.rst | 2 +- src/ansys/fluent/visualization/__init__.py | 2 +- src/ansys/fluent/visualization/graphic/__init__.py | 6 ------ src/ansys/fluent/visualization/graphics/__init__.py | 6 ++++++ .../graphics_objects.py} | 2 +- .../graphics_windows_manager.py} | 10 ++++------ .../{graphic => graphics}/pyvista/__init__.py | 0 .../pyvista/graphics_defns.py} | 0 .../visualization/plotter/pyvista/plotter_defns.py | 2 +- 16 files changed, 25 insertions(+), 27 deletions(-) rename doc/source/api/visualization/{graphic_windows_manager.rst => graphics_windows_manager.rst} (80%) delete mode 100644 src/ansys/fluent/visualization/graphic/__init__.py create mode 100644 src/ansys/fluent/visualization/graphics/__init__.py rename src/ansys/fluent/visualization/{graphic/graphic_objects.py => graphics/graphics_objects.py} (98%) rename src/ansys/fluent/visualization/{graphic/graphic_windows_manager.py => graphics/graphics_windows_manager.py} (99%) rename src/ansys/fluent/visualization/{graphic => graphics}/pyvista/__init__.py (100%) rename src/ansys/fluent/visualization/{graphic/pyvista/graphic_defns.py => graphics/pyvista/graphics_defns.py} (100%) diff --git a/doc/source/api/visualization/contour.rst b/doc/source/api/visualization/contour.rst index 11d0b5aa..b283bcf6 100644 --- a/doc/source/api/visualization/contour.rst +++ b/doc/source/api/visualization/contour.rst @@ -3,4 +3,4 @@ Contour ======= -.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Contour +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Contour diff --git a/doc/source/api/visualization/graphics.rst b/doc/source/api/visualization/graphics.rst index 461943c9..b7e60ef3 100644 --- a/doc/source/api/visualization/graphics.rst +++ b/doc/source/api/visualization/graphics.rst @@ -3,7 +3,7 @@ Graphics ======== -.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Graphics +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Graphics In the following example, a ``Graphics`` object is instantiated with a Fluent session as its context. The ``Graphics`` object is used to create a mesh, diff --git a/doc/source/api/visualization/graphic_windows_manager.rst b/doc/source/api/visualization/graphics_windows_manager.rst similarity index 80% rename from doc/source/api/visualization/graphic_windows_manager.rst rename to doc/source/api/visualization/graphics_windows_manager.rst index c48aad60..53a0ea12 100644 --- a/doc/source/api/visualization/graphic_windows_manager.rst +++ b/doc/source/api/visualization/graphics_windows_manager.rst @@ -1,4 +1,4 @@ -.. _ref_graphic_windows_manager: +.. _ref_graphics_windows_manager: Graphic windows manager ======================= @@ -16,7 +16,7 @@ the end of every time step and creates an animation. .. code-block:: python from ansys.fluent.visualization import Graphics - from ansys.fluent.visualization.graphic import graphic_windows_manager + from ansys.fluent.visualization.graphics import graphics_windows_manager graphics_session = Graphics(session) @@ -30,7 +30,7 @@ the end of every time step and creates an animation. #Create callback that refreshes window-1. def auto_refresh_contour(session_id, event_info): - graphic_windows_manager.refresh_windows(session_id, ["window-1"]) + graphics_windows_manager.refresh_windows(session_id, ["window-1"]) #Register this callback with server events. cb_init_id = session.events_manager.register_callback('InitializedEvent', auto_refresh_contour) @@ -38,8 +38,8 @@ the end of every time step and creates an animation. cb_time_step_ended_id = session.events_manager.register_callback('TimestepEndedEvent', auto_refresh_contour) #Create animation for window-1. - graphic_windows_manager.animate_windows(session.id, ["window-1"]) + graphics_windows_manager.animate_windows(session.id, ["window-1"]) -.. autoclass:: ansys.fluent.visualization.graphic.graphic_windows_manager.GraphicWindowsManager +.. autoclass:: ansys.fluent.visualization.graphics.graphics_windows_manager.GraphicWindowsManager :members: \ No newline at end of file diff --git a/doc/source/api/visualization/mesh.rst b/doc/source/api/visualization/mesh.rst index 673ba2cd..5e17fc0c 100644 --- a/doc/source/api/visualization/mesh.rst +++ b/doc/source/api/visualization/mesh.rst @@ -3,6 +3,6 @@ Mesh ==== -.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Mesh +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Mesh diff --git a/doc/source/api/visualization/pathlines.rst b/doc/source/api/visualization/pathlines.rst index 88ad716c..de8c72f6 100644 --- a/doc/source/api/visualization/pathlines.rst +++ b/doc/source/api/visualization/pathlines.rst @@ -3,4 +3,4 @@ Pathlines ========= -.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Pathlines +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Pathlines diff --git a/doc/source/api/visualization/post_objects.rst b/doc/source/api/visualization/post_objects.rst index 0a78c2a8..01311cb4 100644 --- a/doc/source/api/visualization/post_objects.rst +++ b/doc/source/api/visualization/post_objects.rst @@ -6,7 +6,7 @@ graphics plots - graphic_windows_manager + graphics_windows_manager plotter_windows_manager diff --git a/doc/source/api/visualization/surface.rst b/doc/source/api/visualization/surface.rst index 88a1fcd9..2f3df43a 100644 --- a/doc/source/api/visualization/surface.rst +++ b/doc/source/api/visualization/surface.rst @@ -4,6 +4,6 @@ Surface ======= -.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Surface +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Surface diff --git a/doc/source/api/visualization/vector.rst b/doc/source/api/visualization/vector.rst index 94f48193..2ee7b61a 100644 --- a/doc/source/api/visualization/vector.rst +++ b/doc/source/api/visualization/vector.rst @@ -3,4 +3,4 @@ Vector ====== -.. autopostdoc:: ansys.fluent.visualization.graphic.graphic_objects.Vector \ No newline at end of file +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Vector \ No newline at end of file diff --git a/src/ansys/fluent/visualization/__init__.py b/src/ansys/fluent/visualization/__init__.py index 87d4c331..5454ce69 100644 --- a/src/ansys/fluent/visualization/__init__.py +++ b/src/ansys/fluent/visualization/__init__.py @@ -24,5 +24,5 @@ def version_info() -> str: from ansys.fluent.visualization._config import get_config, set_config # noqa: F401 -from ansys.fluent.visualization.graphic import Graphics # noqa: F401 +from ansys.fluent.visualization.graphics import Graphics # noqa: F401 from ansys.fluent.visualization.plotter import Plots # noqa: F401 diff --git a/src/ansys/fluent/visualization/graphic/__init__.py b/src/ansys/fluent/visualization/graphic/__init__.py deleted file mode 100644 index cfe59cff..00000000 --- a/src/ansys/fluent/visualization/graphic/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""A package that provides interfacing Fluent with graphic renderer (PyVista).""" - -from ansys.fluent.visualization.graphic.graphic_objects import Graphics # noqa: F401 -from ansys.fluent.visualization.graphic.graphic_windows_manager import ( # noqa: F401 - graphics_windows_manager, -) diff --git a/src/ansys/fluent/visualization/graphics/__init__.py b/src/ansys/fluent/visualization/graphics/__init__.py new file mode 100644 index 00000000..40f6dd39 --- /dev/null +++ b/src/ansys/fluent/visualization/graphics/__init__.py @@ -0,0 +1,6 @@ +"""A package that provides interfacing Fluent with graphics renderer (PyVista).""" + +from ansys.fluent.visualization.graphics.graphics_objects import Graphics # noqa: F401 +from ansys.fluent.visualization.graphics.graphics_windows_manager import ( # noqa: F401 + graphics_windows_manager, +) diff --git a/src/ansys/fluent/visualization/graphic/graphic_objects.py b/src/ansys/fluent/visualization/graphics/graphics_objects.py similarity index 98% rename from src/ansys/fluent/visualization/graphic/graphic_objects.py rename to src/ansys/fluent/visualization/graphics/graphics_objects.py index 9caa0408..de8ef06b 100644 --- a/src/ansys/fluent/visualization/graphic/graphic_objects.py +++ b/src/ansys/fluent/visualization/graphics/graphics_objects.py @@ -16,7 +16,7 @@ Graphics as GraphicsContainer, ) -from ansys.fluent.visualization.graphic.graphic_windows_manager import ( +from ansys.fluent.visualization.graphics.graphics_windows_manager import ( graphics_windows_manager, ) diff --git a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py similarity index 99% rename from src/ansys/fluent/visualization/graphic/graphic_windows_manager.py rename to src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index ba2b840b..5e3c11f4 100644 --- a/src/ansys/fluent/visualization/graphic/graphic_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -1,6 +1,4 @@ -"""Module for graphic windows management.""" - -"""Module for pyVista windows management.""" +"""Module for graphics windows management.""" from enum import Enum import itertools @@ -32,7 +30,7 @@ class FieldDataType(Enum): Pathlines = 4 -from ansys.fluent.visualization.graphic.pyvista.graphic_defns import Renderer +from ansys.fluent.visualization.graphics.pyvista.graphics_defns import Renderer class GraphicsWindow(PostWindow): @@ -548,12 +546,12 @@ def save_graphic( window_id: str, format: str, ) -> None: - """Save a graphic. + """Save a graphics. Parameters ---------- window_id : str - Window ID for the graphic to save. + Window ID for the graphics to save. format : str Graphic file format. Supported formats are SVG, EPS, PS, PDF, and TEX. diff --git a/src/ansys/fluent/visualization/graphic/pyvista/__init__.py b/src/ansys/fluent/visualization/graphics/pyvista/__init__.py similarity index 100% rename from src/ansys/fluent/visualization/graphic/pyvista/__init__.py rename to src/ansys/fluent/visualization/graphics/pyvista/__init__.py diff --git a/src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py b/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py similarity index 100% rename from src/ansys/fluent/visualization/graphic/pyvista/graphic_defns.py rename to src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py diff --git a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py index c7c5430d..58a75704 100644 --- a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py +++ b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py @@ -125,7 +125,7 @@ def save_graphic(self, file_name: str): Parameters ---------- file_name : str - File name to save graphic. + File name to save graphics. """ self.plotter.save_graphic(file_name) From fcd8c96aa9560da8464fd36cded77ccef8fe2f8c Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 21:33:35 +0530 Subject: [PATCH 12/22] Fix docs. --- doc/source/api/visualization/graphics_windows_manager.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/api/visualization/graphics_windows_manager.rst b/doc/source/api/visualization/graphics_windows_manager.rst index 53a0ea12..c265e62f 100644 --- a/doc/source/api/visualization/graphics_windows_manager.rst +++ b/doc/source/api/visualization/graphics_windows_manager.rst @@ -1,8 +1,8 @@ .. _ref_graphics_windows_manager: -Graphic windows manager -======================= -The ``GraphicWindowsManager`` class provides for managing and directly interacting +Graphics windows manager +======================== +The ``GraphicsWindowsManager`` class provides for managing and directly interacting with PyVista windows. By registering these methods with ``EventsManager``, you can update graphics during run time and create animations. @@ -41,5 +41,5 @@ the end of every time step and creates an animation. graphics_windows_manager.animate_windows(session.id, ["window-1"]) -.. autoclass:: ansys.fluent.visualization.graphics.graphics_windows_manager.GraphicWindowsManager +.. autoclass:: ansys.fluent.visualization.graphics.graphics_windows_manager.GraphicsWindowsManager :members: \ No newline at end of file From 1a6ecfa711a088a11302c7a37d3147807fc2f04c Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Mon, 23 Sep 2024 22:40:19 +0530 Subject: [PATCH 13/22] Refactor. --- .../graphics/graphics_windows_manager.py | 81 +++++++------------ .../plotter/abstract_plotter_defns.py | 2 +- 2 files changed, 32 insertions(+), 51 deletions(-) diff --git a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index 5e3c11f4..ec1adb99 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -24,10 +24,10 @@ class FieldDataType(Enum): """Provides surface data types.""" - Meshes = 1 - Vectors = 2 - Contours = 3 - Pathlines = 4 + Meshes = "Mesh" + Vectors = "Vector" + Contours = "Contour" + Pathlines = "Pathlines" from ansys.fluent.visualization.graphics.pyvista.graphics_defns import Renderer @@ -68,16 +68,10 @@ def fetch(self): if not self.post_object: return obj = self.post_object - if obj.__class__.__name__ == "Mesh": - self._fetch_mesh(obj) - elif obj.__class__.__name__ == "Surface": + if obj.__class__.__name__ == "Surface": self._fetch_surface(obj) - elif obj.__class__.__name__ == "Contour": - self._fetch_contour(obj) - elif obj.__class__.__name__ == "Vector": - self._fetch_vector(obj) - elif obj.__class__.__name__ == "Pathlines": - self._fetch_pathlines(obj) + else: + self._fetch_data(obj, FieldDataType(obj.__class__.__name__)) def render(self): """Render graphics.""" @@ -110,10 +104,31 @@ def plot(self): self.render() # private methods + def _fetch_data(self, obj, data_type: FieldDataType): + if self._data.get(data_type) is None or self.fetch_data: + self._data[data_type] = FieldDataExtractor(obj).fetch_data() - def _fetch_vector(self, obj): - if self._data.get(FieldDataType.Vectors) is None or self.fetch_data: - self._data[FieldDataType.Vectors] = FieldDataExtractor(obj).fetch_data() + def _fetch_surface(self, obj): + dummy_object = "dummy_object" + post_session = obj.get_root() + if ( + obj.definition.type() == "iso-surface" + and obj.definition.iso_surface.rendering() == "contour" + ): + contour = post_session.Contours[dummy_object] + contour.field = obj.definition.iso_surface.field() + contour.surfaces_list = [obj._name] + contour.show_edges = obj.show_edges() + contour.range.auto_range_on.global_range = True + contour.boundary_values = True + self._fetch_data(contour, FieldDataType.Contours) + del post_session.Contours[dummy_object] + else: + mesh = post_session.Meshes[dummy_object] + mesh.surfaces_list = [obj._name] + mesh.show_edges = obj.show_edges() + self._fetch_data(mesh, FieldDataType.Meshes) + del post_session.Meshes[dummy_object] def _display_vector(self, obj): field_info = obj._api_helper.field_info() @@ -185,10 +200,6 @@ def _display_vector(self, obj): if obj.show_edges(): self.renderer.render(mesh, show_edges=True, color="white") - def _fetch_pathlines(self, obj): - if self._data.get(FieldDataType.Pathlines) is None or self.fetch_data: - self._data[FieldDataType.Pathlines] = FieldDataExtractor(obj).fetch_data() - def _display_pathlines(self, obj): field = obj.field() field_unit = obj._api_helper.get_field_unit(field) @@ -215,10 +226,6 @@ def _display_pathlines(self, obj): scalar_bar_args=scalar_bar_args, ) - def _fetch_contour(self, obj): - if self._data.get(FieldDataType.Contours) is None or self.fetch_data: - self._data[FieldDataType.Contours] = FieldDataExtractor(obj).fetch_data() - def _display_contour(self, obj): # contour properties field = obj.field() @@ -327,28 +334,6 @@ def _display_contour(self, obj): ): self.renderer.render(mesh.contour(isosurfaces=20)) - def _fetch_surface(self, obj): - dummy_object = "dummy_object" - post_session = obj.get_root() - if ( - obj.definition.type() == "iso-surface" - and obj.definition.iso_surface.rendering() == "contour" - ): - contour = post_session.Contours[dummy_object] - contour.field = obj.definition.iso_surface.field() - contour.surfaces_list = [obj._name] - contour.show_edges = obj.show_edges() - contour.range.auto_range_on.global_range = True - contour.boundary_values = True - self._fetch_contour(contour) - del post_session.Contours[dummy_object] - else: - mesh = post_session.Meshes[dummy_object] - mesh.surfaces_list = [obj._name] - mesh.show_edges = obj.show_edges() - self._fetch_mesh(mesh) - del post_session.Meshes[dummy_object] - def _display_surface(self, obj): dummy_object = "dummy_object" post_session = obj.get_root() @@ -371,10 +356,6 @@ def _display_surface(self, obj): self._display_mesh(mesh) del post_session.Meshes[dummy_object] - def _fetch_mesh(self, obj): - if self._data.get(FieldDataType.Meshes) is None or self.fetch_data: - self._data[FieldDataType.Meshes] = FieldDataExtractor(obj).fetch_data() - def _display_mesh(self, obj): for surface_id, mesh_data in self._data[FieldDataType.Meshes].items(): if "vertices" not in mesh_data or "faces" not in mesh_data: diff --git a/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py b/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py index 22c92265..35ec28b8 100644 --- a/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py +++ b/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py @@ -1,4 +1,4 @@ -"""Module providing matplotlib plotter functionality.""" +"""Abstract module providing plotter functionality.""" from abc import ABC, abstractmethod From 5bca610d3aca7d3d1f577569c9e12413cab4d1d9 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 24 Sep 2024 11:58:31 +0530 Subject: [PATCH 14/22] Refactor. --- .../graphics/abstract_graphics_defns.py | 44 +++++++++++++++++++ .../graphics/graphics_windows_manager.py | 4 +- .../graphics/pyvista/graphics_defns.py | 33 +++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/ansys/fluent/visualization/graphics/abstract_graphics_defns.py diff --git a/src/ansys/fluent/visualization/graphics/abstract_graphics_defns.py b/src/ansys/fluent/visualization/graphics/abstract_graphics_defns.py new file mode 100644 index 00000000..6c0790f6 --- /dev/null +++ b/src/ansys/fluent/visualization/graphics/abstract_graphics_defns.py @@ -0,0 +1,44 @@ +"""Abstract module providing graphics functionality.""" + +from abc import ABC, abstractmethod + + +class AbstractRenderer(ABC): + """Abstract class for renderer.""" + + @abstractmethod + def show(self): + """Show graphics.""" + pass + + @abstractmethod + def render(self, mesh, **kwargs): + """Render graphics in window.""" + pass + + @abstractmethod + def save_graphic(self, file_name: str): + """Save graphics to the specified file. + + Parameters + ---------- + file_name : str + File name to save graphic. + """ + pass + + @abstractmethod + def get_animation(self, win_id: str): + """Animate windows. + + Parameters + ---------- + win_id : str + ID for the window to animate. + """ + pass + + @abstractmethod + def close(self): + """Close graphics window.""" + pass diff --git a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index ec1adb99..a2035fe1 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -544,7 +544,7 @@ def save_graphic( with self._condition: window = self._post_windows.get(window_id) if window: - window.renderer.plotter.save_graphic(f"{window_id}.{format}") + window.renderer.save_graphic(f"{window_id}.{format}") def refresh_windows( self, @@ -602,7 +602,7 @@ def animate_windows( window = self._post_windows.get(window_id) if window: window.animate = True - window.renderer.plotter.open_gif(f"{window.id}.gif") + window.renderer.get_animation(window.id) def close_windows( self, diff --git a/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py b/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py index d1f1f791..0b930f80 100644 --- a/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py +++ b/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py @@ -3,8 +3,10 @@ import pyvista as pv from pyvistaqt import BackgroundPlotter +from ansys.fluent.visualization.graphics.abstract_graphics_defns import AbstractRenderer -class Renderer: + +class Renderer(AbstractRenderer): def __init__(self, win_id: str, in_notebook: bool, non_interactive: bool): self.plotter: Union[BackgroundPlotter, pv.Plotter] = ( pv.Plotter(title=f"PyFluent ({win_id})") @@ -67,10 +69,39 @@ def write_frame(self): self.plotter.write_frame() def show(self): + """Show graphics.""" self.plotter.show() def render(self, mesh, **kwargs): + """Render graphics in window. + + Parameters + ---------- + mesh : pyvista.DataSet + Any PyVista or VTK mesh is supported. + """ self.plotter.add_mesh(mesh, **kwargs) + def save_graphic(self, file_name: str): + """Save graphics to the specified file. + + Parameters + ---------- + file_name : str + File name to save graphic. + """ + self.plotter.save_graphic(file_name) + + def get_animation(self, win_id: str): + """Animate windows. + + Parameters + ---------- + win_id : str + ID for the window to animate. + """ + self.plotter.open_gif(f"{win_id}.gif") + def close(self): + """Close graphics window.""" self.plotter.close() From e5964cd48909c2c3e8a8a64a437bc09e9364e353 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 24 Sep 2024 12:16:48 +0530 Subject: [PATCH 15/22] Refactor. --- .../graphics/graphics_windows_manager.py | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index a2035fe1..7c9c24f4 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -130,6 +130,19 @@ def _fetch_surface(self, obj): self._fetch_data(mesh, FieldDataType.Meshes) del post_session.Meshes[dummy_object] + def _resolve_mesh_data(self, mesh_data): + topology = "line" if mesh_data["faces"][0] == 2 else "face" + if topology == "line": + return pv.PolyData( + mesh_data["vertices"], + lines=mesh_data["faces"], + ) + else: + return pv.PolyData( + mesh_data["vertices"], + faces=mesh_data["faces"], + ) + def _display_vector(self, obj): field_info = obj._api_helper.field_info() vectors_of = obj.vectors_of() @@ -149,17 +162,7 @@ def _display_vector(self, obj): 3, ) vector_scale = mesh_data["vector-scale"][0] - topology = "line" if mesh_data["faces"][0] == 2 else "face" - if topology == "line": - mesh = pv.PolyData( - mesh_data["vertices"], - lines=mesh_data["faces"], - ) - else: - mesh = pv.PolyData( - mesh_data["vertices"], - faces=mesh_data["faces"], - ) + mesh = self._resolve_mesh_data(mesh_data) mesh.cell_data["vectors"] = mesh_data[vectors_of] scalar_field = mesh_data[obj.field()] velocity_magnitude = np.linalg.norm(mesh_data[vectors_of], axis=1) @@ -244,17 +247,7 @@ def _display_contour(self, obj): if "vertices" not in surface_data or "faces" not in surface_data: continue surface_data["vertices"].shape = surface_data["vertices"].size // 3, 3 - topology = "line" if surface_data["faces"][0] == 2 else "face" - if topology == "line": - mesh = pv.PolyData( - surface_data["vertices"], - lines=surface_data["faces"], - ) - else: - mesh = pv.PolyData( - surface_data["vertices"], - faces=surface_data["faces"], - ) + mesh = self._resolve_mesh_data(surface_data) if node_values: mesh.point_data[field] = surface_data[obj.field()] else: @@ -361,17 +354,7 @@ def _display_mesh(self, obj): if "vertices" not in mesh_data or "faces" not in mesh_data: continue mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 - topology = "line" if mesh_data["faces"][0] == 2 else "face" - if topology == "line": - mesh = pv.PolyData( - mesh_data["vertices"], - lines=mesh_data["faces"], - ) - else: - mesh = pv.PolyData( - mesh_data["vertices"], - faces=mesh_data["faces"], - ) + mesh = self._resolve_mesh_data(mesh_data) color_size = len(self.renderer._colors) color = list(self.renderer._colors.values())[surface_id % color_size] self.renderer.render(mesh, show_edges=obj.show_edges(), color=color) From 3a6e960651989b7fa5df0e2ad98f8dfe4fca0274 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 24 Sep 2024 13:55:13 +0530 Subject: [PATCH 16/22] Refactor. --- .../graphics/graphics_windows_manager.py | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index 7c9c24f4..6f6c578f 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -108,7 +108,7 @@ def _fetch_data(self, obj, data_type: FieldDataType): if self._data.get(data_type) is None or self.fetch_data: self._data[data_type] = FieldDataExtractor(obj).fetch_data() - def _fetch_surface(self, obj): + def _fetch_or_display_surface(self, obj, fetch: bool): dummy_object = "dummy_object" post_session = obj.get_root() if ( @@ -121,15 +121,24 @@ def _fetch_surface(self, obj): contour.show_edges = obj.show_edges() contour.range.auto_range_on.global_range = True contour.boundary_values = True - self._fetch_data(contour, FieldDataType.Contours) + if fetch: + self._fetch_data(contour, FieldDataType.Contours) + else: + self._display_contour(contour) del post_session.Contours[dummy_object] else: mesh = post_session.Meshes[dummy_object] mesh.surfaces_list = [obj._name] mesh.show_edges = obj.show_edges() - self._fetch_data(mesh, FieldDataType.Meshes) + if fetch: + self._fetch_data(mesh, FieldDataType.Meshes) + else: + self._display_mesh(mesh) del post_session.Meshes[dummy_object] + def _fetch_surface(self, obj): + self._fetch_or_display_surface(obj, fetch=True) + def _resolve_mesh_data(self, mesh_data): topology = "line" if mesh_data["faces"][0] == 2 else "face" if topology == "line": @@ -328,26 +337,7 @@ def _display_contour(self, obj): self.renderer.render(mesh.contour(isosurfaces=20)) def _display_surface(self, obj): - dummy_object = "dummy_object" - post_session = obj.get_root() - if ( - obj.definition.type() == "iso-surface" - and obj.definition.iso_surface.rendering() == "contour" - ): - contour = post_session.Contours[dummy_object] - contour.field = obj.definition.iso_surface.field() - contour.surfaces_list = [obj._name] - contour.show_edges = obj.show_edges() - contour.range.auto_range_on.global_range = True - contour.boundary_values = True - self._display_contour(contour) - del post_session.Contours[dummy_object] - else: - mesh = post_session.Meshes[dummy_object] - mesh.surfaces_list = [obj._name] - mesh.show_edges = obj.show_edges() - self._display_mesh(mesh) - del post_session.Meshes[dummy_object] + self._fetch_or_display_surface(obj, fetch=False) def _display_mesh(self, obj): for surface_id, mesh_data in self._data[FieldDataType.Meshes].items(): From ffede3604a164db09f89c032be4073920e0fb322 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 24 Sep 2024 16:32:30 +0530 Subject: [PATCH 17/22] Bug fix for notebook. --- .../fluent/visualization/plotter/matplotlib/plotter_defns.py | 3 +-- .../fluent/visualization/plotter/pyvista/plotter_defns.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py b/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py index d39a0f2d..f1b678b2 100644 --- a/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py +++ b/src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py @@ -149,8 +149,7 @@ def set_properties(self, properties: dict): def __call__(self): """Reset and show plot.""" self._reset() - self._visible = True - plt.show() + self._visible = False # private methods def _reset(self): diff --git a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py index 58a75704..3ea469dc 100644 --- a/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py +++ b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py @@ -152,8 +152,7 @@ def set_properties(self, properties: dict): def __call__(self): """Reset and show plot.""" self._reset() - self._visible = True - self.plotter.show() + self._visible = False # private methods def _reset(self): From d520b0d5082aa2b514a998b12dc911b3947ddc0f Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 8 Oct 2024 12:28:41 +0530 Subject: [PATCH 18/22] Set a default value for config. --- src/ansys/fluent/visualization/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/fluent/visualization/_config.py b/src/ansys/fluent/visualization/_config.py index 858c48d4..370f9e6c 100644 --- a/src/ansys/fluent/visualization/_config.py +++ b/src/ansys/fluent/visualization/_config.py @@ -14,7 +14,7 @@ def get_config() -> dict: return _global_config.copy() -def set_config(blocking: bool = True, set_view_on_display: str = None): +def set_config(blocking: bool = True, set_view_on_display: str = "isometric"): """Set visualization configuration. Parameters From ffbd5ada1748919b1fccef84f307bdbc106c343f Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 8 Oct 2024 14:56:20 +0530 Subject: [PATCH 19/22] Bug fixing in multi-plotter. --- .../fluent/visualization/graphics/graphics_windows_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index 6f6c578f..eaa0e8b4 100644 --- a/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -692,7 +692,7 @@ def _get_windows_id( for window_id in [ window_id for window_id, window in self._post_windows.items() - if not window.plotter._closed + if not window.renderer.plotter._closed and ( not session_id or session_id == window.post_object._api_helper.id() From f2708a2a1906ee7de0da5402cd1b47ca48f07e82 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 8 Oct 2024 14:57:06 +0530 Subject: [PATCH 20/22] Added exapmple script for additional testing. --- examples/00-postprocessing/script_manifold.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 examples/00-postprocessing/script_manifold.py diff --git a/examples/00-postprocessing/script_manifold.py b/examples/00-postprocessing/script_manifold.py new file mode 100644 index 00000000..2f2d6748 --- /dev/null +++ b/examples/00-postprocessing/script_manifold.py @@ -0,0 +1,113 @@ +# exec(open("script_manifold.py").read()) + +import ansys.fluent.core as pyfluent + +session = pyfluent.connect_to_fluent(ip="10.18.44.105", port=62599, password="hzk1dhbc") + +from ansys.fluent.visualization import set_config + +set_config(blocking=False) + + +from ansys.fluent.visualization import Graphics, Plots +from ansys.fluent.visualization.graphics import graphics_windows_manager +from ansys.fluent.visualization.plotter import plotter_windows_manager + +# get the graphics objects for the session +graphics_session1 = Graphics(session) +graphics_session1_1 = Graphics(session) + +# mesh +mesh1 = graphics_session1.Meshes["mesh-1"] +mesh1.show_edges = True +mesh1.surfaces_list = ["solid_up:1:830"] + +# pathlines +pathlines1 = graphics_session1.Pathlines["pathlines-1"] +pathlines1.field = "velocity-magnitude" +pathlines1.surfaces_list = ["inlet"] + +# contour +contour1 = graphics_session1.Contours["contour-1"] +contour1.field = "velocity-magnitude" +contour1.surfaces_list = ["solid_up:1:830"] + + +graphics_session1.Contours["contour-2"] +graphics_session1_1.Contours["contour-3"] + +# vector +vector1 = graphics_session1_1.Vectors["vector-1"] +vector1.surfaces_list = ["solid_up:1:830"] +vector1.scale = 4.0 +vector1.skip = 0 +vector1.field = "temperature" +vector1.display() + +# iso surface +surface1 = graphics_session1_1.Surfaces["surface-1"] +surface1.definition.type = "iso-surface" +surface1.definition.iso_surface.field = "velocity-magnitude" +surface1.definition.iso_surface.rendering = "contour" +surface1.definition.iso_surface.iso_value = 0.0 + + +local_surfaces_provider = Graphics(session).Surfaces +matplotlib_plots1 = Plots(session, local_surfaces_provider=local_surfaces_provider) + + +p1 = matplotlib_plots1.XYPlots["p1"] +p1.surfaces_list = ["solid_up:1:830", "surface-1"] +p1.surfaces_list = ["solid_up:1:830"] +p1.y_axis_function = "temperature" +p1.plot("p1") + +session.monitors.get_monitor_set_names() +residual = matplotlib_plots1.Monitors["residual"] +residual.monitor_set_name = "residual" +residual.plot("residual") + +mtr = matplotlib_plots1.Monitors["mass-tot-rplot"] +mtr.monitor_set_name = "mass-tot-rplot" +mtr.plot("mass-tot-rplot") + +mbr = matplotlib_plots1.Monitors["mass-bal-rplot"] +mbr.monitor_set_name = "mass-bal-rplot" +mbr.plot("mass-bal-rplot") + +mesh1.display("mesh-1") +vector1.display("vector-1") +contour1.display("contour-1") +pathlines1.display("pthlines-1") +plotter = graphics_windows_manager.get_plotter("contour-1") +plotter.view_isometric() +surface1.display("surface-1") + + +def auto_refersh_call_back_iteration(session_id, event_info): + if event_info.index % 1 == 0: + graphics_windows_manager.refresh_windows(session_id, ["contour-1"]) + plotter_windows_manager.refresh_windows( + session_id, ["residual", "mass-tot-rplot", "mass-bal-rplot"] + ) + + +def auto_refersh_call_back_time_step(session_id, event_info): + graphics_windows_manager.refresh_windows(session_id) + plotter_windows_manager.refresh_windows("", ["residual"]) + + +def initialize_call_back(session_id, event_info): + graphics_windows_manager.refresh_windows(session_id) + plotter_windows_manager.refresh_windows("", ["residual", "mass-tot-rplot"]) + + +cb_init_id = session.events.register_callback("InitializedEvent", initialize_call_back) +cb_data_read_id = session.events.register_callback( + "DataReadEvent", initialize_call_back +) +cb_itr_id = session.events.register_callback( + "IterationEndedEvent", auto_refersh_call_back_iteration +) + +graphics_windows_manager.animate_windows(session.id, ["contour-1"]) From 96047f2de26533326f0e94a30cb86c66aa956375 Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Tue, 8 Oct 2024 15:10:31 +0530 Subject: [PATCH 21/22] Update examples. --- doc/source/conf.py | 3 ++- examples/00-postprocessing/script_manifold.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index fd4c73be..802bbd17 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -162,6 +162,8 @@ def _stop_fluent_container(gallery_conf, fname): "gallery_dirs": ["examples"], # Pattern to search for example files "filename_pattern": r"\.py", + # Disabled example scripts + "ignore_pattern": r"script_manifold\.py|flycheck*", # Remove the "Download all examples" button from the top level gallery "download_all_examples": False, # Sort gallery example by file name instead of number of lines (default) @@ -171,7 +173,6 @@ def _stop_fluent_container(gallery_conf, fname): # Modules for which function level galleries are created. In "doc_module": "ansys-fluent-core", "image_scrapers": ("pyvista", "matplotlib"), - "ignore_pattern": "flycheck*", "thumbnail_size": (350, 350), "reset_modules_order": "after", "reset_modules": (_stop_fluent_container), diff --git a/examples/00-postprocessing/script_manifold.py b/examples/00-postprocessing/script_manifold.py index 2f2d6748..e77ef6c1 100644 --- a/examples/00-postprocessing/script_manifold.py +++ b/examples/00-postprocessing/script_manifold.py @@ -1,3 +1,14 @@ +""".. _ref_script_manifold: + +Triggering callbacks and Animation +---------------------------------- +This example uses PyVista and Matplotlib to demonstrate the use +of callback mechanisms. The 3D model in this example +is an exhaust manifold. +""" + +############################################################################### +# Run the following in command prompt to execute this file: # exec(open("script_manifold.py").read()) import ansys.fluent.core as pyfluent From ea62f917c9ffc603de873d261bd3bec979d97b4a Mon Sep 17 00:00:00 2001 From: Prithwish Mukherjee Date: Wed, 16 Oct 2024 12:32:59 +0530 Subject: [PATCH 22/22] Add deprecated modules. --- .../fluent/visualization/matplotlib/__init__.py | 17 +++++++++++++++++ .../fluent/visualization/pyvista/__init__.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/ansys/fluent/visualization/matplotlib/__init__.py create mode 100644 src/ansys/fluent/visualization/pyvista/__init__.py diff --git a/src/ansys/fluent/visualization/matplotlib/__init__.py b/src/ansys/fluent/visualization/matplotlib/__init__.py new file mode 100644 index 00000000..ec0c82f0 --- /dev/null +++ b/src/ansys/fluent/visualization/matplotlib/__init__.py @@ -0,0 +1,17 @@ +"""A DEPRECATED package that provides interfacing Fluent with plotters.""" + +import warnings + +from ansys.fluent.core.warnings import PyFluentDeprecationWarning + +warnings.warn( + "'matplotlib' is deprecated. Use 'plotter' instead.", + PyFluentDeprecationWarning, +) + +from ansys.fluent.visualization.plotter.plotter_objects import Plots # noqa: F401 +from ansys.fluent.visualization.plotter.plotter_windows_manager import ( # noqa: F401 + plotter_windows_manager, +) + +matplotlib_windows_manager = plotter_windows_manager diff --git a/src/ansys/fluent/visualization/pyvista/__init__.py b/src/ansys/fluent/visualization/pyvista/__init__.py new file mode 100644 index 00000000..0bf48f61 --- /dev/null +++ b/src/ansys/fluent/visualization/pyvista/__init__.py @@ -0,0 +1,17 @@ +"""A DEPRECATED package that provides interfacing Fluent with graphics renderer.""" + +import warnings + +from ansys.fluent.core.warnings import PyFluentDeprecationWarning + +warnings.warn( + "'pyvista' is deprecated. Use 'graphics' instead.", + PyFluentDeprecationWarning, +) + +from ansys.fluent.visualization.graphics.graphics_objects import Graphics # noqa: F401 +from ansys.fluent.visualization.graphics.graphics_windows_manager import ( # noqa: F401 + graphics_windows_manager, +) + +pyvista_windows_manager = graphics_windows_manager