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