pyedb 0.36.0__py3-none-any.whl → 0.38.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.

@@ -19,6 +19,9 @@
19
19
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
+ import os
23
+
24
+ import numpy as np
22
25
 
23
26
  from pyedb.configuration.cfg_common import CfgBase
24
27
  from pyedb.dotnet.edb_core.edb_data.ports import WavePort
@@ -33,6 +36,9 @@ class CfgTerminalInfo(CfgBase):
33
36
  self._pedb = pedb
34
37
  self.type = list(kwargs.keys())[0]
35
38
  self.value = kwargs[self.type]
39
+ self.contact_type = kwargs.get("contact_type", "default") # options are full, center, quad, inline
40
+ self.contact_radius = self._pedb.edb_value(kwargs.get("contact_radius", "0.1mm")).ToDouble()
41
+ self.num_of_contact = kwargs.get("num_of_contact", 4)
36
42
 
37
43
  def export_properties(self):
38
44
  return {self.type: self.value}
@@ -46,7 +52,6 @@ class CfgCoordianteTerminalInfo(CfgTerminalInfo):
46
52
  self.point_x = self.value["point"][0]
47
53
  self.point_y = self.value["point"][1]
48
54
  self.net = self.value["net"]
49
- self.contact_radius = self.value.get("contact_radius", None)
50
55
 
51
56
  def export_properties(self):
52
57
  return {"coordinates": {"layer": self.layer, "point": [self.point_x, self.point_y], "net": self.net}}
@@ -206,6 +211,16 @@ class CfgPorts:
206
211
  return ports
207
212
 
208
213
 
214
+ class CfgProbes:
215
+ def __init__(self, pedb, data):
216
+ self._pedb = pedb
217
+ self.probes = [CfgProbe(self._pedb, **probe) for probe in data]
218
+
219
+ def apply(self):
220
+ for probe in self.probes:
221
+ probe.api.set_parameters_to_edb()
222
+
223
+
209
224
  class CfgCircuitElement(CfgBase):
210
225
  def __init__(self, pedb, **kwargs):
211
226
  self._pedb = pedb
@@ -213,6 +228,7 @@ class CfgCircuitElement(CfgBase):
213
228
  self.type = kwargs["type"]
214
229
  self.reference_designator = kwargs.get("reference_designator", None)
215
230
  self.distributed = kwargs.get("distributed", False)
231
+ self._elem_num = 1
216
232
 
217
233
  pos = kwargs["positive_terminal"] # {"pin" : "A1"}
218
234
  if list(pos.keys())[0] == "coordinates":
@@ -230,9 +246,10 @@ class CfgCircuitElement(CfgBase):
230
246
  else:
231
247
  self.negative_terminal_info = CfgTerminalInfo(self._pedb, **neg)
232
248
 
233
- def _create_terminals(self):
249
+ def create_terminals(self):
234
250
  """Create step 1. Collect positive and negative terminals."""
235
251
 
252
+ # Collect all positive terminals
236
253
  pos_type, pos_value = self.positive_terminal_info.type, self.positive_terminal_info.value
237
254
  pos_objs = dict()
238
255
  pos_coor_terminal = dict()
@@ -245,35 +262,58 @@ class CfgCircuitElement(CfgBase):
245
262
  point = [self.positive_terminal_info.point_x, self.positive_terminal_info.point_y]
246
263
  net_name = self.positive_terminal_info.net
247
264
  pos_coor_terminal[self.name] = self._pedb.get_point_terminal(self.name, net_name, point, layer)
248
- if self.positive_terminal_info.contact_radius:
249
- pos_coor_terminal[self.name].contact_radius = self.positive_terminal_info.contact_radius
250
265
 
266
+ elif pos_type == "pin":
267
+ pins = {pos_value: self._pedb.components.instances[self.reference_designator].pins[pos_value]}
268
+ if self.positive_terminal_info.contact_type in ["quad", "inline"]:
269
+ for _, pin in pins.items():
270
+ contact_type = self.positive_terminal_info.contact_type
271
+ radius = self.positive_terminal_info.contact_radius
272
+ num_of_contact = self.positive_terminal_info.num_of_contact
273
+ virtual_pins = self._create_virtual_pins_on_pin(pin, contact_type, radius, num_of_contact)
274
+ pos_objs.update(virtual_pins)
275
+ self._elem_num = len(pos_objs)
276
+ else:
277
+ pos_objs.update(pins)
251
278
  elif pos_type == "pin_group":
