diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index 90294d8529a..8372c74823b 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -4,7 +4,7 @@ from pyaedt import Edb from pyaedt.edb_core.components import resistor_value_parser -from pyaedt.generic.constants import SourceType +from pyaedt.edb_core.EDB_Data import SimulationConfiguration # Setup paths for module imports # Import required modules @@ -32,7 +32,9 @@ def setup_class(self): self.local_scratch.copyfolder(example_project2, self.target_path2) def teardown_class(self): - BasisTest.my_teardown(self) + self.edbapp.close_edb() + self.local_scratch.remove() + del self.edbapp def test_00_export_ipc2581(self): ipc_path = os.path.join(self.local_scratch.path, "test.xml") @@ -65,35 +67,12 @@ def test_01_find_by_name(self): assert isinstance(parameters[1], list) assert isinstance(parameters[0], int) - def test_01A_create_ports(self): - pins = self.edbapp.core_components.get_pin_from_component("J1") - nets = self.edbapp.core_components.get_nets_from_pin_list(pins) - assert len(nets) == 6 - assert "IO13_ICSP_R" in nets - assert "V5_ALW_ON" in nets - assert "GND" in nets - assert "IO11_ICSP_R" in nets - assert "RESET_N_SHLD" in nets - assert "IO12_ICSP_R" in nets - - assert self.edbapp.core_components.create_port_on_component( - component="J1", net_list=nets[:2], port_type=SourceType.CoaxPort, do_pingroup=True, reference_net="GND" - ) - assert self.edbapp.core_components.create_port_on_component( - component="J1", net_list=nets[3], port_type=SourceType.CoaxPort, do_pingroup=False, reference_net="GND" - ) - assert self.edbapp.core_components.create_port_on_component( - component="J1", net_list=nets[-2:], port_type=SourceType.CircPort, do_pingroup=False, reference_net="GND" - ) - def test_01B_get_vias_from_nets(self): assert self.edbapp.core_padstack.get_via_instance_from_net("GND") assert not self.edbapp.core_padstack.get_via_instance_from_net(["GND2"]) - def test_01C_create_lumped_port_at_location(self): - assert self.edbapp.core_hfss.create_lumped_port_on_trace( - nets="M_DQ<11>", reference_layer="GND", point_list=[(17.169892e-3, 38.874954e-3)] - ) + def test_01C_create_coax_port_on_component(self): + assert self.edbapp.core_hfss.create_coax_port_on_component("U1A1", "M_DQS_N<1>") def test_02_get_properties(self): assert len(self.edbapp.core_components.components) > 0 @@ -625,9 +604,8 @@ def test_67_add_void(self): def test_69_create_solder_balls_on_component(self): assert self.edbapp.core_components.set_solder_ball("U2A5") - assert self.edbapp.core_components.set_solder_ball("U2A5", "100um", "150um") - @pytest.mark.skipif(is_ironpython, reason="This Test uses Matplotlib that is not supported by Ironpython") + @pytest.mark.skipif(is_ironpython, reason="This test uses Matplotlib, which is not supported by IronPython.") def test_70_plot_on_matplotlib(self): local_png = os.path.join(self.local_scratch.path, "test.png") self.edbapp.core_nets.plot(None, None, save_plot=local_png) @@ -760,124 +738,6 @@ def test_79_get_placement_vector(self): edb2.close_edb() del edb2 - def test_79b_get_placement_vector(self): - laminate_edb = Edb( - os.path.join(local_path, "example_models", "lam_for_bottom_place.aedb"), edbversion=desktop_version - ) - chip_edb = Edb(os.path.join(local_path, "example_models", "chip.aedb"), edbversion=desktop_version) - try: - laminate_cmp = laminate_edb.core_components.get_component_by_name("U3") - chip_cmp = chip_edb.core_components.get_component_by_name("U1") - result, vector, rotation, solder_ball_height = laminate_edb.core_components.get_component_placement_vector( - chip_cmp, laminate_cmp, "1", "2", "1", "2", True - ) - assert result - assert abs(rotation - math.pi / 2) < 1e-9 - assert solder_ball_height == 0 - assert abs(vector[0] - 0.5e-3) < 1e-9 - assert abs(vector[1] + 0.5e-3) < 1e-9 - finally: - chip_edb.close_edb() - laminate_edb.close_edb() - - def test_79c_get_placement_vector(self): - laminate_edb = Edb( - os.path.join(local_path, "example_models", "lam_for_bottom_place.aedb"), edbversion=desktop_version - ) - chip_edb = Edb(os.path.join(local_path, "example_models", "chip.aedb"), edbversion=desktop_version) - try: - laminate_cmp = laminate_edb.core_components.get_component_by_name("U1") - chip_cmp = chip_edb.core_components.get_component_by_name("U1") - result, vector, rotation, solder_ball_height = laminate_edb.core_components.get_component_placement_vector( - chip_cmp, laminate_cmp, "1", "2", "1", "2" - ) - assert result - assert abs(rotation) < 1e-9 - assert solder_ball_height == 0 - assert abs(vector[0]) < 1e-9 - assert abs(vector[1]) < 1e-9 - finally: - chip_edb.close_edb() - laminate_edb.close_edb() - - def test_79d_get_placement_vector_offset(self): - laminate_edb = Edb( - os.path.join(local_path, "example_models", "lam_for_bottom_place.aedb"), edbversion=desktop_version - ) - chip_edb = Edb(os.path.join(local_path, "example_models", "chip_offset.aedb"), edbversion=desktop_version) - try: - laminate_cmp = laminate_edb.core_components.get_component_by_name("U3") - chip_cmp = chip_edb.core_components.get_component_by_name("U1") - result, vector, rotation, solder_ball_height = laminate_edb.core_components.get_component_placement_vector( - chip_cmp, laminate_cmp, "1", "4", "1", "4", True - ) - assert result - assert abs(rotation - math.pi / 2) < 1e-9 - assert solder_ball_height == 0 - assert abs(vector[0] - 0.2e-3) < 1e-9 - assert abs(vector[1] + 0.8e-3) < 1e-9 - finally: - chip_edb.close_edb() - laminate_edb.close_edb() - - def test_79e_get_placement_vector_offset(self): - laminate_edb = Edb( - os.path.join(local_path, "example_models", "lam_for_bottom_place.aedb"), edbversion=desktop_version - ) - chip_edb = Edb(os.path.join(local_path, "example_models", "chip_offset.aedb"), edbversion=desktop_version) - try: - laminate_cmp = laminate_edb.core_components.get_component_by_name("U1") - chip_cmp = chip_edb.core_components.get_component_by_name("U1") - result, vector, rotation, solder_ball_height = laminate_edb.core_components.get_component_placement_vector( - chip_cmp, laminate_cmp, "1", "4", "1", "4" - ) - assert result - assert abs(rotation) < 1e-9 - assert solder_ball_height == 0 - assert abs(vector[0] - 0.3e-3) < 1e-9 - assert abs(vector[1] - 0.3e-3) < 1e-9 - finally: - chip_edb.close_edb() - laminate_edb.close_edb() - - def test_79f_get_placement_vector_offset(self): - laminate_edb = Edb( - os.path.join(local_path, "example_models", "lam_for_top_place.aedb"), edbversion=desktop_version - ) - chip_edb = Edb(os.path.join(local_path, "example_models", "chip_offset.aedb"), edbversion=desktop_version) - try: - laminate_cmp = laminate_edb.core_components.get_component_by_name("U3") - chip_cmp = chip_edb.core_components.get_component_by_name("U1") - result, vector, rotation, solder_ball_height = laminate_edb.core_components.get_component_placement_vector( - chip_cmp, laminate_cmp, "1", "4", "1", "4", False - ) - assert result - assert abs(rotation - math.pi / 2) < 1e-9 - assert solder_ball_height == 0 - assert abs(vector[0] - 0.8e-3) < 1e-9 - assert abs(vector[1] + 0.8e-3) < 1e-9 - finally: - chip_edb.close_edb() - laminate_edb.close_edb() - - def test_79g_get_placement_vector(self): - board_edb = Edb(os.path.join(local_path, "example_models", "invert_board.aedb"), edbversion=desktop_version) - package_edb = Edb(os.path.join(local_path, "example_models", "package2.aedb"), edbversion=desktop_version) - try: - laminate_cmp = board_edb.core_components.get_component_by_name("U100") - chip_cmp = package_edb.core_components.get_component_by_name("BGA") - result, vector, rotation, solder_ball_height = board_edb.core_components.get_component_placement_vector( - chip_cmp, laminate_cmp, "A12", "A14", "A12", "A14", True - ) - assert result - assert abs(rotation) < 1e-9 - assert abs(solder_ball_height - 315e-6) < 1e-9 - assert abs(vector[0] + 48.7e-3) < 10e-9 - assert abs(vector[1] - 59.7e-3) < 10e-9 - finally: - package_edb.close_edb() - board_edb.close_edb() - def test_80_edb_without_path(self): edbapp_without_path = Edb(edbversion=desktop_version, isreadonly=False) time.sleep(2) @@ -894,22 +754,12 @@ def test_80_create_rectangle_in_pad(self): edb_padstacks = Edb( edbpath=os.path.join(self.local_scratch.path, "padstacks2.aedb"), edbversion=desktop_version, - isreadonly=False, + isreadonly=True, ) for i in range(7): padstack_instance = list(edb_padstacks.core_padstack.padstack_instances.values())[i] result = padstack_instance.create_rectangle_in_pad("s") assert result - points = padstack_instance.create_rectangle_in_pad("s", return_points=True) - assert len(points) == 4 - assert len(points[0]) == 2 - # for i in range(8, 10): - # padstack_instance = list(edb_padstacks.core_padstack.padstack_instances.values())[i] - # result = padstack_instance.create_rectangle_in_pad("g") - # assert result - # points = padstack_instance.create_rectangle_in_pad("g", return_points=True) - # assert len(points) == 4 - # assert len(points[0]) == 2 edb_padstacks.close_edb() def test_81_edb_with_dxf(self): @@ -1077,7 +927,27 @@ def test_82c_place_on_bottom_of_lam_with_mold_solder(self): chipEdb.close_edb() laminateEdb.close_edb() - def test_83_set_component_type(self): + def test_83_build_siwave_project_from_config_file(self): + cfg_file = os.path.join(os.path.dirname(self.edbapp.edbpath), "test.cfg") + with open(cfg_file, "w") as f: + f.writelines("SolverType = 'Siwave'\n") + f.writelines("PowerNets = ['GND']\n") + f.writelines("Components = ['U2A5', 'U1B5']") + + sim_config = SimulationConfiguration(cfg_file) + assert self.edbapp.build_simulation_project(sim_config) + + def test_84_build_hfss_project_from_config_file(self): + cfg_file = os.path.join(os.path.dirname(self.edbapp.edbpath), "test.cfg") + with open(cfg_file, "w") as f: + f.writelines("SolverType = 'Hfss3dLayout'\n") + f.writelines("PowerNets = ['GND']\n") + f.writelines("Components = ['U2A5', 'U1B5']") + + sim_config = SimulationConfiguration(cfg_file) + assert self.edbapp.build_simulation_project(sim_config) + + def test_85_set_component_type(self): comp = self.edbapp.core_components.components["R2L18"] comp.type = "Resistor" assert comp.type == "Resistor" @@ -1092,10 +962,6 @@ def test_83_set_component_type(self): comp.type = "Other" assert comp.type == "Other" - def test_84_create_coax_port_on_component_pin(self): - assert self.edbapp.core_hfss.create_coax_port_on_component_per_pin("U1A1", "D1", "port1") - assert self.edbapp.core_hfss.create_coax_port_on_component_per_net("U1A1", "M_DQS_N<1>", "port2") - def test_85_deactivate_rlc(self): assert self.edbapp.core_components.deactivate_rlc_component(component="C1", create_circuit_port=True) assert self.edbapp.core_components.deactivate_rlc_component(component="C2", create_circuit_port=False) diff --git a/pyaedt/edb.py b/pyaedt/edb.py index 15a69bf98cf..9c3dad5ecbb 100644 --- a/pyaedt/edb.py +++ b/pyaedt/edb.py @@ -21,10 +21,11 @@ from System.Collections.Generic import List except ImportError: # pragma: no cover if os.name != "posix": - warnings.warn("Pythonnet is needed to run PyAEDT.") + warnings.warn("Python.NET is needed to run PyAEDT.") from pyaedt import settings from pyaedt.edb_core import Components, EdbNets, EdbPadstacks, EdbLayout, EdbHfss, EdbSiwave, EdbStackup -from pyaedt.edb_core.EDB_Data import EdbBuilder +from pyaedt.edb_core.EDB_Data import EdbBuilder, SimulationConfiguration +from pyaedt.generic.constants import CutoutSubdesignType, SolverType, SourceType from pyaedt.generic.general_methods import ( pyaedt_function_handler, env_path, @@ -906,13 +907,14 @@ def import_gds_file(self, inputGDS, WorkDir=None, anstranslator_full_path="", us def create_cutout( self, - signal_list, + 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, + simulation_setup=None, ): """Create a cutout and save it to a new AEDB file. @@ -934,6 +936,8 @@ def create_cutout( open_cutout_at_end : bool, optional Whether to open the cutout at the end. The default is ``True``. + simulation_setup : EDB_Data.SimulationConfiguration object, optional + Simulation setup to use to overwrite the other parameters. The default is ``None``. Returns ------- @@ -941,34 +945,38 @@ def create_cutout( ``True`` when successful, ``False`` when failed. """ - _signal_nets = [] - # validate nets in layout - for _sig in signal_list: - _netobj = self.edb.Cell.Net.FindByName(self.active_layout, _sig) - _signal_nets.append(_netobj) - _ref_nets = [] + if simulation_setup and isinstance(simulation_setup, SimulationConfiguration): + signal_list = simulation_setup.signal_nets + reference_list = simulation_setup.power_nets + if simulation_setup.cutout_subdesign_type == CutoutSubdesignType.Conformal: + extent_type = "Conforming" + expansion_size = float(simulation_setup.cutout_subdesign_expansion) + use_round_corner = bool(simulation_setup.cutout_subdesign_round_corner) + + # validate nets in layout + net_signals = convert_py_list_to_net_list( + [net for net in list(self.active_layout.Nets) if net.GetName() in signal_list] + ) # validate references in layout - for _ref in reference_list: - _netobj = self.edb.Cell.Net.FindByName(self.active_layout, _ref) - _ref_nets.append(_netobj) - - _netsClip = [ - self.edb.Cell.Net.FindByName(self.active_layout, reference_list[i]) for i, p in enumerate(reference_list) - ] - _netsClip = convert_py_list_to_net_list(_netsClip) - net_signals = convert_py_list_to_net_list(_signal_nets) + _netsClip = convert_py_list_to_net_list( + [net for net in list(self.active_layout.Nets) if net.GetName() in reference_list] + ) + if extent_type == "Conforming": _poly = self.active_layout.GetExpandedExtentFromNets( - net_signals, self.edb.Geometry.ExtentType.Conforming, expansion_size, False, use_round_corner, 1 + net_signals, self.edb.Geometry.ExtentType.Conforming, expansion_size, True, use_round_corner, 1 ) else: _poly = self.active_layout.GetExpandedExtentFromNets( - net_signals, self.edb.Geometry.ExtentType.BoundingBox, expansion_size, False, use_round_corner, 1 + net_signals, self.edb.Geometry.ExtentType.BoundingBox, expansion_size, True, use_round_corner, 1 ) # Create new cutout cell/design - _cutout = self.active_cell.CutOut(net_signals, _netsClip, _poly) + included_nets = convert_py_list_to_net_list( + [net for net in list(self.active_layout.Nets) if net.GetName() in signal_list + reference_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. @@ -1397,3 +1405,78 @@ def get_bounding_box(self): """ 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()]] + + @pyaedt_function_handler() + def build_simulation_project(self, simulation_setup=None): + """Build a ready-to-solve simulation project. + + Parameters + ---------- + simulation_setup : EDB_Data.SimulationConfiguratiom object. + SimulationConfiguration object that can be instantiated or directly loaded with a + configuration file. + + Returns + ------- + bool + ``True`` when successful, False when ``Failed``. + """ + self.logger.info("Building simulation project.") + try: + if not simulation_setup or not isinstance(simulation_setup, SimulationConfiguration): # pragma: no cover + return False + if simulation_setup.do_cutout_subdesign: + self.logger.info("Cutting out using method: {0}".format(simulation_setup.cutout_subdesign_type)) + old_cell_name = self.active_cell.GetName() + if self.create_cutout(simulation_setup=simulation_setup): + self.logger.info("Cutout processed.") + old_cell = self.active_cell.FindByName(self._db, 0, old_cell_name) + if old_cell: + old_cell.Delete() + else: # pragma: no cover + self.logger.error("Cutout failed.") + self.logger.info("Deleting existing ports.") + map(lambda port: port.Delete(), list(self.active_layout.Terminals)) + map(lambda pg: pg.Delete(), list(self.active_layout.PinGroups)) + self.logger.info("Creating ports for signal nets.") + if simulation_setup.solver_type == SolverType.Hfss3dLayout: + for cmp in simulation_setup.components: + self.core_components.create_port_on_component( + cmp, + net_list=simulation_setup.signal_nets, + do_pingroup=False, + reference_net=simulation_setup.power_nets, + port_type=SourceType.CoaxPort, + ) + if not self.core_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.core_hfss.get_ports_number())) + self.logger.info("Configure HFSS extents.") + if 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.core_hfss.trim_component_reference_size(simulation_setup) # pragma: no cover + self.core_hfss.configure_hfss_extents(simulation_setup) + if not self.core_hfss.configure_hfss_analysis_setup(simulation_setup): + self.logger.error("Failed to configure HFSS simulation setup.") + if simulation_setup.solver_type == SolverType.Siwave: + for cmp in simulation_setup.components: + self.core_components.create_port_on_component( + cmp, + net_list=simulation_setup.signal_nets, + do_pingroup=True, + reference_net=simulation_setup.power_nets, + port_type=SourceType.CircPort, + ) + self.logger.info("Configuring analysis setup.") + if not self.core_siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover + self.logger.error("Failed to configure Siwave simulation setup.") + + # if simulation_setup.defeature_layout: + # self.core_hfss.layout_defeaturing(simulation_setup) + return True + except: # pragma: no cover + return False diff --git a/pyaedt/edb_core/EDB_Data.py b/pyaedt/edb_core/EDB_Data.py index 5aeccd9e405..a16d4466e50 100644 --- a/pyaedt/edb_core/EDB_Data.py +++ b/pyaedt/edb_core/EDB_Data.py @@ -5,6 +5,11 @@ from collections import OrderedDict from pyaedt.edb_core.general import convert_py_list_to_net_list +from pyaedt.generic.constants import BasisOrder +from pyaedt.generic.constants import CutoutSubdesignType +from pyaedt.generic.constants import RadiationBoxType +from pyaedt.generic.constants import SolverType +from pyaedt.generic.constants import SweepType from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.modeler.GeometryOperators import GeometryOperators @@ -107,8 +112,15 @@ def components(self): return comps @pyaedt_function_handler() - def plot(self, layers=None, show_legend=True, save_plot=None, outline=None, size=(2000, 1000)): - """Plot a Net to Matplotlib 2D Chart. + def plot( + self, + layers=None, + show_legend=True, + save_plot=None, + outline=None, + size=(2000, 1000), + ): + """Plot a net to Matplotlib 2D chart. Parameters ---------- @@ -127,7 +139,12 @@ def plot(self, layers=None, show_legend=True, save_plot=None, outline=None, size """ self._app.core_nets.plot( - self.name, layers=layers, show_legend=show_legend, save_plot=save_plot, outline=outline, size=size + self.name, + layers=layers, + show_legend=show_legend, + save_plot=save_plot, + outline=outline, + size=size, ) @@ -669,8 +686,15 @@ def etch_factor(self, value): self.update_layers() @pyaedt_function_handler() - def plot(self, nets=None, show_legend=True, save_plot=None, outline=None, size=(2000, 1000)): - """Plot a Layer to Matplotlib 2D Chart. + def plot( + self, + nets=None, + show_legend=True, + save_plot=None, + outline=None, + size=(2000, 1000), + ): + """Plot a layer to a Matplotlib 2D chart. Parameters ---------- @@ -716,7 +740,16 @@ def init_vals(self): pass @pyaedt_function_handler() - def update_layer_vals(self, layerName, newLayer, etchMap, materialMap, fillMaterialMap, thicknessMap, layerTypeMap): + def update_layer_vals( + self, + layerName, + newLayer, + etchMap, + materialMap, + fillMaterialMap, + thicknessMap, + layerTypeMap, + ): """Update layer properties. Parameters @@ -923,7 +956,10 @@ def edb_layers(self): ), allLayers, ) - return sorted(allStackuplayers, key=lambda lyr=self._edb.Cell.StackupLayer: lyr.GetLowerElevation()) + return sorted( + allStackuplayers, + key=lambda lyr=self._edb.Cell.StackupLayer: lyr.GetLowerElevation(), + ) @property def signal_layers(self): @@ -1118,7 +1154,13 @@ def add_layer( ) self._edb_object[layerName] = EDBLayer(newLayer, self._pedbstackup) newLayer = self._edb_object[layerName].update_layer_vals( - layerName, newLayer, etchMap, material, fillMaterial, thickness, self._int_to_layer_types(layerType) + layerName, + newLayer, + etchMap, + material, + fillMaterial, + thickness, + self._int_to_layer_types(layerType), ) newLayer.SetLowerElevation(self._get_edb_value(el)) @@ -1460,7 +1502,14 @@ def int_to_geometry_type(self, val=0): @pyaedt_function_handler() def _update_pad_parameters_parameters( - self, layer_name=None, pad_type=None, geom_type=None, params=None, offsetx=None, offsety=None, rotation=None + self, + layer_name=None, + pad_type=None, + geom_type=None, + params=None, + offsetx=None, + offsety=None, + rotation=None, ): """Update padstack parameters. @@ -2201,7 +2250,12 @@ def _translate(p): p2 = [-s2, s2] p3 = [-s2, -s2] p4 = [s2, -s2] - rect = [_translate(_rotate(p1)), _translate(_rotate(p2)), _translate(_rotate(p3)), _translate(_rotate(p4))] + rect = [ + _translate(_rotate(p1)), + _translate(_rotate(p2)), + _translate(_rotate(p3)), + _translate(_rotate(p4)), + ] elif pad_shape == 3: # Rectangle x_size = float(params[0]) @@ -2212,7 +2266,12 @@ def _translate(p): p2 = [-sx2, sy2] p3 = [-sx2, -sy2] p4 = [sx2, -sy2] - rect = [_translate(_rotate(p1)), _translate(_rotate(p2)), _translate(_rotate(p3)), _translate(_rotate(p4))] + rect = [ + _translate(_rotate(p1)), + _translate(_rotate(p2)), + _translate(_rotate(p3)), + _translate(_rotate(p4)), + ] elif pad_shape == 4: # Oval x_size = params[0] @@ -2229,7 +2288,12 @@ def _translate(p): p2 = [-sx - k, sy + k] p3 = [-sx - k, -sy - k] p4 = [sx + k, -sy - k] - rect = [_translate(_rotate(p1)), _translate(_rotate(p2)), _translate(_rotate(p3)), _translate(_rotate(p4))] + rect = [ + _translate(_rotate(p1)), + _translate(_rotate(p2)), + _translate(_rotate(p3)), + _translate(_rotate(p4)), + ] elif pad_shape == 5: # Bullet x_size = params[0] @@ -2246,7 +2310,12 @@ def _translate(p): p2 = [-x_size * 0.5, sy + k] p3 = [-x_size * 0.5, -sy - k] p4 = [sx + k, -sy - k] - rect = [_translate(_rotate(p1)), _translate(_rotate(p2)), _translate(_rotate(p3)), _translate(_rotate(p4))] + rect = [ + _translate(_rotate(p1)), + _translate(_rotate(p2)), + _translate(_rotate(p3)), + _translate(_rotate(p4)), + ] elif pad_shape == 6: # N-Sided Polygon size = params[0] @@ -2257,7 +2326,12 @@ def _translate(p): p2 = [0.0, apothem] p3 = [-apothem, 0.0] p4 = [0.0, -apothem] - rect = [_translate(_rotate(p1)), _translate(_rotate(p2)), _translate(_rotate(p3)), _translate(_rotate(p4))] + rect = [ + _translate(_rotate(p1)), + _translate(_rotate(p2)), + _translate(_rotate(p3)), + _translate(_rotate(p4)), + ] elif pad_shape == 0 and polygon_data is not None: # Polygon points = [] @@ -2609,3 +2683,939 @@ def __init__(self, edbutils, db, cell): self.EdbHandler.dB = db self.EdbHandler.cell = cell self.EdbHandler.layout = cell.GetLayout() + + +class SimulationConfiguration(object): + """Parses an ASCII simulation configuration file, which supports all types of inputs + for setting up and automating any kind of SI or PI simulation with HFSS 3D Layout + or Siwave. If fields are omitted, default values are applied. This class can be instantiated directly from + Configuration file example: + SolverType = 'Hfss3DLayout' + GenerateSolerdBalls = 'True' + SignalNets = ['net1', 'net2'] + PowerNets = ['gnd'] + Components = [] + SolderBallsDiams = ['0.077mm', '0.077mm'] + UseDefaultCoaxPortRadialExtentFactor='True' + TrimRefSize='False' + CutoutSubdesignType='Conformal' + CutoutSubdesignExpansion='0.1' + CutoutSubdesignRoundCorners='True' + SweepInterpolating='True' + UseQ3DForDC='True' + RelatirelativeveErrorS='0.5' + UseErrorZ0='False' + PercentErrorZ0='1' + EnforceCausality='True' + EnforcePassivity='True' + PassivityTolerance='0.0001' + SweepName='Sweep1' + RadiationBox='ConvexHull' + StartFreq = '0.0GHz' + StopFreq = '10.001GHz' + SweepType='LinearStep' + StepFreq = '0.040004GHz' + Mesh_Freq = '3GHz' + MaxNumPasses='30' + MaxMagDeltaS='0.03' + MinNumPasses='1' + BasisOrder='Mixed' + DoLambdaRefinement='True' + ArcAngle='30deg' + StartAzimuth='0' + MaxArcPoints='8' + UseArcToChordError='True' + ArcToChordError='1um' + DefeatureAbsLength='1um' + DefeatureLayout='True' + MinimumVoidSuface = '0' + MaxSufDev = '0.001' + ProcessPadstackDefinitions = 'False' + ReturnCurrentDistribution = 'True' + IgnoreNonFunctionalPads = 'True' + IncludeInterPlaneCoupling = 'True' + XtalkThreshold = '-50' + MinVoidArea = '0.01mm2' + MinPadAreaToMesh = '0.01mm2' + SnapLengthThreshold = '2.5um' + DcMinPlaneAreaToMesh = '8mil2' + MaxInitMeshEdgeLength = '14.5mil' + SignalLayersProperties = [] + """ + + def __init__(self, filename): + self._filename = filename + self._setup_name = "Pyaedt_setup" + self._generate_solder_balls = True + self._signal_nets = [] + self._power_nets = [] + self._components = [] + self._coax_solder_ball_diameter = [] + self._use_default_coax_port_radial_extension = True + self._trim_reference_size = False + self._cutout_subdesign_type = CutoutSubdesignType.Conformal # Conformal + self._cutout_subdesign_expansion = 0.1 + self._cutout_subdesign_round_corner = True + self._sweep_interpolating = True + self._use_q3d_for_dc = False + self._relative_error = 0.5 + self._use_error_z0 = False + self._percentage_error_z0 = 1 + self._enforce_causality = True + self._enforce_passivity = True + self._passivity_tolerance = 0.0001 + self._sweep_name = "Sweep1" + self._radiation_box = RadiationBoxType.ConvexHull # 'ConvexHull' + self._start_frequency = "0.0GHz" # 0.0 + self._stop_freq = "10.0GHz" # 10e9 + self._sweep_type = SweepType.Linear # 'Linear' + self._step_freq = "0.025GHz" # 10e6 + self._decade_count = 100 # Newly Added + self._mesh_freq = "3GHz" # 5e9 + self._max_num_passes = 30 + self._max_mag_delta_s = 0.03 + self._min_num_passes = 1 + self._basis_order = BasisOrder.Mixed # 'Mixed' + self._do_lambda_refinement = True + self._arc_angle = "30deg" # 30 + self._start_azimuth = 0 + self._max_arc_points = 8 + self._use_arc_to_chord_error = True + self._arc_to_chord_error = "1um" # 1e-6 + self._defeature_abs_length = "1um" # 1e-6 + self._defeature_layout = True + self._minimum_void_surface = 0 + self._max_suf_dev = 1e-3 + self._process_padstack_definitions = False + self._return_current_distribution = True + self._ignore_non_functional_pads = True + self._include_inter_plane_coupling = True + self._xtalk_threshold = -50 + self._min_void_area = "0.01mm2" + self._min_pad_area_to_mesh = "0.01mm2" + self._snap_length_threshold = "2.5um" + self._min_plane_area_to_mesh = "4mil2" # Newly Added + self._dc_min_plane_area_to_mesh = "8mil2" + self._max_init_mesh_edge_length = "14.5mil" + self._signal_layers_properties = {} + self._coplanar_instances = [] + self._signal_layer_etching_instances = [] + self._etching_factor_instances = [] + self._dielectric_extent = 0.01 + self._airbox_horizontal_extent = 0.04 + self._airbox_negative_vertical_extent = 0.1 + self._airbox_positive_vertical_extent = 0.1 + self._honor_user_dielectric = False + self._truncate_airbox_at_ground = False + self._use_radiation_boundary = True + self._do_cutout_subdesign = True + self._solver_type = SolverType.Hfss3dLayout + self._read_cfg() + + @property + def generate_solder_balls(self): # pragma: no cover + return self._generate_solder_balls + + @generate_solder_balls.setter + def generate_solder_balls(self, value): + if isinstance(value, bool): # pragma: no cover + self._generate_solder_balls = value + + @property + def signal_nets(self): + return self._signal_nets + + @signal_nets.setter + def signal_nets(self, value): + if isinstance(value, list): # pragma: no cover + self._signal_nets = value + + @property + def setup_name(self): + return self._setup_name + + @setup_name.setter + def setup_name(self, value): + if isinstance(value, str): # pragma: no cover + self._setup_name = value + + @property + def power_nets(self): + return self._power_nets + + @power_nets.setter + def power_nets(self, value): + if isinstance(value, list): + self._power_nets = value + + @property + def components(self): + return self._components + + @components.setter + def components(self, value): + if isinstance(value, list): + self._components = value + + @property + def coax_solder_ball_diameter(self): # pragma: no cover + return self._coax_solder_ball_diameter + + @coax_solder_ball_diameter.setter + def coax_solder_ball_diameter(self, value): # pragma: no cover + if isinstance(value, list): + self._coax_solder_ball_diameter = value + + @property + def use_default_coax_port_radial_extension(self): + return self._use_default_coax_port_radial_extension + + @use_default_coax_port_radial_extension.setter + def use_default_coax_port_radial_extension(self, value): # pragma: no cover + if isinstance(value, bool): + self._use_default_coax_port_radial_extension = value + + @property + def trim_reference_size(self): + return self._trim_reference_size + + @trim_reference_size.setter + def trim_reference_size(self, value): # pragma: no cover + if isinstance(value, bool): + self._trim_reference_size = value + + @property + def do_cutout_subdesign(self): + return self._do_cutout_subdesign + + @do_cutout_subdesign.setter + def do_cutout_subdesign(self, value): # pragma: no cover + if isinstance(value, bool): + self._do_cutout_subdesign = value + + @property + def cutout_subdesign_type(self): + return self._cutout_subdesign_type + + @cutout_subdesign_type.setter + def cutout_subdesign_type(self, value): # pragma: no cover + if isinstance(value, CutoutSubdesignType): + self._cutout_subdesign_type = value + + @property + def cutout_subdesign_expansion(self): + return self._cutout_subdesign_expansion + + @cutout_subdesign_expansion.setter + def cutout_subdesign_expansion(self, value): # pragma: no cover + if isinstance(value, float): + self._cutout_subdesign_expansion = value + + @property + def cutout_subdesign_round_corner(self): + return self._cutout_subdesign_round_corner + + @cutout_subdesign_round_corner.setter + def cutout_subdesign_round_corner(self, value): # pragma: no cover + if isinstance(value, bool): + self._cutout_subdesign_round_corner = value + + @property + def sweep_interpolating(self): # pragma: no cover + return self._sweep_interpolating + + @sweep_interpolating.setter + def sweep_interpolating(self, value): # pragma: no cover + if isinstance(value, bool): + self._sweep_interpolating = value + + @property + def use_q3d_for_dc(self): # pragma: no cover + return self._use_q3d_for_dc + + @use_q3d_for_dc.setter + def use_q3d_for_dc(self, value): # pragma: no cover + if isinstance(value, bool): + self._use_q3d_for_dc = value + + @property + def relative_error(self): # pragma: no cover + return self._relative_error + + @relative_error.setter + def relative_error(self, value): # pragma: no cover + if isinstance(value, float): + self._relative_error = value + + @property + def use_error_z0(self): # pragma: no cover + return self._use_error_z0 + + @use_error_z0.setter + def use_error_z0(self, value): # pragma: no cover + if isinstance(value, bool): + self._use_error_z0 = value + + @property + def percentage_error_z0(self): # pragma: no cover + return self._percentage_error_z0 + + @percentage_error_z0.setter + def percentage_error_z0(self, value): # pragma: no cover + if isinstance(value, float): + self._percentage_error_z0 = value + + @property + def enforce_causality(self): # pragma: no cover + return self._enforce_causality + + @enforce_causality.setter + def enforce_causality(self, value): # pragma: no cover + if isinstance(value, bool): + self._enforce_causality = value + + @property + def enforce_passivity(self): # pragma: no cover + return self._enforce_passivity + + @enforce_passivity.setter + def enforce_passivity(self, value): # pragma: no cover + if isinstance(value, bool): + self._enforce_passivity = value + + @property + def passivity_tolerance(self): # pragma: no cover + return self._passivity_tolerance + + @passivity_tolerance.setter + def passivity_tolerance(self, value): # pragma: no cover + if isinstance(value, float): + self._passivity_tolerance = value + + @property + def sweep_name(self): # pragma: no cover + return self._sweep_name + + @sweep_name.setter + def sweep_name(self, value): # pragma: no cover + if isinstance(value, str): + self._sweep_name = value + + @property + def radiation_box(self): # pragma: no cover + return self._radiation_box + + @radiation_box.setter + def radiation_box(self, value): # pragma: no cover + if isinstance(value, RadiationBoxType): + self._radiation_box = value + + @property + def start_frequency(self): # pragma: no cover + return self._start_frequency + + @start_frequency.setter + def start_frequency(self, value): # pragma: no cover + if isinstance(value, str): + self._start_frequency = value + + @property + def stop_freq(self): # pragma: no cover + return self._stop_freq + + @stop_freq.setter + def stop_freq(self, value): # pragma: no cover + if isinstance(value, str): + self._stop_freq = value + + @property + def sweep_type(self): # pragma: no cover + return self._sweep_type + + @sweep_type.setter + def sweep_type(self, value): # pragma: no cover + if isinstance(value, SweepType): + self._sweep_type = value + # if isinstance(value, str): + # self._sweep_type = value + + @property + def step_freq(self): # pragma: no cover + return self._step_freq + + @step_freq.setter + def step_freq(self, value): # pragma: no cover + if isinstance(value, str): + self._step_freq = value + + @property + def decade_count(self): # pragma: no cover + return self._decade_count + + @decade_count.setter + def decade_count(self, value): # pragma: no cover + if isinstance(value, int): + self._decade_count = value + + @property + def mesh_freq(self): + return self._mesh_freq + + @mesh_freq.setter + def mesh_freq(self, value): # pragma: no cover + if isinstance(value, str): + self._mesh_freq = value + + @property + def max_num_passes(self): # pragma: no cover + return self._max_num_passes + + @max_num_passes.setter + def max_num_passes(self, value): # pragma: no cover + if isinstance(value, int): + self._max_num_passes = value + + @property + def max_mag_delta_s(self): # pragma: no cover + return self._max_mag_delta_s + + @max_mag_delta_s.setter + def max_mag_delta_s(self, value): # pragma: no cover + if isinstance(value, float): + self._max_mag_delta_s = value + + @property + def min_num_passes(self): # pragma: no cover + return self._min_num_passes + + @min_num_passes.setter + def min_num_passes(self, value): # pragma: no cover + if isinstance(value, int): + self._min_num_passes = value + + @property + def basis_order(self): # pragma: no cover + return self._basis_order + + @basis_order.setter + def basis_order(self, value): # pragma: no cover + if isinstance(value, BasisOrder): + self._basis_order = value + + @property + def do_lambda_refinement(self): # pragma: no cover + return self._do_lambda_refinement + + @do_lambda_refinement.setter + def do_lambda_refinement(self, value): # pragma: no cover + if isinstance(value, bool): + self._do_lambda_refinement = value + + @property + def arc_angle(self): # pragma: no cover + return self._arc_angle + + @arc_angle.setter + def arc_angle(self, value): # pragma: no cover + if isinstance(value, str): + self._arc_angle = value + + @property + def start_azimuth(self): # pragma: no cover + return self._start_azimuth + + @start_azimuth.setter + def start_azimuth(self, value): # pragma: no cover + if isinstance(value, float): + self._start_azimuth = value + + @property + def max_arc_points(self): # pragma: no cover + return self._max_arc_points + + @max_arc_points.setter + def max_arc_points(self, value): # pragma: no cover + if isinstance(value, int): + self._max_arc_points = value + + @property + def use_arc_to_chord_error(self): # pragma: no cover + return self._use_arc_to_chord_error + + @use_arc_to_chord_error.setter + def use_arc_to_chord_error(self, value): # pragma: no cover + if isinstance(value, bool): + self._use_arc_to_chord_error = value + + @property + def arc_to_chord_error(self): # pragma: no cover + return self._arc_to_chord_error + + @arc_to_chord_error.setter + def arc_to_chord_error(self, value): # pragma: no cover + if isinstance(value, str): + self._arc_to_chord_error = value + + @property + def defeature_abs_length(self): # pragma: no cover + return self._defeature_abs_length + + @defeature_abs_length.setter + def defeature_abs_length(self, value): # pragma: no cover + if isinstance(value, str): + self._defeature_abs_length = value + + @property + def defeature_layout(self): # pragma: no cover + return self._defeature_layout + + @defeature_layout.setter + def defeature_layout(self, value): # pragma: no cover + if isinstance(value, bool): + self._defeature_layout = value + + @property + def minimum_void_surface(self): # pragma: no cover + return self._minimum_void_surface + + @minimum_void_surface.setter + def minimum_void_surface(self, value): # pragma: no cover + if isinstance(value, float): + self._minimum_void_surface = value + + @property + def max_suf_dev(self): # pragma: no cover + return self._max_suf_dev + + @max_suf_dev.setter + def max_suf_dev(self, value): # pragma: no cover + if isinstance(value, float): + self._max_suf_dev = value + + @property + def process_padstack_definitions(self): # pragma: no cover + return self._process_padstack_definitions + + @process_padstack_definitions.setter + def process_padstack_definitions(self, value): # pragma: no cover + if isinstance(value, bool): + self._process_padstack_definitions = value + + @property + def return_current_distribution(self): # pragma: no cover + return self._return_current_distribution + + @return_current_distribution.setter + def return_current_distribution(self, value): # pragma: no cover + if isinstance(value, bool): + self._return_current_distribution = value + + @property + def ignore_non_functional_pads(self): # pragma: no cover + return self._ignore_non_functional_pads + + @ignore_non_functional_pads.setter + def ignore_non_functional_pads(self, value): # pragma: no cover + if isinstance(value, bool): + self._ignore_non_functional_pads = value + + @property + def include_inter_plane_coupling(self): # pragma: no cover + return self._include_inter_plane_coupling + + @include_inter_plane_coupling.setter + def include_inter_plane_coupling(self, value): # pragma: no cover + if isinstance(value, bool): + self._include_inter_plane_coupling = value + + @property + def xtalk_threshold(self): # pragma: no cover + return self._xtalk_threshold + + @xtalk_threshold.setter + def xtalk_threshold(self, value): # pragma: no cover + if isinstance(value, float): + self._xtalk_threshold = value + + @property + def min_void_area(self): # pragma: no cover + return self._min_void_area + + @min_void_area.setter + def min_void_area(self, value): # pragma: no cover + if isinstance(value, str): + self._min_void_area = value + + @property + def min_pad_area_to_mesh(self): # pragma: no cover + return self._min_pad_area_to_mesh + + @min_pad_area_to_mesh.setter + def min_pad_area_to_mesh(self, value): # pragma: no cover + if isinstance(value, str): + self._min_pad_area_to_mesh = value + + @property + def snap_length_threshold(self): # pragma: no cover + return self._snap_length_threshold + + @snap_length_threshold.setter + def snap_length_threshold(self, value): # pragma: no cover + if isinstance(value, str): + self._snap_length_threshold = value + + @property + def min_plane_area_to_mesh(self): # pragma: no cover + return self._min_plane_area_to_mesh + + @min_plane_area_to_mesh.setter + def min_plane_area_to_mesh(self, value): # pragma: no cover + if isinstance(value, str): + self._min_plane_area_to_mesh = value + + @property + def dc_min_plane_area_to_mesh(self): # pragma: no cover + return self._dc_min_plane_area_to_mesh + + @dc_min_plane_area_to_mesh.setter + def dc_min_plane_area_to_mesh(self, value): # pragma: no cover + if isinstance(value, str): + self._dc_min_plane_area_to_mesh = value + + @property + def max_init_mesh_edge_length(self): # pragma: no cover + return self._max_init_mesh_edge_length + + @max_init_mesh_edge_length.setter + def max_init_mesh_edge_length(self, value): # pragma: no cover + if isinstance(value, str): + self._max_init_mesh_edge_length = value + + @property + def signal_layers_properties(self): # pragma: no cover + return self._signal_layers_properties + + @signal_layers_properties.setter + def signal_layers_properties(self, value): # pragma: no cover + if isinstance(value, dict): + self._signal_layers_properties = value + + @property + def coplanar_instances(self): # pragma: no cover + return self._coplanar_instances + + @coplanar_instances.setter + def coplanar_instances(self, value): # pragma: no cover + if isinstance(value, list): + self._coplanar_instances = value + + @property + def signal_layer_etching_instances(self): # pragma: no cover + return self._signal_layer_etching_instances + + @signal_layer_etching_instances.setter + def signal_layer_etching_instances(self, value): # pragma: no cover + if isinstance(value, list): + self._signal_layer_etching_instances = value + + @property + def etching_factor_instances(self): # pragma: no cover + return self._etching_factor_instances + + @etching_factor_instances.setter + def etching_factor_instances(self, value): # pragma: no cover + if isinstance(value, list): + self._etching_factor_instances = value + + @property + def dielectric_extent(self): # pragma: no cover + return self._dielectric_extent + + @dielectric_extent.setter + def dielectric_extent(self, value): # pragma: no cover + if isinstance(value, float): + self._dielectric_extent = value + + @property + def airbox_horizontal_extent(self): # pragma: no cover + return self._airbox_horizontal_extent + + @airbox_horizontal_extent.setter + def airbox_horizontal_extent(self, value): # pragma: no cover + if isinstance(value, float): + self._airbox_horizontal_extent = value + + @property + def airbox_negative_vertical_extent(self): # pragma: no cover + return self._airbox_negative_vertical_extent + + @airbox_negative_vertical_extent.setter + def airbox_negative_vertical_extent(self, value): # pragma: no cover + if isinstance(value, float): + self._airbox_negative_vertical_extent = value + + @property + def airbox_positive_vertical_extent(self): # pragma: no cover + return self._airbox_positive_vertical_extent + + @airbox_positive_vertical_extent.setter + def airbox_positive_vertical_extent(self, value): # pragma: no cover + if isinstance(value, float): + self._airbox_positive_vertical_extent = value + + @property + def honor_user_dielectric(self): # pragma: no cover + return self._honor_user_dielectric + + @honor_user_dielectric.setter + def honor_user_dielectric(self, value): # pragma: no cover + if isinstance(value, bool): + self._honor_user_dielectric = value + + @property + def truncate_airbox_at_ground(self): # pragma: no cover + return self._truncate_airbox_at_ground + + @truncate_airbox_at_ground.setter + def truncate_airbox_at_ground(self, value): # pragma: no cover + if isinstance(value, bool): + self._truncate_airbox_at_ground = value + + @property + def solver_type(self): # pragma: no cover + return self._solver_type + + @solver_type.setter + def solver_type(self, value): # pragma: no cover + if isinstance(value, int): + self._solver_type = value + + @property + def use_radiation_boundary(self): # pragma: no cover + return self._use_radiation_boundary + + @use_radiation_boundary.setter + def use_radiation_boundary(self, value): # pragma: no cover + if isinstance(value, bool): + self._use_radiation_boundary = value + + def _get_bool_value(self, value): # pragma: no cover + val = value.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError("Invalid truth value %r" % (val,)) + + def _get_list_value(self, value): # pragma: no cover + value = value.strip("[]") + if len(value) == 0: + return [] + else: + value = value.split(",") + if isinstance(value, list): + prop_values = [i.strip() for i in value] + else: + prop_values = [value.strip()] + return prop_values + + def _parse_signal_layer_properties(self, signal_properties): # pragma: no cover + for lay in signal_properties: + lp = lay.split(":") + try: + self.signal_layers_properties.update({lp[0]: [lp[1], lp[2], lp[3], lp[4], lp[5]]}) + except: + print("Missing parameter for layer {0}".format(lp[0])) + + def _read_cfg(self): # pragma: no cover + """Configuration file reader. + + Examples + -------- + + >>> from pyaedt import Edb + >>> from pyaedt.edb_core.EDB_Data 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() + """ + + if not os.path.exists(self._filename): + # raise Exception("{} does not exist.".format(self._filename)) + pass + + try: + with open(self._filename) as cfg_file: + cfg_lines = cfg_file.read().split("\n") + for line in cfg_lines: + if line.strip() != "": + if line.find("="): + i, prop_value = line.strip().split("=") + value = prop_value.replace("'", "").strip() + if i.startswith("GenerateSolderBalls"): + self.generate_solder_balls = self._get_bool_value(value) + elif i.startswith("SignalNets"): + self.signal_nets = self._get_list_value(value) + elif i.startswith("PowerNets"): + self.power_nets = self._get_list_value(value) + elif i.startswith("Components"): + self.components = self._get_list_value(value) + elif i.startswith("coaxSolderBallsDiams"): + self.coax_solder_ball_diameter = self._get_list_value(value) + elif i.startswith("UseDefaultCoaxPortRadialExtentFactor"): + self.signal_nets = self._get_bool_value(value) + elif i.startswith("TrimRefSize"): + self.trim_reference_size = self._get_bool_value(value) + elif i.startswith("CutoutSubdesignType"): + if value.lower().startswith("conformal"): + self.cutout_subdesign_type = CutoutSubdesignType.Conformal + elif value.lower().startswith("boundingbox"): + self.cutout_subdesign_type = CutoutSubdesignType.BoundingBox + else: + print("Unprocessed value for CutoutSubdesignType '{0}'".format(value)) + elif i.startswith("CutoutSubdesignExpansion"): + self.cutout_subdesign_expansion = float(value) + elif i.startswith("CutoutSubdesignRoundCorners"): + self.cutout_subdesign_round_corner = self._get_bool_value(value) + elif i.startswith("SweepInterpolating"): + self.sweep_interpolating = self._get_bool_value(value) + elif i.startswith("UseQ3DForDC"): + self.use_q3d_for_dc = self._get_bool_value(value) + elif i.startswith("RelativeErrorS"): + self.relative_error = float(value) + elif i.startswith("UseErrorZ0"): + self.use_error_z0 = self._get_bool_value(value) + elif i.startswith("PercentErrorZ0"): + self.percentage_error_z0 = float(value) + elif i.startswith("EnforceCausality"): + self.enforce_causality = self._get_bool_value(value) + elif i.startswith("EnforcePassivity"): + self.enforce_passivity = self._get_bool_value(value) + elif i.startswith("PassivityTolerance"): + self.passivity_tolerance = float(value) + elif i.startswith("SweepName"): + self.sweep_name = value + elif i.startswith("RadiationBox"): + if value.lower().startswith("conformal"): + self.radiation_box = RadiationBoxType.Conformal + elif value.lower().startswith("boundingbox"): + self.radiation_box = RadiationBoxType.BoundingBox + elif value.lower().startswith("convexhull"): + self.radiation_box = RadiationBoxType.ConvexHull + else: + print("Unprocessed value for RadiationBox '{0}'".format(value)) + elif i.startswith("StartFreq"): + self.start_frequency = value + elif i.startswith("StopFreq"): + self.stop_freq = value + elif i.startswith("SweepType"): + if value.lower().startswith("linear"): + self.sweep_type = SweepType.Linear + elif value.lower().startswith("logcount"): + self.sweep_type = SweepType.LogCount + else: + print("Unprocessed value for SweepType '{0}'".format(value)) + elif i.startswith("StepFreq"): + self.step_freq = value + elif i.startswith("DecadeCount"): + self.decade_count = int(value) + elif i.startswith("Mesh_Freq"): + self.mesh_freq = value + elif i.startswith("MaxNumPasses"): + self.max_num_passes = int(value) + elif i.startswith("MaxMagDeltaS"): + self.max_mag_delta_s = float(value) + elif i.startswith("MinNumPasses"): + self.min_num_passes = int(value) + elif i.startswith("BasisOrder"): + if value.lower().startswith("mixed"): + self.basis_order = BasisOrder.Mixed + elif value.lower().startswith("zero"): + self.basis_order = BasisOrder.Zero + elif value.lower().startswith("first"): # single + self.basis_order = BasisOrder.single + elif value.lower().startswith("second"): # double + self.basis_order = BasisOrder.Double + else: + print("Unprocessed value for BasisOrder '{0}'".format(value)) + elif i.startswith("DoLambdaRefinement"): + self.do_lambda_refinement = self._get_bool_value(value) + elif i.startswith("ArcAngle"): + self.arc_angle = value + elif i.startswith("StartAzimuth"): + self.start_azimuth = float(value) + elif i.startswith("MaxArcPoints"): + self.max_arc_points = int(value) + elif i.startswith("UseArcToChordError"): + self.use_arc_to_chord_error = self._get_bool_value(value) + elif i.startswith("ArcToChordError"): + self.arc_to_chord_error = value + elif i.startswith("DefeatureAbsLength"): + self.defeature_abs_length = value + elif i.startswith("DefeatureLayout"): + self.defeature_layout = self._get_bool_value(value) + elif i.startswith("MinimumVoidSuface"): + self.minimum_void_surface = float(value) + elif i.startswith("MaxSufDev"): + self.max_suf_dev = float(value) + elif i.startswith("ProcessPadstackDefinitions"): + self.process_padstack_definitions = self._get_bool_value(value) + elif i.startswith("ReturnCurrentDistribution"): + self.return_current_distribution = self._get_bool_value(value) + elif i.startswith("IgnoreNonFunctionalPads"): + self.ignore_non_functional_pads = self._get_bool_value(value) + elif i.startswith("IncludeInterPlaneCoupling"): + self.include_inter_plane_coupling = self._get_bool_value(value) + elif i.startswith("XtalkThreshold"): + self.xtalk_threshold = float(value) + elif i.startswith("MinVoidArea"): + self.min_void_area = value + elif i.startswith("MinPadAreaToMesh"): + self.min_pad_area_to_mesh = value + elif i.startswith("SnapLengthThreshold"): + self.snap_length_threshold = value + elif i.startswith("MinPlaneAreaToMesh"): + self.min_plane_area_to_mesh = value + elif i.startswith("DcMinPlaneAreaToMesh"): + self.dc_min_plane_area_to_mesh = value + elif i.startswith("MaxInitMeshEdgeLength"): + self.max_init_mesh_edge_length = value + elif i.startswith("SignalLayersProperties"): + self._parse_signal_layer_properties(self._get_list_value(value)) + elif i.startswith("coplanar_instances"): + self.coplanar_instances = self._get_list_value(value) + elif i.startswith("SignalLayersEtching"): + self.signal_layer_etching_instances = self._get_list_value(value) + elif i.startswith("EtchingFactor"): + self.etching_factor_instances = self._get_list_value(value) + elif i.startswith("DoCutoutSubdesign"): + self.do_cutout_subdesign = self._get_list_value(value) + elif i.startswith("SolverType"): + if value.lower() == "hfss": + self.solver_type = 0 + if value.lower() == "hfss3dlayout": + self.solver_type = 6 + elif value.lower().startswith("siwave"): + self.solver_type = 1 + elif value.lower().startswith("q3d"): + self.solver_type = 2 + elif value.lower().startswith("nexxim"): + self.solver_type = 4 + elif value.lower().startswith("maxwell"): + self.solver_type = 3 + elif value.lower().startswith("twinbuilder"): + self.solver_type = 5 + else: + self.solver_type = SolverType.Hfss3dLayout + else: + print("Unprocessed line in cfg file: {0}".format(line)) + else: + continue + except EnvironmentError as e: + print("Error reading cfg file: {}".format(e.message)) + raise diff --git a/pyaedt/edb_core/components.py b/pyaedt/edb_core/components.py index f1a2dee6674..c8b9eaab172 100644 --- a/pyaedt/edb_core/components.py +++ b/pyaedt/edb_core/components.py @@ -467,23 +467,31 @@ def get_component_placement_vector( if hosting_component_pin2: h_pin2 = self._get_edb_pin_from_pin_name(hosting_component, hosting_component_pin2) h_pin2_pos = self.get_pin_position(h_pin2) - - if flipped: - m_pin1_pos[1] *= -1.0 - m_pin2_pos[1] *= -1.0 - + # + vector = [h_pin1_pos[0] - m_pin1_pos[0], h_pin1_pos[1] - m_pin1_pos[1]] vector1 = GeometryOperators.v_points(m_pin1_pos, m_pin2_pos) vector2 = GeometryOperators.v_points(h_pin1_pos, h_pin2_pos) + multiplier = 1 + if flipped: + multiplier = -1 + vector1[1] = multiplier * vector1[1] rotation = GeometryOperators.v_angle_sign_2D(vector1, vector2, False) - offset_from_x = m_pin1_pos[0] * math.cos(rotation) + m_pin1_pos[1] * math.sin(rotation) - offset_from_y = -1 * m_pin1_pos[0] * math.sin(rotation) + m_pin1_pos[1] * math.cos(rotation) - vector = [h_pin1_pos[0] - offset_from_x, h_pin1_pos[1] - offset_from_y] - - solder_ball_height = self.get_solder_ball_height(mounted_component) - if isinstance(solder_ball_height, float): + if rotation != 0.0: + layinst = mounted_component.GetLayout().GetLayoutInstance() + cmpinst = layinst.GetLayoutObjInstance(mounted_component, None) + center = cmpinst.GetCenter() + center_double = [center.X.ToDouble(), center.Y.ToDouble()] + vector_center = GeometryOperators.v_points(center_double, m_pin1_pos) + x_v2 = vector_center[0] * math.cos(rotation) + multiplier * vector_center[1] * math.sin(rotation) + y_v2 = -1 * vector_center[0] * math.sin(rotation) + multiplier * vector_center[1] * math.cos(rotation) + new_vector = [x_v2 + center_double[0], y_v2 + center_double[1]] + vector = [h_pin1_pos[0] - new_vector[0], h_pin1_pos[1] - new_vector[1]] + + if vector: + solder_ball_height = self.get_solder_ball_height(mounted_component) return True, vector, rotation, solder_ball_height - self._logger.warning("Failed to compute solder ball height.") + self._logger.warning("Failed to compute vector.") return False, [0, 0], 0, 0 @pyaedt_function_handler() @@ -510,7 +518,12 @@ def get_solder_ball_height(self, cmp): @pyaedt_function_handler() def create_port_on_component( - self, component, net_list, port_type=SourceType.CoaxPort, do_pingroup=True, reference_net="gnd" + self, + component, + net_list, + port_type=SourceType.CoaxPort, + do_pingroup=True, + reference_net="gnd", ): """Create ports on given component. @@ -520,12 +533,12 @@ def create_port_on_component( EDB component or str component name. net_list : str or list of string. - The list of nets where ports have to be created on the component. - If net is not part of the component this one will be skipped. + List of nets where ports must be created on the component. + If the net is not part of the component, this parameter is skipped. - port_type : SourceType enumerator, CoaxPort or CircPort - Define the type of port to be created. CoaxPort will auto generate solder balls. - CircPort will generate circuit ports on pins belonging to the net list. + port_type : SourceType enumerator, CoaxPort or CircuitPort + Type of port to create. ``CoaxPort`` generates solder balls. + ``CircuitPort`` generates circuit ports on pins belonging to the net list. do_pingroup : bool True activate pingroup during port creation (only used with combination of CoaxPort), @@ -545,9 +558,8 @@ def create_port_on_component( >>> from pyaedt import Edb >>> edbapp = Edb("myaedbfolder") >>> net_list = ["M_DQ<1>", "M_DQ<2>", "M_DQ<3>", "M_DQ<4>", "M_DQ<5>"] - >>> edbapp.core_components.create_port_on_component(component="U2A5", net_list=net_list, - ... port_type=SourceType.CoaxPort, - ... do_pingroup=False, reference_net="GND") + >>> edbapp.core_components.create_port_on_component(cmp="U2A5", net_list=net_list, + >>> port_type=SourceType.CoaxPort, do_pingroup=False, refnet="GND") """ if isinstance(component, self._edb.Cell.Hierarchy.Component): @@ -571,9 +583,9 @@ def create_port_on_component( if port_type == SourceType.CoaxPort: pad_params = self._padstack.get_pad_parameters(pin=cmp_pins[0], layername=pin_layers[0], pad_type=0) - sball_diam = min([self._get_edb_value(val).ToDouble() for val in pad_params[1]]) - sb_height = sball_diam - self.set_solder_ball(component, sb_height, sball_diam) + sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]]) + solder_ball_height = sball_diam + self.set_solder_ball(component, solder_ball_height, sball_diam) for pin in cmp_pins: self._padstack.create_coax_port(pin) @@ -582,27 +594,25 @@ def create_port_on_component( if do_pingroup: pingroups = [] if len(ref_pins) == 1: - ref_pin_group_term = self._create_terminal(ref_pins[0]) + self.create_terminal = self._create_terminal(ref_pins[0]) + self.terminal = self.create_terminal + ref_pin_group_term = self.terminal else: ref_pin_group = self.create_pingroup_from_pins(ref_pins) - ref_pin_group_term = self._create_pin_group_terminal(ref_pin_group[1]) - if not ref_pin_group[0]: + if not ref_pin_group: + return False + ref_pin_group_term = self._create_pin_group_terminal(ref_pin_group) + if not ref_pin_group_term: return False - ref_pin_group_term.SetBoundaryType(self._edb.Cell.Terminal.BoundaryType.PortBoundary) - ref_pin_group_term.SetIsCircuitPort(True) for net in net_list: pins = [pin for pin in cmp_pins if pin.GetNet().GetName() == net] pin_group = self.create_pingroup_from_pins(pins) - if pin_group[0]: - pingroups.append(pin_group[1]) - pg_terminal = [] - for pg in pingroups: - pg_term = self._create_pin_group_terminal(pg) - pg_terminal.append(pg_term) - for term in pg_terminal: - term.SetBoundaryType(self._edb.Cell.Terminal.BoundaryType.PortBoundary) - term.SetIsCircuitPort(True) - term.SetReferenceTerminal(ref_pin_group_term) + if not pin_group: + return False + pin_group_term = self._create_pin_group_terminal(pin_group) + if pin_group_term: + pin_group_term.SetReferenceTerminal(ref_pin_group_term) + else: for net in net_list: pins = [pin for pin in cmp_pins if pin.GetNet().GetName().lower() == net] @@ -631,7 +641,8 @@ def _create_terminal(self, pin): """ res, pin_position, pin_rot = pin.GetPositionAndRotation( - self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), 0.0 + self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), + 0.0, ) if not is_ironpython: res, from_layer, to_layer = pin.GetLayerRange(None, None) @@ -662,13 +673,15 @@ def _get_closest_pin_from(self, pin, ref_pinlist): """ res, pin_position, pin_rot = pin.GetPositionAndRotation( - self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), 0.0 + self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), + 0.0, ) distance = 1e3 closest_pin = ref_pinlist[0] for ref_pin in ref_pinlist: res, ref_pin_position, ref_pin_rot = ref_pin.GetPositionAndRotation( - self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), 0.0 + self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), + 0.0, ) temp_distance = pin_position.Distance(ref_pin_position) if temp_distance < distance: @@ -749,7 +762,7 @@ def _create_pin_group_terminal(self, pingroup, isref=False): layout = pingroup.GetLayout() cmp_name = pingroup.GetComponent().GetName() net_name = pingroup.GetNet().GetName() - term_name = pingroup.GetUniqueName(layout, "Pingroup_{0}_{1}".format(cmp_name, net_name)) + term_name = generate_unique_name("Pingroup_{0}_{1}".format(cmp_name, net_name)) pingroup_term = self._edb.Cell.Terminal.PinGroupTerminal.Create( self._active_layout, pingroup.GetNet(), term_name, pingroup, isref ) @@ -950,9 +963,7 @@ def create_pingroup_from_pins(self, pins, group_name=None): self._logger.error("No pins specified for pin group %s", group_name) return (False, None) if group_name is None: - cmp_name = pins[0].GetComponent().GetName() - net_name = pins[0].GetNet().GetName() - group_name = generate_unique_name("{}_{}_".format(cmp_name, net_name), n=3) + group_name = self._edb.Cell.Hierarchy.PinGroup.GetUniqueName(self._active_layout) pingroup = _retry_ntimes( 10, self._edb.Cell.Hierarchy.PinGroup.Create, @@ -961,10 +972,10 @@ def create_pingroup_from_pins(self, pins, group_name=None): convert_py_list_to_net_list(pins), ) if pingroup.IsNull(): - return (False, None) + return False else: pingroup.SetNet(pins[0].GetNet()) - return (True, pingroup) + return pingroup @pyaedt_function_handler() def delete_single_pin_rlc(self): @@ -1064,13 +1075,13 @@ def disable_rlc_component(self, component_name): return False @pyaedt_function_handler() - def set_solder_ball(self, component, sball_diam="", sball_height=""): + def set_solder_ball(self, component="", sball_diam="100um", sball_height="150um"): """Set cylindrical solder balls on a given component. Parameters ---------- - component : str or EDB component - Name of the discret component. + component_name : str or EDB component + Name of the discrete component. sball_diam : str, float, optional Diameter of the solder ball. @@ -1154,7 +1165,14 @@ def set_solder_ball(self, component, sball_diam="", sball_height=""): return False @pyaedt_function_handler() - def set_component_rlc(self, componentname, res_value=None, ind_value=None, cap_value=None, isparallel=False): + def set_component_rlc( + self, + componentname, + res_value=None, + ind_value=None, + cap_value=None, + isparallel=False, + ): """Update values for an RLC component. Parameters @@ -1226,7 +1244,12 @@ def set_component_rlc(self, componentname, res_value=None, ind_value=None, cap_v @pyaedt_function_handler() def update_rlc_from_bom( - self, bom_file, delimiter=";", valuefield="Func des", comptype="Prod name", refdes="Pos / Place" + self, + bom_file, + delimiter=";", + valuefield="Func des", + comptype="Prod name", + refdes="Pos / Place", ): """Update the EDC core component values (RLCs) with values coming from a BOM file. @@ -1403,7 +1426,8 @@ def get_pin_position(self, pin): res, pt_pos, rot_pos = pin.GetPositionAndRotation() else: res, pt_pos, rot_pos = pin.GetPositionAndRotation( - self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), 0.0 + self._edb.Geometry.PointData(self._get_edb_value(0.0), self._get_edb_value(0.0)), + 0.0, ) if pin.GetComponent().IsNull(): transformed_pt_pos = pt_pos @@ -1653,10 +1677,34 @@ def short_component_pins(self, component_name, pins_to_short=None, width=1e-3): ] l0_min = l0.index(min(l0)) p1 = [] - p1.append([positions_to_short[i + 1][0] - delta_pins[i + 1], positions_to_short[i + 1][1], 0]) - p1.append([positions_to_short[i + 1][0] + delta_pins[i + 1], positions_to_short[i + 1][1], 0]) - p1.append([positions_to_short[i + 1][0], positions_to_short[i + 1][1] - delta_pins[i + 1], 0]) - p1.append([positions_to_short[i + 1][0], positions_to_short[i + 1][1] + delta_pins[i + 1], 0]) + p1.append( + [ + positions_to_short[i + 1][0] - delta_pins[i + 1], + positions_to_short[i + 1][1], + 0, + ] + ) + p1.append( + [ + positions_to_short[i + 1][0] + delta_pins[i + 1], + positions_to_short[i + 1][1], + 0, + ] + ) + p1.append( + [ + positions_to_short[i + 1][0], + positions_to_short[i + 1][1] - delta_pins[i + 1], + 0, + ] + ) + p1.append( + [ + positions_to_short[i + 1][0], + positions_to_short[i + 1][1] + delta_pins[i + 1], + 0, + ] + ) p1.append([positions_to_short[i + 1][0], positions_to_short[i + 1][1], 0]) l1 = [ diff --git a/pyaedt/edb_core/general.py b/pyaedt/edb_core/general.py index d5f7114a50a..3a33d9d4214 100644 --- a/pyaedt/edb_core/general.py +++ b/pyaedt/edb_core/general.py @@ -16,9 +16,11 @@ clr.AddReference("System.Collections") from System.Collections.Generic import List + from System import Tuple + except ImportError: if os.name != "posix": - warnings.warn("This module requires pythonnet.") + warnings.warn("This module requires Python.NET.") logger = logging.getLogger(__name__) @@ -44,6 +46,20 @@ def convert_netdict_to_pydict(dict_in): return pydict +@pyaedt_function_handler() +def convert_pytuple_to_nettuple(_tuple): + """Convert a Python tuple into a .NET tuple. + Parameters + ---------- + tuple : Python tuple + + Returns + ------- + .NET tuple. + """ + return Tuple.Create(_tuple[0], _tuple[1]) + + @pyaedt_function_handler() def convert_pydict_to_netdict(dict): """Convert a Python dictionarty to a Net dictionary. diff --git a/pyaedt/edb_core/hfss.py b/pyaedt/edb_core/hfss.py index 86a560a9540..7b8a96a68bb 100644 --- a/pyaedt/edb_core/hfss.py +++ b/pyaedt/edb_core/hfss.py @@ -1,11 +1,18 @@ """ -This module contains the `EdbHfss` class. +This module contains the ``EdbHfss`` class. """ +import math + +from pyaedt.edb_core.EDB_Data import SimulationConfiguration from pyaedt.edb_core.general import convert_netdict_to_pydict from pyaedt.edb_core.general import convert_py_list_to_net_list +from pyaedt.edb_core.general import convert_pytuple_to_nettuple +from pyaedt.generic.constants import RadiationBoxType +from pyaedt.generic.constants import SweepType from pyaedt.generic.general_methods import generate_unique_name from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import pyaedt_function_handler +from pyaedt.modeler.GeometryOperators import GeometryOperators class EdbHfss(object): @@ -441,83 +448,34 @@ 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 net_name in net_list: - port_name = self.create_coax_port_on_component_per_net(ref, net_name) - coax.append(port_name) + for pin in self._pedb.core_components.components[ref].pins.items(): + if pin[1].net_name in net_list and pin[1].pin.IsLayoutPin(): + port_name = "{}_{}_{}".format(ref, pin[1].net_name, pin[1].pin.GetName()) + if is_ironpython: + ( + res, + from_layer_pos, + to_layer_pos, + ) = pin[1].pin.GetLayerRange() + else: + res, from_layer_pos, to_layer_pos = pin[1].pin.GetLayerRange(None, None) + if ( + res + and from_layer_pos + and self._edb.Cell.Terminal.PadstackInstanceTerminal.Create( + self._active_layout, + pin[1].pin.GetNet(), + port_name, + pin[1].pin, + to_layer_pos, + ) + ): + coax.append(port_name) return coax - @pyaedt_function_handler() - def create_coax_port_on_component_per_pin(self, reference_designator, pin_number, port_name=""): - """Create a coaxial port on a component per pin. - - Parameters - ---------- - reference_designator : str - Reference designator of the component. - pin_number : str - Pin number. - port_name : str, optional - Name of the net. The default is ``""``, in which case a name is automatically assigned. - - Returns - ------- - Port name when successful; ``False`` otherwise. - """ - comp = self._pedb.core_components.components[reference_designator] - pin = comp.pins[pin_number] - edb_net = pin.pin.GetNet() - edb_pin = pin.pin - if is_ironpython: - _, from_layer, to_layer = edb_pin.GetLayerRange() - else: - _, from_layer, to_layer = edb_pin.GetLayerRange(None, None) - - if from_layer == comp.placement_layer: - edb_layer = from_layer - else: - edb_layer = to_layer - - net_name = pin.net_name - - if not port_name: - port_name = "{}_{}_{}".format(reference_designator, net_name, pin.pin.GetName()) - - if self._edb.Cell.Terminal.PadstackInstanceTerminal.Create( - self._active_layout, edb_net, port_name, edb_pin, edb_layer - ): - return port_name - else: - return False - - @pyaedt_function_handler() - def create_coax_port_on_component_per_net(self, reference_designator, net_name, port_name=""): - """Create a coaxial port on a component pin. - - Parameters - ---------- - reference_designator : str - Reference designator of the component. - net_name : str - Name of the net. - port_name : str, optional - Name of the net. The default is ``""``, in which case a name is automatically assigned. - - Returns - ------- - bool - `Port name when successful; ``False`` otherwise. - """ - comp = self._pedb.core_components.components[reference_designator] - pin_number = "" - for pnum, pin in comp.pins.items(): - if pin.net_name == net_name: - pin_number = pnum - break - return self.create_coax_port_on_component_per_pin(reference_designator, pin_number, port_name) - @pyaedt_function_handler() def create_hfss_ports_on_padstack(self, pinpos, portname=None): - """Create a HFSS port on a padstack. + """Create an HFSS port on a padstack. Parameters ---------- @@ -640,17 +598,22 @@ def create_lumped_port_on_trace( ] if found_pt: pt = self._edb.Geometry.PointData( - self._get_edb_value(_pt[0]), self._get_edb_value(_pt[1]) + self._get_edb_value(_pt[0]), + self._get_edb_value(_pt[1]), ) if not self._hfss_terminals.CreateEdgePort(path, pt, reference_layer, port_name): raise Exception( "edge port creation failed on point {}, {}".format( - str(pt.X.ToDouble()), str(pt.Y.ToDouble()) + str(pt.X.ToDouble()), + str(pt.Y.ToDouble()), ) ) else: for pt in trace_path_pts: - _pt = [round(pt.X.ToDouble(), digit_resolution), round(pt.Y.ToDouble(), digit_resolution)] + _pt = [ + round(pt.X.ToDouble(), digit_resolution), + round(pt.Y.ToDouble(), digit_resolution), + ] if bool(set(_pt) & set(layout_bbox)): if return_points_only: edges_pts.append(_pt) @@ -681,7 +644,10 @@ def create_lumped_port_on_trace( else: port_name = generate_unique_name("port") if not self._hfss_terminals.CreateEdgePortOnPolygon( - poly, convert_py_list_to_net_list(pt_at_left), reference_layer, port_name + poly, + convert_py_list_to_net_list(pt_at_left), + reference_layer, + port_name, ): # pragma: no cover raise Exception("Failed to create port on polygon {}".format(poly.GetName())) @@ -697,7 +663,10 @@ def create_lumped_port_on_trace( else: port_name = generate_unique_name("port") if not self._hfss_terminals.CreateEdgePortOnPolygon( - poly, convert_py_list_to_net_list(pt_at_bottom), reference_layer, port_name + poly, + convert_py_list_to_net_list(pt_at_bottom), + reference_layer, + port_name, ): # pragma: no cover raise Exception("Failed to create port on polygon {}".format(poly.GetName())) @@ -713,7 +682,10 @@ def create_lumped_port_on_trace( else: port_name = generate_unique_name("port") if not self._hfss_terminals.CreateEdgePortOnPolygon( - poly, convert_py_list_to_net_list(pt_at_right), reference_layer, port_name + poly, + convert_py_list_to_net_list(pt_at_right), + reference_layer, + port_name, ): # pragma: no cover raise Exception("Failed to create port on polygon {}".format(poly.GetName())) @@ -729,7 +701,10 @@ def create_lumped_port_on_trace( else: port_name = generate_unique_name("port") if not self._hfss_terminals.CreateEdgePortOnPolygon( - poly, convert_py_list_to_net_list(pt_at_top), reference_layer, port_name + poly, + convert_py_list_to_net_list(pt_at_top), + reference_layer, + port_name, ): # pragma: no cover raise Exception("Failed to create port on polygon {}".format(poly.GetName())) if return_points_only: @@ -767,3 +742,505 @@ def get_layout_bounding_box(self, layout=None, digit_resolution=6): round(_bbox.Item2.Y.ToDouble(), digit_resolution), ] return layout_bbox + + @pyaedt_function_handler() + def create_circuit_ports_on_components_no_pin_group( + self, + signal_nets=None, + power_nets=None, + simulation_setup=None, + component_list=None, + ): + """Create circuit ports on given components. + For each component, create a coplanar circuit port at each signalNet pin. + Use the closest powerNet pin as a reference, regardless of component. + + Parameters + ---------- + signal_nets : list, optional if simulation_setup is provided + List of signal net names. This list is ignored if a ``simulation_setup`` object is provided. + + power_nets : list, optional if a ``simulatiom_setup`` object is provided + List of power net names. This list is ignored if a ``simulation_setup`` object + is provided. + + component_list : list optional if simulatiom_setup provided. + The list of component names. will be ignored if simulation_setup object is provided + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + if simulation_setup: + if not isinstance(simulation_setup, SimulationConfiguration): + self._logger.error( + "simulation setup was provided but must be an instance of \ + EDB_Data.SimulationConfiguration" + ) + return False + signal_nets = simulation_setup.signal_nets + power_nets = simulation_setup.power_nets + component_list = simulation_setup.coplanar_instances + else: + if not component_list: + return False + + if not simulation_setup.coplanar_instances: + return False + + layout = self._active_layout + l_inst = layout.GetLayoutInstance() + edb_power_nets = [self._pedb.core_nets.find_or_create_net(net) for net in power_nets] + for inst in component_list: + comp = self._edb.Cell.Hierarchy.Component.FindByName(layout, inst) + if comp.IsNull(): + self._logger.warning("SetupCoplanarInstances: could not find {0}".format(inst)) + continue + # Get the portLayer based on the component's pin placement + cmp_layer = self._edb.Cell.Hierarchy.Component.GetPlacementLayer(comp) + # Get the bbox of the comp + bb = self._edb.Geometry.PolygonData.CreateFromBBox(l_inst.GetLayoutObjInstance(comp, None).GetBBox()) + bb_c = bb.GetBoundingCircleCenter() + # Expand x5 to create testing polygon... + bb.Scale(5, bb_c) + # Find the closest pin in the Ground/Power nets... + hit = l_inst.FindLayoutObjInstance(bb, cmp_layer, convert_py_list_to_net_list(edb_power_nets)) + all_hits = [list(hit.Item1.Items) + list(hit.Item2.Items)] + hit_pinsts = [ + obj + for obj in all_hits + if obj.GetLayoutObj().GetObjType() == self._edb.Cell.LayoutObjType.PadstackInstance + ] + if not hit_pinsts: + self._logger.error("SetupCoplanarInstances: could not find a pin in the vicinity of {0}".format(inst)) + continue + # Iterate each pin in the component that's on the signal nets and create a circuit port + pin_list = [ + obj + for obj in list(comp.LayoutObjs) + if obj.GetObjType() == self._edb.Cell.LayoutObjType.PadstackInstance + and obj.GetNet().GetName() in signal_nets + ] + for ii, pin in enumerate(pin_list): + pin_c = l_inst.GetLayoutObjInstance(pin, None).GetCenter() + ref_pinst = None + ref_pt = None + ref_dist = None + for hhLoi in hit_pinsts: + this_c = hhLoi.GetCenter() + this_dist = this_c.Distance(pin_c) + if ref_pt is None or this_dist < ref_dist: + ref_pinst = hhLoi.GetLayoutObj() + ref_pt = this_c + ref_dist = this_dist + + port_nm = "PORT_{0}_{1}@{2}".format(comp.GetName(), ii, pin.GetNet().GetName()) + ## TO complete and check for embefing in create_port_on_component + ########################### + ########################### + self._edbutils.HfssUtilities.CreateCircuitPortFromPoints( + port_nm, + layout, + pin_c, + cmp_layer, + pin.GetNet(), + ref_pt, + cmp_layer, + ref_pinst.GetNet(), + ) + return True + + @pyaedt_function_handler() + def configure_hfss_extents(self, simulation_setup=None): + """Configure the HFSS extent box. + + Parameters + ---------- + simulation_setup : + Edb_DATA.SimulationConfiguration object + + Returns + ------- + bool + True when succeeded, False when failed. + """ + + if not isinstance(simulation_setup, SimulationConfiguration): + self._logger.error("Configure HFSS extent requires EDB_Data.SimulationConfiguration object") + return False + hfss_extent = self._edb.Utility.HFSSExtentInfo() + if simulation_setup.radiation_box == RadiationBoxType.BoundingBox: + hfss_extent.ExtentType = self._edb.Utility.HFSSExtentInfoType.BoundingBox + elif simulation_setup.radiation_box == RadiationBoxType.Conformal: + hfss_extent.ExtentType = self._edb.Utility.HFSSExtentInfoType.Conforming + else: + hfss_extent.ExtentType = self._edb.Utility.HFSSExtentInfoType.ConvexHull + hfss_extent.DielectricExtentSize = convert_pytuple_to_nettuple((simulation_setup.dielectric_extent, True)) + hfss_extent.AirBoxHorizontalExtent = convert_pytuple_to_nettuple( + (simulation_setup.airbox_horizontal_extent, True) + ) + hfss_extent.AirBoxNegativeVerticalExtent = convert_pytuple_to_nettuple( + (simulation_setup.airbox_negative_vertical_extent, True) + ) + hfss_extent.AirBoxPositiveVerticalExtent = convert_pytuple_to_nettuple( + (simulation_setup.airbox_positive_vertical_extent, True) + ) + hfss_extent.HonorUserDielectric = simulation_setup.honor_user_dielectric + hfss_extent.TruncateAirBoxAtGround = simulation_setup.truncate_airbox_at_ground + hfss_extent.UseOpenRegion = simulation_setup.use_radiation_boundary + return self._active_layout.GetCell().SetHFSSExtentInfo(hfss_extent) + + @pyaedt_function_handler() + def configure_hfss_analysis_setup(self, simulation_setup=None): + """ + Configure HFSS analysis setup. + + Parameters + ---------- + simulation_setup : + Edb_DATA.SimulationConfiguration object + + Returns + ------- + bool + True when succeeded, False when failed. + """ + if not isinstance(simulation_setup, SimulationConfiguration): + self._logger.error( + "Configure HFSS analysis requires and EDB_Data.SimulationConfiguration object as \ + argument" + ) + return False + adapt = self._pedb.simsetupdata.AdaptiveFrequencyData() + adapt.AdaptiveFrequency = simulation_setup.mesh_freq + adapt.MaxPasses = int(simulation_setup.max_num_passes) + adapt.MaxDelta = str(simulation_setup.max_mag_delta_s) + simsetup_info = self._pedb.simsetupdata.SimSetupInfo[self._pedb.simsetupdata.HFSSSimulationSettings]() + simsetup_info.Name = simulation_setup.setup_name + + simsetup_info.SimulationSettings.CurveApproxSettings.ArcAngle = simulation_setup.arc_angle + simsetup_info.SimulationSettings.CurveApproxSettings.UseArcToChordError = ( + simulation_setup.use_arc_to_chord_error + ) + simsetup_info.SimulationSettings.CurveApproxSettings.ArcToChordError = simulation_setup.arc_to_chord_error + if is_ironpython: + simsetup_info.SimulationSettings.AdaptiveSettings.AdaptiveFrequencyDataList.Clear() + simsetup_info.SimulationSettings.AdaptiveSettings.AdaptiveFrequencyDataList.Add(adapt) + else: + list(simsetup_info.SimulationSettings.AdaptiveSettings.AdaptiveFrequencyDataList).clear() + list(simsetup_info.SimulationSettings.AdaptiveSettings.AdaptiveFrequencyDataList).append(adapt) + simsetup_info.SimulationSettings.InitialMeshSettings.LambdaRefine = simulation_setup.do_lambda_refinement + simsetup_info.SimulationSettings.InitialMeshSettings.UseDefaultLambda = True + simsetup_info.SimulationSettings.AdaptiveSettings.MaxRefinePerPass = 30 + simsetup_info.SimulationSettings.AdaptiveSettings.MinPasses = simulation_setup.min_num_passes # 1 + simsetup_info.SimulationSettings.AdaptiveSettings.MinConvergedPasses = 1 + simsetup_info.SimulationSettings.HFSSSolverSettings.OrderBasis = ( + simulation_setup.basis_order + ) # -1 # e.g. mixed + simsetup_info.SimulationSettings.HFSSSolverSettings.UseHFSSIterativeSolver = False + simsetup_info.SimulationSettings.DefeatureSettings.UseDefeature = False # set True when using defeature ratio + simsetup_info.SimulationSettings.DefeatureSettings.UseDefeatureAbsLength = ( + simulation_setup.defeature_layout + ) # True + simsetup_info.SimulationSettings.DefeatureSettings.DefeatureAbsLength = simulation_setup.defeature_abs_length + + try: + sweep = self._pedb.simsetupdata.SweepData(simulation_setup.sweep_name) + sweep.IsDiscrete = False + sweep.UseQ3DForDC = simulation_setup.use_q3d_for_dc + sweep.RelativeSError = simulation_setup.relative_error + sweep.InterpUsePortImpedance = False + sweep.EnforceCausality = simulation_setup.start_frequency + # sweep.EnforceCausality = False + sweep.EnforcePassivity = simulation_setup.enforce_passivity + sweep.PassivityTolerance = simulation_setup.passivity_tolerance + if is_ironpython: + sweep.Frequencies.Clear() + else: + list(sweep.Frequencies).clear() + if simulation_setup.sweep_type == SweepType.LogCount: # setup_info.SweepType == 'DecadeCount' + self._setup_decade_count_sweep( + sweep, + simulation_setup.start_frequency, + simulation_setup.stop_freq, + simulation_setup.decade_count, + ) # Added DecadeCount as a new attribute + + else: + if is_ironpython: + sweep.Frequencies = self._pedb.simsetupdata.SweepData.SetFrequencies( + simulation_setup.start_frequency, + simulation_setup.stop_freq, + simulation_setup.step_freq, + ) + else: + sweep.Frequencies = convert_py_list_to_net_list( + self._pedb.simsetupdata.SweepData.SetFrequencies( + simulation_setup.start_frequency, + simulation_setup.stop_freq, + simulation_setup.step_freq, + ) + ) + if is_ironpython: + simsetup_info.SweepDataList.Add(sweep) + else: + simsetup_info.SweepDataList = convert_py_list_to_net_list([sweep]) + except Exception as err: + self._logger.error("Exception in Sweep configuration: {0}".format(err)) + + sim_setup = self._edb.Utility.HFSSSimulationSetup(simsetup_info) + + return self._active_layout.GetCell().AddSimulationSetup(sim_setup) + + def _setup_decade_count_sweep(self, sweep, start_freq="1", stop_freq="1MHz", decade_count="10"): + start_f = GeometryOperators.parse_dim_arg(start_freq) + if start_f == 0.0: + start_f = 10 + self._logger.warning("Decade Count sweep does not support DC value, defaulting starting frequency to 10Hz") + + stop_f = GeometryOperators.parse_dim_arg(stop_freq) + decade_cnt = GeometryOperators.parse_dim_arg(decade_count) + freq = start_f + sweep.Frequencies.Add(str(freq)) + + while freq < stop_f: + freq = freq * math.pow(10, 1.0 / decade_cnt) + sweep.Frequencies.Add(str(freq)) + + @pyaedt_function_handler() + def trim_component_reference_size(self, simulation_setup=None, trim_to_terminals=False): + """Trim the common component reference to the minimally acceptable size. + + Parameters + ---------- + simulation_setup : + Edb_DATA.SimulationConfiguration object + + trim_to_terminals : + bool. + True, reduce the reference to a box covering only the active terminals (i.e. those with + ports). + False, reduce the reference to the minimal size needed to cover all pins + + Returns + ------- + bool + True when succeeded, False when failed. + """ + + if not isinstance(simulation_setup, SimulationConfiguration): + self._logger.error( + "Trim component reference size requires an EDB_Data.SimulationConfiguration object \ + as argument" + ) + return False + + if not simulation_setup.coax_instances: + return + + layout = self._cell.GetLayout() + l_inst = layout.GetLayoutInstance() + + for inst in simulation_setup.coax_instances: + comp = self._edb.Cell.Hierarchy.Component.FindByName(layout, inst) + if comp.IsNull(): + continue + + terms_bbox_pts = self._get_terminals_bbox(comp, l_inst, trim_to_terminals) + if not terms_bbox_pts: + continue + + terms_bbox = self._edb.Geometry.PolygonData.CreateFromBBox(terms_bbox_pts) + + if trim_to_terminals: + # Remove any pins that aren't interior to the Terminals bbox + pin_list = [ + obj + for obj in list(comp.LayoutObjs) + if obj.GetObjType() == self._edb.Cell.LayoutObjType.PadstackInstance + ] + for pin in pin_list: + loi = l_inst.GetLayoutObjInstance(pin, None) + bb_c = loi.GetCenter() + if not terms_bbox.PointInPolygon(bb_c): + comp.RemoveMember(pin) + + # Set the port property reference size + cmp_prop = comp.GetComponentProperty().Clone() + port_prop = cmp_prop.GetPortProperty().Clone() + port_prop.SetReferenceSizeAuto(False) + port_prop.SetReferenceSize( + terms_bbox_pts.Item2.X.ToDouble() - terms_bbox_pts.Item1.X.ToDouble(), + terms_bbox_pts.Item2.Y.ToDouble() - terms_bbox_pts.Item1.Y.ToDouble(), + ) + cmp_prop.SetPortProperty(port_prop) + comp.SetComponentProperty(cmp_prop) + return True + + @pyaedt_function_handler() + def set_coax_port_attributes(self, simulation_setup=None): + """Set coaxial port attribute with forcing default impedance to 50 Ohms and adjusting the coaxial extent radius. + + Parameters + ---------- + simulation_setup : + Edb_DATA.SimulationConfiguration object. + + Returns + ------- + bool + True when succeeded, False when failed. + """ + + if not isinstance(simulation_setup, SimulationConfiguration): + self._logger.error( + "Set coax port attribute requires an EDB_Data.SimulationConfiguration object \ + as argument." + ) + return False + net_names = [net.GetName() for net in list(self._active_layout.Nets) if not net.IsPowerGround()] + cmp_names = ( + simulation_setup.components + if simulation_setup.components + else [gg.GetName() for gg in self._active_layout.Groups] + ) + ii = 0 + for cc in cmp_names: + cmp = self._edb.Cell.Hierarchy.Component.FindByName(self._active_layout, cc) + if cmp.IsNull(): + self._logger.warning("RenamePorts: could not find component {0}".format(cc)) + continue + terms = [obj for obj in list(cmp.LayoutObjs) if obj.GetObjType() == self._edb.Cell.LayoutObjType.Terminal] + for nn in net_names: + for tt in [term for term in terms if term.GetNet().GetName() == nn]: + if not tt.SetImpedance(self._pedb.edb_value("50ohm")): + self._logger.warning("Could not set terminal {0} impedance as 50ohm".format(tt.GetName())) + continue + ii += 1 + + if not simulation_setup.use_default_coax_port_radial_extension: + radial_factor_multiplier = 0.125 + # Set the Radial Extent Factor + typ = cmp.GetComponentType() + if typ in [ + self._edb.Definition.ComponentType.Other, + self._edb.Definition.ComponentType.IC, + self._edb.Definition.ComponentType.IO, + ]: + cmp_prop = cmp.GetComponentProperty().Clone() + ( + success, + diam1, + diam2, + ) = cmp_prop.GetSolderBallProperty().GetDiameter() + if success and diam1 and diam2 > 0: + radial_factor = "{0}meter".format(radial_factor_multiplier * diam1) + for tt in terms: + self._builder.SetHfssSolverOption(tt, "Radial Extent Factor", radial_factor) + self._builder.SetHfssSolverOption(tt, "Layer Alignment", "Upper") # ensure this is also set + return True + + @pyaedt_function_handler() + def _get_terminals_bbox(self, comp, l_inst, terminals_only): + terms_loi = [] + if terminals_only: + term_list = [ + obj for obj in list(comp.LayoutObjs) if obj.GetObjType() == self._edb.Cell.LayoutObjType.Terminal + ] + for tt in term_list: + success, p_inst, lyr = tt.GetParameters() + if success and lyr: + loi = l_inst.GetLayoutObjInstance(p_inst, None) + terms_loi.append(loi) + else: + pin_list = [ + obj + for obj in list(comp.LayoutObjs) + if obj.GetObjType() == self._edb.Cell.LayoutObjType.PadstackInstance + ] + for pi in pin_list: + loi = l_inst.GetLayoutObjInstance(pi, None) + terms_loi.append(loi) + + if len(terms_loi) == 0: + return None + + terms_bbox = [] + for loi in terms_loi: + # Need to account for the coax port dimension + bb = loi.GetBBox() + ll = [bb.Item1.X.ToDouble(), bb.Item1.Y.ToDouble()] + ur = [bb.Item2.X.ToDouble(), bb.Item2.Y.ToDouble()] + # dim = 0.26 * max(abs(UR[0]-LL[0]), abs(UR[1]-LL[1])) # 0.25 corresponds to the default 0.5 + # Radial Extent Factor, so set slightly larger to avoid validation errors + dim = 0.30 * max(abs(ur[0] - ll[0]), abs(ur[1] - ll[1])) # 0.25 corresponds to the default 0.5 + terms_bbox.append(self._edb.Geometry.PolygonData(ll[0] - dim, ll[1] - dim, ur[0] + dim, ur[1] + dim)) + return self._edb.Geometry.PolygonData.GetBBoxOfPolygons(convert_py_list_to_net_list(terms_bbox)) + + @pyaedt_function_handler() + def get_ports_number(self): + """Return the total number of excitation ports in a layout. + + Parameters + ---------- + None + + Returns + ------- + int + Number of ports. + + """ + port_list = [] + for term in self._active_layout.Terminals: + if str(term.GetBoundaryType()) == "PortBoundary": + if "ref" not in term.GetName(): + port_list.append(term) + return len(port_list) + + @pyaedt_function_handler() + def layout_defeaturing(self, simulation_setup=None): + """Defeature the layout by reducing the number of points for polygons based on surface deviation criteria. + + Parameters + ---------- + simulation_setup : Edb_DATA.SimulationConfiguration object + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if not isinstance(simulation_setup, SimulationConfiguration): + self._logger.error("Layout defeaturing requires an EDB_Data.SimulationConfiguration object as argument.") + return False + self._logger.info("Starting Layout Defeaturing") + polygon_list = self._pedb.core_primitives.polygons + polygon_with_voids = self._pedb.core_layout.get_poly_with_voids(polygon_list) + self._logger.info("Number of polygons with voids found: {0}".format(str(polygon_with_voids.Count))) + for _poly in polygon_list: + voids_from_current_poly = _poly.Voids + new_poly_data = self._pedb.core_layout.defeature_polygon(setup_info=simulation_setup, poly=_poly) + _poly.SetPolygonData(new_poly_data) + if len(voids_from_current_poly) > 0: + for void in voids_from_current_poly: + void_data = void.GetPolygonData() + if void_data.Area() < float(simulation_setup.minimum_void_surface): + void.Delete() + self._logger.warning( + "Defeaturing Polygon {0}: Deleting Void {1} area is lower than the minimum criteria".format( + str(_poly.GetId()), str(void.GetId()) + ) + ) + else: + self._logger.info( + "Defeaturing polygon {0}: void {1}".format(str(_poly.GetId()), str(void.GetId())) + ) + new_void_data = self._pedb.core_layout.defeature_polygon( + setup_info=simulation_setup, poly=void_data + ) + void.SetPolygonData(new_void_data) + + return True diff --git a/pyaedt/edb_core/layout.py b/pyaedt/edb_core/layout.py index ec15485cf0d..803eee55b7d 100644 --- a/pyaedt/edb_core/layout.py +++ b/pyaedt/edb_core/layout.py @@ -6,6 +6,7 @@ import warnings from pyaedt.edb_core.EDB_Data import EDBPrimitives +from pyaedt.edb_core.EDB_Data import SimulationConfiguration from pyaedt.edb_core.general import convert_py_list_to_net_list from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import pyaedt_function_handler @@ -13,9 +14,11 @@ try: from System import Tuple + # from System.Collections.Generic import List + except ImportError: if os.name != "posix": - warnings.warn('This module requires the "pythonnet" package.') + warnings.warn("This module requires the Python.NET package.") class EdbLayout(object): @@ -559,9 +562,19 @@ def fix_circle_void_for_clipping(self): if not void_circle.is_void: continue if is_ironpython: # pragma: no cover - res, center_x, center_y, radius = void_circle.primitive_object.GetParameters() + ( + 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) + ( + res, + center_x, + center_y, + radius, + ) = void_circle.primitive_object.GetParameters(0.0, 0.0, 0.0) cloned_circle = self._edb.Cell.Primitive.Circle.Create( self._active_layout, void_circle.layer_name, @@ -610,7 +623,10 @@ def shape_to_polygon_data(self, shape): elif shape.type == "rectangle": return self._createPolygonDataFromRectangle(shape) else: - self._logger.error("Unsupported shape type %s when creating a polygon primitive.", shape.type) + self._logger.error( + "Unsupported shape type %s when creating a polygon primitive.", + shape.type, + ) return None @pyaedt_function_handler() @@ -643,10 +659,12 @@ def _createPolygonDataFromPolygon(self, shape): ) arc = self._edb.Geometry.ArcData( self._edb.Geometry.PointData( - self._get_edb_value(startPoint[0].ToDouble()), self._get_edb_value(startPoint[1].ToDouble()) + self._get_edb_value(startPoint[0].ToDouble()), + self._get_edb_value(startPoint[1].ToDouble()), ), self._edb.Geometry.PointData( - self._get_edb_value(endPoint[0].ToDouble()), self._get_edb_value(endPoint[1].ToDouble()) + self._get_edb_value(endPoint[0].ToDouble()), + self._get_edb_value(endPoint[1].ToDouble()), ), ) arcs.append(arc) @@ -670,14 +688,17 @@ def _createPolygonDataFromPolygon(self, shape): return None arc = self._edb.Geometry.ArcData( self._edb.Geometry.PointData( - self._get_edb_value(startPoint[0].ToDouble()), self._get_edb_value(startPoint[1].ToDouble()) + self._get_edb_value(startPoint[0].ToDouble()), + self._get_edb_value(startPoint[1].ToDouble()), ), self._edb.Geometry.PointData( - self._get_edb_value(endPoint[0].ToDouble()), self._get_edb_value(endPoint[1].ToDouble()) + self._get_edb_value(endPoint[0].ToDouble()), + self._get_edb_value(endPoint[1].ToDouble()), ), rotationDirection, self._edb.Geometry.PointData( - self._get_edb_value(endPoint[3].ToDouble()), self._get_edb_value(endPoint[4].ToDouble()) + self._get_edb_value(endPoint[3].ToDouble()), + self._get_edb_value(endPoint[4].ToDouble()), ), ) arcs.append(arc) @@ -764,8 +785,15 @@ class Shape(object): """ def __init__( - self, type="unknown", pointA=None, pointB=None, centerPoint=None, radius=None, points=None, properties={} - ): + self, + type="unknown", # noqa + pointA=None, + pointB=None, + centerPoint=None, + radius=None, + points=None, + properties={}, + ): # noqa self.type = type self.pointA = pointA self.pointB = pointB @@ -775,7 +803,13 @@ def __init__( self.properties = properties @pyaedt_function_handler() - def parametrize_trace_width(self, nets_name, layers_name=None, parameter_name="trace_width", variable_value=None): + def parametrize_trace_width( + self, + nets_name, + layers_name=None, + parameter_name="trace_width", + variable_value=None, + ): """Parametrize a Trace on specific layer or all stackup. Parameters @@ -858,7 +892,10 @@ def unite_polygons_on_layer(self, layer_name=None, delete_padstack_gemometries=F if int(item.GetIntersectionType(void.GetPolygonData())) == 2: item.AddHole(void.GetPolygonData()) poly = self._edb.Cell.Primitive.Polygon.Create( - self._active_layout, lay, self._pedb.core_nets.nets[net].net_object, item + self._active_layout, + lay, + self._pedb.core_nets.nets[net].net_object, + item, ) list_to_delete = [i for i in poly_by_nets[net]] for v in all_voids: @@ -882,3 +919,133 @@ def unite_polygons_on_layer(self, layer_name=None, delete_padstack_gemometries=F self._pedb.core_padstack.remove_pads_from_padstack(pad) self.update_primitives() return True + + @pyaedt_function_handler() + def defeature_polygon(self, setup_info, poly, max_surface_deviation=0.001): + """Defeature the polygon based on the maximum surface deviation criteria. + + Parameters + ---------- + setup_info : EDB_Data_SimulatiomConfiguratio object + When the ``setup_info`` argument is provided, it overwrites the + ``maximum_surface_deviation`` value. + + poly : Edb Polygon primitive + Polygon to defeature. + + max_surface_deviation : float, optional + Maximum surface deviation criteria. The default is ``0.001``. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + try: + if setup_info: + max_surface_deviation = setup_info.max_suf_dev + poly_data = poly.GetPolygonData() + pts_list = [] + pts = poly_data.Points + defeaturing_step = 1e-6 + if len(poly_data) <= 16: + # Defeaturing skipped for polygons with less than 16 points + self._logger.info( + "Polygon {} is skipped for defeaturing because its number of point is less than 16. ".format( + poly.GetId() + ) + ) + return poly_data + + for pt in pts: + pts_list.append(pt) + nb_ini_pts = len(pts_list) + minimum_distance = defeaturing_step # 1e-6 + init_surf = poly_data.Area() + nb_pts_removed = 0 + surf_dev = 0 + new_poly = None + while (surf_dev < max_surface_deviation and pts_list.Count > 16 and minimum_distance < 1000e-6) and float( + nb_pts_removed + ) / float(nb_ini_pts) < 0.4: + pts_list, nb_pts_removed = self._trim_polygon_points(pts, minimum_distance) + new_poly = self._edb.Geometry.PolygonData(pts_list, True) + current_surf = new_poly.Area() + if current_surf == 0: + surf_dev = 1 + else: + surf_dev = abs(init_surf - current_surf) / init_surf + minimum_distance = minimum_distance + defeaturing_step + self._logger.info( + "Defeaturing polygon {0}: Final surface deviation = {1} , Maximum distance(um) = {2}, " + "Number of points removed = {3}/{4}".format( + str(poly.GetId()), + str(surf_dev), + str(minimum_distance * 1e6), + str(nb_pts_removed), + str(nb_ini_pts), + ) + ) + return new_poly + except: + return False + + @pyaedt_function_handler() + def _trim_polygon_points(self, points, minimum_distance): + pts_list = [] + ind = 0 + + nb_pts_removed = 0 + for pt in points: + pts_list.append(pt) + # NbIniPts = pts_list.Count + + while ind < pts_list.Count - 2: + pts_list, nb_pts_removed = self._get_point_list_with_minimum_distance( + pts_list, minimum_distance, ind, nb_pts_removed + ) + ind = ind + 1 + + return pts_list, nb_pts_removed + + @pyaedt_function_handler() + def _get_point_list_with_minimum_distance(self, pts_list, minimum_distance, ind, nb_pts_removed): + pt_ind = ind + 1 + while pts_list[ind].Distance(pts_list[pt_ind]) < minimum_distance and pt_ind < pts_list.Count - 2: + pts_list.RemoveAt(pt_ind) + nb_pts_removed += 1 + pt_ind += 1 + + return pts_list, nb_pts_removed + + @pyaedt_function_handler() + def setup_net_classes(self, simulation_setup=None): + """ + Define nets listed as power ground nets in the ``simulation_setup`` object. + + Parameters + ---------- + simulation_setup : simulation_setup EDB_Data.SimulationConfiguration object + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + + """ + if not isinstance(simulation_setup, SimulationConfiguration): + return False + + net_list = list(self._active_layout.Nets) + power_net_list = [net for net in self._active_layout.Nets if net.GetName() in simulation_setup.power_nets] + map(lambda obj: obj.SetIsPowerGround(False), net_list) + for net in power_net_list: + self._set_power_net(net) + return True + + @pyaedt_function_handler() + def _set_power_net(self, net): + if isinstance(net, self._edb.Cell.Net): + net.SetIsPowerGround(True) + self._logger.info("NET: {} set to power/ground class".format(net.GetName())) diff --git a/pyaedt/edb_core/nets.py b/pyaedt/edb_core/nets.py index 53781216f74..7941ae9dca7 100644 --- a/pyaedt/edb_core/nets.py +++ b/pyaedt/edb_core/nets.py @@ -259,7 +259,11 @@ def get_plot_data( if label not in label_colors: color = path.layer.GetColor() try: - c = (float(color.Item1 / 255), float(color.Item2 / 255), float(color.Item3 / 255)) + c = ( + float(color.Item1 / 255), + float(color.Item2 / 255), + float(color.Item3 / 255), + ) label_colors[label] = c except: label_colors[label] = list(CSS4_COLORS.keys())[color_index] @@ -314,7 +318,11 @@ def get_plot_data( if label not in label_colors: color = poly.GetLayer().GetColor() try: - c = (float(color.Item1 / 255), float(color.Item2 / 255), float(color.Item3 / 255)) + c = ( + float(color.Item1 / 255), + float(color.Item2 / 255), + float(color.Item3 / 255), + ) label_colors[label] = c except: label_colors[label] = list(CSS4_COLORS.keys())[color_index] @@ -346,7 +354,14 @@ def get_plot_data( @pyaedt_function_handler() def plot( - self, nets, layers=None, color_by_net=False, show_legend=True, save_plot=None, outline=None, size=(2000, 1000) + self, + nets, + layers=None, + color_by_net=False, + show_legend=True, + save_plot=None, + outline=None, + size=(2000, 1000), ): """Plot a Net to Matplotlib 2D Chart. @@ -379,7 +394,15 @@ def plot( color_by_net, outline, ) - plot_matplotlib(object_lists, size, show_legend, "X (m)", "Y (m)", self._pedb.active_cell.GetName(), save_plot) + plot_matplotlib( + object_lists, + size, + show_legend, + "X (m)", + "Y (m)", + self._pedb.active_cell.GetName(), + save_plot, + ) @pyaedt_function_handler() def is_power_gound_net(self, netname_list): @@ -497,7 +520,14 @@ def get_powertree(self, power_net_name, ground_nets): pins = self._pedb.core_components.get_pin_from_component(component=refdes, netName=el[2]) el.append("-".join([i.GetName() for i in pins])) - component_list_columns = ["refdes", "pin_name", "net_name", "component_type", "component_partname", "pin_list"] + component_list_columns = [ + "refdes", + "pin_name", + "net_name", + "component_type", + "component_partname", + "pin_list", + ] return component_list, component_list_columns, net_group @pyaedt_function_handler() diff --git a/pyaedt/edb_core/padstack.py b/pyaedt/edb_core/padstack.py index 71b24b3efb7..6f3ca1772aa 100644 --- a/pyaedt/edb_core/padstack.py +++ b/pyaedt/edb_core/padstack.py @@ -148,7 +148,13 @@ def update_padstacks(self): @pyaedt_function_handler() def create_circular_padstack( - self, padstackname=None, holediam="300um", paddiam="400um", antipaddiam="600um", startlayer=None, endlayer=None + self, + padstackname=None, + holediam="300um", + paddiam="400um", + antipaddiam="600um", + startlayer=None, + endlayer=None, ): """Create a circular padstack. @@ -175,7 +181,13 @@ def create_circular_padstack( Name of the padstack if the operation is successful. """ pad = self._padstack_methods.CreateCircularPadStackDef( - self._builder, padstackname, holediam, paddiam, antipaddiam, startlayer, endlayer + self._builder, + padstackname, + holediam, + paddiam, + antipaddiam, + startlayer, + endlayer, ) self.update_padstacks() @@ -259,14 +271,22 @@ def create_coax_port(self, padstackinstance): if not is_ironpython: res, fromlayer, tolayer = padstackinstance.GetLayerRange(None, None) self._edb.Cell.Terminal.PadstackInstanceTerminal.Create( - self._active_layout, padstackinstance.GetNet(), port_name, padstackinstance, tolayer + self._active_layout, + padstackinstance.GetNet(), + port_name, + padstackinstance, + tolayer, ) if res: return port_name else: res, fromlayer, tolayer = padstackinstance.GetLayerRange() self._edb.Cell.Terminal.PadstackInstanceTerminal.Create( - self._active_layout, padstackinstance.GetNet(), port_name, padstackinstance, tolayer + self._active_layout, + padstackinstance.GetNet(), + port_name, + padstackinstance, + tolayer, ) if res: return port_name @@ -482,6 +502,18 @@ def create_padstack( self.update_padstacks() return padstackname + @pyaedt_function_handler() + def _get_pin_layer_range(self, pin): + if not is_ironpython: + res, fromlayer, tolayer = pin.GetLayerRange(None, None) + + else: + res, fromlayer, tolayer = pin.GetLayerRange() + if res: + return fromlayer, tolayer + else: + return False + @pyaedt_function_handler() def duplicate_padstack(self, target_padstack_name, new_padstack_name=""): """Duplicate a padstack. @@ -569,7 +601,16 @@ def place_padstack( solderlayer = self._pedb.core_stackup.signal_layers[solderlayer]._layer if padstack: padstack_instance = self._edb.Cell.Primitive.PadstackInstance.Create( - self._active_layout, net, via_name, padstack, position, rotation, fromlayer, tolayer, solderlayer, None + self._active_layout, + net, + via_name, + padstack, + position, + rotation, + fromlayer, + tolayer, + solderlayer, + None, ) padstack_instance.SetIsLayoutPin(is_pin) self.update_padstack_instances() @@ -692,9 +733,23 @@ def set_pad_property( elif isinstance(layer_name, str): layer_name = [layer_name] for layer in layer_name: - new_padstack_def.SetPadParameters(layer, 0, pad_shape, pad_params, pad_x_offset, pad_y_offset, pad_rotation) new_padstack_def.SetPadParameters( - layer, 1, antipad_shape, antipad_params, antipad_x_offset, antipad_y_offset, antipad_rotation + layer, + 0, + pad_shape, + pad_params, + pad_x_offset, + pad_y_offset, + pad_rotation, + ) + new_padstack_def.SetPadParameters( + layer, + 1, + antipad_shape, + antipad_params, + antipad_x_offset, + antipad_y_offset, + antipad_rotation, ) self.padstacks[padstack_name].edb_padstack.SetData(new_padstack_def) self.update_padstacks() diff --git a/pyaedt/edb_core/siwave.py b/pyaedt/edb_core/siwave.py index d1bb5679cae..e01606bb1e0 100644 --- a/pyaedt/edb_core/siwave.py +++ b/pyaedt/edb_core/siwave.py @@ -6,10 +6,14 @@ import time import warnings +from pyaedt.edb_core.EDB_Data import SimulationConfiguration +from pyaedt.edb_core.general import convert_py_list_to_net_list +from pyaedt.generic.constants import SweepType from pyaedt.generic.general_methods import _retry_ntimes from pyaedt.generic.general_methods import generate_unique_name from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import pyaedt_function_handler +from pyaedt.modeler.GeometryOperators import GeometryOperators try: from System import String @@ -431,8 +435,16 @@ def _create_terminal_on_pins(self, source): res, fromLayer_pos, toLayer_pos = pos_pin.GetLayerRange() res, fromLayer_neg, toLayer_neg = neg_pin.GetLayerRange() else: - res, fromLayer_pos, toLayer_pos = source.positive_node.node_pins.GetLayerRange(None, None) - res, fromLayer_neg, toLayer_neg = source.negative_node.node_pins.GetLayerRange(None, None) + ( + res, + fromLayer_pos, + toLayer_pos, + ) = source.positive_node.node_pins.GetLayerRange(None, None) + ( + res, + fromLayer_neg, + toLayer_neg, + ) = source.negative_node.node_pins.GetLayerRange(None, None) pos_pingroup_terminal = _retry_ntimes( 10, self._edb.Cell.Terminal.PadstackInstanceTerminal.Create, @@ -773,7 +785,10 @@ def create_circuit_port_on_net( neg_node_pins = self._pedb.core_components.get_pin_from_component(negative_component_name, negative_net_name) if port_name == "": port_name = "Port_{}_{}_{}_{}".format( - positive_component_name, positive_net_name, negative_component_name, negative_net_name + positive_component_name, + positive_net_name, + negative_component_name, + negative_net_name, ) circuit_port.name = port_name circuit_port.positive_node.component_node = pos_node_cmp @@ -841,7 +856,10 @@ def create_voltage_source_on_net( if source_name == "": source_name = "Vsource_{}_{}_{}_{}".format( - positive_component_name, positive_net_name, negative_component_name, negative_net_name + positive_component_name, + positive_net_name, + negative_component_name, + negative_net_name, ) voltage_source.name = source_name voltage_source.positive_node.component_node = pos_node_cmp @@ -909,7 +927,10 @@ def create_current_source_on_net( if source_name == "": source_name = "Port_{}_{}_{}_{}".format( - positive_component_name, positive_net_name, negative_component_name, negative_net_name + positive_component_name, + positive_net_name, + negative_component_name, + negative_net_name, ) current_source.name = source_name current_source.positive_node.component_node = pos_node_cmp @@ -972,7 +993,10 @@ def create_resistor_on_net( if resistor_name == "": resistor_name = "Port_{}_{}_{}_{}".format( - positive_component_name, positive_net_name, negative_component_name, negative_net_name + positive_component_name, + positive_net_name, + negative_component_name, + negative_net_name, ) resistor.name = resistor_name resistor.positive_node.component_node = pos_node_cmp @@ -1204,7 +1228,7 @@ def create_pin_group_terminal(self, source): self._active_layout, pos_node_net, pos_pingroup_term_name, - pos_pin_group[1], + pos_pin_group, False, ) time.sleep(0.5) @@ -1214,7 +1238,7 @@ def create_pin_group_terminal(self, source): self._active_layout, neg_node_net, neg_pingroup_term_name, - neg_pin_group[1], + neg_pin_group, False, ) @@ -1273,3 +1297,122 @@ def create_pin_group_terminal(self, source): else: pass return pos_pingroup_terminal.GetName() + + @pyaedt_function_handler() + def configure_siw_analysis_setup(self, simulation_setup=None): + """Configure Siwave analysis setup. + + Parameters + ---------- + simulation_setup : + Edb_DATA.SimulationConfiguration object. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + + if not isinstance(simulation_setup, SimulationConfiguration): + return False + simsetup_info = self._pedb.simsetupdata.SimSetupInfo[self._pedb.simsetupdata.SIwave.SIWSimulationSettings]() + simsetup_info.Name = simulation_setup.setup_name + simsetup_info.SimulationSettings.AdvancedSettings.PerformERC = False + simsetup_info.SimulationSettings.UseCustomSettings = True + if simulation_setup.include_inter_plane_coupling: + simsetup_info.SimulationSettings.AdvancedSettings.IncludeInterPlaneCoupling = ( + simulation_setup.include_inter_plane_coupling + ) + if abs(simulation_setup.xtalk_threshold): + simsetup_info.SimulationSettings.AdvancedSettings.XtalkThreshold = str(simulation_setup.xtalk_threshold) + if simulation_setup.min_void_area: + simsetup_info.SimulationSettings.AdvancedSettings.MinVoidArea = simulation_setup.min_void_area + if simulation_setup.min_pad_area_to_mesh: + simsetup_info.SimulationSettings.AdvancedSettings.MinPadAreaToMesh = simulation_setup.min_pad_area_to_mesh + if simulation_setup.min_plane_area_to_mesh: + simsetup_info.SimulationSettings.AdvancedSettings.MinPlaneAreaToMesh = ( + simulation_setup.min_plane_area_to_mesh + ) + if simulation_setup.snap_length_threshold: + simsetup_info.SimulationSettings.AdvancedSettings.SnapLengthThreshold = ( + simulation_setup.snap_length_threshold + ) + if simulation_setup.return_current_distribution: + simsetup_info.SimulationSettings.AdvancedSettings.ReturnCurrentDistribution = ( + simulation_setup.return_current_distribution + ) + if simulation_setup.ignore_non_functional_pads: + simsetup_info.SimulationSettings.AdvancedSettings.IgnoreNonFunctionalPads = ( + simulation_setup.ignore_non_functional_pads + ) + if simulation_setup.dc_min_plane_area_to_mesh: + simsetup_info.SimulationSettings.DCAdvancedSettings.DcMinPlaneAreaToMesh = ( + simulation_setup.dc_min_plane_area_to_mesh + ) + if simulation_setup.min_void_area: + simsetup_info.SimulationSettings.DCAdvancedSettings.DcMinVoidAreaToMesh = simulation_setup.min_void_area + if simulation_setup.max_init_mesh_edge_length: + simsetup_info.SimulationSettings.DCAdvancedSettings.MaxInitMeshEdgeLength = ( + simulation_setup.max_init_mesh_edge_length + ) + try: + sweep = self._pedb.simsetupdata.SweepData(simulation_setup.sweep_name) + sweep.IsDiscrete = False # need True for package?? + sweep.UseQ3DForDC = simulation_setup.use_q3d_for_dc + sweep.RelativeSError = simulation_setup.relative_error + sweep.InterpUsePortImpedance = False + sweep.EnforceCausality = (GeometryOperators.parse_dim_arg(simulation_setup.start_frequency) - 0) < 1e-9 + sweep.EnforcePassivity = simulation_setup.enforce_passivity + sweep.PassivityTolerance = simulation_setup.passivity_tolerance + if is_ironpython: + sweep.Frequencies.Clear() + else: + list(sweep.Frequencies).clear() + if simulation_setup.sweep_type == SweepType.LogCount: + self._setup_decade_count_sweep( + sweep, + simulation_setup.start_frequency, + simulation_setup.stop_freq, + simulation_setup.decade_count, + ) + else: + if is_ironpython: + sweep.Frequencies = self._pedb.simsetupdata.SweepData.SetFrequencies( + simulation_setup.start_frequency, + simulation_setup.stop_freq, + simulation_setup.step_freq, + ) + else: + sweep.Frequencies = convert_py_list_to_net_list( + self._pedb.simsetupdata.SweepData.SetFrequencies( + simulation_setup.start_frequency, + simulation_setup.stop_freq, + simulation_setup.step_freq, + ) + ) + if is_ironpython: + simsetup_info.SweepDataList.Add(sweep) + else: + simsetup_info.SweepDataList = convert_py_list_to_net_list([sweep]) + except Exception as err: + self._logger.error("Exception in sweep configuration: {0}".format(err)) + sim_setup = self._edb.Utility.SIWaveSimulationSetup(simsetup_info) + return self._cell.AddSimulationSetup(sim_setup) + + def _setup_decade_count_sweep(self, sweep, start_freq, stop_freq, decade_count): + import math + + start_f = GeometryOperators.parse_dim_arg(start_freq) + if start_f == 0.0: + start_f = 10 + self._logger.warning( + "Decade count sweep does not support a DC value. Defaulting starting frequency to 10Hz." + ) + + stop_f = GeometryOperators.parse_dim_arg(stop_freq) + decade_cnt = GeometryOperators.parse_dim_arg(decade_count) + freq = start_f + sweep.Frequencies.Add(str(freq)) + while freq < stop_f: + freq = freq * math.pow(10, 1.0 / decade_cnt) + sweep.Frequencies.Add(str(freq)) diff --git a/pyaedt/edb_core/stackup.py b/pyaedt/edb_core/stackup.py index e16d66d3676..d7843b7ddc2 100644 --- a/pyaedt/edb_core/stackup.py +++ b/pyaedt/edb_core/stackup.py @@ -13,6 +13,7 @@ from pyaedt.edb_core.general import convert_py_list_to_net_list from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import pyaedt_function_handler +from pyaedt.edb_core.EDB_Data import SimulationConfiguration try: from System import Double @@ -143,10 +144,12 @@ def create_dielectric(self, name, permittivity=1, loss_tangent=0): if self._edb.Definition.MaterialDef.FindByName(self._db, name).IsNull(): material_def = self._edb.Definition.MaterialDef.Create(self._db, name) material_def.SetProperty( - self._edb.Definition.MaterialPropertyId.Permittivity, self._get_edb_value(permittivity) + self._edb.Definition.MaterialPropertyId.Permittivity, + self._get_edb_value(permittivity), ) material_def.SetProperty( - self._edb.Definition.MaterialPropertyId.DielectricLossTangent, self._get_edb_value(loss_tangent) + self._edb.Definition.MaterialPropertyId.DielectricLossTangent, + self._get_edb_value(loss_tangent), ) return material_def return False @@ -837,3 +840,59 @@ def create_symmetric_stackup( new_layer_name, layer_name, fillMaterial="Air", thickness=outer_layer_thickness ) return True + + @pyaedt_function_handler() + def set_etching_layers(self, simulation_setup=None): + """Set the etching layer parameters for a layout stackup. + + Parameters + ---------- + simulation_setup : EDB_DATA_SimulationConfiguration object + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + if not isinstance(simulation_setup, SimulationConfiguration): + return False + cell = self._builder.cell + this_lc = self._edb.Cell.LayerCollection(cell.GetLayout().GetLayerCollection()) + all_layers = list(this_lc.Layers(self._edb.Cell.LayerTypeSet.AllLayerSet)) + + signal_layers = [lay for lay in all_layers if lay.GetLayerType() == self._edb.Cell.LayerType.SignalLayer] + + new_layers = list(all_layers.Where(lambda lyr: lyr.GetLayerType() != self._edb.Cell.LayerType.SignalLayer)) + + if simulation_setup.signal_layer_etching_instances: + for layer in signal_layers: + if not layer.GetName() in simulation_setup.signal_layer_etching_instances: + self._logger.error( + "Signal layer {0} is not found in the etching layers specified in the cfg, " + "skipping the etching factor assignment".format(layer.GetName()) + ) + new_signal_lay = layer.Clone() + else: + new_signal_lay = layer.Clone() + new_signal_lay.SetEtchFactorEnabled(True) + etching_factor = float( + simulation_setup.etching_factor_instances[ + simulation_setup.signal_layer_etching_instances.index(layer.GetName()) + ] + ) + new_signal_lay.SetEtchFactor(etching_factor) + self._logger.info( + "Setting etching factor {0} on layer {1}".format(str(etching_factor), layer.GetName()) + ) + + new_layers.Add(new_signal_lay) + + layers_with_etching = self._edb.Cell.LayerCollection() + if not layers_with_etching.AddLayers(new_layers): + return False + + if not cell.GetLayout().SetLayerCollection(layers_with_etching): + return False + + return True + return True diff --git a/pyaedt/generic/constants.py b/pyaedt/generic/constants.py index 8fc5f16bd86..e661a323cf7 100644 --- a/pyaedt/generic/constants.py +++ b/pyaedt/generic/constants.py @@ -128,7 +128,7 @@ def _resolve_unit_system(unit_system_1, unit_system_2, operation): def unit_converter(value, unit_system="Length", input_units="meter", output_units="mm"): - """Convert Unit in specified Unit System. + """Convert unit in specified unit system. Parameters ---------- @@ -507,6 +507,28 @@ class FlipChipOrientation(object): (Up, Down) = range(0, 2) +class SolverType(object): + """Provides solver type classes.""" + + (Hfss, Siwave, Q3D, Maxwell, Nexxim, TwinBuilder, Hfss3dLayout) = range(0, 7) + + +class CutoutSubdesignType(object): + (Conformal, BoundingBox) = range(0, 2) + + +class RadiationBoxType(object): + (Conformal, BoundingBox, ConvexHull) = range(0, 3) + + +class SweepType(object): + (Linear, LogCount) = range(0, 2) + + +class BasisOrder(object): + (Mixed, Zero, single, Double) = range(0, 4) + + class SourceType(object): """Type of excitation enumerator.""" diff --git a/pyaedt/third_party/ironpython/plumbum/cli/i18n.py b/pyaedt/third_party/ironpython/plumbum/cli/i18n.py index 9098aa69762..43e488935de 100644 --- a/pyaedt/third_party/ironpython/plumbum/cli/i18n.py +++ b/pyaedt/third_party/ironpython/plumbum/cli/i18n.py @@ -18,7 +18,6 @@ def ngettext(self, str1, strN, n): def get_translation_for(package_name): return NullTranslation() - else: import gettext import os diff --git a/pyaedt/third_party/ironpython/plumbum/fs/atomic.py b/pyaedt/third_party/ironpython/plumbum/fs/atomic.py index 8bbdbe5db80..9a5753aa3ab 100644 --- a/pyaedt/third_party/ironpython/plumbum/fs/atomic.py +++ b/pyaedt/third_party/ironpython/plumbum/fs/atomic.py @@ -51,7 +51,6 @@ def locked_file(fileno, blocking=True): finally: UnlockFile(hndl, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF) - else: if hasattr(fcntl, "lockf"): diff --git a/pyaedt/third_party/ironpython/rpyc_27/lib/compat.py b/pyaedt/third_party/ironpython/rpyc_27/lib/compat.py index 515562ca933..dc7d8630e59 100644 --- a/pyaedt/third_party/ironpython/rpyc_27/lib/compat.py +++ b/pyaedt/third_party/ironpython/rpyc_27/lib/compat.py @@ -79,8 +79,7 @@ def callable(obj): select_module = None def select(*args): - raise ImportError("select not supported on this platform") - + raise ImportError("Select is not supported on this platform.") else: # jython @@ -207,7 +206,6 @@ def acquire_lock(lock, blocking, timeout): return lock.acquire(blocking, timeout.timeleft()) return lock.acquire(blocking) - else: def acquire_lock(lock, blocking, timeout): diff --git a/requirements_docs.txt b/requirements_docs.txt index bcbcae249c5..d3d486097a6 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -4,9 +4,9 @@ imageio>=2.5.0 imageio-ffmpeg pyvista<=0.33.3 numpy -Sphinx==4.0.3 +numpydoc +Sphinx==4.5.0 sphinx-autobuild -https://github.com/numpy/numpydoc/archive/main.zip # need latest sphinxcontrib-websupport pytest-sphinx sphinx-notfound-page>=0.3.0