pyedb 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyedb might be problematic. Click here for more details.
- pyedb/__init__.py +17 -0
- pyedb/dotnet/__init__.py +0 -0
- pyedb/dotnet/application/Variables.py +2261 -0
- pyedb/dotnet/application/__init__.py +0 -0
- pyedb/dotnet/clr_module.py +103 -0
- pyedb/dotnet/edb.py +4237 -0
- pyedb/dotnet/edb_core/__init__.py +1 -0
- pyedb/dotnet/edb_core/cell/__init__.py +0 -0
- pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
- pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
- pyedb/dotnet/edb_core/components.py +2669 -0
- pyedb/dotnet/edb_core/configuration.py +423 -0
- pyedb/dotnet/edb_core/definition/__init__.py +0 -0
- pyedb/dotnet/edb_core/definition/component_def.py +166 -0
- pyedb/dotnet/edb_core/definition/component_model.py +30 -0
- pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
- pyedb/dotnet/edb_core/definition/definitions.py +12 -0
- pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
- pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
- pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
- pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
- pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
- pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
- pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
- pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
- pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
- pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
- pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
- pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
- pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
- pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
- pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
- pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
- pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
- pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
- pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
- pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
- pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
- pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
- pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
- pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
- pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
- pyedb/dotnet/edb_core/general.py +181 -0
- pyedb/dotnet/edb_core/hfss.py +1646 -0
- pyedb/dotnet/edb_core/layout.py +1244 -0
- pyedb/dotnet/edb_core/layout_validation.py +272 -0
- pyedb/dotnet/edb_core/materials.py +939 -0
- pyedb/dotnet/edb_core/net_class.py +335 -0
- pyedb/dotnet/edb_core/nets.py +1215 -0
- pyedb/dotnet/edb_core/padstack.py +1389 -0
- pyedb/dotnet/edb_core/siwave.py +1427 -0
- pyedb/dotnet/edb_core/stackup.py +2703 -0
- pyedb/edb_logger.py +396 -0
- pyedb/generic/__init__.py +0 -0
- pyedb/generic/constants.py +1063 -0
- pyedb/generic/data_handlers.py +320 -0
- pyedb/generic/design_types.py +104 -0
- pyedb/generic/filesystem.py +150 -0
- pyedb/generic/general_methods.py +1535 -0
- pyedb/generic/plot.py +1840 -0
- pyedb/generic/process.py +285 -0
- pyedb/generic/settings.py +224 -0
- pyedb/ipc2581/__init__.py +0 -0
- pyedb/ipc2581/bom/__init__.py +0 -0
- pyedb/ipc2581/bom/bom.py +21 -0
- pyedb/ipc2581/bom/bom_item.py +32 -0
- pyedb/ipc2581/bom/characteristics.py +37 -0
- pyedb/ipc2581/bom/refdes.py +16 -0
- pyedb/ipc2581/content/__init__.py +0 -0
- pyedb/ipc2581/content/color.py +38 -0
- pyedb/ipc2581/content/content.py +55 -0
- pyedb/ipc2581/content/dictionary_color.py +29 -0
- pyedb/ipc2581/content/dictionary_fill.py +28 -0
- pyedb/ipc2581/content/dictionary_line.py +30 -0
- pyedb/ipc2581/content/entry_color.py +13 -0
- pyedb/ipc2581/content/entry_line.py +14 -0
- pyedb/ipc2581/content/fill.py +15 -0
- pyedb/ipc2581/content/layer_ref.py +10 -0
- pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
- pyedb/ipc2581/ecad/__init__.py +0 -0
- pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
- pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
- pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
- pyedb/ipc2581/ecad/cad_data/component.py +41 -0
- pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
- pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
- pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
- pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
- pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
- pyedb/ipc2581/ecad/cad_data/package.py +104 -0
- pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
- pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
- pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
- pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
- pyedb/ipc2581/ecad/cad_data/path.py +89 -0
- pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
- pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
- pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
- pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
- pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
- pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
- pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
- pyedb/ipc2581/ecad/cad_data/step.py +275 -0
- pyedb/ipc2581/ecad/cad_header.py +33 -0
- pyedb/ipc2581/ecad/ecad.py +19 -0
- pyedb/ipc2581/ecad/spec.py +46 -0
- pyedb/ipc2581/history_record.py +37 -0
- pyedb/ipc2581/ipc2581.py +387 -0
- pyedb/ipc2581/logistic_header.py +25 -0
- pyedb/misc/__init__.py +0 -0
- pyedb/misc/aedtlib_personalib_install.py +14 -0
- pyedb/misc/downloads.py +322 -0
- pyedb/misc/misc.py +67 -0
- pyedb/misc/pyedb.runtimeconfig.json +13 -0
- pyedb/misc/siw_feature_config/__init__.py +0 -0
- pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
- pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
- pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
- pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
- pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
- pyedb/misc/utilities.py +27 -0
- pyedb/modeler/geometry_operators.py +2082 -0
- pyedb-0.2.0.dist-info/LICENSE +21 -0
- pyedb-0.2.0.dist-info/METADATA +208 -0
- pyedb-0.2.0.dist-info/RECORD +128 -0
- pyedb-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,2703 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the `EdbStackup` class.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import absolute_import # noreorder
|
|
7
|
+
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import math
|
|
12
|
+
import re
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
from pyedb.dotnet.edb_core.edb_data.layer_data import (
|
|
16
|
+
LayerEdbClass,
|
|
17
|
+
StackupLayerEdbClass,
|
|
18
|
+
)
|
|
19
|
+
from pyedb.dotnet.edb_core.general import convert_py_list_to_net_list
|
|
20
|
+
from pyedb.generic.general_methods import (
|
|
21
|
+
ET,
|
|
22
|
+
generate_unique_name,
|
|
23
|
+
is_ironpython,
|
|
24
|
+
pyedb_function_handler,
|
|
25
|
+
)
|
|
26
|
+
from pyedb.misc.aedtlib_personalib_install import write_pretty_xml
|
|
27
|
+
|
|
28
|
+
pd = None
|
|
29
|
+
np = None
|
|
30
|
+
if not is_ironpython:
|
|
31
|
+
try:
|
|
32
|
+
import numpy as np
|
|
33
|
+
except ImportError:
|
|
34
|
+
np = None
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import pandas as pd
|
|
38
|
+
except ImportError:
|
|
39
|
+
pd = None
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Stackup(object):
|
|
45
|
+
"""Manages EDB methods for stackup accessible from `Edb.stackup` property."""
|
|
46
|
+
|
|
47
|
+
def __getitem__(self, item):
|
|
48
|
+
return self.layers[item]
|
|
49
|
+
|
|
50
|
+
def __init__(self, pedb):
|
|
51
|
+
# parent caller class
|
|
52
|
+
self._pedb = pedb
|
|
53
|
+
self._lc = None
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def _logger(self):
|
|
57
|
+
return self._pedb.logger
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def layer_types(self):
|
|
61
|
+
"""Layer types.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
type
|
|
66
|
+
Types of layers.
|
|
67
|
+
"""
|
|
68
|
+
return self._pedb.edb_api.cell.layer_type
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def thickness(self):
|
|
72
|
+
"""Retrieve Stackup thickness.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
float
|
|
77
|
+
Layout stackup thickness.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
return self.get_layout_thickness()
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def num_layers(self):
|
|
84
|
+
"""Retrieve the stackup layer number.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
int
|
|
89
|
+
layer number.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
return len(list(self.stackup_layers.keys()))
|
|
93
|
+
|
|
94
|
+
@pyedb_function_handler()
|
|
95
|
+
def _int_to_layer_types(self, val):
|
|
96
|
+
if int(val) == 0:
|
|
97
|
+
return self.layer_types.SignalLayer
|
|
98
|
+
elif int(val) == 1:
|
|
99
|
+
return self.layer_types.DielectricLayer
|
|
100
|
+
elif int(val) == 2:
|
|
101
|
+
return self.layer_types.ConductingLayer
|
|
102
|
+
elif int(val) == 3:
|
|
103
|
+
return self.layer_types.AirlinesLayer
|
|
104
|
+
elif int(val) == 4:
|
|
105
|
+
return self.layer_types.ErrorsLayer
|
|
106
|
+
elif int(val) == 5:
|
|
107
|
+
return self.layer_types.SymbolLayer
|
|
108
|
+
elif int(val) == 6:
|
|
109
|
+
return self.layer_types.MeasureLayer
|
|
110
|
+
elif int(val) == 8:
|
|
111
|
+
return self.layer_types.AssemblyLayer
|
|
112
|
+
elif int(val) == 9:
|
|
113
|
+
return self.layer_types.SilkscreenLayer
|
|
114
|
+
elif int(val) == 10:
|
|
115
|
+
return self.layer_types.SolderMaskLayer
|
|
116
|
+
elif int(val) == 11:
|
|
117
|
+
return self.layer_types.SolderPasteLayer
|
|
118
|
+
elif int(val) == 12:
|
|
119
|
+
return self.layer_types.GlueLayer
|
|
120
|
+
elif int(val) == 13:
|
|
121
|
+
return self.layer_types.WirebondLayer
|
|
122
|
+
elif int(val) == 14:
|
|
123
|
+
return self.layer_types.UserLayer
|
|
124
|
+
elif int(val) == 16:
|
|
125
|
+
return self.layer_types.SIwaveHFSSSolverRegions
|
|
126
|
+
elif int(val) == 17:
|
|
127
|
+
return self.layer_types.PostprocessingLayer
|
|
128
|
+
elif int(val) == 18:
|
|
129
|
+
return self.layer_types.OutlineLayer
|
|
130
|
+
elif int(val) == 16:
|
|
131
|
+
return self.layer_types.LayerTypesCount
|
|
132
|
+
elif int(val) == -1:
|
|
133
|
+
return self.layer_types.UndefinedLayerType
|
|
134
|
+
|
|
135
|
+
@pyedb_function_handler()
|
|
136
|
+
def _layer_types_to_int(self, layer_type):
|
|
137
|
+
if not isinstance(layer_type, int):
|
|
138
|
+
if layer_type == self.layer_types.SignalLayer:
|
|
139
|
+
return 0
|
|
140
|
+
elif layer_type == self.layer_types.DielectricLayer:
|
|
141
|
+
return 1
|
|
142
|
+
elif layer_type == self.layer_types.ConductingLayer:
|
|
143
|
+
return 2
|
|
144
|
+
elif layer_type == self.layer_types.AirlinesLayer:
|
|
145
|
+
return 3
|
|
146
|
+
elif layer_type == self.layer_types.ErrorsLayer:
|
|
147
|
+
return 4
|
|
148
|
+
elif layer_type == self.layer_types.SymbolLayer:
|
|
149
|
+
return 5
|
|
150
|
+
elif layer_type == self.layer_types.MeasureLayer:
|
|
151
|
+
return 6
|
|
152
|
+
elif layer_type == self.layer_types.AssemblyLayer:
|
|
153
|
+
return 8
|
|
154
|
+
elif layer_type == self.layer_types.SilkscreenLayer:
|
|
155
|
+
return 9
|
|
156
|
+
elif layer_type == self.layer_types.SolderMaskLayer:
|
|
157
|
+
return 10
|
|
158
|
+
elif layer_type == self.layer_types.SolderPasteLayer:
|
|
159
|
+
return 11
|
|
160
|
+
elif layer_type == self.layer_types.GlueLayer:
|
|
161
|
+
return 12
|
|
162
|
+
elif layer_type == self.layer_types.WirebondLayer:
|
|
163
|
+
return 13
|
|
164
|
+
elif layer_type == self.layer_types.UserLayer:
|
|
165
|
+
return 14
|
|
166
|
+
elif layer_type == self.layer_types.SIwaveHFSSSolverRegions:
|
|
167
|
+
return 16
|
|
168
|
+
elif layer_type == self.layer_types.OutlineLayer:
|
|
169
|
+
return 18
|
|
170
|
+
elif isinstance(layer_type, int):
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
@pyedb_function_handler()
|
|
174
|
+
def create_symmetric_stackup(
|
|
175
|
+
self,
|
|
176
|
+
layer_count,
|
|
177
|
+
inner_layer_thickness="17um",
|
|
178
|
+
outer_layer_thickness="50um",
|
|
179
|
+
dielectric_thickness="100um",
|
|
180
|
+
dielectric_material="fr4_epoxy",
|
|
181
|
+
soldermask=True,
|
|
182
|
+
soldermask_thickness="20um",
|
|
183
|
+
): # pragma: no cover
|
|
184
|
+
"""Create a symmetric stackup.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
layer_count : int
|
|
189
|
+
Number of layer count.
|
|
190
|
+
inner_layer_thickness : str, float, optional
|
|
191
|
+
Thickness of inner conductor layer.
|
|
192
|
+
outer_layer_thickness : str, float, optional
|
|
193
|
+
Thickness of outer conductor layer.
|
|
194
|
+
dielectric_thickness : str, float, optional
|
|
195
|
+
Thickness of dielectric layer.
|
|
196
|
+
dielectric_material : str, optional
|
|
197
|
+
Material of dielectric layer.
|
|
198
|
+
soldermask : bool, optional
|
|
199
|
+
Whether to create soldermask layers. The default is``True``.
|
|
200
|
+
soldermask_thickness : str, optional
|
|
201
|
+
Thickness of soldermask layer.
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
bool
|
|
206
|
+
"""
|
|
207
|
+
if not np:
|
|
208
|
+
self._pedb.logger.error("Numpy is needed. Please, install it first.")
|
|
209
|
+
return False
|
|
210
|
+
if not layer_count % 2 == 0:
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
self.add_layer(
|
|
214
|
+
"BOT",
|
|
215
|
+
None,
|
|
216
|
+
material="copper",
|
|
217
|
+
thickness=outer_layer_thickness,
|
|
218
|
+
fillMaterial=dielectric_material,
|
|
219
|
+
)
|
|
220
|
+
self.add_layer(
|
|
221
|
+
"D" + str(int(layer_count / 2)),
|
|
222
|
+
None,
|
|
223
|
+
material="fr4_epoxy",
|
|
224
|
+
thickness=dielectric_thickness,
|
|
225
|
+
layer_type="dielectric",
|
|
226
|
+
fillMaterial=dielectric_material,
|
|
227
|
+
)
|
|
228
|
+
self.add_layer(
|
|
229
|
+
"TOP",
|
|
230
|
+
None,
|
|
231
|
+
material="copper",
|
|
232
|
+
thickness=outer_layer_thickness,
|
|
233
|
+
fillMaterial=dielectric_material,
|
|
234
|
+
)
|
|
235
|
+
if soldermask:
|
|
236
|
+
self.add_layer(
|
|
237
|
+
"SMT",
|
|
238
|
+
None,
|
|
239
|
+
material="solder_mask",
|
|
240
|
+
thickness=soldermask_thickness,
|
|
241
|
+
layer_type="dielectric",
|
|
242
|
+
fillMaterial=dielectric_material,
|
|
243
|
+
)
|
|
244
|
+
self.add_layer(
|
|
245
|
+
"SMB",
|
|
246
|
+
None,
|
|
247
|
+
material="solder_mask",
|
|
248
|
+
thickness=soldermask_thickness,
|
|
249
|
+
layer_type="dielectric",
|
|
250
|
+
fillMaterial=dielectric_material,
|
|
251
|
+
method="add_on_bottom",
|
|
252
|
+
)
|
|
253
|
+
self.stackup_layers["TOP"].dielectric_fill = "solder_mask"
|
|
254
|
+
self.stackup_layers["BOT"].dielectric_fill = "solder_mask"
|
|
255
|
+
|
|
256
|
+
for layer_num in np.arange(int(layer_count / 2), 1, -1):
|
|
257
|
+
# Generate upper half
|
|
258
|
+
self.add_layer(
|
|
259
|
+
"L" + str(layer_num),
|
|
260
|
+
"TOP",
|
|
261
|
+
material="copper",
|
|
262
|
+
thickness=inner_layer_thickness,
|
|
263
|
+
fillMaterial=dielectric_material,
|
|
264
|
+
method="insert_below",
|
|
265
|
+
)
|
|
266
|
+
self.add_layer(
|
|
267
|
+
"D" + str(layer_num - 1),
|
|
268
|
+
"TOP",
|
|
269
|
+
material=dielectric_material,
|
|
270
|
+
thickness=dielectric_thickness,
|
|
271
|
+
layer_type="dielectric",
|
|
272
|
+
fillMaterial=dielectric_material,
|
|
273
|
+
method="insert_below",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Generate lower half
|
|
277
|
+
self.add_layer(
|
|
278
|
+
"L" + str(layer_count - layer_num + 1),
|
|
279
|
+
"BOT",
|
|
280
|
+
material="copper",
|
|
281
|
+
thickness=inner_layer_thickness,
|
|
282
|
+
fillMaterial=dielectric_material,
|
|
283
|
+
method="insert_above",
|
|
284
|
+
)
|
|
285
|
+
self.add_layer(
|
|
286
|
+
"D" + str(layer_count - layer_num + 1),
|
|
287
|
+
"BOT",
|
|
288
|
+
material=dielectric_material,
|
|
289
|
+
thickness=dielectric_thickness,
|
|
290
|
+
layer_type="dielectric",
|
|
291
|
+
fillMaterial=dielectric_material,
|
|
292
|
+
method="insert_above",
|
|
293
|
+
)
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
@pyedb_function_handler()
|
|
297
|
+
def refresh_layer_collection(self):
|
|
298
|
+
"""Refresh layer collection from Edb. This method is run on demand after all edit operations on stackup."""
|
|
299
|
+
lc_readonly = self._pedb.layout.layer_collection
|
|
300
|
+
layers = [
|
|
301
|
+
i.Clone() for i in list(list(lc_readonly.Layers(self._pedb.edb_api.cell.layer_type_set.StackupLayerSet)))
|
|
302
|
+
]
|
|
303
|
+
non_stackup = [
|
|
304
|
+
i.Clone() for i in list(list(lc_readonly.Layers(self._pedb.edb_api.cell.layer_type_set.NonStackupLayerSet)))
|
|
305
|
+
]
|
|
306
|
+
self._lc = self._pedb.edb_api.cell._cell.LayerCollection()
|
|
307
|
+
mode = lc_readonly.GetMode()
|
|
308
|
+
self._lc.SetMode(lc_readonly.GetMode())
|
|
309
|
+
if str(mode) == "Overlapping":
|
|
310
|
+
for layer in layers:
|
|
311
|
+
self._lc.AddStackupLayerAtElevation(layer)
|
|
312
|
+
elif str(mode) == "Laminate":
|
|
313
|
+
for layer in layers:
|
|
314
|
+
self._lc.AddLayerBottom(layer)
|
|
315
|
+
else:
|
|
316
|
+
self._lc.AddLayers(convert_py_list_to_net_list(layers, self._pedb.edb_api.cell.layer))
|
|
317
|
+
for layer in non_stackup:
|
|
318
|
+
self._lc.AddLayerBottom(layer)
|
|
319
|
+
self._lc.SetMode(lc_readonly.GetMode())
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def _layer_collection(self):
|
|
323
|
+
"""Copy of EDB layer collection.
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
:class:`Ansys.Ansoft.Edb.Cell.LayerCollection`
|
|
328
|
+
Collection of layers.
|
|
329
|
+
"""
|
|
330
|
+
if not self._lc:
|
|
331
|
+
self.refresh_layer_collection()
|
|
332
|
+
return self._lc
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def mode(self):
|
|
336
|
+
"""Stackup mode.
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
int, str
|
|
341
|
+
Type of the stackup mode, where:
|
|
342
|
+
|
|
343
|
+
* 0 - Laminate
|
|
344
|
+
* 1 - Overlapping
|
|
345
|
+
* 2 - MultiZone
|
|
346
|
+
"""
|
|
347
|
+
self._stackup_mode = self._layer_collection.GetMode()
|
|
348
|
+
return str(self._stackup_mode)
|
|
349
|
+
|
|
350
|
+
@mode.setter
|
|
351
|
+
def mode(self, value):
|
|
352
|
+
mode = self._pedb.edb_api.Cell.LayerCollectionMode
|
|
353
|
+
if value == 0 or value == mode.Laminate or value == "Laminate":
|
|
354
|
+
self._layer_collection.SetMode(mode.Laminate)
|
|
355
|
+
elif value == 1 or value == mode.Overlapping or value == "Overlapping":
|
|
356
|
+
self._layer_collection.SetMode(mode.Overlapping)
|
|
357
|
+
elif value == 2 or value == mode.MultiZone or value == "MultiZone":
|
|
358
|
+
self._layer_collection.SetMode(mode.MultiZone)
|
|
359
|
+
self._pedb.layout.layer_collection = self._layer_collection
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def stackup_mode(self):
|
|
363
|
+
"""Stackup mode.
|
|
364
|
+
|
|
365
|
+
.. deprecated:: 0.6.52
|
|
366
|
+
Use :func:`mode` method instead.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
int, str
|
|
371
|
+
Type of the stackup mode, where:
|
|
372
|
+
|
|
373
|
+
* 0 - Laminate
|
|
374
|
+
* 1 - Overlapping
|
|
375
|
+
* 2 - MultiZone
|
|
376
|
+
"""
|
|
377
|
+
warnings.warn("`stackup_mode` is deprecated. Use `mode` method instead.", DeprecationWarning)
|
|
378
|
+
return self.mode
|
|
379
|
+
|
|
380
|
+
@stackup_mode.setter
|
|
381
|
+
def stackup_mode(self, value):
|
|
382
|
+
warnings.warn("`stackup_mode` is deprecated. Use `mode` method instead.", DeprecationWarning)
|
|
383
|
+
self.mode = value
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def _edb_layer_list(self):
|
|
387
|
+
layer_list = list(self._layer_collection.Layers(self._pedb.edb_api.cell.layer_type_set.AllLayerSet))
|
|
388
|
+
return [i.Clone() for i in layer_list]
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def _edb_layer_list_nonstackup(self):
|
|
392
|
+
layer_list = list(self._layer_collection.Layers(self._pedb.edb_api.cell.layer_type_set.NonStackupLayerSet))
|
|
393
|
+
return [i.Clone() for i in layer_list]
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def layers(self):
|
|
397
|
+
"""Retrieve the dictionary of layers.
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
Dict[str, :class:`pyedb.dotnet.edb_core.edb_data.layer_data.LayerEdbClass`]
|
|
402
|
+
"""
|
|
403
|
+
_lays = OrderedDict()
|
|
404
|
+
for l in self._edb_layer_list:
|
|
405
|
+
name = l.GetName()
|
|
406
|
+
if not l.IsStackupLayer():
|
|
407
|
+
_lays[name] = LayerEdbClass(self, name)
|
|
408
|
+
else:
|
|
409
|
+
_lays[name] = StackupLayerEdbClass(self, name)
|
|
410
|
+
return _lays
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def signal_layers(self):
|
|
414
|
+
"""Retrieve the dictionary of signal layers.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
Dict[str, :class:`pyedb.dotnet.edb_core.edb_data.layer_data.LayerEdbClass`]
|
|
419
|
+
"""
|
|
420
|
+
layer_type = self._pedb.edb_api.cell.layer_type.SignalLayer
|
|
421
|
+
_lays = OrderedDict()
|
|
422
|
+
for name, obj in self.layers.items():
|
|
423
|
+
if obj._edb_layer.GetLayerType() == layer_type:
|
|
424
|
+
_lays[name] = obj
|
|
425
|
+
return _lays
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def stackup_layers(self):
|
|
429
|
+
"""Retrieve the dictionary of signal and dielectric layers.
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
Dict[str, :class:`dotnet.edb_core.edb_data.layer_data.LayerEdbClass`]
|
|
434
|
+
"""
|
|
435
|
+
layer_type = [
|
|
436
|
+
self._pedb.edb_api.cell.layer_type.SignalLayer,
|
|
437
|
+
self._pedb.edb_api.cell.layer_type.DielectricLayer,
|
|
438
|
+
]
|
|
439
|
+
_lays = OrderedDict()
|
|
440
|
+
for name, obj in self.layers.items():
|
|
441
|
+
if obj._edb_layer.GetLayerType() in layer_type:
|
|
442
|
+
_lays[name] = obj
|
|
443
|
+
return _lays
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def dielectric_layers(self):
|
|
447
|
+
"""Dielectric layers.
|
|
448
|
+
|
|
449
|
+
Returns
|
|
450
|
+
-------
|
|
451
|
+
dict[str, :class:`dotnet.edb_core.edb_data.layer_data.EDBLayer`]
|
|
452
|
+
Dictionary of dielectric layers.
|
|
453
|
+
"""
|
|
454
|
+
layer_type = self._pedb.edb_api.cell.layer_type.DielectricLayer
|
|
455
|
+
_lays = OrderedDict()
|
|
456
|
+
for name, obj in self.layers.items():
|
|
457
|
+
if obj._edb_layer.GetLayerType() == layer_type:
|
|
458
|
+
_lays[name] = obj
|
|
459
|
+
return _lays
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def non_stackup_layers(self):
|
|
463
|
+
"""Retrieve the dictionary of signal layers.
|
|
464
|
+
|
|
465
|
+
Returns
|
|
466
|
+
-------
|
|
467
|
+
Dict[str, :class:`dotnet.edb_core.edb_data.layer_data.LayerEdbClass`]
|
|
468
|
+
"""
|
|
469
|
+
return {l.GetName(): LayerEdbClass(self, l.GetName()) for l in self._edb_layer_list_nonstackup}
|
|
470
|
+
|
|
471
|
+
@pyedb_function_handler()
|
|
472
|
+
def _edb_value(self, value):
|
|
473
|
+
return self._pedb.edb_value(value)
|
|
474
|
+
|
|
475
|
+
@pyedb_function_handler()
|
|
476
|
+
def _set_layout_stackup(self, layer_clone, operation, base_layer=None, method=1):
|
|
477
|
+
"""Internal method. Apply stackup change into EDB.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
layer_clone : :class:`dotnet.edb_core.EDB_Data.EDBLayer`
|
|
482
|
+
operation : str
|
|
483
|
+
Options are ``"change_attribute"``, ``"change_name"``,``"change_position"``, ``"insert_below"``,
|
|
484
|
+
``"insert_above"``, ``"add_on_top"``, ``"add_on_bottom"``, ``"non_stackup"``, ``"add_at_elevation"``.
|
|
485
|
+
base_layer : str, optional
|
|
486
|
+
Name of the base layer. The default value is ``None``.
|
|
487
|
+
|
|
488
|
+
Returns
|
|
489
|
+
-------
|
|
490
|
+
|
|
491
|
+
"""
|
|
492
|
+
_lc = self._layer_collection
|
|
493
|
+
if operation in ["change_position", "change_attribute", "change_name"]:
|
|
494
|
+
lc_readonly = self._pedb.layout.layer_collection
|
|
495
|
+
layers = [
|
|
496
|
+
i.Clone()
|
|
497
|
+
for i in list(list(lc_readonly.Layers(self._pedb.edb_api.cell.layer_type_set.StackupLayerSet)))
|
|
498
|
+
]
|
|
499
|
+
non_stackup = [
|
|
500
|
+
i.Clone()
|
|
501
|
+
for i in list(list(lc_readonly.Layers(self._pedb.edb_api.cell.layer_type_set.NonStackupLayerSet)))
|
|
502
|
+
]
|
|
503
|
+
_lc = self._pedb.edb_api.cell._cell.LayerCollection()
|
|
504
|
+
mode = lc_readonly.GetMode()
|
|
505
|
+
_lc.SetMode(lc_readonly.GetMode())
|
|
506
|
+
if str(mode) == "Overlapping":
|
|
507
|
+
for layer in layers:
|
|
508
|
+
if layer.GetName() == layer_clone.GetName() or layer.GetName() == base_layer:
|
|
509
|
+
_lc.AddStackupLayerAtElevation(layer_clone)
|
|
510
|
+
else:
|
|
511
|
+
_lc.AddStackupLayerAtElevation(layer)
|
|
512
|
+
else:
|
|
513
|
+
for layer in layers:
|
|
514
|
+
if layer.GetName() == layer_clone.GetName() or layer.GetName() == base_layer:
|
|
515
|
+
_lc.AddLayerBottom(layer_clone)
|
|
516
|
+
else:
|
|
517
|
+
_lc.AddLayerBottom(layer)
|
|
518
|
+
for layer in non_stackup:
|
|
519
|
+
_lc.AddLayerBottom(layer)
|
|
520
|
+
_lc.SetMode(lc_readonly.GetMode())
|
|
521
|
+
elif operation == "insert_below":
|
|
522
|
+
_lc.AddLayerBelow(layer_clone, base_layer)
|
|
523
|
+
elif operation == "insert_above":
|
|
524
|
+
_lc.AddLayerAbove(layer_clone, base_layer)
|
|
525
|
+
elif operation == "add_on_top":
|
|
526
|
+
_lc.AddLayerTop(layer_clone)
|
|
527
|
+
elif operation == "add_on_bottom":
|
|
528
|
+
_lc.AddLayerBottom(layer_clone)
|
|
529
|
+
elif operation == "add_at_elevation":
|
|
530
|
+
_lc.AddStackupLayerAtElevation(layer_clone)
|
|
531
|
+
elif operation == "non_stackup":
|
|
532
|
+
_lc.AddLayerBottom(layer_clone)
|
|
533
|
+
self._pedb.layout.layer_collection = _lc
|
|
534
|
+
self.refresh_layer_collection()
|
|
535
|
+
return True
|
|
536
|
+
|
|
537
|
+
@pyedb_function_handler()
|
|
538
|
+
def _create_stackup_layer(self, layer_name, thickness, layer_type="signal"):
|
|
539
|
+
if layer_type == "signal":
|
|
540
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.SignalLayer
|
|
541
|
+
else:
|
|
542
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.DielectricLayer
|
|
543
|
+
|
|
544
|
+
result = self._pedb.edb_api.cell._cell.StackupLayer(
|
|
545
|
+
layer_name,
|
|
546
|
+
_layer_type,
|
|
547
|
+
self._edb_value(thickness),
|
|
548
|
+
self._edb_value(0),
|
|
549
|
+
"",
|
|
550
|
+
)
|
|
551
|
+
self.refresh_layer_collection()
|
|
552
|
+
return result
|
|
553
|
+
|
|
554
|
+
@pyedb_function_handler()
|
|
555
|
+
def _create_nonstackup_layer(self, layer_name, layer_type):
|
|
556
|
+
if layer_type == "conducting": # pragma: no cover
|
|
557
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.ConductingLayer
|
|
558
|
+
elif layer_type == "airlines": # pragma: no cover
|
|
559
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.AirlinesLayer
|
|
560
|
+
elif layer_type == "error": # pragma: no cover
|
|
561
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.ErrorsLayer
|
|
562
|
+
elif layer_type == "symbol": # pragma: no cover
|
|
563
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.SymbolLayer
|
|
564
|
+
elif layer_type == "measure": # pragma: no cover
|
|
565
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.MeasureLayer
|
|
566
|
+
elif layer_type == "assembly": # pragma: no cover
|
|
567
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.AssemblyLayer
|
|
568
|
+
elif layer_type == "silkscreen": # pragma: no cover
|
|
569
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.SilkscreenLayer
|
|
570
|
+
elif layer_type == "soldermask": # pragma: no cover
|
|
571
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.SolderMaskLayer
|
|
572
|
+
elif layer_type == "solderpaste": # pragma: no cover
|
|
573
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.SolderPasteLayer
|
|
574
|
+
elif layer_type == "glue": # pragma: no cover
|
|
575
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.GlueLayer
|
|
576
|
+
elif layer_type == "wirebond": # pragma: no cover
|
|
577
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.WirebondLayer
|
|
578
|
+
elif layer_type == "user": # pragma: no cover
|
|
579
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.UserLayer
|
|
580
|
+
elif layer_type == "siwavehfsssolverregions": # pragma: no cover
|
|
581
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.SIwaveHFSSSolverRegions
|
|
582
|
+
elif layer_type == "outline": # pragma: no cover
|
|
583
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.OutlineLayer
|
|
584
|
+
elif layer_type == "postprocessing": # pragma: no cover
|
|
585
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.PostprocessingLayer
|
|
586
|
+
else: # pragma: no cover
|
|
587
|
+
_layer_type = self._pedb.edb_api.cell.layer_type.UndefinedLayerType
|
|
588
|
+
|
|
589
|
+
result = self._pedb.edb_api.cell.layer(layer_name, _layer_type)
|
|
590
|
+
self.refresh_layer_collection()
|
|
591
|
+
return result
|
|
592
|
+
|
|
593
|
+
@pyedb_function_handler()
|
|
594
|
+
def add_outline_layer(self, outline_name="Outline"):
|
|
595
|
+
"""Add an outline layer named ``"Outline"`` if it is not present.
|
|
596
|
+
|
|
597
|
+
Returns
|
|
598
|
+
-------
|
|
599
|
+
bool
|
|
600
|
+
"True" if successful, ``False`` if failed.
|
|
601
|
+
"""
|
|
602
|
+
outlineLayer = self._pedb.edb_api.cell.layer.FindByName(self._pedb.layout.layer_collection, outline_name)
|
|
603
|
+
if outlineLayer.IsNull():
|
|
604
|
+
return self.add_layer(
|
|
605
|
+
outline_name,
|
|
606
|
+
layer_type="outline",
|
|
607
|
+
material="",
|
|
608
|
+
fillMaterial="",
|
|
609
|
+
thickness="",
|
|
610
|
+
)
|
|
611
|
+
else:
|
|
612
|
+
return False
|
|
613
|
+
|
|
614
|
+
@pyedb_function_handler()
|
|
615
|
+
def add_layer(
|
|
616
|
+
self,
|
|
617
|
+
layer_name,
|
|
618
|
+
base_layer=None,
|
|
619
|
+
method="add_on_top",
|
|
620
|
+
layer_type="signal",
|
|
621
|
+
material="copper",
|
|
622
|
+
fillMaterial="fr4_epoxy",
|
|
623
|
+
thickness="35um",
|
|
624
|
+
etch_factor=None,
|
|
625
|
+
is_negative=False,
|
|
626
|
+
enable_roughness=False,
|
|
627
|
+
elevation=None,
|
|
628
|
+
):
|
|
629
|
+
"""Insert a layer into stackup.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
layer_name : str
|
|
634
|
+
Name of the layer.
|
|
635
|
+
base_layer : str, optional
|
|
636
|
+
Name of the base layer.
|
|
637
|
+
method : str, optional
|
|
638
|
+
Where to insert the new layer. The default is ``"add_on_top"``. Options are ``"add_on_top"``,
|
|
639
|
+
``"add_on_bottom"``, ``"insert_above"``, ``"insert_below"``, ``"add_at_elevation"``,.
|
|
640
|
+
layer_type : str, optional
|
|
641
|
+
Type of layer. The default is ``"signal"``. Options are ``"signal"``, ``"dielectric"``, ``"conducting"``,
|
|
642
|
+
``"air_lines"``, ``"error"``, ``"symbol"``, ``"measure"``, ``"assembly"``, ``"silkscreen"``,
|
|
643
|
+
``"solder_mask"``, ``"solder_paste"``, ``"glue"``, ``"wirebond"``, ``"hfss_region"``, ``"user"``.
|
|
644
|
+
material : str, optional
|
|
645
|
+
Material of the layer.
|
|
646
|
+
fillMaterial : str, optional
|
|
647
|
+
Fill material of the layer.
|
|
648
|
+
thickness : str, float, optional
|
|
649
|
+
Thickness of the layer.
|
|
650
|
+
etch_factor : int, float, optional
|
|
651
|
+
Etch factor of the layer.
|
|
652
|
+
is_negative : bool, optional
|
|
653
|
+
Whether the layer is negative.
|
|
654
|
+
enable_roughness : bool, optional
|
|
655
|
+
Whether roughness is enabled.
|
|
656
|
+
elevation : float, optional
|
|
657
|
+
Elevation of new layer. Only valid for Overlapping Stackup.
|
|
658
|
+
|
|
659
|
+
Returns
|
|
660
|
+
-------
|
|
661
|
+
:class:`pyedb.dotnet.edb_core.edb_data.layer_data.LayerEdbClass`
|
|
662
|
+
"""
|
|
663
|
+
if layer_name in self.layers:
|
|
664
|
+
logger.error("layer {} exists.".format(layer_name))
|
|
665
|
+
return False
|
|
666
|
+
materials_lower = {m.lower(): m for m in list(self._pedb.materials.materials.keys())}
|
|
667
|
+
if not material:
|
|
668
|
+
if layer_type == "signal":
|
|
669
|
+
material = "copper"
|
|
670
|
+
else:
|
|
671
|
+
material = "fr4_epoxy"
|
|
672
|
+
if not fillMaterial:
|
|
673
|
+
fillMaterial = "fr4_epoxy"
|
|
674
|
+
|
|
675
|
+
# TODO: Remove
|
|
676
|
+
if material.lower() not in materials_lower:
|
|
677
|
+
# if material in self._pedb.materials.materials_in_aedt:
|
|
678
|
+
# self._pedb.materials.add_material_from_aedt("material")
|
|
679
|
+
# else:
|
|
680
|
+
# logger.error(material + " does not exist in material library")
|
|
681
|
+
logger.error(material + " does not exist in material library")
|
|
682
|
+
else:
|
|
683
|
+
material = materials_lower[material.lower()]
|
|
684
|
+
|
|
685
|
+
if layer_type != "dielectric":
|
|
686
|
+
if fillMaterial.lower() not in materials_lower:
|
|
687
|
+
logger.error(fillMaterial + " does not exist in material library")
|
|
688
|
+
else:
|
|
689
|
+
fillMaterial = materials_lower[fillMaterial.lower()]
|
|
690
|
+
|
|
691
|
+
if layer_type in ["signal", "dielectric"]:
|
|
692
|
+
new_layer = self._create_stackup_layer(layer_name, thickness, layer_type)
|
|
693
|
+
new_layer.SetMaterial(material)
|
|
694
|
+
if layer_type != "dielectric":
|
|
695
|
+
new_layer.SetFillMaterial(fillMaterial)
|
|
696
|
+
new_layer.SetNegative(is_negative)
|
|
697
|
+
l1 = len(self.layers)
|
|
698
|
+
if method == "add_at_elevation" and elevation:
|
|
699
|
+
new_layer.SetLowerElevation(self._pedb.edb_value(elevation))
|
|
700
|
+
self._set_layout_stackup(new_layer, method, base_layer)
|
|
701
|
+
if len(self.layers) == l1:
|
|
702
|
+
self._set_layout_stackup(new_layer, method, base_layer, method=2)
|
|
703
|
+
if etch_factor:
|
|
704
|
+
new_layer = self.layers[layer_name]
|
|
705
|
+
new_layer.etch_factor = etch_factor
|
|
706
|
+
if enable_roughness:
|
|
707
|
+
new_layer = self.layers[layer_name]
|
|
708
|
+
new_layer.roughness_enabled = True
|
|
709
|
+
else:
|
|
710
|
+
new_layer = self._create_nonstackup_layer(layer_name, layer_type)
|
|
711
|
+
self._set_layout_stackup(new_layer, "non_stackup")
|
|
712
|
+
self.refresh_layer_collection()
|
|
713
|
+
return self.layers[layer_name]
|
|
714
|
+
|
|
715
|
+
def remove_layer(self, name):
|
|
716
|
+
"""Remove a layer from stackup.
|
|
717
|
+
|
|
718
|
+
Parameters
|
|
719
|
+
----------
|
|
720
|
+
name : str
|
|
721
|
+
Name of the layer to remove.
|
|
722
|
+
|
|
723
|
+
Returns
|
|
724
|
+
-------
|
|
725
|
+
|
|
726
|
+
"""
|
|
727
|
+
new_layer_collection = self._pedb.edb_api.Cell.LayerCollection()
|
|
728
|
+
for lyr in self._edb_layer_list:
|
|
729
|
+
if not (lyr.GetName() == name):
|
|
730
|
+
new_layer_collection.AddLayerBottom(lyr)
|
|
731
|
+
|
|
732
|
+
self._pedb.layout.layer_collection = new_layer_collection
|
|
733
|
+
self.refresh_layer_collection()
|
|
734
|
+
return True
|
|
735
|
+
|
|
736
|
+
@pyedb_function_handler()
|
|
737
|
+
def export(self, fpath, file_format="xml", include_material_with_layer=False):
|
|
738
|
+
"""Export stackup definition to a CSV or JSON file.
|
|
739
|
+
|
|
740
|
+
Parameters
|
|
741
|
+
----------
|
|
742
|
+
fpath : str
|
|
743
|
+
File path to csv or json file.
|
|
744
|
+
file_format : str, optional
|
|
745
|
+
Format of the file to export. The default is ``"csv"``. Options are ``"csv"``, ``"xlsx"``,
|
|
746
|
+
``"json"``.
|
|
747
|
+
include_material_with_layer : bool, optional.
|
|
748
|
+
Whether to include the material definition inside layer ones. This parameter is only used
|
|
749
|
+
when a JSON file is exported. The default is ``False``, which keeps the material definition
|
|
750
|
+
section in the JSON file. If ``True``, the material definition is included inside the layer ones.
|
|
751
|
+
|
|
752
|
+
Examples
|
|
753
|
+
--------
|
|
754
|
+
>>> from pyedb import Edb
|
|
755
|
+
>>> edb = Edb()
|
|
756
|
+
>>> edb.stackup.export("stackup.xml")
|
|
757
|
+
"""
|
|
758
|
+
if len(fpath.split(".")) == 1:
|
|
759
|
+
fpath = "{}.{}".format(fpath, file_format)
|
|
760
|
+
|
|
761
|
+
if fpath.endswith(".csv"):
|
|
762
|
+
return self._export_layer_stackup_to_csv_xlsx(fpath, file_format="csv")
|
|
763
|
+
elif fpath.endswith(".xlsx"):
|
|
764
|
+
return self._export_layer_stackup_to_csv_xlsx(fpath, file_format="xlsx")
|
|
765
|
+
elif fpath.endswith(".json"):
|
|
766
|
+
return self._export_layer_stackup_to_json(fpath, include_material_with_layer)
|
|
767
|
+
elif fpath.endswith(".xml"):
|
|
768
|
+
return self._export_xml(fpath)
|
|
769
|
+
else:
|
|
770
|
+
self._logger.warning("Layer stackup format is not supported. Skipping import.")
|
|
771
|
+
return False
|
|
772
|
+
|
|
773
|
+
@pyedb_function_handler()
|
|
774
|
+
def export_stackup(self, fpath, file_format="xml", include_material_with_layer=False):
|
|
775
|
+
"""Export stackup definition to a CSV or JSON file.
|
|
776
|
+
|
|
777
|
+
.. deprecated:: 0.6.61
|
|
778
|
+
Use :func:`export` instead.
|
|
779
|
+
|
|
780
|
+
Parameters
|
|
781
|
+
----------
|
|
782
|
+
fpath : str
|
|
783
|
+
File path to CSV or JSON file.
|
|
784
|
+
file_format : str, optional
|
|
785
|
+
Format of the file to export. The default is ``"csv"``. Options are ``"csv"``, ``"xlsx"``
|
|
786
|
+
and ``"json"``.
|
|
787
|
+
include_material_with_layer : bool, optional.
|
|
788
|
+
Whether to include the material definition inside layer objects. This parameter is only used
|
|
789
|
+
when a JSON file is exported. The default is ``False``, which keeps the material definition
|
|
790
|
+
section in the JSON file. If ``True``, the material definition is included inside the layer ones.
|
|
791
|
+
|
|
792
|
+
Examples
|
|
793
|
+
--------
|
|
794
|
+
>>> from pyedb import Edb
|
|
795
|
+
>>> edb = Edb()
|
|
796
|
+
>>> edb.stackup.export_stackup("stackup.xml")
|
|
797
|
+
"""
|
|
798
|
+
|
|
799
|
+
self._logger.warning("Method export_stackup is deprecated. Use .export.")
|
|
800
|
+
return self.export(fpath, file_format=file_format, include_material_with_layer=include_material_with_layer)
|
|
801
|
+
|
|
802
|
+
@pyedb_function_handler()
|
|
803
|
+
def _export_layer_stackup_to_csv_xlsx(self, fpath=None, file_format=None):
|
|
804
|
+
if not pd:
|
|
805
|
+
self._pedb.logger.error("Pandas is needed. Please, install it first.")
|
|
806
|
+
return False
|
|
807
|
+
if is_ironpython:
|
|
808
|
+
return
|
|
809
|
+
data = {
|
|
810
|
+
"Type": [],
|
|
811
|
+
"Material": [],
|
|
812
|
+
"Dielectric_Fill": [],
|
|
813
|
+
"Thickness": [],
|
|
814
|
+
}
|
|
815
|
+
idx = []
|
|
816
|
+
for lyr in self.stackup_layers.values():
|
|
817
|
+
idx.append(lyr.name)
|
|
818
|
+
data["Type"].append(lyr.type)
|
|
819
|
+
data["Material"].append(lyr.material)
|
|
820
|
+
data["Dielectric_Fill"].append(lyr.dielectric_fill)
|
|
821
|
+
data["Thickness"].append(lyr.thickness)
|
|
822
|
+
df = pd.DataFrame(data, index=idx, columns=["Type", "Material", "Dielectric_Fill", "Thickness"])
|
|
823
|
+
if file_format == "csv": # pragma: no cover
|
|
824
|
+
if not fpath.endswith(".csv"):
|
|
825
|
+
fpath = fpath + ".csv"
|
|
826
|
+
df.to_csv(fpath)
|
|
827
|
+
else: # pragma: no cover
|
|
828
|
+
if not fpath.endswith(".xlsx"): # pragma: no cover
|
|
829
|
+
fpath = fpath + ".xlsx"
|
|
830
|
+
df.to_excel(fpath)
|
|
831
|
+
return True
|
|
832
|
+
|
|
833
|
+
@pyedb_function_handler()
|
|
834
|
+
def _export_layer_stackup_to_json(self, output_file=None, include_material_with_layer=False):
|
|
835
|
+
if not include_material_with_layer:
|
|
836
|
+
material_out = {}
|
|
837
|
+
for k, v in self._pedb.materials.materials.items():
|
|
838
|
+
material_out[k] = v._json_format()
|
|
839
|
+
layers_out = {}
|
|
840
|
+
for k, v in self.stackup_layers.items():
|
|
841
|
+
layers_out[k] = v._json_format()
|
|
842
|
+
if v.material in self._pedb.materials.materials:
|
|
843
|
+
layer_material = self._pedb.materials.materials[v.material]
|
|
844
|
+
if not v.dielectric_fill:
|
|
845
|
+
dielectric_fill = False
|
|
846
|
+
else:
|
|
847
|
+
dielectric_fill = self._pedb.materials.materials[v.dielectric_fill]
|
|
848
|
+
if include_material_with_layer:
|
|
849
|
+
layers_out[k]["material"] = layer_material._json_format()
|
|
850
|
+
if dielectric_fill:
|
|
851
|
+
layers_out[k]["dielectric_fill"] = dielectric_fill._json_format()
|
|
852
|
+
if not include_material_with_layer:
|
|
853
|
+
stackup_out = {"materials": material_out, "layers": layers_out}
|
|
854
|
+
else:
|
|
855
|
+
stackup_out = {"layers": layers_out}
|
|
856
|
+
if output_file:
|
|
857
|
+
with open(output_file, "w") as write_file:
|
|
858
|
+
json.dump(stackup_out, write_file, indent=4)
|
|
859
|
+
|
|
860
|
+
return True
|
|
861
|
+
else:
|
|
862
|
+
return False
|
|
863
|
+
|
|
864
|
+
@pyedb_function_handler()
|
|
865
|
+
def _import_layer_stackup(self, input_file=None):
|
|
866
|
+
if input_file:
|
|
867
|
+
f = open(input_file)
|
|
868
|
+
json_dict = json.load(f) # pragma: no cover
|
|
869
|
+
for k, v in json_dict.items():
|
|
870
|
+
if k == "materials":
|
|
871
|
+
for material in v.values():
|
|
872
|
+
self._pedb.materials._load_materials(material)
|
|
873
|
+
if k == "layers":
|
|
874
|
+
if len(list(v.values())) == len(list(self.stackup_layers.values())):
|
|
875
|
+
imported_layers_list = [l_dict["name"] for l_dict in list(v.values())]
|
|
876
|
+
layout_layer_list = list(self.stackup_layers.keys())
|
|
877
|
+
for layer_name in imported_layers_list:
|
|
878
|
+
layer_index = imported_layers_list.index(layer_name)
|
|
879
|
+
if layout_layer_list[layer_index] != layer_name:
|
|
880
|
+
self.stackup_layers[layout_layer_list[layer_index]].name = layer_name
|
|
881
|
+
prev_layer = None
|
|
882
|
+
for layer_name, layer in v.items():
|
|
883
|
+
if layer["name"] not in self.stackup_layers:
|
|
884
|
+
if not prev_layer:
|
|
885
|
+
self.add_layer(
|
|
886
|
+
layer_name,
|
|
887
|
+
method="add_on_top",
|
|
888
|
+
layer_type=layer["type"],
|
|
889
|
+
material=layer["material"],
|
|
890
|
+
fillMaterial=layer["dielectric_fill"],
|
|
891
|
+
thickness=layer["thickness"],
|
|
892
|
+
)
|
|
893
|
+
prev_layer = layer_name
|
|
894
|
+
else:
|
|
895
|
+
self.add_layer(
|
|
896
|
+
layer_name,
|
|
897
|
+
base_layer=layer_name,
|
|
898
|
+
method="insert_below",
|
|
899
|
+
layer_type=layer["type"],
|
|
900
|
+
material=layer["material"],
|
|
901
|
+
fillMaterial=layer["dielectric_fill"],
|
|
902
|
+
thickness=layer["thickness"],
|
|
903
|
+
)
|
|
904
|
+
prev_layer = layer_name
|
|
905
|
+
if layer_name in self.stackup_layers:
|
|
906
|
+
self.stackup_layers[layer["name"]]._load_layer(layer)
|
|
907
|
+
self.refresh_layer_collection()
|
|
908
|
+
return True
|
|
909
|
+
|
|
910
|
+
@pyedb_function_handler()
|
|
911
|
+
def stackup_limits(self, only_metals=False):
|
|
912
|
+
"""Retrieve stackup limits.
|
|
913
|
+
|
|
914
|
+
.. deprecated:: 0.6.62
|
|
915
|
+
Use :func:`Edb.stackup.limits` function instead.
|
|
916
|
+
|
|
917
|
+
Parameters
|
|
918
|
+
----------
|
|
919
|
+
only_metals : bool, optional
|
|
920
|
+
Whether to retrieve only metals. The default is ``False``.
|
|
921
|
+
|
|
922
|
+
Returns
|
|
923
|
+
-------
|
|
924
|
+
bool
|
|
925
|
+
``True`` when successful, ``False`` when failed.
|
|
926
|
+
"""
|
|
927
|
+
warnings.warn("`stackup_limits` is deprecated. Use `limits` property instead.", DeprecationWarning)
|
|
928
|
+
return self.limits(only_metals=only_metals)
|
|
929
|
+
|
|
930
|
+
@pyedb_function_handler()
|
|
931
|
+
def limits(self, only_metals=False):
|
|
932
|
+
"""Retrieve stackup limits.
|
|
933
|
+
|
|
934
|
+
Parameters
|
|
935
|
+
----------
|
|
936
|
+
only_metals : bool, optional
|
|
937
|
+
Whether to retrieve only metals. The default is ``False``.
|
|
938
|
+
|
|
939
|
+
Returns
|
|
940
|
+
-------
|
|
941
|
+
bool
|
|
942
|
+
``True`` when successful, ``False`` when failed.
|
|
943
|
+
"""
|
|
944
|
+
if only_metals:
|
|
945
|
+
input_layers = self._pedb.edb_api.cell.layer_type_set.SignalLayerSet
|
|
946
|
+
else:
|
|
947
|
+
input_layers = self._pedb.edb_api.cell.layer_type_set.StackupLayerSet
|
|
948
|
+
|
|
949
|
+
res, topl, topz, bottoml, bottomz = self._layer_collection.GetTopBottomStackupLayers(input_layers)
|
|
950
|
+
return topl.GetName(), topz, bottoml.GetName(), bottomz
|
|
951
|
+
|
|
952
|
+
@pyedb_function_handler()
|
|
953
|
+
def flip_design(self):
|
|
954
|
+
"""Flip the current design of a layout.
|
|
955
|
+
|
|
956
|
+
Returns
|
|
957
|
+
-------
|
|
958
|
+
bool
|
|
959
|
+
``True`` when succeed ``False`` if not.
|
|
960
|
+
|
|
961
|
+
Examples
|
|
962
|
+
--------
|
|
963
|
+
>>> edb = Edb(edbpath=targetfile, edbversion="2021.2")
|
|
964
|
+
>>> edb.stackup.flip_design()
|
|
965
|
+
>>> edb.save()
|
|
966
|
+
>>> edb.close_edb()
|
|
967
|
+
"""
|
|
968
|
+
try:
|
|
969
|
+
lc = self._layer_collection
|
|
970
|
+
new_lc = self._pedb.edb_api.Cell.LayerCollection()
|
|
971
|
+
lc_mode = lc.GetMode()
|
|
972
|
+
new_lc.SetMode(lc_mode)
|
|
973
|
+
max_elevation = 0.0
|
|
974
|
+
for layer in lc.Layers(self._pedb.edb_api.cell.layer_type_set.StackupLayerSet):
|
|
975
|
+
if "RadBox" not in layer.GetName(): # Ignore RadBox
|
|
976
|
+
lower_elevation = layer.Clone().GetLowerElevation() * 1.0e6
|
|
977
|
+
upper_elevation = layer.Clone().GetUpperElevation() * 1.0e6
|
|
978
|
+
max_elevation = max([max_elevation, lower_elevation, upper_elevation])
|
|
979
|
+
|
|
980
|
+
non_stackup_layers = []
|
|
981
|
+
for layer in lc.Layers(self._pedb.edb_api.cell.layer_type_set.AllLayerSet):
|
|
982
|
+
cloned_layer = layer.Clone()
|
|
983
|
+
if not cloned_layer.IsStackupLayer():
|
|
984
|
+
non_stackup_layers.append(cloned_layer)
|
|
985
|
+
continue
|
|
986
|
+
if "RadBox" not in cloned_layer.GetName() and not cloned_layer.IsViaLayer():
|
|
987
|
+
upper_elevation = cloned_layer.GetUpperElevation() * 1.0e6
|
|
988
|
+
updated_lower_el = max_elevation - upper_elevation
|
|
989
|
+
val = self._edb_value("{}um".format(updated_lower_el))
|
|
990
|
+
cloned_layer.SetLowerElevation(val)
|
|
991
|
+
if (
|
|
992
|
+
cloned_layer.GetTopBottomAssociation()
|
|
993
|
+
== self._pedb.edb_api.Cell.TopBottomAssociation.TopAssociated
|
|
994
|
+
):
|
|
995
|
+
cloned_layer.SetTopBottomAssociation(
|
|
996
|
+
self._pedb.edb_api.Cell.TopBottomAssociation.BottomAssociated
|
|
997
|
+
)
|
|
998
|
+
else:
|
|
999
|
+
cloned_layer.SetTopBottomAssociation(self._pedb.edb_api.Cell.TopBottomAssociation.TopAssociated)
|
|
1000
|
+
new_lc.AddStackupLayerAtElevation(cloned_layer)
|
|
1001
|
+
|
|
1002
|
+
vialayers = [
|
|
1003
|
+
lay
|
|
1004
|
+
for lay in lc.Layers(self._pedb.edb_api.cell.layer_type_set.StackupLayerSet)
|
|
1005
|
+
if lay.Clone().IsViaLayer()
|
|
1006
|
+
]
|
|
1007
|
+
for layer in vialayers:
|
|
1008
|
+
cloned_via_layer = layer.Clone()
|
|
1009
|
+
upper_ref_name = cloned_via_layer.GetRefLayerName(True)
|
|
1010
|
+
lower_ref_name = cloned_via_layer.GetRefLayerName(False)
|
|
1011
|
+
upper_ref = [
|
|
1012
|
+
lay
|
|
1013
|
+
for lay in lc.Layers(self._pedb.edb_api.cell.layer_type_set.AllLayerSet)
|
|
1014
|
+
if lay.GetName() == upper_ref_name
|
|
1015
|
+
][0]
|
|
1016
|
+
lower_ref = [
|
|
1017
|
+
lay
|
|
1018
|
+
for lay in lc.Layers(self._pedb.edb_api.cell.layer_type_set.AllLayerSet)
|
|
1019
|
+
if lay.GetName() == lower_ref_name
|
|
1020
|
+
][0]
|
|
1021
|
+
cloned_via_layer.SetRefLayer(lower_ref, True)
|
|
1022
|
+
cloned_via_layer.SetRefLayer(upper_ref, False)
|
|
1023
|
+
ref_layer_in_flipped_stackup = [
|
|
1024
|
+
lay
|
|
1025
|
+
for lay in new_lc.Layers(self._pedb.edb_api.cell.layer_type_set.AllLayerSet)
|
|
1026
|
+
if lay.GetName() == upper_ref_name
|
|
1027
|
+
][0]
|
|
1028
|
+
via_layer_lower_elevation = (
|
|
1029
|
+
ref_layer_in_flipped_stackup.GetLowerElevation() + ref_layer_in_flipped_stackup.GetThickness()
|
|
1030
|
+
)
|
|
1031
|
+
cloned_via_layer.SetLowerElevation(self._edb_value(via_layer_lower_elevation))
|
|
1032
|
+
new_lc.AddStackupLayerAtElevation(cloned_via_layer)
|
|
1033
|
+
|
|
1034
|
+
layer_list = convert_py_list_to_net_list(non_stackup_layers)
|
|
1035
|
+
new_lc.AddLayers(layer_list)
|
|
1036
|
+
self._pedb.layout.layer_collection = new_lc
|
|
1037
|
+
|
|
1038
|
+
for pyaedt_cmp in list(self._pedb.components.components.values()):
|
|
1039
|
+
cmp = pyaedt_cmp.edbcomponent
|
|
1040
|
+
cmp_type = cmp.GetComponentType()
|
|
1041
|
+
cmp_prop = cmp.GetComponentProperty().Clone()
|
|
1042
|
+
try:
|
|
1043
|
+
if (
|
|
1044
|
+
cmp_prop.GetSolderBallProperty().GetPlacement()
|
|
1045
|
+
== self._pedb.definition.SolderballPlacement.AbovePadstack
|
|
1046
|
+
):
|
|
1047
|
+
sball_prop = cmp_prop.GetSolderBallProperty().Clone()
|
|
1048
|
+
sball_prop.SetPlacement(self._pedb.definition.SolderballPlacement.BelowPadstack)
|
|
1049
|
+
cmp_prop.SetSolderBallProperty(sball_prop)
|
|
1050
|
+
elif (
|
|
1051
|
+
cmp_prop.GetSolderBallProperty().GetPlacement()
|
|
1052
|
+
== self._pedb.definition.SolderballPlacement.BelowPadstack
|
|
1053
|
+
):
|
|
1054
|
+
sball_prop = cmp_prop.GetSolderBallProperty().Clone()
|
|
1055
|
+
sball_prop.SetPlacement(self._pedb.definition.SolderballPlacement.AbovePadstack)
|
|
1056
|
+
cmp_prop.SetSolderBallProperty(sball_prop)
|
|
1057
|
+
except:
|
|
1058
|
+
pass
|
|
1059
|
+
if cmp_type == self._pedb.definition.ComponentType.IC:
|
|
1060
|
+
die_prop = cmp_prop.GetDieProperty().Clone()
|
|
1061
|
+
chip_orientation = die_prop.GetOrientation()
|
|
1062
|
+
if chip_orientation == self._pedb.definition.DieOrientation.ChipDown:
|
|
1063
|
+
die_prop.SetOrientation(self._pedb.definition.DieOrientation.ChipUp)
|
|
1064
|
+
cmp_prop.SetDieProperty(die_prop)
|
|
1065
|
+
else:
|
|
1066
|
+
die_prop.SetOrientation(self._pedb.definition.DieOrientation.ChipDown)
|
|
1067
|
+
cmp_prop.SetDieProperty(die_prop)
|
|
1068
|
+
cmp.SetComponentProperty(cmp_prop)
|
|
1069
|
+
|
|
1070
|
+
lay_list = list(new_lc.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))
|
|
1071
|
+
for padstack in list(self._pedb.padstacks.instances.values()):
|
|
1072
|
+
start_layer_id = [lay.GetLayerId() for lay in list(lay_list) if lay.GetName() == padstack.start_layer]
|
|
1073
|
+
stop_layer_id = [lay.GetLayerId() for lay in list(lay_list) if lay.GetName() == padstack.stop_layer]
|
|
1074
|
+
layer_map = padstack._edb_padstackinstance.GetLayerMap()
|
|
1075
|
+
layer_map.SetMapping(stop_layer_id[0], start_layer_id[0])
|
|
1076
|
+
padstack._edb_padstackinstance.SetLayerMap(layer_map)
|
|
1077
|
+
self.refresh_layer_collection()
|
|
1078
|
+
return True
|
|
1079
|
+
except:
|
|
1080
|
+
return False
|
|
1081
|
+
|
|
1082
|
+
@pyedb_function_handler()
|
|
1083
|
+
def get_layout_thickness(self):
|
|
1084
|
+
"""Return the layout thickness.
|
|
1085
|
+
|
|
1086
|
+
Returns
|
|
1087
|
+
-------
|
|
1088
|
+
float
|
|
1089
|
+
The thickness value.
|
|
1090
|
+
"""
|
|
1091
|
+
layers = list(self.stackup_layers.values())
|
|
1092
|
+
layers.sort(key=lambda lay: lay.lower_elevation)
|
|
1093
|
+
thickness = 0
|
|
1094
|
+
if layers:
|
|
1095
|
+
top_layer = layers[-1]
|
|
1096
|
+
bottom_layer = layers[0]
|
|
1097
|
+
thickness = abs(top_layer.upper_elevation - bottom_layer.lower_elevation)
|
|
1098
|
+
return round(thickness, 7)
|
|
1099
|
+
|
|
1100
|
+
@pyedb_function_handler()
|
|
1101
|
+
def _get_solder_height(self, layer_name):
|
|
1102
|
+
for _, val in self._pedb.components.components.items():
|
|
1103
|
+
if val.solder_ball_height and val.placement_layer == layer_name:
|
|
1104
|
+
return val.solder_ball_height
|
|
1105
|
+
return 0
|
|
1106
|
+
|
|
1107
|
+
@pyedb_function_handler()
|
|
1108
|
+
def _remove_solder_pec(self, layer_name):
|
|
1109
|
+
for _, val in self._pedb.components.components.items():
|
|
1110
|
+
if val.solder_ball_height and val.placement_layer == layer_name:
|
|
1111
|
+
comp_prop = val.component_property
|
|
1112
|
+
port_property = comp_prop.GetPortProperty().Clone()
|
|
1113
|
+
port_property.SetReferenceSizeAuto(False)
|
|
1114
|
+
port_property.SetReferenceSize(self._edb_value(0.0), self._edb_value(0.0))
|
|
1115
|
+
comp_prop.SetPortProperty(port_property)
|
|
1116
|
+
val.edbcomponent.SetComponentProperty(comp_prop)
|
|
1117
|
+
|
|
1118
|
+
@pyedb_function_handler()
|
|
1119
|
+
def adjust_solder_dielectrics(self):
|
|
1120
|
+
"""Adjust the stack-up by adding or modifying dielectric layers that contains Solder Balls.
|
|
1121
|
+
This method identifies the solder-ball height and adjust the dielectric thickness on top (or bottom) to fit
|
|
1122
|
+
the thickness in order to merge another layout.
|
|
1123
|
+
|
|
1124
|
+
Returns
|
|
1125
|
+
-------
|
|
1126
|
+
bool
|
|
1127
|
+
"""
|
|
1128
|
+
for el, val in self._pedb.components.components.items():
|
|
1129
|
+
if val.solder_ball_height:
|
|
1130
|
+
layer = val.placement_layer
|
|
1131
|
+
if layer == list(self.stackup_layers.keys())[0]:
|
|
1132
|
+
self.add_layer(
|
|
1133
|
+
"Bottom_air",
|
|
1134
|
+
base_layer=list(self.stackup_layers.keys())[-1],
|
|
1135
|
+
method="insert_below",
|
|
1136
|
+
material="air",
|
|
1137
|
+
thickness=val.solder_ball_height,
|
|
1138
|
+
layer_type="dielectric",
|
|
1139
|
+
)
|
|
1140
|
+
elif layer == list(self.stackup_layers.keys())[-1]:
|
|
1141
|
+
self.add_layer(
|
|
1142
|
+
"Top_Air",
|
|
1143
|
+
base_layer=layer,
|
|
1144
|
+
material="air",
|
|
1145
|
+
thickness=val.solder_ball_height,
|
|
1146
|
+
layer_type="dielectric",
|
|
1147
|
+
)
|
|
1148
|
+
elif layer == list(self.signal_layers.keys())[-1]:
|
|
1149
|
+
list(self.stackup_layers.values())[-1].thickness = val.solder_ball_height
|
|
1150
|
+
|
|
1151
|
+
elif layer == list(self.signal_layers.keys())[0]:
|
|
1152
|
+
list(self.stackup_layers.values())[0].thickness = val.solder_ball_height
|
|
1153
|
+
return True
|
|
1154
|
+
|
|
1155
|
+
@pyedb_function_handler()
|
|
1156
|
+
def place_in_layout(
|
|
1157
|
+
self,
|
|
1158
|
+
edb,
|
|
1159
|
+
angle=0.0,
|
|
1160
|
+
offset_x=0.0,
|
|
1161
|
+
offset_y=0.0,
|
|
1162
|
+
flipped_stackup=True,
|
|
1163
|
+
place_on_top=True,
|
|
1164
|
+
):
|
|
1165
|
+
"""Place current Cell into another cell using layer placement method.
|
|
1166
|
+
Flip the current layer stackup of a layout if requested. Transform parameters currently not supported.
|
|
1167
|
+
|
|
1168
|
+
Parameters
|
|
1169
|
+
----------
|
|
1170
|
+
edb : Edb
|
|
1171
|
+
Cell on which to place the current layout. If None the Cell will be applied on an empty new Cell.
|
|
1172
|
+
angle : double, optional
|
|
1173
|
+
The rotation angle applied on the design.
|
|
1174
|
+
offset_x : double, optional
|
|
1175
|
+
The x offset value.
|
|
1176
|
+
offset_y : double, optional
|
|
1177
|
+
The y offset value.
|
|
1178
|
+
flipped_stackup : bool, optional
|
|
1179
|
+
Either if the current layout is inverted.
|
|
1180
|
+
If `True` and place_on_top is `True` the stackup will be flipped before the merge.
|
|
1181
|
+
place_on_top : bool, optional
|
|
1182
|
+
Either if place the current layout on Top or Bottom of destination Layout.
|
|
1183
|
+
|
|
1184
|
+
Returns
|
|
1185
|
+
-------
|
|
1186
|
+
bool
|
|
1187
|
+
``True`` when succeed ``False`` if not.
|
|
1188
|
+
|
|
1189
|
+
Examples
|
|
1190
|
+
--------
|
|
1191
|
+
>>> edb1 = Edb(edbpath=targetfile1, edbversion="2021.2")
|
|
1192
|
+
>>> edb2 = Edb(edbpath=targetfile2, edbversion="2021.2")
|
|
1193
|
+
|
|
1194
|
+
>>> hosting_cmp = edb1.components.get_component_by_name("U100")
|
|
1195
|
+
>>> mounted_cmp = edb2.components.get_component_by_name("BGA")
|
|
1196
|
+
|
|
1197
|
+
>>> vector, rotation, solder_ball_height = edb1.components.get_component_placement_vector(
|
|
1198
|
+
... mounted_component=mounted_cmp,
|
|
1199
|
+
... hosting_component=hosting_cmp,
|
|
1200
|
+
... mounted_component_pin1="A12",
|
|
1201
|
+
... mounted_component_pin2="A14",
|
|
1202
|
+
... hosting_component_pin1="A12",
|
|
1203
|
+
... hosting_component_pin2="A14")
|
|
1204
|
+
>>> edb2.stackup.place_in_layout(edb1.active_cell, angle=0.0, offset_x=vector[0],
|
|
1205
|
+
... offset_y=vector[1], flipped_stackup=False, place_on_top=True,
|
|
1206
|
+
... )
|
|
1207
|
+
"""
|
|
1208
|
+
# if flipped_stackup and place_on_top or (not flipped_stackup and not place_on_top):
|
|
1209
|
+
self.adjust_solder_dielectrics()
|
|
1210
|
+
if not place_on_top:
|
|
1211
|
+
edb.stackup.flip_design()
|
|
1212
|
+
place_on_top = True
|
|
1213
|
+
if not flipped_stackup:
|
|
1214
|
+
self.flip_design()
|
|
1215
|
+
elif flipped_stackup:
|
|
1216
|
+
self.flip_design()
|
|
1217
|
+
edb_cell = edb.active_cell
|
|
1218
|
+
_angle = self._edb_value(angle * math.pi / 180.0)
|
|
1219
|
+
_offset_x = self._edb_value(offset_x)
|
|
1220
|
+
_offset_y = self._edb_value(offset_y)
|
|
1221
|
+
|
|
1222
|
+
if edb_cell.GetName() not in self._pedb.cell_names:
|
|
1223
|
+
list_cells = self._pedb.copy_cells([edb_cell.api_object])
|
|
1224
|
+
edb_cell = list_cells[0]
|
|
1225
|
+
self._pedb.layout.cell.SetBlackBox(True)
|
|
1226
|
+
cell_inst2 = self._pedb.edb_api.cell.hierarchy.cell_instance.Create(
|
|
1227
|
+
edb_cell.GetLayout(), self._pedb.layout.cell.GetName(), self._pedb.active_layout
|
|
1228
|
+
)
|
|
1229
|
+
cell_trans = cell_inst2.GetTransform()
|
|
1230
|
+
cell_trans.SetRotationValue(_angle)
|
|
1231
|
+
cell_trans.SetXOffsetValue(_offset_x)
|
|
1232
|
+
cell_trans.SetYOffsetValue(_offset_y)
|
|
1233
|
+
cell_trans.SetMirror(flipped_stackup)
|
|
1234
|
+
cell_inst2.SetTransform(cell_trans)
|
|
1235
|
+
cell_inst2.SetSolveIndependentPreference(False)
|
|
1236
|
+
stackup_target = edb_cell.GetLayout().GetLayerCollection()
|
|
1237
|
+
|
|
1238
|
+
if place_on_top:
|
|
1239
|
+
cell_inst2.SetPlacementLayer(
|
|
1240
|
+
list(stackup_target.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))[0]
|
|
1241
|
+
)
|
|
1242
|
+
else:
|
|
1243
|
+
cell_inst2.SetPlacementLayer(
|
|
1244
|
+
list(stackup_target.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))[-1]
|
|
1245
|
+
)
|
|
1246
|
+
self.refresh_layer_collection()
|
|
1247
|
+
return True
|
|
1248
|
+
|
|
1249
|
+
@pyedb_function_handler()
|
|
1250
|
+
def place_in_layout_3d_placement(
|
|
1251
|
+
self,
|
|
1252
|
+
edb,
|
|
1253
|
+
angle=0.0,
|
|
1254
|
+
offset_x=0.0,
|
|
1255
|
+
offset_y=0.0,
|
|
1256
|
+
flipped_stackup=True,
|
|
1257
|
+
place_on_top=True,
|
|
1258
|
+
solder_height=0,
|
|
1259
|
+
):
|
|
1260
|
+
"""Place current Cell into another cell using 3d placement method.
|
|
1261
|
+
Flip the current layer stackup of a layout if requested. Transform parameters currently not supported.
|
|
1262
|
+
|
|
1263
|
+
Parameters
|
|
1264
|
+
----------
|
|
1265
|
+
edb : Edb
|
|
1266
|
+
Cell on which to place the current layout. If None the Cell will be applied on an empty new Cell.
|
|
1267
|
+
angle : double, optional
|
|
1268
|
+
The rotation angle applied on the design.
|
|
1269
|
+
offset_x : double, optional
|
|
1270
|
+
The x offset value.
|
|
1271
|
+
offset_y : double, optional
|
|
1272
|
+
The y offset value.
|
|
1273
|
+
flipped_stackup : bool, optional
|
|
1274
|
+
Either if the current layout is inverted.
|
|
1275
|
+
If `True` and place_on_top is `True` the stackup will be flipped before the merge.
|
|
1276
|
+
place_on_top : bool, optional
|
|
1277
|
+
Either if place the current layout on Top or Bottom of destination Layout.
|
|
1278
|
+
solder_height : float, optional
|
|
1279
|
+
Solder Ball or Bumps eight.
|
|
1280
|
+
This value will be added to the elevation to align the two layouts.
|
|
1281
|
+
|
|
1282
|
+
Returns
|
|
1283
|
+
-------
|
|
1284
|
+
bool
|
|
1285
|
+
``True`` when succeed ``False`` if not.
|
|
1286
|
+
|
|
1287
|
+
Examples
|
|
1288
|
+
--------
|
|
1289
|
+
>>> edb1 = Edb(edbpath=targetfile1, edbversion="2021.2")
|
|
1290
|
+
>>> edb2 = Edb(edbpath=targetfile2, edbversion="2021.2")
|
|
1291
|
+
>>> hosting_cmp = edb1.components.get_component_by_name("U100")
|
|
1292
|
+
>>> mounted_cmp = edb2.components.get_component_by_name("BGA")
|
|
1293
|
+
>>> edb2.stackup.place_in_layout(edb1.active_cell, angle=0.0, offset_x="1mm",
|
|
1294
|
+
... offset_y="2mm", flipped_stackup=False, place_on_top=True,
|
|
1295
|
+
... )
|
|
1296
|
+
"""
|
|
1297
|
+
_angle = angle * math.pi / 180.0
|
|
1298
|
+
|
|
1299
|
+
if solder_height <= 0:
|
|
1300
|
+
if flipped_stackup and not place_on_top or (place_on_top and not flipped_stackup):
|
|
1301
|
+
minimum_elevation = None
|
|
1302
|
+
layers_from_the_bottom = sorted(self.signal_layers.values(), key=lambda lay: lay.upper_elevation)
|
|
1303
|
+
for lay in layers_from_the_bottom:
|
|
1304
|
+
if minimum_elevation is None:
|
|
1305
|
+
minimum_elevation = lay.lower_elevation
|
|
1306
|
+
elif lay.lower_elevation > minimum_elevation:
|
|
1307
|
+
break
|
|
1308
|
+
lay_solder_height = self._get_solder_height(lay.name)
|
|
1309
|
+
solder_height = max(lay_solder_height, solder_height)
|
|
1310
|
+
self._remove_solder_pec(lay.name)
|
|
1311
|
+
else:
|
|
1312
|
+
maximum_elevation = None
|
|
1313
|
+
layers_from_the_top = sorted(self.signal_layers.values(), key=lambda lay: -lay.upper_elevation)
|
|
1314
|
+
for lay in layers_from_the_top:
|
|
1315
|
+
if maximum_elevation is None:
|
|
1316
|
+
maximum_elevation = lay.upper_elevation
|
|
1317
|
+
elif lay.upper_elevation < maximum_elevation:
|
|
1318
|
+
break
|
|
1319
|
+
lay_solder_height = self._get_solder_height(lay.name)
|
|
1320
|
+
solder_height = max(lay_solder_height, solder_height)
|
|
1321
|
+
self._remove_solder_pec(lay.name)
|
|
1322
|
+
|
|
1323
|
+
rotation = self._edb_value(0.0)
|
|
1324
|
+
if flipped_stackup:
|
|
1325
|
+
rotation = self._edb_value(math.pi)
|
|
1326
|
+
|
|
1327
|
+
edb_cell = edb.active_cell
|
|
1328
|
+
_offset_x = self._edb_value(offset_x)
|
|
1329
|
+
_offset_y = self._edb_value(offset_y)
|
|
1330
|
+
|
|
1331
|
+
if edb_cell.GetName() not in self._pedb.cell_names:
|
|
1332
|
+
list_cells = self._pedb.copy_cells(edb_cell.api_object)
|
|
1333
|
+
edb_cell = list_cells[0]
|
|
1334
|
+
self._pedb.layout.cell.SetBlackBox(True)
|
|
1335
|
+
cell_inst2 = self._pedb.edb_api.cell.hierarchy.cell_instance.Create(
|
|
1336
|
+
edb_cell.GetLayout(), self._pedb.layout.cell.GetName(), self._pedb.active_layout
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
stackup_target = self._pedb.edb_api.Cell.LayerCollection(edb_cell.GetLayout().GetLayerCollection())
|
|
1340
|
+
stackup_source = self._pedb.edb_api.Cell.LayerCollection(self._pedb.layout.layer_collection)
|
|
1341
|
+
|
|
1342
|
+
if place_on_top:
|
|
1343
|
+
cell_inst2.SetPlacementLayer(
|
|
1344
|
+
list(stackup_target.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))[0]
|
|
1345
|
+
)
|
|
1346
|
+
else:
|
|
1347
|
+
cell_inst2.SetPlacementLayer(
|
|
1348
|
+
list(stackup_target.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))[-1]
|
|
1349
|
+
)
|
|
1350
|
+
cell_inst2.SetIs3DPlacement(True)
|
|
1351
|
+
sig_set = self._pedb.edb_api.cell.layer_type_set.SignalLayerSet
|
|
1352
|
+
res = stackup_target.GetTopBottomStackupLayers(sig_set)
|
|
1353
|
+
target_top_elevation = res[2]
|
|
1354
|
+
target_bottom_elevation = res[4]
|
|
1355
|
+
res_s = stackup_source.GetTopBottomStackupLayers(sig_set)
|
|
1356
|
+
source_stack_top_elevation = res_s[2]
|
|
1357
|
+
source_stack_bot_elevation = res_s[4]
|
|
1358
|
+
|
|
1359
|
+
if place_on_top and flipped_stackup:
|
|
1360
|
+
elevation = target_top_elevation + source_stack_top_elevation
|
|
1361
|
+
elif place_on_top:
|
|
1362
|
+
elevation = target_top_elevation - source_stack_bot_elevation
|
|
1363
|
+
elif flipped_stackup:
|
|
1364
|
+
elevation = target_bottom_elevation + source_stack_bot_elevation
|
|
1365
|
+
solder_height = -solder_height
|
|
1366
|
+
else:
|
|
1367
|
+
elevation = target_bottom_elevation - source_stack_top_elevation
|
|
1368
|
+
solder_height = -solder_height
|
|
1369
|
+
|
|
1370
|
+
h_stackup = self._edb_value(elevation + solder_height)
|
|
1371
|
+
|
|
1372
|
+
zero_data = self._edb_value(0.0)
|
|
1373
|
+
one_data = self._edb_value(1.0)
|
|
1374
|
+
point3d_t = self._pedb.point_3d(_offset_x, _offset_y, h_stackup)
|
|
1375
|
+
point_loc = self._pedb.point_3d(zero_data, zero_data, zero_data)
|
|
1376
|
+
point_from = self._pedb.point_3d(one_data, zero_data, zero_data)
|
|
1377
|
+
point_to = self._pedb.point_3d(math.cos(_angle), -1 * math.sin(_angle), zero_data)
|
|
1378
|
+
cell_inst2.Set3DTransformation(point_loc, point_from, point_to, rotation, point3d_t)
|
|
1379
|
+
self.refresh_layer_collection()
|
|
1380
|
+
return True
|
|
1381
|
+
|
|
1382
|
+
@pyedb_function_handler()
|
|
1383
|
+
def place_instance(
|
|
1384
|
+
self,
|
|
1385
|
+
component_edb,
|
|
1386
|
+
angle=0.0,
|
|
1387
|
+
offset_x=0.0,
|
|
1388
|
+
offset_y=0.0,
|
|
1389
|
+
offset_z=0.0,
|
|
1390
|
+
flipped_stackup=True,
|
|
1391
|
+
place_on_top=True,
|
|
1392
|
+
solder_height=0,
|
|
1393
|
+
):
|
|
1394
|
+
"""Place current Cell into another cell using 3d placement method.
|
|
1395
|
+
Flip the current layer stackup of a layout if requested. Transform parameters currently not supported.
|
|
1396
|
+
|
|
1397
|
+
Parameters
|
|
1398
|
+
----------
|
|
1399
|
+
component_edb : Edb
|
|
1400
|
+
Cell to place in the current layout.
|
|
1401
|
+
angle : double, optional
|
|
1402
|
+
The rotation angle applied on the design.
|
|
1403
|
+
offset_x : double, optional
|
|
1404
|
+
The x offset value.
|
|
1405
|
+
The default value is ``0.0``.
|
|
1406
|
+
offset_y : double, optional
|
|
1407
|
+
The y offset value.
|
|
1408
|
+
The default value is ``0.0``.
|
|
1409
|
+
offset_z : double, optional
|
|
1410
|
+
The z offset value. (i.e. elevation offset for placement relative to the top layer conductor).
|
|
1411
|
+
The default value is ``0.0``, which places the cell layout on top of the top conductor
|
|
1412
|
+
layer of the target EDB.
|
|
1413
|
+
flipped_stackup : bool, optional
|
|
1414
|
+
Either if the current layout is inverted.
|
|
1415
|
+
If `True` and place_on_top is `True` the stackup will be flipped before the merge.
|
|
1416
|
+
place_on_top : bool, optional
|
|
1417
|
+
Either if place the component_edb layout on Top or Bottom of destination Layout.
|
|
1418
|
+
solder_height : float, optional
|
|
1419
|
+
Solder Ball or Bumps eight.
|
|
1420
|
+
This value will be added to the elevation to align the two layouts.
|
|
1421
|
+
|
|
1422
|
+
Returns
|
|
1423
|
+
-------
|
|
1424
|
+
bool
|
|
1425
|
+
``True`` when succeed ``False`` if not.
|
|
1426
|
+
|
|
1427
|
+
Examples
|
|
1428
|
+
--------
|
|
1429
|
+
>>> edb1 = Edb(edbpath=targetfile1, edbversion="2021.2")
|
|
1430
|
+
>>> edb2 = Edb(edbpath=targetfile2, edbversion="2021.2")
|
|
1431
|
+
>>> hosting_cmp = edb1.components.get_component_by_name("U100")
|
|
1432
|
+
>>> mounted_cmp = edb2.components.get_component_by_name("BGA")
|
|
1433
|
+
>>> edb1.stackup.place_instance(edb2, angle=0.0, offset_x="1mm",
|
|
1434
|
+
... offset_y="2mm", flipped_stackup=False, place_on_top=True,
|
|
1435
|
+
... )
|
|
1436
|
+
"""
|
|
1437
|
+
_angle = angle * math.pi / 180.0
|
|
1438
|
+
|
|
1439
|
+
if solder_height <= 0:
|
|
1440
|
+
if flipped_stackup and not place_on_top or (place_on_top and not flipped_stackup):
|
|
1441
|
+
minimum_elevation = None
|
|
1442
|
+
layers_from_the_bottom = sorted(
|
|
1443
|
+
component_edb.stackup.signal_layers.values(), key=lambda lay: lay.upper_elevation
|
|
1444
|
+
)
|
|
1445
|
+
for lay in layers_from_the_bottom:
|
|
1446
|
+
if minimum_elevation is None:
|
|
1447
|
+
minimum_elevation = lay.lower_elevation
|
|
1448
|
+
elif lay.lower_elevation > minimum_elevation:
|
|
1449
|
+
break
|
|
1450
|
+
lay_solder_height = component_edb.stackup._get_solder_height(lay.name)
|
|
1451
|
+
solder_height = max(lay_solder_height, solder_height)
|
|
1452
|
+
component_edb.stackup._remove_solder_pec(lay.name)
|
|
1453
|
+
else:
|
|
1454
|
+
maximum_elevation = None
|
|
1455
|
+
layers_from_the_top = sorted(
|
|
1456
|
+
component_edb.stackup.signal_layers.values(), key=lambda lay: -lay.upper_elevation
|
|
1457
|
+
)
|
|
1458
|
+
for lay in layers_from_the_top:
|
|
1459
|
+
if maximum_elevation is None:
|
|
1460
|
+
maximum_elevation = lay.upper_elevation
|
|
1461
|
+
elif lay.upper_elevation < maximum_elevation:
|
|
1462
|
+
break
|
|
1463
|
+
lay_solder_height = component_edb.stackup._get_solder_height(lay.name)
|
|
1464
|
+
solder_height = max(lay_solder_height, solder_height)
|
|
1465
|
+
component_edb.stackup._remove_solder_pec(lay.name)
|
|
1466
|
+
edb_cell = component_edb.active_cell
|
|
1467
|
+
_offset_x = self._edb_value(offset_x)
|
|
1468
|
+
_offset_y = self._edb_value(offset_y)
|
|
1469
|
+
|
|
1470
|
+
if edb_cell.GetName() not in self._pedb.cell_names:
|
|
1471
|
+
list_cells = self._pedb.copy_cells(edb_cell.api_object)
|
|
1472
|
+
edb_cell = list_cells[0]
|
|
1473
|
+
for cell in list(self._pedb.active_db.CircuitCells):
|
|
1474
|
+
if cell.GetName() == edb_cell.GetName():
|
|
1475
|
+
edb_cell = cell
|
|
1476
|
+
# Keep Cell Independent
|
|
1477
|
+
edb_cell.SetBlackBox(True)
|
|
1478
|
+
rotation = self._edb_value(0.0)
|
|
1479
|
+
if flipped_stackup:
|
|
1480
|
+
rotation = self._edb_value(math.pi)
|
|
1481
|
+
|
|
1482
|
+
_offset_x = self._edb_value(offset_x)
|
|
1483
|
+
_offset_y = self._edb_value(offset_y)
|
|
1484
|
+
|
|
1485
|
+
instance_name = generate_unique_name(edb_cell.GetName(), n=2)
|
|
1486
|
+
|
|
1487
|
+
cell_inst2 = self._pedb.edb_api.cell.hierarchy.cell_instance.Create(
|
|
1488
|
+
self._pedb.active_layout, instance_name, edb_cell.GetLayout()
|
|
1489
|
+
)
|
|
1490
|
+
|
|
1491
|
+
stackup_source = self._pedb.edb_api.Cell.LayerCollection(edb_cell.GetLayout().GetLayerCollection())
|
|
1492
|
+
stackup_target = self._pedb.edb_api.Cell.LayerCollection(self._pedb.layout.layer_collection)
|
|
1493
|
+
|
|
1494
|
+
if place_on_top:
|
|
1495
|
+
cell_inst2.SetPlacementLayer(
|
|
1496
|
+
list(stackup_target.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))[0]
|
|
1497
|
+
)
|
|
1498
|
+
else:
|
|
1499
|
+
cell_inst2.SetPlacementLayer(
|
|
1500
|
+
list(stackup_target.Layers(self._pedb.edb_api.cell.layer_type_set.SignalLayerSet))[-1]
|
|
1501
|
+
)
|
|
1502
|
+
cell_inst2.SetIs3DPlacement(True)
|
|
1503
|
+
sig_set = self._pedb.edb_api.cell.layer_type_set.SignalLayerSet
|
|
1504
|
+
res = stackup_target.GetTopBottomStackupLayers(sig_set)
|
|
1505
|
+
target_top_elevation = res[2]
|
|
1506
|
+
target_bottom_elevation = res[4]
|
|
1507
|
+
res_s = stackup_source.GetTopBottomStackupLayers(sig_set)
|
|
1508
|
+
source_stack_top_elevation = res_s[2]
|
|
1509
|
+
source_stack_bot_elevation = res_s[4]
|
|
1510
|
+
|
|
1511
|
+
if place_on_top and flipped_stackup:
|
|
1512
|
+
elevation = target_top_elevation + source_stack_top_elevation + offset_z
|
|
1513
|
+
elif place_on_top:
|
|
1514
|
+
elevation = target_top_elevation - source_stack_bot_elevation + offset_z
|
|
1515
|
+
elif flipped_stackup:
|
|
1516
|
+
elevation = target_bottom_elevation + source_stack_bot_elevation - offset_z
|
|
1517
|
+
solder_height = -solder_height
|
|
1518
|
+
else:
|
|
1519
|
+
elevation = target_bottom_elevation - source_stack_top_elevation - offset_z
|
|
1520
|
+
solder_height = -solder_height
|
|
1521
|
+
|
|
1522
|
+
h_stackup = self._edb_value(elevation + solder_height)
|
|
1523
|
+
|
|
1524
|
+
zero_data = self._edb_value(0.0)
|
|
1525
|
+
one_data = self._edb_value(1.0)
|
|
1526
|
+
point3d_t = self._pedb.point_3d(_offset_x, _offset_y, h_stackup)
|
|
1527
|
+
point_loc = self._pedb.point_3d(zero_data, zero_data, zero_data)
|
|
1528
|
+
point_from = self._pedb.point_3d(one_data, zero_data, zero_data)
|
|
1529
|
+
point_to = self._pedb.point_3d(math.cos(_angle), -1 * math.sin(_angle), zero_data)
|
|
1530
|
+
cell_inst2.Set3DTransformation(point_loc, point_from, point_to, rotation, point3d_t)
|
|
1531
|
+
self.refresh_layer_collection()
|
|
1532
|
+
return cell_inst2
|
|
1533
|
+
|
|
1534
|
+
@pyedb_function_handler()
|
|
1535
|
+
def place_a3dcomp_3d_placement(
|
|
1536
|
+
self,
|
|
1537
|
+
a3dcomp_path,
|
|
1538
|
+
angle=0.0,
|
|
1539
|
+
offset_x=0.0,
|
|
1540
|
+
offset_y=0.0,
|
|
1541
|
+
offset_z=0.0,
|
|
1542
|
+
place_on_top=True,
|
|
1543
|
+
):
|
|
1544
|
+
"""Place a 3D Component into current layout.
|
|
1545
|
+
3D Component ports are not visible via EDB. They will be visible after the EDB has been opened in Ansys
|
|
1546
|
+
Electronics Desktop as a project.
|
|
1547
|
+
|
|
1548
|
+
Parameters
|
|
1549
|
+
----------
|
|
1550
|
+
a3dcomp_path : str
|
|
1551
|
+
Path to the 3D Component file (\\*.a3dcomp) to place.
|
|
1552
|
+
angle : double, optional
|
|
1553
|
+
Clockwise rotation angle applied to the a3dcomp.
|
|
1554
|
+
offset_x : double, optional
|
|
1555
|
+
The x offset value.
|
|
1556
|
+
The default value is ``0.0``.
|
|
1557
|
+
offset_y : double, optional
|
|
1558
|
+
The y offset value.
|
|
1559
|
+
The default value is ``0.0``.
|
|
1560
|
+
offset_z : double, optional
|
|
1561
|
+
The z offset value. (i.e. elevation)
|
|
1562
|
+
The default value is ``0.0``.
|
|
1563
|
+
place_on_top : bool, optional
|
|
1564
|
+
Whether to place the 3D Component on the top or the bottom of this layout.
|
|
1565
|
+
If ``False`` then the 3D Component will also be flipped over around its X axis.
|
|
1566
|
+
|
|
1567
|
+
Returns
|
|
1568
|
+
-------
|
|
1569
|
+
bool
|
|
1570
|
+
``True`` if successful and ``False`` if not.
|
|
1571
|
+
|
|
1572
|
+
Examples
|
|
1573
|
+
--------
|
|
1574
|
+
>>> edb1 = Edb(edbpath=targetfile1, edbversion="2021.2")
|
|
1575
|
+
>>> a3dcomp_path = "connector.a3dcomp"
|
|
1576
|
+
>>> edb1.stackup.place_a3dcomp_3d_placement(a3dcomp_path, angle=0.0, offset_x="1mm",
|
|
1577
|
+
... offset_y="2mm", flipped_stackup=False, place_on_top=True,
|
|
1578
|
+
... )
|
|
1579
|
+
"""
|
|
1580
|
+
zero_data = self._edb_value(0.0)
|
|
1581
|
+
one_data = self._edb_value(1.0)
|
|
1582
|
+
local_origin = self._pedb.point_3d(0.0, 0.0, 0.0)
|
|
1583
|
+
rotation_axis_from = self._pedb.point_3d(1.0, 0.0, 0.0)
|
|
1584
|
+
_angle = angle * math.pi / 180.0
|
|
1585
|
+
rotation_axis_to = self._pedb.point_3d(math.cos(_angle), -1 * math.sin(_angle), 0.0)
|
|
1586
|
+
|
|
1587
|
+
stackup_target = self._pedb.edb_api.cell._cell.LayerCollection(self._pedb.layout.layer_collection)
|
|
1588
|
+
sig_set = self._pedb.edb_api.cell.layer_type_set.SignalLayerSet
|
|
1589
|
+
res = stackup_target.GetTopBottomStackupLayers(sig_set)
|
|
1590
|
+
target_top_elevation = res[2]
|
|
1591
|
+
target_bottom_elevation = res[4]
|
|
1592
|
+
flip_angle = self._edb_value("0deg")
|
|
1593
|
+
if place_on_top:
|
|
1594
|
+
elevation = target_top_elevation + offset_z
|
|
1595
|
+
else:
|
|
1596
|
+
flip_angle = self._edb_value("180deg")
|
|
1597
|
+
elevation = target_bottom_elevation - offset_z
|
|
1598
|
+
h_stackup = self._edb_value(elevation)
|
|
1599
|
+
location = self._pedb.point_3d(offset_x, offset_y, h_stackup)
|
|
1600
|
+
|
|
1601
|
+
mcad_model = self._pedb.edb_api.McadModel.Create3DComp(self._pedb.active_layout, a3dcomp_path)
|
|
1602
|
+
if mcad_model.IsNull(): # pragma: no cover
|
|
1603
|
+
logger.error("Failed to create MCAD model from a3dcomp")
|
|
1604
|
+
return False
|
|
1605
|
+
|
|
1606
|
+
cell_instance = mcad_model.GetCellInstance()
|
|
1607
|
+
if cell_instance.IsNull(): # pragma: no cover
|
|
1608
|
+
logger.error("Cell instance of a3dcomp is null")
|
|
1609
|
+
return False
|
|
1610
|
+
|
|
1611
|
+
if not cell_instance.SetIs3DPlacement(True): # pragma: no cover
|
|
1612
|
+
logger.error("Failed to set 3D placement on a3dcomp cell instance")
|
|
1613
|
+
return False
|
|
1614
|
+
|
|
1615
|
+
if not cell_instance.Set3DTransformation(
|
|
1616
|
+
local_origin, rotation_axis_from, rotation_axis_to, flip_angle, location
|
|
1617
|
+
): # pragma: no cover
|
|
1618
|
+
logger.error("Failed to set 3D transform on a3dcomp cell instance")
|
|
1619
|
+
return False
|
|
1620
|
+
self.refresh_layer_collection()
|
|
1621
|
+
return True
|
|
1622
|
+
|
|
1623
|
+
@pyedb_function_handler()
|
|
1624
|
+
def residual_copper_area_per_layer(self):
|
|
1625
|
+
"""Report residual copper area per layer in percentage.
|
|
1626
|
+
|
|
1627
|
+
Returns
|
|
1628
|
+
-------
|
|
1629
|
+
dict
|
|
1630
|
+
Copper area per layer.
|
|
1631
|
+
|
|
1632
|
+
Examples
|
|
1633
|
+
--------
|
|
1634
|
+
>>> edb = Edb(edbpath=targetfile1, edbversion="2021.2")
|
|
1635
|
+
>>> edb.stackup.residual_copper_area_per_layer()
|
|
1636
|
+
"""
|
|
1637
|
+
temp_data = {name: 0 for name, _ in self.signal_layers.items()}
|
|
1638
|
+
outline_area = 0
|
|
1639
|
+
for i in self._pedb.modeler.primitives:
|
|
1640
|
+
layer_name = i.GetLayer().GetName()
|
|
1641
|
+
if layer_name.lower() == "outline":
|
|
1642
|
+
if i.area() > outline_area:
|
|
1643
|
+
outline_area = i.area()
|
|
1644
|
+
elif layer_name not in temp_data:
|
|
1645
|
+
continue
|
|
1646
|
+
elif not i.is_void:
|
|
1647
|
+
temp_data[layer_name] = temp_data[layer_name] + i.area()
|
|
1648
|
+
else:
|
|
1649
|
+
pass
|
|
1650
|
+
temp_data = {name: area / outline_area * 100 for name, area in temp_data.items()}
|
|
1651
|
+
return temp_data
|
|
1652
|
+
|
|
1653
|
+
@pyedb_function_handler()
|
|
1654
|
+
def _import_dict(self, json_dict):
|
|
1655
|
+
"""Import stackup from a dictionary."""
|
|
1656
|
+
mats = json_dict["materials"]
|
|
1657
|
+
for material in mats.values():
|
|
1658
|
+
self._pedb.materials._load_materials(material)
|
|
1659
|
+
|
|
1660
|
+
temp = {i: j for i, j in json_dict["layers"].items() if j["type"] in ["signal", "dielectric"]}
|
|
1661
|
+
for name in list(self.stackup_layers.keys()):
|
|
1662
|
+
if name in temp:
|
|
1663
|
+
layer = temp[name]
|
|
1664
|
+
default_layer = {
|
|
1665
|
+
"name": "default",
|
|
1666
|
+
"type": "signal",
|
|
1667
|
+
"material": "copper",
|
|
1668
|
+
"dielectric_fill": "fr4_epoxy",
|
|
1669
|
+
"thickness": 3.5e-05,
|
|
1670
|
+
"etch_factor": 0.0,
|
|
1671
|
+
"roughness_enabled": False,
|
|
1672
|
+
"top_hallhuray_nodule_radius": 0.0,
|
|
1673
|
+
"top_hallhuray_surface_ratio": 0.0,
|
|
1674
|
+
"bottom_hallhuray_nodule_radius": 0.0,
|
|
1675
|
+
"bottom_hallhuray_surface_ratio": 0.0,
|
|
1676
|
+
"side_hallhuray_nodule_radius": 0.0,
|
|
1677
|
+
"side_hallhuray_surface_ratio": 0.0,
|
|
1678
|
+
"upper_elevation": 0.0,
|
|
1679
|
+
"lower_elevation": 0.0,
|
|
1680
|
+
"color": [242, 140, 102],
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if "color" in layer:
|
|
1684
|
+
default_layer["color"] = layer["color"]
|
|
1685
|
+
elif not layer["type"] == "signal":
|
|
1686
|
+
default_layer["color"] = [27, 110, 76]
|
|
1687
|
+
|
|
1688
|
+
for k, v in layer.items():
|
|
1689
|
+
default_layer[k] = v
|
|
1690
|
+
|
|
1691
|
+
self.stackup_layers[name]._load_layer(default_layer)
|
|
1692
|
+
else: # Remove layers not in config file.
|
|
1693
|
+
self.remove_layer(name)
|
|
1694
|
+
|
|
1695
|
+
for layer_name, layer in temp.items():
|
|
1696
|
+
if layer_name in self.stackup_layers:
|
|
1697
|
+
continue # if layer exist, skip
|
|
1698
|
+
|
|
1699
|
+
default_layer = {
|
|
1700
|
+
"name": "default",
|
|
1701
|
+
"type": "signal",
|
|
1702
|
+
"material": "copper",
|
|
1703
|
+
"dielectric_fill": "fr4_epoxy",
|
|
1704
|
+
"thickness": 3.5e-05,
|
|
1705
|
+
"etch_factor": 0.0,
|
|
1706
|
+
"roughness_enabled": False,
|
|
1707
|
+
"top_hallhuray_nodule_radius": 0.0,
|
|
1708
|
+
"top_hallhuray_surface_ratio": 0.0,
|
|
1709
|
+
"bottom_hallhuray_nodule_radius": 0.0,
|
|
1710
|
+
"bottom_hallhuray_surface_ratio": 0.0,
|
|
1711
|
+
"side_hallhuray_nodule_radius": 0.0,
|
|
1712
|
+
"side_hallhuray_surface_ratio": 0.0,
|
|
1713
|
+
"upper_elevation": 0.0,
|
|
1714
|
+
"lower_elevation": 0.0,
|
|
1715
|
+
"color": [242, 140, 102],
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
if "color" in layer:
|
|
1719
|
+
default_layer["color"] = layer["color"]
|
|
1720
|
+
elif not layer["type"] == "signal":
|
|
1721
|
+
default_layer["color"] = [27, 110, 76]
|
|
1722
|
+
|
|
1723
|
+
for k, v in layer.items():
|
|
1724
|
+
default_layer[k] = v
|
|
1725
|
+
|
|
1726
|
+
temp_2 = list(temp.keys())
|
|
1727
|
+
if temp_2.index(layer_name) == 0:
|
|
1728
|
+
new_layer = self.add_layer(
|
|
1729
|
+
layer_name,
|
|
1730
|
+
method="add_on_top",
|
|
1731
|
+
layer_type=default_layer["type"],
|
|
1732
|
+
material=default_layer["material"],
|
|
1733
|
+
fillMaterial=default_layer["dielectric_fill"],
|
|
1734
|
+
thickness=default_layer["thickness"],
|
|
1735
|
+
)
|
|
1736
|
+
|
|
1737
|
+
elif temp_2.index(layer_name) == len(temp_2):
|
|
1738
|
+
new_layer = self.add_layer(
|
|
1739
|
+
layer_name,
|
|
1740
|
+
base_layer=layer_name,
|
|
1741
|
+
method="add_on_bottom",
|
|
1742
|
+
layer_type=default_layer["type"],
|
|
1743
|
+
material=default_layer["material"],
|
|
1744
|
+
fillMaterial=default_layer["dielectric_fill"],
|
|
1745
|
+
thickness=default_layer["thickness"],
|
|
1746
|
+
)
|
|
1747
|
+
else:
|
|
1748
|
+
new_layer = self.add_layer(
|
|
1749
|
+
layer_name,
|
|
1750
|
+
base_layer=temp_2[temp_2.index(layer_name) - 1],
|
|
1751
|
+
method="insert_below",
|
|
1752
|
+
layer_type=default_layer["type"],
|
|
1753
|
+
material=default_layer["material"],
|
|
1754
|
+
fillMaterial=default_layer["dielectric_fill"],
|
|
1755
|
+
thickness=default_layer["thickness"],
|
|
1756
|
+
)
|
|
1757
|
+
|
|
1758
|
+
new_layer.color = default_layer["color"]
|
|
1759
|
+
new_layer.etch_factor = default_layer["etch_factor"]
|
|
1760
|
+
|
|
1761
|
+
new_layer.roughness_enabled = default_layer["roughness_enabled"]
|
|
1762
|
+
new_layer.top_hallhuray_nodule_radius = default_layer["top_hallhuray_nodule_radius"]
|
|
1763
|
+
new_layer.top_hallhuray_surface_ratio = default_layer["top_hallhuray_surface_ratio"]
|
|
1764
|
+
new_layer.bottom_hallhuray_nodule_radius = default_layer["bottom_hallhuray_nodule_radius"]
|
|
1765
|
+
new_layer.bottom_hallhuray_surface_ratio = default_layer["bottom_hallhuray_surface_ratio"]
|
|
1766
|
+
new_layer.side_hallhuray_nodule_radius = default_layer["side_hallhuray_nodule_radius"]
|
|
1767
|
+
new_layer.side_hallhuray_surface_ratio = default_layer["side_hallhuray_surface_ratio"]
|
|
1768
|
+
|
|
1769
|
+
return True
|
|
1770
|
+
|
|
1771
|
+
@pyedb_function_handler()
|
|
1772
|
+
def _import_json(self, file_path):
|
|
1773
|
+
"""Import stackup from a json file."""
|
|
1774
|
+
if file_path:
|
|
1775
|
+
f = open(file_path)
|
|
1776
|
+
json_dict = json.load(f) # pragma: no cover
|
|
1777
|
+
return self._import_dict(json_dict)
|
|
1778
|
+
|
|
1779
|
+
@pyedb_function_handler()
|
|
1780
|
+
def _import_csv(self, file_path):
|
|
1781
|
+
"""Import stackup definition from a CSV file.
|
|
1782
|
+
|
|
1783
|
+
Parameters
|
|
1784
|
+
----------
|
|
1785
|
+
file_path : str
|
|
1786
|
+
File path to the CSV file.
|
|
1787
|
+
"""
|
|
1788
|
+
if not pd:
|
|
1789
|
+
self._pedb.logger.error("Pandas is needed. You must install it first.")
|
|
1790
|
+
return False
|
|
1791
|
+
if is_ironpython:
|
|
1792
|
+
self._pedb.logger.error("Method works on CPython only.")
|
|
1793
|
+
return False
|
|
1794
|
+
df = pd.read_csv(file_path, index_col=0)
|
|
1795
|
+
|
|
1796
|
+
for name in self.stackup_layers.keys(): # pragma: no cover
|
|
1797
|
+
if not name in df.index:
|
|
1798
|
+
logger.error("{} doesn't exist in csv".format(name))
|
|
1799
|
+
return False
|
|
1800
|
+
|
|
1801
|
+
for name, layer_info in df.iterrows():
|
|
1802
|
+
layer_type = layer_info.Type
|
|
1803
|
+
if name in self.layers:
|
|
1804
|
+
layer = self.layers[name]
|
|
1805
|
+
layer.type = layer_type
|
|
1806
|
+
else:
|
|
1807
|
+
layer = self.add_layer(name, layer_type=layer_type, material="copper", fillMaterial="copper")
|
|
1808
|
+
|
|
1809
|
+
layer.material = layer_info.Material
|
|
1810
|
+
layer.thickness = layer_info.Thickness
|
|
1811
|
+
layer.dielectric_fill = layer_info.Dielectric_Fill
|
|
1812
|
+
|
|
1813
|
+
lc_new = self._pedb.edb_api.Cell.LayerCollection()
|
|
1814
|
+
for name, _ in df.iterrows():
|
|
1815
|
+
layer = self.layers[name]
|
|
1816
|
+
lc_new.AddLayerBottom(layer._edb_layer)
|
|
1817
|
+
|
|
1818
|
+
for name, layer in self.non_stackup_layers.items():
|
|
1819
|
+
lc_new.AddLayerBottom(layer._edb_layer)
|
|
1820
|
+
|
|
1821
|
+
self._pedb.layout.layer_collection = lc_new
|
|
1822
|
+
self.refresh_layer_collection()
|
|
1823
|
+
return True
|
|
1824
|
+
|
|
1825
|
+
@pyedb_function_handler()
|
|
1826
|
+
def _set(self, layers=None, materials=None, roughness=None, non_stackup_layers=None):
|
|
1827
|
+
"""Update stackup information.
|
|
1828
|
+
|
|
1829
|
+
Parameters
|
|
1830
|
+
----------
|
|
1831
|
+
layers: dict
|
|
1832
|
+
Dictionary containing layer information.
|
|
1833
|
+
materials: dict
|
|
1834
|
+
Dictionary containing material information.
|
|
1835
|
+
roughness: dict
|
|
1836
|
+
Dictionary containing roughness information.
|
|
1837
|
+
|
|
1838
|
+
Returns
|
|
1839
|
+
-------
|
|
1840
|
+
|
|
1841
|
+
"""
|
|
1842
|
+
if materials:
|
|
1843
|
+
self._add_materials_from_dictionary(materials)
|
|
1844
|
+
|
|
1845
|
+
if layers:
|
|
1846
|
+
prev_layer = None
|
|
1847
|
+
for name, val in layers.items():
|
|
1848
|
+
etching_factor = float(val["EtchFactor"]) if "EtchFactor" in val else None
|
|
1849
|
+
|
|
1850
|
+
if not self.stackup_layers:
|
|
1851
|
+
self.add_layer(
|
|
1852
|
+
name,
|
|
1853
|
+
None,
|
|
1854
|
+
"add_on_top",
|
|
1855
|
+
val["Type"],
|
|
1856
|
+
val["Material"],
|
|
1857
|
+
val["FillMaterial"] if val["Type"] == "signal" else "",
|
|
1858
|
+
val["Thickness"],
|
|
1859
|
+
etching_factor,
|
|
1860
|
+
)
|
|
1861
|
+
else:
|
|
1862
|
+
if name in self.stackup_layers.keys():
|
|
1863
|
+
lyr = self.stackup_layers[name]
|
|
1864
|
+
lyr.type = val["Type"]
|
|
1865
|
+
lyr.material = val["Material"]
|
|
1866
|
+
lyr.dielectric_fill = val["FillMaterial"] if val["Type"] == "signal" else ""
|
|
1867
|
+
lyr.thickness = val["Thickness"]
|
|
1868
|
+
if prev_layer:
|
|
1869
|
+
self._set_layout_stackup(lyr._edb_layer, "change_position", prev_layer)
|
|
1870
|
+
else:
|
|
1871
|
+
if prev_layer and prev_layer in self.stackup_layers:
|
|
1872
|
+
layer_name = prev_layer
|
|
1873
|
+
else:
|
|
1874
|
+
layer_name = list(self.stackup_layers.keys())[-1] if self.stackup_layers else None
|
|
1875
|
+
self.add_layer(
|
|
1876
|
+
name,
|
|
1877
|
+
layer_name,
|
|
1878
|
+
"insert_above",
|
|
1879
|
+
val["Type"],
|
|
1880
|
+
val["Material"],
|
|
1881
|
+
val["FillMaterial"] if val["Type"] == "signal" else "",
|
|
1882
|
+
val["Thickness"],
|
|
1883
|
+
etching_factor,
|
|
1884
|
+
)
|
|
1885
|
+
prev_layer = name
|
|
1886
|
+
for name in self.stackup_layers:
|
|
1887
|
+
if name not in layers:
|
|
1888
|
+
self.remove_layer(name)
|
|
1889
|
+
|
|
1890
|
+
if roughness:
|
|
1891
|
+
for name, attr in roughness.items():
|
|
1892
|
+
layer = self.signal_layers[name]
|
|
1893
|
+
layer.roughness_enabled = True
|
|
1894
|
+
|
|
1895
|
+
attr_name = "HuraySurfaceRoughness"
|
|
1896
|
+
if attr_name in attr:
|
|
1897
|
+
on_surface = "top"
|
|
1898
|
+
layer.assign_roughness_model(
|
|
1899
|
+
"huray",
|
|
1900
|
+
attr[attr_name]["NoduleRadius"],
|
|
1901
|
+
attr[attr_name]["HallHuraySurfaceRatio"],
|
|
1902
|
+
apply_on_surface=on_surface,
|
|
1903
|
+
)
|
|
1904
|
+
|
|
1905
|
+
attr_name = "HurayBottomSurfaceRoughness"
|
|
1906
|
+
if attr_name in attr:
|
|
1907
|
+
on_surface = "bottom"
|
|
1908
|
+
layer.assign_roughness_model(
|
|
1909
|
+
"huray",
|
|
1910
|
+
attr[attr_name]["NoduleRadius"],
|
|
1911
|
+
attr[attr_name]["HallHuraySurfaceRatio"],
|
|
1912
|
+
apply_on_surface=on_surface,
|
|
1913
|
+
)
|
|
1914
|
+
attr_name = "HuraySideSurfaceRoughness"
|
|
1915
|
+
if attr_name in attr:
|
|
1916
|
+
on_surface = "side"
|
|
1917
|
+
layer.assign_roughness_model(
|
|
1918
|
+
"huray",
|
|
1919
|
+
attr[attr_name]["NoduleRadius"],
|
|
1920
|
+
attr[attr_name]["HallHuraySurfaceRatio"],
|
|
1921
|
+
apply_on_surface=on_surface,
|
|
1922
|
+
)
|
|
1923
|
+
|
|
1924
|
+
attr_name = "GroissSurfaceRoughness"
|
|
1925
|
+
if attr_name in attr:
|
|
1926
|
+
on_surface = "top"
|
|
1927
|
+
layer.assign_roughness_model(
|
|
1928
|
+
"groisse", groisse_roughness=attr[attr_name]["Roughness"], apply_on_surface=on_surface
|
|
1929
|
+
)
|
|
1930
|
+
|
|
1931
|
+
attr_name = "GroissBottomSurfaceRoughness"
|
|
1932
|
+
if attr_name in attr:
|
|
1933
|
+
on_surface = "bottom"
|
|
1934
|
+
layer.assign_roughness_model(
|
|
1935
|
+
"groisse", groisse_roughness=attr[attr_name]["Roughness"], apply_on_surface=on_surface
|
|
1936
|
+
)
|
|
1937
|
+
|
|
1938
|
+
attr_name = "GroissSideSurfaceRoughness"
|
|
1939
|
+
if attr_name in attr:
|
|
1940
|
+
on_surface = "side"
|
|
1941
|
+
layer.assign_roughness_model(
|
|
1942
|
+
"groisse", groisse_roughness=attr[attr_name]["Roughness"], apply_on_surface=on_surface
|
|
1943
|
+
)
|
|
1944
|
+
|
|
1945
|
+
if non_stackup_layers:
|
|
1946
|
+
for name, val in non_stackup_layers.items():
|
|
1947
|
+
if name in self.non_stackup_layers:
|
|
1948
|
+
continue
|
|
1949
|
+
else:
|
|
1950
|
+
self.add_layer(name, layer_type=val["Type"])
|
|
1951
|
+
|
|
1952
|
+
return True
|
|
1953
|
+
|
|
1954
|
+
@pyedb_function_handler()
|
|
1955
|
+
def _get(self):
|
|
1956
|
+
"""Get stackup information from layout.
|
|
1957
|
+
|
|
1958
|
+
Returns:
|
|
1959
|
+
tuple: (dict, dict, dict)
|
|
1960
|
+
layers, materials, roughness_models
|
|
1961
|
+
"""
|
|
1962
|
+
layers = OrderedDict()
|
|
1963
|
+
roughness_models = OrderedDict()
|
|
1964
|
+
for name, val in self.stackup_layers.items():
|
|
1965
|
+
layer = dict()
|
|
1966
|
+
layer["Material"] = val.material
|
|
1967
|
+
layer["Name"] = val.name
|
|
1968
|
+
layer["Thickness"] = val.thickness
|
|
1969
|
+
layer["Type"] = val.type
|
|
1970
|
+
if not val.type == "dielectric":
|
|
1971
|
+
layer["FillMaterial"] = val.dielectric_fill
|
|
1972
|
+
layer["EtchFactor"] = val.etch_factor
|
|
1973
|
+
layers[name] = layer
|
|
1974
|
+
|
|
1975
|
+
if val.roughness_enabled:
|
|
1976
|
+
roughness_models[name] = {}
|
|
1977
|
+
model = val.get_roughness_model("top")
|
|
1978
|
+
if model.ToString().endswith("GroissRoughnessModel"):
|
|
1979
|
+
roughness_models[name]["GroissSurfaceRoughness"] = {"Roughness": model.get_Roughness().ToDouble()}
|
|
1980
|
+
else:
|
|
1981
|
+
roughness_models[name]["HuraySurfaceRoughness"] = {
|
|
1982
|
+
"HallHuraySurfaceRatio": model.get_NoduleRadius().ToDouble(),
|
|
1983
|
+
"NoduleRadius": model.get_SurfaceRatio().ToDouble(),
|
|
1984
|
+
}
|
|
1985
|
+
model = val.get_roughness_model("bottom")
|
|
1986
|
+
if model.ToString().endswith("GroissRoughnessModel"):
|
|
1987
|
+
roughness_models[name]["GroissBottomSurfaceRoughness"] = {
|
|
1988
|
+
"Roughness": model.get_Roughness().ToDouble()
|
|
1989
|
+
}
|
|
1990
|
+
else:
|
|
1991
|
+
roughness_models[name]["HurayBottomSurfaceRoughness"] = {
|
|
1992
|
+
"HallHuraySurfaceRatio": model.get_NoduleRadius().ToDouble(),
|
|
1993
|
+
"NoduleRadius": model.get_SurfaceRatio().ToDouble(),
|
|
1994
|
+
}
|
|
1995
|
+
model = val.get_roughness_model("side")
|
|
1996
|
+
if model.ToString().endswith("GroissRoughnessModel"):
|
|
1997
|
+
roughness_models[name]["GroissSideSurfaceRoughness"] = {
|
|
1998
|
+
"Roughness": model.get_Roughness().ToDouble()
|
|
1999
|
+
}
|
|
2000
|
+
else:
|
|
2001
|
+
roughness_models[name]["HuraySideSurfaceRoughness"] = {
|
|
2002
|
+
"HallHuraySurfaceRatio": model.get_NoduleRadius().ToDouble(),
|
|
2003
|
+
"NoduleRadius": model.get_SurfaceRatio().ToDouble(),
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
non_stackup_layers = OrderedDict()
|
|
2007
|
+
for name, val in self.non_stackup_layers.items():
|
|
2008
|
+
layer = dict()
|
|
2009
|
+
layer["Name"] = val.name
|
|
2010
|
+
layer["Type"] = val.type
|
|
2011
|
+
non_stackup_layers[name] = layer
|
|
2012
|
+
|
|
2013
|
+
materials = {}
|
|
2014
|
+
for name, val in self._pedb.materials.materials.items():
|
|
2015
|
+
material = {}
|
|
2016
|
+
if val.conductivity:
|
|
2017
|
+
if val.conductivity > 4e7:
|
|
2018
|
+
material["Conductivity"] = val.conductivity
|
|
2019
|
+
else:
|
|
2020
|
+
material["Permittivity"] = val.permittivity
|
|
2021
|
+
material["DielectricLossTangent"] = val.loss_tangent
|
|
2022
|
+
materials[name] = material
|
|
2023
|
+
|
|
2024
|
+
return layers, materials, roughness_models, non_stackup_layers
|
|
2025
|
+
|
|
2026
|
+
@pyedb_function_handler()
|
|
2027
|
+
def _add_materials_from_dictionary(self, material_dict):
|
|
2028
|
+
mat_keys = [i.lower() for i in self._pedb.materials.materials.keys()]
|
|
2029
|
+
mat_keys_case = [i for i in self._pedb.materials.materials.keys()]
|
|
2030
|
+
for name, attr in material_dict.items():
|
|
2031
|
+
if not name.lower() in mat_keys:
|
|
2032
|
+
if "Conductivity" in attr:
|
|
2033
|
+
self._pedb.materials.add_conductor_material(name, attr["Conductivity"])
|
|
2034
|
+
else:
|
|
2035
|
+
self._pedb.materials.add_dielectric_material(
|
|
2036
|
+
name,
|
|
2037
|
+
attr["Permittivity"],
|
|
2038
|
+
attr["DielectricLossTangent"],
|
|
2039
|
+
)
|
|
2040
|
+
else:
|
|
2041
|
+
local_material = self._pedb.materials[mat_keys_case[mat_keys.index(name.lower())]]
|
|
2042
|
+
if "Conductivity" in attr:
|
|
2043
|
+
local_material.conductivity = attr["Conductivity"]
|
|
2044
|
+
else:
|
|
2045
|
+
local_material.permittivity = attr["Permittivity"]
|
|
2046
|
+
local_material.loss_tanget = attr["DielectricLossTangent"]
|
|
2047
|
+
return True
|
|
2048
|
+
|
|
2049
|
+
@pyedb_function_handler()
|
|
2050
|
+
def _import_xml(self, file_path):
|
|
2051
|
+
"""Read external xml file and update stackup.
|
|
2052
|
+
1, all existing layers must exist in xml file.
|
|
2053
|
+
2, xml can have more layers than the existing stackup.
|
|
2054
|
+
3, if xml has different layer order, reorder the layers according to xml definition.
|
|
2055
|
+
|
|
2056
|
+
Parameters
|
|
2057
|
+
----------
|
|
2058
|
+
file_path: str
|
|
2059
|
+
Path to external XML file.
|
|
2060
|
+
|
|
2061
|
+
Returns
|
|
2062
|
+
-------
|
|
2063
|
+
bool
|
|
2064
|
+
``True`` when successful, ``False`` when failed.
|
|
2065
|
+
"""
|
|
2066
|
+
tree = ET.parse(file_path)
|
|
2067
|
+
material_dict = {}
|
|
2068
|
+
root = tree.getroot()
|
|
2069
|
+
stackup = root.find("Stackup")
|
|
2070
|
+
for m in stackup.find("Materials").findall("Material"):
|
|
2071
|
+
material = {}
|
|
2072
|
+
for i in list(m):
|
|
2073
|
+
material[i.tag] = list(i)[0].text
|
|
2074
|
+
material_dict[m.attrib["Name"]] = material
|
|
2075
|
+
|
|
2076
|
+
self._add_materials_from_dictionary(material_dict)
|
|
2077
|
+
|
|
2078
|
+
lc_import = self._pedb.edb_api.Cell.LayerCollection()
|
|
2079
|
+
|
|
2080
|
+
if not lc_import.ImportFromControlFile(file_path): # pragma: no cover
|
|
2081
|
+
logger.error("Import xml failed. Please check xml content.")
|
|
2082
|
+
return False
|
|
2083
|
+
|
|
2084
|
+
if not len(self.stackup_layers):
|
|
2085
|
+
self._pedb.layout.layer_collection = lc_import
|
|
2086
|
+
self.refresh_layer_collection()
|
|
2087
|
+
return True
|
|
2088
|
+
|
|
2089
|
+
dumy_layers = OrderedDict()
|
|
2090
|
+
for i in list(lc_import.Layers(self._pedb.edb_api.cell.layer_type_set.AllLayerSet)):
|
|
2091
|
+
dumy_layers[i.GetName()] = i.Clone()
|
|
2092
|
+
|
|
2093
|
+
for name in self.layers.keys():
|
|
2094
|
+
if not name in dumy_layers:
|
|
2095
|
+
logger.error("{} doesn't exist in xml".format(name))
|
|
2096
|
+
return False
|
|
2097
|
+
|
|
2098
|
+
for name, l in dumy_layers.items():
|
|
2099
|
+
layer_type = re.sub(r"Layer$", "", l.GetLayerType().ToString()).lower()
|
|
2100
|
+
if name in self.layers:
|
|
2101
|
+
layer = self.layers[name]
|
|
2102
|
+
layer.type = layer_type
|
|
2103
|
+
else:
|
|
2104
|
+
layer = self.add_layer(name, layer_type=layer_type, material="copper", fillMaterial="copper")
|
|
2105
|
+
|
|
2106
|
+
if l.IsStackupLayer():
|
|
2107
|
+
layer.material = l.GetMaterial()
|
|
2108
|
+
layer.thickness = l.GetThicknessValue().ToDouble()
|
|
2109
|
+
layer.dielectric_fill = l.GetFillMaterial()
|
|
2110
|
+
layer.etch_factor = l.GetEtchFactor().ToDouble()
|
|
2111
|
+
|
|
2112
|
+
lc_new = self._pedb.edb_api.Cell.LayerCollection()
|
|
2113
|
+
for name, _ in dumy_layers.items():
|
|
2114
|
+
layer = self.layers[name]
|
|
2115
|
+
lc_new.AddLayerBottom(layer._edb_layer)
|
|
2116
|
+
|
|
2117
|
+
self._pedb.layout.layer_collection = lc_new
|
|
2118
|
+
self.refresh_layer_collection()
|
|
2119
|
+
return True
|
|
2120
|
+
|
|
2121
|
+
@pyedb_function_handler()
|
|
2122
|
+
def _export_xml(self, file_path):
|
|
2123
|
+
"""Export stackup information to an external XMLfile.
|
|
2124
|
+
|
|
2125
|
+
Parameters
|
|
2126
|
+
----------
|
|
2127
|
+
file_path: str
|
|
2128
|
+
Path to external XML file.
|
|
2129
|
+
|
|
2130
|
+
Returns
|
|
2131
|
+
-------
|
|
2132
|
+
bool
|
|
2133
|
+
``True`` when successful, ``False`` when failed.
|
|
2134
|
+
"""
|
|
2135
|
+
layers, materials, roughness, non_stackup_layers = self._get()
|
|
2136
|
+
|
|
2137
|
+
root = ET.Element("{http://www.ansys.com/control}Control", attrib={"schemaVersion": "1.0"})
|
|
2138
|
+
|
|
2139
|
+
el_stackup = ET.SubElement(root, "Stackup", {"schemaVersion": "1.0"})
|
|
2140
|
+
|
|
2141
|
+
el_materials = ET.SubElement(el_stackup, "Materials")
|
|
2142
|
+
for mat, val in materials.items():
|
|
2143
|
+
material = ET.SubElement(el_materials, "Material")
|
|
2144
|
+
material.set("Name", mat)
|
|
2145
|
+
for pname, pval in val.items():
|
|
2146
|
+
mat_prop = ET.SubElement(material, pname)
|
|
2147
|
+
value = ET.SubElement(mat_prop, "Double")
|
|
2148
|
+
value.text = str(pval)
|
|
2149
|
+
|
|
2150
|
+
el_layers = ET.SubElement(el_stackup, "Layers", {"LengthUnit": "meter"})
|
|
2151
|
+
for lyr, val in layers.items():
|
|
2152
|
+
layer = ET.SubElement(el_layers, "Layer")
|
|
2153
|
+
val = {i: str(j) for i, j in val.items()}
|
|
2154
|
+
if val["Type"] == "signal":
|
|
2155
|
+
val["Type"] = "conductor"
|
|
2156
|
+
layer.attrib.update(val)
|
|
2157
|
+
|
|
2158
|
+
for lyr, val in non_stackup_layers.items():
|
|
2159
|
+
layer = ET.SubElement(el_layers, "Layer")
|
|
2160
|
+
val = {i: str(j) for i, j in val.items()}
|
|
2161
|
+
layer.attrib.update(val)
|
|
2162
|
+
|
|
2163
|
+
for lyr, val in roughness.items():
|
|
2164
|
+
el = el_layers.find("./Layer[@Name='{}']".format(lyr))
|
|
2165
|
+
for pname, pval in val.items():
|
|
2166
|
+
pval = {i: str(j) for i, j in pval.items()}
|
|
2167
|
+
ET.SubElement(el, pname, pval)
|
|
2168
|
+
|
|
2169
|
+
write_pretty_xml(root, file_path)
|
|
2170
|
+
return True
|
|
2171
|
+
|
|
2172
|
+
@pyedb_function_handler()
|
|
2173
|
+
def load(self, file_path):
|
|
2174
|
+
"""Import stackup from a file. The file format can be XML, CSV, or JSON.
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
Parameters
|
|
2178
|
+
----------
|
|
2179
|
+
file_path : str
|
|
2180
|
+
Path to stackup file.
|
|
2181
|
+
|
|
2182
|
+
Returns
|
|
2183
|
+
-------
|
|
2184
|
+
bool
|
|
2185
|
+
``True`` when successful, ``False`` when failed.
|
|
2186
|
+
|
|
2187
|
+
Examples
|
|
2188
|
+
--------
|
|
2189
|
+
>>> from pyedb import Edb
|
|
2190
|
+
>>> edb = Edb()
|
|
2191
|
+
>>> edb.stackup.load("stackup.xml")
|
|
2192
|
+
"""
|
|
2193
|
+
|
|
2194
|
+
if isinstance(file_path, dict):
|
|
2195
|
+
return self._import_dict(file_path)
|
|
2196
|
+
elif file_path.endswith(".csv"):
|
|
2197
|
+
return self._import_csv(file_path)
|
|
2198
|
+
elif file_path.endswith(".json"):
|
|
2199
|
+
return self._import_json(file_path)
|
|
2200
|
+
elif file_path.endswith(".xml"):
|
|
2201
|
+
return self._import_xml(file_path)
|
|
2202
|
+
else:
|
|
2203
|
+
return False
|
|
2204
|
+
|
|
2205
|
+
@pyedb_function_handler()
|
|
2206
|
+
def import_stackup(self, file_path):
|
|
2207
|
+
"""Import stackup from a file. The file format can be XML, CSV, or JSON.
|
|
2208
|
+
|
|
2209
|
+
.. deprecated:: 0.6.61
|
|
2210
|
+
Use :func:`load` instead.
|
|
2211
|
+
|
|
2212
|
+
Parameters
|
|
2213
|
+
----------
|
|
2214
|
+
file_path : str
|
|
2215
|
+
Path to stackup file.
|
|
2216
|
+
|
|
2217
|
+
Returns
|
|
2218
|
+
-------
|
|
2219
|
+
bool
|
|
2220
|
+
``True`` when successful, ``False`` when failed.
|
|
2221
|
+
|
|
2222
|
+
Examples
|
|
2223
|
+
--------
|
|
2224
|
+
>>> from pyedb import Edb
|
|
2225
|
+
>>> edb = Edb()
|
|
2226
|
+
>>> edb.stackup.import_stackup("stackup.xml")
|
|
2227
|
+
"""
|
|
2228
|
+
|
|
2229
|
+
self._logger.warning("Method export_stackup is deprecated. Use .export.")
|
|
2230
|
+
return self.load(file_path)
|
|
2231
|
+
|
|
2232
|
+
@pyedb_function_handler()
|
|
2233
|
+
def plot(
|
|
2234
|
+
self,
|
|
2235
|
+
save_plot=None,
|
|
2236
|
+
size=(2000, 1500),
|
|
2237
|
+
plot_definitions=None,
|
|
2238
|
+
first_layer=None,
|
|
2239
|
+
last_layer=None,
|
|
2240
|
+
scale_elevation=True,
|
|
2241
|
+
):
|
|
2242
|
+
"""Plot current stackup and, optionally, overlap padstack definitions.
|
|
2243
|
+
Plot supports only 'Laminate' and 'Overlapping' stackup types.
|
|
2244
|
+
|
|
2245
|
+
Parameters
|
|
2246
|
+
----------
|
|
2247
|
+
save_plot : str, optional
|
|
2248
|
+
If ``None`` the plot will be shown.
|
|
2249
|
+
If a file path is specified the plot will be saved to such file.
|
|
2250
|
+
size : tuple, optional
|
|
2251
|
+
Image size in pixel (width, height). Default value is ``(2000, 1500)``
|
|
2252
|
+
plot_definitions : str, list, optional
|
|
2253
|
+
List of padstack definitions to plot on the stackup.
|
|
2254
|
+
It is supported only for Laminate mode.
|
|
2255
|
+
first_layer : str or :class:`pyedb.dotnet.edb_core.edb_data.layer_data.LayerEdbClass`
|
|
2256
|
+
First layer to plot from the bottom. Default is `None` to start plotting from bottom.
|
|
2257
|
+
last_layer : str or :class:`pyedb.dotnet.edb_core.edb_data.layer_data.LayerEdbClass`
|
|
2258
|
+
Last layer to plot from the bottom. Default is `None` to plot up to top layer.
|
|
2259
|
+
scale_elevation : bool, optional
|
|
2260
|
+
The real layer thickness is scaled so that max_thickness = 3 * min_thickness.
|
|
2261
|
+
Default is `True`.
|
|
2262
|
+
|
|
2263
|
+
Returns
|
|
2264
|
+
-------
|
|
2265
|
+
:class:`matplotlib.plt`
|
|
2266
|
+
"""
|
|
2267
|
+
if is_ironpython:
|
|
2268
|
+
return False
|
|
2269
|
+
from pyedb.generic.constants import CSS4_COLORS
|
|
2270
|
+
from pyedb.generic.plot import plot_matplotlib
|
|
2271
|
+
|
|
2272
|
+
layer_names = list(self.stackup_layers.keys())
|
|
2273
|
+
if first_layer is None or first_layer not in layer_names:
|
|
2274
|
+
bottom_layer = layer_names[-1]
|
|
2275
|
+
elif isinstance(first_layer, str):
|
|
2276
|
+
bottom_layer = first_layer
|
|
2277
|
+
elif isinstance(first_layer, LayerEdbClass):
|
|
2278
|
+
bottom_layer = first_layer.name
|
|
2279
|
+
else:
|
|
2280
|
+
raise AttributeError("first_layer must be str or class `dotnet.edb_core.edb_data.layer_data.LayerEdbClass`")
|
|
2281
|
+
if last_layer is None or last_layer not in layer_names:
|
|
2282
|
+
top_layer = layer_names[0]
|
|
2283
|
+
elif isinstance(last_layer, str):
|
|
2284
|
+
top_layer = last_layer
|
|
2285
|
+
elif isinstance(last_layer, LayerEdbClass):
|
|
2286
|
+
top_layer = last_layer.name
|
|
2287
|
+
else:
|
|
2288
|
+
raise AttributeError("last_layer must be str or class `dotnet.edb_core.edb_data.layer_data.LayerEdbClass`")
|
|
2289
|
+
|
|
2290
|
+
stackup_mode = self.stackup_mode
|
|
2291
|
+
if stackup_mode not in ["Laminate", "Overlapping"]:
|
|
2292
|
+
raise AttributeError("stackup plot supports only 'Laminate' and 'Overlapping' stackup types.")
|
|
2293
|
+
|
|
2294
|
+
# build the layers data
|
|
2295
|
+
layers_data = []
|
|
2296
|
+
skip_flag = True
|
|
2297
|
+
for layer in self.stackup_layers.values(): # start from top
|
|
2298
|
+
if layer.name != top_layer and skip_flag:
|
|
2299
|
+
continue
|
|
2300
|
+
else:
|
|
2301
|
+
skip_flag = False
|
|
2302
|
+
layers_data.append([layer, layer.lower_elevation, layer.upper_elevation, layer.thickness])
|
|
2303
|
+
if layer.name == bottom_layer:
|
|
2304
|
+
break
|
|
2305
|
+
layers_data.reverse() # let's start from the bottom
|
|
2306
|
+
|
|
2307
|
+
# separate dielectric and signal if overlapping stackup
|
|
2308
|
+
if stackup_mode == "Overlapping":
|
|
2309
|
+
dielectric_layers = [l for l in layers_data if l[0].type == "dielectric"]
|
|
2310
|
+
signal_layers = [l for l in layers_data if l[0].type == "signal"]
|
|
2311
|
+
|
|
2312
|
+
# compress the thicknesses if required
|
|
2313
|
+
if scale_elevation:
|
|
2314
|
+
min_thickness = min([i[3] for i in layers_data if i[3] != 0])
|
|
2315
|
+
max_thickness = max([i[3] for i in layers_data])
|
|
2316
|
+
c = 3 # max_thickness = c * min_thickness
|
|
2317
|
+
|
|
2318
|
+
def _compress_t(y):
|
|
2319
|
+
m = min_thickness
|
|
2320
|
+
M = max_thickness
|
|
2321
|
+
k = (c - 1) * m / (M - m)
|
|
2322
|
+
if y > 0:
|
|
2323
|
+
return (y - m) * k + m
|
|
2324
|
+
else:
|
|
2325
|
+
return 0.0
|
|
2326
|
+
|
|
2327
|
+
if stackup_mode == "Laminate":
|
|
2328
|
+
l0 = layers_data[0]
|
|
2329
|
+
compressed_layers_data = [[l0[0], l0[1], _compress_t(l0[3]), _compress_t(l0[3])]] # the first row
|
|
2330
|
+
lp = compressed_layers_data[0]
|
|
2331
|
+
for li in layers_data[1:]: # the other rows
|
|
2332
|
+
ct = _compress_t(li[3])
|
|
2333
|
+
compressed_layers_data.append([li[0], lp[2], lp[2] + ct, ct])
|
|
2334
|
+
lp = compressed_layers_data[-1]
|
|
2335
|
+
layers_data = compressed_layers_data
|
|
2336
|
+
|
|
2337
|
+
elif stackup_mode == "Overlapping":
|
|
2338
|
+
compressed_diels = []
|
|
2339
|
+
first_diel = True
|
|
2340
|
+
for li in dielectric_layers:
|
|
2341
|
+
ct = _compress_t(li[3])
|
|
2342
|
+
if first_diel:
|
|
2343
|
+
if li[1] > 0:
|
|
2344
|
+
l0le = _compress_t(li[1])
|
|
2345
|
+
else:
|
|
2346
|
+
l0le = li[1]
|
|
2347
|
+
compressed_diels.append([li[0], l0le, l0le + ct, ct])
|
|
2348
|
+
first_diel = False
|
|
2349
|
+
else:
|
|
2350
|
+
lp = compressed_diels[-1]
|
|
2351
|
+
compressed_diels.append([li[0], lp[2], lp[2] + ct, ct])
|
|
2352
|
+
|
|
2353
|
+
def _convert_elevation(el):
|
|
2354
|
+
inside = False
|
|
2355
|
+
for i, li in enumerate(dielectric_layers):
|
|
2356
|
+
if li[1] <= el <= li[2]:
|
|
2357
|
+
inside = True
|
|
2358
|
+
break
|
|
2359
|
+
if inside:
|
|
2360
|
+
u = (el - li[1]) / (li[2] - li[1])
|
|
2361
|
+
cli = compressed_diels[i]
|
|
2362
|
+
cel = cli[1] + u * (cli[2] - cli[1])
|
|
2363
|
+
else:
|
|
2364
|
+
cel = el
|
|
2365
|
+
return cel
|
|
2366
|
+
|
|
2367
|
+
compressed_signals = []
|
|
2368
|
+
for li in signal_layers:
|
|
2369
|
+
cle = _convert_elevation(li[1])
|
|
2370
|
+
cue = _convert_elevation(li[2])
|
|
2371
|
+
ct = cue - cle
|
|
2372
|
+
compressed_signals.append([li[0], cle, cue, ct])
|
|
2373
|
+
|
|
2374
|
+
dielectric_layers = compressed_diels
|
|
2375
|
+
signal_layers = compressed_signals
|
|
2376
|
+
|
|
2377
|
+
# create the data for the plot
|
|
2378
|
+
diel_alpha = 0.4
|
|
2379
|
+
signal_alpha = 0.6
|
|
2380
|
+
zero_thickness_alpha = 1.0
|
|
2381
|
+
annotation_fontsize = 14
|
|
2382
|
+
annotation_x_margin = 0.01
|
|
2383
|
+
annotations = []
|
|
2384
|
+
plot_data = []
|
|
2385
|
+
if stackup_mode == "Laminate":
|
|
2386
|
+
min_thickness = min([i[3] for i in layers_data if i[3] != 0])
|
|
2387
|
+
for ly in layers_data:
|
|
2388
|
+
layer = ly[0]
|
|
2389
|
+
|
|
2390
|
+
# set color and label
|
|
2391
|
+
color = [float(i) / 256 for i in layer.color]
|
|
2392
|
+
if color == [1.0, 1.0, 1.0]:
|
|
2393
|
+
color = [0.9, 0.9, 0.9]
|
|
2394
|
+
label = "{}, {}, thick: {:.3f}um, elev: {:.3f}um".format(
|
|
2395
|
+
layer.name, layer.material, layer.thickness * 1e6, layer.lower_elevation * 1e6
|
|
2396
|
+
)
|
|
2397
|
+
|
|
2398
|
+
# create patch
|
|
2399
|
+
x = [0, 0, 1, 1]
|
|
2400
|
+
if ly[3] > 0:
|
|
2401
|
+
lower_elevation = ly[1]
|
|
2402
|
+
upper_elevation = ly[2]
|
|
2403
|
+
y = [lower_elevation, upper_elevation, upper_elevation, lower_elevation]
|
|
2404
|
+
plot_data.insert(0, [x, y, color, label, signal_alpha, "fill"])
|
|
2405
|
+
else:
|
|
2406
|
+
lower_elevation = ly[1] - min_thickness * 0.1 # make the zero thickness layers more visible
|
|
2407
|
+
upper_elevation = ly[2] + min_thickness * 0.1
|
|
2408
|
+
y = [lower_elevation, upper_elevation, upper_elevation, lower_elevation]
|
|
2409
|
+
# put the zero thickness layers on top
|
|
2410
|
+
plot_data.append([x, y, color, label, zero_thickness_alpha, "fill"])
|
|
2411
|
+
|
|
2412
|
+
# create annotation
|
|
2413
|
+
y_pos = (lower_elevation + upper_elevation) / 2
|
|
2414
|
+
if layer.type == "dielectric":
|
|
2415
|
+
x_pos = -annotation_x_margin
|
|
2416
|
+
annotations.append(
|
|
2417
|
+
[x_pos, y_pos, layer.name, {"fontsize": annotation_fontsize, "horizontalalignment": "right"}]
|
|
2418
|
+
)
|
|
2419
|
+
elif layer.type == "signal":
|
|
2420
|
+
x_pos = 1.0 + annotation_x_margin
|
|
2421
|
+
annotations.append([x_pos, y_pos, layer.name, {"fontsize": annotation_fontsize}])
|
|
2422
|
+
|
|
2423
|
+
# evaluate the legend reorder
|
|
2424
|
+
legend_order = []
|
|
2425
|
+
for ly in layers_data:
|
|
2426
|
+
name = ly[0].name
|
|
2427
|
+
for i, a in enumerate(plot_data):
|
|
2428
|
+
iname = a[3].split(",")[0]
|
|
2429
|
+
if name == iname:
|
|
2430
|
+
legend_order.append(i)
|
|
2431
|
+
break
|
|
2432
|
+
|
|
2433
|
+
elif stackup_mode == "Overlapping":
|
|
2434
|
+
min_thickness = min([i[3] for i in signal_layers if i[3] != 0])
|
|
2435
|
+
columns = [] # first column is x=[0,1], second column is x=[1,2] and so on...
|
|
2436
|
+
for ly in signal_layers:
|
|
2437
|
+
lower_elevation = ly[1] # lower elevation
|
|
2438
|
+
t = ly[3] # thickness
|
|
2439
|
+
put_in_column = 0
|
|
2440
|
+
cell_position = 0
|
|
2441
|
+
for c in columns:
|
|
2442
|
+
uep = c[-1][0][2] # upper elevation of the last entry of that column
|
|
2443
|
+
tp = c[-1][0][3] # thickness of the last entry of that column
|
|
2444
|
+
if lower_elevation < uep or (abs(lower_elevation - uep) < 1e-15 and tp == 0 and t == 0):
|
|
2445
|
+
put_in_column += 1
|
|
2446
|
+
cell_position = len(c)
|
|
2447
|
+
else:
|
|
2448
|
+
break
|
|
2449
|
+
if len(columns) < put_in_column + 1: # add a new column if required
|
|
2450
|
+
columns.append([])
|
|
2451
|
+
# put zeros at the beginning of the column until there is the first layer
|
|
2452
|
+
if cell_position != 0:
|
|
2453
|
+
fill_cells = cell_position - 1 - len(columns[put_in_column])
|
|
2454
|
+
for i in range(fill_cells):
|
|
2455
|
+
columns[put_in_column].append(0)
|
|
2456
|
+
# append the layer to the proper column and row
|
|
2457
|
+
x = [put_in_column + 1, put_in_column + 1, put_in_column + 2, put_in_column + 2]
|
|
2458
|
+
columns[put_in_column].append([ly, x])
|
|
2459
|
+
|
|
2460
|
+
# fill the columns matrix with zeros on top
|
|
2461
|
+
n_rows = max([len(i) for i in columns])
|
|
2462
|
+
for c in columns:
|
|
2463
|
+
while len(c) < n_rows:
|
|
2464
|
+
c.append(0)
|
|
2465
|
+
# expand to the right the fill for the signals that have no overlap on the right
|
|
2466
|
+
width = len(columns) + 1
|
|
2467
|
+
for i, c in enumerate(columns[:-1]):
|
|
2468
|
+
for j, r in enumerate(c):
|
|
2469
|
+
if r != 0: # and dname == r[0].name:
|
|
2470
|
+
if columns[i + 1][j] == 0:
|
|
2471
|
+
# nothing on the right, so expand the fill
|
|
2472
|
+
x = r[1]
|
|
2473
|
+
r[1] = [x[0], x[0], width, width]
|
|
2474
|
+
|
|
2475
|
+
for c in columns:
|
|
2476
|
+
for r in c:
|
|
2477
|
+
if r != 0:
|
|
2478
|
+
ly = r[0]
|
|
2479
|
+
layer = ly[0]
|
|
2480
|
+
x = r[1]
|
|
2481
|
+
|
|
2482
|
+
# set color and label
|
|
2483
|
+
color = [float(i) / 256 for i in layer.color]
|
|
2484
|
+
if color == [1.0, 1.0, 1.0]:
|
|
2485
|
+
color = [0.9, 0.9, 0.9]
|
|
2486
|
+
label = "{}, {}, thick: {:.3f}um, elev: {:.3f}um".format(
|
|
2487
|
+
layer.name, layer.material, layer.thickness * 1e6, layer.lower_elevation * 1e6
|
|
2488
|
+
)
|
|
2489
|
+
|
|
2490
|
+
if ly[3] > 0:
|
|
2491
|
+
lower_elevation = ly[1]
|
|
2492
|
+
upper_elevation = ly[2]
|
|
2493
|
+
y = [lower_elevation, upper_elevation, upper_elevation, lower_elevation]
|
|
2494
|
+
plot_data.insert(0, [x, y, color, label, signal_alpha, "fill"])
|
|
2495
|
+
else:
|
|
2496
|
+
lower_elevation = ly[1] - min_thickness * 0.1 # make the zero thickness layers more visible
|
|
2497
|
+
upper_elevation = ly[2] + min_thickness * 0.1
|
|
2498
|
+
y = [lower_elevation, upper_elevation, upper_elevation, lower_elevation]
|
|
2499
|
+
# put the zero thickness layers on top
|
|
2500
|
+
plot_data.append([x, y, color, label, zero_thickness_alpha, "fill"])
|
|
2501
|
+
|
|
2502
|
+
# create annotation
|
|
2503
|
+
x_pos = 1.0
|
|
2504
|
+
y_pos = (lower_elevation + upper_elevation) / 2
|
|
2505
|
+
annotations.append([x_pos, y_pos, layer.name, {"fontsize": annotation_fontsize}])
|
|
2506
|
+
|
|
2507
|
+
# order the annotations based on y_pos (it is necessary later to move them to avoid text overlapping)
|
|
2508
|
+
annotations.sort(key=lambda e: e[1])
|
|
2509
|
+
# move all the annotations to the final x (it could be larger than 1 due to additional columns)
|
|
2510
|
+
width = len(columns) + 1
|
|
2511
|
+
for i, a in enumerate(annotations):
|
|
2512
|
+
a[0] = width + annotation_x_margin * width
|
|
2513
|
+
|
|
2514
|
+
for ly in dielectric_layers:
|
|
2515
|
+
layer = ly[0]
|
|
2516
|
+
# set color and label
|
|
2517
|
+
color = [float(i) / 256 for i in layer.color]
|
|
2518
|
+
if color == [1.0, 1.0, 1.0]:
|
|
2519
|
+
color = [0.9, 0.9, 0.9]
|
|
2520
|
+
label = "{}, {}, thick: {:.3f}um, elev: {:.3f}um".format(
|
|
2521
|
+
layer.name, layer.material, layer.thickness * 1e6, layer.lower_elevation * 1e6
|
|
2522
|
+
)
|
|
2523
|
+
# create the patch
|
|
2524
|
+
lower_elevation = ly[1]
|
|
2525
|
+
upper_elevation = ly[2]
|
|
2526
|
+
y = [lower_elevation, upper_elevation, upper_elevation, lower_elevation]
|
|
2527
|
+
x = [0, 0, width, width]
|
|
2528
|
+
plot_data.insert(0, [x, y, color, label, diel_alpha, "fill"])
|
|
2529
|
+
|
|
2530
|
+
# create annotation
|
|
2531
|
+
x_pos = -annotation_x_margin * width
|
|
2532
|
+
y_pos = (lower_elevation + upper_elevation) / 2
|
|
2533
|
+
annotations.append(
|
|
2534
|
+
[x_pos, y_pos, layer.name, {"fontsize": annotation_fontsize, "horizontalalignment": "right"}]
|
|
2535
|
+
)
|
|
2536
|
+
|
|
2537
|
+
# evaluate the legend reorder
|
|
2538
|
+
legend_order = []
|
|
2539
|
+
for ly in dielectric_layers:
|
|
2540
|
+
name = ly[0].name
|
|
2541
|
+
for i, a in enumerate(plot_data):
|
|
2542
|
+
iname = a[3].split(",")[0]
|
|
2543
|
+
if name == iname:
|
|
2544
|
+
legend_order.append(i)
|
|
2545
|
+
break
|
|
2546
|
+
for ly in signal_layers:
|
|
2547
|
+
name = ly[0].name
|
|
2548
|
+
for i, a in enumerate(plot_data):
|
|
2549
|
+
iname = a[3].split(",")[0]
|
|
2550
|
+
if name == iname:
|
|
2551
|
+
legend_order.append(i)
|
|
2552
|
+
break
|
|
2553
|
+
|
|
2554
|
+
# calculate the extremities of the plot
|
|
2555
|
+
x_min = 0.0
|
|
2556
|
+
x_max = max([max(i[0]) for i in plot_data])
|
|
2557
|
+
if stackup_mode == "Laminate":
|
|
2558
|
+
y_min = layers_data[0][1]
|
|
2559
|
+
y_max = layers_data[-1][2]
|
|
2560
|
+
elif stackup_mode == "Overlapping":
|
|
2561
|
+
y_min = min(dielectric_layers[0][1], signal_layers[0][1])
|
|
2562
|
+
y_max = max(dielectric_layers[-1][2], signal_layers[-1][2])
|
|
2563
|
+
|
|
2564
|
+
# move the annotations to avoid text overlapping
|
|
2565
|
+
new_annotations = []
|
|
2566
|
+
for i, a in enumerate(annotations):
|
|
2567
|
+
if i > 0 and abs(a[1] - annotations[i - 1][1]) < (y_max - y_min) / 75:
|
|
2568
|
+
new_annotations[-1][2] = str(new_annotations[-1][2]) + ", " + str(a[2])
|
|
2569
|
+
else:
|
|
2570
|
+
new_annotations.append(a)
|
|
2571
|
+
annotations = new_annotations
|
|
2572
|
+
|
|
2573
|
+
if plot_definitions:
|
|
2574
|
+
if stackup_mode == "Overlapping":
|
|
2575
|
+
self._logger.warning("Plot of padstacks are supported only for Laminate mode.")
|
|
2576
|
+
|
|
2577
|
+
max_plots = 10
|
|
2578
|
+
|
|
2579
|
+
if not isinstance(plot_definitions, list):
|
|
2580
|
+
plot_definitions = [plot_definitions]
|
|
2581
|
+
color_index = 0
|
|
2582
|
+
color_keys = list(CSS4_COLORS.keys())
|
|
2583
|
+
delta = 1 / (max_plots + 1) # padstack spacing in plot coordinates
|
|
2584
|
+
x_start = delta
|
|
2585
|
+
|
|
2586
|
+
# find the max padstack size to calculate the scaling factor
|
|
2587
|
+
max_padstak_size = 0
|
|
2588
|
+
for definition in plot_definitions:
|
|
2589
|
+
if isinstance(definition, str):
|
|
2590
|
+
definition = self._pedb.padstacks.definitions[definition]
|
|
2591
|
+
for layer, defs in definition.pad_by_layer.items():
|
|
2592
|
+
pad_shape = defs.geometry_type
|
|
2593
|
+
params = defs.parameters_values
|
|
2594
|
+
if pad_shape in [1, 2, 6]:
|
|
2595
|
+
pad_size = params[0]
|
|
2596
|
+
elif pad_shape in [3, 4, 5]:
|
|
2597
|
+
pad_size = max(params[0], params[1])
|
|
2598
|
+
else:
|
|
2599
|
+
pad_size = 1e-4
|
|
2600
|
+
max_padstak_size = max(pad_size, max_padstak_size)
|
|
2601
|
+
if definition.hole_properties:
|
|
2602
|
+
hole_d = definition.hole_properties[0]
|
|
2603
|
+
max_padstak_size = max(hole_d, max_padstak_size)
|
|
2604
|
+
scaling_f_pad = (2 / ((max_plots + 1) * 3)) / max_padstak_size
|
|
2605
|
+
|
|
2606
|
+
for definition in plot_definitions:
|
|
2607
|
+
if isinstance(definition, str):
|
|
2608
|
+
definition = self._pedb.padstacks.definitions[definition]
|
|
2609
|
+
min_le = 1e12
|
|
2610
|
+
max_ue = -1e12
|
|
2611
|
+
max_x = 0
|
|
2612
|
+
padstack_name = definition.name
|
|
2613
|
+
annotations.append([x_start, y_max, padstack_name, {"rotation": 45}])
|
|
2614
|
+
|
|
2615
|
+
via_start_layer = definition.via_start_layer
|
|
2616
|
+
via_stop_layer = definition.via_stop_layer
|
|
2617
|
+
|
|
2618
|
+
if stackup_mode == "Overlapping":
|
|
2619
|
+
# here search the column using the first and last layer. Pick the column with max index.
|
|
2620
|
+
pass
|
|
2621
|
+
|
|
2622
|
+
for layer, defs in definition.pad_by_layer.items():
|
|
2623
|
+
pad_shape = defs.geometry_type
|
|
2624
|
+
params = defs.parameters_values
|
|
2625
|
+
if pad_shape in [1, 2, 6]:
|
|
2626
|
+
pad_size = params[0]
|
|
2627
|
+
elif pad_shape in [3, 4, 5]:
|
|
2628
|
+
pad_size = max(params[0], params[1])
|
|
2629
|
+
else:
|
|
2630
|
+
pad_size = 1e-4
|
|
2631
|
+
|
|
2632
|
+
if stackup_mode == "Laminate":
|
|
2633
|
+
x = [
|
|
2634
|
+
x_start - pad_size / 2 * scaling_f_pad,
|
|
2635
|
+
x_start - pad_size / 2 * scaling_f_pad,
|
|
2636
|
+
x_start + pad_size / 2 * scaling_f_pad,
|
|
2637
|
+
x_start + pad_size / 2 * scaling_f_pad,
|
|
2638
|
+
]
|
|
2639
|
+
lower_elevation = [e[1] for e in layers_data if e[0].name == layer or layer == "Default"][0]
|
|
2640
|
+
upper_elevation = [e[2] for e in layers_data if e[0].name == layer or layer == "Default"][0]
|
|
2641
|
+
y = [lower_elevation, upper_elevation, upper_elevation, lower_elevation]
|
|
2642
|
+
# create the patch for that signal layer
|
|
2643
|
+
plot_data.append([x, y, color_keys[color_index], None, 1.0, "fill"])
|
|
2644
|
+
elif stackup_mode == "Overlapping":
|
|
2645
|
+
# here evaluate the x based on the column evaluated before and the pad size
|
|
2646
|
+
pass
|
|
2647
|
+
|
|
2648
|
+
min_le = min(lower_elevation, min_le)
|
|
2649
|
+
max_ue = max(upper_elevation, max_ue)
|
|
2650
|
+
if definition.hole_properties:
|
|
2651
|
+
# create patch for the hole
|
|
2652
|
+
hole_radius = definition.hole_properties[0] / 2 * scaling_f_pad
|
|
2653
|
+
x = [x_start - hole_radius, x_start - hole_radius, x_start + hole_radius, x_start + hole_radius]
|
|
2654
|
+
y = [min_le, max_ue, max_ue, min_le]
|
|
2655
|
+
plot_data.append([x, y, color_keys[color_index], None, 0.7, "fill"])
|
|
2656
|
+
# create patch for the dielectric
|
|
2657
|
+
max_x = max(max_x, hole_radius)
|
|
2658
|
+
rad = hole_radius * (100 - definition.hole_plating_ratio) / 100
|
|
2659
|
+
x = [x_start - rad, x_start - rad, x_start + rad, x_start + rad]
|
|
2660
|
+
plot_data.append([x, y, color_keys[color_index], None, 1.0, "fill"])
|
|
2661
|
+
|
|
2662
|
+
color_index += 1
|
|
2663
|
+
if color_index == max_plots:
|
|
2664
|
+
self._logger.warning("Maximum number of definitions plotted.")
|
|
2665
|
+
break
|
|
2666
|
+
x_start += delta
|
|
2667
|
+
|
|
2668
|
+
# plot the stackup
|
|
2669
|
+
plt = plot_matplotlib(
|
|
2670
|
+
plot_data,
|
|
2671
|
+
size=size,
|
|
2672
|
+
show_legend=False,
|
|
2673
|
+
xlabel="",
|
|
2674
|
+
ylabel="",
|
|
2675
|
+
title="",
|
|
2676
|
+
snapshot_path=None,
|
|
2677
|
+
x_limits=[x_min, x_max],
|
|
2678
|
+
y_limits=[y_min, y_max],
|
|
2679
|
+
axis_equal=False,
|
|
2680
|
+
annotations=annotations,
|
|
2681
|
+
show=False,
|
|
2682
|
+
)
|
|
2683
|
+
# we have to customize some defaults, so we plot or save the figure here
|
|
2684
|
+
plt.axis("off")
|
|
2685
|
+
plt.box(False)
|
|
2686
|
+
plt.title("Stackup\n ", fontsize=28)
|
|
2687
|
+
# evaluates the number of legend column based on the layer name max length
|
|
2688
|
+
ncol = 3 if max([len(n) for n in layer_names]) < 15 else 2
|
|
2689
|
+
handles, labels = plt.gca().get_legend_handles_labels()
|
|
2690
|
+
plt.legend(
|
|
2691
|
+
[handles[idx] for idx in legend_order],
|
|
2692
|
+
[labels[idx] for idx in legend_order],
|
|
2693
|
+
bbox_to_anchor=(0, -0.05),
|
|
2694
|
+
loc="upper left",
|
|
2695
|
+
borderaxespad=0,
|
|
2696
|
+
ncol=ncol,
|
|
2697
|
+
)
|
|
2698
|
+
plt.tight_layout()
|
|
2699
|
+
if save_plot:
|
|
2700
|
+
plt.savefig(save_plot)
|
|
2701
|
+
else:
|
|
2702
|
+
plt.show()
|
|
2703
|
+
return plt
|