pyedb 0.2.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (128) hide show
  1. pyedb/__init__.py +17 -0
  2. pyedb/dotnet/__init__.py +0 -0
  3. pyedb/dotnet/application/Variables.py +2261 -0
  4. pyedb/dotnet/application/__init__.py +0 -0
  5. pyedb/dotnet/clr_module.py +103 -0
  6. pyedb/dotnet/edb.py +4237 -0
  7. pyedb/dotnet/edb_core/__init__.py +1 -0
  8. pyedb/dotnet/edb_core/cell/__init__.py +0 -0
  9. pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
  10. pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
  11. pyedb/dotnet/edb_core/components.py +2669 -0
  12. pyedb/dotnet/edb_core/configuration.py +423 -0
  13. pyedb/dotnet/edb_core/definition/__init__.py +0 -0
  14. pyedb/dotnet/edb_core/definition/component_def.py +166 -0
  15. pyedb/dotnet/edb_core/definition/component_model.py +30 -0
  16. pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
  17. pyedb/dotnet/edb_core/definition/definitions.py +12 -0
  18. pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
  19. pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
  20. pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
  21. pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
  22. pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
  23. pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
  24. pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
  25. pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
  26. pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
  27. pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
  28. pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
  29. pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
  30. pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
  31. pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
  32. pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
  33. pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
  34. pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
  35. pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
  36. pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
  37. pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
  38. pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
  39. pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
  40. pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
  41. pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
  42. pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
  43. pyedb/dotnet/edb_core/general.py +181 -0
  44. pyedb/dotnet/edb_core/hfss.py +1646 -0
  45. pyedb/dotnet/edb_core/layout.py +1244 -0
  46. pyedb/dotnet/edb_core/layout_validation.py +272 -0
  47. pyedb/dotnet/edb_core/materials.py +939 -0
  48. pyedb/dotnet/edb_core/net_class.py +335 -0
  49. pyedb/dotnet/edb_core/nets.py +1215 -0
  50. pyedb/dotnet/edb_core/padstack.py +1389 -0
  51. pyedb/dotnet/edb_core/siwave.py +1427 -0
  52. pyedb/dotnet/edb_core/stackup.py +2703 -0
  53. pyedb/edb_logger.py +396 -0
  54. pyedb/generic/__init__.py +0 -0
  55. pyedb/generic/constants.py +1063 -0
  56. pyedb/generic/data_handlers.py +320 -0
  57. pyedb/generic/design_types.py +104 -0
  58. pyedb/generic/filesystem.py +150 -0
  59. pyedb/generic/general_methods.py +1535 -0
  60. pyedb/generic/plot.py +1840 -0
  61. pyedb/generic/process.py +285 -0
  62. pyedb/generic/settings.py +224 -0
  63. pyedb/ipc2581/__init__.py +0 -0
  64. pyedb/ipc2581/bom/__init__.py +0 -0
  65. pyedb/ipc2581/bom/bom.py +21 -0
  66. pyedb/ipc2581/bom/bom_item.py +32 -0
  67. pyedb/ipc2581/bom/characteristics.py +37 -0
  68. pyedb/ipc2581/bom/refdes.py +16 -0
  69. pyedb/ipc2581/content/__init__.py +0 -0
  70. pyedb/ipc2581/content/color.py +38 -0
  71. pyedb/ipc2581/content/content.py +55 -0
  72. pyedb/ipc2581/content/dictionary_color.py +29 -0
  73. pyedb/ipc2581/content/dictionary_fill.py +28 -0
  74. pyedb/ipc2581/content/dictionary_line.py +30 -0
  75. pyedb/ipc2581/content/entry_color.py +13 -0
  76. pyedb/ipc2581/content/entry_line.py +14 -0
  77. pyedb/ipc2581/content/fill.py +15 -0
  78. pyedb/ipc2581/content/layer_ref.py +10 -0
  79. pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
  80. pyedb/ipc2581/ecad/__init__.py +0 -0
  81. pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
  82. pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
  83. pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
  84. pyedb/ipc2581/ecad/cad_data/component.py +41 -0
  85. pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
  86. pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
  87. pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
  88. pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
  89. pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
  90. pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
  91. pyedb/ipc2581/ecad/cad_data/package.py +104 -0
  92. pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
  93. pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
  94. pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
  95. pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
  96. pyedb/ipc2581/ecad/cad_data/path.py +89 -0
  97. pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
  98. pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
  99. pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
  100. pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
  101. pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
  102. pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
  103. pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
  104. pyedb/ipc2581/ecad/cad_data/step.py +275 -0
  105. pyedb/ipc2581/ecad/cad_header.py +33 -0
  106. pyedb/ipc2581/ecad/ecad.py +19 -0
  107. pyedb/ipc2581/ecad/spec.py +46 -0
  108. pyedb/ipc2581/history_record.py +37 -0
  109. pyedb/ipc2581/ipc2581.py +387 -0
  110. pyedb/ipc2581/logistic_header.py +25 -0
  111. pyedb/misc/__init__.py +0 -0
  112. pyedb/misc/aedtlib_personalib_install.py +14 -0
  113. pyedb/misc/downloads.py +322 -0
  114. pyedb/misc/misc.py +67 -0
  115. pyedb/misc/pyedb.runtimeconfig.json +13 -0
  116. pyedb/misc/siw_feature_config/__init__.py +0 -0
  117. pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
  118. pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
  119. pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
  120. pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
  121. pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
  122. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
  123. pyedb/misc/utilities.py +27 -0
  124. pyedb/modeler/geometry_operators.py +2082 -0
  125. pyedb-0.2.0.dist-info/LICENSE +21 -0
  126. pyedb-0.2.0.dist-info/METADATA +208 -0
  127. pyedb-0.2.0.dist-info/RECORD +128 -0
  128. pyedb-0.2.0.dist-info/WHEEL +4 -0
