pyedb 0.57.0__py3-none-any.whl → 0.59.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.

Files changed (46) hide show
  1. pyedb/__init__.py +1 -1
  2. pyedb/configuration/cfg_pin_groups.py +2 -0
  3. pyedb/dotnet/database/cell/hierarchy/component.py +2 -8
  4. pyedb/dotnet/database/cell/layout.py +1 -1
  5. pyedb/dotnet/database/components.py +38 -42
  6. pyedb/dotnet/database/edb_data/control_file.py +13 -5
  7. pyedb/dotnet/database/edb_data/padstacks_data.py +34 -12
  8. pyedb/dotnet/database/edb_data/sources.py +21 -2
  9. pyedb/dotnet/database/general.py +4 -8
  10. pyedb/dotnet/database/layout_validation.py +8 -0
  11. pyedb/dotnet/database/sim_setup_data/data/settings.py +2 -2
  12. pyedb/dotnet/database/sim_setup_data/io/siwave.py +53 -0
  13. pyedb/dotnet/database/stackup.py +5 -32
  14. pyedb/dotnet/database/utilities/hfss_simulation_setup.py +81 -0
  15. pyedb/dotnet/database/utilities/siwave_simulation_setup.py +259 -11
  16. pyedb/dotnet/edb.py +26 -13
  17. pyedb/extensions/create_cell_array.py +48 -44
  18. pyedb/generic/general_methods.py +24 -36
  19. pyedb/generic/plot.py +8 -23
  20. pyedb/generic/process.py +78 -10
  21. pyedb/grpc/database/components.py +7 -5
  22. pyedb/grpc/database/control_file.py +13 -5
  23. pyedb/grpc/database/definition/padstack_def.py +10 -5
  24. pyedb/grpc/database/hierarchy/component.py +2 -9
  25. pyedb/grpc/database/modeler.py +28 -8
  26. pyedb/grpc/database/padstacks.py +62 -103
  27. pyedb/grpc/database/primitive/padstack_instance.py +41 -12
  28. pyedb/grpc/database/primitive/path.py +13 -13
  29. pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +79 -0
  30. pyedb/grpc/database/source_excitations.py +7 -7
  31. pyedb/grpc/database/stackup.py +5 -33
  32. pyedb/grpc/database/terminal/padstack_instance_terminal.py +9 -11
  33. pyedb/grpc/database/terminal/point_terminal.py +30 -0
  34. pyedb/grpc/database/terminal/terminal.py +16 -2
  35. pyedb/grpc/database/utility/xml_control_file.py +13 -5
  36. pyedb/grpc/edb.py +46 -20
  37. pyedb/grpc/edb_init.py +7 -19
  38. pyedb/misc/aedtlib_personalib_install.py +2 -2
  39. pyedb/misc/downloads.py +18 -3
  40. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +2 -1
  41. pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +0 -1
  42. pyedb/workflows/sipi/hfss_auto_configuration.py +711 -0
  43. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/METADATA +4 -5
  44. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/RECORD +46 -45
  45. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/WHEEL +0 -0
  46. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/licenses/LICENSE +0 -0
