pyedb 0.34.3__py3-none-any.whl → 0.36.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.

@@ -25,6 +25,7 @@ import os
25
25
  import re
26
26
  import subprocess
27
27
  import sys
28
+ import xml
28
29
 
29
30
  from pyedb.edb_logger import pyedb_logger
30
31
  from pyedb.generic.general_methods import ET, env_path, env_value, is_linux
@@ -964,14 +965,14 @@ class ControlFileMeshOp:
964
965
  class ControlFileSetup:
965
966
  """Setup Class."""
966
967
 
967
- def __init__(self, name):
968
+ def __init__(self, name, adapt_freq="1GHz", maxdelta=0.02, maxpasses=10):
968
969
  self.name = name
969
970
  self.enabled = True
970
971
  self.save_fields = False
971
972
  self.save_rad_fields = False
972
- self.frequency = "1GHz"
973
- self.maxpasses = 10
974
- self.max_delta = 0.02
973
+ self.frequency = adapt_freq
974
+ self.maxpasses = maxpasses
975
+ self.max_delta = maxdelta
975
976
  self.union_polygons = True
976
977
  self.small_voids_area = 0
977
978
  self.mode_type = "IC"
@@ -1082,22 +1083,25 @@ class ControlFileSetups:
1082
1083
  def __init__(self):
1083
1084
  self.setups = []
1084
1085
 
1085
- def add_setup(self, name, frequency):
1086
+ def add_setup(self, name, adapt_freq, maxdelta, maxpasses):
1086
1087
  """Add a new setup
1087
1088
 
1088
1089
  Parameters
1089
1090
  ----------
1090
1091
  name : str
1091
- Setup name.
1092
- frequency : str
1092
+ Setup Name.
1093
+ adapt_freq : str, optional
1093
1094
  Setup Frequency.
1095
+ maxdelta : float, optional
1096
+ Maximum Delta.
1097
+ maxpasses : int, optional
1098
+ Maximum Number of Passes.
1094
1099
 
1095
1100
  Returns
1096
1101
  -------
1097
1102
  :class:`pyedb.dotnet.edb_core.edb_data.control_file.ControlFileSetup`
1098
1103
  """
1099
- setup = ControlFileSetup(name)
1100
- setup.frequency = frequency
1104
+ setup = ControlFileSetup(name, adapt_freq, maxdelta, maxpasses)
1101
1105
  self.setups.append(setup)
1102
1106
  return setup
1103
1107
 
@@ -1112,17 +1116,17 @@ class ControlFile:
1112
1116
 
1113
1117
  def __init__(self, xml_input=None, tecnhology=None, layer_map=None):
1114
1118
  self.stackup = ControlFileStackup()
1119
+ self.boundaries = ControlFileBoundaries()
1120
+ self.setups = ControlFileSetups()
1115
1121
  if xml_input:
1116
1122
  self.parse_xml(xml_input)
1117
1123
  if tecnhology:
1118
1124
  self.parse_technology(tecnhology)
1119
1125
  if layer_map:
1120
1126
  self.parse_layer_map(layer_map)
1121
- self.boundaries = ControlFileBoundaries()
1122
1127
  self.remove_holes = False
1123
1128
  self.remove_holes_area_minimum = 30
1124
1129
  self.remove_holes_units = "um"
1125
- self.setups = ControlFileSetups()
1126
1130
  self.components = ControlFileComponents()
1127
1131
  self.import_options = ControlFileImportOptions()
1128
1132
  pass
@@ -1262,6 +1266,50 @@ class ControlFile:
1262
1266
  via.remove_unconnected = (
1263
1267
  True if i.attrib["RemoveUnconnected"] == "true" else False
1264
1268
  )
1269
+ if el.tag == "Boundaries":
1270
+ for port_el in el:
1271
+ if port_el.tag == "CircuitPortPt":
1272
+ self.boundaries.add_port(
1273
+ name=port_el.attrib["Name"],
1274
+ x1=port_el.attrib["x1"],
1275
+ y1=port_el.attrib["y1"],
1276
+ layer1=port_el.attrib["Layer1"],
1277
+ x2=port_el.attrib["x2"],
1278
+ y2=port_el.attrib["y2"],
1279
+ layer2=port_el.attrib["Layer2"],
1280
+ z0=port_el.attrib["Z0"],
1281
+ )
1282
+ setups = root.find("SimulationSetups")
1283
+ if setups:
1284
+ hfsssetup = setups.find("HFSSSetup")
1285
+ if hfsssetup:
1286
+ if "Name" in hfsssetup.attrib:
1287
+ name = hfsssetup.attrib["Name"]
1288
+ hfsssimset = hfsssetup.find("HFSSSimulationSettings")
1289
+ if hfsssimset:
1290
+ hfssadaptset = hfsssimset.find("HFSSAdaptiveSettings")
1291
+ if hfssadaptset:
1292
+ adaptset = hfssadaptset.find("AdaptiveSettings")
1293
+ if adaptset:
1294
+ singlefreqdatalist = adaptset.find("SingleFrequencyDataList")
1295
+ if singlefreqdatalist:
1296
+ adaptfreqdata = singlefreqdatalist.find("AdaptiveFrequencyData")
1297
+ if adaptfreqdata:
1298
+ if isinstance(
1299
+ adaptfreqdata.find("AdaptiveFrequency"), xml.etree.ElementTree.Element
1300
+ ):
1301
+ adapt_freq = adaptfreqdata.find("AdaptiveFrequency").text
1302
+ else:
1303
+ adapt_freq = "1GHz"
1304
+ if isinstance(adaptfreqdata.find("MaxDelta"), xml.etree.ElementTree.Element):
1305
+ maxdelta = adaptfreqdata.find("MaxDelta").text
1306
+ else:
1307
+ maxdelta = 0.02
1308
+ if isinstance(adaptfreqdata.find("MaxPasses"), xml.etree.ElementTree.Element):
1309
+ maxpasses = adaptfreqdata.find("MaxPasses").text
1310
+ else:
1311
+ maxpasses = 10
1312
+ self.setups.add_setup(name, adapt_freq, maxdelta, maxpasses)
1265
1313
  return True
1266
1314
 
1267
1315
  def write_xml(self, xml_output):
