pyedb 0.39.0__py3-none-any.whl → 0.40.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.

@@ -45,6 +45,7 @@ from ansys.edb.core.definition.padstack_def_data import PadType as GrpcPadType
45
45
  from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
46
46
  from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
47
47
  from ansys.edb.core.utility.value import Value as GrpcValue
48
+ import numpy as np
48
49
  import rtree
49
50
 
50
51
  from pyedb.generic.general_methods import generate_unique_name
@@ -824,13 +825,12 @@ class Padstacks(object):
824
825
  elif polygon_hole:
825
826
  if isinstance(polygon_hole, list):
826
827
  polygon_hole = GrpcPolygonData(points=polygon_hole)
827
-
828
828
  padstack_data.set_hole_parameters(
829
829
  offset_x=value0,
830
830
  offset_y=value0,
831
831
  rotation=value0,
832
832
  type_geom=GrpcPadGeometryType.PADGEOMTYPE_POLYGON,
833
- sizes=polygon_hole,
833
+ fp=polygon_hole,
834
834
  )
835
835
  padstack_data.plating_percentage = GrpcValue(20.0)
836
836
  else:
@@ -1329,27 +1329,61 @@ class Padstacks(object):
1329
1329
  else:
1330
1330
  instances = list(self.instances.values())
1331
1331
  for inst in instances:
1332
- padstack_instances_index.insert(inst.id, inst.position)
1332
+ padstack_instances_index.insert(inst.edb_uid, inst.position)
1333
1333
  return padstack_instances_index
1334
1334
 
1335
- def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None):
1335
+ def get_padstack_instances_id_intersecting_polygon(self, points, nets=None, padstack_instances_index=None):
1336
1336
  """Returns the list of padstack instances ID intersecting a given bounding box and nets.
1337
1337
 
1338
1338
  Parameters
1339
1339
  ----------
1340
- bounding_box : tuple or list.
1340
+ points : tuple or list.
1341
1341
  bounding box, [x1, y1, x2, y2]
1342
1342
  nets : str or list, optional
1343
1343
  net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1344
1344
  all instances are included in the index. Default value is ``None``.
1345
+ padstack_instances_index : optional, Rtree object.
1346
+ Can be provided optionally to prevent computing padstack instances Rtree index again.
1345
1347
 
1346
1348
  Returns
1347
1349
  -------
1350
+ List[int]
1351
+ List of padstack instances ID intersecting the bounding box.
1352
+ """
1353
+ if not points:
1354
+ raise Exception("No points defining polygon was provided")
1355
+ if not padstack_instances_index:
1356
+ padstack_instances_index = {}
1357
+ for inst in self.instances:
1358
+ padstack_instances_index[inst.id] = inst.position
1359
+ _x = [pt[0] for pt in points]
1360
+ _y = [pt[1] for pt in points]
1361
+ points = [_x, _y]
1362
+ return [
1363
+ ind for ind, pt in padstack_instances_index.items() if GeometryOperators.is_point_in_polygon(pt, points)
1364
+ ]
1365
+
1366
+ def get_padstack_instances_intersecting_bounding_box(self, bounding_box, nets=None, padstack_instances_index=None):
1367
+ """Returns the list of padstack instances ID intersecting a given bounding box and nets.
1368
+ Parameters
1369
+ ----------
1370
+ bounding_box : tuple or list.
1371
+ bounding box, [x1, y1, x2, y2]
1372
+ nets : str or list, optional
1373
+ net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1374
+ all instances are included in the index. Default value is ``None``.
1375
+ padstack_instances_index : optional, Rtree object.
1376
+ Can be provided optionally to prevent computing padstack instances Rtree index again.
1377
+ Returns
1378
+ -------
1348
1379
  List of padstack instances ID intersecting the bounding box.
1349
1380
  """
1350
1381
  if not bounding_box:
1351
1382
  raise Exception("No bounding box was provided")
