pyedb 0.38.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.
- pyedb/__init__.py +1 -1
- pyedb/common/nets.py +53 -139
- pyedb/configuration/cfg_components.py +1 -1
- pyedb/configuration/cfg_general.py +4 -2
- pyedb/configuration/cfg_modeler.py +1 -1
- pyedb/configuration/cfg_package_definition.py +1 -1
- pyedb/configuration/cfg_padstacks.py +1 -1
- pyedb/configuration/cfg_ports_sources.py +56 -23
- pyedb/configuration/configuration.py +18 -1
- pyedb/dotnet/{application → database}/Variables.py +21 -21
- pyedb/dotnet/{edb_core → database}/cell/connectable.py +5 -5
- pyedb/dotnet/{edb_core → database}/cell/hierarchy/component.py +11 -11
- pyedb/dotnet/{edb_core → database}/cell/hierarchy/hierarchy_obj.py +1 -1
- pyedb/dotnet/{edb_core → database}/cell/hierarchy/model.py +1 -1
- pyedb/dotnet/{edb_core → database}/cell/layout.py +17 -17
- pyedb/dotnet/{edb_core → database}/cell/layout_obj.py +3 -3
- pyedb/dotnet/{edb_core → database}/cell/primitive/bondwire.py +1 -1
- pyedb/dotnet/{edb_core → database}/cell/primitive/path.py +4 -4
- pyedb/dotnet/{edb_core → database}/cell/primitive/primitive.py +14 -14
- pyedb/dotnet/{edb_core → database}/cell/terminal/bundle_terminal.py +2 -2
- pyedb/dotnet/{edb_core → database}/cell/terminal/edge_terminal.py +4 -4
- pyedb/dotnet/{edb_core → database}/cell/terminal/padstack_instance_terminal.py +2 -2
- pyedb/dotnet/{edb_core → database}/cell/terminal/pingroup_terminal.py +2 -2
- pyedb/dotnet/{edb_core → database}/cell/terminal/point_terminal.py +2 -2
- pyedb/dotnet/{edb_core → database}/cell/terminal/terminal.py +11 -11
- pyedb/dotnet/{edb_core → database}/cell/voltage_regulator.py +2 -2
- pyedb/dotnet/{edb_core → database}/components.py +101 -124
- pyedb/dotnet/{edb_core → database}/definition/component_def.py +5 -5
- pyedb/dotnet/{edb_core → database}/definition/component_model.py +1 -1
- pyedb/dotnet/{edb_core → database}/definition/definition_obj.py +1 -1
- pyedb/dotnet/{edb_core → database}/definition/definitions.py +2 -2
- pyedb/dotnet/{edb_core → database}/definition/package_def.py +4 -4
- pyedb/dotnet/{edb_core → database}/dotnet/database.py +8 -8
- pyedb/dotnet/{edb_core → database}/dotnet/primitive.py +9 -9
- pyedb/dotnet/{edb_core → database}/edb_data/control_file.py +12 -12
- pyedb/dotnet/{edb_core → database}/edb_data/hfss_extent_info.py +7 -7
- pyedb/dotnet/{edb_core → database}/edb_data/nets_data.py +10 -13
- pyedb/dotnet/{edb_core → database}/edb_data/padstacks_data.py +16 -16
- pyedb/dotnet/{edb_core → database}/edb_data/ports.py +4 -4
- pyedb/dotnet/{edb_core → database}/edb_data/primitives_data.py +5 -5
- pyedb/dotnet/{edb_core → database}/edb_data/raptor_x_simulation_setup_data.py +4 -4
- pyedb/dotnet/{edb_core → database}/edb_data/simulation_configuration.py +10 -10
- pyedb/dotnet/{edb_core → database}/edb_data/sources.py +4 -4
- pyedb/dotnet/{edb_core → database}/edb_data/variables.py +1 -1
- pyedb/dotnet/{edb_core → database}/geometry/polygon_data.py +4 -4
- pyedb/dotnet/{edb_core → database}/hfss.py +8 -8
- pyedb/dotnet/{edb_core → database}/layout_obj_instance.py +1 -1
- pyedb/dotnet/{edb_core → database}/layout_validation.py +2 -2
- pyedb/dotnet/{edb_core → database}/materials.py +23 -8
- pyedb/dotnet/{edb_core → database}/modeler.py +27 -27
- pyedb/dotnet/{edb_core → database}/net_class.py +8 -8
- pyedb/dotnet/{edb_core → database}/nets.py +12 -12
- pyedb/dotnet/{edb_core → database}/padstack.py +15 -15
- pyedb/dotnet/{edb_core → database}/sim_setup_data/data/mesh_operation.py +1 -1
- pyedb/dotnet/{edb_core → database}/sim_setup_data/data/settings.py +3 -3
- pyedb/dotnet/{edb_core → database}/sim_setup_data/data/sim_setup_info.py +2 -2
- pyedb/dotnet/{edb_core → database}/sim_setup_data/data/simulation_settings.py +1 -1
- pyedb/dotnet/{edb_core → database}/sim_setup_data/data/siw_dc_ir_settings.py +1 -1
- pyedb/dotnet/{edb_core → database}/sim_setup_data/data/sweep_data.py +1 -1
- pyedb/dotnet/{edb_core → database}/siwave.py +10 -10
- pyedb/dotnet/{edb_core → database}/stackup.py +12 -12
- pyedb/dotnet/{edb_core → database}/utilities/hfss_simulation_setup.py +15 -15
- pyedb/dotnet/{edb_core → database}/utilities/obj_base.py +1 -1
- pyedb/dotnet/{edb_core → database}/utilities/simulation_setup.py +3 -3
- pyedb/dotnet/{edb_core → database}/utilities/siwave_simulation_setup.py +6 -6
- pyedb/dotnet/edb.py +117 -112
- pyedb/generic/design_types.py +26 -19
- pyedb/generic/general_methods.py +1 -1
- pyedb/generic/plot.py +0 -2
- pyedb/grpc/database/__init__.py +1 -0
- pyedb/grpc/database/components.py +2354 -0
- pyedb/grpc/database/control_file.py +1277 -0
- pyedb/grpc/database/definition/component_def.py +218 -0
- pyedb/grpc/database/definition/component_model.py +39 -0
- pyedb/grpc/database/definition/component_pin.py +32 -0
- pyedb/grpc/database/definition/materials.py +1207 -0
- pyedb/grpc/database/definition/n_port_component_model.py +34 -0
- pyedb/grpc/database/definition/package_def.py +227 -0
- pyedb/grpc/database/definition/padstack_def.py +842 -0
- pyedb/grpc/database/definitions.py +70 -0
- pyedb/grpc/database/general.py +43 -0
- pyedb/grpc/database/geometry/__init__.py +0 -0
- pyedb/grpc/database/geometry/arc_data.py +93 -0
- pyedb/grpc/database/geometry/point_3d_data.py +79 -0
- pyedb/grpc/database/geometry/point_data.py +30 -0
- pyedb/grpc/database/geometry/polygon_data.py +133 -0
- pyedb/grpc/database/hfss.py +1279 -0
- pyedb/grpc/database/hierarchy/__init__.py +0 -0
- pyedb/grpc/database/hierarchy/component.py +1301 -0
- pyedb/grpc/database/hierarchy/model.py +31 -0
- pyedb/grpc/database/hierarchy/netlist_model.py +30 -0
- pyedb/grpc/database/hierarchy/pin_pair_model.py +128 -0
- pyedb/grpc/database/hierarchy/pingroup.py +245 -0
- pyedb/grpc/database/hierarchy/s_parameter_model.py +33 -0
- pyedb/grpc/database/hierarchy/spice_model.py +48 -0
- pyedb/grpc/database/layers/__init__.py +0 -0
- pyedb/grpc/database/layers/layer.py +57 -0
- pyedb/grpc/database/layers/stackup_layer.py +410 -0
- pyedb/grpc/database/layout/__init__.py +0 -0
- pyedb/grpc/database/layout/cell.py +30 -0
- pyedb/grpc/database/layout/layout.py +196 -0
- pyedb/grpc/database/layout/voltage_regulator.py +149 -0
- pyedb/grpc/database/layout_validation.py +319 -0
- pyedb/grpc/database/modeler.py +1468 -0
- pyedb/grpc/database/net/__init__.py +0 -0
- pyedb/grpc/database/net/differential_pair.py +138 -0
- pyedb/grpc/database/net/extended_net.py +340 -0
- pyedb/grpc/database/net/net.py +198 -0
- pyedb/grpc/database/net/net_class.py +93 -0
- pyedb/grpc/database/nets.py +633 -0
- pyedb/grpc/database/padstacks.py +1500 -0
- pyedb/grpc/database/ports/__init__.py +0 -0
- pyedb/grpc/database/ports/ports.py +396 -0
- pyedb/grpc/database/primitive/__init__.py +3 -0
- pyedb/grpc/database/primitive/bondwire.py +181 -0
- pyedb/grpc/database/primitive/circle.py +75 -0
- pyedb/grpc/database/primitive/padstack_instance.py +1116 -0
- pyedb/grpc/database/primitive/path.py +346 -0
- pyedb/grpc/database/primitive/polygon.py +276 -0
- pyedb/grpc/database/primitive/primitive.py +739 -0
- pyedb/grpc/database/primitive/rectangle.py +146 -0
- pyedb/grpc/database/simulation_setup/__init__.py +0 -0
- pyedb/grpc/database/simulation_setup/adaptive_frequency.py +33 -0
- pyedb/grpc/database/simulation_setup/hfss_advanced_meshing_settings.py +32 -0
- pyedb/grpc/database/simulation_setup/hfss_advanced_settings.py +59 -0
- pyedb/grpc/database/simulation_setup/hfss_dcr_settings.py +35 -0
- pyedb/grpc/database/simulation_setup/hfss_general_settings.py +61 -0
- pyedb/grpc/database/simulation_setup/hfss_settings_options.py +78 -0
- pyedb/grpc/database/simulation_setup/hfss_simulation_settings.py +118 -0
- pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +355 -0
- pyedb/grpc/database/simulation_setup/hfss_solver_settings.py +34 -0
- pyedb/grpc/database/simulation_setup/mesh_operation.py +34 -0
- pyedb/grpc/database/simulation_setup/raptor_x_advanced_settings.py +34 -0
- pyedb/grpc/database/simulation_setup/raptor_x_general_settings.py +33 -0
- pyedb/grpc/database/simulation_setup/raptor_x_simulation_settings.py +64 -0
- pyedb/grpc/database/simulation_setup/raptor_x_simulation_setup.py +125 -0
- pyedb/grpc/database/simulation_setup/siwave_dcir_simulation_setup.py +34 -0
- pyedb/grpc/database/simulation_setup/siwave_simulation_setup.py +119 -0
- pyedb/grpc/database/simulation_setup/sweep_data.py +32 -0
- pyedb/grpc/database/siwave.py +1023 -0
- pyedb/grpc/database/source_excitations.py +2572 -0
- pyedb/grpc/database/stackup.py +2574 -0
- pyedb/grpc/database/terminal/__init__.py +0 -0
- pyedb/grpc/database/terminal/bundle_terminal.py +218 -0
- pyedb/grpc/database/terminal/edge_terminal.py +51 -0
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +171 -0
- pyedb/grpc/database/terminal/pingroup_terminal.py +162 -0
- pyedb/grpc/database/terminal/point_terminal.py +99 -0
- pyedb/grpc/database/terminal/terminal.py +470 -0
- pyedb/grpc/database/utility/__init__.py +3 -0
- pyedb/grpc/database/utility/constants.py +25 -0
- pyedb/grpc/database/utility/heat_sink.py +124 -0
- pyedb/grpc/database/utility/hfss_extent_info.py +448 -0
- pyedb/grpc/database/utility/layout_statistics.py +277 -0
- pyedb/grpc/database/utility/rlc.py +80 -0
- pyedb/grpc/database/utility/simulation_configuration.py +3305 -0
- pyedb/grpc/database/utility/sources.py +388 -0
- pyedb/grpc/database/utility/sweep_data_distribution.py +83 -0
- pyedb/grpc/database/utility/xml_control_file.py +1277 -0
- pyedb/grpc/edb.py +4151 -0
- pyedb/grpc/edb_init.py +481 -0
- pyedb/grpc/rpc_session.py +177 -0
- pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +3 -2
- pyedb/ipc2581/ecad/cad_data/feature.py +4 -3
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +32 -20
- pyedb/ipc2581/ecad/cad_data/outline.py +3 -2
- pyedb/ipc2581/ecad/cad_data/package.py +4 -3
- pyedb/ipc2581/ecad/cad_data/path.py +82 -31
- pyedb/ipc2581/ecad/cad_data/polygon.py +122 -60
- pyedb/ipc2581/ecad/cad_data/profile.py +13 -12
- pyedb/ipc2581/ecad/cad_data/step.py +53 -21
- pyedb/ipc2581/ipc2581.py +47 -49
- pyedb/modeler/geometry_operators.py +1 -1
- {pyedb-0.38.0.dist-info → pyedb-0.39.0.dist-info}/METADATA +5 -2
- pyedb-0.39.0.dist-info/RECORD +288 -0
- pyedb-0.38.0.dist-info/RECORD +0 -195
- /pyedb/dotnet/{edb_core → database}/__init__.py +0 -0
- /pyedb/dotnet/{application → database/cell}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core/cell → database/cell/hierarchy}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/cell/hierarchy/netlist_model.py +0 -0
- /pyedb/dotnet/{edb_core → database}/cell/hierarchy/pin_pair_model.py +0 -0
- /pyedb/dotnet/{edb_core → database}/cell/hierarchy/s_parameter_model.py +0 -0
- /pyedb/dotnet/{edb_core → database}/cell/hierarchy/spice_model.py +0 -0
- /pyedb/dotnet/{edb_core → database}/cell/primitive/__init__.py +0 -0
- /pyedb/dotnet/{edb_core/cell/hierarchy → database/cell/terminal}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core/cell/terminal → database/definition}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core/definition → database/dotnet}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core/dotnet → database/edb_data}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/edb_data/design_options.py +0 -0
- /pyedb/dotnet/{edb_core → database}/edb_data/edbvalue.py +0 -0
- /pyedb/dotnet/{edb_core → database}/edb_data/layer_data.py +0 -0
- /pyedb/dotnet/{edb_core → database}/edb_data/utilities.py +0 -0
- /pyedb/dotnet/{edb_core → database}/general.py +0 -0
- /pyedb/dotnet/{edb_core/edb_data → database/geometry}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/geometry/point_data.py +0 -0
- /pyedb/dotnet/{edb_core → database}/sim_setup_data/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/sim_setup_data/data/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/sim_setup_data/data/adaptive_frequency_data.py +0 -0
- /pyedb/dotnet/{edb_core/geometry → database/sim_setup_data/io}/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/sim_setup_data/io/siwave.py +0 -0
- /pyedb/dotnet/{edb_core → database}/utilities/__init__.py +0 -0
- /pyedb/dotnet/{edb_core → database}/utilities/heatsink.py +0 -0
- /pyedb/{dotnet/edb_core/sim_setup_data/io → grpc/database/definition}/__init__.py +0 -0
- {pyedb-0.38.0.dist-info → pyedb-0.39.0.dist-info}/LICENSE +0 -0
- {pyedb-0.38.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)
|