Skip to content

Commit e2ed560

Browse files
authored
Expose shell_layer in animations (#2092)
* Fix workflow.view() * Correctly add operators to animation workflow * Patch for badly defined ls-dyna fields (forces norm when several components are reported. * Revert "Patch for badly defined ls-dyna fields (forces norm when several components are reported." This reverts commit 267721d. * handle shell_layers in DpfPlotter * Fix mapping_types imports * Expose shell_layer parameter in Animator.animate() and FieldsContainer.animate() * Fix Code Quality checks * Use latest fix for connection of Enums to operators * Use latest fix for connection of Enums to operators * Revert "Fix workflow.view()" This reverts commit e47b02b. * Ignore TYPE_CHECKING imports for coverage * Fix shell_layer argument transmission * Add test_dpf_plotter_add_field_throw_shell_layers to test_plotter.py * Add test_dpf_plotter_add_field_change_shell_layer to test_plotter.py * Remove comment
1 parent 46ffded commit e2ed560

File tree

9 files changed

+113
-26
lines changed

9 files changed

+113
-26
lines changed

src/ansys/dpf/core/animator.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def animate_workflow(
6060
save_as="",
6161
mode_number=None,
6262
scale_factor=1.0,
63+
shell_layer=core.shell_layers.top,
6364
**kwargs,
6465
):
6566
unit = loop_over.unit
@@ -121,6 +122,7 @@ def render_frame(frame):
121122
field,
122123
deform_by=deform,
123124
scale_factor_legend=scale_factor[frame],
125+
shell_layer=shell_layer,
124126
**kwargs,
125127
)
126128
kwargs_in = _sort_supported_kwargs(bound_method=self._plotter.add_text, **freq_kwargs)
@@ -267,33 +269,37 @@ def animate(
267269
save_as: str = None,
268270
scale_factor: Union[float, Sequence[float]] = 1.0,
269271
freq_kwargs: dict = None,
272+
shell_layer: core.shell_layers = core.shell_layers.top,
270273
**kwargs,
271274
):
272275
"""
273276
Animate the workflow of the Animator, using inputs.
274277
275278
Parameters
276279
----------
277-
loop_over : Field
280+
loop_over:
278281
Field of values to loop over.
279282
Can for example be a subset of sets of TimeFreqSupport.time_frequencies.
280283
The unit of the Field will be displayed if present.
281-
output_name : str, optional
284+
output_name:
282285
Name of the workflow output to use as Field for each frame's contour.
283286
Defaults to "to_render".
284-
input_name : list of str, optional
287+
input_name:
285288
Name of the workflow inputs to feed loop_over values into.
286289
Defaults to "loop_over".
287-
save_as : str, optional
290+
save_as:
288291
Path of file to save the animation to. Defaults to None. Can be of any format supported
289292
by pyvista.Plotter.write_frame (.gif, .mp4, ...).
290-
scale_factor : float, list, optional
293+
scale_factor:
291294
Scale factor to apply when warping the mesh. Defaults to 1.0. Can be a list to make
292295
scaling frequency-dependent.
293-
freq_kwargs : dict, optional
296+
freq_kwargs:
294297
Dictionary of kwargs given to the :func:`pyvista.Plotter.add_text` method, used to
295298
format the frequency information. Can also contain a "fmt" key,
296299
defining the format for the frequency displayed with a string such as ".3e".
300+
shell_layer:
301+
Enum used to set the shell layer if the field to plot
302+
contains shell elements. Defaults to top layer.
297303
**kwargs : optional
298304
Additional keyword arguments for the animator.
299305
Used by :func:`pyvista.Plotter` (off_screen, cpos, ...),
@@ -314,6 +320,7 @@ def animate(
314320
save_as=save_as,
315321
scale_factor=scale_factor,
316322
freq_kwargs=freq_kwargs,
323+
shell_layer=shell_layer,
317324
**kwargs,
318325
)
319326

src/ansys/dpf/core/collection_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
dpf_vector,
4646
)
4747

48-
if TYPE_CHECKING:
48+
if TYPE_CHECKING: # pragma: no cover
4949
from ansys.dpf.core.support import Support
5050

5151
from ansys.dpf.gate.integral_types import MutableListInt32

src/ansys/dpf/core/data_tree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import weakref
2929

3030
from ansys.dpf.core import collection_base, common, errors, server as server_module
31-
from ansys.dpf.core.mapping_types import types
31+
from ansys.dpf.core.common import types
3232
from ansys.dpf.gate import (
3333
data_processing_capi,
3434
data_processing_grpcapi,

src/ansys/dpf/core/dpf_operator.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@
3636
server_meet_version_and_raise,
3737
version_requires,
3838
)
39-
from ansys.dpf.core.common import types_enum_to_types
39+
from ansys.dpf.core.common import types, types_enum_to_types
4040
from ansys.dpf.core.config import Config
4141
from ansys.dpf.core.errors import DpfVersionNotSupported
4242
from ansys.dpf.core.inputs import Inputs
43-
from ansys.dpf.core.mapping_types import types
4443
from ansys.dpf.core.operator_specification import Specification
4544
from ansys.dpf.core.outputs import Output, Outputs, _Outputs
4645
from ansys.dpf.core.unit_system import UnitSystem

src/ansys/dpf/core/fields_container.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@
2626
Contains classes associated with the DPF FieldsContainer.
2727
"""
2828

29+
from __future__ import annotations
30+
31+
from typing import TYPE_CHECKING, Union
32+
2933
from ansys import dpf
3034
from ansys.dpf.core import errors as dpf_errors, field
3135
from ansys.dpf.core.collection_base import CollectionBase
36+
from ansys.dpf.core.common import shell_layers
37+
38+
if TYPE_CHECKING: # pragma: no cover
39+
from ansys.dpf.core import Operator, Result
3240

3341

3442
class FieldsContainer(CollectionBase["field.Field"]):
@@ -543,23 +551,39 @@ def plot(self, label_space: dict = None, **kwargs):
543551
plt.add_field(field=f, **kwargs)
544552
plt.show_figure(**kwargs)
545553

546-
def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs):
554+
def animate(
555+
self,
556+
save_as: str = None,
557+
deform_by: Union[FieldsContainer, Result, Operator] = None,
558+
scale_factor: Union[float, Sequence[float]] = 1.0,
559+
shell_layer: shell_layers = shell_layers.top,
560+
**kwargs,
561+
):
547562
"""Create an animation based on the Fields contained in the FieldsContainer.
548563
549564
This method creates a movie or a gif based on the time ids of a FieldsContainer.
550565
For kwargs see pyvista.Plotter.open_movie/add_text/show.
551566
552567
Parameters
553568
----------
554-
save_as : Path of file to save the animation to. Defaults to None. Can be of any format
569+
save_as:
570+
Path of file to save the animation to. Defaults to None. Can be of any format
555571
supported by pyvista.Plotter.write_frame (.gif, .mp4, ...).
556-
deform_by : FieldsContainer, Result, Operator, optional
572+
deform_by:
557573
Used to deform the plotted mesh. Must return a FieldsContainer of the same length as
558574
self, containing 3D vector Fields of distances.
559575
Defaults to None, which takes self if possible. Set as False to force static animation.
560576
scale_factor : float, list, optional
561577
Scale factor to apply when warping the mesh. Defaults to 1.0. Can be a list to make
562578
scaling frequency-dependent.
579+
shell_layer:
580+
Enum used to set the shell layer if the field to plot
581+
contains shell elements. Defaults to top layer.
582+
**kwargs:
583+
Additional keyword arguments for the animator.
584+
Used by :func:`pyvista.Plotter` (off_screen, cpos, ...),
585+
or by :func:`pyvista.Plotter.open_movie`
586+
(framerate, quality, ...)
563587
"""
564588
from ansys.dpf.core.animator import Animator
565589

