Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MeshesContainer.animate over time #1997

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/ansys/dpf/core/animator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}"
Expand Down
4 changes: 4 additions & 0 deletions src/ansys/dpf/core/fields_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 64 additions & 4 deletions src/ansys/dpf/core/meshes_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
5 changes: 4 additions & 1 deletion src/ansys/dpf/core/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading