pyedb 0.38.0__py3-none-any.whl → 0.39.1__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 (205) hide show
  1. pyedb/__init__.py +1 -1
  2. pyedb/common/nets.py +53 -139
  3. pyedb/configuration/cfg_components.py +1 -1
  4. pyedb/configuration/cfg_general.py +4 -2
  5. pyedb/configuration/cfg_modeler.py +1 -1
  6. pyedb/configuration/cfg_package_definition.py +1 -1
  7. pyedb/configuration/cfg_padstacks.py +1 -1
  8. pyedb/configuration/cfg_ports_sources.py +56 -23
  9. pyedb/configuration/configuration.py +18 -1
  10. pyedb/dotnet/{application → database}/Variables.py +21 -21
  11. pyedb/dotnet/{edb_core → database}/cell/connectable.py +5 -5
  12. pyedb/dotnet/{edb_core → database}/cell/hierarchy/component.py +11 -11
  13. pyedb/dotnet/{edb_core → database}/cell/hierarchy/hierarchy_obj.py +1 -1
  14. pyedb/dotnet/{edb_core → database}/cell/hierarchy/model.py +1 -1
  15. pyedb/dotnet/{edb_core → database}/cell/layout.py +17 -17
  16. pyedb/dotnet/{edb_core → database}/cell/layout_obj.py +3 -3
  17. pyedb/dotnet/{edb_core → database}/cell/primitive/bondwire.py +1 -1
  18. pyedb/dotnet/{edb_core → database}/cell/primitive/path.py +4 -4
  19. pyedb/dotnet/{edb_core → database}/cell/primitive/primitive.py +14 -14
  20. pyedb/dotnet/{edb_core → database}/cell/terminal/bundle_terminal.py +2 -2
  21. pyedb/dotnet/{edb_core → database}/cell/terminal/edge_terminal.py +4 -4
  22. pyedb/dotnet/{edb_core → database}/cell/terminal/padstack_instance_terminal.py +2 -2
  23. pyedb/dotnet/{edb_core → database}/cell/terminal/pingroup_terminal.py +2 -2
  24. pyedb/dotnet/{edb_core → database}/cell/terminal/point_terminal.py +2 -2
  25. pyedb/dotnet/{edb_core → database}/cell/terminal/terminal.py +11 -11
  26. pyedb/dotnet/{edb_core → database}/cell/voltage_regulator.py +2 -2
  27. pyedb/dotnet/{edb_core → database}/components.py +101 -124
  28. pyedb/dotnet/{edb_core → database}/definition/component_def.py +5 -5
  29. pyedb/dotnet/{edb_core → database}/definition/component_model.py +1 -1
  30. pyedb/dotnet/{edb_core → database}/definition/definition_obj.py +1 -1
  31. pyedb/dotnet/{edb_core → database}/definition/definitions.py +2 -2
  32. pyedb/dotnet/{edb_core → database}/definition/package_def.py +4 -4
  33. pyedb/dotnet/{edb_core → database}/dotnet/database.py +8 -8
  34. pyedb/dotnet/{edb_core → database}/dotnet/primitive.py +9 -9
  35. pyedb/dotnet/{edb_core → database}/edb_data/control_file.py +12 -12
  36. pyedb/dotnet/{edb_core → database}/edb_data/hfss_extent_info.py +7 -7
  37. pyedb/dotnet/{edb_core → database}/edb_data/nets_data.py +10 -13
  38. pyedb/dotnet/{edb_core → database}/edb_data/padstacks_data.py +16 -16
  39. pyedb/dotnet/{edb_core → database}/edb_data/ports.py +4 -4
  40. pyedb/dotnet/{edb_core → database}/edb_data/primitives_data.py +5 -5
  41. pyedb/dotnet/{edb_core → database}/edb_data/raptor_x_simulation_setup_data.py +4 -4
  42. pyedb/dotnet/{edb_core → database}/edb_data/simulation_configuration.py +10 -10
  43. pyedb/dotnet/{edb_core → database}/edb_data/sources.py +4 -4
  44. pyedb/dotnet/{edb_core → database}/edb_data/variables.py +1 -1
  45. pyedb/dotnet/{edb_core → database}/geometry/polygon_data.py +4 -4
  46. pyedb/dotnet/{edb_core → database}/hfss.py +8 -8
  47. pyedb/dotnet/{edb_core → database}/layout_obj_instance.py +1 -1
  48. pyedb/dotnet/{edb_core → database}/layout_validation.py +2 -2
  49. pyedb/dotnet/{edb_core → database}/materials.py +23 -8
  50. pyedb/dotnet/{edb_core → database}/modeler.py +27 -27
  51. pyedb/dotnet/{edb_core → database}/net_class.py +8 -8
  52. pyedb/dotnet/{edb_core → database}/nets.py +12 -12
  53. pyedb/dotnet/{edb_core → database}/padstack.py +15 -15
  54. pyedb/dotnet/{edb_core → database}/sim_setup_data/data/mesh_operation.py +1 -1
  55. pyedb/dotnet/{edb_core → database}/sim_setup_data/data/settings.py +3 -3
  56. pyedb/dotnet/{edb_core → database}/sim_setup_data/data/sim_setup_info.py +2 -2
  57. pyedb/dotnet/{edb_core → database}/sim_setup_data/data/simulation_settings.py +1 -1
  58. pyedb/dotnet/{edb_core → database}/sim_setup_data/data/siw_dc_ir_settings.py +1 -1
  59. pyedb/dotnet/{edb_core → database}/sim_setup_data/data/sweep_data.py +1 -1
  60. pyedb/dotnet/{edb_core → database}/siwave.py +10 -10
  61. pyedb/dotnet/{edb_core → database}/stackup.py +12 -12
  62. pyedb/dotnet/{edb_core → database}/utilities/hfss_simulation_setup.py +15 -15
  63. pyedb/dotnet/{edb_core → database}/utilities/obj_base.py +1 -1
  64. pyedb/dotnet/{edb_core → database}/utilities/simulation_setup.py +3 -3
  65. pyedb/dotnet/{edb_core → database}/utilities/siwave_simulation_setup.py +6 -6
  66. pyedb/dotnet/edb.py +117 -112
  67. pyedb/generic/design_types.py +26 -19
  68. pyedb/generic/general_methods.py +1 -1
  69. pyedb/generic/plot.py +0 -2
  70. pyedb/grpc/database/__init__.py +1 -0
  71. pyedb/grpc/database/components.py +2354 -0
  72. pyedb/grpc/database/control_file.py +1277 -0
  73. pyedb/grpc/database/definition/component_def.py +218 -0
  74. pyedb/grpc/database/definition/component_model.py +39 -0
  75. pyedb/grpc/database/definition/component_pin.py +32 -0
  76. pyedb/grpc/database/definition/materials.py +1207 -0
  77. pyedb/grpc/database/definition/n_port_component_model.py +34 -0
  78. pyedb/grpc/database/definition/package_def.py +227 -0
  79. pyedb/grpc/database/definition/padstack_def.py +842 -0
  80. pyedb/grpc/database/definitions.py +70 -0
  81. pyedb/grpc/database/general.py +43 -0
  82. pyedb/grpc/database/geometry/__init__.py +0 -0
  83. pyedb/grpc/database/geometry/arc_data.py +93 -0
  84. pyedb/grpc/database/geometry/point_3d_data.py +79 -0
  85. pyedb/grpc/database/geometry/point_data.py +30 -0
  86. pyedb/grpc/database/geometry/polygon_data.py +133 -0
  87. pyedb/grpc/database/hfss.py +1279 -0
  88. pyedb/grpc/database/hierarchy/__init__.py +0 -0
  89. pyedb/grpc/database/hierarchy/component.py +1301 -0
  90. pyedb/grpc/database/hierarchy/model.py +31 -0
  91. pyedb/grpc/database/hierarchy/netlist_model.py +30 -0
  92. pyedb/grpc/database/hierarchy/pin_pair_model.py +128 -0
  93. pyedb/grpc/database/hierarchy/pingroup.py +245 -0
  94. pyedb/grpc/database/hierarchy/s_parameter_model.py +33 -0
  95. pyedb/grpc/database/hierarchy/spice_model.py +48 -0
  96. pyedb/grpc/database/layers/__init__.py +0 -0
  97. pyedb/grpc/database/layers/layer.py +57 -0
  98. pyedb/grpc/database/layers/stackup_layer.py +410 -0
  99. pyedb/grpc/database/layout/__init__.py +0 -0
  100. pyedb/grpc/database/layout/cell.py +30 -0
  101. pyedb/grpc/database/layout/layout.py +196 -0
  102. pyedb/grpc/database/layout/voltage_regulator.py +149 -0
  103. pyedb/grpc/database/layout_validation.py +319 -0
  104. pyedb/grpc/database/modeler.py +1468 -0
  105. pyedb/grpc/database/net/__init__.py +0 -0
  106. pyedb/grpc/database/net/differential_pair.py +138 -0
  107. pyedb/grpc/database/net/extended_net.py +340 -0
  108. pyedb/grpc/database/net/net.py +198 -0
  109. pyedb/grpc/database/net/net_class.py +93 -0
  110. pyedb/grpc/database/nets.py +633 -0
  111. pyedb/grpc/database/padstacks.py +1500 -0
  112. pyedb/grpc/database/ports/__init__.py +0 -0
  113. pyedb/grpc/database/ports/ports.py +396 -0
  114. pyedb/grpc/database/primitive/__init__.py +3 -0
  115. pyedb/grpc/database/primitive/bondwire.py +181 -0
  116. pyedb/grpc/database/primitive/circle.py +75 -0
  117. pyedb/grpc/database/primitive/padstack_instance.py +1116 -0
  118. pyedb/grpc/database/primitive/path.py +346 -0
  119. pyedb/grpc/database/primitive/polygon.py +276 -0
  120. pyedb/grpc/database/primitive/primitive.py +739 -0
  121. pyedb/grpc/database/primitive/rectangle.py +146 -0
  122. pyedb/grpc/database/simulation_setup/__init__.py +0 -0
  123. pyedb/grpc/database/simulation_setup/adaptive_frequency.py +33 -0
  124. pyedb/grpc/database/simulation_setup/hfss_advanced_meshing_settings.py +32 -0
  125. pyedb/grpc/database/simulation_setup/hfss_advanced_settings.py +59 -0
  126. pyedb/grpc/database/simulation_setup/hfss_dcr_settings.py +35 -0
  127. pyedb/grpc/database/simulation_setup/hfss_general_settings.py +61 -0
  128. pyedb/grpc/database/simulation_setup/hfss_settings_options.py +78 -0
  129. pyedb/grpc/database/simulation_setup/hfss_simulation_settings.py +118 -0
  130. pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +355 -0
  131. pyedb/grpc/database/simulation_setup/hfss_solver_settings.py +34 -0
  132. pyedb/grpc/database/simulation_setup/mesh_operation.py +34 -0
  133. pyedb/grpc/database/simulation_setup/raptor_x_advanced_settings.py +34 -0
  134. pyedb/grpc/database/simulation_setup/raptor_x_general_settings.py +33 -0
  135. pyedb/grpc/database/simulation_setup/raptor_x_simulation_settings.py +64 -0
  136. pyedb/grpc/database/simulation_setup/raptor_x_simulation_setup.py +125 -0
  137. pyedb/grpc/database/simulation_setup/siwave_dcir_simulation_setup.py +34 -0
  138. pyedb/grpc/database/simulation_setup/siwave_simulation_setup.py +119 -0
  139. pyedb/grpc/database/simulation_setup/sweep_data.py +32 -0
  140. pyedb/grpc/database/siwave.py +1023 -0
  141. pyedb/grpc/database/source_excitations.py +2572 -0
  142. pyedb/grpc/database/stackup.py +2574 -0
  143. pyedb/grpc/database/terminal/__init__.py +0 -0
  144. pyedb/grpc/database/terminal/bundle_terminal.py +218 -0
  145. pyedb/grpc/database/terminal/edge_terminal.py +51 -0
  146. pyedb/grpc/database/terminal/padstack_instance_terminal.py +171 -0
  147. pyedb/grpc/database/terminal/pingroup_terminal.py +162 -0
  148. pyedb/grpc/database/terminal/point_terminal.py +99 -0
  149. pyedb/grpc/database/terminal/terminal.py +470 -0
  150. pyedb/grpc/database/utility/__init__.py +3 -0
  151. pyedb/grpc/database/utility/constants.py +25 -0
  152. pyedb/grpc/database/utility/heat_sink.py +124 -0
  153. pyedb/grpc/database/utility/hfss_extent_info.py +448 -0
  154. pyedb/grpc/database/utility/layout_statistics.py +277 -0
  155. pyedb/grpc/database/utility/rlc.py +80 -0
  156. pyedb/grpc/database/utility/simulation_configuration.py +3305 -0
  157. pyedb/grpc/database/utility/sources.py +388 -0
  158. pyedb/grpc/database/utility/sweep_data_distribution.py +83 -0
  159. pyedb/grpc/database/utility/xml_control_file.py +1277 -0
  160. pyedb/grpc/edb.py +4152 -0
  161. pyedb/grpc/edb_init.py +481 -0
  162. pyedb/grpc/rpc_session.py +177 -0
  163. pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +3 -2
  164. pyedb/ipc2581/ecad/cad_data/feature.py +4 -3
  165. pyedb/ipc2581/ecad/cad_data/layer_feature.py +32 -20
  166. pyedb/ipc2581/ecad/cad_data/outline.py +3 -2
  167. pyedb/ipc2581/ecad/cad_data/package.py +4 -3
  168. pyedb/ipc2581/ecad/cad_data/path.py +82 -31
  169. pyedb/ipc2581/ecad/cad_data/polygon.py +122 -60
  170. pyedb/ipc2581/ecad/cad_data/profile.py +13 -12
  171. pyedb/ipc2581/ecad/cad_data/step.py +53 -21
  172. pyedb/ipc2581/ipc2581.py +47 -49
  173. pyedb/modeler/geometry_operators.py +1 -1
  174. {pyedb-0.38.0.dist-info → pyedb-0.39.1.dist-info}/METADATA +5 -2
  175. pyedb-0.39.1.dist-info/RECORD +288 -0
  176. pyedb-0.38.0.dist-info/RECORD +0 -195
  177. /pyedb/dotnet/{edb_core → database}/__init__.py +0 -0
  178. /pyedb/dotnet/{application → database/cell}/__init__.py +0 -0
  179. /pyedb/dotnet/{edb_core/cell → database/cell/hierarchy}/__init__.py +0 -0
  180. /pyedb/dotnet/{edb_core → database}/cell/hierarchy/netlist_model.py +0 -0
  181. /pyedb/dotnet/{edb_core → database}/cell/hierarchy/pin_pair_model.py +0 -0
  182. /pyedb/dotnet/{edb_core → database}/cell/hierarchy/s_parameter_model.py +0 -0
  183. /pyedb/dotnet/{edb_core → database}/cell/hierarchy/spice_model.py +0 -0
  184. /pyedb/dotnet/{edb_core → database}/cell/primitive/__init__.py +0 -0
  185. /pyedb/dotnet/{edb_core/cell/hierarchy → database/cell/terminal}/__init__.py +0 -0
  186. /pyedb/dotnet/{edb_core/cell/terminal → database/definition}/__init__.py +0 -0
  187. /pyedb/dotnet/{edb_core/definition → database/dotnet}/__init__.py +0 -0
  188. /pyedb/dotnet/{edb_core/dotnet → database/edb_data}/__init__.py +0 -0
  189. /pyedb/dotnet/{edb_core → database}/edb_data/design_options.py +0 -0
  190. /pyedb/dotnet/{edb_core → database}/edb_data/edbvalue.py +0 -0
  191. /pyedb/dotnet/{edb_core → database}/edb_data/layer_data.py +0 -0
  192. /pyedb/dotnet/{edb_core → database}/edb_data/utilities.py +0 -0
  193. /pyedb/dotnet/{edb_core → database}/general.py +0 -0
  194. /pyedb/dotnet/{edb_core/edb_data → database/geometry}/__init__.py +0 -0
  195. /pyedb/dotnet/{edb_core → database}/geometry/point_data.py +0 -0
  196. /pyedb/dotnet/{edb_core → database}/sim_setup_data/__init__.py +0 -0
  197. /pyedb/dotnet/{edb_core → database}/sim_setup_data/data/__init__.py +0 -0
  198. /pyedb/dotnet/{edb_core → database}/sim_setup_data/data/adaptive_frequency_data.py +0 -0
  199. /pyedb/dotnet/{edb_core/geometry → database/sim_setup_data/io}/__init__.py +0 -0
  200. /pyedb/dotnet/{edb_core → database}/sim_setup_data/io/siwave.py +0 -0
  201. /pyedb/dotnet/{edb_core → database}/utilities/__init__.py +0 -0
  202. /pyedb/dotnet/{edb_core → database}/utilities/heatsink.py +0 -0
  203. /pyedb/{dotnet/edb_core/sim_setup_data/io → grpc/database/definition}/__init__.py +0 -0
  204. {pyedb-0.38.0.dist-info → pyedb-0.39.1.dist-info}/LICENSE +0 -0
  205. {pyedb-0.38.0.dist-info → pyedb-0.39.1.dist-info}/WHEEL +0 -0