@@ -1278,8 +1326,7 @@ class ControlFile:
1278
1326
  """
1279
1327
  control = ET.Element("{http://www.ansys.com/control}Control", attrib={"schemaVersion": "1.0"})
1280
1328
  self.stackup._write_xml(control)
1281
- if self.boundaries.ports or self.boundaries.extents:
1282
- self.boundaries._write_xml(control)
1329
+ self.boundaries._write_xml(control)
1283
1330
  if self.remove_holes:
1284
1331
  hole = ET.SubElement(control, "RemoveHoles")
1285
1332
  hole.set("HoleAreaMinimum", str(self.remove_holes_area_minimum))
@@ -60,6 +60,8 @@ class LayerEdbClass(object):
60
60
  self.__setattr__(k, v)
61
61
  elif k == "roughness":
62
62
  self.properties = {"roughness": v}
63
+ elif k == "etching":
64
+ self.properties = {"etching": v}
63
65
  else:
64
66
  self._pedb.logger.error(f"{k} is not a valid layer attribute")
65
67
 
@@ -792,6 +794,15 @@ class StackupLayerEdbClass(LayerEdbClass):
792
794
  data["thickness"] = self._edb_object.GetThicknessValue().ToString()
793
795
  data["color"] = self.color
794
796
 
797
+ data["etching"] = {
798
+ "factor": self._edb_layer.GetEtchFactor().ToString(),
799
+ "enabled": self._edb_layer.IsEtchFactorEnabled(),
800
+ }
801
+ if self._pedb.edbversion >= "2024.2":
802
+ etch_power_ground_nets = int(self._edb_layer.GetEtchNetClass())
803
+ etch_power_ground_nets = False if etch_power_ground_nets else True
804
+ data["etching"]["etch_power_ground_nets"] = etch_power_ground_nets
805
+
795
806
  roughness = {}
796
807
  for region in ["top", "bottom", "side"]:
797
808
  temp = {}
@@ -851,5 +862,14 @@ class StackupLayerEdbClass(LayerEdbClass):
851
862
  getattr(self._pedb._edb.Cell.RoughnessModel.Region, region.capitalize()), r_model
852
863
  )
853
864
  self._pedb.stackup._set_layout_stackup(layer_clone, "change_attribute")
854
-
855
- layer_clone.SetRoughnessEnabled(True)
865
+ etching = params.get("etching")
866
+ if etching:
867
+ layer_clone = self._edb_layer
868
+ layer_clone.SetEtchFactorEnabled(etching["enabled"])
869
+ layer_clone.SetEtchFactor(self._pedb.stackup._edb_value(float(etching["factor"])))
870
+ if self._pedb.edbversion >= "2024.2":
871
+ if etching["etch_power_ground_nets"]:
872
+ layer_clone.SetEtchNetClass(self._pedb._edb.Cell.EtchNetClass.NoEtchPowerGroundNets)
873
+ else:
874
+ layer_clone.SetEtchNetClass(self._pedb._edb.Cell.EtchNetClass.EtchAllNets)
875
+ self._pedb.stackup._set_layout_stackup(layer_clone, "change_attribute")
@@ -22,7 +22,6 @@
22
22
 
23
23
  from collections import OrderedDict
24
24
  import math
25
- import re
26
25
  import warnings
27
26
 
28
27
  from pyedb.dotnet.clr_module import String
@@ -1248,68 +1247,30 @@ class EDBPadstackInstance(Primitive):
1248
1247
 
1249
1248
  return self._pedb.create_port(terminal, ref_terminal, is_circuit_port)
1250
1249
 
1251
- @property
1252
- def _em_properties(self):
1253
- """Get EM properties."""
1254
- default = (
1255
- r"$begin 'EM properties'\n"
1256
- r"\tType('Mesh')\n"
1257
- r"\tDataId='EM properties1'\n"
1258
- r"\t$begin 'Properties'\n"
1259
- r"\t\tGeneral=''\n"
1260
- r"\t\tModeled='true'\n"
1261
- r"\t\tUnion='true'\n"
1262
- r"\t\t'Use Precedence'='false'\n"
1263
- r"\t\t'Precedence Value'='1'\n"
1264
- r"\t\tPlanarEM=''\n"
1265
- r"\t\tRefined='true'\n"
1266
- r"\t\tRefineFactor='1'\n"
1267
- r"\t\tNoEdgeMesh='false'\n"
1268
- r"\t\tHFSS=''\n"
1269
- r"\t\t'Solve Inside'='false'\n"
1270
- r"\t\tSIwave=''\n"
1271
- r"\t\t'DCIR Equipotential Region'='false'\n"
1272
- r"\t$end 'Properties'\n"
1273
- r"$end 'EM properties'\n"
1274
- )
1275
-
1276
- pid = self._pedb.edb_api.ProductId.Designer
1277
- _, p = self._edb_padstackinstance.GetProductProperty(pid, 18, "")
1278
- if p:
1279
- return p
1280
- else:
1281
- return default
1282
-
1283
- @_em_properties.setter
1284
- def _em_properties(self, em_prop):
1285
- """Set EM properties"""
1286
- pid = self._pedb.edb_api.ProductId.Designer
1287
- self._edb_padstackinstance.SetProductProperty(pid, 18, em_prop)
1288
-
1289
- @property
1290
- def dcir_equipotential_region(self):
1291
- """Check whether dcir equipotential region is enabled.
1250
+ def _set_equipotential(self):
1251
+ """Workaround solution. Remove when EDBAPI bug is fixed for dcir_equipotential_region."""
1252
+ pad = self.definition.pad_by_layer[self.start_layer]
1253
+ if pad.shape.lower() == "circle":
1254
+ ra = self._pedb.edb_value(pad.parameters_values[0] / 2)
1255
+ pos = self.position
1256
+ prim = self._pedb.modeler.create_circle(pad.layer_name, pos[0], pos[1], ra, self.net_name)
1257
+ elif pad.shape.lower() == "rectangle":
1258
+ width, height = pad.parameters_values
1259
+ prim = self._pedb.modeler.create_rectangle(
1260
+ pad.layer_name,
1261
+ self.net_name,
1262
+ width=width,
1263
+ height=height,
1264
+ representation_type="CenterWidthHeight",
1265
+ center_point=self.position,
1266
+ rotation=self.component.rotation,
1267
+ )
1292
1268
 
