pyedb 0.55.0__py3-none-any.whl → 0.57.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_data.py +3 -0
- pyedb/configuration/cfg_operations.py +2 -2
- pyedb/configuration/cfg_ports_sources.py +1 -1
- pyedb/configuration/cfg_terminals.py +232 -0
- pyedb/configuration/configuration.py +146 -3
- pyedb/dotnet/clr_module.py +1 -2
- pyedb/dotnet/database/Variables.py +56 -41
- pyedb/dotnet/database/cell/layout.py +5 -1
- pyedb/dotnet/database/cell/primitive/primitive.py +2 -2
- pyedb/dotnet/database/cell/terminal/bundle_terminal.py +12 -0
- pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/terminal.py +38 -0
- pyedb/dotnet/database/components.py +55 -52
- pyedb/dotnet/database/dotnet/database.py +1 -0
- pyedb/dotnet/database/edb_data/control_file.py +6 -3
- pyedb/dotnet/database/edb_data/nets_data.py +3 -3
- pyedb/dotnet/database/edb_data/padstacks_data.py +5 -2
- pyedb/dotnet/database/edb_data/ports.py +0 -25
- pyedb/dotnet/database/edb_data/primitives_data.py +3 -3
- pyedb/dotnet/database/edb_data/raptor_x_simulation_setup_data.py +18 -19
- pyedb/dotnet/database/edb_data/simulation_configuration.py +3 -3
- pyedb/dotnet/database/hfss.py +9 -8
- pyedb/dotnet/database/layout_validation.py +6 -3
- pyedb/dotnet/database/materials.py +1 -3
- pyedb/dotnet/database/modeler.py +7 -3
- pyedb/dotnet/database/nets.py +27 -19
- pyedb/dotnet/database/padstack.py +91 -2
- pyedb/dotnet/database/sim_setup_data/io/siwave.py +1 -1
- pyedb/dotnet/database/siwave.py +4 -3
- pyedb/dotnet/database/stackup.py +50 -26
- pyedb/dotnet/database/utilities/heatsink.py +0 -1
- pyedb/dotnet/database/utilities/simulation_setup.py +7 -5
- pyedb/dotnet/database/utilities/siwave_cpa_simulation_setup.py +1 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +5 -2
- pyedb/dotnet/edb.py +41 -36
- pyedb/exceptions.py +1 -2
- pyedb/extensions/create_cell_array.py +408 -0
- pyedb/generic/data_handlers.py +17 -28
- pyedb/generic/design_types.py +25 -38
- pyedb/generic/filesystem.py +9 -4
- pyedb/generic/general_methods.py +6 -7
- pyedb/generic/plot.py +2 -2
- pyedb/generic/settings.py +4 -0
- pyedb/grpc/database/_typing.py +0 -0
- pyedb/grpc/database/components.py +30 -11
- pyedb/grpc/database/control_file.py +14 -35
- pyedb/grpc/database/definition/materials.py +1 -1
- pyedb/grpc/database/definition/package_def.py +6 -3
- pyedb/grpc/database/definition/padstack_def.py +4 -7
- pyedb/grpc/database/hfss.py +1 -4
- pyedb/grpc/database/hierarchy/component.py +3 -4
- pyedb/grpc/database/hierarchy/pingroup.py +16 -3
- pyedb/grpc/database/layers/layer.py +1 -2
- pyedb/grpc/database/layers/stackup_layer.py +42 -19
- pyedb/grpc/database/layout/layout.py +117 -28
- pyedb/grpc/database/layout/voltage_regulator.py +6 -1
- pyedb/grpc/database/layout_validation.py +7 -4
- pyedb/grpc/database/modeler.py +241 -256
- pyedb/grpc/database/net/differential_pair.py +9 -2
- pyedb/grpc/database/net/extended_net.py +24 -9
- pyedb/grpc/database/net/net.py +14 -5
- pyedb/grpc/database/net/net_class.py +24 -7
- pyedb/grpc/database/nets.py +11 -43
- pyedb/grpc/database/padstacks.py +92 -16
- pyedb/grpc/database/primitive/bondwire.py +3 -67
- pyedb/grpc/database/primitive/circle.py +42 -3
- pyedb/grpc/database/primitive/padstack_instance.py +17 -19
- pyedb/grpc/database/primitive/path.py +154 -5
- pyedb/grpc/database/primitive/polygon.py +75 -9
- pyedb/grpc/database/primitive/primitive.py +2 -2
- pyedb/grpc/database/primitive/rectangle.py +105 -4
- pyedb/grpc/database/simulation_setup/hfss_general_settings.py +0 -2
- pyedb/grpc/database/simulation_setup/hfss_settings_options.py +0 -4
- pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +4 -2
- pyedb/grpc/database/simulation_setup/sweep_data.py +1 -3
- pyedb/grpc/database/siwave.py +6 -13
- pyedb/grpc/database/source_excitations.py +49 -57
- pyedb/grpc/database/stackup.py +50 -27
- pyedb/grpc/database/terminal/bundle_terminal.py +10 -3
- pyedb/grpc/database/terminal/pingroup_terminal.py +8 -1
- pyedb/grpc/database/terminal/terminal.py +19 -8
- pyedb/grpc/database/utility/heat_sink.py +0 -1
- pyedb/grpc/database/utility/hfss_extent_info.py +2 -2
- pyedb/grpc/database/utility/value.py +1 -0
- pyedb/grpc/database/utility/xml_control_file.py +6 -3
- pyedb/grpc/edb.py +33 -24
- pyedb/grpc/edb_init.py +1 -0
- pyedb/grpc/rpc_session.py +4 -3
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +6 -2
- pyedb/ipc2581/ecad/cad_data/step.py +1 -1
- pyedb/ipc2581/ipc2581.py +8 -7
- pyedb/libraries/common.py +3 -4
- pyedb/libraries/rf_libraries/base_functions.py +7 -16
- pyedb/libraries/rf_libraries/planar_antennas.py +3 -21
- pyedb/misc/downloads.py +1 -0
- pyedb/misc/misc.py +5 -2
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +1 -1
- pyedb/misc/utilities.py +0 -1
- pyedb/modeler/geometry_operators.py +9 -8
- 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.57.0.dist-info}/METADATA +3 -3
- {pyedb-0.55.0.dist-info → pyedb-0.57.0.dist-info}/RECORD +107 -102
- {pyedb-0.55.0.dist-info → pyedb-0.57.0.dist-info}/WHEEL +0 -0
- {pyedb-0.55.0.dist-info → pyedb-0.57.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,6 +15,7 @@ from pyedb.dotnet.database.sim_setup_data.io.siwave import (
|
|
|
15
15
|
)
|
|
16
16
|
from pyedb.dotnet.database.utilities.simulation_setup import SimulationSetup
|
|
17
17
|
from pyedb.generic.general_methods import is_linux
|
|
18
|
+
from pyedb.generic.settings import settings
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _parse_value(v):
|
|
@@ -79,8 +80,10 @@ def clone_edb_sim_setup_info(source, target):
|
|
|
79
80
|
except TypeError:
|
|
80
81
|
try:
|
|
81
82
|
setter.__setattr__(k, str(value))
|
|
82
|
-
except:
|
|
83
|
-
|
|
83
|
+
except Exception as e:
|
|
84
|
+
settings.logger.warning(
|
|
85
|
+
f"Failed to update attribute {k} with value {value} - {type(e).__name__}: {str(e)}"
|
|
86
|
+
)
|
|
84
87
|
|
|
85
88
|
|
|
86
89
|
class SiwaveSimulationSetup(SimulationSetup):
|
pyedb/dotnet/edb.py
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
This module is implicitly loaded in HFSS 3D Layout when launched.
|
|
26
26
|
|
|
27
27
|
"""
|
|
28
|
+
|
|
28
29
|
from datetime import datetime
|
|
29
30
|
from itertools import combinations
|
|
30
31
|
import os
|
|
@@ -43,7 +44,6 @@ import rtree
|
|
|
43
44
|
|
|
44
45
|
from pyedb.configuration.configuration import Configuration
|
|
45
46
|
import pyedb.dotnet
|
|
46
|
-
from pyedb.dotnet.database.Variables import decompose_variable_value
|
|
47
47
|
from pyedb.dotnet.database.cell.layout import Layout
|
|
48
48
|
from pyedb.dotnet.database.cell.terminal.terminal import Terminal
|
|
49
49
|
from pyedb.dotnet.database.components import Components
|
|
@@ -92,6 +92,7 @@ from pyedb.dotnet.database.utilities.siwave_simulation_setup import (
|
|
|
92
92
|
SiwaveSimulationSetup,
|
|
93
93
|
)
|
|
94
94
|
from pyedb.dotnet.database.utilities.value import Value
|
|
95
|
+
from pyedb.dotnet.database.Variables import decompose_variable_value
|
|
95
96
|
from pyedb.generic.constants import AEDT_UNITS, SolverType, unit_converter
|
|
96
97
|
from pyedb.generic.general_methods import generate_unique_name, is_linux, is_windows
|
|
97
98
|
from pyedb.generic.process import SiwaveSolve
|
|
@@ -152,19 +153,19 @@ class Edb:
|
|
|
152
153
|
|
|
153
154
|
Add a new variable named "s1" to the ``Edb`` instance.
|
|
154
155
|
|
|
155
|
-
>>> app[
|
|
156
|
-
>>> app[
|
|
156
|
+
>>> app["s1"] = "0.25 mm"
|
|
157
|
+
>>> app["s1"].tofloat
|
|
157
158
|
>>> 0.00025
|
|
158
|
-
>>> app[
|
|
159
|
+
>>> app["s1"].tostring
|
|
159
160
|
>>> "0.25mm"
|
|
160
161
|
|
|
161
162
|
or add a new parameter with description:
|
|
162
163
|
|
|
163
|
-
>>> app[
|
|
164
|
-
>>> app[
|
|
164
|
+
>>> app["s2"] = ["20um", "Spacing between traces"]
|
|
165
|
+
>>> app["s2"].value
|
|
165
166
|
>>> 1.9999999999999998e-05
|
|
166
|
-
>>> app[
|
|
167
|
-
>>>
|
|
167
|
+
>>> app["s2"].description
|
|
168
|
+
>>> "Spacing between traces"
|
|
168
169
|
|
|
169
170
|
Create an ``Edb`` object and open the specified project.
|
|
170
171
|
|
|
@@ -1852,8 +1853,11 @@ class Edb:
|
|
|
1852
1853
|
convert_py_list_to_net_list(list(obj_data)),
|
|
1853
1854
|
convert_py_list_to_net_list(voids_poly),
|
|
1854
1855
|
)
|
|
1855
|
-
except:
|
|
1856
|
-
|
|
1856
|
+
except Exception as e:
|
|
1857
|
+
self.logger.error(
|
|
1858
|
+
f"A(n) {type(e).__name__} error occurred in method _create_conformal of "
|
|
1859
|
+
f"class Edb at iteration {k} for data {i}: {str(e)}"
|
|
1860
|
+
)
|
|
1857
1861
|
finally:
|
|
1858
1862
|
unite_polys.extend(list(obj_data))
|
|
1859
1863
|
_poly_unite = self.core.Geometry.PolygonData.Unite(convert_py_list_to_net_list(unite_polys))
|
|
@@ -2048,7 +2052,7 @@ class Edb:
|
|
|
2048
2052
|
Examples
|
|
2049
2053
|
--------
|
|
2050
2054
|
>>> from pyedb import Edb
|
|
2051
|
-
>>> edb = Edb(r
|
|
2055
|
+
>>> edb = Edb(r"C:\\test.aedb", version="2022.2")
|
|
2052
2056
|
>>> edb.logger.info_timer("Edb Opening")
|
|
2053
2057
|
>>> edb.logger.reset_timer()
|
|
2054
2058
|
>>> start = time.time()
|
|
@@ -2058,7 +2062,7 @@ class Edb:
|
|
|
2058
2062
|
>>> signal_list.append(net)
|
|
2059
2063
|
>>> power_list = ["PGND"]
|
|
2060
2064
|
>>> edb.cutout(signal_list=signal_list, reference_list=power_list, extent_type="Conforming")
|
|
2061
|
-
>>> end_time = str((time.time() - start)/60)
|
|
2065
|
+
>>> end_time = str((time.time() - start) / 60)
|
|
2062
2066
|
>>> edb.logger.info("Total legacy cutout time in min %s", end_time)
|
|
2063
2067
|
>>> edb.nets.plot(signal_list, None, color_by_net=True)
|
|
2064
2068
|
>>> edb.nets.plot(power_list, None, color_by_net=True)
|
|
@@ -2291,8 +2295,8 @@ class Edb:
|
|
|
2291
2295
|
if os.path.exists(source) and not os.path.exists(target):
|
|
2292
2296
|
try:
|
|
2293
2297
|
shutil.copy(source, target)
|
|
2294
|
-
except:
|
|
2295
|
-
|
|
2298
|
+
except Exception as e:
|
|
2299
|
+
self.logger.error(f"Failed to copy {source} to {target} - {type(e).__name__}: {str(e)}")
|
|
2296
2300
|
elif open_cutout_at_end:
|
|
2297
2301
|
self._active_cell = _cutout
|
|
2298
2302
|
self._init_objects()
|
|
@@ -2708,7 +2712,7 @@ class Edb:
|
|
|
2708
2712
|
Examples
|
|
2709
2713
|
--------
|
|
2710
2714
|
>>> from pyedb import Edb
|
|
2711
|
-
>>> edb = Edb(r
|
|
2715
|
+
>>> edb = Edb(r"C:\\test.aedb", version="2022.2")
|
|
2712
2716
|
>>> edb.logger.info_timer("Edb Opening")
|
|
2713
2717
|
>>> edb.logger.reset_timer()
|
|
2714
2718
|
>>> start = time.time()
|
|
@@ -2718,7 +2722,7 @@ class Edb:
|
|
|
2718
2722
|
>>> signal_list.append(net)
|
|
2719
2723
|
>>> power_list = ["PGND"]
|
|
2720
2724
|
>>> edb.create_cutout_multithread(signal_list=signal_list, reference_list=power_list, extent_type="Conforming")
|
|
2721
|
-
>>> end_time = str((time.time() - start)/60)
|
|
2725
|
+
>>> end_time = str((time.time() - start) / 60)
|
|
2722
2726
|
>>> edb.logger.info("Total legacy cutout time in min %s", end_time)
|
|
2723
2727
|
>>> edb.nets.plot(signal_list, None, color_by_net=True)
|
|
2724
2728
|
>>> edb.nets.plot(power_list, None, color_by_net=True)
|
|
@@ -2977,8 +2981,8 @@ class Edb:
|
|
|
2977
2981
|
try:
|
|
2978
2982
|
shutil.copy(source, target)
|
|
2979
2983
|
self.logger.warning("aedb def file manually created.")
|
|
2980
|
-
except:
|
|
2981
|
-
|
|
2984
|
+
except Exception as e:
|
|
2985
|
+
self.logger.error(f"Failed to copy {source} to {target} - {type(e).__name__}: {str(e)}")
|
|
2982
2986
|
return [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(polygonData.GetPolygonWithoutArcs().Points)]
|
|
2983
2987
|
|
|
2984
2988
|
def create_cutout_on_point_list(
|
|
@@ -3107,7 +3111,7 @@ class Edb:
|
|
|
3107
3111
|
>>> from pyedb import Edb
|
|
3108
3112
|
>>> edb = Edb(edbpath="C:\\temp\\myproject.aedb", version="2023.2")
|
|
3109
3113
|
|
|
3110
|
-
>>> options_config = {
|
|
3114
|
+
>>> options_config = {"UNITE_NETS": 1, "LAUNCH_Q3D": 0}
|
|
3111
3115
|
>>> edb.write_export3d_option_config_file(r"C:\\temp", options_config)
|
|
3112
3116
|
>>> edb.export_hfss(r"C:\\temp")
|
|
3113
3117
|
"""
|
|
@@ -3149,7 +3153,7 @@ class Edb:
|
|
|
3149
3153
|
|
|
3150
3154
|
>>> from pyedb import Edb
|
|
3151
3155
|
>>> edb = Edb(edbpath="C:\\temp\\myproject.aedb", version="2021.2")
|
|
3152
|
-
>>> options_config = {
|
|
3156
|
+
>>> options_config = {"UNITE_NETS": 1, "LAUNCH_Q3D": 0}
|
|
3153
3157
|
>>> edb.write_export3d_option_config_file("C:\\temp", options_config)
|
|
3154
3158
|
>>> edb.export_q3d("C:\\temp")
|
|
3155
3159
|
"""
|
|
@@ -3201,7 +3205,7 @@ class Edb:
|
|
|
3201
3205
|
|
|
3202
3206
|
>>> edb = Edb(edbpath="C:\\temp\\myproject.aedb", version="2021.2")
|
|
3203
3207
|
|
|
3204
|
-
>>> options_config = {
|
|
3208
|
+
>>> options_config = {"UNITE_NETS": 1, "LAUNCH_Q3D": 0}
|
|
3205
3209
|
>>> edb.write_export3d_option_config_file("C:\\temp", options_config)
|
|
3206
3210
|
>>> edb.export_maxwell("C:\\temp")
|
|
3207
3211
|
"""
|
|
@@ -3369,8 +3373,8 @@ class Edb:
|
|
|
3369
3373
|
>>> from pyedb import Edb
|
|
3370
3374
|
>>> edb_app = Edb()
|
|
3371
3375
|
>>> boolean_1, ant_length = edb_app.add_project_variable("my_local_variable", "1cm")
|
|
3372
|
-
>>> print(edb_app["$my_local_variable"])
|
|
3373
|
-
>>> edb_app["$my_local_variable"] = "1cm"
|
|
3376
|
+
>>> print(edb_app["$my_local_variable"]) # using getitem
|
|
3377
|
+
>>> edb_app["$my_local_variable"] = "1cm" # using setitem
|
|
3374
3378
|
|
|
3375
3379
|
"""
|
|
3376
3380
|
if not variable_name.startswith("$"):
|
|
@@ -3408,8 +3412,8 @@ class Edb:
|
|
|
3408
3412
|
>>> from pyedb import Edb
|
|
3409
3413
|
>>> edb_app = Edb()
|
|
3410
3414
|
>>> boolean_1, ant_length = edb_app.add_design_variable("my_local_variable", "1cm")
|
|
3411
|
-
>>> print(edb_app["my_local_variable"])
|
|
3412
|
-
>>> edb_app["my_local_variable"] = "1cm"
|
|
3415
|
+
>>> print(edb_app["my_local_variable"]) # using getitem
|
|
3416
|
+
>>> edb_app["my_local_variable"] = "1cm" # using setitem
|
|
3413
3417
|
>>> boolean_2, para_length = edb_app.change_design_variable_value("my_parameter", "1m", is_parameter=True
|
|
3414
3418
|
>>> boolean_3, project_length = edb_app.change_design_variable_value("$my_project_variable", "1m")
|
|
3415
3419
|
|
|
@@ -3449,7 +3453,7 @@ class Edb:
|
|
|
3449
3453
|
>>> edb_app = Edb()
|
|
3450
3454
|
>>> boolean, ant_length = edb_app.add_design_variable("ant_length", "1cm")
|
|
3451
3455
|
>>> boolean, ant_length = edb_app.change_design_variable_value("ant_length", "1m")
|
|
3452
|
-
>>> print(edb_app["ant_length"])
|
|
3456
|
+
>>> print(edb_app["ant_length"]) # using getitem
|
|
3453
3457
|
"""
|
|
3454
3458
|
var_server = self.variable_exists(variable_name)
|
|
3455
3459
|
if var_server[0]:
|
|
@@ -3928,11 +3932,13 @@ class Edb:
|
|
|
3928
3932
|
>>> from pyedb import Edb
|
|
3929
3933
|
>>> edbapp = Edb()
|
|
3930
3934
|
>>> setup1 = edbapp.create_siwave_syz_setup("setup1")
|
|
3931
|
-
>>> setup1.add_frequency_sweep(
|
|
3932
|
-
...
|
|
3933
|
-
...
|
|
3934
|
-
...
|
|
3935
|
-
...
|
|
3935
|
+
>>> setup1.add_frequency_sweep(
|
|
3936
|
+
... frequency_sweep=[
|
|
3937
|
+
... ["linear count", "0", "1kHz", 1],
|
|
3938
|
+
... ["log scale", "1kHz", "0.1GHz", 10],
|
|
3939
|
+
... ["linear scale", "0.1GHz", "10GHz", "0.1GHz"],
|
|
3940
|
+
... ]
|
|
3941
|
+
... )
|
|
3936
3942
|
"""
|
|
3937
3943
|
if not name:
|
|
3938
3944
|
name = generate_unique_name("Siwave_SYZ")
|
|
@@ -4033,7 +4039,7 @@ class Edb:
|
|
|
4033
4039
|
edb_zones = {}
|
|
4034
4040
|
if not self.setups:
|
|
4035
4041
|
self.siwave.add_siwave_syz_analysis()
|
|
4036
|
-
self.
|
|
4042
|
+
self.save()
|
|
4037
4043
|
for zone_primitive in zone_primitives:
|
|
4038
4044
|
edb_zone_path = os.path.join(
|
|
4039
4045
|
working_directory,
|
|
@@ -4041,7 +4047,7 @@ class Edb:
|
|
|
4041
4047
|
)
|
|
4042
4048
|
shutil.copytree(self.edbpath, edb_zone_path)
|
|
4043
4049
|
poly_data = zone_primitive.GetPolygonData()
|
|
4044
|
-
if self.
|
|
4050
|
+
if self._db.GetVersion()[0] >= 10:
|
|
4045
4051
|
edb_zones[edb_zone_path] = (zone_primitive.GetZoneId(), poly_data)
|
|
4046
4052
|
elif len(zone_primitives) == len(zone_ids):
|
|
4047
4053
|
edb_zones[edb_zone_path] = (zone_ids[0], poly_data)
|
|
@@ -4687,8 +4693,7 @@ class Edb:
|
|
|
4687
4693
|
]
|
|
4688
4694
|
if not polys:
|
|
4689
4695
|
raise RuntimeWarning(
|
|
4690
|
-
f"No polygon found with voids on layer {reference_layer} during model creation for "
|
|
4691
|
-
f"arbitrary wave ports"
|
|
4696
|
+
f"No polygon found with voids on layer {reference_layer} during model creation for arbitrary wave ports"
|
|
4692
4697
|
)
|
|
4693
4698
|
void_padstacks = []
|
|
4694
4699
|
for poly in polys:
|
|
@@ -4705,7 +4710,7 @@ class Edb:
|
|
|
4705
4710
|
|
|
4706
4711
|
if not void_padstacks:
|
|
4707
4712
|
raise RuntimeWarning(
|
|
4708
|
-
"No padstack instances found inside evaluated voids during model creation for arbitrary
|
|
4713
|
+
"No padstack instances found inside evaluated voids during model creation for arbitrary waveports"
|
|
4709
4714
|
)
|
|
4710
4715
|
cloned_edb = Edb(edbpath=output_edb)
|
|
4711
4716
|
|
pyedb/exceptions.py
CHANGED
|
@@ -0,0 +1,408 @@
|
|
|
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
|
+
|
|
27
|
+
import itertools
|
|
28
|
+
from typing import Optional, Union
|
|
29
|
+
|
|
30
|
+
from pyedb import Edb
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ----------------------
|
|
34
|
+
# Public façade function
|
|
35
|
+
# ----------------------
|
|
36
|
+
def create_array_from_unit_cell(
|
|
37
|
+
edb: Edb,
|
|
38
|
+
x_number: int = 2,
|
|
39
|
+
y_number: int = 2,
|
|
40
|
+
offset_x: Optional[Union[int, float, str]] = None,
|
|
41
|
+
offset_y: Optional[Union[int, float, str]] = None,
|
|
42
|
+
) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Create a 2-D rectangular array from the current EDB unit cell.
|
|
45
|
+
|
|
46
|
+
The function duplicates every primitive (polygon, rectangle, circle), path,
|
|
47
|
+
padstack via, and component found in the active layout and places copies on
|
|
48
|
+
a regular grid defined by *offset_x* and *offset_y*. If the offsets are
|
|
49
|
+
omitted they are automatically derived from the bounding box of the first
|
|
50
|
+
primitive found on the layer called **outline** (case-insensitive).
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
edb : pyedb.Edb
|
|
55
|
+
An open Edb instance whose active layout is used as the unit cell.
|
|
56
|
+
x_number : int, optional
|
|
57
|
+
Number of columns (X-direction). Must be > 0. Defaults to 2.
|
|
58
|
+
y_number : int, optional
|
|
59
|
+
Number of rows (Y-direction). Must be > 0. Defaults to 2.
|
|
60
|
+
offset_x : int | float | str, None, optional
|
|
61
|
+
Horizontal pitch (distance between cell origins). When *None* the
|
|
62
|
+
value is derived from the outline geometry.
|
|
63
|
+
offset_y : int | float | str, None, optional
|
|
64
|
+
Vertical pitch (distance between cell origins). When *None* the
|
|
65
|
+
value is derived from the outline geometry.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
bool
|
|
70
|
+
``True`` if the operation completed successfully.
|
|
71
|
+
|
|
72
|
+
Raises
|
|
73
|
+
------
|
|
74
|
+
ValueError
|
|
75
|
+
If *x_number* or *y_number* are non-positive.
|
|
76
|
+
RuntimeError
|
|
77
|
+
If no outline is found and the offsets were not supplied, or if the
|
|
78
|
+
outline is not a supported type (polygon/rectangle).
|
|
79
|
+
|
|
80
|
+
Notes
|
|
81
|
+
-----
|
|
82
|
+
The routine is technology-agnostic; it delegates all EDB-specific calls to
|
|
83
|
+
small adapter classes that handle either the **gRPC** or **.NET** back-end
|
|
84
|
+
transparently.
|
|
85
|
+
|
|
86
|
+
Examples
|
|
87
|
+
--------
|
|
88
|
+
>>> from pyedb import Edb
|
|
89
|
+
>>> edb = Edb("unit_cell.aedb")
|
|
90
|
+
>>> create_array_from_unit_cell(edb, x_number=4, y_number=3)
|
|
91
|
+
True
|
|
92
|
+
"""
|
|
93
|
+
if edb.grpc:
|
|
94
|
+
adapter = _GrpcAdapter(edb)
|
|
95
|
+
else:
|
|
96
|
+
adapter = _DotNetAdapter(edb)
|
|
97
|
+
return __create_array_from_unit_cell_impl(edb, adapter, x_number, y_number, offset_x, offset_y)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# Implementation (technology-agnostic)
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
def __create_array_from_unit_cell_impl(
|
|
104
|
+
edb: Edb,
|
|
105
|
+
adapter: "_BaseAdapter",
|
|
106
|
+
x_number: int,
|
|
107
|
+
y_number: int,
|
|
108
|
+
offset_x: Optional[Union[int, float]],
|
|
109
|
+
offset_y: Optional[Union[int, float]],
|
|
110
|
+
) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Inner worker that performs the actual replication.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
edb : pyedb.Edb
|
|
117
|
+
Edb instance (already validated by the façade).
|
|
118
|
+
adapter : _BaseAdapter
|
|
119
|
+
Technology-specific adapter (gRPC or .NET).
|
|
120
|
+
x_number : int
|
|
121
|
+
Number of columns.
|
|
122
|
+
y_number : int
|
|
123
|
+
Number of rows.
|
|
124
|
+
offset_x : float
|
|
125
|
+
Absolute pitch in X (always resolved by the caller).
|
|
126
|
+
offset_y : float
|
|
127
|
+
Absolute pitch in Y (always resolved by the caller).
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
bool
|
|
132
|
+
``True`` when finished.
|
|
133
|
+
"""
|
|
134
|
+
# ---------- Sanity & auto-pitch detection ----------
|
|
135
|
+
if x_number <= 0 or y_number <= 0:
|
|
136
|
+
raise ValueError("x_number and y_number must be positive integers")
|
|
137
|
+
if offset_x and not offset_y:
|
|
138
|
+
raise ValueError("If offset_x is provided, offset_y must be provided as well")
|
|
139
|
+
if offset_y and not offset_x:
|
|
140
|
+
raise ValueError("If offset_y is provided, offset_x must be provided as well")
|
|
141
|
+
|
|
142
|
+
if not offset_x and not offset_y:
|
|
143
|
+
edb.logger.info("Auto-detecting outline extents")
|
|
144
|
+
outline_prims = [p for p in edb.modeler.primitives if p.layer_name.lower() == "outline"]
|
|
145
|
+
if not outline_prims:
|
|
146
|
+
raise RuntimeError("No outline found. Provide offset_x / offset_y or add an 'Outline' layer primitive.")
|
|
147
|
+
outline = outline_prims[0]
|
|
148
|
+
if not adapter.is_supported_outline(outline):
|
|
149
|
+
raise RuntimeError("Outline primitive is not a polygon/rectangle. Provide offset_x / offset_y.")
|
|
150
|
+
offset_x, offset_y = adapter.pitch_from_outline(outline)
|
|
151
|
+
offset_x = edb.value(offset_x)
|
|
152
|
+
offset_y = edb.value(offset_y)
|
|
153
|
+
|
|
154
|
+
# ---------- Collect everything we have to replicate ----------
|
|
155
|
+
primitives = [p for p in edb.modeler.primitives if adapter.is_primitive_to_copy(p)]
|
|
156
|
+
paths = list(edb.modeler.paths)
|
|
157
|
+
vias = list(edb.padstacks.vias.values())
|
|
158
|
+
components = list(edb.components.instances.values())
|
|
159
|
+
|
|
160
|
+
# ---------- Replication loops ----------
|
|
161
|
+
edb.logger.info(f"Starting array replication {x_number}×{y_number}")
|
|
162
|
+
for i, j in itertools.product(range(x_number), range(y_number)):
|
|
163
|
+
if i == 0 and j == 0:
|
|
164
|
+
continue # original already exists
|
|
165
|
+
|
|
166
|
+
dx = edb.value(offset_x * i)
|
|
167
|
+
dy = edb.value(offset_y * j)
|
|
168
|
+
|
|
169
|
+
# Primitives & voids
|
|
170
|
+
for prim in primitives:
|
|
171
|
+
new_poly = adapter.duplicate_primitive(prim, dx, dy, i, j)
|
|
172
|
+
for void in prim.voids:
|
|
173
|
+
adapter.duplicate_void(new_poly, void, dx, dy)
|
|
174
|
+
|
|
175
|
+
# Paths
|
|
176
|
+
for path in paths:
|
|
177
|
+
adapter.duplicate_path(path, dx, dy, i, j)
|
|
178
|
+
|
|
179
|
+
# Stand-alone vias
|
|
180
|
+
for via in (v for v in vias if not v.component):
|
|
181
|
+
adapter.duplicate_standalone_via(via, dx, dy, i, j)
|
|
182
|
+
|
|
183
|
+
# Components
|
|
184
|
+
for comp in components:
|
|
185
|
+
adapter.duplicate_component(comp, dx, dy, i, j)
|
|
186
|
+
|
|
187
|
+
edb.logger.info("Array replication finished successfully")
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ------------------------------------------------------------------
|
|
192
|
+
# Technology-specific adapters
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
class _BaseAdapter:
|
|
195
|
+
"""Abstract adapter defining the required interface."""
|
|
196
|
+
|
|
197
|
+
def __init__(self, edb: Edb):
|
|
198
|
+
self.edb = edb
|
|
199
|
+
|
|
200
|
+
# ---- Outline helpers ----
|
|
201
|
+
def is_supported_outline(self, outline) -> bool:
|
|
202
|
+
"""Return True when *outline* is a primitive type from which pitch can be inferred."""
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
|
|
205
|
+
def pitch_from_outline(self, outline) -> tuple[float, float]:
|
|
206
|
+
"""
|
|
207
|
+
Compute the (offset_x, offset_y) pitch from the bounding box of *outline*.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
tuple[float, float]
|
|
212
|
+
(width, height) of the outline primitive in database units.
|
|
213
|
+
"""
|
|
214
|
+
raise NotImplementedError
|
|
215
|
+
|
|
216
|
+
# ---- Duplication helpers ----
|
|
217
|
+
def is_primitive_to_copy(self, prim) -> bool:
|
|
218
|
+
"""Return True when *prim* is a primitive that must be duplicated."""
|
|
219
|
+
raise NotImplementedError
|
|
220
|
+
|
|
221
|
+
def duplicate_primitive(self, prim, dx, dy, i, j):
|
|
222
|
+
"""Return a new primitive translated by (dx, dy)."""
|
|
223
|
+
raise NotImplementedError
|
|
224
|
+
|
|
225
|
+
def duplicate_void(self, new_poly, void, dx, dy):
|
|
226
|
+
"""Add a translated copy of *void* to *new_poly*."""
|
|
227
|
+
raise NotImplementedError
|
|
228
|
+
|
|
229
|
+
def duplicate_path(self, path, dx, dy, i, j):
|
|
230
|
+
"""Create a translated copy of *path*."""
|
|
231
|
+
raise NotImplementedError
|
|
232
|
+
|
|
233
|
+
def duplicate_standalone_via(self, via, dx, dy, i, j):
|
|
234
|
+
"""Create a translated copy of a stand-alone via."""
|
|
235
|
+
raise NotImplementedError
|
|
236
|
+
|
|
237
|
+
def duplicate_component(self, comp, dx, dy, i, j):
|
|
238
|
+
"""Create a translated copy of *comp* (including its pins)."""
|
|
239
|
+
raise NotImplementedError
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class _GrpcAdapter(_BaseAdapter):
|
|
243
|
+
"""Adapter for the gRPC-based EDB back-end."""
|
|
244
|
+
|
|
245
|
+
def is_supported_outline(self, outline) -> bool:
|
|
246
|
+
return outline.type in {"polygon", "rectangle"}
|
|
247
|
+
|
|
248
|
+
def pitch_from_outline(self, outline):
|
|
249
|
+
bbox = outline.polygon_data.bbox()
|
|
250
|
+
return self.edb.value(bbox[1].x - bbox[0].x), self.edb.value(bbox[1].y - bbox[0].y)
|
|
251
|
+
|
|
252
|
+
def is_primitive_to_copy(self, prim):
|
|
253
|
+
return prim.type in {"polygon", "rectangle", "circle"}
|
|
254
|
+
|
|
255
|
+
def duplicate_primitive(self, prim, dx, dy, i, j):
|
|
256
|
+
moved_pd = prim.polygon_data.move((dx, dy))
|
|
257
|
+
return self.edb.modeler.create_polygon(
|
|
258
|
+
moved_pd,
|
|
259
|
+
layer_name=prim.layer.name,
|
|
260
|
+
net_name=prim.net.name,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def duplicate_void(self, new_poly, void, dx, dy):
|
|
264
|
+
new_poly.add_void(void.polygon_data.move((dx, dy)))
|
|
265
|
+
|
|
266
|
+
def duplicate_path(self, path, dx, dy, i, j):
|
|
267
|
+
moved_line = path.cast().center_line.move((dx, dy))
|
|
268
|
+
self.edb.modeler.create_trace(
|
|
269
|
+
moved_line,
|
|
270
|
+
width=path.width,
|
|
271
|
+
layer_name=path.layer.name,
|
|
272
|
+
net_name=path.net.name,
|
|
273
|
+
corner_style=path.corner_style,
|
|
274
|
+
start_cap_style=path.end_cap1,
|
|
275
|
+
end_cap_style=path.end_cap2,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def duplicate_standalone_via(self, via, dx, dy, i, j):
|
|
279
|
+
from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
|
|
280
|
+
|
|
281
|
+
pos = via.position
|
|
282
|
+
PadstackInstance.create(
|
|
283
|
+
self.edb.active_layout,
|
|
284
|
+
net=via.net,
|
|
285
|
+
name=f"{via.name}_i{i}_j{j}",
|
|
286
|
+
padstack_def=self.edb.padstacks.definitions[via.padstack_definition],
|
|
287
|
+
position_x=pos[0] + dx,
|
|
288
|
+
position_y=pos[1] + dy,
|
|
289
|
+
rotation=0.0,
|
|
290
|
+
top_layer=self.edb.stackup.layers[via.start_layer],
|
|
291
|
+
bottom_layer=self.edb.stackup.layers[via.stop_layer],
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def duplicate_component(self, comp, dx, dy, i, j):
|
|
295
|
+
from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
|
|
296
|
+
|
|
297
|
+
new_pins = []
|
|
298
|
+
for pin in comp.pins.values():
|
|
299
|
+
pos = pin.position
|
|
300
|
+
new_pin = PadstackInstance.create(
|
|
301
|
+
self.edb.active_layout,
|
|
302
|
+
net=pin.net,
|
|
303
|
+
name=f"{pin.name}_i{i}_j{j}",
|
|
304
|
+
padstack_def=self.edb.padstacks.definitions[pin.padstack_definition],
|
|
305
|
+
position_x=pos[0] + dx,
|
|
306
|
+
position_y=pos[1] + dy,
|
|
307
|
+
rotation=0.0,
|
|
308
|
+
top_layer=self.edb.stackup.layers[pin.start_layer],
|
|
309
|
+
bottom_layer=self.edb.stackup.layers[pin.stop_layer],
|
|
310
|
+
)
|
|
311
|
+
new_pins.append(new_pin)
|
|
312
|
+
|
|
313
|
+
if new_pins:
|
|
314
|
+
res = self.edb.value(comp.res_value) if hasattr(comp, "res_value") and comp.res_value else None
|
|
315
|
+
cap = self.edb.value(comp.cap_value) if hasattr(comp, "cap_value") and comp.cap_value else None
|
|
316
|
+
ind = self.edb.value(comp.ind_value) if hasattr(comp, "ind_value") and comp.ind_value else None
|
|
317
|
+
new_comp = self.edb.components.create(
|
|
318
|
+
pins=new_pins,
|
|
319
|
+
component_name=f"{comp.name}_array_{i}_{j}",
|
|
320
|
+
placement_layer=comp.placement_layer,
|
|
321
|
+
component_part_name=comp.part_name,
|
|
322
|
+
r_value=res,
|
|
323
|
+
l_value=ind,
|
|
324
|
+
c_value=cap,
|
|
325
|
+
)
|
|
326
|
+
if hasattr(comp, "component_property") and comp.component_property:
|
|
327
|
+
new_comp.component_property = comp.component_property
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class _DotNetAdapter(_BaseAdapter):
|
|
331
|
+
"""Adapter for the legacy .NET-based EDB back-end."""
|
|
332
|
+
|
|
333
|
+
def is_supported_outline(self, outline) -> bool:
|
|
334
|
+
return outline.type.lower() in {"polygon", "rectangle"}
|
|
335
|
+
|
|
336
|
+
def pitch_from_outline(self, outline):
|
|
337
|
+
bbox = outline.polygon_data.bounding_box
|
|
338
|
+
return self.edb.value(bbox[1][0] - bbox[0][0]), self.edb.value(bbox[1][1] - bbox[0][1])
|
|
339
|
+
|
|
340
|
+
def is_primitive_to_copy(self, prim):
|
|
341
|
+
return prim.type.lower() in {"polygon", "rectangle", "circle"}
|
|
342
|
+
|
|
343
|
+
def duplicate_primitive(self, prim, dx, dy, i, j):
|
|
344
|
+
from pyedb.dotnet.database.geometry.point_data import PointData
|
|
345
|
+
|
|
346
|
+
vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
|
|
347
|
+
moved_pd = prim.polygon_data
|
|
348
|
+
moved_pd._edb_object.Move(vector._edb_object)
|
|
349
|
+
return self.edb.modeler.create_polygon(
|
|
350
|
+
moved_pd,
|
|
351
|
+
layer_name=prim.layer.name,
|
|
352
|
+
net_name=prim.net.name,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def duplicate_void(self, new_poly, void, dx, dy):
|
|
356
|
+
from pyedb.dotnet.database.geometry.point_data import PointData
|
|
357
|
+
|
|
358
|
+
vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
|
|
359
|
+
void_polygon_data = void.polygon_data
|
|
360
|
+
void_polygon_data._edb_object.Move(vector._edb_object)
|
|
361
|
+
new_poly.add_void(void_polygon_data.points)
|
|
362
|
+
|
|
363
|
+
def duplicate_path(self, path, dx, dy, i, j):
|
|
364
|
+
from pyedb.dotnet.database.geometry.point_data import PointData
|
|
365
|
+
|
|
366
|
+
vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
|
|
367
|
+
moved_path = path._edb_object.GetCenterLine()
|
|
368
|
+
moved_path.Move(vector._edb_object)
|
|
369
|
+
moved_path = [[pt.X.ToDouble(), pt.Y.ToDouble()] for pt in list(moved_path.Points)]
|
|
370
|
+
end_caps = path._edb_object.GetEndCapStyle()
|
|
371
|
+
|
|
372
|
+
self.edb.modeler.create_trace(
|
|
373
|
+
path_list=list(moved_path),
|
|
374
|
+
width=path.width,
|
|
375
|
+
layer_name=path.layer.name,
|
|
376
|
+
net_name=path.net.name,
|
|
377
|
+
start_cap_style=str(end_caps[1]),
|
|
378
|
+
end_cap_style=str(end_caps[2]),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def duplicate_standalone_via(self, via, dx, dy, i, j):
|
|
382
|
+
pos = via.position
|
|
383
|
+
self.edb.padstacks.place(
|
|
384
|
+
[pos[0] + dx, pos[1] + dy],
|
|
385
|
+
via.padstack_definition,
|
|
386
|
+
via_name=f"{via.aedt_name}_i{i}_j{j}",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
def duplicate_component(self, comp, dx, dy, i, j):
|
|
390
|
+
new_pins = []
|
|
391
|
+
for pin in comp.pins.values():
|
|
392
|
+
pos = pin.position
|
|
393
|
+
new_pin = self.edb.padstacks.place(
|
|
394
|
+
[pos[0] + dx, pos[1] + dy],
|
|
395
|
+
pin.padstack_definition,
|
|
396
|
+
via_name=f"{pin.aedt_name}_i{i}_j{j}",
|
|
397
|
+
)
|
|
398
|
+
new_pins.append(new_pin)
|
|
399
|
+
|
|
400
|
+
if new_pins:
|
|
401
|
+
new_comp = self.edb.components.create(
|
|
402
|
+
pins=new_pins,
|
|
403
|
+
component_name=f"{comp.name}_array_{i}_{j}",
|
|
404
|
+
placement_layer=comp.placement_layer,
|
|
405
|
+
component_part_name=comp.part_name,
|
|
406
|
+
)
|
|
407
|
+
if hasattr(comp, "component_property"):
|
|
408
|
+
new_comp._edb_object.SetComponentProperty(comp.component_property)
|