279
+ pins = self._get_pins(pos_type, pos_value)
252
280
  if self.distributed:
253
- pins = self._get_pins(pos_type, pos_value)
254
281
  pos_objs.update(pins)
255
282
  self._elem_num = len(pos_objs)
283
+ elif self.positive_terminal_info.contact_type in ["quad", "inline"]:
284
+ for _, pin in pins.items():
285
+ contact_type = self.positive_terminal_info.contact_type
286
+ radius = self.positive_terminal_info.contact_radius
287
+ num_of_contact = self.positive_terminal_info.num_of_contact
288
+ virtual_pins = self._create_virtual_pins_on_pin(pin, contact_type, radius, num_of_contact)
289
+ pos_objs.update(virtual_pins)
290
+ self._elem_num = len(pos_objs)
256
291
  else:
257
292
  pos_objs[pos_value] = self._pedb.siwave.pin_groups[pos_value]
258
293
  elif pos_type == "net":
294
+ pins = self._get_pins(pos_type, pos_value)
259
295
  if self.distributed:
260
- pins = self._get_pins(pos_type, pos_value)
261
296
  pos_objs.update(pins)
262
297
  self._elem_num = len(pos_objs)
298
+ elif self.positive_terminal_info.contact_type in ["quad", "inline"]:
299
+ for _, pin in pins.items():
300
+ contact_type = self.positive_terminal_info.contact_type
301
+ radius = self.positive_terminal_info.contact_radius
302
+ num_of_contact = self.positive_terminal_info.num_of_contact
303
+ virtual_pins = self._create_virtual_pins_on_pin(pin, contact_type, radius, num_of_contact)
304
+ pos_objs.update(virtual_pins)
305
+ self._elem_num = len(pos_objs)
263
306
  else:
264
- pins = self._get_pins(pos_type, pos_value)
265
307
  # create pin group
266
308
  neg_obj = self._create_pin_group(pins)
267
309
  pos_objs.update(neg_obj)
268
- elif pos_type == "pin":
269
- pins = {pos_value: self._pedb.components.instances[self.reference_designator].pins[pos_value]}
270
- pos_objs.update(pins)
271
310
  else:
272
311
  raise Exception(f"Wrong positive terminal type {pos_type}.")
273
312
 
274
313
  self.pos_terminals = {i: j.create_terminal(i) for i, j in pos_objs.items()}
275
314
  self.pos_terminals.update(pos_coor_terminal)
276
315
 
316
+ # Collect all negative terminals
277
317
  self.neg_terminal = None
278
318
  if self.negative_terminal_info:
279
319
  neg_type, neg_value = self.negative_terminal_info.type, self.negative_terminal_info.value
@@ -330,6 +370,66 @@ class CfgCircuitElement(CfgBase):
330
370
  pins.update({f"{self.reference_designator}_{terminal_value[0]}_{i}": j for i, j in temp.items()})
331
371
  return pins
332
372
 