1352
- index = self.get_padstack_instances_rtree_index(nets=nets)
1383
+ if not padstack_instances_index:
1384
+ index = self.get_padstack_instances_rtree_index(nets=nets)
1385
+ else:
1386
+ index = padstack_instances_index
1353
1387
  if not len(bounding_box) == 4:
1354
1388
  raise Exception("The bounding box length must be equal to 4")
1355
1389
  if isinstance(bounding_box, list):
@@ -1357,18 +1391,23 @@ class Padstacks(object):
1357
1391
  return list(index.intersection(bounding_box))
1358
1392
 
1359
1393
  def merge_via_along_lines(
1360
- self, net_name="GND", distance_threshold=5e-3, minimum_via_number=6, selected_angles=None
1394
+ self,
1395
+ net_name="GND",
1396
+ distance_threshold=5e-3,
1397
+ minimum_via_number=6,
1398
+ selected_angles=None,
1399
+ padstack_instances_id=None,
1361
1400
  ):
1362
1401
  """Replace padstack instances along lines into a single polygon.
1363
1402
 
1364
- Detect all padstack instances that are placed along lines and replace them by a single polygon based one
1403
+ Detect all pad-stack instances that are placed along lines and replace them by a single polygon based one
1365
1404
  forming a wall shape. This method is designed to simplify meshing on via fence usually added to shield RF traces
1366
1405
  on PCB.
1367
1406
 
1368
1407
  Parameters
1369
1408
  ----------
1370
1409
  net_name : str
1371
- Net name used for detected padstack instances. Default value is ``"GND"``.
1410
+ Net name used for detected pad-stack instances. Default value is ``"GND"``.
1372
1411
 
1373
1412
  distance_threshold : float, None, optional
1374
1413
  If two points in a line are separated by a distance larger than `distance_threshold`,
@@ -1382,22 +1421,29 @@ class Padstacks(object):
1382
1421
  Other values can be assigned like 45 degrees. When `None` is provided all lines are detected. Default value
1383
1422
  is `None`.
1384
1423
 
1424
+ padstack_instances_id : List[int]
1425
+ List of pad-stack instances ID's to include. If `None`, the algorithm will scan all pad-stack
1426
+ instances belonging to the specified net. Default value is `None`.
1427
+
1385
1428
  Returns
1386
1429
  -------
1387
- bool
1388
- ``True`` when succeeded ``False`` when failed. <
1430
+ List[int], list of created pad-stack instances id.
1389
1431
 
1390
1432
  """
1391
1433
  _def = list(set([inst.padstack_def for inst in list(self.instances.values()) if inst.net_name == net_name]))
1392
1434
  if not _def:
1393
1435
  self._logger.error(f"No padstack definition found for net {net_name}")
1394
1436
  return False
1437
+ instances_created = []
1395
1438
  _instances_to_delete = []
1396
1439
  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
- )
1440
+ if padstack_instances_id:
1441
+ padstack_instances = [[self.instances[id] for id in padstack_instances_id]]
1442
+ else:
1443
+ for pdstk_def in _def:
1444
+ padstack_instances.append(
1445
+ [inst for inst in self.definitions[pdstk_def.name].instances if inst.net_name == net_name]
1446
+ )
1401
1447
  for pdstk_series in padstack_instances:
1402
1448
  instances_location = [inst.position for inst in pdstk_series]
1403
1449
  lines, line_indexes = GeometryOperators.find_points_along_lines(
@@ -1431,44 +1477,99 @@ class Padstacks(object):
1431
1477
  polygon_hole=polygon_data,
1432
1478
  ):
1433
1479
  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}")
1480
+ new_instance = self.place(position=[0, 0], definition_name=new_padstack_def, net_name=net_name)
1481
+ if not new_instance:
1482
+ self._logger.error(f"Failed to place padstack instance {new_padstack_def}")
1483
+ else:
1484
+ instances_created.append(new_instance.id)
1436
1485
  for inst in _instances_to_delete:
1437
1486
  inst.delete()
