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.

Files changed (107) hide show
  1. pyedb/__init__.py +1 -1
  2. pyedb/configuration/cfg_data.py +3 -0
  3. pyedb/configuration/cfg_operations.py +2 -2
  4. pyedb/configuration/cfg_ports_sources.py +1 -1
  5. pyedb/configuration/cfg_terminals.py +232 -0
  6. pyedb/configuration/configuration.py +146 -3
  7. pyedb/dotnet/clr_module.py +1 -2
  8. pyedb/dotnet/database/Variables.py +56 -41
  9. pyedb/dotnet/database/cell/layout.py +5 -1
  10. pyedb/dotnet/database/cell/primitive/primitive.py +2 -2
  11. pyedb/dotnet/database/cell/terminal/bundle_terminal.py +12 -0
  12. pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
  13. pyedb/dotnet/database/cell/terminal/terminal.py +38 -0
  14. pyedb/dotnet/database/components.py +55 -52
  15. pyedb/dotnet/database/dotnet/database.py +1 -0
  16. pyedb/dotnet/database/edb_data/control_file.py +6 -3
  17. pyedb/dotnet/database/edb_data/nets_data.py +3 -3
  18. pyedb/dotnet/database/edb_data/padstacks_data.py +5 -2
  19. pyedb/dotnet/database/edb_data/ports.py +0 -25
  20. pyedb/dotnet/database/edb_data/primitives_data.py +3 -3
  21. pyedb/dotnet/database/edb_data/raptor_x_simulation_setup_data.py +18 -19
  22. pyedb/dotnet/database/edb_data/simulation_configuration.py +3 -3
  23. pyedb/dotnet/database/hfss.py +9 -8
  24. pyedb/dotnet/database/layout_validation.py +6 -3
  25. pyedb/dotnet/database/materials.py +1 -3
  26. pyedb/dotnet/database/modeler.py +7 -3
  27. pyedb/dotnet/database/nets.py +27 -19
  28. pyedb/dotnet/database/padstack.py +91 -2
  29. pyedb/dotnet/database/sim_setup_data/io/siwave.py +1 -1
  30. pyedb/dotnet/database/siwave.py +4 -3
  31. pyedb/dotnet/database/stackup.py +50 -26
  32. pyedb/dotnet/database/utilities/heatsink.py +0 -1
  33. pyedb/dotnet/database/utilities/simulation_setup.py +7 -5
  34. pyedb/dotnet/database/utilities/siwave_cpa_simulation_setup.py +1 -0
  35. pyedb/dotnet/database/utilities/siwave_simulation_setup.py +5 -2
  36. pyedb/dotnet/edb.py +41 -36
  37. pyedb/exceptions.py +1 -2
  38. pyedb/extensions/create_cell_array.py +408 -0
  39. pyedb/generic/data_handlers.py +17 -28
  40. pyedb/generic/design_types.py +25 -38
  41. pyedb/generic/filesystem.py +9 -4
  42. pyedb/generic/general_methods.py +6 -7
  43. pyedb/generic/plot.py +2 -2
  44. pyedb/generic/settings.py +4 -0
  45. pyedb/grpc/database/_typing.py +0 -0
  46. pyedb/grpc/database/components.py +30 -11
  47. pyedb/grpc/database/control_file.py +14 -35
  48. pyedb/grpc/database/definition/materials.py +1 -1
  49. pyedb/grpc/database/definition/package_def.py +6 -3
  50. pyedb/grpc/database/definition/padstack_def.py +4 -7
  51. pyedb/grpc/database/hfss.py +1 -4
  52. pyedb/grpc/database/hierarchy/component.py +3 -4
  53. pyedb/grpc/database/hierarchy/pingroup.py +16 -3
  54. pyedb/grpc/database/layers/layer.py +1 -2
  55. pyedb/grpc/database/layers/stackup_layer.py +42 -19
  56. pyedb/grpc/database/layout/layout.py +117 -28
  57. pyedb/grpc/database/layout/voltage_regulator.py +6 -1
  58. pyedb/grpc/database/layout_validation.py +7 -4
  59. pyedb/grpc/database/modeler.py +241 -256
  60. pyedb/grpc/database/net/differential_pair.py +9 -2
  61. pyedb/grpc/database/net/extended_net.py +24 -9
  62. pyedb/grpc/database/net/net.py +14 -5
  63. pyedb/grpc/database/net/net_class.py +24 -7
  64. pyedb/grpc/database/nets.py +11 -43
  65. pyedb/grpc/database/padstacks.py +92 -16
  66. pyedb/grpc/database/primitive/bondwire.py +3 -67
  67. pyedb/grpc/database/primitive/circle.py +42 -3
  68. pyedb/grpc/database/primitive/padstack_instance.py +17 -19
  69. pyedb/grpc/database/primitive/path.py +154 -5
  70. pyedb/grpc/database/primitive/polygon.py +75 -9
  71. pyedb/grpc/database/primitive/primitive.py +2 -2
  72. pyedb/grpc/database/primitive/rectangle.py +105 -4
  73. pyedb/grpc/database/simulation_setup/hfss_general_settings.py +0 -2
  74. pyedb/grpc/database/simulation_setup/hfss_settings_options.py +0 -4
  75. pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +4 -2
  76. pyedb/grpc/database/simulation_setup/sweep_data.py +1 -3
  77. pyedb/grpc/database/siwave.py +6 -13
  78. pyedb/grpc/database/source_excitations.py +49 -57
  79. pyedb/grpc/database/stackup.py +50 -27
  80. pyedb/grpc/database/terminal/bundle_terminal.py +10 -3
  81. pyedb/grpc/database/terminal/pingroup_terminal.py +8 -1
  82. pyedb/grpc/database/terminal/terminal.py +19 -8
  83. pyedb/grpc/database/utility/heat_sink.py +0 -1
  84. pyedb/grpc/database/utility/hfss_extent_info.py +2 -2
  85. pyedb/grpc/database/utility/value.py +1 -0
  86. pyedb/grpc/database/utility/xml_control_file.py +6 -3
  87. pyedb/grpc/edb.py +33 -24
  88. pyedb/grpc/edb_init.py +1 -0
  89. pyedb/grpc/rpc_session.py +4 -3
  90. pyedb/ipc2581/ecad/cad_data/layer_feature.py +6 -2
  91. pyedb/ipc2581/ecad/cad_data/step.py +1 -1
  92. pyedb/ipc2581/ipc2581.py +8 -7
  93. pyedb/libraries/common.py +3 -4
  94. pyedb/libraries/rf_libraries/base_functions.py +7 -16
  95. pyedb/libraries/rf_libraries/planar_antennas.py +3 -21
  96. pyedb/misc/downloads.py +1 -0
  97. pyedb/misc/misc.py +5 -2
  98. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +1 -1
  99. pyedb/misc/utilities.py +0 -1
  100. pyedb/modeler/geometry_operators.py +9 -8
  101. pyedb/siwave.py +4 -6
  102. pyedb/siwave_core/__init__.py +0 -0
  103. pyedb/siwave_core/cpa/__init__.py +0 -0
  104. {pyedb-0.55.0.dist-info → pyedb-0.57.0.dist-info}/METADATA +3 -3
  105. {pyedb-0.55.0.dist-info → pyedb-0.57.0.dist-info}/RECORD +107 -102
  106. {pyedb-0.55.0.dist-info → pyedb-0.57.0.dist-info}/WHEEL +0 -0
  107. {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
- pass
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['s1'] = "0.25 mm"
156
- >>> app['s1'].tofloat
156
+ >>> app["s1"] = "0.25 mm"
157
+ >>> app["s1"].tofloat
157
158
  >>> 0.00025
158
- >>> app['s1'].tostring
159
+ >>> app["s1"].tostring
159
160
  >>> "0.25mm"
160
161
 
161
162
  or add a new parameter with description:
162
163
 
163
- >>> app['s2'] = ["20um", "Spacing between traces"]
164
- >>> app['s2'].value
164
+ >>> app["s2"] = ["20um", "Spacing between traces"]
165
+ >>> app["s2"].value
165
166
  >>> 1.9999999999999998e-05
166
- >>> app['s2'].description
167
- >>> 'Spacing between traces'
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
- pass
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'C:\\test.aedb', version="2022.2")
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
- pass
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'C:\\test.aedb', version="2022.2")
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
- pass
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 = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
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 = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
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 = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
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"]) #using getitem
3373
- >>> edb_app["$my_local_variable"] = "1cm" #using setitem
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"]) #using getitem
3412
- >>> edb_app["my_local_variable"] = "1cm" #using setitem
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"]) #using getitem
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(frequency_sweep=[
3932
- ... ["linear count", "0", "1kHz", 1],
3933
- ... ["log scale", "1kHz", "0.1GHz", 10],
3934
- ... ["linear scale", "0.1GHz", "10GHz", "0.1GHz"],
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.save_edb()
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.version[0] >= 10:
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" "waveports"
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
@@ -1,5 +1,4 @@
1
- """
2
- """
1
+ """ """
3
2
 
4
3
 
5
4
  class MaterialModelException(Exception):
@@ -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)