373
+ def _create_virtual_pins_on_pin(self, pin, contact_type, radius, num_of_contact=4):
374
+ component = pin.component
375
+ placement_layer = component.placement_layer
376
+ pos_x, pos_y = pin.position
377
+ comp_rotation = self._pedb.edb_value(component.rotation).ToDouble() % 3.141592653589793
378
+
379
+ pad = pin.definition.pad_by_layer[placement_layer]
380
+ if pad.shape.lower() in ["rectangle", "oval"]:
381
+ width, height = pad.parameters_values[0:2]
382
+ elif pad.shape.lower() == "nogeometry":
383
+ polygon_data = pad.polygon_data
384
+ if polygon_data:
385
+ p1, p2 = polygon_data.bounding_box
386
+ width = p2[0] - p1[0]
387
+ height = p2[1] - p1[1]
388
+ else:
389
+ raise AttributeError(f"Unsupported pad shape {pad.shape.lower()}")
390
+ else: # pragma no cover
391
+ raise AttributeError(f"Unsupported pad shape {pad.shape.lower()}")
392
+
393
+ positions = []
394
+ if contact_type.lower() == "inline":
395
+ if width > height:
396
+ offset = (width - radius * 2) / (num_of_contact - 1)
397
+ else:
398
+ offset = (height - radius * 2) / (num_of_contact - 1)
399
+
400
+ start_pos = (num_of_contact - 1) / 2
401
+ offset = [offset * i for i in np.arange(start_pos * -1, start_pos + 1)]
402
+
403
+ if (width > height and comp_rotation == 0) or (width < height and comp_rotation != 0):
404
+ positions.extend(list(zip(offset, [0] * num_of_contact)))
405
+ else:
406
+ positions.extend(list(zip([0] * num_of_contact, offset)))
407
+ else: # quad
408
+ x_offset = width / 2 - radius if comp_rotation == 0 else height / 2 - radius
409
+ y_offset = height / 2 - radius if comp_rotation == 0 else width / 2 - radius
410
+
411
+ for x, y in [[1, 1], [-1, 1], [1, -1], [-1, -1]]:
412
+ positions.append([x_offset * x, y_offset * y])
413
+
414
+ pdef_name = f"{self.name}_{pin.pin_number}"
415
+ self._pedb.padstacks.create(padstackname=pdef_name, has_hole=False, paddiam=radius * 2, antipaddiam=0)
416
+ instances = {}
417
+ for idx, xy in enumerate(positions):
418
+ x = xy[0] + pos_x
419
+ y = xy[1] + pos_y
420
+ pin_name = f"{pdef_name}_{idx}"
421
+ p_inst = self._pedb.padstacks.place(
422
+ position=[x, y],
423
+ definition_name=pdef_name,
424
+ net_name=pin.net_name,
425
+ via_name=pin_name,
426
+ fromlayer=placement_layer,
427
+ tolayer=placement_layer,
428
+ is_pin=True,
429
+ )
430
+ instances[pin_name] = p_inst
431
+ return instances
432
+
333
433
  def _create_pin_group(self, pins, is_ref=False):
334
434
  if is_ref:
335
435
  pg_name = f"pg_{self.name}_{self.reference_designator}_ref"
@@ -350,7 +450,7 @@ class CfgPort(CfgCircuitElement):
350
450
 
351
451
  def set_parameters_to_edb(self):
352
452
  """Create port."""
353
- self._create_terminals()
453
+ self.create_terminals()
354
454
  is_circuit_port = True if self.type == "circuit" else False
355
455
  circuit_elements = []
356
456
  for name, j in self.pos_terminals.items():
@@ -382,52 +482,66 @@ class CfgSource(CfgCircuitElement):
382
482
  super().__init__(pedb, **kwargs)
383
483
 
384
484
  self.magnitude = kwargs.get("magnitude", 0.001)
385
- self.equipotential = kwargs.get("equipotential", False)
386
485
 
387
486
  def set_parameters_to_edb(self):
388
487
  """Create sources."""
389
- self._create_terminals()
488
+ self.create_terminals()
390
489
  # is_circuit_port = True if self.type == "circuit" else False
391
490
  circuit_elements = []
392
- method = self._pedb.create_current_source if self.type == "current" else self._pedb.create_voltage_source
491
+ create_xxx_source = (
492
+ self._pedb.create_current_source if self.type == "current" else self._pedb.create_voltage_source
493
+ )
393
494
  for name, j in self.pos_terminals.items():
394
495
  if isinstance(self.neg_terminal, dict):
395
- elem = method(j, self.neg_terminal[name])
496
+ elem = create_xxx_source(j, self.neg_terminal[name])
396
497
  else:
397
- elem = method(j, self.neg_terminal)
398
- if not self.distributed:
498
+ elem = create_xxx_source(j, self.neg_terminal)
499
+ if self._elem_num == 1:
399
500
  elem.name = self.name
400
501
  elem.magnitude = self.magnitude
401
502
  else:
402
- elem.name = f"{self.name}_{elem.name}"
503
+ elem.name = name
403
504
  elem.magnitude = self.magnitude / self._elem_num
505
+ elem = self._pedb.terminals[elem.name]
404
506
  circuit_elements.append(elem)