pyedb/grpc/edb.py ADDED
@@ -0,0 +1,4152 @@
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
+ """This module contains the ``Edb`` class.
24
+
25
+ This module is implicitly loaded in HFSS 3D Layout when launched.
26
+
27
+ """
28
+
29
+ from itertools import combinations
30
+ import os
31
+ import re
32
+ import shutil
33
+ import subprocess
34
+ import sys
35
+ import tempfile
36
+ import time
37
+ import traceback
38
+ import warnings
39
+ from zipfile import ZipFile as zpf
40
+
41
+ from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData
42
+ from ansys.edb.core.simulation_setup.siwave_dcir_simulation_setup import (
43
+ SIWaveDCIRSimulationSetup as GrpcSIWaveDCIRSimulationSetup,
44
+ )
45
+ from ansys.edb.core.utility.value import Value as GrpcValue
46
+ import rtree
47
+
48
+ from pyedb.configuration.configuration import Configuration
49
+ from pyedb.generic.general_methods import (
50
+ generate_unique_name,
51
+ get_string_version,
52
+ is_linux,
53
+ is_windows,
54
+ )
55
+ from pyedb.generic.process import SiwaveSolve
56
+ from pyedb.generic.settings import settings
57
+ from pyedb.grpc.database.components import Components
58
+ from pyedb.grpc.database.control_file import ControlFile, convert_technology_file
59
+ from pyedb.grpc.database.definition.materials import Materials
60
+ from pyedb.grpc.database.hfss import Hfss
61
+ from pyedb.grpc.database.layout.layout import Layout
62
+ from pyedb.grpc.database.layout_validation import LayoutValidation
63
+ from pyedb.grpc.database.modeler import Modeler
64
+ from pyedb.grpc.database.net.differential_pair import DifferentialPairs
65
+ from pyedb.grpc.database.net.extended_net import ExtendedNets
66
+ from pyedb.grpc.database.net.net_class import NetClass
67
+ from pyedb.grpc.database.nets import Nets
68
+ from pyedb.grpc.database.padstacks import Padstacks
69
+ from pyedb.grpc.database.ports.ports import BundleWavePort, CoaxPort, GapPort, WavePort
70
+ from pyedb.grpc.database.primitive.circle import Circle
71
+ from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
72
+ from pyedb.grpc.database.primitive.path import Path
73
+ from pyedb.grpc.database.primitive.polygon import Polygon
74
+ from pyedb.grpc.database.primitive.rectangle import Rectangle
75
+ from pyedb.grpc.database.simulation_setup.hfss_simulation_setup import (
76
+ HfssSimulationSetup,
77
+ )
78
+ from pyedb.grpc.database.simulation_setup.raptor_x_simulation_setup import (
79
+ RaptorXSimulationSetup,
80
+ )
81
+ from pyedb.grpc.database.simulation_setup.siwave_dcir_simulation_setup import (
82
+ SIWaveDCIRSimulationSetup,
83
+ )
84
+ from pyedb.grpc.database.simulation_setup.siwave_simulation_setup import (
85
+ SiwaveSimulationSetup,
86
+ )
87
+ from pyedb.grpc.database.siwave import Siwave
88
+ from pyedb.grpc.database.source_excitations import SourceExcitation
89
+ from pyedb.grpc.database.stackup import Stackup
90
+ from pyedb.grpc.database.terminal.padstack_instance_terminal import (
91
+ PadstackInstanceTerminal,
92
+ )
93
+ from pyedb.grpc.database.terminal.terminal import Terminal
94
+ from pyedb.grpc.database.utility.constants import get_terminal_supported_boundary_types
95
+ from pyedb.grpc.edb_init import EdbInit
96
+ from pyedb.ipc2581.ipc2581 import Ipc2581
97
+ from pyedb.modeler.geometry_operators import GeometryOperators
98
+ from pyedb.workflow import Workflow
99
+
100
+
101
+ class Edb(EdbInit):
102
+ """Provides the EDB application interface.
103
+
104
+ This module inherits all objects that belong to EDB.
105
+
106
+ Parameters
107
+ ----------
108
+ edbpath : str, optional
109
+ Full path to the ``aedb`` folder. The variable can also contain
110
+ the path to a layout to import. Allowed formats are BRD, MCM,
111
+ XML (IPC2581), GDS, and DXF. The default is ``None``.
112
+ For GDS import, the Ansys control file (also XML) should have the same
113
+ name as the GDS file. Only the file extension differs.
114
+ cellname : str, optional
115
+ Name of the cell to select. The default is ``None``.
116
+ isreadonly : bool, optional
117
+ Whether to open EBD in read-only mode when it is
118
+ owned by HFSS 3D Layout. The default is ``False``.
119
+ edbversion : str, int, float, optional
120
+ Version of EDB to use. The default is ``None``.
121
+ Examples of input values are ``252``, ``25.2``,``2025.2``,``"2025.2"``.
122
+ isaedtowned : bool, optional
123
+ Whether to launch EDB from HFSS 3D Layout. The
124
+ default is ``False``.
125
+ oproject : optional
126
+ Reference to the AEDT project object.
127
+ technology_file : str, optional
128
+ Technology file full path to be converted to XML before importing or XML. Supported by GDS format only.
129
+ restart_rpc_server : bool, optional
130
+ ``True`` RPC server is terminated and restarted. This will close all open EDB. RPC server is running on single
131
+ instance loading all EDB, enabling this option should be used with caution but can be a solution to release
132
+ memory in case the server is draining resources. Default value is ``False``.
133
+
134
+ Examples
135
+ --------
136
+ Create :class:`Edb <pyedb.grpc.edb.Edb>` object.
137
+
138
+ >>> from pyedb.grpc.edb import Edb as Edb
139
+ >>> app = Edb()
140
+
141
+ Add a new variable named "s1" to the ``Edb`` instance.
142
+
143
+ >>> app['s1'] = "0.25 mm"
144
+ >>> app['s1']
145
+ >>> 0.00025
146
+
147
+ Create an ``Edb`` object and open the specified project.
148
+
149
+ >>> app = Edb(edbpath="myfile.aedb")
150
+
151
+ Create an ``Edb`` object from GDS and control files.
152
+ The XML control file resides in the same directory as the GDS file: (myfile.xml).
153
+
154
+ >>> app = Edb("/path/to/file/myfile.gds")
155
+
156
+ Loading Ansys layout
157
+
158
+ >>> from ansys.aedt.core.generic.general_methods import generate_unique_folder_name
159
+ >>> import pyedb.misc.downloads as downloads
160
+ >>> temp_folder = generate_unique_folder_name()
161
+ >>> targetfile = downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_folder)
162
+ >>> from pyedb.grpc.edb import Edb as Edb
163
+ >>> edbapp = Edb(edbpath=targetfile)
164
+
165
+ Retrieving signal nets dictionary
166
+
167
+ >>> edbapp.nets.signal
168
+
169
+ Retrieving layers
170
+
171
+ >>> edbapp.stackup.layers
172
+
173
+ Retrieving all component instances
174
+
175
+ >>> edbapp.components.instances
176
+
177
+ Retrieving all padstacks definitions
178
+
179
+ >>> edbapp.padstacks.definitions
180
+
181
+ Retrieving component pins
182
+
183
+ >>> edbapp.components["U1"].pins
184
+
185
+ """
186
+
187
+ def __init__(
188
+ self,
189
+ edbpath=None,
190
+ cellname=None,
191
+ isreadonly=False,
192
+ edbversion=None,
193
+ isaedtowned=False,
194
+ oproject=None,
195
+ student_version: bool = False,
196
+ use_ppe=False,
197
+ technology_file=None,
198
+ restart_rpc_server=False,
199
+ kill_all_instances=False,
200
+ ):
201
+ edbversion = get_string_version(edbversion)
202
+ self._clean_variables()
203
+ EdbInit.__init__(self, edbversion=edbversion)
204
+ self.standalone = True
205
+ self.oproject = oproject
206
+ self._main = sys.modules["__main__"]
207
+ self.edbversion = edbversion
208
+ if not float(self.edbversion) >= 2025.2:
209
+ raise "EDB gRPC is only supported with ANSYS release 2025R2 and higher."
210
+ self.logger.info("Using PyEDB with gRPC as Beta until ANSYS 2025R2 official release.")
211
+ self.isaedtowned = isaedtowned
212
+ self.isreadonly = isreadonly
213
+ self._setups = {}
214
+ if cellname:
215
+ self.cellname = cellname
216
+ else:
217
+ self.cellname = ""
218
+ if not edbpath:
219
+ if is_windows:
220
+ edbpath = os.getenv("USERPROFILE")
221
+ if not edbpath:
222
+ edbpath = os.path.expanduser("~")
223
+ edbpath = os.path.join(edbpath, "Documents", generate_unique_name("layout") + ".aedb")
224
+ else:
225
+ edbpath = os.getenv("HOME")
226
+ if not edbpath:
227
+ edbpath = os.path.expanduser("~")
228
+ edbpath = os.path.join(edbpath, generate_unique_name("layout") + ".aedb")
229
+ self.logger.info("No EDB is provided. Creating a new EDB {}.".format(edbpath))
230
+ self.edbpath = edbpath
231
+ self.log_name = None
232
+ if edbpath:
233
+ self.log_name = os.path.join(
234
+ os.path.dirname(edbpath), "pyaedt_" + os.path.splitext(os.path.split(edbpath)[-1])[0] + ".log"
235
+ )
236
+ if edbpath[-3:] == "zip":
237
+ self.edbpath = edbpath[:-4] + ".aedb"
238
+ working_dir = os.path.dirname(edbpath)
239
+ zipped_file = zpf(edbpath, "r")
240
+ top_level_folders = {item.split("/")[0] for item in zipped_file.namelist()}
241
+ if len(top_level_folders) == 1:
242
+ self.logger.info("Unzipping ODB++...")
243
+ zipped_file.extractall(working_dir)
244
+ else:
245
+ self.logger.info("Unzipping ODB++ before translating to EDB...")
246
+ zipped_file.extractall(edbpath[:-4])
247
+ self.logger.info("ODB++ unzipped successfully.")
248
+ zipped_file.close()
249
+ control_file = None
250
+ if technology_file:
251
+ if os.path.splitext(technology_file)[1] == ".xml":
252
+ control_file = technology_file
253
+ else:
254
+ control_file = convert_technology_file(technology_file, edbversion=edbversion)
255
+ self.logger.info("Translating ODB++ to EDB...")
256
+ self.import_layout_pcb(edbpath[:-4], working_dir, use_ppe=use_ppe, control_file=control_file)
257
+ if settings.enable_local_log_file and self.log_name:
258
+ self.logger.add_file_logger(self.log_name, "Edb")
259
+ self.logger.info("EDB %s was created correctly from %s file.", self.edbpath, edbpath)
260
+
261
+ elif edbpath[-3:] in ["brd", "mcm", "gds", "xml", "dxf", "tgz"]:
262
+ self.edbpath = edbpath[:-4] + ".aedb"
263
+ working_dir = os.path.dirname(edbpath)
264
+ control_file = None
265
+ if technology_file:
266
+ if os.path.splitext(technology_file)[1] == ".xml":
267
+ control_file = technology_file
268
+ else:
269
+ control_file = convert_technology_file(technology_file, edbversion=edbversion)
270
+ self.import_layout_pcb(edbpath, working_dir, use_ppe=use_ppe, control_file=control_file)
271
+ self.logger.info("EDB %s was created correctly from %s file.", self.edbpath, edbpath[-2:])
272
+ elif edbpath.endswith("edb.def"):
273
+ self.edbpath = os.path.dirname(edbpath)
274
+ self.open_edb(restart_rpc_server=restart_rpc_server, kill_all_instances=kill_all_instances)
275
+ elif not os.path.exists(os.path.join(self.edbpath, "edb.def")):
276
+ self.create_edb(restart_rpc_server=restart_rpc_server, kill_all_instances=kill_all_instances)
277
+ self.logger.info("EDB %s created correctly.", self.edbpath)
278
+ elif ".aedb" in edbpath:
279
+ self.edbpath = edbpath
280
+ self.open_edb(restart_rpc_server=restart_rpc_server)
281
+ if self.active_cell:
282
+ self.logger.info("EDB initialized.")
283
+ else:
284
+ self.logger.info("Failed to initialize EDB.")
285
+ self._layout_instance = None
286
+
287
+ def __enter__(self):
288
+ return self
289
+
290
+ def __exit__(self, ex_type, ex_value, ex_traceback):
291
+ if ex_type:
292
+ self.edb_exception(ex_value, ex_traceback)
293
+
294
+ def __getitem__(self, variable_name):
295
+ """Get a variable to the Edb project. The variable can be project using ``$`` prefix or
296
+ it can be a design variable, in which case the ``$`` is omitted.
297
+
298
+ Parameters
299
+ ----------
300
+ variable_name : str
301
+
302
+ Returns
303
+ -------
304
+ variable object.
305
+
306
+ """
307
+ if self.variable_exists(variable_name):
308
+ return self.variables[variable_name]
309
+ return
310
+
311
+ def __setitem__(self, variable_name, variable_value):
312
+ """Set a variable to the Edb project. The variable can be project using ``$`` prefix or
313
+ it can be a design variable, in which case the ``$`` is omitted.
314
+
315
+ Parameters
316
+ ----------
317
+ variable_name : str
318
+ variable name.
319
+ variable_value : str, float, int.
320
+ variable value.
321
+ """
322
+ type_error_message = "Allowed values are str, numeric or two-item list with variable description."
323
+ if type(variable_value) in [
324
+ list,
325
+ tuple,
326
+ ]: # Two-item list or tuple. 2nd argument is a str description.
327
+ if len(variable_value) == 2:
328
+ if type(variable_value[1]) is str:
329
+ description = variable_value[1] if len(variable_value[1]) > 0 else None
330
+ else:
331
+ description = None
332
+ self.logger.warning("Invalid type for Edb variable description is ignored.")
333
+ val = variable_value[0]
334
+ else:
335
+ raise TypeError(type_error_message)
336
+ else:
337
+ description = None
338
+ val = variable_value
339
+ if self.variable_exists(variable_name):
340
+ self.change_design_variable_value(variable_name, val)
341
+ else:
342
+ if variable_name.startswith("$"):
343
+ self.add_project_variable(variable_name, val)
344
+ else:
345
+ self.add_design_variable(variable_name, val)
346
+ if description: # Add the variable description if a two-item list is passed for variable_value.
347
+ self.__getitem__(variable_name).description = description
348
+
349
+ def _check_remove_project_files(self, edbpath: str, remove_existing_aedt: bool) -> None:
350
+ aedt_file = os.path.splitext(edbpath)[0] + ".aedt"
351
+ files = [aedt_file, aedt_file + ".lock"]
352
+ for file in files:
353
+ if os.path.isfile(file):
354
+ if not remove_existing_aedt:
355
+ self.logger.warning(
356
+ f"AEDT project-related file {file} exists and may need to be deleted before opening the EDB in "
357
+ f"HFSS 3D Layout."
358
+ # noqa: E501
359
+ )
360
+ else:
361
+ try:
362
+ os.unlink(file)
363
+ self.logger.info(f"Deleted AEDT project-related file {file}.")
364
+ except:
365
+ self.logger.info(f"Failed to delete AEDT project-related file {file}.")
366
+
367
+ def _clean_variables(self):
368
+ """Initialize internal variables and perform garbage collection."""
369
+ self.grpc = True
370
+ self._materials = None
371
+ self._components = None
372
+ self._core_primitives = None
373
+ self._stackup = None
374
+ self._padstack = None
375
+ self._siwave = None
376
+ self._hfss = None
377
+ self._nets = None
378
+ self._layout_instance = None
379
+ self._variables = None
380
+ self._active_cell = None
381
+ self._layout = None
382
+ self._configuration = None
383
+
384
+ def _init_objects(self):
385
+ self._components = Components(self)
386
+ self._stackup = Stackup(self, self.layout.layer_collection)
387
+ self._padstack = Padstacks(self)
388
+ self._siwave = Siwave(self)
389
+ self._hfss = Hfss(self)
390
+ self._nets = Nets(self)
391
+ self._modeler = Modeler(self)
392
+ self._materials = Materials(self)
393
+ self._source_excitation = SourceExcitation(self)
394
+ self._differential_pairs = DifferentialPairs(self)
395
+ self._extended_nets = ExtendedNets(self)
396
+
397
+ @property
398
+ def cell_names(self):
399
+ """Cell name container.
400
+
401
+ Returns
402
+ -------
403
+ list of cell names : List[str]
404
+ """
405
+ return [cell.name for cell in self.active_db.top_circuit_cells]
406
+
407
+ @property
408
+ def design_variables(self):
409
+ """Get all edb design variables.
410
+
411
+ Returns
412
+ -------
413
+ variable dictionary : Dict[str, variable_name: float, variable_value]
414
+ """
415
+ return {i: self.active_cell.get_variable_value(i).value for i in self.active_cell.get_all_variable_names()}
416
+
417
+ @property
418
+ def project_variables(self):
419
+ """Get all project variables.
420
+
421
+ Returns
422
+ -------
423
+ variables dictionary : Dict[str, variable_name: float, variable_value]
424
+
425
+ """
426
+ return {i: self.active_db.get_variable_value(i).value for i in self.active_db.get_all_variable_names()}
427
+
428
+ @property
429
+ def layout_validation(self):
430
+ """Return LayoutValidation object.
431
+
432
+ Returns
433
+ -------
434
+ :class:`LayoutValidation <pyedb.grpc.database.layout_validation.LayoutValidation>`
435
+ """
436
+ return LayoutValidation(self)
437
+
438
+ @property
439
+ def variables(self):
440
+ """Get all Edb variables.
441
+
442
+ Returns
443
+ -------
444
+ variables dictionary : Dict[str: float]
445
+
446
+ """
447
+ all_vars = dict()
448
+ for i, j in self.project_variables.items():
449
+ all_vars[i] = j
450
+ for i, j in self.design_variables.items():
451
+ all_vars[i] = j
452
+ return all_vars
453
+
454
+ @property
455
+ def terminals(self):
456
+ """Get terminals belonging to active layout.
457
+
458
+ Returns
459
+ -------
460
+ Dict : Dict[str: :class:`Terminal <pyedb.grpc.database.terminal.terminal.Terminal>`],
461
+ """
462
+ return {i.name: i for i in self.layout.terminals}
463
+
464
+ @property
465
+ def excitations(self):
466
+ """Get all layout excitations.
467
+
468
+ Returns
469
+ -------
470
+ Dict: Dict[str: :class:`<pyedb.grpc.database.port.GapPort>`]
471
+ """
472
+
473
+ terms = [term for term in self.layout.terminals if term.boundary_type == "port"]
474
+ temp = {}
475
+ for term in terms:
476
+ if not term.bundle_terminal.is_null:
477
+ temp[term.name] = BundleWavePort(self, term)
478
+ else:
479
+ temp[term.name] = GapPort(self, term)
480
+ return temp
481
+
482
+ @property
483
+ def ports(self):
484
+ """Get all ports.
485
+
486
+ Returns
487
+ -------
488
+ port dictionary : Dict[str: :class:`GapPort <pyedb.grpc.database.ports.GapPort>` or
489
+ :class:`WavePort <pyedb.grpc.database.ports.WavePort>`]
490
+
491
+ """
492
+ terminals = [term for term in self.layout.terminals if not term.is_reference_terminal]
493
+ ports = {}
494
+ from pyedb.grpc.database.terminal.bundle_terminal import BundleTerminal
495
+ from pyedb.grpc.database.terminal.padstack_instance_terminal import (
496
+ PadstackInstanceTerminal,
497
+ )
498
+
499
+ for t in terminals:
500
+ if isinstance(t, BundleTerminal):
501
+ bundle_ter = WavePort(self, t)
502
+ ports[bundle_ter.name] = bundle_ter
503
+ elif isinstance(t, PadstackInstanceTerminal):
504
+ ports[t.name] = CoaxPort(self, t)
505
+ else:
506
+ ports[t.name] = GapPort(self, t)
507
+ return ports
508
+
509
+ @property
510
+ def excitations_nets(self):
511
+ """Get all net names with excitation defined.
512
+
513
+ Returns
514
+ -------
515
+ List[str]
516
+ List of net name.
517
+ """
518
+ return list(set([i.net.name for i in self.layout.terminals if not i.is_reference_terminal]))
519
+
520
+ @property
521
+ def sources(self):
522
+ """Get all layout sources.
523
+
524
+ Returns
525
+ -------
526
+ Dict: Dic[str, :class:`Terminal <pyedb.grpc.database.terminal.terminal.Terminal>`]
527
+ """
528
+ return self.terminals
529
+
530
+ @property
531
+ def voltage_regulator_modules(self):
532
+ """Get all voltage regulator modules
533
+
534
+ Returns
535
+ -------
536
+ List of voltage regulator modules.
537
+ List[:class:`VoltageRegulator <pyedb.grpc.database.layout.voltage_regulator.VoltageRegulator>`]
538
+
539
+ """
540
+ vrms = self.layout.voltage_regulators
541
+ _vrms = {}
542
+ for vrm in vrms:
543
+ _vrms[vrm.name] = vrm
544
+ return _vrms
545
+
546
+ @property
547
+ def probes(self):
548
+ """Get all layout probes.
549
+
550
+ Returns
551
+ -------
552
+ Dictionary of probes.
553
+ Dict[str, :class:`Terminal <pyedb.grpc.database.terminal.terminal.Terminal>`
554
+ """
555
+ terms = [term for term in self.layout.terminals if term.boundary_type.value == 8]
556
+ return {ter.name: ter for ter in terms}
557
+
558
+ def open_edb(self, restart_rpc_server=False, kill_all_instances=False):
559
+ """Open EDB.
560
+
561
+ Returns
562
+ -------
563
+ bool: `True` when succeed `False` if failed.
564
+ """
565
+ self.standalone = self.standalone
566
+ n_try = 10
567
+ while not self.db and n_try:
568
+ try:
569
+ self.open(
570
+ self.edbpath,
571
+ self.isreadonly,
572
+ restart_rpc_server=restart_rpc_server,
573
+ kill_all_instances=kill_all_instances,
574
+ )
575
+ n_try -= 1
576
+ except Exception as e:
577
+ self.logger.error(e.args[0])
578
+ if not self.db:
579
+ raise ValueError("Failed during EDB loading.")
580
+ else:
581
+ if self.db.is_null:
582
+ self.logger.warning("Error Opening db")
583
+ self._active_cell = None
584
+ self.logger.info(f"Database {os.path.split(self.edbpath)[-1]} Opened in {self.edbversion}")
585
+ self._active_cell = None
586
+ if self.cellname:
587
+ for cell in self.active_db.circuit_cells:
588
+ if cell.name == self.cellname:
589
+ self._active_cell = cell
590
+ if self._active_cell is None:
591
+ self._active_cell = self._db.circuit_cells[0]
592
+ self.logger.info("Cell %s Opened", self._active_cell.name)
593
+ if self._active_cell:
594
+ self._init_objects()
595
+ self.logger.info("Builder was initialized.")
596
+ else:
597
+ self.logger.error("Builder was not initialized.")
598
+ return True
599
+
600
+ def create_edb(self, restart_rpc_server=False, kill_all_instances=False):
601
+ """Create EDB.
602
+
603
+ Returns
604
+ -------
605
+ bool: `True` when succeed `False` if failed.
606
+ """
607
+ from ansys.edb.core.layout.cell import Cell as GrpcCell
608
+ from ansys.edb.core.layout.cell import CellType as GrpcCellType
609
+
610
+ self.standalone = self.standalone
611
+ n_try = 10
612
+ while not self.db and n_try:
613
+ try:
614
+ self.create(self.edbpath, restart_rpc_server=restart_rpc_server, kill_all_instances=kill_all_instances)
615
+ n_try -= 1
616
+ except Exception as e:
617
+ self.logger.error(e.args[0])
618
+ if not self.db:
619
+ raise ValueError("Failed creating EDB.")
620
+ self._active_cell = None
621
+ else:
622
+ if not self.cellname:
623
+ self.cellname = generate_unique_name("Cell")
624
+ self._active_cell = GrpcCell.create(
625
+ db=self.active_db, cell_type=GrpcCellType.CIRCUIT_CELL, cell_name=self.cellname
626
+ )
627
+ if self._active_cell:
628
+ self._init_objects()
629
+ return True
630
+ return None
631
+
632
+ def import_layout_pcb(
633
+ self,
634
+ input_file,
635
+ working_dir,
636
+ anstranslator_full_path="",
637
+ use_ppe=False,
638
+ control_file=None,
639
+ ):
640
+ """Import a board file and generate an ``edb.def`` file in the working directory.
641
+
642
+ This function supports all AEDT formats, including DXF, GDS, SML (IPC2581), BRD, MCM, SIP, ZIP and TGZ.
643
+
644
+ Parameters
645
+ ----------
646
+ input_file : str
647
+ Full path to the board file.
648
+ working_dir : str
649
+ Directory in which to create the ``aedb`` folder. The name given to the AEDB file
650
+ is the same as the name of the board file.
651
+ anstranslator_full_path : str, optional
652
+ Full path to the Ansys translator. The default is ``""``.
653
+ use_ppe : bool
654
+ Whether to use the PPE License. The default is ``False``.
655
+ control_file : str, optional
656
+ Path to the XML file. The default is ``None``, in which case an attempt is made to find
657
+ the XML file in the same directory as the board file. To succeed, the XML file and board file
658
+ must have the same name. Only the extension differs.
659
+
660
+ Returns
661
+ -------
662
+ str: Full path to the AEDB file.
663
+
664
+ """
665
+ self._components = None
666
+ self._core_primitives = None
667
+ self._stackup = None
668
+ self._padstack = None
669
+ self._siwave = None
670
+ self._hfss = None
671
+ self._nets = None
672
+ aedb_name = os.path.splitext(os.path.basename(input_file))[0] + ".aedb"
673
+ if anstranslator_full_path and os.path.exists(anstranslator_full_path):
674
+ command = anstranslator_full_path
675
+ else:
676
+ command = os.path.join(self.base_path, "anstranslator")
677
+ if is_windows:
678
+ command += ".exe"
679
+
680
+ if not working_dir:
681
+ working_dir = os.path.dirname(input_file)
682
+ cmd_translator = [
683
+ command,
684
+ input_file,
685
+ os.path.join(working_dir, aedb_name),
686
+ "-l={}".format(os.path.join(working_dir, "Translator.log")),
687
+ ]
688
+ if not use_ppe:
689
+ cmd_translator.append("-ppe=false")
690
+ if control_file and input_file[-3:] not in ["brd", "mcm"]:
691
+ if is_linux:
692
+ cmd_translator.append("-c={}".format(control_file))
693
+ else:
694
+ cmd_translator.append('-c="{}"'.format(control_file))
695
+ p = subprocess.Popen(cmd_translator)
696
+ p.wait()
697
+ if not os.path.exists(os.path.join(working_dir, aedb_name)):
698
+ self.logger.error("Translator failed to translate.")
699
+ return False
700
+ else:
701
+ self.logger.info("Translation correctly completed")
702
+ self.edbpath = os.path.join(working_dir, aedb_name)
703
+ return self.open_edb()
704
+
705
+ def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER"):
706
+ """Create an XML IPC2581 file from the active EDB.
707
+
708
+ .. note::
709
+ The method works only in CPython because of some limitations on Ironpython in XML parsing and
710
+ because it's time-consuming.
711
+ This method is still being tested and may need further debugging.
712
+ Any feedback is welcome. Back drills and custom pads are not supported yet.
713
+
714
+ Parameters
715
+ ----------
716
+ ipc_path : str, optional
717
+ Path to the XML IPC2581 file. The default is ``None``, in which case
718
+ an attempt is made to find the XML IPC2581 file in the same directory
719
+ as the active EDB. To succeed, the XML IPC2581 file and the active
720
+ EDT must have the same name. Only the extension differs.
721
+ units : str, optional
722
+ Units of the XML IPC2581 file. Options are ``"millimeter"``,
723
+ ``"inch"``, and ``"micron"``. The default is ``"millimeter"``.
724
+
725
+ Returns
726
+ -------
727
+ bool: `True` if successful, `False` if failed.
728
+
729
+ """
730
+ if units.lower() not in ["millimeter", "inch", "micron"]: # pragma no cover
731
+ self.logger.warning("The wrong unit is entered. Setting to the default, millimeter.")
732
+ units = "millimeter"
733
+
734
+ if not ipc_path:
735
+ ipc_path = self.edbpath[:-4] + "xml"
736
+ self.logger.info("Export IPC 2581 is starting. This operation can take a while.")
737
+ start = time.time()
738
+ ipc = Ipc2581(self, units)
739
+ ipc.load_ipc_model()
740
+ ipc.file_path = ipc_path
741
+ result = ipc.write_xml()
742
+
743
+ if result: # pragma no cover
744
+ self.logger.info_timer("Export IPC 2581 completed.", start)
745
+ self.logger.info("File saved as %s", ipc_path)
746
+ return ipc_path
747
+ self.logger.info("Error exporting IPC 2581.")
748
+ return False
749
+
750
+ @property
751
+ def configuration(self):
752
+ """Edb project configuration from a file.
753
+
754
+ Returns
755
+ -------
756
+ :class:`Configuration <pyedb.configuration.configuration.Configuration>`.
757
+ """
758
+ if not self._configuration:
759
+ self._configuration = Configuration(self)
760
+ return self._configuration
761
+
762
+ def edb_exception(self, ex_value, tb_data):
763
+ """Write the trace stack to AEDT when a Python error occurs.
764
+
765
+ Parameters
766
+ ----------
767
+ ex_value :
768
+
769
+ tb_data :
770
+
771
+
772
+ Returns
773
+ -------
774
+ None
775
+
776
+ """
777
+ tb_trace = traceback.format_tb(tb_data)
778
+ tblist = tb_trace[0].split("\n")
779
+ self.logger.error(str(ex_value))
780
+ for el in tblist:
781
+ self.logger.error(el)
782
+
783
+ @property
784
+ def active_db(self):
785
+ """Database object.
786
+
787
+ Returns
788
+ -------
789
+ :class:`Database <ansys.edb.core.database.Database>`.
790
+ """
791
+ return self.db
792
+
793
+ @property
794
+ def active_cell(self):
795
+ """Active cell.
796
+
797
+ Returns
798
+ -------
799
+ :class:`Cell <ansys.edb.core.layout.cell.Cell>`.
800
+ """
801
+ return self._active_cell
802
+
803
+ @property
804
+ def components(self):
805
+ """Edb Components methods and properties.
806
+
807
+ Returns
808
+ -------
809
+ :class:`Components <pyedb.grpc.database.components.Components>`.
810
+
811
+ Examples
812
+ --------
813
+ >>> from pyedb import Edb
814
+ >>> edbapp = Edb("myproject.aedb")
815
+ >>> comp = edbapp.components.get_component_by_name("J1")
816
+ """
817
+ if not self._components and self.active_db:
818
+ self._components = Components(self)
819
+ return self._components
820
+
821
+ @property
822
+ def stackup(self):
823
+ """Stackup manager.
824
+
825
+ Returns
826
+ -------
827
+ :class:`Stackup <pyedb.grpc.database.stackup.Stackup>`
828
+
829
+ Examples
830
+ --------
831
+ >>> from pyedb.grpc.edb import Edb
832
+ >>> edbapp = Edb("myproject.aedb")
833
+ >>> edbapp.stackup.layers["TOP"].thickness = 4e-5
834
+ >>> edbapp.stackup.layers["TOP"].thickness == 4e-05
835
+ >>> edbapp.stackup.add_layer("Diel", "GND", layer_type="dielectric", thickness="0.1mm", material="FR4_epoxy")
836
+ """
837
+ if self.active_db:
838
+ self._stackup = Stackup(self, self.active_cell.layout.layer_collection)
839
+ return self._stackup
840
+
841
+ @property
842
+ def source_excitation(self):
843
+ """Returns layout source excitations.
844
+
845
+ Returns
846
+ -------
847
+ :class:`SourceExcitation <pyedb.grpc.database.source_excitations.SourceExcitation>`.
848
+ """
849
+ if self.active_db:
850
+ return self._source_excitation
851
+
852
+ @property
853
+ def materials(self):
854
+ """Material Database.
855
+
856
+ Returns
857
+ -------
858
+ :class:`Materials <pyedb.grpc.database.definition.materials.Materials>`.
859
+
860
+ Examples
861
+ --------
862
+ >>> from pyedb import Edb
863
+ >>> edbapp = Edb()
864
+ >>> edbapp.materials.add_material("air", permittivity=1.0)
865
+ >>> edbapp.materials.add_debye_material("debye_mat", 5, 3, 0.02, 0.05, 1e5, 1e9)
866
+ >>> edbapp.materials.add_djordjevicsarkar_material("djord_mat", 3.3, 0.02, 3.3)
867
+ """
868
+ if self.active_db:
869
+ self._materials = Materials(self)
870
+ return self._materials
871
+
872
+ @property
873
+ def padstacks(self):
874
+ """Returns padstack object.
875
+
876
+
877
+ Returns
878
+ -------
879
+ :class:`Padstacks <pyedb.grpc.database.padstack.Padstacks>`.
880
+
881
+ Examples
882
+ --------
883
+ >>> from pyedb import Edb
884
+ >>> edbapp = Edb("myproject.aedb")
885
+ >>> p = edbapp.padstacks.create(padstackname="myVia_bullet", antipad_shape="Bullet")
886
+ >>> edbapp.padstacks.get_pad_parameters(
887
+ >>> ... p, "TOP", edbapp.padstacks.pad_type.RegularPad
888
+ >>> ... )
889
+ """
890
+
891
+ if not self._padstack and self.active_db:
892
+ self._padstack = Padstacks(self)
893
+ return self._padstack
894
+
895
+ @property
896
+ def siwave(self):
897
+ """Returns SIWave object.
898
+
899
+ Returns
900
+ -------
901
+ :class:`Siwave <pyedb.grpc.database.siwave.Siwave>`.
902
+
903
+ Examples
904
+ --------
905
+ >>> from pyedb import Edb
906
+ >>> edbapp = Edb("myproject.aedb")
907
+ >>> p2 = edbapp.siwave.create_circuit_port_on_net("U2A5", "V3P3_S0", "U2A5", "GND", 50, "test")
908
+ """
909
+ if not self._siwave and self.active_db:
910
+ self._siwave = Siwave(self)
911
+ return self._siwave
912
+
913
+ @property
914
+ def hfss(self):
915
+ """Returns HFSS object.
916
+
917
+ Returns
918
+ -------
919
+ :class:`Hfss <pyedb.grpc.database.hfss.Hfss>`.
920
+
921
+ Examples
922
+ --------
923
+ >>> from pyedb import Edb
924
+ >>> edbapp = Edb("myproject.aedb")
925
+ >>> sim_config = edbapp.new_simulation_configuration()
926
+ >>> sim_config.mesh_freq = "10Ghz"
927
+ >>> edbapp.hfss.configure_hfss_analysis_setup(sim_config)
928
+ """
929
+ if not self._hfss and self.active_db:
930
+ self._hfss = Hfss(self)
931
+ return self._hfss
932
+
933
+ @property
934
+ def nets(self):
935
+ """Returns nets object.
936
+
937
+ Returns
938
+ -------
939
+ :class:`Nets <pyedb.grpc.database.nets.Nets>`.
940
+
941
+ Examples
942
+ --------
943
+ >>> from pyedb import Edb
944
+ >>> edbapp = Edb"myproject.aedb")
945
+ >>> edbapp.nets.find_or_create_net("GND")
946
+ >>> edbapp.nets.find_and_fix_disjoint_nets("GND", keep_only_main_net=True)
947
+ """
948
+
949
+ if not self._nets and self.active_db:
950
+ self._nets = Nets(self)
951
+ return self._nets
952
+
953
+ @property
954
+ def net_classes(self):
955
+ """Returns net classes object.
956
+
957
+ Returns
958
+ -------
959
+ :class:`NetClass <pyedb.grpc.database.net.net_class.NetClass>`.
960
+
961
+ Examples
962
+ --------
963
+ >>> from pyedb import Edb
964
+ >>> edbapp = Edb("myproject.aedb")
965
+ >>> edbapp.net_classes
966
+ """
967
+
968
+ if self.active_db:
969
+ return {net.name: NetClass(self, net) for net in self.active_layout.net_classes}
970
+
971
+ @property
972
+ def extended_nets(self):
973
+ """Returns extended nets.
974
+
975
+ Returns
976
+ -------
977
+ :class:`ExtendedNets <pyedb.grpc.database.net.extended_net.ExtendedNets>`.
978
+
979
+ Examples
980
+ --------
981
+ >>> from pyedb import Edb
982
+ >>> edbapp = Edb("myproject.aedb")
983
+ >>> edbapp.extended_nets
984
+ """
985
+
986
+ if not self._extended_nets:
987
+ self._extended_nets = ExtendedNets(self)
988
+ return self._extended_nets
989
+
990
+ @property
991
+ def differential_pairs(self):
992
+ """Returns differential pairs.
993
+
994
+ Returns
995
+ -------
996
+ :class:`DifferentialPairs <pyedb.grpc.database.net.differential_par.DifferentialPairs>`.
997
+
998
+ Examples
999
+ --------
1000
+ >>> from pyedb import Edb
1001
+ >>> edbapp = Edb("myproject.aedb")
1002
+ >>> edbapp.differential_pairs
1003
+ """
1004
+ if not self._differential_pairs and self.active_db:
1005
+ self._differential_pairs = DifferentialPairs(self)
1006
+ return self._differential_pairs
1007
+
1008
+ @property
1009
+ def modeler(self):
1010
+ """Returns primitives modeler object.
1011
+
1012
+ Returns
1013
+ -------
1014
+ :class:`Modeler <pyedb.grpc.database.modeler.Modeler>`.
1015
+
1016
+ Examples
1017
+ --------
1018
+ >>> from pyedb import Edb
1019
+ >>> edbapp = Edb("myproject.aedb")
1020
+ >>> top_prims = edbapp.modeler.primitives_by_layer["TOP"]
1021
+ """
1022
+ if not self._modeler and self.active_db:
1023
+ self._modeler = Modeler(self)
1024
+ return self._modeler
1025
+
1026
+ @property
1027
+ def layout(self):
1028
+ """Returns Layout object.
1029
+
1030
+ Returns
1031
+ -------
1032
+ :class:`Layout <pyedb.grpc.database.layout.layout.Layout>`.
1033
+ """
1034
+ return Layout(self)
1035
+
1036
+ @property
1037
+ def active_layout(self):
1038
+ """Active layout.
1039
+
1040
+ Returns
1041
+ -------
1042
+ :class:`Layout <pyedb.grpc.database.layout.layout.Layout>`.
1043
+ """
1044
+ return self.layout
1045
+
1046
+ @property
1047
+ def layout_instance(self):
1048
+ """Returns Layout Instance object.
1049
+
1050
+ Returns
1051
+ -------
1052
+ :class:`LayoutInstance <ansys.edb.core.layout_instance.layout_instance.LayoutInstance>`
1053
+ """
1054
+ if not self._layout_instance:
1055
+ self._layout_instance = self.layout.layout_instance
1056
+ return self._layout_instance
1057
+
1058
+ def get_connected_objects(self, layout_object_instance):
1059
+ """Returns connected objects.
1060
+
1061
+ Returns
1062
+ -------
1063
+ list[:class:`PadstackInstance <pyedb.grpc.database.primitive.padstack_instance.PadstackInstance>`,
1064
+ :class:`Path <pyedb.grpc.database.primitive.path.Path>`,
1065
+ :class:`Rectangle <pyedb.grpc.database.primitive.rectangle.Rectangle>`,
1066
+ :class:`Circle <pyedb.grpc.database.primitive.circle.Circle>`,
1067
+ :class:`Polygon <pyedb.grpc.database.primitive.polygon.Polygon>`]
1068
+ """
1069
+ from ansys.edb.core.terminal.terminals import (
1070
+ PadstackInstanceTerminal as GrpcPadstackInstanceTerminal,
1071
+ )
1072
+
1073
+ temp = []
1074
+ try:
1075
+ for i in self.layout_instance.get_connected_objects(layout_object_instance, True):
1076
+ if isinstance(i.layout_obj, GrpcPadstackInstanceTerminal):
1077
+ temp.append(PadstackInstanceTerminal(self, i.layout_obj))
1078
+ else:
1079
+ layout_obj_type = i.layout_obj.layout_obj_type.name
1080
+ if layout_obj_type == "PADSTACK_INSTANCE":
1081
+ temp.append(PadstackInstance(self, i.layout_obj))
1082
+ elif layout_obj_type == "PATH":
1083
+ temp.append(Path(self, i.layout_obj))
1084
+ elif layout_obj_type == "RECTANGLE":
1085
+ temp.append(Rectangle(self, i.layout_obj))
1086
+ elif layout_obj_type == "CIRCLE":
1087
+ temp.append(Circle(self, i.layout_obj))
1088
+ elif layout_obj_type == "POLYGON":
1089
+ temp.append(Polygon(self, i.layout_obj))
1090
+ else:
1091
+ continue
1092
+ except:
1093
+ self.logger.warning(
1094
+ f"Failed to find connected objects on layout_obj " f"{layout_object_instance.layout_obj.id}, skipping."
1095
+ )
1096
+ pass
1097
+ return temp
1098
+
1099
+ def point_3d(self, x, y, z=0.0):
1100
+ """Compute the Edb 3d Point Data.
1101
+
1102
+ Parameters
1103
+ ----------
1104
+ x : float, int or str
1105
+ X value.
1106
+ y : float, int or str
1107
+ Y value.
1108
+ z : float, int or str, optional
1109
+ Z value.
1110
+
1111
+ Returns
1112
+ -------
1113
+ :class:`Point3DData <pyedb.grpc.database.geometry.point_3d_data.Point3DData>`
1114
+ """
1115
+ from pyedb.grpc.database.geometry.point_3d_data import Point3DData
1116
+
1117
+ return Point3DData(x, y, z)
1118
+
1119
+ def point_data(self, x, y=None):
1120
+ """Compute the Edb Point Data.
1121
+
1122
+ Parameters
1123
+ ----------
1124
+ x : float, int or str
1125
+ X value.
1126
+ y : float, int or str, optional
1127
+ Y value.
1128
+
1129
+
1130
+ Returns
1131
+ -------
1132
+ :class:`PointData <pyedb.grpc.database.geometry.point_data.PointData>`
1133
+ """
1134
+ from pyedb.grpc.database.geometry.point_data import PointData
1135
+
1136
+ if y is None:
1137
+ return PointData(x)
1138
+ else:
1139
+ return PointData(x, y)
1140
+
1141
+ @staticmethod
1142
+ def _is_file_existing_and_released(filename):
1143
+ if os.path.exists(filename):
1144
+ try:
1145
+ os.rename(filename, filename + "_")
1146
+ os.rename(filename + "_", filename)
1147
+ return True
1148
+ except OSError as e:
1149
+ return False
1150
+ else:
1151
+ return False
1152
+
1153
+ @staticmethod
1154
+ def _is_file_existing(filename):
1155
+ if os.path.exists(filename):
1156
+ return True
1157
+ else:
1158
+ return False
1159
+
1160
+ def _wait_for_file_release(self, timeout=30, file_to_release=None):
1161
+ if not file_to_release:
1162
+ file_to_release = os.path.join(self.edbpath)
1163
+ tstart = time.time()
1164
+ while True:
1165
+ if self._is_file_existing_and_released(file_to_release):
1166
+ return True
1167
+ elif time.time() - tstart > timeout:
1168
+ return False
1169
+ else:
1170
+ time.sleep(0.250)
1171
+
1172
+ def _wait_for_file_exists(self, timeout=30, file_to_release=None, wait_count=4):
1173
+ if not file_to_release:
1174
+ file_to_release = os.path.join(self.edbpath)
1175
+ tstart = time.time()
1176
+ times = 0
1177
+ while True:
1178
+ if self._is_file_existing(file_to_release):
1179
+ # print 'File is released'
1180
+ times += 1
1181
+ if times == wait_count:
1182
+ return True
1183
+ elif time.time() - tstart > timeout:
1184
+ # print 'Timeout reached'
1185
+ return False
1186
+ else:
1187
+ times = 0
1188
+ time.sleep(0.250)
1189
+
1190
+ def close_edb(self):
1191
+ """Close EDB and cleanup variables.
1192
+
1193
+ Returns
1194
+ -------
1195
+ bool: `True` when successful, `False` when failed.
1196
+ """
1197
+ self.close()
1198
+ start_time = time.time()
1199
+ self._wait_for_file_release()
1200
+ elapsed_time = time.time() - start_time
1201
+ self.logger.info("EDB file release time: {0:.2f}ms".format(elapsed_time * 1000.0))
1202
+ self._clean_variables()
1203
+ return True
1204
+
1205
+ def save_edb(self):
1206
+ """Save the EDB file.
1207
+
1208
+ Returns
1209
+ -------
1210
+ bool: `True` when successful, `False` when failed.
1211
+ """
1212
+ self.save()
1213
+ start_time = time.time()
1214
+ self._wait_for_file_release()
1215
+ elapsed_time = time.time() - start_time
1216
+ self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0))
1217
+ return True
1218
+
1219
+ def save_edb_as(self, fname):
1220
+ """Save the EDB file as another file.
1221
+
1222
+ Parameters
1223
+ ----------
1224
+ fname : str
1225
+ Name of the new file to save to.
1226
+
1227
+ Returns
1228
+ -------
1229
+ bool: `True` when successful, `False` when failed.
1230
+ """
1231
+ self.save_as(fname)
1232
+ start_time = time.time()
1233
+ self._wait_for_file_release()
1234
+ elapsed_time = time.time() - start_time
1235
+ self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0))
1236
+ self.edbpath = self.directory
1237
+ self.log_name = os.path.join(
1238
+ os.path.dirname(fname), "pyedb_" + os.path.splitext(os.path.split(fname)[-1])[0] + ".log"
1239
+ )
1240
+ return True
1241
+
1242
+ def execute(self, func):
1243
+ """Execute a function.
1244
+
1245
+ Parameters
1246
+ ----------
1247
+ func : str
1248
+ Function to execute.
1249
+
1250
+
1251
+ Returns
1252
+ -------
1253
+ bool: `True` when successful, `False` when failed.
1254
+ """
1255
+ # return self.edb_api.utility.utility.Command.Execute(func)
1256
+ pass
1257
+
1258
+ def import_cadence_file(self, inputBrd, WorkDir=None, anstranslator_full_path="", use_ppe=False):
1259
+ """Import a board file and generate an ``edb.def`` file in the working directory.
1260
+
1261
+ Parameters
1262
+ ----------
1263
+ inputBrd : str
1264
+ Full path to the board file.
1265
+ WorkDir : str, optional
1266
+ Directory in which to create the ``aedb`` folder. The default value is ``None``,
1267
+ in which case the AEDB file is given the same name as the board file. Only
1268
+ the extension differs.
1269
+ anstranslator_full_path : str, optional
1270
+ Full path to the Ansys translator.
1271
+ use_ppe : bool, optional
1272
+ Whether to use the PPE License. The default is ``False``.
1273
+
1274
+ Returns
1275
+ -------
1276
+ bool: `True` when successful, `False` when failed.
1277
+ """
1278
+ if self.import_layout_pcb(
1279
+ inputBrd,
1280
+ working_dir=WorkDir,
1281
+ anstranslator_full_path=anstranslator_full_path,
1282
+ use_ppe=use_ppe,
1283
+ ):
1284
+ return True
1285
+ else:
1286
+ return False
1287
+
1288
+ def import_gds_file(
1289
+ self,
1290
+ inputGDS,
1291
+ anstranslator_full_path="",
1292
+ use_ppe=False,
1293
+ control_file=None,
1294
+ tech_file=None,
1295
+ map_file=None,
1296
+ layer_filter=None,
1297
+ ):
1298
+ """Import a GDS file and generate an ``edb.def`` file in the working directory.
1299
+
1300
+ ..note::
1301
+ ANSYS license is necessary to run the translator.
1302
+
1303
+ Parameters
1304
+ ----------
1305
+ inputGDS : str
1306
+ Full path to the GDS file.
1307
+ anstranslator_full_path : str, optional
1308
+ Full path to the Ansys translator.
1309
+ use_ppe : bool, optional
1310
+ Whether to use the PPE License. The default is ``False``.
1311
+ control_file : str, optional
1312
+ Path to the XML file. The default is ``None``, in which case an attempt is made to find
1313
+ the XML file in the same directory as the GDS file. To succeed, the XML file and GDS file must
1314
+ have the same name. Only the extension differs.
1315
+ tech_file : str, optional
1316
+ Technology file. For versions<2024.1 it uses Helic to convert tech file to xml and then imports
1317
+ the gds. Works on Linux only.
1318
+ For versions>=2024.1 it can directly parse through supported foundry tech files.
1319
+ map_file : str, optional
1320
+ Layer map file.
1321
+ layer_filter:str,optional
1322
+ Layer filter file.
1323
+
1324
+ """
1325
+ control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml")
1326
+ if float(self.edbversion) < 2024.1:
1327
+ if not is_linux and tech_file:
1328
+ self.logger.error("Technology files are supported only in Linux. Use control file instead.")
1329
+ return False
1330
+
1331
+ ControlFile(xml_input=control_file, tecnhology=tech_file, layer_map=map_file).write_xml(control_file_temp)
1332
+ if self.import_layout_pcb(
1333
+ inputGDS,
1334
+ anstranslator_full_path=anstranslator_full_path,
1335
+ use_ppe=use_ppe,
1336
+ control_file=control_file_temp,
1337
+ ):
1338
+ return True
1339
+ else:
1340
+ return False
1341
+ else:
1342
+ temp_map_file = os.path.splitext(inputGDS)[0] + ".map"
1343
+ temp_layermap_file = os.path.splitext(inputGDS)[0] + ".layermap"
1344
+
1345
+ if map_file is None:
1346
+ if os.path.isfile(temp_map_file):
1347
+ map_file = temp_map_file
1348
+ elif os.path.isfile(temp_layermap_file):
1349
+ map_file = temp_layermap_file
1350
+ else:
1351
+ self.logger.error("Unable to define map file.")
1352
+
1353
+ if tech_file is None:
1354
+ if control_file is None:
1355
+ temp_control_file = os.path.splitext(inputGDS)[0] + ".xml"
1356
+ if os.path.isfile(temp_control_file):
1357
+ control_file = temp_control_file
1358
+ else:
1359
+ self.logger.error("Unable to define control file.")
1360
+
1361
+ command = [anstranslator_full_path, inputGDS, f'-g="{map_file}"', f'-c="{control_file}"']
1362
+ else:
1363
+ command = [
1364
+ anstranslator_full_path,
1365
+ inputGDS,
1366
+ f'-o="{control_file_temp}"' f'-t="{tech_file}"',
1367
+ f'-g="{map_file}"',
1368
+ f'-f="{layer_filter}"',
1369
+ ]
1370
+
1371
+ result = subprocess.run(command, capture_output=True, text=True, shell=True)
1372
+ print(result.stdout)
1373
+ print(command)
1374
+ temp_inputGDS = inputGDS.split(".gds")[0]
1375
+ self.edbpath = temp_inputGDS + ".aedb"
1376
+ return self.open_edb()
1377
+
1378
+ def _create_extent(
1379
+ self,
1380
+ net_signals,
1381
+ extent_type,
1382
+ expansion_size,
1383
+ use_round_corner,
1384
+ use_pyaedt_extent=False,
1385
+ smart_cut=False,
1386
+ reference_list=[],
1387
+ include_pingroups=True,
1388
+ pins_to_preserve=None,
1389
+ inlcude_voids_in_extents=False,
1390
+ ):
1391
+ from ansys.edb.core.geometry.polygon_data import ExtentType as GrpcExtentType
1392
+
1393
+ if extent_type in [
1394
+ "Conforming",
1395
+ GrpcExtentType.CONFORMING,
1396
+ 1,
1397
+ ]:
1398
+ if use_pyaedt_extent:
1399
+ _poly = self._create_conformal(
1400
+ net_signals,
1401
+ expansion_size,
1402
+ 1e-12,
1403
+ use_round_corner,
1404
+ expansion_size,
1405
+ smart_cut,
1406
+ reference_list,
1407
+ pins_to_preserve,
1408
+ inlcude_voids_in_extents=inlcude_voids_in_extents,
1409
+ )
1410
+ else:
1411
+ _poly = self.layout.expanded_extent(
1412
+ net_signals,
1413
+ GrpcExtentType.CONFORMING,
1414
+ expansion_size,
1415
+ False,
1416
+ use_round_corner,
1417
+ 1,
1418
+ )
1419
+ elif extent_type in [
1420
+ "Bounding",
1421
+ GrpcExtentType.BOUNDING_BOX,
1422
+ 0,
1423
+ ]:
1424
+ _poly = self.layout.expanded_extent(
1425
+ net_signals,
1426
+ GrpcExtentType.BOUNDING_BOX,
1427
+ expansion_size,
1428
+ False,
1429
+ use_round_corner,
1430
+ 1,
1431
+ )
1432
+ else:
1433
+ if use_pyaedt_extent:
1434
+ _poly = self._create_convex_hull(
1435
+ net_signals,
1436
+ expansion_size,
1437
+ 1e-12,
1438
+ use_round_corner,
1439
+ expansion_size,
1440
+ smart_cut,
1441
+ reference_list,
1442
+ pins_to_preserve,
1443
+ )
1444
+ else:
1445
+ _poly = self.layout.expanded_extent(
1446
+ net_signals,
1447
+ GrpcExtentType.CONFORMING,
1448
+ expansion_size,
1449
+ False,
1450
+ use_round_corner,
1451
+ 1,
1452
+ )
1453
+ if not isinstance(_poly, list):
1454
+ _poly = [_poly]
1455
+ _poly = GrpcPolygonData.convex_hull(_poly)
1456
+ return _poly
1457
+
1458
+ def _create_conformal(
1459
+ self,
1460
+ net_signals,
1461
+ expansion_size,
1462
+ tolerance,
1463
+ round_corner,
1464
+ round_extension,
1465
+ smart_cutout=False,
1466
+ reference_list=[],
1467
+ pins_to_preserve=None,
1468
+ inlcude_voids_in_extents=False,
1469
+ ):
1470
+ names = []
1471
+ _polys = []
1472
+ for net in net_signals:
1473
+ names.append(net.name)
1474
+ if pins_to_preserve:
1475
+ insts = self.padstacks.instances
1476
+ for i in pins_to_preserve:
1477
+ p = insts[i].position
1478
+ pos_1 = [i - expansion_size for i in p]
1479
+ pos_2 = [i + expansion_size for i in p]
1480
+ plane = self.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2)
1481
+ rectangle_data = self.modeler.shape_to_polygon_data(plane)
1482
+ _polys.append(rectangle_data)
1483
+
1484
+ for prim in self.modeler.primitives:
1485
+ if prim is not None and prim.net_name in names:
1486
+ _polys.append(prim)
1487
+ if smart_cutout:
1488
+ objs_data = self._smart_cut(reference_list, expansion_size)
1489
+ _polys.extend(objs_data)
1490
+ k = 0
1491
+ delta = expansion_size / 5
1492
+ while k < 10:
1493
+ unite_polys = []
1494
+ for i in _polys:
1495
+ if "PolygonData" not in str(i):
1496
+ obj_data = i.polygon_data.expand(expansion_size, tolerance, round_corner, round_extension)
1497
+ else:
1498
+ obj_data = i.expand(expansion_size, tolerance, round_corner, round_extension)
1499
+ if inlcude_voids_in_extents and "PolygonData" not in str(i) and i.has_voids and obj_data:
1500
+ for void in i.voids:
1501
+ void_data = void.polygon_data.expand(
1502
+ -1 * expansion_size, tolerance, round_corner, round_extension
1503
+ )
1504
+ if void_data:
1505
+ for v in list(void_data):
1506
+ obj_data[0].holes.append(v)
1507
+ if obj_data:
1508
+ if not inlcude_voids_in_extents:
1509
+ unite_polys.extend(list(obj_data))
1510
+ else:
1511
+ voids_poly = []
1512
+ try:
1513
+ if i.has_voids:
1514
+ area = i.area()
1515
+ for void in i.voids:
1516
+ void_polydata = void.polygon_data
1517
+ if void_polydata.area() >= 0.05 * area:
1518
+ voids_poly.append(void_polydata)
1519
+ if voids_poly:
1520
+ obj_data = obj_data[0].subtract(list(obj_data), voids_poly)
1521
+ except:
1522
+ pass
1523
+ finally:
1524
+ unite_polys.extend(list(obj_data))
1525
+ _poly_unite = GrpcPolygonData.unite(unite_polys)
1526
+ if len(_poly_unite) == 1:
1527
+ self.logger.info("Correctly computed Extension at first iteration.")
1528
+ return _poly_unite[0]
1529
+ k += 1
1530
+ expansion_size += delta
1531
+ if len(_poly_unite) == 1:
1532
+ self.logger.info(f"Correctly computed Extension in {k} iterations.")
1533
+ return _poly_unite[0]
1534
+ else:
1535
+ self.logger.info("Failed to Correctly computed Extension.")
1536
+ areas = [i.area() for i in _poly_unite]
1537
+ return _poly_unite[areas.index(max(areas))]
1538
+
1539
+ def _smart_cut(self, reference_list=[], expansion_size=1e-12):
1540
+ from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
1541
+
1542
+ _polys = []
1543
+ boundary_types = [
1544
+ "port",
1545
+ ]
1546
+ terms = [term for term in self.layout.terminals if term.boundary_type in [0, 3, 4, 7, 8]]
1547
+ locations = []
1548
+ for term in terms:
1549
+ if term.type == "PointTerminal" and term.net.name in reference_list:
1550
+ pd = term.get_parameters()[1]
1551
+ locations.append([pd.x.value, pd.y.value])
1552
+ for point in locations:
1553
+ pointA = GrpcPointData([point[0] - expansion_size, point[1] - expansion_size])
1554
+ pointB = GrpcPointData([point[0] + expansion_size, point[1] + expansion_size])
1555
+ points = [pointA, GrpcPointData([pointB.x, pointA.y]), pointB, GrpcPointData([pointA.x, pointB.y])]
1556
+ _polys.append(GrpcPolygonData(points=points))
1557
+ return _polys
1558
+
1559
+ def _create_convex_hull(
1560
+ self,
1561
+ net_signals,
1562
+ expansion_size,
1563
+ tolerance,
1564
+ round_corner,
1565
+ round_extension,
1566
+ smart_cut=False,
1567
+ reference_list=[],
1568
+ pins_to_preserve=None,
1569
+ ):
1570
+ names = []
1571
+ _polys = []
1572
+ for net in net_signals:
1573
+ names.append(net.name)
1574
+ if pins_to_preserve:
1575
+ insts = self.padstacks.instances
1576
+ for i in pins_to_preserve:
1577
+ p = insts[i].position
1578
+ pos_1 = [i - 1e-12 for i in p]
1579
+ pos_2 = [i + 1e-12 for i in p]
1580
+ pos_3 = [pos_2[0], pos_1[1]]
1581
+ pos_4 = pos_1[0], pos_2[1]
1582
+ rectangle_data = GrpcPolygonData(points=[pos_1, pos_3, pos_2, pos_4])
1583
+ _polys.append(rectangle_data)
1584
+ for prim in self.modeler.primitives:
1585
+ if not prim.is_null and not prim.net.is_null:
1586
+ if prim.net.name in names:
1587
+ _polys.append(prim.polygon_data)
1588
+ if smart_cut:
1589
+ objs_data = self._smart_cut(reference_list, expansion_size)
1590
+ _polys.extend(objs_data)
1591
+ _poly = GrpcPolygonData.convex_hull(_polys)
1592
+ _poly = _poly.expand(
1593
+ offset=expansion_size, round_corner=round_corner, max_corner_ext=round_extension, tol=tolerance
1594
+ )[0]
1595
+ return _poly
1596
+
1597
+ def cutout(
1598
+ self,
1599
+ signal_list=None,
1600
+ reference_list=None,
1601
+ extent_type="ConvexHull",
1602
+ expansion_size=0.002,
1603
+ use_round_corner=False,
1604
+ output_aedb_path=None,
1605
+ open_cutout_at_end=True,
1606
+ use_pyaedt_cutout=True,
1607
+ number_of_threads=4,
1608
+ use_pyaedt_extent_computing=True,
1609
+ extent_defeature=0,
1610
+ remove_single_pin_components=False,
1611
+ custom_extent=None,
1612
+ custom_extent_units="mm",
1613
+ include_partial_instances=False,
1614
+ keep_voids=True,
1615
+ check_terminals=False,
1616
+ include_pingroups=False,
1617
+ expansion_factor=0,
1618
+ maximum_iterations=10,
1619
+ preserve_components_with_model=False,
1620
+ simple_pad_check=True,
1621
+ keep_lines_as_path=False,
1622
+ include_voids_in_extents=False,
1623
+ ):
1624
+ """Create a cutout using an approach entirely based on PyAEDT.
1625
+ This method replaces all legacy cutout methods in PyAEDT.
1626
+ It does in sequence:
1627
+ - delete all nets not in list,
1628
+ - create a extent of the nets,
1629
+ - check and delete all vias not in the extent,
1630
+ - check and delete all the primitives not in extent,
1631
+ - check and intersect all the primitives that intersect the extent.
1632
+
1633
+ Parameters
1634
+ ----------
1635
+ signal_list : list
1636
+ List of signal strings.
1637
+ reference_list : list, optional
1638
+ List of references to add. The default is ``["GND"]``.
1639
+ extent_type : str, optional
1640
+ Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and
1641
+ ``"Bounding"``. The default is ``"Conforming"``.
1642
+ expansion_size : float, str, optional
1643
+ Expansion size ratio in meters. The default is ``0.002``.
1644
+ use_round_corner : bool, optional
1645
+ Whether to use round corners. The default is ``False``.
1646
+ output_aedb_path : str, optional
1647
+ Full path and name for the new AEDB file. If None, then current aedb will be cutout.
1648
+ open_cutout_at_end : bool, optional
1649
+ Whether to open the cutout at the end. The default is ``True``.
1650
+ use_pyaedt_cutout : bool, optional
1651
+ Whether to use new PyAEDT cutout method or EDB API method.
1652
+ New method is faster than native API method since it benefits of multithread.
1653
+ number_of_threads : int, optional
1654
+ Number of thread to use. Default is 4. Valid only if ``use_pyaedt_cutout`` is set to ``True``.
1655
+ use_pyaedt_extent_computing : bool, optional
1656
+ Whether to use legacy extent computing (experimental) or EDB API.
1657
+ extent_defeature : float, optional
1658
+ Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental).
1659
+ It applies only to Conforming bounding box. Default value is ``0`` which disable it.
1660
+ remove_single_pin_components : bool, optional
1661
+ Remove all Single Pin RLC after the cutout is completed. Default is `False`.
1662
+ custom_extent : list
1663
+ Points list defining the cutout shape. This setting will override `extent_type` field.
1664
+ custom_extent_units : str
1665
+ Units of the point list. The default is ``"mm"``. Valid only if `custom_extend` is provided.
1666
+ include_partial_instances : bool, optional
1667
+ Whether to include padstack instances that have bounding boxes intersecting with point list polygons.
1668
+ This operation may slow down the cutout export.Valid only if `custom_extend` and
1669
+ `use_pyaedt_cutout` is provided.
1670
+ keep_voids : bool
1671
+ Boolean used for keep or not the voids intersecting the polygon used for clipping the layout.
1672
+ Default value is ``True``, ``False`` will remove the voids.Valid only if `custom_extend` is provided.
1673
+ check_terminals : bool, optional
1674
+ Whether to check for all reference terminals and increase extent to include them into the cutout.
1675
+ This applies to components which have a model (spice, touchstone or netlist) associated.
1676
+ include_pingroups : bool, optional
1677
+ Whether to check for all pingroups terminals and increase extent to include them into the cutout.
1678
+ It requires ``check_terminals``.
1679
+ expansion_factor : int, optional
1680
+ The method computes a float representing the largest number between
1681
+ the dielectric thickness or trace width multiplied by the expansion_factor factor.
1682
+ The trace width search is limited to nets with ports attached. Works only if `use_pyaedt_cutout`.
1683
+ Default is `0` to disable the search.
1684
+ maximum_iterations : int, optional
1685
+ Maximum number of iterations before stopping a search for a cutout with an error.
1686
+ Default is `10`.
1687
+ preserve_components_with_model : bool, optional
1688
+ Whether to preserve all pins of components that have associated models (Spice or NPort).
1689
+ This parameter is applicable only for a PyAEDT cutout (except point list).
1690
+ simple_pad_check : bool, optional
1691
+ Whether to use the center of the pad to find the intersection with extent or use the bounding box.
1692
+ Second method is much slower and requires to disable multithread on padstack removal.
1693
+ Default is `True`.
1694
+ keep_lines_as_path : bool, optional
1695
+ Whether to keep the lines as Path after they are cutout or convert them to PolygonData.
1696
+ This feature works only in Electronics Desktop (3D Layout).
1697
+ If the flag is set to ``True`` it can cause issues in SiWave once the Edb is imported.
1698
+ Default is ``False`` to generate PolygonData of cut lines.
1699
+ include_voids_in_extents : bool, optional
1700
+ Whether to compute and include voids in pyaedt extent before the cutout. Cutout time can be affected.
1701
+ It works only with Conforming cutout.
1702
+ Default is ``False`` to generate extent without voids.
1703
+
1704
+
1705
+ Returns
1706
+ -------
1707
+ List
1708
+ List of coordinate points defining the extent used for clipping the design. If failed, return an empty
1709
+ list.
1710
+
1711
+ Examples
1712
+ --------
1713
+ >>> from pyedb import Edb
1714
+ >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2")
1715
+ >>> edb.logger.info_timer("Edb Opening")
1716
+ >>> edb.logger.reset_timer()
1717
+ >>> start = time.time()
1718
+ >>> signal_list = []
1719
+ >>> for net in edb.nets.netlist:
1720
+ >>> if "3V3" in net:
1721
+ >>> signal_list.append(net)
1722
+ >>> power_list = ["PGND"]
1723
+ >>> edb.cutout(signal_list=signal_list, reference_list=power_list, extent_type="Conforming")
1724
+ >>> end_time = str((time.time() - start)/60)
1725
+ >>> edb.logger.info("Total legacy cutout time in min %s", end_time)
1726
+ >>> edb.nets.plot(signal_list, None, color_by_net=True)
1727
+ >>> edb.nets.plot(power_list, None, color_by_net=True)
1728
+ >>> edb.save_edb()
1729
+ >>> edb.close_edb()
1730
+
1731
+
1732
+ """
1733
+ if expansion_factor > 0:
1734
+ expansion_size = self.calculate_initial_extent(expansion_factor)
1735
+ if signal_list is None:
1736
+ signal_list = []
1737
+ if isinstance(reference_list, str):
1738
+ reference_list = [reference_list]
1739
+ elif reference_list is None:
1740
+ reference_list = []
1741
+ if not use_pyaedt_cutout and custom_extent:
1742
+ return self._create_cutout_on_point_list(
1743
+ custom_extent,
1744
+ units=custom_extent_units,
1745
+ output_aedb_path=output_aedb_path,
1746
+ open_cutout_at_end=open_cutout_at_end,
1747
+ nets_to_include=signal_list + reference_list,
1748
+ include_partial_instances=include_partial_instances,
1749
+ keep_voids=keep_voids,
1750
+ )
1751
+ elif not use_pyaedt_cutout:
1752
+ return self._create_cutout_legacy(
1753
+ signal_list=signal_list,
1754
+ reference_list=reference_list,
1755
+ extent_type=extent_type,
1756
+ expansion_size=expansion_size,
1757
+ use_round_corner=use_round_corner,
1758
+ output_aedb_path=output_aedb_path,
1759
+ open_cutout_at_end=open_cutout_at_end,
1760
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
1761
+ check_terminals=check_terminals,
1762
+ include_pingroups=include_pingroups,
1763
+ inlcude_voids_in_extents=include_voids_in_extents,
1764
+ )
1765
+ else:
1766
+ legacy_path = self.edbpath
1767
+ if expansion_factor > 0 and not custom_extent:
1768
+ start = time.time()
1769
+ self.save_edb()
1770
+ dummy_path = self.edbpath.replace(".aedb", "_smart_cutout_temp.aedb")
1771
+ working_cutout = False
1772
+ i = 1
1773
+ expansion = expansion_size
1774
+ while i <= maximum_iterations:
1775
+ self.logger.info("-----------------------------------------")
1776
+ self.logger.info(f"Trying cutout with {expansion * 1e3}mm expansion size")
1777
+ self.logger.info("-----------------------------------------")
1778
+ result = self._create_cutout_multithread(
1779
+ signal_list=signal_list,
1780
+ reference_list=reference_list,
1781
+ extent_type=extent_type,
1782
+ expansion_size=expansion,
1783
+ use_round_corner=use_round_corner,
1784
+ number_of_threads=number_of_threads,
1785
+ custom_extent=custom_extent,
1786
+ output_aedb_path=dummy_path,
1787
+ remove_single_pin_components=remove_single_pin_components,
1788
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
1789
+ extent_defeature=extent_defeature,
1790
+ custom_extent_units=custom_extent_units,
1791
+ check_terminals=check_terminals,
1792
+ include_pingroups=include_pingroups,
1793
+ preserve_components_with_model=preserve_components_with_model,
1794
+ include_partial=include_partial_instances,
1795
+ simple_pad_check=simple_pad_check,
1796
+ keep_lines_as_path=keep_lines_as_path,
1797
+ inlcude_voids_in_extents=include_voids_in_extents,
1798
+ )
1799
+ if self.are_port_reference_terminals_connected():
1800
+ if output_aedb_path:
1801
+ self.save_edb_as(output_aedb_path)
1802
+ else:
1803
+ self.save_edb_as(legacy_path)
1804
+ working_cutout = True
1805
+ break
1806
+ self.close_edb()
1807
+ self.edbpath = legacy_path
1808
+ self.open_edb()
1809
+ i += 1
1810
+ expansion = expansion_size * i
1811
+ if working_cutout:
1812
+ msg = f"Cutout completed in {i} iterations with expansion size of {expansion * 1e3}mm"
1813
+ self.logger.info_timer(msg, start)
1814
+ else:
1815
+ msg = f"Cutout failed after {i} iterations and expansion size of {expansion * 1e3}mm"
1816
+ self.logger.info_timer(msg, start)
1817
+ return False
1818
+ else:
1819
+ result = self._create_cutout_multithread(
1820
+ signal_list=signal_list,
1821
+ reference_list=reference_list,
1822
+ extent_type=extent_type,
1823
+ expansion_size=expansion_size,
1824
+ use_round_corner=use_round_corner,
1825
+ number_of_threads=number_of_threads,
1826
+ custom_extent=custom_extent,
1827
+ output_aedb_path=output_aedb_path,
1828
+ remove_single_pin_components=remove_single_pin_components,
1829
+ use_pyaedt_extent_computing=use_pyaedt_extent_computing,
1830
+ extent_defeature=extent_defeature,
1831
+ custom_extent_units=custom_extent_units,
1832
+ check_terminals=check_terminals,
1833
+ include_pingroups=include_pingroups,
1834
+ preserve_components_with_model=preserve_components_with_model,
1835
+ include_partial=include_partial_instances,
1836
+ simple_pad_check=simple_pad_check,
1837
+ keep_lines_as_path=keep_lines_as_path,
1838
+ inlcude_voids_in_extents=include_voids_in_extents,
1839
+ )
1840
+ if result and not open_cutout_at_end and self.edbpath != legacy_path:
1841
+ self.save_edb()
1842
+ self.close_edb()
1843
+ self.edbpath = legacy_path
1844
+ self.open_edb()
1845
+ return result
1846
+
1847
+ def _create_cutout_legacy(
1848
+ self,
1849
+ signal_list=[],
1850
+ reference_list=["GND"],
1851
+ extent_type="Conforming",
1852
+ expansion_size=0.002,
1853
+ use_round_corner=False,
1854
+ output_aedb_path=None,
1855
+ open_cutout_at_end=True,
1856
+ use_pyaedt_extent_computing=False,
1857
+ remove_single_pin_components=False,
1858
+ check_terminals=False,
1859
+ include_pingroups=True,
1860
+ inlcude_voids_in_extents=False,
1861
+ ):
1862
+ expansion_size = GrpcValue(expansion_size).value
1863
+
1864
+ # validate nets in layout
1865
+ net_signals = [net for net in self.layout.nets if net.name in signal_list]
1866
+
1867
+ # validate references in layout
1868
+ _netsClip = [net for net in self.layout.nets if net.name in reference_list]
1869
+
1870
+ _poly = self._create_extent(
1871
+ net_signals,
1872
+ extent_type,
1873
+ expansion_size,
1874
+ use_round_corner,
1875
+ use_pyaedt_extent_computing,
1876
+ smart_cut=check_terminals,
1877
+ reference_list=reference_list,
1878
+ include_pingroups=include_pingroups,
1879
+ inlcude_voids_in_extents=inlcude_voids_in_extents,
1880
+ )
1881
+ _poly1 = GrpcPolygonData(arcs=_poly.arc_data, closed=True)
1882
+ if inlcude_voids_in_extents:
1883
+ for hole in _poly.holes:
1884
+ if hole.area() >= 0.05 * _poly1.area():
1885
+ _poly1.holes.append(hole)
1886
+ _poly = _poly1
1887
+ # Create new cutout cell/design
1888
+ included_nets_list = signal_list + reference_list
1889
+ included_nets = [net for net in self.layout.nets if net.name in included_nets_list]
1890
+ _cutout = self.active_cell.cutout(included_nets, _netsClip, _poly, True)
1891
+ # _cutout.simulation_setups = self.active_cell.simulation_setups see bug #433 status.
1892
+ _dbCells = [_cutout]
1893
+ if output_aedb_path:
1894
+ db2 = self.create(output_aedb_path)
1895
+ _success = db2.save()
1896
+ _dbCells = _dbCells
1897
+ db2.copy_cells(_dbCells) # Copies cutout cell/design to db2 project
1898
+ if len(list(db2.circuit_cells)) > 0:
1899
+ for net in db2.circuit_cells[0].layout.nets:
1900
+ if not net.name in included_nets_list:
1901
+ net.delete()
1902
+ _success = db2.save()
1903
+ for c in self.active_db.top_circuit_cells:
1904
+ if c.name == _cutout.name:
1905
+ c.delete()
1906
+ if open_cutout_at_end: # pragma: no cover
1907
+ self._db = db2
1908
+ self.edbpath = output_aedb_path
1909
+ self._active_cell = self.top_circuit_cells[0]
1910
+ self.edbpath = self.directory
1911
+ self._init_objects()
1912
+ if remove_single_pin_components:
1913
+ self.components.delete_single_pin_rlc()
1914
+ self.logger.info_timer("Single Pins components deleted")
1915
+ self.components.refresh_components()
1916
+ else:
1917
+ if remove_single_pin_components:
1918
+ try:
1919
+ from ansys.edb.core.hierarchy.component_group import (
1920
+ ComponentGroup as GrpcComponentGroup,
1921
+ )
1922
+
1923
+ layout = db2.circuit_cells[0].layout
1924
+ _cmps = [l for l in layout.groups if isinstance(l, GrpcComponentGroup) and l.num_pins < 2]
1925
+ for _cmp in _cmps:
1926
+ _cmp.delete()
1927
+ except:
1928
+ self._logger.error("Failed to remove single pin components.")
1929
+ db2.close()
1930
+ source = os.path.join(output_aedb_path, "edb.def.tmp")
1931
+ target = os.path.join(output_aedb_path, "edb.def")
1932
+ self._wait_for_file_release(file_to_release=output_aedb_path)
1933
+ if os.path.exists(source) and not os.path.exists(target):
1934
+ try:
1935
+ shutil.copy(source, target)
1936
+ except:
1937
+ pass
1938
+ elif open_cutout_at_end:
1939
+ self._active_cell = _cutout
1940
+ self._init_objects()
1941
+ if remove_single_pin_components:
1942
+ self.components.delete_single_pin_rlc()
1943
+ self.logger.info_timer("Single Pins components deleted")
1944
+ self.components.refresh_components()
1945
+ return [[pt.x.value, pt.y.value] for pt in _poly.without_arcs().points]
1946
+
1947
+ def _create_cutout_multithread(
1948
+ self,
1949
+ signal_list=[],
1950
+ reference_list=["GND"],
1951
+ extent_type="Conforming",
1952
+ expansion_size=0.002,
1953
+ use_round_corner=False,
1954
+ number_of_threads=4,
1955
+ custom_extent=None,
1956
+ output_aedb_path=None,
1957
+ remove_single_pin_components=False,
1958
+ use_pyaedt_extent_computing=False,
1959
+ extent_defeature=0.0,
1960
+ custom_extent_units="mm",
1961
+ check_terminals=False,
1962
+ include_pingroups=True,
1963
+ preserve_components_with_model=False,
1964
+ include_partial=False,
1965
+ simple_pad_check=True,
1966
+ keep_lines_as_path=False,
1967
+ inlcude_voids_in_extents=False,
1968
+ ):
1969
+ from concurrent.futures import ThreadPoolExecutor
1970
+
1971
+ if output_aedb_path:
1972
+ self.save_edb_as(output_aedb_path)
1973
+ self.logger.info("Cutout Multithread started.")
1974
+ expansion_size = GrpcValue(expansion_size).value
1975
+
1976
+ timer_start = self.logger.reset_timer()
1977
+ if custom_extent:
1978
+ if not reference_list and not signal_list:
1979
+ reference_list = self.nets.netlist[::]
1980
+ all_list = reference_list
1981
+ else:
1982
+ reference_list = reference_list + signal_list
1983
+ all_list = reference_list
1984
+ else:
1985
+ all_list = signal_list + reference_list
1986
+ pins_to_preserve = []
1987
+ nets_to_preserve = []
1988
+ if preserve_components_with_model:
1989
+ for el in self.components.instances.values():
1990
+ if el.model_type in [
1991
+ "SPICEModel",
1992
+ "SParameterModel",
1993
+ "NetlistModel",
1994
+ ] and list(set(el.nets[:]) & set(signal_list[:])):
1995
+ pins_to_preserve.extend([i.edb_uid for i in el.pins.values()])
1996
+ nets_to_preserve.extend(el.nets)
1997
+ if include_pingroups:
1998
+ for pingroup in self.layout.pin_groups:
1999
+ for pin in pingroup.pins:
2000
+ if pin.net_name in reference_list:
2001
+ pins_to_preserve.append(pin.edb_uid)
2002
+ if check_terminals:
2003
+ terms = [
2004
+ term for term in self.layout.terminals if term.boundary_type in get_terminal_supported_boundary_types()
2005
+ ]
2006
+ for term in terms:
2007
+ if isinstance(term, PadstackInstanceTerminal):
2008
+ if term.net.name in reference_list:
2009
+ pins_to_preserve.append(term.edb_uid)
2010
+
2011
+ for i in self.nets.nets.values():
2012
+ name = i.name
2013
+ if name not in all_list and name not in nets_to_preserve:
2014
+ i.delete()
2015
+ reference_pinsts = []
2016
+ reference_prims = []
2017
+ reference_paths = []
2018
+ for i in self.padstacks.instances.values():
2019
+ net_name = i.net_name
2020
+ id = i.id
2021
+ if net_name not in all_list and id not in pins_to_preserve:
2022
+ i.delete()
2023
+ elif net_name in reference_list and id not in pins_to_preserve:
2024
+ reference_pinsts.append(i)
2025
+ for i in self.modeler.primitives:
2026
+ if not i.is_null and not i.net.is_null:
2027
+ if i.net.name not in all_list:
2028
+ i.delete()
2029
+ elif i.net.name in reference_list and not i.is_void:
2030
+ if keep_lines_as_path and isinstance(i, Path):
2031
+ reference_paths.append(i)
2032
+ else:
2033
+ reference_prims.append(i)
2034
+ self.logger.info_timer("Net clean up")
2035
+ self.logger.reset_timer()
2036
+
2037
+ if custom_extent and isinstance(custom_extent, list):
2038
+ if custom_extent[0] != custom_extent[-1]:
2039
+ custom_extent.append(custom_extent[0])
2040
+ custom_extent = [
2041
+ [
2042
+ self.number_with_units(i[0], custom_extent_units),
2043
+ self.number_with_units(i[1], custom_extent_units),
2044
+ ]
2045
+ for i in custom_extent
2046
+ ]
2047
+ _poly = GrpcPolygonData(points=custom_extent)
2048
+ elif custom_extent:
2049
+ _poly = custom_extent
2050
+ else:
2051
+ net_signals = [net for net in self.layout.nets if net.name in signal_list]
2052
+ _poly = self._create_extent(
2053
+ net_signals,
2054
+ extent_type,
2055
+ expansion_size,
2056
+ use_round_corner,
2057
+ use_pyaedt_extent_computing,
2058
+ smart_cut=check_terminals,
2059
+ reference_list=reference_list,
2060
+ include_pingroups=include_pingroups,
2061
+ pins_to_preserve=pins_to_preserve,
2062
+ inlcude_voids_in_extents=inlcude_voids_in_extents,
2063
+ )
2064
+ from ansys.edb.core.geometry.polygon_data import (
2065
+ ExtentType as GrpcExtentType,
2066
+ )
2067
+
2068
+ if extent_type in ["Conforming", GrpcExtentType.CONFORMING, 1]:
2069
+ if extent_defeature > 0:
2070
+ _poly = _poly.defeature(extent_defeature)
2071
+ _poly1 = GrpcPolygonData(arcs=_poly.arc_data, closed=True)
2072
+ if inlcude_voids_in_extents:
2073
+ for hole in list(_poly.holes):
2074
+ if hole.area() >= 0.05 * _poly1.area():
2075
+ _poly1.holes.append(hole)
2076
+ self.logger.info(f"Number of voids included:{len(list(_poly1.holes))}")
2077
+ _poly = _poly1
2078
+ if not _poly.points:
2079
+ self._logger.error("Failed to create Extent.")
2080
+ return []
2081
+ self.logger.info_timer("Expanded Net Polygon Creation")
2082
+ self.logger.reset_timer()
2083
+ _poly_list = [_poly]
2084
+ prims_to_delete = []
2085
+ poly_to_create = []
2086
+ pins_to_delete = []
2087
+
2088
+ def intersect(poly1, poly2):
2089
+ if not isinstance(poly2, list):
2090
+ poly2 = [poly2]
2091
+ return poly1.intersect(poly1, poly2)
2092
+
2093
+ def subtract(poly, voids):
2094
+ return poly.subtract(poly, voids)
2095
+
2096
+ def clip_path(path):
2097
+ pdata = path.polygon_data
2098
+ int_data = _poly.intersection_type(pdata)
2099
+ if int_data == 0:
2100
+ prims_to_delete.append(path)
2101
+ return
2102
+ result = path.set_clip_info(_poly, True)
2103
+ if not result:
2104
+ self.logger.info(f"Failed to clip path {path.id}. Clipping as polygon.")
2105
+ reference_prims.append(path)
2106
+
2107
+ def clean_prim(prim_1): # pragma: no cover
2108
+ pdata = prim_1.polygon_data
2109
+ int_data = _poly.intersection_type(pdata)
2110
+ if int_data == 2:
2111
+ if not inlcude_voids_in_extents:
2112
+ return
2113
+ skip = False
2114
+ for hole in list(_poly.Holes):
2115
+ if hole.intersection_type(pdata) == 0:
2116
+ prims_to_delete.append(prim_1)
2117
+ return
2118
+ elif hole.intersection_type(pdata) == 1:
2119
+ skip = True
2120
+ if skip:
2121
+ return
2122
+ elif int_data == 0:
2123
+ prims_to_delete.append(prim_1)
2124
+ return
2125
+ list_poly = intersect(_poly, pdata)
2126
+ if list_poly:
2127
+ net = prim_1.net.name
2128
+ voids = prim_1.voids
2129
+ for p in list_poly:
2130
+ if not p.points:
2131
+ continue
2132
+ list_void = []
2133
+ if voids:
2134
+ voids_data = [void.polygon_data for void in voids]
2135
+ list_prims = subtract(p, voids_data)
2136
+ for prim in list_prims:
2137
+ if prim.points:
2138
+ poly_to_create.append([prim, prim_1.layer.name, net, list_void])
2139
+ else:
2140
+ poly_to_create.append([p, prim_1.layer.name, net, list_void])
2141
+
2142
+ prims_to_delete.append(prim_1)
2143
+
2144
+ def pins_clean(pinst):
2145
+ if not pinst.in_polygon(_poly, include_partial=include_partial, simple_check=simple_pad_check):
2146
+ pins_to_delete.append(pinst)
2147
+
2148
+ if not simple_pad_check:
2149
+ pad_cores = 1
2150
+ else:
2151
+ pad_cores = number_of_threads
2152
+ with ThreadPoolExecutor(pad_cores) as pool:
2153
+ pool.map(lambda item: pins_clean(item), reference_pinsts)
2154
+
2155
+ for pin in pins_to_delete:
2156
+ pin.delete()
2157
+
2158
+ self.logger.info_timer(f"Padstack Instances removal completed. {len(pins_to_delete)} instances removed.")
2159
+ self.logger.reset_timer()
2160
+
2161
+ for item in reference_paths:
2162
+ clip_path(item)
2163
+ for prim in reference_prims: # removing multithreading as failing with new layer from primitive
2164
+ clean_prim(prim)
2165
+
2166
+ for el in poly_to_create:
2167
+ self.modeler.create_polygon(el[0], el[1], net_name=el[2], voids=el[3])
2168
+
2169
+ for prim in prims_to_delete:
2170
+ prim.delete()
2171
+
2172
+ self.logger.info_timer(f"Primitives cleanup completed. {len(prims_to_delete)} primitives deleted.")
2173
+ self.logger.reset_timer()
2174
+
2175
+ i = 0
2176
+ for _, val in self.components.instances.items():
2177
+ if val.numpins == 0:
2178
+ val.delete()
2179
+ i += 1
2180
+ i += 1
2181
+ self.logger.info(f"Deleted {i} additional components")
2182
+ if remove_single_pin_components:
2183
+ self.components.delete_single_pin_rlc()
2184
+ self.logger.info_timer("Single Pins components deleted")
2185
+
2186
+ self.components.refresh_components()
2187
+ if output_aedb_path:
2188
+ self.save_edb()
2189
+ self.logger.info_timer("Cutout completed.", timer_start)
2190
+ self.logger.reset_timer()
2191
+ return [[pt.x.value, pt.y.value] for pt in _poly.without_arcs().points]
2192
+
2193
+ def get_conformal_polygon_from_netlist(self, netlist=None):
2194
+ """Returns conformal polygon data based on a netlist.
2195
+
2196
+ Parameters
2197
+ ----------
2198
+ netlist : List of net names.
2199
+ list[str]
2200
+
2201
+ Returns
2202
+ -------
2203
+ :class:`PolygonData <ansys.edb.core.geometry.polygon_data.PolygonData>`
2204
+ """
2205
+ from ansys.edb.core.geometry.polygon_data import ExtentType as GrpcExtentType
2206
+
2207
+ temp_edb_path = self.edbpath[:-5] + "_temp_aedb.aedb"
2208
+ shutil.copytree(self.edbpath, temp_edb_path)
2209
+ temp_edb = Edb(temp_edb_path)
2210
+ for via in list(temp_edb.padstacks.instances.values()):
2211
+ via.pin.delete()
2212
+ if netlist:
2213
+ nets = [net for net in temp_edb.layout.nets if net.name in netlist]
2214
+ _poly = temp_edb.layout.expanded_extent(nets, GrpcExtentType.CONFORMING, 0.0, True, True, 1)
2215
+ else:
2216
+ nets = [net for net in temp_edb.layout.nets if "gnd" in net.name.lower()]
2217
+ _poly = temp_edb.layout.expanded_extent(nets, GrpcExtentType.CONFORMING, 0.0, True, True, 1)
2218
+ temp_edb.close()
2219
+ if _poly:
2220
+ return _poly
2221
+ else:
2222
+ return False
2223
+
2224
+ def number_with_units(self, value, units=None):
2225
+ """Convert a number to a string with units. If value is a string, it's returned as is.
2226
+
2227
+ Parameters
2228
+ ----------
2229
+ value : float, int, str
2230
+ Input number or string.
2231
+ units : optional
2232
+ Units for formatting. The default is ``None``, which uses ``"meter"``.
2233
+
2234
+ Returns
2235
+ -------
2236
+ str
2237
+ String concatenating the value and unit.
2238
+
2239
+ """
2240
+ if units is None:
2241
+ units = "meter"
2242
+ if isinstance(value, str):
2243
+ return value
2244
+ else:
2245
+ return f"{value}{units}"
2246
+
2247
+ def _create_cutout_on_point_list(
2248
+ self,
2249
+ point_list,
2250
+ units="mm",
2251
+ output_aedb_path=None,
2252
+ open_cutout_at_end=True,
2253
+ nets_to_include=None,
2254
+ include_partial_instances=False,
2255
+ keep_voids=True,
2256
+ ):
2257
+ from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
2258
+
2259
+ if point_list[0] != point_list[-1]:
2260
+ point_list.append(point_list[0])
2261
+ point_list = [[self.number_with_units(i[0], units), self.number_with_units(i[1], units)] for i in point_list]
2262
+ polygon_data = GrpcPolygonData(points=[GrpcPointData(pt) for pt in point_list])
2263
+ _ref_nets = []
2264
+ if nets_to_include:
2265
+ self.logger.info(f"Creating cutout on {len(nets_to_include)} nets.")
2266
+ else:
2267
+ self.logger.info("Creating cutout on all nets.") # pragma: no cover
2268
+
2269
+ # Check Padstack Instances overlapping the cutout
2270
+ pinstance_to_add = []
2271
+ if include_partial_instances:
2272
+ if nets_to_include:
2273
+ pinst = [i for i in list(self.padstacks.instances.values()) if i.net_name in nets_to_include]
2274
+ else:
2275
+ pinst = [i for i in list(self.padstacks.instances.values())]
2276
+ for p in pinst:
2277
+ pin_position = p.position # check bug #434 status
2278
+ if polygon_data.is_inside(p.position): # check bug #434 status
2279
+ pinstance_to_add.append(p)
2280
+ # validate references in layout
2281
+ for _ref in self.nets.nets:
2282
+ if nets_to_include:
2283
+ if _ref in nets_to_include:
2284
+ _ref_nets.append(self.nets.nets[_ref])
2285
+ else:
2286
+ _ref_nets.append(self.nets.nets[_ref]) # pragma: no cover
2287
+ if keep_voids:
2288
+ voids = [p for p in self.modeler.circles if p.is_void]
2289
+ voids2 = [p for p in self.modeler.polygons if p.is_void]
2290
+ voids.extend(voids2)
2291
+ else:
2292
+ voids = []
2293
+ voids_to_add = []
2294
+ for circle in voids:
2295
+ if polygon_data.get_intersection_type(circle.polygon_data) >= 3:
2296
+ voids_to_add.append(circle)
2297
+
2298
+ _netsClip = _ref_nets
2299
+ # Create new cutout cell/design
2300
+ _cutout = self.active_cell.cutout(_netsClip, _netsClip, polygon_data)
2301
+ layout = _cutout.layout
2302
+ cutout_obj_coll = layout.padstack_instances
2303
+ ids = []
2304
+ for lobj in cutout_obj_coll:
2305
+ ids.append(lobj.id)
2306
+ if include_partial_instances:
2307
+ from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
2308
+ from ansys.edb.core.primitive.primitive import (
2309
+ PadstackInstance as GrpcPadstackInstance,
2310
+ )
2311
+
2312
+ p_missing = [i for i in pinstance_to_add if i.id not in ids]
2313
+ self.logger.info(f"Added {len(p_missing)} padstack instances after cutout")
2314
+ for p in p_missing:
2315
+ position = GrpcPointData(p.position)
2316
+ net = self.nets.find_or_create_net(p.net_name)
2317
+ rotation = GrpcValue(p.rotation)
2318
+ sign_layers = list(self.stackup.signal_layers.keys())
2319
+ if not p.start_layer: # pragma: no cover
2320
+ fromlayer = self.stackup.signal_layers[sign_layers[0]]
2321
+ else:
2322
+ fromlayer = self.stackup.signal_layers[p.start_layer]
2323
+
2324
+ if not p.stop_layer: # pragma: no cover
2325
+ tolayer = self.stackup.signal_layers[sign_layers[-1]]
2326
+ else:
2327
+ tolayer = self.stackup.signal_layers[p.stop_layer]
2328
+ for pad in list(self.padstacks.definitions.keys()):
2329
+ if pad == p.padstack_definition:
2330
+ padstack = self.padstacks.definitions[pad]
2331
+ padstack_instance = GrpcPadstackInstance.create(
2332
+ layout=_cutout.layout,
2333
+ net=net,
2334
+ name=p.name,
2335
+ padstack_def=padstack,
2336
+ position_x=position.x,
2337
+ position_y=position.y,
2338
+ rotation=rotation,
2339
+ top_layer=fromlayer,
2340
+ bottom_layer=tolayer,
2341
+ layer_map=None,
2342
+ solder_ball_layer=None,
2343
+ )
2344
+ padstack_instance.is_layout_pin = p.is_pin
2345
+ break
2346
+
2347
+ for void_circle in voids_to_add:
2348
+ if isinstance(void_circle, Circle):
2349
+ res = void_circle.get_parameters()
2350
+ cloned_circle = Circle.create(
2351
+ layout=layout,
2352
+ layer=void_circle.layer.name,
2353
+ net=void_circle.net,
2354
+ center_x=res[0].x,
2355
+ center_y=res[0].y,
2356
+ radius=res[1],
2357
+ )
2358
+ cloned_circle.is_negative = True
2359
+ elif isinstance(void_circle, Polygon):
2360
+ cloned_polygon = Polygon.create(
2361
+ layout,
2362
+ void_circle.layer.name,
2363
+ void_circle.net,
2364
+ void_circle.polygon_data,
2365
+ )
2366
+ cloned_polygon.is_negative = True
2367
+ layers = [i for i in list(self.stackup.signal_layers.keys())]
2368
+ for layer in layers:
2369
+ layer_primitves = self.modeler.get_primitives(layer_name=layer)
2370
+ if len(layer_primitves) == 0:
2371
+ self.modeler.create_polygon(point_list, layer, net_name="DUMMY")
2372
+ self.logger.info(f"Cutout {_cutout.name} created correctly")
2373
+ for _setup in self.active_cell.simulation_setups:
2374
+ # Add the create Simulation setup to cutout cell
2375
+ # might need to add a clone setup method.
2376
+ pass
2377
+
2378
+ _dbCells = [_cutout]
2379
+ if output_aedb_path:
2380
+ db2 = self.create(output_aedb_path)
2381
+ db2.save()
2382
+ cell_copied = db2.copy_cells(_dbCells) # Copies cutout cell/design to db2 project
2383
+ cell = cell_copied[0]
2384
+ cell.name = os.path.basename(output_aedb_path[:-5])
2385
+ db2.save()
2386
+ for c in list(self.active_db.top_circuit_cells):
2387
+ if c.name == _cutout.name:
2388
+ c.delete()
2389
+ if open_cutout_at_end: # pragma: no cover
2390
+ _success = db2.save()
2391
+ self._db = db2
2392
+ self.edbpath = output_aedb_path
2393
+ self._active_cell = cell
2394
+ self.edbpath = self.directory
2395
+ self._init_objects()
2396
+ else:
2397
+ db2.close()
2398
+ source = os.path.join(output_aedb_path, "edb.def.tmp")
2399
+ target = os.path.join(output_aedb_path, "edb.def")
2400
+ self._wait_for_file_release(file_to_release=output_aedb_path)
2401
+ if os.path.exists(source) and not os.path.exists(target):
2402
+ try:
2403
+ shutil.copy(source, target)
2404
+ self.logger.warning("aedb def file manually created.")
2405
+ except:
2406
+ pass
2407
+ return [[pt.x.value, pt.y.value] for pt in polygon_data.without_arcs().points]
2408
+
2409
+ @staticmethod
2410
+ def write_export3d_option_config_file(path_to_output, config_dictionaries=None):
2411
+ """Write the options for a 3D export to a configuration file.
2412
+
2413
+ Parameters
2414
+ ----------
2415
+ path_to_output : str
2416
+ Full path to the configuration file to save 3D export options to.
2417
+
2418
+ config_dictionaries : dict, optional
2419
+ Configuration dictionaries. The default is ``None``.
2420
+
2421
+ """
2422
+ option_config = {
2423
+ "UNITE_NETS": 1,
2424
+ "ASSIGN_SOLDER_BALLS_AS_SOURCES": 0,
2425
+ "Q3D_MERGE_SOURCES": 0,
2426
+ "Q3D_MERGE_SINKS": 0,
2427
+ "CREATE_PORTS_FOR_PWR_GND_NETS": 0,
2428
+ "PORTS_FOR_PWR_GND_NETS": 0,
2429
+ "GENERATE_TERMINALS": 0,
2430
+ "SOLVE_CAPACITANCE": 0,
2431
+ "SOLVE_DC_RESISTANCE": 0,
2432
+ "SOLVE_DC_INDUCTANCE_RESISTANCE": 1,
2433
+ "SOLVE_AC_INDUCTANCE_RESISTANCE": 0,
2434
+ "CreateSources": 0,
2435
+ "CreateSinks": 0,
2436
+ "LAUNCH_Q3D": 0,
2437
+ "LAUNCH_HFSS": 0,
2438
+ }
2439
+ if config_dictionaries:
2440
+ for el, val in config_dictionaries.items():
2441
+ option_config[el] = val
2442
+ with open(os.path.join(path_to_output, "options.config"), "w") as f:
2443
+ for el, val in option_config.items():
2444
+ f.write(el + " " + str(val) + "\n")
2445
+ return os.path.join(path_to_output, "options.config")
2446
+
2447
+ def export_hfss(
2448
+ self,
2449
+ path_to_output,
2450
+ net_list=None,
2451
+ num_cores=None,
2452
+ aedt_file_name=None,
2453
+ hidden=False,
2454
+ ):
2455
+ """Export EDB to HFSS.
2456
+
2457
+ Parameters
2458
+ ----------
2459
+ path_to_output : str
2460
+ Full path and name for saving the AEDT file.
2461
+ net_list : list, optional
2462
+ List of nets to export if only certain ones are to be exported.
2463
+ The default is ``None``, in which case all nets are eported.
2464
+ num_cores : int, optional
2465
+ Number of cores to use for the export. The default is ``None``.
2466
+ aedt_file_name : str, optional
2467
+ Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``,
2468
+ in which case the default name is used.
2469
+ hidden : bool, optional
2470
+ Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden.
2471
+
2472
+ Returns
2473
+ -------
2474
+ str
2475
+ Full path to the AEDT file.
2476
+
2477
+ Examples
2478
+ --------
2479
+
2480
+ >>> from pyedb import Edb
2481
+ >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2023.2")
2482
+
2483
+ >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
2484
+ >>> edb.write_export3d_option_config_file(r"C:\temp", options_config)
2485
+ >>> edb.export_hfss(r"C:\temp")
2486
+ """
2487
+ siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path)
2488
+ return siwave_s.export_3d_cad("HFSS", path_to_output, net_list, num_cores, aedt_file_name, hidden=hidden)
2489
+
2490
+ def export_q3d(
2491
+ self,
2492
+ path_to_output,
2493
+ net_list=None,
2494
+ num_cores=None,
2495
+ aedt_file_name=None,
2496
+ hidden=False,
2497
+ ):
2498
+ """Export EDB to Q3D.
2499
+
2500
+ Parameters
2501
+ ----------
2502
+ path_to_output : str
2503
+ Full path and name for saving the AEDT file.
2504
+ net_list : list, optional
2505
+ List of nets to export only if certain ones are to be exported.
2506
+ The default is ``None``, in which case all nets are eported.
2507
+ num_cores : int, optional
2508
+ Number of cores to use for the export. The default is ``None``.
2509
+ aedt_file_name : str, optional
2510
+ Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``,
2511
+ in which case the default name is used.
2512
+ hidden : bool, optional
2513
+ Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden.
2514
+
2515
+ Returns
2516
+ -------
2517
+ str
2518
+ Full path to the AEDT file.
2519
+
2520
+ Examples
2521
+ --------
2522
+
2523
+ >>> from pyedb import Edb
2524
+ >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2")
2525
+ >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
2526
+ >>> edb.write_export3d_option_config_file(r"C:\temp", options_config)
2527
+ >>> edb.export_q3d(r"C:\temp")
2528
+ """
2529
+
2530
+ siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path)
2531
+ return siwave_s.export_3d_cad(
2532
+ "Q3D",
2533
+ path_to_output,
2534
+ net_list,
2535
+ num_cores=num_cores,
2536
+ aedt_file_name=aedt_file_name,
2537
+ hidden=hidden,
2538
+ )
2539
+
2540
+ def export_maxwell(
2541
+ self,
2542
+ path_to_output,
2543
+ net_list=None,
2544
+ num_cores=None,
2545
+ aedt_file_name=None,
2546
+ hidden=False,
2547
+ ):
2548
+ """Export EDB to Maxwell 3D.
2549
+
2550
+ Parameters
2551
+ ----------
2552
+ path_to_output : str
2553
+ Full path and name for saving the AEDT file.
2554
+ net_list : list, optional
2555
+ List of nets to export only if certain ones are to be
2556
+ exported. The default is ``None``, in which case all nets are exported.
2557
+ num_cores : int, optional
2558
+ Number of cores to use for the export. The default is ``None.``
2559
+ aedt_file_name : str, optional
2560
+ Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``,
2561
+ in which case the default name is used.
2562
+ hidden : bool, optional
2563
+ Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden.
2564
+
2565
+ Returns
2566
+ -------
2567
+ str
2568
+ Full path to the AEDT file.
2569
+
2570
+ Examples
2571
+ --------
2572
+
2573
+ >>> from pyedb import Edb
2574
+
2575
+ >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2")
2576
+
2577
+ >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0}
2578
+ >>> edb.write_export3d_option_config_file(r"C:\temp", options_config)
2579
+ >>> edb.export_maxwell(r"C:\temp")
2580
+ """
2581
+ siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path)
2582
+ return siwave_s.export_3d_cad(
2583
+ "Maxwell",
2584
+ path_to_output,
2585
+ net_list,
2586
+ num_cores=num_cores,
2587
+ aedt_file_name=aedt_file_name,
2588
+ hidden=hidden,
2589
+ )
2590
+
2591
+ def solve_siwave(self):
2592
+ """Close EDB and solve it with Siwave.
2593
+
2594
+ Returns
2595
+ -------
2596
+ str
2597
+ Siwave project path.
2598
+ """
2599
+ process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion)
2600
+ try:
2601
+ self.close()
2602
+ except:
2603
+ pass
2604
+ process.solve()
2605
+ return self.edbpath[:-5] + ".siw"
2606
+
2607
+ def export_siwave_dc_results(
2608
+ self,
2609
+ siwave_project,
2610
+ solution_name,
2611
+ output_folder=None,
2612
+ html_report=True,
2613
+ vias=True,
2614
+ voltage_probes=True,
2615
+ current_sources=True,
2616
+ voltage_sources=True,
2617
+ power_tree=True,
2618
+ loop_res=True,
2619
+ ):
2620
+ """Close EDB and solve it with Siwave.
2621
+
2622
+ Parameters
2623
+ ----------
2624
+ siwave_project : str
2625
+ Siwave full project name.
2626
+ solution_name : str
2627
+ Siwave DC Analysis name.
2628
+ output_folder : str, optional
2629
+ Ouptu folder where files will be downloaded.
2630
+ html_report : bool, optional
2631
+ Either if generate or not html report. Default is `True`.
2632
+ vias : bool, optional
2633
+ Either if generate or not vias report. Default is `True`.
2634
+ voltage_probes : bool, optional
2635
+ Either if generate or not voltage probe report. Default is `True`.
2636
+ current_sources : bool, optional
2637
+ Either if generate or not current source report. Default is `True`.
2638
+ voltage_sources : bool, optional
2639
+ Either if generate or not voltage source report. Default is `True`.
2640
+ power_tree : bool, optional
2641
+ Either if generate or not power tree image. Default is `True`.
2642
+ loop_res : bool, optional
2643
+ Either if generate or not loop resistance report. Default is `True`.
2644
+
2645
+ Returns
2646
+ -------
2647
+ list[str]
2648
+ List of files generated.
2649
+ """
2650
+ process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion)
2651
+ try:
2652
+ self.close()
2653
+ except:
2654
+ pass
2655
+ return process.export_dc_report(
2656
+ siwave_project,
2657
+ solution_name,
2658
+ output_folder,
2659
+ html_report,
2660
+ vias,
2661
+ voltage_probes,
2662
+ current_sources,
2663
+ voltage_sources,
2664
+ power_tree,
2665
+ loop_res,
2666
+ hidden=True,
2667
+ )
2668
+
2669
+ def variable_exists(self, variable_name):
2670
+ """Check if a variable exists or not.
2671
+
2672
+ Returns
2673
+ -------
2674
+ bool
2675
+ """
2676
+ if "$" in variable_name:
2677
+ if variable_name.index("$") == 0:
2678
+ variables = self.active_db.get_all_variable_names()
2679
+ else:
2680
+ variables = self.active_cell.get_all_variable_names()
2681
+ else:
2682
+ variables = self.active_cell.get_all_variable_names()
2683
+ if variable_name in variables:
2684
+ return True
2685
+ return False
2686
+
2687
+ def get_variable(self, variable_name):
2688
+ """Return Variable Value if variable exists.
2689
+
2690
+ Parameters
2691
+ ----------
2692
+ variable_name
2693
+
2694
+ Returns
2695
+ -------
2696
+ float
2697
+ """
2698
+ if self.variable_exists(variable_name):
2699
+ if "$" in variable_name:
2700
+ if variable_name.index("$") == 0:
2701
+ variable = next(var for var in self.active_db.get_all_variable_names())
2702
+ else:
2703
+ variable = next(var for var in self.active_cell.get_all_variable_names())
2704
+ return self.db.get_variable_value(variable)
2705
+ self.logger.info(f"Variable {variable_name} doesn't exists.")
2706
+ return False
2707
+
2708
+ def add_project_variable(self, variable_name, variable_value):
2709
+ """Add a variable to database. The variable will have the prefix `$`.
2710
+
2711
+ Parameters
2712
+ ----------
2713
+ variable_name : str
2714
+ Name of the variable. Name can be provided without ``$`` prefix.
2715
+ variable_value : str, float
2716
+ Value of the variable with units.
2717
+
2718
+ Returns
2719
+ -------
2720
+ bool
2721
+
2722
+ Examples
2723
+ --------
2724
+
2725
+ >>> from pyedb import Edb
2726
+ >>> edb_app = Edb()
2727
+ >>> boolean_1, ant_length = edb_app.add_project_variable("my_local_variable", "1cm")
2728
+ >>> print(edb_app["$my_local_variable"]) #using getitem
2729
+ >>> edb_app["$my_local_variable"] = "1cm" #using setitem
2730
+
2731
+ """
2732
+ if not variable_name.startswith("$"):
2733
+ variable_name = f"${variable_name}"
2734
+ if not self.variable_exists(variable_name):
2735
+ return self.active_db.add_variable(variable_name, variable_value)
2736
+ else:
2737
+ self.logger.error(f"Variable {variable_name} already exists.")
2738
+ return False
2739
+
2740
+ def add_design_variable(self, variable_name, variable_value, is_parameter=False):
2741
+ """Add a variable to edb. The variable can be a design one or a project variable (using ``$`` prefix).
2742
+
2743
+ Parameters
2744
+ ----------
2745
+ variable_name : str
2746
+ Name of the variable. To added the variable as a project variable, the name
2747
+ must begin with ``$``.
2748
+ variable_value : str, float
2749
+ Value of the variable with units.
2750
+ is_parameter : bool, optional
2751
+ Whether to add the variable as a local variable. The default is ``False``.
2752
+ When ``True``, the variable is added as a parameter default.
2753
+
2754
+ Returns
2755
+ -------
2756
+ bool.
2757
+
2758
+ Examples
2759
+ --------
2760
+
2761
+ >>> from pyedb import Edb
2762
+ >>> edb_app = Edb()
2763
+ >>> boolean_1, ant_length = edb_app.add_design_variable("my_local_variable", "1cm")
2764
+ >>> print(edb_app["my_local_variable"]) #using getitem
2765
+ >>> edb_app["my_local_variable"] = "1cm" #using setitem
2766
+ >>> boolean_2, para_length = edb_app.change_design_variable_value("my_parameter", "1m", is_parameter=True
2767
+ >>> boolean_3, project_length = edb_app.change_design_variable_value("$my_project_variable", "1m")
2768
+
2769
+
2770
+ """
2771
+ if variable_name.startswith("$"):
2772
+ variable_name = variable_name[1:]
2773
+ if not self.variable_exists(variable_name):
2774
+ return self.active_cell.add_variable(variable_name, variable_value)
2775
+ else:
2776
+ self.logger.error(f"Variable {variable_name} already exists.")
2777
+ return False
2778
+
2779
+ def change_design_variable_value(self, variable_name, variable_value):
2780
+ """Change a variable value.
2781
+
2782
+ Parameters
2783
+ ----------
2784
+ variable_name : str
2785
+ Name of the variable.
2786
+ variable_value : str, float
2787
+ Value of the variable with units.
2788
+
2789
+ Returns
2790
+ -------
2791
+ bool.
2792
+
2793
+ Examples
2794
+ --------
2795
+
2796
+ >>> from pyedb import Edb
2797
+ >>> edb_app = Edb()
2798
+ >>> boolean, ant_length = edb_app.add_design_variable("ant_length", "1cm")
2799
+ >>> boolean, ant_length = edb_app.change_design_variable_value("ant_length", "1m")
2800
+ >>> print(edb_app["ant_length"]) #using getitem
2801
+ """
2802
+ if self.variable_exists(variable_name):
2803
+ if variable_name in self.db.get_all_variable_names():
2804
+ self.db.set_variable_value(variable_name, GrpcValue(variable_value))
2805
+ elif variable_name in self.active_cell.get_all_variable_names():
2806
+ self.active_cell.set_variable_value(variable_name, GrpcValue(variable_value))
2807
+
2808
+ def get_bounding_box(self):
2809
+ """Get the layout bounding box.
2810
+
2811
+ Returns
2812
+ -------
2813
+ list[float]
2814
+ Bounding box as a [lower-left X, lower-left Y, upper-right X, upper-right Y] in meters.
2815
+ """
2816
+ lay_inst_polygon_data = [obj_inst.get_bbox() for obj_inst in self.layout_instance.query_layout_obj_instances()]
2817
+ layout_bbox = GrpcPolygonData.bbox_of_polygons(lay_inst_polygon_data)
2818
+ return [[layout_bbox[0].x.value, layout_bbox[0].y.value], [layout_bbox[1].x.value, layout_bbox[1].y.value]]
2819
+
2820
+ # def build_simulation_project(self, simulation_setup):
2821
+ # # type: (SimulationConfiguration) -> bool
2822
+ # """Build a ready-to-solve simulation project.
2823
+ #
2824
+ # Parameters
2825
+ # ----------
2826
+ # simulation_setup : :class:`pyedb.dotnet.database.edb_data.simulation_configuration.SimulationConfiguration`.
2827
+ # SimulationConfiguration object that can be instantiated or directly loaded with a
2828
+ # configuration file.
2829
+ #
2830
+ # Returns
2831
+ # -------
2832
+ # bool
2833
+ # ``True`` when successful, False when ``Failed``.
2834
+ #
2835
+ # Examples
2836
+ # --------
2837
+ #
2838
+ # >>> from pyedb import Edb
2839
+ # >>> from pyedb.dotnet.database.edb_data.simulation_configuration import SimulationConfiguration
2840
+ # >>> config_file = path_configuration_file
2841
+ # >>> source_file = path_to_edb_folder
2842
+ # >>> edb = Edb(source_file)
2843
+ # >>> sim_setup = SimulationConfiguration(config_file)
2844
+ # >>> edb.build_simulation_project(sim_setup)
2845
+ # >>> edb.save_edb()
2846
+ # >>> edb.close_edb()
2847
+ # """
2848
+ # self.logger.info("Building simulation project.")
2849
+ # from ansys.edb.core.layout.cell import CellType as GrpcCellType
2850
+ #
2851
+ # legacy_name = self.edbpath
2852
+ # if simulation_setup.output_aedb:
2853
+ # self.save_edb_as(simulation_setup.output_aedb)
2854
+ # if simulation_setup.signal_layer_etching_instances:
2855
+ # for layer in simulation_setup.signal_layer_etching_instances:
2856
+ # if layer in self.stackup.layers:
2857
+ # idx = simulation_setup.signal_layer_etching_instances.index(layer)
2858
+ # if len(simulation_setup.etching_factor_instances) > idx:
2859
+ # self.stackup[layer].etch_factor = float(simulation_setup.etching_factor_instances[idx])
2860
+ #
2861
+ # if not simulation_setup.signal_nets and simulation_setup.components:
2862
+ # nets_to_include = []
2863
+ # pnets = list(self.nets.power.keys())[:]
2864
+ # for el in simulation_setup.components:
2865
+ # nets_to_include.append([i for i in self.components[el].nets if i not in pnets])
2866
+ # simulation_setup.signal_nets = [
2867
+ # i
2868
+ # for i in list(set.intersection(*map(set, nets_to_include)))
2869
+ # if i not in simulation_setup.power_nets and i != ""
2870
+ # ]
2871
+ # self.nets.classify_nets(simulation_setup.power_nets, simulation_setup.signal_nets)
2872
+ # if not simulation_setup.power_nets or not simulation_setup.signal_nets:
2873
+ # self.logger.info("Disabling cutout as no signals or power nets have been defined.")
2874
+ # simulation_setup.do_cutout_subdesign = False
2875
+ # if simulation_setup.do_cutout_subdesign:
2876
+ # self.logger.info(f"Cutting out using method: {simulation_setup.cutout_subdesign_type}")
2877
+ # if simulation_setup.use_default_cutout:
2878
+ # old_cell_name = self.active_cell.name
2879
+ # if self.cutout(
2880
+ # signal_list=simulation_setup.signal_nets,
2881
+ # reference_list=simulation_setup.power_nets,
2882
+ # expansion_size=simulation_setup.cutout_subdesign_expansion,
2883
+ # use_round_corner=simulation_setup.cutout_subdesign_round_corner,
2884
+ # extent_type=simulation_setup.cutout_subdesign_type,
2885
+ # use_pyaedt_cutout=False,
2886
+ # use_pyaedt_extent_computing=False,
2887
+ # ):
2888
+ # self.logger.info("Cutout processed.")
2889
+ # old_cell = self.active_cell.find_by_name(
2890
+ # self.db,
2891
+ # GrpcCellType.CIRCUIT_CELL,
2892
+ # old_cell_name,
2893
+ # )
2894
+ # if old_cell:
2895
+ # old_cell.delete()
2896
+ # else: # pragma: no cover
2897
+ # self.logger.error("Cutout failed.")
2898
+ # else:
2899
+ # self.logger.info(f"Cutting out using method: {simulation_setup.cutout_subdesign_type}")
2900
+ # self.cutout(
2901
+ # signal_list=simulation_setup.signal_nets,
2902
+ # reference_list=simulation_setup.power_nets,
2903
+ # expansion_size=simulation_setup.cutout_subdesign_expansion,
2904
+ # use_round_corner=simulation_setup.cutout_subdesign_round_corner,
2905
+ # extent_type=simulation_setup.cutout_subdesign_type,
2906
+ # use_pyaedt_cutout=True,
2907
+ # use_pyaedt_extent_computing=True,
2908
+ # remove_single_pin_components=True,
2909
+ # )
2910
+ # self.logger.info("Cutout processed.")
2911
+ # else:
2912
+ # if simulation_setup.include_only_selected_nets:
2913
+ # included_nets = simulation_setup.signal_nets + simulation_setup.power_nets
2914
+ # nets_to_remove = [net.name for net in list(self.nets.nets.values()) if not net.name in included_nets]
2915
+ # self.nets.delete(nets_to_remove)
2916
+ # self.logger.info("Deleting existing ports.")
2917
+ # map(lambda port: port.Delete(), self.layout.terminals)
2918
+ # map(lambda pg: pg.delete(), self.layout.pin_groups)
2919
+ # if simulation_setup.solver_type == SolverType.Hfss3dLayout:
2920
+ # if simulation_setup.generate_excitations:
2921
+ # self.logger.info("Creating HFSS ports for signal nets.")
2922
+ # source_type = SourceType.CoaxPort
2923
+ # if not simulation_setup.generate_solder_balls:
2924
+ # source_type = SourceType.CircPort
2925
+ # for cmp in simulation_setup.components:
2926
+ # if isinstance(cmp, str): # keep legacy component
2927
+ # self.components.create_port_on_component(
2928
+ # cmp,
2929
+ # net_list=simulation_setup.signal_nets,
2930
+ # do_pingroup=False,
2931
+ # reference_net=simulation_setup.power_nets,
2932
+ # port_type=source_type,
2933
+ # )
2934
+ # elif isinstance(cmp, dict):
2935
+ # if "refdes" in cmp:
2936
+ # if not "solder_balls_height" in cmp: # pragma no cover
2937
+ # cmp["solder_balls_height"] = None
2938
+ # if not "solder_balls_size" in cmp: # pragma no cover
2939
+ # cmp["solder_balls_size"] = None
2940
+ # cmp["solder_balls_mid_size"] = None
2941
+ # if not "solder_balls_mid_size" in cmp: # pragma no cover
2942
+ # cmp["solder_balls_mid_size"] = None
2943
+ # self.components.create_port_on_component(
2944
+ # cmp["refdes"],
2945
+ # net_list=simulation_setup.signal_nets,
2946
+ # do_pingroup=False,
2947
+ # reference_net=simulation_setup.power_nets,
2948
+ # port_type=source_type,
2949
+ # solder_balls_height=cmp["solder_balls_height"],
2950
+ # solder_balls_size=cmp["solder_balls_size"],
2951
+ # solder_balls_mid_size=cmp["solder_balls_mid_size"],
2952
+ # )
2953
+ # if simulation_setup.generate_solder_balls and not self.hfss.set_coax_port_attributes(
2954
+ # simulation_setup
2955
+ # ): # pragma: no cover
2956
+ # self.logger.error("Failed to configure coaxial port attributes.")
2957
+ # self.logger.info(f"Number of ports: {self.hfss.get_ports_number()}")
2958
+ # self.logger.info("Configure HFSS extents.")
2959
+ # if simulation_setup.generate_solder_balls and simulation_setup.trim_reference_size:
2960
+ # self.logger.info(
2961
+ # f"Trimming the reference plane for coaxial ports: {bool(simulation_setup.trim_reference_size)}"
2962
+ # )
2963
+ # self.hfss.trim_component_reference_size(simulation_setup) # pragma: no cover
2964
+ # self.hfss.configure_hfss_extents(simulation_setup)
2965
+ # if not self.hfss.configure_hfss_analysis_setup(simulation_setup):
2966
+ # self.logger.error("Failed to configure HFSS simulation setup.")
2967
+ # if simulation_setup.solver_type == SolverType.SiwaveSYZ:
2968
+ # if simulation_setup.generate_excitations:
2969
+ # for cmp in simulation_setup.components:
2970
+ # if isinstance(cmp, str): # keep legacy
2971
+ # self.components.create_port_on_component(
2972
+ # cmp,
2973
+ # net_list=simulation_setup.signal_nets,
2974
+ # do_pingroup=simulation_setup.do_pingroup,
2975
+ # reference_net=simulation_setup.power_nets,
2976
+ # port_type=SourceType.CircPort,
2977
+ # )
2978
+ # elif isinstance(cmp, dict):
2979
+ # if "refdes" in cmp: # pragma no cover
2980
+ # self.components.create_port_on_component(
2981
+ # cmp["refdes"],
2982
+ # net_list=simulation_setup.signal_nets,
2983
+ # do_pingroup=simulation_setup.do_pingroup,
2984
+ # reference_net=simulation_setup.power_nets,
2985
+ # port_type=SourceType.CircPort,
2986
+ # )
2987
+ # self.logger.info("Configuring analysis setup.")
2988
+ # if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover
2989
+ # self.logger.error("Failed to configure Siwave simulation setup.")
2990
+ # if simulation_setup.solver_type == SolverType.SiwaveDC:
2991
+ # if simulation_setup.generate_excitations:
2992
+ # self.components.create_source_on_component(simulation_setup.sources)
2993
+ # if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover
2994
+ # self.logger.error("Failed to configure Siwave simulation setup.")
2995
+ # self.padstacks.check_and_fix_via_plating()
2996
+ # self.save_edb()
2997
+ # if not simulation_setup.open_edb_after_build and simulation_setup.output_aedb:
2998
+ # self.close_edb()
2999
+ # self.edbpath = legacy_name
3000
+ # self.open_edb()
3001
+ # return True
3002
+
3003
+ def get_statistics(self, compute_area=False):
3004
+ """Get the EDBStatistics object.
3005
+
3006
+ Returns
3007
+ -------
3008
+ :class:`LayoutStatistics <pyedb.grpc.database.utility.layout_statistics.LayoutStatistics>`
3009
+ """
3010
+ return self.modeler.get_layout_statistics(evaluate_area=compute_area, net_list=None)
3011
+
3012
+ def are_port_reference_terminals_connected(self, common_reference=None):
3013
+ """Check if all terminal references in design are connected.
3014
+ If the reference nets are different, there is no hope for the terminal references to be connected.
3015
+ After we have identified a common reference net we need to loop the terminals again to get
3016
+ the correct reference terminals that uses that net.
3017
+
3018
+ Parameters
3019
+ ----------
3020
+ common_reference : str, optional
3021
+ Common Reference name. If ``None`` it will be searched in ports terminal.
3022
+ If a string is passed then all excitations must have such reference assigned.
3023
+
3024
+ Returns
3025
+ -------
3026
+ bool
3027
+ Either if the ports are connected to reference_name or not.
3028
+
3029
+ Examples
3030
+ --------
3031
+ >>> from pyedb import Edb
3032
+ >>>edb = Edb()
3033
+ >>> edb.hfss.create_edge_port_vertical(prim_1_id, ["-66mm", "-4mm"], "port_ver")
3034
+ >>> edb.hfss.create_edge_port_horizontal(
3035
+ >>> ... prim_1_id, ["-60mm", "-4mm"], prim_2_id, ["-59mm", "-4mm"], "port_hori", 30, "Lower"
3036
+ >>> ... )
3037
+ >>> edb.hfss.create_wave_port(traces[0].id, trace_paths[0][0], "wave_port")
3038
+ >>> edb.cutout(["Net1"])
3039
+ >>> assert edb.are_port_reference_terminals_connected()
3040
+ """
3041
+ all_sources = [i for i in self.excitations.values() if not isinstance(i, (WavePort, GapPort, BundleWavePort))]
3042
+ all_sources.extend([i for i in self.sources.values()])
3043
+ if not all_sources:
3044
+ return True
3045
+ self.logger.reset_timer()
3046
+ if not common_reference:
3047
+ common_reference = list(set([i.reference_net.name for i in all_sources if i.reference_net.name]))
3048
+ if len(common_reference) > 1:
3049
+ self.logger.error("More than 1 reference found.")
3050
+ return False
3051
+ if not common_reference:
3052
+ self.logger.error("No Reference found.")
3053
+ return False
3054
+
3055
+ common_reference = common_reference[0]
3056
+ all_sources = [i for i in all_sources if i.net.name != common_reference]
3057
+
3058
+ setList = [
3059
+ set(i.reference_object.get_connected_object_id_set())
3060
+ for i in all_sources
3061
+ if i.reference_object and i.reference_net.name == common_reference
3062
+ ]
3063
+ if len(setList) != len(all_sources):
3064
+ self.logger.error("No Reference found.")
3065
+ return False
3066
+ cmps = [
3067
+ i
3068
+ for i in list(self.components.resistors.values())
3069
+ if i.numpins == 2 and common_reference in i.nets and self._decompose_variable_value(i.res_value) <= 1
3070
+ ]
3071
+ cmps.extend(
3072
+ [i for i in list(self.components.inductors.values()) if i.numpins == 2 and common_reference in i.nets]
3073
+ )
3074
+
3075
+ for cmp in cmps:
3076
+ found = False
3077
+ ids = [i.id for i in cmp.pinlist]
3078
+ for list_obj in setList:
3079
+ if len(set(ids).intersection(list_obj)) == 1:
3080
+ for list_obj2 in setList:
3081
+ if list_obj2 != list_obj and len(set(ids).intersection(list_obj)) == 1:
3082
+ if (ids[0] in list_obj and ids[1] in list_obj2) or (
3083
+ ids[1] in list_obj and ids[0] in list_obj2
3084
+ ):
3085
+ setList[setList.index(list_obj)] = list_obj.union(list_obj2)
3086
+ setList[setList.index(list_obj2)] = list_obj.union(list_obj2)
3087
+ found = True
3088
+ break
3089
+ if found:
3090
+ break
3091
+
3092
+ # Get the set intersections for all the ID sets.
3093
+ iDintersection = set.intersection(*setList)
3094
+ self.logger.info_timer(f"Terminal reference primitive IDs total intersections = {len(iDintersection)}\n\n")
3095
+
3096
+ # If the intersections are non-zero, the terminal references are connected.
3097
+ return True if len(iDintersection) > 0 else False
3098
+
3099
+ # def new_simulation_configuration(self, filename=None):
3100
+ # # type: (str) -> SimulationConfiguration
3101
+ # """New SimulationConfiguration Object.
3102
+ #
3103
+ # Parameters
3104
+ # ----------
3105
+ # filename : str, optional
3106
+ # Input config file.
3107
+ #
3108
+ # Returns
3109
+ # -------
3110
+ # :class:`legacy.database.edb_data.simulation_configuration.SimulationConfiguration`
3111
+ # """
3112
+ # return SimulationConfiguration(filename, self)
3113
+
3114
+ @property
3115
+ def setups(self):
3116
+ """Get the dictionary of all EDB HFSS and SIwave setups.
3117
+
3118
+ Returns
3119
+ -------
3120
+ Dict[str,: class:`pyedb.grpc.database.simulation_setup.hfss_simulation_setup.HfssSimulationSetup`] or
3121
+ Dict[str,: class:`pyedb.grpc.database.simulation_setup.siwave_simulation_setup.SiwaveSimulationSetup`] or
3122
+ Dict[str,: class:`SIWaveDCIRSimulationSetup`] or
3123
+ Dict[str,: class:`pyedb.grpc.database.simulation_setup.raptor_x_simulation_setup.RaptorXSimulationSetup`]
3124
+
3125
+ """
3126
+ self._setups = {}
3127
+ for setup in self.active_cell.simulation_setups:
3128
+ setup = setup.cast()
3129
+ setup_type = setup.type.name
3130
+ if setup_type == "HFSS":
3131
+ self._setups[setup.name] = HfssSimulationSetup(self, setup)
3132
+ elif setup_type == "SI_WAVE":
3133
+ self._setups[setup.name] = SiwaveSimulationSetup(self, setup)
3134
+ elif setup_type == "SI_WAVE_DCIR":
3135
+ self._setups[setup.name] = SIWaveDCIRSimulationSetup(self, setup)
3136
+ elif setup_type == "RAPTOR_X":
3137
+ self._setups[setup.name] = RaptorXSimulationSetup(self, setup)
3138
+ return self._setups
3139
+
3140
+ @property
3141
+ def hfss_setups(self):
3142
+ """Active HFSS setup in EDB.
3143
+
3144
+ Returns
3145
+ -------
3146
+ Dict[str,
3147
+ :class:`HfssSimulationSetup <pyedb.grpc.database.simulation_setup.hfss_simulation_setup.HfssSimulationSetup>`]
3148
+
3149
+ """
3150
+ setups = {}
3151
+ for setup in self.active_cell.simulation_setups:
3152
+ if setup.type.name == "HFSS":
3153
+ setups[setup.name] = HfssSimulationSetup(self, setup)
3154
+ return setups
3155
+
3156
+ @property
3157
+ def siwave_dc_setups(self):
3158
+ """Active Siwave DC IR Setups.
3159
+
3160
+ Returns
3161
+ -------
3162
+ Dict[str,
3163
+ :class:`SIWaveDCIRSimulationSetup
3164
+ <pyedb.grpc.database.simulation_setup.siwave_dcir_simulation_setup.SIWaveDCIRSimulationSetup>`]
3165
+ """
3166
+ return {name: i for name, i in self.setups.items() if isinstance(i, SIWaveDCIRSimulationSetup)}
3167
+
3168
+ @property
3169
+ def siwave_ac_setups(self):
3170
+ """Active Siwave SYZ setups.
3171
+
3172
+ Returns
3173
+ -------
3174
+ Dict[str,:class:`SiwaveSimulationSetup
3175
+ <pyedb.grpc.database.simulation_setup.siwave_simulation_setup.SiwaveSimulationSetup>`]
3176
+ """
3177
+ return {name: i for name, i in self.setups.items() if isinstance(i, SiwaveSimulationSetup)}
3178
+
3179
+ def create_hfss_setup(self, name=None, start_frequency="0GHz", stop_frequency="20GHz", step_frequency="10MHz"):
3180
+ """Create an HFSS simulation setup from a template.
3181
+
3182
+ . deprecated:: pyedb 0.30.0
3183
+ Use :func:`pyedb.grpc.core.hfss.add_setup` instead.
3184
+
3185
+ Parameters
3186
+ ----------
3187
+ name : str, optional
3188
+ Setup name.
3189
+
3190
+ Returns
3191
+ -------
3192
+ :class:`HfssSimulationSetup <legacy.database.edb_data.hfss_simulation_setup_data.HfssSimulationSetup>`
3193
+
3194
+ """
3195
+ warnings.warn(
3196
+ "`create_hfss_setup` is deprecated and is now located here " "`pyedb.grpc.core.hfss.add_setup` instead.",
3197
+ DeprecationWarning,
3198
+ )
3199
+ return self._hfss.add_setup(
3200
+ name=name,
3201
+ distribution="linear",
3202
+ start_freq=start_frequency,
3203
+ stop_freq=stop_frequency,
3204
+ step_freq=step_frequency,
3205
+ )
3206
+
3207
+ def create_raptorx_setup(self, name=None):
3208
+ """Create an RaptorX simulation setup from a template.
3209
+
3210
+ Parameters
3211
+ ----------
3212
+ name : str, optional
3213
+ Setup name.
3214
+
3215
+ Returns
3216
+ -------
3217
+ :class:`RaptorXSimulationSetup <legacy.database.edb_data.raptor_x_simulation_setup_data.RaptorXSimulationSetup>`
3218
+
3219
+ """
3220
+ from ansys.edb.core.simulation_setup.raptor_x_simulation_setup import (
3221
+ RaptorXSimulationSetup as GrpcRaptorXSimulationSetup,
3222
+ )
3223
+
3224
+ if name in self.setups:
3225
+ self.logger.error("Setup name already used in the layout")
3226
+ return False
3227
+ version = self.edbversion.split(".")
3228
+ if int(version[0]) >= 2024 and int(version[-1]) >= 2 or int(version[0]) > 2024:
3229
+ setup = GrpcRaptorXSimulationSetup.create(cell=self.active_cell, name=name)
3230
+ return RaptorXSimulationSetup(self, setup)
3231
+ else:
3232
+ self.logger.error("RaptorX simulation only supported with Ansys release 2024R2 and higher")
3233
+ return False
3234
+
3235
+ def create_hfsspi_setup(self, name=None):
3236
+ # """Create an HFSS PI simulation setup from a template.
3237
+ #
3238
+ # Parameters
3239
+ # ----------
3240
+ # name : str, optional
3241
+ # Setup name.
3242
+ #
3243
+ # Returns
3244
+ # -------
3245
+ # :class:`legacy.database.edb_data.hfss_pi_simulation_setup_data.HFSSPISimulationSetup when succeeded, ``False``
3246
+ # when failed.
3247
+ #
3248
+ # """
3249
+ # if name in self.setups:
3250
+ # self.logger.error("Setup name already used in the layout")
3251
+ # return False
3252
+ # version = self.edbversion.split(".")
3253
+ # if float(self.edbversion) < 2024.2:
3254
+ # self.logger.error("HFSSPI simulation only supported with Ansys release 2024R2 and higher")
3255
+ # return False
3256
+ # return HFSSPISimulationSetup(self, name=name)
3257
+
3258
+ # TODO check HFSS-PI with Grpc. seems to defined at terminal level not setup.
3259
+ pass
3260
+
3261
+ def create_siwave_syz_setup(self, name=None, **kwargs):
3262
+ """Create a setup from a template.
3263
+
3264
+ Parameters
3265
+ ----------
3266
+ name : str, optional
3267
+ Setup name.
3268
+
3269
+ Returns
3270
+ -------
3271
+ :class:`SiwaveSimulationSetup
3272
+ <pyedb.grpc.database.simulation_setup.siwave_simulation_setup.SiwaveSimulationSetup>`
3273
+
3274
+ Examples
3275
+ --------
3276
+ >>> from pyedb import Edb
3277
+ >>> edbapp = Edb()
3278
+ >>> setup1 = edbapp.create_siwave_syz_setup("setup1")
3279
+ >>> setup1.add_frequency_sweep(frequency_sweep=[
3280
+ ... ["linear count", "0", "1kHz", 1],
3281
+ ... ["log scale", "1kHz", "0.1GHz", 10],
3282
+ ... ["linear scale", "0.1GHz", "10GHz", "0.1GHz"],
3283
+ ... ])
3284
+ """
3285
+ if not name:
3286
+ name = generate_unique_name("Siwave_SYZ")
3287
+ if name in self.setups:
3288
+ return False
3289
+ from ansys.edb.core.simulation_setup.siwave_simulation_setup import (
3290
+ SIWaveSimulationSetup as GrpcSIWaveSimulationSetup,
3291
+ )
3292
+
3293
+ setup = SiwaveSimulationSetup(self, GrpcSIWaveSimulationSetup.create(cell=self.active_cell, name=name))
3294
+ for k, v in kwargs.items():
3295
+ setattr(setup, k, v)
3296
+ return self.setups[name]
3297
+
3298
+ def create_siwave_dc_setup(self, name=None, **kwargs):
3299
+ """Create a setup from a template.
3300
+
3301
+ Parameters
3302
+ ----------
3303
+ name : str, optional
3304
+ Setup name.
3305
+
3306
+ Returns
3307
+ -------
3308
+ :class:`SIWaveDCIRSimulationSetup
3309
+ <pyedb.grpc.database.simulation_setup.siwave_dcir_simulation_setup.SIWaveDCIRSimulationSetup>`
3310
+
3311
+ Examples
3312
+ --------
3313
+ >>> from pyedb import Edb
3314
+ >>> edbapp = Edb()
3315
+ >>> setup1 = edbapp.create_siwave_dc_setup("setup1")
3316
+ >>> setup1.mesh_bondwires = True
3317
+
3318
+ """
3319
+ if not name:
3320
+ name = generate_unique_name("Siwave_DC")
3321
+ if name in self.setups:
3322
+ return False
3323
+ setup = SIWaveDCIRSimulationSetup(self, GrpcSIWaveDCIRSimulationSetup.create(cell=self.active_cell, name=name))
3324
+ for k, v in kwargs.items():
3325
+ setattr(setup, k, v)
3326
+ return setup
3327
+
3328
+ def calculate_initial_extent(self, expansion_factor):
3329
+ """Compute a float representing the larger number between the dielectric thickness or trace width
3330
+ multiplied by the nW factor. The trace width search is limited to nets with ports attached.
3331
+
3332
+ Parameters
3333
+ ----------
3334
+ expansion_factor : float
3335
+ Value for the width multiplier (nW factor).
3336
+
3337
+ Returns
3338
+ -------
3339
+ float
3340
+ """
3341
+ nets = []
3342
+ for port in self.excitations.values():
3343
+ nets.append(port.net.name)
3344
+ for port in self.sources.values():
3345
+ nets.append(port.net_name)
3346
+ nets = list(set(nets))
3347
+ max_width = 0
3348
+ for net in nets:
3349
+ for primitive in self.nets[net].primitives:
3350
+ if primitive.type == "Path":
3351
+ max_width = max(max_width, primitive.width)
3352
+
3353
+ for layer in list(self.stackup.dielectric_layers.values()):
3354
+ max_width = max(max_width, layer.thickness)
3355
+
3356
+ max_width = max_width * expansion_factor
3357
+ self.logger.info(f"The W factor is {expansion_factor}, The initial extent = {max_width}")
3358
+ return max_width
3359
+
3360
+ def copy_zones(self, working_directory=None):
3361
+ """Copy multizone EDB project to one new edb per zone.
3362
+
3363
+ Parameters
3364
+ ----------
3365
+ working_directory : str
3366
+ Directory path where all EDB project are copied, if empty will use the current EDB project.
3367
+
3368
+ Returns
3369
+ -------
3370
+ dict[str, [int,: class:`PolygonData <ansys.edb.core.geometry.polygon_data.PolygonData>`]]
3371
+ Return a dictionary with edb path as key and tuple Zone Id as first item and EDB polygon Data defining
3372
+ the region as second item.
3373
+
3374
+ """
3375
+ if working_directory:
3376
+ if not os.path.isdir(working_directory):
3377
+ os.mkdir(working_directory)
3378
+ else:
3379
+ shutil.rmtree(working_directory)
3380
+ os.mkdir(working_directory)
3381
+ else:
3382
+ working_directory = os.path.dirname(self.edbpath)
3383
+ self.layout.synchronize_bend_manager()
3384
+ zone_primitives = self.layout.zone_primitives
3385
+ zone_ids = self.stackup.zone_ids
3386
+ edb_zones = {}
3387
+ if not self.setups:
3388
+ self.siwave.add_siwave_syz_analysis()
3389
+ self.save_edb()
3390
+ for zone_primitive in zone_primitives:
3391
+ if zone_primitive:
3392
+ edb_zone_path = os.path.join(working_directory, f"{zone_primitive.id}_{os.path.basename(self.edbpath)}")
3393
+ shutil.copytree(self.edbpath, edb_zone_path)
3394
+ poly_data = zone_primitive.polygon_data
3395
+ if self.version[0] >= 10:
3396
+ edb_zones[edb_zone_path] = (zone_primitive.id, poly_data)
3397
+ elif len(zone_primitives) == len(zone_ids):
3398
+ edb_zones[edb_zone_path] = (zone_ids[0], poly_data)
3399
+ else:
3400
+ self.logger.info(
3401
+ "Number of zone primitives is not equal to zone number. Zone information will be lost."
3402
+ "Use Ansys 2024 R1 or later."
3403
+ )
3404
+ edb_zones[edb_zone_path] = (-1, poly_data)
3405
+ return edb_zones
3406
+
3407
+ def cutout_multizone_layout(self, zone_dict, common_reference_net=None):
3408
+ """Create a multizone project cutout.
3409
+
3410
+ Parameters
3411
+ ----------
3412
+ zone_dict : dict[str](EDB PolygonData)
3413
+ Dictionary with EDB path as key and EDB PolygonData as value defining the zone region.
3414
+ This dictionary is returned from the command copy_zones():
3415
+ >>> edb = Edb(edb_file)
3416
+ >>> zone_dict = edb.copy_zones(r"C:\Temp\test")
3417
+
3418
+ common_reference_net : str
3419
+ the common reference net name. This net name must be provided to provide a valid project.
3420
+
3421
+ Returns
3422
+ -------
3423
+ Dict[str: str] or List[str]
3424
+ first dictionary defined_ports with edb name as key and existing port name list as value. Those ports are the
3425
+ ones defined before processing the multizone clipping.
3426
+ second is the list of connected port.
3427
+
3428
+ """
3429
+ terminals = {}
3430
+ defined_ports = {}
3431
+ project_connexions = None
3432
+ for edb_path, zone_info in zone_dict.items():
3433
+ edb = Edb(edbversion=self.edbversion, edbpath=edb_path)
3434
+ edb.cutout(
3435
+ use_pyaedt_cutout=True,
3436
+ custom_extent=zone_info[1],
3437
+ open_cutout_at_end=True,
3438
+ )
3439
+ if not zone_info[0] == -1:
3440
+ layers_to_remove = [
3441
+ lay.name for lay in list(edb.stackup.layers.values()) if not lay.is_in_zone(zone_info[0])
3442
+ ]
3443
+ for layer in layers_to_remove:
3444
+ edb.stackup.remove_layer(layer)
3445
+ edb.stackup.stackup_mode = "Laminate"
3446
+ edb.cutout(
3447
+ use_pyaedt_cutout=True,
3448
+ custom_extent=zone_info[1],
3449
+ open_cutout_at_end=True,
3450
+ )
3451
+ edb.active_cell.name = os.path.splitext(os.path.basename(edb_path))[0]
3452
+ if common_reference_net:
3453
+ signal_nets = list(self.nets.signal.keys())
3454
+ defined_ports[os.path.splitext(os.path.basename(edb_path))[0]] = list(edb.excitations.keys())
3455
+ edb_terminals_info = edb.source_excitation.create_vertical_circuit_port_on_clipped_traces(
3456
+ nets=signal_nets,
3457
+ reference_net=common_reference_net,
3458
+ user_defined_extent=zone_info[1],
3459
+ )
3460
+ if edb_terminals_info:
3461
+ terminals[os.path.splitext(os.path.basename(edb_path))[0]] = edb_terminals_info
3462
+ project_connexions = self._get_connected_ports_from_multizone_cutout(terminals)
3463
+ edb.save_edb()
3464
+ edb.close_edb()
3465
+ return defined_ports, project_connexions
3466
+
3467
+ @staticmethod
3468
+ def _get_connected_ports_from_multizone_cutout(terminal_info_dict):
3469
+ """Return connected port list from clipped multizone layout.
3470
+
3471
+ Parameters
3472
+ terminal_info_dict : dict[str][str]
3473
+ dictionary terminals with edb name as key and created ports name on clipped signal nets.
3474
+ Dictionary is generated by the command cutout_multizone_layout:
3475
+ >>> edb = Edb(edb_file)
3476
+ >>> edb_zones = edb.copy_zones(r"C:\Temp\test")
3477
+ >>> defined_ports, terminals_info = edb.cutout_multizone_layout(edb_zones, common_reference_net)
3478
+ >>> project_connexions = get_connected_ports(terminals_info)
3479
+
3480
+ Returns
3481
+ -------
3482
+ list[str]
3483
+ list of connected ports.
3484
+ """
3485
+ if terminal_info_dict:
3486
+ tolerance = 1e-8
3487
+ connected_ports_list = []
3488
+ project_list = list(terminal_info_dict.keys())
3489
+ project_combinations = list(combinations(range(0, len(project_list)), 2))
3490
+ for comb in project_combinations:
3491
+ terminal_set1 = terminal_info_dict[project_list[comb[0]]]
3492
+ terminal_set2 = terminal_info_dict[project_list[comb[1]]]
3493
+ project1_nets = [t[0] for t in terminal_set1]
3494
+ project2_nets = [t[0] for t in terminal_set2]
3495
+ net_with_connected_ports = list(set(project1_nets).intersection(project2_nets))
3496
+ if net_with_connected_ports:
3497
+ for net_name in net_with_connected_ports:
3498
+ project1_port_info = [term_info for term_info in terminal_set1 if term_info[0] == net_name]
3499
+ project2_port_info = [term_info for term_info in terminal_set2 if term_info[0] == net_name]
3500
+ port_list = [p[3] for p in project1_port_info] + [p[3] for p in project2_port_info]
3501
+ port_combinations = list(combinations(port_list, 2))
3502
+ for port_combination in port_combinations:
3503
+ if not port_combination[0] == port_combination[1]:
3504
+ port1 = [port for port in terminal_set1 if port[3] == port_combination[0]]
3505
+ if not port1:
3506
+ port1 = [port for port in terminal_set2 if port[3] == port_combination[0]]
3507
+ port2 = [port for port in terminal_set2 if port[3] == port_combination[1]]
3508
+ if not port2:
3509
+ port2 = [port for port in terminal_set1 if port[3] == port_combination[1]]
3510
+ port1 = port1[0]
3511
+ port2 = port2[0]
3512
+ if not port1[3] == port2[3]:
3513
+ port_distance = GeometryOperators.points_distance(port1[1:3], port2[1:3])
3514
+ if port_distance < tolerance:
3515
+ port1_connexion = None
3516
+ port2_connexion = None
3517
+ for (
3518
+ project_path,
3519
+ port_info,
3520
+ ) in terminal_info_dict.items():
3521
+ port1_map = [port for port in port_info if port[3] == port1[3]]
3522
+ if port1_map:
3523
+ port1_connexion = (
3524
+ project_path,
3525
+ port1[3],
3526
+ )
3527
+ port2_map = [port for port in port_info if port[3] == port2[3]]
3528
+ if port2_map:
3529
+ port2_connexion = (
3530
+ project_path,
3531
+ port2[3],
3532
+ )
3533
+ if port1_connexion and port2_connexion:
3534
+ if (
3535
+ not port1_connexion[0] == port2_connexion[0]
3536
+ or not port1_connexion[1] == port2_connexion[1]
3537
+ ):
3538
+ connected_ports_list.append((port1_connexion, port2_connexion))
3539
+ return connected_ports_list
3540
+
3541
+ def create_port(self, terminal, ref_terminal=None, is_circuit_port=False, name=None):
3542
+ """Create a port.
3543
+
3544
+ Parameters
3545
+ ----------
3546
+ terminal : class:`pyedb.dotnet.database.edb_data.terminals.EdgeTerminal`,
3547
+ class:`pyedb.grpc.database.terminals.PadstackInstanceTerminal`,
3548
+ class:`pyedb.grpc.database.terminals.PointTerminal`,
3549
+ class:`pyedb.grpc.database.terminals.PinGroupTerminal`,
3550
+ Positive terminal of the port.
3551
+ ref_terminal : class:`pyedb.grpc.database.terminals.EdgeTerminal`,
3552
+ class:`pyedb.grpc.database.terminals.PadstackInstanceTerminal`,
3553
+ class:`pyedb.grpc.database.terminals.PointTerminal`,
3554
+ class:`pyedb.grpc.database.terminals.PinGroupTerminal`,
3555
+ optional
3556
+ Negative terminal of the port.
3557
+ is_circuit_port : bool, optional
3558
+ Whether it is a circuit port. The default is ``False``.
3559
+ name: str, optional
3560
+ Name of the created port. The default is None, a random name is generated.
3561
+ Returns
3562
+ -------
3563
+ list: [:class:`GapPort <pyedb.grpc.database.ports.ports.GapPort`>,
3564
+ :class:`WavePort <pyedb.grpc.database.ports.ports.WavePort>`].
3565
+ """
3566
+ from ansys.edb.core.terminal.terminals import BoundaryType as GrpcBoundaryType
3567
+
3568
+ if isinstance(terminal.boundary_type, GrpcBoundaryType):
3569
+ terminal.boundary_type = GrpcBoundaryType.PORT
3570
+ terminal.is_circuit_port = is_circuit_port
3571
+
3572
+ if isinstance(ref_terminal.boundary_type, GrpcBoundaryType):
3573
+ ref_terminal.boundary_type = GrpcBoundaryType.PORT
3574
+ terminal.ref_terminal = ref_terminal
3575
+ if name:
3576
+ terminal.name = name
3577
+ return self.ports[terminal.name]
3578
+
3579
+ def create_voltage_probe(self, terminal, ref_terminal):
3580
+ """Create a voltage probe.
3581
+
3582
+ Parameters
3583
+ ----------
3584
+ terminal : :class:`EdgeTerminal <pyedb.grpc.database.terminals.EdgeTerminal>`,
3585
+ :class:`PadstackInstanceTerminal <pyedb.grpc.database.terminals.PadstackInstanceTerminal>`,
3586
+ :class:`PointTerminal <pyedb.grpc.database.terminals.PointTerminal>`,
3587
+ :class:`PinGroupTerminal <pyedb.grpc.database.terminals.PinGroupTerminal>`,
3588
+ Positive terminal of the port.
3589
+ ref_terminal : :class:`EdgeTerminal <pyedb.grpc.database.terminals.EdgeTerminal>`,
3590
+ :class:`pyedb.grpc.database.terminals.PadstackInstanceTerminal`,
3591
+ :class:`PadstackInstanceTerminal <pyedb.grpc.database.terminals.PointTerminal>`,
3592
+ :class:`PinGroupTerminal <pyedb.grpc.database.terminals.PinGroupTerminal>`,
3593
+ Negative terminal of the probe.
3594
+
3595
+ Returns
3596
+ -------
3597
+ :class:`Terminal <pyedb.dotnet.database.edb_data.terminals.Terminal>`
3598
+ """
3599
+ term = Terminal(self, terminal)
3600
+ term.boundary_type = "voltage_probe"
3601
+
3602
+ ref_term = Terminal(self, ref_terminal)
3603
+ ref_term.boundary_type = "voltage_probe"
3604
+
3605
+ term.ref_terminal = ref_terminal
3606
+ return term
3607
+
3608
+ def create_voltage_source(self, terminal, ref_terminal):
3609
+ """Create a voltage source.
3610
+
3611
+ Parameters
3612
+ ----------
3613
+ terminal : :class:`EdgeTerminal <pyedb.grpc.database.terminals.EdgeTerminal>`,
3614
+ :class:`PadstackInstanceTerminal <pyedb.grpc.database.terminals.PadstackInstanceTerminal>`,
3615
+ :class:`PointTerminal <pyedb.grpc.database.terminals.PointTerminal>`,
3616
+ :class:`PinGroupTerminal <pyedb.grpc.database.terminals.PinGroupTerminal>`,
3617
+ Positive terminal of the source.
3618
+ ref_terminal : :class:`EdgeTerminal <pyedb.grpc.database.terminals.EdgeTerminal>`,
3619
+ :class:`pyedb.grpc.database.terminals.PadstackInstanceTerminal`,
3620
+ :class:`PadstackInstanceTerminal <pyedb.grpc.database.terminals.PointTerminal>`,
3621
+ :class:`PinGroupTerminal <pyedb.grpc.database.terminals.PinGroupTerminal>`,
3622
+ Negative terminal of the source.
3623
+
3624
+ Returns
3625
+ -------
3626
+ class:`ExcitationSources <legacy.database.edb_data.ports.ExcitationSources>`
3627
+ """
3628
+ term = Terminal(self, terminal)
3629
+ term.boundary_type = "voltage_source"
3630
+
3631
+ ref_term = Terminal(self, ref_terminal)
3632
+ ref_term.boundary_type = "voltage_source"
3633
+
3634
+ term.ref_terminal = ref_terminal
3635
+ return term
3636
+
3637
+ def create_current_source(self, terminal, ref_terminal):
3638
+ """Create a current source.
3639
+
3640
+ Parameters
3641
+ ----------
3642
+ terminal : :class:`EdgeTerminal <pyedb.grpc.database.terminals.EdgeTerminal>`,
3643
+ :class:`PadstackInstanceTerminal <pyedb.grpc.database.terminals.PadstackInstanceTerminal>`,
3644
+ :class:`PointTerminal <pyedb.grpc.database.terminals.PointTerminal>`,
3645
+ :class:`PinGroupTerminal <pyedb.grpc.database.terminals.PinGroupTerminal>`,
3646
+ Positive terminal of the source.
3647
+ ref_terminal : :class:`EdgeTerminal <pyedb.grpc.database.terminals.EdgeTerminal>`,
3648
+ :class:`pyedb.grpc.database.terminals.PadstackInstanceTerminal`,
3649
+ :class:`PadstackInstanceTerminal <pyedb.grpc.database.terminals.PointTerminal>`,
3650
+ :class:`PinGroupTerminal <pyedb.grpc.database.terminals.PinGroupTerminal>`,
3651
+ Negative terminal of the source.
3652
+
3653
+ Returns
3654
+ -------
3655
+ :class:`ExcitationSources <legacy.database.edb_data.ports.ExcitationSources>`
3656
+ """
3657
+ term = Terminal(self, terminal)
3658
+ term.boundary_type = "current_source"
3659
+
3660
+ ref_term = Terminal(self, ref_terminal)
3661
+ ref_term.boundary_type = "current_source"
3662
+
3663
+ term.ref_terminal = ref_terminal
3664
+ return term
3665
+
3666
+ def get_point_terminal(self, name, net_name, location, layer):
3667
+ """Place a voltage probe between two points.
3668
+
3669
+ Parameters
3670
+ ----------
3671
+ name : str,
3672
+ Name of the terminal.
3673
+ net_name : str
3674
+ Name of the net.
3675
+ location : list
3676
+ Location of the terminal.
3677
+ layer : str,
3678
+ Layer of the terminal.
3679
+
3680
+ Returns
3681
+ -------
3682
+ :class:`PointTerminal <pyedb.grpc.database.terminal.point_terminal.PointTerminal>`
3683
+ """
3684
+ from pyedb.grpc.database.terminal.point_terminal import PointTerminal
3685
+
3686
+ return PointTerminal.create(layout=self.active_layout, name=name, net=net_name, layer=layer, point=location)
3687
+
3688
+ def auto_parametrize_design(
3689
+ self,
3690
+ layers=True,
3691
+ materials=True,
3692
+ via_holes=True,
3693
+ pads=True,
3694
+ antipads=True,
3695
+ traces=True,
3696
+ layer_filter=None,
3697
+ material_filter=None,
3698
+ padstack_definition_filter=None,
3699
+ trace_net_filter=None,
3700
+ use_single_variable_for_padstack_definitions=True,
3701
+ use_relative_variables=True,
3702
+ output_aedb_path=None,
3703
+ open_aedb_at_end=True,
3704
+ expand_polygons_size=0,
3705
+ expand_voids_size=0,
3706
+ via_offset=True,
3707
+ ):
3708
+ """Assign automatically design and project variables with current values.
3709
+
3710
+ Parameters
3711
+ ----------
3712
+ layers : bool, optional
3713
+ Enable layer thickness parametrization. Default value is ``True``.
3714
+ materials : bool, optional
3715
+ Enable material parametrization. Default value is ``True``.
3716
+ via_holes : bool, optional
3717
+ Enable via diameter parametrization. Default value is ``True``.
3718
+ pads : bool, optional
3719
+ Enable pads size parametrization. Default value is ``True``.
3720
+ antipads : bool, optional
3721
+ Enable anti pads size parametrization. Default value is ``True``.
3722
+ traces : bool, optional
3723
+ Enable trace width parametrization. Default value is ``True``.
3724
+ layer_filter : str, List(str), optional
3725
+ Enable layer filter. Default value is ``None``, all layers are parametrized.
3726
+ material_filter : str, List(str), optional
3727
+ Enable material filter. Default value is ``None``, all material are parametrized.
3728
+ padstack_definition_filter : str, List(str), optional
3729
+ Enable padstack definition filter. Default value is ``None``, all padsatcks are parametrized.
3730
+ trace_net_filter : str, List(str), optional
3731
+ Enable nets filter for trace width parametrization. Default value is ``None``, all layers are parametrized.
3732
+ use_single_variable_for_padstack_definitions : bool, optional
3733
+ Whether to use a single design variable for each padstack definition or a variable per pad layer.
3734
+ Default value is ``True``.
3735
+ use_relative_variables : bool, optional
3736
+ Whether if use an absolute variable for each trace, padstacks and layers or a delta variable instead.
3737
+ Default value is ``True``.
3738
+ output_aedb_path : str, optional
3739
+ Full path and name for the new AEDB file. If None, then current aedb will be cutout.
3740
+ open_aedb_at_end : bool, optional
3741
+ Whether to open the cutout at the end. The default is ``True``.
3742
+ expand_polygons_size : float, optional
3743
+ Expansion size on polygons. Polygons will be expanded in all directions. The default is ``0``.
3744
+ expand_voids_size : float, optional
3745
+ Expansion size on polygon voids. Polygons voids will be expanded in all directions. The default is ``0``.
3746
+ via_offset : bool, optional
3747
+ Whether if offset the via position or not. The default is ``True``.
3748
+
3749
+ Returns
3750
+ -------
3751
+ List(str)
3752
+ List of all parameter name created.
3753
+ """
3754
+ edb_original_path = self.edbpath
3755
+ if output_aedb_path:
3756
+ self.save_edb_as(output_aedb_path)
3757
+ if isinstance(trace_net_filter, str):
3758
+ trace_net_filter = [trace_net_filter]
3759
+ parameters = []
3760
+
3761
+ def _apply_variable(orig_name, orig_value):
3762
+ if use_relative_variables:
3763
+ var = f"{orig_name}_delta"
3764
+ else:
3765
+ var = f"{orig_name}_value"
3766
+ var = self._clean_string_for_variable_name(var)
3767
+ if var not in self.variables:
3768
+ if use_relative_variables:
3769
+ if var.startswith("$"):
3770
+ self.add_project_variable(var, 0.0)
3771
+ else:
3772
+ self.add_design_variable(var, 0.0)
3773
+ else:
3774
+ if var.startswith("$"):
3775
+ self.add_project_variable(var, orig_value)
3776
+ else:
3777
+ self.add_design_variable(var, orig_value)
3778
+ if use_relative_variables:
3779
+ return f"{orig_value}+{var}", var
3780
+ else:
3781
+ return var, var
3782
+
3783
+ if layers:
3784
+ if not layer_filter:
3785
+ _layers = self.stackup.layers
3786
+ else:
3787
+ if isinstance(layer_filter, str):
3788
+ layer_filter = [layer_filter]
3789
+ _layers = {k: v for k, v in self.stackup.layers.items() if k in layer_filter}
3790
+ for layer_name, layer in _layers.items():
3791
+ var, val = _apply_variable(f"${layer_name}", layer.thickness)
3792
+ layer.thickness = GrpcValue(var, self.active_db)
3793
+ parameters.append(val)
3794
+ if materials:
3795
+ if not material_filter:
3796
+ _materials = self.materials.materials
3797
+ else:
3798
+ _materials = {k: v for k, v in self.materials.materials.items() if k in material_filter}
3799
+ for mat_name, material in _materials.items():
3800
+ if not material.conductivity or material.conductivity < 1e4:
3801
+ var, val = _apply_variable(f"$epsr_{mat_name}", material.permittivity)
3802
+ material.permittivity = GrpcValue(var, self.active_db)
3803
+ parameters.append(val)
3804
+ var, val = _apply_variable(f"$loss_tangent_{mat_name}", material.dielectric_loss_tangent)
3805
+ material.dielectric_loss_tangent = GrpcValue(var, self.active_db)
3806
+ parameters.append(val)
3807
+ else:
3808
+ var, val = _apply_variable(f"$sigma_{mat_name}", material.conductivity)
3809
+ material.conductivity = GrpcValue(var, self.active_db)
3810
+ parameters.append(val)
3811
+ if traces:
3812
+ if not trace_net_filter:
3813
+ paths = self.modeler.paths
3814
+ else:
3815
+ paths = [path for path in self.modeler.paths if path.net_name in trace_net_filter]
3816
+ for path in paths:
3817
+ net_name = path.net_name
3818
+ if use_relative_variables:
3819
+ trace_width_variable = "trace"
3820
+ elif net_name:
3821
+ trace_width_variable = f"{path.net_name}_{path.aedt_name}"
3822
+ else:
3823
+ trace_width_variable = f"{path.aedt_name}"
3824
+ var, val = _apply_variable(trace_width_variable, path.width)
3825
+ path.width = GrpcValue(var, self.active_cell)
3826
+ parameters.append(val)
3827
+ if not padstack_definition_filter:
3828
+ if trace_net_filter:
3829
+ padstack_defs = {}
3830
+ for net in trace_net_filter:
3831
+ for via in self.nets[net].padstack_instances:
3832
+ padstack_defs[via.padstack_definition] = self.padstacks.definitions[via.padstack_definition]
3833
+ else:
3834
+ used_padsatck_defs = list(
3835
+ set(
3836
+ [padstack_inst.padstack_definition for padstack_inst in list(self.padstacks.instances.values())]
3837
+ )
3838
+ )
3839
+ padstack_defs = {k: v for k, v in self.padstacks.definitions.items() if k in used_padsatck_defs}
3840
+ else:
3841
+ padstack_defs = {k: v for k, v in self.padstacks.definitions.items() if k in padstack_definition_filter}
3842
+
3843
+ for def_name, padstack_def in padstack_defs.items():
3844
+ if not padstack_def.start_layer == padstack_def.stop_layer:
3845
+ if via_holes: # pragma no cover
3846
+ if use_relative_variables:
3847
+ hole_variable = "$hole_diameter"
3848
+ else:
3849
+ hole_variable = f"${def_name}_hole_diameter"
3850
+ if padstack_def.hole_diameter:
3851
+ var, val = _apply_variable(hole_variable, padstack_def.hole_diameter)
3852
+ padstack_def.hole_properties = GrpcValue(var, self.active_db)
3853
+ parameters.append(val)
3854
+ if pads:
3855
+ for layer, pad in padstack_def.pad_by_layer.items():
3856
+ if use_relative_variables:
3857
+ pad_name = "$pad"
3858
+ elif use_single_variable_for_padstack_definitions:
3859
+ pad_name = f"${def_name}_pad"
3860
+ else:
3861
+ pad_name = f"${def_name}_{layer}_pad"
3862
+
3863
+ if pad.geometry_type in [1, 2]:
3864
+ var, val = _apply_variable(pad_name, pad.parameters_values_string[0])
3865
+ if pad.geometry_type == 1:
3866
+ pad.parameters = {"Diameter": var}
3867
+ else:
3868
+ pad.parameters = {"Size": var}
3869
+ parameters.append(val)
3870
+ elif pad.geometry_type == 3: # pragma no cover
3871
+ if use_relative_variables:
3872
+ pad_name_x = "$pad_x"
3873
+ pad_name_y = "$pad_y"
3874
+ elif use_single_variable_for_padstack_definitions:
3875
+ pad_name_x = f"${def_name}_pad_x"
3876
+ pad_name_y = f"${def_name}_pad_y"
3877
+ else:
3878
+ pad_name_x = f"${def_name}_{layer}_pad_x"
3879
+ pad_name_y = f"${def_name}_pad_y"
3880
+ var, val = _apply_variable(pad_name_x, pad.parameters_values_string[0])
3881
+ var2, val2 = _apply_variable(pad_name_y, pad.parameters_values_string[1])
3882
+
3883
+ pad.parameters = {"XSize": var, "YSize": var2}
3884
+ parameters.append(val)
3885
+ parameters.append(val2)
3886
+ if antipads:
3887
+ for layer, antipad in padstack_def.antipad_by_layer.items():
3888
+ if antipad:
3889
+ if use_relative_variables:
3890
+ pad_name = "$antipad"
3891
+ elif use_single_variable_for_padstack_definitions:
3892
+ pad_name = f"${def_name}_antipad"
3893
+ else:
3894
+ pad_name = f"${def_name}_{layer}_antipad"
3895
+
3896
+ if antipad.geometry_type in [1, 2]:
3897
+ var, val = _apply_variable(pad_name, antipad.parameters_values_string[0])
3898
+ if antipad.geometry_type == 1: # pragma no cover
3899
+ antipad.parameters = {"Diameter": var}
3900
+ else:
3901
+ antipad.parameters = {"Size": var}
3902
+ parameters.append(val)
3903
+ elif antipad.geometry_type == 3: # pragma no cover
3904
+ if use_relative_variables:
3905
+ pad_name_x = "$antipad_x"
3906
+ pad_name_y = "$antipad_y"
3907
+ elif use_single_variable_for_padstack_definitions:
3908
+ pad_name_x = f"${def_name}_antipad_x"
3909
+ pad_name_y = f"${def_name}_antipad_y"
3910
+ else:
3911
+ pad_name_x = f"${def_name}_{layer}_antipad_x"
3912
+ pad_name_y = f"${def_name}_antipad_y"
3913
+
3914
+ var, val = _apply_variable(pad_name_x, antipad.parameters_values_string[0])
3915
+ var2, val2 = _apply_variable(pad_name_y, antipad.parameters_values_string[1])
3916
+ antipad.parameters = {"XSize": var, "YSize": var2}
3917
+ parameters.append(val)
3918
+ parameters.append(val2)
3919
+
3920
+ if via_offset:
3921
+ var_x = "via_offset_x"
3922
+ if var_x not in self.variables:
3923
+ self.add_design_variable(var_x, 0.0)
3924
+ var_y = "via_offset_y"
3925
+ if var_y not in self.variables:
3926
+ self.add_design_variable(var_y, 0.0)
3927
+ for via in self.padstacks.instances.values():
3928
+ if not via.is_pin and (not trace_net_filter or (trace_net_filter and via.net_name in trace_net_filter)):
3929
+ via.position = [f"{via.position[0]}+via_offset_x", f"{via.position[1]}+via_offset_y"]
3930
+
3931
+ if expand_polygons_size:
3932
+ for poly in self.modeler.polygons:
3933
+ if not poly.is_void:
3934
+ poly.expand(expand_polygons_size)
3935
+ if expand_voids_size:
3936
+ for poly in self.modeler.polygons:
3937
+ if poly.is_void:
3938
+ poly.expand(expand_voids_size, round_corners=False)
3939
+ elif poly.has_voids:
3940
+ for void in poly.voids:
3941
+ void.expand(expand_voids_size, round_corners=False)
3942
+
3943
+ if not open_aedb_at_end and self.edbpath != edb_original_path:
3944
+ self.save_edb()
3945
+ self.close_edb()
3946
+ self.edbpath = edb_original_path
3947
+ self.open_edb()
3948
+ return parameters
3949
+
3950
+ @staticmethod
3951
+ def _clean_string_for_variable_name(variable_name):
3952
+ """Remove forbidden character for variable name.
3953
+ Parameters
3954
+ ----------
3955
+ variable_name : str
3956
+ Variable name.
3957
+ Returns
3958
+ -------
3959
+ str
3960
+ Edited name.
3961
+ """
3962
+ if "-" in variable_name:
3963
+ variable_name = variable_name.replace("-", "_")
3964
+ if "+" in variable_name:
3965
+ variable_name = variable_name.replace("+", "p")
3966
+ variable_name = re.sub(r"[() ]", "_", variable_name)
3967
+
3968
+ return variable_name
3969
+
3970
+ def create_model_for_arbitrary_wave_ports(
3971
+ self,
3972
+ temp_directory,
3973
+ mounting_side="top",
3974
+ signal_nets=None,
3975
+ terminal_diameter=None,
3976
+ output_edb=None,
3977
+ launching_box_thickness="100um",
3978
+ ):
3979
+ """Generate EDB design to be consumed by PyAEDT to generate arbitrary wave ports shapes.
3980
+ This model has to be considered as merged onto another one. The current opened design must have voids
3981
+ surrounding the pad-stacks where wave ports terminal will be created. THe open design won't be edited, only
3982
+ primitives like voids and pads-stack definition included in the voids are collected to generate a new design.
3983
+
3984
+ Parameters
3985
+ ----------
3986
+ temp_directory : str
3987
+ Temporary directory used during the method execution.
3988
+
3989
+ mounting_side : str
3990
+ Gives the orientation to be considered for the current design. 2 options are available ``"top"`` and
3991
+ ``"bottom". Default value is ``"top"``. If ``"top"`` is selected the method will voids at the top signal
3992
+ layer, and the bottom layer if ``"bottom"`` is used.
3993
+
3994
+ signal_nets : List[str], optional
3995
+ Provides the nets to be included for the model creation. Default value is ``None``. If None is provided,
3996
+ all nets will be included.
3997
+
3998
+ terminal_diameter : float, str, optional
3999
+ When ``None``, the terminal diameter is evaluated at each pads-tack instance found inside the voids. The top
4000
+ or bottom layer pad diameter will be taken, depending on ``mounting_side`` selected. If value is provided,
4001
+ it will overwrite the evaluated diameter.
4002
+
4003
+ output_edb : str, optional
4004
+ The output EDB absolute. If ``None`` the edb is created in the ``temp_directory`` as default name
4005
+ `"waveport_model.aedb"``
4006
+
4007
+ launching_box_thickness : float, str, optional
4008
+ Launching box thickness used for wave ports. Default value is ``"100um"``.
4009
+
4010
+ Returns
4011
+ -------
4012
+ bool
4013
+ ``True`` when succeeded, ``False`` if failed.
4014
+ """
4015
+ if not temp_directory:
4016
+ self.logger.error("Temp directory must be provided when creating model foe arbitrary wave port")
4017
+ return False
4018
+ if mounting_side not in ["top", "bottom"]:
4019
+ self.logger.error(
4020
+ "Mounting side must be provided and only `top` or `bottom` are supported. Setting to "
4021
+ "`top` will take the top layer from the current design as reference. Setting to `bottom` "
4022
+ "will take the bottom one."
4023
+ )
4024
+ if not output_edb:
4025
+ output_edb = os.path.join(temp_directory, "waveport_model.aedb")
4026
+ else:
4027
+ output_edb = os.path.join(temp_directory, output_edb)
4028
+ if os.path.isdir(temp_directory):
4029
+ shutil.rmtree(temp_directory)
4030
+ os.mkdir(temp_directory)
4031
+ reference_layer = list(self.stackup.signal_layers.keys())[0]
4032
+ if mounting_side.lower() == "bottom":
4033
+ reference_layer = list(self.stackup.signal_layers.keys())[-1]
4034
+ if not signal_nets:
4035
+ signal_nets = list(self.nets.signal.keys())
4036
+
4037
+ used_padstack_defs = []
4038
+ padstack_instances_index = rtree.index.Index()
4039
+ for padstack_inst in list(self.padstacks.instances.values()):
4040
+ if not reference_layer in [padstack_inst.start_layer, padstack_inst.stop_layer]:
4041
+ padstack_inst.delete()
4042
+ else:
4043
+ if padstack_inst.net.name in signal_nets:
4044
+ padstack_instances_index.insert(padstack_inst.edb_uid, padstack_inst.position)
4045
+ if not padstack_inst.padstack_def.name in used_padstack_defs:
4046
+ used_padstack_defs.append(padstack_inst.padstack_def.name)
4047
+
4048
+ polys = [
4049
+ poly
4050
+ for poly in self.modeler.primitives
4051
+ if poly.layer.name == reference_layer and self.modeler.primitives[0].type == "polygon" and poly.has_voids
4052
+ ]
4053
+ if not polys:
4054
+ self.logger.error(
4055
+ f"No polygon found with voids on layer {reference_layer} during model creation for "
4056
+ f"arbitrary wave ports"
4057
+ )
4058
+ return False
4059
+ void_padstacks = []
4060
+ for poly in polys:
4061
+ for void in poly.voids:
4062
+ void_bbox = void.bbox
4063
+ included_instances = list(padstack_instances_index.intersection(void_bbox))
4064
+ if included_instances:
4065
+ void_padstacks.append((void, [self.padstacks.instances[edb_uid] for edb_uid in included_instances]))
4066
+
4067
+ if not void_padstacks:
4068
+ self.logger.error(
4069
+ "No padstack instances found inside evaluated voids during model creation for arbitrary" "waveports"
4070
+ )
4071
+ return False
4072
+ cloned_edb = Edb(edbpath=output_edb, edbversion=self.edbversion, restart_rpc_server=True)
4073
+
4074
+ cloned_edb.stackup.add_layer(
4075
+ layer_name="ports",
4076
+ layer_type="signal",
4077
+ thickness=self.stackup.signal_layers[reference_layer].thickness,
4078
+ material="pec",
4079
+ )
4080
+ if launching_box_thickness:
4081
+ launching_box_thickness = str(GrpcValue(launching_box_thickness))
4082
+ cloned_edb.stackup.add_layer(
4083
+ layer_name="ref",
4084
+ layer_type="signal",
4085
+ thickness=0.0,
4086
+ material="pec",
4087
+ method=f"add_on_{mounting_side}",
4088
+ base_layer="ports",
4089
+ )
4090
+ cloned_edb.stackup.add_layer(
4091
+ layer_name="port_pec",
4092
+ layer_type="signal",
4093
+ thickness=launching_box_thickness,
4094
+ method=f"add_on_{mounting_side}",
4095
+ material="pec",
4096
+ base_layer="ports",
4097
+ )
4098
+ for void_info in void_padstacks:
4099
+ port_poly = cloned_edb.modeler.create_polygon(
4100
+ points=void_info[0].cast().polygon_data, layer_name="ref", net_name="GND"
4101
+ )
4102
+ pec_poly = cloned_edb.modeler.create_polygon(
4103
+ points=port_poly.cast().polygon_data, layer_name="port_pec", net_name="GND"
4104
+ )
4105
+ pec_poly.scale(1.5)
4106
+
4107
+ for void_info in void_padstacks:
4108
+ for inst in void_info[1]:
4109
+ if not terminal_diameter:
4110
+ pad_diameter = (
4111
+ self.padstacks.definitions[inst.padstack_def.name]
4112
+ .pad_by_layer[reference_layer]
4113
+ .parameters_values
4114
+ )
4115
+ else:
4116
+ pad_diameter = GrpcValue(terminal_diameter).value
4117
+ _temp_circle = cloned_edb.modeler.create_circle(
4118
+ layer_name="ports",
4119
+ x=inst.position[0],
4120
+ y=inst.position[1],
4121
+ radius=pad_diameter[0] / 2,
4122
+ net_name=inst.net_name,
4123
+ )
4124
+ if not _temp_circle:
4125
+ self.logger.error(
4126
+ f"Failed to create circle for terminal during create_model_for_arbitrary_wave_ports"
4127
+ )
4128
+ cloned_edb.save_as(output_edb)
4129
+ cloned_edb.close(terminate_rpc_session=False)
4130
+ return True
4131
+
4132
+ @property
4133
+ def definitions(self):
4134
+ """Returns Definitions class.
4135
+
4136
+ Returns
4137
+ -------
4138
+ :class:`Definitions <pyedb.grpc.database.definitions.Definitions>`
4139
+ """
4140
+ from pyedb.grpc.database.definitions import Definitions
4141
+
4142
+ return Definitions(self)
4143
+
4144
+ @property
4145
+ def workflow(self):
4146
+ """Returns workflow class.
4147
+
4148
+ Returns
4149
+ ------
4150
+ :class:`Workflow <pyedb.workflow.Workflow>`
4151
+ """
4152
+ return Workflow(self)