@@ -25,6 +25,7 @@ This module contains the `EdbPadstacks` class.
25
25
  """
26
26
 
27
27
  from collections import defaultdict
28
+ from functools import lru_cache
28
29
  import math
29
30
  from typing import Any, Dict, List, Optional, Tuple, Union
30
31
  import warnings
@@ -48,6 +49,29 @@ from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
48
49
  from pyedb.grpc.database.utility.value import Value
49
50
  from pyedb.modeler.geometry_operators import GeometryOperators
50
51
 
52
+ GEOMETRY_MAP = {
53
+ 0: GrpcPadGeometryType.PADGEOMTYPE_NO_GEOMETRY,
54
+ 1: GrpcPadGeometryType.PADGEOMTYPE_CIRCLE,
55
+ 2: GrpcPadGeometryType.PADGEOMTYPE_SQUARE,
56
+ 3: GrpcPadGeometryType.PADGEOMTYPE_RECTANGLE,
57
+ 4: GrpcPadGeometryType.PADGEOMTYPE_OVAL,
58
+ 5: GrpcPadGeometryType.PADGEOMTYPE_BULLET,
59
+ 6: GrpcPadGeometryType.PADGEOMTYPE_NSIDED_POLYGON,
60
+ 7: GrpcPadGeometryType.PADGEOMTYPE_POLYGON,
61
+ 8: GrpcPadGeometryType.PADGEOMTYPE_ROUND45,
62
+ 9: GrpcPadGeometryType.PADGEOMTYPE_ROUND90,
63
+ 10: GrpcPadGeometryType.PADGEOMTYPE_SQUARE45,
64
+ 11: GrpcPadGeometryType.PADGEOMTYPE_SQUARE90,
65
+ }
66
+
67
+ PAD_TYPE_MAP = {
68
+ 0: GrpcPadType.REGULAR_PAD,
69
+ 1: GrpcPadType.ANTI_PAD,
70
+ 2: GrpcPadType.THERMAL_PAD,
71
+ 3: GrpcPadType.HOLE,
72
+ 4: GrpcPadType.UNKNOWN_GEOM_TYPE,
73
+ }
74
+
51
75
 
52
76
  class Padstacks(object):
53
77
  """Manages EDB methods for padstacks accessible from `Edb.padstacks` property.
@@ -87,6 +111,15 @@ class Padstacks(object):
87
111
  def __init__(self, p_edb: Any) -> None:
88
112
  self._pedb = p_edb
89
113
  self.__definitions: Dict[str, Any] = {}
114
+ self._instances = None
115
+ self._instances_by_name = {}
116
+ self._instances_by_net = {}
117
+
118
+ def clear_instances_cache(self):
119
+ """Clear the cached padstack instances."""
120
+ self._instances = None
121
+ self._instances_by_name = {}
122
+ self._instances_by_net = {}
90
123
 
91
124
  @property
92
125
  def _active_layout(self) -> Any:
@@ -113,7 +146,8 @@ class Padstacks(object):
113
146
  """ """
114
147
  return self._pedb.stackup.layers
115
148
 
116
- def int_to_pad_type(self, val=0) -> GrpcPadType:
149
+ @staticmethod
150
+ def int_to_pad_type(val=0) -> GrpcPadType:
117
151
  """Convert an integer to an EDB.PadGeometryType.
118
152
 
119
153
  Parameters
@@ -131,20 +165,10 @@ class Padstacks(object):
131
165
  >>> pad_type = edb_padstacks.int_to_pad_type(1) # Returns ANTI_PAD
132
166
  """
133
167
 
134
- if val == 0:
135
- return GrpcPadType.REGULAR_PAD
136
- elif val == 1:
137
- return GrpcPadType.ANTI_PAD
138
- elif val == 2:
139
- return GrpcPadType.THERMAL_PAD
140
- elif val == 3:
141
- return GrpcPadType.HOLE
142
- elif val == 4:
143
- return GrpcPadType.UNKNOWN_GEOM_TYPE
144
- else:
145
- return val
168
+ return PAD_TYPE_MAP.get(val, val)
146
169
 
147
- def int_to_geometry_type(self, val: int = 0) -> GrpcPadGeometryType:
170
+ @staticmethod
171
+ def int_to_geometry_type(val: int = 0) -> GrpcPadGeometryType:
148
172
  """Convert an integer to an EDB.PadGeometryType.
149
173
 
150
174
  Parameters
@@ -161,32 +185,7 @@ class Padstacks(object):
161
185
  >>> geom_type = edb_padstacks.int_to_geometry_type(1) # Returns CIRCLE
162
186
  >>> geom_type = edb_padstacks.int_to_geometry_type(2) # Returns SQUARE
163
187
  """