1293
- Returns
1294
- -------
1295
- bool
1296
- """
1297
- pattern = r"'DCIR Equipotential Region'='([^']+)'"
1298
- em_pp = self._em_properties
1299
- result = re.search(pattern, em_pp).group(1)
1300
- if result == "true":
1301
- return True
1269
+ elif pad.polygon_data:
1270
+ prim = self._pedb.modeler.create_polygon(pad.polygon_data, self.start_layer, net_name=self.net_name)
1302
1271
  else:
1303
- return False
1304
-
1305
- @dcir_equipotential_region.setter
1306
- def dcir_equipotential_region(self, value):
1307
- """Set dcir equipotential region."""
1308
- pp = r"'DCIR Equipotential Region'='true'" if value else r"'DCIR Equipotential Region'='false'"
1309
- em_pp = self._em_properties
1310
- pattern = r"'DCIR Equipotential Region'='([^']+)'"
1311
- new_em_pp = re.sub(pattern, pp, em_pp)
1312
- self._em_properties = new_em_pp
1272
+ return
1273
+ prim.dcir_equipotential_region = True
1313
1274
 
1314
1275
  @property
1315
1276
  def object_instance(self):
@@ -1577,7 +1538,6 @@ class EDBPadstackInstance(Primitive):
1577
1538
  str
1578
1539
  Name of the starting layer.
1579
1540
  """
1580
- layer = self._pedb.edb_api.cell.layer("", self._pedb.edb_api.cell.layer_type.SignalLayer)
1581
1541
  _, start_layer, stop_layer = self._edb_object.GetLayerRange()
1582
1542
 
1583
1543
  if start_layer:
@@ -1599,7 +1559,6 @@ class EDBPadstackInstance(Primitive):
1599
1559
  str
1600
1560
  Name of the stopping layer.
1601
1561
  """
1602
- layer = self._pedb.edb_api.cell.layer("", self._pedb.edb_api.cell.layer_type.SignalLayer)
1603
1562
  _, start_layer, stop_layer = self._edb_padstackinstance.GetLayerRange()
1604
1563
 
1605
1564
  if stop_layer:
@@ -1705,7 +1664,6 @@ class EDBPadstackInstance(Primitive):
1705
1664
  float
1706
1665
  Rotatation value for the padstack instance.
1707
1666
  """
1708
- point_data = self._pedb.edb_api.geometry.point_data(self._pedb.edb_value(0.0), self._pedb.edb_value(0.0))
1709
1667
  out = self._edb_padstackinstance.GetPositionAndRotationValue()
1710
1668
 
1711
1669
  if out[0]:
@@ -26,7 +26,9 @@ This module contains the `EdbPadstacks` class.
26
26
  import math
27
27
  import warnings
28
28
 
29
+ import numpy as np
29
30
  import rtree
31
+ from scipy.spatial import ConvexHull
30
32
 
31
33
  from pyedb.dotnet.clr_module import Array
32
34
  from pyedb.dotnet.edb_core.edb_data.padstacks_data import (
@@ -1553,7 +1555,7 @@ class EdbPadstacks(object):
1553
1555
  padstack_instances_index.insert(inst.id, inst.position)
1554
1556
  return padstack_instances_index
1555
1557
 
1556
- def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None):
1558
+ def get_padstack_instances_id_intersecting_polygon(self, points, nets=None, padstack_instances_index=None):
1557
1559
  """Returns the list of padstack instances ID intersecting a given bounding box and nets.
1558
1560
 
1559
1561
  Parameters
@@ -1563,6 +1565,38 @@ class EdbPadstacks(object):
1563
1565
  nets : str or list, optional
1564
1566
  net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1565
1567
  all instances are included in the index. Default value is ``None``.
1568
+ padstack_instances_index : optional, Rtree object.
1569
+ Can be provided optionally to prevent computing padstack instances Rtree index again.
1570
+
1571
+ Returns
1572
+ -------
1573
+ List of padstack instances ID intersecting the bounding box.
1574
+ """
1575
+ if not points:
1576
+ raise Exception("No points defining polygon was provided")
1577
+ if not padstack_instances_index:
1578
+ padstack_instances_index = {}
1579
+ for inst in self.instances:
1580
+ padstack_instances_index[inst.id] = inst.position
1581
+ _x = [pt[0] for pt in points]
1582
+ _y = [pt[1] for pt in points]
1583
+ points = [_x, _y]
1584
+ return [
1585
+ ind for ind, pt in padstack_instances_index.items() if GeometryOperators.is_point_in_polygon(pt, points)
1586
+ ]
1587
+
1588
+ def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None, padstack_instances_index=None):
1589
+ """Returns the list of padstack instances ID intersecting a given bounding box and nets.
1590
+
1591
+ Parameters
1592
+ ----------
1593
+ bounding_box : tuple or list.
1594
+ bounding box, [x1, y1, x2, y2]
1595
+ nets : str or list, optional
1596
+ net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1597
+ all instances are included in the index. Default value is ``None``.
1598
+ padstack_instances_index : optional, Rtree object.
1599
+ Can be provided optionally to prevent computing padstack instances Rtree index again.
1566
1600
 
1567
1601
  Returns
1568
1602
  -------
@@ -1570,15 +1604,123 @@ class EdbPadstacks(object):
1570
1604
  """
