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
@@ -0,0 +1,1535 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import absolute_import
3
+
4
+ import ast
5
+ import codecs
6
+ from collections import OrderedDict
7
+ import csv
8
+ import datetime
9
+ import difflib
10
+ import fnmatch
11
+ from functools import update_wrapper
12
+ import inspect
13
+ import itertools
14
+ import json
15
+ import logging
16
+ import math
17
+ import os
18
+ import random
19
+ import re
20
+ import string
21
+ import sys
22
+ import tempfile
23
+ import time
24
+ import traceback
25
+
26
+ from pyedb.generic.constants import CSS4_COLORS
27
+ from pyedb.generic.settings import settings
28
+
29
+ is_ironpython = "IronPython" in sys.version or ".NETFramework" in sys.version
30
+ is_linux = os.name == "posix"
31
+ is_windows = not is_linux
32
+ _pythonver = sys.version_info[0]
33
+ inside_desktop = True if is_ironpython and "4.0.30319.42000" in sys.version else False
34
+
35
+ if not is_ironpython:
36
+ import psutil
37
+
38
+ try:
39
+ import xml.etree.cElementTree as ET
40
+
41
+ ET.VERSION
42
+ except ImportError:
43
+ ET = None
44
+
45
+
46
+ class GrpcApiError(Exception):
47
+ """ """
48
+
49
+ pass
50
+
51
+
52
+ class MethodNotSupportedError(Exception):
53
+ """ """
54
+
55
+ pass
56
+
57
+
58
+ def _exception(ex_info, func, args, kwargs, message="Type Error"):
59
+ """Write the trace stack to the desktop when a Python error occurs.
60
+
61
+ Parameters
62
+ ----------
63
+ ex_info :
64
+
65
+ func :
66
+
67
+ args :
68
+
69
+ kwargs :
70
+
71
+ message :
72
+ (Default value = "Type Error")
73
+
74
+ Returns
75
+ -------
76
+
77
+ """
78
+
79
+ tb_data = ex_info[2]
80
+ tb_trace = traceback.format_tb(tb_data)
81
+ _write_mes("{} on {}".format(message.upper(), func.__name__))
82
+ try:
83
+ _write_mes(ex_info[1].args[0])
84
+ except (IndexError, AttributeError):
85
+ pass
86
+ for trace in traceback.format_stack():
87
+ if func.__name__ in trace:
88
+ for el in trace.split("\n"):
89
+ _write_mes(el)
90
+ for trace in tb_trace:
91
+ tblist = trace.split("\n")
92
+ for el in tblist:
93
+ if func.__name__ in el:
94
+ _write_mes(el)
95
+
96
+ message_to_print = ""
97
+ messages = ""
98
+ if "error" in messages:
99
+ message_to_print = messages[messages.index("[error]") :]
100
+ # _write_mes("{} - {} - {}.".format(ex_info[1], func.__name__, message.upper()))
101
+
102
+ if message_to_print:
103
+ _write_mes("Last Electronics Desktop Message - " + message_to_print)
104
+
105
+ args_name = []
106
+ try:
107
+ args_dict = _get_args_dicts(func, args, kwargs)
108
+ first_time_log = True
109
+
110
+ for el in args_dict:
111
+ if el != "self" and args_dict[el]:
112
+ if first_time_log:
113
+ _write_mes("Method arguments: ")
114
+ first_time_log = False
115
+ _write_mes(" {} = {} ".format(el, args_dict[el]))
116
+ except:
117
+ pass
118
+ args = [func.__name__] + [i for i in args_name if i not in ["self"]]
119
+ if not func.__name__.startswith("_"):
120
+ _write_mes(
121
+ "Check Online documentation on: https://edb.docs.pyansys.com/version/stable/search.html?q={}".format(
122
+ "+".join(args)
123
+ )
124
+ )
125
+
126
+
127
+ def _function_handler_wrapper(user_function): # pragma: no cover
128
+ def wrapper(*args, **kwargs): # pragma: no cover
129
+ if not settings.enable_error_handler:
130
+ result = user_function(*args, **kwargs)
131
+ return result
132
+ else:
133
+ try:
134
+ settings.time_tick = time.time()
135
+ out = user_function(*args, **kwargs)
136
+ if settings.enable_debug_logger or settings.enable_debug_edb_logger:
137
+ _log_method(user_function, args, kwargs)
138
+ return out
139
+ except TypeError:
140
+ _exception(sys.exc_info(), user_function, args, kwargs, "Type Error")
141
+ return False
142
+ except ValueError:
143
+ _exception(sys.exc_info(), user_function, args, kwargs, "Value Error")
144
+ return False
145
+ except AttributeError:
146
+ _exception(sys.exc_info(), user_function, args, kwargs, "Attribute Error")
147
+ return False
148
+ except KeyError:
149
+ _exception(sys.exc_info(), user_function, args, kwargs, "Key Error")
150
+ return False
151
+ except IndexError:
152
+ _exception(sys.exc_info(), user_function, args, kwargs, "Index Error")
153
+ return False
154
+ except AssertionError:
155
+ _exception(sys.exc_info(), user_function, args, kwargs, "Assertion Error")
156
+ return False
157
+ except NameError:
158
+ _exception(sys.exc_info(), user_function, args, kwargs, "Name Error")
159
+ return False
160
+ except IOError:
161
+ _exception(sys.exc_info(), user_function, args, kwargs, "IO Error")
162
+ return False
163
+
164
+ return wrapper
165
+
166
+
167
+ def pyedb_function_handler(direct_func=None):
168
+ """Provides an exception handler, logging mechanism, and argument converter for client-server
169
+ communications.
170
+
171
+ This method returns the function itself if correctly executed. Otherwise, it returns ``False``
172
+ and displays errors.
173
+
174
+ """
175
+ if callable(direct_func):
176
+ user_function = direct_func
177
+ wrapper = _function_handler_wrapper(user_function)
178
+ return update_wrapper(wrapper, user_function)
179
+ elif direct_func is not None:
180
+ raise TypeError("Expected first argument to be a callable, or None")
181
+
182
+ def decorating_function(user_function):
183
+ wrapper = _function_handler_wrapper(user_function)
184
+ return update_wrapper(wrapper, user_function)
185
+
186
+ return decorating_function
187
+
188
+
189
+ @pyedb_function_handler()
190
+ def get_filename_without_extension(path):
191
+ """Get the filename without its extension.
192
+
193
+ Parameters
194
+ ----------
195
+ path : str
196
+ Path for the file.
197
+
198
+
199
+ Returns
200
+ -------
201
+ str
202
+ Name for the file, excluding its extension.
203
+
204
+ """
205
+ return os.path.splitext(os.path.split(path)[1])[0]
206
+
207
+
208
+ def _write_mes(mes_text):
209
+ mes_text = str(mes_text)
210
+ parts = [mes_text[i : i + 250] for i in range(0, len(mes_text), 250)]
211
+ for el in parts:
212
+ settings.logger.error(el)
213
+
214
+
215
+ def _log_method(func, new_args, new_kwargs): # pragma: no cover
216
+ if not settings.enable_debug_internal_methods_logger and str(func.__name__)[0] == "_":
217
+ return
218
+ if not settings.enable_debug_geometry_operator_logger and "GeometryOperators" in str(func):
219
+ return
220
+ if (
221
+ not settings.enable_debug_edb_logger
222
+ and "Edb" in str(func) + str(new_args)
223
+ or "edb_core" in str(func) + str(new_args)
224
+ ):
225
+ return
226
+ line_begin = "ARGUMENTS: "
227
+ message = []
228
+ delta = time.time() - settings.time_tick
229
+ m, s = divmod(delta, 60)
230
+ h, m = divmod(m, 60)
231
+ d, h = divmod(h, 24)
232
+ msec = (s - int(s)) * 1000
233
+ if d > 0:
234
+ time_msg = " {}days {}h {}m {}sec.".format(d, h, m, int(s))
235
+ elif h > 0:
236
+ time_msg = " {}h {}m {}sec.".format(h, m, int(s))
237
+ else:
238
+ time_msg = " {}m {}sec {}msec.".format(m, int(s), int(msec))
239
+ if settings.enable_debug_methods_argument_logger:
240
+ args_dict = _get_args_dicts(func, new_args, new_kwargs)
241
+ id = 0
242
+ if new_args:
243
+ object_name = str([new_args[0]])[1:-1]
244
+ id = object_name.find(" object at ")
245
+ if id > 0:
246
+ object_name = object_name[1:id]
247
+ message.append("'{}' was run in {}".format(object_name + "." + str(func.__name__), time_msg))
248
+ else:
249
+ message.append("'{}' was run in {}".format(str(func.__name__), time_msg))
250
+ message.append(line_begin)
251
+ for k, v in args_dict.items():
252
+ if k != "self":
253
+ message.append(" {} = {}".format(k, v))
254
+ for m in message:
255
+ settings.logger.debug(m)
256
+
257
+
258
+ def _get_args_dicts(func, args, kwargs):
259
+ if int(sys.version[0]) > 2:
260
+ args_name = list(OrderedDict.fromkeys(inspect.getfullargspec(func)[0] + list(kwargs.keys())))
261
+ args_dict = OrderedDict(list(itertools.zip_longest(args_name, args)) + list(kwargs.items()))
262
+ else:
263
+ args_name = list(OrderedDict.fromkeys(inspect.getargspec(func)[0] + list(kwargs.keys())))
264
+ args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
265
+ return args_dict
266
+
267
+
268
+ @pyedb_function_handler()
269
+ def env_path(input_version):
270
+ """Get the path of the version environment variable for an AEDT version.
271
+
272
+ Parameters
273
+ ----------
274
+ input_version : str
275
+ AEDT version.
276
+
277
+ Returns
278
+ -------
279
+ str
280
+ Path for the version environment variable.
281
+
282
+ Examples
283
+ --------
284
+ >>> env_path_student("2021.2")
285
+ "C:/Program Files/ANSYSEM/ANSYSEM2021.2/Win64"
286
+ """
287
+ return os.getenv(
288
+ "ANSYSEM_ROOT{0}{1}".format(
289
+ get_version_and_release(input_version)[0], get_version_and_release(input_version)[1]
290
+ ),
291
+ "",
292
+ )
293
+
294
+
295
+ @pyedb_function_handler()
296
+ def get_version_and_release(input_version):
297
+ version = int(input_version[2:4])
298
+ release = int(input_version[5])
299
+ if version < 20:
300
+ if release < 3:
301
+ version -= 1
302
+ else:
303
+ release -= 2
304
+ return (version, release)
305
+
306
+
307
+ @pyedb_function_handler()
308
+ def env_value(input_version):
309
+ """Get the name of the version environment variable for an AEDT version.
310
+
311
+ Parameters
312
+ ----------
313
+ input_version : str
314
+ AEDT version.
315
+
316
+ Returns
317
+ -------
318
+ str
319
+ Name for the version environment variable.
320
+
321
+ Examples
322
+ --------
323
+ >>> env_value("2021.2")
324
+ "ANSYSEM_ROOT211"
325
+ """
326
+ return "ANSYSEM_ROOT{0}{1}".format(
327
+ get_version_and_release(input_version)[0], get_version_and_release(input_version)[1]
328
+ )
329
+
330
+
331
+ @pyedb_function_handler()
332
+ def generate_unique_name(rootname, suffix="", n=6):
333
+ """Generate a new name given a root name and optional suffix.
334
+
335
+ Parameters
336
+ ----------
337
+ rootname :
338
+ Root name to add random characters to.
339
+ suffix : string
340
+ Suffix to add. The default is ``''``.
341
+ n : int
342
+ Number of random characters to add to the name. The default value is ``6``.
343
+
344
+ Returns
345
+ -------
346
+ str
347
+ Newly generated name.
348
+
349
+ """
350
+ char_set = string.ascii_uppercase + string.digits
351
+ uName = "".join(random.choice(char_set) for _ in range(n))
352
+ unique_name = rootname + "_" + uName
353
+ if suffix:
354
+ unique_name += "_" + suffix
355
+ return unique_name
356
+
357
+
358
+ def normalize_path(path_in, sep=None):
359
+ """Normalize path separators.
360
+
361
+ Parameters
362
+ ----------
363
+ path_in : str
364
+ Path to normalize.
365
+ sep : str, optional
366
+ Separator.
367
+
368
+ Returns
369
+ -------
370
+ str
371
+ Path normalized to new separator.
372
+ """
373
+ if sep is None:
374
+ sep = os.sep
375
+ return path_in.replace("\\", sep).replace("/", sep)
376
+
377
+
378
+ @pyedb_function_handler()
379
+ def check_numeric_equivalence(a, b, relative_tolerance=1e-7):
380
+ """Check if two numeric values are equivalent to within a relative tolerance.
381
+
382
+ Paraemters
383
+ ----------
384
+ a : int, float
385
+ Reference value to compare to.
386
+ b : int, float
387
+ Secondary value for the comparison.
388
+ relative_tolerance : float, optional
389
+ Relative tolerance for the equivalence test. The difference is relative to the first value.
390
+ The default is ``1E-7``.
391
+
392
+ Returns
393
+ -------
394
+ bool
395
+ ``True`` if the two passed values are equivalent.
396
+ """
397
+ if abs(a) > 0.0:
398
+ reldiff = abs(a - b) / a
399
+ else:
400
+ reldiff = abs(b)
401
+ return True if reldiff < relative_tolerance else False
402
+
403
+
404
+ @pyedb_function_handler()
405
+ def check_and_download_file(local_path, remote_path, overwrite=True):
406
+ """Check if a file is remote and either download it or return the path.
407
+
408
+ Parameters
409
+ ----------
410
+ local_path : str
411
+ Local path to save the file to.
412
+ remote_path : str
413
+ Path to the remote file.
414
+ overwrite : bool
415
+ Whether to overwrite the file if it already exits locally.
416
+ The default is ``True``.
417
+
418
+ Returns
419
+ -------
420
+ str
421
+ """
422
+ if settings.remote_rpc_session:
423
+ remote_path = remote_path.replace("\\", "/") if remote_path[0] != "\\" else remote_path
424
+ settings.remote_rpc_session.filemanager.download_file(remote_path, local_path, overwrite=overwrite)
425
+ return local_path
426
+ return remote_path
427
+
428
+
429
+ def check_if_path_exists(path):
430
+ """Check whether a path exists or not local or remote machine (for remote sessions only).
431
+
432
+ Parameters
433
+ ----------
434
+ path : str
435
+ Local or remote path to check.
436
+
437
+ Returns
438
+ -------
439
+ bool
440
+ """
441
+ if settings.remote_rpc_session:
442
+ return settings.remote_rpc_session.filemanager.pathexists(path)
443
+ return os.path.exists(path)
444
+
445
+
446
+ @pyedb_function_handler()
447
+ def check_and_download_folder(local_path, remote_path, overwrite=True):
448
+ """Check if a folder is remote and either download it or return the path.
449
+
450
+ Parameters
451
+ ----------
452
+ local_path : str
453
+ Local path to save the folder to.
454
+ remote_path : str
455
+ Path to the remote folder.
456
+ overwrite : bool
457
+ Whether to overwrite the folder if it already exits locally.
458
+ The default is ``True``.
459
+
460
+ Returns
461
+ -------
462
+ str
463
+ """
464
+ if settings.remote_rpc_session:
465
+ remote_path = remote_path.replace("\\", "/") if remote_path[0] != "\\" else remote_path
466
+ settings.remote_rpc_session.filemanager.download_folder(remote_path, local_path, overwrite=overwrite)
467
+ return local_path
468
+ return remote_path
469
+
470
+
471
+ def open_file(file_path, file_options="r"): # pragma: no cover
472
+ """Open a file and return the object.
473
+
474
+ Parameters
475
+ ----------
476
+ file_path : str
477
+ Full absolute path to the file (either local or remote).
478
+ file_options : str, optional
479
+ Options for opening the file.
480
+
481
+ Returns
482
+ -------
483
+ object
484
+ Opened file.
485
+ """
486
+ file_path = file_path.replace("\\", "/") if file_path[0] != "\\" else file_path
487
+ dir_name = os.path.dirname(file_path)
488
+ if "r" in file_options:
489
+ if os.path.exists(file_path):
490
+ return open(file_path, file_options)
491
+ elif settings.remote_rpc_session and settings.remote_rpc_session.filemanager.pathexists(
492
+ file_path
493
+ ): # pragma: no cover
494
+ local_file = os.path.join(tempfile.gettempdir(), os.path.split(file_path)[-1])
495
+ settings.remote_rpc_session.filemanager.download_file(file_path, local_file)
496
+ return open(local_file, file_options)
497
+ elif os.path.exists(dir_name):
498
+ return open(file_path, file_options)
499
+ else:
500
+ settings.logger.error("The file or folder %s does not exist", dir_name)
501
+
502
+
503
+ @pyedb_function_handler()
504
+ def get_string_version(input_version):
505
+ output_version = input_version
506
+ if isinstance(input_version, float):
507
+ output_version = str(input_version)
508
+ if len(output_version) == 4:
509
+ output_version = "20" + output_version
510
+ elif isinstance(input_version, int):
511
+ output_version = str(input_version)
512
+ output_version = "20{}.{}".format(output_version[:2], output_version[-1])
513
+ elif isinstance(input_version, str):
514
+ if len(input_version) == 3:
515
+ output_version = "20{}.{}".format(input_version[:2], input_version[-1])
516
+ elif len(input_version) == 4:
517
+ output_version = "20" + input_version
518
+ return output_version
519
+
520
+
521
+ @pyedb_function_handler()
522
+ def env_path_student(input_version):
523
+ """Get the path of the version environment variable for an AEDT student version.
524
+
525
+ Parameters
526
+ ----------
527
+ input_version : str
528
+ AEDT student version.
529
+
530
+ Returns
531
+ -------
532
+ str
533
+ Path for the student version environment variable.
534
+
535
+ Examples
536
+ --------
537
+ >>> env_path_student("2021.2")
538
+ "C:/Program Files/ANSYSEM/ANSYSEM2021.2/Win64"
539
+ """
540
+ return os.getenv(
541
+ "ANSYSEMSV_ROOT{0}{1}".format(
542
+ get_version_and_release(input_version)[0], get_version_and_release(input_version)[1]
543
+ ),
544
+ "",
545
+ )
546
+
547
+
548
+ @pyedb_function_handler()
549
+ def env_value_student(input_version):
550
+ """Get the name of the version environment variable for an AEDT student version.
551
+
552
+ Parameters
553
+ ----------
554
+ input_version : str
555
+ AEDT student version.
556
+
557
+ Returns
558
+ -------
559
+ str
560
+ Name for the student version environment variable.
561
+
562
+ Examples
563
+ --------
564
+ >>> env_value_student("2021.2")
565
+ "ANSYSEMSV_ROOT211"
566
+ """
567
+ return "ANSYSEMSV_ROOT{0}{1}".format(
568
+ get_version_and_release(input_version)[0], get_version_and_release(input_version)[1]
569
+ )
570
+
571
+
572
+ @pyedb_function_handler()
573
+ def generate_unique_folder_name(rootname=None, folder_name=None): # pragma: no cover
574
+ """Generate a new AEDT folder name given a rootname.
575
+
576
+ Parameters
577
+ ----------
578
+ rootname : str, optional
579
+ Root name for the new folder. The default is ``None``.
580
+ folder_name : str, optional
581
+ Name for the new AEDT folder if one must be created.
582
+
583
+ Returns
584
+ -------
585
+ str
586
+ """
587
+ if not rootname:
588
+ if settings.remote_rpc_session:
589
+ rootname = settings.remote_rpc_session_temp_folder
590
+ else:
591
+ rootname = tempfile.gettempdir()
592
+ if folder_name is None:
593
+ folder_name = generate_unique_name("pyedb_prj", n=3)
594
+ temp_folder = os.path.join(rootname, folder_name)
595
+ if settings.remote_rpc_session and not settings.remote_rpc_session.filemanager.pathexists(temp_folder):
596
+ settings.remote_rpc_session.filemanager.makedirs(temp_folder)
597
+ elif not os.path.exists(temp_folder):
598
+ os.makedirs(temp_folder)
599
+
600
+ return temp_folder
601
+
602
+
603
+ @pyedb_function_handler()
604
+ def generate_unique_project_name(rootname=None, folder_name=None, project_name=None, project_format="aedt"):
605
+ """Generate a new AEDT project name given a rootname.
606
+
607
+ Parameters
608
+ ----------
609
+ rootname : str, optional
610
+ Root name where the new project is to be created.
611
+ folder_name : str, optional
612
+ Name of the folder to create. The default is ``None``, in which case a random folder
613
+ is created. Use ``""`` if you do not want to create a subfolder.
614
+ project_name : str, optional
615
+ Name for the project. The default is ``None``, in which case a random project is
616
+ created. If a project with this name already exists, a new suffix is added.
617
+ project_format : str, optional
618
+ Project format. The default is ``"aedt"``. Options are ``"aedt"`` and ``"aedb"``.
619
+
620
+ Returns
621
+ -------
622
+ str
623
+ """
624
+ if not project_name:
625
+ project_name = generate_unique_name("Project", n=3)
626
+ name_with_ext = project_name + "." + project_format
627
+ folder_path = generate_unique_folder_name(rootname, folder_name=folder_name)
628
+ prj = os.path.join(folder_path, name_with_ext)
629
+ if check_if_path_exists(prj):
630
+ name_with_ext = generate_unique_name(project_name, n=3) + "." + project_format
631
+ prj = os.path.join(folder_path, name_with_ext)
632
+ return prj
633
+
634
+
635
+ def _retry_ntimes(n, function, *args, **kwargs):
636
+ """
637
+
638
+ Parameters
639
+ ----------
640
+ n :
641
+
642
+ function :
643
+
644
+ *args :
645
+
646
+ **kwargs :
647
+
648
+
649
+ Returns
650
+ -------
651
+
652
+ """
653
+ func_name = None
654
+ if function.__name__ == "InvokeAedtObjMethod":
655
+ func_name = args[1]
656
+ retry = 0
657
+ ret_val = None
658
+ inclusion_list = [
659
+ "CreateVia",
660
+ "PasteDesign",
661
+ "Paste",
662
+ "PushExcitations",
663
+ "Rename",
664
+ "RestoreProjectArchive",
665
+ "ImportGerber",
666
+ ]
667
+ # if func_name and func_name not in inclusion_list and not func_name.startswith("Get"):
668
+ if func_name and func_name not in inclusion_list:
669
+ n = 1
670
+ while retry < n:
671
+ try:
672
+ ret_val = function(*args, **kwargs)
673
+ except:
674
+ retry += 1
675
+ time.sleep(settings.retry_n_times_time_interval)
676
+ else:
677
+ return ret_val
678
+ if retry == n:
679
+ if "__name__" in dir(function):
680
+ raise AttributeError("Error in Executing Method {}.".format(function.__name__))
681
+ else:
682
+ raise AttributeError("Error in Executing Method.")
683
+
684
+
685
+ def time_fn(fn, *args, **kwargs): # pragma: no cover
686
+ start = datetime.datetime.now()
687
+ results = fn(*args, **kwargs)
688
+ end = datetime.datetime.now()
689
+ fn_name = fn.__module__ + "." + fn.__name__
690
+ delta = (end - start).microseconds * 1e-6
691
+ print(fn_name + ": " + str(delta) + "s")
692
+ return results
693
+
694
+
695
+ def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
696
+ return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
697
+
698
+
699
+ def is_number(a):
700
+ if isinstance(a, float) or isinstance(a, int):
701
+ return True
702
+ elif isinstance(a, str):
703
+ try:
704
+ float(a)
705
+ return True
706
+ except ValueError:
707
+ return False
708
+ else:
709
+ return False
710
+
711
+
712
+ def is_array(a): # pragma: no cover
713
+ try:
714
+ v = list(ast.literal_eval(a))
715
+ except (ValueError, TypeError, NameError, SyntaxError):
716
+ return False
717
+ else:
718
+ if type(v) is list:
719
+ return True
720
+ else:
721
+ return False
722
+
723
+
724
+ def is_project_locked(project_path):
725
+ """Check if an AEDT project lock file exists.
726
+
727
+ Parameters
728
+ ----------
729
+ project_path : str
730
+ Path for the AEDT project.
731
+
732
+ Returns
733
+ -------
734
+ bool
735
+ ``True`` when successful, ``False`` when failed.
736
+ """
737
+ return check_if_path_exists(project_path + ".lock")
738
+
739
+
740
+ @pyedb_function_handler()
741
+ def remove_project_lock(project_path): # pragma: no cover
742
+ """Check if an AEDT project exists and try to remove the lock file.
743
+
744
+ .. note::
745
+ This operation is risky because the file could be opened in another AEDT instance.
746
+
747
+ Parameters
748
+ ----------
749
+ project_path : str
750
+ Path for the AEDT project.
751
+
752
+ Returns
753
+ -------
754
+ bool
755
+ ``True`` when successful, ``False`` when failed.
756
+ """
757
+ if os.path.exists(project_path + ".lock"):
758
+ os.remove(project_path + ".lock")
759
+ return True
760
+
761
+
762
+ @pyedb_function_handler()
763
+ def read_csv(filename, encoding="utf-8"): # pragma: no cover
764
+ """Read information from a CSV file and return a list.
765
+
766
+ Parameters
767
+ ----------
768
+ filename : str
769
+ Full path and name for the CSV file.
770
+ encoding : str, optional
771
+ File encoding for the CSV file. The default is ``"utf-8"``.
772
+
773
+ Returns
774
+ -------
775
+ list
776
+
777
+ """
778
+
779
+ lines = []
780
+ with codecs.open(filename, "rb", encoding) as csvfile:
781
+ reader = csv.reader(csvfile, delimiter=",")
782
+ for row in reader:
783
+ lines.append(row)
784
+ return lines
785
+
786
+
787
+ @pyedb_function_handler()
788
+ def read_csv_pandas(filename, encoding="utf-8"): # pragma: no cover
789
+ """Read information from a CSV file and return a list.
790
+
791
+ Parameters
792
+ ----------
793
+ filename : str
794
+ Full path and name for the CSV file.
795
+ encoding : str, optional
796
+ File encoding for the CSV file. The default is ``"utf-8"``.
797
+
798
+ Returns
799
+ -------
800
+ :class:`pandas.DataFrame`
801
+
802
+ """
803
+ try:
804
+ import pandas as pd
805
+
806
+ return pd.read_csv(filename, encoding=encoding, header=0, na_values=".")
807
+ except ImportError:
808
+ logging.error("Pandas is not available. Install it.")
809
+ return None
810
+
811
+
812
+ @pyedb_function_handler()
813
+ def read_tab(filename): # pragma: no cover
814
+ """Read information from a TAB file and return a list.
815
+
816
+ Parameters
817
+ ----------
818
+ filename : str
819
+ Full path and name for the TAB file.
820
+
821
+ Returns
822
+ -------
823
+ list
824
+
825
+ """
826
+ with open(filename) as my_file:
827
+ lines = my_file.readlines()
828
+ return lines
829
+
830
+
831
+ @pyedb_function_handler()
832
+ def read_xlsx(filename): # pragma: no cover
833
+ """Read information from an XLSX file and return a list.
834
+
835
+ Parameters
836
+ ----------
837
+ filename : str
838
+ Full path and name for the XLSX file.
839
+
840
+ Returns
841
+ -------
842
+ list
843
+
844
+ """
845
+ try:
846
+ import pandas as pd
847
+
848
+ lines = pd.read_excel(filename)
849
+ return lines
850
+ except ImportError:
851
+ lines = []
852
+ return lines
853
+
854
+
855
+ @pyedb_function_handler()
856
+ def write_csv(output, list_data, delimiter=",", quotechar="|", quoting=csv.QUOTE_MINIMAL): # pragma: no cover
857
+ if is_ironpython:
858
+ f = open(output, "wb")
859
+ else:
860
+ f = open(output, "w", newline="")
861
+ writer = csv.writer(f, delimiter=delimiter, quotechar=quotechar, quoting=quoting)
862
+ for data in list_data:
863
+ writer.writerow(data)
864
+ f.close()
865
+ return True
866
+
867
+
868
+ @pyedb_function_handler()
869
+ def filter_tuple(value, search_key1, search_key2): # pragma: no cover
870
+ """Filter a tuple of two elements with two search keywords."""
871
+ ignore_case = True
872
+
873
+ def _create_pattern(k1, k2):
874
+ k1a = re.sub(r"\?", r".", k1)
875
+ k1b = re.sub(r"\*", r".*?", k1a)
876
+ k2a = re.sub(r"\?", r".", k2)
877
+ k2b = re.sub(r"\*", r".*?", k2a)
878
+ pattern = r".*\({},{}\)".format(k1b, k2b)
879
+ return pattern
880
+
881
+ if ignore_case:
882
+ compiled_re = re.compile(_create_pattern(search_key1, search_key2), re.IGNORECASE)
883
+ else:
884
+ compiled_re = re.compile(_create_pattern(search_key1, search_key2))
885
+
886
+ m = compiled_re.search(value)
887
+ if m:
888
+ return True
889
+ return False
890
+
891
+
892
+ @pyedb_function_handler()
893
+ def filter_string(value, search_key1): # pragma: no cover
894
+ """Filter a string"""
895
+ ignore_case = True
896
+
897
+ def _create_pattern(k1):
898
+ k1a = re.sub(r"\?", r".", k1.replace("\\", "\\\\"))
899
+ k1b = re.sub(r"\*", r".*?", k1a)
900
+ pattern = r"^{}$".format(k1b)
901
+ return pattern
902
+
903
+ if ignore_case:
904
+ compiled_re = re.compile(_create_pattern(search_key1), re.IGNORECASE)
905
+ else:
906
+ compiled_re = re.compile(_create_pattern(search_key1)) # pragma: no cover
907
+
908
+ m = compiled_re.search(value)
909
+ if m:
910
+ return True
911
+ return False
912
+
913
+
914
+ @pyedb_function_handler()
915
+ def recursive_glob(startpath, filepattern): # pragma: no cover
916
+ """Get a list of files matching a pattern, searching recursively from a start path.
917
+
918
+ Keyword Arguments:
919
+ startpath -- starting path (directory)
920
+ filepattern -- fnmatch-style filename pattern
921
+ """
922
+ if settings.remote_rpc_session:
923
+ files = []
924
+ for i in settings.remote_rpc_session.filemanager.listdir(startpath):
925
+ if settings.remote_rpc_session.filemanager.isdir(os.path.join(startpath, i)):
926
+ files.extend(recursive_glob(os.path.join(startpath, i), filepattern))
927
+ elif fnmatch.fnmatch(i, filepattern):
928
+ files.append(os.path.join(startpath, i))
929
+ return files
930
+ else:
931
+ return [
932
+ os.path.join(dirpath, filename)
933
+ for dirpath, _, filenames in os.walk(startpath)
934
+ for filename in filenames
935
+ if fnmatch.fnmatch(filename, filepattern)
936
+ ]
937
+
938
+
939
+ @pyedb_function_handler()
940
+ def number_aware_string_key(s): # pragma: no cover
941
+ """Get a key for sorting strings that treats embedded digit sequences as integers.
942
+
943
+ Parameters
944
+ ----------
945
+ s : str
946
+ String to calculate the key from.
947
+
948
+ Returns
949
+ -------
950
+ tuple
951
+ Tuple of key entries.
952
+ """
953
+
954
+ def is_digit(c):
955
+ return "0" <= c and c <= "9"
956
+
957
+ result = []
958
+ i = 0
959
+ while i < len(s):
960
+ if is_digit(s[i]):
961
+ j = i + 1
962
+ while j < len(s) and is_digit(s[j]):
963
+ j += 1
964
+ key = int(s[i:j])
965
+ result.append(key)
966
+ i = j
967
+ else:
968
+ j = i + 1
969
+ while j < len(s) and not is_digit(s[j]):
970
+ j += 1
971
+ key = s[i:j]
972
+ result.append(key)
973
+ i = j
974
+ return tuple(result)
975
+
976
+
977
+ @pyedb_function_handler()
978
+ def active_sessions(version=None, student_version=False, non_graphical=False): # pragma: no cover
979
+ """Get information for the active AEDT sessions.
980
+
981
+ Parameters
982
+ ----------
983
+ version : str, optional
984
+ Version to check. The default is ``None``, in which case all versions are checked.
985
+ When specifying a version, you can use a three-digit format like ``"222"`` or a
986
+ five-digit format like ``"2022.2"``.
987
+ student_version : bool, optional
988
+ non_graphical : bool, optional
989
+
990
+
991
+ Returns
992
+ -------
993
+ dict
994
+ {AEDT PID: port}
995
+ If the PID corresponds to a COM session port is set to -1
996
+ """
997
+ return_dict = {}
998
+ if student_version:
999
+ keys = ["ansysedtsv.exe", "ansysedtsv"]
1000
+ else:
1001
+ keys = ["ansysedt.exe", "ansysedt"]
1002
+ if version and "." in version:
1003
+ version = version[-4:].replace(".", "")
1004
+ if version and version < "221":
1005
+ version = version[:2] + "." + version[2]
1006
+ for p in psutil.process_iter():
1007
+ try:
1008
+ if p.name() in keys:
1009
+ cmd = p.cmdline()
1010
+ if non_graphical and "-ng" in cmd or not non_graphical:
1011
+ if not version or (version and version in cmd[0]):
1012
+ if "-grpcsrv" in cmd:
1013
+ if not version or (version and version in cmd[0]):
1014
+ try:
1015
+ return_dict[p.pid] = int(cmd[cmd.index("-grpcsrv") + 1])
1016
+ except (IndexError, ValueError):
1017
+ # default desktop grpc port.
1018
+ return_dict[p.pid] = 50051
1019
+ else:
1020
+ return_dict[p.pid] = -1
1021
+ for i in psutil.net_connections():
1022
+ if i.pid == p.pid and (i.laddr.port > 50050 and i.laddr.port < 50200):
1023
+ return_dict[p.pid] = i.laddr.port
1024
+ break
1025
+ except:
1026
+ pass
1027
+ return return_dict
1028
+
1029
+
1030
+ @pyedb_function_handler()
1031
+ def com_active_sessions(version=None, student_version=False, non_graphical=False): # pragma: no cover
1032
+ """Get information for the active COM AEDT sessions.
1033
+
1034
+ Parameters
1035
+ ----------
1036
+ version : str, optional
1037
+ Version to check. The default is ``None``, in which case all versions are checked.
1038
+ When specifying a version, you can use a three-digit format like ``"222"`` or a
1039
+ five-digit format like ``"2022.2"``.
1040
+ student_version : bool, optional
1041
+ Whether to check for student version sessions. The default is ``False``.
1042
+ non_graphical : bool, optional
1043
+ Whether to check only for active non-graphical sessions. The default is ``False``.
1044
+
1045
+ Returns
1046
+ -------
1047
+ List
1048
+ List of AEDT process IDs.
1049
+ """
1050
+
1051
+ all_sessions = active_sessions(version, student_version, non_graphical)
1052
+
1053
+ return_list = []
1054
+ for s, p in all_sessions.items():
1055
+ if p == -1:
1056
+ return_list.append(s)
1057
+ return return_list
1058
+
1059
+
1060
+ @pyedb_function_handler()
1061
+ def grpc_active_sessions(version=None, student_version=False, non_graphical=False): # pragma: no cover
1062
+ """Get information for the active gRPC AEDT sessions.
1063
+
1064
+ Parameters
1065
+ ----------
1066
+ version : str, optional
1067
+ Version to check. The default is ``None``, in which case all versions are checked.
1068
+ When specifying a version, you can use a three-digit format like ``"222"`` or a
1069
+ five-digit format like ``"2022.2"``.
1070
+ student_version : bool, optional
1071
+ Whether to check for student version sessions. The default is ``False``.
1072
+ non_graphical : bool, optional
1073
+ Whether to check only for active non-graphical sessions. The default is ``False``.
1074
+
1075
+ Returns
1076
+ -------
1077
+ List
1078
+ List of gRPC ports.
1079
+ """
1080
+ all_sessions = active_sessions(version, student_version, non_graphical)
1081
+
1082
+ return_list = []
1083
+ for _, p in all_sessions.items():
1084
+ if p > -1:
1085
+ return_list.append(p)
1086
+ return return_list
1087
+
1088
+
1089
+ @pyedb_function_handler()
1090
+ def compute_fft(time_vals, value): # pragma: no cover
1091
+ """Compute FFT of input transient data.
1092
+
1093
+ Parameters
1094
+ ----------
1095
+ time_vals : `pandas.Series`
1096
+ value : `pandas.Series`
1097
+
1098
+ Returns
1099
+ -------
1100
+ tuple
1101
+ Frequency and Values.
1102
+ """
1103
+ try:
1104
+ import numpy as np
1105
+ except ImportError:
1106
+ logging.error("NumPy is not available. Install it.")
1107
+ return False
1108
+
1109
+ deltaT = time_vals[-1] - time_vals[0]
1110
+ num_points = len(time_vals)
1111
+ valueFFT = np.fft.fft(value, num_points)
1112
+ Npoints = int(len(valueFFT) / 2)
1113
+ valueFFT = valueFFT[1 : Npoints + 1]
1114
+ valueFFT = valueFFT / len(valueFFT)
1115
+ n = np.arange(num_points)
1116
+ freq = n / deltaT
1117
+ return freq, valueFFT
1118
+
1119
+
1120
+ def parse_excitation_file(
1121
+ file_name,
1122
+ is_time_domain=True,
1123
+ x_scale=1,
1124
+ y_scale=1,
1125
+ impedance=50,
1126
+ data_format="Power",
1127
+ encoding="utf-8",
1128
+ out_mag="Voltage",
1129
+ ): # pragma: no cover
1130
+ """Parse a csv file and convert data in list that can be applied to Hfss and Hfss3dLayout sources.
1131
+
1132
+ Parameters
1133
+ ----------
1134
+ file_name : str
1135
+ Full name of the input file.
1136
+ is_time_domain : bool, optional
1137
+ Either if the input data is Time based or Frequency Based. Frequency based data are Mag/Phase (deg).
1138
+ x_scale : float, optional
1139
+ Scaling factor for x axis.
1140
+ y_scale : float, optional
1141
+ Scaling factor for y axis.
1142
+ data_format : str, optional
1143
+ Either `"Power"`, `"Current"` or `"Voltage"`.
1144
+ impedance : float, optional
1145
+ Excitation impedance. Default is `50`.
1146
+ encoding : str, optional
1147
+ Csv file encoding.
1148
+ out_mag : str, optional
1149
+ Output magnitude format. It can be `"Voltage"` or `"Power"` depending on Hfss solution.
1150
+
1151
+ Returns
1152
+ -------
1153
+ tuple
1154
+ Frequency, magnitude and phase.
1155
+ """
1156
+ try:
1157
+ import numpy as np
1158
+ except ImportError:
1159
+ logging.error("NumPy is not available. Install it.")
1160
+ return False
1161
+ df = read_csv_pandas(file_name, encoding=encoding)
1162
+ if is_time_domain:
1163
+ time = df[df.keys()[0]].values * x_scale
1164
+ val = df[df.keys()[1]].values * y_scale
1165
+ freq, fval = compute_fft(time, val)
1166
+
1167
+ if data_format.lower() == "current":
1168
+ if out_mag == "Voltage":
1169
+ fval = fval * impedance
1170
+ else:
1171
+ fval = fval * fval * impedance
1172
+ elif data_format.lower() == "voltage":
1173
+ if out_mag == "Power":
1174
+ fval = fval * fval / impedance
1175
+ else:
1176
+ if out_mag == "Voltage":
1177
+ fval = np.sqrt(fval * impedance)
1178
+ mag = list(np.abs(fval))
1179
+ phase = [math.atan2(j, i) * 180 / math.pi for i, j in zip(list(fval.real), list(fval.imag))]
1180
+
1181
+ else:
1182
+ freq = list(df[df.keys()[0]].values * x_scale)
1183
+ if data_format.lower() == "current":
1184
+ mag = df[df.keys()[1]].values * df[df.keys()[1]].values * impedance * y_scale * y_scale
1185
+ elif data_format.lower() == "voltage":
1186
+ mag = df[df.keys()[1]].values * df[df.keys()[1]].values / impedance * y_scale * y_scale
1187
+ else:
1188
+ mag = df[df.keys()[1]].values * y_scale
1189
+ mag = list(mag)
1190
+ phase = list(df[df.keys()[2]].values)
1191
+ return freq, mag, phase
1192
+
1193
+
1194
+ def tech_to_control_file(tech_path, unit="nm", control_path=None): # pragma: no cover
1195
+ """Convert a TECH file to an XML file for use in a GDS or DXF import.
1196
+
1197
+ Parameters
1198
+ ----------
1199
+ tech_path : str
1200
+ Full path to the TECH file.
1201
+ unit : str, optional
1202
+ Tech units. If specified in tech file this parameter will not be used. Default is ``"nm"``.
1203
+ control_path : str, optional
1204
+ Path for outputting the XML file.
1205
+
1206
+ Returns
1207
+ -------
1208
+ str
1209
+ Out xml file.
1210
+ """
1211
+ result = []
1212
+ with open(tech_path) as f:
1213
+ vals = list(CSS4_COLORS.values())
1214
+ id_layer = 0
1215
+ for line in f:
1216
+ line_split = line.split()
1217
+ if len(line_split) == 5:
1218
+ layerID, layer_name, _, elevation, layer_height = line.split()
1219
+ x = ' <Layer Color="{}" GDSIIVia="{}" Name="{}" TargetLayer="{}" Thickness="{}"'.format(
1220
+ vals[id_layer],
1221
+ "true" if layer_name.lower().startswith("v") else "false",
1222
+ layerID,
1223
+ layer_name,
1224
+ layer_height,
1225
+ )
1226
+ x += ' Type="conductor"/>'
1227
+ result.append(x)
1228
+ id_layer += 1
1229
+ elif len(line_split) > 1 and "UNIT" in line_split[0]:
1230
+ unit = line_split[1]
1231
+ if not control_path:
1232
+ control_path = os.path.splitext(tech_path)[0] + ".xml"
1233
+ with open(control_path, "w") as f:
1234
+ f.write('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n')
1235
+ f.write(' <c:Control xmlns:c="http://www.ansys.com/control" schemaVersion="1.0">\n')
1236
+ f.write("\n")
1237
+ f.write(' <Stackup schemaVersion="1.0">\n')
1238
+ f.write(' <Layers LengthUnit="{}">\n'.format(unit))
1239
+ for res in result:
1240
+ f.write(res + "\n")
1241
+
1242
+ f.write(" </Layers>\n")
1243
+ f.write(" </Stackup>\n")
1244
+ f.write("\n")
1245
+ f.write(' <ImportOptions Flatten="true" GDSIIConvertPolygonToCircles="false" ImportDummyNet="true"/>\n')
1246
+ f.write("\n")
1247
+ f.write("</c:Control>\n")
1248
+
1249
+ return control_path
1250
+
1251
+
1252
+ class PropsManager(object):
1253
+ def __getitem__(self, item): # pragma: no cover
1254
+ """Get the `self.props` key value.
1255
+
1256
+ Parameters
1257
+ ----------
1258
+ item : str
1259
+ Key to search
1260
+ """
1261
+ item_split = item.split("/")
1262
+ if len(item_split) == 1:
1263
+ item_split = item_split[0].split("__")
1264
+ props = self.props
1265
+ found_el = []
1266
+ matching_percentage = 1
1267
+ while matching_percentage >= 0.4:
1268
+ for item_value in item_split:
1269
+ found_el = self._recursive_search(props, item_value, matching_percentage)
1270
+ # found_el = difflib.get_close_matches(item_value, list(props.keys()), 1, matching_percentage)
1271
+ if found_el:
1272
+ props = found_el[1][found_el[2]]
1273
+ # props = props[found_el[0]]
1274
+ if found_el:
1275
+ return props
1276
+ else:
1277
+ matching_percentage -= 0.02
1278
+ self._app.logger.warning("Key %s not found.Check one of available keys in self.available_properties", item)
1279
+ return None
1280
+
1281
+ def __setitem__(self, key, value): # pragma: no cover
1282
+ """Set the `self.props` key value.
1283
+
1284
+ Parameters
1285
+ ----------
1286
+ key : str
1287
+ Key to apply.
1288
+ value : int, float, bool, str, dict
1289
+ Value to apply.
1290
+ """
1291
+ item_split = key.split("/")
1292
+ if len(item_split) == 1:
1293
+ item_split = item_split[0].split("__")
1294
+ found_el = []
1295
+ props = self.props
1296
+ matching_percentage = 1
1297
+ key_path = []
1298
+ while matching_percentage >= 0.4:
1299
+ for item_value in item_split:
1300
+ found_el = self._recursive_search(props, item_value, matching_percentage)
1301
+ if found_el:
1302
+ props = found_el[1][found_el[2]]
1303
+ key_path.append(found_el[2])
1304
+ if found_el:
1305
+ if matching_percentage < 1:
1306
+ self._app.logger.info(
1307
+ "Key %s matched internal key '%s' with confidence of %s.",
1308
+ key,
1309
+ "/".join(key_path),
1310
+ round(matching_percentage * 100),
1311
+ )
1312
+ matching_percentage = 0
1313
+
1314
+ else:
1315
+ matching_percentage -= 0.02
1316
+ if found_el:
1317
+ found_el[1][found_el[2]] = value
1318
+ self.update()
1319
+ else:
1320
+ props[key] = value
1321
+ self.update()
1322
+ self._app.logger.warning("Key %s not found. Trying to applying new key ", key)
1323
+
1324
+ @pyedb_function_handler()
1325
+ def _recursive_search(self, dict_in, key="", matching_percentage=0.8): # pragma: no cover
1326
+ f = difflib.get_close_matches(key, list(dict_in.keys()), 1, matching_percentage)
1327
+ if f:
1328
+ return True, dict_in, f[0]
1329
+ else:
1330
+ for v in list(dict_in.values()):
1331
+ if isinstance(v, (dict, OrderedDict)):
1332
+ out_val = self._recursive_search(v, key, matching_percentage)
1333
+ if out_val:
1334
+ return out_val
1335
+ elif isinstance(v, list) and isinstance(v[0], (dict, OrderedDict)):
1336
+ for val in v:
1337
+ out_val = self._recursive_search(val, key, matching_percentage)
1338
+ if out_val:
1339
+ return out_val
1340
+ return False
1341
+
1342
+ @pyedb_function_handler()
1343
+ def _recursive_list(self, dict_in, prefix=""): # pragma: no cover
1344
+ available_list = []
1345
+ for k, v in dict_in.items():
1346
+ if prefix:
1347
+ name = prefix + "/" + k
1348
+ else:
1349
+ name = k
1350
+ available_list.append(name)
1351
+ if isinstance(v, (dict, OrderedDict)):
1352
+ available_list.extend(self._recursive_list(v, name))
1353
+ return available_list
1354
+
1355
+ @property
1356
+ def available_properties(self): # pragma: no cover
1357
+ """Available properties.
1358
+
1359
+ Returns
1360
+ -------
1361
+ list
1362
+ """
1363
+ if self.props:
1364
+ return self._recursive_list(self.props)
1365
+ return []
1366
+
1367
+ @pyedb_function_handler()
1368
+ def update(self):
1369
+ """Update method."""
1370
+ pass
1371
+
1372
+
1373
+ clamp = lambda n, minn, maxn: max(min(maxn, n), minn)
1374
+ rgb_color_codes = {
1375
+ "Black": (0, 0, 0),
1376
+ "Green": (0, 128, 0),
1377
+ "White": (255, 255, 255),
1378
+ "Red": (255, 0, 0),
1379
+ "Lime": (0, 255, 0),
1380
+ "Blue": (0, 0, 255),
1381
+ "Yellow": (255, 255, 0),
1382
+ "Cyan": (0, 255, 255),
1383
+ "Magenta": (255, 0, 255),
1384
+ "Silver": (192, 192, 192),
1385
+ "Gray": (128, 128, 128),
1386
+ "Maroon": (128, 0, 0),
1387
+ "Olive": (128, 128, 0),
1388
+ "Purple": (128, 0, 128),
1389
+ "Teal": (0, 128, 128),
1390
+ "Navy": (0, 0, 128),
1391
+ "copper": (184, 115, 51),
1392
+ "stainless steel": (224, 223, 219),
1393
+ }
1394
+
1395
+
1396
+ def install_with_pip(package_name, package_path=None, upgrade=False, uninstall=False): # pragma: no cover
1397
+ """Install a new package using pip.
1398
+ This method is useful for installing a package from the AEDT Console without launching the Python environment.
1399
+
1400
+ Parameters
1401
+ ----------
1402
+ package_name : str
1403
+ Name of the package to install.
1404
+ package_path : str, optional
1405
+ Path for the GitHub package to download and install. For example, ``git+https://.....``.
1406
+ upgrade : bool, optional
1407
+ Whether to upgrade the package. The default is ``False``.
1408
+ uninstall : bool, optional
1409
+ Whether to install the package or uninstall the package.
1410
+ """
1411
+ if is_linux and is_ironpython:
1412
+ import subprocessdotnet as subprocess
1413
+ else:
1414
+ import subprocess
1415
+ executable = '"{}"'.format(sys.executable) if is_windows else sys.executable
1416
+
1417
+ commands = []
1418
+ if uninstall:
1419
+ commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name])
1420
+ else:
1421
+ if package_path and upgrade:
1422
+ commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name])
1423
+ command = [executable, "-m", "pip", "install", package_path]
1424
+ else:
1425
+ command = [executable, "-m", "pip", "install", package_name]
1426
+ if upgrade:
1427
+ command.append("-U")
1428
+
1429
+ commands.append(command)
1430
+ for command in commands:
1431
+ if is_linux:
1432
+ p = subprocess.Popen(command)
1433
+ else:
1434
+ p = subprocess.Popen(" ".join(command))
1435
+ p.wait()
1436
+
1437
+
1438
+ class Help: # pragma: no cover
1439
+ def __init__(self):
1440
+ self._base_path = "https://edb.docs.pyansys.com/version/stable"
1441
+ self.browser = "default"
1442
+
1443
+ def _launch_ur(self, url):
1444
+ import webbrowser
1445
+
1446
+ if self.browser != "default":
1447
+ webbrowser.get(self.browser).open_new_tab(url)
1448
+ else:
1449
+ webbrowser.open_new_tab(url)
1450
+
1451
+ def search(self, keywords, app_name=None, search_in_examples_only=False):
1452
+ """Search for one or more keywords.
1453
+
1454
+ Parameters
1455
+ ----------
1456
+ keywords : str or list
1457
+ app_name : str, optional
1458
+ Name of a PyEDB app.
1459
+ search_in_examples_only : bool, optional
1460
+ Whether to search for the one or more keywords only in the PyEDB examples.
1461
+ The default is ``False``.
1462
+ """
1463
+ if isinstance(keywords, str):
1464
+ keywords = [keywords]
1465
+ if search_in_examples_only:
1466
+ keywords.append("This example")
1467
+ if app_name:
1468
+ keywords.append(app_name)
1469
+ url = self._base_path + "/search.html?q={}".format("+".join(keywords))
1470
+ self._launch_ur(url)
1471
+
1472
+ def getting_started(self):
1473
+ """Open the PyEDB User guide page."""
1474
+ url = self._base_path + "/user_guide/index.html"
1475
+ self._launch_ur(url)
1476
+
1477
+ def examples(self):
1478
+ """Open the PyEDB Examples page."""
1479
+ url = self._base_path + "/examples/index.html"
1480
+ self._launch_ur(url)
1481
+
1482
+ def github(self):
1483
+ """Open the PyEDB GitHub page."""
1484
+ url = "https://github.com/ansys/pyedb"
1485
+ self._launch_ur(url)
1486
+
1487
+ def changelog(self, release=None):
1488
+ """Open the PyEDB GitHub Changelog for a given release.
1489
+
1490
+ Parameters
1491
+ ----------
1492
+ release : str, optional
1493
+ Release to get the changelog for. For example, ``"0.6.70"``.
1494
+ """
1495
+ if release is None:
1496
+ from pyedb import __version__ as release
1497
+ url = "https://github.com/ansys/pyedb/releases/tag/v" + release
1498
+ self._launch_ur(url)
1499
+
1500
+ def issues(self):
1501
+ """Open the PyEDB GitHub Issues page."""
1502
+ url = "https://github.com/ansys/pyedb/issues"
1503
+ self._launch_ur(url)
1504
+
1505
+ def ansys_forum(self):
1506
+ """Open the PyEDB GitHub Issues page."""
1507
+ url = "https://discuss.ansys.com/discussions/tagged/pyedb"
1508
+ self._launch_ur(url)
1509
+
1510
+ def developer_forum(self):
1511
+ """Open the Discussions page on the Ansys Developer site."""
1512
+ url = "https://developer.ansys.com/"
1513
+ self._launch_ur(url)
1514
+
1515
+
1516
+ # class Property(property):
1517
+ #
1518
+ # @pyedb_function_handler()
1519
+ # def getter(self, fget):
1520
+ # """Property getter."""
1521
+ # return self.__class__.__base__(fget, self.fset, self.fdel, self.__doc__)
1522
+ #
1523
+ # @pyedb_function_handler()
1524
+ # def setter(self, fset):
1525
+ # """Property setter."""
1526
+ # return self.__class__.__base__(self.fget, fset, self.fdel, self.__doc__)
1527
+ #
1528
+ # @pyedb_function_handler()
1529
+ # def deleter(self, fdel):
1530
+ # """Property deleter."""
1531
+ # return self.__class__.__base__(self.fget, self.fset, fdel, self.__doc__)
1532
+
1533
+ # property = Property
1534
+
1535
+ online_help = Help()