diff --git a/_unittest_solvers/test_45_workflows.py b/_unittest_solvers/test_45_workflows.py index e6c5bf61f10..355c99c6cb7 100644 --- a/_unittest_solvers/test_45_workflows.py +++ b/_unittest_solvers/test_45_workflows.py @@ -162,7 +162,33 @@ def test_08_configure_a3d(self, local_scratch): local_scratch.copyfolder(os.path.join(solver_local_path, "example_models", "T45", "ANSYS-HSD_V1.aedb"), file_path) - assert main({"is_test": True, "aedb_path": file_path, "configuration_path": configuration_path}) + main(is_test=True, execute={ + "aedt_load": [ + {"project_file": file_path, + "file_cfg_path": configuration_path, + "file_save_path": file_path.replace(".aedb", "_1.aedt")} + ], + "aedt_export": [ + {"project_file": file_path, + "file_path_save": configuration_path.replace(".json", "_1.json")} + ], + "active_load": [], + "active_export": [], + "siwave_load": [], + "siwave_export": [], + }) + + main(is_test=True, execute={ + "aedt_load": [], + "aedt_export": [], + "active_load": [{"project_file": file_path, + "file_cfg_path": configuration_path, + "file_save_path": file_path.replace(".aedb", "_1.aedt")}], + "active_export": [{"project_file": file_path, + "file_path_save": configuration_path.replace(".json", "_1.json")}], + "siwave_load": [], + "siwave_export": [], + }) def test_08_advanced_fields_calculator_non_general(self, add_app): aedtapp = add_app(application=ansys.aedt.core.Hfss, @@ -179,13 +205,13 @@ def test_08_advanced_fields_calculator_non_general(self, add_app): "assignment": "", "assignment_type": ["Line"], "operations": ["Fundamental_Quantity('E')", - "Operation('Real')", - "Operation('Tangent')", - "Operation('Dot')", - "EnterLine('assignment')", - "Operation('LineValue')", - "Operation('Integrate')", - "Operation('CmplxR')"], + "Operation('Real')", + "Operation('Tangent')", + "Operation('Dot')", + "EnterLine('assignment')", + "Operation('LineValue')", + "Operation('Integrate')", + "Operation('CmplxR')"], "report": ["Data Table", "Rectangular Plot"], } @@ -383,4 +409,3 @@ def test_15_import_asc(self, local_scratch, add_app): from ansys.aedt.core.workflows.circuit.import_schematic import main assert main({"is_test": True, "asc_file": file_path}) aedtapp.close_project() - diff --git a/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst b/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst index 0874b239676..d8317179049 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst @@ -1,28 +1,31 @@ Configure layout EDB ==================== -Single configuration file to set up layout for any kind of PCB & package analysis. +------------ +Introduction +------------ +This extension provides the capability of -The following image shows the extension user interface: - -.. image:: ../../../_static/extensions/configure_edb.png - :width: 800 - :alt: Configure Layout UI - - -The available arguments are: ``aedb_path``, ``configuration_path``. -User can pass as an argument a configuration file (a json formatted file or a toml file), or a folder containing more -than N configuration files. In such case the script creates N new aedb projects, each one with corresponding -setting file applied. +- Apply simulation configuration to HFSS 3D Layout design or SIwave project. +- Export simulation configuration as a text file from HFSS 3D Layout design or SIwave project. +The simulation configuration file is a text file in json or toml format. It contains information like layer stackup, +materials, components, HFSS/SIwave setups, etc. This configure file can be used to set up PCB for DCIR, signal +integrity as well as power integrity analysis. .. image:: ../../../_static/extensions/configure_edb_way_of_work.png :width: 800 :alt: Principle of working of Layout UI +Please refer to EDB Configuration `User Guide`_ for details + +.. _User Guide: https://edb.docs.pyansys.com/version/stable/examples/use_configuration/index.html + +-------------------------------------------------------------------------- +A brief description of which options are defined in the configuration file +-------------------------------------------------------------------------- -A brief description of which options are defined in the configuration file: .. image:: ../../../_static/extensions/edb_config_setup.png :width: 800 @@ -38,3 +41,46 @@ configuration setup and re-use it with or without modifications as many times as The value of this format and toolkit, lies in the fact that it is totally reusable, it is really user-friendly, even with users that are not familiar with scripting. It supports most of the options that the UI also supports (not only the ones explained above, but many additional), and it has the advantage of obtaining the initial configuration file from the design, by using its export property. + +---------- +How to use +---------- + +.. image:: ../../../_static/extensions/configure_edb.png + :width: 800 + :alt: Configure Layout UI + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configure HFSS 3D Layout design in active AEDT project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1, Select ``Active Design`` in GUI. + +2, Make sure the HFSS 3D Layout design is open and active in AEDT. + +3, Click ``Select and Apply Configuration`` and browse to your configuration files. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configure HFSS 3D Layout design in a AEDT project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1, Select ``HFSS 3D Layout`` in GUI. + +2, Click ``Select Project File`` and browse to .aedt file. + +3, Click ``Select and Apply Configuration`` and browse to your configuration files. + +4, In the second pop-up window. Specify where to save the new project. + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configure design in siwave project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1, Select ``SIwave`` in GUI. + +2, Click ``Select Project File`` and browse to .siw file. + +3, Click ``Select and Apply Configuration`` and browse to your configuration files. + +4, In the second pop-up window. Specify where to save the new project. \ No newline at end of file diff --git a/doc/source/_static/extensions/configure_edb.png b/doc/source/_static/extensions/configure_edb.png index c233b528409..8dfdf2d83e9 100644 Binary files a/doc/source/_static/extensions/configure_edb.png and b/doc/source/_static/extensions/configure_edb.png differ diff --git a/src/ansys/aedt/core/workflows/project/configure_edb.py b/src/ansys/aedt/core/workflows/project/configure_edb.py index a4a32493b60..b45dcec1fa6 100644 --- a/src/ansys/aedt/core/workflows/project/configure_edb.py +++ b/src/ansys/aedt/core/workflows/project/configure_edb.py @@ -21,166 +21,389 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os.path - +import os +from pathlib import Path +import tkinter as tk +from tkinter import filedialog +from tkinter import messagebox +from tkinter import ttk + +import PIL.Image +import PIL.ImageTk import ansys.aedt.core from ansys.aedt.core import Hfss3dLayout from ansys.aedt.core import generate_unique_name -from ansys.aedt.core.generic.filesystem import search_files -import ansys.aedt.core.workflows +import ansys.aedt.core.workflows.hfss3dlayout from ansys.aedt.core.workflows.misc import get_aedt_version -from ansys.aedt.core.workflows.misc import get_arguments from ansys.aedt.core.workflows.misc import get_port from ansys.aedt.core.workflows.misc import get_process_id from ansys.aedt.core.workflows.misc import is_student from pyedb import Edb +from pyedb import Siwave port = get_port() version = get_aedt_version() aedt_process_id = get_process_id() is_student = is_student() -# Extension batch arguments -extension_arguments = {"aedb_path": "", "configuration_path": ""} -extension_description = "Configure project from aedb file" - - -def frontend(): # pragma: no cover - - import tkinter - from tkinter import filedialog - from tkinter import ttk - - import PIL.Image - import PIL.ImageTk - - master = tkinter.Tk() - master.geometry("750x250") - - master.title(extension_description) - - # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") - im = PIL.Image.open(icon_path) - photo = PIL.ImageTk.PhotoImage(im) +class ConfigureEdbFrontend(tk.Tk): # pragma: no cover + app_options = ["Active Design", "HFSS 3D Layout", "SIwave"] + _execute = { + "active_load": [], + "siwave_load": [], + "aedt_load": [], + "active_export": [], + "siwave_export": [], + "aedt_export": [], + } - # Set the icon for the main window - master.iconphoto(True, photo) + def get_active_project_info(self): + desktop = self.desktop + active_project = desktop.active_project() + if active_project: + project_name = active_project.GetName() + project_dir = active_project.GetPath() + project_file = os.path.join(project_dir, project_name + ".aedt") + desktop.release_desktop(False, False) + return project_file + else: + desktop.release_desktop(False, False) + return + + @property + def desktop(self): + return ansys.aedt.core.Desktop( + new_desktop_session=False, + specified_version=version, + port=port, + aedt_process_id=aedt_process_id, + student_version=is_student, + ) - # Configure style for ttk buttons - style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + def __init__(self): + super().__init__() + + self.geometry("600x300") + self.title("EDB Configuration 2.0") + + self.status = tk.StringVar(value="") + self.selected_app_option = tk.StringVar(value="Active Design") + self.selected_project_file_path = "" + self.selected_project_file = tk.StringVar(value="") + self.selected_cfg_file_folder = tk.StringVar(value="") + + # Load the logo for the main window + icon_path = os.path.join(os.path.dirname(ansys.aedt.core.workflows.__file__), "images", "large", "logo.png") + im = PIL.Image.open(icon_path) + photo = PIL.ImageTk.PhotoImage(im) + + # Set the icon for the main window + self.iconphoto(True, photo) + + # Configure style for ttk buttons + style = ttk.Style() + style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + + # Main window + self.create_main_window() + + def create_main_window(self): + col_width = [50, 20, 30] + + # Section 1 + self.label_version = ttk.Label(self, text=f"AEDT {version}") + self.label_version.grid(row=0, column=0) + label = ttk.Label(self, textvariable=self.status) + label.grid(row=0, column=1) + + # Section 2 + s2_start_row = 1 + for index, option in enumerate(self.app_options): + ttk.Radiobutton(self, text=option, value=option, variable=self.selected_app_option).grid( + row=index + s2_start_row, column=0, sticky=tk.W + ) + + # Section 3 + s3_start_row = 4 + button_select_project_file = ttk.Button( + self, text="Select project file", width=col_width[0], command=self.call_select_project + ) + button_select_project_file.grid(row=s3_start_row, column=0) - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file:") - label2.grid(row=0, column=0, pady=10) - text = tkinter.Text(master, width=40, height=1) - text.grid(row=0, column=1, pady=10, padx=5) + # Siwave + label_project_file = tk.Label(self, width=col_width[2], height=1, textvariable=self.selected_project_file) + label_project_file.grid(row=s3_start_row, column=2) - def browse_aedb(): - filename = filedialog.askopenfilename( - initialdir="/", - title="Select a layout folder or file", - filetypes=(("aedb file", "*.def"), ("brd", "*.brd"), ("all files", "*.*")), + # Apply cfg + button = ttk.Button( + self, text="Select and Apply Configuration", width=col_width[0], command=self.call_apply_cfg_file ) - if filename.endswith(".def"): - filename = os.path.dirname(filename) - text.insert(tkinter.END, filename) - - b1 = tkinter.Button(master, text="...", width=10, command=browse_aedb) - b1.grid(row=0, column=2, pady=10) - - var3 = tkinter.StringVar() - label3 = tkinter.Label(master, textvariable=var3) - var3.set("Browse configuration file or folder:") - label3.grid(row=1, column=0, pady=10) - text2 = tkinter.Text(master, width=40, height=1) - text2.grid(row=1, column=1, pady=10, padx=5) - - def browse_config(): - inital_dir = text.get("1.0", tkinter.END).strip() - filename = filedialog.askopenfilename( - initialdir=os.path.dirname(inital_dir) if inital_dir else "/", - title="Select configuration file", - filetypes=(("Configuration file", "*.json"), ("Configuration file", "*.toml")), + button.grid(row=s3_start_row + 2, column=0) + + # Export cfg + button = ttk.Button(self, text="Export Configuration", width=col_width[0], command=self.call_export_cfg) + button.grid(row=s3_start_row + 4, column=0) + + def call_select_project(self): + if self.selected_app_option.get() == "HFSS 3D Layout": + file_path = filedialog.askopenfilename( + initialdir="/", + title="Select File", + filetypes=(("Electronics Desktop", "*.aedt"),), + ) + elif self.selected_app_option.get() == "SIwave": + file_path = filedialog.askopenfilename( + initialdir="/", + title="Select File", + filetypes=(("SIwave project", "*.siw"),), + ) + else: + file_path = None + + if not file_path: + return + else: + self.selected_project_file_path = file_path + self.selected_project_file.set(Path(file_path).parts[-1]) + + def _call_apply_cfg_file(self): + if not self.selected_app_option.get() == "Active Design": + if not self.selected_project_file_path: + return + + init_dir = Path(self.selected_project_file_path).parent if self.selected_project_file_path else "/" + file_cfg_path = filedialog.askopenfilename( + initialdir=init_dir, + title="Select Configuration File", + filetypes=(("json file", "*.json"), ("toml", "*.toml")), + defaultextension=".json", ) - text2.insert(tkinter.END, filename) - b2 = tkinter.Button(master, text="...", width=10, command=browse_config) - b2.grid(row=1, column=2, pady=10) + if not file_cfg_path: + return + + if self.selected_app_option.get() == "SIwave": + project_file = self.selected_project_file_path + file_save_path = filedialog.asksaveasfilename( + initialdir=init_dir, + title="Save new project as", + filetypes=[("SIwave", "*.siw")], + defaultextension=".siw", + ) + if not file_save_path: + return + self._execute["siwave_load"].append( + {"project_file": project_file, "file_cfg_path": file_cfg_path, "file_save_path": file_save_path} + ) + # self.execute_load_cfg_siw(project_file, file_cfg_path, file_save_path) + elif self.selected_app_option.get() == "HFSS 3D Layout": + project_file = self.selected_project_file_path + file_save_path = filedialog.asksaveasfilename( + initialdir=init_dir, + title="Save new project as", + filetypes=[("Electronics Desktop", "*.aedt")], + defaultextension=".aedt", + ) + self._execute["aedt_load"].append( + {"project_file": project_file, "file_cfg_path": file_cfg_path, "file_save_path": file_save_path} + ) + # self.execute_load_cfg_aedt(project_file, file_cfg_path, file_save_path) + else: + data = self.get_active_project_info() + if data: + project_dir, project_file = data + else: + return + file_save_path = os.path.join( + project_dir, Path(project_file).stem + "_" + generate_unique_name(Path(file_cfg_path).stem) + ".aedt" + ) + self._execute["active_load"].append( + {"project_file": project_file, "file_cfg_path": file_cfg_path, "file_save_path": file_save_path} + ) + self.execute() + + def call_select_cfg_folder(self): + pass + + def call_apply_cfg_file(self): + init_dir = Path(self.selected_project_file_path).parent if self.selected_project_file_path else "/" + # Get original project file path + if self.selected_app_option.get() == "Active Design": + project_file = self.get_active_project_info() + if not project_file: + return + else: + project_file = self.selected_project_file_path + + # Get cfg files + cfg_files = filedialog.askopenfilenames( + initialdir=init_dir, + title="Select Configuration", + filetypes=(("json file", "*.json"), ("toml", "*.toml")), + defaultextension=".json", + ) + if not cfg_files: + return - def callback(): - master.aedb_path_ui = text.get("1.0", tkinter.END).strip() - master.configuration_path_ui = text2.get("1.0", tkinter.END).strip() - master.destroy() + file_save_dir = filedialog.askdirectory( + initialdir=init_dir, + title="Save new projects to", + ) - b3 = tkinter.Button(master, text="Ok", width=40, command=callback) - b3.grid(row=25, column=1, pady=10, padx=10) + for file_cfg_path in cfg_files: + fname = Path(project_file).stem + "_" + generate_unique_name(Path(file_cfg_path).stem) + if file_cfg_path.endswith(".json") or file_cfg_path.endswith(".toml"): + if self.selected_app_option.get() == "SIwave": + file_save_path = os.path.join(Path(file_save_dir), fname + ".siw") + self._execute["siwave_load"].append( + {"project_file": project_file, "file_cfg_path": file_cfg_path, "file_save_path": file_save_path} + ) + elif self.selected_app_option.get() == "HFSS 3D Layout": + file_save_path = os.path.join(Path(file_save_dir), fname + ".aedt") + self._execute["aedt_load"].append( + {"project_file": project_file, "file_cfg_path": file_cfg_path, "file_save_path": file_save_path} + ) + else: + file_save_path = os.path.join(file_save_dir, fname + ".aedt") + self._execute["active_load"].append( + {"project_file": project_file, "file_cfg_path": file_cfg_path, "file_save_path": file_save_path} + ) + self.execute_load() + + def call_export_cfg(self): + """Export configuration file.""" + init_dir = Path(self.selected_project_file_path).parent + file_path_save = filedialog.asksaveasfilename( + initialdir=init_dir, + title="Select Configuration File", + filetypes=(("json file", "*.json"), ("toml", "*.toml")), + defaultextension=".json", + ) + if not file_path_save: + return + + if self.selected_app_option.get() == "SIwave": + self._execute["siwave_export"].append( + {"project_file": self.selected_project_file_path, "file_path_save": file_path_save} + ) + elif self.selected_app_option.get() == "HFSS 3D Layout": + self._execute["aedt_export"].append( + {"project_file": self.selected_project_file_path, "file_path_save": file_path_save} + ) + elif self.selected_app_option.get() == "Active Design": + data = self.get_active_project_info() + if data: + project_file = data + self._execute["active_export"].append({"project_file": project_file, "file_path_save": file_path_save}) + else: + return + + self.execute_export(file_path_save) + + def execute(self): + ConfigureEdbBackend(self._execute) + self._execute = { + "active_load": [], + "siwave_load": [], + "aedt_load": [], + "active_export": [], + "siwave_export": [], + "aedt_export": [], + } + + def execute_load(self): + desktop = self.desktop + self.execute() + desktop.release_desktop(False, False) + messagebox.showinfo("Information", "Done!") + + def execute_export(self, file_path): + ConfigureEdbBackend(self._execute) + self._execute = { + "active_load": [], + "siwave_load": [], + "aedt_load": [], + "active_export": [], + "siwave_export": [], + "aedt_export": [], + } + messagebox.showinfo("Information", f"Configuration file saved to {file_path}.") + + +class ConfigureEdbBackend: + def __init__(self, args, is_test=False): + if len(args["siwave_load"]): # pragma: no cover + for i in args["siwave_load"]: + self.execute_load_cfg_siw(**i) + + if len(args["aedt_load"]): + for i in args["aedt_load"]: + self.execute_load_cfg_aedt(**i) + + if len(args["active_load"]): + for i in args["active_load"]: + self.execute_load_cfg_aedt(**i) + + if len(args["siwave_export"]): # pragma: no cover + for i in args["siwave_export"]: + self.execute_export_cfg_siw(**i) + + if len(args["aedt_export"]): + for i in args["aedt_export"]: + self.execute_export_cfg_aedt(**i) + + if len(args["active_export"]): + for i in args["active_export"]: + self.execute_export_cfg_aedt(**i) + + @staticmethod + def execute_load_cfg_siw(project_file, file_cfg_path, file_save_path): # pragma: no cover + """Load configuration file.""" + fdir = Path(file_save_path).parent + fname = Path(file_save_path).stem + siw = Siwave(specified_version=version) + siw.open_project(str(project_file)) + siw.load_configuration(file_cfg_path) + siw.save_project(fdir, fname) + siw.quit_application() + + @staticmethod + def execute_load_cfg_aedt(project_file, file_cfg_path, file_save_path): + fedb = Path(project_file).with_suffix(".aedb") + edbapp = Edb(str(fedb), edbversion=version) + edbapp.configuration.load(file_cfg_path) + edbapp.configuration.run() + edbapp.save_as(str(Path(file_save_path).with_suffix(".aedb"))) + edbapp.close() - tkinter.mainloop() + h3d = Hfss3dLayout(str(Path(file_save_path).with_suffix(".aedb"))) + h3d.save_project() - aedb_path_ui = getattr(master, "aedb_path_ui", extension_arguments["aedb_path"]) + @staticmethod + def execute_export_cfg_siw(project_file, file_path_save): # pragma: no cover + siw = Siwave(specified_version=version) + siw.open_project(str(project_file)) + siw.export_configuration(file_path_save) + siw.quit_application() + + @staticmethod + def execute_export_cfg_aedt(project_file, file_path_save): + fedb = Path(project_file).with_suffix(".aedb") + edbapp = Edb(str(fedb), edbversion=version) + edbapp.configuration.export(file_path_save) + edbapp.close() - configuration_path_ui = getattr(master, "configuration_path_ui", extension_arguments["configuration_path"]) - output_dict = { - "configuration_path": configuration_path_ui, - "aedb_path": aedb_path_ui, - } - return output_dict - - -def main(extension_args): - aedb_path = extension_args["aedb_path"] - config = extension_args["configuration_path"] - if os.path.isdir(config): - configs = search_files(config, "*.json") - configs += search_files(config, "*.toml") - else: - configs = [config] - app = ansys.aedt.core.Desktop( - new_desktop_session=False, - specified_version=version, - port=port, - aedt_process_id=aedt_process_id, - student_version=is_student, - ) - if aedb_path == "": - project_name = app.active_project().GetName() - aedb_path = os.path.join(app.active_project().GetPath(), project_name + ".aedb") - else: - project_name = os.path.splitext(os.path.split(aedb_path)[-1])[0] - if project_name in app.project_list(): - app.odesktop.CloseProject(project_name) - for config in configs: - edbapp = Edb(aedb_path, edbversion=version) - config_name = os.path.splitext(os.path.split(config)[-1])[0] - output_path = aedb_path[:-5] + f"_{config_name}.aedb" - if os.path.exists(output_path): - new_name = generate_unique_name(config_name, n=2) - output_path = aedb_path[:-5] + f"_{new_name}.aedb" - edbapp.configuration.load(config_file=config) - edbapp.configuration.run() - edbapp.save_edb_as(output_path) - edbapp.close() - h3d = Hfss3dLayout(output_path) - h3d.save_project() - if not extension_args["is_test"]: # pragma: no cover - app.release_desktop(False, False) - return True +def main(is_test=False, execute=""): + if is_test: + ConfigureEdbBackend(execute) + else: # pragma: no cover + app = ConfigureEdbFrontend() + app.mainloop() -if __name__ == "__main__": # pragma: no cover - args = get_arguments(extension_arguments, extension_description) +if __name__ == "__main__": # Open UI - if not args["is_batch"]: # pragma: no cover - output = frontend() - if output: - for output_name, output_value in output.items(): - if output_name in extension_arguments: - args[output_name] = output_value - - main(args) + main()