diff --git a/src/ansys/dpf/core/animator.py b/src/ansys/dpf/core/animator.py index 31983196189..56ac79e4721 100644 --- a/src/ansys/dpf/core/animator.py +++ b/src/ansys/dpf/core/animator.py @@ -60,6 +60,7 @@ def animate_workflow( save_as="", mode_number=None, scale_factor=1.0, + shell_layer=core.shell_layers.top, **kwargs, ): unit = loop_over.unit @@ -121,6 +122,7 @@ def render_frame(frame): field, deform_by=deform, scale_factor_legend=scale_factor[frame], + shell_layer=shell_layer, **kwargs, ) kwargs_in = _sort_supported_kwargs(bound_method=self._plotter.add_text, **freq_kwargs) @@ -267,6 +269,7 @@ def animate( save_as: str = None, scale_factor: Union[float, Sequence[float]] = 1.0, freq_kwargs: dict = None, + shell_layer: core.shell_layers = core.shell_layers.top, **kwargs, ): """ @@ -274,26 +277,29 @@ def animate( Parameters ---------- - loop_over : Field + loop_over: Field of values to loop over. Can for example be a subset of sets of TimeFreqSupport.time_frequencies. The unit of the Field will be displayed if present. - output_name : str, optional + output_name: Name of the workflow output to use as Field for each frame's contour. Defaults to "to_render". - input_name : list of str, optional + input_name: Name of the workflow inputs to feed loop_over values into. Defaults to "loop_over". - save_as : str, optional + save_as: Path of file to save the animation to. Defaults to None. Can be of any format supported by pyvista.Plotter.write_frame (.gif, .mp4, ...). - scale_factor : float, list, optional + scale_factor: Scale factor to apply when warping the mesh. Defaults to 1.0. Can be a list to make scaling frequency-dependent. - freq_kwargs : dict, optional + freq_kwargs: Dictionary of kwargs given to the :func:`pyvista.Plotter.add_text` method, used to format the frequency information. Can also contain a "fmt" key, defining the format for the frequency displayed with a string such as ".3e". + shell_layer: + Enum used to set the shell layer if the field to plot + contains shell elements. Defaults to top layer. **kwargs : optional Additional keyword arguments for the animator. Used by :func:`pyvista.Plotter` (off_screen, cpos, ...), @@ -314,6 +320,7 @@ def animate( save_as=save_as, scale_factor=scale_factor, freq_kwargs=freq_kwargs, + shell_layer=shell_layer, **kwargs, ) diff --git a/src/ansys/dpf/core/collection_base.py b/src/ansys/dpf/core/collection_base.py index bf05ff6336b..257d96fc7c5 100644 --- a/src/ansys/dpf/core/collection_base.py +++ b/src/ansys/dpf/core/collection_base.py @@ -45,7 +45,7 @@ dpf_vector, ) -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.dpf.core.support import Support from ansys.dpf.gate.integral_types import MutableListInt32 diff --git a/src/ansys/dpf/core/data_tree.py b/src/ansys/dpf/core/data_tree.py index 4d24174f7e1..d49bb58d108 100644 --- a/src/ansys/dpf/core/data_tree.py +++ b/src/ansys/dpf/core/data_tree.py @@ -28,7 +28,7 @@ import weakref from ansys.dpf.core import collection_base, common, errors, server as server_module -from ansys.dpf.core.mapping_types import types +from ansys.dpf.core.common import types from ansys.dpf.gate import ( data_processing_capi, data_processing_grpcapi, diff --git a/src/ansys/dpf/core/dpf_operator.py b/src/ansys/dpf/core/dpf_operator.py index 981525d00fa..c8191bfa9de 100644 --- a/src/ansys/dpf/core/dpf_operator.py +++ b/src/ansys/dpf/core/dpf_operator.py @@ -36,11 +36,10 @@ server_meet_version_and_raise, version_requires, ) -from ansys.dpf.core.common import types_enum_to_types +from ansys.dpf.core.common import types, types_enum_to_types from ansys.dpf.core.config import Config from ansys.dpf.core.errors import DpfVersionNotSupported from ansys.dpf.core.inputs import Inputs -from ansys.dpf.core.mapping_types import types from ansys.dpf.core.operator_specification import Specification from ansys.dpf.core.outputs import Output, Outputs, _Outputs from ansys.dpf.core.unit_system import UnitSystem diff --git a/src/ansys/dpf/core/fields_container.py b/src/ansys/dpf/core/fields_container.py index 0f8f43a1a22..7190043b5ce 100644 --- a/src/ansys/dpf/core/fields_container.py +++ b/src/ansys/dpf/core/fields_container.py @@ -26,9 +26,17 @@ Contains classes associated with the DPF FieldsContainer. """ +from __future__ import annotations + +from typing import TYPE_CHECKING, Union + from ansys import dpf from ansys.dpf.core import errors as dpf_errors, field from ansys.dpf.core.collection_base import CollectionBase +from ansys.dpf.core.common import shell_layers + +if TYPE_CHECKING: # pragma: no cover + from ansys.dpf.core import Operator, Result class FieldsContainer(CollectionBase["field.Field"]): @@ -543,7 +551,14 @@ def plot(self, label_space: dict = None, **kwargs): plt.add_field(field=f, **kwargs) plt.show_figure(**kwargs) - def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): + def animate( + self, + save_as: str = None, + deform_by: Union[FieldsContainer, Result, Operator] = None, + scale_factor: Union[float, Sequence[float]] = 1.0, + shell_layer: shell_layers = shell_layers.top, + **kwargs, + ): """Create an animation based on the Fields contained in the FieldsContainer. This method creates a movie or a gif based on the time ids of a FieldsContainer. @@ -551,15 +566,24 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): Parameters ---------- - save_as : Path of file to save the animation to. Defaults to None. Can be of any format + save_as: + Path of file to save the animation to. Defaults to None. Can be of any format supported by pyvista.Plotter.write_frame (.gif, .mp4, ...). - deform_by : FieldsContainer, Result, Operator, optional + deform_by: Used to deform the plotted mesh. Must return a FieldsContainer of the same length as self, containing 3D vector Fields of distances. Defaults to None, which takes self if possible. Set as False to force static animation. scale_factor : float, list, optional Scale factor to apply when warping the mesh. Defaults to 1.0. Can be a list to make scaling frequency-dependent. + shell_layer: + Enum used to set the shell layer if the field to plot + contains shell elements. Defaults to top layer. + **kwargs: + Additional keyword arguments for the animator. + Used by :func:`pyvista.Plotter` (off_screen, cpos, ...), + or by :func:`pyvista.Plotter.open_movie` + (framerate, quality, ...) """ from ansys.dpf.core.animator import Animator @@ -571,11 +595,17 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): # Define the field extraction using the fields_container and indices extract_field_op = dpf.core.operators.utility.extract_field(self) to_render = extract_field_op.outputs.field + # Add the operators to the workflow + wf.add_operators([extract_field_op, forward_index]) + + # Treat multi-component fields by taking their norm n_components = self[0].component_count if n_components > 1: norm_op = dpf.core.operators.math.norm(extract_field_op.outputs.field) + wf.add_operator(norm_op) to_render = norm_op.outputs.field + # Get time steps IDs and values loop_over = self.get_time_scoping() frequencies = self.time_freq_support.time_frequencies if frequencies is None: @@ -586,8 +616,6 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): wf.set_input_name("indices", extract_field_op.inputs.indices) # Have to do it this way wf.connect("indices", forward_index) # Otherwise not accepted - # Add the operators to the workflow - wf.add_operators([extract_field_op, forward_index]) deform = True # Define whether to deform and what with @@ -627,6 +655,10 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): extract_field_op_2.outputs.field, extract_scale_factor_op.outputs.field ) wf.set_output_name("deform_by", divide_op.outputs.field) + + wf.add_operators( + [scale_factor_invert, extract_field_op_2, extract_scale_factor_op, divide_op] + ) else: scale_factor = None wf.set_output_name("to_render", to_render) @@ -647,6 +679,7 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): loop_over=loop_over_field, save_as=save_as, scale_factor=scale_factor, + shell_layer=shell_layer, **kwargs, ) diff --git a/src/ansys/dpf/core/mapping_types.py b/src/ansys/dpf/core/mapping_types.py index d23dcbd48c3..9a7ad1e1904 100644 --- a/src/ansys/dpf/core/mapping_types.py +++ b/src/ansys/dpf/core/mapping_types.py @@ -21,23 +21,30 @@ # SOFTWARE. """Provides utilities for mapping and transforming data types between Python and C++ representations.""" +from enum import Enum # noqa: F401 # pylint: disable=W0611 import inspect +from pathlib import Path # noqa: F401 # pylint: disable=W0611 import sys -from ansys.dpf.core.available_result import * # noqa: F401, F403 +# Import types to map to cpp (camel case to snake case) +from ansys.dpf.core.available_result import ( # noqa: F401 # pylint: disable=W0611 + AvailableResult, + Homogeneity, +) +from ansys.dpf.core.collection_base import CollectionBase # noqa: F401 # pylint: disable=W0611 from ansys.dpf.core.common import ( _camel_to_snake_case, _smart_dict_camel, _snake_to_camel_case, ) -from ansys.dpf.core.data_sources import * # noqa: F401, F403 -from ansys.dpf.core.field import * # noqa: F401, F403 -from ansys.dpf.core.fields_container import * # noqa: F401, F403 - -## to do : change that one the module is done -from ansys.dpf.core.meshed_region import * # noqa: F401, F403 -from ansys.dpf.core.scoping import * # noqa: F401, F403 -from ansys.dpf.core.time_freq_support import * # noqa: F401, F403 +from ansys.dpf.core.data_sources import DataSources # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.dpf_array import DPFArray # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.field import Field, FieldDefinition # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.fields_container import FieldsContainer # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.meshed_region import MeshedRegion # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.scoping import Scoping # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.support import Support # noqa: F401 # pylint: disable=W0611 +from ansys.dpf.core.time_freq_support import TimeFreqSupport # noqa: F401 # pylint: disable=W0611 map_types_to_cpp = _smart_dict_camel() for classes in inspect.getmembers(sys.modules[__name__], inspect.isclass): diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index b7a7558bcbe..ef95b941ddb 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -233,6 +233,7 @@ def add_field( scale_factor=1.0, scale_factor_legend=None, as_linear=True, + shell_layer=eshell_layers.top, **kwargs, ): # Get the field name @@ -275,6 +276,20 @@ def add_field( mesh_location = meshed_region.elements else: raise ValueError("Only elemental, nodal or faces location are supported for plotting.") + + # Treat multilayered shells + if not isinstance(shell_layer, eshell_layers): + raise TypeError("shell_layer attribute must be a core.shell_layers instance.") + if field.shell_layers in [ + eshell_layers.topbottom, + eshell_layers.topbottommid, + ]: + change_shell_layer_op = core.operators.utility.change_shell_layers( + fields_container=field, + e_shell_layer=shell_layer, + ) + field = change_shell_layer_op.get_output(0, core.types.field) + component_count = field.component_count if component_count > 1: overall_data = np.full((len(mesh_location), component_count), np.nan) @@ -603,6 +618,7 @@ def add_field( label_point_size=20, deform_by=None, scale_factor=1.0, + shell_layer=eshell_layers.top, **kwargs, ): """Add a field containing data to the plotter. @@ -627,6 +643,9 @@ def add_field( Defaults to None. scale_factor : float, optional Scaling factor to apply when warping the mesh. Defaults to 1.0. + shell_layer: core.shell_layers, optional + Enum used to set the shell layer if the field to plot + contains shell elements. Defaults to top layer. **kwargs : optional Additional keyword arguments for the plotter. More information are available at :func:`pyvista.plot`. @@ -653,6 +672,7 @@ def add_field( deform_by=deform_by, scale_factor=scale_factor, as_linear=True, + shell_layer=shell_layer, **kwargs, ) diff --git a/src/ansys/dpf/core/server_types.py b/src/ansys/dpf/core/server_types.py index b98d243e3b7..b5e1d638d9a 100644 --- a/src/ansys/dpf/core/server_types.py +++ b/src/ansys/dpf/core/server_types.py @@ -52,7 +52,7 @@ from ansys.dpf.core.check_version import server_meet_version from ansys.dpf.gate import data_processing_grpcapi, load_api -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.dpf.core.server_factory import DockerConfig import logging diff --git a/tests/test_plotter.py b/tests/test_plotter.py index ce39cc31731..4615b6d0441 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -253,7 +253,7 @@ def test_field_shell_plot_scoping_elemental(multishells): @pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") -def test_throw_shell_layers(multishells): +def test_plotter_plot_contour_throw_shell_layers(multishells): model = core.Model(multishells) stress = model.results.stress() scoping = core.Scoping() @@ -269,6 +269,27 @@ def test_throw_shell_layers(multishells): f.plot(shell_layers="test") +@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") +def test_dpf_plotter_add_field_throw_shell_layer(multishells): + field: core.Field = core.operators.result.stress( + data_sources=core.DataSources(multishells), + requested_location=core.locations.elemental, + ).eval()[1] + plt = DpfPlotter() + with pytest.raises(TypeError): + plt.add_field(field=field, shell_layer="test") + + +@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") +def test_dpf_plotter_add_field_change_shell_layer(multishells): + field: core.Field = core.operators.result.stress( + data_sources=core.DataSources(multishells), + requested_location=core.locations.elemental, + ).eval()[1] + plt = DpfPlotter() + plt.add_field(field=field) + + @pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista") def test_plot_fieldscontainer_on_mesh_scoping(multishells): model = core.Model(multishells)