Skip to content

Commit

Permalink
FEAT: Via clustering based on polygon (#942)
Browse files Browse the repository at this point in the history
* hfsspi SimsetupInfo bug fixed

* temp

* create port in pin exception handling

* via clustering backend

* new feature via clustering
  • Loading branch information
svandenb-dev authored Dec 17, 2024
1 parent 9b5b48f commit 4268e34
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 2 deletions.
102 changes: 100 additions & 2 deletions src/pyedb/dotnet/edb_core/padstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import math
import warnings

import numpy as np
import rtree
from scipy.spatial import ConvexHull

from pyedb.dotnet.clr_module import Array
from pyedb.dotnet.edb_core.edb_data.padstacks_data import (
Expand Down Expand Up @@ -1553,7 +1555,7 @@ def get_padstack_instances_rtree_index(self, nets=None):
padstack_instances_index.insert(inst.id, inst.position)
return padstack_instances_index

def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None):
def get_padstack_instances_id_intersecting_polygon(self, points, nets=None, padstack_instances_index=None):
"""Returns the list of padstack instances ID intersecting a given bounding box and nets.
Parameters
Expand All @@ -1563,20 +1565,116 @@ def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=No
nets : str or list, optional
net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
all instances are included in the index. Default value is ``None``.
padstack_instances_index : optional, Rtree object.
Can be provided optionally to prevent computing padstack instances Rtree index again.
Returns
-------
List of padstack instances ID intersecting the bounding box.
"""
if not points:
raise Exception("No points defining polygon was provided")
if not padstack_instances_index:
padstack_instances_index = {}
for inst in self.instances:
padstack_instances_index[inst.id] = inst.position
_x = [pt[0] for pt in points]
_y = [pt[1] for pt in points]
points = [_x, _y]
return [
ind for ind, pt in padstack_instances_index.items() if GeometryOperators.is_point_in_polygon(pt, points)
]

def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None, padstack_instances_index=None):
"""Returns the list of padstack instances ID intersecting a given bounding box and nets.
Parameters
----------
bounding_box : tuple or list.
bounding box, [x1, y1, x2, y2]
nets : str or list, optional
net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
all instances are included in the index. Default value is ``None``.
padstack_instances_index : optional, Rtree object.
Can be provided optionally to prevent computing padstack instances Rtree index again.
Returns
-------
List of padstack instances ID intersecting the bounding box.
"""
if not bounding_box:
raise Exception("No bounding box was provided")
index = self.get_padstack_instances_rtree_index(nets=nets)
if not padstack_instances_index:
index = self.get_padstack_instances_rtree_index(nets=nets)
else:
index = padstack_instances_index
if not len(bounding_box) == 4:
raise Exception("The bounding box length must be equal to 4")
if isinstance(bounding_box, list):
bounding_box = tuple(bounding_box)
return list(index.intersection(bounding_box))

def merge_via(self, contour_boxes, net_filter=None, start_layer=None, stop_layer=None):
"""Evaluate padstack instances included on the provided point list and replace all by single instance.
Parameters
----------
contour_boxes : List[List[List[float, float]]]
Nested list of polygon with points [x,y].
net_filter : optional
List[str: net_name] apply a net filter, nets included in the filter are excluded from the via merge.
start_layer : optional, str
Padstack instance start layer, if `None` the top layer is selected.
stop_layer : optional, str
Padstack instance stop layer, if `None` the bottom layer is selected.
Return
------
List[str], list of created padstack instances ID.
"""
merged_via_ids = []
if not contour_boxes:
self._pedb.logger.error("No contour box provided, you need to pass a nested list as argument.")
return False
if not start_layer:
start_layer = list(self._pedb.stackup.layers.values())[0].name
if not stop_layer:
stop_layer = list(self._pedb.stackup.layers.values())[-1].name
instances_index = {}
for id, inst in self.instances.items():
instances_index[id] = inst.position
for contour_box in contour_boxes:
instances = self.get_padstack_instances_id_intersecting_polygon(
points=contour_box, padstack_instances_index=instances_index
)
if net_filter:
instances = [self.instances[id] for id in instances if not self.instances[id].net.name in net_filter]
net = self.instances[instances[0]].net.name
instances_pts = np.array([self.instances[id].position for id in instances])
convex_hull_contour = ConvexHull(instances_pts)
contour_points = list(instances_pts[convex_hull_contour.vertices])
layer = list(self._pedb.stackup.layers.values())[0].name
polygon = self._pedb.modeler.create_polygon(main_shape=contour_points, layer_name=layer)
polygon_data = polygon.polygon_data
polygon.delete()
new_padstack_def = generate_unique_name(self.instances[instances[0]].definition.name)
if not self.create(
padstackname=new_padstack_def,
pad_shape="Polygon",
antipad_shape="Polygon",
pad_polygon=polygon_data,
antipad_polygon=polygon_data,
polygon_hole=polygon_data,
start_layer=start_layer,
stop_layer=stop_layer,
):
self._logger.error(f"Failed to create padstack definition {new_padstack_def}")
merged_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net)
merged_via_ids.append(merged_instance.id)
[self.instances[id].delete() for id in instances]
return merged_via_ids

def merge_via_along_lines(
self, net_name="GND", distance_threshold=5e-3, minimum_via_number=6, selected_angles=None
):
Expand Down
7 changes: 7 additions & 0 deletions tests/legacy/system/test_edb_padstacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,10 @@ def test_via_fence(self):
assert "main_via" in edbapp.padstacks.definitions
assert "via_central" in edbapp.padstacks.definitions
edbapp.close()

def test_via_merge(self, edb_examples):
edbapp = edb_examples.get_si_verse()
polygon = [[[118e-3, 60e-3], [125e-3, 60e-3], [124e-3, 56e-3], [118e-3, 56e-3]]]
result = edbapp.padstacks.merge_via(contour_boxes=polygon, start_layer="1_Top", stop_layer="16_Bottom")
assert len(result) == 1
edbapp.close()

0 comments on commit 4268e34

Please sign in to comment.