From ee8356481ffd2f00238f9c739c84406a56ee929c Mon Sep 17 00:00:00 2001 From: svandenb Date: Mon, 6 Nov 2023 13:46:25 +0100 Subject: [PATCH] first test added --- src/pyedb/__init__.py | 2 +- src/pyedb/generic/general_methods.py | 35 +- src/pyedb/grpc/components.py | 29 +- src/pyedb/grpc/edb.py | 5650 ++++++++--------- .../grpc/edb_core/edb_data/padstacks_data.py | 15 +- .../grpc/edb_core/edb_data/primitives_data.py | 2 +- src/pyedb/grpc/edb_core/edb_data/sources.py | 57 +- src/pyedb/grpc/grpc_init/database.py | 2 +- src/pyedb/grpc/hfss.py | 33 +- src/pyedb/grpc/nets.py | 9 +- src/pyedb/grpc/siwave.py | 223 +- .../edb_core/edb_data/padstacks_data.py | 2 +- src/pyedb/legacy/generic/general_methods.py | 4 +- tests/conftest.py | 132 +- tests/grpc/__init__.py | 0 tests/grpc/integration/__init__.py | 3 + tests/grpc/system/__init__.py | 3 + tests/grpc/system/conftest.py | 61 + tests/grpc/system/test_edb.py | 1667 +++++ tests/grpc/system/test_edb_components.py | 464 ++ .../system/test_edb_differential_pairs.py | 23 + tests/grpc/system/test_edb_extended_nets.py | 27 + tests/grpc/system/test_edb_ipc.py | 62 + tests/grpc/system/test_edb_materials.py | 113 + tests/grpc/system/test_edb_modeler.py | 274 + tests/grpc/system/test_edb_net_classes.py | 25 + tests/grpc/system/test_edb_nets.py | 121 + tests/grpc/system/test_edb_padstacks.py | 306 + tests/grpc/system/test_edb_stackup.py | 1030 +++ tests/grpc/unit/__init__.py | 0 tests/grpc/unit/test_edb.py | 124 + tests/grpc/unit/test_edbsiwave.py | 17 + tests/grpc/unit/test_materials.py | 79 + tests/grpc/unit/test_padstack.py | 22 + .../unit/test_simulation_configuration.py | 46 + tests/grpc/unit/test_source.py | 23 + tests/grpc/unit/test_stackup.py | 89 + 37 files changed, 7613 insertions(+), 3161 deletions(-) create mode 100644 tests/grpc/__init__.py create mode 100644 tests/grpc/integration/__init__.py create mode 100644 tests/grpc/system/__init__.py create mode 100644 tests/grpc/system/conftest.py create mode 100644 tests/grpc/system/test_edb.py create mode 100644 tests/grpc/system/test_edb_components.py create mode 100644 tests/grpc/system/test_edb_differential_pairs.py create mode 100644 tests/grpc/system/test_edb_extended_nets.py create mode 100644 tests/grpc/system/test_edb_ipc.py create mode 100644 tests/grpc/system/test_edb_materials.py create mode 100644 tests/grpc/system/test_edb_modeler.py create mode 100644 tests/grpc/system/test_edb_net_classes.py create mode 100644 tests/grpc/system/test_edb_nets.py create mode 100644 tests/grpc/system/test_edb_padstacks.py create mode 100644 tests/grpc/system/test_edb_stackup.py create mode 100644 tests/grpc/unit/__init__.py create mode 100644 tests/grpc/unit/test_edb.py create mode 100644 tests/grpc/unit/test_edbsiwave.py create mode 100644 tests/grpc/unit/test_materials.py create mode 100644 tests/grpc/unit/test_padstack.py create mode 100644 tests/grpc/unit/test_simulation_configuration.py create mode 100644 tests/grpc/unit/test_source.py create mode 100644 tests/grpc/unit/test_stackup.py diff --git a/src/pyedb/__init__.py b/src/pyedb/__init__.py index 635efc04ae..f21d9b6199 100644 --- a/src/pyedb/__init__.py +++ b/src/pyedb/__init__.py @@ -12,6 +12,6 @@ # By default we use pyedb legacy implementation if "PYEDB_USE_LEGACY" not in os.environ: - os.environ["PYEDB_USE_LEGACY"] = "1" + os.environ["PYEDB_USE_LEGACY"] = "0" from pyedb.generic.design_types import Edb diff --git a/src/pyedb/generic/general_methods.py b/src/pyedb/generic/general_methods.py index a064399dae..95f2f0f601 100644 --- a/src/pyedb/generic/general_methods.py +++ b/src/pyedb/generic/general_methods.py @@ -54,6 +54,7 @@ class MethodNotSupportedError(Exception): pass + def _exception(ex_info, func, args, kwargs, message="Type Error"): """Write the trace stack to the desktop when a Python error occurs. @@ -122,6 +123,7 @@ def _exception(ex_info, func, args, kwargs, message="Type Error"): ) ) + def _function_handler_wrapper(user_function): def wrapper(*args, **kwargs): if not settings.enable_error_handler: @@ -158,8 +160,10 @@ def wrapper(*args, **kwargs): except IOError: _exception(sys.exc_info(), user_function, args, kwargs, "IO Error") return False + return wrapper + def pyedb_function_handler(direct_func=None): """Provides an exception handler, logging mechanism, and argument converter for client-server communications. @@ -200,6 +204,7 @@ def get_filename_without_extension(path): """ return os.path.splitext(os.path.split(path)[1])[0] + def _write_mes(mes_text): mes_text = str(mes_text) parts = [mes_text[i: i + 250] for i in range(0, len(mes_text), 250)] @@ -496,7 +501,7 @@ def open_file(file_path, file_options="r"): if os.path.exists(file_path): return open(file_path, file_options) elif settings.remote_rpc_session and settings.remote_rpc_session.filemanager.pathexists( - file_path + file_path ): # pragma: no cover local_file = os.path.join(tempfile.gettempdir(), os.path.split(file_path)[-1]) settings.remote_rpc_session.filemanager.download_file(file_path, local_file) @@ -506,6 +511,7 @@ def open_file(file_path, file_options="r"): else: settings.logger.error("The file or folder %s does not exist", dir_name) + @pyedb_function_handler() def get_string_version(input_version): output_version = input_version @@ -524,10 +530,6 @@ def get_string_version(input_version): return output_version - - - - @pyedb_function_handler() def env_path_student(input_version): """Get the path of the version environment variable for an AEDT student version. @@ -579,7 +581,6 @@ def env_value_student(input_version): ) - @pyedb_function_handler() def generate_unique_folder_name(rootname=None, folder_name=None): """Generate a new AEDT folder name given a rootname. @@ -1299,7 +1300,7 @@ def compute_fft(time_vals, value): # pragma: no cover num_points = len(time_vals) valueFFT = np.fft.fft(value, num_points) Npoints = int(len(valueFFT) / 2) - valueFFT = valueFFT[1 : Npoints + 1] + valueFFT = valueFFT[1: Npoints + 1] valueFFT = valueFFT / len(valueFFT) n = np.arange(num_points) freq = n / deltaT @@ -1307,14 +1308,14 @@ def compute_fft(time_vals, value): # pragma: no cover def parse_excitation_file( - file_name, - is_time_domain=True, - x_scale=1, - y_scale=1, - impedance=50, - data_format="Power", - encoding="utf-8", - out_mag="Voltage", + file_name, + is_time_domain=True, + x_scale=1, + y_scale=1, + impedance=50, + data_format="Power", + encoding="utf-8", + out_mag="Voltage", ): """Parse a csv file and convert data in list that can be applied to Hfss and Hfss3dLayout sources. @@ -1599,7 +1600,7 @@ def _arg2dict(arg, dict_out): i = 1 while i < len(arg): if arg[i][0][:5] == "NAME:" and ( - isinstance(arg[i], (list, tuple)) or str(type(arg[i])) == r"" + isinstance(arg[i], (list, tuple)) or str(type(arg[i])) == r"" ): _arg2dict(list(arg[i]), dict_in) i += 1 @@ -1875,5 +1876,3 @@ def developer_forum(self): # property = Property online_help = Help() - - diff --git a/src/pyedb/grpc/components.py b/src/pyedb/grpc/components.py index db426bb112..926b67d24d 100644 --- a/src/pyedb/grpc/components.py +++ b/src/pyedb/grpc/components.py @@ -434,7 +434,7 @@ def get_component_by_name(self, name): ``True`` when successful, ``False`` when failed. """ - edbcmp = self._pedb.cell.hierarchy.group.find(self._active_layout, name) + edbcmp = hierarchy.ComponentGroup.find(self._edb.layout, name) if edbcmp: return edbcmp else: @@ -1495,12 +1495,12 @@ def create_pingroup_from_pins(self, pins, group_name=None): if _pins: pins = _pins if not group_name: - group_name = hierarchy.PinGroup.unique_name(self._active_layout) + group_name = hierarchy.PinGroup.unique_name(self._edb.layout, "") for pin in pins: pin.is_layout_pin = True forbiden_car = "-><" group_name = group_name.translate({ord(i): "_" for i in forbiden_car}) - for pgroup in list(self._pedb.active_layout.pin_groups): + for pgroup in self._pedb.layout.pin_groups: if pgroup.name == group_name: pin_group_exists = True if len(pgroup.pins) == len(pins): @@ -1510,15 +1510,15 @@ def create_pingroup_from_pins(self, pins, group_name=None): continue else: group_name = self._edb.cell.hierarchy.pin_group.unique_name( - self._active_layout, group_name + self._edb.layout, group_name ) pin_group_exists = False else: - group_name = self._edb.cell.hierarchy.pin_group.unique_name(self._active_layout, group_name) + group_name = hierarchy.PinGroup.unique_name(self._edb.layout, group_name) pin_group_exists = False if pin_group_exists: return pgroup - pingroup = hierarchy.PinGroup.create(self._active_layout, group_name, pins) + pingroup = hierarchy.PinGroup.create(self._edb.layout, group_name, pins) if pingroup.is_null: return False else: @@ -2026,27 +2026,30 @@ def get_pin_from_component(self, component, netName=None, pinName=None): """ if not isinstance(component, hierarchy.ComponentGroup): - component = hierarchy.ComponentGroup.find(self._active_layout, component) + component = hierarchy.ComponentGroup.find(self._edb.layout, component) if netName: if not isinstance(netName, list): netName = [netName] pins = [ p - for p in list(component.layout_objs) - if int(p.obj_type) == 1 and p.is_layout_pin and p.net.name in netName] + for p in component.members if p.layout_obj_type.value == 1 and not p.net.is_null + and p.is_layout_pin and p.net.name in netName] elif pinName: if not isinstance(pinName, list): pinName = [pinName] pins = [ p - for p in list(component.layout_objs) - if int(p.obj_type) == 1 + for p in list(component.members) + if p.layout_obj_type.value == 1 and p.is_layout_pin + and not p.net.is_null and (self.get_aedt_pin_name(p) in pinName or p.name in pinName) ] else: - pins = [p for p in list(component.layout_objs) if int(p.obj_type) == 1 and p.is_layout_pin] - return pins + pins = [p for p in list(component.members) if p.layout_obj_type.value == 1 and not p.net.is_null + and p.is_layout_pin] + + return [EDBPadstackInstance(pin, self._pedb) for pin in pins] @pyedb_function_handler() def get_aedt_pin_name(self, pin): diff --git a/src/pyedb/grpc/edb.py b/src/pyedb/grpc/edb.py index aba618f0bb..324d4939f7 100644 --- a/src/pyedb/grpc/edb.py +++ b/src/pyedb/grpc/edb.py @@ -12,48 +12,48 @@ import traceback import warnings - import pyedb.grpc.edb_core.edb_data as edb_data -from pyedb.grpc.grpc_init.database import EdbGrpc +from pyedb.grpc.grpc_init.database import EdbInit +import ansys.edb.geometry as geometry import ansys.edb.database as database +import ansys.edb.layout_instance as layout_instance import pyedb.generic as generic import ansys.edb.terminal as terminal - from pyedb.grpc.components import Components from pyedb.grpc.edb_core.edb_data.control_file import ControlFile from pyedb.grpc.edb_core.edb_data.control_file import convert_technology_file -#from pyedb.grpc.edb_core.edb_data.design_options import EdbDesignOptions -#from pyedb.grpc.edb_core.edb_data.hfss_simulation_setup_data import HfssSimulationSetup +from pyedb.grpc.edb_core.edb_data.design_options import EdbDesignOptions +from pyedb.grpc.edb_core.edb_data.hfss_simulation_setup_data import HfssSimulationSetup from pyedb.grpc.edb_core.edb_data.ports import BundleWavePort from pyedb.grpc.edb_core.edb_data.ports import CoaxPort from pyedb.grpc.edb_core.edb_data.ports import ExcitationProbes from pyedb.grpc.edb_core.edb_data.ports import ExcitationSources from pyedb.grpc.edb_core.edb_data.ports import GapPort from pyedb.grpc.edb_core.edb_data.ports import WavePort -#from pyedb.grpc.edb_core.edb_data.simulation_configuration import SimulationConfiguration -#from pyedb.grpc.edb_core.edb_data.siwave_simulation_setup_data import SiwaveDCSimulationSetup -#from pyedb.grpc.edb_core.edb_data.siwave_simulation_setup_data import SiwaveSYZSimulationSetup -#from pyedb.grpc.edb_core.edb_data.sources import SourceType -#from pyedb.grpc.edb_core.edb_data.terminals import BundleTerminal -#from pyedb.grpc.edb_core.edb_data.terminals import EdgeTerminal -#from pyedb.grpc.edb_core.edb_data.terminals import PadstackInstanceTerminal -#from pyedb.grpc.edb_core.edb_data.terminals import Terminal -#from pyedb.grpc.edb_core.edb_data.variables import Variable +from pyedb.grpc.edb_core.edb_data.simulation_configuration import SimulationConfiguration +from pyedb.grpc.edb_core.edb_data.siwave_simulation_setup_data import SiwaveDCSimulationSetup +from pyedb.grpc.edb_core.edb_data.siwave_simulation_setup_data import SiwaveSYZSimulationSetup +from pyedb.grpc.edb_core.edb_data.sources import SourceType +from pyedb.grpc.edb_core.edb_data.terminals import BundleTerminal +from pyedb.grpc.edb_core.edb_data.terminals import EdgeTerminal +from pyedb.grpc.edb_core.edb_data.terminals import PadstackInstanceTerminal +from pyedb.grpc.edb_core.edb_data.terminals import Terminal +from pyedb.grpc.edb_core.edb_data.variables import Variable #from pyedb.grpc.general import TerminalType from pyedb.grpc.hfss import EdbHfss from pyedb.ipc2581.ipc2581 import Ipc2581 from pyedb.grpc.layout import EdbLayout from pyedb.grpc.materials import Materials -#from pyedb.grpc.net_class import EdbDifferentialPairs -#from pyedb.grpc.net_class import EdbExtendedNets -#from pyedb.grpc.net_class import EdbNetClasses +from pyedb.grpc.net_class import EdbDifferentialPairs +from pyedb.grpc.net_class import EdbExtendedNets +from pyedb.grpc.net_class import EdbNetClasses from pyedb.grpc.nets import EdbNets from pyedb.grpc.padstack import EdbPadstacks from pyedb.grpc.siwave import EdbSiwave from pyedb.grpc.stackup import Stackup -#from pyedb.generic.constants import AEDT_UNITS -#from pyedb.generic.constants import SolverType +from pyedb.generic.constants import AEDT_UNITS +from pyedb.generic.constants import SolverType from pyedb.generic.general_methods import generate_unique_name from pyedb.generic.general_methods import get_string_version from pyedb.generic.general_methods import inside_desktop @@ -61,12 +61,10 @@ from pyedb.generic.general_methods import is_windows from pyedb.generic.general_methods import pyedb_function_handler from pyedb.modeler.geometry_operators import GeometryOperators -#from pyedb.grpc.grpc_init.database import EdbGrpc as grpc -#from ansys.edb.database import Database import subprocess -class Edb(EdbGrpc): +class EdbGrpc(EdbInit): """Provides the EDB application interface. This module inherits all objects that belong to EDB. @@ -131,20 +129,20 @@ class Edb(EdbGrpc): """ def __init__( - self, - edbpath=None, - cellname=None, - isreadonly=False, - edbversion=None, - isaedtowned=False, - oproject=None, - port=50051, - use_ppe=False, - technology_file=None, + self, + edbpath=None, + cellname=None, + isreadonly=False, + edbversion=None, + isaedtowned=False, + oproject=None, + port=50051, + use_ppe=False, + technology_file=None, ): edbversion = get_string_version(edbversion) self._clean_variables() - EdbGrpc.__init__(self, edbversion=edbversion, port=port) + EdbInit.__init__(self, edbversion=edbversion, port=port) self.standalone = True self.oproject = oproject self._main = sys.modules["__main__"] @@ -255,7 +253,6 @@ def _clean_variables(self): self._components = None self._core_primitives = None self._stackup = None - #self._stackup2 = None self._padstack = None self._siwave = None self._hfss = None @@ -276,10 +273,6 @@ def _init_objects(self): self._siwave = EdbSiwave(self) self._hfss = EdbHfss(self) self._nets = EdbNets(self) - #self._core_primitives = EdbLayout(self) - #self._stackup2 = self._stackup - - @property def cell_names(self): @@ -299,78 +292,77 @@ def active_db(self): def active_cell(self): """Active cell.""" return self._active_cell - # - # @property - # def design_variables(self): - # """Get all edb design variables. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.variables.Variable`] - # """ - # d_var = dict() - # for i in self.active_cell.variable_server.get_all_variable_names(): - # d_var[i] = Variable(self, i) - # return d_var - # - # @property - # def project_variables(self): - # """Get all project variables. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.variables.Variable`] - # - # """ - # p_var = dict() - # for i in self.active_db.variable_server.get_all_variable_names(): - # p_var[i] = Variable(self, i) - # return p_var - # - # @property - # def variables(self): - # """Get all Edb variables. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.variables.Variable`] - # - # """ - # all_vars = dict() - # for i, j in self.project_variables.items(): - # all_vars[i] = j - # for i, j in self.design_variables.items(): - # all_vars[i] = j - # return all_vars - # + + @property + def design_variables(self): + """Get all edb design variables. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.variables.Variable`] + """ + d_var = dict() + for i in self.active_cell.variable_server.get_all_variable_names(): + d_var[i] = Variable(self, i) + return d_var + + @property + def project_variables(self): + """Get all project variables. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.variables.Variable`] + + """ + p_var = dict() + for i in self.active_db.variable_server.get_all_variable_names(): + p_var[i] = Variable(self, i) + return p_var + + @property + def variables(self): + """Get all Edb variables. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.variables.Variable`] + + """ + all_vars = dict() + for i, j in self.project_variables.items(): + all_vars[i] = j + for i, j in self.design_variables.items(): + all_vars[i] = j + return all_vars + @property def terminals(self): """Get terminals belonging to active layout.""" - temp = {} + terminal_dict = {} for i in self.layout.terminals: - terminal_type = i.ToString().split(".")[-1] - if terminal_type == terminal.TerminalType.name: - ter = terminal.EdgeTerminal(self, i) - elif terminal_type == terminal.TerminalType.name: - ter = terminal.BundleTerminal(self, i) - elif terminal_type == terminal.TerminalType.name: - ter = terminal.PadstackInstanceTerminal(self, i) + terminal_type = i.type + if terminal_type == terminal.TerminalType.EDGE: + ter = EdgeTerminal(self, i) + elif not i.bundle_terminal.is_null: + ter = BundleTerminal(self, i) + elif terminal_type == terminal.TerminalType.PADSTACK_INST: + ter = PadstackInstanceTerminal(self, i) else: - ter = terminal.Terminal(self, i) - temp[ter.name] = ter - - return temp + ter = Terminal(self, i) + terminal_dict[ter.name] = ter + return terminal_dict @property def excitations(self): """Get all layout excitations.""" - terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) == 0] + terms = [term for term in self.layout.terminals if term.boundary_type.value == 0] temp = {} - for ter in terms: - if "BundleTerminal" in ter.GetType().ToString(): - temp[ter.GetName()] = BundleWavePort(self, ter) + for term in terms: + if not term.bundle_terminal.is_null: + temp[term.name] = BundleWavePort(self, term) else: - temp[ter.GetName()] = GapPort(self, ter) + temp[term.name] = GapPort(self, term) return temp @property @@ -383,20 +375,18 @@ def ports(self): :class:`pyaedt.edb_core.edb_data.ports.WavePort`,]] """ - temp = [term for term in self.layout.terminals if not term.IsReferenceTerminal()] - + terminals = [term for term in self.layout.terminals if not term.is_reference_terminal] ports = {} - for t in temp: - t2 = terminal.Terminal(self, t) - if t2.terminal_type == terminal.TerminalType.name: + for t in terminals: + if t.type == terminal.TerminalType.BUNDLE: bundle_ter = BundleWavePort(self, t) ports[bundle_ter.name] = bundle_ter - elif t2.hfss_type == "Wave": - ports[t2.name] = WavePort(self, t) - elif t2.terminal_type == terminal.TerminalType.name: - ports[t2.name] = CoaxPort(self, t) + elif t.type == terminal.TerminalType.BUNDLE: + ports[t.name] = WavePort(self, t) + elif t.type == terminal.TerminalType.PADSTACK_INST: + ports[t.name] = CoaxPort(self, t) else: - ports[t2.name] = GapPort(self, t) + ports[t.name] = GapPort(self, t) return ports @property @@ -409,13 +399,13 @@ def excitations_nets(self): @property def sources(self): """Get all layout sources.""" - terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [3, 4, 7]] + terms = [term for term in self.layout.terminals if term.boundary_type.value in [3, 4, 7]] return {ter.name: ExcitationSources(self, ter) for ter in terms} @property def probes(self): """Get all layout sources.""" - terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [8]] + terms = [term for term in self.layout.terminals if term.boundary_type.value == 8] return {ter.name: ExcitationProbes(self, ter) for ter in terms} @pyedb_function_handler() @@ -601,284 +591,267 @@ def modeler(self): self._core_primitives = EdbLayout(self) return self._core_primitives - # @pyedb_function_handler() - # def open_edb_inside_aedt(self): - # """Open EDB inside of AEDT. - # - # Returns - # ------- - # - # """ - # self.logger.info("Opening EDB from HDL") - # self.run_as_standalone(False) - # if self.oproject.GetEDBHandle(): - # self.attach(self.oproject.GetEDBHandle()) - # if not self.active_db: - # self.logger.warning("Error getting the database.") - # self._active_cell = None - # return None - # self._active_cell = self.cell.cell.find( - # self.active_db, self.cell._cell.CellType.CircuitCell, self.cellname - # ) - # if self._active_cell is None: - # self._active_cell = list(self.top_circuit_cells)[0] - # if self._active_cell: - # if not os.path.exists(self.edbpath): - # os.makedirs(self.edbpath) - # self._init_objects() - # return True - # else: - # return None - # else: - # self._active_cell = None - # return None - # - # @pyedb_function_handler() - # def create_edb(self): - # """Create EDB.""" - # # if self.edbversion > "2023.1": - # # self.standalone = False - # - # self.run_as_standalone(self.standalone) - # self.create(self.edbpath) - # if not self.active_db: - # self.logger.warning("Error creating the database.") - # self._active_cell = None - # return None - # if not self.cellname: - # self.cellname = generate_unique_name("Cell") - # self._active_cell = self.edb_api.cell.create( - # self.active_db, self.edb_api.cell.CellType.CircuitCell, self.cellname - # ) - # if self._active_cell: - # self._init_objects() - # return True - # return None - # - # @pyedb_function_handler() - # def import_layout_pcb(self, input_file, working_dir, anstranslator_full_path="", use_ppe=False, control_file=None): - # """Import a board file and generate an ``edb.def`` file in the working directory. - # - # This function supports all AEDT formats, including DXF, GDS, SML (IPC2581), BRD, MCM and TGZ. - # - # Parameters - # ---------- - # input_file : str - # Full path to the board file. - # working_dir : str - # Directory in which to create the ``aedb`` folder. The name given to the AEDB file - # is the same as the name of the board file. - # anstranslator_full_path : str, optional - # Full path to the Ansys translator. The default is ``""``. - # use_ppe : bool - # Whether to use the PPE License. The default is ``False``. - # control_file : str, optional - # Path to the XML file. The default is ``None``, in which case an attempt is made to find - # the XML file in the same directory as the board file. To succeed, the XML file and board file - # must have the same name. Only the extension differs. - # - # Returns - # ------- - # str - # Full path to the AEDB file. - # """ - # self._components = None - # self._core_primitives = None - # self._stackup = None - # self._padstack = None - # self._siwave = None - # self._hfss = None - # self._nets = None - # aedb_name = os.path.splitext(os.path.basename(input_file))[0] + ".aedb" - # if anstranslator_full_path and os.path.exists(anstranslator_full_path): - # command = anstranslator_full_path - # else: - # command = os.path.join(self.base_path, "anstranslator") - # if is_windows: - # command += ".exe" - # - # if not working_dir: - # working_dir = os.path.dirname(input_file) - # cmd_translator = [ - # command, - # input_file, - # os.path.join(working_dir, aedb_name), - # "-l={}".format(os.path.join(working_dir, "Translator.log")), - # ] - # if not use_ppe: - # cmd_translator.append("-ppe=false") - # if control_file and input_file[-3:] not in ["brd", "mcm"]: - # if is_linux: - # cmd_translator.append("-c={}".format(control_file)) - # else: - # cmd_translator.append('-c="{}"'.format(control_file)) - # p = subprocess.Popen(cmd_translator) - # p.wait() - # if not os.path.exists(os.path.join(working_dir, aedb_name)): - # self.logger.error("Translator failed to translate.") - # return False - # else: - # self.logger.info("Translation correctly completed") - # self.edbpath = os.path.join(working_dir, aedb_name) - # return self.open_edb() - # - # @pyedb_function_handler() - # def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER"): - # """Create an XML IPC2581 file from the active EDB. - # - # .. note:: - # The method works only in CPython because of some limitations on Ironpython in XML parsing and - # because it's time-consuming. - # This method is still being tested and may need further debugging. - # Any feedback is welcome. Backdrills and custom pads are not supported yet. - # - # Parameters - # ---------- - # ipc_path : str, optional - # Path to the XML IPC2581 file. The default is ``None``, in which case - # an attempt is made to find the XML IPC2581 file in the same directory - # as the active EDB. To succeed, the XML IPC2581 file and the active - # EDT must have the same name. Only the extension differs. - # units : str, optional - # Units of the XML IPC2581 file. Options are ``"millimeter"``, - # ``"inch"``, and ``"micron"``. The default is ``"millimeter"``. - # - # Returns - # ------- - # bool - # ``True`` if successful, ``False`` if failed. - # """ - # if is_ironpython: # pragma no cover - # self.logger.error("This method is not supported in Ironpython") - # return False - # if units.lower() not in ["millimeter", "inch", "micron"]: # pragma no cover - # self.logger.warning("The wrong unit is entered. Setting to the default, millimeter.") - # units = "millimeter" - # - # if not ipc_path: - # ipc_path = self.edbpath[:-4] + "xml" - # self.logger.info("Export IPC 2581 is starting. This operation can take a while.") - # start = time.time() - # ipc = Ipc2581(self, units) - # ipc.load_ipc_model() - # ipc.file_path = ipc_path - # result = ipc.write_xml() - # - # if result: # pragma no cover - # self.logger.info_timer("Export IPC 2581 completed.", start) - # self.logger.info("File saved as %s", ipc_path) - # return ipc_path - # self.logger.info("Error exporting IPC 2581.") - # return False - # - # def edb_exception(self, ex_value, tb_data): - # """Write the trace stack to AEDT when a Python error occurs. - # - # Parameters - # ---------- - # ex_value : - # - # tb_data : - # - # - # Returns - # ------- - # - # """ - # tb_trace = traceback.format_tb(tb_data) - # tblist = tb_trace[0].split("\n") - # self.logger.error(str(ex_value)) - # for el in tblist: - # self.logger.error(el) - # + @property + def layout(self): + """Layout object. - # + Returns + ------- + :class:`pyaedt.edb_core.dotnet.layout.Layout` + """ + return self.active_cell.layout - # - # @property - # def design_options(self): - # """Edb Design Settings and Options. - # - # Returns - # ------- - # Instance of :class:`pyaedt.edb_core.edb_data.design_options.EdbDesignOptions` - # """ - # return EdbDesignOptions(self.active_cell) - # + @property + def active_layout(self): + """Active layout. - # + Returns + ------- + Instance of EDB API Layout Class. + """ + return self.layout._layout - # + @property + def layout_instance(self): + """Edb Layout Instance.""" + return self.layout.layout_instance - # + @pyedb_function_handler() + def open_edb_inside_aedt(self): + """Open EDB inside of AEDT. - # + Returns + ------- - # + """ + self.logger.info("Opening EDB from HDL") + self.run_as_standalone(False) + if self.oproject.GetEDBHandle(): + self.attach(self.oproject.GetEDBHandle()) + if not self.active_db: + self.logger.warning("Error getting the database.") + self._active_cell = None + return None + self._active_cell = self.cell.cell.find( + self.active_db, self.cell._cell.CellType.CircuitCell, self.cellname + ) + if self._active_cell is None: + self._active_cell = list(self.top_circuit_cells)[0] + if self._active_cell: + if not os.path.exists(self.edbpath): + os.makedirs(self.edbpath) + self._init_objects() + return True + else: + return None + else: + self._active_cell = None + return None - # - # @property - # def extended_nets(self): - # """Get all extended nets. - # - # Returns - # ------- - # :class:`pyaedt.edb_core.nets.EdbExtendedNets` - # - # Examples - # -------- - # >>> edbapp = pyaedt.Edb("myproject.aedb") - # >>> edbapp.extended_nets - # """ - # - # if self.active_db: - # return EdbExtendedNets(self) - # - # @property - # def differential_pairs(self): - # """Get all differential pairs. - # - # Returns - # ------- - # :class:`pyaedt.edb_core.nets.EdbDifferentialPairs` - # - # Examples - # -------- - # >>> edbapp = pyaedt.Edb("myproject.aedb") - # >>> edbapp.differential_pairs - # """ - # if self.active_db: - # return EdbDifferentialPairs(self) - # else: # pragma: no cover - # return - # + @pyedb_function_handler() + def create_edb(self): + """Create EDB.""" + # if self.edbversion > "2023.1": + # self.standalone = False + + self.run_as_standalone(self.standalone) + self.create(self.edbpath) + if not self.active_db: + self.logger.warning("Error creating the database.") + self._active_cell = None + return None + if not self.cellname: + self.cellname = generate_unique_name("Cell") + self._active_cell = self.edb_api.cell.create( + self.active_db, self.edb_api.cell.CellType.CircuitCell, self.cellname + ) + if self._active_cell: + self._init_objects() + return True + return None + + @pyedb_function_handler() + def import_layout_pcb(self, input_file, working_dir, anstranslator_full_path="", use_ppe=False, control_file=None): + """Import a board file and generate an ``edb.def`` file in the working directory. + + This function supports all AEDT formats, including DXF, GDS, SML (IPC2581), BRD, MCM and TGZ. + + Parameters + ---------- + input_file : str + Full path to the board file. + working_dir : str + Directory in which to create the ``aedb`` folder. The name given to the AEDB file + is the same as the name of the board file. + anstranslator_full_path : str, optional + Full path to the Ansys translator. The default is ``""``. + use_ppe : bool + Whether to use the PPE License. The default is ``False``. + control_file : str, optional + Path to the XML file. The default is ``None``, in which case an attempt is made to find + the XML file in the same directory as the board file. To succeed, the XML file and board file + must have the same name. Only the extension differs. + + Returns + ------- + str + Full path to the AEDB file. + """ + self._components = None + self._core_primitives = None + self._stackup = None + self._padstack = None + self._siwave = None + self._hfss = None + self._nets = None + aedb_name = os.path.splitext(os.path.basename(input_file))[0] + ".aedb" + if anstranslator_full_path and os.path.exists(anstranslator_full_path): + command = anstranslator_full_path + else: + command = os.path.join(self.base_path, "anstranslator") + if is_windows: + command += ".exe" + + if not working_dir: + working_dir = os.path.dirname(input_file) + cmd_translator = [ + command, + input_file, + os.path.join(working_dir, aedb_name), + "-l={}".format(os.path.join(working_dir, "Translator.log")), + ] + if not use_ppe: + cmd_translator.append("-ppe=false") + if control_file and input_file[-3:] not in ["brd", "mcm"]: + if is_linux: + cmd_translator.append("-c={}".format(control_file)) + else: + cmd_translator.append('-c="{}"'.format(control_file)) + p = subprocess.Popen(cmd_translator) + p.wait() + if not os.path.exists(os.path.join(working_dir, aedb_name)): + self.logger.error("Translator failed to translate.") + return False + else: + self.logger.info("Translation correctly completed") + self.edbpath = os.path.join(working_dir, aedb_name) + return self.open_edb() + + @pyedb_function_handler() + def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER"): + """Create an XML IPC2581 file from the active EDB. + + .. note:: + The method works only in CPython because of some limitations on Ironpython in XML parsing and + because it's time-consuming. + This method is still being tested and may need further debugging. + Any feedback is welcome. Backdrills and custom pads are not supported yet. + + Parameters + ---------- + ipc_path : str, optional + Path to the XML IPC2581 file. The default is ``None``, in which case + an attempt is made to find the XML IPC2581 file in the same directory + as the active EDB. To succeed, the XML IPC2581 file and the active + EDT must have the same name. Only the extension differs. + units : str, optional + Units of the XML IPC2581 file. Options are ``"millimeter"``, + ``"inch"``, and ``"micron"``. The default is ``"millimeter"``. + + Returns + ------- + bool + ``True`` if successful, ``False`` if failed. + """ + if is_ironpython: # pragma no cover + self.logger.error("This method is not supported in Ironpython") + return False + if units.lower() not in ["millimeter", "inch", "micron"]: # pragma no cover + self.logger.warning("The wrong unit is entered. Setting to the default, millimeter.") + units = "millimeter" + + if not ipc_path: + ipc_path = self.edbpath[:-4] + "xml" + self.logger.info("Export IPC 2581 is starting. This operation can take a while.") + start = time.time() + ipc = Ipc2581(self, units) + ipc.load_ipc_model() + ipc.file_path = ipc_path + result = ipc.write_xml() + + if result: # pragma no cover + self.logger.info_timer("Export IPC 2581 completed.", start) + self.logger.info("File saved as %s", ipc_path) + return ipc_path + self.logger.info("Error exporting IPC 2581.") + return False + + def edb_exception(self, ex_value, tb_data): + """Write the trace stack to AEDT when a Python error occurs. + + Parameters + ---------- + ex_value : + + tb_data : + + + Returns + ------- + + """ + tb_trace = traceback.format_tb(tb_data) + tblist = tb_trace[0].split("\n") + self.logger.error(str(ex_value)) + for el in tblist: + self.logger.error(el) - # @property - def layout(self): - """Layout object. + def design_options(self): + """Edb Design Settings and Options. Returns ------- - :class:`pyaedt.edb_core.dotnet.layout.Layout` + Instance of :class:`pyaedt.edb_core.edb_data.design_options.EdbDesignOptions` """ - return self.active_cell.layout - # - # @property - # def active_layout(self): - # """Active layout. - # - # Returns - # ------- - # Instance of EDB API Layout Class. - # """ - # return self.layout._layout - # + return EdbDesignOptions(self.active_cell) + @property - def layout_instance(self): - """Edb Layout Instance.""" - return self.layout.layout_instance - # + def extended_nets(self): + """Get all extended nets. + + Returns + ------- + :class:`pyaedt.edb_core.nets.EdbExtendedNets` + + Examples + -------- + >>> edbapp = pyaedt.Edb("myproject.aedb") + >>> edbapp.extended_nets + """ + + if self.active_db: + return EdbExtendedNets(self) + + @property + def differential_pairs(self): + """Get all differential pairs. + + Returns + ------- + :class:`pyaedt.edb_core.nets.EdbDifferentialPairs` + + Examples + -------- + >>> edbapp = pyaedt.Edb("myproject.aedb") + >>> edbapp.differential_pairs + """ + if self.active_db: + return EdbDifferentialPairs(self) + else: # pragma: no cover + return + + # class Boundaries: # """Boundaries Enumerator. # @@ -889,2465 +862,2470 @@ def layout_instance(self): # # (Port, Pec, RLC, CurrentSource, VoltageSource, NexximGround, NexximPort, DcTerminal, VoltageProbe) = range(0, 9) # - # @pyedb_function_handler() - # def point_3d(self, x, y, z=0.0): - # """Compute the Edb 3d Point Data. - # - # Parameters - # ---------- - # x : float, int or str - # X value. - # y : float, int or str - # Y value. - # z : float, int or str, optional - # Z value. - # - # Returns - # ------- - # ``Geometry.Point3DData``. - # """ - # return self.edb_api.geometry.point3d_data(x, y, z) - # - # @pyedb_function_handler() - # def point_data(self, x, y=None): - # """Compute the Edb Point Data. - # - # Parameters - # ---------- - # x : float, int or str - # X value. - # y : float, int or str, optional - # Y value. - # - # - # Returns - # ------- - # ``Geometry.PointData``. - # """ - # if y is None: - # return self.edb_api.geometry.point_data(x) - # else: - # return self.edb_api.geometry.point_data(x, y) - # - # @pyedb_function_handler() - # def _is_file_existing_and_released(self, filename): - # if os.path.exists(filename): - # try: - # os.rename(filename, filename + "_") - # os.rename(filename + "_", filename) - # return True - # except OSError as e: - # return False - # else: - # return False - # - # @pyedb_function_handler() - # def _is_file_existing(self, filename): - # if os.path.exists(filename): - # return True - # else: - # return False - # - # @pyedb_function_handler() - # def _wait_for_file_release(self, timeout=30, file_to_release=None): - # if not file_to_release: - # file_to_release = os.path.join(self.edbpath) - # tstart = time.time() - # while True: - # if self._is_file_existing_and_released(file_to_release): - # return True - # elif time.time() - tstart > timeout: - # return False - # else: - # time.sleep(0.250) - # - # @pyedb_function_handler() - # def _wait_for_file_exists(self, timeout=30, file_to_release=None, wait_count=4): - # if not file_to_release: - # file_to_release = os.path.join(self.edbpath) - # tstart = time.time() - # times = 0 - # while True: - # if self._is_file_existing(file_to_release): - # # print 'File is released' - # times += 1 - # if times == wait_count: - # return True - # elif time.time() - tstart > timeout: - # # print 'Timeout reached' - # return False - # else: - # times = 0 - # time.sleep(0.250) - # - # @pyedb_function_handler() - # def close_edb(self): - # """Close EDB and cleanup variables. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # self.close() - # self._global_logger.remove_file_logger(os.path.splitext(os.path.split(self.log_name)[-1])[0]) - # self._logger = self._global_logger - # start_time = time.time() - # self._wait_for_file_release() - # elapsed_time = time.time() - start_time - # self.logger.info("EDB file release time: {0:.2f}ms".format(elapsed_time * 1000.0)) - # self._clean_variables() - # self.session.disconnect() - # return True - # - # @pyedb_function_handler() - # def save_edb(self): - # """Save the EDB file. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # self.save() - # start_time = time.time() - # self._wait_for_file_release() - # elapsed_time = time.time() - start_time - # self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0)) - # return True - # - # @pyedb_function_handler() - # def save_edb_as(self, fname): - # """Save the EDB file as another file. - # - # Parameters - # ---------- - # fname : str - # Name of the new file to save to. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # self.save_as(fname) - # start_time = time.time() - # self._wait_for_file_release() - # elapsed_time = time.time() - start_time - # self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0)) - # self.edbpath = self.directory - # if self.log_name: - # self._global_logger.remove_file_logger(os.path.splitext(os.path.split(self.log_name)[-1])[0]) - # self._logger = self._global_logger - # - # self.log_name = os.path.join( - # os.path.dirname(fname), "pyaedt_" + os.path.splitext(os.path.split(fname)[-1])[0] + ".log" - # ) - # if settings.enable_local_log_file: - # self._logger = self._global_logger.add_file_logger(self.log_name, "Edb") - # return True - # - # @pyedb_function_handler() - # def execute(self, func): - # """Execute a function. - # - # Parameters - # ---------- - # func : str - # Function to execute. - # - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # return self.edb_api.utility.utility.Command.Execute(func) - # - # @pyedb_function_handler() - # def import_cadence_file(self, inputBrd, WorkDir=None, anstranslator_full_path="", use_ppe=False): - # """Import a board file and generate an ``edb.def`` file in the working directory. - # - # Parameters - # ---------- - # inputBrd : str - # Full path to the board file. - # WorkDir : str, optional - # Directory in which to create the ``aedb`` folder. The default value is ``None``, - # in which case the AEDB file is given the same name as the board file. Only - # the extension differs. - # anstranslator_full_path : str, optional - # Full path to the Ansys translator. - # use_ppe : bool, optional - # Whether to use the PPE License. The default is ``False``. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # if self.import_layout_pcb( - # inputBrd, working_dir=WorkDir, anstranslator_full_path=anstranslator_full_path, use_ppe=use_ppe - # ): - # return True - # else: - # return False - # - # @pyedb_function_handler() - # def import_gds_file( - # self, - # inputGDS, - # WorkDir=None, - # anstranslator_full_path="", - # use_ppe=False, - # control_file=None, - # tech_file=None, - # map_file=None, - # ): - # """Import a GDS file and generate an ``edb.def`` file in the working directory. - # - # ..note:: - # `ANSYSLMD_LICENSE_FILE` is needed to run the translator. - # - # Parameters - # ---------- - # inputGDS : str - # Full path to the GDS file. - # WorkDir : str, optional - # Directory in which to create the ``aedb`` folder. The default value is ``None``, - # in which case the AEDB file is given the same name as the GDS file. Only the extension - # differs. - # anstranslator_full_path : str, optional - # Full path to the Ansys translator. - # use_ppe : bool, optional - # Whether to use the PPE License. The default is ``False``. - # control_file : str, optional - # Path to the XML file. The default is ``None``, in which case an attempt is made to find - # the XML file in the same directory as the GDS file. To succeed, the XML file and GDS file must - # have the same name. Only the extension differs. - # tech_file : str, optional - # Technology file. It uses Helic to convert tech file to xml and then imports the gds. Works on Linux only. - # map_file : str, optional - # Layer map file. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # if tech_file or map_file: - # control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml") - # control_file = ControlFile(xml_input=control_file, tecnhology=tech_file, layer_map=map_file).write_xml( - # control_file_temp - # ) - # elif tech_file: - # self.logger.error("Technology files are supported only in Linux. Use control file instead.") - # return False - # if self.import_layout_pcb( - # inputGDS, - # working_dir=WorkDir, - # anstranslator_full_path=anstranslator_full_path, - # use_ppe=use_ppe, - # control_file=control_file, - # ): - # return True - # else: - # return False - # - # @pyedb_function_handler() - # def _create_extent( - # self, - # net_signals, - # extent_type, - # expansion_size, - # use_round_corner, - # use_pyaedt_extent=False, - # smart_cut=False, - # reference_list=[], - # include_pingroups=True, - # ): - # if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1]: - # if use_pyaedt_extent: - # _poly = self._create_conformal( - # net_signals, - # expansion_size, - # 1e-12, - # use_round_corner, - # expansion_size, - # smart_cut, - # reference_list, - # include_pingroups, - # ) - # else: - # _poly = self.layout.expanded_extent( - # net_signals, - # self.edb_api.geometry.extent_type.Conforming, - # expansion_size, - # False, - # use_round_corner, - # 1, - # ) - # elif extent_type in ["Bounding", self.edb_api.geometry.extent_type.BoundingBox, 0]: - # _poly = self.layout.expanded_extent( - # net_signals, self.edb_api.geometry.extent_type.BoundingBox, expansion_size, False, use_round_corner, 1 - # ) - # else: - # if use_pyaedt_extent: - # _poly = self._create_convex_hull( - # net_signals, - # expansion_size, - # 1e-12, - # use_round_corner, - # expansion_size, - # smart_cut, - # reference_list, - # include_pingroups, - # ) - # else: - # _poly = self.layout.expanded_extent( - # net_signals, - # self.edb_api.geometry.extent_type.Conforming, - # expansion_size, - # False, - # use_round_corner, - # 1, - # ) - # _poly_list = convert_py_list_to_net_list([_poly]) - # _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(_poly_list) - # return _poly - # - # @pyedb_function_handler() - # def _create_conformal( - # self, - # net_signals, - # expansion_size, - # tolerance, - # round_corner, - # round_extension, - # smart_cutout=False, - # reference_list=[], - # include_pingroups=True, - # ): - # names = [] - # _polys = [] - # for net in net_signals: - # names.append(net.GetName()) - # for prim in self.modeler.primitives: - # if prim is not None and prim.net_name in names: - # obj_data = prim.primitive_object.GetPolygonData().Expand( - # expansion_size, tolerance, round_corner, round_extension - # ) - # if obj_data: - # _polys.extend(list(obj_data)) - # if smart_cutout: - # _polys.extend(self._smart_cut(net_signals, reference_list, include_pingroups)) - # _poly_unite = self.edb_api.geometry.polygon_data.unite(_polys) - # if len(_poly_unite) == 1: - # return _poly_unite[0] - # else: - # areas = [i.Area() for i in _poly_unite] - # return _poly_unite[areas.index(max(areas))] - # - # @pyedb_function_handler() - # def _smart_cut(self, net_signals, reference_list=[], include_pingroups=True): - # _polys = [] - # terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [0, 3, 4, 7, 8]] - # locations = [] - # for term in terms: - # if term.GetTerminalType().ToString() == "PadstackInstanceTerminal": - # if term.GetParameters()[1].GetNet().GetName() in reference_list: - # locations.append(self.padstacks.instances[term.GetParameters()[1].GetId()].position) - # elif term.GetTerminalType().ToString() == "PointTerminal" and term.GetNet().GetName() in reference_list: - # pd = term.GetParameters()[1] - # locations.append([pd.X.ToDouble(), pd.Y.ToDouble()]) - # if include_pingroups: - # for reference in reference_list: - # for pin in self.nets.nets[reference].padstack_instances: - # if pin.pingroups: - # locations.append(pin.position) - # for point in locations: - # pointA = self.edb_api.geometry.point_data( - # self.edb_value(point[0] - 1e-12), self.edb_value(point[1] - 1e-12) - # ) - # pointB = self.edb_api.geometry.point_data( - # self.edb_value(point[0] + 1e-12), self.edb_value(point[1] + 1e-12) - # ) - # - # points = Tuple[self.edb_api.geometry.geometry.PointData, self.edb_api.geometry.geometry.PointData]( - # pointA, pointB - # ) - # _polys.append(self.edb_api.geometry.polygon_data.create_from_bbox(points)) - # for cname, c in self.components.instances.items(): - # if ( - # set(net_signals).intersection(c.nets) - # and c.is_enabled - # and c.model_type in ["SParameterModel", "SpiceModel", "NetlistModel"] - # ): - # for pin in c.pins: - # locations.append(pin.position) - # return _polys - # - # @pyedb_function_handler() - # def _create_convex_hull( - # self, - # net_signals, - # expansion_size, - # tolerance, - # round_corner, - # round_extension, - # smart_cut=False, - # reference_list=[], - # include_pingroups=True, - # ): - # names = [] - # _polys = [] - # for net in net_signals: - # names.append(net.GetName()) - # for prim in self.modeler.primitives: - # if prim is not None and prim.net_name in names: - # _polys.append(prim.primitive_object.GetPolygonData()) - # if smart_cut: - # _polys.extend(self._smart_cut(net_signals, reference_list, include_pingroups)) - # _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(convert_py_list_to_net_list(_polys)) - # _poly = _poly.Expand(expansion_size, tolerance, round_corner, round_extension)[0] - # return _poly - # - # @pyedb_function_handler() - # def cutout( - # self, - # signal_list=None, - # reference_list=None, - # extent_type="ConvexHull", - # expansion_size=0.002, - # use_round_corner=False, - # output_aedb_path=None, - # open_cutout_at_end=True, - # use_pyaedt_cutout=True, - # number_of_threads=4, - # use_pyaedt_extent_computing=True, - # extent_defeature=0, - # remove_single_pin_components=False, - # custom_extent=None, - # custom_extent_units="mm", - # include_partial_instances=False, - # keep_voids=True, - # check_terminals=False, - # include_pingroups=False, - # expansion_factor=0, - # maximum_iterations=10, - # preserve_components_with_model=False, - # simple_pad_check=True, - # ): - # """Create a cutout using an approach entirely based on PyAEDT. - # This method replaces all legacy cutout methods in PyAEDT. - # It does in sequence: - # - delete all nets not in list, - # - create a extent of the nets, - # - check and delete all vias not in the extent, - # - check and delete all the primitives not in extent, - # - check and intersect all the primitives that intersect the extent. - # - # Parameters - # ---------- - # signal_list : list - # List of signal strings. - # reference_list : list, optional - # List of references to add. The default is ``["GND"]``. - # extent_type : str, optional - # Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and - # ``"Bounding"``. The default is ``"Conforming"``. - # expansion_size : float, str, optional - # Expansion size ratio in meters. The default is ``0.002``. - # use_round_corner : bool, optional - # Whether to use round corners. The default is ``False``. - # output_aedb_path : str, optional - # Full path and name for the new AEDB file. If None, then current aedb will be cutout. - # open_cutout_at_end : bool, optional - # Whether to open the cutout at the end. The default is ``True``. - # use_pyaedt_cutout : bool, optional - # Whether to use new PyAEDT cutout method or EDB API method. - # New method is faster than native API method since it benefits of multithread. - # number_of_threads : int, optional - # Number of thread to use. Default is 4. Valid only if ``use_pyaedt_cutout`` is set to ``True``. - # use_pyaedt_extent_computing : bool, optional - # Whether to use pyaedt extent computing (experimental) or EDB API. - # extent_defeature : float, optional - # Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental). - # It applies only to Conforming bounding box. Default value is ``0`` which disable it. - # remove_single_pin_components : bool, optional - # Remove all Single Pin RLC after the cutout is completed. Default is `False`. - # custom_extent : list - # Points list defining the cutout shape. This setting will override `extent_type` field. - # custom_extent_units : str - # Units of the point list. The default is ``"mm"``. Valid only if `custom_extend` is provided. - # include_partial_instances : bool, optional - # Whether to include padstack instances that have bounding boxes intersecting with point list polygons. - # This operation may slow down the cutout export.Valid only if `custom_extend` and - # `use_pyaedt_cutout` is provided. - # keep_voids : bool - # Boolean used for keep or not the voids intersecting the polygon used for clipping the layout. - # Default value is ``True``, ``False`` will remove the voids.Valid only if `custom_extend` is provided. - # check_terminals : bool, optional - # Whether to check for all reference terminals and increase extent to include them into the cutout. - # This applies to components which have a model (spice, touchstone or netlist) associated. - # include_pingroups : bool, optional - # Whether to check for all pingroups terminals and increase extent to include them into the cutout. - # It requires ``check_terminals``. - # expansion_factor : int, optional - # The method computes a float representing the largest number between - # the dielectric thickness or trace width multiplied by the expansion_factor factor. - # The trace width search is limited to nets with ports attached. Works only if `use_pyaedt_cutout`. - # Default is `0` to disable the search. - # maximum_iterations : int, optional - # Maximum number of iterations before stopping a search for a cutout with an error. - # Default is `10`. - # preserve_components_with_model : bool, optional - # Whether to preserve all pins of components that have associated models (Spice or NPort). - # This parameter is applicable only for a PyAEDT cutout (except point list). - # simple_pad_check : bool, optional - # Whether to use the center of the pad to find the intersection with extent or use the bounding box. - # Second method is much slower and requires to disable multithread on padstack removal. - # Default is `True`. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # Examples - # -------- - # >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2") - # >>> edb.logger.info_timer("Edb Opening") - # >>> edb.logger.reset_timer() - # >>> start = time.time() - # >>> signal_list = [] - # >>> for net in edb.nets.netlist: - # >>> if "3V3" in net: - # >>> signal_list.append(net) - # >>> power_list = ["PGND"] - # >>> edb.cutout(signal_list=signal_list, reference_list=power_list, extent_type="Conforming") - # >>> end_time = str((time.time() - start)/60) - # >>> edb.logger.info("Total pyaedt cutout time in min %s", end_time) - # >>> edb.nets.plot(signal_list, None, color_by_net=True) - # >>> edb.nets.plot(power_list, None, color_by_net=True) - # >>> edb.save_edb() - # >>> edb.close_edb() - # - # - # """ - # if expansion_factor > 0: - # expansion_size = self.calculate_initial_extent(expansion_factor) - # if signal_list is None: - # signal_list = [] - # if isinstance(reference_list, str): - # reference_list = [reference_list] - # elif reference_list is None: - # reference_list = [] - # if not use_pyaedt_cutout and custom_extent: - # return self._create_cutout_on_point_list( - # custom_extent, - # units=custom_extent_units, - # output_aedb_path=output_aedb_path, - # open_cutout_at_end=open_cutout_at_end, - # nets_to_include=signal_list + reference_list, - # include_partial_instances=include_partial_instances, - # keep_voids=keep_voids, - # ) - # elif not use_pyaedt_cutout: - # return self._create_cutout_legacy( - # signal_list=signal_list, - # reference_list=reference_list, - # extent_type=extent_type, - # expansion_size=expansion_size, - # use_round_corner=use_round_corner, - # output_aedb_path=output_aedb_path, - # open_cutout_at_end=open_cutout_at_end, - # use_pyaedt_extent_computing=use_pyaedt_extent_computing, - # check_terminals=check_terminals, - # include_pingroups=include_pingroups, - # ) - # else: - # legacy_path = self.edbpath - # if expansion_factor > 0 and not custom_extent: - # start = time.time() - # self.save_edb() - # dummy_path = self.edbpath.replace(".aedb", "_smart_cutout_temp.aedb") - # working_cutout = False - # i = 1 - # expansion = expansion_size - # while i <= maximum_iterations: - # self.logger.info("-----------------------------------------") - # self.logger.info("Trying cutout with {}mm expansion size".format(expansion * 1e3)) - # self.logger.info("-----------------------------------------") - # result = self._create_cutout_multithread( - # signal_list=signal_list, - # reference_list=reference_list, - # extent_type=extent_type, - # expansion_size=expansion, - # use_round_corner=use_round_corner, - # number_of_threads=number_of_threads, - # custom_extent=custom_extent, - # output_aedb_path=dummy_path, - # remove_single_pin_components=remove_single_pin_components, - # use_pyaedt_extent_computing=use_pyaedt_extent_computing, - # extent_defeature=extent_defeature, - # custom_extent_units=custom_extent_units, - # check_terminals=check_terminals, - # include_pingroups=include_pingroups, - # preserve_components_with_model=preserve_components_with_model, - # include_partial=include_partial_instances, - # simple_pad_check=simple_pad_check, - # ) - # if self.are_port_reference_terminals_connected(): - # if output_aedb_path: - # self.save_edb_as(output_aedb_path) - # else: - # self.save_edb_as(legacy_path) - # working_cutout = True - # break - # self.close_edb() - # self.edbpath = legacy_path - # self.open_edb() - # i += 1 - # expansion = expansion_size * i - # if working_cutout: - # msg = "Cutout completed in {} iterations with expansion size of {}mm".format(i, expansion * 1e3) - # self.logger.info_timer(msg, start) - # else: - # msg = "Cutout failed after {} iterations and expansion size of {}mm".format(i, expansion * 1e3) - # self.logger.info_timer(msg, start) - # return False - # else: - # result = self._create_cutout_multithread( - # signal_list=signal_list, - # reference_list=reference_list, - # extent_type=extent_type, - # expansion_size=expansion_size, - # use_round_corner=use_round_corner, - # number_of_threads=number_of_threads, - # custom_extent=custom_extent, - # output_aedb_path=output_aedb_path, - # remove_single_pin_components=remove_single_pin_components, - # use_pyaedt_extent_computing=use_pyaedt_extent_computing, - # extent_defeature=extent_defeature, - # custom_extent_units=custom_extent_units, - # check_terminals=check_terminals, - # include_pingroups=include_pingroups, - # preserve_components_with_model=preserve_components_with_model, - # include_partial=include_partial_instances, - # simple_pad_check=simple_pad_check, - # ) - # if result and not open_cutout_at_end and self.edbpath != legacy_path: - # self.save_edb() - # self.close_edb() - # self.edbpath = legacy_path - # self.open_edb() - # return result - # - # @pyedb_function_handler() - # def _create_cutout_legacy( - # self, - # signal_list=[], - # reference_list=["GND"], - # extent_type="Conforming", - # expansion_size=0.002, - # use_round_corner=False, - # output_aedb_path=None, - # open_cutout_at_end=True, - # use_pyaedt_extent_computing=False, - # remove_single_pin_components=False, - # check_terminals=False, - # include_pingroups=True, - # ): - # expansion_size = self.edb_value(expansion_size).ToDouble() - # - # # validate nets in layout - # net_signals = [net.api_object for net in self.layout.nets if net.name in signal_list] - # - # # validate references in layout - # _netsClip = convert_py_list_to_net_list( - # [net.api_object for net in self.layout.nets if net.name in reference_list] - # ) - # - # _poly = self._create_extent( - # net_signals, - # extent_type, - # expansion_size, - # use_round_corner, - # use_pyaedt_extent_computing, - # smart_cut=check_terminals, - # reference_list=reference_list, - # include_pingroups=include_pingroups, - # ) - # - # # Create new cutout cell/design - # included_nets_list = signal_list + reference_list - # included_nets = convert_py_list_to_net_list( - # [net.api_object for net in self.layout.nets if net.name in included_nets_list] - # ) - # _cutout = self.active_cell.CutOut(included_nets, _netsClip, _poly, True) - # # Analysis setups do not come over with the clipped design copy, - # # so add the analysis setups from the original here. - # id = 1 - # for _setup in self.active_cell.SimulationSetups: - # # Empty string '' if coming from setup copy and don't set explicitly. - # _setup_name = _setup.GetName() - # if "GetSimSetupInfo" in dir(_setup): - # # setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object - # _hfssSimSetupInfo = _setup.GetSimSetupInfo() - # _hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup - # # Write the simulation setup info into the cell/design setup - # _setup.SetSimSetupInfo(_hfssSimSetupInfo) - # _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design - # id += 1 - # else: - # _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design - # - # _dbCells = [_cutout] - # - # if output_aedb_path: - # db2 = self.create(output_aedb_path) - # _success = db2.Save() - # _dbCells = convert_py_list_to_net_list(_dbCells) - # db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project - # if len(list(db2.CircuitCells)) > 0: - # for net in list(list(db2.CircuitCells)[0].GetLayout().Nets): - # if not net.GetName() in included_nets_list: - # net.Delete() - # _success = db2.Save() - # for c in list(self.active_db.TopCircuitCells): - # if c.GetName() == _cutout.GetName(): - # c.Delete() - # if open_cutout_at_end: # pragma: no cover - # self._db = db2 - # self.edbpath = output_aedb_path - # self._active_cell = list(self.top_circuit_cells)[0] - # self.edbpath = self.directory - # self._init_objects() - # if remove_single_pin_components: - # self.components.delete_single_pin_rlc() - # self.logger.info_timer("Single Pins components deleted") - # self.components.refresh_components() - # else: - # if remove_single_pin_components: - # try: - # layout = list(db2.CircuitCells)[0].GetLayout() - # _cmps = [ - # l - # for l in layout.Groups - # if l.ToString() == "Ansys.Ansoft.Edb.Cell.Hierarchy.Component" and l.GetNumberOfPins() < 2 - # ] - # for _cmp in _cmps: - # _cmp.Delete() - # except: - # self._logger.error("Failed to remove single pin components.") - # db2.Close() - # source = os.path.join(output_aedb_path, "edb.def.tmp") - # target = os.path.join(output_aedb_path, "edb.def") - # self._wait_for_file_release(file_to_release=output_aedb_path) - # if os.path.exists(source) and not os.path.exists(target): - # try: - # shutil.copy(source, target) - # except: - # pass - # elif open_cutout_at_end: - # self._active_cell = _cutout - # self._init_objects() - # if remove_single_pin_components: - # self.components.delete_single_pin_rlc() - # self.logger.info_timer("Single Pins components deleted") - # self.components.refresh_components() - # return True - # - # @pyedb_function_handler() - # def create_cutout( - # self, - # signal_list=[], - # reference_list=["GND"], - # extent_type="Conforming", - # expansion_size=0.002, - # use_round_corner=False, - # output_aedb_path=None, - # open_cutout_at_end=True, - # use_pyaedt_extent_computing=False, - # ): - # """Create a cutout using an approach entirely based on pyaedt. - # It does in sequence: - # - delete all nets not in list, - # - create an extent of the nets, - # - check and delete all vias not in the extent, - # - check and delete all the primitives not in extent, - # - check and intersect all the primitives that intersect the extent. - # - # .. deprecated:: 0.6.58 - # Use new method :func:`cutout` instead. - # - # Parameters - # ---------- - # signal_list : list - # List of signal strings. - # reference_list : list, optional - # List of references to add. The default is ``["GND"]``. - # extent_type : str, optional - # Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and - # ``"Bounding"``. The default is ``"Conforming"``. - # expansion_size : float, str, optional - # Expansion size ratio in meters. The default is ``0.002``. - # use_round_corner : bool, optional - # Whether to use round corners. The default is ``False``. - # output_aedb_path : str, optional - # Full path and name for the new AEDB file. - # open_cutout_at_end : bool, optional - # Whether to open the cutout at the end. The default - # is ``True``. - # use_pyaedt_extent_computing : bool, optional - # Whether to use pyaedt extent computing (experimental). - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # warnings.warn("Use new method `cutout` instead.", DeprecationWarning) - # return self._create_cutout_legacy( - # signal_list=signal_list, - # reference_list=reference_list, - # extent_type=extent_type, - # expansion_size=expansion_size, - # use_round_corner=use_round_corner, - # output_aedb_path=output_aedb_path, - # open_cutout_at_end=open_cutout_at_end, - # use_pyaedt_extent_computing=use_pyaedt_extent_computing, - # ) - # - # @pyedb_function_handler() - # def _create_cutout_multithread( - # self, - # signal_list=[], - # reference_list=["GND"], - # extent_type="Conforming", - # expansion_size=0.002, - # use_round_corner=False, - # number_of_threads=4, - # custom_extent=None, - # output_aedb_path=None, - # remove_single_pin_components=False, - # use_pyaedt_extent_computing=False, - # extent_defeature=0.0, - # custom_extent_units="mm", - # check_terminals=False, - # include_pingroups=True, - # preserve_components_with_model=False, - # include_partial=False, - # simple_pad_check=True, - # ): - # if is_ironpython: # pragma: no cover - # self.logger.error("Method working only in Cpython") - # return False - # from concurrent.futures import ThreadPoolExecutor - # - # if output_aedb_path: - # self.save_edb_as(output_aedb_path) - # self.logger.info("Cutout Multithread started.") - # expansion_size = self.edb_value(expansion_size).ToDouble() - # - # timer_start = self.logger.reset_timer() - # if custom_extent: - # if not reference_list and not signal_list: - # reference_list = self.nets.netlist[::] - # all_list = reference_list - # else: - # reference_list = reference_list + signal_list - # all_list = reference_list - # else: - # all_list = signal_list + reference_list - # pins_to_preserve = [] - # nets_to_preserve = [] - # if preserve_components_with_model: - # for el in self.components.instances.values(): - # if el.model_type in ["SPICEModel", "SParameterModel", "NetlistModel"] and list( - # set(el.nets[:]) & set(signal_list[:]) - # ): - # pins_to_preserve.extend([i.id for i in el.pins.values()]) - # nets_to_preserve.extend(el.nets) - # - # for i in self.nets.nets.values(): - # name = i.name - # if name not in all_list and name not in nets_to_preserve: - # i.net_object.Delete() - # reference_pinsts = [] - # reference_prims = [] - # for i in self.padstacks.instances.values(): - # net_name = i.net_name - # id = i.id - # if net_name not in all_list and id not in pins_to_preserve: - # i.delete() - # elif net_name in reference_list and id not in pins_to_preserve: - # reference_pinsts.append(i) - # for i in self.modeler.primitives: - # if i: - # net_name = i.net_name - # if net_name not in all_list: - # i.delete() - # elif net_name in reference_list and not i.is_void: - # reference_prims.append(i) - # self.logger.info_timer("Net clean up") - # self.logger.reset_timer() - # - # if custom_extent and isinstance(custom_extent, list): - # if custom_extent[0] != custom_extent[-1]: - # custom_extent.append(custom_extent[0]) - # custom_extent = [ - # [self.number_with_units(i[0], custom_extent_units), self.number_with_units(i[1], custom_extent_units)] - # for i in custom_extent - # ] - # plane = self.modeler.Shape("polygon", points=custom_extent) - # _poly = self.modeler.shape_to_polygon_data(plane) - # elif custom_extent: - # _poly = custom_extent - # else: - # net_signals = [net.api_object for net in self.layout.nets if net.name in signal_list] - # _poly = self._create_extent( - # net_signals, - # extent_type, - # expansion_size, - # use_round_corner, - # use_pyaedt_extent_computing, - # smart_cut=check_terminals, - # reference_list=reference_list, - # include_pingroups=include_pingroups, - # ) - # if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1] and extent_defeature > 0: - # _poly = _poly.Defeature(extent_defeature) - # - # if not _poly or _poly.IsNull(): - # self._logger.error("Failed to create Extent.") - # return False - # self.logger.info_timer("Expanded Net Polygon Creation") - # self.logger.reset_timer() - # _poly_list = convert_py_list_to_net_list([_poly]) - # prims_to_delete = [] - # poly_to_create = [] - # pins_to_delete = [] - # - # def intersect(poly1, poly2): - # if not isinstance(poly2, list): - # poly2 = [poly2] - # return list(poly1.Intersect(convert_py_list_to_net_list(poly1), convert_py_list_to_net_list(poly2))) - # - # def subtract(poly, voids): - # return poly.Subtract(convert_py_list_to_net_list(poly), convert_py_list_to_net_list(voids)) - # - # def clean_prim(prim_1): # pragma: no cover - # pdata = prim_1.polygon_data.edb_api - # int_data = _poly.GetIntersectionType(pdata) - # if int_data == 2: - # return - # elif int_data == 0: - # prims_to_delete.append(prim_1) - # else: - # list_poly = intersect(_poly, pdata) - # if list_poly: - # net = prim_1.net_name - # voids = prim_1.voids - # for p in list_poly: - # if p.IsNull(): - # continue - # # points = list(p.Points) - # list_void = [] - # if voids: - # voids_data = [void.polygon_data.edb_api for void in voids] - # list_prims = subtract(p, voids_data) - # for prim in list_prims: - # if not prim.IsNull(): - # poly_to_create.append([prim, prim_1.layer_name, net, list_void]) - # else: - # poly_to_create.append([p, prim_1.layer_name, net, list_void]) - # - # prims_to_delete.append(prim_1) - # - # def pins_clean(pinst): - # if not pinst.in_polygon(_poly, include_partial=include_partial, simple_check=simple_pad_check): - # pins_to_delete.append(pinst) - # - # if not simple_pad_check: - # pad_cores = 1 - # else: - # pad_cores = number_of_threads - # with ThreadPoolExecutor(pad_cores) as pool: - # pool.map(lambda item: pins_clean(item), reference_pinsts) - # - # for pin in pins_to_delete: - # pin.delete() - # - # self.logger.info_timer("Padstack Instances removal completed") - # self.logger.reset_timer() - # - # with ThreadPoolExecutor(number_of_threads) as pool: - # pool.map(lambda item: clean_prim(item), reference_prims) - # - # for el in poly_to_create: - # self.modeler.create_polygon(el[0], el[1], net_name=el[2], voids=el[3]) - # - # for prim in prims_to_delete: - # prim.delete() - # self.logger.info_timer("Primitives cleanup completed") - # self.logger.reset_timer() - # - # i = 0 - # for _, val in self.components.components.items(): - # if val.numpins == 0: - # val.edbcomponent.Delete() - # i += 1 - # i += 1 - # self.logger.info("Deleted {} additional components".format(i)) - # if remove_single_pin_components: - # self.components.delete_single_pin_rlc() - # self.logger.info_timer("Single Pins components deleted") - # - # self.components.refresh_components() - # if output_aedb_path: - # self.save_edb() - # self.logger.info_timer("Cutout completed.", timer_start) - # self.logger.reset_timer() - # return True - # - # @pyedb_function_handler() - # def create_cutout_multithread( - # self, - # signal_list=[], - # reference_list=["GND"], - # extent_type="Conforming", - # expansion_size=0.002, - # use_round_corner=False, - # number_of_threads=4, - # custom_extent=None, - # output_aedb_path=None, - # remove_single_pin_components=False, - # use_pyaedt_extent_computing=False, - # extent_defeature=0, - # ): - # """Create a cutout using an approach entirely based on pyaedt. - # It does in sequence: - # - delete all nets not in list, - # - create a extent of the nets, - # - check and delete all vias not in the extent, - # - check and delete all the primitives not in extent, - # - check and intersect all the primitives that intersect the extent. - # - # - # .. deprecated:: 0.6.58 - # Use new method :func:`cutout` instead. - # - # Parameters - # ---------- - # signal_list : list - # List of signal strings. - # reference_list : list, optional - # List of references to add. The default is ``["GND"]``. - # extent_type : str, optional - # Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and - # ``"Bounding"``. The default is ``"Conforming"``. - # expansion_size : float, str, optional - # Expansion size ratio in meters. The default is ``0.002``. - # use_round_corner : bool, optional - # Whether to use round corners. The default is ``False``. - # number_of_threads : int, optional - # Number of thread to use. Default is 4 - # custom_extent : list, optional - # Custom extent to use for the cutout. It has to be a list of points [[x1,y1],[x2,y2]....] or - # Edb PolygonData object. In this case, both signal_list and reference_list will be cut. - # output_aedb_path : str, optional - # Full path and name for the new AEDB file. If None, then current aedb will be cutout. - # remove_single_pin_components : bool, optional - # Remove all Single Pin RLC after the cutout is completed. Default is `False`. - # use_pyaedt_extent_computing : bool, optional - # Whether to use pyaedt extent computing (experimental). - # extent_defeature : float, optional - # Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental). - # It applies only to Conforming bounding box. Default value is ``0`` which disable it. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # Examples - # -------- - # >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2") - # >>> edb.logger.info_timer("Edb Opening") - # >>> edb.logger.reset_timer() - # >>> start = time.time() - # >>> signal_list = [] - # >>> for net in edb.nets.nets.keys(): - # >>> if "3V3" in net: - # >>> signal_list.append(net) - # >>> power_list = ["PGND"] - # >>> edb.create_cutout_multithread(signal_list=signal_list, reference_list=power_list, extent_type="Conforming") - # >>> end_time = str((time.time() - start)/60) - # >>> edb.logger.info("Total pyaedt cutout time in min %s", end_time) - # >>> edb.nets.plot(signal_list, None, color_by_net=True) - # >>> edb.nets.plot(power_list, None, color_by_net=True) - # >>> edb.save_edb() - # >>> edb.close_edb() - # - # """ - # warnings.warn("Use new method `cutout` instead.", DeprecationWarning) - # return self._create_cutout_multithread( - # signal_list=signal_list, - # reference_list=reference_list, - # extent_type=extent_type, - # expansion_size=expansion_size, - # use_round_corner=use_round_corner, - # number_of_threads=number_of_threads, - # custom_extent=custom_extent, - # output_aedb_path=output_aedb_path, - # remove_single_pin_components=remove_single_pin_components, - # use_pyaedt_extent_computing=use_pyaedt_extent_computing, - # extent_defeature=extent_defeature, - # ) - # - # @pyedb_function_handler() - # def get_conformal_polygon_from_netlist(self, netlist=None): - # """Return an EDB conformal polygon based on a netlist. - # - # Parameters - # ---------- - # - # netlist : List of net names. - # list[str] - # - # Returns - # ------- - # :class:`Edb.Cell.Primitive.Polygon` - # Edb polygon object. - # - # """ - # temp_edb_path = self.edbpath[:-5] + "_temp_aedb.aedb" - # shutil.copytree(self.edbpath, temp_edb_path) - # temp_edb = Edb(temp_edb_path) - # for via in list(temp_edb.padstacks.instances.values()): - # via.pin.Delete() - # if netlist: - # nets = [net.net_obj for net in temp_edb.layout.nets if net.name in netlist] - # _poly = temp_edb.layout.expanded_extent( - # nets, self.edb_api.geometry.extent_type.Conforming, 0.0, True, True, 1 - # ) - # else: - # nets = [net.api_object for net in temp_edb.layout.nets if "gnd" in net.name.lower()] - # _poly = temp_edb.layout.expanded_extent( - # nets, self.edb_api.geometry.extent_type.Conforming, 0.0, True, True, 1 - # ) - # temp_edb.close_edb() - # if _poly: - # return _poly - # else: - # return False - # - # @pyedb_function_handler() - # def number_with_units(self, value, units=None): - # """Convert a number to a string with units. If value is a string, it's returned as is. - # - # Parameters - # ---------- - # value : float, int, str - # Input number or string. - # units : optional - # Units for formatting. The default is ``None``, which uses ``"meter"``. - # - # Returns - # ------- - # str - # String concatenating the value and unit. - # - # """ - # if units is None: - # units = "meter" - # if isinstance(value, str): - # return value - # else: - # return "{0}{1}".format(value, units) - # - # @pyedb_function_handler() - # def arg_with_dim(self, Value, sUnits): - # """Convert a number to a string with units. If value is a string, it's returned as is. - # - # .. deprecated:: 0.6.56 - # Use :func:`number_with_units` property instead. - # - # Parameters - # ---------- - # Value : float, int, str - # Input number or string. - # sUnits : optional - # Units for formatting. The default is ``None``, which uses ``"meter"``. - # - # Returns - # ------- - # str - # String concatenating the value and unit. - # - # """ - # warnings.warn("Use :func:`number_with_units` instead.", DeprecationWarning) - # return self.number_with_units(Value, sUnits) - # - # def _decompose_variable_value(self, value, unit_system=None): - # val, units = decompose_variable_value(value) - # if units and unit_system and units in AEDT_UNITS[unit_system]: - # return AEDT_UNITS[unit_system][units] * val - # else: - # return val - # - # @pyedb_function_handler() - # def _create_cutout_on_point_list( - # self, - # point_list, - # units="mm", - # output_aedb_path=None, - # open_cutout_at_end=True, - # nets_to_include=None, - # include_partial_instances=False, - # keep_voids=True, - # ): - # if point_list[0] != point_list[-1]: - # point_list.append(point_list[0]) - # point_list = [[self.number_with_units(i[0], units), self.number_with_units(i[1], units)] for i in point_list] - # plane = self.modeler.Shape("polygon", points=point_list) - # polygonData = self.modeler.shape_to_polygon_data(plane) - # _ref_nets = [] - # if nets_to_include: - # self.logger.info("Creating cutout on {} nets.".format(len(nets_to_include))) - # else: - # self.logger.info("Creating cutout on all nets.") # pragma: no cover - # - # # Check Padstack Instances overlapping the cutout - # pinstance_to_add = [] - # if include_partial_instances: - # if nets_to_include: - # pinst = [i for i in list(self.padstacks.instances.values()) if i.net_name in nets_to_include] - # else: - # pinst = [i for i in list(self.padstacks.instances.values())] - # for p in pinst: - # if p.in_polygon(polygonData): - # pinstance_to_add.append(p) - # # validate references in layout - # for _ref in self.nets.nets: - # if nets_to_include: - # if _ref in nets_to_include: - # _ref_nets.append(self.nets.nets[_ref].net_object) - # else: - # _ref_nets.append(self.nets.nets[_ref].net_object) # pragma: no cover - # if keep_voids: - # voids = [p for p in self.modeler.circles if p.is_void] - # voids2 = [p for p in self.modeler.polygons if p.is_void] - # voids.extend(voids2) - # else: - # voids = [] - # voids_to_add = [] - # for circle in voids: - # if polygonData.GetIntersectionType(circle.primitive_object.GetPolygonData()) >= 3: - # voids_to_add.append(circle) - # - # _netsClip = convert_py_list_to_net_list(_ref_nets) - # # net_signals = convert_py_list_to_net_list([], type(_ref_nets[0])) - # - # # Create new cutout cell/design - # _cutout = self.active_cell.CutOut(_netsClip, _netsClip, polygonData) - # layout = _cutout.GetLayout() - # cutout_obj_coll = list(layout.PadstackInstances) - # ids = [] - # for lobj in cutout_obj_coll: - # ids.append(lobj.GetId()) - # - # if include_partial_instances: - # p_missing = [i for i in pinstance_to_add if i.id not in ids] - # self.logger.info("Added {} padstack instances after cutout".format(len(p_missing))) - # for p in p_missing: - # position = self.edb_api.geometry.point_data( - # self.edb_value(p.position[0]), self.edb_value(p.position[1]) - # ) - # net = self.nets.find_or_create_net(p.net_name) - # rotation = self.edb_value(p.rotation) - # sign_layers = list(self.stackup.signal_layers.keys()) - # if not p.start_layer: # pragma: no cover - # fromlayer = self.stackup.signal_layers[sign_layers[0]]._edb_layer - # else: - # fromlayer = self.stackup.signal_layers[p.start_layer]._edb_layer - # - # if not p.stop_layer: # pragma: no cover - # tolayer = self.stackup.signal_layers[sign_layers[-1]]._edb_layer - # else: - # tolayer = self.stackup.signal_layers[p.stop_layer]._edb_layer - # padstack = None - # for pad in list(self.padstacks.definitions.keys()): - # if pad == p.padstack_definition: - # padstack = self.padstacks.definitions[pad].edb_padstack - # padstack_instance = self.edb_api.cell.primitive.padstack_instance.create( - # _cutout.GetLayout(), - # net, - # p.name, - # padstack, - # position, - # rotation, - # fromlayer, - # tolayer, - # None, - # None, - # ) - # padstack_instance.SetIsLayoutPin(p.is_pin) - # break - # - # for void_circle in voids_to_add: - # if void_circle.type == "Circle": - # if is_ironpython: # pragma: no cover - # res, center_x, center_y, radius = void_circle.primitive_object.GetParameters() - # else: - # res, center_x, center_y, radius = void_circle.primitive_object.GetParameters(0.0, 0.0, 0.0) - # cloned_circle = self.edb_api.cell.primitive.circle.create( - # layout, - # void_circle.layer_name, - # void_circle.net, - # self.edb_value(center_x), - # self.edb_value(center_y), - # self.edb_value(radius), - # ) - # cloned_circle.SetIsNegative(True) - # elif void_circle.type == "Polygon": - # cloned_polygon = self.edb_api.cell.primitive.polygon.create( - # layout, void_circle.layer_name, void_circle.net, void_circle.primitive_object.GetPolygonData() - # ) - # cloned_polygon.SetIsNegative(True) - # layers = [i for i in list(self.stackup.signal_layers.keys())] - # for layer in layers: - # layer_primitves = self.modeler.get_primitives(layer_name=layer) - # if len(layer_primitves) == 0: - # self.modeler.create_polygon(plane, layer, net_name="DUMMY") - # self.logger.info("Cutout %s created correctly", _cutout.GetName()) - # id = 1 - # for _setup in self.active_cell.SimulationSetups: - # # Empty string '' if coming from setup copy and don't set explicitly. - # _setup_name = _setup.GetName() - # if "GetSimSetupInfo" in dir(_setup): - # # setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object - # _hfssSimSetupInfo = _setup.GetSimSetupInfo() - # _hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup - # # Write the simulation setup info into the cell/design setup - # _setup.SetSimSetupInfo(_hfssSimSetupInfo) - # _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design - # id += 1 - # else: - # _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design - # - # _dbCells = [_cutout] - # if output_aedb_path: - # db2 = self.create(output_aedb_path) - # if not db2.Save(): - # self.logger.error("Failed to create new Edb. Check if the path already exists and remove it.") - # return False - # _dbCells = convert_py_list_to_net_list(_dbCells) - # cell_copied = db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project - # cell = list(cell_copied)[0] - # cell.SetName(os.path.basename(output_aedb_path[:-5])) - # db2.Save() - # for c in list(self.active_db.TopCircuitCells): - # if c.GetName() == _cutout.GetName(): - # c.Delete() - # if open_cutout_at_end: # pragma: no cover - # _success = db2.Save() - # self._db = db2 - # self.edbpath = output_aedb_path - # self._active_cell = cell - # self.edbpath = self.directory - # self._init_objects() - # else: - # db2.Close() - # source = os.path.join(output_aedb_path, "edb.def.tmp") - # target = os.path.join(output_aedb_path, "edb.def") - # self._wait_for_file_release(file_to_release=output_aedb_path) - # if os.path.exists(source) and not os.path.exists(target): - # try: - # shutil.copy(source, target) - # self.logger.warning("aedb def file manually created.") - # except: - # pass - # return True - # - # @pyedb_function_handler() - # def create_cutout_on_point_list( - # self, - # point_list, - # units="mm", - # output_aedb_path=None, - # open_cutout_at_end=True, - # nets_to_include=None, - # include_partial_instances=False, - # keep_voids=True, - # ): - # """Create a cutout on a specified shape and save it to a new AEDB file. - # - # .. deprecated:: 0.6.58 - # Use new method :func:`cutout` instead. - # - # Parameters - # ---------- - # point_list : list - # Points list defining the cutout shape. - # units : str - # Units of the point list. The default is ``"mm"``. - # output_aedb_path : str, optional - # Full path and name for the new AEDB file. - # The aedb folder shall not exist otherwise the method will return ``False``. - # open_cutout_at_end : bool, optional - # Whether to open the cutout at the end. The default is ``True``. - # nets_to_include : list, optional - # List of nets to include in the cutout. The default is ``None``, in - # which case all nets are included. - # include_partial_instances : bool, optional - # Whether to include padstack instances that have bounding boxes intersecting with point list polygons. - # This operation may slow down the cutout export. - # keep_voids : bool - # Boolean used for keep or not the voids intersecting the polygon used for clipping the layout. - # Default value is ``True``, ``False`` will remove the voids. - # - # Returns - # ------- - # bool - # ``True`` when successful, ``False`` when failed. - # - # """ - # warnings.warn("Use new method `cutout` instead.", DeprecationWarning) - # return self._create_cutout_on_point_list( - # point_list=point_list, - # units=units, - # output_aedb_path=output_aedb_path, - # open_cutout_at_end=open_cutout_at_end, - # nets_to_include=nets_to_include, - # include_partial_instances=include_partial_instances, - # keep_voids=keep_voids, - # ) - # - # @pyedb_function_handler() - # def write_export3d_option_config_file(self, path_to_output, config_dictionaries=None): - # """Write the options for a 3D export to a configuration file. - # - # Parameters - # ---------- - # path_to_output : str - # Full path to the configuration file to save 3D export options to. - # - # config_dictionaries : dict, optional - # Configuration dictionaries. The default is ``None``. - # - # """ - # option_config = { - # "UNITE_NETS": 1, - # "ASSIGN_SOLDER_BALLS_AS_SOURCES": 0, - # "Q3D_MERGE_SOURCES": 0, - # "Q3D_MERGE_SINKS": 0, - # "CREATE_PORTS_FOR_PWR_GND_NETS": 0, - # "PORTS_FOR_PWR_GND_NETS": 0, - # "GENERATE_TERMINALS": 0, - # "SOLVE_CAPACITANCE": 0, - # "SOLVE_DC_RESISTANCE": 0, - # "SOLVE_DC_INDUCTANCE_RESISTANCE": 1, - # "SOLVE_AC_INDUCTANCE_RESISTANCE": 0, - # "CreateSources": 0, - # "CreateSinks": 0, - # "LAUNCH_Q3D": 0, - # "LAUNCH_HFSS": 0, - # } - # if config_dictionaries: - # for el, val in config_dictionaries.items(): - # option_config[el] = val - # with open(os.path.join(path_to_output, "options.config"), "w") as f: - # for el, val in option_config.items(): - # f.write(el + " " + str(val) + "\n") - # return os.path.join(path_to_output, "options.config") - # - # @pyedb_function_handler() - # def export_hfss(self, path_to_output, net_list=None, num_cores=None, aedt_file_name=None, hidden=False): - # """Export EDB to HFSS. - # - # Parameters - # ---------- - # path_to_output : str - # Full path and name for saving the AEDT file. - # net_list : list, optional - # List of nets to export if only certain ones are to be exported. - # The default is ``None``, in which case all nets are eported. - # num_cores : int, optional - # Number of cores to use for the export. The default is ``None``. - # aedt_file_name : str, optional - # Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``, - # in which case the default name is used. - # hidden : bool, optional - # Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden. - # - # Returns - # ------- - # str - # Full path to the AEDT file. - # - # Examples - # -------- - # - # >>> from pyedb import Edb - # - # >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2") - # - # >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0} - # >>> edb.write_export3d_option_config_file(r"C:\temp", options_config) - # >>> edb.export_hfss(r"C:\temp") - # "C:\\temp\\hfss_siwave.aedt" - # - # """ - # siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path) - # return siwave_s.export_3d_cad("HFSS", path_to_output, net_list, num_cores, aedt_file_name, hidden=hidden) - # - # @pyedb_function_handler() - # def export_q3d(self, path_to_output, net_list=None, num_cores=None, aedt_file_name=None, hidden=False): - # """Export EDB to Q3D. - # - # Parameters - # ---------- - # path_to_output : str - # Full path and name for saving the AEDT file. - # net_list : list, optional - # List of nets to export only if certain ones are to be exported. - # The default is ``None``, in which case all nets are eported. - # num_cores : int, optional - # Number of cores to use for the export. The default is ``None``. - # aedt_file_name : str, optional - # Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``, - # in which case the default name is used. - # hidden : bool, optional - # Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden. - # - # Returns - # ------- - # str - # Full path to the AEDT file. - # - # Examples - # -------- - # - # >>> from pyaedt import Edb - # - # >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2") - # - # >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0} - # >>> edb.write_export3d_option_config_file(r"C:\temp", options_config) - # >>> edb.export_q3d(r"C:\temp") - # "C:\\temp\\q3d_siwave.aedt" - # - # """ - # - # siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path) - # return siwave_s.export_3d_cad( - # "Q3D", path_to_output, net_list, num_cores=num_cores, aedt_file_name=aedt_file_name, hidden=hidden - # ) - # - # @pyedb_function_handler() - # def export_maxwell(self, path_to_output, net_list=None, num_cores=None, aedt_file_name=None, hidden=False): - # """Export EDB to Maxwell 3D. - # - # Parameters - # ---------- - # path_to_output : str - # Full path and name for saving the AEDT file. - # net_list : list, optional - # List of nets to export only if certain ones are to be - # exported. The default is ``None``, in which case all nets are exported. - # num_cores : int, optional - # Number of cores to use for the export. The default is ``None.`` - # aedt_file_name : str, optional - # Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``, - # in which case the default name is used. - # hidden : bool, optional - # Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden. - # - # Returns - # ------- - # str - # Full path to the AEDT file. - # - # Examples - # -------- - # - # >>> from pyaedt import Edb - # - # >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2") - # - # >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0} - # >>> edb.write_export3d_option_config_file(r"C:\temp", options_config) - # >>> edb.export_maxwell(r"C:\temp") - # "C:\\temp\\maxwell_siwave.aedt" - # - # """ - # siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path) - # return siwave_s.export_3d_cad( - # "Maxwell", - # path_to_output, - # net_list, - # num_cores=num_cores, - # aedt_file_name=aedt_file_name, - # hidden=hidden, - # ) - # - # @pyedb_function_handler() - # def solve_siwave(self): - # """Close EDB and solve it with Siwave. - # - # Returns - # ------- - # str - # Siwave project path. - # """ - # process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion) - # try: - # self.close() - # except: - # pass - # process.solve() - # return self.edbpath[:-5] + ".siw" - # - # @pyedb_function_handler() - # def export_siwave_dc_results( - # self, - # siwave_project, - # solution_name, - # output_folder=None, - # html_report=True, - # vias=True, - # voltage_probes=True, - # current_sources=True, - # voltage_sources=True, - # power_tree=True, - # loop_res=True, - # ): - # """Close EDB and solve it with Siwave. - # - # Parameters - # ---------- - # siwave_project : str - # Siwave full project name. - # solution_name : str - # Siwave DC Analysis name. - # output_folder : str, optional - # Ouptu folder where files will be downloaded. - # html_report : bool, optional - # Either if generate or not html report. Default is `True`. - # vias : bool, optional - # Either if generate or not vias report. Default is `True`. - # voltage_probes : bool, optional - # Either if generate or not voltage probe report. Default is `True`. - # current_sources : bool, optional - # Either if generate or not current source report. Default is `True`. - # voltage_sources : bool, optional - # Either if generate or not voltage source report. Default is `True`. - # power_tree : bool, optional - # Either if generate or not power tree image. Default is `True`. - # loop_res : bool, optional - # Either if generate or not loop resistance report. Default is `True`. - # - # Returns - # ------- - # list - # List of files generated. - # """ - # process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion) - # try: - # self.close() - # except: - # pass - # return process.export_dc_report( - # siwave_project, - # solution_name, - # output_folder, - # html_report, - # vias, - # voltage_probes, - # current_sources, - # voltage_sources, - # power_tree, - # loop_res, - # hidden=True, - # ) - # - # @pyedb_function_handler() - # def variable_exists(self, variable_name): - # """Check if a variable exists or not. - # - # Returns - # ------- - # tuple of bool and VaribleServer - # It returns a booleand to check if the variable exists and the variable - # server that should contain the variable. - # """ - # if "$" in variable_name: - # if variable_name.index("$") == 0: - # var_server = self.active_db.GetVariableServer() - # - # else: - # var_server = self.active_cell.GetVariableServer() - # - # else: - # var_server = self.active_cell.GetVariableServer() - # - # variables = var_server.GetAllVariableNames() - # if variable_name in list(variables): - # return True, var_server - # return False, var_server - # - # @pyedb_function_handler() - # def get_variable(self, variable_name): - # """Return Variable Value if variable exists. - # - # Parameters - # ---------- - # variable_name - # - # Returns - # ------- - # :class:`pyaedt.edb_core.edb_data.edbvalue.EdbValue` - # """ - # var_server = self.variable_exists(variable_name) - # if var_server[0]: - # tuple_value = var_server[1].GetVariableValue(variable_name) - # return EdbValue(tuple_value[1]) - # self.logger.info("Variable %s doesn't exists.", variable_name) - # return None - # - # @pyedb_function_handler() - # def add_project_variable(self, variable_name, variable_value): - # """Add a variable to edb database (project). The variable will have the prefix `$`. - # - # ..note:: - # User can use also the setitem to create or assign a variable. See example below. - # - # Parameters - # ---------- - # variable_name : str - # Name of the variable. Name can be provided without ``$`` prefix. - # variable_value : str, float - # Value of the variable with units. - # - # Returns - # ------- - # tuple - # Tuple containing the ``AddVariable`` result and variable server. - # - # Examples - # -------- - # - # >>> from pyaedt import Edb - # >>> edb_app = Edb() - # >>> boolean_1, ant_length = edb_app.add_project_variable("my_local_variable", "1cm") - # >>> print(edb_app["$my_local_variable"]) #using getitem - # >>> edb_app["$my_local_variable"] = "1cm" #using setitem - # - # """ - # if not variable_name.startswith("$"): - # variable_name = "${}".format(variable_name) - # return self.add_design_variable(variable_name=variable_name, variable_value=variable_value) - # - # @pyedb_function_handler() - # def add_design_variable(self, variable_name, variable_value, is_parameter=False): - # """Add a variable to edb. The variable can be a design one or a project variable (using ``$`` prefix). - # - # ..note:: - # User can use also the setitem to create or assign a variable. See example below. - # - # Parameters - # ---------- - # variable_name : str - # Name of the variable. To added the variable as a project variable, the name - # must begin with ``$``. - # variable_value : str, float - # Value of the variable with units. - # is_parameter : bool, optional - # Whether to add the variable as a local variable. The default is ``False``. - # When ``True``, the variable is added as a parameter default. - # - # Returns - # ------- - # tuple - # Tuple containing the ``AddVariable`` result and variable server. - # - # Examples - # -------- - # - # >>> from pyaedt import Edb - # >>> edb_app = Edb() - # >>> boolean_1, ant_length = edb_app.add_design_variable("my_local_variable", "1cm") - # >>> print(edb_app["my_local_variable"]) #using getitem - # >>> edb_app["my_local_variable"] = "1cm" #using setitem - # >>> boolean_2, para_length = edb_app.change_design_variable_value("my_parameter", "1m", is_parameter=True - # >>> boolean_3, project_length = edb_app.change_design_variable_value("$my_project_variable", "1m") - # - # - # """ - # var_server = self.variable_exists(variable_name) - # if not var_server[0]: - # var_server[1].AddVariable(variable_name, self.edb_value(variable_value), is_parameter) - # return True, var_server[1] - # self.logger.error("Variable %s already exists.", variable_name) - # return False, var_server[1] - # - # @pyedb_function_handler() - # def change_design_variable_value(self, variable_name, variable_value): - # """Change a variable value. - # - # ..note:: - # User can use also the getitem to read the variable value. See example below. - # - # Parameters - # ---------- - # variable_name : str - # Name of the variable. - # variable_value : str, float - # Value of the variable with units. - # - # Returns - # ------- - # tuple - # Tuple containing the ``SetVariableValue`` result and variable server. - # - # Examples - # -------- - # - # >>> from pyaedt import Edb - # >>> edb_app = Edb() - # >>> boolean, ant_length = edb_app.add_design_variable("ant_length", "1cm") - # >>> boolean, ant_length = edb_app.change_design_variable_value("ant_length", "1m") - # >>> print(edb_app["ant_length"]) #using getitem - # """ - # var_server = self.variable_exists(variable_name) - # if var_server[0]: - # var_server[1].SetVariableValue(variable_name, self.edb_value(variable_value)) - # return True, var_server[1] - # self.logger.error("Variable %s does not exists.", variable_name) - # return False, var_server[1] - # - # @pyedb_function_handler() - # def get_bounding_box(self): - # """Get the layout bounding box. - # - # Returns - # ------- - # list of list of double - # Bounding box as a [lower-left X, lower-left Y], [upper-right X, upper-right Y]) pair in meters. - # """ - # bbox = self.edbutils.HfssUtilities.GetBBox(self.active_layout) - # return [[bbox.Item1.X.ToDouble(), bbox.Item1.Y.ToDouble()], [bbox.Item2.X.ToDouble(), bbox.Item2.Y.ToDouble()]] - # - # @pyedb_function_handler() - # def build_simulation_project(self, simulation_setup): - # # type: (SimulationConfiguration) -> bool - # """Build a ready-to-solve simulation project. - # - # Parameters - # ---------- - # simulation_setup : :class:`pyaedt.edb_core.edb_data.simulation_configuration.SimulationConfiguration` object. - # SimulationConfiguration object that can be instantiated or directly loaded with a - # configuration file. - # - # Returns - # ------- - # bool - # ``True`` when successful, False when ``Failed``. - # - # Examples - # -------- - # - # >>> from pyaedt import Edb - # >>> from pyaedt.edb_core.edb_data.simulation_configuration import SimulationConfiguration - # >>> config_file = path_configuration_file - # >>> source_file = path_to_edb_folder - # >>> edb = Edb(source_file) - # >>> sim_setup = SimulationConfiguration(config_file) - # >>> edb.build_simulation_project(sim_setup) - # >>> edb.save_edb() - # >>> edb.close_edb() - # """ - # self.logger.info("Building simulation project.") - # legacy_name = self.edbpath - # if simulation_setup.output_aedb: - # self.save_edb_as(simulation_setup.output_aedb) - # try: - # if simulation_setup.signal_layer_etching_instances: - # for layer in simulation_setup.signal_layer_etching_instances: - # if layer in self.stackup.layers: - # idx = simulation_setup.signal_layer_etching_instances.index(layer) - # if len(simulation_setup.etching_factor_instances) > idx: - # self.stackup[layer].etch_factor = float(simulation_setup.etching_factor_instances[idx]) - # - # if not simulation_setup.signal_nets and simulation_setup.components: - # nets_to_include = [] - # pnets = list(self.nets.power_nets.keys())[:] - # for el in simulation_setup.components: - # nets_to_include.append([i for i in self.components[el].nets if i not in pnets]) - # simulation_setup.signal_nets = [ - # i - # for i in list(set.intersection(*map(set, nets_to_include))) - # if i not in simulation_setup.power_nets and i != "" - # ] - # self.nets.classify_nets(simulation_setup.power_nets, simulation_setup.signal_nets) - # if not simulation_setup.power_nets or not simulation_setup.signal_nets: - # self.logger.info("Disabling cutout as no signals or power nets have been defined.") - # simulation_setup.do_cutout_subdesign = False - # if simulation_setup.do_cutout_subdesign: - # self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type)) - # if simulation_setup.use_default_cutout: - # old_cell_name = self.active_cell.GetName() - # if self.cutout( - # signal_list=simulation_setup.signal_nets, - # reference_list=simulation_setup.power_nets, - # expansion_size=simulation_setup.cutout_subdesign_expansion, - # use_round_corner=simulation_setup.cutout_subdesign_round_corner, - # extent_type=simulation_setup.cutout_subdesign_type, - # use_pyaedt_cutout=False, - # use_pyaedt_extent_computing=False, - # ): - # self.logger.info("Cutout processed.") - # old_cell = self.active_cell.FindByName( - # self.db, self.edb_api.cell.CellType.CircuitCell, old_cell_name - # ) - # if old_cell: - # old_cell.Delete() - # else: # pragma: no cover - # self.logger.error("Cutout failed.") - # else: - # self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type)) - # self.cutout( - # signal_list=simulation_setup.signal_nets, - # reference_list=simulation_setup.power_nets, - # expansion_size=simulation_setup.cutout_subdesign_expansion, - # use_round_corner=simulation_setup.cutout_subdesign_round_corner, - # extent_type=simulation_setup.cutout_subdesign_type, - # use_pyaedt_cutout=True, - # use_pyaedt_extent_computing=True, - # remove_single_pin_components=True, - # ) - # self.logger.info("Cutout processed.") - # else: - # if simulation_setup.include_only_selected_nets: - # included_nets = simulation_setup.signal_nets + simulation_setup.power_nets - # nets_to_remove = [ - # net.name for net in list(self.nets.nets.values()) if not net.name in included_nets - # ] - # self.nets.delete(nets_to_remove) - # self.logger.info("Deleting existing ports.") - # map(lambda port: port.Delete(), self.layout.terminals) - # map(lambda pg: pg.Delete(), self.layout.pin_groups) - # if simulation_setup.solver_type == SolverType.Hfss3dLayout: - # if simulation_setup.generate_excitations: - # self.logger.info("Creating HFSS ports for signal nets.") - # source_type = SourceType.CoaxPort - # if not simulation_setup.generate_solder_balls: - # source_type = SourceType.CircPort - # for cmp in simulation_setup.components: - # self.components.create_port_on_component( - # cmp, - # net_list=simulation_setup.signal_nets, - # do_pingroup=False, - # reference_net=simulation_setup.power_nets, - # port_type=source_type, - # ) - # if simulation_setup.generate_solder_balls and not self.hfss.set_coax_port_attributes( - # simulation_setup - # ): # pragma: no cover - # self.logger.error("Failed to configure coaxial port attributes.") - # self.logger.info("Number of ports: {}".format(self.hfss.get_ports_number())) - # self.logger.info("Configure HFSS extents.") - # if ( - # simulation_setup.generate_solder_balls and simulation_setup.trim_reference_size - # ): # pragma: no cover - # self.logger.info( - # "Trimming the reference plane for coaxial ports: {0}".format( - # bool(simulation_setup.trim_reference_size) - # ) - # ) - # self.hfss.trim_component_reference_size(simulation_setup) # pragma: no cover - # self.hfss.configure_hfss_extents(simulation_setup) - # if not self.hfss.configure_hfss_analysis_setup(simulation_setup): - # self.logger.error("Failed to configure HFSS simulation setup.") - # if simulation_setup.solver_type == SolverType.SiwaveSYZ: - # if simulation_setup.generate_excitations: - # for cmp in simulation_setup.components: - # self.components.create_port_on_component( - # cmp, - # net_list=simulation_setup.signal_nets, - # do_pingroup=simulation_setup.do_pingroup, - # reference_net=simulation_setup.power_nets, - # port_type=SourceType.CircPort, - # ) - # self.logger.info("Configuring analysis setup.") - # if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover - # self.logger.error("Failed to configure Siwave simulation setup.") - # - # if simulation_setup.solver_type == SolverType.SiwaveDC: - # if simulation_setup.generate_excitations: - # self.components.create_source_on_component(simulation_setup.sources) - # if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover - # self.logger.error("Failed to configure Siwave simulation setup.") - # self.padstacks.check_and_fix_via_plating() - # self.save_edb() - # if not simulation_setup.open_edb_after_build and simulation_setup.output_aedb: - # self.close_edb() - # self.edbpath = legacy_name - # self.open_edb() - # return True - # except: # pragma: no cover - # return False - # - # @pyedb_function_handler() - # def get_statistics(self, compute_area=False): - # """Get the EDBStatistics object. - # - # Returns - # ------- - # EDBStatistics object from the loaded layout. - # """ - # return self.modeler.get_layout_statistics(evaluate_area=compute_area, net_list=None) - # - # @pyedb_function_handler() - # def are_port_reference_terminals_connected(self, common_reference=None): - # """Check if all terminal references in design are connected. - # If the reference nets are different, there is no hope for the terminal references to be connected. - # After we have identified a common reference net we need to loop the terminals again to get - # the correct reference terminals that uses that net. - # - # Parameters - # ---------- - # common_reference : str, optional - # Common Reference name. If ``None`` it will be searched in ports terminal. - # If a string is passed then all excitations must have such reference assigned. - # - # Returns - # ------- - # bool - # Either if the ports are connected to reference_name or not. - # - # Examples - # -------- - # >>>edb = Edb() - # >>> edb.hfss.create_edge_port_vertical(prim_1_id, ["-66mm", "-4mm"], "port_ver") - # >>> edb.hfss.create_edge_port_horizontal( - # >>> ... prim_1_id, ["-60mm", "-4mm"], prim_2_id, ["-59mm", "-4mm"], "port_hori", 30, "Lower" - # >>> ... ) - # >>> edb.hfss.create_wave_port(traces[0].id, trace_paths[0][0], "wave_port") - # >>> edb.cutout(["Net1"]) - # >>> assert edb.are_port_reference_terminals_connected() - # """ - # all_sources = [i for i in self.excitations.values() if not isinstance(i, (WavePort, GapPort, BundleWavePort))] - # all_sources.extend([i for i in self.sources.values()]) - # if not all_sources: - # return True - # self.logger.reset_timer() - # if not common_reference: - # common_reference = list(set([i.reference_net_name for i in all_sources if i.reference_net_name])) - # if len(common_reference) > 1: - # self.logger.error("More than 1 reference found.") - # return False - # if not common_reference: - # self.logger.error("No Reference found.") - # return False - # - # common_reference = common_reference[0] - # all_sources = [i for i in all_sources if i.net_name != common_reference] - # - # setList = [ - # set(i.reference_object.get_connected_object_id_set()) - # for i in all_sources - # if i.reference_object and i.reference_net_name == common_reference - # ] - # if len(setList) != len(all_sources): - # self.logger.error("No Reference found.") - # return False - # cmps = [ - # i - # for i in list(self.components.resistors.values()) - # if i.numpins == 2 and common_reference in i.nets and self._decompose_variable_value(i.res_value) <= 1 - # ] - # cmps.extend( - # [i for i in list(self.components.inductors.values()) if i.numpins == 2 and common_reference in i.nets] - # ) - # - # for cmp in cmps: - # found = False - # ids = [i.GetId() for i in cmp.pinlist] - # for list_obj in setList: - # if len(set(ids).intersection(list_obj)) == 1: - # for list_obj2 in setList: - # if list_obj2 != list_obj and len(set(ids).intersection(list_obj)) == 1: - # if (ids[0] in list_obj and ids[1] in list_obj2) or ( - # ids[1] in list_obj and ids[0] in list_obj2 - # ): - # setList[setList.index(list_obj)] = list_obj.union(list_obj2) - # setList[setList.index(list_obj2)] = list_obj.union(list_obj2) - # found = True - # break - # if found: - # break - # - # # Get the set intersections for all the ID sets. - # iDintersection = set.intersection(*setList) - # self.logger.info_timer( - # "Terminal reference primitive IDs total intersections = {}\n\n".format(len(iDintersection)) - # ) - # - # # If the intersections are non-zero, the terminal references are connected. - # return True if len(iDintersection) > 0 else False - # - # @pyedb_function_handler() - # def new_simulation_configuration(self, filename=None): - # # type: (str) -> SimulationConfiguration - # """New SimulationConfiguration Object. - # - # Parameters - # ---------- - # filename : str, optional - # Input config file. - # - # Returns - # ------- - # :class:`pyaedt.edb_core.edb_data.simulation_configuration.SimulationConfiguration` - # """ - # return SimulationConfiguration(filename, self) - # - # @property - # def setups(self): - # """Get the dictionary of all EDB HFSS and SIwave setups. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`] or - # Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveDCSimulationSetup`] or - # Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`] - # - # """ - # for i in list(self.active_cell.SimulationSetups): - # if i.GetName() not in self._setups: - # if i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kHFSS: - # self._setups[i.GetName()] = HfssSimulationSetup(self, i.GetName(), i) - # elif i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kSIWave: - # self._setups[i.GetName()] = SiwaveSYZSimulationSetup(self, i.GetName(), i) - # elif i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kSIWaveDCIR: - # self._setups[i.GetName()] = SiwaveDCSimulationSetup(self, i.GetName(), i) - # return self._setups - # - # @property - # def hfss_setups(self): - # """Active HFSS setup in EDB. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`] - # - # """ - # return {name: i for name, i in self.setups.items() if i.setup_type == "kHFSS"} - # - # @property - # def siwave_dc_setups(self): - # """Active Siwave DC IR Setups. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveDCSimulationSetup`] - # """ - # return {name: i for name, i in self.setups.items() if i.setup_type == "kSIWaveDCIR"} - # - # @property - # def siwave_ac_setups(self): - # """Active Siwave SYZ setups. - # - # Returns - # ------- - # Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`] - # """ - # return {name: i for name, i in self.setups.items() if i.setup_type == "kSIWave"} - # - # def create_hfss_setup(self, name=None): - # """Create a setup from a template. - # - # Parameters - # ---------- - # name : str, optional - # Setup name. - # - # Returns - # ------- - # :class:`pyaedt.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup` - # - # Examples - # -------- - # >>> setup1 = edbapp.create_hfss_setup("setup1") - # >>> setup1.hfss_port_settings.max_delta_z0 = 0.5 - # """ - # if name in self.setups: - # return False - # setup = HfssSimulationSetup(self, name) - # self._setups[name] = setup - # return setup - # - # @pyedb_function_handler() - # def create_siwave_syz_setup(self, name=None): - # """Create a setup from a template. - # - # Parameters - # ---------- - # name : str, optional - # Setup name. - # - # Returns - # ------- - # :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup` - # - # Examples - # -------- - # >>> setup1 = edbapp.create_siwave_syz_setup("setup1") - # >>> setup1.add_frequency_sweep(frequency_sweep=[ - # ... ["linear count", "0", "1kHz", 1], - # ... ["log scale", "1kHz", "0.1GHz", 10], - # ... ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], - # ... ]) - # """ - # if not name: - # name = generate_unique_name("Siwave_SYZ") - # if name in self.setups: - # return False - # setup = SiwaveSYZSimulationSetup(self, name) - # self._setups[name] = setup - # return setup - # - # @pyedb_function_handler() - # def create_siwave_dc_setup(self, name=None): - # """Create a setup from a template. - # - # Parameters - # ---------- - # name : str, optional - # Setup name. - # - # Returns - # ------- - # :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup` - # - # Examples - # -------- - # >>> setup1 = edbapp.create_siwave_dc_setup("setup1") - # >>> setup1.mesh_bondwires = True - # - # """ - # if not name: - # name = generate_unique_name("Siwave_DC") - # if name in self.setups: - # return False - # setup = SiwaveDCSimulationSetup(self, name) - # self._setups[name] = setup - # return setup - # - # @pyedb_function_handler() - # def calculate_initial_extent(self, expansion_factor): - # """Compute a float representing the larger number between the dielectric thickness or trace width - # multiplied by the nW factor. The trace width search is limited to nets with ports attached. - # - # Parameters - # ---------- - # expansion_factor : float - # Value for the width multiplier (nW factor). - # - # Returns - # ------- - # float - # """ - # nets = [] - # for port in self.excitations.values(): - # nets.append(port.net_name) - # for port in self.sources.values(): - # nets.append(port.net_name) - # nets = list(set(nets)) - # max_width = 0 - # for net in nets: - # for primitive in self.nets[net].primitives: - # if primitive.type == "Path": - # max_width = max(max_width, primitive.width) - # - # for layer in list(self.stackup.dielectric_layers.values()): - # max_width = max(max_width, layer.thickness) - # - # max_width = max_width * expansion_factor - # self.logger.info("The W factor is {}, The initial extent = {:e}".format(expansion_factor, max_width)) - # return max_width - # - # @pyedb_function_handler() - # def copy_zones(self, working_directory=None): - # """Copy multizone EDB project to one new edb per zone. - # - # Parameters - # ---------- - # working_directory : str - # Directory path where all EDB project are copied, if empty will use the current EDB project. - # - # Returns - # ------- - # dict[str](int, EDB PolygonData) - # Return a dictionary with edb path as key and tuple Zone Id as first item and EDB polygon Data defining - # the region as second item. - # - # """ - # if working_directory: - # if not os.path.isdir(working_directory): - # os.mkdir(working_directory) - # else: - # shutil.rmtree(working_directory) - # os.mkdir(working_directory) - # else: - # working_directory = os.path.dirname(self.edbpath) - # zone_primitives = list(self.layout.zone_primitives) - # zone_ids = list(self.stackup._layer_collection.GetZoneIds()) - # edb_zones = {} - # if not self.setups: - # self.siwave.add_siwave_syz_analysis() - # self.save_edb() - # for zone_primitive in zone_primitives: - # edb_zone_path = os.path.join( - # working_directory, "{}_{}".format(zone_primitive.GetId(), os.path.basename(self.edbpath)) - # ) - # shutil.copytree(self.edbpath, edb_zone_path) - # poly_data = zone_primitive.GetPolygonData() - # if self.version[0] >= 10: - # edb_zones[edb_zone_path] = (zone_primitive.GetZoneId(), poly_data) - # elif len(zone_primitives) == len(zone_ids): - # edb_zones[edb_zone_path] = (zone_ids[0], poly_data) - # else: - # self.logger.info( - # "Number of zone primitives is not equal to zone number. Zone information will be lost." - # "Use Ansys 2024 R1 or later." - # ) - # edb_zones[edb_zone_path] = (-1, poly_data) - # return edb_zones - # - # @pyedb_function_handler() - # def cutout_multizone_layout(self, zone_dict, common_reference_net=None): - # """Create a multizone project cutout. - # - # Parameters - # ---------- - # zone_dict : dict[str](EDB PolygonData) - # Dictionary with EDB path as key and EDB PolygonData as value defining the zone region. - # This dictionary is returned from the command copy_zones(): - # >>> edb = Edb(edb_file) - # >>> zone_dict = edb.copy_zones(r"C:\Temp\test") - # - # common_reference_net : str - # the common reference net name. This net name must be provided to provide a valid project. - # - # Returns - # ------- - # dict[str][str] , list of str - # first dictionary defined_ports with edb name as key and existing port name list as value. Those ports are the - # ones defined before processing the multizone clipping. - # second is the list of connected port. - # - # """ - # terminals = {} - # defined_ports = {} - # project_connexions = None - # for edb_path, zone_info in zone_dict.items(): - # edb = Edb(edbversion=self.edbversion, edbpath=edb_path) - # edb.cutout(use_pyaedt_cutout=True, custom_extent=zone_info[1], open_cutout_at_end=True) - # if not zone_info[0] == -1: - # layers_to_remove = [ - # lay.name for lay in list(edb.stackup.layers.values()) if not lay._edb_layer.IsInZone(zone_info[0]) - # ] - # for layer in layers_to_remove: - # edb.stackup.remove_layer(layer) - # edb.stackup.stackup_mode = "Laminate" - # edb.cutout(use_pyaedt_cutout=True, custom_extent=zone_info[1], open_cutout_at_end=True) - # edb.active_cell.SetName(os.path.splitext(os.path.basename(edb_path))[0]) - # if common_reference_net: - # signal_nets = list(self.nets.signal.keys()) - # defined_ports[os.path.splitext(os.path.basename(edb_path))[0]] = list(edb.excitations.keys()) - # edb_terminals_info = edb.hfss.create_vertical_circuit_port_on_clipped_traces( - # nets=signal_nets, reference_net=common_reference_net, user_defined_extent=zone_info[1] - # ) - # if edb_terminals_info: - # terminals[os.path.splitext(os.path.basename(edb_path))[0]] = edb_terminals_info - # project_connexions = self._get_connected_ports_from_multizone_cutout(terminals) - # edb.save_edb() - # edb.close_edb() - # return defined_ports, project_connexions - # - # @pyedb_function_handler() - # def _get_connected_ports_from_multizone_cutout(self, terminal_info_dict): - # """Return connected port list from clipped multizone layout. - # - # Parameters - # terminal_info_dict : dict[str][str] - # dictionary terminals with edb name as key and created ports name on clipped signal nets. - # Dictionary is generated by the command cutout_multizone_layout: - # >>> edb = Edb(edb_file) - # >>> edb_zones = edb.copy_zones(r"C:\Temp\test") - # >>> defined_ports, terminals_info = edb.cutout_multizone_layout(edb_zones, common_reference_net) - # >>> project_connexions = get_connected_ports(terminals_info) - # - # Returns - # ------- - # list[str] - # list of connected ports. - # """ - # if terminal_info_dict: - # tolerance = 1e-8 - # connected_ports_list = [] - # project_list = list(terminal_info_dict.keys()) - # project_combinations = list(combinations(range(0, len(project_list)), 2)) - # for comb in project_combinations: - # terminal_set1 = terminal_info_dict[project_list[comb[0]]] - # terminal_set2 = terminal_info_dict[project_list[comb[1]]] - # project1_nets = [t[0] for t in terminal_set1] - # project2_nets = [t[0] for t in terminal_set2] - # net_with_connected_ports = list(set(project1_nets).intersection(project2_nets)) - # if net_with_connected_ports: - # for net_name in net_with_connected_ports: - # project1_port_info = [term_info for term_info in terminal_set1 if term_info[0] == net_name] - # project2_port_info = [term_info for term_info in terminal_set2 if term_info[0] == net_name] - # port_list = [p[3] for p in project1_port_info] + [p[3] for p in project2_port_info] - # port_combinations = list(combinations(port_list, 2)) - # for port_combination in port_combinations: - # if not port_combination[0] == port_combination[1]: - # port1 = [port for port in terminal_set1 if port[3] == port_combination[0]] - # if not port1: - # port1 = [port for port in terminal_set2 if port[3] == port_combination[0]] - # port2 = [port for port in terminal_set2 if port[3] == port_combination[1]] - # if not port2: - # port2 = [port for port in terminal_set1 if port[3] == port_combination[1]] - # port1 = port1[0] - # port2 = port2[0] - # if not port1[3] == port2[3]: - # port_distance = GeometryOperators.points_distance(port1[1:3], port2[1:3]) - # if port_distance < tolerance: - # port1_connexion = None - # port2_connexion = None - # for project_path, port_info in terminal_info_dict.items(): - # port1_map = [port for port in port_info if port[3] == port1[3]] - # if port1_map: - # port1_connexion = (project_path, port1[3]) - # port2_map = [port for port in port_info if port[3] == port2[3]] - # if port2_map: - # port2_connexion = (project_path, port2[3]) - # if port1_connexion and port2_connexion: - # if ( - # not port1_connexion[0] == port2_connexion[0] - # or not port1_connexion[1] == port2_connexion[1] - # ): - # connected_ports_list.append((port1_connexion, port2_connexion)) - # return connected_ports_list + @pyedb_function_handler() + def point_3d(self, x, y, z=0.0): + """Compute the Edb 3d Point Data. + + Parameters + ---------- + x : float, int or str + X value. + y : float, int or str + Y value. + z : float, int or str, optional + Z value. + + Returns + ------- + ``Geometry.Point3DData``. + """ + return self.edb_api.geometry.point3d_data(x, y, z) + + @pyedb_function_handler() + def point_data(self, x, y=None): + """Compute the Edb Point Data. + + Parameters + ---------- + x : float, int or str + X value. + y : float, int or str, optional + Y value. + + + Returns + ------- + ``Geometry.PointData``. + """ + if y is None: + return self.edb_api.geometry.point_data(x) + else: + return self.edb_api.geometry.point_data(x, y) + + @pyedb_function_handler() + def _is_file_existing_and_released(self, filename): + if os.path.exists(filename): + try: + os.rename(filename, filename + "_") + os.rename(filename + "_", filename) + return True + except OSError as e: + return False + else: + return False + + @pyedb_function_handler() + def _is_file_existing(self, filename): + if os.path.exists(filename): + return True + else: + return False + + @pyedb_function_handler() + def _wait_for_file_release(self, timeout=30, file_to_release=None): + if not file_to_release: + file_to_release = os.path.join(self.edbpath) + tstart = time.time() + while True: + if self._is_file_existing_and_released(file_to_release): + return True + elif time.time() - tstart > timeout: + return False + else: + time.sleep(0.250) + + @pyedb_function_handler() + def _wait_for_file_exists(self, timeout=30, file_to_release=None, wait_count=4): + if not file_to_release: + file_to_release = os.path.join(self.edbpath) + tstart = time.time() + times = 0 + while True: + if self._is_file_existing(file_to_release): + # print 'File is released' + times += 1 + if times == wait_count: + return True + elif time.time() - tstart > timeout: + # print 'Timeout reached' + return False + else: + times = 0 + time.sleep(0.250) + + @pyedb_function_handler() + def close_edb(self): + """Close EDB and cleanup variables. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self.close() + self._global_logger.remove_file_logger(os.path.splitext(os.path.split(self.log_name)[-1])[0]) + self._logger = self._global_logger + start_time = time.time() + self._wait_for_file_release() + elapsed_time = time.time() - start_time + self.logger.info("EDB file release time: {0:.2f}ms".format(elapsed_time * 1000.0)) + self._clean_variables() + self.session.disconnect() + return True + + @pyedb_function_handler() + def save_edb(self): + """Save the EDB file. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self.save() + start_time = time.time() + self._wait_for_file_release() + elapsed_time = time.time() - start_time + self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0)) + return True + + @pyedb_function_handler() + def save_edb_as(self, fname): + """Save the EDB file as another file. + + Parameters + ---------- + fname : str + Name of the new file to save to. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self.save_as(fname) + start_time = time.time() + self._wait_for_file_release() + elapsed_time = time.time() - start_time + self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0)) + self.edbpath = self.directory + if self.log_name: + self._global_logger.remove_file_logger(os.path.splitext(os.path.split(self.log_name)[-1])[0]) + self._logger = self._global_logger + + self.log_name = os.path.join( + os.path.dirname(fname), "pyaedt_" + os.path.splitext(os.path.split(fname)[-1])[0] + ".log" + ) + if settings.enable_local_log_file: + self._logger = self._global_logger.add_file_logger(self.log_name, "Edb") + return True + + @pyedb_function_handler() + def execute(self, func): + """Execute a function. + + Parameters + ---------- + func : str + Function to execute. + + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + return self.edb_api.utility.utility.Command.Execute(func) + + @pyedb_function_handler() + def import_cadence_file(self, inputBrd, WorkDir=None, anstranslator_full_path="", use_ppe=False): + """Import a board file and generate an ``edb.def`` file in the working directory. + + Parameters + ---------- + inputBrd : str + Full path to the board file. + WorkDir : str, optional + Directory in which to create the ``aedb`` folder. The default value is ``None``, + in which case the AEDB file is given the same name as the board file. Only + the extension differs. + anstranslator_full_path : str, optional + Full path to the Ansys translator. + use_ppe : bool, optional + Whether to use the PPE License. The default is ``False``. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if self.import_layout_pcb( + inputBrd, working_dir=WorkDir, anstranslator_full_path=anstranslator_full_path, use_ppe=use_ppe + ): + return True + else: + return False + + @pyedb_function_handler() + def import_gds_file( + self, + inputGDS, + WorkDir=None, + anstranslator_full_path="", + use_ppe=False, + control_file=None, + tech_file=None, + map_file=None, + ): + """Import a GDS file and generate an ``edb.def`` file in the working directory. + + ..note:: + `ANSYSLMD_LICENSE_FILE` is needed to run the translator. + + Parameters + ---------- + inputGDS : str + Full path to the GDS file. + WorkDir : str, optional + Directory in which to create the ``aedb`` folder. The default value is ``None``, + in which case the AEDB file is given the same name as the GDS file. Only the extension + differs. + anstranslator_full_path : str, optional + Full path to the Ansys translator. + use_ppe : bool, optional + Whether to use the PPE License. The default is ``False``. + control_file : str, optional + Path to the XML file. The default is ``None``, in which case an attempt is made to find + the XML file in the same directory as the GDS file. To succeed, the XML file and GDS file must + have the same name. Only the extension differs. + tech_file : str, optional + Technology file. It uses Helic to convert tech file to xml and then imports the gds. Works on Linux only. + map_file : str, optional + Layer map file. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if tech_file or map_file: + control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml") + control_file = ControlFile(xml_input=control_file, tecnhology=tech_file, layer_map=map_file).write_xml( + control_file_temp + ) + elif tech_file: + self.logger.error("Technology files are supported only in Linux. Use control file instead.") + return False + if self.import_layout_pcb( + inputGDS, + working_dir=WorkDir, + anstranslator_full_path=anstranslator_full_path, + use_ppe=use_ppe, + control_file=control_file, + ): + return True + else: + return False + + @pyedb_function_handler() + def _create_extent( + self, + net_signals, + extent_type, + expansion_size, + use_round_corner, + use_pyaedt_extent=False, + smart_cut=False, + reference_list=[], + include_pingroups=True, + ): + if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1]: + if use_pyaedt_extent: + _poly = self._create_conformal( + net_signals, + expansion_size, + 1e-12, + use_round_corner, + expansion_size, + smart_cut, + reference_list, + include_pingroups, + ) + else: + _poly = self.layout.expanded_extent( + net_signals, + self.edb_api.geometry.extent_type.Conforming, + expansion_size, + False, + use_round_corner, + 1, + ) + elif extent_type in ["Bounding", self.edb_api.geometry.extent_type.BoundingBox, 0]: + _poly = self.layout.expanded_extent( + net_signals, self.edb_api.geometry.extent_type.BoundingBox, expansion_size, False, use_round_corner, 1 + ) + else: + if use_pyaedt_extent: + _poly = self._create_convex_hull( + net_signals, + expansion_size, + 1e-12, + use_round_corner, + expansion_size, + smart_cut, + reference_list, + include_pingroups, + ) + else: + _poly = self.layout.expanded_extent( + net_signals, + self.edb_api.geometry.extent_type.Conforming, + expansion_size, + False, + use_round_corner, + 1, + ) + _poly_list = convert_py_list_to_net_list([_poly]) + _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(_poly_list) + return _poly + + @pyedb_function_handler() + def _create_conformal( + self, + net_signals, + expansion_size, + tolerance, + round_corner, + round_extension, + smart_cutout=False, + reference_list=[], + include_pingroups=True, + ): + names = [] + _polys = [] + for net in net_signals: + names.append(net.GetName()) + for prim in self.modeler.primitives: + if prim is not None and prim.net_name in names: + obj_data = prim.primitive_object.GetPolygonData().Expand( + expansion_size, tolerance, round_corner, round_extension + ) + if obj_data: + _polys.extend(list(obj_data)) + if smart_cutout: + _polys.extend(self._smart_cut(net_signals, reference_list, include_pingroups)) + _poly_unite = self.edb_api.geometry.polygon_data.unite(_polys) + if len(_poly_unite) == 1: + return _poly_unite[0] + else: + areas = [i.Area() for i in _poly_unite] + return _poly_unite[areas.index(max(areas))] + + @pyedb_function_handler() + def _smart_cut(self, net_signals, reference_list=[], include_pingroups=True): + _polys = [] + terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [0, 3, 4, 7, 8]] + locations = [] + for term in terms: + if term.GetTerminalType().ToString() == "PadstackInstanceTerminal": + if term.GetParameters()[1].GetNet().GetName() in reference_list: + locations.append(self.padstacks.instances[term.GetParameters()[1].GetId()].position) + elif term.GetTerminalType().ToString() == "PointTerminal" and term.GetNet().GetName() in reference_list: + pd = term.GetParameters()[1] + locations.append([pd.X.ToDouble(), pd.Y.ToDouble()]) + if include_pingroups: + for reference in reference_list: + for pin in self.nets.nets[reference].padstack_instances: + if pin.pingroups: + locations.append(pin.position) + for point in locations: + pointA = self.edb_api.geometry.point_data( + self.edb_value(point[0] - 1e-12), self.edb_value(point[1] - 1e-12) + ) + pointB = self.edb_api.geometry.point_data( + self.edb_value(point[0] + 1e-12), self.edb_value(point[1] + 1e-12) + ) + + points = Tuple[self.edb_api.geometry.geometry.PointData, self.edb_api.geometry.geometry.PointData]( + pointA, pointB + ) + _polys.append(self.edb_api.geometry.polygon_data.create_from_bbox(points)) + for cname, c in self.components.instances.items(): + if ( + set(net_signals).intersection(c.nets) + and c.is_enabled + and c.model_type in ["SParameterModel", "SpiceModel", "NetlistModel"] + ): + for pin in c.pins: + locations.append(pin.position) + return _polys + + @pyedb_function_handler() + def _create_convex_hull( + self, + net_signals, + expansion_size, + tolerance, + round_corner, + round_extension, + smart_cut=False, + reference_list=[], + include_pingroups=True, + ): + names = [] + _polys = [] + for net in net_signals: + names.append(net.GetName()) + for prim in self.modeler.primitives: + if prim is not None and prim.net_name in names: + _polys.append(prim.primitive_object.GetPolygonData()) + if smart_cut: + _polys.extend(self._smart_cut(net_signals, reference_list, include_pingroups)) + _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(convert_py_list_to_net_list(_polys)) + _poly = _poly.Expand(expansion_size, tolerance, round_corner, round_extension)[0] + return _poly + + @pyedb_function_handler() + def cutout( + self, + signal_list=None, + reference_list=None, + extent_type="ConvexHull", + expansion_size=0.002, + use_round_corner=False, + output_aedb_path=None, + open_cutout_at_end=True, + use_pyaedt_cutout=True, + number_of_threads=4, + use_pyaedt_extent_computing=True, + extent_defeature=0, + remove_single_pin_components=False, + custom_extent=None, + custom_extent_units="mm", + include_partial_instances=False, + keep_voids=True, + check_terminals=False, + include_pingroups=False, + expansion_factor=0, + maximum_iterations=10, + preserve_components_with_model=False, + simple_pad_check=True, + ): + """Create a cutout using an approach entirely based on PyAEDT. + This method replaces all legacy cutout methods in PyAEDT. + It does in sequence: + - delete all nets not in list, + - create a extent of the nets, + - check and delete all vias not in the extent, + - check and delete all the primitives not in extent, + - check and intersect all the primitives that intersect the extent. + + Parameters + ---------- + signal_list : list + List of signal strings. + reference_list : list, optional + List of references to add. The default is ``["GND"]``. + extent_type : str, optional + Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and + ``"Bounding"``. The default is ``"Conforming"``. + expansion_size : float, str, optional + Expansion size ratio in meters. The default is ``0.002``. + use_round_corner : bool, optional + Whether to use round corners. The default is ``False``. + output_aedb_path : str, optional + Full path and name for the new AEDB file. If None, then current aedb will be cutout. + open_cutout_at_end : bool, optional + Whether to open the cutout at the end. The default is ``True``. + use_pyaedt_cutout : bool, optional + Whether to use new PyAEDT cutout method or EDB API method. + New method is faster than native API method since it benefits of multithread. + number_of_threads : int, optional + Number of thread to use. Default is 4. Valid only if ``use_pyaedt_cutout`` is set to ``True``. + use_pyaedt_extent_computing : bool, optional + Whether to use pyaedt extent computing (experimental) or EDB API. + extent_defeature : float, optional + Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental). + It applies only to Conforming bounding box. Default value is ``0`` which disable it. + remove_single_pin_components : bool, optional + Remove all Single Pin RLC after the cutout is completed. Default is `False`. + custom_extent : list + Points list defining the cutout shape. This setting will override `extent_type` field. + custom_extent_units : str + Units of the point list. The default is ``"mm"``. Valid only if `custom_extend` is provided. + include_partial_instances : bool, optional + Whether to include padstack instances that have bounding boxes intersecting with point list polygons. + This operation may slow down the cutout export.Valid only if `custom_extend` and + `use_pyaedt_cutout` is provided. + keep_voids : bool + Boolean used for keep or not the voids intersecting the polygon used for clipping the layout. + Default value is ``True``, ``False`` will remove the voids.Valid only if `custom_extend` is provided. + check_terminals : bool, optional + Whether to check for all reference terminals and increase extent to include them into the cutout. + This applies to components which have a model (spice, touchstone or netlist) associated. + include_pingroups : bool, optional + Whether to check for all pingroups terminals and increase extent to include them into the cutout. + It requires ``check_terminals``. + expansion_factor : int, optional + The method computes a float representing the largest number between + the dielectric thickness or trace width multiplied by the expansion_factor factor. + The trace width search is limited to nets with ports attached. Works only if `use_pyaedt_cutout`. + Default is `0` to disable the search. + maximum_iterations : int, optional + Maximum number of iterations before stopping a search for a cutout with an error. + Default is `10`. + preserve_components_with_model : bool, optional + Whether to preserve all pins of components that have associated models (Spice or NPort). + This parameter is applicable only for a PyAEDT cutout (except point list). + simple_pad_check : bool, optional + Whether to use the center of the pad to find the intersection with extent or use the bounding box. + Second method is much slower and requires to disable multithread on padstack removal. + Default is `True`. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + Examples + -------- + >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2") + >>> edb.logger.info_timer("Edb Opening") + >>> edb.logger.reset_timer() + >>> start = time.time() + >>> signal_list = [] + >>> for net in edb.nets.netlist: + >>> if "3V3" in net: + >>> signal_list.append(net) + >>> power_list = ["PGND"] + >>> edb.cutout(signal_list=signal_list, reference_list=power_list, extent_type="Conforming") + >>> end_time = str((time.time() - start)/60) + >>> edb.logger.info("Total pyaedt cutout time in min %s", end_time) + >>> edb.nets.plot(signal_list, None, color_by_net=True) + >>> edb.nets.plot(power_list, None, color_by_net=True) + >>> edb.save_edb() + >>> edb.close_edb() + + + """ + if expansion_factor > 0: + expansion_size = self.calculate_initial_extent(expansion_factor) + if signal_list is None: + signal_list = [] + if isinstance(reference_list, str): + reference_list = [reference_list] + elif reference_list is None: + reference_list = [] + if not use_pyaedt_cutout and custom_extent: + return self._create_cutout_on_point_list( + custom_extent, + units=custom_extent_units, + output_aedb_path=output_aedb_path, + open_cutout_at_end=open_cutout_at_end, + nets_to_include=signal_list + reference_list, + include_partial_instances=include_partial_instances, + keep_voids=keep_voids, + ) + elif not use_pyaedt_cutout: + return self._create_cutout_legacy( + signal_list=signal_list, + reference_list=reference_list, + extent_type=extent_type, + expansion_size=expansion_size, + use_round_corner=use_round_corner, + output_aedb_path=output_aedb_path, + open_cutout_at_end=open_cutout_at_end, + use_pyaedt_extent_computing=use_pyaedt_extent_computing, + check_terminals=check_terminals, + include_pingroups=include_pingroups, + ) + else: + legacy_path = self.edbpath + if expansion_factor > 0 and not custom_extent: + start = time.time() + self.save_edb() + dummy_path = self.edbpath.replace(".aedb", "_smart_cutout_temp.aedb") + working_cutout = False + i = 1 + expansion = expansion_size + while i <= maximum_iterations: + self.logger.info("-----------------------------------------") + self.logger.info("Trying cutout with {}mm expansion size".format(expansion * 1e3)) + self.logger.info("-----------------------------------------") + result = self._create_cutout_multithread( + signal_list=signal_list, + reference_list=reference_list, + extent_type=extent_type, + expansion_size=expansion, + use_round_corner=use_round_corner, + number_of_threads=number_of_threads, + custom_extent=custom_extent, + output_aedb_path=dummy_path, + remove_single_pin_components=remove_single_pin_components, + use_pyaedt_extent_computing=use_pyaedt_extent_computing, + extent_defeature=extent_defeature, + custom_extent_units=custom_extent_units, + check_terminals=check_terminals, + include_pingroups=include_pingroups, + preserve_components_with_model=preserve_components_with_model, + include_partial=include_partial_instances, + simple_pad_check=simple_pad_check, + ) + if self.are_port_reference_terminals_connected(): + if output_aedb_path: + self.save_edb_as(output_aedb_path) + else: + self.save_edb_as(legacy_path) + working_cutout = True + break + self.close_edb() + self.edbpath = legacy_path + self.open_edb() + i += 1 + expansion = expansion_size * i + if working_cutout: + msg = "Cutout completed in {} iterations with expansion size of {}mm".format(i, expansion * 1e3) + self.logger.info_timer(msg, start) + else: + msg = "Cutout failed after {} iterations and expansion size of {}mm".format(i, expansion * 1e3) + self.logger.info_timer(msg, start) + return False + else: + result = self._create_cutout_multithread( + signal_list=signal_list, + reference_list=reference_list, + extent_type=extent_type, + expansion_size=expansion_size, + use_round_corner=use_round_corner, + number_of_threads=number_of_threads, + custom_extent=custom_extent, + output_aedb_path=output_aedb_path, + remove_single_pin_components=remove_single_pin_components, + use_pyaedt_extent_computing=use_pyaedt_extent_computing, + extent_defeature=extent_defeature, + custom_extent_units=custom_extent_units, + check_terminals=check_terminals, + include_pingroups=include_pingroups, + preserve_components_with_model=preserve_components_with_model, + include_partial=include_partial_instances, + simple_pad_check=simple_pad_check, + ) + if result and not open_cutout_at_end and self.edbpath != legacy_path: + self.save_edb() + self.close_edb() + self.edbpath = legacy_path + self.open_edb() + return result + + @pyedb_function_handler() + def _create_cutout_legacy( + self, + signal_list=[], + reference_list=["GND"], + extent_type="Conforming", + expansion_size=0.002, + use_round_corner=False, + output_aedb_path=None, + open_cutout_at_end=True, + use_pyaedt_extent_computing=False, + remove_single_pin_components=False, + check_terminals=False, + include_pingroups=True, + ): + expansion_size = self.edb_value(expansion_size).ToDouble() + + # validate nets in layout + net_signals = [net.api_object for net in self.layout.nets if net.name in signal_list] + + # validate references in layout + _netsClip = convert_py_list_to_net_list( + [net.api_object for net in self.layout.nets if net.name in reference_list] + ) + + _poly = self._create_extent( + net_signals, + extent_type, + expansion_size, + use_round_corner, + use_pyaedt_extent_computing, + smart_cut=check_terminals, + reference_list=reference_list, + include_pingroups=include_pingroups, + ) + + # Create new cutout cell/design + included_nets_list = signal_list + reference_list + included_nets = convert_py_list_to_net_list( + [net.api_object for net in self.layout.nets if net.name in included_nets_list] + ) + _cutout = self.active_cell.CutOut(included_nets, _netsClip, _poly, True) + # Analysis setups do not come over with the clipped design copy, + # so add the analysis setups from the original here. + id = 1 + for _setup in self.active_cell.SimulationSetups: + # Empty string '' if coming from setup copy and don't set explicitly. + _setup_name = _setup.GetName() + if "GetSimSetupInfo" in dir(_setup): + # setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object + _hfssSimSetupInfo = _setup.GetSimSetupInfo() + _hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup + # Write the simulation setup info into the cell/design setup + _setup.SetSimSetupInfo(_hfssSimSetupInfo) + _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design + id += 1 + else: + _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design + + _dbCells = [_cutout] + + if output_aedb_path: + db2 = self.create(output_aedb_path) + _success = db2.Save() + _dbCells = convert_py_list_to_net_list(_dbCells) + db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project + if len(list(db2.CircuitCells)) > 0: + for net in list(list(db2.CircuitCells)[0].GetLayout().Nets): + if not net.GetName() in included_nets_list: + net.Delete() + _success = db2.Save() + for c in list(self.active_db.TopCircuitCells): + if c.GetName() == _cutout.GetName(): + c.Delete() + if open_cutout_at_end: # pragma: no cover + self._db = db2 + self.edbpath = output_aedb_path + self._active_cell = list(self.top_circuit_cells)[0] + self.edbpath = self.directory + self._init_objects() + if remove_single_pin_components: + self.components.delete_single_pin_rlc() + self.logger.info_timer("Single Pins components deleted") + self.components.refresh_components() + else: + if remove_single_pin_components: + try: + layout = list(db2.CircuitCells)[0].GetLayout() + _cmps = [ + l + for l in layout.Groups + if l.ToString() == "Ansys.Ansoft.Edb.Cell.Hierarchy.Component" and l.GetNumberOfPins() < 2 + ] + for _cmp in _cmps: + _cmp.Delete() + except: + self._logger.error("Failed to remove single pin components.") + db2.Close() + source = os.path.join(output_aedb_path, "edb.def.tmp") + target = os.path.join(output_aedb_path, "edb.def") + self._wait_for_file_release(file_to_release=output_aedb_path) + if os.path.exists(source) and not os.path.exists(target): + try: + shutil.copy(source, target) + except: + pass + elif open_cutout_at_end: + self._active_cell = _cutout + self._init_objects() + if remove_single_pin_components: + self.components.delete_single_pin_rlc() + self.logger.info_timer("Single Pins components deleted") + self.components.refresh_components() + return True + + @pyedb_function_handler() + def create_cutout( + self, + signal_list=[], + reference_list=["GND"], + extent_type="Conforming", + expansion_size=0.002, + use_round_corner=False, + output_aedb_path=None, + open_cutout_at_end=True, + use_pyaedt_extent_computing=False, + ): + """Create a cutout using an approach entirely based on pyaedt. + It does in sequence: + - delete all nets not in list, + - create an extent of the nets, + - check and delete all vias not in the extent, + - check and delete all the primitives not in extent, + - check and intersect all the primitives that intersect the extent. + + .. deprecated:: 0.6.58 + Use new method :func:`cutout` instead. + + Parameters + ---------- + signal_list : list + List of signal strings. + reference_list : list, optional + List of references to add. The default is ``["GND"]``. + extent_type : str, optional + Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and + ``"Bounding"``. The default is ``"Conforming"``. + expansion_size : float, str, optional + Expansion size ratio in meters. The default is ``0.002``. + use_round_corner : bool, optional + Whether to use round corners. The default is ``False``. + output_aedb_path : str, optional + Full path and name for the new AEDB file. + open_cutout_at_end : bool, optional + Whether to open the cutout at the end. The default + is ``True``. + use_pyaedt_extent_computing : bool, optional + Whether to use pyaedt extent computing (experimental). + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + warnings.warn("Use new method `cutout` instead.", DeprecationWarning) + return self._create_cutout_legacy( + signal_list=signal_list, + reference_list=reference_list, + extent_type=extent_type, + expansion_size=expansion_size, + use_round_corner=use_round_corner, + output_aedb_path=output_aedb_path, + open_cutout_at_end=open_cutout_at_end, + use_pyaedt_extent_computing=use_pyaedt_extent_computing, + ) + + @pyedb_function_handler() + def _create_cutout_multithread( + self, + signal_list=[], + reference_list=["GND"], + extent_type="Conforming", + expansion_size=0.002, + use_round_corner=False, + number_of_threads=4, + custom_extent=None, + output_aedb_path=None, + remove_single_pin_components=False, + use_pyaedt_extent_computing=False, + extent_defeature=0.0, + custom_extent_units="mm", + check_terminals=False, + include_pingroups=True, + preserve_components_with_model=False, + include_partial=False, + simple_pad_check=True, + ): + if is_ironpython: # pragma: no cover + self.logger.error("Method working only in Cpython") + return False + from concurrent.futures import ThreadPoolExecutor + + if output_aedb_path: + self.save_edb_as(output_aedb_path) + self.logger.info("Cutout Multithread started.") + expansion_size = self.edb_value(expansion_size).ToDouble() + + timer_start = self.logger.reset_timer() + if custom_extent: + if not reference_list and not signal_list: + reference_list = self.nets.netlist[::] + all_list = reference_list + else: + reference_list = reference_list + signal_list + all_list = reference_list + else: + all_list = signal_list + reference_list + pins_to_preserve = [] + nets_to_preserve = [] + if preserve_components_with_model: + for el in self.components.instances.values(): + if el.model_type in ["SPICEModel", "SParameterModel", "NetlistModel"] and list( + set(el.nets[:]) & set(signal_list[:]) + ): + pins_to_preserve.extend([i.id for i in el.pins.values()]) + nets_to_preserve.extend(el.nets) + + for i in self.nets.nets.values(): + name = i.name + if name not in all_list and name not in nets_to_preserve: + i.net_object.Delete() + reference_pinsts = [] + reference_prims = [] + for i in self.padstacks.instances.values(): + net_name = i.net_name + id = i.id + if net_name not in all_list and id not in pins_to_preserve: + i.delete() + elif net_name in reference_list and id not in pins_to_preserve: + reference_pinsts.append(i) + for i in self.modeler.primitives: + if i: + net_name = i.net_name + if net_name not in all_list: + i.delete() + elif net_name in reference_list and not i.is_void: + reference_prims.append(i) + self.logger.info_timer("Net clean up") + self.logger.reset_timer() + + if custom_extent and isinstance(custom_extent, list): + if custom_extent[0] != custom_extent[-1]: + custom_extent.append(custom_extent[0]) + custom_extent = [ + [self.number_with_units(i[0], custom_extent_units), self.number_with_units(i[1], custom_extent_units)] + for i in custom_extent + ] + plane = self.modeler.Shape("polygon", points=custom_extent) + _poly = self.modeler.shape_to_polygon_data(plane) + elif custom_extent: + _poly = custom_extent + else: + net_signals = [net.api_object for net in self.layout.nets if net.name in signal_list] + _poly = self._create_extent( + net_signals, + extent_type, + expansion_size, + use_round_corner, + use_pyaedt_extent_computing, + smart_cut=check_terminals, + reference_list=reference_list, + include_pingroups=include_pingroups, + ) + if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1] and extent_defeature > 0: + _poly = _poly.Defeature(extent_defeature) + + if not _poly or _poly.IsNull(): + self._logger.error("Failed to create Extent.") + return False + self.logger.info_timer("Expanded Net Polygon Creation") + self.logger.reset_timer() + _poly_list = convert_py_list_to_net_list([_poly]) + prims_to_delete = [] + poly_to_create = [] + pins_to_delete = [] + + def intersect(poly1, poly2): + if not isinstance(poly2, list): + poly2 = [poly2] + return list(poly1.Intersect(convert_py_list_to_net_list(poly1), convert_py_list_to_net_list(poly2))) + + def subtract(poly, voids): + return poly.Subtract(convert_py_list_to_net_list(poly), convert_py_list_to_net_list(voids)) + + def clean_prim(prim_1): # pragma: no cover + pdata = prim_1.polygon_data.edb_api + int_data = _poly.GetIntersectionType(pdata) + if int_data == 2: + return + elif int_data == 0: + prims_to_delete.append(prim_1) + else: + list_poly = intersect(_poly, pdata) + if list_poly: + net = prim_1.net_name + voids = prim_1.voids + for p in list_poly: + if p.IsNull(): + continue + # points = list(p.Points) + list_void = [] + if voids: + voids_data = [void.polygon_data.edb_api for void in voids] + list_prims = subtract(p, voids_data) + for prim in list_prims: + if not prim.IsNull(): + poly_to_create.append([prim, prim_1.layer_name, net, list_void]) + else: + poly_to_create.append([p, prim_1.layer_name, net, list_void]) + + prims_to_delete.append(prim_1) + + def pins_clean(pinst): + if not pinst.in_polygon(_poly, include_partial=include_partial, simple_check=simple_pad_check): + pins_to_delete.append(pinst) + + if not simple_pad_check: + pad_cores = 1 + else: + pad_cores = number_of_threads + with ThreadPoolExecutor(pad_cores) as pool: + pool.map(lambda item: pins_clean(item), reference_pinsts) + + for pin in pins_to_delete: + pin.delete() + + self.logger.info_timer("Padstack Instances removal completed") + self.logger.reset_timer() + + with ThreadPoolExecutor(number_of_threads) as pool: + pool.map(lambda item: clean_prim(item), reference_prims) + + for el in poly_to_create: + self.modeler.create_polygon(el[0], el[1], net_name=el[2], voids=el[3]) + + for prim in prims_to_delete: + prim.delete() + self.logger.info_timer("Primitives cleanup completed") + self.logger.reset_timer() + + i = 0 + for _, val in self.components.components.items(): + if val.numpins == 0: + val.edbcomponent.Delete() + i += 1 + i += 1 + self.logger.info("Deleted {} additional components".format(i)) + if remove_single_pin_components: + self.components.delete_single_pin_rlc() + self.logger.info_timer("Single Pins components deleted") + + self.components.refresh_components() + if output_aedb_path: + self.save_edb() + self.logger.info_timer("Cutout completed.", timer_start) + self.logger.reset_timer() + return True + + @pyedb_function_handler() + def create_cutout_multithread( + self, + signal_list=[], + reference_list=["GND"], + extent_type="Conforming", + expansion_size=0.002, + use_round_corner=False, + number_of_threads=4, + custom_extent=None, + output_aedb_path=None, + remove_single_pin_components=False, + use_pyaedt_extent_computing=False, + extent_defeature=0, + ): + """Create a cutout using an approach entirely based on pyaedt. + It does in sequence: + - delete all nets not in list, + - create a extent of the nets, + - check and delete all vias not in the extent, + - check and delete all the primitives not in extent, + - check and intersect all the primitives that intersect the extent. + + + .. deprecated:: 0.6.58 + Use new method :func:`cutout` instead. + + Parameters + ---------- + signal_list : list + List of signal strings. + reference_list : list, optional + List of references to add. The default is ``["GND"]``. + extent_type : str, optional + Type of the extension. Options are ``"Conforming"``, ``"ConvexHull"``, and + ``"Bounding"``. The default is ``"Conforming"``. + expansion_size : float, str, optional + Expansion size ratio in meters. The default is ``0.002``. + use_round_corner : bool, optional + Whether to use round corners. The default is ``False``. + number_of_threads : int, optional + Number of thread to use. Default is 4 + custom_extent : list, optional + Custom extent to use for the cutout. It has to be a list of points [[x1,y1],[x2,y2]....] or + Edb PolygonData object. In this case, both signal_list and reference_list will be cut. + output_aedb_path : str, optional + Full path and name for the new AEDB file. If None, then current aedb will be cutout. + remove_single_pin_components : bool, optional + Remove all Single Pin RLC after the cutout is completed. Default is `False`. + use_pyaedt_extent_computing : bool, optional + Whether to use pyaedt extent computing (experimental). + extent_defeature : float, optional + Defeature the cutout before applying it to produce simpler geometry for mesh (Experimental). + It applies only to Conforming bounding box. Default value is ``0`` which disable it. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + Examples + -------- + >>> edb = Edb(r'C:\\test.aedb', edbversion="2022.2") + >>> edb.logger.info_timer("Edb Opening") + >>> edb.logger.reset_timer() + >>> start = time.time() + >>> signal_list = [] + >>> for net in edb.nets.nets.keys(): + >>> if "3V3" in net: + >>> signal_list.append(net) + >>> power_list = ["PGND"] + >>> edb.create_cutout_multithread(signal_list=signal_list, reference_list=power_list, extent_type="Conforming") + >>> end_time = str((time.time() - start)/60) + >>> edb.logger.info("Total pyaedt cutout time in min %s", end_time) + >>> edb.nets.plot(signal_list, None, color_by_net=True) + >>> edb.nets.plot(power_list, None, color_by_net=True) + >>> edb.save_edb() + >>> edb.close_edb() + + """ + warnings.warn("Use new method `cutout` instead.", DeprecationWarning) + return self._create_cutout_multithread( + signal_list=signal_list, + reference_list=reference_list, + extent_type=extent_type, + expansion_size=expansion_size, + use_round_corner=use_round_corner, + number_of_threads=number_of_threads, + custom_extent=custom_extent, + output_aedb_path=output_aedb_path, + remove_single_pin_components=remove_single_pin_components, + use_pyaedt_extent_computing=use_pyaedt_extent_computing, + extent_defeature=extent_defeature, + ) + + @pyedb_function_handler() + def get_conformal_polygon_from_netlist(self, netlist=None): + """Return an EDB conformal polygon based on a netlist. + + Parameters + ---------- + + netlist : List of net names. + list[str] + + Returns + ------- + :class:`Edb.Cell.Primitive.Polygon` + Edb polygon object. + + """ + temp_edb_path = self.edbpath[:-5] + "_temp_aedb.aedb" + shutil.copytree(self.edbpath, temp_edb_path) + temp_edb = Edb(temp_edb_path) + for via in list(temp_edb.padstacks.instances.values()): + via.pin.Delete() + if netlist: + nets = [net.net_obj for net in temp_edb.layout.nets if net.name in netlist] + _poly = temp_edb.layout.expanded_extent( + nets, self.edb_api.geometry.extent_type.Conforming, 0.0, True, True, 1 + ) + else: + nets = [net.api_object for net in temp_edb.layout.nets if "gnd" in net.name.lower()] + _poly = temp_edb.layout.expanded_extent( + nets, self.edb_api.geometry.extent_type.Conforming, 0.0, True, True, 1 + ) + temp_edb.close_edb() + if _poly: + return _poly + else: + return False + + @pyedb_function_handler() + def number_with_units(self, value, units=None): + """Convert a number to a string with units. If value is a string, it's returned as is. + + Parameters + ---------- + value : float, int, str + Input number or string. + units : optional + Units for formatting. The default is ``None``, which uses ``"meter"``. + + Returns + ------- + str + String concatenating the value and unit. + + """ + if units is None: + units = "meter" + if isinstance(value, str): + return value + else: + return "{0}{1}".format(value, units) + + @pyedb_function_handler() + def arg_with_dim(self, Value, sUnits): + """Convert a number to a string with units. If value is a string, it's returned as is. + + .. deprecated:: 0.6.56 + Use :func:`number_with_units` property instead. + + Parameters + ---------- + Value : float, int, str + Input number or string. + sUnits : optional + Units for formatting. The default is ``None``, which uses ``"meter"``. + + Returns + ------- + str + String concatenating the value and unit. + + """ + warnings.warn("Use :func:`number_with_units` instead.", DeprecationWarning) + return self.number_with_units(Value, sUnits) + + def _decompose_variable_value(self, value, unit_system=None): + val, units = decompose_variable_value(value) + if units and unit_system and units in AEDT_UNITS[unit_system]: + return AEDT_UNITS[unit_system][units] * val + else: + return val + + @pyedb_function_handler() + def _create_cutout_on_point_list( + self, + point_list, + units="mm", + output_aedb_path=None, + open_cutout_at_end=True, + nets_to_include=None, + include_partial_instances=False, + keep_voids=True, + ): + if point_list[0] != point_list[-1]: + point_list.append(point_list[0]) + point_list = [[self.number_with_units(i[0], units), self.number_with_units(i[1], units)] for i in point_list] + plane = self.modeler.Shape("polygon", points=point_list) + polygonData = self.modeler.shape_to_polygon_data(plane) + _ref_nets = [] + if nets_to_include: + self.logger.info("Creating cutout on {} nets.".format(len(nets_to_include))) + else: + self.logger.info("Creating cutout on all nets.") # pragma: no cover + + # Check Padstack Instances overlapping the cutout + pinstance_to_add = [] + if include_partial_instances: + if nets_to_include: + pinst = [i for i in list(self.padstacks.instances.values()) if i.net_name in nets_to_include] + else: + pinst = [i for i in list(self.padstacks.instances.values())] + for p in pinst: + if p.in_polygon(polygonData): + pinstance_to_add.append(p) + # validate references in layout + for _ref in self.nets.nets: + if nets_to_include: + if _ref in nets_to_include: + _ref_nets.append(self.nets.nets[_ref].net_object) + else: + _ref_nets.append(self.nets.nets[_ref].net_object) # pragma: no cover + if keep_voids: + voids = [p for p in self.modeler.circles if p.is_void] + voids2 = [p for p in self.modeler.polygons if p.is_void] + voids.extend(voids2) + else: + voids = [] + voids_to_add = [] + for circle in voids: + if polygonData.GetIntersectionType(circle.primitive_object.GetPolygonData()) >= 3: + voids_to_add.append(circle) + + _netsClip = convert_py_list_to_net_list(_ref_nets) + # net_signals = convert_py_list_to_net_list([], type(_ref_nets[0])) + + # Create new cutout cell/design + _cutout = self.active_cell.CutOut(_netsClip, _netsClip, polygonData) + layout = _cutout.GetLayout() + cutout_obj_coll = list(layout.PadstackInstances) + ids = [] + for lobj in cutout_obj_coll: + ids.append(lobj.GetId()) + + if include_partial_instances: + p_missing = [i for i in pinstance_to_add if i.id not in ids] + self.logger.info("Added {} padstack instances after cutout".format(len(p_missing))) + for p in p_missing: + position = self.edb_api.geometry.point_data( + self.edb_value(p.position[0]), self.edb_value(p.position[1]) + ) + net = self.nets.find_or_create_net(p.net_name) + rotation = self.edb_value(p.rotation) + sign_layers = list(self.stackup.signal_layers.keys()) + if not p.start_layer: # pragma: no cover + fromlayer = self.stackup.signal_layers[sign_layers[0]]._edb_layer + else: + fromlayer = self.stackup.signal_layers[p.start_layer]._edb_layer + + if not p.stop_layer: # pragma: no cover + tolayer = self.stackup.signal_layers[sign_layers[-1]]._edb_layer + else: + tolayer = self.stackup.signal_layers[p.stop_layer]._edb_layer + padstack = None + for pad in list(self.padstacks.definitions.keys()): + if pad == p.padstack_definition: + padstack = self.padstacks.definitions[pad].edb_padstack + padstack_instance = self.edb_api.cell.primitive.padstack_instance.create( + _cutout.GetLayout(), + net, + p.name, + padstack, + position, + rotation, + fromlayer, + tolayer, + None, + None, + ) + padstack_instance.SetIsLayoutPin(p.is_pin) + break + + for void_circle in voids_to_add: + if void_circle.type == "Circle": + if is_ironpython: # pragma: no cover + res, center_x, center_y, radius = void_circle.primitive_object.GetParameters() + else: + res, center_x, center_y, radius = void_circle.primitive_object.GetParameters(0.0, 0.0, 0.0) + cloned_circle = self.edb_api.cell.primitive.circle.create( + layout, + void_circle.layer_name, + void_circle.net, + self.edb_value(center_x), + self.edb_value(center_y), + self.edb_value(radius), + ) + cloned_circle.SetIsNegative(True) + elif void_circle.type == "Polygon": + cloned_polygon = self.edb_api.cell.primitive.polygon.create( + layout, void_circle.layer_name, void_circle.net, void_circle.primitive_object.GetPolygonData() + ) + cloned_polygon.SetIsNegative(True) + layers = [i for i in list(self.stackup.signal_layers.keys())] + for layer in layers: + layer_primitves = self.modeler.get_primitives(layer_name=layer) + if len(layer_primitves) == 0: + self.modeler.create_polygon(plane, layer, net_name="DUMMY") + self.logger.info("Cutout %s created correctly", _cutout.GetName()) + id = 1 + for _setup in self.active_cell.SimulationSetups: + # Empty string '' if coming from setup copy and don't set explicitly. + _setup_name = _setup.GetName() + if "GetSimSetupInfo" in dir(_setup): + # setup is an Ansys.Ansoft.Edb.Utility.HFSSSimulationSetup object + _hfssSimSetupInfo = _setup.GetSimSetupInfo() + _hfssSimSetupInfo.Name = "HFSS Setup " + str(id) # Set name of analysis setup + # Write the simulation setup info into the cell/design setup + _setup.SetSimSetupInfo(_hfssSimSetupInfo) + _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design + id += 1 + else: + _cutout.AddSimulationSetup(_setup) # Add simulation setup to the cutout design + + _dbCells = [_cutout] + if output_aedb_path: + db2 = self.create(output_aedb_path) + if not db2.Save(): + self.logger.error("Failed to create new Edb. Check if the path already exists and remove it.") + return False + _dbCells = convert_py_list_to_net_list(_dbCells) + cell_copied = db2.CopyCells(_dbCells) # Copies cutout cell/design to db2 project + cell = list(cell_copied)[0] + cell.SetName(os.path.basename(output_aedb_path[:-5])) + db2.Save() + for c in list(self.active_db.TopCircuitCells): + if c.GetName() == _cutout.GetName(): + c.Delete() + if open_cutout_at_end: # pragma: no cover + _success = db2.Save() + self._db = db2 + self.edbpath = output_aedb_path + self._active_cell = cell + self.edbpath = self.directory + self._init_objects() + else: + db2.Close() + source = os.path.join(output_aedb_path, "edb.def.tmp") + target = os.path.join(output_aedb_path, "edb.def") + self._wait_for_file_release(file_to_release=output_aedb_path) + if os.path.exists(source) and not os.path.exists(target): + try: + shutil.copy(source, target) + self.logger.warning("aedb def file manually created.") + except: + pass + return True + + @pyedb_function_handler() + def create_cutout_on_point_list( + self, + point_list, + units="mm", + output_aedb_path=None, + open_cutout_at_end=True, + nets_to_include=None, + include_partial_instances=False, + keep_voids=True, + ): + """Create a cutout on a specified shape and save it to a new AEDB file. + + .. deprecated:: 0.6.58 + Use new method :func:`cutout` instead. + + Parameters + ---------- + point_list : list + Points list defining the cutout shape. + units : str + Units of the point list. The default is ``"mm"``. + output_aedb_path : str, optional + Full path and name for the new AEDB file. + The aedb folder shall not exist otherwise the method will return ``False``. + open_cutout_at_end : bool, optional + Whether to open the cutout at the end. The default is ``True``. + nets_to_include : list, optional + List of nets to include in the cutout. The default is ``None``, in + which case all nets are included. + include_partial_instances : bool, optional + Whether to include padstack instances that have bounding boxes intersecting with point list polygons. + This operation may slow down the cutout export. + keep_voids : bool + Boolean used for keep or not the voids intersecting the polygon used for clipping the layout. + Default value is ``True``, ``False`` will remove the voids. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + warnings.warn("Use new method `cutout` instead.", DeprecationWarning) + return self._create_cutout_on_point_list( + point_list=point_list, + units=units, + output_aedb_path=output_aedb_path, + open_cutout_at_end=open_cutout_at_end, + nets_to_include=nets_to_include, + include_partial_instances=include_partial_instances, + keep_voids=keep_voids, + ) + + @pyedb_function_handler() + def write_export3d_option_config_file(self, path_to_output, config_dictionaries=None): + """Write the options for a 3D export to a configuration file. + + Parameters + ---------- + path_to_output : str + Full path to the configuration file to save 3D export options to. + + config_dictionaries : dict, optional + Configuration dictionaries. The default is ``None``. + + """ + option_config = { + "UNITE_NETS": 1, + "ASSIGN_SOLDER_BALLS_AS_SOURCES": 0, + "Q3D_MERGE_SOURCES": 0, + "Q3D_MERGE_SINKS": 0, + "CREATE_PORTS_FOR_PWR_GND_NETS": 0, + "PORTS_FOR_PWR_GND_NETS": 0, + "GENERATE_TERMINALS": 0, + "SOLVE_CAPACITANCE": 0, + "SOLVE_DC_RESISTANCE": 0, + "SOLVE_DC_INDUCTANCE_RESISTANCE": 1, + "SOLVE_AC_INDUCTANCE_RESISTANCE": 0, + "CreateSources": 0, + "CreateSinks": 0, + "LAUNCH_Q3D": 0, + "LAUNCH_HFSS": 0, + } + if config_dictionaries: + for el, val in config_dictionaries.items(): + option_config[el] = val + with open(os.path.join(path_to_output, "options.config"), "w") as f: + for el, val in option_config.items(): + f.write(el + " " + str(val) + "\n") + return os.path.join(path_to_output, "options.config") + + @pyedb_function_handler() + def export_hfss(self, path_to_output, net_list=None, num_cores=None, aedt_file_name=None, hidden=False): + """Export EDB to HFSS. + + Parameters + ---------- + path_to_output : str + Full path and name for saving the AEDT file. + net_list : list, optional + List of nets to export if only certain ones are to be exported. + The default is ``None``, in which case all nets are eported. + num_cores : int, optional + Number of cores to use for the export. The default is ``None``. + aedt_file_name : str, optional + Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``, + in which case the default name is used. + hidden : bool, optional + Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden. + + Returns + ------- + str + Full path to the AEDT file. + + Examples + -------- + + >>> from pyedb import Edb + + >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2") + + >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0} + >>> edb.write_export3d_option_config_file(r"C:\temp", options_config) + >>> edb.export_hfss(r"C:\temp") + "C:\\temp\\hfss_siwave.aedt" + + """ + siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path) + return siwave_s.export_3d_cad("HFSS", path_to_output, net_list, num_cores, aedt_file_name, hidden=hidden) + + @pyedb_function_handler() + def export_q3d(self, path_to_output, net_list=None, num_cores=None, aedt_file_name=None, hidden=False): + """Export EDB to Q3D. + + Parameters + ---------- + path_to_output : str + Full path and name for saving the AEDT file. + net_list : list, optional + List of nets to export only if certain ones are to be exported. + The default is ``None``, in which case all nets are eported. + num_cores : int, optional + Number of cores to use for the export. The default is ``None``. + aedt_file_name : str, optional + Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``, + in which case the default name is used. + hidden : bool, optional + Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden. + + Returns + ------- + str + Full path to the AEDT file. + + Examples + -------- + + >>> from pyaedt import Edb + + >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2") + + >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0} + >>> edb.write_export3d_option_config_file(r"C:\temp", options_config) + >>> edb.export_q3d(r"C:\temp") + "C:\\temp\\q3d_siwave.aedt" + + """ + + siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path) + return siwave_s.export_3d_cad( + "Q3D", path_to_output, net_list, num_cores=num_cores, aedt_file_name=aedt_file_name, hidden=hidden + ) + + @pyedb_function_handler() + def export_maxwell(self, path_to_output, net_list=None, num_cores=None, aedt_file_name=None, hidden=False): + """Export EDB to Maxwell 3D. + + Parameters + ---------- + path_to_output : str + Full path and name for saving the AEDT file. + net_list : list, optional + List of nets to export only if certain ones are to be + exported. The default is ``None``, in which case all nets are exported. + num_cores : int, optional + Number of cores to use for the export. The default is ``None.`` + aedt_file_name : str, optional + Name of the AEDT output file without the ``.aedt`` extension. The default is ``None``, + in which case the default name is used. + hidden : bool, optional + Open Siwave in embedding mode. User will only see Siwave Icon but UI will be hidden. + + Returns + ------- + str + Full path to the AEDT file. + + Examples + -------- + + >>> from pyaedt import Edb + + >>> edb = Edb(edbpath=r"C:\temp\myproject.aedb", edbversion="2021.2") + + >>> options_config = {'UNITE_NETS' : 1, 'LAUNCH_Q3D' : 0} + >>> edb.write_export3d_option_config_file(r"C:\temp", options_config) + >>> edb.export_maxwell(r"C:\temp") + "C:\\temp\\maxwell_siwave.aedt" + + """ + siwave_s = SiwaveSolve(self.edbpath, aedt_installer_path=self.base_path) + return siwave_s.export_3d_cad( + "Maxwell", + path_to_output, + net_list, + num_cores=num_cores, + aedt_file_name=aedt_file_name, + hidden=hidden, + ) + + @pyedb_function_handler() + def solve_siwave(self): + """Close EDB and solve it with Siwave. + + Returns + ------- + str + Siwave project path. + """ + process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion) + try: + self.close() + except: + pass + process.solve() + return self.edbpath[:-5] + ".siw" + + @pyedb_function_handler() + def export_siwave_dc_results( + self, + siwave_project, + solution_name, + output_folder=None, + html_report=True, + vias=True, + voltage_probes=True, + current_sources=True, + voltage_sources=True, + power_tree=True, + loop_res=True, + ): + """Close EDB and solve it with Siwave. + + Parameters + ---------- + siwave_project : str + Siwave full project name. + solution_name : str + Siwave DC Analysis name. + output_folder : str, optional + Ouptu folder where files will be downloaded. + html_report : bool, optional + Either if generate or not html report. Default is `True`. + vias : bool, optional + Either if generate or not vias report. Default is `True`. + voltage_probes : bool, optional + Either if generate or not voltage probe report. Default is `True`. + current_sources : bool, optional + Either if generate or not current source report. Default is `True`. + voltage_sources : bool, optional + Either if generate or not voltage source report. Default is `True`. + power_tree : bool, optional + Either if generate or not power tree image. Default is `True`. + loop_res : bool, optional + Either if generate or not loop resistance report. Default is `True`. + + Returns + ------- + list + List of files generated. + """ + process = SiwaveSolve(self.edbpath, aedt_version=self.edbversion) + try: + self.close() + except: + pass + return process.export_dc_report( + siwave_project, + solution_name, + output_folder, + html_report, + vias, + voltage_probes, + current_sources, + voltage_sources, + power_tree, + loop_res, + hidden=True, + ) + + @pyedb_function_handler() + def variable_exists(self, variable_name): + """Check if a variable exists or not. + + Returns + ------- + tuple of bool and VaribleServer + It returns a booleand to check if the variable exists and the variable + server that should contain the variable. + """ + if "$" in variable_name: + if variable_name.index("$") == 0: + var_server = self.active_db.GetVariableServer() + + else: + var_server = self.active_cell.GetVariableServer() + + else: + var_server = self.active_cell.GetVariableServer() + + variables = var_server.GetAllVariableNames() + if variable_name in list(variables): + return True, var_server + return False, var_server + + @pyedb_function_handler() + def get_variable(self, variable_name): + """Return Variable Value if variable exists. + + Parameters + ---------- + variable_name + + Returns + ------- + :class:`pyaedt.edb_core.edb_data.edbvalue.EdbValue` + """ + var_server = self.variable_exists(variable_name) + if var_server[0]: + tuple_value = var_server[1].GetVariableValue(variable_name) + return EdbValue(tuple_value[1]) + self.logger.info("Variable %s doesn't exists.", variable_name) + return None + + @pyedb_function_handler() + def add_project_variable(self, variable_name, variable_value): + """Add a variable to edb database (project). The variable will have the prefix `$`. + + ..note:: + User can use also the setitem to create or assign a variable. See example below. + + Parameters + ---------- + variable_name : str + Name of the variable. Name can be provided without ``$`` prefix. + variable_value : str, float + Value of the variable with units. + + Returns + ------- + tuple + Tuple containing the ``AddVariable`` result and variable server. + + Examples + -------- + + >>> from pyaedt import Edb + >>> edb_app = Edb() + >>> boolean_1, ant_length = edb_app.add_project_variable("my_local_variable", "1cm") + >>> print(edb_app["$my_local_variable"]) #using getitem + >>> edb_app["$my_local_variable"] = "1cm" #using setitem + + """ + if not variable_name.startswith("$"): + variable_name = "${}".format(variable_name) + return self.add_design_variable(variable_name=variable_name, variable_value=variable_value) + + @pyedb_function_handler() + def add_design_variable(self, variable_name, variable_value, is_parameter=False): + """Add a variable to edb. The variable can be a design one or a project variable (using ``$`` prefix). + + ..note:: + User can use also the setitem to create or assign a variable. See example below. + + Parameters + ---------- + variable_name : str + Name of the variable. To added the variable as a project variable, the name + must begin with ``$``. + variable_value : str, float + Value of the variable with units. + is_parameter : bool, optional + Whether to add the variable as a local variable. The default is ``False``. + When ``True``, the variable is added as a parameter default. + + Returns + ------- + tuple + Tuple containing the ``AddVariable`` result and variable server. + + Examples + -------- + + >>> from pyaedt import Edb + >>> edb_app = Edb() + >>> boolean_1, ant_length = edb_app.add_design_variable("my_local_variable", "1cm") + >>> print(edb_app["my_local_variable"]) #using getitem + >>> edb_app["my_local_variable"] = "1cm" #using setitem + >>> boolean_2, para_length = edb_app.change_design_variable_value("my_parameter", "1m", is_parameter=True + >>> boolean_3, project_length = edb_app.change_design_variable_value("$my_project_variable", "1m") + + + """ + var_server = self.variable_exists(variable_name) + if not var_server[0]: + var_server[1].AddVariable(variable_name, self.edb_value(variable_value), is_parameter) + return True, var_server[1] + self.logger.error("Variable %s already exists.", variable_name) + return False, var_server[1] + + @pyedb_function_handler() + def change_design_variable_value(self, variable_name, variable_value): + """Change a variable value. + + ..note:: + User can use also the getitem to read the variable value. See example below. + + Parameters + ---------- + variable_name : str + Name of the variable. + variable_value : str, float + Value of the variable with units. + + Returns + ------- + tuple + Tuple containing the ``SetVariableValue`` result and variable server. + + Examples + -------- + + >>> from pyaedt import Edb + >>> edb_app = Edb() + >>> boolean, ant_length = edb_app.add_design_variable("ant_length", "1cm") + >>> boolean, ant_length = edb_app.change_design_variable_value("ant_length", "1m") + >>> print(edb_app["ant_length"]) #using getitem + """ + var_server = self.variable_exists(variable_name) + if var_server[0]: + var_server[1].SetVariableValue(variable_name, self.edb_value(variable_value)) + return True, var_server[1] + self.logger.error("Variable %s does not exists.", variable_name) + return False, var_server[1] + + @pyedb_function_handler() + def get_bounding_box(self): + """Get the layout bounding box. + + Returns + ------- + list of list of double + Bounding box as a [lower-left X, lower-left Y], [upper-right X, upper-right Y]) pair in meters. + """ + layout_inst = self.layout.layout_instance + all_layout_obj_inst = layout_inst.query_layout_obj_instances() + obj_poly_data = [obj.get_bbox(False) for obj in all_layout_obj_inst] + layout_bbox = geometry.polygon_data.PolygonData.bbox_of_polygons(obj_poly_data) + if layout_bbox: + return [[layout_bbox[0].x.value, layout_bbox[0].y.value], [layout_bbox[1].x.value, layout_bbox[1].y.value]] + return False + + @pyedb_function_handler() + def build_simulation_project(self, simulation_setup): + # type: (SimulationConfiguration) -> bool + """Build a ready-to-solve simulation project. + + Parameters + ---------- + simulation_setup : :class:`pyaedt.edb_core.edb_data.simulation_configuration.SimulationConfiguration` object. + SimulationConfiguration object that can be instantiated or directly loaded with a + configuration file. + + Returns + ------- + bool + ``True`` when successful, False when ``Failed``. + + Examples + -------- + + >>> from pyaedt import Edb + >>> from pyaedt.edb_core.edb_data.simulation_configuration import SimulationConfiguration + >>> config_file = path_configuration_file + >>> source_file = path_to_edb_folder + >>> edb = Edb(source_file) + >>> sim_setup = SimulationConfiguration(config_file) + >>> edb.build_simulation_project(sim_setup) + >>> edb.save_edb() + >>> edb.close_edb() + """ + self.logger.info("Building simulation project.") + legacy_name = self.edbpath + if simulation_setup.output_aedb: + self.save_edb_as(simulation_setup.output_aedb) + try: + if simulation_setup.signal_layer_etching_instances: + for layer in simulation_setup.signal_layer_etching_instances: + if layer in self.stackup.layers: + idx = simulation_setup.signal_layer_etching_instances.index(layer) + if len(simulation_setup.etching_factor_instances) > idx: + self.stackup[layer].etch_factor = float(simulation_setup.etching_factor_instances[idx]) + + if not simulation_setup.signal_nets and simulation_setup.components: + nets_to_include = [] + pnets = list(self.nets.power_nets.keys())[:] + for el in simulation_setup.components: + nets_to_include.append([i for i in self.components[el].nets if i not in pnets]) + simulation_setup.signal_nets = [ + i + for i in list(set.intersection(*map(set, nets_to_include))) + if i not in simulation_setup.power_nets and i != "" + ] + self.nets.classify_nets(simulation_setup.power_nets, simulation_setup.signal_nets) + if not simulation_setup.power_nets or not simulation_setup.signal_nets: + self.logger.info("Disabling cutout as no signals or power nets have been defined.") + simulation_setup.do_cutout_subdesign = False + if simulation_setup.do_cutout_subdesign: + self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type)) + if simulation_setup.use_default_cutout: + old_cell_name = self.active_cell.GetName() + if self.cutout( + signal_list=simulation_setup.signal_nets, + reference_list=simulation_setup.power_nets, + expansion_size=simulation_setup.cutout_subdesign_expansion, + use_round_corner=simulation_setup.cutout_subdesign_round_corner, + extent_type=simulation_setup.cutout_subdesign_type, + use_pyaedt_cutout=False, + use_pyaedt_extent_computing=False, + ): + self.logger.info("Cutout processed.") + old_cell = self.active_cell.FindByName( + self.db, self.edb_api.cell.CellType.CircuitCell, old_cell_name + ) + if old_cell: + old_cell.Delete() + else: # pragma: no cover + self.logger.error("Cutout failed.") + else: + self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type)) + self.cutout( + signal_list=simulation_setup.signal_nets, + reference_list=simulation_setup.power_nets, + expansion_size=simulation_setup.cutout_subdesign_expansion, + use_round_corner=simulation_setup.cutout_subdesign_round_corner, + extent_type=simulation_setup.cutout_subdesign_type, + use_pyaedt_cutout=True, + use_pyaedt_extent_computing=True, + remove_single_pin_components=True, + ) + self.logger.info("Cutout processed.") + else: + if simulation_setup.include_only_selected_nets: + included_nets = simulation_setup.signal_nets + simulation_setup.power_nets + nets_to_remove = [ + net.name for net in list(self.nets.nets.values()) if not net.name in included_nets + ] + self.nets.delete(nets_to_remove) + self.logger.info("Deleting existing ports.") + map(lambda port: port.Delete(), self.layout.terminals) + map(lambda pg: pg.Delete(), self.layout.pin_groups) + if simulation_setup.solver_type == SolverType.Hfss3dLayout: + if simulation_setup.generate_excitations: + self.logger.info("Creating HFSS ports for signal nets.") + source_type = SourceType.CoaxPort + if not simulation_setup.generate_solder_balls: + source_type = SourceType.CircPort + for cmp in simulation_setup.components: + self.components.create_port_on_component( + cmp, + net_list=simulation_setup.signal_nets, + do_pingroup=False, + reference_net=simulation_setup.power_nets, + port_type=source_type, + ) + if simulation_setup.generate_solder_balls and not self.hfss.set_coax_port_attributes( + simulation_setup + ): # pragma: no cover + self.logger.error("Failed to configure coaxial port attributes.") + self.logger.info("Number of ports: {}".format(self.hfss.get_ports_number())) + self.logger.info("Configure HFSS extents.") + if ( + simulation_setup.generate_solder_balls and simulation_setup.trim_reference_size + ): # pragma: no cover + self.logger.info( + "Trimming the reference plane for coaxial ports: {0}".format( + bool(simulation_setup.trim_reference_size) + ) + ) + self.hfss.trim_component_reference_size(simulation_setup) # pragma: no cover + self.hfss.configure_hfss_extents(simulation_setup) + if not self.hfss.configure_hfss_analysis_setup(simulation_setup): + self.logger.error("Failed to configure HFSS simulation setup.") + if simulation_setup.solver_type == SolverType.SiwaveSYZ: + if simulation_setup.generate_excitations: + for cmp in simulation_setup.components: + self.components.create_port_on_component( + cmp, + net_list=simulation_setup.signal_nets, + do_pingroup=simulation_setup.do_pingroup, + reference_net=simulation_setup.power_nets, + port_type=SourceType.CircPort, + ) + self.logger.info("Configuring analysis setup.") + if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover + self.logger.error("Failed to configure Siwave simulation setup.") + + if simulation_setup.solver_type == SolverType.SiwaveDC: + if simulation_setup.generate_excitations: + self.components.create_source_on_component(simulation_setup.sources) + if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover + self.logger.error("Failed to configure Siwave simulation setup.") + self.padstacks.check_and_fix_via_plating() + self.save_edb() + if not simulation_setup.open_edb_after_build and simulation_setup.output_aedb: + self.close_edb() + self.edbpath = legacy_name + self.open_edb() + return True + except: # pragma: no cover + return False + + @pyedb_function_handler() + def get_statistics(self, compute_area=False): + """Get the EDBStatistics object. + + Returns + ------- + EDBStatistics object from the loaded layout. + """ + return self.modeler.get_layout_statistics(evaluate_area=compute_area, net_list=None) + + @pyedb_function_handler() + def are_port_reference_terminals_connected(self, common_reference=None): + """Check if all terminal references in design are connected. + If the reference nets are different, there is no hope for the terminal references to be connected. + After we have identified a common reference net we need to loop the terminals again to get + the correct reference terminals that uses that net. + + Parameters + ---------- + common_reference : str, optional + Common Reference name. If ``None`` it will be searched in ports terminal. + If a string is passed then all excitations must have such reference assigned. + + Returns + ------- + bool + Either if the ports are connected to reference_name or not. + + Examples + -------- + >>>edb = Edb() + >>> edb.hfss.create_edge_port_vertical(prim_1_id, ["-66mm", "-4mm"], "port_ver") + >>> edb.hfss.create_edge_port_horizontal( + >>> ... prim_1_id, ["-60mm", "-4mm"], prim_2_id, ["-59mm", "-4mm"], "port_hori", 30, "Lower" + >>> ... ) + >>> edb.hfss.create_wave_port(traces[0].id, trace_paths[0][0], "wave_port") + >>> edb.cutout(["Net1"]) + >>> assert edb.are_port_reference_terminals_connected() + """ + all_sources = [i for i in self.excitations.values() if not isinstance(i, (WavePort, GapPort, BundleWavePort))] + all_sources.extend([i for i in self.sources.values()]) + if not all_sources: + return True + self.logger.reset_timer() + if not common_reference: + common_reference = list(set([i.reference_net_name for i in all_sources if i.reference_net_name])) + if len(common_reference) > 1: + self.logger.error("More than 1 reference found.") + return False + if not common_reference: + self.logger.error("No Reference found.") + return False + + common_reference = common_reference[0] + all_sources = [i for i in all_sources if i.net_name != common_reference] + + setList = [ + set(i.reference_object.get_connected_object_id_set()) + for i in all_sources + if i.reference_object and i.reference_net_name == common_reference + ] + if len(setList) != len(all_sources): + self.logger.error("No Reference found.") + return False + cmps = [ + i + for i in list(self.components.resistors.values()) + if i.numpins == 2 and common_reference in i.nets and self._decompose_variable_value(i.res_value) <= 1 + ] + cmps.extend( + [i for i in list(self.components.inductors.values()) if i.numpins == 2 and common_reference in i.nets] + ) + + for cmp in cmps: + found = False + ids = [i.GetId() for i in cmp.pinlist] + for list_obj in setList: + if len(set(ids).intersection(list_obj)) == 1: + for list_obj2 in setList: + if list_obj2 != list_obj and len(set(ids).intersection(list_obj)) == 1: + if (ids[0] in list_obj and ids[1] in list_obj2) or ( + ids[1] in list_obj and ids[0] in list_obj2 + ): + setList[setList.index(list_obj)] = list_obj.union(list_obj2) + setList[setList.index(list_obj2)] = list_obj.union(list_obj2) + found = True + break + if found: + break + + # Get the set intersections for all the ID sets. + iDintersection = set.intersection(*setList) + self.logger.info_timer( + "Terminal reference primitive IDs total intersections = {}\n\n".format(len(iDintersection)) + ) + + # If the intersections are non-zero, the terminal references are connected. + return True if len(iDintersection) > 0 else False + + @pyedb_function_handler() + def new_simulation_configuration(self, filename=None): + # type: (str) -> SimulationConfiguration + """New SimulationConfiguration Object. + + Parameters + ---------- + filename : str, optional + Input config file. + + Returns + ------- + :class:`pyaedt.edb_core.edb_data.simulation_configuration.SimulationConfiguration` + """ + return SimulationConfiguration(filename, self) + + @property + def setups(self): + """Get the dictionary of all EDB HFSS and SIwave setups. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`] or + Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveDCSimulationSetup`] or + Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`] + + """ + for i in list(self.active_cell.SimulationSetups): + if i.GetName() not in self._setups: + if i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kHFSS: + self._setups[i.GetName()] = HfssSimulationSetup(self, i.GetName(), i) + elif i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kSIWave: + self._setups[i.GetName()] = SiwaveSYZSimulationSetup(self, i.GetName(), i) + elif i.GetType() == self.edb_api.utility.utility.SimulationSetupType.kSIWaveDCIR: + self._setups[i.GetName()] = SiwaveDCSimulationSetup(self, i.GetName(), i) + return self._setups + + @property + def hfss_setups(self): + """Active HFSS setup in EDB. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup`] + + """ + return {name: i for name, i in self.setups.items() if i.setup_type == "kHFSS"} + + @property + def siwave_dc_setups(self): + """Active Siwave DC IR Setups. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveDCSimulationSetup`] + """ + return {name: i for name, i in self.setups.items() if i.setup_type == "kSIWaveDCIR"} + + @property + def siwave_ac_setups(self): + """Active Siwave SYZ setups. + + Returns + ------- + Dict[str, :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup`] + """ + return {name: i for name, i in self.setups.items() if i.setup_type == "kSIWave"} + + def create_hfss_setup(self, name=None): + """Create a setup from a template. + + Parameters + ---------- + name : str, optional + Setup name. + + Returns + ------- + :class:`pyaedt.edb_core.edb_data.hfss_simulation_setup_data.HfssSimulationSetup` + + Examples + -------- + >>> setup1 = edbapp.create_hfss_setup("setup1") + >>> setup1.hfss_port_settings.max_delta_z0 = 0.5 + """ + if name in self.setups: + return False + setup = HfssSimulationSetup(self, name) + self._setups[name] = setup + return setup + + @pyedb_function_handler() + def create_siwave_syz_setup(self, name=None): + """Create a setup from a template. + + Parameters + ---------- + name : str, optional + Setup name. + + Returns + ------- + :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup` + + Examples + -------- + >>> setup1 = edbapp.create_siwave_syz_setup("setup1") + >>> setup1.add_frequency_sweep(frequency_sweep=[ + ... ["linear count", "0", "1kHz", 1], + ... ["log scale", "1kHz", "0.1GHz", 10], + ... ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], + ... ]) + """ + if not name: + name = generate_unique_name("Siwave_SYZ") + if name in self.setups: + return False + setup = SiwaveSYZSimulationSetup(self, name) + self._setups[name] = setup + return setup + + @pyedb_function_handler() + def create_siwave_dc_setup(self, name=None): + """Create a setup from a template. + + Parameters + ---------- + name : str, optional + Setup name. + + Returns + ------- + :class:`pyaedt.edb_core.edb_data.siwave_simulation_setup_data.SiwaveSYZSimulationSetup` + + Examples + -------- + >>> setup1 = edbapp.create_siwave_dc_setup("setup1") + >>> setup1.mesh_bondwires = True + + """ + if not name: + name = generate_unique_name("Siwave_DC") + if name in self.setups: + return False + setup = SiwaveDCSimulationSetup(self, name) + self._setups[name] = setup + return setup + + @pyedb_function_handler() + def calculate_initial_extent(self, expansion_factor): + """Compute a float representing the larger number between the dielectric thickness or trace width + multiplied by the nW factor. The trace width search is limited to nets with ports attached. + + Parameters + ---------- + expansion_factor : float + Value for the width multiplier (nW factor). + + Returns + ------- + float + """ + nets = [] + for port in self.excitations.values(): + nets.append(port.net_name) + for port in self.sources.values(): + nets.append(port.net_name) + nets = list(set(nets)) + max_width = 0 + for net in nets: + for primitive in self.nets[net].primitives: + if primitive.type == "Path": + max_width = max(max_width, primitive.width) + + for layer in list(self.stackup.dielectric_layers.values()): + max_width = max(max_width, layer.thickness) + + max_width = max_width * expansion_factor + self.logger.info("The W factor is {}, The initial extent = {:e}".format(expansion_factor, max_width)) + return max_width + + @pyedb_function_handler() + def copy_zones(self, working_directory=None): + """Copy multizone EDB project to one new edb per zone. + + Parameters + ---------- + working_directory : str + Directory path where all EDB project are copied, if empty will use the current EDB project. + + Returns + ------- + dict[str](int, EDB PolygonData) + Return a dictionary with edb path as key and tuple Zone Id as first item and EDB polygon Data defining + the region as second item. + + """ + if working_directory: + if not os.path.isdir(working_directory): + os.mkdir(working_directory) + else: + shutil.rmtree(working_directory) + os.mkdir(working_directory) + else: + working_directory = os.path.dirname(self.edbpath) + zone_primitives = list(self.layout.zone_primitives) + zone_ids = list(self.stackup._layer_collection.GetZoneIds()) + edb_zones = {} + if not self.setups: + self.siwave.add_siwave_syz_analysis() + self.save_edb() + for zone_primitive in zone_primitives: + edb_zone_path = os.path.join( + working_directory, "{}_{}".format(zone_primitive.GetId(), os.path.basename(self.edbpath)) + ) + shutil.copytree(self.edbpath, edb_zone_path) + poly_data = zone_primitive.GetPolygonData() + if self.version[0] >= 10: + edb_zones[edb_zone_path] = (zone_primitive.GetZoneId(), poly_data) + elif len(zone_primitives) == len(zone_ids): + edb_zones[edb_zone_path] = (zone_ids[0], poly_data) + else: + self.logger.info( + "Number of zone primitives is not equal to zone number. Zone information will be lost." + "Use Ansys 2024 R1 or later." + ) + edb_zones[edb_zone_path] = (-1, poly_data) + return edb_zones + + @pyedb_function_handler() + def cutout_multizone_layout(self, zone_dict, common_reference_net=None): + """Create a multizone project cutout. + + Parameters + ---------- + zone_dict : dict[str](EDB PolygonData) + Dictionary with EDB path as key and EDB PolygonData as value defining the zone region. + This dictionary is returned from the command copy_zones(): + >>> edb = Edb(edb_file) + >>> zone_dict = edb.copy_zones(r"C:\Temp\test") + + common_reference_net : str + the common reference net name. This net name must be provided to provide a valid project. + + Returns + ------- + dict[str][str] , list of str + first dictionary defined_ports with edb name as key and existing port name list as value. Those ports are the + ones defined before processing the multizone clipping. + second is the list of connected port. + + """ + terminals = {} + defined_ports = {} + project_connexions = None + for edb_path, zone_info in zone_dict.items(): + edb = Edb(edbversion=self.edbversion, edbpath=edb_path) + edb.cutout(use_pyaedt_cutout=True, custom_extent=zone_info[1], open_cutout_at_end=True) + if not zone_info[0] == -1: + layers_to_remove = [ + lay.name for lay in list(edb.stackup.layers.values()) if not lay._edb_layer.IsInZone(zone_info[0]) + ] + for layer in layers_to_remove: + edb.stackup.remove_layer(layer) + edb.stackup.stackup_mode = "Laminate" + edb.cutout(use_pyaedt_cutout=True, custom_extent=zone_info[1], open_cutout_at_end=True) + edb.active_cell.SetName(os.path.splitext(os.path.basename(edb_path))[0]) + if common_reference_net: + signal_nets = list(self.nets.signal.keys()) + defined_ports[os.path.splitext(os.path.basename(edb_path))[0]] = list(edb.excitations.keys()) + edb_terminals_info = edb.hfss.create_vertical_circuit_port_on_clipped_traces( + nets=signal_nets, reference_net=common_reference_net, user_defined_extent=zone_info[1] + ) + if edb_terminals_info: + terminals[os.path.splitext(os.path.basename(edb_path))[0]] = edb_terminals_info + project_connexions = self._get_connected_ports_from_multizone_cutout(terminals) + edb.save_edb() + edb.close_edb() + return defined_ports, project_connexions + + @pyedb_function_handler() + def _get_connected_ports_from_multizone_cutout(self, terminal_info_dict): + """Return connected port list from clipped multizone layout. + + Parameters + terminal_info_dict : dict[str][str] + dictionary terminals with edb name as key and created ports name on clipped signal nets. + Dictionary is generated by the command cutout_multizone_layout: + >>> edb = Edb(edb_file) + >>> edb_zones = edb.copy_zones(r"C:\Temp\test") + >>> defined_ports, terminals_info = edb.cutout_multizone_layout(edb_zones, common_reference_net) + >>> project_connexions = get_connected_ports(terminals_info) + + Returns + ------- + list[str] + list of connected ports. + """ + if terminal_info_dict: + tolerance = 1e-8 + connected_ports_list = [] + project_list = list(terminal_info_dict.keys()) + project_combinations = list(combinations(range(0, len(project_list)), 2)) + for comb in project_combinations: + terminal_set1 = terminal_info_dict[project_list[comb[0]]] + terminal_set2 = terminal_info_dict[project_list[comb[1]]] + project1_nets = [t[0] for t in terminal_set1] + project2_nets = [t[0] for t in terminal_set2] + net_with_connected_ports = list(set(project1_nets).intersection(project2_nets)) + if net_with_connected_ports: + for net_name in net_with_connected_ports: + project1_port_info = [term_info for term_info in terminal_set1 if term_info[0] == net_name] + project2_port_info = [term_info for term_info in terminal_set2 if term_info[0] == net_name] + port_list = [p[3] for p in project1_port_info] + [p[3] for p in project2_port_info] + port_combinations = list(combinations(port_list, 2)) + for port_combination in port_combinations: + if not port_combination[0] == port_combination[1]: + port1 = [port for port in terminal_set1 if port[3] == port_combination[0]] + if not port1: + port1 = [port for port in terminal_set2 if port[3] == port_combination[0]] + port2 = [port for port in terminal_set2 if port[3] == port_combination[1]] + if not port2: + port2 = [port for port in terminal_set1 if port[3] == port_combination[1]] + port1 = port1[0] + port2 = port2[0] + if not port1[3] == port2[3]: + port_distance = GeometryOperators.points_distance(port1[1:3], port2[1:3]) + if port_distance < tolerance: + port1_connexion = None + port2_connexion = None + for project_path, port_info in terminal_info_dict.items(): + port1_map = [port for port in port_info if port[3] == port1[3]] + if port1_map: + port1_connexion = (project_path, port1[3]) + port2_map = [port for port in port_info if port[3] == port2[3]] + if port2_map: + port2_connexion = (project_path, port2[3]) + if port1_connexion and port2_connexion: + if ( + not port1_connexion[0] == port2_connexion[0] + or not port1_connexion[1] == port2_connexion[1] + ): + connected_ports_list.append((port1_connexion, port2_connexion)) + return connected_ports_list diff --git a/src/pyedb/grpc/edb_core/edb_data/padstacks_data.py b/src/pyedb/grpc/edb_core/edb_data/padstacks_data.py index 0491e3505a..d2130b0fe1 100644 --- a/src/pyedb/grpc/edb_core/edb_data/padstacks_data.py +++ b/src/pyedb/grpc/edb_core/edb_data/padstacks_data.py @@ -935,6 +935,11 @@ def terminal(self): if not term.is_null: return term + @property + def component(self): + """Return Padstack Instance Component""" + return self._app.components.instances[self._edb_padstackinstance.component.name] + @pyedb_function_handler def _create_terminal(self, name=None): """Create a padstack instance terminal""" @@ -1307,10 +1312,10 @@ def position(self): self._position = [] out = self._edb_padstackinstance.get_position_and_rotation() if self._edb_padstackinstance.component: - out2 = self._edb_padstackinstance.component.transform.transform_point(out[1]) - self._position = [out2.x.value, out2.y.value] + out2 = self._edb_padstackinstance.component.transform.transform_point(geometry.PointData(out[0], out[1])) + self._position = [out2[0].value, out2[1].value] elif out[0]: - self._position = [out[1].x.value, out[1].y.value] + self._position = [out[0].value, out[1].value] return self._position @position.setter @@ -1334,9 +1339,7 @@ def rotation(self): Rotation value for the padstack instance. """ out = self._edb_padstackinstance.get_position_and_rotation() - - if out[0]: - return out[2].value + return out[2].value @property def name(self): diff --git a/src/pyedb/grpc/edb_core/edb_data/primitives_data.py b/src/pyedb/grpc/edb_core/edb_data/primitives_data.py index 7f7dc6d4b5..2986b009d7 100644 --- a/src/pyedb/grpc/edb_core/edb_data/primitives_data.py +++ b/src/pyedb/grpc/edb_core/edb_data/primitives_data.py @@ -97,7 +97,7 @@ def type(self): @property def net(self): - return self._edb_object.net + return self._app.nets.nets[self.net_name] @net.setter def net(self, value): diff --git a/src/pyedb/grpc/edb_core/edb_data/sources.py b/src/pyedb/grpc/edb_core/edb_data/sources.py index 43df72434e..a287229d11 100644 --- a/src/pyedb/grpc/edb_core/edb_data/sources.py +++ b/src/pyedb/grpc/edb_core/edb_data/sources.py @@ -1,8 +1,10 @@ from pyedb.generic.general_methods import pyedb_function_handler from pyedb.generic.constants import NodeType from pyedb.generic.constants import SourceType -from ansys.edb.terminal.terminals import BoundaryType -from ansys.edb.utility.value import Value +import ansys.edb.terminal as terminal +import ansys.edb.utility as utility +#from ansys.edb.terminal.terminals import BoundaryType +#from ansys.edb.utility.value import Value class Node(object): @@ -234,7 +236,7 @@ def __init__(self, name="", edb_pin_group=None, pedb=None): @property def _active_layout(self): - return self._pedb.active_layout + return self._pedb.layout @property def name(self): @@ -278,52 +280,51 @@ def net_name(self): @pyedb_function_handler() def _create_pin_group_terminal(self, is_reference=False): - pg_term = self._edb_pin_group.pin_group_terminal() - pin_group_net = self._edb_pin_group.net - if pin_group_net.is_null: # pragma: no cover + if not self._edb_pin_group.is_null: pin_group_net = self._edb_pin_group.pins[0].net - if pg_term.IsNull(): - return self._pedb.cell.terminal.pin_group_terminal.create( - self._active_layout, - pin_group_net, - self.name, - self._edb_pin_group, - is_reference, + return terminal.PinGroupTerminal.create( + layout=self._active_layout, + net_ref=pin_group_net, + name=self.name, + pin_group=self._edb_pin_group, + is_ref=is_reference, ) else: - return pg_term + return False @pyedb_function_handler() def create_current_source_terminal(self, magnitude=1, phase=0): terminal = self._create_pin_group_terminal() - terminal.boundary_type = BoundaryType.CURRENT_SOURCE - terminal.source_amplitude = Value(magnitude) - terminal.source_phase = Value(phase) + terminal.boundary_type = terminal.BoundaryType.CURRENT_SOURCE + terminal.source_amplitude = utility.Value(magnitude) + terminal.source_phase = utility.Value(phase) return terminal @pyedb_function_handler() def create_voltage_source_terminal(self, magnitude=1, phase=0, impedance=0.001): terminal = self._create_pin_group_terminal() - terminal.boundary_type = BoundaryType.VOLTAGE_SOURCE - terminal.source_amplitude = Value(magnitude) - terminal.source_phase = Value(phase) - terminal.impedance = Value(impedance) + terminal.boundary_type = terminal.BoundaryType.VOLTAGE_SOURCE + terminal.source_amplitude = utility.Value(magnitude) + terminal.source_phase = utility.Value(phase) + terminal.impedance = utility.Value(impedance) return terminal @pyedb_function_handler() def create_voltage_probe_terminal(self, impedance=1000000): terminal = self._create_pin_group_terminal() - terminal.boundary_type = BoundaryType.VOLTAGE_PROBE - terminal.impedance = Value(impedance) + terminal.boundary_type = terminal.BoundaryType.VOLTAGE_PROBE + terminal.impedance = utility.Value(impedance) return terminal @pyedb_function_handler() def create_port_terminal(self, impedance=50): - terminal = self._create_pin_group_terminal() - terminal.boundary_type = BoundaryType.PORT - terminal.impedance = Value(impedance) - terminal.SetIsCircuitPort(True) - return terminal + edb_terminal = self._create_pin_group_terminal() + if not edb_terminal.is_null: + edb_terminal.boundary_type = terminal.BoundaryType.PORT + edb_terminal.impedance = utility.Value(impedance) + edb_terminal.is_circuit_port = True + return edb_terminal + return False @pyedb_function_handler() def delete(self): diff --git a/src/pyedb/grpc/grpc_init/database.py b/src/pyedb/grpc/grpc_init/database.py index e505a6cd52..e398bc858c 100644 --- a/src/pyedb/grpc/grpc_init/database.py +++ b/src/pyedb/grpc/grpc_init/database.py @@ -14,7 +14,7 @@ from pyedb.generic.general_methods import is_linux -class EdbGrpc(object): +class EdbInit(object): """Edb Dot Net Class.""" def __init__(self, edbversion, port): diff --git a/src/pyedb/grpc/hfss.py b/src/pyedb/grpc/hfss.py index 278025b537..481a489c88 100644 --- a/src/pyedb/grpc/hfss.py +++ b/src/pyedb/grpc/hfss.py @@ -2,7 +2,7 @@ This module contains the ``EdbHfss`` class. """ import math - +import ansys.edb.terminal.terminals as terminal from pyedb.grpc.edb_core.edb_data.hfss_extent_info import HfssExtentInfo from pyedb.grpc.edb_core.edb_data.ports import BundleWavePort from pyedb.grpc.edb_core.edb_data.ports import WavePort @@ -447,26 +447,17 @@ def create_coax_port_on_component(self, ref_des_list, net_list): if not isinstance(net_list, list): net_list = [net_list] for ref in ref_des_list: - for _, py_inst in self._pedb.components.components[ref].pins.items(): - if py_inst.net_name in net_list and py_inst.is_pin: - port_name = "{}_{}_{}".format(ref, py_inst.net_name, py_inst.pin.GetName()) - ( - res, - from_layer_pos, - to_layer_pos, - ) = py_inst.pin.GetLayerRange() - if ( - res - and from_layer_pos - and self._edb.cell.terminal.PadstackInstanceTerminal.Create( - self._active_layout, - py_inst.pin.GetNet(), - port_name, - py_inst.pin, - to_layer_pos, - ) - ): - coax.append(port_name) + selected_pins = [pin for pin in list(self._pedb.components[ref].pins.values()) if not pin.net.is_null + and pin.net_name in net_list] + for pin_inst in selected_pins: + port_name = "{}_{}_{}".format(ref, pin_inst.net_name, pin_inst.pin.name) + layer_range = pin_inst.pin.get_layer_range() + if layer_range and terminal.PadstackInstanceTerminal.create(layout=self._layout, + name= port_name, + net=pin_inst.pin.net, + padstack_instance=pin_inst.pin, + layer=layer_range[1]): + coax.append(port_name) return coax @pyedb_function_handler diff --git a/src/pyedb/grpc/nets.py b/src/pyedb/grpc/nets.py index 1f208c4832..5c16603923 100644 --- a/src/pyedb/grpc/nets.py +++ b/src/pyedb/grpc/nets.py @@ -59,7 +59,7 @@ def _edb(self): @property def _active_layout(self): """ """ - return self._pedb.active_layout + return self._pedb.layout @property def _layout(self): @@ -950,9 +950,10 @@ def get_powertree(self, power_net_name, ground_nets): @pyedb_function_handler() def get_net_by_name(self, net_name): """Find a net by name.""" - edb_net = Net.find_by_name(self._active_layout, net_name) - if edb_net is not None: - return edb_net + #edb_net = Net.find_by_name(self._active_layout, net_name) + if net_name in self.nets: + return self.nets[net_name] + return False @pyedb_function_handler() def delete_nets(self, netlist): diff --git a/src/pyedb/grpc/siwave.py b/src/pyedb/grpc/siwave.py index d58e7446b3..ebd26415f3 100644 --- a/src/pyedb/grpc/siwave.py +++ b/src/pyedb/grpc/siwave.py @@ -18,13 +18,17 @@ from pyedb.generic.general_methods import generate_unique_name from pyedb.generic.general_methods import pyedb_function_handler from pyedb.modeler.geometry_operators import GeometryOperators -from ansys.edb.terminal.terminals import PadstackInstanceTerminal -from ansys.edb.terminal.terminals import BoundaryType -from ansys.edb.utility.value import Value -from ansys.edb.utility.rlc import Rlc -from ansys.edb.geometry.point_data import PointData -from ansys.edb.terminal.terminals import PointTerminal, PinGroupTerminal -from ansys.edb.hierarchy.pin_group import PinGroup +import ansys.edb.terminal as terminal +import ansys.edb.utility as utility +import ansys.edb.geometry as geometry +import ansys.edb.hierarchy as hierarchy +#from ansys.edb.terminal.terminals import PadstackInstanceTerminal +#from ansys.edb.terminal.terminals import BoundaryType +#from ansys.edb.utility.value import Value +#from ansys.edb.utility.rlc import Rlc +#from ansys.edb.geometry.point_data import PointData +#from ansys.edb.terminal.terminals import PointTerminal, PinGroupTerminal +#from ansys.edb.hierarchy.pin_group import PinGroup class EdbSiwave(object): @@ -58,7 +62,7 @@ def _logger(self): @property def _active_layout(self): """Active layout.""" - return self._pedb.active_layout + return self._pedb.layout @property def _layout(self): @@ -101,7 +105,7 @@ def pin_groups(self): """ _pingroups = {} for el in self._layout.pin_groups: - _pingroups[el.GetName()] = PinGroup(el.name, el, self._pedb) + _pingroups[el.name] = PinGroup(el.name, el, self._pedb) return _pingroups @pyedb_function_handler() @@ -117,72 +121,92 @@ def _create_terminal_on_pins(self, source): pos_pin = source.positive_node.node_pins neg_pin = source.negative_node.node_pins - res, fromLayer_pos, toLayer_pos = pos_pin.layer_range - res, fromLayer_neg, toLayer_neg = neg_pin.layer_range - - pos_pingroup_terminal = PadstackInstanceTerminal.create(self._active_layout, pos_pin.net, pos_pin.name, pos_pin, toLayer_pos) - time.sleep(0.5) - neg_pingroup_terminal = PadstackInstanceTerminal.create(self._active_layout, neg_pin.net, neg_pin.name, neg_pin, toLayer_neg) + fromLayer_pos, toLayer_pos = pos_pin.primitive_object.get_layer_range() + fromLayer_neg, toLayer_neg = neg_pin.primitive_object.get_layer_range() + + pos_terminal = terminal.PadstackInstanceTerminal.create(layout=self._active_layout, + name=source.name, + padstack_instance=pos_pin.pin, + layer=fromLayer_pos, + net=pos_pin.net.net_object, + is_ref=False) + if pos_terminal.is_null: + self._logger.error(f"Failed to create voltage source on pin {pos_pin.name}, " + f"component {pos_pin.component.refdes} (Positive terminal)") + return False + ref_term_name = neg_pin.name + if ref_term_name == pos_pin.name: + ref_term_name = f"{pos_pin.name}_ref" + neg_terminal = terminal.PadstackInstanceTerminal.create(layout=self._active_layout, + name=ref_term_name, + padstack_instance=neg_pin.pin, + layer=fromLayer_neg, + net=neg_pin.net.net_object, + is_ref=True) + if neg_terminal.is_null: + self._logger.error(f"Failed to create voltage source on pin {neg_pin.name}, " + f"component {neg_pin.component.refdes} (Reference terminal)") + return False if source.source_type in [SourceType.CoaxPort, SourceType.CircPort, SourceType.LumpedPort]: - pos_pingroup_terminal.boundary_type = BoundaryType.PORT - neg_pingroup_terminal.boundary_type = BoundaryType.PORT - pos_pingroup_terminal.impedance = Value(source.impedance) + pos_terminal.boundary_type = terminal.BoundaryType.PORT + neg_terminal.boundary_type = terminal.BoundaryType.PORT + pos_terminal.impedance = utility.Value(source.impedance) if source.source_type == SourceType.CircPort: - pos_pingroup_terminal.is_circuit_port = True - neg_pingroup_terminal.is_circuit_port = True - pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal + pos_terminal.is_circuit_port = True + neg_terminal.is_circuit_port = True + pos_terminal.reference_terminal = neg_terminal try: - pos_pingroup_terminal.name = source.name + pos_terminal.name = source.name except: name = generate_unique_name(source.name) - pos_pingroup_terminal.name = name + pos_terminal.name = name self._logger.warning("%s already exists. Renaming to %s", source.name, name) elif source.source_type == SourceType.Isource: - pos_pingroup_terminal.boundary_type = BoundaryType.CURRENT_SOURCE - neg_pingroup_terminal.boundary_type = BoundaryType.CURRENT_SOURCE - pos_pingroup_terminal.source_amplitude = Value(source.magnitude) - pos_pingroup_terminal.source_phase = Value(source.phase) - pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal + pos_terminal.boundary_type = terminal.BoundaryType.CURRENT_SOURCE + neg_terminal.boundary_type = terminal.BoundaryType.CURRENT_SOURCE + pos_terminal.source_amplitude = utility.Value(source.magnitude) + pos_terminal.source_phase = utility.Value(source.phase) + pos_terminal.reference_terminal = neg_terminal try: - pos_pingroup_terminal.name = source.name + pos_terminal.name = source.name except Exception as e: name = generate_unique_name(source.name) - pos_pingroup_terminal.name = name + pos_terminal.name = name self._logger.warning("%s already exists. Renaming to %s", source.name, name) elif source.source_type == SourceType.Vsource: - pos_pingroup_terminal.boundary_type = BoundaryType.VOLTAGE_SOURCE - neg_pingroup_terminal.boundary_type = BoundaryType.VOLTAGE_SOURCE - pos_pingroup_terminal.source_amplitude = Value(source.magnitude) - pos_pingroup_terminal.source_phase = Value(source.phase) - pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal + pos_terminal.boundary_type = terminal.BoundaryType.VOLTAGE_SOURCE + neg_terminal.boundary_type = terminal.BoundaryType.VOLTAGE_SOURCE + pos_terminal.source_amplitude = utility.Value(source.magnitude) + pos_terminal.source_phase = utility.Value(source.phase) + pos_terminal.reference_terminal = neg_terminal try: - pos_pingroup_terminal.name = source.name + pos_terminal.name = source.name except: name = generate_unique_name(source.name) - pos_pingroup_terminal.name = name + pos_terminal.name = name self._logger.warning("%s already exists. Renaming to %s", source.name, name) elif source.source_type == SourceType.Rlc: - pos_pingroup_terminal.boundary_type = BoundaryType.RLC - neg_pingroup_terminal.boundary_type = BoundaryType.RLC - pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal - pos_pingroup_terminal.source_amplitude = Value(source.rvalue) - rlc = Rlc() + pos_terminal.boundary_type = terminal.BoundaryType.RLC + neg_terminal.boundary_type = terminal.BoundaryType.RLC + pos_terminal.reference_terminal = neg_terminal + pos_terminal.source_amplitude = utility.Value(source.rvalue) + rlc = utility.Rlc() rlc.c_enabled = False rlc.l_enabled = False rlc.r_enabled = True - rlc.r = Value(source.rvalue) - pos_pingroup_terminal.rlc_boundary_parameters(Rlc) + rlc.r = utility.Value(source.rvalue) + pos_terminal.rlc_boundary_parameters(utility.Rlc) try: - pos_pingroup_terminal.name = source.name + pos_terminal.name = source.name except: name = generate_unique_name(source.name) - pos_pingroup_terminal.name = name + pos_terminal.name = name self._logger.warning("%s already exists. Renaming to %s", source.name, name) else: pass - return pos_pingroup_terminal.name + return pos_terminal.name @pyedb_function_handler() def create_circuit_port_on_pin(self, pos_pin, neg_pin, impedance=50, port_name=None): @@ -282,22 +306,22 @@ def create_port_between_pin_and_layer( res, start_layer, stop_layer = pin.layer_range() if res: pin_instance = pin._edb_padstackinstance - positive_terminal = PadstackInstanceTerminal.create( + positive_terminal = terminal.PadstackInstanceTerminal.create( self._active_layout, pin_instance.net, term_name, pin_instance, start_layer ) - positive_terminal.boundary_type = BoundaryType.PORT - positive_terminal.impedance = Value(impedance) + positive_terminal.boundary_type = terminal.BoundaryType.PORT + positive_terminal.impedance = utility.Value(impedance) positive_terminal.is_circuit_port = True pos = self._pedb.components.get_pin_position(pin_instance) - position = PointData(Value(pos[0]), Value(pos[1])) - negative_terminal = PointTerminal.create( + position = geometry.PointData(utility.Value(pos[0]), utility.Value(pos[1])) + negative_terminal = terminal.PointTerminal.create( layout=self._active_layout, net=reference_net.net_obj, name="{}_ref".format(term_name), point=position, layer=self._pedb.stackup.signal_layers[layer_name]._edb_layer) - negative_terminal.boundary_type = BoundaryType.PORT - negative_terminal.impedance = Value(impedance) + negative_terminal.boundary_type = terminal.BoundaryType.PORT + negative_terminal.impedance = utility.Value(impedance) negative_terminal.is_circuit_port = True positive_terminal.reference_terminal = negative_terminal return positive_terminal @@ -341,16 +365,16 @@ def create_voltage_source_on_pin(self, pos_pin, neg_pin, voltage_value=3.3, phas voltage_source.phase = phase_value if not source_name: source_name = "VSource_{}_{}_{}_{}".format( - pos_pin.component.name, + pos_pin.component.refdes, pos_pin.net.name, - neg_pin.component.name, + neg_pin.component.refdes, neg_pin.net.name, ) voltage_source.name = source_name - voltage_source.positive_node.component_node = pos_pin.component() + voltage_source.positive_node.component_node = pos_pin.component voltage_source.positive_node.node_pins = pos_pin - voltage_source.negative_node.component_node = neg_pin.component() - voltage_source.negative_node.node_pins = pos_pin + voltage_source.negative_node.component_node = neg_pin.component + voltage_source.negative_node.node_pins = neg_pin return self._create_terminal_on_pins(voltage_source) @pyedb_function_handler() @@ -879,26 +903,26 @@ def create_pin_group_terminal(self, source): pos_node_net = self._pedb.nets.get_net_by_name(source.positive_node.net) pos_pingroup_term_name = source.name - pos_pingroup_terminal = PinGroupTerminal.create(self._active_layout, - pos_node_net.net_obj, - pos_pingroup_term_name, - pos_pin_group, - False) + pos_pingroup_terminal = terminal.PinGroupTerminal.create(layout=self._active_layout, + net_ref=pos_node_net.net_object, + name=pos_pingroup_term_name, + pin_group=pos_pin_group, + is_ref=False) time.sleep(0.5) if source.negative_node.node_pins: neg_pin_group = self._pedb.components.create_pingroup_from_pins(source.negative_node.node_pins) neg_node_net = self._pedb.nets.get_net_by_name(source.negative_node.net) neg_pingroup_term_name = source.name + "_N" - neg_pingroup_terminal = PinGroupTerminal.create(self._active_layout, - neg_node_net.net_obj, - neg_pingroup_term_name, - neg_pin_group, - False) + neg_pingroup_terminal = terminal.PinGroupTerminal.create(layout=self._active_layout, + net_ref=neg_node_net.net_object, + name=neg_pingroup_term_name, + pin_group=neg_pin_group, + is_ref=False) if source.source_type in [SourceType.CoaxPort, SourceType.CircPort, SourceType.LumpedPort]: - pos_pingroup_terminal.boundary_type = BoundaryType.PORT - neg_pingroup_terminal.boundary_type = BoundaryType.PORT - pos_pingroup_terminal.source_amplitude = Value(source.impedance) + pos_pingroup_terminal.boundary_type = terminal.BoundaryType.PORT + neg_pingroup_terminal.boundary_type = terminal.BoundaryType.PORT + pos_pingroup_terminal.source_amplitude = utility.Value(source.impedance) if source.source_type == SourceType.CircPort: pos_pingroup_terminal.is_circuit_port = True neg_pingroup_terminal.is_circuit_port = True @@ -911,10 +935,10 @@ def create_pin_group_terminal(self, source): self._logger.warning("%s already exists. Renaming to %s", source.name, name) elif source.source_type == SourceType.Isource: - pos_pingroup_terminal.boundary_type = BoundaryType.CURRENT_SOURCE - neg_pingroup_terminal.boundary_type = BoundaryType.CURRENT_SOURCE - pos_pingroup_terminal.source_amplitude = Value(source.magnitude) - pos_pingroup_terminal.source_phase = Value(source.phase) + pos_pingroup_terminal.boundary_type = terminal.BoundaryType.CURRENT_SOURCE + neg_pingroup_terminal.boundary_type = terminal.BoundaryType.CURRENT_SOURCE + pos_pingroup_terminal.source_amplitude = utility.Value(source.magnitude) + pos_pingroup_terminal.source_phase = utility.Value(source.phase) pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal try: pos_pingroup_terminal.name = source.name @@ -924,10 +948,10 @@ def create_pin_group_terminal(self, source): self._logger.warning("%s already exists. Renaming to %s", source.name, name) elif source.source_type == SourceType.Vsource: - pos_pingroup_terminal.boundary_type = BoundaryType.VOLTAGE_SOURCE - neg_pingroup_terminal.boundary_type = BoundaryType.VOLTAGE_SOURCE - pos_pingroup_terminal.source_amplitude = Value(source.magnitude) - pos_pingroup_terminal.source_phase = Value(source.phase) + pos_pingroup_terminal.boundary_type = terminal.BoundaryType.VOLTAGE_SOURCE + neg_pingroup_terminal.boundary_type = terminal.BoundaryType.VOLTAGE_SOURCE + pos_pingroup_terminal.source_amplitude = utility.Value(source.magnitude) + pos_pingroup_terminal.source_phase = utility.Value(source.phase) pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal try: pos_pingroup_terminal.name = source.name @@ -937,18 +961,18 @@ def create_pin_group_terminal(self, source): self._logger.warning("%s already exists. Renaming to %s", source.name, name) elif source.source_type == SourceType.Rlc: - pos_pingroup_terminal.boundary_type = BoundaryType.RLC - neg_pingroup_terminal.boundary_type = BoundaryType.RLC + pos_pingroup_terminal.boundary_type = terminal.BoundaryType.RLC + neg_pingroup_terminal.boundary_type = terminal.BoundaryType.RLC pos_pingroup_terminal.reference_terminal = neg_pingroup_terminal - pos_pingroup_terminal.source_amplitude = Value(source.rvalue) - rlc = Rlc() + pos_pingroup_terminal.source_amplitude = utility.Value(source.rvalue) + rlc = utility.Rlc() rlc.c_enabled = False rlc.l_enabled = False rlc.r_enabled = True - rlc.r = Value(source.rvalue) - pos_pingroup_terminal.rlc_boundary_parameters(Rlc) + rlc.r = utility.Value(source.rvalue) + pos_pingroup_terminal.rlc_boundary_parameters(utility.Rlc) elif source.source_type == SourceType.DcTerminal: - pos_pingroup_terminal.boundary_type = BoundaryType.DC_TERMINAL + pos_pingroup_terminal.boundary_type = terminal.BoundaryType.DC_TERMINAL else: pass return pos_pingroup_terminal.name @@ -1190,16 +1214,13 @@ def create_pin_group(self, reference_designator, pin_numbers, group_name=None): pin_numbers = [pin_numbers] pin_numbers = [str(p) for p in pin_numbers] if group_name is None: - group_name = PinGroup.unique_name(self._active_layout) - comp = self._pedb.components.components[reference_designator] + group_name = hierarchy.PinGroup.unique_name(self._active_layout) + comp = self._pedb.components.instances[reference_designator] pins = [pin.pin for name, pin in comp.pins.items() if name in pin_numbers] - edb_pingroup = PinGroup.create(self._active_layout, group_name, pins) - - if edb_pingroup.is_null: # pragma: no cover - return False - else: - edb_pingroup.net = pins[0].net - return group_name, self.pin_groups[group_name] + edb_pingroup = hierarchy.PinGroup.create(layout=self._active_layout, name=group_name, padstack_instances=pins) + if not edb_pingroup.is_null: + return PinGroup(name=group_name, edb_pin_group=edb_pingroup, pedb=self._pedb) + return False @pyedb_function_handler() def create_pin_group_on_net(self, reference_designator, net_name, group_name=None): @@ -1219,7 +1240,7 @@ def create_pin_group_on_net(self, reference_designator, net_name, group_name=Non PinGroup """ pins = self._pedb.components.get_pin_from_component(reference_designator, net_name) - pin_names = [p.name for p in pins] + pin_names = [p.pin.name for p in pins] return self.create_pin_group(reference_designator, pin_names, group_name) @pyedb_function_handler() @@ -1347,13 +1368,17 @@ def create_circuit_port_on_pin_group(self, pos_pin_group_name, neg_pin_group_nam """ pos_pin_group = self.pin_groups[pos_pin_group_name] pos_terminal = pos_pin_group.create_port_terminal(impedance) + if not pos_terminal: + return False if name: # pragma: no cover pos_terminal.name = name else: name = generate_unique_name("port") pos_terminal.name = name - neg_pin_group_name = self.pin_groups[neg_pin_group_name] - neg_terminal = neg_pin_group_name.create_port_terminal(impedance) + neg_pin_group = self.pin_groups[neg_pin_group_name] + neg_terminal = neg_pin_group.create_port_terminal(impedance) + if not neg_terminal: + return False neg_terminal.name = name + "_ref" pos_terminal.reference_terminal = neg_terminal return True diff --git a/src/pyedb/legacy/edb_core/edb_data/padstacks_data.py b/src/pyedb/legacy/edb_core/edb_data/padstacks_data.py index 52f6dfbddf..95a8d2033b 100644 --- a/src/pyedb/legacy/edb_core/edb_data/padstacks_data.py +++ b/src/pyedb/legacy/edb_core/edb_data/padstacks_data.py @@ -1373,7 +1373,7 @@ def net_name(self): str Name of the net. """ - return self._edb_padstackinstance.GetNet().GetName() + return self._edb_padstackinstance.net.name @net_name.setter def net_name(self, val): diff --git a/src/pyedb/legacy/generic/general_methods.py b/src/pyedb/legacy/generic/general_methods.py index 2228a28dee..6485688292 100644 --- a/src/pyedb/legacy/generic/general_methods.py +++ b/src/pyedb/legacy/generic/general_methods.py @@ -23,8 +23,8 @@ import time import traceback -from pyaedt.generic.constants import CSS4_COLORS -from pyaedt.generic.settings import settings +from pyedb.generic.constants import CSS4_COLORS +from pyedb.generic.settings import settings is_ironpython = "IronPython" in sys.version or ".NETFramework" in sys.version is_linux = os.name == "posix" diff --git a/tests/conftest.py b/tests/conftest.py index 40ea31eee3..da36c2fb7b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,73 +28,11 @@ import pytest from pyedb.edb_logger import pyedb_logger -# from pyedb.generic.settings import settings - -# # settings.enable_local_log_file = False -# settings.enable_global_log_file = False -# # settings.number_of_grpc_api_retries = 6 -# # settings.retry_n_times_time_interval = 0.5 -# settings.enable_error_handler = False -# # settings.enable_desktop_logs = False -# # settings.desktop_launch_timeout = 180 - - -# # from pyaedt import Hfss - -# # from pyaedt.desktop import Desktop -# # from pyaedt.desktop import _delete_objects -# # from pyaedt.generic.desktop_sessions import _desktop_sessions - - -# # from pyaedt.generic.general_methods import inside_desktop -# # from pyaedt.misc.misc import list_installed_ansysem - -# from pyedb import Edb -from pyedb.legacy.edb import EdbLegacy from pyedb.legacy.generic.filesystem import Scratch from pyedb.generic.general_methods import generate_unique_name from pyedb.misc.misc import list_installed_ansysem +from pyedb.grpc.edb import EdbGrpc -# local_path = os.path.dirname(os.path.realpath(__file__)) -# sys.path.append(local_path) - -# # Initialize default desktop configuration -# desktop_version = "2023.2" -# if "ANSYSEM_ROOT{}".format(desktop_version[2:].replace(".", "")) not in list_installed_ansysem(): -# desktop_version = list_installed_ansysem()[0][12:].replace(".", "") -# desktop_version = "20{}.{}".format(desktop_version[:2], desktop_version[-1]) -# os.environ["ANSYSEM_FEATURE_SS544753_ICEPAK_VIRTUALMESHREGION_PARADIGM_ENABLE"] = "1" - -# config = { -# "edb_version": desktop_version, -# "NonGraphical": True, -# "NewThread": True, -# "skip_desktop_test": False, -# "build_machine": True, -# "skip_space_claim": False, -# "skip_circuits": False, -# "skip_edb": False, -# "skip_debug": False, -# "local": False, -# "use_grpc": True, -# "disable_sat_bounding_box": True, -# } - -# # Check for the local config file, override defaults if found -# local_config_file = os.path.join(local_path, "local_config.json") -# if os.path.exists(local_config_file): -# try: -# with open(local_config_file) as f: -# local_config = json.load(f) -# except: # pragma: no cover -# local_config = {} -# config.update(local_config) - -# NONGRAPHICAL = config["NonGraphical"] -# settings.disable_bounding_box_sat = config["disable_sat_bounding_box"] -# edb_version = config["edb_version"] -# new_thread = config["NewThread"] -# # settings.use_grpc_api = config["use_grpc"] logger = pyedb_logger @@ -146,72 +84,6 @@ def local_scratch(init_scratch): scratch.remove() -# # @pytest.fixture(scope="module", autouse=True) -# # def desktop(): -# # _delete_objects() -# # keys = list(_desktop_sessions.keys()) -# # for key in keys: -# # del _desktop_sessions[key] -# # d = Desktop(desktop_version, NONGRAPHICAL, new_thread) -# # d.disable_autosave() -# # d.odesktop.SetDesktopConfiguration("All") -# # d.odesktop.SetSchematicEnvironment(0) -# # yield d -# # d.release_desktop(True, True) -# # time.sleep(1) - - -# # TODO: See if we move that to conftest in system as it may use pyaedt -# @pytest.fixture(scope="module") -# def add_app(local_scratch): -# def _method( -# project_name=None, design_name=None, solution_type=None, application=None, subfolder="", just_open=False -# ): -# if project_name and not just_open: -# example_project = os.path.join(local_path, "example_models", subfolder, project_name + ".aedt") -# example_folder = os.path.join(local_path, "example_models", subfolder, project_name + ".aedb") -# if os.path.exists(example_project): -# # Copy unit test project to scratch folder. Return full file path to the project without extension. -# test_project = local_scratch.copyfile(example_project) -# elif os.path.exists(example_project + "z"): -# example_project = example_project + "z" -# test_project = local_scratch.copyfile(example_project) -# else: -# test_project = os.path.join(local_scratch.path, project_name + ".aedt") -# if os.path.exists(example_folder): -# target_folder = os.path.join(local_scratch.path, project_name + ".aedb") -# local_scratch.copyfolder(example_folder, target_folder) -# elif project_name and just_open: -# test_project = project_name -# else: -# test_project = None -# if not application: -# application = Hfss -# return application( -# projectname=test_project, -# designname=design_name, -# solution_type=solution_type, -# specified_version=desktop_version, -# ) - -# return _method - - -# @pytest.fixture(scope="module") -# def test_project_file(local_scratch): -# def _method(project_name=None, subfolder=None): -# if subfolder: -# project_file = os.path.join(local_path, "example_models", subfolder, project_name + ".aedt") -# else: -# project_file = os.path.join(local_scratch.path, project_name + ".aedt") -# if os.path.exists(project_file): -# return project_file -# else: -# return None - -# return _method - - @pytest.fixture(scope="module") def add_edb(local_scratch): def _method(project_name=None, subfolder=""): @@ -224,7 +96,7 @@ def _method(project_name=None, subfolder=""): target_folder = os.path.join(local_scratch.path, project_name + ".aedb") else: target_folder = os.path.join(local_scratch.path, generate_unique_name("TestEdb") + ".aedb") - return EdbLegacy( + return EdbGrpc( target_folder, edbversion=desktop_version, ) diff --git a/tests/grpc/__init__.py b/tests/grpc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/grpc/integration/__init__.py b/tests/grpc/integration/__init__.py new file mode 100644 index 0000000000..6e10663507 --- /dev/null +++ b/tests/grpc/integration/__init__.py @@ -0,0 +1,3 @@ +"""Tests related to the interaction of multiple classes +from PyEDB, e.g. Edb and Ipc2581, ... +""" \ No newline at end of file diff --git a/tests/grpc/system/__init__.py b/tests/grpc/system/__init__.py new file mode 100644 index 0000000000..2194fa96e2 --- /dev/null +++ b/tests/grpc/system/__init__.py @@ -0,0 +1,3 @@ +"""Tests related to testing the system as a whole, e.g. exporting +the data of an aedb file to ipc2581, ... +""" \ No newline at end of file diff --git a/tests/grpc/system/conftest.py b/tests/grpc/system/conftest.py new file mode 100644 index 0000000000..41826fbe51 --- /dev/null +++ b/tests/grpc/system/conftest.py @@ -0,0 +1,61 @@ +""" +""" +import sys +import os + +import pytest + +# from pyedb import Edb +# from pyedb.legacy.edb_core.components import resistor_value_parser +# from pyedb.legacy.edb_core.edb_data.edbvalue import EdbValue +# from pyedb.legacy.edb_core.edb_data.simulation_configuration import SimulationConfiguration +# from pyedb.legacy.edb_core.edb_data.sources import Source +# from pyedb.generic.constants import RadiationBoxType +# from pyedb.generic.general_methods import check_numeric_equivalence + +# from pyedb.generic.constants import SolverType +# from pyedb.generic.constants import SourceType +# from tests.conftest import config +from tests.conftest import local_path +# from tests.conftest import edb_version + +test_subfolder = "TEDB" +test_project_name = "ANSYS-HSD_V1" +bom_example = "bom_example.csv" + + +@pytest.fixture(scope="class") +def edbapp(add_edb): + app = add_edb(test_project_name, subfolder=test_subfolder) + return app + + +@pytest.fixture(scope="class", autouse=True) +def target_path(local_scratch): + example_project = os.path.join(local_path, "example_models", test_subfolder, "example_package.aedb") + target_path = os.path.join(local_scratch.path, "example_package.aedb") + local_scratch.copyfolder(example_project, target_path) + return target_path + + +@pytest.fixture(scope="class", autouse=True) +def target_path2(local_scratch): + example_project2 = os.path.join(local_path, "example_models", test_subfolder, "simple.aedb") + target_path2 = os.path.join(local_scratch.path, "simple_00.aedb") + local_scratch.copyfolder(example_project2, target_path2) + return target_path2 + +@pytest.fixture(scope="class", autouse=True) +def target_path3(local_scratch): + example_project3 = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1_cut.aedb") + target_path3 = os.path.join(local_scratch.path, "test_plot.aedb") + local_scratch.copyfolder(example_project3, target_path3) + return target_path3 + + +@pytest.fixture(scope="class", autouse=True) +def target_path4(local_scratch): + example_project4 = os.path.join(local_path, "example_models", test_subfolder, "Package.aedb") + target_path4 = os.path.join(local_scratch.path, "Package_00.aedb") + local_scratch.copyfolder(example_project4, target_path4) + return target_path4 diff --git a/tests/grpc/system/test_edb.py b/tests/grpc/system/test_edb.py new file mode 100644 index 0000000000..5fd24da7da --- /dev/null +++ b/tests/grpc/system/test_edb.py @@ -0,0 +1,1667 @@ +"""Tests related to Edb +""" + +import os +#from pyedb.legacy.edb_core.edb_data.edbvalue import EdbValue +#from pyedb.legacy.edb_core.edb_data.simulation_configuration import SimulationConfiguration +import pytest + +from pyedb.grpc.edb import EdbGrpc +from pyedb.generic.constants import RadiationBoxType, SourceType +from pyedb.generic.constants import SolverType +from pyedb.generic.general_methods import is_linux +from tests.conftest import local_path +from tests.conftest import desktop_version +from tests.legacy.system.conftest import test_subfolder + +pytestmark = [pytest.mark.system, pytest.mark.grpc] + + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_hfss_create_coax_port_on_component_from_hfss(self): + """Create a coaxial port on a component from its pin.""" + assert self.edbapp.hfss.create_coax_port_on_component("U1", "LVDS_CH12_P") + assert self.edbapp.hfss.create_coax_port_on_component("U1", ["DDR4_BG0"]) + + def test_layout_bounding_box(self): + """Evaluate layout bounding box""" + assert len(self.edbapp.get_bounding_box()) == 2 + assert self.edbapp.get_bounding_box() == [[-0.01426004895, -0.00455000106], [0.15010507444, 0.08000000002]] + + def test_siwave_create_circuit_port_on_net(self): + """Create a circuit port on a net.""" + initial_len = len(self.edbapp.padstacks.pingroups) + assert self.edbapp.siwave.create_circuit_port_on_net("U1", "1V0", "U1", "GND", 50, "test") == "test" + p2 = self.edbapp.siwave.create_circuit_port_on_net("U1", "PLL_1V8", "U1", "GND", 50, "test") + assert p2 != "test" and "test" in p2 + pins = self.edbapp.components.get_pin_from_component("U1") + p3 = self.edbapp.siwave.create_circuit_port_on_pin(pins[200], pins[0], 45) + assert p3 != "" + p4 = self.edbapp.hfss.create_circuit_port_on_net("U1", "USB3_D_P") + assert len(self.edbapp.padstacks.pingroups) == initial_len + 6 + assert "GND" in p4 and "USB3_D_P" in p4 + + # TODO: Moves this piece of code in another place + assert "test" in self.edbapp.terminals + pingroup = self.edbapp.siwave.create_pin_group_on_net("U1", "1V0", "PG_V1P0_S0") + ref_pingroup = self.edbapp.siwave.create_pin_group_on_net("U1", "GND", "Ref_pingroup") + assert pingroup + assert ref_pingroup + assert self.edbapp.siwave.create_circuit_port_on_pin_group("PG_V1P0_S0", "Ref_pingroup", impedance=50, name="test_port") + self.edbapp.excitations["test_port"].name = "test_rename" + assert any(port for port in list(self.edbapp.excitations) if port == "test_rename") + + def test_siwave_create_voltage_source(self): + """Create a voltage source.""" + assert len(self.edbapp.sources) == 0 + assert "Vsource_" in self.edbapp.siwave.create_voltage_source_on_net("U1", "USB3_D_P", "U1", "GND", 3.3, 0) + assert len(self.edbapp.sources) == 2 + assert list(self.edbapp.sources.values())[0].magnitude == 3.3 + + pins = self.edbapp.components.get_pin_from_component("U1") + assert "VSource_" in self.edbapp.siwave.create_voltage_source_on_pin(pins[300], pins[10], 3.3, 0) + assert len(self.edbapp.sources) == 4 + assert len(self.edbapp.probes) == 0 + + list(self.edbapp.sources.values())[0].phase = 1 + assert list(self.edbapp.sources.values())[0].phase == 1.0 + + def test_siwave_create_current_source(self): + """Create a current source.""" + assert self.edbapp.siwave.create_current_source_on_net("U1", "USB3_D_N", "U1", "GND", 0.1, 0) != "" + pins = self.edbapp.components.get_pin_from_component("U1") + assert "I22" == self.edbapp.siwave.create_current_source_on_pin(pins[301], pins[10], 0.1, 0, "I22") + + assert self.edbapp.siwave.create_pin_group_on_net(reference_designator="U1", net_name="GND", group_name="gnd") + self.edbapp.siwave.create_pin_group(reference_designator="U1", pin_numbers=["A27", "A28"], group_name="vrm_pos") + self.edbapp.siwave.create_current_source_on_pin_group( + pos_pin_group_name="vrm_pos", neg_pin_group_name="gnd", name="vrm_current_source" + ) + + self.edbapp.siwave.create_pin_group( + reference_designator="U1", pin_numbers=["A14", "A15"], group_name="sink_pos" + ) + + # TODO: Moves this piece of code in another place + assert self.edbapp.siwave.create_voltage_source_on_pin_group("sink_pos", "gnd", name="vrm_voltage_source") + self.edbapp.siwave.create_pin_group(reference_designator="U1", pin_numbers=["A27", "A28"], group_name="vp_pos") + self.edbapp.siwave.create_pin_group(reference_designator="U1", pin_numbers=["A14", "A15"], group_name="vp_neg") + assert self.edbapp.siwave.create_voltage_probe_on_pin_group("vprobe", "vp_pos", "vp_neg") + assert self.edbapp.probes["vprobe"] + + def test_siwave_create_dc_terminal(self): + """Create a DC terminal.""" + assert self.edbapp.siwave.create_dc_terminal("U1", "DDR4_DQ40", "dc_terminal1") == "dc_terminal1" + + def test_siwave_create_resistors_on_pin(self): + """Create a resistor on pin.""" + pins = self.edbapp.components.get_pin_from_component("U1") + assert "RST4000" == self.edbapp.siwave.create_resistor_on_pin(pins[302], pins[10], 40, "RST4000") + + def test_siwave_add_syz_analsyis(self): + """Add a sywave AC analysis.""" + assert self.edbapp.siwave.add_siwave_syz_analysis() + + def test_siwave_add_dc_analysis(self): + """Add a sywave DC analysis.""" + assert self.edbapp.siwave.add_siwave_dc_analysis() + + def test_hfss_mesh_operations(self): + """Retrieve the trace width for traces with ports.""" + self.edbapp.components.create_port_on_component( + "U1", + ["VDD_DDR"], + reference_net="GND", + port_type=SourceType.CircPort, + ) + mesh_ops = self.edbapp.hfss.get_trace_width_for_traces_with_ports() + assert len(mesh_ops) > 0 + + def test_add_variables(self): + """Add design and project variables.""" + result, var_server = self.edbapp.add_design_variable("my_variable", "1mm") + assert result + assert var_server + result, var_server = self.edbapp.add_design_variable("my_variable", "1mm") + assert not result + assert self.edbapp.modeler.parametrize_trace_width("A0_N") + assert self.edbapp.modeler.parametrize_trace_width("A0_N_R") + result, var_server = self.edbapp.add_design_variable("my_parameter", "2mm", True) + assert result + assert var_server.IsVariableParameter("my_parameter") + result, var_server = self.edbapp.add_design_variable("my_parameter", "2mm", True) + assert not result + result, var_server = self.edbapp.add_project_variable("$my_project_variable", "3mm") + assert result + assert var_server + result, var_server = self.edbapp.add_project_variable("$my_project_variable", "3mm") + assert not result + + def test_save_edb_as(self): + """Save edb as some file.""" + assert self.edbapp.save_edb_as(os.path.join(self.local_scratch.path, "Gelileo_new.aedb")) + assert os.path.exists(os.path.join(self.local_scratch.path, "Gelileo_new.aedb", "edb.def")) + + def test_create_custom_cutout_0(self): + """Create custom cutout 0.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1_cut.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_cutou1.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + output = os.path.join(self.local_scratch.path, "cutout.aedb") + assert edbapp.cutout( + ["DDR4_DQS0_P", "DDR4_DQS0_N"], + ["GND"], + output_aedb_path=output, + open_cutout_at_end=False, + use_pyaedt_extent_computing=True, + use_pyaedt_cutout=False, + ) + assert edbapp.cutout( + ["DDR4_DQS0_P", "DDR4_DQS0_N"], + ["GND"], + output_aedb_path=output, + open_cutout_at_end=False, + remove_single_pin_components=True, + use_pyaedt_cutout=False, + ) + assert os.path.exists(os.path.join(output, "edb.def")) + bounding = edbapp.get_bounding_box() + cutout_line_x = 41 + cutout_line_y = 30 + points = [[bounding[0][0], bounding[0][1]]] + points.append([cutout_line_x, bounding[0][1]]) + points.append([cutout_line_x, cutout_line_y]) + points.append([bounding[0][0], cutout_line_y]) + points.append([bounding[0][0], bounding[0][1]]) + output = os.path.join(self.local_scratch.path, "cutout2.aedb") + + assert edbapp.cutout( + custom_extent=points, + signal_list=["GND", "1V0"], + output_aedb_path=output, + open_cutout_at_end=False, + include_partial_instances=True, + use_pyaedt_cutout=False, + ) + assert os.path.exists(os.path.join(output, "edb.def")) + output = os.path.join(self.local_scratch.path, "cutout3.aedb") + + assert edbapp.cutout( + custom_extent=points, + signal_list=["GND", "1V0"], + output_aedb_path=output, + open_cutout_at_end=False, + include_partial_instances=True, + use_pyaedt_cutout=False, + ) + assert os.path.exists(os.path.join(output, "edb.def")) + edbapp.close() + + def test_create_custom_cutout_1(self): + """Create custom cutout 1.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_cutou2.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + spice_path = os.path.join(local_path, "example_models", test_subfolder, "GRM32_DC0V_25degC.mod") + edbapp.components.instances["R8"].assign_spice_model(spice_path) + edbapp.nets.nets + assert edbapp.cutout( + signal_list=["1V0"], + reference_list=["GND"], + extent_type="Bounding", + number_of_threads=4, + extent_defeature=0.001, + preserve_components_with_model=True, + ) + assert "A0_N" not in edbapp.nets.nets + assert isinstance(edbapp.nets.find_and_fix_disjoint_nets("GND", order_by_area=True), list) + assert isinstance(edbapp.nets.find_and_fix_disjoint_nets("GND", keep_only_main_net=True), list) + assert isinstance(edbapp.nets.find_and_fix_disjoint_nets("GND", clean_disjoints_less_than=0.005), list) + edbapp.close() + + def test_create_custom_cutout_2(self): + """Create custom cutout 2.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_cutou3.aedb") + self.local_scratch.copyfolder(source_path, target_path) + + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + bounding = edbapp.get_bounding_box() + cutout_line_x = 41 + cutout_line_y = 30 + points = [[bounding[0][0], bounding[0][1]]] + points.append([cutout_line_x, bounding[0][1]]) + points.append([cutout_line_x, cutout_line_y]) + points.append([bounding[0][0], cutout_line_y]) + points.append([bounding[0][0], bounding[0][1]]) + assert edbapp.cutout( + signal_list=["1V0"], + reference_list=["GND"], + number_of_threads=4, + extent_type="ConvexHull", + custom_extent=points, + simple_pad_check=False, + ) + edbapp.close() + + def test_create_custom_cutout_3(self): + """Create custom cutout 3.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_cutou5.aedb") + self.local_scratch.copyfolder(source_path, target_path) + + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + edbapp.components.create_port_on_component( + "U1", + ["5V"], + reference_net="GND", + port_type=SourceType.CircPort, + ) + edbapp.components.create_port_on_component("U2", ["5V"], reference_net="GND") + edbapp.hfss.create_voltage_source_on_net("U4", "5V", "U4", "GND") + legacy_name = edbapp.edbpath + assert edbapp.cutout( + signal_list=["5V"], + reference_list=["GND"], + number_of_threads=4, + extent_type="ConvexHull", + use_pyaedt_extent_computing=True, + check_terminals=True, + ) + assert edbapp.edbpath == legacy_name + assert edbapp.are_port_reference_terminals_connected(common_reference="GND") + + edbapp.close() + + def test_create_custom_cutout_4(self): + """Create custom cutout 4.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1_cut.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_cut_smart.aedb") + self.local_scratch.copyfolder(source_path, target_path) + + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + + assert edbapp.cutout( + signal_list=["DDR4_DQS0_P", "DDR4_DQS0_N"], + reference_list=["GND"], + number_of_threads=4, + extent_type="ConvexHull", + use_pyaedt_extent_computing=True, + check_terminals=True, + expansion_factor=4, + ) + edbapp.close() + source_path = os.path.join(local_path, "example_models", test_subfolder, "MicrostripSpliGnd.aedb") + target_path = os.path.join(self.local_scratch.path, "MicrostripSpliGnd.aedb") + self.local_scratch.copyfolder(source_path, target_path) + + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + + assert edbapp.cutout( + signal_list=["trace_n"], + reference_list=["ground"], + number_of_threads=4, + extent_type="Conformal", + use_pyaedt_extent_computing=True, + check_terminals=True, + expansion_factor=2, + ) + edbapp.close() + source_path = os.path.join(local_path, "example_models", test_subfolder, "Multizone_GroundVoids.aedb") + target_path = os.path.join(self.local_scratch.path, "Multizone_GroundVoids.aedb") + self.local_scratch.copyfolder(source_path, target_path) + + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + + assert edbapp.cutout( + signal_list=["DIFF_N", "DIFF_P"], + reference_list=["GND"], + number_of_threads=4, + extent_type="Conformal", + use_pyaedt_extent_computing=True, + check_terminals=True, + expansion_factor=3, + ) + edbapp.close() + + def test_export_to_hfss(self): + """Export EDB to HFSS.""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", test_subfolder, "simple.aedb"), + edbversion=desktop_version, + ) + options_config = {"UNITE_NETS": 1, "LAUNCH_Q3D": 0} + out = edb.write_export3d_option_config_file(self.local_scratch, options_config) + assert os.path.exists(out) + out = edb.export_hfss(self.local_scratch) + assert os.path.exists(out) + edb.close() + + def test_export_to_q3d(self): + """Export EDB to Q3D.""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", test_subfolder, "simple.aedb"), + edbversion=desktop_version, + ) + options_config = {"UNITE_NETS": 1, "LAUNCH_Q3D": 0} + out = edb.write_export3d_option_config_file(self.local_scratch, options_config) + assert os.path.exists(out) + out = edb.export_q3d(self.local_scratch, net_list=["ANALOG_A0", "ANALOG_A1", "ANALOG_A2"], hidden=True) + assert os.path.exists(out) + edb.close() + + def test_074_export_to_maxwell(self): + """Export EDB to Maxwell 3D.""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", test_subfolder, "simple.aedb"), + edbversion=desktop_version, + ) + options_config = {"UNITE_NETS": 1, "LAUNCH_MAXWELL": 0} + out = edb.write_export3d_option_config_file(self.local_scratch, options_config) + assert os.path.exists(out) + out = edb.export_maxwell(self.local_scratch, num_cores=6) + assert os.path.exists(out) + edb.close() + + # def test_change_design_variable_value(self): + # """Change a variable value.""" + # self.edbapp.add_design_variable("ant_length", "1cm") + # self.edbapp.add_design_variable("my_parameter_default", "1mm", is_parameter=True) + # self.edbapp.add_design_variable("$my_project_variable", "1mm") + # changed_variable_1 = self.edbapp.change_design_variable_value("ant_length", "1m") + # if isinstance(changed_variable_1, tuple): + # changed_variable_done, ant_length_value = changed_variable_1 + # assert changed_variable_done + # else: + # assert changed_variable_1 + # changed_variable_2 = self.edbapp.change_design_variable_value("elephant_length", "1m") + # if isinstance(changed_variable_2, tuple): + # changed_variable_done, elephant_length_value = changed_variable_2 + # assert not changed_variable_done + # else: + # assert not changed_variable_2 + # changed_variable_3 = self.edbapp.change_design_variable_value("my_parameter_default", "1m") + # if isinstance(changed_variable_3, tuple): + # changed_variable_done, my_parameter_value = changed_variable_3 + # assert changed_variable_done + # else: + # assert changed_variable_3 + # changed_variable_4 = self.edbapp.change_design_variable_value("$my_project_variable", "1m") + # if isinstance(changed_variable_4, tuple): + # changed_variable_done, my_project_variable_value = changed_variable_4 + # assert changed_variable_done + # else: + # assert changed_variable_4 + # changed_variable_5 = self.edbapp.change_design_variable_value("$my_parameter", "1m") + # if isinstance(changed_variable_5, tuple): + # changed_variable_done, my_project_variable_value = changed_variable_5 + # assert not changed_variable_done + # else: + # assert not changed_variable_5 + + # def test_variables_value(self): + # """Evaluate variables value.""" + # from pyedb.generic.general_methods import check_numeric_equivalence + + # variables = { + # "var1": 0.01, + # "var2": "10um", + # "var3": [0.03, "test description"], + # "$var4": ["1mm", "Project variable."], + # "$var5": 0.1, + # } + # for key, val in variables.items(): + # self.edbapp[key] = val + # if key == "var1": + # assert self.edbapp[key].value == val + # elif key == "var2": + # assert check_numeric_equivalence(self.edbapp[key].value, 1.0e-5) + # elif key == "var3": + # assert self.edbapp[key].value == val[0] + # assert self.edbapp[key].description == val[1] + # elif key == "$var4": + # assert self.edbapp[key].value == 0.001 + # assert self.edbapp[key].description == val[1] + # elif key == "$var5": + # assert self.edbapp[key].value == 0.1 + # assert self.edbapp.project_variables[key].delete() + + def test_create_edge_port_on_polygon(self): + """Create lumped and vertical port.""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", test_subfolder, "edge_ports.aedb"), + edbversion=desktop_version, + ) + poly_list = [poly for poly in edb.layout.primitives if int(poly.GetPrimitiveType()) == 2] + port_poly = [poly for poly in poly_list if poly.GetId() == 17][0] + ref_poly = [poly for poly in poly_list if poly.GetId() == 19][0] + port_location = [-65e-3, -13e-3] + ref_location = [-63e-3, -13e-3] + assert edb.hfss.create_edge_port_on_polygon( + polygon=port_poly, + reference_polygon=ref_poly, + terminal_point=port_location, + reference_point=ref_location, + ) + port_poly = [poly for poly in poly_list if poly.GetId() == 23][0] + ref_poly = [poly for poly in poly_list if poly.GetId() == 22][0] + port_location = [-65e-3, -10e-3] + ref_location = [-65e-3, -10e-3] + assert edb.hfss.create_edge_port_on_polygon( + polygon=port_poly, + reference_polygon=ref_poly, + terminal_point=port_location, + reference_point=ref_location, + ) + port_poly = [poly for poly in poly_list if poly.GetId() == 25][0] + port_location = [-65e-3, -7e-3] + assert edb.hfss.create_edge_port_on_polygon( + polygon=port_poly, terminal_point=port_location, reference_layer="gnd" + ) + sig = edb.modeler.create_trace([[0, 0], ["9mm", 0]], "TOP", "1mm", "SIG", "Flat", "Flat") + assert sig.create_edge_port("pcb_port_1", "end", "Wave", None, 8, 8) + assert sig.create_edge_port("pcb_port_2", "start", "gap") + gap_port = edb.ports["pcb_port_2"] + assert gap_port.component is None + assert gap_port.magnitude == 0.0 + assert gap_port.phase == 0.0 + assert gap_port.impedance + assert not gap_port.deembed + gap_port.name = "gap_port" + assert gap_port.name == "gap_port" + assert isinstance(gap_port.renormalize_z0, tuple) + edb.close() + + def test_create_dc_simulation(self): + """Create Siwave DC simulation""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", test_subfolder, "dc_flow.aedb"), + edbversion=desktop_version, + ) + sim_setup = edb.new_simulation_configuration() + sim_setup.do_cutout_subdesign = False + sim_setup.solver_type = SolverType.SiwaveDC + sim_setup.add_voltage_source( + positive_node_component="Q3", + positive_node_net="SOURCE_HBA_PHASEA", + negative_node_component="Q3", + negative_node_net="HV_DC+", + ) + sim_setup.add_current_source( + name="I25", + positive_node_component="Q5", + positive_node_net="SOURCE_HBB_PHASEB", + negative_node_component="Q5", + negative_node_net="HV_DC+", + ) + assert len(sim_setup.sources) == 2 + sim_setup.open_edb_after_build = False + sim_setup.batch_solve_settings.output_aedb = os.path.join(self.local_scratch.path, "build.aedb") + original_path = edb.edbpath + assert sim_setup.batch_solve_settings.use_pyaedt_cutout + assert not sim_setup.batch_solve_settings.use_default_cutout + sim_setup.batch_solve_settings.use_pyaedt_cutout = True + assert sim_setup.batch_solve_settings.use_pyaedt_cutout + assert not sim_setup.batch_solve_settings.use_default_cutout + assert sim_setup.build_simulation_project() + assert edb.edbpath == original_path + sim_setup.open_edb_after_build = True + assert sim_setup.build_simulation_project() + assert edb.edbpath == os.path.join(self.local_scratch.path, "build.aedb") + + edb.close() + + def test_edb_statistics(self): + """Get statistics.""" + example_project = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_110.aedb") + self.local_scratch.copyfolder(example_project, target_path) + edb = EdbGrpc(target_path, edbversion=desktop_version) + edb_stats = edb.get_statistics(compute_area=True) + assert edb_stats + assert edb_stats.num_layers + assert edb_stats.stackup_thickness + assert edb_stats.num_vias + assert edb_stats.occupying_ratio + assert edb_stats.occupying_surface + assert edb_stats.layout_size + assert edb_stats.num_polygons + assert edb_stats.num_traces + assert edb_stats.num_nets + assert edb_stats.num_discrete_components + assert edb_stats.num_inductors + assert edb_stats.num_capacitors + assert edb_stats.num_resistors + edb.close() + + def test_hfss_set_bounding_box_extent(self): + """Configure HFSS with bounding box""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "test_107.aedb") + target_path = os.path.join(self.local_scratch.path, "test_113.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edb = EdbGrpc(target_path, edbversion=desktop_version) + initial_extent_info = edb.active_cell.GetHFSSExtentInfo() + assert initial_extent_info.ExtentType == edb.edb_api.utility.utility.HFSSExtentInfoType.Conforming + config = SimulationConfiguration() + config.radiation_box = RadiationBoxType.BoundingBox + assert edb.hfss.configure_hfss_extents(config) + final_extent_info = edb.active_cell.GetHFSSExtentInfo() + assert final_extent_info.ExtentType == edb.edb_api.utility.utility.HFSSExtentInfoType.BoundingBox + edb.close() + + def test_create_rlc_component(self): + """Create rlc components from pin""" + example_project = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS_114.aedb") + self.local_scratch.copyfolder(example_project, target_path) + edb = EdbGrpc(target_path, edbversion=desktop_version) + pins = edb.components.get_pin_from_component("U1", "1V0") + ref_pins = edb.components.get_pin_from_component("U1", "GND") + assert edb.components.create([pins[0], ref_pins[0]], "test_0rlc", r_value=1.67, l_value=1e-13, c_value=1e-11) + assert edb.components.create([pins[0], ref_pins[0]], "test_1rlc", r_value=None, l_value=1e-13, c_value=1e-11) + assert edb.components.create([pins[0], ref_pins[0]], "test_2rlc", r_value=None, c_value=1e-13) + edb.close() + + def test_create_rlc_boundary_on_pins(self): + """Create hfss rlc boundary on pins.""" + example_project = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_115.aedb") + if not os.path.exists(self.local_scratch.path): + os.mkdir(self.local_scratch.path) + self.local_scratch.copyfolder(example_project, target_path) + edb = EdbGrpc(target_path, edbversion=desktop_version) + pins = edb.components.get_pin_from_component("U1", "1V0") + ref_pins = edb.components.get_pin_from_component("U1", "GND") + assert edb.hfss.create_rlc_boundary_on_pins(pins[0], ref_pins[0], rvalue=1.05, lvalue=1.05e-12, cvalue=1.78e-13) + edb.close() + + def test_configure_hfss_analysis_setup_enforce_causality(self): + """Configure HFSS analysis setup.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "lam_for_top_place_no_setups.aedb") + target_path = os.path.join(self.local_scratch.path, "lam_for_top_place_no_setups_t116.aedb") + if not os.path.exists(self.local_scratch.path): + os.mkdir(self.local_scratch.path) + self.local_scratch.copyfolder(source_path, target_path) + edb = EdbGrpc(target_path, edbversion=desktop_version) + assert len(list(edb.active_cell.SimulationSetups)) == 0 + sim_config = SimulationConfiguration() + sim_config.enforce_causality = False + assert sim_config.do_lambda_refinement + sim_config.mesh_sizefactor = 0.1 + assert sim_config.mesh_sizefactor == 0.1 + assert not sim_config.do_lambda_refinement + sim_config.start_freq = "1GHz" + edb.hfss.configure_hfss_analysis_setup(sim_config) + assert len(list(edb.active_cell.SimulationSetups)) == 1 + setup = list(edb.active_cell.SimulationSetups)[0] + ssi = setup.GetSimSetupInfo() + assert len(list(ssi.SweepDataList)) == 1 + sweep = list(ssi.SweepDataList)[0] + assert not sweep.EnforceCausality + edb.close() + + def test_configure_hfss_analysis_setup(self): + """Configure HFSS analysis setup.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0117.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edb = EdbGrpc(target_path, edbversion=desktop_version) + sim_setup = SimulationConfiguration() + sim_setup.mesh_sizefactor = 1.9 + assert not sim_setup.do_lambda_refinement + edb.hfss.configure_hfss_analysis_setup(sim_setup) + mesh_size_factor = ( + list(edb.active_cell.SimulationSetups)[0] + .GetSimSetupInfo() + .get_SimulationSettings() + .get_InitialMeshSettings() + .get_MeshSizefactor() + ) + assert mesh_size_factor == 1.9 + edb.close() + + def test_create_various_ports_0(self): + """Create various ports.""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", "edb_edge_ports.aedb"), + edbversion=desktop_version, + ) + prim_1_id = [i.id for i in edb.modeler.primitives if i.net_name == "trace_2"][0] + assert edb.hfss.create_edge_port_vertical(prim_1_id, ["-66mm", "-4mm"], "port_ver") + + prim_2_id = [i.id for i in edb.modeler.primitives if i.net_name == "trace_3"][0] + assert edb.hfss.create_edge_port_horizontal( + prim_1_id, ["-60mm", "-4mm"], prim_2_id, ["-59mm", "-4mm"], "port_hori", 30, "Lower" + ) + assert edb.hfss.get_ports_number() == 2 + port_ver = edb.ports["port_ver"] + assert not port_ver.is_null + assert port_ver.hfss_type == "Gap" + + args = { + "layer_name": "1_Top", + "net_name": "SIGP", + "width": "0.1mm", + "start_cap_style": "Flat", + "end_cap_style": "Flat", + } + traces = [] + trace_paths = [ + [["-40mm", "-10mm"], ["-30mm", "-10mm"]], + [["-40mm", "-10.2mm"], ["-30mm", "-10.2mm"]], + [["-40mm", "-10.4mm"], ["-30mm", "-10.4mm"]], + ] + for p in trace_paths: + t = edb.modeler.create_trace(path_list=p, **args) + traces.append(t) + + assert edb.hfss.create_wave_port(traces[0].id, trace_paths[0][0], "wave_port") + wave_port = edb.ports["wave_port"] + wave_port.horizontal_extent_factor = 10 + wave_port.vertical_extent_factor = 10 + assert wave_port.horizontal_extent_factor == 10 + assert wave_port.vertical_extent_factor == 10 + wave_port.radial_extent_factor = 1 + assert wave_port.radial_extent_factor == 1 + assert wave_port.pec_launch_width + assert not wave_port.deembed + assert wave_port.deembed_length == 0.0 + assert wave_port.do_renormalize + wave_port.do_renormalize = False + assert not wave_port.do_renormalize + assert edb.hfss.create_differential_wave_port( + traces[0].id, + trace_paths[0][0], + traces[1].id, + trace_paths[1][0], + horizontal_extent_factor=8, + port_name="df_port", + ) + assert edb.ports["df_port"] + p, n = edb.ports["df_port"].terminals + assert edb.ports["df_port"].decouple() + p.couple_ports(n) + + traces_id = [i.id for i in traces] + paths = [i[1] for i in trace_paths] + _, df_port = edb.hfss.create_bundle_wave_port(traces_id, paths) + assert df_port.name + assert df_port.terminals + df_port.horizontal_extent_factor = 10 + df_port.vertical_extent_factor = 10 + df_port.deembed = True + df_port.deembed_length = "1mm" + assert df_port.horizontal_extent_factor == 10 + assert df_port.vertical_extent_factor == 10 + assert df_port.deembed + assert df_port.deembed_length == 1e-3 + edb.close() + + def test_create_various_ports_1(self): + """Create various ports.""" + edb = EdbGrpc( + edbpath=os.path.join(local_path, "example_models", "edb_edge_ports.aedb"), + edbversion=desktop_version, + ) + args = { + "layer_name": "1_Top", + "net_name": "SIGP", + "width": "0.1mm", + "start_cap_style": "Flat", + "end_cap_style": "Flat", + } + traces = [] + trace_pathes = [ + [["-40mm", "-10mm"], ["-30mm", "-10mm"]], + [["-40mm", "-10.2mm"], ["-30mm", "-10.2mm"]], + [["-40mm", "-10.4mm"], ["-30mm", "-10.4mm"]], + ] + for p in trace_pathes: + t = edb.modeler.create_trace(path_list=p, **args) + traces.append(t) + + assert edb.hfss.create_wave_port(traces[0], trace_pathes[0][0], "wave_port") + + assert edb.hfss.create_differential_wave_port( + traces[0], + trace_pathes[0][0], + traces[1], + trace_pathes[1][0], + horizontal_extent_factor=8, + ) + + paths = [i[1] for i in trace_pathes] + assert edb.hfss.create_bundle_wave_port(traces, paths) + p = edb.excitations["wave_port"] + p.horizontal_extent_factor = 6 + p.vertical_extent_factor = 5 + p.pec_launch_width = "0.02mm" + p.radial_extent_factor = 1 + assert p.horizontal_extent_factor == 6 + assert p.vertical_extent_factor == 5 + assert p.pec_launch_width == "0.02mm" + assert p.radial_extent_factor == 1 + edb.close() + + def test_build_hfss_project_from_config_file(self): + """Build a simulation project from config file.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0122.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + cfg_file = os.path.join(os.path.dirname(edbapp.edbpath), "test.cfg") + with open(cfg_file, "w") as f: + f.writelines("SolverType = 'Hfss3dLayout'\n") + f.writelines("PowerNets = ['GND']\n") + f.writelines("Components = ['U1', 'U7']") + + sim_config = SimulationConfiguration(cfg_file) + assert edbapp.build_simulation_project(sim_config) + edbapp.close() + + def test_set_all_antipad_values(self): + """Set all anti-pads from all pad-stack definition to the given value.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0120.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + assert edbapp.padstacks.set_all_antipad_value(0.0) + edbapp.close() + + def test_hfss_simulation_setup(self): + """Create a setup from a template and evaluate its properties.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0129.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + setup1 = edbapp.create_hfss_setup("setup1") + assert setup1.set_solution_single_frequency() + assert setup1.set_solution_multi_frequencies() + assert setup1.set_solution_broadband() + + setup1.hfss_solver_settings.enhanced_low_freq_accuracy = True + setup1.hfss_solver_settings.order_basis = "first" + setup1.hfss_solver_settings.relative_residual = 0.0002 + setup1.hfss_solver_settings.use_shell_elements = True + + hfss_solver_settings = edbapp.setups["setup1"].hfss_solver_settings + assert hfss_solver_settings.order_basis == "first" + assert hfss_solver_settings.relative_residual == 0.0002 + assert hfss_solver_settings.solver_type + assert hfss_solver_settings.enhanced_low_freq_accuracy + assert not hfss_solver_settings.use_shell_elements + + assert setup1.adaptive_settings.add_adaptive_frequency_data("5GHz", 8, "0.01") + assert setup1.adaptive_settings.adaptive_frequency_data_list + setup1.adaptive_settings.adapt_type = "kBroadband" + setup1.adaptive_settings.basic = False + setup1.adaptive_settings.max_refinement = 1000001 + setup1.adaptive_settings.max_refine_per_pass = 20 + setup1.adaptive_settings.min_passes = 2 + setup1.adaptive_settings.save_fields = True + setup1.adaptive_settings.save_rad_field_only = True + setup1.adaptive_settings.use_convergence_matrix = True + setup1.adaptive_settings.use_max_refinement = True + + assert edbapp.setups["setup1"].adaptive_settings.adapt_type == "kBroadband" + assert not edbapp.setups["setup1"].adaptive_settings.basic + assert edbapp.setups["setup1"].adaptive_settings.max_refinement == 1000001 + assert edbapp.setups["setup1"].adaptive_settings.max_refine_per_pass == 20 + assert edbapp.setups["setup1"].adaptive_settings.min_passes == 2 + assert edbapp.setups["setup1"].adaptive_settings.save_fields + assert edbapp.setups["setup1"].adaptive_settings.save_rad_field_only + # assert adaptive_settings.use_convergence_matrix + assert edbapp.setups["setup1"].adaptive_settings.use_max_refinement + + setup1.defeature_settings.defeature_abs_length = "1um" + setup1.defeature_settings.defeature_ratio = 1e-5 + setup1.defeature_settings.healing_option = 0 + setup1.defeature_settings.model_type = 1 + setup1.defeature_settings.remove_floating_geometry = True + setup1.defeature_settings.small_void_area = 0.1 + setup1.defeature_settings.union_polygons = False + setup1.defeature_settings.use_defeature = False + setup1.defeature_settings.use_defeature_abs_length = True + + defeature_settings = edbapp.setups["setup1"].defeature_settings + assert defeature_settings.defeature_abs_length == "1um" + assert defeature_settings.defeature_ratio == 1e-5 + # assert defeature_settings.healing_option == 0 + # assert defeature_settings.model_type == 1 + assert defeature_settings.remove_floating_geometry + assert defeature_settings.small_void_area == 0.1 + assert not defeature_settings.union_polygons + assert not defeature_settings.use_defeature + assert defeature_settings.use_defeature_abs_length + + via_settings = setup1.via_settings + via_settings.via_density = 1 + via_settings.via_material = "pec" + via_settings.via_num_sides = 8 + via_settings.via_style = "kNum25DViaStyle" + + via_settings = edbapp.setups["setup1"].via_settings + assert via_settings.via_density == 1 + assert via_settings.via_material == "pec" + assert via_settings.via_num_sides == 8 + # assert via_settings.via_style == "kNum25DViaStyle" + + advanced_mesh_settings = setup1.advanced_mesh_settings + advanced_mesh_settings.layer_snap_tol = "1e-6" + advanced_mesh_settings.mesh_display_attributes = "#0000001" + advanced_mesh_settings.replace_3d_triangles = False + + advanced_mesh_settings = edbapp.setups["setup1"].advanced_mesh_settings + assert advanced_mesh_settings.layer_snap_tol == "1e-6" + assert advanced_mesh_settings.mesh_display_attributes == "#0000001" + assert not advanced_mesh_settings.replace_3d_triangles + + curve_approx_settings = setup1.curve_approx_settings + curve_approx_settings.arc_angle = "15deg" + curve_approx_settings.arc_to_chord_error = "0.1" + curve_approx_settings.max_arc_points = 12 + curve_approx_settings.start_azimuth = "1" + curve_approx_settings.use_arc_to_chord_error = True + + curve_approx_settings = edbapp.setups["setup1"].curve_approx_settings + assert curve_approx_settings.arc_to_chord_error == "0.1" + assert curve_approx_settings.max_arc_points == 12 + assert curve_approx_settings.start_azimuth == "1" + assert curve_approx_settings.use_arc_to_chord_error + + dcr_settings = setup1.dcr_settings + dcr_settings.conduction_max_passes = 11 + dcr_settings.conduction_min_converged_passes = 2 + dcr_settings.conduction_min_passes = 2 + dcr_settings.conduction_per_error = 2.0 + dcr_settings.conduction_per_refine = 33.0 + + dcr_settings = edbapp.setups["setup1"].dcr_settings + assert dcr_settings.conduction_max_passes == 11 + assert dcr_settings.conduction_min_converged_passes == 2 + assert dcr_settings.conduction_min_passes == 2 + assert dcr_settings.conduction_per_error == 2.0 + assert dcr_settings.conduction_per_refine == 33.0 + + hfss_port_settings = setup1.hfss_port_settings + hfss_port_settings.max_delta_z0 = 0.5 + assert hfss_port_settings.max_delta_z0 == 0.5 + hfss_port_settings.max_triangles_wave_port = 1000 + assert hfss_port_settings.max_triangles_wave_port == 1000 + hfss_port_settings.min_triangles_wave_port = 200 + assert hfss_port_settings.min_triangles_wave_port == 200 + hfss_port_settings.set_triangles_wave_port = True + assert hfss_port_settings.set_triangles_wave_port + + # mesh_operations = setup1.mesh_operations + # setup1.mesh_operations = mesh_operations + + setup1.add_frequency_sweep( + "sweep1", + frequency_sweep=[ + ["linear count", "0", "1kHz", 1], + ["log scale", "1kHz", "0.1GHz", 10], + ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], + ], + ) + assert "sweep1" in setup1.frequency_sweeps + sweep1 = setup1.frequency_sweeps["sweep1"] + sweep1.adaptive_sampling = True + assert sweep1.adaptive_sampling + + edbapp.setups["setup1"].name = "setup1a" + assert "setup1" not in edbapp.setups + assert "setup1a" in edbapp.setups + + mop = edbapp.setups["setup1a"].add_length_mesh_operation({"GND": ["1_Top", "16_Bottom"]}, "m1") + assert mop.name == "m1" + assert mop.max_elements == "1000" + assert mop.restrict_max_elements + assert mop.restrict_length + assert mop.max_length == "1mm" + + mop.name = "m2" + mop.max_elements = 2000 + mop.restrict_max_elements = False + mop.restrict_length = False + mop.max_length = "2mm" + + assert mop.name == "m2" + assert mop.max_elements == "2000" + assert not mop.restrict_max_elements + assert not mop.restrict_length + assert mop.max_length == "2mm" + + mop = edbapp.setups["setup1a"].add_skin_depth_mesh_operation({"GND": ["1_Top", "16_Bottom"]}) + assert mop.max_elements == "1000" + assert mop.restrict_max_elements + assert mop.skin_depth == "1um" + assert mop.surface_triangle_length == "1mm" + assert mop.number_of_layer_elements == "2" + + mop.skin_depth = "5um" + mop.surface_triangle_length = "2mm" + mop.number_of_layer_elements = "3" + + assert mop.skin_depth == "5um" + assert mop.surface_triangle_length == "2mm" + assert mop.number_of_layer_elements == "3" + edbapp.close() + + def test_siwave_dc_simulation_setup(self): + """Create a dc simulation setup and evaluate its properties.""" + setup1 = self.edbapp.create_siwave_dc_setup("DC1") + assert setup1.name == "DC1" + assert not setup1.compute_inductance + assert setup1.contact_radius == "0.1mm" + assert setup1.dc_slider_position == 1 + assert setup1.enabled + assert setup1.energy_error == 3.0 + assert setup1.max_init_mesh_edge_length == "2.5mm" + assert setup1.max_num_pass == 5 + assert setup1.min_num_pass == 1 + assert setup1.mesh_bondwires + assert setup1.mesh_vias + assert setup1.min_plane_area == "0.25mm2" + assert setup1.min_void_area == "0.01mm2" + assert setup1.num_bondwire_sides == 8 + assert setup1.num_via_sides == 8 + assert setup1.percent_local_refinement == 20.0 + assert setup1.perform_adaptive_refinement + assert setup1.plot_jv + assert not setup1.refine_bondwires + assert not setup1.refine_vias + setup1.name = "DC2" + setup1.compute_inductance = True + setup1.contact_radius = "0.2mm" + setup1.dc_slider_position = 2 + setup1.energy_error = 2.0 + setup1.max_init_mesh_edge_length = "5.5mm" + setup1.max_num_pass = 3 + setup1.min_num_pass = 2 + setup1.mesh_bondwires = False + setup1.mesh_vias = False + assert not setup1.mesh_bondwires + assert not setup1.mesh_vias + setup1.min_plane_area = "0.5mm2" + setup1.min_void_area = "0.021mm2" + setup1.num_bondwire_sides = 6 + setup1.num_via_sides = 10 + setup1.percent_local_refinement = 10.0 + setup1.perform_adaptive_refinement = False + setup1.plot_jv = False + setup1.refine_bondwires = True + setup1.refine_vias = True + + assert setup1.name == "DC2" + assert setup1.compute_inductance + assert setup1.contact_radius == "0.2mm" + assert setup1.dc_slider_position == 2 + assert setup1.energy_error == 2.0 + assert setup1.max_init_mesh_edge_length == "5.5mm" + assert setup1.max_num_pass == 3 + assert setup1.min_num_pass == 2 + assert setup1.mesh_bondwires + assert setup1.mesh_vias + assert setup1.min_plane_area == "0.5mm2" + assert setup1.min_void_area == "0.021mm2" + assert setup1.num_bondwire_sides == 6 + assert setup1.num_via_sides == 10 + assert setup1.percent_local_refinement == 10.0 + assert not setup1.perform_adaptive_refinement + assert not setup1.plot_jv + assert setup1.refine_bondwires + assert setup1.refine_vias + + def test_131_siwave_ac_simulation_setup(self): + """Create an ac simulation setup and evaluate its properties.""" + setup1 = self.edbapp.create_siwave_syz_setup("AC1") + assert setup1.name == "AC1" + assert setup1.enabled + sweep = setup1.add_frequency_sweep( + "sweep1", + frequency_sweep=[ + ["linear count", "0", "1kHz", 1], + ["log scale", "1kHz", "0.1GHz", 10], + ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], + ], + ) + assert "sweep1" in setup1.frequency_sweeps + assert "0" in sweep.frequencies + assert not sweep.adaptive_sampling + assert not sweep.adv_dc_extrapolation + assert sweep.auto_s_mat_only_solve + assert not sweep.enforce_causality + assert not sweep.enforce_dc_and_causality + assert sweep.enforce_passivity + assert sweep.freq_sweep_type == "kInterpolatingSweep" + assert sweep.interp_use_full_basis + assert sweep.interp_use_port_impedance + assert sweep.interp_use_prop_const + assert sweep.max_solutions == 250 + assert sweep.min_freq_s_mat_only_solve == "1MHz" + assert not sweep.min_solved_freq + assert sweep.passivity_tolerance == 0.0001 + assert sweep.relative_s_error == 0.005 + assert not sweep.save_fields + assert not sweep.save_rad_fields_only + assert not sweep.use_q3d_for_dc + + sweep.adaptive_sampling = True + sweep.adv_dc_extrapolation = True + sweep.auto_s_mat_only_solve = False + sweep.enforce_causality = True + sweep.enforce_dc_and_causality = True + sweep.enforce_passivity = False + sweep.freq_sweep_type = "kDiscreteSweep" + sweep.interp_use_full_basis = False + sweep.interp_use_port_impedance = False + sweep.interp_use_prop_const = False + sweep.max_solutions = 200 + sweep.min_freq_s_mat_only_solve = "2MHz" + sweep.min_solved_freq = "1Hz" + sweep.passivity_tolerance = 0.0002 + sweep.relative_s_error = 0.004 + sweep.save_fields = True + sweep.save_rad_fields_only = True + sweep.use_q3d_for_dc = True + + assert sweep.adaptive_sampling + assert sweep.adv_dc_extrapolation + assert not sweep.auto_s_mat_only_solve + assert sweep.enforce_causality + assert sweep.enforce_dc_and_causality + assert not sweep.enforce_passivity + assert sweep.freq_sweep_type == "kDiscreteSweep" + assert not sweep.interp_use_full_basis + assert not sweep.interp_use_port_impedance + assert not sweep.interp_use_prop_const + assert sweep.max_solutions == 200 + assert sweep.min_freq_s_mat_only_solve == "2MHz" + assert sweep.min_solved_freq == "1Hz" + assert sweep.passivity_tolerance == 0.0002 + assert sweep.relative_s_error == 0.004 + assert sweep.save_fields + assert sweep.save_rad_fields_only + assert sweep.use_q3d_for_dc + + assert setup1.automatic_mesh + assert setup1.enabled + assert setup1.dc_settings + assert setup1.ignore_non_functional_pads + assert setup1.include_coplane_coupling + assert setup1.include_fringe_coupling + assert not setup1.include_infinite_ground + assert not setup1.include_inter_plane_coupling + assert setup1.include_split_plane_coupling + assert setup1.include_trace_coupling + assert not setup1.include_vi_sources + assert setup1.infinite_ground_location == "0" + assert setup1.max_coupled_lines == 12 + assert setup1.mesh_frequency == "4GHz" + assert setup1.min_pad_area_to_mesh == "1mm2" + assert setup1.min_plane_area_to_mesh == "6.25e-6mm2" + assert setup1.min_void_area == "2mm2" + assert setup1.name == "AC1" + assert setup1.perform_erc + assert setup1.pi_slider_postion == 1 + assert setup1.si_slider_postion == 1 + assert not setup1.return_current_distribution + assert setup1.snap_length_threshold == "2.5um" + assert setup1.use_si_settings + assert setup1.use_custom_settings + assert setup1.xtalk_threshold == "-34" + + setup1.automatic_mesh = False + setup1.enabled = False + setup1.ignore_non_functional_pads = False + setup1.include_coplane_coupling = False + setup1.include_fringe_coupling = False + setup1.include_infinite_ground = True + setup1.include_inter_plane_coupling = True + setup1.include_split_plane_coupling = False + setup1.include_trace_coupling = False + assert setup1.use_custom_settings + setup1.include_vi_sources = True + setup1.infinite_ground_location = "0.1" + setup1.max_coupled_lines = 10 + setup1.mesh_frequency = "3GHz" + setup1.min_pad_area_to_mesh = "2mm2" + setup1.min_plane_area_to_mesh = "5.25e-6mm2" + setup1.min_void_area = "1mm2" + setup1.name = "AC2" + setup1.perform_erc = False + setup1.pi_slider_postion = 0 + setup1.si_slider_postion = 2 + setup1.return_current_distribution = True + setup1.snap_length_threshold = "3.5um" + setup1.use_si_settings = False + assert not setup1.use_custom_settings + setup1.xtalk_threshold = "-44" + + assert not setup1.automatic_mesh + assert not setup1.enabled + assert not setup1.ignore_non_functional_pads + assert not setup1.include_coplane_coupling + assert not setup1.include_fringe_coupling + assert setup1.include_infinite_ground + assert setup1.include_inter_plane_coupling + assert not setup1.include_split_plane_coupling + assert not setup1.include_trace_coupling + assert setup1.include_vi_sources + assert setup1.infinite_ground_location == "0.1" + assert setup1.max_coupled_lines == 10 + assert setup1.mesh_frequency == "3GHz" + assert setup1.min_pad_area_to_mesh == "2mm2" + assert setup1.min_plane_area_to_mesh == "5.25e-6mm2" + assert setup1.min_void_area == "1mm2" + assert setup1.name == "AC2" + assert not setup1.perform_erc + assert setup1.pi_slider_postion == 0 + assert setup1.si_slider_postion == 2 + assert setup1.return_current_distribution + assert setup1.snap_length_threshold == "3.5um" + assert not setup1.use_si_settings + assert setup1.xtalk_threshold == "-44" + + def test_siwave_build_ac_project(self): + """Build ac simulation project.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "padstacks.aedb") + target_path = os.path.join(self.local_scratch.path, "test_133_simconfig.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + simconfig = edbapp.new_simulation_configuration() + simconfig.solver_type = SolverType.SiwaveSYZ + simconfig.mesh_freq = "40.25GHz" + edbapp.build_simulation_project(simconfig) + assert edbapp.siwave_ac_setups[simconfig.setup_name].mesh_frequency == simconfig.mesh_freq + edbapp.close() + + def test_siwave_create_port_between_pin_and_layer(self): + """Create circuit port between pin and a reference layer.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0134.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + edbapp.siwave.create_port_between_pin_and_layer( + component_name="U1", pins_name="A27", layer_name="16_Bottom", reference_net="GND" + ) + edbapp.close() + + def test_siwave_source_setter(self): + """Evaluate siwave sources property.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "test_sources.aedb") + target_path = os.path.join(self.local_scratch.path, "test_134_source_setter.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + sources = list(edbapp.siwave.sources.values()) + sources[0].magnitude = 1.45 + assert sources[0].magnitude == 1.45 + sources[1].magnitude = 1.45 + assert sources[1].magnitude == 1.45 + sources[2].magnitude = 1.45 + assert sources[2].magnitude == 1.45 + edbapp.close() + + def test_delete_pingroup(self): + """Delete siwave pin groups.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "test_pin_group.aedb") + target_path = os.path.join(self.local_scratch.path, "test_135_pin_group.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbGrpc(target_path, edbversion=desktop_version) + for _, pingroup in edbapp.siwave.pin_groups.items(): + assert pingroup.delete() + assert not edbapp.siwave.pin_groups + edbapp.close() + + def test_design_options(self): + """Evaluate Edb design settings and options.""" + self.edbapp.design_options.suppress_pads = False + assert not self.edbapp.design_options.suppress_pads + self.edbapp.design_options.antipads_always_on = True + assert self.edbapp.design_options.antipads_always_on + + def test_pins(self): + """Evaluate the pins.""" + assert len(self.edbapp.pins) > 0 + + def test_create_padstack_instance(self): + """Create padstack instances.""" + edb = EdbGrpc(edbversion=desktop_version) + edb.stackup.add_layer(layer_name="1_Top", fillMaterial="AIR", thickness="30um") + edb.stackup.add_layer(layer_name="contact", fillMaterial="AIR", thickness="100um", base_layer="1_Top") + + assert edb.padstacks.create( + pad_shape="Rectangle", + padstackname="pad", + x_size="350um", + y_size="500um", + holediam=0, + ) + pad_instance1 = edb.padstacks.place(position=["-0.65mm", "-0.665mm"], definition_name="pad") + assert pad_instance1 + pad_instance1.start_layer = "1_Top" + pad_instance1.stop_layer = "1_Top" + assert pad_instance1.start_layer == "1_Top" + assert pad_instance1.stop_layer == "1_Top" + + assert edb.padstacks.create(pad_shape="Circle", padstackname="pad2", paddiam="350um", holediam="15um") + pad_instance2 = edb.padstacks.place(position=["-0.65mm", "-0.665mm"], definition_name="pad2") + assert pad_instance2 + pad_instance2.start_layer = "1_Top" + pad_instance2.stop_layer = "1_Top" + assert pad_instance2.start_layer == "1_Top" + assert pad_instance2.stop_layer == "1_Top" + + assert edb.padstacks.create( + pad_shape="Circle", + padstackname="test2", + paddiam="400um", + holediam="200um", + antipad_shape="Rectangle", + anti_pad_x_size="700um", + anti_pad_y_size="800um", + start_layer="1_Top", + stop_layer="1_Top", + ) + + pad_instance3 = edb.padstacks.place(position=["-1.65mm", "-1.665mm"], definition_name="test2") + assert pad_instance3.start_layer == "1_Top" + assert pad_instance3.stop_layer == "1_Top" + pad_instance3.dcir_equipotential_region = True + assert pad_instance3.dcir_equipotential_region + pad_instance3.dcir_equipotential_region = False + assert not pad_instance3.dcir_equipotential_region + edb.close() + + def test_assign_hfss_extent_non_multiple_with_simconfig(self): + """Build simulation project without multiple.""" + edb = EdbGrpc() + edb.stackup.add_layer(layer_name="GND", fillMaterial="AIR", thickness="30um") + edb.stackup.add_layer(layer_name="FR4", base_layer="gnd", thickness="250um") + edb.stackup.add_layer(layer_name="SIGNAL", base_layer="FR4", thickness="30um") + edb.modeler.create_trace(layer_name="SIGNAL", width=0.02, net_name="net1", path_list=[[-1e3, 0, 1e-3, 0]]) + edb.modeler.create_rectangle( + layer_name="GND", + representation_type="CenterWidthHeight", + center_point=["0mm", "0mm"], + width="4mm", + height="4mm", + net_name="GND", + ) + sim_setup = edb.new_simulation_configuration() + sim_setup.signal_nets = ["net1"] + # sim_setup.power_nets = ["GND"] + sim_setup.use_dielectric_extent_multiple = False + sim_setup.use_airbox_horizontal_extent_multiple = False + sim_setup.use_airbox_negative_vertical_extent_multiple = False + sim_setup.use_airbox_positive_vertical_extent_multiple = False + sim_setup.dielectric_extent = 0.0005 + sim_setup.airbox_horizontal_extent = 0.001 + sim_setup.airbox_negative_vertical_extent = 0.05 + sim_setup.airbox_positive_vertical_extent = 0.04 + sim_setup.add_frequency_sweep = False + sim_setup.include_only_selected_nets = True + sim_setup.do_cutout_subdesign = False + sim_setup.generate_excitations = False + edb.build_simulation_project(sim_setup) + hfss_ext_info = edb.active_cell.GetHFSSExtentInfo() + assert list(edb.nets.nets.values())[0].name == "net1" + assert not edb.setups["Pyaedt_setup"].frequency_sweeps + assert hfss_ext_info + assert hfss_ext_info.AirBoxHorizontalExtent.Item1 == 0.001 + assert not hfss_ext_info.AirBoxHorizontalExtent.Item2 + assert hfss_ext_info.AirBoxNegativeVerticalExtent.Item1 == 0.05 + assert not hfss_ext_info.AirBoxNegativeVerticalExtent.Item2 + assert hfss_ext_info.AirBoxPositiveVerticalExtent.Item1 == 0.04 + assert not hfss_ext_info.AirBoxPositiveVerticalExtent.Item2 + assert hfss_ext_info.DielectricExtentSize.Item1 == 0.0005 + assert not hfss_ext_info.AirBoxPositiveVerticalExtent.Item2 + edb.close() + + def test_assign_hfss_extent_multiple_with_simconfig(self): + """Build simulation project with multiple.""" + edb = EdbGrpc() + edb.stackup.add_layer(layer_name="GND", fillMaterial="AIR", thickness="30um") + edb.stackup.add_layer(layer_name="FR4", base_layer="gnd", thickness="250um") + edb.stackup.add_layer(layer_name="SIGNAL", base_layer="FR4", thickness="30um") + edb.modeler.create_trace(layer_name="SIGNAL", width=0.02, net_name="net1", path_list=[[-1e3, 0, 1e-3, 0]]) + edb.modeler.create_rectangle( + layer_name="GND", + representation_type="CenterWidthHeight", + center_point=["0mm", "0mm"], + width="4mm", + height="4mm", + net_name="GND", + ) + sim_setup = edb.new_simulation_configuration() + sim_setup.signal_nets = ["net1"] + sim_setup.power_nets = ["GND"] + sim_setup.use_dielectric_extent_multiple = True + sim_setup.use_airbox_horizontal_extent_multiple = True + sim_setup.use_airbox_negative_vertical_extent_multiple = True + sim_setup.use_airbox_positive_vertical_extent_multiple = True + sim_setup.dielectric_extent = 0.0005 + sim_setup.airbox_horizontal_extent = 0.001 + sim_setup.airbox_negative_vertical_extent = 0.05 + sim_setup.airbox_positive_vertical_extent = 0.04 + edb.build_simulation_project(sim_setup) + hfss_ext_info = edb.active_cell.GetHFSSExtentInfo() + assert hfss_ext_info + assert hfss_ext_info.AirBoxHorizontalExtent.Item1 == 0.001 + assert hfss_ext_info.AirBoxHorizontalExtent.Item2 + assert hfss_ext_info.AirBoxNegativeVerticalExtent.Item1 == 0.05 + assert hfss_ext_info.AirBoxNegativeVerticalExtent.Item2 + assert hfss_ext_info.AirBoxPositiveVerticalExtent.Item1 == 0.04 + assert hfss_ext_info.AirBoxPositiveVerticalExtent.Item2 + assert hfss_ext_info.DielectricExtentSize.Item1 == 0.0005 + assert hfss_ext_info.AirBoxPositiveVerticalExtent.Item2 + edb.close() + + def test_stackup_properties(self): + """Evaluate stackup properties.""" + edb = EdbGrpc(edbversion=desktop_version) + edb.stackup.add_layer(layer_name="gnd", fillMaterial="AIR", thickness="10um") + edb.stackup.add_layer(layer_name="diel1", fillMaterial="AIR", thickness="200um", base_layer="gnd") + edb.stackup.add_layer(layer_name="sig1", fillMaterial="AIR", thickness="10um", base_layer="diel1") + edb.stackup.add_layer(layer_name="diel2", fillMaterial="AIR", thickness="200um", base_layer="sig1") + edb.stackup.add_layer(layer_name="sig3", fillMaterial="AIR", thickness="10um", base_layer="diel2") + assert edb.stackup.thickness == 0.00043 + assert edb.stackup.num_layers == 5 + edb.close() + + def test_hfss_extent_info(self): + """HFSS extent information.""" + from pyedb.legacy.edb_core.edb_data.primitives_data import EDBPrimitives as EDBPrimitives + + config = { + "air_box_horizontal_extent_enabled": False, + "air_box_horizontal_extent": 0.01, + "air_box_positive_vertical_extent": 0.3, + "air_box_positive_vertical_extent_enabled": False, + "air_box_negative_vertical_extent": 0.1, + "air_box_negative_vertical_extent_enabled": False, + "base_polygon": self.edbapp.modeler.polygons[0], + "dielectric_base_polygon": self.edbapp.modeler.polygons[1], + "dielectric_extent_size": 0.1, + "dielectric_extent_size_enabled": False, + "dielectric_extent_type": "Conforming", + "extent_type": "Conforming", + "honor_user_dielectric": False, + "is_pml_visible": False, + "open_region_type": "PML", + "operating_freq": "2GHz", + "radiation_level": 1, + "sync_air_box_vertical_extent": False, + "use_open_region": False, + "use_xy_data_extent_for_vertical_expansion": False, + "truncate_air_box_at_ground": True, + } + hfss_extent_info = self.edbapp.hfss.hfss_extent_info + hfss_extent_info.load_config(config) + exported_config = hfss_extent_info.export_config() + for i, j in exported_config.items(): + if not i in config: + continue + if isinstance(j, EDBPrimitives): + assert j.id == config[i].id + elif isinstance(j, EdbValue): + assert j.tofloat == hfss_extent_info._get_edb_value(config[i]).ToDouble() + else: + assert j == config[i] + + def test_import_gds_from_tech(self): + """Use techfile.""" + from pyedb.legacy.edb_core.edb_data.control_file import ControlFile + c_file_in = os.path.join( + local_path, "example_models", "cad", "GDS", "sky130_fictitious_dtc_example_control_no_map.xml" + ) + c_map = os.path.join(local_path, "example_models", "cad", "GDS", "dummy_layermap.map") + gds_in = os.path.join(local_path, "example_models", "cad", "GDS", "sky130_fictitious_dtc_example.gds") + gds_out = os.path.join(self.local_scratch.path, "sky130_fictitious_dtc_example.gds") + self.local_scratch.copyfile(gds_in, gds_out) + + c = ControlFile(c_file_in, layer_map=c_map) + setup = c.setups.add_setup("Setup1", "1GHz") + setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") + c.boundaries.units = "um" + c.stackup.units = "um" + c.boundaries.add_port("P1", x1=223.7, y1=222.6, layer1="Metal6", x2=223.7, y2=100, layer2="Metal6") + c.boundaries.add_extent() + comp = c.components.add_component("B1", "BGA", "IC", "Flip chip", "Cylinder") + comp.solder_diameter = "65um" + comp.add_pin("1", "81.28", "84.6", "met2") + comp.add_pin("2", "211.28", "84.6", "met2") + comp.add_pin("3", "211.28", "214.6", "met2") + comp.add_pin("4", "81.28", "214.6", "met2") + for via in c.stackup.vias: + via.create_via_group = True + via.snap_via_group = True + c.write_xml(os.path.join(self.local_scratch.path, "test_138.xml")) + c.import_options.import_dummy_nets = True + + edb = EdbGrpc( + gds_out, edbversion=desktop_version, technology_file=os.path.join(self.local_scratch.path, "test_138.xml") + ) + + assert edb + assert "P1" in edb.excitations + assert "Setup1" in edb.setups + assert "B1" in edb.components.components + edb.close() + + def test_database_properties(self): + """Evaluate database properties.""" + assert isinstance(self.edbapp.dataset_defs, list) + assert isinstance(self.edbapp.material_defs, list) + assert isinstance(self.edbapp.component_defs, list) + assert isinstance(self.edbapp.package_defs, list) + + assert isinstance(self.edbapp.padstack_defs, list) + assert isinstance(self.edbapp.jedec5_bondwire_defs, list) + assert isinstance(self.edbapp.jedec4_bondwire_defs, list) + assert isinstance(self.edbapp.apd_bondwire_defs, list) + assert self.edbapp.source_version == "" + self.edbapp.source_version = "2022.2" + assert self.edbapp.source == "" + assert self.edbapp.scale(1.0) + assert isinstance(self.edbapp.version, tuple) + assert isinstance(self.edbapp.footprint_cells, list) + + def test_backdrill_via_with_offset(self): + """Set backdrill from top.""" + edb = EdbGrpc(edbversion=desktop_version) + edb.stackup.add_layer(layer_name="bot") + edb.stackup.add_layer(layer_name="diel1", base_layer="bot", layer_type="dielectric", thickness="127um") + edb.stackup.add_layer(layer_name="signal1", base_layer="diel1") + edb.stackup.add_layer(layer_name="diel2", base_layer="signal1", layer_type="dielectric", thickness="127um") + edb.stackup.add_layer(layer_name="signal2", base_layer="diel2") + edb.stackup.add_layer(layer_name="diel3", base_layer="signal2", layer_type="dielectric", thickness="127um") + edb.stackup.add_layer(layer_name="top", base_layer="diel2") + edb.padstacks.create(padstackname="test1") + padstack_instance = edb.padstacks.place(position=[0, 0], net_name="test", definition_name="test1") + edb.padstacks.definitions["test1"].hole_range = "through" + padstack_instance.set_backdrill_top(drill_depth="signal1", drill_diameter="200um", offset="100um") + assert len(padstack_instance.backdrill_top) == 3 + assert padstack_instance.backdrill_top[0] == "signal1" + assert padstack_instance.backdrill_top[1] == "200um" + assert padstack_instance.backdrill_top[2] == "100um" + padstack_instance2 = edb.padstacks.place(position=[0.5, 0.5], net_name="test", definition_name="test1") + padstack_instance2.set_backdrill_bottom(drill_depth="signal1", drill_diameter="200um", offset="100um") + assert len(padstack_instance2.backdrill_bottom) == 3 + assert padstack_instance2.backdrill_bottom[0] == "signal1" + assert padstack_instance2.backdrill_bottom[1] == "200um" + assert padstack_instance2.backdrill_bottom[2] == "100um" + edb.close() + + def test_add_layer_api_with_control_file(self): + """Add new layers with control file.""" + from pyedb.legacy.edb_core.edb_data.control_file import ControlFile + + ctrl = ControlFile() + # Material + ctrl.stackup.add_material(material_name="Copper", conductivity=5.56e7) + ctrl.stackup.add_material(material_name="BCB", permittivity=2.7) + ctrl.stackup.add_material(material_name="Silicon", conductivity=0.04) + ctrl.stackup.add_material(material_name="SiliconOxide", conductivity=4.4) + ctrl.stackup.units = "um" + assert len(ctrl.stackup.materials) == 4 + assert ctrl.stackup.units == "um" + # Dielectrics + ctrl.stackup.add_dielectric(material="Silicon", layer_name="Silicon", thickness=180) + ctrl.stackup.add_dielectric(layer_index=1, material="SiliconOxide", layer_name="USG1", thickness=1.2) + assert next(diel for diel in ctrl.stackup.dielectrics if diel.name == "USG1").properties["Index"] == 1 + ctrl.stackup.add_dielectric(material="BCB", layer_name="BCB2", thickness=9.5, base_layer="USG1") + ctrl.stackup.add_dielectric( + material="BCB", layer_name="BCB1", thickness=4.1, base_layer="BCB2", add_on_top=False + ) + ctrl.stackup.add_dielectric(layer_index=4, material="BCB", layer_name="BCB3", thickness=6.5) + assert ctrl.stackup.dielectrics[0].properties["Index"] == 0 + assert ctrl.stackup.dielectrics[1].properties["Index"] == 1 + assert ctrl.stackup.dielectrics[2].properties["Index"] == 3 + assert ctrl.stackup.dielectrics[3].properties["Index"] == 2 + assert ctrl.stackup.dielectrics[4].properties["Index"] == 4 + # Metal layer + ctrl.stackup.add_layer( + layer_name="9", elevation=185.3, material="Copper", target_layer="meta2", gds_type=0, thickness=6 + ) + assert [layer for layer in ctrl.stackup.layers if layer.name == "9"] + ctrl.stackup.add_layer( + layer_name="15", elevation=194.8, material="Copper", target_layer="meta3", gds_type=0, thickness=3 + ) + assert [layer for layer in ctrl.stackup.layers if layer.name == "15"] + # Via layer + ctrl.stackup.add_via( + layer_name="14", material="Copper", target_layer="via2", start_layer="meta2", stop_layer="meta3", gds_type=0 + ) + assert [layer for layer in ctrl.stackup.vias if layer.name == "14"] + # Port + ctrl.boundaries.add_port( + "test_port", x1=-21.1, y1=-288.7, layer1="meta3", x2=21.1, y2=-288.7, layer2="meta3", z0=50 + ) + assert ctrl.boundaries.ports + # setup using q3D for DC point + setup = ctrl.setups.add_setup("test_setup", "10GHz") + assert setup + setup.add_sweep( + name="test_sweep", + start="0GHz", + stop="20GHz", + step="10MHz", + sweep_type="Interpolating", + step_type="LinearStep", + use_q3d=True, + ) + assert setup.sweeps + + @pytest.mark.skipif(is_linux, reason="Failing download files") + def test_create_edb_with_dxf(self): + """Create EDB from dxf file.""" + src = os.path.join(local_path, "example_models", test_subfolder, "edb_test_82.dxf") + dxf_path = self.local_scratch.copyfile(src) + edb3 = EdbGrpc(dxf_path, edbversion=desktop_version) + edb3.close() + del edb3 + + def test_build_siwave_project_from_config_file(self): + """Build Siwave simulation project from configuration file.""" + example_project = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_15.aedb") + self.local_scratch.copyfolder(example_project, target_path) + cfg_file = os.path.join(target_path, "test.cfg") + with open(cfg_file, "w") as f: + f.writelines("SolverType = 'SiwaveSYZ'\n") + f.writelines("PowerNets = ['GND']\n") + f.writelines("Components = ['U1', 'U2']") + sim_config = SimulationConfiguration(cfg_file) + assert EdbGrpc(target_path, edbversion=desktop_version).build_simulation_project(sim_config) + + @pytest.mark.skipif(is_linux, reason="Not supported in IPY") + def test_solve_siwave(self): + """Solve EDB with Siwave.""" + target_path = os.path.join(local_path, "example_models", "T40", "ANSYS-HSD_V1_DCIR.aedb") + out_edb = os.path.join(self.local_scratch.path, "to_be_solved.aedb") + self.local_scratch.copyfolder(target_path, out_edb) + edbapp = EdbGrpc(out_edb, edbversion=desktop_version) + edbapp.siwave.create_exec_file(add_dc=True) + out = edbapp.solve_siwave() + assert os.path.exists(out) + res = edbapp.export_siwave_dc_results(out, "SIwaveDCIR1") + for i in res: + assert os.path.exists(i) + edbapp.close() + + def test_build_simulation_project(self): + """Build a ready-to-solve simulation project.""" + target_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + out_edb = os.path.join(self.local_scratch.path, "Build_project.aedb") + self.local_scratch.copyfolder(target_path, out_edb) + edbapp = EdbGrpc(out_edb, edbversion=desktop_version) + sim_setup = SimulationConfiguration() + sim_setup.signal_nets = [ + "DDR4_A0", + "DDR4_A1", + "DDR4_A2", + "DDR4_A3", + "DDR4_A4", + "DDR4_A5", + ] + sim_setup.power_nets = ["GND"] + sim_setup.do_cutout_subdesign = True + sim_setup.components = ["U1", "U15"] + sim_setup.use_default_coax_port_radial_extension = False + sim_setup.cutout_subdesign_expansion = 0.001 + sim_setup.start_freq = 0 + sim_setup.stop_freq = 20e9 + sim_setup.step_freq = 10e6 + assert edbapp.build_simulation_project(sim_setup) + edbapp.close() + + def test_build_simulation_project_with_multiple_batch_solve_settings(self): + """Build a ready-to-solve simulation project.""" + target_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + out_edb = os.path.join(self.local_scratch.path, "build_project2.aedb") + self.local_scratch.copyfolder(target_path, out_edb) + edbapp = EdbGrpc(out_edb, edbversion=desktop_version) + sim_setup = SimulationConfiguration() + sim_setup.batch_solve_settings.signal_nets = [ + "DDR4_A0", + "DDR4_A1", + "DDR4_A2", + "DDR4_A3", + "DDR4_A4", + "DDR4_A5", + ] + sim_setup.batch_solve_settings.power_nets = ["GND"] + sim_setup.batch_solve_settings.do_cutout_subdesign = True + sim_setup.batch_solve_settings.components = ["U1", "U15"] + sim_setup.batch_solve_settings.use_default_coax_port_radial_extension = False + sim_setup.batch_solve_settings.cutout_subdesign_expansion = 0.001 + sim_setup.batch_solve_settings.start_freq = 0 + sim_setup.batch_solve_settings.stop_freq = 20e9 + sim_setup.batch_solve_settings.step_freq = 10e6 + sim_setup.batch_solve_settings.use_pyaedt_cutout = True + assert edbapp.build_simulation_project(sim_setup) + assert edbapp.are_port_reference_terminals_connected() + port1 = list(edbapp.excitations.values())[0] + assert port1.magnitude == 0.0 + assert port1.phase == 0 + assert port1.reference_net_name == "GND" + assert not port1.deembed + assert port1.impedance == 50.0 + assert not port1.is_circuit_port + assert not port1.renormalize + assert port1.renormalize_z0 == (50.0, 0.0) + assert not port1.get_pin_group_terminal_reference_pin() + assert not port1.get_pad_edge_terminal_reference_pin() + edbapp.close() diff --git a/tests/grpc/system/test_edb_components.py b/tests/grpc/system/test_edb_components.py new file mode 100644 index 0000000000..fb41eba639 --- /dev/null +++ b/tests/grpc/system/test_edb_components.py @@ -0,0 +1,464 @@ +"""Tests related to Edb components +""" +import os +import pytest +import math + +# from pyedb import Edb +from pyedb.legacy.edb import EdbLegacy + +from tests.conftest import local_path +from tests.conftest import desktop_version +from tests.legacy.system.conftest import test_subfolder + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +bom_example = "bom_example.csv" + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_components_get_pin_from_component(self): + """Evaluate access to a pin from a component.""" + comp = self.edbapp.components.get_component_by_name("J1") + assert comp is not None + pin = self.edbapp.components.get_pin_from_component("J1", pinName="1") + assert pin is not False + + def test_components_create_coax_port_on_component(self): + """Create a coaxial port on a component from its pin.""" + coax_port = self.edbapp.components["U6"].pins["R3"].create_coax_port("coax_port") + coax_port.radial_extent_factor = 3 + assert coax_port.radial_extent_factor == 3 + assert coax_port.component + assert self.edbapp.components["U6"].pins["R3"].terminal + assert self.edbapp.components["U6"].pins["R3"].id + assert self.edbapp.terminals + assert self.edbapp.ports + assert self.edbapp.components["U6"].pins["R3"].get_connected_objects() + + def test_components_properties(self): + """Access components properties.""" + assert len(self.edbapp.components.components) > 2 + assert len(self.edbapp.components.inductors) > 0 + assert len(self.edbapp.components.resistors) > 0 + assert len(self.edbapp.components.capacitors) > 0 + assert len(self.edbapp.components.ICs) > 0 + assert len(self.edbapp.components.IOs) > 0 + assert len(self.edbapp.components.Others) > 0 + + def test_components_rlc_components_values(self): + """Update values of an RLC component.""" + assert self.edbapp.components.set_component_rlc("C1", res_value=1e-3, cap_value="10e-6", isparallel=False) + assert self.edbapp.components.set_component_rlc("L10", res_value=1e-3, ind_value="10e-6", isparallel=True) + + def test_components_R1_queries(self): + """Evaluate queries over component R1.""" + assert "R1" in list(self.edbapp.components.components.keys()) + assert not self.edbapp.components.components["R1"].is_null + assert self.edbapp.components.components["R1"].res_value + assert self.edbapp.components.components["R1"].placement_layer + assert isinstance(self.edbapp.components.components["R1"].lower_elevation, float) + assert isinstance(self.edbapp.components.components["R1"].upper_elevation, float) + assert self.edbapp.components.components["R1"].top_bottom_association == 2 + assert self.edbapp.components.components["R1"].pinlist + assert self.edbapp.components.components["R1"].pins + assert self.edbapp.components.components["R1"].pins["1"].pin_number + assert self.edbapp.components.components["R1"].pins["1"].component + assert ( + self.edbapp.components.components["R1"].pins["1"].lower_elevation + == self.edbapp.components.components["R1"].lower_elevation + ) + assert ( + self.edbapp.components.components["R1"].pins["1"].placement_layer + == self.edbapp.components.components["R1"].placement_layer + ) + assert ( + self.edbapp.components.components["R1"].pins["1"].upper_elevation + == self.edbapp.components.components["R1"].upper_elevation + ) + assert ( + self.edbapp.components.components["R1"].pins["1"].top_bottom_association + == self.edbapp.components.components["R1"].top_bottom_association + ) + assert self.edbapp.components.components["R1"].pins["1"].position + assert self.edbapp.components.components["R1"].pins["1"].rotation + + def test_components_create_clearance_on_component(self): + """Evaluate the creation of a clearance on soldermask.""" + comp = self.edbapp.components.components["U1"] + assert comp.create_clearance_on_component() + + def test_components_get_components_from_nets(self): + """Access to components from nets.""" + assert self.edbapp.components.get_components_from_nets("DDR4_DQS0_P") + + def test_components_resistors(self): + """Evaluate the components resistors.""" + assert "R1" in list(self.edbapp.components.resistors.keys()) + assert "C1" not in list(self.edbapp.components.resistors.keys()) + + def test_components_capacitors(self): + """Evaluate the components capacitors.""" + assert "C1" in list(self.edbapp.components.capacitors.keys()) + assert "R1" not in list(self.edbapp.components.capacitors.keys()) + + def test_components_inductors(self): + """Evaluate the components inductors.""" + assert "L10" in list(self.edbapp.components.inductors.keys()) + assert "R1" not in list(self.edbapp.components.inductors.keys()) + + def test_components_integrated_circuits(self): + """Evaluate the components integrated circuits.""" + assert "U1" in list(self.edbapp.components.ICs.keys()) + assert "R1" not in list(self.edbapp.components.ICs.keys()) + + def test_components_inputs_outputs(self): + """Evaluate the components inputs and outputs.""" + assert "X1" in list(self.edbapp.components.IOs.keys()) + assert "R1" not in list(self.edbapp.components.IOs.keys()) + + def test_components_others(self): + """Evaluate the components other core components.""" + assert "B1" in self.edbapp.components.Others + assert "R1" not in self.edbapp.components.Others + + def test_components_components_by_partname(self): + """Evaluate the components by partname""" + comp = self.edbapp.components.components_by_partname + assert "ALTR-FBGA24_A-130" in comp + assert len(comp["ALTR-FBGA24_A-130"]) == 1 + + def test_components_get_through_resistor_list(self): + """Evaluate the components retrieve through resistors.""" + assert self.edbapp.components.get_through_resistor_list(10) + + def test_components_get_rats(self): + """Retrieve a list of dictionaries of the reference designator, pin names, and net names.""" + assert len(self.edbapp.components.get_rats()) > 0 + + def test_components_get_component_net_connections_info(self): + """Evaluate net connection information.""" + assert len(self.edbapp.components.get_component_net_connection_info("U1")) > 0 + + def test_components_get_pin_name_and_position(self): + """Retrieve components name and position.""" + cmp_pinlist = self.edbapp.padstacks.get_pinlist_from_component_and_net("U6", "GND") + pin_name = self.edbapp.components.get_aedt_pin_name(cmp_pinlist[0]) + assert type(pin_name) is str + assert len(pin_name) > 0 + assert len(cmp_pinlist[0].position) == 2 + assert len(self.edbapp.components.get_pin_position(cmp_pinlist[0])) == 2 + + def test_components_get_pins_name_from_net(self): + """Retrieve pins belonging to a net.""" + cmp_pinlist = self.edbapp.components.get_pin_from_component("U6") + assert len(self.edbapp.components.get_pins_name_from_net(cmp_pinlist, "GND")) > 0 + assert len(self.edbapp.components.get_pins_name_from_net(cmp_pinlist, "5V")) == 0 + + def test_components_delete_single_pin_rlc(self): + """Delete all RLC components with a single pin.""" + assert len(self.edbapp.components.delete_single_pin_rlc()) == 0 + + def test_components_set_component_rlc(self): + """Update values for an RLC component.""" + assert self.edbapp.components.set_component_rlc("R1", 30, 1e-9, 1e-12) + + def test_components_disable_rlc_component(self): + """Disable a RLC component.""" + assert self.edbapp.components.disable_rlc_component("R1") + + def test_components_delete(self): + """Delete a component.""" + assert self.edbapp.components.delete("R1") + + def test_components_set_model(self): + """Assign component model.""" + assert self.edbapp.components.set_component_model( + "C10", + modelpath=os.path.join( + local_path, + "example_models", + test_subfolder, + "GRM32ER72A225KA35_25C_0V.sp", + ), + modelname="GRM32ER72A225KA35_25C_0V", + ) + assert not self.edbapp.components.set_component_model( + "C100000", + modelpath=os.path.join( + local_path, + test_subfolder, + "GRM32ER72A225KA35_25C_0V.sp", + ), + modelname="GRM32ER72A225KA35_25C_0V", + ) + + # TODO: Maybe rework this test if #25 is accepted + def test_modeler_parametrize_layout(self): + """Parametrize a polygon""" + assert len(self.edbapp.modeler.polygons) > 0 + for el in self.edbapp.modeler.polygons: + if el.GetId() == 5953: + poly = el + for el in self.edbapp.modeler.polygons: + if el.GetId() == 5954: + selection_poly = el + + assert self.edbapp.modeler.parametrize_polygon(poly, selection_poly) + + def test_components_update_from_bom(self): + """Update components with values coming from a BOM file.""" + assert self.edbapp.components.update_rlc_from_bom( + os.path.join(local_path, "example_models", test_subfolder, bom_example), + delimiter=",", + valuefield="Value", + comptype="Prod name", + refdes="RefDes", + ) + assert not self.edbapp.components.components["R2"].is_enabled + self.edbapp.components.components["R2"].is_enabled = True + assert self.edbapp.components.components["R2"].is_enabled + + def test_components_export_bom(self): + """Export Bom file from layout.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_bom.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + edbapp.components.import_bom(os.path.join(local_path, "example_models", test_subfolder, "bom_example_2.csv")) + assert not edbapp.components.instances["R2"].is_enabled + assert edbapp.components.instances["U13"].partname == "SLAB-QFN-24-2550x2550TP_V" + + export_bom_path = os.path.join(self.local_scratch.path, "export_bom.csv") + assert edbapp.components.export_bom(export_bom_path) + edbapp.close() + + def test_components_create_component_from_pins(self): + """Create a component from a pin.""" + pins = self.edbapp.components.get_pin_from_component("R13") + component = self.edbapp.components.create(pins, "newcomp") + assert component + assert component.part_name == "newcomp" + assert len(component.pins) == 2 + + def test_convert_resistor_value(self): + """Convert a resistor value.""" + from pyedb.legacy.edb_core.components import resistor_value_parser + assert resistor_value_parser("100meg") + + def test_components_create_solder_ball_on_component(self): + """Set cylindrical solder balls on a given component""" + assert self.edbapp.components.set_solder_ball("U1") + + def test_components_short_component(self): + """Short pins of component with a trace.""" + assert self.edbapp.components.short_component_pins("U12", width=0.2e-3) + assert self.edbapp.components.short_component_pins("U10", ["2", "5"]) + + def test_components_type(self): + """Retrieve components type.""" + comp = self.edbapp.components["R4"] + comp.type = "Resistor" + assert comp.type == "Resistor" + comp.type = "Inductor" + assert comp.type == "Inductor" + comp.type = "Capacitor" + assert comp.type == "Capacitor" + comp.type = "IO" + assert comp.type == "IO" + comp.type = "IC" + assert comp.type == "IC" + comp.type = "Other" + assert comp.type == "Other" + + def test_componenets_deactivate_rlc(self): + """Deactivate RLC component and convert to a circuit port.""" + assert self.edbapp.components.deactivate_rlc_component(component="C1", create_circuit_port=True) + assert self.edbapp.components["C1"].is_enabled is False + self.edbapp.components["C2"].is_enabled = False + assert self.edbapp.components["C2"].is_enabled is False + self.edbapp.components["C2"].is_enabled = True + assert self.edbapp.components["C2"].is_enabled is True + + def test_components_definitions(self): + """Evaluate components definition.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0126.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + assert edbapp.components.components + assert edbapp.components.definitions + comp_def = edbapp.components.definitions["CAPC2012X12N"] + assert comp_def + comp_def.part_name = "CAPC2012X12N_new" + assert comp_def.part_name == "CAPC2012X12N_new" + assert len(comp_def.components) > 0 + cap = edbapp.components.definitions["CAPC2012X12N_new"] + assert cap.type == "Capacitor" + cap.type = "Resistor" + assert cap.type == "Resistor" + + export_path = os.path.join(self.local_scratch.path, "comp_definition.csv") + assert edbapp.components.export_definition(export_path) + assert edbapp.components.import_definition(export_path) + + assert edbapp.components.definitions["CAPC3216X180X20ML20"].assign_rlc_model(1, 2, 3) + sparam_path = os.path.join(local_path, "example_models", test_subfolder, "GRM32_DC0V_25degC_series.s2p") + assert edbapp.components.definitions["CAPC3216X180X55ML20T25"].assign_s_param_model(sparam_path) + spice_path = os.path.join(local_path, "example_models", test_subfolder, "GRM32_DC0V_25degC.mod") + assert edbapp.components.definitions["CAPMP7343X31N"].assign_spice_model(spice_path) + edbapp.close() + + def test_rlc_component_values_getter_setter(self): + """Evaluate component values getter and setter.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0136.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + components_to_change = [res for res in list(edbapp.components.Others.values()) if res.partname == "A93549-027"] + for res in components_to_change: + res.type = "Resistor" + res.res_value = [25, 0, 0] + res.res_value = 10 + assert res.res_value == 10 + res.rlc_values = [20, 1e-9, 1e-12] + assert res.res_value == 20 + assert res.ind_value == 1e-9 + assert res.cap_value == 1e-12 + res.res_value = 12.5 + assert res.res_value == 12.5 and res.ind_value == 1e-9 and res.cap_value == 1e-12 + res.ind_value = 5e-9 + assert res.res_value == 12.5 and res.ind_value == 5e-9 and res.cap_value == 1e-12 + res.cap_value = 8e-12 + assert res.res_value == 12.5 and res.ind_value == 5e-9 and res.cap_value == 8e-12 + edbapp.close() + + def test_create_port_on_pin(self): + """Create port on pins.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0134b.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + pin = "A24" + ref_pins = [pin for pin in list(edbapp.components["U1"].pins.values()) if pin.net_name == "GND"] + assert edbapp.components.create_port_on_pins(refdes="U1", pins=pin, reference_pins=ref_pins) + assert edbapp.components.create_port_on_pins(refdes="U1", pins="C1", reference_pins=["A11"]) + assert edbapp.components.create_port_on_pins(refdes="U1", pins="C2", reference_pins=["A11"]) + assert edbapp.components.create_port_on_pins(refdes="U1", pins=["A24"], reference_pins=["A11", "A16"]) + assert edbapp.components.create_port_on_pins(refdes="U1", pins=["A26"], reference_pins=["A11", "A16", "A17"]) + assert edbapp.components.create_port_on_pins(refdes="U1", pins=["A28"], reference_pins=["A11", "A16"]) + edbapp.close() + + def test_replace_rlc_by_gap_boundaries(self): + """Replace RLC component by RLC gap boundaries.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_boundaries.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + for refdes, cmp in edbapp.components.components.items(): + edbapp.components.replace_rlc_by_gap_boundaries(refdes) + rlc_list = [ + term for term in list(edbapp.active_layout.Terminals) if str(term.GetBoundaryType()) == "RlcBoundary" + ] + assert len(rlc_list) == 944 + edbapp.close() + + def test_components_get_component_placement_vector(self): + """Get the placement vector between 2 components.""" + edb2 = EdbLegacy(self.target_path4, edbversion=desktop_version) + for _, cmp in edb2.components.instances.items(): + assert isinstance(cmp.solder_ball_placement, int) + mounted_cmp = edb2.components.get_component_by_name("BGA") + hosting_cmp = self.edbapp.components.get_component_by_name("U1") + ( + result, + vector, + rotation, + solder_ball_height, + ) = self.edbapp.components.get_component_placement_vector( + mounted_component=mounted_cmp, + hosting_component=hosting_cmp, + mounted_component_pin1="A10", + mounted_component_pin2="A12", + hosting_component_pin1="A2", + hosting_component_pin2="A4", + ) + assert result + assert abs(abs(rotation) - math.pi / 2) < 1e-9 + assert solder_ball_height == 0.00033 + assert len(vector) == 2 + ( + result, + vector, + rotation, + solder_ball_height, + ) = self.edbapp.components.get_component_placement_vector( + mounted_component=mounted_cmp, + hosting_component=hosting_cmp, + mounted_component_pin1="A10", + mounted_component_pin2="A12", + hosting_component_pin1="A2", + hosting_component_pin2="A4", + flipped=True, + ) + assert result + assert abs(rotation + math.pi / 2) < 1e-9 + assert solder_ball_height == 0.00033 + assert len(vector) == 2 + edb2.close() + + def test_components_assign(self): + """Assign RLC model, S-parameter model and spice model.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_17.aedb") + self.local_scratch.copyfolder(source_path, target_path) + sparam_path = os.path.join(local_path, "example_models", test_subfolder, "GRM32_DC0V_25degC_series.s2p") + spice_path = os.path.join(local_path, "example_models", test_subfolder, "GRM32_DC0V_25degC.mod") + + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + comp = edbapp.components.instances["R2"] + assert not comp.assign_rlc_model() + comp.assign_rlc_model(1, None, 3, False) + assert ( + not comp.is_parallel_rlc + and float(comp.res_value) == 1 + and float(comp.ind_value) == 0 + and float(comp.cap_value) == 3 + ) + comp.assign_rlc_model(1, 2, 3, True) + assert comp.is_parallel_rlc + assert ( + comp.is_parallel_rlc + and float(comp.res_value) == 1 + and float(comp.ind_value) == 2 + and float(comp.cap_value) == 3 + ) + assert comp.value + assert not comp.spice_model and not comp.s_param_model and not comp.netlist_model + assert comp.assign_s_param_model(sparam_path) and comp.value + assert comp.s_param_model + assert edbapp.components.nport_comp_definition + assert comp.assign_spice_model(spice_path) and comp.value + assert comp.spice_model + comp.type = "Inductor" + comp.value = 10 # This command set the model back to ideal RLC + assert comp.type == "Inductor" and comp.value == 10 and float(comp.ind_value) == 10 + edbapp.close() + + def test_components_bounding_box(self): + """Get component's bounding box.""" + target_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + out_edb = os.path.join(self.local_scratch.path, "get_comp_bbox.aedb") + self.local_scratch.copyfolder(target_path, out_edb) + edbapp = EdbLegacy(out_edb, edbversion=desktop_version) + component = edbapp.components.instances["U1"] + assert component.bounding_box + assert isinstance(component.rotation, float) + edbapp.close() diff --git a/tests/grpc/system/test_edb_differential_pairs.py b/tests/grpc/system/test_edb_differential_pairs.py new file mode 100644 index 0000000000..25ab89f149 --- /dev/null +++ b/tests/grpc/system/test_edb_differential_pairs.py @@ -0,0 +1,23 @@ +"""Tests related to Edb differential pairs +""" + +import pytest + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_differential_pairs_queries(self): + """Evaluate differential pairs queries""" + self.edbapp.differential_pairs.auto_identify() + diff_pair = self.edbapp.differential_pairs.create("new_pair1", "PCIe_Gen4_RX1_P", "PCIe_Gen4_RX1_N") + assert diff_pair.positive_net.name == "PCIe_Gen4_RX1_P" + assert diff_pair.negative_net.name == "PCIe_Gen4_RX1_N" + assert self.edbapp.differential_pairs.items diff --git a/tests/grpc/system/test_edb_extended_nets.py b/tests/grpc/system/test_edb_extended_nets.py new file mode 100644 index 0000000000..1b7eaa62c6 --- /dev/null +++ b/tests/grpc/system/test_edb_extended_nets.py @@ -0,0 +1,27 @@ +"""Tests related to Edb extended nets +""" + +import pytest + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_nets_queries(self): + """Evaluate nets queries""" + assert self.edbapp.extended_nets.auto_identify_signal() + assert self.edbapp.extended_nets.auto_identify_power() + extended_net_name, _ = next(iter(self.edbapp.extended_nets.items.items())) + assert self.edbapp.extended_nets[extended_net_name] + assert self.edbapp.extended_nets[extended_net_name].nets + assert self.edbapp.extended_nets[extended_net_name].components + assert self.edbapp.extended_nets[extended_net_name].rlc + assert self.edbapp.extended_nets[extended_net_name].serial_rlc + assert self.edbapp.extended_nets.create("new_ex_net", "DDR4_A1") diff --git a/tests/grpc/system/test_edb_ipc.py b/tests/grpc/system/test_edb_ipc.py new file mode 100644 index 0000000000..91b3a87eaf --- /dev/null +++ b/tests/grpc/system/test_edb_ipc.py @@ -0,0 +1,62 @@ +"""Tests related to the interaction between Edb and Ipc2581 +""" + +import os +import pytest + +from pyedb.legacy.edb import EdbLegacy +from tests.legacy.system.conftest import test_subfolder +from tests.legacy.system.conftest import local_path +from tests.conftest import desktop_version + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_export_to_ipc2581_0(self): + """Export of a loaded aedb file to an XML IPC2581 file""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1_cut.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_ipc.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + xml_file = os.path.join(self.local_scratch.path, "test.xml") + edbapp.export_to_ipc2581(xml_file) + assert os.path.exists(xml_file) + + # Export should be made with units set to default -millimeter-. + edbapp.export_to_ipc2581(xml_file, "mm") + assert os.path.exists(xml_file) + edbapp.close() + + def test_146_export_ipc_1(self): + """Export of a loaded aedb file to an XML IPC2581 file""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_ipc", "ANSYS-HSD_V1_boundaries.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + xml_file = os.path.join(target_path, "test.xml") + edbapp.export_to_ipc2581(xml_file) + assert os.path.isfile(xml_file) + ipc_edb = EdbLegacy(xml_file, edbversion=desktop_version) + ipc_stats = ipc_edb.get_statistics() + assert ipc_stats.layout_size == (0.1492, 0.0837) + assert ipc_stats.num_capacitors == 380 + assert ipc_stats.num_discrete_components == 31 + assert ipc_stats.num_inductors == 10 + assert ipc_stats.num_layers == 15 + assert ipc_stats.num_nets == 348 + assert ipc_stats.num_polygons == 138 + assert ipc_stats.num_resistors == 82 + assert ipc_stats.num_traces == 1565 + assert ipc_stats.num_traces == 1565 + assert ipc_stats.num_vias == 4730 + assert ipc_stats.stackup_thickness == 0.001748 + edbapp.close() + ipc_edb.close() diff --git a/tests/grpc/system/test_edb_materials.py b/tests/grpc/system/test_edb_materials.py new file mode 100644 index 0000000000..54eb13eb96 --- /dev/null +++ b/tests/grpc/system/test_edb_materials.py @@ -0,0 +1,113 @@ +"""Tests related to Edb +""" + +import os +from pyedb.legacy.edb_core.edb_data.simulation_configuration import SimulationConfiguration +import pytest + +from pyedb.legacy.edb import EdbLegacy +from tests.conftest import local_path +from tests.conftest import desktop_version +from tests.legacy.system.conftest import test_subfolder + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch): + self.edbapp = edbapp + self.local_scratch = local_scratch + + def test_material_properties(self): + """Evaluate materials properties.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0127.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + assert isinstance(edbapp.materials.materials, dict) + edbapp.materials["FR4_epoxy"].conductivity = 1 + assert edbapp.materials["FR4_epoxy"].conductivity == 1 + edbapp.materials["FR4_epoxy"].permittivity = 1 + assert edbapp.materials["FR4_epoxy"].permittivity == 1 + edbapp.materials["FR4_epoxy"].loss_tangent = 1 + assert edbapp.materials["FR4_epoxy"].loss_tangent == 1 + edbapp.materials.add_conductor_material("new_conductor", 1) + assert not edbapp.materials.add_conductor_material("new_conductor", 1) + edbapp.materials.add_dielectric_material("new_dielectric", 1, 2) + assert not edbapp.materials.add_dielectric_material("new_dielectric", 1, 2) + edbapp.materials["FR4_epoxy"].magnetic_loss_tangent = 0.01 + assert edbapp.materials["FR4_epoxy"].magnetic_loss_tangent == 0.01 + edbapp.materials["FR4_epoxy"].youngs_modulus = 5000 + assert edbapp.materials["FR4_epoxy"].youngs_modulus == 5000 + edbapp.materials["FR4_epoxy"].mass_density = 50 + + assert edbapp.materials["FR4_epoxy"].mass_density == 50 + edbapp.materials["FR4_epoxy"].thermal_conductivity = 1e-5 + + assert edbapp.materials["FR4_epoxy"].thermal_conductivity == 1e-5 + edbapp.materials["FR4_epoxy"].thermal_expansion_coefficient = 1e-7 + + assert edbapp.materials["FR4_epoxy"].thermal_expansion_coefficient == 1e-7 + edbapp.materials["FR4_epoxy"].poisson_ratio = 1e-3 + assert edbapp.materials["FR4_epoxy"].poisson_ratio == 1e-3 + assert edbapp.materials["new_conductor"] + assert edbapp.materials.duplicate("FR4_epoxy", "FR41") + assert edbapp.materials["FR41"] + assert edbapp.materials["FR4_epoxy"].conductivity == edbapp.materials["FR41"].conductivity + assert edbapp.materials["FR4_epoxy"].permittivity == edbapp.materials["FR41"].permittivity + assert edbapp.materials["FR4_epoxy"].loss_tangent == edbapp.materials["FR41"].loss_tangent + assert edbapp.materials["FR4_epoxy"].magnetic_loss_tangent == edbapp.materials["FR41"].magnetic_loss_tangent + assert edbapp.materials["FR4_epoxy"].youngs_modulus == edbapp.materials["FR41"].youngs_modulus + assert edbapp.materials["FR4_epoxy"].mass_density == edbapp.materials["FR41"].mass_density + assert edbapp.materials["FR4_epoxy"].thermal_conductivity == edbapp.materials["FR41"].thermal_conductivity + assert ( + edbapp.materials["FR4_epoxy"].thermal_expansion_coefficient + == edbapp.materials["FR41"].thermal_expansion_coefficient + ) + assert edbapp.materials["FR4_epoxy"].poisson_ratio == edbapp.materials["FR41"].poisson_ratio + assert edbapp.materials.add_debye_material("My_Debye2", 5, 3, 0.02, 0.05, 1e5, 1e9) + assert edbapp.materials.add_djordjevicsarkar_material("MyDjord2", 3.3, 0.02, 3.3) + freq = [0, 2, 3, 4, 5, 6] + rel_perm = [1e9, 1.1e9, 1.2e9, 1.3e9, 1.5e9, 1.6e9] + loss_tan = [0.025, 0.026, 0.027, 0.028, 0.029, 0.030] + assert edbapp.materials.add_multipole_debye_material("My_MP_Debye2", freq, rel_perm, loss_tan) + edbapp.close() + edbapp = EdbLegacy(edbversion=desktop_version) + assert "air" in edbapp.materials.materials + edbapp.close() + + def test_material_load_syslib_amat(self): + """Load material from an amat file.""" + assert self.edbapp.materials.load_syslib_amat() + material_list = list(self.edbapp.materials.materials.keys()) + assert material_list + assert len(material_list) > 0 + assert self.edbapp.materials.materials["Rogers RO3003 (tm)"].loss_tangent == 0.0013 + assert self.edbapp.materials.materials["Rogers RO3003 (tm)"].permittivity == 3.0 + + def test_materials_read_materials(self): + """Read materials.""" + path = os.path.join(local_path, "example_models", "syslib", "Materials.amat") + mats = self.edbapp.materials.read_materials(path) + key = "FC-78" + assert key in mats + assert mats[key]["thermal_conductivity"] == 0.062 + assert mats[key]["mass_density"] == 1700 + assert mats[key]["specific_heat"] == 1050 + assert mats[key]["thermal_expansion_coeffcient"] == 0.0016 + key = "Polyflon CuFlon (tm)" + assert key in mats + assert mats[key]["permittivity"] == 2.1 + assert mats[key]["tangent_delta"] == 0.00045 + key = "Water(@360K)" + assert key in mats + assert mats[key]["thermal_conductivity"] == 0.6743 + assert mats[key]["mass_density"] == 967.4 + assert mats[key]["specific_heat"] == 4206 + assert mats[key]["thermal_expansion_coeffcient"] == 0.0006979 + key = "steel_stainless" + assert mats[key]["conductivity"] == 1100000 + assert mats[key]["thermal_conductivity"] == 13.8 + assert mats[key]["mass_density"] == 8055 + assert mats[key]["specific_heat"] == 480 + assert mats[key]["thermal_expansion_coeffcient"] == 1.08e-005 diff --git a/tests/grpc/system/test_edb_modeler.py b/tests/grpc/system/test_edb_modeler.py new file mode 100644 index 0000000000..b6f35ddf2e --- /dev/null +++ b/tests/grpc/system/test_edb_modeler.py @@ -0,0 +1,274 @@ +"""Tests related to Edb modeler +""" + +from pyedb.legacy.edb import EdbLegacy +import pytest +from pyedb.generic.settings import settings +from tests.conftest import local_path +from tests.conftest import desktop_version + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_modeler_polygons(self): + """Evaluate modeler polygons""" + assert len(self.edbapp.modeler.polygons) > 0 + assert self.edbapp.modeler.polygons[0].is_void == self.edbapp.modeler.polygons[0].IsVoid() + + poly0 = self.edbapp.modeler.polygons[0] + assert self.edbapp.modeler.polygons[0].clone() + assert isinstance(poly0.voids, list) + assert isinstance(poly0.points_raw(), list) + assert isinstance(poly0.points(), tuple) + assert isinstance(poly0.points()[0], list) + assert poly0.points()[0][0] >= 0.0 + assert poly0.points_raw()[0].X.ToDouble() >= 0.0 + assert poly0.type == "Polygon" + assert not poly0.is_arc(poly0.points_raw()[0]) + assert isinstance(poly0.voids, list) + assert isinstance(poly0.get_closest_point([0, 0]), list) + assert isinstance(poly0.get_closest_arc_midpoint([0, 0]), list) + assert isinstance(poly0.arcs, list) + assert isinstance(poly0.longest_arc.length, float) + assert isinstance(poly0.shortest_arc.length, float) + assert not poly0.in_polygon([0, 0]) + assert isinstance(poly0.arcs[0].center, list) + assert isinstance(poly0.arcs[0].radius, float) + assert poly0.arcs[0].is_segment + assert not poly0.arcs[0].is_point + assert not poly0.arcs[0].is_ccw + assert isinstance(poly0.arcs[0].points_raw, list) + assert isinstance(poly0.arcs[0].points, tuple) + assert isinstance(poly0.intersection_type(poly0), int) + assert poly0.is_intersecting(poly0) + + def test_modeler_paths(self): + """Evaluate modeler paths""" + assert len(self.edbapp.modeler.paths) > 0 + assert self.edbapp.modeler.paths[0].type == "Path" + assert self.edbapp.modeler.paths[0].clone() + assert isinstance(self.edbapp.modeler.paths[0].width, float) + self.edbapp.modeler.paths[0].width = "1mm" + assert self.edbapp.modeler.paths[0].width == 0.001 + + def test_modeler_primitives_by_layer(self): + """Evaluate modeler primitives by layer""" + assert self.edbapp.modeler.primitives_by_layer["1_Top"][0].layer_name == "1_Top" + assert self.edbapp.modeler.primitives_by_layer["1_Top"][0].layer.GetName() == "1_Top" + assert not self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_negative + assert not self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_void + self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_negative = True + assert self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_negative + self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_negative = False + assert not self.edbapp.modeler.primitives_by_layer["1_Top"][0].has_voids + assert not self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_parameterized + assert isinstance(self.edbapp.modeler.primitives_by_layer["1_Top"][0].get_hfss_prop(), tuple) + assert not self.edbapp.modeler.primitives_by_layer["1_Top"][0].is_zone_primitive + assert self.edbapp.modeler.primitives_by_layer["1_Top"][0].can_be_zone_primitive + + def test_modeler_primitives(self): + """Evaluate modeler primitives""" + assert len(self.edbapp.modeler.rectangles) > 0 + assert len(self.edbapp.modeler.circles) > 0 + assert len(self.edbapp.modeler.bondwires) == 0 + assert "1_Top" in self.edbapp.modeler.polygons_by_layer.keys() + assert len(self.edbapp.modeler.polygons_by_layer["1_Top"]) > 0 + assert len(self.edbapp.modeler.polygons_by_layer["DE1"]) == 0 + assert self.edbapp.modeler.rectangles[0].type == "Rectangle" + assert self.edbapp.modeler.circles[0].type == "Circle" + + def test_modeler_get_polygons_bounding(self): + """Retrieve polygons bounding box.""" + polys = self.edbapp.modeler.get_polygons_by_layer("GND") + for poly in polys: + bounding = self.edbapp.modeler.get_polygon_bounding_box(poly) + assert len(bounding) == 4 + + def test_modeler_get_polygons_by_layer_and_nets(self): + """Retrieve polygons by layer and nets.""" + nets = ["GND", "1V0"] + polys = self.edbapp.modeler.get_polygons_by_layer("16_Bottom", nets) + assert polys + + + def test_modeler_get_polygons_points(self): + """Retrieve polygons points.""" + polys = self.edbapp.modeler.get_polygons_by_layer("GND") + for poly in polys: + points = self.edbapp.modeler.get_polygon_points(poly) + assert points + + def test_modeler_create_polygon(self): + """Create a polygon based on a shape or points.""" + settings.enable_error_handler = True + points = [ + [-0.025, -0.02], + [0.025, -0.02], + [0.025, 0.02], + [-0.025, 0.02], + [-0.025, -0.02], + ] + plane = self.edbapp.modeler.Shape("polygon", points=points) + points = [ + [-0.001, -0.001], + [0.001, -0.001, "ccw", 0.0, -0.0012], + [0.001, 0.001], + [0.0015, 0.0015, 0.0001], + [-0.001, 0.0015], + [-0.001, -0.001], + ] + void1 = self.edbapp.modeler.Shape("polygon", points=points) + void2 = self.edbapp.modeler.Shape("rectangle", [-0.002, 0.0], [-0.015, 0.0005]) + assert self.edbapp.modeler.create_polygon(plane, "1_Top", [void1, void2]) + self.edbapp["polygon_pts_x"] = -1.025 + self.edbapp["polygon_pts_y"] = -1.02 + points = [ + ["polygon_pts_x", "polygon_pts_y"], + [1.025, -1.02], + [1.025, 1.02], + [-1.025, 1.02], + [-1.025, -1.02], + ] + assert self.edbapp.modeler.create_polygon(points, "1_Top") + settings.enable_error_handler = False + + def test_modeler_create_trace(self): + """Create a trace based on a list of points.""" + points = [ + [-0.025, -0.02], + [0.025, -0.02], + [0.025, 0.02], + ] + trace = self.edbapp.modeler.create_trace(points, "1_Top") + assert trace + assert isinstance(trace.get_center_line(), list) + assert isinstance(trace.get_center_line(True), list) + self.edbapp["delta_x"] = "1mm" + assert trace.add_point("delta_x", "1mm", True) + assert trace.get_center_line(True)[-1][0] == "(delta_x)+(0.025)" + assert trace.add_point(0.001, 0.002) + assert trace.get_center_line()[-1] == [0.001, 0.002] + + def test_modeler_add_void(self): + """Add a void into a shape.""" + plane_shape = self.edbapp.modeler.Shape("rectangle", pointA=["-5mm", "-5mm"], pointB=["5mm", "5mm"]) + plane = self.edbapp.modeler.create_polygon(plane_shape, "1_Top", net_name="GND") + void = self.edbapp.modeler.create_trace([["0", "0"], ["0", "1mm"]], layer_name="1_Top", width="0.1mm") + assert self.edbapp.modeler.add_void(plane, void) + assert plane.add_void(void) + + def test_modeler_fix_circle_void(self): + """Fix issues when circle void are clipped due to a bug in EDB.""" + assert self.edbapp.modeler.fix_circle_void_for_clipping() + + def test_modeler_primitives_area(self): + """Access primitives total area.""" + i = 0 + while i < 10: + assert self.edbapp.modeler.primitives[i].area(False) > 0 + assert self.edbapp.modeler.primitives[i].area(True) > 0 + i += 1 + assert self.edbapp.modeler.primitives[i].bbox + assert self.edbapp.modeler.primitives[i].center + assert self.edbapp.modeler.primitives[i].get_closest_point((0, 0)) + assert self.edbapp.modeler.primitives[i].polygon_data + assert self.edbapp.modeler.paths[0].length + + def test_modeler_create_rectangle(self): + """Create rectangle.""" + rect = self.edbapp.modeler.create_rectangle("1_Top", "SIG1", ["0", "0"], ["2mm", "3mm"]) + assert rect + rect.is_negative = True + assert rect.is_negative + rect.is_negative = False + assert not rect.is_negative + assert self.edbapp.modeler.create_rectangle( + "1_Top", + "SIG2", + center_point=["0", "0"], + width="4mm", + height="5mm", + representation_type="CenterWidthHeight", + ) + + def test_modeler_create_circle(self): + """Create circle.""" + poly = self.edbapp.modeler.create_polygon_from_points([[0, 0], [100, 0], [100, 100], [0, 100]], "1_Top") + assert poly + poly.add_void([[20, 20], [20, 30], [100, 30], [100, 20]]) + poly2 = self.edbapp.modeler.create_polygon_from_points([[60, 60], [60, 150], [150, 150], [150, 60]], "1_Top") + new_polys = poly.subtract(poly2) + assert len(new_polys) == 1 + circle = self.edbapp.modeler.create_circle("1_Top", 40, 40, 15) + assert circle + intersection = new_polys[0].intersect(circle) + assert len(intersection) == 1 + circle2 = self.edbapp.modeler.create_circle("1_Top", 20, 20, 15) + assert circle2.unite(intersection) + + def test_modeler_defeature(self): + """Defeature the polygon.""" + assert self.edbapp.modeler.defeature_polygon(self.edbapp.modeler.primitives_by_net["GND"][-1], 0.01) + + def test_modeler_primitives_boolean_operation(self): + """Evaluate modeler primitives boolean operations.""" + from pyedb.legacy.edb import EdbLegacy + edb = EdbLegacy() + edb.stackup.add_layer(layer_name="test") + x = edb.modeler.create_polygon( + layer_name="test", main_shape=[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]] + ) + assert x + x_hole1 = edb.modeler.create_polygon( + layer_name="test", main_shape=[[1.0, 1.0], [4.5, 1.0], [4.5, 9.0], [1.0, 9.0]] + ) + x_hole2 = edb.modeler.create_polygon( + layer_name="test", main_shape=[[4.5, 1.0], [9.0, 1.0], [9.0, 9.0], [4.5, 9.0]] + ) + x = x.subtract([x_hole1, x_hole2])[0] + assert x + y = edb.modeler.create_polygon(layer_name="foo", main_shape=[[4.0, 3.0], [6.0, 3.0], [6.0, 6.0], [4.0, 6.0]]) + z = x.subtract(y) + assert z + edb.stackup.add_layer(layer_name="foo") + x = edb.modeler.create_polygon( + layer_name="foo", main_shape=[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]] + ) + x_hole = edb.modeler.create_polygon( + layer_name="foo", main_shape=[[1.0, 1.0], [9.0, 1.0], [9.0, 9.0], [1.0, 9.0]] + ) + y = x.subtract(x_hole)[0] + z = edb.modeler.create_polygon( + layer_name="foo", main_shape=[[-15.0, 5.0], [15.0, 5.0], [15.0, 6.0], [-15.0, 6.0]] + ) + assert y.intersect(z) + + edb.stackup.add_layer(layer_name="test2") + x = edb.modeler.create_polygon( + layer_name="test2", main_shape=[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]] + ) + x_hole = edb.modeler.create_polygon( + layer_name="test2", main_shape=[[1.0, 1.0], [9.0, 1.0], [9.0, 9.0], [1.0, 9.0]] + ) + y = x.subtract(x_hole)[0] + assert y.voids + y_clone = y.clone() + assert y_clone.voids + edb.close() + + def test_modeler_path_convert_to_polygon(self): + import os + target_path = os.path.join(local_path, "example_models", "convert_and_merge_path.aedb") + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + for path in edbapp.modeler.paths: + assert path.convert_to_polygon() + assert edbapp.nets.merge_nets_polygons("test") + edbapp.close() diff --git a/tests/grpc/system/test_edb_net_classes.py b/tests/grpc/system/test_edb_net_classes.py new file mode 100644 index 0000000000..e5df180f8f --- /dev/null +++ b/tests/grpc/system/test_edb_net_classes.py @@ -0,0 +1,25 @@ +"""Tests related to Edb net classes +""" + +import pytest + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_net_classes_queries(self): + """Evaluate net classes queries""" + assert self.edbapp.net_classes.items + assert self.edbapp.net_classes.create("DDR4_ADD", ["DDR4_A0", "DDR4_A1"]) + assert self.edbapp.net_classes["DDR4_ADD"].name == "DDR4_ADD" + assert self.edbapp.net_classes["DDR4_ADD"].nets + self.edbapp.net_classes["DDR4_ADD"].name = "DDR4_ADD_RENAMED" + assert not self.edbapp.net_classes["DDR4_ADD_RENAMED"].is_null + diff --git a/tests/grpc/system/test_edb_nets.py b/tests/grpc/system/test_edb_nets.py new file mode 100644 index 0000000000..23b93f6088 --- /dev/null +++ b/tests/grpc/system/test_edb_nets.py @@ -0,0 +1,121 @@ +"""Tests related to Edb nets +""" + +import os +import pytest +from pyedb.legacy.edb import EdbLegacy +from tests.conftest import desktop_version +from tests.conftest import local_path +from tests.legacy.system.conftest import test_subfolder + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + + def test_nets_queries(self): + """Evaluate nets queries""" + assert len(self.edbapp.nets.netlist) > 0 + signalnets = self.edbapp.nets.signal + assert not signalnets[list(signalnets.keys())[0]].is_power_ground + assert not signalnets[list(signalnets.keys())[0]].IsPowerGround() + assert len(list(signalnets[list(signalnets.keys())[0]].primitives)) > 0 + assert len(signalnets) > 2 + + powernets = self.edbapp.nets.power + assert len(powernets) > 2 + assert powernets["AVCC_1V3"].is_power_ground + powernets["AVCC_1V3"].is_power_ground = False + assert not powernets["AVCC_1V3"].is_power_ground + powernets["AVCC_1V3"].is_power_ground = True + assert powernets["AVCC_1V3"].name == "AVCC_1V3" + assert powernets["AVCC_1V3"].IsPowerGround() + assert len(list(powernets["AVCC_1V3"].components.keys())) > 0 + assert len(powernets["AVCC_1V3"].primitives) > 0 + + assert self.edbapp.nets.find_or_create_net("GND") + assert self.edbapp.nets.find_or_create_net(start_with="gn") + assert self.edbapp.nets.find_or_create_net(start_with="g", end_with="d") + assert self.edbapp.nets.find_or_create_net(end_with="d") + assert self.edbapp.nets.find_or_create_net(contain="usb") + assert self.edbapp.nets["AVCC_1V3"].extended_net is None + self.edbapp.extended_nets.auto_identify_power() + assert self.edbapp.nets["AVCC_1V3"].extended_net + + def test_nets_get_power_tree(self): + """Evaluate nets get powertree.""" + OUTPUT_NET = "5V" + GROUND_NETS = ["GND", "PGND"] + ( + component_list, + component_list_columns, + net_group, + ) = self.edbapp.nets.get_powertree(OUTPUT_NET, GROUND_NETS) + assert component_list + assert component_list_columns + assert net_group + + def test_nets_delete(self): + """Delete a net.""" + assert "JTAG_TDI" in self.edbapp.nets + self.edbapp.nets["JTAG_TCK"].delete() + nets_deleted = self.edbapp.nets.delete("JTAG_TDI") + assert "JTAG_TDI" in nets_deleted + assert "JTAG_TDI" not in self.edbapp.nets + + def test_nets_classify_nets(self): + """Reassign power based on list of nets.""" + assert "SFPA_SDA" in self.edbapp.nets.signal + assert "SFPA_SCL" in self.edbapp.nets.signal + assert "SFPA_VCCR" in self.edbapp.nets.power + + assert self.edbapp.nets.classify_nets(["SFPA_SDA", "SFPA_SCL"], ["SFPA_VCCR"]) + assert "SFPA_SDA" in self.edbapp.nets.power + assert "SFPA_SDA" not in self.edbapp.nets.signal + assert "SFPA_SCL" in self.edbapp.nets.power + assert "SFPA_SCL" not in self.edbapp.nets.signal + assert "SFPA_VCCR" not in self.edbapp.nets.power + assert "SFPA_VCCR" in self.edbapp.nets.signal + + assert self.edbapp.nets.classify_nets(["SFPA_VCCR"], ["SFPA_SDA", "SFPA_SCL"]) + assert "SFPA_SDA" in self.edbapp.nets.signal + assert "SFPA_SCL" in self.edbapp.nets.signal + assert "SFPA_VCCR" in self.edbapp.nets.power + + def test_nets_arc_data(self): + """Evaluate primitive arc data.""" + assert len(self.edbapp.nets["1.2V_DVDDL"].primitives[0].arcs) > 0 + assert self.edbapp.nets["1.2V_DVDDL"].primitives[0].arcs[0].start + assert self.edbapp.nets["1.2V_DVDDL"].primitives[0].arcs[0].end + assert self.edbapp.nets["1.2V_DVDDL"].primitives[0].arcs[0].height + + @pytest.mark.slow + def test_nets_dc_shorts(self): + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_dc_shorts", "ANSYS-HSD_V1_dc_shorts.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + dc_shorts = edbapp.layout_validation.dc_shorts() + assert dc_shorts + edbapp.nets.nets["DDR4_A0"].name = "DDR4$A0" + edbapp.layout_validation.illegal_net_names(True) + + # assert len(dc_shorts) == 20 + assert ["LVDS_CH09_N", "GND"] in dc_shorts + assert ["LVDS_CH09_N", "DDR4_DM3"] in dc_shorts + assert ["DDR4_DM3", "LVDS_CH07_N"] in dc_shorts + assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) > 0 + edbapp.nets["DDR4_DM3"].find_dc_short(True) + assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) == 0 + edbapp.close() + + def test_nets_eligible_power_nets(self): + """Evaluate eligible power nets.""" + assert "GND" in [i.name for i in self.edbapp.nets.eligible_power_nets()] diff --git a/tests/grpc/system/test_edb_padstacks.py b/tests/grpc/system/test_edb_padstacks.py new file mode 100644 index 0000000000..effdee566f --- /dev/null +++ b/tests/grpc/system/test_edb_padstacks.py @@ -0,0 +1,306 @@ +"""Tests related to Edb padstacks +""" +import os +import pytest + +from pyedb.legacy.edb import EdbLegacy +from tests.conftest import local_path +from tests.conftest import desktop_version +from tests.legacy.system.conftest import target_path3 +from tests.legacy.system.conftest import test_subfolder + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path3, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path3 = target_path3 + self.target_path4 = target_path4 + + def test_get_pad_parameters(self): + """Access to pad parameters.""" + pin = self.edbapp.components.get_pin_from_component("J1", pinName="1") + parameters = self.edbapp.padstacks.get_pad_parameters( + pin[0], "1_Top", self.edbapp.padstacks.pad_type.RegularPad + ) + assert isinstance(parameters[1], list) + assert isinstance(parameters[0], int) + + def test_get_vias_from_nets(self): + """Use padstacks' get_via_instance_from_net method.""" + assert self.edbapp.padstacks.get_via_instance_from_net("GND") + assert not self.edbapp.padstacks.get_via_instance_from_net(["GND2"]) + + def test_create_with_packstack_name(self): + """Create a padstack""" + # Create myVia + self.edbapp.padstacks.create(padstackname="myVia") + assert "myVia" in list(self.edbapp.padstacks.definitions.keys()) + self.edbapp.padstacks.definitions["myVia"].hole_range = "begin_on_upper_pad" + assert self.edbapp.padstacks.definitions["myVia"].hole_range == "begin_on_upper_pad" + self.edbapp.padstacks.definitions["myVia"].hole_range = "through" + assert self.edbapp.padstacks.definitions["myVia"].hole_range == "through" + # Create myVia_vullet + self.edbapp.padstacks.create(padstackname="myVia_bullet", antipad_shape="Bullet") + assert "myVia_bullet" in list(self.edbapp.padstacks.definitions.keys()) + + self.edbapp.add_design_variable("via_x", 5e-3) + self.edbapp["via_y"] = "1mm" + assert self.edbapp["via_y"].value == 1e-3 + assert self.edbapp["via_y"].value_string == "1mm" + assert self.edbapp.padstacks.place(["via_x", "via_x+via_y"], "myVia", via_name="via_test1") + assert self.edbapp.padstacks.place(["via_x", "via_x+via_y*2"], "myVia_bullet") + self.edbapp.padstacks["via_test1"].net_name = "GND" + assert self.edbapp.padstacks["via_test1"].net_name == "GND" + padstack = self.edbapp.padstacks.place(["via_x", "via_x+via_y*3"], "myVia", is_pin=True) + for test_prop in (self.edbapp.padstacks.padstack_instances, self.edbapp.padstacks.instances): + padstack_instance = test_prop[padstack.id] + assert padstack_instance.is_pin + assert padstack_instance.position + assert padstack_instance.start_layer in padstack_instance.layer_range_names + assert padstack_instance.stop_layer in padstack_instance.layer_range_names + padstack_instance.position = [0.001, 0.002] + assert padstack_instance.position == [0.001, 0.002] + assert padstack_instance.parametrize_position() + assert isinstance(padstack_instance.rotation, float) + self.edbapp.padstacks.create_circular_padstack(padstackname="mycircularvia") + assert "mycircularvia" in list(self.edbapp.padstacks.definitions.keys()) + assert not padstack_instance.backdrill_top + assert not padstack_instance.backdrill_bottom + assert padstack_instance.delete() + via = self.edbapp.padstacks.place([0, 0], "myVia") + assert via.set_backdrill_top("Inner4(Sig2)", 0.5e-3) + assert via.backdrill_top + assert via.set_backdrill_bottom("16_Bottom", 0.5e-3) + assert via.backdrill_bottom + + def test_padstacks_get_nets_from_pin_list(self): + """Retrieve pin list from component and net.""" + cmp_pinlist = self.edbapp.padstacks.get_pinlist_from_component_and_net("U1", "GND") + assert cmp_pinlist[0].GetNet().GetName() + + def test_padstack_properties_getter(self): + """Evaluate properties""" + for el in self.edbapp.padstacks.definitions: + padstack = self.edbapp.padstacks.definitions[el] + assert padstack.hole_plating_thickness is not None or False + assert padstack.hole_properties is not None or False + assert padstack.hole_plating_thickness is not None or False + assert padstack.hole_plating_ratio is not None or False + assert padstack.via_start_layer is not None or False + assert padstack.via_stop_layer is not None or False + assert padstack.material is not None or False + assert padstack.hole_finished_size is not None or False + assert padstack.hole_rotation is not None or False + assert padstack.hole_offset_x is not None or False + assert padstack.hole_offset_y is not None or False + assert padstack.hole_type is not None or False + pad = padstack.pad_by_layer[padstack.via_stop_layer] + if not pad.shape == "NoGeometry": + assert pad.parameters is not None or False + assert pad.parameters_values is not None or False + assert pad.offset_x is not None or False + assert pad.offset_y is not None or False + assert isinstance(pad.geometry_type, int) + polygon = pad.polygon_data + if polygon: + assert polygon.GetBBox() + + def test_padstack_properties_setter(self): + """Set padstack properties""" + pad = self.edbapp.padstacks.definitions["c180h127"] + hole_pad = 8 + tol = 1e-12 + pad.hole_properties = hole_pad + pad.hole_offset_x = 0 + pad.hole_offset_y = 1 + pad.hole_rotation = 0 + pad.hole_plating_ratio = 90 + assert pad.hole_plating_ratio == 90 + pad.hole_plating_thickness = 0.3 + assert abs(pad.hole_plating_thickness - 0.3) <= tol + pad.material = "copper" + assert abs(pad.hole_properties[0] - hole_pad) < tol + offset_x = 7 + offset_y = 1 + pad.pad_by_layer[pad.via_stop_layer].shape = "Circle" + pad.pad_by_layer[pad.via_stop_layer].parameters = 7 + pad.pad_by_layer[pad.via_stop_layer].offset_x = offset_x + pad.pad_by_layer[pad.via_stop_layer].offset_y = offset_y + assert pad.pad_by_layer[pad.via_stop_layer].parameters["Diameter"].tofloat == 7 + assert pad.pad_by_layer[pad.via_stop_layer].offset_x == str(offset_x) + assert pad.pad_by_layer[pad.via_stop_layer].offset_y == str(offset_y) + pad.pad_by_layer[pad.via_stop_layer].parameters = {"Diameter": 8} + assert pad.pad_by_layer[pad.via_stop_layer].parameters["Diameter"].tofloat == 8 + pad.pad_by_layer[pad.via_stop_layer].parameters = {"Diameter": 1} + pad.pad_by_layer[pad.via_stop_layer].shape = "Square" + pad.pad_by_layer[pad.via_stop_layer].parameters = {"Size": 1} + pad.pad_by_layer[pad.via_stop_layer].shape = "Rectangle" + pad.pad_by_layer[pad.via_stop_layer].parameters = {"XSize": 1, "YSize": 1} + pad.pad_by_layer[pad.via_stop_layer].shape = "Oval" + pad.pad_by_layer[pad.via_stop_layer].parameters = {"XSize": 1, "YSize": 1, "CornerRadius": 1} + pad.pad_by_layer[pad.via_stop_layer].parameters = {"XSize": 1, "YSize": 1, "CornerRadius": 1} + pad.pad_by_layer[pad.via_stop_layer].parameters = [1, 1, 1] + + def test_padstack_get_instance_by_name(self): + """Access padstack instance by name.""" + padstack_instances = self.edbapp.padstacks.get_padstack_instance_by_net_name("GND") + assert len(padstack_instances) + padstack_1 = padstack_instances[0] + assert padstack_1.id + assert isinstance(padstack_1.bounding_box, list) + for v in padstack_instances: + if not v.is_pin: + v.name = "TestInst" + assert v.name == "TestInst" + break + + def test_padstack_duplicate_padstack(self): + """Duplicate a padstack.""" + self.edbapp.padstacks.duplicate( + target_padstack_name="c180h127", + new_padstack_name="c180h127_NEW", + ) + assert self.edbapp.padstacks.definitions["c180h127_NEW"] + + def test_padstack_set_pad_property(self): + """Set pad and antipad properties of the padstack.""" + self.edbapp.padstacks.set_pad_property( + padstack_name="c180h127", + layer_name="new", + pad_shape="Circle", + pad_params="800um", + ) + assert self.edbapp.padstacks.definitions["c180h127"].pad_by_layer["new"] + + def test_microvias(self): + """Convert padstack to microvias 3D objects.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "padstacks.aedb") + target_path = os.path.join(self.local_scratch.path, "test_128_microvias.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + assert edbapp.padstacks.definitions["Padstack_Circle"].convert_to_3d_microvias(False) + assert edbapp.padstacks.definitions["Padstack_Rectangle"].convert_to_3d_microvias(False, hole_wall_angle=10) + assert edbapp.padstacks.definitions["Padstack_Polygon_p12"].convert_to_3d_microvias(False) + edbapp.close() + + def test_split_microvias(self): + """Convert padstack definition to multiple microvias definitions.""" + edbapp = EdbLegacy(self.target_path4, edbversion=desktop_version) + assert len(edbapp.padstacks.definitions["C4_POWER_1"].split_to_microvias()) > 0 + edbapp.close() + + def test_padstack_plating_ratio_fixing(self): + """Fix hole plating ratio.""" + assert self.edbapp.padstacks.check_and_fix_via_plating() + + def test_padstack_search_reference_pins(self): + """Search for reference pins using given criteria.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_boundaries.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + pin = edbapp.components.instances["J5"].pins["19"] + assert pin + ref_pins = pin.get_reference_pins(reference_net="GND", search_radius=5e-3, max_limit=0, component_only=True) + assert len(ref_pins) == 3 + reference_pins = edbapp.padstacks.get_reference_pins( + positive_pin=pin, reference_net="GND", search_radius=5e-3, max_limit=0, component_only=True + ) + assert len(reference_pins) == 3 + reference_pins = edbapp.padstacks.get_reference_pins( + positive_pin=pin, reference_net="GND", search_radius=5e-3, max_limit=2, component_only=True + ) + assert len(reference_pins) == 2 + reference_pins = edbapp.padstacks.get_reference_pins( + positive_pin=pin, reference_net="GND", search_radius=5e-3, max_limit=0, component_only=False + ) + assert len(reference_pins) == 11 + edbapp.close() + + def test_vias_metal_volume(self): + """Metal volume of the via hole instance.""" + vias = [ + via + for via in list(self.edbapp.padstacks.padstack_instances.values()) + if not via.start_layer == via.stop_layer + ] + assert vias[0].metal_volume + assert vias[1].metal_volume + + def test_padstacks_create_rectangle_in_pad(self): + """Create a rectangle inscribed inside a padstack instance pad.""" + example_model = os.path.join(local_path, "example_models", test_subfolder, "padstacks.aedb") + self.local_scratch.copyfolder( + example_model, + os.path.join(self.local_scratch.path, "padstacks2.aedb"), + ) + edb = EdbLegacy( + edbpath=os.path.join(self.local_scratch.path, "padstacks2.aedb"), + edbversion=desktop_version, + isreadonly=True, + ) + for test_prop in (edb.padstacks.instances, edb.padstacks.padstack_instances): + padstack_instances = list(test_prop.values()) + for padstack_instance in padstack_instances: + result = padstack_instance.create_rectangle_in_pad("s", partition_max_order=8) + if padstack_instance.padstack_definition != "Padstack_None": + assert result + else: + assert not result + edb.close() + + def test_padstaks_plot_on_matplotlib(self): + """Plot a Net to Matplotlib 2D Chart.""" + edb_plot = EdbLegacy(self.target_path3, edbversion=desktop_version) + + local_png1 = os.path.join(self.local_scratch.path, "test1.png") + edb_plot.nets.plot( + nets=None, + layers=None, + save_plot=local_png1, + plot_components_on_top=True, + plot_components_on_bottom=True, + outline=[[-10e-3, -10e-3], [110e-3, -10e-3], [110e-3, 70e-3], [-10e-3, 70e-3]], + ) + assert os.path.exists(local_png1) + + local_png2 = os.path.join(self.local_scratch.path, "test2.png") + edb_plot.nets.plot( + nets="V3P3_S5", + layers=None, + save_plot=local_png2, + plot_components_on_top=True, + plot_components_on_bottom=True, + ) + assert os.path.exists(local_png2) + + local_png3 = os.path.join(self.local_scratch.path, "test3.png") + edb_plot.nets.plot( + nets=["LVL_I2C_SCL", "V3P3_S5", "GATE_V5_USB"], + layers="TOP", + color_by_net=True, + save_plot=local_png3, + plot_components_on_top=True, + plot_components_on_bottom=True, + ) + assert os.path.exists(local_png3) + + local_png4 = os.path.join(self.local_scratch.path, "test4.png") + edb_plot.stackup.plot( + save_plot=local_png4, + plot_definitions=list(edb_plot.padstacks.definitions.keys())[0], + ) + assert os.path.exists(local_png4) + + local_png5 = os.path.join(self.local_scratch.path, "test5.png") + edb_plot.stackup.plot( + scale_elevation=False, + save_plot=local_png5, + plot_definitions=list(edb_plot.padstacks.definitions.keys())[0], + ) + assert os.path.exists(local_png4) + edb_plot.close() diff --git a/tests/grpc/system/test_edb_stackup.py b/tests/grpc/system/test_edb_stackup.py new file mode 100644 index 0000000000..a6a8826307 --- /dev/null +++ b/tests/grpc/system/test_edb_stackup.py @@ -0,0 +1,1030 @@ +"""Tests related to Edb stackup +""" + +import os +import math +import pytest +from pyedb.legacy.edb import EdbLegacy +from tests.conftest import desktop_version +from tests.conftest import local_path +from tests.legacy.system.conftest import test_subfolder + +pytestmark = [pytest.mark.system, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, edbapp, local_scratch, target_path, target_path2, target_path4): + self.edbapp = edbapp + self.local_scratch = local_scratch + self.target_path = target_path + self.target_path2 = target_path2 + self.target_path4 = target_path4 + + def test_stackup_get_signal_layers(self): + """Report residual copper area per layer.""" + assert self.edbapp.stackup.residual_copper_area_per_layer() + + def test_stackup_limits(self): + """Retrieve stackup limits.""" + assert self.edbapp.stackup.limits() + + def test_stackup_add_outline(self): + """Add an outline layer named ``"Outline1"`` if it is not present.""" + edbapp = EdbLegacy( + edbversion=desktop_version, + ) + assert edbapp.stackup.add_outline_layer("Outline1") + assert not edbapp.stackup.add_outline_layer("Outline1") + edbapp.stackup.add_layer("1_Top") + assert edbapp.stackup.layers["1_Top"].thickness == 3.5e-05 + edbapp.stackup.layers["1_Top"].thickness = 4e-5 + assert edbapp.stackup.layers["1_Top"].thickness == 4e-05 + edbapp.close() + + def test_stackup_create_symmetric_stackup(self): + """Create a symmetric stackup.""" + app_edb = EdbLegacy(edbversion=desktop_version) + assert not app_edb.stackup.create_symmetric_stackup(9) + assert app_edb.stackup.create_symmetric_stackup(8) + app_edb.close() + + app_edb = EdbLegacy(edbversion=desktop_version) + assert app_edb.stackup.create_symmetric_stackup(8, soldermask=False) + app_edb.close() + + def test_stackup_place_a3dcomp_3d_placement(self): + """Place a 3D Component into current layout.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "lam_for_bottom_place.aedb") + target_path = os.path.join(self.local_scratch.path, "output.aedb") + self.local_scratch.copyfolder(source_path, target_path) + laminate_edb = EdbLegacy(target_path, edbversion=desktop_version) + chip_a3dcomp = os.path.join(local_path, "example_models", test_subfolder, "chip.a3dcomp") + try: + layout = laminate_edb.active_layout + cell_instances = list(layout.CellInstances) + assert len(cell_instances) == 0 + assert laminate_edb.stackup.place_a3dcomp_3d_placement( + chip_a3dcomp, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + place_on_top=True, + ) + cell_instances = list(layout.CellInstances) + assert len(cell_instances) == 1 + cell_instance = cell_instances[0] + assert cell_instance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + local_origin, + rotation_axis_from, + rotation_axis_to, + angle, + loc, + _, + ) = cell_instance.Get3DTransformation() + else: + ( + res, + local_origin, + rotation_axis_from, + rotation_axis_to, + angle, + loc, + ) = cell_instance.Get3DTransformation() + assert res + zero_value = laminate_edb.edb_value(0) + one_value = laminate_edb.edb_value(1) + origin_point = laminate_edb.edb_api.geometry.point3d_data(zero_value, zero_value, zero_value) + x_axis_point = laminate_edb.edb_api.geometry.point3d_data(one_value, zero_value, zero_value) + assert local_origin.IsEqual(origin_point) + assert rotation_axis_from.IsEqual(x_axis_point) + assert rotation_axis_to.IsEqual(x_axis_point) + assert angle.IsEqual(zero_value) + assert loc.IsEqual( + laminate_edb.edb_api.geometry.point3d_data(zero_value, zero_value, laminate_edb.edb_value(170e-6)) + ) + assert laminate_edb.save_edb() + finally: + laminate_edb.close() + + def test_stackup_place_a3dcomp_3d_placement_on_bottom(self): + """Place a 3D Component into current layout.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "lam_for_bottom_place.aedb") + target_path = os.path.join(self.local_scratch.path, "output.aedb") + self.local_scratch.copyfolder(source_path, target_path) + laminate_edb = EdbLegacy(target_path, edbversion=desktop_version) + chip_a3dcomp = os.path.join(local_path, "example_models", test_subfolder, "chip.a3dcomp") + try: + layout = laminate_edb.active_layout + cell_instances = list(layout.CellInstances) + assert len(cell_instances) == 0 + assert laminate_edb.stackup.place_a3dcomp_3d_placement( + chip_a3dcomp, + angle=90.0, + offset_x=0.5e-3, + offset_y=-0.5e-3, + place_on_top=False, + ) + cell_instances = list(layout.CellInstances) + assert len(cell_instances) == 1 + cell_instance = cell_instances[0] + assert cell_instance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + local_origin, + rotation_axis_from, + rotation_axis_to, + angle, + loc, + mirror, + ) = cell_instance.Get3DTransformation() + else: + ( + res, + local_origin, + rotation_axis_from, + rotation_axis_to, + angle, + loc, + ) = cell_instance.Get3DTransformation() + assert res + zero_value = laminate_edb.edb_value(0) + one_value = laminate_edb.edb_value(1) + flip_angle_value = laminate_edb.edb_value("180deg") + origin_point = laminate_edb.edb_api.geometry.point3d_data(zero_value, zero_value, zero_value) + x_axis_point = laminate_edb.edb_api.geometry.point3d_data(one_value, zero_value, zero_value) + assert local_origin.IsEqual(origin_point) + assert rotation_axis_from.IsEqual(x_axis_point) + assert rotation_axis_to.IsEqual( + laminate_edb.edb_api.geometry.point3d_data(zero_value, laminate_edb.edb_value(-1.0), zero_value) + ) + assert angle.IsEqual(flip_angle_value) + assert loc.IsEqual( + laminate_edb.edb_api.geometry.point3d_data( + laminate_edb.edb_value(0.5e-3), + laminate_edb.edb_value(-0.5e-3), + zero_value, + ) + ) + assert laminate_edb.save_edb() + finally: + laminate_edb.close() + + def test_stackup_properties_0(self): + """Evaluate various stackup properties.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0124.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + assert isinstance(edbapp.stackup.layers, dict) + assert isinstance(edbapp.stackup.signal_layers, dict) + assert isinstance(edbapp.stackup.dielectric_layers, dict) + assert isinstance(edbapp.stackup.stackup_layers, dict) + assert isinstance(edbapp.stackup.non_stackup_layers, dict) + assert not edbapp.stackup["Outline"].is_stackup_layer + assert edbapp.stackup["1_Top"].conductivity + assert edbapp.stackup["DE1"].permittivity + assert edbapp.stackup.add_layer("new_layer") + new_layer = edbapp.stackup["new_layer"] + assert new_layer.is_stackup_layer + assert not new_layer.is_negative + new_layer.name = "renamed_layer" + assert new_layer.name == "renamed_layer" + rename_layer = edbapp.stackup["renamed_layer"] + rename_layer.thickness = 50e-6 + assert rename_layer.thickness == 50e-6 + rename_layer.etch_factor = 0 + rename_layer.etch_factor = 2 + assert rename_layer.etch_factor == 2 + assert rename_layer.material + assert rename_layer.type + assert rename_layer.dielectric_fill + + rename_layer.roughness_enabled = True + assert rename_layer.roughness_enabled + rename_layer.roughness_enabled = False + assert not rename_layer.roughness_enabled + assert rename_layer.assign_roughness_model("groisse", groisse_roughness="2um") + assert rename_layer.assign_roughness_model(apply_on_surface="1_Top") + assert rename_layer.assign_roughness_model(apply_on_surface="bottom") + assert rename_layer.assign_roughness_model(apply_on_surface="side") + assert edbapp.stackup.add_layer("new_above", "1_Top", "insert_above") + assert edbapp.stackup.add_layer("new_below", "1_Top", "insert_below") + assert edbapp.stackup.add_layer("new_bottom", "1_Top", "add_on_bottom", "dielectric") + assert edbapp.stackup.remove_layer("new_bottom") + assert "new_bottom" not in edbapp.stackup.layers + + assert edbapp.stackup["1_Top"].color + edbapp.stackup["1_Top"].color = [0, 120, 0] + assert edbapp.stackup["1_Top"].color == (0, 120, 0) + edbapp.stackup["1_Top"].transparency = 10 + assert edbapp.stackup["1_Top"].transparency == 10 + assert edbapp.stackup.mode == "Laminate" + edbapp.stackup.mode = "Overlapping" + assert edbapp.stackup.mode == "Overlapping" + edbapp.stackup.mode = "MultiZone" + assert edbapp.stackup.mode == "MultiZone" + edbapp.stackup.mode = "Overlapping" + assert edbapp.stackup.mode == "Overlapping" + assert edbapp.stackup.add_layer("new_bottom", "1_Top", "add_at_elevation", "dielectric", elevation=0.0003) + edbapp.close() + + def test_stackup_properties_1(self): + """Evaluate various stackup properties.""" + edbapp = EdbLegacy(edbversion=desktop_version) + import_method = edbapp.stackup.load + export_method = edbapp.stackup.export + + assert import_method(os.path.join(local_path, "example_models", test_subfolder, "ansys_pcb_stackup.xml")) + assert "17_Bottom" in edbapp.stackup.layers.keys() + xml_export = os.path.join(self.local_scratch.path, "stackup.xml") + assert export_method(xml_export) + assert os.path.exists(xml_export) + assert import_method(os.path.join(local_path, "example_models", test_subfolder, "ansys_pcb_stackup.csv")) + assert "18_Bottom" in edbapp.stackup.layers.keys() + assert edbapp.stackup.add_layer("19_Bottom", None, "add_on_top", material="iron") + export_stackup_path = os.path.join(self.local_scratch.path, "export_galileo_stackup.csv") + assert export_method(export_stackup_path) + assert os.path.exists(export_stackup_path) + + edbapp.close() + + def test_stackup_properties_2(self): + """Evaluate various stackup properties.""" + edbapp = EdbLegacy(edbversion=desktop_version) + import_method = edbapp.stackup.import_stackup + export_method = edbapp.stackup.export_stackup + + assert import_method(os.path.join(local_path, "example_models", test_subfolder, "ansys_pcb_stackup.xml")) + assert "17_Bottom" in edbapp.stackup.layers.keys() + xml_export = os.path.join(self.local_scratch.path, "stackup.xml") + assert export_method(xml_export) + assert os.path.exists(xml_export) + assert import_method(os.path.join(local_path, "example_models", test_subfolder, "ansys_pcb_stackup.csv")) + assert "18_Bottom" in edbapp.stackup.layers.keys() + assert edbapp.stackup.add_layer("19_Bottom", None, "add_on_top", material="iron") + export_stackup_path = os.path.join(self.local_scratch.path, "export_galileo_stackup.csv") + assert export_method(export_stackup_path) + assert os.path.exists(export_stackup_path) + edbapp.close() + + def test_stackup_layer_properties(self): + """Evaluate various layer properties.""" + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0126.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, edbversion=desktop_version) + edbapp.stackup.load(os.path.join(local_path, "example_models", test_subfolder, "ansys_pcb_stackup.xml")) + layer = edbapp.stackup["1_Top"] + layer.name = "TOP" + assert layer.name == "TOP" + layer.type = "dielectric" + assert layer.type == "dielectric" + layer.type = "signal" + layer.color = (0, 0, 0) + assert layer.color == (0, 0, 0) + layer.transparency = 0 + assert layer.transparency == 0 + layer.etch_factor = 2 + assert layer.etch_factor == 2 + layer.thickness = 50e-6 + assert layer.thickness == 50e-6 + assert layer.lower_elevation + assert layer.upper_elevation + layer.is_negative = True + assert layer.is_negative + assert not layer.is_via_layer + assert layer.material == "copper" + edbapp.close() + + def test_stackup_load(self): + """Import stackup from a file.""" + import json + fpath = os.path.join(local_path, "example_models", test_subfolder, "stackup.json") + stackup_json = json.load(open(fpath, "r")) + + edbapp = EdbLegacy(edbversion=desktop_version) + edbapp.stackup.load(fpath) + edbapp.close() + + edbapp = EdbLegacy(edbversion=desktop_version) + edbapp.stackup.load(stackup_json) + edbapp.close() + + def test_stackup_place_in_3d_with_flipped_stackup(self): + """Place into another cell using 3d placement method with and + without flipping the current layer stackup. + """ + edb_path = os.path.join(self.target_path2, "edb.def") + edb1 = EdbLegacy(edb_path, edbversion=desktop_version) + + edb2 = EdbLegacy(self.target_path, edbversion=desktop_version) + assert edb2.stackup.place_in_layout_3d_placement( + edb1, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=False, + place_on_top=False, + solder_height=0.0, + ) + edb2.close() + edb2 = EdbLegacy(self.target_path, edbversion=desktop_version) + assert edb2.stackup.place_in_layout_3d_placement( + edb1, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=True, + place_on_top=False, + solder_height=0.0, + ) + edb2.close() + edb2 = EdbLegacy(self.target_path, edbversion=desktop_version) + assert edb2.stackup.place_in_layout_3d_placement( + edb1, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=False, + place_on_top=True, + solder_height=0.0, + ) + edb2.close() + edb2 = EdbLegacy(self.target_path, edbversion=desktop_version) + assert edb2.stackup.place_in_layout_3d_placement( + edb1, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=True, + place_on_top=True, + solder_height=0.0, + ) + edb2.close() + edb1.close() + + def test_stackup_place_instance_with_flipped_stackup(self): + """Place into another cell using 3d placement method with and + without flipping the current layer stackup. + """ + edb_path = os.path.join(self.target_path2, "edb.def") + edb1 = EdbLegacy(edb_path, edbversion=desktop_version) + + edb2 = EdbLegacy(self.target_path, edbversion=desktop_version) + assert edb1.stackup.place_instance( + edb2, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=False, + place_on_top=False, + solder_height=0.0, + ) + assert edb1.stackup.place_instance( + edb2, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=True, + place_on_top=False, + solder_height=0.0, + ) + assert edb1.stackup.place_instance( + edb2, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=False, + place_on_top=True, + solder_height=0.0, + ) + assert edb1.stackup.place_instance( + edb2, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=True, + place_on_top=True, + solder_height=0.0, + ) + edb2.close() + edb1.close() + + def test_stackup_place_in_layout_with_flipped_stackup(self): + """Place into another cell using layer placement method with and + without flipping the current layer stackup. + """ + edb2 = EdbLegacy(self.target_path, edbversion=desktop_version) + assert edb2.stackup.place_in_layout( + self.edbapp, + angle=0.0, + offset_x="41.783mm", + offset_y="35.179mm", + flipped_stackup=True, + place_on_top=True, + ) + edb2.close() + + def test_stackup_place_on_top_of_lam_with_mold(self): + """Place on top lam with mold using 3d placement method""" + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip.aedb"), + edbversion=desktop_version, + ) + try: + cellInstances = laminateEdb.layout.cell_instances + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=False, + place_on_top=True, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + originPoint = chipEdb.point_3d(0.0, 0.0, 0.0) + xAxisPoint = chipEdb.point_3d(1.0, 0.0, 0.0) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(zeroValue) + assert loc.IsEqual(chipEdb.point_3d(0.0, 0.0, chipEdb.edb_value(170e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_bottom_of_lam_with_mold(self): + """Place on lam with mold using 3d placement method""" + + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_flipped_stackup.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=False, + place_on_top=False, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + originPoint = chipEdb.point_3d(0.0, 0.0, 0.0) + xAxisPoint = chipEdb.point_3d(1.0, 0.0, 0.0) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(zeroValue) + assert loc.IsEqual(chipEdb.point_3d(0.0, 0.0, chipEdb.edb_value(-90e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_top_of_lam_with_mold_solder(self): + """Place on top of lam with mold solder using 3d placement method.""" + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_solder.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=False, + place_on_top=True, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + oneValue = chipEdb.edb_value(1) + originPoint = chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, zeroValue) + xAxisPoint = chipEdb.edb_api.geometry.point3d_data(oneValue, zeroValue, zeroValue) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(zeroValue) + assert loc.IsEqual(chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, chipEdb.edb_value(190e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_bottom_of_lam_with_mold_solder(self): + """Place on bottom of lam with mold solder using 3d placement method.""" + + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_solder.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=True, + place_on_top=False, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + oneValue = chipEdb.edb_value(1) + originPoint = chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, zeroValue) + xAxisPoint = chipEdb.edb_api.geometry.point3d_data(oneValue, zeroValue, zeroValue) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(chipEdb.edb_value(math.pi)) + assert loc.IsEqual(chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, chipEdb.edb_value(-20e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_top_with_zoffset_chip(self): + """Place on top of lam with mold chip zoffset using 3d placement method.""" + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_zoffset.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=False, + place_on_top=True, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + oneValue = chipEdb.edb_value(1) + originPoint = chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, zeroValue) + xAxisPoint = chipEdb.edb_api.geometry.point3d_data(oneValue, zeroValue, zeroValue) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(zeroValue) + assert loc.IsEqual(chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, chipEdb.edb_value(160e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_bottom_with_zoffset_chip(self): + """Place on bottom of lam with mold chip zoffset using 3d placement method.""" + + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_zoffset.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=True, + place_on_top=False, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + oneValue = chipEdb.edb_value(1) + originPoint = chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, zeroValue) + xAxisPoint = chipEdb.edb_api.geometry.point3d_data(oneValue, zeroValue, zeroValue) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(chipEdb.edb_value(math.pi)) + assert loc.IsEqual(chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, chipEdb.edb_value(10e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_top_with_zoffset_solder_chip(self): + """Place on top of lam with mold chip zoffset using 3d placement method.""" + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_zoffset_solder.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=False, + place_on_top=True, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + oneValue = chipEdb.edb_value(1) + originPoint = chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, zeroValue) + xAxisPoint = chipEdb.edb_api.geometry.point3d_data(oneValue, zeroValue, zeroValue) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(zeroValue) + assert loc.IsEqual(chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, chipEdb.edb_value(150e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_stackup_place_on_bottom_with_zoffset_solder_chip(self): + """Place on bottom of lam with mold chip zoffset using 3d placement method.""" + + laminateEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "lam_with_mold.aedb"), + edbversion=desktop_version, + ) + chipEdb = EdbLegacy( + os.path.join(local_path, "example_models", test_subfolder, "chip_zoffset_solder.aedb"), + edbversion=desktop_version, + ) + try: + layout = laminateEdb.active_layout + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 0 + assert chipEdb.stackup.place_in_layout_3d_placement( + laminateEdb, + angle=0.0, + offset_x=0.0, + offset_y=0.0, + flipped_stackup=True, + place_on_top=False, + ) + merged_cell = chipEdb.edb_api.cell.cell.FindByName( + chipEdb.active_db, chipEdb.edb_api.cell.CellType.CircuitCell, "lam_with_mold" + ) + assert not merged_cell.IsNull() + layout = merged_cell.GetLayout() + cellInstances = list(layout.CellInstances) + assert len(cellInstances) == 1 + cellInstance = cellInstances[0] + assert cellInstance.Is3DPlacement() + if desktop_version > "2023.1": + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + _, + ) = cellInstance.Get3DTransformation() + else: + ( + res, + localOrigin, + rotAxisFrom, + rotAxisTo, + angle, + loc, + ) = cellInstance.Get3DTransformation() + assert res + zeroValue = chipEdb.edb_value(0) + oneValue = chipEdb.edb_value(1) + originPoint = chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, zeroValue) + xAxisPoint = chipEdb.edb_api.geometry.point3d_data(oneValue, zeroValue, zeroValue) + assert localOrigin.IsEqual(originPoint) + assert rotAxisFrom.IsEqual(xAxisPoint) + assert rotAxisTo.IsEqual(xAxisPoint) + assert angle.IsEqual(chipEdb.edb_value(math.pi)) + assert loc.IsEqual(chipEdb.edb_api.geometry.point3d_data(zeroValue, zeroValue, chipEdb.edb_value(20e-6))) + finally: + chipEdb.close() + laminateEdb.close() + + def test_18_stackup(self): + def validate_material(pedb_materials, material, delta): + pedb_mat = pedb_materials[material["name"]] + if not material["dielectric_model_frequency"]: + assert (pedb_mat.conductivity - material["conductivity"]) < delta + assert (pedb_mat.permittivity - material["permittivity"]) < delta + assert (pedb_mat.loss_tangent - material["loss_tangent"]) < delta + assert (pedb_mat.permeability - material["permeability"]) < delta + assert (pedb_mat.magnetic_loss_tangent - material["magnetic_loss_tangent"]) < delta + assert (pedb_mat.mass_density - material["mass_density"]) < delta + assert (pedb_mat.poisson_ratio - material["poisson_ratio"]) < delta + assert (pedb_mat.specific_heat - material["specific_heat"]) < delta + assert (pedb_mat.thermal_conductivity - material["thermal_conductivity"]) < delta + assert (pedb_mat.youngs_modulus - material["youngs_modulus"]) < delta + assert (pedb_mat.thermal_expansion_coefficient - material["thermal_expansion_coefficient"]) < delta + if material["dc_conductivity"] is not None: + assert (pedb_mat.dc_conductivity - material["dc_conductivity"]) < delta + else: + assert pedb_mat.dc_conductivity == material["dc_conductivity"] + if material["dc_permittivity"] is not None: + assert (pedb_mat.dc_permittivity - material["dc_permittivity"]) < delta + else: + assert pedb_mat.dc_permittivity == material["dc_permittivity"] + if material["dielectric_model_frequency"] is not None: + assert (pedb_mat.dielectric_model_frequency - material["dielectric_model_frequency"]) < delta + else: + assert pedb_mat.dielectric_model_frequency == material["dielectric_model_frequency"] + if material["loss_tangent_at_frequency"] is not None: + assert (pedb_mat.loss_tangent_at_frequency - material["loss_tangent_at_frequency"]) < delta + else: + assert pedb_mat.loss_tangent_at_frequency == material["loss_tangent_at_frequency"] + if material["permittivity_at_frequency"] is not None: + assert (pedb_mat.permittivity_at_frequency - material["permittivity_at_frequency"]) < delta + else: + assert pedb_mat.permittivity_at_frequency == material["permittivity_at_frequency"] + return 0 + import json + + target_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + out_edb = os.path.join(self.local_scratch.path, "ANSYS-HSD_V1_test.aedb") + self.local_scratch.copyfolder(target_path, out_edb) + json_path = os.path.join(local_path, "example_models", test_subfolder, "test_mat.json") + edbapp = EdbLegacy(out_edb, edbversion=desktop_version) + edbapp.stackup.import_stackup(json_path) + edbapp.save_edb() + delta = 1e-6 + f = open(json_path) + json_dict = json.load(f) + for k, v in json_dict.items(): + if k == "materials": + for material in v.values(): + assert 0 == validate_material(edbapp.materials, material, delta) + for k, v in json_dict.items(): + if k == "layers": + for layer_name, layer in v.items(): + pedb_lay = edbapp.stackup.layers[layer_name] + assert list(pedb_lay.color) == layer["color"] + assert pedb_lay.type == layer["type"] + if isinstance(layer["material"], str): + assert pedb_lay.material == layer["material"] + else: + assert 0 == validate_material(edbapp.materials, layer["material"], delta) + if isinstance(layer["dielectric_fill"], str) or layer["dielectric_fill"] is None: + assert pedb_lay.dielectric_fill == layer["dielectric_fill"] + else: + assert 0 == validate_material(edbapp.materials, layer["dielectric_fill"], delta) + assert (pedb_lay.thickness - layer["thickness"]) < delta + assert (pedb_lay.etch_factor - layer["etch_factor"]) < delta + assert pedb_lay.roughness_enabled == layer["roughness_enabled"] + if layer["roughness_enabled"]: + assert (pedb_lay.top_hallhuray_nodule_radius - layer["top_hallhuray_nodule_radius"]) < delta + assert (pedb_lay.top_hallhuray_surface_ratio - layer["top_hallhuray_surface_ratio"]) < delta + assert ( + pedb_lay.bottom_hallhuray_nodule_radius - layer["bottom_hallhuray_nodule_radius"] + ) < delta + assert ( + pedb_lay.bottom_hallhuray_surface_ratio - layer["bottom_hallhuray_surface_ratio"] + ) < delta + assert (pedb_lay.side_hallhuray_nodule_radius - layer["side_hallhuray_nodule_radius"]) < delta + assert (pedb_lay.side_hallhuray_surface_ratio - layer["side_hallhuray_surface_ratio"]) < delta + edbapp.close() + edbapp = EdbLegacy(edbversion=desktop_version) + json_path = os.path.join(local_path, "example_models", test_subfolder, "test_mat2.json") + assert edbapp.stackup.import_stackup(json_path) + assert "SOLDER" in edbapp.stackup.stackup_layers + edbapp.close() diff --git a/tests/grpc/unit/__init__.py b/tests/grpc/unit/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/grpc/unit/test_edb.py b/tests/grpc/unit/test_edb.py new file mode 100644 index 0000000000..34af88e9d0 --- /dev/null +++ b/tests/grpc/unit/test_edb.py @@ -0,0 +1,124 @@ +import os +import pytest + +from tests.conftest import desktop_version +from pyedb.grpc.edb import EdbGrpc + +pytestmark = [pytest.mark.unit, pytest.mark.grpc] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, local_scratch): + self.local_scratch = local_scratch + + def test_create_edb(self): + """Create EDB.""" + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + assert edb + assert edb.active_layout + edb.close() + + def test_create_edb_without_path(self): + """Create EDB without path.""" + import time + edbapp_without_path = EdbGrpc(edbversion=desktop_version, isreadonly=False) + time.sleep(2) + edbapp_without_path.close() + + def test_variables_value(self): + """Evaluate variables value.""" + from pyedb.generic.general_methods import check_numeric_equivalence + + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb["var1"] = 0.01 + edb["var2"] = "10um" + edb["var3"] = [0.03, "test description"] + edb["$var4"] = ["1mm", "Project variable."] + edb["$var5"] = 0.1 + assert edb["var1"].value == 0.01 + assert check_numeric_equivalence(edb["var2"].value, 1.0e-5) + assert edb["var3"].value == 0.03 + assert edb["var3"].description == "test description" + assert edb["$var4"].value == 0.001 + assert edb["$var4"].description == "Project variable." + assert edb["$var5"].value == 0.1 + assert edb.project_variables["$var5"].delete() + + def test_add_design_variable(self): + """Add a variable value.""" + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + is_added, _ = edb.add_design_variable("ant_length", "1cm") + assert is_added + is_added, _ = edb.add_design_variable("ant_length", "1cm") + assert not is_added + is_added, _ = edb.add_design_variable("my_parameter_default", "1mm", is_parameter=True) + assert is_added + is_added, _ = edb.add_design_variable("my_parameter_default", "1mm", is_parameter=True) + assert not is_added + is_added, _ = edb.add_design_variable("$my_project_variable", "1mm") + assert is_added + is_added, _ = edb.add_design_variable("$my_project_variable", "1mm") + assert not is_added + + def test_add_design_variable_with_setitem(self): + """Add a variable value.""" + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb["ant_length"] = "1cm" + assert edb.variable_exists("ant_length")[0] + assert edb["ant_length"].value == 0.01 + + def test_change_design_variable_value(self): + """Change a variable value.""" + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb.add_design_variable("ant_length", "1cm") + edb.add_design_variable("my_parameter_default", "1mm", is_parameter=True) + edb.add_design_variable("$my_project_variable", "1mm") + + is_changed, _ = edb.change_design_variable_value("ant_length", "1m") + assert is_changed + is_changed, _ = edb.change_design_variable_value("elephant_length", "1m") + assert not is_changed + is_changed, _ = edb.change_design_variable_value("my_parameter_default", "1m") + assert is_changed + is_changed, _ = edb.change_design_variable_value("$my_project_variable", "1m") + assert is_changed + is_changed, _ = edb.change_design_variable_value("$my_parameter", "1m") + assert not is_changed + + def test_change_design_variable_value_with_setitem(self): + """Change a variable value.""" + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb["ant_length"] = "1cm" + assert edb["ant_length"].value == 0.01 + edb["ant_length"] = "2cm" + assert edb["ant_length"].value == 0.02 + + def test_create_padstack_instance(self): + """Create padstack instances.""" + edb = EdbGrpc(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + + pad_name = edb.padstacks.create( + pad_shape="Rectangle", + padstackname="pad", + x_size="350um", + y_size="500um", + holediam=0, + ) + assert pad_name == "pad" + + pad_name = edb.padstacks.create(pad_shape="Circle", padstackname="pad2", paddiam="350um", holediam="15um") + assert pad_name == "pad2" + + pad_name = edb.padstacks.create( + pad_shape="Circle", + padstackname="test2", + paddiam="400um", + holediam="200um", + antipad_shape="Rectangle", + anti_pad_x_size="700um", + anti_pad_y_size="800um", + start_layer="1_Top", + stop_layer="1_Top", + ) + pad_name == "test2" + edb.close() diff --git a/tests/grpc/unit/test_edbsiwave.py b/tests/grpc/unit/test_edbsiwave.py new file mode 100644 index 0000000000..8925aa3421 --- /dev/null +++ b/tests/grpc/unit/test_edbsiwave.py @@ -0,0 +1,17 @@ +import os +import pytest +from mock import Mock +from pyedb.grpc.siwave import EdbSiwave + +pytestmark = [pytest.mark.unit, pytest.mark.no_licence, pytest.mark.grpc] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self): + self.edb = Mock() + self.edb.edbpath = os.path.join(os.path.expanduser("~"), "fake_edb.aedb") + self.siwave = EdbSiwave(self.edb) + + def test_siwave_add_syz_analsyis(self): + """Add a sywave AC analysis.""" + assert self.siwave.add_siwave_syz_analysis() diff --git a/tests/grpc/unit/test_materials.py b/tests/grpc/unit/test_materials.py new file mode 100644 index 0000000000..2e56888d1f --- /dev/null +++ b/tests/grpc/unit/test_materials.py @@ -0,0 +1,79 @@ +import builtins +from unittest.mock import mock_open +import pytest +from mock import patch, MagicMock +from pyedb.grpc.materials import Materials + +pytestmark = [pytest.mark.unit, pytest.mark.no_licence, pytest.mark.legacy] + +MATERIALS = """ +$begin 'Polyflon CuFlon (tm)' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=230 + Green=225 + Blue=220 + $end 'MatAppearanceData' + $end 'AttachedData' + simple('permittivity', 2.1) + simple('dielectric_loss_tangent', 0.00045) + ModTime=1499970477 +$end 'Polyflon CuFlon (tm)' +$begin 'Water(@360K)' + $begin 'MaterialDef' + $begin 'Water(@360K)' + CoordinateSystemType='Cartesian' + BulkOrSurfaceType=1 + $begin 'PhysicsTypes' + set('Thermal') + $end 'PhysicsTypes' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=0 + Green=128 + Blue=255 + Transparency=0.8 + $end 'MatAppearanceData' + $end 'AttachedData' + thermal_conductivity='0.6743' + mass_density='967.4' + specific_heat='4206' + thermal_expansion_coeffcient='0.0006979' + $begin 'thermal_material_type' + property_type='ChoiceProperty' + Choice='Fluid' + $end 'thermal_material_type' + $begin 'clarity_type' + property_type='ChoiceProperty' + Choice='Transparent' + $end 'clarity_type' + material_refractive_index='1.333' + diffusivity='1.657e-007' + molecular_mass='0.018015' + viscosity='0.000324' + ModTime=1592011950 + $end 'Water(@360K)' + $end 'MaterialDef' +$end 'Water(@360K)' +""" + +class TestClass: + @pytest.fixture(autouse=True) + def init(self): + self.materials = Materials(MagicMock(materials=["copper"])) + + @patch.object(builtins, "open", new_callable=mock_open, read_data=MATERIALS) + def test_materials_read_materials(self, mock_file_open): + """Read materials from an amat file.""" + expected_res = { + "Polyflon CuFlon (tm)": {"permittivity": 2.1, "tangent_delta": 0.00045}, + "Water(@360K)": {"thermal_conductivity": 0.6743, + "mass_density": 967.4, + "specific_heat": 4206, + "thermal_expansion_coeffcient": 0.0006979 + } + } + mats = self.materials.read_materials("some path") + assert mats == expected_res \ No newline at end of file diff --git a/tests/grpc/unit/test_padstack.py b/tests/grpc/unit/test_padstack.py new file mode 100644 index 0000000000..6c7d9e431b --- /dev/null +++ b/tests/grpc/unit/test_padstack.py @@ -0,0 +1,22 @@ +import pytest +from mock import PropertyMock, patch, MagicMock +from pyedb.grpc.padstack import EdbPadstacks + +pytestmark = [pytest.mark.unit, pytest.mark.no_licence, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self): + self.padstacks = EdbPadstacks(MagicMock()) + + @patch('pyedb.legacy.edb_core.padstack.EdbPadstacks.definitions', new_callable=PropertyMock) + def test_padstack_plating_ratio_fixing(self, mock_definitions): + """Fix hole plating ratio.""" + mock_definitions.return_value = { + "definition_0": MagicMock(hole_plating_ratio = -0.1), + "definition_1": MagicMock(hole_plating_ratio = 0.3) + } + assert self.padstacks["definition_0"].hole_plating_ratio == -0.1 + self.padstacks.check_and_fix_via_plating() + assert self.padstacks["definition_0"].hole_plating_ratio == 0.2 + assert self.padstacks["definition_1"].hole_plating_ratio == 0.3 diff --git a/tests/grpc/unit/test_simulation_configuration.py b/tests/grpc/unit/test_simulation_configuration.py new file mode 100644 index 0000000000..02586fbdf8 --- /dev/null +++ b/tests/grpc/unit/test_simulation_configuration.py @@ -0,0 +1,46 @@ +import os +from pyedb.generic.constants import SourceType +from pyedb.legacy.edb_core.edb_data.simulation_configuration import SimulationConfiguration +import pytest + +pytestmark = [pytest.mark.unit, pytest.mark.no_licence, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, local_scratch,): + self.local_scratch = local_scratch + + def test_simulation_configuration_export_import(self): + """Export and import simulation file.""" + sim_config = SimulationConfiguration() + assert sim_config.output_aedb is None + sim_config.output_aedb = os.path.join(self.local_scratch.path, "test.aedb") + assert sim_config.output_aedb == os.path.join(self.local_scratch.path, "test.aedb") + json_file = os.path.join(self.local_scratch.path, "test.json") + sim_config._filename = json_file + sim_config.arc_angle = "90deg" + assert sim_config.export_json(json_file) + + test_0import = SimulationConfiguration() + assert test_0import.import_json(json_file) + assert test_0import.arc_angle == "90deg" + assert test_0import._filename == json_file + + def test_simulation_configuration_add_rlc(self): + """Add voltage source.""" + sim_config = SimulationConfiguration() + sim_config.add_rlc( + "test", + r_value=1.5, + c_value=1e-13, + l_value=1e-10, + positive_node_net="test_0net", + positive_node_component="U2", + negative_node_net="neg_net", + negative_node_component="U2", + ) + assert sim_config.sources + assert sim_config.sources[0].source_type == SourceType.Rlc + assert sim_config.sources[0].r_value == 1.5 + assert sim_config.sources[0].l_value == 1e-10 + assert sim_config.sources[0].c_value == 1e-13 diff --git a/tests/grpc/unit/test_source.py b/tests/grpc/unit/test_source.py new file mode 100644 index 0000000000..611e18d496 --- /dev/null +++ b/tests/grpc/unit/test_source.py @@ -0,0 +1,23 @@ +import os +from pyedb.legacy.edb_core.edb_data.simulation_configuration import SimulationConfiguration +from pyedb.legacy.edb_core.edb_data.sources import Source +import pytest + +pytestmark = [pytest.mark.unit, pytest.mark.no_licence, pytest.mark.legacy] + +class TestClass: + # @pytest.fixture(autouse=True) + # def init(self,local_scratch,): + # self.local_scratch = local_scratch + + def test_source_change_values(self): + """Create source and change its values """ + source = Source() + source.l_value = 1e-9 + assert source.l_value == 1e-9 + source.r_value = 1.3 + assert source.r_value == 1.3 + source.c_value = 1e-13 + assert source.c_value == 1e-13 + source.create_physical_resistor = True + assert source.create_physical_resistor diff --git a/tests/grpc/unit/test_stackup.py b/tests/grpc/unit/test_stackup.py new file mode 100644 index 0000000000..eb01a591fb --- /dev/null +++ b/tests/grpc/unit/test_stackup.py @@ -0,0 +1,89 @@ +import pytest +from mock import PropertyMock, patch, MagicMock +from pyedb.legacy.edb_core.stackup import Stackup + +pytestmark = [pytest.mark.unit, pytest.mark.no_licence, pytest.mark.legacy] + +class TestClass: + @pytest.fixture(autouse=True) + def init(self): + self.stackup = Stackup(MagicMock()) + + def test_stackup_int_to_layer_types(self): + """Evaluate mapping from integer to layer type.""" + signal_layer = self.stackup._int_to_layer_types(0) + assert signal_layer == self.stackup.layer_types.SignalLayer + dielectric_layer = self.stackup._int_to_layer_types(1) + assert dielectric_layer == self.stackup.layer_types.DielectricLayer + conducting_layer = self.stackup._int_to_layer_types(2) + assert conducting_layer == self.stackup.layer_types.ConductingLayer + airlines_layer = self.stackup._int_to_layer_types(3) + assert airlines_layer == self.stackup.layer_types.AirlinesLayer + errors_layer = self.stackup._int_to_layer_types(4) + assert errors_layer == self.stackup.layer_types.ErrorsLayer + symbol_layer = self.stackup._int_to_layer_types(5) + assert symbol_layer == self.stackup.layer_types.SymbolLayer + measure_layer = self.stackup._int_to_layer_types(6) + assert measure_layer == self.stackup.layer_types.MeasureLayer + assembly_layer = self.stackup._int_to_layer_types(8) + assert assembly_layer == self.stackup.layer_types.AssemblyLayer + silkscreen_layer = self.stackup._int_to_layer_types(9) + assert silkscreen_layer == self.stackup.layer_types.SilkscreenLayer + solder_mask_layer = self.stackup._int_to_layer_types(10) + assert solder_mask_layer == self.stackup.layer_types.SolderMaskLayer + solder_paste_layer = self.stackup._int_to_layer_types(11) + assert solder_paste_layer == self.stackup.layer_types.SolderPasteLayer + glue_layer = self.stackup._int_to_layer_types(12) + assert glue_layer == self.stackup.layer_types.GlueLayer + wirebond_layer = self.stackup._int_to_layer_types(13) + assert wirebond_layer == self.stackup.layer_types.WirebondLayer + user_layer = self.stackup._int_to_layer_types(14) + assert user_layer == self.stackup.layer_types.UserLayer + siwave_hfss_solver_regions = self.stackup._int_to_layer_types(16) + assert siwave_hfss_solver_regions == self.stackup.layer_types.SIwaveHFSSSolverRegions + outline_layer = self.stackup._int_to_layer_types(18) + assert outline_layer == self.stackup.layer_types.OutlineLayer + + def test_stackup_layer_types_to_int(self): + """Evaluate mapping from layer type to int.""" + signal_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.SignalLayer) + assert signal_layer == 0 + dielectric_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.DielectricLayer) + assert dielectric_layer == 1 + conducting_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.ConductingLayer) + assert conducting_layer == 2 + airlines_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.AirlinesLayer) + assert airlines_layer == 3 + errors_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.ErrorsLayer) + assert errors_layer == 4 + symbol_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.SymbolLayer) + assert symbol_layer == 5 + measure_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.MeasureLayer) + assert measure_layer == 6 + assembly_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.AssemblyLayer) + assert assembly_layer == 8 + silkscreen_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.SilkscreenLayer) + assert silkscreen_layer == 9 + solder_mask_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.SolderMaskLayer) + assert solder_mask_layer == 10 + solder_paste_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.SolderPasteLayer) + assert solder_paste_layer == 11 + glue_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.GlueLayer) + assert glue_layer == 12 + wirebond_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.WirebondLayer) + assert wirebond_layer == 13 + user_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.UserLayer) + assert user_layer == 14 + siwave_hfss_solver_regions = self.stackup._layer_types_to_int(self.stackup.layer_types.SIwaveHFSSSolverRegions) + assert siwave_hfss_solver_regions == 16 + outline_layer = self.stackup._layer_types_to_int(self.stackup.layer_types.OutlineLayer) + assert outline_layer == 18 + + @patch('pyedb.legacy.edb_core.stackup.Stackup.stackup_layers', new_callable=PropertyMock) + def test_110_layout_tchickness(self, mock_stackup_layers): + """""" + mock_stackup_layers.return_value = {"layer": MagicMock(upper_elevation = 42, lower_elevation = 0)} + assert self.stackup.get_layout_thickness() == 42 + mock_stackup_layers.return_value = {"layer": MagicMock(upper_elevation = 0, lower_elevation = 0)} + assert self.stackup.get_layout_thickness() == 0 +