diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 365b89ec..83878387 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -41,7 +41,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest] - python-version: ['3.7', '3.8', '3.9'] + python-version: ['3.7', '3.8', '3.9', '3.10'] fail-fast: false steps: diff --git a/pyproject.toml b/pyproject.toml index 74effc39..f3be1ff2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,11 @@ packages = [ python = ">=3.7,<4.0" importlib-metadata = {version = "^4.0", python = "<3.8"} ansys-fluent-solver = { git = "https://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@github.com/pyansys/pyfluent.git", branch = "main" } -vtk = "9.1.0" +vtk = [ +{ url = "https://github.com/pyvista/pyvista-wheels/raw/main/vtk-9.1.0.dev0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" , markers = "python_version > '3.9' and sys_platform == 'linux'"}, +{ url = "https://github.com/pyvista/pyvista-wheels/raw/main/vtk-9.1.0.dev0-cp310-cp310-win_amd64.whl" , markers = "python_version > '3.9' and sys_platform != 'linux'"}, +{ version = "9.1.0", python = "<=3.9" }, +] pyvista = "0.33.2" pyvistaqt = "0.7.0" pyside6 = "6.2.3" diff --git a/src/ansys/fluent/post/matplotlib/matplot_objects.py b/src/ansys/fluent/post/matplotlib/matplot_objects.py index 837f1962..0b269ed4 100644 --- a/src/ansys/fluent/post/matplotlib/matplot_objects.py +++ b/src/ansys/fluent/post/matplotlib/matplot_objects.py @@ -6,7 +6,7 @@ from ansys.fluent.core.meta import PyLocalContainer from ansys.fluent.post.matplotlib.matplot_windows_manager import matplot_windows_manager -from ansys.fluent.post.post_object_defns import XYPlotDefn +from ansys.fluent.post.post_object_defns import MonitorDefn, XYPlotDefn class Plots: @@ -60,6 +60,18 @@ def plot(self, window_id: Optional[str] = None): window_id : str, optional Window id. If not specified unique id is used. """ - self._pre_display() matplot_windows_manager.plot(self, window_id) - self._post_display() + + +class MonitorPlot(MonitorDefn): + """Monitor Plot.""" + + def plot(self, window_id: Optional[str] = None): + """Draw Monitor Plot. + + Parameters + ---------- + window_id : str, optional + Window id. If not specified unique id is used. + """ + matplot_windows_manager.plot(self, window_id) diff --git a/src/ansys/fluent/post/matplotlib/matplot_windows_manager.py b/src/ansys/fluent/post/matplotlib/matplot_windows_manager.py index bf02ca47..e17de803 100644 --- a/src/ansys/fluent/post/matplotlib/matplot_windows_manager.py +++ b/src/ansys/fluent/post/matplotlib/matplot_windows_manager.py @@ -3,14 +3,13 @@ import multiprocessing as mp from typing import List, Optional, Union -from ansys.api.fluent.v0.field_data_pb2 import PayloadTag from ansys.fluent.core.session import Session from ansys.fluent.core.utils.generic import AbstractSingletonMeta, in_notebook -import numpy as np 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_data_extractor import XYPlotDataExtractor +from ansys.fluent.post.post_object_defns import MonitorDefn, PlotDefn, XYPlotDefn from ansys.fluent.post.post_windows_manager import PostWindow, PostWindowsManager @@ -65,40 +64,31 @@ def close(self): class MatplotWindow(PostWindow): """Class for MatplotWindow.""" - def __init__(self, id: str, post_object: Union[GraphicsDefn, PlotDefn]): + def __init__(self, id: str, post_object: PlotDefn): """Instantiate a MatplotWindow. Parameters ---------- id : str Window id. - post_object : Union[GraphicsDefn, PlotDefn] + post_object : PlotDefn Object to plot. """ - self.post_object: Union[GraphicsDefn, PlotDefn] = post_object self.id: str = id - self.properties: dict = None + self.post_object = None self.plotter: Union[_ProcessPlotterHandle, Plotter] = self._get_plotter() - self.animate: bool = False self.close: bool = False self.refresh: bool = False def plot(self): """Draw plot.""" - if not self.post_object: - return - xy_data = self._get_xy_plot_data() - if in_notebook() or get_config()["blocking"]: - self.plotter.set_properties(self.properties) - else: - try: - self.plotter.set_properties(self.properties) - except BrokenPipeError: - self.plotter: Union[ - _ProcessPlotterHandle, Plotter - ] = self._get_plotter() - self.plotter.set_properties(self.properties) - self.plotter.plot(xy_data) + if self.post_object is not None: + plot = ( + _XYPlot(self.post_object, self.plotter) + if self.post_object.__class__.__name__ == "XYPlot" + else _MonitorPlot(self.post_object, self.plotter) + ) + plot() # private methods def _get_plotter(self): @@ -108,86 +98,99 @@ def _get_plotter(self): else _ProcessPlotterHandle(self.id) ) - def _get_xy_plot_data(self): - obj = self.post_object - field = obj.y_axis_function() - node_values = obj.node_values() - boundary_values = obj.boundary_values() - direction_vector = obj.direction_vector() - surfaces_list = obj.surfaces_list() - self.properties = { - "curves": surfaces_list, + +class _XYPlot: + """Class for XYPlot.""" + + def __init__( + self, post_object: XYPlotDefn, plotter: Union[_ProcessPlotterHandle, Plotter] + ): + """Instantiate XYPlot. + + Parameters + ---------- + post_object : XYPlotDefn + Object to plot. + plotter: Union[_ProcessPlotterHandle, Plotter] + Plotter to plot data. + """ + self.post_object: XYPlotDefn = post_object + self.plotter: Union[_ProcessPlotterHandle, Plotter] = plotter + + def __call__(self): + """Draw XY plot.""" + if not self.post_object: + return + properties = { + "curves": self.post_object.surfaces_list(), "title": "XY Plot", "xlabel": "position", - "ylabel": field, + "ylabel": self.post_object.y_axis_function(), } - field_info = obj._data_extractor.field_info() - field_data = obj._data_extractor.field_data() - surfaces_info = field_info.get_surfaces_info() - surface_ids = [ - id - for surf in map( - obj._data_extractor.remote_surface_name, obj.surfaces_list() - ) - for id in surfaces_info[surf]["surface_id"] - ] + xy_data = XYPlotDataExtractor(self.post_object).fetch_data() + if in_notebook() or get_config()["blocking"]: + self.plotter.set_properties(properties) + else: + try: + self.plotter.set_properties(properties) + except BrokenPipeError: + self.plotter: Union[ + _ProcessPlotterHandle, Plotter + ] = self._get_plotter() + self.plotter.set_properties(properties) + self.plotter.plot(xy_data) - # get scalar field data - field_data.add_get_surfaces_request( - surface_ids, - provide_faces=False, - provide_vertices=True if node_values else False, - provide_faces_centroid=False if node_values else True, - ) - field_data.add_get_scalar_fields_request( - surface_ids, - field, - node_values, - boundary_values, - ) - location_tag = ( - field_data._payloadTags[PayloadTag.NODE_LOCATION] - if node_values - else field_data._payloadTags[PayloadTag.ELEMENT_LOCATION] - ) - boundary_value_tag = ( - field_data._payloadTags[PayloadTag.BOUNDARY_VALUES] - if boundary_values - else 0 +class _MonitorPlot: + """Class MonitorPlot.""" + + def __init__( + self, post_object: MonitorDefn, plotter: Union[_ProcessPlotterHandle, Plotter] + ): + """Instantiate MonitorPlot. + + Parameters + ---------- + post_object : MonitorDefn + Object to plot. + plotter: Union[_ProcessPlotterHandle, Plotter] + Plotter to plot data. + """ + self.post_object: MonitorDefn = post_object + self.plotter: Union[_ProcessPlotterHandle, Plotter] = plotter + + def __call__(self): + """Draw Monitor plot.""" + if not self.post_object: + return + monitors_manager = self.post_object._data_extractor.monitors_manager() + indices, columns_data = monitors_manager.get_monitor_set_data( + self.post_object.monitor_set_name() ) - surface_tag = 0 - xyplot_payload_data = field_data.get_fields() - data_tag = location_tag | boundary_value_tag - xyplot_data = xyplot_payload_data[data_tag] - surface_data = xyplot_payload_data[surface_tag] - - # loop over all meshes - xy_plots_data = {} - surfaces_list_iter = iter(surfaces_list) - for surface_id, mesh_data in surface_data.items(): - mesh_data["vertices" if node_values else "centroid"].shape = ( - mesh_data["vertices" if node_values else "centroid"].size // 3, - 3, - ) - y_values = xyplot_data[surface_id][field] - x_values = np.matmul( - mesh_data["vertices" if node_values else "centroid"], - direction_vector, - ) - structured_data = np.empty( - x_values.size, - dtype={ - "names": ("xvalues", "yvalues"), - "formats": ("f8", "f8"), - }, - ) - structured_data["xvalues"] = x_values - structured_data["yvalues"] = y_values - sort = np.argsort(structured_data, order=["xvalues"]) - surface_name = next(surfaces_list_iter) - xy_plots_data[surface_name] = structured_data[sort] - return xy_plots_data + xy_data = {} + for column_name, column_data in columns_data.items(): + xy_data[column_name] = {"xvalues": indices, "yvalues": column_data} + monitor_set_name = self.post_object.monitor_set_name() + properties = { + "curves": list(xy_data.keys()), + "title": monitor_set_name, + "xlabel": monitors_manager.get_monitor_set_prop(monitor_set_name, "xlabel"), + "ylabel": monitors_manager.get_monitor_set_prop(monitor_set_name, "ylabel"), + "yscale": "log" if monitor_set_name == "residual" else "linear", + } + + if in_notebook() or get_config()["blocking"]: + self.plotter.set_properties(properties) + else: + try: + self.plotter.set_properties(properties) + except BrokenPipeError: + self.plotter: Union[ + _ProcessPlotterHandle, Plotter + ] = self._get_plotter() + self.plotter.set_properties(properties) + if xy_data: + self.plotter.plot(xy_data) class MatplotWindowsManager(PostWindowsManager, metaclass=AbstractSingletonMeta): @@ -215,14 +218,12 @@ def open_window(self, window_id: Optional[str] = None) -> str: self._open_window(window_id) return window_id - def set_object_for_window( - self, object: Union[PlotDefn, GraphicsDefn], window_id: str - ) -> None: + def set_object_for_window(self, object: PlotDefn, window_id: str) -> None: """Associate post object with running window instance. Parameters ---------- - object : Union[GraphicsDefn, PlotDefn] + object : PlotDefn Post object to associate with window. window_id : str @@ -241,14 +242,14 @@ def set_object_for_window( def plot( self, - object: Union[PlotDefn, GraphicsDefn], + object: PlotDefn, window_id: Optional[str] = None, ) -> None: """Draw plot. Parameters ---------- - object: Union[GraphicsDefn, PlotDefn] + object: PlotDefn Object to plot. window_id : str, optional diff --git a/src/ansys/fluent/post/matplotlib/plotter_defns.py b/src/ansys/fluent/post/matplotlib/plotter_defns.py index 2aabe6ca..e0fb193a 100644 --- a/src/ansys/fluent/post/matplotlib/plotter_defns.py +++ b/src/ansys/fluent/post/matplotlib/plotter_defns.py @@ -48,6 +48,7 @@ def __init__( self._max_y = None self._min_x = None self._max_x = None + self._yscale = None self._data = {} self._closed = False self._visible = False @@ -72,8 +73,8 @@ def plot(self, data: dict) -> None: 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._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 @@ -84,10 +85,13 @@ def plot(self, data: dict) -> None: curve_line.set_data( self._data[curve]["xvalues"], self._data[curve]["yvalues"] ) - x_range = max_x_value - min_x_value - y_range = max_y_value - min_y_value - self.ax.set_xlim(self._min_x, self._max_x) + if self._max_x > self._min_x: + self.ax.set_xlim(self._min_x, self._max_x) + y_range = self._max_y - self._min_y + if self._yscale == "log": + y_range = 0 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() @@ -123,6 +127,7 @@ def set_properties(self, properties: dict): 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 @@ -140,6 +145,8 @@ def __call__(self): def _reset(self): plt.figure(self.fig.number) self.ax.cla() + if self._yscale: + self.ax.set_yscale(self._yscale) for curve_name in self._curves: self._data[curve_name] = {} self._data[curve_name]["xvalues"] = [] diff --git a/src/ansys/fluent/post/post_data_extractor.py b/src/ansys/fluent/post/post_data_extractor.py new file mode 100644 index 00000000..891eb94d --- /dev/null +++ b/src/ansys/fluent/post/post_data_extractor.py @@ -0,0 +1,280 @@ +"""Module providing data extractor APIs.""" + +from typing import Dict + +from ansys.api.fluent.v0.field_data_pb2 import PayloadTag +import numpy as np + +from ansys.fluent.post.post_object_defns import GraphicsDefn, PlotDefn + + +class FieldDataExtractor: + """FieldData DataExtractor.""" + + def __init__(self, post_object: GraphicsDefn): + """Instantiate FieldData DataExtractor. + + Parameters + ---------- + post_object : GraphicsDefn + Graphics definition object for which data needs to be extracted. + """ + self._post_object: GraphicsDefn = post_object + + def fetch_data(self, *args, **kwargs): + """Fetch data for Graphics object. + + Parameters + ---------- + None + + Returns + ------- + Dict[int: Dict[str: np.array]] + Return dictionary of surfaces id to field name to numpy array. + """ + if self._post_object.__class__.__name__ == "Mesh": + return self._fetch_mesh_data(self._post_object, *args, **kwargs) + elif self._post_object.__class__.__name__ == "Surface": + return self._fetch_surface_data(self._post_object, *args, **kwargs) + elif self._post_object.__class__.__name__ == "Contour": + return self._fetch_contour_data(self._post_object, *args, **kwargs) + elif self._post_object.__class__.__name__ == "Vector": + return self._fetch_vector_data(self._post_object, *args, **kwargs) + + def _fetch_mesh_data(self, obj, *args, **kwargs): + if not obj.surfaces_list(): + raise RuntimeError("Mesh definition is incomplete.") + obj._pre_display() + field_info = obj._data_extractor.field_info() + field_data = obj._data_extractor.field_data() + surfaces_info = field_info.get_surfaces_info() + surface_ids = [ + id + for surf in map( + obj._data_extractor.remote_surface_name, obj.surfaces_list() + ) + for id in surfaces_info[surf]["surface_id"] + ] + + field_data.add_get_surfaces_request(surface_ids, *args, **kwargs) + surface_tag = 0 + surfaces_data = field_data.get_fields()[surface_tag] + obj._post_display() + return surfaces_data + + def _fetch_surface_data(self, obj, *args, **kwargs): + surface_api = obj._data_extractor.surface_api + surface_api.create_surface_on_server() + dummy_object = "dummy_object" + post_session = obj._get_top_most_parent() + if ( + obj.surface.type() == "iso-surface" + and obj.surface.iso_surface.rendering() == "contour" + ): + contour = post_session.Contours[dummy_object] + contour.field = obj.surface.iso_surface.field() + contour.surfaces_list = [obj._name] + contour.show_edges = True + contour.range.auto_range_on.global_range = True + surface_data = self._fetch_contour_data(contour) + del post_session.Contours[dummy_object] + else: + mesh = post_session.Meshes[dummy_object] + mesh.surfaces_list = [obj._name] + mesh.show_edges = True + surface_data = self._fetch_mesh_data(mesh) + surface_api.delete_surface_on_server() + return surface_data + + def _fetch_contour_data(self, obj, *args, **kwargs): + if not obj.surfaces_list() or not obj.field(): + raise RuntimeError("Contour definition is incomplete.") + + # contour properties + obj._pre_display() + field = obj.field() + range_option = obj.range.option() + filled = obj.filled() + contour_lines = obj.contour_lines() + node_values = obj.node_values() + boundary_values = obj.boundary_values() + + field_info = obj._data_extractor.field_info() + field_data = obj._data_extractor.field_data() + surfaces_info = field_info.get_surfaces_info() + surface_ids = [ + id + for surf in map( + obj._data_extractor.remote_surface_name, obj.surfaces_list() + ) + for id in surfaces_info[surf]["surface_id"] + ] + # get scalar field data + field_data.add_get_surfaces_request(surface_ids, *args, **kwargs) + field_data.add_get_scalar_fields_request( + surface_ids, + field, + node_values, + boundary_values, + ) + + location_tag = ( + field_data._payloadTags[PayloadTag.NODE_LOCATION] + if node_values + else field_data._payloadTags[PayloadTag.ELEMENT_LOCATION] + ) + boundary_value_tag = ( + field_data._payloadTags[PayloadTag.BOUNDARY_VALUES] + if boundary_values + else 0 + ) + surface_tag = 0 + + scalar_field_payload_data = field_data.get_fields() + data_tag = location_tag | boundary_value_tag + scalar_field_data = scalar_field_payload_data[data_tag] + surface_data = scalar_field_payload_data[surface_tag] + obj._post_display() + return self._merge(surface_data, scalar_field_data) + + def _fetch_vector_data(self, obj, *args, **kwargs): + + if not obj.surfaces_list(): + raise RuntimeError("Vector definition is incomplete.") + + obj._pre_display() + field_info = obj._data_extractor.field_info() + field_data = obj._data_extractor.field_data() + + # surface ids + surfaces_info = field_info.get_surfaces_info() + surface_ids = [ + id + for surf in map( + obj._data_extractor.remote_surface_name, obj.surfaces_list() + ) + for id in surfaces_info[surf]["surface_id"] + ] + + field_data.add_get_surfaces_request(surface_ids, *args, **kwargs) + field_data.add_get_vector_fields_request(surface_ids, obj.vectors_of()) + vector_field_tag = 0 + fields = field_data.get_fields()[vector_field_tag] + obj._post_display() + return fields + + def _merge(self, a, b): + if b is not None: + for k, v in a.items(): + if b.get(k): + a[k].update(b[k]) + del b[k] + a.update(b) + return a + + +class XYPlotDataExtractor: + """XYPlot DataExtractor.""" + + def __init__(self, post_object: PlotDefn): + """Instantiate XYPlot DataExtractor. + + Parameters + ---------- + post_object : PlotDefn + Plot definition object for which data needs to be extracted. + """ + self._post_object: PlotDefn = post_object + + def fetch_data(self) -> Dict[str, Dict[str, np.array]]: + """Fetch data for post object. + + Parameters + ---------- + None + + Returns + ------- + Dict[str: Dict[str: np.array]] + Return dictionary of surfaces name to numpy array of x and y values. + """ + + if self._post_object.__class__.__name__ == "XYPlot": + return self._fetch_xy_data(self._post_object) + + def _fetch_xy_data(self, obj): + obj._pre_display() + field = obj.y_axis_function() + node_values = obj.node_values() + boundary_values = obj.boundary_values() + direction_vector = obj.direction_vector() + surfaces_list = obj.surfaces_list() + field_info = obj._data_extractor.field_info() + field_data = obj._data_extractor.field_data() + surfaces_info = field_info.get_surfaces_info() + surface_ids = [ + id + for surf in map( + obj._data_extractor.remote_surface_name, obj.surfaces_list() + ) + for id in surfaces_info[surf]["surface_id"] + ] + + # get scalar field data + field_data.add_get_surfaces_request( + surface_ids, + provide_faces=False, + provide_vertices=True if node_values else False, + provide_faces_centroid=False if node_values else True, + ) + field_data.add_get_scalar_fields_request( + surface_ids, + field, + node_values, + boundary_values, + ) + + location_tag = ( + field_data._payloadTags[PayloadTag.NODE_LOCATION] + if node_values + else field_data._payloadTags[PayloadTag.ELEMENT_LOCATION] + ) + boundary_value_tag = ( + field_data._payloadTags[PayloadTag.BOUNDARY_VALUES] + if boundary_values + else 0 + ) + surface_tag = 0 + xyplot_payload_data = field_data.get_fields() + data_tag = location_tag | boundary_value_tag + xyplot_data = xyplot_payload_data[data_tag] + surface_data = xyplot_payload_data[surface_tag] + + # loop over all surfaces + xy_plots_data = {} + surfaces_list_iter = iter(surfaces_list) + for surface_id, mesh_data in surface_data.items(): + mesh_data["vertices" if node_values else "centroid"].shape = ( + mesh_data["vertices" if node_values else "centroid"].size // 3, + 3, + ) + y_values = xyplot_data[surface_id][field] + x_values = np.matmul( + mesh_data["vertices" if node_values else "centroid"], + direction_vector, + ) + structured_data = np.empty( + x_values.size, + dtype={ + "names": ("xvalues", "yvalues"), + "formats": ("f8", "f8"), + }, + ) + structured_data["xvalues"] = x_values + structured_data["yvalues"] = y_values + sort = np.argsort(structured_data, order=["xvalues"]) + surface_name = next(surfaces_list_iter) + xy_plots_data[surface_name] = structured_data[sort] + obj._post_display() + return xy_plots_data diff --git a/src/ansys/fluent/post/post_object_defns.py b/src/ansys/fluent/post/post_object_defns.py index b6c49e77..ca4837c5 100644 --- a/src/ansys/fluent/post/post_object_defns.py +++ b/src/ansys/fluent/post/post_object_defns.py @@ -68,6 +68,22 @@ class Vector(NamedTuple): z: float +class MonitorDefn(PlotDefn): + """Monitor Definition.""" + + PLURAL = "Monitors" + + class monitor_set_name(metaclass=PyLocalPropertyMeta): + """Monitor set name.""" + + value: str + + @Attribute + def allowed_values(self): + """Monitor set allowed values.""" + return self._data_extractor.monitors_manager().get_monitor_set_names() + + class XYPlotDefn(PlotDefn): """XYPlot Definition.""" diff --git a/src/ansys/fluent/post/pyvista/pyvista_objects.py b/src/ansys/fluent/post/pyvista/pyvista_objects.py index 34369d4e..0d82abb1 100644 --- a/src/ansys/fluent/post/pyvista/pyvista_objects.py +++ b/src/ansys/fluent/post/pyvista/pyvista_objects.py @@ -54,6 +54,30 @@ def _init_module(self, obj, mod): PyLocalContainer(self, cls), ) + def add_outline_mesh(self): + """Add mesh outline. + + Parameters + ---------- + None + + Returns + ------- + None + """ + meshes = getattr(self, "Meshes", None) + if meshes is not None: + outline_mesh_id = "Mesh-outline" + outline_mesh = meshes[outline_mesh_id] + outline_mesh.surfaces_list = [ + k + for k, v in outline_mesh._data_extractor.field_info() + .get_surfaces_info() + .items() + if v["type"] == "zone-surf" and v["zone_type"] != "interior" + ] + return outline_mesh + class Mesh(MeshDefn): """Mesh graphics.""" @@ -66,9 +90,7 @@ def display(self, window_id: Optional[str] = None): window_id : str, optional Window id. If not specified unique id is used. """ - self._pre_display() pyvista_windows_manager.plot(self, window_id) - self._post_display() class Surface(SurfaceDefn): @@ -96,9 +118,7 @@ def display(self, window_id: Optional[str] = None): window_id : str, optional Window id. If not specified unique id is used. """ - self._pre_display() pyvista_windows_manager.plot(self, window_id) - self._post_display() class Vector(VectorDefn): @@ -112,6 +132,4 @@ def display(self, window_id: Optional[str] = None): window_id : str, optional Window id. If not specified unique id is used. """ - self._pre_display() pyvista_windows_manager.plot(self, window_id) - self._post_display() diff --git a/src/ansys/fluent/post/pyvista/pyvista_windows_manager.py b/src/ansys/fluent/post/pyvista/pyvista_windows_manager.py index 7b3c9b69..c28c0c04 100644 --- a/src/ansys/fluent/post/pyvista/pyvista_windows_manager.py +++ b/src/ansys/fluent/post/pyvista/pyvista_windows_manager.py @@ -3,7 +3,6 @@ import threading from typing import List, Optional, Union -from ansys.api.fluent.v0.field_data_pb2 import PayloadTag from ansys.fluent.core.session import Session from ansys.fluent.core.utils.generic import AbstractSingletonMeta, in_notebook import numpy as np @@ -11,24 +10,25 @@ from pyvistaqt import BackgroundPlotter from ansys.fluent.post import get_config -from ansys.fluent.post.post_object_defns import GraphicsDefn, PlotDefn +from ansys.fluent.post.post_data_extractor import FieldDataExtractor +from ansys.fluent.post.post_object_defns import GraphicsDefn from ansys.fluent.post.post_windows_manager import PostWindow, PostWindowsManager class PyVistaWindow(PostWindow): """Class for PyVista window.""" - def __init__(self, id: str, post_object: Union[GraphicsDefn, PlotDefn]): + def __init__(self, id: str, post_object: GraphicsDefn): """Instantiate a PyVistaWindow. Parameters ---------- id : str Window id. - post_object : Union[GraphicsDefn, PlotDefn] + post_object : GraphicsDefn Object to draw. """ - self.post_object: Union[GraphicsDefn, PlotDefn] = post_object + self.post_object: GraphicsDefn = post_object self.id: str = id self.plotter: Union[BackgroundPlotter, pv.Plotter] = ( pv.Plotter(title=f"PyFluent ({self.id})") @@ -102,11 +102,8 @@ def _scalar_bar_default_properties(self) -> dict: def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - if not obj.surfaces_list(): - raise RuntimeError("Vector definition is incomplete.") - + vector_field_data = FieldDataExtractor(obj).fetch_data() field_info = obj._data_extractor.field_info() - field_data = obj._data_extractor.field_data() # surface ids surfaces_info = field_info.get_surfaces_info() @@ -118,16 +115,12 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): for id in surfaces_info[surf]["surface_id"] ] - # field - field = "velocity-magnitude" - # scalar bar properties scalar_bar_args = self._scalar_bar_default_properties() - field_data.add_get_surfaces_request(surface_ids) - field_data.add_get_vector_fields_request(surface_ids, obj.vectors_of()) - vector_field_tag = 0 - vector_field_data = field_data.get_fields()[vector_field_tag] + # field + field = "velocity-magnitude" + for surface_id, mesh_data in vector_field_data.items(): mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 mesh_data[obj.vectors_of()].shape = ( @@ -184,9 +177,6 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): plotter.add_mesh(mesh, show_edges=True, color="white") def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - if not obj.surfaces_list() or not obj.field(): - raise RuntimeError("Contour definition is incomplete.") - # contour properties field = obj.field() range_option = obj.range.option() @@ -197,61 +187,26 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): # scalar bar properties scalar_bar_args = self._scalar_bar_default_properties() - - field_info = obj._data_extractor.field_info() - field_data = obj._data_extractor.field_data() - surfaces_info = field_info.get_surfaces_info() - surface_ids = [ - id - for surf in map( - obj._data_extractor.remote_surface_name, obj.surfaces_list() - ) - for id in surfaces_info[surf]["surface_id"] - ] - # get scalar field data - field_data.add_get_surfaces_request(surface_ids) - field_data.add_get_scalar_fields_request( - surface_ids, - field, - node_values, - boundary_values, - ) - - location_tag = ( - field_data._payloadTags[PayloadTag.NODE_LOCATION] - if node_values - else field_data._payloadTags[PayloadTag.ELEMENT_LOCATION] - ) - boundary_value_tag = ( - field_data._payloadTags[PayloadTag.BOUNDARY_VALUES] - if boundary_values - else 0 - ) - surface_tag = 0 - - scalar_field_payload_data = field_data.get_fields() - data_tag = location_tag | boundary_value_tag - scalar_field_data = scalar_field_payload_data[data_tag] - surface_data = scalar_field_payload_data[surface_tag] + scalar_field_data = FieldDataExtractor(obj).fetch_data() # loop over all meshes - for surface_id, mesh_data in surface_data.items(): - mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 - topology = "line" if mesh_data["faces"][0] == 2 else "face" + for surface_id, surface_data in scalar_field_data.items(): + 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( - mesh_data["vertices"], - lines=mesh_data["faces"], + surface_data["vertices"], + lines=surface_data["faces"], ) else: mesh = pv.PolyData( - mesh_data["vertices"], - faces=mesh_data["faces"], + surface_data["vertices"], + faces=surface_data["faces"], ) if node_values: - mesh.point_data[field] = scalar_field_data[surface_id][field] + mesh.point_data[field] = surface_data[field] else: - mesh.cell_data[field] = scalar_field_data[surface_id][field] + mesh.cell_data[field] = surface_data[field] if range_option == "auto-range-off": auto_range_off = obj.range.auto_range_off if auto_range_off.clip_to_range(): @@ -299,6 +254,7 @@ def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): auto_range_on = obj.range.auto_range_on if auto_range_on.global_range(): if filled: + field_info = obj._data_extractor.field_info() plotter.add_mesh( mesh, clim=field_info.get_range(field, False), @@ -349,23 +305,8 @@ def _display_surface(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): surface_api.delete_surface_on_server() def _display_mesh(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): - if not obj.surfaces_list(): - raise RuntimeError("Mesh definition is incomplete.") - field_info = obj._data_extractor.field_info() - field_data = obj._data_extractor.field_data() - surfaces_info = field_info.get_surfaces_info() - surface_ids = [ - id - for surf in map( - obj._data_extractor.remote_surface_name, obj.surfaces_list() - ) - for id in surfaces_info[surf]["surface_id"] - ] - - field_data.add_get_surfaces_request(surface_ids) - surface_tag = 0 - surfaces_data = field_data.get_fields()[surface_tag] + surfaces_data = FieldDataExtractor(obj).fetch_data() for surface_id, mesh_data in surfaces_data.items(): mesh_data["vertices"].shape = mesh_data["vertices"].size // 3, 3 topology = "line" if mesh_data["faces"][0] == 2 else "face" @@ -410,7 +351,7 @@ def __init__(self): """Instantiate WindowManager for PyVista.""" self._post_windows: Dict[str:PyVistaWindow] = {} self._plotter_thread: threading.Thread = None - self._post_object: Union[GraphicsDefn, PlotDefn] = None + self._post_object: GraphicsDefn = None self._window_id: str = None self._exit_thread: bool = False self._app = None @@ -453,14 +394,12 @@ def open_window(self, window_id: Optional[str] = None) -> str: self._open_and_plot_console(None, window_id) return window_id - def set_object_for_window( - self, object: Union[GraphicsDefn, PlotDefn], window_id: str - ) -> None: + def set_object_for_window(self, object: GraphicsDefn, window_id: str) -> None: """Associate post object with running window instance. Parameters ---------- - object : Union[GraphicsDefn, PlotDefn] + object : GraphicsDefn Post object to associate with window. window_id : str @@ -478,14 +417,12 @@ def set_object_for_window( if window: window.post_object = object - def plot( - self, object: Union[GraphicsDefn, PlotDefn], window_id: Optional[str] = None - ) -> None: + def plot(self, object: GraphicsDefn, window_id: Optional[str] = None) -> None: """Draw plot. Parameters ---------- - object: Union[GraphicsDefn, PlotDefn] + object: GraphicsDefn Object to plot. window_id : str, optional