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.
- pyedb/__init__.py +1 -1
- pyedb/configuration/cfg_components.py +2 -2
- pyedb/configuration/cfg_modeler.py +1 -0
- pyedb/configuration/cfg_padstacks.py +9 -9
- pyedb/configuration/cfg_pin_groups.py +7 -8
- pyedb/configuration/cfg_ports_sources.py +39 -4
- pyedb/configuration/cfg_stackup.py +1 -0
- pyedb/configuration/configuration.py +9 -5
- pyedb/dotnet/edb.py +12 -4
- pyedb/dotnet/edb_core/cell/hierarchy/pin_pair_model.py +3 -3
- pyedb/dotnet/edb_core/cell/primitive/primitive.py +64 -0
- pyedb/dotnet/edb_core/cell/terminal/padstack_instance_terminal.py +12 -0
- pyedb/dotnet/edb_core/cell/terminal/point_terminal.py +12 -0
- pyedb/dotnet/edb_core/cell/terminal/terminal.py +6 -15
- pyedb/dotnet/edb_core/components.py +21 -45
- pyedb/dotnet/edb_core/edb_data/control_file.py +60 -13
- pyedb/dotnet/edb_core/edb_data/layer_data.py +22 -2
- pyedb/dotnet/edb_core/edb_data/padstacks_data.py +22 -64
- pyedb/dotnet/edb_core/padstack.py +217 -10
- pyedb/dotnet/edb_core/siwave.py +1 -2
- pyedb/generic/process.py +10 -10
- {pyedb-0.34.3.dist-info → pyedb-0.36.0.dist-info}/METADATA +6 -6
- {pyedb-0.34.3.dist-info → pyedb-0.36.0.dist-info}/RECORD +25 -25
- {pyedb-0.34.3.dist-info → pyedb-0.36.0.dist-info}/LICENSE +0 -0
- {pyedb-0.34.3.dist-info → pyedb-0.36.0.dist-info}/WHEEL +0 -0
|
@@ -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 =
|
|
973
|
-
self.maxpasses =
|
|
974
|
-
self.max_delta =
|
|
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,
|
|
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
|
|
1092
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1621
|
-
padstack_instances.
|
|
1622
|
-
|
|
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
|
-
|
|
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
|
|
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
|
pyedb/dotnet/edb_core/siwave.py
CHANGED
|
@@ -1242,8 +1242,7 @@ class EdbSiwave(object):
|
|
|
1242
1242
|
)
|
|
1243
1243
|
|
|
1244
1244
|
if edb_pingroup.IsNull(): # pragma: no cover
|
|
1245
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
155
|
-
command.append(scriptname)
|
|
153
|
+
command += ["-RunScriptAndExit", scriptname]
|
|
156
154
|
print(command)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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(
|