164
- if val == 0:
165
- return GrpcPadGeometryType.PADGEOMTYPE_NO_GEOMETRY
166
- elif val == 1:
167
- return GrpcPadGeometryType.PADGEOMTYPE_CIRCLE
168
- elif val == 2:
169
- return GrpcPadGeometryType.PADGEOMTYPE_SQUARE
170
- elif val == 3:
171
- return GrpcPadGeometryType.PADGEOMTYPE_RECTANGLE
172
- elif val == 4:
173
- return GrpcPadGeometryType.PADGEOMTYPE_OVAL
174
- elif val == 5:
175
- return GrpcPadGeometryType.PADGEOMTYPE_BULLET
176
- elif val == 6:
177
- return GrpcPadGeometryType.PADGEOMTYPE_NSIDED_POLYGON
178
- elif val == 7:
179
- return GrpcPadGeometryType.PADGEOMTYPE_POLYGON
180
- elif val == 8:
181
- return GrpcPadGeometryType.PADGEOMTYPE_ROUND45
182
- elif val == 9:
183
- return GrpcPadGeometryType.PADGEOMTYPE_ROUND90
184
- elif val == 10:
185
- return GrpcPadGeometryType.PADGEOMTYPE_SQUARE45
186
- elif val == 11:
187
- return GrpcPadGeometryType.PADGEOMTYPE_SQUARE90
188
- else:
189
- return val
188
+ return GEOMETRY_MAP.get(val, val)
190
189
 
191
190
  @property
192
191
  def definitions(self) -> Dict[str, PadstackDef]:
@@ -225,7 +224,17 @@ class Padstacks(object):
225
224
  >>> for id, instance in all_instances.items():
226
225
  ... print(f"Instance {id}: {instance.name}")
227
226
  """
228
- return self._pedb.layout.padstack_instances
227
+ if self._instances is None:
228
+ self._instances = self._pedb.layout.padstack_instances
229
+ return self._instances
230
+
231
+ @property
232
+ def instances_by_net(self) -> Dict[Any, PadstackInstance]:
233
+ if not self._instances_by_net:
234
+ for edb_padstack_instance in self._instances.values():
235
+ if edb_padstack_instance.net_name:
236
+ self._instances_by_net.setdefault(edb_padstack_instance.net_name, []).append(edb_padstack_instance)
237
+ return self._instances_by_net
229
238
 
230
239
  @property
231
240
  def instances_by_name(self) -> Dict[str, PadstackInstance]:
@@ -242,11 +251,11 @@ class Padstacks(object):
242
251
  >>> for name, instance in named_instances.items():
243
252
  ... print(f"Instance named {name}")
244
253
  """
245
- padstack_instances = {}
246
- for _, edb_padstack_instance in self.instances.items():
247
- if edb_padstack_instance.aedt_name:
248
- padstack_instances[edb_padstack_instance.aedt_name] = edb_padstack_instance
249
- return padstack_instances
254
+ if not self._instances_by_name:
255
+ for _, edb_padstack_instance in self.instances.items():
256
+ if edb_padstack_instance.aedt_name:
257
+ self._instances_by_name[edb_padstack_instance.aedt_name] = edb_padstack_instance
258
+ return self._instances_by_name
250
259
 
251
260
  def find_instance_by_id(self, value: int) -> Optional[PadstackInstance]:
252
261
  """Find a padstack instance by database ID.
@@ -471,6 +480,7 @@ class Padstacks(object):
471
480
  if p.net_name in net_names:
472
481
  if not p.delete(): # pragma: no cover
473
482
  return False
483
+ self.clear_instances_cache()
474
484
  return True
475
485
 
476
486
  def set_solderball(self, padstackInst, sballLayer_name, isTopPlaced=True, ballDiam=100e-6):
@@ -1133,6 +1143,7 @@ class Padstacks(object):
1133
1143
  layer_map=None,
1134
1144
  )
1135
1145
  padstack_instance.is_layout_pin = is_pin
1146
+ self.clear_instances_cache()
1136
1147
  return PadstackInstance(self._pedb, padstack_instance)
1137
1148
  else:
1138
1149
  raise RuntimeError("Place padstack failed")
@@ -1505,7 +1516,7 @@ class Padstacks(object):
1505
1516
  minimum_via_number: int = 6,
1506
1517
  selected_angles: Optional[List[float]] = None,
1507
1518
  padstack_instances_id: Optional[List[int]] = None,
1508
- ) -> None:
1519
+ ) -> List[str]:
1509
1520
  """Replace padstack instances along lines into a single polygon.
1510
1521
 
1511
1522
  Detect all pad-stack instances that are placed along lines and replace them by a single polygon based one
@@ -1594,66 +1605,13 @@ class Padstacks(object):
1594
1605
  inst.delete()
1595
1606
  return instances_created
1596
1607
 