1438
- return True
1487
+ return instances_created
1488
+
1489
+ def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
1490
+ """Reduces the number of vias intersecting bounding box and nets by x and y samples.
1491
+
1492
+ Parameters
1493
+ ----------
1494
+ bounding_box : tuple or list.
1495
+ bounding box, [x1, y1, x2, y2]
1496
+ x_samples : int
1497
+ y_samples : int
1498
+ nets : str or list, optional
1499
+ net name or list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1500
+ all instances are included in the index. Default value is ``None``.
1501
+
1502
+ Returns
1503
+ -------
1504
+ bool
1505
+ ``True`` when succeeded ``False`` when failed.
1506
+ """
1507
+
1508
+ padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
1509
+ if not padstacks_inbox:
1510
+ self._logger.info("no padstack in bounding box")
1511
+ return False
1512
+ else:
1513
+ if len(padstacks_inbox) <= (x_samples * y_samples):
1514
+ self._logger.info(f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}")
1515
+ return False
1516
+ else:
1517
+ # extract ids and positions
1518
+ vias = {item: self.instances[item].position for item in padstacks_inbox}
1519
+ ids, positions = zip(*vias.items())
1520
+ pt_x, pt_y = zip(*positions)
1521
+
1522
+ # meshgrid
1523
+ _x_min, _x_max = min(pt_x), max(pt_x)
1524
+ _y_min, _y_max = min(pt_y), max(pt_y)
1525
+
1526
+ x_grid, y_grid = np.meshgrid(
1527
+ np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
1528
+ )
1529
+
1530
+ # mapping to meshgrid
1531
+ to_keep = {
1532
+ ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
1533
+ for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
1534
+ }
1535
+
1536
+ for item in padstacks_inbox:
1537
+ if item not in to_keep:
1538
+ self.instances[item].delete()
1539
+
1540
+ return True
1439
1541
 
1440
1542
  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.
1543
+ """Evaluate pad-stack instances included on the provided point list and replace all by single instance.
1442
1544
 
1443
1545
  Parameters
1444
1546
  ----------
1445
1547
  contour_boxes : List[List[List[float, float]]]
1446
1548
  Nested list of polygon with points [x,y].
1447
1549
  net_filter : optional
1448
- List[str: net_name] apply a net filter,
1449
- nets included in the filter are excluded from the via merge.
1550
+ List[str: net_name] apply a net filter, nets included in the filter are excluded from the via merge.
1450
1551
  start_layer : optional, str
1451
- Padstack instance start layer, if `None` the top layer is selected.
1552
+ Pad-stack instance start layer, if `None` the top layer is selected.
1452
1553
  stop_layer : optional, str
1453
- Padstack instance stop layer, if `None` the bottom layer is selected.
1554
+ Pad-stack instance stop layer, if `None` the bottom layer is selected.
1454
1555
 
1455
1556
  Return
1456
1557
  ------
1457
- List[str], list of created padstack instances ID.
1558
+ List[str], list of created pad-stack instances ID.
1458
1559
 
1459
1560
  """
1561
+
1562
+ import numpy as np
1563
+ from scipy.spatial import ConvexHull
1564
+
1460
1565
  merged_via_ids = []
1461
1566
  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
1567
+ raise Exception("No contour box provided, you need to pass a nested list as argument.")
1468
1568
  instances_index = {}
1469
1569
  for id, inst in self.instances.items():
1470
1570
  instances_index[id] = inst.position
1471
1571
  for contour_box in contour_boxes:
1572
+ all_instances = self.instances
1472
1573
  instances = self.get_padstack_instances_id_intersecting_polygon(
1473
1574
  points=contour_box, padstack_instances_index=instances_index
1474
1575
  )
@@ -1479,7 +1580,7 @@ class Padstacks(object):
1479
1580
  convex_hull_contour = ConvexHull(instances_pts)
1480
1581
  contour_points = list(instances_pts[convex_hull_contour.vertices])
1481
1582
  layer = list(self._pedb.stackup.layers.values())[0].name
1482
- polygon = self._pedb.modeler.create_polygon(main_shape=contour_points, layer_name=layer)
1583
+ polygon = self._pedb.modeler.create_polygon(points=contour_points, layer_name=layer)
1483
1584
  polygon_data = polygon.polygon_data
