diff --git a/doc/source/_static/parametrized_design.png b/doc/source/_static/parametrized_design.png new file mode 100644 index 0000000000..80bcb84b1a Binary files /dev/null and b/doc/source/_static/parametrized_design.png differ diff --git a/examples/legacy/pyaedt_integration/14_edb_create_parametrized_design.py b/examples/legacy/pyaedt_integration/14_edb_create_parametrized_design.py new file mode 100644 index 0000000000..aad5aa4ab5 --- /dev/null +++ b/examples/legacy/pyaedt_integration/14_edb_create_parametrized_design.py @@ -0,0 +1,71 @@ +""" +EDB: parameterized design +------------------------ +This example shows how to +1, Create an HFSS simulation project using SimulationConfiguration class. +2, Create automatically parametrized design. +""" +###################################################################### +# +# Final expected project +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# .. image:: ../../_static/parametrized_design.png +# :width: 600 +# :alt: Fully automated parametrization. +###################################################################### + +###################################################################### +# Create HFSS simulatio project +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Load an existing EDB folder. +###################################################################### + +from pyaedt import Hfss3dLayout + +import pyedb +from pyedb.generic.general_methods import generate_unique_folder_name +from pyedb.legacy.downloads import download_file + +project_path = generate_unique_folder_name() +target_aedb = download_file("edb/ANSYS-HSD_V1.aedb", destination=project_path) +print("Project folder will be", target_aedb) + +aedt_version = "2023.2" +edb = pyedb.Edb(edbpath=target_aedb, edbversion=aedt_version) +print("EDB is located at {}".format(target_aedb)) + +######################################################################## +# Create SimulationConfiguration object and define simulation parameters +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +simulation_configuration = edb.new_simulation_configuration() +simulation_configuration.signal_nets = ["PCIe_Gen4_RX0_P", "PCIe_Gen4_RX0_N", "PCIe_Gen4_RX1_P", "PCIe_Gen4_RX1_N"] +simulation_configuration.power_nets = ["GND"] +simulation_configuration.components = ["X1", "U1"] +simulation_configuration.do_cutout_subdesign = True +simulation_configuration.start_freq = "OGHz" +simulation_configuration.stop_freq = "20GHz" +simulation_configuration.step_freq = "10MHz" + +########################## +# Build simulation project +# ~~~~~~~~~~~~~~~~~~~~~~~~ + +edb.build_simulation_project(simulation_configuration) + +############################# +# Generated design parameters +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + +edb.auto_parametrize_design(layers=True, materials=True, via_holes=True, pads=True, antipads=True, traces=True) +edb.save_edb() +edb.close_edb() + +###################### +# Open project in AEDT +# ~~~~~~~~~~~~~~~~~~~~ + +hfss = Hfss3dLayout(projectname=target_aedb, specified_version=aedt_version) +hfss.release_desktop(False, False) diff --git a/src/pyedb/legacy/edb.py b/src/pyedb/legacy/edb.py index 0cdfb3edd0..eb1ef1b702 100644 --- a/src/pyedb/legacy/edb.py +++ b/src/pyedb/legacy/edb.py @@ -4012,3 +4012,199 @@ def get_point_terminal(self, name, net_name, location, layer): point_terminal = PointTerminal(self) return point_terminal.create(name, net_name, location, layer) + + @pyedb_function_handler + def auto_parametrize_design( + self, + layers=True, + materials=True, + via_holes=True, + pads=True, + antipads=True, + traces=True, + layer_filter=None, + material_filter=None, + padstack_definition_filter=None, + trace_net_filter=None, + ): + """Assign automatically design and project variables with current values. + Parameters + ---------- + layers : bool, optional + ``True`` enable layer thickness parametrization. Default value is ``True``. + materials : bool, optional + ``True`` enable material parametrization. Default value is ``True``. + via_holes : bool, optional + ``True`` enable via diameter parametrization. Default value is ``True``. + pads : bool, optional + ``True`` enable pads size parametrization. Default value is ``True``. + antipads : bool, optional + ``True`` enable anti pads size parametrization. Default value is ``True``. + traces : bool, optional + ``True`` enable trace width parametrization. Default value is ``True``. + layer_filter : str, List(str), optional + Enable layer filter. Default value is ``None``, all layers are parametrized. + material_filter : str, List(str), optional + Enable material filter. Default value is ``None``, all material are parametrized. + padstack_definition_filter : str, List(str), optional + Enable padstack definition filter. Default value is ``None``, all padsatcks are parametrized. + trace_net_filter : str, List(str), optional + Enable nets filter for trace width parametrization. Default value is ``None``, all layers are + parametrized. + Returns + ------- + List(str) + List of all parameters name created. + """ + parameters = [] + if layers: + if not layer_filter: + _layers = self.stackup.stackup_layers + else: + if isinstance(layer_filter, str): + layer_filter = [layer_filter] + _layers = {k: v for k, v in self.stackup.stackup_layers.items() if k in layer_filter} + for layer_name, layer in _layers.items(): + thickness_variable = "${}_thick".format(layer_name) + self._clean_string_for_variable_name(thickness_variable) + if thickness_variable not in self.variables: + self.add_design_variable(thickness_variable, layer.thickness) + layer.thickness = thickness_variable + parameters.append(thickness_variable) + if materials: + if not material_filter: + _materials = self.materials.materials + else: + _materials = {k: v for k, v in self.materials.materials.items() if k in material_filter} + for mat_name, material in _materials.items(): + if material.conductivity < 1e4: + epsr_variable = "$epsr_{}".format(mat_name) + self._clean_string_for_variable_name(epsr_variable) + if epsr_variable not in self.variables: + self.add_design_variable(epsr_variable, material.permittivity) + material.permittivity = epsr_variable + parameters.append(epsr_variable) + loss_tg_variable = "$loss_tangent_{}".format(mat_name) + self._clean_string_for_variable_name(loss_tg_variable) + if not loss_tg_variable in self.variables: + self.add_design_variable(loss_tg_variable, material.loss_tangent) + material.loss_tangent = loss_tg_variable + parameters.append(loss_tg_variable) + else: + sigma_variable = "$sigma_{}".format(mat_name) + self._clean_string_for_variable_name(sigma_variable) + if not sigma_variable in self.variables: + self.add_design_variable(sigma_variable, material.conductivity) + material.conductivity = sigma_variable + parameters.append(sigma_variable) + if traces: + if not trace_net_filter: + paths = self.modeler.paths + else: + paths = [path for path in self.modeler.paths if path.net_name in trace_net_filter] + for path in paths: + trace_width_variable = "trace_w_{}_{}".format(path.net_name, path.id) + self._clean_string_for_variable_name(trace_width_variable) + if trace_width_variable not in self.variables: + self.add_design_variable(trace_width_variable, path.width) + path.width = trace_width_variable + parameters.append(trace_width_variable) + if not padstack_definition_filter: + used_padsatck_defs = list( + set([padstack_inst.padstack_definition for padstack_inst in list(self.padstacks.instances.values())]) + ) + padstack_defs = {k: v for k, v in self.padstacks.definitions.items() if k in used_padsatck_defs} + else: + padstack_defs = {k: v for k, v in self.padstacks.definitions.items() if k in padstack_definition_filter} + for def_name, padstack_def in padstack_defs.items(): + if not padstack_def.via_start_layer == padstack_def.via_stop_layer: + if via_holes: # pragma no cover + hole_variable = self._clean_string_for_variable_name("$hole_diam_{}".format(def_name)) + if hole_variable not in self.variables: + self.add_design_variable(hole_variable, padstack_def.hole_properties[0]) + padstack_def.hole_properties = hole_variable + parameters.append(hole_variable) + if pads: + for layer, pad in padstack_def.pad_by_layer.items(): + if pad.geometry_type == 1: + pad_diameter_variable = self._clean_string_for_variable_name( + "$pad_diam_{}_{}".format(def_name, layer) + ) + if pad_diameter_variable not in self.variables: + self.add_design_variable(pad_diameter_variable, pad.parameters_values[0]) + pad.parameters = {"Diameter": pad_diameter_variable} + parameters.append(pad_diameter_variable) + if pad.geometry_type == 2: # pragma no cover + pad_size_variable = self._clean_string_for_variable_name( + "$pad_size_{}_{}".format(def_name, layer) + ) + if pad_size_variable not in self.variables: + self.add_design_variable(pad_size_variable, pad.parameters_values[0]) + pad.parameters = {"Size": pad_size_variable} + parameters.append(pad_size_variable) + elif pad.geometry_type == 3: # pragma no cover + pad_size_variable_x = self._clean_string_for_variable_name( + "$pad_size_x_{}_{}".format(def_name, layer) + ) + pad_size_variable_y = self._clean_string_for_variable_name( + "$pad_size_y_{}_{}".format(def_name, layer) + ) + if pad_size_variable_x not in self.variables and pad_size_variable_y not in self.variables: + self.add_design_variable(pad_size_variable_x, pad.parameters_values[0]) + self.add_design_variable(pad_size_variable_y, pad.parameters_values[1]) + pad.parameters = {"XSize": pad_size_variable_x, "YSize": pad_size_variable_y} + parameters.append(pad_size_variable_x) + parameters.append(pad_size_variable_y) + if antipads: + for layer, antipad in padstack_def.antipad_by_layer.items(): + if antipad.geometry_type == 1: # pragma no cover + antipad_diameter_variable = self._clean_string_for_variable_name( + "$antipad_diam_{}_{}".format(def_name, layer) + ) + if antipad_diameter_variable not in self.variables: # pragma no cover + self.add_design_variable(antipad_diameter_variable, antipad.parameters_values[0]) + antipad.parameters = {"Diameter": antipad_diameter_variable} + parameters.append(antipad_diameter_variable) + if antipad.geometry_type == 2: # pragma no cover + antipad_size_variable = self._clean_string_for_variable_name( + "$antipad_size_{}_{}".format(def_name, layer) + ) + if antipad_size_variable not in self.variables: # pragma no cover + self.add_design_variable(antipad_size_variable, antipad.parameters_values[0]) + antipad.parameters = {"Size": antipad_size_variable} + parameters.append(antipad_size_variable) + elif antipad.geometry_type == 3: # pragma no cover + antipad_size_variable_x = self._clean_string_for_variable_name( + "$antipad_size_x_{}_{}".format(def_name, layer) + ) + antipad_size_variable_y = self._clean_string_for_variable_name( + "$antipad_size_y_{}_{}".format(def_name, layer) + ) + if ( + antipad_size_variable_x not in self.variables + and antipad_size_variable_y not in self.variables + ): # pragma no cover + self.add_design_variable(antipad_size_variable_x, antipad.parameters_values[0]) + self.add_design_variable(antipad_size_variable_y, antipad.parameters_values[1]) + antipad.parameters = {"XSize": antipad_size_variable_x, "YSize": antipad_size_variable_y} + parameters.append(antipad_size_variable_x) + parameters.append(antipad_size_variable_y) + return parameters + + @pyedb_function_handler + def _clean_string_for_variable_name(self, variable_name): + """Remove forbidden character for variable name. + Parameter + ---------- + variable_name : str + Variable name. + Returns + ------- + str + Edited name. + """ + if "-" in variable_name: + variable_name = variable_name.replace("-", "_") + if "+" in variable_name: + variable_name = variable_name.replace("+", "p") + return variable_name diff --git a/tests/legacy/system/test_edb_nets.py b/tests/legacy/system/test_edb_nets.py index ec78945e32..67512c2eff 100644 --- a/tests/legacy/system/test_edb_nets.py +++ b/tests/legacy/system/test_edb_nets.py @@ -128,3 +128,65 @@ def test_nets_merge_polygon(self): self.local_scratch.copyfolder(source_path, target_path) edbapp = EdbLegacy(target_path, desktop_version) assert edbapp.nets.merge_nets_polygons(["net1", "net2"]) + edbapp.close_edb() + + def test_layout_auto_parametrization(self): + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_auto_parameters", "test.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = EdbLegacy(target_path, desktop_version) + edbapp.auto_parametrize_design( + layers=True, + layer_filter="1_Top", + materials=False, + via_holes=False, + pads=False, + antipads=False, + traces=False, + ) + assert "$1_Top_thick" in edbapp.variables + edbapp.auto_parametrize_design( + layers=True, materials=False, via_holes=False, pads=False, antipads=False, traces=False + ) + assert len(list(edbapp.variables.keys())) == len(list(edbapp.stackup.stackup_layers.keys())) + edbapp.auto_parametrize_design( + layers=False, + materials=True, + via_holes=False, + pads=False, + antipads=False, + traces=False, + material_filter=["copper"], + ) + assert "$sigma_copper" in edbapp.variables + edbapp.auto_parametrize_design( + layers=False, materials=True, via_holes=False, pads=False, antipads=False, traces=False + ) + assert len(list(edbapp.variables.values())) == 26 + edbapp.auto_parametrize_design( + layers=False, materials=False, via_holes=True, pads=False, antipads=False, traces=False + ) + assert len(list(edbapp.variables.values())) == 65 + edbapp.auto_parametrize_design( + layers=False, materials=False, via_holes=False, pads=True, antipads=False, traces=False + ) + assert len(list(edbapp.variables.values())) == 469 + edbapp.auto_parametrize_design( + layers=False, materials=False, via_holes=False, pads=False, antipads=True, traces=False + ) + assert len(list(edbapp.variables.values())) == 469 + edbapp.auto_parametrize_design( + layers=False, + materials=False, + via_holes=False, + pads=False, + antipads=False, + traces=True, + trace_net_filter=["SFPA_Tx_Fault", "SFPA_Tx_Disable", "SFPA_SDA", "SFPA_SCL", "SFPA_Rx_LOS"], + ) + assert len(list(edbapp.variables.keys())) == 474 + edbapp.auto_parametrize_design( + layers=False, materials=False, via_holes=False, pads=False, antipads=False, traces=True + ) + assert len(list(edbapp.variables.values())) == 2308 + edbapp.close_edb()