1597
- def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=None):
1598
- """Reduces the number of vias intersecting bounding box and nets by x and y samples.
1599
-
1600
- Parameters
1601
- ----------
1602
- bounding_box : tuple or list.
1603
- bounding box, [x1, y1, x2, y2]
1604
- x_samples : int
1605
- y_samples : int
1606
- nets : str or list, optional
1607
- net name or list of nets name applying filtering on padstack instances selection. If ``None`` is provided
1608
- all instances are included in the index. Default value is ``None``.
1609
-
1610
- Returns
1611
- -------
1612
- bool
1613
- ``True`` when succeeded ``False`` when failed.
1614
- """
1615
-
1616
- padstacks_inbox = self.get_padstack_instances_intersecting_bounding_box(bounding_box, nets)
1617
- if not padstacks_inbox:
1618
- self._logger.info("no padstack in bounding box")
1619
- return False
1620
- else:
1621
- if len(padstacks_inbox) <= (x_samples * y_samples):
1622
- self._logger.info(f"more samples {x_samples * y_samples} than existing {len(padstacks_inbox)}")
1623
- return False
1624
- else:
1625
- # extract ids and positions
1626
- vias = {item: self.instances[item].position for item in padstacks_inbox}
1627
- ids, positions = zip(*vias.items())
1628
- pt_x, pt_y = zip(*positions)
1629
-
1630
- # meshgrid
1631
- _x_min, _x_max = min(pt_x), max(pt_x)
1632
- _y_min, _y_max = min(pt_y), max(pt_y)
1633
-
1634
- x_grid, y_grid = np.meshgrid(
1635
- np.linspace(_x_min, _x_max, x_samples), np.linspace(_y_min, _y_max, y_samples)
1636
- )
1637
-
1638
- # mapping to meshgrid
1639
- to_keep = {
1640
- ids[np.argmin(np.square(_x - pt_x) + np.square(_y - pt_y))]
1641
- for _x, _y in zip(x_grid.ravel(), y_grid.ravel())
1642
- }
1643
-
1644
- for item in padstacks_inbox:
1645
- if item not in to_keep:
1646
- self.instances[item].delete()
1647
-
1648
- return True
1649
-
1650
1608
  def merge_via(
1651
1609
  self,
1652
1610
  contour_boxes: List[List[float]],
1653
1611
  net_filter: Optional[Union[str, List[str]]] = None,
1654
1612
  start_layer: Optional[str] = None,
1655
1613
  stop_layer: Optional[str] = None,
1656
- ) -> bool:
1614
+ ) -> List[str]:
1657
1615
  """Evaluate pad-stack instances included on the provided point list and replace all by single instance.
1658
1616
 
1659
1617
  Parameters
@@ -1673,7 +1631,6 @@ class Padstacks(object):
1673
1631
 
1674
1632
  """
1675
1633
 
1676
- import numpy as np
1677
1634
  from scipy.spatial import ConvexHull
1678
1635
 
1679
1636
  merged_via_ids = []
@@ -1717,6 +1674,7 @@ class Padstacks(object):
1717
1674
  )
1718
1675
  merged_via_ids.append(merged_instance.edb_uid)
1719
1676
  [self.instances[inst].delete() for inst in instances]
1677
+ self.clear_instances_cache()
1720
1678
  return merged_via_ids
1721
1679
 