1484
1585
  polygon.delete()
1485
1586
  new_padstack_def = generate_unique_name("test")
@@ -1498,3 +1599,55 @@ class Padstacks(object):
1498
1599
  merged_via_ids.append(merged_instance.id)
1499
1600
  [self.instances[id].delete() for id in instances]
1500
1601
  return merged_via_ids
1602
+
1603
+ def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
1604
+ """
1605
+ reduce the number of vias intersecting bounding box and nets by x and y samples.
1606
+
1607
+ Parameters
1608
+ ----------
1609
+ bounding_box : tuple or list.
1610
+ bounding box, [x1, y1, x2, y2]
1611
+ x_samples : int
1612
+ y_samples : int
1613
+ nets : str or list, optional
1614
+ net name of list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1615
+ all instances are included in the index. Default value is ``None``.
1616
+
1617
+ Returns
1618
+ -------
1619
+ bool
1620
+ ``True`` when succeeded ``False`` when failed.
1621
+ """
1622
+
1623
+ padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
1624
+ if not padstacks_inbox:
1625
+ raise "No pad-stack in bounding box."
1626
+ else:
1627
+ if len(padstacks_inbox) <= (x_samples * y_samples):
1628
+ raise f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}"
1629
+ else:
1630
+ # extract ids and positions
1631
+ vias = {item: self.instances[item].position for item in padstacks_inbox}
1632
+ ids, positions = zip(*vias.items())
1633
+ pt_x, pt_y = zip(*positions)
1634
+
1635
+ # meshgrid
1636
+ _x_min, _x_max = min(pt_x), max(pt_x)
1637
+ _y_min, _y_max = min(pt_y), max(pt_y)
1638
+
1639
+ x_grid, y_grid = np.meshgrid(
1640
+ np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
1641
+ )
1642
+
1643
+ # mapping to meshgrid
1644
+ to_keep = {
1645
+ ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
1646
+ for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
1647
+ }
1648
+
1649
+ all_instances = self.instances
1650
+ for item in padstacks_inbox:
1651
+ if item not in to_keep:
1652
+ all_instances[item].delete()
1653
+ return True
@@ -399,7 +399,7 @@ class PadstackInstance(GrpcPadstackInstance):
399
399
  layer_list = []
400
400
  start_layer_name = start_layer.name
401
401
  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()):
403
403
  if started:
404
404
  layer_list.append(layer_name)
405
405
  if layer_name == stop_layer_name or layer_name == start_layer_name:
@@ -20,12 +20,11 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ from typing import Union
24
+
23
25
  from ansys.edb.core.database import ProductIdType as GrpcProductIdType
24
26
  from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
25
27
  from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
26
- from ansys.edb.core.hierarchy.component_group import (
27
- ComponentGroup as GrpcComponentGroup,
28
- )
29
28
  from ansys.edb.core.terminal.terminals import BoundaryType as GrpcBoundaryType
30
29
  from ansys.edb.core.terminal.terminals import EdgeTerminal as GrpcEdgeTerminal
31
30
  from ansys.edb.core.terminal.terminals import PrimitiveEdge as GrpcPrimitiveEdge
@@ -33,6 +32,7 @@ from ansys.edb.core.utility.rlc import Rlc as GrpcRlc
33
32
  from ansys.edb.core.utility.value import Value as GrpcValue
34
33
 
35
34
  from pyedb.generic.general_methods import generate_unique_name
35
+ from pyedb.grpc.database.components import Component
36
36
  from pyedb.grpc.database.layers.stackup_layer import StackupLayer
37
37
  from pyedb.grpc.database.net.net import Net
38
38
  from pyedb.grpc.database.ports.ports import BundleWavePort, WavePort
@@ -169,17 +169,18 @@ class SourceExcitation:
169
169
  Parameters
170
170
  ----------
171
171
  refdes : Component reference designator