1571
1605
  if not bounding_box:
1572
1606
  raise Exception("No bounding box was provided")
1573
- index = self.get_padstack_instances_rtree_index(nets=nets)
1607
+ if not padstack_instances_index:
1608
+ index = self.get_padstack_instances_rtree_index(nets=nets)
1609
+ else:
1610
+ index = padstack_instances_index
1574
1611
  if not len(bounding_box) == 4:
1575
1612
  raise Exception("The bounding box length must be equal to 4")
1576
1613
  if isinstance(bounding_box, list):
1577
1614
  bounding_box = tuple(bounding_box)
1578
1615
  return list(index.intersection(bounding_box))
1579
1616
 
1617
+ def merge_via(self, contour_boxes, net_filter=None, start_layer=None, stop_layer=None):
1618
+ """Evaluate padstack instances included on the provided point list and replace all by single instance.
1619
+
1620
+ Parameters
1621
+ ----------
1622
+ contour_boxes : List[List[List[float, float]]]
1623
+ Nested list of polygon with points [x,y].
1624
+ net_filter : optional
1625
+ List[str: net_name] apply a net filter, nets included in the filter are excluded from the via merge.
1626
+ start_layer : optional, str
1627
+ Padstack instance start layer, if `None` the top layer is selected.
1628
+ stop_layer : optional, str
1629
+ Padstack instance stop layer, if `None` the bottom layer is selected.
1630
+
1631
+ Return
1632
+ ------
1633
+ List[str], list of created padstack instances ID.
1634
+
1635
+ """
1636
+ merged_via_ids = []
1637
+ if not contour_boxes:
1638
+ raise Exception("No contour box provided, you need to pass a nested list as argument.")
1639
+
1640
+ instances_index = {}
1641
+ for id, inst in self.instances.items():
1642
+ instances_index[id] = inst.position
1643
+ for contour_box in contour_boxes:
1644
+ all_instances = self.instances
1645
+ instances = self.get_padstack_instances_id_intersecting_polygon(
1646
+ points=contour_box, padstack_instances_index=instances_index
1647
+ )
1648
+ if not instances:
1649
+ raise Exception(f"No padstack instances found inside {contour_box}")
1650
+ else:
1651
+ if net_filter:
1652
+ # instances = [id for id in instances if not self.instances[id].net_name in net_filter]
1653
+ instances = [id for id in instances if all_instances[id].net_name not in net_filter]
1654
+ # filter instances by start and stop layer
1655
+ if start_layer:
1656
+ if start_layer not in self._pedb.stackup.layers.keys():
1657
+ raise Exception(f"{start_layer} not exist")
1658
+ else:
1659
+ instances = [id for id in instances if all_instances[id].start_layer == start_layer]
1660
+ if stop_layer:
1661
+ if stop_layer not in self._pedb.stackup.layers.keys():
1662
+ raise Exception(f"{stop_layer} not exist")
1663
+ else:
1664
+ instances = [id for id in instances if all_instances[id].stop_layer == stop_layer]
1665
+ if not instances:
1666
+ raise Exception(
1667
+ f"No padstack instances found inside {contour_box} between {start_layer} and {stop_layer}"
1668
+ )
1669
+
1670
+ if not start_layer:
1671
+ start_layer = list(self._pedb.stackup.layers.values())[0].name
1672
+ if not stop_layer:
1673
+ stop_layer = list(self._pedb.stackup.layers.values())[-1].name
1674
+
1675
+ net = self.instances[instances[0]].net_name
1676
+ x_values = []
1677
+ y_values = []
1678
+ for inst in instances:
1679
+ pos = instances_index[inst]
1680
+ x_values.append(pos[0])
1681
+ y_values.append(pos[1])
1682
+ x_values = list(set(x_values))
1683
+ y_values = list(set(y_values))
1684
+ if len(x_values) == 1 or len(y_values) == 1:
1685
+ create_instances = self.merge_via_along_lines(
1686
+ net_name=net, padstack_instances_id=instances, minimum_via_number=2
1687
+ )
1688
+ merged_via_ids.extend(create_instances)
1689
+ else:
1690
+ instances_pts = np.array([instances_index[id] for id in instances])
1691
+ convex_hull_contour = ConvexHull(instances_pts)
1692
+ contour_points = list(instances_pts[convex_hull_contour.vertices])
1693
+ layer = list(self._pedb.stackup.layers.values())[0].name
1694
+ polygon = self._pedb.modeler.create_polygon(main_shape=contour_points, layer_name=layer)
1695
+ polygon_data = polygon.polygon_data
1696
+ polygon.delete()
1697
+ new_padstack_def = generate_unique_name(self.instances[instances[0]].definition.name)
1698
+ if not self.create(
1699
+ padstackname=new_padstack_def,
1700
+ pad_shape="Polygon",
1701
+ antipad_shape="Polygon",
1702
+ pad_polygon=polygon_data,
1703
+ antipad_polygon=polygon_data,
1704
+ polygon_hole=polygon_data,
1705
+ start_layer=start_layer,
1706
+ stop_layer=stop_layer,
1707
+ ):
1708
+ raise Exception(f"Failed to create padstack definition {new_padstack_def}")
1709
+ merged_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net)
1710
+ merged_instance.start_layer = start_layer
1711
+ merged_instance.stop_layer = stop_layer
1712
+
1713
+ merged_via_ids.append(merged_instance.id)
1714
+ _ = [all_instances[id].delete() for id in instances]
1715
+ return merged_via_ids
1716
+
1580
1717
  def merge_via_along_lines(
1581
- self, net_name="GND", distance_threshold=5e-3, minimum_via_number=6, selected_angles=None
1718
+ self,
1719
+ net_name="GND",
1720
+ distance_threshold=5e-3,
1721
+ minimum_via_number=6,
1722
+ selected_angles=None,
1723
+ padstack_instances_id=None,
1582
1724
  ):