1722
1680
  def reduce_via_in_bounding_box(
@@ -1772,6 +1730,7 @@ class Padstacks(object):
1772
1730
  for item in padstacks_inbox:
1773
1731
  if item not in to_keep:
1774
1732
  all_instances[item].delete()
1733
+ self.clear_instances_cache()
1775
1734
  return True
1776
1735
 
1777
1736
  @staticmethod
@@ -1927,5 +1886,5 @@ class Padstacks(object):
1927
1886
  to_delete = set(padstacks) - to_keep
1928
1887
  for _id in to_delete:
1929
1888
  all_instances[_id].delete()
1930
-
1889
+ self.clear_instances_cache()
1931
1890
  return list(to_keep), grid
@@ -122,6 +122,47 @@ class PadstackInstance(GrpcPadstackInstance):
122
122
  term = PadstackInstanceTerminal(self._pedb, term)
123
123
  return term if not term.is_null else None
124
124
 
125
+ @property
126
+ def side_number(self):
127
+ """Return the number of sides meshed of the padstack instance.
128
+ Returns
129
+ -------
130
+ int
131
+ Number of sides meshed of the padstack instance.
132
+ """
133
+ side_value = self.get_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21)
134
+ if side_value:
135
+ return int(re.search(r"(?m)^\s*sid=(\d+)", side_value).group(1))
136
+ return 0
137
+
138
+ @side_number.setter
139
+ def side_number(self, value):
140
+ """Set the number of sides meshed of the padstack instance.
141
+
142
+ Parameters
143
+ ----------
144
+ value : int
145
+ Number of sides to mesh the padstack instance.
146
+
147
+ Returns
148
+ -------
149
+ bool
150
+ True if successful, False otherwise.
151
+ """
152
+ if isinstance(value, int) and 3 <= value <= 64:
153
+ prop_string = f"$begin ''\n\tsid={value}\n\tmat='copper'\n\tvs='Wirebond'\n$end ''\n"
154
+ self.set_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21, prop_string)
155
+ else:
156
+ raise ValueError("Number of sides must be an integer between 3 and 64")
157
+
158
+ def delete(self):
159
+ """Delete the padstack instance."""
160
+ try:
161
+ self._pedb.padstacks._instances.pop(self.edb_uid, None)
162
+ except Exception:
163
+ self._pedb.padstacks.clear_instances_cache()
164
+ super().delete()
165
+
125
166
  def set_backdrill_top(self, drill_depth, drill_diameter, offset=0.0):
126
167
  """Set backdrill from top.
127
168
 
@@ -759,18 +800,6 @@ class PadstackInstance(GrpcPadstackInstance):
759
800
  def aedt_name(self, value):
760
801
  self.set_product_property(GrpcProductIdType.DESIGNER, 11, value)
761
802
 
762
- @property
763
- def side_number(self) -> int:
764
- if not self._side_number:
765
- prop_string = "$begin ''\n\tsid=3\n\tmat='copper'\n\tvs='Wirebond'\n$end ''\n"
766
- self.set_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21, prop_string)
767
- self._side_number = self.get_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21)
768
- return self._side_number
769
-
770
- @side_number.setter
771
- def side_number(self, value):
772
- self._side_number = self.set_product_property(GrpcProductIdType.HFSS_3D_LAYOUT, 21, value)
773
-
774
803
  def split(self) -> list:
775
804
  """Split padstack instance into multiple instances. The new instances only connect adjacent layers."""
776
805
  pdef_name = self.padstack_definition
@@ -25,7 +25,7 @@ from typing import Union
25
25
  from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
26
26
  from ansys.edb.core.primitive.path import (
27
27
  Path as GrpcPath,
28
- PathCornerType as GrpcPatCornerType,
28
+ PathCornerType as GrpcPathCornerType,
29
29
  PathEndCapType as GrpcPathEndCapType,
30
30
  )
31
31
 
@@ -84,9 +84,9 @@ class Path(GrpcPath, Primitive):
84
84
  layer: Union[str, Layer] = None,
85
85
  net: Union[str, "Net"] = None,
86
86
  width: float = 100e-6,
87
- end_cap1: str = "flat",
88
- end_cap2: str = "flat",
89
- corner_style: str = "sharp",
87
+ end_cap1: Union[str, GrpcPathEndCapType] = "flat",
88
+ end_cap2: Union[str, GrpcPathEndCapType] = "flat",
89
+ corner_style: Union[str, GrpcPathCornerType] = "sharp",
90
90
  points: Union[list, GrpcPolygonData] = None,
91
91
  ):
92
92
  """
@@ -141,9 +141,9 @@ class Path(GrpcPath, Primitive):
141
141
  "clipped": GrpcPathEndCapType.CLIPPED,
142
142
  }
143
143
  corner_style_mapping = {
144
- "round": GrpcPatCornerType.ROUND,
145
- "mitter": GrpcPatCornerType.MITER,
146
- "sharp": GrpcPatCornerType.SHARP,
144
+ "round": GrpcPathCornerType.ROUND,
145
+ "mitter": GrpcPathCornerType.MITER,
146
+ "sharp": GrpcPathCornerType.SHARP,
147
147
  }
