diff --git a/doc/source/conf.py b/doc/source/conf.py index ff5c29d02..44c7ceb95 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -264,6 +264,7 @@ def convert_examples_into_notebooks(app): "interference.py", "hfss_emit.py", "component_conversion.py", + "touchstone_file.py" ) # NOTE: Only convert the examples if the workflow isn't tagged as coupling HTML and PDF build. diff --git a/examples/aedt_general/report/automatic_report.py b/examples/aedt_general/report/automatic_report.py index a5274824d..8dd618ad0 100644 --- a/examples/aedt_general/report/automatic_report.py +++ b/examples/aedt_general/report/automatic_report.py @@ -97,7 +97,7 @@ # The following code modifies the trace rendering prior to creating the report. # + -props = ansys.aedt.core.general_methods.read_json( +props = ansys.aedt.core.generic.file_utils.read_json( os.path.join(project_path, "Transient_CISPR_Custom.json") ) diff --git a/examples/aedt_general/report/touchstone_file.py b/examples/aedt_general/report/touchstone_file.py index 240c1b6e3..18b883a64 100644 --- a/examples/aedt_general/report/touchstone_file.py +++ b/examples/aedt_general/report/touchstone_file.py @@ -9,14 +9,15 @@ # # Keywords: **Touchstone**, **report**. -# ## Perform imports -# Import the required packages. +# ## Prerequisites +# +# ### Perform imports from ansys.aedt.core import downloads from ansys.aedt.core.visualization.advanced.touchstone_parser import \ read_touchstone -# ## Download example data +# ### Download example data example_path = downloads.download_touchstone() @@ -24,7 +25,9 @@ data = read_touchstone(example_path) -# ## Plot data +# ## Demonstrate post-processing +# +# ### Plot serial channel metrics # # Get the curve plot by category. The following code shows how to plot lists of the return losses, # insertion losses, next, and fext based on a few inputs and port names. @@ -34,7 +37,7 @@ data.plot_next_xtalk_losses("U1") data.plot_fext_xtalk_losses(tx_prefix="U1", rx_prefix="U7") -# ## Identify cross-talk +# ### Visualize cross-talk # # Identify the worst case cross-talk. diff --git a/examples/high_frequency/antenna/_static/ff_report_ui_1.png b/examples/high_frequency/antenna/_static/ff_report_ui_1.png new file mode 100644 index 000000000..2114cecb2 Binary files /dev/null and b/examples/high_frequency/antenna/_static/ff_report_ui_1.png differ diff --git a/examples/high_frequency/antenna/_static/ff_report_ui_2.png b/examples/high_frequency/antenna/_static/ff_report_ui_2.png new file mode 100644 index 000000000..7606bd018 Binary files /dev/null and b/examples/high_frequency/antenna/_static/ff_report_ui_2.png differ diff --git a/examples/high_frequency/antenna/_static/sphere_3d.png b/examples/high_frequency/antenna/_static/sphere_3d.png new file mode 100644 index 000000000..35999626f Binary files /dev/null and b/examples/high_frequency/antenna/_static/sphere_3d.png differ diff --git a/examples/high_frequency/antenna/array.py b/examples/high_frequency/antenna/array.py index f900da8a0..4fa1664d8 100644 --- a/examples/high_frequency/antenna/array.py +++ b/examples/high_frequency/antenna/array.py @@ -1,86 +1,120 @@ # # Component antenna array -# This example shows how to use PyAEDT to create an example using a 3D component file. It sets -# up the analysis, solves it, and uses postprocessing functions to create plots using Matplotlib and -# PyVista without opening the HFSS user interface. This example runs only on Windows using CPython. +# This example shows how to create an antenna array using 3D components for the unit +# cell definition. You will see how to set +# up the analysis, generates the EM solution, and post-process the solution data +# using Matplotlib and +# PyVista. This example runs only on Windows using CPython. # # Keywords: **HFSS**, **antenna array**, **3D components**, **far field**. -# ## Perform imports and define constants +# ## Prerequisites # -# Perform required imports. +# ### Perform imports +# + import os import tempfile import time -import ansys.aedt.core +from ansys.aedt.core import Hfss from ansys.aedt.core.visualization.advanced.farfield_visualization import \ FfdSolutionData +from ansys.aedt.core.downloads import download_3dcomponent +from ansys.aedt.core import general_methods +# - -# Define constants +# ### Define constants +# Constants help ensure consistency and avoid repetition throughout the example. -AEDT_VERSION = "2024.2" +AEDT_VERSION = "2025.1" NUM_CORES = 4 NG_MODE = False # Open AEDT UI when it is launched. -# ## Create temporary directory +# ### Create temporary directory +# +# Create a temporary working directory. +# The name of the working folder is stored in ``temp_folder.name``. +# +# > **Note:** The final cell in the notebook cleans up the temporary folder. If you want to +# > retrieve the AEDT project and data, do so before executing the final cell in the notebook. # -# Create a temporary directory where downloaded data or -# dumped data can be stored. -# If you'd like to retrieve the project data for subsequent use, -# the temporary folder name is given by ``temp_folder.name``. temp_folder = tempfile.TemporaryDirectory(suffix=".ansys") -# ## Download 3D component -# Download the 3D component that is needed to run the example. +# ### Download 3D component +# Download the 3D component that will be used to define +# the unit cell in the antenna array. -example_path = ansys.aedt.core.downloads.download_3dcomponent( - destination=temp_folder.name -) +path_to_3dcomp = download_3dcomponent(destination=temp_folder.name) -# ## Launch HFSS and open project +# ### Launch HFSS # -# Launch HFSS and open the project. +# The following cell creates a new ``Hfss`` object. Electronics desktop is launched and +# a new HFSS design is inserted into the project. # + project_name = os.path.join(temp_folder.name, "array.aedt") -hfss = ansys.aedt.core.Hfss( +hfss = Hfss( project=project_name, version=AEDT_VERSION, design="Array_Simple", non_graphical=NG_MODE, - new_desktop=True, + new_desktop=False, # Set to `False` to connect to an existing AEDT session. ) print("Project name " + project_name) # - -# ## Read array definition +# ### Read array definition # # Read array definition from the JSON file. -dict_in = ansys.aedt.core.general_methods.read_json( - os.path.join(example_path, "array_simple.json") +array_definition = general_methods.read_json( + os.path.join(path_to_3dcomp, "array_simple.json") ) -# ## Define 3D component +# ### Add the 3D component definition +# +# The JSON file links the unit cell to the 3D component +# named "Circ_Patch_5GHz1". +# This can be seen by examining ``array_definition["cells"]``. The following +# code prints the row and column indices of the array elements along with the name +# of the 3D component for each element. # -# Define the 3D component cell. +# > **Note:** The ``array_definition["cells"]`` is of type ``dict`` and the key +# > is builta as a string from the _(row, column)_ indices of the array element. For example: +# > the key for the element in the first row and 2nd column is ``"(1,2)"``. + +print("Element\t\tName") +print("--------\t-------------") +for cell in array_definition["cells"]: + cell_name = array_definition["cells"][cell]["name"] + print(f"{cell}\t\t'{cell_name}'.") + +# Each unit cell is defined by a 3D Component. The 3D component may be added +# to the HFSS design using the method +# [``Hfss.modeler.insert_3d_component()``] +# (https://aedt.docs.pyansys.com/version/stable/API/_autosummary/ansys.aedt.core.modeler.modeler_3d.Modeler3D.insert_3d_component.html) +# or it can be added as a key to the ``array_definition`` as shown below. -dict_in["Circ_Patch_5GHz1"] = os.path.join(example_path, "Circ_Patch_5GHz.a3dcomp") +array_definition["Circ_Patch_5GHz1"] = os.path.join(path_to_3dcomp, "Circ_Patch_5GHz.a3dcomp") -# ## Add 3D component array +# Note that the 3D component name is identical to the value for each element +# in the ``"cells"`` dictionary. For example, +# ``array_definition["cells"][0][0]["name"] + +# ### Create the 3D component array in HFSS # -# A 3D component array is created from the previous dictionary. +# The array is now generated in HFSS from the information in +# ``array_definition``. # If a 3D component is not available in the design, it is loaded # into the dictionary from the path that you specify. The following -# code edits the dictionary to point to the location of the A3DCOMP file. +# code edits the dictionary to point to the location of the ``A3DCOMP`` file. -array = hfss.add_3d_component_array_from_json(dict_in) +array = hfss.add_3d_component_array_from_json(array_definition, name="circ_patch_array") -# ## Modify cells +# ### Modify cells # # Make the center element passive and rotate the corner elements. @@ -90,7 +124,7 @@ array.cells[2][0].rotation = 90 array.cells[2][2].rotation = 90 -# ## Set up simulation and analyze +# ### Set up simulation and run analysis # # Set up a simulation and analyze it. @@ -100,27 +134,30 @@ hfss.analyze(cores=NUM_CORES) -# ## Get far field data +# ## Postprocess +# +# ### Retrieve far-field data # -# Get far field data. After the simulation completes, the far +# Get far-field data. After the simulation completes, the far # field data is generated port by port and stored in a data class. ffdata = hfss.get_antenna_data(setup=hfss.nominal_adaptive, sphere="Infinite Sphere1") -# ## Generate contour plot +# ### Generate contour plot # # Generate a contour plot. You can define the Theta scan and Phi scan. ffdata.farfield_data.plot_contour( quantity="RealizedGain", - title="Contour at {}Hz".format(ffdata.farfield_data.frequency), + title=f"Contour at {ffdata.farfield_data.frequency * 1E-9:0.1f} GHz" ) -# ## Release AEDT +# ### Save the project and data # -# Release AEDT. -# You can perform far field postprocessing without AEDT because the data is stored. +# Farfield data can be accessed from disk after the solution has been generated +# using the ``metadata_file`` property of ``ffdata``. +# + metadata_file = ffdata.metadata_file working_directory = hfss.working_directory @@ -128,10 +165,13 @@ hfss.release_desktop() # Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory. time.sleep(3) +# - -# ## Load far field data +# ### Load far field data # -# Load the stored far field data. +# An instance of the ``FfdSolutionData`` class +# can be instantiated from the metadata file. Embedded element +# patters are linked through the metadata file. ffdata = FfdSolutionData(input_file=metadata_file) @@ -141,19 +181,20 @@ # and Phi scan. ffdata.plot_contour( - quantity="RealizedGain", title="Contour at {}Hz".format(ffdata.frequency) + quantity="RealizedGain", title=f"Contour at {ffdata.frequency * 1e-9:.1f} GHz" ) -# ## Generate 2D cutout plots +# ### Generate 2D cutout plots # # Generate 2D cutout plots. You can define the Theta scan # and Phi scan. +# + ffdata.plot_cut( quantity="RealizedGain", primary_sweep="theta", secondary_sweep_value=[-180, -75, 75], - title="Azimuth at {}Hz".format(ffdata.frequency), + title=f"Azimuth at {ffdata.frequency * 1E-9:.1f} GHz", quantity_format="dB10", ) @@ -164,8 +205,9 @@ title="Elevation", quantity_format="dB10", ) +# - -# ## Generate 3D plot +# ### Generate 3D plot # # Generate 3D plots. You can define the Theta scan and Phi scan. @@ -175,7 +217,7 @@ show=False, ) -# ## Clean up +# ### Clean up # # All project files are saved in the folder ``temp_folder.name``. # If you've run this example as a Jupyter notebook, you diff --git a/examples/high_frequency/antenna/dipole.py b/examples/high_frequency/antenna/dipole.py index bf9b7f717..5c69bd36d 100644 --- a/examples/high_frequency/antenna/dipole.py +++ b/examples/high_frequency/antenna/dipole.py @@ -1,197 +1,245 @@ # # Dipole antenna # -# This example shows how to use PyAEDT to create a dipole antenna in HFSS -# and postprocess results. +# This example demonstrates how to create and analyze a half-wave dipole +# antenna in HFSS. # -# Keywords: **HFSS**, **modal**, **antenna**, **3D components**, **far field**. +# Keywords: **HFSS**, **antenna**, **3D component**, **far field**. -# ## Perform imports and define constants +# ## Prerequisites # -# Perform required imports. +# ### Perform imports +# +# Import the packages required to run this example. +# + import os import tempfile import time -import ansys.aedt.core +from ansys.aedt.core import Hfss +# - -# Define constants. +# ### Define constants +# Constants help ensure consistency and avoid repetition throughout the example. -AEDT_VERSION = "2024.2" +AEDT_VERSION = "2025.1" NUM_CORES = 4 NG_MODE = False # Open AEDT UI when it is launched. -# ## Create temporary directory +# ### Create temporary directory +# +# Create a temporary working directory. +# The name of the working folder is stored in ``temp_folder.name``. # -# Create a temporary directory where downloaded data or -# dumped data can be stored. -# If you'd like to retrieve the project data for subsequent use, -# the temporary folder name is given by ``temp_folder.name``. +# > **Note:** The final cell in this notebook cleans up the temporary folder. If you want to +# > retrieve the AEDT project and data, do so before executing the final cell in the notebook. temp_folder = tempfile.TemporaryDirectory(suffix=".ansys") -# ## Launch AEDT - -d = ansys.aedt.core.launch_desktop( - AEDT_VERSION, non_graphical=NG_MODE, new_desktop=True -) - -# ## Launch HFSS +# ### Launch HFSS # -# Create an HFSS design. +# Create an instance of +# the ``Hfss`` class. The Ansys Electronics Desktop will be launched +# with an active HFSS design. The ``hfss`` object is subsequently +# used to +# create and simulate the dipole antenna. project_name = os.path.join(temp_folder.name, "dipole.aedt") -hfss = ansys.aedt.core.Hfss( - version=AEDT_VERSION, project=project_name, solution_type="Modal" -) - -# ## Define variable +hfss = Hfss(version=AEDT_VERSION, + non_graphical=NG_MODE, + project=project_name, + new_desktop=True, + solution_type="Modal", + ) + +# ## Model Preparation # -# Define a variable for the dipole length. - -hfss["l_dipole"] = "13.5cm" - -# ## Get 3D component from system library +# ### Define the dipole length as a parameter # -# Get a 3D component from the ``syslib`` directory. For this example to run -# correctly, you must get all geometry parameters of the 3D component or, in -# case of an encrypted 3D component, create a dictionary of the parameters. +# The dipole length can be modified by changing the +# parameter ``l_dipole`` to tune the resonance frequency. -compfile = hfss.components3d["Dipole_Antenna_DM"] -geometryparams = hfss.get_components3d_vars("Dipole_Antenna_DM") -geometryparams["dipole_length"] = "l_dipole" -hfss.modeler.insert_3d_component(compfile, geometryparams) +hfss["l_dipole"] = "10.2cm" +component_name = "Dipole_Antenna_DM" +freq_range = ["1GHz", "2GHz"] # Frequency range for analysis and post-processing. +center_freq = "1.5GHz" # Center frequency +freq_step = "0.5GHz" -# ## Create boundaries -# -# Create an open region. -hfss.create_open_region(frequency="1GHz") +# ### Insert the dipole antenna model +# +# The 3D component "Dipole_Antenna_DM" will be inserted from +# the built-in ``syslib`` folder. The full path to the 3D components +# can be retrieved from ``hfss.components3d``. +# +# The component is inserted using the method +# ``hfss.modeler.insert_3d_component()``. +# +# - The first argument passed to ``insert_3d_component()`` +# is the full path and name of the +# ``*.a3dcomp`` file. +# - The second argument is a ``dict`` whose keys are the names of the parameters +# accessible in the 3D component. In this case, we assign the +# dipole length, ``"l_dipole"`` to the 3D Component +# parameter ``dipole_length`` and leave other parameters unchanged. + +component_fn = hfss.components3d[component_name] # Full file name. +comp_params = hfss.get_components3d_vars(component_name) # Retrieve dipole parameters. +comp_params["dipole_length"] = "l_dipole" # Update the dipole length. +hfss.modeler.insert_3d_component(component_fn, geometry_parameters=comp_params) + +# ### Create the 3D domain region +# +# An open region object places a an airbox around the dipole antenna +# and assigns a radiation boundary to the outer surfaces of the region. -# ## Create setup +hfss.create_open_region(frequency=center_freq) -setup = hfss.create_setup("MySetup") -setup.props["Frequency"] = "1GHz" -setup.props["MaximumPasses"] = 1 +# ### Specify the solution setup +# +# The solution setup defines parameters that govern the HFSS solution process. This example demonstrates +# how to adapt the solution mesh simultaneously at two frequencies. +# - ``"MaximumPasses"`` specifies the maximum number of passes used for automatic +# adaptive mesh refinement. In this example the solution runs very fast since only two passes +# are used. Accuracy can be improved by increasing this value. +# - ``"MultipleAdaptiveFreqsSetup"`` specifies the solution frequencies used during adaptive +# mesh refinement. Selection of two frequencies, one above and one below the +# expected resonance frequency help improve mesh quality at the resonant frequency. +# +# > **Note:** The parameter names used here are passed directly to the native AEDT API and therfore +# > do not adhere to [PEP-8](https://peps.python.org/pep-0008/). +# +# Both a discrete frequency sweep and an interpolating sweep are added to the solution setup. +# The discrete sweep provides access to field solution data for post-processing. +# The interpolating sweep builds the +# rational polynomial fit for the network (scattering) parameters over the +# frequency interval defined by ``RangeStart`` and +# ``RangeEnd``. The solutions from the discrete sweep are used as the starting +# solutions for the interpolating sweep. + +# + +setup = hfss.create_setup(name="MySetup", MultipleAdaptiveFreqsSetup=freq_range, MaximumPasses=2) + +disc_sweep = setup.add_sweep(name="DiscreteSweep", sweep_type="Discrete", + RangeStart=freq_range[0], RangeEnd=freq_range[1], RangeStep=freq_step, + SaveFields=True) + +interp_sweep = setup.add_sweep(name="InterpolatingSweep", sweep_type="Interpolating", + RangeStart=freq_range[0], RangeEnd=freq_range[1], + SaveFields=False) +# - + +# ### Run simulation +# +# The following cell runs the analysis in HFSS including adaptive mesh refinement and +# solution of both frequency sweeps. +setup.analyze() -# ## Run simulation +# ### Postprocess +# +# Plot the return loss in Ansys Electronics Desktop (AEDT). A plot similar to the one shown here will be +# generated in . +# +# -hfss.analyze_setup(name="MySetup", cores=NUM_CORES) +#spar_plot = hfss.post.create_report_from_configuration(input_file=spar_template,solution_name=interp_sweep.name) +spar_plot = hfss.create_scattering(plot="Return Loss", sweep=interp_sweep.name) -# ### Postprocess +# ### Visualize far-field data # -# Plot s-parameters and far field. +# Parameters passed to ``hfss.post.create_report()`` specify the details of the report that will be created in AEDT. +# Below you can see how parameters map from Python to the reporter in the AEDT user interface. +# +# +# +# +# > **Note:** These images are from the 24R2 release -hfss.create_scattering("MyScattering") variations = hfss.available_variations.nominal_w_values_dict -variations["Freq"] = ["1GHz"] +variations["Freq"] = [center_freq] variations["Theta"] = ["All"] variations["Phi"] = ["All"] -hfss.post.create_report( - "db(GainTotal)", - hfss.nominal_adaptive, - variations, - primary_sweep_variable="Theta", - context="3D", - report_category="Far Fields", -) - -# Create a far field report. - -new_report = hfss.post.reports_by_category.far_field( - "db(RealizedGainTotal)", hfss.nominal_adaptive, "3D" -) -new_report.report_type = "3D Polar Plot" -new_report.secondary_sweep = "Phi" -new_report.variations["Freq"] = ["1000MHz"] -new_report.create("Realized3D") - -# This code generates a 2D plot. - -hfss.field_setups[2].phi_step = 90 -new_report2 = hfss.post.reports_by_category.far_field( - "db(RealizedGainTotal)", hfss.nominal_adaptive, hfss.field_setups[2].name -) -new_report2.variations = variations -new_report2.primary_sweep = "Theta" -new_report2.create("Realized2D") - -# Get solution data using the ``new_report`` object and postprocess or plot the -# data outside AEDT. - -solution_data = new_report.get_solution_data() -solution_data.plot() - -# Generate a far field plot by creating a postprocessing variable and assigning -# it to a new coordinate system. You can use the ``post`` prefix to create a -# postprocessing variable directly from a setter, or you can use the ``set_variable()`` -# method with an arbitrary name. - -hfss["post_x"] = 2 -hfss.variable_manager.set_variable(name="y_post", expression=1, is_post_processing=True) -hfss.modeler.create_coordinate_system(origin=["post_x", "y_post", 0], name="CS_Post") -hfss.insert_infinite_sphere(custom_coordinate_system="CS_Post", name="Sphere_Custom") - -# ## Retrieve solution data +elevation_ffd_plot = hfss.post.create_report(expressions="db(GainTheta)", + setup_sweep_name=disc_sweep.name, + variations=variations, + primary_sweep_variable="Theta", + context="Elevation", # Far-field setup is pre-defined. + report_category="Far Fields", + plot_type="Radiation Pattern", + plot_name="Elevation Gain (dB)" + ) +elevation_ffd_plot.children["Legend"].properties["Show Trace Name"] = False +elevation_ffd_plot.children["Legend"].properties["Show Solution Name"] = False + +# ### Create a far-field report # -# You can also process solution data using Python libraries like Matplotlib. - -new_report = hfss.post.reports_by_category.far_field( - "GainTotal", hfss.nominal_adaptive, "3D" -) -new_report.primary_sweep = "Theta" -new_report.far_field_sphere = "3D" -new_report.variations["Freq"] = ["1000MHz"] -solutions = new_report.get_solution_data() +# The ``hfss.post.reports_by_category`` helps simplify the syntax required to access +# post-processing capabilities. In this case, results are shown for the current +# variation. The concept of +# "variations" is essential for managing parametric solutions +# in HFSS. +# +# The argument ``sphere_name`` specifies the far-field sphere used to generate the plot. In this case, the far-field sphere "3D" was automatically created when HFSS was launched by instantiating the ``Hfss`` class. +# +# -# Generate a 3D plot using Matplotlib. +# + +report_3d = hfss.post.reports_by_category.far_field("db(RealizedGainTheta)", + disc_sweep.name, + sphere_name="3D", + Freq= [center_freq],) -solutions.plot_3d() +report_3d.report_type = "3D Polar Plot" +report_3d.create(name="Realized Gain (dB)") +# - -# Generate a far fields plot using Matplotlib. +# ### Retrieve solution data for external post-processing +# +# An instance of the ``SolutionData`` class can be created from the report by calling the ``get_solution_data()`` +# method. This class makes data accessible for further post-processing using +# [Matplotlib](https://matplotlib.org/) and is used, for example, to create plots that can be viewed +# directly in the browser or embedded in PDF reports as shown below. -new_report.far_field_sphere = "Sphere_Custom" -solutions_custom = new_report.get_solution_data() -solutions_custom.plot_3d() +report_3d_data = report_3d.get_solution_data() +new_plot = report_3d_data.plot_3d() -# Generate a 2D plot using Matplotlib where you specify whether it is a polar -# plot or a rectangular plot. +# ### View cross-polarization +# +# The dipole is linearly polarized as can be seen from the comparison of $\theta$-polarized +# and $\phi$-polarized "realized gain" at $\theta=90\degree$. The following code creates the gain plots +# in AEDT. -solutions.plot(formula="db20", is_polar=True) +# + +xpol_expressions = ["db(RealizedGainTheta)", "db(RealizedGainPhi)"] +xpol = hfss.post.reports_by_category.far_field(["db(RealizedGainTheta)", "db(RealizedGainPhi)"], + disc_sweep.name, + name="Cross Polarization", + sphere_name="Azimuth", + Freq= [center_freq],) -# ## Retrieve far-field data -# -# After the simulation completes, the far -# field data is generated port by port and stored in a data class. You can use this data -# once AEDT is released. +xpol.report_type = "Radiation Pattern" +xpol.create(name="xpol") +xpol.children["Legend"].properties["Show Solution Name"] = False +xpol.children["Legend"].properties["Show Variation Key"] = False +# - -ffdata = hfss.get_antenna_data( - sphere="Sphere_Custom", - setup=hfss.nominal_adaptive, - frequencies=1 -) +# The ``get_solution_data()`` method is again used to create an inline plot of cross-polarization from +# the report in HFSS. -# ## Generate 2D cutout plot -# -# Generate a 2D cutout plot. You can define the Theta scan -# and Phi scan. +ff_el_data = elevation_ffd_plot.get_solution_data() +ff_el_data.plot(x_label="Theta", y_label="Gain", is_polar=True) -ffdata.farfield_data.plot_cut( - primary_sweep="theta", - secondary_sweep_value=0, - quantity="RealizedGain", - title="FarField", - quantity_format="dB20", - is_polar=True, -) # ## Release AEDT +# + hfss.save_project() -d.release_desktop() +hfss.release_desktop() + # Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory. time.sleep(3) +# - # ## Clean up #