1583
1725
  """Replace padstack instances along lines into a single polygon.
1584
1726
 
@@ -1602,11 +1744,15 @@ class EdbPadstacks(object):
1602
1744
  Specify angle in degrees to detected, for instance [0, 180] is only detecting horizontal and vertical lines.
1603
1745
  Other values can be assigned like 45 degrees. When `None` is provided all lines are detected. Default value
1604
1746
  is `None`.
1747
+ padstack_instances_id : List[int]
1748
+ List of padstack instances ID's to include. If `None`, the algorithm will scan all padstack instances belonging
1749
+ to the specified net. Default value is `None`.
1750
+
1605
1751
 
1606
1752
  Returns
1607
1753
  -------
1608
1754
  bool
1609
- ``True`` when succeeded ``False`` when failed. <
1755
+ List[int], list of created padstack instances id.
1610
1756
 
1611
1757
  """
1612
1758
  _def = list(
@@ -1615,12 +1761,16 @@ class EdbPadstacks(object):
1615
1761
  if not _def:
1616
1762
  self._logger.error(f"No padstack definition found for net {net_name}")
1617
1763
  return False
1764
+ instances_created = []
1618
1765
  _instances_to_delete = []
1619
1766
  padstack_instances = []
1620
- for pdstk_def in _def:
1621
- padstack_instances.append(
1622
- [inst for inst in self.definitions[pdstk_def].instances if inst.net_name == net_name]
1623
- )
1767
+ if padstack_instances_id:
1768
+ padstack_instances = [[self.instances[id] for id in padstack_instances_id]]
1769
+ else:
1770
+ for pdstk_def in _def:
1771
+ padstack_instances.append(
1772
+ [inst for inst in self.definitions[pdstk_def].instances if inst.net_name == net_name]
1773
+ )
1624
1774
  for pdstk_series in padstack_instances:
1625
1775
  instances_location = [inst.position for inst in pdstk_series]
1626
1776
  lines, line_indexes = GeometryOperators.find_points_along_lines(
@@ -1652,8 +1802,65 @@ class EdbPadstacks(object):
1652
1802
  polygon_hole=polygon_data,
1653
1803
  ):
1654
1804
  self._logger.error(f"Failed to create padstack definition {new_padstack_def}")
1655
- if not self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net_name):
1805
+ new_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net_name)
1806
+ if not new_instance:
1656
1807
  self._logger.error(f"Failed to place padstack instance {new_padstack_def}")
