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.
- pyedb/__init__.py +17 -0
- pyedb/dotnet/__init__.py +0 -0
- pyedb/dotnet/application/Variables.py +2261 -0
- pyedb/dotnet/application/__init__.py +0 -0
- pyedb/dotnet/clr_module.py +103 -0
- pyedb/dotnet/edb.py +4237 -0
- pyedb/dotnet/edb_core/__init__.py +1 -0
- pyedb/dotnet/edb_core/cell/__init__.py +0 -0
- pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
- pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
- pyedb/dotnet/edb_core/components.py +2669 -0
- pyedb/dotnet/edb_core/configuration.py +423 -0
- pyedb/dotnet/edb_core/definition/__init__.py +0 -0
- pyedb/dotnet/edb_core/definition/component_def.py +166 -0
- pyedb/dotnet/edb_core/definition/component_model.py +30 -0
- pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
- pyedb/dotnet/edb_core/definition/definitions.py +12 -0
- pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
- pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
- pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
- pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
- pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
- pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
- pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
- pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
- pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
- pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
- pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
- pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
- pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
- pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
- pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
- pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
- pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
- pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
- pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
- pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
- pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
- pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
- pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
- pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
- pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
- pyedb/dotnet/edb_core/general.py +181 -0
- pyedb/dotnet/edb_core/hfss.py +1646 -0
- pyedb/dotnet/edb_core/layout.py +1244 -0
- pyedb/dotnet/edb_core/layout_validation.py +272 -0
- pyedb/dotnet/edb_core/materials.py +939 -0
- pyedb/dotnet/edb_core/net_class.py +335 -0
- pyedb/dotnet/edb_core/nets.py +1215 -0
- pyedb/dotnet/edb_core/padstack.py +1389 -0
- pyedb/dotnet/edb_core/siwave.py +1427 -0
- pyedb/dotnet/edb_core/stackup.py +2703 -0
- pyedb/edb_logger.py +396 -0
- pyedb/generic/__init__.py +0 -0
- pyedb/generic/constants.py +1063 -0
- pyedb/generic/data_handlers.py +320 -0
- pyedb/generic/design_types.py +104 -0
- pyedb/generic/filesystem.py +150 -0
- pyedb/generic/general_methods.py +1535 -0
- pyedb/generic/plot.py +1840 -0
- pyedb/generic/process.py +285 -0
- pyedb/generic/settings.py +224 -0
- pyedb/ipc2581/__init__.py +0 -0
- pyedb/ipc2581/bom/__init__.py +0 -0
- pyedb/ipc2581/bom/bom.py +21 -0
- pyedb/ipc2581/bom/bom_item.py +32 -0
- pyedb/ipc2581/bom/characteristics.py +37 -0
- pyedb/ipc2581/bom/refdes.py +16 -0
- pyedb/ipc2581/content/__init__.py +0 -0
- pyedb/ipc2581/content/color.py +38 -0
- pyedb/ipc2581/content/content.py +55 -0
- pyedb/ipc2581/content/dictionary_color.py +29 -0
- pyedb/ipc2581/content/dictionary_fill.py +28 -0
- pyedb/ipc2581/content/dictionary_line.py +30 -0
- pyedb/ipc2581/content/entry_color.py +13 -0
- pyedb/ipc2581/content/entry_line.py +14 -0
- pyedb/ipc2581/content/fill.py +15 -0
- pyedb/ipc2581/content/layer_ref.py +10 -0
- pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
- pyedb/ipc2581/ecad/__init__.py +0 -0
- pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
- pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
- pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
- pyedb/ipc2581/ecad/cad_data/component.py +41 -0
- pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
- pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
- pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
- pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
- pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
- pyedb/ipc2581/ecad/cad_data/package.py +104 -0
- pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
- pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
- pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
- pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
- pyedb/ipc2581/ecad/cad_data/path.py +89 -0
- pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
- pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
- pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
- pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
- pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
- pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
- pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
- pyedb/ipc2581/ecad/cad_data/step.py +275 -0
- pyedb/ipc2581/ecad/cad_header.py +33 -0
- pyedb/ipc2581/ecad/ecad.py +19 -0
- pyedb/ipc2581/ecad/spec.py +46 -0
- pyedb/ipc2581/history_record.py +37 -0
- pyedb/ipc2581/ipc2581.py +387 -0
- pyedb/ipc2581/logistic_header.py +25 -0
- pyedb/misc/__init__.py +0 -0
- pyedb/misc/aedtlib_personalib_install.py +14 -0
- pyedb/misc/downloads.py +322 -0
- pyedb/misc/misc.py +67 -0
- pyedb/misc/pyedb.runtimeconfig.json +13 -0
- pyedb/misc/siw_feature_config/__init__.py +0 -0
- pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
- pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
- pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
- pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
- pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
- pyedb/misc/utilities.py +27 -0
- pyedb/modeler/geometry_operators.py +2082 -0
- pyedb-0.2.0.dist-info/LICENSE +21 -0
- pyedb-0.2.0.dist-info/METADATA +208 -0
- pyedb-0.2.0.dist-info/RECORD +128 -0
- 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()
|