172
- str or EDBComponent object.
173
- pins : pin name where the terminal has to be created. Single pin or several ones can be provided.If several
174
- pins are provided a pin group will is created. Pin names can be the EDB name or the EDBPadstackInstance one.
175
- For instance the pin called ``Pin1`` located on component ``U1``, ``U1-Pin1`` or ``Pin1`` can be provided and
176
- will be handled.
177
- str, [str], EDBPadstackInstance, [EDBPadstackInstance]
178
- reference_pins : reference pin name used for terminal reference. Single pin or several ones can be provided.
179
- If several pins are provided a pin group will is created. Pin names can be the EDB name or the
180
- EDBPadstackInstance one. For instance the pin called ``Pin1`` located on component ``U1``, ``U1-Pin1``
181
- or ``Pin1`` can be provided and will be handled.
182
- str, [str], EDBPadstackInstance, [EDBPadstackInstance]
172
+ str or Component object.
173
+ pins : pin specifier(s) or instance(s) where the port terminal is to be created. Single pin name or a list of
174
+ several can be provided. If several pins are provided a pin group will be created. Pin specifiers can be the
175
+ global EDB object ID or padstack instance name or pin name on component with refdes ``refdes``. Pin instances
176
+ can be provided as ``EDBPadstackInstance`` objects.
177
+ For instance for the pin called ``Pin1`` located on component with refdes ``U1``: ``U1-Pin1``, ``Pin1`` with
178
+ ``refdes=U1``, the pin's global EDB object ID, or the ``EDBPadstackInstance`` corresponding to the pin can be
179
+ provided.
180
+ Union[int, str, PadstackInstance], List[Union[int, str, PadstackInstance]]
181
+ reference_pins : reference pin specifier(s) or instance(s) for the port reference terminal. Allowed values are
182
+ the same as for the ``pins`` parameter.
183
+ Union[int, str, PadstackInstance], List[Union[int, str, PadstackInstance]]
183
184
  impedance : Port impedance
184
185
  str, float
185
186
  port_name : str, optional
@@ -208,116 +209,51 @@ class SourceExcitation:
208
209
  >>> edb.save_edb()
209
210
  >>> edb.close_edb()
