pyedb 0.12.1__py3-none-any.whl → 0.13.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 +1 -1
- pyedb/configuration/cfg_boundaries.py +1 -1
- pyedb/configuration/cfg_common.py +48 -0
- pyedb/configuration/cfg_components.py +94 -166
- pyedb/configuration/cfg_data.py +12 -7
- pyedb/configuration/cfg_general.py +1 -1
- pyedb/configuration/cfg_nets.py +1 -1
- pyedb/configuration/cfg_operations.py +63 -0
- pyedb/configuration/cfg_package_definition.py +128 -0
- pyedb/configuration/cfg_padstacks.py +3 -3
- pyedb/configuration/cfg_pin_groups.py +1 -1
- pyedb/configuration/cfg_ports_sources.py +4 -4
- pyedb/configuration/cfg_s_parameter_models.py +1 -1
- pyedb/configuration/cfg_setup.py +1 -1
- pyedb/configuration/cfg_spice_models.py +1 -1
- pyedb/configuration/cfg_stackup.py +169 -0
- pyedb/configuration/configuration.py +46 -19
- pyedb/dotnet/edb.py +167 -7
- pyedb/dotnet/edb_core/cell/hierarchy/model.py +1 -1
- pyedb/dotnet/edb_core/{edb_data/connectable.py → cell/layout_obj.py} +1 -1
- pyedb/dotnet/edb_core/cell/primitive.py +142 -0
- pyedb/dotnet/edb_core/components.py +1 -1
- pyedb/dotnet/edb_core/definition/component_def.py +1 -1
- pyedb/dotnet/edb_core/definition/component_model.py +1 -1
- pyedb/dotnet/edb_core/definition/definition_obj.py +1 -1
- pyedb/dotnet/edb_core/definition/package_def.py +2 -1
- pyedb/dotnet/edb_core/edb_data/components_data.py +19 -7
- pyedb/dotnet/edb_core/edb_data/padstacks_data.py +3 -3
- pyedb/dotnet/edb_core/edb_data/primitives_data.py +5 -126
- pyedb/dotnet/edb_core/edb_data/terminals.py +2 -2
- pyedb/dotnet/edb_core/geometry/polygon_data.py +1 -1
- pyedb/dotnet/edb_core/materials.py +2 -1
- pyedb/dotnet/edb_core/padstack.py +86 -27
- pyedb/generic/general_methods.py +33 -0
- pyedb/siwave.py +23 -2
- {pyedb-0.12.1.dist-info → pyedb-0.13.0.dist-info}/METADATA +2 -2
- {pyedb-0.12.1.dist-info → pyedb-0.13.0.dist-info}/RECORD +40 -35
- /pyedb/dotnet/edb_core/{obj_base.py → utilities/obj_base.py} +0 -0
- {pyedb-0.12.1.dist-info → pyedb-0.13.0.dist-info}/LICENSE +0 -0
- {pyedb-0.12.1.dist-info → pyedb-0.13.0.dist-info}/WHEEL +0 -0
|
@@ -27,7 +27,7 @@ class CfgCircuitElement:
|
|
|
27
27
|
@property
|
|
28
28
|
def pedb(self):
|
|
29
29
|
"""Edb."""
|
|
30
|
-
return self._pdata.
|
|
30
|
+
return self._pdata._pedb
|
|
31
31
|
|
|
32
32
|
@pyedb_function_handler
|
|
33
33
|
def __init__(self, pdata, **kwargs):
|
|
@@ -76,7 +76,7 @@ class CfgCircuitElement:
|
|
|
76
76
|
search_radius = neg_value.get("search_radius", "5e-3")
|
|
77
77
|
temp = dict()
|
|
78
78
|
for i, j in pos_objs.items():
|
|
79
|
-
temp[i] = self._pdata.
|
|
79
|
+
temp[i] = self._pdata._pedb.padstacks.get_reference_pins(j, ref_net, search_radius, max_limit=1)[0]
|
|
80
80
|
self.neg_terminal = {
|
|
81
81
|
i: j.create_terminal(i + "_ref") if not j.terminal else j.terminal for i, j in temp.items()
|
|
82
82
|
}
|
|
@@ -105,7 +105,7 @@ class CfgCircuitElement:
|
|
|
105
105
|
pins.update(get_pin_obj(i))
|
|
106
106
|
else:
|
|
107
107
|
if terminal_type == "net":
|
|
108
|
-
temp = self._pdata.
|
|
108
|
+
temp = self._pdata._pedb.components.get_pins(self.reference_designator, terminal_value[0])
|
|
109
109
|
elif terminal_type == "pin_group":
|
|
110
110
|
pin_group = self.pedb.siwave.pin_groups[terminal_value[0]]
|
|
111
111
|
temp = pin_group.pins
|
|
@@ -119,7 +119,7 @@ class CfgCircuitElement:
|
|
|
119
119
|
else:
|
|
120
120
|
pg_name = f"pg_{self.name}_{self.reference_designator}"
|
|
121
121
|
pin_names = [i.pin_number for i in pins.values()]
|
|
122
|
-
name, temp = self._pdata.
|
|
122
|
+
name, temp = self._pdata._pedb.siwave.create_pin_group(self.reference_designator, pin_names, pg_name)
|
|
123
123
|
return {name: temp}
|
|
124
124
|
|
|
125
125
|
|
|
@@ -25,7 +25,7 @@ from pathlib import Path
|
|
|
25
25
|
|
|
26
26
|
class CfgSParameterModel:
|
|
27
27
|
def __init__(self, pdata, path_lib, sparam_dict):
|
|
28
|
-
self._pedb = pdata.
|
|
28
|
+
self._pedb = pdata._pedb
|
|
29
29
|
self.path_libraries = path_lib
|
|
30
30
|
self._sparam_dict = sparam_dict
|
|
31
31
|
self.name = self._sparam_dict.get("name", "")
|
pyedb/configuration/cfg_setup.py
CHANGED
|
@@ -25,7 +25,7 @@ from pathlib import Path
|
|
|
25
25
|
|
|
26
26
|
class CfgSpiceModel:
|
|
27
27
|
def __init__(self, pdata, path_lib, spice_dict):
|
|
28
|
-
self._pedb = pdata.
|
|
28
|
+
self._pedb = pdata._pedb
|
|
29
29
|
self.path_libraries = path_lib
|
|
30
30
|
self._spice_dict = spice_dict
|
|
31
31
|
self.name = self._spice_dict.get("name", "")
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
from pyedb.configuration.cfg_common import CfgBase
|
|
24
|
+
from pyedb.generic.general_methods import pyedb_function_handler
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CfgMaterial(CfgBase):
|
|
28
|
+
def __init__(self, **kwargs):
|
|
29
|
+
self.name = kwargs.get("name", None)
|
|
30
|
+
self.permittivity = kwargs.get("permittivity", None)
|
|
31
|
+
self.conductivity = kwargs.get("conductivity", None)
|
|
32
|
+
self.dielectric_loss_tangent = kwargs.get("dielectric_loss_tangent", None)
|
|
33
|
+
self.magnetic_loss_tangent = kwargs.get("magnetic_loss_tangent", None)
|
|
34
|
+
self.mass_density = kwargs.get("mass_density", None)
|
|
35
|
+
self.permeability = kwargs.get("permeability", None)
|
|
36
|
+
self.poisson_ratio = kwargs.get("poisson_ratio", None)
|
|
37
|
+
self.specific_heat = kwargs.get("specific_heat", None)
|
|
38
|
+
self.thermal_conductivity = kwargs.get("thermal_conductivity", None)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CfgLayer(CfgBase):
|
|
42
|
+
def __init__(self, **kwargs):
|
|
43
|
+
self.name = kwargs.get("name", None)
|
|
44
|
+
self.type = kwargs.get("type", None)
|
|
45
|
+
self.material = kwargs.get("material", None)
|
|
46
|
+
self.fill_material = kwargs.get("fill_material", None)
|
|
47
|
+
self.thickness = kwargs.get("thickness", None)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CfgStackup:
|
|
51
|
+
def __init__(self, pedb, data):
|
|
52
|
+
self._pedb = pedb
|
|
53
|
+
|
|
54
|
+
self.materials = [CfgMaterial(**mat) for mat in data.get("materials", [])]
|
|
55
|
+
self.layers = [CfgLayer(**lay) for lay in data.get("layers", [])]
|
|
56
|
+
|
|
57
|
+
@pyedb_function_handler
|
|
58
|
+
def apply(self):
|
|
59
|
+
"""Apply configuration settings to the current design"""
|
|
60
|
+
if len(self.materials):
|
|
61
|
+
self.__apply_materials()
|
|
62
|
+
|
|
63
|
+
input_signal_layers = [i for i in self.layers if i.type.lower() == "signal"]
|
|
64
|
+
|
|
65
|
+
if len(self.layers):
|
|
66
|
+
if len(self._pedb.stackup.signal_layers) == 0:
|
|
67
|
+
self.__create_stackup()
|
|
68
|
+
elif not len(input_signal_layers) == len(self._pedb.stackup.signal_layers):
|
|
69
|
+
raise Exception(f"Input signal layer count do not match.")
|
|
70
|
+
else:
|
|
71
|
+
self.__apply_layers()
|
|
72
|
+
|
|
73
|
+
def __create_stackup(self):
|
|
74
|
+
layers = list()
|
|
75
|
+
layers.extend(self.layers)
|
|
76
|
+
for l_attrs in layers:
|
|
77
|
+
attrs = l_attrs.get_attributes()
|
|
78
|
+
self._pedb.stackup.add_layer_bottom(**attrs)
|
|
79
|
+
|
|
80
|
+
@pyedb_function_handler
|
|
81
|
+
def __apply_layers(self):
|
|
82
|
+
"""Apply layer settings to the current design"""
|
|
83
|
+
layers = list()
|
|
84
|
+
layers.extend(self.layers)
|
|
85
|
+
|
|
86
|
+
removal_list = []
|
|
87
|
+
lc_signal_layers = []
|
|
88
|
+
for name, obj in self._pedb.stackup.all_layers.items():
|
|
89
|
+
if obj.type == "dielectric":
|
|
90
|
+
removal_list.append(name)
|
|
91
|
+
elif obj.type == "signal":
|
|
92
|
+
lc_signal_layers.append(obj.id)
|
|
93
|
+
for l in removal_list:
|
|
94
|
+
self._pedb.stackup.remove_layer(l)
|
|
95
|
+
|
|
96
|
+
# update all signal layers
|
|
97
|
+
id_name = {i[0]: i[1] for i in self._pedb.stackup.layers_by_id}
|
|
98
|
+
signal_idx = 0
|
|
99
|
+
for l in layers:
|
|
100
|
+
if l.type == "signal":
|
|
101
|
+
layer_id = lc_signal_layers[signal_idx]
|
|
102
|
+
layer_name = id_name[layer_id]
|
|
103
|
+
attrs = l.get_attributes()
|
|
104
|
+
self._pedb.stackup.layers[layer_name].update(**attrs)
|
|
105
|
+
signal_idx = signal_idx + 1
|
|
106
|
+
|
|
107
|
+
# add all dielectric layers. Dielectric layers must be added last. Otherwise,
|
|
108
|
+
# dielectric layer will occupy signal and document layer id.
|
|
109
|
+
prev_layer_clone = None
|
|
110
|
+
l = layers.pop(0)
|
|
111
|
+
if l.type == "signal":
|
|
112
|
+
prev_layer_clone = self._pedb.stackup.layers[l.name]
|
|
113
|
+
else:
|
|
114
|
+
attrs = l.get_attributes()
|
|
115
|
+
prev_layer_clone = self._pedb.stackup.add_layer_top(**attrs)
|
|
116
|
+
for idx, l in enumerate(layers):
|
|
117
|
+
if l.type == "dielectric":
|
|
118
|
+
attrs = l.get_attributes()
|
|
119
|
+
prev_layer_clone = self._pedb.stackup.add_layer_below(base_layer_name=prev_layer_clone.name, **attrs)
|
|
120
|
+
elif l.type == "signal":
|
|
121
|
+
prev_layer_clone = self._pedb.stackup.layers[l.name]
|
|
122
|
+
|
|
123
|
+
@pyedb_function_handler
|
|
124
|
+
def __apply_materials(self):
|
|
125
|
+
"""Apply material settings to the current design"""
|
|
126
|
+
materials_in_db = {i.lower(): i for i, _ in self._pedb.materials.materials.items()}
|
|
127
|
+
for mat_in_cfg in self.materials:
|
|
128
|
+
if mat_in_cfg.name.lower() in materials_in_db:
|
|
129
|
+
self._pedb.materials.delete_material(materials_in_db[mat_in_cfg.name.lower()])
|
|
130
|
+
|
|
131
|
+
attrs = mat_in_cfg.get_attributes()
|
|
132
|
+
mat = self._pedb.materials.add_material(**attrs)
|
|
133
|
+
|
|
134
|
+
@pyedb_function_handler
|
|
135
|
+
def __get_materials_from_db(self):
|
|
136
|
+
materials = []
|
|
137
|
+
for name, p in self._pedb.materials.materials.items():
|
|
138
|
+
mat = {}
|
|
139
|
+
for p_name in CfgMaterial().__dict__:
|
|
140
|
+
mat[p_name] = getattr(p, p_name)
|
|
141
|
+
materials.append(mat)
|
|
142
|
+
return materials
|
|
143
|
+
|
|
144
|
+
@pyedb_function_handler
|
|
145
|
+
def __get_layers_from_db(self):
|
|
146
|
+
layers = []
|
|
147
|
+
for name, obj in self._pedb.stackup.all_layers.items():
|
|
148
|
+
layer = {}
|
|
149
|
+
for p_name in CfgLayer().__dict__:
|
|
150
|
+
p_value = getattr(obj, p_name, None)
|
|
151
|
+
if p_value is not None:
|
|
152
|
+
layer[p_name] = getattr(obj, p_name)
|
|
153
|
+
layers.append(layer)
|
|
154
|
+
return layers
|
|
155
|
+
|
|
156
|
+
@pyedb_function_handler
|
|
157
|
+
def get_data_from_db(self):
|
|
158
|
+
"""Get configuration data from layout.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
dict
|
|
163
|
+
"""
|
|
164
|
+
stackup = {}
|
|
165
|
+
materials = self.__get_materials_from_db()
|
|
166
|
+
stackup["materials"] = materials
|
|
167
|
+
layers = self.__get_layers_from_db()
|
|
168
|
+
stackup["layers"] = layers
|
|
169
|
+
return stackup
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import json
|
|
24
24
|
import os
|
|
25
|
+
from pathlib import Path
|
|
25
26
|
|
|
26
27
|
import toml
|
|
27
28
|
|
|
@@ -115,9 +116,7 @@ class Configuration:
|
|
|
115
116
|
self.cfg_data.nets.apply()
|
|
116
117
|
|
|
117
118
|
# Configure components
|
|
118
|
-
|
|
119
|
-
for comp in self.cfg_data.components:
|
|
120
|
-
comp.apply()
|
|
119
|
+
self.cfg_data.components.apply()
|
|
121
120
|
|
|
122
121
|
# Configure padstacks
|
|
123
122
|
if self.cfg_data.padstacks:
|
|
@@ -132,8 +131,6 @@ class Configuration:
|
|
|
132
131
|
port.create()
|
|
133
132
|
|
|
134
133
|
# Configure sources
|
|
135
|
-
"""if "sources" in self.data:
|
|
136
|
-
self._load_sources()"""
|
|
137
134
|
for source in self.cfg_data.sources:
|
|
138
135
|
source.create()
|
|
139
136
|
|
|
@@ -142,8 +139,7 @@ class Configuration:
|
|
|
142
139
|
setup.apply()
|
|
143
140
|
|
|
144
141
|
# Configure stackup
|
|
145
|
-
|
|
146
|
-
self._load_stackup()
|
|
142
|
+
self.cfg_data.stackup.apply()
|
|
147
143
|
|
|
148
144
|
# Configure S-parameter
|
|
149
145
|
for s_parameter_model in self.cfg_data.s_parameters:
|
|
@@ -154,12 +150,10 @@ class Configuration:
|
|
|
154
150
|
spice_model.apply()
|
|
155
151
|
|
|
156
152
|
# Configure package definitions
|
|
157
|
-
|
|
158
|
-
self._load_package_def()
|
|
153
|
+
self.cfg_data.package_definitions.apply()
|
|
159
154
|
|
|
160
155
|
# Configure operations
|
|
161
|
-
|
|
162
|
-
self._load_operations()
|
|
156
|
+
self.cfg_data.operations.apply()
|
|
163
157
|
|
|
164
158
|
return True
|
|
165
159
|
|
|
@@ -220,14 +214,6 @@ class Configuration:
|
|
|
220
214
|
elif l["type"] == "signal":
|
|
221
215
|
prev_layer_clone = self._pedb.stackup.layers[l["name"]]
|
|
222
216
|
|
|
223
|
-
@pyedb_function_handler
|
|
224
|
-
def _load_operations(self):
|
|
225
|
-
"""Imports operation information from JSON."""
|
|
226
|
-
operations = self.data["operations"]
|
|
227
|
-
cutout = operations.get("cutout", None)
|
|
228
|
-
if cutout:
|
|
229
|
-
self._pedb.cutout(**cutout)
|
|
230
|
-
|
|
231
217
|
@pyedb_function_handler
|
|
232
218
|
def _load_package_def(self):
|
|
233
219
|
"""Imports package definition information from JSON."""
|
|
@@ -271,3 +257,44 @@ class Configuration:
|
|
|
271
257
|
)
|
|
272
258
|
for _, i in comp_list.items():
|
|
273
259
|
i.package_def = name
|
|
260
|
+
|
|
261
|
+
def get_data_from_db(self, **kwargs):
|
|
262
|
+
"""Get configuration data from layout.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
stackup
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
data = {}
|
|
273
|
+
if kwargs.get("stackup", False):
|
|
274
|
+
data["stackup"] = self.cfg_data.stackup.get_data_from_db()
|
|
275
|
+
if kwargs.get("package_definitions", False):
|
|
276
|
+
data["package_definitions"] = self.cfg_data.package_definitions.get_data_from_db()
|
|
277
|
+
|
|
278
|
+
return data
|
|
279
|
+
|
|
280
|
+
@pyedb_function_handler
|
|
281
|
+
def export(self, file_path, stackup=True, package_definitions=True):
|
|
282
|
+
"""Export the configuration data from layout to a file.
|
|
283
|
+
|
|
284
|
+
Parameters
|
|
285
|
+
----------
|
|
286
|
+
file_path : str, Path
|
|
287
|
+
File path to export the configuration data.
|
|
288
|
+
stackup : bool
|
|
289
|
+
Whether to export stackup or not.
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
bool
|
|
294
|
+
"""
|
|
295
|
+
file_path = file_path if isinstance(file_path, Path) else Path(file_path)
|
|
296
|
+
file_path = file_path if file_path.suffix == ".json" else file_path.with_suffix(".json")
|
|
297
|
+
data = self.get_data_from_db(stackup=stackup, package_definitions=package_definitions)
|
|
298
|
+
with open(file_path, "w") as f:
|
|
299
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
300
|
+
return True if os.path.isfile(file_path) else False
|
pyedb/dotnet/edb.py
CHANGED
|
@@ -27,12 +27,14 @@ This module is implicitly loaded in HFSS 3D Layout when launched.
|
|
|
27
27
|
"""
|
|
28
28
|
from itertools import combinations
|
|
29
29
|
import os
|
|
30
|
+
from pathlib import Path
|
|
30
31
|
import re
|
|
31
32
|
import shutil
|
|
32
33
|
import sys
|
|
33
34
|
import tempfile
|
|
34
35
|
import time
|
|
35
36
|
import traceback
|
|
37
|
+
from typing import Union
|
|
36
38
|
import warnings
|
|
37
39
|
|
|
38
40
|
from pyedb.configuration.configuration import Configuration
|
|
@@ -111,6 +113,8 @@ if is_linux and is_ironpython:
|
|
|
111
113
|
else:
|
|
112
114
|
import subprocess
|
|
113
115
|
|
|
116
|
+
import rtree
|
|
117
|
+
|
|
114
118
|
|
|
115
119
|
class Edb(Database):
|
|
116
120
|
"""Provides the EDB application interface.
|
|
@@ -180,7 +184,7 @@ class Edb(Database):
|
|
|
180
184
|
|
|
181
185
|
def __init__(
|
|
182
186
|
self,
|
|
183
|
-
edbpath: str = None,
|
|
187
|
+
edbpath: Union[str, Path] = None,
|
|
184
188
|
cellname: str = None,
|
|
185
189
|
isreadonly: bool = False,
|
|
186
190
|
edbversion: str = None,
|
|
@@ -191,6 +195,9 @@ class Edb(Database):
|
|
|
191
195
|
technology_file: str = None,
|
|
192
196
|
remove_existing_aedt: bool = False,
|
|
193
197
|
):
|
|
198
|
+
if isinstance(edbpath, Path):
|
|
199
|
+
edbpath = str(edbpath)
|
|
200
|
+
|
|
194
201
|
edbversion = get_string_version(edbversion)
|
|
195
202
|
self._clean_variables()
|
|
196
203
|
Database.__init__(self, edbversion=edbversion, student_version=student_version)
|
|
@@ -315,7 +322,9 @@ class Edb(Database):
|
|
|
315
322
|
if os.path.isfile(file):
|
|
316
323
|
if not remove_existing_aedt:
|
|
317
324
|
self.logger.warning(
|
|
318
|
-
f"AEDT project-related file {file} exists and may need to be deleted before opening the EDB in
|
|
325
|
+
f"AEDT project-related file {file} exists and may need to be deleted before opening the EDB in "
|
|
326
|
+
f"HFSS 3D Layout."
|
|
327
|
+
# noqa: E501
|
|
319
328
|
)
|
|
320
329
|
else:
|
|
321
330
|
try:
|
|
@@ -1523,12 +1532,11 @@ class Edb(Database):
|
|
|
1523
1532
|
``True`` when successful, ``False`` when failed.
|
|
1524
1533
|
|
|
1525
1534
|
"""
|
|
1526
|
-
if
|
|
1527
|
-
control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml")
|
|
1528
|
-
ControlFile(xml_input=control_file, tecnhology=tech_file, layer_map=map_file).write_xml(control_file_temp)
|
|
1529
|
-
elif tech_file:
|
|
1535
|
+
if not is_linux and tech_file:
|
|
1530
1536
|
self.logger.error("Technology files are supported only in Linux. Use control file instead.")
|
|
1531
1537
|
return False
|
|
1538
|
+
control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml")
|
|
1539
|
+
ControlFile(xml_input=control_file, tecnhology=tech_file, layer_map=map_file).write_xml(control_file_temp)
|
|
1532
1540
|
if self.import_layout_pcb(
|
|
1533
1541
|
inputGDS,
|
|
1534
1542
|
working_dir=WorkDir,
|
|
@@ -4314,7 +4322,7 @@ class Edb(Database):
|
|
|
4314
4322
|
@pyedb_function_handler
|
|
4315
4323
|
def _clean_string_for_variable_name(self, variable_name):
|
|
4316
4324
|
"""Remove forbidden character for variable name.
|
|
4317
|
-
|
|
4325
|
+
Parameters
|
|
4318
4326
|
----------
|
|
4319
4327
|
variable_name : str
|
|
4320
4328
|
Variable name.
|
|
@@ -4331,6 +4339,158 @@ class Edb(Database):
|
|
|
4331
4339
|
|
|
4332
4340
|
return variable_name
|
|
4333
4341
|
|
|
4342
|
+
def create_model_for_arbitrary_wave_ports(
|
|
4343
|
+
self,
|
|
4344
|
+
temp_directory,
|
|
4345
|
+
mounting_side="top",
|
|
4346
|
+
signal_nets=None,
|
|
4347
|
+
terminal_diameter=None,
|
|
4348
|
+
output_edb=None,
|
|
4349
|
+
launching_box_thickness="100um",
|
|
4350
|
+
):
|
|
4351
|
+
"""Generate EDB design to be consumed by PyAEDT to generate arbitrary wave ports shapes.
|
|
4352
|
+
This model has to be considered as merged onto another one. The current opened design must have voids
|
|
4353
|
+
surrounding the pad-stacks where wave ports terminal will be created. THe open design won't be edited, only
|
|
4354
|
+
primitives like voids and pads-stack definition included in the voids are collected to generate a new design.
|
|
4355
|
+
|
|
4356
|
+
Parameters
|
|
4357
|
+
----------
|
|
4358
|
+
temp_directory : str
|
|
4359
|
+
Temporary directory used during the method execution.
|
|
4360
|
+
|
|
4361
|
+
mounting_side : str
|
|
4362
|
+
Gives the orientation to be considered for the current design. 2 options are available ``"top"`` and
|
|
4363
|
+
``"bottom". Default value is ``"top"``. If ``"top"`` is selected the method will voids at the top signal
|
|
4364
|
+
layer, and the bottom layer if ``"bottom"`` is used.
|
|
4365
|
+
|
|
4366
|
+
signal_nets : List[str], optional
|
|
4367
|
+
Provides the nets to be included for the model creation. Default value is ``None``. If None is provided,
|
|
4368
|
+
all nets will be included.
|
|
4369
|
+
|
|
4370
|
+
terminal_diameter : float, str, optional
|
|
4371
|
+
When ``None``, the terminal diameter is evaluated at each pads-tack instance found inside the voids. The top
|
|
4372
|
+
or bottom layer pad diameter will be taken, depending on ``mounting_side`` selected. If value is provided,
|
|
4373
|
+
it will overwrite the evaluated diameter.
|
|
4374
|
+
|
|
4375
|
+
output_edb : str, optional
|
|
4376
|
+
The output EDB absolute. If ``None`` the edb is created in the ``temp_directory`` as default name
|
|
4377
|
+
`"waveport_model.aedb"``
|
|
4378
|
+
|
|
4379
|
+
launching_box_thickness : float, str, optional
|
|
4380
|
+
Launching box thickness used for wave ports. Default value is ``"100um"``.
|
|
4381
|
+
|
|
4382
|
+
Returns
|
|
4383
|
+
-------
|
|
4384
|
+
bool
|
|
4385
|
+
``True`` when succeeded, ``False`` if failed.
|
|
4386
|
+
"""
|
|
4387
|
+
if not temp_directory:
|
|
4388
|
+
self.logger.error("Temp directory must be provided when creating model foe arbitrary wave port")
|
|
4389
|
+
return False
|
|
4390
|
+
if not output_edb:
|
|
4391
|
+
output_edb = os.path.join(temp_directory, "waveport_model.aedb")
|
|
4392
|
+
if os.path.isdir(temp_directory):
|
|
4393
|
+
shutil.rmtree(temp_directory)
|
|
4394
|
+
reference_layer = list(self.stackup.signal_layers.keys())[0]
|
|
4395
|
+
if mounting_side.lower == "bottom":
|
|
4396
|
+
reference_layer = list(self.stackup.signal_layers.keys())[-1]
|
|
4397
|
+
if not signal_nets:
|
|
4398
|
+
signal_nets = list(self.nets.signal.keys())
|
|
4399
|
+
|
|
4400
|
+
used_padstack_defs = []
|
|
4401
|
+
padstack_instances_index = rtree.index.Index()
|
|
4402
|
+
for padstack_inst in list(self.padstacks.instances.values()):
|
|
4403
|
+
if not reference_layer in [padstack_inst.start_layer, padstack_inst.stop_layer]:
|
|
4404
|
+
padstack_inst.delete()
|
|
4405
|
+
else:
|
|
4406
|
+
if padstack_inst.net_name in signal_nets:
|
|
4407
|
+
padstack_instances_index.insert(padstack_inst.id, padstack_inst.position)
|
|
4408
|
+
if not padstack_inst.padstack_definition in used_padstack_defs:
|
|
4409
|
+
used_padstack_defs.append(padstack_inst.padstack_definition)
|
|
4410
|
+
|
|
4411
|
+
polys = [
|
|
4412
|
+
poly
|
|
4413
|
+
for poly in self.modeler.primitives
|
|
4414
|
+
if poly.layer_name == reference_layer and poly.type == "Polygon" and poly.has_voids
|
|
4415
|
+
]
|
|
4416
|
+
if not polys:
|
|
4417
|
+
self.logger.error(
|
|
4418
|
+
f"No polygon found with voids on layer {reference_layer} during model creation for "
|
|
4419
|
+
f"arbitrary wave ports"
|
|
4420
|
+
)
|
|
4421
|
+
return False
|
|
4422
|
+
void_padstacks = []
|
|
4423
|
+
for poly in polys:
|
|
4424
|
+
for void in poly.voids:
|
|
4425
|
+
void_bbox = (
|
|
4426
|
+
void.polygon_data.edb_api.GetBBox().Item1.X.ToDouble(),
|
|
4427
|
+
void.polygon_data.edb_api.GetBBox().Item1.Y.ToDouble(),
|
|
4428
|
+
void.polygon_data.edb_api.GetBBox().Item2.X.ToDouble(),
|
|
4429
|
+
void.polygon_data.edb_api.GetBBox().Item2.Y.ToDouble(),
|
|
4430
|
+
)
|
|
4431
|
+
included_instances = list(padstack_instances_index.intersection(void_bbox))
|
|
4432
|
+
if included_instances:
|
|
4433
|
+
void_padstacks.append((void, [self.padstacks.instances[edb_id] for edb_id in included_instances]))
|
|
4434
|
+
|
|
4435
|
+
if not void_padstacks:
|
|
4436
|
+
self.logger.error(
|
|
4437
|
+
"No padstack instances found inside evaluated voids during model creation for arbitrary" "waveports"
|
|
4438
|
+
)
|
|
4439
|
+
return False
|
|
4440
|
+
cloned_edb = Edb(edbpath=output_edb, edbversion=self.edbversion)
|
|
4441
|
+
cloned_edb.stackup.add_layer(layer_name="ref", layer_type="signal", thickness=0.0, material="pec")
|
|
4442
|
+
cloned_edb.stackup.add_layer(
|
|
4443
|
+
layer_name="ports",
|
|
4444
|
+
layer_type="signal",
|
|
4445
|
+
thickness=self.stackup.signal_layers[reference_layer].thickness,
|
|
4446
|
+
material="pec",
|
|
4447
|
+
)
|
|
4448
|
+
box_thick = "100um"
|
|
4449
|
+
if launching_box_thickness:
|
|
4450
|
+
box_thick = self.edb_value(launching_box_thickness).ToString()
|
|
4451
|
+
if mounting_side == "top":
|
|
4452
|
+
cloned_edb.stackup.add_layer(
|
|
4453
|
+
layer_name="port_pec", layer_type="signal", thickness=box_thick, method="add_on_bottom", material="pec"
|
|
4454
|
+
)
|
|
4455
|
+
else:
|
|
4456
|
+
cloned_edb.stackup.add_layer(
|
|
4457
|
+
layer_name="port_pec", layer_type="signal", thickness=box_thick, method="add_on_top", material="pec"
|
|
4458
|
+
)
|
|
4459
|
+
|
|
4460
|
+
for void_info in void_padstacks:
|
|
4461
|
+
port_poly = cloned_edb.modeler.create_polygon(
|
|
4462
|
+
main_shape=void_info[0].polygon_data.edb_api, layer_name="ref", net_name="GND"
|
|
4463
|
+
)
|
|
4464
|
+
pec_poly = cloned_edb.modeler.create_polygon(
|
|
4465
|
+
main_shape=port_poly.polygon_data.edb_api, layer_name="port_pec", net_name="GND"
|
|
4466
|
+
)
|
|
4467
|
+
pec_poly.scale(1.5)
|
|
4468
|
+
|
|
4469
|
+
for void_info in void_padstacks:
|
|
4470
|
+
for inst in void_info[1]:
|
|
4471
|
+
if not terminal_diameter:
|
|
4472
|
+
pad_diameter = (
|
|
4473
|
+
self.padstacks.definitions[inst.padstack_definition]
|
|
4474
|
+
.pad_by_layer[reference_layer]
|
|
4475
|
+
.parameters_values[0]
|
|
4476
|
+
)
|
|
4477
|
+
else:
|
|
4478
|
+
pad_diameter = self.edb_value(terminal_diameter).ToDouble()
|
|
4479
|
+
_temp_circle = cloned_edb.modeler.create_circle(
|
|
4480
|
+
layer_name="ports",
|
|
4481
|
+
x=inst.position[0],
|
|
4482
|
+
y=inst.position[1],
|
|
4483
|
+
radius=pad_diameter / 2,
|
|
4484
|
+
net_name=inst.net_name,
|
|
4485
|
+
)
|
|
4486
|
+
if not _temp_circle:
|
|
4487
|
+
self.logger.error(
|
|
4488
|
+
f"Failed to create circle for terminal during create_model_for_arbitrary_wave_ports"
|
|
4489
|
+
)
|
|
4490
|
+
cloned_edb.save_as(output_edb)
|
|
4491
|
+
cloned_edb.close()
|
|
4492
|
+
return True
|
|
4493
|
+
|
|
4334
4494
|
@property
|
|
4335
4495
|
def definitions(self):
|
|
4336
4496
|
"""Definitions class."""
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
# SOFTWARE.
|
|
22
22
|
|
|
23
|
-
from pyedb.dotnet.edb_core.obj_base import ObjBase
|
|
23
|
+
from pyedb.dotnet.edb_core.utilities.obj_base import ObjBase
|
|
24
24
|
from pyedb.generic.general_methods import pyedb_function_handler
|
|
25
25
|
|
|
26
26
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
# SOFTWARE.
|
|
22
22
|
|
|
23
|
-
from pyedb.dotnet.edb_core.obj_base import ObjBase
|
|
23
|
+
from pyedb.dotnet.edb_core.utilities.obj_base import ObjBase
|
|
24
24
|
from pyedb.generic.general_methods import pyedb_function_handler
|
|
25
25
|
|
|
26
26
|
|