From 2a285519673e8b5f4eccd141d6012402a715c465 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 28 Jan 2024 16:45:05 -0600 Subject: [PATCH 01/56] Update examples Enable Jupytext. --- .github/workflows/full_documentation.yml | 3 +- doc/make.bat | 28 ++ .../_static/{ => thumbnails}/diff_via.png | Bin doc/source/conf.py | 221 +++++++++++--- examples/00-EDB/00_EDB_Create_VIA.py | 77 ++--- examples/00-EDB/01_edb_example.py | 193 +++++++----- examples/00-EDB/02_edb_to_ipc2581.py | 52 ++-- examples/00-EDB/03_5G_antenna_example.py | 281 ------------------ .../03_5G_antenna_example_parametrics.py | 197 +++++------- examples/00-EDB/04_edb_parametrized_design.py | 116 +++----- examples/00-EDB/05_Plot_nets.py | 74 ++--- examples/00-EDB/06_Advanced_EDB.py | 93 +++--- examples/00-EDB/08_CPWG.py | 197 ------------ examples/00-EDB/09_Configuration.py | 170 ++++++----- examples/00-EDB/10_GDS_workflow.py | 95 +++--- .../00-EDB/11_post_layout_parameterization.py | 79 +++-- .../00-EDB/12_edb_sma_connector_on_board.py | 158 +++++----- examples/00-EDB/13_edb_create_component.py | 117 +++++--- .../14_edb_create_parametrized_design.py | 86 ++++-- examples/00-EDB/15_ac_analysis.py | 111 +++---- .../00-EDB}/_static/connector_example.png | Bin examples/00-EDB/_static/diff_via.png | Bin 0 -> 57701 bytes .../edb_example_12_sma_connector_on_board.png | Bin .../00-EDB/_static/parameterized_design.png | Bin .../_static/pcb_transition_parameterized.png | Bin 0 -> 37379 bytes examples/00-EDB/index.rst | 26 ++ examples/01-HFSS3DLayout/Dcir_in_3DLayout.py | 38 ++- examples/01-HFSS3DLayout/EDB_in_3DLayout.py | 66 ++-- examples/01-HFSS3DLayout/HFSS3DLayout_Via.py | 32 +- examples/01-HFSS3DLayout/Hfss3DComponent.py | 67 ++--- 30 files changed, 1140 insertions(+), 1437 deletions(-) rename doc/source/_static/{ => thumbnails}/diff_via.png (100%) delete mode 100644 examples/00-EDB/03_5G_antenna_example.py delete mode 100644 examples/00-EDB/08_CPWG.py rename {doc/source => examples/00-EDB}/_static/connector_example.png (100%) create mode 100644 examples/00-EDB/_static/diff_via.png rename {doc/source => examples/00-EDB}/_static/edb_example_12_sma_connector_on_board.png (100%) rename doc/source/_static/parametrized_design.png => examples/00-EDB/_static/parameterized_design.png (100%) create mode 100644 examples/00-EDB/_static/pcb_transition_parameterized.png create mode 100644 examples/00-EDB/index.rst diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index 38364e830ad..779c3c83162 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -71,7 +71,8 @@ jobs: - name: Create HTML Documentations run: | testenv\Scripts\Activate.ps1 - sphinx-build -j auto --color -b html -a doc/source doc/_build/html + cd doc + .\make.bat html # - name: Create PDF Documentations # run: | diff --git a/doc/make.bat b/doc/make.bat index 5c169485edb..d5a0a904a40 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -11,6 +11,8 @@ set SOURCEDIR=source set BUILDDIR=_build if "%1" == "" goto help +if "%1" == "clean" goto clean +if "%1" == "html" goto html if "%1" == "pdf" goto pdf %SPHINXBUILD% >NUL 2>NUL @@ -32,11 +34,37 @@ goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +:clean +rmdir /s /q %SOURCEDIR%\%BUILDDIR% +for /d /r %SOURCEDIR% %%d in (_autosummary) do @if exist "%%d" rmdir /s /q "%%d" +goto end + +:html +%SPHINXBUILD% -M html %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -v %O% +goto build-examples-py + +:build-examples-py +cd "%BUILDDIR%\html\examples" +for /d %%D in (*) do ( +Echo Processing examples folder... %%D +cd %%D +for %%f in (*.ipynb) do ( + jupytext --to py "%%f" +) +cd ../ +) +goto end + :pdf %SPHINXBUILD% -M latex %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% cd "%BUILDDIR%\latex" for %%f in (*.tex) do ( pdflatex "%%f" --interaction=nonstopmode) +if NOT EXIST pyaedt.pdf ( + Echo "no pdf generated!" + exit /b 1) +Echo "pdf generated!" +goto end :end popd diff --git a/doc/source/_static/diff_via.png b/doc/source/_static/thumbnails/diff_via.png similarity index 100% rename from doc/source/_static/diff_via.png rename to doc/source/_static/thumbnails/diff_via.png diff --git a/doc/source/conf.py b/doc/source/conf.py index a072c1cf724..8d5138903e7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -3,14 +3,16 @@ # -- Project information ----------------------------------------------------- import datetime import os +import re import pathlib import sys import warnings +from sphinx.util import logging import pyvista import numpy as np import json -from sphinx_gallery.sorting import FileNameSortKey +# from sphinx_gallery.sorting import FileNameSortKey from ansys_sphinx_theme import (ansys_favicon, get_version_match, pyansys_logo_black, watermark, @@ -42,6 +44,11 @@ def visit_desc_content(self, node: Element) -> None: # <----------------- End of sphinx pdf builder override----------------> +logger = logging.getLogger(__name__) + +path = pathlib.Path(__file__).parent.parent.parent / "examples" +EXAMPLES_DIRECTORY = path.resolve() + class PrettyPrintDirective(Directive): """Renders a constant using ``pprint.pformat`` and inserts into the document.""" required_arguments = 1 @@ -60,6 +67,18 @@ def run(self): addnodes.desc_content('', literal) ] +# Sphinx event hooks + +def directory_size(directory_path): + """Compute the size (in mega bytes) of a directory.""" + res = 0 + for path, _, files in os.walk(directory_path): + for f in files: + fp = os.path.join(path, f) + res += os.stat(fp).st_size + # Convert in mega bytes + res /= 1e6 + return res def autodoc_skip_member(app, what, name, obj, skip, options): try: @@ -70,19 +89,129 @@ def autodoc_skip_member(app, what, name, obj, skip, options): return True if (skip or exclude or exclude2) else None # Can interfere with subsequent skip functions. # return True if exclude else None - def remove_doctree(app, exception): """Remove the .doctree directory created during the documentation build. """ + size = directory_size(app.doctreedir) + logger.info(f"Removing doctree {app.doctreedir} ({size} MB).") shutil.rmtree(app.doctreedir) + logger.info(f"Doctree removed.") + +def copy_examples(app): + """Copy directory examples (root directory) files into the doc/source/examples directory. + """ + DESTINATION_DIRECTORY = pathlib.Path(app.srcdir, "examples").resolve() + logger.info(f"Copying examples from {EXAMPLES_DIRECTORY} to {DESTINATION_DIRECTORY}.") + if os.path.exists(DESTINATION_DIRECTORY): + size = directory_size(DESTINATION_DIRECTORY) + logger.info(f"Directory {DESTINATION_DIRECTORY} ({size} MB) already exist, removing it.") + shutil.rmtree(DESTINATION_DIRECTORY) + logger.info(f"Directory removed.") + + shutil.copytree(EXAMPLES_DIRECTORY, DESTINATION_DIRECTORY) + logger.info(f"Copy performed") + +def remove_examples(app, exception): + """Remove the doc/source/examples directory created during the documentation build. + """ + DESTINATION_DIRECTORY = pathlib.Path(app.srcdir) / "examples" + size = directory_size(DESTINATION_DIRECTORY) + logger.info(f"Removing directory {DESTINATION_DIRECTORY} ({size} MB).") + shutil.rmtree(DESTINATION_DIRECTORY) + logger.info(f"Directory removed.") + +def add_ipython_time(app, docname, source): + """Add '# %%time' to every code cell in an example Python script. + """ + # Get the full path to the document + docpath = os.path.join(app.srcdir, docname) + + # Check if this is a .py example file + if not os.path.exists(docpath + '.py') or not docname.startswith("examples"): + return + + logger.info(f"Adding '# %%time' to file {docname}.py") + lines = source[0].split("\n") + modified_lines = [] + in_code_cell = False + in_code_cell_plus = False + + for line in lines: + stripped_line = line.strip() + # Detect the start of a new code cell + if stripped_line.startswith('# +'): + in_code_cell = True + in_code_cell_plus = True + modified_lines.append(line) + modified_lines.append('# %%time') + # Detect the end of a code cell + elif stripped_line.startswith('# -'): + in_code_cell = False + in_code_cell_plus = False + modified_lines.append(line) + # Detect already being in a code cell + elif in_code_cell and in_code_cell_plus: + modified_lines.append(line) + elif in_code_cell and not in_code_cell_plus: + # Detect being out of a code cell + if stripped_line == "": + in_code_cell = False + modified_lines.append(line) + elif not in_code_cell: + # Detect the start of a new code cell + if not stripped_line.startswith("# ") and stripped_line not in ("", "#"): + in_code_cell = True + modified_lines.append('# %%time') + modified_lines.append(line) + # Detect already being out of a code cell + else: + modified_lines.append(line) + else: + raise Exception("not handled") + + # Update the source + source[0] = "\n".join(modified_lines) + # logger.info(source[0]) + +def adjust_image_path(app, docname, source): + """Adjust the HTML label used to insert images in the examples. + + The following path makes the examples in the root directory work: + # + However, examples fail when used through the documentation build because + reaching the associated path should be "../../_static/diff_via.png". + Indeed, the directory ``_static`` is automatically copied into the output directory + ``_build/html/_static``. + """ + # Get the full path to the document + docpath = os.path.join(app.srcdir, docname) + + # Check if this is a PY example file + if not os.path.exists(docpath + '.py') or not docname.startswith("examples"): + return + + logger.info(f"Changing HTML image path in '{docname}.py' file.") + source[0] = source[0].replace('../../doc/source/_static', '../../_static') + +def remove_ipython_time_from_html(app, pagename, templatename, context, doctree): + """Remove '# %%time' from examples generated HTML files. + """ + if pagename.startswith("examples") and not pagename.endswith("/index"): + logger.info(f"Removing '# %%time' from file {pagename}") + pattern = r'%%time<\/span>\n' + context['body'] = re.sub(pattern, '', context['body']) def setup(app): app.add_directive('pprint', PrettyPrintDirective) app.connect('autodoc-skip-member', autodoc_skip_member) + app.connect('builder-inited', copy_examples) + app.connect('source-read', add_ipython_time) + app.connect('source-read', adjust_image_path) + app.connect('html-page-context', remove_ipython_time_from_html) + app.connect('build-finished', remove_examples) app.connect('build-finished', remove_doctree) - local_path = os.path.dirname(os.path.realpath(__file__)) module_path = pathlib.Path(local_path) root_path = module_path.parent.parent @@ -120,6 +249,9 @@ def setup(app): # extensions coming with Sphinx_PyAEDT (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "sphinx.ext.graphviz", + "sphinx.ext.mathjax", + "sphinx.ext.inheritance_diagram", "sphinx.ext.intersphinx", "sphinx.ext.autodoc", "sphinx.ext.todo", @@ -129,14 +261,16 @@ def setup(app): "sphinx_copybutton", "sphinx_design", "sphinx_jinja", + "nbsphinx", "recommonmark", - "sphinx.ext.graphviz", - "sphinx.ext.mathjax", - "sphinx.ext.inheritance_diagram", "numpydoc", "ansys_sphinx_theme.extension.linkcode", + # "myst_parser" ] +# MathJax config +# myst_update_mathjax = False + # Intersphinx mapping intersphinx_mapping = { "python": ("https://docs.python.org/3.11", None), @@ -212,7 +346,7 @@ def setup(app): # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "sphinx_boogergreen_theme_1", "Thumbs.db", ".DS_Store", "*.txt"] +exclude_patterns = ["_build", "sphinx_boogergreen_theme_1", "Thumbs.db", ".DS_Store", "*.txt", "conf.py", "Resources/PyAEDTInstallerFromDesktop.py"] inheritance_graph_attrs = dict(rankdir="RL", size='"8.0, 10.0"', fontsize=14, ratio="compress") inheritance_node_attrs = dict(shape="ellipse", fontsize=14, height=0.75, color="dodgerblue1", style="filled") @@ -236,6 +370,22 @@ def setup(app): # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" +# Execute notebooks before convertion +nbsphinx_execute = "always" + +# Allow errors to help debug. +nbsphinx_allow_errors = True + +# Sphinx gallery customization + +nbsphinx_thumbnails = { + "examples/00-EDB/00_EDB_Create_VIA": "_static/thumbnails/diff_via.png", +} + +nbsphinx_custom_formats = { + # ".py": ["jupytext.reads", {"fmt": "", "cell_metadata_filter": "ExecuteTime"}], + ".py": ["jupytext.reads", {"fmt": ""}], +} # Manage errors pyvista.set_error_output_file("errors.txt") @@ -267,33 +417,33 @@ def setup(app): # necessary for pyvista when building the sphinx gallery pyvista.BUILDING_GALLERY = True - if config["run_examples"]: - extensions.append("sphinx_gallery.gen_gallery") - - sphinx_gallery_conf = { - # convert rst to md for ipynb - "pypandoc": True, - # path to your examples scripts - "examples_dirs": ["../../examples/"], - # path where to save gallery generated examples - "gallery_dirs": ["examples"], - # Pattern to search for examples files - "filename_pattern": r"\.py", - # Remove the "Download all examples" button from the top level gallery - "download_all_examples": False, - # Sort gallery examples by file name instead of number of lines (default) - "within_subsection_order": FileNameSortKey, - # directory where function granular galleries are stored - "backreferences_dir": None, - # Modules for which function level galleries are created. In - "doc_module": "ansys-pyaedt", - "image_scrapers": ("pyvista", "matplotlib"), - "ignore_pattern": "flycheck*", - "thumbnail_size": (350, 350), - # 'first_notebook_cell': ("%matplotlib inline\n" - # "from pyvista import set_plot_theme\n" - # "set_plot_theme('document')"), - } + # if config["run_examples"]: + # extensions.append("sphinx_gallery.gen_gallery") + + # sphinx_gallery_conf = { + # # convert rst to md for ipynb + # "pypandoc": True, + # # path to your examples scripts + # "examples_dirs": ["../../examples/"], + # # path where to save gallery generated examples + # "gallery_dirs": ["examples"], + # # Pattern to search for examples files + # "filename_pattern": r"\.py", + # # Remove the "Download all examples" button from the top level gallery + # "download_all_examples": False, + # # Sort gallery examples by file name instead of number of lines (default) + # "within_subsection_order": FileNameSortKey, + # # directory where function granular galleries are stored + # "backreferences_dir": None, + # # Modules for which function level galleries are created. In + # "doc_module": "ansys-pyaedt", + # "image_scrapers": ("pyvista", "matplotlib"), + # "ignore_pattern": "flycheck*", + # "thumbnail_size": (350, 350), + # # 'first_notebook_cell': ("%matplotlib inline\n" + # # "from pyvista import set_plot_theme\n" + # # "set_plot_theme('document')"), + # } jinja_contexts = { "main_toctree": { @@ -362,7 +512,8 @@ def setup(app): # These paths are either relative to html_static_path # or fully qualified paths (eg. https://...) html_css_files = [ - 'custom.css', + 'css/custom.css', + 'css/highlight.css', ] diff --git a/examples/00-EDB/00_EDB_Create_VIA.py b/examples/00-EDB/00_EDB_Create_VIA.py index 4ddbb65ba5d..5cc4f610e6f 100644 --- a/examples/00-EDB/00_EDB_Create_VIA.py +++ b/examples/00-EDB/00_EDB_Create_VIA.py @@ -1,63 +1,45 @@ -""" -EDB: geometry creation ----------------------- -This example shows how you can use EDB to create a layout. -""" -###################################################################### -# Final expected project -# ~~~~~~~~~~~~~~~~~~~~~~ +# # EDB: geometry creation + +# This example shows how you can use EDB to create a layout. +# ## Final expected project +# +# # -# .. image:: ../../_static/diff_via.png -# :width: 600 -# :alt: Differential Vias. -###################################################################### - -###################################################################### -# Import EDB layout object -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Import EDB layout object # Import the EDB layout object and initialize it on version 2023 R2. -###################################################################### +# + import time import os import pyaedt +import tempfile -start = time.time() - -aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("pcb") + ".aedb") +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "create_via.aedb") print(aedb_path) edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") +# - -#################### -# Add stackup layers -# ~~~~~~~~~~~~~~~~~~ +# ## Add stackup layers # Add stackup layers. # A stackup can be created layer by layer or imported from a csv file or xml file. -# edb.stackup.add_layer("GND") edb.stackup.add_layer("Diel", "GND", layer_type="dielectric", thickness="0.1mm", material="FR4_epoxy") edb.stackup.add_layer("TOP", "Diel", thickness="0.05mm") -##################################### -# Create signal net and ground planes -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create signal net and ground planes # Create a signal net and ground planes. -points = [ - [0.0, 0], - [100e-3, 0.0], -] +points = [[0.0, 0], [100e-3, 0.0]] edb.modeler.create_trace(points, "TOP", width=1e-3) points = [[0.0, 1e-3], [0.0, 10e-3], [100e-3, 10e-3], [100e-3, 1e-3], [0.0, 1e-3]] edb.modeler.create_polygon(points, "TOP") - points = [[0.0, -1e-3], [0.0, -10e-3], [100e-3, -10e-3], [100e-3, -1e-3], [0.0, -1e-3]] edb.modeler.create_polygon(points, "TOP") -####################################### -# Create vias with parametric positions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Create vias with parametric positions # Create vias with parametric positions. edb.padstacks.create("MyVia") @@ -71,27 +53,24 @@ edb.padstacks.place([45e-3, -5e-3], "MyVia") -####################################### -# Geometry Plot -# ~~~~~~~~~~~~~ -# +# ## Geometry Plot + edb.nets.plot(None, color_by_net=True) -####################################### -# Stackup Plot -# ~~~~~~~~~~~~ -# -edb.stackup.plot(plot_definitions="MyVia") +# ## Stackup Plot +edb.stackup.plot(plot_definitions="MyVia") -#################### -# Save and close EDB -# ~~~~~~~~~~~~~~~~~~ +# ## Save and close EDB # Save and close EDB. if edb: edb.save_edb() edb.close_edb() print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) -end = time.time() - start -print(end) + +# ### Temp Directory Cleanup +# +# The following command removes the project and the temporary directory. If you'd like to save this project, save it to a folder of your choice prior to running the following cell. + +temp_dir.cleanup() diff --git a/examples/00-EDB/01_edb_example.py b/examples/00-EDB/01_edb_example.py index 62c016f2c29..361b69684fe 100644 --- a/examples/00-EDB/01_edb_example.py +++ b/examples/00-EDB/01_edb_example.py @@ -1,39 +1,40 @@ """ -EDB: Siwave analysis from EDB setup ------------------------------------ -This example shows how you can use EDB to interact with a layout. +# EDB: SIwave DC-IR Analysis + +This example demonstrates the use of EDB to interact with a PCB +layout and run DC-IR analysis in SIwave. """ ############################################################################### # Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. import os import time import pyaedt +import tempfile -temp_folder = pyaedt.generate_unique_folder_name() -targetfile = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_folder) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +targetfile = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', + destination=temp_dir.name) siwave_file = os.path.join(os.path.dirname(targetfile), "ANSYS-HSD_V1.siw") print(targetfile) aedt_file = targetfile[:-4] + "aedt" - ############################################################################### -# Launch EDB -# ~~~~~~~~~~ -# Launch the :class:`pyaedt.Edb` class, using EDB 2023 R2 and SI units. +# ## Electronics Database (EDB) +# +# Instantiate an instance of the `pyaedt.Edb` class +# using EDB 2023 R2 and SI units. edb_version = "2023.2" if os.path.exists(aedt_file): os.remove(aedt_file) edb = pyaedt.Edb(edbpath=targetfile, edbversion=edb_version) ############################################################################### -# Compute nets and components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Computes nets and components. -# There are queries for nets, stackups, layers, components, and geometries. +# ## Identify nets and components +# +# The ``Edb.nets.netlist`` and ``Edb.components.components`` propreties contain information +# about all of the nets and components. The following cell uses this information to print the number of nets and components. print("Nets {}".format(len(edb.nets.netlist))) start = time.time() @@ -41,87 +42,122 @@ print("elapsed time = ", time.time() - start) ############################################################################### -# Get pin position -# ~~~~~~~~~~~~~~~~ -# Get the position for a specific pin. -# The next section shows how to get all pins for a specific component and -# the positions of each of them. -# Each pin is a list of ``[X, Y]`` coordinate positions. +# ## Identify Pin Positions +# +# The next section shows how to obtain all pins for a specific component and +# print the ``[x, y]`` position of each pin. pins = edb.components["U2"].pins +count = 0 for pin in edb.components["U2"].pins.values(): - print(pin.position) - -############################################################################### -# Get all nets connected to a component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Get all nets connected to a specific component. - -edb.components.get_component_net_connection_info("U2") - -############################################################################### -# Compute rats -# ~~~~~~~~~~~~ -# Computes rats. + if count < 10: # Only print the first 10 pin coordinates. + print(pin.position) + elif count == 10: + print("...and many more.") + else: + pass + count += 1 + +############################################################################### +# Get all nets connected to a specific component. Print +# the pin and the name of the net to which it is connected. + +connections = edb.components.get_component_net_connection_info("U2") +n_print = 0 # Counter to limite the number of printed lines. +print_max = 15 +for m in range(len(connections["pin_name"])): + ref_des = connections["refdes"][m] + pin_name = connections["pin_name"][m] + net_name = connections["net_name"][m] + if net_name != "" and (n_print < print_max): + print("{}, pin {} -> net \"{}\"".format(ref_des, pin_name, net_name)) + n_print += 1 + elif n_print == print_max: + print("...and many more.") + n_print += 1 + +############################################################################### +# Compute rats. rats = edb.components.get_rats() ############################################################################### -# Get all DC-connected net lists through inductance -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Get all DC-connected net lists through inductance. -# The inputs needed are ground net lists. The returned list contains all nets -# connected to a ground through an inductor. +# ## Idenify Connected Nets +# +# The method ``get_dcconnected_net_list()`` retrieves a list of +# all DC-connected power nets. Each group of connected nets is returned +# as a [set](https://docs.python.org/3/tutorial/datastructures.html#sets) +# The first argument to the method is the list of ground nets which will +# not be considered in the search for connected nets. GROUND_NETS = ["GND", "GND_DP"] dc_connected_net_list = edb.nets.get_dcconnected_net_list(GROUND_NETS) -print(dc_connected_net_list) +for pnets in dc_connected_net_list: + print(pnets) ############################################################################### -# Get power tree based on a specific net -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Get the power tree based on a specific net. +# ## Power Tree +# +# The power tree provides connectivity through all components from the VRM to +# the device. VRM = "U1" OUTPUT_NET = "AVCC_1V3" powertree_df, component_list_columns, net_group = edb.nets.get_powertree(OUTPUT_NET, GROUND_NETS) + +############################################################################### +# Print some information about the power tree. + +print_columns = ["refdes", "pin_name", "component_partname"] +ncol = [component_list_columns.index(c) for c in print_columns] + +# This prints the header. Replace "pin_name" with "pin" to +# make the header align with the values. +print("\t".join(print_columns).replace("pin_name", "pin")) + for el in powertree_df: - print(el) + s = "" + count = 0 + for e in el: + if count in ncol: + s += "{}\t".format(e) + count += 1 + s.rstrip() + print(s) ############################################################################### -# Delete all RLCs with only one pin -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Delete all RLCs with only one pin. This method provides a useful way of -# removing components not needed in the simulation. +# ## Remove Unused Components +# +# Delete all RLC components that are connected with only one pin. +# The method ``Edb.components.delete_single_pin_rlc()`` +# provides a useful way to +# remove components that are not needed for the simulation. edb.components.delete_single_pin_rlc() ############################################################################### -# Delete components -# ~~~~~~~~~~~~~~~~~ -# Delete manually one or more components. +# Unused components can also be removed explicitly by name. edb.components.delete("C380") ############################################################################### -# Delete nets -# ~~~~~~~~~~~ -# Delete manually one or more nets. +# Nets can also be removed explicitly. edb.nets.delete("PDEN") ############################################################################### -# Get stackup limits -# ~~~~~~~~~~~~~~~~~~ -# Get the stackup limits (top and bottom layers and elevations). - -print(edb.stackup.limits()) - +# Print the top and bottom +# elevation of the stackup obtained using +# the method ``Edb.stackup.limits()``. +s = "Top layer name: \"{top}\", Elevation: {top_el:.2f} " +s += "mm\nBottom layer name: \"{bot}\", Elevation: {bot_el:2f} mm" +top, top_el, bot, bot_el = edb.stackup.limits() +print(s.format(top = top, top_el = top_el*1E3, bot = bot, bot_el = bot_el*1E3)) ############################################################################### -# Create voltage source and Siwave DCIR analysis -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Setup for SIwave DCIR analysis +# # Create a voltage source and then set up a DCIR analysis. edb.siwave.create_voltage_source_on_net("U1", "AVCC_1V3", "U1", "GND", 1.3, 0, "V1") @@ -131,12 +167,10 @@ setup.set_dc_slider = 0 setup.add_source_terminal_to_ground("V1", 1) - - ############################################################################### -# Save modifications -# ~~~~~~~~~~~~~~~~~~ -# Save modifications. +# ## Solve +# +# Save the modifications and run the analysis in SIwave. edb.save_edb() edb.nets.plot(None, "1_Top",plot_components_on_top=True) @@ -144,28 +178,31 @@ siw_file = edb.solve_siwave() ############################################################################### -# Export Siwave Reports -# ~~~~~~~~~~~~~~~~~~~~~ -# Export all DC Reports quantities. +# ## Export Results +# +# Export all quantities calculated from the DC-IR analysis. The following method runs SIwave in batch mode from the command line. Results are written to the edb folder. outputs = edb.export_siwave_dc_results(siw_file, setup.name, ) ############################################################################### -# Close EDB -# ~~~~~~~~~ -# Close EDB. After EDB is closed, it can be opened by AEDT. +# Close the EDB. After EDB is closed, it can be opened by AEDT. edb.close_edb() ############################################################################### -# Postprocess in Siwave -# ~~~~~~~~~~~~~~~~~~~~~ -# Open Siwave and generate a report. This works on Window only. +# ## View Layout in SIwave +# +# The SIwave user interface can be visualized and manipulated +# using the SIwave user interface. This command works on Window OS only. -# from pyaedt import Siwave -# siwave = Siwave("2023.2") +# siwave = pyaedt.Siwave("2023.2") # siwave.open_project(siwave_file) # report_file = os.path.join(temp_folder,'Ansys.htm') # # siwave.export_siwave_report("myDCIR_4", report_file) # siwave.close_project() # siwave.quit_application() + +############################################################################### +# Clean up the temporary files and directory. + +temp_dir.cleanup() diff --git a/examples/00-EDB/02_edb_to_ipc2581.py b/examples/00-EDB/02_edb_to_ipc2581.py index f5733929697..86cf1d622de 100644 --- a/examples/00-EDB/02_edb_to_ipc2581.py +++ b/examples/00-EDB/02_edb_to_ipc2581.py @@ -5,49 +5,38 @@ """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports, which includes importing a section. import os import pyaedt +import tempfile ############################################################################### -# Download file -# ~~~~~~~~~~~~~ # Download the AEDB file and copy it in the temporary folder. - -temp_folder = pyaedt.generate_unique_folder_name() -targetfile = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_folder) - - -ipc2581_file = os.path.join(temp_folder, "Ansys_Hsd.xml") - +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +targetfile = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', + destination=temp_dir.name) +ipc2581_file_name = os.path.join(temp_dir.name, "Ansys_Hsd.xml") print(targetfile) - ############################################################################### -# Launch EDB -# ~~~~~~~~~~ -# Launch the :class:`pyaedt.Edb` class, using EDB 2023 R2 and SI units. +# ## Launch EDB +# +# Launch the `pyaedt.Edb` class, using Verson 2023. +# > Note that length dimensions passed to Edb are in SI units. edb = pyaedt.Edb(edbpath=targetfile, edbversion="2023.2") - ############################################################################### -# Parametrize net -# ~~~~~~~~~~~~~~~ -# Parametrize a net. +# Parametrize the width of a trace. edb.modeler.parametrize_trace_width( "A0_N", parameter_name=pyaedt.generate_unique_name("Par"), variable_value="0.4321mm" ) ############################################################################### -# Cutout -# ~~~~~~ -# Create a cutout. +# Create a cutout and plot it. signal_list = [] for net in edb.nets.netlist: if "PCIe" in net: @@ -61,25 +50,20 @@ use_pyaedt_extent_computing=True, extent_defeature=0, ) - -############################################################################### -# Plot cutout -# ~~~~~~~~~~~ -# Plot cutout before exporting to IPC2581 file. - edb.nets.plot(None, None, color_by_net=True) ############################################################################### -# Create IPC2581 file -# ~~~~~~~~~~~~~~~~~~~ -# Create the IPC2581 file. +# Export the EDB to IPC2581 file. edb.export_to_ipc2581(ipc2581_file, "inch") -print("IPC2581 File has been saved to {}".format(ipc2581_file)) +print("IPC2581 File has been saved to {}".format(ipc2581_file_name)) ############################################################################### # Close EDB -# ~~~~~~~~~ -# Close EDB. edb.close_edb() + +############################################################################### +# Clean up the temporary directory + +temp_dir.cleanup() diff --git a/examples/00-EDB/03_5G_antenna_example.py b/examples/00-EDB/03_5G_antenna_example.py deleted file mode 100644 index 4d490fccef5..00000000000 --- a/examples/00-EDB/03_5G_antenna_example.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -EDB: 5G linear array antenna ----------------------------- -This example shows how you can use HFSS 3D Layout to create and solve a 5G linear array antenna. -""" - -########################################################## -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. - -import tempfile -import pyaedt -import os - -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. The default is ``False``. - -non_graphical = False - - -class Patch: - def __init__(self, width=0.0, height=0.0, position=0.0): - self.width = width - self.height = height - self.position = position - - @property - def points(self): - return [ - [self.position, -self.height / 2], - [self.position + self.width, -self.height / 2], - [self.position + self.width, self.height / 2], - [self.position, self.height / 2], - ] - - -class Line: - def __init__(self, length=0.0, width=0.0, position=0.0): - self.length = length - self.width = width - self.position = position - - @property - def points(self): - return [ - [self.position, -self.width / 2], - [self.position + self.length, -self.width / 2], - [self.position + self.length, self.width / 2], - [self.position, self.width / 2], - ] - - -class LinearArray: - def __init__(self, nb_patch=1, array_length=10e-3, array_width=5e-3): - self.nbpatch = nb_patch - self.length = array_length - self.width = array_width - - @property - def points(self): - return [ - [-1e-3, -self.width / 2 - 1e-3], - [self.length + 1e-3, -self.width / 2 - 1e-3], - [self.length + 1e-3, self.width / 2 + 1e-3], - [-1e-3, self.width / 2 + 1e-3], - ] - - -tmpfold = tempfile.gettempdir() -aedb_path = os.path.join(tmpfold, pyaedt.generate_unique_name("pcb") + ".aedb") -print(aedb_path) -edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") - - -############################################################################### -# Add stackup layers -# ~~~~~~~~~~~~~~~~~~ -# Add the stackup layers. -# -if edb: - edb.stackup.add_layer("Virt_GND") - edb.stackup.add_layer("Gap", "Virt_GND", layer_type="dielectric", thickness="0.05mm", material="Air") - edb.stackup.add_layer("GND", "Gap") - edb.stackup.add_layer("Substrat", "GND", layer_type="dielectric", thickness="0.5mm", material="Duroid (tm)") - edb.stackup.add_layer("TOP", "Substrat") - -############################################################################### -# Create linear array -# ~~~~~~~~~~~~~~~~~~~ -# Create the first patch of the linear array. - -first_patch = Patch(width=1.4e-3, height=1.2e-3, position=0.0) -edb.modeler.create_polygon(first_patch.points, "TOP", net_name="Array_antenna") -# First line -first_line = Line(length=2.4e-3, width=0.3e-3, position=first_patch.width) -edb.modeler.create_polygon(first_line.points, "TOP", net_name="Array_antenna") - -############################################################################### -# Patch linear array -# ~~~~~~~~~~~~~~~~~~ -# Patch the linear array. - -patch = Patch(width=2.29e-3, height=3.3e-3) -line = Line(length=1.9e-3, width=0.2e-3) -linear_array = LinearArray(nb_patch=8, array_width=patch.height) - -current_patch = 1 -current_position = first_line.position + first_line.length - -while current_patch <= linear_array.nbpatch: - patch.position = current_position - edb.modeler.create_polygon(patch.points, "TOP", net_name="Array_antenna") - current_position += patch.width - if current_patch < linear_array.nbpatch: - line.position = current_position - edb.modeler.create_polygon(line.points, "TOP", net_name="Array_antenna") - current_position += line.length - current_patch += 1 - -linear_array.length = current_position - - -############################################################################### -# Add ground -# ~~~~~~~~~~ -# Add a ground. - -edb.modeler.create_polygon(linear_array.points, "GND", net_name="GND") - - -############################################################################### -# Add connector pin -# ~~~~~~~~~~~~~~~~~ -# Add a central connector pin. - -edb.padstacks.create(padstackname="Connector_pin", holediam="100um", paddiam="0", antipaddiam="200um") -con_pin = edb.padstacks.place( - [first_patch.width / 4, 0], - "Connector_pin", - net_name="Array_antenna", - fromlayer="TOP", - tolayer="GND", - via_name="coax", -) - - -############################################################################### -# Add connector ground -# ~~~~~~~~~~~~~~~~~~~~ -# Add a connector ground. - -edb.modeler.create_polygon(first_patch.points, "Virt_GND", net_name="GND") -edb.padstacks.create("gnd_via", "100um", "0", "0") -con_ref1 = edb.padstacks.place( - [first_patch.points[0][0] + 0.2e-3, first_patch.points[0][1] + 0.2e-3], - "gnd_via", - fromlayer="GND", - tolayer="Virt_GND", - net_name="GND", -) -con_ref2 = edb.padstacks.place( - [first_patch.points[1][0] - 0.2e-3, first_patch.points[1][1] + 0.2e-3], - "gnd_via", - fromlayer="GND", - tolayer="Virt_GND", - net_name="GND", -) -con_ref3 = edb.padstacks.place( - [first_patch.points[2][0] - 0.2e-3, first_patch.points[2][1] - 0.2e-3], - "gnd_via", - fromlayer="GND", - tolayer="Virt_GND", - net_name="GND", -) -con_ref4 = edb.padstacks.place( - [first_patch.points[3][0] + 0.2e-3, first_patch.points[3][1] - 0.2e-3], - "gnd_via", - fromlayer="GND", - tolayer="Virt_GND", - net_name="GND", -) - - -############################################################################### -# Add excitation port -# ~~~~~~~~~~~~~~~~~~~ -# Add an excitation port. - -edb.padstacks.set_solderball(con_pin, "Virt_GND", isTopPlaced=False, ballDiam=0.1e-3) -port_name = edb.padstacks.create_coax_port(con_pin) - - -############################################################################### -# Plot geometry -# ~~~~~~~~~~~~~ -# Plot the geometry. - -edb.nets.plot(None) - -############################################################################### -# Save and close Edb instance prior to opening it in Electronics Desktop. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Save EDB. - -edb.save_edb() -edb.close_edb() -print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) -############################################################################### -# Launch HFSS 3D Layout and open EDB -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Launch HFSS 3D Layout and open EDB. - -h3d = pyaedt.Hfss3dLayout(projectname=aedb_path, specified_version="2023.2", new_desktop_session=True, - non_graphical=non_graphical) - -############################################################################### -# Plot geometry -# ~~~~~~~~~~~~~~~~~ -# Plot the geometry. The EDB methods are also accessible from the ``Hfss3dlayout`` class. - -h3d.modeler.edb.nets.plot(None) - -############################################################################### -# Create setup and sweeps -# ~~~~~~~~~~~~~~~~~~~~~~~ -# Getters and setters facilitate the settings on the nested property dictionary. -# Previously, you had to use these commands: -# -# - ``setup.props["AdaptiveSettings"]["SingleFrequencyDataList"]["AdaptiveFrequencyData"]["AdaptiveFrequency"] = "20GHz"`` -# - ``setup.props["AdaptiveSettings"]["SingleFrequencyDataList"]["AdaptiveFrequencyData"]["MaxPasses"] = 4`` -# -# You can now use the simpler approach that follows. - -setup = h3d.create_setup() - -setup["AdaptiveFrequency"] = "20GHz" -setup["AdaptiveSettings/SingleFrequencyDataList/AdaptiveFrequencyData/MaxPasses"] = 4 -h3d.create_linear_count_sweep( - setupname=setup.name, - unit="GHz", - freqstart=20, - freqstop=50, - num_of_freq_points=1001, - sweepname="sweep1", - sweep_type="Interpolating", - interpolation_tol_percent=1, - interpolation_max_solutions=255, - save_fields=False, - use_q3d_for_dc=False, -) - - -############################################################################### -# Solve setup and create report -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Solve the project and create a report. - -h3d.analyze() -h3d.post.create_report(["db(S({0},{1}))".format(port_name, port_name)]) - - -############################################################################### -# Plot results outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Plot results using Matplotlib. - -solution = h3d.post.get_solution_data(["S({0},{1})".format(port_name, port_name)]) -solution.plot() - -############################################################################### -# Close AEDT -# ~~~~~~~~~~ -# After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.release_desktop` method. -# All methods provide for saving the project before closing AEDT. - -h3d.save_project() -h3d.release_desktop() diff --git a/examples/00-EDB/03_5G_antenna_example_parametrics.py b/examples/00-EDB/03_5G_antenna_example_parametrics.py index 771b745f6b8..35ff065fbf8 100644 --- a/examples/00-EDB/03_5G_antenna_example_parametrics.py +++ b/examples/00-EDB/03_5G_antenna_example_parametrics.py @@ -1,12 +1,13 @@ """ EDB: Layout Components ---------------------- -This example shows how you can use EDB to create a layout component parametrics and use it in HFSS 3D. +This example shows how you can use EDB to create a parametric component using + 3D Layout and use it in HFSS 3D. """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports, which includes importing the ``Hfss3dlayout`` object # and initializing it on version 2023 R2. @@ -14,19 +15,14 @@ import pyaedt import os - ########################################################## # Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. The default is ``False``. - - non_graphical = False ########################################################## -# Creating data classes -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Creating data classes +# # Data classes are useful to do calculations and store variables. # We create 3 Data classes for Patch, Line and Array @@ -77,23 +73,18 @@ def points(self): [-1e-3, "{}/2+1e-3".format(self.width)], ] + ############################################################################### -# Launch EDB -# ~~~~~~~~~~ +# ## Launch EDB +# # PyAEDT.Edb allows to open existing Edb project or create a new empty project. - -tmpfold = tempfile.gettempdir() -aedb_path = os.path.join(tmpfold, pyaedt.generate_unique_name("pcb") + ".aedb") -print(aedb_path) -edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "linear_array.aedb") +edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") # Create an instance of the Edb class. ############################################################################### # Add stackup layers -# ~~~~~~~~~~~~~~~~~~ -# Add the stackup layers. - - edb.stackup.add_layer("Virt_GND") edb.stackup.add_layer("Gap", "Virt_GND", layer_type="dielectric", thickness="0.05mm", material="Air") @@ -102,29 +93,24 @@ def points(self): edb.stackup.add_layer("TOP", "Substrat") ############################################################################### -# Create linear array -# ~~~~~~~~~~~~~~~~~~~ -# Create the first patch of the linear array. - - +# Create the the first patch and feed line using the ``Patch``, ``Line``classes defined above. +# Define parameters: edb["w1"] = 1.4e-3 edb["h1"] = 1.2e-3 edb["initial_position"] = 0.0 edb["l1"] = 2.4e-3 edb["trace_w"] = 0.3e-3 + first_patch = Patch(width="w1", height="h1", position="initial_position") edb.modeler.create_polygon(first_patch.points, "TOP", net_name="Array_antenna") -# First line +# First line first_line = Line(length="l1", width="trace_w", position=first_patch.width) edb.modeler.create_polygon(first_line.points, "TOP", net_name="Array_antenna") ############################################################################### -# Patch linear array -# ~~~~~~~~~~~~~~~~~~ -# Patch the linear array. - +# Now use the ``LinearArray`` class to create the array. edb["w2"] = 2.29e-3 edb["h2"] = 3.3e-3 @@ -151,18 +137,12 @@ def points(self): linear_array.length = current_position ############################################################################### -# Add ground -# ~~~~~~~~~~ -# Add a ground. - +# Add the ground conductor. edb.modeler.create_polygon(linear_array.points, "GND", net_name="GND") ############################################################################### -# Add connector pin -# ~~~~~~~~~~~~~~~~~ -# Add a central connector pin. - +# Add connector pin that will be used to assign the port. edb.padstacks.create(padstackname="Connector_pin", holediam="100um", paddiam="0", antipaddiam="200um") con_pin = edb.padstacks.place( @@ -175,11 +155,8 @@ def points(self): ) ############################################################################### -# Add connector ground -# ~~~~~~~~~~~~~~~~~~~~ # Add a connector ground. - edb.modeler.create_polygon(first_patch.points, "Virt_GND", net_name="GND") edb.padstacks.create("gnd_via", "100um", "0", "0") edb["via_spacing"] = 0.2e-3 @@ -214,113 +191,98 @@ def points(self): ################################################################################ -# Add excitation port -# ~~~~~~~~~~~~~~~~~~~ -# Add an excitation port. - +# Define the port. edb.padstacks.set_solderball(con_pin, "Virt_GND", isTopPlaced=False, ballDiam=0.1e-3) port_name = edb.padstacks.create_coax_port(con_pin) - ############################################################################### -# Plot geometry -# ~~~~~~~~~~~~~ -# Plot the geometry. - +# Display the model using the ``Edb.nets.plot()`` method. edb.nets.plot() - ############################################################################### -# Save and close Edb instance prior to opening it in Electronics Desktop. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Save EDB. - +# The EDB is complete. Now close the EDB and import it into HFSS as a "Layout Component". edb.save_edb() edb.close_edb() print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) - ############################################################################### -# Launch HFSS 3D -# ~~~~~~~~~~~~~~ -# Launch HFSS 3D. - -h3d = pyaedt.Hfss(specified_version="2023.2", new_desktop_session=True, close_on_exit=True, solution_type="Terminal") +# ## 3D Component in HFSS +# +# First create an instance of the ``pyaedt.Hfss`` class. If you set +# > ``non_graphical = False +# +# then AEDT user interface will be visible after the following cell is executed. It is now possible to monitor the progress in the UI as each of the following cells is executed. All commands can be run without the UI by chaning the value of ``non_graphical``. + +h3d = pyaedt.Hfss(projectname="Demo_3DComp", + designname="Linear_Array", + specified_version="2023.2", + new_desktop_session=True, + non_graphical=non_graphical, + close_on_exit=True, + solution_type="Terminal") + +# Set units to mm. +h3d.modeler.model_units = "mm" ############################################################################### -# Add the layout component -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Hfss allows user to add Layout components (aedb) or 3D Components into a 3D Design -# and benefit of different functionalities like parametrization, mesh fusion and others. - +# ## Import the EDB as a 3D Component +# +# One or more layout components can be imported into HFSS. The combination of layout data and 3D CAD data helps streamline +# model creation and setup. component = h3d.modeler.insert_layout_component(aedb_path, parameter_mapping=True) ############################################################################### -# Edit Parameters -# ~~~~~~~~~~~~~~~ +# ## Expose the Component Paramers +# # If a layout component is parametric, parameters can be exposed and changed in HFSS - component.parameters w1_name = "{}_{}".format("w1", h3d.modeler.user_defined_component_names[0]) h3d[w1_name]= 0.0015 ############################################################################### -# Boundaries -# ~~~~~~~~~~ -# To run the simulation we need an airbox to which apply radiation boundaries. -# We don't need to create ports because are embedded in layout component. - +# ### Radiation Boundary Assignment +# +# The 3D domain includes the air volume surrounding the antenna. This antenna will be simulted from 20 GHz - 50 GHz. +# +# A "radiation boundary" will be assigned to the outer boundaries of the domain. This boundary should be roughly one quarter wavelength away from the radiating strucure: +# +# $$ \lambda/4 = \frac{c_0}{4 f} \approx 2.8mm $$ h3d.modeler.fit_all() - -h3d.modeler.create_air_region(130,400,1000, 130,400,300) +h3d.modeler.create_air_region(2.8, 2.8, 2.8, 2.8, 2.8, 2.8, is_percentage=False) h3d.assign_radiation_boundary_to_objects("Region") ############################################################################### -# Create setup and sweeps -# ~~~~~~~~~~~~~~~~~~~~~~~ -# Getters and setters facilitate the settings on the nested property dictionary. -# -# - ``setup.props['Frequency']="20GHz"`` -# -# -# You can now use the simpler approach that follows. -# -# - +# ### Analysis Setup +# +# The finite element mesh is adapted iteratively. The maximum number of adaptive passes is set using the ``MaximumPasses`` property. This model will converge such that the $S_{11}$ will be independent of the mesh. The default accuracy setting is: +# $$ \max(|\Delta S|) < 0.02 $$ setup = h3d.create_setup() - setup.props['Frequency']="20GHz" -setup.props['MaximumPasses'] = 2 +setup.props['MaximumPasses'] = 10 -sweep1 = setup.add_sweep() +# Specify properties of the frequency sweep: +sweep1 = setup.add_sweep(sweepname="20GHz_to_50GHz") sweep1.props["RangeStart"]="20GHz" sweep1.props["RangeEnd"]="50GHz" sweep1.update() ############################################################################### -# Solve setup and create report -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Solve the project and create a report. - - +# Solve the project h3d.analyze() - - - ############################################################################### -# Plot results outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot results outside AEDT +# # Plot results using Matplotlib. trace = h3d.get_traces_for_plot() @@ -329,8 +291,8 @@ def points(self): ################################################################################ -# Plot Far Fields in AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot Far Fields in AEDT +# # Plot Radiation patterns in AEDT. @@ -348,8 +310,8 @@ def points(self): ################################################################################ -# Plot Far Fields in AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot Far Fields in AEDT +# # Plot Radiation patterns in AEDT. @@ -360,21 +322,18 @@ def points(self): ################################################################################ -# Plot Far Fields outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot Far Fields outside AEDT +# # Plot Radiation patterns outside AEDT. - - solutions_custom = new_report.get_solution_data() solutions_custom.plot_3d() ################################################################################ -# Plot E Field on nets and layers -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot E Field on nets and layers +# # Plot E Field on nets and layers in AEDT. - h3d.post.create_fieldplot_layers_nets( [["TOP","Array_antenna"]], "Mag_E", @@ -382,17 +341,19 @@ def points(self): plot_name="E_Layers", ) - ############################################################################### -# Close AEDT -# ~~~~~~~~~~ -# After the simulation completes, you can close AEDT or release it using the +# ## Close AEDT +# +# After the simulation completes, the application can be released from the # :func:`pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing AEDT. - h3d.save_project(os.path.join(tmpfold, "test_layout.aedt")) h3d.release_desktop() +############################################################################### +# ### Temp Directory Cleanup +# +# The following command removes the project and the temporary directory. If you'd like to save this project, save it to a folder of your choice prior to running the following cell. - +temp_dir.cleanup() diff --git a/examples/00-EDB/04_edb_parametrized_design.py b/examples/00-EDB/04_edb_parametrized_design.py index abc2ed53286..9651cd2b356 100644 --- a/examples/00-EDB/04_edb_parametrized_design.py +++ b/examples/00-EDB/04_edb_parametrized_design.py @@ -1,37 +1,33 @@ """ -EDB: fully parametrized design ------------------------------- -This example shows how you can use HFSS 3D Layout to create and solve a parametric design. -""" +# EDB: fully parametrized design -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports, which includes importing the ``Hfss3dlayout`` object -# and initializing it on version 2023 R2. +This example shows how to use the EDB interface along with HFSS 3D Layout to create and solve a parameterized layout. The layout shows a differential via transition on a printed circuit board with back-to-back microstrip to stripline transitions. The model is fully parameterized to enable investigation of the transition performance on the many degrees of freedom. + +The resulting model is shown below + + +""" import pyaedt import os +import tempfile ########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. The default is ``False``. +# ## Set non-graphical mode +# +# Set non-graphical mode. The default is ``False`` in order to open +# the AEDT user interface. non_graphical = False ########################################################## -# Launch EDB -# ~~~~~~~~~~ # Launch EDB. -aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("pcb") + ".aedb") -print(aedb_path) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "pcb.aedb") edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") ###################################################################### -# Define parameters -# ~~~~~~~~~~~~~~~~~ # Define the parameters. params = {"$ms_width": "0.4mm", @@ -42,7 +38,7 @@ "$via_diam": "0.3mm", "$pad_diam": "0.6mm", "$anti_pad_diam": "0.7mm", - "$pcb_len": "30mm", + "$pcb_len": "15mm", "$pcb_w": "5mm", "$x_size": "1.2mm", "$y_size": "1mm", @@ -52,11 +48,8 @@ edb.add_project_variable(par_name, params[par_name]) ###################################################################### -# Define stackup layers -# ~~~~~~~~~~~~~~~~~~~~~ # Define the stackup layers from bottom to top. - layers = [{"name": "bottom", "layer_type": "signal", "thickness": "35um", "material": "copper"}, {"name": "diel_3", "layer_type": "dielectric", "thickness": "275um", "material": "FR4_epoxy"}, {"name": "sig_2", "layer_type": "signal", "thickness": "35um", "material": "copper"}, @@ -68,15 +61,16 @@ # Create EDB stackup. # Bottom layer + prev = None for layer in layers: - edb.stackup.add_layer(layer["name"], base_layer=prev, layer_type=layer["layer_type"], thickness=layer["thickness"], - material=layer["material"]) + edb.stackup.add_layer(layer["name"], base_layer=prev, + layer_type=layer["layer_type"], + thickness=layer["thickness"], + material=layer["material"]) prev = layer["name"] ############################################################################### -# Create padstack for signal via -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create a parametrized padstack for the signal via. signal_via_padstack = "automated_via" @@ -94,17 +88,13 @@ ) ############################################################################### -# Assign net names -# ~~~~~~~~~~~~~~~~ -# # Assign net names. There are only two signal nets. +# Assign net names. There are only two signal nets. net_p = "p" net_n = "n" ############################################################################### -# Place signal vias -# ~~~~~~~~~~~~~~~~~ -# Place signal vias. +# Place the signal vias. edb.padstacks.place( position=["$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing)/2"], @@ -139,11 +129,10 @@ ) -# ############################################################################### -# Draw parametrized traces -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Draw parametrized traces. -# Trace the width and the routing (Microstrip-Stripline-Microstrip). +############################################################################### +# ## Draw parametrized traces +# +# Trace width and the routing (Microstrip-Stripline-Microstrip). # Applies to both p and n nets. width = ["$ms_width", "$sl_width", "$ms_width"] # Trace width, n and p @@ -192,10 +181,8 @@ ["$pcb_len", "-($ms_width + $ms_spacing)/2"], ], ] -# ############################################################################### -# Add traces to EDB -# ~~~~~~~~~~~~~~~~~ -# Add traces to EDB. +############################################################################### +# Add traces to the EDB. trace_p = [] trace_n = [] @@ -204,9 +191,7 @@ trace_n.append(edb.modeler.create_trace(points_n[n], route_layer[n], width[n], net_n, "Flat", "Flat")) ############################################################################### -# Create wave ports -# ~~~~~~~~~~~~~~~~~ -# Create wave ports: +# Create the wave ports edb.hfss.create_differential_wave_port(trace_p[0].id, ["0.0", "($ms_width+$ms_spacing)/2"], trace_n[0].id, ["0.0", "-($ms_width+$ms_spacing)/2"], @@ -216,9 +201,7 @@ "wave_port_2") ############################################################################### -# Draw ground polygons -# ~~~~~~~~~~~~~~~~~~~~ -# Draw ground polygons. +# Draw a conducting rectangle on the the ground layers. gnd_poly = [[0.0, "-$pcb_w/2"], ["$pcb_len", "-$pcb_w/2"], @@ -261,25 +244,18 @@ ############################################################################### -# Plot EDB -# ~~~~~~~~ -# Plot EDB. +# Plot the layout. edb.nets.plot(None) ############################################################################### -# Save EDB -# ~~~~~~~~ -# Save EDB. +# Save the EDB. edb.save_edb() edb.close_edb() - ############################################################################### -# Open EDB in AEDT -# ~~~~~~~~~~~~~~~~ -# Open EDB in AEDT. +# Open the project in AEDT 3D Layout. h3d = pyaedt.Hfss3dLayout(projectname=aedb_path, specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) @@ -307,36 +283,28 @@ ) ############################################################################### -# Set Differential Pairs. -# ~~~~~~~~~~~~~~~~~~~~~~ -# Define the differential pairs to be used in the postprocessing. +# Define the differential pairs to be used to calculte differential and common mode +# s-parameters. h3d.set_differential_pair(diff_name="In", positive_terminal="wave_port_1:T1", negative_terminal="wave_port_1:T2") h3d.set_differential_pair(diff_name="Out", positive_terminal="wave_port_2:T1", negative_terminal="wave_port_2:T2") ############################################################################### -# Start HFSS solver -# ~~~~~~~~~~~~~~~~~ -# Start the HFSS solver by uncommenting the ``h3d.analyze()`` command. +# Solve the project. h3d.analyze() - ############################################################################### -# Generate Plot -# ~~~~~~~~~~~~~ -# Generate the plot of differential pairs. +# Plot the results and shut down the Electronics Desktop. solutions = h3d.post.get_solution_data(["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs") solutions.plot() - - - - - h3d.release_desktop() ############################################################################### # Note that the ground nets are only connected to each other due # to the wave ports. The problem with poor grounding can be seen in the -# S-parameters. Try to modify this script to add ground vias and eliminate -# the resonance. +# S-parameters. This example can be downloaded as a Jupyter Notebook, so you can modify it. Try changing parameters or adding ground vias to improve performance. +# +# The final cell cleans up the temporary directory, removing all files. + +temp_dir.cleanup() diff --git a/examples/00-EDB/05_Plot_nets.py b/examples/00-EDB/05_Plot_nets.py index 19a909f51cb..5bfe5c2d373 100644 --- a/examples/00-EDB/05_Plot_nets.py +++ b/examples/00-EDB/05_Plot_nets.py @@ -1,65 +1,69 @@ """ -EDB: plot nets with Matplotlib ------------------------------- -This example shows how you can use the ``Edb`` class to plot a net or a layout. -""" +# EDB: plot nets with Matplotlib -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports, which includes importing a section. +This example shows how to use the ``Edb`` class to view nets, layers and +via geometry directly in Python. The methods demonstrated in this example +rely on +[matplotlib](https://matplotlib.org/cheatsheets/_images/cheatsheets-1.png). + +## Perform required imports + +Perform required imports, which includes importing a section. +""" import os import pyaedt +import tempfile ############################################################################### -# Download file -# ~~~~~~~~~~~~~ -# Download the AEDT file and copy it into the temporary folder. - -temp_folder = pyaedt.generate_unique_folder_name() - -targetfolder = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_folder) +# Download the EDB and copy it into the temporary folder. +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +targetfolder = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', + destination=temp_dir.name) ############################################################################### -# Launch EDB -# ~~~~~~~~~~ -# Launch the :class:`pyaedt.Edb` class, using EDB 2023 R2 and SI units. +# Create an instance of the Electronics Database usig the +# `pyaedt.Edb` class. +# +# > Note that units are SI edb = pyaedt.Edb(edbpath=targetfolder, edbversion="2023.2") ############################################################################### -# Plot custom set of nets colored by layer -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Plot a custom set of nets colored by layer (default). +# Display the nets on a layer. Net geometry can be displayed directly in Python usig ``matplotlib`` from +# the ``pyaedt.Edb`` class. edb.nets.plot("AVCC_1V3") ############################################################################### -# Plot custom set of nets colored by nets -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Plot a custom set of nets colored by nets. +# Multiple nets may be viewed by passing a list containing the net +# names to the ``plot`` method. edb.nets.plot(["GND", "GND_DP", "AVCC_1V3"], color_by_net=True) ############################################################################### -# Plot all nets on a layer colored by nets -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Plot all nets on a layer colored by nets +# All copper on a single layer may also be displayed by passing ``None`` +# as the first argument. The 2nd argument is a list +# of layers to be plotted. In this case, only one +# layers is displayed. -edb.nets.plot(None, ["1_Top"], color_by_net=True, plot_components_on_top=True) +edb.nets.plot(None, ["1_Top"], color_by_net=True, + plot_components_on_top=True) ############################################################################### -# Plot stackup and some padstack definition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Plot all nets on a layer colored by nets +# A side-view of the layers and padstack geometry is displayed using the +# ``Edb.stackup.plot()`` method. -edb.stackup.plot(scale_elevation=False,plot_definitions=["c100hn140", "c35"]) +edb.stackup.plot(scale_elevation=False, + plot_definitions=["c100hn140", "c35"]) ############################################################################### -# Close EDB -# ~~~~~~~~~ -# Close EDB. +# Close the EDB. edb.close_edb() + +############################################################################### +# Remove all files and the temporary directory. + +temp_dir.cleanup() diff --git a/examples/00-EDB/06_Advanced_EDB.py b/examples/00-EDB/06_Advanced_EDB.py index b0cfab95b74..fb27d033824 100644 --- a/examples/00-EDB/06_Advanced_EDB.py +++ b/examples/00-EDB/06_Advanced_EDB.py @@ -1,49 +1,44 @@ """ -EDB: parametric via creation ----------------------------- +# EDB: parametric via creation + This example shows how you can use EDB to create a layout. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. +First import the required Python packages. +""" import os import numpy as np import pyaedt +import tempfile +############################################################################### +# Creat the EDB project. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "parametric_via.aedb") -aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("via_opt") + ".aedb") ############################################################################### -# Create stackup -# ~~~~~~~~~~~~~~ +# ## Create stackup +# # The ``StackupSimple`` class creates a stackup based on few inputs. This stackup # is used later. +# +# Define a function to create the ground conductor. - -############################################################################### -# Create ground plane -# ~~~~~~~~~~~~~~~~~~~ -# Create a ground plane on specific layers. - -def _create_ground_planes(edb, layers): +def create_ground_planes(edb, layers): plane = edb.modeler.Shape("rectangle", pointA=["-3mm", "-3mm"], pointB=["3mm", "3mm"]) for i in layers: edb.modeler.create_polygon(plane, i, net_name="GND") - ################################################################################## -# Create EDB -# ~~~~~~~~~~ -# Create EDB. If the path doesn't exist, PyAEDT automatically generates a new AEDB folder. +# ## Create the EDB +# +# Create the Electronics Databas (EDB) instance. If the path doesn't exist, PyAEDT automatically generates a new AEDB folder. edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") ################################################################################## -# Create stackup layers -# ~~~~~~~~~~~~~~~~~~~~~ -# Create stackup layers. +# Insert the stackup layers. layout_count = 12 diel_material_name = "FR4_epoxy" @@ -62,10 +57,11 @@ def _create_ground_planes(edb, layers): ################################################################################## -# Create variables -# ~~~~~~~~~~~~~~~~ -# Create all variables. If a variable has a ``$`` prefix, it is a project variable. -# Otherwise, is a design variable. +# ## Define Parameters +# +# Define parameters to allow changes in the model dimesons. Parameters preceeded by +# the ``$`` character have project-wide scope. +# Without the ``$`` prefix the parameter scope is limited to the design. giva_angle_rad = gvia_angle / 180 * np.pi @@ -77,10 +73,9 @@ def _create_ground_planes(edb, layers): edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) ################################################################################## -# Create definitions -# ~~~~~~~~~~~~~~~~~~ -# Create two definitions, one for the ground and one for the signal. The definitions -# are parametric. +# ## Define padstacks +# +# Create two padstck definitions, one for the ground via and one for the signal via. edb.padstacks.create(padstackname="SVIA", holediam="$via_hole_size", @@ -92,17 +87,12 @@ def _create_ground_planes(edb, layers): edb.padstacks.create(padstackname="GVIA", holediam="0.3mm", antipaddiam="0.7mm", paddiam="0.5mm") ################################################################################## -# Place padstack for signal -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Place the padstack for the signal. +# Place the signal via. edb.padstacks.place([0, 0], "SVIA", net_name="RF") ################################################################################## -# Place padstack for ground -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Place the padstack for the ground. A loop iterates and places multiple ground -# vias on different positions. +# Place the ground vias. gvia_num_side = gvia_num / 2 @@ -132,9 +122,7 @@ def _create_ground_planes(edb, layers): edb.padstacks.place([xloc + "*-1", yloc + "*-1"], "GVIA", net_name="GND") ################################################################################## -# Generate traces -# ~~~~~~~~~~~~~~~ -# Generate and place parametric traces. +# Draw the traces edb.modeler.create_trace( [[0, 0], [0, "-3mm"]], layer_name=trace_in_layer, net_name="RF", width="trace_in_width", start_cap_style="Flat", end_cap_style="Flat" @@ -150,30 +138,27 @@ def _create_ground_planes(edb, layers): ) ################################################################################## -# Generate ground layers -# ~~~~~~~~~~~~~~~~~~~~~~ -# Generate and place ground layers. +# Draw ground conductors ground_layers = [i for i in edb.stackup.signal_layers.keys()] ground_layers.remove(trace_in_layer) ground_layers.remove(trace_out_layer) -_create_ground_planes(edb=edb, layers=ground_layers) +create_ground_planes(edb=edb, layers=ground_layers) ################################################################################## -# Plot Layout -# ~~~~~~~~~~~ -# Generate and plot the layout. +# Display the layout #edb.nets.plot(layers=["TOP", "L10"]) edb.stackup.plot(plot_definitions=["GVIA", "SVIA"]) - ################################################################################## -# Save EDB and close -# ~~~~~~~~~~~~~~~~~~ -# Save EDB and close. +# Save EDB and close the EDB. edb.save_edb() edb.close_edb() - print("aedb Saved in {}".format(aedb_path)) + +############################################################################### +# Clean up the temporary directory. + +temp_dir.cleanup() diff --git a/examples/00-EDB/08_CPWG.py b/examples/00-EDB/08_CPWG.py deleted file mode 100644 index a6401426767..00000000000 --- a/examples/00-EDB/08_CPWG.py +++ /dev/null @@ -1,197 +0,0 @@ -""" -EDB: fully parametrized CPWG design ------------------------------------ -This example shows how you can use HFSS 3D Layout to create a parametric design -for a CPWG (coplanar waveguide with ground). -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. Importing the ``Hfss3dlayout`` object initializes it -# on version 2023 R2. - -import pyaedt -import os -import numpy as np - -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. The default is ``False``. - -non_graphical = False - -############################################################################### -# Launch EDB -# ~~~~~~~~~~ -# Launch EDB. - -aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("pcb") + ".aedb") -print(aedb_path) -edbapp = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") - -############################################################################### -# Define parameters -# ~~~~~~~~~~~~~~~~~ -# Define parameters. - -params = {"$ms_width": "0.4mm", - "$ms_clearance": "0.3mm", - "$ms_length": "20mm", - } -for par_name in params: - edbapp.add_project_variable(par_name, params[par_name]) - -############################################################################### -# Create stackup -# ~~~~~~~~~~~~~~ -# Create a symmetric stackup. - -edbapp.stackup.create_symmetric_stackup(2) -edbapp.stackup.plot() - -############################################################################### -# Draw planes -# ~~~~~~~~~~~ -# Draw planes. - -plane_lw_pt = ["0mm", "-3mm"] -plane_up_pt = ["$ms_length", "3mm"] - -top_layer_obj = edbapp.modeler.create_rectangle("TOP", net_name="gnd", - lower_left_point=plane_lw_pt, - upper_right_point=plane_up_pt) -bot_layer_obj = edbapp.modeler.create_rectangle("BOTTOM", net_name="gnd", - lower_left_point=plane_lw_pt, - upper_right_point=plane_up_pt) -layer_dict = {"TOP": top_layer_obj, - "BOTTOM": bot_layer_obj} - -############################################################################### -# Draw trace -# ~~~~~~~~~~ -# Draw a trace. - -trace_path = [["0", "0"], ["$ms_length", "0"]] -edbapp.modeler.create_trace(trace_path, - layer_name="TOP", - width="$ms_width", - net_name="sig", - start_cap_style="Flat", - end_cap_style="Flat" - ) - -############################################################################### -# Create trace to plane clearance -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a trace to the plane clearance. - -poly_void = edbapp.modeler.create_trace(trace_path, layer_name="TOP", net_name="gnd", - width="{}+2*{}".format("$ms_width", "$ms_clearance"), - start_cap_style="Flat", - end_cap_style="Flat") -edbapp.modeler.add_void(layer_dict["TOP"], poly_void) - -############################################################################### -# Create ground via padstack and place ground stitching vias -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a ground via padstack and place ground stitching vias. - -edbapp.padstacks.create(padstackname="GVIA", - holediam="0.3mm", - paddiam="0.5mm", - ) - -yloc_u = "$ms_width/2+$ms_clearance+0.25mm" -yloc_l = "-$ms_width/2-$ms_clearance-0.25mm" - -for i in np.arange(1, 20): - edbapp.padstacks.place([str(i) + "mm", yloc_u], "GVIA", net_name="GND") - edbapp.padstacks.place([str(i) + "mm", yloc_l], "GVIA", net_name="GND") - -############################################################################### -# Save and close EDB -# ~~~~~~~~~~~~~~~~~~ -# Save and close EDB. - -edbapp.save_edb() -edbapp.close_edb() - -############################################################################### -# Open EDB in AEDT -# ~~~~~~~~~~~~~~~~ -# Open EDB in AEDT. - -h3d = pyaedt.Hfss3dLayout(projectname=aedb_path, specified_version="2023.2", - non_graphical=non_graphical, new_desktop_session=True) - -############################################################################### -# Create wave ports -# ~~~~~~~~~~~~~~~~~ -# Create wave ports. - -h3d.create_edge_port("line_3", 0, iswave=True, wave_vertical_extension=10, wave_horizontal_extension=10) -h3d.create_edge_port("line_3", 2, iswave=True, wave_vertical_extension=10, wave_horizontal_extension=10) - -############################################################################### -# Edit airbox extents -# ~~~~~~~~~~~~~~~~~~~ -# Edit airbox extents. - -h3d.edit_hfss_extents(air_vertical_positive_padding="10mm", - air_vertical_negative_padding="1mm") - -############################################################################### -# Create setup -# ~~~~~~~~~~~~ -# Create an HFSS simulation setup. - -setup = h3d.create_setup() -setup["MaxPasses"]=2 -setup["AdaptiveFrequency"]="3GHz" -setup["SaveAdaptiveCurrents"]=True -h3d.create_linear_count_sweep( - setupname=setup.name, - unit="GHz", - freqstart=0, - freqstop=5, - num_of_freq_points=1001, - sweepname="sweep1", - sweep_type="Interpolating", - interpolation_tol_percent=1, - interpolation_max_solutions=255, - save_fields=False, - use_q3d_for_dc=False, -) - -############################################################################### -# Plot layout -# ~~~~~~~~~~~ -# Plot layout - -h3d.modeler.edb.nets.plot(None, None, color_by_net=True) - -cp_name = h3d.modeler.clip_plane() - -h3d.save_project() - -############################################################################### -# Start HFSS solver -# ~~~~~~~~~~~~~~~~~ -# Start the HFSS solver by uncommenting the ``h3d.analyze()`` command. - -h3d.analyze() - -# Save AEDT -aedt_path = aedb_path.replace(".aedb", ".aedt") -h3d.logger.info("Your AEDT project is saved to {}".format(aedt_path)) -solutions = h3d.get_touchstone_data()[0] -solutions.log_x = False -solutions.plot() - -h3d.post.create_fieldplot_cutplane(cp_name, "Mag_E", h3d.nominal_adaptive, intrinsincDict={"Freq":"3GHz", "Phase":"0deg"}) - -# Release AEDT. -h3d.release_desktop() - diff --git a/examples/00-EDB/09_Configuration.py b/examples/00-EDB/09_Configuration.py index 9d095755ff6..e1867117868 100644 --- a/examples/00-EDB/09_Configuration.py +++ b/examples/00-EDB/09_Configuration.py @@ -1,50 +1,47 @@ """ -EDB: Pin to Pin project ------------------------ -This example shows how you can create a project using a BOM file and configuration files. -run anlasyis and get results. +# EDB: Pin to Pin project +This example demonstrates the use of the Electronics +Database (EDB) interface to create a layout using the BOM and +a configuration file. """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. Importing the ``Hfss3dlayout`` object initializes it +# ## Perform required imports +# +# The ``Hfss3dlayout`` class provides an interface to +# the 3D Layout editor in Elecronics Deskop. # on version 2023 R2. import os import pyaedt - -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. The default is ``True``. - -non_graphical = True +import tempfile ############################################################################### -# Download file -# ~~~~~~~~~~~~~ -# Download the AEDB file and copy it in the temporary folder. - +# Download the AEDB file and copy it to a temporary folder. -project_path = pyaedt.generate_unique_folder_name() -target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=project_path) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', + destination=temp_dir.name) print("Project folder will be", target_aedb) ############################################################################### -# Launch EDB -# ~~~~~~~~~~ -# Launch the :class:`pyaedt.Edb` class, using EDB 2023 R2 and SI units. +# ## Launch EDB +# +# Launch the `pyaedt.Edb` class using EDB 2023 R2. Length units are SI. edbapp = pyaedt.Edb(target_aedb, edbversion="2023.2") ############################################################################### -# Import Definitions -# ~~~~~~~~~~~~~~~~~~ -# A definitions file is a json containing, for each part name the model associated. -# Model can be RLC, Sparameter or Spice. -# Once imported the definition is applied to the board. -# In this example the json file is stored for convenience in aedb folder and has the following format: +# ## Import Definitions +# +# The definition file uses the [json](https://www.json.org/json-en.html) to +# map layout part numbers to their corresponding models. +# +# The model may be RLC, Sparameter or a +# [SPICE](https://en.wikipedia.org/wiki/SPICE) model definition. +# Once imported the, definition is applied to the components in the layout. +# In this example the json file is in the ``*.aedb`` folder and has the following format: +# ``` json # { # "SParameterModel": { # "GRM32_DC0V_25degC_series": "./GRM32_DC0V_25degC_series.s2p" @@ -73,23 +70,32 @@ # } # } # } +# ``` +# +# The method ``Edb.components.import_definitions`` imports the componet definitions that map electrical models to the components in the simulaton model. -edbapp.components.import_definition(os.path.join(target_aedb, "1_comp_definition.json")) +edbapp.components.import_definition(os.path.join(target_aedb, + "1_comp_definition.json")) ############################################################################### -# Import BOM -# ~~~~~~~~~~ -# This step imports a BOM file in CSV format. The BOM contains the -# reference designator, part name, component type, and default value. -# Components not in the BOM are deactivated. -# In this example the csv file is stored for convenience in aedb folder. +# ## Import BOM # +# The bill of materials (BOM) file provides the list of all components +# by reference designator, part name, component type, and nominal value. +# +# Components that are not contained in the BOM are deactivated in the +# simulation model. +# In this example the csv file was saved in the aedb folder. +# +# ``` # +------------+-----------------------+-----------+------------+ # | RefDes | Part name | Type | Value | # +============+=======================+===========+============+ # | C380 | CAPC1005X55X25LL05T10 | Capacitor | 11nF | # +------------+-----------------------+-----------+------------+ - +# ``` +# +# Having red the informaton in the BOM and definitions file, electrical models can be assigned to all of the components in the simulation model. edbapp.components.import_bom(os.path.join(target_aedb, "0_bom.csv"), refdes_col=0, @@ -97,37 +103,36 @@ comp_type_col=2, value_col=3) - - ############################################################################### -# Check Component Values -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Veriify a Component +# # Component property allows to access all components instances and their property with getters and setters. comp = edbapp.components["C1"] comp.model_type, comp.value - ############################################################################### -# Check Component Definition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Check Component Definition +# # When an s-parameter model is associated to a component it will be available in nport_comp_definition property. edbapp.components.nport_comp_definition ############################################################################### -# Save Edb -# ~~~~~~~~ +# Save the EDB. edbapp.save_edb() ############################################################################### -# Configure Setup -# ~~~~~~~~~~~~~~~ -# This step allows to define the project. It includes: -# - Definition of nets to be included into the cutout, -# - Cutout details, -# - Components on which to create the ports, -# - Simulation settings. +# ## Configure the Simulation Setup +# +# This step enables the following: +# - Definition of nets to be included in a cutout region +# - Cutout details +# - Components on which to create the ports +# - Simulation settings +# +# The method ``Edb.new_simulaton_configuration()`` returns an instance +# of the [``SimulationConfiguration``](https://aedt.docs.pyansys.com/version/stable/EDBAPI/SimulationConfigurationEdb.html) class. sim_setup = edbapp.new_simulation_configuration() sim_setup.solver_type = sim_setup.SOLVER_TYPE.SiwaveSYZ @@ -148,55 +153,66 @@ sim_setup.ac_settings.step_freq = "10MHz" ############################################################################### -# Run Setup -# ~~~~~~~~~ -# This step allows to create the cutout and apply all settings. +# ## Implement the Setup +# +# The cutout and all other simulation settings are applied to the simulation model. -sim_setup.export_json(os.path.join(project_path, "configuration.json")) +sim_setup.export_json(os.path.join(temp_dir.name, "configuration.json")) edbapp.build_simulation_project(sim_setup) ############################################################################### -# Plot Cutout -# ~~~~~~~~~~~ -# Plot cutout once finished. +# ## Display the Cutout +# +# Plot cutout once finished. The model is ready to simulate. edbapp.nets.plot(None,None) ############################################################################### -# Save and Close EDB -# ~~~~~~~~~~~~~~~~~~ -# Edb will be saved and closed in order to be opened by Hfss 3D Layout and solved. +# ## Save and Close EDB +# +# Edb will be saved and re-opened in Electronics +# Deskopt 3D Layout. The HFSS simulation can then be run. edbapp.save_edb() edbapp.close_edb() ############################################################################### -# Open Aedt -# ~~~~~~~~~ -# Project folder aedb will be opened in AEDT Hfss3DLayout and loaded. -h3d = pyaedt.Hfss3dLayout(specified_version="2023.2", projectname=target_aedb, non_graphical=non_graphical, new_desktop_session=True) +# ## Open Electronics Desktop +# +# The EDB is opened in AEDT Hfss3DLayout. +# +# Set ``non_graphical=True`` to run the simulation in non-graphical mode. +h3d = pyaedt.Hfss3dLayout(specified_version="2023.2", + projectname=target_aedb, + non_graphical=False, + new_desktop_session=False) ############################################################################### -# Analyze -# ~~~~~~~ -# Project will be solved. +# ## Analyze +# +# This project is ready to solve. Executing the following cell runs the HFSS simulatoin on the layout. h3d.analyze() ############################################################################### -# Get Results -# ~~~~~~~~~~~ -# S Parameter data will be loaded at the end of simulation. +# ## View Results +# +# S-Parameter data will be loaded at the end of simulation. solutions = h3d.post.get_solution_data() ############################################################################### -# Plot Results -# ~~~~~~~~~~~~ +# ## Plot Results +# # Plot S Parameter data. solutions.plot(solutions.expressions, "db20") ############################################################################### -# Save and Close AEDT -# ~~~~~~~~~~~~~~~~~~~ +# ## Save and Close AEDT +# # Hfss3dLayout is saved and closed. h3d.save_project() h3d.release_desktop() + +############################################################################### +# Clean up the temporary directory. All files and the temporary project folder will be deleted in the next step. + +temp_dir.cleanup() diff --git a/examples/00-EDB/10_GDS_workflow.py b/examples/00-EDB/10_GDS_workflow.py index 091e289c5e7..ac4a998aab7 100644 --- a/examples/00-EDB/10_GDS_workflow.py +++ b/examples/00-EDB/10_GDS_workflow.py @@ -1,13 +1,12 @@ """ -EDB: Edit Control File and import gds -------------------------------------- -This example shows how you can use PyAEDT to import a gds from an IC file. +# EDB: Edit Control File and import gds + +This example demonstrates how to import a gds layout for subsequent +simulation with HFSS. """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports, which includes importing a section. +# Perform imports. import os import tempfile @@ -16,39 +15,40 @@ from pyaedt.edb_core.edb_data.control_file import ControlFile ############################################################################### -# Download file -# ~~~~~~~~~~~~~ -# Download the AEDB file and copy it in the temporary folder. -temppath = tempfile.gettempdir() -local_path = pyaedt.downloads.download_file('gds') -c_file_in = os.path.join( - local_path, "sky130_fictitious_dtc_example_control_no_map.xml" -) -c_map = os.path.join(local_path, "dummy_layermap.map") -gds_in = os.path.join(local_path, "sky130_fictitious_dtc_example.gds") -gds_out = os.path.join(temppath, "example.gds") +# ## Fetch Example Data +# +# Download the EDB folder and copy it to a temporary folder. +# The following files are used in this example: +# - _sky130_fictious_dtc_exmple_contol_no_map.xml_ +# defines physical information such +# as material properties, stackup layers, and boundary conditions. +# - _dummy_layermap.map_ +# maps properties to stackup layers. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +control_fn = "sky130_fictitious_dtc_example_control_no_map.xml" +gds_fn = "sky130_fictitious_dtc_example.gds" +layer_map = "dummy_layermap.map" + +local_path = pyaedt.downloads.download_file('gds', destination=temp_dir.name) +c_file_in = os.path.join(local_path, control_fn) +c_map = os.path.join(local_path, layer_map) +gds_in = os.path.join(local_path, gds_fn) +gds_out = os.path.join(temppath, "gds_out.gds") shutil.copy2(gds_in,gds_out ) -############################################################################### -# Control file -# ~~~~~~~~~~~~ -# A Control file is an xml file which purpose if to provide additional -# information during import phase. It can include, materials, stackup, setup, boundaries and settings. -# In this example we will import an existing xml, integrate it with a layer mapping file of gds -# and then adding setup and boundaries. - +"" c = ControlFile(c_file_in, layer_map=c_map) - ############################################################################### -# Simulation setup -# ~~~~~~~~~~~~~~~~ +# ## Simulation setup +# # Here we setup simulation with HFSS and add a frequency sweep. setup = c.setups.add_setup("Setup1", "1GHz") setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") ############################################################################### -# Additional stackup settings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Additional stackup settings +# # After import user can change stackup settings and add/remove layers or materials. c.stackup.units = "um" c.stackup.dielectrics_base_elevation = -100 @@ -59,12 +59,13 @@ ############################################################################### -# Boundaries settings -# ~~~~~~~~~~~~~~~~~~~ +# ## Boundaries settings +# # Boundaries can include ports, components and boundary extent. c.boundaries.units = "um" -c.boundaries.add_port("P1", x1=223.7, y1=222.6, layer1="Metal6", x2=223.7, y2=100, layer2="Metal6") +c.boundaries.add_port("P1", x1=223.7, y1=222.6, layer1="Metal6", + x2=223.7, y2=100, layer2="Metal6") c.boundaries.add_extent() comp = c.components.add_component("B1", "BGA", "IC", "Flip chip", "Cylinder") comp.solder_diameter = "65um" @@ -75,30 +76,36 @@ c.import_options.import_dummy_nets = True ############################################################################### -# Write xml -# ~~~~~~~~~ +# ## Write xml +# # After all settings are ready we can write xml. -c.write_xml(os.path.join(temppath, "output.xml")) +c.write_xml(os.path.join(temp_dir.name, "output.xml")) ############################################################################### -# Open Edb -# ~~~~~~~~~ +# ## Open Edb +# # Import the gds and open the edb. from pyaedt import Edb -edb = Edb(gds_out, edbversion="2023.2", technology_file=os.path.join(temppath, "output.xml")) +edb = Edb(gds_out, edbversion="2023.2", + technology_file=os.path.join(temp_dir.name, "output.xml")) ############################################################################### -# Plot Stackup -# ~~~~~~~~~~~~ +# ## Plot Stackup +# # Stackup plot. edb.stackup.plot(first_layer="met1") ############################################################################### -# Close Edb -# ~~~~~~~~~ +# ## Close Edb +# # Close the project. -edb.close_edb() \ No newline at end of file +edb.close_edb() + +############################################################################### +# Clean up the temporary folder. + +temp_dir.cleanup() diff --git a/examples/00-EDB/11_post_layout_parameterization.py b/examples/00-EDB/11_post_layout_parameterization.py index 4925a6e4217..97d389bdeb6 100644 --- a/examples/00-EDB/11_post_layout_parameterization.py +++ b/examples/00-EDB/11_post_layout_parameterization.py @@ -5,40 +5,37 @@ """ ############################################################################### -# Define input parameters -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define input parameters. signal_net_name = "DDR4_ALERT3" coplanar_plane_net_name = "1V0" # Specify coplanar plane net name for adding clearance layers = ["16_Bottom"] # Specify layers to be parameterized ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Perform required imports. import os import tempfile import pyaedt -from pyaedt import downloads -from pyaedt import Edb - -temppath = pyaedt.generate_unique_folder_name() +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") ############################################################################### -# Download and open example layout file in edb format -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -edb_fpath = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb',destination=temppath) -appedb = Edb(edb_fpath, edbversion="2023.2") +# Download and open example layout file in edb format. +edb_path = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', + destination=temp_dir.name) +edb = pyaedt.Edb(edb_path, edbversion="2023.2") ############################################################################### -# Cutout -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -appedb.cutout([signal_net_name], [coplanar_plane_net_name, "GND"], +# ## Cutout +# +# The ``Edb.cutout()`` method takes a list of +# signal nets as the first argument and a list of +# reference nets as the 2nd argument. +edb.cutout([signal_net_name], [coplanar_plane_net_name, "GND"], remove_single_pin_components=True) ############################################################################### -# Get all trace segments from the signal net -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -net = appedb.nets[signal_net_name] +# Retrive the path segments from the signal net. +net = edb.nets[signal_net_name] trace_segments = [] for p in net.primitives: if p.layer_name not in layers: @@ -48,46 +45,48 @@ trace_segments.append(p) ############################################################################### -# Create and assign delta w variable per layer -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create and assign delta w variable per layer. for p in trace_segments: vname = f"{p.net_name}_{p.layer_name}_dw" - if vname not in appedb.variables: - appedb[vname] = "0mm" + if vname not in edb.variables: + edb[vname] = "0mm" new_w = f"{p.width}+{vname}" p.width = new_w ############################################################################### -# Delete existing clearance -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete existing clearance. for p in trace_segments: - for g in appedb.modeler.get_polygons_by_layer(p.layer_name, coplanar_plane_net_name): + for g in edb.modeler.get_polygons_by_layer(p.layer_name, + coplanar_plane_net_name): for v in g.voids: if p.is_intersecting(v): v.delete() ############################################################################### -# Create and assign clearance variable per layer -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create and assign the clearance variable for each layer. for p in trace_segments: clr = f"{p.net_name}_{p.layer_name}_clr" - if clr not in appedb.variables: - appedb[clr] = "0.5mm" + if clr not in edb.variables: + edb[clr] = "0.5mm" path = p.get_center_line() - for g in appedb.modeler.get_polygons_by_layer(p.layer_name, coplanar_plane_net_name): - void = appedb.modeler.create_trace(path, p.layer_name, f"{p.width}+{clr}*2") + for g in edb.modeler.get_polygons_by_layer(p.layer_name, + coplanar_plane_net_name): + void = edb.modeler.create_trace(path, p.layer_name, f"{p.width}+{clr}*2") g.add_void(void) ############################################################################### -# Plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -appedb.nets.plot(layers=layers[0], size=2000) +# Visualize the layout. +edb.nets.plot(layers=layers[0], size=2000) + +############################################################################### +# Save and close the EDB. + +save_edb_path = os.path.join(temp_dir.name, "post_layout_parameterization.aedb") +edb.save_edb_as(save_edb_path) +print("Edb is saved to ", save_edb_path) +edb.close_edb() ############################################################################### -# Save and close Edb -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Clean up the temporary folder. -save_edb_fpath = os.path.join(temppath, pyaedt.generate_unique_name("post_layout_parameterization") + ".aedb") -appedb.save_edb_as(save_edb_fpath) -print("Edb is saved to ", save_edb_fpath) -appedb.close_edb() +temp_dir.cleanup() diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py index ee73e020f51..fa419433014 100644 --- a/examples/00-EDB/12_edb_sma_connector_on_board.py +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -1,49 +1,50 @@ """ -EDB: geometry creation ----------------------- +# EDB: geometry creation + This example shows how to -1, Create a parameterized PCB layout design. -2, Place 3D component on PCB. -3, Create HFSS setup and frequency sweep with a mesh operation. -4, Create return loss plot +1. Create a parameterized PCB with an SMA connector footprint for a single-ended + SMA connector launch footprint.. +22 Place 3D component on PCB. +3. Create HFSS setup and frequency sweep with a mesh operation. +4. Create return loss plot """ ###################################################################### +# ## The Finished Project # -# Final expected project -# ~~~~~~~~~~~~~~~~~~~~~~ -# -# .. image:: ../../_static/edb_example_12_sma_connector_on_board.png -# :width: 600 -# :alt: Differential Vias. -###################################################################### +# ###################################################################### -# Create parameterized PCB -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Initialize an empty EDB layout object on version 2023 R2. -###################################################################### +# ## Create parameterized PCB +# +# Import dependencies. import os import numpy as np import pyaedt +import tempfile + +############################################################################### +# Create the EDB. ansys_version = "2023.2" +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +working_folder = temp_dir.name -aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("pcb") + ".aedb") +aedb_path = os.path.join(working_folder, "pcb.aedb") edb = pyaedt.Edb(edbpath=aedb_path, edbversion=ansys_version) print("EDB is located at {}".format(aedb_path)) ##################### -# Create FR4 material -# ~~~~~~~~~~~~~~~~~~~ +# Defne the FR4 dielectric for the PCB. edb.materials.add_dielectric_material("ANSYS_FR4", 3.5, 0.005) -################ -# Create stackup -# ~~~~~~~~~~~~~~ -# A stackup can be created by importing from a csv/xml file or adding layer by layer. +############################################################################### +# ## Create Stackup # +# The stackup is defined explicitly here, but also can be imported +# from a from a csv or xml file using the method +# ``Edb.stackup.import_stackup()``. edb.add_design_variable("$DIEL_T", "0.15mm") edb.stackup.add_layer("BOT") @@ -59,9 +60,7 @@ edb.stackup.add_layer("TOP", "Diel", thickness="0.05mm") ###################### -# Create ground planes -# ~~~~~~~~~~~~~~~~~~~~ -# +# Create ground conductors. edb.add_design_variable("PCB_W", "20mm") edb.add_design_variable("PCB_L", "20mm") @@ -70,9 +69,9 @@ for layer_name in edb.stackup.signal_layers.keys(): gnd_dict[layer_name] = edb.modeler.create_rectangle(layer_name, "GND", [0, "PCB_W/-2"], ["PCB_L", "PCB_W/2"]) -################### -# Create signal net -# ~~~~~~~~~~~~~~~~~ +############################################################################### +# ## Create signal net +# # Create signal net on layer 3, and add clearance to the ground plane. edb.add_design_variable("SIG_L", "10mm") @@ -87,112 +86,123 @@ gnd_dict["L3"].add_void(clr) #################### -# Create signal vias -# ~~~~~~~~~~~~~~~~~~ +# ## Signal Vias +# # Create via padstack definition. Place the signal vias. edb.add_design_variable("SG_VIA_D", "1mm") - edb.add_design_variable("$VIA_AP_D", "1.2mm") - edb.padstacks.create("ANSYS_VIA", "0.3mm", "0.5mm", "$VIA_AP_D") - edb.padstacks.place(["5mm", 0], "ANSYS_VIA", "SIG") ###################################### -# Create ground vias around signal via -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create ground vias around the SMA +# connector launch footprint. The vas +# are placed around the circumference +# of the launch from 35 degrees to 325 +# degrees. -for i in np.arange(30, 331, 30): +for i in np.arange(30, 326, 35): px = np.cos(i / 180 * np.pi) py = np.sin(i / 180 * np.pi) edb.padstacks.place(["{}*{}+5mm".format("SG_VIA_D", px), "{}*{}".format("SG_VIA_D", py)], "ANSYS_VIA", "GND") ####################################### -# Create ground vias along signal trace -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create ground vias along signal trace. for i in np.arange(2e-3, edb.variables["SIG_L"].value - 2e-3, 2e-3): edb.padstacks.place(["{}+5mm".format(i), "1mm"], "ANSYS_VIA", "GND") edb.padstacks.place(["{}+5mm".format(i), "-1mm"], "ANSYS_VIA", "GND") ################################################### -# Create a wave port at the end of the signal trace -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a wave port at the end of the signal trace. signal_trace.create_edge_port("port_1", "End", "Wave", horizontal_extent_factor=10) -################## -# Set hfss options -# ~~~~~~~~~~~~~~~~ +############################################################################### +# ## HFSS Simulation Setup +# +# The named argument ``max_num_passes`` sets an upper limit on the +# number of adaptive passes for mesh refinement. +# +# For broadband applications when the simulation results may be used +# to generate a SPICE model, the outer domain boundary can be +# located roughly $$ d=\lambda/8 $$ from the internal structures +# in the model. +extend_domain = 3E11/5E9/8.0 # Quarter wavelength at 4 GHz. edb.design_options.antipads_always_on = True -edb.hfss.hfss_extent_info.air_box_horizontal_extent = 0.01 -edb.hfss.hfss_extent_info.air_box_positive_vertical_extent = 2 -edb.hfss.hfss_extent_info.air_box_negative_vertical_extent = 2 - -############## -# Create setup -# ~~~~~~~~~~~~ +edb.hfss.hfss_extent_info.air_box_horizontal_extent = extend_domain +edb.hfss.hfss_extent_info.air_box_positive_vertical_extent = extend_domain +edb.hfss.hfss_extent_info.air_box_negative_vertical_extent = extend_domain setup = edb.create_hfss_setup("Setup1") -setup.set_solution_single_frequency("5GHz", max_num_passes=2, max_delta_s="0.01") +setup.set_solution_single_frequency("5GHz", max_num_passes=8, max_delta_s="0.02") setup.hfss_solver_settings.order_basis = "first" ############################# -# Add mesh operation to setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add a mesh operation to the setup. + edb.setups["Setup1"].add_length_mesh_operation({"SIG": ["L3"]}, "m1", max_length="0.1mm") ############################## -# Add frequency sweep to setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add frequency sweep to setup. +# +# When the simulation results will +# be used for transient SPICE analysis, it is advisible +# to use the following strategy. +# +# - DC point +# - Logarithmic sweep from 1 kHz to 100 MHz +# - Linear scale for higher frequencies. setup.add_frequency_sweep( "Sweep1", frequency_sweep=[ ["linear count", "0", "1KHz", 1], - ["log scale", "1KHz", "0.1GHz", 10], + ["log scale", "1KHz", "100MHz", 10], ["linear scale", "0.1GHz", "5GHz", "0.1GHz"], ], ) #################### -# Save and close EDB -# ~~~~~~~~~~~~~~~~~~ +# Save and close EDB. edb.save_edb() edb.close_edb() ##################### -# Launch Hfss3dLayout -# ~~~~~~~~~~~~~~~~~~~ +# Launch Hfss3dLayout. h3d = pyaedt.Hfss3dLayout(aedb_path, specified_version=ansys_version, new_desktop_session=True) #################### -# Place 3D component -# ~~~~~~~~~~~~~~~~~~ - -component3d = pyaedt.downloads.download_file("component_3d", "SMA_RF_SURFACE_MOUNT.a3dcomp",) +# Place a 3D component. +full_comp_name = pyaedt.downloads.download_file("component_3d", + filename="SMA_RF_SURFACE_MOUNT.a3dcomp", + destination=working_folder) comp = h3d.modeler.place_3d_component( - component_path=component3d, number_of_terminals=1, placement_layer="TOP", component_name="my_connector", + component_path=full_comp_name, number_of_terminals=1, + placement_layer="TOP", component_name="my_connector", pos_x="5mm", pos_y=0.000) -########## -# Analysis -# ~~~~~~~~ +############################################################################### +# ## Run Simulation + h3d.analyze(num_cores=4) ######################### -# Create return loss plot -# ~~~~~~~~~~~~~~~~~~~~~~~ +# Visualize the return loss. h3d.post.create_report("dB(S(port_1, port_1))") ############################ -# Save and close the project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Save and close the project. h3d.save_project() print("Project is saved to {}".format(h3d.project_path)) h3d.release_desktop(True, True) + +############################################################################### +# Clean up the temporary folder. + +temp_dir.cleanup() diff --git a/examples/00-EDB/13_edb_create_component.py b/examples/00-EDB/13_edb_create_component.py index 9c3571e4e7a..e43ca4e3fd3 100644 --- a/examples/00-EDB/13_edb_create_component.py +++ b/examples/00-EDB/13_edb_create_component.py @@ -1,44 +1,47 @@ """ -EDB: geometry creation ----------------------- -This example shows how to -1, Create a layout layer stackup. -2, Create Padstack definition. -3, Place padstack instances at given location. -4, Create primitives, polygon and trace. -5, Create component from pins. -6, Create HFSS simulation setup and excitation ports. +# EDB: Layout Creation and Setup + +This example demonstrates how to to + +1. Create a layout layer stackup. +2. Define padstacks. +3. Place padstack instances in the layout where the connectors are located. +4. Create primitives such as polygons and traces. +5. Create "components" from the padstack definitions using "pins". + >The "component" in EDB acts as a placeholder to enable automatic + >placement of electrical models, or + >as in this example to assign ports. In many + >cases the EDB is imported from a 3rd party layout, in which case the + >concept of a "component" as a placeholder is needed to map + >models to the components on the PCB for later use in the + >simulation. +7. Create the HFSS simulation setup and assign ports where the connectors are located. """ ###################################################################### +# ## PCB Trace Model # -# Final expected project -# ~~~~~~~~~~~~~~~~~~~~~~ +# Here is an image of the model that will be created in this example. # -# .. image:: ../../_static/connector_example.png -# :width: 600 -# :alt: Connector from Vias. -###################################################################### +# +# +# The rectangular sheets at each end of the PCB enable placement of ports where the connectors are located. ###################################################################### -# Create connector component from pad-stack -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Initialize an empty EDB layout object on version 2023 R2. -###################################################################### +# Initialize the EDB layout object. import os import pyaedt +import tempfile from pyaedt import Edb -aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), - pyaedt.generate_unique_name("component_example") + ".aedb") - +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "component_example.aedb") edb = Edb(edbpath=aedb_path, edbversion="2023.2") print("EDB is located at {}".format(aedb_path)) ###################### # Initialize variables -# ~~~~~~~~~~~~~~~~~~~~ layout_count = 12 diel_material_name = "FR4_epoxy" @@ -52,9 +55,9 @@ connector_size = 2e-3 conectors_position = [[0, 0], [10e-3, 0]] -################ -# Create stackup -# ~~~~~~~~~~~~~~ +############################################################################### +# Create the stackup + edb.stackup.create_symmetric_stackup(layer_count=layout_count, inner_layer_thickness=cond_thickness_inner, outer_layer_thickness=cond_thickness_outer, soldermask_thickness=soldermask_thickness, dielectric_thickness=diel_thickness, @@ -62,8 +65,6 @@ ###################### # Create ground planes -# ~~~~~~~~~~~~~~~~~~~~ -# ground_layers = [layer_name for layer_name in edb.stackup.signal_layers.keys() if layer_name not in [trace_in_layer, trace_out_layer]] @@ -72,8 +73,13 @@ edb.modeler.create_polygon(plane_shape, i, net_name="VSS") ###################### -# Add design variables -# ~~~~~~~~~~~~~~~~~~~~ +# ### Design Parameters +# +# Parameters that are preceeded by a _"$"_ character have project-wide scope. +# Therefore, the padstack **definition** and hence all instances of that padstack rely on the parameters. +# +# Parameters such as _"trace_in_width"_ and _"trace_out_width"_ have local scope and +# are only used in in the design. edb.add_design_variable("$via_hole_size", "0.3mm") edb.add_design_variable("$antipaddiam", "0.7mm") @@ -82,15 +88,15 @@ edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) ############################ -# Create padstack definition -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Create the Connector Component +# +# The component definition is used to place the connector on the PCB. First define the padstacks. edb.padstacks.create_padstack(padstackname="Via", holediam="$via_hole_size", antipaddiam="$antipaddiam", paddiam="$paddiam") #################### -# Create connector 1 -# ~~~~~~~~~~~~~~~~~~ +# Create the first connector component1_pins = [edb.padstacks.place_padstack(conectors_position[0], "Via", net_name="VDD", fromlayer=trace_in_layer, tolayer=trace_out_layer), @@ -108,8 +114,7 @@ "Via", net_name="VSS")] #################### -# Create connector 2 -# ~~~~~~~~~~~~~~~~~~ +# Create the 2nd connector component2_pins = [ edb.padstacks.place_padstack(conectors_position[-1], "Via", net_name="VDD", fromlayer=trace_in_layer, @@ -128,22 +133,22 @@ "Via", net_name="VSS")] #################### -# Create layout pins -# ~~~~~~~~~~~~~~~~~~ +# ### Define "Pins" +# +# Pins are fist defined to allow a component to subsequently connect to the remainder +# of the model. In this case, ports will be assigned at the connector instances using the "pins". for padstack_instance in list(edb.padstacks.instances.values()): padstack_instance.is_pin = True ############################ -# create component from pins -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create components from he pins edb.components.create(component1_pins, 'connector_1') edb.components.create(component2_pins, 'connector_2') ################################################################################ -# Creating ports and adding simulation setup using SimulationConfiguration class -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Creating ports on the pins and insert a simulation setup using the ``SimulationConfiguration`` class. sim_setup = edb.new_simulation_configuration() sim_setup.solver_type = sim_setup.SOLVER_TYPE.Hfss3dLayout @@ -158,13 +163,33 @@ edb.build_simulation_project(sim_setup) ########################### -# Save EDB and open in AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Save the EDB and open it in the 3D Layout editor. If ``non_graphical==False`` +# there may be a delay while AEDT started. edb.save_edb() edb.close_edb() h3d = pyaedt.Hfss3dLayout(specified_version="2023.2", projectname=aedb_path, - non_graphical=False, + non_graphical=False, # Set non_graphical = False to launch AEDT in graphical mode. new_desktop_session=True) -h3d.release_desktop(False, False) + +############################################################################### +# ### Release the application from the Python kernel +# +# It is important to release the application from the Python kernel after +# execution of the script. The default behavior of the ``release_desktop()`` method closes all open +# projects and closes the application. +# +# If you want to conintue working on the project in graphical mode +# after script execution, call the following method with both arguments set to ``False``. + +h3d.release_desktop(close_projects=True, close_desktop=True) + +############################################################################### +# ### Clean up the Temporary Directory +# +# The following command cleans up the temporary directory, thereby removing all +# project files. If you'd like to save this project, save it to a folder of your choice +# prior to running the following cell. + +temp_dir.cleanup() diff --git a/examples/00-EDB/14_edb_create_parametrized_design.py b/examples/00-EDB/14_edb_create_parametrized_design.py index d4cf224f299..038321ba16d 100644 --- a/examples/00-EDB/14_edb_create_parametrized_design.py +++ b/examples/00-EDB/14_edb_create_parametrized_design.py @@ -2,39 +2,39 @@ EDB: parameterized design ------------------------ This example shows how to -1, Create an HFSS simulation project using SimulationConfiguration class. +1, Set up an HFSS project using SimulationConfiguration class. 2, Create automatically parametrized design. """ ###################################################################### +# The following layout will be created in this example +# +# # -# Final expected project -# ~~~~~~~~~~~~~~~~~~~~~~ # -# .. image:: ../../_static/parametrized_design.png -# :width: 600 -# :alt: Fully automated parametrization. -###################################################################### ###################################################################### -# Create HFSS simulatio project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Load an existing EDB folder. -###################################################################### +# Create HFSS simulation project from an existing layout. import os import pyaedt +import tempfile -project_path = pyaedt.generate_unique_folder_name() -target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=project_path) -print("Project folder will be", target_aedb) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) +print("Project will be located in ", target_aedb) aedt_version = "2023.2" edb = pyaedt.Edb(edbpath=target_aedb, edbversion=aedt_version) print("EDB is located at {}".format(target_aedb)) ######################################################################## -# Create SimulationConfiguration object and define simulation parameters -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Prepare the Layout for Simulation +# +# The ``new_simulation_configuration()`` method creates an instance of +# the ``SimulationConfiguration`` class. This class helps define all pre-processing steps +# required to set up the PCB for simulation. After the simulation configuration has been defined, +# they are applied to the EDB using the method +# ``Edb.build_simulation()``. simulation_configuration = edb.new_simulation_configuration() simulation_configuration.signal_nets = ["PCIe_Gen4_RX0_P", "PCIe_Gen4_RX0_N", @@ -47,23 +47,59 @@ simulation_configuration.step_freq = "10MHz" ########################## -# Build simulation project -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Now apply the simulation setup to the EDB. edb.build_simulation_project(simulation_configuration) ############################# -# Generated design parameters -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Parameterization +# +# The layout can automatically be set up to enable parametric studies. For example, the +# impact of antipad diameter or trace width on signal integrity performance may be invested parametrically. 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 -# ~~~~~~~~~~~~~~~~~~~~ +# ## Open project in AEDT +# +# All manipulations thus far have been executed using the EDB API which provides fast, streamlined processing of +# layout data in non-graphical mode. The layout and simulation setup can be visualized by opening it using the +# 3D Layout editor in Electronics Desktop. +# +# Note that there may be some delay while AEDT is being launched. + +hfss = pyaedt.Hfss3dLayout(projectname=target_aedb, + specified_version=aedt_version, + non_graphical=False, + new_desktop_session=True) + +############################################################################### +# The following cell can be used to ensure that the design is valid for simulation. + +validation_info = hfss.validate_full_design() +is_ready_to_simulate = True + +for s in validation_info[0]: + if "error" in s: + print(s) + is_ready_to_simulate = False + +if is_ready_to_simulate: + print("The model is ready for simulation.") +else: + print("There are errors in the model that need to be fixed.") + +############################################################################### +# ### Release the application from the Python kernel +# +# It is important to release the application from the Python kernel after +# execution of the script. The default behavior of the ``release_desktop()`` method closes all open +# projects and closes the application. +# +# If you want to conintue working on the project in graphical mode +# after script execution, call the following method with both arguments set to ``False``. -# Uncomment the following line to open the design in HFSS 3D Layout -# hfss = pyaedt.Hfss3dLayout(projectname=target_aedb, specified_version=aedt_version, new_desktop_session=True) -# hfss.release_desktop() +hfss.release_desktop(close_projects=True, close_desktop=True) +temp_dir.cleanup() # Remove the temporary folder and files. All data will be removd! diff --git a/examples/00-EDB/15_ac_analysis.py b/examples/00-EDB/15_ac_analysis.py index 557d2554cd2..ec34e59fe29 100644 --- a/examples/00-EDB/15_ac_analysis.py +++ b/examples/00-EDB/15_ac_analysis.py @@ -1,50 +1,53 @@ """ -EDB: SYZ analysis -------------------- -This example shows how you can use PyAEDT to set up SYZ analysis on Serdes channel. -The input is the name of the differential nets. The positive net is PCIe_Gen4_TX3_CAP_P. -The negative net is PCIe_Gen4_TX3_CAP_N. The code will place ports on driver and +# EDB: Network Analysis in SIwave + +This example shows how to use PyAEDT to set up SYZ analysis on a +[serdes](https://en.wikipedia.org/wiki/SerDes) channel. +The signal input is applied differetially. The positive net is _"PCIe_Gen4_TX3_CAP_P"_. +The negative net is _"PCIe_Gen4_TX3_CAP_N"_. In this example, ports are placed on the +driver and receiver components. """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Perform required imports +# # Perform required imports, which includes importing a section. import time import pyaedt +import tempfile ############################################################################### -# Download file -# ~~~~~~~~~~~~~ +# ### Download file +# # Download the AEDB file and copy it in the temporary folder. -temp_folder = pyaedt.generate_unique_folder_name() -targetfile = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_folder) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +edb_full_path = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) time.sleep(5) -print(targetfile) +print(edb_full_path) ############################################################################### -# Configure EDB -# ~~~~~~~~~~~~~ -# Launch the :class:`pyaedt.Edb` class, using EDB 2023 R2. +# ### Configure EDB +# +# Creat an instance of the `pyaedt.Edb` class. -edbapp = pyaedt.Edb(edbpath=targetfile, edbversion="2023.2") +edbapp = pyaedt.Edb(edbpath=edb_full_path, edbversion="2023.2") ############################################################################### -# Generate extended nets -# ~~~~~~~~~~~~~~~~~~~~~~ -# An extended net is a connection between two nets that are usually connected -# through a passive component like a resistor or capacitor. +# ### Generate extended nets +# +# An extended net is a connection between two nets that are connected +# through a passive component such as a resistor or capacitor. -edbapp.extended_nets.auto_identify_signal(resistor_below=10, inductor_below=1, capacitor_above=1e-9) +all_nets = edbapp.extended_nets.auto_identify_signal(resistor_below=10, + inductor_below=1, + capacitor_above=1e-9) ############################################################################### -# Review extended net properties -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Review extended net properties. +# Review the properties of extended nets. diff_p = edbapp.nets["PCIe_Gen4_TX3_CAP_P"] diff_n = edbapp.nets["PCIe_Gen4_TX3_CAP_N"] @@ -61,8 +64,6 @@ print(comp_p, rlc_p, comp_n, rlc_n, sep="\n") ############################################################################### -# Prepare input data for port creation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Prepare input data for port creation. ports = [] @@ -83,8 +84,8 @@ print(*ports, sep="\n") ############################################################################### -# Create ports -# ~~~~~~~~~~~~ +# ### Create ports +# # Solder balls are generated automatically. The default port type is coax port. for d in ports: @@ -97,21 +98,17 @@ ) ############################################################################### -# Cutout -# ~~~~~~ -# Delete all irrelevant nets. +# ### Cutout +# +# Retain only relevant parts of the layout. nets = [] nets.extend(nets_p) nets.extend(nets_n) - edbapp.cutout(signal_list=nets, reference_list=["GND"], extent_type="Bounding") - ############################################################################### -# Create SYZ analysis setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create SIwave SYZ setup. +# Set up the model for network analysis in SIwave. setup = edbapp.create_siwave_syz_setup("setup1") setup.add_frequency_sweep(frequency_sweep=[ @@ -121,47 +118,51 @@ ]) ############################################################################### -# Save and close AEDT -# ~~~~~~~~~~~~~~~~~~~ -# Close AEDT. +# Save and close the EDB. edbapp.save() edbapp.close_edb() ############################################################################### -# Launch Hfss3dLayout -# ~~~~~~~~~~~~~~~~~~~ -# To do SYZ analysis, you must launch HFSS 3D Layout and import EDB into it. +# ### Launch Hfss3dLayout +# +# The HFSS 3D Layout user inteface in AEDT is used to import the EDB and +# run the analysis. AEDT 3D Layout can be used to view the model +# if it is launched in graphical mode. -h3d = pyaedt.Hfss3dLayout(targetfile, specified_version="2023.2", new_desktop_session=True) +h3d = pyaedt.Hfss3dLayout(edb_full_path, + specified_version="2023.2", + non_graphical=False, # Set to true for non-graphical mode. + new_desktop_session=True) ############################################################################### -# Set differential pair -# ~~~~~~~~~~~~~~~~~~~~~ -# Set differential pair. +# Define the differential pair. -h3d.set_differential_pair(positive_terminal="U1_PCIe_Gen4_TX3_CAP_P", negative_terminal="U1_PCIe_Gen4_TX3_CAP_N", diff_name="PAIR_U1") -h3d.set_differential_pair(positive_terminal="X1_PCIe_Gen4_TX3_P", negative_terminal="X1_PCIe_Gen4_TX3_N", diff_name="PAIR_X1") +h3d.set_differential_pair(positive_terminal="U1_PCIe_Gen4_TX3_CAP_P", + negative_terminal="U1_PCIe_Gen4_TX3_CAP_N", + diff_name="PAIR_U1") +h3d.set_differential_pair(positive_terminal="X1_PCIe_Gen4_TX3_P", + negative_terminal="X1_PCIe_Gen4_TX3_N", + diff_name="PAIR_X1") ############################################################################### -# Solve and plot results -# ~~~~~~~~~~~~~~~~~~~~~~ # Solve and plot the results. h3d.analyze(num_cores=4) ############################################################################### -# Create report outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a report. +# Visualze the results. h3d.post.create_report("dB(S(PAIR_U1,PAIR_U1))", context="Differential Pairs") ############################################################################### -# Close AEDT -# ~~~~~~~~~~ # Close AEDT. h3d.save_project() print("Project is saved to {}".format(h3d.project_path)) h3d.release_desktop(True, True) + +############################################################################### +# The following cell cleans up the temporary directory and removes all project data. + +temp_dir.cleanup() diff --git a/doc/source/_static/connector_example.png b/examples/00-EDB/_static/connector_example.png similarity index 100% rename from doc/source/_static/connector_example.png rename to examples/00-EDB/_static/connector_example.png diff --git a/examples/00-EDB/_static/diff_via.png b/examples/00-EDB/_static/diff_via.png new file mode 100644 index 0000000000000000000000000000000000000000..d444d1ccbe602c3d54a18e05b0075a5e8d62240c GIT binary patch literal 57701 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 literal 0 HcmV?d00001 diff --git a/doc/source/_static/edb_example_12_sma_connector_on_board.png b/examples/00-EDB/_static/edb_example_12_sma_connector_on_board.png similarity index 100% rename from doc/source/_static/edb_example_12_sma_connector_on_board.png rename to examples/00-EDB/_static/edb_example_12_sma_connector_on_board.png diff --git a/doc/source/_static/parametrized_design.png b/examples/00-EDB/_static/parameterized_design.png similarity index 100% rename from doc/source/_static/parametrized_design.png rename to examples/00-EDB/_static/parameterized_design.png diff --git a/examples/00-EDB/_static/pcb_transition_parameterized.png b/examples/00-EDB/_static/pcb_transition_parameterized.png new file mode 100644 index 0000000000000000000000000000000000000000..f75d4a2e673d6fb0dfe747c503ec64b5acdaa24d GIT binary patch literal 37379 zcmZtuc_7s5`#+9%+R!FrOG3sTQubYGhA|jtP?o}xJ!z7CCY*{GYh%l<#+Gc^l4Wv| zEJZS72^lAZv1ZTyy`ST}&g=F5{XT!3qhX%Qec#vhxE|Nzy6&6!J4XNbk<&-^?b~a@qLuzI|n}N4M+`fzNDr zQC432_MLbS{r6i7?uFyNeWYXq#AS1T+gXZT=^aDg-s*;f791AXvjQw_vmy@CJ7KhD z#X0XmkAdW`symMc&R(}{^!~kQwCPUdNs&+Cd2BGm$bM-A9L7uf9e$MUm-siho!9%0 z&wTD#>hASb&FQ$F7pJqEU*Lm^^pl+n8dnjiF7wozdW}CsyrmZOxrK zmtuD&uVDUI-B7isa8&eto0&!)qA)Ju@Ze7gbY~qF6DzNZ2_oG)oyvcakSR@Y=gDiF zJgT*^ZIyCy-V8xh$gbw=Yh}gLE~?{}^H?a1WY6fRC~Z!YSr2deq%k}A!|3U3@1tgy z6@K=_NB=pF%-S}g$&(qzOImdg=*zC^mvFT!+3T4@mvHKeo%+kGzZtVk8V?ULhTPP{ zyVIvH77ko(Soe;wla(ra%JG36s zmmOz4P{~IuI3rcRqrR;iz}8N@R4J+^l=ObiyTaBvK{ zTKg5<`{&y)*EM`_q`I_oMu=rL{oMP1W!Gtc#9zT}GoQnPr(y9|`~rWsyODlL!xE@+| zTvdN^FK^M#FR!9acqeDMm-F)TkAe@K>Qn0jLzi*t_}a;UojJ_>=|(&uw_3$}?Lp>w z@=6{ok7R&Ityd8d>XukjuBBt&H^*rDKy0CL*d`h!VJ= zk`Z-(-l#Ujt@l~B5k1mMs6Bs0uRV31Na;vkDw=luZO^zm)|%+f?E$==8^!b6^ru_j zEgCi$^R4Yu9=$&0f6~3%)90t?sUdAshhEp-5tY#nH<|3X8N)Oud!Hc`7%Pt#0*5a* zG~>vNJ6%XGD&c2a&U2`yXC8@f`OEi)qTp`#tsjkLjSiSe?Z*zPEv1}wcu&~Kc6Ek-L|)qrfyGUD9{zAziD`n;OOK4MunfcwHDl+cS5e5B$v zrXPhXXKDajK{+6CczIO3D9U01{s}Jvk5(GkXb_=V7PlNl7s=)5W}7CI$E=z!bs8joWGk`k3#rAFAs-F=;An< zpMkZs)Vi~rcYwkGHUy8l{CK*5 z^4?{^zFe@}5?uS)><(5mtS_)0VAPki6R~QuU(@=uzn7>O1XcFWR=Tw)g+aC`Rds>m zn%raz+1z18?^W#`E*ZjLjYi@6bEfZqrf`6p^t^rUBx^XLkUBHs=rbhwOhqeoJ(oAH zv7Oi74?EI6<1t=Eeneje#-=b432d_dnm2fND?)fCUG#fO;_VT|X?3e)HM?cs4&_Gk zQvqegJB;5C<9ScGj1ejxfw%a)-AxbgqGM+7IL=MZ(i#|JV|2YWP71|) znDy~rTnyr0r_WS=4%D!RVazQnxCCKOUf)@?a?be`!q`15bD~2Lafmv3`G8 z`;2iMU#b7@rm>|LgofP~0S$s8Mq3At9L*D;^Wvu;tv#L_kb-~}Eam_FA~CS-m?&u> zrb;!Dj=scSFSp#Q9}sMc8$3$uVS(9Qx}-P7LeZLdDYER_X#>K*X{PDEnPR03dG!n) z^Nc&_eAPLUHUtJFRR56fjhb&O4lVd`fc5BvtZc@!>T2*Z#n8{n)yH#!Zj6Hz3W$2H zvyO;VQBSK3zRKmtE2~LKN_~q;`NBj~dqex$w4dCTinS{2^t(d9cERf!v+qTz=PFth z0oPTz5K$QpvX;k`jgBQw=*#k-^Z~lxb+-7`M|pP_`R{ns0fNe}bhw@2svIU&Jfb)-IJxEmUc@ah%R|ISglz6bZA?+K*bA$H6etRSE81(wDq6(^ zegjkZI6B>VO?eYff-rpdIRbA#m2sC{L+;F{8er%eYkHQSJ=W+&{>Z3#-i=6`&% zUR8@rN1SN6XFO9&XZ@t)5-{82N)ax;;3iRIpN||}7ymkY99-bv0fHWIGT*7_pEDl$ zbM@o*s^>{LqWHs!cSQX!Xe@J#--Y951#D(;{Iy_QiB>C2tG{NOOkrCCdw3u** zmzQyPa&*~DtmtoTr{vt=s0GXKx?e-gGMcAO_gc>$ozNlI^hh8*vdCM>P8b&)_vZv^VDv8HKj_Df} z_h$MDV4f1!1eq*r7FAwmR{vKjt?b^6ZJ5l{-7c2-HiGfj1D*71=c)bvpTW1l($>$r zuz?Vx#tUB50T<^ulVr2IEk3{EL7dR6r+jAwi*;^klMt%zav*ChRJj^nkQdePvo@y5EbfB_6xuyaC$9; z@%y)M*-M$8(YGN}o#il)#1MHEdOu>^OA`6KV*Wlp)QE7E2qsJRxZ=~wzLsat?xQ?c zIW7Wl#w57wH$a-KD+OkH(?nqsVjFJptDC;M3cD!!GgzMP^!`63wWv$YT{5pS7=Z6k zr~@o`!a>MH)$tpbaIwqFuO0m25b4RTBbC#58kMH6h&u(#Yqa_8WUl)hzHYOE2FwFu zd3U}HD)~(xq1Y@Hsd!6x-NVIcrl+%xR4(E9f0R_e2$L}jDfml5$fuFx`_YLz91rxt zMYYn)y^p*3Yu!Px``hMuwJ!!>+hNC!hGFlIscFB>pQ}|d;uhae&N+a)0O4S7I1z~KTUFu@aLiI(!tyfNyvW9M{@E>) z`RFg70TopL5;T0C`8Kh>JCnwJ4^}s?Eg2>Fi()Sc(xfr@#nmaU^$)_S*PmW+Ll%rT zg(^6_&0kekciDqAp9~{#vBG#1V5?Kb`bm4&!7&xR*YlKA9`Vhcd|vHrpDK)2l8`wa z$*b1bSOyp`z$KY+e>ARQ!p*b872H5#HIF-29hx(es%J;XAHBjyU7T2SY+3-fU z)I(f>P_&Q2c$LWuf)v2FZw`m@SCkDCrcmTR>+{l;1B${>(;hs%^h-sPv!=35Cu zkeqHL{nMWm_E3##S+T<7R8MMNJhq%S+C;BM7Q{dgt8ZP@79$5da`KaFOZ;Z@*!7>au@bgR0Q!8ddoh0k7Loy2I$^M&&Sk0X99lC@^lU7|_W&{E zW5^a?;FjTyX?b`f)CX7R8^(NC(h}gcGNlwi4)jw8Pn0)77M(26v<<%M#0%Wk5_Xf@8#Y~l7id2N@U^lA z{sCg6EB+G17JN*OQUvFkiE21?+0^#pJX3+U&KmB({)Bz0X}=VhGG zpOS5R!=e&k zZ?BvJ-)A96?W^2NR_j4Ne1hpy>0i@C54SQG)j3C6cWBI=)VWo zCD4o1OdTH@67dP4XL{Ylsa`!D$#kO#PTt(O#<}?(K9s2f*`^lbiS$n78v9qWkeDBO0jEDplCg-W?wFOPA?q>*)H%| z02p{eE1e(FrVKoTw7iT@>n+xiD=0l$H3Ch&wSokqG&-Ua=FDz*=cd$_yz&CiA$>?{bd`YlCBS^>uL^0RMP5mnh7(=wZ z^}_zSS9hp`Z24t9#7xIMqm#McNCiuqB z-Nva8T_@dSI+<)d6Xnp+dp%)6j|rYJRt}*uvwQ|z*7qdPn$$JtPs9;Ntt6G*6SS$e z(zm;;V5vTqWiyv-YBLs^mNT0~FqNOfMUR+T10VJ-zP)hshf7qS%eNoF%kSLx9h%>? zD!VDDv8)Gtithe-Gw2%l$E0!Wnx^kmvD5jJxe6jgU81Nig;bIzrIDo^K%a+rS@yp9 zhgv_UJgWDm^*!EpZ|OW^xxaN744_0Na*__t7Eo=b2#%jHBb6fxa|Hm)4!^$HNVja~ zV9urQd^8xOOmF%JrEK7F-Cj)vuto=AfPvwThR&YlZ=9t&8WHWMCqJ^4f>b?O34~S$ zrj7z(F5nahw(n&;AUcLSM)KC(Z}wp`yR7k(mD9GZb<1Qf`V?b!Y_4)iA2(_IzICf> zFh7{q<_0F?iF(`@exTkH%qTQ-KiNoiKLyY=AfiFb1j$K>nLgc1RdtW( za}Q*MHR<_i&4#5=yC9;dFdrv2ogCPFoXza}9o;VRGejo!0+U~H=S!NAA#M@_LiwBN zamd72O%7r=9+hsd8j7yB0yf6AqHrhT0>{e1Y>X-#JZXp%@1AYzurhoAzJ#%}e37x8w7@H84kVu5T~6*MHpTq_fIp}OV6SY7{X5iqCM zAOO9UGjmQ?cG`|IW8*Z|Rp6Lin>~o9J!b$Wqj3qh0l+c8VG?{$Z>6VI+UtEb&M^}+ z;F+Up_%cqXkU8>avOI?x6&?P@iqE%2@dh?4l`}#6f4O@4SAI+)G{EJ>X_FQ)qHYGM z%&_lM5|8J=4}}t|WM_8e#xbo|$r5;)y=O-_cuzbv+py->T}1&Mj_K(1*Zd$)5pmn-K3^>(p%;XOv2QY zQgUhlQiG^%HNK}PyBg}`;P|`H<-{5*6-)rEHRM4PTU|bi+gC2yf{}{=B-DR zd=2#EKP>88`Mj_b#Tn}MgT*6+-Cwql@M%DR8Ip?55LW5eq5)C0^ueW{6V^PL`xXU{ zldU7t0O{KJqnIcka}6G?VunNH(B*x2o$8r66G&hEqYscneim_wp_Q>C&o3H+i)<}} zn<=bjIqW`|Zck5OPA?n~Ohxy_=m?mO{1vcrQz+Qq+(9q9rO{lBR9h*BU6bAM;mG}Q z7d6tADUrXP+d5eNnZ(RhA$xq`5Jl(bsH&bELY^6A8k*qNuZ;66U;SXfI!Bh58{&A0 ztn9*(yzc;y5%~y_F3C~Xy}VGbaGc6BY|!^Z`e zxoFG&>*I6G@q-X3zoxewBLt0V$1kuv0+uUSC(2vbY?vC(m(|4xDfoN+Zu`!KG5@vK zhJJfEr6vT#&n4U5%JxHGxrpBKBGL2bsY(>nSQ}V`S=^6GDTsOrUCaKV*L?2|bLuMG zaUl>3b5X12Bs>C;@M}TCCa260=6RDGX@<9lSH?HoTwQ?!PifDT20UsLkKN-o@)p3M z9|<+k4w1VBqA-5$TzMbex2d2>Tn-dAlY+@e+oRQGhl2H2Jj{`ns^}vNk{U zD6KtAxMixA1mLG=JRHbjHY!KJrBIwZnc2QuVlrMv(1$&yEgkD{@xNui=<%8|?5Ki;V} zzND9f>H!3i(&GSci{41El!yHMyJg;+Pk@ksTD*Jnmh*i~J}j??Jr7h2VD{hm2qN(1 zq_Vo6Q9Wk-ncuvUaFU4yb>XH025`&~mtL2pI-QMdlVI*-R`x5H%7t&>)(C#!LL45N z79b!q@y8z?Zpsm3+ute^UlsO4*tZfd%tbNf%_*hojU8(yhU;tG0qz~Y4s0kk zup0HS?7GaGtyt}%llevLS3XCCZA1`013m!hT@%0x(tG(4Yy~0)e_Eg}MBH_Cyi%KI zq1W=(OSJ2&7!Q<=2B2sWEU%OVy>yuzdI&E99#cy9ou2DY&xt<%uTvxdk?o3%|2dpj)Dh)-y*_Lkcb!repk1-E;oD zmO~Fup=5M}h;q^W?f;M2hzP;k^qVce0D8pXsm`#sR46Yv1@wHTmv**(GnqHm*af!D z3P!`paMU|JY?I@T4~-QdD+(H1D*vz_WQX`1MrGJ{0WFZVSp3(Epgb{fA7$u(m9GDu zgA3Heio&gKzI$?kqZ5<%iufd;_b5~b1nS~=e=43?^z#O?_9b9C%!Se4fU_AaTV9Yc z3bjth+&^{e3I3nlOACb57X)3L2TCggnrRTk`4fuBhzF9zk)q;Qhj)NLJyEph>@w*$ ze^v+S^UTCu)u+RNpMC5bLOq4l?Dw?YL4O<(7av~s$IAjU!@@`W-R|T5kl;9BArLP=ms5qDuj}b>+VaI_cMrI+s1nBhldf4%G6Sq8U zwzYrl!_z!(GU0tG_7&i3E+j`WU71rnc}Zb{O$=FC%4~hX7b=wvPcU?sn4BeL0>~Bl zC;YC7WfnK;O5R?T8L&I3#9SSuILyiBcHlue|6a(= zT@dyZA%1#k`9;Frrvd-Cuge(Yccwu<$_23t$2gs}Px!-(|f%UM5l|LX5JIafOM03hVnk;<)~I z=c|G*Ag#0P^ys6rfQ2 z8;*h+Nh1Sg>Wp0yo$Sk-(n>+jfkZn+OZ&e_-)cn)aZ%)%`?74; z7I}%w^W!Vx--y$=JbE~a;xu*sqd%ZQfLs7(eG>B7m~;Vy`;}7d%a0Ws%`K%cDass5 z(khpw=e0n!1w`1QGrN08pICkwh(}!EFnQ^?eJhU@K7sOu**mk;Yj^%|9)B|qar z_MgBLx)5>XI?XGi$FH-R**{jzctw1XQWlWP6x0FqwnPPF{U2C# z&sTmXLQK+FCssiY&{UrL3n2h-s#`| z3+-?2{;!olmxT`S$8d;06qm6Qc@u`0{wM#w-RG2V_yzl_@1>%HN}c$@ zVhv_+8eV}GnwZqEbVX+?Zg;u`^d-=ATY9{_uG>%bOI}K~WiW~--2DK0fxCbr-lr!ves^jZuKsL|l<9B5H z{w{WyA+HXe#}o8Y@m_iN;_YvG$<7{!FF8tKFoGzn`;pvMy4AL{DmLY;j5k7K20AikJ(MVOilSsvwFsicXWUem6 zYyO^>G!~@n^+7;cRkB4TJts``-W~a@cEssAMeT!a30KMHpzJP~#`^huzA>G`L0_x5 ziNtAmPiV1&97Gq$b)L zal}NCa|ARVB8+6wI-Au&6BSzp^X{Da{heigg6z#4@L0KWMdX|;ATvzg4cF8z^fX{c zIhJx=jF8mVyrEp)MrAI^uL4t9v`+f>R%Kbu))m~Z964UUCMsVg`{rKhu%S64IC z(o9Q8lVL5FZHTd=U3&sA=Vl|&+ztT6k)WFH7N5PuLf1Y(y+}FY2l~Y-3H23O4nz; zrnRKk&y4~Bp-Wz<)pTwKZjfM|fDd(W;)R^U^?N?L0#Oii$aptVvoMU(MB&c2hO z!l9(_!C>Io%*Q=x9CY-gPeXvZ=u&N#8wHUDVT}*g36jVJ$z}JWwIRe)Lzkk1f}Qnp z7@*@}JO(ri@gD-t2Flw9$O$?cyT39NhwBjPSL2$)&ZdkAqmfBPhyoCHL+Qrv+|t6O z@g`|s2BrUxio1G-IZ*WQ2DP+dmm;^bvKG9lzcb)c-bIk-G)Ta=#GLIi%{pgLt^hla z7A!}+2rc+|JO1v7I60KZQx}JGvof*tl80R07V@S9oqi{VP~Z<7WlSh&oqv9}r*oUK zw!7%EGq)MMW7cp0tNSFh0O__`Ks|yzcMLSJ4O~cnP5g3}NF(yUvdxM4GUx@?u^^j< ze!oHj^ z;KWa}Ohf$=?+6;j8Vq4#Q)H!DzT-(TgD6R4jQ__h0|8A#pyjyTXN{8*BsV#^ai7fF zm+;m)$cSCjw3n~MI1W=InKEC%OhgX?@djsTiQ z62^B5%&!Qwx!y7N%w4H-q^5Awog1mc?L||Mvd)1U3NV)(6d6ZlF;jIbcS`ULK^BTy zmr_9x;_v+iG%-*WQ7uzp{I)kOH;C_Ago3wDGTfbWTHhs}3TVYEpc6bA;vhrqJ)2c$UgUs z!2*{!Se@%{B&}Mh;gSpfD6>8R=R5o5@0_ayCeCDZDSZ*2XKFI3{GLE>$h|>QX84E- z-y(V!yAaQ_(#sEGb$y9dl2~Vur5SqOs$HBpe#&K{BiRY35gEUNuc-As2)J|MI&v0F ziF9Sn6J>^}DBrDUZ!M`VBymK{us$+e>nsYVi705L%3xnQyqmpB5nRQpa9e9mRHP{e zth}b-Me2LgLCX`Ekyg{ppT>_Xtx81z70Ge$C7wr%^)~+ROz@O1ukUVM3HrMM2%J~h z*HD1`NjEpVccVr01wCDPee(oxftQ=GlewBCWqxWNB}VkJ7OBKaSNBq|?8CFs8fZ;N zHgd=ea3Y>^FtZ78G}lJeAzaC6dl4@CW0azAtlU6n3ZcWJo-ELa`(R< zm0SNdp)GMoJttZtR`gP8cb_b|Y1j62aE|2>nn_R#JK%w`H)YXW?AVcV$2J#>*w^bs zHXy{eL1RmWPD{n=~6K+&@kay z?@#w0vbnAUX`Dl>H{VdMQS=+{WqBY>ypaL^A7`ELu^{e;*Gfjq6++01(MbCBFV0&7K)sTCHQL0s(lhN#z;k&tHdMrK9cT_+Bc9Bvhl z1I6nke>4r$u{(j52((pht)(Y|aj2qhvbWDm@27C%-TBVl?X}%byB#f0GaLsD#KCi< z@p+E)^TMqTnh4^jI8hCe0%XAtb4E-TIQ#X@N;%&e{a(7PegdYN{AIokRgl z(Dqf&_rsj$(Of}7ZfA;X`TpTe}ww;NDLc?KR;fHCO<$U6Dw%F+XFY^X9gYzvMdx40f@Xj956C3peCI6pY`}DW_cE{xw$m zaUbqGHH1D;09so-P>^bZkg`A}YdV6LZutD-Lf0;-*xN?X?Y0PMPWXdif~2aAF1-0l zLWGP}q^Y^p$j@ySW`!9uc)x=@(X#xPh@}^lv6d&u4Q~$(%ihtH0XM?v*`FiLAs4uE zVd*l$q(+m(B4nFxQaRDoxIgmm5VmiPbq`gm-V;U}4zG3FWPIUANV!aU6X710WZ==y zF|UQ%3Psu?{eEWqqZ007%b$q3W66%Zx#cX+8}7&%-EUN~XkecsJW{n;x=pd!=2I;N zc0zw58IXA%o8#HtCTbK{lu)F4mAuvH7s#d)5hfE0VgTe7rs5^5X9ka$O^X+AcCvPO!B-EM1<5V?+tlGvT`we9xJ zT?Wf83or}AQDO9rB+-6816ed5XsY;%Nv=$LnE?p4;!=wn?Q5z=3W;9s8)lZfwXlQL z%nV+u&{?~XRJYO%H(H*T>9S)?>#WsuQfl-B+>DAzqGITviK7i$JM+6=he1bN+<5fa zUZ(eb0p6Dy__Femq}HGX$RZV#u_-DJ*3Rwg2+!26!66L2eUhK9vvzR z`oa%K#k(oweE{$6IFv29$y-Ev{vrPxLR6I&UnBwzi5B%|*b()EDARgNIxr zB)8x71#ODSH8?NnpPc>=nmOrK_@QKtdeg)}k*U$awhkTdg9Ek#UlrF9CDmI6)+(Mu zR|>}m?Vbn4aS21NIB|m~b8wq{iIZZEKL159sZr3HT-i8QqZaXok2azfn}JEI@B0fg z-BDNFGxXiut+PneCa#%wI$AQ(EH_qrV-lGvNqsX1Z$k?hgj>UHrmcPzzz}R!9C*2V zR&jI{fQoHX=28`s(KGCzJM!c4u8)M+@_RGP>4wmhKu5INlHp^w3sMb z)EJqba}iAnK}LvPDJGRjghs*~*{n#+3EXrR6(pNGG8D1ra#B{Nj-QhFsA?VFeO=iz zqLN--T*K-Qt&9d(8C-Mha949wj{b%hT2^0->K~z6m8onnVyYkajE?G)1w@@=k8UHS z(qua52kMsw%w0h@J#xD1>Z>x14IyyJj2&vMwOG6~*NV^z!%K(4mZmNoG6+H!MUHU^pa# z2D;A~op#$`WbH848dlvKR_{eiW657m{Zh?60fZKaro~lBu6%^KtWjD$q-_r@q`zXx zT!K2pK-}v~XVF+)y{-KucWE&cFd}3UIrIE>ckp^mr?M-xs@^P18Ec(Wkb_L0bpO~# z+YP2=$N)ew&C%vSaK?$8H>BeE*&yr&aqvv1*26X1oC1MQmjg_`a()VR0G+y)5j3ch z>M=oQb-QyicS#pG+(6%3o(=s4RPr^@q!b#-$z~R-*kJ^5fGO_F)*#=WStCOXHPW;r zW|K6Y(|QNnYcheR#HhauhFO{$9d#9N@-*m2oi?nLQw*72pUY4Q znw%Kk#p!HFk0j@yw&AT;K{f-d=tfdeN{vuTfb8)hkLVNrD&BcM{9hDT%S z=AX_$yaMF?vti&%QIDHN6%{gx3Bg%y==5Jz1tM<8WDDx_PNKozilHP&anszbO43u2 z5Q*9(eIK_LhdGs#T&)hu+IgLp1(AMLj7eEVJWw_65* z85`PEOKVd*7B!WB85kG#Wm~OK3ia=vCOAg*G^jv}VxZP|0Z27xv?DmgKCLJCOnxWkQg3G zZTeaMiXg+7D*OS(N{JGnvuE(0M_V}&<7T}LQV+0qWF0j#u?{7|ql?9;RZjxnBiVF< zCQOpHTPv^Sw&`WvKlo2W|K;=nRS)Lum}AMVj&4tBV%mNNkFEFH>pDZ(J%mA0g_54| zdHu(OfyyIWFK4* zR`@USRLv;C=aAjU*O;lF3t;{9^Jk5PCTBguRt{UK%WauUktMN;<&}>v7?2!ah#`|v zwK>y^C!ukuw0iw=Phh~TxsycMAql4K$i7Szktn|77ln10T{68IGcp>dHvecS!$E4@oP{c zP5+?Vh@uz8$UV6r*ZJy}+`YlKed2A?+L=IS0-Kl?D%;#;5!N^WFp$=fmMqC&#MhsZZ0nH5$S z!$3nM5+^;^_5i@3bpkx=Ty#li!^ZkN+N+l92gS095xnzuK6sa)qxn{OYFb3iG!nLL zjSs9U1MJclI}o6=INoD<(f;$)raSS%9iR^usv<3MnGSz~`^6)O%6<)-E<1y3!&?u8 zxJnz;6?FvNP82_{K{x2Y;_|xU0XTSw`u+z{nxCN4hiv`u8?97phXf(CXIpw5x< zIQ!B;O1O&Q!IGm`5tRP;5?qRPh~D_3j8h3U%k4@WVtq0@);5O-@2F6sYCU=LeO_Rr zYB@C~NBe4=ql;*jWLIgn*64h=GUn4lJ*(LmulQmkP;lX~vWsqJV|3QYGOIiEC&>^S zGB!$xDlz%gA;|j-=5|c*dCfu%PaEm&Yq1}jhOTd+SS>OlI&_qnf;ToOh4L1_oJ2v4 z8FSeoIuLk$8kI=RZYwt}Pr4O>?OdXm6nvUeZ(^E;qwy_l#Qm3wwc0MXSK;Bm8~)N{ z^eL`d`&SldJntS64UFb;n?cog&u5h9IX43U=>)qYs7t#|+bH24c63S5^J~MwAw8p? z2L^9>eypb}I)KoXA_I7P^Kfcchi%>(OV)Hn09sxYxj`@F^1P+y5!W2s^r@AQFScdg zhEBjc6+8>f`+Rd{TR&N8&K#6V78;#wB-5`_X1`=qE6!4{cWUOah?e^SvD6!Tn#6sc zU4j_b)BIX##VuYGLp}4v^1J)Tn_8Y&>y=Tz;-ato>92Wf;JFvLHF;vd_Bk-0nwT`- zndmoZoB*)YJjW`zl}{NXQ z-5s{un$O4GayeX{AhJSexoI2_Zc55c0l9e@?nPFQ203Mv2Xx5cAGp{^08O1>jjL#4 zR4TDx)fKD|MEP<(eHB8|JiEdDoNjqN$#uzeu(4|nBW(;6ZYE&4xnRklS?Ii+1t~{AVwplsRyYXmu^c2dFXRp~j9hNA zxk7oqY5;rRz#x?0S*dE-)j<`~PB$7eD1rHk585j6t5BJy$NfU}8$8ZLP8}t3@6gMb z`AHE^Zfd4hGjWq7o#|5s`fldLV?*y9$I}*!(jgA*ZHwU+!&$wY4~%2gI*LggaapF& zV35|rb$n*j`-V>i1vJz_8Con#3v4;K<)-f00WuQDc}Eb%jI0<#GaV${aSnbcDj7VPNcq|3Bz<_HXt@%b>SO_)UIg)7xZTkA#Mr-pT#RdWiCg^k|aBmcP$AUETwlSrv z`&)1yDp0uWCMBpC0Q1!Z*0)06p-hhAA!lR}IXboWjslHj z>5LoOzKEaCcx0CQYr(RC8G<>#wlGK={nwJ`c#hM*al#5?x?)!5|&Sd)OGjz?nwHQ*np zC`w4=TN__{I_G(F?{cu%>Q_I?pedUi9cwdshAh|FXi>b|O-O~pYFcgD#fjS^2SHS- zl0<4>wU(8pDqS}lP79J98cld|HW3>2NLi2;qz1Bq4PXO;F_R3BqxPv~zpGZ1@}~4J zfrc}3BqyLAV9_EJcY34ltX|7>ojnpV~U~uj&7?#KA^>lrqQ|F=(yfESASP# z;xT<0ks#hm4FJ`FE~J}dAa(qXV};5RqHv9FbwF)EqIdc!Btbawh&A4mdl>lbIu&%` zMKYMB<`bPY2sMv~k`T3!*!pF25Ib-+DdN=SnAOOYjVfj+SR9D;r+-TAs>$KZOO&%6 zM^@BU&kPm+kq9l&CpWTUc|U*6?j$Aq@I7xm&EC>5Np7o$l2!46>?#ghZr4723~dCx zc!K086$h}pbVke=qzv((q5ziX@e8Xvij@6o>I&c;A*IAr*(#!(7y7z$%$m*P{qPt$ zLtIxg)Yy}yd^zbKwn%o&Eoy(`SJUu8kEZY>X9=ur4W#sDNILb$B18;v6)nH|Kfa43 zgQ{=1YV$El2aiNJSXiXgVgVIxl@c&N1N2>Wx!&o%|0E`xe5)cHEv{;LVcc9!i`eEa zP+;bCs_)11b3>P^T}@C-qmVXZ=^CbI(GwpWw(;Sxr@1`jnJjWtc9QDidyKqg!O6_^|V9%J^ zdi&AbX0PvHx&h0`Ua4fuM^Au4T%7VnEOd`=X~TB4`bmGAGwLl%hnad*$I0B&Gy2{$ z-v(({owUx*vk{=a11OIuX<`G&!Hi6m*4J{}U+2A5*|x6Le|#z7^WNHiyp4TX_Dyc4 zUZ^a6@FhLfqHpH6lGImV?|J7;KqmD%2N-!u8<2_Jwu#b-9G!Py`7z!49q`5PWbekf(!fxygUfG0NUDM z6Sq1+?8te_JYB$NWnn)VbTs?mCQG7epgl5!H|QmoO~SR0AKN))Bct&YdksK+9c*K=0cg*n9*YA$%)|-?$W5M%2p2k9>45Il(x|dKF$btM5!ItA3u? zukifEp`vHd=>i|YTmHWtF?Xr4PY!9#EApM}C~TO5Bu@6*&6EqJTsGnD!kZO;**kOq zn1?Hl4|Gre6CsL|xEZO%1uChjoBC{k4FIs))9gswtp!o5JkRS&64l+mfIeqX7FBS6 zYXvCwUG?t4AG_T%CcEH|)tdX>0TmY* z^^hztE{+E48bw&zG+b+iL3!Eq%5{*Ao8<~17?s1fI@36lN4yw&cHCjB%x7H1$Kk|4tI5N?1gI7qd%_xbqp*q%3*PZEWd!>!16lf;KOUASUt8zMywaVN(_hST|* zdt-aPLQeNaG6VFgNh68>LQwZ~_1YfED?k!C*8@xtf=+@&t>heL$1#|~3fBvo9+m^|BtsxF$ zGlwhdT0Z8;v@xg(rU*wXNv|I2UxOp&gB%b4pMwd)LQO|6=0m+m8KlztKRhs-3w87k zdON}8sTc?d00;Ki?U~ZRhkb#XfFkRSCs-%57V>t3cGOp?!*0NtSEEGr6704<*xipe zL;U|6EH@+vDP7x?1x5F6_`h~* zaTe{;{2#LoiHysY@pPUQ#tcNBQ~(DWl=SN*9}$uB#RX_wlwX5dwSOG{xAc45aO_`V zLlOhoCWxKxOo>=G;g;SiXZ|cwRfJFm`Wi9Q->Nyq$!nqW+Z!2xk^$})YKSF*0WD5Y z#l;MrKOWh>TH6Ae9$)9tU2n|^i2)1WBX;H1396LMNX|78(7pQaIR?8k&d0!-?;P)ec=`z5cFsPCx zY7lp(N^c;?u?@hXCoU+MA!zjwkYMKgK*aAXS}A`{Uk@y_KMu5PXDJ@4WVSzsn{J^N z6;8X2!Y&p%A#0u8rwFvtT!JGjuzCL?J9bd0N-#(WFBkg03Hy0w?=S@4kP8M!LxR|k zPGmyU@gY`Jy+Xv}CK)l619^C&gf$5W9RHG{UbEcPj5QIc#V;oY9P5yT>;eZEfJVwE zxxYwb&F83(ot^Z0B|PTzlnDwZmH2r9*?))zM2jFb#d?)bV;1S$Rj%*8o=ST&?PzFE zmDw+IIuMrvkoZTqsFL6MuhH0H#71o$Dvf`>NV6wJ3Jt;!ARr)WSx3Ue>LgcYWXtD~ z&d(XYQL0v6Cth&lIpVUssi2@fe;e5Py|Fo+sh^`+oA1Kx4CB1HgEHz+`~s0*@G>O) zq%p|s=K`LRJS}mGS_eQ-7M$*bB;48aXr=7oEa>DKA3Ag2-9}ERf>#M2)O&$i?+h`7 zy+;z^Y*_bSJ|%Due?9r@G5&fgglva$x+{JU1Lbr;Nj3L&xgufriM@_I*od0#SHcKw zTl`Q}zj_O*FmdT16DD^}h>agWgH?p+WlBM*mHxo5%4XA;si@nlpv3f!63 z>EO!!Uw+w-Y;(x^yJmjjFE4GdtI8WxBz{*doYRQk^8}r%=g8*pjB^X9{oVYGTFv{K zVufSjF5VR?@;hAweSmd)W;9ZsK)ky7FcF|MXbD$n&YH5}`F`lro4fF+e!?+8HHo|f zmR}wxHaam%bsJwgoJc6BTmTMOP@L-OI1c&tDIn4F@a_VP-wo`jKUm>+3wFcqI| z(2z7o68;x*sQ1XA6aY1=PW>usUitYMI@^IyUz*emL;+@d#~Ewv$ojZS!}<>Y@UO>` zdOoNGYbOS%GL);bd)&H6M%_e61)ulnO(m@{CkNd6AD(M^6uT+{QUY|&0@w!H>%cMm zKw3g5*Z+90T(1DA`$YK8K+~Uwuef+6Va~Ma#Z_QdA7M|)aT*uN-2%~w(`N3zP*SJk zIPVYAhP^D5+5-SddhLMK8a%{TNB$8wJK{$O{rqq!J${Y3B}#w##@&|*3isa&vqe}y z^*xy#ZT2?P!V8|?zb_!umL?N2Dl5L3MhWi!=KX*`z$JK{gjzIV1nnHSDv zIJ)=1V&*Hbx3sgT{wzAcO!*8@0Q}rI#L$H@i5)4TdR%g9FS7%(y7`Sas=Q~9{SSTA zLlzv~7Z)sf}NmDqlzas9A%vqp+ zz-KeK0a<1ik$WLBcqWq4>b6O;0^xwC?{@BHhML1OX^Hv>J}+&k_hsixp2Lqes?8h8 zs#cgs>#xJb^c%&P`OVO`+I5mxz??xnO14)HDxfTKiG3~#Y;q%T+`NX%_fc{9%z-zf zT&X$`d*OS}vue=uw&@ru(^UZa8sJjY|FT$T7(ng|_N!elSb^PjNEtEcRFMWs}oh#Fg>ezW7jsT0Le4 z^-m27s_XY|qdMRI0PR3AncCzP&oMy&j0^*XKh<3l1gqlI^1%VX57Xi$GA$a zh9t=~t`5qXTW*_k}&W z+5e?P!YN3}=}9a>Fl$FD{e3(*(z=xWNfE_fkhZw%dVo|qGDR6?dVnPFQS%Qed&%7f-XcHo7da!gdz1KOr z9qmae3HgmY@m?@s$3?$fY;^dOzEv-MPiL-*lVJGEH36A=Mu=NKCc3E$4fW?yj%4V2HvIa zVKR3JTbB&d`Jo}-qNbsEJOUBgt%L1FWZ4+6t;6}L;SN=7(ux28cr!{VK3gAr37HG+ z4?m?{zpCUL5V|bTGMK~Ha#%^X&w|B#v)SW(8h}NB?U*>GCcGS+?DdXrLq$Va$Mv!6 zGZ)5Y-YLlbp*9Pu8PV}-;?#iI$8_GQ=9GsQORv7KL`MFS=EC9C3Nl~KNaU&-RwOPV z^Eq7vevk;6L)-$FDDd=(XG2?zJ7qjO=;g=G#33LRI3)6>FX&l#Mw-;~CB~YqveOM> zIO&i+kRi&*dM;#jxkH|_)!UL8DyLv+<>9==q-&=?v_~$y>c>8NKBxhm-5W}|S`Uq^ zRGD77^WmYpBjys=wkOHWG-Iv1{G0nHo`_Sb1Lu)CppcR0ggUMHht@h3EOCBsli&5n zH-F44&6;u*#XQ0k;Nvzy>$>d@ZL~kPic|`UXnm>vw!XJ?Y)g*SB7@J}Ef0KF#8z zarmypR~RA87H<*snFtV@e8B|cs5eiX4iK)%3i_cnFFJ|D#vD^yz_w9zD@&Qy#4kL%7 z7QlSwi7CV!he=>&0QZpOdIF40#(b7MDC;FnKAmh^05E8JLOrh^V?i2IL}&3|!_88F z+t!cF_JpCxfC<90Rh8`(^PENgVrD=FAqt`L9*O@92w;w;v_A)wZs>iX>DG*L6=+hA zEN8U!Us#uJDmuZvWi$j3H6!8z)6CG{!=q(2R2^k9+GKAGKulpQYZx+S`h_KZ-?Sf% z%4Y#5*?>=-@)>Y-x^^to6v>C&|Augq5RulumAhD@!snkw9r;@X!<(uL`l-6d`WwX7fyjs+um9doGWduHO{^I`b zHt*%8wXy}D5vTQNDfXbVG4qO}sLr4O%+8&P)b@FQ^;jb>UtCRoLXtL(;Iw~X?K}8VCyaPrQ4jY} zR^plK?NbvhNPnNZ?maSw0^r`;@zpoS;m&hGbex>>K^@2*(AZv&lq(gu5ZJzcAh||? zN%WojHu?^^!HYH+V$5tZQ*FzB8k7pC7eXU#IL2MLPGK}k5(;4HJ zZM6m9w*s0)s3R(aUt2#At_v=>E)W&9iET|pM8Qs&eyG?FSR|t=2{)dE9s6tTU%SfH z8xYN(@D;PZ<5ZSy!UU)T2w7w*8W86~6SUohjKw*d3z3y@$bfHox zS|F@F8pNj{ju0~r0|a&0=?euE9z0Ojt#i95(qGR2t)0IlVLf_G=6+i<0~r%lr_M9}C&H5-PmRJqa2q|reJ*vqSjunBCw6%Kx&G)~ zDS$d-i8A3rOg;=Zibg4hJ?7~Q2*MEb6ZK)(~AUo(d=Xm?{H_>r+>&_Pq4 z$8a&^`bT&2&zmYyc zD-QV-22}9ml#MR7Ox;%Ar~(UmQW0r}IG>ekrMf)uDa?M}TAloF$h8A)#)ec(@i^9{ zC$!;Tgh`l*$gISaQ=aY5jPWCntSU;}SMsTb}Y z=f7(uE3AwfD6HzSah>$6_5%pLoBq3Im=_i#dDc@#Ja5LCyib0Hc z`x7?@%7|SM{D5p%*GrHlmBy6aU*AsC2bE>YiaR;qV29rV+XJNyxIP>&q=vqnDK(qf zTe%Xlus%ApMp^b*6!lqHU0>9#oK-r+!)i-M$5i#n0y( zr^Zb(cjLqRnBjp?1#d3pm4(Glg~(_GTJW}UA{aU#$kf^F-OuTJ3|A6jyt5E#5 zR~sd&T;Xq7Ro)7K=ATig-tBR51S{LC1DJ!7z(fXwc*Eh@^5Mvb+eZY<+=LEo5NqK# zeF|W}#&5%RU!Y{Dv|e=-=oBgxQ-`ca8bLBV`AP!-KgQM#l%2VI1=Oe@J|zDEs>C&m zGpYxscW_+Mh&b5Gvm82taJJjCMB=of>OYF#xOsqZcTvbBk-x<`CF7-1nZ&~k1_JCX zEnzi%q(u$DxlzpVely>vZj>hO(eE_gr0vzcDTd*^7pplz=Cm+?X_IR#5qO;%$Vj8% zD1R9~(PBM|w;gH50On0yHxx!$H>x(Us_#Pk8r?GAa{^?@AJJ^(YK;f&*0F+$#Wo|k z#ig}~KjygKRQ3TDd4s)c%0!s$I;849s?z6r;DmY;3|^wR&?$kCWwt?-E$6th2;-yD zU_`EfkLt2G@^zJL+N%ZG0!9u-ZyQ`kd9D`gn|hPzhc`nFNJS2mv2YNR2g0WjHCosn zZlFJt6|20(we#V2l>~kmIV!rPu9VAn!I;S?$}K%)q+X>TGUWg!bVERgRU7KT32g3E z0SF+yu>hT#`!#a5@d}MXJ3B%7-~0| zxI;LJ7gkS<@j8DJ_-Oaq54%oOB|(2m zWkB(=-v$KQJwT_iVYODZVSFNe61IRTQP-=Guq%I2UiGGR^-?jA6KLGk*iJeuq!Z2rlEX`w4zgkbVAy_EnQK{HJ`3StHPYMmLUkK7AH%fY6S{p z;1q|!m=d^28^xCZl0Uv@p8;+q$FD6WG0L=FM~6s3tw>}C}HG08)LvG^%v7gP(3XteC#kub`nUH+R` zA0M5%?*~F8^#kU;$_5`cfHu9$#ljpeKlK90geM(DSn*3ie||{FzpFxW_7Qo1(5*?F z8y~RWztugQ`;{Qdq?_*j$)x*vKU!Z+w>IJ8+R&*4p#^Ibi>N`Zny$O{q-5^n3|w*= z;iNktKmwd=_|Y3@6M;_^N#cqYB(>uZ$}UWc@m)QwTB>6#^Ida1(gBAOB_>#De0$*D zA$<(UzUxk?bu5P;nI5*UL3Z&GkMh-G>fsN1TOwjHd#ebcmLS0*5(*5;O=umIGa*By z-09?ZCxCKEnrKNHGO6%jQV=KYdYwHbNT(WeHgSgcf0(8lcvP#%jCPN3y@g~bozcni z{Z10QytDx>_Du~(fXXwKz2@B38yW)VQa4W;O!_&=X1@M~CvABUN;Pbs8sg!TDSvBU zJa#CO`xxup*36H{>xZn&1!_-}QcxewkM-(s;+#_EWGXAIMS@ip2nsIIgABsp*~H5{ zh#<>wbVx(Q{bS9WWS^UUUo#cet4_CLj=+EoxSCx3@j+-`@18>1!*q-LXA?X4X6rOS zCT8G#n8x(J&GM7S`|(ZXs1Pk`qk$%(&qEW{5Uu&c-01Mg=a7#(QKb){+>UO5HjrXE z4^&sXw_AO><@k&6O~XM2-{WVmcYld&P$0*+Bky+;x5qLG_Irw9D{X7@{vYRyNViX5QPA-;0002b6@#`%mr5 za{qa3{sZ0=#YIoXlFYlEXZ0+ad+3nmgBkRcEQiLqZG=Pg7*&MAuTp$n8|K+_qA%Rf z5VlpbCc(`LF&!q($24AVa~xIo1-dvt!ed97g($AF0vnLl6-;<(19GK*Zt5Epu?ZhJ z8ym5`pDK%L3j89$B7>pp;@Chq=>wz1N*5UxGcg8$Zy|#FF!g&{*3n>UOe402B+s& zDgE8L{fPp&xOLMMG!Sl9oG?a7EPMpQ1s{Oc3?;{<#*u$X=VB#lRr~kTL^Nn;XM^#D z0wz_v-Bc@gQ2^M`hQOQBlYya`PSNpH^jVcrI2pKg<{&}0Kz&1tL8TZbw6qQf(70d- z?e?sTY^|ZxeakU&)&_`4!~e0F1E+vQ@yZm7M7D) z3zt0N6 zR4Jy3I^4k^aA{gmh_X8HR})?Og7JvvAgb%|DDI+>Ip-BJ;Lx8SdWZqU?kH541z~if z#3@09tvxyY{Jx}_Q+J*uIj6Mu#Vp9-cyMQ&YG%#2+6- z#am_-TFtnP99Um~A_M&WT^3%8%I4vGKH}!U#5)_`Z6W;sw_F4+iZiHnT(e5!l!V;D zga^YGL1$hv)y#MCc@@|7g=Ar4)UJ28vCumP{!qap*Zc{Q(I0(g=8PEjNvPb2w!qO2r?nOg%WOm_Wv->rd^_~fnn+w4e_ z3u}u{;{BO$2}?KlaN#)X^yyw6kaFQR`ibNxe7P|4ryya5f^oAbWA9>pM{z-vJ-YHr z^Z8!q-kvwRk8myhBdPn2&{buh^mO{U=b&Dax7%nnoYd}bkhPDW^L$UvEld8`6O5>8 z!f70(T+s3j->SshkWr_zv2h=@@~UpHfcq)z;E#N$0-FuGQcB4+U97!9iQQ~4-EQ?j zL>D&gDefizA;%Oe{&}gG@J#A+arQ|{f0bXFfdpvofy&wV{>>7po$9X}rHR@8pOa0! z$&gq2nXjsd4zKx~nQz2gh7rzQ?%Mzal=ishM9AL%5K;9L1$w77;yLX(ISXtRB93g- z%IZ~=OgWxWKkaqW0c@fWE`fhv)=L%gsfZ*@wkvWsLBd3hlZJ*ffR}*Tt$fCQhL~@i zHMyHqqP+n0x(y*8VO!VM6lOtQ;}k%|F|Dm;4#hclM?r3jyxg`_OJ}6Hh>hV4($Qqd z`X|BvpD5C9V4jmkUd6YDXOw1Z?U5%;1*}gi%)d8DM7DRHRQRR+pFh;z1K}1_-~lk{ zC^b(4-`}J&QblK?OEfNEuhXFE`ha}3<*0pq;r@3WO4(-S6VI@+kdq=|0CN8xI z;7laM$hX-m&xawXLv+Lgq4qh^MKOL7U&dYsGP-cDS&rF%r)v>89kdk+ZVUHr%x$TX zAzZwY6$tPhn%f6S3$?Uujxv8B(`vR6aC|HlGB1&~931?f4b|x6grV1kxK+gk69-Jx zD1f=;J}Ga7i+ZZ=5AT&f&kg>Pa$O!2ol9@!4O*?GGo-pGLO^c9k;LUpg?4?<{VgIR zj(p)VZ3*t<64DzF@5Ru;$2STbn6BgH3|yJ{-ckNH^RU-*DAi(ZI@jAfRQbJ09;vV& zYb6L((Y3T4RZaXhb)2L2=b(i=8}}ko%6$r3G5KrGse;*zN zIBbkdByfMu=Ars7$og2o`he_$PAGr7O4H7h`V^0-=q$}R53Z+(ppgP3`ZJWYhC?1_ z0FxbA%AB=S3{jC=^@H!bmTUd#2w0axhIW@DB$Ne^wHh*8EasfYb)T zFW`B+r!cPNJdwMj-_pJN!aDJOu|I8TWHwqJEkgKyL-{Yt>=oe!%vq)$MP^Lp`O>H^ z*oXImC8rMLn;E`q#1qRj6;oY3amlF^z=Y65V&{eBD(QE z90#Zk?$DIpTnR-am>p&0gADmRja-6_k;P^!6>)s#hmpIQ0UT9NR0W z=inaX;$|?MxA&)9tyXW(X)?w{ILat7UGtR+92+v_) zj9Fj1exvBP?jYEvP3En69D@`cIvvhh;Ly@b4r9i7bz2_C>1Syow_1YX_5vK_qojwl z3m!o~jxJsY&jP2;J-CE?_h*?;Df!@C^CA)e1NkoisZV6SOLgw(zkuFqt9p!n;VW4_ zvs37S{kNEIT1{9DIwkO8ftA%x^_-Zb-rV9y+&%`1#6Tj8rogqZ3pf%d+AMcypyp#VQANBkWrj#_8f*U|j=dJq-@a<)?3TyR}gu5=2oW)U&L zIY|iC10i=LmhR&XVlfmbfNN<#Ur;M=V20GW_l#(|595O8)?#31Kqf@C zcYg%;BYn~YCF$+q<^&Byu}M2)%s2>QAS@_|VUw-s`h63RZZz!xVrVQo`8lK}(d7Ui zoQI>ch!|oI*Kxl(3;5lW2JnkV4#HmiTti*j>ZoY+lY-~2z_uHPub<^YO{?lW8r?VD z4Oo+u&zKu6l1+sVZ=7XbTjVUOBv@|$fAUnWe_Za5gk*9eNI_<26&MUd+lsFJq{QdnCyjFJ|6|_=rH52XdIZniBu9 zZu$>oJ%^YqP2?CgcBeWQ*pH3eztOyE&LRkYmJnB{YSRjbD3O- zcKV;8r%mLA4K(BxN77WB{@Gj&i1g(K zexBm5N3xo__V%#sl>2fU^^pINFDYzL7hYyIt19dS4qRB~JamI<(1{Uf1MPH^7@m%A zx<+yjXnio&Xn`|ZmJD+J)%!5T(;+?6E>WKU?1lY$B1{{AO7;5K10b!3dmp?Quri^bBRlw z_VwXz2oa`4frgC$;#CNhHOq>oiVtc0nQY0+gC<&I_J~01gSgI?53Afqj(i)XdxD8C zEs<)^(v0cH_(X#QFNA{r%+1=a`Z#bF1UdyM>*#t zRxWrw+lN&8CUZJJ4tE(FQkU$@DnU1TF?9G#T4uUJZQh`c3l8qQnkd_Kb4J!dNp+T- z7!lAE>bB(K6b?jhU|KFee>Q$)lUT|Ky^{1MsgZ?OT+md%*^OlHhy_A;$3v>ezu{L& znqKuNJNUvsPnPadjF41^-tmrD$*z7&+PDU~^R=lL?%Ldi4qck8RbtGxF0V~n44K~xV!gyrzW zg!!sy-uOqi+Ev&G4M_N832o$C{J3sQ&0)K#T&usF_g6*d?zb@zJ>Kgnhh=)?6W<{J zN<+xjlJ+RL8}qU_&j=D0v&?i#VT1TdA>VckH}RSfDZe?U=mNEvu5aUZIH*jojHG`p z^WX$SL?aLzb|6d$8@Z11U7xb##Ch+C-+MJ%((Y&e3KjnkhUlZk+?t{-q>v9iARW4m zWS74%<_Z!vAE^7XODFlJhv^lTR^wO~Z@nDNgBxsCoROB%bDukFhGH1g7aCyF1XHW@ zuU~9Hzwq>#1>FonaVgE3tLVIgsn$T;wg)=5c8RVNI)280FDl1y_Tk)H+&zG7UO+qk z;64&R=Po~M)C?Dw^?T>RS?M!HowdiuLSlP+j`ubuj)99|a(&EQ6KQulAqD@8PAE>| zAt*sy;lxo5R1e}px#{_`HiN-!{<`s%t4}A9k;zBYRnhQ0Obnk^%pWP$vUzeYFTUbH zGJA!N1Co3eLnaI5cs`fvn*ds5M0)zkdx#Z-MHUXg71m6wAkLy@5%-5Yp_81_l4JG? zgu&|q_jcz{0;TYYwD+9^@0%3Dy*G;%Bkg>EEo&vKH)DO@=1rh9Y?Q7Hf}R5)QJJ7u z1+)X%Y%T<~q3P=*z$2Xa%ck#C+3KhUjaWh+um%x`VMA*1Qjs+IAiQDl2b3l|nV$FU zJOg%%oVjI)w?q`g?90lrr}(rz#*lyxUV}XLCYVTZ@0Y2e zm9T)M)M;yaj^b%9exdjy!lNffofL@R2nKy+%w)*ZgBRmd=mLtbi>yRuLj3=k>9>bN zmonnQ_`wadP8(1H!sQ2m)a7@DWHka^j)1~lwH?MN^b;I=g1am;` zdgz%(^0`D_T!hK^{!Mi)r0=uH%IAf%G)Me`9)P?PWv}H#h@mvhyWwkN#piF5F>aOD z;~;w-ZQKYBXRz_ybsst4{y>KthoFD$Qw>)*BaH4mPLxvzNeUmW8uJVthVxQPiCjuU z;Q+8Wdh*MO)T%P{TsQ4NxobJKq{E&YnD?E<$+z}W_IAp4N7O+hw*PVGjN7;d^i-WeUtCz%g$YOhmht zFM(>4VQb;2eQB$^P`#QSwBrFc28 zwKEZXs`iiwgt&E-hWlGq5aZC}Qskchf|k2)xVpX;t1^cFfo6y;huEOZO$G`q)w3FW z8t9-kj5uHz7%dzGWX+~(+xVd1q161f?Zi;l_C0&>IfAehI5@$nt zBomjjFz}4rwzS0;JFB9_-qJ~X^N<`r&3(WvltX*<0)@qq{EkFQE8hoXii=J()a=;v zmSc3UOytp&nW*!%y~_?V<}Kd;{<|kDVOv7JwR9j_KN0U#!LSU|2pLY9If|uLLHZKq*zm z0fxq!NDO-)Br!i2GRdFRgT%A$2viYyvi1?_ z)dCw+qr$p~q2;IzUycPZ>Udz9PIyb4tw;f*j+$YGn<{)7X-r2sX32Qz`5X`9jdhve z!gJsvJ~kjHqR|UQ@F5dcK?MtaZ(K0wB%$SK!3}n?yU|bFJLZ&fO`H(yW$uD{d=e&o zufIkRZwu$cYMspmdaIkr{W|UkN(Oo!6$Z|*8t`t?3ySwy<%Z8=g)#0)qWPu#1C4Dx z;|n$A!}NTkyA-L2$75tQ@_ejEX^HjG7Vd!eS2nW^sph^M5Dg?rLNe{Y=^5#z!-1Gc z>cI;XqU=qX5aMLsx47-^eFddp>rr;df&uznnMgQURms&#;Qq`SfTr1Ik&rZCDqnK; zdvGo-M?EJ#$>;tdo!y;$T-|}81|(<=*WTQfEDzcpS(^Ct0H|?4cT3?2DMO3LoK{{z z$B1<0ND%dme?t?VXyZ~#Kj8+RyUFXX>!1C$P*4vlZQLZ z#@r^JxR^Mgwr&-qM}$su=|$*zSKs*Tc0$b|e^7rj9p~P`6$d*7vM@zUZlDOUn2V*C zLCO`=Z|T^LVP)V`_0K0`N4~IE+F8we*lk#mB!Z8E6F3Skm}cdb;S)Q0a?A3;!{SKo z@D~Fuw*zS;M9?XAwarqV^j5H=3A@j$Sk`Xxm64l?4n}zaw%SNkcohNQ4F2Vsm)d>Dg>;g>8iYs*y38N)R1~~ohngUxC-FZk| zLR(Oe-~)Ofoa=f#yzh#(UjTm+2!)EclG>s2>sE%!uZ5tC0FM9!iv|;sI>y(X3B`l2 zz<~w2TJW?{?^7MjxbBE&-Bl-AW=>qF_mvLkP5FE?`xrRV!Now!VcklOAzu$fpXGP#K+@ zb2Bqam+XFIdts1eBMoY3-1p{{fwzhPL?uKtDRaZ4S+-{=slJGURHDVXS^lJK*Apa% zzxC+(9t)KeJ}&jAx^Q^eBl)a1H(FLTqBVQHuQh4Ta~04c? zNY&tzuJrn`Bv4>q-!RP`op3Q20B3JQDQs-I z+rQIyjHu5Jg-VyYa509cZ_|xXN4lI-TCn)m8?xAIoHC$2XyU#Om0^M+)4O6`j5Cmb$JL;7lv-^o_ z^&Io(x%uIVq9k1Lj-%$EpY5|x#6$EB(LEH3Hb~O?Aqm9B1oB&LsFYidgrJ0J>>)cu z#N0lyc|O_zGbRi~YN5RHzqw2x2EP#~Tsu-YL@)AEaoh{Wxy*P~VjIon}#YP%klk z$(K`;R8~qHM>nyYj6EVwicB_0?uDp94A?kZ%sm%fcy?nPR?2Gbo{XIXd_)y!-*Yi) z2s-?}FCkMb&*Yz?DXV!P1vCQ!;9!Lu?{TwDiDHsYAgTUmIf0}I7K2Kh$0 zHC0Th7z)>mk`g7VgSkM?)GS^d79y@ti*uK{9wV+ij0oC;S?Qu-ABnQf0)l02ImyS4%DID6BU@`MhjsB zEJ`GD^VxQwlaZ&fFo-JhaL{L))$RCn6&XP`c7Ifi-7UXp6iZB3)xGRu-N-^(Tv^sc z2q)r26pNz#NcgP9pr_U-;{0a~_fcmZIX7g9KmwpL$ilR?f)nY5Fw1}zvZ}+=_;ATj zfE^jKorL7Ruuxp8CYty=zc7O5wPV*(-aO^WSIa4TI z@?Y@x@Ba!df1W2`BTDSt*8$?|P~+^S-UORqDBh6?_!H{Y{^lM*IXj$Smds3U>4e|N zwHdjfQAB+%I{{|t(Nb7ox!Kr_cBQ1yd^co4XtoZ_)F&x3SI>tc!m~idZETP4R*OAq z6|lDUW~wu?Qec}b=NvlGHs-;B@LN28dz)Q8ZKiy%tPRkscXrf+>2ob(iP-=8fRi zoQkN!{whH9X--c2yvPYzJnev=Mkqf!Y{rbVn@!#-<oCItXZ!hUZEW=uWY?NjYTx@iyyhHL=`=9{j3hIW;g^ zx4TgI<9Q>eIf5=Y3#N({m3MUp!K*eu-Gu5hKj^Z9x<6IKBRR{z6|`XrDgG39Fg+!Z z)5sS-x_>qVeB4NQe&btY1SLx>s#p`CjaCusI4C{~ilp%G-z_=g16-~VV0)2^e6#-+ zmgG!!LIy*V*#4XSfQtKQz$5r(qNDbb04%L$Av* zo62g+&y`!TfaqN44Je}n*ai}VpvSvUVCGa{yONx0{pHymbtkvhQZUK8U9l46TWi3*-v#HlF=bG>`u%P7Jy{IAT&w|D@9W_U*a zI}zuiY*Q=VcMR-{zTB>u;(XD;Tp)Ke#L&5qHFpwmf&KWX^Z={`0*i{K;YDp%z?A;D z5Fo>gW}FvZEad69;k*Y)`rwQ+_Ll3{B`&2@3=Ys%AE2U6CuHUZkK+V+>?M+?*qb~U zw4%wxv&@|r!N&oHIj=Wfs=3!H;#MBxgkrJ&1hL*K9ZMI86h}IZi*y=}{{7WAZ8sA* zP+w_C9#SPi({y|c3z;QcvDQtq=@jUz!k&VsZD3AFg4kLhub*FfEhAR@)|@XFoi4|D z2Zp%zp6uR`>o**@5QipsGS<#oz%N4b*RU^%36pJIO$>NC*rxV~^?9c+*YCRLT5lUT zgu|YK-S-nJi6)GXCjJG0Y6!IL2#dvM^O8mi#Ee~Nl3(^VIr@e1Np2sop`Far0H=^ z4XK1vFo&uQ;sLu<#2xT|Bj5#9<(P$0v5bHC%5TiX`KpdWQ@0DQmr-$pb-6I~XC=Sj z+c3|;p?<5(JpQ_DElYtFlzY+rU2G88wT76~`^nV0KCh)+)4SV^IVWGB&Zir39+3<} zJ6cWf$0EWI0ZV0>xcpniSvz~eG^rZ3o-=!6F!9uRAMIiMA2j_t2+*yqI{;~Kb7T%C zx$v+c`iL6K9%Z;AMQWK{8(TEmdx7~;l5gD?LG2|Wsxr&E@!Q&!tc4hV5aFA`L5HfYJ2P1|I(NOvJ*Snh0t|ucc!BjdR+^3NQLcEt2UWt<#bYl~*|5OydZlV47Gx@?=r&>Ctg8BU) zoFQOr27eulSP`Z{i(xa8RsiDw!>wHhw(lK@APSUwti>0rohtRpDqwvo@Gp|yrK_N_ ziR3Y-B#wk%H2C2@2%juW#KdIQKFMkSNb!sR=Ev_|dCDZFeqD+(z{=4rB|67U$OU_8 z=t$8HL34E+_|#j3fMrB#i0HfHIFJU#J5-H3%E!Or*SH~neu)%6BQCp@a&kXQ=8Qt) z>br#wl}el2t`3Y0)qleqHI%!C;_u)YID^l@wkaTf!h^^r`MK6b2PfC@i1DX=koT^X zeDRyHk=^4~`8n|0w{_CWn$Pzy1}TGouE`x9SrD2AS5?QRE(pG;rS^DB!h$RUZbgGZ z(d9o`0e|QxHSu)Ny1{(%foViCATZxCHre!@AE_GHA;iNn;=ahv#FH#2zu5Y4y>r^XZUf?c@EXVD&Ctx_erF==V^}8gNW)dx!GXCYHxZGnMH!b93gev-LeasO z=I)r88Zc%=z&Gh?KS=MJtxA<7kG{};Iu2rAxr5;J179Swp!Cyh++{z{gZ34diw?fl z-V!n{5r0Q$g>kFa?-spyY2-}AG1apENCmHb=}S`vr^`$6oq;r8_lCriP|KlXE+o3f z&GzCUFArqu=n+T0Bn7?%q)2!{Oi0~5f_wVYbe)0h%1K5N{e96HQWvbY(vUYaL9RGt zxk!q*Qs+ndZr(vXfwJb6f*X2A+jzEdIkB5Qi6bN}ub9fv{T2SVmXAG;vH{95y_goB z`H$QDGbR$`s(Y_MBptTj4ujBAP#N2z2|{X>_hE}?X6?#_CcN`$*P*rSV{*qI+HmO*P){t z0MKH))~;B03~C07zVky`Hvhvvu@`y)aP)Pw_GJvIAmio3*=c1m3xAU@@bAbq;g9pp z-Un1JG??vuu@Q_RE<1PJAZ|VS=;hPFrQ=E3Hzq#q`ZqH3mINXTy_W+0`Yfs+1?7DH z=MVT(eJM-)UgyM?kB=jhA;k1qve zP-A0y-FFGuU(v}coJ1oTbmY3HR}S@11R)nVV8vI*n|q9nI+!5ebS2pz*AS-9$b=pg zriTsict)@lfvgL2lV<3OA^oUES+pOeqHFA6Oc!bW=sMW+il)uVmz9Q>g?jryIs$ta z;uCAqLLDij!Hc?FBtjf@v~@_EJvt#Lnn$wd(h%)CmPUV)NpvU>UCk`2Qp4m#6ny=I zI43NE0kogLw^}SN85CKS?HZEK!uw9r??=xM>g=y8@VC6PdqS(wgd~S$7OcxoxG?kC zI}rTpZaiv((jVxlHASUKFJ|M>Tah~6#`d|Z2iFS~uvx;)k Date: Sun, 28 Jan 2024 16:53:10 -0600 Subject: [PATCH 02/56] Update css --- doc/source/_static/{ => css}/custom.css | 0 doc/source/_static/css/highlight.css | 83 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) rename doc/source/_static/{ => css}/custom.css (100%) create mode 100644 doc/source/_static/css/highlight.css diff --git a/doc/source/_static/custom.css b/doc/source/_static/css/custom.css similarity index 100% rename from doc/source/_static/custom.css rename to doc/source/_static/css/custom.css diff --git a/doc/source/_static/css/highlight.css b/doc/source/_static/css/highlight.css new file mode 100644 index 00000000000..f2ca46252fc --- /dev/null +++ b/doc/source/_static/css/highlight.css @@ -0,0 +1,83 @@ +/* + * The 'friendly' style from Pygments CSS style. Directly taken from: + * https://github.com/richleland/pygments-css + * License is 'UNLICENSE.txt'. + */ + +@import "../../ansys-sphinx-theme.css"; + +/* Do not show number cells */ +.nbinput .prompt, +.nboutput .prompt { + display: none; +} + +.highlight .hll { background-color: #ffffcc !important; } +.highlight { font-size: 1rem; background: #f0f0f0 !important; } +.highlight .c { color: #60a0b0 !important; font-style: italic !important; } /* Comment */ +.highlight .err { border: 1px solid #FF0000 !important; } /* Error */ +.highlight .k { color: #007020 !important; font-weight: bold !important; } /* Keyword */ +.highlight .o { color: #666666 !important; } /* Operator */ +.highlight .ch { color: #60a0b0 !important; font-style: italic !important; } /* Comment.Hashbang */ +.highlight .cm { color: #60a0b0 !important; font-style: italic !important; } /* Comment.Multiline */ +.highlight .cp { color: #007020 !important; } /* Comment.Preproc */ +.highlight .cpf { color: #60a0b0 !important; font-style: italic !important; } /* Comment.PreprocFile */ +.highlight .c1 { color: #60a0b0 !important; font-style: italic !important; } /* Comment.Single */ +.highlight .cs { color: #60a0b0 !important; background-color: #fff0f0 !important; } /* Comment.Special */ +.highlight .gd { color: #A00000 !important; } /* Generic.Deleted */ +.highlight .ge { font-style: italic !important; } /* Generic.Emph */ +.highlight .gr { color: #FF0000 !important; } /* Generic.Error */ +.highlight .gh { color: #000080 !important; font-weight: bold !important; } /* Generic.Heading */ +.highlight .gi { color: #00A000 !important; } /* Generic.Inserted */ +.highlight .go { color: #888888 !important; } /* Generic.Output */ +.highlight .gp { color: #c65d09 !important; font-weight: bold !important; } /* Generic.Prompt */ +.highlight .gs { font-weight: bold !important; } /* Generic.Strong */ +.highlight .gu { color: #800080 !important; font-weight: bold !important; } /* Generic.Subheading */ +.highlight .gt { color: #0044DD !important; } /* Generic.Traceback */ +.highlight .kc { color: #007020 !important; font-weight: bold !important; } /* Keyword.Constant */ +.highlight .kd { color: #007020 !important; font-weight: bold !important; } /* Keyword.Declaration */ +.highlight .kn { color: #007020 !important; font-weight: bold !important; } /* Keyword.Namespace */ +.highlight .kp { color: #007020 !important; } /* Keyword.Pseudo */ +.highlight .kr { color: #007020 !important; font-weight: bold !important; } /* Keyword.Reserved */ +.highlight .kt { color: #902000 !important; } /* Keyword.Type */ +.highlight .m { color: #40a070 !important; } /* Literal.Number */ +.highlight .s { color: #4070a0 !important; } /* Literal.String */ +.highlight .na { color: #4070a0 !important; } /* Name.Attribute */ +.highlight .nb { color: #007020 !important; } /* Name.Builtin */ +.highlight .nc { color: #0e84b5 !important; font-weight: bold !important; } /* Name.Class */ +.highlight .no { color: #60add5 !important; } /* Name.Constant */ +.highlight .nd { color: #555555 !important; font-weight: bold !important; } /* Name.Decorator */ +.highlight .ni { color: #d55537 !important; font-weight: bold !important; } /* Name.Entity */ +.highlight .ne { color: #007020 !important; } /* Name.Exception */ +.highlight .nf { color: #06287e !important; } /* Name.Function */ +.highlight .nl { color: #002070 !important; font-weight: bold !important; } /* Name.Label */ +.highlight .nn { color: #0e84b5 !important; font-weight: bold !important; } /* Name.Namespace */ +.highlight .nt { color: #062873 !important; font-weight: bold !important; } /* Name.Tag */ +.highlight .nv { color: #bb60d5 !important; } /* Name.Variable */ +.highlight .ow { color: #007020 !important; font-weight: bold !important; } /* Operator.Word */ +.highlight .w { color: #bbbbbb !important; } /* Text.Whitespace */ +.highlight .mb { color: #40a070 !important; } /* Literal.Number.Bin */ +.highlight .mf { color: #40a070 !important; } /* Literal.Number.Float */ +.highlight .mh { color: #40a070 !important; } /* Literal.Number.Hex */ +.highlight .mi { color: #40a070 !important; } /* Literal.Number.Integer */ +.highlight .mo { color: #40a070 !important; } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 !important; } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 !important; } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 !important; } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 !important; } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0 !important; font-style: italic !important; } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 !important; } /* Literal.String.Double */ +.highlight .se { color: #4070a0 !important; font-weight: bold !important; } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 !important; } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0 !important; font-style: italic !important; } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 !important; } /* Literal.String.Other */ +.highlight .sr { color: #235388 !important; } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 !important; } /* Literal.String.Single */ +.highlight .ss { color: #517918 !important; } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 !important; } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e !important; } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 !important; } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 !important; } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 !important; } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 !important; } /* Name.Variable.Magic */ +.highlight .il { color: #40a070 !important; } /* Literal.Number.Integer.Long */ From a9a8f303c12b1544775ee18ae44c1b0208305653 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 28 Jan 2024 17:22:36 -0600 Subject: [PATCH 03/56] Update Example directories Move readme.txt to index.rst --- examples/00-EDB/Readme.txt | 5 ----- .../01-HFSS3DLayout/{Readme.txt => index.rst} | 7 +++++++ .../{Readme.txt => index.rst} | 7 +++++++ examples/02-HFSS/Readme.txt | 4 ---- examples/02-HFSS/index.rst | 17 ++++++++++++++++ examples/02-SBR+/{Readme.txt => index.rst} | 7 +++++++ examples/03-Maxwell/Readme.txt | 6 ------ examples/03-Maxwell/index.rst | 20 +++++++++++++++++++ examples/04-Icepak/{Readme.txt => index.rst} | 7 +++++++ examples/05-Q3D/{Readme.txt => index.rst} | 8 ++++++++ .../06-Multiphysics/{Readme.txt => index.rst} | 6 ++++++ examples/07-Circuit/Readme.txt | 5 ----- examples/07-Circuit/index.rst | 15 ++++++++++++++ examples/07-EMIT/{Readme.txt => index.rst} | 7 +++++++ .../07-TwinBuilder/{Readme.txt => index.rst} | 7 +++++++ 15 files changed, 108 insertions(+), 20 deletions(-) delete mode 100644 examples/00-EDB/Readme.txt rename examples/01-HFSS3DLayout/{Readme.txt => index.rst} (64%) rename examples/01-Modeling-Setup/{Readme.txt => index.rst} (58%) delete mode 100644 examples/02-HFSS/Readme.txt create mode 100644 examples/02-HFSS/index.rst rename examples/02-SBR+/{Readme.txt => index.rst} (61%) delete mode 100644 examples/03-Maxwell/Readme.txt create mode 100644 examples/03-Maxwell/index.rst rename examples/04-Icepak/{Readme.txt => index.rst} (53%) rename examples/05-Q3D/{Readme.txt => index.rst} (61%) rename examples/06-Multiphysics/{Readme.txt => index.rst} (74%) delete mode 100644 examples/07-Circuit/Readme.txt create mode 100644 examples/07-Circuit/index.rst rename examples/07-EMIT/{Readme.txt => index.rst} (52%) rename examples/07-TwinBuilder/{Readme.txt => index.rst} (51%) diff --git a/examples/00-EDB/Readme.txt b/examples/00-EDB/Readme.txt deleted file mode 100644 index c7104afa4d1..00000000000 --- a/examples/00-EDB/Readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -EDB examples -~~~~~~~~~~~~ -These examples use EDB (Electronics Database) with PyAEDT. -EDB is a powerful API that allows to control PCB data efficently. -You can either use EDB standalone or embedded in HFSS 3D Layout in AEDT. diff --git a/examples/01-HFSS3DLayout/Readme.txt b/examples/01-HFSS3DLayout/index.rst similarity index 64% rename from examples/01-HFSS3DLayout/Readme.txt rename to examples/01-HFSS3DLayout/index.rst index daf9b1637e8..5c981532638 100644 --- a/examples/01-HFSS3DLayout/Readme.txt +++ b/examples/01-HFSS3DLayout/index.rst @@ -2,3 +2,10 @@ HFSS 3D Layout examples ~~~~~~~~~~~~~~~~~~~~~~~ These examples use PyAEDT to show some end-to-end workflows for HFSS 3D Layout. It includes model generation, setup, meshing, and post-processing. + +.. nbgallery:: + + Dcr_in_3DLayout.py + EDB_in_3DLayout.py + Hfss3DComponent.py + HFSS3DLayout_Via.py \ No newline at end of file diff --git a/examples/01-Modeling-Setup/Readme.txt b/examples/01-Modeling-Setup/index.rst similarity index 58% rename from examples/01-Modeling-Setup/Readme.txt rename to examples/01-Modeling-Setup/index.rst index d9417436357..d157b53d5b6 100644 --- a/examples/01-Modeling-Setup/Readme.txt +++ b/examples/01-Modeling-Setup/index.rst @@ -2,3 +2,10 @@ General model and setup examples ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These examples use PyAEDT to show some general model and simulation setup features inside AEDT. + +.. nbgallery:: + + Configurations.py + HFSS_CoordinateSystem.py + Optimetrics.py + Polyline_Primitives.py \ No newline at end of file diff --git a/examples/02-HFSS/Readme.txt b/examples/02-HFSS/Readme.txt deleted file mode 100644 index 9f03c21f896..00000000000 --- a/examples/02-HFSS/Readme.txt +++ /dev/null @@ -1,4 +0,0 @@ -HFSS examples -~~~~~~~~~~~~~ -These examples use PyAEDT to show some end-to-end workflows for HFSS 3D. -This includes model generation, setup, meshing, and postprocessing. diff --git a/examples/02-HFSS/index.rst b/examples/02-HFSS/index.rst new file mode 100644 index 00000000000..52bc6d9a8a8 --- /dev/null +++ b/examples/02-HFSS/index.rst @@ -0,0 +1,17 @@ +HFSS examples +~~~~~~~~~~~~~ +These examples use PyAEDT to show some end-to-end workflows for HFSS 3D. +This includes model generation, setup, meshing, and postprocessing. + +.. nbgallery:: + + Array.py + Create_3d_Component_and_use_it.py + Flex_CPWG.py + HFSS_Choke.py + HFSS_Dipole.py + HFSS_eigenmode.py + HFSS_FSS_unitcell.py + HFSS_Spiral.py + Probe_Fed_Patch.py + Waveguide_Filter.py \ No newline at end of file diff --git a/examples/02-SBR+/Readme.txt b/examples/02-SBR+/index.rst similarity index 61% rename from examples/02-SBR+/Readme.txt rename to examples/02-SBR+/index.rst index edb3b62f744..011c8582ce8 100644 --- a/examples/02-SBR+/Readme.txt +++ b/examples/02-SBR+/index.rst @@ -2,3 +2,10 @@ SBR+ examples ~~~~~~~~~~~~~ These examples use PyAEDT to show some end-to-end workflows for HFSS SBR+. This includes model generation, setup, meshing, and postprocessing. + +.. nbgallery:: + + SBR_City_Import.py + SBR_Doppler_Example.py + SBR_Example.py + SBR_Time_Plot.py \ No newline at end of file diff --git a/examples/03-Maxwell/Readme.txt b/examples/03-Maxwell/Readme.txt deleted file mode 100644 index 6eb8131b407..00000000000 --- a/examples/03-Maxwell/Readme.txt +++ /dev/null @@ -1,6 +0,0 @@ -Maxwell examples -~~~~~~~~~~~~~~~~ -These examples use PyAEDT to show some end-to-end workflows for Maxwell 2D and -Maxwell 3D. This includes model generation, setup, meshing, and postprocessing. -Examples cover different Maxwell solution types (Eddy Current, Magnetostatic, -and Transient). diff --git a/examples/03-Maxwell/index.rst b/examples/03-Maxwell/index.rst new file mode 100644 index 00000000000..3c3f7263280 --- /dev/null +++ b/examples/03-Maxwell/index.rst @@ -0,0 +1,20 @@ +Maxwell examples +~~~~~~~~~~~~~~~~ +These examples use PyAEDT to show some end-to-end workflows for Maxwell 2D and +Maxwell 3D. This includes model generation, setup, meshing, and postprocessing. +Examples cover different Maxwell solution types (Eddy Current, Magnetostatic, +and Transient). + +.. nbgallery:: + + Maxwell2D_DCConduction.py + Maxwell2D_Electrostatic.py + Maxwell2D_NissanLeaf.py + Maxwell2D_Transient.py + Maxwell3DTeam7.py + Maxwell3D_Choke.py + Maxwell3D_Segmentation.py + Maxwell3D_Team3_bath_plate.py + Maxwell_Control_Program.py + Maxwell_Magnet.py + Maxwell_Transformer_Coreloss.py \ No newline at end of file diff --git a/examples/04-Icepak/Readme.txt b/examples/04-Icepak/index.rst similarity index 53% rename from examples/04-Icepak/Readme.txt rename to examples/04-Icepak/index.rst index f7bc280fad1..8cffba8a873 100644 --- a/examples/04-Icepak/Readme.txt +++ b/examples/04-Icepak/index.rst @@ -3,3 +3,10 @@ Icepak examples These examples use PyAEDT to show end-to-end workflows for Icepak. This includes schematic generation, setup, and thermal postprocessing. +.. nbgallery:: + + Icepak_3DComponents_Example.py + Icepak_CSV_Import.py + Icepak_ECAD_Import.py + Icepak_Example.py + Sherlock_Example.py diff --git a/examples/05-Q3D/Readme.txt b/examples/05-Q3D/index.rst similarity index 61% rename from examples/05-Q3D/Readme.txt rename to examples/05-Q3D/index.rst index 1d4f3398872..d49937f7529 100644 --- a/examples/05-Q3D/Readme.txt +++ b/examples/05-Q3D/index.rst @@ -3,3 +3,11 @@ These examples use PyAEDT to show some end-to-end workflows for 2D Extractor and Q3D Extractor. This includes model generation, setup, and thermal postprocessing. +.. nbgallery:: + + Q2D_Armoured_Cable.py + Q2D_Example_CPWG.py + Q2D_Example_Stripline.py + Q3D_DC_IR.py + Q3D_Example.py + Q3D_from_EDB.py \ No newline at end of file diff --git a/examples/06-Multiphysics/Readme.txt b/examples/06-Multiphysics/index.rst similarity index 74% rename from examples/06-Multiphysics/Readme.txt rename to examples/06-Multiphysics/index.rst index d35019b7dbd..3feeb59f7f0 100644 --- a/examples/06-Multiphysics/Readme.txt +++ b/examples/06-Multiphysics/index.rst @@ -3,3 +3,9 @@ Multiphysics examples These examples use PyAEDT to create some multiphysics workflows. They might use an electromagnetic tool like HFSS or Maxwell and a thermal or structural tool like Icepak or Mechanical. + +.. nbgallery:: + + Hfss_Icepak_Coupling.py + Hfss_Mechanical.py + MRI.py \ No newline at end of file diff --git a/examples/07-Circuit/Readme.txt b/examples/07-Circuit/Readme.txt deleted file mode 100644 index 85222d203e1..00000000000 --- a/examples/07-Circuit/Readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -Circuit examples -~~~~~~~~~~~~~~~~ -These examples use PyAEDT to show some end-to-end workflows for Circuit. -This includes schematic generation, setup, and postprocessing. - diff --git a/examples/07-Circuit/index.rst b/examples/07-Circuit/index.rst new file mode 100644 index 00000000000..5cedc20bae8 --- /dev/null +++ b/examples/07-Circuit/index.rst @@ -0,0 +1,15 @@ +Circuit examples +~~~~~~~~~~~~~~~~ +These examples use PyAEDT to show some end-to-end workflows for Circuit. +This includes schematic generation, setup, and postprocessing. + +.. nbgallery:: + + Circuit_AMI.py + Circuit_Example.py + Circuit_Siwave_Multizones.py + Circuit_Subcircuit_Example.py + Circuit_Transient.py + Create_Netlist.py + Reports.py + Touchstone_Management.py \ No newline at end of file diff --git a/examples/07-EMIT/Readme.txt b/examples/07-EMIT/index.rst similarity index 52% rename from examples/07-EMIT/Readme.txt rename to examples/07-EMIT/index.rst index 35b36f1cd11..0453065f357 100644 --- a/examples/07-EMIT/Readme.txt +++ b/examples/07-EMIT/index.rst @@ -3,3 +3,10 @@ EMIT examples These examples use PyAEDT to show some end-to-end workflows for EMIT. This includes schematic generation, setup, and postprocessing. +.. nbgallery:: + + ComputeInterferenceType.py + ComputeProtectionLevels.py + EMIT_Example.py + EMIT_HFSS_Example.py + interference_gui.py \ No newline at end of file diff --git a/examples/07-TwinBuilder/Readme.txt b/examples/07-TwinBuilder/index.rst similarity index 51% rename from examples/07-TwinBuilder/Readme.txt rename to examples/07-TwinBuilder/index.rst index df697376902..324d39b88cd 100644 --- a/examples/07-TwinBuilder/Readme.txt +++ b/examples/07-TwinBuilder/index.rst @@ -2,3 +2,10 @@ Twin Builder examples ~~~~~~~~~~~~~~~~~~~~~ These examples use PyAEDT to show some end-to-end workflows for Twin Builder. This includes schematic generation, setup, and postprocessing. + +.. nbgallery:: + + 01-RC_Circuit_Example.py + 02-Wiring_A_Rectifier.py + 03-Dynamic_ROM_Creation_And_Visualization.py + 04-Static_ROM_Creation_And_Visualization.py From d5afc27441c6f64525e6ddef40c798835ae14c07 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 28 Jan 2024 17:32:35 -0600 Subject: [PATCH 04/56] Update Example directories Move readme.txt to index.rst --- pyproject.toml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6bdff74f8c6..2eb44c921c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,10 +63,12 @@ tests = [ "scikit-learn==1.3.1; python_version > '3.7'", "SRTM.py", "utm", - "scikit-rf==0.31.0", + "scikit-rf", + "jsonschema", ] doc = [ - "ansys-sphinx-theme==0.13.1", + "ansys-sphinx-theme==0.12.2", + "pydata-sphinx-theme==0.14.4", "imageio==2.31.5", "imageio-ffmpeg==0.4.9", "ipython==8.13.0; python_version < '3.9'", @@ -78,6 +80,7 @@ doc = [ "matplotlib==3.7.3; python_version == '3.8'", "matplotlib==3.8.0; python_version > '3.8'", "nbsphinx==0.9.3", + "jupytext==1.16.0", "numpydoc==1.5.0; python_version == '3.7'", "numpydoc==1.6.0; python_version > '3.7'", "osmnx", @@ -94,7 +97,6 @@ doc = [ "sphinx-autobuild==2021.3.14", "sphinx-autodoc-typehints==1.24.0", "sphinx-copybutton==0.5.2", - "sphinx-gallery==0.14.0", "sphinx-notfound-page==1.0.0", "sphinxcontrib-websupport==1.2.4; python_version <= '3.9'", "sphinxcontrib-websupport==1.2.5; python_version <= '3.7'", @@ -212,4 +214,4 @@ exclude = [ '\.Modeler3D\.create_choke$', # bad RT05 '\._unittest', # missing docstring for tests 'HistoryProps.', # bad RT05 because of the base class named OrderedDict -] +] \ No newline at end of file From 8da4fbe214d4a5e8d4280147190f22d132690bc4 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Mon, 29 Jan 2024 13:51:45 +0100 Subject: [PATCH 05/56] DOC: fix EDB in 3D layout example Note: The current workflow to build documentation does not show something clear in the CI if an example contains an error. It only fails without much information. It would be nice to add a way to display the issue in the CI. --- examples/01-HFSS3DLayout/EDB_in_3DLayout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/01-HFSS3DLayout/EDB_in_3DLayout.py b/examples/01-HFSS3DLayout/EDB_in_3DLayout.py index 7e670a2e363..6b29e120a1c 100644 --- a/examples/01-HFSS3DLayout/EDB_in_3DLayout.py +++ b/examples/01-HFSS3DLayout/EDB_in_3DLayout.py @@ -44,7 +44,7 @@ if os.path.exists(aedt_file): os.remove(aedt_file) h3d = pyaedt.Hfss3dLayout(targetfile) -h3d.save_project(os.path.join(temp_folder, "edb_demo.aedt")) +h3d.save_project(os.path.join(project_folder, "edb_demo.aedt")) ############################################################################### # ## Print boundaries From 5b020b5e1a4ffb1746a5bf17a1771d92d85eb64b Mon Sep 17 00:00:00 2001 From: Devin Date: Mon, 29 Jan 2024 16:23:56 -0600 Subject: [PATCH 06/56] Add pandoc installation Add pandoc to build_documentation.yml --- .github/workflows/build_documentation.yml | 1 + .github/workflows/full_documentation.yml | 12 ++++++------ .gitignore | 3 +++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 5dfbb3d0d84..7376dd693e0 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -75,6 +75,7 @@ jobs: - name: Install doc build requirements run: | sudo apt install graphviz + sudo apt install -y pandoc # run doc build, without creating the examples directory # note that we have to add the examples file here since it won't diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index 779c3c83162..fd6b5d2850f 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -65,7 +65,7 @@ jobs: id: version run: | testenv\Scripts\Activate.ps1 - echo "PYAEDT_VERSION=$(python -c 'from pyaedt import __version__; print(__version__)')" >> $GITHUB_OUTPUT + echo "::set-output name=PYAEDT_VERSION::$(python -c "from pyaedt import __version__; print(__version__)")" echo "PyAEDT version is: $(python -c "from pyaedt import __version__; print(__version__)")" - name: Create HTML Documentations @@ -80,7 +80,7 @@ jobs: # .\doc\make.bat pdf - name: Upload HTML documentation artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: documentation-html path: doc/_build/html @@ -115,7 +115,7 @@ jobs: if: github.event_name == 'push' && contains(github.ref, 'refs/tags') steps: - name: Deploy the stable documentation - uses: ansys/actions/doc-deploy-stable@v4 + uses: ansys/actions/doc-deploy-stable@v5 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} @@ -136,7 +136,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 - name: Display structure of downloaded files run: ls -R @@ -154,7 +154,7 @@ jobs: echo "VERSION_MEILI=$VERSION_MEILI" >> $GITHUB_ENV - name: "Deploy the stable documentation index for PyAEDT API" - uses: ansys/actions/doc-deploy-index@v4 + uses: ansys/actions/doc-deploy-index@v5 with: cname: ${{ env.DOCUMENTATION_CNAME }}/version/${{ env.VERSION }} index-name: pyaedt-v${{ env.VERSION_MEILI }} @@ -163,7 +163,7 @@ jobs: pymeilisearchopts: --stop_urls \"EDBAPI\" # Add EDB API as another index. - name: "Deploy the stable documentation index for EDB API" - uses: ansys/actions/doc-deploy-index@v4 + uses: ansys/actions/doc-deploy-index@v5 with: cname: ${{ env.DOCUMENTATION_CNAME }}/version/${{ env.VERSION }}/EDBAPI/ index-name: pyedb-v${{ env.VERSION_MEILI }} diff --git a/.gitignore b/.gitignore index 32172648189..0af6f3e1d03 100644 --- a/.gitignore +++ b/.gitignore @@ -381,6 +381,9 @@ test-output.xml # Scratch Jupyter Notebooks scratch_notebooks/ +# Ipython notebook checkpoints +.ipynb_checkpoints + # Visual studio code local settings .vscode/ From 98a8a4b921d6349c920ba379986ae04dee246e42 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 09:59:41 +0100 Subject: [PATCH 07/56] FIX: doc build-finished doctree cleaning --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 8d5138903e7..e09ba8391ca 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -94,7 +94,7 @@ def remove_doctree(app, exception): """ size = directory_size(app.doctreedir) logger.info(f"Removing doctree {app.doctreedir} ({size} MB).") - shutil.rmtree(app.doctreedir) + shutil.rmtree(app.doctreedir, ignore_errors=True) logger.info(f"Doctree removed.") def copy_examples(app): From cdccd9b694c0edf3bf5b85a75831a4ef242502a9 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 11:19:09 +0100 Subject: [PATCH 08/56] FIX: revert action changes Note: some changes are conflicting with the current full documentation upload process (cf https://github.com/actions/upload-artifact/issues/485) Extra: removing setoutput as its deprecated by github --- .github/workflows/full_documentation.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index fd6b5d2850f..52336ea951c 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -65,7 +65,7 @@ jobs: id: version run: | testenv\Scripts\Activate.ps1 - echo "::set-output name=PYAEDT_VERSION::$(python -c "from pyaedt import __version__; print(__version__)")" + echo "PYAEDT_VERSION=$(python -c 'from pyaedt import __version__; print(__version__)')" >> $GITHUB_OUTPUT echo "PyAEDT version is: $(python -c "from pyaedt import __version__; print(__version__)")" - name: Create HTML Documentations @@ -80,7 +80,7 @@ jobs: # .\doc\make.bat pdf - name: Upload HTML documentation artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: documentation-html path: doc/_build/html @@ -94,7 +94,7 @@ jobs: retention-days: 7 # - name: Upload PDF documentation artifact -# uses: actions/upload-artifact@v4 +# uses: actions/upload-artifact@v3 # with: # name: documentation-pdf # path: doc/_build/pdf @@ -115,7 +115,7 @@ jobs: if: github.event_name == 'push' && contains(github.ref, 'refs/tags') steps: - name: Deploy the stable documentation - uses: ansys/actions/doc-deploy-stable@v5 + uses: ansys/actions/doc-deploy-stable@v4 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} @@ -136,7 +136,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 - name: Display structure of downloaded files run: ls -R @@ -154,7 +154,7 @@ jobs: echo "VERSION_MEILI=$VERSION_MEILI" >> $GITHUB_ENV - name: "Deploy the stable documentation index for PyAEDT API" - uses: ansys/actions/doc-deploy-index@v5 + uses: ansys/actions/doc-deploy-index@v4 with: cname: ${{ env.DOCUMENTATION_CNAME }}/version/${{ env.VERSION }} index-name: pyaedt-v${{ env.VERSION_MEILI }} @@ -163,7 +163,7 @@ jobs: pymeilisearchopts: --stop_urls \"EDBAPI\" # Add EDB API as another index. - name: "Deploy the stable documentation index for EDB API" - uses: ansys/actions/doc-deploy-index@v5 + uses: ansys/actions/doc-deploy-index@v4 with: cname: ${{ env.DOCUMENTATION_CNAME }}/version/${{ env.VERSION }}/EDBAPI/ index-name: pyedb-v${{ env.VERSION_MEILI }} From 9ea63df3cdb44a421102ebeec6ec17cd03584c42 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 11:20:57 +0100 Subject: [PATCH 09/56] MISC: temporary remove actions to focus on fulldoc --- .github/workflows/cpython_linux.yml | 91 ----------- .github/workflows/ironpython.yml | 40 ----- .github/workflows/nightly-docs.yml | 133 ---------------- .github/workflows/unit_test_prerelease.yml | 89 ----------- .github/workflows/unit_tests.yml | 172 --------------------- .github/workflows/unit_tests_solvers.bkp | 103 ------------ .github/workflows/wheelhouse.yml | 90 ----------- .github/workflows/wheelhouse_linux.yml | 89 ----------- 8 files changed, 807 deletions(-) delete mode 100644 .github/workflows/cpython_linux.yml delete mode 100644 .github/workflows/ironpython.yml delete mode 100644 .github/workflows/nightly-docs.yml delete mode 100644 .github/workflows/unit_test_prerelease.yml delete mode 100644 .github/workflows/unit_tests.yml delete mode 100644 .github/workflows/unit_tests_solvers.bkp delete mode 100644 .github/workflows/wheelhouse.yml delete mode 100644 .github/workflows/wheelhouse_linux.yml diff --git a/.github/workflows/cpython_linux.yml b/.github/workflows/cpython_linux.yml deleted file mode 100644 index 4275fa0134b..00000000000 --- a/.github/workflows/cpython_linux.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Linux_CPython_UnitTests - -env: - python.version: '3.10' - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it haven't been deleted already. - # It applies 7 days retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT - - -on: - workflow_dispatch: - inputs: - logLevel: - description: 'Log level' - required: true - default: 'warning' - tags: - description: 'Linux CPython daily' - schedule: # UTC at 0100 - - cron: '0 1 * * *' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - runs-on: [Linux, pyaedt] - strategy: - matrix: - python-version: [ '3.10' ] - steps: - - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - architecture: 'x86' - - - name: 'Install pyaedt' - run: | - python -m venv .pyaedt_test_env - export ANSYSEM_ROOT232=/apps/AnsysEM/v232/Linux64 - export LD_LIBRARY_PATH=$ANSYSEM_ROOT232/common/mono/Linux64/lib64:$LD_LIBRARY_PATH - source .pyaedt_test_env/bin/activate - python -m pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org pip -U - python -m pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org wheel setuptools -U - python -c "import sys; print(sys.executable)" - pip install .[tests] - pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org pytest-azurepipelines - python -c "import pyaedt; print('Imported pyaedt')" - - - name: 'Unit testing' - uses: nick-fields/retry@v2 - with: - max_attempts: 3 - retry_on: error - timeout_minutes: 60 - command: | - export ANS_NODEPCHECK=1 - export ANSYSEM_ROOT232=/apps/AnsysEM/v232/Linux64 - export LD_LIBRARY_PATH=$ANSYSEM_ROOT232/common/mono/Linux64/lib64:$LD_LIBRARY_PATH - source .pyaedt_test_env/bin/activate - pytest --tx 6*popen --durations=50 --dist loadfile -v _unittest - - - name: 'Unit testing Solvers' - continue-on-error: true - uses: nick-fields/retry@v2 - with: - max_attempts: 3 - retry_on: error - timeout_minutes: 60 - command: | - export ANS_NODEPCHECK=1 - export ANSYSEM_ROOT232=/apps/AnsysEM/v232/Linux64 - export LD_LIBRARY_PATH=$ANSYSEM_ROOT232/common/mono/Linux64/lib64:$LD_LIBRARY_PATH - source .pyaedt_test_env/bin/activate - pytest --tx 2*popen --durations=50 --dist loadfile -v _unittest_solvers - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-results - path: junit/test-results.xml - if: ${{ always() }} diff --git a/.github/workflows/ironpython.yml b/.github/workflows/ironpython.yml deleted file mode 100644 index b169c44d1c3..00000000000 --- a/.github/workflows/ironpython.yml +++ /dev/null @@ -1,40 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI_Ironpython - - -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - pull_request: - branches: [ main ] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - - name: 'Run Unit Tests in Ironpython' - timeout-minutes: 60 - run: | - $processA = start-process 'cmd' -ArgumentList '/c .\_unittest_ironpython\run_unittests_batchmode.cmd' -PassThru - $processA.WaitForExit() - get-content .\_unittest_ironpython\pyaedt_unit_test_ironpython.log - $test_errors_failures = Select-String -Path .\_unittest_ironpython\pyaedt_unit_test_ironpython.log -Pattern "TextTestResult errors=" - if ($test_errors_failures -ne $null) - { - exit 1 - } - else - { - exit 0 - } diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml deleted file mode 100644 index bebff44a875..00000000000 --- a/.github/workflows/nightly-docs.yml +++ /dev/null @@ -1,133 +0,0 @@ -name: Nightly Documentation Build - -on: - workflow_dispatch: - schedule: # UTC at 0400 - - cron: '0 4 * * *' - -env: - DOCUMENTATION_CNAME: 'aedt.docs.pyansys.com' - MEILISEARCH_API_KEY: ${{ secrets.MEILISEARCH_API_KEY }} - MEILISEARCH_PUBLIC_API_KEY: ${{ secrets.MEILISEARCH_PUBLIC_API_KEY }} - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - docs_build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Install pyaedt - run: | - pip install . - - - name: Install doc build requirements - run: | - pip install .[doc] - - - name: Full Documentation Build - run: | - make -C doc phtml - - - name: Upload documentation HTML artifact - uses: actions/upload-artifact@v4 - with: - name: documentation-html - path: doc/_build/html - retention-days: 7 - - - name: Upload HTML documentation artifact - uses: actions/upload-artifact@v4 - with: - name: documentation-html-edb - path: doc/_build/html/EDBAPI - retention-days: 7 - - docs_upload: - needs: docs_build - runs-on: ubuntu-latest - steps: - - - name: Deploy development documentation - uses: ansys/actions/doc-deploy-dev@v4 - with: - cname: ${{ env.DOCUMENTATION_CNAME }} - token: ${{ secrets.GITHUB_TOKEN }} - - doc-index-dev: - name: "Deploy dev docs index" - runs-on: ubuntu-latest - needs: docs_upload - steps: - - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v3 - - - name: Display structure of downloaded files - run: ls -R - - - name: "Deploy the dev documentation index for PyAEDT API" - uses: ansys/actions/doc-deploy-index@v4 - with: - cname: ${{ env.DOCUMENTATION_CNAME }}/version/dev - index-name: pyaedt-vdev - host-url: ${{ vars.MEILISEARCH_HOST_URL }} - api-key: ${{ env.MEILISEARCH_API_KEY }} - pymeilisearchopts: --stop_urls \"EDBAPI\" # Add EDB API as another index to show it in dropdown button - - - name: "Deploy the dev documentation index for EDB API" - uses: ansys/actions/doc-deploy-index@v4 - with: - cname: ${{ env.DOCUMENTATION_CNAME }}/version/dev/EDBAPI/ - index-name: pyedb-vdev - host-url: ${{ vars.MEILISEARCH_HOST_URL }} - api-key: ${{ env.MEILISEARCH_API_KEY }} - doc-artifact-name: documentation-html-edb # Add only EDB API as page in this index. - pymeilisearchopts: --port 8001 # serve in another port as 8000 is deafult - - # docstring_testing: - # runs-on: Windows - - # steps: - # - uses: actions/checkout@v4 - - # - name: Setup Python - # uses: actions/setup-python@v2 - # with: - # python-version: 3.8 - - # - name: 'Create virtual env' - # run: | - # python -m venv testenv - # testenv\Scripts\Activate.ps1 - # python -m pip install pip -U - # python -m pip install wheel setuptools -U - # python -c "import sys; print(sys.executable)" - - # - name: 'Install pyaedt' - # run: | - # testenv\Scripts\Activate.ps1 - # pip install . --use-feature=in-tree-build - # cd _unittest - # python -c "import pyaedt; print('Imported pyaedt')" - - # - name: Install testing requirements - # run: | - # testenv\Scripts\Activate.ps1 - # pip install -r requirements/requirements_test.txt - # pip install pytest-azurepipelines - - # - name: Docstring testing - # run: | - # testenv\Scripts\Activate.ps1 - # pytest -v pyaedt/desktop.py pyaedt/icepak.py - # pytest -v pyaedt/desktop.py pyaedt/hfss.py diff --git a/.github/workflows/unit_test_prerelease.yml b/.github/workflows/unit_test_prerelease.yml deleted file mode 100644 index 272a6ec491c..00000000000 --- a/.github/workflows/unit_test_prerelease.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: CI_PreRelease - -env: - python.version: '3.8' - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it haven't been deleted already. - # It applies 7 days retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT -# Controls when the workflow will run -on: - workflow_dispatch: - inputs: - logLevel: - description: 'Log level' - required: true - default: 'warning' - tags: - description: 'Linux CPython daily' - schedule: # UTC at 0300 - - cron: '0 3 * * *' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: [pre_release] - strategy: - matrix: - python-version: ['3.8'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - python -m venv testenv - testenv\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - - - name: 'Install pyaedt' - run: | - testenv\Scripts\Activate.ps1 - pip install .[tests] - pip install pytest-azurepipelines - Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv\Lib\site-packages\vtkmodules" -Force - Copy-Item -Path "C:\actions-runner\local_config.json" -Destination "_unittest" -Force - mkdir tmp - cd tmp - python -c "import pyaedt; print('Imported pyaedt')" - - # - name: "Check licences of packages" - # uses: pyansys/pydpf-actions/check-licenses@v2.0 - - - name: 'Unit testing' - timeout-minutes: 60 - run: | - testenv\Scripts\Activate.ps1 - Set-Item -Path env:PYTHONMALLOC -Value "malloc" - pytest --tx 6*popen --durations=50 --dist loadfile -v --cov=pyaedt --cov-report=xml --junitxml=junit/test-results.xml --cov-report=html _unittest - - - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - if: matrix.python-version == '3.8' - name: 'Upload coverage to Codecov' - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-results - path: junit/test-results.xml - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml deleted file mode 100644 index cdefc1e64d6..00000000000 --- a/.github/workflows/unit_tests.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: CI - -env: - python.version: '3.10' - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it hasn't been deleted already. - # It applies 7 days retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - tags: - - 'v*' - branches: - - main - pull_request: - branches: [ main ] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build_solvers: - # The type of runner that the job will run on - runs-on: [ windows-latest, pyaedt ] - strategy: - matrix: - python-version: [ '3.10' ] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - Remove-Item D:\Temp\* -Recurse -Force -ErrorAction SilentlyContinue - python -m venv testenv_s - testenv_s\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - - - name: 'Install pyaedt' - run: | - testenv_s\Scripts\Activate.ps1 - pip install . - pip install .[tests] - pip install pytest-azurepipelines - Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv_s\Lib\site-packages\vtkmodules" -Force - mkdir tmp - cd tmp - python -c "import pyaedt; print('Imported pyaedt')" - - # - name: "Check licences of packages" - # uses: pyansys/pydpf-actions/check-licenses@v2.0 - - - name: 'Unit testing' - uses: nick-fields/retry@v2 - with: - max_attempts: 3 - retry_on: error - timeout_minutes: 40 - command: | - testenv_s\Scripts\Activate.ps1 - Set-Item -Path env:PYTHONMALLOC -Value "malloc" - pytest --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest_solvers - - - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - name: 'Upload coverage to Codecov' - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-solver-results - path: junit/test-results.xml - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - - - build: - # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] - strategy: - matrix: - python-version: ['3.10'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - Remove-Item D:\Temp\* -Recurse -Force -ErrorAction SilentlyContinue - python -m venv testenv - testenv\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - - - name: 'Install pyaedt' - run: | - testenv\Scripts\Activate.ps1 - pip install . - pip install .[tests] - pip install pytest-azurepipelines - Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv\Lib\site-packages\vtkmodules" -Force - mkdir tmp - cd tmp - python -c "import pyaedt; print('Imported pyaedt')" - - # - name: "Check licences of packages" - # uses: pyansys/pydpf-actions/check-licenses@v2.0 - - - name: 'Unit testing' - uses: nick-fields/retry@v2 - with: - max_attempts: 3 - retry_on: error - timeout_minutes: 50 - command: | - testenv\Scripts\Activate.ps1 - Set-Item -Path env:PYTHONMALLOC -Value "malloc" - pytest -n 6 --dist loadfile --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest - - - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - name: 'Upload coverage to Codecov' - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-results - path: junit/test-results.xml - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - - - name: 'Build and validate source distribution' - run: | - testenv\Scripts\Activate.ps1 - python -m pip install build twine - python -m build - python -m twine check dist/* - - - name: "Builds and uploads to PyPI" - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - run: | - testenv\Scripts\Activate.ps1 - python setup.py sdist - python -m pip install twine - python -m twine upload --skip-existing dist/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/unit_tests_solvers.bkp b/.github/workflows/unit_tests_solvers.bkp deleted file mode 100644 index 19080841594..00000000000 --- a/.github/workflows/unit_tests_solvers.bkp +++ /dev/null @@ -1,103 +0,0 @@ -name: CI_Solvers - -env: - python.version: '3.10' - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it hasn't been deleted already. - # It applies 7 days retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - tags: - - 'v*' - branches: - - main - pull_request: - branches: [ main ] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] - strategy: - matrix: - python-version: ['3.10'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - Remove-Item D:\Temp\* -Recurse -Force - python -m venv testenv_s - testenv_s\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - - - name: 'Install pyaedt' - run: | - testenv_s\Scripts\Activate.ps1 - pip install . - pip install .[tests] - pip install pytest-azurepipelines - Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv_s\Lib\site-packages\vtkmodules" -Force - mkdir tmp - cd tmp - python -c "import pyaedt; print('Imported pyaedt')" - - # - name: "Check licences of packages" - # uses: pyansys/pydpf-actions/check-licenses@v2.0 - - - name: 'Unit testing' - timeout-minutes: 40 - run: | - testenv_s\Scripts\Activate.ps1 - Set-Item -Path env:PYTHONMALLOC -Value "malloc" - pytest --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest_solvers - - - uses: codecov/codecov-action@v3 - if: matrix.python-version == '3.10' - name: 'Upload coverage to Codecov' - - - name: Upload pytest test results - uses: actions/upload-artifact@v3 - with: - name: pytest-results - path: junit/test-results.xml - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - - - name: 'Build and validate source distribution' - run: | - testenv_s\Scripts\Activate.ps1 - python -m pip install build twine - python -m build - python -m twine check dist/* - - - name: "Builds and uploads to PyPI" - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - run: | - testenv_s\Scripts\Activate.ps1 - python setup.py sdist - python -m pip install twine - python -m twine upload --skip-existing dist/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/wheelhouse.yml b/.github/workflows/wheelhouse.yml deleted file mode 100644 index 3b8e2631955..00000000000 --- a/.github/workflows/wheelhouse.yml +++ /dev/null @@ -1,90 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: WheelHouse - -env: - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it haven't been deleted already. - # It applies 7 days retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - tags: - - 'v*' - - v* - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: [windows-latest] - strategy: - matrix: - python-version: [ 3.7, 3.8, 3.9, '3.10'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - python -m venv testenv - testenv\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - pip install .[all] - pip install jupyterlab - - - - name: Retrieve PyAEDT version - run: | - testenv\Scripts\Activate.ps1 - echo "PYAEDT_VERSION=$(python -c 'from pyaedt import __version__; print(__version__)')" >> $GITHUB_OUTPUT - echo "PyAEDT version is: $(python -c "from pyaedt import __version__; print(__version__)")" - id: version - - - name: Generate wheelhouse - run: | - testenv\Scripts\Activate.ps1 - $packages=$(pip freeze) - # Iterate over the packages and generate wheels - foreach ($package in $packages) { - echo "Generating wheel for $package" - pip wheel "$package" -w wheelhouse - } - - - name: Zip wheelhouse - uses: vimtor/action-zip@v1 - with: - files: wheelhouse - dest: ${{ env.PACKAGE_NAME }}-v${{ steps.version.outputs.PYAEDT_VERSION }}-${{ runner.os }}-${{ matrix.python-version }}.zip - - - name: Upload Wheelhouse - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PACKAGE_NAME }}-v${{ steps.version.outputs.PYAEDT_VERSION }}-${{ runner.os }}-${{ matrix.python-version }} - path: '*.zip' - retention-days: 7 - - - name: Release - uses: softprops/action-gh-release@v1 - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - with: - generate_release_notes: true - files: | - ${{ env.PACKAGE_NAME }}-v${{ steps.version.outputs.PYAEDT_VERSION }}-${{ runner.os }}-${{ matrix.python-version }}.zip diff --git a/.github/workflows/wheelhouse_linux.yml b/.github/workflows/wheelhouse_linux.yml deleted file mode 100644 index c3d61262668..00000000000 --- a/.github/workflows/wheelhouse_linux.yml +++ /dev/null @@ -1,89 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: WheelHouse Linux - -env: - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number. If you go down (or repeat a previous value), - # you might end up reusing a previous cache if it hasn't been deleted already. - # It applies a 7-day retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - tags: - - 'v*' - - v* - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-20.04 - strategy: - matrix: - python-version: [ 3.7, 3.8, 3.9, '3.10'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install pyaedt - run: | - pip install .[all] - pip install jupyterlab - - - name: Verify pyaedt can be imported - run: python -c "import pyaedt" - - - name: Retrieve PyAEDT version - run: | - echo "PYAEDT_VERSION=$(python -c 'from pyaedt import __version__; print(__version__)')" >> $GITHUB_OUTPUT - echo "PyAEDT version is: $(python -c "from pyaedt import __version__; print(__version__)")" - id: version - - - name: Generate wheelhouse - run: | - pip install wheel setuptools -U - pip install --upgrade pip - pip wheel . -w wheelhouse - export wheellist=$(pip freeze) - for file in $wheellist; do - if [[ $file != *"@"* ]] && [[ $file != *"pyaedt"* ]]; then - pip wheel $file -w wheelhouse - fi - done - continue-on-error: true - - - name: Zip wheelhouse - uses: vimtor/action-zip@v1 - with: - files: wheelhouse - dest: ${{ env.PACKAGE_NAME }}-v${{ steps.version.outputs.PYAEDT_VERSION }}-wheelhouse-${{ runner.os }}-${{ matrix.python-version }}.zip - - - name: Upload Wheelhouse - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PACKAGE_NAME }}-v${{ steps.version.outputs.PYAEDT_VERSION }}-wheelhouse-${{ runner.os }}-${{ matrix.python-version }} - path: '*.zip' - retention-days: 7 - - - name: Release - uses: softprops/action-gh-release@v1 - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - with: - generate_release_notes: true - files: | - ${{ env.PACKAGE_NAME }}-v${{ steps.version.outputs.PYAEDT_VERSION }}-wheelhouse-${{ runner.os }}-${{ matrix.python-version }}.zip \ No newline at end of file From 7dc1b0cd858ecdfa2081dcaf3ab496200e6f9e23 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 11:22:28 +0100 Subject: [PATCH 10/56] MISC: temporary update workflow trigger --- .github/workflows/full_documentation.yml | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index 52336ea951c..1b80cce0813 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -10,19 +10,19 @@ env: MEILISEARCH_HOST_URL: https://backend.search.pyansys.com MEILISEARCH_PUBLIC_API_KEY: ${{ secrets.MEILISEARCH_PUBLIC_API_KEY }} # Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - tags: - - v* - workflow_dispatch: - inputs: - logLevel: - description: 'Log level' - required: true - default: 'warning' - tags: - description: 'Test scenario tags' +on: [pull_request, workflow_dispatch] + # # Triggers the workflow on push or pull request events but only for the main branch + # push: + # tags: + # - v* + # workflow_dispatch: + # inputs: + # logLevel: + # description: 'Log level' + # required: true + # default: 'warning' + # tags: + # description: 'Test scenario tags' concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 284f5a8676ccb9d09bb4430fb24019b1e4d67dcf Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 11:39:17 +0100 Subject: [PATCH 11/56] MISC: temporary removal to lighten doc --- doc/source/API/Application.rst | 69 ------- doc/source/API/Boundaries.rst | 36 ---- doc/source/API/CableModeling.rst | 132 ------------- doc/source/API/Configuration.rst | 52 ------ doc/source/API/Constants.rst | 21 --- doc/source/API/DesktopMessenger.rst | 13 -- doc/source/API/MaterialManagement.rst | 56 ------ doc/source/API/Mesh.rst | 31 ---- doc/source/API/MultiPartComponent.rst | 23 --- doc/source/API/Optimetrics.rst | 34 ---- doc/source/API/Post.rst | 112 ----------- doc/source/API/Primitive_Objects.rst | 143 -------------- doc/source/API/Primitives2D.rst | 51 ----- doc/source/API/Primitives3D.rst | 74 -------- doc/source/API/Primitives3DLayout.rst | 81 -------- doc/source/API/PrimitivesCircuit.rst | 174 ------------------ doc/source/API/Setup.rst | 62 ------- doc/source/API/SetupTemplates.rst | 32 ---- doc/source/API/SetupTemplates3DLayout.rst | 22 --- doc/source/API/SetupTemplatesCircuit.rst | 26 --- doc/source/API/SetupTemplatesHFSS.rst | 25 --- doc/source/API/SetupTemplatesIcepak.rst | 24 --- doc/source/API/SetupTemplatesMaxwell.rst | 23 --- doc/source/API/SetupTemplatesMechanical.rst | 23 --- doc/source/API/SetupTemplatesQ3D.rst | 24 --- doc/source/API/SetupTemplatesRmxprt.rst | 33 ---- doc/source/API/SetupTemplatesTwinBuilder.rst | 21 --- doc/source/API/Stackup3D.rst | 22 --- doc/source/API/Variables.rst | 31 ---- doc/source/API/index.rst | 102 ---------- doc/source/EDBAPI/ComponentsEdb.rst | 50 ----- doc/source/EDBAPI/CoreEdb.rst | 84 --------- doc/source/EDBAPI/LayerData.rst | 37 ---- doc/source/EDBAPI/NetsEdb.rst | 52 ------ doc/source/EDBAPI/PadstackEdb.rst | 41 ----- doc/source/EDBAPI/PortsEdb.rst | 23 --- doc/source/EDBAPI/PrimitivesEdb.rst | 52 ------ doc/source/EDBAPI/SiWave.rst | 30 --- .../EDBAPI/SimulationConfigurationEdb.rst | 40 ---- doc/source/EDBAPI/SimulationEdb.rst | 43 ----- doc/source/EDBAPI/SourcesEdb.rst | 20 -- doc/source/EDBAPI/index.rst | 43 ----- doc/source/index.rst | 61 ------ 43 files changed, 2148 deletions(-) delete mode 100644 doc/source/API/Application.rst delete mode 100644 doc/source/API/Boundaries.rst delete mode 100644 doc/source/API/CableModeling.rst delete mode 100644 doc/source/API/Configuration.rst delete mode 100644 doc/source/API/Constants.rst delete mode 100644 doc/source/API/DesktopMessenger.rst delete mode 100644 doc/source/API/MaterialManagement.rst delete mode 100644 doc/source/API/Mesh.rst delete mode 100644 doc/source/API/MultiPartComponent.rst delete mode 100644 doc/source/API/Optimetrics.rst delete mode 100644 doc/source/API/Post.rst delete mode 100644 doc/source/API/Primitive_Objects.rst delete mode 100644 doc/source/API/Primitives2D.rst delete mode 100644 doc/source/API/Primitives3D.rst delete mode 100644 doc/source/API/Primitives3DLayout.rst delete mode 100644 doc/source/API/PrimitivesCircuit.rst delete mode 100644 doc/source/API/Setup.rst delete mode 100644 doc/source/API/SetupTemplates.rst delete mode 100644 doc/source/API/SetupTemplates3DLayout.rst delete mode 100644 doc/source/API/SetupTemplatesCircuit.rst delete mode 100644 doc/source/API/SetupTemplatesHFSS.rst delete mode 100644 doc/source/API/SetupTemplatesIcepak.rst delete mode 100644 doc/source/API/SetupTemplatesMaxwell.rst delete mode 100644 doc/source/API/SetupTemplatesMechanical.rst delete mode 100644 doc/source/API/SetupTemplatesQ3D.rst delete mode 100644 doc/source/API/SetupTemplatesRmxprt.rst delete mode 100644 doc/source/API/SetupTemplatesTwinBuilder.rst delete mode 100644 doc/source/API/Stackup3D.rst delete mode 100644 doc/source/API/Variables.rst delete mode 100644 doc/source/API/index.rst delete mode 100644 doc/source/EDBAPI/ComponentsEdb.rst delete mode 100644 doc/source/EDBAPI/CoreEdb.rst delete mode 100644 doc/source/EDBAPI/LayerData.rst delete mode 100644 doc/source/EDBAPI/NetsEdb.rst delete mode 100644 doc/source/EDBAPI/PadstackEdb.rst delete mode 100644 doc/source/EDBAPI/PortsEdb.rst delete mode 100644 doc/source/EDBAPI/PrimitivesEdb.rst delete mode 100644 doc/source/EDBAPI/SiWave.rst delete mode 100644 doc/source/EDBAPI/SimulationConfigurationEdb.rst delete mode 100644 doc/source/EDBAPI/SimulationEdb.rst delete mode 100644 doc/source/EDBAPI/SourcesEdb.rst delete mode 100644 doc/source/EDBAPI/index.rst diff --git a/doc/source/API/Application.rst b/doc/source/API/Application.rst deleted file mode 100644 index 87ccb2a90b4..00000000000 --- a/doc/source/API/Application.rst +++ /dev/null @@ -1,69 +0,0 @@ -Application and solvers -======================= -The PyAEDT API includes classes for different applications available in Ansys Electronics Desktop (AEDT). -You must initialize AEDT to get access to all PyAEDT modules and methods. - -.. image:: ../Resources/aedt_2.png - :width: 800 - :alt: Ansys Electronics Desktop (AEDT) is a platform that enables true electronics system design. - - -Available PyAEDT apps are: - -.. autosummary:: - :toctree: _autosummary - - pyaedt.desktop.Desktop - pyaedt.hfss.Hfss - pyaedt.q3d.Q3d - pyaedt.q3d.Q2d - pyaedt.maxwell.Maxwell2d - pyaedt.maxwell.Maxwell3d - pyaedt.icepak.Icepak - pyaedt.hfss3dlayout.Hfss3dLayout - pyaedt.mechanical.Mechanical - pyaedt.rmxprt.Rmxprt - pyaedt.circuit.Circuit - pyaedt.maxwellcircuit.MaxwellCircuit - pyaedt.emit.Emit - pyaedt.twinbuilder.TwinBuilder - - -All other classes and methods are inherited into the app class. -AEDT, which is also referred to as the desktop app, is implicitly launched in any PyAEDT app. -Before accessing a PyAEDT app, the desktop app must be launched and initialized. -The desktop app can be explicitly or implicitly initialized as in the following examples. - -Example with ``Desktop`` class explicit initialization: - -.. code:: python - - from pyaedt import launch_desktop, Circuit - d = launch_desktop(specified_version="2023.1", - non_graphical=False, - new_desktop_session=True, - close_on_exit=True, - student_version=False): - circuit = Circuit() - ... - # Any error here will be caught by Desktop. - ... - d.release_desktop() - -Example with ``Desktop`` class implicit initialization: - -.. code:: python - - from pyaedt import Circuit - circuit = Circuit(specified_version="2023.1", - non_graphical=False, - new_desktop_session=True, - close_on_exit=True, - student_version=False): - circuit = Circuit() - ... - # Any error here will be caught by Desktop. - ... - circuit.release_desktop() - - diff --git a/doc/source/API/Boundaries.rst b/doc/source/API/Boundaries.rst deleted file mode 100644 index a222340181f..00000000000 --- a/doc/source/API/Boundaries.rst +++ /dev/null @@ -1,36 +0,0 @@ -Boundary objects -================ -This section lists classes for creating and editing -boundaries in the 3D tools. These objects are returned by -app methods and can be used to edit or delete a boundary condition. - - -.. currentmodule:: pyaedt.modules.Boundary - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - NativeComponentObject - BoundaryObject - BoundaryObject3dLayout - FarFieldSetup - Matrix - BoundaryObject3dLayout - Sources - Excitations - -Example without ``Native Component Object``: - -.. code:: python - - from pyaedt import Icepak - ipk = Icepak() - component_name = "RadioBoard1" - native_comp = self.aedtapp.create_ipk_3dcomponent_pcb( - component_name, link_data, solution_freq, resolution, custom_x_resolution=400, custom_y_resolution=500 - ) - # native_comp is a NativeComponentObject - ... - ipk.release_desktop() - diff --git a/doc/source/API/CableModeling.rst b/doc/source/API/CableModeling.rst deleted file mode 100644 index 8166b35acca..00000000000 --- a/doc/source/API/CableModeling.rst +++ /dev/null @@ -1,132 +0,0 @@ -Cable modeling -============== -The ``Cable Modeling`` module includes several methods to work -with the Cable Modeling HFSS Beta feature: - - -* ``create_cable`` to create all available types of cables: bundle, straight wire and twisted pair. -* ``update_cable_properties`` to update all cables properties for all cable types. -* ``update_shielding`` to update only the shielding jacket type for bundle cable. -* ``remove_cables`` to remove cables. -* ``add_cable_to_bundle`` to add a cable or a list of cables to a bundle. -* ``create_clock_source`` to create a clock source. -* ``update_clock_source`` to update a clock source. -* ``remove_source`` to remove a source. -* ``remove_all_sources`` to remove all sources. -* ``create_pwl_source`` to create a pwl source. -* ``create_pwl_source_from_file`` to create a pwl source from file. -* ``update_pwl_source`` to update a pwl source. -* ``create_cable_harness`` to create a cable harness. - -They are accessible through: - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - CableModeling.Cable - -Cable bundle creation example: - -.. code:: python - - from pyaedt import Hfss - from pyaedt.generic.DataHandlers import json_to_dict - from pyaedt.modules.CableModeling import Cable - - hfss = Hfss(projectname=project_path, specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - # This call returns a dictionary out of the JSON file - cable_props = json_to_dict(json_path) - # This example shows how to manually change from script the cable properties - cable_props["Add_Cable"] = "True" - cable_props["Cable_prop"]["CableType"] = "bundle" - cable_props["Cable_prop"]["IsJacketTypeInsulation"] = "True" - cable_props["CableManager"]["Definitions"]["CableBundle"]["BundleParams"]["InsulationJacketParams"][ - "InsThickness" - ] = "3.66mm" - cable_props["CableManager"]["Definitions"]["CableBundle"]["BundleParams"]["InsulationJacketParams"][ - "JacketMaterial" - ] = "pec" - cable_props["CableManager"]["Definitions"]["CableBundle"]["BundleParams"]["InsulationJacketParams"][ - "InnerDiameter" - ] = "2.88mm" - cable_props["CableManager"]["Definitions"]["CableBundle"]["BundleAttribs"]["Name"] = "Bundle_Cable_Insulation" - # This call returns the Cable class - cable = Cable(hfss, cable_props) - # This call creates the cable bundle - cable.create_cable() - -Clock source creation example: - -.. code:: python - - from pyaedt import Hfss - from pyaedt.generic.DataHandlers import json_to_dict - from pyaedt.modules.CableModeling import Cable - - hfss = Hfss(projectname=project_path, specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - # This call returns a dictionary out of the JSON file - cable_props = json_to_dict(json_path) - # This example shows how to manually change from script the clock source properties - cable_props["Add_Cable"] = "False" - cable_props["Update_Cable"] = "False" - cable_props["Add_CablesToBundle"] = "False" - cable_props["Remove_Cable"] = "False" - cable_props["Add_Source"] = "True" - cable_props["Source_prop"]["AddClockSource"] = "True" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["ClockSignalParams"]["Period"] = "40us" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["ClockSignalParams"]["LowPulseVal"] = "0.1V" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["ClockSignalParams"]["HighPulseVal"] = "2V" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["ClockSignalParams"]["Risetime"] = "5us" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["ClockSignalParams"]["Falltime"] = "10us" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["ClockSignalParams"]["PulseWidth"] = "23us" - cable_props["CableManager"]["TDSources"]["ClockSourceDef"]["TDSourceAttribs"]["Name"] = "clock_test_1" - # This call returns the Cable class - cable = Cable(hfss, cable_props) - # This call creates the clock source - cable.create_clock_source() - -Cable harness creation example: - -.. code:: python - - from pyaedt import Hfss - from pyaedt.generic.DataHandlers import json_to_dict - from pyaedt.modules.CableModeling import Cable - - hfss = Hfss(projectname=project_path, specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - # This call returns a dictionary out of the JSON file - cable_props = json_to_dict(json_path) - # This example shows how to manually change from script the cable harness properties - cable_props["Add_Cable"] = "False" - cable_props["Update_Cable"] = "False" - cable_props["Add_CablesToBundle"] = "False" - cable_props["Remove_Cable"] = "False" - cable_props["Add_Source"] = "False" - cable_props["Update_Source"] = "False" - cable_props["Remove_Source"] = "False" - cable_props["Add_CableHarness"] = "True" - cable_props["CableHarness_prop"]["Name"] = "cable_harness_test" - cable_props["CableHarness_prop"]["Bundle"] = "New_updated_name_cable_bundle_insulation" - cable_props["CableHarness_prop"]["TwistAngleAlongRoute"] = "20deg" - cable_props["CableHarness_prop"]["Polyline"] = "Polyline1" - cable_props["CableHarness_prop"]["AutoOrient"] = "False" - cable_props["CableHarness_prop"]["XAxis"] = "Undefined" - cable_props["CableHarness_prop"]["XAxisOrigin"] = ["0mm", "0mm", "0mm"] - cable_props["CableHarness_prop"]["XAxisEnd"] = ["0mm", "0mm", "0mm"] - cable_props["CableHarness_prop"]["ReverseYAxisDirection"] = "True" - cable_props["CableHarness_prop"]["CableTerminationsToInclude"][0]["CableName"] = "straight_wire_cable" - cable_props["CableHarness_prop"]["CableTerminationsToInclude"][1]["CableName"] = "straight_wire_cable1" - cable_props["CableHarness_prop"]["CableTerminationsToInclude"][2]["CableName"] = "straight_wire_cable2" - # This call returns the Cable class - cable = Cable(hfss, cable_props) - # This call creates the cable harness - cable.create_cable_harness() \ No newline at end of file diff --git a/doc/source/API/Configuration.rst b/doc/source/API/Configuration.rst deleted file mode 100644 index b6284a959c5..00000000000 --- a/doc/source/API/Configuration.rst +++ /dev/null @@ -1,52 +0,0 @@ -Configuration files -~~~~~~~~~~~~~~~~~~~ -This module contains all methods to export project settings to a JSON file -and import and apply settings to a new design. Currently the configuration -cover the following apps: -* HFSS -* Q2D and Q3D Extractor -* Maxwell -* Icepak -* Mechanical - -The sections covered are: - -* Variables -* Mesh operations -* Setup and optimetrics -* Material properties -* Object properties -* Boundaries and excitations - -When a boundary is attached to a face, the tool tries to match it with a -FaceByPosition on the same object name on the target design. If, for any -reason, this face position has changed or the object name in the target design has changed, -the boundary fails to apply. - - -.. currentmodule:: pyaedt.generic.configurations - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - Configurations - ConfigurationsOptions - ImportResults - - -.. code:: python - - from pyaedt import Hfss - app = Hfss(project_name="original_project", specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - conf_file = self.aedtapp.configurations.export_config() - - app2 = Hfss(projec_name='newproject') - app2.modeler.import_3d_cad(file_path) - out = app2.configurations.import_config(conf_file) - app2.configurations.results.global_import_success - - ... diff --git a/doc/source/API/Constants.rst b/doc/source/API/Constants.rst deleted file mode 100644 index b905b125b64..00000000000 --- a/doc/source/API/Constants.rst +++ /dev/null @@ -1,21 +0,0 @@ -Constants -========== -This section lists constants that are commonly used in PyAEDT. - - -Example of constants usage: - -.. code:: python - - from pyaedt import constants - ipk = Icepak() - # Use of AXIS Constant - cylinder = ipk.modeler.create_cylinder(constants.AXIS.X, [0,0,0],10,3) - # Use of PLANE Constant - ipk.modeler.split(cylinder, constants.PLANE.YZ, sides="Both") - ... - ipk.release_desktop() - - -.. automodule:: pyaedt.generic.constants - :members: diff --git a/doc/source/API/DesktopMessenger.rst b/doc/source/API/DesktopMessenger.rst deleted file mode 100644 index 5d960814d03..00000000000 --- a/doc/source/API/DesktopMessenger.rst +++ /dev/null @@ -1,13 +0,0 @@ -Logger -~~~~~~ -This section lists modules for creating and editing -PyAEDT log files. - -.. currentmodule:: pyaedt.aedt_logger - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - AedtLogger - AppFilter diff --git a/doc/source/API/MaterialManagement.rst b/doc/source/API/MaterialManagement.rst deleted file mode 100644 index 90cdc55479f..00000000000 --- a/doc/source/API/MaterialManagement.rst +++ /dev/null @@ -1,56 +0,0 @@ -Material and stackup -==================== -This section lists material and stackup modules. -These classes cannot be used directly but can be accessed through an app. -Example: - - - -Material management -~~~~~~~~~~~~~~~~~~~ -This section describes all material-related classes and methods. - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - MaterialLib.Materials - Material.Material - Material.SurfaceMaterial - Material.MatProperties - Material.SurfMatProperties - Material.MatProperty - - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Materials class - my_materials = app.materials - # This call returns the Material class - copper = my_materials["copper"] - # This property is from the MatProperty class - copper.conductivity - ... - - - -Stackup management -~~~~~~~~~~~~~~~~~~ -This section describes all layer-related classes and methods used in HFSS 3D Layout (and indirectly in Circuit). - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - LayerStackup.Layers - LayerStackup.Layer \ No newline at end of file diff --git a/doc/source/API/Mesh.rst b/doc/source/API/Mesh.rst deleted file mode 100644 index 7b1a362ac43..00000000000 --- a/doc/source/API/Mesh.rst +++ /dev/null @@ -1,31 +0,0 @@ -Mesh operations -=============== -The ``Mesh`` module includes these classes: - -* ``Mesh`` for HFSS, Maxwell 2D, Maxwell 3D, Q2D Extractor, and Q3D Extractor -* ``IcepakMesh`` for Icepak -* ``Mesh3d`` for HFSS 3D Layout - -They are accessible through the mesh property: - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - Mesh.Mesh - MeshIcepak.IcepakMesh - Mesh3DLayout.Mesh3d - -.. code:: python - - from pyaedt import Maxwell3d - app = Maxwell3d(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - # This call returns the Mesh class - my_mesh = app.mesh - # This call executes a Mesh method and creates an object to control the mesh operation - mesh_operation_object = my_mesh.assign_surface_mesh("MyBox", 2) - ... diff --git a/doc/source/API/MultiPartComponent.rst b/doc/source/API/MultiPartComponent.rst deleted file mode 100644 index e8e192c4aa3..00000000000 --- a/doc/source/API/MultiPartComponent.rst +++ /dev/null @@ -1,23 +0,0 @@ -Multi-part components -===================== -This section lists classes for creating and editing multi-part components in the 3D tools. -This consists of a set of one or more 3D component objects, linked together and parametrized -to allow movements. - - - -.. currentmodule:: pyaedt.modeler.advanced_cad - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - actors.Person - actors.Vehicle - actors.Bird - actors.Radar - parts.Part - parts.Antenna - multiparts.MultiPartComponent - multiparts.Environment - multiparts.Actor diff --git a/doc/source/API/Optimetrics.rst b/doc/source/API/Optimetrics.rst deleted file mode 100644 index 6a5e1258642..00000000000 --- a/doc/source/API/Optimetrics.rst +++ /dev/null @@ -1,34 +0,0 @@ -Optimetrics -=========== -This module contains all properties and methods needed to create -optimetrics setups. - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # returns the ParametericsSetups Class - app.parametrics - - # returns the OptimizationSetups Class - app.optimizations - - # adds an optimization and returns Setup class with all settings and methods - sweep3 = hfss.opti_optimization.add_optimization(calculation="dB(S(1,1))", calculation_value="2.5GHz") - - ... - -.. currentmodule:: pyaedt.modules.DesignXPloration - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - ParametricSetups - OptimizationSetups - SetupParam - SetupOpti - diff --git a/doc/source/API/Post.rst b/doc/source/API/Post.rst deleted file mode 100644 index 22cc06f0075..00000000000 --- a/doc/source/API/Post.rst +++ /dev/null @@ -1,112 +0,0 @@ -Postprocessing -============== -This section lists modules for creating and editing -plots in AEDT. They are accessible through the ``post`` property. - -.. note:: - Some capabilities of the ``AdvancedPostProcessing`` module require Python 3 and - installations of the `numpy `_, - `matplotlib `_, and `pyvista `_ - packages. - -.. note:: - Some functionalities are available only when AEDT is running - in graphical mode. - - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - AdvancedPostProcessing.PostProcessor - solutions.SolutionData - solutions.FieldPlot - solutions.FfdSolutionData - - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the PostProcessor class - post = app.post - - # This call returns a FieldPlot object - plotf = post.create_fieldplot_volume(object_list, quantityname, setup_name, intrinsic_dict) - - # This call returns a SolutionData object - my_data = post.get_solution_data(expressions=trace_names) - - # This call returns a new standard report object and creates one or multiple reports from it. - standard_report = post.reports_by_category.standard("db(S(1,1))") - standard_report.create() - sols = standard_report.get_solution_data() - ... - - -AEDT report management -~~~~~~~~~~~~~~~~~~~~~~ -AEDT provides great flexibility in reports. -PyAEDT has classes for manipulating any report property. - - -.. note:: - Some functionalities are available only when AEDT is running - in graphical mode. - - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - report_templates.Trace - report_templates.LimitLine - report_templates.Standard - report_templates.Fields - report_templates.NearField - report_templates.FarField - report_templates.EyeDiagram - report_templates.Emission - report_templates.Spectral - - -Plot fields and data outside AEDT -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -PyAEDT supports external report capabilities available with installed third-party -packages like `numpy `_, -`pandas `_, `matplotlib `_, -and `pyvista `_. - -.. currentmodule:: pyaedt.modules - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - solutions.SolutionData - solutions.FieldPlot - solutions.FfdSolutionData - AdvancedPostProcessing.ModelPlotter - - -Icepak monitors -~~~~~~~~~~~~~~~ - -.. currentmodule:: pyaedt.modules.monitor_icepak - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - FaceMonitor - PointMonitor - Monitor diff --git a/doc/source/API/Primitive_Objects.rst b/doc/source/API/Primitive_Objects.rst deleted file mode 100644 index ee4652f3461..00000000000 --- a/doc/source/API/Primitive_Objects.rst +++ /dev/null @@ -1,143 +0,0 @@ -Primitives -========== - -This section lists the core AEDT Modeler primitives that are supported both in 2D and 3D solvers (HFSS, Maxwell, -Icepak, Q3D, and Mechanical): - -* Primitives -* Objects - -They are accessible through the ``modeler.objects`` property: - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call return the Modeler3D class - modeler = app.modeler - - # This call returns a Primitives3D object - primitives = modeler - - # This call return an Object3d object - my_box = primitives.create_box([0,0,0],[10,10,10]) - my_box = primitives.objects[my_box.id] - - # This call return a FacePrimitive object list - my_box.faces - # This call returns an EdgePrimitive object list - my_box.edges - my_box.faces[0].edges - - # This call returns a VertexPrimitive object list - my_box.vertices - my_box.faces[0].vertices - my_box.faces[0].edges[0].vertices - - ... - - - -Objects -~~~~~~~ - -The following classes define objects properties for 3D and 2D Solvers (excluding HFSS 3D Layout). -They contain all getters and setters to simplify object manipulation. - - - -.. currentmodule:: pyaedt.modeler - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - cad.object3d.Object3d - cad.elements3d.FacePrimitive - cad.elements3d.EdgePrimitive - cad.elements3d.VertexPrimitive - cad.polylines.PolylineSegment - cad.polylines.Polyline - cad.component_array.ComponentArray - cad.components_3d.UserDefinedComponent - cad.elements3d.Point - cad.elements3d.Plane - cad.elements3d.HistoryProps - cad.elements3d.BinaryTreeNode - - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Modeler3D class - modeler = app.modeler - - # This call returns a Primitives3D object - primitives = modeler - - # This call returns an Object3d object - my_box = primitives.create_box([0,0,0],[10,10,10]) - - # Getter and setter - my_box.material_name - my_box.material_name = "copper" - - my_box.faces[0].center - - ... - - -Coordinate systems and geometry operators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This module contains all properties and methods needed to edit a -coordinate system and a set of useful geometry operators. -The ``CoordinateSystem`` class is accessible through the ``create_coordinate_system`` -method or the ``coordinate_systems`` list. The ``GeometryOperators`` class can be -imported and used because it is made by static methods. - - -.. currentmodule:: pyaedt.modeler - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - cad.Modeler.CoordinateSystem - geometry_operators.GeometryOperators - - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the CoordinateSystem object list - cs = app.modeler.coordinate_systems - - # This call returns a CoordinateSystem object - new_cs = app.modeler.create_coordinate_system() - - ... - - -Advanced modeler operations -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -PyAEDT includes some advanced modeler tools like ``MultiPartComponent`` for 3D component -management and ``Stackup3D`` for parametric creation of 3D modeler stackups. - -.. toctree:: - :maxdepth: 2 - - MultiPartComponent - Stackup3D \ No newline at end of file diff --git a/doc/source/API/Primitives2D.rst b/doc/source/API/Primitives2D.rst deleted file mode 100644 index c41e3c5d1d0..00000000000 --- a/doc/source/API/Primitives2D.rst +++ /dev/null @@ -1,51 +0,0 @@ -2D modeler -=========== - -This section lists the core AEDT Modeler modules for 2D and 3D solvers (Maxwell 2D, 2D Extractor). - - -They are accessible through the ``modeler`` property: - -.. code:: python - - from pyaedt import Maxwell2d - app = Maxwell2d(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call return the Modeler2D class - modeler = app.modeler - - - ... - - - -The ``Modeler`` module contains all properties and methods needed to edit a -modeler, including all primitives methods and properties: - - -.. currentmodule:: pyaedt.modeler - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - modeler2d.Modeler2D - - - -.. code:: python - - from pyaedt import Maxwell2d - app = Maxwell2d(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the NexximComponents class - origin = [0,0,0] - dimensions = [10,5,20] - #Material and name are not mandatory fields - box_object = app.modeler.primivites.create_rectangle([15, 20, 0], [5, 5], matname="aluminum") - - ... diff --git a/doc/source/API/Primitives3D.rst b/doc/source/API/Primitives3D.rst deleted file mode 100644 index 186fe639076..00000000000 --- a/doc/source/API/Primitives3D.rst +++ /dev/null @@ -1,74 +0,0 @@ -3D modeler -========== - -This section lists the core AEDT Modeler modules with 3D solvers (HFSS, Maxwell, -Icepak, Q3D, and Mechanical): - -* Modeler -* Primitives -* Objects - -They are accessible through the ``modeler`` property: - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call return the Modeler3D class - modeler = app.modeler - - # This call returns a Primitives3D object - primitives = modeler - - # This call return an Object3d object - my_box = primitives.create_box([0,0,0],[10,10,10]) - my_box = primitives.objects[my_box.id] - - # This call return a FacePrimitive object list - my_box.faces - # This call returns an EdgePrimitive object list - my_box.edges - my_box.faces[0].edges - - # This call returns a VertexPrimitive object list - my_box.vertices - my_box.faces[0].vertices - my_box.faces[0].edges[0].vertices - - ... - - -Modeler -~~~~~~~ - -The ``Modeler`` module contains all properties and methods needed to edit a -modeler, including all primitives methods and properties for HFSS, Maxwell 3D, Q3D Extractor, and Icepak: - - - -.. currentmodule:: pyaedt.modeler - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - modeler3d.Modeler3D - - -.. code:: python - - from pyaedt import Circuit - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the NexximComponents class - origin = [0,0,0] - dimensions = [10,5,20] - #Material and name are not mandatory fields - box_object = app.modeler.primivites.create_box(origin, dimensions, name="mybox", matname="copper") - - ... diff --git a/doc/source/API/Primitives3DLayout.rst b/doc/source/API/Primitives3DLayout.rst deleted file mode 100644 index c9bb0807cf3..00000000000 --- a/doc/source/API/Primitives3DLayout.rst +++ /dev/null @@ -1,81 +0,0 @@ -Modeler in HFSS 3D Layout -========================== - -This section lists the core AEDT Modeler modules available in HFSS 3D Layout: - -* Modeler -* Primitives -* Objects - -They are accessible through the ``modeler`` module and ``modeler.objects`` property: - -.. code:: python - - from pyaedt import Hfss3dLayout - hfss = Hfss3dLayout() - my_modeler = hfss.modeler - - ... - - -Modeler -~~~~~~~ - -The ``Modeler`` module contains all properties and methods needed to edit a -modeler, including all primitives methods and properties: - - -* ``Modeler3DLayout`` for HFSS 3D Layout - - - -.. currentmodule:: pyaedt.modeler - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - modelerpcb.Modeler3DLayout - - -Objects in HFSS 3D Layout -~~~~~~~~~~~~~~~~~~~~~~~~~ -The following classes define the object properties for HFSS 3D Layout. -They contain all getters and setters to simplify object manipulation. - -.. currentmodule:: pyaedt.modeler.pcb - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - object3dlayout.Components3DLayout - object3dlayout.Nets3DLayout - object3dlayout.Pins3DLayout - object3dlayout.Line3dLayout - object3dlayout.Polygons3DLayout - object3dlayout.Circle3dLayout - object3dlayout.Rect3dLayout - object3dlayout.Points3dLayout - object3dlayout.Padstack - -.. code:: python - - from pyaedt import Hfss3dLayout - app = Hfss3dLayout(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Modeler3DLayout class - modeler = app.modeler - - # This call returns a Primitives3D object - primitives = modeler - - # This call returns an Object3d object - my_rect = primitives.create_rectangle([0,0,0],[10,10]) - - # Getter and setter - my_rect.material_name - - ... diff --git a/doc/source/API/PrimitivesCircuit.rst b/doc/source/API/PrimitivesCircuit.rst deleted file mode 100644 index c6c8d185bc1..00000000000 --- a/doc/source/API/PrimitivesCircuit.rst +++ /dev/null @@ -1,174 +0,0 @@ -Modeler and components Circuit -============================== - -This section lists the core AEDT Modeler modules: - -* Modeler -* Primitives -* Objects - -They are accessible through the ``modeler`` module and ``modeler.objects`` property: - -.. code:: python - - - from pyaedt import TwinBuilder - app = TwinBuilder(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Modeler class - modeler = app.modeler - - ... - - -Modeler -~~~~~~~ - -The ``Modeler`` module contains all properties and methods needed to edit a -modeler, including all primitives methods and properties: - -* ``ModelerNexxim`` for Circuit -* ``ModelerTwinBuilder`` for Twin Builder -* ``ModelerEmit`` for EMIT - - -.. currentmodule:: pyaedt.modeler - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - schematic.ModelerNexxim - schematic.ModelerTwinBuilder - schematic.ModelerEmit - schematic.ModelerMaxwellCircuit - - -Schematic in Circuit -~~~~~~~~~~~~~~~~~~~~ -The following classes define the object properties for Circuit components. -They contain all getters and setters to simplify object manipulation. - -.. currentmodule:: pyaedt.modeler.circuits.PrimitivesNexxim - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - NexximComponents - -.. code:: python - - from pyaedt import Circuit - app = Circuit(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns a Schematic object - schematic = modeler.schematic - - # This call returns an Object3d object - my_res = schematic.create_resistor("R1", 50) - - -Objects in Circuit -~~~~~~~~~~~~~~~~~~ -The following classes define the object properties for Circuit. -They contain all getters and setters to simplify object manipulation. - -.. currentmodule:: pyaedt.modeler.circuits - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - object3dcircuit.CircuitComponent - object3dcircuit.CircuitPins - object3dcircuit.Wire - -.. code:: python - - from pyaedt import Circuit - app = Circuit(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Modeler class - modeler = app.modeler - - # This call returns a Schematic object - schematic = modeler.schematic - - # This call returns an Object3d object - my_res = schematic.create_resistor("R1", 50) - - # Getter and setter - my_res.location - my_res.parameters["R"]=100 - - ... - -Schematic in EMIT -~~~~~~~~~~~~~~~~~ -The following classes define the object properties for EMIT components. -They contain all getters and setters to simplify object manipulation. - -.. currentmodule:: pyaedt.modeler.circuits.PrimitivesEmit - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - EmitComponents - - -Schematic in Twin Builder -~~~~~~~~~~~~~~~~~~~~~~~~~ -The following classes define the object properties for Twin Builder components. -They contain all getters and setters to simplify object manipulation. - -.. currentmodule:: pyaedt.modeler.circuits.PrimitivesTwinBuilder - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - TwinBuilderComponents - -.. code:: python - - from pyaedt import TwinBuilder - app = TwinBuilder(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Modeler class - modeler = app.modeler - - # This call returns a Schematic object - schematic = modeler.schematic - - # This call returns an Object3d object - my_res = schematic.create_resistor("R1", 50) - - # Getter and setter - my_res.location - my_res.parameters["R"]=100 - - ... - - -Schematic in Maxwell Circuit -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following classes define the object properties for Maxwell Circuit components. -They contain all getters and setters to simplify object manipulation. - -.. currentmodule:: pyaedt.modeler.circuits.PrimitivesMaxwellCircuit - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - MaxwellCircuitComponents diff --git a/doc/source/API/Setup.rst b/doc/source/API/Setup.rst deleted file mode 100644 index c2dc1654f24..00000000000 --- a/doc/source/API/Setup.rst +++ /dev/null @@ -1,62 +0,0 @@ -Setup -===== -This section lists setup modules: - -* ``Setup`` for HFSS, Maxwell 2D, Maxwell 3D, Q2D Extractor, and Q3D Extractor -* ``Setup3DLayout`` for HFSS 3D Layout -* ``SetupCircuit`` for Circuit and Twin Builder - -The ``Setup`` object is accessible through the ``create_setup`` method and ``setups`` object list. - -.. currentmodule:: pyaedt.modules.SolveSetup - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - SetupHFSS - SetupHFSSAuto - SetupSBR - SetupQ3D - SetupMaxwell - Setup - Setup3DLayout - SetupCircuit - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the Setup class - my_setup = app.setups[0] - - - # This call returns a Setup object - setup = app.create_setup("MySetup") - - ... - - -Sweep classes -============= -This section lists sweep classes and their default values: - -* ``SweepHFSS`` for HFSS -* ``SweepHFSS3DLayout`` for HFSS 3D Layout -* ``SweepMatrix`` for Q3D and 2D Extractor - -The ``Setup`` object is accessible through the methods available for sweep creation. - - -.. currentmodule:: pyaedt.modules.SolveSweeps - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - SweepHFSS - SweepHFSS3DLayout - SweepMatrix diff --git a/doc/source/API/SetupTemplates.rst b/doc/source/API/SetupTemplates.rst deleted file mode 100644 index ece5d77edb1..00000000000 --- a/doc/source/API/SetupTemplates.rst +++ /dev/null @@ -1,32 +0,0 @@ -Setup templates -=============== - -This section lists all setup templates with their default values and keys. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - Launch AEDT 2023 R1 in non-graphical mode - - from pyaedt import Hfss - - hfss = Hfss() - # Any property of this setup can be found on this page. - setup = hfss.create_setup() - setup.props["AdaptMultipleFreqs"] = True - setup.update() - - -.. toctree:: - :maxdepth: 2 - - SetupTemplatesHFSS - SetupTemplates3DLayout - SetupTemplatesMaxwell - SetupTemplatesQ3D - SetupTemplatesIcepak - SetupTemplatesMechanical - SetupTemplatesCircuit - SetupTemplatesTwinBuilder - SetupTemplatesRmxprt diff --git a/doc/source/API/SetupTemplates3DLayout.rst b/doc/source/API/SetupTemplates3DLayout.rst deleted file mode 100644 index ec9befa3850..00000000000 --- a/doc/source/API/SetupTemplates3DLayout.rst +++ /dev/null @@ -1,22 +0,0 @@ -HFSS 3D Layout and arguments -============================ - -This section lists all setup templates with their default values and keys available in HFSS 3D Layout. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - Launch AEDT 2023 R1 in non-graphical mode - - from pyaedt import Hfss - - hfss = Hfss() - # Any property of this setup can be found on this page. - setup = hfss.create_setup() - setup.props["AdaptMultipleFreqs"] = True - setup.update() - - - -.. pprint:: pyaedt.modules.SetupTemplates.HFSS3DLayout diff --git a/doc/source/API/SetupTemplatesCircuit.rst b/doc/source/API/SetupTemplatesCircuit.rst deleted file mode 100644 index 1fe1df1a41c..00000000000 --- a/doc/source/API/SetupTemplatesCircuit.rst +++ /dev/null @@ -1,26 +0,0 @@ -Circuit templates and arguments -================================ - -This section lists all setup templates with their default values and keys available in Circuit. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - from pyaedt import Hfss - - hfss = Hfss() - # Any property of this setup can be found on this page. - setup = hfss.create_setup() - setup.props["AdaptMultipleFreqs"] = True - setup.update() - - - -.. pprint:: pyaedt.modules.SetupTemplates.NexximLNA -.. pprint:: pyaedt.modules.SetupTemplates.NexximDC -.. pprint:: pyaedt.modules.SetupTemplates.NexximTransient -.. pprint:: pyaedt.modules.SetupTemplates.NexximQuickEye -.. pprint:: pyaedt.modules.SetupTemplates.NexximVerifEye -.. pprint:: pyaedt.modules.SetupTemplates.NexximAMI - diff --git a/doc/source/API/SetupTemplatesHFSS.rst b/doc/source/API/SetupTemplatesHFSS.rst deleted file mode 100644 index ef5ede84f66..00000000000 --- a/doc/source/API/SetupTemplatesHFSS.rst +++ /dev/null @@ -1,25 +0,0 @@ -HFSS templates and arguments -============================ - -This section lists all setup templates with their default values and keys available in HFSS. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - from pyaedt import Hfss - - hfss = Hfss() - # Any property of this setup can be found on this page. - setup = hfss.create_setup() - setup.props["AdaptMultipleFreqs"] = True - setup.update() - - - -.. pprint:: pyaedt.modules.SetupTemplates.HFSSDrivenAuto -.. pprint:: pyaedt.modules.SetupTemplates.HFSSDrivenDefault -.. pprint:: pyaedt.modules.SetupTemplates.HFSSDrivenDefault -.. pprint:: pyaedt.modules.SetupTemplates.HFSSTransient -.. pprint:: pyaedt.modules.SetupTemplates.HFSSSBR - diff --git a/doc/source/API/SetupTemplatesIcepak.rst b/doc/source/API/SetupTemplatesIcepak.rst deleted file mode 100644 index 0d3c46980c8..00000000000 --- a/doc/source/API/SetupTemplatesIcepak.rst +++ /dev/null @@ -1,24 +0,0 @@ -Icepak templates and arguments -=============================== - -This section lists all setup templates with their default values and keys available in Icepak. -Note that Icepak parameters contain spaces. To use them as arguments of the ``"create_setup"`` method, these -same parameters have to be used without spaces. -You can edit a setup after it is created. Here is an example: - -.. code:: python - - from pyaedt import Icepak - - app = Icepak() - # Any property of this setup can be found on this page. - setup = app.create_setup(MaxIterations=5) - - - -.. pprint:: pyaedt.modules.SetupTemplates.TransientFlowOnly -.. pprint:: pyaedt.modules.SetupTemplates.TransientTemperatureOnly -.. pprint:: pyaedt.modules.SetupTemplates.TransientTemperatureAndFlow -.. pprint:: pyaedt.modules.SetupTemplates.SteadyFlowOnly -.. pprint:: pyaedt.modules.SetupTemplates.SteadyTemperatureOnly -.. pprint:: pyaedt.modules.SetupTemplates.SteadyTemperatureAndFlow diff --git a/doc/source/API/SetupTemplatesMaxwell.rst b/doc/source/API/SetupTemplatesMaxwell.rst deleted file mode 100644 index 6531ca1c2e9..00000000000 --- a/doc/source/API/SetupTemplatesMaxwell.rst +++ /dev/null @@ -1,23 +0,0 @@ -Maxwell templates and arguments -=============================== - - -This section lists all setup templates with their default values and keys available in Maxwell 2D and 3D. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - from pyaedt import Maxwell3d - - Maxwell3d = Maxwell3d () - # Any property of this setup can be found on this page. - setup = Maxwell3d.create_setup () - setup.props["MaximumPasses"] = 5 - setup.update () - -.. pprint:: pyaedt.modules.SetupTemplates.MaxwellTransient -.. pprint:: pyaedt.modules.SetupTemplates.Magnetostatic -.. pprint:: pyaedt.modules.SetupTemplates.Electrostatic -.. pprint:: pyaedt.modules.SetupTemplates.EddyCurrent -.. pprint:: pyaedt.modules.SetupTemplates.ElectricTransient diff --git a/doc/source/API/SetupTemplatesMechanical.rst b/doc/source/API/SetupTemplatesMechanical.rst deleted file mode 100644 index c636fd2676e..00000000000 --- a/doc/source/API/SetupTemplatesMechanical.rst +++ /dev/null @@ -1,23 +0,0 @@ -Mechanical templates and arguments -================================== - -This section lists all setup templates with their default values and keys available in Mechanical. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - - from pyaedt import Mechanical - - app = Mechanical() - # Any property of this setup can be found on this page. - setup = app.create_setup(MaxModes=6) - - - - -.. pprint:: pyaedt.modules.SetupTemplates.MechTerm -.. pprint:: pyaedt.modules.SetupTemplates.MechModal -.. pprint:: pyaedt.modules.SetupTemplates.MechStructural - diff --git a/doc/source/API/SetupTemplatesQ3D.rst b/doc/source/API/SetupTemplatesQ3D.rst deleted file mode 100644 index c7221a64e3b..00000000000 --- a/doc/source/API/SetupTemplatesQ3D.rst +++ /dev/null @@ -1,24 +0,0 @@ -Q3D templates and arguments -=========================== - - -This section lists all setup templates with their default values and keys available in Q3D and 2D Extractor. -Note that to use nested parameters, you can set a parameter using the "__" separator as shown in the following example. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - - from pyaedt import Q3d - - app = Q3d() - # Any property of this setup can be found on this page. - setup = app.create_setup(AC__MaxPasses=6) - - - -.. pprint:: pyaedt.modules.SetupTemplates.Matrix -.. pprint:: pyaedt.modules.SetupTemplates.Close -.. pprint:: pyaedt.modules.SetupTemplates.Open - diff --git a/doc/source/API/SetupTemplatesRmxprt.rst b/doc/source/API/SetupTemplatesRmxprt.rst deleted file mode 100644 index 47224efea47..00000000000 --- a/doc/source/API/SetupTemplatesRmxprt.rst +++ /dev/null @@ -1,33 +0,0 @@ -RMXprt templates and arguments -============================== - -This section lists all setup templates with their default values and keys available in RMXprt. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - from pyaedt import Hfss - - hfss = Hfss() - # Any property of this setup can be found on this page. - setup = hfss.create_setup() - setup.props["AdaptMultipleFreqs"] = True - setup.update() - - - -.. pprint:: pyaedt.modules.SetupTemplates.GRM -.. pprint:: pyaedt.modules.SetupTemplates.DFIG -.. pprint:: pyaedt.modules.SetupTemplates.TPIM -.. pprint:: pyaedt.modules.SetupTemplates.TPSM -.. pprint:: pyaedt.modules.SetupTemplates.BLDC -.. pprint:: pyaedt.modules.SetupTemplates.ASSM -.. pprint:: pyaedt.modules.SetupTemplates.PMDC -.. pprint:: pyaedt.modules.SetupTemplates.SRM -.. pprint:: pyaedt.modules.SetupTemplates.LSSM -.. pprint:: pyaedt.modules.SetupTemplates.UNIM -.. pprint:: pyaedt.modules.SetupTemplates.DCM -.. pprint:: pyaedt.modules.SetupTemplates.CPSM -.. pprint:: pyaedt.modules.SetupTemplates.NSSM - diff --git a/doc/source/API/SetupTemplatesTwinBuilder.rst b/doc/source/API/SetupTemplatesTwinBuilder.rst deleted file mode 100644 index 220bf097168..00000000000 --- a/doc/source/API/SetupTemplatesTwinBuilder.rst +++ /dev/null @@ -1,21 +0,0 @@ -Twin Builder templates and arguments -==================================== - - -This section lists all setup templates with their default values and keys available in Twin Builder. - -You can edit a setup after it is created. Here is an example: - -.. code:: python - - from pyaedt import Hfss - - hfss = Hfss() - # Any property of this setup can be found on this page. - setup = hfss.create_setup() - setup.props["AdaptMultipleFreqs"] = True - setup.update() - - - -.. pprint:: pyaedt.modules.SetupTemplates.TR diff --git a/doc/source/API/Stackup3D.rst b/doc/source/API/Stackup3D.rst deleted file mode 100644 index 178a80dbaed..00000000000 --- a/doc/source/API/Stackup3D.rst +++ /dev/null @@ -1,22 +0,0 @@ -Stackup 3D components -===================== -This section lists ``stackup_3d`` classes for creating and editing a stackup and objects in the 3D tools. -This consists of a set of one or more parametrized layer objects and placing lines, patches, polygons, -and vias. - - - -.. currentmodule:: pyaedt.modeler.advanced_cad - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - stackup_3d.Stackup3D - stackup_3d.Layer3D - stackup_3d.Padstack - stackup_3d.PadstackLayer - stackup_3d.Patch - stackup_3d.Trace - stackup_3d.Polygon - stackup_3d.NamedVariable diff --git a/doc/source/API/Variables.rst b/doc/source/API/Variables.rst deleted file mode 100644 index 8915d342f3a..00000000000 --- a/doc/source/API/Variables.rst +++ /dev/null @@ -1,31 +0,0 @@ -Variable -======== -This module provides all functionalities for creating and editing -design and project variables in the 3D tools. - -.. code:: python - - from pyaedt import Hfss - app = Hfss(specified_version="2023.1", - non_graphical=False, new_desktop_session=True, - close_on_exit=True, student_version=False) - - # This call returns the VariableManager class - variable_manager = self.aedtapp._variable_manager - - # Set and get a variable - app["w"] = "10mm" - a = app["w"] - ... - - -.. currentmodule:: pyaedt.application.Variables - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - VariableManager - Variable - DataSet - CSVDataset diff --git a/doc/source/API/index.rst b/doc/source/API/index.rst deleted file mode 100644 index 2467efda4c8..00000000000 --- a/doc/source/API/index.rst +++ /dev/null @@ -1,102 +0,0 @@ -======== -AEDT API -======== - -This section describes PyAEDT core classes, methods, and functions -for AEDT apps and modules. Use the search feature or click links -to view API documentation. -The Ansys Electronics Desktop (AEDT) is a platform that enables true electronics system design. -`AEDT `_ provides access to the Ansys gold-standard -electro-magnetics simulation solutions such as Ansys HFSS, -Ansys Maxwell, Ansys Q3D Extractor, Ansys Siwave, and Ansys Icepak using electrical CAD (ECAD) and -Mechanical CAD (MCAD) workflows. -In addition, it includes direct links to the complete Ansys portfolio of thermal, fluid, -and Mechanical solvers for comprehensive multiphysics analysis. -Tight integration among these solutions provides unprecedented ease of use for setup and -faster resolution of complex simulations for design and optimization. - -.. image:: ../Resources/aedt_2.png - :width: 800 - :alt: AEDT Applications - :target: https://www.ansys.com/products/electronics - -The PyAEDT API includes classes for apps and modules. You must initialize the -PyAEDT app to get access to all modules and methods. Available apps are: - -- `HFSS `_ -- `HFSS 3D Layout `_ -- `Maxwell 3D `_ -- `Maxwell 2D `_ -- `Maxwell Circuit `_ -- `Q3D `_ -- `Q2D Extractor `_ -- `Icepak `_ -- `Mechanical `_ -- RMXprt -- EMIT -- Circuit -- `TwinBuilder `_ - -All other classes and methods are inherited into the app class. -The desktop app is implicitly launched in any of the other applications. -Before accessing a PyAEDT app, the desktop app has to be launched and initialized. -The desktop app can be explicitly or implicitly initialized as shown in the following examples. - -Example with ``Desktop`` class explicit initialization: - -.. code:: python - - from pyaedt import launch_desktop, Circuit - d = launch_desktop(specified_version="2023.1", - non_graphical=False, - new_desktop_session=True, - close_on_exit=True, - student_version=False): - circuit = Circuit() - ... - # Any error here should be caught by the desktop app. - ... - d.release_desktop() - -Example with ``Desktop`` class implicit initialization: - -.. code:: python - - from pyaedt import Circuit - circuit = Circuit(specified_version="2023.1", - non_graphical=False, - new_desktop_session=True, - close_on_exit=True, - student_version=False): - circuit = Circuit() - ... - # Any error here should be caught by the desktop app. - ... - circuit.release_desktop() - - -.. toctree:: - :maxdepth: 2 - - Application - MaterialManagement - Primitives3D - Primitives2D - Primitive_Objects - Primitives3DLayout - PrimitivesCircuit - Boundaries - Mesh - Setup - Post - DesktopMessenger - Optimetrics - Variables - Constants - Configuration - SetupTemplates - CableModeling - - - - diff --git a/doc/source/EDBAPI/ComponentsEdb.rst b/doc/source/EDBAPI/ComponentsEdb.rst deleted file mode 100644 index 97a173f77d3..00000000000 --- a/doc/source/EDBAPI/ComponentsEdb.rst +++ /dev/null @@ -1,50 +0,0 @@ -Components -========== -This section contains API references for component management. -The main component object is called directly from main application using the property ``components``. - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - pins =edb.components.get_pin_from_component("U2A5") - - ... - - -.. currentmodule:: pyaedt.edb_core.components - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - Components - - -Instances and definitions -------------------------- -These classes are the containers of data management for components reference designator and definitions. - - -.. currentmodule:: pyaedt.edb_core.edb_data.components_data - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - EDBComponent - EDBComponentDef - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - comp = edb.components["C1"] - - comp.is_enabled = True - part = edb.components.definitions["AAA111"] - ... \ No newline at end of file diff --git a/doc/source/EDBAPI/CoreEdb.rst b/doc/source/EDBAPI/CoreEdb.rst deleted file mode 100644 index 0ab464e1be6..00000000000 --- a/doc/source/EDBAPI/CoreEdb.rst +++ /dev/null @@ -1,84 +0,0 @@ -EDB manager -=========== -An AEDB database is a folder that contains the database representing any part of a PCB. -It can be opened and edited using the ``Edb`` class. - -.. image:: ../Resources/3dlayout_1.png - :width: 800 - :alt: HFSS 3D Layout is the tool used to visualize EDB content. - - - -.. autosummary:: - :toctree: _autosummary - - pyaedt.edb.Edb - pyaedt.edb_core.edb_data.variables.Variable - pyaedt.edb_core.edb_data.edbvalue.EdbValue - - -.. code:: python - - from pyaedt import Edb - # this call returns the Edb class initialized on 2023 R1 - edb = Edb(myedb, edbversion="2023.1") - - ... - - -EDB modules -~~~~~~~~~~~ -This section lists the core EDB modules for reading and writing information -to AEDB files. - - -.. currentmodule:: pyaedt.edb_core - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - hfss.EdbHfss - siwave.EdbSiwave - materials.Materials - - - -.. currentmodule:: pyaedt.edb_core.edb_data.edbvalue - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - EdbValue - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - # this call returns the EdbHfss Class - comp = edb.hfss - - # this call returns the Components Class - comp = edb.components - - # this call returns the EdbSiwave Class - comp = edb.siwave - - # this call returns the EdbPadstacks Class - comp = edb.padstacks - - # this call returns the Stackup Class - comp = edb.stackup - - # this call returns the Materials Class - comp = edb.materials - - # this call returns the EdbNets Class - comp = edb.nets - - ... diff --git a/doc/source/EDBAPI/LayerData.rst b/doc/source/EDBAPI/LayerData.rst deleted file mode 100644 index 622bca08acf..00000000000 --- a/doc/source/EDBAPI/LayerData.rst +++ /dev/null @@ -1,37 +0,0 @@ -Stackup & layers -================ -These classes are the containers of the layer and stackup manager of the EDB API. - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - # this call returns the EDBLayers class - layer = edb.stackup.stackup_layers - - # this call returns the EDBLayer class - layer = edb.stackup["TOP"] - ... - - -.. currentmodule:: pyaedt.edb_core.stackup - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - Stackup - - -.. currentmodule:: pyaedt.edb_core.edb_data.layer_data - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - LayerEdbClass - - diff --git a/doc/source/EDBAPI/NetsEdb.rst b/doc/source/EDBAPI/NetsEdb.rst deleted file mode 100644 index 52df7f43633..00000000000 --- a/doc/source/EDBAPI/NetsEdb.rst +++ /dev/null @@ -1,52 +0,0 @@ -Nets -==== -This section contains API references for net management. -The main component object is called directly from main application using the property ``nets``. - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - edb.nets.plot(None,None) - - ... - - -.. currentmodule:: pyaedt.edb_core.nets - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - EdbNets - - -Net properties --------------- -The following class is the container of data management for nets, extended nets and differential pairs. - - -.. currentmodule:: pyaedt.edb_core.edb_data.nets_data - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - EDBNetsData - EDBNetClassData - EDBExtendedNetData - EDBDifferentialPairData - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - edb.nets["M_MA<6>"].delete() - edb.net_classes - edb.differential_pairs - edb.extended_nets - - - ... \ No newline at end of file diff --git a/doc/source/EDBAPI/PadstackEdb.rst b/doc/source/EDBAPI/PadstackEdb.rst deleted file mode 100644 index 9eac145f723..00000000000 --- a/doc/source/EDBAPI/PadstackEdb.rst +++ /dev/null @@ -1,41 +0,0 @@ -vias and padstacks -================== -This section contains API references for padstack management. -The main padstack object is called directly from main application using the property ``padstacks``. - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - edb.padstacks.create_padstack( - padstackname="SVIA", holediam="$via_hole_size", antipaddiam="$antipaddiam", paddiam="$paddiam" - ) - - - ... - - -.. currentmodule:: pyaedt.edb_core.padstack - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - EdbPadstacks - - -Instances and definitions -------------------------- -These classes are the containers of data management for padstacks instances and padstack definitions. - - -.. currentmodule:: pyaedt.edb_core.edb_data.padstacks_data - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - EDBPadProperties - EDBPadstack - EDBPadstackInstance diff --git a/doc/source/EDBAPI/PortsEdb.rst b/doc/source/EDBAPI/PortsEdb.rst deleted file mode 100644 index a2959237a1d..00000000000 --- a/doc/source/EDBAPI/PortsEdb.rst +++ /dev/null @@ -1,23 +0,0 @@ -Ports -===== -These classes are the containers of ports methods of the EDB for both HFSS and Siwave. - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - # this call returns the EDB excitations dictionary - edb.ports - ... - - -.. currentmodule:: pyaedt.edb_core.edb_data.ports - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - GapPort - WavePort diff --git a/doc/source/EDBAPI/PrimitivesEdb.rst b/doc/source/EDBAPI/PrimitivesEdb.rst deleted file mode 100644 index 26b73de557c..00000000000 --- a/doc/source/EDBAPI/PrimitivesEdb.rst +++ /dev/null @@ -1,52 +0,0 @@ -Modeler & primitives -==================== -These classes are the containers of primitives and all relative methods. -Primitives are planes, lines, rectangles, and circles. - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - top_layer_obj = edb.modeler.create_rectangle("TOP", net_name="gnd", - lower_left_point=plane_lw_pt, - upper_right_point=plane_up_pt) - - ... - -.. currentmodule:: pyaedt.edb_core.layout - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - EdbLayout - - -Primitives properties ---------------------- -These classes are the containers of data management for primitives and arcs. - -.. currentmodule:: pyaedt.edb_core.edb_data.primitives_data - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - EDBPrimitives - EDBArcs - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - polygon = edbapp.modeler.polygons[0] - polygon.is_void - poly2 = polygon.clone() - - ... diff --git a/doc/source/EDBAPI/SiWave.rst b/doc/source/EDBAPI/SiWave.rst deleted file mode 100644 index db813f29cb5..00000000000 --- a/doc/source/EDBAPI/SiWave.rst +++ /dev/null @@ -1,30 +0,0 @@ -Siwave manager -============== -`Siwave `_ is a specialized tool -for power integrity, signal integrity, and EMI analysis of IC packages and PCB. This tool -solves power delivery systems and high-speed channels in electronic devices. It can be -accessed from PyAEDT in Windows only. All setups can be implemented through EDB API. - -.. image:: ../Resources/siwave.png - :width: 800 - :alt: Siwave - :target: https://www.ansys.com/products/electronics/ansys-siwave - - -.. currentmodule:: pyaedt - -.. autosummary:: - :toctree: _autosummary - - siwave.Siwave - - -.. code:: python - - from pyaedt.siwave import Siwave - # this call returns the Edb class initialized on 2023 R1 - siwave = Siwave(specified_version="2023.1") - siwave.open_project("pyproject.siw") - siwave.export_element_data("mydata.txt") - siwave.close_project() - ... \ No newline at end of file diff --git a/doc/source/EDBAPI/SimulationConfigurationEdb.rst b/doc/source/EDBAPI/SimulationConfigurationEdb.rst deleted file mode 100644 index 5895facc85f..00000000000 --- a/doc/source/EDBAPI/SimulationConfigurationEdb.rst +++ /dev/null @@ -1,40 +0,0 @@ -Simulation configuration -======================== -These classes are the containers of simulation configuration constructors for the EDB. - - -.. currentmodule:: pyaedt.edb_core.edb_data.simulation_configuration - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - SimulationConfiguration - SimulationConfigurationDc - SimulationConfigurationAc - SimulationConfigurationBatch - - - -.. code:: python - - from pyaedt import Edb - edbapp = Edb(myedb, edbversion="2023.1") - - sim_setup = edbapp.new_simulation_configuration() - sim_setup.solver_type = sim_setup.SOLVER_TYPE.SiwaveSYZ - sim_setup.batch_solve_settings.cutout_subdesign_expansion = 0.01 - sim_setup.batch_solve_settings.do_cutout_subdesign = True - sim_setup.use_default_cutout = False - sim_setup.batch_solve_settings.signal_nets = ["PCIE0_RX0_P", "PCIE0_RX0_N", "PCIE0_TX0_P_C", "PCIE0_TX0_N_C", - "PCIE0_TX0_P", "PCIE0_TX0_N"] - sim_setup.batch_solve_settings.components = ["U2A5", "J2L1"] - sim_setup.batch_solve_settings.power_nets = ["GND"] - sim_setup.ac_settings.start_freq = "100Hz" - sim_setup.ac_settings.stop_freq = "6GHz" - sim_setup.ac_settings.step_freq = "10MHz" - - sim_setup.export_json(os.path.join(project_path, "configuration.json")) - edbapp.build_simulation_project(sim_setup) - - ... diff --git a/doc/source/EDBAPI/SimulationEdb.rst b/doc/source/EDBAPI/SimulationEdb.rst deleted file mode 100644 index 48d3d97158a..00000000000 --- a/doc/source/EDBAPI/SimulationEdb.rst +++ /dev/null @@ -1,43 +0,0 @@ -Simulation setups -================= -These classes are the containers of ``setup`` classes in EDB for both HFSS and Siwave. - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - # this call create a setup and returns the object - setup = edb.create_hfss_setup("my_setup") - setup.set_solution_single_frequency() - setup.hfss_solver_settings.enhanced_low_freq_accuracy = True - setup.hfss_solver_settings.order_basis = "first" - - setup.adaptive_settings.add_adaptive_frequency_data("5GHz", 8, "0.01") - ... - - - -.. currentmodule:: pyaedt.edb_core.edb_data - -.. autosummary:: - :toctree: _autosummary - :nosignatures: - - - hfss_simulation_setup_data.HfssSimulationSetup - hfss_simulation_setup_data.EdbFrequencySweep - hfss_simulation_setup_data.DcrSettings - hfss_simulation_setup_data.CurveApproxSettings - hfss_simulation_setup_data.AdvancedMeshSettings - hfss_simulation_setup_data.ViaSettings - hfss_simulation_setup_data.DefeatureSettings - hfss_simulation_setup_data.AdaptiveSettings - hfss_simulation_setup_data.AdaptiveFrequencyData - hfss_simulation_setup_data.HfssSolverSettings - hfss_simulation_setup_data.HfssPortSettings - hfss_simulation_setup_data.MeshOperationLength - hfss_simulation_setup_data.MeshOperationSkinDepth - siwave_simulation_setup_data.SiwaveSYZSimulationSetup - siwave_simulation_setup_data.SiwaveDCSimulationSetup diff --git a/doc/source/EDBAPI/SourcesEdb.rst b/doc/source/EDBAPI/SourcesEdb.rst deleted file mode 100644 index 59302352af3..00000000000 --- a/doc/source/EDBAPI/SourcesEdb.rst +++ /dev/null @@ -1,20 +0,0 @@ -Sources and excitations -======================= -These classes are the containers of sources methods of the EDB for both HFSS and Siwave. - - -.. code:: python - - from pyaedt import Edb - edb = Edb(myedb, edbversion="2023.1") - - # this call returns the EDB excitations dictionary - edb.excitations - ... - - -.. currentmodule:: pyaedt.edb_core.edb_data.sources - -.. autosummary:: - :toctree: _autosummary - :nosignatures: diff --git a/doc/source/EDBAPI/index.rst b/doc/source/EDBAPI/index.rst deleted file mode 100644 index 14e1abb5707..00000000000 --- a/doc/source/EDBAPI/index.rst +++ /dev/null @@ -1,43 +0,0 @@ -======= -EDB API -======= - -This section describes PyAEDT EDB classes, methods, and functions -for EDB apps and modules. Use the search feature or click links -to view API documentation. - -The PyAEDT EDB API includes classes for apps and modules. You must initialize the -EDB class to get access to all modules and methods. -All other classes and methods are inherited into the app class. -If EDB is launched within the ``HfssdLayout`` class, EDB is accessible in read-only mode. - - -.. image:: ../Resources/edb_intro.png - :width: 800 - :alt: EDB Applications - :target: https://www.ansys.com/applications/pcbs-ics-ic-packages - - -Example: - -.. code:: python - - from pyaedt import Edb - edb = Edb("my_project.aedb", edbversion="2023.1") - edb.core_components.components["R1"].r_value = 40 - edb.close_edb() - - -.. toctree:: - :maxdepth: 2 - - CoreEdb - LayerData - PrimitivesEdb - ComponentsEdb - NetsEdb - PadstackEdb - SourcesEdb - SimulationEdb - SimulationConfigurationEdb - SiWave \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst index 4414d182e69..d8c107c5a17 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -11,24 +11,6 @@ enabling straightforward and efficient automation in your workflow. .. grid:: 2 - .. grid-item-card:: - :img-top: _static/assets/index_getting_started.png - - Getting started - ^^^^^^^^^^^^^^^ - - New to PyAEDT? This section provides the information that you need to get started with PyAEDT. - - +++ - - .. button-link:: Getting_started/index.html - :color: secondary - :expand: - :outline: - :click-parent: - - Getting started - .. grid-item-card:: :img-top: _static/assets/index_user_guide.png @@ -46,46 +28,6 @@ enabling straightforward and efficient automation in your workflow. User guide - - -.. grid:: 2 - - .. grid-item-card:: - :img-top: _static/assets/index_api.png - - AEDT API reference - ^^^^^^^^^^^^^^^^^^ - - The PyAEDT API reference contains descriptions of the functions and modules included in PyAEDT. - It describes how the methods work and the parameter that can be used. - - +++ - .. button-link:: API/index.html - :color: secondary - :expand: - :outline: - :click-parent: - - AEDT API reference - - .. grid-item-card:: - :img-top: _static/assets/index_api.png - - EDB API reference - ^^^^^^^^^^^^^^^^^ - - The PyAEDT EDB API reference contains descriptions of the functions and modules included in PyAEDT. - It describes how the methods work and the parameter that can be used. - - +++ - .. button-link:: EDBAPI/index.html - :color: secondary - :expand: - :outline: - :click-parent: - - EDB API reference - .. jinja:: main_toctree .. grid:: 2 @@ -139,10 +81,7 @@ Indices and tables .. toctree:: :hidden: - Getting_started/index User_guide/index - API/index - EDBAPI/index {% if run_examples %} examples/index {% endif %} From 742b64f17b07b17e1d9e5d4a2a995bc51a680b4d Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 15:07:13 +0100 Subject: [PATCH 12/56] FIX: rename and refact example index --- examples/{Readme.txt => index.rst} | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) rename examples/{Readme.txt => index.rst} (77%) diff --git a/examples/Readme.txt b/examples/index.rst similarity index 77% rename from examples/Readme.txt rename to examples/index.rst index 088894ba498..c67716ec568 100644 --- a/examples/Readme.txt +++ b/examples/index.rst @@ -1,4 +1,10 @@ -.. _ref_example_gallery: +.. _ref_examples: + +.. toctree:: + :hidden: + :maxdepth: 2 + + 00-EDB/index Examples ======== From 2cb0099043bbfbdf19702010b9580c3a1aa4d4a5 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 16:10:13 +0100 Subject: [PATCH 13/56] FIX: examples removal on build error --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index e09ba8391ca..76a54754ef2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -117,7 +117,7 @@ def remove_examples(app, exception): DESTINATION_DIRECTORY = pathlib.Path(app.srcdir) / "examples" size = directory_size(DESTINATION_DIRECTORY) logger.info(f"Removing directory {DESTINATION_DIRECTORY} ({size} MB).") - shutil.rmtree(DESTINATION_DIRECTORY) + shutil.rmtree(DESTINATION_DIRECTORY, ignore_errors=True) logger.info(f"Directory removed.") def add_ipython_time(app, docname, source): From 15ce0d8704307de1d32aa04e6af9d908345a702f Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 16:16:07 +0100 Subject: [PATCH 14/56] FEAT: add example documentation build check Note: changes consist in detecting an error in the html pages context while not stopping the overall build process (for debugging purposes). However, a check is performed at the end of the build process and if an error was detected, the documentation build will fail. Warning: if this way of working changes, one would want to change `nbsphinx_allow_errors` value and the content of the PR changes. --- doc/source/conf.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 76a54754ef2..125d3f82510 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -201,6 +201,22 @@ def remove_ipython_time_from_html(app, pagename, templatename, context, doctree) pattern = r'%%time<\/span>\n' context['body'] = re.sub(pattern, '', context['body']) +def check_example_error(app, pagename, templatename, context, doctree): + """Log an error if the execution of an example as a notebook triggered an error. + + Since the documentation build might not stop if the execution of a notebook triggered + an error, we use a flag to log that an error is spotted in the html page context. + """ + # Check if the HTML contains an error message + if pagename.startswith("examples") and not pagename.endswith("/index"): + if any(map(lambda msg: msg in context['body'], ['UsageError', 'NameError'])): + logger.error(f"An error was detected in file {pagename}") + app.builder.config.html_context['build_error'] = True + +def check_build_finished_without_error(app, exception): + """Check that no error is detected along the documentation build process.""" + if app.builder.config.html_context.get('build_error', False): + raise Exception('Build failed due to error in html-page-context') def setup(app): app.add_directive('pprint', PrettyPrintDirective) @@ -209,8 +225,10 @@ def setup(app): app.connect('source-read', add_ipython_time) app.connect('source-read', adjust_image_path) app.connect('html-page-context', remove_ipython_time_from_html) + app.connect('html-page-context', check_example_error) app.connect('build-finished', remove_examples) app.connect('build-finished', remove_doctree) + app.connect('build-finished', check_build_finished_without_error) local_path = os.path.dirname(os.path.realpath(__file__)) module_path = pathlib.Path(local_path) From 3b5532fde3c22ba5166bf06547db68f59960a365 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 30 Jan 2024 16:16:40 +0100 Subject: [PATCH 15/56] DOC: fix code-block format --- doc/source/User_guide/pyaedt_file_data/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/User_guide/pyaedt_file_data/project.rst b/doc/source/User_guide/pyaedt_file_data/project.rst index d9b965f0de1..9f716d74373 100644 --- a/doc/source/User_guide/pyaedt_file_data/project.rst +++ b/doc/source/User_guide/pyaedt_file_data/project.rst @@ -33,7 +33,7 @@ File structure examples: :download:`HFSS 3D Layout Example <../../Resources/hfss3dlayout_project_example.json>` -.. code-block:: json +.. code-block:: { "general": { From e59503f658279c41c4b40b96f12ad43b8167ccd3 Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:45:19 +0100 Subject: [PATCH 16/56] Update following code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jorge Martínez <28702884+jorgepiloto@users.noreply.github.com> --- .github/workflows/build_documentation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 7376dd693e0..2ea05d93373 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -74,8 +74,7 @@ jobs: - name: Install doc build requirements run: | - sudo apt install graphviz - sudo apt install -y pandoc + sudo apt install -y graphviz pandoc # run doc build, without creating the examples directory # note that we have to add the examples file here since it won't From 5f3f6562b7bbc1d67507fa71815149410784d2fa Mon Sep 17 00:00:00 2001 From: Devin Date: Tue, 30 Jan 2024 15:51:52 -0600 Subject: [PATCH 17/56] Convert to md format 00-EDB and 01-HFSS3DLayout --- examples/00-EDB/01_edb_example.py | 46 +++++--------- examples/00-EDB/02_edb_to_ipc2581.py | 19 ++---- .../03_5G_antenna_example_parametrics.py | 52 ++++------------ examples/00-EDB/04_edb_parametrized_design.py | 49 ++++++--------- examples/00-EDB/05_Plot_nets.py | 26 +++----- examples/00-EDB/06_Advanced_EDB.py | 24 ++------ examples/00-EDB/09_Configuration.py | 50 ++++++--------- examples/00-EDB/10_GDS_workflow.py | 20 ++---- .../00-EDB/11_post_layout_parameterization.py | 28 +++------ .../00-EDB/12_edb_sma_connector_on_board.py | 41 +++---------- examples/00-EDB/13_edb_create_component.py | 52 ++++++---------- .../14_edb_create_parametrized_design.py | 29 ++++----- examples/00-EDB/15_ac_analysis.py | 38 ++++-------- examples/01-HFSS3DLayout/Dcir_in_3DLayout.py | 38 ++++-------- examples/01-HFSS3DLayout/EDB_in_3DLayout.py | 26 ++------ examples/01-HFSS3DLayout/HFSS3DLayout_Via.py | 26 +++----- examples/01-HFSS3DLayout/Hfss3DComponent.py | 61 ++++++------------- 17 files changed, 192 insertions(+), 433 deletions(-) diff --git a/examples/00-EDB/01_edb_example.py b/examples/00-EDB/01_edb_example.py index 361b69684fe..c4acd10d78b 100644 --- a/examples/00-EDB/01_edb_example.py +++ b/examples/00-EDB/01_edb_example.py @@ -1,10 +1,7 @@ -""" -# EDB: SIwave DC-IR Analysis - -This example demonstrates the use of EDB to interact with a PCB -layout and run DC-IR analysis in SIwave. -""" -############################################################################### +# # EDB: SIwave DC-IR Analysis +# +# This example demonstrates the use of EDB to interact with a PCB +# layout and run DC-IR analysis in SIwave. # Perform required imports import os @@ -20,28 +17,27 @@ print(targetfile) aedt_file = targetfile[:-4] + "aedt" -############################################################################### # ## Electronics Database (EDB) # # Instantiate an instance of the `pyaedt.Edb` class # using EDB 2023 R2 and SI units. + edb_version = "2023.2" if os.path.exists(aedt_file): os.remove(aedt_file) edb = pyaedt.Edb(edbpath=targetfile, edbversion=edb_version) -############################################################################### # ## Identify nets and components # # The ``Edb.nets.netlist`` and ``Edb.components.components`` propreties contain information -# about all of the nets and components. The following cell uses this information to print the number of nets and components. +# about all of the nets and components. The following cell uses this information to print the number of nets and +# components. print("Nets {}".format(len(edb.nets.netlist))) start = time.time() print("Components {}".format(len(edb.components.components.keys()))) print("elapsed time = ", time.time() - start) -############################################################################### # ## Identify Pin Positions # # The next section shows how to obtain all pins for a specific component and @@ -58,7 +54,6 @@ pass count += 1 -############################################################################### # Get all nets connected to a specific component. Print # the pin and the name of the net to which it is connected. @@ -76,12 +71,10 @@ print("...and many more.") n_print += 1 -############################################################################### # Compute rats. rats = edb.components.get_rats() -############################################################################### # ## Idenify Connected Nets # # The method ``get_dcconnected_net_list()`` retrieves a list of @@ -95,17 +88,15 @@ for pnets in dc_connected_net_list: print(pnets) -############################################################################### # ## Power Tree # # The power tree provides connectivity through all components from the VRM to -# the device. +# the device. VRM = "U1" OUTPUT_NET = "AVCC_1V3" powertree_df, component_list_columns, net_group = edb.nets.get_powertree(OUTPUT_NET, GROUND_NETS) -############################################################################### # Print some information about the power tree. print_columns = ["refdes", "pin_name", "component_partname"] @@ -113,6 +104,7 @@ # This prints the header. Replace "pin_name" with "pin" to # make the header align with the values. + print("\t".join(print_columns).replace("pin_name", "pin")) for el in powertree_df: @@ -125,7 +117,6 @@ s.rstrip() print(s) -############################################################################### # ## Remove Unused Components # # Delete all RLC components that are connected with only one pin. @@ -135,17 +126,14 @@ edb.components.delete_single_pin_rlc() -############################################################################### # Unused components can also be removed explicitly by name. edb.components.delete("C380") -############################################################################### -# Nets can also be removed explicitly. +# Nets can also be removed explicitly. edb.nets.delete("PDEN") -############################################################################### # Print the top and bottom # elevation of the stackup obtained using # the method ``Edb.stackup.limits()``. @@ -155,7 +143,6 @@ top, top_el, bot, bot_el = edb.stackup.limits() print(s.format(top = top, top_el = top_el*1E3, bot = bot, bot_el = bot_el*1E3)) -############################################################################### # ## Setup for SIwave DCIR analysis # # Create a voltage source and then set up a DCIR analysis. @@ -167,7 +154,6 @@ setup.set_dc_slider = 0 setup.add_source_terminal_to_ground("V1", 1) -############################################################################### # ## Solve # # Save the modifications and run the analysis in SIwave. @@ -177,32 +163,32 @@ siw_file = edb.solve_siwave() -############################################################################### # ## Export Results # -# Export all quantities calculated from the DC-IR analysis. The following method runs SIwave in batch mode from the command line. Results are written to the edb folder. +# Export all quantities calculated from the DC-IR analysis. The following method runs SIwave in batch mode from +# the command line. Results are written to the edb folder. + outputs = edb.export_siwave_dc_results(siw_file, setup.name, ) -############################################################################### # Close the EDB. After EDB is closed, it can be opened by AEDT. edb.close_edb() -############################################################################### # ## View Layout in SIwave # # The SIwave user interface can be visualized and manipulated # using the SIwave user interface. This command works on Window OS only. +# + # siwave = pyaedt.Siwave("2023.2") # siwave.open_project(siwave_file) # report_file = os.path.join(temp_folder,'Ansys.htm') -# + # siwave.export_siwave_report("myDCIR_4", report_file) # siwave.close_project() # siwave.quit_application() +# - -############################################################################### # Clean up the temporary files and directory. temp_dir.cleanup() diff --git a/examples/00-EDB/02_edb_to_ipc2581.py b/examples/00-EDB/02_edb_to_ipc2581.py index 86cf1d622de..823e17e516e 100644 --- a/examples/00-EDB/02_edb_to_ipc2581.py +++ b/examples/00-EDB/02_edb_to_ipc2581.py @@ -1,17 +1,13 @@ -""" -EDB: IPC2581 export -------------------- -This example shows how you can use PyAEDT to export an IPC2581 file. -""" - -############################################################################### +# # EDB: IPC2581 export +# +# This example shows how you can use PyAEDT to export an IPC2581 file. +# # Perform required imports, which includes importing a section. import os import pyaedt import tempfile -############################################################################### # Download the AEDB file and copy it in the temporary folder. temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") @@ -20,7 +16,6 @@ ipc2581_file_name = os.path.join(temp_dir.name, "Ansys_Hsd.xml") print(targetfile) -############################################################################### # ## Launch EDB # # Launch the `pyaedt.Edb` class, using Verson 2023. @@ -28,15 +23,14 @@ edb = pyaedt.Edb(edbpath=targetfile, edbversion="2023.2") -############################################################################### # Parametrize the width of a trace. edb.modeler.parametrize_trace_width( "A0_N", parameter_name=pyaedt.generate_unique_name("Par"), variable_value="0.4321mm" ) -############################################################################### # Create a cutout and plot it. + signal_list = [] for net in edb.nets.netlist: if "PCIe" in net: @@ -52,18 +46,15 @@ ) edb.nets.plot(None, None, color_by_net=True) -############################################################################### # Export the EDB to IPC2581 file. edb.export_to_ipc2581(ipc2581_file, "inch") print("IPC2581 File has been saved to {}".format(ipc2581_file_name)) -############################################################################### # Close EDB edb.close_edb() -############################################################################### # Clean up the temporary directory temp_dir.cleanup() diff --git a/examples/00-EDB/03_5G_antenna_example_parametrics.py b/examples/00-EDB/03_5G_antenna_example_parametrics.py index 35ff065fbf8..81c06f433af 100644 --- a/examples/00-EDB/03_5G_antenna_example_parametrics.py +++ b/examples/00-EDB/03_5G_antenna_example_parametrics.py @@ -1,11 +1,8 @@ -""" -EDB: Layout Components ----------------------- -This example shows how you can use EDB to create a parametric component using - 3D Layout and use it in HFSS 3D. -""" - -############################################################################### +# # EDB: Layout Components +# +# This example shows how you can use EDB to create a parametric component using +# 3D Layout and use it in HFSS 3D. + # ## Perform required imports # # Perform required imports, which includes importing the ``Hfss3dlayout`` object @@ -15,12 +12,10 @@ import pyaedt import os -########################################################## # Set non-graphical mode non_graphical = False -########################################################## # ## Creating data classes # # Data classes are useful to do calculations and store variables. @@ -74,7 +69,6 @@ def points(self): ] -############################################################################### # ## Launch EDB # # PyAEDT.Edb allows to open existing Edb project or create a new empty project. @@ -83,7 +77,6 @@ def points(self): aedb_path = os.path.join(temp_dir.name, "linear_array.aedb") edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") # Create an instance of the Edb class. -############################################################################### # Add stackup layers edb.stackup.add_layer("Virt_GND") @@ -92,10 +85,10 @@ def points(self): edb.stackup.add_layer("Substrat", "GND", layer_type="dielectric", thickness="0.5mm", material="Duroid (tm)") edb.stackup.add_layer("TOP", "Substrat") -############################################################################### # Create the the first patch and feed line using the ``Patch``, ``Line``classes defined above. - +# # Define parameters: + edb["w1"] = 1.4e-3 edb["h1"] = 1.2e-3 edb["initial_position"] = 0.0 @@ -109,7 +102,6 @@ def points(self): first_line = Line(length="l1", width="trace_w", position=first_patch.width) edb.modeler.create_polygon(first_line.points, "TOP", net_name="Array_antenna") -############################################################################### # Now use the ``LinearArray`` class to create the array. edb["w2"] = 2.29e-3 @@ -136,12 +128,10 @@ def points(self): linear_array.length = current_position -############################################################################### # Add the ground conductor. edb.modeler.create_polygon(linear_array.points, "GND", net_name="GND") -############################################################################### # Add connector pin that will be used to assign the port. edb.padstacks.create(padstackname="Connector_pin", holediam="100um", paddiam="0", antipaddiam="200um") @@ -154,7 +144,6 @@ def points(self): via_name="coax", ) -############################################################################### # Add a connector ground. edb.modeler.create_polygon(first_patch.points, "Virt_GND", net_name="GND") @@ -189,32 +178,29 @@ def points(self): net_name="GND", ) - -################################################################################ # Define the port. edb.padstacks.set_solderball(con_pin, "Virt_GND", isTopPlaced=False, ballDiam=0.1e-3) port_name = edb.padstacks.create_coax_port(con_pin) -############################################################################### # Display the model using the ``Edb.nets.plot()`` method. edb.nets.plot() -############################################################################### # The EDB is complete. Now close the EDB and import it into HFSS as a "Layout Component". edb.save_edb() edb.close_edb() print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) -############################################################################### # ## 3D Component in HFSS # # First create an instance of the ``pyaedt.Hfss`` class. If you set # > ``non_graphical = False # -# then AEDT user interface will be visible after the following cell is executed. It is now possible to monitor the progress in the UI as each of the following cells is executed. All commands can be run without the UI by chaning the value of ``non_graphical``. +# then AEDT user interface will be visible after the following cell is executed. It is now possible +# to monitor the progress in the UI as each of the following cells is executed. All commands can be run +# without the UI by chaning the value of ``non_graphical``. h3d = pyaedt.Hfss(projectname="Demo_3DComp", designname="Linear_Array", @@ -227,7 +213,6 @@ def points(self): # Set units to mm. h3d.modeler.model_units = "mm" -############################################################################### # ## Import the EDB as a 3D Component # # One or more layout components can be imported into HFSS. The combination of layout data and 3D CAD data helps streamline @@ -235,7 +220,6 @@ def points(self): component = h3d.modeler.insert_layout_component(aedb_path, parameter_mapping=True) -############################################################################### # ## Expose the Component Paramers # # If a layout component is parametric, parameters can be exposed and changed in HFSS @@ -245,12 +229,12 @@ def points(self): w1_name = "{}_{}".format("w1", h3d.modeler.user_defined_component_names[0]) h3d[w1_name]= 0.0015 -############################################################################### # ### Radiation Boundary Assignment # # The 3D domain includes the air volume surrounding the antenna. This antenna will be simulted from 20 GHz - 50 GHz. # -# A "radiation boundary" will be assigned to the outer boundaries of the domain. This boundary should be roughly one quarter wavelength away from the radiating strucure: +# A "radiation boundary" will be assigned to the outer boundaries of the domain. +# This boundary should be roughly one quarter wavelength away from the radiating strucure: # # $$ \lambda/4 = \frac{c_0}{4 f} \approx 2.8mm $$ @@ -259,7 +243,6 @@ def points(self): h3d.modeler.create_air_region(2.8, 2.8, 2.8, 2.8, 2.8, 2.8, is_percentage=False) h3d.assign_radiation_boundary_to_objects("Region") -############################################################################### # ### Analysis Setup # # The finite element mesh is adapted iteratively. The maximum number of adaptive passes is set using the ``MaximumPasses`` property. This model will converge such that the $S_{11}$ will be independent of the mesh. The default accuracy setting is: @@ -275,12 +258,10 @@ def points(self): sweep1.props["RangeEnd"]="50GHz" sweep1.update() -############################################################################### # Solve the project h3d.analyze() -############################################################################### # ## Plot results outside AEDT # # Plot results using Matplotlib. @@ -290,7 +271,6 @@ def points(self): solution.plot() -################################################################################ # ## Plot Far Fields in AEDT # # Plot Radiation patterns in AEDT. @@ -308,20 +288,15 @@ def points(self): new_report.primary_sweep = "Theta" new_report.create("Realized2D") - -################################################################################ # ## Plot Far Fields in AEDT # # Plot Radiation patterns in AEDT. - - new_report.report_type = "3D Polar Plot" new_report.secondary_sweep = "Phi" new_report.create("Realized3D") -################################################################################ # ## Plot Far Fields outside AEDT # # Plot Radiation patterns outside AEDT. @@ -329,7 +304,6 @@ def points(self): solutions_custom = new_report.get_solution_data() solutions_custom.plot_3d() -################################################################################ # ## Plot E Field on nets and layers # # Plot E Field on nets and layers in AEDT. @@ -341,7 +315,6 @@ def points(self): plot_name="E_Layers", ) -############################################################################### # ## Close AEDT # # After the simulation completes, the application can be released from the @@ -351,7 +324,6 @@ def points(self): h3d.save_project(os.path.join(tmpfold, "test_layout.aedt")) h3d.release_desktop() -############################################################################### # ### Temp Directory Cleanup # # The following command removes the project and the temporary directory. If you'd like to save this project, save it to a folder of your choice prior to running the following cell. diff --git a/examples/00-EDB/04_edb_parametrized_design.py b/examples/00-EDB/04_edb_parametrized_design.py index 9651cd2b356..c902a0c56ce 100644 --- a/examples/00-EDB/04_edb_parametrized_design.py +++ b/examples/00-EDB/04_edb_parametrized_design.py @@ -1,18 +1,19 @@ -""" -# EDB: fully parametrized design - -This example shows how to use the EDB interface along with HFSS 3D Layout to create and solve a parameterized layout. The layout shows a differential via transition on a printed circuit board with back-to-back microstrip to stripline transitions. The model is fully parameterized to enable investigation of the transition performance on the many degrees of freedom. - -The resulting model is shown below +# # EDB: fully parametrized design +# +# This example shows how to use the EDB interface along with HFSS 3D Layout to create and solve a +# parameterized layout. The layout shows a differential via transition on a printed circuit board with +# back-to-back microstrip to stripline transitions. The model is fully parameterized to enable investigation of +# the transition performance on the many degrees of freedom. +# +# The resulting model is shown below +# +# - -""" import pyaedt import os import tempfile -########################################################## # ## Set non-graphical mode # # Set non-graphical mode. The default is ``False`` in order to open @@ -20,14 +21,12 @@ non_graphical = False -########################################################## # Launch EDB. temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") aedb_path = os.path.join(temp_dir.name, "pcb.aedb") edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") -###################################################################### # Define the parameters. params = {"$ms_width": "0.4mm", @@ -47,7 +46,6 @@ for par_name in params: edb.add_project_variable(par_name, params[par_name]) -###################################################################### # Define the stackup layers from bottom to top. layers = [{"name": "bottom", "layer_type": "signal", "thickness": "35um", "material": "copper"}, @@ -70,7 +68,6 @@ material=layer["material"]) prev = layer["name"] -############################################################################### # Create a parametrized padstack for the signal via. signal_via_padstack = "automated_via" @@ -87,13 +84,11 @@ stop_layer=layers[-3]["name"] ) -############################################################################### # Assign net names. There are only two signal nets. net_p = "p" net_n = "n" -############################################################################### # Place the signal vias. edb.padstacks.place( @@ -129,7 +124,6 @@ ) -############################################################################### # ## Draw parametrized traces # # Trace width and the routing (Microstrip-Stripline-Microstrip). @@ -181,7 +175,7 @@ ["$pcb_len", "-($ms_width + $ms_spacing)/2"], ], ] -############################################################################### + # Add traces to the EDB. trace_p = [] @@ -190,7 +184,6 @@ trace_p.append(edb.modeler.create_trace(points_p[n], route_layer[n], width[n], net_p, "Flat", "Flat")) trace_n.append(edb.modeler.create_trace(points_n[n], route_layer[n], width[n], net_n, "Flat", "Flat")) -############################################################################### # Create the wave ports edb.hfss.create_differential_wave_port(trace_p[0].id, ["0.0", "($ms_width+$ms_spacing)/2"], @@ -200,7 +193,6 @@ trace_n[2].id, ["$pcb_len", "-($ms_width + $ms_spacing)/2"], "wave_port_2") -############################################################################### # Draw a conducting rectangle on the the ground layers. gnd_poly = [[0.0, "-$pcb_w/2"], @@ -230,7 +222,7 @@ void_shape = edb.modeler.Shape("polygon", points=void_poly) -# Add ground layers +# Add ground conductors. for layer in layers[:-1:2]: @@ -243,26 +235,22 @@ net_name="gnd") -############################################################################### # Plot the layout. edb.nets.plot(None) -############################################################################### # Save the EDB. edb.save_edb() edb.close_edb() -############################################################################### # Open the project in AEDT 3D Layout. h3d = pyaedt.Hfss3dLayout(projectname=aedb_path, specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Add HFSS simulation setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# # Add HFSS simulation setup +# # Add HFSS simulation setup. setup = h3d.create_setup() @@ -282,28 +270,27 @@ use_q3d_for_dc=False, ) -############################################################################### + # Define the differential pairs to be used to calculte differential and common mode # s-parameters. + h3d.set_differential_pair(diff_name="In", positive_terminal="wave_port_1:T1", negative_terminal="wave_port_1:T2") h3d.set_differential_pair(diff_name="Out", positive_terminal="wave_port_2:T1", negative_terminal="wave_port_2:T2") -############################################################################### # Solve the project. h3d.analyze() -############################################################################### # Plot the results and shut down the Electronics Desktop. solutions = h3d.post.get_solution_data(["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs") solutions.plot() h3d.release_desktop() -############################################################################### # Note that the ground nets are only connected to each other due # to the wave ports. The problem with poor grounding can be seen in the -# S-parameters. This example can be downloaded as a Jupyter Notebook, so you can modify it. Try changing parameters or adding ground vias to improve performance. +# S-parameters. This example can be downloaded as a Jupyter Notebook, so +# you can modify it. Try changing parameters or adding ground vias to improve performance. # # The final cell cleans up the temporary directory, removing all files. diff --git a/examples/00-EDB/05_Plot_nets.py b/examples/00-EDB/05_Plot_nets.py index 5bfe5c2d373..c699e790b20 100644 --- a/examples/00-EDB/05_Plot_nets.py +++ b/examples/00-EDB/05_Plot_nets.py @@ -1,48 +1,41 @@ -""" -# EDB: plot nets with Matplotlib - -This example shows how to use the ``Edb`` class to view nets, layers and -via geometry directly in Python. The methods demonstrated in this example -rely on -[matplotlib](https://matplotlib.org/cheatsheets/_images/cheatsheets-1.png). +# # EDB: plot nets with Matplotlib +# +# This example shows how to use the ``Edb`` class to view nets, layers and +# via geometry directly in Python. The methods demonstrated in this example +# rely on +# [matplotlib](https://matplotlib.org/cheatsheets/_images/cheatsheets-1.png). -## Perform required imports +# ## Perform required imports Perform required imports, which includes importing a section. -""" import os import pyaedt import tempfile -############################################################################### # Download the EDB and copy it into the temporary folder. temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") targetfolder = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) -############################################################################### -# Create an instance of the Electronics Database usig the +# Create an instance of the Electronics Database usig the # `pyaedt.Edb` class. # # > Note that units are SI edb = pyaedt.Edb(edbpath=targetfolder, edbversion="2023.2") -############################################################################### # Display the nets on a layer. Net geometry can be displayed directly in Python usig ``matplotlib`` from # the ``pyaedt.Edb`` class. edb.nets.plot("AVCC_1V3") -############################################################################### # Multiple nets may be viewed by passing a list containing the net # names to the ``plot`` method. edb.nets.plot(["GND", "GND_DP", "AVCC_1V3"], color_by_net=True) -############################################################################### # All copper on a single layer may also be displayed by passing ``None`` # as the first argument. The 2nd argument is a list # of layers to be plotted. In this case, only one @@ -51,19 +44,16 @@ edb.nets.plot(None, ["1_Top"], color_by_net=True, plot_components_on_top=True) -############################################################################### # A side-view of the layers and padstack geometry is displayed using the # ``Edb.stackup.plot()`` method. edb.stackup.plot(scale_elevation=False, plot_definitions=["c100hn140", "c35"]) -############################################################################### # Close the EDB. edb.close_edb() -############################################################################### # Remove all files and the temporary directory. temp_dir.cleanup() diff --git a/examples/00-EDB/06_Advanced_EDB.py b/examples/00-EDB/06_Advanced_EDB.py index fb27d033824..4b5fa9de5e6 100644 --- a/examples/00-EDB/06_Advanced_EDB.py +++ b/examples/00-EDB/06_Advanced_EDB.py @@ -1,23 +1,22 @@ -""" -# EDB: parametric via creation +# # EDB: parametric via creation +# +# This example shows how you can use EDB to create a layout. +# +# First import the required Python packages. -This example shows how you can use EDB to create a layout. -First import the required Python packages. -""" import os import numpy as np import pyaedt import tempfile -############################################################################### # Creat the EDB project. temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") aedb_path = os.path.join(temp_dir.name, "parametric_via.aedb") -############################################################################### + # ## Create stackup # # The ``StackupSimple`` class creates a stackup based on few inputs. This stackup @@ -30,14 +29,12 @@ def create_ground_planes(edb, layers): for i in layers: edb.modeler.create_polygon(plane, i, net_name="GND") -################################################################################## # ## Create the EDB # # Create the Electronics Databas (EDB) instance. If the path doesn't exist, PyAEDT automatically generates a new AEDB folder. edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") -################################################################################## # Insert the stackup layers. layout_count = 12 @@ -56,7 +53,6 @@ def create_ground_planes(edb, layers): dielectric_material=diel_material_name) -################################################################################## # ## Define Parameters # # Define parameters to allow changes in the model dimesons. Parameters preceeded by @@ -72,7 +68,6 @@ def create_ground_planes(edb, layers): edb.add_design_variable("trace_in_width", "0.2mm", is_parameter=True) edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) -################################################################################## # ## Define padstacks # # Create two padstck definitions, one for the ground via and one for the signal via. @@ -86,12 +81,10 @@ def create_ground_planes(edb, layers): ) edb.padstacks.create(padstackname="GVIA", holediam="0.3mm", antipaddiam="0.7mm", paddiam="0.5mm") -################################################################################## # Place the signal via. edb.padstacks.place([0, 0], "SVIA", net_name="RF") -################################################################################## # Place the ground vias. gvia_num_side = gvia_num / 2 @@ -121,7 +114,6 @@ def create_ground_planes(edb, layers): edb.padstacks.place([xloc + "*-1", yloc], "GVIA", net_name="GND") edb.padstacks.place([xloc + "*-1", yloc + "*-1"], "GVIA", net_name="GND") -################################################################################## # Draw the traces edb.modeler.create_trace( @@ -137,7 +129,6 @@ def create_ground_planes(edb, layers): end_cap_style="Flat", ) -################################################################################## # Draw ground conductors ground_layers = [i for i in edb.stackup.signal_layers.keys()] @@ -145,20 +136,17 @@ def create_ground_planes(edb, layers): ground_layers.remove(trace_out_layer) create_ground_planes(edb=edb, layers=ground_layers) -################################################################################## # Display the layout #edb.nets.plot(layers=["TOP", "L10"]) edb.stackup.plot(plot_definitions=["GVIA", "SVIA"]) -################################################################################## # Save EDB and close the EDB. edb.save_edb() edb.close_edb() print("aedb Saved in {}".format(aedb_path)) -############################################################################### # Clean up the temporary directory. temp_dir.cleanup() diff --git a/examples/00-EDB/09_Configuration.py b/examples/00-EDB/09_Configuration.py index e1867117868..1ca84e0eda3 100644 --- a/examples/00-EDB/09_Configuration.py +++ b/examples/00-EDB/09_Configuration.py @@ -1,12 +1,9 @@ -""" -# EDB: Pin to Pin project - -This example demonstrates the use of the Electronics -Database (EDB) interface to create a layout using the BOM and -a configuration file. -""" +# # EDB: Pin to Pin project +# +# This example demonstrates the use of the Electronics +# Database (EDB) interface to create a layout using the BOM and +# a configuration file. -############################################################################### # ## Perform required imports # # The ``Hfss3dlayout`` class provides an interface to @@ -17,7 +14,6 @@ import pyaedt import tempfile -############################################################################### # Download the AEDB file and copy it to a temporary folder. temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") @@ -25,13 +21,12 @@ destination=temp_dir.name) print("Project folder will be", target_aedb) -############################################################################### # ## Launch EDB # # Launch the `pyaedt.Edb` class using EDB 2023 R2. Length units are SI. edbapp = pyaedt.Edb(target_aedb, edbversion="2023.2") -############################################################################### + # ## Import Definitions # # The definition file uses the [json](https://www.json.org/json-en.html) to @@ -77,7 +72,6 @@ edbapp.components.import_definition(os.path.join(target_aedb, "1_comp_definition.json")) -############################################################################### # ## Import BOM # # The bill of materials (BOM) file provides the list of all components @@ -95,7 +89,8 @@ # +------------+-----------------------+-----------+------------+ # ``` # -# Having red the informaton in the BOM and definitions file, electrical models can be assigned to all of the components in the simulation model. +# Having red the informaton in the BOM and definitions file, electrical models can be +# assigned to all of the components in the simulation model. edbapp.components.import_bom(os.path.join(target_aedb, "0_bom.csv"), refdes_col=0, @@ -103,26 +98,20 @@ comp_type_col=2, value_col=3) -############################################################################### -# ## Veriify a Component +# ## Verify a Component # # Component property allows to access all components instances and their property with getters and setters. comp = edbapp.components["C1"] comp.model_type, comp.value -############################################################################### # ## Check Component Definition # # When an s-parameter model is associated to a component it will be available in nport_comp_definition property. edbapp.components.nport_comp_definition - -############################################################################### -# Save the EDB. edbapp.save_edb() -############################################################################### # ## Configure the Simulation Setup # # This step enables the following: @@ -134,6 +123,7 @@ # The method ``Edb.new_simulaton_configuration()`` returns an instance # of the [``SimulationConfiguration``](https://aedt.docs.pyansys.com/version/stable/EDBAPI/SimulationConfigurationEdb.html) class. +# + sim_setup = edbapp.new_simulation_configuration() sim_setup.solver_type = sim_setup.SOLVER_TYPE.SiwaveSYZ sim_setup.batch_solve_settings.cutout_subdesign_expansion = 0.003 @@ -151,8 +141,8 @@ sim_setup.ac_settings.start_freq = "100Hz" sim_setup.ac_settings.stop_freq = "6GHz" sim_setup.ac_settings.step_freq = "10MHz" +# - -############################################################################### # ## Implement the Setup # # The cutout and all other simulation settings are applied to the simulation model. @@ -160,14 +150,12 @@ sim_setup.export_json(os.path.join(temp_dir.name, "configuration.json")) edbapp.build_simulation_project(sim_setup) -############################################################################### # ## Display the Cutout # # Plot cutout once finished. The model is ready to simulate. edbapp.nets.plot(None,None) -############################################################################### # ## Save and Close EDB # # Edb will be saved and re-opened in Electronics @@ -176,43 +164,43 @@ edbapp.save_edb() edbapp.close_edb() -############################################################################### # ## Open Electronics Desktop # # The EDB is opened in AEDT Hfss3DLayout. # # Set ``non_graphical=True`` to run the simulation in non-graphical mode. + h3d = pyaedt.Hfss3dLayout(specified_version="2023.2", projectname=target_aedb, non_graphical=False, new_desktop_session=False) -############################################################################### # ## Analyze # # This project is ready to solve. Executing the following cell runs the HFSS simulatoin on the layout. + h3d.analyze() -############################################################################### # ## View Results # # S-Parameter data will be loaded at the end of simulation. + solutions = h3d.post.get_solution_data() -############################################################################### # ## Plot Results # -# Plot S Parameter data. +# Plot S-Parameter data. + solutions.plot(solutions.expressions, "db20") -############################################################################### # ## Save and Close AEDT # # Hfss3dLayout is saved and closed. + h3d.save_project() h3d.release_desktop() -############################################################################### -# Clean up the temporary directory. All files and the temporary project folder will be deleted in the next step. +# Clean up the temporary directory. All files and the temporary project +# folder will be deleted in the next step. temp_dir.cleanup() diff --git a/examples/00-EDB/10_GDS_workflow.py b/examples/00-EDB/10_GDS_workflow.py index ac4a998aab7..f68320e2930 100644 --- a/examples/00-EDB/10_GDS_workflow.py +++ b/examples/00-EDB/10_GDS_workflow.py @@ -1,11 +1,8 @@ -""" -# EDB: Edit Control File and import gds - -This example demonstrates how to import a gds layout for subsequent -simulation with HFSS. -""" +# # EDB: Edit Control File and import gds +# +# This example demonstrates how to import a gds layout for subsequent +# simulation with HFSS. -############################################################################### # Perform imports. import os @@ -14,7 +11,6 @@ import shutil from pyaedt.edb_core.edb_data.control_file import ControlFile -############################################################################### # ## Fetch Example Data # # Download the EDB folder and copy it to a temporary folder. @@ -39,14 +35,12 @@ "" c = ControlFile(c_file_in, layer_map=c_map) -############################################################################### # ## Simulation setup # # Here we setup simulation with HFSS and add a frequency sweep. setup = c.setups.add_setup("Setup1", "1GHz") setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") -############################################################################### # ## Additional stackup settings # # After import user can change stackup settings and add/remove layers or materials. @@ -58,7 +52,6 @@ via.snap_via_group = True -############################################################################### # ## Boundaries settings # # Boundaries can include ports, components and boundary extent. @@ -75,14 +68,12 @@ comp.add_pin("4", "81.28", "214.6", "met2") c.import_options.import_dummy_nets = True -############################################################################### # ## Write xml # # After all settings are ready we can write xml. c.write_xml(os.path.join(temp_dir.name, "output.xml")) -############################################################################### # ## Open Edb # # Import the gds and open the edb. @@ -92,20 +83,17 @@ edb = Edb(gds_out, edbversion="2023.2", technology_file=os.path.join(temp_dir.name, "output.xml")) -############################################################################### # ## Plot Stackup # # Stackup plot. edb.stackup.plot(first_layer="met1") -############################################################################### # ## Close Edb # # Close the project. edb.close_edb() -############################################################################### # Clean up the temporary folder. temp_dir.cleanup() diff --git a/examples/00-EDB/11_post_layout_parameterization.py b/examples/00-EDB/11_post_layout_parameterization.py index 97d389bdeb6..4918b5ac7ac 100644 --- a/examples/00-EDB/11_post_layout_parameterization.py +++ b/examples/00-EDB/11_post_layout_parameterization.py @@ -1,16 +1,13 @@ -""" -EDB: post-layout parameterization ---------------------------------- -This example shows you how to parameterize the signal net in post-layout. -""" - -############################################################################### +# # EDB: post-layout parameterization +# +# This example shows you how to parameterize the signal net in post-layout. +# # Define input parameters. + signal_net_name = "DDR4_ALERT3" coplanar_plane_net_name = "1V0" # Specify coplanar plane net name for adding clearance layers = ["16_Bottom"] # Specify layers to be parameterized -############################################################################### # Perform required imports. import os import tempfile @@ -18,13 +15,11 @@ temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") -############################################################################### # Download and open example layout file in edb format. edb_path = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) edb = pyaedt.Edb(edb_path, edbversion="2023.2") -############################################################################### # ## Cutout # # The ``Edb.cutout()`` method takes a list of @@ -33,7 +28,6 @@ edb.cutout([signal_net_name], [coplanar_plane_net_name, "GND"], remove_single_pin_components=True) -############################################################################### # Retrive the path segments from the signal net. net = edb.nets[signal_net_name] trace_segments = [] @@ -44,8 +38,8 @@ continue trace_segments.append(p) -############################################################################### # Create and assign delta w variable per layer. + for p in trace_segments: vname = f"{p.net_name}_{p.layer_name}_dw" if vname not in edb.variables: @@ -53,8 +47,8 @@ new_w = f"{p.width}+{vname}" p.width = new_w -############################################################################### # Delete existing clearance. + for p in trace_segments: for g in edb.modeler.get_polygons_by_layer(p.layer_name, coplanar_plane_net_name): @@ -62,8 +56,8 @@ if p.is_intersecting(v): v.delete() -############################################################################### # Create and assign the clearance variable for each layer. + for p in trace_segments: clr = f"{p.net_name}_{p.layer_name}_clr" if clr not in edb.variables: @@ -74,11 +68,10 @@ void = edb.modeler.create_trace(path, p.layer_name, f"{p.width}+{clr}*2") g.add_void(void) -############################################################################### # Visualize the layout. + edb.nets.plot(layers=layers[0], size=2000) -############################################################################### # Save and close the EDB. save_edb_path = os.path.join(temp_dir.name, "post_layout_parameterization.aedb") @@ -86,7 +79,6 @@ print("Edb is saved to ", save_edb_path) edb.close_edb() -############################################################################### # Clean up the temporary folder. -temp_dir.cleanup() +temp_dir.cleanup() \ No newline at end of file diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py index fa419433014..160570c7066 100644 --- a/examples/00-EDB/12_edb_sma_connector_on_board.py +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -1,19 +1,16 @@ -""" -# EDB: geometry creation - -This example shows how to -1. Create a parameterized PCB with an SMA connector footprint for a single-ended - SMA connector launch footprint.. -22 Place 3D component on PCB. -3. Create HFSS setup and frequency sweep with a mesh operation. -4. Create return loss plot -""" -###################################################################### +# # EDB: geometry creation +# +# This example shows how to +# 1. Create a parameterized PCB with an SMA connector footprint for a single-ended +# SMA connector launch footprint.. +# 2. Place 3D component on PCB. +# 3. Create HFSS setup and frequency sweep with a mesh operation. +# 4. Create return loss plot + # ## The Finished Project # # -###################################################################### # ## Create parameterized PCB # # Import dependencies. @@ -23,7 +20,6 @@ import pyaedt import tempfile -############################################################################### # Create the EDB. ansys_version = "2023.2" @@ -34,12 +30,10 @@ edb = pyaedt.Edb(edbpath=aedb_path, edbversion=ansys_version) print("EDB is located at {}".format(aedb_path)) -##################### # Defne the FR4 dielectric for the PCB. edb.materials.add_dielectric_material("ANSYS_FR4", 3.5, 0.005) -############################################################################### # ## Create Stackup # # The stackup is defined explicitly here, but also can be imported @@ -59,7 +53,6 @@ edb.stackup.add_layer("D1", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4") edb.stackup.add_layer("TOP", "Diel", thickness="0.05mm") -###################### # Create ground conductors. edb.add_design_variable("PCB_W", "20mm") @@ -69,7 +62,6 @@ for layer_name in edb.stackup.signal_layers.keys(): gnd_dict[layer_name] = edb.modeler.create_rectangle(layer_name, "GND", [0, "PCB_W/-2"], ["PCB_L", "PCB_W/2"]) -############################################################################### # ## Create signal net # # Create signal net on layer 3, and add clearance to the ground plane. @@ -85,7 +77,6 @@ clr = edb.modeler.create_trace(signal_path, "L3", "SIG_C*2+SIG_W", "SIG", "Flat", "Flat") gnd_dict["L3"].add_void(clr) -#################### # ## Signal Vias # # Create via padstack definition. Place the signal vias. @@ -95,7 +86,6 @@ edb.padstacks.create("ANSYS_VIA", "0.3mm", "0.5mm", "$VIA_AP_D") edb.padstacks.place(["5mm", 0], "ANSYS_VIA", "SIG") -###################################### # Create ground vias around the SMA # connector launch footprint. The vas # are placed around the circumference @@ -107,19 +97,16 @@ py = np.sin(i / 180 * np.pi) edb.padstacks.place(["{}*{}+5mm".format("SG_VIA_D", px), "{}*{}".format("SG_VIA_D", py)], "ANSYS_VIA", "GND") -####################################### # Create ground vias along signal trace. for i in np.arange(2e-3, edb.variables["SIG_L"].value - 2e-3, 2e-3): edb.padstacks.place(["{}+5mm".format(i), "1mm"], "ANSYS_VIA", "GND") edb.padstacks.place(["{}+5mm".format(i), "-1mm"], "ANSYS_VIA", "GND") -################################################### # Create a wave port at the end of the signal trace. signal_trace.create_edge_port("port_1", "End", "Wave", horizontal_extent_factor=10) -############################################################################### # ## HFSS Simulation Setup # # The named argument ``max_num_passes`` sets an upper limit on the @@ -140,12 +127,10 @@ setup.set_solution_single_frequency("5GHz", max_num_passes=8, max_delta_s="0.02") setup.hfss_solver_settings.order_basis = "first" -############################# # Add a mesh operation to the setup. edb.setups["Setup1"].add_length_mesh_operation({"SIG": ["L3"]}, "m1", max_length="0.1mm") -############################## # Add frequency sweep to setup. # # When the simulation results will @@ -165,18 +150,15 @@ ], ) -#################### # Save and close EDB. edb.save_edb() edb.close_edb() -##################### # Launch Hfss3dLayout. h3d = pyaedt.Hfss3dLayout(aedb_path, specified_version=ansys_version, new_desktop_session=True) -#################### # Place a 3D component. full_comp_name = pyaedt.downloads.download_file("component_3d", @@ -187,22 +169,19 @@ placement_layer="TOP", component_name="my_connector", pos_x="5mm", pos_y=0.000) -############################################################################### # ## Run Simulation h3d.analyze(num_cores=4) -######################### # Visualize the return loss. h3d.post.create_report("dB(S(port_1, port_1))") -############################ # Save and close the project. + h3d.save_project() print("Project is saved to {}".format(h3d.project_path)) h3d.release_desktop(True, True) -############################################################################### # Clean up the temporary folder. temp_dir.cleanup() diff --git a/examples/00-EDB/13_edb_create_component.py b/examples/00-EDB/13_edb_create_component.py index e43ca4e3fd3..ecfd5c64f56 100644 --- a/examples/00-EDB/13_edb_create_component.py +++ b/examples/00-EDB/13_edb_create_component.py @@ -1,23 +1,21 @@ -""" -# EDB: Layout Creation and Setup - -This example demonstrates how to to - -1. Create a layout layer stackup. -2. Define padstacks. -3. Place padstack instances in the layout where the connectors are located. -4. Create primitives such as polygons and traces. -5. Create "components" from the padstack definitions using "pins". - >The "component" in EDB acts as a placeholder to enable automatic - >placement of electrical models, or - >as in this example to assign ports. In many - >cases the EDB is imported from a 3rd party layout, in which case the - >concept of a "component" as a placeholder is needed to map - >models to the components on the PCB for later use in the - >simulation. -7. Create the HFSS simulation setup and assign ports where the connectors are located. -""" -###################################################################### +# # EDB: Layout Creation and Setup +# +# This example demonstrates how to to +# +# 1. Create a layout layer stackup. +# 2. Define padstacks. +# 3. Place padstack instances in the layout where the connectors are located. +# 4. Create primitives such as polygons and traces. +# 5. Create "components" from the padstack definitions using "pins". +# >The "component" in EDB acts as a placeholder to enable automatic +# >placement of electrical models, or +# >as in this example to assign ports. In many +# >cases the EDB is imported from a 3rd party layout, in which case the +# >concept of a "component" as a placeholder is needed to map +# >models to the components on the PCB for later use in the +# >simulation. +# 7. Create the HFSS simulation setup and assign ports where the connectors are located. + # ## PCB Trace Model # # Here is an image of the model that will be created in this example. @@ -26,7 +24,6 @@ # # The rectangular sheets at each end of the PCB enable placement of ports where the connectors are located. -###################################################################### # Initialize the EDB layout object. import os @@ -40,7 +37,6 @@ edb = Edb(edbpath=aedb_path, edbversion="2023.2") print("EDB is located at {}".format(aedb_path)) -###################### # Initialize variables layout_count = 12 @@ -55,7 +51,6 @@ connector_size = 2e-3 conectors_position = [[0, 0], [10e-3, 0]] -############################################################################### # Create the stackup edb.stackup.create_symmetric_stackup(layer_count=layout_count, inner_layer_thickness=cond_thickness_inner, @@ -63,7 +58,6 @@ soldermask_thickness=soldermask_thickness, dielectric_thickness=diel_thickness, dielectric_material=diel_material_name) -###################### # Create ground planes ground_layers = [layer_name for layer_name in edb.stackup.signal_layers.keys() if layer_name not in @@ -72,7 +66,6 @@ for i in ground_layers: edb.modeler.create_polygon(plane_shape, i, net_name="VSS") -###################### # ### Design Parameters # # Parameters that are preceeded by a _"$"_ character have project-wide scope. @@ -87,7 +80,6 @@ edb.add_design_variable("trace_in_width", "0.2mm", is_parameter=True) edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) -############################ # ### Create the Connector Component # # The component definition is used to place the connector on the PCB. First define the padstacks. @@ -95,7 +87,6 @@ edb.padstacks.create_padstack(padstackname="Via", holediam="$via_hole_size", antipaddiam="$antipaddiam", paddiam="$paddiam") -#################### # Create the first connector component1_pins = [edb.padstacks.place_padstack(conectors_position[0], "Via", net_name="VDD", fromlayer=trace_in_layer, @@ -113,7 +104,6 @@ conectors_position[0][1] + connector_size / 2], "Via", net_name="VSS")] -#################### # Create the 2nd connector component2_pins = [ @@ -132,7 +122,6 @@ conectors_position[1][1] + connector_size / 2], "Via", net_name="VSS")] -#################### # ### Define "Pins" # # Pins are fist defined to allow a component to subsequently connect to the remainder @@ -141,13 +130,11 @@ for padstack_instance in list(edb.padstacks.instances.values()): padstack_instance.is_pin = True -############################ # Create components from he pins edb.components.create(component1_pins, 'connector_1') edb.components.create(component2_pins, 'connector_2') -################################################################################ # Creating ports on the pins and insert a simulation setup using the ``SimulationConfiguration`` class. sim_setup = edb.new_simulation_configuration() @@ -162,7 +149,6 @@ sim_setup.ac_settings.step_freq = "1GHz" edb.build_simulation_project(sim_setup) -########################### # Save the EDB and open it in the 3D Layout editor. If ``non_graphical==False`` # there may be a delay while AEDT started. @@ -173,7 +159,6 @@ non_graphical=False, # Set non_graphical = False to launch AEDT in graphical mode. new_desktop_session=True) -############################################################################### # ### Release the application from the Python kernel # # It is important to release the application from the Python kernel after @@ -185,7 +170,6 @@ h3d.release_desktop(close_projects=True, close_desktop=True) -############################################################################### # ### Clean up the Temporary Directory # # The following command cleans up the temporary directory, thereby removing all diff --git a/examples/00-EDB/14_edb_create_parametrized_design.py b/examples/00-EDB/14_edb_create_parametrized_design.py index 038321ba16d..b88746b4cb6 100644 --- a/examples/00-EDB/14_edb_create_parametrized_design.py +++ b/examples/00-EDB/14_edb_create_parametrized_design.py @@ -1,24 +1,24 @@ -""" -EDB: parameterized design ------------------------- -This example shows how to -1, Set up an HFSS project using SimulationConfiguration class. -2, Create automatically parametrized design. -""" -###################################################################### +# # EDB: parameterized design +# +# This example shows how to +# 1. Set up an HFSS project using SimulationConfiguration class. +# 2. Create automatically parametrized design. +# # The following layout will be created in this example # # # # -###################################################################### -# Create HFSS simulation project from an existing layout. +# Import dependencies. import os import pyaedt import tempfile +# Create an instance of a pyaedt.Edb object. + +# + temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) print("Project will be located in ", target_aedb) @@ -26,8 +26,8 @@ aedt_version = "2023.2" edb = pyaedt.Edb(edbpath=target_aedb, edbversion=aedt_version) print("EDB is located at {}".format(target_aedb)) +# - -######################################################################## # ### Prepare the Layout for Simulation # # The ``new_simulation_configuration()`` method creates an instance of @@ -46,12 +46,10 @@ simulation_configuration.stop_freq = "20GHz" simulation_configuration.step_freq = "10MHz" -########################## # Now apply the simulation setup to the EDB. edb.build_simulation_project(simulation_configuration) -############################# # ### Parameterization # # The layout can automatically be set up to enable parametric studies. For example, the @@ -61,7 +59,6 @@ edb.save_edb() edb.close_edb() -###################### # ## Open project in AEDT # # All manipulations thus far have been executed using the EDB API which provides fast, streamlined processing of @@ -75,12 +72,12 @@ non_graphical=False, new_desktop_session=True) -############################################################################### # The following cell can be used to ensure that the design is valid for simulation. validation_info = hfss.validate_full_design() is_ready_to_simulate = True +# + for s in validation_info[0]: if "error" in s: print(s) @@ -90,8 +87,8 @@ print("The model is ready for simulation.") else: print("There are errors in the model that need to be fixed.") +# - -############################################################################### # ### Release the application from the Python kernel # # It is important to release the application from the Python kernel after diff --git a/examples/00-EDB/15_ac_analysis.py b/examples/00-EDB/15_ac_analysis.py index ec34e59fe29..81d878d4ddc 100644 --- a/examples/00-EDB/15_ac_analysis.py +++ b/examples/00-EDB/15_ac_analysis.py @@ -1,15 +1,12 @@ -""" -# EDB: Network Analysis in SIwave - -This example shows how to use PyAEDT to set up SYZ analysis on a -[serdes](https://en.wikipedia.org/wiki/SerDes) channel. -The signal input is applied differetially. The positive net is _"PCIe_Gen4_TX3_CAP_P"_. -The negative net is _"PCIe_Gen4_TX3_CAP_N"_. In this example, ports are placed on the -driver and -receiver components. -""" - -############################################################################### +# # EDB: Network Analysis in SIwave +# +# This example shows how to use PyAEDT to set up SYZ analysis on a +# [serdes](https://en.wikipedia.org/wiki/SerDes) channel. +# The signal input is applied differetially. The positive net is _"PCIe_Gen4_TX3_CAP_P"_. +# The negative net is _"PCIe_Gen4_TX3_CAP_N"_. In this example, ports are placed on the +# driver and +# receiver components. + # ### Perform required imports # # Perform required imports, which includes importing a section. @@ -18,7 +15,6 @@ import pyaedt import tempfile -############################################################################### # ### Download file # # Download the AEDB file and copy it in the temporary folder. @@ -29,14 +25,12 @@ print(edb_full_path) -############################################################################### # ### Configure EDB # # Creat an instance of the `pyaedt.Edb` class. edbapp = pyaedt.Edb(edbpath=edb_full_path, edbversion="2023.2") -############################################################################### # ### Generate extended nets # # An extended net is a connection between two nets that are connected @@ -46,9 +40,9 @@ inductor_below=1, capacitor_above=1e-9) -############################################################################### # Review the properties of extended nets. +# + diff_p = edbapp.nets["PCIe_Gen4_TX3_CAP_P"] diff_n = edbapp.nets["PCIe_Gen4_TX3_CAP_N"] @@ -62,8 +56,8 @@ rlc_n = list(diff_n.extended_net.rlc.keys()) print(comp_p, rlc_p, comp_n, rlc_n, sep="\n") +# - -############################################################################### # Prepare input data for port creation. ports = [] @@ -83,7 +77,6 @@ print(*ports, sep="\n") -############################################################################### # ### Create ports # # Solder balls are generated automatically. The default port type is coax port. @@ -97,7 +90,6 @@ port_name=port_name ) -############################################################################### # ### Cutout # # Retain only relevant parts of the layout. @@ -107,7 +99,6 @@ nets.extend(nets_n) edbapp.cutout(signal_list=nets, reference_list=["GND"], extent_type="Bounding") -############################################################################### # Set up the model for network analysis in SIwave. setup = edbapp.create_siwave_syz_setup("setup1") @@ -117,13 +108,11 @@ ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], ]) -############################################################################### # Save and close the EDB. edbapp.save() edbapp.close_edb() -############################################################################### # ### Launch Hfss3dLayout # # The HFSS 3D Layout user inteface in AEDT is used to import the EDB and @@ -135,7 +124,6 @@ non_graphical=False, # Set to true for non-graphical mode. new_desktop_session=True) -############################################################################### # Define the differential pair. h3d.set_differential_pair(positive_terminal="U1_PCIe_Gen4_TX3_CAP_P", @@ -145,24 +133,20 @@ negative_terminal="X1_PCIe_Gen4_TX3_N", diff_name="PAIR_X1") -############################################################################### # Solve and plot the results. h3d.analyze(num_cores=4) -############################################################################### # Visualze the results. h3d.post.create_report("dB(S(PAIR_U1,PAIR_U1))", context="Differential Pairs") -############################################################################### # Close AEDT. h3d.save_project() print("Project is saved to {}".format(h3d.project_path)) h3d.release_desktop(True, True) -############################################################################### # The following cell cleans up the temporary directory and removes all project data. temp_dir.cleanup() diff --git a/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py b/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py index 4b080074210..d45243eb73f 100644 --- a/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py +++ b/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py @@ -1,29 +1,26 @@ -""" -HFSS 3D Layout: SIwave DCIR analysis in HFSS 3D Layout ------------------------------------------------------- -This example shows how to configure a model using the 3D Layout -interface for SIwave DC-IR -analysis. -""" +# # HFSS 3D Layout: SIwave DCIR analysis in HFSS 3D Layout +# +# This example shows how to configure a model using the 3D Layout +# interface for SIwave DC-IR +# analysis. import os import tempfile import pyaedt -############################################################################### + # Copy example into temporary folder + temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") dst_dir = os.path.join(temp_dir.name, "pyaedt_dcir") os.mkdir(dst_dir) local_path = pyaedt.downloads.download_aedb(dst_dir) -##################################################################################### # Load example board into EDB edbversion = "2023.2" appedb = pyaedt.Edb(local_path, edbversion=edbversion) -##################################################################################### # Create pin group on VRM positive pins gnd_name = "GND" @@ -32,7 +29,6 @@ net_name="BST_V3P3_S5", group_name="U3A1-BST_V3P3_S5") -##################################################################################### # Create pin group on VRM negative pins appedb.siwave.create_pin_group_on_net( @@ -40,8 +36,8 @@ net_name="GND", group_name="U3A1-GND") -##################################################################################### # Create voltage source between VRM positive and negative pin groups + appedb.siwave.create_voltage_source_on_pin_group( pos_pin_group_name="U3A1-BST_V3P3_S5", neg_pin_group_name="U3A1-GND", @@ -49,7 +45,6 @@ name="U3A1-BST_V3P3_S5" ) -##################################################################################### # Create pin group on sink component positive pins appedb.siwave.create_pin_group_on_net( @@ -57,7 +52,6 @@ net_name="V3P3_S5", group_name="U2A5-V3P3_S5") -##################################################################################### # Create pin group on sink component negative pins appedb.siwave.create_pin_group_on_net( @@ -65,7 +59,6 @@ net_name="GND", group_name="U2A5-GND") -############################################################################### # Create place current source between sink component positive and negative pin groups. appedb.siwave.create_current_source_on_pin_group( @@ -75,57 +68,50 @@ name="U2A5-V3P3_S5" ) -############################################################################### # Add SIwave DCIR analysis appedb.siwave.add_siwave_dc_analysis(name="my_setup") -############################################################################### # Save and close EDB. appedb.save_edb() appedb.close_edb() -############################################################################### # Launch AEDT and import the configured EDB and analysis DCIR + desktop = pyaedt.Desktop(edbversion, non_graphical=False, new_desktop_session=True) hfss3dl = pyaedt.Hfss3dLayout(local_path) hfss3dl.analyze() hfss3dl.save_project() -############################################################################### # Get loop resistance loop_resistance = hfss3dl.get_dcir_element_data_loop_resistance(setup_name="my_setup") print(loop_resistance) -############################################################################### # Get current source current_source = hfss3dl.get_dcir_element_data_current_source(setup_name="my_setup") print(current_source) -############################################################################### # Get via information via = hfss3dl.get_dcir_element_data_via(setup_name="my_setup") print(via) -############################################################################### # Get voltage from the DC-IR solution data. + voltage = hfss3dl.get_dcir_solution_data( setup_name="my_setup", show="Sources", category="Voltage") print({expression: voltage.data_magnitude(expression) for expression in voltage.expressions}) -############################################################################### # ## Close AEDT + hfss3dl.close_project() desktop.release_desktop() -"" -Clean up the temporary directory. +# Clean up the temporary directory. -"" temp_dir.cleanup() diff --git a/examples/01-HFSS3DLayout/EDB_in_3DLayout.py b/examples/01-HFSS3DLayout/EDB_in_3DLayout.py index 6b29e120a1c..6177aa10fdb 100644 --- a/examples/01-HFSS3DLayout/EDB_in_3DLayout.py +++ b/examples/01-HFSS3DLayout/EDB_in_3DLayout.py @@ -1,9 +1,8 @@ -""" -HFSS 3D Layout: PCB and EDB in 3D layout ----------------------------------------- -This example shows how you can use HFSS 3D Layout combined with EDB to -interact with a 3D layout. -""" +# # HFSS 3D Layout: PCB and EDB in 3D layout +# +# This example shows how you can use HFSS 3D Layout combined with EDB to +# interact with a 3D layout. + import os import tempfile @@ -14,27 +13,23 @@ if not os.path.exists(project_folder): os.makedirs(project_folder) print(project_folder) - -############################################################################### # Copy an example into the temporary project folder. + targetfile = pyaedt.downloads.download_aedb() print(targetfile) aedt_file = targetfile[:-12] + "aedt" -############################################################################### # Set ``non_graphical`` to ``True`` in order to run in non-graphical mode. # The example is currently set up to run in graphical mode. non_graphical = False NewThread = True -############################################################################### # Launch AEDT 2023R2 in graphical mode. Units are SI. desktopVersion = "2023.2" -############################################################################### # ## Initialize AEDT and launch HFSS 3D Layout # # Initialize AEDT and launch HFSS 3D Layout. @@ -46,39 +41,33 @@ h3d = pyaedt.Hfss3dLayout(targetfile) h3d.save_project(os.path.join(project_folder, "edb_demo.aedt")) -############################################################################### # ## Print boundaries # # Print boundaries from the ``setups`` object. h3d.boundaries -############################################################################### # Hide all nets. h3d.modeler.change_net_visibility(visible=False) -############################################################################### # Show only two specified nets. h3d.modeler.change_net_visibility(["A0_GPIO", "A0_MUX"], visible=True) edb = h3d.modeler.edb edb.nets.plot(["A0_GPIO", "A0_MUX"]) -############################################################################### # Show all layers. for layer in h3d.modeler.layers.all_signal_layers: layer.is_visible = True -############################################################################### # Change the layer color. layer = h3d.modeler.layers.layers[h3d.modeler.layers.layer_id("TOP")] layer.set_layer_color(0, 255, 0) h3d.modeler.fit_all() -############################################################################### # ## Disable component visibility # # Disable component visibility for ``"TOP"`` and ``"BOTTOM"`` layers. @@ -91,14 +80,12 @@ bot = h3d.modeler.layers.layers[h3d.modeler.layers.layer_id("BOTTOM")] bot.is_visible_component = False -############################################################################### # ## Display the Layout # # Fit all so that you can visualize all. h3d.modeler.fit_all() -############################################################################### # ## Close AEDT # # After the simulation completes, you can close AEDT or release it using the @@ -108,7 +95,6 @@ h3d.close_project() d.release_desktop() -############################################################################### # Clean up the temporary directory and remove all files. temp_dir.cleanup() diff --git a/examples/01-HFSS3DLayout/HFSS3DLayout_Via.py b/examples/01-HFSS3DLayout/HFSS3DLayout_Via.py index d79b7f7e47e..c0d8f9e5c52 100644 --- a/examples/01-HFSS3DLayout/HFSS3DLayout_Via.py +++ b/examples/01-HFSS3DLayout/HFSS3DLayout_Via.py @@ -1,16 +1,12 @@ -""" -HFSS 3D Layout: parametric via analysis ---------------------------------------- -This example shows how you can use HFSS 3D Layout to create and solve a -parametric via analysis. -""" - -############################################################################### +# # HFSS 3D Layout: parametric via analysis +# +# This example shows how you can use HFSS 3D Layout to create and solve a + # Perform required imports. + import pyaedt import os -############################################################################### # ## Set non-graphical mode # # Set non-graphical mode. @@ -18,12 +14,10 @@ non_graphical = True -############################################################################### # Launch AEDT 2023 R2 in graphical mode. h3d = pyaedt.Hfss3dLayout(specified_version="2023.2", new_desktop_session=True, non_graphical=non_graphical) -############################################################################### # Set up all parametric variables to use in the layout. h3d["viatotrace"] = "5mm" @@ -32,21 +26,18 @@ h3d["sp"] = "0.5mm" h3d["len"] = "50mm" -############################################################################### # Add stackup layers. h3d.modeler.layers.add_layer(layername="GND", layertype="signal", thickness="0", isnegative=True) h3d.modeler.layers.add_layer(layername="diel", layertype="dielectric", thickness="0.2mm", material="FR4_epoxy") h3d.modeler.layers.add_layer(layername="TOP", layertype="signal", thickness="0.035mm", elevation="0.2mm") -############################################################################### # Create a signal net and ground planes. h3d.modeler.create_line(layername="TOP", center_line_list=[[0, 0], ["len", 0]], lw="w1", netname="microstrip", name="microstrip") h3d.modeler.create_rectangle(layername="TOP", origin=[0, "-w1/2-sp"], dimensions=["len", "-w1/2-sp-20mm"]) h3d.modeler.create_rectangle(layername="TOP", origin=[0, "w1/2+sp"], dimensions=["len", "w1/2+sp+20mm"]) -############################################################################### # Create vias with parametric positions. h3d.modeler.create_via(x="viatovia", y="-viatotrace", name="via1") @@ -56,13 +47,11 @@ h3d.modeler.create_via(x="3*viatovia", y="-viatotrace") h3d.modeler.create_via(x="3*viatovia", y="viatotrace") -############################################################################### # Create circuit ports. h3d.create_edge_port("microstrip", 0) h3d.create_edge_port("microstrip", 2) -############################################################################### # Create a setup and a sweep. setup = h3d.create_setup() @@ -80,22 +69,21 @@ use_q3d_for_dc=False, ) -############################################################################### # Solve and plot the results. h3d.analyze() traces = h3d.get_traces_for_plot(first_element_filter="Port1") h3d.post.create_report(traces, variations=h3d.available_variations.nominal_w_values_dict) -############################################################################### # Display the results using [Matplotlib](https://matplotlib.org/stable/users/getting_started/). +# + traces = h3d.get_traces_for_plot(first_element_filter="Port1", category="S") solutions = h3d.post.get_solution_data(expressions=traces) solutions.plot(math_formula="db20") +# - -############################################################################### # ## Close AEDT # # After the simulation completes, you can close AEDT or release it using the diff --git a/examples/01-HFSS3DLayout/Hfss3DComponent.py b/examples/01-HFSS3DLayout/Hfss3DComponent.py index b4ae276ef94..0d532c83c9f 100644 --- a/examples/01-HFSS3DLayout/Hfss3DComponent.py +++ b/examples/01-HFSS3DLayout/Hfss3DComponent.py @@ -1,19 +1,16 @@ -""" -HFSS: 3D Components -------------------- -This example shows how you can use PyAEDT to place 3D Components in Hfss and in Hfss 3D Layout. -""" +# # HFSS: 3D Components +# +# This example shows how you can use PyAEDT to place 3D Components in Hfss and in Hfss 3D Layout. + import os import pyaedt import tempfile -############################################################################### # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### # Set common properties. trace_width = 0.6 @@ -25,14 +22,12 @@ desktop_version = "2023.2" new_session = True -############################################################################### # ## 3D Component Definition # # Download the 3D component file. component3d = pyaedt.downloads.download_file("component_3d", "SMA_RF_Jack.a3dcomp",) -############################################################################### # ## Hfss Example # # This example will create a stackup in Hfss place a 3d component, build a ground plane, a trace, @@ -49,13 +44,11 @@ hfss.solution_type = "Terminal" -############################################################################### # To insert a 3d component we need to read parameters and then import in Hfss. comp_param = hfss.get_components3d_vars(component3d) hfss.modeler.insert_3d_component(component3d, comp_param) -############################################################################### # ## Define the Stackup # # Pyaedt has a Stackup class which allows to parametrize stacked structures. @@ -65,7 +58,6 @@ d1 = stackup.add_dielectric_layer("D1", thickness=diel_height) g1 = stackup.add_ground_layer("G1", thickness=sig_height) -############################################################################### # Define stackup elevation and size. Defines also the stackup origin. stackup.start_position = "-131mil" @@ -74,7 +66,6 @@ stackup.dielectric_y_position = "-dielectric_width/2" stackup.dielectric_x_position = "-dielectric_length/4" -############################################################################### # ## Padstack Definition # # Padstacks are needed to create a clearance around 3d component since @@ -99,7 +90,6 @@ p2.padstacks_by_layer["L1"].pad_radius = 0.3048 p2.add_via(0, 0) -############################################################################### # ## Trace Definition # # The trace will connect the pin to the port on layer L1. @@ -110,7 +100,6 @@ dimension_list=["15*" + t1.width.name, "-3*" + stackup.thickness.name]) p1 = hfss.wave_port(signal=rect1, reference="G1", name="P1") -############################################################################### # ## Set Simulation Boundaries # # Define regione and simulation boundaries. @@ -120,7 +109,6 @@ sheets = [i for i in region.faces] hfss.assign_radiation_boundary_to_faces(sheets) -############################################################################### # ## Create Setup # # Iterations will be reduced to reduce simulation time. @@ -130,7 +118,6 @@ setup1.props["Frequency"] = freq setup1.props["MaximumPasses"] = max_steps -############################################################################### # ## Solve Setup # # Save the project first and then solve the setup. @@ -138,7 +125,6 @@ hfss.save_project() hfss.analyze() -############################################################################### # ## Plot results # # Plot the results when analysis is completed. @@ -147,31 +133,27 @@ solutions = hfss.post.get_solution_data(traces) solutions.plot(traces, math_formula="db20") -############################################################################### -# Hfss 3D Layout Example -# ---------------------- +# ## Hfss 3D Layout Example +# # Previous example will be repeated this time in Hfss 3d Layout. # Small differences are expected in layout but results should be similar. -############################################################################### -# Launch Hfss3dLayout -# ~~~~~~~~~~~~~~~~~~~ +# ## Launch Hfss3dLayout +# # Launch HFSS3dLayout application h3d = pyaedt.Hfss3dLayout() -############################################################################### -# Add stackup layers -# ~~~~~~~~~~~~~~~~~~ +# ## Add stackup layers +# # Add stackup layers. l1 = h3d.modeler.layers.add_layer("L1", "signal", thickness=sig_height) h3d.modeler.layers.add_layer("diel", "dielectric", thickness=diel_height, material="FR4_epoxy") h3d.modeler.layers.add_layer("G1", "signal", thickness=sig_height, isnegative=True) -############################################################################### -# Place 3d Component -# ~~~~~~~~~~~~~~~~~~ +# ## Place 3d Component +# # Place a 3d component by specifying the .a3dcomp file path. comp = h3d.modeler.place_3d_component( @@ -179,9 +161,8 @@ pos_x=0.000, pos_y=0.000 ) -############################################################################### -# Create signal net and ground planes -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create signal net and ground planes +# # Create a signal net and ground planes. h3d["len"] = str(trace_length) + "mm" @@ -189,16 +170,12 @@ line = h3d.modeler.create_line("L1", [[0, 0], ["len", 0]], lw="w1", netname="microstrip", name="microstrip") h3d.create_edge_port(line, h3d.modeler[line].top_edge_x, iswave=True, wave_horizontal_extension=15, ) -############################################################################### # Create void on Ground plane for pin -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a void. h3d.modeler.create_circle("G1", 0, 0, 0.5) -############################################################################### -# Create Setup -# ~~~~~~~~~~~~ +# ## Create Setup +# # Iterations will be reduced to reduce simulation time. h3d.set_meshing_settings(mesh_method="PhiPlus", enable_intersections_check=False) @@ -209,21 +186,17 @@ setup1.props["AdaptiveSettings"]["SingleFrequencyDataList"]["AdaptiveFrequencyData"]["AdaptiveFrequency"] = freq setup1.props["AdaptiveSettings"]["SingleFrequencyDataList"]["AdaptiveFrequencyData"]["MaxPasses"] = max_steps -############################################################################### # Solve Setup h3d.analyze() -############################################################################### # Plot results traces = h3d.get_traces_for_plot(category="S") solutions = h3d.post.get_solution_data(traces) solutions.plot(traces, math_formula="db20") -"" h3d.save_project() h3d.release_desktop() -"" -temp_dir.cleanup() +temp_dir.cleanup() # Remove project folder and files. From ba8447b625a74d77b9a9a0c0a02afe87bbe9ade6 Mon Sep 17 00:00:00 2001 From: Devin Date: Tue, 30 Jan 2024 16:00:47 -0600 Subject: [PATCH 18/56] Convert to md format 01-Modeling-setup --- examples/01-Modeling-Setup/Configurations.py | 92 +++++------ .../HFSS_CoordinateSystem.py | 144 +++++++----------- 2 files changed, 99 insertions(+), 137 deletions(-) diff --git a/examples/01-Modeling-Setup/Configurations.py b/examples/01-Modeling-Setup/Configurations.py index b87f31de41b..624d33b543c 100644 --- a/examples/01-Modeling-Setup/Configurations.py +++ b/examples/01-Modeling-Setup/Configurations.py @@ -1,31 +1,28 @@ -""" -General: configuration files ----------------------------- -This example shows how you can use PyAEDT to export configuration files and re-use -them to import in a new project. A configuration file is supported by these applications: - -* HFSS -* 2D Extractor and Q3D Extractor -* Maxwell -* Icepak (in AEDT) -* Mechanical (in AEDT) - -The following sections are covered: - -* Variables -* Mesh operations (except Icepak) -* Setup and optimetrics -* Material properties -* Object properties -* Boundaries and excitations - -When a boundary is attached to a face, the tool tries to match it with a -``FaceByPosition`` on the same object name on the target design. If, for -any reason, this face position has changed or the object name in the target -design has changed, the boundary fails to apply. -""" +# # General: configuration files +# +# This example shows how you can use PyAEDT to export configuration files and re-use +# them to import in a new project. A configuration file is supported by these applications: +# +# * HFSS +# * 2D Extractor and Q3D Extractor +# * Maxwell +# * Icepak (in AEDT) +# * Mechanical (in AEDT) +# +# The following sections are covered: +# +# * Variables +# * Mesh operations (except Icepak) +# * Setup and optimetrics +# * Material properties +# * Object properties +# * Boundaries and excitations +# +# When a boundary is attached to a face, the tool tries to match it with a +# ``FaceByPosition`` on the same object name on the target design. If, for +# any reason, this face position has changed or the object name in the target +# design has changed, the boundary fails to apply. -############################################################################### # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports from PyAEDT. @@ -33,7 +30,6 @@ import os import pyaedt -############################################################################### # Set non-graphical mode # ~~~~~~~~~~~~~~~~~~~~~~ # Set non-graphical mode. @@ -41,7 +37,6 @@ non_graphical = False -############################################################################### # Open project # ~~~~~~~~~~~~ # Download the project, open it, and save it to the temporary folder. @@ -52,26 +47,23 @@ new_desktop_session=True, non_graphical=non_graphical) ipk.autosave_disable() -############################################################################### -# Create source blocks -# ~~~~~~~~~~~~~~~~~~~~ +# ## Create source blocks +# # Create a source block on the CPU and memories. ipk.create_source_block(object_name="CPU", input_power="25W") ipk.create_source_block(object_name=["MEMORY1", "MEMORY1_1"], input_power="5W") -############################################################################### -# Assign boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Assign boundaries +# # Assign the opening and grille. region = ipk.modeler["Region"] ipk.assign_openings(air_faces=region.bottom_face_x.id) ipk.assign_grille(air_faces=region.top_face_x.id, free_area_ratio=0.8) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create the setup. Properties can be set up from the ``setup`` object # with getters and setters. They don't have to perfectly match the property # syntax. @@ -83,9 +75,8 @@ setup1["Solver Type Temperature"] = "flex" ipk.save_project(r"C:\temp\Graphic_card.aedt") -############################################################################### -# Export project to step file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Export project to step file +# # Export the current project to the step file. filename = ipk.design_name @@ -94,34 +85,31 @@ removed_objects=[]) ############################################################################### -# Export configuration files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Export configuration files +# # Export the configuration files. You can optionally disable the export and # import sections. conf_file = ipk.configurations.export_config() ipk.close_project() -############################################################################### -# Create project -# ~~~~~~~~~~~~~~ +# ## Create project +# # Create an Icepak project and import the step. app = pyaedt.Icepak(projectname="new_proj_Ipk") app.modeler.import_3d_cad(file_path) -############################################################################### -# Import and apply configuration file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Import and apply configuration file +# # Import and apply the configuration file. You can apply all or part of the # JSON file that you import using options in the ``configurations`` object. out = app.configurations.import_config(conf_file) app.configurations.results.global_import_success -############################################################################### -# Close project -# ~~~~~~~~~~~~~ +# ## Close project +# # Close the project. app.release_desktop() diff --git a/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py b/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py index 29bb7e6abaf..812e8a2a4b5 100644 --- a/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py +++ b/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py @@ -1,18 +1,15 @@ -""" -General: coordinate system creation ------------------------------------ -This example shows how you can use PyAEDT to create and modify coordinate systems in the modeler. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # General: coordinate system creation +# +# This example shows how you can use PyAEDT to create and modify coordinate systems in the modeler. +# +# ## Perform required imports +# # Perform required imports import os import pyaedt -############################################################################### # Set non-graphical mode # ~~~~~~~~~~~~~~~~~~~~~~ # Set non-graphical mode. @@ -20,32 +17,28 @@ non_graphical = False -############################################################################### -# Launch AEDT in graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT in graphical mode +# # Launch AEDT 2023 R2 in graphical mode. d = pyaedt.launch_desktop(specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Insert HFSS design -# ~~~~~~~~~~~~~~~~~~ +# ## Insert HFSS design +# # Insert an HFSS design with the default name. hfss = pyaedt.Hfss(projectname=pyaedt.generate_unique_project_name(folder_name="CoordSysDemo")) -############################################################################### -# Create coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system +# # The coordinate system is centered on the global origin and has the axis # aligned to the global coordinate system. The new coordinate system is # saved in the object ``cs1``. cs1 = hfss.modeler.create_coordinate_system() -############################################################################### -# Modify coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Modify coordinate system +# # The ``cs1`` object exposes properties and methods to manipulate the # coordinate system. The origin can be changed. @@ -60,16 +53,14 @@ cs1.props["YAxisYvec"] = ypoint[1] cs1.props["YAxisZvec"] = ypoint[2] -############################################################################### -# Rename coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Rename coordinate system +# # Rename the coordinate system. cs1.rename("newCS") -############################################################################### -# Change coordinate system mode -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Change coordinate system mode +# # Use the ``change_cs_mode`` method to change the mode. Options are ``0`` # for axis/position, ``1`` for Euler angle ZXZ, and ``2`` for Euler angle ZYZ. # Here ``1`` sets Euler angle ZXZ as the mode. @@ -77,20 +68,19 @@ cs1.change_cs_mode(1) # In the new mode, these properties can be edited + cs1.props["Phi"] = "10deg" cs1.props["Theta"] = "22deg" cs1.props["Psi"] = "30deg" -############################################################################### -# Delete coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Delete coordinate system +# # Delete the coordinate system. cs1.delete() -############################################################################### -# Create coordinate system by defining axes -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system by defining axes +# # Create a coordinate system by defining the axes. During creation, you can # specify all coordinate system properties. @@ -98,34 +88,30 @@ name="CS2", origin=[1, 2, 3.5], mode="axis", x_pointing=[1, 0, 1], y_pointing=[0, -1, 0] ) -############################################################################### -# Create coordinate system by defining Euler angles -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system by defining Euler angles +# # Create a coordinate system by defining Euler angles. cs3 = hfss.modeler.create_coordinate_system(name="CS3", origin=[2, 2, 2], mode="zyz", phi=10, theta=20, psi=30) -############################################################################### -# Create coordinate system by defining view -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system by defining view +# # Create a coordinate system by defining the view. Options are ``"iso"``, # ``"XY"``, ``"XZ"``, and ``"XY"``. Here ``"iso"`` is specified. # The axes are set automatically. cs4 = hfss.modeler.create_coordinate_system(name="CS4", origin=[1, 0, 0], reference_cs="CS3", mode="view", view="iso") -############################################################################### -# Create coordinate system by defining axis and angle rotation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system by defining axis and angle rotation +# # Create a coordinate system by defining the axis and angle rotation. When you # specify the axis and angle rotation, this data is automatically translated # to Euler angles. cs5 = hfss.modeler.create_coordinate_system(name="CS5", mode="axisrotation", u=[1, 0, 0], theta=123) -############################################################################### -# Create face coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create face coordinate system +# # Face coordinate systems are bound to an object face. # First create a box and then define the face coordinate system on one of its # faces. To create the reference face for the face coordinate system, you must @@ -137,9 +123,8 @@ face=face, origin=face.edges[0], axis_position=face.edges[1], name="FCS1" ) -############################################################################### -# Create face coordinate system centered on face -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create face coordinate system centered on face +# # Create a face coordinate system centered on the face with the X axis pointing # to the edge vertex. @@ -147,9 +132,8 @@ face=face, origin=face, axis_position=face.edges[0].vertices[0], name="FCS2" ) -############################################################################### -# Swap X and Y axes of face coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Swap X and Y axes of face coordinate system +# # Swap the X axis and Y axis of the face coordinate system. The X axis is the # pointing ``axis_position`` by default. You can optionally select the Y axis. @@ -158,9 +142,9 @@ # Axis can also be changed after coordinate system creation fcs3.props["WhichAxis"] = "X" -############################################################################### -# Apply a rotation around Z axis -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Apply a rotation around Z axis +# # Apply a rotation around the Z axis. The Z axis of a face coordinate system # is always orthogonal to the face. A rotation can be applied at definition. # Rotation is expressed in degrees. @@ -170,9 +154,8 @@ # Rotation can also be changed after coordinate system creation fcs4.props["ZRotationAngle"] = "3deg" -############################################################################### -# Apply offset to X and Y axes of face coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Apply offset to X and Y axes of face coordinate system +# # Apply an offset to the X axis and Y axis of a face coordinate system. # The offset is in respect to the face coordinate system itself. @@ -184,9 +167,8 @@ fcs5.props["XOffset"] = "0.2mm" fcs5.props["YOffset"] = "0.1mm" -############################################################################### -# Create coordinate system relative to face coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system relative to face coordinate system +# # Create a coordinate system relative to a face coordinate system. Coordinate # systems and face coordinate systems interact with each other. @@ -196,9 +178,8 @@ name="CS_FCS", origin=[0, 0, 0], reference_cs=fcs6.name, mode="view", view="iso" ) -############################################################################### -# Create object coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object coordinate system +# # Create object coordinate system with origin on face obj_cs = hfss.modeler.create_object_coordinate_system( @@ -206,9 +187,8 @@ ) obj_cs.rename("new_obj_cs") -############################################################################### -# Create object coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object coordinate system +# # Create object coordinate system with origin on edge obj_cs_1 = hfss.modeler.create_object_coordinate_system( @@ -216,9 +196,8 @@ ) obj_cs_1.set_as_working_cs() -############################################################################### -# Create object coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object coordinate system +# # Create object coordinate system with origin specified on point obj_cs_2 = hfss.modeler.create_object_coordinate_system( @@ -227,9 +206,8 @@ new_obj_cs_2 = hfss.modeler.duplicate_coordinate_system_to_global(obj_cs_2) obj_cs_2.delete() -############################################################################### -# Create object coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object coordinate system +# # Create object coordinate system with origin on vertex obj_cs_3 = hfss.modeler.create_object_coordinate_system( @@ -238,27 +216,24 @@ obj_cs_3.props["MoveToEnd"] = False obj_cs_3.update() -############################################################################### -# Get all coordinate systems -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get ## all coordinate systems +# # Get all coordinate systems. css = hfss.modeler.coordinate_systems names = [i.name for i in css] print(names) -############################################################################### -# Select coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select coordinate system +# # Select an existing coordinate system. css = hfss.modeler.coordinate_systems cs_selected = css[0] cs_selected.delete() -############################################################################### -# Get point coordinate under another coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get point coordinate under another coordinate system +# # Get a point coordinate under another coordinate system. A point coordinate # can be translated in respect to any coordinate system. @@ -268,11 +243,10 @@ p2 = hfss.modeler.global_to_cs(p, "CS5") print("CS5 :", p2) -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulaton completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.release_desktop` method. +# `pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing. d.release_desktop() From 3c688889c66ba9e31a7630cc5a72f3c1135b22b9 Mon Sep 17 00:00:00 2001 From: Devin Date: Tue, 30 Jan 2024 18:20:59 -0600 Subject: [PATCH 19/56] Convert to md format 01-Modeling-setup --- examples/01-Modeling-Setup/Optimetrics.py | 72 +++++-------- .../01-Modeling-Setup/Polyline_Primitives.py | 102 +++++++----------- 2 files changed, 64 insertions(+), 110 deletions(-) diff --git a/examples/01-Modeling-Setup/Optimetrics.py b/examples/01-Modeling-Setup/Optimetrics.py index 6d30d4c5d74..b750e5f6d21 100644 --- a/examples/01-Modeling-Setup/Optimetrics.py +++ b/examples/01-Modeling-Setup/Optimetrics.py @@ -1,29 +1,25 @@ -""" -General: optimetrics setup --------------------------- -This example shows how you can use PyAEDT to create a project in HFSS and create all optimetrics setups. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # General: optimetrics setup +# +# This example shows how you can use PyAEDT to create a project in HFSS and create all optimetrics setups. + + +# ## Perform required imports +# # Perform required imports. import pyaedt import os -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Initialize object and create variables -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize object and create variables +# # Initialize the ``Hfss`` object and create two needed design variables, # ``w1`` and ``w2``. @@ -31,9 +27,8 @@ hfss["w1"] = "1mm" hfss["w2"] = "100mm" -############################################################################### -# Create waveguide with sheets on it -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create waveguide with sheets on it +# # Create one of the standard waveguide structures and parametrize it. # You can also create rectangles of waveguide openings and assign ports later. @@ -51,17 +46,13 @@ model.show_grid = False model.plot(os.path.join(hfss.working_directory, "Image.jpg")) -############################################################################### -# Create wave ports on sheets -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create two wave ports on the sheets. hfss.wave_port(p1, integration_line=hfss.AxisDir.ZPos, name="1") hfss.wave_port(p2, integration_line=hfss.AxisDir.ZPos, name="2") -############################################################################### -# Create setup and frequency sweep -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and frequency sweep +# # Create a setup and a frequency sweep to use as the base for optimetrics # setups. @@ -70,11 +61,10 @@ setupname=setup.name, unit="GHz", freqstart=1, freqstop=5, step_size=0.1, sweepname="Sweep1", save_fields=True ) -############################################################################### -# Optimetrics analysis -# ---------------------- -# Create parametrics analysis -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Optimetrics analysis +# +# ### Parametric Analysis +# # Create a simple optimetrics parametrics analysis with output calculations. sweep = hfss.parametrics.add("w2", 90, 200, 5) @@ -82,18 +72,12 @@ sweep.add_calculation(calculation="dB(S(1,1))", ranges={"Freq": "2.5GHz"}) sweep.add_calculation(calculation="dB(S(1,1))", ranges={"Freq": "2.6GHz"}) -############################################################################### -# Create sensitivity analysis -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create an optimetrics sensitivity analysis with output calculations. sweep2 = hfss.optimizations.add(calculation="dB(S(1,1))", ranges={"Freq": "2.5GHz"}, optim_type="Sensitivity") sweep2.add_variation("w1", 0.1, 3, 0.5) sweep2.add_calculation(calculation="dB(S(1,1))", ranges={"Freq": "2.6GHz"}) -############################################################################### -# Create optimization based on goals and calculations -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create an optimization analysis based on goals and calculations. sweep3 = hfss.optimizations.add(calculation="dB(S(1,1))", ranges={"Freq": "2.5GHz"}) @@ -106,24 +90,19 @@ condition="Maximize", ) -############################################################################### -# Create DX optimization based on a goal and calculation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create a DX (DesignXplorer) optimization based on a goal and a calculation. sweep4 = hfss.optimizations.add(calculation="dB(S(1,1))", ranges={"Freq": "2.5GHz"}, optim_type="DesignExplorer") sweep4.add_goal(calculation="dB(S(1,1))", ranges={"Freq": "2.6GHz"}) -############################################################################### -# Create DOE based on a goal and calculation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create DOE based on a goal and calculation +# # Create a DOE (Design of Experiments) based on a goal and a calculation. sweep5 = hfss.optimizations.add(calculation="dB(S(1,1))", ranges={"Freq": "2.5GHz"}, optim_type="DXDOE") -############################################################################### -# Create DOE based on a goal and calculation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create DOE based on a goal and calculation +# # Create a DOE based on a goal and a calculation. region = hfss.modeler.create_region() @@ -136,9 +115,8 @@ context="Infinite_1", ) -############################################################################### -# Close AEDT -# ---------- +# ## Close AEDT +# # After the simulaton completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/01-Modeling-Setup/Polyline_Primitives.py b/examples/01-Modeling-Setup/Polyline_Primitives.py index c3c3a35c984..3894be107c3 100644 --- a/examples/01-Modeling-Setup/Polyline_Primitives.py +++ b/examples/01-Modeling-Setup/Polyline_Primitives.py @@ -1,27 +1,24 @@ -""" -General: polyline creation --------------------------- -This example shows how you can use PyAEDT to create and manipulate polylines. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # General: polyline creation +# +# This example shows how you can use PyAEDT to create and manipulate polylines. + + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### # Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Create Maxwell 3D object -# ~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Create Maxwell 3D object +# # Create a :class:`pyaedt.maxwell.Maxwell3d` object and set the unit type to ``"mm"``. M3D = pyaedt.Maxwell3d(solution_type="Transient", designname="test_polyline_3D", specified_version="2023.2", @@ -29,30 +26,27 @@ M3D.modeler.model_units = "mm" prim3D = M3D.modeler -############################################################################### -# Define variables -# ~~~~~~~~~~~~~~~~ +# ## Define variables +# # Define two design variables as parameters for the polyline objects. M3D["p1"] = "100mm" M3D["p2"] = "71mm" -############################################################################### -# Input data -# ~~~~~~~~~~ +# ## Input data +# # Input data. All data for the polyline functions can be entered as either floating point # values or strings. Floating point values are assumed to be in model units # (``M3D.modeler.model_units``). test_points = [["0mm", "p1", "0mm"], ["-p1", "0mm", "0mm"], ["-p1/2", "-p1/2", "0mm"], ["0mm", "0mm", "0mm"]] -############################################################################### -# Polyline primitives -# ------------------- +# ## Polyline primitives +# # The following examples are for creating polyline primitives. -# Create line primitive -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Create line primitive +# # Create a line primitive. The basic polyline command takes a list of positions # (``[X, Y, Z]`` coordinates) and creates a polyline object with one or more # segments. The supported segment types are ``Line``, ``Arc`` (3 points), @@ -64,9 +58,8 @@ print("Segment types : {}".format([s.type for s in P.segment_types])) print("primitive id = {}".format(P.id)) -############################################################################### -# Create arc primitive -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create arc primitive +# # Create an arc primitive. The parameter ``position_list`` must contain at # least three position values. The first three position values are used. @@ -74,9 +67,8 @@ print("Created object with id {} and name {}.".format(P.id, prim3D.objects[P.id].name)) -############################################################################### -# Create spline primitive -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create spline primitive +# # Create a spline primitive. Defining the segment using a ``PolylineSegment`` # object allows you to provide additional input parameters for the spine, such # as the number of points (in this case 4). The parameter ``position_list`` @@ -86,9 +78,8 @@ position_list=test_points, segment_type=prim3D.polyline_segment("Spline", num_points=4), name="PL03_spline_4pt" ) -############################################################################### -# Create center-point arc primitive -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create center-point arc primitive +# # Create a center-point arc primitive. A center-point arc segment is defined # by a starting point, a center point, and an angle of rotation around the # center point. The rotation occurs in a plane parallel to the XY, YZ, or ZX @@ -106,7 +97,6 @@ name="PL04_center_point_arc", ) -############################################################################### # Here ``start_point`` and ``center_point`` have the same values for the Y and # Z coordinates, so the plane or rotation could be either XY or ZX. # For these special cases when the rotation plane is ambiguous, you can specify @@ -125,9 +115,8 @@ name="PL04_center_point_arc_rot_ZX", ) -############################################################################### -# Compound polylines -# ------------------ +# ## Compound polylines +# # You can use a list of points in a single command to create a multi-segment # polyline. # @@ -136,34 +125,30 @@ P = prim3D.create_polyline(position_list=test_points, name="PL06_segmented_compound_line") -############################################################################### # You can specify the segment type with the parameter ``segment_type``. # In this case, you must specify that the four input points in ``position_list`` # are to be connected as a line segment followed by a 3-point arc segment. P = prim3D.create_polyline(position_list=test_points, segment_type=["Line", "Arc"], name="PL05_compound_line_arc") -############################################################################### # The parameter ``close_surface`` ensures that the polyline starting point and # ending point are the same. If necessary, you can add an additional line # segment to achieve this. P = prim3D.create_polyline(position_list=test_points, close_surface=True, name="PL07_segmented_compound_line_closed") -############################################################################### # The parameter ``cover_surface=True`` also performs the modeler command # ``cover_surface``. Note that specifying ``cover_surface=True`` automatically # results in the polyline being closed. P = prim3D.create_polyline(position_list=test_points, cover_surface=True, name="SPL01_segmented_compound_line") -############################################################################### -# Compound lines -# -------------- +# ## Compound lines +# # The following examples are for inserting compound lines. # -# Insert line segment -# ~~~~~~~~~~~~~~~~~~~ +# ### Insert line segment +# # Insert a line segment starting at vertex 1 ``["100mm", "0mm", "0mm"]`` # of an existing polyline and ending at some new point ``["90mm", "20mm", "0mm"].`` # By numerical comparison of the starting point with the existing vertices of the @@ -177,9 +162,8 @@ P.insert_segment(position_list=[insert_point, p2]) -############################################################################### -# Insert compound line with insert curve -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Insert compound line with insert curve +# # Insert a compound line starting a line segment at vertex 1 ``["100mm", "0mm", "0mm"]`` # of an existing polyline and end at some new point ``["90mm", "20mm", "0mm"]``. # By numerical comparison of the starting point, it is determined automatically @@ -193,9 +177,8 @@ P.insert_segment(position_list=[start_point, insert_point1, insert_point2], segment="Arc") -############################################################################### -# Insert compound line at end of a center-point arc -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Insert compound line at end of a center-point arc +# # Insert a compound line at the end of a center-point arc (``type="AngularArc"``). # This is a special case. # @@ -211,7 +194,6 @@ segment_type=prim3D.polyline_segment(type="AngularArc", arc_angle=arc_angle_1, arc_center=arc_center_1), ) -############################################################################### # Step 2: Insert a line segment at the end of the arc with a specified end point. start_of_line_segment = P.end_point @@ -219,7 +201,6 @@ P.insert_segment(position_list=[start_of_line_segment, end_of_line_segment]) -############################################################################### # Step 3: Append a center-point arc segment to the line object. arc_angle_2 = "39.716deg" @@ -230,7 +211,6 @@ segment=prim3D.polyline_segment(type="AngularArc", arc_center=arc_center_2, arc_angle=arc_angle_2), ) -############################################################################### # You can use the compound polyline definition to complete all three steps in # a single step. @@ -244,9 +224,8 @@ name="Compound_Polyline_One_Command", ) -######################################################################### -# Insert two 3-point arcs forming a circle and covered -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Insert two 3-point arcs forming a circle and covered +# # Insert two 3-point arcs forming a circle and covered. # Note that the last point of the second arc segment is not defined in # the position list. @@ -260,7 +239,6 @@ matname="vacuum", ) -############################################################################### # Here is an example of a complex polyline where the number of points is # insufficient to populate the requested segments. This results in an # ``IndexError`` that PyAEDT catches silently. The return value of the command @@ -283,7 +261,6 @@ return_value = prim3D.create_polyline(MDL_points, segment_type=MDL_segments, name="MDL_Polyline") assert return_value # triggers an error at the application error -############################################################################### # Here is an example that provides more points than the segment list requires. # This is valid usage. The remaining points are ignored. @@ -291,9 +268,8 @@ P = prim3D.create_polyline(MDL_points, segment_type=MDL_segments, name="MDL_Polyline") -############################################################################### -# Save project -# ------------ +# ## Save project +# # Save the project. project_dir = r"C:\temp" From 1c1357219c2722f053b1fae4639ab103e4769963 Mon Sep 17 00:00:00 2001 From: Devin Date: Wed, 31 Jan 2024 09:06:32 -0600 Subject: [PATCH 20/56] Convert to md format 02-HFSS --- examples/02-HFSS/Array.py | 87 ++++++------ .../02-HFSS/Create_3d_Component_and_use_it.py | 125 ++++++++---------- examples/02-HFSS/Flex_CPWG.py | 85 +++++------- examples/02-HFSS/HFSS_Choke.py | 86 +++++------- examples/02-HFSS/HFSS_Dipole.py | 121 +++++++---------- examples/02-HFSS/HFSS_FSS_unitcell.py | 80 +++++------ examples/02-HFSS/HFSS_Spiral.py | 106 ++++++--------- examples/02-HFSS/HFSS_eigenmode.py | 98 ++++++-------- examples/02-HFSS/Probe_Fed_Patch.py | 50 +++---- examples/02-HFSS/Waveguide_Filter.py | 75 ++++------- 10 files changed, 375 insertions(+), 538 deletions(-) diff --git a/examples/02-HFSS/Array.py b/examples/02-HFSS/Array.py index 88060a4b564..cf6c06b56b3 100644 --- a/examples/02-HFSS/Array.py +++ b/examples/02-HFSS/Array.py @@ -1,37 +1,34 @@ -""" -HFSS: component antenna array ------------------------------ -This example shows how you can 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 examples runs only on Windows using CPython. -""" -########################################################## -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: component antenna array + +# This example shows how you can 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 examples runs only on Windows using CPython. + + +# ## Perform required imports +# # Perform required imports. import os import pyaedt from pyaedt.modules.solutions import FfdSolutionData -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -########################################################## -# Download 3D component -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Download 3D component +# # Download the 3D component that is needed to run the example. example_path = pyaedt.downloads.download_3dcomponent() -########################################################## -# Launch HFSS and save project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch HFSS and save project +# # Launch HFSS and save the project. + project_name = pyaedt.generate_unique_project_name(project_name="array") hfss = pyaedt.Hfss(projectname=project_name, specified_version="2023.2", @@ -41,9 +38,8 @@ print("Project name " + project_name) -########################################################## -# Read array definition from JSON file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Read array definition from JSON file +# # Read the array definition from a JSON file. A JSON file # can contain all information needed to import and set up a # full array in HFSS. @@ -57,9 +53,8 @@ dict_in["cells"][(3, 3)] = {"name": "Circ_Patch_5GHz1"} array = hfss.add_3d_component_array_from_json(dict_in) -########################################################## -# Modify cells -# ~~~~~~~~~~~~ +# ## Modify cells +# # Make center element passive and rotate corner elements. array.cells[1][1].is_active = False @@ -68,9 +63,8 @@ array.cells[2][0].rotation = 90 array.cells[2][2].rotation = 90 -########################################################## -# Set up simulation -# ~~~~~~~~~~~~~~~~~ +# ## Set up simulation +# # Set up a simulation and analyze it. setup = hfss.create_setup() @@ -79,27 +73,24 @@ hfss.analyze(num_cores=4) -########################################################## -# Get far field data -# ~~~~~~~~~~~~~~~~~~ + +# ## Get far field data +# # 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_ffd_solution_data(sphere_name="Infinite Sphere1", setup_name=hfss.nominal_adaptive, frequencies=[5e9]) - -########################################################## -# Generate contour plot -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Generate contour plot +# # Generate a contour plot. You can define the Theta scan # and Phi scan. ffdata.plot_farfield_contour(farfield_quantity='RealizedGain', title='Contour at {}Hz'.format(ffdata.frequency)) -########################################################## -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. # Far field post-processing can be performed without AEDT because the data is stored. @@ -109,25 +100,22 @@ hfss.release_desktop() -########################################################## -# Load far field data -# ~~~~~~~~~~~~~~~~~~~ +# ## Load far field data +# # Load far field data stored. ffdata = FfdSolutionData(frequencies=frequencies[0], eep_files=eep_file[0]) -########################################################## -# Generate contour plot -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Generate contour plot +# # Generate a contour plot. You can define the Theta scan # and Phi scan. ffdata.plot_farfield_contour(farfield_quantity='RealizedGain', title='Contour at {}Hz'.format(ffdata.frequency)) -########################################################## -# Generate 2D cutout plots -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate 2D cutout plots +# # Generate 2D cutout plots. You can define the Theta scan # and Phi scan. @@ -141,9 +129,8 @@ title='Elevation', quantity_format="dB10") -########################################################## -# Generate 3D polar plots in Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate 3D polar plots in Matplotlib +# # Generate 3D polar plots in Matplotlib. You can define # the Theta scan and Phi scan. diff --git a/examples/02-HFSS/Create_3d_Component_and_use_it.py b/examples/02-HFSS/Create_3d_Component_and_use_it.py index 34958943383..c73af2cc590 100644 --- a/examples/02-HFSS/Create_3d_Component_and_use_it.py +++ b/examples/02-HFSS/Create_3d_Component_and_use_it.py @@ -1,83 +1,82 @@ -""" -Create a 3D Component and reuse it ----------------------------------- -Summary of the workflow -1. Create an antenna using PyAEDT and HFSS 3D Modeler (same can be done with EDB and HFSS 3D Layout) -2. Store the object as a 3D Component on the disk -3. Reuse the 3D component in another project -4. Parametrize and optimize target design -""" - -########################################################## -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Create a 3D Component and reuse it + +# Summary of the workflow +# 1. Create an antenna using PyAEDT and HFSS 3D Modeler (same can be done with EDB and HFSS 3D Layout) +# 2. Store the object as a 3D Component on the disk +# 3. Reuse the 3D component in another project +# 4. Parametrize and optimize target design + +# ## Perform required imports +# # Perform required imports. + import os import tempfile from pyaedt import Hfss from pyaedt.generic.general_methods import generate_unique_name -########################################################## -# Launch HFSS 2023.2 -# ~~~~~~~~~~~~~~~~~~ +# ## Launch HFSS 2023.2 +# # PyAEDT can initialize a new session of Electronics Desktop or connect to an existing one. # Once Desktop is connected, a new HFSS session is started and a design is created. hfss = Hfss(specified_version="2023.2", new_desktop_session=True, close_on_exit=True) -########################################################## -# Variables -# ~~~~~~~~~ +# ## Variables +# # PyAEDT can create and store all variables available in AEDT (Design, Project, Post Processing) hfss["thick"] = "0.1mm" hfss["width"] = "1mm" -########################################################## -# Modeler -# ~~~~~~~~ +# ## Modeler +# # PyAEDT supports all modeler functionalities available in the Desktop. # Objects can be created, deleted and modified using all available boolean operations. # History is also fully accessible to PyAEDT. -substrate = hfss.modeler.create_box(["-width","-width","-thick"],["2*width","2*width", "thick"], matname="FR4_epoxy", name="sub") +# + +substrate = hfss.modeler.create_box(["-width","-width","-thick"],["2*width","2*width", "thick"], + matname="FR4_epoxy", name="sub") -patch = hfss.modeler.create_rectangle("XY",["-width/2","-width/2","0mm"],["width","width"], name="patch1") +patch = hfss.modeler.create_rectangle("XY",["-width/2","-width/2","0mm"],["width","width"], + name="patch1") -via1 = hfss.modeler.create_cylinder(2, ["-width/8","-width/4","-thick"],"0.01mm", "thick", matname="copper", name="via_inner") +via1 = hfss.modeler.create_cylinder(2, ["-width/8","-width/4","-thick"],"0.01mm", "thick", + matname="copper", name="via_inner") -via_outer = hfss.modeler.create_cylinder(2, ["-width/8","-width/4","-thick"],"0.025mm", "thick", matname="Teflon_based", name="via_teflon") +via_outer = hfss.modeler.create_cylinder(2, ["-width/8","-width/4","-thick"],"0.025mm", "thick", + matname="Teflon_based", name="via_teflon") +# - -########################################################## -# Boundaries -# ~~~~~~~~~~ +# ## Boundaries +# # Most of HFSS boundaries and excitations are already available in PyAEDT. # User can assign easily a boundary to a face or to an object by taking benefits of # Object-Oriented Programming (OOP) available in PyAEDT. hfss.assign_perfecte_to_sheets(patch) -########################################################## -# Advanced Modeler functions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Thanks to Python capabilities a lot of additional functionalities have been added to the Modeler of PyAEDT. -# in this example there is a property to retrieve automatically top and bottom faces of an objects. +# ## Advanced Modeler functions +# +# Thanks to Python capabilities a lot of additional functionalities have been added to the modeler of PyAEDT. +# In this example there is a property to retrieve automatically top and bottom faces of an objects. +# + side_face = [i for i in via_outer.faces if i.id not in [via_outer.top_face_z.id, via_outer.bottom_face_z.id]] hfss.assign_perfecte_to_sheets(side_face) hfss.assign_perfecte_to_sheets(substrate.bottom_face_z) +# - -########################################################## -# Create Wave Port -# ~~~~~~~~~~~~~~~~ +# ## Create Wave Port +# # Wave port can be assigned to a sheet or to a face of an object. hfss.wave_port(via_outer.bottom_face_z, name="P1", ) -########################################################## -# Create 3D Component -# ~~~~~~~~~~~~~~~~~~~ +# ## Create 3D Component +# # Once the model is ready a 3D Component can be created. # Multiple options are available to partially select objects, cs, boundaries and mesh operations. # Furthermore, encrypted 3d comp can be created too. @@ -85,52 +84,43 @@ component_path = os.path.join(tempfile.gettempdir(), generate_unique_name("component_test")+".aedbcomp") hfss.modeler.create_3dcomponent(component_path, "patch_antenna") -########################################################## -# Multiple project management -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Multiple project management +# # PyAEDT allows to control multiple projects, design and solution type at the same time. hfss2 = Hfss(projectname="new_project", designname="new_design") -########################################################## -# Insert of 3d component -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Insert of 3d component +# # The 3d component can be inserted without any additional info. # All needed info will be read from the file itself. hfss2.modeler.insert_3d_component(component_path) -########################################################## -# 3D Component Parameters -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## 3D Component Parameters +# # All 3d Component parameters are available and can be parametrized. hfss2.modeler.user_defined_components["patch_antenna1"].parameters - hfss2["p_thick"] = "1mm" - hfss2.modeler.user_defined_components["patch_antenna1"].parameters["thick"]="p_thick" -########################################################## -# Multiple 3d Components -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Multiple 3d Components +# # There is no limit to the number of 3D components that can be added on the same design. # They can be the same or linked to different files. hfss2.modeler.create_coordinate_system(origin=[20, 20, 10], name="Second_antenna") - ant2 = hfss2.modeler.insert_3d_component(component_path, targetCS="Second_antenna") -########################################################## -# Move components -# ~~~~~~~~~~~~~~~ +# ## Move components +# # The component can be moved by changing is position or moving the relative coordinate system. hfss2.modeler.coordinate_systems[0].origin = [10, 10, 3] -########################################################## -# Boundaries -# ~~~~~~~~~~ +# ## Boundaries +# # Most of HFSS boundaries and excitations are already available in PyAEDT. # User can assign easily a boundary to a face or to an object by taking benefits of @@ -142,22 +132,19 @@ # All setup parameters can be edited. setup1 = hfss2.create_setup() - optim = hfss2.parametrics.add("p_thick", "0.2mm", "1.5mm", step=14) -############################################################################### -# Save project -# ~~~~~~~~~~~~ +# ## Save project +# # Save the project. hfss2.modeler.fit_all() hfss2.plot(show=False, export_path=os.path.join(hfss.working_directory, "Image.jpg"), plot_air_objects=True) -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.release_desktop` method. +# `pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing AEDT. hfss2.save_project(os.path.join(tempfile.gettempdir(), generate_unique_name("parametrized")+".aedt")) diff --git a/examples/02-HFSS/Flex_CPWG.py b/examples/02-HFSS/Flex_CPWG.py index 65b473766da..e7029ae0159 100644 --- a/examples/02-HFSS/Flex_CPWG.py +++ b/examples/02-HFSS/Flex_CPWG.py @@ -1,29 +1,24 @@ -""" -HFSS: flex cable CPWG ---------------------- -This example shows how you can use PyAEDT to create a flex cable CPWG (coplanar waveguide with ground). -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: flex cable CPWG +# +# This example shows how you can use PyAEDT to create a flex cable CPWG (coplanar waveguide with ground). + +# ## Perform required imports +# # Perform required imports. import os from math import radians, sin, cos, sqrt import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. hfss = pyaedt.Hfss(specified_version="2023.2", @@ -36,9 +31,8 @@ hfss.modeler.model_units = "mil" hfss.mesh.assign_initial_mesh_from_slider(applycurvilinear=True) -############################################################################### -# Create variables -# ~~~~~~~~~~~~~~~~ +# ## Create variables +# # Create input variables for creating the flex cable CPWG. total_length = 300 @@ -53,9 +47,8 @@ xt = (total_length - r * radians(theta)) / 2 -############################################################################### -# Create bend -# ~~~~~~~~~~~ +# ## Create bend +# # Create the bend. The ``create_bending`` method creates a list of points for # the bend based on the curvature radius and extension. @@ -75,9 +68,8 @@ def create_bending(radius, extension=0): return position_list -############################################################################### -# Draw signal line -# ~~~~~~~~~~~~~~~~ +# ## Draw signal line +# # Draw a signal line to create a bent signal wire. position_list = create_bending(r, 1) @@ -89,9 +81,8 @@ def create_bending(radius, extension=0): matname="copper", ) -############################################################################### -# Draw ground line -# ~~~~~~~~~~~~~~~~ +# ## Draw ground line +# # Draw a ground line to create two bent ground wires. gnd_r = [(x, spacing + width / 2 + gnd_width / 2, z) for x, y, z in position_list] @@ -105,9 +96,8 @@ def create_bending(radius, extension=0): x.color = (255, 0, 0) gnd_objs.append(x) -############################################################################### -# Draw dielectric -# ~~~~~~~~~~~~~~~ +# ## Draw dielectric +# # Draw a dielectric to create a dielectric cable. position_list = create_bending(r + (height + gnd_thickness) / 2) @@ -120,9 +110,8 @@ def create_bending(radius, extension=0): matname="FR4_epoxy", ) -############################################################################### -# Create bottom metals -# ~~~~~~~~~~~~~~~~~~~~ +# ## Create bottom metals +# # Create the bottom metals. position_list = create_bending(r + height + gnd_thickness, 1) @@ -135,9 +124,8 @@ def create_bending(radius, extension=0): matname="copper", ) -############################################################################### -# Create port interfaces -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create port interfaces +# # Create port interfaces (PEC enclosures). port_faces = [] @@ -163,9 +151,8 @@ def create_bending(radius, extension=0): print(port_faces) -############################################################################### -# Create boundary condition -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create boundary condition +# # Creates a Perfect E boundary condition. boundary = [] @@ -174,9 +161,8 @@ def create_bending(radius, extension=0): boundary.append(s) hfss.assign_perfecte_to_sheets(s) -############################################################################### -# Create ports -# ~~~~~~~~~~~~ +# ## Create ports +# # Creates ports. for s, port_name in zip(port_faces, ["1", "2"]): @@ -184,9 +170,8 @@ def create_bending(radius, extension=0): hfss.wave_port(s.id, name=port_name, reference=reference) -############################################################################### -# Create setup and sweep -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and sweep +# # Create the setup and sweep. setup = hfss.create_setup("setup1") @@ -204,9 +189,8 @@ def create_bending(radius, extension=0): sweep_type="Interpolating", ) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. my_plot = hfss.plot(show=False, plot_air_objects=False) @@ -216,9 +200,8 @@ def create_bending(radius, extension=0): os.path.join(hfss.working_directory, "Image.jpg"), ) -############################################################################### -# Analyze and release -# ~~~~~~~~~~~~~~~~~~~~ +# ## Analyze and release +# # Uncomment the ``hfss.analyze`` command if you want to analyze the # model and release AEDT. diff --git a/examples/02-HFSS/HFSS_Choke.py b/examples/02-HFSS/HFSS_Choke.py index d6501bc473c..42aa61761f6 100644 --- a/examples/02-HFSS/HFSS_Choke.py +++ b/examples/02-HFSS/HFSS_Choke.py @@ -1,11 +1,9 @@ -""" -HFSS: choke ------------ -This example shows how you can use PyAEDT to create a choke setup in HFSS. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: choke +# +# This example shows how you can use PyAEDT to create a choke setup in HFSS. + +# ## Perform required imports +# # Perform required imports. import json @@ -14,17 +12,15 @@ project_name = pyaedt.generate_unique_project_name(project_name="choke") -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch HFSS -# ~~~~~~~~~~~ +# ## Launch HFSS +# # Launches HFSS 2023 R2 in graphical mode. hfss = pyaedt.Hfss(projectname=project_name, @@ -33,9 +29,8 @@ new_desktop_session=True, solution_type="Terminal") -############################################################################### -# Rules and information of use -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Rules and information of use +# # The dictionary values contain the different parameter values of the core and # the windings that compose the choke. You must not change the main structure of # the dictionary. The dictionary has many primary keys, including @@ -43,7 +38,7 @@ # dictionaries as values. The keys of these dictionaries are secondary keys # of the dictionary values, such as ``"1"``, ``"2"``, ``"3"``, ``"4"``, and # ``"Simple"``. -# +# # You must not modify the primary or secondary keys. You can modify only their values. # You must not change the data types for these keys. For the dictionaries from # ``"Number of Windings"`` through ``"Wire Section"``, values must be Boolean. Only @@ -98,20 +93,17 @@ "Inner Winding": {"Turns": 4, "Coil Pit(deg)": 0.1, "Occupation(%)": 0}, } -############################################################################### -# Convert dictionary to JSON file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Convert dictionary to JSON file +# # Convert a dictionary to a JSON file. You must supply the path of the # JSON file as an argument. json_path = os.path.join(hfss.working_directory, "choke_example.json") - with open(json_path, "w") as outfile: json.dump(values, outfile) -############################################################################### -# Verify parameters of JSON file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Verify parameters of JSON file +# # Verify parameters of the JSON file. The ``check_choke_values`` method takes # the JSON file path as an argument and does the following: # @@ -121,10 +113,9 @@ dictionary_values = hfss.modeler.check_choke_values(json_path, create_another_file=False) print(dictionary_values) -############################################################################### -# Create choke -# ~~~~~~~~~~~~ -# Create the choke. The ``create_choke`` method takes the JSON file path as an +# ## Create choke +# +# Create the choke. The ``Hfss.modeler.create_choke()`` method takes the JSON file path as an # argument. list_object = hfss.modeler.create_choke(json_path) @@ -133,9 +124,8 @@ first_winding_list = list_object[2] second_winding_list = list_object[3] -############################################################################### -# Create ground -# ~~~~~~~~~~~~~ +# ## Create ground +# # Create a ground. ground_radius = 1.2 * dictionary_values[1]["Outer Winding"]["Outer Radius"] @@ -143,9 +133,8 @@ ground = hfss.modeler.create_circle("XY", ground_position, ground_radius, name="GND", matname="copper") coat = hfss.assign_coating(ground, isinfgnd=True) -############################################################################### -# Create lumped ports -# ~~~~~~~~~~~~~~~~~~~ +# ## Create lumped ports +# # Create lumped ports. port_position_list = [ @@ -163,9 +152,8 @@ reference=[ground] ) -############################################################################### -# Create mesh -# ~~~~~~~~~~~ +# ## Create mesh +# # Create the mesh. cylinder_height = 2.5 * dictionary_values[1]["Outer Winding"]["Height"] @@ -176,17 +164,15 @@ hfss.mesh.assign_length_mesh([mesh_operation_cylinder], maxlength=15, maxel=None, meshop_name="choke_mesh") -############################################################################### -# Create boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Create boundaries +# # Create the boundaries. A region with openings is needed to run the analysis. region = hfss.modeler.create_region(pad_percent=1000) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup with a sweep to run the simulation. Depending on your machine's # computing power, the simulation can take some time to run. @@ -204,20 +190,18 @@ save_fields=False, ) -############################################################################### -# Save project -# ~~~~~~~~~~~~ +# ## Save project +# # Save the project. hfss.modeler.fit_all() hfss.plot(show=False, export_path=os.path.join(hfss.working_directory, "Image.jpg"), plot_air_objects=True) -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.release_desktop` method. +# `pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing. hfss.release_desktop() diff --git a/examples/02-HFSS/HFSS_Dipole.py b/examples/02-HFSS/HFSS_Dipole.py index 4b104b92e67..cb05bebec8d 100644 --- a/examples/02-HFSS/HFSS_Dipole.py +++ b/examples/02-HFSS/HFSS_Dipole.py @@ -1,12 +1,10 @@ -""" -HFSS: dipole antenna --------------------- -This example shows how you can use PyAEDT to create a dipole antenna in HFSS and postprocess results. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: dipole antenna +# +# This example shows how you can use PyAEDT to create a dipole antenna in HFSS +# and postprocess results. + +# ## Perform required imports +# # Perform required imports. import os @@ -14,38 +12,33 @@ project_name = pyaedt.generate_unique_project_name(project_name="dipole") -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ` # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. d = pyaedt.launch_desktop("2023.2", non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Launch HFSS -# ~~~~~~~~~~~ +# ## Launch HFSS +# # Launch HFSS 2023 R2 in graphical mode. hfss = pyaedt.Hfss(projectname=project_name, solution_type="Modal") -############################################################################### -# Define variable -# ~~~~~~~~~~~~~~~ +# ## Define variable +# # Define a variable for the dipole length. hfss["l_dipole"] = "13.5cm" -############################################################################### -# Get 3D component from system library -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get 3D component from system library +# # 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. @@ -55,16 +48,14 @@ geometryparams["dipole_length"] = "l_dipole" hfss.modeler.insert_3d_component(compfile, geometryparams) -############################################################################### -# Create boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Create boundaries +# # Create boundaries. A region with openings is needed to run the analysis. hfss.create_open_region(Frequency="1GHz") -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. my_plot = hfss.plot(show=False, plot_air_objects=False) @@ -75,9 +66,8 @@ os.path.join(hfss.working_directory, "Image.jpg"), ) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup with a sweep to run the simulation. setup = hfss.create_setup("MySetup") @@ -96,16 +86,14 @@ save_fields=False, ) -############################################################################### -# Save and run simulation -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save and run simulation +# # Save and run the simulation. hfss.analyze_setup("MySetup") -############################################################################### -# Create scattering plot and far fields report -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create scattering plot and far fields report +# # Create a scattering plot and a far fields report. hfss.create_scattering("MyScattering") @@ -122,9 +110,8 @@ report_category="Far Fields", ) -############################################################################### -# Create far fields report using report objects -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create far fields report using report objects +# # Create a far fields report using the ``report_by_category.far field`` method, # which gives you more freedom. @@ -133,9 +120,8 @@ new_report.primary_sweep = "Theta" new_report.create("Realized2D") -############################################################################### -# Generate multiple plots -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate multiple plots +# # Generate multiple plots using the object ``new_report``. This code generates # 2D and 3D polar plots. @@ -143,18 +129,16 @@ new_report.secondary_sweep = "Phi" new_report.create("Realized3D") -############################################################################### -# Get solution data -# ~~~~~~~~~~~~~~~~~ +# ## Get solution data +# # Get solution data using the object ``new_report``` and postprocess or plot the # data outside AEDT. solution_data = new_report.get_solution_data() solution_data.plot() -############################################################################### -# Generate far field plot -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate far field 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`` @@ -165,9 +149,8 @@ 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") -############################################################################### -# Get solution data -# ~~~~~~~~~~~~~~~~~ +# ## Get solution data +# # Get solution data. You can use this code to generate the same plot outside AEDT. new_report = hfss.post.reports_by_category.far_field("GainTotal", hfss.nominal_adaptive, "3D") @@ -175,33 +158,29 @@ new_report.far_field_sphere = "3D" solutions = new_report.get_solution_data() -############################################################################### -# Generate 3D plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate 3D plot using Matplotlib +# # Generate a 3D plot using Matplotlib. solutions.plot_3d() -############################################################################### -# Generate 3D far fields plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate 3D far fields plot using Matplotlib +# # Generate a far fields plot using Matplotlib. new_report.far_field_sphere = "Sphere_Custom" solutions_custom = new_report.get_solution_data() solutions_custom.plot_3d() -############################################################################### -# Generate 2D plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate 2D plot using Matplotlib +# # Generate a 2D plot using Matplotlib where you specify whether it is a polar # plot or a rectangular plot. solutions.plot(math_formula="db20", is_polar=True) -########################################################## -# Get far field data -# ~~~~~~~~~~~~~~~~~~ +# ## Get far field data +# # Get far field data. After the simulation completes, the far # field data is generated port by port and stored in a data class, , user can use this data # once AEDT is released. @@ -209,9 +188,8 @@ ffdata = hfss.get_antenna_ffd_solution_data(sphere_name="Sphere_Custom", setup_name=hfss.nominal_adaptive, frequencies=["1000MHz"]) -########################################################## -# Generate 2D cutout plot -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate 2D cutout plot +# # Generate 2D cutout plot. You can define the Theta scan # and Phi scan. @@ -221,9 +199,8 @@ quantity_format="dB20", is_polar=True) -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/02-HFSS/HFSS_FSS_unitcell.py b/examples/02-HFSS/HFSS_FSS_unitcell.py index 772b7dcf05c..24d73c0f88a 100644 --- a/examples/02-HFSS/HFSS_FSS_unitcell.py +++ b/examples/02-HFSS/HFSS_FSS_unitcell.py @@ -1,12 +1,9 @@ -""" -HFSS: FSS Unitcell Simulation --------------------- -This example shows how you can use PyAEDT to create a FSS unitcell simulations in HFSS and postprocess results. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: FSS Unitcell Simulation +# +# This example shows how you can use PyAEDT to create a FSS unitcell simulations in HFSS and postprocess results. + +# ## Perform required imports +# # Perform required imports. import os @@ -14,38 +11,33 @@ project_name = pyaedt.generate_unique_project_name(project_name="FSS") -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ` # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. d = pyaedt.launch_desktop("2023.2", non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Launch HFSS -# ~~~~~~~~~~~ +# ## Launch HFSS +# # Launch HFSS 2023 R2 in graphical mode. hfss = pyaedt.Hfss(projectname=project_name, solution_type="Modal") -############################################################################### -# Define variable -# ~~~~~~~~~~~~~~~ +# ## Define variable +# # Define a variable for the 3D-component. hfss["patch_dim"] = "10mm" -############################################################################### -# Insert 3D component from system library -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Insert 3D component from system library +# # Download the 3D component from the example data and insert the 3D Component. unitcell_3d_component_path = pyaedt.downloads.download_FSS_3dcomponent() @@ -53,17 +45,15 @@ comp = hfss.modeler.insert_3d_component(unitcell_path) -############################################################################### -# Assign design parameter to 3D Component parameter -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign design parameter to 3D Component parameter +# # Assign parameter. component_name = hfss.modeler.user_defined_component_names comp.parameters["a"] = "patch_dim" -############################################################################### -# Create air region -# ~~~~~~~~~~~~~~~~~ +# ## Create air region +# # Create an open region along +Z direction for unitcell analysis. bounding_dimensions = hfss.modeler.get_bounding_dimension() @@ -78,16 +68,14 @@ [x_min, y_min, z_min, x_max, y_max, z_max] = region.bounding_box -############################################################################### -# Assign Lattice pair boundary -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign Lattice pair boundary +# # Assigning lattice pair boundary automatically detected. hfss.auto_assign_lattice_pairs(object_to_assign=region.name) -############################################################################### -# Assign Floquet port excitation along +Z direction -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign Floquet port excitation along +Z direction +# # Assign Floquet port. id_z_pos = region.top_face_z @@ -95,9 +83,8 @@ portname='port_z_max', deembed_dist=10 * bounding_dimensions[2]) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup with a sweep to run the simulation. setup = hfss.create_setup("MySetup") @@ -115,9 +102,8 @@ save_fields=False, ) -############################################################################### -# Create S-parameter report using report objects -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create S-parameter report using report objects +# # Create S-parameter reports using create report. all_quantities = hfss.post.available_report_quantities() @@ -141,16 +127,14 @@ plotname="phase_plot", ) -############################################################################### -# Save and run simulation -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save and run simulation +# # Save and run the simulation. Uncomment the line following line to run the analysis. # hfss.analyze() -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/02-HFSS/HFSS_Spiral.py b/examples/02-HFSS/HFSS_Spiral.py index 5b5dc42099c..4d0fa07a5db 100644 --- a/examples/02-HFSS/HFSS_Spiral.py +++ b/examples/02-HFSS/HFSS_Spiral.py @@ -1,12 +1,9 @@ -""" -HFSS: spiral inductor ---------------------- -This example shows how you can use PyAEDT to create a spiral inductor, solve it, and plot results. -""" - -############################################################# -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: spiral inductor +# +# This example shows how you can use PyAEDT to create a spiral inductor, solve it, and plot results. + +# ## Perform required imports +# # Perform required imports. import os @@ -14,17 +11,15 @@ project_name = pyaedt.generate_unique_project_name(project_name="spiral") -############################################################# -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################# -# Launch HFSS -# ~~~~~~~~~~~ +# ## Launch HFSS +# # Launch HFSS 2023 R2 in non-graphical mode and change the # units to microns. @@ -32,9 +27,8 @@ hfss.modeler.model_units = "um" p = hfss.modeler -############################################################# -# Define variables -# ~~~~~~~~~~~~~~~~ +# ## Define variables +# # Define input variables. You can use the values that follow or edit # them. @@ -47,9 +41,8 @@ gap = 3 hfss["Tsub"] = "6" + hfss.modeler.model_units -############################################################# -# Standardize polyline -# ~~~~~~~~~~~~~~~~~~~~ +# ## Standardize polyline +# # Standardize the polyline using the ``create_line`` method to fix # the width, thickness, and material. @@ -57,9 +50,8 @@ def create_line(pts): p.create_polyline(pts, xsection_type="Rectangle", xsection_width=width, xsection_height=thickness, matname="copper") -################################################################ -# Create spiral inductor -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create spiral inductor +# # Create the spiral inductor. This spiral inductor is not # parametric, but you could parametrize it later. @@ -75,9 +67,8 @@ def create_line(pts): ) -################################################################ -# Center return path -# ~~~~~~~~~~~~~~~~~~ +# ## Center return path +# # Center the return path. x0, y0, z0 = ind.points[0] @@ -87,9 +78,8 @@ def create_line(pts): [width, width, gap + thickness], matname="copper") -################################################################ -# Create port 1 -# ~~~~~~~~~~~~~ +# ## Create port 1 +# # Create port 1. p.create_rectangle(csPlane=pyaedt.constants.PLANE.YZ, @@ -99,9 +89,8 @@ def create_line(pts): ) hfss.lumped_port(signal="port1", integration_line=pyaedt.constants.AXIS.Z) -################################################################ -# Create port 2 -# ~~~~~~~~~~~~~ +# ## Create port 2 +# # Create port 2. create_line([(x1 + width / 2, y1, 0), (x1 - 5, y1, 0)]) @@ -110,9 +99,8 @@ def create_line(pts): name="port2") hfss.lumped_port(signal="port2", integration_line=pyaedt.constants.AXIS.Z) -################################################################ -# Create silicon substrate and ground plane -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create silicon substrate and ground plane +# # Create the silicon substrate and the ground plane. p.create_box([x1 - 20, x1 - 20, "-Tsub-{}{}/2".format(thickness, hfss.modeler.model_units)], @@ -123,9 +111,8 @@ def create_line(pts): [-2 * x1 + 40, -2 * x1 + 40, -0.1], matname="PEC") -################################################################ -# Assign airbox and radiation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign airbox and radiation +# # Assign the airbox and the radiation. box = p.create_box( @@ -137,24 +124,21 @@ def create_line(pts): hfss.assign_radiation_boundary_to_objects("airbox") -################################################################ -# Assign material override -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign material override +# # Assign a material override so that the validation check does # not fail. hfss.change_material_override() -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. hfss.plot(show=False, export_path=os.path.join(hfss.working_directory, "Image.jpg"), plot_air_objects=False) -################################################################ -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create the setup and define a frequency sweep to solve the project. setup1 = hfss.create_setup(setupname="setup1") @@ -164,38 +148,34 @@ def create_line(pts): hfss.save_project() hfss.analyze() -################################################################ -# Get report data -# ~~~~~~~~~~~~~~~ +# ## Get report data +# # Get report data and use the following formulas to calculate # the inductance and quality factor. L_formula = "1e9*im(1/Y(1,1))/(2*pi*freq)" Q_formula = "im(Y(1,1))/re(Y(1,1))" -################################################################ -# Create output variable -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create output variable +# # Create output variable + hfss.create_output_variable("L", L_formula, solution="setup1 : LastAdaptive") -################################################################ -# Plot calculated values in Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot calculated values in Matplotlib +# # Plot the calculated values in Matplotlib. data = hfss.post.get_solution_data([L_formula, Q_formula]) data.plot(curves=[L_formula, Q_formula], math_formula="re", xlabel="Freq", ylabel="L and Q") -################################################################ -# Export results to csv file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Export results to csv file +# # Export results to csv file data.export_data_to_csv(os.path.join(hfss.toolkit_directory,"output.csv")) -################################################################ -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. hfss.save_project(project_name) diff --git a/examples/02-HFSS/HFSS_eigenmode.py b/examples/02-HFSS/HFSS_eigenmode.py index 93d5e6bb671..00d6f42c02c 100644 --- a/examples/02-HFSS/HFSS_eigenmode.py +++ b/examples/02-HFSS/HFSS_eigenmode.py @@ -1,31 +1,28 @@ -""" -HFSS: Eigenmode filter ----------------------- -This example shows how you can use PyAEDT to automate the eigenmode solver in HFSS. -Eigenmode analysis can be applied to open, radiating structures -using an absorbing boundary condition. This type of analysis is useful for -determining the resonant frequency of a geometry or an antenna and can be used to refine -the mesh at the resonance, even when the resonant frequency of the antenna is not known. - -The challenge posed by this method is to identify and filter the non-physical modes -resulting from reflection from boundaries of the main domain. -Because the Eigenmode solver sorts by frequency and does not filter on the -quality factor, these virtual modes are present when the eigenmode approach is -applied to nominally open structures. -When looking for resonant modes over a wide frequency range for nominally -enclosed structures, several iterations may be required because the minimum frequency -is determined manually and simulations re-run until the complete frequency range is covered -and all important physical modes are calculated. - -The following script finds the physical modes of a model in a wide frequency range by automating the solution setup. -During each simulation, a user-defined number of modes is simulated, and the modes with a Q higher than a user- defined value are filtered. -The next simulation automatically continues to find modes having a frequency higher than the last mode of the previous analysis. -This continues until the maximum frequency in the desired range is achieved. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # HFSS: Eigenmode filter +# +# This example shows how you can use PyAEDT to automate the eigenmode solver in HFSS. +# Eigenmode analysis can be applied to open, radiating structures +# using an absorbing boundary condition. This type of analysis is useful for +# determining the resonant frequency of a geometry or an antenna and can be used to refine +# the mesh at the resonance, even when the resonant frequency of the antenna is not known. +# +# The challenge posed by this method is to identify and filter the non-physical modes +# resulting from reflection from boundaries of the main domain. +# Because the Eigenmode solver sorts by frequency and does not filter on the +# quality factor, these virtual modes are present when the eigenmode approach is +# applied to nominally open structures. +# When looking for resonant modes over a wide frequency range for nominally +# enclosed structures, several iterations may be required because the minimum frequency +# is determined manually and simulations re-run until the complete frequency range is covered +# and all important physical modes are calculated. +# +# The following script finds the physical modes of a model in a wide frequency range by automating the solution setup. +# During each simulation, a user-defined number of modes is simulated, and the modes with a Q higher than a user- defined value are filtered. +# The next simulation automatically continues to find modes having a frequency higher than the last mode of the previous analysis. +# This continues until the maximum frequency in the desired range is achieved. + +# ## Perform required imports +# # Run through each cell. This cell imports the required packages. import sys @@ -37,38 +34,33 @@ temp_folder = pyaedt.generate_unique_folder_name() project_path = pyaedt.downloads.download_file("eigenmode", "emi_PCB_house.aedt", temp_folder) -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. desktop_version = "2023.2" -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. d = pyaedt.launch_desktop(desktop_version, non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Launch HFSS -# ~~~~~~~~~~~ +# ## Launch HFSS +# # Launch HFSS 2023 R2 in graphical mode. hfss = pyaedt.Hfss(projectname=project_path, non_graphical=non_graphical) -############################################################################### -# Input parameters for eigenmode solver -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Input parameters for eigenmode solver +# # The geometry and material should be already set. The analyses are generated by the code. # Number of modes during each analysis, max allowed number is 20. # Entering a number higher than 10 might need long simulation time as the @@ -86,9 +78,8 @@ resonance = {} -############################################################################### -# Find the modes -# ~~~~~~~~~~~~~~ +# ## Find the modes +# # The following cell is a function. If called, it creates an eigenmode setup and solves it. # After the solve, each mode, along with its corresponding real frequency and quality factor, # are saved for further processing. @@ -125,9 +116,8 @@ def find_resonance(): return data -############################################################################### -# Automate eigenmode solution -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Automate eigenmode solution +# # Running the next cell calls the resonance function and saves only those modes with a Q higher than the defined # limit. The ``find_resonance`` function is called until the complete frequency range is covered. # When the automation ends, the physical modes in the whole frequency range are reported. @@ -145,17 +135,15 @@ def find_resonance(): resonance_frequencies = [f"{resonance[i][1] / 1e9:.5} GHz" for i in resonance] print(str(resonance_frequencies)) -############################################################################### -# Save project -# ~~~~~~~~~~~~ +# ## Save project +# # Save the project. hfss.modeler.fit_all() hfss.plot(show=False, export_path=os.path.join(hfss.working_directory, "Image.jpg"), plot_air_objects=False) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. hfss.save_project() diff --git a/examples/02-HFSS/Probe_Fed_Patch.py b/examples/02-HFSS/Probe_Fed_Patch.py index 13b377d7c7d..0ad2718c560 100644 --- a/examples/02-HFSS/Probe_Fed_Patch.py +++ b/examples/02-HFSS/Probe_Fed_Patch.py @@ -1,16 +1,12 @@ -""" -HFSS: Probe-fed patch antenna ---------------------------------------------------------- -This example shows how to use the ``Stackup3D`` class -to create and analyze a patch antenna in HFSS. - -Note that the HFSS 3D Layout interface may offer advantages for -laminate structures such as the patch antenna. -""" +# # HFSS: Probe-fed patch antenna +# +# This example shows how to use the ``Stackup3D`` class +# to create and analyze a patch antenna in HFSS. +# +# Note that the HFSS 3D Layout interface may offer advantages for +# laminate structures such as the patch antenna. -########################### -# Perform imports -# ~~~~~~~~~~~~~~~~~~ +# ## Perform imports import os @@ -18,9 +14,8 @@ import tempfile from pyaedt.modeler.advanced_cad.stackup_3d import Stackup3D -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ``"PYAEDT_NON_GRAPHICAL"`` is set to ``False`` # to create this documentation. # @@ -34,9 +29,8 @@ length_units = "mm" freq_units = "GHz" -######################################################## -# Create temporary working folder -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create temporary working folder +# # Use tempfile to create a temporary working folder. Project data # is deleted after this example is run. # @@ -51,9 +45,8 @@ project_folder = tmpdir.name proj_name = os.path.join(project_folder, "antenna") -##################### -# Launch HFSS -# ----------- +# ## Launch HFSS +# # hfss = pyaedt.Hfss(projectname=proj_name, @@ -64,9 +57,8 @@ hfss.modeler.model_units = length_units -##################################### -# Create patch -# ------------ +# ## Create patch +# # Create the patch. # @@ -96,18 +88,16 @@ hfss.save_project() hfss.analyze() -############################### -# Plot S11 -# --------- +# ## Plot S11 +# plot_data = hfss.get_traces_for_plot() report = hfss.post.create_report(plot_data) solution = report.get_solution_data() plt = solution.plot(solution.expressions) -############################################################################### -# Release AEDT -# ------------ +# ## Release AEDT +# # Release AEDT and clean up temporary folders and files. hfss.release_desktop() diff --git a/examples/02-HFSS/Waveguide_Filter.py b/examples/02-HFSS/Waveguide_Filter.py index 9624c0f21c3..dc41698c837 100644 --- a/examples/02-HFSS/Waveguide_Filter.py +++ b/examples/02-HFSS/Waveguide_Filter.py @@ -1,14 +1,10 @@ -""" -HFSS: Inductive Iris waveguide filter -------------------------------------- -This example shows how to build and analyze a 4-pole -X-Band waveguide filter using inductive irises. - -""" +# # HFSS: Inductive Iris waveguide filter +# +# This example shows how to build and analyze a 4-pole +# X-Band waveguide filter using inductive irises. # sphinx_gallery_thumbnail_path = 'Resources/wgf.png' -############################################################################### # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. @@ -19,15 +15,11 @@ import pyaedt from pyaedt import general_methods -############################################################################### -# Launch Ansys Electronics Desktop (AEDT) -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# ## Launch Ansys Electronics Desktop (AEDT) -############################################################################### -# Define parameters and values for waveguide iris filter -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Define parameters and values for waveguide iris filter +# # l: Length of the cavity from the mid-point of one iris # to the midpoint of the next iris. # w: Width of the iris opening. @@ -45,9 +37,7 @@ non_graphical = False new_thread = True -############################################################################### -# Save the project and results in the TEMP folder -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Save the project and results in the TEMP folder project_folder = os.path.join(tempfile.gettempdir(), "waveguide_example") if not os.path.exists(project_folder): @@ -66,9 +56,7 @@ var_mapping = dict() # Used by parse_expr to parse expressions. -############################################################################### -# Initialize design parameters in HFSS. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Initialize design parameters in HFSS. hfss.modeler.model_units = "in" # Set to inches for key in wgparams: @@ -91,9 +79,8 @@ is_even = False -############################################################################### -# Draw parametric waveguide filter -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Draw parametric waveguide filter +# # Define a function to place each iris at the correct longitudinal (z) position, # Loop from the largest index (interior of the filter) to 1, which is the first # iris nearest the waveguide ports. @@ -110,9 +97,8 @@ def place_iris(zpos, dz, n): return iris -############################################################################### -# Place irises -# ~~~~~~~~~~~~ +# ### Place irises +# # Place the irises from inner (highest integer) to outer. for count in reversed(range(1, len(wgparams['w']) + 1)): @@ -127,9 +113,8 @@ def place_iris(zpos, dz, n): if not is_even: iris = place_iris("-(" + zpos + ")", "-t", count) -############################################################################### -# Draw full waveguide with ports -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Draw full waveguide with ports +# # Use ``hfss.variable_manager`` which acts like a dict() to return an instance of # the ``pyaedt.application.Variables.VariableManager`` class for any variable. # The ``VariableManager`` instance takes the HFSS variable name as a key. @@ -147,20 +132,17 @@ def place_iris(zpos, dz, n): hfss.modeler.create_box(["-b/2", "-a/2", "wg_z_start"], ["b", "a", "wg_length"], name="waveguide", matname="vacuum") -############################################################################### -# Draw the whole waveguide. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Draw the whole waveguide. +# # wg_z is the total length of the waveguide, including port extension. # Note that the ``.evaluated_value`` provides access to the numerical value of # ``wg_z_start`` which is an expression in HFSS. wg_z = [wg_z_start.evaluated_value, hfss.value_with_units(wg_z_start.numeric_value + wg_length.numeric_value, "in")] -############################################################################### -# Assign wave ports to the end faces of the waveguid +# Assign wave ports to the end faces of the waveguide # and define the calibration lines to ensure self-consistent # polarization between wave ports. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ count = 0 ports = [] @@ -171,9 +153,8 @@ def place_iris(zpos, dz, n): ports.append(hfss.wave_port(face_id, integration_line=[u_start, u_end], name="P" + str(n + 1), renormalize=False)) -############################################################################### -# Insert the mesh adaptation setup using refinement at two frequencies. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Insert the mesh adaptation setup using refinement at two frequencies. +# # This approach is useful for resonant structures as the coarse initial # mesh impacts the resonant frequency and hence, the field propagation through the # filter. Adaptation at multiple frequencies helps to ensure that energy propagates @@ -191,16 +172,14 @@ def place_iris(zpos, dz, n): sweep_type="Interpolating", ) -################################################################################# # Solve the project with two tasks. # Each frequency point is solved simultaneously. setup.analyze(num_tasks=2) -############################################################################### -# Generate S-Parameter Plots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Generate S-Parameter Plots +# # The following commands fetch solution data from HFSS for plotting directly # from the Python interpreter. # Caution: The syntax for expressions must be identical to that used @@ -212,9 +191,8 @@ def place_iris(zpos, dz, n): plt = solution.plot(solution.expressions) # Matplotlib axes object. -############################################################################### -# Generate E field plot -# ~~~~~~~~~~~~~~~~~~~~~ +# ### Generate E field plot +# # The following command generates a field plot in HFSS and uses PyVista # to plot the field in Jupyter. @@ -226,9 +204,8 @@ def place_iris(zpos, dz, n): export_path=hfss.working_directory, show=False) -############################################################################### -# Save and close the desktop -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ### Save and close the desktop +# # The following command saves the project to a file and closes the desktop. hfss.save_project() From 98babc97ffb17b344ec61b8ad45b7e446cd85d9b Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 31 Jan 2024 16:11:14 +0100 Subject: [PATCH 21/56] MISC: add fixed 00-EDB examples --- examples/00-EDB/01_edb_example.py | 7 ++++-- examples/00-EDB/02_edb_to_ipc2581.py | 2 +- .../03_5G_antenna_example_parametrics.py | 22 ++++++++++++++----- examples/00-EDB/04_edb_parametrized_design.py | 12 ++++++---- examples/00-EDB/05_Plot_nets.py | 2 +- examples/00-EDB/06_Advanced_EDB.py | 10 +++++---- examples/00-EDB/10_GDS_workflow.py | 19 +++++++++++++--- .../00-EDB/11_post_layout_parameterization.py | 4 ++++ .../00-EDB/12_edb_sma_connector_on_board.py | 8 +++++++ examples/00-EDB/15_ac_analysis.py | 4 ++++ examples/00-EDB/index.rst | 6 ++--- 11 files changed, 72 insertions(+), 24 deletions(-) diff --git a/examples/00-EDB/01_edb_example.py b/examples/00-EDB/01_edb_example.py index c4acd10d78b..bf19005453e 100644 --- a/examples/00-EDB/01_edb_example.py +++ b/examples/00-EDB/01_edb_example.py @@ -4,6 +4,7 @@ # layout and run DC-IR analysis in SIwave. # Perform required imports +# + import os import time import pyaedt @@ -16,6 +17,7 @@ siwave_file = os.path.join(os.path.dirname(targetfile), "ANSYS-HSD_V1.siw") print(targetfile) aedt_file = targetfile[:-4] + "aedt" +# - # ## Electronics Database (EDB) # @@ -105,6 +107,7 @@ # This prints the header. Replace "pin_name" with "pin" to # make the header align with the values. +# + print("\t".join(print_columns).replace("pin_name", "pin")) for el in powertree_df: @@ -116,6 +119,7 @@ count += 1 s.rstrip() print(s) +# - # ## Remove Unused Components # @@ -134,8 +138,7 @@ edb.nets.delete("PDEN") -# Print the top and bottom -# elevation of the stackup obtained using +# Print the top and bottom elevation of the stackup obtained using # the method ``Edb.stackup.limits()``. s = "Top layer name: \"{top}\", Elevation: {top_el:.2f} " diff --git a/examples/00-EDB/02_edb_to_ipc2581.py b/examples/00-EDB/02_edb_to_ipc2581.py index 823e17e516e..c1864bf900f 100644 --- a/examples/00-EDB/02_edb_to_ipc2581.py +++ b/examples/00-EDB/02_edb_to_ipc2581.py @@ -48,7 +48,7 @@ # Export the EDB to IPC2581 file. -edb.export_to_ipc2581(ipc2581_file, "inch") +edb.export_to_ipc2581(ipc2581_file_name, "inch") print("IPC2581 File has been saved to {}".format(ipc2581_file_name)) # Close EDB diff --git a/examples/00-EDB/03_5G_antenna_example_parametrics.py b/examples/00-EDB/03_5G_antenna_example_parametrics.py index 81c06f433af..b6e838e8b8f 100644 --- a/examples/00-EDB/03_5G_antenna_example_parametrics.py +++ b/examples/00-EDB/03_5G_antenna_example_parametrics.py @@ -21,6 +21,7 @@ # Data classes are useful to do calculations and store variables. # We create 3 Data classes for Patch, Line and Array +# + class Patch: def __init__(self, width=0.0, height=0.0, position=0.0): self.width = width @@ -67,7 +68,7 @@ def points(self): ["{}+1e-3".format(self.length), "{}/2+1e-3".format(self.width)], [-1e-3, "{}/2+1e-3".format(self.width)], ] - +# - # ## Launch EDB # @@ -89,6 +90,7 @@ def points(self): # # Define parameters: +# + edb["w1"] = 1.4e-3 edb["h1"] = 1.2e-3 edb["initial_position"] = 0.0 @@ -97,13 +99,16 @@ def points(self): first_patch = Patch(width="w1", height="h1", position="initial_position") edb.modeler.create_polygon(first_patch.points, "TOP", net_name="Array_antenna") +# - # First line + first_line = Line(length="l1", width="trace_w", position=first_patch.width) edb.modeler.create_polygon(first_line.points, "TOP", net_name="Array_antenna") # Now use the ``LinearArray`` class to create the array. +# + edb["w2"] = 2.29e-3 edb["h2"] = 3.3e-3 edb["l2"] = 1.9e-3 @@ -127,6 +132,7 @@ def points(self): current_patch += 1 linear_array.length = current_position +# - # Add the ground conductor. @@ -211,6 +217,7 @@ def points(self): solution_type="Terminal") # Set units to mm. + h3d.modeler.model_units = "mm" # ## Import the EDB as a 3D Component @@ -224,10 +231,12 @@ def points(self): # # If a layout component is parametric, parameters can be exposed and changed in HFSS +# + component.parameters w1_name = "{}_{}".format("w1", h3d.modeler.user_defined_component_names[0]) h3d[w1_name]= 0.0015 +# - # ### Radiation Boundary Assignment # @@ -238,10 +247,12 @@ def points(self): # # $$ \lambda/4 = \frac{c_0}{4 f} \approx 2.8mm $$ +# + h3d.modeler.fit_all() h3d.modeler.create_air_region(2.8, 2.8, 2.8, 2.8, 2.8, 2.8, is_percentage=False) h3d.assign_radiation_boundary_to_objects("Region") +# - # ### Analysis Setup # @@ -253,6 +264,7 @@ def points(self): setup.props['MaximumPasses'] = 10 # Specify properties of the frequency sweep: + sweep1 = setup.add_sweep(sweepname="20GHz_to_50GHz") sweep1.props["RangeStart"]="20GHz" sweep1.props["RangeEnd"]="50GHz" @@ -270,23 +282,22 @@ def points(self): solution = h3d.post.get_solution_data(trace[0]) solution.plot() - # ## Plot Far Fields in AEDT # # Plot Radiation patterns in AEDT. - +# + variations = {} variations["Freq"] = ["20GHz"] variations["Theta"] = ["All"] variations["Phi"] = ["All"] h3d.insert_infinite_sphere( name="3D") - new_report = h3d.post.reports_by_category.far_field("db(RealizedGainTotal)", h3d.nominal_adaptive, "3D") new_report.variations = variations new_report.primary_sweep = "Theta" new_report.create("Realized2D") +# - # ## Plot Far Fields in AEDT # @@ -296,7 +307,6 @@ def points(self): new_report.secondary_sweep = "Phi" new_report.create("Realized3D") - # ## Plot Far Fields outside AEDT # # Plot Radiation patterns outside AEDT. @@ -321,7 +331,7 @@ def points(self): # :func:`pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing AEDT. -h3d.save_project(os.path.join(tmpfold, "test_layout.aedt")) +h3d.save_project(os.path.join(temp_dir, "test_layout.aedt")) h3d.release_desktop() # ### Temp Directory Cleanup diff --git a/examples/00-EDB/04_edb_parametrized_design.py b/examples/00-EDB/04_edb_parametrized_design.py index c902a0c56ce..b806f2efb06 100644 --- a/examples/00-EDB/04_edb_parametrized_design.py +++ b/examples/00-EDB/04_edb_parametrized_design.py @@ -9,7 +9,6 @@ # # - import pyaedt import os import tempfile @@ -29,6 +28,7 @@ # Define the parameters. +# + params = {"$ms_width": "0.4mm", "$sl_width": "0.2mm", "$ms_spacing": "0.2mm", @@ -45,6 +45,7 @@ for par_name in params: edb.add_project_variable(par_name, params[par_name]) +# - # Define the stackup layers from bottom to top. @@ -56,7 +57,6 @@ {"name": "diel_1", "layer_type": "dielectric", "thickness": "275um", "material": "FR4_epoxy"}, {"name": "top", "layer_type": "signal", "thickness": "35um", "material": "copper"}] - # Create EDB stackup. # Bottom layer @@ -203,6 +203,7 @@ # Void in ground for traces on the signal routing layer +# + void_poly = [["$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2-$via_spacing/2"], ["$pcb_len/3 + $via_spacing", "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2-$via_spacing/2"], ["$pcb_len/3 + 2*$via_spacing", @@ -221,9 +222,11 @@ ["$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2"]] void_shape = edb.modeler.Shape("polygon", points=void_poly) +# - # Add ground conductors. +# + for layer in layers[:-1:2]: # add void if the layer is the signal routing layer. @@ -233,7 +236,7 @@ layer_name=layer["name"], voids=void, net_name="gnd") - +# - # Plot the layout. @@ -253,6 +256,7 @@ # # Add HFSS simulation setup. +# + setup = h3d.create_setup() setup.props["AdaptiveSettings"]["SingleFrequencyDataList"]["AdaptiveFrequencyData"]["MaxPasses"] = 3 @@ -269,7 +273,7 @@ save_fields=False, use_q3d_for_dc=False, ) - +# - # Define the differential pairs to be used to calculte differential and common mode # s-parameters. diff --git a/examples/00-EDB/05_Plot_nets.py b/examples/00-EDB/05_Plot_nets.py index c699e790b20..306b109954b 100644 --- a/examples/00-EDB/05_Plot_nets.py +++ b/examples/00-EDB/05_Plot_nets.py @@ -7,7 +7,7 @@ # ## Perform required imports -Perform required imports, which includes importing a section. +# Perform required imports, which includes importing a section. import os import pyaedt diff --git a/examples/00-EDB/06_Advanced_EDB.py b/examples/00-EDB/06_Advanced_EDB.py index 4b5fa9de5e6..0403985a3b8 100644 --- a/examples/00-EDB/06_Advanced_EDB.py +++ b/examples/00-EDB/06_Advanced_EDB.py @@ -15,8 +15,6 @@ temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") aedb_path = os.path.join(temp_dir.name, "parametric_via.aedb") - - # ## Create stackup # # The ``StackupSimple`` class creates a stackup based on few inputs. This stackup @@ -52,13 +50,13 @@ def create_ground_planes(edb, layers): soldermask_thickness=soldermask_thickness, dielectric_thickness=diel_thickness, dielectric_material=diel_material_name) - # ## Define Parameters # # Define parameters to allow changes in the model dimesons. Parameters preceeded by # the ``$`` character have project-wide scope. # Without the ``$`` prefix the parameter scope is limited to the design. +# + giva_angle_rad = gvia_angle / 180 * np.pi edb["$via_hole_size"] = "0.3mm" @@ -67,6 +65,7 @@ def create_ground_planes(edb, layers): edb.add_design_variable("via_pitch", "1mm", is_parameter=True) edb.add_design_variable("trace_in_width", "0.2mm", is_parameter=True) edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) +# - # ## Define padstacks # @@ -87,6 +86,7 @@ def create_ground_planes(edb, layers): # Place the ground vias. +# + gvia_num_side = gvia_num / 2 if gvia_num_side % 2: @@ -113,9 +113,11 @@ def create_ground_planes(edb, layers): edb.padstacks.place([xloc + "*-1", yloc], "GVIA", net_name="GND") edb.padstacks.place([xloc + "*-1", yloc + "*-1"], "GVIA", net_name="GND") +# - # Draw the traces +# + edb.modeler.create_trace( [[0, 0], [0, "-3mm"]], layer_name=trace_in_layer, net_name="RF", width="trace_in_width", start_cap_style="Flat", end_cap_style="Flat" ) @@ -128,6 +130,7 @@ def create_ground_planes(edb, layers): start_cap_style="Flat", end_cap_style="Flat", ) +# - # Draw ground conductors @@ -138,7 +141,6 @@ def create_ground_planes(edb, layers): # Display the layout -#edb.nets.plot(layers=["TOP", "L10"]) edb.stackup.plot(plot_definitions=["GVIA", "SVIA"]) # Save EDB and close the EDB. diff --git a/examples/00-EDB/10_GDS_workflow.py b/examples/00-EDB/10_GDS_workflow.py index f68320e2930..d838658a174 100644 --- a/examples/00-EDB/10_GDS_workflow.py +++ b/examples/00-EDB/10_GDS_workflow.py @@ -21,6 +21,7 @@ # - _dummy_layermap.map_ # maps properties to stackup layers. +# + temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") control_fn = "sky130_fictitious_dtc_example_control_no_map.xml" gds_fn = "sky130_fictitious_dtc_example.gds" @@ -30,20 +31,30 @@ c_file_in = os.path.join(local_path, control_fn) c_map = os.path.join(local_path, layer_map) gds_in = os.path.join(local_path, gds_fn) -gds_out = os.path.join(temppath, "gds_out.gds") +gds_out = os.path.join(temp_dir, "gds_out.gds") shutil.copy2(gds_in,gds_out ) -"" +# - + +# ## Control file +# +# A Control file is an xml file which purpose if to provide additional +# information during import phase. It can include, materials, stackup, setup, boundaries and settings. +# In this example we will import an existing xml, integrate it with a layer mapping file of gds +# and then adding setup and boundaries. + c = ControlFile(c_file_in, layer_map=c_map) # ## Simulation setup # # Here we setup simulation with HFSS and add a frequency sweep. + setup = c.setups.add_setup("Setup1", "1GHz") setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") # ## Additional stackup settings # # After import user can change stackup settings and add/remove layers or materials. + c.stackup.units = "um" c.stackup.dielectrics_base_elevation = -100 c.stackup.metal_layer_snapping_tolerance = "10nm" @@ -51,7 +62,6 @@ via.create_via_group = True via.snap_via_group = True - # ## Boundaries settings # # Boundaries can include ports, components and boundary extent. @@ -78,14 +88,17 @@ # # Import the gds and open the edb. +# + from pyaedt import Edb edb = Edb(gds_out, edbversion="2023.2", technology_file=os.path.join(temp_dir.name, "output.xml")) +# - # ## Plot Stackup # # Stackup plot. + edb.stackup.plot(first_layer="met1") # ## Close Edb diff --git a/examples/00-EDB/11_post_layout_parameterization.py b/examples/00-EDB/11_post_layout_parameterization.py index 4918b5ac7ac..126212b0fc0 100644 --- a/examples/00-EDB/11_post_layout_parameterization.py +++ b/examples/00-EDB/11_post_layout_parameterization.py @@ -9,6 +9,7 @@ layers = ["16_Bottom"] # Specify layers to be parameterized # Perform required imports. + import os import tempfile import pyaedt @@ -16,6 +17,7 @@ temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") # Download and open example layout file in edb format. + edb_path = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) edb = pyaedt.Edb(edb_path, edbversion="2023.2") @@ -25,10 +27,12 @@ # The ``Edb.cutout()`` method takes a list of # signal nets as the first argument and a list of # reference nets as the 2nd argument. + edb.cutout([signal_net_name], [coplanar_plane_net_name, "GND"], remove_single_pin_components=True) # Retrive the path segments from the signal net. + net = edb.nets[signal_net_name] trace_segments = [] for p in net.primitives: diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py index 160570c7066..02d974667b6 100644 --- a/examples/00-EDB/12_edb_sma_connector_on_board.py +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -22,6 +22,7 @@ # Create the EDB. +# + ansys_version = "2023.2" temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") working_folder = temp_dir.name @@ -29,6 +30,7 @@ aedb_path = os.path.join(working_folder, "pcb.aedb") edb = pyaedt.Edb(edbpath=aedb_path, edbversion=ansys_version) print("EDB is located at {}".format(aedb_path)) +# - # Defne the FR4 dielectric for the PCB. @@ -55,17 +57,20 @@ # Create ground conductors. +# + edb.add_design_variable("PCB_W", "20mm") edb.add_design_variable("PCB_L", "20mm") gnd_dict = {} for layer_name in edb.stackup.signal_layers.keys(): gnd_dict[layer_name] = edb.modeler.create_rectangle(layer_name, "GND", [0, "PCB_W/-2"], ["PCB_L", "PCB_W/2"]) +# - # ## Create signal net # # Create signal net on layer 3, and add clearance to the ground plane. +# + edb.add_design_variable("SIG_L", "10mm") edb.add_design_variable("SIG_W", "0.1mm") edb.add_design_variable("SIG_C", "0.3mm") @@ -76,6 +81,7 @@ signal_path = (["5mm", 0], ["PCB_L", 0]) clr = edb.modeler.create_trace(signal_path, "L3", "SIG_C*2+SIG_W", "SIG", "Flat", "Flat") gnd_dict["L3"].add_void(clr) +# - # ## Signal Vias # @@ -117,6 +123,7 @@ # located roughly $$ d=\lambda/8 $$ from the internal structures # in the model. +# + extend_domain = 3E11/5E9/8.0 # Quarter wavelength at 4 GHz. edb.design_options.antipads_always_on = True edb.hfss.hfss_extent_info.air_box_horizontal_extent = extend_domain @@ -126,6 +133,7 @@ setup = edb.create_hfss_setup("Setup1") setup.set_solution_single_frequency("5GHz", max_num_passes=8, max_delta_s="0.02") setup.hfss_solver_settings.order_basis = "first" +# - # Add a mesh operation to the setup. diff --git a/examples/00-EDB/15_ac_analysis.py b/examples/00-EDB/15_ac_analysis.py index 81d878d4ddc..0cc81a9f887 100644 --- a/examples/00-EDB/15_ac_analysis.py +++ b/examples/00-EDB/15_ac_analysis.py @@ -19,11 +19,13 @@ # # Download the AEDB file and copy it in the temporary folder. +# + temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") edb_full_path = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) time.sleep(5) print(edb_full_path) +# - # ### Configure EDB # @@ -60,6 +62,7 @@ # Prepare input data for port creation. +# + ports = [] for net_name, net_obj in diff_p.extended_net.nets.items(): for comp_name, comp_obj in net_obj.components.items(): @@ -76,6 +79,7 @@ "net_name":net_name}) print(*ports, sep="\n") +# - # ### Create ports # diff --git a/examples/00-EDB/index.rst b/examples/00-EDB/index.rst index e856feb3f2e..1577b1ccb2d 100644 --- a/examples/00-EDB/index.rst +++ b/examples/00-EDB/index.rst @@ -14,13 +14,13 @@ electronics desktop user interface. 01_edb_example.py 02_edb_to_ipc2581.py 03_5G_antenna_example_parametrics.py - 04_edb_parameterized_design.py + 04_edb_parametrized_design.py 05_Plot_nets.py 06_Advanced_EDB.py 09_Configuration.py 10_GDS_workflow.py - 11_post_layout_parameterization.py + 11_post_layout_parametrization.py 12_edb_sma_connector_on_board.py 13_edb_create_component.py - 14_edb_create_parameterized_design.py + 14_edb_create_parametrized_design.py 15_ac_analysis.py From 18ae1dacf1f68810465920e441d40fda965a171a Mon Sep 17 00:00:00 2001 From: Devin Date: Wed, 31 Jan 2024 09:35:03 -0600 Subject: [PATCH 22/56] Convert to md format 02-SBR+ --- examples/02-HFSS/Waveguide_Filter.py | 6 +- .../02-HFSS/_static}/wgf.png | Bin examples/02-SBR+/SBR_City_Import.py | 46 +++++------- examples/02-SBR+/SBR_Doppler_Example.py | 71 +++++++----------- examples/02-SBR+/SBR_Example.py | 61 ++++++--------- examples/02-SBR+/SBR_Time_Plot.py | 47 +++++------- 6 files changed, 95 insertions(+), 136 deletions(-) rename {doc/source/Resources => examples/02-HFSS/_static}/wgf.png (100%) diff --git a/examples/02-HFSS/Waveguide_Filter.py b/examples/02-HFSS/Waveguide_Filter.py index dc41698c837..3e6013b54ed 100644 --- a/examples/02-HFSS/Waveguide_Filter.py +++ b/examples/02-HFSS/Waveguide_Filter.py @@ -3,10 +3,10 @@ # This example shows how to build and analyze a 4-pole # X-Band waveguide filter using inductive irises. -# sphinx_gallery_thumbnail_path = 'Resources/wgf.png' +# -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports. # diff --git a/doc/source/Resources/wgf.png b/examples/02-HFSS/_static/wgf.png similarity index 100% rename from doc/source/Resources/wgf.png rename to examples/02-HFSS/_static/wgf.png diff --git a/examples/02-SBR+/SBR_City_Import.py b/examples/02-SBR+/SBR_City_Import.py index 88b66b020a1..fdc9912da79 100644 --- a/examples/02-SBR+/SBR_City_Import.py +++ b/examples/02-SBR+/SBR_City_Import.py @@ -1,29 +1,25 @@ -""" -SBR+: Import Geometry from Maps -------------------------------- -This example shows how you can use PyAEDT to create an HFSS SBR+ project from an -OpenStreeMaps. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # SBR+: Import Geometry from Maps +# +# This example shows how you can use PyAEDT to create an HFSS SBR+ project from an +# OpenStreeMaps. + +# ## Perform required imports +# # Perform required imports and set up the local path to the PyAEDT # directory path. import os from pyaedt import Hfss -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Define designs -# ~~~~~~~~~~~~~~ +# ## Define designs +# # Define two designs, one source and one target. # Each design is connected to a different object. @@ -35,15 +31,13 @@ non_graphical=non_graphical ) -############################################################################### -# Define Location to import -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define Location to import +# # Define latitude and longitude to import. ansys_home = [40.273726, -80.168269] -############################################################################### -# Generate map and import -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate map and import +# # Assign boundaries. app.modeler.import_from_openstreet_map(ansys_home, @@ -52,9 +46,8 @@ plot_before_importing=False, import_in_aedt=True) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. plot_obj = app.plot(show=False, plot_air_objects=True) @@ -65,9 +58,8 @@ plot_obj.bounding_box = False plot_obj.plot(os.path.join(app.working_directory, "Source.jpg")) -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT and close the example. app.release_desktop() diff --git a/examples/02-SBR+/SBR_Doppler_Example.py b/examples/02-SBR+/SBR_Doppler_Example.py index 07a015299d6..d208ae73e73 100644 --- a/examples/02-SBR+/SBR_Doppler_Example.py +++ b/examples/02-SBR+/SBR_Doppler_Example.py @@ -1,20 +1,17 @@ -""" -SBR+: doppler setup -------------------- -This example shows how you can use PyAEDT to create a multipart scenario in HFSS SBR+ -and set up a doppler analysis. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # SBR+: doppler setup +# +# This example shows how you can use PyAEDT to create a multipart scenario in HFSS SBR+ +# and set up a doppler analysis. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT. aedt_version = "2023.2" @@ -22,17 +19,15 @@ designname = "doppler" library_path = pyaedt.downloads.download_multiparts() -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Download and open project -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download and open project +# # Download and open the project. project_name = pyaedt.generate_unique_project_name(project_name="doppler") @@ -49,17 +44,15 @@ app.autosave_disable() -############################################################################### -# Save project and rename design -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and rename design +# # Save the project to the temporary folder and rename the design. app.save_project() app.rename_design(designname) -############################################################################### -# Set up library paths -# ~~~~~~~~~~~~~~~~~~~~ +# ## Set up library paths +# # Set up library paths to 3D components. actor_lib = os.path.join(library_path, "actor_library") @@ -71,17 +64,15 @@ bike_folder = os.path.join(actor_lib, "bike1") bird_folder = os.path.join(actor_lib, "bird1") -############################################################################### -# Define environment -# ~~~~~~~~~~~~~~~~~~ +# ## Define environment +# # Define the background environment. road1 = app.modeler.add_environment(env_folder=env_folder, environment_name="Bari") prim = app.modeler -############################################################################### -# Place actors -# ~~~~~~~~~~~~ +# ## Place actors +# # Place actors in the environment. This code places persons, birds, bikes, and cars # in the environment. @@ -108,9 +99,8 @@ actor_folder=bird_folder, speed=1.0, global_offset=[6, 2, 3], yaw=-60, pitch=10, actor_name="Eagle" ) -############################################################################### -# Place radar -# ~~~~~~~~~~~ +# ## Place radar +# # Place radar on the car. The radar is created relative to the car's coordinate # system. @@ -122,9 +112,8 @@ relative_cs_name=car1.cs_name, ) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create setup and validate it. The ``create_sbr_pulse_doppler_setup`` method # creates a setup and a parametric sweep on the time variable with a # duration of two seconds. The step is computed automatically from CPI. @@ -133,16 +122,14 @@ app.set_sbr_current_sources_options() app.validate_simple() -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. app.plot(show=False, export_path=os.path.join(app.working_directory, "Image.jpg"), plot_air_objects=True) -############################################################################### -# Solve and release AEDT -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Solve and release AEDT +# # Solve and release AEDT. To solve, uncomment the ``app.analyze_setup`` command # to activate the simulation. diff --git a/examples/02-SBR+/SBR_Example.py b/examples/02-SBR+/SBR_Example.py index 4616973cd06..85e539e1b7f 100644 --- a/examples/02-SBR+/SBR_Example.py +++ b/examples/02-SBR+/SBR_Example.py @@ -1,12 +1,10 @@ -""" -SBR+: HFSS to SBR+ coupling ---------------------------- -This example shows how you can use PyAEDT to create an HFSS SBR+ project from an -HFSS antenna and run a simulation. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # SBR+: HFSS to SBR+ coupling +# +# This example shows how you can use PyAEDT to create an HFSS SBR+ project from an +# HFSS antenna and run a simulation. + +# ## Perform required imports +# # Perform required imports and set up the local path to the path for the PyAEDT # directory. @@ -15,17 +13,15 @@ project_full_name = pyaedt.downloads.download_sbr(pyaedt.generate_unique_project_name(project_name="sbr_freq")) -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Define designs -# ~~~~~~~~~~~~~~ +# ## Define designs +# # Define two designs, one source and one target, with each design connected to # a different object. @@ -43,32 +39,28 @@ specified_version="2023.2", ) -############################################################################### -# Define linked antenna -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define linked antenna +# # Define a linked antenna. This is HFSS far field applied to HFSS SBR+. target.create_sbr_linked_antenna(source, target_cs="feederPosition", fieldtype="farfield") -############################################################################### -# Assign boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Assign boundaries +# # Assign boundaries. target.assign_perfecte_to_sheets(["Reflector", "Subreflector"]) target.mesh.assign_curvilinear_elements(["Reflector", "Subreflector"]) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model source.plot(show=False, export_path=os.path.join(target.working_directory, "Source.jpg"), plot_air_objects=True) target.plot(show=False, export_path=os.path.join(target.working_directory, "Target.jpg"), plot_air_objects=False) -############################################################################### -# Create setup and solve -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and solve +# # Create a setup and solve it. setup1 = target.create_setup() @@ -80,9 +72,8 @@ setup1["RangeStart"] = "10GHz" target.analyze() -############################################################################### -# Plot results -# ~~~~~~~~~~~~ +# ## Plot results +# # Plot results. variations = target.available_variations.nominal_w_values_dict @@ -98,9 +89,8 @@ report_category="Far Fields", ) -############################################################################### -# Plot results outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot results outside AEDT +# # Plot results using Matplotlib. solution = target.post.get_solution_data( @@ -113,9 +103,8 @@ ) solution.plot() -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT and close the example. target.release_desktop() diff --git a/examples/02-SBR+/SBR_Time_Plot.py b/examples/02-SBR+/SBR_Time_Plot.py index c227f3ee99f..ad3d249daeb 100644 --- a/examples/02-SBR+/SBR_Time_Plot.py +++ b/examples/02-SBR+/SBR_Time_Plot.py @@ -1,29 +1,24 @@ -""" -SBR+: HFSS to SBR+ time animation ---------------------------------- -This example shows how you can use PyAEDT to create an SBR+ time animation -and save it to a GIF file. This example works only on CPython. -""" - -############################################################################### -# Perform required imports. -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# # SBR+: HFSS to SBR+ time animation +# +# This example shows how you can use PyAEDT to create an SBR+ time animation +# and save it to a GIF file. This example works only on CPython. + +# ## Perform required imports. +# # Perform required imports. import os from pyaedt import Hfss, downloads -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT and load project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and load project +# # Launch AEDT and load the project. project_file = downloads.download_sbr_time() @@ -32,9 +27,8 @@ hfss.analyze() -############################################################################### -# Get solution data -# ~~~~~~~~~~~~~~~~~ +# ## Get solution data +# # Get solution data. After simulation is performed, you can load solutions # in the ``solution_data`` object. @@ -43,25 +37,22 @@ context="Near_Field", report_category="Near Fields") -############################################################################### -# Compute IFFT -# ~~~~~~~~~~~~ +# ## Compute IFFT +# # Compute IFFT (Inverse Fast Fourier Transform). t_matrix = solution_data.ifft("NearE", window=True) -############################################################################### -# Export IFFT to CSV file -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Export IFFT to CSV file +# # Export IFFT to a CSV file. frames_list_file = solution_data.ifft_to_file(coord_system_center=[-0.15, 0, 0], db_val=True, csv_dir=os.path.join(hfss.working_directory, "csv")) -############################################################################### -# Plot scene -# ~~~~~~~~~~ +# ## Plot scene +# # Plot the scene to create the time plot animation hfss.post.plot_scene(frames_list=frames_list_file, From 1e0f3aad7fd0bc39e413d8497757a8da1b9e154e Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Thu, 1 Feb 2024 06:49:56 +0100 Subject: [PATCH 23/56] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- examples/00-EDB/00_EDB_Create_VIA.py | 8 +++---- examples/00-EDB/01_edb_example.py | 36 ++++++++++++++-------------- examples/00-EDB/02_edb_to_ipc2581.py | 6 ++--- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/00-EDB/00_EDB_Create_VIA.py b/examples/00-EDB/00_EDB_Create_VIA.py index 5cc4f610e6f..71421ff955b 100644 --- a/examples/00-EDB/00_EDB_Create_VIA.py +++ b/examples/00-EDB/00_EDB_Create_VIA.py @@ -22,7 +22,7 @@ # ## Add stackup layers # Add stackup layers. -# A stackup can be created layer by layer or imported from a csv file or xml file. +# A stackup can be created layer by layer or imported from a CSV file or XML file. edb.stackup.add_layer("GND") edb.stackup.add_layer("Diel", "GND", layer_type="dielectric", thickness="0.1mm", material="FR4_epoxy") @@ -53,11 +53,11 @@ edb.padstacks.place([45e-3, -5e-3], "MyVia") -# ## Geometry Plot +# ## Generate geometry plot edb.nets.plot(None, color_by_net=True) -# ## Stackup Plot +# ## Generate stackup plot edb.stackup.plot(plot_definitions="MyVia") @@ -69,7 +69,7 @@ edb.close_edb() print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) -# ### Temp Directory Cleanup +# ### Clean up temporary directory # # The following command removes the project and the temporary directory. If you'd like to save this project, save it to a folder of your choice prior to running the following cell. diff --git a/examples/00-EDB/01_edb_example.py b/examples/00-EDB/01_edb_example.py index bf19005453e..0df5a65c8ea 100644 --- a/examples/00-EDB/01_edb_example.py +++ b/examples/00-EDB/01_edb_example.py @@ -19,7 +19,7 @@ aedt_file = targetfile[:-4] + "aedt" # - -# ## Electronics Database (EDB) +# ## Launch Ansys Electronics Database (EDB) # # Instantiate an instance of the `pyaedt.Edb` class # using EDB 2023 R2 and SI units. @@ -31,7 +31,7 @@ # ## Identify nets and components # -# The ``Edb.nets.netlist`` and ``Edb.components.components`` propreties contain information +# The ``Edb.nets.netlist`` and ``Edb.components.components`` properties contain information # about all of the nets and components. The following cell uses this information to print the number of nets and # components. @@ -40,9 +40,9 @@ print("Components {}".format(len(edb.components.components.keys()))) print("elapsed time = ", time.time() - start) -# ## Identify Pin Positions +# ## Identify pin positions # -# The next section shows how to obtain all pins for a specific component and +# This code shows how to obtain all pins for a specific component and # print the ``[x, y]`` position of each pin. pins = edb.components["U2"].pins @@ -57,10 +57,10 @@ count += 1 # Get all nets connected to a specific component. Print -# the pin and the name of the net to which it is connected. +# the pin and the name of the net that it is connected to. connections = edb.components.get_component_net_connection_info("U2") -n_print = 0 # Counter to limite the number of printed lines. +n_print = 0 # Counter to limit the number of printed lines. print_max = 15 for m in range(len(connections["pin_name"])): ref_des = connections["refdes"][m] @@ -77,13 +77,13 @@ rats = edb.components.get_rats() -# ## Idenify Connected Nets +# ## Identify connected nets # -# The method ``get_dcconnected_net_list()`` retrieves a list of +# The ``get_dcconnected_net_list()`` method retrieves a list of # all DC-connected power nets. Each group of connected nets is returned -# as a [set](https://docs.python.org/3/tutorial/datastructures.html#sets) -# The first argument to the method is the list of ground nets which will -# not be considered in the search for connected nets. +# as a [set](https://docs.python.org/3/tutorial/datastructures.html#sets). +# The first argument to the method is the list of ground nets, which are +# not considered in the search for connected nets. GROUND_NETS = ["GND", "GND_DP"] dc_connected_net_list = edb.nets.get_dcconnected_net_list(GROUND_NETS) @@ -121,16 +121,16 @@ print(s) # - -# ## Remove Unused Components +# ## Remove unused components # # Delete all RLC components that are connected with only one pin. -# The method ``Edb.components.delete_single_pin_rlc()`` +# The ``Edb.components.delete_single_pin_rlc()`` method # provides a useful way to # remove components that are not needed for the simulation. edb.components.delete_single_pin_rlc() -# Unused components can also be removed explicitly by name. +# You can also remove unused components explicitly by name. edb.components.delete("C380") @@ -139,14 +139,14 @@ edb.nets.delete("PDEN") # Print the top and bottom elevation of the stackup obtained using -# the method ``Edb.stackup.limits()``. +# the ``Edb.stackup.limits()`` method. s = "Top layer name: \"{top}\", Elevation: {top_el:.2f} " s += "mm\nBottom layer name: \"{bot}\", Elevation: {bot_el:2f} mm" top, top_el, bot, bot_el = edb.stackup.limits() print(s.format(top = top, top_el = top_el*1E3, bot = bot, bot_el = bot_el*1E3)) -# ## Setup for SIwave DCIR analysis +# ## Set up for SIwave DCIR analysis # # Create a voltage source and then set up a DCIR analysis. @@ -166,14 +166,14 @@ siw_file = edb.solve_siwave() -# ## Export Results +# ## Export results # # Export all quantities calculated from the DC-IR analysis. The following method runs SIwave in batch mode from # the command line. Results are written to the edb folder. outputs = edb.export_siwave_dc_results(siw_file, setup.name, ) -# Close the EDB. After EDB is closed, it can be opened by AEDT. +# Close EDB. After EDB is closed, it can be opened by AEDT. edb.close_edb() diff --git a/examples/00-EDB/02_edb_to_ipc2581.py b/examples/00-EDB/02_edb_to_ipc2581.py index c1864bf900f..230dd25815e 100644 --- a/examples/00-EDB/02_edb_to_ipc2581.py +++ b/examples/00-EDB/02_edb_to_ipc2581.py @@ -18,8 +18,8 @@ # ## Launch EDB # -# Launch the `pyaedt.Edb` class, using Verson 2023. -# > Note that length dimensions passed to Edb are in SI units. +# Launch the `pyaedt.Edb` class, using EDB 2023. +# > Note that length dimensions passed to EDB are in SI units. edb = pyaedt.Edb(edbpath=targetfile, edbversion="2023.2") @@ -46,7 +46,7 @@ ) edb.nets.plot(None, None, color_by_net=True) -# Export the EDB to IPC2581 file. +# Export the EDB to an IPC2581 file. edb.export_to_ipc2581(ipc2581_file_name, "inch") print("IPC2581 File has been saved to {}".format(ipc2581_file_name)) From 3233439ee6075c5e30e2029b6f9744a9e8c12506 Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Thu, 1 Feb 2024 06:57:17 +0100 Subject: [PATCH 24/56] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- .../03_5G_antenna_example_parametrics.py | 36 +++++++++---------- examples/00-EDB/04_edb_parametrized_design.py | 14 ++++---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/00-EDB/03_5G_antenna_example_parametrics.py b/examples/00-EDB/03_5G_antenna_example_parametrics.py index b6e838e8b8f..692f7539c6d 100644 --- a/examples/00-EDB/03_5G_antenna_example_parametrics.py +++ b/examples/00-EDB/03_5G_antenna_example_parametrics.py @@ -16,10 +16,10 @@ non_graphical = False -# ## Creating data classes +# ## Create data classes # # Data classes are useful to do calculations and store variables. -# We create 3 Data classes for Patch, Line and Array +# There are three data classes: ``Patch``, ``Line``, and ``Array``. # + class Patch: @@ -138,7 +138,7 @@ def points(self): edb.modeler.create_polygon(linear_array.points, "GND", net_name="GND") -# Add connector pin that will be used to assign the port. +# Add the connector pin to use to assign the port. edb.padstacks.create(padstackname="Connector_pin", holediam="100um", paddiam="0", antipaddiam="200um") con_pin = edb.padstacks.place( @@ -199,7 +199,7 @@ def points(self): edb.close_edb() print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) -# ## 3D Component in HFSS +# ## 3D component in HFSS # # First create an instance of the ``pyaedt.Hfss`` class. If you set # > ``non_graphical = False @@ -216,20 +216,20 @@ def points(self): close_on_exit=True, solution_type="Terminal") -# Set units to mm. +# Set units to ``mm``. h3d.modeler.model_units = "mm" -# ## Import the EDB as a 3D Component +# ## Import the EDB as a 3D component # # One or more layout components can be imported into HFSS. The combination of layout data and 3D CAD data helps streamline # model creation and setup. component = h3d.modeler.insert_layout_component(aedb_path, parameter_mapping=True) -# ## Expose the Component Paramers +# ## Expose the component parameters # -# If a layout component is parametric, parameters can be exposed and changed in HFSS +# If a layout component is parametric, you can expose and change parameters in HFSS # + component.parameters @@ -254,9 +254,9 @@ def points(self): h3d.assign_radiation_boundary_to_objects("Region") # - -# ### Analysis Setup +# ### Set up analysis # -# The finite element mesh is adapted iteratively. The maximum number of adaptive passes is set using the ``MaximumPasses`` property. This model will converge such that the $S_{11}$ will be independent of the mesh. The default accuracy setting is: +# The finite element mesh is adapted iteratively. The maximum number of adaptive passes is set using the ``MaximumPasses`` property. This model converges such that the $S_{11}$ is independent of the mesh. The default accuracy setting is: # $$ \max(|\Delta S|) < 0.02 $$ setup = h3d.create_setup() @@ -282,9 +282,9 @@ def points(self): solution = h3d.post.get_solution_data(trace[0]) solution.plot() -# ## Plot Far Fields in AEDT +# ## Plot far fields in AEDT # -# Plot Radiation patterns in AEDT. +# Plot radiation patterns in AEDT. # + variations = {} @@ -299,24 +299,24 @@ def points(self): new_report.create("Realized2D") # - -# ## Plot Far Fields in AEDT +# ## Plot far fields in AEDT # -# Plot Radiation patterns in AEDT. +# Plot radiation patterns in AEDT new_report.report_type = "3D Polar Plot" new_report.secondary_sweep = "Phi" new_report.create("Realized3D") -# ## Plot Far Fields outside AEDT +# ## Plot far fields outside AEDT # -# Plot Radiation patterns outside AEDT. +# Plot radiation patterns outside AEDT solutions_custom = new_report.get_solution_data() solutions_custom.plot_3d() # ## Plot E Field on nets and layers # -# Plot E Field on nets and layers in AEDT. +# Plot E Field on nets and layers in AEDT h3d.post.create_fieldplot_layers_nets( [["TOP","Array_antenna"]], @@ -334,7 +334,7 @@ def points(self): h3d.save_project(os.path.join(temp_dir, "test_layout.aedt")) h3d.release_desktop() -# ### Temp Directory Cleanup +# ### Clean up the temporary directory # # The following command removes the project and the temporary directory. If you'd like to save this project, save it to a folder of your choice prior to running the following cell. diff --git a/examples/00-EDB/04_edb_parametrized_design.py b/examples/00-EDB/04_edb_parametrized_design.py index b806f2efb06..29394147992 100644 --- a/examples/00-EDB/04_edb_parametrized_design.py +++ b/examples/00-EDB/04_edb_parametrized_design.py @@ -15,8 +15,8 @@ # ## Set non-graphical mode # -# Set non-graphical mode. The default is ``False`` in order to open -# the AEDT user interface. +# Set non-graphical mode. The default is ``False``, which opens +# the AEDT UI. non_graphical = False @@ -57,8 +57,8 @@ {"name": "diel_1", "layer_type": "dielectric", "thickness": "275um", "material": "FR4_epoxy"}, {"name": "top", "layer_type": "signal", "thickness": "35um", "material": "copper"}] -# Create EDB stackup. -# Bottom layer +# Create the EDB stackup. +# Define the bottom layer prev = None for layer in layers: @@ -247,7 +247,7 @@ edb.save_edb() edb.close_edb() -# Open the project in AEDT 3D Layout. +# Open the project in HFSS 3D Layout. h3d = pyaedt.Hfss3dLayout(projectname=aedb_path, specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) @@ -275,7 +275,7 @@ ) # - -# Define the differential pairs to be used to calculte differential and common mode +# Define the differential pairs to used to calculate differential and common mode # s-parameters. h3d.set_differential_pair(diff_name="In", positive_terminal="wave_port_1:T1", negative_terminal="wave_port_1:T2") @@ -285,7 +285,7 @@ h3d.analyze() -# Plot the results and shut down the Electronics Desktop. +# Plot the results and shut down AEDT. solutions = h3d.post.get_solution_data(["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs") solutions.plot() From 831db923e1129a0c02c0e34f5e924a92a2c32c75 Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:05:56 +0100 Subject: [PATCH 25/56] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- examples/00-EDB/05_Plot_nets.py | 18 +++++----- examples/00-EDB/06_Advanced_EDB.py | 8 ++--- examples/00-EDB/09_Configuration.py | 55 +++++++++++++++-------------- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/examples/00-EDB/05_Plot_nets.py b/examples/00-EDB/05_Plot_nets.py index 306b109954b..45519aa3af4 100644 --- a/examples/00-EDB/05_Plot_nets.py +++ b/examples/00-EDB/05_Plot_nets.py @@ -22,29 +22,29 @@ # Create an instance of the Electronics Database usig the # `pyaedt.Edb` class. # -# > Note that units are SI +# > Note that units are SI. edb = pyaedt.Edb(edbpath=targetfolder, edbversion="2023.2") -# Display the nets on a layer. Net geometry can be displayed directly in Python usig ``matplotlib`` from +# Display the nets on a layer. You can display the net geometry directly in Python using ``matplotlib`` from # the ``pyaedt.Edb`` class. edb.nets.plot("AVCC_1V3") -# Multiple nets may be viewed by passing a list containing the net -# names to the ``plot`` method. +# You can view multiple nets by passing a list containing the net +# names to the ``plot()`` method. edb.nets.plot(["GND", "GND_DP", "AVCC_1V3"], color_by_net=True) -# All copper on a single layer may also be displayed by passing ``None`` -# as the first argument. The 2nd argument is a list -# of layers to be plotted. In this case, only one -# layers is displayed. +# You can display all copper on a single layer by passing ``None`` +# as the first argument. The second argument is a list +# of layers to plot. In this case, only one +# layer is to be displayed. edb.nets.plot(None, ["1_Top"], color_by_net=True, plot_components_on_top=True) -# A side-view of the layers and padstack geometry is displayed using the +# Display a side view of the layers and padstack geometry using the # ``Edb.stackup.plot()`` method. edb.stackup.plot(scale_elevation=False, diff --git a/examples/00-EDB/06_Advanced_EDB.py b/examples/00-EDB/06_Advanced_EDB.py index 0403985a3b8..d6d22a58918 100644 --- a/examples/00-EDB/06_Advanced_EDB.py +++ b/examples/00-EDB/06_Advanced_EDB.py @@ -10,7 +10,7 @@ import pyaedt import tempfile -# Creat the EDB project. +# Create the EDB project. temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") aedb_path = os.path.join(temp_dir.name, "parametric_via.aedb") @@ -29,7 +29,7 @@ def create_ground_planes(edb, layers): # ## Create the EDB # -# Create the Electronics Databas (EDB) instance. If the path doesn't exist, PyAEDT automatically generates a new AEDB folder. +# Create the EDB instance. If the path doesn't exist, PyAEDT automatically generates a new AEDB folder. edb = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") @@ -50,11 +50,11 @@ def create_ground_planes(edb, layers): soldermask_thickness=soldermask_thickness, dielectric_thickness=diel_thickness, dielectric_material=diel_material_name) -# ## Define Parameters +# ## Define parameters # # Define parameters to allow changes in the model dimesons. Parameters preceeded by # the ``$`` character have project-wide scope. -# Without the ``$`` prefix the parameter scope is limited to the design. +# Without the ``$`` prefix, the parameter scope is limited to the design. # + giva_angle_rad = gvia_angle / 180 * np.pi diff --git a/examples/00-EDB/09_Configuration.py b/examples/00-EDB/09_Configuration.py index 1ca84e0eda3..9a07d3ca74e 100644 --- a/examples/00-EDB/09_Configuration.py +++ b/examples/00-EDB/09_Configuration.py @@ -7,7 +7,7 @@ # ## Perform required imports # # The ``Hfss3dlayout`` class provides an interface to -# the 3D Layout editor in Elecronics Deskop. +# the 3D Layout editor in AEDT. # on version 2023 R2. import os @@ -19,23 +19,23 @@ temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) -print("Project folder will be", target_aedb) +print("Project folder is", target_aedb) # ## Launch EDB # -# Launch the `pyaedt.Edb` class using EDB 2023 R2. Length units are SI. +# Launch the ``pyaedt.Edb`` class using EDB 2023 R2. Length units are SI. edbapp = pyaedt.Edb(target_aedb, edbversion="2023.2") -# ## Import Definitions +# ## Import definitions # # The definition file uses the [json](https://www.json.org/json-en.html) to # map layout part numbers to their corresponding models. # -# The model may be RLC, Sparameter or a +# The model may be an RLC, S-parameter, or # [SPICE](https://en.wikipedia.org/wiki/SPICE) model definition. -# Once imported the, definition is applied to the components in the layout. -# In this example the json file is in the ``*.aedb`` folder and has the following format: +# Once imported, the definition is applied to the components in the layout. +# In this example, the JSON file is in the ``*.aedb`` folder and has the following format: # ``` json # { # "SParameterModel": { @@ -67,7 +67,7 @@ # } # ``` # -# The method ``Edb.components.import_definitions`` imports the componet definitions that map electrical models to the components in the simulaton model. +# The ``Edb.components.import_definitions()`` method imports the component definitions that map electrical models to the components in the simulation model. edbapp.components.import_definition(os.path.join(target_aedb, "1_comp_definition.json")) @@ -79,7 +79,7 @@ # # Components that are not contained in the BOM are deactivated in the # simulation model. -# In this example the csv file was saved in the aedb folder. +# This example saves the CSV file in the ``aedb`` folder. # # ``` # +------------+-----------------------+-----------+------------+ @@ -105,22 +105,23 @@ comp = edbapp.components["C1"] comp.model_type, comp.value -# ## Check Component Definition +# ## Check component definition # # When an s-parameter model is associated to a component it will be available in nport_comp_definition property. edbapp.components.nport_comp_definition edbapp.save_edb() -# ## Configure the Simulation Setup +# ## Configure the simulation setup # # This step enables the following: -# - Definition of nets to be included in a cutout region -# - Cutout details -# - Components on which to create the ports -# - Simulation settings + +# - Definition of the nets to include in the cutout region +# - Cutout details +# - Components to create the ports on +# - Simulation settings # -# The method ``Edb.new_simulaton_configuration()`` returns an instance +# The ``Edb.new_simulaton_configuration()`` method returns an instance # of the [``SimulationConfiguration``](https://aedt.docs.pyansys.com/version/stable/EDBAPI/SimulationConfigurationEdb.html) class. # + @@ -143,23 +144,23 @@ sim_setup.ac_settings.step_freq = "10MHz" # - -# ## Implement the Setup +# ## Implement the setup # # The cutout and all other simulation settings are applied to the simulation model. sim_setup.export_json(os.path.join(temp_dir.name, "configuration.json")) edbapp.build_simulation_project(sim_setup) -# ## Display the Cutout +# ## Display the cutout # # Plot cutout once finished. The model is ready to simulate. edbapp.nets.plot(None,None) -# ## Save and Close EDB +# ## Save and close EDB # -# Edb will be saved and re-opened in Electronics -# Deskopt 3D Layout. The HFSS simulation can then be run. +# EDB is saved and re-opened in HFSS +# 3D Layout, where the HFSS simulation can be run. edbapp.save_edb() edbapp.close_edb() @@ -177,25 +178,25 @@ # ## Analyze # -# This project is ready to solve. Executing the following cell runs the HFSS simulatoin on the layout. +# This project is ready to solve. Executing the following cell runs the HFSS simulation on the layout. h3d.analyze() -# ## View Results +# ## View results # -# S-Parameter data will be loaded at the end of simulation. +# S-parameter data is loaded at the end of simulation. solutions = h3d.post.get_solution_data() -# ## Plot Results +# ## Plot results # # Plot S-Parameter data. solutions.plot(solutions.expressions, "db20") -# ## Save and Close AEDT +# ## Save and close AEDT # -# Hfss3dLayout is saved and closed. +# HFSS 3D Layout is saved and closed. h3d.save_project() h3d.release_desktop() From 74652b51c9d04e2461345ceb4a449943f0402806 Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:31:24 +0100 Subject: [PATCH 26/56] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- examples/00-EDB/10_GDS_workflow.py | 22 +++++------ .../00-EDB/11_post_layout_parameterization.py | 12 +++--- .../00-EDB/12_edb_sma_connector_on_board.py | 38 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/examples/00-EDB/10_GDS_workflow.py b/examples/00-EDB/10_GDS_workflow.py index d838658a174..7215dc6fdd7 100644 --- a/examples/00-EDB/10_GDS_workflow.py +++ b/examples/00-EDB/10_GDS_workflow.py @@ -44,16 +44,16 @@ c = ControlFile(c_file_in, layer_map=c_map) -# ## Simulation setup +# ## Set up simulation # -# Here we setup simulation with HFSS and add a frequency sweep. +# This code sets up a simulation with HFSS and adds a frequency sweep. setup = c.setups.add_setup("Setup1", "1GHz") setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") -# ## Additional stackup settings +# ## Provide additional stackup settings # -# After import user can change stackup settings and add/remove layers or materials. +# After import, you can change the stackup settings and add or remove layers or materials. c.stackup.units = "um" c.stackup.dielectrics_base_elevation = -100 @@ -62,7 +62,7 @@ via.create_via_group = True via.snap_via_group = True -# ## Boundaries settings +# ## Define boundary settings # # Boundaries can include ports, components and boundary extent. @@ -78,13 +78,13 @@ comp.add_pin("4", "81.28", "214.6", "met2") c.import_options.import_dummy_nets = True -# ## Write xml +# ## Write XML file # -# After all settings are ready we can write xml. +# After all settings are ready, you can write an XML file. c.write_xml(os.path.join(temp_dir.name, "output.xml")) -# ## Open Edb +# ## Open EDB # # Import the gds and open the edb. @@ -95,13 +95,13 @@ technology_file=os.path.join(temp_dir.name, "output.xml")) # - -# ## Plot Stackup +# ## Plot stackup # -# Stackup plot. +# Plot the stackup. edb.stackup.plot(first_layer="met1") -# ## Close Edb +# ## Close EDB # # Close the project. diff --git a/examples/00-EDB/11_post_layout_parameterization.py b/examples/00-EDB/11_post_layout_parameterization.py index 126212b0fc0..34a019edc82 100644 --- a/examples/00-EDB/11_post_layout_parameterization.py +++ b/examples/00-EDB/11_post_layout_parameterization.py @@ -5,8 +5,8 @@ # Define input parameters. signal_net_name = "DDR4_ALERT3" -coplanar_plane_net_name = "1V0" # Specify coplanar plane net name for adding clearance -layers = ["16_Bottom"] # Specify layers to be parameterized +coplanar_plane_net_name = "1V0" # Specify name of coplanar plane net for adding clearance +layers = ["16_Bottom"] # Specify layers to parameterize # Perform required imports. @@ -22,16 +22,16 @@ destination=temp_dir.name) edb = pyaedt.Edb(edb_path, edbversion="2023.2") -# ## Cutout +# ## Create cutout # # The ``Edb.cutout()`` method takes a list of # signal nets as the first argument and a list of -# reference nets as the 2nd argument. +# reference nets as the second argument. edb.cutout([signal_net_name], [coplanar_plane_net_name, "GND"], remove_single_pin_components=True) -# Retrive the path segments from the signal net. +# Retrieve the path segments from the signal net. net = edb.nets[signal_net_name] trace_segments = [] @@ -76,7 +76,7 @@ edb.nets.plot(layers=layers[0], size=2000) -# Save and close the EDB. +# Save the AEDB file and close EDB. save_edb_path = os.path.join(temp_dir.name, "post_layout_parameterization.aedb") edb.save_edb_as(save_edb_path) diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py index 02d974667b6..461c948ea7a 100644 --- a/examples/00-EDB/12_edb_sma_connector_on_board.py +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -7,11 +7,11 @@ # 3. Create HFSS setup and frequency sweep with a mesh operation. # 4. Create return loss plot -# ## The Finished Project +# ## See the finished project # # -# ## Create parameterized PCB +# ## Create a parameterized PCB # # Import dependencies. @@ -29,18 +29,18 @@ aedb_path = os.path.join(working_folder, "pcb.aedb") edb = pyaedt.Edb(edbpath=aedb_path, edbversion=ansys_version) -print("EDB is located at {}".format(aedb_path)) +print("AEDB file is located in {}".format(aedb_path)) # - -# Defne the FR4 dielectric for the PCB. +# Add the FR4 dielectric for the PCB. edb.materials.add_dielectric_material("ANSYS_FR4", 3.5, 0.005) # ## Create Stackup # -# The stackup is defined explicitly here, but also can be imported -# from a from a csv or xml file using the method -# ``Edb.stackup.import_stackup()``. +# While this code explicitly defines the stackup, you can import it +# from a from a CSV or XML file using the +# ``Edb.stackup.import_stackup()`` method. edb.add_design_variable("$DIEL_T", "0.15mm") edb.stackup.add_layer("BOT") @@ -83,9 +83,9 @@ gnd_dict["L3"].add_void(clr) # - -# ## Signal Vias +# ## Place signal vias # -# Create via padstack definition. Place the signal vias. +# Create the via padstack definition and place the signal vias. edb.add_design_variable("SG_VIA_D", "1mm") edb.add_design_variable("$VIA_AP_D", "1.2mm") @@ -93,7 +93,7 @@ edb.padstacks.place(["5mm", 0], "ANSYS_VIA", "SIG") # Create ground vias around the SMA -# connector launch footprint. The vas +# connector launch footprint. The vias # are placed around the circumference # of the launch from 35 degrees to 325 # degrees. @@ -103,7 +103,7 @@ py = np.sin(i / 180 * np.pi) edb.padstacks.place(["{}*{}+5mm".format("SG_VIA_D", px), "{}*{}".format("SG_VIA_D", py)], "ANSYS_VIA", "GND") -# Create ground vias along signal trace. +# Create ground vias along the signal trace. for i in np.arange(2e-3, edb.variables["SIG_L"].value - 2e-3, 2e-3): edb.padstacks.place(["{}+5mm".format(i), "1mm"], "ANSYS_VIA", "GND") @@ -113,9 +113,9 @@ signal_trace.create_edge_port("port_1", "End", "Wave", horizontal_extent_factor=10) -# ## HFSS Simulation Setup +# ## Set up HFSS simulation # -# The named argument ``max_num_passes`` sets an upper limit on the +# The ``max_num_passes`` argument sets an upper limit on the # number of adaptive passes for mesh refinement. # # For broadband applications when the simulation results may be used @@ -139,11 +139,11 @@ edb.setups["Setup1"].add_length_mesh_operation({"SIG": ["L3"]}, "m1", max_length="0.1mm") -# Add frequency sweep to setup. +# Add a frequency sweep to setup. # -# When the simulation results will -# be used for transient SPICE analysis, it is advisible -# to use the following strategy. +# When the simulation results are to +# be used for transient SPICE analysis, you should +# use the following strategy: # # - DC point # - Logarithmic sweep from 1 kHz to 100 MHz @@ -163,7 +163,7 @@ edb.save_edb() edb.close_edb() -# Launch Hfss3dLayout. +# Launch HFSS 3D Layout. h3d = pyaedt.Hfss3dLayout(aedb_path, specified_version=ansys_version, new_desktop_session=True) @@ -177,7 +177,7 @@ placement_layer="TOP", component_name="my_connector", pos_x="5mm", pos_y=0.000) -# ## Run Simulation +# ## Run simulation h3d.analyze(num_cores=4) From 51a89bf0f01a34e54e205ecce1a7b9206602dcb0 Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:40:09 +0100 Subject: [PATCH 27/56] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- examples/00-EDB/13_edb_create_component.py | 24 +++++++++---------- .../14_edb_create_parametrized_design.py | 23 +++++++++--------- examples/00-EDB/15_ac_analysis.py | 4 ++-- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/examples/00-EDB/13_edb_create_component.py b/examples/00-EDB/13_edb_create_component.py index ecfd5c64f56..7f7c6916a81 100644 --- a/examples/00-EDB/13_edb_create_component.py +++ b/examples/00-EDB/13_edb_create_component.py @@ -16,9 +16,9 @@ # >simulation. # 7. Create the HFSS simulation setup and assign ports where the connectors are located. -# ## PCB Trace Model +# ## View PCB trace model # -# Here is an image of the model that will be created in this example. +# Here is an image of the model that is created in this example. # # # @@ -66,7 +66,7 @@ for i in ground_layers: edb.modeler.create_polygon(plane_shape, i, net_name="VSS") -# ### Design Parameters +# ### Add design parameters # # Parameters that are preceeded by a _"$"_ character have project-wide scope. # Therefore, the padstack **definition** and hence all instances of that padstack rely on the parameters. @@ -80,7 +80,7 @@ edb.add_design_variable("trace_in_width", "0.2mm", is_parameter=True) edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) -# ### Create the Connector Component +# ### Create the connector component # # The component definition is used to place the connector on the PCB. First define the padstacks. @@ -104,7 +104,7 @@ conectors_position[0][1] + connector_size / 2], "Via", net_name="VSS")] -# Create the 2nd connector +# Create the second connector component2_pins = [ edb.padstacks.place_padstack(conectors_position[-1], "Via", net_name="VDD", fromlayer=trace_in_layer, @@ -122,20 +122,20 @@ conectors_position[1][1] + connector_size / 2], "Via", net_name="VSS")] -# ### Define "Pins" +# ### Define pins # # Pins are fist defined to allow a component to subsequently connect to the remainder -# of the model. In this case, ports will be assigned at the connector instances using the "pins". +# of the model. In this case, ports are assigned at the connector instances using the pins. for padstack_instance in list(edb.padstacks.instances.values()): padstack_instance.is_pin = True -# Create components from he pins +# Create components from the pins edb.components.create(component1_pins, 'connector_1') edb.components.create(component2_pins, 'connector_2') -# Creating ports on the pins and insert a simulation setup using the ``SimulationConfiguration`` class. +# Create ports on the pins and insert a simulation setup using the ``SimulationConfiguration`` class. sim_setup = edb.new_simulation_configuration() sim_setup.solver_type = sim_setup.SOLVER_TYPE.Hfss3dLayout @@ -149,8 +149,8 @@ sim_setup.ac_settings.step_freq = "1GHz" edb.build_simulation_project(sim_setup) -# Save the EDB and open it in the 3D Layout editor. If ``non_graphical==False`` -# there may be a delay while AEDT started. +# Save the EDB and open it in the 3D Layout editor. If ``non_graphical==False``, +# there may be a delay while AEDT starts. edb.save_edb() edb.close_edb() @@ -170,7 +170,7 @@ h3d.release_desktop(close_projects=True, close_desktop=True) -# ### Clean up the Temporary Directory +# ### Clean up the temporary directory # # The following command cleans up the temporary directory, thereby removing all # project files. If you'd like to save this project, save it to a folder of your choice diff --git a/examples/00-EDB/14_edb_create_parametrized_design.py b/examples/00-EDB/14_edb_create_parametrized_design.py index b88746b4cb6..0e5b7740711 100644 --- a/examples/00-EDB/14_edb_create_parametrized_design.py +++ b/examples/00-EDB/14_edb_create_parametrized_design.py @@ -4,7 +4,7 @@ # 1. Set up an HFSS project using SimulationConfiguration class. # 2. Create automatically parametrized design. # -# The following layout will be created in this example +# This image shows the layout created in this example: # # # @@ -21,20 +21,19 @@ # + temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") target_aedb = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=temp_dir.name) -print("Project will be located in ", target_aedb) +print("Project is located in ", target_aedb) aedt_version = "2023.2" edb = pyaedt.Edb(edbpath=target_aedb, edbversion=aedt_version) -print("EDB is located at {}".format(target_aedb)) +print("AEDB file is located in {}".format(target_aedb)) # - -# ### Prepare the Layout for Simulation +# ### Prepare the layout for the simulation # # The ``new_simulation_configuration()`` method creates an instance of -# the ``SimulationConfiguration`` class. This class helps define all pre-processing steps +# the ``SimulationConfiguration`` class. This class helps define all preprocessing steps # required to set up the PCB for simulation. After the simulation configuration has been defined, -# they are applied to the EDB using the method -# ``Edb.build_simulation()``. +# they are applied to the EDB using the ``Edb.build_simulation()`` method. simulation_configuration = edb.new_simulation_configuration() simulation_configuration.signal_nets = ["PCIe_Gen4_RX0_P", "PCIe_Gen4_RX0_N", @@ -50,7 +49,7 @@ edb.build_simulation_project(simulation_configuration) -# ### Parameterization +# ### Parameterize # # The layout can automatically be set up to enable parametric studies. For example, the # impact of antipad diameter or trace width on signal integrity performance may be invested parametrically. @@ -61,9 +60,9 @@ # ## Open project in AEDT # -# All manipulations thus far have been executed using the EDB API which provides fast, streamlined processing of +# All manipulations thus far have been executed using the EDB API, which provides fast, streamlined processing of # layout data in non-graphical mode. The layout and simulation setup can be visualized by opening it using the -# 3D Layout editor in Electronics Desktop. +# 3D Layout editor in AEDT. # # Note that there may be some delay while AEDT is being launched. @@ -86,7 +85,7 @@ if is_ready_to_simulate: print("The model is ready for simulation.") else: - print("There are errors in the model that need to be fixed.") + print("There are errors in the model that must be fixed.") # - # ### Release the application from the Python kernel @@ -95,7 +94,7 @@ # execution of the script. The default behavior of the ``release_desktop()`` method closes all open # projects and closes the application. # -# If you want to conintue working on the project in graphical mode +# If you want to continue working on the project in graphical mode # after script execution, call the following method with both arguments set to ``False``. hfss.release_desktop(close_projects=True, close_desktop=True) diff --git a/examples/00-EDB/15_ac_analysis.py b/examples/00-EDB/15_ac_analysis.py index 0cc81a9f887..4b65f352459 100644 --- a/examples/00-EDB/15_ac_analysis.py +++ b/examples/00-EDB/15_ac_analysis.py @@ -29,13 +29,13 @@ # ### Configure EDB # -# Creat an instance of the `pyaedt.Edb` class. +# Create an instance of the ``pyaedt.Edb`` class. edbapp = pyaedt.Edb(edbpath=edb_full_path, edbversion="2023.2") # ### Generate extended nets # -# An extended net is a connection between two nets that are connected +# An extended net consists of two nets that are connected # through a passive component such as a resistor or capacitor. all_nets = edbapp.extended_nets.auto_identify_signal(resistor_below=10, From 731add7a09b850a3d1b9f759c3d95e0e35745aa1 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 1 Feb 2024 10:09:36 +0100 Subject: [PATCH 28/56] MISC: add example 08_CPWG.PY --- examples/00-EDB/08_CPWG.py | 167 +++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 examples/00-EDB/08_CPWG.py diff --git a/examples/00-EDB/08_CPWG.py b/examples/00-EDB/08_CPWG.py new file mode 100644 index 00000000000..46f40d6067c --- /dev/null +++ b/examples/00-EDB/08_CPWG.py @@ -0,0 +1,167 @@ +# # EDB: fully parametrized CPWG design + +# This example shows how you can use HFSS 3D Layout to create a parametric design +# for a CPWG (coplanar waveguide with ground). + + +# ## Perform required imports + +# Perform required imports. Importing the ``Hfss3dlayout`` object initializes it +# on version 2023 R2. + +import pyaedt +import os +import numpy as np + + +# ## Set non-graphical mode + +# Set non-graphical mode. The default is ``False``. + +non_graphical = False + +# ## Launch EDB + +aedb_path = os.path.join(pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("pcb") + ".aedb") +print(aedb_path) +edbapp = pyaedt.Edb(edbpath=aedb_path, edbversion="2023.2") + +# ## Define parameters + +params = {"$ms_width": "0.4mm", + "$ms_clearance": "0.3mm", + "$ms_length": "20mm", + } +for par_name in params: + edbapp.add_project_variable(par_name, params[par_name]) + + +# ## Create s symmetric stackup + +edbapp.stackup.create_symmetric_stackup(2) +edbapp.stackup.plot() + +# ## Draw planes + +# + +plane_lw_pt = ["0mm", "-3mm"] +plane_up_pt = ["$ms_length", "3mm"] + +top_layer_obj = edbapp.modeler.create_rectangle("TOP", net_name="gnd", + lower_left_point=plane_lw_pt, + upper_right_point=plane_up_pt) +bot_layer_obj = edbapp.modeler.create_rectangle("BOTTOM", net_name="gnd", + lower_left_point=plane_lw_pt, + upper_right_point=plane_up_pt) +layer_dict = {"TOP": top_layer_obj, + "BOTTOM": bot_layer_obj} +# - + +# ## Draw a trace + +trace_path = [["0", "0"], ["$ms_length", "0"]] +edbapp.modeler.create_trace(trace_path, + layer_name="TOP", + width="$ms_width", + net_name="sig", + start_cap_style="Flat", + end_cap_style="Flat" + ) + +# ## Create a trace to plane clearance + +poly_void = edbapp.modeler.create_trace(trace_path, layer_name="TOP", net_name="gnd", + width="{}+2*{}".format("$ms_width", "$ms_clearance"), + start_cap_style="Flat", + end_cap_style="Flat") +edbapp.modeler.add_void(layer_dict["TOP"], poly_void) + +# ## Create a ground via padstack and place ground stitching vias + +# + +edbapp.padstacks.create(padstackname="GVIA", + holediam="0.3mm", + paddiam="0.5mm", + ) + +yloc_u = "$ms_width/2+$ms_clearance+0.25mm" +yloc_l = "-$ms_width/2-$ms_clearance-0.25mm" + +for i in np.arange(1, 20): + edbapp.padstacks.place([str(i) + "mm", yloc_u], "GVIA", net_name="GND") + edbapp.padstacks.place([str(i) + "mm", yloc_l], "GVIA", net_name="GND") +# - + +# ## Save and close EDB + +edbapp.save_edb() +edbapp.close_edb() + +# ## Open EDB in AEDT + +h3d = pyaedt.Hfss3dLayout(projectname=aedb_path, specified_version="2023.2", + non_graphical=non_graphical, new_desktop_session=True) + +# ## Create wave ports + +h3d.create_edge_port("line_3", 0, iswave=True, wave_vertical_extension=10, wave_horizontal_extension=10) +h3d.create_edge_port("line_3", 2, iswave=True, wave_vertical_extension=10, wave_horizontal_extension=10) + +# ## Edit airbox extents + +h3d.edit_hfss_extents(air_vertical_positive_padding="10mm", + air_vertical_negative_padding="1mm") + +# ## Create setup + +setup = h3d.create_setup() +setup["MaxPasses"]=2 +setup["AdaptiveFrequency"]="3GHz" +setup["SaveAdaptiveCurrents"]=True +h3d.create_linear_count_sweep( + setupname=setup.name, + unit="GHz", + freqstart=0, + freqstop=5, + num_of_freq_points=1001, + sweepname="sweep1", + sweep_type="Interpolating", + interpolation_tol_percent=1, + interpolation_max_solutions=255, + save_fields=False, + use_q3d_for_dc=False, +) + +# ## Plot layout + +# + +h3d.modeler.edb.nets.plot(None, None, color_by_net=True) + +cp_name = h3d.modeler.clip_plane() + +h3d.save_project() +# - + +# ## Start HFSS solver + +# Start the HFSS solver by uncommenting the ``h3d.analyze()`` command. + +# + +# h3d.analyze() +# - + +# ## Save AEDT + +# + +aedt_path = aedb_path.replace(".aedb", ".aedt") +h3d.logger.info("Your AEDT project is saved to {}".format(aedt_path)) +solutions = h3d.get_touchstone_data()[0] +solutions.log_x = False +solutions.plot() + +h3d.post.create_fieldplot_cutplane(cp_name, "Mag_E", h3d.nominal_adaptive, intrinsincDict={"Freq":"3GHz", "Phase":"0deg"}) +# - + +# ## Release AEDT + +h3d.release_desktop() From 5fdb3995429cb4d75c92666320dea16c65048d37 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 1 Feb 2024 10:53:38 +0100 Subject: [PATCH 29/56] MISC: clean examples and add index.rst files --- examples/01-HFSS3DLayout/Dcir_in_3DLayout.py | 4 +- examples/01-HFSS3DLayout/EDB_in_3DLayout.py | 3 +- examples/01-HFSS3DLayout/index.rst | 4 +- examples/01-Modeling-Setup/Configurations.py | 16 +- .../HFSS_CoordinateSystem.py | 6 +- examples/01-Modeling-Setup/Optimetrics.py | 4 +- examples/01-Modeling-Setup/index.rst | 2 +- examples/02-HFSS/Advanced_Far_Field.py.back | 218 ------------------ examples/02-HFSS/Flex_CPWG.py | 16 +- examples/02-HFSS/HFSS_Choke.py | 2 + examples/02-HFSS/HFSS_FSS_unitcell.py | 8 + examples/02-HFSS/HFSS_Spiral.py | 6 + examples/02-HFSS/HFSS_eigenmode.py | 9 +- examples/02-HFSS/Probe_Fed_Patch.py | 9 +- examples/02-HFSS/Waveguide_Filter.py | 16 +- examples/02-HFSS/index.rst | 2 +- examples/02-SBR+/SBR_Example.py | 2 + examples/02-SBR+/SBR_Time_Plot.py | 4 + examples/02-SBR+/index.rst | 2 +- 19 files changed, 78 insertions(+), 255 deletions(-) delete mode 100644 examples/02-HFSS/Advanced_Far_Field.py.back diff --git a/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py b/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py index d45243eb73f..061218cd7d9 100644 --- a/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py +++ b/examples/01-HFSS3DLayout/Dcir_in_3DLayout.py @@ -1,14 +1,12 @@ # # HFSS 3D Layout: SIwave DCIR analysis in HFSS 3D Layout # # This example shows how to configure a model using the 3D Layout -# interface for SIwave DC-IR -# analysis. +# interface for SIwave DC-IR analysis. import os import tempfile import pyaedt - # Copy example into temporary folder temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") diff --git a/examples/01-HFSS3DLayout/EDB_in_3DLayout.py b/examples/01-HFSS3DLayout/EDB_in_3DLayout.py index 6177aa10fdb..9c9c36bda97 100644 --- a/examples/01-HFSS3DLayout/EDB_in_3DLayout.py +++ b/examples/01-HFSS3DLayout/EDB_in_3DLayout.py @@ -3,7 +3,6 @@ # This example shows how you can use HFSS 3D Layout combined with EDB to # interact with a 3D layout. - import os import tempfile import pyaedt @@ -13,8 +12,8 @@ if not os.path.exists(project_folder): os.makedirs(project_folder) print(project_folder) -# Copy an example into the temporary project folder. +# Copy an example into the temporary project folder. targetfile = pyaedt.downloads.download_aedb() print(targetfile) diff --git a/examples/01-HFSS3DLayout/index.rst b/examples/01-HFSS3DLayout/index.rst index 5c981532638..6e8a78a1346 100644 --- a/examples/01-HFSS3DLayout/index.rst +++ b/examples/01-HFSS3DLayout/index.rst @@ -5,7 +5,7 @@ It includes model generation, setup, meshing, and post-processing. .. nbgallery:: - Dcr_in_3DLayout.py + Dcir_in_3DLayout.py EDB_in_3DLayout.py Hfss3DComponent.py - HFSS3DLayout_Via.py \ No newline at end of file + HFSS3DLayout_Via.py diff --git a/examples/01-Modeling-Setup/Configurations.py b/examples/01-Modeling-Setup/Configurations.py index 624d33b543c..dcd7ef1b77f 100644 --- a/examples/01-Modeling-Setup/Configurations.py +++ b/examples/01-Modeling-Setup/Configurations.py @@ -23,29 +23,28 @@ # any reason, this face position has changed or the object name in the target # design has changed, the boundary fails to apply. -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports from PyAEDT. +# ## Perform required imports import os import pyaedt -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. +# ## Set non-graphical mode + # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -# Open project -# ~~~~~~~~~~~~ +# ## Open project + # Download the project, open it, and save it to the temporary folder. +# + project_full_name = pyaedt.downloads.download_icepak(pyaedt.generate_unique_folder_name(folder_name="Graphic_Card")) ipk = pyaedt.Icepak(projectname=project_full_name, specified_version="2023.2", new_desktop_session=True, non_graphical=non_graphical) ipk.autosave_disable() +# - # ## Create source blocks # @@ -84,7 +83,6 @@ ipk.export_3d_model(file_name=filename, file_path=ipk.working_directory, file_format=".step", object_list=[], removed_objects=[]) -############################################################################### # ## Export configuration files # # Export the configuration files. You can optionally disable the export and diff --git a/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py b/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py index 812e8a2a4b5..072c633b9df 100644 --- a/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py +++ b/examples/01-Modeling-Setup/HFSS_CoordinateSystem.py @@ -6,12 +6,10 @@ # # Perform required imports -import os - import pyaedt -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode + # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. diff --git a/examples/01-Modeling-Setup/Optimetrics.py b/examples/01-Modeling-Setup/Optimetrics.py index b750e5f6d21..e87b46ca409 100644 --- a/examples/01-Modeling-Setup/Optimetrics.py +++ b/examples/01-Modeling-Setup/Optimetrics.py @@ -32,6 +32,7 @@ # Create one of the standard waveguide structures and parametrize it. # You can also create rectangles of waveguide openings and assign ports later. +# + wg1, p1, p2 = hfss.modeler.create_waveguide( [0, 0, 0], hfss.AXIS.Y, @@ -45,6 +46,7 @@ model.show_grid = False model.plot(os.path.join(hfss.working_directory, "Image.jpg")) +# - # Create two wave ports on the sheets. @@ -102,8 +104,6 @@ sweep5 = hfss.optimizations.add(calculation="dB(S(1,1))", ranges={"Freq": "2.5GHz"}, optim_type="DXDOE") # ## Create DOE based on a goal and calculation -# -# Create a DOE based on a goal and a calculation. region = hfss.modeler.create_region() hfss.assign_radiation_boundary_to_objects(region) diff --git a/examples/01-Modeling-Setup/index.rst b/examples/01-Modeling-Setup/index.rst index d157b53d5b6..1cecf178867 100644 --- a/examples/01-Modeling-Setup/index.rst +++ b/examples/01-Modeling-Setup/index.rst @@ -8,4 +8,4 @@ setup features inside AEDT. Configurations.py HFSS_CoordinateSystem.py Optimetrics.py - Polyline_Primitives.py \ No newline at end of file + Polyline_Primitives.py diff --git a/examples/02-HFSS/Advanced_Far_Field.py.back b/examples/02-HFSS/Advanced_Far_Field.py.back deleted file mode 100644 index ab372e63618..00000000000 --- a/examples/02-HFSS/Advanced_Far_Field.py.back +++ /dev/null @@ -1,218 +0,0 @@ -""" -HFSS: advanced far field postprocessing ---------------------------------------- -This example shows how you can use advanced postprocessing functions to create plots -using Matplotlib without opening the HFSS user interface. -This examples runs only on Windows using CPython. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. - -import os -import time - -import pyaedt -from pyaedt.generic.general_methods import remove_project_lock - -project_name = pyaedt.downloads.download_antenna_array() - -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ -# Set non-graphical mode. -# You can set ``non_graphical`` either to ``True`` or ``False``. - -non_graphical = False - -############################################################################### -# Import modules for postprocessing -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import modules for postprocessing. - -import numpy as np -import matplotlib.pyplot as plt - -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ -# Launch AEDT 2023 R1 in non-graphical mode. - -desktopVersion = "2023.1" -NewThread = True -desktop = pyaedt.launch_desktop(specified_version=desktopVersion, - non_graphical=non_graphical, - new_desktop_session=NewThread - ) - -############################################################################### -# Open HFSS project -# ~~~~~~~~~~~~~~~~~ -# Open the HFSS project. ``Hfss`` class allows to initialize a new project or -# open an existing project and point to a design name. - -remove_project_lock(project_name) - -hfss = pyaedt.Hfss(projectname=project_name, - designname="4X4_MultiCell_CA-Array") - -############################################################################### -# Solve HFSS project -# ~~~~~~~~~~~~~~~~~~ -# Solves the HFSS project. -# The solution time is computed. - -hfss.analyze_setup("Setup1") -hfss.save_project() - -####################################### -# Get efields data from solution -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Gets efields data from the solution. - -start = time.time() -ff_data = hfss.post.get_efields_data(ff_setup="3D") -end = time.time() - start -print("Postprocessing Time", end) - - -############################################################################### -# Calculate far field values -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Use Matplotlib to read the solution generated in ``ff_data``. Process -# the field based on Phi and Theta and generate a plot. - -def ff_calc(x=0, y=0, qty="rETotal", dB=True): - array_size = [4, 4] - loc_offset = 2 # if array index is not starting at [1,1] - xphase = float(y) - yphase = float(x) - array_shape = (array_size[0], array_size[1]) - weight = np.zeros(array_shape, dtype=complex) - mag = np.ones(array_shape, dtype="object") - port_names_arranged = np.chararray(array_shape) - all_ports = ff_data.keys() - w_dict = {} - # calculate weights based off of progressive phase shift - for m in range(array_shape[0]): - for n in range(array_shape[1]): - mag_val = mag[m][n] - ang = np.radians(xphase * m) + np.radians(yphase * n) - weight[m][n] = np.sqrt(mag_val) * np.exp(1j * ang) - current_index_str = "[" + str(m + 1 + loc_offset) + "," + str(n + 1 + loc_offset) + "]" - port_name = [y for y in all_ports if current_index_str in y] - w_dict[port_name[0]] = weight[m][n] - - length_of_ff_data = len(ff_data[port_name[0]][2]) - - array_shape = (len(w_dict), length_of_ff_data) - rEtheta_fields = np.zeros(array_shape, dtype=complex) - rEphi_fields = np.zeros(array_shape, dtype=complex) - w = np.zeros((1, array_shape[0]), dtype=complex) - # create port mapping - for n, port in enumerate(ff_data.keys()): - re_theta = ff_data[port][2] - re_phi = ff_data[port][3] - re_theta = re_theta * w_dict[port] - - w[0][n] = w_dict[port] - re_phi = re_phi * w_dict[port] - - rEtheta_fields[n] = re_theta - rEphi_fields[n] = re_phi - - theta_range = ff_data[port][0] - phi_range = ff_data[port][1] - theta = [int(np.min(theta_range)), int(np.max(theta_range)), np.size(theta_range)] - phi = [int(np.min(phi_range)), int(np.max(phi_range)), np.size(phi_range)] - Ntheta = len(theta_range) - Nphi = len(phi_range) - - rEtheta_fields = np.dot(w, rEtheta_fields) - rEtheta_fields = np.reshape(rEtheta_fields, (Ntheta, Nphi)) - - rEphi_fields = np.dot(w, rEphi_fields) - rEphi_fields = np.reshape(rEphi_fields, (Ntheta, Nphi)) - - all_qtys = {} - all_qtys["rEPhi"] = rEphi_fields - all_qtys["rETheta"] = rEtheta_fields - all_qtys["rETotal"] = np.sqrt(np.power(np.abs(rEphi_fields), 2) + np.power(np.abs(rEtheta_fields), 2)) - - pin = np.sum(w) - print(str(pin)) - real_gain = 2 * np.pi * np.abs(np.power(all_qtys["rETotal"], 2)) / pin / 377 - all_qtys["RealizedGain"] = real_gain - - if dB: - if "Gain" in qty: - qty_to_plot = 10 * np.log10(np.abs(all_qtys[qty])) - else: - qty_to_plot = 20 * np.log10(np.abs(all_qtys[qty])) - qty_str = qty + " (dB)" - else: - qty_to_plot = np.abs(all_qtys[qty]) - qty_str = qty + " (mag)" - - plt.figure(figsize=(25, 15)) - plt.title(qty_str) - plt.xlabel("Theta (degree)") - plt.ylabel("Phi (degree)") - - plt.imshow(qty_to_plot, cmap="jet") - plt.colorbar() - - np.max(qty_to_plot) - - -############################################################################### -# Create plot and interact with it -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create the plot and interact with it. - -ff_calc() - -# interact(ff_calc, x=widgets.FloatSlider(value=0, min=-180, max=180, step=1), -# y=widgets.FloatSlider(value=0, min=-180, max=180, step=1)) - - -vals = hfss.post.get_far_field_data(setup_sweep_name=hfss.nominal_sweep, - expression="RealizedGainTotal", - domain="Elevation" - ) - -############################################################################### -# Generate polar plot -# ~~~~~~~~~~~~~~~~~~~ -# Generate a polar plot. - -vals.plot(math_formula="db20", is_polar=True) - -############################################################################### -# Generate scalar plot -# ~~~~~~~~~~~~~~~~~~~~ -# Generate a scalar plot. - -vals.plot(math_formula="db20", is_polar=False) - -############################################################################### -# Generate plot using Phi as primary sweep -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Generate the plot using Phi as the primary sweep. - -vals3d = hfss.post.get_far_field_data(setup_sweep_name=hfss.nominal_sweep, - expression="RealizedGainTotal", - domain="Infinite Sphere1" - ) - -vals3d.plot_3d() - -####################################### -# Close HFSS project and AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Close the HFSS project and release AEDT. - -# hfss.close_project() -hfss.save_project() -desktop.release_desktop() diff --git a/examples/02-HFSS/Flex_CPWG.py b/examples/02-HFSS/Flex_CPWG.py index e7029ae0159..51c2ea830f0 100644 --- a/examples/02-HFSS/Flex_CPWG.py +++ b/examples/02-HFSS/Flex_CPWG.py @@ -35,6 +35,7 @@ # # Create input variables for creating the flex cable CPWG. +# + total_length = 300 theta = 120 r = 100 @@ -45,13 +46,14 @@ gnd_thickness = 2 xt = (total_length - r * radians(theta)) / 2 - +# - # ## Create bend # # Create the bend. The ``create_bending`` method creates a list of points for # the bend based on the curvature radius and extension. +# + def create_bending(radius, extension=0): position_list = [(-xt, 0, -radius), (0, 0, -radius)] @@ -66,7 +68,7 @@ def create_bending(radius, extension=0): position_list[-1] = (x, y, z) return position_list - +# - # ## Draw signal line # @@ -85,6 +87,7 @@ def create_bending(radius, extension=0): # # Draw a ground line to create two bent ground wires. +# + gnd_r = [(x, spacing + width / 2 + gnd_width / 2, z) for x, y, z in position_list] gnd_l = [(x, -y, z) for x, y, z in gnd_r] @@ -95,11 +98,13 @@ def create_bending(radius, extension=0): ) x.color = (255, 0, 0) gnd_objs.append(x) +# - # ## Draw dielectric # # Draw a dielectric to create a dielectric cable. +# + position_list = create_bending(r + (height + gnd_thickness) / 2) fr4 = hfss.modeler.create_polyline( @@ -109,11 +114,13 @@ def create_bending(radius, extension=0): xsection_height=width + 2 * spacing + 2 * gnd_width, matname="FR4_epoxy", ) +# - # ## Create bottom metals # # Create the bottom metals. +# + position_list = create_bending(r + height + gnd_thickness, 1) bot = hfss.modeler.create_polyline( @@ -123,11 +130,13 @@ def create_bending(radius, extension=0): xsection_height=width + 2 * spacing + 2 * gnd_width, matname="copper", ) +# - # ## Create port interfaces # # Create port interfaces (PEC enclosures). +# + port_faces = [] for face, blockname in zip([fr4.top_face_z, fr4.bottom_face_x], ["b1", "b2"]): xc, yc, zc = face.center @@ -150,6 +159,7 @@ def create_bending(radius, extension=0): i.subtract([port_block], True) print(port_faces) +# - # ## Create boundary condition # @@ -165,10 +175,12 @@ def create_bending(radius, extension=0): # # Creates ports. +# + for s, port_name in zip(port_faces, ["1", "2"]): reference = [i.name for i in gnd_objs + boundary + [bot]] + ["b1", "b2"] hfss.wave_port(s.id, name=port_name, reference=reference) +# - # ## Create setup and sweep # diff --git a/examples/02-HFSS/HFSS_Choke.py b/examples/02-HFSS/HFSS_Choke.py index 42aa61761f6..db2c2c54a20 100644 --- a/examples/02-HFSS/HFSS_Choke.py +++ b/examples/02-HFSS/HFSS_Choke.py @@ -6,11 +6,13 @@ # # Perform required imports. +# + import json import os import pyaedt project_name = pyaedt.generate_unique_project_name(project_name="choke") +# - # ## Set non-graphical mode # diff --git a/examples/02-HFSS/HFSS_FSS_unitcell.py b/examples/02-HFSS/HFSS_FSS_unitcell.py index 24d73c0f88a..2530d8648c6 100644 --- a/examples/02-HFSS/HFSS_FSS_unitcell.py +++ b/examples/02-HFSS/HFSS_FSS_unitcell.py @@ -6,10 +6,12 @@ # # Perform required imports. +# + import os import pyaedt project_name = pyaedt.generate_unique_project_name(project_name="FSS") +# - # ## Set non-graphical mode # @@ -40,10 +42,12 @@ # # Download the 3D component from the example data and insert the 3D Component. +# + unitcell_3d_component_path = pyaedt.downloads.download_FSS_3dcomponent() unitcell_path = os.path.join(unitcell_3d_component_path, "FSS_unitcell_23R2.a3dcomp") comp = hfss.modeler.insert_3d_component(unitcell_path) +# - # ## Assign design parameter to 3D Component parameter # @@ -56,6 +60,7 @@ # # Create an open region along +Z direction for unitcell analysis. +# + bounding_dimensions = hfss.modeler.get_bounding_dimension() periodicity_x = bounding_dimensions[0] @@ -67,6 +72,7 @@ ) [x_min, y_min, z_min, x_max, y_max, z_max] = region.bounding_box +# - # ## Assign Lattice pair boundary # @@ -106,6 +112,7 @@ # # Create S-parameter reports using create report. +# + all_quantities = hfss.post.available_report_quantities() str_mag = [] str_ang = [] @@ -126,6 +133,7 @@ variations=variation, plotname="phase_plot", ) +# - # ## Save and run simulation # diff --git a/examples/02-HFSS/HFSS_Spiral.py b/examples/02-HFSS/HFSS_Spiral.py index 4d0fa07a5db..af9c1dff297 100644 --- a/examples/02-HFSS/HFSS_Spiral.py +++ b/examples/02-HFSS/HFSS_Spiral.py @@ -6,10 +6,12 @@ # # Perform required imports. +# + import os import pyaedt project_name = pyaedt.generate_unique_project_name(project_name="spiral") +# - # ## Set non-graphical mode # @@ -103,6 +105,7 @@ def create_line(pts): # # Create the silicon substrate and the ground plane. +# + p.create_box([x1 - 20, x1 - 20, "-Tsub-{}{}/2".format(thickness, hfss.modeler.model_units)], [-2 * x1 + 40, -2 * x1 + 40, "Tsub"], matname="silicon") @@ -110,11 +113,13 @@ def create_line(pts): p.create_box([x1 - 20, x1 - 20, "-Tsub-{}{}/2".format(thickness, hfss.modeler.model_units)], [-2 * x1 + 40, -2 * x1 + 40, -0.1], matname="PEC") +# - # ## Assign airbox and radiation # # Assign the airbox and the radiation. +# + box = p.create_box( [x1 - 20, x1 - 20, "-Tsub-{}{}/2 - 0.1{}".format(thickness, hfss.modeler.model_units, hfss.modeler.model_units)], [-2 * x1 + 40, -2 * x1 + 40, 100], @@ -123,6 +128,7 @@ def create_line(pts): ) hfss.assign_radiation_boundary_to_objects("airbox") +# - # ## Assign material override # diff --git a/examples/02-HFSS/HFSS_eigenmode.py b/examples/02-HFSS/HFSS_eigenmode.py index 00d6f42c02c..443e490b1a1 100644 --- a/examples/02-HFSS/HFSS_eigenmode.py +++ b/examples/02-HFSS/HFSS_eigenmode.py @@ -25,7 +25,6 @@ # # Run through each cell. This cell imports the required packages. -import sys import os import pyaedt @@ -68,6 +67,7 @@ # of interest. ``fmax`` is the highest frequency of interest. # ``limit`` is the parameter limit that determines which modes are ignored. +# + num_modes = 6 fmin = 1 fmax = 2 @@ -76,7 +76,7 @@ limit = 10 resonance = {} - +# - # ## Find the modes # @@ -84,6 +84,7 @@ # After the solve, each mode, along with its corresponding real frequency and quality factor, # are saved for further processing. +# + def find_resonance(): # setup creation next_min_freq = str(next_fmin) + " GHz" @@ -114,7 +115,7 @@ def find_resonance(): print(data) return data - +# - # ## Automate eigenmode solution # @@ -122,6 +123,7 @@ def find_resonance(): # limit. The ``find_resonance`` function is called until the complete frequency range is covered. # When the automation ends, the physical modes in the whole frequency range are reported. +# + while next_fmin < fmax: output = find_resonance() next_fmin = output[len(output) - 1][1] / 1e9 @@ -134,6 +136,7 @@ def find_resonance(): resonance_frequencies = [f"{resonance[i][1] / 1e9:.5} GHz" for i in resonance] print(str(resonance_frequencies)) +# - # ## Save project # diff --git a/examples/02-HFSS/Probe_Fed_Patch.py b/examples/02-HFSS/Probe_Fed_Patch.py index 0ad2718c560..3ed10cd890c 100644 --- a/examples/02-HFSS/Probe_Fed_Patch.py +++ b/examples/02-HFSS/Probe_Fed_Patch.py @@ -9,7 +9,6 @@ # ## Perform imports import os - import pyaedt import tempfile from pyaedt.modeler.advanced_cad.stackup_3d import Stackup3D @@ -46,9 +45,8 @@ proj_name = os.path.join(project_folder, "antenna") # ## Launch HFSS -# -# +# + hfss = pyaedt.Hfss(projectname=proj_name, solution_type="Terminal", designname="patch", @@ -56,12 +54,14 @@ specified_version=desktop_version) hfss.modeler.model_units = length_units +# - # ## Create patch # # Create the patch. -# + +# + stackup = Stackup3D(hfss) ground = stackup.add_ground_layer("ground", material="copper", thickness=0.035, fill_material="air") dielectric = stackup.add_dielectric_layer("dielectric", thickness="0.5" + length_units, material="Duroid (tm)") @@ -87,6 +87,7 @@ hfss.save_project() hfss.analyze() +# - # ## Plot S11 # diff --git a/examples/02-HFSS/Waveguide_Filter.py b/examples/02-HFSS/Waveguide_Filter.py index 3e6013b54ed..a082b2ca2c1 100644 --- a/examples/02-HFSS/Waveguide_Filter.py +++ b/examples/02-HFSS/Waveguide_Filter.py @@ -17,7 +17,6 @@ # ## Launch Ansys Electronics Desktop (AEDT) - # ### Define parameters and values for waveguide iris filter # # l: Length of the cavity from the mid-point of one iris @@ -27,6 +26,7 @@ # b: Short dimension of the waveguide cross-section. # t: Metal thickness of the iris insert. +# + wgparams = {'l': [0.7428, 0.82188], 'w': [0.50013, 0.3642, 0.3458], 'a': 0.4, @@ -36,6 +36,7 @@ non_graphical = False new_thread = True +# - # ### Save the project and results in the TEMP folder @@ -45,6 +46,7 @@ project_name = os.path.join(project_folder, general_methods.generate_unique_name("wgf", n=2)) # Instantiate the HFSS application + hfss = pyaedt.Hfss(projectname=project_name + '.aedt', specified_version="2023.2", designname="filter", @@ -58,6 +60,7 @@ # ### Initialize design parameters in HFSS. +# + hfss.modeler.model_units = "in" # Set to inches for key in wgparams: if type(wgparams[key]) in [int, float]: @@ -77,7 +80,7 @@ else: zstart = "l1/2 - t/2" # Odd number of cavities, even number of irises. is_even = False - +# - # ### Draw parametric waveguide filter # @@ -96,11 +99,11 @@ def place_iris(zpos, dz, n): iris.append(iris[0].mirror([0, 0, 0], [1, 0, 0], duplicate=True)) return iris - # ### Place irises # # Place the irises from inner (highest integer) to outer. +# + for count in reversed(range(1, len(wgparams['w']) + 1)): if count < len(wgparams['w']): # Update zpos zpos = zpos + "".join([" + l" + str(count) + " + "])[:-3] @@ -112,6 +115,7 @@ def place_iris(zpos, dz, n): iris = place_iris(zpos, "t", count) if not is_even: iris = place_iris("-(" + zpos + ")", "-t", count) +# - # ### Draw full waveguide with ports # @@ -144,6 +148,7 @@ def place_iris(zpos, dz, n): # and define the calibration lines to ensure self-consistent # polarization between wave ports. +# + count = 0 ports = [] for n, z in enumerate(wg_z): @@ -152,6 +157,7 @@ def place_iris(zpos, dz, n): u_end = [0, hfss.variable_manager["u_end"].evaluated_value, z] ports.append(hfss.wave_port(face_id, integration_line=[u_start, u_end], name="P" + str(n + 1), renormalize=False)) +# - # ### Insert the mesh adaptation setup using refinement at two frequencies. # @@ -160,6 +166,7 @@ def place_iris(zpos, dz, n): # filter. Adaptation at multiple frequencies helps to ensure that energy propagates # through the resonant structure while the mesh is refined. +# + setup = hfss.create_setup("Setup1", setuptype="HFSSDriven", MultipleAdaptiveFreqsSetup=['9.8GHz', '10.2GHz'], MaximumPasses=5) @@ -171,6 +178,7 @@ def place_iris(zpos, dz, n): freqstop=10.5, sweep_type="Interpolating", ) +# - # Solve the project with two tasks. # Each frequency point is solved simultaneously. @@ -185,11 +193,13 @@ def place_iris(zpos, dz, n): # Caution: The syntax for expressions must be identical to that used # in HFSS. +# + traces_to_plot = hfss.get_traces_for_plot(second_element_filter="P1*") report = hfss.post.create_report(traces_to_plot) # Creates a report in HFSS solution = report.get_solution_data() plt = solution.plot(solution.expressions) # Matplotlib axes object. +# - # ### Generate E field plot # diff --git a/examples/02-HFSS/index.rst b/examples/02-HFSS/index.rst index 52bc6d9a8a8..75907a9834a 100644 --- a/examples/02-HFSS/index.rst +++ b/examples/02-HFSS/index.rst @@ -14,4 +14,4 @@ This includes model generation, setup, meshing, and postprocessing. HFSS_FSS_unitcell.py HFSS_Spiral.py Probe_Fed_Patch.py - Waveguide_Filter.py \ No newline at end of file + Waveguide_Filter.py diff --git a/examples/02-SBR+/SBR_Example.py b/examples/02-SBR+/SBR_Example.py index 85e539e1b7f..1ee324ab159 100644 --- a/examples/02-SBR+/SBR_Example.py +++ b/examples/02-SBR+/SBR_Example.py @@ -25,6 +25,7 @@ # Define two designs, one source and one target, with each design connected to # a different object. +# + target = pyaedt.Hfss( projectname=project_full_name, designname="Cassegrain_", @@ -38,6 +39,7 @@ designname="feeder", specified_version="2023.2", ) +# - # ## Define linked antenna # diff --git a/examples/02-SBR+/SBR_Time_Plot.py b/examples/02-SBR+/SBR_Time_Plot.py index ad3d249daeb..780555c4e69 100644 --- a/examples/02-SBR+/SBR_Time_Plot.py +++ b/examples/02-SBR+/SBR_Time_Plot.py @@ -21,11 +21,13 @@ # # Launch AEDT and load the project. +# + project_file = downloads.download_sbr_time() hfss = Hfss(projectname=project_file, specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) hfss.analyze() +# - # ## Get solution data # @@ -55,6 +57,7 @@ # # Plot the scene to create the time plot animation +# + hfss.post.plot_scene(frames_list=frames_list_file, output_gif_path=os.path.join(hfss.working_directory, "animation.gif"), norm_index=15, @@ -62,3 +65,4 @@ show=False, view="xy", zoom=1) hfss.release_desktop() +# - \ No newline at end of file diff --git a/examples/02-SBR+/index.rst b/examples/02-SBR+/index.rst index 011c8582ce8..d6a3be8fdc9 100644 --- a/examples/02-SBR+/index.rst +++ b/examples/02-SBR+/index.rst @@ -8,4 +8,4 @@ This includes model generation, setup, meshing, and postprocessing. SBR_City_Import.py SBR_Doppler_Example.py SBR_Example.py - SBR_Time_Plot.py \ No newline at end of file + SBR_Time_Plot.py From 0f7d4438158ad8489863a884be99976e04e3cd1d Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 1 Feb 2024 14:41:04 +0100 Subject: [PATCH 30/56] DOC: convert 03-Maxwell into md --- examples/03-Maxwell/Maxwell2D_DCConduction.py | 92 +++--- .../03-Maxwell/Maxwell2D_Electrostatic.py | 101 +++--- examples/03-Maxwell/Maxwell2D_NissanLeaf.py | 311 ++++++++---------- examples/03-Maxwell/Maxwell2D_Transient.py | 106 +++--- examples/03-Maxwell/Maxwell3DTeam7.py | 126 ++++--- examples/03-Maxwell/Maxwell3D_Choke.py | 81 ++--- examples/03-Maxwell/Maxwell3D_Segmentation.py | 51 ++- .../03-Maxwell/Maxwell3D_Team3_bath_plate.py | 115 +++---- .../03-Maxwell/Maxwell_Control_Program.py | 67 ++-- examples/03-Maxwell/Maxwell_Magnet.py | 76 ++--- .../Maxwell_Transformer_Coreloss.py | 38 +-- 11 files changed, 496 insertions(+), 668 deletions(-) diff --git a/examples/03-Maxwell/Maxwell2D_DCConduction.py b/examples/03-Maxwell/Maxwell2D_DCConduction.py index d65034dddf6..49094000a1d 100644 --- a/examples/03-Maxwell/Maxwell2D_DCConduction.py +++ b/examples/03-Maxwell/Maxwell2D_DCConduction.py @@ -1,17 +1,14 @@ -""" -Maxwell 2D: resistance calculation ----------------------------------- -This example uses PyAEDT to set up a resistance calculation -and solve it using the Maxwell 2D DCConduction solver. -Keywords: DXF import, material sweep, expression cache -""" -import os.path +# # Maxwell 2D: resistance calculation + +# This example uses PyAEDT to set up a resistance calculation +# and solve it using the Maxwell 2D DCConduction solver. +# Keywords: DXF import, material sweep, expression cache +import os.path import pyaedt -################################################################################## -# Launch AEDT and Maxwell 2D -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and Maxwell 2D +# # Launch AEDT and Maxwell 2D after first setting up the project and design names, # the solver, and the version. The following code also creates an instance of the # ``Maxwell2d`` class named ``m2d``. @@ -24,22 +21,23 @@ designname="Ansys_resistor" ) -################################################################################## -# Import geometry as a DXF file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Import geometry as a DXF file +# # You can test importing a DXF or a Parasolid file by commenting/uncommenting # the following lines. # Importing DXF files only works in graphical mode. +# + # DXFPath = pyaedt.downloads.download_file("dxf", "Ansys_logo_2D.dxf") # dxf_layers = m2d.get_dxf_layers(DXFPath) # m2d.import_dxf(DXFPath, dxf_layers, scale=1E-05) + ParasolidPath = pyaedt.downloads.download_file("x_t", "Ansys_logo_2D.x_t") m2d.modeler.import_3d_cad(ParasolidPath) +# - -################################################################################## -# Define variables -# ~~~~~~~~~~~~~~~~ +# ## Define variables +# # Define conductor thickness in z-direction, material array with 4 materials, # and MaterialIndex referring to the material array @@ -49,42 +47,35 @@ m2d["MaterialIndex"] = str(MaterialIndex) no_materials = 4 - -################################################################################## -# Assign materials -# ~~~~~~~~~~~~~~~~ +# ## Assign materials +# # Voltage ports will be defined as perfect electric conductor (pec), conductor # gets the material defined by the 0th entry of the material array m2d.assign_material(["ANSYS_LOGO_2D_1", "ANSYS_LOGO_2D_2"], "pec") m2d.modeler["ANSYS_LOGO_2D_3"].material_name = "ConductorMaterial[MaterialIndex]" - -################################################################################## -# Assign voltages -# ~~~~~~~~~~~~~~~ +# ## Assign voltages +# # 1V and 0V m2d.assign_voltage(["ANSYS_LOGO_2D_1"], amplitude=1, name="1V") m2d.assign_voltage(["ANSYS_LOGO_2D_2"], amplitude=0, name="0V") -################################################################################## -# Setup conductance calculation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Setup conductance calculation +# # 1V is the source, 0V ground m2d.assign_matrix(sources=['1V'], group_sources=['0V'], matrix_name="Matrix1") -################################################################################## -# Assign mesh operation -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Assign mesh operation +# # 3mm on the conductor m2d.mesh.assign_length_mesh(["ANSYS_LOGO_2D_3"], meshop_name="conductor", maxlength=3, maxel=None) -################################################################################## -# Create simulation setup and enable expression cache -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create simulation setup and enable expression cache +# # Create simulation setup with minimum 4 adaptive passes to ensure convergence. # Enable expression cache to observe the convergence. @@ -97,9 +88,8 @@ use_cache_for_freq=False) setup1.analyze() -################################################################################## -# Create parametric sweep -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create parametric sweep +# # Create parametric sweep to sweep all the entries in the material array. # Save fields and mesh and use the mesh for all the materials. @@ -111,9 +101,8 @@ param_sweep["SolveWithCopiedMeshOnly"] = True param_sweep.analyze() -################################################################################## -# Create resistance report -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create resistance report +# # Create R. vs. material report variations = {"MaterialIndex": ["All"], "MaterialThickness": ["Nominal"]} @@ -129,21 +118,16 @@ d.primary_sweep = "MaterialIndex" d.plot() - - - -################################################################################## -# Field overlay -# ~~~~~~~~~~~~~ +# ## Field overlay +# # Plot electric field and current density on the conductor surface conductor_surface = m2d.modeler["ANSYS_LOGO_2D_3"].faces m2d.post.create_fieldplot_surface(conductor_surface, "Mag_E", plot_name="Electric Field") m2d.post.create_fieldplot_surface(conductor_surface, "Mag_J", plot_name="Current Density") -################################################################################## -# Field overlay -# ~~~~~~~~~~~~~ +# ## Field overlay +# # Plot electric field using pyvista and saving to an image py_vista_plot = m2d.post.plot_field("Mag_E", conductor_surface, plot_cad_objs=False, show=False) @@ -155,9 +139,8 @@ py_vista_plot.azimuth_angle = 0 py_vista_plot.plot(os.path.join(m2d.working_directory, "Image.jpg")) -################################################################################## -# Field animation -# ~~~~~~~~~~~~~~~ +# ## Field animation +# # Plot current density vs the Material index. animated = m2d.post.plot_animated_field( @@ -179,7 +162,6 @@ animated.animate() -################################################################################## -# Release desktop -# ~~~~~~~~~~~~~~~ +# ## Release desktop + m2d.release_desktop() diff --git a/examples/03-Maxwell/Maxwell2D_Electrostatic.py b/examples/03-Maxwell/Maxwell2D_Electrostatic.py index df21b3763f8..0f1f391dce5 100644 --- a/examples/03-Maxwell/Maxwell2D_Electrostatic.py +++ b/examples/03-Maxwell/Maxwell2D_Electrostatic.py @@ -1,45 +1,39 @@ -""" -Maxwell 2D Electrostatic analysis ---------------------------------- -This example shows how you can use PyAEDT to create a Maxwell 2D electrostatic analysis. -It shows how to create the geometry, load material properties from an Excel file and -set up the mesh settings. Moreover, it focuses on post-processing operations, in particular how to -plot field line traces, relevant for an electrostatic analysis. - -""" -################################################################################# -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Maxwell 2D Electrostatic analysis + +# This example shows how you can use PyAEDT to create a Maxwell 2D electrostatic analysis. +# It shows how to create the geometry, load material properties from an Excel file and +# set up the mesh settings. Moreover, it focuses on post-processing operations, in particular how to +# plot field line traces, relevant for an electrostatic analysis. + +# ## Perform required imports +# # Perform required imports. import pyaedt -################################################################################# -# Initialize Maxwell 2D -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize Maxwell 2D +# # Initialize Maxwell 2D, providing the version, path to the project, and the design # name and type. desktopVersion = '2023.2' - sName = 'MySetupAuto' sType = 'Electrostatic' dName = 'Design1' pName = pyaedt.generate_unique_project_name() non_graphical = False -################################################################################# -# Download .xlsx file -# ~~~~~~~~~~~~~~~~~~~ +# ## Download .xlsx file +# # Set local temporary folder to export the .xlsx file to. file_name_xlsx = pyaedt.downloads.download_file("field_line_traces", "my_copper.xlsx") -################################################################################# -# Initialize dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize dictionaries +# # Initialize dictionaries that contain all the definitions for the design variables. +# + geom_params_circle = { 'circle_x0': '-10mm', 'circle_y0': '0mm', @@ -56,10 +50,10 @@ 'r_dx': '-1mm', 'r_dy': '-10mm' } +# - -################################################################################## -# Launch Maxwell 2D -# ~~~~~~~~~~~~~~~~~ +# ## Launch Maxwell 2D +# # Launch Maxwell 2D and save the project. M2D = pyaedt.Maxwell2d(projectname=pName, @@ -70,18 +64,16 @@ non_graphical=non_graphical ) -################################################################################## -# Create object to access 2D modeler -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object to access 2D modeler +# # Create the object ``mod2D`` to access the 2D modeler easily. mod2D = M2D.modeler mod2D.delete() mod2D.model_units = "mm" -################################################################################## -# Define variables from dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define variables from dictionaries +# # Define design variables from the created dictionaries. for k, v in geom_params_circle.items(): @@ -89,19 +81,18 @@ for k, v in geom_params_rectangle.items(): M2D[k] = v -################################################################################## -# Read materials from .xslx file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Read materials from .xslx file +# # Read materials from .xslx file into and set into design. mats = M2D.materials.import_materials_from_excel(file_name_xlsx) -################################################################################## -# Create design geometries -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create design geometries +# # Create rectangle and a circle and assign the material read from the .xlsx file. # Create two new polylines and a region. +# + rect = mod2D.create_rectangle(position=['r_x0', 'r_y0', 'r_z0'], dimension_list=['r_dx', 'r_dy', 0], name='Ground', matname=mats[0]) @@ -119,25 +110,23 @@ poly2_id = mod2D.create_polyline(position_list=poly2_points, name='Poly2') mod2D.split([poly1_id, poly2_id], 'YZ', sides='NegativeOnly') mod2D.create_region([20, 100, 20, 100]) +# - -################################################################################## -# Define excitations -# ~~~~~~~~~~~~~~~~~~ +# ## Define excitations +# # Assign voltage excitations to rectangle and circle. M2D.assign_voltage(rect.id, amplitude=0, name='Ground') M2D.assign_voltage(circle.id, amplitude=50e6, name='50kV') -################################################################################## -# Create initial mesh settings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create initial mesh settings +# # Assign a surface mesh to the rectangle. M2D.mesh.assign_surface_mesh_manual(names=['Ground'], surf_dev=0.001) -################################################################################## -# Create, validate and analyze the setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create, validate and analyze the setup +# # Create, update, validate and analyze the setup. setup = M2D.create_setup(setupname=sName) @@ -146,9 +135,8 @@ M2D.validate_simple() M2D.analyze_setup(sName) -################################################################################## -# Evaluate the E Field tangential component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Evaluate the E Field tangential component +# # Evaluate the E Field tangential component along the given polylines. # Add these operations to the Named Expression list in Field Calculator. @@ -165,9 +153,8 @@ fields.CalcOp("Dot") fields.AddNamedExpression("e_tan_poly2", "Fields") -################################################################################## -# Create Field Line Traces Plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create Field Line Traces Plot +# # Create Field Line Traces Plot specifying as seeding faces # the ground, the electrode and the region # and as ``In surface objects`` only the region. @@ -176,9 +163,8 @@ "Region", plot_name="LineTracesTest") -################################################################################### -# Update Field Line Traces Plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Update Field Line Traces Plot +# # Update field line traces plot. # Update seeding points number, line style and line width. @@ -187,9 +173,8 @@ plot.LineWidth = 3 plot.update() -################################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. M2D.save_project() diff --git a/examples/03-Maxwell/Maxwell2D_NissanLeaf.py b/examples/03-Maxwell/Maxwell2D_NissanLeaf.py index 54fc718f5ea..dda5f87e62c 100644 --- a/examples/03-Maxwell/Maxwell2D_NissanLeaf.py +++ b/examples/03-Maxwell/Maxwell2D_NissanLeaf.py @@ -1,13 +1,10 @@ -""" -Maxwell 2D: PM synchronous motor transient analysis ---------------------------------------------------- -This example shows how you can use PyAEDT to create a Maxwell 2D transient analysis for -an interior permanent magnet electric motor. - -""" -################################################################################# -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Maxwell 2D: PM synchronous motor transient analysis +# +# This example shows how you can use PyAEDT to create a Maxwell 2D transient analysis for +# an interior permanent magnet electric motor. + +# ## Perform required imports +# # Perform required imports. from math import sqrt as mysqrt @@ -16,29 +13,24 @@ import os import pyaedt -################################################################################# -# Initialize Maxwell 2D -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize Maxwell 2D +# # Initialize Maxwell 2D, providing the version, path to the project, and the design # name and type. desktopVersion = "2023.2" - sName = "MySetupAuto" sType = "TransientXY" - pName = pyaedt.generate_unique_project_name() dName = "Sinusoidal" -################################################################################# -# Initialize dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize dictionaries +# # Initialize dictionaries that contain all the definitions for the design # variables and output variables. -################################################################################# -# Initialize definitions for stator, rotor, and shaft -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize definitions for stator, rotor, and shaft +# # Initialize geometry parameter definitions for the stator, rotor, and shaft. # The naming refers to RMxprt primitives. @@ -54,9 +46,8 @@ "SlotType": "3" } -################################################################################# -# Initialize definitions for stator windings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize definitions for stator windings +# # Initialize geometry parameter definitions for the stator windings. The naming # refers to RMxprt primitives. @@ -74,9 +65,8 @@ "Coil_Edge_Long": "15.37828521mm" } -################################################################################# -# Initialize definitions for model setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize definitions for model setup +# # Initialize geometry parameter definitions for the model setup. mod_params = { @@ -92,9 +82,8 @@ "Section_Angle": "360deg/SymmetryFactor" } -################################################################################# -# Initialize definitions for operational machine -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize definitions for operational machine +# # Initialize geometry parameter definitions for the operational machine. This # identifies the operating point for the transient setup. @@ -110,18 +99,16 @@ "Theta_i": "135deg" } -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ``"PYAEDT_NON_GRAPHICAL"`` is needed to # generate documentation only. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -########################################################## -# Launch Maxwell 2D -# ~~~~~~~~~~~~~~~~~ +# ## Launch Maxwell 2D +# # Launch Maxwell 2D and save the project. M2D = pyaedt.Maxwell2d(projectname=pName, @@ -132,18 +119,16 @@ non_graphical=non_graphical ) -########################################################## -# Create object to access 2D modeler -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object to access 2D modeler +# # Create the object ``mod2D`` to access the 2D modeler easily. mod2D = M2D.modeler mod2D.delete() mod2D.model_units = "mm" -########################################################## -# Define variables from dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define variables from dictionaries +# # Define design variables from the created dictionaries. for k, v in geom_params.items(): @@ -155,17 +140,15 @@ for k, v in oper_params.items(): M2D[k] = v -########################################################## -# Define path for non-linear material properties -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define path for non-linear material properties +# # Define the path for non-linear material properties. # Materials are stored in text files. filename_lam, filename_PM = pyaedt.downloads.download_leaf() -########################################################## -# Create first material -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Create first material +# # Create the material ``"Copper (Annealed)_65C"``. mat_coils = M2D.materials.add_material("Copper (Annealed)_65C") @@ -173,9 +156,8 @@ mat_coils.conductivity = "49288048.9198" mat_coils.permeability = "1" -########################################################## -# Create second material -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create second material +# # Create the material ``"Arnold_Magnetics_N30UH_80C"``. # The BH curve is read from a tabbed CSV file, and a list (``BH_List_PM``) # is created. This list is passed to the ``mat_PM.permeability.value`` @@ -194,9 +176,8 @@ BH_List_PM.append([float(row[0]), float(row[1])]) mat_PM.permeability.value = BH_List_PM -########################################################## -# Create third material -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Create third material +# # Create the laminated material ``30DH_20C_smooth``. # This material has a BH curve and a core loss model, # which is set to electrical steel. @@ -219,13 +200,13 @@ BH_List_lam.append([float(row[0]), float(row[1])]) mat_lam.permeability.value = BH_List_lam -########################################################## -# Create geometry for stator -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for stator +# # Create the geometry for the stator. It is created via # the RMxprt user-defined primitive. A list of lists is # created with the proper UDP parameters. +# + udp_par_list_stator = [["DiaGap", "DiaGap"], ["DiaYoke", "DiaStatorYoke"], ["Length", "Stator_Lam_Length"], ["Skew", "StatorSkewAngle"], ["Slots", "SlotNumber"], ["SlotType", "SlotType"], ["Hs0", "1.2mm"], ["Hs01", "0mm"], ["Hs1", "0.4834227384999mm"], @@ -240,10 +221,10 @@ stator_id = mod2D.create_udp(udp_dll_name="RMxprt/VentSlotCore.dll", udp_parameters_list=udp_par_list_stator, upd_library='syslib', name='my_stator') # name not taken +# - -########################################################## -# Assign properties to stator -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign properties to stator +# # Assign properties to the stator. The following code assigns # the material, name, color, and ``solve_inside`` properties. @@ -252,10 +233,8 @@ stator_id.color = (0, 0, 255) # rgb stator_id.solve_inside = True # to be reassigned: M2D.assign material puts False if not dielectric - -##################################################################################### -# Create geometry for PMs -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for PMs +# # Create the geometry for the PMs (permanent magnets). In Maxwell 2D, you assign # magnetization via the coordinate system. Because each PM needs to have a coordinate # system in the face center, auxiliary functions are created. Here, you use the auxiliary @@ -265,10 +244,8 @@ def find_elements(lst1, lst2): return [lst1[i] for i in lst2] - -##################################################################################### -# Find largest elements in list -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Find largest elements in list +# # Use the auxiliary function ``find_n_largest (input_len_list, n_largest_edges)`` # to find the ``n`` largest elements in the list ``input_len_list``. @@ -282,15 +259,14 @@ def find_n_largest(input_len_list, n_largest_edges): tmp[tmp.index(copied[-n])] = 0 # index can only get the first occurrence that solves the problem return index_list - -##################################################################################### -# Create coordinate system for PMs -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system for PMs +# # Create the coordinate system for the PMs. The inputs are the object name, coordinate # system name, and inner or outer magnetization. Find the two longest edges of the magnets # and get the midpoint of the outer edge. You must have this point to create the face # coordinate systems in case of outer magnetization. +# + def create_cs_magnets(pm_id, cs_name, point_direction): pm_face_id = mod2D.get_object_faces(pm_id.name)[0] # works with name only pm_edges = mod2D.get_object_edges(pm_id.name) # gets the edges of the PM object @@ -313,11 +289,10 @@ def create_cs_magnets(pm_id, cs_name, point_direction): axis="X", name=cs_name) pm_id.part_coordinate_system = cs_name mod2D.set_working_coordinate_system('Global') +# - - -##################################################################################### -# Create outer and inner PMs -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create outer and inner PMs +# # Create the outer and inner PMs and assign color to them. IM1_points = [[56.70957112, 3.104886585, 0], [40.25081875, 16.67243502, 0], [38.59701538, 14.66621111, 0], @@ -331,26 +306,23 @@ def create_cs_magnets(pm_id, cs_name, point_direction): matname="Arnold_Magnetics_N30UH_80C_new") OPM1_id.color = (0, 128, 64) -##################################################################################### -# Create coordinate system for PMs in face center -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system for PMs in face center +# # Create the coordinate system for PMs in the face center. create_cs_magnets(IPM1_id, 'CS_' + IPM1_id.name, 'outer') create_cs_magnets(OPM1_id, 'CS_' + OPM1_id.name, 'outer') -##################################################################################### -# Duplicate and mirror PMs -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Duplicate and mirror PMs +# # Duplicate and mirror the PMs along with the local coordinate system. mod2D.duplicate_and_mirror([IPM1_id, OPM1_id], position=[0, 0, 0], vector=["cos((360deg/SymmetryFactor/2)+90deg)", "sin((360deg/SymmetryFactor/2)+90deg)", 0]) id_PMs = mod2D.get_objects_w_string("PM", case_sensitive=True) -########################################################## -# Create coils -# ~~~~~~~~~~~~ +# ## Create coils +# # Create the coils. coil_id = mod2D.create_rectangle(position=['DiaRotorLam/2+Airgap+Coil_SetBack', '-Coil_Edge_Short/2', 0], @@ -362,9 +334,8 @@ def create_cs_magnets(pm_id, cs_name, point_direction): create_new_objects=True) id_coils = mod2D.get_objects_w_string("Coil", case_sensitive=True) -########################################################## -# Create shaft and region -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create shaft and region +# # Create the shaft and region. region_id = mod2D.create_circle(position=[0, 0, 0], radius='DiaOuter/2', @@ -372,9 +343,8 @@ def create_cs_magnets(pm_id, cs_name, point_direction): shaft_id = mod2D.create_circle(position=[0, 0, 0], radius='DiaShaft/2', num_sides='SegAngle', is_covered=True, name='Shaft') -########################################################## -# Create bands -# ~~~~~~~~~~~~ +# ## Create bands +# # Create the inner band, band, and outer band. bandIN_id = mod2D.create_circle(position=[0, 0, 0], radius='(DiaGap - (1.5 * Airgap))/2', @@ -384,29 +354,27 @@ def create_cs_magnets(pm_id, cs_name, point_direction): bandOUT_id = mod2D.create_circle(position=[0, 0, 0], radius='(DiaGap - (0.5 * Airgap))/2', num_sides='mapping_angle', is_covered=True, name='Outer_Band') -########################################################## -# Assign motion setup to object -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign motion setup to object +# # Assign a motion setup to a ``Band`` object named ``RotatingBand_mid``. M2D.assign_rotate_motion(band_object='Band', coordinate_system="Global", axis="Z", positive_movement=True, start_position="InitialPositionMD", angular_velocity="MachineRPM") -########################################################## -# Create list of vacuum objects -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create list of vacuum objects +# # Create a list of vacuum objects and assign color. vacuum_obj_id = [shaft_id, region_id, bandIN_id, bandMID_id, bandOUT_id] # put shaft first for item in vacuum_obj_id: item.color = (128, 255, 255) -########################################################## -# Create rotor -# ~~~~~~~~~~~~ +# ## Create rotor +# # Create the rotor. Holes are specific to the lamination. # Allocated PMs are created. +# + rotor_id = mod2D.create_circle(position=[0, 0, 0], radius='DiaRotorLam/2', num_sides=0, name="Rotor", matname="30DH_20C_smooth") rotor_id.color = (0, 128, 255) @@ -434,13 +402,14 @@ def create_cs_magnets(pm_id, cs_name, point_direction): id_holes = mod2D.get_objects_w_string("slot_", case_sensitive=True) M2D.modeler.subtract(rotor_id, id_holes, keep_originals=True) +# - -########################################################## -# Create section of machine -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create section of machine +# # Create a section of the machine. This allows you to take # advantage of symmetries. +# + object_list = [stator_id, rotor_id] + vacuum_obj_id mod2D.create_coordinate_system(origin=[0, 0, 0], reference_cs="Global", @@ -453,10 +422,10 @@ def create_cs_magnets(pm_id, cs_name, point_direction): mod2D.split(object_list, "ZX", sides="NegativeOnly") mod2D.set_working_coordinate_system("Global") mod2D.split(object_list, "ZX", sides="PositiveOnly") +# - -########################################################## -# Create boundary conditions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create boundary conditions +# # Create independent and dependent boundary conditions. # Edges for assignment are picked by position. # The points for edge picking are in the airgap. @@ -472,9 +441,8 @@ def create_cs_magnets(pm_id, cs_name, point_direction): same_as_master=False, bound_name="Matching") -########################################################## -# Assign vector potential -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign vector potential +# # Assign a vector potential of ``0`` to the second position. pos_2 = "(DiaOuter/2)" @@ -483,18 +451,16 @@ def create_cs_magnets(pm_id, cs_name, point_direction): obj_name='Region') M2D.assign_vector_potential(id_bc_az, vectorvalue=0, bound_name="VectorPotentialZero") -########################################################## -# Create excitations -# ~~~~~~~~~~~~~~~~~~ +# ## Create excitations +# # Create excitations, defining phase currents for the windings. PhA_current = "IPeak * cos(2*pi*ElectricFrequency*time+Theta_i)" PhB_current = "IPeak * cos(2*pi * ElectricFrequency*time - 120deg+Theta_i)" PhC_current = "IPeak * cos(2*pi * ElectricFrequency*time - 240deg+Theta_i)" -########################################################## -# Define windings in phase A -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define windings in phase A +# # Define windings in phase A. M2D.assign_coil(input_object=["Coil"], conductor_number=6, polarity="Positive", name="CT_Ph1_P2_C1_Go") @@ -503,9 +469,8 @@ def create_cs_magnets(pm_id, cs_name, point_direction): current_value=PhA_current, parallel_branches=1, name="Phase_A") M2D.add_winding_coils(windingname="Phase_A", coil_names=["CT_Ph1_P2_C1_Go", "CT_Ph1_P2_C1_Ret"]) -########################################################## -# Define windings in phase B -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define windings in phase B +# # Define windings in phase B. M2D.assign_coil(input_object="Coil_3", conductor_number=6, polarity="Positive", name="CT_Ph3_P1_C2_Go") @@ -515,9 +480,8 @@ def create_cs_magnets(pm_id, cs_name, point_direction): name="Phase_B") M2D.add_winding_coils(windingname="Phase_B", coil_names=["CT_Ph3_P1_C2_Go", "CT_Ph3_P1_C1_Go"]) -########################################################## -# Define windings in phase C -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define windings in phase C +# # Define windings in phase C. M2D.assign_coil(input_object="Coil_1", conductor_number=6, polarity="Negative", name="CT_Ph2_P2_C2_Ret") @@ -527,65 +491,58 @@ def create_cs_magnets(pm_id, cs_name, point_direction): name="Phase_C") M2D.add_winding_coils(windingname="Phase_C", coil_names=["CT_Ph2_P2_C2_Ret", "CT_Ph2_P2_C1_Ret"]) -########################################################## -# Assign total current on PMs -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign total current on PMs +# # Assign a total current of ``0`` on the PMs. PM_list = id_PMs for item in PM_list: M2D.assign_current(item, amplitude=0, solid=True, name=item + "_I0") -########################################################## -# Create mesh operations -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create mesh operations +# # Create the mesh operations. M2D.mesh.assign_length_mesh(id_coils, isinside=True, maxlength=3, maxel=None, meshop_name="coils") M2D.mesh.assign_length_mesh(stator_id, isinside=True, maxlength=3, maxel=None, meshop_name="stator") M2D.mesh.assign_length_mesh(rotor_id, isinside=True, maxlength=3, maxel=None, meshop_name="rotor") -########################################################## -# Turn on eddy effects -# ~~~~~~~~~~~~~~~~~~~~ +# ## Turn on eddy effects +# # Turn on eddy effects. # M2D.eddy_effects_on(eddy_effects_list,activate_eddy_effects=True, activate_displacement_current=False) -########################################################## -# Turn on core loss -# ~~~~~~~~~~~~~~~~~ +# ## Turn on core loss +# # Turn on core loss. core_loss_list = ['Rotor', 'Stator'] M2D.set_core_losses(core_loss_list, value=True) -########################################################## -# Compute transient inductance -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Compute transient inductance +# # Compute the transient inductance. M2D.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=False) -########################################################## -# Set model depth -# ~~~~~~~~~~~~~~~ +# ## Set model depth +# # Set the model depth. M2D.model_depth = "Magnetic_Axial_Length" -########################################################## -# Set symmetry factor -# ~~~~~~~~~~~~~~~~~~~ +# ## Set symmetry factor +# # Set the symmetry factor. M2D.change_symmetry_multiplier("SymmetryFactor") -########################################################## -# Create setup and validate -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and validate +# # Create the setup and validate it. +# + setup = M2D.create_setup(setupname=sName) setup.props["StopTime"] = "StopTime" setup.props["TimeStep"] = "TimeStep" @@ -598,10 +555,10 @@ def create_cs_magnets(pm_id, cs_name, point_direction): model = M2D.plot(show=False) model.plot(os.path.join(M2D.working_directory, "Image.jpg")) +# - -################################################################################# -# Initialize definitions for output variables -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize definitions for output variables +# # Initialize the definitions for the output variables. # These will be used later to generate reports. @@ -646,25 +603,23 @@ def create_cs_magnets(pm_id, cs_name, point_direction): "U_q": "-2/3*(U_A*sin0 + U_B*sin1 + U_C*sin2)" } -########################################################## -# Create output variables for postprocessing -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create output variables for postprocessing +# # Create output variables for postprocessing. for k, v in output_vars.items(): M2D.create_output_variable(k, v) -################################################################################# -# Initialize definition for postprocessing plots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize definition for postprocessing plots +# # Initialize the definition for postprocessing plots. post_params = { "Moving1.Torque": "TorquePlots" } -################################################################################# -# Initialize definition for postprocessing multiplots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Initialize definition for postprocessing multiplots +# # Initialize the definition for postprocessing multiplots. post_params_multiplot = { # reports @@ -686,9 +641,8 @@ def create_cs_magnets(pm_id, cs_name, point_direction): ("SolidLoss", "SolidLoss(IPM1)", "SolidLoss(IPM1_1)", "SolidLoss(OPM1)", "SolidLoss(OPM1_1)"): "SolidLoss" } -########################################################## -# Create report -# ~~~~~~~~~~~~~ +# ## Create report +# # Create a report. for k, v in post_params.items(): @@ -697,20 +651,20 @@ def create_cs_magnets(pm_id, cs_name, point_direction): report_category=None, plot_type="Rectangular Plot", context=None, subdesign_id=None, polyline_points=1001, plotname=v) -########################################################## -# Create multiplot report -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create multiplot report +# # Create a multiplot report. +# + # for k, v in post_params_multiplot.items(): # M2D.post.create_report(expressions=list(k), setup_sweep_name="", domain="Sweep", variations=None, # primary_sweep_variable="Time", secondary_sweep_variable=None, # report_category=None, plot_type="Rectangular Plot", context=None, subdesign_id=None, # polyline_points=1001, plotname=v) +# - -########################################################## -# Create flux lines plot on region -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create flux lines plot on region +# # Create a flux lines plot on a region. The ``object_list`` is # formerly created when the section is applied. @@ -720,17 +674,15 @@ def create_cs_magnets(pm_id, cs_name, point_direction): M2D.post.create_fieldplot_surface(objlist=faces_reg, quantityName='Flux_Lines', intrinsincDict={"Time": "0.000"}, plot_name="Flux_Lines") -########################################################## -# Analyze and save project -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Analyze and save project +# # Analyze and save the project. M2D.save_project() M2D.analyze_setup(sName, use_auto_settings=False) -############################################### -# Get solution data -# ~~~~~~~~~~~~~~~~~ +# ## Get solution data +# # Get a simulation result from a solved setup and cast it in a ``SolutionData`` object. # Plot the desired expression by using Matplotlib plot(). @@ -738,26 +690,23 @@ def create_cs_magnets(pm_id, cs_name, point_direction): primary_sweep_variable="Time") solutions.plot() -############################################### -# Retrieve the data magnitude of an expression -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Retrieve the data magnitude of an expression +# # List of shaft torque points and compute average. mag = solutions.data_magnitude() avg = sum(mag)/len(mag) -############################################### -# Export a report to a file -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Export a report to a file +# # Export a 2D Plot data to a .csv file. M2D.post.export_report_to_file(output_dir=M2D.toolkit_directory, plot_name="TorquePlots", extension=".csv") -############################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # Close AEDT. M2D.release_desktop() diff --git a/examples/03-Maxwell/Maxwell2D_Transient.py b/examples/03-Maxwell/Maxwell2D_Transient.py index c65c9b89ab8..520b725d6c1 100644 --- a/examples/03-Maxwell/Maxwell2D_Transient.py +++ b/examples/03-Maxwell/Maxwell2D_Transient.py @@ -1,81 +1,71 @@ -""" -Maxwell 2D: transient winding analysis --------------------------------------- -This example shows how you can use PyAEDT to create a project in Maxwell 2D -and run a transient simulation. It runs only on Windows using CPython. - -The following libraries are required for the advanced postprocessing features -used in this example: - -- `Matplotlib `_ -- `Numpty `_ -- `PyVista `_ - -Install these libraries with: - -.. code:: - - pip install numpy pyvista matplotlib - -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Maxwell 2D: transient winding analysis +# +# This example shows how you can use PyAEDT to create a project in Maxwell 2D +# and run a transient simulation. It runs only on Windows using CPython. +# +# The following libraries are required for the advanced postprocessing features +# used in this example: +# +# - `Matplotlib `_ +# - `Numpty `_ +# - `PyVista `_ +# +# Install these libraries with: +# +# ```console +# pip install numpy pyvista matplotlib +# ``` + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Insert Maxwell 2D design and save project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Insert Maxwell 2D design and save project +# # Insert a Maxwell 2D design and save the project. maxwell_2d = pyaedt.Maxwell2d(solution_type="TransientXY", specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True, projectname=pyaedt.generate_unique_project_name()) -############################################################################### -# Create rectangle and duplicate it -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create rectangle and duplicate it +# # Create a rectangle and duplicate it. rect1 = maxwell_2d.modeler.create_rectangle([0, 0, 0], [10, 20], name="winding", matname="copper") added = rect1.duplicate_along_line([14, 0, 0]) rect2 = maxwell_2d.modeler[added[0]] -############################################################################### -# Create air region -# ~~~~~~~~~~~~~~~~~ +# ## Create air region +# # Create an air region. region = maxwell_2d.modeler.create_region([100, 100, 100, 100, 100, 100]) -############################################################################### -# Assign windings and balloon -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign windings and balloon +# # Assigns windings to the sheets and a balloon to the air region. maxwell_2d.assign_winding([rect1.name, rect2.name], name="PHA") maxwell_2d.assign_balloon(region.edges) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. maxwell_2d.plot(show=False, export_path=os.path.join(maxwell_2d.working_directory, "Image.jpg"), plot_air_objects=True) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create the transient setup. setup = maxwell_2d.create_setup() @@ -86,27 +76,25 @@ setup.props["Steps From"] = "0s" setup.props["Steps To"] = "0.002s" -############################################################################### -# Create rectangular plot -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create rectangular plot +# # Create a rectangular plot. maxwell_2d.post.create_report( "InputCurrent(PHA)", domain="Time", primary_sweep_variable="Time", plotname="Winding Plot 1" ) -############################################################################### -# Solve model -# ~~~~~~~~~~~ +# ## Solve model +# # Solve the model. maxwell_2d.analyze(use_auto_settings=False) -############################################################################### -# Create output and plot using PyVista -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create output and plot using PyVista +# # Create the output and plot it using PyVista. +# + cutlist = ["Global:XY"] face_lists = rect1.faces face_lists += rect2.faces @@ -129,21 +117,21 @@ animatedGif.roll_angle = 0 animatedGif.elevation_angle = 0 animatedGif.azimuth_angle = 0 + # Set off_screen to False to visualize the animation. # animatedGif.off_screen = False animatedGif.animate() +# - -############################################################################### -# Generate plot outside of AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate plot outside of AEDT +# # Generate the same plot outside AEDT. solutions = maxwell_2d.post.get_solution_data("InputCurrent(PHA)", primary_sweep_variable="Time") solutions.plot() -############################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # Close AEDT. maxwell_2d.release_desktop() diff --git a/examples/03-Maxwell/Maxwell3DTeam7.py b/examples/03-Maxwell/Maxwell3DTeam7.py index 8ffd1d989b6..a90e183bb2d 100644 --- a/examples/03-Maxwell/Maxwell3DTeam7.py +++ b/examples/03-Maxwell/Maxwell3DTeam7.py @@ -4,9 +4,8 @@ This example uses PyAEDT to set up the TEAM 7 problem for an asymmetric conductor with a hole and solve it using the Maxwell 3D Eddy Current solver. """ -########################################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports. from pyaedt import Maxwell3d @@ -15,20 +14,19 @@ import csv import os -########################################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -########################################################################################### -# Launch AEDT and Maxwell 3D -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and Maxwell 3D +# # Launch AEDT and Maxwell 3D. The following code sets up the project and design names, the solver, and # the version. It also creates an instance of the ``Maxwell3d`` class named ``M3D``. +# + Project_Name = "COMPUMAG" Design_Name = "TEAM 7 Asymmetric Conductor" Solver = "EddyCurrent" @@ -45,15 +43,16 @@ M3D.modeler.model_units = "mm" modeler = M3D.modeler Plot = M3D.odesign.GetModule("ReportSetup") +# - -########################################################################################### -# Add Maxwell 3D setup -# ~~~~~~~~~~~~~~~~~~~~ +# ## Add Maxwell 3D setup +# # Add a Maxwell 3D setup with frequency points at DC, 50 Hz, and 200Hz. # Otherwise, the default PyAEDT setup values are used. To approximate a DC field in the # Eddy Current solver, use a low frequency value. Second-order shape functions improve # the smoothness of the induced currents in the plate. +# + dc_freq = 0.1 stop_freq = 50 @@ -63,10 +62,10 @@ Setup.add_eddy_current_sweep("LinearStep", dc_freq, stop_freq, stop_freq - dc_freq, clear=True) Setup.props["UseHighOrderShapeFunc"] = True Setup.props["PercentError"] = 0.4 +# - -########################################################################################### -# Define coil dimensions -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Define coil dimensions +# # Define coil dimensions as shown on the TEAM7 drawing of the coil. coil_external = 150 + 25 + 25 @@ -85,29 +84,27 @@ dim3 = dim2 + np.sqrt(((coil_r1 + (coil_r2 - coil_r1) / 2) ** 2) / 2) # Use coordinates to draw a polyline along which to sweep the coil cross sections. + P1 = [dim1, -dim2, 0] P2 = [dim1, dim2, 0] P3 = [dim3, dim3, 0] P4 = [dim2, dim1, 0] -########################################################################################### -# Create coordinate system for positioning coil -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system for positioning coil +# # Create a coordinate system for positioning the coil. M3D.modeler.create_coordinate_system(origin=coil_centre, mode="view", view="XY", name="Coil_CS") -########################################################################################### -# Create polyline -# ~~~~~~~~~~~~~~~ +# ## Create polyline +# # Create a polyline. One quarter of the coil is modeled by sweeping a 2D sheet along a polyline. test = M3D.modeler.create_polyline(position_list=[P1, P2, P3, P4], segment_type=["Line", "Arc"], name="Coil") test.set_crosssection_properties(type="Rectangle", width=coil_thk, height=coil_height) -########################################################################################### -# Duplicate and unite polyline to create full coil -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Duplicate and unite polyline to create full coil +# # Duplicate and unit the polyline to create a full coil. M3D.modeler.duplicate_around_axis( @@ -117,26 +114,24 @@ M3D.modeler.unite("Coil,Coil_3") M3D.modeler.fit_all() -########################################################################################### -# Assign material and if solution is allowed inside coil -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign material and if solution is allowed inside coil +# # Assign the material ``Cooper`` from the Maxwell internal library to the coil and # allow a solution inside the coil. M3D.assign_material("Coil", "Copper") M3D.solve_inside("Coil") -########################################################################################### -# Create terminal -# ~~~~~~~~~~~~~~~ +# ## Create terminal +# # Create a terminal for the coil from a cross section that is split and one half deleted. M3D.modeler.section("Coil", "YZ") M3D.modeler.separate_bodies("Coil_Section1") M3D.modeler.delete("Coil_Section1_Separate1") -# Add variable for coil excitation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Add variable for coil excitation +# # Add a design variable for coil excitation. The NB units here are AmpereTurns. Coil_Excitation = 2742 @@ -144,17 +139,15 @@ M3D.assign_current("Coil_Section1", amplitude="Coil_Excitation", solid=False) M3D.modeler.set_working_coordinate_system("Global") -########################################################################################### -# Add a material -# ~~~~~~~~~~~~~~ +# ## Add a material +# # Add a material named ``team3_aluminium``. mat = M3D.materials.add_material("team7_aluminium") mat.conductivity = 3.526e7 -########################################################################################### -# Model aluminium plate with a hole -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Model aluminium plate with a hole +# # Model the aluminium plate with a hole by subtracting two rectangular cuboids. plate = M3D.modeler.create_box(position=[0, 0, 0], dimensions_list=[294, 294, 19], name="Plate", @@ -163,16 +156,14 @@ hole = M3D.modeler.create_box(position=[18, 18, 0], dimensions_list=[108, 108, 19], name="Hole") M3D.modeler.subtract("Plate", ["Hole"], keep_originals=False) -########################################################################################### -# Draw a background region -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Draw a background region +# # Draw a background region that uses the default properties for an air region. M3D.modeler.create_air_region(x_pos=100, y_pos=100, z_pos=100, x_neg=100, y_neg=100, z_neg=100) -################################################################################ -# Adjust eddy effects for plate and coil -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Adjust eddy effects for plate and coil +# # Adjust the eddy effects for the plate and coil by turning off displacement currents # for all parts. The setting for eddy effect is ignored for the stranded conductor type # used in the coil. @@ -182,9 +173,8 @@ activate_eddy_effects=False, activate_displacement_current=False) -################################################################################ -# Create expression for Z component of B in Gauss -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create expression for Z component of B in Gauss +# # Create an expression for the Z component of B in Gauss using the fields calculator. Fields = M3D.odesign.GetModule("FieldsReporter") @@ -197,12 +187,12 @@ Fields.CalcOp("Smooth") Fields.AddNamedExpression("Bz", "Fields") -################################################################################ -# Draw two lines along which to plot Bz -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Draw two lines along which to plot Bz +# # Draw two lines along which to plot Bz. The following code also adds a small cylinder # to refine the mesh locally around each line. +# + lines = ["Line_A1_B1", "Line_A2_B2"] mesh_diameter = "2mm" @@ -215,19 +205,19 @@ polyline2 = modeler.create_polyline(position_list=line_points_2, name=lines[1]) polyline2_mesh = modeler.create_polyline(position_list=line_points_2, name=lines[1] + "mesh") polyline2_mesh.set_crosssection_properties(type="Circle", width=mesh_diameter) +# - -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. M3D.plot(show=False, export_path=os.path.join(M3D.working_directory, "Image.jpg"), plot_air_objects=False) -################################################################################ # Published measurement results are included with this script via the list below. # Test results are used to create text files for import into a rectangular plot # and to overlay simulation results. +# + project_dir = M3D.working_directory dataset = [ "Bz A1_B1 000 0", @@ -344,12 +334,14 @@ ], [-1.35, -0.71, -0.81, -0.67, 0.15, 1.39, 2.67, 3.00, 4.01, 3.80, 4.00, 3.02, 2.20, 2.78, 1.58, 1.37, 0.93], ] +# - # Dataset details are used to encode test parameters in the text files. # For example, ``Bz A1_B1 050 0`` is the Z component of flux density ``B`` # along line ``A1_B1`` at 50 Hz and 0 deg. These text files are created # and saved in the default project directory. +# + print("project_dir", project_dir) dataset_range = range(int(0), len(dataset), int(1)) line_length_range = range(int(0), len(line_length), int(1)) @@ -364,9 +356,10 @@ writer = csv.writer(f, delimiter=",") writer.writerow(header) writer.writerows(ziplist) - -# Create rectangular plots -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# - + +# ## Create rectangular plots +# # Create rectangular plots, using text file encoding to control their formatting. Create # the DC plot separately because it needs a different frequency and phase than the other plots. @@ -404,8 +397,8 @@ expressions=dataset[item][0:2], ) -# Import test data into correct plot and overlay with simulation results -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Import test data into correct plot and overlay with simulation results +# # Import test data into the correct plot and overlay it with the simulation results. if item == 0: @@ -414,9 +407,8 @@ Plot.ImportIntoReport(plotname, project_dir + "\\" + str(dataset[item - 1]) + ".csv") Plot.ImportIntoReport(plotname, project_dir + "\\" + str(dataset[item]) + ".csv") -################################################################################################### -# Create plots of induced current and flux density on surface of plate -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create plots of induced current and flux density on surface of plate +# # Create two plots of the induced current (``Mag_J``) and the flux density (``Mag_B``) on the # surface of the plate. @@ -425,17 +417,15 @@ M3D.post.create_fieldplot_surface(surflist, "Mag_J", intrinsincDict=intrinsic_dict, plot_name="Mag_J") M3D.post.create_fieldplot_surface(surflist, "Mag_B", intrinsincDict=intrinsic_dict, plot_name="Mag_B") -################################################################################################### -# Save project and solve -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and solve +# # Save the project and solve it. M3D.save_project() M3D.analyze() -#################################################################################################### -# Release AEDT from PyAEDT scripting -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Release AEDT from PyAEDT scripting +# # Release AEDT from PyAEDT scripting. If you wanted to leave AEDT and the project open # after running the above script, in the following command, you would set ``(False, False)``. diff --git a/examples/03-Maxwell/Maxwell3D_Choke.py b/examples/03-Maxwell/Maxwell3D_Choke.py index d21690b8d1e..8a4b14ea1aa 100644 --- a/examples/03-Maxwell/Maxwell3D_Choke.py +++ b/examples/03-Maxwell/Maxwell3D_Choke.py @@ -1,29 +1,25 @@ -""" -Maxwell 3D: choke setup ------------------------ -This example shows how you can use PyAEDT to create a choke setup in Maxwell 3D. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Maxwell 3D: choke setup +# +# This example shows how you can use PyAEDT to create a choke setup in Maxwell 3D. + +# ## Perform required imports +# # Perform required imports. import json import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can define ``non_graphical`` either to ``True`` or ``False``. non_graphical = False version = "2023.2" -############################################################################### -# Launch Maxwell3D -# ~~~~~~~~~~~~~~~~ +# ## Launch Maxwell3D +# # Launch Maxwell 3D 2023 R2 in graphical mode. m3d = pyaedt.Maxwell3d(projectname=pyaedt.generate_unique_project_name(), @@ -33,9 +29,8 @@ new_desktop_session=True ) -############################################################################### -# Rules and information of use -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Rules and information of use +# # The dictionary values containing the different parameters of the core and # the windings that compose the choke. You must not change the main structure of # the dictionary. The dictionary has many primary keys, including @@ -98,20 +93,20 @@ "Inner Winding": {"Turns": 10, "Coil Pit(deg)": 4, "Occupation(%)": 0}, } -############################################################################### -# Convert dictionary to JSON file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Convert dictionary to JSON file +# # Covert a dictionary to a JSON file. PyAEDT methods ask for the path of the # JSON file as an argument. You can convert a dictionary to a JSON file. +# + json_path = os.path.join(m3d.working_directory, "choke_example.json") with open(json_path, "w") as outfile: json.dump(values, outfile) +# - -############################################################################### -# Verify parameters of JSON file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Verify parameters of JSON file +# # Verify parameters of the JSON file. The ``check_choke_values`` method takes # the JSON file path as an argument and does the following: # @@ -121,9 +116,8 @@ dictionary_values = m3d.modeler.check_choke_values(json_path, create_another_file=False) print(dictionary_values) -############################################################################### -# Create choke -# ~~~~~~~~~~~~ +# ## Create choke +# # Create the choke. The ``create_choke`` method takes the JSON file path as an # argument. @@ -134,9 +128,8 @@ second_winding_list = list_object[3] third_winding_list = list_object[4] -############################################################################### -# Assign excitations -# ~~~~~~~~~~~~~~~~~~ +# ## Assign excitations +# # Assign excitations. first_winding_faces = m3d.modeler.get_object_faces(first_winding_list[0].name) @@ -149,16 +142,14 @@ m3d.assign_current([third_winding_faces[-1]], amplitude=1000, phase="240deg", swap_direction=False, name="phase_3_in") m3d.assign_current([third_winding_faces[-2]], amplitude=1000, phase="240deg", swap_direction=True, name="phase_3_out") -############################################################################### -# Assign matrix -# ~~~~~~~~~~~~~ +# ## Assign matrix +# # Assign the matrix. m3d.assign_matrix(["phase_1_in", "phase_2_in", "phase_3_in"], matrix_name="current_matrix") -############################################################################### -# Create mesh operation -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Create mesh operation +# # Create the mesh operation. mesh = m3d.mesh @@ -175,16 +166,14 @@ meshop_name="surface_approx", ) -############################################################################### -# Create boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Create boundaries +# # Create the boundaries. A region with openings is needed to run the analysis. region = m3d.modeler.create_air_region(x_pos=100, y_pos=100, z_pos=100, x_neg=100, y_neg=100, z_neg=0) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup with a sweep to run the simulation. Depending on your machine's # computing power, the simulation can take some time to run. @@ -196,18 +185,16 @@ setup.props["HasSweepSetup"] = True setup.add_eddy_current_sweep(range_type="LinearCount", start=100, end=1000, count=12, units="kHz", clear=True) -############################################################################### -# Save project -# ~~~~~~~~~~~~ +# ## Save project +# # Save the project. m3d.save_project() m3d.modeler.fit_all() m3d.plot(show=False, export_path=os.path.join(m3d.working_directory, "Image.jpg"), plot_air_objects=True) -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.release_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/03-Maxwell/Maxwell3D_Segmentation.py b/examples/03-Maxwell/Maxwell3D_Segmentation.py index e012e6b4e18..7fa569492a8 100644 --- a/examples/03-Maxwell/Maxwell3D_Segmentation.py +++ b/examples/03-Maxwell/Maxwell3D_Segmentation.py @@ -4,33 +4,30 @@ This example shows how you can use PyAEDT to segment magnets of an electric motor. The method is valid and usable for any object the user would like to segment. """ -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports. from pyaedt import downloads from pyaedt import generate_unique_folder_name from pyaedt import Maxwell3d -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -################################################################################# -# Download .aedt file example -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download .aedt file example +# # Set local temporary folder to export the .aedt file to. + temp_folder = generate_unique_folder_name() aedt_file = downloads.download_file("object_segmentation", "Motor3D_obj_segments.aedt", temp_folder) -################################################################################## -# Launch Maxwell 3D -# ~~~~~~~~~~~~~~~~~ +# ## Launch Maxwell 3D +# # Launch Maxwell 3D. m3d = Maxwell3d(projectname=aedt_file, @@ -38,16 +35,14 @@ new_desktop_session=True, non_graphical=non_graphical) -################################################################################## -# Create object to access 3D modeler -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object to access 3D modeler +# # Create the object ``mod3D`` to access the 3D modeler easily. modeler = m3d.modeler -################################################################################## -# Segment first magnet by specifying the number of segments -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Segment first magnet by specifying the number of segments +# # Select first magnet to segment by specifying the number of segments. # The method accepts in input either the list of magnets names to segment or # magnets ids or the magnet object :class:`pyaedt.modeler.cad.object3d.Object3d`. @@ -59,9 +54,8 @@ object_name = "PM_I1" sheets_1 = modeler.objects_segmentation(object_name, segments_number=segments_number, apply_mesh_sheets=True) -################################################################################## -# Segment second magnet by specifying the number of segments -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Segment second magnet by specifying the number of segments +# # Select second magnet to segment by specifying the number of segments. # In this specific case we give as input the id of the magnet. @@ -70,9 +64,8 @@ magnet_id = [obj.id for obj in modeler.object_list if obj.name == object_name][0] sheets_2 = modeler.objects_segmentation(magnet_id, segments_number=segments_number, apply_mesh_sheets=True) -################################################################################## -# Segment third magnet by specifying the segmentation thickness -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Segment third magnet by specifying the segmentation thickness +# # Select third magnet to segment by specifying the segmentation thickness. # In this specific case we give as input the magnet object of type :class:`pyaedt.modeler.cad.object3d.Object3d`. @@ -81,9 +74,8 @@ magnet = [obj for obj in modeler.object_list if obj.name == object_name][0] sheets_3 = modeler.objects_segmentation(magnet, segmentation_thickness=segmentation_thickness, apply_mesh_sheets=True) -################################################################################## -# Segment fourth magnet by specifying the number of segments -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Segment fourth magnet by specifying the number of segments +# # Select fourth magnet to segment by specifying the number of segments. # In this specific case we give as input the name of the magnet and we disable the mesh sheets. @@ -91,9 +83,8 @@ segments_number = 10 sheets_4 = modeler.objects_segmentation(object_name, segments_number=segments_number) -################################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. m3d.save_project() diff --git a/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py b/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py index d93025e6f78..919f42bb8e7 100644 --- a/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py +++ b/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py @@ -4,29 +4,27 @@ This example uses PyAEDT to set up the TEAM 3 bath plate problem and solve it using the Maxwell 3D Eddy Current solver. """ -################################################################################## -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports. import os import pyaedt -################################################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -################################################################################## -# Launch AEDT and Maxwell 3D -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and Maxwell 3D +# # Launch AEDT and Maxwell 3D after first setting up the project and design names, # the solver, and the version. The following code also creates an instance of the # ``Maxwell3d`` class named ``M3D``. +# + project_name = "COMPUMAG" design_name = "TEAM 3 Bath Plate" Solver = "EddyCurrent" @@ -42,34 +40,31 @@ ) uom = m3d.modeler.model_units = "mm" modeler = m3d.modeler +# - -############################################################################### -# Add variable -# ~~~~~~~~~~~~ +# ## Add variable +# # Add a design variable named ``Coil_Position`` that you use later to adjust the # position of the coil. Coil_Position = -20 m3d["Coil_Position"] = str(Coil_Position) + uom # Creates a design variable in Maxwell -################################################################################ -# Add material -# ~~~~~~~~~~~~ +# ## Add material +# # Add a material named ``team3_aluminium`` for the ladder plate. mat = m3d.materials.add_material("team3_aluminium") mat.conductivity = 32780000 -############################################################################### -# Draw background region -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Draw background region +# # Draw a background region that uses the default properties for an air region. m3d.modeler.create_air_region(x_pos=100, y_pos=100, z_pos=100, x_neg=100, y_neg=100, z_neg=100) -################################################################################ -# Draw ladder plate and assign material -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Draw ladder plate and assign material +# # Draw a ladder plate and assign it the newly created material ``team3_aluminium``. m3d.modeler.create_box(position=[-30, -55, 0], dimensions_list=[60, 110, -6.35], name="LadderPlate", @@ -78,16 +73,14 @@ m3d.modeler.create_box(position=[-20, 5, 0], dimensions_list=[40, 30, -6.35], name="CutoutTool2") m3d.modeler.subtract("LadderPlate", ["CutoutTool1", "CutoutTool2"], keep_originals=False) -################################################################################ -# Add mesh refinement to ladder plate -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Add mesh refinement to ladder plate +# # Add a mesh refinement to the ladder plate. m3d.mesh.assign_length_mesh("LadderPlate", maxlength=3, maxel=None, meshop_name="Ladder_Mesh") -################################################################################ -# Draw search coil and assign excitation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Draw search coil and assign excitation +# # Draw a search coil and assign it a ``stranded`` current excitation. # The stranded type forces the current density to be constant in the coil. @@ -103,9 +96,8 @@ m3d.modeler.delete("SearchCoil_Section1_Separate1") m3d.assign_current(object_list=["SearchCoil_Section1"], amplitude=1260, solid=False, name="SearchCoil_Excitation") -################################################################################ -# Draw a line for plotting Bz -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Draw a line for plotting Bz +# # Draw a line for plotting Bz later. Bz is the Z component of the flux # density. The following code also adds a small diameter cylinder to refine the # mesh locally around the line. @@ -115,16 +107,14 @@ P2 = modeler.create_polyline(position_list=Line_Points, name="Line_AB_MeshRefinement") P2.set_crosssection_properties(type="Circle", width="0.5mm") -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. m3d.plot(show=False, export_path=os.path.join(m3d.working_directory, "Image.jpg"), plot_air_objects=False) -############################################################################### -# Add Maxwell 3D setup -# ~~~~~~~~~~~~~~~~~~~~ +# ## Add Maxwell 3D setup +# # Add a Maxwell 3D setup with frequency points at 50 Hz and 200 Hz. Setup = m3d.create_setup(setupname="Setup1") @@ -132,18 +122,16 @@ Setup.props["HasSweepSetup"] = True Setup.add_eddy_current_sweep(range_type="LinearStep", start=50, end=200, count=150, clear=True) -################################################################################ -# Adjust eddy effects -# ~~~~~~~~~~~~~~~~~~~ +# ## Adjust eddy effects +# # Adjust eddy effects for the ladder plate and the search coil. The setting for # eddy effect is ignored for the stranded conductor type used in the search coil. m3d.eddy_effects_on(["LadderPlate"], activate_eddy_effects=True, activate_displacement_current=True) m3d.eddy_effects_on(["SearchCoil"], activate_eddy_effects=False, activate_displacement_current=True) -################################################################################ -# Add linear parametric sweep -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Add linear parametric sweep +# # Add a linear parametric sweep for the two coil positions. sweepname = "CoilSweep" @@ -152,15 +140,14 @@ param["CopyMesh"] = False param["SolveWithCopiedMeshOnly"] = True -# Solve parametric sweep -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Solve parametric sweep +# # Solve the parametric sweep directly so that results of all variations are available. m3d.analyze_setup(sweepname) -############################################################################### -# Create expression for Bz -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create expression for Bz +# # Create an expression for Bz using the fields calculator. Fields = m3d.odesign.GetModule("FieldsReporter") @@ -171,11 +158,11 @@ Fields.CalcOp("Smooth") Fields.AddNamedExpression("Bz", "Fields") -############################################################################### -# Plot mag(Bz) as a function of frequency -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot mag(Bz) as a function of frequency +# # Plot mag(Bz) as a function of frequency for both coil positions. +# + variations = {"Distance": ["All"], "Freq": ["All"], "Phase": ["0deg"], "Coil_Position": ["-20mm"]} m3d.post.create_report( expressions="mag(Bz)", @@ -195,12 +182,13 @@ primary_sweep_variable="Distance", plotname="mag(Bz) Along 'Line_AB' Coil", ) +# - -############################################################################### -# Generate plot outside of AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate plot outside of AEDT +# # Generate the same plot outside AEDT. +# + variations = {"Distance": ["All"], "Freq": ["All"], "Phase": ["0deg"], "Coil_Position": ["All"]} solutions = m3d.post.get_solution_data( @@ -210,35 +198,32 @@ variations=variations, primary_sweep_variable="Distance", ) +# - -############################################################################### -# Set up sweep value and plot solution -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set up sweep value and plot solution +# # Set up a sweep value and plot the solution. solutions.active_variation["Coil_Position"] = -0.02 solutions.plot() -############################################################################### -# Change sweep value and plot solution -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Change sweep value and plot solution +# # Change the sweep value and plot the solution again. solutions.active_variation["Coil_Position"] = 0 solutions.plot() -############################################################################### -# Plot induced current density on surface of ladder plate -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot induced current density on surface of ladder plate +# # Plot the induced current density, ``"Mag_J"``, on the surface of the ladder plate. surflist = modeler.get_object_faces("LadderPlate") intrinsic_dict = {"Freq": "50Hz", "Phase": "0deg"} m3d.post.create_fieldplot_surface(surflist, "Mag_J", intrinsincDict=intrinsic_dict, plot_name="Mag_J") -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT from the script engine, leaving both AEDT and the project open. m3d.release_desktop(True, True) diff --git a/examples/03-Maxwell/Maxwell_Control_Program.py b/examples/03-Maxwell/Maxwell_Control_Program.py index 182bccaf34c..20b66d45e27 100644 --- a/examples/03-Maxwell/Maxwell_Control_Program.py +++ b/examples/03-Maxwell/Maxwell_Control_Program.py @@ -1,40 +1,35 @@ -""" -Enabling Control Program in a Maxwell 2D Project ------------------------------------------------- -This example shows how you can use PyAEDT to enable control program in a Maxwell 2D project. -It shows how to create the geometry, load material properties from an Excel file and -set up the mesh settings. Moreover, it focuses on post-processing operations, in particular how to -plot field line traces, relevant for an electrostatic analysis. - -""" -################################################################################# -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Enabling Control Program in a Maxwell 2D Project + +# This example shows how you can use PyAEDT to enable control program in a Maxwell 2D project. +# It shows how to create the geometry, load material properties from an Excel file and +# set up the mesh settings. Moreover, it focuses on post-processing operations, in particular how to +# plot field line traces, relevant for an electrostatic analysis. + +# ## Perform required imports +# # Perform required imports. from pyaedt import downloads from pyaedt import generate_unique_folder_name from pyaedt import Maxwell2d -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -################################################################################# -# Download .aedt file example -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download .aedt file example +# # Set local temporary folder to export the .aedt file to. + temp_folder = generate_unique_folder_name() aedt_file = downloads.download_file("maxwell_ctrl_prg", "ControlProgramDemo.aedt", temp_folder) ctrl_prg_file = downloads.download_file("maxwell_ctrl_prg", "timestep_only.py", temp_folder) -################################################################################## -# Launch Maxwell 2D -# ~~~~~~~~~~~~~~~~~ +# ## Launch Maxwell 2D +# # Launch Maxwell 2D. m2d = Maxwell2d(projectname=aedt_file, @@ -42,46 +37,40 @@ new_desktop_session=True, non_graphical=non_graphical) -################################################################################## -# Set active design -# ~~~~~~~~~~~~~~~~~ +# ## Set active design +# # Set active design. m2d.set_active_design("1 time step control") -################################################################################## -# Get design setup -# ~~~~~~~~~~~~~~~~ +# ## Get design setup +# # Get design setup to enable the control program to. setup = m2d.setups[0] -################################################################################## -# Enable control program -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Enable control program +# # Enable control program by giving the path to the file. setup.enable_control_program(control_program_path=ctrl_prg_file) -################################################################################## -# Analyze setup -# ~~~~~~~~~~~~~ +# ## Analyze setup +# # Analyze setup. setup.analyze() -################################################################################## -# Plot results -# ~~~~~~~~~~~~ +# ## Plot results +# # Plot Solved Results. sols = m2d.post.get_solution_data("FluxLinkage(Winding1)",variations={"Time":["All"]}, primary_sweep_variable="Time") sols.plot() -################################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. m2d.save_project() diff --git a/examples/03-Maxwell/Maxwell_Magnet.py b/examples/03-Maxwell/Maxwell_Magnet.py index 5a5abc53db4..7dc9c562265 100644 --- a/examples/03-Maxwell/Maxwell_Magnet.py +++ b/examples/03-Maxwell/Maxwell_Magnet.py @@ -1,29 +1,25 @@ -""" -Maxwell 3D: magnet DC analysis ------------------------------- -This example shows how you can use PyAEDT to create a Maxwell DC analysis, -compute mass center, and move coordinate systems. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Maxwell 3D: magnet DC analysis + +# This example shows how you can use PyAEDT to create a Maxwell DC analysis, +# compute mass center, and move coordinate systems. + +# ## Perform required imports +# # Perform required imports. from pyaedt import Maxwell3d from pyaedt import generate_unique_project_name import os -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. m3d = Maxwell3d(projectname=generate_unique_project_name(), @@ -31,45 +27,39 @@ new_desktop_session=True, non_graphical=non_graphical) -############################################################################### -# Set up Maxwell solution -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set up Maxwell solution +# # Set up the Maxwell solution to DC. m3d.solution_type = m3d.SOLUTIONS.Maxwell3d.ElectroDCConduction -############################################################################### -# Create magnet -# ~~~~~~~~~~~~~ +# ## Create magnet +# # Create a magnet. magnet = m3d.modeler.create_box(position=[7, 4, 22], dimensions_list=[10, 5, 30], name="Magnet", matname="copper") -############################################################################### -# Create setup and assign voltage -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and assign voltage +# # Create the setup and assign a voltage. m3d.assign_voltage(magnet.faces, 0) m3d.create_setup() -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. m3d.plot(show=False, export_path=os.path.join(m3d.working_directory, "Image.jpg"), plot_air_objects=True) -############################################################################### -# Solve setup -# ~~~~~~~~~~~ +# ## Solve setup +# # Solve the setup. m3d.analyze() -############################################################################### -# Compute mass center -# ~~~~~~~~~~~~~~~~~~~ +# ## Compute mass center +# # Compute mass center using the fields calculator. m3d.post.ofieldsreporter.EnterScalarFunc("X") @@ -86,36 +76,32 @@ m3d.post.ofieldsreporter.AddNamedExpression("CM_Z", "Fields") m3d.post.ofieldsreporter.CalcStack("clear") -############################################################################### -# Get mass center -# ~~~~~~~~~~~~~~~ +# ## Get mass center +# # Get mass center using the fields calculator. xval = m3d.post.get_scalar_field_value("CM_X", None) yval = m3d.post.get_scalar_field_value("CM_Y", None) zval = m3d.post.get_scalar_field_value("CM_Z", None) -############################################################################### -# Create variables -# ~~~~~~~~~~~~~~~~ +# ## Create variables +# # Create variables with mass center values. m3d[magnet.name + "x"] = str(xval * 1e3) + "mm" m3d[magnet.name + "y"] = str(yval * 1e3) + "mm" m3d[magnet.name + "z"] = str(zval * 1e3) + "mm" -############################################################################### -# Create coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coordinate system +# # Create a parametric coordinate system. cs1 = m3d.modeler.create_coordinate_system( [magnet.name + "x", magnet.name + "y", magnet.name + "z"], reference_cs="Global", name=magnet.name + "CS" ) -############################################################################### -# Save and close -# ~~~~~~~~~~~~~~ +# ## Save and close +# # Save the project and close AEDT. m3d.save_project() diff --git a/examples/03-Maxwell/Maxwell_Transformer_Coreloss.py b/examples/03-Maxwell/Maxwell_Transformer_Coreloss.py index cab8575bbf2..9bff16e7a26 100644 --- a/examples/03-Maxwell/Maxwell_Transformer_Coreloss.py +++ b/examples/03-Maxwell/Maxwell_Transformer_Coreloss.py @@ -1,12 +1,10 @@ -""" -Maxwell 3D: Transformer ------------------------ -This example shows how you can use PyAEDT to set core loss given a set -of Power-Volume [kw/m^3] curves at different frequencies. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Maxwell 3D: Transformer + +# This example shows how you can use PyAEDT to set core loss given a set +# of Power-Volume [kw/m^3] curves at different frequencies. + +# ## Perform required imports +# # Perform required imports. from pyaedt import downloads @@ -15,11 +13,11 @@ from pyaedt.generic.constants import unit_converter from pyaedt.generic.general_methods import read_csv_pandas -################################################################################# -# Download .aedt file example -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download .aedt file example +# # Set local temporary folder to export the .aedt file to. +# + temp_folder = generate_unique_folder_name() aedt_file = downloads.download_file("core_loss_transformer", "Ex2-PlanarTransformer_2023R2.aedtz", temp_folder) freq_curve_csv_25kHz = downloads.download_file("core_loss_transformer", "mf3_25kHz.csv", temp_folder) @@ -47,10 +45,10 @@ data = read_csv_pandas(filename=freq_curve_csv_1MHz) curves_csv_1MHz = list(zip(data[data.columns[0]], data[data.columns[1]])) +# - -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. m3d = Maxwell3d(projectname=aedt_file, @@ -59,9 +57,8 @@ new_desktop_session=True, non_graphical=False) -############################################################################### -# Set core loss at frequencies -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set core loss at frequencies +# # Create a new material, create a dictionary of Power-Volume [kw/m^3] points for a set of frequencies # retrieved from datasheet provided by supplier and finally set Power-Ferrite core loss model. @@ -82,9 +79,8 @@ coefficients = m3d.materials[mat.name].get_core_loss_coefficients(points_list_at_freq=pv, coefficient_setup="kw_per_cubic_meter") -################################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. m3d.save_project() From 4fd46f71aa2cb32c8792098fbb96027b8e59c626 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 1 Feb 2024 16:58:20 +0100 Subject: [PATCH 31/56] MISC: convert 04-Icepak into md --- .../04-Icepak/Icepak_3DComponents_Example.py | 69 ++++++--- examples/04-Icepak/Icepak_CSV_Import.py | 43 +++--- examples/04-Icepak/Icepak_ECAD_Import.py | 88 +++++------ examples/04-Icepak/Icepak_Example.py | 66 ++++---- examples/04-Icepak/Sherlock_Example.py | 145 ++++++++---------- 5 files changed, 201 insertions(+), 210 deletions(-) diff --git a/examples/04-Icepak/Icepak_3DComponents_Example.py b/examples/04-Icepak/Icepak_3DComponents_Example.py index 9f498290f99..d501939af39 100644 --- a/examples/04-Icepak/Icepak_3DComponents_Example.py +++ b/examples/04-Icepak/Icepak_3DComponents_Example.py @@ -1,33 +1,33 @@ -""" -Icepak: thermal analysis with 3D components -------------------------------------------- -This example shows how to create a thermal analysis of an electronic package by taking advantage of 3D components and -features added by PyAEDT. -""" - -############################################################################### -# Import PyAEDT and download files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# # Icepak: thermal analysis with 3D components + +# This example shows how to create a thermal analysis of an electronic package by +# taking advantage of 3D components and features added by PyAEDT. + +# ## Import PyAEDT and download files +# # Perform import of required classes from the ``pyaedt`` package and import the ``os`` package. from pyaedt import Icepak, generate_unique_folder_name, downloads, settings import os + # Download needed files + temp_folder = generate_unique_folder_name() package_temp_name, qfp_temp_name = downloads.download_icepak_3d_component(temp_folder) -############################################################################### -# Create heatsink -# ~~~~~~~~~~~~~~~ +# ## Create heatsink +# # Open a new project in non-graphical mode. ipk = Icepak(projectname=os.path.join(temp_folder, "Heatsink.aedt"), specified_version="2023.2", non_graphical=True, close_on_exit=True, new_desktop_session=True) # Remove air region created by default because it is not needed as the heatsink will be exported as a 3dcomponent + ipk.modeler.get_object_from_name("Region").delete() # Definition of heatsink with boxes + hs_base = ipk.modeler.create_box(position=[0, 0, 0], dimensions_list=[37.5, 37.5, 2], name="HS_Base") hs_base.material_name="Al-Extruded" hs_fin = ipk.modeler.create_box(position=[0, 0, 2], dimensions_list=[37.5, 1, 18], name="HS_Fin1") @@ -37,6 +37,7 @@ ipk.plot(show=False, export_path=os.path.join(temp_folder, "Heatsink.jpg")) # Definition of a mesh region. First a non-model box is created, then the mesh region is assigned + mesh_box = ipk.modeler.create_box(position=[-2, -2, -3], dimensions_list=[41.5, 41.5, 24]) mesh_box.model = False mesh_region = ipk.mesh.assign_mesh_region([mesh_box.name]) @@ -50,6 +51,8 @@ mesh_region.update() # Assignment of monitor objects + +# + hs_fin5_object = ipk.modeler.get_object_from_name("HS_Fin1_5") point_monitor_position = [0.5 * (hs_base.bounding_box[i] + hs_base.bounding_box[i + 3]) for i in range(2)] + [ hs_fin5_object.bounding_box[-1]] # average x,y, top z @@ -58,9 +61,11 @@ monitor_name="TopPoint") ipk.monitor.assign_face_monitor(hs_base.bottom_face_z.id, monitor_quantity="Temperature", monitor_name="Bottom") ipk.monitor.assign_point_monitor_in_object("HS_Fin1_5", monitor_quantity="Temperature", monitor_name="Fin5Center") +# - # Export the heatsink 3D component and close project. auxiliary_dict is set to true in order to export the # monitor objects along with the .a3dcomp file. + os.mkdir(os.path.join(temp_folder, "componentLibrary")) ipk.modeler.create_3dcomponent( os.path.join(temp_folder, "componentLibrary", "Heatsink.a3dcomp"), @@ -69,14 +74,15 @@ ) ipk.close_project(save_project=False) -############################################################################### -# Create QFP -# ~~~~~~~~~~ +# ## Create QFP +# # Download and open a project containing a QPF. + ipk = Icepak(projectname=qfp_temp_name) ipk.plot(show=False, export_path=os.path.join(temp_folder, "QFP2.jpg")) # Create dataset for power dissipation. + x_datalist = [45, 53, 60, 70] y_datalist = [0.5, 3, 6, 9] ipk.create_dataset( @@ -93,6 +99,7 @@ ) # Assign source power condition to the die. + ipk.create_source_power( "DieSource", thermal_dependent_dataset="PowerDissipationDataset", @@ -100,6 +107,7 @@ ) # Assign thickness to the die attach surface. + ipk.create_conduting_plate(face_id="Die_Attach", thermal_specification="Thickness", shell_conduction=True, @@ -109,11 +117,13 @@ ) # Assign monitor objects. + ipk.monitor.assign_point_monitor_in_object("QFP2_die", monitor_quantity="Temperature", monitor_name="DieCenter") ipk.monitor.assign_surface_monitor("Die_Attach", monitor_quantity="Temperature", monitor_name="DieAttach") # Export the QFP 3D component and close project. Here the auxiliary dictionary allows exporting not only the monitor # objects but also the dataset used for the power source assignment. + ipk.modeler.create_3dcomponent( os.path.join(temp_folder, "componentLibrary", "QFP.a3dcomp"), component_name="QFP", @@ -122,16 +132,18 @@ ) ipk.close_project(save_project=False) -############################################################################### -# Create electronic package -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create electronic package +# # Download and open a project containing the electronic package. + ipk = Icepak(projectname=package_temp_name) ipk.plot(show=False, export_path=os.path.join(temp_folder, "electronic_package_missing_obj.jpg")) # The heatsink and the QFP are missing. They can be inserted as 3d components. The auxiliary files are needed since # the aim is to import also monitor objects and datasets. Also, a coordinate system is created for the heatsink so # that it is placed on top of the AGP. + +# + agp = ipk.modeler.get_object_from_name("AGP_IDF") cs = ipk.modeler.create_coordinate_system( origin=[agp.bounding_box[0], agp.bounding_box[1], agp.bounding_box[-1]], @@ -148,8 +160,10 @@ comp_file=os.path.join(temp_folder, "componentLibrary", "QFP.a3dcomp"), targetCS="Global", auxiliary_dict=True) ipk.plot(show=False, export_path=os.path.join(temp_folder, "electronic_package.jpg")) +# - # Create a coordinate system at the xmin, ymin, zmin of the model + bounding_box = ipk.modeler.get_model_bounding_box() cs_pcb_assembly = ipk.modeler.create_coordinate_system( origin=[bounding_box[0], bounding_box[1], bounding_box[2]], @@ -162,6 +176,8 @@ # Export of the whole assembly as 3d component and close project. First, a flattening is needed because nested 3d # components are not natively supported. Then it is possible to export the whole package as 3d component. Here the # auxiliary dictionary is needed to export monitor objects, datasets and native components. + +# + ipk.flatten_3d_components() ipk.modeler.create_3dcomponent( component_file=os.path.join(temp_folder, "componentLibrary", "PCBAssembly.a3dcomp"), @@ -172,18 +188,21 @@ ) ipk.close_project(save_project=False) +# - -############################################################################### -# Create main assembly -# ~~~~~~~~~~~~~~~~~~~~ +# ## Create main assembly +# # Open a new empty project. + ipk = Icepak(projectname=os.path.join(temp_folder, "main_assembly.aedt")) # Create a support for multiple PCB assemblies + support_obj = ipk.modeler.create_box(position=[-60, -160, 0], dimensions_list=[270, 580, -10], name="PCB_support") support_obj.material_name="plexiglass" # Create two coordinate systems to place two electronic package assemblies + cs1 = ipk.modeler.create_coordinate_system( origin=[-40, -120, 0], name="PCB1", @@ -200,6 +219,8 @@ ) # Import the electronic packages on the support + +# + PCB1_obj = ipk.modeler.insert_3d_component( comp_file=os.path.join(temp_folder, "componentLibrary", "PCBAssembly.a3dcomp"), targetCS=cs1.name, auxiliary_dict=True) @@ -207,8 +228,11 @@ PCB2_obj = ipk.modeler.insert_3d_component( comp_file=os.path.join(temp_folder, "componentLibrary", "PCBAssembly.a3dcomp"), targetCS=cs2.name, auxiliary_dict=True) +# - # To demonstrate once again the flexibility of this method: flatten the nested components structure again and export the whole assembly as a 3d component + +# + ipk.flatten_3d_components() ipk.modeler.create_3dcomponent( component_file=os.path.join(temp_folder, "componentLibrary", "MainAssembly.a3dcomp"), @@ -222,3 +246,4 @@ ipk.plot(show=False, export_path=os.path.join(temp_folder, "main_assembly.jpg")) ipk.close_project(save_project=True) ipk.release_desktop() +# - diff --git a/examples/04-Icepak/Icepak_CSV_Import.py b/examples/04-Icepak/Icepak_CSV_Import.py index b098c2e28be..e87283928a4 100644 --- a/examples/04-Icepak/Icepak_CSV_Import.py +++ b/examples/04-Icepak/Icepak_CSV_Import.py @@ -1,12 +1,10 @@ -""" -Icepak: Creating blocks and assigning materials and power -------------------------------------- -This example shows how to create different types of blocks and assign power and material to them using -a *.csv input file -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Icepak: Creating blocks and assigning materials and power + +# This example shows how to create different types of blocks and assign power and material to them using +# a *.csv input file + +# ## Perform required imports +# # Perform required imports including the operating system, regular expression, csv, Ansys PyAEDT # and its boundary objects. @@ -17,19 +15,18 @@ import pyaedt from pyaedt.modules.Boundary import BoundaryObject -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Download and open project -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download and open project +# # Download the project, open it, and save it to the temporary folder. +# + temp_folder = pyaedt.generate_unique_folder_name() ipk = pyaedt.Icepak(projectname=os.path.join(temp_folder, "Icepak_CSV_Import.aedt"), @@ -39,13 +36,14 @@ ) ipk.autosave_disable() +# - # Create the PCB as a simple block. + board = ipk.modeler.create_box([-30.48, -27.305, 0], [146.685, 71.755, 0.4064], "board_outline", matname="FR-4_Ref") -############################################################################### -# Blocks creation with a CSV file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Blocks creation with a CSV file +# # The CSV file lists the name of blocks, their type and material properties. # Block types (solid, network, hollow), block name, block starting and end points, solid material, and power are listed. # Hollow and network blocks do not need the material name. @@ -63,6 +61,7 @@ # It will create solid blocks and assign BCs. # Every row of the csv has information of a particular block. +# + filename = pyaedt.downloads.download_file('icepak', 'blocks-list.csv', destination=temp_folder) with open(filename, 'r') as csv_file: @@ -94,10 +93,10 @@ if row["Monitor_point"] == '1': ipk.monitor.assign_point_monitor_in_object(row["name"], monitor_quantity="Temperature", monitor_name=row[ "name"]) # creates the monitor point at the center of the object - -############################################################################### -# Save the project and close -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# - + +# ## Save the project and close +# # This will scale to fit all objects in AEDT and save the project. ipk.modeler.fit_all() diff --git a/examples/04-Icepak/Icepak_ECAD_Import.py b/examples/04-Icepak/Icepak_ECAD_Import.py index 2131a6011fc..0b15a6a1100 100644 --- a/examples/04-Icepak/Icepak_ECAD_Import.py +++ b/examples/04-Icepak/Icepak_ECAD_Import.py @@ -1,20 +1,18 @@ -""" -Icepak: Importing a PCB and its components via IDF and EDB -------------------------------------- -This example shows how to import a PCB and its components using IDF files (*.ldb/*.bdf). -The *.emn/*.emp combination can also be used in a similar way. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports including the operating system, Ansys PyAEDT packages. +# # Icepak: Importing a PCB and its components via IDF and EDB +# This example shows how to import a PCB and its components using IDF files (*.ldb/*.bdf). +# The *.emn/*.emp combination can also be used in a similar way. + +# ## Perform required imports +# +# Perform required imports including the operating system, Ansys PyAEDT packages. # Generic Python packages import os # PyAEDT Packages + import pyaedt from pyaedt import Icepak from pyaedt import Desktop @@ -22,20 +20,19 @@ from pyaedt.modules.Boundary import BoundaryObject -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Download and open project -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download and open project +# # Download the project, open it, and save it to the temporary folder. +# + temp_folder = pyaedt.generate_unique_folder_name() ipk = pyaedt.Icepak(projectname=os.path.join(temp_folder, "Icepak_ECAD_Import.aedt"), @@ -44,11 +41,11 @@ non_graphical=non_graphical ) -ipk.autosave_disable() # Saves the project +ipk.autosave_disable() +# - -############################################################################### -# Import the IDF files -# ~~~~~~~~~~~~~~~~~~~~ +# ## Import the IDF files +# # Sample *.bdf and *.ldf files are presented here. # # @@ -67,7 +64,7 @@ # In this examples, the default values are used for the PCB. # The imported PCB here will be deleted later and replaced by a PCB that has the trace information for higher accuracy. - +# + def_path = pyaedt.downloads.download_file('icepak/Icepak_ECAD_Import/A1_uprev.aedb','edb.def',temp_folder) board_path = pyaedt.downloads.download_file('icepak/Icepak_ECAD_Import/','A1.bdf',temp_folder) library_path = pyaedt.downloads.download_file('icepak/Icepak_ECAD_Import/','A1.ldf',temp_folder) @@ -83,58 +80,57 @@ substrate_material='FR-4', create_board=True, model_board_as_rect=False, model_device_as_rect=True, cutoff_height='5mm', component_lib='') +# - -############################################################################### -# Fit to scale, save the project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Fit to scale, save the project +# ipk.modeler.fit_all() # scales to fit all objects in AEDT ipk.save_project() # saves the project -############################################################################### -# Add an HFSS 3D Layout design with the layout information of the PCB -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Layout_name = 'A1_uprev' # 3D layout name available for import, the extension of .aedb should not be listed here - -hfss3dLO = Hfss3dLayout('Icepak_ECAD_Import', 'PCB_temp') # adding a dummy HFSS 3D layout to the current project - -#edb_full_path = os.path.join(os.getcwd(), Layout_name+'.aedb\edb.def') # path to the EDB file -hfss3dLO.import_edb(def_path) # importing the EDB file -hfss3dLO.save_project() # save the new project so files are stored in the path +# ## Add an HFSS 3D Layout design with the layout information of the PCB +# -ipk.delete_design(name='PCB_temp', fallback_design=None) # deleting the dummy layout from the original project +# 3D layout name available for import, the extension of .aedb should not be listed here +Layout_name = 'A1_uprev' +# Adding a dummy HFSS 3D layout to the current project +hfss3dLO = Hfss3dLayout('Icepak_ECAD_Import', 'PCB_temp') +# Importing the EDB file +hfss3dLO.import_edb(def_path) +# Save the new project so files are stored in the path +hfss3dLO.save_project() +# Deleting the dummy layout from the original project +ipk.delete_design(name='PCB_temp', fallback_design=None) # This part creates a 3D component PCB in Icepak from the imported EDB file # 1 watt is assigned to the PCB as power input +# + component_name = "PCB_ECAD" odb_path = os.path.join(temp_folder, 'icepak/Icepak_ECAD_Import/'+Layout_name+'.aedt') ipk.create_pcb_from_3dlayout( component_name, odb_path, Layout_name, resolution=2, extenttype="Polygon", outlinepolygon='poly_0', custom_x_resolution=None, custom_y_resolution=None,power_in=1) +# - -############################################################################### -# Delete PCB objects -# ~~~~~~~~~~~~~~~~~~ +# ## Delete PCB objects +# # Delete the PCB object from IDF import. ipk.modeler.delete_objects_containing("IDF_BoardOutline", False) -############################################################################### -# Compute power budget -# ~~~~~~~~~~~~~~~~~~~~ +# ## Compute power budget +# -# creates a setup to be able to calculate the power +# Creates a setup to be able to calculate the power ipk.create_setup("setup1") power_budget, total = ipk.post.power_budget("W") print(total) -############################################################################### -# Closing and releasing AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Closing and releasing AEDT +# ipk.close_project() # close the project ipk.release_desktop() # release the AEDT session. If this step is missing, AEDT cannot be closed. \ No newline at end of file diff --git a/examples/04-Icepak/Icepak_Example.py b/examples/04-Icepak/Icepak_Example.py index f66fe1f7501..a559680a3fb 100644 --- a/examples/04-Icepak/Icepak_Example.py +++ b/examples/04-Icepak/Icepak_Example.py @@ -1,30 +1,27 @@ -""" -Icepak: graphic card thermal analysis -------------------------------------- -This example shows how you can use PyAEDT to create a graphic card setup in Icepak and postprocess results. -The example file is an Icepak project with a model that is already created and has materials assigned. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Icepak: graphic card thermal analysis + +# This example shows how you can use PyAEDT to create a graphic card setup in Icepak and postprocess results. +# The example file is an Icepak project with a model that is already created and has materials assigned. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Download and open project -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download and open project +# # Download the project, open it, and save it to the temporary folder. +# + temp_folder = pyaedt.generate_unique_folder_name() project_temp_name = pyaedt.downloads.download_icepak(temp_folder) @@ -35,34 +32,31 @@ ) ipk.autosave_disable() +# - -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. ipk.plot(show=False, export_path=os.path.join(temp_folder, "Graphics_card.jpg"), plot_air_objects=False) -############################################################################### -# Create source blocks -# ~~~~~~~~~~~~~~~~~~~~ +# ## Create source blocks +# # Create source blocks on the CPU and memories. ipk.create_source_block("CPU", "25W") ipk.create_source_block(["MEMORY1", "MEMORY1_1"], "5W") -############################################################################### -# Assign openings and grille -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign openings and grille +# # Assign openings and a grille. region = ipk.modeler["Region"] ipk.assign_openings(air_faces=region.bottom_face_x.id) ipk.assign_grille(air_faces=region.top_face_x.id, free_area_ratio=0.8) -############################################################################### -# Assign mesh regions -# ~~~~~~~~~~~~~~~~~~~ +# ## Assign mesh regions +# # Assign a mesh region to the heat sink and CPU. mesh_region = ipk.mesh.assign_mesh_region(objectlist=["HEAT_SINK", "CPU"]) @@ -73,9 +67,8 @@ mesh_region.MaxLevels = "2" mesh_region.update() -############################################################################### -# Assign point monitor -# ~~~~~~~~~~~~~~~~~~~~ +# ## Assign point monitor +# # Assign a point monitor and set it up. ipk.assign_point_monitor(point_position=["-35mm", "3.6mm", "-86mm"], monitor_name="TemperatureMonitor1") @@ -87,11 +80,11 @@ setup1.props["Linear Solver Type - Temperature"] = "flex" ipk.save_project() -############################################################################### -# Solve project and postprocess -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Solve project and postprocess +# # Solve the project and plot temperatures. +# + quantity_name = "SurfTemperature" surflist = [i.id for i in ipk.modeler["CPU"].faces] surflist += [i.id for i in ipk.modeler["MEMORY1"].faces] @@ -100,12 +93,11 @@ plot5 = ipk.post.create_fieldplot_surface(surflist, "SurfTemperature") - ipk.analyze() +# - -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. ipk.release_desktop(True, True) diff --git a/examples/04-Icepak/Sherlock_Example.py b/examples/04-Icepak/Sherlock_Example.py index 3c7ac08b0b1..0e9f0a57d23 100644 --- a/examples/04-Icepak/Sherlock_Example.py +++ b/examples/04-Icepak/Sherlock_Example.py @@ -1,12 +1,10 @@ -""" -Icepak: setup from Sherlock inputs ------------------------------------ -This example shows how you can create an Icepak project from Sherlock -files (STEP and CSV) and an AEDB board. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Icepak: setup from Sherlock inputs + +# This example shows how you can create an Icepak project from Sherlock +# files (STEP and CSV) and an AEDB board. + +# ## Perform required imports +# # Perform required imports and set paths. import time @@ -15,20 +13,19 @@ import datetime # Set paths + project_folder = pyaedt.generate_unique_folder_name() input_dir = pyaedt.downloads.download_sherlock(destination=project_folder) -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` value either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Define variables -# ~~~~~~~~~~~~~~~~ +# ## Define variables +# # Define input variables. The following code creates all input variables that are needed # to run this example. @@ -40,11 +37,11 @@ stackup_thickness = 2.11836 outline_polygon_name = "poly_14188" -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. +# + d = pyaedt.launch_desktop(specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) start = time.time() @@ -53,120 +50,107 @@ validation = os.path.join(project_folder, "validation.log") file_path = os.path.join(input_dir, component_step) project_name = os.path.join(project_folder, component_step[:-3] + "aedt") +# - -############################################################################### -# Create Icepak project -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Create Icepak project +# # Create an Icepak project. ipk = pyaedt.Icepak(project_name) -############################################################################### -# Delete region to speed up import -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Delete region to speed up import +# # Delete the region and disable autosave to speed up the import. d.disable_autosave() ipk.modeler.delete("Region") component_name = "from_ODB" -############################################################################### -# Import PCB from AEDB file -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Import PCB from AEDB file +# # Import a PCB from an AEDB file. odb_path = os.path.join(input_dir, aedt_odb_project) ipk.create_pcb_from_3dlayout(component_name=component_name, project_name=odb_path, design_name=aedt_odb_design_name, extenttype="Polygon") -############################################################################### -# Create offset coordinate system -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create offset coordinate system +# # Create an offset coordinate system to match ODB++ with the # Sherlock STEP file. ipk.modeler.create_coordinate_system(origin=[0, 0, stackup_thickness / 2], mode="view", view="XY") -############################################################################### -# Import CAD file -# ~~~~~~~~~~~~~~~ +# ## Import CAD file +# # Import a CAD file. ipk.modeler.import_3d_cad(file_path, refresh_all_ids=False) -############################################################################### -# Save CAD file -# ~~~~~~~~~~~~~ +# ## Save CAD file +# # Save the CAD file and refresh the properties from the parsing of the AEDT file. ipk.save_project(refresh_obj_ids_after_save=True) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. ipk.plot(show=False, export_path=os.path.join(project_folder, "Sherlock_Example.jpg"), plot_air_objects=False) -############################################################################### -# Delete PCB objects -# ~~~~~~~~~~~~~~~~~~ +# ## Delete PCB objects +# # Delete the PCB objects. ipk.modeler.delete_objects_containing("pcb", False) -############################################################################### -# Create region -# ~~~~~~~~~~~~~ +# ## Create region +# # Create an air region. ipk.modeler.create_air_region(*[20, 20, 300, 20, 20, 300]) -############################################################################### -# Assign materials -# ~~~~~~~~~~~~~~~~ +# ## Assign materials +# # Assign materials from Sherlock file. ipk.assignmaterial_from_sherlock_files(component_list, material_list) -############################################################################### -# Delete objects with no material assignments -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Delete objects with no material assignments +# # Delete objects with no materials assignments. no_material_objs = ipk.modeler.get_objects_by_material("") ipk.modeler.delete(no_material_objs) ipk.save_project() -############################################################################### -# Assign power to component blocks -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign power to component blocks +# # Assign power to component blocks. all_objects = ipk.modeler.object_names -############################################################################### -# Assign power blocks -# ~~~~~~~~~~~~~~~~~~~ +# ## Assign power blocks +# # Assign power blocks from the Sherlock file. total_power = ipk.assign_block_from_sherlock_file(csv_name=component_list) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model again now that materials are assigned. ipk.plot(show=False, export_path=os.path.join(project_folder, "Sherlock_Example.jpg"), plot_air_objects=False) -############################################################################### -# Set up boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Set up boundaries +# # Set up boundaries. # Mesh settings that is tailored for PCB # Max iterations is set to 20 for quick demonstration, please increase to at least 100 for better accuracy. +# + ipk.globalMeshSettings(3, gap_min_elements='1', noOgrids=True, MLM_en=True, MLM_Type='2D', edge_min_elements='2', object='Region') @@ -177,56 +161,51 @@ setup1.props["Secondary Gradient"] = True setup1.props["Convergence Criteria - Max Iterations"] = 10 ipk.assign_openings(ipk.modeler.get_object_faces("Region")) +# - -############################################################################### -# Create point monitor -# ~~~~~~~~~~~~~~~~~~~~ +# ## Create point monitor +# point1 = ipk.assign_point_monitor(ipk.modeler["COMP_U10"].top_face_z.center, monitor_name="Point1") ipk.modeler.set_working_coordinate_system("Global") line = ipk.modeler.create_polyline([ipk.modeler["COMP_U10"].top_face_z.vertices[0].position, ipk.modeler["COMP_U10"].top_face_z.vertices[2].position], non_model=True) ipk.post.create_report(expressions="Point1.Temperature", primary_sweep_variable="X") -############################################################################### -# Check for intersections -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Check for intersections +# # Check for intersections using validation and fix them by # assigning priorities. ipk.assign_priority_on_intersections() -############################################################################### -# Compute power budget -# ~~~~~~~~~~~~~~~~~~~~ +# ## Compute power budget +# power_budget, total = ipk.post.power_budget("W" ) print(total) -############################################################################### -# Analyze the model -# ~~~~~~~~~~~~~~~~~ +# ## Analyze the model +# ipk.analyze(num_cores=4, num_tasks=4) ipk.save_project() -############################################################################### -# Get solution data and plots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get solution data and plots +# plot1 = ipk.post.create_fieldplot_surface(ipk.modeler["COMP_U10"].faces, "SurfTemperature") ipk.post.plot_field("SurfPressure",ipk.modeler["COMP_U10"].faces,export_path=ipk.working_directory, show=False) -############################################################################### -# Save project and release AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and release AEDT +# # Save the project and release AEDT. +# + ipk.save_project() end = time.time() - start print("Elapsed time: {}".format(datetime.timedelta(seconds=end))) print("Project Saved in {} ".format(ipk.project_file)) ipk.release_desktop() - - +# - From c0977783f7d07a1db408f1ea8429077d70b2c3bb Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 1 Feb 2024 17:14:16 +0100 Subject: [PATCH 32/56] MISC: use _static and md format for images --- examples/04-Icepak/Icepak_CSV_Import.py | 5 ++--- examples/04-Icepak/Icepak_ECAD_Import.py | 15 ++------------- examples/04-Icepak/_static/CSV_Import.png | Bin 0 -> 90398 bytes examples/04-Icepak/_static/bdf.png | Bin 0 -> 33707 bytes examples/04-Icepak/_static/ldf.png | Bin 0 -> 39061 bytes 5 files changed, 4 insertions(+), 16 deletions(-) create mode 100644 examples/04-Icepak/_static/CSV_Import.png create mode 100644 examples/04-Icepak/_static/bdf.png create mode 100644 examples/04-Icepak/_static/ldf.png diff --git a/examples/04-Icepak/Icepak_CSV_Import.py b/examples/04-Icepak/Icepak_CSV_Import.py index e87283928a4..35f52031af4 100644 --- a/examples/04-Icepak/Icepak_CSV_Import.py +++ b/examples/04-Icepak/Icepak_CSV_Import.py @@ -52,9 +52,8 @@ # # The following image does not show the entire rows and data and only serves as a sample. # -# .. image:: ../../_static/CSV_Import.png -# :width: 400 -# :alt: CSV Screenshot. +# +# # # # In this step the code will loop over the csv file lines and creates the blocks. diff --git a/examples/04-Icepak/Icepak_ECAD_Import.py b/examples/04-Icepak/Icepak_ECAD_Import.py index 0b15a6a1100..3962ecbab2f 100644 --- a/examples/04-Icepak/Icepak_ECAD_Import.py +++ b/examples/04-Icepak/Icepak_ECAD_Import.py @@ -14,11 +14,7 @@ # PyAEDT Packages import pyaedt -from pyaedt import Icepak -from pyaedt import Desktop from pyaedt import Hfss3dLayout -from pyaedt.modules.Boundary import BoundaryObject - # ## Set non-graphical mode # @@ -48,16 +44,9 @@ # # Sample *.bdf and *.ldf files are presented here. # +# # -# .. image:: ../../_static/bdf.png -# :width: 400 -# :alt: BDF image. -# -# -# .. image:: ../../_static/ldf.png -# :width: 400 -# :alt: LDF image. -# +# # # Imports the idf files with several filtering options including caps, resistors, inductors, power, size, ... # There are also options for the PCB creation (number o flayers, copper percentages, layer sizes). diff --git a/examples/04-Icepak/_static/CSV_Import.png b/examples/04-Icepak/_static/CSV_Import.png new file mode 100644 index 0000000000000000000000000000000000000000..b73cd592054f0f8658c797f56a2faf73944ad05b GIT binary patch literal 90398 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 MwQpa}*Rl-!KX=F1PXGV_ literal 0 HcmV?d00001 diff --git a/examples/04-Icepak/_static/ldf.png b/examples/04-Icepak/_static/ldf.png new file mode 100644 index 0000000000000000000000000000000000000000..43646374eade476cab60b7aa73aa96bee8e7d19e GIT binary patch literal 39061 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 literal 0 HcmV?d00001 From 23a4ff2e8dbf1515cc1702584d7cff31634213ae Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 1 Feb 2024 17:32:35 +0100 Subject: [PATCH 33/56] MISC: convert 05-Q3D into md --- examples/05-Q3D/Q2D_Armoured_Cable.py | 123 ++++++++------------- examples/05-Q3D/Q2D_Example_CPWG.py | 97 ++++++++--------- examples/05-Q3D/Q2D_Example_Stripline.py | 103 +++++++++--------- examples/05-Q3D/Q3D_DC_IR.py | 130 +++++++++++------------ examples/05-Q3D/Q3D_Example.py | 82 +++++++------- examples/05-Q3D/Q3D_from_EDB.py | 94 ++++++++-------- 6 files changed, 279 insertions(+), 350 deletions(-) diff --git a/examples/05-Q3D/Q2D_Armoured_Cable.py b/examples/05-Q3D/Q2D_Armoured_Cable.py index 27583bd977f..1222bdbaa6d 100644 --- a/examples/05-Q3D/Q2D_Armoured_Cable.py +++ b/examples/05-Q3D/Q2D_Armoured_Cable.py @@ -1,25 +1,21 @@ -""" -Q2D: Cable parameter identification ---------------------------------------------------- -This example shows how you can use PyAEDT to perform these tasks: +# # Q2D: Cable parameter identification - - Create a Q2D design using the Modeler primitives and importing part of the geometry. - - Set up the entire simulation. - - Link the solution to a Simplorer design. +# This example shows how you can use PyAEDT to perform these tasks: +# +# - Create a Q2D design using the Modeler primitives and importing part of the geometry. +# - Set up the entire simulation. +# - Link the solution to a Simplorer design. +# +# For cable information, see `4 Core Armoured Power Cable `_ - For cable information, see `4 Core Armoured Power Cable `_ - -""" -################################################################################# -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# import pyaedt import math -################################################################################# -# Initialize core strand dimensions and positions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize core strand dimensions and positions +# # Initialize cable sizing - radii in mm. c_strand_radius = 2.575 @@ -28,9 +24,8 @@ core_xlpe_ins_thickness = 0.5 core_xy_coord = math.ceil(3 * c_strand_radius + 2 * core_xlpe_ins_thickness) -################################################################################# -# Initialize filling and sheath dimensions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize filling and sheath dimensions +# # Initialize radii of further structures incrementally adding thicknesses. filling_radius = 1.4142 * (core_xy_coord + 3 * c_strand_radius + core_xlpe_ins_thickness + 0.5) @@ -39,18 +34,16 @@ armour_radius = inner_sheath_radius + armour_thickness outer_sheath_radius = armour_radius + 2 -################################################################################# -# Initialize armature strand dimensions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize armature strand dimensions +# # Initialize radii. armour_centre_pos = inner_sheath_radius + armour_thickness / 2.0 arm_strand_rad = armour_thickness / 2.0 - 0.2 n_arm_strands = 30 -################################################################################# -# Initialize dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize dictionaries +# # Initialize dictionaries that contain all the definitions for the design # variables and output variables. @@ -72,9 +65,8 @@ "n_arm_strands": str(n_arm_strands) } -################################################################################# -# Initialize Q2D -# ~~~~~~~~~~~~~~ +# ## Initialize Q2D +# # Initialize Q2D, providing the version, path to the project, and the design # name and type. @@ -86,9 +78,8 @@ tb_design_name = 'CableSystem' q2d = pyaedt.Q2d(projectname=project_name, designname=q2d_design_name, specified_version=desktop_version) -########################################################## -# Define variables from dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define variables from dictionaries +# # Define design variables from the created dictionaries. for k, v in core_params.items(): @@ -98,18 +89,16 @@ for k, v in armour_params.items(): q2d[k] = v -########################################################## -# Create object to access 2D modeler -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create object to access 2D modeler +# # Create the ``mod2D`` object to access the 2D modeler easily. mod2D = q2d.modeler mod2D.delete() mod2D.model_units = "mm" -################################################################################# -# Initialize required material properties -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize required material properties +# # Cable insulators require the definition of specific materials since they are not included in the Sys Library. # Plastic, PE (cross-linked, wire, and cable grade) @@ -118,15 +107,16 @@ mat_pe_cable_grade.permittivity = "2.09762" mat_pe_cable_grade.dielectric_loss_tangent = "0.000264575" mat_pe_cable_grade.update() + # Plastic, PP (10% carbon fiber) + mat_pp = q2d.materials.add_material("plastic_pp_carbon_fiber") mat_pp.conductivity = "0.0003161" mat_pp.update() -##################################################################################### -# Create geometry for core strands, filling, and XLPE insulation -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for core strands, filling, and XLPE insulation +# + mod2D.create_coordinate_system(['c_strand_xy_coord', 'c_strand_xy_coord', '0mm'], name='CS_c_strand_1') mod2D.set_working_coordinate_system('CS_c_strand_1') c1_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'c_strand_radius', name='c_strand_1', matname='copper') @@ -146,42 +136,33 @@ all_obj_names = q2d.get_all_conductors_names() + q2d.get_all_dielectrics_names() mod2D.duplicate_around_axis(all_obj_names, cs_axis="Z", angle=360 / cable_n_cores, nclones=4) cond_names = q2d.get_all_conductors_names() +# + -##################################################################################### -# Create geometry for filling object -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for filling object filling_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'filling_radius', name='Filling', matname='plastic_pp_carbon_fiber') filling_id.color = (255, 255, 180) -##################################################################################### -# Create geometry for inner sheath object -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for inner sheath object inner_sheath_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'inner_sheath_radius', name='InnerSheath', matname='PVC plastic') inner_sheath_id.color = (0, 0, 0) -##################################################################################### -# Create geometry for armature fill -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for armature fill arm_fill_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'armour_radius', name='ArmourFilling', matname='plastic_pp_carbon_fiber') arm_fill_id.color = (255, 255, 255) -##################################################################################### -# Create geometry for outer sheath -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for outer sheath outer_sheath_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'outer_sheath_radius', name='OuterSheath', matname='PVC plastic') outer_sheath_id.color = (0, 0, 0) -##################################################################################### -# Create geometry for armature steel strands -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create geometry for armature steel strands arm_strand_1_id = mod2D.create_circle(['0mm', 'armour_centre_pos', '0mm'], '1.1mm', name='arm_strand_1', matname='steel_stainless') @@ -189,16 +170,12 @@ arm_strand_1_id.duplicate_around_axis('Z', '360deg/n_arm_strands', nclones='n_arm_strands') arm_strand_names = mod2D.get_objects_w_string('arm_strand') -##################################################################################### -# Create region -# ~~~~~~~~~~~~~ +# ## Create region region = q2d.modeler.create_region([500, 500, 500, 500, 0, 0]) region.material_name = "vacuum" -########################################################## -# Assign conductors and reference ground -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign conductors and reference ground obj = [q2d.modeler.get_object_from_name(i) for i in cond_names] [q2d.assign_single_conductor(name='C1' + str(obj.index(i) + 1), target_objects=i, conductor_type='SignalLine') for i @@ -207,18 +184,14 @@ q2d.assign_single_conductor(name="gnd", target_objects=obj, conductor_type="ReferenceGround") mod2D.fit_all() -########################################################## -# Assign design settings -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign design settings lumped_length = "100m" q2d_des_settings = q2d.design_settings() q2d_des_settings['LumpedLength'] = lumped_length q2d.change_design_settings(q2d_des_settings) -########################################################## -# Insert setup and frequency sweep -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Insert setup and frequency sweep q2d_setup = q2d.create_setup(setupname=setup_name) q2d_sweep = q2d_setup.add_sweep(sweepname=sweep_name) @@ -229,21 +202,15 @@ q2d_sweep.props["RangeSamples"] = 1 q2d_sweep.update() -########################################################## -# Analyze setup -# ~~~~~~~~~~~~~ +# ## Analyze setup # q2d.analyze(setup_name=setup_name) -################################################################### -# Add a Simplorer/Twin Builder design and the Q3D dynamic component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Add a Simplorer/Twin Builder design and the Q3D dynamic component tb = pyaedt.TwinBuilder(designname=tb_design_name) -########################################################## -# Add a Q3D dynamic component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Add a Q3D dynamic component tb.add_q3d_dynamic_component(project_name, q2d_design_name, @@ -252,9 +219,7 @@ model_depth=lumped_length, coupling_matrix_name="Original") -########################################################## -# Save project and release desktop -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and release desktop tb.save_project() tb.release_desktop(True, True) diff --git a/examples/05-Q3D/Q2D_Example_CPWG.py b/examples/05-Q3D/Q2D_Example_CPWG.py index c7f12e637f8..d5a6d5eb4c1 100644 --- a/examples/05-Q3D/Q2D_Example_CPWG.py +++ b/examples/05-Q3D/Q2D_Example_CPWG.py @@ -1,28 +1,25 @@ -""" -2D Extractor: CPWG analysis ---------------------------- -This example shows how you can use PyAEDT to create a CPWG (coplanar waveguide with ground) design -in 2D Extractor and run a simulation. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # 2D Extractor: CPWG analysis + +# This example shows how you can use PyAEDT to create a CPWG (coplanar waveguide with ground) design +# in 2D Extractor and run a simulation. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False desktop_version = "2023.2" -############################################################################### -# Launch AEDT and 2D Extractor -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Launch AEDT and 2D Extractor +# # Launch AEDT 2023 R2 in graphical mode and launch 2D Extractor. This example # uses SI units. @@ -32,11 +29,11 @@ projectname=pyaedt.generate_unique_name("pyaedt_q2d_example"), designname="coplanar_waveguide") -############################################################################### -# Define variables -# ~~~~~~~~~~~~~~~~ +# ## Define variables +# # Define variables. +# + e_factor = "e_factor" sig_bot_w = "sig_bot_w" co_gnd_w = "gnd_w" @@ -60,10 +57,10 @@ sig_top_w = "({1}-{0}*2)".format(delta_w_half, sig_bot_w) co_gnd_top_w = "({1}-{0}*2)".format(delta_w_half, co_gnd_w) model_w = "{}*2+{}*2+{}".format(co_gnd_w, clearance, sig_bot_w) +# - -############################################################################### -# Create primitives -# ~~~~~~~~~~~~~~~~~ +# ## Create primitives +# # Create primitives and define the layer heights. layer_1_lh = 0 @@ -71,9 +68,8 @@ layer_2_lh = layer_1_uh + "+" + d_h layer_2_uh = layer_2_lh + "+" + cond_h -############################################################################### -# Create signal -# ~~~~~~~~~~~~~ +# ## Create signal +# # Create a signal. base_line_obj = q.modeler.create_polyline(position_list=[[0, layer_2_lh, 0], [sig_bot_w, layer_2_lh, 0]], name="signal") @@ -82,11 +78,11 @@ q.modeler.connect([base_line_obj, top_line_obj]) q.modeler.move(objid=[base_line_obj], vector=["{}+{}".format(co_gnd_w, clearance), 0, 0]) -############################################################################### -# Create coplanar ground -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coplanar ground +# # Create a coplanar ground. +# + base_line_obj = q.modeler.create_polyline(position_list=[[0, layer_2_lh, 0], [co_gnd_w, layer_2_lh, 0]], name="co_gnd_left") top_line_obj = q.modeler.create_polyline(position_list=[[0, layer_2_uh, 0], [co_gnd_top_w, layer_2_uh, 0]]) @@ -99,28 +95,27 @@ q.modeler.move(objid=[top_line_obj], vector=[delta_w_half, 0, 0]) q.modeler.connect([base_line_obj, top_line_obj]) q.modeler.move(objid=[base_line_obj], vector=["{}+{}*2+{}".format(co_gnd_w, clearance, sig_bot_w), 0, 0]) +# - -############################################################################### -# Create reference ground plane -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create reference ground plane +# # Create a reference ground plane. q.modeler.create_rectangle(position=[0, layer_1_lh, 0], dimension_list=[model_w, cond_h], name="ref_gnd") -############################################################################### -# Create dielectric -# ~~~~~~~~~~~~~~~~~ +# ## Create dielectric +# # Create a dielectric. q.modeler.create_rectangle( position=[0, layer_1_uh, 0], dimension_list=[model_w, d_h], name="Dielectric", matname="FR4_epoxy" ) -############################################################################### -# Create conformal coating -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create conformal coating +# # Create a conformal coating. +# + sm_obj_list = [] ids = [1,2,3] if desktop_version >= "2023.1": @@ -149,10 +144,10 @@ sm_obj.material_name = "SolderMask" sm_obj.color = (0, 150, 100) sm_obj.name = "solder_mask" +# - -############################################################################### -# Assign conductor -# ~~~~~~~~~~~~~~~~ +# ## Assign conductor +# # Assign a conductor to the signal. obj = q.modeler.get_object_from_name("signal") @@ -160,9 +155,8 @@ name=obj.name, target_objects=[obj], conductor_type="SignalLine", solve_option="SolveOnBoundary", unit="mm" ) -############################################################################### -# Create reference ground -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create reference ground +# # Create a reference ground. obj = [q.modeler.get_object_from_name(i) for i in ["co_gnd_left", "co_gnd_right", "ref_gnd"]] @@ -170,19 +164,18 @@ name="gnd", target_objects=obj, conductor_type="ReferenceGround", solve_option="SolveOnBoundary", unit="mm" ) -############################################################################### -# Assign Huray model on signal -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign Huray model on signal +# # Assign the Huray model on the signal. obj = q.modeler.get_object_from_name("signal") q.assign_huray_finitecond_to_edges(obj.edges, radius="0.5um", ratio=3, name="b_" + obj.name) -############################################################################### -# Create setup, analyze, and plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup, analyze, and plot +# # Create the setup, analyze it, and plot solution data. - + +# + setup = q.create_setup(setupname="new_setup") sweep = setup.add_sweep(sweepname="sweep1", sweeptype="Discrete") @@ -200,10 +193,10 @@ a = q.post.get_solution_data(expressions="Z0(signal,signal)", context="Original") a.plot() +# - -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. home = os.path.expanduser("~") diff --git a/examples/05-Q3D/Q2D_Example_Stripline.py b/examples/05-Q3D/Q2D_Example_Stripline.py index a36df6f8956..19f289b7600 100644 --- a/examples/05-Q3D/Q2D_Example_Stripline.py +++ b/examples/05-Q3D/Q2D_Example_Stripline.py @@ -1,29 +1,25 @@ -""" -2D Extractor: stripline analysis --------------------------------- -This example shows how you can use PyAEDT to create a differential stripline design in -2D Extractor and run a simulation. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # 2D Extractor: stripline analysis + +# This example shows how you can use PyAEDT to create a differential stripline design in +# 2D Extractor and run a simulation. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False project_path = pyaedt.generate_unique_project_name() -############################################################################### -# Launch AEDT and 2D Extractor -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and 2D Extractor +# # Launch AEDT 2023 R2 in graphical mode and launch 2D Extractor. This example # uses SI units. @@ -34,11 +30,11 @@ new_desktop_session=True ) -############################################################################### -# Define variables -# ~~~~~~~~~~~~~~~~ +# ## Define variables +# # Define variables. +# + e_factor = "e_factor" sig_w = "sig_bot_w" sig_gap = "sig_gap" @@ -65,10 +61,10 @@ sig_top_w = "({1}-{0}*2)".format(delta_w_half, sig_w) co_gnd_top_w = "({1}-{0}*2)".format(delta_w_half, co_gnd_w) model_w = "{}*2+{}*2+{}*2+{}".format(co_gnd_w, clearance, sig_w, sig_gap) +# - -############################################################################### -# Create primitives -# ~~~~~~~~~~~~~~~~~ +# ## Create primitives +# # Create primitives and define the layer heights. layer_1_lh = 0 @@ -78,9 +74,8 @@ layer_3_lh = layer_2_uh + "+" + pp_h layer_3_uh = layer_3_lh + "+" + cond_h -############################################################################### -# Create positive signal -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create positive signal +# # Create a positive signal. base_line_obj = q.modeler.create_polyline([[0, layer_2_lh, 0], [sig_w, layer_2_lh, 0]], name="signal_p") @@ -89,8 +84,8 @@ q.modeler.connect([base_line_obj, top_line_obj]) q.modeler.move([base_line_obj], ["{}+{}".format(co_gnd_w, clearance), 0, 0]) -# Create negative signal -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create negative signal +# # Create a negative signal. base_line_obj = q.modeler.create_polyline(position_list=[[0, layer_2_lh, 0], [sig_w, layer_2_lh, 0]], name="signal_n") @@ -99,11 +94,11 @@ q.modeler.connect([base_line_obj, top_line_obj]) q.modeler.move(objid=[base_line_obj], vector=["{}+{}+{}+{}".format(co_gnd_w, clearance, sig_w, sig_gap), 0, 0]) -############################################################################### -# Create coplanar ground -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coplanar ground +# # Create a coplanar ground. +# + base_line_obj = q.modeler.create_polyline(position_list=[[0, layer_2_lh, 0], [co_gnd_w, layer_2_lh, 0]], name="co_gnd_left") top_line_obj = q.modeler.create_polyline(position_list=[[0, layer_2_uh, 0], [co_gnd_top_w, layer_2_uh, 0]]) @@ -117,18 +112,17 @@ q.modeler.connect([base_line_obj, top_line_obj]) q.modeler.move(objid=[base_line_obj], vector=["{}+{}*2+{}*2+{}".format(co_gnd_w, clearance, sig_w, sig_gap), 0, 0]) +# - -############################################################################### -# Create reference ground plane -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create reference ground plane +# # Create a reference ground plane. q.modeler.create_rectangle(position=[0, layer_1_lh, 0], dimension_list=[model_w, cond_h], name="ref_gnd_u") q.modeler.create_rectangle(position=[0, layer_3_lh, 0], dimension_list=[model_w, cond_h], name="ref_gnd_l") -############################################################################### -# Create dielectric -# ~~~~~~~~~~~~~~~~~ +# ## Create dielectric +# # Create a dielectric. q.modeler.create_rectangle( @@ -141,11 +135,11 @@ position=[0, layer_2_lh, 0], dimension_list=[model_w, cond_h], name="Filling", matname="FR4_epoxy" ) -############################################################################### -# Assign conductors -# ~~~~~~~~~~~~~~~~~ +# ## Assign conductors +# # Assign conductors to the signal. +# + obj = q.modeler.get_object_from_name("signal_p") q.assign_single_conductor( name=obj.name, target_objects=[obj], conductor_type="SignalLine", solve_option="SolveOnBoundary", unit="mm" @@ -155,10 +149,10 @@ q.assign_single_conductor( name=obj.name, target_objects=[obj], conductor_type="SignalLine", solve_option="SolveOnBoundary", unit="mm" ) +# - -############################################################################### -# Create reference ground -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create reference ground +# # Create a reference ground. obj = [q.modeler.get_object_from_name(i) for i in ["co_gnd_left", "co_gnd_right", "ref_gnd_u", "ref_gnd_l"]] @@ -166,34 +160,35 @@ name="gnd", target_objects=obj, conductor_type="ReferenceGround", solve_option="SolveOnBoundary", unit="mm" ) -############################################################################### -# Assign Huray model on signals -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign Huray model on signals +# # Assign the Huray model on the signals. +# + obj = q.modeler.get_object_from_name("signal_p") q.assign_huray_finitecond_to_edges(obj.edges, radius="0.5um", ratio=3, name="b_" + obj.name) obj = q.modeler.get_object_from_name("signal_n") q.assign_huray_finitecond_to_edges(obj.edges, radius="0.5um", ratio=3, name="b_" + obj.name) +# - -############################################################################### -# Define differential pair -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define differential pair +# # Define the differential pair. matrix = q.insert_reduced_matrix(operation_name=q.MATRIXOPERATIONS.DiffPair, source_names=["signal_p", "signal_n"], rm_name="diff_pair") -############################################################################### -# Create setup, analyze, and plot -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup, analyze, and plot +# # Create a setup, analyze, and plot solution data. # Create a setup. + setup = q.create_setup(setupname="new_setup") # Add a sweep. + sweep = setup.add_sweep(sweepname="sweep1", sweeptype="Discrete") sweep.props["RangeType"] = "LinearStep" sweep.props["RangeStart"] = "1GHz" @@ -205,21 +200,23 @@ sweep.update() # Analyze the nominal design and plot characteristic impedance. + q.analyze() plot_sources = matrix.get_sources_for_plot(category="Z0") a = q.post.get_solution_data(expressions=plot_sources, context=matrix.name) a.plot(snapshot_path=os.path.join(q.working_directory, "plot.jpg")) # Save plot as jpg # Add a parametric sweep and analyze. + parametric = q.parametrics.add(sweep_var="sig_bot_w", start_point=75, end_point=100, step=5, variation_type="LinearStep") parametric.add_variation(sweep_var="sig_gap", start_point="100um", end_point="200um", step=5, variation_type="LinearCount") q.analyze_setup(name=parametric.name) -############################################################################### -# Save project and release AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and release AEDT +# # Save the project and release AEDT. + q.save_project() q.release_desktop() diff --git a/examples/05-Q3D/Q3D_DC_IR.py b/examples/05-Q3D/Q3D_DC_IR.py index 976279b53c4..6af3dddbd3d 100644 --- a/examples/05-Q3D/Q3D_DC_IR.py +++ b/examples/05-Q3D/Q3D_DC_IR.py @@ -1,22 +1,19 @@ -""" -Q3D Extractor: PCB DCIR analysis --------------------------------- -This example shows how you can use PyAEDT to create a design in -Q3D Extractor and run a DC IR Drop simulation starting from an EDB Project. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Q3D Extractor: PCB DCIR analysis +# +# This example shows how you can use PyAEDT to create a design in +# Q3D Extractor and run a DC IR Drop simulation starting from an EDB Project. + +# ## Perform required imports +# # Perform required imports. + import os import pyaedt - -############################################################################### -# Set up project files and path -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set up project files and path +# # Download needed project file and set up temporary project directory. + project_dir = pyaedt.generate_unique_folder_name() aedb_project = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=project_dir) coil = pyaedt.downloads.download_file('inductance_3d_component', 'air_coil.a3dcomp') @@ -25,12 +22,11 @@ output_edb = os.path.join(project_dir, project_name + '.aedb') output_q3d = os.path.join(project_dir, project_name + '_q3d.aedt') - -############################################################################### -# Open EDB -# ~~~~~~~~ +# ## Open EDB +# # Open the EDB project and create a cutout on the selected nets # before exporting to Q3D. + edb = pyaedt.Edb(aedb_project, edbversion="2023.2") edb.cutout(["1.2V_AVDLL_PLL", "1.2V_AVDDL", "1.2V_DVDDL", "NetR106_1"], ["GND"], @@ -38,10 +34,8 @@ use_pyaedt_extent_computing=True, ) - -############################################################################### -# Identify pin positions -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Identify pin positions +# # Identify [x,y] pin locations on the components to define where to assign sources # and sinks for Q3D. @@ -50,11 +44,11 @@ pin_u9_2 = [i for i in edb.components["U9"].pins.values() if i.net_name == "1.2V_DVDDL"] pin_u11_r106 = [i for i in edb.components["U11"].pins.values() if i.net_name == "NetR106_1"] -############################################################################### -# Append Z Positions -# ~~~~~~~~~~~~~~~~~~ +# ## Append Z Positions +# # Compute Q3D 3D position. The factor 1000 converts from "meters" to "mm". +# + location_u11_scl = [i * 1000 for i in pin_u11_scl[0].position] location_u11_scl.append(edb.components["U11"].upper_elevation * 1000) @@ -66,12 +60,13 @@ location_u11_r106 = [i * 1000 for i in pin_u11_r106[0].position] location_u11_r106.append(edb.components["U11"].upper_elevation * 1000) +# - -############################################################################### -# Identify pin positions for 3D components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Identify pin positions for 3D components +# # Identify the pin positions where 3D components of passives are to be added. +# + location_l2_1 = [i * 1000 for i in edb.components["L2"].pins["1"].position] location_l2_1.append(edb.components["L2"].upper_elevation * 1000) location_l4_1 = [i * 1000 for i in edb.components["L4"].pins["1"].position] @@ -79,43 +74,41 @@ location_r106_1 = [i * 1000 for i in edb.components["R106"].pins["1"].position] location_r106_1.append(edb.components["R106"].upper_elevation * 1000) +# - -############################################################################### -# Save and close EDB -# ~~~~~~~~~~~~~~~~~~ +# ## Save and close EDB +# # Save and close EDB. Then, open EDT in HFSS 3D Layout to generate the 3D model. +# + edb.save_edb() edb.close_edb() h3d = pyaedt.Hfss3dLayout(output_edb, specified_version="2023.2", non_graphical=False, new_desktop_session=True) +# - -############################################################################### -# Export to Q3D -# ~~~~~~~~~~~~~ +# ## Export to Q3D +# # Create a dummy setup and export the layout in Q3D. # The ``keep_net_name`` parameter reassigns Q3D net names from HFSS 3D Layout. + setup = h3d.create_setup() setup.export_to_q3d(output_q3d, keep_net_name=True) h3d.close_project() - - -############################################################################### -# Open Q3D -# ~~~~~~~~ +# ## Open Q3D +# # Launch the newly created q3d project. q3d = pyaedt.Q3d(output_q3d) q3d.modeler.delete("GND") q3d.delete_all_nets() - -############################################################################### -# Insert inductors -# ~~~~~~~~~~~~~~~~ +# ## Insert inductors +# # Create new coordinate systems and place 3D component inductors. +# + q3d.modeler.create_coordinate_system(location_l2_1, name="L2") comp = q3d.modeler.insert_3d_component(coil, targetCS="L2") comp.rotate(q3d.AXIS.Z, -90) @@ -135,12 +128,13 @@ comp3.rotate(q3d.AXIS.Z, -90) q3d.modeler.set_working_coordinate_system("Global") +# - -############################################################################### -# Delete dielectrics -# ~~~~~~~~~~~~~~~~~~ +# ## Delete dielectrics +# # Delete all dielectric objects since not needed in DC analysis. +# + q3d.modeler.delete(q3d.modeler.get_objects_by_material("Megtron4")) q3d.modeler.delete(q3d.modeler.get_objects_by_material("Megtron4_2")) q3d.modeler.delete(q3d.modeler.get_objects_by_material("Megtron4_3")) @@ -150,13 +144,14 @@ objs_copper_names = [i.name for i in objs_copper] q3d.plot(show=False,objects=objs_copper_names, plot_as_separate_objects=False, export_path=os.path.join(q3d.working_directory, "Q3D.jpg"), plot_air_objects=False) +# - -############################################################################### -# Assign source and sink -# ~~~~~~~~~~~~~~~~~~~~~~ -# Use previously calculated positions to identify faces, -# select the net "1_Top" and +# ## Assign source and sink +# +# Use previously calculated positions to identify faces, select the net "1_Top" and # assign sources and sinks on nets. + +# + sink_f = q3d.modeler.create_circle(q3d.PLANE.XY, location_u11_scl, 0.1) source_f1 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u9_1_scl, 0.1) source_f2 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u9_2_scl, 0.1) @@ -177,10 +172,10 @@ q3d.edit_sources(dcrl={"{}:{}".format(source1.props["Net"], source1.name): "-1.0A", "{}:{}".format(source2.props["Net"], source2.name): "-1.0A", "{}:{}".format(source2.props["Net"], source3.name): "-1.0A"}) +# - -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup and a frequency sweep from DC to 2GHz. # Analyze project. @@ -192,9 +187,8 @@ setup.props["DC"]["Cond"]["MaxPass"]=3 setup.analyze() -############################################################################### -# Field Calculator -# ~~~~~~~~~~~~~~~~ +# ## Field Calculator +# # We will create a named expression using field calculator. drop_name = "Vdrop3_3" @@ -205,11 +199,11 @@ q3d.ofieldsreporter.CalcOp("+") q3d.ofieldsreporter.AddNamedExpression(drop_name, "DC R/L Fields") -############################################################################### -# Phi plot -# ~~~~~~~~ +# ## Phi plot +# # Compute ACL solutions and plot them. +# + plot1 = q3d.post.create_fieldplot_surface(q3d.modeler.get_objects_by_material("copper"), quantityName=drop_name, intrinsincDict={"Freq": "1GHz"}) @@ -223,14 +217,15 @@ plot_cad_objs=False, log_scale=False, ) +# - -############################################################################### -# Computing Voltage on Source Circles -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Computing Voltage on Source Circles +# # Using Field Calculator we can compute the voltage on source circles and get the value # using get_solution_data method. +# + curves = [] for source_circle, source_bound in zip(sources_objs, sources_bounds): source_sheet_name = source_circle.name @@ -243,7 +238,6 @@ q3d.ofieldsreporter.CalcOp("Maximum") q3d.ofieldsreporter.AddNamedExpression("V{}".format(source_bound.name), "DC R/L Fields") - data = q3d.post.get_solution_data( curves, q3d.nominal_adaptive, @@ -252,12 +246,12 @@ ) for curve in curves: print(data.data_real(curve)) +# - - -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # ``release_desktop`` method. All methods provide for saving projects before closing. + q3d.save_project() q3d.release_desktop() diff --git a/examples/05-Q3D/Q3D_Example.py b/examples/05-Q3D/Q3D_Example.py index 2bf882c57f0..cee6f9398ab 100644 --- a/examples/05-Q3D/Q3D_Example.py +++ b/examples/05-Q3D/Q3D_Example.py @@ -1,28 +1,24 @@ -""" -Q3D Extractor: busbar analysis ------------------------------- -This example shows how you can use PyAEDT to create a busbar design in -Q3D Extractor and run a simulation. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Q3D Extractor: busbar analysis + +# This example shows how you can use PyAEDT to create a busbar design in +# Q3D Extractor and run a simulation. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Set debugger mode -# ~~~~~~~~~~~~~~~~~ +# ## Set debugger mode +# # PyAEDT allows to enable a debug logger which logs all methods called and argument passed. # This example shows how to enable it. @@ -30,10 +26,8 @@ pyaedt.settings.enable_debug_methods_argument_logger = True pyaedt.settings.enable_debug_internal_methods_logger = False - -############################################################################### -# Launch AEDT and Q3D Extractor -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and Q3D Extractor +# # Launch AEDT 2023 R2 in graphical mode and launch Q3D Extractor. This example uses SI units. q = pyaedt.Q3d(projectname=pyaedt.generate_unique_project_name(), @@ -41,11 +35,11 @@ non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Create primitives -# ~~~~~~~~~~~~~~~~~ +# ## Create primitives +# # Create polylines for three busbars and a box for the substrate. +# + b1 = q.modeler.create_polyline( [[0, 0, 0], [-100, 0, 0]], name="Bar1", @@ -81,13 +75,14 @@ q.modeler["substrate"].transparency = 0.8 q.plot(show=False, export_path=os.path.join(q.working_directory, "Q3D.jpg"), plot_air_objects=False) +# - -############################################################################### -# Set up boundaries -# ~~~~~~~~~~~~~~~~~ +# ## Set up boundaries +# # Identify nets and assign sources and sinks to all nets. # There is a source and sink for each busbar. +# + q.auto_identify_nets() q.source("Bar1", axisdir=q.AxisDir.XPos, name="Source1") @@ -98,10 +93,10 @@ q.source("Bar3", axisdir=q.AxisDir.XPos, name="Source3") bar3_sink = q.sink("Bar3", axisdir=q.AxisDir.YPos) bar3_sink.name = "Sink3" +# - -############################################################################### -# Print information -# ~~~~~~~~~~~~~~~~~ +# ## Print information +# # Use the different methods available to print net and terminal information. print(q.nets) @@ -112,9 +107,8 @@ print(q.net_sources("Bar2")) print(q.net_sources("Bar3")) -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup for Q3D Extractor and add a sweep that defines the adaptive # frequency value. @@ -125,9 +119,8 @@ sw1.props["RangeStep"] = "5MHz" sw1.update() -############################################################################### -# Get curves to plot -# ~~~~~~~~~~~~~~~~~~ +# ## Get curves to plot +# # Get the curves to plot. The following code simplifies the way to get curves. data_plot_self = q.matrices[0].get_sources_for_plot(get_self_terms=True, get_mutual_terms=False) @@ -135,25 +128,22 @@ data_plot_self data_plot_mutual -############################################################################### -# Create rectangular plot -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create rectangular plot +# # Create a rectangular plot and a data table. q.post.create_report(expressions=data_plot_self) q.post.create_report(expressions=data_plot_mutual, context="Original", plot_type="Data Table") -############################################################################### -# Solve setup -# ~~~~~~~~~~~ +# ## Solve setup +# # Solve the setup. q.analyze() q.save_project() -############################################################################### -# Get report data -# ~~~~~~~~~~~~~~~ +# ## Get report data +# # Get the report data into a data structure that allows you to manipulate it. a = q.post.get_solution_data(expressions=data_plot_self, context="Original") @@ -161,11 +151,11 @@ a.data_magnitude() a.plot() -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # ``release_desktop`` method. All methods provide for saving projects before closing. + pyaedt.settings.enable_debug_logger = False pyaedt.settings.enable_debug_methods_argument_logger = False q.release_desktop(close_projects=True, close_desktop=True) diff --git a/examples/05-Q3D/Q3D_from_EDB.py b/examples/05-Q3D/Q3D_from_EDB.py index a104684b832..fe4c8d8b3a8 100644 --- a/examples/05-Q3D/Q3D_from_EDB.py +++ b/examples/05-Q3D/Q3D_from_EDB.py @@ -1,43 +1,40 @@ -""" -Q3D Extractor: PCB analysis ---------------------------- -This example shows how you can use PyAEDT to create a design in -Q3D Extractor and run a simulation starting from an EDB Project. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Q3D Extractor: PCB analysis + +# This example shows how you can use PyAEDT to create a design in +# Q3D Extractor and run a simulation starting from an EDB Project. + +# ## Perform required imports +# # Perform required imports. + import os import pyaedt - -############################################################################### -# Setup project files and path -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Setup project files and path +# # Download of needed project file and setup of temporary project directory. + +# + project_dir = pyaedt.generate_unique_folder_name() aedb_project = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb',destination=project_dir) project_name = pyaedt.generate_unique_name("HSD") output_edb = os.path.join(project_dir, project_name + '.aedb') output_q3d = os.path.join(project_dir, project_name + '_q3d.aedt') +# - - -############################################################################### -# Open EDB -# ~~~~~~~~ +# ## Open EDB +# # Open the edb project and created a cutout on the selected nets # before exporting to Q3D. + edb = pyaedt.Edb(aedb_project, edbversion="2023.2") edb.cutout(["CLOCK_I2C_SCL", "CLOCK_I2C_SDA"], ["GND"], output_aedb_path=output_edb, use_pyaedt_extent_computing=True, ) -############################################################################### -# Identify pins position -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Identify pins position +# # Identify [x,y] pin locations on the components to define where to assign sources # and sinks for Q3D and append Z elevation. @@ -47,11 +44,11 @@ pin_u1_sda = [i for i in edb.components["U1"].pins.values() if i.net_name == "CLOCK_I2C_SDA"] -############################################################################### -# Append Z Positions -# ~~~~~~~~~~~~~~~~~~ +# ## Append Z Positions +# # Note: The factor 100 converts from "meters" to "mm" +# + location_u13_scl = [i * 1000 for i in pin_u13_scl[0].position] location_u13_scl.append(edb.components["U13"].upper_elevation * 1000) @@ -63,45 +60,41 @@ location_u1_sda = [i * 1000 for i in pin_u1_sda[0].position] location_u1_sda.append(edb.components["U1"].upper_elevation * 1000) +# - -############################################################################### -# Save and close Edb -# ~~~~~~~~~~~~~~~~~~ +# ## Save and close Edb +# # Save, close Edb and open it in Hfss 3D Layout to generate the 3D model. +# + edb.save_edb() edb.close_edb() h3d = pyaedt.Hfss3dLayout(output_edb, specified_version="2023.2", non_graphical=True, new_desktop_session=True) +# - -############################################################################### -# Export to Q3D -# ~~~~~~~~~~~~~ +# ## Export to Q3D +# # Create a dummy setup and export the layout in Q3D. # keep_net_name will reassign Q3D nets names from Hfss 3D Layout. + setup = h3d.create_setup() setup.export_to_q3d(output_q3d, keep_net_name=True) h3d.close_project() - - -############################################################################### -# Open Q3D -# ~~~~~~~~ +# ## Open Q3D +# # Launch the newly created q3d project and plot it. q3d = pyaedt.Q3d(output_q3d) q3d.plot(show=False, objects=["CLOCK_I2C_SCL", "CLOCK_I2C_SDA"], export_path=os.path.join(q3d.working_directory, "Q3D.jpg"), plot_air_objects=False) -############################################################################### -# Assign Source and Sink -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Assign Source and Sink +# # Use previously calculated position to identify faces and # assign sources and sinks on nets. - - f1 = q3d.modeler.get_faceid_from_position(location_u13_scl, obj_name="CLOCK_I2C_SCL") q3d.source(f1, net_name="CLOCK_I2C_SCL") f1 = q3d.modeler.get_faceid_from_position(location_u13_sda, obj_name="CLOCK_I2C_SDA") @@ -111,9 +104,8 @@ f1 = q3d.modeler.get_faceid_from_position(location_u1_sda, obj_name="CLOCK_I2C_SDA") q3d.sink(f1, net_name="CLOCK_I2C_SDA") -############################################################################### -# Create Setup -# ~~~~~~~~~~~~ +# ## Create Setup +# # Create a setup and a frequency sweep from DC to 2GHz. # Analyze project. @@ -124,27 +116,25 @@ sweep.add_subrange("LinearStep", 0, end=2, count=0.05, unit="GHz", save_single_fields=False, clear=True) setup.analyze() -############################################################################### -# ACL Report -# ~~~~~~~~~~ +# ## ACL Report +# # Compute ACL solutions and plot them. traces_acl = q3d.post.available_report_quantities(quantities_category="ACL Matrix") solution = q3d.post.get_solution_data(traces_acl) solution.plot() -############################################################################### -# ACR Report -# ~~~~~~~~~~ +# ## ACR Report +# # Compute ACR solutions and plot them. traces_acr = q3d.post.available_report_quantities(quantities_category="ACR Matrix") solution2 = q3d.post.get_solution_data(traces_acr) solution2.plot() -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # ``release_desktop`` method. All methods provide for saving projects before closing. + q3d.release_desktop() From 540e18fc22b69162a8764fdfc143cda84397c32a Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Sat, 3 Feb 2024 18:06:27 +0100 Subject: [PATCH 34/56] MISC: convert 06-Multiphysics --- .../06-Multiphysics/Hfss_Icepak_Coupling.py | 164 ++++++++---------- examples/06-Multiphysics/Hfss_Mechanical.py | 84 ++++----- examples/06-Multiphysics/MRI.py | 58 +++---- 3 files changed, 134 insertions(+), 172 deletions(-) diff --git a/examples/06-Multiphysics/Hfss_Icepak_Coupling.py b/examples/06-Multiphysics/Hfss_Icepak_Coupling.py index e193d7b2509..9ef5cc2df31 100644 --- a/examples/06-Multiphysics/Hfss_Icepak_Coupling.py +++ b/examples/06-Multiphysics/Hfss_Icepak_Coupling.py @@ -1,44 +1,38 @@ -""" -Multiphysics: HFSS-Icepak multiphysics analysis ------------------------------------------------- -This example shows how you can create a project from scratch in HFSS and Icepak (linked to HFSS). -This includes creating a setup, solving it, and creating postprocessing outputs. - -To provide the advanced postprocessing features needed for this example, the ``numpy``, -``matplotlib``, and ``pyvista`` packages must be installed on the machine. - -This examples runs only on Windows using CPython. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Multiphysics: HFSS-Icepak multiphysics analysis +# +# This example shows how you can create a project from scratch in HFSS and Icepak (linked to HFSS). +# This includes creating a setup, solving it, and creating postprocessing outputs. +# +# To provide the advanced postprocessing features needed for this example, the ``numpy``, +# ``matplotlib``, and ``pyvista`` packages must be installed on the machine. +# +# This examples runs only on Windows using CPython. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt from pyaedt.generic.pdf import AnsysReport -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False desktopVersion = "2023.2" -############################################################################### -# Open project -# ~~~~~~~~~~~~ +# ## Open project +# # Open the project. NewThread = True - project_file = pyaedt.generate_unique_project_name() -############################################################################### -# Launch AEDT and initialize HFSS -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and initialize HFSS +# # Launch AEDT and initialize HFSS. If there is an active HFSS design, the ``aedtapp`` # object is linked to it. Otherwise, a new design is created. @@ -48,9 +42,8 @@ new_desktop_session=NewThread ) -############################################################################### -# Initialize variable settings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Initialize variable settings +# # Initialize variable settings. You can initialize a variable simply by creating # it as a list object. If you enter the prefix ``$``, the variable is created for # the project. Otherwise, the variable is created for the design. @@ -59,9 +52,8 @@ udp = aedtapp.modeler.Position(0, 0, 0) aedtapp["inner"] = "3mm" -############################################################################### -# Create coaxial and cylinders -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create coaxial and cylinders +# # Create a coaxial and three cylinders. You can apply parameters # directly using the :func:`pyaedt.modeler.Primitives3D.Primitives3D.create_cylinder` # method. You can assign a material directly to the object creation action. @@ -75,9 +67,8 @@ o3 = aedtapp.modeler.create_cylinder(cs_axis=aedtapp.PLANE.ZX, position=udp, radius=10, height="$coax_dimension", numSides=0, name="outer") -############################################################################### -# Assign colors -# ~~~~~~~~~~~~~ +# ## Assign colors +# # Assign colors to each primitive. o1.color = (255, 0, 0) @@ -86,27 +77,24 @@ o3.transparency = 0.8 aedtapp.modeler.fit_all() -############################################################################### -# Assign materials -# ~~~~~~~~~~~~~~~~ +# ## Assign materials +# # Assign materials. You can assign materials either directly when creating the primitive, # which was done for ``id2``, or after the object is created. o1.material_name = "Copper" o3.material_name = "Copper" -############################################################################### -# Perform modeler operations -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform modeler operations +# # Perform modeler operations. You can subtract, add, and perform other operations # using either the object ID or object name. aedtapp.modeler.subtract(o3, o2, True) aedtapp.modeler.subtract(o2, o1, True) -############################################################################### -# Perform mesh operations -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform mesh operations +# # Perform mesh operations. Most mesh operations are available. # After a mesh is created, you can access a mesh operation to # edit or review parameter values. @@ -115,20 +103,21 @@ aedtapp.mesh.assign_model_resolution(names=[o1.name, o3.name], defeature_length=None) aedtapp.mesh.assign_length_mesh(names=o2.faces, isinside=False, maxlength=1, maxel=2000) -############################################################################### -# Create excitations -# ~~~~~~~~~~~~~~~~~~ +# ## Create excitations +# # Create excitations. The ``create_wave_port_between_objects`` method automatically # identifies the closest faces on a predefined direction and creates a sheet to cover # the faces. It also assigns a port to this face. If ``add_pec_cap=True``, the method # creates a PEC cap. +# + aedtapp.wave_port(signal="inner", reference="outer", integration_line=1, create_port_sheet=True, create_pec_cap=True, name="P1") + aedtapp.wave_port(signal="inner", reference="outer", integration_line=4, @@ -138,10 +127,10 @@ port_names = aedtapp.get_all_sources() aedtapp.modeler.fit_all() +# - -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup. A setup is created with default values. After its creation, # you can change values and update the setup. The ``update`` method returns a Boolean # value. @@ -152,17 +141,15 @@ setup.props["BasisOrder"] = 2 setup.props["MaximumPasses"] = 1 -############################################################################### -# Create sweep -# ~~~~~~~~~~~~ +# ## Create sweep +# # Create a sweep. A sweep is created with default values. sweepname = aedtapp.create_linear_count_sweep(setupname="MySetup", unit="GHz", freqstart=0.8, freqstop=1.2, num_of_freq_points=401, sweep_type="Interpolating") -################################################################################ -# Create Icepak model -# ~~~~~~~~~~~~~~~~~~~ +# ## Create Icepak model +# # Create an Icepak model. After an HFSS setup is ready, link this model to an Icepak # project and run a coupled physics analysis. The :func:`FieldAnalysis3D.copy_solid_bodies_from` # method imports a model from HFSS with all material settings. @@ -170,25 +157,22 @@ ipkapp = pyaedt.Icepak() ipkapp.copy_solid_bodies_from(aedtapp) -################################################################################ -# Link sources to EM losses -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Link sources to EM losses +# # Link sources to the EM losses. surfaceobj = ["inner", "outer"] ipkapp.assign_em_losses(designname=aedtapp.design_name, setupname="MySetup", sweepname="LastAdaptive", map_frequency="1GHz", surface_objects=surfaceobj, paramlist=["$coax_dimension", "inner"]) -################################################################################# -# Edit gravity setting -# ~~~~~~~~~~~~~~~~~~~~ +# ## Edit gravity setting +# # Edit the gravity setting if necessary because it is important for a fluid analysis. ipkapp.edit_design_settings(aedtapp.GRAVITY.ZNeg) -################################################################################ -# Set up Icepak project -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Set up Icepak project +# # Set up the Icepak project. When you create a setup, default settings are applied. # When you need to change a property of the setup, you can use the ``props`` # command to pass the correct value to the property. The ``update`` function @@ -198,9 +182,8 @@ setup_ipk = ipkapp.create_setup("SetupIPK") setup_ipk.props["Convergence Criteria - Max Iterations"] = 3 -################################################################################ -# Edit or review mesh parameters -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Edit or review mesh parameters +# # Edit or review the mesh parameters. After a mesh is created, you can access # a mesh operation to edit or review parameter values. @@ -209,9 +192,8 @@ airfaces = ipkapp.modeler.get_object_faces(airbox) ipkapp.assign_openings(airfaces) -################################################################################ -# Close and open projects -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Close and open projects +# # Close and open the projects to ensure that the HFSS - Icepak coupling works # correctly in AEDT versions 2019 R3 through 2021 R1. Closing and opening projects # can be helpful when performing operations on multiple projects. @@ -223,9 +205,8 @@ ipkapp.solution_type = ipkapp.SOLUTIONS.Icepak.SteadyTemperatureAndFlow ipkapp.modeler.fit_all() -################################################################################ -# Solve Icepak project -# ~~~~~~~~~~~~~~~~~~~~ +# ## Solve Icepak project +# # Solve the Icepak project and the HFSS sweep. setup1 = ipkapp.analyze_setup("SetupIPK") @@ -233,11 +214,11 @@ aedtapp.modeler.fit_all() aedtapp.analyze_setup("MySetup") -################################################################################ -# Generate field plots and export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate field plots and export +# # Generate field plots on the HFSS project and export them as images. +# + cutlist = [pyaedt.constants.GLOBALCS.XY, pyaedt.constants.GLOBALCS.ZX, pyaedt.constants.GLOBALCS.YZ] vollist = [o2.name] setup_name = "MySetup : LastAdaptive" @@ -261,12 +242,13 @@ plot_cad_objs=False, log_scale = False, ) +# - -################################################################################ -# Generate animation from field plots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate animation from field plots +# # Generate an animation from field plots using PyVista. +# + import time start = time.time() @@ -295,13 +277,14 @@ endtime = time.time() - start print("Total Time", endtime) +# - -################################################################################ -# Create Icepak plots and export -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create Icepak plots and export +# # Create Icepak plots and export them as images using the same functions that # were used early. Only the quantity is different. +# + quantity_name = "Temperature" setup_name = ipkapp.existing_analysis_sweeps[0] intrinsic = "" @@ -309,10 +292,10 @@ plot5 = ipkapp.post.create_fieldplot_surface(surflist, "SurfTemperature") aedtapp.save_project() +# - -################################################################################ -# Generate plots outside of AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Generate plots outside of AEDT +# # Generate plots outside of AEDT using Matplotlib and NumPy. trace_names = aedtapp.get_traces_for_plot(category="S") @@ -325,10 +308,10 @@ title="Scattering Chart", snapshot_path=os.path.join(results_folder, "Touchstone_from_matplotlib.jpg")) -################################################################################ -# Generate pdf report -# ~~~~~~~~~~~~~~~~~~~ +# ## Generate pdf report +# # Generate a pdf report with output of simultion. + report = AnsysReport(project_name=aedtapp.project_name, design_name=aedtapp.design_name,version=desktopVersion) report.create() report.add_section() @@ -348,11 +331,8 @@ #report.add_image(os.path.join(results_folder, plot5.name+".jpg"), "Coaxial Cable Temperatures") report.save_pdf(results_folder, "AEDT_Results.pdf") - - -################################################################################ -# Close project and release AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Close project and release AEDT +# # Close the project and release AEDT. aedtapp.release_desktop() diff --git a/examples/06-Multiphysics/Hfss_Mechanical.py b/examples/06-Multiphysics/Hfss_Mechanical.py index ad4ecfa32ce..ac473c01505 100644 --- a/examples/06-Multiphysics/Hfss_Mechanical.py +++ b/examples/06-Multiphysics/Hfss_Mechanical.py @@ -1,36 +1,30 @@ -""" -Multiphysics: HFSS-Mechanical multiphysics analysis ---------------------------------------------------- -This example shows how you can use PyAEDT to create a multiphysics workflow that -includes Circuit, HFSS, and Mechanical. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Multiphysics: HFSS-Mechanical multiphysics analysis +# +# This example shows how you can use PyAEDT to create a multiphysics workflow that +# includes Circuit, HFSS, and Mechanical. + +# ## Perform required imports +# # Perform required imports. import os import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Download and open project -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Download and open project +# # Download and open the project. Save it to the temporary folder. project_temp_name = pyaedt.downloads.download_via_wizard(pyaedt.generate_unique_folder_name()) -############################################################################### -# Start HFSS -# ~~~~~~~~~~ +# ## Start HFSS +# # Start HFSS and initialize the PyAEDT object. version = "2023.2" @@ -39,17 +33,15 @@ pin_names = hfss.excitations hfss.change_material_override(True) -############################################################################### -# Start Circuit -# ~~~~~~~~~~~~~ +# ## Start Circuit +# # Start Circuit and add the HFSS dynamic link component to it. circuit = pyaedt.Circuit() hfss_comp = circuit.modeler.schematic.add_subcircuit_dynamic_link(hfss) -############################################################################### -# Set up dynamic link options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set up dynamic link options +# # Set up dynamic link options. The argument for the ``set_sim_option_on_hfss_subcircuit`` # method can be the component name, component ID, or component object. @@ -58,12 +50,12 @@ hfss_setup_name = hfss.setups[0].name + " : " + hfss.setups[0].sweeps[0].name circuit.modeler.schematic.set_sim_solution_on_hfss_subcircuit(hfss_comp.composed_name, hfss_setup_name) -############################################################################### -# Create ports and excitations -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create ports and excitations +# # Create ports and excitations. Find component pin locations and create interface # ports on them. Define the voltage source on the input port. +# + circuit.modeler.schematic.create_interface_port( name="Excitation_1", location=[hfss_comp.pins[0].location[0], hfss_comp.pins[0].location[1]] ) @@ -83,10 +75,10 @@ source = circuit.assign_voltage_sinusoidal_excitation_to_ports(ports_list) source.ac_magnitude = voltage source.phase = phase +# - -############################################################################### -# Create setup -# ~~~~~~~~~~~~ +# ## Create setup +# # Create a setup. setup_name = "MySetup" @@ -98,9 +90,8 @@ sweep_list = ["LINC", str(bw_start) + unit, str(bw_stop) + unit, str(n_points)] LNA_setup.props["SweepDefinition"]["Data"] = " ".join(sweep_list) -############################################################################### -# Solve and push excitations -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Solve and push excitations +# # Solve the circuit and push excitations to the HFSS model to calculate the # correct value of losses. @@ -108,18 +99,16 @@ circuit.push_excitations(instance_name="S1", setup_name=setup_name) -############################################################################### -# Start Mechanical -# ~~~~~~~~~~~~~~~~ +# ## Start Mechanical +# # Start Mechanical and copy bodies from the HFSS project. mech = pyaedt.Mechanical() mech.copy_solid_bodies_from(hfss) mech.change_material_override(True) -############################################################################### -# Get losses from HFSS and assign convection to Mechanical -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get losses from HFSS and assign convection to Mechanical +# # Get losses from HFSS and assign the convection to Mechanical. mech.assign_em_losses( @@ -134,16 +123,14 @@ mech.assign_uniform_convection(objects_list=[mech.modeler[el].top_face_y, mech.modeler[el].bottom_face_y], convection_value=3) -############################################################################### -# Plot model -# ~~~~~~~~~~ +# ## Plot model +# # Plot the model. mech.plot(show=False, export_path=os.path.join(mech.working_directory, "Mech.jpg"), plot_air_objects=False) -############################################################################### -# Solve and plot thermal results -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Solve and plot thermal results +# # Solve and plot the thermal results. mech.create_setup() @@ -154,9 +141,8 @@ surfaces.extend(mech.modeler.get_object_faces(name)) mech.post.create_fieldplot_surface(objlist=surfaces, quantityName="Temperature") -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. mech.release_desktop(True, True) diff --git a/examples/06-Multiphysics/MRI.py b/examples/06-Multiphysics/MRI.py index 47b94a38bcf..3f7243bf330 100644 --- a/examples/06-Multiphysics/MRI.py +++ b/examples/06-Multiphysics/MRI.py @@ -1,49 +1,44 @@ -""" -Multiphysics: HFSS-Mechanical MRI analysis ---------------------------------------------------- -The goal of this workshop is to use a coil tuned to 63.8 MHz to determine the temperature -rise in a gel phantom near an implant given a background SAR of 1 W/kg. - -Steps to follow -Step 1: Simulate coil loaded by empty phantom: -Scale input to coil ports to produce desired background SAR of 1 W/kg at location that will later contain the implant. -Step 2: Simulate coil loaded by phantom containing implant in proper location: -View SAR in tissue surrounding implant. -Step 3: Thermal simulation: -Link HFSS to transient thermal solver to find temperature rise in tissue near implant vs. time. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Multiphysics: HFSS-Mechanical MRI analysis +# +# The goal of this workshop is to use a coil tuned to 63.8 MHz to determine the temperature +# rise in a gel phantom near an implant given a background SAR of 1 W/kg. +# +# Steps to follow +# Step 1: Simulate coil loaded by empty phantom: +# Scale input to coil ports to produce desired background SAR of 1 W/kg at location that will later contain the implant. +# Step 2: Simulate coil loaded by phantom containing implant in proper location: +# View SAR in tissue surrounding implant. +# Step 3: Thermal simulation: +# Link HFSS to transient thermal solver to find temperature rise in tissue near implant vs. time. + +# ## Perform required imports # Perform required imports. -import os.path +import os.path from pyaedt import Hfss, Mechanical, Icepak, downloads -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ` # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Project load -# ~~~~~~~~~~~~ +# ## Project load +# # Open the ANSYS Electronics Desktop 2018.2 # Open project background_SAR.aedt # Project contains phantom and airbox # Phantom consists of two objects: phantom and implant_box # Separate objects are used to selectively assign mesh operations # Material properties defined in this project already contain #electrical and thermal properties. + project_path = downloads.download_file(directory="mri") hfss = Hfss(os.path.join(project_path, "background_SAR.aedt"), specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) -############################################################################### -# Insert 3D component -# ~~~~~~~~~~~~~~~~~~~ + +# ## Insert 3D component +# # The MRI Coil is saved as a separate 3D Component # ‒ 3D Components store geometry (including parameters), # material properties, boundary conditions, mesh assignments, @@ -52,14 +47,14 @@ hfss.modeler.insert_3d_component(os.path.join(project_path, "coil.a3dcomp")) -############################################################################### -# Expression Cache -# ~~~~~~~~~~~~~~~~~ +# ## Expression Cache +# # On the expression cache tab, define additional convergence criteria for self impedance of the four coil # ports # ‒ Set each of these convergence criteria to 2.5 ohm # For this demo number of passes is limited to 2 to reduce simulation time. +# + im_traces = hfss.get_traces_for_plot(get_mutual_terms=False, category="im(Z", first_element_filter="Coil1_p*") hfss.setups[0].enable_expression_cache( @@ -71,6 +66,7 @@ use_cache_for_freq=False) hfss.setups[0].props["MaximumPasses"] = 2 im_traces +# - ############################################################################### # Edit Sources From 4f72e43faadc5d88dd212abecc064d6f03f69314 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 07:43:14 -0600 Subject: [PATCH 35/56] Convert to md format 07-Circui --- examples/07-Circuit/Circuit_AMI.py | 90 ++++++++----------- examples/07-Circuit/Circuit_Example.py | 82 +++++++---------- .../07-Circuit/Circuit_Siwave_Multizones.py | 81 +++++++---------- .../07-Circuit/Circuit_Subcircuit_Example.py | 41 ++++----- examples/07-Circuit/Circuit_Transient.py | 82 +++++++---------- examples/07-Circuit/Create_Netlist.py | 47 ++++------ 6 files changed, 173 insertions(+), 250 deletions(-) diff --git a/examples/07-Circuit/Circuit_AMI.py b/examples/07-Circuit/Circuit_AMI.py index 21045388daa..1e17d04e930 100644 --- a/examples/07-Circuit/Circuit_AMI.py +++ b/examples/07-Circuit/Circuit_AMI.py @@ -1,12 +1,9 @@ -""" -Circuit: AMI PostProcessing ----------------------------------- -This example shows how you can use PyAEDT to perform advanced postprocessing of AMI simulations. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: AMI PostProcessing +# +# This example shows how you can use PyAEDT to perform advanced postprocessing of AMI simulations. + +# # Perform required imports +# # Perform required imports and set the local path to the path for PyAEDT. # sphinx_gallery_thumbnail_path = 'Resources/spectrum_plot.png' @@ -21,16 +18,14 @@ temp_folder = pyaedt.generate_unique_folder_name() project_path = pyaedt.downloads.download_file("ami", "ami_usb.aedtz", temp_folder) -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktopVersion = "2023.2" -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -39,9 +34,8 @@ non_graphical = False NewThread = True -############################################################################### -# Launch AEDT with Circuit and enable Pandas as the output format -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit and enable Pandas as the output format +# # All outputs obtained with the `get_solution_data` method will have the Pandas format. # Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT # and starts the specified version in the specified mode. @@ -50,16 +44,14 @@ cir = pyaedt.Circuit(projectname=os.path.join(project_path), non_graphical=non_graphical, specified_version=desktopVersion, new_desktop_session=NewThread) -############################################################################### -# Solve AMI setup -# ~~~~~~~~~~~~~~~ +# ## Solve AMI setup +# # Solve the transient setup. cir.analyze() -############################################################################### -# Get AMI report -# ~~~~~~~~~~~~~~ +# ## Get AMI report +# # Get AMI report data plot_name = "WaveAfterProbe" @@ -71,16 +63,14 @@ original_data_sweep = original_data.primary_sweep_values print(original_data_value) -############################################################################### -# Plot data -# ~~~~~~~~~ +# ## Plot data +# # Create a plot based on solution data. fig = original_data.plot() -############################################################################### -# Sample WaveAfterProbe waveform using receiver clock -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Sample WaveAfterProbe waveform using receiver clock +# # Extract waveform at specific clock time plus half unit interval probe_name = "b_input_43" @@ -94,9 +84,8 @@ unit_interval=unit_interval, ignore_bits=ignore_bits, plot_type=plot_type) -############################################################################### -# Plot waveform and samples -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot waveform and samples +# # Create the plot from a start time to stop time in seconds tstop = 55e-9 @@ -140,9 +129,8 @@ ax.set_ylabel(original_data.units_data[plot_name]) plt.show() -############################################################################### -# Plot Slicer Scatter -# ~~~~~~~~~~~~~~~~~~~ +# ## Plot Slicer Scatter +# # Create the plot from a start time to stop time in seconds fig, ax2 = plt.subplots() @@ -152,9 +140,8 @@ ax2.set_ylabel("V") plt.show() -############################################################################### -# Plot scatter histogram -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot scatter histogram +# # Create the plot from a start time to stop time in seconds. fig, ax4 = plt.subplots() @@ -164,9 +151,8 @@ ax4.grid() plt.show() -############################################################################### -# Get Transient report -# ~~~~~~~~~~~~~~~~~~~~ +# ## Get Transient report +# # Get Transient report data plot_name = "V(b_input_43.int_ami_rx.eye_probe.out)" @@ -175,9 +161,8 @@ setup_sweep_name="NexximTransient", domain="Time", variations=cir.available_variations.nominal) -############################################################################### -# Sample waveform using a user-defined clock -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Sample waveform using a user-defined clock +# # Extract waveform at specific clock time plus half unit interval. original_data.enable_pandas_output = False @@ -197,9 +182,8 @@ pandas_enabled=False, ) -############################################################################### -# Plot waveform and samples -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot waveform and samples +# # Create the plot from a start time to stop time in seconds. tstop = 40.0e-9 @@ -248,9 +232,8 @@ ax.set_ylabel(waveform_unit) plt.show() -############################################################################### -# Plot slicer scatter -# ~~~~~~~~~~~~~~~~~~~ +# ## Plot slicer scatter +# # Create the plot from a start time to stop time in seconds. sample_waveform_array = np.array(sample_waveform) @@ -261,9 +244,8 @@ ax2.set_ylabel("V") plt.show() -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. cir.save_project() diff --git a/examples/07-Circuit/Circuit_Example.py b/examples/07-Circuit/Circuit_Example.py index 673c091f8b0..61593e44cd3 100644 --- a/examples/07-Circuit/Circuit_Example.py +++ b/examples/07-Circuit/Circuit_Example.py @@ -1,28 +1,24 @@ -""" -Circuit: schematic creation and analysis ----------------------------------------- -This example shows how you can use PyAEDT to create a circuit design -and run a Nexxim time-domain simulation. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: schematic creation and analysis +# +# This example shows how you can use PyAEDT to create a circuit design +# and run a Nexxim time-domain simulation. + +# ## Perform required imports +# # Perform required imports. # sphinx_gallery_thumbnail_path = 'Resources/circuit.png' import pyaedt -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktop_version = "2023.2" -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -31,50 +27,45 @@ non_graphical = False new_thread = True -############################################################################### -# Launch AEDT and Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and Circuit +# # Launch AEDT and Circuit. The :class:`pyaedt.Desktop` class initializes AEDT and # starts the specified version in the specified mode. desktop = pyaedt.launch_desktop(desktop_version, non_graphical, new_thread) aedt_app = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name()) aedt_app.modeler.schematic.schematic_units = "mil" -############################################################################### -# Create circuit setup -# ~~~~~~~~~~~~~~~~~~~~ + +# ## Create circuit setup +# # Create and customize an LNA (linear network analysis) setup. setup1 = aedt_app.create_setup("MyLNA") setup1.props["SweepDefinition"]["Data"] = "LINC 0GHz 4GHz 10001" -############################################################################### -# Create components -# ~~~~~~~~~~~~~~~~~ +# ## Create components +# # Create components, such as an inductor, resistor, and capacitor. inductor = aedt_app.modeler.schematic.create_inductor(compname="L1", value=1e-9, location=[0, 0]) resistor = aedt_app.modeler.schematic.create_resistor(compname="R1", value=50, location=[500, 0]) capacitor = aedt_app.modeler.schematic.create_capacitor(compname="C1", value=1e-12, location=[1000, 0]) -############################################################################### -# Get all pins -# ~~~~~~~~~~~~ +# ## Get all pins +# # Get all pins of a specified component. pins_resistor = resistor.pins -############################################################################### -# Create port and ground -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create port and ground +# # Create a port and a ground, which are needed for the circuit analysis. port = aedt_app.modeler.components.create_interface_port(name="myport", location=[-200, 0] ) gnd = aedt_app.modeler.components.create_gnd(location=[1200, -100]) -############################################################################### -# Connect components -# ~~~~~~~~~~~~~~~~~~ +# ## Connect components +# # Connect components with wires. port.pins[0].connect_to_component(component_pin=inductor.pins[0], use_wire=True) @@ -82,26 +73,23 @@ resistor.pins[0].connect_to_component(component_pin=capacitor.pins[0], use_wire=True) capacitor.pins[1].connect_to_component(component_pin=gnd.pins[0], use_wire=True) -############################################################################### -# Create transient setup -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create transient setup +# # Create a transient setup. setup2 = aedt_app.create_setup(setupname="MyTransient", setuptype=aedt_app.SETUPS.NexximTransient) setup2.props["TransientData"] = ["0.01ns", "200ns"] setup3 = aedt_app.create_setup(setupname="MyDC", setuptype=aedt_app.SETUPS.NexximDC) -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. aedt_app.analyze_setup("MyLNA") aedt_app.export_fullwave_spice() -############################################################################### -# Create report -# ~~~~~~~~~~~~~ +# ## Create report +# # Create a report that plots solution data. solutions = aedt_app.post.get_solution_data( @@ -111,16 +99,14 @@ real, imag = solutions.full_matrix_real_imag print(real) -############################################################################### -# Plot data -# ~~~~~~~~~ +# ## Plot data +# # Create a plot based on solution data. fig = solutions.plot() -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/07-Circuit/Circuit_Siwave_Multizones.py b/examples/07-Circuit/Circuit_Siwave_Multizones.py index 25dc68945bf..97dd7d28005 100644 --- a/examples/07-Circuit/Circuit_Siwave_Multizones.py +++ b/examples/07-Circuit/Circuit_Siwave_Multizones.py @@ -1,21 +1,17 @@ -""" -Circuit: Simulate multi-zones layout with Siwave ------------------------------------------------- -This example shows how you can use PyAEDT simulate multi-zones with Siwave. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: Simulate multi-zones layout with Siwave +# +# This example shows how you can use PyAEDT simulate multi-zones with Siwave. + +# ## Perform required imports +# # Perform required imports, which includes importing a section. from pyaedt import Edb, Circuit import os.path import pyaedt -############################################################################### -# Download file -# ~~~~~~~~~~~~~ +# ## Download file +# # Download the AEDB file and copy it in the temporary folder. temp_folder = pyaedt.generate_unique_folder_name() @@ -27,84 +23,73 @@ print(edb_file) -############################################################################### -# AEDT version -# ~~~~~~~~~~~~ +# ## AEDT version +# # Sets the Aedt version to 2023 R2. edb_version = "2023.2" -##################################################################################### -# Ground net -# ~~~~~~~~~~ +# ## Ground net +# # Common reference net used across all sub-designs, Mandatory for this work flow. common_reference_net = "GND" -######################################################################################## -# Project load -# ~~~~~~~~~~~~ +# ## Project load +# # Load initial Edb file, checking if aedt file exists and remove to allow Edb loading. if os.path.isfile(aedt_file): os.remove(aedt_file) edb = Edb(edbversion=edb_version, edbpath=edb_file) -############################################################################### -# Project zones -# ~~~~~~~~~~~~~ +# ## Project zones +# # Copy project zone into sub project. edb_zones = edb.copy_zones(working_directory=working_directory) -############################################################################### -# Split zones -# ~~~~~~~~~~~ +# ## Split zones +# # Clip sub-designs along with corresponding zone definition # and create port of clipped signal traces. defined_ports, project_connexions = edb.cutout_multizone_layout(edb_zones, common_reference_net) -############################################################################################################# -# Circuit -# ~~~~~~~ +# ## Circuit +# # Create circuit design, import all sub-project as EM model and connect all corresponding pins in circuit. circuit = Circuit(specified_version=edb_version, projectname=circuit_project_file) circuit.connect_circuit_models_from_multi_zone_cutout(project_connections=project_connexions, edb_zones_dict=edb_zones, ports=defined_ports, model_inc=70) -############################################################################### -# Setup -# ~~~~~ +# ## Setup +# # Add Nexxim LNA simulation setup. circuit_setup= circuit.create_setup("Pyedt_LNA") -############################################################################### -# Frequency sweep -# ~~~~~~~~~~~~~~~ +# ## Frequency sweep +# # Add frequency sweep from 0GHt to 20GHz with 10NHz frequency step. circuit_setup.props["SweepDefinition"]["Data"] = "LIN {} {} {}".format("0GHz", "20GHz", "10MHz") -############################################################################### -# Start simulation -# ~~~~~~~~~~~~~~~~ +# ## Start simulation +# # Analyze all siwave projects and solves the circuit. circuit.analyze() -############################################################################### -# Define differential pairs -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define differential pairs +# circuit.set_differential_pair(diff_name="U0", positive_terminal="U0.via_38.B2B_SIGP", negative_terminal="U0.via_39.B2B_SIGN") circuit.set_differential_pair(diff_name="U1", positive_terminal="U1.via_32.B2B_SIGP", negative_terminal="U1.via_33.B2B_SIGN") -############################################################################### -# Plot results -# ~~~~~~~~~~~~ +# ## Plot results +# circuit.post.create_report(expressions=["dB(S(U0,U0))", "dB(S(U1,U0))"], context="Differential Pairs") -############################################################################### -# Release AEDT desktop -# ~~~~~~~~~~~~~~~~~~~~ +# ## Release AEDT desktop +# + circuit.release_desktop() \ No newline at end of file diff --git a/examples/07-Circuit/Circuit_Subcircuit_Example.py b/examples/07-Circuit/Circuit_Subcircuit_Example.py index ed2cffd087e..9e39a508363 100644 --- a/examples/07-Circuit/Circuit_Subcircuit_Example.py +++ b/examples/07-Circuit/Circuit_Subcircuit_Example.py @@ -1,29 +1,25 @@ -""" -Circuit: schematic subcircuit management ----------------------------------------- -This example shows how you can use PyAEDT to add a subcircuit to a circuit design. -It pushes down the child subcircuit and pops up to the parent design. -""" -########################################################## -# Perform required import -# ~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: schematic subcircuit management +# +# This example shows how you can use PyAEDT to add a subcircuit to a circuit design. +# It pushes down the child subcircuit and pops up to the parent design. + +# ## Perform required import +# # Perform the required import. import os import pyaedt -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT 2023 R2 in graphical mode with Circuit. circuit = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name(), @@ -33,9 +29,8 @@ ) circuit.modeler.schematic_units = "mil" -############################################################################### -# Add subcircuit -# ~~~~~~~~~~~~~~ +# ## Add subcircuit +# # Add a new subcircuit to the previously created circuit design, creating a # child circuit. Push this child circuit down into the child subcircuit. @@ -43,9 +38,8 @@ subcircuit_name = subcircuit.composed_name circuit.push_down(subcircuit) -############################################################################### -# Parametrize subcircuit -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize subcircuit +# # Parametrize the subcircuit and add a resistor, inductor, and a capacitor with # the parameter values in the following code example. Connect them in series # and then use the ``pop_up`` # method to get back to the parent design. @@ -62,9 +56,8 @@ circuit.pop_up() -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. circuit.release_desktop(True, True) diff --git a/examples/07-Circuit/Circuit_Transient.py b/examples/07-Circuit/Circuit_Transient.py index 4f1f1fa994c..52d64b01b82 100644 --- a/examples/07-Circuit/Circuit_Transient.py +++ b/examples/07-Circuit/Circuit_Transient.py @@ -1,12 +1,11 @@ -""" -Circuit: transient analysis and eye plot ----------------------------------------- -This example shows how you can use PyAEDT to create a circuit design, -run a Nexxim time-domain simulation, and create an eye diagram. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: transient analysis and eye plot +# ---------------------------------------- +# This example shows how you can use PyAEDT to create a circuit design, +# run a Nexxim time-domain simulation, and create an eye diagram. + + +# ## Perform required imports +# # Perform required imports. import os @@ -14,18 +13,16 @@ import numpy as np import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode, ``"PYAEDT_NON_GRAPHICAL"`` is needed to generate # documentation only. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT 2023 R2 in graphical mode with Circuit. cir = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name(), @@ -34,42 +31,37 @@ non_graphical=non_graphical ) -############################################################################### -# Read IBIS file -# ~~~~~~~~~~~~~~ +# ## Read IBIS file +# # Read an IBIS file and place a buffer in the schematic. ibis = cir.get_ibis_model_from_file(os.path.join(cir.desktop_install_dir, 'buflib', 'IBIS', 'u26a_800.ibs')) ibs = ibis.buffers["DQ_u26a_800"].insert(0, 0) -############################################################################### -# Place ideal transmission line -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Place ideal transmission line +# # Place an ideal transmission line in the schematic and parametrize it. tr1 = cir.modeler.components.components_catalog["Ideal Distributed:TRLK_NX"].place("tr1") tr1.parameters["P"] = "50mm" -############################################################################### -# Create resistor and ground -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create resistor and ground +# # Create a resistor and ground in the schematic. res = cir.modeler.components.create_resistor(compname="R1", value="1Meg") gnd1 = cir.modeler.components.create_gnd() -############################################################################### -# Connect elements -# ~~~~~~~~~~~~~~~~ +# ## Connect elements +# # Connect elements in the schematic. tr1.pins[0].connect_to_component(ibs.pins[0]) tr1.pins[1].connect_to_component(res.pins[0]) res.pins[1].connect_to_component(gnd1.pins[0]) -############################################################################### -# Place probe -# ~~~~~~~~~~~ +# ## Place probe +# # Place a probe and rename it to ``Vout``. pr1 = cir.modeler.components.components_catalog["Probes:VPROBE"].place("vout") @@ -79,18 +71,16 @@ pr2.parameters["Name"] = "Vin" pr2.pins[0].connect_to_component(ibs.pins[0]) -############################################################################### -# Create setup and analyze -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and analyze +# # Create a transient analysis setup and analyze it. trans_setup = cir.create_setup(setupname="TransientRun", setuptype="NexximTransient") trans_setup.props["TransientData"] = ["0.01ns", "200ns"] cir.analyze_setup("TransientRun") -############################################################################### -# Create report outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create report outside AEDT +# # Create a report outside AEDT using the ``get_solution_data`` method. This # method allows you to get solution data and plot it outside AEDT without needing # a UI. @@ -101,9 +91,8 @@ solutions = cir.post.get_solution_data(domain="Time") solutions.plot("V(Vout)") -############################################################################### -# Create report inside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create report inside AEDT +# # Create a report inside AEDT using the ``new_report`` object. This object is # fully customizable and usable with most of the reports available in AEDT. # The standard report is the main one used in Circuit and Twin Builder. @@ -126,9 +115,8 @@ sol = new_report.get_solution_data() sol.plot() -############################################################################### -# Create eye diagram inside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create eye diagram inside AEDT +# # Create an eye diagram inside AEDT using the ``new_eye`` object. new_eye = cir.post.reports_by_category.eye_diagram("V(Vout)") @@ -136,9 +124,8 @@ new_eye.time_stop = "100ns" new_eye.create() -############################################################################### -# Create eye diagram outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create eye diagram outside AEDT +# # Create the same eye diagram outside AEDT using Matplotlib and the # ``get_solution_data`` method. @@ -168,9 +155,8 @@ plt.plot(cellst.T, cellsv.T, zorder=0) plt.show() -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. cir.save_project() cir.release_desktop() diff --git a/examples/07-Circuit/Create_Netlist.py b/examples/07-Circuit/Create_Netlist.py index e71a1de0778..15869a71adc 100644 --- a/examples/07-Circuit/Create_Netlist.py +++ b/examples/07-Circuit/Create_Netlist.py @@ -1,13 +1,10 @@ -""" -Circuit: netlist to schematic import ------------------------------------- -This example shows how you can import netlist data into a circuit design. -HSPICE files are fully supported. Mentor files are partially supported. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: netlist to schematic import +# +# This example shows how you can import netlist data into a circuit design. +# HSPICE files are fully supported. Mentor files are partially supported. + +# ## Perform required imports +# # Perform required imports and set paths. import os @@ -19,15 +16,13 @@ project_name = pyaedt.generate_unique_project_name() print(project_name) -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktopVersion = "2023.2" -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``NewThread`` defines whether to create a new instance @@ -36,34 +31,30 @@ non_graphical = False NewThread = True -############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT # and starts it on the specified version in the specified graphical mode. desktop = pyaedt.launch_desktop(desktopVersion, non_graphical, NewThread) aedtapp = pyaedt.Circuit(projectname=project_name) -############################################################################### -# Define variable -# ~~~~~~~~~~~~~~~ +# ## Define variable +# # Define a design variable by using a ``$`` prefix. aedtapp["Voltage"] = "5" -############################################################################### -# Create schematic from netlist file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create schematic from netlist file +# # Create a schematic from a netlist file. The ``create_schematic_from_netlist`` # method reads the netlist file and parses it. All components are parsed # but only these categories are mapped: R, L, C, Q, U, J, V, and I. aedtapp.create_schematic_from_netlist(netlist) -############################################################################### -# Close project and release AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Close project and release AEDT +# # After adding any other desired functionalities, close the project and release # AEDT. From 28e8e91bce90696fa131667417e875813cb15d23 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 11:16:50 -0600 Subject: [PATCH 36/56] Convert to md format 07-EMIT --- examples/07-EMIT/ComputeInterferenceType.py | 46 +++++----- examples/07-EMIT/ComputeProtectionLevels.py | 71 +++++++--------- examples/07-EMIT/EMIT_Example.py | 46 +++++----- examples/07-EMIT/EMIT_HFSS_Example.py | 51 +++++------ examples/07-EMIT/interference_gui.py | 93 ++++++++++----------- 5 files changed, 132 insertions(+), 175 deletions(-) diff --git a/examples/07-EMIT/ComputeInterferenceType.py b/examples/07-EMIT/ComputeInterferenceType.py index dfe37528795..c22d2452ab6 100644 --- a/examples/07-EMIT/ComputeInterferenceType.py +++ b/examples/07-EMIT/ComputeInterferenceType.py @@ -1,13 +1,11 @@ -""" -EMIT: Classify interference type --------------------------------- -This example shows how you can use PyAEDT to load an existing AEDT -project with an EMIT design and analyze the results to classify the -worst-case interference. -""" -############################################################################### +# # EMIT: Classify interference type +# # +# This example shows how you can use PyAEDT to load an existing AEDT +# project with an EMIT design and analyze the results to classify the +# worst-case interference. + # Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Perform required imports. import sys from pyaedt.emit_core.emit_constants import InterfererType, ResultType, TxRxMode @@ -44,9 +42,8 @@ def install(package): print("Warning: this example requires AEDT 2023.2 or later.") sys.exit() -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -58,8 +55,8 @@ def install(package): path_to_desktop_project = pyaedt.downloads.download_file("emit", "interference.aedtz") emitapp = Emit(non_graphical=False, new_desktop_session=False, projectname=path_to_desktop_project) -# Get all the radios in the project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get all the radios in the project +# # Get lists of all transmitters and receivers in the project. rev = emitapp.results.analyze() tx_interferer = InterfererType().TRANSMITTERS @@ -72,9 +69,8 @@ def install(package): sys.exit() -############################################################################### -# Classify the interference -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Classify the interference +# # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Computes # which, if any, type of interference occurred. @@ -83,9 +79,8 @@ def install(package): all_colors=[] all_colors, power_matrix = rev.interference_type_classification(domain, use_filter = False, filter_list = []) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. @@ -93,9 +88,8 @@ def install(package): emitapp.save_project() emitapp.release_desktop() -############################################################################### -# Create a scenario matrix view -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create a scenario matrix view +# # Create a scenario matrix view with the transmitters defined across the top # and receivers down the left-most column. The power at the input to each # receiver is shown in each cell of the matrix and color-coded based on the @@ -145,9 +139,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): ) fig.show() -############################################################################### -# Generate a legend -# ~~~~~~~~~~~~~~~~~ +# ## Generate a legend +# # Define the interference types and colors used to display the results of # the analysis. @@ -183,6 +176,7 @@ def create_legend_table(): width = 600 ) fig.show() + if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": # Create a scenario view for all the interference types create_scenario_view(power_matrix, all_colors, tx_radios, rx_radios) diff --git a/examples/07-EMIT/ComputeProtectionLevels.py b/examples/07-EMIT/ComputeProtectionLevels.py index 84a8ac45379..5e2ca15fbb6 100644 --- a/examples/07-EMIT/ComputeProtectionLevels.py +++ b/examples/07-EMIT/ComputeProtectionLevels.py @@ -1,12 +1,10 @@ -""" -EMIT: Compute receiver protection levels ----------------------------------------- -This example shows how you can use PyAEDT to open an AEDT project with -an EMIT design and analyze the results to determine if the received -power at the input to each receiver exceeds the specified protection -levels. -""" -############################################################################### +# # EMIT: Compute receiver protection levels +# ---------------------------------------- +# This example shows how you can use PyAEDT to open an AEDT project with +# an EMIT design and analyze the results to determine if the received +# power at the input to each receiver exceeds the specified protection +# levels. + # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. @@ -36,9 +34,8 @@ def install(package): # Import required modules import plotly.graph_objects as go -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ``"PYAEDT_NON_GRAPHICAL"``` is needed to generate # documentation only. # You can set ``non_graphical`` either to ``True`` or ``False``. @@ -49,9 +46,8 @@ def install(package): new_thread = True desktop_version = "2023.2" -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -62,9 +58,8 @@ def install(package): d = pyaedt.launch_desktop(desktop_version, non_graphical, new_thread) emitapp = Emit(pyaedt.generate_unique_project_name()) -############################################################################### -# Specify the protection levels -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Specify the protection levels +# # The protection levels are specified in dBm. # If the damage threshold is exceeded, permanent damage to the receiver front # end may occur. @@ -82,18 +77,16 @@ def install(package): protection_levels = [damage_threshold, overload_threshold, intermod_threshold, desense_threshold] -############################################################################### -# Create and connect EMIT components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create and connect EMIT components +# # Set up the scenario with radios connected to antennas. bluetooth, blue_ant = emitapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)", "Bluetooth") gps, gps_ant = emitapp.modeler.components.create_radio_antenna("GPS Receiver", "GPS") wifi, wifi_ant = emitapp.modeler.components.create_radio_antenna("WiFi - 802.11-2012", "WiFi") -############################################################################### -# Configure the radios -# ~~~~~~~~~~~~~~~~~~~~ +# ## Configure the radios +# # Enable the HR-DSSS bands for the Wi-Fi radio and set the power level # for all transmit bands to -20 dBm. @@ -132,16 +125,14 @@ def get_radio_node(radio_name): else: band.enabled=False -############################################################################### -# Load the results set -# ~~~~~~~~~~~~~~~~~~~~ +# ## Load the results set +# # Create a results revision and load it for analysis. rev = emitapp.results.analyze() -############################################################################### -# Generate a legend -# ~~~~~~~~~~~~~~~~~ +# ## Generate a legend +# # Define the thresholds and colors used to display the results of # the protection level analysis. @@ -177,9 +168,8 @@ def create_legend_table(): ) fig.show() -############################################################################### -# Create a scenario matrix view -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create a scenario matrix view +# # Create a scenario matrix view with the transmitters defined across the top # and receivers down the left-most column. The power at the input to each # receiver is shown in each cell of the matrix and color-coded based on the @@ -221,9 +211,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): ) fig.show() -############################################################################### -# Get all the radios in the project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get all the radios in the project +# # Get lists of all transmitters and receivers in the project. if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": rev = emitapp.results.current_revision @@ -231,9 +220,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): tx_radios = rev.get_interferer_names(InterfererType.TRANSMITTERS) domain = emitapp.results.interaction_domain() -############################################################################### -# Classify the results -# ~~~~~~~~~~~~~~~~~~~~ +# ## Classify the results +# # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Computes # which, if any, protection levels are exceeded by these power levels. @@ -249,9 +237,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): # Create a legend for the protection levels create_legend_table() -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/07-EMIT/EMIT_Example.py b/examples/07-EMIT/EMIT_Example.py index 3de88c6148e..142ec1ba4e1 100644 --- a/examples/07-EMIT/EMIT_Example.py +++ b/examples/07-EMIT/EMIT_Example.py @@ -1,12 +1,10 @@ -""" -EMIT: antenna ---------------------- -This example shows how you can use PyAEDT to create a project in EMIT for -the simulation of an antenna. -""" -############################################################################### -# Perform required inputs -# ~~~~~~~~~~~~~~~~~~~~~~~ +# # EMIT: antenna +# +# This example shows how you can use PyAEDT to create a project in EMIT for +# the simulation of an antenna. + +# ## Perform required inputs +# # Perform required imports. # # sphinx_gallery_thumbnail_path = "Resources/emit_simple_cosite.png" @@ -16,9 +14,8 @@ from pyaedt.emit_core.emit_constants import TxRxMode, ResultType -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The ``NewThread`` Boolean variable defines whether to create a new instance @@ -29,9 +26,8 @@ desktop_version = "2023.2" -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -39,9 +35,8 @@ aedtapp = pyaedt.Emit(pyaedt.generate_unique_project_name()) -############################################################################### -# Create and connect EMIT components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create and connect EMIT components +# # Create three radios and connect an antenna to each one. rad1 = aedtapp.modeler.components.create_component("New Radio") @@ -53,16 +48,14 @@ rad2, ant2 = aedtapp.modeler.components.create_radio_antenna("GPS Receiver") rad3, ant3 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)", "Bluetooth") -############################################################################### -# Define coupling among RF systems -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define coupling among RF systems +# # Define the coupling among the RF systems. This portion of the EMIT API is not # yet implemented. -############################################################################### -# Run EMIT simulation -# ~~~~~~~~~~~~~~~~~~~ +# ## Run EMIT simulation +# # Run the EMIT simulation. # # This part of the example requires Ansys AEDT 2023 R2. @@ -80,9 +73,8 @@ emi = worst.get_value(ResultType.EMI) print("Worst case interference is: {} dB".format(emi)) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/07-EMIT/EMIT_HFSS_Example.py b/examples/07-EMIT/EMIT_HFSS_Example.py index 840d5b96f56..2b531404989 100644 --- a/examples/07-EMIT/EMIT_HFSS_Example.py +++ b/examples/07-EMIT/EMIT_HFSS_Example.py @@ -1,13 +1,11 @@ -""" -EMIT: HFSS to EMIT coupling ---------------------------- -This example shows how you can use PyAEDT to open an AEDT project with -an HFSS design, create an EMIT design in the project, and link the HFSS design -as a coupling link in the EMIT design. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # EMIT: HFSS to EMIT coupling +# +# This example shows how you can use PyAEDT to open an AEDT project with +# an HFSS design, create an EMIT design in the project, and link the HFSS design +# as a coupling link in the EMIT design. + +# ## Perform required imports +# # Perform required imports. # # sphinx_gallery_thumbnail_path = "Resources/emit_hfss.png" @@ -19,9 +17,8 @@ from pyaedt.generic.filesystem import Scratch from pyaedt.emit_core.emit_constants import TxRxMode, ResultType -############################################################################### -## Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -34,9 +31,8 @@ desktop_version = "2023.2" scratch_path = pyaedt.generate_unique_folder_name() -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -57,7 +53,6 @@ example_results_folder = os.path.join(example_dir, example_results) example_pdf = os.path.join(example_dir, example_pdf_file) -######################################################################################################## # If the ``Cell Phone RFT Defense`` example is not # in the installation directory, exit from this example. @@ -89,17 +84,15 @@ aedtapp = pyaedt.Emit(my_project) -############################################################################### -# Create and connect EMIT components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create and connect EMIT components +# # Create two radios with antennas connected to each one. rad1, ant1 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)") rad2, ant2 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)") -############################################################################### -# Define coupling among RF systems -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define coupling among RF systems +# # Define coupling among the RF systems. for link in aedtapp.couplings.linkable_design_names: @@ -108,9 +101,8 @@ for link in aedtapp.couplings.coupling_names: aedtapp.couplings.update_link(link) -############################################################################### -# Run EMIT simulation -# ~~~~~~~~~~~~~~~~~~~ +# ## Run EMIT simulation +# # Run the EMIT simulation. This portion of the EMIT API is not yet implemented. # # This part of the example requires Ansys AEDT 2023 R2. @@ -128,11 +120,10 @@ emi = worst.get_value(ResultType.EMI) print("Worst case interference is: {} dB".format(emi)) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.force_close_desktop` method. +# `pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. aedtapp.save_project() diff --git a/examples/07-EMIT/interference_gui.py b/examples/07-EMIT/interference_gui.py index cea3dfd1165..59982f14162 100644 --- a/examples/07-EMIT/interference_gui.py +++ b/examples/07-EMIT/interference_gui.py @@ -1,11 +1,9 @@ -""" -EMIT: Classify interference type GUI ----------------------------------------- -This example uses a GUI to open an AEDT project with -an EMIT design and analyze the results to classify the -worst-case interference. -""" -############################################################################### +# # EMIT: Classify interference type GUI +# ---------------------------------------- +# This example uses a GUI to open an AEDT project with +# an EMIT design and analyze the results to classify the +# worst-case interference. + # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. @@ -44,13 +42,16 @@ def install(package): from openpyxl.styles import PatternFill import openpyxl + # Uncomment if there are Qt plugin errors # import PySide6 # dirname = os.path.dirname(PySide6.__file__) # plugin_path = os.path.join(dirname, 'plugins', 'platforms') # os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path - +# # Launch EMIT + +# + non_graphical = False new_thread = True desktop = pyaedt.launch_desktop(emitapp_desktop_version, non_graphical, new_thread) @@ -64,6 +65,11 @@ def install(package): # Define .ui file for GUI ui_file = pyaedt.downloads.download_file("emit", "interference_gui.ui") Ui_MainWindow, _ = QtUiTools.loadUiType(ui_file) +# - + +# ### Create a simpl QT UI +# +# Build a simple UI using QT. class DoubleDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, decimals, values, max_power, min_power): @@ -104,9 +110,8 @@ def __init__(self): self.setupUi(self) self.setup_widgets() - ############################################################################### - # Setup widgets - # ~~~~~~~~~~~~~ + # ## Setup widgets + # # Define all widgets from the UI file, connect the widgets to functions, define # table colors, and format table settings. @@ -230,9 +235,8 @@ def setup_widgets(self): self.protection_legend_table.setItemDelegateForColumn(0, self.delegate) self.open_file_dialog() - ############################################################################### - # Open file dialog and select project - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Open file dialog and select project + # # Open the file dialog for project selection and populate the design dropdown # with all EMIT designs in the project. @@ -311,9 +315,8 @@ def open_file_dialog(self): self.protection_results_btn.setEnabled(True) self.interference_results_btn.setEnabled(True) - ############################################################################### - # Change design selection - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Change design selection + # # Refresh the warning messages when the selected design changes def design_dropdown_changed(self): @@ -332,9 +335,8 @@ def design_dropdown_changed(self): # clear the table if the design is changed self.clear_table() - ############################################################################### - # Enable radio specific protection levels - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Enable radio specific protection levels + # # Activate radio selection dropdown and initialize dictionary to store protection levels # when the radio-specific level dropdown is checked. @@ -355,9 +357,8 @@ def radio_specific(self): self.protection_levels['Global'] = values self.global_protection_level = not self.radio_specific_levels.isChecked() - ############################################################################### - # Update legend table - # ~~~~~~~~~~~~~~~~~~~ + # ### Update legend table + # # Update shown legend table values when the radio dropdown value changes. def radio_dropdown_changed(self): @@ -372,9 +373,8 @@ def radio_dropdown_changed(self): range(self.protection_legend_table.rowCount())] self.delegate.update_values(values) - ############################################################################### - # Save legend table values - # ~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Save legend table values + # # Save inputted radio protection level threshold values every time one is changed # in the legend table. @@ -389,9 +389,8 @@ def table_changed(self): self.protection_levels[index] = values self.delegate.update_values(values) - ############################################################################### - # Save scenario matrix to as PNG file - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Save scenario matrix to as PNG file + # # Save the scenario matrix table as a PNG file. def save_image(self): @@ -406,9 +405,8 @@ def save_image(self): table.render(image) image.save(fname) - ############################################################################### - # Save scenario matrix to Excel file - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Save scenario matrix to Excel file + # # Write the scenario matrix results to an Excel file with color coding. def save_results_excel(self): @@ -439,9 +437,8 @@ def save_results_excel(self): fill_type = "solid") workbook.save(fname) - ############################################################################### - # Run interference type simulation - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Run interference type simulation + # # Run interference type simulation and classify results. def interference_results(self): @@ -479,8 +476,8 @@ def interference_results(self): if self.tx_radios is None or self.rx_radios is None: return - # Classify the interference - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Classify the interference + # # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Compute # which, if any, type of interference occurred. @@ -491,9 +488,8 @@ def interference_results(self): self.emitapp.save_project() self.populate_table() - ############################################################################### - # Run protection level simulation - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Run protection level simulation + # # Run protection level simulation and classify results accroding to inputted # threshold levels. @@ -539,9 +535,8 @@ def protection_results(self): self.populate_table() - ############################################################################### - # Populate the scenario matrix - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Populate the scenario matrix + # # Create a scenario matrix view with the transmitters defined across the top # and receivers down the left-most column. @@ -591,9 +586,8 @@ def clear_table(self): table.setColumnCount(0) table.setRowCount(0) - ############################################################################### - # GUI closing event - # ~~~~~~~~~~~~~~~~~ + # ### GUI closing event + # # Close AEDT if the GUI is closed. def closeEvent(self, event): msg = QtWidgets.QMessageBox() @@ -606,9 +600,8 @@ def closeEvent(self, event): else: desktop.release_desktop(True, True) -############################################################################### -# Run GUI -# ~~~~~~~ +# ### Run GUI +# # Launch the GUI. If you want to run the GUI, uncomment the ``window.show()`` and # ``app.exec_()`` method calls. From 0cecc3d1b54307e70130940c68e600de8a111152 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 11:27:33 -0600 Subject: [PATCH 37/56] Convert to md format 07-TwinBuilder --- .../07-TwinBuilder/01-RC_Circuit_Example.py | 63 +++++++---------- .../07-TwinBuilder/02-Wiring_A_Rectifier.py | 57 +++++++-------- ...-Dynamic_ROM_Creation_And_Visualization.py | 67 ++++++++---------- ...4-Static_ROM_Creation_And_Visualization.py | 69 +++++++++---------- 4 files changed, 109 insertions(+), 147 deletions(-) diff --git a/examples/07-TwinBuilder/01-RC_Circuit_Example.py b/examples/07-TwinBuilder/01-RC_Circuit_Example.py index 42b842b459a..ee71aef6118 100644 --- a/examples/07-TwinBuilder/01-RC_Circuit_Example.py +++ b/examples/07-TwinBuilder/01-RC_Circuit_Example.py @@ -1,20 +1,17 @@ -""" -Twin Builder: RC circuit design anaysis ---------------------------------------- -This example shows how you can use PyAEDT to create a Twin Builder design -and run a Twin Builder time-domain simulation. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: RC circuit design anaysis +# +# This example shows how you can use PyAEDT to create a Twin Builder design +# and run a Twin Builder time-domain simulation. + + +# ## Perform required imports +# # Perform required imports. import pyaedt -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select version and set launch options +# # Select the Twin Builder version and set the launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -28,9 +25,8 @@ non_graphical = False new_thread = True -############################################################################### -# Launch Twin Builder -# ~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup. @@ -41,9 +37,8 @@ ) tb.modeler.schematic_units = "mil" -############################################################################### -# Create components for RC circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create components for RC circuit +# # Create components for an RC circuit driven by a pulse voltage source. # Create components, such as a voltage source, resistor, and capacitor. @@ -51,16 +46,14 @@ resistor = tb.modeler.schematic.create_resistor("R1", 10000, [1000, 1000], 90) capacitor = tb.modeler.schematic.create_capacitor("C1", 1e-6, [2000, 0]) -############################################################################### -# Create ground -# ~~~~~~~~~~~~~ +# ## Create ground +# # Create a ground, which is needed for an analog analysis. gnd = tb.modeler.components.create_gnd([0, -1000]) -############################################################################### -# Connect components -# ~~~~~~~~~~~~~~~~~~ +# ## Connect components +# # Connects components with pins. source.pins[1].connect_to_component(resistor.pins[0]) @@ -68,24 +61,21 @@ capacitor.pins[1].connect_to_component(source.pins[0]) source.pins[0].connect_to_component(gnd.pins[0]) -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("300ms") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # voltage on the capacitor in the RC circuit. @@ -98,9 +88,8 @@ tb.save_project() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation completes, you can close Twin Builder or release it. # All methods provide for saving the project before closing. diff --git a/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py b/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py index bd7b1b45469..aeef5d39d0f 100644 --- a/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py +++ b/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py @@ -1,22 +1,18 @@ -""" -Twin Builder: wiring a rectifier with a capacitor filter ---------------------------------------------------------- -This example shows how you can use PyAEDT to create a Twin Builder design -and run a Twin Builder time-domain simulation. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: wiring a rectifier with a capacitor filter +# +# This example shows how you can use PyAEDT to create a Twin Builder design +# and run a Twin Builder time-domain simulation. + +# ## Perform required imports +# # Perform required imports. import math import matplotlib.pyplot as plt import pyaedt -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select version and set launch options +# # Select the Twin Builder version and set the launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -29,9 +25,8 @@ non_graphical = False new_thread = True -############################################################################### -# Launch Twin Builder -# ~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup. @@ -41,9 +36,8 @@ new_desktop_session=new_thread ) -############################################################################### -# Create components for bridge rectifier -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create components for bridge rectifier +# # Create components for a bridge rectifier with a capacitor filter. # Define the grid distance for ease in calculations. @@ -73,9 +67,8 @@ gnd = tb.modeler.components.create_gnd(location=[5 * G, -16 * G]) -############################################################################### -# Connect components -# ~~~~~~~~~~~~~~~~~~ +# ## Connect components +# # Connect components with wires. # Wire the diode bridge. @@ -104,24 +97,21 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("100ms") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # voltage on the capacitor in the RC circuit. @@ -139,9 +129,8 @@ plt.ylabel("AC to DC Conversion using Rectifier") plt.show() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation is completed, you can close Twin Builder or release it. # All methods provide for saving the project before closing. diff --git a/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py b/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py index 979bae7e9a8..719f2effbac 100644 --- a/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py +++ b/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py @@ -1,17 +1,15 @@ -""" -Twin Builder: dynamic ROM creation and simulation (2023 R2 beta) ----------------------------------------------------------------- -This example shows how you can use PyAEDT to create a dynamic ROM in Twin Builder -and run a Twin Builder time-domain simulation. - -.. note:: - This example uses functionality only available in Twin Builder 2023 R2 and later. - For 2023 R2, the build date must be 8/7/2022 or later. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: dynamic ROM creation and simulation (2023 R2 beta) +# +# This example shows how you can use PyAEDT to create a dynamic ROM in Twin Builder +# and run a Twin Builder time-domain simulation. +# +# > _Note:_ This example uses functionality only available in Twin +# > Builder 2023 R2 and later. +# > For 2023 R2, the build date must be 8/7/2022 or later. + + +# ## Perform required imports +# # Perform required imports. import os @@ -22,9 +20,9 @@ from pyaedt import generate_unique_folder_name from pyaedt import downloads from pyaedt import settings -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Select version and set launch options +# # Select the Twin Builder version and set launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -37,9 +35,8 @@ non_graphical = False new_thread = True -############################################################################### -# Set up input data -# ~~~~~~~~~~~~~~~~~ +# ## Set up input data +# # Define needed file name source_snapshot_data_zipfilename = "Ex1_Mechanical_DynamicRom.zip" @@ -60,9 +57,8 @@ shutil.copyfile(os.path.join(source_data_folder ,source_build_conf_file), os.path.join(data_folder,source_build_conf_file)) -############################################################################### -# Launch Twin Builder and build ROM component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder and build ROM component +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup for building the dynamic ROM component. @@ -95,9 +91,8 @@ rom_manager.CreateROMComponent(dynamic_rom_path.replace('\\', '/'),'dynarom') -############################################################################### -# Create schematic -# ~~~~~~~~~~~~~~~~ +# ## Create schematic +# # Place components to create a schematic. # Define the grid distance for ease in calculations @@ -121,26 +116,23 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("1000s") tb.set_hmin("1s") tb.set_hmax("1s") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # output of the dynamic ROM. @@ -159,9 +151,8 @@ plt.show() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation is completed, you can close Twin Builder or release it. # All methods provide for saving the project before closing. diff --git a/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py b/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py index 10f78cc11a6..7c1978fbb3a 100644 --- a/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py +++ b/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py @@ -1,17 +1,14 @@ -""" -Twin Builder: static ROM creation and simulation (2023 R2 beta) ---------------------------------------------------------------- -This example shows how you can use PyAEDT to create a static ROM in Twin Builder -and run a Twin Builder time-domain simulation. - -.. note:: - This example uses functionality only available in Twin Builder 2023 R2 and later. - For 2023 R2, the build date must be 8/7/2022 or later. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: static ROM creation and simulation (2023 R2 beta) +# +# This example shows how you can use PyAEDT to create a static ROM in Twin Builder +# and run a Twin Builder time-domain simulation. +# +# > _Note:_ This example uses functionality only +# > available in Twin Builder 2023 R2 and later. +# > For 2023 R2, the build date must be 8/7/2022 or later. + +# ## Perform required imports +# # Perform required imports. import os @@ -24,9 +21,8 @@ from pyaedt import downloads from pyaedt import settings -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select version and set launch options +# # Select the Twin Builder version and set launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -38,9 +34,9 @@ desktop_version = "2023.2" non_graphical = False new_thread = True -############################################################################### -# Set up input data -# ~~~~~~~~~~~~~~~~~ + +# ## Set up input data +# # Define needed file name source_snapshot_data_zipfilename = "Ex1_Fluent_StaticRom.zip" @@ -64,18 +60,19 @@ shutil.copyfile(os.path.join(source_data_folder, source_props_conf_file), os.path.join(data_folder, source_props_conf_file)) -############################################################################### -# Launch Twin Builder and build ROM component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder and build ROM component +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup for building the static ROM component. +# + tb = TwinBuilder(projectname=generate_unique_project_name(), specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=new_thread) # Switch the current desktop configuration and the schematic environment to "Twin Builder". # The Static ROM feature is only available with a twin builder license. # This and the restoring section at the end are not needed if the desktop is already configured as "Twin Builder". + current_desktop_config = tb._odesktop.GetDesktopConfiguration() current_schematic_environment = tb._odesktop.GetSchematicEnvironment() tb._odesktop.SetDesktopConfiguration("Twin Builder") @@ -98,10 +95,10 @@ # Create the ROM component definition in Twin Builder rom_manager.CreateROMComponent(static_rom_path.replace('\\', '/'), 'staticrom') +# - -############################################################################### -# Create schematic -# ~~~~~~~~~~~~~~~~ +# ## Create schematic +# # Place components to create a schematic. # Define the grid distance for ease in calculations @@ -128,26 +125,23 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("300s") tb.set_hmin("1s") tb.set_hmax("1s") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. Skipping in case of documentation build. if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # output of the dynamic ROM. @@ -166,9 +160,8 @@ x.plot() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation is completed, you can close Twin Builder or release it. # All methods provide for saving the project before closing. From 3f527d17dda4a15bd76ca8218f7f732122d03160 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 07:43:14 -0600 Subject: [PATCH 38/56] MISC: Convert 07-Circuit to md format --- examples/07-Circuit/Circuit_AMI.py | 90 ++++++++----------- examples/07-Circuit/Circuit_Example.py | 82 +++++++---------- .../07-Circuit/Circuit_Siwave_Multizones.py | 81 +++++++---------- .../07-Circuit/Circuit_Subcircuit_Example.py | 41 ++++----- examples/07-Circuit/Circuit_Transient.py | 82 +++++++---------- examples/07-Circuit/Create_Netlist.py | 47 ++++------ 6 files changed, 173 insertions(+), 250 deletions(-) diff --git a/examples/07-Circuit/Circuit_AMI.py b/examples/07-Circuit/Circuit_AMI.py index 21045388daa..1e17d04e930 100644 --- a/examples/07-Circuit/Circuit_AMI.py +++ b/examples/07-Circuit/Circuit_AMI.py @@ -1,12 +1,9 @@ -""" -Circuit: AMI PostProcessing ----------------------------------- -This example shows how you can use PyAEDT to perform advanced postprocessing of AMI simulations. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: AMI PostProcessing +# +# This example shows how you can use PyAEDT to perform advanced postprocessing of AMI simulations. + +# # Perform required imports +# # Perform required imports and set the local path to the path for PyAEDT. # sphinx_gallery_thumbnail_path = 'Resources/spectrum_plot.png' @@ -21,16 +18,14 @@ temp_folder = pyaedt.generate_unique_folder_name() project_path = pyaedt.downloads.download_file("ami", "ami_usb.aedtz", temp_folder) -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktopVersion = "2023.2" -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -39,9 +34,8 @@ non_graphical = False NewThread = True -############################################################################### -# Launch AEDT with Circuit and enable Pandas as the output format -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit and enable Pandas as the output format +# # All outputs obtained with the `get_solution_data` method will have the Pandas format. # Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT # and starts the specified version in the specified mode. @@ -50,16 +44,14 @@ cir = pyaedt.Circuit(projectname=os.path.join(project_path), non_graphical=non_graphical, specified_version=desktopVersion, new_desktop_session=NewThread) -############################################################################### -# Solve AMI setup -# ~~~~~~~~~~~~~~~ +# ## Solve AMI setup +# # Solve the transient setup. cir.analyze() -############################################################################### -# Get AMI report -# ~~~~~~~~~~~~~~ +# ## Get AMI report +# # Get AMI report data plot_name = "WaveAfterProbe" @@ -71,16 +63,14 @@ original_data_sweep = original_data.primary_sweep_values print(original_data_value) -############################################################################### -# Plot data -# ~~~~~~~~~ +# ## Plot data +# # Create a plot based on solution data. fig = original_data.plot() -############################################################################### -# Sample WaveAfterProbe waveform using receiver clock -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Sample WaveAfterProbe waveform using receiver clock +# # Extract waveform at specific clock time plus half unit interval probe_name = "b_input_43" @@ -94,9 +84,8 @@ unit_interval=unit_interval, ignore_bits=ignore_bits, plot_type=plot_type) -############################################################################### -# Plot waveform and samples -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot waveform and samples +# # Create the plot from a start time to stop time in seconds tstop = 55e-9 @@ -140,9 +129,8 @@ ax.set_ylabel(original_data.units_data[plot_name]) plt.show() -############################################################################### -# Plot Slicer Scatter -# ~~~~~~~~~~~~~~~~~~~ +# ## Plot Slicer Scatter +# # Create the plot from a start time to stop time in seconds fig, ax2 = plt.subplots() @@ -152,9 +140,8 @@ ax2.set_ylabel("V") plt.show() -############################################################################### -# Plot scatter histogram -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot scatter histogram +# # Create the plot from a start time to stop time in seconds. fig, ax4 = plt.subplots() @@ -164,9 +151,8 @@ ax4.grid() plt.show() -############################################################################### -# Get Transient report -# ~~~~~~~~~~~~~~~~~~~~ +# ## Get Transient report +# # Get Transient report data plot_name = "V(b_input_43.int_ami_rx.eye_probe.out)" @@ -175,9 +161,8 @@ setup_sweep_name="NexximTransient", domain="Time", variations=cir.available_variations.nominal) -############################################################################### -# Sample waveform using a user-defined clock -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Sample waveform using a user-defined clock +# # Extract waveform at specific clock time plus half unit interval. original_data.enable_pandas_output = False @@ -197,9 +182,8 @@ pandas_enabled=False, ) -############################################################################### -# Plot waveform and samples -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Plot waveform and samples +# # Create the plot from a start time to stop time in seconds. tstop = 40.0e-9 @@ -248,9 +232,8 @@ ax.set_ylabel(waveform_unit) plt.show() -############################################################################### -# Plot slicer scatter -# ~~~~~~~~~~~~~~~~~~~ +# ## Plot slicer scatter +# # Create the plot from a start time to stop time in seconds. sample_waveform_array = np.array(sample_waveform) @@ -261,9 +244,8 @@ ax2.set_ylabel("V") plt.show() -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. cir.save_project() diff --git a/examples/07-Circuit/Circuit_Example.py b/examples/07-Circuit/Circuit_Example.py index 673c091f8b0..61593e44cd3 100644 --- a/examples/07-Circuit/Circuit_Example.py +++ b/examples/07-Circuit/Circuit_Example.py @@ -1,28 +1,24 @@ -""" -Circuit: schematic creation and analysis ----------------------------------------- -This example shows how you can use PyAEDT to create a circuit design -and run a Nexxim time-domain simulation. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: schematic creation and analysis +# +# This example shows how you can use PyAEDT to create a circuit design +# and run a Nexxim time-domain simulation. + +# ## Perform required imports +# # Perform required imports. # sphinx_gallery_thumbnail_path = 'Resources/circuit.png' import pyaedt -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktop_version = "2023.2" -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -31,50 +27,45 @@ non_graphical = False new_thread = True -############################################################################### -# Launch AEDT and Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT and Circuit +# # Launch AEDT and Circuit. The :class:`pyaedt.Desktop` class initializes AEDT and # starts the specified version in the specified mode. desktop = pyaedt.launch_desktop(desktop_version, non_graphical, new_thread) aedt_app = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name()) aedt_app.modeler.schematic.schematic_units = "mil" -############################################################################### -# Create circuit setup -# ~~~~~~~~~~~~~~~~~~~~ + +# ## Create circuit setup +# # Create and customize an LNA (linear network analysis) setup. setup1 = aedt_app.create_setup("MyLNA") setup1.props["SweepDefinition"]["Data"] = "LINC 0GHz 4GHz 10001" -############################################################################### -# Create components -# ~~~~~~~~~~~~~~~~~ +# ## Create components +# # Create components, such as an inductor, resistor, and capacitor. inductor = aedt_app.modeler.schematic.create_inductor(compname="L1", value=1e-9, location=[0, 0]) resistor = aedt_app.modeler.schematic.create_resistor(compname="R1", value=50, location=[500, 0]) capacitor = aedt_app.modeler.schematic.create_capacitor(compname="C1", value=1e-12, location=[1000, 0]) -############################################################################### -# Get all pins -# ~~~~~~~~~~~~ +# ## Get all pins +# # Get all pins of a specified component. pins_resistor = resistor.pins -############################################################################### -# Create port and ground -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create port and ground +# # Create a port and a ground, which are needed for the circuit analysis. port = aedt_app.modeler.components.create_interface_port(name="myport", location=[-200, 0] ) gnd = aedt_app.modeler.components.create_gnd(location=[1200, -100]) -############################################################################### -# Connect components -# ~~~~~~~~~~~~~~~~~~ +# ## Connect components +# # Connect components with wires. port.pins[0].connect_to_component(component_pin=inductor.pins[0], use_wire=True) @@ -82,26 +73,23 @@ resistor.pins[0].connect_to_component(component_pin=capacitor.pins[0], use_wire=True) capacitor.pins[1].connect_to_component(component_pin=gnd.pins[0], use_wire=True) -############################################################################### -# Create transient setup -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create transient setup +# # Create a transient setup. setup2 = aedt_app.create_setup(setupname="MyTransient", setuptype=aedt_app.SETUPS.NexximTransient) setup2.props["TransientData"] = ["0.01ns", "200ns"] setup3 = aedt_app.create_setup(setupname="MyDC", setuptype=aedt_app.SETUPS.NexximDC) -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. aedt_app.analyze_setup("MyLNA") aedt_app.export_fullwave_spice() -############################################################################### -# Create report -# ~~~~~~~~~~~~~ +# ## Create report +# # Create a report that plots solution data. solutions = aedt_app.post.get_solution_data( @@ -111,16 +99,14 @@ real, imag = solutions.full_matrix_real_imag print(real) -############################################################################### -# Plot data -# ~~~~~~~~~ +# ## Plot data +# # Create a plot based on solution data. fig = solutions.plot() -############################################################################### -# Close AEDT -# ~~~~~~~~~~ +# ## Close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/07-Circuit/Circuit_Siwave_Multizones.py b/examples/07-Circuit/Circuit_Siwave_Multizones.py index 25dc68945bf..97dd7d28005 100644 --- a/examples/07-Circuit/Circuit_Siwave_Multizones.py +++ b/examples/07-Circuit/Circuit_Siwave_Multizones.py @@ -1,21 +1,17 @@ -""" -Circuit: Simulate multi-zones layout with Siwave ------------------------------------------------- -This example shows how you can use PyAEDT simulate multi-zones with Siwave. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: Simulate multi-zones layout with Siwave +# +# This example shows how you can use PyAEDT simulate multi-zones with Siwave. + +# ## Perform required imports +# # Perform required imports, which includes importing a section. from pyaedt import Edb, Circuit import os.path import pyaedt -############################################################################### -# Download file -# ~~~~~~~~~~~~~ +# ## Download file +# # Download the AEDB file and copy it in the temporary folder. temp_folder = pyaedt.generate_unique_folder_name() @@ -27,84 +23,73 @@ print(edb_file) -############################################################################### -# AEDT version -# ~~~~~~~~~~~~ +# ## AEDT version +# # Sets the Aedt version to 2023 R2. edb_version = "2023.2" -##################################################################################### -# Ground net -# ~~~~~~~~~~ +# ## Ground net +# # Common reference net used across all sub-designs, Mandatory for this work flow. common_reference_net = "GND" -######################################################################################## -# Project load -# ~~~~~~~~~~~~ +# ## Project load +# # Load initial Edb file, checking if aedt file exists and remove to allow Edb loading. if os.path.isfile(aedt_file): os.remove(aedt_file) edb = Edb(edbversion=edb_version, edbpath=edb_file) -############################################################################### -# Project zones -# ~~~~~~~~~~~~~ +# ## Project zones +# # Copy project zone into sub project. edb_zones = edb.copy_zones(working_directory=working_directory) -############################################################################### -# Split zones -# ~~~~~~~~~~~ +# ## Split zones +# # Clip sub-designs along with corresponding zone definition # and create port of clipped signal traces. defined_ports, project_connexions = edb.cutout_multizone_layout(edb_zones, common_reference_net) -############################################################################################################# -# Circuit -# ~~~~~~~ +# ## Circuit +# # Create circuit design, import all sub-project as EM model and connect all corresponding pins in circuit. circuit = Circuit(specified_version=edb_version, projectname=circuit_project_file) circuit.connect_circuit_models_from_multi_zone_cutout(project_connections=project_connexions, edb_zones_dict=edb_zones, ports=defined_ports, model_inc=70) -############################################################################### -# Setup -# ~~~~~ +# ## Setup +# # Add Nexxim LNA simulation setup. circuit_setup= circuit.create_setup("Pyedt_LNA") -############################################################################### -# Frequency sweep -# ~~~~~~~~~~~~~~~ +# ## Frequency sweep +# # Add frequency sweep from 0GHt to 20GHz with 10NHz frequency step. circuit_setup.props["SweepDefinition"]["Data"] = "LIN {} {} {}".format("0GHz", "20GHz", "10MHz") -############################################################################### -# Start simulation -# ~~~~~~~~~~~~~~~~ +# ## Start simulation +# # Analyze all siwave projects and solves the circuit. circuit.analyze() -############################################################################### -# Define differential pairs -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define differential pairs +# circuit.set_differential_pair(diff_name="U0", positive_terminal="U0.via_38.B2B_SIGP", negative_terminal="U0.via_39.B2B_SIGN") circuit.set_differential_pair(diff_name="U1", positive_terminal="U1.via_32.B2B_SIGP", negative_terminal="U1.via_33.B2B_SIGN") -############################################################################### -# Plot results -# ~~~~~~~~~~~~ +# ## Plot results +# circuit.post.create_report(expressions=["dB(S(U0,U0))", "dB(S(U1,U0))"], context="Differential Pairs") -############################################################################### -# Release AEDT desktop -# ~~~~~~~~~~~~~~~~~~~~ +# ## Release AEDT desktop +# + circuit.release_desktop() \ No newline at end of file diff --git a/examples/07-Circuit/Circuit_Subcircuit_Example.py b/examples/07-Circuit/Circuit_Subcircuit_Example.py index ed2cffd087e..9e39a508363 100644 --- a/examples/07-Circuit/Circuit_Subcircuit_Example.py +++ b/examples/07-Circuit/Circuit_Subcircuit_Example.py @@ -1,29 +1,25 @@ -""" -Circuit: schematic subcircuit management ----------------------------------------- -This example shows how you can use PyAEDT to add a subcircuit to a circuit design. -It pushes down the child subcircuit and pops up to the parent design. -""" -########################################################## -# Perform required import -# ~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: schematic subcircuit management +# +# This example shows how you can use PyAEDT to add a subcircuit to a circuit design. +# It pushes down the child subcircuit and pops up to the parent design. + +# ## Perform required import +# # Perform the required import. import os import pyaedt -########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT 2023 R2 in graphical mode with Circuit. circuit = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name(), @@ -33,9 +29,8 @@ ) circuit.modeler.schematic_units = "mil" -############################################################################### -# Add subcircuit -# ~~~~~~~~~~~~~~ +# ## Add subcircuit +# # Add a new subcircuit to the previously created circuit design, creating a # child circuit. Push this child circuit down into the child subcircuit. @@ -43,9 +38,8 @@ subcircuit_name = subcircuit.composed_name circuit.push_down(subcircuit) -############################################################################### -# Parametrize subcircuit -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize subcircuit +# # Parametrize the subcircuit and add a resistor, inductor, and a capacitor with # the parameter values in the following code example. Connect them in series # and then use the ``pop_up`` # method to get back to the parent design. @@ -62,9 +56,8 @@ circuit.pop_up() -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. circuit.release_desktop(True, True) diff --git a/examples/07-Circuit/Circuit_Transient.py b/examples/07-Circuit/Circuit_Transient.py index 4f1f1fa994c..52d64b01b82 100644 --- a/examples/07-Circuit/Circuit_Transient.py +++ b/examples/07-Circuit/Circuit_Transient.py @@ -1,12 +1,11 @@ -""" -Circuit: transient analysis and eye plot ----------------------------------------- -This example shows how you can use PyAEDT to create a circuit design, -run a Nexxim time-domain simulation, and create an eye diagram. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: transient analysis and eye plot +# ---------------------------------------- +# This example shows how you can use PyAEDT to create a circuit design, +# run a Nexxim time-domain simulation, and create an eye diagram. + + +# ## Perform required imports +# # Perform required imports. import os @@ -14,18 +13,16 @@ import numpy as np import pyaedt -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode, ``"PYAEDT_NON_GRAPHICAL"`` is needed to generate # documentation only. # You can set ``non_graphical`` either to ``True`` or ``False``. non_graphical = False -############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT 2023 R2 in graphical mode with Circuit. cir = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name(), @@ -34,42 +31,37 @@ non_graphical=non_graphical ) -############################################################################### -# Read IBIS file -# ~~~~~~~~~~~~~~ +# ## Read IBIS file +# # Read an IBIS file and place a buffer in the schematic. ibis = cir.get_ibis_model_from_file(os.path.join(cir.desktop_install_dir, 'buflib', 'IBIS', 'u26a_800.ibs')) ibs = ibis.buffers["DQ_u26a_800"].insert(0, 0) -############################################################################### -# Place ideal transmission line -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Place ideal transmission line +# # Place an ideal transmission line in the schematic and parametrize it. tr1 = cir.modeler.components.components_catalog["Ideal Distributed:TRLK_NX"].place("tr1") tr1.parameters["P"] = "50mm" -############################################################################### -# Create resistor and ground -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create resistor and ground +# # Create a resistor and ground in the schematic. res = cir.modeler.components.create_resistor(compname="R1", value="1Meg") gnd1 = cir.modeler.components.create_gnd() -############################################################################### -# Connect elements -# ~~~~~~~~~~~~~~~~ +# ## Connect elements +# # Connect elements in the schematic. tr1.pins[0].connect_to_component(ibs.pins[0]) tr1.pins[1].connect_to_component(res.pins[0]) res.pins[1].connect_to_component(gnd1.pins[0]) -############################################################################### -# Place probe -# ~~~~~~~~~~~ +# ## Place probe +# # Place a probe and rename it to ``Vout``. pr1 = cir.modeler.components.components_catalog["Probes:VPROBE"].place("vout") @@ -79,18 +71,16 @@ pr2.parameters["Name"] = "Vin" pr2.pins[0].connect_to_component(ibs.pins[0]) -############################################################################### -# Create setup and analyze -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create setup and analyze +# # Create a transient analysis setup and analyze it. trans_setup = cir.create_setup(setupname="TransientRun", setuptype="NexximTransient") trans_setup.props["TransientData"] = ["0.01ns", "200ns"] cir.analyze_setup("TransientRun") -############################################################################### -# Create report outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create report outside AEDT +# # Create a report outside AEDT using the ``get_solution_data`` method. This # method allows you to get solution data and plot it outside AEDT without needing # a UI. @@ -101,9 +91,8 @@ solutions = cir.post.get_solution_data(domain="Time") solutions.plot("V(Vout)") -############################################################################### -# Create report inside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create report inside AEDT +# # Create a report inside AEDT using the ``new_report`` object. This object is # fully customizable and usable with most of the reports available in AEDT. # The standard report is the main one used in Circuit and Twin Builder. @@ -126,9 +115,8 @@ sol = new_report.get_solution_data() sol.plot() -############################################################################### -# Create eye diagram inside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create eye diagram inside AEDT +# # Create an eye diagram inside AEDT using the ``new_eye`` object. new_eye = cir.post.reports_by_category.eye_diagram("V(Vout)") @@ -136,9 +124,8 @@ new_eye.time_stop = "100ns" new_eye.create() -############################################################################### -# Create eye diagram outside AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create eye diagram outside AEDT +# # Create the same eye diagram outside AEDT using Matplotlib and the # ``get_solution_data`` method. @@ -168,9 +155,8 @@ plt.plot(cellst.T, cellsv.T, zorder=0) plt.show() -############################################################################### -# Release AEDT -# ~~~~~~~~~~~~ +# ## Release AEDT +# # Release AEDT. cir.save_project() cir.release_desktop() diff --git a/examples/07-Circuit/Create_Netlist.py b/examples/07-Circuit/Create_Netlist.py index e71a1de0778..15869a71adc 100644 --- a/examples/07-Circuit/Create_Netlist.py +++ b/examples/07-Circuit/Create_Netlist.py @@ -1,13 +1,10 @@ -""" -Circuit: netlist to schematic import ------------------------------------- -This example shows how you can import netlist data into a circuit design. -HSPICE files are fully supported. Mentor files are partially supported. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Circuit: netlist to schematic import +# +# This example shows how you can import netlist data into a circuit design. +# HSPICE files are fully supported. Mentor files are partially supported. + +# ## Perform required imports +# # Perform required imports and set paths. import os @@ -19,15 +16,13 @@ project_name = pyaedt.generate_unique_project_name() print(project_name) -############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktopVersion = "2023.2" -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``NewThread`` defines whether to create a new instance @@ -36,34 +31,30 @@ non_graphical = False NewThread = True -############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT # and starts it on the specified version in the specified graphical mode. desktop = pyaedt.launch_desktop(desktopVersion, non_graphical, NewThread) aedtapp = pyaedt.Circuit(projectname=project_name) -############################################################################### -# Define variable -# ~~~~~~~~~~~~~~~ +# ## Define variable +# # Define a design variable by using a ``$`` prefix. aedtapp["Voltage"] = "5" -############################################################################### -# Create schematic from netlist file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create schematic from netlist file +# # Create a schematic from a netlist file. The ``create_schematic_from_netlist`` # method reads the netlist file and parses it. All components are parsed # but only these categories are mapped: R, L, C, Q, U, J, V, and I. aedtapp.create_schematic_from_netlist(netlist) -############################################################################### -# Close project and release AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Close project and release AEDT +# # After adding any other desired functionalities, close the project and release # AEDT. From 1f326d8b8cb20c6a35868c250ecbd8e4357f94bf Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 11:16:50 -0600 Subject: [PATCH 39/56] MISC: Convert 07-EMIT to md format --- examples/07-EMIT/ComputeInterferenceType.py | 46 +++++----- examples/07-EMIT/ComputeProtectionLevels.py | 71 +++++++--------- examples/07-EMIT/EMIT_Example.py | 46 +++++----- examples/07-EMIT/EMIT_HFSS_Example.py | 51 +++++------ examples/07-EMIT/interference_gui.py | 93 ++++++++++----------- 5 files changed, 132 insertions(+), 175 deletions(-) diff --git a/examples/07-EMIT/ComputeInterferenceType.py b/examples/07-EMIT/ComputeInterferenceType.py index dfe37528795..c22d2452ab6 100644 --- a/examples/07-EMIT/ComputeInterferenceType.py +++ b/examples/07-EMIT/ComputeInterferenceType.py @@ -1,13 +1,11 @@ -""" -EMIT: Classify interference type --------------------------------- -This example shows how you can use PyAEDT to load an existing AEDT -project with an EMIT design and analyze the results to classify the -worst-case interference. -""" -############################################################################### +# # EMIT: Classify interference type +# # +# This example shows how you can use PyAEDT to load an existing AEDT +# project with an EMIT design and analyze the results to classify the +# worst-case interference. + # Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Perform required imports. import sys from pyaedt.emit_core.emit_constants import InterfererType, ResultType, TxRxMode @@ -44,9 +42,8 @@ def install(package): print("Warning: this example requires AEDT 2023.2 or later.") sys.exit() -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -58,8 +55,8 @@ def install(package): path_to_desktop_project = pyaedt.downloads.download_file("emit", "interference.aedtz") emitapp = Emit(non_graphical=False, new_desktop_session=False, projectname=path_to_desktop_project) -# Get all the radios in the project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get all the radios in the project +# # Get lists of all transmitters and receivers in the project. rev = emitapp.results.analyze() tx_interferer = InterfererType().TRANSMITTERS @@ -72,9 +69,8 @@ def install(package): sys.exit() -############################################################################### -# Classify the interference -# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Classify the interference +# # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Computes # which, if any, type of interference occurred. @@ -83,9 +79,8 @@ def install(package): all_colors=[] all_colors, power_matrix = rev.interference_type_classification(domain, use_filter = False, filter_list = []) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. @@ -93,9 +88,8 @@ def install(package): emitapp.save_project() emitapp.release_desktop() -############################################################################### -# Create a scenario matrix view -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create a scenario matrix view +# # Create a scenario matrix view with the transmitters defined across the top # and receivers down the left-most column. The power at the input to each # receiver is shown in each cell of the matrix and color-coded based on the @@ -145,9 +139,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): ) fig.show() -############################################################################### -# Generate a legend -# ~~~~~~~~~~~~~~~~~ +# ## Generate a legend +# # Define the interference types and colors used to display the results of # the analysis. @@ -183,6 +176,7 @@ def create_legend_table(): width = 600 ) fig.show() + if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": # Create a scenario view for all the interference types create_scenario_view(power_matrix, all_colors, tx_radios, rx_radios) diff --git a/examples/07-EMIT/ComputeProtectionLevels.py b/examples/07-EMIT/ComputeProtectionLevels.py index 84a8ac45379..5e2ca15fbb6 100644 --- a/examples/07-EMIT/ComputeProtectionLevels.py +++ b/examples/07-EMIT/ComputeProtectionLevels.py @@ -1,12 +1,10 @@ -""" -EMIT: Compute receiver protection levels ----------------------------------------- -This example shows how you can use PyAEDT to open an AEDT project with -an EMIT design and analyze the results to determine if the received -power at the input to each receiver exceeds the specified protection -levels. -""" -############################################################################### +# # EMIT: Compute receiver protection levels +# ---------------------------------------- +# This example shows how you can use PyAEDT to open an AEDT project with +# an EMIT design and analyze the results to determine if the received +# power at the input to each receiver exceeds the specified protection +# levels. + # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. @@ -36,9 +34,8 @@ def install(package): # Import required modules import plotly.graph_objects as go -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. ``"PYAEDT_NON_GRAPHICAL"``` is needed to generate # documentation only. # You can set ``non_graphical`` either to ``True`` or ``False``. @@ -49,9 +46,8 @@ def install(package): new_thread = True desktop_version = "2023.2" -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -62,9 +58,8 @@ def install(package): d = pyaedt.launch_desktop(desktop_version, non_graphical, new_thread) emitapp = Emit(pyaedt.generate_unique_project_name()) -############################################################################### -# Specify the protection levels -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Specify the protection levels +# # The protection levels are specified in dBm. # If the damage threshold is exceeded, permanent damage to the receiver front # end may occur. @@ -82,18 +77,16 @@ def install(package): protection_levels = [damage_threshold, overload_threshold, intermod_threshold, desense_threshold] -############################################################################### -# Create and connect EMIT components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create and connect EMIT components +# # Set up the scenario with radios connected to antennas. bluetooth, blue_ant = emitapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)", "Bluetooth") gps, gps_ant = emitapp.modeler.components.create_radio_antenna("GPS Receiver", "GPS") wifi, wifi_ant = emitapp.modeler.components.create_radio_antenna("WiFi - 802.11-2012", "WiFi") -############################################################################### -# Configure the radios -# ~~~~~~~~~~~~~~~~~~~~ +# ## Configure the radios +# # Enable the HR-DSSS bands for the Wi-Fi radio and set the power level # for all transmit bands to -20 dBm. @@ -132,16 +125,14 @@ def get_radio_node(radio_name): else: band.enabled=False -############################################################################### -# Load the results set -# ~~~~~~~~~~~~~~~~~~~~ +# ## Load the results set +# # Create a results revision and load it for analysis. rev = emitapp.results.analyze() -############################################################################### -# Generate a legend -# ~~~~~~~~~~~~~~~~~ +# ## Generate a legend +# # Define the thresholds and colors used to display the results of # the protection level analysis. @@ -177,9 +168,8 @@ def create_legend_table(): ) fig.show() -############################################################################### -# Create a scenario matrix view -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create a scenario matrix view +# # Create a scenario matrix view with the transmitters defined across the top # and receivers down the left-most column. The power at the input to each # receiver is shown in each cell of the matrix and color-coded based on the @@ -221,9 +211,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): ) fig.show() -############################################################################### -# Get all the radios in the project -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get all the radios in the project +# # Get lists of all transmitters and receivers in the project. if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": rev = emitapp.results.current_revision @@ -231,9 +220,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): tx_radios = rev.get_interferer_names(InterfererType.TRANSMITTERS) domain = emitapp.results.interaction_domain() -############################################################################### -# Classify the results -# ~~~~~~~~~~~~~~~~~~~~ +# ## Classify the results +# # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Computes # which, if any, protection levels are exceeded by these power levels. @@ -249,9 +237,8 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): # Create a legend for the protection levels create_legend_table() -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/07-EMIT/EMIT_Example.py b/examples/07-EMIT/EMIT_Example.py index 3de88c6148e..142ec1ba4e1 100644 --- a/examples/07-EMIT/EMIT_Example.py +++ b/examples/07-EMIT/EMIT_Example.py @@ -1,12 +1,10 @@ -""" -EMIT: antenna ---------------------- -This example shows how you can use PyAEDT to create a project in EMIT for -the simulation of an antenna. -""" -############################################################################### -# Perform required inputs -# ~~~~~~~~~~~~~~~~~~~~~~~ +# # EMIT: antenna +# +# This example shows how you can use PyAEDT to create a project in EMIT for +# the simulation of an antenna. + +# ## Perform required inputs +# # Perform required imports. # # sphinx_gallery_thumbnail_path = "Resources/emit_simple_cosite.png" @@ -16,9 +14,8 @@ from pyaedt.emit_core.emit_constants import TxRxMode, ResultType -############################################################################### -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The ``NewThread`` Boolean variable defines whether to create a new instance @@ -29,9 +26,8 @@ desktop_version = "2023.2" -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -39,9 +35,8 @@ aedtapp = pyaedt.Emit(pyaedt.generate_unique_project_name()) -############################################################################### -# Create and connect EMIT components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create and connect EMIT components +# # Create three radios and connect an antenna to each one. rad1 = aedtapp.modeler.components.create_component("New Radio") @@ -53,16 +48,14 @@ rad2, ant2 = aedtapp.modeler.components.create_radio_antenna("GPS Receiver") rad3, ant3 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)", "Bluetooth") -############################################################################### -# Define coupling among RF systems -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define coupling among RF systems +# # Define the coupling among the RF systems. This portion of the EMIT API is not # yet implemented. -############################################################################### -# Run EMIT simulation -# ~~~~~~~~~~~~~~~~~~~ +# ## Run EMIT simulation +# # Run the EMIT simulation. # # This part of the example requires Ansys AEDT 2023 R2. @@ -80,9 +73,8 @@ emi = worst.get_value(ResultType.EMI) print("Worst case interference is: {} dB".format(emi)) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the # :func:`pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. diff --git a/examples/07-EMIT/EMIT_HFSS_Example.py b/examples/07-EMIT/EMIT_HFSS_Example.py index 840d5b96f56..2b531404989 100644 --- a/examples/07-EMIT/EMIT_HFSS_Example.py +++ b/examples/07-EMIT/EMIT_HFSS_Example.py @@ -1,13 +1,11 @@ -""" -EMIT: HFSS to EMIT coupling ---------------------------- -This example shows how you can use PyAEDT to open an AEDT project with -an HFSS design, create an EMIT design in the project, and link the HFSS design -as a coupling link in the EMIT design. -""" -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # EMIT: HFSS to EMIT coupling +# +# This example shows how you can use PyAEDT to open an AEDT project with +# an HFSS design, create an EMIT design in the project, and link the HFSS design +# as a coupling link in the EMIT design. + +# ## Perform required imports +# # Perform required imports. # # sphinx_gallery_thumbnail_path = "Resources/emit_hfss.png" @@ -19,9 +17,8 @@ from pyaedt.generic.filesystem import Scratch from pyaedt.emit_core.emit_constants import TxRxMode, ResultType -############################################################################### -## Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -34,9 +31,8 @@ desktop_version = "2023.2" scratch_path = pyaedt.generate_unique_folder_name() -############################################################################### -# Launch AEDT with EMIT -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with EMIT +# # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. @@ -57,7 +53,6 @@ example_results_folder = os.path.join(example_dir, example_results) example_pdf = os.path.join(example_dir, example_pdf_file) -######################################################################################################## # If the ``Cell Phone RFT Defense`` example is not # in the installation directory, exit from this example. @@ -89,17 +84,15 @@ aedtapp = pyaedt.Emit(my_project) -############################################################################### -# Create and connect EMIT components -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create and connect EMIT components +# # Create two radios with antennas connected to each one. rad1, ant1 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)") rad2, ant2 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)") -############################################################################### -# Define coupling among RF systems -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Define coupling among RF systems +# # Define coupling among the RF systems. for link in aedtapp.couplings.linkable_design_names: @@ -108,9 +101,8 @@ for link in aedtapp.couplings.coupling_names: aedtapp.couplings.update_link(link) -############################################################################### -# Run EMIT simulation -# ~~~~~~~~~~~~~~~~~~~ +# ## Run EMIT simulation +# # Run the EMIT simulation. This portion of the EMIT API is not yet implemented. # # This part of the example requires Ansys AEDT 2023 R2. @@ -128,11 +120,10 @@ emi = worst.get_value(ResultType.EMI) print("Worst case interference is: {} dB".format(emi)) -############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.force_close_desktop` method. +# `pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. aedtapp.save_project() diff --git a/examples/07-EMIT/interference_gui.py b/examples/07-EMIT/interference_gui.py index cea3dfd1165..59982f14162 100644 --- a/examples/07-EMIT/interference_gui.py +++ b/examples/07-EMIT/interference_gui.py @@ -1,11 +1,9 @@ -""" -EMIT: Classify interference type GUI ----------------------------------------- -This example uses a GUI to open an AEDT project with -an EMIT design and analyze the results to classify the -worst-case interference. -""" -############################################################################### +# # EMIT: Classify interference type GUI +# ---------------------------------------- +# This example uses a GUI to open an AEDT project with +# an EMIT design and analyze the results to classify the +# worst-case interference. + # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. @@ -44,13 +42,16 @@ def install(package): from openpyxl.styles import PatternFill import openpyxl + # Uncomment if there are Qt plugin errors # import PySide6 # dirname = os.path.dirname(PySide6.__file__) # plugin_path = os.path.join(dirname, 'plugins', 'platforms') # os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path - +# # Launch EMIT + +# + non_graphical = False new_thread = True desktop = pyaedt.launch_desktop(emitapp_desktop_version, non_graphical, new_thread) @@ -64,6 +65,11 @@ def install(package): # Define .ui file for GUI ui_file = pyaedt.downloads.download_file("emit", "interference_gui.ui") Ui_MainWindow, _ = QtUiTools.loadUiType(ui_file) +# - + +# ### Create a simpl QT UI +# +# Build a simple UI using QT. class DoubleDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, decimals, values, max_power, min_power): @@ -104,9 +110,8 @@ def __init__(self): self.setupUi(self) self.setup_widgets() - ############################################################################### - # Setup widgets - # ~~~~~~~~~~~~~ + # ## Setup widgets + # # Define all widgets from the UI file, connect the widgets to functions, define # table colors, and format table settings. @@ -230,9 +235,8 @@ def setup_widgets(self): self.protection_legend_table.setItemDelegateForColumn(0, self.delegate) self.open_file_dialog() - ############################################################################### - # Open file dialog and select project - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Open file dialog and select project + # # Open the file dialog for project selection and populate the design dropdown # with all EMIT designs in the project. @@ -311,9 +315,8 @@ def open_file_dialog(self): self.protection_results_btn.setEnabled(True) self.interference_results_btn.setEnabled(True) - ############################################################################### - # Change design selection - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Change design selection + # # Refresh the warning messages when the selected design changes def design_dropdown_changed(self): @@ -332,9 +335,8 @@ def design_dropdown_changed(self): # clear the table if the design is changed self.clear_table() - ############################################################################### - # Enable radio specific protection levels - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Enable radio specific protection levels + # # Activate radio selection dropdown and initialize dictionary to store protection levels # when the radio-specific level dropdown is checked. @@ -355,9 +357,8 @@ def radio_specific(self): self.protection_levels['Global'] = values self.global_protection_level = not self.radio_specific_levels.isChecked() - ############################################################################### - # Update legend table - # ~~~~~~~~~~~~~~~~~~~ + # ### Update legend table + # # Update shown legend table values when the radio dropdown value changes. def radio_dropdown_changed(self): @@ -372,9 +373,8 @@ def radio_dropdown_changed(self): range(self.protection_legend_table.rowCount())] self.delegate.update_values(values) - ############################################################################### - # Save legend table values - # ~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Save legend table values + # # Save inputted radio protection level threshold values every time one is changed # in the legend table. @@ -389,9 +389,8 @@ def table_changed(self): self.protection_levels[index] = values self.delegate.update_values(values) - ############################################################################### - # Save scenario matrix to as PNG file - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Save scenario matrix to as PNG file + # # Save the scenario matrix table as a PNG file. def save_image(self): @@ -406,9 +405,8 @@ def save_image(self): table.render(image) image.save(fname) - ############################################################################### - # Save scenario matrix to Excel file - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Save scenario matrix to Excel file + # # Write the scenario matrix results to an Excel file with color coding. def save_results_excel(self): @@ -439,9 +437,8 @@ def save_results_excel(self): fill_type = "solid") workbook.save(fname) - ############################################################################### - # Run interference type simulation - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Run interference type simulation + # # Run interference type simulation and classify results. def interference_results(self): @@ -479,8 +476,8 @@ def interference_results(self): if self.tx_radios is None or self.rx_radios is None: return - # Classify the interference - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Classify the interference + # # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Compute # which, if any, type of interference occurred. @@ -491,9 +488,8 @@ def interference_results(self): self.emitapp.save_project() self.populate_table() - ############################################################################### - # Run protection level simulation - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Run protection level simulation + # # Run protection level simulation and classify results accroding to inputted # threshold levels. @@ -539,9 +535,8 @@ def protection_results(self): self.populate_table() - ############################################################################### - # Populate the scenario matrix - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ### Populate the scenario matrix + # # Create a scenario matrix view with the transmitters defined across the top # and receivers down the left-most column. @@ -591,9 +586,8 @@ def clear_table(self): table.setColumnCount(0) table.setRowCount(0) - ############################################################################### - # GUI closing event - # ~~~~~~~~~~~~~~~~~ + # ### GUI closing event + # # Close AEDT if the GUI is closed. def closeEvent(self, event): msg = QtWidgets.QMessageBox() @@ -606,9 +600,8 @@ def closeEvent(self, event): else: desktop.release_desktop(True, True) -############################################################################### -# Run GUI -# ~~~~~~~ +# ### Run GUI +# # Launch the GUI. If you want to run the GUI, uncomment the ``window.show()`` and # ``app.exec_()`` method calls. From 9910b166df754c8ef9793ab6d026ccb600aa43b4 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 11:27:33 -0600 Subject: [PATCH 40/56] MISC: Convert 07-TwinBuilder to md format --- .../07-TwinBuilder/01-RC_Circuit_Example.py | 63 +++++++---------- .../07-TwinBuilder/02-Wiring_A_Rectifier.py | 57 +++++++-------- ...-Dynamic_ROM_Creation_And_Visualization.py | 67 ++++++++---------- ...4-Static_ROM_Creation_And_Visualization.py | 69 +++++++++---------- 4 files changed, 109 insertions(+), 147 deletions(-) diff --git a/examples/07-TwinBuilder/01-RC_Circuit_Example.py b/examples/07-TwinBuilder/01-RC_Circuit_Example.py index 42b842b459a..ee71aef6118 100644 --- a/examples/07-TwinBuilder/01-RC_Circuit_Example.py +++ b/examples/07-TwinBuilder/01-RC_Circuit_Example.py @@ -1,20 +1,17 @@ -""" -Twin Builder: RC circuit design anaysis ---------------------------------------- -This example shows how you can use PyAEDT to create a Twin Builder design -and run a Twin Builder time-domain simulation. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: RC circuit design anaysis +# +# This example shows how you can use PyAEDT to create a Twin Builder design +# and run a Twin Builder time-domain simulation. + + +# ## Perform required imports +# # Perform required imports. import pyaedt -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select version and set launch options +# # Select the Twin Builder version and set the launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -28,9 +25,8 @@ non_graphical = False new_thread = True -############################################################################### -# Launch Twin Builder -# ~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup. @@ -41,9 +37,8 @@ ) tb.modeler.schematic_units = "mil" -############################################################################### -# Create components for RC circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create components for RC circuit +# # Create components for an RC circuit driven by a pulse voltage source. # Create components, such as a voltage source, resistor, and capacitor. @@ -51,16 +46,14 @@ resistor = tb.modeler.schematic.create_resistor("R1", 10000, [1000, 1000], 90) capacitor = tb.modeler.schematic.create_capacitor("C1", 1e-6, [2000, 0]) -############################################################################### -# Create ground -# ~~~~~~~~~~~~~ +# ## Create ground +# # Create a ground, which is needed for an analog analysis. gnd = tb.modeler.components.create_gnd([0, -1000]) -############################################################################### -# Connect components -# ~~~~~~~~~~~~~~~~~~ +# ## Connect components +# # Connects components with pins. source.pins[1].connect_to_component(resistor.pins[0]) @@ -68,24 +61,21 @@ capacitor.pins[1].connect_to_component(source.pins[0]) source.pins[0].connect_to_component(gnd.pins[0]) -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("300ms") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # voltage on the capacitor in the RC circuit. @@ -98,9 +88,8 @@ tb.save_project() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation completes, you can close Twin Builder or release it. # All methods provide for saving the project before closing. diff --git a/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py b/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py index bd7b1b45469..aeef5d39d0f 100644 --- a/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py +++ b/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py @@ -1,22 +1,18 @@ -""" -Twin Builder: wiring a rectifier with a capacitor filter ---------------------------------------------------------- -This example shows how you can use PyAEDT to create a Twin Builder design -and run a Twin Builder time-domain simulation. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: wiring a rectifier with a capacitor filter +# +# This example shows how you can use PyAEDT to create a Twin Builder design +# and run a Twin Builder time-domain simulation. + +# ## Perform required imports +# # Perform required imports. import math import matplotlib.pyplot as plt import pyaedt -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select version and set launch options +# # Select the Twin Builder version and set the launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -29,9 +25,8 @@ non_graphical = False new_thread = True -############################################################################### -# Launch Twin Builder -# ~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup. @@ -41,9 +36,8 @@ new_desktop_session=new_thread ) -############################################################################### -# Create components for bridge rectifier -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create components for bridge rectifier +# # Create components for a bridge rectifier with a capacitor filter. # Define the grid distance for ease in calculations. @@ -73,9 +67,8 @@ gnd = tb.modeler.components.create_gnd(location=[5 * G, -16 * G]) -############################################################################### -# Connect components -# ~~~~~~~~~~~~~~~~~~ +# ## Connect components +# # Connect components with wires. # Wire the diode bridge. @@ -104,24 +97,21 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("100ms") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # voltage on the capacitor in the RC circuit. @@ -139,9 +129,8 @@ plt.ylabel("AC to DC Conversion using Rectifier") plt.show() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation is completed, you can close Twin Builder or release it. # All methods provide for saving the project before closing. diff --git a/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py b/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py index 979bae7e9a8..719f2effbac 100644 --- a/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py +++ b/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py @@ -1,17 +1,15 @@ -""" -Twin Builder: dynamic ROM creation and simulation (2023 R2 beta) ----------------------------------------------------------------- -This example shows how you can use PyAEDT to create a dynamic ROM in Twin Builder -and run a Twin Builder time-domain simulation. - -.. note:: - This example uses functionality only available in Twin Builder 2023 R2 and later. - For 2023 R2, the build date must be 8/7/2022 or later. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: dynamic ROM creation and simulation (2023 R2 beta) +# +# This example shows how you can use PyAEDT to create a dynamic ROM in Twin Builder +# and run a Twin Builder time-domain simulation. +# +# > _Note:_ This example uses functionality only available in Twin +# > Builder 2023 R2 and later. +# > For 2023 R2, the build date must be 8/7/2022 or later. + + +# ## Perform required imports +# # Perform required imports. import os @@ -22,9 +20,9 @@ from pyaedt import generate_unique_folder_name from pyaedt import downloads from pyaedt import settings -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# ## Select version and set launch options +# # Select the Twin Builder version and set launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -37,9 +35,8 @@ non_graphical = False new_thread = True -############################################################################### -# Set up input data -# ~~~~~~~~~~~~~~~~~ +# ## Set up input data +# # Define needed file name source_snapshot_data_zipfilename = "Ex1_Mechanical_DynamicRom.zip" @@ -60,9 +57,8 @@ shutil.copyfile(os.path.join(source_data_folder ,source_build_conf_file), os.path.join(data_folder,source_build_conf_file)) -############################################################################### -# Launch Twin Builder and build ROM component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder and build ROM component +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup for building the dynamic ROM component. @@ -95,9 +91,8 @@ rom_manager.CreateROMComponent(dynamic_rom_path.replace('\\', '/'),'dynarom') -############################################################################### -# Create schematic -# ~~~~~~~~~~~~~~~~ +# ## Create schematic +# # Place components to create a schematic. # Define the grid distance for ease in calculations @@ -121,26 +116,23 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("1000s") tb.set_hmin("1s") tb.set_hmax("1s") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # output of the dynamic ROM. @@ -159,9 +151,8 @@ plt.show() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation is completed, you can close Twin Builder or release it. # All methods provide for saving the project before closing. diff --git a/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py b/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py index 10f78cc11a6..7c1978fbb3a 100644 --- a/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py +++ b/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py @@ -1,17 +1,14 @@ -""" -Twin Builder: static ROM creation and simulation (2023 R2 beta) ---------------------------------------------------------------- -This example shows how you can use PyAEDT to create a static ROM in Twin Builder -and run a Twin Builder time-domain simulation. - -.. note:: - This example uses functionality only available in Twin Builder 2023 R2 and later. - For 2023 R2, the build date must be 8/7/2022 or later. -""" - -############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# # Twin Builder: static ROM creation and simulation (2023 R2 beta) +# +# This example shows how you can use PyAEDT to create a static ROM in Twin Builder +# and run a Twin Builder time-domain simulation. +# +# > _Note:_ This example uses functionality only +# > available in Twin Builder 2023 R2 and later. +# > For 2023 R2, the build date must be 8/7/2022 or later. + +# ## Perform required imports +# # Perform required imports. import os @@ -24,9 +21,8 @@ from pyaedt import downloads from pyaedt import settings -############################################################################### -# Select version and set launch options -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Select version and set launch options +# # Select the Twin Builder version and set launch options. The following code # launches Twin Builder 2023 R2 in graphical mode. # @@ -38,9 +34,9 @@ desktop_version = "2023.2" non_graphical = False new_thread = True -############################################################################### -# Set up input data -# ~~~~~~~~~~~~~~~~~ + +# ## Set up input data +# # Define needed file name source_snapshot_data_zipfilename = "Ex1_Fluent_StaticRom.zip" @@ -64,18 +60,19 @@ shutil.copyfile(os.path.join(source_data_folder, source_props_conf_file), os.path.join(data_folder, source_props_conf_file)) -############################################################################### -# Launch Twin Builder and build ROM component -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch Twin Builder and build ROM component +# # Launch Twin Builder using an implicit declaration and add a new design with # a default setup for building the static ROM component. +# + tb = TwinBuilder(projectname=generate_unique_project_name(), specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=new_thread) # Switch the current desktop configuration and the schematic environment to "Twin Builder". # The Static ROM feature is only available with a twin builder license. # This and the restoring section at the end are not needed if the desktop is already configured as "Twin Builder". + current_desktop_config = tb._odesktop.GetDesktopConfiguration() current_schematic_environment = tb._odesktop.GetSchematicEnvironment() tb._odesktop.SetDesktopConfiguration("Twin Builder") @@ -98,10 +95,10 @@ # Create the ROM component definition in Twin Builder rom_manager.CreateROMComponent(static_rom_path.replace('\\', '/'), 'staticrom') +# - -############################################################################### -# Create schematic -# ~~~~~~~~~~~~~~~~ +# ## Create schematic +# # Place components to create a schematic. # Define the grid distance for ease in calculations @@ -128,26 +125,23 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -############################################################################### -# Parametrize transient setup -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Parametrize transient setup +# # Parametrize the default transient setup by setting the end time. tb.set_end_time("300s") tb.set_hmin("1s") tb.set_hmax("1s") -############################################################################### -# Solve transient setup -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Solve transient setup +# # Solve the transient setup. Skipping in case of documentation build. if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": tb.analyze_setup("TR") -############################################################################### -# Get report data and plot using Matplotlib -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Get report data and plot using Matplotlib +# # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # output of the dynamic ROM. @@ -166,9 +160,8 @@ x.plot() -############################################################################### -# Close Twin Builder -# ~~~~~~~~~~~~~~~~~~ +# ## Close Twin Builder +# # After the simulation is completed, you can close Twin Builder or release it. # All methods provide for saving the project before closing. From 52fdab55e15fd47ff2db17f5ee1f4cafb00385aa Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 17:23:41 -0600 Subject: [PATCH 41/56] MISC: Cleanup 07-TwinBuilder Examples Remove 01-RC_Circuit_Example.py as the same content is covered by 02-Wiring_A_Rectifier.py --- .../07-TwinBuilder/01-RC_Circuit_Example.py | 96 ------------------- .../07-TwinBuilder/02-Wiring_A_Rectifier.py | 73 ++++++++------ ...-Dynamic_ROM_Creation_And_Visualization.py | 22 +++-- ...4-Static_ROM_Creation_And_Visualization.py | 38 +++++--- 4 files changed, 85 insertions(+), 144 deletions(-) delete mode 100644 examples/07-TwinBuilder/01-RC_Circuit_Example.py diff --git a/examples/07-TwinBuilder/01-RC_Circuit_Example.py b/examples/07-TwinBuilder/01-RC_Circuit_Example.py deleted file mode 100644 index ee71aef6118..00000000000 --- a/examples/07-TwinBuilder/01-RC_Circuit_Example.py +++ /dev/null @@ -1,96 +0,0 @@ -# # Twin Builder: RC circuit design anaysis -# -# This example shows how you can use PyAEDT to create a Twin Builder design -# and run a Twin Builder time-domain simulation. - - -# ## Perform required imports -# -# Perform required imports. - -import pyaedt - -# ## Select version and set launch options -# -# Select the Twin Builder version and set the launch options. The following code -# launches Twin Builder 2023 R2 in graphical mode. -# -# You can change the Boolean parameter ``non_graphical`` to ``True`` to launch -# Twin Builder in non-graphical mode. You can also change the Boolean parameter -# ``new_thread`` to ``False`` to launch Twin Builder in an existing AEDT session -# if one is running. - -desktop_version = "2023.2" - -non_graphical = False -new_thread = True - -# ## Launch Twin Builder -# -# Launch Twin Builder using an implicit declaration and add a new design with -# a default setup. - -tb = pyaedt.TwinBuilder(projectname=pyaedt.generate_unique_project_name(), - specified_version=desktop_version, - non_graphical=non_graphical, - new_desktop_session=new_thread - ) -tb.modeler.schematic_units = "mil" - -# ## Create components for RC circuit -# -# Create components for an RC circuit driven by a pulse voltage source. -# Create components, such as a voltage source, resistor, and capacitor. - -source = tb.modeler.schematic.create_voltage_source("E1", "EPULSE", 10, 10, [0, 0]) -resistor = tb.modeler.schematic.create_resistor("R1", 10000, [1000, 1000], 90) -capacitor = tb.modeler.schematic.create_capacitor("C1", 1e-6, [2000, 0]) - -# ## Create ground -# -# Create a ground, which is needed for an analog analysis. - -gnd = tb.modeler.components.create_gnd([0, -1000]) - -# ## Connect components -# -# Connects components with pins. - -source.pins[1].connect_to_component(resistor.pins[0]) -resistor.pins[1].connect_to_component(capacitor.pins[0]) -capacitor.pins[1].connect_to_component(source.pins[0]) -source.pins[0].connect_to_component(gnd.pins[0]) - -# ## Parametrize transient setup -# -# Parametrize the default transient setup by setting the end time. - -tb.set_end_time("300ms") - -# ## Solve transient setup -# -# Solve the transient setup. - -tb.analyze_setup("TR") - - -# ## Get report data and plot using Matplotlib -# -# Get report data and plot it using Matplotlib. The following code gets and plots -# the values for the voltage on the pulse voltage source and the values for the -# voltage on the capacitor in the RC circuit. - -E_Value = "E1.V" -C_Value = "C1.V" - -x = tb.post.get_solution_data([E_Value, C_Value], "TR", "Time") -x.plot([E_Value, C_Value], xlabel="Time", ylabel="Capacitor Voltage vs Input Pulse") - -tb.save_project() - -# ## Close Twin Builder -# -# After the simulation completes, you can close Twin Builder or release it. -# All methods provide for saving the project before closing. - -tb.release_desktop() diff --git a/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py b/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py index aeef5d39d0f..f226aea4aec 100644 --- a/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py +++ b/examples/07-TwinBuilder/02-Wiring_A_Rectifier.py @@ -2,6 +2,8 @@ # # This example shows how you can use PyAEDT to create a Twin Builder design # and run a Twin Builder time-domain simulation. +# +# # ## Perform required imports # @@ -10,6 +12,8 @@ import math import matplotlib.pyplot as plt import pyaedt +import tempfile +import os # ## Select version and set launch options # @@ -24,13 +28,14 @@ desktop_version = "2023.2" non_graphical = False new_thread = True +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") # ## Launch Twin Builder # # Launch Twin Builder using an implicit declaration and add a new design with # a default setup. -tb = pyaedt.TwinBuilder(projectname=pyaedt.generate_unique_project_name(), +tb = pyaedt.TwinBuilder(projectname=os.path.join(temp_dir.name, "TB_Rectifier_Demo"), specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=new_thread @@ -38,32 +43,35 @@ # ## Create components for bridge rectifier # -# Create components for a bridge rectifier with a capacitor filter. - -# Define the grid distance for ease in calculations. +# Place components for a bridge rectifier and a capacitor filter in the schematic editor. +# +# Specify the grid spacing to use for placement +# of components in the schematic editor. Components are placed using the named +# argument ``location`` as a list of ``[x, y]`` values in mm. G = 0.00254 -# Create an AC sinosoidal source component. +# Create an AC sinosoidal voltage source. -source = tb.modeler.schematic.create_voltage_source("V_AC", "ESINE", 100, 50, [-1 * G, 0]) +source = tb.modeler.schematic.create_voltage_source("V_AC", "ESINE", 100, 50, location=[-1 * G, 0]) -# Create the four diodes of the bridge rectifier. +# Place the four diodes of the bridge rectifier. The named argument ``angle`` is the rotation angle +# of the component in radians. diode1 = tb.modeler.schematic.create_diode(compname="D1", location=[10 * G, 6 * G], angle=3 * math.pi / 2) diode2 = tb.modeler.schematic.create_diode(compname="D2", location=[20 * G, 6 * G], angle=3 * math.pi / 2) diode3 = tb.modeler.schematic.create_diode(compname="D3", location=[10 * G, -4 * G], angle=3 * math.pi / 2) diode4 = tb.modeler.schematic.create_diode(compname="D4", location=[20 * G, -4 * G], angle=3 * math.pi / 2) -# Create a capacitor filter. +# Place a capacitor filter. capacitor = tb.modeler.schematic.create_capacitor(compname="C_FILTER", value=1e-6, location=[29 * G, -10 * G]) -# Create a load resistor. +# Place a load resistor. resistor = tb.modeler.schematic.create_resistor(compname="RL", value=100000, location=[39 * G, -10 * G]) -# Create a ground. +# Place the ground component. gnd = tb.modeler.components.create_gnd(location=[5 * G, -16 * G]) @@ -71,24 +79,24 @@ # # Connect components with wires. -# Wire the diode bridge. +# Connect the diode pins to create the bridge. tb.modeler.schematic.create_wire(points_array=[diode1.pins[0].location, diode3.pins[0].location]) tb.modeler.schematic.create_wire(points_array=[diode2.pins[1].location, diode4.pins[1].location]) tb.modeler.schematic.create_wire(points_array=[diode1.pins[1].location, diode2.pins[0].location]) tb.modeler.schematic.create_wire(points_array=[diode3.pins[1].location, diode4.pins[0].location]) -# Wire the AC source. +# Connect the voltage source to the bridge. tb.modeler.schematic.create_wire(points_array=[source.pins[1].location, [0, 10 * G], [15 * G, 10 * G], [15 * G, 5 * G]]) tb.modeler.schematic.create_wire(points_array=[source.pins[0].location, [0, -10 * G], [15 * G, -10 * G], [15 * G, -5 * G]]) -# Wire the filter capacitor and load resistor. +# Connect the filter capacitor and load resistor. tb.modeler.schematic.create_wire(points_array=[resistor.pins[0].location, [40 * G, 0], [22 * G, 0]]) tb.modeler.schematic.create_wire(points_array=[capacitor.pins[0].location, [30 * G, 0]]) -# Wire the ground. +# Add the ground connection. tb.modeler.schematic.create_wire(points_array=[resistor.pins[1].location, [40 * G, -15 * G], gnd.pins[0].location]) tb.modeler.schematic.create_wire(points_array=[capacitor.pins[1].location, [30 * G, -15 * G]]) @@ -97,33 +105,33 @@ # Zoom to fit the schematic tb.modeler.zoom_to_fit() -# ## Parametrize transient setup +# The circuit schematic will now be visible in the Twin Builder schematic editor and should look like +# the image shown at the beginning of the example. # -# Parametrize the default transient setup by setting the end time. - -tb.set_end_time("100ms") - -# ## Solve transient setup +# ## Run the Simulation # -# Solve the transient setup. +# Update the total time to be simulated and run the analysis +tb.set_end_time("100ms") tb.analyze_setup("TR") - # ## Get report data and plot using Matplotlib # # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # voltage on the capacitor in the RC circuit. -E_Value = "V_AC.V" -x = tb.post.get_solution_data(E_Value, "TR", "Time") -plt.plot(x.intrinsics["Time"], x.data_real(E_Value)) - -R_Value = "RL.V" -x = tb.post.get_solution_data(R_Value, "TR", "Time") -plt.plot(x.intrinsics["Time"], x.data_real(R_Value)) +src_name = source.InstanceName + ".V" +x = tb.post.get_solution_data(src_name, tb.analysis_setup, "Time") +plt.plot(x.intrinsics["Time"], x.data_real(src_name)) +plt.grid() +plt.xlabel("Time") +plt.ylabel("AC Voltage") +plt.show() +r_voltage = resistor.InstanceName + ".V" +x = tb.post.get_solution_data(r_voltage, tb.analysis_setup, "Time") +plt.plot(x.intrinsics["Time"], x.data_real(r_voltage)) plt.grid() plt.xlabel("Time") plt.ylabel("AC to DC Conversion using Rectifier") @@ -135,3 +143,10 @@ # All methods provide for saving the project before closing. tb.release_desktop() + +# ## Cleanup +# +# Remove the project and temporary folder. The project files can be retrieved from the +# temporary directory, ``temp_dir.name``, prior to executing the following cell, if desired. + +temp_dir.cleanup() # Cleans up all files and removes the project directory. diff --git a/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py b/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py index 719f2effbac..840de7802e3 100644 --- a/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py +++ b/examples/07-TwinBuilder/03-Dynamic_ROM_Creation_And_Visualization.py @@ -1,11 +1,10 @@ -# # Twin Builder: dynamic ROM creation and simulation (2023 R2 beta) +# # Twin Builder: Dynamic ROM # # This example shows how you can use PyAEDT to create a dynamic ROM in Twin Builder # and run a Twin Builder time-domain simulation. # -# > _Note:_ This example uses functionality only available in Twin +# > **Note:** This example uses functionality only available in Twin # > Builder 2023 R2 and later. -# > For 2023 R2, the build date must be 8/7/2022 or later. # ## Perform required imports @@ -64,9 +63,18 @@ tb = TwinBuilder(projectname=generate_unique_project_name(),specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=new_thread) -# Switch the current desktop configuration and the schematic environment to "Twin Builder". -# The Dynamic ROM feature is only available with a twin builder license. -# This and the restoring section at the end are not needed if the desktop is already configured as "Twin Builder". +# ## Desktop Configuration +# +# > **Note:** Only run following cell if AEDT is not configured to run _"Twin Builder"_. +# > +# > The following cell configures Electronics Desktop (AEDT) and the schematic editor +# > to use the _"Twin Builder"_ confguration. +# > The dynamic ROM feature is only available with a Twin Builder license. +# > A cell at the end of this example restores the AEDT configuration. If your +# > environment is set up +# > to use the _"Twin Builder"_ configuration, you do not need to run these sections. +# > + current_desktop_config = tb._odesktop.GetDesktopConfiguration() current_schematic_environment = tb._odesktop.GetSchematicEnvironment() tb._odesktop.SetDesktopConfiguration("Twin Builder") @@ -94,7 +102,7 @@ # ## Create schematic # # Place components to create a schematic. - + # Define the grid distance for ease in calculations G = 0.00254 diff --git a/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py b/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py index 7c1978fbb3a..ab069e77ee1 100644 --- a/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py +++ b/examples/07-TwinBuilder/04-Static_ROM_Creation_And_Visualization.py @@ -1,11 +1,10 @@ -# # Twin Builder: static ROM creation and simulation (2023 R2 beta) +# # Twin Builder: Static ROM # # This example shows how you can use PyAEDT to create a static ROM in Twin Builder # and run a Twin Builder time-domain simulation. # -# > _Note:_ This example uses functionality only +# > **Note:** This example uses functionality only # > available in Twin Builder 2023 R2 and later. -# > For 2023 R2, the build date must be 8/7/2022 or later. # ## Perform required imports # @@ -37,20 +36,23 @@ # ## Set up input data # -# Define needed file name +# The following files will be downloaded along with the +# other project data used to run this example. source_snapshot_data_zipfilename = "Ex1_Fluent_StaticRom.zip" source_build_conf_file = "SROMbuild.conf" source_props_conf_file = "SROM_props.conf" -# Download data from example_data repository +# ## Download Example Data +# +# The following cell downloads the required files needed to run this example and extracts them in a local folder ``"Ex04"`` + +# + source_data_folder = downloads.download_twin_builder_data(source_snapshot_data_zipfilename, True) source_data_folder = downloads.download_twin_builder_data(source_build_conf_file, True) source_data_folder = downloads.download_twin_builder_data(source_props_conf_file, True) -# Uncomment the following line for local testing -# source_data_folder = "D:\\Scratch\\TempStatic" - +# Target folder to extract project files. data_folder = os.path.join(source_data_folder, "Ex04") # Unzip training data and config file @@ -59,25 +61,33 @@ os.path.join(data_folder, source_build_conf_file)) shutil.copyfile(os.path.join(source_data_folder, source_props_conf_file), os.path.join(data_folder, source_props_conf_file)) +# - # ## Launch Twin Builder and build ROM component # # Launch Twin Builder using an implicit declaration and add a new design with # a default setup for building the static ROM component. -# + tb = TwinBuilder(projectname=generate_unique_project_name(), specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=new_thread) -# Switch the current desktop configuration and the schematic environment to "Twin Builder". -# The Static ROM feature is only available with a twin builder license. -# This and the restoring section at the end are not needed if the desktop is already configured as "Twin Builder". +# ## Desktop Configuration +# +# > **Note:** Only run following cell if AEDT is not configured to run _"Twin Builder"_. +# > +# > The following cell configures Electronics Desktop (AEDT) and the schematic editor +# > to use the _"Twin Builder"_ confguration. +# > The Static ROM feature is only available with a Twin Builder license. +# > A cell at the end of this example restores the AEDT configuration. If your +# > environment is set up +# > to use the _"Twin Builder"_ configuration, you do not need to run these sections._ current_desktop_config = tb._odesktop.GetDesktopConfiguration() current_schematic_environment = tb._odesktop.GetSchematicEnvironment() tb._odesktop.SetDesktopConfiguration("Twin Builder") tb._odesktop.SetSchematicEnvironment(1) +# + # Get the static ROM builder object rom_manager = tb._odesign.GetROMManager() static_rom_builder = rom_manager.GetStaticROMBuilder() @@ -101,6 +111,7 @@ # # Place components to create a schematic. +# + # Define the grid distance for ease in calculations G = 0.00254 @@ -110,6 +121,7 @@ # Place two excitation sources source1 = tb.modeler.schematic.create_periodic_waveform_source(None, "SINE", 2.5, 0.01, 0, 7.5, 0, [20 * G, 29 * G]) source2 = tb.modeler.schematic.create_periodic_waveform_source(None, "SINE", 50, 0.02, 0, 450, 0, [20 * G, 25 * G]) +# - # Connect components with wires @@ -145,6 +157,8 @@ # Get report data and plot it using Matplotlib. The following code gets and plots # the values for the voltage on the pulse voltage source and the values for the # output of the dynamic ROM. + + if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": e_value = "ROM1.outfield_mode_1" x = tb.post.get_solution_data(e_value, "TR", "Time") From ca1567e57a8683a36d8affc2b192f9b5a3009ab6 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 17:25:54 -0600 Subject: [PATCH 42/56] MISC: Cleanup 07-TwinBuilder Examples Remove 01-RC_Circuit_Example.py as the same content is covered by 02-Wiring_A_Rectifier.py --- examples/07-TwinBuilder/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/07-TwinBuilder/index.rst b/examples/07-TwinBuilder/index.rst index 324d39b88cd..b511feabf0c 100644 --- a/examples/07-TwinBuilder/index.rst +++ b/examples/07-TwinBuilder/index.rst @@ -5,7 +5,6 @@ This includes schematic generation, setup, and postprocessing. .. nbgallery:: - 01-RC_Circuit_Example.py 02-Wiring_A_Rectifier.py 03-Dynamic_ROM_Creation_And_Visualization.py 04-Static_ROM_Creation_And_Visualization.py From b24b8829118ce634766e4c72e8d41bdb3c17bb9d Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 20:42:10 -0600 Subject: [PATCH 43/56] MISC: Cleanup 07-EMIT Examples Clean up formatting and readability. Move some images to the local examples _static folder. --- examples/07-EMIT/ComputeInterferenceType.py | 45 +++-- examples/07-EMIT/ComputeProtectionLevels.py | 46 +++-- examples/07-EMIT/EMIT_Example.py | 41 +++-- examples/07-EMIT/EMIT_HFSS_Example.py | 100 +++++----- examples/07-EMIT/_static/coupling.png | Bin 0 -> 4817 bytes .../07-EMIT/_static}/emit_hfss.png | Bin .../07-EMIT/_static}/emit_simple_cosite.png | Bin examples/07-EMIT/interference_gui.py | 174 +++++++++--------- 8 files changed, 228 insertions(+), 178 deletions(-) create mode 100644 examples/07-EMIT/_static/coupling.png rename {doc/source/Resources => examples/07-EMIT/_static}/emit_hfss.png (100%) rename {doc/source/Resources => examples/07-EMIT/_static}/emit_simple_cosite.png (100%) diff --git a/examples/07-EMIT/ComputeInterferenceType.py b/examples/07-EMIT/ComputeInterferenceType.py index c22d2452ab6..568af83ab44 100644 --- a/examples/07-EMIT/ComputeInterferenceType.py +++ b/examples/07-EMIT/ComputeInterferenceType.py @@ -1,12 +1,12 @@ -# # EMIT: Classify interference type -# # -# This example shows how you can use PyAEDT to load an existing AEDT -# project with an EMIT design and analyze the results to classify the +# # EMIT: Classify Interference Type +# +# This example shows how to load an existing AEDT EMIT +# design and analyze the results to classify the # worst-case interference. +# + # Perform required imports -# -# Perform required imports. + import sys from pyaedt.emit_core.emit_constants import InterfererType, ResultType, TxRxMode from pyaedt import Emit @@ -14,8 +14,16 @@ import os import pyaedt.generic.constants as consts import subprocess +# - -# Check to see which Python libraries have been installed +# ## Python Dependencies +# +# The followig cell can be run to make sure the ``plotly`` package is installed +# in the current Python environment. If ``plotly`` is installed there is no need +# to run this cell. + +# + +# Check to see which Python packages have been installed reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) installed_packages = [r.decode().split('==')[0] for r in reqs.split()] @@ -23,7 +31,8 @@ def install(package): subprocess.check_call([sys.executable, '-m', 'pip', 'install', package]) -# Install plotly library (if needed) to display legend and scenario matrix results (internet connection needed) +# Install plotly (if needed) to display legend and +# scenario matrix results (internet connection needed) required_packages = ['plotly'] for package in required_packages: if package not in installed_packages: @@ -31,12 +40,10 @@ def install(package): # Import plotly library import plotly.graph_objects as go +# - -# Define colors for tables -table_colors = {"green":'#7d73ca', "yellow":'#d359a2', "orange": '#ff6361', "red": '#ffa600', "white": '#ffffff'} -header_color = 'grey' +# Check that EMIT version 2023.2 or greater is installed. -# Check for if emit version is compatible desktop_version = "2023.2" if desktop_version <= "2023.1": print("Warning: this example requires AEDT 2023.2 or later.") @@ -49,15 +56,17 @@ def install(package): non_graphical = False new_thread = True -desktop = pyaedt.launch_desktop(desktop_version, non_graphical=non_graphical, new_desktop_session=new_thread) +desktop = pyaedt.launch_desktop(desktop_version, non_graphical=non_graphical, + new_desktop_session=new_thread) path_to_desktop_project = pyaedt.downloads.download_file("emit", "interference.aedtz") emitapp = Emit(non_graphical=False, new_desktop_session=False, projectname=path_to_desktop_project) -# ## Get all the radios in the project +# ## Get a List of Transmitters # # Get lists of all transmitters and receivers in the project. + rev = emitapp.results.analyze() tx_interferer = InterfererType().TRANSMITTERS rx_radios = rev.get_receiver_names() @@ -95,6 +104,12 @@ def install(package): # receiver is shown in each cell of the matrix and color-coded based on the # interference type. +# Set up colors to visualize results in a table. + +table_colors = {"green":'#7d73ca', "yellow":'#d359a2', "orange": '#ff6361', "red": '#ffa600', "white": '#ffffff'} +header_color = 'grey' + + def create_scenario_view(emis, colors, tx_radios, rx_radios): """Create a scenario matrix-like table with the higher received power for each Tx-Rx radio combination. The colors @@ -182,4 +197,4 @@ def create_legend_table(): create_scenario_view(power_matrix, all_colors, tx_radios, rx_radios) # Create a legend for the interference types - create_legend_table() \ No newline at end of file + create_legend_table() diff --git a/examples/07-EMIT/ComputeProtectionLevels.py b/examples/07-EMIT/ComputeProtectionLevels.py index 5e2ca15fbb6..a0938ceb2cc 100644 --- a/examples/07-EMIT/ComputeProtectionLevels.py +++ b/examples/07-EMIT/ComputeProtectionLevels.py @@ -1,15 +1,12 @@ # # EMIT: Compute receiver protection levels -# ---------------------------------------- -# This example shows how you can use PyAEDT to open an AEDT project with +# +# This example shows how to open an AEDT project with # an EMIT design and analyze the results to determine if the received # power at the input to each receiver exceeds the specified protection # levels. - -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Perform required imports. # -# sphinx_gallery_thumbnail_path = "Resources/emit_protection_levels.png" +# Perform required imports + import os import sys import subprocess @@ -17,7 +14,14 @@ from pyaedt import Emit from pyaedt.emit_core.emit_constants import TxRxMode, ResultType, InterfererType -# Check to see which Python libraries have been installed +# ## Python Dependencies +# +# The followig cell can be run to make sure the ``plotly`` package is installed +# in the current Python environment. If ``plotly`` is installed there is no need +# to run this cell. + +# + +# Check to see which Python packages have been installed reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) installed_packages = [r.decode().split('==')[0] for r in reqs.split()] @@ -33,6 +37,7 @@ def install(package): # Import required modules import plotly.graph_objects as go +# - # ## Set non-graphical mode # @@ -50,11 +55,12 @@ def install(package): # # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. +# +# Check that the correct version of EMIT is installed. if desktop_version <= "2023.1": print("Warning: this example requires AEDT 2023.2 or later.") sys.exit() - d = pyaedt.launch_desktop(desktop_version, non_graphical, new_thread) emitapp = Emit(pyaedt.generate_unique_project_name()) @@ -69,6 +75,7 @@ def install(package): # Exceeding the desense threshold reduces the signal-to-noise ratio and can # reduce the maximum range, maximum bandwidth, and/or the overall link quality. +# + header_color = 'grey' damage_threshold = 30 overload_threshold = -4 @@ -76,6 +83,7 @@ def install(package): desense_threshold = -104 protection_levels = [damage_threshold, overload_threshold, intermod_threshold, desense_threshold] +# - # ## Create and connect EMIT components # @@ -90,24 +98,30 @@ def install(package): # Enable the HR-DSSS bands for the Wi-Fi radio and set the power level # for all transmit bands to -20 dBm. +# + bands = wifi.bands() for band in bands: if "HR-DSSS" in band.node_name: if "Ch 1-13" in band.node_name: band.enabled=True band.set_band_power_level(-20) - + # Reduce the bluetooth transmit power bands = bluetooth.bands() for band in bands: band.set_band_power_level(-20) + +# - + def get_radio_node(radio_name): """Get the radio node that matches the - given radio name. - Arguments: - radio_name: String name of the radio. - Returns: Instance of the radio. + given radio name. + + Arguments: + radio_name: String name of the radio. + + Returns: Instance of the radio. """ if gps.name == radio_name: radio = gps @@ -214,6 +228,7 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): # ## Get all the radios in the project # # Get lists of all transmitters and receivers in the project. + if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": rev = emitapp.results.current_revision rx_radios = rev.get_receiver_names() @@ -225,6 +240,7 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Computes # which, if any, protection levels are exceeded by these power levels. + if os.getenv("PYAEDT_DOC_GENERATION", "False") != "1": power_matrix=[] all_colors=[] @@ -240,7 +256,7 @@ def create_scenario_view(emis, colors, tx_radios, rx_radios): # ## Save project and close AEDT # # After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.force_close_desktop` method. +# `pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. emitapp.save_project() diff --git a/examples/07-EMIT/EMIT_Example.py b/examples/07-EMIT/EMIT_Example.py index 142ec1ba4e1..1b83b4199de 100644 --- a/examples/07-EMIT/EMIT_Example.py +++ b/examples/07-EMIT/EMIT_Example.py @@ -1,16 +1,17 @@ -# # EMIT: antenna +# # EMIT: Antenna # -# This example shows how you can use PyAEDT to create a project in EMIT for -# the simulation of an antenna. +# This example shows how to create a project in EMIT for +# the simulation of an antenna using HFSS. +# +# # ## Perform required inputs # # Perform required imports. -# -# sphinx_gallery_thumbnail_path = "Resources/emit_simple_cosite.png" import os import pyaedt +import tempfile from pyaedt.emit_core.emit_constants import TxRxMode, ResultType @@ -28,11 +29,13 @@ # ## Launch AEDT with EMIT # -# Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it -# on the specified version and in the specified graphical mode. +# Launch AEDT with EMIT. The ``launch_desktop()`` method initializes AEDT +# using the specified version. The 2nd argument can be set to ``True`` to +# run AEDT in non-graphical mode. +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") d = pyaedt.launch_desktop(desktop_version, non_graphical, NewThread) -aedtapp = pyaedt.Emit(pyaedt.generate_unique_project_name()) +aedtapp = pyaedt.Emit(os.path.join(temp_dir.name, "antenna_cosite")) # ## Create and connect EMIT components @@ -44,14 +47,21 @@ if rad1 and ant1: ant1.move_and_connect_to(rad1) -# Convenience method to create a radio and antenna connected together +# ## Place Antenna Radio Pairs +# +# The method ``create_radio_antenna()`` places radio/antenna pair. The first +# argument is the type of radio. The 2nd argumnt is the name that +# will be assigned to the radio. + rad2, ant2 = aedtapp.modeler.components.create_radio_antenna("GPS Receiver") rad3, ant3 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)", "Bluetooth") -# ## Define coupling among RF systems +# ## Define the RF Environment # -# Define the coupling among the RF systems. This portion of the EMIT API is not -# yet implemented. +# The next step in this workflow is to specify the RF coupling among antennas. +# This functionality yet to be implemented in the API, but can be entered from the UI. +# +# # ## Run EMIT simulation @@ -76,8 +86,13 @@ # ## Save project and close AEDT # # After the simulation completes, you can close AEDT or release it using the -# :func:`pyaedt.Desktop.force_close_desktop` method. +# `pyaedt.Desktop.force_close_desktop` method. # All methods provide for saving the project before closing. aedtapp.save_project() aedtapp.release_desktop(close_projects=True, close_desktop=True) + +# Clean up the temporary directory and remove the project files located +# in ``temp_dir.name``. + +temp_dir.cleanup() diff --git a/examples/07-EMIT/EMIT_HFSS_Example.py b/examples/07-EMIT/EMIT_HFSS_Example.py index 2b531404989..a4726ee1671 100644 --- a/examples/07-EMIT/EMIT_HFSS_Example.py +++ b/examples/07-EMIT/EMIT_HFSS_Example.py @@ -1,21 +1,22 @@ # # EMIT: HFSS to EMIT coupling # -# This example shows how you can use PyAEDT to open an AEDT project with -# an HFSS design, create an EMIT design in the project, and link the HFSS design -# as a coupling link in the EMIT design. +# This example +# demonstrates how link an HFSS design +# to EMIT and model RF interference among various components. +# +# > **Note:** This example uses the ``Cell Phone RFI Desense`` +# > project that is available with the AEDT installation in the +# > folder ``\Examples\EMIT\`` # ## Perform required imports # # Perform required imports. -# -# sphinx_gallery_thumbnail_path = "Resources/emit_hfss.png" import os - -# Import required modules import pyaedt -from pyaedt.generic.filesystem import Scratch +import tempfile from pyaedt.emit_core.emit_constants import TxRxMode, ResultType +import shutil # ## Set non-graphical mode # @@ -23,66 +24,59 @@ # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance # of AEDT or try to connect to an existing instance of it. -# -# The following code uses AEDT 2023 R2. non_graphical = False NewThread = True desktop_version = "2023.2" -scratch_path = pyaedt.generate_unique_folder_name() # ## Launch AEDT with EMIT # # Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it # on the specified version and in the specified graphical mode. +# A temporary working directory is created using ``tempfile``. d = pyaedt.launch_desktop(desktop_version, non_graphical, NewThread) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") -temp_folder = os.path.join(scratch_path, ("EmitHFSSExample")) -if not os.path.exists(temp_folder): - os.mkdir(temp_folder) - -example_name = "Cell Phone RFI Desense" -example_aedt = example_name + ".aedt" -example_results = example_name + ".aedtresults" -example_lock = example_aedt + ".lock" -example_pdf_file = example_name + " Example.pdf" - -example_dir = os.path.join(d.install_path, "Examples\\EMIT") -example_project = os.path.join(example_dir, example_aedt) -example_results_folder = os.path.join(example_dir, example_results) -example_pdf = os.path.join(example_dir, example_pdf_file) +# ## Copy Example Files +# +# Copy the ``Cell Phone RFT Defense`` example data from the +# installed examples folder to the temporary working +# directory. +# +# > **Note:** The HFSS design from the installed example +# > used to model the RF environment +# > has been pre-solved. Hence, the results folder is copied and +# > the RF interference between transcievers is calculated in EMIT using +# > results from the linked HFSS design. +# +# The following lambda functions help create file and folder +# names when copying data from the Examples folder. -# If the ``Cell Phone RFT Defense`` example is not -# in the installation directory, exit from this example. +file_name = lambda s: s + ".aedt" +results_name = lambda s: s + ".aedtresults" +pdf_name = lambda s: s + " Example.pdf" -if not os.path.exists(example_project): - msg = """ - Cell phone RFT Desense example file is not in the - Examples/EMIT directory under the EDT installation. You cannot run this example. - """ - print(msg) - d.release_desktop(True, True) - exit() +# Build the names of the source files for this example. -my_project = os.path.join(temp_folder, example_aedt) -my_results_folder = os.path.join(temp_folder, example_results) -my_project_lock = os.path.join(temp_folder, example_lock) -my_project_pdf = os.path.join(temp_folder, example_pdf_file) +example = "Cell Phone RFI Desense" +example_dir = os.path.join(d.install_path, "Examples\\EMIT") +example_project = os.path.join(example_dir, file_name(example)) +example_results_folder = os.path.join(example_dir, results_name(example)) +example_pdf = os.path.join(example_dir, pdf_name(example)) -if os.path.exists(my_project): - os.remove(my_project) +# Copy the files to the temporary working directory. -if os.path.exists(my_project_lock): - os.remove(my_project_lock) +project_name = shutil.copyfile(example_project, + os.path.join(temp_dir.name, file_name(example))) +results_folder = shutil.copytree(example_results_folder, + os.path.join(temp_dir.name, results_name(example))) +project_pdf = shutil.copyfile(example_pdf, + os.path.join(temp_dir.name, pdf_name(example))) -with Scratch(scratch_path) as local_scratch: - local_scratch.copyfile(example_project, my_project) - local_scratch.copyfolder(example_results_folder, my_results_folder) - if os.path.exists(example_pdf): - local_scratch.copyfile(example_pdf, my_project_pdf) +# Open the project in the working directory. -aedtapp = pyaedt.Emit(my_project) +aedtapp = pyaedt.Emit(project_name) # ## Create and connect EMIT components # @@ -97,11 +91,13 @@ for link in aedtapp.couplings.linkable_design_names: aedtapp.couplings.add_link(link) + print("linked \"" + link + "\".") for link in aedtapp.couplings.coupling_names: aedtapp.couplings.update_link(link) + print("linked \"" + link + "\".") -# ## Run EMIT simulation +# ## Calculate RF Interference # # Run the EMIT simulation. This portion of the EMIT API is not yet implemented. # @@ -120,7 +116,7 @@ emi = worst.get_value(ResultType.EMI) print("Worst case interference is: {} dB".format(emi)) -# ## Save project and close AEDT +# ## Save and Close the Project # # After the simulation completes, you can close AEDT or release it using the # `pyaedt.Desktop.force_close_desktop` method. @@ -128,3 +124,5 @@ aedtapp.save_project() aedtapp.release_desktop(close_projects=True, close_desktop=True) + +temp_dir.cleanup() # Remove temporary project files. diff --git a/examples/07-EMIT/_static/coupling.png b/examples/07-EMIT/_static/coupling.png new file mode 100644 index 0000000000000000000000000000000000000000..865600ccd780cf90458aec8464005918ce02cb8a GIT binary patch literal 4817 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 Y(sk&aFc!qgZzw?PmY!y@x@GWx0W-5AH~;_u literal 0 HcmV?d00001 diff --git a/doc/source/Resources/emit_hfss.png b/examples/07-EMIT/_static/emit_hfss.png similarity index 100% rename from doc/source/Resources/emit_hfss.png rename to examples/07-EMIT/_static/emit_hfss.png diff --git a/doc/source/Resources/emit_simple_cosite.png b/examples/07-EMIT/_static/emit_simple_cosite.png similarity index 100% rename from doc/source/Resources/emit_simple_cosite.png rename to examples/07-EMIT/_static/emit_simple_cosite.png diff --git a/examples/07-EMIT/interference_gui.py b/examples/07-EMIT/interference_gui.py index 59982f14162..5ca3cb4a977 100644 --- a/examples/07-EMIT/interference_gui.py +++ b/examples/07-EMIT/interference_gui.py @@ -1,11 +1,11 @@ # # EMIT: Classify interference type GUI -# ---------------------------------------- +# # This example uses a GUI to open an AEDT project with # an EMIT design and analyze the results to classify the # worst-case interference. +# +# > **Note:** This example requires EMIT Version 2023.2 or newer. -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. import sys @@ -17,43 +17,45 @@ import subprocess import pyaedt.generic.constants as consts -# Check that emit is a compatible version -emitapp_desktop_version = "2023.2" -if emitapp_desktop_version < "2023.2": - print("Must have v2023.2 or later") - sys.exit() +# Check to see which Python libraries have been installed. +# PySide6 and openpyxl will be installed if they are not found. An internet connection +# is required to install missing packages. -# Check to see which Python libraries have been installed +# + reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) installed_packages = [r.decode().split('==')[0] for r in reqs.split()] -# Install required packages if they are not installed def install(package): subprocess.check_call([sys.executable, '-m', 'pip', 'install', package]) -# Install required libraries for GUI and Excel exporting (internet connection needed) required_packages = ['PySide6', 'openpyxl'] for package in required_packages: if package not in installed_packages: install(package) +# - # Import PySide6 and openpyxl libraries + + from PySide6 import QtWidgets, QtUiTools, QtGui, QtCore from openpyxl.styles import PatternFill import openpyxl +# + # Uncomment if there are Qt plugin errors # import PySide6 # dirname = os.path.dirname(PySide6.__file__) # plugin_path = os.path.join(dirname, 'plugins', 'platforms') # os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path -# +# - + # Launch EMIT # + non_graphical = False new_thread = True +emitapp_desktop_version = "2023.2" desktop = pyaedt.launch_desktop(emitapp_desktop_version, non_graphical, new_thread) # Add emitapi to system path @@ -110,24 +112,24 @@ def __init__(self): self.setupUi(self) self.setup_widgets() - # ## Setup widgets - # - # Define all widgets from the UI file, connect the widgets to functions, define - # table colors, and format table settings. - - def setup_widgets(self): +# ## Setup widgets +# +# Define all widgets from the UI file, connect the widgets to functions, define +# table colors, and format table settings. + + def setup_widgets(self): # Widget definitions for file selection/tab management self.file_select_btn = self.findChild(QtWidgets.QToolButton, "file_select_btn") self.file_path_box = self.findChild(QtWidgets.QLineEdit, "file_path_box") self.design_name_dropdown = self.findChild(QtWidgets.QComboBox, "design_name_dropdown") self.design_name_dropdown.setEnabled(True) self.tab_widget = self.findChild(QtWidgets.QTabWidget, "tab_widget") - + # Widget definitions for protection level classification self.protection_results_btn = self.findChild(QtWidgets.QPushButton, "protection_results_btn") self.protection_matrix = self.findChild(QtWidgets.QTableWidget, "protection_matrix") self.protection_legend_table = self.findChild(QtWidgets.QTableWidget, "protection_legend_table") - + self.damage_check = self.findChild(QtWidgets.QCheckBox, "damage_check") self.overload_check = self.findChild(QtWidgets.QCheckBox, "overload_check") self.intermodulation_check = self.findChild(QtWidgets.QCheckBox, "intermodulation_check") @@ -136,7 +138,7 @@ def setup_widgets(self): self.radio_specific_levels = self.findChild(QtWidgets.QCheckBox, "radio_specific_levels") self.radio_dropdown = self.findChild(QtWidgets.QComboBox, "radio_dropdown") self.protection_save_img_btn = self.findChild(QtWidgets.QPushButton, 'protection_save_img_btn') - + # warning label self.warning_label = self.findChild(QtWidgets.QLabel, "warnings") myFont = QtGui.QFont() @@ -144,7 +146,7 @@ def setup_widgets(self): self.warning_label.setFont(myFont) self.warning_label.setHidden(True) self.design_name_dropdown.currentIndexChanged.connect(self.design_dropdown_changed) - + # Setup for protection level buttons and table self.protection_results_btn.setEnabled(False) self.protection_export_btn.setEnabled(False) @@ -167,18 +169,18 @@ def setup_widgets(self): self.radio_dropdown.currentIndexChanged.connect(self.radio_dropdown_changed) self.protection_legend_table.itemChanged.connect(self.table_changed) self.protection_save_img_btn.clicked.connect(self.save_image) - + # Widget definitions for interference type self.interference_results_btn = self.findChild(QtWidgets.QPushButton, "interference_results_btn") self.interference_matrix = self.findChild(QtWidgets.QTableWidget, "interference_matrix") self.interference_legend_table = self.findChild(QtWidgets.QTableWidget, "interference_legend_table") - + # set the items read only for i in range(0, self.interference_legend_table.rowCount()): item = self.interference_legend_table.item(i, 0) item.setFlags(QtCore.Qt.ItemIsEnabled) self.interference_legend_table.setItem(i, 0, item) - + self.in_in_check = self.findChild(QtWidgets.QCheckBox, "in_in_check") self.in_out_check = self.findChild(QtWidgets.QCheckBox, "in_out_check") self.out_in_check = self.findChild(QtWidgets.QCheckBox, "out_in_check") @@ -200,7 +202,7 @@ def setup_widgets(self): self.out_out_check.stateChanged.connect(self.interference_results) self.radio_specific_levels.stateChanged.connect(self.radio_specific) self.interference_save_img_btn.clicked.connect(self.save_image) - + # Color definition dictionary and previous project/design names self.color_dict = {"green": [QtGui.QColor(125, 115, 202),'#7d73ca'], "yellow":[QtGui.QColor(211, 89, 162), '#d359a2'], @@ -209,48 +211,49 @@ def setup_widgets(self): "white": [QtGui.QColor("white"),'#ffffff']} self.previous_design = '' self.previous_project = '' - + # Set the legend tables to strech resize mode header = self.protection_legend_table.horizontalHeader() v_header = self.protection_legend_table.verticalHeader() - + header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeMode.Stretch) - + header = self.interference_legend_table.horizontalHeader() v_header = self.interference_legend_table.verticalHeader() - + header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch) v_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeMode.Stretch) - + # Input validation for protection level legend table self.delegate = DoubleDelegate(decimals=2, values=values, max_power=1000, min_power=-200) self.protection_legend_table.setItemDelegateForColumn(0, self.delegate) self.open_file_dialog() + - # ### Open file dialog and select project - # - # Open the file dialog for project selection and populate the design dropdown - # with all EMIT designs in the project. +# ### Open File Dialog +# +# Open the file dialog for project selection and populate the design dropdown +# with all EMIT designs in the project. def open_file_dialog(self): fname, _filter = QtWidgets.QFileDialog.getOpenFileName(self, "Select EMIT Project", "", "Ansys Electronics Desktop Files (*.aedt)", ) if fname: self.file_path_box.setText(fname) - + # Close previous project and open specified one if self.emitapp is not None: self.emitapp.close_project() self.emitapp = None desktop_proj = desktop.load_project(self.file_path_box.text()) - + # check for an empty project (i.e. no designs) if isinstance(desktop_proj, bool): self.file_path_box.setText("") @@ -261,7 +264,7 @@ def open_file_dialog(self): "one EMIT design. See AEDT log for more information.") x = msg.exec() return - + # Check if project is already open if desktop_proj.lock_file == None: msg = QtWidgets.QMessageBox() @@ -280,7 +283,7 @@ def open_file_dialog(self): design_type = desktop.design_type(desktop_proj.project_name, d) if design_type == "EMIT": emit_designs.append(d) - + # add warning if no EMIT design # Note: this should never happen since loading a project without an EMIT design # should add a blank EMIT design @@ -289,20 +292,20 @@ def open_file_dialog(self): self.warning_label.setText("Warning: The project must contain at least one EMIT design.") self.warning_label.setHidden(False) return - + self.populating_dropdown = True self.design_name_dropdown.addItems(emit_designs) self.populating_dropdown = False self.emitapp = get_pyaedt_app(desktop_proj.project_name, emit_designs[0]) self.design_name_dropdown.setCurrentIndex(0) - + # check for at least 2 radios radios = self.emitapp.modeler.components.get_radios() self.warning_label.setHidden(True) if len(radios) < 2: self.warning_label.setText("Warning: The selected design must contain at least two radios.") self.warning_label.setHidden(False) - + if self.radio_specific_levels.isEnabled(): self.radio_specific_levels.setChecked(False) self.radio_dropdown.clear() @@ -310,14 +313,14 @@ def open_file_dialog(self): self.protection_levels = {} values = [float(self.protection_legend_table.item(row, 0).text()) for row in range(self.protection_legend_table.rowCount())] self.protection_levels['Global'] = values - + self.radio_specific_levels.setEnabled(True) self.protection_results_btn.setEnabled(True) self.interference_results_btn.setEnabled(True) - # ### Change design selection - # - # Refresh the warning messages when the selected design changes +# ### Change design selection +# +# Refresh the warning messages when the selected design changes def design_dropdown_changed(self): if self.populating_dropdown: @@ -335,10 +338,10 @@ def design_dropdown_changed(self): # clear the table if the design is changed self.clear_table() - # ### Enable radio specific protection levels - # - # Activate radio selection dropdown and initialize dictionary to store protection levels - # when the radio-specific level dropdown is checked. +# ### Enable radio specific protection levels +# +# Activate radio selection dropdown and initialize dictionary to store protection levels +# when the radio-specific level dropdown is checked. def radio_specific(self): self.radio_dropdown.setEnabled(self.radio_specific_levels.isChecked()) @@ -356,10 +359,11 @@ def radio_specific(self): values = [float(self.protection_legend_table.item(row, 0).text()) for row in range(self.protection_legend_table.rowCount())] self.protection_levels['Global'] = values self.global_protection_level = not self.radio_specific_levels.isChecked() - - # ### Update legend table - # - # Update shown legend table values when the radio dropdown value changes. + + +# ### Update legend table +# +# Update shown legend table values when the radio dropdown value changes. def radio_dropdown_changed(self): if self.radio_dropdown.isEnabled(): @@ -373,10 +377,10 @@ def radio_dropdown_changed(self): range(self.protection_legend_table.rowCount())] self.delegate.update_values(values) - # ### Save legend table values - # - # Save inputted radio protection level threshold values every time one is changed - # in the legend table. +# ### Save legend table values +# +# Save inputted radio protection level threshold values every time one is changed +# in the legend table. def table_changed(self): if self.changing == False: @@ -389,9 +393,9 @@ def table_changed(self): self.protection_levels[index] = values self.delegate.update_values(values) - # ### Save scenario matrix to as PNG file - # - # Save the scenario matrix table as a PNG file. +# ### Save scenario matrix to as PNG file +# +# Save the scenario matrix table as a PNG file. def save_image(self): if self.tab_widget.currentIndex() == 0: @@ -405,9 +409,9 @@ def save_image(self): table.render(image) image.save(fname) - # ### Save scenario matrix to Excel file - # - # Write the scenario matrix results to an Excel file with color coding. +# ### Save scenario matrix to Excel file +# +# Write the scenario matrix results to an Excel file with color coding. def save_results_excel(self): defaultName = "" @@ -437,9 +441,9 @@ def save_results_excel(self): fill_type = "solid") workbook.save(fname) - # ### Run interference type simulation - # - # Run interference type simulation and classify results. +# ### Run interference type simulation +# +# Run interference type simulation and classify results. def interference_results(self): # Initialize filter check marks and expected filter results @@ -476,11 +480,12 @@ def interference_results(self): if self.tx_radios is None or self.rx_radios is None: return - # ### Classify the interference - # - # Iterate over all the transmitters and receivers and compute the power - # at the input to each receiver due to each of the transmitters. Compute - # which, if any, type of interference occurred. +# ### Classify the interference +# +# Iterate over all the transmitters and receivers and compute the power +# at the input to each receiver due to each of the transmitters. Compute +# which, if any, type of interference occurred. + domain = self.emitapp.results.interaction_domain() self.all_colors, self.power_matrix = self.rev.interference_type_classification(domain, use_filter = True, filter_list = filter) @@ -488,10 +493,10 @@ def interference_results(self): self.emitapp.save_project() self.populate_table() - # ### Run protection level simulation - # - # Run protection level simulation and classify results accroding to inputted - # threshold levels. +# ### Run protection level simulation +# +# Run protection level simulation and classify results accroding to inputted +# threshold levels. def protection_results(self): # Initialize filter check marks and expected filter results @@ -534,11 +539,11 @@ def protection_results(self): filter_list = filter) self.populate_table() - - # ### Populate the scenario matrix - # - # Create a scenario matrix view with the transmitters defined across the top - # and receivers down the left-most column. + +# ### Populate the scenario matrix +# +# Create a scenario matrix view with the transmitters defined across the top +# and receivers down the left-most column. def populate_table(self): if self.tab_widget.currentIndex() == 0: @@ -586,9 +591,10 @@ def clear_table(self): table.setColumnCount(0) table.setRowCount(0) - # ### GUI closing event - # - # Close AEDT if the GUI is closed. +# ### GUI closing event +# +# Close AEDT if the GUI is closed. + def closeEvent(self, event): msg = QtWidgets.QMessageBox() msg.setWindowTitle("Closing GUI") From 97e39623b06c53a6a976cc97dc631815af906715 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 21:42:01 -0600 Subject: [PATCH 44/56] FIX: Docstring in CircuitPins class --- pyaedt/modeler/circuits/object3dcircuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyaedt/modeler/circuits/object3dcircuit.py b/pyaedt/modeler/circuits/object3dcircuit.py index a0843748611..abd15f04395 100644 --- a/pyaedt/modeler/circuits/object3dcircuit.py +++ b/pyaedt/modeler/circuits/object3dcircuit.py @@ -137,7 +137,7 @@ def connect_to_component(self, component_pin, page_name=None, use_wire=False, wi Parameters ---------- - component_pin : :class:`pyaedt.modeler.circuits.PrimitivesNexxim.CircuitPins` + component_pin : :class:`pyaedt.modeler.circuits.objct3dcircuit.CircuitPins` Component pin to attach. page_name : str, optional Page port name. The default value is ``None``, in which case From dfd1ab6f43637fd0f9346e768f6a2eb4781e2b37 Mon Sep 17 00:00:00 2001 From: Devin Date: Sun, 4 Feb 2024 21:44:22 -0600 Subject: [PATCH 45/56] FIX: Docstring in CircuitPins class --- pyaedt/modeler/circuits/object3dcircuit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyaedt/modeler/circuits/object3dcircuit.py b/pyaedt/modeler/circuits/object3dcircuit.py index abd15f04395..cfad89c47e6 100644 --- a/pyaedt/modeler/circuits/object3dcircuit.py +++ b/pyaedt/modeler/circuits/object3dcircuit.py @@ -145,7 +145,8 @@ def connect_to_component(self, component_pin, page_name=None, use_wire=False, wi use_wire : bool, optional Whether to use wires or a page port to connect the pins. The default is ``False``, in which case a page port is used. Note - that if wires are used but not well placed, shorts can result. + that if wires are used but not well-placed, unintentional + short-circuits may result. wire_name : str, optional Wire name used only when user_wire is ``True``. Default value is ``""``. clearance_units : int, optional From 45d0f10bc90d8d73d2bf226b8d8d00b6c1fbb752 Mon Sep 17 00:00:00 2001 From: Devin Date: Mon, 5 Feb 2024 00:04:21 -0600 Subject: [PATCH 46/56] MISC: 07-Circuit Examples cleanup. --- examples/07-Circuit/Circuit_AMI.py | 45 +++++++++---- examples/07-Circuit/Circuit_Example.py | 57 ++++++++++------- .../07-Circuit/Circuit_Siwave_Multizones.py | 28 +++++---- .../07-Circuit/Circuit_Subcircuit_Example.py | 18 +++--- examples/07-Circuit/Circuit_Transient.py | 29 +++++---- examples/07-Circuit/Create_Netlist.py | 20 +++--- examples/07-Circuit/Reports.py | 59 ++++++++++-------- examples/07-Circuit/Touchstone_Management.py | 25 +++----- .../07-Circuit/_static}/circuit.png | Bin .../07-Circuit/_static}/spectrum_plot.png | Bin 10 files changed, 167 insertions(+), 114 deletions(-) rename {doc/source/Resources => examples/07-Circuit/_static}/circuit.png (100%) rename {doc/source/Resources => examples/07-Circuit/_static}/spectrum_plot.png (100%) diff --git a/examples/07-Circuit/Circuit_AMI.py b/examples/07-Circuit/Circuit_AMI.py index 1e17d04e930..f21c20851d6 100644 --- a/examples/07-Circuit/Circuit_AMI.py +++ b/examples/07-Circuit/Circuit_AMI.py @@ -2,21 +2,32 @@ # # This example shows how you can use PyAEDT to perform advanced postprocessing of AMI simulations. -# # Perform required imports +# + +# ## Perform required imports # # Perform required imports and set the local path to the path for PyAEDT. -# sphinx_gallery_thumbnail_path = 'Resources/spectrum_plot.png' - import os from matplotlib import pyplot as plt import numpy as np - import pyaedt +import tempfile + +# ## Download Example Data +# +# The ``download_file()`` method retrieves example +# data from the PyAnsys _example-data_ repository. +# +# - The fist argument is the folder name where +# the example files are located in the GitHub repository. +# - The 2nd argument is the file to retrieve. +# - The 3rd argument is the desination folder. +# +# Files are placed in the destination folder. -# Set local path to path for PyAEDT -temp_folder = pyaedt.generate_unique_folder_name() -project_path = pyaedt.downloads.download_file("ami", "ami_usb.aedtz", temp_folder) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +project_path = pyaedt.downloads.download_file("ami", "ami_usb.aedtz", temp_dir.name) # ## Launch AEDT # @@ -37,7 +48,7 @@ # ## Launch AEDT with Circuit and enable Pandas as the output format # # All outputs obtained with the `get_solution_data` method will have the Pandas format. -# Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT +# Launch AEDT with Circuit. The `pyaedt.Desktop` class initializes AEDT # and starts the specified version in the specified mode. pyaedt.settings.enable_pandas_output = True @@ -69,9 +80,12 @@ fig = original_data.plot() -# ## Sample WaveAfterProbe waveform using receiver clock +# ## Extract Wave Form # -# Extract waveform at specific clock time plus half unit interval +# Use the _WaveAfterProbe_ plot type to extract the +# waveform using an AMI receiver clock probe. +# The signal is extracted at a specific clock +# flank with addiional half unit interval. probe_name = "b_input_43" source_name = "b_output4_42" @@ -79,7 +93,8 @@ setup_name = "AMIAnalysis" ignore_bits = 100 unit_interval = 0.1e-9 -sample_waveform = cir.post.sample_ami_waveform(setupname=setup_name, probe_name=probe_name, source_name=source_name, +sample_waveform = cir.post.sample_ami_waveform(setupname=setup_name, probe_name=probe_name, + source_name=source_name, variation_list_w_value=cir.available_variations.nominal, unit_interval=unit_interval, ignore_bits=ignore_bits, plot_type=plot_type) @@ -88,6 +103,7 @@ # # Create the plot from a start time to stop time in seconds +# + tstop = 55e-9 tstart = 50e-9 scale_time = pyaedt.constants.unit_converter(1, unit_system="Time", input_units="s", @@ -128,6 +144,7 @@ ax.set_xlabel(original_data.units_sweeps["Time"]) ax.set_ylabel(original_data.units_data[plot_name]) plt.show() +# - # ## Plot Slicer Scatter # @@ -165,6 +182,7 @@ # # Extract waveform at specific clock time plus half unit interval. +# + original_data.enable_pandas_output = False original_data_value = original_data.data_real() original_data_sweep = original_data.primary_sweep_values @@ -181,11 +199,13 @@ clock_tics=tics, pandas_enabled=False, ) +# - # ## Plot waveform and samples # # Create the plot from a start time to stop time in seconds. +# + tstop = 40.0e-9 tstart = 25.0e-9 scale_time = pyaedt.constants.unit_converter(1, unit_system="Time", input_units="s", @@ -231,6 +251,7 @@ ax.set_xlabel(waveform_sweep_unit) ax.set_ylabel(waveform_unit) plt.show() +# - # ## Plot slicer scatter # @@ -251,3 +272,5 @@ cir.save_project() print("Project Saved in {}".format(cir.project_path)) cir.release_desktop() + +temp_dir.cleanup() # Remove project folder and temporary files. diff --git a/examples/07-Circuit/Circuit_Example.py b/examples/07-Circuit/Circuit_Example.py index 61593e44cd3..d817ad21fba 100644 --- a/examples/07-Circuit/Circuit_Example.py +++ b/examples/07-Circuit/Circuit_Example.py @@ -1,22 +1,22 @@ # # Circuit: schematic creation and analysis # -# This example shows how you can use PyAEDT to create a circuit design -# and run a Nexxim time-domain simulation. +# This example shows how to build a circuit schematic +# and run a transient circuit simulation. +# +# # ## Perform required imports # # Perform required imports. -# sphinx_gallery_thumbnail_path = 'Resources/circuit.png' - import pyaedt +import tempfile +import os # ## Launch AEDT # # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. -desktop_version = "2023.2" - # ## Set non-graphical mode # # Set non-graphical mode. @@ -24,28 +24,34 @@ # The Boolean parameter ``new_thread`` defines whether to create a new instance # of AEDT or try to connect to an existing instance of it. -non_graphical = False -new_thread = True - # ## Launch AEDT and Circuit # # Launch AEDT and Circuit. The :class:`pyaedt.Desktop` class initializes AEDT and # starts the specified version in the specified mode. +# + +desktop_version = "2023.2" +non_graphical = False +new_thread = True +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") + desktop = pyaedt.launch_desktop(desktop_version, non_graphical, new_thread) -aedt_app = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name()) +aedt_app = pyaedt.Circuit(projectname=os.path.join(temp_dir.name, "CircuitExample"), + designname="Simple") aedt_app.modeler.schematic.schematic_units = "mil" +# - # ## Create circuit setup # -# Create and customize an LNA (linear network analysis) setup. +# Create and customize an linear network analysis (LNA) setup. setup1 = aedt_app.create_setup("MyLNA") setup1.props["SweepDefinition"]["Data"] = "LINC 0GHz 4GHz 10001" -# ## Create components +# ## Place Components # -# Create components, such as an inductor, resistor, and capacitor. +# Place components such as an inductor, resistor, and capacitor. The ``location`` argument +# provides the ``[x, y]`` coordinates to place the component. inductor = aedt_app.modeler.schematic.create_inductor(compname="L1", value=1e-9, location=[0, 0]) resistor = aedt_app.modeler.schematic.create_resistor(compname="R1", value=50, location=[500, 0]) @@ -53,20 +59,25 @@ # ## Get all pins # -# Get all pins of a specified component. - -pins_resistor = resistor.pins - -# ## Create port and ground +# The component pins are instances of the class +# ``pyaedt.modeler.circuits.objct3dcircuit.CircuitPins`` class and +# provide access to the +# pin location, net connectivity and the method ``connect_to_component()`` which +# can be used to connect components in the schematic +# as will be demonstrated in +# this example. + +# ## Place a Port and Ground # -# Create a port and a ground, which are needed for the circuit analysis. +# Place a port and a ground in the schematic. -port = aedt_app.modeler.components.create_interface_port(name="myport", location=[-200, 0] ) +port = aedt_app.modeler.components.create_interface_port(name="myport", location=[-300, 50] ) gnd = aedt_app.modeler.components.create_gnd(location=[1200, -100]) # ## Connect components # -# Connect components with wires. +# Connect components with wires in the schematic. The ``connect_to_component()`` +# method is used to create connections between pins. port.pins[0].connect_to_component(component_pin=inductor.pins[0], use_wire=True) inductor.pins[1].connect_to_component(component_pin=resistor.pins[1], use_wire=True) @@ -90,7 +101,7 @@ # ## Create report # -# Create a report that plots solution data. +# Display the scattering parameters. solutions = aedt_app.post.get_solution_data( expressions=aedt_app.get_traces_for_plot(category="S"), @@ -112,3 +123,5 @@ # All methods provide for saving the project before closing. desktop.release_desktop() + +temp_dir.cleanup() # Remove project data and temporary working directory. diff --git a/examples/07-Circuit/Circuit_Siwave_Multizones.py b/examples/07-Circuit/Circuit_Siwave_Multizones.py index 97dd7d28005..fb9968a2c14 100644 --- a/examples/07-Circuit/Circuit_Siwave_Multizones.py +++ b/examples/07-Circuit/Circuit_Siwave_Multizones.py @@ -9,17 +9,16 @@ from pyaedt import Edb, Circuit import os.path import pyaedt +import tempfile # ## Download file # # Download the AEDB file and copy it in the temporary folder. -temp_folder = pyaedt.generate_unique_folder_name() -edb_file = pyaedt.downloads.download_file(destination=temp_folder, directory="edb/siwave_multi_zones.aedb") -working_directory = os.path.join(temp_folder, "workdir") +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +edb_file = pyaedt.downloads.download_file(destination=temp_dir.name, directory="edb/siwave_multi_zones.aedb") aedt_file = os.path.splitext(edb_file)[0] + ".aedt" -circuit_project_file = os.path.join(working_directory, os.path.splitext(os.path.basename(edb_file))[0] + - "multizone_clipped_circuit.aedt") +circuit_project_file = os.path.join(temp_dir.name, "multizone_clipped_circuit.aedt") print(edb_file) @@ -53,6 +52,7 @@ # # Clip sub-designs along with corresponding zone definition # and create port of clipped signal traces. + defined_ports, project_connexions = edb.cutout_multizone_layout(edb_zones, common_reference_net) # ## Circuit @@ -63,33 +63,39 @@ circuit.connect_circuit_models_from_multi_zone_cutout(project_connections=project_connexions, edb_zones_dict=edb_zones, ports=defined_ports, model_inc=70) + # ## Setup # -# Add Nexxim LNA simulation setup. +# Add Nexxim LNA simulation setup. + circuit_setup= circuit.create_setup("Pyedt_LNA") # ## Frequency sweep # # Add frequency sweep from 0GHt to 20GHz with 10NHz frequency step. + circuit_setup.props["SweepDefinition"]["Data"] = "LIN {} {} {}".format("0GHz", "20GHz", "10MHz") # ## Start simulation # # Analyze all siwave projects and solves the circuit. + circuit.analyze() -# ## Define differential pairs -# +# Define differential pairs + circuit.set_differential_pair(diff_name="U0", positive_terminal="U0.via_38.B2B_SIGP", negative_terminal="U0.via_39.B2B_SIGN") circuit.set_differential_pair(diff_name="U1", positive_terminal="U1.via_32.B2B_SIGP", negative_terminal="U1.via_33.B2B_SIGN") -# ## Plot results -# +# Plot results + circuit.post.create_report(expressions=["dB(S(U0,U0))", "dB(S(U1,U0))"], context="Differential Pairs") # ## Release AEDT desktop # -circuit.release_desktop() \ No newline at end of file +circuit.release_desktop() + +temp_dir.cleanup() # Remove the temporary working folder and all project files diff --git a/examples/07-Circuit/Circuit_Subcircuit_Example.py b/examples/07-Circuit/Circuit_Subcircuit_Example.py index 9e39a508363..61ae277b748 100644 --- a/examples/07-Circuit/Circuit_Subcircuit_Example.py +++ b/examples/07-Circuit/Circuit_Subcircuit_Example.py @@ -9,6 +9,7 @@ import os import pyaedt +import tempfile # ## Set non-graphical mode @@ -16,17 +17,18 @@ # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. -non_graphical = False - # ## Launch AEDT with Circuit # # Launch AEDT 2023 R2 in graphical mode with Circuit. -circuit = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name(), - specified_version="2023.2", - non_graphical=non_graphical, - new_desktop_session=True - ) +non_graphical = False +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +circuit = pyaedt.Circuit(projectname=os.path.join(temp_dir.name, "SubcircuitExample"), + designname="SimpleExample", + specified_version="2023.2", + non_graphical=non_graphical, + new_desktop_session=True + ) circuit.modeler.schematic_units = "mil" # ## Add subcircuit @@ -61,3 +63,5 @@ # Release AEDT. circuit.release_desktop(True, True) + +temp_dir.cleanup() # Clean up temporary folder and remove project data. diff --git a/examples/07-Circuit/Circuit_Transient.py b/examples/07-Circuit/Circuit_Transient.py index 52d64b01b82..b2d8bc0c3fd 100644 --- a/examples/07-Circuit/Circuit_Transient.py +++ b/examples/07-Circuit/Circuit_Transient.py @@ -1,5 +1,5 @@ # # Circuit: transient analysis and eye plot -# ---------------------------------------- +# # This example shows how you can use PyAEDT to create a circuit design, # run a Nexxim time-domain simulation, and create an eye diagram. @@ -12,6 +12,7 @@ from matplotlib import pyplot as plt import numpy as np import pyaedt +import tempfile # ## Set non-graphical mode # @@ -19,13 +20,14 @@ # documentation only. # You can set ``non_graphical`` either to ``True`` or ``False``. -non_graphical = False - # ## Launch AEDT with Circuit # # Launch AEDT 2023 R2 in graphical mode with Circuit. -cir = pyaedt.Circuit(projectname=pyaedt.generate_unique_project_name(), +non_graphical = False +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +cir = pyaedt.Circuit(projectname=os.path.join(temp_dir.name, "CktTransient"), + designname="Circuit Examples", specified_version="2023.2", new_desktop_session=True, non_graphical=non_graphical @@ -79,11 +81,11 @@ trans_setup.props["TransientData"] = ["0.01ns", "200ns"] cir.analyze_setup("TransientRun") -# ## Create report outside AEDT +# ## View Results # -# Create a report outside AEDT using the ``get_solution_data`` method. This -# method allows you to get solution data and plot it outside AEDT without needing -# a UI. +# Create a report using the ``get_solution_data()`` method. This +# method allows you to view and post-process results using Python packages. +# The ``solutions.plot()`` method uses Matplotlib. report = cir.post.create_report("V(Vout)", domain="Time") if not non_graphical: @@ -91,7 +93,7 @@ solutions = cir.post.get_solution_data(domain="Time") solutions.plot("V(Vout)") -# ## Create report inside AEDT +# ## Create a Report in AEDT # # Create a report inside AEDT using the ``new_report`` object. This object is # fully customizable and usable with most of the reports available in AEDT. @@ -115,7 +117,7 @@ sol = new_report.get_solution_data() sol.plot() -# ## Create eye diagram inside AEDT +# ## Create Eye Diagram in AEDT # # Create an eye diagram inside AEDT using the ``new_eye`` object. @@ -127,8 +129,9 @@ # ## Create eye diagram outside AEDT # # Create the same eye diagram outside AEDT using Matplotlib and the -# ``get_solution_data`` method. +# ``get_solution_data()`` method. +# + unit_interval = 1 offset = 0.25 tstop = 200 @@ -154,9 +157,13 @@ cellsv = np.append(cellsv, bn) plt.plot(cellst.T, cellsv.T, zorder=0) plt.show() +# - # ## Release AEDT # # Release AEDT. + cir.save_project() cir.release_desktop() + +temp_dir.cleanup() # Clean up temporary working folder and project files. diff --git a/examples/07-Circuit/Create_Netlist.py b/examples/07-Circuit/Create_Netlist.py index 15869a71adc..93e97f0df08 100644 --- a/examples/07-Circuit/Create_Netlist.py +++ b/examples/07-Circuit/Create_Netlist.py @@ -8,18 +8,15 @@ # Perform required imports and set paths. import os - import pyaedt +import tempfile -netlist = pyaedt.downloads.download_netlist() - -project_name = pyaedt.generate_unique_project_name() -print(project_name) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +netlist = pyaedt.downloads.download_netlist(destination=temp_dir.name) # ## Launch AEDT # # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. -desktopVersion = "2023.2" # ## Set non-graphical mode # @@ -28,20 +25,21 @@ # The Boolean parameter ``NewThread`` defines whether to create a new instance # of AEDT or try to connect to an existing instance of it. +desktopVersion = "2023.2" non_graphical = False NewThread = True # ## Launch AEDT with Circuit # -# Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT +# Launch AEDT with Circuit. The `pyaedt.Desktop` class initializes AEDT # and starts it on the specified version in the specified graphical mode. desktop = pyaedt.launch_desktop(desktopVersion, non_graphical, NewThread) -aedtapp = pyaedt.Circuit(projectname=project_name) +aedtapp = pyaedt.Circuit(projectname=os.path.join(temp_dir.name, "NetlistExample")) -# ## Define variable +# ## Define a Parameter # -# Define a design variable by using a ``$`` prefix. +# Specify the voltage as a parameter. aedtapp["Voltage"] = "5" @@ -59,3 +57,5 @@ # AEDT. desktop.release_desktop() + +temp_dir.cleanup() # Clean up temporary directory and project data. diff --git a/examples/07-Circuit/Reports.py b/examples/07-Circuit/Reports.py index e09ee381453..e989859e31a 100644 --- a/examples/07-Circuit/Reports.py +++ b/examples/07-Circuit/Reports.py @@ -5,28 +5,30 @@ """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports and set the local path to the path for PyAEDT. import os from IPython.display import Image import pyaedt +import tempfile +"" # Set local path to path for PyAEDT -temp_folder = pyaedt.generate_unique_folder_name() -project_path = pyaedt.downloads.download_custom_reports(destination=temp_folder) +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +project_path = pyaedt.downloads.download_custom_reports(destination=temp_dir.name) ############################################################################### -# Launch AEDT -# ~~~~~~~~~~~ +# ## Launch AEDT +# # Launch AEDT 2023 R2 in graphical mode. This example uses SI units. desktopVersion = "2023.2" ########################################################## -# Set non-graphical mode -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Set non-graphical mode +# # Set non-graphical mode. # You can set ``non_graphical`` either to ``True`` or ``False``. # The Boolean parameter ``new_thread`` defines whether to create a new instance @@ -36,12 +38,12 @@ NewThread = True ############################################################################### -# Launch AEDT with Circuit -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Launch AEDT with Circuit +# # Launch AEDT with Circuit. The :class:`pyaedt.Desktop` class initializes AEDT # and starts the specified version in the specified mode. -cir = pyaedt.Circuit(projectname=os.path.join(project_path, 'CISPR25_Radiated_Emissions_Example23R1.aedtz'), +cir = pyaedt.Circuit(projectname=os.path.join(temp_dir.name, 'CISPR25_Radiated_Emissions_Example23R1.aedtz'), non_graphical=non_graphical, specified_version=desktopVersion, new_desktop_session=True @@ -49,8 +51,8 @@ cir.analyze() ############################################################################### -# Create spectrum report -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create spectrum report +# # Create a spectrum report. You can use a JSON file to create a simple setup # or a fully customized one. The following code creates a simple setup and changes # the JSON file to customize it. In a spectrum report, you can add limitilines and @@ -62,22 +64,21 @@ Image(out) ############################################################################### -# Create spectrum report -# ~~~~~~~~~~~~~~~~~~~~~~ +# ## Create spectrum report +# # Every aspect of the report can be customized. report1_full = cir.post.create_report_from_configuration(os.path.join(project_path,'Spectrum_CISPR_Custom.json')) out = cir.post.export_report_to_jpg(cir.working_directory, report1_full.plot_name) Image(out) ############################################################################### -# Create transient report -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create transient report +# # Create a transient report. You can read and modify the JSON file # before running the script. The following code modifies the traces # before generating the report. You can create custom reports in non-graphical # mode in AEDT 2023 R2 and later. - props = pyaedt.data_handler.json_to_dict(os.path.join(project_path, 'Transient_CISPR_Custom.json')) report2 = cir.post.create_report_from_configuration(input_dict=props, solution_name="NexximTransient") @@ -85,8 +86,8 @@ Image(out) ############################################################################### -# Create transient report -# ~~~~~~~~~~~~~~~~~~~~~~~ +# ## Create transient report +# # Property dictionary can be customized in any aspect and new report can be created easily. # In this example the curve name is customized. @@ -97,8 +98,8 @@ Image(out) ############################################################################### -# Create eye diagram -# ~~~~~~~~~~~~~~~~~~ +# ## Create eye diagram +# # Create an eye diagram. If the JSON file contains an eye mask, you can create # an eye diagram and fully customize it. @@ -107,8 +108,8 @@ Image(out) ############################################################################### -# Create eye diagram -# ~~~~~~~~~~~~~~~~~~ +# ## Create eye diagram +# # You can create custom reports in # non-graphical mode in AEDT 2023 R2 and later. @@ -118,13 +119,17 @@ Image(out) ################################################ # This is how the spectrum looks like -# .. image:: Resources/spectrum_plot.png +# +# ############################################################################### -# Save project and close AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Save project and close AEDT +# # Save the project and close AEDT. cir.save_project() print("Project Saved in {}".format(cir.project_path)) cir.release_desktop() + +"" +temp_dir.cleanup() # Clean up project folder and remove files. diff --git a/examples/07-Circuit/Touchstone_Management.py b/examples/07-Circuit/Touchstone_Management.py index 6a192e1249f..7458eab9c1d 100644 --- a/examples/07-Circuit/Touchstone_Management.py +++ b/examples/07-Circuit/Touchstone_Management.py @@ -9,46 +9,41 @@ This example runs only on Windows using CPython. """ ############################################################################### -# Perform required imports -# ~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Perform required imports +# # Perform required imports and set the local path to the path for PyAEDT. from pyaedt import downloads - example_path = downloads.download_touchstone() ############################################################################### -# Import libraries and Touchstone file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ## Import libraries and Touchstone file +# # Import Matplotlib, NumPy, and the Touchstone file. from pyaedt.generic.touchstone_parser import read_touchstone ############################################################################### -# Read Touchstone file -# ~~~~~~~~~~~~~~~~~~~~ +# ## Read Touchstone file +# # Read the Touchstone file. data = read_touchstone(example_path) ############################################################################### -# Get curve plot -# ~~~~~~~~~~~~~~ +# ## Get curve plot +# # Get the curve plot by category. The following code shows how to plot lists of the return losses, # insertion losses, fext, and next based on a few inputs and port names. data.plot_return_losses() - data.plot_insertion_losses() - data.plot_next_xtalk_losses("U1") - data.plot_fext_xtalk_losses(tx_prefix="U1", rx_prefix="U7") - ############################################################################### -# Get curve worst cases -# ~~~~~~~~~~~~~~~~~~~~~ +# ## Get curve worst cases +# # Get curve worst cases. worst_rl, global_mean = data.get_worst_curve( diff --git a/doc/source/Resources/circuit.png b/examples/07-Circuit/_static/circuit.png similarity index 100% rename from doc/source/Resources/circuit.png rename to examples/07-Circuit/_static/circuit.png diff --git a/doc/source/Resources/spectrum_plot.png b/examples/07-Circuit/_static/spectrum_plot.png similarity index 100% rename from doc/source/Resources/spectrum_plot.png rename to examples/07-Circuit/_static/spectrum_plot.png From facd1696ed67503b31558d953cd513feb2141bb0 Mon Sep 17 00:00:00 2001 From: Devin Date: Mon, 5 Feb 2024 17:16:01 +0100 Subject: [PATCH 47/56] MISC: Update 06-Multiphysics/Hfss_Icepak_Coupling.py --- .../06-Multiphysics/Hfss_Icepak_Coupling.py | 145 ++++++++++-------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/examples/06-Multiphysics/Hfss_Icepak_Coupling.py b/examples/06-Multiphysics/Hfss_Icepak_Coupling.py index 9ef5cc2df31..5a538fa2c04 100644 --- a/examples/06-Multiphysics/Hfss_Icepak_Coupling.py +++ b/examples/06-Multiphysics/Hfss_Icepak_Coupling.py @@ -15,51 +15,48 @@ import os import pyaedt from pyaedt.generic.pdf import AnsysReport +import tempfile -# ## Set non-graphical mode +# ## Setup # # Set non-graphical mode. -# You can set ``non_graphical`` either to ``True`` or ``False``. +# Set ``non_graphical`` to ``False`` if the AEDT user interface should +# be started when HFSS and Icepak are accessed. +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") non_graphical = False desktopVersion = "2023.2" -# ## Open project -# -# Open the project. - -NewThread = True -project_file = pyaedt.generate_unique_project_name() - # ## Launch AEDT and initialize HFSS # # Launch AEDT and initialize HFSS. If there is an active HFSS design, the ``aedtapp`` # object is linked to it. Otherwise, a new design is created. -aedtapp = pyaedt.Hfss(projectname=project_file, +aedtapp = pyaedt.Hfss(projectname=os.path.join(temp_dir.name, "Icepak_HFSS_Coupling"), + designname="RF", specified_version=desktopVersion, non_graphical=non_graphical, - new_desktop_session=NewThread + new_desktop_session=True ) -# ## Initialize variable settings +# ## Parameters # -# Initialize variable settings. You can initialize a variable simply by creating -# it as a list object. If you enter the prefix ``$``, the variable is created for -# the project. Otherwise, the variable is created for the design. +# Parameters can be instantiated by defining them as a key used for the application +# instance as demonstrated below. The prefix ``$`` is used to define +# project-wide scope for the parameter. Otherwise the parameter scope is limited the current design. -aedtapp["$coax_dimension"] = "100mm" +aedtapp["$coax_dimension"] = "100mm" # Project-wide scope. udp = aedtapp.modeler.Position(0, 0, 0) -aedtapp["inner"] = "3mm" +aedtapp["inner"] = "3mm" # Local "Design" scope. # ## Create coaxial and cylinders # # Create a coaxial and three cylinders. You can apply parameters -# directly using the :func:`pyaedt.modeler.Primitives3D.Primitives3D.create_cylinder` +# directly using the `pyaedt.modeler.Primitives3D.Primitives3D.create_cylinder` # method. You can assign a material directly to the object creation action. -# Optionally, you can assign a material using the :func:`assign_material` method. +# Optionally, you can assign a material using the `assign_material` method. -# TODO: How does this work when two truesurfaces are defined? +# TODO: How does this work when two true surfaces are defined? o1 = aedtapp.modeler.create_cylinder(cs_axis=aedtapp.PLANE.ZX, position=udp, radius="inner", height="$coax_dimension", numSides=0, name="inner") o2 = aedtapp.modeler.create_cylinder(cs_axis=aedtapp.PLANE.ZX, position=udp, radius=8, height="$coax_dimension", @@ -93,22 +90,25 @@ aedtapp.modeler.subtract(o3, o2, True) aedtapp.modeler.subtract(o2, o1, True) -# ## Perform mesh operations +# ## Assign Mesh Operations # -# Perform mesh operations. Most mesh operations are available. -# After a mesh is created, you can access a mesh operation to -# edit or review parameter values. +# Most mesh operations are accessible using the ``mesh`` property +# which is an instance of the ``pyaedt.modules.MeshIcepak.IcepakMesh`` class. +# +# This example demonstrates the use of several common mesh +# operatons. aedtapp.mesh.assign_initial_mesh_from_slider(level=6) aedtapp.mesh.assign_model_resolution(names=[o1.name, o3.name], defeature_length=None) aedtapp.mesh.assign_length_mesh(names=o2.faces, isinside=False, maxlength=1, maxel=2000) -# ## Create excitations +# ## Create HFSS Sources # -# Create excitations. The ``create_wave_port_between_objects`` method automatically -# identifies the closest faces on a predefined direction and creates a sheet to cover -# the faces. It also assigns a port to this face. If ``add_pec_cap=True``, the method -# creates a PEC cap. +# The RF power dissipated in the HFSS model will act as the thermal +# source for in Icepak. The ``create_wave_port_between_objects`` method +# s used to assign the RF ports that inject RF power into the HFSS +# model. If the parameter ``add_pec_cap=True``, then the method +# creates a perfectly conducting (lossless) cap covering the port. # + aedtapp.wave_port(signal="inner", @@ -129,7 +129,7 @@ aedtapp.modeler.fit_all() # - -# ## Create setup +# ## HFSS Simulation Setup # # Create a setup. A setup is created with default values. After its creation, # you can change values and update the setup. The ``update`` method returns a Boolean @@ -141,48 +141,68 @@ setup.props["BasisOrder"] = 2 setup.props["MaximumPasses"] = 1 -# ## Create sweep +# ## HFSS Frequency Sweep # -# Create a sweep. A sweep is created with default values. +# The frequency sweep defines the RF frequency range over which the RF power is +# injected into the structure. sweepname = aedtapp.create_linear_count_sweep(setupname="MySetup", unit="GHz", freqstart=0.8, freqstop=1.2, num_of_freq_points=401, sweep_type="Interpolating") # ## Create Icepak model # -# Create an Icepak model. After an HFSS setup is ready, link this model to an Icepak -# project and run a coupled physics analysis. The :func:`FieldAnalysis3D.copy_solid_bodies_from` -# method imports a model from HFSS with all material settings. +# After an HFSS setup has been defined, the model can be lnked to an Icepak +# design and the coupled physics analysis can be run. The `FieldAnalysis3D.copy_solid_bodies_from()` +# method imports a model from HFSS into Icepak including all material definitions. -ipkapp = pyaedt.Icepak() +ipkapp = pyaedt.Icepak(designname="CalcTemp") ipkapp.copy_solid_bodies_from(aedtapp) -# ## Link sources to EM losses +# ## Link RF Thermal Source # -# Link sources to the EM losses. +# The RF loss in HFSS will be used as the thermal source in Icepak. surfaceobj = ["inner", "outer"] -ipkapp.assign_em_losses(designname=aedtapp.design_name, setupname="MySetup", sweepname="LastAdaptive", - map_frequency="1GHz", surface_objects=surfaceobj, paramlist=["$coax_dimension", "inner"]) +ipkapp.assign_em_losses(designname=aedtapp.design_name, setupname="MySetup", + sweepname="LastAdaptive", + map_frequency="1GHz", + surface_objects=surfaceobj, + paramlist=["$coax_dimension", "inner"]) -# ## Edit gravity setting +# ## Assign the Direction of Gravity # -# Edit the gravity setting if necessary because it is important for a fluid analysis. +# Set the direction of gravity for convection in Icepak. Gravity drives a temperature gradient +# due to the dependence of gas density on temperature. ipkapp.edit_design_settings(aedtapp.GRAVITY.ZNeg) -# ## Set up Icepak project +# ## Set up the Icepak Project +# +# The initial solution setup applies default values that can subsequently +# be modified as shown here. +# The ``props`` property enables access to all solution settings. # -# Set up the Icepak project. When you create a setup, default settings are applied. -# When you need to change a property of the setup, you can use the ``props`` -# command to pass the correct value to the property. The ``update`` function +# The ``update`` function # applies the settings to the setup. The setup creation process is identical # for all tools. setup_ipk = ipkapp.create_setup("SetupIPK") setup_ipk.props["Convergence Criteria - Max Iterations"] = 3 -# ## Edit or review mesh parameters +# ### Icepak Solution Properteis +# +# The setup properties are accessible through the ``props`` property as +# an ordered dict. The ``keys()`` method can be used to retrieve all settings for +# the setup. +# +# Find propertes that contain the string ``"Convergence"`` and print the default values. + +conv_props = [k for k in setup_ipk.props.keys() if "Convergence" in k] +print("Here are some default setup properties:") +for p in conv_props: + print("\"" + p + "\" -> " + str(setup_ipk.props[p])) + +# ### Edit or Review Mesh Parameters # # Edit or review the mesh parameters. After a mesh is created, you can access # a mesh operation to edit or review parameter values. @@ -199,34 +219,37 @@ # can be helpful when performing operations on multiple projects. aedtapp.save_project() +project_filename = aedtapp.project_file # Save the project file name. aedtapp.close_project(aedtapp.project_name) -aedtapp = pyaedt.Hfss(project_file) +aedtapp = pyaedt.Hfss(project_filename) ipkapp = pyaedt.Icepak() ipkapp.solution_type = ipkapp.SOLUTIONS.Icepak.SteadyTemperatureAndFlow ipkapp.modeler.fit_all() -# ## Solve Icepak project +# ## Solve the Project # -# Solve the Icepak project and the HFSS sweep. +# Solve the Icepak and HFSS models. -setup1 = ipkapp.analyze_setup("SetupIPK") +ipkapp.setups[0].analyze() # Run the Icepak analysis. aedtapp.save_project() aedtapp.modeler.fit_all() -aedtapp.analyze_setup("MySetup") +aedtapp.setups[0].analyze() # Run the HFSS analysis. -# ## Generate field plots and export +# ### Plot and Export Results # # Generate field plots on the HFSS project and export them as images. # + cutlist = [pyaedt.constants.GLOBALCS.XY, pyaedt.constants.GLOBALCS.ZX, pyaedt.constants.GLOBALCS.YZ] vollist = [o2.name] -setup_name = "MySetup : LastAdaptive" -quantity_name = "ComplexMag_E" +quantity_name1 = "ComplexMag_E" quantity_name2 = "ComplexMag_H" -intrinsic = {"Freq": "1GHz", "Phase": "0deg"} +intrinsic = {"Freq": aedtapp.setups[0].props["Frequency"], + "Phase": "0deg"} surflist = aedtapp.modeler.get_object_faces("outer") -plot1 = aedtapp.post.create_fieldplot_surface(surflist, quantity_name2, setup_name, intrinsic) +plot1 = aedtapp.post.create_fieldplot_surface(surflist, quantity_name2, + setup_name=aedtapp.nominal_adaptive, + intrinsicDict=intrinsic) results_folder = os.path.join(aedtapp.working_directory, "Coaxial_Results_NG") if not os.path.exists(results_folder): @@ -253,17 +276,17 @@ start = time.time() cutlist = ["Global:XY"] -phases = [str(i * 5) + "deg" for i in range(18)] +phase_values = [str(i * 5) + "deg" for i in range(18)] animated = aedtapp.post.plot_animated_field( quantity="Mag_E", object_list=cutlist, plot_type="CutPlane", setup_name=aedtapp.nominal_adaptive, - intrinsics={"Freq": "1GHz", "Phase": "0deg"}, + intrinsics=intrinsic, export_path=results_folder, variation_variable="Phase", - variation_list=phases, + variation_list=phase_values, show=False, export_gif=False, log_scale=True, @@ -336,3 +359,5 @@ # Close the project and release AEDT. aedtapp.release_desktop() + +temp_dir.cleanup() # Clean up temporary directory and delete project files. From 730f311c0d73c06c8d2d7e34eecf1f6412b13670 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Tue, 6 Feb 2024 16:29:22 +0100 Subject: [PATCH 48/56] WIP: use pypandoc-binary to avoid issues Notes: since we need pandoc during the creation of the examples' documentation, we need to ensure that it is available every time. This should avoid us to install it on every self hosted rnner --- .github/workflows/full_documentation.yml | 1 + pyproject.toml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index 1b80cce0813..a167bbb6280 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -59,6 +59,7 @@ jobs: run: | testenv\Scripts\Activate.ps1 pip install .[doc] + pip install .[ci] Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv\Lib\site-packages\vtkmodules" -Force - name: Retrieve PyAEDT version diff --git a/pyproject.toml b/pyproject.toml index 2eb44c921c5..bf3dfd203a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,6 +146,9 @@ all = [ "scikit-rf==0.31.0", "openpyxl==3.1.2", ] +ci = [ + "pypandoc-binary", +] [tool.flit.module] name = "pyaedt" From df929ae27bfac2f9ebef7f9a09c83deb99bdf729 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 09:07:55 +0100 Subject: [PATCH 49/56] MISC: fix examples --- examples/00-EDB/index.rst | 2 +- examples/03-Maxwell/Maxwell2D_DCConduction.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/00-EDB/index.rst b/examples/00-EDB/index.rst index 1577b1ccb2d..b989ef9ede4 100644 --- a/examples/00-EDB/index.rst +++ b/examples/00-EDB/index.rst @@ -19,7 +19,7 @@ electronics desktop user interface. 06_Advanced_EDB.py 09_Configuration.py 10_GDS_workflow.py - 11_post_layout_parametrization.py + 11_post_layout_parameterization.py 12_edb_sma_connector_on_board.py 13_edb_create_component.py 14_edb_create_parametrized_design.py diff --git a/examples/03-Maxwell/Maxwell2D_DCConduction.py b/examples/03-Maxwell/Maxwell2D_DCConduction.py index b072e2d8c10..4f1ab90b276 100644 --- a/examples/03-Maxwell/Maxwell2D_DCConduction.py +++ b/examples/03-Maxwell/Maxwell2D_DCConduction.py @@ -115,6 +115,7 @@ # # Create R. vs. material report. +# + variations = {"MaterialIndex": ["All"], "MaterialThickness": ["Nominal"]} report = m2d.post.create_report( expressions="1/Matrix1.G(1V,1V)/MaterialThickness", @@ -135,6 +136,7 @@ for i in range(len(d.primary_sweep_values)): material_index_vs_resistance.append([str(d.primary_sweep_values[i]), str(resistence[i])]) colors.append([None, None]) +# - # ## Field overlay # @@ -179,16 +181,14 @@ animated.azimuth_angle = 0 animated.animate() -################################################################################ -# Export model picture -# ~~~~~~~~~~~~~~~~~~~~ +# ## Export model picture +# # Export model picture. model_picture = m2d.post.export_model_picture() -################################################################################ -# Generate PDF report -# ~~~~~~~~~~~~~~~~~~~ +# ## Generate PDF report +# # Generate a PDF report with output of simulation. pdf_report = AnsysReport(project_name=m2d.project_name, design_name=m2d.design_name, version="2023.2") From e11b4f428e3d9d1f37f03027c8a5a726807206ba Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 09:11:52 +0100 Subject: [PATCH 50/56] WIP: extend timeout for testing purposes --- .github/workflows/full_documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index a167bbb6280..68f6a319f48 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -34,7 +34,7 @@ jobs: # The type of runner that the job will run on name: full_documentation runs-on: [windows-latest, pyaedt] - timeout-minutes: 720 + timeout-minutes: 900 strategy: matrix: python-version: ['3.10'] From 18712dec2dd5fa18d3a55b1711ae2a889a7b5380 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 10:59:54 +0100 Subject: [PATCH 51/56] WIP: temporary remove unit tests --- .github/workflows/unit_tests.yml | 172 ------------------------------- 1 file changed, 172 deletions(-) delete mode 100644 .github/workflows/unit_tests.yml diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml deleted file mode 100644 index 2dcee8a270d..00000000000 --- a/.github/workflows/unit_tests.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: CI - -env: - python.version: '3.10' - python.venv: 'testvenv' - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it hasn't been deleted already. - # It applies 7 days retention policy by default. - RESET_PIP_CACHE: 0 - PACKAGE_NAME: PyAEDT -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - tags: - - 'v*' - branches: - - main - pull_request: - branches: [ main ] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build_solvers: - # The type of runner that the job will run on - runs-on: [ windows-latest, pyaedt ] - strategy: - matrix: - python-version: [ '3.10' ] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - Remove-Item D:\Temp\* -Recurse -Force -ErrorAction SilentlyContinue - python -m venv testenv_s - testenv_s\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - - - name: 'Install pyaedt' - run: | - testenv_s\Scripts\Activate.ps1 - pip install . - pip install .[tests] - pip install pytest-azurepipelines - Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv_s\Lib\site-packages\vtkmodules" -Force - mkdir tmp - cd tmp - python -c "import pyaedt; print('Imported pyaedt')" - - # - name: "Check licences of packages" - # uses: pyansys/pydpf-actions/check-licenses@v2.0 - - - name: 'Unit testing' - uses: nick-fields/retry@v3 - with: - max_attempts: 3 - retry_on: error - timeout_minutes: 40 - command: | - testenv_s\Scripts\Activate.ps1 - Set-Item -Path env:PYTHONMALLOC -Value "malloc" - pytest --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest_solvers - - - uses: codecov/codecov-action@v4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - name: 'Upload coverage to Codecov' - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-solver-results - path: junit/test-results.xml - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - - - build: - # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] - strategy: - matrix: - python-version: ['3.10'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: 'Create virtual env' - run: | - Remove-Item D:\Temp\* -Recurse -Force -ErrorAction SilentlyContinue - python -m venv testenv - testenv\Scripts\Activate.ps1 - python -m pip install pip -U - python -m pip install wheel setuptools -U - python -c "import sys; print(sys.executable)" - - - name: 'Install pyaedt' - run: | - testenv\Scripts\Activate.ps1 - pip install . - pip install .[tests] - pip install pytest-azurepipelines - Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv\Lib\site-packages\vtkmodules" -Force - mkdir tmp - cd tmp - python -c "import pyaedt; print('Imported pyaedt')" - - # - name: "Check licences of packages" - # uses: pyansys/pydpf-actions/check-licenses@v2.0 - - - name: 'Unit testing' - uses: nick-fields/retry@v3 - with: - max_attempts: 3 - retry_on: error - timeout_minutes: 50 - command: | - testenv\Scripts\Activate.ps1 - Set-Item -Path env:PYTHONMALLOC -Value "malloc" - pytest -n 6 --dist loadfile --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest - - - uses: codecov/codecov-action@v4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - name: 'Upload coverage to Codecov' - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - with: - name: pytest-results - path: junit/test-results.xml - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - - - name: 'Build and validate source distribution' - run: | - testenv\Scripts\Activate.ps1 - python -m pip install build twine - python -m build - python -m twine check dist/* - - - name: "Builds and uploads to PyPI" - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - run: | - testenv\Scripts\Activate.ps1 - python setup.py sdist - python -m pip install twine - python -m twine upload --skip-existing dist/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} From 370005bde79d2add17a344b9b0b9e8fc5f9c471f Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 11:00:13 +0100 Subject: [PATCH 52/56] DOC: check pandoc installation and fail on error --- doc/source/conf.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 125d3f82510..96d01fa6a61 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -222,6 +222,7 @@ def setup(app): app.add_directive('pprint', PrettyPrintDirective) app.connect('autodoc-skip-member', autodoc_skip_member) app.connect('builder-inited', copy_examples) + app.connect("builder-inited", ensure_pandoc_installed) app.connect('source-read', add_ipython_time) app.connect('source-read', adjust_image_path) app.connect('html-page-context', remove_ipython_time_from_html) @@ -230,6 +231,19 @@ def setup(app): app.connect('build-finished', remove_doctree) app.connect('build-finished', check_build_finished_without_error) +def check_pandoc_installed(): + """Ensure that pandoc is installed + """ + import pypandoc + + try: + pandoc_path = pypandoc.get_pandoc_path() + pandoc_dir = os.path.dirname(pandoc_path) + if pandoc_dir not in os.environ["PATH"].split(os.pathsep): + os.environ["PATH"] += os.pathsep + pandoc_dir + except OSError: + logger.error("Pandoc was not found, please add it to your path or install pypandoc-binary") + local_path = os.path.dirname(os.path.realpath(__file__)) module_path = pathlib.Path(local_path) root_path = module_path.parent.parent @@ -392,7 +406,7 @@ def setup(app): nbsphinx_execute = "always" # Allow errors to help debug. -nbsphinx_allow_errors = True +nbsphinx_allow_errors = False # Sphinx gallery customization From f9e15553bf29dcd8ba0e07e49dd22cd0585bac08 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 16:29:47 +0100 Subject: [PATCH 53/56] WIP: fix check pandoc --- doc/source/conf.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 96d01fa6a61..8a9753dff72 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -218,19 +218,6 @@ def check_build_finished_without_error(app, exception): if app.builder.config.html_context.get('build_error', False): raise Exception('Build failed due to error in html-page-context') -def setup(app): - app.add_directive('pprint', PrettyPrintDirective) - app.connect('autodoc-skip-member', autodoc_skip_member) - app.connect('builder-inited', copy_examples) - app.connect("builder-inited", ensure_pandoc_installed) - app.connect('source-read', add_ipython_time) - app.connect('source-read', adjust_image_path) - app.connect('html-page-context', remove_ipython_time_from_html) - app.connect('html-page-context', check_example_error) - app.connect('build-finished', remove_examples) - app.connect('build-finished', remove_doctree) - app.connect('build-finished', check_build_finished_without_error) - def check_pandoc_installed(): """Ensure that pandoc is installed """ @@ -244,6 +231,19 @@ def check_pandoc_installed(): except OSError: logger.error("Pandoc was not found, please add it to your path or install pypandoc-binary") +def setup(app): + app.add_directive('pprint', PrettyPrintDirective) + app.connect('autodoc-skip-member', autodoc_skip_member) + app.connect('builder-inited', copy_examples) + app.connect("builder-inited", check_pandoc_installed) + app.connect('source-read', add_ipython_time) + app.connect('source-read', adjust_image_path) + app.connect('html-page-context', remove_ipython_time_from_html) + app.connect('html-page-context', check_example_error) + app.connect('build-finished', remove_examples) + app.connect('build-finished', remove_doctree) + app.connect('build-finished', check_build_finished_without_error) + local_path = os.path.dirname(os.path.realpath(__file__)) module_path = pathlib.Path(local_path) root_path = module_path.parent.parent From a2c26d3ac4d9cdbcafd5cca41e57e3539b3a8144 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 16:51:19 +0100 Subject: [PATCH 54/56] WIP: fix check pandoc --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 8a9753dff72..d9f3a523d89 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -218,7 +218,7 @@ def check_build_finished_without_error(app, exception): if app.builder.config.html_context.get('build_error', False): raise Exception('Build failed due to error in html-page-context') -def check_pandoc_installed(): +def check_pandoc_installed(app): """Ensure that pandoc is installed """ import pypandoc From 2b68c129ce4f9428f41d8e787d1859e62b9b6a0a Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Wed, 7 Feb 2024 16:57:50 +0100 Subject: [PATCH 55/56] WIP: temporary remove doc build --- .github/workflows/build_documentation.yml | 99 ----------------------- 1 file changed, 99 deletions(-) delete mode 100644 .github/workflows/build_documentation.yml diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml deleted file mode 100644 index 2ea05d93373..00000000000 --- a/.github/workflows/build_documentation.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Documentation Build - -on: [pull_request, workflow_dispatch] - -env: - # Following env vars when changed will "reset" the mentioned cache, - # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-... - # You should go up in number, if you go down (or repeat a previous value) - # you might end up reusing a previous cache if it haven't been deleted already. - # It applies 7 days retention policy by default. - RESET_EXAMPLES_CACHE: 3 - RESET_DOC_BUILD_CACHE: 3 - RESET_AUTOSUMMARY_CACHE: 3 - - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - docs-style: - name: "Check documentation style" - runs-on: ubuntu-latest - steps: - - name: "Check documentation style" - uses: ansys/actions/doc-style@v5 - with: - token: ${{ secrets.GITHUB_TOKEN }} - vale-config: "doc/.vale.ini" - vale-version: "2.29.6" - - docs_build: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Update pip - run: | - pip install --upgrade pip - - - name: Install pyaedt - run: | - pip install .[doc] - - - name: Verify pyaedt can be imported - run: python -c "import pyaedt" - - - name: Retrieve PyAEDT version - id: version - run: | - echo "PYAEDT_VERSION=$(python -c 'from pyaedt import __version__; print(__version__)')" >> $GITHUB_OUTPUT - echo "PyAEDT version is: $(python -c "from pyaedt import __version__; print(__version__)")" - - # - name: Cache docs build directory - # uses: actions/cache@v3 - # with: - # path: doc/build - # key: doc-build-v${{ env.RESET_DOC_BUILD_CACHE }}-${{ steps.version.outputs.PYAEDT_VERSION }}-${{ github.sha }} - # restore-keys: | - # doc-build-v${{ env.RESET_DOC_BUILD_CACHE }}-${{ steps.version.outputs.PYAEDT_VERSION }} - # - name: Cache autosummary - # uses: actions/cache@v3 - # with: - # path: doc/source/**/_autosummary/*.rst - # key: autosummary-v${{ env.RESET_AUTOSUMMARY_CACHE }}-${{ steps.version.outputs.PYAEDT_VERSION }}-${{ github.sha }} - # restore-keys: | - # autosummary-v${{ env.RESET_AUTOSUMMARY_CACHE }}-${{ steps.version.outputs.PYAEDT_VERSION }} - - - name: Install doc build requirements - run: | - sudo apt install -y graphviz pandoc - - # run doc build, without creating the examples directory - # note that we have to add the examples file here since it won't - # be created as gallery is disabled on linux. - - name: Documentation Build - run: | - make -C doc clean - mkdir doc/source/examples -p - echo $'Examples\n========' > doc/source/examples/index.rst - make -C doc html SPHINXOPTS="-j auto -w build_errors.txt -N" - - # Verify that sphinx generates no warnings - - name: Check for warnings - run: | - python doc/print_errors.py - -# - name: Upload Documentation -# uses: actions/upload-artifact@v4 -# with: -# name: Documentation -# path: doc/_build/html -# retention-days: 7 From c8d74f131e4af67712a498318a0fdd2e875b1497 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Thu, 8 Feb 2024 09:09:19 +0100 Subject: [PATCH 56/56] FIX: example format --- examples/00-EDB/12_edb_sma_connector_on_board.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py index 461c948ea7a..e590988f7bc 100644 --- a/examples/00-EDB/12_edb_sma_connector_on_board.py +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -181,15 +181,16 @@ h3d.analyze(num_cores=4) -# Visualize the return loss. +# ## Visualize the return loss. + h3d.post.create_report("dB(S(port_1, port_1))") -# Save and close the project. +# ## Save and close the project. h3d.save_project() print("Project is saved to {}".format(h3d.project_path)) h3d.release_desktop(True, True) -# Clean up the temporary folder. +# ## Clean up the temporary folder. temp_dir.cleanup()