405
- for terminal in circuit_elements:
406
- if self.equipotential:
407
- terms = [terminal, terminal.ref_terminal] if terminal.ref_terminal else [terminal]
408
- for t in terms:
409
- pads = []
410
- if t.terminal_type == "PadstackInstanceTerminal":
411
- pads.append(t.reference_object)
412
- t._edb_object.dcir_equipotential_region = True
413
- elif t.terminal_type == "PinGroupTerminal":
414
- name = t._edb_object.GetPinGroup().GetName()
415
- pg = self._pedb.siwave.pin_groups[name]
416
- pads.extend([i for _, i in pg.pins.items()])
417
- elif t.terminal_type == "PointTerminal":
418
- temp = [i for i in self._pedb.layout.terminals if i.name == t.name][0]
419
- if not temp.is_reference_terminal:
420
- radius = self.positive_terminal_info.contact_radius
421
- else:
422
- radius = self.negative_terminal_info.contact_radius
423
- if radius is not None:
424
- prim = self._pedb.modeler.create_circle(
425
- temp.layer.name, temp.location[0], temp.location[1], radius, temp.net_name
426
- )
427
- prim.dcir_equipotential_region = True
428
507
 
508
+ for terminal in circuit_elements:
509
+ # Get reference terminal
510
+ terms = [terminal, terminal.ref_terminal] if terminal.ref_terminal else [terminal]
511
+ for t in terms:
512
+ if not t.is_reference_terminal:
513
+ radius = self.positive_terminal_info.contact_radius
514
+ contact_type = self.positive_terminal_info.contact_type
515
+ else:
516
+ radius = self.negative_terminal_info.contact_radius
517
+ contact_type = self.negative_terminal_info.contact_type
518
+ if t.terminal_type == "PointTerminal":
519
+ temp = [i for i in self._pedb.layout.terminals if i.name == t.name][0]
520
+ prim = self._pedb.modeler.create_circle(
521
+ temp.layer.name, temp.location[0], temp.location[1], radius, temp.net_name
522
+ )
523
+ prim.dcir_equipotential_region = True
524
+ continue
525
+ elif contact_type.lower() == "default":
526
+ continue
527
+ elif t.terminal_type == "PadstackInstanceTerminal":
528
+ if contact_type.lower() in ["full", "quad", "inline"]:
529
+ t.padstack_instance._set_equipotential()
530
+ elif contact_type.lower() == "center":
531
+ t.padstack_instance._set_equipotential(contact_radius=radius)
532
+ elif t.terminal_type == "PinGroupTerminal":
533
+ name = t._edb_object.GetPinGroup().GetName()
534
+ pg = self._pedb.siwave.pin_groups[name]
535
+ pads = [i for _, i in pg.pins.items()]
429
536
  for i in pads:
430
- i._set_equipotential()
537
+ if contact_type.lower() in ["full", "quad", "inline"]:
538
+ i._set_equipotential()
539
+ elif contact_type.lower() == "center":
540
+ i._set_equipotential(contact_radius=radius)
541
+ elif t.is_reference_terminal:
542
+ continue
543
+ else:
544
+ raise AttributeError("Unsupported terminal type.")
431
545
 
432
546
  return circuit_elements
433
547
 
@@ -442,6 +556,41 @@ class CfgSource(CfgCircuitElement):
442
556
  }
443
557
 
444
558
 
559
+ class CfgProbe(CfgCircuitElement):
560
+ class Common:
561
+ def __init__(self, parent):
562
+ self.parent = parent
563
+ self.pedb = parent._pedb
564
+
565
+ def set_parameters_to_edb(self):
566
+ self.parent.create_terminals()
567
+ circuit_elements = []
568
+ for name, j in self.parent.pos_terminals.items():
569
+ if isinstance(self.parent.neg_terminal, dict):
570
+ elem = self.pedb.create_voltage_probe(j, self.parent.neg_terminal[name])
571
+ else:
572
+ elem = self.pedb.create_voltage_probe(j, self.parent.neg_terminal)
573
+ elem.name = self.parent.name
574
+ circuit_elements.append(elem)
575
+ return circuit_elements
576
+
577
+ class Grpc(Common):
578
+ def __init__(self, parent):
579
+ super().__init__(parent)
580
+
581
+ class DotNet(Grpc):
582
+ def __init__(self, parent):
583
+ super().__init__(parent)
584
+
585
+ def __init__(self, pedb, **kwargs):
586
+ kwargs["type"] = "probe"
587
+ super().__init__(pedb, **kwargs)
588
+ if os.environ["PYEDB_USE_DOTNET"] == "0":
589
+ self.api = self.Grpc(self)
590
+ else:
591
+ self.api = self.DotNet(self)
592
+
593
+
445
594
  class CfgWavePort:
446
595
  def __init__(self, pedb, **kwargs):
447
596
  self._pedb = pedb
@@ -140,11 +140,11 @@ class Configuration:
140
140
  temp = []
141
141
  for _, pdef in pedb_defs.items():
142
142
  cfg_def = CfgPadstackDefinition(self._pedb, pdef)
143
- cfg_def.retrieve_parameters_from_edb()
143
+ cfg_def.api.retrieve_parameters_from_edb()
144
144
  temp.append(cfg_def)
145
145
  self.cfg_data.stackup.apply()
146
146
  for cfg_pdef in temp:
147
- cfg_pdef.set_parameters_to_edb()
147
+ cfg_pdef.api.set_parameters_to_edb()
148
148
  else:
149
149
  self.cfg_data.stackup.apply()
150
150
 
@@ -171,6 +171,9 @@ class Configuration:
171
171
  # Configure ports
172
172
  self.cfg_data.ports.apply()
173
173
 
174
+ # Configure probes
175
+ self.cfg_data.probes.apply()
176
+
174
177
  return True
175
178
 
176
179
  def _load_stackup(self):
pyedb/dotnet/edb.py CHANGED
@@ -4106,7 +4106,7 @@ class Edb(Database):
4106
4106
  term.boundary_type = "kVoltageSource"
4107
4107
 
4108
4108
  ref_term = Terminal(self, ref_terminal._edb_object)
4109
- ref_term.boundary_type = "kVoltageProbe"
4109
+ ref_term.boundary_type = "kVoltageSource"
4110
4110
 
4111
4111
  term.ref_terminal = ref_terminal
4112
4112
  return self.sources[term.name]
@@ -66,7 +66,7 @@ def primitive_cast(pedb, edb_object):
66
66
  elif edb_object.GetPrimitiveType().ToString() == "Bondwire":
67
67
  return Bondwire(pedb, edb_object)
68
68
  elif edb_object.GetPrimitiveType().ToString() == "Text":
69
- return EdbText(edb_object, pedb)
69
+ return EdbText(pedb, edb_object)
70
70
  elif edb_object.GetPrimitiveType().ToString() == "PrimitivePlugin":
71
71
  return
72
72
  elif edb_object.GetPrimitiveType().ToString() == "Path3D":
@@ -152,7 +152,7 @@ class Layout(ObjBase):
152
152
  is_pins : bool, optional
153
153
  True for pins, false for vias (default).
154
154
  """
155
- self._edb_object.ConvertPrimitivesToVias(convert_py_list_to_net_list(primitives), is_pins)
155
+ self._edb_object.ConvertPrimitivestoVias(convert_py_list_to_net_list(primitives), is_pins)
156
156
 
157
157
  @property
158
158
  def zone_primitives(self):
@@ -220,7 +220,7 @@ class Layout(ObjBase):
220
220
  -------
221
221
  """
222
222
 
223
- return [EDBNetsData(net, self._pedb) for net in self._edb_object.Nets]
223
+ return [EDBNetsData(net, self._pedb) for net in self._edb_object.Nets if net]
224
224
 
225
225
  @property
226
226
  def primitives(self):
@@ -230,7 +230,7 @@ class Layout(ObjBase):
230
230
  -------
231
231
  list of :class:`dotnet.edb_core.dotnet.primitive.PrimitiveDotNet` cast objects.
232
232
  """
233
- return [primitive_cast(self._pedb, p) for p in self._edb_object.Primitives]
233
+ return [primitive_cast(self._pedb, p) for p in self._edb_object.Primitives if p]
234
234
 
235
235
  @property
236
236
  def bondwires(self):
@@ -331,16 +331,32 @@ class Layout(ObjBase):
331
331
  obj = self._pedb._edb.Cell.Hierarchy.Component.FindByName(self._edb_object, value)
332
332
  return EDBComponent(self._pedb, obj) if obj is not None else None
333
333
 
334
- def find_primitive(self, layer_name: Union[str, list]) -> list:
334
+ def find_primitive(
335
+ self, layer_name: Union[str, list] = None, name: Union[str, list] = None, net_name: Union[str, list] = None
336
+ ) -> list:
335
337
  """Find a primitive objects by layer name.
