Skip to content

Commit 7ba1f83

Browse files
authored
FEAT: Grpc transition pr#992 tracker (#1091)
* grpc transition PR#1005 tracker * grpc transition PR#992 tracker * grpc transition PR#992 tracker * grpc transition PR#992 tracker
1 parent 788a5b9 commit 7ba1f83

File tree

5 files changed

+201
-58
lines changed

5 files changed

+201
-58
lines changed

src/pyedb/grpc/database/layout/layout.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from typing import Union
2727

2828
from ansys.edb.core.layout.layout import Layout as GrpcLayout
29+
import ansys.edb.core.primitive.primitive
2930

3031
from pyedb.grpc.database.hierarchy.component import Component
3132
from pyedb.grpc.database.hierarchy.pingroup import PinGroup
@@ -34,7 +35,12 @@
3435
from pyedb.grpc.database.net.extended_net import ExtendedNet
3536
from pyedb.grpc.database.net.net import Net
3637
from pyedb.grpc.database.net.net_class import NetClass
38+
from pyedb.grpc.database.primitive.bondwire import Bondwire
39+
from pyedb.grpc.database.primitive.circle import Circle
3740
from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
41+
from pyedb.grpc.database.primitive.path import Path
42+
from pyedb.grpc.database.primitive.polygon import Polygon
43+
from pyedb.grpc.database.primitive.rectangle import Rectangle
3844
from pyedb.grpc.database.terminal.bundle_terminal import BundleTerminal
3945
from pyedb.grpc.database.terminal.edge_terminal import EdgeTerminal
4046
from pyedb.grpc.database.terminal.padstack_instance_terminal import (
@@ -59,6 +65,24 @@ def cell(self):
5965
"""
6066
return self._pedb._active_cell
6167

68+
@property
69+
def primitives(self):
70+
prims = []
71+
for prim in super().primitives:
72+
if isinstance(prim, ansys.edb.core.primitive.primitive.Path):
73+
prims.append(Path(self._pedb, prim))
74+
elif isinstance(prim, ansys.edb.core.primitive.primitive.Polygon):
75+
prims.append(Polygon(self._pedb, prim))
76+
elif isinstance(prim, ansys.edb.core.primitive.primitive.PadstackInstance):
77+
prims.append(PadstackInstance(self._pedb, prim))
78+
elif isinstance(prim, ansys.edb.core.primitive.primitive.Rectangle):
79+
prims.append(Rectangle(self._pedb, prim))
80+
elif isinstance(prim, ansys.edb.core.primitive.primitive.Circle):
81+
prims.append(Circle(self._pedb, prim))
82+
elif isinstance(prim, ansys.edb.core.primitive.primitive.Bondwire):
83+
prims.append(Bondwire(self._pedb, prim))
84+
return prims
85+
6286
@property
6387
def terminals(self):
6488
"""Get terminals belonging to active layout.
@@ -180,17 +204,32 @@ def voltage_regulators(self):
180204
"""
181205
return [VoltageRegulator(self._pedb, i) for i in self._pedb.active_cell.layout.voltage_regulators]
182206

183-
def find_primitive(self, layer_name: Union[str, list]) -> list:
207+
def find_primitive(
208+
self, layer_name: Union[str, list] = None, name: Union[str, list] = None, net_name: Union[str, list] = None
209+
) -> list:
184210
"""Find a primitive objects by layer name.
185-
186211
Parameters
187212
----------
188213
layer_name : str, list
214+
layer_name : str, list, optional
189215
Name of the layer.
216+
name : str, list, optional
217+
Name of the primitive
218+
net_name : str, list, optional
219+
Name of the primitive
190220
Returns
191221
-------
192222
List[:class:`Primitive <pyedb.grpc.database.primitive.primitive.Primitive`].
193223
List of Primitive.
194224
"""
195-
layer_name = layer_name if isinstance(layer_name, list) else [layer_name]
196-
return [i for i in self.primitives if i.layer.name in layer_name]
225+
if layer_name:
226+
layer_name = layer_name if isinstance(layer_name, list) else [layer_name]
227+
if name:
228+
name = name if isinstance(name, list) else [name]
229+
if net_name:
230+
net_name = net_name if isinstance(net_name, list) else [net_name]
231+
prims = self.primitives
232+
prims = [i for i in prims if i.aedt_name in name] if name is not None else prims
233+
prims = [i for i in prims if i.layer_name in layer_name] if layer_name is not None else prims
234+
prims = [i for i in prims if i.net_name in net_name] if net_name is not None else prims
235+
return prims

src/pyedb/grpc/database/padstacks.py

Lines changed: 134 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -824,13 +824,12 @@ def create(
824824
elif polygon_hole:
825825
if isinstance(polygon_hole, list):
826826
polygon_hole = GrpcPolygonData(points=polygon_hole)
827-
828827
padstack_data.set_hole_parameters(
829828
offset_x=value0,
830829
offset_y=value0,
831830
rotation=value0,
832831
type_geom=GrpcPadGeometryType.PADGEOMTYPE_POLYGON,
833-
sizes=polygon_hole,
832+
fp=polygon_hole,
834833
)
835834
padstack_data.plating_percentage = GrpcValue(20.0)
836835
else:
@@ -1332,6 +1331,36 @@ def get_padstack_instances_rtree_index(self, nets=None):
13321331
padstack_instances_index.insert(inst.id, inst.position)
13331332
return padstack_instances_index
13341333

1334+
def get_padstack_instances_id_intersecting_polygon(self, points, nets=None, padstack_instances_index=None):
1335+
"""Returns the list of padstack instances ID intersecting a given bounding box and nets.
1336+
1337+
Parameters
1338+
----------
1339+
bounding_box : tuple or list.
1340+
bounding box, [x1, y1, x2, y2]
1341+
nets : str or list, optional
1342+
net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1343+
all instances are included in the index. Default value is ``None``.
1344+
padstack_instances_index : optional, Rtree object.
1345+
Can be provided optionally to prevent computing padstack instances Rtree index again.
1346+
1347+
Returns
1348+
-------
1349+
List of padstack instances ID intersecting the bounding box.
1350+
"""
1351+
if not points:
1352+
raise Exception("No points defining polygon was provided")
1353+
if not padstack_instances_index:
1354+
padstack_instances_index = {}
1355+
for inst in self.instances:
1356+
padstack_instances_index[inst.edb_uid] = inst.position
1357+
_x = [pt[0] for pt in points]
1358+
_y = [pt[1] for pt in points]
1359+
points = [_x, _y]
1360+
return [
1361+
ind for ind, pt in padstack_instances_index.items() if GeometryOperators.is_point_in_polygon(pt, points)
1362+
]
1363+
13351364
def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None):
13361365
"""Returns the list of padstack instances ID intersecting a given bounding box and nets.
13371366
@@ -1357,18 +1386,23 @@ def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=No
13571386
return list(index.intersection(bounding_box))
13581387

13591388
def merge_via_along_lines(
1360-
self, net_name="GND", distance_threshold=5e-3, minimum_via_number=6, selected_angles=None
1389+
self,
1390+
net_name="GND",
1391+
distance_threshold=5e-3,
1392+
minimum_via_number=6,
1393+
selected_angles=None,
1394+
padstack_instances_id=None,
13611395
):
13621396
"""Replace padstack instances along lines into a single polygon.
13631397
1364-
Detect all padstack instances that are placed along lines and replace them by a single polygon based one
1398+
Detect all pad-stack instances that are placed along lines and replace them by a single polygon based one
13651399
forming a wall shape. This method is designed to simplify meshing on via fence usually added to shield RF traces
13661400
on PCB.
13671401
13681402
Parameters
13691403
----------
13701404
net_name : str
1371-
Net name used for detected padstack instances. Default value is ``"GND"``.
1405+
Net name used for detected pad-stack instances. Default value is ``"GND"``.
13721406
13731407
distance_threshold : float, None, optional
13741408
If two points in a line are separated by a distance larger than `distance_threshold`,
@@ -1382,22 +1416,29 @@ def merge_via_along_lines(
13821416
Other values can be assigned like 45 degrees. When `None` is provided all lines are detected. Default value
13831417
is `None`.
13841418
1419+
padstack_instances_id : List[int]
1420+
List of pad-stack instances ID's to include. If `None`, the algorithm will scan all pad-stack
1421+
instances belonging to the specified net. Default value is `None`.
1422+
13851423
Returns
13861424
-------
1387-
bool
1388-
``True`` when succeeded ``False`` when failed. <
1425+
List[int], list of created pad-stack instances id.
13891426
13901427
"""
13911428
_def = list(set([inst.padstack_def for inst in list(self.instances.values()) if inst.net_name == net_name]))
13921429
if not _def:
13931430
self._logger.error(f"No padstack definition found for net {net_name}")
13941431
return False
1432+
instances_created = []
13951433
_instances_to_delete = []
13961434
padstack_instances = []
1397-
for pdstk_def in _def:
1398-
padstack_instances.append(
1399-
[inst for inst in self.definitions[pdstk_def.name].instances if inst.net_name == net_name]
1400-
)
1435+
if padstack_instances_id:
1436+
padstack_instances = [[self.instances[id] for id in padstack_instances_id]]
1437+
else:
1438+
for pdstk_def in _def:
1439+
padstack_instances.append(
1440+
[inst for inst in self.definitions[pdstk_def.name].instances if inst.net_name == net_name]
1441+
)
14011442
for pdstk_series in padstack_instances:
14021443
instances_location = [inst.position for inst in pdstk_series]
14031444
lines, line_indexes = GeometryOperators.find_points_along_lines(
@@ -1431,70 +1472,112 @@ def merge_via_along_lines(
14311472
polygon_hole=polygon_data,
14321473
):
14331474
self._logger.error(f"Failed to create padstack definition {new_padstack_def.name}")
1434-
if not self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net_name):
1435-
self._logger.error(f"Failed to place padstack instance {new_padstack_def.name}")
1475+
new_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net_name)
1476+
if not new_instance:
1477+
self._logger.error(f"Failed to place padstack instance {new_padstack_def}")
1478+
else:
1479+
instances_created.append(new_instance.id)
14361480
for inst in _instances_to_delete:
14371481
inst.delete()
1438-
return True
1482+
return instances_created
14391483