1808
+ else:
1809
+ instances_created.append(new_instance.id)
1657
1810
  for inst in _instances_to_delete:
1658
1811
  inst.delete()
1659
- return True
1812
+ return instances_created
1813
+
1814
+ def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
1815
+ """
1816
+ reduce the number of vias intersecting bounding box and nets by x and y samples.
1817
+
1818
+ Parameters
1819
+ ----------
1820
+ bounding_box : tuple or list.
1821
+ bounding box, [x1, y1, x2, y2]
1822
+ x_samples : int
1823
+ y_samples : int
1824
+ nets : str or list, optional
1825
+ net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1826
+ all instances are included in the index. Default value is ``None``.
1827
+
1828
+ Returns
1829
+ -------
1830
+ bool
1831
+ ``True`` when succeeded ``False`` when failed. <
1832
+ """
1833
+
1834
+ padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
1835
+ if not padstacks_inbox:
1836
+ self._logger.info("no padstack in bounding box")
1837
+ return False
1838
+ else:
1839
+ if len(padstacks_inbox) <= (x_samples * y_samples):
1840
+ self._logger.info(f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}")
1841
+ return False
1842
+ else:
1843
+ # extract ids and positions
1844
+ vias = {item: self.instances[item].position for item in padstacks_inbox}
1845
+ ids, positions = zip(*vias.items())
1846
+ pt_x, pt_y = zip(*positions)
1847
+
1848
+ # meshgrid
1849
+ _x_min, _x_max = min(pt_x), max(pt_x)
1850
+ _y_min, _y_max = min(pt_y), max(pt_y)
1851
+
1852
+ x_grid, y_grid = np.meshgrid(
1853
+ np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
1854
+ )
1855
+
1856
+ # mapping to meshgrid
1857
+ to_keep = {
1858
+ ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
1859
+ for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
1860
+ }
1861
+
1862
+ for item in padstacks_inbox:
1863
+ if item not in to_keep:
1864
+ self.instances[item].delete()
1865
+
1866
+ return True
@@ -1242,8 +1242,7 @@ class EdbSiwave(object):
1242
1242
  )
1243
1243
 
1244
1244
  if edb_pingroup.IsNull(): # pragma: no cover
1245
- self._logger.error(f"Failed to create pin group {group_name}.")
1246
- return False
1245
+ raise RuntimeError(f"Failed to create pin group {group_name}.")
1247
1246
  else:
1248
1247
  names = [i for i in pins if i.GetNet().GetName()]
1249
1248
  edb_pingroup.SetNet(names[0].GetNet())
pyedb/generic/process.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os.path
2
2
  import subprocess
3
3
 
4
- from pyedb.generic.general_methods import env_path, is_linux
4
+ from pyedb.generic.general_methods import env_path, is_linux, is_windows
5
5
 
6
6
 
7
7
  class SiwaveSolve(object):
@@ -144,19 +144,19 @@ class SiwaveSolve(object):
144
144
  f.write("oDoc.ScrExport3DModel('{}', q3d_filename)\n".format(format_3d))
145
145
  f.write("oDoc.ScrCloseProject()\n")
146
146
  f.write("oApp.Quit()\n")
147
- if is_linux:
148
- _exe = '"' + os.path.join(self.installer_path, "siwave") + '"'
149
- else:
150
- _exe = '"' + os.path.join(self.installer_path, "siwave.exe") + '"'
147
+ _exe = os.path.join(self.installer_path, "siwave")
148
+ if is_windows:
149
+ _exe += ".exe"
151
150
  command = [_exe]
152
151
  if hidden:
153
152
  command.append("-embedding")
154
- command.append("-RunScriptAndExit")
155
- command.append(scriptname)
153
+ command += ["-RunScriptAndExit", scriptname]
156
154
  print(command)
157
- os.system(" ".join(command))
158
- # p1 = subprocess.call(" ".join(command))
159
- # p1.wait()
155
+ try:
156
+ result = subprocess.run(command, check=True, capture_output=True)
157
+ print(result.stdout.decode())
158
+ except subprocess.CalledProcessError as e:
159
+ print(f"Error occurred: {e.stderr.decode()}")
160
160
  return os.path.join(output_folder, aedt_file_name)
161
161
 
162
162
  def export_dc_report(