pyedb 0.37.0__py3-none-any.whl → 0.39.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyedb might be problematic. Click here for more details.

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