148
148
  if isinstance(end_cap1, str):
149
149
  end_cap1 = end_cap_mapping[end_cap1.lower()]
@@ -215,9 +215,9 @@ class Path(GrpcPath, Primitive):
215
215
  ``True`` when successful, ``False`` when failed.
216
216
  """
217
217
  mapping = {
218
- "round": GrpcPatCornerType.ROUND,
219
- "mitter": GrpcPatCornerType.MITER,
220
- "sharp": GrpcPatCornerType.SHARP,
218
+ "round": GrpcPathCornerType.ROUND,
219
+ "mitter": GrpcPathCornerType.MITER,
220
+ "sharp": GrpcPathCornerType.SHARP,
221
221
  }
222
222
 
223
223
  cloned_path = GrpcPath.create(
@@ -444,9 +444,9 @@ class Path(GrpcPath, Primitive):
444
444
  def corner_style(self, corner_type):
445
445
  if isinstance(corner_type, str):
446
446
  mapping = {
447
- "round": GrpcPatCornerType.ROUND,
448
- "mitter": GrpcPatCornerType.MITER,
449
- "sharp": GrpcPatCornerType.SHARP,
447
+ "round": GrpcPathCornerType.ROUND,
448
+ "mitter": GrpcPathCornerType.MITER,
449
+ "sharp": GrpcPathCornerType.SHARP,
450
450
  }
451
451
  self.corner_style = mapping[corner_type]
452
452
 
@@ -30,6 +30,7 @@ from ansys.edb.core.simulation_setup.hfss_simulation_settings import (
30
30
  from ansys.edb.core.simulation_setup.hfss_simulation_setup import (
31
31
  HfssSimulationSetup as GrpcHfssSimulationSetup,
32
32
  )
33
+ from ansys.edb.core.simulation_setup.mesh_operation import LengthMeshOperation as GrpcLengthMeshOperation
33
34
 
34
35
  from pyedb.generic.general_methods import generate_unique_name
35
36
  from pyedb.grpc.database.simulation_setup.sweep_data import SweepData
@@ -411,3 +412,81 @@ class HfssSimulationSetup(GrpcHfssSimulationSetup):
411
412
  else:
412
413
  self._pedb.logger.error("Failed to add frequency sweep data")
413
414
  return False
415
+
416
+ def auto_mesh_operation(
417
+ self,
418
+ trace_ratio_seeding: float = 3,
419
+ signal_via_side_number: int = 12,
420
+ power_ground_via_side_number: int = 6,
421
+ ) -> bool:
422
+ """
423
+ Automatically create and apply a length-based mesh operation for all nets in the design.
424
+
425
+ The method inspects every signal net, determines the smallest trace width, and
426
+ seeds a :class:`GrpcLengthMeshOperation` whose maximum element length is
427
+ ``smallest_width * trace_ratio_seeding``. Signal vias (padstack instances) are
428
+ configured with the requested number of polygon sides, while power/ground vias
429
+ are updated through the global ``num_via_sides`` advanced setting.
430
+
431
+ Parameters
432
+ ----------
433
+ trace_ratio_seeding : float, optional
434
+ Ratio used to compute the maximum allowed element length from the
435
+ smallest trace width found in the design. The resulting length is
436
+ ``min_width * trace_ratio_seeding``. Defaults to ``3``.
437
+ signal_via_side_number : int, optional
438
+ Number of sides (i.e. faceting resolution) assigned to **signal**
439
+ padstack instances that belong to the nets being meshed.
440
+ Defaults to ``12``.
441
+ power_ground_via_side_number : int, optional
442
+ Number of sides assigned to **power/ground** vias via the global
443
+ ``advanced.num_via_sides`` setting. Defaults to ``6``.
444
+
445
+ Returns
446
+ -------
447
+ bool
448
+
449
+ Raises
450
+ ------
451
+ ValueError
452
+ If the design contains no terminals, making mesh seeding impossible.
453
+
454
+ Notes
455
+ -----
456
+ * Only primitives of type ``"path"`` are considered when determining the
457
+ smallest trace width.
458
+ * Every ``(net, layer, sheet)`` tuple required by the mesher is
459
+ automatically populated; sheet are explicitly marked as ``False``.
460
+ * Existing contents of :attr:`mesh_operations` are **replaced** by the
461
+ single new operation.
462
+
463
+ Examples
464
+ --------
465
+ >>> setup = edbapp.setups["my_setup"]
466
+ >>> setup.auto_mesh_operation(trace_ratio_seeding=4, signal_via_side_number=16)
467
+ >>> setup.mesh_operations[0].max_length
468
+ '2.5um'
469
+ """
470
+ net_for_mesh_seeding = list(set([term.net.name for term in list(self._pedb.terminals.values())]))
471
+ if not net_for_mesh_seeding:
472
+ raise ValueError("No terminals found to seed the mesh operation.")
473
+ meshop = GrpcLengthMeshOperation(name=f"{self.name}_AutoMeshOp")
474
+ layer_info = []
475
+ smallest_width = 1e3
476
+ for net in net_for_mesh_seeding:
477
+ traces = [prim for prim in self._pedb.modeler.primitives_by_net[net] if prim.type == "path"]
478
+ _width = min([trace.width for trace in traces], default=1e3)
479
+ if _width < smallest_width:
480
+ smallest_width = _width
481
+ layers = list(set([trace.layer.name for trace in traces]))
482
+ for layer in layers:
483
+ layer_info.append((net, layer, False))
484
+ for inst in self._pedb.padstacks.instances_by_net[net]:
485
+ inst.side_number = signal_via_side_number
486
+ meshop.max_length = f"{round(float((smallest_width * trace_ratio_seeding)), 9) * 1e6}um"
487
+ meshop.net_layer_info = layer_info
488
+ self.mesh_operations = [meshop]
489
+ self.settings.advanced.num_via_sides = power_ground_via_side_number
490
+ if self.mesh_operations:
491
+ return True
492
+ return False
@@ -486,22 +486,22 @@ class SourceExcitation:
486
486
 
487
487
  def create_port_on_component(
488
488
  self,
489
- component: Union[str, List[str]],
489
+ component: Union[str, Component],
490
490
  net_list: Union[str, List[str]],
491
- port_type: SourceType,
491
+ port_type: str = "coax_port",
492
492
  do_pingroup: Optional[bool] = True,
493
493
  reference_net: Optional[str] = None,
494
494
  port_name: Optional[List[str]] = None,
495
- solder_balls_height: Optional[float] = None,
496
- solder_balls_size: Optional[float] = None,
497
- solder_balls_mid_size: Optional[float] = None,
495
+ solder_balls_height: Union[float, str] = None,
496
+ solder_balls_size: Union[float, str] = None,
497
+ solder_balls_mid_size: Union[float, str] = None,
498
498
  extend_reference_pins_outside_component: Optional[bool] = False,
499
499
  ) -> List[str]:
500
500
  """Create ports on a component.
