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..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.pyvista.pyvista_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 94452ff3..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.pyvista.pyvista_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, @@ -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/pyvista_windows_manager.rst b/doc/source/api/visualization/graphics_windows_manager.rst similarity index 69% rename from doc/source/api/visualization/pyvista_windows_manager.rst rename to doc/source/api/visualization/graphics_windows_manager.rst index 06ca9da8..c265e62f 100644 --- a/doc/source/api/visualization/pyvista_windows_manager.rst +++ b/doc/source/api/visualization/graphics_windows_manager.rst @@ -1,8 +1,8 @@ -.. _ref_pyvista_windows_manager: +.. _ref_graphics_windows_manager: -PyVista windows manager -======================= -The ``PyVistaWindowsManager`` 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. @@ -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.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): - pyvista_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. - pyvista_windows_manager.animate_windows(session.id, ["window-1"]) + graphics_windows_manager.animate_windows(session.id, ["window-1"]) -.. autoclass:: ansys.fluent.visualization.pyvista.pyvista_windows_manager.PyVistaWindowsManager +.. autoclass:: ansys.fluent.visualization.graphics.graphics_windows_manager.GraphicsWindowsManager :members: \ No newline at end of file diff --git a/doc/source/api/visualization/index.rst b/doc/source/api/visualization/index.rst index 5f1da46b..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 @@ -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/mesh.rst b/doc/source/api/visualization/mesh.rst index aa665ba0..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.pyvista.pyvista_objects.Mesh +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Mesh 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/pathlines.rst b/doc/source/api/visualization/pathlines.rst index 1bf530c0..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.pyvista.pyvista_objects.Pathlines +.. autopostdoc:: ansys.fluent.visualization.graphics.graphics_objects.Pathlines diff --git a/doc/source/api/visualization/plots.rst b/doc/source/api/visualization/plots.rst index 71fa9ac8..5016ae9f 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"] @@ -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/matplot_windows_manager.rst b/doc/source/api/visualization/plotter_windows_manager.rst similarity index 73% rename from doc/source/api/visualization/matplot_windows_manager.rst rename to doc/source/api/visualization/plotter_windows_manager.rst index dcdabd69..93f8d61c 100644 --- a/doc/source/api/visualization/matplot_windows_manager.rst +++ b/doc/source/api/visualization/plotter_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. @@ -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 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_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) @@ -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.PlotterWindowsManager :members: \ No newline at end of file diff --git a/doc/source/api/visualization/post_objects.rst b/doc/source/api/visualization/post_objects.rst index 2d99b94a..01311cb4 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 + graphics_windows_manager + plotter_windows_manager diff --git a/doc/source/api/visualization/surface.rst b/doc/source/api/visualization/surface.rst index e95edcaf..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.pyvista.pyvista_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 42eb09ef..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.pyvista.pyvista_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/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/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/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/examples/00-postprocessing/script_manifold.py b/examples/00-postprocessing/script_manifold.py new file mode 100644 index 00000000..e77ef6c1 --- /dev/null +++ b/examples/00-postprocessing/script_manifold.py @@ -0,0 +1,124 @@ +""".. _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 + +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"]) diff --git a/src/ansys/fluent/visualization/__init__.py b/src/ansys/fluent/visualization/__init__.py index 1f7bd55f..5454ce69 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.pyvista 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/_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 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/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/pyvista/pyvista_objects.py b/src/ansys/fluent/visualization/graphics/graphics_objects.py similarity index 90% rename from src/ansys/fluent/visualization/pyvista/pyvista_objects.py rename to src/ansys/fluent/visualization/graphics/graphics_objects.py index edc1238a..de8ef06b 100644 --- a/src/ansys/fluent/visualization/pyvista/pyvista_objects.py +++ b/src/ansys/fluent/visualization/graphics/graphics_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.graphics.graphics_windows_manager import ( + graphics_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( + graphics_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( + graphics_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( + graphics_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( + graphics_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( + graphics_windows_manager.plot( self, window_id=window_id, overlay=overlay, fetch_data=True ) diff --git a/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py similarity index 71% rename from src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py rename to src/ansys/fluent/visualization/graphics/graphics_windows_manager.py index b79ca53f..eaa0e8b4 100644 --- a/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py +++ b/src/ansys/fluent/visualization/graphics/graphics_windows_manager.py @@ -1,4 +1,4 @@ -"""Module for pyVista windows management.""" +"""Module for graphics windows management.""" from enum import Enum import itertools @@ -24,17 +24,20 @@ class FieldDataType(Enum): """Provides surface data types.""" - Meshes = 1 - Vectors = 2 - Contours = 3 - Pathlines = 4 + Meshes = "Mesh" + Vectors = "Vector" + Contours = "Contour" + Pathlines = "Pathlines" -class PyVistaWindow(PostWindow): - """Provides for managing PyVista windows.""" +from ansys.fluent.visualization.graphics.pyvista.graphics_defns import Renderer + + +class GraphicsWindow(PostWindow): + """Provides for managing Graphics windows.""" def __init__(self, id: str, post_object: GraphicsDefn): - """Instantiate a PyVista window. + """Instantiate a Graphics window. Parameters ---------- @@ -45,11 +48,7 @@ def __init__(self, id: str, post_object: GraphicsDefn): """ self.post_object: GraphicsDefn = post_object self.id: str = id - 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})") - ) + self.renderer = Renderer(id, in_notebook(), get_config()["blocking"]) self.overlay: bool = False self.fetch_data: bool = False self.show_window: bool = True @@ -58,27 +57,7 @@ def __init__(self, id: str, post_object: GraphicsDefn): 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], - "blue": [0, 0, 255], - "yellow": [255, 255, 0], - "cyan": [0, 255, 255], - "magenta": [255, 0, 255], - "silver": [192, 192, 192], - "gray": [128, 128, 128], - "maroon": [128, 0, 0], - "olive": [128, 128, 0], - "green": [0, 128, 0], - "purple": [128, 0, 128], - "teal": [0, 128, 128], - "navy": [0, 0, 128], - "orange": [255, 165, 0], - "brown": [210, 105, 30], - "white": [255, 255, 255], - } def set_data(self, data_type: FieldDataType, data: Dict[int, Dict[str, np.array]]): """Set data for graphics.""" @@ -89,57 +68,34 @@ 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.""" 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() + self.renderer._clear_plotter(in_notebook()) if obj.__class__.__name__ == "Mesh": - self._display_mesh(obj, plotter) + self._display_mesh(obj) elif obj.__class__.__name__ == "Surface": - self._display_surface(obj, plotter) + self._display_surface(obj) elif obj.__class__.__name__ == "Contour": - self._display_contour(obj, plotter) + self._display_contour(obj) elif obj.__class__.__name__ == "Vector": - self._display_vector(obj, plotter) + self._display_vector(obj) elif obj.__class__.__name__ == "Pathlines": - self._display_pathlines(obj, plotter) + self._display_pathlines(obj) 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() + self.renderer.write_frame() + self.renderer._set_camera(get_config()["set_view_on_display"]) if not self._visible and self.show_window: - plotter.show() + self.renderer.show() self._visible = True def plot(self): @@ -148,33 +104,59 @@ 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_or_display_surface(self, obj, fetch: bool): + 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 + 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() + if fetch: + self._fetch_data(mesh, FieldDataType.Meshes) + else: + self._display_mesh(mesh) + del post_session.Meshes[dummy_object] - def _init_properties(self): - self.plotter.theme.cmap = "jet" - self.plotter.background_color = "white" - self.plotter.theme.font.color = "black" - - def _scalar_bar_default_properties(self) -> dict: - return dict( - title_font_size=20, - label_font_size=16, - shadow=True, - fmt="%.6e", - font_family="arial", - vertical=True, - position_x=0.06, - 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]): + 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": + 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() # scalar bar properties - scalar_bar_args = self._scalar_bar_default_properties() + scalar_bar_args = self.renderer._scalar_bar_default_properties() field = obj.field() field_unit = obj._api_helper.get_field_unit(field) @@ -189,17 +171,7 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): 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) @@ -231,26 +203,22 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): factor=vector_scale * obj.scale(), geom=pv.Arrow(), ) - plotter.add_mesh( + self.renderer.render( 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() + self.renderer.render(mesh, show_edges=True, color="white") - def _display_pathlines(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): + 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._scalar_bar_default_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(): @@ -264,17 +232,13 @@ def _display_pathlines(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]) ) mesh.point_data[field] = surface_data[obj.field()] - plotter.add_mesh( + 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, plotter: Union[BackgroundPlotter, pv.Plotter]): + def _display_contour(self, obj): # contour properties field = obj.field() field_unit = obj._api_helper.get_field_unit(field) @@ -285,24 +249,14 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): node_values = obj.node_values() # scalar bar properties - scalar_bar_args = self._scalar_bar_default_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"], - ) + mesh = self._resolve_mesh_data(surface_data) if node_values: mesh.point_data[field] = surface_data[obj.field()] else: @@ -322,7 +276,7 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): value=auto_range_off.minimum(), ) if filled: - plotter.add_mesh( + self.renderer.render( minimum_above, scalars=field, show_edges=obj.show_edges(), @@ -333,10 +287,12 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): np.min(minimum_above[field]) != np.max(minimum_above[field]) ): - plotter.add_mesh(minimum_above.contour(isosurfaces=20)) + self.renderer.render( + minimum_above.contour(isosurfaces=20) + ) else: if filled: - plotter.add_mesh( + self.renderer.render( mesh, clim=[ auto_range_off.minimum(), @@ -349,13 +305,13 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): if (not filled or contour_lines) and ( np.min(mesh[field]) != np.max(mesh[field]) ): - plotter.add_mesh(mesh.contour(isosurfaces=20)) + 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() - plotter.add_mesh( + self.renderer.render( mesh, clim=field_info.get_scalar_field_range(obj.field(), False), scalars=field, @@ -365,11 +321,11 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): if (not filled or contour_lines) and ( np.min(mesh[field]) != np.max(mesh[field]) ): - plotter.add_mesh(mesh.contour(isosurfaces=20)) + self.renderer.render(mesh.contour(isosurfaces=20)) else: if filled: - plotter.add_mesh( + self.renderer.render( mesh, scalars=field, show_edges=obj.show_edges(), @@ -378,80 +334,25 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): if (not filled or contour_lines) and ( np.min(mesh[field]) != np.max(mesh[field]) ): - plotter.add_mesh(mesh.contour(isosurfaces=20)) + 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): + self._fetch_or_display_surface(obj, fetch=False) - 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]): + 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._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"): + 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) + + def _get_refresh_for_plotter(self, window: "GraphicsWindow"): def refresh(): - with PyVistaWindowsManager._condition: - plotter = window.plotter + with GraphicsWindowsManager._condition: + plotter = window.renderer if window.close: window.animate = False plotter.close() @@ -462,27 +363,27 @@ def refresh(): try: window.plot() finally: - PyVistaWindowsManager._condition.notify() + GraphicsWindowsManager._condition.notify() return refresh -class PyVistaWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): - """Provides for managing PyVista windows.""" +class GraphicsWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): + """Provides for managing Graphics windows.""" _condition = threading.Condition() def __init__(self): - """Instantiate ``PyVistaWindow`` for PyVista.""" - self._post_windows: Dict[str:PyVistaWindow] = {} + """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) -> PyVistaWindow: - """Get the PyVista window. + def get_window(self, window_id: str) -> GraphicsWindow: + """Get the Graphics window. Parameters ---------- @@ -491,8 +392,8 @@ def get_window(self, window_id: str) -> PyVistaWindow: Returns ------- - PyVistaWindow - PyVista window. + GraphicsWindow + Graphics window. """ with self._condition: return self._post_windows.get(window_id, None) @@ -511,7 +412,7 @@ def get_plotter(self, window_id: str) -> Union[BackgroundPlotter, pv.Plotter]: PyVista plotter. """ with self._condition: - return self._post_windows[window_id].plotter + return self._post_windows[window_id].renderer.plotter def open_window(self, window_id: Optional[str] = None) -> str: """Open a new window. @@ -599,12 +500,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. @@ -616,7 +517,7 @@ def save_graphic( with self._condition: window = self._post_windows.get(window_id) if window: - window.plotter.save_graphic(f"{window_id}.{format}") + window.renderer.save_graphic(f"{window_id}.{format}") def refresh_windows( self, @@ -674,7 +575,7 @@ def animate_windows( window = self._post_windows.get(window_id) if window: window.animate = True - window.plotter.open_gif(f"{window.id}.gif") + window.renderer.get_animation(window.id) def close_windows( self, @@ -699,7 +600,7 @@ def close_windows( window = self._post_windows.get(window_id) if window: if in_notebook() or get_config()["blocking"]: - window.plotter.close() + window.renderer.plotter.close() window.close = True # private methods @@ -711,11 +612,11 @@ def _display(self) -> None: break if self._window_id: window = self._post_windows.get(self._window_id) - plotter = window.plotter if window else None + plotter = window.renderer.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 + 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), @@ -732,7 +633,7 @@ def _display(self) -> None: self._app.processEvents() with self._condition: for window in self._post_windows.values(): - plotter = window.plotter + plotter = window.renderer.plotter plotter.close() plotter.app.quit() self._post_windows.clear() @@ -767,7 +668,7 @@ def _open_window_notebook(self, window_id: str) -> pv.Plotter: if window and not window.close and window.refresh: window.refresh = False else: - window = PyVistaWindow(window_id, None) + window = GraphicsWindow(window_id, None) self._post_windows[window_id] = window return window @@ -791,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() @@ -817,4 +718,4 @@ def _get_unique_window_id(self) -> str: return window_id -pyvista_windows_manager = PyVistaWindowsManager() +graphics_windows_manager = GraphicsWindowsManager() diff --git a/src/ansys/fluent/visualization/graphics/pyvista/__init__.py b/src/ansys/fluent/visualization/graphics/pyvista/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py b/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py new file mode 100644 index 00000000..0b930f80 --- /dev/null +++ b/src/ansys/fluent/visualization/graphics/pyvista/graphics_defns.py @@ -0,0 +1,107 @@ +"""Module for pyVista windows management.""" + +import pyvista as pv +from pyvistaqt import BackgroundPlotter + +from ansys.fluent.visualization.graphics.abstract_graphics_defns import AbstractRenderer + + +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})") + if in_notebook or non_interactive + else BackgroundPlotter(title=f"PyFluent ({win_id})") + ) + self._init_properties() + self._colors = { + "red": [255, 0, 0], + "lime": [0, 255, 0], + "blue": [0, 0, 255], + "yellow": [255, 255, 0], + "cyan": [0, 255, 255], + "magenta": [255, 0, 255], + "silver": [192, 192, 192], + "gray": [128, 128, 128], + "maroon": [128, 0, 0], + "olive": [128, 128, 0], + "green": [0, 128, 0], + "purple": [128, 0, 128], + "teal": [0, 128, 128], + "navy": [0, 0, 128], + "orange": [255, 165, 0], + "brown": [210, 105, 30], + "white": [255, 255, 255], + } + + def _init_properties(self): + self.plotter.theme.cmap = "jet" + self.plotter.background_color = "white" + self.plotter.theme.font.color = "black" + + def _scalar_bar_default_properties(self) -> dict: + return dict( + title_font_size=20, + label_font_size=16, + shadow=True, + fmt="%.6e", + font_family="arial", + vertical=True, + position_x=0.06, + position_y=0.3, + ) + + 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: + self.plotter.clear() + + 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: + self.plotter.camera = camera.copy() + + 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() diff --git a/src/ansys/fluent/visualization/matplotlib/__init__.py b/src/ansys/fluent/visualization/matplotlib/__init__.py index 49e3f46e..ec0c82f0 100644 --- a/src/ansys/fluent/visualization/matplotlib/__init__.py +++ b/src/ansys/fluent/visualization/matplotlib/__init__.py @@ -1,6 +1,17 @@ -"""A package that provides interfacing Fluent with Matplotlib.""" +"""A DEPRECATED package that provides interfacing Fluent with plotters.""" -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, +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/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/abstract_plotter_defns.py b/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py new file mode 100644 index 00000000..35ec28b8 --- /dev/null +++ b/src/ansys/fluent/visualization/plotter/abstract_plotter_defns.py @@ -0,0 +1,51 @@ +"""Abstract module providing 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/__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 97% rename from src/ansys/fluent/visualization/matplotlib/plotter_defns.py rename to src/ansys/fluent/visualization/plotter/matplotlib/plotter_defns.py index 19cfc868..f1b678b2 100644 --- a/src/ansys/fluent/visualization/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__( @@ -147,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/matplotlib/matplot_objects.py b/src/ansys/fluent/visualization/plotter/plotter_objects.py similarity index 82% rename from src/ansys/fluent/visualization/matplotlib/matplot_objects.py rename to src/ansys/fluent/visualization/plotter/plotter_objects.py index 701e9e3b..55476d94 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, ) @@ -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" @@ -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): @@ -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") @@ -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..3ea469dc --- /dev/null +++ b/src/ansys/fluent/visualization/plotter/pyvista/plotter_defns.py @@ -0,0 +1,167 @@ +from typing import List, Optional + +import numpy as np +import pyvista as pv + +from ansys.fluent.visualization.plotter.abstract_plotter_defns import AbstractPlotter + + +class Plotter(AbstractPlotter): + """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 pyvista chart 2D 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 graphics. + """ + 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 = False + + # 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 diff --git a/src/ansys/fluent/visualization/pyvista/__init__.py b/src/ansys/fluent/visualization/pyvista/__init__.py index 25d907dd..0bf48f61 100644 --- a/src/ansys/fluent/visualization/pyvista/__init__.py +++ b/src/ansys/fluent/visualization/pyvista/__init__.py @@ -1,6 +1,17 @@ -"""A package that provides interfacing Fluent with PyVista.""" +"""A DEPRECATED package that provides interfacing Fluent with graphics renderer.""" -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, +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 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)