210
211
  """
211
- from pyedb.grpc.database.components import Component
212
212
 
213
- if isinstance(pins, str):
214
- pins = [pins]
215
- elif isinstance(pins, PadstackInstance):
216
- pins = [pins.name]
217
- if not reference_pins:
218
- self._logger.error("No reference pin provided.")
219
- return False
220
- if isinstance(reference_pins, str):
221
- reference_pins = [reference_pins]
222
- elif isinstance(reference_pins, int):
223
- reference_pins = [reference_pins]
224
- elif isinstance(reference_pins, PadstackInstance):
225
- reference_pins = [reference_pins]
226
- if isinstance(reference_pins, list):
227
- _temp = []
228
- for ref_pin in reference_pins:
229
- if isinstance(ref_pin, int):
230
- pins = self._pedb.padstacks.instances
231
- reference_pins = [pins[ref_pin] for ref_pin in reference_pins if ref_pin in pins]
232
- # if reference_pins in pins:
233
- # reference_pins = pins[reference_pins]
234
- elif isinstance(ref_pin, str):
235
- component_pins = self._pedb.components.instances[refdes].pins
236
- if ref_pin in component_pins:
237
- _temp.append(component_pins[ref_pin])
238
- else:
239
- p = [pp for pp in list(self._pedb.padstack.instances.values()) if pp.name == ref_pin]
240
- if p:
241
- _temp.extend(p)
242
- elif isinstance(ref_pin, PadstackInstance):
243
- _temp.append(ref_pin)
244
- reference_pins = _temp
245
213
  if isinstance(refdes, str):
246
214
  refdes = self._pedb.components.instances[refdes]
247
- elif isinstance(refdes, GrpcComponentGroup):
215
+ elif isinstance(refdes, Component):
248
216
  refdes = Component(self._pedb, refdes)
249
- refdes_pins = refdes.pins
250
- if any(refdes.rlc_values):
251
- return self._pedb.components.deactivate_rlc_component(component=refdes, create_circuit_port=True)
252
- if len([pin for pin in pins if isinstance(pin, str)]) == len(pins):
253
- cmp_pins = []
254
- for pin_name in pins:
255
- cmp_pins = [pin for pin in list(refdes_pins.values()) if pin_name == pin.name]
256
- if not cmp_pins:
257
- for pin in list(refdes_pins.values()):
258
- if pin.name and "-" in pin.name:
259
- if pin_name == pin.name.split("-")[1]:
260
- cmp_pins.append(pin)
261
- if not cmp_pins:
262
- self._logger.warning("No pin found during port creation. Port is not defined.")
263
- return
264
- pins = cmp_pins
265
- if not len([pin for pin in pins if isinstance(pin, PadstackInstance)]) == len(pins):
266
- self._logger.error("Pin list must contain only pins instances")
217
+ pins = self._get_pins_for_ports(pins, refdes)
218
+ if not pins:
219
+ self._logger.error("No pins found during port creation. Port is not defined.")
267
220
  return False
268
- if not port_name:
269
- pin = pins[0]
270
- if pin.net.is_null:
271
- pin_net_name = "no_net"
272
- else:
273
- pin_net_name = pin.net.name
274
- port_name = f"Port_{pin_net_name}_{refdes.name}_{pins[0].name}"
275
-
276
- ref_cmp_pins = []
277
- for ref_pin in reference_pins:
278
- if ref_pin.name in refdes_pins:
279
- ref_cmp_pins.append(ref_pin)
280
- elif "-" in ref_pin.name:
281
- if ref_pin.name.split("-")[1] in refdes_pins:
282
- ref_cmp_pins.append(ref_pin)
283
- elif "via" in ref_pin.name:
284
- _ref_pin = [
285
- pin for pin in list(self._pedb.padstacks.instances.values()) if pin.aedt_name == ref_pin.name
286
- ]
287
- if _ref_pin:
288
- _ref_pin[0].is_layout_pin = True
289
- ref_cmp_pins.append(_ref_pin[0])
290
- if not ref_cmp_pins:
291
- self._logger.error("No reference pins found.")
221
+ reference_pins = self._get_pins_for_ports(reference_pins, refdes)
222
+ if not reference_pins:
223
+ self._logger.error("No reference pins found during port creation. Port is not defined.")
292
224
  return False
293
- reference_pins = ref_cmp_pins
225
+ if refdes and any(refdes.rlc_values):
226
+ return self._pedb.components.deactivate_rlc_component(component=refdes, create_circuit_port=True)
227
+ if not port_name:
228
+ port_name = f"Port_{pins[0].net_name}_{pins[0].name}"
229
+
294
230
  if len(pins) > 1 or pingroup_on_single_pin:
295
231
  pec_boundary = False
296
232
  self._logger.info(
297
233
  "Disabling PEC boundary creation, this feature is supported on single pin "
298
- "ports only, {} pins found".format(len(pins))
234
+ f"ports only, {len(pins)} pins found (pingroup_on_single_pin: {pingroup_on_single_pin})."
299
235
  )
300
- group_name = "group_{}".format(port_name)
236
+ group_name = f"group_{port_name}"
301
237
  pin_group = self._pedb.components.create_pingroup_from_pins(pins, group_name)
302
238
  term = self._create_pin_group_terminal(pingroup=pin_group, term_name=port_name)
303
-
304
239
  else:
305
240
  term = self._create_terminal(pins[0], term_name=port_name)
306
241
  term.is_circuit_port = True
242
+
307
243
  if len(reference_pins) > 1 or pingroup_on_single_pin:
308
244
  pec_boundary = False
309
245
  self._logger.info(
310
- "Disabling PEC boundary creation. This feature is supported on single pin"
311
- "ports only {} reference pins found.".format(len(reference_pins))
246
+ "Disabling PEC boundary creation. This feature is supported on single pin "
247
+ f"ports only, {len(reference_pins)} reference pins found "
248
+ f"(pingroup_on_single_pin: {pingroup_on_single_pin})."
312
249
  )
313
- ref_group_name = "group_{}_ref".format(port_name)
250
+ ref_group_name = f"group_{port_name}_ref"
314
251
  ref_pin_group = self._pedb.components.create_pingroup_from_pins(reference_pins, ref_group_name)
315
- ref_pin_group = self._pedb.siwave.pin_groups[ref_pin_group.name]
316
252
  ref_term = self._create_pin_group_terminal(pingroup=ref_pin_group, term_name=port_name + "_ref")
317
-
318
253
  else:
319
254
  ref_term = self._create_terminal(reference_pins[0], term_name=port_name + "_ref")
320
255
  ref_term.is_circuit_port = True
256
+
321
257
  term.impedance = GrpcValue(impedance)
322
258
  term.reference_terminal = ref_term
323
259
  if pec_boundary:
@@ -326,11 +262,33 @@ class SourceExcitation:
326
262
  term.boundary_type = GrpcBoundaryType.PEC
327
263
  ref_term.boundary_type = GrpcBoundaryType.PEC
328
264
  self._logger.info(
329
- "PEC boundary created between pin {} and reference pin {}".format(pins[0].name, reference_pins[0].name)
265
+ f"PEC boundary created between pin {pins[0].name} and reference pin {reference_pins[0].name}"
330
266
  )
331
- if term:
332
- return term
333
- return False
267
+ return term or False
268
+
269
+ def _get_pins_for_ports(
270
+ self, pins: Union[int, str, PadstackInstance, list[Union[int, str, PadstackInstance]]], comp: Component
271
+ ) -> list[PadstackInstance]:
272
+ if not isinstance(pins, list):
273
+ pins = [pins]
274
+ result = []
275
+ for pin in pins:
276
+ if isinstance(pin, int) and pin in self._pedb.padstacks.instances:
277
+ result.append(self._pedb.padstacks.instances[pin])
278
+ elif isinstance(pin, str):
279
+ if comp and pin in comp.pins:
280
+ result.append(comp.pins[pin])
281
+ else:
282
+ p = [
283
+ pp
284
+ for pp in list(self._pedb.padstacks.instances.values())
285
+ if pp.name == pin or pp.aedt_name == pin
286
+ ]
287
+ if p:
288
+ result.append(p[0])
289
+ elif isinstance(pin, PadstackInstance):
290
+ result.append(pin)
291
+ return result
334
292
 
335
293
  def create_port_on_component(
336
294
  self,
@@ -407,8 +365,14 @@ class SourceExcitation:
407
365
  net_list.append(net_name)
408
366
  except:
409
367
  pass
410
- if reference_net in net_list:
411
- net_list.remove(reference_net)
368
+ if isinstance(reference_net, str) or isinstance(reference_net, Net):
369
+ reference_net = [reference_net]
370
+ _reference_net = [ref.name for ref in reference_net if isinstance(ref, Net)]
371
+ if len(_reference_net) == len(reference_net):
372
+ reference_net = _reference_net
373
+ for ref_net in reference_net:
374
+ if ref_net in net_list:
375
+ net_list.remove(ref_net)
412
376
  cmp_pins = [p for p in list(component.pins.values()) if p.net_name in net_list]
413
377
  for p in cmp_pins: # pragma no cover
414
378
  p.is_layout_pin = True
@@ -541,6 +505,20 @@ class SourceExcitation:
541
505
  self._logger.error("Skipping port creation no reference pin found.")
542
506
  return True
543
507
 
508
+ @staticmethod
509
+ def _normalize_net_list(net_list) -> set[str]:
510
+ if not isinstance(net_list, list):
511
+ net_list = [net_list]
512
+ nets = set()
513
+ for net in net_list:
514
+ if isinstance(net, Net):
515
+ net_name = net.name
516
+ if net_name != "":
517
+ nets.add(net_name)
518
+ elif isinstance(net, str) and net != "":
519
+ nets.add(net)
520
+ return nets
521
+
544
522
  def _create_terminal(self, pin, term_name=None):
545
523
  """Create terminal on component pin.
546
524