From 6392b1461ae570bae8be3471e1ce4468b04ccda0 Mon Sep 17 00:00:00 2001 From: Aseem Jain Date: Fri, 30 Sep 2022 12:46:04 +0530 Subject: [PATCH] Pathlines and Request info. --- doc/source/api/visualization/graphics.rst | 4 +- doc/source/api/visualization/pathlines.rst | 6 + doc/styles/Vocab/ANSYS/accept.txt | 4 +- .../post_processing_exhaust_manifold.py | 11 ++ pyproject.toml | 2 +- .../visualization/matplotlib/plotter_defns.py | 1 - .../visualization/post_data_extractor.py | 115 +++++++++++++++--- .../fluent/visualization/post_object_defns.py | 38 ++++++ .../visualization/pyvista/pyvista_objects.py | 27 ++++ .../pyvista/pyvista_windows_manager.py | 53 ++++++-- 10 files changed, 231 insertions(+), 30 deletions(-) create mode 100644 doc/source/api/visualization/pathlines.rst diff --git a/doc/source/api/visualization/graphics.rst b/doc/source/api/visualization/graphics.rst index dc342e95..94452ff3 100644 --- a/doc/source/api/visualization/graphics.rst +++ b/doc/source/api/visualization/graphics.rst @@ -19,6 +19,7 @@ contour, vector, and surface. The contour is then deleted. contour1 = graphics_session.Contours["contour-1"] vector1 = graphics_session.Vectors["vector-1"] surface1 = graphics_session.Surfaces["surface-1"] + pathlines1 = graphics_session.Pathlines["pathlines-1"] #Delete object del graphics_session.Contours["contour-1"] @@ -30,4 +31,5 @@ contour, vector, and surface. The contour is then deleted. mesh surface contour - vector \ No newline at end of file + vector + pathlines \ No newline at end of file diff --git a/doc/source/api/visualization/pathlines.rst b/doc/source/api/visualization/pathlines.rst new file mode 100644 index 00000000..1bf530c0 --- /dev/null +++ b/doc/source/api/visualization/pathlines.rst @@ -0,0 +1,6 @@ +.. _ref_pathlines: + +Pathlines +========= + +.. autopostdoc:: ansys.fluent.visualization.pyvista.pyvista_objects.Pathlines diff --git a/doc/styles/Vocab/ANSYS/accept.txt b/doc/styles/Vocab/ANSYS/accept.txt index 6efbd6a6..d4378271 100644 --- a/doc/styles/Vocab/ANSYS/accept.txt +++ b/doc/styles/Vocab/ANSYS/accept.txt @@ -25,4 +25,6 @@ postprocess Pythonic PyVista Util -XY \ No newline at end of file +XY +pathlines +Pathlines \ No newline at end of file diff --git a/examples/00-postprocessing/post_processing_exhaust_manifold.py b/examples/00-postprocessing/post_processing_exhaust_manifold.py index 7f2d7e60..616b70a2 100644 --- a/examples/00-postprocessing/post_processing_exhaust_manifold.py +++ b/examples/00-postprocessing/post_processing_exhaust_manifold.py @@ -193,10 +193,21 @@ # Create a vector on a predefined surface. velocity_vector = graphics.Vectors["velocity-vector"] +velocity_vector.field = "pressure" velocity_vector.surfaces_list = ["solid_up:1:830"] velocity_vector.scale = 2 velocity_vector.display("window-8") +############################################################################### +# Create Pathlines +# ~~~~~~~~~~~~~~~~ +# Create a pathlines on a predefined surface. + +pathlines = graphics.Pathlines["pathlines"] +pathlines.field = "velocity-magnitude" +pathlines.surfaces_list = ["inlet", "inlet1", "inlet2"] +# pathlines.display("window-9") + ############################################################################### # Create plot object # ~~~~~~~~~~~~~~~~~~ diff --git a/pyproject.toml b/pyproject.toml index 72832846..3d8c581a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ packages = [ [tool.poetry.dependencies] python = ">=3.7,<4.0" importlib-metadata = {version = "^4.0", python = "<3.8"} -ansys-fluent-core = "~=0.12.dev0" +ansys-fluent-core = "~=0.12.dev5" vtk = {version = ">=9.0.3", python = "<=3.9"} ipyvtklink = ">=0.2.2" pyvista = ">=0.33.2" diff --git a/src/ansys/fluent/visualization/matplotlib/plotter_defns.py b/src/ansys/fluent/visualization/matplotlib/plotter_defns.py index e0fb193a..c8f91707 100644 --- a/src/ansys/fluent/visualization/matplotlib/plotter_defns.py +++ b/src/ansys/fluent/visualization/matplotlib/plotter_defns.py @@ -67,7 +67,6 @@ def plot(self, data: dict) -> None: """ if not data: return - for curve in data: min_y_value = np.amin(data[curve]["yvalues"]) max_y_value = np.amax(data[curve]["yvalues"]) diff --git a/src/ansys/fluent/visualization/post_data_extractor.py b/src/ansys/fluent/visualization/post_data_extractor.py index 64221c4b..8af963d4 100644 --- a/src/ansys/fluent/visualization/post_data_extractor.py +++ b/src/ansys/fluent/visualization/post_data_extractor.py @@ -3,8 +3,11 @@ import itertools from typing import Dict -from ansys.api.fluent.v0.field_data_pb2 import PayloadTag -from ansys.fluent.core.services.field_data import _FieldDataConstants +from ansys.api.fluent.v0.field_data_pb2 import DataLocation, PayloadTag +from ansys.fluent.core.services.field_data import ( + _FieldDataConstants, + merge_pathlines_data, +) import numpy as np from ansys.fluent.visualization.post_object_defns import GraphicsDefn, PlotDefn @@ -43,6 +46,8 @@ def fetch_data(self, *args, **kwargs): 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) + elif self._post_object.__class__.__name__ == "Pathlines": + return self._fetch_pathlines_data(self._post_object, *args, **kwargs) def _fetch_mesh_data(self, obj, *args, **kwargs): if not obj.surfaces_list(): @@ -59,9 +64,10 @@ def _fetch_mesh_data(self, obj, *args, **kwargs): ] transaction.add_surfaces_request(surface_ids, *args, **kwargs) - surface_tag = 0 try: - surfaces_data = transaction.get_fields()[surface_tag] + fields = transaction.get_fields() + # 0 is old tag + surfaces_data = fields.get(0) or fields[(("type", "surface-data"),)] except: raise RuntimeError("Error while requesting data from server.") finally: @@ -133,24 +139,72 @@ def _fetch_contour_data(self, obj, *args, **kwargs): if boundary_values else 0 ) - surface_tag = 0 + try: - scalar_field_payload_data = transaction.get_fields() + fields = transaction.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] - except: + scalar_field_data = ( + fields.get(data_tag) + or fields[ + ( + ("type", "scalar-field"), + ( + "dataLocation", + DataLocation.Nodes + if node_values + else DataLocation.Elements, + ), + ("boundaryValues", boundary_values), + ) + ] + ) + surface_data = fields.get(0) or fields[(("type", "surface-data"),)] + except Exception: raise RuntimeError("Error while requesting data from server.") finally: obj._post_display() return self._merge(surface_data, scalar_field_data) + def _fetch_pathlines_data(self, obj, *args, **kwargs): + if not obj.surfaces_list() or not obj.field(): + raise RuntimeError("Ptahline definition is incomplete.") + + obj._pre_display() + field = obj.field() + surfaces_list = obj.surfaces_list() + + field_info = obj._api_helper.field_info() + field_data = obj._api_helper.field_data() + surfaces_info = field_info.get_surfaces_info() + transaction = field_data.new_transaction() + surface_ids = [ + id + for surf in map(obj._api_helper.remote_surface_name, obj.surfaces_list()) + for id in surfaces_info[surf]["surface_id"] + ] + transaction.add_pathlines_fields_request( + surface_ids=surface_ids, field_name=field + ) + + try: + fields = transaction.get_fields() + pathlines_data = fields[(("type", "pathlines-field"), ("field", field))] + data = merge_pathlines_data(pathlines_data, field) + except Exception as e: + raise RuntimeError("Error while requesting data from server." + str(e)) + finally: + obj._post_display() + return data + def _fetch_vector_data(self, obj, *args, **kwargs): if not obj.surfaces_list(): raise RuntimeError("Vector definition is incomplete.") obj._pre_display() + field = obj.field() + if not field: + field = obj.field = "velocity-magnitude" field_info = obj._api_helper.field_info() field_data = obj._api_helper.field_data() @@ -165,19 +219,42 @@ def _fetch_vector_data(self, obj, *args, **kwargs): ] transaction.add_surfaces_request(surface_ids=surface_ids, *args, **kwargs) + transaction.add_scalar_fields_request( + surface_ids=surface_ids, + field_name=field, + node_value=False, + boundary_value=False, + ) transaction.add_vector_fields_request( - field_name=obj.vectors_of(), surface_ids=surface_ids + surface_ids=surface_ids, field_name=obj.vectors_of() ) - vector_field_tag = 0 try: - fields = transaction.get_fields()[vector_field_tag] + fields = transaction.get_fields() + vector_field = fields.get(0) or fields[(("type", "vector-field"),)] + scalar_field = ( + fields.get(_FieldDataConstants.payloadTags[PayloadTag.ELEMENT_LOCATION]) + or fields[ + ( + ("type", "scalar-field"), + ( + "dataLocation", + DataLocation.Elements, + ), + ("boundaryValues", False), + ) + ] + ) + surface_data = fields.get(0) or fields[(("type", "surface-data"),)] except: raise RuntimeError("Error while requesting data from server.") finally: obj._post_display() - return fields + data = self._merge(surface_data, vector_field) + return self._merge(data, scalar_field) def _merge(self, a, b): + if a is b: + return a if b is not None: for k, v in a.items(): if b.get(k): @@ -283,7 +360,17 @@ def _fetch_xy_data(self, obj): xyplot_payload_data = transaction.get_fields() data_tag = location_tag | boundary_value_tag if data_tag not in xyplot_payload_data: - raise RuntimeError("Plot surface is not valid.") + data_tag = ( + ("type", "scalar-field"), + ( + "dataLocation", + DataLocation.Nodes if node_values else DataLocation.Elements, + ), + ("boundaryValues", boundary_values), + ) + surface_tag = (("type", "surface-data"),) + if data_tag not in xyplot_payload_data: + raise RuntimeError("Plot surface is not valid.") xyplot_data = xyplot_payload_data[data_tag] surface_data = xyplot_payload_data[surface_tag] diff --git a/src/ansys/fluent/visualization/post_object_defns.py b/src/ansys/fluent/visualization/post_object_defns.py index 9a57f100..07fa46a5 100644 --- a/src/ansys/fluent/visualization/post_object_defns.py +++ b/src/ansys/fluent/visualization/post_object_defns.py @@ -161,6 +161,34 @@ class show_edges(metaclass=PyLocalPropertyMeta): value: bool = False +class PathlinesDefn(GraphicsDefn): + """Pathlines definition.""" + + PLURAL = "Pathlines" + + class field(metaclass=PyLocalPropertyMeta): + """Pathlines field.""" + + value: str + + @Attribute + def allowed_values(self): + """Field allowed values.""" + return list(self._api_helper.field_info().get_fields_info()) + + class surfaces_list(metaclass=PyLocalPropertyMeta): + """List of surfaces for pathlines.""" + + value: List[str] + + @Attribute + def allowed_values(self): + """Surface list allowed values.""" + return list( + (self._api_helper.field_info().get_surfaces_info().keys()) + ) + list(self._get_top_most_parent()._local_surfaces_provider()) + + class SurfaceDefn(GraphicsDefn): """Surface graphics definition.""" @@ -488,6 +516,16 @@ def allowed_values(self): """Vectors of allowed values.""" return list(self._api_helper.get_vector_fields()) + class field(metaclass=PyLocalPropertyMeta): + """Vector color field.""" + + value: str + + @Attribute + def allowed_values(self): + """Field allowed values.""" + return list(self._api_helper.field_info().get_fields_info()) + class surfaces_list(metaclass=PyLocalPropertyMeta): """List of surfaces for vector graphics.""" diff --git a/src/ansys/fluent/visualization/pyvista/pyvista_objects.py b/src/ansys/fluent/visualization/pyvista/pyvista_objects.py index a69186f7..905ed228 100644 --- a/src/ansys/fluent/visualization/pyvista/pyvista_objects.py +++ b/src/ansys/fluent/visualization/pyvista/pyvista_objects.py @@ -9,6 +9,7 @@ from ansys.fluent.visualization.post_object_defns import ( ContourDefn, MeshDefn, + PathlinesDefn, SurfaceDefn, VectorDefn, ) @@ -142,6 +143,32 @@ def display(self, window_id: Optional[str] = None): pyvista_windows_manager.plot(self, window_id) +class Pathlines(PathlinesDefn): + """Pathlines definition for PyVista. + + .. code-block:: python + + from ansys.fluent.visualization.pyvista import Graphics + + graphics_session = Graphics(session) + pathlines1 = graphics_session.Pathlines["pathlines-1"] + pathlines1.field = "velocity-magnitude" + pathlines1.surfaces_list = ['inlet'] + pathlines1.display("window-0") + """ + + def display(self, window_id: Optional[str] = None): + """Display mesh graphics. + + Parameters + ---------- + window_id : str, optional + Window ID. If an ID is not specified, a unique ID is used. + The default is ``None``. + """ + pyvista_windows_manager.plot(self, window_id) + + class Surface(SurfaceDefn): """Provides for displaying surface graphics. diff --git a/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py b/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py index 7bfc7036..a47735e4 100644 --- a/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py +++ b/src/ansys/fluent/visualization/pyvista/pyvista_windows_manager.py @@ -64,6 +64,8 @@ def plot(self): self._display_contour(obj, plotter) elif obj.__class__.__name__ == "Vector": self._display_vector(obj, plotter) + elif obj.__class__.__name__ == "Pathlines": + self._display_pathlines(obj, plotter) if self.animate: plotter.write_frame() view = get_config()["set_view_on_display"] @@ -111,16 +113,9 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): # scalar bar properties scalar_bar_args = self._scalar_bar_default_properties() - # field - field = ( - "rel-velocity-magnitude" - if "relative" in vectors_of - else "velocity-magnitude" - ) - - phases = list(filter(vectors_of.startswith, obj._api_helper._get_phases())) - if phases: - field = f"{phases[0]}-{field}" + field = obj.field() + field_unit = obj._api_helper.get_field_unit(field) + field = f"{field}\n[{field_unit}]" if field_unit else field for surface_id, mesh_data in vector_field_data.items(): if "vertices" not in mesh_data or "faces" not in mesh_data: @@ -143,6 +138,7 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): faces=mesh_data["faces"], ) mesh.cell_data["vectors"] = mesh_data[obj.vectors_of()] + scalar_field = mesh_data[obj.field()] velocity_magnitude = np.linalg.norm(mesh_data[obj.vectors_of()], axis=1) if obj.range.option() == "auto-range-off": auto_range_off = obj.range.auto_range_off @@ -156,15 +152,16 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): else: auto_range_on = obj.range.auto_range_on if auto_range_on.global_range(): - range = field_info.get_range(field, False) + range = field_info.get_range(obj.field(), False) else: - range = [np.min(velocity_magnitude), np.max(velocity_magnitude)] + range = [np.min(scalar_field), np.max(scalar_field)] if obj.skip(): vmag = np.zeros(velocity_magnitude.size) vmag[:: obj.skip() + 1] = velocity_magnitude[:: obj.skip() + 1] velocity_magnitude = vmag mesh.cell_data["Velocity Magnitude"] = velocity_magnitude + mesh.cell_data[field] = scalar_field glyphs = mesh.glyph( orient="vectors", scale="Velocity Magnitude", @@ -173,12 +170,44 @@ def _display_vector(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): ) plotter.add_mesh( glyphs, + scalars=field, scalar_bar_args=scalar_bar_args, clim=range, ) if obj.show_edges(): plotter.add_mesh(mesh, show_edges=True, color="white") + def _display_pathlines(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): + field = obj.field() + field_unit = obj._api_helper.get_field_unit(field) + field = f"{field}\n[{field_unit}]" if field_unit else field + node_values = True + + # scalar bar properties + scalar_bar_args = self._scalar_bar_default_properties() + data = FieldDataExtractor(obj).fetch_data() + + # loop over all meshes + for surface_id, surface_data in data.items(): + if "vertices" not in surface_data or "faces" not in surface_data: + continue + surface_data["vertices"].shape = surface_data["vertices"].size // 3, 3 + + mesh = pv.PolyData( + surface_data["vertices"], + lines=surface_data["faces"], + ) + + if node_values: + mesh.point_data[field] = surface_data[obj.field()] + else: + mesh.cell_data[field] = surface_data[obj.field()] + plotter.add_mesh( + mesh, + scalars=field, + scalar_bar_args=scalar_bar_args, + ) + def _display_contour(self, obj, plotter: Union[BackgroundPlotter, pv.Plotter]): # contour properties field = obj.field()