pyedb 0.55.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 -1
- pyedb/configuration/cfg_operations.py +2 -2
- pyedb/configuration/cfg_ports_sources.py +1 -1
- pyedb/dotnet/database/Variables.py +26 -19
- pyedb/dotnet/database/components.py +41 -36
- pyedb/dotnet/database/padstack.py +87 -0
- pyedb/dotnet/edb.py +2 -2
- pyedb/extensions/create_cell_array.py +394 -0
- pyedb/generic/data_handlers.py +6 -7
- pyedb/generic/design_types.py +16 -3
- pyedb/generic/filesystem.py +5 -2
- pyedb/generic/general_methods.py +2 -2
- pyedb/generic/settings.py +4 -0
- pyedb/grpc/database/components.py +24 -4
- pyedb/grpc/database/layout/layout.py +74 -1
- pyedb/grpc/database/layout_validation.py +2 -2
- pyedb/grpc/database/modeler.py +15 -12
- pyedb/grpc/database/padstacks.py +87 -0
- pyedb/grpc/database/primitive/polygon.py +2 -2
- pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +3 -2
- pyedb/grpc/database/source_excitations.py +10 -1
- pyedb/grpc/database/utility/value.py +1 -0
- pyedb/grpc/edb.py +9 -5
- pyedb/grpc/rpc_session.py +4 -3
- pyedb/modeler/geometry_operators.py +6 -6
- pyedb/siwave.py +4 -6
- pyedb/siwave_core/__init__.py +0 -0
- pyedb/siwave_core/cpa/__init__.py +0 -0
- {pyedb-0.55.0.dist-info → pyedb-0.56.0.dist-info}/METADATA +1 -1
- {pyedb-0.55.0.dist-info → pyedb-0.56.0.dist-info}/RECORD +32 -29
- {pyedb-0.55.0.dist-info → pyedb-0.56.0.dist-info}/WHEEL +0 -0
- {pyedb-0.55.0.dist-info → pyedb-0.56.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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)
|
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
|
|
pyedb/generic/design_types.py
CHANGED
|
@@ -57,9 +57,11 @@ def Edb(
|
|
|
57
57
|
oproject=None,
|
|
58
58
|
student_version=False,
|
|
59
59
|
use_ppe=False,
|
|
60
|
+
map_file=None,
|
|
60
61
|
technology_file=None,
|
|
61
62
|
grpc=False,
|
|
62
63
|
control_file=None,
|
|
64
|
+
layer_filter=None,
|
|
63
65
|
):
|
|
64
66
|
"""Provides the EDB application interface.
|
|
65
67
|
|
|
@@ -78,7 +80,7 @@ def Edb(
|
|
|
78
80
|
isreadonly : bool, optional
|
|
79
81
|
Whether to open EBD in read-only mode when it is
|
|
80
82
|
owned by HFSS 3D Layout. The default is ``False``.
|
|
81
|
-
|
|
83
|
+
version : str, optional
|
|
82
84
|
Version of EDB to use. The default is ``"2021.2"``.
|
|
83
85
|
isaedtowned : bool, optional
|
|
84
86
|
Whether to launch EDB from HFSS 3D Layout. The
|
|
@@ -87,10 +89,20 @@ def Edb(
|
|
|
87
89
|
Reference to the AEDT project object.
|
|
88
90
|
student_version : bool, optional
|
|
89
91
|
Whether to open the AEDT student version. The default is ``False.``
|
|
92
|
+
use_ppe : bool, optional
|
|
93
|
+
Whether to use PPE license. The default is ``False``.
|
|
90
94
|
technology_file : str, optional
|
|
91
95
|
Full path to technology file to be converted to xml before importing or xml. Supported by GDS format only.
|
|
92
96
|
grpc : bool, optional
|
|
93
97
|
Whether to enable gRPC. Default value is ``False``.
|
|
98
|
+
layer_filter: str,optional
|
|
99
|
+
Layer filter .txt file.
|
|
100
|
+
map_file : str, optional
|
|
101
|
+
Layer map .map file.
|
|
102
|
+
control_file : str, optional
|
|
103
|
+
Path to the XML file. The default is ``None``, in which case an attempt is made to find
|
|
104
|
+
the XML file in the same directory as the board file. To succeed, the XML file and board file
|
|
105
|
+
must have the same name. Only the extension differs.
|
|
94
106
|
|
|
95
107
|
Returns
|
|
96
108
|
-------
|
|
@@ -263,7 +275,6 @@ def Edb(
|
|
|
263
275
|
if grpc is False and settings.edb_dll_path is not None:
|
|
264
276
|
# Check if the user specified a .dll path
|
|
265
277
|
settings.logger.info(f"Force to use .dll from {settings.edb_dll_path} defined in settings.")
|
|
266
|
-
settings.specified_version = "unknown"
|
|
267
278
|
elif version is None:
|
|
268
279
|
if settings.specified_version is not None:
|
|
269
280
|
settings.logger.info(f"Use {settings.specified_version} defined in settings.")
|
|
@@ -324,8 +335,10 @@ def Edb(
|
|
|
324
335
|
isaedtowned=isaedtowned,
|
|
325
336
|
oproject=oproject,
|
|
326
337
|
use_ppe=use_ppe,
|
|
327
|
-
technology_file=technology_file,
|
|
328
338
|
control_file=control_file,
|
|
339
|
+
map_file=map_file,
|
|
340
|
+
technology_file=technology_file,
|
|
341
|
+
layer_filter=layer_filter,
|
|
329
342
|
)
|
|
330
343
|
|
|
331
344
|
|
pyedb/generic/filesystem.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import
|
|
2
|
+
import secrets
|
|
3
3
|
import shutil
|
|
4
4
|
import string
|
|
5
5
|
|
|
@@ -44,7 +44,10 @@ class Scratch:
|
|
|
44
44
|
self._volatile = volatile
|
|
45
45
|
self._cleaned = True
|
|
46
46
|
char_set = string.ascii_uppercase + string.digits
|
|
47
|
-
|
|
47
|
+
generator = secrets.SystemRandom()
|
|
48
|
+
self._scratch_path = os.path.normpath(
|
|
49
|
+
os.path.join(local_path, "scratch" + "".join(secrets.SystemRandom.sample(generator, char_set, 6)))
|
|
50
|
+
)
|
|
48
51
|
if os.path.exists(self._scratch_path):
|
|
49
52
|
try:
|
|
50
53
|
self.remove()
|
pyedb/generic/general_methods.py
CHANGED
|
@@ -35,8 +35,8 @@ import itertools
|
|
|
35
35
|
import logging
|
|
36
36
|
import math
|
|
37
37
|
import os
|
|
38
|
-
import random
|
|
39
38
|
import re
|
|
39
|
+
import secrets
|
|
40
40
|
import string
|
|
41
41
|
import sys
|
|
42
42
|
import tempfile
|
|
@@ -254,7 +254,7 @@ def generate_unique_name(rootname, suffix="", n=6):
|
|
|
254
254
|
|
|
255
255
|
"""
|
|
256
256
|
char_set = string.ascii_uppercase + string.digits
|
|
257
|
-
uName = "".join(
|
|
257
|
+
uName = "".join(secrets.choice(char_set) for _ in range(n))
|
|
258
258
|
unique_name = rootname + "_" + uName
|
|
259
259
|
if suffix:
|
|
260
260
|
unique_name += "_" + suffix
|
pyedb/generic/settings.py
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
# SOFTWARE.
|
|
22
22
|
|
|
23
23
|
import os
|
|
24
|
+
from pathlib import Path
|
|
24
25
|
import re
|
|
25
26
|
import sys
|
|
26
27
|
import time
|
|
@@ -248,6 +249,9 @@ class Settings(object):
|
|
|
248
249
|
@edb_dll_path.setter
|
|
249
250
|
def edb_dll_path(self, value):
|
|
250
251
|
if os.path.exists(value):
|
|
252
|
+
ver = Path(value).parts[-2]
|
|
253
|
+
version, release = ver[-3:-1], ver[-1]
|
|
254
|
+
self.specified_version = f"20{version}.{release}"
|
|
251
255
|
self._edb_dll_path = value
|
|
252
256
|
|
|
253
257
|
@property
|
|
@@ -1065,6 +1065,9 @@ class Components(object):
|
|
|
1065
1065
|
ComponentGroup as GrpcComponentGroup,
|
|
1066
1066
|
)
|
|
1067
1067
|
|
|
1068
|
+
if not pins:
|
|
1069
|
+
raise ValueError("Pins must be a list of PadstackInstance objects.")
|
|
1070
|
+
|
|
1068
1071
|
if not component_name:
|
|
1069
1072
|
component_name = generate_unique_name("Comp_")
|
|
1070
1073
|
if component_part_name:
|
|
@@ -1074,7 +1077,10 @@ class Components(object):
|
|
|
1074
1077
|
if not compdef:
|
|
1075
1078
|
return False
|
|
1076
1079
|
new_cmp = GrpcComponentGroup.create(self._active_layout, component_name, compdef.name)
|
|
1077
|
-
|
|
1080
|
+
if hasattr(pins[0], "component") and pins[0].component:
|
|
1081
|
+
hosting_component_location = pins[0].component.transform
|
|
1082
|
+
else:
|
|
1083
|
+
hosting_component_location = None
|
|
1078
1084
|
if not len(pins) == len(compdef.component_pins):
|
|
1079
1085
|
self._pedb.logger.error(
|
|
1080
1086
|
f"Number on pins {len(pins)} does not match component definition number "
|
|
@@ -1092,7 +1098,18 @@ class Components(object):
|
|
|
1092
1098
|
if new_cmp_layer_name in self._pedb.stackup.signal_layers:
|
|
1093
1099
|
new_cmp_placement_layer = self._pedb.stackup.signal_layers[new_cmp_layer_name]
|
|
1094
1100
|
new_cmp.placement_layer = new_cmp_placement_layer
|
|
1095
|
-
|
|
1101
|
+
if r_value:
|
|
1102
|
+
new_cmp.component_type = GrpcComponentType.RESISTOR
|
|
1103
|
+
is_rlc = True
|
|
1104
|
+
elif c_value:
|
|
1105
|
+
new_cmp.component_type = GrpcComponentType.CAPACITOR
|
|
1106
|
+
is_rlc = True
|
|
1107
|
+
elif l_value:
|
|
1108
|
+
new_cmp.component_type = GrpcComponentType.INDUCTOR
|
|
1109
|
+
is_rlc = True
|
|
1110
|
+
else:
|
|
1111
|
+
new_cmp.component_type = GrpcComponentType.OTHER
|
|
1112
|
+
is_rlc = False
|
|
1096
1113
|
if is_rlc and len(pins) == 2:
|
|
1097
1114
|
rlc = GrpcRlc()
|
|
1098
1115
|
rlc.is_parallel = is_parallel
|
|
@@ -1125,7 +1142,8 @@ class Components(object):
|
|
|
1125
1142
|
component_property = new_cmp.component_property
|
|
1126
1143
|
component_property.model = rlc_model
|
|
1127
1144
|
new_cmp.component_property = component_property
|
|
1128
|
-
|
|
1145
|
+
if hosting_component_location:
|
|
1146
|
+
new_cmp.transform = hosting_component_location
|
|
1129
1147
|
new_edb_comp = Component(self._pedb, new_cmp)
|
|
1130
1148
|
self._cmp[new_cmp.name] = new_edb_comp
|
|
1131
1149
|
return new_edb_comp
|
|
@@ -1666,7 +1684,9 @@ class Components(object):
|
|
|
1666
1684
|
if comp.partname == part_name:
|
|
1667
1685
|
pass
|
|
1668
1686
|
else:
|
|
1669
|
-
pinlist = self.
|
|
1687
|
+
pinlist = list(self.instances[refdes].pins.values())
|
|
1688
|
+
if not pinlist:
|
|
1689
|
+
continue
|
|
1670
1690
|
if not part_name in self.definitions:
|
|
1671
1691
|
comp_def = ComponentDef.create(self._db, part_name, None)
|
|
1672
1692
|
# for pin in range(len(pinlist)):
|