501
501
 
502
502
  Parameters
503
503
  ----------
504
- component : str or self._pedb.component
504
+ component : str or Component
505
505
  EDB component or str component name.
506
506
  net_list : str or list of string.
507
507
  List of nets where ports must be created on the component.
@@ -2661,7 +2661,7 @@ class SourceExcitation:
2661
2661
  for __pin in pins_name:
2662
2662
  if __pin in pins:
2663
2663
  pin = pins[__pin]
2664
- term_name = f"{pin.component.name}_{pin.net.name}_{pin.component}"
2664
+ term_name = f"{pin.component.name}_{pin.net.name}_{pin.component.name}"
2665
2665
  start_layer, stop_layer = pin.get_layer_range()
2666
2666
  if start_layer:
2667
2667
  positive_terminal = PadstackInstanceTerminal.create(
@@ -49,6 +49,10 @@ from ansys.edb.core.layer.layer_collection import (
49
49
  )
50
50
  from ansys.edb.core.layer.stackup_layer import StackupLayer as GrpcStackupLayer
51
51
  from ansys.edb.core.layout.mcad_model import McadModel as GrpcMcadModel
52
+ from defusedxml.ElementTree import parse as defused_parse
53
+ import matplotlib.colors as colors
54
+ import numpy as np
55
+ import pandas as pd
52
56
 
53
57
  from pyedb.generic.general_methods import ET, generate_unique_name
54
58
  from pyedb.grpc.database.layers.layer import Layer
@@ -56,24 +60,6 @@ from pyedb.grpc.database.layers.stackup_layer import StackupLayer
56
60
  from pyedb.grpc.database.utility.value import Value
57
61
  from pyedb.misc.aedtlib_personalib_install import write_pretty_xml
58
62
 
59
- colors = None
60
- pd = None
61
- np = None
62
- try:
63
- import matplotlib.colors as colors
64
- except ImportError:
65
- colors = None
66
-
67
- try:
68
- import numpy as np
69
- except ImportError:
70
- np = None
71
-
72
- try:
73
- import pandas as pd
74
- except ImportError:
75
- pd = None
76
-
77
63
  logger = logging.getLogger(__name__)
78
64
 
79
65
 
@@ -565,9 +551,6 @@ class Stackup(LayerCollection):
565
551
  >>> edb = Edb()
566
552
  >>> edb.stackup.create_symmetric_stackup(layer_count=4)
567
553
  """
568
- if not np:
569
- self._pedb.logger.error("Numpy is needed. Please, install it first.")
570
- return False
571
554
  if not layer_count % 2 == 0:
572
555
  return False
573
556
 
@@ -1024,10 +1007,6 @@ class Stackup(LayerCollection):
1024
1007
  return self.export(fpath, file_format=file_format, include_material_with_layer=include_material_with_layer)
1025
1008
 
1026
1009
  def _export_layer_stackup_to_csv_xlsx(self, fpath: Optional[str] = None, file_format: Optional[str] = None) -> bool:
1027
- if not pd:
1028
- self._pedb.logger.error("Pandas is needed. Please, install it first.")
1029
- return False
1030
-
1031
1010
  data = {
1032
1011
  "Type": [],
1033
1012
  "Material": [],
@@ -1955,10 +1934,6 @@ class Stackup(LayerCollection):
1955
1934
  bool
1956
1935
  ``True`` when successful.
1957
1936
  """
1958
- if not pd:
1959
- self._pedb.logger.error("Pandas is needed. You must install it first.")
1960
- return False
1961
-
1962
1937
  df = pd.read_csv(file_path, index_col=0)
1963
1938
 
1964
1939
  for name in self.layers.keys(): # pragma: no cover
@@ -2227,10 +2202,7 @@ class Stackup(LayerCollection):
2227
2202
  bool
2228
2203
  ``True`` when successful.
2229
2204
  """
2230
- if not colors:
2231
- self._pedb.logger.error("Matplotlib is needed. Please, install it first.")
2232
- return False
2233
- tree = ET.parse(file_path)
2205
+ tree = defused_parse(file_path)
2234
2206
  root = tree.getroot()
2235
2207
  stackup = root.find("Stackup")
2236
2208
  stackup_dict = {}
@@ -37,16 +37,6 @@ class PadstackInstanceTerminal(GrpcPadstackInstanceTerminal):
37
37
  super().__init__(edb_object.msg)
38
38
  self._pedb = pedb
39
39
 
40
- @property
41
- def boundary_type(self) -> str:
42
- """Boundary type.
43
-
44
- Returns
45
- -------
46
- str : boundary type.
47
- """
48
- return super().boundary_type.name.lower()
49
-
50
40
  @property
51
41
  def position(self) -> list[float]:
52
42
  """Terminal position.
@@ -193,7 +183,15 @@ class PadstackInstanceTerminal(GrpcPadstackInstanceTerminal):
193
183
  "rlc": GrpcBoundaryType.RLC,
194
184
  "pec": GrpcBoundaryType.PEC,
195
185
  }
196
- super(PadstackInstanceTerminal, self.__class__).boundary_type.__set__(self, mapping[value.name.lower()])
186
+ if isinstance(value, str):
187
+ key = value.lower()
188
+ else:
189
+ key = value.name.lower()
190
+ new_boundary_type = mapping.get(key)
191
+ if new_boundary_type is None:
192
+ valid_types = ", ".join(mapping.keys())
193
+ raise ValueError(f"Invalid boundary type '{value}'. Valid types are: {valid_types}")
194
+ super(PadstackInstanceTerminal, self.__class__).boundary_type.__set__(self, new_boundary_type)
197
195
 
198
196
  @property
199
197
  def is_port(self) -> bool: