pyedb 0.57.0__py3-none-any.whl → 0.58.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyedb might be problematic. Click here for more details.
- pyedb/__init__.py +1 -1
- pyedb/configuration/cfg_pin_groups.py +2 -0
- pyedb/dotnet/database/cell/hierarchy/component.py +2 -8
- pyedb/dotnet/database/cell/layout.py +1 -1
- pyedb/dotnet/database/components.py +1 -3
- pyedb/dotnet/database/edb_data/control_file.py +13 -5
- pyedb/dotnet/database/edb_data/padstacks_data.py +34 -12
- pyedb/dotnet/database/edb_data/sources.py +21 -2
- pyedb/dotnet/database/general.py +1 -6
- pyedb/dotnet/database/layout_validation.py +8 -0
- pyedb/dotnet/database/sim_setup_data/io/siwave.py +53 -0
- pyedb/dotnet/database/stackup.py +5 -32
- pyedb/dotnet/database/utilities/hfss_simulation_setup.py +81 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +259 -11
- pyedb/dotnet/edb.py +26 -13
- pyedb/extensions/create_cell_array.py +48 -44
- pyedb/generic/general_methods.py +24 -36
- pyedb/generic/plot.py +8 -23
- pyedb/generic/process.py +78 -10
- pyedb/grpc/database/components.py +7 -5
- pyedb/grpc/database/control_file.py +13 -5
- pyedb/grpc/database/definition/padstack_def.py +10 -5
- pyedb/grpc/database/hierarchy/component.py +2 -9
- pyedb/grpc/database/modeler.py +28 -8
- pyedb/grpc/database/padstacks.py +62 -103
- pyedb/grpc/database/primitive/padstack_instance.py +41 -12
- pyedb/grpc/database/primitive/path.py +13 -13
- pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +79 -0
- pyedb/grpc/database/source_excitations.py +7 -7
- pyedb/grpc/database/stackup.py +5 -33
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +9 -11
- pyedb/grpc/database/terminal/point_terminal.py +30 -0
- pyedb/grpc/database/terminal/terminal.py +16 -2
- pyedb/grpc/database/utility/xml_control_file.py +13 -5
- pyedb/grpc/edb.py +39 -13
- pyedb/misc/aedtlib_personalib_install.py +2 -2
- pyedb/misc/downloads.py +18 -3
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +2 -1
- pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +0 -1
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/METADATA +4 -5
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/RECORD +43 -43
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/WHEEL +0 -0
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/licenses/LICENSE +0 -0
pyedb/grpc/database/padstacks.py
CHANGED
|
@@ -25,6 +25,7 @@ This module contains the `EdbPadstacks` class.
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from collections import defaultdict
|
|
28
|
+
from functools import lru_cache
|
|
28
29
|
import math
|
|
29
30
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
30
31
|
import warnings
|
|
@@ -48,6 +49,29 @@ from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
|
|
|
48
49
|
from pyedb.grpc.database.utility.value import Value
|
|
49
50
|
from pyedb.modeler.geometry_operators import GeometryOperators
|
|
50
51
|
|
|
52
|
+
GEOMETRY_MAP = {
|
|
53
|
+
0: GrpcPadGeometryType.PADGEOMTYPE_NO_GEOMETRY,
|
|
54
|
+
1: GrpcPadGeometryType.PADGEOMTYPE_CIRCLE,
|
|
55
|
+
2: GrpcPadGeometryType.PADGEOMTYPE_SQUARE,
|
|
56
|
+
3: GrpcPadGeometryType.PADGEOMTYPE_RECTANGLE,
|
|
57
|
+
4: GrpcPadGeometryType.PADGEOMTYPE_OVAL,
|
|
58
|
+
5: GrpcPadGeometryType.PADGEOMTYPE_BULLET,
|
|
59
|
+
6: GrpcPadGeometryType.PADGEOMTYPE_NSIDED_POLYGON,
|
|
60
|
+
7: GrpcPadGeometryType.PADGEOMTYPE_POLYGON,
|
|
61
|
+
8: GrpcPadGeometryType.PADGEOMTYPE_ROUND45,
|
|
62
|
+
9: GrpcPadGeometryType.PADGEOMTYPE_ROUND90,
|
|
63
|
+
10: GrpcPadGeometryType.PADGEOMTYPE_SQUARE45,
|
|
64
|
+
11: GrpcPadGeometryType.PADGEOMTYPE_SQUARE90,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
PAD_TYPE_MAP = {
|
|
68
|
+
0: GrpcPadType.REGULAR_PAD,
|
|
69
|
+
1: GrpcPadType.ANTI_PAD,
|
|
70
|
+
2: GrpcPadType.THERMAL_PAD,
|
|
71
|
+
3: GrpcPadType.HOLE,
|
|
72
|
+
4: GrpcPadType.UNKNOWN_GEOM_TYPE,
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
|
|
52
76
|
class Padstacks(object):
|
|
53
77
|
"""Manages EDB methods for padstacks accessible from `Edb.padstacks` property.
|
|
@@ -87,6 +111,15 @@ class Padstacks(object):
|
|
|
87
111
|
def __init__(self, p_edb: Any) -> None:
|
|
88
112
|
self._pedb = p_edb
|
|
89
113
|
self.__definitions: Dict[str, Any] = {}
|
|
114
|
+
self._instances = None
|
|
115
|
+
self._instances_by_name = {}
|
|
116
|
+
self._instances_by_net = {}
|
|
117
|
+
|
|
118
|
+
def clear_instances_cache(self):
|
|
119
|
+
"""Clear the cached padstack instances."""
|
|
120
|
+
self._instances = None
|
|
121
|
+
self._instances_by_name = {}
|
|
122
|
+
self._instances_by_net = {}
|
|
90
123
|
|
|
91
124
|
@property
|
|
92
125
|
def _active_layout(self) -> Any:
|
|
@@ -113,7 +146,8 @@ class Padstacks(object):
|
|
|
113
146
|
""" """
|
|
114
147
|
return self._pedb.stackup.layers
|
|
115
148
|
|
|
116
|
-
|
|
149
|
+
@staticmethod
|
|
150
|
+
def int_to_pad_type(val=0) -> GrpcPadType:
|
|
117
151
|
"""Convert an integer to an EDB.PadGeometryType.
|
|
118
152
|
|
|
119
153
|
Parameters
|
|
@@ -131,20 +165,10 @@ class Padstacks(object):
|
|
|
131
165
|
>>> pad_type = edb_padstacks.int_to_pad_type(1) # Returns ANTI_PAD
|
|
132
166
|
"""
|
|
133
167
|
|
|
134
|
-
|
|
135
|
-
return GrpcPadType.REGULAR_PAD
|
|
136
|
-
elif val == 1:
|
|
137
|
-
return GrpcPadType.ANTI_PAD
|
|
138
|
-
elif val == 2:
|
|
139
|
-
return GrpcPadType.THERMAL_PAD
|
|
140
|
-
elif val == 3:
|
|
141
|
-
return GrpcPadType.HOLE
|
|
142
|
-
elif val == 4:
|
|
143
|
-
return GrpcPadType.UNKNOWN_GEOM_TYPE
|
|
144
|
-
else:
|
|
145
|
-
return val
|
|
168
|
+
return PAD_TYPE_MAP.get(val, val)
|
|
146
169
|
|
|
147
|
-
|
|
170
|
+
@staticmethod
|
|
171
|
+
def int_to_geometry_type(val: int = 0) -> GrpcPadGeometryType:
|
|
148
172
|
"""Convert an integer to an EDB.PadGeometryType.
|
|
149
173
|
|
|
150
174
|
Parameters
|
|
@@ -161,32 +185,7 @@ class Padstacks(object):
|
|
|
161
185
|
>>> geom_type = edb_padstacks.int_to_geometry_type(1) # Returns CIRCLE
|
|
162
186
|
>>> geom_type = edb_padstacks.int_to_geometry_type(2) # Returns SQUARE
|
|
163
187
|
"""
|
|
164
|
-
|
|
165
|
-
return GrpcPadGeometryType.PADGEOMTYPE_NO_GEOMETRY
|
|
166
|
-
elif val == 1:
|
|
167
|
-
return GrpcPadGeometryType.PADGEOMTYPE_CIRCLE
|
|
168
|
-
elif val == 2:
|
|
169
|
-
return GrpcPadGeometryType.PADGEOMTYPE_SQUARE
|
|
170
|
-
elif val == 3:
|
|
171
|
-
return GrpcPadGeometryType.PADGEOMTYPE_RECTANGLE
|
|
172
|
-
elif val == 4:
|
|
173
|
-
return GrpcPadGeometryType.PADGEOMTYPE_OVAL
|
|
174
|
-
elif val == 5:
|
|
175
|
-
return GrpcPadGeometryType.PADGEOMTYPE_BULLET
|
|
176
|
-
elif val == 6:
|
|
177
|
-
return GrpcPadGeometryType.PADGEOMTYPE_NSIDED_POLYGON
|
|
178
|
-
elif val == 7:
|
|
179
|
-
return GrpcPadGeometryType.PADGEOMTYPE_POLYGON
|
|
180
|
-
elif val == 8:
|
|
181
|
-
return GrpcPadGeometryType.PADGEOMTYPE_ROUND45
|
|
182
|
-
elif val == 9:
|
|
183
|
-
return GrpcPadGeometryType.PADGEOMTYPE_ROUND90
|
|
184
|
-
elif val == 10:
|
|
185
|
-
return GrpcPadGeometryType.PADGEOMTYPE_SQUARE45
|
|
186
|
-
elif val == 11:
|
|
187
|
-
return GrpcPadGeometryType.PADGEOMTYPE_SQUARE90
|
|
188
|
-
else:
|
|
189
|
-
return val
|
|
188
|
+
return GEOMETRY_MAP.get(val, val)
|
|
190
189
|
|
|
191
190
|
@property
|
|
192
191
|
def definitions(self) -> Dict[str, PadstackDef]:
|
|
@@ -225,7 +224,17 @@ class Padstacks(object):
|
|
|
225
224
|
>>> for id, instance in all_instances.items():
|
|
226
225
|
... print(f"Instance {id}: {instance.name}")
|
|
227
226
|
"""
|
|
228
|
-
|
|
227
|
+
if self._instances is None:
|
|
228
|
+
self._instances = self._pedb.layout.padstack_instances
|
|
229
|
+
return self._instances
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def instances_by_net(self) -> Dict[Any, PadstackInstance]:
|
|
233
|
+
if not self._instances_by_net:
|
|
234
|
+
for edb_padstack_instance in self._instances.values():
|
|
235
|
+
if edb_padstack_instance.net_name:
|
|
236
|
+
self._instances_by_net.setdefault(edb_padstack_instance.net_name, []).append(edb_padstack_instance)
|
|
237
|
+
return self._instances_by_net
|
|
229
238
|
|
|
230
239
|
@property
|
|
231
240
|
def instances_by_name(self) -> Dict[str, PadstackInstance]:
|
|
@@ -242,11 +251,11 @@ class Padstacks(object):
|
|
|
242
251
|
>>> for name, instance in named_instances.items():
|
|
243
252
|
... print(f"Instance named {name}")
|
|
244
253
|
"""
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return
|
|
254
|
+
if not self._instances_by_name:
|
|
255
|
+
for _, edb_padstack_instance in self.instances.items():
|
|
256
|
+
if edb_padstack_instance.aedt_name:
|
|
257
|
+
self._instances_by_name[edb_padstack_instance.aedt_name] = edb_padstack_instance
|
|
258
|
+
return self._instances_by_name
|
|
250
259
|
|
|
251
260
|
def find_instance_by_id(self, value: int) -> Optional[PadstackInstance]:
|
|
252
261
|
"""Find a padstack instance by database ID.
|
|
@@ -471,6 +480,7 @@ class Padstacks(object):
|
|
|
471
480
|
if p.net_name in net_names:
|
|
472
481
|
if not p.delete(): # pragma: no cover
|
|
473
482
|
return False
|
|
483
|
+
self.clear_instances_cache()
|
|
474
484
|
return True
|
|
475
485
|
|
|
476
486
|
def set_solderball(self, padstackInst, sballLayer_name, isTopPlaced=True, ballDiam=100e-6):
|
|
@@ -1133,6 +1143,7 @@ class Padstacks(object):
|
|
|
1133
1143
|
layer_map=None,
|
|
1134
1144
|
)
|
|
1135
1145
|
padstack_instance.is_layout_pin = is_pin
|
|
1146
|
+
self.clear_instances_cache()
|
|
1136
1147
|
return PadstackInstance(self._pedb, padstack_instance)
|
|
1137
1148
|
else:
|
|
1138
1149
|
raise RuntimeError("Place padstack failed")
|
|
@@ -1505,7 +1516,7 @@ class Padstacks(object):
|
|
|
1505
1516
|
minimum_via_number: int = 6,
|
|
1506
1517
|
selected_angles: Optional[List[float]] = None,
|
|
1507
1518
|
padstack_instances_id: Optional[List[int]] = None,
|
|
1508
|
-
) ->
|
|
1519
|
+
) -> List[str]:
|
|
1509
1520
|
"""Replace padstack instances along lines into a single polygon.
|
|
1510
1521
|
|
|
1511
1522
|
Detect all pad-stack instances that are placed along lines and replace them by a single polygon based one
|
|
@@ -1594,66 +1605,13 @@ class Padstacks(object):
|
|
|
1594
1605
|
inst.delete()
|
|
1595
1606
|
return instances_created
|
|
1596
1607
|
|
|
1597
|
-
def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
|
|
1598
|
-
"""Reduces the number of vias intersecting bounding box and nets by x and y samples.
|
|
1599
|
-
|
|
1600
|
-
Parameters
|
|
1601
|
-
----------
|
|
1602
|
-
bounding_box : tuple or list.
|
|
1603
|
-
bounding box, [x1, y1, x2, y2]
|
|
1604
|
-
x_samples : int
|
|
1605
|
-
y_samples : int
|
|
1606
|
-
nets : str or list, optional
|
|
1607
|
-
net name or list of nets name applying filtering on padstack instances selection. If ``None`` is provided
|
|
1608
|
-
all instances are included in the index. Default value is ``None``.
|
|
1609
|
-
|
|
1610
|
-
Returns
|
|
1611
|
-
-------
|
|
1612
|
-
bool
|
|
1613
|
-
``True`` when succeeded ``False`` when failed.
|
|
1614
|
-
"""
|
|
1615
|
-
|
|
1616
|
-
padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
|
|
1617
|
-
if not padstacks_inbox:
|
|
1618
|
-
self._logger.info("no padstack in bounding box")
|
|
1619
|
-
return False
|
|
1620
|
-
else:
|
|
1621
|
-
if len(padstacks_inbox) <= (x_samples * y_samples):
|
|
1622
|
-
self._logger.info(f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}")
|
|
1623
|
-
return False
|
|
1624
|
-
else:
|
|
1625
|
-
# extract ids and positions
|
|
1626
|
-
vias = {item: self.instances[item].position for item in padstacks_inbox}
|
|
1627
|
-
ids, positions = zip(*vias.items())
|
|
1628
|
-
pt_x, pt_y = zip(*positions)
|
|
1629
|
-
|
|
1630
|
-
# meshgrid
|
|
1631
|
-
_x_min, _x_max = min(pt_x), max(pt_x)
|
|
1632
|
-
_y_min, _y_max = min(pt_y), max(pt_y)
|
|
1633
|
-
|
|
1634
|
-
x_grid, y_grid = np.meshgrid(
|
|
1635
|
-
np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
|
|
1636
|
-
)
|
|
1637
|
-
|
|
1638
|
-
# mapping to meshgrid
|
|
1639
|
-
to_keep = {
|
|
1640
|
-
ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
|
|
1641
|
-
for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
for item in padstacks_inbox:
|
|
1645
|
-
if item not in to_keep:
|
|
1646
|
-
self.instances[item].delete()
|
|
1647
|
-
|
|
1648
|
-
return True
|
|
1649
|
-
|
|
1650
1608
|
def merge_via(
|
|
1651
1609
|
self,
|
|
1652
1610
|
contour_boxes: List[List[float]],
|
|
1653
1611
|
net_filter: Optional[Union[str, List[str]]] = None,
|
|
1654
1612
|
start_layer: Optional[str] = None,
|
|
1655
1613
|
stop_layer: Optional[str] = None,
|
|
1656
|
-
) ->
|
|
1614
|
+
) -> List[str]:
|
|
1657
1615
|
"""Evaluate pad-stack instances included on the provided point list and replace all by single instance.
|
|
1658
1616
|
|
|
1659
1617
|
Parameters
|
|
@@ -1673,7 +1631,6 @@ class Padstacks(object):
|
|
|
1673
1631
|
|
|
1674
1632
|
"""
|
|
1675
1633
|
|
|
1676
|
-
import numpy as np
|
|
1677
1634
|
from scipy.spatial import ConvexHull
|
|
1678
1635
|
|
|
1679
1636
|
merged_via_ids = []
|
|
@@ -1717,6 +1674,7 @@ class Padstacks(object):
|
|
|
1717
1674
|
)
|
|
1718
1675
|
merged_via_ids.append(merged_instance.edb_uid)
|
|
1719
1676
|
[self.instances[inst].delete() for inst in instances]
|
|
1677
|
+
self.clear_instances_cache()
|
|
1720
1678
|
return merged_via_ids
|
|
1721
1679
|
|
|
1722
1680
|
def reduce_via_in_bounding_box(
|
|
@@ -1772,6 +1730,7 @@ class Padstacks(object):
|
|
|
1772
1730
|
for item in padstacks_inbox:
|
|
1773
1731
|
if item not in to_keep:
|
|
1774
1732
|
all_instances[item].delete()
|
|
1733
|
+
self.clear_instances_cache()
|
|
1775
1734
|
return True
|
|
1776
1735
|
|
|
1777
1736
|
@staticmethod
|
|
@@ -1927,5 +1886,5 @@ class Padstacks(object):
|
|
|
1927
1886
|
to_delete = set(padstacks) - to_keep
|
|
1928
1887
|
for _id in to_delete:
|
|
1929
1888
|
all_instances[_id].delete()
|
|
1930
|
-
|
|
1889
|
+
self.clear_instances_cache()
|
|
1931
1890
|
return list(to_keep), grid
|
|
@@ -122,6 +122,47 @@ class PadstackInstance(GrpcPadstackInstance):
|
|
|
122
122
|
term = PadstackInstanceTerminal(self._pedb, term)
|
|
123
123
|
return term if not term.is_null else None
|
|
124
124
|
|
|
125
|
+
@property
|
|
126
|
+
def side_number(self):
|
|
127
|
+
"""Return the number of sides meshed of the padstack instance.
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
int
|
|
131
|
+
Number of sides meshed of the padstack instance.
|
|
132
|
+
"""
|
|
133
|
+
side_value = self.get_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21)
|
|
134
|
+
if side_value:
|
|
135
|
+
return int(re.search(r"(?m)^\s*sid=(\d+)", side_value).group(1))
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
@side_number.setter
|
|
139
|
+
def side_number(self, value):
|
|
140
|
+
"""Set the number of sides meshed of the padstack instance.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
value : int
|
|
145
|
+
Number of sides to mesh the padstack instance.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
bool
|
|
150
|
+
True if successful, False otherwise.
|
|
151
|
+
"""
|
|
152
|
+
if isinstance(value, int) and 3 <= value <= 64:
|
|
153
|
+
prop_string = f"$begin ''\n\tsid={value}\n\tmat='copper'\n\tvs='Wirebond'\n$end ''\n"
|
|
154
|
+
self.set_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21, prop_string)
|
|
155
|
+
else:
|
|
156
|
+
raise ValueError("Number of sides must be an integer between 3 and 64")
|
|
157
|
+
|
|
158
|
+
def delete(self):
|
|
159
|
+
"""Delete the padstack instance."""
|
|
160
|
+
try:
|
|
161
|
+
self._pedb.padstacks._instances.pop(self.edb_uid, None)
|
|
162
|
+
except Exception:
|
|
163
|
+
self._pedb.padstacks.clear_instances_cache()
|
|
164
|
+
super().delete()
|
|
165
|
+
|
|
125
166
|
def set_backdrill_top(self, drill_depth, drill_diameter, offset=0.0):
|
|
126
167
|
"""Set backdrill from top.
|
|
127
168
|
|
|
@@ -759,18 +800,6 @@ class PadstackInstance(GrpcPadstackInstance):
|
|
|
759
800
|
def aedt_name(self, value):
|
|
760
801
|
self.set_product_property(GrpcProductIdType.DESIGNER, 11, value)
|
|
761
802
|
|
|
762
|
-
@property
|
|
763
|
-
def side_number(self) -> int:
|
|
764
|
-
if not self._side_number:
|
|
765
|
-
prop_string = "$begin ''\n\tsid=3\n\tmat='copper'\n\tvs='Wirebond'\n$end ''\n"
|
|
766
|
-
self.set_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21, prop_string)
|
|
767
|
-
self._side_number = self.get_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21)
|
|
768
|
-
return self._side_number
|
|
769
|
-
|
|
770
|
-
@side_number.setter
|
|
771
|
-
def side_number(self, value):
|
|
772
|
-
self._side_number = self.set_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21, value)
|
|
773
|
-
|
|
774
803
|
def split(self) -> list:
|
|
775
804
|
"""Split padstack instance into multiple instances. The new instances only connect adjacent layers."""
|
|
776
805
|
pdef_name = self.padstack_definition
|
|
@@ -25,7 +25,7 @@ from typing import Union
|
|
|
25
25
|
from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
|
|
26
26
|
from ansys.edb.core.primitive.path import (
|
|
27
27
|
Path as GrpcPath,
|
|
28
|
-
PathCornerType as
|
|
28
|
+
PathCornerType as GrpcPathCornerType,
|
|
29
29
|
PathEndCapType as GrpcPathEndCapType,
|
|
30
30
|
)
|
|
31
31
|
|
|
@@ -84,9 +84,9 @@ class Path(GrpcPath, Primitive):
|
|
|
84
84
|
layer: Union[str, Layer] = None,
|
|
85
85
|
net: Union[str, "Net"] = None,
|
|
86
86
|
width: float = 100e-6,
|
|
87
|
-
end_cap1: str = "flat",
|
|
88
|
-
end_cap2: str = "flat",
|
|
89
|
-
corner_style: str = "sharp",
|
|
87
|
+
end_cap1: Union[str, GrpcPathEndCapType] = "flat",
|
|
88
|
+
end_cap2: Union[str, GrpcPathEndCapType] = "flat",
|
|
89
|
+
corner_style: Union[str, GrpcPathCornerType] = "sharp",
|
|
90
90
|
points: Union[list, GrpcPolygonData] = None,
|
|
91
91
|
):
|
|
92
92
|
"""
|
|
@@ -141,9 +141,9 @@ class Path(GrpcPath, Primitive):
|
|
|
141
141
|
"clipped": GrpcPathEndCapType.CLIPPED,
|
|
142
142
|
}
|
|
143
143
|
corner_style_mapping = {
|
|
144
|
-
"round":
|
|
145
|
-
"mitter":
|
|
146
|
-
"sharp":
|
|
144
|
+
"round": GrpcPathCornerType.ROUND,
|
|
145
|
+
"mitter": GrpcPathCornerType.MITER,
|
|
146
|
+
"sharp": GrpcPathCornerType.SHARP,
|
|
147
147
|
}
|
|
148
148
|
if isinstance(end_cap1, str):
|
|
149
149
|
end_cap1 = end_cap_mapping[end_cap1.lower()]
|
|
@@ -215,9 +215,9 @@ class Path(GrpcPath, Primitive):
|
|
|
215
215
|
``True`` when successful, ``False`` when failed.
|
|
216
216
|
"""
|
|
217
217
|
mapping = {
|
|
218
|
-
"round":
|
|
219
|
-
"mitter":
|
|
220
|
-
"sharp":
|
|
218
|
+
"round": GrpcPathCornerType.ROUND,
|
|
219
|
+
"mitter": GrpcPathCornerType.MITER,
|
|
220
|
+
"sharp": GrpcPathCornerType.SHARP,
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
cloned_path = GrpcPath.create(
|
|
@@ -444,9 +444,9 @@ class Path(GrpcPath, Primitive):
|
|
|
444
444
|
def corner_style(self, corner_type):
|
|
445
445
|
if isinstance(corner_type, str):
|
|
446
446
|
mapping = {
|
|
447
|
-
"round":
|
|
448
|
-
"mitter":
|
|
449
|
-
"sharp":
|
|
447
|
+
"round": GrpcPathCornerType.ROUND,
|
|
448
|
+
"mitter": GrpcPathCornerType.MITER,
|
|
449
|
+
"sharp": GrpcPathCornerType.SHARP,
|
|
450
450
|
}
|
|
451
451
|
self.corner_style = mapping[corner_type]
|
|
452
452
|
|
|
@@ -30,6 +30,7 @@ from ansys.edb.core.simulation_setup.hfss_simulation_settings import (
|
|
|
30
30
|
from ansys.edb.core.simulation_setup.hfss_simulation_setup import (
|
|
31
31
|
HfssSimulationSetup as GrpcHfssSimulationSetup,
|
|
32
32
|
)
|
|
33
|
+
from ansys.edb.core.simulation_setup.mesh_operation import LengthMeshOperation as GrpcLengthMeshOperation
|
|
33
34
|
|
|
34
35
|
from pyedb.generic.general_methods import generate_unique_name
|
|
35
36
|
from pyedb.grpc.database.simulation_setup.sweep_data import SweepData
|
|
@@ -411,3 +412,81 @@ class HfssSimulationSetup(GrpcHfssSimulationSetup):
|
|
|
411
412
|
else:
|
|
412
413
|
self._pedb.logger.error("Failed to add frequency sweep data")
|
|
413
414
|
return False
|
|
415
|
+
|
|
416
|
+
def auto_mesh_operation(
|
|
417
|
+
self,
|
|
418
|
+
trace_ratio_seeding: float = 3,
|
|
419
|
+
signal_via_side_number: int = 12,
|
|
420
|
+
power_ground_via_side_number: int = 6,
|
|
421
|
+
) -> bool:
|
|
422
|
+
"""
|
|
423
|
+
Automatically create and apply a length-based mesh operation for all nets in the design.
|
|
424
|
+
|
|
425
|
+
The method inspects every signal net, determines the smallest trace width, and
|
|
426
|
+
seeds a :class:`GrpcLengthMeshOperation` whose maximum element length is
|
|
427
|
+
``smallest_width * trace_ratio_seeding``. Signal vias (padstack instances) are
|
|
428
|
+
configured with the requested number of polygon sides, while power/ground vias
|
|
429
|
+
are updated through the global ``num_via_sides`` advanced setting.
|
|
430
|
+
|
|
431
|
+
Parameters
|
|
432
|
+
----------
|
|
433
|
+
trace_ratio_seeding : float, optional
|
|
434
|
+
Ratio used to compute the maximum allowed element length from the
|
|
435
|
+
smallest trace width found in the design. The resulting length is
|
|
436
|
+
``min_width * trace_ratio_seeding``. Defaults to ``3``.
|
|
437
|
+
signal_via_side_number : int, optional
|
|
438
|
+
Number of sides (i.e. faceting resolution) assigned to **signal**
|
|
439
|
+
padstack instances that belong to the nets being meshed.
|
|
440
|
+
Defaults to ``12``.
|
|
441
|
+
power_ground_via_side_number : int, optional
|
|
442
|
+
Number of sides assigned to **power/ground** vias via the global
|
|
443
|
+
``advanced.num_via_sides`` setting. Defaults to ``6``.
|
|
444
|
+
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
bool
|
|
448
|
+
|
|
449
|
+
Raises
|
|
450
|
+
------
|
|
451
|
+
ValueError
|
|
452
|
+
If the design contains no terminals, making mesh seeding impossible.
|
|
453
|
+
|
|
454
|
+
Notes
|
|
455
|
+
-----
|
|
456
|
+
* Only primitives of type ``"path"`` are considered when determining the
|
|
457
|
+
smallest trace width.
|
|
458
|
+
* Every ``(net, layer, sheet)`` tuple required by the mesher is
|
|
459
|
+
automatically populated; sheet are explicitly marked as ``False``.
|
|
460
|
+
* Existing contents of :attr:`mesh_operations` are **replaced** by the
|
|
461
|
+
single new operation.
|
|
462
|
+
|
|
463
|
+
Examples
|
|
464
|
+
--------
|
|
465
|
+
>>> setup = edbapp.setups["my_setup"]
|
|
466
|
+
>>> setup.auto_mesh_operation(trace_ratio_seeding=4, signal_via_side_number=16)
|
|
467
|
+
>>> setup.mesh_operations[0].max_length
|
|
468
|
+
'2.5um'
|
|
469
|
+
"""
|
|
470
|
+
net_for_mesh_seeding = list(set([term.net.name for term in list(self._pedb.terminals.values())]))
|
|
471
|
+
if not net_for_mesh_seeding:
|
|
472
|
+
raise ValueError("No terminals found to seed the mesh operation.")
|
|
473
|
+
meshop = GrpcLengthMeshOperation(name=f"{self.name}_AutoMeshOp")
|
|
474
|
+
layer_info = []
|
|
475
|
+
smallest_width = 1e3
|
|
476
|
+
for net in net_for_mesh_seeding:
|
|
477
|
+
traces = [prim for prim in self._pedb.modeler.primitives_by_net[net] if prim.type == "path"]
|
|
478
|
+
_width = min([trace.width for trace in traces], default=1e3)
|
|
479
|
+
if _width < smallest_width:
|
|
480
|
+
smallest_width = _width
|
|
481
|
+
layers = list(set([trace.layer.name for trace in traces]))
|
|
482
|
+
for layer in layers:
|
|
483
|
+
layer_info.append((net, layer, False))
|
|
484
|
+
for inst in self._pedb.padstacks.instances_by_net[net]:
|
|
485
|
+
inst.side_number = signal_via_side_number
|
|
486
|
+
meshop.max_length = f"{round(float((smallest_width * trace_ratio_seeding)), 9) * 1e6}um"
|
|
487
|
+
meshop.net_layer_info = layer_info
|
|
488
|
+
self.mesh_operations = [meshop]
|
|
489
|
+
self.settings.advanced.num_via_sides = power_ground_via_side_number
|
|
490
|
+
if self.mesh_operations:
|
|
491
|
+
return True
|
|
492
|
+
return False
|
|
@@ -486,22 +486,22 @@ class SourceExcitation:
|
|
|
486
486
|
|
|
487
487
|
def create_port_on_component(
|
|
488
488
|
self,
|
|
489
|
-
component: Union[str,
|
|
489
|
+
component: Union[str, Component],
|
|
490
490
|
net_list: Union[str, List[str]],
|
|
491
|
-
port_type:
|
|
491
|
+
port_type: str = "coax_port",
|
|
492
492
|
do_pingroup: Optional[bool] = True,
|
|
493
493
|
reference_net: Optional[str] = None,
|
|
494
494
|
port_name: Optional[List[str]] = None,
|
|
495
|
-
solder_balls_height:
|
|
496
|
-
solder_balls_size:
|
|
497
|
-
solder_balls_mid_size:
|
|
495
|
+
solder_balls_height: Union[float, str] = None,
|
|
496
|
+
solder_balls_size: Union[float, str] = None,
|
|
497
|
+
solder_balls_mid_size: Union[float, str] = None,
|
|
498
498
|
extend_reference_pins_outside_component: Optional[bool] = False,
|
|
499
499
|
) -> List[str]:
|
|
500
500
|
"""Create ports on a component.
|
|
501
501
|
|
|
502
502
|
Parameters
|
|
503
503
|
----------
|
|
504
|
-
component : str or
|
|
504
|
+
component : str or Component
|
|
505
505
|
EDB component or str component name.
|
|
506
506
|
net_list : str or list of string.
|
|
507
507
|
List of nets where ports must be created on the component.
|
|
@@ -2661,7 +2661,7 @@ class SourceExcitation:
|
|
|
2661
2661
|
for __pin in pins_name:
|
|
2662
2662
|
if __pin in pins:
|
|
2663
2663
|
pin = pins[__pin]
|
|
2664
|
-
term_name = f"{pin.component.name}_{pin.net.name}_{pin.component}"
|
|
2664
|
+
term_name = f"{pin.component.name}_{pin.net.name}_{pin.component.name}"
|
|
2665
2665
|
start_layer, stop_layer = pin.get_layer_range()
|
|
2666
2666
|
if start_layer:
|
|
2667
2667
|
positive_terminal = PadstackInstanceTerminal.create(
|
pyedb/grpc/database/stackup.py
CHANGED
|
@@ -49,6 +49,10 @@ from ansys.edb.core.layer.layer_collection import (
|
|
|
49
49
|
)
|
|
50
50
|
from ansys.edb.core.layer.stackup_layer import StackupLayer as GrpcStackupLayer
|
|
51
51
|
from ansys.edb.core.layout.mcad_model import McadModel as GrpcMcadModel
|
|
52
|
+
from defusedxml.ElementTree import parse as defused_parse
|
|
53
|
+
import matplotlib.colors as colors
|
|
54
|
+
import numpy as np
|
|
55
|
+
import pandas as pd
|
|
52
56
|
|
|
53
57
|
from pyedb.generic.general_methods import ET, generate_unique_name
|
|
54
58
|
from pyedb.grpc.database.layers.layer import Layer
|
|
@@ -56,24 +60,6 @@ from pyedb.grpc.database.layers.stackup_layer import StackupLayer
|
|
|
56
60
|
from pyedb.grpc.database.utility.value import Value
|
|
57
61
|
from pyedb.misc.aedtlib_personalib_install import write_pretty_xml
|
|
58
62
|
|
|
59
|
-
colors = None
|
|
60
|
-
pd = None
|
|
61
|
-
np = None
|
|
62
|
-
try:
|
|
63
|
-
import matplotlib.colors as colors
|
|
64
|
-
except ImportError:
|
|
65
|
-
colors = None
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
import numpy as np
|
|
69
|
-
except ImportError:
|
|
70
|
-
np = None
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
import pandas as pd
|
|
74
|
-
except ImportError:
|
|
75
|
-
pd = None
|
|
76
|
-
|
|
77
63
|
logger = logging.getLogger(__name__)
|
|
78
64
|
|
|
79
65
|
|
|
@@ -565,9 +551,6 @@ class Stackup(LayerCollection):
|
|
|
565
551
|
>>> edb = Edb()
|
|
566
552
|
>>> edb.stackup.create_symmetric_stackup(layer_count=4)
|
|
567
553
|
"""
|
|
568
|
-
if not np:
|
|
569
|
-
self._pedb.logger.error("Numpy is needed. Please, install it first.")
|
|
570
|
-
return False
|
|
571
554
|
if not layer_count % 2 == 0:
|
|
572
555
|
return False
|
|
573
556
|
|
|
@@ -1024,10 +1007,6 @@ class Stackup(LayerCollection):
|
|
|
1024
1007
|
return self.export(fpath, file_format=file_format, include_material_with_layer=include_material_with_layer)
|
|
1025
1008
|
|
|
1026
1009
|
def _export_layer_stackup_to_csv_xlsx(self, fpath: Optional[str] = None, file_format: Optional[str] = None) -> bool:
|
|
1027
|
-
if not pd:
|
|
1028
|
-
self._pedb.logger.error("Pandas is needed. Please, install it first.")
|
|
1029
|
-
return False
|
|
1030
|
-
|
|
1031
1010
|
data = {
|
|
1032
1011
|
"Type": [],
|
|
1033
1012
|
"Material": [],
|
|
@@ -1955,10 +1934,6 @@ class Stackup(LayerCollection):
|
|
|
1955
1934
|
bool
|
|
1956
1935
|
``True`` when successful.
|
|
1957
1936
|
"""
|
|
1958
|
-
if not pd:
|
|
1959
|
-
self._pedb.logger.error("Pandas is needed. You must install it first.")
|
|
1960
|
-
return False
|
|
1961
|
-
|
|
1962
1937
|
df = pd.read_csv(file_path, index_col=0)
|
|
1963
1938
|
|
|
1964
1939
|
for name in self.layers.keys(): # pragma: no cover
|
|
@@ -2227,10 +2202,7 @@ class Stackup(LayerCollection):
|
|
|
2227
2202
|
bool
|
|
2228
2203
|
``True`` when successful.
|
|
2229
2204
|
"""
|
|
2230
|
-
|
|
2231
|
-
self._pedb.logger.error("Matplotlib is needed. Please, install it first.")
|
|
2232
|
-
return False
|
|
2233
|
-
tree = ET.parse(file_path)
|
|
2205
|
+
tree = defused_parse(file_path)
|
|
2234
2206
|
root = tree.getroot()
|
|
2235
2207
|
stackup = root.find("Stackup")
|
|
2236
2208
|
stackup_dict = {}
|
|
@@ -37,16 +37,6 @@ class PadstackInstanceTerminal(GrpcPadstackInstanceTerminal):
|
|
|
37
37
|
super().__init__(edb_object.msg)
|
|
38
38
|
self._pedb = pedb
|
|
39
39
|
|
|
40
|
-
@property
|
|
41
|
-
def boundary_type(self) -> str:
|
|
42
|
-
"""Boundary type.
|
|
43
|
-
|
|
44
|
-
Returns
|
|
45
|
-
-------
|
|
46
|
-
str : boundary type.
|
|
47
|
-
"""
|
|
48
|
-
return super().boundary_type.name.lower()
|
|
49
|
-
|
|
50
40
|
@property
|
|
51
41
|
def position(self) -> list[float]:
|
|
52
42
|
"""Terminal position.
|
|
@@ -193,7 +183,15 @@ class PadstackInstanceTerminal(GrpcPadstackInstanceTerminal):
|
|
|
193
183
|
"rlc": GrpcBoundaryType.RLC,
|
|
194
184
|
"pec": GrpcBoundaryType.PEC,
|
|
195
185
|
}
|
|
196
|
-
|
|
186
|
+
if isinstance(value, str):
|
|
187
|
+
key = value.lower()
|
|
188
|
+
else:
|
|
189
|
+
key = value.name.lower()
|
|
190
|
+
new_boundary_type = mapping.get(key)
|
|
191
|
+
if new_boundary_type is None:
|
|
192
|
+
valid_types = ", ".join(mapping.keys())
|
|
193
|
+
raise ValueError(f"Invalid boundary type '{value}'. Valid types are: {valid_types}")
|
|
194
|
+
super(PadstackInstanceTerminal, self.__class__).boundary_type.__set__(self, new_boundary_type)
|
|
197
195
|
|
|
198
196
|
@property
|
|
199
197
|
def is_port(self) -> bool:
|