pyedb 0.57.0__py3-none-any.whl → 0.58.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyedb might be problematic. Click here for more details.
- pyedb/__init__.py +1 -1
- pyedb/configuration/cfg_pin_groups.py +2 -0
- pyedb/dotnet/database/cell/hierarchy/component.py +2 -8
- pyedb/dotnet/database/cell/layout.py +1 -1
- pyedb/dotnet/database/components.py +1 -3
- pyedb/dotnet/database/edb_data/control_file.py +13 -5
- pyedb/dotnet/database/edb_data/padstacks_data.py +34 -12
- pyedb/dotnet/database/edb_data/sources.py +21 -2
- pyedb/dotnet/database/general.py +1 -6
- pyedb/dotnet/database/layout_validation.py +8 -0
- pyedb/dotnet/database/sim_setup_data/io/siwave.py +53 -0
- pyedb/dotnet/database/stackup.py +5 -32
- pyedb/dotnet/database/utilities/hfss_simulation_setup.py +81 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +259 -11
- pyedb/dotnet/edb.py +26 -13
- pyedb/extensions/create_cell_array.py +48 -44
- pyedb/generic/general_methods.py +24 -36
- pyedb/generic/plot.py +8 -23
- pyedb/generic/process.py +78 -10
- pyedb/grpc/database/components.py +7 -5
- pyedb/grpc/database/control_file.py +13 -5
- pyedb/grpc/database/definition/padstack_def.py +10 -5
- pyedb/grpc/database/hierarchy/component.py +2 -9
- pyedb/grpc/database/modeler.py +28 -8
- pyedb/grpc/database/padstacks.py +62 -103
- pyedb/grpc/database/primitive/padstack_instance.py +41 -12
- pyedb/grpc/database/primitive/path.py +13 -13
- pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +79 -0
- pyedb/grpc/database/source_excitations.py +7 -7
- pyedb/grpc/database/stackup.py +5 -33
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +9 -11
- pyedb/grpc/database/terminal/point_terminal.py +30 -0
- pyedb/grpc/database/terminal/terminal.py +16 -2
- pyedb/grpc/database/utility/xml_control_file.py +13 -5
- pyedb/grpc/edb.py +39 -13
- pyedb/misc/aedtlib_personalib_install.py +2 -2
- pyedb/misc/downloads.py +18 -3
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +2 -1
- pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +0 -1
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/METADATA +4 -5
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/RECORD +43 -43
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/WHEEL +0 -0
- {pyedb-0.57.0.dist-info → pyedb-0.58.0.dist-info}/licenses/LICENSE +0 -0
pyedb/generic/general_methods.py
CHANGED
|
@@ -50,13 +50,9 @@ is_linux = os.name == "posix"
|
|
|
50
50
|
is_windows = not is_linux
|
|
51
51
|
_pythonver = sys.version_info[0]
|
|
52
52
|
|
|
53
|
+
import xml.etree.cElementTree as ET # nosec B405
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
import xml.etree.cElementTree as ET
|
|
56
|
-
|
|
57
|
-
ET.VERSION
|
|
58
|
-
except ImportError:
|
|
59
|
-
ET = None
|
|
55
|
+
ET.VERSION
|
|
60
56
|
|
|
61
57
|
|
|
62
58
|
class GrpcApiError(Exception):
|
|
@@ -694,13 +690,9 @@ def read_csv_pandas(filename, encoding="utf-8"): # pragma: no cover
|
|
|
694
690
|
:class:`pandas.DataFrame`
|
|
695
691
|
|
|
696
692
|
"""
|
|
697
|
-
|
|
698
|
-
import pandas as pd
|
|
693
|
+
import pandas as pd
|
|
699
694
|
|
|
700
|
-
|
|
701
|
-
except ImportError:
|
|
702
|
-
logging.error("Pandas is not available. Install it.")
|
|
703
|
-
return None
|
|
695
|
+
return pd.read_csv(filename, encoding=encoding, header=0, na_values=".")
|
|
704
696
|
|
|
705
697
|
|
|
706
698
|
def read_tab(filename): # pragma: no cover
|
|
@@ -734,14 +726,10 @@ def read_xlsx(filename): # pragma: no cover
|
|
|
734
726
|
list
|
|
735
727
|
|
|
736
728
|
"""
|
|
737
|
-
|
|
738
|
-
import pandas as pd
|
|
729
|
+
import pandas as pd
|
|
739
730
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
except ImportError:
|
|
743
|
-
lines = []
|
|
744
|
-
return lines
|
|
731
|
+
lines = pd.read_excel(filename)
|
|
732
|
+
return lines
|
|
745
733
|
|
|
746
734
|
|
|
747
735
|
def write_csv(output, list_data, delimiter=",", quotechar="|", quoting=csv.QUOTE_MINIMAL): # pragma: no cover
|
|
@@ -871,11 +859,7 @@ def compute_fft(time_vals, value): # pragma: no cover
|
|
|
871
859
|
tuple
|
|
872
860
|
Frequency and Values.
|
|
873
861
|
"""
|
|
874
|
-
|
|
875
|
-
import numpy as np
|
|
876
|
-
except ImportError:
|
|
877
|
-
logging.error("NumPy is not available. Install it.")
|
|
878
|
-
return False
|
|
862
|
+
import numpy as np
|
|
879
863
|
|
|
880
864
|
deltaT = time_vals[-1] - time_vals[0]
|
|
881
865
|
num_points = len(time_vals)
|
|
@@ -924,11 +908,8 @@ def parse_excitation_file(
|
|
|
924
908
|
tuple
|
|
925
909
|
Frequency, magnitude and phase.
|
|
926
910
|
"""
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
except ImportError:
|
|
930
|
-
logging.error("NumPy is not available. Install it.")
|
|
931
|
-
return False
|
|
911
|
+
import numpy as np
|
|
912
|
+
|
|
932
913
|
df = read_csv_pandas(file_name, encoding=encoding)
|
|
933
914
|
if is_time_domain:
|
|
934
915
|
time = df[df.keys()[0]].values * x_scale
|
|
@@ -1165,6 +1146,11 @@ def install_with_pip(package_name, package_path=None, upgrade=False, uninstall=F
|
|
|
1165
1146
|
"""Install a new package using pip.
|
|
1166
1147
|
This method is useful for installing a package from the AEDT Console without launching the Python environment.
|
|
1167
1148
|
|
|
1149
|
+
.. warning::
|
|
1150
|
+
Do not execute this function with untrusted function argument, environment
|
|
1151
|
+
variables or pyedb global settings.
|
|
1152
|
+
See the :ref:`security guide<ref_security_consideration>` for details.
|
|
1153
|
+
|
|
1168
1154
|
Parameters
|
|
1169
1155
|
----------
|
|
1170
1156
|
package_name : str
|
|
@@ -1177,10 +1163,12 @@ def install_with_pip(package_name, package_path=None, upgrade=False, uninstall=F
|
|
|
1177
1163
|
Whether to install the package or uninstall the package.
|
|
1178
1164
|
"""
|
|
1179
1165
|
|
|
1180
|
-
import subprocess
|
|
1166
|
+
import subprocess # nosec B404
|
|
1181
1167
|
|
|
1182
|
-
|
|
1168
|
+
if not package_name or not isinstance(package_name, str):
|
|
1169
|
+
raise ValueError("A valid package name must be provided.")
|
|
1183
1170
|
|
|
1171
|
+
executable = sys.executable
|
|
1184
1172
|
commands = []
|
|
1185
1173
|
if uninstall:
|
|
1186
1174
|
commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name])
|
|
@@ -1194,12 +1182,12 @@ def install_with_pip(package_name, package_path=None, upgrade=False, uninstall=F
|
|
|
1194
1182
|
command.append("-U")
|
|
1195
1183
|
|
|
1196
1184
|
commands.append(command)
|
|
1185
|
+
|
|
1197
1186
|
for command in commands:
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
p.wait()
|
|
1187
|
+
try:
|
|
1188
|
+
subprocess.run(command, check=True) # nosec
|
|
1189
|
+
except subprocess.CalledProcessError as e: # nosec
|
|
1190
|
+
raise RuntimeError("An error occurred while installing with pip") from e
|
|
1203
1191
|
|
|
1204
1192
|
|
|
1205
1193
|
class Help: # pragma: no cover
|
pyedb/generic/plot.py
CHANGED
|
@@ -1,31 +1,16 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import warnings
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
warnings.warn(
|
|
8
|
-
"The NumPy module is required to run some functionalities of PostProcess.\n"
|
|
9
|
-
"Install with \n\npip install numpy\n\nRequires CPython."
|
|
10
|
-
)
|
|
11
|
-
try:
|
|
12
|
-
from matplotlib.patches import PathPatch
|
|
13
|
-
from matplotlib.path import Path
|
|
3
|
+
from matplotlib.patches import PathPatch
|
|
4
|
+
from matplotlib.path import Path
|
|
5
|
+
import numpy # noqa: F401
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
# Use matplotlib agg backend (non-interactive) when the CI is running.
|
|
8
|
+
if bool(int(os.getenv("PYEDB_CI_NO_DISPLAY", "0"))): # pragma: no cover
|
|
9
|
+
import matplotlib
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
import matplotlib.pyplot as plt
|
|
11
|
+
matplotlib.use("Agg")
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
warnings.warn(
|
|
24
|
-
"The Matplotlib module is required to run some functionalities of PostProcess.\n"
|
|
25
|
-
"Install with \n\npip install matplotlib\n\nRequires CPython."
|
|
26
|
-
)
|
|
27
|
-
except Exception:
|
|
28
|
-
warnings.warn("Unknown error occurred while attempting to import Matplotlib.")
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
29
14
|
|
|
30
15
|
|
|
31
16
|
def plot_matplotlib(
|
pyedb/generic/process.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os.path
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
import subprocess
|
|
3
|
+
import subprocess # nosec B404
|
|
4
4
|
|
|
5
5
|
from pyedb.generic.general_methods import is_linux
|
|
6
6
|
|
|
@@ -23,7 +23,63 @@ class SiwaveSolve(object):
|
|
|
23
23
|
full_path = Path(self._pedb.ansys_em_path) / executable
|
|
24
24
|
return str(full_path)
|
|
25
25
|
|
|
26
|
+
def __create_exec(self, type):
|
|
27
|
+
base = os.path.splitext(self._pedb.edbpath)[0]
|
|
28
|
+
txt_path = base + ".txt"
|
|
29
|
+
exec_path = base + ".exec"
|
|
30
|
+
with open(txt_path, "w") as file:
|
|
31
|
+
if type:
|
|
32
|
+
if type == "DCIR":
|
|
33
|
+
file.write("ExecDcSim")
|
|
34
|
+
elif type == "SYZ":
|
|
35
|
+
file.write("ExecSyzSim")
|
|
36
|
+
elif type == "CPA":
|
|
37
|
+
file.write("ExecSentinelCpaSim")
|
|
38
|
+
elif type == "TimeCrosstalk":
|
|
39
|
+
file.write("ExecTimeDomainCrosstalkSim")
|
|
40
|
+
elif type == "FreqCrosstalk":
|
|
41
|
+
file.write("ExecCrosstalkSim")
|
|
42
|
+
elif type == "Impedance":
|
|
43
|
+
file.write("ExecZ0Sim")
|
|
44
|
+
|
|
45
|
+
os.rename(txt_path, exec_path)
|
|
46
|
+
return exec_path
|
|
47
|
+
|
|
48
|
+
def solve_siwave(self, edbpath, analysis_type):
|
|
49
|
+
"""Solve an SIWave setup. Only non-graphical batch mode is supported.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
analysis_type: str
|
|
54
|
+
Type of SIWave analysis to perform. Available types are "SYZ", "DCIR", "CPA", "TimeCrosstalk",
|
|
55
|
+
"FreqCrosstalk", "Impedance".
|
|
56
|
+
edbpath: str
|
|
57
|
+
Full path to the .aedb folder, siw or siwz file to be solved.
|
|
58
|
+
siwave_ng: str, optinial
|
|
59
|
+
Path to the siwave_ng. Default is the SIWave installation path.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
command = [
|
|
63
|
+
self.__siwave_ng_exe_path,
|
|
64
|
+
edbpath,
|
|
65
|
+
self.__create_exec(type=analysis_type),
|
|
66
|
+
"-formatOutput",
|
|
67
|
+
"-useSubdir",
|
|
68
|
+
]
|
|
69
|
+
try:
|
|
70
|
+
subprocess.run(command, check=True)
|
|
71
|
+
except subprocess.CalledProcessError as e:
|
|
72
|
+
raise RuntimeError(f"An error occurred when launching the solver. Please check input paths") from e
|
|
73
|
+
|
|
26
74
|
def solve(self, num_of_cores=4):
|
|
75
|
+
"""Solve using siwave_ng.exe
|
|
76
|
+
|
|
77
|
+
.. warning::
|
|
78
|
+
Do not execute this function with untrusted function argument, environment
|
|
79
|
+
variables or pyedb global settings.
|
|
80
|
+
See the :ref:`security guide<ref_security_consideration>` for details.
|
|
81
|
+
|
|
82
|
+
"""
|
|
27
83
|
exec_file = os.path.splitext(self._pedb.edbpath)[0] + ".exec"
|
|
28
84
|
if os.path.exists(exec_file):
|
|
29
85
|
with open(exec_file, "r+") as f:
|
|
@@ -39,16 +95,21 @@ class SiwaveSolve(object):
|
|
|
39
95
|
f = open(exec_file, "w")
|
|
40
96
|
f.writelines(content)
|
|
41
97
|
command = [self.__siwave_ng_exe_path, self._pedb.edbpath, exec_file, "-formatOutput -useSubdir"]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
98
|
+
try:
|
|
99
|
+
subprocess.run(command, check=True) # nosec
|
|
100
|
+
except subprocess.CalledProcessError as e: # nosec
|
|
101
|
+
raise RuntimeError("An error occurred while solving") from e
|
|
46
102
|
|
|
47
103
|
def export_3d_cad(
|
|
48
104
|
self, format_3d="Q3D", output_folder=None, net_list=None, num_cores=4, aedt_file_name=None, hidden=False
|
|
49
105
|
): # pragma: no cover
|
|
50
106
|
"""Export edb to Q3D or HFSS
|
|
51
107
|
|
|
108
|
+
.. warning::
|
|
109
|
+
Do not execute this function with untrusted function argument, environment
|
|
110
|
+
variables or pyedb global settings.
|
|
111
|
+
See the :ref:`security guide<ref_security_consideration>` for details.
|
|
112
|
+
|
|
52
113
|
Parameters
|
|
53
114
|
----------
|
|
54
115
|
format_3d : str, default ``Q3D``
|
|
@@ -97,10 +158,10 @@ class SiwaveSolve(object):
|
|
|
97
158
|
command += ["-RunScriptAndExit", scriptname]
|
|
98
159
|
print(command)
|
|
99
160
|
try:
|
|
100
|
-
result = subprocess.run(command, check=True, capture_output=True)
|
|
161
|
+
result = subprocess.run(command, check=True, capture_output=True) # nosec
|
|
101
162
|
print(result.stdout.decode())
|
|
102
|
-
except subprocess.CalledProcessError as e:
|
|
103
|
-
|
|
163
|
+
except subprocess.CalledProcessError as e: # nosec
|
|
164
|
+
raise RuntimeError("An error occurred while exporting 3D CAD") from e
|
|
104
165
|
return os.path.join(output_folder, aedt_file_name)
|
|
105
166
|
|
|
106
167
|
def export_dc_report(
|
|
@@ -119,6 +180,11 @@ class SiwaveSolve(object):
|
|
|
119
180
|
):
|
|
120
181
|
"""Close EDB and solve it with Siwave.
|
|
121
182
|
|
|
183
|
+
.. warning::
|
|
184
|
+
Do not execute this function with untrusted function argument, environment
|
|
185
|
+
variables or pyedb global settings.
|
|
186
|
+
See the :ref:`security guide<ref_security_consideration>` for details.
|
|
187
|
+
|
|
122
188
|
Parameters
|
|
123
189
|
----------
|
|
124
190
|
siwave_project : str
|
|
@@ -215,6 +281,8 @@ class SiwaveSolve(object):
|
|
|
215
281
|
command.append("-RunScriptAndExit")
|
|
216
282
|
command.append(scriptname)
|
|
217
283
|
print(command)
|
|
218
|
-
|
|
219
|
-
|
|
284
|
+
try:
|
|
285
|
+
subprocess.run(command, check=True) # nosec
|
|
286
|
+
except subprocess.CalledProcessError as e: # nosec
|
|
287
|
+
raise RuntimeError("An error occurred while solving with Siwave") from e
|
|
220
288
|
return output_list
|
|
@@ -1010,8 +1010,6 @@ class Components(object):
|
|
|
1010
1010
|
if component_definition_pin.is_null:
|
|
1011
1011
|
self._logger.error(f"Failed to create component definition pin {name}-{pin.name}")
|
|
1012
1012
|
return None
|
|
1013
|
-
else:
|
|
1014
|
-
self._logger.warning("Found existing component definition for footprint {}".format(name))
|
|
1015
1013
|
return component_definition
|
|
1016
1014
|
|
|
1017
1015
|
def create(
|
|
@@ -1074,6 +1072,8 @@ class Components(object):
|
|
|
1074
1072
|
if not compdef:
|
|
1075
1073
|
return False
|
|
1076
1074
|
new_cmp = GrpcComponentGroup.create(self._active_layout, component_name, compdef.name)
|
|
1075
|
+
if new_cmp.is_null:
|
|
1076
|
+
raise ValueError(f"Failed to create component {component_name}.")
|
|
1077
1077
|
if hasattr(pins[0], "component") and pins[0].component:
|
|
1078
1078
|
hosting_component_location = None
|
|
1079
1079
|
if not pins[0].component.is_null:
|
|
@@ -1469,13 +1469,15 @@ class Components(object):
|
|
|
1469
1469
|
sball_shape = GrpcSolderballShape.SOLDERBALL_SPHEROID
|
|
1470
1470
|
|
|
1471
1471
|
cmp_property = cmp.component_property
|
|
1472
|
-
if cmp.
|
|
1472
|
+
if cmp.component_type == GrpcComponentType.IC:
|
|
1473
1473
|
ic_die_prop = cmp_property.die_property
|
|
1474
1474
|
ic_die_prop.die_type = GrpcDieType.FLIPCHIP
|
|
1475
|
+
if not cmp.placement_layer == list(self._pedb.stackup.layers.keys())[0]:
|
|
1476
|
+
chip_orientation = "chip_up"
|
|
1475
1477
|
if chip_orientation.lower() == "chip_up":
|
|
1476
|
-
ic_die_prop.
|
|
1478
|
+
ic_die_prop.die_orientation = GrpDieOrientation.CHIP_UP
|
|
1477
1479
|
else:
|
|
1478
|
-
ic_die_prop.
|
|
1480
|
+
ic_die_prop.die_orientation = GrpDieOrientation.CHIP_DOWN
|
|
1479
1481
|
cmp_property.die_property = ic_die_prop
|
|
1480
1482
|
|
|
1481
1483
|
solder_ball_prop = cmp_property.solder_ball_property
|
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
import copy
|
|
24
24
|
import os
|
|
25
25
|
import re
|
|
26
|
-
import subprocess
|
|
26
|
+
import subprocess # nosec B404
|
|
27
27
|
import sys
|
|
28
28
|
from typing import Any, Dict, List, Optional, Union
|
|
29
29
|
|
|
30
|
+
from defusedxml.ElementTree import parse as defused_parse
|
|
31
|
+
|
|
30
32
|
from pyedb.generic.general_methods import ET, env_path, env_value, is_linux
|
|
31
33
|
from pyedb.generic.settings import settings
|
|
32
34
|
from pyedb.misc.aedtlib_personalib_install import write_pretty_xml
|
|
@@ -36,6 +38,11 @@ from pyedb.misc.misc import list_installed_ansysem
|
|
|
36
38
|
def convert_technology_file(tech_file, edbversion=None, control_file=None):
|
|
37
39
|
"""Convert a technology file to EDB control file (XML).
|
|
38
40
|
|
|
41
|
+
.. warning::
|
|
42
|
+
Do not execute this function with untrusted function argument, environment
|
|
43
|
+
variables or pyedb global settings.
|
|
44
|
+
See the :ref:`security guide<ref_security_consideration>` for details.
|
|
45
|
+
|
|
39
46
|
Parameters
|
|
40
47
|
----------
|
|
41
48
|
tech_file : str
|
|
@@ -212,10 +219,11 @@ def convert_technology_file(tech_file, edbversion=None, control_file=None):
|
|
|
212
219
|
]
|
|
213
220
|
commands.append(command)
|
|
214
221
|
commands.append(["rm", "-r", vlc_file_name + ".aedb"])
|
|
215
|
-
my_env = os.environ.copy()
|
|
216
222
|
for command in commands:
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
try:
|
|
224
|
+
subprocess.run(command, check=True) # nosec
|
|
225
|
+
except subprocess.CalledProcessError as e: # nosec
|
|
226
|
+
raise RuntimeError("An error occurred while converting a technology file to edb control file") from e
|
|
219
227
|
if os.path.exists(control_file):
|
|
220
228
|
settings.logger.info("XML file created.")
|
|
221
229
|
return control_file
|
|
@@ -1652,7 +1660,7 @@ class ControlFile:
|
|
|
1652
1660
|
bool
|
|
1653
1661
|
``True`` if successful, ``False`` otherwise.
|
|
1654
1662
|
"""
|
|
1655
|
-
tree =
|
|
1663
|
+
tree = defused_parse(xml_input)
|
|
1656
1664
|
root = tree.getroot()
|
|
1657
1665
|
for el in root:
|
|
1658
1666
|
if el.tag == "Stackup":
|
|
@@ -35,6 +35,7 @@ from ansys.edb.core.hierarchy.structure3d import MeshClosure as GrpcMeshClosure,
|
|
|
35
35
|
from ansys.edb.core.primitive.circle import Circle as GrpcCircle
|
|
36
36
|
|
|
37
37
|
from pyedb.generic.general_methods import generate_unique_name
|
|
38
|
+
from pyedb.grpc.database.primitive.circle import Circle
|
|
38
39
|
from pyedb.grpc.database.utility.value import Value
|
|
39
40
|
|
|
40
41
|
|
|
@@ -294,7 +295,11 @@ class PadstackDef(GrpcPadstackDef):
|
|
|
294
295
|
List[:class:`PadstackInstance <pyedb.grpc.database.primitive.padstack_instance.PadstackInstance>`]
|
|
295
296
|
List of PadstackInstance objects.
|
|
296
297
|
"""
|
|
297
|
-
return [
|
|
298
|
+
return [
|
|
299
|
+
i
|
|
300
|
+
for i in list(self._pedb.padstacks.instances.values())
|
|
301
|
+
if not i.is_null and i.padstack_def.name == self.name
|
|
302
|
+
]
|
|
298
303
|
|
|
299
304
|
@property
|
|
300
305
|
def layers(self) -> list[str]:
|
|
@@ -717,7 +722,7 @@ class PadstackDef(GrpcPadstackDef):
|
|
|
717
722
|
net_name=via.net_name,
|
|
718
723
|
)
|
|
719
724
|
else:
|
|
720
|
-
|
|
725
|
+
Circle(self._pedb).create(
|
|
721
726
|
layout,
|
|
722
727
|
self.start_layer,
|
|
723
728
|
via.net,
|
|
@@ -732,7 +737,7 @@ class PadstackDef(GrpcPadstackDef):
|
|
|
732
737
|
net_name=via.net_name,
|
|
733
738
|
)
|
|
734
739
|
else:
|
|
735
|
-
|
|
740
|
+
Circle(self._pedb).create(
|
|
736
741
|
layout,
|
|
737
742
|
self.stop_layer,
|
|
738
743
|
via.net,
|
|
@@ -745,7 +750,7 @@ class PadstackDef(GrpcPadstackDef):
|
|
|
745
750
|
if layer_name == via.start_layer or started:
|
|
746
751
|
start = layer_name
|
|
747
752
|
stop = layer_names[layer_names.index(layer_name) + 1]
|
|
748
|
-
cloned_circle =
|
|
753
|
+
cloned_circle = Circle(self._pedb).create(
|
|
749
754
|
layout,
|
|
750
755
|
start,
|
|
751
756
|
via.net,
|
|
@@ -753,7 +758,7 @@ class PadstackDef(GrpcPadstackDef):
|
|
|
753
758
|
Value(pos[1]),
|
|
754
759
|
Value(rad1),
|
|
755
760
|
)
|
|
756
|
-
cloned_circle2 =
|
|
761
|
+
cloned_circle2 = Circle(self._pedb).create(
|
|
757
762
|
layout,
|
|
758
763
|
stop,
|
|
759
764
|
via.net,
|
|
@@ -48,7 +48,9 @@ from ansys.edb.core.terminal.padstack_instance_terminal import (
|
|
|
48
48
|
PadstackInstanceTerminal as GrpcPadstackInstanceTerminal,
|
|
49
49
|
)
|
|
50
50
|
from ansys.edb.core.utility.rlc import Rlc as GrpcRlc
|
|
51
|
+
import numpy as np
|
|
51
52
|
|
|
53
|
+
from pyedb.generic.general_methods import get_filename_without_extension
|
|
52
54
|
from pyedb.grpc.database.hierarchy.pin_pair_model import PinPairModel
|
|
53
55
|
from pyedb.grpc.database.hierarchy.s_parameter_model import SparamModel
|
|
54
56
|
from pyedb.grpc.database.hierarchy.spice_model import SpiceModel
|
|
@@ -59,15 +61,6 @@ from pyedb.grpc.database.terminal.padstack_instance_terminal import (
|
|
|
59
61
|
)
|
|
60
62
|
from pyedb.grpc.database.utility.value import Value
|
|
61
63
|
|
|
62
|
-
try:
|
|
63
|
-
import numpy as np
|
|
64
|
-
except ImportError:
|
|
65
|
-
warnings.warn(
|
|
66
|
-
"The NumPy module is required to run some functionalities of EDB.\n"
|
|
67
|
-
"Install with \n\npip install numpy\n\nRequires CPython."
|
|
68
|
-
)
|
|
69
|
-
from pyedb.generic.general_methods import get_filename_without_extension
|
|
70
|
-
|
|
71
64
|
|
|
72
65
|
class Component(GrpcComponentGroup):
|
|
73
66
|
"""Manages EDB functionalities for components.
|
pyedb/grpc/database/modeler.py
CHANGED
|
@@ -25,16 +25,13 @@ This module contains these classes: `EdbLayout` and `Shape`.
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
import math
|
|
28
|
-
from typing import Any, Dict, List, Optional, Union
|
|
28
|
+
from typing import Any, Dict, Iterable, List, Optional, Union
|
|
29
29
|
|
|
30
|
-
from ansys.edb.core.geometry.arc_data import ArcData as GrpcArcData
|
|
31
30
|
from ansys.edb.core.geometry.point_data import PointData as GrpcPointData
|
|
32
31
|
from ansys.edb.core.geometry.polygon_data import (
|
|
33
32
|
PolygonData as GrpcPolygonData,
|
|
34
|
-
PolygonSenseType as GrpcPolygonSenseType,
|
|
35
33
|
)
|
|
36
34
|
from ansys.edb.core.hierarchy.pin_group import PinGroup as GrpcPinGroup
|
|
37
|
-
from ansys.edb.core.inner.exceptions import InvalidArgumentException
|
|
38
35
|
from ansys.edb.core.primitive.bondwire import BondwireType as GrpcBondwireType
|
|
39
36
|
from ansys.edb.core.primitive.path import PathCornerType as GrpcPathCornerType, PathEndCapType as GrpcPathEndCapType
|
|
40
37
|
from ansys.edb.core.primitive.rectangle import (
|
|
@@ -51,6 +48,25 @@ from pyedb.grpc.database.utility.layout_statistics import LayoutStatistics
|
|
|
51
48
|
from pyedb.grpc.database.utility.value import Value
|
|
52
49
|
|
|
53
50
|
|
|
51
|
+
def normalize_pairs(points: Iterable[float]) -> List[List[float]]:
|
|
52
|
+
"""
|
|
53
|
+
Convert any reasonable point description into [[x1, y1], [x2, y2], …]
|
|
54
|
+
"""
|
|
55
|
+
pts = list(points)
|
|
56
|
+
if not pts: # empty input
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
# Detect flat vs nested
|
|
60
|
+
if isinstance(pts[0], (list, tuple)):
|
|
61
|
+
# already nested – just ensure every item is a *list* (not tuple)
|
|
62
|
+
return [list(pair) for pair in pts]
|
|
63
|
+
else:
|
|
64
|
+
# flat list – chunk into pairs
|
|
65
|
+
if len(pts) % 2:
|
|
66
|
+
raise ValueError("Odd number of coordinates supplied")
|
|
67
|
+
return [[pts[i], pts[i + 1]] for i in range(0, len(pts), 2)]
|
|
68
|
+
|
|
69
|
+
|
|
54
70
|
class Modeler(object):
|
|
55
71
|
"""Manages EDB methods for primitives management accessible from `Edb.modeler`.
|
|
56
72
|
|
|
@@ -683,7 +699,8 @@ class Modeler(object):
|
|
|
683
699
|
else:
|
|
684
700
|
corner_style = GrpcPathCornerType.MITER
|
|
685
701
|
_points = []
|
|
686
|
-
if isinstance(points, list):
|
|
702
|
+
if isinstance(points, (list, tuple)):
|
|
703
|
+
points = normalize_pairs(points)
|
|
687
704
|
for pt in points:
|
|
688
705
|
_pt = []
|
|
689
706
|
for coord in pt:
|
|
@@ -714,7 +731,7 @@ class Modeler(object):
|
|
|
714
731
|
|
|
715
732
|
def create_trace(
|
|
716
733
|
self,
|
|
717
|
-
path_list: Union[
|
|
734
|
+
path_list: Union[Iterable[float], GrpcPolygonData],
|
|
718
735
|
layer_name: str,
|
|
719
736
|
width: float = 1,
|
|
720
737
|
net_name: str = "",
|
|
@@ -726,8 +743,9 @@ class Modeler(object):
|
|
|
726
743
|
|
|
727
744
|
Parameters
|
|
728
745
|
----------
|
|
729
|
-
path_list :
|
|
730
|
-
List of [x,y]
|
|
746
|
+
path_list : Iterable
|
|
747
|
+
List of points [x,y] or [[x, y], ...]
|
|
748
|
+
or [(x, y)...].
|
|
731
749
|
layer_name : str
|
|
732
750
|
Layer name.
|
|
733
751
|
width : float, optional
|
|
@@ -803,6 +821,8 @@ class Modeler(object):
|
|
|
803
821
|
for void in voids:
|
|
804
822
|
if isinstance(void, list):
|
|
805
823
|
void_polygon_data = GrpcPolygonData(points=void)
|
|
824
|
+
elif isinstance(void, GrpcPolygonData):
|
|
825
|
+
void_polygon_data = void
|
|
806
826
|
else:
|
|
807
827
|
void_polygon_data = void.polygon_data
|
|
808
828
|
if not void_polygon_data.points:
|