diff --git a/doc/source/links_and_refs.rst b/doc/source/links_and_refs.rst index 1bb88ec64a..3e37c60fa2 100644 --- a/doc/source/links_and_refs.rst +++ b/doc/source/links_and_refs.rst @@ -22,6 +22,7 @@ .. Other libraries repos .. _pyvista_github : https://github.com/pyvista/pyvista +.. _matplotlib_github : https://github.com/matplotlib/matplotlib .. External links .. _sphinx: https://www.sphinx-doc.org/en/master/ diff --git a/doc/source/user_guide/tutorials/plot/index.rst b/doc/source/user_guide/tutorials/plot/index.rst index 32f11495e5..d595bdd603 100644 --- a/doc/source/user_guide/tutorials/plot/index.rst +++ b/doc/source/user_guide/tutorials/plot/index.rst @@ -11,34 +11,64 @@ These tutorials demonstrate some different approaches to visualise the data in p :padding: 2 :margin: 2 - .. grid-item-card:: Plotting meshes - :link: ref_tutorials + .. grid-item-card:: Plot a mesh + :link: ref_tutorials_plot_mesh :link-type: ref :text-align: center - This tutorial + This tutorial shows different plotting commands to plot a bare mesh - .. grid-item-card:: Plotting data on the mesh - :link: ref_tutorials + .. grid-item-card:: Plot a deformed mesh + :link: ref_tutorials_plot_deformed_mesh :link-type: ref :text-align: center - This tutorial + This tutorial shows different plotting commands to plot a bare deformed mesh - .. grid-item-card:: Plotting data on specific placements - :link: ref_tutorials + .. grid-item-card:: Plot data on a mesh + :link: ref_plot_data_on_a_mesh :link-type: ref :text-align: center - This tutorial + This tutorial explains how to plot data on its supporting mesh by + different approaches. - .. grid-item-card:: Plotting a graph - :link: ref_tutorials + .. grid-item-card:: Plot data on a deformed mesh + :link: ref_plot_data_on_deformed_mesh :link-type: ref :text-align: center - This tutorial + This tutorial explains how to plot data on its supporting deformed mesh. + + .. grid-item-card:: Plot data on custom path + :link: ref_plot_data_on_custom_path + :link-type: ref + :text-align: center + + This tutorial shows how to plot data on custom path + + .. grid-item-card:: Plot data on custom geometry + :link: ref_plot_data_on_custom_geometry + :link-type: ref + :text-align: center + + This tutorial shows how to plot data on custom geometric shapes + + .. grid-item-card:: Plot a graph + :link: ref_plot_a_graph + :link-type: ref + :text-align: center + + This tutorial explains how to plot a graph with data in DPF .. toctree:: :maxdepth: 2 :hidden: + + plot_mesh.rst + plot_deformed_mesh.rst + plot_data_on_a_mesh.rst + plot_data_on_deformed_mesh.rst + plot_data_on_custom_path.rst + plot_data_on_custom_geometry.rst + plot_a_graph.rst \ No newline at end of file diff --git a/doc/source/user_guide/tutorials/plot/plot_a_graph.rst b/doc/source/user_guide/tutorials/plot/plot_a_graph.rst new file mode 100644 index 0000000000..113aad8427 --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_a_graph.rst @@ -0,0 +1,255 @@ +.. _ref_plot_a_graph: + +==================== +Plot data on a graph +==================== + +.. |Line| replace:: :class:`Line ` +.. |mapping| replace:: :class:`mapping ` +.. |Line.path| replace:: :func:`Line.path` +.. |min_max_fc| replace:: :class:`min_max_fc ` + +This tutorial explains how to plot a graph with data in DPF. + +The current |DpfPlotter| module don't have method to plotting graphs. Thus, you need to import the +`matplotlib `_ library to plot a graph with PyDPF-Core. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +There is a large range of graphs you can plot. Here, we plot: + +- :ref:`Results data vs. space position graph ` +- :ref:`Results data vs. time graph ` + +.. _ref_graph_result_space: + +Results data vs. space position +------------------------------- + +In this tutorial, we plot the norm of the displacement results on a |Line|. For more information about how +this object can be defined, see the :ref:`ref_plot_data_on_custom_geometry` tutorial. + +Define the results data +^^^^^^^^^^^^^^^^^^^^^^^ + +First, import a results file. For this tutorial, you can use the one available in the |Examples| module. +For more information about how to import your own result file in DPF, see +the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + # Import the geometry module + from ansys.dpf.core import geometry as geo + + # Import the ``matplotlib.pyplot`` module + import matplotlib.pyplot as plt + + # Define the result file path + result_file_path_1 = examples.find_static_rst() + +The results will be mapped over a defined set of coordinates. Thus, we need the spatial support to +those coordinates: the mesh. The mesh object in DPF is a |MeshedRegion|. + +You can obtain a |MeshedRegion| by creating your own from scratch or by getting it from a result file. +For more information, see the :ref:`ref_tutorials_create_a_mesh_from_scratch` and +:ref:`ref_tutorials_get_mesh_from_result_file` tutorials. + +Here, we extract it from the result file. + +.. jupyter-execute:: + + # Create the model + model_1 = dpf.Model(data_sources=result_file_path_1) + + # Extract the mesh + meshed_region_1 = model_1.metadata.meshed_region + +Extract the results to be plotted on the graph. In this tutorial, we plot the norm of the +displacement results over time. + +.. jupyter-execute:: + + # Get the displacement results + disp_results_1 = model_1.results.displacement.eval() + +Define the line +^^^^^^^^^^^^^^^ + +Create a |Line| passing through the mesh diagonal. + +.. jupyter-execute:: + + # Create the Line object + line_1 = geo.Line(coordinates=[[0.0, 0.06, 0.0], [0.03, 0.03, 0.03]], + n_points=50 + ) + +Map the results to the line +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Map the displacement results to the |Line| using the |mapping| operator. This operator +retrieves the results of the entities located in the given coordinates. If the given coordinates don't +match with any entity coordinate, the operator interpolates the results inside elements with shape functions. + +The displacement results are defined in a *`nodal`* location. Thus, each node has a coordinate in the +mesh and a corresponding displacement data. + +The |mapping| operator takes the coordinates stored in a |Field|. Thus, we must create a |Field| with the +|Line| coordinates. + +.. jupyter-execute:: + + # Get the coordinates field + line_coords_field = line_1.mesh.nodes.coordinates_field + + # Map the line coordinates with the displacement results + mapped_disp_line = ops.mapping.on_coordinates(fields_container=disp_results_1, + coordinates=line_coords_field, + create_support=True, + mesh=meshed_region_1 + ).eval()[0] + +Plot the graph +^^^^^^^^^^^^^^ + +Plot a graph of the norm of the displacement results along the |Line| length using the +`matplotlib `_ library. + +To get the |Line| length you can use the |Line.path| method. It gives the 1D line coordinates, based on +the points where the line was discretized. + +.. jupyter-execute:: + + # Define the norm of the displacement results + norm_disp = ops.math.norm(field=mapped_disp_line).eval() + + # Define the point coordinates on the line length + line_length_points = line_1.path + + # Define the plot figure + plt.plot(line_length_points, norm_disp.data) + + # Graph formating + plt.xlabel("Line length"); plt.ylabel("Displacement norm field"); plt.title("Displacement evolution on the line") + + # Display the graph + plt.show() + +.. _ref_graph_result_time: + +Results data vs. time +--------------------- + +In this tutorial, we plot the displacement results over time for a transient analysis. +For more information about using PyDPF-Core with a transient analysis, see the :ref:`static_transient_examples` examples. + +Define the results data +^^^^^^^^^^^^^^^^^^^^^^^ + +First, import a transient results file. For this tutorial, you can use the one available in the |Examples| module. +For more information about how to import your own result file in DPF, see +the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + + # Import the ``matplotlib.pyplot`` module + import matplotlib.pyplot as plt + + # Define the result file path + result_file_path_2 = examples.download_transient_result() + +The results will be mapped over a defined path of coordinates. Thus, we need the spatial support to +those coordinates: the mesh. The mesh object in DPF is a |MeshedRegion|. + +You can obtain a |MeshedRegion| by creating your own from scratch or by getting it from a result file. +For more information, see the :ref:`ref_tutorials_create_a_mesh_from_scratch` and +:ref:`ref_tutorials_get_mesh_from_result_file` tutorials. + +Here, we extract it from the result file. + +.. jupyter-execute:: + + # Create the model + model_2 = dpf.Model(data_sources=result_file_path_2) + + # Extract the mesh + meshed_region_2 = model_2.metadata.meshed_region + +Extract the results to be plotted on the graph. Here, we plot the maximum and minimum +displacement results over time. + +First extract the displacement results for all the time frequencies. + +.. jupyter-execute:: + + # Get the displacement results + disp_results_2 = model_2.results.displacement.on_all_time_freqs.eval() + +Next, define the minimal and maximal displacements for each time step by using the |min_max_fc| +operator. + +.. jupyter-execute:: + + # Define the min_max operator and give the normed displacement results + min_max_op = ops.min_max.min_max_fc(fields_container=ops.math.norm_fc(disp_results_2)) + + # Get the max displacement results + max_disp = min_max_op.eval(pin=1) + + # Get the min displacement results + min_disp = min_max_op.eval(pin=0) + +Define the time data +^^^^^^^^^^^^^^^^^^^^ + +The results time steps in DPF are given by the |TimeFreqSupport| object. You can extract it +from the displacement results |Field|. + +.. jupyter-execute:: + + # Define the time steps + time_steps_1 = disp_results_2.time_freq_support.time_frequencies + + # Print the time frequencies + print(time_steps_1) + +The time steps are given in a |Field|. To plot the graph you need to extract the +|Field| data. + +.. jupyter-execute:: + + # Get the time steps data + time_data = time_steps_1.data + + +Plot the graph +^^^^^^^^^^^^^^ + +Plot a graph of the minimal and maximal displacements over time using the +`matplotlib `_ library. + +.. jupyter-execute:: + + # Define the plot figure + plt.plot(time_data, max_disp.data, "r", label="Max") + plt.plot(time_data, min_disp.data, "b", label="Min") + + # Graph formating + plt.xlabel("Time (s)"); plt.ylabel("Displacement (m)"); plt.legend(); + + # Display the graph + plt.show() \ No newline at end of file diff --git a/doc/source/user_guide/tutorials/plot/plot_data_on_a_mesh.rst b/doc/source/user_guide/tutorials/plot/plot_data_on_a_mesh.rst new file mode 100644 index 0000000000..f97fe5638f --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_data_on_a_mesh.rst @@ -0,0 +1,232 @@ +.. _ref_plot_data_on_a_mesh: + +=================== +Plot data on a mesh +=================== + +.. |Field.plot| replace:: :func:`Field.plot()` +.. |MeshedRegion.plot| replace:: :func:`MeshedRegion.plot()` +.. |add_mesh| replace:: :func:`add_mesh()` +.. |add_field| replace:: :func:`add_field()` +.. |show_figure| replace:: :func:`show_figure()` +.. |to_nodal_fc| replace:: :class:`to_nodal_fc ` +.. |select_component| replace:: :func:`select_component() ` +.. |stress_op| replace:: :class:`stress ` + +This tutorial shows how to plot data on its supporting mesh by different approaches. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Define the data +--------------- + +First, import a results file. For this tutorial, you can use the one available in the |Examples| module. +For more information about how to import your own result file in DPF, see +the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + + # Define the result file path + result_file_path_1 = examples.find_multishells_rst() + +The |Model| is a helper designed to give shortcuts to access the analysis results +metadata and to instanciate results providers by opening a |DataSources| or a Streams. + +Printing the model displays the available results. + +.. jupyter-execute:: + + # Create the model + model_1 = dpf.Model(data_sources=result_file_path_1) + + # Print the model + print(model_1) + +Extract the data to be plotted. For more information about extracting results from a result file, +see the :ref:`ref_tutorials_import_data` tutorials section. + +.. note:: + + Only the *'elemental'* or *'nodal'* locations are supported for plotting. + +Here, we chose to plot the XX stress tensor component data. + +First, get the stress results using the |stress_op| operator. + +.. jupyter-execute:: + + # Extract the stress results + stress_result = model_1.results.stress() + + # Print the results + print(stress_result.eval()) + +We must request the stress in a *'nodal'* location as the default *'ElementalNodal'* location for the stress results +is not supported for plotting. + +There are different ways to change the location. Here, we define the new location using the input of the |stress_op| +operator. Another option would be using an averaging operator, like the |to_nodal_fc| operator + +.. jupyter-execute:: + + # Define the desired location as an input of the stress operator + stress_result.inputs.requested_location(dpf.locations.nodal) + + # Get the output (here a FieldsContainer) + fc_stress = stress_result.eval() + +To get the results for the XX stress component, we use the |select_component| method. This methods takes +the index the component as an input. The stress tensor has 6 components per elementary data +(symmetrical tensor XX,YY,ZZ,XY,YZ,XZ). Thus, we get the component of index=0 + +.. jupyter-execute:: + + # Get the stress results for the XX component + fc_stress_XX = fc_stress.select_component(index=0) + +Define the mesh +--------------- + +The mesh object in DPF is a |MeshedRegion|. Thus, to plot the data on a mesh you need a |MeshedRegion| to be based on. +Here, we get a |MeshedRegion| from a result file. For more information about how to extract a |MeshedRegion| +from a result file, see the :ref:`ref_tutorials_get_mesh_from_result_file` tutorial. + +.. jupyter-execute:: + + # Define the meshed region + meshed_region_1 = model_1.metadata.meshed_region + +Plot the data on the mesh +------------------------- + +There are two different approaches to plot the data on the mesh: + +- :ref:`Plot the data on its mesh support ` +- :ref:`Plot the mesh and add the data on top of that ` + +.. hint:: + + :ref:`ref_method_plot_data_mesh_2` is faster than :ref:`ref_method_plot_data_mesh_1` + + +.. _ref_method_plot_data_mesh_1: + +Plot the data on its mesh support +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Plotting the data in DPF means plotting the |Field| that contains the data. +To plot a |Field|, you can use: + +- The |Field.plot| method; +- The |DpfPlotter| object. + +.. hint:: + + Using the |DpfPlotter| class is faster than using the |Field.plot| method + +.. tab-set:: + + .. tab-item:: Field.plot() method + + First, get a |Field| from the stress results |FieldsContainer|. Then, use the |Field.plot| method [1]_. + If the |Field| does not have a predefined mesh support, you must use the *'meshed_region'* argument and + give the |Field| supporting mesh. + + .. jupyter-execute:: + + # Define the field + field_stress_XX = fc_stress_XX[0] + + # Plot the data on the mesh + field_stress_XX.plot(meshed_region=meshed_region_1) + + .. tab-item:: DpfPlotter object + + First define the |DpfPlotter| object [2]_. Then, add the |Field| to it using the |add_field| method. + If the |Field| does not have a predefined mesh support, you must use the *'meshed_region'* argument + and give the |Field| supporting mesh. + + To display the figure built by the plotter object, use the |show_figure| method. + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_1 = dpf.plotter.DpfPlotter() + + # Add the Field and MeshedRegion to the DpfPlotter object + plotter_1.add_field(field=field_stress_XX, meshed_region=meshed_region_1) + + # Display the plot + plotter_1.show_figure() + +.. _ref_method_plot_data_mesh_2: + +Plot the mesh and add the data on top of that +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To plot the |MeshedRegion| and add the data on top of that you can use: + +- The |MeshedRegion.plot| method; +- The |DpfPlotter| object. + +.. hint:: + + The |DpfPlotter| class is faster than using the |MeshedRegion.plot| method. + +.. tab-set:: + + .. tab-item:: MeshedRegion.plot() method + + For this approach, you can use data stored in a |Field| or in a |FieldsContainer|. + In this tutorial, we use data stored in a |Field|. + + Use the |MeshedRegion.plot| method [1]_. You must use the *'field_or_fields_container'* argument and + give the |Field| or the |FieldsContainer| containing the stress results data. + + .. jupyter-execute:: + + # Plot the mesh and add the stress results + meshed_region_1.plot(field_or_fields_container=field_stress_XX) + + .. tab-item:: DpfPlotter object + + First, define the |DpfPlotter| object [2]_. Then, add the |MeshedRegion| + and the |Field| using the |add_mesh| and |add_field| methods respectively. + + To display the figure built by the plotter object use the |show_figure| method. + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_2 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + plotter_2.add_mesh(meshed_region=meshed_region_1) + + # Add the Field to the DpfPlotter object + plotter_2.add_field(field=field_stress_XX) + + # Display the plot + plotter_2.show_figure() + +.. rubric:: Footnotes + +.. [1] The default plotter settings display the mesh with edges, lighting and axis widget enabled. +Nevertheless, as we use the `PyVista `_ library to create the plot, you can use additional +PyVista arguments (available at `pyvista.plot() `_). + +.. [2] The |DpfPlotter| object is currently a PyVista based object. +That means that PyVista must be installed, and that it supports kwargs as +parameter (the argument must be supported by the installed PyVista version). +More information about the available arguments are available at `pyvista.plot() `_`. + +The default |DpfPlotter| object settings displays the mesh with edges and lighting +enabled. Nevertheless, as we use the `PyVista `_ +library to create the plot, you can use additional PyVista arguments for the |DpfPlotter| +object and |add_field| method (available at `pyvista.plot() `_`). \ No newline at end of file diff --git a/doc/source/user_guide/tutorials/plot/plot_data_on_custom_geometry.rst b/doc/source/user_guide/tutorials/plot/plot_data_on_custom_geometry.rst new file mode 100644 index 0000000000..ab8c1fb955 --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_data_on_custom_geometry.rst @@ -0,0 +1,392 @@ +.. _ref_plot_data_on_custom_geometry: + +============================ +Plot data on custom geometry +============================ + +.. |add_mesh| replace:: :func:`add_mesh()` +.. |add_field| replace:: :func:`add_field()` +.. |show_figure| replace:: :func:`show_figure()` +.. |Model| replace:: :class:`Model ` +.. |Line| replace:: :class:`Line ` +.. |Points| replace:: :class:`Points ` +.. |Plane| replace:: :class:`Plane ` +.. |mapping| replace:: :class:`mapping ` +.. |nodes_coordinates| replace:: :class:`nodes_coordinates` +.. |Points.plot| replace:: :func:`Points.plot()` +.. |Line.plot| replace:: :func:`Line.plot()` +.. |Plane.plot| replace:: :func:`Plane.plot()` + +This tutorials shows how to get a result mapped over different geometric objects: + +- Points +- Line +- Plane + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Define the data +--------------- + +First, import a results file. For this tutorial, you can use the one available in the |Examples| module. +For more information about how to import your own result file in DPF, see +the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + # Import the geometry module + from ansys.dpf.core import geometry as geo + + # Define the result file path + result_file_path_1 = examples.find_static_rst() + +The results will be mapped over a defined set of coordinates. Thus, we need the spatial support to +those coordinates: the mesh. The mesh object in DPF is a |MeshedRegion|. + +You can obtain a |MeshedRegion| by creating your own from scratch or by getting it from a result file. +For more information, see the :ref:`ref_tutorials_create_a_mesh_from_scratch` and +:ref:`ref_tutorials_get_mesh_from_result_file` tutorials. + +Here, we extract it from the result file. + +.. jupyter-execute:: + + # Create the model + model_1 = dpf.Model(data_sources=result_file_path_1) + + # Extract the mesh + meshed_region_1 = model_1.metadata.meshed_region + +Extract the results to be plotted on the geometry elements. Here, we get the displacement results. + +.. jupyter-execute:: + + # Get the displacement results + disp_results = model_1.results.displacement.eval() + +To a better visualization of the mesh and the geometry elements, we define a camera position. +A camera position is a combination of: + +- A position; +- A focal point (the target); +- A upwards vector. + +It results in a list of format: + +.. code-block:: python + + camera_position= [[pos_x, pos_y, pos_z], # position + [fp_x, fp_y, fp_z], # focal point + [up_x, up_y, up_z]] # upwards vector + +.. jupyter-execute:: + + # Define the camera position + camera_position = [ + (0.07635352356975698, 0.1200500294271993, 0.041072502929096165), + (0.015, 0.045, 0.015), + (-0.16771051558419411, -0.1983722658245161, 0.9656715938216944), + ] + +Create the geometry elements +---------------------------- + +The geometry elements must be in the space domain of the mesh. You can verify the range of coordinates +values by checking the nodes coordinates. + +You can get the nodes coordinates with the |nodes_coordinates| operator. + +.. jupyter-execute:: + + # Get the nodes coordinates + nodes_coords = ops.mesh.node_coordinates(mesh=meshed_region_1).eval() + +To obtain the domain limits, get the maximal and minimal values of the nodes coordinates. + +.. jupyter-execute:: + + # Get the maximal nodes coordinates + max_coords = ops.min_max.min_max(field=nodes_coords).eval(pin=1) + + # Get the minimal nodes coordinates + min_coords = ops.min_max.min_max(field=nodes_coords).eval(pin=0) + + # Print the space domain limits + print("Max coordinates:", max_coords.data, '\n') + print("Min coordinates:", min_coords.data) + + +.. tab-set:: + + .. tab-item:: Points + + Create |Points| by defining their coordinates. + + The coordinates are define at the global Cartesian coordinates system by default. Thus, combining + the max and min coordinates gives us the points that are in the corner of the mesh. We can also + place one point in the middle of the mesh by calculating the middle distance between the coordinates. + + You can do it by hand or by calculating this combinations. + + .. jupyter-execute:: + + # Define the coordinates of the point on the middle of the mesh + # 1) Get the distance between the max and min coordinates + distance_minmax_coords = ops.math.minus(fieldA=max_coords.data_as_list, fieldB=min_coords.data_as_list).eval() + # 2) Get the middle of that distance + middle = ops.math.scale(field=distance_minmax_coords, ponderation=0.5).eval() + # 3) Find the coordinate to the point on the middle of the mesh + middle_coords = ops.math.add(fieldA=min_coords.data_as_list,fieldB=middle.data_as_list).eval() + + # Define the points coordinates + pts = geo.Points(coordinates=[ + [0.0, 0.03, 0.0], + [0.0, 0.06, 0.0], + [0.03, 0.06, 0.0], + [0.03, 0.03, 0.0], + [0.0, 0.03, 0.03], + [0.0, 0.06, 0.03], + [0.03, 0.06, 0.03], + [0.03, 0.03, 0.03], + middle_coords.data_as_list + ] + ) + + .. tab-item:: Line + + Create a |Line| passing through the mesh diagonal. To create a |Line| + you must define: + + - The coordinates of the starting point + - The coordinates of the ending point + - The number of points where the |Line| object will be discretized. + + .. jupyter-execute:: + + # Create the Line object + line_1 = geo.Line(coordinates=[[0.0, 0.06, 0.0], [0.03, 0.03, 0.03]], + n_points=50 + ) + + .. tab-item:: Plane + + Create a vertical |Plane| passing through the mesh mid point. To create a |Plane| + you must define: + + - The coordinates of the point in the center of the plane + - The vector of the normal direction to the plane + - The plane width (x direction) + - The plane height (y direction) + - The number of cells (x and y direction) where the |Plane| object will be discretized. + + .. jupyter-execute:: + + # Define the coordinates of the point on the middle of the mesh + # 1) Get the distance between the max and min coordinates + distance_minmax_coords = ops.math.minus(fieldA=max_coords.data_as_list, fieldB=min_coords.data_as_list).eval() + # 2) Get the middle of that distance + middle = ops.math.scale(field=distance_minmax_coords, ponderation=0.5).eval() + # 3) Find the coordinate to the point on the middle of the mesh + middle_coords = ops.math.add(fieldA=min_coords.data_as_list,fieldB=middle.data_as_list).eval() + + # Create the Plane object + plane_1 = geo.Plane(center=middle_coords.data_as_list, + normal=[1, 1, 0], + width=0.03, + height=0.03, + n_cells_x=10, + n_cells_y=10, + ) + +Plot the geometry elements on the mesh +-------------------------------------- + +.. tab-set:: + + .. tab-item:: Points + + You can plot the |Points| objects on the mesh using the |Points.plot| method [1]_. + + .. jupyter-execute:: + + # Display the mesh and the points + pts.plot(mesh=meshed_region_1, cpos=camera_position) + + .. tab-item:: Line + + You can plot the |Line| object on the mesh using the |Line.plot| method [1]_. + + .. jupyter-execute:: + + # Display the mesh and the line + line_1.plot(mesh=meshed_region_1, cpos=camera_position) + + .. tab-item:: Plane + + You can plot the |Plane| object on the mesh using the |Plane.plot| method [1]_. + + .. jupyter-execute:: + + # Display the mesh and the plane + plane_1.plot(mesh=meshed_region_1, cpos=camera_position) + +Map the results to the geometry elements +---------------------------------------- + +Map the displacement results to the geometry elements using the |mapping| operator. This operator +retrieves the results of the entities located in the given coordinates (The path coordinates have to be in +the space domain of the mesh). If the given coordinates don't match with any entity coordinate, the operator +interpolates the results inside elements with shape functions. + +The displacement results are defined in a *`nodal`* location. Thus, each node has a coordinate in the +mesh and a corresponding displacement data. + +.. tab-set:: + + .. tab-item:: Points + + The |mapping| operator takes the coordinates stored in a |Field|. Thus, we must create a |Field| with the + |Points| coordinates. + + .. jupyter-execute:: + + # Create the coordinates field + points_coords_field = dpf.fields_factory.field_from_array(arr=pts.coordinates.data) + + # Map the points coordinates with the displacement results + mapped_disp_points = ops.mapping.on_coordinates(fields_container=disp_results, + coordinates=points_coords_field, + create_support=True, + mesh=meshed_region_1 + ).eval()[0] + + .. tab-item:: Line + + The |mapping| operator takes the coordinates stored in a |Field|. Thus, we must create a |Field| with the + |Line| coordinates. + + .. jupyter-execute:: + + # Get the coordinates field + line_coords_field = line_1.mesh.nodes.coordinates_field + + # Map the line coordinates with the displacement results + mapped_disp_line = ops.mapping.on_coordinates(fields_container=disp_results, + coordinates=line_coords_field, + create_support=True, + mesh=meshed_region_1 + ).eval()[0] + + .. tab-item:: Plane + + The |mapping| operator takes the coordinates stored in a |Field|. Thus, we must create a |Field| with the + |Plane| coordinates. + + .. jupyter-execute:: + + # Get the coordinates field + plane_coords_field = plane_1.mesh.nodes.coordinates_field + + # Map the plane coordinates with the displacement results + mapped_disp_plane = ops.mapping.on_coordinates(fields_container=disp_results, + coordinates=plane_coords_field, + create_support=True, + mesh=meshed_region_1 + ).eval()[0] + +Plot the results on the geometry elements +----------------------------------------- + +To plot the results on the path, we use the |DpfPlotter| object. For more information about +plotting data on a mesh, see the :ref:`ref_plot_data_on_a_mesh` tutorial. + +First, define the |DpfPlotter| object [2]_. Next, add the |MeshedRegion| +and the |Field| containing the results using the |add_mesh| and |add_field| methods respectively. + +To display the figure built by the plotter object use the |show_figure| method. + +.. tab-set:: + + .. tab-item:: Points + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_1 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + # We use custom style for the mesh so we can visualize the path (that is inside the mesh) + plotter_1.add_mesh(meshed_region=meshed_region_1, + style="surface",show_edges=True, color="w", opacity=0.3) + + # Add the Field to the DpfPlotter object + plotter_1.add_field(field=mapped_disp_points, + point_size=20.0, + render_points_as_spheres=True) + + # Display the plot + plotter_1.show_figure(show_axes=True, + cpos=camera_position) + + .. tab-item:: Line + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_2 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + # We use custom style for the mesh so we can visualize the path (that is inside the mesh) + plotter_2.add_mesh(meshed_region=meshed_region_1, + style="surface",show_edges=True, color="w", opacity=0.3) + + # Add the Field to the DpfPlotter object + plotter_2.add_field(field=mapped_disp_line) + + # Display the plot + plotter_2.show_figure(show_axes=True, + cpos=camera_position) + + .. tab-item:: Plane + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_3 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + # We use custom style for the mesh so we can visualize the path (that is inside the mesh) + plotter_3.add_mesh(meshed_region=meshed_region_1, + style="surface",show_edges=True, color="w", opacity=0.3) + + # Add the Field to the DpfPlotter object + plotter_3.add_field(field=mapped_disp_plane, + meshed_region=plane_1.mesh, + show_edges=False) + + # Display the plot + plotter_3.show_figure(show_axes=True, + cpos=camera_position) + +.. rubric:: Footnotes + +.. [1] The default plotter settings display the mesh with edges, lighting and axis widget enabled. +Nevertheless, as we use the `PyVista `_ library to create the plot, you can use additional +PyVista arguments (available at `pyvista.plot() `_). + +.. [2] The |DpfPlotter| object is currently a PyVista based object. +That means that PyVista must be installed, and that it supports kwargs as +parameter (the argument must be supported by the installed PyVista version). +More information about the available arguments are available at `pyvista.plot() `_`. + +The default |DpfPlotter| object settings displays the mesh with edges and lighting +enabled. Nevertheless, as we use the `PyVista `_ +library to create the plot, you can use additional PyVista arguments for the |DpfPlotter| +object and |add_field| method (available at `pyvista.plot() `_`). diff --git a/doc/source/user_guide/tutorials/plot/plot_data_on_custom_path.rst b/doc/source/user_guide/tutorials/plot/plot_data_on_custom_path.rst new file mode 100644 index 0000000000..5ec231b456 --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_data_on_custom_path.rst @@ -0,0 +1,185 @@ +.. _ref_plot_data_on_custom_path: + +======================== +Plot data on custom path +======================== + +.. |add_mesh| replace:: :func:`add_mesh()` +.. |add_field| replace:: :func:`add_field()` +.. |show_figure| replace:: :func:`show_figure()` +.. |Line| replace:: :class:`Line ` +.. |Points| replace:: :class:`Points ` +.. |Plane| replace:: :class:`Plane ` +.. |mapping| replace:: :class:`mapping ` +.. |nodes_coordinates| replace:: :class:`nodes_coordinates` + +This tutorial shows how to get a result mapped over a specific path and how to plot it. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Define the data +--------------- + +First, import a results file. For this tutorial, you can use the one available in the |Examples| module. +For more information about how to import your own result file in DPF, see +the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + + # Define the result file path + result_file_path_1 = examples.find_static_rst() + +The results will be mapped over a defined path of coordinates. Thus, we need the spatial support to +those coordinates: the mesh. The mesh object in DPF is a |MeshedRegion|. + +You can obtain a |MeshedRegion| by creating your own from scratch or by getting it from a result file. +For more information, see the :ref:`ref_tutorials_create_a_mesh_from_scratch` and +:ref:`ref_tutorials_get_mesh_from_result_file` tutorials. + +Here, we extract it from the result file. + +.. jupyter-execute:: + + # Create the model + model_1 = dpf.Model(data_sources=result_file_path_1) + + # Extract the mesh + meshed_region_1 = model_1.metadata.meshed_region + +Extract the results to be plotted on the path. Here, we get the equivalent stress results. + +.. jupyter-execute:: + + # Get the equivalent stress results + eq_stress = model_1.results.stress().eqv().eval() + +Define the path +--------------- + +The path coordinates have to be in the space domain of the mesh. You can verify the +range of coordinates existing on the |MeshedRegion| by checking the nodes coordinates. + +You can get the nodes coordinates with the |nodes_coordinates| operator. + +.. jupyter-execute:: + + # Get the nodes coordinates + nodes_coords = ops.mesh.node_coordinates(mesh=meshed_region_1).eval() + +To obtain the domain limits, get the maximal and minimal values of the nodes coordinates. + +.. jupyter-execute:: + + # Get the maximal nodes coordinates + max_coords = ops.min_max.min_max(field=nodes_coords).eval(pin=1) + + # Get the minimal nodes coordinates + min_coords = ops.min_max.min_max(field=nodes_coords).eval(pin=0) + + # Print the space domain limits + print("Max coordinates:", max_coords.data, '\n') + print("Min coordinates:", min_coords.data) + +Create the path based on a set of coordinates. Here, define the path by choosing: + +- The origin coordinates of the path; +- Number of points in the path; +- The distance between each point coordinate. + +.. jupyter-execute:: + + # Initial coordinates + initial_coords = [0.024, 0.03, 0.003] + + # Number of points in the path + n_points = 51 + + # Distance between each opint coordinate + delta = 0.001 + +The coordinates must be stored in a |Field|. For more information about how to create a Field, see +the :ref:`ref_tutorials_data_structures` and :ref:`ref_tutorials_load_custom_data` tutorials. + +.. jupyter-execute:: + + # Create the paths coordinates Field + path_coords = dpf.fields_factory.create_3d_vector_field(n_points) + path_coords.scoping.ids = list(range(0, n_points)) + +Here, we make a loop to define the paths coordinates. For each iteration, we add to the |Field| a new set of +coordinates based on the predefined distance between each coordinate. The path only moves along the y-axis. + +.. jupyter-execute:: + + # Define the path coordinates + for i in range(0, n_points): + initial_coords[1] += delta + path_coords.append(data=initial_coords, scopingid=0) + +Map the results to the path +--------------------------- + +Map the stress data to the path using the |mapping| operator. The |mapping| operator retrieves the results +of the entities located in the given coordinates (those coordinates have to be in the space domain of the mesh). +If the given coordinates don't match with any entity coordinate, operator interpolates the results inside elements +with shape functions. + +.. jupyter-execute:: + + # Map the stress results to the path coordinates + mapped_stress = ops.mapping.on_coordinates(fields_container=eq_stress, + coordinates=path_coords, + create_support=True, + mesh=meshed_region_1 + ).eval() + +Plot the results on the path +---------------------------- + +To plot the results on the path, we use the |DpfPlotter| object. For more information about +plotting data on a mesh, see the :ref:`ref_plot_data_on_a_mesh` tutorial. + +First, define the |DpfPlotter| object [2]_. Next, add the |MeshedRegion| +and the |Field| using the |add_mesh| and |add_field| methods respectively. + +To display the figure built by the plotter object use the |show_figure| method. + +.. jupyter-execute:: + + # Define the DpfPlotter object + plotter_1 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + # We use custom style for the mesh so we can visualize the path (that is inside the mesh) + plotter_1.add_mesh(meshed_region=meshed_region_1, + style="surface",show_edges=True, color="w", opacity=0.3) + + # Add the Field to the DpfPlotter object + plotter_1.add_field(field=mapped_stress[0]) + + # Display the plot + plotter_1.show_figure() + +.. rubric:: Footnotes + +.. [1] The default plotter settings display the mesh with edges, lighting and axis widget enabled. +Nevertheless, as we use the `PyVista `_ library to create the plot, you can use additional +PyVista arguments (available at `pyvista.plot() `_). + +.. [2] The |DpfPlotter| object is currently a PyVista based object. +That means that PyVista must be installed, and that it supports kwargs as +parameter (the argument must be supported by the installed PyVista version). +More information about the available arguments are available at `pyvista.plot() `_`. + +The default |DpfPlotter| object settings displays the mesh with edges and lighting +enabled. Nevertheless, as we use the `PyVista `_ +library to create the plot, you can use additional PyVista arguments for the |DpfPlotter| +object and |add_field| method (available at `pyvista.plot() `_`). \ No newline at end of file diff --git a/doc/source/user_guide/tutorials/plot/plot_data_on_deformed_mesh.rst b/doc/source/user_guide/tutorials/plot/plot_data_on_deformed_mesh.rst new file mode 100644 index 0000000000..cc87ad0bfc --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_data_on_deformed_mesh.rst @@ -0,0 +1,281 @@ +.. _ref_plot_data_on_deformed_mesh: + +========================== +Plot data on deformed mesh +========================== + +.. |to_nodal_fc| replace:: :class:`to_nodal_fc() ` +.. |select_component| replace:: :func:`select_component() ` +.. |split_mesh| replace:: :class:`split_mesh ` +.. |stress_op| replace:: :class:`stress ` +.. |Field.plot| replace:: :func:`Field.plot()` +.. |MeshedRegion.plot| replace:: :func:`MeshedRegion.plot()` + +This tutorial shows how to plot data on the deformed mesh. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Define the data +--------------- + +First, import a results file. For this tutorial, you can use the one available in the |Examples| module. +For more information about how to import your own result file in DPF, see +the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + + # Define the result file path + result_file_path_1 = examples.find_multishells_rst() + +The |Model| is a helper designed to give shortcuts to access the analysis results +metadata and to instanciate results providers by opening a |DataSources| or a Streams. + +Printing the model displays the available results. + +.. jupyter-execute:: + + # Create the model + model_1 = dpf.Model(data_sources=result_file_path_1) + + # Print the model + print(model_1) + +Extract the data to be plotted on the deformed mesh. + +.. note:: + + Only the *'elemental'* or *'nodal'* locations are supported for plotting. + +Here, we chose to plot the XX stress tensor component data. Thud, get the stress results using the |stress_op| operator. + +.. jupyter-execute:: + + # Extract the stress results + stress_result = model_1.results.stress() + + # Print the results + print(stress_result.eval()) + +We must request the stress in a *'nodal'* location as the default *'ElementalNodal'* location for the stress results +is not supported for plotting. + +There are different ways to change the location. Here, we define the new location using the input of the |stress_op| +operator. Another option would be using an averaging operator, like the |to_nodal_fc| operator + +.. jupyter-execute:: + + # Define the desired location as an input of the stress operator + stress_result.inputs.requested_location(dpf.locations.nodal) + + # Get the output (here a FieldsContainer) + fc_stress = stress_result.eval() + +To get the results for the XX stress component, we use the |select_component| method. This methods takes +the index the component as an input. The stress tensor has 6 components per elementary data +(symmetrical tensor XX,YY,ZZ,XY,YZ,XZ). Thus, we get the component of index=0 + +.. jupyter-execute:: + + # Get the stress results for the XX component + fc_stress_XX = fc_stress.select_component(index=0) + +Define the mesh +--------------- + +The mesh object in DPF is a |MeshedRegion|. You can store multiple |MeshedRegion| in a DPF collection +called |MeshesContainer|. Thus, the geometry can be defined by a |MeshedRegion| or by a |MeshesContainer|. + +First, extract the |MeshedRegion| from the |Model|. + +.. jupyter-execute:: + + # Define the MeshedRegion + meshed_region_1 = model_1.metadata.meshed_region + +There are different ways to obtain a |MeshesContainer|. You can, for example, split a given |MeshedRegion| in different +parts. + +Here, we get a |MeshesContainer| by splitting the |MeshedRegion| by material using the |split_mesh| operator. +This operator gives a |MeshesContainer| with the |MeshedRegion| split parts with a *'mat'* label. + +.. jupyter-execute:: + + # Define the MeshesContainer + meshes_1 = ops.mesh.split_mesh(mesh=meshed_region_1).eval() + +Define the deforming actor +-------------------------- + +The geometry can be deformed by: + +- A |Result| object; +- An |Operator|; +- A |Field|; +- A |FieldsContainer|. + +Here, we deform the mesh using an |Operator|. + +To deform the mesh we need values with a homogeneous unit dimension, a distance unit. +Thus, to deform the mesh we need the displacement results. + +First, extract the displacements results |Operator| from the |Model|. For more information about extracting results +from a result file, see the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Get the displacement results Operator + disp_op = model_1.results.displacement() + +Plot data on the deformed geometry +---------------------------------- + +Plotting the data in DPF means plotting the |Field| that contains the data. +Get a |Field| from the |FieldsContainer| containing the stress results . + +.. jupyter-execute:: + + # Define the field + field_stress_XX = fc_stress_XX[0] + +There are two different approaches to plot the data on the deformed mesh: + +- :ref:`Plot the data on its mesh support `; +- :ref:`Plot the mesh and add the stress data on top of that `. + +For all approaches, we use a scale factor so the deformed mesh fits properly on the plot. + +.. jupyter-execute:: + + # Define the scale factor + scl_fct = 0.001 + +.. _ref_method_plot_data_deformed_mesh_1: + +Plot the data on its mesh support +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Plotting the data in DPF means plotting the |Field| that contains the data. +To plot a |Field| on the deformed mesh, you can use: + +- The |Field.plot| method; +- The |DpfPlotter| object. + +Plot the stress results |Field| on the deformed geometry using the |Field.plot| method. Use the +*'deform_by'* argument and give the displacement results. + +.. tab-set:: + + .. tab-item:: Field.plot() method + + To plot the stress results in the deformed mesh, use the |Field.plot| method [1]_. + Additionally, you must use the *'meshed_region'* and *'deform_by'* arguments and + give the mesh and displacement results. + + .. jupyter-execute:: + + # Plot the stress results on the deformed mesh + field_stress_XX.plot(meshed_region=meshed_region_1, + deform_by=disp_op, + scale_factor=scl_fct) + + .. tab-item:: DpfPlotter object + + First define the |DpfPlotter| object [2]_. Then, add the |Field| to it using the |add_field| method. + You must use the *'meshed_region'* and *'deform_by'* arguments and give the mesh and displacement results. + + To display the figure built by the plotter object, use the |show_figure| method. + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_1 = dpf.plotter.DpfPlotter() + + # Add the Field and MeshedRegion to the DpfPlotter object + plotter_1.add_field(field=field_stress_XX, + meshed_region=meshed_region_1, + deform_by=disp_op, + scale_factor=scl_fct) + + # Display the plot + plotter_1.show_figure() + +.. _ref_method_plot_data_deformed_mesh_2: + +Plot the mesh and add the stress data on top of that +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To plot the deformed |MeshedRegion| and add the data on top of that you can use: + +- The |MeshedRegion.plot| method; +- The |DpfPlotter| object. + +.. hint:: + + The |DpfPlotter| class is faster than using the |MeshedRegion.plot| method. + +.. tab-set:: + + .. tab-item:: MeshedRegion.plot() method + + For this approach, you can use data stored in a |Field| or in a |FieldsContainer|. + In this tutorial, we use data stored in a |Field|. + + To plot the stress results in the deformed mesh, use the |MeshedRegion.plot| method [1]_. + You must use the *'field_or_fields_container'* and *'deform_by'* arguments and give the + stress and the displacement results. + + .. jupyter-execute:: + + # Plot the deformed mesh and add the stress results + meshed_region_1.plot(field_or_fields_container=field_stress_XX, + deform_by=disp_op, + scale_factor=scl_fct) + + .. tab-item:: DpfPlotter object + + First, define the |DpfPlotter| object [2]_. Then, add the |MeshedRegion| + and the |Field| using the |add_mesh| and |add_field| methods respectively. + + To display the figure built by the plotter object use the |show_figure| method. + + .. jupyter-execute:: + + # Define the DpfPlotter object + plotter_2 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + plotter_2.add_mesh(meshed_region=meshed_region_1) + + # Add the Field to the DpfPlotter object + plotter_2.add_field(field=field_stress_XX) + + # Display the plot + plotter_2.show_figure() + + +.. rubric:: Footnotes + +.. [1] The default plotter settings display the mesh with edges, lighting and axis widget enabled. +Nevertheless, as we use the `PyVista `_ library to create the plot, you can use additional +PyVista arguments (available at `pyvista.plot() `_). + +.. [2] The |DpfPlotter| object is currently a PyVista based object. +That means that PyVista must be installed, and that it supports kwargs as +parameter (the argument must be supported by the installed PyVista version). +More information about the available arguments are available at `pyvista.plot() `_`. + +The default |DpfPlotter| object settings displays the mesh with edges and lighting +enabled. Nevertheless, as we use the `PyVista `_ +library to create the plot, you can use additional PyVista arguments for the |DpfPlotter| +object and |add_field| method (available at `pyvista.plot() `_`). + + + diff --git a/doc/source/user_guide/tutorials/plot/plot_deformed_mesh.rst b/doc/source/user_guide/tutorials/plot/plot_deformed_mesh.rst new file mode 100644 index 0000000000..53b96caada --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_deformed_mesh.rst @@ -0,0 +1,213 @@ +.. _ref_tutorials_plot_deformed_mesh: + +==================== +Plot a deformed mesh +==================== + +.. |Model.plot| replace:: :func:`Model.plot()` +.. |MeshedRegion.plot| replace:: :func:`MeshedRegion.plot() ` +.. |MeshesContainer.plot| replace:: :func:`MeshesContainer.plot()` +.. |add_mesh| replace:: :func:`add_mesh()` +.. |show_figure| replace:: :func:`show_figure()` +.. |split_mesh| replace:: :class:`split_mesh ` + +This tutorial shows different plotting commands to plot the bare deformed mesh +of a model. + +DPF-Core has a variety of plotting methods for generating 3D plots of +Ansys models directly from Python. These methods use VTK and leverage +the `PyVista `_ library to simplify plotting. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Define the mesh +--------------- + +The mesh object in DPF is a |MeshedRegion|. You can store multiple |MeshedRegion| in a DPF collection +called |MeshesContainer|. + +You can obtain a |MeshedRegion| by creating your own from scratch or by getting it from a result file. +For more information, see the :ref:`ref_tutorials_create_a_mesh_from_scratch` and +:ref:`ref_tutorials_get_mesh_from_result_file` tutorials. + +For this tutorial, we get a |MeshedRegion| from a result file. You can use one available in the |Examples| module. +For more information see the :ref:`ref_tutorials_get_mesh_from_result_file` tutorial. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + + # Define the result file path + result_file_path_1 = examples.find_multishells_rst() + + # Define the DataSources + ds_1 = dpf.DataSources(result_path=result_file_path_1) + + # Create a model + model_1 = dpf.Model(data_sources=ds_1) + + # Extract the mesh + meshed_region_1 = model_1.metadata.meshed_region + +There are different ways to obtain a |MeshesContainer|. You can, for example, split a |MeshedRegion| extracted +from the result file. + +Here, we get a |MeshesContainer| by splitting the |MeshedRegion| by material +using the |split_mesh| operator. This operator gives a |MeshesContainer| with the |MeshedRegion| split parts +with a *'mat'* label. For more information about how to get a split mesh, see the :ref:`ref_tutorials_split_mesh` +and :ref:`ref_tutorials_extract_mesh_in_split_parts` tutorials. + +.. jupyter-execute:: + + # Extract the mesh in split parts + meshes = ops.mesh.split_mesh(mesh=meshed_region_1, property="mat").eval() + +Define the deforming actor +-------------------------- + +The geometry can be deformed by: + +- A |Result| object; +- An |Operator|; +- A |Field|; +- A |FieldsContainer|. + +Here, we deform the mesh using an |Operator|. + +To deform the mesh we need values with a homogeneous unit dimension, a distance unit. +Thus, to deform the mesh we need the displacement results. + +First, extract the displacements results |Operator| from the |Model|. For more information about extracting results +from a result file, see the :ref:`ref_tutorials_import_data` tutorials section. + +.. jupyter-execute:: + + # Get the displacement results Operator + disp_results = model_1.results.displacement() + +Plot the deformed mesh +---------------------- + +To display a deformed mesh, you can: + +- :ref:`Plot the Model `; +- :ref:`Plot the MeshedRegion `; +- :ref:`Plot the MeshesContainer `. + +For all approaches, we use a scale factor so the deformed mesh fits properly on the plot. + +.. jupyter-execute:: + + # Define the scale factor + scl_fct = 0.001 + +.. _ref_plot_deformed_mesh_with_model: + +Plot the |Model| +^^^^^^^^^^^^^^^^ + +To plot the |Model|, you have to use the |Model.plot| method [1]_. This method plots the +bare mesh associated to the result file by default. Thus,you must also use the *'deform_by'* +argument and give the displacement results. + +.. jupyter-execute:: + + # Plot the deformed mesh + model_1.plot(deform_by=disp_results, + scale_factor=scl_fct, ) + +.. _ref_plot_deformed_mesh_with_meshed_region: + +Plot the |MeshedRegion| +^^^^^^^^^^^^^^^^^^^^^^^ + +To plot the deformed |MeshedRegion| you can use: + +- The |MeshedRegion.plot| method; +- The |DpfPlotter| object. + +.. tab-set:: + + .. tab-item:: MeshedRegion.plot() method + + To plot the mesh with this approach, use the |MeshedRegion.plot| method [1]_ with + the |MeshedRegion| object we defined. Additionally, you must use the *'deform_by'* + argument and give the displacement results. + + .. jupyter-execute:: + + # Plot the deformed mesh + meshed_region_1.plot(deform_by=disp_results, + scale_factor=scl_fct, ) + + .. tab-item:: DpfPlotter object + + To plot the mesh with this approach, start by defining the |DpfPlotter| object [2]_. + Then, add the |MeshedRegion| to it, using the |add_mesh| method. Additionally, you must + use the *'deform_by'* argument and give the displacement results. + + To display the figure built by the plotter object use the |show_figure| method. + + .. jupyter-execute:: + + # Declare the DpfPlotter object + plotter_1 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + plotter_1.add_mesh(meshed_region=meshed_region_1, + deform_by=disp_results, + scale_factor=scl_fct, ) + + # Display the plot + plotter_1.show_figure() + +.. _ref_plot_deformed_mesh_with_meshes_container: + +Plot the |MeshesContainer| +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To plot the deformed |MeshesContainer| you must use the |MeshesContainer.plot| method [1]_ with +the |MeshesContainer| object we defined.Additionally, you must use the *'deform_by'* +argument and give the displacement results. + +This method plots all the |MeshedRegion| stored in the |MeshesContainer| and their color code respects the +property used to split the mesh. + +.. jupyter-execute:: + + # Plot the deformed mesh + meshes.plot(deform_by=disp_results, + scale_factor=scl_fct, ) + +.. rubric:: Footnotes + +.. [1] The default plotter settings display the mesh with edges, lighting and axis widget enabled. +Nevertheless, as we use the `PyVista `_ library to create the plot, you can use additional +PyVista arguments (available at `pyvista.plot() `_), such as: + +.. jupyter-execute:: + + model_1.plot(deform_by=disp_results, + scale_factor=scl_fct, + title= "Model plot", + text= "Model.plot()", # Adds the given text at the bottom of the plot + window_size=[450, 450]) + # Notes: + # - To save a screenshot to file, use "screenshot=figure_name.png" ( as well as "notebook=False" if on a Jupyter notebook). + # - The "off_screen" keyword only works when "notebook=False". If "off_screen=True" the plot is not displayed when running the code. + +.. [2] The |DpfPlotter| object is currently a PyVista based object. +That means that PyVista must be installed, and that it supports kwargs as +parameter (the argument must be supported by the installed PyVista version). +More information about the available arguments are available at `pyvista.plot() `_. + +The default |DpfPlotter| object settings displays the mesh with edges and lighting +enabled. Nevertheless, as we use the `PyVista `_ +library to create the plot, you can use additional PyVista arguments for the |DpfPlotter| +object and |add_field| method (available at `pyvista.plot() `_). \ No newline at end of file diff --git a/doc/source/user_guide/tutorials/plot/plot_mesh.rst b/doc/source/user_guide/tutorials/plot/plot_mesh.rst new file mode 100644 index 0000000000..6b8002c48b --- /dev/null +++ b/doc/source/user_guide/tutorials/plot/plot_mesh.rst @@ -0,0 +1,180 @@ +.. _ref_tutorials_plot_mesh: + +=========== +Plot a mesh +=========== + +.. |Model.plot| replace:: :func:`Model.plot()` +.. |MeshedRegion.plot| replace:: :func:`MeshedRegion.plot() ` +.. |MeshesContainer.plot| replace:: :func:`MeshesContainer.plot()` +.. |add_mesh| replace:: :func:`add_mesh()` +.. |show_figure| replace:: :func:`show_figure()` +.. |split_mesh| replace:: :class:`split_mesh ` + +This tutorial shows different plotting commands to plot the bare mesh +of a model. + +DPF-Core has a variety of plotting methods for generating 3D plots of +Ansys models directly from Python. These methods use VTK and leverage +the `PyVista `_ library to simplify plotting. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Define the mesh +--------------- + +The mesh object in DPF is a |MeshedRegion|. You can store multiple |MeshedRegion| in a DPF collection +called |MeshesContainer|. + +You can obtain a |MeshedRegion| by creating your own from scratch or by getting it from a result file. +For more information, see the :ref:`ref_tutorials_create_a_mesh_from_scratch` and +:ref:`ref_tutorials_get_mesh_from_result_file` tutorials. + +For this tutorial, we get a |MeshedRegion| from a result file. You can use one available in the |Examples| module. +For more information see the :ref:`ref_tutorials_get_mesh_from_result_file` tutorial. + +.. jupyter-execute:: + + # Import the ``ansys.dpf.core`` module + from ansys.dpf import core as dpf + # Import the examples module + from ansys.dpf.core import examples + # Import the operators module + from ansys.dpf.core import operators as ops + + # Define the result file path + result_file_path_1 = examples.find_multishells_rst() + + # Define the DataSources + ds_1 = dpf.DataSources(result_path=result_file_path_1) + + # Create a model + model_1 = dpf.Model(data_sources=ds_1) + + # Extract the mesh + meshed_region_1 = model_1.metadata.meshed_region + +There are different ways to obtain a |MeshesContainer|. You can, for example, split a |MeshedRegion| extracted +from the result file. + +Here, we get a |MeshesContainer| by splitting the |MeshedRegion| by material +using the |split_mesh| operator. This operator gives a |MeshesContainer| with the |MeshedRegion| split parts +with a *'mat'* label. For more information about how to get a split mesh, see the :ref:`ref_tutorials_split_mesh` +and :ref:`ref_tutorials_extract_mesh_in_split_parts` tutorials. + +.. jupyter-execute:: + + # Extract the mesh in split parts + meshes = ops.mesh.split_mesh(mesh=meshed_region_1, property="mat").eval() + +Plot the mesh +------------- + +To plot the mesh you can: + +- :ref:`Plot the Model `; +- :ref:`Plot the MeshedRegion `; +- :ref:`Plot the MeshesContainer `. + +.. _method_plot_mesh_1: + +Plot the |Model| +^^^^^^^^^^^^^^^^ + +To plot the mesh with this approach, you have to use the |Model.plot| method [1]_. +This method plots the bare mesh associated to the result file by default. + +.. jupyter-execute:: + + # Plot the mesh + model_1.plot() + +.. _method_plot_mesh_2: + +Plot the |MeshedRegion| +^^^^^^^^^^^^^^^^^^^^^^^ + +To plot the |MeshedRegion| you can use: + +- The |MeshedRegion.plot| method; +- The |DpfPlotter| object. + +.. tab-set:: + + .. tab-item:: MeshedRegion.plot() method + + To plot the mesh with this approach, use the |MeshedRegion.plot| method [1]_ with + the |MeshedRegion| object we defined. + + .. jupyter-execute:: + + # Plot the deformed mesh + meshed_region_1.plot() + + .. tab-item:: DpfPlotter object + + To plot the mesh with this approach, start by defining the |DpfPlotter| object [2]_. + Then, add the |MeshedRegion| to it, using the |add_mesh| method. + + To display the figure built by the plotter object, use the |show_figure| method. + + .. jupyter-execute:: + + # Declare the DpfPlotter object + plotter_1 = dpf.plotter.DpfPlotter() + + # Add the MeshedRegion to the DpfPlotter object + plotter_1.add_mesh(meshed_region=meshed_region_1, ) + + # Display the plot + plotter_1.show_figure() + +As the meshed region is generated from the |Model|, the plot displayed here is identical to the plot generated by +the :ref:`method_plot_mesh_1` approach. + +.. _method_plot_mesh_3: + +Plot the |MeshesContainer| +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To plot the |MeshesContainer| you must use the |MeshesContainer.plot| method [1]_ with +the |MeshesContainer| object we defined. + +This method plots all the |MeshedRegion| stored in the |MeshesContainer| and their color code respects the +property used to split the mesh. + +.. jupyter-execute:: + + # Plot the mesh + meshes.plot() + +You can also plot results data on its supporting mesh. For more information, see :ref:`ref_plot_data_on_a_mesh` + +.. rubric:: Footnotes + +.. [1] The default plotter settings display the mesh with edges, lighting and axis widget enabled. +Nevertheless, as we use the `PyVista `_ library to create the plot, you can use additional +PyVista arguments (available at `pyvista.plot() `_), such as: + +.. jupyter-execute:: + + model_1.plot(title= "Mesh", + text= "Plot mesh method 1", # Adds the given text at the bottom of the plot + off_screen=True, + screenshot="mesh_plot_1.png", # Save a screenshot to file with the given name + window_size=[1050,350] + ) + # Notes: + # - To save a screenshot to file, use the "screenshot" argument (as well as "notebook=False" if on a Jupyter notebook). + # - The "off_screen" keyword only works when "notebook=False". If "off_screen=True" the plot is not displayed when running the code. + +.. [2] The |DpfPlotter| object is currently a PyVista based object. +That means that PyVista must be installed, and that it supports kwargs as +parameter (the argument must be supported by the installed PyVista version). +More information about the available arguments are available at `pyvista.plot() `_. + +The default |DpfPlotter| object settings displays the mesh with edges and lighting +enabled. Nevertheless, as we use the `PyVista `_ +library to create the plot, you can use additional PyVista arguments for the |DpfPlotter| +object and |add_mesh| method (available at `pyvista.plot() `_). \ No newline at end of file