336
338
 
337
339
  Parameters
338
340
  ----------
339
- layer_name : str, list
341
+ layer_name : str, list, optional
340
342
  Name of the layer.
343
+ name : str, list, optional
344
+ Name of the primitive
345
+ net_name : str, list, optional
346
+ Name of the primitive
341
347
  Returns
342
348
  -------
343
349
  list
344
350
  """
345
- layer_name = layer_name if isinstance(layer_name, list) else [layer_name]
346
- return [i for i in self.primitives if i.layer_name in layer_name]
351
+ if layer_name is not None:
352
+ layer_name = layer_name if isinstance(layer_name, list) else [layer_name]
353
+ if name is not None:
354
+ name = name if isinstance(name, list) else [name]
355
+ if net_name is not None:
356
+ net_name = net_name if isinstance(net_name, list) else [net_name]
357
+
358
+ prims = self.primitives
359
+ prims = [i for i in prims if i.aedt_name in name] if name is not None else prims
360
+ prims = [i for i in prims if i.layer_name in layer_name] if layer_name is not None else prims
361
+ prims = [i for i in prims if i.net_name in net_name] if net_name is not None else prims
362
+ return prims
@@ -59,7 +59,7 @@ class EDBNetsData(NetDotNet):
59
59
  """
60
60
  from pyedb.dotnet.edb_core.cell.layout import primitive_cast
61
61
 
62
- return [primitive_cast(self._app, i) for i in self.net_object.Primitives]
62
+ return [primitive_cast(self._app, i) for i in self.net_object.Primitives if i]
63
63
  # return [self._app.layout.find_object_by_id(i.GetId()) for i in self.net_object.Primitives]
64
64
 
65
65
  @property
@@ -34,6 +34,7 @@ from pyedb.dotnet.edb_core.general import (
34
34
  pascal_to_snake,
35
35
  snake_to_pascal,
36
36
  )
37
+ from pyedb.dotnet.edb_core.geometry.polygon_data import PolygonData
37
38
  from pyedb.generic.general_methods import generate_unique_name
38
39
  from pyedb.modeler.geometry_operators import GeometryOperators
39
40
 
@@ -155,6 +156,24 @@ class EDBPadProperties(object):
155
156
  def polygon_data(self):
156
157
  """Parameters.
157
158
 
159
+ Returns
160
+ -------
161
+ list
162
+ List of parameters.
163
+ """
164
+
165
+ flag, edb_object, _, _, _ = self._edb_padstack.GetData().GetPolygonalPadParameters(
166
+ self.layer_name, self.int_to_pad_type(self.pad_type)
167
+ )
168
+ if flag:
169
+ return PolygonData(self._edb._app, edb_object)
170
+ else: # pragma no cover
171
+ raise AttributeError("No polygon data.")
172
+
173
+ @property
174
+ def _polygon_data_dotnet(self):
175
+ """Parameters.
176
+
158
177
  Returns
159
178
  -------
160
179
  list
@@ -780,7 +799,7 @@ class EDBPadstack(object):
780
799
  pdef_data.SetHoleRange(getattr(self._edb.definition.PadstackHoleRange, snake_to_pascal(value)))
781
800
  self._padstack_def_data = pdef_data
782
801
 
783
- def convert_to_3d_microvias(self, convert_only_signal_vias=True, hole_wall_angle=15, delete_padstack_def=True):
802
+ def convert_to_3d_microvias(self, convert_only_signal_vias=True, hole_wall_angle=75, delete_padstack_def=True):
784
803
  """Convert actual padstack instance to microvias 3D Objects with a given aspect ratio.
785
804
 
786
805
  Parameters
@@ -812,22 +831,9 @@ class EDBPadstack(object):
812
831
  layer_names = [i for i in list(layers.keys())]
813
832
  if convert_only_signal_vias:
814
833
  signal_nets = [i for i in list(self._ppadstack._pedb.nets.signal_nets.keys())]
