pyedb 0.2.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (128) hide show
  1. pyedb/__init__.py +17 -0
  2. pyedb/dotnet/__init__.py +0 -0
  3. pyedb/dotnet/application/Variables.py +2261 -0
  4. pyedb/dotnet/application/__init__.py +0 -0
  5. pyedb/dotnet/clr_module.py +103 -0
  6. pyedb/dotnet/edb.py +4237 -0
  7. pyedb/dotnet/edb_core/__init__.py +1 -0
  8. pyedb/dotnet/edb_core/cell/__init__.py +0 -0
  9. pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
  10. pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
  11. pyedb/dotnet/edb_core/components.py +2669 -0
  12. pyedb/dotnet/edb_core/configuration.py +423 -0
  13. pyedb/dotnet/edb_core/definition/__init__.py +0 -0
  14. pyedb/dotnet/edb_core/definition/component_def.py +166 -0
  15. pyedb/dotnet/edb_core/definition/component_model.py +30 -0
  16. pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
  17. pyedb/dotnet/edb_core/definition/definitions.py +12 -0
  18. pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
  19. pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
  20. pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
  21. pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
  22. pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
  23. pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
  24. pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
  25. pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
  26. pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
  27. pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
  28. pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
  29. pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
  30. pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
  31. pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
  32. pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
  33. pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
  34. pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
  35. pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
  36. pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
  37. pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
  38. pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
  39. pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
  40. pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
  41. pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
  42. pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
  43. pyedb/dotnet/edb_core/general.py +181 -0
  44. pyedb/dotnet/edb_core/hfss.py +1646 -0
  45. pyedb/dotnet/edb_core/layout.py +1244 -0
  46. pyedb/dotnet/edb_core/layout_validation.py +272 -0
  47. pyedb/dotnet/edb_core/materials.py +939 -0
  48. pyedb/dotnet/edb_core/net_class.py +335 -0
  49. pyedb/dotnet/edb_core/nets.py +1215 -0
  50. pyedb/dotnet/edb_core/padstack.py +1389 -0
  51. pyedb/dotnet/edb_core/siwave.py +1427 -0
  52. pyedb/dotnet/edb_core/stackup.py +2703 -0
  53. pyedb/edb_logger.py +396 -0
  54. pyedb/generic/__init__.py +0 -0
  55. pyedb/generic/constants.py +1063 -0
  56. pyedb/generic/data_handlers.py +320 -0
  57. pyedb/generic/design_types.py +104 -0
  58. pyedb/generic/filesystem.py +150 -0
  59. pyedb/generic/general_methods.py +1535 -0
  60. pyedb/generic/plot.py +1840 -0
  61. pyedb/generic/process.py +285 -0
  62. pyedb/generic/settings.py +224 -0
  63. pyedb/ipc2581/__init__.py +0 -0
  64. pyedb/ipc2581/bom/__init__.py +0 -0
  65. pyedb/ipc2581/bom/bom.py +21 -0
  66. pyedb/ipc2581/bom/bom_item.py +32 -0
  67. pyedb/ipc2581/bom/characteristics.py +37 -0
  68. pyedb/ipc2581/bom/refdes.py +16 -0
  69. pyedb/ipc2581/content/__init__.py +0 -0
  70. pyedb/ipc2581/content/color.py +38 -0
  71. pyedb/ipc2581/content/content.py +55 -0
  72. pyedb/ipc2581/content/dictionary_color.py +29 -0
  73. pyedb/ipc2581/content/dictionary_fill.py +28 -0
  74. pyedb/ipc2581/content/dictionary_line.py +30 -0
  75. pyedb/ipc2581/content/entry_color.py +13 -0
  76. pyedb/ipc2581/content/entry_line.py +14 -0
  77. pyedb/ipc2581/content/fill.py +15 -0
  78. pyedb/ipc2581/content/layer_ref.py +10 -0
  79. pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
  80. pyedb/ipc2581/ecad/__init__.py +0 -0
  81. pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
  82. pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
  83. pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
  84. pyedb/ipc2581/ecad/cad_data/component.py +41 -0
  85. pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
  86. pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
  87. pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
  88. pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
  89. pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
  90. pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
  91. pyedb/ipc2581/ecad/cad_data/package.py +104 -0
  92. pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
  93. pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
  94. pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
  95. pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
  96. pyedb/ipc2581/ecad/cad_data/path.py +89 -0
  97. pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
  98. pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
  99. pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
  100. pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
  101. pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
  102. pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
  103. pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
  104. pyedb/ipc2581/ecad/cad_data/step.py +275 -0
  105. pyedb/ipc2581/ecad/cad_header.py +33 -0
  106. pyedb/ipc2581/ecad/ecad.py +19 -0
  107. pyedb/ipc2581/ecad/spec.py +46 -0
  108. pyedb/ipc2581/history_record.py +37 -0
  109. pyedb/ipc2581/ipc2581.py +387 -0
  110. pyedb/ipc2581/logistic_header.py +25 -0
  111. pyedb/misc/__init__.py +0 -0
  112. pyedb/misc/aedtlib_personalib_install.py +14 -0
  113. pyedb/misc/downloads.py +322 -0
  114. pyedb/misc/misc.py +67 -0
  115. pyedb/misc/pyedb.runtimeconfig.json +13 -0
  116. pyedb/misc/siw_feature_config/__init__.py +0 -0
  117. pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
  118. pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
  119. pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
  120. pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
  121. pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
  122. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
  123. pyedb/misc/utilities.py +27 -0
  124. pyedb/modeler/geometry_operators.py +2082 -0
  125. pyedb-0.2.0.dist-info/LICENSE +21 -0
  126. pyedb-0.2.0.dist-info/METADATA +208 -0
  127. pyedb-0.2.0.dist-info/RECORD +128 -0
  128. pyedb-0.2.0.dist-info/WHEEL +4 -0
@@ -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