@@ -571,11 +595,17 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs):
571595
# Define the field extraction using the fields_container and indices
572596
extract_field_op = dpf.core.operators.utility.extract_field(self)
573597
to_render = extract_field_op.outputs.field
598+
# Add the operators to the workflow
599+
wf.add_operators([extract_field_op, forward_index])
600+
601+
# Treat multi-component fields by taking their norm
574602
n_components = self[0].component_count
575603
if n_components > 1:
576604
norm_op = dpf.core.operators.math.norm(extract_field_op.outputs.field)
605+
wf.add_operator(norm_op)
577606
to_render = norm_op.outputs.field
578607

608+
# Get time steps IDs and values
579609
loop_over = self.get_time_scoping()
580610
frequencies = self.time_freq_support.time_frequencies
581611
if frequencies is None:
@@ -586,8 +616,6 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs):
586616

587617
wf.set_input_name("indices", extract_field_op.inputs.indices) # Have to do it this way
588618
wf.connect("indices", forward_index) # Otherwise not accepted
589-
# Add the operators to the workflow
590-
wf.add_operators([extract_field_op, forward_index])
591619

592620
deform = True
593621
# 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):
627655
extract_field_op_2.outputs.field, extract_scale_factor_op.outputs.field
628656
)
629657
wf.set_output_name("deform_by", divide_op.outputs.field)
658+
659+
wf.add_operators(
660+
[scale_factor_invert, extract_field_op_2, extract_scale_factor_op, divide_op]
661+
)
630662
else:
631663
scale_factor = None
632664
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):
647679
loop_over=loop_over_field,
648680
save_as=save_as,
649681
scale_factor=scale_factor,
682+
shell_layer=shell_layer,
650683
**kwargs,
651684
)
652685

