diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss/shielding.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss/shielding.rst new file mode 100644 index 00000000000..cbe0741877f --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss/shielding.rst @@ -0,0 +1,51 @@ +Shielding Effectiveness +======================= + +The **Shielding Effectiveness** extension computes the shielding effectiveness of an enclosure. +It calculates the attenuation of an electromagnetic field inside the enclosure due to the presence of a shield. + +The extension provides a graphical user interface (GUI) for configuration, +or it can be used in batch mode via command line arguments. + +The following image shows the extension GUI: + +.. image:: ../../../_static/extensions/shielding_ui.png + :width: 800 + :alt: Shielding Effectiveness GUI + + +Features +-------- + +- Configure input parameters including source sphere radius, polarization, start and stop frequency and dipole type. +- Automatic HFSS setup. +- Switch between light and dark themes in the GUI. + + +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS interface. +2. Locate and click the **Shielding Effectiveness** icon under the Extension Manager. +3. In the GUI, users can interact with the following elements: + - **Source sphere radius**: Source sphere radius in meters. It must fit inside the shielding. + - **Polarization**: X, Y, Z polarization component. + - **Frequency**: Start and stop frequency and the number of steps to analyze. + - **Electric dipole**: Activate electric dipole. Electric or magnetic dipole are available. + - **Cores**: Number of cores for the simulation. + - Toggle between light and dark themes. +4. Click on **Launch** to start the automated workflow. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + + +Use the following syntax to run the extension: + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/_static/extensions/shielding_ui.png b/doc/source/_static/extensions/shielding_ui.png new file mode 100644 index 00000000000..fb276c1dea0 Binary files /dev/null and b/doc/source/_static/extensions/shielding_ui.png differ diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index a8e8cc0a262..9ccfc9c65d7 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -6766,6 +6766,110 @@ def plane_wave( return self._create_boundary(name, inc_wave_args, "Plane Incident Wave") + @pyaedt_function_handler() + def hertzian_dipole_wave( + self, + assignment=None, + origin=None, + polarization=None, + is_electric=True, + radius="10mm", + name=None, + ) -> BoundaryObject: + """Create a hertzian dipole wave excitation. + + The excitation is assigned in the assigned sphere. Inside this sphere, the field magnitude + is equal to the field magnitude calculated on the surface of the sphere. + + Parameters + ---------- + assignment : str or list, optional + One or more objects or faces to assign finite conductivity to. The default is ``None``, in which + case the excitation is assigned to anything. + origin : list, optional + Excitation location. The default is ``["0mm", "0mm", "0mm"]``. + polarization : list, optional + Electric field polarization vector. + The default is ``[0, 0, 1]``. + is_electric : bool, optional + Type of dipole. Electric dipole if ``True``, magnetic dipole if ``False``. The default is ``True``. + radius : str or float, optional + Radius of surrounding sphere. The default is "10mm". + name : str, optional + Name of the boundary. + + Returns + ------- + :class:`pyaedt.modules.Boundary.BoundaryObject` + Port object. + + References + ---------- + >>> oModule.AssignHertzianDipoleWave + + Examples + -------- + + Create a hertzian dipole wave excitation. + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss() + >>> sphere = hfss.modeler.primitives.create_sphere([0, 0, 0], 10) + >>> port1 = hfss.hertzian_dipole_wave(assignment=sphere, radius=10) + """ + userlst = self.modeler.convert_to_selections(assignment, True) + lstobj = [] + lstface = [] + for selection in userlst: + if selection in self.modeler.model_objects: + lstobj.append(selection) + elif isinstance(selection, int) and self.modeler._find_object_from_face_id(selection): + lstface.append(selection) + + props = {"Objects": [], "Faces": []} + + if lstobj: + props["Objects"] = lstobj + if lstface: + props["Faces"] = lstface + + if not origin: + origin = ["0mm", "0mm", "0mm"] + elif not isinstance(origin, list) or len(origin) != 3: + self.logger.error("Invalid value for `origin`.") + return False + + x_origin, y_origin, z_origin = self.modeler._pos_with_arg(origin) + + name = self._get_unique_source_name(name, "IncPWave") + + hetzian_wave_args = {"OriginX": x_origin, "OriginY": y_origin, "OriginZ": z_origin} + + if not polarization: + polarization = [0, 0, 1] + elif not isinstance(polarization, list) or len(polarization) != 3: + self.logger.error("Invalid value for `polarization`.") + return False + + new_hertzian_args = { + "IsCartesian": True, + "EoX": polarization[0], + "EoY": polarization[1], + "EoZ": polarization[2], + "kX": polarization[0], + "kY": polarization[1], + "kZ": polarization[2], + } + + hetzian_wave_args["IsElectricDipole"] = False + if is_electric: + hetzian_wave_args["IsElectricDipole"] = True + + hetzian_wave_args["SphereRadius"] = radius + hetzian_wave_args.update(new_hertzian_args) + hetzian_wave_args.update(props) + + return self._create_boundary(name, hetzian_wave_args, "Hertzian Dipole Wave") + @pyaedt_function_handler() def set_radiated_power_calc_method(self, method="Auto"): """Set the radiated power calculation method in Hfss. diff --git a/src/ansys/aedt/core/modeler/modeler_3d.py b/src/ansys/aedt/core/modeler/modeler_3d.py index 73e8c283898..7726e1182da 100644 --- a/src/ansys/aedt/core/modeler/modeler_3d.py +++ b/src/ansys/aedt/core/modeler/modeler_3d.py @@ -776,6 +776,9 @@ def create_waveguide( } if wgmodel in WG: + original_model_units = self.model_units + self.model_units = "mm" + wgwidth = WG[wgmodel][0] wgheight = WG[wgmodel][1] if not wg_thickness: @@ -840,7 +843,7 @@ def create_waveguide( wgbox = self.create_box(origin, [wg_length, wb, hb], name=name) self.subtract(wgbox, airbox, False) wgbox.material_name = wg_material - + self.model_units = original_model_units return wgbox, p1, p2 else: return None diff --git a/src/ansys/aedt/core/modules/boundary/common.py b/src/ansys/aedt/core/modules/boundary/common.py index 8027747eb4f..caffda84eaa 100644 --- a/src/ansys/aedt/core/modules/boundary/common.py +++ b/src/ansys/aedt/core/modules/boundary/common.py @@ -537,6 +537,8 @@ def create(self): self._app.oboundary.AssignFluxTangential(self._get_args()) elif bound_type == "Plane Incident Wave": self._app.oboundary.AssignPlaneWave(self._get_args()) + elif bound_type == "Hertzian Dipole Wave": + self._app.oboundary.AssignHertzianDipoleWave(self._get_args()) elif bound_type == "ResistiveSheet": self._app.oboundary.AssignResistiveSheet(self._get_args()) else: @@ -681,6 +683,8 @@ def update(self): self._app.oboundary.EditTerminal(self.name, self._get_args()) elif bound_type == "Plane Incident Wave": self._app.oboundary.EditIncidentWave(self.name, self._get_args()) + elif bound_type == "Hertzian Dipole Wave": + self._app.oboundary.EditIncidentWave(self.name, self._get_args()) elif bound_type == "ResistiveSheet": self._app.oboundary.EditResistiveSheet(self.name, self._get_args()) else: diff --git a/src/ansys/aedt/core/visualization/report/common.py b/src/ansys/aedt/core/visualization/report/common.py index e75df8225a6..9939d949989 100644 --- a/src/ansys/aedt/core/visualization/report/common.py +++ b/src/ansys/aedt/core/visualization/report/common.py @@ -1323,7 +1323,11 @@ def create(self, name=None): self.plot_name = generate_unique_name("Plot") else: self.plot_name = name - if self.setup not in self._post._app.existing_analysis_sweeps and "AdaptivePass" not in self.setup: + if ( + self.setup not in self._post._app.existing_analysis_sweeps + and "AdaptivePass" not in self.setup + and " : Table" not in self.setup + ): self._post._app.logger.error("Setup doesn't exist in this design.") return False self._post.oreportsetup.CreateReport( diff --git a/src/ansys/aedt/core/workflows/hfss/images/large/shielding.png b/src/ansys/aedt/core/workflows/hfss/images/large/shielding.png new file mode 100644 index 00000000000..72360307cb5 Binary files /dev/null and b/src/ansys/aedt/core/workflows/hfss/images/large/shielding.png differ diff --git a/src/ansys/aedt/core/workflows/hfss/shielding_effectiveness.py b/src/ansys/aedt/core/workflows/hfss/shielding_effectiveness.py new file mode 100644 index 00000000000..0aa9b7b554e --- /dev/null +++ b/src/ansys/aedt/core/workflows/hfss/shielding_effectiveness.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from pathlib import Path + +import ansys.aedt.core +from ansys.aedt.core import get_pyaedt_app +from ansys.aedt.core.generic.general_methods import write_csv +import ansys.aedt.core.workflows +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 +import numpy as np + +port = get_port() +version = get_aedt_version() +aedt_process_id = get_process_id() +is_student = is_student() + +# Extension batch arguments +extension_arguments = { + "sphere_size": 0.01, + "x_pol": 0.0, + "y_pol": 0.0, + "z_pol": 1.0, + "dipole_type": "Electric", + "frequency_units": "GHz", + "start_frequency": 0.1, + "stop_frequency": 1, + "points": 10, + "cores": 4, +} + +extension_description = "Shielding effectiveness workflow" + + +def frontend(): # pragma: no cover + + import tkinter + from tkinter import ttk + + import PIL.Image + import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme + + master = tkinter.Tk() + master.title("Shielding effectiveness") + + # Detect if user closes the UI + master.flag = False + + # Load the logo for the main window + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" + im = PIL.Image.open(icon_path) + photo = PIL.ImageTk.PhotoImage(im) + + # Set the icon for the main window + master.iconphoto(True, photo) + + # Configure style for ttk buttons + style = ttk.Style() + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label = ttk.Label(master, text="Source sphere radius (meters):", style="PyAEDT.TLabel") + label.grid(row=0, column=0, pady=10, padx=5) + + sphere_size = tkinter.Text(master, width=20, height=1) + sphere_size.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + sphere_size.insert(tkinter.END, "0.01") + sphere_size.grid(row=0, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="X Polarization:", style="PyAEDT.TLabel") + label.grid(row=1, column=0, pady=10) + + x_pol = tkinter.Text(master, width=20, height=1) + x_pol.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + x_pol.insert(tkinter.END, "0.0") + x_pol.grid(row=1, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Y Polarization:", style="PyAEDT.TLabel") + label.grid(row=2, column=0, pady=10) + + y_pol = tkinter.Text(master, width=20, height=1) + y_pol.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + y_pol.insert(tkinter.END, "0.0") + y_pol.grid(row=2, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Z Polarization:", style="PyAEDT.TLabel") + label.grid(row=3, column=0, pady=10) + + z_pol = tkinter.Text(master, width=20, height=1) + z_pol.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + z_pol.insert(tkinter.END, "1.0") + z_pol.grid(row=3, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Frequency units:", style="PyAEDT.TLabel") + label.grid(row=4, column=0, pady=10) + + units = tkinter.Text(master, width=20, height=1) + units.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + units.insert(tkinter.END, "GHz") + units.grid(row=4, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Start frequency:", style="PyAEDT.TLabel") + label.grid(row=5, column=0, pady=10) + + start_freq = tkinter.Text(master, width=20, height=1) + start_freq.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + start_freq.insert(tkinter.END, "0.1") + start_freq.grid(row=5, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Stop frequency:", style="PyAEDT.TLabel") + label.grid(row=6, column=0, pady=10) + + stop_freq = tkinter.Text(master, width=20, height=1) + stop_freq.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + stop_freq.insert(tkinter.END, "1.0") + stop_freq.grid(row=6, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Points:", style="PyAEDT.TLabel") + label.grid(row=7, column=0, pady=10) + + points = tkinter.Text(master, width=20, height=1) + points.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + points.insert(tkinter.END, "10") + points.grid(row=7, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Electric dipole:", style="PyAEDT.TLabel") + label.grid(row=8, column=0, pady=10) + dipole = tkinter.IntVar(value=1) + check2 = ttk.Checkbutton(master, variable=dipole, style="PyAEDT.TCheckbutton") + check2.grid(row=8, column=1, pady=10, padx=5) + + label = ttk.Label(master, text="Cores:", style="PyAEDT.TLabel") + label.grid(row=9, column=0, pady=10) + + cores = tkinter.Text(master, width=20, height=1) + cores.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + cores.insert(tkinter.END, "4") + cores.grid(row=9, column=1, pady=10, padx=5) + + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + sphere_size.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + x_pol.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + y_pol.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + z_pol.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + units.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + start_freq.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + stop_freq.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + points.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + cores.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + sphere_size.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + x_pol.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + y_pol.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + z_pol.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + units.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + start_freq.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + stop_freq.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + points.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + cores.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=10, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=10, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=10, column=3, padx=0) + + def callback(): + master.flag = True + master.sphere_size_ui = float(sphere_size.get("1.0", tkinter.END).strip()) + master.x_pol_ui = float(x_pol.get("1.0", tkinter.END).strip()) + master.y_pol_ui = float(y_pol.get("1.0", tkinter.END).strip()) + master.z_pol_ui = float(z_pol.get("1.0", tkinter.END).strip()) + master.units_ui = units.get("1.0", tkinter.END).strip() + master.start_freq_ui = float(start_freq.get("1.0", tkinter.END).strip()) + master.stop_freq_ui = float(stop_freq.get("1.0", tkinter.END).strip()) + master.points_ui = int(points.get("1.0", tkinter.END).strip()) + master.cores_ui = int(cores.get("1.0", tkinter.END).strip()) + master.dipole = "Electric" if dipole.get() == 1 else "Magnetic" + master.destroy() + + b3 = ttk.Button(master, text="Launch", width=40, command=callback, style="PyAEDT.TButton") + b3.grid(row=10, column=1, pady=10, padx=10) + + tkinter.mainloop() + + sphere_size_ui = getattr(master, "sphere_size_ui", extension_arguments["sphere_size"]) + x_pol_ui = getattr(master, "x_pol_ui", extension_arguments["x_pol"]) + y_pol_ui = getattr(master, "y_pol_ui", extension_arguments["y_pol"]) + z_pol_ui = getattr(master, "z_pol_ui", extension_arguments["z_pol"]) + units_ui = getattr(master, "units_ui", extension_arguments["frequency_units"]) + start_freq_ui = getattr(master, "start_freq_ui", extension_arguments["start_frequency"]) + stop_freq_ui = getattr(master, "stop_freq_ui", extension_arguments["stop_frequency"]) + points_ui = getattr(master, "points_ui", extension_arguments["frequency_units"]) + cores_ui = getattr(master, "cores_ui", extension_arguments["cores"]) + dipole_ui = getattr(master, "dipole_ui", extension_arguments["dipole_type"]) + + output_dict = {} + if master.flag: + output_dict = { + "sphere_size": sphere_size_ui, + "x_pol": x_pol_ui, + "y_pol": y_pol_ui, + "z_pol": z_pol_ui, + "frequency_units": units_ui, + "start_frequency": start_freq_ui, + "stop_frequency": stop_freq_ui, + "points": points_ui, + "dipole_type": dipole_ui, + "cores": cores_ui, + } + return output_dict + + +def main(extension_args): + + sphere_size = extension_args["sphere_size"] + dipole_type = extension_args["dipole_type"] + frequency_units = extension_args["frequency_units"] + start_frequency = extension_args["start_frequency"] + stop_frequency = extension_args["stop_frequency"] + points = extension_args["points"] + cores = extension_args["cores"] + x_pol = extension_args["x_pol"] + y_pol = extension_args["y_pol"] + z_pol = extension_args["z_pol"] + polarization = [x_pol, y_pol, z_pol] + + app = ansys.aedt.core.Desktop( + new_desktop=False, + version=version, + port=port, + aedt_process_id=aedt_process_id, + student_version=is_student, + ) + + active_project = app.active_project() + + if not active_project: # pragma: no cover + app.logger.error("Not active project.") + if not extension_args["is_test"]: + app.release_desktop(False, False) + return False + + active_design = app.active_design() + + project_name = active_project.GetName() + + if not active_design: # pragma: no cover + app.logger.error("Not active design.") + if not extension_args["is_test"]: + app.release_desktop(False, False) + return False + + design_name = active_design.GetName() + + aedtapp = get_pyaedt_app(project_name, design_name) + + if aedtapp.design_type != "HFSS": # pragma: no cover + app.logger.error("Active design is not HFSS.") + if not extension_args["is_test"]: + app.release_desktop(False, False) + return False + + aedtapp.solution_type = "Terminal" + + aedtapp.modeler.model_units = "meter" + aedtapp.modeler.set_working_coordinate_system("Global") + + object_names = aedtapp.modeler.object_names + + if len(object_names) != 1: + aedtapp.logger.error("There should be only one object in the design.") + if not extension_args["is_test"]: # pragma: no cover + app.release_desktop(False, False) + return False + + aedtapp.logger.info("Add Hertzian dipole excitation.") + + shielding = aedtapp.modeler[object_names[0]] + shielding_name = shielding.name + b = shielding.bounding_box + + center = [(b[0] + b[3]) / 2, (b[1] + b[4]) / 2, (b[2] + b[5]) / 2] + + # Create sphere + + sphere = aedtapp.modeler.create_sphere(origin=center, radius=sphere_size, material="pec") + + # Assign incident wave + is_electric = False + if dipole_type == "Electric": + is_electric = True + + aedtapp.hertzian_dipole_wave( + assignment=sphere, + origin=center, + polarization=polarization, + is_electric=is_electric, + radius=f"{sphere_size}meter", + ) + + aedtapp.logger.info("Create setup.") + + # Compute frequency mesh + + freq_med = (stop_frequency + start_frequency) / 2 + + freq_h_med = (stop_frequency + freq_med) / 2 + + freq_mesh = f"{freq_h_med}{frequency_units}" + + # Radiation box + + aedtapp.create_open_region(frequency=freq_mesh) + + # Create setup and frequency sweep + + setup = aedtapp.create_setup() + setup.properties["Solution Freq"] = freq_mesh + setup_name = setup.name + + aedtapp.create_linear_count_sweep( + setup_name, + units=frequency_units, + start_frequency=start_frequency, + stop_frequency=stop_frequency, + num_of_freq_points=points, + save_fields=True, + sweep_type="Discrete", + ) + + # Save + + aedtapp.save_project() + + # Duplicate design + + aedtapp.logger.info("Duplicate design without enclosure.") + original_design = aedtapp.design_name + aedtapp.duplicate_design(f"{original_design}_free_space") + free_space_design = aedtapp.design_name + + # Change material to vacuum + + shielding_vacuum = aedtapp.modeler[shielding_name] + shielding_vacuum.material_name = "vacuum" + + aedtapp.save_project() + + # Analyze free space + + free_space = ansys.aedt.core.Hfss(design=free_space_design, new_desktop=False) + free_space.analyze(cores=cores, setup=setup_name) + + # Analyze original + original = ansys.aedt.core.Hfss(design=original_design, new_desktop=False) + original.analyze(cores=cores, setup=setup_name) + + # Get data + aedtapp.logger.info("Get data from both designs.") + + free_space_1meter = free_space.post.get_solution_data("Sphere1meter", report_category="Emission Test") + free_space_3meters = free_space.post.get_solution_data("Sphere3meters", report_category="Emission Test") + + original_1meter = original.post.get_solution_data("Sphere1meter", report_category="Emission Test") + original_3meters = original.post.get_solution_data("Sphere3meters", report_category="Emission Test") + + frequencies = free_space_1meter.primary_sweep_values + frequency_units = free_space_1meter.units_sweeps["Freq"] + + # 1 Meter shielding + original_1meter = np.array(original_1meter.data_magnitude()) + free_space_1meter = np.array(free_space_1meter.data_magnitude()) + + shielding_1meter = original_1meter / free_space_1meter + + shielding_1meter_db = -20 * np.log10(shielding_1meter) + + # 3 meter shielding + + original_3meters = np.array(original_3meters.data_magnitude()) + free_space_3meters = np.array(free_space_3meters.data_magnitude()) + + shielding_3meters = original_3meters / free_space_3meters + + shielding_3meters_db = -20 * np.log10(shielding_3meters) + + # Create tables + + # Sphere 1 meter + + input_file_1meter = Path(original.toolkit_directory) / "Shielding_Sphere1meter.csv" + + list_data = [[frequency_units, "V_per_meter"]] + + for idx, frequency in enumerate(frequencies): + list_data.append([frequency, shielding_1meter_db[idx]]) + + write_csv(str(input_file_1meter), list_data, delimiter=",") + + # Sphere 3 meters + input_file_3meters = Path(original.toolkit_directory) / "Shielding_Sphere3meters.csv" + + list_data = [[frequency_units, "V_per_meter"]] + for idx, frequency in enumerate(frequencies): + list_data.append([frequency, shielding_3meters_db[idx]]) + + write_csv(str(input_file_3meters), list_data, delimiter=",") + + # Import tables + if "Shielding_Sphere1meter" in original.table_names: # pragma: no cover + original.delete_table("Shielding_Sphere1meter") + if "Shielding_Sphere3meters" in original.table_names: # pragma: no cover + original.delete_table("Shielding_Sphere3meters") + + original.import_table( + str(input_file_1meter), + "Shielding_Sphere1meter", + is_real_imag=True, + is_field=True, + independent_columns=[True, False], + column_names=[frequency_units, "V_per_meter"], + ) + + original.import_table( + str(input_file_3meters), + "Shielding_Sphere3meters", + is_real_imag=True, + is_field=True, + independent_columns=[True, False], + column_names=[frequency_units, "V_per_meter"], + ) + + original.post.create_report( + expressions="Tb(V_per_meter)", + primary_sweep_variable="Tb(GHz)", + setup_sweep_name="Shielding_Sphere1meter : Table", + plot_name="Shielding Sphere1meter", + ) + + original.post.create_report( + expressions="Tb(V_per_meter)", + primary_sweep_variable="Tb(GHz)", + setup_sweep_name="Shielding_Sphere3meters : Table", + plot_name="Shielding Sphere3meters", + ) + + if not extension_args["is_test"]: # pragma: no cover + app.release_desktop(False, False) + return True + + +if __name__ == "__main__": # pragma: no cover + args = get_arguments(extension_arguments, extension_description) + + # 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) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss/toolkits_catalog.toml b/src/ansys/aedt/core/workflows/hfss/toolkits_catalog.toml index 5fc9e5f2be7..0d17efa682b 100644 --- a/src/ansys/aedt/core/workflows/hfss/toolkits_catalog.toml +++ b/src/ansys/aedt/core/workflows/hfss/toolkits_catalog.toml @@ -18,4 +18,11 @@ name = "Choke Designer" script = "choke_designer.py" icon = "images/large/choke.png" template = "run_pyaedt_toolkit_script" +pip = "" + +[ShieldingEffectiveness] +name = "Shielding Effectiveness" +script = "shielding_effectiveness.py" +icon = "images/large/shielding.png" +template = "run_pyaedt_toolkit_script" pip = "" \ No newline at end of file diff --git a/tests/system/general/test_20_HFSS.py b/tests/system/general/test_20_HFSS.py index aba5db2bdfd..dd1f314337e 100644 --- a/tests/system/general/test_20_HFSS.py +++ b/tests/system/general/test_20_HFSS.py @@ -1804,3 +1804,24 @@ def test_72_import_table(self, add_app): assert aedtapp.import_table(input_file=file_no_header, name="Table2") assert "Table2" in aedtapp.table_names + + def test_73_plane_wave(self, add_app): + aedtapp = add_app(project_name="test_73") + assert not aedtapp.hertzian_dipole_wave(origin=[0, 0]) + assert not aedtapp.hertzian_dipole_wave(polarization=[1]) + + sphere = aedtapp.modeler.create_sphere([0, 0, 0], 10) + sphere2 = aedtapp.modeler.create_sphere([10, 100, 0], 10) + + assignment = [sphere, sphere2.faces[0].id] + + exc = aedtapp.hertzian_dipole_wave(assignment=assignment, is_electric=True) + assert len(aedtapp.excitations) == 1 + assert exc.properties["Electric Dipole"] + exc.props["IsElectricDipole"] = False + assert not exc.properties["Electric Dipole"] + + exc2 = aedtapp.hertzian_dipole_wave(polarization=[1, 0, 0], name="dipole", radius=20) + assert len(aedtapp.excitations) == 2 + assert exc2.name == "dipole" + aedtapp.close_project(save=False) diff --git a/tests/system/solvers/test_45_workflows.py b/tests/system/solvers/test_45_workflows.py index 4c36bffcf96..038dd00f44e 100644 --- a/tests/system/solvers/test_45_workflows.py +++ b/tests/system/solvers/test_45_workflows.py @@ -594,3 +594,46 @@ def test_18_via_merging(self, local_scratch): "test_mode": True, } assert main(_input_) + + @pytest.mark.skipif(is_linux, reason="Simulation takes too long in Linux machine.") + def test_19_shielding_effectiveness(self, add_app, local_scratch): + aedtapp = add_app(application=ansys.aedt.core.Hfss, project_name="se") + + from ansys.aedt.core.workflows.hfss.shielding_effectiveness import main + + assert not main( + { + "is_test": True, + "sphere_size": 0.01, + "x_pol": 0.0, + "y_pol": 0.1, + "z_pol": 1.0, + "dipole_type": "Electric", + "frequency_units": "GHz", + "start_frequency": 0.1, + "stop_frequency": 1, + "points": 5, + "cores": 4, + } + ) + + aedtapp.modeler.create_waveguide(origin=[0, 0, 0], wg_direction_axis=0) + + assert main( + { + "is_test": True, + "sphere_size": 0.01, + "x_pol": 0.0, + "y_pol": 0.1, + "z_pol": 1.0, + "dipole_type": "Electric", + "frequency_units": "GHz", + "start_frequency": 0.1, + "stop_frequency": 0.2, + "points": 2, + "cores": 2, + } + ) + + assert len(aedtapp.post.all_report_names) == 2 + aedtapp.close_project(aedtapp.project_name)