pyedb 0.54.0__py3-none-any.whl → 0.56.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 (105) hide show
  1. pyedb/__init__.py +1 -8
  2. pyedb/configuration/cfg_boundaries.py +69 -151
  3. pyedb/configuration/cfg_components.py +201 -460
  4. pyedb/configuration/cfg_data.py +4 -2
  5. pyedb/configuration/cfg_general.py +13 -36
  6. pyedb/configuration/cfg_modeler.py +2 -1
  7. pyedb/configuration/cfg_nets.py +21 -35
  8. pyedb/configuration/cfg_operations.py +22 -151
  9. pyedb/configuration/cfg_package_definition.py +56 -112
  10. pyedb/configuration/cfg_padstacks.py +292 -688
  11. pyedb/configuration/cfg_pin_groups.py +32 -79
  12. pyedb/configuration/cfg_ports_sources.py +19 -6
  13. pyedb/configuration/cfg_s_parameter_models.py +67 -172
  14. pyedb/configuration/cfg_setup.py +102 -295
  15. pyedb/configuration/configuration.py +64 -5
  16. pyedb/dotnet/database/Variables.py +26 -19
  17. pyedb/dotnet/database/cell/connectable.py +38 -9
  18. pyedb/dotnet/database/cell/hierarchy/component.py +28 -28
  19. pyedb/dotnet/database/cell/hierarchy/model.py +1 -1
  20. pyedb/dotnet/database/cell/layout.py +63 -2
  21. pyedb/dotnet/database/cell/layout_obj.py +2 -2
  22. pyedb/dotnet/database/cell/primitive/path.py +6 -8
  23. pyedb/dotnet/database/cell/primitive/primitive.py +3 -24
  24. pyedb/dotnet/database/cell/terminal/edge_terminal.py +2 -2
  25. pyedb/dotnet/database/cell/terminal/padstack_instance_terminal.py +1 -1
  26. pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
  27. pyedb/dotnet/database/cell/terminal/point_terminal.py +1 -1
  28. pyedb/dotnet/database/cell/terminal/terminal.py +24 -24
  29. pyedb/dotnet/database/cell/voltage_regulator.py +0 -21
  30. pyedb/dotnet/database/components.py +137 -124
  31. pyedb/dotnet/database/definition/component_def.py +4 -4
  32. pyedb/dotnet/database/definition/component_model.py +1 -1
  33. pyedb/dotnet/database/definition/package_def.py +2 -3
  34. pyedb/dotnet/database/dotnet/database.py +3 -199
  35. pyedb/dotnet/database/dotnet/primitive.py +3 -3
  36. pyedb/dotnet/database/edb_data/control_file.py +5 -5
  37. pyedb/dotnet/database/edb_data/hfss_extent_info.py +6 -6
  38. pyedb/dotnet/database/edb_data/layer_data.py +23 -23
  39. pyedb/dotnet/database/edb_data/padstacks_data.py +63 -88
  40. pyedb/dotnet/database/edb_data/primitives_data.py +5 -5
  41. pyedb/dotnet/database/edb_data/sources.py +6 -6
  42. pyedb/dotnet/database/edb_data/variables.py +1 -1
  43. pyedb/dotnet/database/geometry/point_data.py +14 -10
  44. pyedb/dotnet/database/geometry/polygon_data.py +3 -3
  45. pyedb/dotnet/database/hfss.py +46 -48
  46. pyedb/dotnet/database/layout_validation.py +14 -11
  47. pyedb/dotnet/database/materials.py +10 -11
  48. pyedb/dotnet/database/modeler.py +97 -91
  49. pyedb/dotnet/database/nets.py +19 -22
  50. pyedb/dotnet/database/padstack.py +171 -83
  51. pyedb/dotnet/database/siwave.py +42 -42
  52. pyedb/dotnet/database/stackup.py +140 -72
  53. pyedb/dotnet/database/utilities/heatsink.py +4 -4
  54. pyedb/dotnet/database/utilities/obj_base.py +2 -2
  55. pyedb/dotnet/database/utilities/simulation_setup.py +2 -2
  56. pyedb/dotnet/database/utilities/value.py +16 -16
  57. pyedb/dotnet/edb.py +230 -152
  58. pyedb/edb_logger.py +12 -27
  59. pyedb/extensions/create_cell_array.py +394 -0
  60. pyedb/extensions/via_design_backend.py +6 -3
  61. pyedb/generic/data_handlers.py +6 -7
  62. pyedb/generic/design_types.py +81 -30
  63. pyedb/generic/filesystem.py +5 -2
  64. pyedb/generic/general_methods.py +2 -122
  65. pyedb/generic/process.py +44 -108
  66. pyedb/generic/settings.py +79 -19
  67. pyedb/grpc/database/components.py +26 -4
  68. pyedb/grpc/database/control_file.py +5 -5
  69. pyedb/grpc/database/definition/materials.py +1 -1
  70. pyedb/grpc/database/definition/package_def.py +3 -3
  71. pyedb/grpc/database/definition/padstack_def.py +53 -0
  72. pyedb/grpc/database/geometry/polygon_data.py +1 -1
  73. pyedb/grpc/database/layout/layout.py +81 -5
  74. pyedb/grpc/database/layout_validation.py +5 -5
  75. pyedb/grpc/database/modeler.py +24 -16
  76. pyedb/grpc/database/net/net.py +15 -14
  77. pyedb/grpc/database/nets.py +70 -0
  78. pyedb/grpc/database/padstacks.py +122 -17
  79. pyedb/grpc/database/primitive/padstack_instance.py +175 -7
  80. pyedb/grpc/database/primitive/polygon.py +2 -2
  81. pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +3 -2
  82. pyedb/grpc/database/siwave.py +1 -1
  83. pyedb/grpc/database/source_excitations.py +12 -5
  84. pyedb/grpc/database/stackup.py +1 -1
  85. pyedb/grpc/database/terminal/bundle_terminal.py +1 -1
  86. pyedb/grpc/database/terminal/padstack_instance_terminal.py +1 -1
  87. pyedb/grpc/database/terminal/pingroup_terminal.py +1 -1
  88. pyedb/grpc/database/utility/value.py +1 -0
  89. pyedb/grpc/database/utility/xml_control_file.py +5 -5
  90. pyedb/grpc/edb.py +80 -30
  91. pyedb/grpc/edb_init.py +3 -3
  92. pyedb/grpc/rpc_session.py +14 -13
  93. pyedb/libraries/common.py +366 -0
  94. pyedb/libraries/rf_libraries/base_functions.py +1358 -0
  95. pyedb/libraries/rf_libraries/planar_antennas.py +628 -0
  96. pyedb/misc/decorators.py +61 -0
  97. pyedb/misc/misc.py +0 -13
  98. pyedb/modeler/geometry_operators.py +6 -6
  99. pyedb/siwave.py +6 -8
  100. pyedb/siwave_core/__init__.py +0 -0
  101. pyedb/siwave_core/cpa/__init__.py +0 -0
  102. {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/METADATA +1 -2
  103. {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/RECORD +105 -98
  104. {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/WHEEL +0 -0
  105. {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/licenses/LICENSE +0 -0
pyedb/edb_logger.py CHANGED
@@ -29,8 +29,6 @@ import sys
29
29
  import tempfile
30
30
  import time
31
31
 
32
- from pyedb.generic.settings import settings
33
-
34
32
 
35
33
  class Msg:
36
34
  (INFO, WARNING, ERROR, FATAL) = range(4)
@@ -91,14 +89,17 @@ class EdbLogger(object):
91
89
  Whether to write log messages to stdout. The default is ``False``.
92
90
  """
93
91
 
94
- def __init__(self, level=logging.DEBUG, filename=None, to_stdout=False):
92
+ log_file = ""
93
+
94
+ def __init__(self, level=logging.DEBUG, filename=None, to_stdout=False, settings=None):
95
+ self.settings = settings
95
96
  self._std_out_handler = None
96
97
  self._files_handlers = []
97
98
  self.level = level
98
99
  self.filename = filename or settings.logger_file_path
99
100
  settings.logger_file_path = self.filename
100
101
 
101
- self._global = logging.getLogger("Global")
102
+ self._global = logging.getLogger("Edb")
102
103
  if not settings.enable_logger:
103
104
  self._global.addHandler(logging.NullHandler())
104
105
  return
@@ -123,6 +124,7 @@ class EdbLogger(object):
123
124
  encoding="utf-8",
124
125
  delay=0,
125
126
  )
127
+ self.log_file = log_file
126
128
  my_handler.setFormatter(self.formatter)
127
129
  my_handler.setLevel(self.level)
128
130
  if not global_handler and settings.global_log_file_name:
@@ -171,11 +173,11 @@ class EdbLogger(object):
171
173
 
172
174
  @property
173
175
  def _log_on_file(self):
174
- return settings.enable_file_logs
176
+ return self.settings.enable_file_logs
175
177
 
176
178
  @_log_on_file.setter
177
179
  def _log_on_file(self, val):
178
- settings.enable_file_logs = val
180
+ self.settings.enable_file_logs = val
179
181
 
180
182
  @property
181
183
  def logger(self):
@@ -334,7 +336,7 @@ class EdbLogger(object):
334
336
 
335
337
  def info(self, msg, *args, **kwargs):
336
338
  """Write an info message to the global logger."""
337
- if not settings.enable_logger:
339
+ if not self.settings.enable_logger:
338
340
  return
339
341
  if args:
340
342
  try:
@@ -348,7 +350,7 @@ class EdbLogger(object):
348
350
  def info_timer(self, msg, start_time=None, *args, **kwargs):
349
351
  """Write an info message to the global logger with elapsed time.
350
352
  Message will have an appendix of type Elapsed time: time."""
351
- if not settings.enable_logger:
353
+ if not self.settings.enable_logger:
352
354
  return
353
355
  if not start_time:
354
356
  start_time = self._timer
@@ -373,7 +375,7 @@ class EdbLogger(object):
373
375
 
374
376
  def warning(self, msg, *args, **kwargs):
375
377
  """Write a warning message to the global logger."""
376
- if not settings.enable_logger:
378
+ if not self.settings.enable_logger:
377
379
  return
378
380
  if args:
379
381
  try:
@@ -397,7 +399,7 @@ class EdbLogger(object):
397
399
 
398
400
  def debug(self, msg, *args, **kwargs):
399
401
  """Write a debug message to the global logger."""
400
- if not settings.enable_debug_logger or not settings.enable_logger:
402
+ if not self.settings.enable_debug_logger or not self.settings.enable_logger:
401
403
  return
402
404
  if args:
403
405
  try:
@@ -413,20 +415,3 @@ class EdbLogger(object):
413
415
  """Global logger."""
414
416
  self._global = logging.getLogger("Global")
415
417
  return self._global
416
-
417
-
418
- logger = logging.getLogger("Global")
419
- if any("aedt_logger" in str(i) for i in logger.filters):
420
- from ansys.aedt.core.generic.settings import settings as pyaedt_settings
421
-
422
- from pyedb.generic.settings import settings as pyaedb_settings
423
-
424
- pyedb_logger = pyaedt_settings.logger
425
- pyaedb_settings.use_pyaedt_log = True
426
- pyaedb_settings.logger = pyedb_logger
427
-
428
- else:
429
- pyedb_logger = EdbLogger(to_stdout=settings.enable_screen_logs)
430
- from pyedb.generic.settings import settings as pyaedb_settings
431
-
432
- pyaedb_settings.logger = pyedb_logger
@@ -0,0 +1,394 @@
1
+ # Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: MIT
3
+ #
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ """
24
+ This module contains the array building feature from unit cell.
25
+ """
26
+ import itertools
27
+ from typing import Optional, Union
28
+
29
+ from pyedb import Edb
30
+
31
+
32
+ # ----------------------
33
+ # Public façade function
34
+ # ----------------------
35
+ def create_array_from_unit_cell(
36
+ edb: Edb,
37
+ x_number: int = 2,
38
+ y_number: int = 2,
39
+ offset_x: Optional[Union[int, float]] = None,
40
+ offset_y: Optional[Union[int, float]] = None,
41
+ ) -> bool:
42
+ """
43
+ Create a 2-D rectangular array from the current EDB unit cell.
44
+
45
+ The function duplicates every primitive (polygon, rectangle, circle), path,
46
+ padstack via, and component found in the active layout and places copies on
47
+ a regular grid defined by *offset_x* and *offset_y*. If the offsets are
48
+ omitted they are automatically derived from the bounding box of the first
49
+ primitive found on the layer called **outline** (case-insensitive).
50
+
51
+ Parameters
52
+ ----------
53
+ edb : pyedb.Edb
54
+ An open Edb instance whose active layout is used as the unit cell.
55
+ x_number : int, optional
56
+ Number of columns (X-direction). Must be > 0. Defaults to 2.
57
+ y_number : int, optional
58
+ Number of rows (Y-direction). Must be > 0. Defaults to 2.
59
+ offset_x : int | float | None, optional
60
+ Horizontal pitch (distance between cell origins). When *None* the
61
+ value is derived from the outline geometry.
62
+ offset_y : int | float | None, optional
63
+ Vertical pitch (distance between cell origins). When *None* the
64
+ value is derived from the outline geometry.
65
+
66
+ Returns
67
+ -------
68
+ bool
69
+ ``True`` if the operation completed successfully.
70
+
71
+ Raises
72
+ ------
73
+ ValueError
74
+ If *x_number* or *y_number* are non-positive.
75
+ RuntimeError
76
+ If no outline is found and the offsets were not supplied, or if the
77
+ outline is not a supported type (polygon/rectangle).
78
+
79
+ Notes
80
+ -----
81
+ The routine is technology-agnostic; it delegates all EDB-specific calls to
82
+ small adapter classes that handle either the **gRPC** or **.NET** back-end
83
+ transparently.
84
+
85
+ Examples
86
+ --------
87
+ >>> from pyedb import Edb
88
+ >>> edb = Edb("unit_cell.aedb")
89
+ >>> create_array_from_unit_cell(edb, x_number=4, y_number=3)
90
+ True
91
+ """
92
+ if edb.grpc:
93
+ adapter = _GrpcAdapter(edb)
94
+ else:
95
+ adapter = _DotNetAdapter(edb)
96
+ return __create_array_from_unit_cell_impl(edb, adapter, x_number, y_number, offset_x, offset_y)
97
+
98
+
99
+ # ------------------------------------------------------------------
100
+ # Implementation (technology-agnostic)
101
+ # ------------------------------------------------------------------
102
+ def __create_array_from_unit_cell_impl(
103
+ edb: Edb,
104
+ adapter: "_BaseAdapter",
105
+ x_number: int,
106
+ y_number: int,
107
+ offset_x: Optional[Union[int, float]],
108
+ offset_y: Optional[Union[int, float]],
109
+ ) -> bool:
110
+ """
111
+ Inner worker that performs the actual replication.
112
+
113
+ Parameters
114
+ ----------
115
+ edb : pyedb.Edb
116
+ Edb instance (already validated by the façade).
117
+ adapter : _BaseAdapter
118
+ Technology-specific adapter (gRPC or .NET).
119
+ x_number : int
120
+ Number of columns.
121
+ y_number : int
122
+ Number of rows.
123
+ offset_x : float
124
+ Absolute pitch in X (always resolved by the caller).
125
+ offset_y : float
126
+ Absolute pitch in Y (always resolved by the caller).
127
+
128
+ Returns
129
+ -------
130
+ bool
131
+ ``True`` when finished.
132
+ """
133
+ # ---------- Sanity & auto-pitch detection ----------
134
+ if x_number <= 0 or y_number <= 0:
135
+ raise ValueError("x_number and y_number must be positive integers")
136
+
137
+ if offset_x is None or offset_y is None:
138
+ edb.logger.info("Auto-detecting outline extents")
139
+ outline_prims = [p for p in edb.modeler.primitives if p.layer_name.lower() == "outline"]
140
+ if not outline_prims:
141
+ raise RuntimeError("No outline found. Provide offset_x / offset_y or add an 'Outline' layer primitive.")
142
+ outline = outline_prims[0]
143
+ if not adapter.is_supported_outline(outline):
144
+ raise RuntimeError("Outline primitive is not a polygon/rectangle. Provide offset_x / offset_y.")
145
+ offset_x, offset_y = adapter.pitch_from_outline(outline)
146
+
147
+ # ---------- Collect everything we have to replicate ----------
148
+ primitives = [p for p in edb.modeler.primitives if adapter.is_primitive_to_copy(p)]
149
+ paths = list(edb.modeler.paths)
150
+ vias = list(edb.padstacks.vias.values())
151
+ components = list(edb.components.instances.values())
152
+
153
+ # ---------- Replication loops ----------
154
+ edb.logger.info(f"Starting array replication {x_number}×{y_number}")
155
+ for i, j in itertools.product(range(x_number), range(y_number)):
156
+ if i == 0 and j == 0:
157
+ continue # original already exists
158
+
159
+ dx = edb.value(offset_x * i)
160
+ dy = edb.value(offset_y * j)
161
+
162
+ # Primitives & voids
163
+ for prim in primitives:
164
+ new_poly = adapter.duplicate_primitive(prim, dx, dy, i, j)
165
+ for void in prim.voids:
166
+ adapter.duplicate_void(new_poly, void, dx, dy)
167
+
168
+ # Paths
169
+ for path in paths:
170
+ adapter.duplicate_path(path, dx, dy, i, j)
171
+
172
+ # Stand-alone vias
173
+ for via in (v for v in vias if not v.component):
174
+ adapter.duplicate_standalone_via(via, dx, dy, i, j)
175
+
176
+ # Components
177
+ for comp in components:
178
+ adapter.duplicate_component(comp, dx, dy, i, j)
179
+
180
+ edb.logger.info("Array replication finished successfully")
181
+ return True
182
+
183
+
184
+ # ------------------------------------------------------------------
185
+ # Technology-specific adapters
186
+ # ------------------------------------------------------------------
187
+ class _BaseAdapter:
188
+ """Abstract adapter defining the required interface."""
189
+
190
+ def __init__(self, edb: Edb):
191
+ self.edb = edb
192
+
193
+ # ---- Outline helpers ----
194
+ def is_supported_outline(self, outline) -> bool:
195
+ """Return True when *outline* is a primitive type from which pitch can be inferred."""
196
+ raise NotImplementedError
197
+
198
+ def pitch_from_outline(self, outline) -> tuple[float, float]:
199
+ """
200
+ Compute the (offset_x, offset_y) pitch from the bounding box of *outline*.
201
+
202
+ Returns
203
+ -------
204
+ tuple[float, float]
205
+ (width, height) of the outline primitive in database units.
206
+ """
207
+ raise NotImplementedError
208
+
209
+ # ---- Duplication helpers ----
210
+ def is_primitive_to_copy(self, prim) -> bool:
211
+ """Return True when *prim* is a primitive that must be duplicated."""
212
+ raise NotImplementedError
213
+
214
+ def duplicate_primitive(self, prim, dx, dy, i, j):
215
+ """Return a new primitive translated by (dx, dy)."""
216
+ raise NotImplementedError
217
+
218
+ def duplicate_void(self, new_poly, void, dx, dy):
219
+ """Add a translated copy of *void* to *new_poly*."""
220
+ raise NotImplementedError
221
+
222
+ def duplicate_path(self, path, dx, dy, i, j):
223
+ """Create a translated copy of *path*."""
224
+ raise NotImplementedError
225
+
226
+ def duplicate_standalone_via(self, via, dx, dy, i, j):
227
+ """Create a translated copy of a stand-alone via."""
228
+ raise NotImplementedError
229
+
230
+ def duplicate_component(self, comp, dx, dy, i, j):
231
+ """Create a translated copy of *comp* (including its pins)."""
232
+ raise NotImplementedError
233
+
234
+
235
+ class _GrpcAdapter(_BaseAdapter):
236
+ """Adapter for the gRPC-based EDB back-end."""
237
+
238
+ def is_supported_outline(self, outline) -> bool:
239
+ return outline.type in {"polygon", "rectangle"}
240
+
241
+ def pitch_from_outline(self, outline):
242
+ bbox = outline.polygon_data.bbox()
243
+ return self.edb.value(bbox[1].x - bbox[0].x), self.edb.value(bbox[1].y - bbox[0].y)
244
+
245
+ def is_primitive_to_copy(self, prim):
246
+ return prim.type in {"polygon", "rectangle", "circle"}
247
+
248
+ def duplicate_primitive(self, prim, dx, dy, i, j):
249
+ moved_pd = prim.polygon_data.move((dx, dy))
250
+ return self.edb.modeler.create_polygon(
251
+ moved_pd,
252
+ layer_name=prim.layer.name,
253
+ net_name=prim.net.name,
254
+ )
255
+
256
+ def duplicate_void(self, new_poly, void, dx, dy):
257
+ new_poly.add_void(void.polygon_data.move((dx, dy)))
258
+
259
+ def duplicate_path(self, path, dx, dy, i, j):
260
+ moved_line = path.cast().center_line.move((dx, dy))
261
+ self.edb.modeler.create_trace(
262
+ moved_line,
263
+ width=path.width,
264
+ layer_name=path.layer.name,
265
+ net_name=path.net.name,
266
+ )
267
+
268
+ def duplicate_standalone_via(self, via, dx, dy, i, j):
269
+ from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
270
+
271
+ pos = via.position
272
+ PadstackInstance.create(
273
+ self.edb.active_layout,
274
+ net=via.net,
275
+ name=f"{via.name}_i{i}_j{j}",
276
+ padstack_def=self.edb.padstacks.definitions[via.padstack_definition],
277
+ position_x=pos[0] + dx,
278
+ position_y=pos[1] + dy,
279
+ rotation=0.0,
280
+ top_layer=self.edb.stackup.layers[via.start_layer],
281
+ bottom_layer=self.edb.stackup.layers[via.stop_layer],
282
+ )
283
+
284
+ def duplicate_component(self, comp, dx, dy, i, j):
285
+ from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
286
+
287
+ new_pins = []
288
+ for pin in comp.pins.values():
289
+ pos = pin.position
290
+ new_pin = PadstackInstance.create(
291
+ self.edb.active_layout,
292
+ net=pin.net,
293
+ name=f"{pin.name}_i{i}_j{j}",
294
+ padstack_def=self.edb.padstacks.definitions[pin.padstack_definition],
295
+ position_x=pos[0] + dx,
296
+ position_y=pos[1] + dy,
297
+ rotation=0.0,
298
+ top_layer=self.edb.stackup.layers[pin.start_layer],
299
+ bottom_layer=self.edb.stackup.layers[pin.stop_layer],
300
+ )
301
+ new_pins.append(new_pin)
302
+
303
+ if new_pins:
304
+ res = self.edb.value(comp.res_value) if hasattr(comp, "res_value") and comp.res_value else None
305
+ cap = self.edb.value(comp.cap_value) if hasattr(comp, "cap_value") and comp.cap_value else None
306
+ ind = self.edb.value(comp.ind_value) if hasattr(comp, "ind_value") and comp.ind_value else None
307
+ new_comp = self.edb.components.create(
308
+ pins=new_pins,
309
+ component_name=f"{comp.name}_array_{i}_{j}",
310
+ placement_layer=comp.placement_layer,
311
+ component_part_name=comp.part_name,
312
+ r_value=res,
313
+ l_value=ind,
314
+ c_value=cap,
315
+ )
316
+ if hasattr(comp, "component_property") and comp.component_property:
317
+ new_comp.component_property = comp.component_property
318
+
319
+
320
+ class _DotNetAdapter(_BaseAdapter):
321
+ """Adapter for the legacy .NET-based EDB back-end."""
322
+
323
+ def is_supported_outline(self, outline) -> bool:
324
+ return outline.type.lower() in {"polygon", "rectangle"}
325
+
326
+ def pitch_from_outline(self, outline):
327
+ bbox = outline.polygon_data.bounding_box
328
+ return self.edb.value(bbox[1][0] - bbox[0][0]), self.edb.value(bbox[1][1] - bbox[0][1])
329
+
330
+ def is_primitive_to_copy(self, prim):
331
+ return prim.type.lower() in {"polygon", "rectangle", "circle"}
332
+
333
+ def duplicate_primitive(self, prim, dx, dy, i, j):
334
+ from pyedb.dotnet.database.geometry.point_data import PointData
335
+
336
+ vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
337
+ moved_pd = prim.polygon_data
338
+ moved_pd._edb_object.Move(vector._edb_object)
339
+ return self.edb.modeler.create_polygon(
340
+ moved_pd,
341
+ layer_name=prim.layer.name,
342
+ net_name=prim.net.name,
343
+ )
344
+
345
+ def duplicate_void(self, new_poly, void, dx, dy):
346
+ from pyedb.dotnet.database.geometry.point_data import PointData
347
+
348
+ vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
349
+ void_polygon_data = void.polygon_data
350
+ void_polygon_data._edb_object.Move(vector._edb_object)
351
+ new_poly.add_void(void_polygon_data.points)
352
+
353
+ def duplicate_path(self, path, dx, dy, i, j):
354
+ from pyedb.dotnet.database.geometry.point_data import PointData
355
+
356
+ vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
357
+ moved_path = path._edb_object.GetCenterLine()
358
+ moved_path.Move(vector._edb_object)
359
+ moved_path = [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(moved_path.Points)]
360
+ self.edb.modeler.create_trace(
361
+ path_list=list(moved_path),
362
+ width=path.width,
363
+ layer_name=path.layer.name,
364
+ net_name=path.net.name,
365
+ )
366
+
367
+ def duplicate_standalone_via(self, via, dx, dy, i, j):
368
+ pos = via.position
369
+ self.edb.padstacks.place(
370
+ [pos[0] + dx, pos[1] + dy],
371
+ via.padstack_definition,
372
+ via_name=f"{via.aedt_name}_i{i}_j{j}",
373
+ )
374
+
375
+ def duplicate_component(self, comp, dx, dy, i, j):
376
+ new_pins = []
377
+ for pin in comp.pins.values():
378
+ pos = pin.position
379
+ new_pin = self.edb.padstacks.place(
380
+ [pos[0] + dx, pos[1] + dy],
381
+ pin.padstack_definition,
382
+ via_name=f"{pin.aedt_name}_i{i}_j{j}",
383
+ )
384
+ new_pins.append(new_pin)
385
+
386
+ if new_pins:
387
+ new_comp = self.edb.components.create(
388
+ pins=new_pins,
389
+ component_name=f"{comp.name}_array_{i}_{j}",
390
+ placement_layer=comp.placement_layer,
391
+ component_part_name=comp.part_name,
392
+ )
393
+ if hasattr(comp, "component_property"):
394
+ new_comp._edb_object.SetComponentProperty(comp.component_property)
@@ -663,7 +663,7 @@ class ViaDesignBackend:
663
663
  pitch = self.cfg["general"]["pitch"]
664
664
 
665
665
  board = Board(
666
- stackup=self.cfg["stackup"],
666
+ stackup=self.cfg["stackup"] if isinstance(self.cfg["stackup"], list) else self.cfg["stackup"]["layers"],
667
667
  padstack_defs=self.cfg["padstack_defs"],
668
668
  outline_extent=outline_extent,
669
669
  pitch=pitch,
@@ -673,9 +673,12 @@ class ViaDesignBackend:
673
673
  )
674
674
  board.populate_config(cfg_json)
675
675
 
676
+ self.output_dir.mkdir(parents=True, exist_ok=True)
677
+ with open(self.output_dir / "config.json", "w") as f:
678
+ json.dump(cfg_json, f, indent=4)
676
679
  self.app = Edb(
677
680
  edbpath=str((Path(self.output_dir) / self.cfg["title"]).with_suffix(".aedb")), edbversion=self.version
678
681
  )
679
682
  self.app.configuration.load(cfg_json, apply_file=True)
680
- self.app.save_edb()
681
- self.app.close_edb()
683
+ self.app.save()
684
+ self.app.close()
@@ -2,8 +2,8 @@
2
2
  from decimal import Decimal
3
3
  import json
4
4
  import math
5
- import random
6
5
  import re
6
+ import secrets
7
7
  import string
8
8
 
9
9
  from pyedb.generic.general_methods import settings
@@ -54,7 +54,7 @@ def random_string(length=6, only_digits=False, char_set=None): # pragma: no cov
54
54
  char_set = string.digits
55
55
  else:
56
56
  char_set = string.ascii_uppercase + string.digits
57
- random_str = "".join(random.choice(char_set) for _ in range(int(length)))
57
+ random_str = "".join(secrets.choice(char_set) for _ in range(int(length)))
58
58
  return random_str
59
59
 
60
60
 
@@ -85,9 +85,8 @@ def unique_string_list(element_list, only_string=True): # pragma: no cover
85
85
  pass
86
86
  raise Exception(error_message)
87
87
 
88
- if only_string:
89
- non_string_entries = [x for x in element_list if type(x) is not str]
90
- assert not non_string_entries, "Invalid list entries {} are not a string!".format(non_string_entries)
88
+ if only_string and any(not isinstance(x, str) for x in element_list):
89
+ raise TypeError("Invalid list entries, some elements are not of type string.")
91
90
 
92
91
  return element_list
93
92
 
@@ -104,10 +103,10 @@ def string_list(element_list): # pragma: no cover
104
103
  -------
105
104
 
106
105
  """
106
+ if not isinstance(element_list, (str, list)):
107
+ raise TypeError("Input must be a list or a string")
107
108
  if isinstance(element_list, str):
108
109
  element_list = [element_list]
109
- else:
110
- assert isinstance(element_list, str), "Input must be a list or a string"
111
110
  return element_list
112
111
 
113
112