src/ansys/dpf/core/mapping_types.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,30 @@
2121
# SOFTWARE.
2222
"""Provides utilities for mapping and transforming data types between Python and C++ representations."""
2323

24+
from enum import Enum # noqa: F401 # pylint: disable=W0611
2425
import inspect
26+
from pathlib import Path # noqa: F401 # pylint: disable=W0611
2527
import sys
2628

27-
from ansys.dpf.core.available_result import * # noqa: F401, F403
29+
# Import types to map to cpp (camel case to snake case)
30+
from ansys.dpf.core.available_result import ( # noqa: F401 # pylint: disable=W0611
31+
AvailableResult,
32+
Homogeneity,
33+
)
34+
from ansys.dpf.core.collection_base import CollectionBase # noqa: F401 # pylint: disable=W0611
2835
from ansys.dpf.core.common import (
2936
_camel_to_snake_case,
3037
_smart_dict_camel,
3138
_snake_to_camel_case,
3239
)
33-
from ansys.dpf.core.data_sources import * # noqa: F401, F403
34-
from ansys.dpf.core.field import * # noqa: F401, F403
35-
from ansys.dpf.core.fields_container import * # noqa: F401, F403
36-
37-
## to do : change that one the module is done
38-
from ansys.dpf.core.meshed_region import * # noqa: F401, F403
39-
from ansys.dpf.core.scoping import * # noqa: F401, F403
40-
from ansys.dpf.core.time_freq_support import * # noqa: F401, F403
40+
from ansys.dpf.core.data_sources import DataSources # noqa: F401 # pylint: disable=W0611
41+
from ansys.dpf.core.dpf_array import DPFArray # noqa: F401 # pylint: disable=W0611
42+
from ansys.dpf.core.field import Field, FieldDefinition # noqa: F401 # pylint: disable=W0611
43+
from ansys.dpf.core.fields_container import FieldsContainer # noqa: F401 # pylint: disable=W0611
44+
from ansys.dpf.core.meshed_region import MeshedRegion # noqa: F401 # pylint: disable=W0611
45+
from ansys.dpf.core.scoping import Scoping # noqa: F401 # pylint: disable=W0611
46+
from ansys.dpf.core.support import Support # noqa: F401 # pylint: disable=W0611
47+
from ansys.dpf.core.time_freq_support import TimeFreqSupport # noqa: F401 # pylint: disable=W0611
4148

4249
map_types_to_cpp = _smart_dict_camel()
4350
for classes in inspect.getmembers(sys.modules[__name__], inspect.isclass):

