diff --git a/examples/06-plotting/08_plot_3d_streamlines.py b/examples/12-streamlines/00_plot_3d_streamlines.py similarity index 72% rename from examples/06-plotting/08_plot_3d_streamlines.py rename to examples/12-streamlines/00_plot_3d_streamlines.py index 233fe5da27b..bffe68876f1 100644 --- a/examples/06-plotting/08_plot_3d_streamlines.py +++ b/examples/12-streamlines/00_plot_3d_streamlines.py @@ -1,16 +1,17 @@ """ .. _plot_3d_streamlines: -Plot 3D streamlines -~~~~~~~~~~~~~~~~~~~ -This example shows you how to plot streamlines of fluid simulation results, +Compute and plot 3D streamlines +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This example shows you how to compute and +plot streamlines of fluid simulation results, for 3D models. """ ############################################################################### -# Plot streamlines from single source -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Compute and plot streamlines from single source +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ############################################################################### # Import modules, create the data sources and the model @@ -19,6 +20,7 @@ from ansys.dpf import core as dpf from ansys.dpf.core import examples +from ansys.dpf.core.helpers.streamlines import compute_streamlines from ansys.dpf.core.plotter import DpfPlotter ############################################################################### @@ -38,25 +40,29 @@ # Meshed region is used as geometric base to compute the streamlines. # Velocity data is used to compute the streamlines. The velocity data must be nodal. +############################################################################### # Get the meshed region: meshed_region = m_fluent.metadata.meshed_region +############################################################################### # Get the velocity result at nodes: velocity_op = m_fluent.results.velocity() fc = velocity_op.outputs.fields_container() field = dpf.operators.averaging.to_nodal_fc(fields_container=fc).outputs.fields_container()[0] ############################################################################### -# Plot the streamlines adjusting the request -# ------------------------------------------ +# Compute and plot the streamlines adjusting the request +# ------------------------------------------------------ # The following steps show you how to create streamlines using DpfPlotter, with several sets # of parameters. It demonstrates the issues that can happen and the adjustments that you can make. +############################################################################### # First, a DpfPlotter is created and the streamline is created with default values. pl0 = DpfPlotter() try: + streamline_obj = compute_streamlines(meshed_region=meshed_region, field=field) pl0.add_mesh(meshed_region=meshed_region, opacity=0.3) - pl0.add_streamlines(meshed_region=meshed_region, field=field) + pl0.add_streamlines(streamlines=streamline_obj) pl0.show_figure(show_axes=True) except: # It throws an error and ends here, because source points are not set correctly. @@ -64,22 +70,28 @@ # the streamlines source center. pass +############################################################################### # Then, you can correctly set the source coordinates using the # "source_center" argument that moves the source center, # the "return_source" arguments that displays the source, and # "permissive" option that allows you to display the source even, if the computed # streamline size is zero. -pl1 = DpfPlotter() -pl1.add_mesh(meshed_region=meshed_region, opacity=0.3) -pl1.add_streamlines( +streamline_obj, source_obj = compute_streamlines( meshed_region=meshed_region, field=field, return_source=True, source_center=(0.1, 0.1, 0.2), +) +pl1 = DpfPlotter() +pl1.add_mesh(meshed_region=meshed_region, opacity=0.3) +pl1.add_streamlines( + streamlines=streamline_obj, + source=source_obj, permissive=True, ) pl1.show_figure(show_axes=True) +############################################################################### # After the adjustment, the correct values for the "source_center" argument are set. # You can remove the "permissive" option. # You can display velocity data with a small opacity value to avoid hiding the streamlines. @@ -89,23 +101,27 @@ # - n_points: source number of points # - source_radius # - max_time: allows the streamline to be computed along a certain length -pl2 = DpfPlotter() -pl2.add_field(field, meshed_region, opacity=0.2) -pl2.add_streamlines( +streamline_obj, source_obj = compute_streamlines( meshed_region=meshed_region, field=field, return_source=True, source_center=(0.56, 0.48, 0.0), - radius=0.001, n_points=10, source_radius=0.075, max_time=10.0, ) +pl2 = DpfPlotter() +pl2.add_field(field, meshed_region, opacity=0.2) +pl2.add_streamlines( + streamlines=streamline_obj, + source=source_obj, + radius=0.001, +) pl2.show_figure(show_axes=True) ############################################################################### -# Plot streamlines from several sources -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Compute and plot streamlines from several sources +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ############################################################################### # Get data to plot @@ -128,44 +144,64 @@ field = velocity_op.outputs.fields_container()[0] ############################################################################### -# Compute streamlines from different sources and plot -# --------------------------------------------------- +# Compute streamlines from different sources +# ------------------------------------------ -# Add streamlines from different sources: -pl = DpfPlotter() -pl.add_field(field, meshed_region, opacity=0.2) -pl.add_streamlines( +############################################################################### +# Compute streamlines from different sources: +streamline_1, source_1 = compute_streamlines( meshed_region=meshed_region, field=field, - radius=0.007, return_source=True, source_radius=0.25, source_center=(0.75, 0.0, 0.0), ) -pl.add_streamlines( +streamline_2, source_2 = compute_streamlines( meshed_region=meshed_region, field=field, - radius=0.007, return_source=True, source_radius=0.25, source_center=(0.0, 0.75, 0.0), ) -pl.add_streamlines( +streamline_3, source_3 = compute_streamlines( meshed_region=meshed_region, field=field, - radius=0.007, return_source=True, source_radius=0.25, source_center=(-0.75, 0.0, 0.0), ) -pl.add_streamlines( +streamline_4, source_4 = compute_streamlines( meshed_region=meshed_region, field=field, - radius=0.007, return_source=True, source_radius=0.25, source_center=(0.0, -0.75, 0.0), ) -# Plot: +############################################################################### +# Plot streamlines from different sources +# --------------------------------------- + +pl = DpfPlotter() +pl.add_field(field, meshed_region, opacity=0.2) +pl.add_streamlines( + streamlines=streamline_1, + source=source_1, + radius=0.007, +) +pl.add_streamlines( + streamlines=streamline_2, + source=source_2, + radius=0.007, +) +pl.add_streamlines( + streamlines=streamline_3, + source=source_3, + radius=0.007, +) +pl.add_streamlines( + streamlines=streamline_4, + source=source_4, + radius=0.007, +) pl.show_figure(show_axes=True) diff --git a/examples/06-plotting/09_plot_surface_streamlines.py b/examples/12-streamlines/01_plot_surface_streamlines.py similarity index 73% rename from examples/06-plotting/09_plot_surface_streamlines.py rename to examples/12-streamlines/01_plot_surface_streamlines.py index 09b95a892c8..71d9f9b4d63 100644 --- a/examples/06-plotting/09_plot_surface_streamlines.py +++ b/examples/12-streamlines/01_plot_surface_streamlines.py @@ -1,10 +1,10 @@ """ .. _plot_surf_streamlines: -Plot 2D streamlines -~~~~~~~~~~~~~~~~~~~ -This example shows you how to plot streamlines of fluid simulation results, -for 2D models. +Compute and plot 2D streamlines +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This example shows you how to compute and plot +streamlines of fluid simulation results, for 2D models. """ @@ -19,6 +19,7 @@ from ansys.dpf import core as dpf from ansys.dpf.core import examples +from ansys.dpf.core.helpers.streamlines import compute_streamlines from ansys.dpf.core.plotter import DpfPlotter ############################################################################### @@ -38,14 +39,28 @@ # Meshed region is used as the geometric base to compute the streamlines. # Velocity data is used to compute the streamlines. The velocity data must be nodal. +############################################################################### # Get the meshed region: meshed_region = m_fluent.metadata.meshed_region +############################################################################### # Get the velocity result at nodes: velocity_op = m_fluent.results.velocity() fc = velocity_op.outputs.fields_container() field = dpf.operators.averaging.to_nodal_fc(fields_container=fc).outputs.fields_container()[0] +############################################################################### +# Compute single streamline +# ------------------------- + +single_2d_streamline, single_2d_source = compute_streamlines( + meshed_region=meshed_region, + field=field, + start_position=(0.005, 0.0005, 0.0), + surface_streamlines=True, + return_source=True, +) + ############################################################################### # Plot single streamline # ---------------------- @@ -53,14 +68,25 @@ pl_single = DpfPlotter() pl_single.add_field(field, meshed_region, opacity=0.2) pl_single.add_streamlines( + streamlines=single_2d_streamline, + source=single_2d_source, + radius=0.00002, +) +pl_single.show_figure(show_axes=True) + +############################################################################### +# Compute multiple streamlines +# ---------------------------- +multiple_2d_streamlines, multiple_2d_source = compute_streamlines( meshed_region=meshed_region, field=field, - return_source=True, - radius=0.00002, - start_position=(0.005, 0.0005, 0.0), + pointa=(0.005, 0.0001, 0.0), + pointb=(0.005, 0.001, 0.0), + n_points=10, surface_streamlines=True, + return_source=True, ) -pl_single.show_figure(show_axes=True) + ############################################################################### # Plot multiple streamlines @@ -69,13 +95,8 @@ pl_multiple = DpfPlotter() pl_multiple.add_field(field, meshed_region, opacity=0.2) pl_multiple.add_streamlines( - meshed_region=meshed_region, - field=field, - return_source=True, + streamlines=multiple_2d_streamlines, + source=multiple_2d_source, radius=0.000015, - pointa=(0.005, 0.0001, 0.0), - pointb=(0.005, 0.001, 0.0), - n_points=10, - surface_streamlines=True, ) pl_multiple.show_figure(plane="xy", show_axes=True) diff --git a/examples/12-streamlines/README.txt b/examples/12-streamlines/README.txt new file mode 100644 index 00000000000..93b07a71867 --- /dev/null +++ b/examples/12-streamlines/README.txt @@ -0,0 +1,6 @@ +.. _examples_streamlines: + +Streamlines examples +==================== +These examples show how compute and plot streamlines. + diff --git a/src/ansys/dpf/core/animator.py b/src/ansys/dpf/core/animator.py index 1c8c065953e..a799a8c5e2e 100644 --- a/src/ansys/dpf/core/animator.py +++ b/src/ansys/dpf/core/animator.py @@ -9,7 +9,8 @@ from typing import Union, Sequence import ansys.dpf.core as core -from ansys.dpf.core.plotter import _sort_supported_kwargs, _PyVistaPlotter +from ansys.dpf.core.helpers.utils import _sort_supported_kwargs +from ansys.dpf.core.plotter import _PyVistaPlotter class _InternalAnimatorFactory: diff --git a/src/ansys/dpf/core/helpers/streamlines.py b/src/ansys/dpf/core/helpers/streamlines.py new file mode 100644 index 00000000000..1b7c258a0f7 --- /dev/null +++ b/src/ansys/dpf/core/helpers/streamlines.py @@ -0,0 +1,144 @@ +"""Streamlines computation specific helpers. +""" + +import numpy as np +import warnings + +from ansys.dpf.core.common import locations +from ansys.dpf.core.helpers.utils import _sort_supported_kwargs + + +class Streamlines: + """Class to define the Streamline object + scripting with `ansys-dpf-core`. + + """ + + def __init__(self, pv_data_set): + """Instantiate Streamline + from pyvista.PolyData object. + This construction is only + intended to be used internally. + + Parameters + ---------- + pv_data_set: pyvista.PolyData + """ + # in the future, a FieldsContainer would + # probably work to store the related data + self._pv_data_set = pv_data_set + + +class StreamlinesSource: + """Class to define the StreamlineSource + object scripting with `ansys-dpf-core`. + + """ + + def __init__(self, pv_data_set): + """Instantiate Streamline + from pyvista.PolyData object. + This construction is only + intended to be used internally. + + Parameters + ---------- + pv_data_set: pyvista.PolyData + """ + # in the future, a MeshedRegion would + # probably work to store the related data + self._pv_data_set = pv_data_set + + +def compute_streamlines(meshed_region, field, **kwargs): + """Compute the streamlines for a given mesh and velocity + field. + + Parameters + ---------- + meshed_region: MeshedRegion + MeshedRegion the streamline will be computed on. + field: Field + Field containing raw vector data the streamline is + computed from. The data location must be nodal, velocity + values must be defined at nodes. + **kwargs : optional + Additional keyword arguments for the streamline + computation. More information is available at + :func:`pyvista.DataSetFilters.streamlines`. + + Returns + ------- + streamlines: helpers.streamlines.Streamlines + + Examples + -------- + >>> from ansys.dpf import core as dpf + >>> from ansys.dpf.core import examples + >>> from ansys.dpf.core.helpers.streamlines import compute_streamlines + >>> # Get model and meshed region + >>> files = examples.download_fluent_mixing_elbow_steady_state() + >>> ds = dpf.DataSources() + >>> ds.set_result_file_path(files["cas"][0], "cas") + >>> ds.add_file_path(files["dat"][1], "dat") + >>> model = dpf.Model(ds) + >>> mesh = model.metadata.meshed_region + >>> # Get velocity data + >>> velocity_op = model.results.velocity() + >>> fc = velocity_op.outputs.fields_container() + >>> op = dpf.operators.averaging.to_nodal_fc(fields_container=fc) + >>> field = op.outputs.fields_container()[0] + >>> # compute streamline + >>> streamline_obj = compute_streamlines( + ... meshed_region=mesh, + ... field=field, + ... source_center=(0.55, 0.55, 0.), + ... n_points=10, + ... source_radius=0.08, + ... max_time=10.0 + ... ) + + """ + # Check velocity field location + if field.location is not locations.nodal: + warnings.warn( + "Velocity field must have a nodal location. Result must be carefully checked." + ) + + # handles input data + f_name = field.name + stream_name = "streamlines " + f_name + " (" + str(field.unit) + ")" + grid = meshed_region.grid + mesh_nodes = meshed_region.nodes + + ind, mask = mesh_nodes.map_scoping(field.scoping) + overall_data = np.full((len(mesh_nodes), 3), np.nan) # velocity has 3 components + overall_data[ind] = field.data[mask] + + grid.set_active_scalars(None) + grid[f"{stream_name}"] = overall_data + + # check src request + return_source = kwargs.pop("return_source", None) + + # filter kwargs + kwargs_base = _sort_supported_kwargs(bound_method=grid.streamlines, **kwargs) + kwargs_from_source = _sort_supported_kwargs(bound_method=grid.streamlines_from_source, **kwargs) + kwargs_from_source.update(kwargs_base) # merge both dicts in kwargs_from_source + + if return_source: + streamlines, src = grid.streamlines( + vectors=f"{stream_name}", + return_source=True, + **kwargs_from_source, + ) + streamlines = Streamlines(pv_data_set=streamlines) + src = StreamlinesSource(pv_data_set=src) + return streamlines, src + else: + streamlines = grid.streamlines( + vectors=f"{stream_name}", + **kwargs_from_source, + ) + streamlines = Streamlines(pv_data_set=streamlines) + return streamlines diff --git a/src/ansys/dpf/core/helpers/utils.py b/src/ansys/dpf/core/helpers/utils.py new file mode 100644 index 00000000000..68ead2cad32 --- /dev/null +++ b/src/ansys/dpf/core/helpers/utils.py @@ -0,0 +1,28 @@ +import inspect +import sys + + +def _sort_supported_kwargs(bound_method, **kwargs): + """Filters the kwargs for a given method.""" + # Ignore warnings unless specified + if not sys.warnoptions: + import warnings + + warnings.simplefilter("ignore") + # Get supported arguments + supported_args = inspect.getfullargspec(bound_method).args + kwargs_in = {} + kwargs_not_avail = {} + # Filter the given arguments + for key, item in kwargs.items(): + if key in supported_args: + kwargs_in[key] = item + else: + kwargs_not_avail[key] = item + # Prompt a warning for arguments filtered out + if len(kwargs_not_avail) > 0: + txt = f"The following arguments are not supported by {bound_method}: " + txt += str(kwargs_not_avail) + warnings.warn(txt) + # Return the accepted arguments + return kwargs_in diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index a9f5568d358..7f2f20b7997 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -12,7 +12,6 @@ import os import sys import numpy as np -import inspect import warnings from typing import TYPE_CHECKING, List, Union @@ -20,6 +19,7 @@ from ansys.dpf import core from ansys.dpf.core.common import locations, DefinitionLabels from ansys.dpf.core.common import shell_layers as eshell_layers +from ansys.dpf.core.helpers.streamlines import _sort_supported_kwargs from ansys.dpf.core import errors as dpf_errors from ansys.dpf.core.nodes import Node, Nodes @@ -27,32 +27,6 @@ from ansys.dpf.core.meshed_region import MeshedRegion -def _sort_supported_kwargs(bound_method, **kwargs): - """Filters the kwargs for a given method.""" - # Ignore warnings unless specified - if not sys.warnoptions: - import warnings - - warnings.simplefilter("ignore") - # Get supported arguments - supported_args = inspect.getfullargspec(bound_method).args - kwargs_in = {} - kwargs_not_avail = {} - # Filter the given arguments - for key, item in kwargs.items(): - if key in supported_args: - kwargs_in[key] = item - else: - kwargs_not_avail[key] = item - # Prompt a warning for arguments filtered out - if len(kwargs_not_avail) > 0: - txt = f"The following arguments are not supported by {bound_method}: " - txt += str(kwargs_not_avail) - warnings.warn(txt) - # Return the accepted arguments - return kwargs_in - - class _InternalPlotterFactory: """ Factory for _InternalPlotter based on the backend.""" @@ -340,56 +314,19 @@ def add_field( point_size=label_point_size, ) - def add_streamlines(self, meshed_region, field, radius=1.0, **kwargs): - # Check velocity field location - if field.location is not dpf.core.locations.nodal: - warnings.warn( - "Velocity field must have a nodal location. Result must be carefully checked." - ) - - # handles input data - f_name = field.name - stream_name = "streamlines " + f_name + " (" + str(field.unit) + ")" - grid = meshed_region.grid - mesh_nodes = meshed_region.nodes - - ind, mask = mesh_nodes.map_scoping(field.scoping) - overall_data = np.full((len(mesh_nodes), 3), np.nan) # velocity has 3 components - overall_data[ind] = field.data[mask] - - grid.set_active_scalars(None) - grid[f"{stream_name}"] = overall_data - - # check src request - return_source = kwargs.pop("return_source", None) + def add_streamlines(self, streamlines, source=None, radius=1.0, **kwargs): permissive = kwargs.pop("permissive", None) - - # filter kwargs - kwargs_base = _sort_supported_kwargs(bound_method=grid.streamlines, **kwargs) - kwargs_from_source = _sort_supported_kwargs( - bound_method=grid.streamlines_from_source, **kwargs - ) - kwargs_from_source.update(kwargs_base) # merge both dicts in kwargs_from_source - - # create streamlines - if return_source: - streamlines, src = grid.streamlines( - vectors=f"{stream_name}", - return_source=True, - **kwargs_from_source, - ) - else: - streamlines = grid.streamlines( - vectors=f"{stream_name}", - **kwargs_from_source, - ) - + kwargs_in = _sort_supported_kwargs(bound_method=self._plotter.add_mesh, **kwargs) # set streamline on plotter sargs = dict(vertical=False) + streamlines = streamlines._pv_data_set if not (permissive and streamlines.n_points == 0): - self._plotter.add_mesh(streamlines.tube(radius=radius), scalar_bar_args=sargs) - if return_source: - self._plotter.add_mesh(src) + self._plotter.add_mesh( + streamlines.tube(radius=radius), scalar_bar_args=sargs, **kwargs_in + ) + if source is not None: + src = source._pv_data_set + self._plotter.add_mesh(src, **kwargs_in) def show_figure(self, **kwargs): @@ -560,8 +497,8 @@ def add_mesh(self, meshed_region, deform_by=None, scale_factor=1.0, **kwargs): def add_streamlines( self, - meshed_region, - field, + streamlines, + source=None, radius=0.1, **kwargs, ): @@ -574,15 +511,17 @@ def add_streamlines( Parameters ---------- - meshed_region : MeshedRegion - MeshedRegion the streamline will be computed on. - field : Field - Field containing raw vector data the streamline is - computed from. The data location must be nodal, velocity - values must be defined at nodes. + streamlines : helpers.streamlines.Streamlines + Object containing computed streamlines data, + computed using `helpers.streamlines.compute_streamlines` + function. + source : helpers.streamlines.StreamlinesSource, optional + Object containing computed streamines source data, + computed using `helpers.streamlines.compute_streamlines` + function. **kwargs : optional Additional keyword arguments for the plotter. More information - is available at :func:`pyvista.DataSetFilters.streamlines`. + is available at :func:`pyvista.plot`. The "permissive" (boolean) can be used to avoid throwing if computed streamlines are empty. See ``Examples`` section for more information. @@ -591,6 +530,7 @@ def add_streamlines( -------- >>> from ansys.dpf import core as dpf >>> from ansys.dpf.core import examples + >>> from ansys.dpf.core.helpers.streamlines import compute_streamlines >>> # Get model and meshed region >>> files = examples.download_fluent_mixing_elbow_steady_state() >>> ds = dpf.DataSources() @@ -607,20 +547,24 @@ def add_streamlines( >>> from ansys.dpf.core.plotter import DpfPlotter >>> pl = DpfPlotter() >>> pl.add_mesh(meshed_region=mesh, opacity=0.15, color="g") - >>> pl.add_streamlines(meshed_region=mesh, + >>> streamline_obj = compute_streamlines( + ... meshed_region=mesh, ... field=field, - ... radius=0.001, ... source_center=(0.55, 0.55, 0.), ... n_points=10, ... source_radius=0.08, ... max_time=10.0 ... ) + >>> pl.add_streamlines( + ... streamlines=streamline_obj, + ... radius=0.001, + ... ) >>> pl.show_figure(show_axes=True) """ self._internal_plotter.add_streamlines( - meshed_region=meshed_region, - field=field, + streamlines=streamlines, + source=source, radius=radius, **kwargs, )