pyedb/dotnet/edb.py ADDED
@@ -0,0 +1,4237 @@
1
+ """This module contains the ``Edb`` class.
2
+
3
+ This module is implicitly loaded in HFSS 3D Layout when launched.
4
+
5
+ """
6
+ from itertools import combinations
7
+ import os
8
+ import re
9
+ import shutil
10
+ import sys
11
+ import tempfile
12
+ import time
13
+ import traceback
14
+ import warnings
15
+
16
+ from pyedb.dotnet.application.Variables import decompose_variable_value
17
+ from pyedb.dotnet.edb_core.components import Components
18
+ from pyedb.dotnet.edb_core.configuration import Configuration
19
+ from pyedb.dotnet.edb_core.dotnet.database import Database
20
+ from pyedb.dotnet.edb_core.dotnet.layout import LayoutDotNet
21
+ from pyedb.dotnet.edb_core.edb_data.control_file import (
22
+ ControlFile,
23
+ convert_technology_file,
24
+ )
25
+ from pyedb.dotnet.edb_core.edb_data.design_options import EdbDesignOptions
26
+ from pyedb.dotnet.edb_core.edb_data.edbvalue import EdbValue
27
+ from pyedb.dotnet.edb_core.edb_data.hfss_simulation_setup_data import (
28
+ HfssSimulationSetup,
29
+ )
30
+ from pyedb.dotnet.edb_core.edb_data.ports import (
31
+ BundleWavePort,
32
+ CircuitPort,
33
+ CoaxPort,
34
+ ExcitationSources,
35
+ GapPort,
36
+ WavePort,
37
+ )
38
+ from pyedb.dotnet.edb_core.edb_data.simulation_configuration import (
39
+ SimulationConfiguration,
40
+ )
41
+ from pyedb.dotnet.edb_core.edb_data.siwave_simulation_setup_data import (
42
+ SiwaveDCSimulationSetup,
43
+ SiwaveSYZSimulationSetup,
44
+ )
45
+ from pyedb.dotnet.edb_core.edb_data.sources import SourceType
46
+ from pyedb.dotnet.edb_core.edb_data.terminals import Terminal
47
+ from pyedb.dotnet.edb_core.edb_data.variables import Variable
48
+ from pyedb.dotnet.edb_core.general import (
49
+ LayoutObjType,
50
+ Primitives,
51
+ convert_py_list_to_net_list,
52
+ )
53
+ from pyedb.dotnet.edb_core.hfss import EdbHfss
54
+ from pyedb.dotnet.edb_core.layout import EdbLayout
55
+ from pyedb.dotnet.edb_core.layout_validation import LayoutValidation
56
+ from pyedb.dotnet.edb_core.materials import Materials
57
+ from pyedb.dotnet.edb_core.net_class import (
58
+ EdbDifferentialPairs,
59
+ EdbExtendedNets,
60
+ EdbNetClasses,
61
+ )
62
+ from pyedb.dotnet.edb_core.nets import EdbNets
63
+ from pyedb.dotnet.edb_core.padstack import EdbPadstacks
64
+ from pyedb.dotnet.edb_core.siwave import EdbSiwave
65
+ from pyedb.dotnet.edb_core.stackup import Stackup
66
+ from pyedb.generic.constants import AEDT_UNITS, SolverType
67
+ from pyedb.generic.general_methods import (
68
+ generate_unique_name,
69
+ get_string_version,
70
+ inside_desktop,
71
+ is_ironpython,
72
+ is_linux,
73
+ is_windows,
74
+ pyedb_function_handler,
75
+ )
76
+ from pyedb.generic.process import SiwaveSolve
77
+ from pyedb.generic.settings import settings
78
+ from pyedb.ipc2581.ipc2581 import Ipc2581
79
+ from pyedb.modeler.geometry_operators import GeometryOperators
80
+
81
+ if is_linux and is_ironpython:
82
+ import subprocessdotnet as subprocess
83
+ else:
84
+ import subprocess
85
+
86
+
87
+ class Edb(Database):
88
+ """Provides the EDB application interface.
89
+
90
+ This module inherits all objects that belong to EDB.
91
+
92
+ Parameters
93
+ ----------
94
+ edbpath : str, optional
95
+ Full path to the ``aedb`` folder. The variable can also contain
96
+ the path to a layout to import. Allowed formats are BRD, MCM,
97
+ XML (IPC2581), GDS, and DXF. The default is ``None``.
98
+ For GDS import, the Ansys control file (also XML) should have the same
99
+ name as the GDS file. Only the file extension differs.
100
+ cellname : str, optional
101
+ Name of the cell to select. The default is ``None``.
102
+ isreadonly : bool, optional
103
+ Whether to open EBD in read-only mode when it is
104
+ owned by HFSS 3D Layout. The default is ``False``.
105
+ edbversion : str, int, float, optional
106
+ Version of EDB to use. The default is ``None``.
107
+ Examples of input values are ``232``, ``23.2``, ``2023.2``, ``"2023.2"``.
108
+ isaedtowned : bool, optional
109
+ Whether to launch EDB from HFSS 3D Layout. The
110
+ default is ``False``.
111
+ oproject : optional
112
+ Reference to the AEDT project object.
113
+ student_version : bool, optional
114
+ Whether to open the AEDT student version. The default is ``False.``
115
+ technology_file : str, optional
116
+ Full path to technology file to be converted to xml before importing or xml.
117
+ Supported by GDS format only.
118
+
119
+ Examples
120
+ --------
121
+ Create an ``Edb`` object and a new EDB cell.
122
+
123
+ >>> from pyedb.dotnet.edb import Edb
124
+ >>> app = Edb()
125
+
126
+ Add a new variable named "s1" to the ``Edb`` instance.
127
+
128
+ >>> app['s1'] = "0.25 mm"
129
+ >>> app['s1'].tofloat
130
+ >>> 0.00025
131
+ >>> app['s1'].tostring
132
+ >>> "0.25mm"
133
+
134
+ or add a new parameter with description:
135
+
136
+ >>> app['s2'] = ["20um", "Spacing between traces"]
137
+ >>> app['s2'].value
138
+ >>> 1.9999999999999998e-05
139
+ >>> app['s2'].description
140
+ >>> 'Spacing between traces'
141
+
142
+ Create an ``Edb`` object and open the specified project.
143
+
144
+ >>> app = Edb("myfile.aedb")
145
+
146
+ Create an ``Edb`` object from GDS and control files.
147
+ The XML control file resides in the same directory as the GDS file: (myfile.xml).
148
+
149
+ >>> app = Edb("/path/to/file/myfile.gds")
150
+
151
+ """
152
+
153
+ def __init__(
154
+ self,
155
+ edbpath=None,
156
+ cellname=None,
157
+ isreadonly=False,
158
+ edbversion=None,
159
+ isaedtowned=False,
160
+ oproject=None,
161
+ student_version=False,
162
+ use_ppe=False,
163
+ technology_file=None,
164
+ ):
165
+ edbversion = get_string_version(edbversion)
166
+ self._clean_variables()
167
+ Database.__init__(self, edbversion=edbversion, student_version=student_version)
168
+ self.standalone = True
169
+ self.oproject = oproject
170
+ self._main = sys.modules["__main__"]
171
+ self.edbversion = edbversion
172
+ self.isaedtowned = isaedtowned
173
+ self.isreadonly = isreadonly
174
+ self.cellname = cellname
175
+ if not edbpath:
176
+ if is_windows:
177
+ edbpath = os.getenv("USERPROFILE")
178
+ if not edbpath:
179
+ edbpath = os.path.expanduser("~")
180
+ edbpath = os.path.join(edbpath, "Documents", generate_unique_name("layout") + ".aedb")
181
+ else:
182
+ edbpath = os.getenv("HOME")
183
+ if not edbpath:
184
+ edbpath = os.path.expanduser("~")
185
+ edbpath = os.path.join(edbpath, generate_unique_name("layout") + ".aedb")
186
+ self.logger.info("No EDB is provided. Creating a new EDB {}.".format(edbpath))
187
+ self.edbpath = edbpath
188
+ self.log_name = None
189
+ if edbpath:
190
+ self.log_name = os.path.join(
191
+ os.path.dirname(edbpath),
192
+ "pyedb_" + os.path.splitext(os.path.split(edbpath)[-1])[0] + ".log",
193
+ )
194
+
195
+ if isaedtowned and (inside_desktop or settings.remote_rpc_session):
196
+ self.open_edb_inside_aedt()
197
+ elif edbpath[-3:] in ["brd", "mcm", "sip", "gds", "xml", "dxf", "tgz"]:
198
+ self.edbpath = edbpath[:-4] + ".aedb"
199
+ working_dir = os.path.dirname(edbpath)
200
+ control_file = None
201
+ if technology_file:
202
+ if os.path.splitext(technology_file)[1] == ".xml":
203
+ control_file = technology_file
204
+ else:
205
+ control_file = convert_technology_file(technology_file, edbversion=edbversion)
206
+ self.import_layout_pcb(edbpath, working_dir, use_ppe=use_ppe, control_file=control_file)
207
+ if settings.enable_local_log_file and self.log_name:
208
+ self._logger.add_file_logger(self.log_name, "Edb")
209
+ self.logger.info("EDB %s was created correctly from %s file.", self.edbpath, edbpath[-2:])
210
+ elif edbpath.endswith("edb.def"):
211
+ self.edbpath = os.path.dirname(edbpath)
212
+ if settings.enable_local_log_file and self.log_name:
213
+ self._logger.add_file_logger(self.log_name, "Edb")
214
+ self.open_edb()
215
+ elif not os.path.exists(os.path.join(self.edbpath, "edb.def")):
216
+ self.create_edb()
217
+ if settings.enable_local_log_file and self.log_name:
218
+ self._logger.add_file_logger(self.log_name, "Edb")
219
+ self.logger.info("EDB %s created correctly.", self.edbpath)
220
+ elif ".aedb" in edbpath:
221
+ self.edbpath = edbpath
222
+ if settings.enable_local_log_file and self.log_name:
223
+ self._logger.add_file_logger(self.log_name, "Edb")
224
+ self.open_edb()
225
+ if self.active_cell:
226
+ self.logger.info("EDB initialized.")
227
+ else:
228
+ self.logger.info("Failed to initialize DLLs.")
229
+
230
+ def __enter__(self):
231
+ return self
232
+
233
+ def __exit__(self, ex_type, ex_value, ex_traceback):
234
+ if ex_type:
235
+ self.edb_exception(ex_value, ex_traceback)
236
+
237
+ @pyedb_function_handler()
238
+ def __getitem__(self, variable_name):
239
+ """Get or Set a variable to the Edb project. The variable can be project using ``$`` prefix or
240
+ it can be a design variable, in which case the ``$`` is omitted.
241
+
242
+ Parameters
243
+ ----------
244
+ variable_name : str
245
+
246
+ Returns
247
+ -------
248
+ variable object : :class:`pyedb.dotnet.edb_core.edb_data.variables.Variable`
249
+
250
+ """
251
+ if self.variable_exists(variable_name)[0]:
252
+ return self.variables[variable_name]
253
+ return
254
+
255
+ @pyedb_function_handler()
256
+ def __setitem__(self, variable_name, variable_value):
257
+ type_error_message = "Allowed values are str, numeric or two-item list with variable description."
258
+ if type(variable_value) in [
259
+ list,
260
+ tuple,
261
+ ]: # Two-item list or tuple. 2nd argument is a str description.
262
+ if len(variable_value) == 2:
263
+ if type(variable_value[1]) is str:
264
+ description = variable_value[1] if len(variable_value[1]) > 0 else None
265
+ else:
266
+ description = None
267
+ self.logger.warning("Invalid type for Edb variable desciprtion is ignored.")
268
+ val = variable_value[0]
269
+ else:
270
+ raise TypeError(type_error_message)
271
+ else:
272
+ description = None
273
+ val = variable_value
274
+ if self.variable_exists(variable_name)[0]:
275
+ self.change_design_variable_value(variable_name, val)
276
+ else:
277
+ self.add_design_variable(variable_name, val)
278
+ if description: # Add the variable description if a two-item list is passed for variable_value.
279
+ self.__getitem__(variable_name).description = description
280
+
281
+ def _clean_variables(self):
282
+ """Initialize internal variables and perform garbage collection."""
283
+ self._materials = None
284
+ self._components = None
285
+ self._core_primitives = None
286
+ self._stackup = None
287
+ self._stackup2 = None
288
+ self._padstack = None
289
+ self._siwave = None
290
+ self._hfss = None
291
+ self._nets = None
292
+ self._layout_instance = None
293
+ self._variables = None
294
+ self._active_cell = None
295
+ self._layout = None
296
+ self._configuration = None
297
+
298
+ @pyedb_function_handler()
299
+ def _init_objects(self):
300
+ self._components = Components(self)
301
+ self._stackup = Stackup(self)
302
+ self._padstack = EdbPadstacks(self)
303
+ self._siwave = EdbSiwave(self)
304
+ self._hfss = EdbHfss(self)
305
+ self._nets = EdbNets(self)
306
+ self._core_primitives = EdbLayout(self)
307
+ self._stackup2 = self._stackup
308
+ self._materials = Materials(self)
309
+
310
+ @property
311
+ def cell_names(self):
312
+ """Cell name container.
313
+
314
+ Returns
315
+ -------
316
+ list of cell names : List[str]
317
+ """
318
+ names = []
319
+ for cell in self.circuit_cells:
320
+ names.append(cell.GetName())
321
+ return names
322
+
323
+ @property
324
+ def design_variables(self):
325
+ """Get all edb design variables.
326
+
327
+ Returns
328
+ -------
329
+ variable dictionary : Dict[str, :class:`pyedb.dotnet.edb_core.edb_data.variables.Variable`]
330
+ """
331
+ d_var = dict()
332
+ for i in self.active_cell.GetVariableServer().GetAllVariableNames():
333
+ d_var[i] = Variable(self, i)
334
+ return d_var
335
+
336
+ @property
337
+ def project_variables(self):
338
+ """Get all project variables.
339
+
340
+ Returns
341
+ -------
342
+ variables dictionary : Dict[str, :class:`pyedb.dotnet.edb_core.edb_data.variables.Variable`]
343
+
344
+ """
345
+ p_var = dict()
346
+ for i in self.active_db.GetVariableServer().GetAllVariableNames():
347
+ p_var[i] = Variable(self, i)
348
+ return p_var
349
+
350
+ @property
351
+ def layout_validation(self):
352
+ """:class:`pyedb.dotnet.edb_core.edb_data.layout_validation.LayoutValidation`.
353
+
354
+ Returns
355
+ -------
356
+ layout validation object : :class: 'pyedb.dotnet.edb_core.layout_validation.LayoutValidation'
357
+ """
358
+ return LayoutValidation(self)
359
+
360
+ @property
361
+ def variables(self):
362
+ """Get all Edb variables.
363
+
364
+ Returns
365
+ -------
366
+ variables dictionary : Dict[str, :class:`pyedb.dotnet.edb_core.edb_data.variables.Variable`]
367
+
368
+ """
369
+ all_vars = dict()
370
+ for i, j in self.project_variables.items():
371
+ all_vars[i] = j
372
+ for i, j in self.design_variables.items():
373
+ all_vars[i] = j
374
+ return all_vars
375
+
376
+ @property
377
+ def terminals(self):
378
+ """Get terminals belonging to active layout.
379
+
380
+ Returns
381
+ -------
382
+ Terminal dictionary : Dict[str, pyedb.dotnet.edb_core.edb_data.terminals.Terminal]
383
+ """
384
+
385
+ temp = {}
386
+ terminal_mapping = Terminal(self)._terminal_mapping
387
+ for i in self.layout.terminals:
388
+ terminal_type = i.ToString().split(".")[-1]
389
+ ter = terminal_mapping[terminal_type](self, i)
390
+ temp[ter.name] = ter
391
+
392
+ return temp
393
+
394
+ @property
395
+ def excitations(self):
396
+ """Get all layout excitations."""
397
+ terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) == 0]
398
+ temp = {}
399
+ for ter in terms:
400
+ if "BundleTerminal" in ter.GetType().ToString():
401
+ temp[ter.GetName()] = BundleWavePort(self, ter)
402
+ else:
403
+ temp[ter.GetName()] = GapPort(self, ter)
404
+ return temp
405
+
406
+ @property
407
+ def ports(self):
408
+ """Get all ports.
409
+
410
+ Returns
411
+ -------
412
+ port dictionary : Dict[str, [:class:`pyedb.dotnet.edb_core.edb_data.ports.GapPort`,
413
+ :class:`pyedb.dotnet.edb_core.edb_data.ports.WavePort`,]]
414
+
415
+ """
416
+ temp = [term for term in self.layout.terminals if not term.IsReferenceTerminal()]
417
+
418
+ ports = {}
419
+ for t in temp:
420
+ t2 = Terminal(self, t)
421
+ if not t2.boundary_type == "PortBoundary":
422
+ continue
423
+
424
+ if t2.is_circuit_port:
425
+ port = CircuitPort(self, t)
426
+ ports[port.name] = port
427
+ elif t2.terminal_type == "BundleTerminal":
428
+ port = BundleWavePort(self, t)
429
+ ports[port.name] = port
430
+ elif t2.hfss_type == "Wave":
431
+ ports[t2.name] = WavePort(self, t)
432
+ elif t2.terminal_type == "PadstackInstanceTerminal":
433
+ ports[t2.name] = CoaxPort(self, t)
434
+ else:
435
+ ports[t2.name] = GapPort(self, t)
436
+ return ports
437
+
438
+ @property
439
+ def excitations_nets(self):
440
+ """Get all excitations net names."""
441
+ names = list(set([i.GetNet().GetName() for i in self.layout.terminals]))
442
+ names = [i for i in names if i]
443
+ return names
444
+
445
+ @property
446
+ def sources(self):
447
+ """Get all layout sources."""
448
+ terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [3, 4, 7]]
449
+ return {ter.GetName(): ExcitationSources(self, ter) for ter in terms}
450
+
451
+ @property
452
+ def probes(self):
453
+ """Get all layout probes."""
454
+ temp = {}
455
+ for name, val in self.terminals.items():
456
+ if val.boundary_type == "kVoltageProbe":
457
+ if not val.is_reference_terminal:
458
+ temp[name] = val
459
+ return temp
460
+
461
+ @pyedb_function_handler()
462
+ def open_edb(self):
463
+ """Open EDB.
464
+
465
+ Returns
466
+ -------
467
+ ``True`` when succeed ``False`` if failed : bool
468
+ """
469
+ # self.logger.info("EDB Path is %s", self.edbpath)
470
+ # self.logger.info("EDB Version is %s", self.edbversion)
471
+ # if self.edbversion > "2023.1":
472
+ # self.standalone = False
473
+
474
+ self.run_as_standalone(self.standalone)
475
+
476
+ # self.logger.info("EDB Standalone %s", self.standalone)
477
+ try:
478
+ self.open(self.edbpath, self.isreadonly)
479
+ except Exception as e:
480
+ self.logger.error("Builder is not Initialized.")
481
+ if not self.active_db:
482
+ self.logger.warning("Error Opening db")
483
+ self._active_cell = None
484
+ return None
485
+ self.logger.info("Database {} Opened in {}".format(os.path.split(self.edbpath)[-1], self.edbversion))
486
+
487
+ self._active_cell = None
488
+ if self.cellname:
489
+ for cell in list(self.top_circuit_cells):
490
+ if cell.GetName() == self.cellname:
491
+ self._active_cell = cell
492
+ # if self._active_cell is still None, set it to default cell
493
+ if self._active_cell is None:
494
+ self._active_cell = list(self.top_circuit_cells)[0]
495
+ self.logger.info("Cell %s Opened", self._active_cell.GetName())
496
+ if self._active_cell:
497
+ self._init_objects()
498
+ self.logger.info("Builder was initialized.")
499
+ else:
500
+ self.logger.error("Builder was not initialized.")
501
+
502
+ return True
503
+
504
+ @pyedb_function_handler()
505
+ def open_edb_inside_aedt(self):
506
+ """Open EDB inside AEDT.
507
+
508
+ Returns
509
+ -------
510
+ ``True`` when succeed ``False`` if failed : bool
511
+
512
+ """
513
+ self.logger.info("Opening EDB from HDL")
514
+ self.run_as_standalone(False)
515
+ if self.oproject.GetEDBHandle():
516
+ self.attach(self.oproject.GetEDBHandle())
517
+ if not self.active_db:
518
+ self.logger.warning("Error getting the database.")
519
+ self._active_cell = None
520
+ return None
521
+ self._active_cell = self.edb_api.cell.cell.FindByName(
522
+ self.active_db,
523
+ self.edb_api.cell._cell.CellType.CircuitCell,
524
+ self.cellname,
525
+ )
526
+ if self._active_cell is None:
527
+ self._active_cell = list(self.top_circuit_cells)[0]
528
+ if self._active_cell:
529
+ if not os.path.exists(self.edbpath):
530
+ os.makedirs(self.edbpath)
531
+ self._init_objects()
532
+ return True
533
+ else:
534
+ return None
535
+ else:
536
+ self._active_cell = None
537
+ return None
538
+
539
+ @pyedb_function_handler()
540
+ def create_edb(self):
541
+ """Create EDB.
542
+
543
+ Returns
544
+ -------
545
+ ``True`` when succeed ``False`` if failed : bool
546
+ """
547
+ # if self.edbversion > "2023.1":
548
+ # self.standalone = False
549
+
550
+ self.run_as_standalone(self.standalone)
551
+ self.create(self.edbpath)
552
+ if not self.active_db:
553
+ self.logger.warning("Error creating the database.")
554
+ self._active_cell = None
555
+ return None
556
+ if not self.cellname:
557
+ self.cellname = generate_unique_name("Cell")
558
+ self._active_cell = self.edb_api.cell.create(
559
+ self.active_db, self.edb_api.cell.CellType.CircuitCell, self.cellname
560
+ )
561
+ if self._active_cell:
562
+ self._init_objects()
563
+ return True
564
+ return None
565
+
566
+ @pyedb_function_handler()
567
+ def import_layout_pcb(
568
+ self,
569
+ input_file,
570
+ working_dir,
571
+ anstranslator_full_path="",
572
+ use_ppe=False,
573
+ control_file=None,
574
+ ):
575
+ """Import a board file and generate an ``edb.def`` file in the working directory.
576
+
577
+ This function supports all AEDT formats, including DXF, GDS, SML (IPC2581), BRD, MCM and TGZ.
578
+
579
+ Parameters
580
+ ----------
581
+ input_file : str
582
+ Full path to the board file.
583
+ working_dir : str
584
+ Directory in which to create the ``aedb`` folder. The name given to the AEDB file
585
+ is the same as the name of the board file.
586
+ anstranslator_full_path : str, optional
587
+ Full path to the Ansys translator. The default is ``""``.
588
+ use_ppe : bool
589
+ Whether to use the PPE License. The default is ``False``.
590
+ control_file : str, optional
591
+ Path to the XML file. The default is ``None``, in which case an attempt is made to find
592
+ the XML file in the same directory as the board file. To succeed, the XML file and board file
593
+ must have the same name. Only the extension differs.
594
+
595
+ Returns
596
+ -------
597
+ Full path to the AEDB file : str
598
+
599
+ """
600
+ self._components = None
601
+ self._core_primitives = None
602
+ self._stackup = None
603
+ self._padstack = None
604
+ self._siwave = None
605
+ self._hfss = None
606
+ self._nets = None
607
+ aedb_name = os.path.splitext(os.path.basename(input_file))[0] + ".aedb"
608
+ if anstranslator_full_path and os.path.exists(anstranslator_full_path):
609
+ command = anstranslator_full_path
610
+ else:
611
+ command = os.path.join(self.base_path, "anstranslator")
612
+ if is_windows:
613
+ command += ".exe"
614
+
615
+ if not working_dir:
616
+ working_dir = os.path.dirname(input_file)
617
+ cmd_translator = [
618
+ command,
619
+ input_file,
620
+ os.path.join(working_dir, aedb_name),
621
+ "-l={}".format(os.path.join(working_dir, "Translator.log")),
622
+ ]
623
+ if not use_ppe:
624
+ cmd_translator.append("-ppe=false")
625
+ if control_file and input_file[-3:] not in ["brd", "mcm", "sip"]:
626
+ if is_linux:
627
+ cmd_translator.append("-c={}".format(control_file))
628
+ else:
629
+ cmd_translator.append('-c="{}"'.format(control_file))
630
+ p = subprocess.Popen(cmd_translator)
631
+ p.wait()
632
+ if not os.path.exists(os.path.join(working_dir, aedb_name)):
633
+ self.logger.error("Translator failed to translate.")
634
+ return False
635
+ else:
636
+ self.logger.info("Translation correctly completed")
637
+ self.edbpath = os.path.join(working_dir, aedb_name)
638
+ return self.open_edb()
639
+
640
+ @pyedb_function_handler()
641
+ def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER"):
642
+ """Create an XML IPC2581 file from the active EDB.
643
+
644
+ .. note::
645
+ The method works only in CPython because of some limitations on Ironpython in XML parsing and
646
+ because it's time-consuming.
647
+ This method is still being tested and may need further debugging.
648
+ Any feedback is welcome. Back drills and custom pads are not supported yet.
649
+
650
+ Parameters
651
+ ----------
652
+ ipc_path : str, optional
653
+ Path to the XML IPC2581 file. The default is ``None``, in which case
654
+ an attempt is made to find the XML IPC2581 file in the same directory
655
+ as the active EDB. To succeed, the XML IPC2581 file and the active
656
+ EDT must have the same name. Only the extension differs.
657
+ units : str, optional
658
+ Units of the XML IPC2581 file. Options are ``"millimeter"``,
659
+ ``"inch"``, and ``"micron"``. The default is ``"millimeter"``.
660
+
661
+ Returns
662
+ -------
663
+ ``True`` if successful, ``False`` if failed : bool
664
+
665
+ """
666
+ if units.lower() not in ["millimeter", "inch", "micron"]: # pragma no cover
667
+ self.logger.warning("The wrong unit is entered. Setting to the default, millimeter.")
668
+ units = "millimeter"
669
+
670
+ if not ipc_path:
671
+ ipc_path = self.edbpath[:-4] + "xml"
672
+ self.logger.info("Export IPC 2581 is starting. This operation can take a while.")
673
+ start = time.time()
674
+ ipc = Ipc2581(self, units)
675
+ ipc.load_ipc_model()
676
+ ipc.file_path = ipc_path
677
+ result = ipc.write_xml()
678
+
679
+ if result: # pragma no cover
680
+ self.logger.info_timer("Export IPC 2581 completed.", start)
681
+ self.logger.info("File saved as %s", ipc_path)
682
+ return ipc_path
683
+ self.logger.info("Error exporting IPC 2581.")
684
+ return False
685
+
686
+ @property
687
+ def configuration(self):
688
+ """Edb project configuration from file."""
689
+ if not self._configuration:
690
+ self._configuration = Configuration(self)
691
+ return self._configuration
692
+
693
+ def edb_exception(self, ex_value, tb_data):
694
+ """Write the trace stack to AEDT when a Python error occurs.
695
+
696
+ Parameters
697
+ ----------
698
+ ex_value :
699
+
700
+ tb_data :
701
+
702
+
703
+ Returns
704
+ -------
705
+ None
706
+
707
+ """
708
+ tb_trace = traceback.format_tb(tb_data)
709
+ tblist = tb_trace[0].split("\n")
710
+ self.logger.error(str(ex_value))
711
+ for el in tblist:
712
+ self.logger.error(el)
713
+
714
+ @property
715
+ def active_db(self):
716
+ """Database object."""
717
+ return self.db
718
+
719
+ @property
720
+ def active_cell(self):
721
+ """Active cell."""
722
+ return self._active_cell
723
+
724
+ @property
725
+ def core_components(self): # pragma: no cover
726
+ """Edb Components methods and properties.
727
+
728
+ .. deprecated:: 0.6.62
729
+ Use new property :func:`components` instead.
730
+
731
+ Returns
732
+ -------
733
+ Instance of :class:`pyedb.dotnet.edb_core.Components.Components`
734
+
735
+ Examples
736
+ --------
737
+ >>> from pyedb.dotnet.edb import Edb
738
+ >>> edbapp = Edb("myproject.aedb")
739
+ >>> comp = edbapp.components.get_component_by_name("J1")
740
+ """
741
+ warnings.warn("Use new property :func:`components` instead.", DeprecationWarning)
742
+ return self.components
743
+
744
+ @property
745
+ def components(self):
746
+ """Edb Components methods and properties.
747
+
748
+ Returns
749
+ -------
750
+ Instance of :class:`pyedb.dotnet.edb_core.components.Components`
751
+
752
+ Examples
753
+ --------
754
+ >>> from pyedb.dotnet.edb import Edb
755
+ >>> edbapp = Edb("myproject.aedb")
756
+ >>> comp = edbapp.components.get_component_by_name("J1")
757
+ """
758
+ if not self._components and self.active_db:
759
+ self._components = Components(self)
760
+ return self._components
761
+
762
+ @property
763
+ def core_stackup(self):
764
+ """Core stackup.
765
+
766
+ .. deprecated:: 0.6.5
767
+ There is no need to use the ``core_stackup`` property anymore.
768
+ You can instantiate a new ``stackup`` class directly from the ``Edb`` class.
769
+ """
770
+ mess = "`core_stackup` is deprecated.\n"
771
+ mess += " Use `app.stackup` directly to instantiate new stackup methods."
772
+ warnings.warn(mess, DeprecationWarning)
773
+ if not self._stackup and self.active_db:
774
+ self._stackup = Stackup(self)
775
+ return self._stackup
776
+
777
+ @property
778
+ def design_options(self):
779
+ """Edb Design Settings and Options.
780
+
781
+ Returns
782
+ -------
783
+ Instance of :class:`pyedb.dotnet.edb_core.edb_data.design_options.EdbDesignOptions`
784
+ """
785
+ return EdbDesignOptions(self.active_cell)
786
+
787
+ @property
788
+ def stackup(self):
789
+ """Stackup manager.
790
+
791
+ Returns
792
+ -------
793
+ Instance of :class: 'pyedb.dotnet.edb_core.Stackup`
794
+
795
+ Examples
796
+ --------
797
+ >>> from pyedb.dotnet.edb import Edb
798
+ >>> edbapp = Edb("myproject.aedb")
799
+ >>> edbapp.stackup.layers["TOP"].thickness = 4e-5
800
+ >>> edbapp.stackup.layers["TOP"].thickness == 4e-05
801
+ >>> edbapp.stackup.add_layer("Diel", "GND", layer_type="dielectric", thickness="0.1mm", material="FR4_epoxy")
802
+ """
803
+ if not self._stackup2 and self.active_db:
804
+ self._stackup2 = Stackup(self)
805
+ return self._stackup2
806
+
807
+ @property
808
+ def materials(self):
809
+ """Material Database.
810
+
811
+ Returns
812
+ -------
813
+ Instance of :class: `pyedb.dotnet.edb_core.Materials`
814
+
815
+ Examples
816
+ --------
817
+ >>> from pyedb.dotnet.edb import Edb
818
+ >>> edbapp = Edb("myproject.aedb")
819
+ >>> edbapp.materials["FR4_epoxy"].conductivity = 1
820
+ >>> edbapp.materials.add_debye_material("My_Debye2", 5, 3, 0.02, 0.05, 1e5, 1e9)
821
+ >>> edbapp.materials.add_djordjevicsarkar_material("MyDjord2", 3.3, 0.02, 3.3)
822
+ """
823
+
824
+ if not self._materials and self.active_db:
825
+ self._materials = Materials(self)
826
+ return self._materials
827
+
828
+ @property
829
+ def core_padstack(self): # pragma: no cover
830
+ """Core padstack.
831
+
832
+
833
+ .. deprecated:: 0.6.62
834
+ Use new property :func:`padstacks` instead.
835
+
836
+ Returns
837
+ -------
838
+ Instance of :class: `pyedb.dotnet.edb_core.padstack.EdbPadstack`
839
+
840
+ Examples
841
+ --------
842
+ >>> from pyedb.dotnet.edb import Edb
843
+ >>> edbapp = Edb("myproject.aedb")
844
+ >>> p = edbapp.padstacks.create(padstackname="myVia_bullet", antipad_shape="Bullet")
845
+ >>> edbapp.padstacks.get_pad_parameters(
846
+ >>> ... p, "TOP", edbapp.padstacks.pad_type.RegularPad
847
+ >>> ... )
848
+ """
849
+
850
+ warnings.warn("Use new property :func:`padstacks` instead.", DeprecationWarning)
851
+ return self.padstacks
852
+
853
+ @property
854
+ def padstacks(self):
855
+ """Core padstack.
856
+
857
+
858
+ Returns
859
+ -------
860
+ Instance of :class: `legacy.edb_core.padstack.EdbPadstack`
861
+
862
+ Examples
863
+ --------
864
+ >>> from pyedb.dotnet.edb import Edb
865
+ >>> edbapp = Edb("myproject.aedb")
866
+ >>> p = edbapp.padstacks.create(padstackname="myVia_bullet", antipad_shape="Bullet")
867
+ >>> edbapp.padstacks.get_pad_parameters(
868
+ >>> ... p, "TOP", edbapp.padstacks.pad_type.RegularPad
869
+ >>> ... )
870
+ """
871
+
872
+ if not self._padstack and self.active_db:
873
+ self._padstack = EdbPadstacks(self)
874
+ return self._padstack
875
+
876
+ @property
877
+ def core_siwave(self): # pragma: no cover
878
+ """Core SIWave methods and properties.
879
+
880
+ .. deprecated:: 0.6.62
881
+ Use new property :func:`siwave` instead.
882
+
883
+ Returns
884
+ -------
885
+ Instance of :class: `pyedb.dotnet.edb_core.siwave.EdbSiwave`
886
+
887
+ Examples
888
+ --------
889
+ >>> from pyedb.dotnet.edb import Edb
890
+ >>> edbapp = Edb("myproject.aedb")
891
+ >>> p2 = edbapp.siwave.create_circuit_port_on_net("U2A5", "V3P3_S0", "U2A5", "GND", 50, "test")
892
+ """
893
+ warnings.warn("Use new property :func:`siwave` instead.", DeprecationWarning)
894
+ return self.siwave
895
+
896
+ @property
897
+ def siwave(self):
898
+ """Core SIWave methods and properties.
899
+
900
+ Returns
901
+ -------
902
+ Instance of :class: `pyedb.dotnet.edb_core.siwave.EdbSiwave`
903
+
904
+ Examples
905
+ --------
906
+ >>> from pyedb.dotnet.edb import Edb
907
+ >>> edbapp = Edb("myproject.aedb")
908
+ >>> p2 = edbapp.siwave.create_circuit_port_on_net("U2A5", "V3P3_S0", "U2A5", "GND", 50, "test")
909
+ """
910
+ if not self._siwave and self.active_db:
911
+ self._siwave = EdbSiwave(self)
912
+ return self._siwave
913
+
914
+ @property
915
+ def core_hfss(self): # pragma: no cover
916
+ """Core HFSS methods and properties.
917
+
918
+ .. deprecated:: 0.6.62
919
+ Use new property :func:`hfss` instead.
920
+
921
+ Returns
922
+ -------
923
+ Instance of :class:`legacy.edb_core.hfss.EdbHfss`
924
+
925
+ Examples
926
+ --------
927
+ >>> from pyedb.dotnet.edb import Edb
928
+ >>> edbapp = Edb("myproject.aedb")
929
+ >>> edbapp.hfss.configure_hfss_analysis_setup(sim_config)
930
+ """
931
+ warnings.warn("Use new property :func:`hfss` instead.", DeprecationWarning)
932
+ return self.hfss
933
+
934
+ @property
935
+ def hfss(self):
936
+ """Core HFSS methods and properties.
937
+
938
+ Returns
939
+ -------
940
+ :class:`pyedb.dotnet.edb_core.hfss.EdbHfss`
941
+
942
+ See Also
943
+ --------
944
+ :class:`legacy.edb_core.edb_data.simulation_configuration.SimulationConfiguration`
945
+
946
+ Examples
947
+ --------
948
+ >>> from pyedb.dotnet.edb import Edb
949
+ >>> edbapp = Edb("myproject.aedb")
950
+ >>> sim_config = edbapp.new_simulation_configuration()
951
+ >>> sim_config.mesh_freq = "10Ghz"
952
+ >>> edbapp.hfss.configure_hfss_analysis_setup(sim_config)
953
+ """
954
+ if not self._hfss and self.active_db:
955
+ self._hfss = EdbHfss(self)
956
+ return self._hfss
957
+
958
+ @property
959
+ def core_nets(self): # pragma: no cover
960
+ """Core nets.
961
+
962
+ .. deprecated:: 0.6.62
963
+ Use new property :func:`nets` instead.
964
+
965
+ Returns
966
+ -------
967
+ :class:`pyedb.dotnet.edb_core.nets.EdbNets`
968
+
969
+ Examples
970
+ --------
971
+ >>> from pyedb.dotnet.edb import Edb
972
+ >>> edbapp = Edb("myproject.aedb")
973
+ >>> edbapp.nets.find_or_create_net("GND")
974
+ >>> edbapp.nets.find_and_fix_disjoint_nets("GND", keep_only_main_net=True)
975
+ """
976
+ warnings.warn("Use new property :func:`nets` instead.", DeprecationWarning)
977
+ return self.nets
978
+
979
+ @property
980
+ def nets(self):
981
+ """Core nets.
982
+
983
+ Returns
984
+ -------
985
+ :class:`legacy.edb_core.nets.EdbNets`
986
+
987
+ Examples
988
+ --------
989
+ >>> from pyedb.dotnet.edb import Edb
990
+ >>> edbapp = Edb"myproject.aedb")
991
+ >>> edbapp.nets.find_or_create_net("GND")
992
+ >>> edbapp.nets.find_and_fix_disjoint_nets("GND", keep_only_main_net=True)
993
+ """
994
+
995
+ if not self._nets and self.active_db:
996
+ raise Exception("")
997
+ self._nets = EdbNets(self)
998
+ return self._nets
999
+
1000
+ @property
1001
+ def net_classes(self):
1002
+ """Get all net classes.
1003
+
1004
+ Returns
1005
+ -------
1006
+ :class:`legacy.edb_core.nets.EdbNetClasses`
1007
+
1008
+ Examples
1009
+ --------
1010
+ >>> from pyedb.dotnet.edb import Edb
1011
+ >>> edbapp = Edb("myproject.aedb")
1012
+ >>> edbapp.net_classes
1013
+ """
1014
+
1015
+ if self.active_db:
1016
+ return EdbNetClasses(self)
1017
+
1018
+ @property
1019
+ def extended_nets(self):
1020
+ """Get all extended nets.
1021
+
1022
+ Returns
1023
+ -------
1024
+ :class:`legacy.edb_core.nets.EdbExtendedNets`
1025
+
1026
+ Examples
1027
+ --------
1028
+ >>> from pyedb.dotnet.edb import Edb
1029
+ >>> edbapp = Edb("myproject.aedb")
1030
+ >>> edbapp.extended_nets
1031
+ """
1032
+
1033
+ if self.active_db:
1034
+ return EdbExtendedNets(self)
1035
+
1036
+ @property
1037
+ def differential_pairs(self):
1038
+ """Get all differential pairs.
1039
+
1040
+ Returns
1041
+ -------
1042
+ :class:`legacy.edb_core.nets.EdbDifferentialPairs`
1043
+
1044
+ Examples
1045
+ --------
1046
+ >>> from pyedb.dotnet.edb import Edb
1047
+ >>> edbapp = Edb("myproject.aedb")
1048
+ >>> edbapp.differential_pairs
1049
+ """
1050
+ if self.active_db:
1051
+ return EdbDifferentialPairs(self)
1052
+ else: # pragma: no cover
1053
+ return
1054
+
1055
+ @property
1056
+ def core_primitives(self): # pragma: no cover
1057
+ """Core primitives.
1058
+
1059
+ .. deprecated:: 0.6.62
1060
+ Use new property :func:`modeler` instead.
1061
+
1062
+ Returns
1063
+ -------
1064
+ Instance of :class: `legacy.edb_core.layout.EdbLayout`
1065
+
1066
+ Examples
1067
+ --------
1068
+ >>> from pyedb.dotnet.edb import Edb
1069
+ >>> edbapp = Edb("myproject.aedb")
1070
+ >>> top_prims = edbapp.modeler.primitives_by_layer["TOP"]
1071
+ """
1072
+ warnings.warn("Use new property :func:`modeler` instead.", DeprecationWarning)
1073
+ return self.modeler
1074
+
1075
+ @property
1076
+ def modeler(self):
1077
+ """Core primitives modeler.
1078
+
1079
+ Returns
1080
+ -------
1081
+ Instance of :class: `legacy.edb_core.layout.EdbLayout`
1082
+
1083
+ Examples
1084
+ --------
1085
+ >>> from pyedb.dotnet.edb import Edb
1086
+ >>> edbapp = Edb("myproject.aedb")
1087
+ >>> top_prims = edbapp.modeler.primitives_by_layer["TOP"]
1088
+ """
1089
+ if not self._core_primitives and self.active_db:
1090
+ self._core_primitives = EdbLayout(self)
1091
+ return self._core_primitives
1092
+
1093
+ @property
1094
+ def layout(self):
1095
+ """Layout object.
1096
+
1097
+ Returns
1098
+ -------
1099
+ :class:`legacy.edb_core.dotnet.layout.Layout`
1100
+ """
1101
+ return LayoutDotNet(self)
1102
+
1103
+ @property
1104
+ def active_layout(self):
1105
+ """Active layout.
1106
+
1107
+ Returns
1108
+ -------
1109
+ Instance of EDB API Layout Class.
1110
+ """
1111
+ return self.layout._layout
1112
+
1113
+ @property
1114
+ def layout_instance(self):
1115
+ """Edb Layout Instance."""
1116
+ return self.layout.layout_instance
1117
+
1118
+ @pyedb_function_handler()
1119
+ def get_connected_objects(self, layout_object_instance):
1120
+ """Get connected objects.
1121
+
1122
+ Returns
1123
+ -------
1124
+ list
1125
+ """
1126
+ temp = []
1127
+ for i in list(
1128
+ [
1129
+ loi.GetLayoutObj()
1130
+ for loi in self.layout_instance.GetConnectedObjects(layout_object_instance._edb_object).Items
1131
+ ]
1132
+ ):
1133
+ obj_type = i.GetObjType().ToString()
1134
+ if obj_type == LayoutObjType.PadstackInstance.name:
1135
+ from pyedb.dotnet.edb_core.edb_data.padstacks_data import (
1136
+ EDBPadstackInstance,
1137
+ )
1138
+
1139
+ temp.append(EDBPadstackInstance(i, self))
1140
+ elif obj_type == LayoutObjType.Primitive.name:
1141
+ prim_type = i.GetPrimitiveType().ToString()
1142
+ if prim_type == Primitives.Path.name:
1143
+ from pyedb.dotnet.edb_core.edb_data.primitives_data import EdbPath
1144
+
1145
+ temp.append(EdbPath(i, self))
1146
+ elif prim_type == Primitives.Rectangle.name:
1147
+ from pyedb.dotnet.edb_core.edb_data.primitives_data import (
1148
+ EdbRectangle,
1149
+ )
1150
+
1151
+ temp.append(EdbRectangle(i, self))
1152
+ elif prim_type == Primitives.Circle.name:
1153
+ from pyedb.dotnet.edb_core.edb_data.primitives_data import EdbCircle
1154
+
1155
+ temp.append(EdbCircle(i, self))
1156
+ elif prim_type == Primitives.Polygon.name:
1157
+ from pyedb.dotnet.edb_core.edb_data.primitives_data import (
1158
+ EdbPolygon,
1159
+ )
1160
+
1161
+ temp.append(EdbPolygon(i, self))
1162
+ else:
1163
+ continue
1164
+ else:
1165
+ continue
1166
+ return temp
1167
+
1168
+ @property
1169
+ def pins(self):
1170
+ """EDB padstack instance of the component.
1171
+
1172
+ .. deprecated:: 0.6.62
1173
+ Use new method :func:`edb.padstacks.pins` instead.
1174
+
1175
+ Returns
1176
+ -------
1177
+ dic[str, :class:`legacy.edb_core.edb_data.definitions.EDBPadstackInstance`]
1178
+ Dictionary of EDBPadstackInstance Components.
1179
+
1180
+
1181
+ Examples
1182
+ --------
1183
+ >>> from pyedb.dotnet.edb import Edb
1184
+ >>> edbapp = Edb("myproject.aedb")
1185
+ >>> pin_net_name = edbapp.pins[424968329].netname
1186
+ """
1187
+ warnings.warn("Use new method :func:`edb.padstacks.pins` instead.", DeprecationWarning)
1188
+ return self.padstacks.pins
1189
+
1190
+ class Boundaries:
1191
+ """Boundaries Enumerator.
1192
+
1193
+ Returns
1194
+ -------
1195
+ int
1196
+ """
1197
+
1198
+ (
1199
+ Port,
1200
+ Pec,
1201
+ RLC,
1202
+ CurrentSource,
1203
+ VoltageSource,
1204
+ NexximGround,
1205
+ NexximPort,
1206
+ DcTerminal,
1207
+ VoltageProbe,
1208
+ ) = range(0, 9)
1209
+
1210
+ @pyedb_function_handler()
1211
+ def edb_value(self, val):
1212
+ """Convert a value to an EDB value. Value can be a string, float or integer. Mainly used in internal calls.
1213
+
1214
+ Parameters
1215
+ ----------
1216
+ val : str, float, int
1217
+
1218
+
1219
+ Returns
1220
+ -------
1221
+ Instance of `Edb.Utility.Value`
1222
+
1223
+ """
1224
+ return self.edb_api.utility.value(val)
1225
+
1226
+ @pyedb_function_handler()
1227
+ def point_3d(self, x, y, z=0.0):
1228
+ """Compute the Edb 3d Point Data.
1229
+
1230
+ Parameters
1231
+ ----------
1232
+ x : float, int or str
1233
+ X value.
1234
+ y : float, int or str
1235
+ Y value.
1236
+ z : float, int or str, optional
1237
+ Z value.
1238
+
1239
+ Returns
1240
+ -------
1241
+ ``Geometry.Point3DData``.
1242
+ """
1243
+ return self.edb_api.geometry.point3d_data(x, y, z)
1244
+
1245
+ @pyedb_function_handler()
1246
+ def point_data(self, x, y=None):
1247
+ """Compute the Edb Point Data.
1248
+
1249
+ Parameters
1250
+ ----------
1251
+ x : float, int or str
1252
+ X value.
1253
+ y : float, int or str, optional
1254
+ Y value.
1255
+
1256
+
1257
+ Returns
1258
+ -------
1259
+ ``Geometry.PointData``.
1260
+ """
1261
+ if y is None:
1262
+ return self.edb_api.geometry.point_data(x)
1263
+ else:
1264
+ return self.edb_api.geometry.point_data(x, y)
1265
+
1266
+ @pyedb_function_handler()
1267
+ def _is_file_existing_and_released(self, filename):
1268
+ if os.path.exists(filename):
1269
+ try:
1270
+ os.rename(filename, filename + "_")
1271
+ os.rename(filename + "_", filename)
1272
+ return True
1273
+ except OSError as e:
1274
+ return False
1275
+ else:
1276
+ return False
1277
+
1278
+ @pyedb_function_handler()
1279
+ def _is_file_existing(self, filename):
1280
+ if os.path.exists(filename):
1281
+ return True
1282
+ else:
1283
+ return False
1284
+
1285
+ @pyedb_function_handler()
1286
+ def _wait_for_file_release(self, timeout=30, file_to_release=None):
1287
+ if not file_to_release:
1288
+ file_to_release = os.path.join(self.edbpath)
1289
+ tstart = time.time()
1290
+ while True:
1291
+ if self._is_file_existing_and_released(file_to_release):
1292
+ return True
1293
+ elif time.time() - tstart > timeout:
1294
+ return False
1295
+ else:
1296
+ time.sleep(0.250)
1297
+
1298
+ @pyedb_function_handler()
1299
+ def _wait_for_file_exists(self, timeout=30, file_to_release=None, wait_count=4):
1300
+ if not file_to_release:
1301
+ file_to_release = os.path.join(self.edbpath)
1302
+ tstart = time.time()
1303
+ times = 0
1304
+ while True:
1305
+ if self._is_file_existing(file_to_release):
1306
+ # print 'File is released'
1307
+ times += 1
1308
+ if times == wait_count:
1309
+ return True
1310
+ elif time.time() - tstart > timeout:
1311
+ # print 'Timeout reached'
1312
+ return False
1313
+ else:
1314
+ times = 0
1315
+ time.sleep(0.250)
1316
+
1317
+ @pyedb_function_handler()
1318
+ def close_edb(self):
1319
+ """Close EDB and cleanup variables.
1320
+
1321
+ Returns
1322
+ -------
1323
+ bool
1324
+ ``True`` when successful, ``False`` when failed.
1325
+
1326
+ """
1327
+ self.close()
1328
+
1329
+ if self.log_name and settings.enable_local_log_file:
1330
+ self._logger.remove_all_file_loggers()
1331
+ start_time = time.time()
1332
+ self._wait_for_file_release()
1333
+ elapsed_time = time.time() - start_time
1334
+ self.logger.info("EDB file release time: {0:.2f}ms".format(elapsed_time * 1000.0))
1335
+ self._clean_variables()
1336
+ return True
1337
+
1338
+ @pyedb_function_handler()
1339
+ def save_edb(self):
1340
+ """Save the EDB file.
1341
+
1342
+ Returns
1343
+ -------
1344
+ bool
1345
+ ``True`` when successful, ``False`` when failed.
1346
+
1347
+ """
1348
+ self.save()
1349
+ start_time = time.time()
1350
+ self._wait_for_file_release()
1351
+ elapsed_time = time.time() - start_time
1352
+ self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0))
1353
+ return True
1354
+
1355
+ @pyedb_function_handler()
1356
+ def save_edb_as(self, fname):
1357
+ """Save the EDB file as another file.
1358
+
1359
+ Parameters
1360
+ ----------
1361
+ fname : str
1362
+ Name of the new file to save to.
1363
+
1364
+ Returns
1365
+ -------
1366
+ bool
1367
+ ``True`` when successful, ``False`` when failed.
1368
+
1369
+ """
1370
+ origin_name = "pyedb_" + os.path.splitext(os.path.split(self.edbpath)[-1])[0]
1371
+ self.save_as(fname)
1372
+ start_time = time.time()
1373
+ self._wait_for_file_release()
1374
+ elapsed_time = time.time() - start_time
1375
+ self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0))
1376
+ self.edbpath = self.directory
1377
+ if self.log_name:
1378
+ self._logger.remove_file_logger(os.path.splitext(os.path.split(self.log_name)[-1])[0])
1379
+
1380
+ self.log_name = os.path.join(
1381
+ os.path.dirname(fname),
1382
+ "pyedb_" + os.path.splitext(os.path.split(fname)[-1])[0] + ".log",
1383
+ )
1384
+ if settings.enable_local_log_file:
1385
+ self._logger.add_file_logger(self.log_name, "Edb")
1386
+ self._logger.remove_file_logger(origin_name)
1387
+ return True
1388
+
1389
+ @pyedb_function_handler()
1390
+ def execute(self, func):
1391
+ """Execute a function.
1392
+
1393
+ Parameters
1394
+ ----------
1395
+ func : str
1396
+ Function to execute.
1397
+
1398
+
1399
+ Returns
1400
+ -------
1401
+ bool
1402
+ ``True`` when successful, ``False`` when failed.
1403
+
1404
+ """
1405
+ return self.edb_api.utility.utility.Command.Execute(func)
1406
+
1407
+ @pyedb_function_handler()
1408
+ def import_cadence_file(self, inputBrd, WorkDir=None, anstranslator_full_path="", use_ppe=False):
1409
+ """Import a board file and generate an ``edb.def`` file in the working directory.
1410
+
1411
+ Parameters
1412
+ ----------
1413
+ inputBrd : str
1414
+ Full path to the board file.
1415
+ WorkDir : str, optional
1416
+ Directory in which to create the ``aedb`` folder. The default value is ``None``,
1417
+ in which case the AEDB file is given the same name as the board file. Only
1418
+ the extension differs.
1419
+ anstranslator_full_path : str, optional
1420
+ Full path to the Ansys translator.
1421
+ use_ppe : bool, optional
1422
+ Whether to use the PPE License. The default is ``False``.
1423
+
1424
+ Returns
1425
+ -------
1426
+ bool
1427
+ ``True`` when successful, ``False`` when failed.
1428
+
1429
+ """
1430
+ if self.import_layout_pcb(
1431
+ inputBrd,
1432
+ working_dir=WorkDir,
1433
+ anstranslator_full_path=anstranslator_full_path,
1434
+ use_ppe=use_ppe,
1435
+ ):
1436
+ return True
1437
+ else:
1438
+ return False
1439
+
1440
+ @pyedb_function_handler()
1441
+ def import_gds_file(
1442
+ self,
1443
+ inputGDS,
1444
+ WorkDir=None,
1445
+ anstranslator_full_path="",
1446
+ use_ppe=False,
1447
+ control_file=None,
1448
+ tech_file=None,
1449
+ map_file=None,
1450
+ ):
1451
+ """Import a GDS file and generate an ``edb.def`` file in the working directory.
1452
+
1453
+ ..note::
1454
+ `ANSYSLMD_LICENSE_FILE` is needed to run the translator.
1455
+
1456
+ Parameters
1457
+ ----------
1458
+ inputGDS : str
1459
+ Full path to the GDS file.
1460
+ WorkDir : str, optional
1461
+ Directory in which to create the ``aedb`` folder. The default value is ``None``,
1462
+ in which case the AEDB file is given the same name as the GDS file. Only the extension
1463
+ differs.
1464
+ anstranslator_full_path : str, optional
1465
+ Full path to the Ansys translator.
1466
+ use_ppe : bool, optional
1467
+ Whether to use the PPE License. The default is ``False``.
1468
+ control_file : str, optional
1469
+ Path to the XML file. The default is ``None``, in which case an attempt is made to find
1470
+ the XML file in the same directory as the GDS file. To succeed, the XML file and GDS file must
1471
+ have the same name. Only the extension differs.
1472
+ tech_file : str, optional
1473
+ Technology file. It uses Helic to convert tech file to xml and then imports the gds. Works on Linux only.
1474
+ map_file : str, optional
1475
+ Layer map file.
1476
+
1477
+ Returns
1478
+ -------
1479
+ bool
1480
+ ``True`` when successful, ``False`` when failed.
1481
+
1482
+ """
1483
+ if tech_file or map_file:
1484
+ control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml")
1485
+ control_file = ControlFile(xml_input=control_file, tecnhology=tech_file, layer_map=map_file).write_xml(
1486
+ control_file_temp
1487
+ )
1488
+ elif tech_file:
1489
+ self.logger.error("Technology files are supported only in Linux. Use control file instead.")
1490
+ return False
1491
+ if self.import_layout_pcb(
1492
+ inputGDS,
1493
+ working_dir=WorkDir,
1494
+ anstranslator_full_path=anstranslator_full_path,
1495
+ use_ppe=use_ppe,
1496
+ control_file=control_file,
1497
+ ):
1498
+ return True
1499
+ else:
1500
+ return False
1501
+
1502
+ @pyedb_function_handler()
1503
+ def _create_extent(
1504
+ self,
1505
+ net_signals,
1506
+ extent_type,
1507
+ expansion_size,
1508
+ use_round_corner,
1509
+ use_pyaedt_extent=False,
1510
+ smart_cut=False,
1511
+ reference_list=[],
1512
+ include_pingroups=True,
1513
+ pins_to_preserve=None,
1514
+ ):
1515
+ if extent_type in [
1516
+ "Conforming",
1517
+ self.edb_api.geometry.extent_type.Conforming,
1518
+ 1,
1519
+ ]:
1520
+ if use_pyaedt_extent:
1521
+ _poly = self._create_conformal(
1522
+ net_signals,
1523
+ expansion_size,
1524
+ 1e-12,
1525
+ use_round_corner,
1526
+ expansion_size,
1527
+ smart_cut,
1528
+ reference_list,
1529
+ pins_to_preserve,
1530
+ )
1531
+ else:
1532
+ _poly = self.layout.expanded_extent(
1533
+ net_signals,
1534
+ self.edb_api.geometry.extent_type.Conforming,
1535
+ expansion_size,
1536
+ False,
1537
+ use_round_corner,
1538
+ 1,
1539
+ )
1540
+ elif extent_type in [
1541
+ "Bounding",
1542
+ self.edb_api.geometry.extent_type.BoundingBox,
1543
+ 0,
1544
+ ]:
1545
+ _poly = self.layout.expanded_extent(
1546
+ net_signals,
1547
+ self.edb_api.geometry.extent_type.BoundingBox,
1548
+ expansion_size,
1549
+ False,
1550
+ use_round_corner,
1551
+ 1,
1552
+ )
1553
+ else:
1554
+ if use_pyaedt_extent:
1555
+ _poly = self._create_convex_hull(
1556
+ net_signals,
1557
+ expansion_size,
1558
+ 1e-12,
1559
+ use_round_corner,
1560
+ expansion_size,
1561
+ smart_cut,
1562
+ reference_list,
1563
+ pins_to_preserve,
1564
+ )
1565
+ else:
1566
+ _poly = self.layout.expanded_extent(
1567
+ net_signals,
1568
+ self.edb_api.geometry.extent_type.Conforming,
1569
+ expansion_size,
1570
+ False,
1571
+ use_round_corner,
1572
+ 1,
1573
+ )
1574
+ _poly_list = convert_py_list_to_net_list([_poly])
1575
+ _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(_poly_list)
1576
+ return _poly
1577
+
1578
+ @pyedb_function_handler()
1579
+ def _create_conformal(
1580
+ self,
1581
+ net_signals,
1582
+ expansion_size,
1583
+ tolerance,
1584
+ round_corner,
1585
+ round_extension,
1586
+ smart_cutout=False,
1587
+ reference_list=[],
1588
+ pins_to_preserve=None,
1589
+ ):
1590
+ names = []
1591
+ _polys = []
1592
+ for net in net_signals:
1593
+ names.append(net.GetName())
1594
+ if pins_to_preserve:
1595
+ insts = self.padstacks.instances
1596
+ for i in pins_to_preserve:
1597
+ p = insts[i].position
1598
+ pos_1 = [i - expansion_size for i in p]
1599
+ pos_2 = [i + expansion_size for i in p]
1600
+ plane = self.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2)
1601
+ rectangle_data = self.modeler.shape_to_polygon_data(plane)
1602
+ _polys.append(rectangle_data)
1603
+
1604
+ for prim in self.modeler.primitives:
1605
+ if prim is not None and prim.net_name in names:
1606
+ _polys.append(prim.primitive_object.GetPolygonData())
1607
+ if smart_cutout:
1608
+ objs_data = self._smart_cut(reference_list, expansion_size)
1609
+ _polys.extend(objs_data)
1610
+ k = 0
1611
+ delta = expansion_size / 5
1612
+ while k < 10:
1613
+ unite_polys = []
1614
+ for i in _polys:
1615
+ obj_data = i.Expand(expansion_size, tolerance, round_corner, round_extension)
1616
+ if obj_data:
1617
+ unite_polys.extend(list(obj_data))
1618
+ _poly_unite = self.edb_api.geometry.polygon_data.unite(unite_polys)
1619
+ if len(_poly_unite) == 1:
1620
+ self.logger.info("Correctly computed Extension at first iteration.")
1621
+ return _poly_unite[0]
1622
+ k += 1
1623
+ expansion_size += delta
1624
+ if len(_poly_unite) == 1:
1625
+ self.logger.info("Correctly computed Extension in {} iterations.".format(k))
1626
+ return _poly_unite[0]
1627
+ else:
1628
+ self.logger.info("Failed to Correctly computed Extension.")
1629
+ areas = [i.Area() for i in _poly_unite]
1630
+ return _poly_unite[areas.index(max(areas))]
1631
+
1632
+ @pyedb_function_handler()
1633
+ def _smart_cut(self, reference_list=[], expansion_size=1e-12):
1634
+ from pyedb.dotnet.clr_module import Tuple
1635
+
1636
+ _polys = []
1637
+ terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [0, 3, 4, 7, 8]]
1638
+ locations = []
1639
+ for term in terms:
1640
+ if term.GetTerminalType().ToString() == "PointTerminal" and term.GetNet().GetName() in reference_list:
1641
+ pd = term.GetParameters()[1]
1642
+ locations.append([pd.X.ToDouble(), pd.Y.ToDouble()])
1643
+ for point in locations:
1644
+ pointA = self.edb_api.geometry.point_data(
1645
+ self.edb_value(point[0] - expansion_size),
1646
+ self.edb_value(point[1] - expansion_size),
1647
+ )
1648
+ pointB = self.edb_api.geometry.point_data(
1649
+ self.edb_value(point[0] + expansion_size),
1650
+ self.edb_value(point[1] + expansion_size),
1651
+ )
1652
+
1653
+ points = Tuple[
1654
+ self.edb_api.geometry.geometry.PointData,
1655
+ self.edb_api.geometry.geometry.PointData,
1656
+ ](pointA, pointB)
1657
+ _polys.append(self.edb_api.geometry.polygon_data.create_from_bbox(points))
1658
+ return _polys
1659
+
1660
+ @pyedb_function_handler()
1661
+ def _create_convex_hull(
1662
+ self,
1663
+ net_signals,
1664
+ expansion_size,
1665
+ tolerance,
1666
+ round_corner,
1667
+ round_extension,
1668
+ smart_cut=False,
1669
+ reference_list=[],
1670
+ pins_to_preserve=None,
1671
+ ):
1672
+ names = []
1673
+ _polys = []
1674
+ for net in net_signals:
1675
+ names.append(net.GetName())
1676
+ if pins_to_preserve:
1677
+ insts = self.padstacks.instances
1678
+ for i in pins_to_preserve:
1679
+ p = insts[i].position
1680
+ pos_1 = [i - 1e-12 for i in p]
1681
+ pos_2 = [i + 1e-12 for i in p]
1682
+ plane = self.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2)
1683
+ rectangle_data = self.modeler.shape_to_polygon_data(plane)
1684
+ _polys.append(rectangle_data)
1685
+ for prim in self.modeler.primitives:
1686
+ if prim is not None and prim.net_name in names:
1687
+ _polys.append(prim.primitive_object.GetPolygonData())
1688
+ if smart_cut:
1689
+ objs_data = self._smart_cut(reference_list, expansion_size)
1690
+ _polys.extend(objs_data)
1691
+ _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(convert_py_list_to_net_list(_polys))
1692
+ _poly = _poly.Expand(expansion_size, tolerance, round_corner, round_extension)[0]
1693
+ return _poly
1694
+
1695
+ @pyedb_function_handler()
1696
+ def cutout(
1697
+ self,
1698
+ signal_list=None,
1699
+ reference_list=None,
1700
+ extent_type="ConvexHull",
1701
+ expansion_size=0.002,
1702
+ use_round_corner=False,
1703
+ output_aedb_path=None,
1704
+ open_cutout_at_end=True,
1705
+ use_pyaedt_cutout=True,
1706
+ number_of_threads=4,
1707
+ use_pyaedt_extent_computing=True,
1708
+ extent_defeature=0,
1709
+ remove_single_pin_components=False,
1710
+ custom_extent=None,
1711
+ custom_extent_units="mm",
1712
+ include_partial_instances=False,
1713
+ keep_voids=True,
1714
+ check_terminals=False,
1715
+ include_pingroups=False,
1716
+ expansion_factor=0,
1717
+ maximum_iterations=10,
1718
+ preserve_components_with_model=False,
1719
+ simple_pad_check=True,
1720
+ keep_lines_as_path=False,
1721
+ ):
1722
+ """Create a cutout using an approach entirely based on PyAEDT.
1723
+ This method replaces all legacy cutout methods in PyAEDT.
1724
+ It does in sequence:
1725
+ - delete all nets not in list,
1726
+ - create a extent of the nets,
1727
+ - check and delete all vias not in the extent,
1728
+ - check and delete all the primitives not in extent,
1729
+ - check and intersect all the primitives that intersect the extent.
1730
+
1731
+ Parameters
1732
+ ----------
1733
+ signal_list : list
1734
+ List of signal strings.
1735
+ reference_list : list, optional
1736
+ List of references to add. The default is ``["GND"]``.
1737
+ extent_type : str, optional
1738
+ Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and
1739
+ ``"Bounding"``. The default is ``"Conforming"``.
1740
+ expansion_size : float, str, optional
1741
+ Expansion size ratio in meters. The default is ``0.002``.
1742
+ use_round_corner : bool, optional
1743
+ Whether to use round corners. The default is ``False``.
1744
+ output_aedb_path : str, optional
1745
+ Full path and name for the new AEDB file. If None, then current aedb will be cutout.
1746
+ open_cutout_at_end : bool, optional
1747
+ Whether to open the cutout at the end. The default is ``True``.
1748
+ use_pyaedt_cutout : bool, optional
1749
+ Whether to use new PyAEDT cutout method or EDB API method.
1750
+ New method is faster than native API method since it benefits of multithread.
1751
+ number_of_threads : int, optional
1752
+ Number of thread to use. Default is 4. Valid only if ``use_pyaedt_cutout`` is set to ``True``.
1753
+ use_pyaedt_extent_computing : bool, optional
1754
+ Whether to use legacy extent computing (experimental) or EDB API.
1755
+ extent_defeature : float, optional
1756
+ Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental).
1757
+ It applies only to Conforming bounding box. Default value is ``0`` which disable it.
1758
+ remove_single_pin_components : bool, optional
1759
+ Remove all Single Pin RLC after the cutout is completed. Default is `False`.
1760
+ custom_extent : list
1761
+ Points list defining the cutout shape. This setting will override `extent_type` field.
1762
+ custom_extent_units : str
1763
+ Units of the point list. The default is ``"mm"``. Valid only if `custom_extend` is provided.
1764
+ include_partial_instances : bool, optional
1765
+ Whether to include padstack instances that have bounding boxes intersecting with point list polygons.
1766
+ This operation may slow down the cutout export.Valid only if `custom_extend` and
1767
+ `use_pyaedt_cutout` is provided.
1768
+ keep_voids : bool
1769
+ Boolean used for keep or not the voids intersecting the polygon used for clipping the layout.
1770
+ Default value is ``True``, ``False`` will remove the voids.Valid only if `custom_extend` is provided.
1771
+ check_terminals : bool, optional
1772
+ Whether to check for all reference terminals and increase extent to include them into the cutout.
1773
+ This applies to components which have a model (spice, touchstone or netlist) associated.
1774
+ include_pingroups : bool, optional
1775
+ Whether to check for all pingroups terminals and increase extent to include them into the cutout.
1776
+ It requires ``check_terminals``.
1777
+ expansion_factor : int, optional
1778
+ The method computes a float representing the largest number between
1779
+ the dielectric thickness or trace width multiplied by the expansion_factor factor.
1780
+ The trace width search is limited to nets with ports attached. Works only if `use_pyaedt_cutout`.
1781
+ Default is `0` to disable the search.
1782
+ maximum_iterations : int, optional
1783
+ Maximum number of iterations before stopping a search for a cutout with an error.
1784
+ Default is `10`.
1785
+ preserve_components_with_model : bool, optional
1786
+ Whether to preserve all pins of components that have associated models (Spice or NPort).
1787
+ This parameter is applicable only for a PyAEDT cutout (except point list).
1788
+ simple_pad_check : bool, optional
1789
+ Whether to use the center of the pad to find the intersection with extent or use the bounding box.
1790
+ Second method is much slower and requires to disable multithread on padstack removal.
1791
+ Default is `True`.
1792
+ keep_lines_as_path : bool, optional
1793
+ Whether to keep the lines as Path after they are cutout or convert them to PolygonData.
1794
+ This feature works only in Electronics Desktop (3D Layout).
1795
+ If the flag is set to ``True`` it can cause issues in SiWave once the Edb is imported.
1796
+ Default is ``False`` to generate PolygonData of cut lines.
1797
+
1798
+ Returns
1799
+ -------
1800
+ List
1801
+ List of coordinate points defining the extent used for clipping the design. If it failed return an empty
1802
+ list.
1803
+
1804
+ Examples
1805
+ --------
1806
+ >>> from pyedb.dotnet.edb import Edb
1807
+ >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2")
1808
+ >>> edb.logger.info_timer("Edb Opening")
1809
+ >>> edb.logger.reset_timer()
1810
+ >>> start = time.time()
1811
+ >>> signal_list = []
1812
+ >>> for net in edb.nets.netlist:
1813
+ >>> if "3V3" in net:
1814
+ >>> signal_list.append(net)
1815
+ >>> power_list = ["PGND"]
1816
+ >>> edb.cutout(signal_list=signal_list, reference_list=power_list, extent_type="Conforming")
1817
+ >>> end_time = str((time.time() - start)/60)
1818
+ >>> edb.logger.info("Total legacy cutout time in min %s", end_time)
1819
+ >>> edb.nets.plot(signal_list, None, color_by_net=True)
1820
+ >>> edb.nets.plot(power_list, None, color_by_net=True)
1821
+ >>> edb.save_edb()
1822
+ >>> edb.close_edb()
1823
+
1824
+
1825
+ """
1826
+ if expansion_factor > 0:
1827
+ expansion_size = self.calculate_initial_extent(expansion_factor)
1828
+ if signal_list is None:
1829
+ signal_list = []
1830
+ if isinstance(reference_list, str):
1831
+ reference_list = [reference_list]
1832
+ elif reference_list is None:
1833
+ reference_list = []
1834
+ if not use_pyaedt_cutout and custom_extent:
1835
+ return self._create_cutout_on_point_list(
1836
+ custom_extent,
1837
+ units=custom_extent_units,
1838
+ output_aedb_path=output_aedb_path,
1839
+ open_cutout_at_end=open_cutout_at_end,
1840
+ nets_to_include=signal_list + reference_list,
1841
+ include_partial_instances=include_partial_instances,
1842
+ keep_voids=keep_voids,
1843
+ )
1844
+ elif not use_pyaedt_cutout:
1845
+ return self._create_cutout_legacy(
1846
+ signal_list=signal_list,
1847
+ reference_list=reference_list,
1848
+ extent_type=extent_type,
1849
+ expansion_size=expansion_size,
1850
+ use_round_corner=use_round_corner,
1851
+ output_aedb_path=output_aedb_path,
1852
+ open_cutout_at_end=open_cutout_at_end,
1853
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
1854
+ check_terminals=check_terminals,
1855
+ include_pingroups=include_pingroups,
1856
+ )
1857
+ else:
1858
+ legacy_path = self.edbpath
1859
+ if expansion_factor > 0 and not custom_extent:
1860
+ start = time.time()
1861
+ self.save_edb()
1862
+ dummy_path = self.edbpath.replace(".aedb", "_smart_cutout_temp.aedb")
1863
+ working_cutout = False
1864
+ i = 1
1865
+ expansion = expansion_size
1866
+ while i <= maximum_iterations:
1867
+ self.logger.info("-----------------------------------------")
1868
+ self.logger.info("Trying cutout with {}mm expansion size".format(expansion * 1e3))
1869
+ self.logger.info("-----------------------------------------")
1870
+ result = self._create_cutout_multithread(
1871
+ signal_list=signal_list,
1872
+ reference_list=reference_list,
1873
+ extent_type=extent_type,
1874
+ expansion_size=expansion,
1875
+ use_round_corner=use_round_corner,
1876
+ number_of_threads=number_of_threads,
1877
+ custom_extent=custom_extent,
1878
+ output_aedb_path=dummy_path,
1879
+ remove_single_pin_components=remove_single_pin_components,
1880
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
1881
+ extent_defeature=extent_defeature,
1882
+ custom_extent_units=custom_extent_units,
1883
+ check_terminals=check_terminals,
1884
+ include_pingroups=include_pingroups,
1885
+ preserve_components_with_model=preserve_components_with_model,
1886
+ include_partial=include_partial_instances,
1887
+ simple_pad_check=simple_pad_check,
1888
+ keep_lines_as_path=keep_lines_as_path,
1889
+ )
1890
+ if self.are_port_reference_terminals_connected():
1891
+ if output_aedb_path:
1892
+ self.save_edb_as(output_aedb_path)
1893
+ else:
1894
+ self.save_edb_as(legacy_path)
1895
+ working_cutout = True
1896
+ break
1897
+ self.close_edb()
1898
+ self.edbpath = legacy_path
1899
+ self.open_edb()
1900
+ i += 1
1901
+ expansion = expansion_size * i
1902
+ if working_cutout:
1903
+ msg = "Cutout completed in {} iterations with expansion size of {}mm".format(i, expansion * 1e3)
1904
+ self.logger.info_timer(msg, start)
1905
+ else:
1906
+ msg = "Cutout failed after {} iterations and expansion size of {}mm".format(i, expansion * 1e3)
1907
+ self.logger.info_timer(msg, start)
1908
+ return False
1909
+ else:
1910
+ result = self._create_cutout_multithread(
1911
+ signal_list=signal_list,
1912
+ reference_list=reference_list,
1913
+ extent_type=extent_type,
1914
+ expansion_size=expansion_size,
1915
+ use_round_corner=use_round_corner,
1916
+ number_of_threads=number_of_threads,
1917
+ custom_extent=custom_extent,
1918
+ output_aedb_path=output_aedb_path,
1919
+ remove_single_pin_components=remove_single_pin_components,
1920
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
1921
+ extent_defeature=extent_defeature,
1922
+ custom_extent_units=custom_extent_units,
1923
+ check_terminals=check_terminals,
1924
+ include_pingroups=include_pingroups,
1925
+ preserve_components_with_model=preserve_components_with_model,
1926
+ include_partial=include_partial_instances,
1927
+ simple_pad_check=simple_pad_check,
1928
+ keep_lines_as_path=keep_lines_as_path,
1929
+ )
1930
+ if result and not open_cutout_at_end and self.edbpath != legacy_path:
1931
+ self.save_edb()
1932
+ self.close_edb()
1933
+ self.edbpath = legacy_path
1934
+ self.open_edb()
1935
+ return result
1936
+
1937
+ @pyedb_function_handler()
1938
+ def _create_cutout_legacy(
1939
+ self,
1940
+ signal_list=[],
1941
+ reference_list=["GND"],
1942
+ extent_type="Conforming",
1943
+ expansion_size=0.002,
1944
+ use_round_corner=False,
1945
+ output_aedb_path=None,
1946
+ open_cutout_at_end=True,
1947
+ use_pyaedt_extent_computing=False,
1948
+ remove_single_pin_components=False,
1949
+ check_terminals=False,
1950
+ include_pingroups=True,
1951
+ ):
1952
+ expansion_size = self.edb_value(expansion_size).ToDouble()
1953
+
1954
+ # validate nets in layout
1955
+ net_signals = [net.api_object for net in self.layout.nets if net.name in signal_list]
1956
+
1957
+ # validate references in layout
1958
+ _netsClip = convert_py_list_to_net_list(
1959
+ [net.api_object for net in self.layout.nets if net.name in reference_list]
1960
+ )
1961
+
1962
+ _poly = self._create_extent(
1963
+ net_signals,
1964
+ extent_type,
1965
+ expansion_size,
1966
+ use_round_corner,
1967
+ use_pyaedt_extent_computing,
1968
+ smart_cut=check_terminals,
1969
+ reference_list=reference_list,
1970
+ include_pingroups=include_pingroups,
1971
+ )
1972
+
1973
+ # Create new cutout cell/design
1974
+ included_nets_list = signal_list + reference_list
1975
+ included_nets = convert_py_list_to_net_list(
1976
+ [net.api_object for net in self.layout.nets if net.name in included_nets_list]
1977
+ )
1978
+ _cutout = self.active_cell.CutOut(included_nets, _netsClip, _poly, True)
1979
+ # Analysis setups do not come over with the clipped design copy,
1980
+ # so add the analysis setups from the original here.
1981
+ id = 1
1982
+ for _setup in self.active_cell.SimulationSetups:
1983
+ # Empty string '' if coming from setup copy and don't set explicitly.
1984
+ _setup_name = _setup.GetName()
1985
+ if "GetSimSetupInfo" in dir(_setup):
1986
+ # setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object
1987
+ _hfssSimSetupInfo = _setup.GetSimSetupInfo()
1988
+ _hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup
1989
+ # Write the simulation setup info into the cell/design setup
1990
+ _setup.SetSimSetupInfo(_hfssSimSetupInfo)
1991
+ _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
1992
+ id += 1
1993
+ else:
1994
+ _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
1995
+
1996
+ _dbCells = [_cutout]
1997
+
1998
+ if output_aedb_path:
1999
+ db2 = self.create(output_aedb_path)
2000
+ _success = db2.Save()
2001
+ _dbCells = convert_py_list_to_net_list(_dbCells)
2002
+ db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project
2003
+ if len(list(db2.CircuitCells)) > 0:
2004
+ for net in list(list(db2.CircuitCells)[0].GetLayout().Nets):
2005
+ if not net.GetName() in included_nets_list:
2006
+ net.Delete()
2007
+ _success = db2.Save()
2008
+ for c in list(self.active_db.TopCircuitCells):
2009
+ if c.GetName() == _cutout.GetName():
2010
+ c.Delete()
2011
+ if open_cutout_at_end: # pragma: no cover
2012
+ self._db = db2
2013
+ self.edbpath = output_aedb_path
2014
+ self._active_cell = list(self.top_circuit_cells)[0]
2015
+ self.edbpath = self.directory
2016
+ self._init_objects()
2017
+ if remove_single_pin_components:
2018
+ self.components.delete_single_pin_rlc()
2019
+ self.logger.info_timer("Single Pins components deleted")
2020
+ self.components.refresh_components()
2021
+ else:
2022
+ if remove_single_pin_components:
2023
+ try:
2024
+ layout = list(db2.CircuitCells)[0].GetLayout()
2025
+ _cmps = [
2026
+ l
2027
+ for l in layout.Groups
2028
+ if l.ToString() == "Ansys.Ansoft.Edb.Cell.Hierarchy.Component" and l.GetNumberOfPins() < 2
2029
+ ]
2030
+ for _cmp in _cmps:
2031
+ _cmp.Delete()
2032
+ except:
2033
+ self._logger.error("Failed to remove single pin components.")
2034
+ db2.Close()
2035
+ source = os.path.join(output_aedb_path, "edb.def.tmp")
2036
+ target = os.path.join(output_aedb_path, "edb.def")
2037
+ self._wait_for_file_release(file_to_release=output_aedb_path)
2038
+ if os.path.exists(source) and not os.path.exists(target):
2039
+ try:
2040
+ shutil.copy(source, target)
2041
+ except:
2042
+ pass
2043
+ elif open_cutout_at_end:
2044
+ self._active_cell = _cutout
2045
+ self._init_objects()
2046
+ if remove_single_pin_components:
2047
+ self.components.delete_single_pin_rlc()
2048
+ self.logger.info_timer("Single Pins components deleted")
2049
+ self.components.refresh_components()
2050
+ return [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(_poly.GetPolygonWithoutArcs().Points)]
2051
+
2052
+ @pyedb_function_handler()
2053
+ def create_cutout(
2054
+ self,
2055
+ signal_list=[],
2056
+ reference_list=["GND"],
2057
+ extent_type="Conforming",
2058
+ expansion_size=0.002,
2059
+ use_round_corner=False,
2060
+ output_aedb_path=None,
2061
+ open_cutout_at_end=True,
2062
+ use_pyaedt_extent_computing=False,
2063
+ ):
2064
+ """Create a cutout using an approach entirely based on legacy.
2065
+ It does in sequence:
2066
+ - delete all nets not in list,
2067
+ - create an extent of the nets,
2068
+ - check and delete all vias not in the extent,
2069
+ - check and delete all the primitives not in extent,
2070
+ - check and intersect all the primitives that intersect the extent.
2071
+
2072
+ .. deprecated:: 0.6.58
2073
+ Use new method :func:`cutout` instead.
2074
+
2075
+ Parameters
2076
+ ----------
2077
+ signal_list : list
2078
+ List of signal strings.
2079
+ reference_list : list, optional
2080
+ List of references to add. The default is ``["GND"]``.
2081
+ extent_type : str, optional
2082
+ Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and
2083
+ ``"Bounding"``. The default is ``"Conforming"``.
2084
+ expansion_size : float, str, optional
2085
+ Expansion size ratio in meters. The default is ``0.002``.
2086
+ use_round_corner : bool, optional
2087
+ Whether to use round corners. The default is ``False``.
2088
+ output_aedb_path : str, optional
2089
+ Full path and name for the new AEDB file.
2090
+ open_cutout_at_end : bool, optional
2091
+ Whether to open the cutout at the end. The default
2092
+ is ``True``.
2093
+ use_pyaedt_extent_computing : bool, optional
2094
+ Whether to use legacy extent computing (experimental).
2095
+
2096
+ Returns
2097
+ -------
2098
+ bool
2099
+ ``True`` when successful, ``False`` when failed.
2100
+
2101
+ """
2102
+ warnings.warn("Use new method `cutout` instead.", DeprecationWarning)
2103
+ return self._create_cutout_legacy(
2104
+ signal_list=signal_list,
2105
+ reference_list=reference_list,
2106
+ extent_type=extent_type,
2107
+ expansion_size=expansion_size,
2108
+ use_round_corner=use_round_corner,
2109
+ output_aedb_path=output_aedb_path,
2110
+ open_cutout_at_end=open_cutout_at_end,
2111
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
2112
+ )
2113
+
2114
+ @pyedb_function_handler()
2115
+ def _create_cutout_multithread(
2116
+ self,
2117
+ signal_list=[],
2118
+ reference_list=["GND"],
2119
+ extent_type="Conforming",
2120
+ expansion_size=0.002,
2121
+ use_round_corner=False,
2122
+ number_of_threads=4,
2123
+ custom_extent=None,
2124
+ output_aedb_path=None,
2125
+ remove_single_pin_components=False,
2126
+ use_pyaedt_extent_computing=False,
2127
+ extent_defeature=0.0,
2128
+ custom_extent_units="mm",
2129
+ check_terminals=False,
2130
+ include_pingroups=True,
2131
+ preserve_components_with_model=False,
2132
+ include_partial=False,
2133
+ simple_pad_check=True,
2134
+ keep_lines_as_path=False,
2135
+ ):
2136
+ if is_ironpython: # pragma: no cover
2137
+ self.logger.error("Method working only in Cpython")
2138
+ return False
2139
+ from concurrent.futures import ThreadPoolExecutor
2140
+
2141
+ if output_aedb_path:
2142
+ self.save_edb_as(output_aedb_path)
2143
+ self.logger.info("Cutout Multithread started.")
2144
+ expansion_size = self.edb_value(expansion_size).ToDouble()
2145
+
2146
+ timer_start = self.logger.reset_timer()
2147
+ if custom_extent:
2148
+ if not reference_list and not signal_list:
2149
+ reference_list = self.nets.netlist[::]
2150
+ all_list = reference_list
2151
+ else:
2152
+ reference_list = reference_list + signal_list
2153
+ all_list = reference_list
2154
+ else:
2155
+ all_list = signal_list + reference_list
2156
+ pins_to_preserve = []
2157
+ nets_to_preserve = []
2158
+ if preserve_components_with_model:
2159
+ for el in self.components.instances.values():
2160
+ if el.model_type in [
2161
+ "SPICEModel",
2162
+ "SParameterModel",
2163
+ "NetlistModel",
2164
+ ] and list(set(el.nets[:]) & set(signal_list[:])):
2165
+ pins_to_preserve.extend([i.id for i in el.pins.values()])
2166
+ nets_to_preserve.extend(el.nets)
2167
+ if include_pingroups:
2168
+ for reference in reference_list:
2169
+ for pin in self.nets.nets[reference].padstack_instances:
2170
+ if pin.pingroups:
2171
+ pins_to_preserve.append(pin.id)
2172
+ if check_terminals:
2173
+ terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [0, 3, 4, 7, 8]]
2174
+ for term in terms:
2175
+ if term.GetTerminalType().ToString() == "PadstackInstanceTerminal":
2176
+ if term.GetParameters()[1].GetNet().GetName() in reference_list:
2177
+ pins_to_preserve.append(term.GetParameters()[1].GetId())
2178
+
2179
+ for i in self.nets.nets.values():
2180
+ name = i.name
2181
+ if name not in all_list and name not in nets_to_preserve:
2182
+ i.net_object.Delete()
2183
+ reference_pinsts = []
2184
+ reference_prims = []
2185
+ reference_paths = []
2186
+ for i in self.padstacks.instances.values():
2187
+ net_name = i.net_name
2188
+ id = i.id
2189
+ if net_name not in all_list and id not in pins_to_preserve:
2190
+ i.delete()
2191
+ elif net_name in reference_list and id not in pins_to_preserve:
2192
+ reference_pinsts.append(i)
2193
+ for i in self.modeler.primitives:
2194
+ if i:
2195
+ net_name = i.net_name
2196
+ if net_name not in all_list:
2197
+ i.delete()
2198
+ elif net_name in reference_list and not i.is_void:
2199
+ if keep_lines_as_path and i.type == "Path":
2200
+ reference_paths.append(i)
2201
+ else:
2202
+ reference_prims.append(i)
2203
+ self.logger.info_timer("Net clean up")
2204
+ self.logger.reset_timer()
2205
+
2206
+ if custom_extent and isinstance(custom_extent, list):
2207
+ if custom_extent[0] != custom_extent[-1]:
2208
+ custom_extent.append(custom_extent[0])
2209
+ custom_extent = [
2210
+ [
2211
+ self.number_with_units(i[0], custom_extent_units),
2212
+ self.number_with_units(i[1], custom_extent_units),
2213
+ ]
2214
+ for i in custom_extent
2215
+ ]
2216
+ plane = self.modeler.Shape("polygon", points=custom_extent)
2217
+ _poly = self.modeler.shape_to_polygon_data(plane)
2218
+ elif custom_extent:
2219
+ _poly = custom_extent
2220
+ else:
2221
+ net_signals = [net.api_object for net in self.layout.nets if net.name in signal_list]
2222
+ _poly = self._create_extent(
2223
+ net_signals,
2224
+ extent_type,
2225
+ expansion_size,
2226
+ use_round_corner,
2227
+ use_pyaedt_extent_computing,
2228
+ smart_cut=check_terminals,
2229
+ reference_list=reference_list,
2230
+ include_pingroups=include_pingroups,
2231
+ pins_to_preserve=pins_to_preserve,
2232
+ )
2233
+ if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1] and extent_defeature > 0:
2234
+ _poly = _poly.Defeature(extent_defeature)
2235
+
2236
+ if not _poly or _poly.IsNull():
2237
+ self._logger.error("Failed to create Extent.")
2238
+ return []
2239
+ self.logger.info_timer("Expanded Net Polygon Creation")
2240
+ self.logger.reset_timer()
2241
+ _poly_list = convert_py_list_to_net_list([_poly])
2242
+ prims_to_delete = []
2243
+ poly_to_create = []
2244
+ pins_to_delete = []
2245
+
2246
+ def intersect(poly1, poly2):
2247
+ if not isinstance(poly2, list):
2248
+ poly2 = [poly2]
2249
+ return list(
2250
+ poly1.Intersect(
2251
+ convert_py_list_to_net_list(poly1),
2252
+ convert_py_list_to_net_list(poly2),
2253
+ )
2254
+ )
2255
+
2256
+ def subtract(poly, voids):
2257
+ return poly.Subtract(convert_py_list_to_net_list(poly), convert_py_list_to_net_list(voids))
2258
+
2259
+ def clip_path(path):
2260
+ pdata = path.polygon_data.edb_api
2261
+ int_data = _poly.GetIntersectionType(pdata)
2262
+ if int_data == 0:
2263
+ prims_to_delete.append(path)
2264
+ return
2265
+ result = path._edb_object.SetClipInfo(_poly, True)
2266
+ if not result:
2267
+ self.logger.info("Failed to clip path {}. Clipping as polygon.".format(path.id))
2268
+ reference_prims.append(path)
2269
+
2270
+ def clean_prim(prim_1): # pragma: no cover
2271
+ pdata = prim_1.polygon_data.edb_api
2272
+ int_data = _poly.GetIntersectionType(pdata)
2273
+ if int_data == 2:
2274
+ return
2275
+ elif int_data == 0:
2276
+ prims_to_delete.append(prim_1)
2277
+ else:
2278
+ list_poly = intersect(_poly, pdata)
2279
+ if list_poly:
2280
+ net = prim_1.net_name
2281
+ voids = prim_1.voids
2282
+ for p in list_poly:
2283
+ if p.IsNull():
2284
+ continue
2285
+ # points = list(p.Points)
2286
+ list_void = []
2287
+ if voids:
2288
+ voids_data = [void.polygon_data.edb_api for void in voids]
2289
+ list_prims = subtract(p, voids_data)
2290
+ for prim in list_prims:
2291
+ if not prim.IsNull():
2292
+ poly_to_create.append([prim, prim_1.layer_name, net, list_void])
2293
+ else:
2294
+ poly_to_create.append([p, prim_1.layer_name, net, list_void])
2295
+
2296
+ prims_to_delete.append(prim_1)
2297
+
2298
+ def pins_clean(pinst):
2299
+ if not pinst.in_polygon(_poly, include_partial=include_partial, simple_check=simple_pad_check):
2300
+ pins_to_delete.append(pinst)
2301
+
2302
+ if not simple_pad_check:
2303
+ pad_cores = 1
2304
+ else:
2305
+ pad_cores = number_of_threads
2306
+ with ThreadPoolExecutor(pad_cores) as pool:
2307
+ pool.map(lambda item: pins_clean(item), reference_pinsts)
2308
+
2309
+ for pin in pins_to_delete:
2310
+ pin.delete()
2311
+
2312
+ self.logger.info_timer("Padstack Instances removal completed")
2313
+ self.logger.reset_timer()
2314
+
2315
+ # with ThreadPoolExecutor(number_of_threads) as pool:
2316
+ # pool.map(lambda item: clip_path(item), reference_paths)
2317
+
2318
+ for item in reference_paths:
2319
+ clip_path(item)
2320
+ with ThreadPoolExecutor(number_of_threads) as pool:
2321
+ pool.map(lambda item: clean_prim(item), reference_prims)
2322
+
2323
+ for el in poly_to_create:
2324
+ self.modeler.create_polygon(el[0], el[1], net_name=el[2], voids=el[3])
2325
+
2326
+ for prim in prims_to_delete:
2327
+ prim.delete()
2328
+
2329
+ self.logger.info_timer("Primitives cleanup completed")
2330
+ self.logger.reset_timer()
2331
+
2332
+ i = 0
2333
+ for _, val in self.components.components.items():
2334
+ if val.numpins == 0:
2335
+ val.edbcomponent.Delete()
2336
+ i += 1
2337
+ i += 1
2338
+ self.logger.info("Deleted {} additional components".format(i))
2339
+ if remove_single_pin_components:
2340
+ self.components.delete_single_pin_rlc()
2341
+ self.logger.info_timer("Single Pins components deleted")
2342
+
2343
+ self.components.refresh_components()
2344
+ if output_aedb_path:
2345
+ self.save_edb()
2346
+ self.logger.info_timer("Cutout completed.", timer_start)
2347
+ self.logger.reset_timer()
2348
+ return [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(_poly.GetPolygonWithoutArcs().Points)]
2349
+
2350
+ @pyedb_function_handler()
2351
+ def create_cutout_multithread(
2352
+ self,
2353
+ signal_list=[],
2354
+ reference_list=["GND"],
2355
+ extent_type="Conforming",
2356
+ expansion_size=0.002,
2357
+ use_round_corner=False,
2358
+ number_of_threads=4,
2359
+ custom_extent=None,
2360
+ output_aedb_path=None,
2361
+ remove_single_pin_components=False,
2362
+ use_pyaedt_extent_computing=False,
2363
+ extent_defeature=0,
2364
+ keep_lines_as_path=False,
2365
+ return_extent=False,
2366
+ ):
2367
+ """Create a cutout using an approach entirely based on legacy.
2368
+ It does in sequence:
2369
+ - delete all nets not in list,
2370
+ - create a extent of the nets,
2371
+ - check and delete all vias not in the extent,
2372
+ - check and delete all the primitives not in extent,
2373
+ - check and intersect all the primitives that intersect the extent.
2374
+
2375
+
2376
+ .. deprecated:: 0.6.58
2377
+ Use new method :func:`cutout` instead.
2378
+
2379
+ Parameters
2380
+ ----------
2381
+ signal_list : list
2382
+ List of signal strings.
2383
+ reference_list : list, optional
2384
+ List of references to add. The default is ``["GND"]``.
2385
+ extent_type : str, optional
2386
+ Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and
2387
+ ``"Bounding"``. The default is ``"Conforming"``.
2388
+ expansion_size : float, str, optional
2389
+ Expansion size ratio in meters. The default is ``0.002``.
2390
+ use_round_corner : bool, optional
2391
+ Whether to use round corners. The default is ``False``.
2392
+ number_of_threads : int, optional
2393
+ Number of thread to use. Default is 4
2394
+ custom_extent : list, optional
2395
+ Custom extent to use for the cutout. It has to be a list of points [[x1,y1],[x2,y2]....] or
2396
+ Edb PolygonData object. In this case, both signal_list and reference_list will be cut.
2397
+ output_aedb_path : str, optional
2398
+ Full path and name for the new AEDB file. If None, then current aedb will be cutout.
2399
+ remove_single_pin_components : bool, optional
2400
+ Remove all Single Pin RLC after the cutout is completed. Default is `False`.
2401
+ use_pyaedt_extent_computing : bool, optional
2402
+ Whether to use legacy extent computing (experimental).
2403
+ extent_defeature : float, optional
2404
+ Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental).
2405
+ It applies only to Conforming bounding box. Default value is ``0`` which disable it.
2406
+ keep_lines_as_path : bool, optional
2407
+ Whether to keep the lines as Path after they are cutout or convert them to PolygonData.
2408
+ This feature works only in Electronics Desktop (3D Layout).
2409
+ If the flag is set to True it can cause issues in SiWave once the Edb is imported.
2410
+ Default is ``False`` to generate PolygonData of cut lines.
2411
+ return_extent : bool, optional
2412
+ When ``True`` extent used for clipping is returned, if ``False`` only the boolean indicating whether
2413
+ clipping succeed or not is returned. Not applicable with custom extent usage.
2414
+ Default is ``False``.
2415
+
2416
+ Returns
2417
+ -------
2418
+ bool
2419
+ ``True`` when successful, ``False`` when failed.
2420
+
2421
+ Examples
2422
+ --------
2423
+ >>> from pyedb.dotnet.edb import Edb
2424
+ >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2")
2425
+ >>> edb.logger.info_timer("Edb Opening")
2426
+ >>> edb.logger.reset_timer()
2427
+ >>> start = time.time()
2428
+ >>> signal_list = []
2429
+ >>> for net in edb.nets.nets.keys():
2430
+ >>> if "3V3" in net:
2431
+ >>> signal_list.append(net)
2432
+ >>> power_list = ["PGND"]
2433
+ >>> edb.create_cutout_multithread(signal_list=signal_list, reference_list=power_list, extent_type="Conforming")
2434
+ >>> end_time = str((time.time() - start)/60)
2435
+ >>> edb.logger.info("Total legacy cutout time in min %s", end_time)
2436
+ >>> edb.nets.plot(signal_list, None, color_by_net=True)
2437
+ >>> edb.nets.plot(power_list, None, color_by_net=True)
2438
+ >>> edb.save_edb()
2439
+ >>> edb.close_edb()
2440
+
2441
+ """
2442
+ warnings.warn("Use new method `cutout` instead.", DeprecationWarning)
2443
+ return self._create_cutout_multithread(
2444
+ signal_list=signal_list,
2445
+ reference_list=reference_list,
2446
+ extent_type=extent_type,
2447
+ expansion_size=expansion_size,
2448
+ use_round_corner=use_round_corner,
2449
+ number_of_threads=number_of_threads,
2450
+ custom_extent=custom_extent,
2451
+ output_aedb_path=output_aedb_path,
2452
+ remove_single_pin_components=remove_single_pin_components,
2453
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
2454
+ extent_defeature=extent_defeature,
2455
+ keep_lines_as_path=keep_lines_as_path,
2456
+ return_extent=return_extent,
2457
+ )
2458
+
2459
+ @pyedb_function_handler()
2460
+ def get_conformal_polygon_from_netlist(self, netlist=None):
2461
+ """Return an EDB conformal polygon based on a netlist.
2462
+
2463
+ Parameters
2464
+ ----------
2465
+
2466
+ netlist : List of net names.
2467
+ list[str]
2468
+
2469
+ Returns
2470
+ -------
2471
+ :class:`Edb.Cell.Primitive.Polygon`
2472
+ Edb polygon object.
2473
+
2474
+ """
2475
+ temp_edb_path = self.edbpath[:-5] + "_temp_aedb.aedb"
2476
+ shutil.copytree(self.edbpath, temp_edb_path)
2477
+ temp_edb = Edb(temp_edb_path)
2478
+ for via in list(temp_edb.padstacks.instances.values()):
2479
+ via.pin.Delete()
2480
+ if netlist:
2481
+ nets = [net.net_obj for net in temp_edb.layout.nets if net.name in netlist]
2482
+ _poly = temp_edb.layout.expanded_extent(
2483
+ nets, self.edb_api.geometry.extent_type.Conforming, 0.0, True, True, 1
2484
+ )
2485
+ else:
2486
+ nets = [net.api_object for net in temp_edb.layout.nets if "gnd" in net.name.lower()]
2487
+ _poly = temp_edb.layout.expanded_extent(
2488
+ nets, self.edb_api.geometry.extent_type.Conforming, 0.0, True, True, 1
2489
+ )
2490
+ temp_edb.close_edb()
2491
+ if _poly:
2492
+ return _poly
2493
+ else:
2494
+ return False
2495
+
2496
+ @pyedb_function_handler()
2497
+ def number_with_units(self, value, units=None):
2498
+ """Convert a number to a string with units. If value is a string, it's returned as is.
2499
+
2500
+ Parameters
2501
+ ----------
2502
+ value : float, int, str
2503
+ Input number or string.
2504
+ units : optional
2505
+ Units for formatting. The default is ``None``, which uses ``"meter"``.
2506
+
2507
+ Returns
2508
+ -------
2509
+ str
2510
+ String concatenating the value and unit.
2511
+
2512
+ """
2513
+ if units is None:
2514
+ units = "meter"
2515
+ if isinstance(value, str):
2516
+ return value
2517
+ else:
2518
+ return "{0}{1}".format(value, units)
2519
+
2520
+ @pyedb_function_handler()
2521
+ def arg_with_dim(self, Value, sUnits):
2522
+ """Convert a number to a string with units. If value is a string, it's returned as is.
2523
+
2524
+ .. deprecated:: 0.6.56
2525
+ Use :func:`number_with_units` property instead.
2526
+
2527
+ Parameters
2528
+ ----------
2529
+ Value : float, int, str
2530
+ Input number or string.
2531
+ sUnits : optional
2532
+ Units for formatting. The default is ``None``, which uses ``"meter"``.
2533
+
2534
+ Returns
2535
+ -------
2536
+ str
2537
+ String concatenating the value and unit.
2538
+
2539
+ """
2540
+ warnings.warn("Use :func:`number_with_units` instead.", DeprecationWarning)
2541
+ return self.number_with_units(Value, sUnits)
2542
+
2543
+ def _decompose_variable_value(self, value, unit_system=None):
2544
+ val, units = decompose_variable_value(value)
2545
+ if units and unit_system and units in AEDT_UNITS[unit_system]:
2546
+ return AEDT_UNITS[unit_system][units] * val
2547
+ else:
2548
+ return val
2549
+
2550
+ @pyedb_function_handler()
2551
+ def _create_cutout_on_point_list(
2552
+ self,
2553
+ point_list,
2554
+ units="mm",
2555
+ output_aedb_path=None,
2556
+ open_cutout_at_end=True,
2557
+ nets_to_include=None,
2558
+ include_partial_instances=False,
2559
+ keep_voids=True,
2560
+ ):
2561
+ if point_list[0] != point_list[-1]:
2562
+ point_list.append(point_list[0])
2563
+ point_list = [[self.number_with_units(i[0], units), self.number_with_units(i[1], units)] for i in point_list]
2564
+ plane = self.modeler.Shape("polygon", points=point_list)
2565
+ polygonData = self.modeler.shape_to_polygon_data(plane)
2566
+ _ref_nets = []
2567
+ if nets_to_include:
2568
+ self.logger.info("Creating cutout on {} nets.".format(len(nets_to_include)))
2569
+ else:
2570
+ self.logger.info("Creating cutout on all nets.") # pragma: no cover
2571
+
2572
+ # Check Padstack Instances overlapping the cutout
2573
+ pinstance_to_add = []
2574
+ if include_partial_instances:
2575
+ if nets_to_include:
2576
+ pinst = [i for i in list(self.padstacks.instances.values()) if i.net_name in nets_to_include]
2577
+ else:
2578
+ pinst = [i for i in list(self.padstacks.instances.values())]
2579
+ for p in pinst:
2580
+ if p.in_polygon(polygonData):
2581
+ pinstance_to_add.append(p)
2582
+ # validate references in layout
2583
+ for _ref in self.nets.nets:
2584
+ if nets_to_include:
2585
+ if _ref in nets_to_include:
2586
+ _ref_nets.append(self.nets.nets[_ref].net_object)
2587
+ else:
2588
+ _ref_nets.append(self.nets.nets[_ref].net_object) # pragma: no cover
2589
+ if keep_voids:
2590
+ voids = [p for p in self.modeler.circles if p.is_void]
2591
+ voids2 = [p for p in self.modeler.polygons if p.is_void]
2592
+ voids.extend(voids2)
2593
+ else:
2594
+ voids = []
2595
+ voids_to_add = []
2596
+ for circle in voids:
2597
+ if polygonData.GetIntersectionType(circle.primitive_object.GetPolygonData()) >= 3:
2598
+ voids_to_add.append(circle)
2599
+
2600
+ _netsClip = convert_py_list_to_net_list(_ref_nets)
2601
+ # net_signals = convert_py_list_to_net_list([], type(_ref_nets[0]))
2602
+
2603
+ # Create new cutout cell/design
2604
+ _cutout = self.active_cell.CutOut(_netsClip, _netsClip, polygonData)
2605
+ layout = _cutout.GetLayout()
2606
+ cutout_obj_coll = list(layout.PadstackInstances)
2607
+ ids = []
2608
+ for lobj in cutout_obj_coll:
2609
+ ids.append(lobj.GetId())
2610
+
2611
+ if include_partial_instances:
2612
+ p_missing = [i for i in pinstance_to_add if i.id not in ids]
2613
+ self.logger.info("Added {} padstack instances after cutout".format(len(p_missing)))
2614
+ for p in p_missing:
2615
+ position = self.edb_api.geometry.point_data(
2616
+ self.edb_value(p.position[0]), self.edb_value(p.position[1])
2617
+ )
2618
+ net = self.nets.find_or_create_net(p.net_name)
2619
+ rotation = self.edb_value(p.rotation)
2620
+ sign_layers = list(self.stackup.signal_layers.keys())
2621
+ if not p.start_layer: # pragma: no cover
2622
+ fromlayer = self.stackup.signal_layers[sign_layers[0]]._edb_layer
2623
+ else:
2624
+ fromlayer = self.stackup.signal_layers[p.start_layer]._edb_layer
2625
+
2626
+ if not p.stop_layer: # pragma: no cover
2627
+ tolayer = self.stackup.signal_layers[sign_layers[-1]]._edb_layer
2628
+ else:
2629
+ tolayer = self.stackup.signal_layers[p.stop_layer]._edb_layer
2630
+ padstack = None
2631
+ for pad in list(self.padstacks.definitions.keys()):
2632
+ if pad == p.padstack_definition:
2633
+ padstack = self.padstacks.definitions[pad].edb_padstack
2634
+ padstack_instance = self.edb_api.cell.primitive.padstack_instance.create(
2635
+ _cutout.GetLayout(),
2636
+ net,
2637
+ p.name,
2638
+ padstack,
2639
+ position,
2640
+ rotation,
2641
+ fromlayer,
2642
+ tolayer,
2643
+ None,
2644
+ None,
2645
+ )
2646
+ padstack_instance.SetIsLayoutPin(p.is_pin)
2647
+ break
2648
+
2649
+ for void_circle in voids_to_add:
2650
+ if void_circle.type == "Circle":
2651
+ if is_ironpython: # pragma: no cover
2652
+ (
2653
+ res,
2654
+ center_x,
2655
+ center_y,
2656
+ radius,
2657
+ ) = void_circle.primitive_object.GetParameters()
2658
+ else:
2659
+ (
2660
+ res,
2661
+ center_x,
2662
+ center_y,
2663
+ radius,
2664
+ ) = void_circle.primitive_object.GetParameters(0.0, 0.0, 0.0)
2665
+ cloned_circle = self.edb_api.cell.primitive.circle.create(
2666
+ layout,
2667
+ void_circle.layer_name,
2668
+ void_circle.net,
2669
+ self.edb_value(center_x),
2670
+ self.edb_value(center_y),
2671
+ self.edb_value(radius),
2672
+ )
2673
+ cloned_circle.SetIsNegative(True)
2674
+ elif void_circle.type == "Polygon":
2675
+ cloned_polygon = self.edb_api.cell.primitive.polygon.create(
2676
+ layout,
2677
+ void_circle.layer_name,
2678
+ void_circle.net,
2679
+ void_circle.primitive_object.GetPolygonData(),
2680
+ )
2681
+ cloned_polygon.SetIsNegative(True)
2682
+ layers = [i for i in list(self.stackup.signal_layers.keys())]
2683
+ for layer in layers:
2684
+ layer_primitves = self.modeler.get_primitives(layer_name=layer)
2685
+ if len(layer_primitves) == 0:
2686
+ self.modeler.create_polygon(plane, layer, net_name="DUMMY")
2687
+ self.logger.info("Cutout %s created correctly", _cutout.GetName())
2688
+ id = 1
2689
+ for _setup in self.active_cell.SimulationSetups:
2690
+ # Empty string '' if coming from setup copy and don't set explicitly.
2691
+ _setup_name = _setup.GetName()
2692
+ if "GetSimSetupInfo" in dir(_setup):
2693
+ # setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object
2694
+ _hfssSimSetupInfo = _setup.GetSimSetupInfo()
2695
+ _hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup
2696
+ # Write the simulation setup info into the cell/design setup
2697
+ _setup.SetSimSetupInfo(_hfssSimSetupInfo)
2698
+ _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
2699
+ id += 1
2700
+ else:
2701
+ _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design
2702
+
2703
+ _dbCells = [_cutout]
2704
+ if output_aedb_path:
2705
+ db2 = self.create(output_aedb_path)
2706
+ if not db2.Save():
2707
+ self.logger.error("Failed to create new Edb. Check if the path already exists and remove it.")
2708
+ return []
2709
+ _dbCells = convert_py_list_to_net_list(_dbCells)
2710
+ cell_copied = db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project
2711
+ cell = list(cell_copied)[0]
2712
+ cell.SetName(os.path.basename(output_aedb_path[:-5]))
2713
+ db2.Save()
2714
+ for c in list(self.active_db.TopCircuitCells):
2715
+ if c.GetName() == _cutout.GetName():
2716
+ c.Delete()
2717
+ if open_cutout_at_end: # pragma: no cover
2718
+ _success = db2.Save()
2719
+ self._db = db2
2720
+ self.edbpath = output_aedb_path
2721
+ self._active_cell = cell
2722
+ self.edbpath = self.directory
2723
+ self._init_objects()
2724
+ else:
2725
+ db2.Close()
2726
+ source = os.path.join(output_aedb_path, "edb.def.tmp")
2727
+ target = os.path.join(output_aedb_path, "edb.def")
2728
+ self._wait_for_file_release(file_to_release=output_aedb_path)
2729
+ if os.path.exists(source) and not os.path.exists(target):
2730
+ try:
2731
+ shutil.copy(source, target)
2732
+ self.logger.warning("aedb def file manually created.")
2733
+ except:
2734
+ pass
2735
+ return [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(polygonData.GetPolygonWithoutArcs().Points)]
2736
+
2737
+ @pyedb_function_handler()
2738
+ def create_cutout_on_point_list(
2739
+ self,
2740
+ point_list,
2741
+ units="mm",
2742
+ output_aedb_path=None,
2743
+ open_cutout_at_end=True,
2744
+ nets_to_include=None,
2745
+ include_partial_instances=False,
2746
+ keep_voids=True,
2747
+ ):
2748
+ """Create a cutout on a specified shape and save it to a new AEDB file.
2749
+
2750
+ .. deprecated:: 0.6.58
2751
+ Use new method :func:`cutout` instead.
2752
+
2753
+ Parameters
2754
+ ----------
2755
+ point_list : list
2756
+ Points list defining the cutout shape.
2757
+ units : str
2758
+ Units of the point list. The default is ``"mm"``.
2759
+ output_aedb_path : str, optional
2760
+ Full path and name for the new AEDB file.
2761
+ The aedb folder shall not exist otherwise the method will return ``False``.
2762
+ open_cutout_at_end : bool, optional
2763
+ Whether to open the cutout at the end. The default is ``True``.
2764
+ nets_to_include : list, optional
2765
+ List of nets to include in the cutout. The default is ``None``, in
2766
+ which case all nets are included.
2767
+ include_partial_instances : bool, optional
2768
+ Whether to include padstack instances that have bounding boxes intersecting with point list polygons.
2769
+ This operation may slow down the cutout export.
2770
+ keep_voids : bool
2771
+ Boolean used for keep or not the voids intersecting the polygon used for clipping the layout.
2772
+ Default value is ``True``, ``False`` will remove the voids.
2773
+
2774
+ Returns
2775
+ -------
2776
+ bool
2777
+ ``True`` when successful, ``False`` when failed.
2778
+
2779
+ """
2780
+ warnings.warn("Use new method `cutout` instead.", DeprecationWarning)
2781
+ return self._create_cutout_on_point_list(
2782
+ point_list=point_list,
2783
+ units=units,
2784
+ output_aedb_path=output_aedb_path,
2785
+ open_cutout_at_end=open_cutout_at_end,
2786
+ nets_to_include=nets_to_include,
2787
+ include_partial_instances=include_partial_instances,
2788
+ keep_voids=keep_voids,
2789
+ )
2790
+
2791
+ @pyedb_function_handler()
2792
+ def write_export3d_option_config_file(self, path_to_output, config_dictionaries=None):
2793
+ """Write the options for a 3D export to a configuration file.
2794
+
2795
+ Parameters
2796
+ ----------
2797
+ path_to_output : str
2798
+ Full path to the configuration file to save 3D export options to.
2799
+
2800
+ config_dictionaries : dict, optional
2801
+ Configuration dictionaries. The default is ``None``.
2802
+
2803
+ """
2804
+ option_config = {
2805
+ "UNITE_NETS": 1,
2806
+ "ASSIGN_SOLDER_BALLS_AS_SOURCES": 0,
2807
+ "Q3D_MERGE_SOURCES": 0,
2808
+ "Q3D_MERGE_SINKS": 0,
2809
+ "CREATE_PORTS_FOR_PWR_GND_NETS": 0,
2810
+ "PORTS_FOR_PWR_GND_NETS": 0,
2811
+ "GENERATE_TERMINALS": 0,
2812
+ "SOLVE_CAPACITANCE": 0,
2813
+ "SOLVE_DC_RESISTANCE": 0,
2814
+ "SOLVE_DC_INDUCTANCE_RESISTANCE": 1,
2815
+ "SOLVE_AC_INDUCTANCE_RESISTANCE": 0,
2816
+ "CreateSources": 0,
2817
+ "CreateSinks": 0,
2818
+ "LAUNCH_Q3D": 0,
2819
+ "LAUNCH_HFSS": 0,
2820
+ }
2821
+ if config_dictionaries:
2822
+ for el, val in config_dictionaries.items():
2823
+ option_config[el] = val
2824
+ with open(os.path.join(path_to_output, "options.config"), "w") as f:
2825
+ for el, val in option_config.items():
2826
+ f.write(el + " " + str(val) + "\n")
2827
+ return os.path.join(path_to_output, "options.config")
2828
+
2829
+ @pyedb_function_handler()
2830
+ def export_hfss(
2831
+ self,
2832
+ path_to_output,
2833
+ net_list=None,
2834
+ num_cores=None,
2835
+ aedt_file_name=None,
2836
+ hidden=False,
2837
+ ):
2838
+ """Export EDB to HFSS.
2839
+
2840
+ Parameters
2841
+ ----------
2842
+ path_to_output : str
2843
+ Full path and name for saving the AEDT file.
2844
+ net_list : list, optional
2845
+ List of nets to export if only certain ones are to be exported.
2846
+ The default is ``None``, in which case all nets are eported.
2847
+ num_cores : int, optional
2848
+ Number of cores to use for the export. The default is ``None``.
2849
+ aedt_file_name : str, optional
2850
+ Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``,
2851
+ in which case the default name is used.
2852
+ hidden : bool, optional
2853
+ Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden.
2854
+
2855
+ Returns
2856
+ -------
2857
+ str
2858
+ Full path to the AEDT file.
2859
+
2860
+ Examples
2861
+ --------
2862
+
2863
+ >>> from pyedb.dotnet.edb import Edb
2864
+ >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2023.2")
2865
+
2866
+ >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
2867
+ >>> edb.write_export3d_option_config_file(r"C:\temp", options_config)
2868
+ >>> edb.export_hfss(r"C:\temp")
2869
+ """
2870
+ siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path)
2871
+ return siwave_s.export_3d_cad("HFSS", path_to_output, net_list, num_cores, aedt_file_name, hidden=hidden)
2872
+
2873
+ @pyedb_function_handler()
2874
+ def export_q3d(
2875
+ self,
2876
+ path_to_output,
2877
+ net_list=None,
2878
+ num_cores=None,
2879
+ aedt_file_name=None,
2880
+ hidden=False,
2881
+ ):
2882
+ """Export EDB to Q3D.
2883
+
2884
+ Parameters
2885
+ ----------
2886
+ path_to_output : str
2887
+ Full path and name for saving the AEDT file.
2888
+ net_list : list, optional
2889
+ List of nets to export only if certain ones are to be exported.
2890
+ The default is ``None``, in which case all nets are eported.
2891
+ num_cores : int, optional
2892
+ Number of cores to use for the export. The default is ``None``.
2893
+ aedt_file_name : str, optional
2894
+ Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``,
2895
+ in which case the default name is used.
2896
+ hidden : bool, optional
2897
+ Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden.
2898
+
2899
+ Returns
2900
+ -------
2901
+ str
2902
+ Full path to the AEDT file.
2903
+
2904
+ Examples
2905
+ --------
2906
+
2907
+ >>> from pyedb.dotnet.edb import Edb
2908
+ >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2")
2909
+ >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
2910
+ >>> edb.write_export3d_option_config_file(r"C:\temp", options_config)
2911
+ >>> edb.export_q3d(r"C:\temp")
2912
+ """
2913
+
2914
+ siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path)
2915
+ return siwave_s.export_3d_cad(
2916
+ "Q3D",
2917
+ path_to_output,
2918
+ net_list,
2919
+ num_cores=num_cores,
2920
+ aedt_file_name=aedt_file_name,
2921
+ hidden=hidden,
2922
+ )
2923
+
2924
+ @pyedb_function_handler()
2925
+ def export_maxwell(
2926
+ self,
2927
+ path_to_output,
2928
+ net_list=None,
2929
+ num_cores=None,
2930
+ aedt_file_name=None,
2931
+ hidden=False,
2932
+ ):
2933
+ """Export EDB to Maxwell 3D.
2934
+
2935
+ Parameters
2936
+ ----------
2937
+ path_to_output : str
2938
+ Full path and name for saving the AEDT file.
2939
+ net_list : list, optional
2940
+ List of nets to export only if certain ones are to be
2941
+ exported. The default is ``None``, in which case all nets are exported.
2942
+ num_cores : int, optional
2943
+ Number of cores to use for the export. The default is ``None.``
2944
+ aedt_file_name : str, optional
2945
+ Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``,
2946
+ in which case the default name is used.
2947
+ hidden : bool, optional
2948
+ Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden.
2949
+
2950
+ Returns
2951
+ -------
2952
+ str
2953
+ Full path to the AEDT file.
2954
+
2955
+ Examples
2956
+ --------
2957
+
2958
+ >>> from pyedb.dotnet.edb import Edb
2959
+
2960
+ >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2")
2961
+
2962
+ >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
2963
+ >>> edb.write_export3d_option_config_file(r"C:\temp", options_config)
2964
+ >>> edb.export_maxwell(r"C:\temp")
2965
+ """
2966
+ siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path)
2967
+ return siwave_s.export_3d_cad(
2968
+ "Maxwell",
2969
+ path_to_output,
2970
+ net_list,
2971
+ num_cores=num_cores,
2972
+ aedt_file_name=aedt_file_name,
2973
+ hidden=hidden,
2974
+ )
2975
+
2976
+ @pyedb_function_handler()
2977
+ def solve_siwave(self):
2978
+ """Close EDB and solve it with Siwave.
2979
+
2980
+ Returns
2981
+ -------
2982
+ str
2983
+ Siwave project path.
2984
+ """
2985
+ process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion)
2986
+ try:
2987
+ self.close()
2988
+ except:
2989
+ pass
2990
+ process.solve()
2991
+ return self.edbpath[:-5] + ".siw"
2992
+
2993
+ @pyedb_function_handler()
2994
+ def export_siwave_dc_results(
2995
+ self,
2996
+ siwave_project,
2997
+ solution_name,
2998
+ output_folder=None,
2999
+ html_report=True,
3000
+ vias=True,
3001
+ voltage_probes=True,
3002
+ current_sources=True,
3003
+ voltage_sources=True,
3004
+ power_tree=True,
3005
+ loop_res=True,
3006
+ ):
3007
+ """Close EDB and solve it with Siwave.
3008
+
3009
+ Parameters
3010
+ ----------
3011
+ siwave_project : str
3012
+ Siwave full project name.
3013
+ solution_name : str
3014
+ Siwave DC Analysis name.
3015
+ output_folder : str, optional
3016
+ Ouptu folder where files will be downloaded.
3017
+ html_report : bool, optional
3018
+ Either if generate or not html report. Default is `True`.
3019
+ vias : bool, optional
3020
+ Either if generate or not vias report. Default is `True`.
3021
+ voltage_probes : bool, optional
3022
+ Either if generate or not voltage probe report. Default is `True`.
3023
+ current_sources : bool, optional
3024
+ Either if generate or not current source report. Default is `True`.
3025
+ voltage_sources : bool, optional
3026
+ Either if generate or not voltage source report. Default is `True`.
3027
+ power_tree : bool, optional
3028
+ Either if generate or not power tree image. Default is `True`.
3029
+ loop_res : bool, optional
3030
+ Either if generate or not loop resistance report. Default is `True`.
3031
+
3032
+ Returns
3033
+ -------
3034
+ list
3035
+ List of files generated.
3036
+ """
3037
+ process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion)
3038
+ try:
3039
+ self.close()
3040
+ except:
3041
+ pass
3042
+ return process.export_dc_report(
3043
+ siwave_project,
3044
+ solution_name,
3045
+ output_folder,
3046
+ html_report,
3047
+ vias,
3048
+ voltage_probes,
3049
+ current_sources,
3050
+ voltage_sources,
3051
+ power_tree,
3052
+ loop_res,
3053
+ hidden=True,
3054
+ )
3055
+
3056
+ @pyedb_function_handler()
3057
+ def variable_exists(self, variable_name):
3058
+ """Check if a variable exists or not.
3059
+
3060
+ Returns
3061
+ -------
3062
+ tuple of bool and VariableServer
3063
+ It returns a booleand to check if the variable exists and the variable
3064
+ server that should contain the variable.
3065
+ """
3066
+ if "$" in variable_name:
3067
+ if variable_name.index("$") == 0:
3068
+ var_server = self.active_db.GetVariableServer()
3069
+
3070
+ else:
3071
+ var_server = self.active_cell.GetVariableServer()
3072
+
3073
+ else:
3074
+ var_server = self.active_cell.GetVariableServer()
3075
+
3076
+ variables = var_server.GetAllVariableNames()
3077
+ if variable_name in list(variables):
3078
+ return True, var_server
3079
+ return False, var_server
3080
+
3081
+ @pyedb_function_handler()
3082
+ def get_variable(self, variable_name):
3083
+ """Return Variable Value if variable exists.
3084
+
3085
+ Parameters
3086
+ ----------
3087
+ variable_name
3088
+
3089
+ Returns
3090
+ -------
3091
+ :class:`pyedb.dotnet.edb_core.edb_data.edbvalue.EdbValue`
3092
+ """
3093
+ var_server = self.variable_exists(variable_name)
3094
+ if var_server[0]:
3095
+ tuple_value = var_server[1].GetVariableValue(variable_name)
3096
+ return EdbValue(tuple_value[1])
3097
+ self.logger.info("Variable %s doesn't exists.", variable_name)
3098
+ return None
3099
+
3100
+ @pyedb_function_handler()
3101
+ def add_project_variable(self, variable_name, variable_value):
3102
+ """Add a variable to edb database (project). The variable will have the prefix `$`.
3103
+
3104
+ ..note::
3105
+ User can use also the setitem to create or assign a variable. See example below.
3106
+
3107
+ Parameters
3108
+ ----------
3109
+ variable_name : str
3110
+ Name of the variable. Name can be provided without ``$`` prefix.
3111
+ variable_value : str, float
3112
+ Value of the variable with units.
3113
+
3114
+ Returns
3115
+ -------
3116
+ tuple
3117
+ Tuple containing the ``AddVariable`` result and variable server.
3118
+
3119
+ Examples
3120
+ --------
3121
+
3122
+ >>> from pyedb.dotnet.edb import Edb
3123
+ >>> edb_app = Edb()
3124
+ >>> boolean_1, ant_length = edb_app.add_project_variable("my_local_variable", "1cm")
3125
+ >>> print(edb_app["$my_local_variable"]) #using getitem
3126
+ >>> edb_app["$my_local_variable"] = "1cm" #using setitem
3127
+
3128
+ """
3129
+ if not variable_name.startswith("$"):
3130
+ variable_name = "${}".format(variable_name)
3131
+ return self.add_design_variable(variable_name=variable_name, variable_value=variable_value)
3132
+
3133
+ @pyedb_function_handler()
3134
+ def add_design_variable(self, variable_name, variable_value, is_parameter=False):
3135
+ """Add a variable to edb. The variable can be a design one or a project variable (using ``$`` prefix).
3136
+
3137
+ ..note::
3138
+ User can use also the setitem to create or assign a variable. See example below.
3139
+
3140
+ Parameters
3141
+ ----------
3142
+ variable_name : str
3143
+ Name of the variable. To added the variable as a project variable, the name
3144
+ must begin with ``$``.
3145
+ variable_value : str, float
3146
+ Value of the variable with units.
3147
+ is_parameter : bool, optional
3148
+ Whether to add the variable as a local variable. The default is ``False``.
3149
+ When ``True``, the variable is added as a parameter default.
3150
+
3151
+ Returns
3152
+ -------
3153
+ tuple
3154
+ Tuple containing the ``AddVariable`` result and variable server.
3155
+
3156
+ Examples
3157
+ --------
3158
+
3159
+ >>> from pyedb.dotnet.edb import Edb
3160
+ >>> edb_app = Edb()
3161
+ >>> boolean_1, ant_length = edb_app.add_design_variable("my_local_variable", "1cm")
3162
+ >>> print(edb_app["my_local_variable"]) #using getitem
3163
+ >>> edb_app["my_local_variable"] = "1cm" #using setitem
3164
+ >>> boolean_2, para_length = edb_app.change_design_variable_value("my_parameter", "1m", is_parameter=True
3165
+ >>> boolean_3, project_length = edb_app.change_design_variable_value("$my_project_variable", "1m")
3166
+
3167
+
3168
+ """
3169
+ var_server = self.variable_exists(variable_name)
3170
+ if not var_server[0]:
3171
+ var_server[1].AddVariable(variable_name, self.edb_value(variable_value), is_parameter)
3172
+ return True, var_server[1]
3173
+ self.logger.error("Variable %s already exists.", variable_name)
3174
+ return False, var_server[1]
3175
+
3176
+ @pyedb_function_handler()
3177
+ def change_design_variable_value(self, variable_name, variable_value):
3178
+ """Change a variable value.
3179
+
3180
+ ..note::
3181
+ User can use also the getitem to read the variable value. See example below.
3182
+
3183
+ Parameters
3184
+ ----------
3185
+ variable_name : str
3186
+ Name of the variable.
3187
+ variable_value : str, float
3188
+ Value of the variable with units.
3189
+
3190
+ Returns
3191
+ -------
3192
+ tuple
3193
+ Tuple containing the ``SetVariableValue`` result and variable server.
3194
+
3195
+ Examples
3196
+ --------
3197
+
3198
+ >>> from pyedb.dotnet.edb import Edb
3199
+ >>> edb_app = Edb()
3200
+ >>> boolean, ant_length = edb_app.add_design_variable("ant_length", "1cm")
3201
+ >>> boolean, ant_length = edb_app.change_design_variable_value("ant_length", "1m")
3202
+ >>> print(edb_app["ant_length"]) #using getitem
3203
+ """
3204
+ var_server = self.variable_exists(variable_name)
3205
+ if var_server[0]:
3206
+ var_server[1].SetVariableValue(variable_name, self.edb_value(variable_value))
3207
+ return True, var_server[1]
3208
+ self.logger.error("Variable %s does not exists.", variable_name)
3209
+ return False, var_server[1]
3210
+
3211
+ @pyedb_function_handler()
3212
+ def get_bounding_box(self):
3213
+ """Get the layout bounding box.
3214
+
3215
+ Returns
3216
+ -------
3217
+ list of list of double
3218
+ Bounding box as a [lower-left X, lower-left Y], [upper-right X, upper-right Y]) pair in meters.
3219
+ """
3220
+ bbox = self.edbutils.HfssUtilities.GetBBox(self.active_layout)
3221
+ return [
3222
+ [bbox.Item1.X.ToDouble(), bbox.Item1.Y.ToDouble()],
3223
+ [bbox.Item2.X.ToDouble(), bbox.Item2.Y.ToDouble()],
3224
+ ]
3225
+
3226
+ @pyedb_function_handler()
3227
+ def build_simulation_project(self, simulation_setup):
3228
+ # type: (SimulationConfiguration) -> bool
3229
+ """Build a ready-to-solve simulation project.
3230
+
3231
+ Parameters
3232
+ ----------
3233
+ simulation_setup : :class:`pyedb.dotnet.edb_core.edb_data.simulation_configuration.SimulationConfiguration`.
3234
+ SimulationConfiguration object that can be instantiated or directly loaded with a
3235
+ configuration file.
3236
+
3237
+ Returns
3238
+ -------
3239
+ bool
3240
+ ``True`` when successful, False when ``Failed``.
3241
+
3242
+ Examples
3243
+ --------
3244
+
3245
+ >>> from pyedb.dotnet.edb import Edb
3246
+ >>> from pyedb.dotnet.edb_core.edb_data.simulation_configuration import SimulationConfiguration
3247
+ >>> config_file = path_configuration_file
3248
+ >>> source_file = path_to_edb_folder
3249
+ >>> edb = Edb(source_file)
3250
+ >>> sim_setup = SimulationConfiguration(config_file)
3251
+ >>> edb.build_simulation_project(sim_setup)
3252
+ >>> edb.save_edb()
3253
+ >>> edb.close_edb()
3254
+ """
3255
+ self.logger.info("Building simulation project.")
3256
+ legacy_name = self.edbpath
3257
+ if simulation_setup.output_aedb:
3258
+ self.save_edb_as(simulation_setup.output_aedb)
3259
+ try:
3260
+ if simulation_setup.signal_layer_etching_instances:
3261
+ for layer in simulation_setup.signal_layer_etching_instances:
3262
+ if layer in self.stackup.layers:
3263
+ idx = simulation_setup.signal_layer_etching_instances.index(layer)
3264
+ if len(simulation_setup.etching_factor_instances) > idx:
3265
+ self.stackup[layer].etch_factor = float(simulation_setup.etching_factor_instances[idx])
3266
+
3267
+ if not simulation_setup.signal_nets and simulation_setup.components:
3268
+ nets_to_include = []
3269
+ pnets = list(self.nets.power.keys())[:]
3270
+ for el in simulation_setup.components:
3271
+ nets_to_include.append([i for i in self.components[el].nets if i not in pnets])
3272
+ simulation_setup.signal_nets = [
3273
+ i
3274
+ for i in list(set.intersection(*map(set, nets_to_include)))
3275
+ if i not in simulation_setup.power_nets and i != ""
3276
+ ]
3277
+ self.nets.classify_nets(simulation_setup.power_nets, simulation_setup.signal_nets)
3278
+ if not simulation_setup.power_nets or not simulation_setup.signal_nets:
3279
+ self.logger.info("Disabling cutout as no signals or power nets have been defined.")
3280
+ simulation_setup.do_cutout_subdesign = False
3281
+ if simulation_setup.do_cutout_subdesign:
3282
+ self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type))
3283
+ if simulation_setup.use_default_cutout:
3284
+ old_cell_name = self.active_cell.GetName()
3285
+ if self.cutout(
3286
+ signal_list=simulation_setup.signal_nets,
3287
+ reference_list=simulation_setup.power_nets,
3288
+ expansion_size=simulation_setup.cutout_subdesign_expansion,
3289
+ use_round_corner=simulation_setup.cutout_subdesign_round_corner,
3290
+ extent_type=simulation_setup.cutout_subdesign_type,
3291
+ use_pyaedt_cutout=False,
3292
+ use_pyaedt_extent_computing=False,
3293
+ ):
3294
+ self.logger.info("Cutout processed.")
3295
+ old_cell = self.active_cell.FindByName(
3296
+ self.db,
3297
+ self.edb_api.cell.CellType.CircuitCell,
3298
+ old_cell_name,
3299
+ )
3300
+ if old_cell:
3301
+ old_cell.Delete()
3302
+ else: # pragma: no cover
3303
+ self.logger.error("Cutout failed.")
3304
+ else:
3305
+ self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type))
3306
+ self.cutout(
3307
+ signal_list=simulation_setup.signal_nets,
3308
+ reference_list=simulation_setup.power_nets,
3309
+ expansion_size=simulation_setup.cutout_subdesign_expansion,
3310
+ use_round_corner=simulation_setup.cutout_subdesign_round_corner,
3311
+ extent_type=simulation_setup.cutout_subdesign_type,
3312
+ use_pyaedt_cutout=True,
3313
+ use_pyaedt_extent_computing=True,
3314
+ remove_single_pin_components=True,
3315
+ )
3316
+ self.logger.info("Cutout processed.")
3317
+ else:
3318
+ if simulation_setup.include_only_selected_nets:
3319
+ included_nets = simulation_setup.signal_nets + simulation_setup.power_nets
3320
+ nets_to_remove = [
3321
+ net.name for net in list(self.nets.nets.values()) if not net.name in included_nets
3322
+ ]
3323
+ self.nets.delete(nets_to_remove)
3324
+ self.logger.info("Deleting existing ports.")
3325
+ map(lambda port: port.Delete(), self.layout.terminals)
3326
+ map(lambda pg: pg.Delete(), self.layout.pin_groups)
3327
+ if simulation_setup.solver_type == SolverType.Hfss3dLayout:
3328
+ if simulation_setup.generate_excitations:
3329
+ self.logger.info("Creating HFSS ports for signal nets.")
3330
+ source_type = SourceType.CoaxPort
3331
+ if not simulation_setup.generate_solder_balls:
3332
+ source_type = SourceType.CircPort
3333
+ for cmp in simulation_setup.components:
3334
+ if isinstance(cmp, str): # keep legacy component
3335
+ self.components.create_port_on_component(
3336
+ cmp,
3337
+ net_list=simulation_setup.signal_nets,
3338
+ do_pingroup=False,
3339
+ reference_net=simulation_setup.power_nets,
3340
+ port_type=source_type,
3341
+ )
3342
+ elif isinstance(cmp, dict):
3343
+ if "refdes" in cmp:
3344
+ if not "solder_balls_height" in cmp: # pragma no cover
3345
+ cmp["solder_balls_height"] = None
3346
+ if not "solder_balls_size" in cmp: # pragma no cover
3347
+ cmp["solder_balls_size"] = None
3348
+ cmp["solder_balls_mid_size"] = None
3349
+ if not "solder_balls_mid_size" in cmp: # pragma no cover
3350
+ cmp["solder_balls_mid_size"] = None
3351
+ self.components.create_port_on_component(
3352
+ cmp["refdes"],
3353
+ net_list=simulation_setup.signal_nets,
3354
+ do_pingroup=False,
3355
+ reference_net=simulation_setup.power_nets,
3356
+ port_type=source_type,
3357
+ solder_balls_height=cmp["solder_balls_height"],
3358
+ solder_balls_size=cmp["solder_balls_size"],
3359
+ solder_balls_mid_size=cmp["solder_balls_mid_size"],
3360
+ )
3361
+ if simulation_setup.generate_solder_balls and not self.hfss.set_coax_port_attributes(
3362
+ simulation_setup
3363
+ ): # pragma: no cover
3364
+ self.logger.error("Failed to configure coaxial port attributes.")
3365
+ self.logger.info("Number of ports: {}".format(self.hfss.get_ports_number()))
3366
+ self.logger.info("Configure HFSS extents.")
3367
+ if (
3368
+ simulation_setup.generate_solder_balls and simulation_setup.trim_reference_size
3369
+ ): # pragma: no cover
3370
+ self.logger.info(
3371
+ "Trimming the reference plane for coaxial ports: {0}".format(
3372
+ bool(simulation_setup.trim_reference_size)
3373
+ )
3374
+ )
3375
+ self.hfss.trim_component_reference_size(simulation_setup) # pragma: no cover
3376
+ self.hfss.configure_hfss_extents(simulation_setup)
3377
+ if not self.hfss.configure_hfss_analysis_setup(simulation_setup):
3378
+ self.logger.error("Failed to configure HFSS simulation setup.")
3379
+ if simulation_setup.solver_type == SolverType.SiwaveSYZ:
3380
+ if simulation_setup.generate_excitations:
3381
+ for cmp in simulation_setup.components:
3382
+ if isinstance(cmp, str): # keep legacy
3383
+ self.components.create_port_on_component(
3384
+ cmp,
3385
+ net_list=simulation_setup.signal_nets,
3386
+ do_pingroup=simulation_setup.do_pingroup,
3387
+ reference_net=simulation_setup.power_nets,
3388
+ port_type=SourceType.CircPort,
3389
+ )
3390
+ elif isinstance(cmp, dict):
3391
+ if "refdes" in cmp: # pragma no cover
3392
+ self.components.create_port_on_component(
3393
+ cmp["refdes"],
3394
+ net_list=simulation_setup.signal_nets,
3395
+ do_pingroup=simulation_setup.do_pingroup,
3396
+ reference_net=simulation_setup.power_nets,
3397
+ port_type=SourceType.CircPort,
3398
+ )
3399
+ self.logger.info("Configuring analysis setup.")
3400
+ if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover
3401
+ self.logger.error("Failed to configure Siwave simulation setup.")
3402
+ if simulation_setup.solver_type == SolverType.SiwaveDC:
3403
+ if simulation_setup.generate_excitations:
3404
+ self.components.create_source_on_component(simulation_setup.sources)
3405
+ if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover
3406
+ self.logger.error("Failed to configure Siwave simulation setup.")
3407
+ self.padstacks.check_and_fix_via_plating()
3408
+ self.save_edb()
3409
+ if not simulation_setup.open_edb_after_build and simulation_setup.output_aedb:
3410
+ self.close_edb()
3411
+ self.edbpath = legacy_name
3412
+ self.open_edb()
3413
+ return True
3414
+ except:
3415
+ return False
3416
+
3417
+ @pyedb_function_handler()
3418
+ def get_statistics(self, compute_area=False):
3419
+ """Get the EDBStatistics object.
3420
+
3421
+ Returns
3422
+ -------
3423
+ EDBStatistics object from the loaded layout.
3424
+ """
3425
+ return self.modeler.get_layout_statistics(evaluate_area=compute_area, net_list=None)
3426
+
3427
+ @pyedb_function_handler()
3428
+ def are_port_reference_terminals_connected(self, common_reference=None):
3429
+ """Check if all terminal references in design are connected.
3430
+ If the reference nets are different, there is no hope for the terminal references to be connected.
3431
+ After we have identified a common reference net we need to loop the terminals again to get
3432
+ the correct reference terminals that uses that net.
3433
+
3434
+ Parameters
3435
+ ----------
3436
+ common_reference : str, optional
3437
+ Common Reference name. If ``None`` it will be searched in ports terminal.
3438
+ If a string is passed then all excitations must have such reference assigned.
3439
+
3440
+ Returns
3441
+ -------
3442
+ bool
3443
+ Either if the ports are connected to reference_name or not.
3444
+
3445
+ Examples
3446
+ --------
3447
+ >>> from pyedb.dotnet.edb import Edb
3448
+ >>>edb = Edb()
3449
+ >>> edb.hfss.create_edge_port_vertical(prim_1_id, ["-66mm", "-4mm"], "port_ver")
3450
+ >>> edb.hfss.create_edge_port_horizontal(
3451
+ >>> ... prim_1_id, ["-60mm", "-4mm"], prim_2_id, ["-59mm", "-4mm"], "port_hori", 30, "Lower"
3452
+ >>> ... )
3453
+ >>> edb.hfss.create_wave_port(traces[0].id, trace_paths[0][0], "wave_port")
3454
+ >>> edb.cutout(["Net1"])
3455
+ >>> assert edb.are_port_reference_terminals_connected()
3456
+ """
3457
+ all_sources = [i for i in self.excitations.values() if not isinstance(i, (WavePort, GapPort, BundleWavePort))]
3458
+ all_sources.extend([i for i in self.sources.values()])
3459
+ if not all_sources:
3460
+ return True
3461
+ self.logger.reset_timer()
3462
+ if not common_reference:
3463
+ common_reference = list(set([i.reference_net_name for i in all_sources if i.reference_net_name]))
3464
+ if len(common_reference) > 1:
3465
+ self.logger.error("More than 1 reference found.")
3466
+ return False
3467
+ if not common_reference:
3468
+ self.logger.error("No Reference found.")
3469
+ return False
3470
+
3471
+ common_reference = common_reference[0]
3472
+ all_sources = [i for i in all_sources if i.net_name != common_reference]
3473
+
3474
+ setList = [
3475
+ set(i.reference_object.get_connected_object_id_set())
3476
+ for i in all_sources
3477
+ if i.reference_object and i.reference_net_name == common_reference
3478
+ ]
3479
+ if len(setList) != len(all_sources):
3480
+ self.logger.error("No Reference found.")
3481
+ return False
3482
+ cmps = [
3483
+ i
3484
+ for i in list(self.components.resistors.values())
3485
+ if i.numpins == 2 and common_reference in i.nets and self._decompose_variable_value(i.res_value) <= 1
3486
+ ]
3487
+ cmps.extend(
3488
+ [i for i in list(self.components.inductors.values()) if i.numpins == 2 and common_reference in i.nets]
3489
+ )
3490
+
3491
+ for cmp in cmps:
3492
+ found = False
3493
+ ids = [i.GetId() for i in cmp.pinlist]
3494
+ for list_obj in setList:
3495
+ if len(set(ids).intersection(list_obj)) == 1:
3496
+ for list_obj2 in setList:
3497
+ if list_obj2 != list_obj and len(set(ids).intersection(list_obj)) == 1:
3498
+ if (ids[0] in list_obj and ids[1] in list_obj2) or (
3499
+ ids[1] in list_obj and ids[0] in list_obj2
3500
+ ):
3501
+ setList[setList.index(list_obj)] = list_obj.union(list_obj2)
3502
+ setList[setList.index(list_obj2)] = list_obj.union(list_obj2)
3503
+ found = True
3504
+ break
3505
+ if found:
3506
+ break
3507
+
3508
+ # Get the set intersections for all the ID sets.
3509
+ iDintersection = set.intersection(*setList)
3510
+ self.logger.info_timer(
3511
+ "Terminal reference primitive IDs total intersections = {}\n\n".format(len(iDintersection))
3512
+ )
3513
+
3514
+ # If the intersections are non-zero, the terminal references are connected.
3515
+ return True if len(iDintersection) > 0 else False
3516
+
3517
+ @pyedb_function_handler()
3518
+ def new_simulation_configuration(self, filename=None):
3519
+ # type: (str) -> SimulationConfiguration
3520
+ """New SimulationConfiguration Object.
3521
+
3522
+ Parameters
3523
+ ----------
3524
+ filename : str, optional
3525
+ Input config file.
3526
+
3527
+ Returns
3528
+ -------
3529
+ :class:`legacy.edb_core.edb_data.simulation_configuration.SimulationConfiguration`
3530
+ """
3531
+ return SimulationConfiguration(filename, self)
3532
+
3533
+ @property
3534
+ def setups(self):
3535
+ """Get the dictionary of all EDB HFSS and SIwave setups.
3536
+
3537
+ Returns
3538
+ -------
3539
+ Dict[str, :class:`legacy.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`] or
3540
+ Dict[str, :class:`legacy.edb_core.edb_data.siwave_simulation_setup_data.SiwaveDCSimulationSetup`] or
3541
+ Dict[str, :class:`legacy.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`]
3542
+
3543
+ """
3544
+ setups = {}
3545
+ for i in list(self.active_cell.SimulationSetups):
3546
+ if i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kHFSS:
3547
+ setups[i.GetName()] = HfssSimulationSetup(self, i)
3548
+ elif i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kSIWave:
3549
+ setups[i.GetName()] = SiwaveSYZSimulationSetup(self, i)
3550
+ elif i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kSIWaveDCIR:
3551
+ setups[i.GetName()] = SiwaveDCSimulationSetup(self, i)
3552
+ return setups
3553
+
3554
+ @property
3555
+ def hfss_setups(self):
3556
+ """Active HFSS setup in EDB.
3557
+
3558
+ Returns
3559
+ -------
3560
+ Dict[str, :class:`legacy.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`]
3561
+
3562
+ """
3563
+ return {name: i for name, i in self.setups.items() if i.setup_type == "kHFSS"}
3564
+
3565
+ @property
3566
+ def siwave_dc_setups(self):
3567
+ """Active Siwave DC IR Setups.
3568
+
3569
+ Returns
3570
+ -------
3571
+ Dict[str, :class:`legacy.edb_core.edb_data.siwave_simulation_setup_data.SiwaveDCSimulationSetup`]
3572
+ """
3573
+ return {name: i for name, i in self.setups.items() if isinstance(i, SiwaveDCSimulationSetup)}
3574
+
3575
+ @property
3576
+ def siwave_ac_setups(self):
3577
+ """Active Siwave SYZ setups.
3578
+
3579
+ Returns
3580
+ -------
3581
+ Dict[str, :class:`legacy.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`]
3582
+ """
3583
+ return {name: i for name, i in self.setups.items() if isinstance(i, SiwaveSYZSimulationSetup)}
3584
+
3585
+ def create_hfss_setup(self, name=None):
3586
+ """Create an HFSS simulation setup from a template.
3587
+
3588
+ Parameters
3589
+ ----------
3590
+ name : str, optional
3591
+ Setup name.
3592
+
3593
+ Returns
3594
+ -------
3595
+ :class:`legacy.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`
3596
+
3597
+ Examples
3598
+ --------
3599
+ >>> from pyedb.dotnet.edb import Edb
3600
+ >>> edbapp = Edb()
3601
+ >>> setup1 = edbapp.create_hfss_setup("setup1")
3602
+ >>> setup1.hfss_port_settings.max_delta_z0 = 0.5
3603
+ """
3604
+ if name in self.setups:
3605
+ return False
3606
+ setup = HfssSimulationSetup(self).create(name)
3607
+ return setup
3608
+
3609
+ @pyedb_function_handler()
3610
+ def create_siwave_syz_setup(self, name=None):
3611
+ """Create a setup from a template.
3612
+
3613
+ Parameters
3614
+ ----------
3615
+ name : str, optional
3616
+ Setup name.
3617
+
3618
+ Returns
3619
+ -------
3620
+ :class:`pyedb.dotnet.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`
3621
+
3622
+ Examples
3623
+ --------
3624
+ >>> from pyedb.dotnet.edb import Edb
3625
+ >>> edbapp = Edb()
3626
+ >>> setup1 = edbapp.create_siwave_syz_setup("setup1")
3627
+ >>> setup1.add_frequency_sweep(frequency_sweep=[
3628
+ ... ["linear count", "0", "1kHz", 1],
3629
+ ... ["log scale", "1kHz", "0.1GHz", 10],
3630
+ ... ["linear scale", "0.1GHz", "10GHz", "0.1GHz"],
3631
+ ... ])
3632
+ """
3633
+ if not name:
3634
+ name = generate_unique_name("Siwave_SYZ")
3635
+ if name in self.setups:
3636
+ return False
3637
+ SiwaveSYZSimulationSetup(self).create(name)
3638
+ return self.setups[name]
3639
+
3640
+ @pyedb_function_handler()
3641
+ def create_siwave_dc_setup(self, name=None):
3642
+ """Create a setup from a template.
3643
+
3644
+ Parameters
3645
+ ----------
3646
+ name : str, optional
3647
+ Setup name.
3648
+
3649
+ Returns
3650
+ -------
3651
+ :class:`legacy.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`
3652
+
3653
+ Examples
3654
+ --------
3655
+ >>> from pyedb.dotnet.edb import Edb
3656
+ >>> edbapp = Edb()
3657
+ >>> setup1 = edbapp.create_siwave_dc_setup("setup1")
3658
+ >>> setup1.mesh_bondwires = True
3659
+
3660
+ """
3661
+ if not name:
3662
+ name = generate_unique_name("Siwave_DC")
3663
+ if name in self.setups:
3664
+ return False
3665
+ setup = SiwaveDCSimulationSetup(self).create(name)
3666
+ return setup
3667
+
3668
+ @pyedb_function_handler()
3669
+ def calculate_initial_extent(self, expansion_factor):
3670
+ """Compute a float representing the larger number between the dielectric thickness or trace width
3671
+ multiplied by the nW factor. The trace width search is limited to nets with ports attached.
3672
+
3673
+ Parameters
3674
+ ----------
3675
+ expansion_factor : float
3676
+ Value for the width multiplier (nW factor).
3677
+
3678
+ Returns
3679
+ -------
3680
+ float
3681
+ """
3682
+ nets = []
3683
+ for port in self.excitations.values():
3684
+ nets.append(port.net_name)
3685
+ for port in self.sources.values():
3686
+ nets.append(port.net_name)
3687
+ nets = list(set(nets))
3688
+ max_width = 0
3689
+ for net in nets:
3690
+ for primitive in self.nets[net].primitives:
3691
+ if primitive.type == "Path":
3692
+ max_width = max(max_width, primitive.width)
3693
+
3694
+ for layer in list(self.stackup.dielectric_layers.values()):
3695
+ max_width = max(max_width, layer.thickness)
3696
+
3697
+ max_width = max_width * expansion_factor
3698
+ self.logger.info("The W factor is {}, The initial extent = {:e}".format(expansion_factor, max_width))
3699
+ return max_width
3700
+
3701
+ @pyedb_function_handler()
3702
+ def copy_zones(self, working_directory=None):
3703
+ """Copy multizone EDB project to one new edb per zone.
3704
+
3705
+ Parameters
3706
+ ----------
3707
+ working_directory : str
3708
+ Directory path where all EDB project are copied, if empty will use the current EDB project.
3709
+
3710
+ Returns
3711
+ -------
3712
+ dict[str](int, EDB PolygonData)
3713
+ Return a dictionary with edb path as key and tuple Zone Id as first item and EDB polygon Data defining
3714
+ the region as second item.
3715
+
3716
+ """
3717
+ if working_directory:
3718
+ if not os.path.isdir(working_directory):
3719
+ os.mkdir(working_directory)
3720
+ else:
3721
+ shutil.rmtree(working_directory)
3722
+ os.mkdir(working_directory)
3723
+ else:
3724
+ working_directory = os.path.dirname(self.edbpath)
3725
+ zone_primitives = list(self.layout.zone_primitives)
3726
+ zone_ids = list(self.stackup._layer_collection.GetZoneIds())
3727
+ edb_zones = {}
3728
+ if not self.setups:
3729
+ self.siwave.add_siwave_syz_analysis()
3730
+ self.save_edb()
3731
+ for zone_primitive in zone_primitives:
3732
+ edb_zone_path = os.path.join(
3733
+ working_directory,
3734
+ "{}_{}".format(zone_primitive.GetId(), os.path.basename(self.edbpath)),
3735
+ )
3736
+ shutil.copytree(self.edbpath, edb_zone_path)
3737
+ poly_data = zone_primitive.GetPolygonData()
3738
+ if self.version[0] >= 10:
3739
+ edb_zones[edb_zone_path] = (zone_primitive.GetZoneId(), poly_data)
3740
+ elif len(zone_primitives) == len(zone_ids):
3741
+ edb_zones[edb_zone_path] = (zone_ids[0], poly_data)
3742
+ else:
3743
+ self.logger.info(
3744
+ "Number of zone primitives is not equal to zone number. Zone information will be lost."
3745
+ "Use Ansys 2024 R1 or later."
3746
+ )
3747
+ edb_zones[edb_zone_path] = (-1, poly_data)
3748
+ return edb_zones
3749
+
3750
+ @pyedb_function_handler()
3751
+ def cutout_multizone_layout(self, zone_dict, common_reference_net=None):
3752
+ """Create a multizone project cutout.
3753
+
3754
+ Parameters
3755
+ ----------
3756
+ zone_dict : dict[str](EDB PolygonData)
3757
+ Dictionary with EDB path as key and EDB PolygonData as value defining the zone region.
3758
+ This dictionary is returned from the command copy_zones():
3759
+ >>> edb = Edb(edb_file)
3760
+ >>> zone_dict = edb.copy_zones(r"C:\Temp\test")
3761
+
3762
+ common_reference_net : str
3763
+ the common reference net name. This net name must be provided to provide a valid project.
3764
+
3765
+ Returns
3766
+ -------
3767
+ dict[str][str] , list of str
3768
+ first dictionary defined_ports with edb name as key and existing port name list as value. Those ports are the
3769
+ ones defined before processing the multizone clipping.
3770
+ second is the list of connected port.
3771
+
3772
+ """
3773
+ terminals = {}
3774
+ defined_ports = {}
3775
+ project_connexions = None
3776
+ for edb_path, zone_info in zone_dict.items():
3777
+ edb = Edb(edbversion=self.edbversion, edbpath=edb_path)
3778
+ edb.cutout(
3779
+ use_pyaedt_cutout=True,
3780
+ custom_extent=zone_info[1],
3781
+ open_cutout_at_end=True,
3782
+ )
3783
+ if not zone_info[0] == -1:
3784
+ layers_to_remove = [
3785
+ lay.name for lay in list(edb.stackup.layers.values()) if not lay._edb_layer.IsInZone(zone_info[0])
3786
+ ]
3787
+ for layer in layers_to_remove:
3788
+ edb.stackup.remove_layer(layer)
3789
+ edb.stackup.stackup_mode = "Laminate"
3790
+ edb.cutout(
3791
+ use_pyaedt_cutout=True,
3792
+ custom_extent=zone_info[1],
3793
+ open_cutout_at_end=True,
3794
+ )
3795
+ edb.active_cell.SetName(os.path.splitext(os.path.basename(edb_path))[0])
3796
+ if common_reference_net:
3797
+ signal_nets = list(self.nets.signal.keys())
3798
+ defined_ports[os.path.splitext(os.path.basename(edb_path))[0]] = list(edb.excitations.keys())
3799
+ edb_terminals_info = edb.hfss.create_vertical_circuit_port_on_clipped_traces(
3800
+ nets=signal_nets,
3801
+ reference_net=common_reference_net,
3802
+ user_defined_extent=zone_info[1],
3803
+ )
3804
+ if edb_terminals_info:
3805
+ terminals[os.path.splitext(os.path.basename(edb_path))[0]] = edb_terminals_info
3806
+ project_connexions = self._get_connected_ports_from_multizone_cutout(terminals)
3807
+ edb.save_edb()
3808
+ edb.close_edb()
3809
+ return defined_ports, project_connexions
3810
+
3811
+ @pyedb_function_handler()
3812
+ def _get_connected_ports_from_multizone_cutout(self, terminal_info_dict):
3813
+ """Return connected port list from clipped multizone layout.
3814
+
3815
+ Parameters
3816
+ terminal_info_dict : dict[str][str]
3817
+ dictionary terminals with edb name as key and created ports name on clipped signal nets.
3818
+ Dictionary is generated by the command cutout_multizone_layout:
3819
+ >>> edb = Edb(edb_file)
3820
+ >>> edb_zones = edb.copy_zones(r"C:\Temp\test")
3821
+ >>> defined_ports, terminals_info = edb.cutout_multizone_layout(edb_zones, common_reference_net)
3822
+ >>> project_connexions = get_connected_ports(terminals_info)
3823
+
3824
+ Returns
3825
+ -------
3826
+ list[str]
3827
+ list of connected ports.
3828
+ """
3829
+ if terminal_info_dict:
3830
+ tolerance = 1e-8
3831
+ connected_ports_list = []
3832
+ project_list = list(terminal_info_dict.keys())
3833
+ project_combinations = list(combinations(range(0, len(project_list)), 2))
3834
+ for comb in project_combinations:
3835
+ terminal_set1 = terminal_info_dict[project_list[comb[0]]]
3836
+ terminal_set2 = terminal_info_dict[project_list[comb[1]]]
3837
+ project1_nets = [t[0] for t in terminal_set1]
3838
+ project2_nets = [t[0] for t in terminal_set2]
3839
+ net_with_connected_ports = list(set(project1_nets).intersection(project2_nets))
3840
+ if net_with_connected_ports:
3841
+ for net_name in net_with_connected_ports:
3842
+ project1_port_info = [term_info for term_info in terminal_set1 if term_info[0] == net_name]
3843
+ project2_port_info = [term_info for term_info in terminal_set2 if term_info[0] == net_name]
3844
+ port_list = [p[3] for p in project1_port_info] + [p[3] for p in project2_port_info]
3845
+ port_combinations = list(combinations(port_list, 2))
3846
+ for port_combination in port_combinations:
3847
+ if not port_combination[0] == port_combination[1]:
3848
+ port1 = [port for port in terminal_set1 if port[3] == port_combination[0]]
3849
+ if not port1:
3850
+ port1 = [port for port in terminal_set2 if port[3] == port_combination[0]]
3851
+ port2 = [port for port in terminal_set2 if port[3] == port_combination[1]]
3852
+ if not port2:
3853
+ port2 = [port for port in terminal_set1 if port[3] == port_combination[1]]
3854
+ port1 = port1[0]
3855
+ port2 = port2[0]
3856
+ if not port1[3] == port2[3]:
3857
+ port_distance = GeometryOperators.points_distance(port1[1:3], port2[1:3])
3858
+ if port_distance < tolerance:
3859
+ port1_connexion = None
3860
+ port2_connexion = None
3861
+ for (
3862
+ project_path,
3863
+ port_info,
3864
+ ) in terminal_info_dict.items():
3865
+ port1_map = [port for port in port_info if port[3] == port1[3]]
3866
+ if port1_map:
3867
+ port1_connexion = (
3868
+ project_path,
3869
+ port1[3],
3870
+ )
3871
+ port2_map = [port for port in port_info if port[3] == port2[3]]
3872
+ if port2_map:
3873
+ port2_connexion = (
3874
+ project_path,
3875
+ port2[3],
3876
+ )
3877
+ if port1_connexion and port2_connexion:
3878
+ if (
3879
+ not port1_connexion[0] == port2_connexion[0]
3880
+ or not port1_connexion[1] == port2_connexion[1]
3881
+ ):
3882
+ connected_ports_list.append((port1_connexion, port2_connexion))
3883
+ return connected_ports_list
3884
+
3885
+ @pyedb_function_handler
3886
+ def create_port(self, terminal, ref_terminal=None, is_circuit_port=False):
3887
+ """Create a port.
3888
+
3889
+ Parameters
3890
+ ----------
3891
+ terminal : class:`pyedb.dotnet.edb_core.edb_data.terminals.EdgeTerminal`,
3892
+ class:`pyedb.dotnet.edb_core.edb_data.terminals.PadstackInstanceTerminal`,
3893
+ class:`pyedb.dotnet.edb_core.edb_data.terminals.PointTerminal`,
3894
+ class:`pyedb.dotnet.edb_core.edb_data.terminals.PinGroupTerminal`,
3895
+ Positive terminal of the port.
3896
+ ref_terminal : class:`pyedb.dotnet.edb_core.edb_data.terminals.EdgeTerminal`,
3897
+ class:`pyedb.dotnet.edb_core.edb_data.terminals.PadstackInstanceTerminal`,
3898
+ class:`pyedb.dotnet.edb_core.edb_data.terminals.PointTerminal`,
3899
+ class:`pyedb.dotnet.edb_core.edb_data.terminals.PinGroupTerminal`,
3900
+ optional
3901
+ Negative terminal of the port.
3902
+ is_circuit_port : bool, optional
3903
+ Whether it is a circuit port. The default is ``False``.
3904
+
3905
+ Returns
3906
+ -------
3907
+ list: [:class:`pyedb.dotnet.edb_core.edb_data.ports.GapPort`,
3908
+ :class:`pyedb.dotnet.edb_core.edb_data.ports.WavePort`,].
3909
+ """
3910
+
3911
+ terminal.boundary_type = "PortBoundary"
3912
+ terminal.is_circuit_port = is_circuit_port
3913
+
3914
+ if ref_terminal:
3915
+ ref_terminal.boundary_type = "PortBoundary"
3916
+ terminal.ref_terminal = ref_terminal
3917
+
3918
+ return self.ports[terminal.name]
3919
+
3920
+ @pyedb_function_handler
3921
+ def create_voltage_probe(self, terminal, ref_terminal):
3922
+ """Create a voltage probe.
3923
+
3924
+ Parameters
3925
+ ----------
3926
+ terminal : :class:`pyedb.dotnet.edb_core.edb_data.terminals.EdgeTerminal`,
3927
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PadstackInstanceTerminal`,
3928
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PointTerminal`,
3929
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PinGroupTerminal`,
3930
+ Positive terminal of the port.
3931
+ ref_terminal : :class:`pyedb.dotnet.edb_core.edb_data.terminals.EdgeTerminal`,
3932
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PadstackInstanceTerminal`,
3933
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PointTerminal`,
3934
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PinGroupTerminal`,
3935
+ Negative terminal of the probe.
3936
+
3937
+ Returns
3938
+ -------
3939
+ pyedb.dotnet.edb_core.edb_data.terminals.Terminal
3940
+ """
3941
+ term = Terminal(self, terminal._edb_object)
3942
+ term.boundary_type = "kVoltageProbe"
3943
+
3944
+ ref_term = Terminal(self, ref_terminal._edb_object)
3945
+ ref_term.boundary_type = "kVoltageProbe"
3946
+
3947
+ term.ref_terminal = ref_terminal
3948
+ return self.probes[term.name]
3949
+
3950
+ @pyedb_function_handler
3951
+ def create_voltage_source(self, terminal, ref_terminal):
3952
+ """Create a voltage source.
3953
+
3954
+ Parameters
3955
+ ----------
3956
+ terminal : :class:`pyedb.dotnet.edb_core.edb_data.terminals.EdgeTerminal`, \
3957
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PadstackInstanceTerminal`, \
3958
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PointTerminal`, \
3959
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PinGroupTerminal`
3960
+ Positive terminal of the port.
3961
+ ref_terminal : class:`pyedb.dotnet.edb_core.edb_data.terminals.EdgeTerminal`, \
3962
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PadstackInstanceTerminal`, \
3963
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PointTerminal`, \
3964
+ :class:`pyedb.dotnet.edb_core.edb_data.terminals.PinGroupTerminal`
3965
+ Negative terminal of the source.
3966
+
3967
+ Returns
3968
+ -------
3969
+ class:`legacy.edb_core.edb_data.ports.ExcitationSources`
3970
+ """
3971
+ term = Terminal(self, terminal._edb_object)
3972
+ term.boundary_type = "kVoltageSource"
3973
+
3974
+ ref_term = Terminal(self, ref_terminal._edb_object)
3975
+ ref_term.boundary_type = "kVoltageProbe"
3976
+
3977
+ term.ref_terminal = ref_terminal
3978
+ return self.sources[term.name]
3979
+
3980
+ @pyedb_function_handler
3981
+ def create_current_source(self, terminal, ref_terminal):
3982
+ """Create a current source.
3983
+
3984
+ Parameters
3985
+ ----------
3986
+ terminal : :class:`legacy.edb_core.edb_data.terminals.EdgeTerminal`,
3987
+ :class:`legacy.edb_core.edb_data.terminals.PadstackInstanceTerminal`,
3988
+ :class:`legacy.edb_core.edb_data.terminals.PointTerminal`,
3989
+ :class:`legacy.edb_core.edb_data.terminals.PinGroupTerminal`,
3990
+ Positive terminal of the port.
3991
+ ref_terminal : class:`legacy.edb_core.edb_data.terminals.EdgeTerminal`,
3992
+ :class:`legacy.edb_core.edb_data.terminals.PadstackInstanceTerminal`,
3993
+ :class:`legacy.edb_core.edb_data.terminals.PointTerminal`,
3994
+ :class:`legacy.edb_core.edb_data.terminals.PinGroupTerminal`,
3995
+ Negative terminal of the source.
3996
+
3997
+ Returns
3998
+ -------
3999
+ :class:`legacy.edb_core.edb_data.ports.ExcitationSources`
4000
+ """
4001
+ term = Terminal(self, terminal._edb_object)
4002
+ term.boundary_type = "kCurrentSource"
4003
+
4004
+ ref_term = Terminal(self, ref_terminal._edb_object)
4005
+ ref_term.boundary_type = "kCurrentSource"
4006
+
4007
+ term.ref_terminal = ref_terminal
4008
+ return self.sources[term.name]
4009
+
4010
+ @pyedb_function_handler
4011
+ def get_point_terminal(self, name, net_name, location, layer):
4012
+ """Place a voltage probe between two points.
4013
+
4014
+ Parameters
4015
+ ----------
4016
+ name : str,
4017
+ Name of the terminal.
4018
+ net_name : str
4019
+ Name of the net.
4020
+ location : list
4021
+ Location of the terminal.
4022
+ layer : str,
4023
+ Layer of the terminal.
4024
+
4025
+ Returns
4026
+ -------
4027
+ :class:`legacy.edb_core.edb_data.terminals.PointTerminal`
4028
+ """
4029
+ from pyedb.dotnet.edb_core.edb_data.terminals import PointTerminal
4030
+
4031
+ point_terminal = PointTerminal(self)
4032
+ return point_terminal.create(name, net_name, location, layer)
4033
+
4034
+ @pyedb_function_handler
4035
+ def auto_parametrize_design(
4036
+ self,
4037
+ layers=True,
4038
+ materials=True,
4039
+ via_holes=True,
4040
+ pads=True,
4041
+ antipads=True,
4042
+ traces=True,
4043
+ layer_filter=None,
4044
+ material_filter=None,
4045
+ padstack_definition_filter=None,
4046
+ trace_net_filter=None,
4047
+ ):
4048
+ """Assign automatically design and project variables with current values.
4049
+
4050
+ Parameters
4051
+ ----------
4052
+ layers : bool, optional
4053
+ Enable layer thickness parametrization. Default value is ``True``.
4054
+ materials : bool, optional
4055
+ Enable material parametrization. Default value is ``True``.
4056
+ via_holes : bool, optional
4057
+ Enable via diameter parametrization. Default value is ``True``.
4058
+ pads : bool, optional
4059
+ Enable pads size parametrization. Default value is ``True``.
4060
+ antipads : bool, optional
4061
+ Enable anti pads size parametrization. Default value is ``True``.
4062
+ traces : bool, optional
4063
+ Enable trace width parametrization. Default value is ``True``.
4064
+ layer_filter : str, List(str), optional
4065
+ Enable layer filter. Default value is ``None``, all layers are parametrized.
4066
+ material_filter : str, List(str), optional
4067
+ Enable material filter. Default value is ``None``, all material are parametrized.
4068
+ padstack_definition_filter : str, List(str), optional
4069
+ Enable padstack definition filter. Default value is ``None``, all padsatcks are parametrized.
4070
+ trace_net_filter : str, List(str), optional
4071
+ Enable nets filter for trace width parametrization. Default value is ``None``, all layers are parametrized.
4072
+
4073
+ Returns
4074
+ -------
4075
+ List(str)
4076
+ List of all parameters name created.
4077
+ """
4078
+ parameters = []
4079
+ if layers:
4080
+ if not layer_filter:
4081
+ _layers = self.stackup.stackup_layers
4082
+ else:
4083
+ if isinstance(layer_filter, str):
4084
+ layer_filter = [layer_filter]
4085
+ _layers = {k: v for k, v in self.stackup.stackup_layers.items() if k in layer_filter}
4086
+ for layer_name, layer in _layers.items():
4087
+ thickness_variable = "${}_thick".format(layer_name)
4088
+ thickness_variable = self._clean_string_for_variable_name(thickness_variable)
4089
+ if thickness_variable not in self.variables:
4090
+ self.add_design_variable(thickness_variable, layer.thickness)
4091
+ layer.thickness = thickness_variable
4092
+ parameters.append(thickness_variable)
4093
+ if materials:
4094
+ if not material_filter:
4095
+ _materials = self.materials.materials
4096
+ else:
4097
+ _materials = {k: v for k, v in self.materials.materials.items() if k in material_filter}
4098
+ for mat_name, material in _materials.items():
4099
+ if material.conductivity < 1e4:
4100
+ epsr_variable = "$epsr_{}".format(mat_name)
4101
+ epsr_variable = self._clean_string_for_variable_name(epsr_variable)
4102
+ if epsr_variable not in self.variables:
4103
+ self.add_design_variable(epsr_variable, material.permittivity)
4104
+ material.permittivity = epsr_variable
4105
+ parameters.append(epsr_variable)
4106
+ loss_tg_variable = "$loss_tangent_{}".format(mat_name)
4107
+ loss_tg_variable = self._clean_string_for_variable_name(loss_tg_variable)
4108
+ if not loss_tg_variable in self.variables:
4109
+ self.add_design_variable(loss_tg_variable, material.loss_tangent)
4110
+ material.loss_tangent = loss_tg_variable
4111
+ parameters.append(loss_tg_variable)
4112
+ else:
4113
+ sigma_variable = "$sigma_{}".format(mat_name)
4114
+ sigma_variable = self._clean_string_for_variable_name(sigma_variable)
4115
+ if not sigma_variable in self.variables:
4116
+ self.add_design_variable(sigma_variable, material.conductivity)
4117
+ material.conductivity = sigma_variable
4118
+ parameters.append(sigma_variable)
4119
+ if traces:
4120
+ if not trace_net_filter:
4121
+ paths = self.modeler.paths
4122
+ else:
4123
+ paths = [path for path in self.modeler.paths if path.net_name in trace_net_filter]
4124
+ for path in paths:
4125
+ trace_width_variable = "trace_w_{}_{}".format(path.net_name, path.id)
4126
+ trace_width_variable = self._clean_string_for_variable_name(trace_width_variable)
4127
+ if trace_width_variable not in self.variables:
4128
+ self.add_design_variable(trace_width_variable, path.width)
4129
+ path.width = trace_width_variable
4130
+ parameters.append(trace_width_variable)
4131
+ if not padstack_definition_filter:
4132
+ used_padsatck_defs = list(
4133
+ set([padstack_inst.padstack_definition for padstack_inst in list(self.padstacks.instances.values())])
4134
+ )
4135
+ padstack_defs = {k: v for k, v in self.padstacks.definitions.items() if k in used_padsatck_defs}
4136
+ else:
4137
+ padstack_defs = {k: v for k, v in self.padstacks.definitions.items() if k in padstack_definition_filter}
4138
+ for def_name, padstack_def in padstack_defs.items():
4139
+ if not padstack_def.via_start_layer == padstack_def.via_stop_layer:
4140
+ if via_holes: # pragma no cover
4141
+ hole_variable = self._clean_string_for_variable_name("$hole_diam_{}".format(def_name))
4142
+ if hole_variable not in self.variables:
4143
+ self.add_design_variable(hole_variable, padstack_def.hole_properties[0])
4144
+ padstack_def.hole_properties = hole_variable
4145
+ parameters.append(hole_variable)
4146
+ if pads:
4147
+ for layer, pad in padstack_def.pad_by_layer.items():
4148
+ if pad.geometry_type == 1:
4149
+ pad_diameter_variable = self._clean_string_for_variable_name(
4150
+ "$pad_diam_{}_{}".format(def_name, layer)
4151
+ )
4152
+ if pad_diameter_variable not in self.variables:
4153
+ self.add_design_variable(pad_diameter_variable, pad.parameters_values[0])
4154
+ pad.parameters = {"Diameter": pad_diameter_variable}
4155
+ parameters.append(pad_diameter_variable)
4156
+ if pad.geometry_type == 2: # pragma no cover
4157
+ pad_size_variable = self._clean_string_for_variable_name(
4158
+ "$pad_size_{}_{}".format(def_name, layer)
4159
+ )
4160
+ if pad_size_variable not in self.variables:
4161
+ self.add_design_variable(pad_size_variable, pad.parameters_values[0])
4162
+ pad.parameters = {"Size": pad_size_variable}
4163
+ parameters.append(pad_size_variable)
4164
+ elif pad.geometry_type == 3: # pragma no cover
4165
+ pad_size_variable_x = self._clean_string_for_variable_name(
4166
+ "$pad_size_x_{}_{}".format(def_name, layer)
4167
+ )
4168
+ pad_size_variable_y = self._clean_string_for_variable_name(
4169
+ "$pad_size_y_{}_{}".format(def_name, layer)
4170
+ )
4171
+ if pad_size_variable_x not in self.variables and pad_size_variable_y not in self.variables:
4172
+ self.add_design_variable(pad_size_variable_x, pad.parameters_values[0])
4173
+ self.add_design_variable(pad_size_variable_y, pad.parameters_values[1])
4174
+ pad.parameters = {"XSize": pad_size_variable_x, "YSize": pad_size_variable_y}
4175
+ parameters.append(pad_size_variable_x)
4176
+ parameters.append(pad_size_variable_y)
4177
+ if antipads:
4178
+ for layer, antipad in padstack_def.antipad_by_layer.items():
4179
+ if antipad.geometry_type == 1: # pragma no cover
4180
+ antipad_diameter_variable = self._clean_string_for_variable_name(
4181
+ "$antipad_diam_{}_{}".format(def_name, layer)
4182
+ )
4183
+ if antipad_diameter_variable not in self.variables: # pragma no cover
4184
+ self.add_design_variable(antipad_diameter_variable, antipad.parameters_values[0])
4185
+ antipad.parameters = {"Diameter": antipad_diameter_variable}
4186
+ parameters.append(antipad_diameter_variable)
4187
+ if antipad.geometry_type == 2: # pragma no cover
4188
+ antipad_size_variable = self._clean_string_for_variable_name(
4189
+ "$antipad_size_{}_{}".format(def_name, layer)
4190
+ )
4191
+ if antipad_size_variable not in self.variables: # pragma no cover
4192
+ self.add_design_variable(antipad_size_variable, antipad.parameters_values[0])
4193
+ antipad.parameters = {"Size": antipad_size_variable}
4194
+ parameters.append(antipad_size_variable)
4195
+ elif antipad.geometry_type == 3: # pragma no cover
4196
+ antipad_size_variable_x = self._clean_string_for_variable_name(
4197
+ "$antipad_size_x_{}_{}".format(def_name, layer)
4198
+ )
4199
+ antipad_size_variable_y = self._clean_string_for_variable_name(
4200
+ "$antipad_size_y_{}_{}".format(def_name, layer)
4201
+ )
4202
+ if (
4203
+ antipad_size_variable_x not in self.variables
4204
+ and antipad_size_variable_y not in self.variables
4205
+ ): # pragma no cover
4206
+ self.add_design_variable(antipad_size_variable_x, antipad.parameters_values[0])
4207
+ self.add_design_variable(antipad_size_variable_y, antipad.parameters_values[1])
4208
+ antipad.parameters = {"XSize": antipad_size_variable_x, "YSize": antipad_size_variable_y}
4209
+ parameters.append(antipad_size_variable_x)
4210
+ parameters.append(antipad_size_variable_y)
4211
+ return parameters
4212
+
4213
+ @pyedb_function_handler
4214
+ def _clean_string_for_variable_name(self, variable_name):
4215
+ """Remove forbidden character for variable name.
4216
+ Parameter
4217
+ ----------
4218
+ variable_name : str
4219
+ Variable name.
4220
+ Returns
4221
+ -------
4222
+ str
4223
+ Edited name.
4224
+ """
4225
+ if "-" in variable_name:
4226
+ variable_name = variable_name.replace("-", "_")
4227
+ if "+" in variable_name:
4228
+ variable_name = variable_name.replace("+", "p")
4229
+ variable_name = re.sub(r"[() ]", "_", variable_name)
4230
+
4231
+ return variable_name
4232
+
4233
+ @property
4234
+ def definitions(self):
4235
+ """Definitions class."""
4236
+ from pyedb.dotnet.edb_core.definition.definitions import Definitions
4237
+ return Definitions(self)