src/ansys/dpf/core/plotter.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ def add_field(
233233
scale_factor=1.0,
234234
scale_factor_legend=None,
235235
as_linear=True,
236+
shell_layer=eshell_layers.top,
236237
**kwargs,
237238
):
238239
# Get the field name
@@ -275,6 +276,20 @@ def add_field(
275276
mesh_location = meshed_region.elements
276277
else:
277278
raise ValueError("Only elemental, nodal or faces location are supported for plotting.")
279+
280+
# Treat multilayered shells
281+
if not isinstance(shell_layer, eshell_layers):
282+
raise TypeError("shell_layer attribute must be a core.shell_layers instance.")
283+
if field.shell_layers in [
284+
eshell_layers.topbottom,
285+
eshell_layers.topbottommid,
286+
]:
287+
change_shell_layer_op = core.operators.utility.change_shell_layers(
288+
fields_container=field,
289+
e_shell_layer=shell_layer,
290+
)
291+
field = change_shell_layer_op.get_output(0, core.types.field)
292+
278293
component_count = field.component_count
279294
if component_count > 1:
280295
overall_data = np.full((len(mesh_location), component_count), np.nan)
@@ -603,6 +618,7 @@ def add_field(
603618
label_point_size=20,
604619
deform_by=None,
605620
scale_factor=1.0,
621+
shell_layer=eshell_layers.top,
606622
**kwargs,
607623
):
608624
"""Add a field containing data to the plotter.
@@ -627,6 +643,9 @@ def add_field(
627643
Defaults to None.
628644
scale_factor : float, optional
629645
Scaling factor to apply when warping the mesh. Defaults to 1.0.
646+
shell_layer: core.shell_layers, optional
647+
Enum used to set the shell layer if the field to plot
648+
contains shell elements. Defaults to top layer.
630649
**kwargs : optional
631650
Additional keyword arguments for the plotter. More information
632651
are available at :func:`pyvista.plot`.
@@ -653,6 +672,7 @@ def add_field(
653672
deform_by=deform_by,
654673
scale_factor=scale_factor,
655674
as_linear=True,
675+
shell_layer=shell_layer,
656676
**kwargs,
657677
)
658678

src/ansys/dpf/core/server_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from ansys.dpf.core.check_version import server_meet_version
5353
from ansys.dpf.gate import data_processing_grpcapi, load_api
5454

55-
if TYPE_CHECKING:
55+
if TYPE_CHECKING: # pragma: no cover
5656
from ansys.dpf.core.server_factory import DockerConfig
5757

5858
import logging

tests/test_plotter.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def test_field_shell_plot_scoping_elemental(multishells):
253253

254254

255255
@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
256-
def test_throw_shell_layers(multishells):
256+
def test_plotter_plot_contour_throw_shell_layers(multishells):
257257
model = core.Model(multishells)
258258
stress = model.results.stress()
259259
scoping = core.Scoping()
@@ -269,6 +269,27 @@ def test_throw_shell_layers(multishells):
269269
f.plot(shell_layers="test")
270270

271271

272+
@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
273+
def test_dpf_plotter_add_field_throw_shell_layer(multishells):
274+
field: core.Field = core.operators.result.stress(
275+
data_sources=core.DataSources(multishells),
276+
requested_location=core.locations.elemental,
277+
).eval()[1]
278+
plt = DpfPlotter()
279+
with pytest.raises(TypeError):
280+
plt.add_field(field=field, shell_layer="test")
281+
282+
283+
@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
284+
def test_dpf_plotter_add_field_change_shell_layer(multishells):
285+
field: core.Field = core.operators.result.stress(
286+
data_sources=core.DataSources(multishells),
287+
requested_location=core.locations.elemental,
288+
).eval()[1]
289+
plt = DpfPlotter()
290+
plt.add_field(field=field)
291+
292+
272293
@pytest.mark.skipif(not HAS_PYVISTA, reason="Please install pyvista")
273294
def test_plot_fieldscontainer_on_mesh_scoping(multishells):
274295
model = core.Model(multishells)

0 commit comments

Comments
 (0)