From e738f274f0a751adc654368e8e9c6459256d0663 Mon Sep 17 00:00:00 2001 From: Aseem Jain Date: Wed, 30 Mar 2022 09:45:15 +0530 Subject: [PATCH] Blocking post display. --- ansys/fluent/core/session.py | 13 ++++-- ansys/fluent/post/__init__.py | 3 +- ansys/fluent/post/_config.py | 38 ++++++++++++++++++ .../fluent/post/matplotlib/matplot_objects.py | 6 +-- .../matplotlib/matplot_windows_manager.py | 40 ++++++++++++++++--- ansys/fluent/post/matplotlib/plotter_defns.py | 20 ++++++++++ ansys/fluent/post/post_windows_manager.py | 23 +++++++++++ ansys/fluent/post/pyvista/pyvista_objects.py | 6 +-- .../post/pyvista/pyvista_windows_manager.py | 39 +++++++++++++++--- tests/test_post.py | 11 +++-- 10 files changed, 168 insertions(+), 31 deletions(-) create mode 100644 ansys/fluent/post/_config.py diff --git a/ansys/fluent/core/session.py b/ansys/fluent/core/session.py index 42c244f5ab1d..29ae2ccc8abf 100644 --- a/ansys/fluent/core/session.py +++ b/ansys/fluent/core/session.py @@ -118,6 +118,7 @@ def __init__( port: int = None, channel: grpc.Channel = None, cleanup_on_exit: bool = True, + metadata: List[Tuple[str, str]] = [], ): """ Instantiate a Session. @@ -141,6 +142,8 @@ def __init__( When True, the connected Fluent session will be shut down when PyFluent is exited or exit() is called on the session instance, by default True. + metadata : List[Tuple[str, str]], optional + Metadata information. """ if channel is not None: self._channel = channel @@ -154,7 +157,7 @@ def __init__( "The port to connect to Fluent session is not provided." ) self._channel = grpc.insecure_channel(f"{ip}:{port}") - self._metadata: List[Tuple[str, str]] = [] + self._metadata: List[Tuple[str, str]] = metadata self._id = f"session-{next(Session._id_iter)}" self._settings_root = None @@ -229,8 +232,12 @@ def create_from_server_info_file( Session instance """ ip, port, password = _parse_server_info_file(server_info_filepath) - session = Session(ip=ip, port=port, cleanup_on_exit=cleanup_on_exit) - session._metadata.append(("password", password)) + session = Session( + ip=ip, + port=port, + cleanup_on_exit=cleanup_on_exit, + metadata=[("password", password)], + ) return session @property diff --git a/ansys/fluent/post/__init__.py b/ansys/fluent/post/__init__.py index 316f135d5ba7..1c416234ae48 100644 --- a/ansys/fluent/post/__init__.py +++ b/ansys/fluent/post/__init__.py @@ -84,5 +84,4 @@ def _update_vtk_version(): if import_errors: raise ImportError("\n".join(import_errors)) - -import ansys.fluent.post.pyvista as pyvista # noqa: F401 +from ansys.fluent.post._config import get_config, set_config # noqa: F401 diff --git a/ansys/fluent/post/_config.py b/ansys/fluent/post/_config.py new file mode 100644 index 000000000000..cbef3608fc7c --- /dev/null +++ b/ansys/fluent/post/_config.py @@ -0,0 +1,38 @@ +"""Global configuration state for post.""" +import threading + +_global_config = { + "blocking": False, +} +_threadlocal = threading.local() + + +def _get_threadlocal_config(): + if not hasattr(_threadlocal, "global_config"): + _threadlocal.global_config = _global_config.copy() + return _threadlocal.global_config + + +def get_config() -> dict: + """ + Retrieve post configuration. + + Returns + ------- + config : dict + Keys are parameter names that can be passed to :func:`set_config`. + """ + return _get_threadlocal_config().copy() + + +def set_config(blocking: bool = False): + """ + Set post configuration. + + Parameters + ---------- + blocking : bool, default=False + If True, then graphics/plot display will block the current thread. + """ + local_config = _get_threadlocal_config() + local_config["blocking"] = blocking diff --git a/ansys/fluent/post/matplotlib/matplot_objects.py b/ansys/fluent/post/matplotlib/matplot_objects.py index ffaf9f8ca62b..dcfc2d65ee18 100644 --- a/ansys/fluent/post/matplotlib/matplot_objects.py +++ b/ansys/fluent/post/matplotlib/matplot_objects.py @@ -33,11 +33,7 @@ def __init__(self, session, local_surfaces_provider=None): else: self.__dict__ = session_state self._local_surfaces_provider = ( - lambda: local_surfaces_provider - if local_surfaces_provider - else self.Surfaces - if hasattr(self, "Surfaces") - else [] + lambda: local_surfaces_provider or getattr(self, "Surfaces", []) ) def _init_module(self, obj, mod): diff --git a/ansys/fluent/post/matplotlib/matplot_windows_manager.py b/ansys/fluent/post/matplotlib/matplot_windows_manager.py index 7d072738c446..344dbb832239 100644 --- a/ansys/fluent/post/matplotlib/matplot_windows_manager.py +++ b/ansys/fluent/post/matplotlib/matplot_windows_manager.py @@ -4,8 +4,10 @@ from typing import List, Optional, Union import numpy as np + from ansys.fluent.core.session import Session from ansys.fluent.core.utils.generic import AbstractSingletonMeta, in_notebook +from ansys.fluent.post import get_config from ansys.fluent.post.matplotlib.plotter_defns import Plotter, ProcessPlotter from ansys.fluent.post.post_object_defns import GraphicsDefn, PlotDefn from ansys.fluent.post.post_windows_manager import ( @@ -40,6 +42,9 @@ def plot(self, data): def set_properties(self, properties): self.plot_pipe.send({"properties": properties}) + def save_graphic(self, name: str): + self.plot_pipe.send({"save_graphic": name}) + def is_closed(self): if self._closed: return True @@ -82,15 +87,13 @@ def __init__(self, id: str, post_object: Union[GraphicsDefn, PlotDefn]): self.animate: bool = False self.close: bool = False self.refresh: bool = False - if in_notebook(): - self.plotter() def plot(self): """Draw plot.""" if not self.post_object: return xy_data = self._get_xy_plot_data() - if in_notebook(): + if in_notebook() or get_config()["blocking"]: self.plotter.set_properties(self.properties) else: try: @@ -106,7 +109,7 @@ def plot(self): def _get_plotter(self): return ( Plotter(self.id) - if in_notebook() + if in_notebook() or get_config()["blocking"] else _ProcessPlotterHandle(self.id) ) @@ -244,6 +247,30 @@ def plot( window.post_object = object window.plot() + def save_graphic( + self, + window_id: str, + format: str, + ) -> None: + """ + Save graphics. + + Parameters + ---------- + window_id : str + Window id for which graphic should be saved. + format : str + Graphic format. + + Raises + ------ + ValueError + If window does not support specified format. + """ + 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] = "", @@ -332,7 +359,10 @@ def _open_window( if ( window and not window.plotter.is_closed() - and (not in_notebook() or window.refresh) + and ( + not (in_notebook() or get_config()["blocking"]) + or window.refresh + ) ): window.refresh = False else: diff --git a/ansys/fluent/post/matplotlib/plotter_defns.py b/ansys/fluent/post/matplotlib/plotter_defns.py index 1a4b094da368..275a82103962 100644 --- a/ansys/fluent/post/matplotlib/plotter_defns.py +++ b/ansys/fluent/post/matplotlib/plotter_defns.py @@ -50,6 +50,7 @@ def __init__( self._max_x = None self._data = {} self._closed = False + self._visible = False if not remote_process: self.fig = plt.figure(num=self._window_id) self.ax = self.fig.add_subplot(111) @@ -98,6 +99,9 @@ def plot(self, data: dict) -> None: self.ax.set_ylim( self._min_y - y_range * 0.2, self._max_y + y_range * 0.2 ) + if not self._visible: + self._visible = True + plt.show() def close(self): """Close window.""" @@ -108,6 +112,17 @@ 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. + """ + plt.savefig(name) + def set_properties(self, properties: dict): """ Set plot properties. @@ -131,6 +146,7 @@ def set_properties(self, properties: dict): def __call__(self): """Reset and show plot.""" self._reset() + self._visible = True plt.show() # private methods @@ -193,6 +209,9 @@ def _call_back(self): if "properties" in data: properties = data["properties"] self.set_properties(properties) + elif "save_graphic" in data: + name = data["save_graphic"] + self.save_graphic(name) else: self.plot(data) self.fig.canvas.draw() @@ -209,4 +228,5 @@ def __call__(self, pipe): timer = self.fig.canvas.new_timer(interval=10) timer.add_callback(self._call_back) timer.start() + self._visible = True plt.show() diff --git a/ansys/fluent/post/post_windows_manager.py b/ansys/fluent/post/post_windows_manager.py index f4edc8974163..aff61aedade4 100644 --- a/ansys/fluent/post/post_windows_manager.py +++ b/ansys/fluent/post/post_windows_manager.py @@ -83,6 +83,29 @@ def plot( """ pass + @abstractmethod + def save_graphic( + self, + window_id: str, + format: str, + ) -> None: + """ + Save graphics. + + Parameters + ---------- + window_id : str + Window id for which graphic should be saved. + format : str + Graphic format. + + Raises + ------ + ValueError + If window does not support specified format. + """ + pass + @abstractmethod def refresh_windows( self, diff --git a/ansys/fluent/post/pyvista/pyvista_objects.py b/ansys/fluent/post/pyvista/pyvista_objects.py index 8a65cae39f50..c540151784b0 100644 --- a/ansys/fluent/post/pyvista/pyvista_objects.py +++ b/ansys/fluent/post/pyvista/pyvista_objects.py @@ -45,11 +45,7 @@ def __init__(self, session, local_surfaces_provider=None): else: self.__dict__ = session_state self._local_surfaces_provider = ( - lambda: local_surfaces_provider - if local_surfaces_provider - else self.Surfaces - if hasattr(self, "Surfaces") - else [] + lambda: local_surfaces_provider or getattr(self, "Surfaces", []) ) def _init_module(self, obj, mod): diff --git a/ansys/fluent/post/pyvista/pyvista_windows_manager.py b/ansys/fluent/post/pyvista/pyvista_windows_manager.py index 60374b7b8752..14b93c098783 100644 --- a/ansys/fluent/post/pyvista/pyvista_windows_manager.py +++ b/ansys/fluent/post/pyvista/pyvista_windows_manager.py @@ -9,6 +9,7 @@ from ansys.fluent.core.session import Session from ansys.fluent.core.utils.generic import AbstractSingletonMeta, in_notebook +from ansys.fluent.post import get_config from ansys.fluent.post.post_object_defns import GraphicsDefn, PlotDefn from ansys.fluent.post.post_windows_manager import ( PostWindow, @@ -34,15 +35,15 @@ def __init__(self, id: str, post_object: Union[GraphicsDefn, PlotDefn]): self.id: str = id self.plotter: Union[BackgroundPlotter, pv.Plotter] = ( pv.Plotter() - if in_notebook() + if in_notebook() or get_config()["blocking"] else BackgroundPlotter(title=f"PyFluent ({self.id})") ) self.animate: bool = False self.close: bool = False self.refresh: bool = False self.update: bool = False + self._visible: bool = False self._init_properties() - self.plotter.show() def plot(self): """Plot graphics.""" @@ -64,6 +65,9 @@ def plot(self): if self.animate: plotter.write_frame() plotter.camera = camera.copy() + if not self._visible: + self._visible = True + plotter.show() # private methods @@ -415,7 +419,7 @@ def open_window(self, window_id: Optional[str] = None) -> str: with self._condition: if not window_id: window_id = self._get_unique_window_id() - if in_notebook(): + if in_notebook() or get_config()["blocking"]: self._open_window_notebook(window_id) else: self._open_and_plot_console(None, window_id) @@ -473,11 +477,36 @@ def plot( with self._condition: if not window_id: window_id = self._get_unique_window_id() - if in_notebook(): + if in_notebook() or get_config()["blocking"]: self._plot_notebook(object, window_id) else: self._open_and_plot_console(object, window_id) + def save_graphic( + self, + window_id: str, + format: str, + ) -> None: + """ + Save graphics. + + Parameters + ---------- + window_id : str + Window id for which graphic should be saved. + format : str + Graphic format. + + Raises + ------ + ValueError + If window does not support 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] = "", @@ -561,7 +590,7 @@ def close_windows( for window_id in windows_id: window = self._post_windows.get(window_id) if window: - if in_notebook(): + if in_notebook() or get_config()["blocking"]: window.plotter.close() window.close = True diff --git a/tests/test_post.py b/tests/test_post.py index 110312def71a..e29ea2646c32 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -90,16 +90,15 @@ class MockLocalObjectDataExtractor: def __init__(self, obj=None): if not MockLocalObjectDataExtractor._session_data: - pickle_obj = open( + with open( str( Path(MockLocalObjectDataExtractor._session_dump).resolve() ), "rb", - ) - MockLocalObjectDataExtractor._session_data = pickle.load( - pickle_obj - ) - pickle_obj.close() + ) as pickle_obj: + MockLocalObjectDataExtractor._session_data = pickle.load( + pickle_obj + ) self.field_info = lambda: MockFieldInfo( MockLocalObjectDataExtractor._session_data )