815
- topl, topz, bottoml, bottomz = self._ppadstack._pedb.stackup.limits(True)
816
- if self.via_start_layer in layers:
817
- start_elevation = layers[self.via_start_layer].lower_elevation
818
- else:
819
- start_elevation = layers[self.instances[0].start_layer].lower_elevation
820
- if self.via_stop_layer in layers:
821
- stop_elevation = layers[self.via_stop_layer].upper_elevation
822
- else:
823
- stop_elevation = layers[self.instances[0].stop_layer].upper_elevation
824
834
 
825
- diel_thick = abs(start_elevation - stop_elevation)
826
- rad1 = self.hole_properties[0] / 2 - math.tan(hole_wall_angle * diel_thick * math.pi / 180)
827
- rad2 = self.hole_properties[0] / 2
835
+ layer_count = len(self._ppadstack._pedb.stackup.signal_layers)
828
836
 
829
- if start_elevation < (topz + bottomz) / 2:
830
- rad1, rad2 = rad2, rad1
831
837
  i = 0
832
838
  for via in list(self.padstack_instances.values()):
833
839
  if convert_only_signal_vias and via.net_name in signal_nets or not convert_only_signal_vias:
@@ -863,18 +869,33 @@ class EDBPadstack(object):
863
869
  self._get_edb_value(pos[1]),
864
870
  self._get_edb_value(self.pad_by_layer[self.via_stop_layer].parameters_values[0] / 2),
865
871
  )
866
- for layer_name in layer_names:
872
+ for layer_idx, layer_name in enumerate(layer_names):
867
873
  stop = ""
868
874
  if layer_name == via.start_layer or started:
869
875
  start = layer_name
870
876
  stop = layer_names[layer_names.index(layer_name) + 1]
877
+
878
+ start_elevation = layers[start].lower_elevation
879
+ stop_elevation = layers[stop].upper_elevation
880
+ diel_thick = abs(start_elevation - stop_elevation)
881
+
882
+ rad_large = self.hole_diameter / 2
883
+ rad_small = rad_large - diel_thick * 1 / math.tan(math.radians(hole_wall_angle))
884
+
885
+ if layer_idx + 1 < layer_count / 2: # upper half of stack
886
+ rad_u = rad_large
887
+ rad_l = rad_small
888
+ else:
889
+ rad_u = rad_small
890
+ rad_l = rad_large
891
+
871
892
  cloned_circle = self._edb.cell.primitive.circle.create(
872
893
  layout,
873
894
  start,
874
895
  via._edb_padstackinstance.GetNet(),
875
896
  self._get_edb_value(pos[0]),
876
897
  self._get_edb_value(pos[1]),
877
- self._get_edb_value(rad1),
898
+ self._get_edb_value(rad_u),
878
899
  )
879
900
  cloned_circle2 = self._edb.cell.primitive.circle.create(
880
901
  layout,
@@ -882,7 +903,7 @@ class EDBPadstack(object):
882
903
  via._edb_padstackinstance.GetNet(),
883
904
  self._get_edb_value(pos[0]),
884
905
  self._get_edb_value(pos[1]),
885
- self._get_edb_value(rad2),
906
+ self._get_edb_value(rad_l),
886
907
  )
887
908
  s3d = self._edb.cell.hierarchy._hierarchy.Structure3D.Create(
888
909
  layout, generate_unique_name("via3d_" + via.aedt_name.replace("via_", ""), n=3)
@@ -1247,10 +1268,18 @@ class EDBPadstackInstance(Primitive):
1247
1268
 
1248
1269
  return self._pedb.create_port(terminal, ref_terminal, is_circuit_port)
1249
1270
 
1250
- def _set_equipotential(self):
1271
+ def _set_equipotential(self, contact_radius=None):
1251
1272
  """Workaround solution. Remove when EDBAPI bug is fixed for dcir_equipotential_region."""
1252
1273
  pad = self.definition.pad_by_layer[self.start_layer]
1253
- if pad.shape.lower() == "circle":
1274
+
1275
+ pos_x, pos_y = self.position
1276
+
1277
+ if contact_radius is not None:
1278
+ prim = self._pedb.modeler.create_circle(pad.layer_name, pos_x, pos_y, contact_radius, self.net_name)
1279
+ prim.dcir_equipotential_region = True
1280
+ return
1281
+
1282
+ elif pad.shape.lower() == "circle":
1254
1283
  ra = self._pedb.edb_value(pad.parameters_values[0] / 2)
1255
1284
  pos = self.position
1256
1285
  prim = self._pedb.modeler.create_circle(pad.layer_name, pos[0], pos[1], ra, self.net_name)
@@ -1265,9 +1294,16 @@ class EDBPadstackInstance(Primitive):
1265
1294
  center_point=self.position,
1266
1295
  rotation=self.component.rotation,
1267
1296
  )
