pyedb 0.39.1__py3-none-any.whl → 0.40.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/common/nets.py +11 -2
- pyedb/configuration/cfg_components.py +2 -0
- pyedb/configuration/cfg_padstacks.py +1 -1
- pyedb/configuration/cfg_ports_sources.py +63 -11
- pyedb/configuration/cfg_stackup.py +22 -1
- pyedb/dotnet/database/cell/terminal/edge_terminal.py +55 -0
- pyedb/dotnet/database/components.py +14 -12
- pyedb/dotnet/database/edb_data/ports.py +0 -55
- pyedb/dotnet/database/materials.py +40 -11
- pyedb/dotnet/database/modeler.py +11 -4
- pyedb/grpc/database/components.py +24 -72
- pyedb/grpc/database/definition/materials.py +16 -1
- pyedb/grpc/database/layout/layout.py +43 -4
- pyedb/grpc/database/layout_validation.py +164 -143
- pyedb/grpc/database/net/net.py +5 -8
- pyedb/grpc/database/padstacks.py +184 -31
- pyedb/grpc/database/primitive/padstack_instance.py +1 -1
- pyedb/grpc/database/source_excitations.py +83 -105
- pyedb/grpc/edb.py +41 -198
- {pyedb-0.39.1.dist-info → pyedb-0.40.0.dist-info}/METADATA +2 -1
- {pyedb-0.39.1.dist-info → pyedb-0.40.0.dist-info}/RECORD +24 -24
- {pyedb-0.39.1.dist-info → pyedb-0.40.0.dist-info}/LICENSE +0 -0
- {pyedb-0.39.1.dist-info → pyedb-0.40.0.dist-info}/WHEEL +0 -0
pyedb/grpc/database/padstacks.py
CHANGED
|
@@ -45,6 +45,7 @@ from ansys.edb.core.definition.padstack_def_data import PadType as GrpcPadType
|
|
|
45
45
|
from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
|
|
46
46
|
from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
|
|
47
47
|
from ansys.edb.core.utility.value import Value as GrpcValue
|
|
48
|
+
import numpy as np
|
|
48
49
|
import rtree
|
|
49
50
|
|
|
50
51
|
from pyedb.generic.general_methods import generate_unique_name
|
|
@@ -824,13 +825,12 @@ class Padstacks(object):
|
|
|
824
825
|
elif polygon_hole:
|
|
825
826
|
if isinstance(polygon_hole, list):
|
|
826
827
|
polygon_hole = GrpcPolygonData(points=polygon_hole)
|
|
827
|
-
|
|
828
828
|
padstack_data.set_hole_parameters(
|
|
829
829
|
offset_x=value0,
|
|
830
830
|
offset_y=value0,
|
|
831
831
|
rotation=value0,
|
|
832
832
|
type_geom=GrpcPadGeometryType.PADGEOMTYPE_POLYGON,
|
|
833
|
-
|
|
833
|
+
fp=polygon_hole,
|
|
834
834
|
)
|
|
835
835
|
padstack_data.plating_percentage = GrpcValue(20.0)
|
|
836
836
|
else:
|
|
@@ -1329,27 +1329,61 @@ class Padstacks(object):
|
|
|
1329
1329
|
else:
|
|
1330
1330
|
instances = list(self.instances.values())
|
|
1331
1331
|
for inst in instances:
|
|
1332
|
-
padstack_instances_index.insert(inst.
|
|
1332
|
+
padstack_instances_index.insert(inst.edb_uid, inst.position)
|
|
1333
1333
|
return padstack_instances_index
|
|
1334
1334
|
|
|
1335
|
-
def
|
|
1335
|
+
def get_padstack_instances_id_intersecting_polygon(self, points, nets=None, padstack_instances_index=None):
|
|
1336
1336
|
"""Returns the list of padstack instances ID intersecting a given bounding box and nets.
|
|
1337
1337
|
|
|
1338
1338
|
Parameters
|
|
1339
1339
|
----------
|
|
1340
|
-
|
|
1340
|
+
points : tuple or list.
|
|
1341
1341
|
bounding box, [x1, y1, x2, y2]
|
|
1342
1342
|
nets : str or list, optional
|
|
1343
1343
|
net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
|
|
1344
1344
|
all instances are included in the index. Default value is ``None``.
|
|
1345
|
+
padstack_instances_index : optional, Rtree object.
|
|
1346
|
+
Can be provided optionally to prevent computing padstack instances Rtree index again.
|
|
1345
1347
|
|
|
1346
1348
|
Returns
|
|
1347
1349
|
-------
|
|
1350
|
+
List[int]
|
|
1351
|
+
List of padstack instances ID intersecting the bounding box.
|
|
1352
|
+
"""
|
|
1353
|
+
if not points:
|
|
1354
|
+
raise Exception("No points defining polygon was provided")
|
|
1355
|
+
if not padstack_instances_index:
|
|
1356
|
+
padstack_instances_index = {}
|
|
1357
|
+
for inst in self.instances:
|
|
1358
|
+
padstack_instances_index[inst.id] = inst.position
|
|
1359
|
+
_x = [pt[0] for pt in points]
|
|
1360
|
+
_y = [pt[1] for pt in points]
|
|
1361
|
+
points = [_x, _y]
|
|
1362
|
+
return [
|
|
1363
|
+
ind for ind, pt in padstack_instances_index.items() if GeometryOperators.is_point_in_polygon(pt, points)
|
|
1364
|
+
]
|
|
1365
|
+
|
|
1366
|
+
def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None, padstack_instances_index=None):
|
|
1367
|
+
"""Returns the list of padstack instances ID intersecting a given bounding box and nets.
|
|
1368
|
+
Parameters
|
|
1369
|
+
----------
|
|
1370
|
+
bounding_box : tuple or list.
|
|
1371
|
+
bounding box, [x1, y1, x2, y2]
|
|
1372
|
+
nets : str or list, optional
|
|
1373
|
+
net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
|
|
1374
|
+
all instances are included in the index. Default value is ``None``.
|
|
1375
|
+
padstack_instances_index : optional, Rtree object.
|
|
1376
|
+
Can be provided optionally to prevent computing padstack instances Rtree index again.
|
|
1377
|
+
Returns
|
|
1378
|
+
-------
|
|
1348
1379
|
List of padstack instances ID intersecting the bounding box.
|
|
1349
1380
|
"""
|
|
1350
1381
|
if not bounding_box:
|
|
1351
1382
|
raise Exception("No bounding box was provided")
|
|
1352
|
-
|
|
1383
|
+
if not padstack_instances_index:
|
|
1384
|
+
index = self.get_padstack_instances_rtree_index(nets=nets)
|
|
1385
|
+
else:
|
|
1386
|
+
index = padstack_instances_index
|
|
1353
1387
|
if not len(bounding_box) == 4:
|
|
1354
1388
|
raise Exception("The bounding box length must be equal to 4")
|
|
1355
1389
|
if isinstance(bounding_box, list):
|
|
@@ -1357,18 +1391,23 @@ class Padstacks(object):
|
|
|
1357
1391
|
return list(index.intersection(bounding_box))
|
|
1358
1392
|
|
|
1359
1393
|
def merge_via_along_lines(
|
|
1360
|
-
self,
|
|
1394
|
+
self,
|
|
1395
|
+
net_name="GND",
|
|
1396
|
+
distance_threshold=5e-3,
|
|
1397
|
+
minimum_via_number=6,
|
|
1398
|
+
selected_angles=None,
|
|
1399
|
+
padstack_instances_id=None,
|
|
1361
1400
|
):
|
|
1362
1401
|
"""Replace padstack instances along lines into a single polygon.
|
|
1363
1402
|
|
|
1364
|
-
Detect all
|
|
1403
|
+
Detect all pad-stack instances that are placed along lines and replace them by a single polygon based one
|
|
1365
1404
|
forming a wall shape. This method is designed to simplify meshing on via fence usually added to shield RF traces
|
|
1366
1405
|
on PCB.
|
|
1367
1406
|
|
|
1368
1407
|
Parameters
|
|
1369
1408
|
----------
|
|
1370
1409
|
net_name : str
|
|
1371
|
-
Net name used for detected
|
|
1410
|
+
Net name used for detected pad-stack instances. Default value is ``"GND"``.
|
|
1372
1411
|
|
|
1373
1412
|
distance_threshold : float, None, optional
|
|
1374
1413
|
If two points in a line are separated by a distance larger than `distance_threshold`,
|
|
@@ -1382,22 +1421,29 @@ class Padstacks(object):
|
|
|
1382
1421
|
Other values can be assigned like 45 degrees. When `None` is provided all lines are detected. Default value
|
|
1383
1422
|
is `None`.
|
|
1384
1423
|
|
|
1424
|
+
padstack_instances_id : List[int]
|
|
1425
|
+
List of pad-stack instances ID's to include. If `None`, the algorithm will scan all pad-stack
|
|
1426
|
+
instances belonging to the specified net. Default value is `None`.
|
|
1427
|
+
|
|
1385
1428
|
Returns
|
|
1386
1429
|
-------
|
|
1387
|
-
|
|
1388
|
-
``True`` when succeeded ``False`` when failed. <
|
|
1430
|
+
List[int], list of created pad-stack instances id.
|
|
1389
1431
|
|
|
1390
1432
|
"""
|
|
1391
1433
|
_def = list(set([inst.padstack_def for inst in list(self.instances.values()) if inst.net_name == net_name]))
|
|
1392
1434
|
if not _def:
|
|
1393
1435
|
self._logger.error(f"No padstack definition found for net {net_name}")
|
|
1394
1436
|
return False
|
|
1437
|
+
instances_created = []
|
|
1395
1438
|
_instances_to_delete = []
|
|
1396
1439
|
padstack_instances = []
|
|
1397
|
-
|
|
1398
|
-
padstack_instances.
|
|
1399
|
-
|
|
1400
|
-
|
|
1440
|
+
if padstack_instances_id:
|
|
1441
|
+
padstack_instances = [[self.instances[id] for id in padstack_instances_id]]
|
|
1442
|
+
else:
|
|
1443
|
+
for pdstk_def in _def:
|
|
1444
|
+
padstack_instances.append(
|
|
1445
|
+
[inst for inst in self.definitions[pdstk_def.name].instances if inst.net_name == net_name]
|
|
1446
|
+
)
|
|
1401
1447
|
for pdstk_series in padstack_instances:
|
|
1402
1448
|
instances_location = [inst.position for inst in pdstk_series]
|
|
1403
1449
|
lines, line_indexes = GeometryOperators.find_points_along_lines(
|
|
@@ -1431,44 +1477,99 @@ class Padstacks(object):
|
|
|
1431
1477
|
polygon_hole=polygon_data,
|
|
1432
1478
|
):
|
|
1433
1479
|
self._logger.error(f"Failed to create padstack definition {new_padstack_def.name}")
|
|
1434
|
-
|
|
1435
|
-
|
|
1480
|
+
new_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net_name)
|
|
1481
|
+
if not new_instance:
|
|
1482
|
+
self._logger.error(f"Failed to place padstack instance {new_padstack_def}")
|
|
1483
|
+
else:
|
|
1484
|
+
instances_created.append(new_instance.id)
|
|
1436
1485
|
for inst in _instances_to_delete:
|
|
1437
1486
|
inst.delete()
|
|
1438
|
-
return
|
|
1487
|
+
return instances_created
|
|
1488
|
+
|
|
1489
|
+
def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
|
|
1490
|
+
"""Reduces the number of vias intersecting bounding box and nets by x and y samples.
|
|
1491
|
+
|
|
1492
|
+
Parameters
|
|
1493
|
+
----------
|
|
1494
|
+
bounding_box : tuple or list.
|
|
1495
|
+
bounding box, [x1, y1, x2, y2]
|
|
1496
|
+
x_samples : int
|
|
1497
|
+
y_samples : int
|
|
1498
|
+
nets : str or list, optional
|
|
1499
|
+
net name or list of nets name applying filtering on padstack instances selection. If ``None`` is provided
|
|
1500
|
+
all instances are included in the index. Default value is ``None``.
|
|
1501
|
+
|
|
1502
|
+
Returns
|
|
1503
|
+
-------
|
|
1504
|
+
bool
|
|
1505
|
+
``True`` when succeeded ``False`` when failed.
|
|
1506
|
+
"""
|
|
1507
|
+
|
|
1508
|
+
padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
|
|
1509
|
+
if not padstacks_inbox:
|
|
1510
|
+
self._logger.info("no padstack in bounding box")
|
|
1511
|
+
return False
|
|
1512
|
+
else:
|
|
1513
|
+
if len(padstacks_inbox) <= (x_samples * y_samples):
|
|
1514
|
+
self._logger.info(f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}")
|
|
1515
|
+
return False
|
|
1516
|
+
else:
|
|
1517
|
+
# extract ids and positions
|
|
1518
|
+
vias = {item: self.instances[item].position for item in padstacks_inbox}
|
|
1519
|
+
ids, positions = zip(*vias.items())
|
|
1520
|
+
pt_x, pt_y = zip(*positions)
|
|
1521
|
+
|
|
1522
|
+
# meshgrid
|
|
1523
|
+
_x_min, _x_max = min(pt_x), max(pt_x)
|
|
1524
|
+
_y_min, _y_max = min(pt_y), max(pt_y)
|
|
1525
|
+
|
|
1526
|
+
x_grid, y_grid = np.meshgrid(
|
|
1527
|
+
np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
|
|
1528
|
+
)
|
|
1529
|
+
|
|
1530
|
+
# mapping to meshgrid
|
|
1531
|
+
to_keep = {
|
|
1532
|
+
ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
|
|
1533
|
+
for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
for item in padstacks_inbox:
|
|
1537
|
+
if item not in to_keep:
|
|
1538
|
+
self.instances[item].delete()
|
|
1539
|
+
|
|
1540
|
+
return True
|
|
1439
1541
|
|
|
1440
1542
|
def merge_via(self, contour_boxes, net_filter=None, start_layer=None, stop_layer=None):
|
|
1441
|
-
"""Evaluate
|
|
1543
|
+
"""Evaluate pad-stack instances included on the provided point list and replace all by single instance.
|
|
1442
1544
|
|
|
1443
1545
|
Parameters
|
|
1444
1546
|
----------
|
|
1445
1547
|
contour_boxes : List[List[List[float, float]]]
|
|
1446
1548
|
Nested list of polygon with points [x,y].
|
|
1447
1549
|
net_filter : optional
|
|
1448
|
-
List[str: net_name] apply a net filter,
|
|
1449
|
-
nets included in the filter are excluded from the via merge.
|
|
1550
|
+
List[str: net_name] apply a net filter, nets included in the filter are excluded from the via merge.
|
|
1450
1551
|
start_layer : optional, str
|
|
1451
|
-
|
|
1552
|
+
Pad-stack instance start layer, if `None` the top layer is selected.
|
|
1452
1553
|
stop_layer : optional, str
|
|
1453
|
-
|
|
1554
|
+
Pad-stack instance stop layer, if `None` the bottom layer is selected.
|
|
1454
1555
|
|
|
1455
1556
|
Return
|
|
1456
1557
|
------
|
|
1457
|
-
List[str], list of created
|
|
1558
|
+
List[str], list of created pad-stack instances ID.
|
|
1458
1559
|
|
|
1459
1560
|
"""
|
|
1561
|
+
|
|
1562
|
+
import numpy as np
|
|
1563
|
+
from scipy.spatial import ConvexHull
|
|
1564
|
+
|
|
1460
1565
|
merged_via_ids = []
|
|
1461
1566
|
if not contour_boxes:
|
|
1462
|
-
|
|
1463
|
-
return False
|
|
1464
|
-
if not start_layer:
|
|
1465
|
-
start_layer = list(self._pedb.stackup.layers.values())[0].name
|
|
1466
|
-
if not stop_layer:
|
|
1467
|
-
stop_layer = list(self._pedb.stackup.layers.values())[-1].name
|
|
1567
|
+
raise Exception("No contour box provided, you need to pass a nested list as argument.")
|
|
1468
1568
|
instances_index = {}
|
|
1469
1569
|
for id, inst in self.instances.items():
|
|
1470
1570
|
instances_index[id] = inst.position
|
|
1471
1571
|
for contour_box in contour_boxes:
|
|
1572
|
+
all_instances = self.instances
|
|
1472
1573
|
instances = self.get_padstack_instances_id_intersecting_polygon(
|
|
1473
1574
|
points=contour_box, padstack_instances_index=instances_index
|
|
1474
1575
|
)
|
|
@@ -1479,7 +1580,7 @@ class Padstacks(object):
|
|
|
1479
1580
|
convex_hull_contour = ConvexHull(instances_pts)
|
|
1480
1581
|
contour_points = list(instances_pts[convex_hull_contour.vertices])
|
|
1481
1582
|
layer = list(self._pedb.stackup.layers.values())[0].name
|
|
1482
|
-
polygon = self._pedb.modeler.create_polygon(
|
|
1583
|
+
polygon = self._pedb.modeler.create_polygon(points=contour_points, layer_name=layer)
|
|
1483
1584
|
polygon_data = polygon.polygon_data
|
|
1484
1585
|
polygon.delete()
|
|
1485
1586
|
new_padstack_def = generate_unique_name("test")
|
|
@@ -1498,3 +1599,55 @@ class Padstacks(object):
|
|
|
1498
1599
|
merged_via_ids.append(merged_instance.id)
|
|
1499
1600
|
[self.instances[id].delete() for id in instances]
|
|
1500
1601
|
return merged_via_ids
|
|
1602
|
+
|
|
1603
|
+
def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
|
|
1604
|
+
"""
|
|
1605
|
+
reduce the number of vias intersecting bounding box and nets by x and y samples.
|
|
1606
|
+
|
|
1607
|
+
Parameters
|
|
1608
|
+
----------
|
|
1609
|
+
bounding_box : tuple or list.
|
|
1610
|
+
bounding box, [x1, y1, x2, y2]
|
|
1611
|
+
x_samples : int
|
|
1612
|
+
y_samples : int
|
|
1613
|
+
nets : str or list, optional
|
|
1614
|
+
net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
|
|
1615
|
+
all instances are included in the index. Default value is ``None``.
|
|
1616
|
+
|
|
1617
|
+
Returns
|
|
1618
|
+
-------
|
|
1619
|
+
bool
|
|
1620
|
+
``True`` when succeeded ``False`` when failed.
|
|
1621
|
+
"""
|
|
1622
|
+
|
|
1623
|
+
padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
|
|
1624
|
+
if not padstacks_inbox:
|
|
1625
|
+
raise "No pad-stack in bounding box."
|
|
1626
|
+
else:
|
|
1627
|
+
if len(padstacks_inbox) <= (x_samples * y_samples):
|
|
1628
|
+
raise f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}"
|
|
1629
|
+
else:
|
|
1630
|
+
# extract ids and positions
|
|
1631
|
+
vias = {item: self.instances[item].position for item in padstacks_inbox}
|
|
1632
|
+
ids, positions = zip(*vias.items())
|
|
1633
|
+
pt_x, pt_y = zip(*positions)
|
|
1634
|
+
|
|
1635
|
+
# meshgrid
|
|
1636
|
+
_x_min, _x_max = min(pt_x), max(pt_x)
|
|
1637
|
+
_y_min, _y_max = min(pt_y), max(pt_y)
|
|
1638
|
+
|
|
1639
|
+
x_grid, y_grid = np.meshgrid(
|
|
1640
|
+
np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
|
|
1641
|
+
)
|
|
1642
|
+
|
|
1643
|
+
# mapping to meshgrid
|
|
1644
|
+
to_keep = {
|
|
1645
|
+
ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
|
|
1646
|
+
for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
all_instances = self.instances
|
|
1650
|
+
for item in padstacks_inbox:
|
|
1651
|
+
if item not in to_keep:
|
|
1652
|
+
all_instances[item].delete()
|
|
1653
|
+
return True
|
|
@@ -399,7 +399,7 @@ class PadstackInstance(GrpcPadstackInstance):
|
|
|
399
399
|
layer_list = []
|
|
400
400
|
start_layer_name = start_layer.name
|
|
401
401
|
stop_layer_name = stop_layer.name
|
|
402
|
-
for layer_name in list(self._pedb.stackup.
|
|
402
|
+
for layer_name in list(self._pedb.stackup.signal_layers.keys()):
|
|
403
403
|
if started:
|
|
404
404
|
layer_list.append(layer_name)
|
|
405
405
|
if layer_name == stop_layer_name or layer_name == start_layer_name:
|
|
@@ -20,12 +20,11 @@
|
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
# SOFTWARE.
|
|
22
22
|
|
|
23
|
+
from typing import Union
|
|
24
|
+
|
|
23
25
|
from ansys.edb.core.database import ProductIdType as GrpcProductIdType
|
|
24
26
|
from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
|
|
25
27
|
from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
|
|
26
|
-
from ansys.edb.core.hierarchy.component_group import (
|
|
27
|
-
ComponentGroup as GrpcComponentGroup,
|
|
28
|
-
)
|
|
29
28
|
from ansys.edb.core.terminal.terminals import BoundaryType as GrpcBoundaryType
|
|
30
29
|
from ansys.edb.core.terminal.terminals import EdgeTerminal as GrpcEdgeTerminal
|
|
31
30
|
from ansys.edb.core.terminal.terminals import PrimitiveEdge as GrpcPrimitiveEdge
|
|
@@ -33,6 +32,7 @@ from ansys.edb.core.utility.rlc import Rlc as GrpcRlc
|
|
|
33
32
|
from ansys.edb.core.utility.value import Value as GrpcValue
|
|
34
33
|
|
|
35
34
|
from pyedb.generic.general_methods import generate_unique_name
|
|
35
|
+
from pyedb.grpc.database.components import Component
|
|
36
36
|
from pyedb.grpc.database.layers.stackup_layer import StackupLayer
|
|
37
37
|
from pyedb.grpc.database.net.net import Net
|
|
38
38
|
from pyedb.grpc.database.ports.ports import BundleWavePort, WavePort
|
|
@@ -169,17 +169,18 @@ class SourceExcitation:
|
|
|
169
169
|
Parameters
|
|
170
170
|
----------
|
|
171
171
|
refdes : Component reference designator
|
|
172
|
-
str or
|
|
173
|
-
pins : pin
|
|
174
|
-
pins are provided a pin group will
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
or
|
|
182
|
-
|
|
172
|
+
str or Component object.
|
|
173
|
+
pins : pin specifier(s) or instance(s) where the port terminal is to be created. Single pin name or a list of
|
|
174
|
+
several can be provided. If several pins are provided a pin group will be created. Pin specifiers can be the
|
|
175
|
+
global EDB object ID or padstack instance name or pin name on component with refdes ``refdes``. Pin instances
|
|
176
|
+
can be provided as ``EDBPadstackInstance`` objects.
|
|
177
|
+
For instance for the pin called ``Pin1`` located on component with refdes ``U1``: ``U1-Pin1``, ``Pin1`` with
|
|
178
|
+
``refdes=U1``, the pin's global EDB object ID, or the ``EDBPadstackInstance`` corresponding to the pin can be
|
|
179
|
+
provided.
|
|
180
|
+
Union[int, str, PadstackInstance], List[Union[int, str, PadstackInstance]]
|
|
181
|
+
reference_pins : reference pin specifier(s) or instance(s) for the port reference terminal. Allowed values are
|
|
182
|
+
the same as for the ``pins`` parameter.
|
|
183
|
+
Union[int, str, PadstackInstance], List[Union[int, str, PadstackInstance]]
|
|
183
184
|
impedance : Port impedance
|
|
184
185
|
str, float
|
|
185
186
|
port_name : str, optional
|
|
@@ -208,116 +209,51 @@ class SourceExcitation:
|
|
|
208
209
|
>>> edb.save_edb()
|
|
209
210
|
>>> edb.close_edb()
|
|
210
211
|
"""
|
|
211
|
-
from pyedb.grpc.database.components import Component
|
|
212
212
|
|
|
213
|
-
if isinstance(pins, str):
|
|
214
|
-
pins = [pins]
|
|
215
|
-
elif isinstance(pins, PadstackInstance):
|
|
216
|
-
pins = [pins.name]
|
|
217
|
-
if not reference_pins:
|
|
218
|
-
self._logger.error("No reference pin provided.")
|
|
219
|
-
return False
|
|
220
|
-
if isinstance(reference_pins, str):
|
|
221
|
-
reference_pins = [reference_pins]
|
|
222
|
-
elif isinstance(reference_pins, int):
|
|
223
|
-
reference_pins = [reference_pins]
|
|
224
|
-
elif isinstance(reference_pins, PadstackInstance):
|
|
225
|
-
reference_pins = [reference_pins]
|
|
226
|
-
if isinstance(reference_pins, list):
|
|
227
|
-
_temp = []
|
|
228
|
-
for ref_pin in reference_pins:
|
|
229
|
-
if isinstance(ref_pin, int):
|
|
230
|
-
pins = self._pedb.padstacks.instances
|
|
231
|
-
reference_pins = [pins[ref_pin] for ref_pin in reference_pins if ref_pin in pins]
|
|
232
|
-
# if reference_pins in pins:
|
|
233
|
-
# reference_pins = pins[reference_pins]
|
|
234
|
-
elif isinstance(ref_pin, str):
|
|
235
|
-
component_pins = self._pedb.components.instances[refdes].pins
|
|
236
|
-
if ref_pin in component_pins:
|
|
237
|
-
_temp.append(component_pins[ref_pin])
|
|
238
|
-
else:
|
|
239
|
-
p = [pp for pp in list(self._pedb.padstack.instances.values()) if pp.name == ref_pin]
|
|
240
|
-
if p:
|
|
241
|
-
_temp.extend(p)
|
|
242
|
-
elif isinstance(ref_pin, PadstackInstance):
|
|
243
|
-
_temp.append(ref_pin)
|
|
244
|
-
reference_pins = _temp
|
|
245
213
|
if isinstance(refdes, str):
|
|
246
214
|
refdes = self._pedb.components.instances[refdes]
|
|
247
|
-
elif isinstance(refdes,
|
|
215
|
+
elif isinstance(refdes, Component):
|
|
248
216
|
refdes = Component(self._pedb, refdes)
|
|
249
|
-
|
|
250
|
-
if
|
|
251
|
-
|
|
252
|
-
if len([pin for pin in pins if isinstance(pin, str)]) == len(pins):
|
|
253
|
-
cmp_pins = []
|
|
254
|
-
for pin_name in pins:
|
|
255
|
-
cmp_pins = [pin for pin in list(refdes_pins.values()) if pin_name == pin.name]
|
|
256
|
-
if not cmp_pins:
|
|
257
|
-
for pin in list(refdes_pins.values()):
|
|
258
|
-
if pin.name and "-" in pin.name:
|
|
259
|
-
if pin_name == pin.name.split("-")[1]:
|
|
260
|
-
cmp_pins.append(pin)
|
|
261
|
-
if not cmp_pins:
|
|
262
|
-
self._logger.warning("No pin found during port creation. Port is not defined.")
|
|
263
|
-
return
|
|
264
|
-
pins = cmp_pins
|
|
265
|
-
if not len([pin for pin in pins if isinstance(pin, PadstackInstance)]) == len(pins):
|
|
266
|
-
self._logger.error("Pin list must contain only pins instances")
|
|
217
|
+
pins = self._get_pins_for_ports(pins, refdes)
|
|
218
|
+
if not pins:
|
|
219
|
+
self._logger.error("No pins found during port creation. Port is not defined.")
|
|
267
220
|
return False
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
pin_net_name = "no_net"
|
|
272
|
-
else:
|
|
273
|
-
pin_net_name = pin.net.name
|
|
274
|
-
port_name = f"Port_{pin_net_name}_{refdes.name}_{pins[0].name}"
|
|
275
|
-
|
|
276
|
-
ref_cmp_pins = []
|
|
277
|
-
for ref_pin in reference_pins:
|
|
278
|
-
if ref_pin.name in refdes_pins:
|
|
279
|
-
ref_cmp_pins.append(ref_pin)
|
|
280
|
-
elif "-" in ref_pin.name:
|
|
281
|
-
if ref_pin.name.split("-")[1] in refdes_pins:
|
|
282
|
-
ref_cmp_pins.append(ref_pin)
|
|
283
|
-
elif "via" in ref_pin.name:
|
|
284
|
-
_ref_pin = [
|
|
285
|
-
pin for pin in list(self._pedb.padstacks.instances.values()) if pin.aedt_name == ref_pin.name
|
|
286
|
-
]
|
|
287
|
-
if _ref_pin:
|
|
288
|
-
_ref_pin[0].is_layout_pin = True
|
|
289
|
-
ref_cmp_pins.append(_ref_pin[0])
|
|
290
|
-
if not ref_cmp_pins:
|
|
291
|
-
self._logger.error("No reference pins found.")
|
|
221
|
+
reference_pins = self._get_pins_for_ports(reference_pins, refdes)
|
|
222
|
+
if not reference_pins:
|
|
223
|
+
self._logger.error("No reference pins found during port creation. Port is not defined.")
|
|
292
224
|
return False
|
|
293
|
-
|
|
225
|
+
if refdes and any(refdes.rlc_values):
|
|
226
|
+
return self._pedb.components.deactivate_rlc_component(component=refdes, create_circuit_port=True)
|
|
227
|
+
if not port_name:
|
|
228
|
+
port_name = f"Port_{pins[0].net_name}_{pins[0].name}"
|
|
229
|
+
|
|
294
230
|
if len(pins) > 1 or pingroup_on_single_pin:
|
|
295
231
|
pec_boundary = False
|
|
296
232
|
self._logger.info(
|
|
297
233
|
"Disabling PEC boundary creation, this feature is supported on single pin "
|
|
298
|
-
"ports only, {} pins found
|
|
234
|
+
f"ports only, {len(pins)} pins found (pingroup_on_single_pin: {pingroup_on_single_pin})."
|
|
299
235
|
)
|
|
300
|
-
group_name = "group_{}"
|
|
236
|
+
group_name = f"group_{port_name}"
|
|
301
237
|
pin_group = self._pedb.components.create_pingroup_from_pins(pins, group_name)
|
|
302
238
|
term = self._create_pin_group_terminal(pingroup=pin_group, term_name=port_name)
|
|
303
|
-
|
|
304
239
|
else:
|
|
305
240
|
term = self._create_terminal(pins[0], term_name=port_name)
|
|
306
241
|
term.is_circuit_port = True
|
|
242
|
+
|
|
307
243
|
if len(reference_pins) > 1 or pingroup_on_single_pin:
|
|
308
244
|
pec_boundary = False
|
|
309
245
|
self._logger.info(
|
|
310
|
-
"Disabling PEC boundary creation. This feature is supported on single pin"
|
|
311
|
-
"ports only {} reference pins found
|
|
246
|
+
"Disabling PEC boundary creation. This feature is supported on single pin "
|
|
247
|
+
f"ports only, {len(reference_pins)} reference pins found "
|
|
248
|
+
f"(pingroup_on_single_pin: {pingroup_on_single_pin})."
|
|
312
249
|
)
|
|
313
|
-
ref_group_name = "group_{}_ref"
|
|
250
|
+
ref_group_name = f"group_{port_name}_ref"
|
|
314
251
|
ref_pin_group = self._pedb.components.create_pingroup_from_pins(reference_pins, ref_group_name)
|
|
315
|
-
ref_pin_group = self._pedb.siwave.pin_groups[ref_pin_group.name]
|
|
316
252
|
ref_term = self._create_pin_group_terminal(pingroup=ref_pin_group, term_name=port_name + "_ref")
|
|
317
|
-
|
|
318
253
|
else:
|
|
319
254
|
ref_term = self._create_terminal(reference_pins[0], term_name=port_name + "_ref")
|
|
320
255
|
ref_term.is_circuit_port = True
|
|
256
|
+
|
|
321
257
|
term.impedance = GrpcValue(impedance)
|
|
322
258
|
term.reference_terminal = ref_term
|
|
323
259
|
if pec_boundary:
|
|
@@ -326,11 +262,33 @@ class SourceExcitation:
|
|
|
326
262
|
term.boundary_type = GrpcBoundaryType.PEC
|
|
327
263
|
ref_term.boundary_type = GrpcBoundaryType.PEC
|
|
328
264
|
self._logger.info(
|
|
329
|
-
"PEC boundary created between pin {} and reference pin {
|
|
265
|
+
f"PEC boundary created between pin {pins[0].name} and reference pin {reference_pins[0].name}"
|
|
330
266
|
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
267
|
+
return term or False
|
|
268
|
+
|
|
269
|
+
def _get_pins_for_ports(
|
|
270
|
+
self, pins: Union[int, str, PadstackInstance, list[Union[int, str, PadstackInstance]]], comp: Component
|
|
271
|
+
) -> list[PadstackInstance]:
|
|
272
|
+
if not isinstance(pins, list):
|
|
273
|
+
pins = [pins]
|
|
274
|
+
result = []
|
|
275
|
+
for pin in pins:
|
|
276
|
+
if isinstance(pin, int) and pin in self._pedb.padstacks.instances:
|
|
277
|
+
result.append(self._pedb.padstacks.instances[pin])
|
|
278
|
+
elif isinstance(pin, str):
|
|
279
|
+
if comp and pin in comp.pins:
|
|
280
|
+
result.append(comp.pins[pin])
|
|
281
|
+
else:
|
|
282
|
+
p = [
|
|
283
|
+
pp
|
|
284
|
+
for pp in list(self._pedb.padstacks.instances.values())
|
|
285
|
+
if pp.name == pin or pp.aedt_name == pin
|
|
286
|
+
]
|
|
287
|
+
if p:
|
|
288
|
+
result.append(p[0])
|
|
289
|
+
elif isinstance(pin, PadstackInstance):
|
|
290
|
+
result.append(pin)
|
|
291
|
+
return result
|
|
334
292
|
|
|
335
293
|
def create_port_on_component(
|
|
336
294
|
self,
|
|
@@ -407,8 +365,14 @@ class SourceExcitation:
|
|
|
407
365
|
net_list.append(net_name)
|
|
408
366
|
except:
|
|
409
367
|
pass
|
|
410
|
-
if reference_net
|
|
411
|
-
|
|
368
|
+
if isinstance(reference_net, str) or isinstance(reference_net, Net):
|
|
369
|
+
reference_net = [reference_net]
|
|
370
|
+
_reference_net = [ref.name for ref in reference_net if isinstance(ref, Net)]
|
|
371
|
+
if len(_reference_net) == len(reference_net):
|
|
372
|
+
reference_net = _reference_net
|
|
373
|
+
for ref_net in reference_net:
|
|
374
|
+
if ref_net in net_list:
|
|
375
|
+
net_list.remove(ref_net)
|
|
412
376
|
cmp_pins = [p for p in list(component.pins.values()) if p.net_name in net_list]
|
|
413
377
|
for p in cmp_pins: # pragma no cover
|
|
414
378
|
p.is_layout_pin = True
|
|
@@ -541,6 +505,20 @@ class SourceExcitation:
|
|
|
541
505
|
self._logger.error("Skipping port creation no reference pin found.")
|
|
542
506
|
return True
|
|
543
507
|
|
|
508
|
+
@staticmethod
|
|
509
|
+
def _normalize_net_list(net_list) -> set[str]:
|
|
510
|
+
if not isinstance(net_list, list):
|
|
511
|
+
net_list = [net_list]
|
|
512
|
+
nets = set()
|
|
513
|
+
for net in net_list:
|
|
514
|
+
if isinstance(net, Net):
|
|
515
|
+
net_name = net.name
|
|
516
|
+
if net_name != "":
|
|
517
|
+
nets.add(net_name)
|
|
518
|
+
elif isinstance(net, str) and net != "":
|
|
519
|
+
nets.add(net)
|
|
520
|
+
return nets
|
|
521
|
+
|
|
544
522
|
def _create_terminal(self, pin, term_name=None):
|
|
545
523
|
"""Create terminal on component pin.
|
|
546
524
|
|