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.
- pyedb/__init__.py +1 -8
- pyedb/configuration/cfg_boundaries.py +69 -151
- pyedb/configuration/cfg_components.py +201 -460
- pyedb/configuration/cfg_data.py +4 -2
- pyedb/configuration/cfg_general.py +13 -36
- pyedb/configuration/cfg_modeler.py +2 -1
- pyedb/configuration/cfg_nets.py +21 -35
- pyedb/configuration/cfg_operations.py +22 -151
- pyedb/configuration/cfg_package_definition.py +56 -112
- pyedb/configuration/cfg_padstacks.py +292 -688
- pyedb/configuration/cfg_pin_groups.py +32 -79
- pyedb/configuration/cfg_ports_sources.py +19 -6
- pyedb/configuration/cfg_s_parameter_models.py +67 -172
- pyedb/configuration/cfg_setup.py +102 -295
- pyedb/configuration/configuration.py +64 -5
- pyedb/dotnet/database/Variables.py +26 -19
- pyedb/dotnet/database/cell/connectable.py +38 -9
- pyedb/dotnet/database/cell/hierarchy/component.py +28 -28
- pyedb/dotnet/database/cell/hierarchy/model.py +1 -1
- pyedb/dotnet/database/cell/layout.py +63 -2
- pyedb/dotnet/database/cell/layout_obj.py +2 -2
- pyedb/dotnet/database/cell/primitive/path.py +6 -8
- pyedb/dotnet/database/cell/primitive/primitive.py +3 -24
- pyedb/dotnet/database/cell/terminal/edge_terminal.py +2 -2
- pyedb/dotnet/database/cell/terminal/padstack_instance_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/point_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/terminal.py +24 -24
- pyedb/dotnet/database/cell/voltage_regulator.py +0 -21
- pyedb/dotnet/database/components.py +137 -124
- pyedb/dotnet/database/definition/component_def.py +4 -4
- pyedb/dotnet/database/definition/component_model.py +1 -1
- pyedb/dotnet/database/definition/package_def.py +2 -3
- pyedb/dotnet/database/dotnet/database.py +3 -199
- pyedb/dotnet/database/dotnet/primitive.py +3 -3
- pyedb/dotnet/database/edb_data/control_file.py +5 -5
- pyedb/dotnet/database/edb_data/hfss_extent_info.py +6 -6
- pyedb/dotnet/database/edb_data/layer_data.py +23 -23
- pyedb/dotnet/database/edb_data/padstacks_data.py +63 -88
- pyedb/dotnet/database/edb_data/primitives_data.py +5 -5
- pyedb/dotnet/database/edb_data/sources.py +6 -6
- pyedb/dotnet/database/edb_data/variables.py +1 -1
- pyedb/dotnet/database/geometry/point_data.py +14 -10
- pyedb/dotnet/database/geometry/polygon_data.py +3 -3
- pyedb/dotnet/database/hfss.py +46 -48
- pyedb/dotnet/database/layout_validation.py +14 -11
- pyedb/dotnet/database/materials.py +10 -11
- pyedb/dotnet/database/modeler.py +97 -91
- pyedb/dotnet/database/nets.py +19 -22
- pyedb/dotnet/database/padstack.py +171 -83
- pyedb/dotnet/database/siwave.py +42 -42
- pyedb/dotnet/database/stackup.py +140 -72
- pyedb/dotnet/database/utilities/heatsink.py +4 -4
- pyedb/dotnet/database/utilities/obj_base.py +2 -2
- pyedb/dotnet/database/utilities/simulation_setup.py +2 -2
- pyedb/dotnet/database/utilities/value.py +16 -16
- pyedb/dotnet/edb.py +230 -152
- pyedb/edb_logger.py +12 -27
- pyedb/extensions/create_cell_array.py +394 -0
- pyedb/extensions/via_design_backend.py +6 -3
- pyedb/generic/data_handlers.py +6 -7
- pyedb/generic/design_types.py +81 -30
- pyedb/generic/filesystem.py +5 -2
- pyedb/generic/general_methods.py +2 -122
- pyedb/generic/process.py +44 -108
- pyedb/generic/settings.py +79 -19
- pyedb/grpc/database/components.py +26 -4
- pyedb/grpc/database/control_file.py +5 -5
- pyedb/grpc/database/definition/materials.py +1 -1
- pyedb/grpc/database/definition/package_def.py +3 -3
- pyedb/grpc/database/definition/padstack_def.py +53 -0
- pyedb/grpc/database/geometry/polygon_data.py +1 -1
- pyedb/grpc/database/layout/layout.py +81 -5
- pyedb/grpc/database/layout_validation.py +5 -5
- pyedb/grpc/database/modeler.py +24 -16
- pyedb/grpc/database/net/net.py +15 -14
- pyedb/grpc/database/nets.py +70 -0
- pyedb/grpc/database/padstacks.py +122 -17
- pyedb/grpc/database/primitive/padstack_instance.py +175 -7
- pyedb/grpc/database/primitive/polygon.py +2 -2
- pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +3 -2
- pyedb/grpc/database/siwave.py +1 -1
- pyedb/grpc/database/source_excitations.py +12 -5
- pyedb/grpc/database/stackup.py +1 -1
- pyedb/grpc/database/terminal/bundle_terminal.py +1 -1
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +1 -1
- pyedb/grpc/database/terminal/pingroup_terminal.py +1 -1
- pyedb/grpc/database/utility/value.py +1 -0
- pyedb/grpc/database/utility/xml_control_file.py +5 -5
- pyedb/grpc/edb.py +80 -30
- pyedb/grpc/edb_init.py +3 -3
- pyedb/grpc/rpc_session.py +14 -13
- pyedb/libraries/common.py +366 -0
- pyedb/libraries/rf_libraries/base_functions.py +1358 -0
- pyedb/libraries/rf_libraries/planar_antennas.py +628 -0
- pyedb/misc/decorators.py +61 -0
- pyedb/misc/misc.py +0 -13
- pyedb/modeler/geometry_operators.py +6 -6
- pyedb/siwave.py +6 -8
- pyedb/siwave_core/__init__.py +0 -0
- pyedb/siwave_core/cpa/__init__.py +0 -0
- {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/METADATA +1 -2
- {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/RECORD +105 -98
- {pyedb-0.54.0.dist-info → pyedb-0.56.0.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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("
|
|
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.
|
|
681
|
-
self.app.
|
|
683
|
+
self.app.save()
|
|
684
|
+
self.app.close()
|
pyedb/generic/data_handlers.py
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|