|
23 | 23 | """ |
24 | 24 | .. _lsdyna_operators: |
25 | 25 |
|
26 | | -Results extraction and analysis from LS-DYNA sources |
27 | | -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 26 | +Beam results manipulations |
| 27 | +-------------------------- |
28 | 28 |
|
29 | 29 | This example provides an overview of the LS-DYNA beam results manipulations. |
30 | 30 |
|
|
40 | 40 | from ansys.dpf.core import operators as ops |
41 | 41 |
|
42 | 42 | ############################################################################### |
43 | | -# d3plot file results extraction |
44 | | -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 43 | +# d3plot file data extraction |
| 44 | +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
45 | 45 | # Create the model and print its contents. This LS-DYNA d3plot file contains |
46 | 46 | # several individual results, each at different times. The d3plot file does not |
47 | 47 | # contain information related to Units. |
48 | | - |
| 48 | +# |
49 | 49 | # In this case, as the simulation was run through Mechanical, a ''file.actunits'' |
50 | 50 | # file is produced. If this file is supplemented in the data_sources, the units |
51 | 51 | # will be correctly fetched for all results in the file as well as for the mesh. |
|
58 | 58 | print(my_model) |
59 | 59 |
|
60 | 60 | ############################################################################### |
| 61 | +# Exploring the mesh |
| 62 | +# ~~~~~~~~~~~~~~~~~~ |
| 63 | +# |
61 | 64 | # The model has solid (3D) elements and beam (1D) elements. Some of the results |
62 | 65 | # only apply to one type of elements (such as the stress tensor for solids, or |
63 | 66 | # the axial force for beams, for example). |
64 | | - |
| 67 | +# |
65 | 68 | # By splitting the mesh by element shape we see that the ball is made by the solid |
66 | 69 | # 3D elements and the plate by the beam 1D elements |
67 | | - |
68 | | -# Define the analysis mesh |
| 70 | +# |
| 71 | +# - Define the analysis mesh |
69 | 72 | my_meshed_region = my_model.metadata.meshed_region |
70 | 73 |
|
71 | | -# Get separate meshes for each body |
| 74 | +# - Get separate meshes for each body |
72 | 75 | my_meshes = ops.mesh.split_mesh( |
73 | 76 | mesh=my_meshed_region, property=dpf.common.elemental_properties.element_shape |
74 | 77 | ).eval() |
75 | 78 |
|
76 | | -# Define the meshes for each body in separate variables |
| 79 | +# - Define the meshes for each body in separate variables |
77 | 80 | ball_mesh = my_meshes.get_mesh(label_space_or_index={"body": 1, "elshape": 1}) |
78 | 81 | plate_mesh = my_meshes.get_mesh(label_space_or_index={"body": 2, "elshape": 2}) |
79 | 82 |
|
80 | | -# print(my_meshes) |
| 83 | +print(my_meshes) |
81 | 84 |
|
82 | 85 | ############################################################################### |
83 | | -# Ball |
| 86 | +# Plate mesh |
84 | 87 |
|
85 | | -print("Ball mesh", "\n", ball_mesh, "\n") |
86 | | -ball_mesh.plot(title="Ball mesh", text="Ball mesh") |
| 88 | +print("Plate mesh", "\n", plate_mesh) |
| 89 | +plate_mesh.plot(title="Plate mesh", text="Plate mesh") |
87 | 90 |
|
88 | 91 | ############################################################################### |
89 | | -# Plate |
| 92 | +# Ball mesh |
90 | 93 |
|
91 | | -print("Plate mesh", "\n", plate_mesh) |
92 | | -plate_mesh.plot(title="Plate mesh", text="Plate mesh") |
| 94 | +print("Ball mesh", "\n", ball_mesh, "\n") |
| 95 | +ball_mesh.plot(title="Ball mesh", text="Ball mesh") |
93 | 96 |
|
94 | 97 | ############################################################################### |
95 | | -# Define the mesh scoping to use it with the operators |
| 98 | +# Scoping |
| 99 | +# ~~~~~~~ |
| 100 | +# |
| 101 | +# - Define the mesh scoping to use it with the operators |
96 | 102 | my_meshes_scoping = ops.scoping.split_on_property_type(mesh=my_meshed_region).eval() |
97 | 103 |
|
98 | | -# Define the mesh scoping for each body/element shape in separate variables |
| 104 | +############################################################################### |
| 105 | +# - Define the mesh scoping for each body/element shape in separate variables |
99 | 106 | ball_scoping = my_meshes_scoping.get_scoping(label_space_or_index={"elshape": 1}) |
100 | 107 | plate_scoping = my_meshes_scoping.get_scoping(label_space_or_index={"elshape": 2}) |
101 | 108 |
|
102 | | -# We will plot the results in a mesh deformed by the displacement. The displacement |
103 | | -# is in a nodal location, so we need to define a nodal scoping for the palte |
| 109 | +############################################################################### |
| 110 | +# - We will plot the results in a mesh deformed by the displacement. |
| 111 | +# The displacement is in a nodal location, so we need to define a nodal scoping for the plate |
104 | 112 | plate_scoping_nodal = dpf.operators.scoping.transpose( |
105 | 113 | mesh_scoping=plate_scoping, meshed_region=my_meshed_region |
106 | 114 | ).eval() |
107 | 115 |
|
108 | 116 | ############################################################################### |
109 | | - |
| 117 | +# Beam results |
| 118 | +# ~~~~~~~~~~~~ |
110 | 119 | # The next manipulations can be applied to the following beam operators |
111 | 120 | # that handle the correspondent results : |
112 | | - |
| 121 | +# |
113 | 122 | # - beam_axial_force: Beam Axial Force |
114 | 123 | # - beam_s_shear_force: Beam S Shear Force |
115 | 124 | # - beam_t_shear_force: Beam T Shear Force |
|
121 | 130 | # - beam_tr_shear_stress: Beam Tr Shear Stress |
122 | 131 | # - beam_axial_plastic_strain: Beam Axial Plastic Strain |
123 | 132 | # - beam_axial_total_strain: Beam Axial Total Strain |
124 | | - |
| 133 | +# |
125 | 134 | # We do not demonstrate separately how to use each of them in this example |
126 | | -# once they have similar methods. We .... in the beam stress and forces results |
127 | | - |
| 135 | +# once they have similar methods. |
| 136 | +# |
128 | 137 | # So, if you want to operate on other operator, uou just need to change their |
129 | 138 | # scripting name in the code lines. |
130 | 139 |
|
|
137 | 146 |
|
138 | 147 | # 2) Prepare the collections to store the results for each time step |
139 | 148 |
|
140 | | -# To compare the results in the same image you have to copy the mesh for each plot |
| 149 | +# a. To compare the results in the same image you have to copy the mesh for each plot |
141 | 150 | plate_meshes = dpf.MeshesContainer() |
142 | 151 | plate_meshes.add_label("time") |
143 | 152 |
|
144 | | -# The displacements for each time steps to deform the mesh accordingly |
| 153 | +# b. The displacements for each time steps to deform the mesh accordingly |
145 | 154 | plate_displacements = dpf.FieldsContainer() |
146 | 155 | plate_displacements.add_label(label="time") |
147 | 156 |
|
148 | | -# The axial force results for each time steps. Here |
| 157 | +# c. The axial force results for each time steps. Here |
149 | 158 | plate_axial_force = dpf.FieldsContainer() |
150 | 159 | plate_axial_force.add_label(label="time") |
151 | 160 |
|
152 | | -# 3) Use the :class: `Plotter <ansys.dpf.core.plotter.DpfPlotter>` class |
153 | | -# to add the plots in the same image |
| 161 | +# 3) Use the Plotter class to add the plots in the same image |
154 | 162 | comparison_plot = dpf.plotter.DpfPlotter() |
155 | 163 |
|
| 164 | +# Side bar arguments definition |
156 | 165 | side_bar_args = dict( |
157 | 166 | title="Beam axial force (N)", fmt="%.2e", title_font_size=15, label_font_size=15 |
158 | 167 | ) |
|
161 | 170 | # It represents the distance between the meshes |
162 | 171 | j = -400 |
163 | 172 |
|
| 173 | +# 5) Copy the mesh of interest. Here it is the plate mesh that we copy along the X axis |
164 | 174 | # Here we use a loop where each iteration correspond to the manipulations for a given time step |
165 | 175 |
|
166 | | -# 5) Copy the mesh of interest. Here it is the plate mesh that we copy along the X axis |
167 | 176 | for i in time_steps_set: # Loop through the time steps |
168 | 177 | # Copy the mesh |
169 | 178 | plate_meshes.add_mesh(label_space={"time": i}, mesh=plate_mesh.deep_copy()) |
|
213 | 222 | # 12) Increment the coordinate value for the loop |
214 | 223 | j = j - 400 |
215 | 224 |
|
| 225 | + |
216 | 226 | # Visualise the plot |
217 | 227 | comparison_plot.show_figure() |
218 | 228 |
|
219 | 229 | ############################################################################### |
220 | 230 | # Plot a graph over time for the elements with max and min results values |
221 | 231 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
222 | | - |
| 232 | +# |
223 | 233 | # Here we make a workflow with a more verbose approach. This is useful because we use operators |
224 | 234 | # having several matching inputs or outputs. So the connexions are more clear, and it is |
225 | 235 | # easier to use and reuse the workflow. |
226 | | - |
227 | | -# Find the element with the max values over all the time steps and return its ID |
| 236 | +# |
| 237 | +# The following workflow finds the element with the max values over all the time steps and return its ID |
228 | 238 |
|
229 | 239 | # Define the workflow object |
230 | 240 | max_workflow = dpf.Workflow() |
|
249 | 259 | max_workflow.set_output_name("max_entity_scoping", max_scop.outputs.mesh_scoping_as_scoping) |
250 | 260 |
|
251 | 261 | ############################################################################### |
| 262 | +# Using the workflow to the stresses results on the plate: |
| 263 | +# |
| 264 | +# - Extract the results |
| 265 | + |
252 | 266 | # Get all the time steps |
253 | 267 | time_all = my_model.metadata.time_freq_support.time_frequencies |
| 268 | + |
254 | 269 | # Extract all the stresses results on the plate |
255 | 270 | plate_beam_axial_stress = my_model.results.beam_axial_stress( |
256 | 271 | time_scoping=time_all, mesh_scoping=plate_scoping |
|
262 | 277 | time_scoping=time_all, mesh_scoping=plate_scoping |
263 | 278 | ).eval() |
264 | 279 |
|
265 | | -# List of operators to simplify the code |
| 280 | +############################################################################### |
| 281 | +# - As we will use the workflow for different results operators we group them and |
| 282 | +# use a loop through the group. Here we prepare where the workflow outputs will be stored |
| 283 | + |
| 284 | +# List of operators to be used in the workflow |
266 | 285 | beam_stresses = [plate_beam_axial_stress, plate_beam_rs_shear_stress, plate_beam_tr_shear_stress] |
267 | 286 | graph_labels = [ |
268 | 287 | "Beam axial stress", |
269 | 288 | "Beam rs shear stress", |
270 | 289 | "Beam tr shear stress", |
271 | 290 | ] |
272 | | -# List of elements ids |
| 291 | + |
| 292 | +# List of elements ids that we will get from the workflow |
273 | 293 | max_stress_elements_ids = [] |
| 294 | + |
274 | 295 | # Scopings container |
275 | 296 | max_stress_elements_scopings = dpf.ScopingsContainer() |
276 | 297 | max_stress_elements_scopings.add_label("stress_result") |
277 | 298 |
|
278 | | -# Loop through each stress result that gets the elements with maximum solicitation id, re-escope the fields |
279 | | -# container to keep only the data for this element, and finally plot a stress x time graph |
| 299 | +############################################################################### |
| 300 | +# - The following loop: |
| 301 | +# a) Goes through each stress result and get the element id with maximum solicitation |
| 302 | +# b) Re-escope the fields container to keep only the data for this element |
| 303 | +# c) Plot a stress x time graph |
280 | 304 |
|
281 | 305 | for j in range(0, len(beam_stresses)): # Loop through each stress result |
282 | 306 | # Use the pre-defined workflow to define the element with maximum solicitation |
|
309 | 333 | label=f"{graph_labels[j]}, element id:{max_stress_elements_ids[j][0]}", |
310 | 334 | ) |
311 | 335 |
|
| 336 | +# Graph formatting |
312 | 337 | plt.title("Beam stresses evolution") |
313 | 338 | plt.xlabel("Time (s)") |
314 | 339 | plt.ylabel("Beam stresses (MPa)") |
|
318 | 343 | ############################################################################### |
319 | 344 | # Results coordinates system |
320 | 345 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
321 | | - |
322 | | -# The results are given in the Cartesian coordinates system by default. |
323 | | - |
324 | | -# The beam results are given directly in the local directions. For example the beam stresses: |
325 | | - |
326 | | -# We have the axial stress, given in the beam axis, and the stresses defined in the |
327 | | -# cross-section directions, tr stress in the transverse direction (t) and rs stress |
328 | | -# perpendicular to the tr direction (s). |
329 | | - |
330 | | -# Those results are given as scalars. |
331 | | - |
| 346 | +# |
| 347 | +# The general results are given in the Cartesian coordinates system by default. |
| 348 | +# |
| 349 | +# The beam results are given directly in the local directions as scalars. |
| 350 | +# For example the beam stresses we have: |
| 351 | +# |
| 352 | +# - The axial stress, given in the beam axis |
| 353 | +# - The stresses defined in the cross-section directions: tr stress in the transverse |
| 354 | +# direction (t) and rs stress perpendicular to the tr direction (s). |
| 355 | +# |
| 356 | +# |
332 | 357 | # Unfortunately there are no operators for LS-DYNA files that directly allows you to: |
333 | 358 | # - Rotate results from local coordinate system to global coordinate system; |
334 | 359 | # - Extract the rotation matrix between the local and global coordinate systems; |
0 commit comments