14401484
def merge_via(self, contour_boxes, net_filter=None, start_layer=None, stop_layer=None):
1441-
"""Evaluate padstack instances included on the provided point list and replace all by single instance.
1485+
"""Evaluate pad-stack instances included on the provided point list and replace all by single instance.
14421486
14431487
Parameters
14441488
----------
14451489
contour_boxes : List[List[List[float, float]]]
14461490
Nested list of polygon with points [x,y].
14471491
net_filter : optional
1448-
List[str: net_name] apply a net filter,
1449-
nets included in the filter are excluded from the via merge.
1492+
List[str: net_name] apply a net filter, nets included in the filter are excluded from the via merge.
14501493
start_layer : optional, str
1451-
Padstack instance start layer, if `None` the top layer is selected.
1494+
Pad-stack instance start layer, if `None` the top layer is selected.
14521495
stop_layer : optional, str
1453-
Padstack instance stop layer, if `None` the bottom layer is selected.
1496+
Pad-stack instance stop layer, if `None` the bottom layer is selected.
14541497
14551498
Return
14561499
------
1457-
List[str], list of created padstack instances ID.
1500+
List[str], list of created pad-stack instances ID.
14581501
14591502
"""
1503+
1504+
import numpy as np
1505+
from scipy.spatial import ConvexHull
1506+
14601507
merged_via_ids = []
14611508
if not contour_boxes:
1462-
self._pedb.logger.error("No contour box provided, you need to pass a nested list as argument.")
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
1509+
raise Exception("No contour box provided, you need to pass a nested list as argument.")
14681510
instances_index = {}
14691511
for id, inst in self.instances.items():
14701512
instances_index[id] = inst.position
14711513
for contour_box in contour_boxes:
1514+
all_instances = self.instances
14721515
instances = self.get_padstack_instances_id_intersecting_polygon(
14731516
points=contour_box, padstack_instances_index=instances_index
14741517
)
1475-
if net_filter:
1476-
instances = [self.instances[id] for id in instances if not self.instances[id].net.name in net_filter]
1477-
net = self.instances[instances[0]].net.name
1478-
instances_pts = np.array([self.instances[id].position for id in instances])
1479-
convex_hull_contour = ConvexHull(instances_pts)
1480-
contour_points = list(instances_pts[convex_hull_contour.vertices])
1481-
layer = list(self._pedb.stackup.layers.values())[0].name
1482-
polygon = self._pedb.modeler.create_polygon(main_shape=contour_points, layer_name=layer)
1483-
polygon_data = polygon.polygon_data
1484-
polygon.delete()
1485-
new_padstack_def = generate_unique_name("test")
1486-
if not self.create(
1487-
padstackname=new_padstack_def,
1488-
pad_shape="Polygon",
1489-
antipad_shape="Polygon",
1490-
pad_polygon=polygon_data,
1491-
antipad_polygon=polygon_data,
1492-
polygon_hole=polygon_data,
1493-
start_layer=start_layer,
1494-
stop_layer=stop_layer,
1495-
):
1496-
self._logger.error(f"Failed to create padstack definition {new_padstack_def}")
1497-
merged_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net)
1498-
merged_via_ids.append(merged_instance.id)
1499-
[self.instances[id].delete() for id in instances]
1518+
if not instances:
1519+
raise Exception(f"No padstack instances found inside {contour_box}")
1520+
else:
1521+
if net_filter:
1522+
instances = [id for id in instances if not self.instances[id].net_name in net_filter]
1523+
if start_layer:
1524+
if start_layer not in self._pedb.stackup.layers.keys():
1525+
raise Exception(f"{start_layer} not exist")
1526+
else:
1527+
instances = [id for id in instances if all_instances[id].start_layer == start_layer]
1528+
if stop_layer:
1529+
if stop_layer not in self._pedb.stackup.layers.keys():
1530+
raise Exception(f"{stop_layer} not exist")
1531+
else:
1532+
instances = [id for id in instances if all_instances[id].stop_layer == stop_layer]
1533+
if not instances:
1534+
raise Exception(
1535+
f"No padstack instances found inside {contour_box} between {start_layer} and {stop_layer}"
1536+
)
1537+
1538+
if not start_layer:
1539+
start_layer = list(self._pedb.stackup.layers.values())[0].name
1540+
if not stop_layer:
1541+
stop_layer = list(self._pedb.stackup.layers.values())[-1].name
1542+
1543+
net = self.instances[instances[0]].net_name
1544+
x_values = []
1545+
y_values = []
1546+
for inst in instances:
1547+
pos = instances_index[inst]
1548+
x_values.append(pos[0])
1549+
y_values.append(pos[1])
1550+
x_values = list(set(x_values))
1551+
y_values = list(set(y_values))
1552+
if len(x_values) == 1 or len(y_values) == 1:
1553+
create_instances = self.merge_via_along_lines(
1554+
net_name=net, padstack_instances_id=instances, minimum_via_number=2
1555+
)
1556+
merged_via_ids.extend(create_instances)
1557+
else:
1558+
instances_pts = np.array([instances_index[id] for id in instances])
1559+
convex_hull_contour = ConvexHull(instances_pts)
1560+
contour_points = list(instances_pts[convex_hull_contour.vertices])
1561+
layer = list(self._pedb.stackup.layers.values())[0].name
1562+
polygon = self._pedb.modeler.create_polygon(points=contour_points, layer_name=layer)
1563+
polygon_data = polygon.polygon_data
1564+
polygon.delete()
1565+
new_padstack_def = generate_unique_name(self.instances[instances[0]].definition.name)
1566+
if not self.create(
1567+
padstackname=new_padstack_def,
1568+
pad_shape="Polygon",
1569+
antipad_shape="Polygon",
1570+
pad_polygon=polygon_data,
1571+
antipad_polygon=polygon_data,
1572+
polygon_hole=polygon_data,
1573+
start_layer=start_layer,
1574+
stop_layer=stop_layer,
1575+
):
1576+
raise Exception(f"Failed to create padstack definition {new_padstack_def}")
1577+
merged_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net)
1578+
merged_instance.start_layer = start_layer
1579+
merged_instance.stop_layer = stop_layer
1580+
1581+
merged_via_ids.append(merged_instance.edb_uid)
1582+
_ = [all_instances[id].delete() for id in instances]
15001583
return merged_via_ids

src/pyedb/grpc/database/primitive/padstack_instance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def layer_range_names(self):
399399
layer_list = []
400400
start_layer_name = start_layer.name
401401
stop_layer_name = stop_layer.name
402-
for layer_name in list(self._pedb.stackup.layers.keys()):
402+
for layer_name in list(self._pedb.stackup.signal_layers.keys()):
403403
if started:
404404
layer_list.append(layer_name)
405405
if layer_name == stop_layer_name or layer_name == start_layer_name:

tests/grpc/system/test_edb_layout.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ def init(self, local_scratch):
3232
pass
3333

3434
def test_find(self, edb_examples):
35-
# Done
3635
edbapp = edb_examples.get_si_verse()
37-
assert edbapp.layout.find_primitive(layer_name="Inner5(PWR2)")
36+
assert edbapp.layout.find_primitive(layer_name="Inner5(PWR2)", name="poly_4128", net_name=["2V5"])
37+
edbapp.close()
38+
39+
def test_primitives(self, edb_examples):
40+
edbapp = edb_examples.get_si_verse()
41+
prim = edbapp.layout.find_primitive(layer_name="Inner5(PWR2)", name="poly_4128", net_name=["2V5"])[0]
42+
assert prim.polygon_data.is_inside(["111.4mm", 44.7e-3])
3843
edbapp.close()

tests/grpc/system/test_edb_padstacks.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,19 @@ def test_via_merge(self, edb_examples):
512512
# assert len(result) == 1
513513
# edbapp.close()
514514
pass
515+
516+
def test_via_merge3(self):
517+
source_path = os.path.join(local_path, "example_models", "TEDB", "merge_via_4layers.aedb")
518+
edbapp = Edb(edbpath=source_path, edbversion=desktop_version)
519+
520+
merged_via = edbapp.padstacks.merge_via(
521+
contour_boxes=[[[11e-3, -5e-3], [17e-3, -5e-3], [17e-3, 1e-3], [11e-3, 1e-3], [11e-3, -5e-3]]],
522+
net_filter=["NET_3"],
523+
start_layer="layer1",
524+
stop_layer="layer2",
525+
)
526+
527+
assert edbapp.padstacks.instances[merged_via[0]].net_name == "NET_1"
528+
assert edbapp.padstacks.instances[merged_via[0]].start_layer == "layer1"
529+
assert edbapp.padstacks.instances[merged_via[0]].stop_layer == "layer2"
530+
edbapp.close()

0 commit comments

Comments
 (0)