1268
-
1297
+ elif pad.shape.lower() == "oval":
1298
+ width, height, _ = pad.parameters_values
1299
+ prim = self._pedb.modeler.create_circle(
1300
+ pad.layer_name, self.position[0], self.position[1], height / 2, self.net_name
1301
+ )
1269
1302
  elif pad.polygon_data:
1270
- prim = self._pedb.modeler.create_polygon(pad.polygon_data, self.start_layer, net_name=self.net_name)
1303
+ prim = self._pedb.modeler.create_polygon(
1304
+ pad.polygon_data._edb_object, self.start_layer, net_name=self.net_name
1305
+ )
1306
+ prim.move(self.position)
1271
1307
  else:
1272
1308
  return
1273
1309
  prim.dcir_equipotential_region = True
@@ -1579,7 +1615,7 @@ class EDBPadstackInstance(Primitive):
1579
1615
  layer_list = []
1580
1616
  start_layer_name = start_layer.GetName()
1581
1617
  stop_layer_name = stop_layer.GetName()
1582
- for layer_name in list(self._pedb.stackup.layers.keys()):
1618
+ for layer_name in list(self._pedb.stackup.signal_layers.keys()):
1583
1619
  if started:
1584
1620
  layer_list.append(layer_name)
1585
1621
  if layer_name == stop_layer_name or layer_name == start_layer_name:
@@ -1921,7 +1957,7 @@ class EDBPadstackInstance(Primitive):
1921
1957
 
1922
1958
  pad_shape = padstack_pad.geometry_type
1923
1959
  params = padstack_pad.parameters_values
1924
- polygon_data = padstack_pad.polygon_data
1960
+ polygon_data = padstack_pad._polygon_data_dotnet
1925
1961
 
1926
1962
  def _rotate(p):
1927
1963
  x = p[0] * math.cos(rotation) - p[1] * math.sin(rotation)
@@ -2038,8 +2074,8 @@ class EDBPadstackInstance(Primitive):
2038
2074
  # Polygon
2039
2075
  points = []
2040
2076
  i = 0
2041
- while i < polygon_data.edb_api.Count:
2042
- point = polygon_data.edb_api.GetPoint(i)
2077
+ while i < polygon_data._edb_object.Count:
2078
+ point = polygon_data._edb_object.GetPoint(i)
2043
2079
  i += 1
2044
2080
  if point.IsArc():
2045
2081
  continue
@@ -19,6 +19,7 @@
19
19
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
+ from typing import Union
22
23
 
23
24
  from pyedb.dotnet.edb_core.general import convert_py_list_to_net_list
24
25
  from pyedb.dotnet.edb_core.geometry.point_data import PointData
@@ -128,3 +129,7 @@ class PolygonData:
128
129
  arcs = convert_py_list_to_net_list(arcs)
129
130
  poly = self._edb_object.CreateFromArcs(arcs, flag)
130
131
  return PolygonData(self._pedb, poly)
132
+
133
+ def point_in_polygon(self, x: Union[str, float], y: Union[str, float]) -> bool:
134
+ """Determines whether a point is inside the polygon."""
135
+ return self._edb_object.PointInPolygon(self._pedb.point_data(x, y))
@@ -661,8 +661,7 @@ class Modeler(object):
661
661
  else:
662
662
  polygonData = main_shape
663
663
  if not polygonData or polygonData.IsNull():
664
- self._logger.error("Failed to create main shape polygon data")
665
- return False
664
+ raise RuntimeError("Failed to create main shape polygon data")
666
665
  for void in voids:
667
666
  if isinstance(void, list):
668
667
  void = self.Shape("polygon", points=void)