diff --git a/src/pyedb/grpc/database/padstacks.py b/src/pyedb/grpc/database/padstacks.py index babd1c0e36..25abc385cc 100644 --- a/src/pyedb/grpc/database/padstacks.py +++ b/src/pyedb/grpc/database/padstacks.py @@ -45,6 +45,7 @@ from ansys.edb.core.geometry.point_data import PointData as GrpcPointData from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData from ansys.edb.core.utility.value import Value as GrpcValue +import numpy as np import rtree from pyedb.generic.general_methods import generate_unique_name @@ -1328,7 +1329,7 @@ def get_padstack_instances_rtree_index(self, nets=None): else: instances = list(self.instances.values()) for inst in instances: - padstack_instances_index.insert(inst.id, inst.position) + padstack_instances_index.insert(inst.edb_uid, inst.position) return padstack_instances_index def get_padstack_instances_id_intersecting_polygon(self, points, nets=None, padstack_instances_index=None): @@ -1481,6 +1482,59 @@ def merge_via_along_lines( inst.delete() return instances_created + def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None): + """Reduces the number of vias intersecting bounding box and nets by x and y samples. + + Parameters + ---------- + bounding_box : tuple or list. + bounding box, [x1, y1, x2, y2] + x_samples : int + y_samples : int + nets : str or list, optional + net name or 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``. + + Returns + ------- + bool + ``True`` when succeeded ``False`` when failed. + """ + + padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets) + if not padstacks_inbox: + self._logger.info("no padstack in bounding box") + return False + else: + if len(padstacks_inbox) <= (x_samples * y_samples): + self._logger.info(f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}") + return False + else: + # extract ids and positions + vias = {item: self.instances[item].position for item in padstacks_inbox} + ids, positions = zip(*vias.items()) + pt_x, pt_y = zip(*positions) + + # meshgrid + _x_min, _x_max = min(pt_x), max(pt_x) + _y_min, _y_max = min(pt_y), max(pt_y) + + x_grid, y_grid = np.meshgrid( + np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples) + ) + + # mapping to meshgrid + to_keep = { + ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))] + for _x, _y in zip(x_grid.ravel(), y_grid.ravel()) + } + + for item in padstacks_inbox: + if item not in to_keep: + self.instances[item].delete() + + return True + def merge_via(self, contour_boxes, net_filter=None, start_layer=None, stop_layer=None): """Evaluate pad-stack instances included on the provided point list and replace all by single instance. diff --git a/tests/grpc/system/test_edb_padstacks.py b/tests/grpc/system/test_edb_padstacks.py index 472a529575..0cd0cc65e7 100644 --- a/tests/grpc/system/test_edb_padstacks.py +++ b/tests/grpc/system/test_edb_padstacks.py @@ -513,6 +513,18 @@ def test_via_merge(self, edb_examples): # edbapp.close() pass + def test_reduce_via_in_bounding_box(self): + source_path = os.path.join(local_path, "example_models", test_subfolder, "vias_300.aedb") + edbapp = Edb(edbpath=source_path, edbversion=desktop_version) + assert len(edbapp.padstacks.instances) == 301 + # empty bounding box + assert edbapp.padstacks.reduce_via_in_bounding_box([-16e-3, -7e-3, -13e-3, -6e-3], 10, 10) is False + # over sampling + assert edbapp.padstacks.reduce_via_in_bounding_box([-20e-3, -10e-3, 20e-3, 10e-3], 20, 20) is False + assert edbapp.padstacks.reduce_via_in_bounding_box([-20e-3, -10e-3, 20e-3, 10e-3], 10, 10) is True + assert len(edbapp.padstacks.instances) == 96 + edbapp.close_edb() + def test_via_merge3(self): source_path = os.path.join(local_path, "example_models", "TEDB", "merge_via_4layers.aedb") edbapp = Edb(edbpath=source_path, edbversion=desktop_version)