pyedb 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyedb might be problematic. Click here for more details.

Files changed (128) hide show
  1. pyedb/__init__.py +17 -0
  2. pyedb/dotnet/__init__.py +0 -0
  3. pyedb/dotnet/application/Variables.py +2261 -0
  4. pyedb/dotnet/application/__init__.py +0 -0
  5. pyedb/dotnet/clr_module.py +103 -0
  6. pyedb/dotnet/edb.py +4237 -0
  7. pyedb/dotnet/edb_core/__init__.py +1 -0
  8. pyedb/dotnet/edb_core/cell/__init__.py +0 -0
  9. pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
  10. pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
  11. pyedb/dotnet/edb_core/components.py +2669 -0
  12. pyedb/dotnet/edb_core/configuration.py +423 -0
  13. pyedb/dotnet/edb_core/definition/__init__.py +0 -0
  14. pyedb/dotnet/edb_core/definition/component_def.py +166 -0
  15. pyedb/dotnet/edb_core/definition/component_model.py +30 -0
  16. pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
  17. pyedb/dotnet/edb_core/definition/definitions.py +12 -0
  18. pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
  19. pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
  20. pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
  21. pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
  22. pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
  23. pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
  24. pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
  25. pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
  26. pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
  27. pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
  28. pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
  29. pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
  30. pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
  31. pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
  32. pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
  33. pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
  34. pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
  35. pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
  36. pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
  37. pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
  38. pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
  39. pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
  40. pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
  41. pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
  42. pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
  43. pyedb/dotnet/edb_core/general.py +181 -0
  44. pyedb/dotnet/edb_core/hfss.py +1646 -0
  45. pyedb/dotnet/edb_core/layout.py +1244 -0
  46. pyedb/dotnet/edb_core/layout_validation.py +272 -0
  47. pyedb/dotnet/edb_core/materials.py +939 -0
  48. pyedb/dotnet/edb_core/net_class.py +335 -0
  49. pyedb/dotnet/edb_core/nets.py +1215 -0
  50. pyedb/dotnet/edb_core/padstack.py +1389 -0
  51. pyedb/dotnet/edb_core/siwave.py +1427 -0
  52. pyedb/dotnet/edb_core/stackup.py +2703 -0
  53. pyedb/edb_logger.py +396 -0
  54. pyedb/generic/__init__.py +0 -0
  55. pyedb/generic/constants.py +1063 -0
  56. pyedb/generic/data_handlers.py +320 -0
  57. pyedb/generic/design_types.py +104 -0
  58. pyedb/generic/filesystem.py +150 -0
  59. pyedb/generic/general_methods.py +1535 -0
  60. pyedb/generic/plot.py +1840 -0
  61. pyedb/generic/process.py +285 -0
  62. pyedb/generic/settings.py +224 -0
  63. pyedb/ipc2581/__init__.py +0 -0
  64. pyedb/ipc2581/bom/__init__.py +0 -0
  65. pyedb/ipc2581/bom/bom.py +21 -0
  66. pyedb/ipc2581/bom/bom_item.py +32 -0
  67. pyedb/ipc2581/bom/characteristics.py +37 -0
  68. pyedb/ipc2581/bom/refdes.py +16 -0
  69. pyedb/ipc2581/content/__init__.py +0 -0
  70. pyedb/ipc2581/content/color.py +38 -0
  71. pyedb/ipc2581/content/content.py +55 -0
  72. pyedb/ipc2581/content/dictionary_color.py +29 -0
  73. pyedb/ipc2581/content/dictionary_fill.py +28 -0
  74. pyedb/ipc2581/content/dictionary_line.py +30 -0
  75. pyedb/ipc2581/content/entry_color.py +13 -0
  76. pyedb/ipc2581/content/entry_line.py +14 -0
  77. pyedb/ipc2581/content/fill.py +15 -0
  78. pyedb/ipc2581/content/layer_ref.py +10 -0
  79. pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
  80. pyedb/ipc2581/ecad/__init__.py +0 -0
  81. pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
  82. pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
  83. pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
  84. pyedb/ipc2581/ecad/cad_data/component.py +41 -0
  85. pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
  86. pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
  87. pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
  88. pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
  89. pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
  90. pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
  91. pyedb/ipc2581/ecad/cad_data/package.py +104 -0
  92. pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
  93. pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
  94. pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
  95. pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
  96. pyedb/ipc2581/ecad/cad_data/path.py +89 -0
  97. pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
  98. pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
  99. pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
  100. pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
  101. pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
  102. pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
  103. pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
  104. pyedb/ipc2581/ecad/cad_data/step.py +275 -0
  105. pyedb/ipc2581/ecad/cad_header.py +33 -0
  106. pyedb/ipc2581/ecad/ecad.py +19 -0
  107. pyedb/ipc2581/ecad/spec.py +46 -0
  108. pyedb/ipc2581/history_record.py +37 -0
  109. pyedb/ipc2581/ipc2581.py +387 -0
  110. pyedb/ipc2581/logistic_header.py +25 -0
  111. pyedb/misc/__init__.py +0 -0
  112. pyedb/misc/aedtlib_personalib_install.py +14 -0
  113. pyedb/misc/downloads.py +322 -0
  114. pyedb/misc/misc.py +67 -0
  115. pyedb/misc/pyedb.runtimeconfig.json +13 -0
  116. pyedb/misc/siw_feature_config/__init__.py +0 -0
  117. pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
  118. pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
  119. pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
  120. pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
  121. pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
  122. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
  123. pyedb/misc/utilities.py +27 -0
  124. pyedb/modeler/geometry_operators.py +2082 -0
  125. pyedb-0.2.0.dist-info/LICENSE +21 -0
  126. pyedb-0.2.0.dist-info/METADATA +208 -0
  127. pyedb-0.2.0.dist-info/RECORD +128 -0
  128. pyedb-0.2.0.dist-info/WHEEL +4 -0
