From 5c29a5fb5ebce352be4933052f611d3631155ccd Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 8 Jan 2025 16:48:28 +0100 Subject: [PATCH 1/3] Allow plots of empty overall fields --- src/ansys/dpf/core/plotter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 9c957c4198..ceb6e57053 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -285,7 +285,10 @@ def add_field( ind, mask = mesh_location.map_scoping(field.scoping) overall_data[ind] = field.data[mask] else: - overall_data[:] = field.data[0] + if len(field.data) > 0: + overall_data[:] = field.data[0] + else: + overall_data[:] = np.nan # Filter kwargs for add_mesh kwargs_in = _sort_supported_kwargs(bound_method=self._plotter.add_mesh, **kwargs) # Have to remove any active scalar field from the pre-existing grid object, From e7933c6ff0e0bef12582b1684c78301629744a7e Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 8 Jan 2025 16:49:38 +0100 Subject: [PATCH 2/3] Add MeshesContainer.animate --- src/ansys/dpf/core/meshes_container.py | 68 ++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/ansys/dpf/core/meshes_container.py b/src/ansys/dpf/core/meshes_container.py index e2e43e8ee8..300ec6d035 100644 --- a/src/ansys/dpf/core/meshes_container.py +++ b/src/ansys/dpf/core/meshes_container.py @@ -27,13 +27,23 @@ Contains classes associated with the DPF MeshesContainer. """ -from ansys.dpf.core import meshed_region +from __future__ import annotations + +import os +from typing import List, Union, TYPE_CHECKING + +if TYPE_CHECKING: + from ansys.dpf.core import FieldsContainer, Operator, TimeFreqSupport + from ansys.dpf.core.results import Result + +import ansys.dpf.core as dpf +from ansys.dpf.core.meshed_region import MeshedRegion from ansys.dpf.core.collection_base import CollectionBase from ansys.dpf.core.plotter import DpfPlotter from ansys.dpf.core import errors as dpf_errors -class MeshesContainer(CollectionBase[meshed_region.MeshedRegion]): +class MeshesContainer(CollectionBase[MeshedRegion]): """Represents a meshes container, which contains meshes split on a given space. Parameters @@ -48,7 +58,7 @@ class MeshesContainer(CollectionBase[meshed_region.MeshedRegion]): global server. """ - entries_type = meshed_region.MeshedRegion + entries_type = MeshedRegion def __init__(self, meshes_container=None, server=None): super().__init__(collection=meshes_container, server=server) @@ -60,7 +70,7 @@ def __init__(self, meshes_container=None, server=None): def create_subtype(self, obj_by_copy): """Create a meshed region sub type.""" - return meshed_region.MeshedRegion(mesh=obj_by_copy, server=self._server) + return MeshedRegion(mesh=obj_by_copy, server=self._server) def plot(self, fields_container=None, deform_by=None, scale_factor=1.0, **kwargs): """Plot the meshes container with a specific result if fields_container is specified. @@ -144,6 +154,56 @@ def plot(self, fields_container=None, deform_by=None, scale_factor=1.0, **kwargs kwargs.pop("notebook", None) return pl.show_figure(**kwargs) + def animate( + self, + frequencies: TimeFreqSupport, + save_as: Union[str, os.PathLike] = None, + deform_by: Union[FieldsContainer, Result, Operator, bool] = None, + scale_factor: Union[float, List[float]] = 1.0, + **kwargs, + ): + """Create an animation based on the meshes contained in the MeshesContainer. + + This method creates a movie or a gif based on the time ids of a MeshesContainer. + For kwargs see pyvista.Plotter.open_movie/add_text/show. + + Parameters + ---------- + 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: + 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: + Scale factor to apply when warping the mesh. Defaults to 1.0. Can be a list to make + scaling vary in time. + """ + # Build the list of time values to animate + time_scoping = self.get_label_scoping(label="time") + + # For now create empty fields for each frame with the corresponding mesh as support + # We can add options later to populate those fields with values or to accept a + # FieldsContainer as input (similar to the plot method). + fields = {} + for time_id in time_scoping.ids: + field_i = dpf.Field(location=dpf.locations.overall) + field_i.meshed_region = self.get_mesh({"time": time_id}) + fields[time_id] = field_i + fc = dpf.fields_container_factory.over_time_freq_fields_container( + fields=fields, + time_freq_unit=frequencies.time_frequencies.unit, + ) + fc.time_freq_support = frequencies + fc.animate( + save_as=save_as, + deform_by=deform_by, + scale_factor=scale_factor, + show_scalar_bar=False, + **kwargs, + ) + def get_meshes(self, label_space): """Retrieve the meshes at a label space. From bd52a3b9753715f8a6c2f3bd8101fea2e824beab Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 8 Jan 2025 17:49:10 +0100 Subject: [PATCH 3/3] Fix frequency value shown --- src/ansys/dpf/core/animator.py | 5 +++-- src/ansys/dpf/core/fields_container.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/core/animator.py b/src/ansys/dpf/core/animator.py index 122facd6bf..45c6edd7cc 100644 --- a/src/ansys/dpf/core/animator.py +++ b/src/ansys/dpf/core/animator.py @@ -110,7 +110,8 @@ def render_frame(frame): workflow.connect(input_name, [frame]) else: - workflow.connect(input_name, loop_over.data[frame]) + workflow.connect(input_name, loop_over.scoping.ids[frame]) + workflow.connect("loop_over_values", loop_over.data) field = workflow.get_output(output_name, core.types.field) deform = None @@ -126,7 +127,7 @@ def render_frame(frame): if mode_number is None: str_template = "t={0:{2}} {1}" self._plotter.add_text( - str_template.format(indices[frame], unit, freq_fmt), **kwargs_in + str_template.format(loop_over.data_as_list[frame], unit, freq_fmt), **kwargs_in ) else: str_template = "frq={0:{2}} {1}" diff --git a/src/ansys/dpf/core/fields_container.py b/src/ansys/dpf/core/fields_container.py index 07161c3b1e..bb81bd9b3f 100644 --- a/src/ansys/dpf/core/fields_container.py +++ b/src/ansys/dpf/core/fields_container.py @@ -571,6 +571,10 @@ def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): # First define the workflow index input forward_index = dpf.core.operators.utility.forward() wf.set_input_name("loop_over", forward_index.inputs.any) + # Add a time values input + forward_time = dpf.core.operators.utility.forward() + wf.set_input_name("loop_over_values", forward_time.inputs.any) + wf.set_output_name("time_values", forward_time.outputs.any) # 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