pyedb/generic/plot.py ADDED
@@ -0,0 +1,1840 @@
1
+ import ast
2
+ from collections import defaultdict
3
+ import csv
4
+ from datetime import datetime
5
+ import math
6
+ import os
7
+ import tempfile
8
+ import time
9
+ import warnings
10
+
11
+ from pyedb.generic.constants import AEDT_UNITS, CSS4_COLORS
12
+ from pyedb.generic.general_methods import (
13
+ is_ironpython,
14
+ open_file,
15
+ pyedb_function_handler,
16
+ )
17
+
18
+ if not is_ironpython: # pragma: no cover
19
+ try:
20
+ import numpy as np
21
+ except ImportError:
22
+ warnings.warn(
23
+ "The NumPy module is required to run some functionalities of PostProcess.\n"
24
+ "Install with \n\npip install numpy\n\nRequires CPython."
25
+ )
26
+
27
+ try:
28
+ import pyvista as pv
29
+
30
+ pyvista_available = True
31
+ except ImportError:
32
+ warnings.warn(
33
+ "The PyVista module is required to run some functionalities of PostProcess.\n"
34
+ "Install with \n\npip install pyvista\n\nRequires CPython."
35
+ )
36
+
37
+ try:
38
+ from matplotlib.patches import PathPatch
39
+ from matplotlib.path import Path
40
+
41
+ # Use matplotlib agg backend (non-interactive) when the CI is running.
42
+ if bool(int(os.getenv("PYEDB_CI_NO_DISPLAY", "0"))): # pragma: no cover
43
+ import matplotlib
44
+
45
+ matplotlib.use("Agg")
46
+ import matplotlib.pyplot as plt
47
+
48
+ except ImportError:
49
+ warnings.warn(
50
+ "The Matplotlib module is required to run some functionalities of PostProcess.\n"
51
+ "Install with \n\npip install matplotlib\n\nRequires CPython."
52
+ )
53
+ except:
54
+ pass
55
+
56
+
57
+ @pyedb_function_handler()
58
+ def get_structured_mesh(theta, phi, ff_data): # pragma: no cover
59
+ if ff_data.min() < 0:
60
+ ff_data_renorm = ff_data + np.abs(ff_data.min())
61
+ else:
62
+ ff_data_renorm = ff_data
63
+ phi_grid, theta_grid = np.meshgrid(phi, theta)
64
+ r_no_renorm = np.reshape(ff_data, (len(theta), len(phi)))
65
+ r = np.reshape(ff_data_renorm, (len(theta), len(phi)))
66
+
67
+ x = r * np.sin(theta_grid) * np.cos(phi_grid)
68
+ y = r * np.sin(theta_grid) * np.sin(phi_grid)
69
+ z = r * np.cos(theta_grid)
70
+
71
+ mag = np.ndarray.flatten(r_no_renorm, order="F")
72
+ ff_mesh = pv.StructuredGrid(x, y, z)
73
+ ff_mesh["FarFieldData"] = mag
74
+ return ff_mesh
75
+
76
+
77
+ def is_notebook(): # pragma: no cover
78
+ """Check if pyedb is running in Jupyter or not.
79
+
80
+ Returns
81
+ -------
82
+ bool
83
+ """
84
+ try:
85
+ shell = get_ipython().__class__.__name__
86
+ if shell == "ZMQInteractiveShell":
87
+ return True # Jupyter notebook or qtconsole
88
+ else:
89
+ return False
90
+ except NameError:
91
+ return False # Probably standard Python interpreter
92
+
93
+
94
+ def is_float(istring): # pragma: no cover
95
+ """Convert a string to a float.
96
+
97
+ Parameters
98
+ ----------
99
+ istring : str
100
+ String to convert to a float.
101
+
102
+ Returns
103
+ -------
104
+ float
105
+ Converted float when successful, ``0`` when when failed.
106
+ """
107
+ try:
108
+ return float(istring.strip())
109
+ except Exception:
110
+ return 0
111
+
112
+
113
+ def _triangle_vertex(elements_nodes, num_nodes_per_element, take_all_nodes=True): # pragma: no cover
114
+ trg_vertex = []
115
+ if num_nodes_per_element == 10 and take_all_nodes:
116
+ for e in elements_nodes:
117
+ trg_vertex.append([e[0], e[1], e[3]])
118
+ trg_vertex.append([e[1], e[2], e[4]])
119
+ trg_vertex.append([e[1], e[4], e[3]])
120
+ trg_vertex.append([e[3], e[4], e[5]])
121
+
122
+ trg_vertex.append([e[9], e[6], e[8]])
123
+ trg_vertex.append([e[6], e[0], e[3]])
124
+ trg_vertex.append([e[6], e[3], e[8]])
125
+ trg_vertex.append([e[8], e[3], e[5]])
126
+
127
+ trg_vertex.append([e[9], e[7], e[8]])
128
+ trg_vertex.append([e[7], e[2], e[4]])
129
+ trg_vertex.append([e[7], e[4], e[8]])
130
+ trg_vertex.append([e[8], e[4], e[5]])
131
+
132
+ trg_vertex.append([e[9], e[7], e[6]])
133
+ trg_vertex.append([e[7], e[2], e[1]])
134
+ trg_vertex.append([e[7], e[1], e[6]])
135
+ trg_vertex.append([e[6], e[1], e[0]])
136
+
137
+ elif num_nodes_per_element == 10 and not take_all_nodes:
138
+ for e in elements_nodes:
139
+ trg_vertex.append([e[0], e[2], e[5]])
140
+ trg_vertex.append([e[9], e[0], e[5]])
141
+ trg_vertex.append([e[9], e[2], e[0]])
142
+ trg_vertex.append([e[9], e[2], e[5]])
143
+
144
+ elif num_nodes_per_element == 6 and not take_all_nodes:
145
+ for e in elements_nodes:
146
+ trg_vertex.append([e[0], e[2], e[5]])
147
+
148
+ elif num_nodes_per_element == 6 and take_all_nodes:
149
+ for e in elements_nodes:
150
+ trg_vertex.append([e[0], e[1], e[3]])
151
+ trg_vertex.append([e[1], e[2], e[4]])
152
+ trg_vertex.append([e[1], e[4], e[3]])
153
+ trg_vertex.append([e[3], e[4], e[5]])
154
+
155
+ elif num_nodes_per_element == 4 and take_all_nodes:
156
+ for e in elements_nodes:
157
+ trg_vertex.append([e[0], e[1], e[3]])
158
+ trg_vertex.append([e[1], e[2], e[3]])
159
+ trg_vertex.append([e[0], e[1], e[2]])
160
+ trg_vertex.append([e[0], e[2], e[3]])
161
+
162
+ elif num_nodes_per_element == 3:
163
+ trg_vertex = elements_nodes
164
+
165
+ return trg_vertex
166
+
167
+
168
+ def _parse_aedtplt(filepath): # pragma: no cover
169
+ lines = []
170
+ vertices = []
171
+ faces = []
172
+ scalars = []
173
+ with open_file(filepath, "r") as f:
174
+ drawing_found = False
175
+ for line in f:
176
+ if "$begin Drawing" in line:
177
+ drawing_found = True
178
+ l_tmp = []
179
+ continue
180
+ if "$end Drawing" in line:
181
+ lines.append(l_tmp)
182
+ drawing_found = False
183
+ continue
184
+ if drawing_found:
185
+ l_tmp.append(line)
186
+ continue
187
+ for drawing_lines in lines:
188
+ bounding = []
189
+ elements = []
190
+ nodes_list = []
191
+ solution = []
192
+ for l in drawing_lines:
193
+ if "BoundingBox(" in l:
194
+ bounding = l[l.find("(") + 1 : -2].split(",")
195
+ bounding = [i.strip() for i in bounding]
196
+ if "Elements(" in l:
197
+ elements = l[l.find("(") + 1 : -2].split(",")
198
+ elements = [int(i.strip()) for i in elements]
199
+ if "Nodes(" in l:
200
+ nodes_list = l[l.find("(") + 1 : -2].split(",")
201
+ nodes_list = [float(i.strip()) for i in nodes_list]
202
+ if "ElemSolution(" in l:
203
+ # convert list of strings to list of floats
204
+ sols = l[l.find("(") + 1 : -2].split(",")
205
+ sols = [is_float(value) for value in sols]
206
+ # sols = [float(i.strip()) for i in sols]
207
+ num_solution_per_element = int(sols[2])
208
+ num_elements = elements[1]
209
+ num_nodes = elements[6]
210
+ sols = sols[3:]
211
+ if num_nodes == num_solution_per_element or num_solution_per_element // num_nodes < 3:
212
+ sols = [
213
+ sols[i : i + num_solution_per_element] for i in range(0, len(sols), num_solution_per_element)
214
+ ]
215
+ solution = [sum(i) / num_solution_per_element for i in sols]
216
+ else:
217
+ sols = [
218
+ sols[i : i + num_solution_per_element] for i in range(0, len(sols), num_solution_per_element)
219
+ ]
220
+ solution = [
221
+ [sum(i[::3]) / num_solution_per_element * 3 for i in sols],
222
+ [sum(i[1::3]) / num_solution_per_element * 3 for i in sols],
223
+ [sum(i[2::3]) / num_solution_per_element * 3 for i in sols],
224
+ ]
225
+
226
+ nodes = [[nodes_list[i], nodes_list[i + 1], nodes_list[i + 2]] for i in range(0, len(nodes_list), 3)]
227
+ num_nodes = elements[0]
228
+ num_elements = elements[1]
229
+ elements = elements[2:]
230
+ element_type = elements[0]
231
+ num_nodes_per_element = elements[4]
232
+ header_length = 5
233
+ elements_nodes = []
234
+ # Todo Aedt 23R2 supports mixed elements size. To be implemented.
235
+ for i in range(0, len(elements), num_nodes_per_element + header_length):
236
+ elements_nodes.append([elements[i + header_length + n] for n in range(num_nodes_per_element)])
237
+ if solution:
238
+ take_all_nodes = True # solution case
239
+ else:
240
+ take_all_nodes = False # mesh case
241
+ trg_vertex = _triangle_vertex(elements_nodes, num_nodes_per_element, take_all_nodes)
242
+ # remove duplicates
243
+ nodup_list = [list(i) for i in list(set([frozenset(t) for t in trg_vertex]))]
244
+ log = True
245
+ if solution:
246
+ if isinstance(solution[0], list):
247
+ temps = []
248
+ for sol in solution:
249
+ sv = {}
250
+ sv_i = {}
251
+ sv = defaultdict(lambda: 0, sv)
252
+ sv_i = defaultdict(lambda: 1, sv_i)
253
+ for els, s in zip(elements_nodes, sol):
254
+ for el in els:
255
+ sv[el] = (sv[el] + s) / sv_i[el]
256
+ sv_i[el] = 2
257
+ temps.append(np.array([sv[v] for v in sorted(sv.keys())]))
258
+ else:
259
+ sv = {}
260
+ sv_i = {}
261
+ sv = defaultdict(lambda: 0, sv)
262
+ sv_i = defaultdict(lambda: 1, sv_i)
263
+
264
+ for els, s in zip(elements_nodes, solution):
265
+ for el in els:
266
+ sv[el] = (sv[el] + s) / sv_i[el]
267
+ sv_i[el] = 2
268
+ temps = np.array([sv[v] for v in sorted(sv.keys())])
269
+ scalars.append(temps)
270
+ if np.min(temps) <= 0:
271
+ log = False
272
+ array = [[3] + [j - 1 for j in i] for i in nodup_list]
273
+
274
+ faces.append(np.hstack(array))
275
+ vertices.append(np.array(nodes))
276
+ # surf = pv.PolyData(vertices, faces)
277
+
278
+ # surf.point_data[field.label] = temps
279
+ # field.log = log
280
+ # field._cached_polydata = surf
281
+ return vertices, faces, scalars, log
282
+
283
+
284
+ @pyedb_function_handler()
285
+ def plot_polar_chart(
286
+ plot_data, size=(2000, 1000), show_legend=True, xlabel="", ylabel="", title="", snapshot_path=None
287
+ ): # pragma: no cover
288
+ """Create a matplotlib polar plot based on a list of data.
289
+
290
+ Parameters
291
+ ----------
292
+ plot_data : list of list
293
+ List of plot data. Every item has to be in the following format
294
+ `[x points, y points, label]`.
295
+ size : tuple, optional
296
+ Image size in pixel (width, height).
297
+ show_legend : bool
298
+ Either to show legend or not.
299
+ xlabel : str
300
+ Plot X label.
301
+ ylabel : str
302
+ Plot Y label.
303
+ title : str
304
+ Plot Title label.
305
+ snapshot_path : str
306
+ Full path to image file if a snapshot is needed.
307
+ """
308
+ dpi = 100.0
309
+
310
+ ax = plt.subplot(111, projection="polar")
311
+
312
+ label_id = 1
313
+ legend = []
314
+ for object in plot_data:
315
+ if len(object) == 3:
316
+ label = object[2]
317
+ else:
318
+ label = "Trace " + str(label_id)
319
+ theta = np.array(object[0])
320
+ r = np.array(object[1])
321
+ ax.plot(theta, r)
322
+ ax.grid(True)
323
+ ax.set_theta_zero_location("N")
324
+ ax.set_theta_direction(-1)
325
+ legend.append(label)
326
+ label_id += 1
327
+
328
+ ax.set(xlabel=xlabel, ylabel=ylabel, title=title)
329
+ if show_legend:
330
+ ax.legend(legend)
331
+
332
+ fig = plt.gcf()
333
+ fig.set_size_inches(size[0] / dpi, size[1] / dpi)
334
+ if snapshot_path:
335
+ fig.savefig(snapshot_path)
336
+ else:
337
+ fig.show()
338
+ return fig
339
+
340
+
341
+ @pyedb_function_handler()
342
+ def plot_3d_chart(plot_data, size=(2000, 1000), xlabel="", ylabel="", title="", snapshot_path=None): # pragma: no cover
343
+ """Create a matplotlib 3D plot based on a list of data.
344
+
345
+ Parameters
346
+ ----------
347
+ plot_data : list of list
348
+ List of plot data. Every item has to be in the following format
349
+ `[x points, y points, z points, label]`.
350
+ size : tuple, optional
351
+ Image size in pixel (width, height).
352
+ xlabel : str
353
+ Plot X label.
354
+ ylabel : str
355
+ Plot Y label.
356
+ title : str
357
+ Plot Title label.
358
+ snapshot_path : str
359
+ Full path to image file if a snapshot is needed.
360
+
361
+ Returns
362
+ -------
363
+ :class:`matplotlib.plt`
364
+ Matplotlib fig object.
365
+ """
366
+ dpi = 100.0
367
+
368
+ ax = plt.subplot(111, projection="3d")
369
+
370
+ if isinstance(plot_data[0], np.ndarray):
371
+ x = plot_data[0]
372
+ y = plot_data[1]
373
+ z = plot_data[2]
374
+ else:
375
+ theta_grid, phi_grid = np.meshgrid(plot_data[0], plot_data[1])
376
+ if isinstance(plot_data[2], list):
377
+ r = np.array(plot_data[2])
378
+ else:
379
+ r = plot_data[2]
380
+ x = r * np.sin(theta_grid) * np.cos(phi_grid)
381
+ y = r * np.sin(theta_grid) * np.sin(phi_grid)
382
+ z = r * np.cos(theta_grid)
383
+ ax.set(xlabel=xlabel, ylabel=ylabel, title=title)
384
+ ax.plot_surface(x, y, z, rstride=1, cstride=1, cmap=plt.get_cmap("jet"), linewidth=0, antialiased=True, alpha=0.8)
385
+ fig = plt.gcf()
386
+ fig.set_size_inches(size[0] / dpi, size[1] / dpi)
387
+ if snapshot_path:
388
+ fig.savefig(snapshot_path)
389
+ else:
390
+ fig.show()
391
+ return fig
392
+
393
+
394
+ @pyedb_function_handler()
395
+ def plot_2d_chart(
396
+ plot_data, size=(2000, 1000), show_legend=True, xlabel="", ylabel="", title="", snapshot_path=None
397
+ ): # pragma: no cover
398
+ """Create a matplotlib plot based on a list of data.
399
+
400
+ Parameters
401
+ ----------
402
+ plot_data : list of list
403
+ List of plot data. Every item has to be in the following format
404
+ `[x points, y points, label]`.
405
+ size : tuple, optional
406
+ Image size in pixel (width, height).
407
+ show_legend : bool, optional
408
+ Either to show legend or not. The default value is ``True``.
409
+ xlabel : str, optional
410
+ Plot X label. The default value is ``""``.
411
+ ylabel : str, optional
412
+ Plot Y label. The default value is ``""``.
413
+ title : str, optional
414
+ Plot Title label. The default value is ``""``.
415
+ snapshot_path : str, optional
416
+ Full path to image file if a snapshot is needed.
417
+ The default value is ``None``.
418
+
419
+ Returns
420
+ -------
421
+ :class:`matplotlib.plt`
422
+ Matplotlib fig object.
423
+ """
424
+ dpi = 100.0
425
+ figsize = (size[0] / dpi, size[1] / dpi)
426
+ fig, ax = plt.subplots(figsize=figsize)
427
+ label_id = 1
428
+ for plo_obj in plot_data:
429
+ if len(plo_obj) == 3:
430
+ label = plo_obj[2]
431
+ else:
432
+ label = "Trace " + str(label_id)
433
+ if isinstance(plo_obj[0], np.ndarray):
434
+ x = plo_obj[0]
435
+ y = plo_obj[1]
436
+ else:
437
+ x = np.array([i for i, j in zip(plo_obj[0], plo_obj[1]) if j])
438
+ y = np.array([i for i in plo_obj[1] if i])
439
+ ax.plot(x, y, label=label)
440
+ label_id += 1
441
+
442
+ ax.set(xlabel=xlabel, ylabel=ylabel, title=title)
443
+ if show_legend:
444
+ ax.legend()
445
+
446
+ if snapshot_path:
447
+ fig.savefig(snapshot_path)
448
+ elif not is_notebook():
449
+ fig.show()
450
+ return fig
451
+
452
+
453
+ @pyedb_function_handler()
454
+ def plot_matplotlib(
455
+ plot_data,
456
+ size=(2000, 1000),
457
+ show_legend=True,
458
+ xlabel="",
459
+ ylabel="",
460
+ title="",
461
+ snapshot_path=None,
462
+ x_limits=None,
463
+ y_limits=None,
464
+ axis_equal=False,
465
+ annotations=None,
466
+ show=True,
467
+ ):
468
+ """Create a matplotlib plot based on a list of data.
469
+
470
+ Parameters
471
+ ----------
472
+ plot_data : list of list
473
+ List of plot data. Every item has to be in the following format
474
+ For type ``fill``: `[x points, y points, color, label, alpha, type=="fill"]`.
475
+ For type ``path``: `[vertices, codes, color, label, alpha, type=="path"]`.
476
+ For type ``contour``: `[vertices, codes, color, label, alpha, line_width, type=="contour"]`.
477
+ size : tuple, optional
478
+ Image size in pixel (width, height). Default is `(2000, 1000)`.
479
+ show_legend : bool, optional
480
+ Either to show legend or not. Default is `True`.
481
+ xlabel : str, optional
482
+ Plot X label. Default is `""`.
483
+ ylabel : str, optional
484
+ Plot Y label. Default is `""`.
485
+ title : str, optional
486
+ Plot Title label. Default is `""`.
487
+ snapshot_path : str, optional
488
+ Full path to image file if a snapshot is needed. Default is `None`.
489
+ x_limits : list, optional
490
+ List of x limits (left and right). Default is `None`.
491
+ y_limits : list, optional
492
+ List of y limits (bottom and top). Default is `None`.
493
+ axis_equal : bool, optional
494
+ Whether to show the same scale on both axis or have a different scale based on plot size.
495
+ Default is `False`.
496
+ annotations : list, optional
497
+ List of annotations to add to the plot. The format is [x, y, string, dictionary of font options].
498
+ Default is `None`.
499
+ show : bool, optional
500
+ Whether to show the plot or return the matplotlib object. Default is `True`.
501
+
502
+
503
+ Returns
504
+ -------
505
+ :class:`matplotlib.plt`
506
+ Matplotlib fig object.
507
+ """
508
+ dpi = 100.0
509
+ figsize = (size[0] / dpi, size[1] / dpi)
510
+ fig = plt.figure(figsize=figsize)
511
+ ax = fig.add_subplot(1, 1, 1)
512
+ if isinstance(plot_data, str):
513
+ plot_data = ast.literal_eval(plot_data)
514
+ for points in plot_data:
515
+ if points[-1] == "fill":
516
+ plt.fill(points[0], points[1], c=points[2], label=points[3], alpha=points[4])
517
+ elif points[-1] == "path":
518
+ path = Path(points[0], points[1])
519
+ patch = PathPatch(path, color=points[2], alpha=points[4], label=points[3])
520
+ ax.add_patch(patch)
521
+ elif points[-1] == "contour":
522
+ path = Path(points[0], points[1])
523
+ patch = PathPatch(path, color=points[2], alpha=points[4], label=points[3], fill=False, linewidth=points[5])
524
+ ax.add_patch(patch)
525
+
526
+ ax.set(xlabel=xlabel, ylabel=ylabel, title=title)
527
+ if show_legend:
528
+ ax.legend(loc="upper right")
529
+
530
+ # evaluating the limits
531
+ xmin = ymin = 1e30
532
+ xmax = ymax = -1e30
533
+ for points in plot_data:
534
+ if points[-1] == "fill":
535
+ xmin = min(xmin, min(points[0]))
536
+ xmax = max(xmax, max(points[0]))
537
+ ymin = min(ymin, min(points[1]))
538
+ ymax = max(ymax, max(points[1]))
539
+ else:
540
+ for p in points[0]:
541
+ xmin = min(xmin, p[0])
542
+ xmax = max(xmax, p[0])
543
+ ymin = min(ymin, p[1])
544
+ ymax = max(ymax, p[1])
545
+ if x_limits:
546
+ ax.set_xlim(x_limits)
547
+ else:
548
+ ax.set_xlim([xmin, xmax])
549
+ if y_limits:
550
+ ax.set_ylim(y_limits)
551
+ else:
552
+ ax.set_ylim([ymin, ymax])
553
+
554
+ if axis_equal:
555
+ ax.axis("equal")
556
+
557
+ if annotations:
558
+ for annotation in annotations:
559
+ plt.text(annotation[0], annotation[1], annotation[2], **annotation[3])
560
+
561
+ if snapshot_path:
562
+ plt.savefig(snapshot_path)
563
+ elif show:
564
+ plt.show()
565
+ return plt
566
+
567
+
568
+ @pyedb_function_handler()
569
+ def plot_contour(
570
+ qty_to_plot, x, y, size=(2000, 1600), xlabel="", ylabel="", title="", levels=64, snapshot_path=None
571
+ ): # pragma: no cover
572
+ """Create a matplotlib contour plot.
573
+
574
+ Parameters
575
+ ----------
576
+ qty_to_plot : :class:`numpy.ndarray`
577
+ Quantity to plot.
578
+ x : :class:`numpy.ndarray`
579
+ X axis quantity.
580
+ y : :class:`numpy.ndarray`
581
+ Y axis quantity.
582
+ size : tuple, list, optional
583
+ Window Size. Default is `(2000,1600)`.
584
+ xlabel : str, optional
585
+ X Label. Default is `""`.
586
+ ylabel : str, optional
587
+ Y Label. Default is `""`.
588
+ title : str, optional
589
+ Plot Title Label. Default is `""`.
590
+ levels : int, optional
591
+ Colormap levels. Default is `64`.
592
+ snapshot_path : str, optional
593
+ Full path to image to save. Default is None.
594
+
595
+ Returns
596
+ -------
597
+ :class:`matplotlib.plt`
598
+ Matplotlib fig object.
599
+ """
600
+ dpi = 100.0
601
+ figsize = (size[0] / dpi, size[1] / dpi)
602
+ fig, ax = plt.subplots(figsize=figsize)
603
+ if title:
604
+ plt.title(title)
605
+ if xlabel:
606
+ plt.xlabel(xlabel)
607
+ if ylabel:
608
+ plt.ylabel(ylabel)
609
+
610
+ plt.contourf(
611
+ x,
612
+ y,
613
+ qty_to_plot.T,
614
+ levels=levels,
615
+ cmap="jet",
616
+ )
617
+
618
+ plt.colorbar()
619
+ if snapshot_path:
620
+ plt.savefig(snapshot_path)
621
+ else:
622
+ plt.show()
623
+ return plt
624
+
625
+
626
+ class ObjClass(object):
627
+ """Manages mesh files to be plotted in pyvista.
628
+
629
+ Parameters
630
+ ----------
631
+ path : str
632
+ Full path to the file.
633
+ color : str or tuple
634
+ Can be a string with color name or a tuple with (r,g,b) values.
635
+ opacity : float
636
+ Value between 0 to 1 of opacity.
637
+ units : str
638
+ Model units.
639
+
640
+ """
641
+
642
+ def __init__(self, path, color, opacity, units): # pragma: no cover
643
+ self.path = path
644
+ self._color = (0, 0, 0)
645
+ self.color = color
646
+ self.opacity = opacity
647
+ self.units = units
648
+ self._cached_mesh = None
649
+ self._cached_polydata = None
650
+ self.name = os.path.splitext(os.path.basename(self.path))[0]
651
+
652
+ @property
653
+ def color(self):
654
+ return self._color
655
+
656
+ @color.setter
657
+ def color(self, value):
658
+ if isinstance(value, (tuple, list)):
659
+ self._color = value
660
+ elif value in CSS4_COLORS:
661
+ h = CSS4_COLORS[value].lstrip("#")
662
+ self._color = tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))
663
+
664
+
665
+ class FieldClass(object):
666
+ """Class to manage Field data to be plotted in pyvista.
667
+
668
+ Parameters
669
+ ----------
670
+ path : str
671
+ Full path to the file.
672
+ log_scale : bool, optional
673
+ Either if the field has to be plotted log or not. The default value is ``True``.
674
+ coordinate_units : str, optional
675
+ Fields coordinates units. The default value is ``"meter"``.
676
+ opacity : float, optional
677
+ Value between 0 to 1 of opacity. The default value is ``1``.
678
+ color_map : str, optional
679
+ Color map of field plot. The default value is ``"rainbow"``.
680
+ label : str, optional
681
+ Name of the field. The default value is ``"Field"``.
682
+ tolerance : float, optional
683
+ Delauny tolerance value used for interpolating points. The default value is ``1e-3``.
684
+ headers : int, optional
685
+ Number of lines to of the file containing header info that has to be removed.
686
+ The default value is ``2``.
687
+ """
688
+
689
+ def __init__(
690
+ self,
691
+ path,
692
+ log_scale=True,
693
+ coordinate_units="meter",
694
+ opacity=1,
695
+ color_map="jet",
696
+ label="Field",
697
+ tolerance=1e-3,
698
+ headers=2,
699
+ show_edge=True,
700
+ ): # pragma: no cover
701
+ self.path = path
702
+ self.log_scale = log_scale
703
+ self.units = coordinate_units
704
+ self.opacity = opacity
705
+ self.color_map = color_map
706
+ self._cached_mesh = None
707
+ self._cached_polydata = None
708
+ self.label = label
709
+ self.name = os.path.splitext(os.path.basename(self.path))[0]
710
+ self.color = (255, 0, 0)
711
+ self.surface_mapping_tolerance = tolerance
712
+ self.header_lines = headers
713
+ self.show_edge = show_edge
714
+ self._is_frame = False
715
+ self.is_vector = False
716
+ self.vector_scale = 1.0
717
+
718
+
719
+ class CommonPlotter(object):
720
+ def __init__(self): # pragma: no cover
721
+ self._objects = []
722
+ self._fields = []
723
+ self._frames = []
724
+ self.show_axes = True
725
+ self.show_legend = True
726
+ self.show_grid = True
727
+ self.is_notebook = is_notebook()
728
+ self.gif_file = None
729
+ self._background_color = (255, 255, 255)
730
+ self._background_image = None
731
+ self.off_screen = False
732
+ if self.is_notebook:
733
+ self.windows_size = [600, 600]
734
+ else:
735
+ self.windows_size = [1024, 768]
736
+ self.pv = None
737
+ self._orientation = ["xy", 0, 0, 0]
738
+ self.units = "meter"
739
+ self.frame_per_seconds = 3
740
+ self._plot_meshes = []
741
+ self.range_min = None
742
+ self.range_max = None
743
+ self.image_file = None
744
+ self._camera_position = "yz"
745
+ self._view_up = (0.0, 1.0, 0.0)
746
+ self._focal_point = (0.0, 0.0, 0.0)
747
+ self._roll_angle = 0
748
+ self._azimuth_angle = 0
749
+ self._elevation_angle = 0
750
+ self._zoom = 1
751
+ self._isometric_view = True
752
+ self.bounding_box = True
753
+ self.color_bar = True
754
+ self.array_coordinates = []
755
+ self.meshes = None
756
+ self._x_scale = 1.0
757
+ self._y_scale = 1.0
758
+ self._z_scale = 1.0
759
+ self._convert_fields_in_db = False
760
+ self._log_multiplier = 10.0
761
+
762
+ @property
763
+ def convert_fields_in_db(self):
764
+ """Either if convert the fields before plotting in dB. Log scale will be disabled.
765
+
766
+ Returns
767
+ -------
768
+ bool
769
+ """
770
+ return self._convert_fields_in_db
771
+
772
+ @convert_fields_in_db.setter
773
+ def convert_fields_in_db(self, value):
774
+ self._convert_fields_in_db = value
775
+ for f in self.fields:
776
+ f._cached_polydata = None
777
+ for f in self.frames:
778
+ f._cached_polydata = None
779
+
780
+ @property
781
+ def log_multiplier(self):
782
+ """Multiply the log value.
783
+
784
+ Returns
785
+ -------
786
+ float
787
+ """
788
+ return self._log_multiplier
789
+
790
+ @log_multiplier.setter
791
+ def log_multiplier(self, value):
792
+ self._log_multiplier = value
793
+
794
+ @property
795
+ def x_scale(self):
796
+ """Scale plot on X.
797
+
798
+ Returns
799
+ -------
800
+ float
801
+ """
802
+ return self._x_scale
803
+
804
+ @x_scale.setter
805
+ def x_scale(self, value):
806
+ self._x_scale = value
807
+
808
+ @property
809
+ def y_scale(self):
810
+ """Scale plot on Y.
811
+
812
+ Returns
813
+ -------
814
+ float
815
+ """
816
+ return self._y_scale
817
+
818
+ @y_scale.setter
819
+ def y_scale(self, value):
820
+ self._y_scale = value
821
+
822
+ @property
823
+ def z_scale(self):
824
+ """Scale plot on Z.
825
+
826
+ Returns
827
+ -------
828
+ float
829
+ """
830
+ return self._z_scale
831
+
832
+ @z_scale.setter
833
+ def z_scale(self, value):
834
+ self._z_scale = value
835
+
836
+ @property
837
+ def isometric_view(self):
838
+ """Enable or disable the default iso view.
839
+
840
+ Parameters
841
+ ----------
842
+ value : bool
843
+ Either if iso view is enabled or disabled.
844
+
845
+ Returns
846
+ -------
847
+ bool
848
+ """
849
+ return self._isometric_view
850
+
851
+ @isometric_view.setter
852
+ def isometric_view(self, value=True):
853
+ self._isometric_view = value
854
+
855
+ @property
856
+ def view_up(self):
857
+ """Get/Set the camera view axis. It disables the default iso view.
858
+
859
+ Parameters
860
+ ----------
861
+ value : tuple
862
+ Value of camera view position.
863
+
864
+ Returns
865
+ -------
866
+ tuple
867
+ """
868
+ return self._view_up
869
+
870
+ @view_up.setter
871
+ def view_up(self, value):
872
+ if isinstance(value, list):
873
+ self._view_up = tuple(value)
874
+ else:
875
+ self._view_up = value
876
+ self.isometric_view = False
877
+
878
+ @property
879
+ def focal_point(self):
880
+ """Get/Set the camera focal point value. It disables the default iso view.
881
+
882
+ Parameters
883
+ ----------
884
+ value : tuple
885
+ Value of focal point position.
886
+
887
+ Returns
888
+ -------
889
+ tuple
890
+ """
891
+ return self._focal_point
892
+
893
+ @focal_point.setter
894
+ def focal_point(self, value):
895
+ if isinstance(value, list):
896
+ self._focal_point = tuple(value)
897
+ else:
898
+ self._focal_point = value
899
+ self.isometric_view = False
900
+
901
+ @property
902
+ def camera_position(self):
903
+ """Get or set the camera position value. This parameter disables the default iso view.
904
+ Value for the camera position. The value is for ``"xy"``, ``"xz"`` or ``"yz"``.
905
+
906
+ Returns
907
+ -------
908
+ str
909
+ """
910
+ return self._camera_position
911
+
912
+ @camera_position.setter
913
+ def camera_position(self, value):
914
+ if isinstance(value, list):
915
+ self._camera_position = tuple(value)
916
+ else:
917
+ self._camera_position = value
918
+ self.isometric_view = False
919
+
920
+ @property
921
+ def roll_angle(self):
922
+ """Get/Set the roll angle value. It disables the default iso view.
923
+
924
+ Parameters
925
+ ----------
926
+ value : float
927
+ Value of roll angle in degrees.
928
+
929
+ Returns
930
+ -------
931
+ float
932
+ """
933
+ return self._roll_angle
934
+
935
+ @roll_angle.setter
936
+ def roll_angle(self, value=20):
937
+ self._roll_angle = value
938
+ self.isometric_view = False
939
+
940
+ @property
941
+ def azimuth_angle(self):
942
+ """Get/Set the azimuth angle value. It disables the default iso view.
943
+
944
+ Parameters
945
+ ----------
946
+ value : float
947
+ Value of azimuth angle in degrees.
948
+
949
+ Returns
950
+ -------
951
+ float
952
+ """
953
+ return self._azimuth_angle
954
+
955
+ @azimuth_angle.setter
956
+ def azimuth_angle(self, value=45):
957
+ self._azimuth_angle = value
958
+ self.use_default_iso_view = False
959
+
960
+ @property
961
+ def elevation_angle(self):
962
+ """Get/Set the elevation angle value. It disables the default iso view.
963
+
964
+ Parameters
965
+ ----------
966
+ value : float
967
+ Value of elevation angle in degrees.
968
+
969
+ Returns
970
+ -------
971
+ float
972
+ """
973
+ return self._elevation_angle
974
+
975
+ @elevation_angle.setter
976
+ def elevation_angle(self, value=45):
977
+ self._elevation_angle = value
978
+ self.use_default_iso_view = False
979
+
980
+ @property
981
+ def zoom(self):
982
+ """Get/Set the zoom value.
983
+
984
+ Parameters
985
+ ----------
986
+ value : float
987
+ Value of zoom in degrees.
988
+
989
+ Returns
990
+ -------
991
+ float
992
+ """
993
+ return self._zoom
994
+
995
+ @zoom.setter
996
+ def zoom(self, value=1):
997
+ self._zoom = value
998
+
999
+ @pyedb_function_handler()
1000
+ def set_orientation(self, camera_position="xy", roll_angle=0, azimuth_angle=45, elevation_angle=20):
1001
+ """Change the plot default orientation.
1002
+
1003
+ Parameters
1004
+ ----------
1005
+ camera_position : str
1006
+ Camera view. Default is `"xy"`. Options are `"xz"` and `"yz"`.
1007
+ roll_angle : int, float
1008
+ Roll camera angle on the specified the camera_position.
1009
+ azimuth_angle : int, float
1010
+ Azimuth angle of camera on the specified the camera_position.
1011
+ elevation_angle : int, float
1012
+ Elevation camera angle on the specified the camera_position.
1013
+
1014
+ Returns
1015
+ -------
1016
+ bool
1017
+ """
1018
+ if camera_position in ["xy", "yz", "xz"]:
1019
+ self.camera_position = camera_position
1020
+ else:
1021
+ warnings.warn("Plane has to be one of xy, xz, yz.")
1022
+ self.roll_angle = roll_angle
1023
+ self.azimuth_angle = azimuth_angle
1024
+ self.elevation_angle = elevation_angle
1025
+ self.use_default_iso_view = False
1026
+ return True
1027
+
1028
+ @property
1029
+ def background_color(self):
1030
+ """Background color.
1031
+ It can be a tuple of (r,g,b) or color name."""
1032
+ return self._background_color
1033
+
1034
+ @background_color.setter
1035
+ def background_color(self, value):
1036
+ if isinstance(value, (tuple, list)):
1037
+ self._background_color = value
1038
+ elif value in CSS4_COLORS:
1039
+ h = CSS4_COLORS[value].lstrip("#")
1040
+ self._background_color = tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))
1041
+
1042
+ @property
1043
+ def background_image(self):
1044
+ """Background image.
1045
+
1046
+ Returns
1047
+ -------
1048
+ str
1049
+ """
1050
+ return self._background_image
1051
+
1052
+ @background_image.setter
1053
+ def background_image(self, value):
1054
+ if os.path.exists(value):
1055
+ self._background_image = value
1056
+
1057
+
1058
+ class ModelPlotter(CommonPlotter):
1059
+ """Manages the data to be plotted with ``pyvista``.
1060
+
1061
+ Examples
1062
+ --------
1063
+ This Class can be instantiated within PyEDB (with plot_model_object or different field plots
1064
+ and standalone).
1065
+ Here an example of standalone project
1066
+
1067
+ >>> model = ModelPlotter()
1068
+ >>> model.add_object(r'D:\Simulation\antenna.obj', (200,20,255), 0.6, "in")
1069
+ >>> model.add_object(r'D:\Simulation\helix.obj', (0,255,0), 0.5, "in")
1070
+ >>> model.add_field_from_file(r'D:\Simulation\helic_antenna.csv', True, "meter", 1)
1071
+ >>> model.background_color = (0,0,0)
1072
+ >>> model.plot()
1073
+
1074
+ And here an example of animation:
1075
+
1076
+ >>> model = ModelPlotter()
1077
+ >>> model.add_object(r'D:\Simulation\antenna.obj', (200,20,255), 0.6, "in")
1078
+ >>> model.add_object(r'D:\Simulation\helix.obj', (0,255,0), 0.5, "in")
1079
+ >>> frames = [r'D:\Simulation\helic_antenna.csv', r'D:\Simulation\helic_antenna_10.fld',
1080
+ ... r'D:\Simulation\helic_antenna_20.fld', r'D:\Simulation\helic_antenna_30.fld',
1081
+ ... r'D:\Simulation\helic_antenna_40.fld']
1082
+ >>> model.gif_file = r"D:\Simulation\animation.gif"
1083
+ >>> model.animate()
1084
+ """
1085
+
1086
+ def __init__(self):
1087
+ CommonPlotter.__init__(self)
1088
+
1089
+ @property
1090
+ def fields(self):
1091
+ """List of fields object.
1092
+
1093
+ Returns
1094
+ -------
1095
+ list of :class:`pyaedt.modules.AdvancedPostProcessing.FieldClass`
1096
+ """
1097
+ return self._fields
1098
+
1099
+ @property
1100
+ def frames(self):
1101
+ """Frames list for animation.
1102
+
1103
+ Returns
1104
+ -------
1105
+ list of :class:`pyaedt.modules.AdvancedPostProcessing.FieldClass`
1106
+ """
1107
+ return self._frames
1108
+
1109
+ @property
1110
+ def objects(self):
1111
+ """List of class objects.
1112
+
1113
+ Returns
1114
+ -------
1115
+ list of :class:`pyaedt.modules.AdvancedPostProcessing.ObjClass`
1116
+ """
1117
+ return self._objects
1118
+
1119
+ @pyedb_function_handler()
1120
+ def add_object(self, cad_path, cad_color="dodgerblue", opacity=1, units="mm"):
1121
+ """Add an mesh file to the scenario. It can be obj or any of pyvista supported files.
1122
+
1123
+ Parameters
1124
+ ----------
1125
+ cad_path : str
1126
+ Full path to the file.
1127
+ cad_color : str or tuple
1128
+ Can be a string with color name or a tuple with (r,g,b) values.
1129
+ The default value is ``"dodgerblue"``.
1130
+ opacity : float
1131
+ Value between 0 to 1 of opacity. The default value is ``1``.
1132
+ units : str
1133
+ Model units. The default value is ``"mm"``.
1134
+
1135
+ Returns
1136
+ -------
1137
+ bool
1138
+ """
1139
+ self._objects.append(ObjClass(cad_path, cad_color, opacity, units))
1140
+ self.units = units
1141
+ return True
1142
+
1143
+ @pyedb_function_handler()
1144
+ def add_field_from_file(
1145
+ self,
1146
+ field_path,
1147
+ log_scale=True,
1148
+ coordinate_units="meter",
1149
+ opacity=1,
1150
+ color_map="jet",
1151
+ label_name="Field",
1152
+ surface_mapping_tolerance=1e-3,
1153
+ header_lines=2,
1154
+ show_edges=True,
1155
+ ):
1156
+ """Add a field file to the scenario.
1157
+ It can be aedtplt, fld or csv file or any txt file with 4 column [x,y,z,field].
1158
+ If text file they have to be space separated column.
1159
+
1160
+ Parameters
1161
+ ----------
1162
+ field_path : str
1163
+ Full path to the file.
1164
+ log_scale : bool
1165
+ Either if the field has to be plotted log or not.
1166
+ coordinate_units : str
1167
+ Fields coordinates units.
1168
+ opacity : float
1169
+ Value between 0 to 1 of opacity.
1170
+ color_map : str
1171
+ Color map of field plot. Default rainbow.
1172
+ label_name : str, optional
1173
+ Name of the field.
1174
+ surface_mapping_tolerance : float, optional
1175
+ Delauny tolerance value used for interpolating points.
1176
+ header_lines : int
1177
+ Number of lines to of the file containing header info that has to be removed.
1178
+
1179
+ Returns
1180
+ -------
1181
+ bool
1182
+ """
1183
+ self._fields.append(
1184
+ FieldClass(
1185
+ field_path,
1186
+ log_scale,
1187
+ coordinate_units,
1188
+ opacity,
1189
+ color_map,
1190
+ label_name,
1191
+ surface_mapping_tolerance,
1192
+ header_lines,
1193
+ show_edges,
1194
+ )
1195
+ )
1196
+
1197
+ @pyedb_function_handler()
1198
+ def add_frames_from_file(
1199
+ self,
1200
+ field_files,
1201
+ log_scale=True,
1202
+ coordinate_units="meter",
1203
+ opacity=1,
1204
+ color_map="jet",
1205
+ label_name="Field",
1206
+ surface_mapping_tolerance=1e-3,
1207
+ header_lines=2,
1208
+ ): # pragma: no cover
1209
+ """Add a field file to the scenario. It can be aedtplt, fld or csv file.
1210
+
1211
+ Parameters
1212
+ ----------
1213
+ field_files : list
1214
+ List of full path to frame file.
1215
+ log_scale : bool
1216
+ Either if the field has to be plotted log or not.
1217
+ coordinate_units : str
1218
+ Fields coordinates units.
1219
+ opacity : float
1220
+ Value between 0 to 1 of opacity.
1221
+ color_map : str
1222
+ Color map of field plot. Default rainbow.
1223
+ label_name : str, optional
1224
+ Name of the field.
1225
+ surface_mapping_tolerance : float, optional
1226
+ Delauny tolerance value used for interpolating points.
1227
+ header_lines : int
1228
+ Number of lines to of the file containing header info that has to be removed.
1229
+
1230
+ Returns
1231
+ -------
1232
+ bool
1233
+ """
1234
+ for field in field_files:
1235
+ self._frames.append(
1236
+ FieldClass(
1237
+ field,
1238
+ log_scale,
1239
+ coordinate_units,
1240
+ opacity,
1241
+ color_map,
1242
+ label_name,
1243
+ surface_mapping_tolerance,
1244
+ header_lines,
1245
+ False,
1246
+ )
1247
+ )
1248
+ self._frames[-1]._is_frame = True
1249
+
1250
+ @pyedb_function_handler()
1251
+ def add_field_from_data(
1252
+ self,
1253
+ coordinates,
1254
+ fields_data,
1255
+ log_scale=True,
1256
+ coordinate_units="meter",
1257
+ opacity=1,
1258
+ color_map="jet",
1259
+ label_name="Field",
1260
+ surface_mapping_tolerance=1e-3,
1261
+ show_edges=True,
1262
+ ): # pragma: no cover
1263
+ """Add field data to the scenario.
1264
+
1265
+ Parameters
1266
+ ----------
1267
+ coordinates : list of list
1268
+ List of list [x,y,z] coordinates.
1269
+ fields_data : list
1270
+ List of list Fields Value.
1271
+ log_scale : bool
1272
+ Either if the field has to be plotted log or not.
1273
+ coordinate_units : str
1274
+ Fields coordinates units.
1275
+ opacity : float
1276
+ Value between 0 to 1 of opacity.
1277
+ color_map : str
1278
+ Color map of field plot. Default rainbow.
1279
+ label_name : str, optional
1280
+ Name of the field.
1281
+ surface_mapping_tolerance : float, optional
1282
+ Delauny tolerance value used for interpolating points.
1283
+
1284
+ Returns
1285
+ -------
1286
+ bool
1287
+ """
1288
+ self._fields.append(
1289
+ FieldClass(
1290
+ None, log_scale, coordinate_units, opacity, color_map, label_name, surface_mapping_tolerance, show_edges
1291
+ )
1292
+ )
1293
+ vertices = np.array(coordinates)
1294
+ filedata = pv.PolyData(vertices)
1295
+ filedata = filedata.delaunay_2d(tol=surface_mapping_tolerance)
1296
+ filedata.point_data[self.fields[-1].label] = np.array(fields_data)
1297
+ self.fields[-1]._cached_polydata = filedata
1298
+
1299
+ @pyedb_function_handler()
1300
+ def _read_mesh_files(self, read_frames=False): # pragma: no cover
1301
+ for cad in self.objects:
1302
+ if not cad._cached_polydata:
1303
+ filedata = pv.read(cad.path)
1304
+ cad._cached_polydata = filedata
1305
+ color_cad = [i / 255 for i in cad.color]
1306
+ cad._cached_mesh = self.pv.add_mesh(cad._cached_polydata, color=color_cad, opacity=cad.opacity)
1307
+ if self.meshes:
1308
+ self.meshes += cad._cached_polydata
1309
+ else:
1310
+ self.meshes = cad._cached_polydata
1311
+ obj_to_iterate = [i for i in self._fields]
1312
+ if read_frames:
1313
+ for i in self.frames:
1314
+ obj_to_iterate.append(i)
1315
+ for field in obj_to_iterate:
1316
+ if field.path and not field._cached_polydata:
1317
+ if ".aedtplt" in field.path:
1318
+ vertices, faces, scalars, log1 = _parse_aedtplt(field.path)
1319
+ if self.convert_fields_in_db:
1320
+ scalars = [np.multiply(np.log10(i), self.log_multiplier) for i in scalars]
1321
+ fields_vals = pv.PolyData(vertices[0], faces[0])
1322
+ field._cached_polydata = fields_vals
1323
+ if isinstance(scalars[0], list):
1324
+ vector_scale = (max(fields_vals.bounds) - min(fields_vals.bounds)) / (
1325
+ 50 * (np.vstack(scalars[0]).max() - np.vstack(scalars[0]).min())
1326
+ )
1327
+
1328
+ field._cached_polydata["vectors"] = np.vstack(scalars[0]).T * vector_scale
1329
+ field.label = "Vector " + field.label
1330
+ field._cached_polydata.point_data[field.label] = np.array(
1331
+ [np.linalg.norm(x) for x in np.vstack(scalars[0]).T]
1332
+ )
1333
+
1334
+ field.is_vector = True
1335
+ else:
1336
+ field._cached_polydata.point_data[field.label] = scalars[0]
1337
+ field.is_vector = False
1338
+ field.log = log1
1339
+ else:
1340
+ nodes = []
1341
+ values = []
1342
+ is_vector = False
1343
+ with open_file(field.path, "r") as f:
1344
+ try:
1345
+ lines = f.read().splitlines()[field.header_lines :]
1346
+ if ".csv" in field.path:
1347
+ sniffer = csv.Sniffer()
1348
+ delimiter = sniffer.sniff(lines[0]).delimiter
1349
+ else:
1350
+ delimiter = " "
1351
+ if len(lines) > 2000 and not field._is_frame:
1352
+ lines = list(dict.fromkeys(lines))
1353
+ # decimate = 2
1354
+ # del lines[decimate - 1 :: decimate]
1355
+ except:
1356
+ lines = []
1357
+ for line in lines:
1358
+ tmp = line.strip().split(delimiter)
1359
+ if len(tmp) < 4:
1360
+ continue
1361
+ nodes.append([float(tmp[0]), float(tmp[1]), float(tmp[2])])
1362
+ if len(tmp) == 6:
1363
+ values.append([float(tmp[3]), float(tmp[4]), float(tmp[5])])
1364
+ is_vector = True
1365
+ elif len(tmp) == 9:
1366
+ values.append([float(tmp[3]), float(tmp[5]), float(tmp[7])])
1367
+ is_vector = True
1368
+ else:
1369
+ values.append(float(tmp[3]))
1370
+ if self.convert_fields_in_db:
1371
+ if not isinstance(values[0], list):
1372
+ values = [self.log_multiplier * math.log10(abs(i)) for i in values]
1373
+ else:
1374
+ values = [[self.log_multiplier * math.log10(abs(i)) for i in value] for value in values]
1375
+ if nodes:
1376
+ try:
1377
+ conv = 1 / AEDT_UNITS["Length"][self.units]
1378
+ except:
1379
+ conv = 1
1380
+ vertices = np.array(nodes) * conv
1381
+ filedata = pv.PolyData(vertices)
1382
+ if is_vector:
1383
+ vector_scale = (max(filedata.bounds) - min(filedata.bounds)) / (
1384
+ 20 * (np.vstack(values).max() - np.vstack(values).min())
1385
+ )
1386
+ filedata["vectors"] = np.vstack(values) * vector_scale
1387
+ field.label = "Vector " + field.label
1388
+ filedata.point_data[field.label] = np.array([np.linalg.norm(x) for x in np.vstack(values)])
1389
+ field.is_vector = True
1390
+ else:
1391
+ filedata = filedata.delaunay_2d(tol=field.surface_mapping_tolerance)
1392
+ filedata.point_data[field.label] = np.array(values)
1393
+ field._cached_polydata = filedata
1394
+
1395
+ @pyedb_function_handler()
1396
+ def _add_buttons(self): # pragma: no cover
1397
+ size = int(self.pv.window_size[1] / 40)
1398
+ startpos = self.pv.window_size[1] - 2 * size
1399
+ endpos = 100
1400
+ color = self.pv.background_color
1401
+ axes_color = [0 if i >= 0.5 else 255 for i in color]
1402
+ buttons = []
1403
+ texts = []
1404
+ max_elements = (startpos - endpos) // (size + (size // 10))
1405
+
1406
+ class SetVisibilityCallback:
1407
+ """Helper callback to keep a reference to the actor being modified."""
1408
+
1409
+ def __init__(self, actor):
1410
+ self.actor = actor
1411
+
1412
+ def __call__(self, state):
1413
+ try:
1414
+ self.actor._cached_mesh.SetVisibility(state)
1415
+ except AttributeError:
1416
+ self.actor.SetVisibility(state)
1417
+
1418
+ class ChangePageCallback:
1419
+ """Helper callback to keep a reference to the actor being modified."""
1420
+
1421
+ def __init__(self, plot, actor, axes_color):
1422
+ self.plot = plot
1423
+ self.actors = actor
1424
+ self.id = 0
1425
+ self.endpos = 100
1426
+ self.size = int(plot.window_size[1] / 40)
1427
+ self.startpos = plot.window_size[1] - 2 * self.size
1428
+ self.max_elements = (self.startpos - self.endpos) // (self.size + (self.size // 10))
1429
+ self.i = self.max_elements
1430
+ self.axes_color = axes_color
1431
+ self.text = []
1432
+
1433
+ def __call__(self, state):
1434
+ try:
1435
+ self.plot.button_widgets = [self.plot.button_widgets[0]]
1436
+ except:
1437
+ self.plot.button_widgets = []
1438
+ self.id += 1
1439
+ k = 0
1440
+ startpos = self.startpos
1441
+ while k < self.max_elements:
1442
+ if len(self.text) > k:
1443
+ self.plot.remove_actor(self.text[k])
1444
+ k += 1
1445
+ self.text = []
1446
+ k = 0
1447
+
1448
+ while k < min(self.max_elements, len(self.actors)):
1449
+ if self.i >= len(self.actors):
1450
+ self.i = 0
1451
+ self.id = 0
1452
+ callback = SetVisibilityCallback(self.actors[self.i])
1453
+ self.plot.add_checkbox_button_widget(
1454
+ callback,
1455
+ value=self.actors[self.i]._cached_mesh.GetVisibility() == 1,
1456
+ position=(5.0, startpos),
1457
+ size=self.size,
1458
+ border_size=1,
1459
+ color_on=[i / 255 for i in self.actors[self.i].color],
1460
+ color_off="grey",
1461
+ background_color=None,
1462
+ )
1463
+ self.text.append(
1464
+ self.plot.add_text(
1465
+ self.actors[self.i].name,
1466
+ position=(25.0, startpos),
1467
+ font_size=self.size // 3,
1468
+ color=self.axes_color,
1469
+ )
1470
+ )
1471
+ startpos = startpos - self.size - (self.size // 10)
1472
+ k += 1
1473
+ self.i += 1
1474
+
1475
+ if len(self.objects) > 100:
1476
+ actors = [i for i in self._fields if i._cached_mesh] + self._objects
1477
+ else:
1478
+ actors = [i for i in self._fields if i._cached_mesh] + self._objects
1479
+ # if texts and len(texts) < len(actors):
1480
+ callback = ChangePageCallback(self.pv, actors, axes_color)
1481
+
1482
+ callback.__call__(False)
1483
+ if callback.max_elements < len(actors):
1484
+ self.pv.add_checkbox_button_widget(
1485
+ callback,
1486
+ value=True,
1487
+ position=(5.0, self.pv.window_size[1]),
1488
+ size=int(1.5 * size),
1489
+ border_size=2,
1490
+ color_on=axes_color,
1491
+ color_off=axes_color,
1492
+ )
1493
+ self.pv.add_text("Next", position=(50.0, self.pv.window_size[1]), font_size=size // 3, color="grey")
1494
+ self.pv.button_widgets.insert(
1495
+ 0, self.pv.button_widgets.pop(self.pv.button_widgets.index(self.pv.button_widgets[-1]))
1496
+ )
1497
+
1498
+ @pyedb_function_handler()
1499
+ def plot(self, export_image_path=None): # pragma: no cover
1500
+ """Plot the current available Data. With `s` key a screenshot is saved in export_image_path or in tempdir.
1501
+
1502
+ Parameters
1503
+ ----------
1504
+
1505
+ export_image_path : str
1506
+ Path to image to save.
1507
+
1508
+ Returns
1509
+ -------
1510
+ bool
1511
+ """
1512
+ self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=self.off_screen, window_size=self.windows_size)
1513
+ self.pv.enable_ssao()
1514
+ self.pv.enable_parallel_projection()
1515
+ self.meshes = None
1516
+ if self.background_image:
1517
+ self.pv.add_background_image(self.background_image)
1518
+ else:
1519
+ self.pv.background_color = [i / 255 for i in self.background_color]
1520
+ self._read_mesh_files()
1521
+ axes_color = [0 if i >= 128 else 255 for i in self.background_color]
1522
+ if self.color_bar:
1523
+ sargs = dict(
1524
+ title_font_size=10,
1525
+ label_font_size=10,
1526
+ shadow=True,
1527
+ n_labels=9,
1528
+ italic=True,
1529
+ fmt="%.1f",
1530
+ font_family="arial",
1531
+ interactive=True,
1532
+ color=axes_color,
1533
+ vertical=False,
1534
+ )
1535
+ else:
1536
+ sargs = dict(
1537
+ position_x=2,
1538
+ position_y=2,
1539
+ )
1540
+ for field in self._fields:
1541
+ if field.is_vector:
1542
+ field._cached_polydata.set_active_vectors("vectors")
1543
+ field._cached_polydata["vectors"] = field._cached_polydata["vectors"] * field.vector_scale
1544
+ self.pv.add_mesh(
1545
+ field._cached_polydata.arrows,
1546
+ scalars=field.label,
1547
+ log_scale=False if self.convert_fields_in_db else field.log_scale,
1548
+ scalar_bar_args=sargs,
1549
+ cmap=field.color_map,
1550
+ )
1551
+ field._cached_polydata["vectors"] = field._cached_polydata["vectors"] / field.vector_scale
1552
+ elif self.range_max is not None and self.range_min is not None:
1553
+ field._cached_mesh = self.pv.add_mesh(
1554
+ field._cached_polydata,
1555
+ scalars=field.label,
1556
+ log_scale=False if self.convert_fields_in_db else field.log_scale,
1557
+ scalar_bar_args=sargs,
1558
+ cmap=field.color_map,
1559
+ clim=[self.range_min, self.range_max],
1560
+ opacity=field.opacity,
1561
+ show_edges=field.show_edge,
1562
+ )
1563
+ else:
1564
+ field._cached_mesh = self.pv.add_mesh(
1565
+ field._cached_polydata,
1566
+ scalars=field.label,
1567
+ log_scale=False if self.convert_fields_in_db else field.log_scale,
1568
+ scalar_bar_args=sargs,
1569
+ cmap=field.color_map,
1570
+ opacity=field.opacity,
1571
+ show_edges=field.show_edge,
1572
+ )
1573
+
1574
+ self.pv.set_scale(self.x_scale, self.y_scale, self.z_scale)
1575
+
1576
+ if self.show_legend:
1577
+ self._add_buttons()
1578
+
1579
+ if self.show_axes:
1580
+ self.pv.show_axes()
1581
+ if not self.is_notebook and self.show_grid:
1582
+ self.pv.show_grid(color=tuple(axes_color), grid=self.show_grid, fmt="%.3e")
1583
+ if self.bounding_box:
1584
+ self.pv.add_bounding_box(color=tuple(axes_color))
1585
+ self.pv.set_focus(self.pv.mesh.center)
1586
+
1587
+ if not self.isometric_view:
1588
+ if isinstance(self.camera_position, (tuple, list)):
1589
+ self.pv.camera.position = self.camera_position
1590
+ self.pv.camera.focal_point = self.focal_point
1591
+ self.pv.camera.viewup = self.view_up
1592
+ else:
1593
+ self.pv.camera_position = self.camera_position
1594
+ self.pv.camera.focal_point = self.focal_point
1595
+ self.pv.camera.azimuth += self.azimuth_angle
1596
+ self.pv.camera.roll += self.roll_angle
1597
+ self.pv.camera.elevation += self.elevation_angle
1598
+ else:
1599
+ self.pv.isometric_view()
1600
+ self.pv.camera.zoom(self.zoom)
1601
+ if export_image_path:
1602
+ path_image = os.path.dirname(export_image_path)
1603
+ root_name, format = os.path.splitext(os.path.basename(export_image_path))
1604
+ else:
1605
+ path_image = tempfile.gettempdir() # pragma: no cover
1606
+ format = ".png" # pragma: no cover
1607
+ root_name = "Image" # pragma: no cover
1608
+
1609
+ def s_callback(): # pragma: no cover
1610
+ """save screenshots"""
1611
+ exp = os.path.join(
1612
+ path_image, "{}{}{}".format(root_name, datetime.now().strftime("%Y_%M_%d_%H-%M-%S"), format)
1613
+ )
1614
+ self.pv.screenshot(exp, return_img=False)
1615
+
1616
+ self.pv.add_key_event("s", s_callback)
1617
+ if export_image_path:
1618
+ self.pv.show(screenshot=export_image_path, full_screen=True)
1619
+ elif self.is_notebook: # pragma: no cover
1620
+ self.pv.show() # pragma: no cover
1621
+ else:
1622
+ self.pv.show(full_screen=True) # pragma: no cover
1623
+
1624
+ self.image_file = export_image_path
1625
+ return True
1626
+
1627
+ @pyedb_function_handler()
1628
+ def clean_cache_and_files(self, remove_objs=True, remove_fields=True, clean_cache=False): # pragma: no cover
1629
+ """Clean downloaded files, and, on demand, also the cached meshes.
1630
+
1631
+ Parameters
1632
+ ----------
1633
+ remove_objs : bool
1634
+ remove_fields : bool
1635
+ clean_cache : bool
1636
+
1637
+ Returns
1638
+ -------
1639
+ bool
1640
+ """
1641
+ if remove_objs:
1642
+ for el in self.objects:
1643
+ if os.path.exists(el.path):
1644
+ os.remove(el.path)
1645
+ if clean_cache:
1646
+ el._cached_mesh = None
1647
+ el._cached_polydata = None
1648
+ if remove_fields:
1649
+ for el in self.fields:
1650
+ if os.path.exists(el.path):
1651
+ os.remove(el.path)
1652
+ if clean_cache:
1653
+ el._cached_mesh = None
1654
+ el._cached_polydata = None
1655
+ return True
1656
+
1657
+ @pyedb_function_handler()
1658
+ def animate(self): # pragma: no cover
1659
+ """Animate the current field plot.
1660
+
1661
+ Returns
1662
+ -------
1663
+ bool
1664
+ """
1665
+
1666
+ assert len(self.frames) > 0, "Number of Fields have to be greater than 1 to do an animation."
1667
+ if self.is_notebook:
1668
+ self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=True, window_size=self.windows_size)
1669
+ else:
1670
+ self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=self.off_screen, window_size=self.windows_size)
1671
+ if self.background_image:
1672
+ self.pv.add_background_image(self.background_image)
1673
+ else:
1674
+ self.pv.background_color = [i / 255 for i in self.background_color]
1675
+ self._read_mesh_files(read_frames=True)
1676
+
1677
+ axes_color = [0 if i >= 128 else 1 for i in self.background_color]
1678
+
1679
+ self.pv.set_scale(self.x_scale, self.y_scale, self.z_scale)
1680
+ if self.show_axes:
1681
+ self.pv.show_axes()
1682
+ if self.show_grid and not self.is_notebook:
1683
+ self.pv.show_grid(color=tuple(axes_color))
1684
+ if self.bounding_box:
1685
+ self.pv.add_bounding_box(color=tuple(axes_color))
1686
+ if self.show_legend:
1687
+ labels = []
1688
+ for m in self.objects:
1689
+ labels.append([m.name, [i / 255 for i in m.color]])
1690
+ for m in self.fields:
1691
+ labels.append([m.name, "red"])
1692
+ self.pv.add_legend(labels=labels, bcolor=None, face="circle", size=[0.15, 0.15])
1693
+ if not self.isometric_view:
1694
+ if isinstance(self.camera_position, (tuple, list)):
1695
+ self.pv.camera.position = self.camera_position
1696
+ self.pv.camera.focal_point = self.focal_point
1697
+ self.pv.camera.up = self.view_up
1698
+ else:
1699
+ self.pv.camera_position = self.camera_position
1700
+ self.pv.camera.azimuth += self.azimuth_angle
1701
+ self.pv.camera.roll += self.roll_angle
1702
+ self.pv.camera.elevation += self.elevation_angle
1703
+ else:
1704
+ self.pv.isometric_view()
1705
+ self.pv.zoom = self.zoom
1706
+ self._animating = True
1707
+
1708
+ if self.gif_file:
1709
+ self.pv.open_gif(self.gif_file)
1710
+
1711
+ def q_callback():
1712
+ """exit when user wants to leave"""
1713
+ self._animating = False
1714
+
1715
+ self._pause = False
1716
+
1717
+ def p_callback():
1718
+ """exit when user wants to leave"""
1719
+ self._pause = not self._pause
1720
+
1721
+ self.pv.add_text(
1722
+ "Press p for Play/Pause, Press q to exit ", font_size=8, position="upper_left", color=tuple(axes_color)
1723
+ )
1724
+ self.pv.add_text(" ", font_size=10, position=[0, 0], color=tuple(axes_color))
1725
+ self.pv.add_key_event("q", q_callback)
1726
+ self.pv.add_key_event("p", p_callback)
1727
+ if self.color_bar:
1728
+ sargs = dict(
1729
+ title_font_size=10,
1730
+ label_font_size=10,
1731
+ shadow=True,
1732
+ n_labels=9,
1733
+ italic=True,
1734
+ fmt="%.1f",
1735
+ font_family="arial",
1736
+ )
1737
+ else:
1738
+ sargs = dict(
1739
+ position_x=2,
1740
+ position_y=2,
1741
+ )
1742
+
1743
+ for field in self._fields:
1744
+ field._cached_mesh = self.pv.add_mesh(
1745
+ field._cached_polydata,
1746
+ scalars=field.label,
1747
+ log_scale=False if self.convert_fields_in_db else field.log_scale,
1748
+ scalar_bar_args=sargs,
1749
+ cmap=field.color_map,
1750
+ opacity=field.opacity,
1751
+ )
1752
+ # run until q is pressed
1753
+ if self.pv.mesh:
1754
+ self.pv.set_focus(self.pv.mesh.center)
1755
+
1756
+ cpos = self.pv.show(interactive=False, auto_close=False, interactive_update=not self.off_screen)
1757
+
1758
+ if self.range_min is not None and self.range_max is not None:
1759
+ mins = self.range_min
1760
+ maxs = self.range_max
1761
+ else:
1762
+ mins = 1e20
1763
+ maxs = -1e20
1764
+ for el in self.frames:
1765
+ if np.min(el._cached_polydata.point_data[el.label]) < mins:
1766
+ mins = np.min(el._cached_polydata.point_data[el.label])
1767
+ if np.max(el._cached_polydata.point_data[el.label]) > maxs:
1768
+ maxs = np.max(el._cached_polydata.point_data[el.label])
1769
+
1770
+ self.frames[0]._cached_mesh = self.pv.add_mesh(
1771
+ self.frames[0]._cached_polydata,
1772
+ scalars=self.frames[0].label,
1773
+ log_scale=False if self.convert_fields_in_db else self.frames[0].log_scale,
1774
+ scalar_bar_args=sargs,
1775
+ cmap=self.frames[0].color_map,
1776
+ clim=[mins, maxs],
1777
+ show_edges=False,
1778
+ pickable=True,
1779
+ smooth_shading=True,
1780
+ name="FieldPlot",
1781
+ opacity=self.frames[0].opacity,
1782
+ )
1783
+ start = time.time()
1784
+ try:
1785
+ self.pv.update(1, force_redraw=True)
1786
+ except:
1787
+ pass
1788
+ if self.gif_file:
1789
+ first_loop = True
1790
+ self.pv.write_frame()
1791
+ else:
1792
+ first_loop = False
1793
+ i = 1
1794
+ while self._animating:
1795
+ if self._pause:
1796
+ time.sleep(1)
1797
+ self.pv.update(1, force_redraw=True)
1798
+ continue
1799
+ # p.remove_actor("FieldPlot")
1800
+ if i >= len(self.frames):
1801
+ if self.off_screen or self.is_notebook:
1802
+ break
1803
+ i = 0
1804
+ first_loop = False
1805
+ scalars = self.frames[i]._cached_polydata.point_data[self.frames[i].label]
1806
+ self.pv.update_scalars(scalars, render=False)
1807
+ if not hasattr(self.pv, "ren_win"):
1808
+ break
1809
+ time.sleep(max(0, (1 / self.frame_per_seconds) - (time.time() - start)))
1810
+ start = time.time()
1811
+ if self.off_screen:
1812
+ self.pv.render()
1813
+ else:
1814
+ self.pv.update(1, force_redraw=True)
1815
+ if first_loop:
1816
+ self.pv.write_frame()
1817
+ i += 1
1818
+ self.pv.close()
1819
+ if self.gif_file:
1820
+ return self.gif_file
1821
+ else:
1822
+ return True
1823
+
1824
+ @pyedb_function_handler()
1825
+ def generate_geometry_mesh(self): # pragma: no cover
1826
+ """Generate mesh for objects only.
1827
+
1828
+ Returns
1829
+ -------
1830
+ Mesh
1831
+ """
1832
+ self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=self.off_screen, window_size=self.windows_size)
1833
+ self._read_mesh_files()
1834
+ if self.array_coordinates:
1835
+ duplicate_mesh = self.meshes.copy()
1836
+ for offset_xyz in self.array_coordinates:
1837
+ translated_mesh = duplicate_mesh.copy()
1838
+ translated_mesh.translate(offset_xyz, inplace=True)
1839
+ self.meshes += translated_mesh
1840
+ return self.meshes