diff --git a/.github/labeler.yml b/.github/labeler.yml index daadd414b4e..a6f0f67aee5 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,6 +1,7 @@ documentation: - doc/source/**/* -- README.rst +- README.md +- README_CN.md maintenance: - .github/**/* - .flake8 diff --git a/.github/workflows/ironpython.yml b/.github/workflows/ironpython.yml index 70f1b6ea4e7..658d948108f 100644 --- a/.github/workflows/ironpython.yml +++ b/.github/workflows/ironpython.yml @@ -31,6 +31,7 @@ jobs: Set-Item -Path env:ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE -Value "1" Set-Item -Path env:ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE -Value "1" Set-Item -Path env:ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE -Value "1" + Set-Item -Path env:ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE -Value "1" $processA = start-process 'cmd' -ArgumentList '/c .\_unittest_ironpython\run_unittests_batchmode.cmd --test-filter test_0*.py' -PassThru $processB = start-process 'cmd' -ArgumentList '/c .\_unittest_ironpython\run_unittests_batchmode.cmd --test-filter test_1*.py' -PassThru $processC = start-process 'cmd' -ArgumentList '/c .\_unittest_ironpython\run_unittests_batchmode.cmd --test-filter test_2*.py' -PassThru diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 898a0067025..42db1b74d4e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,4 +69,4 @@ repos: # additional_dependencies: [toml] # files: ^pyaedt/ # args: -# - --ignore=D412,D413,D213,D101,D107,D203,D102 \ No newline at end of file +# - --ignore=D412,D413,D213,D101,D107,D203,D102, D205 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000000..51cbed12771 --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ + + + + + +# PyAEDT + +

+
English | 中文 +

+ +[![PyAnsys](https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC)](https://docs.pyansys.com/)[![pypi](https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white)](https://pypi.org/project/pyaedt/)[![PyPIact](https://pepy.tech/badge/pyaedt/month)](https://pypi.org/project/pyaedt/)[![PythonVersion](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/)[![GH-CI](https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml)[![codecov](https://codecov.io/gh/pyansys/pyaedt/branch/main/graph/badge.svg)](https://codecov.io/gh/pyansys/pyaedt)[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)[![black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat)](https://github.com/psf/black)[![Anaconda](https://anaconda.org/conda-forge/pyaedt/badges/version.svg)](https://anaconda.org/conda-forge/pyaedt)[![pre-commit](https://results.pre-commit.ci/badge/github/pyansys/pyaedt/main.svg)](https://results.pre-commit.ci/latest/github/pyansys/pyaedt/main) + +## What is PyAEDT? + +`PyAEDT` is a Python library that interacts directly with the API for +Ansys Electronics Desktop (AEDT) to make scripting simpler. The architecture +for PyAEDT can be reused for all AEDT 3D products (HFSS, Icepak, Maxwell 3D, +and Q3D Extractor), 2D tools, and Ansys Mechanical. PyAEDT also provides +support for circuit tools like Nexxim and system simulation tools like +Twin Builder. Finally, PyAEDT provides scripting capabilities in Ansys layout +tools like HFSS 3D Layout and EDB. The PyAEDT class and method structures +simplify operation while reusing information as much as possible across +the API. + +## Install on CPython from PyPI + +You can install PyAEDT on CPython 3.7 through 3.10 from PyPI with this command: + +```sh + pip install pyaedt +``` + +Install `PyAEDT` with all extra packages (matplotlib, numpy, pandas, pyvista): + +```sh + pip install pyaedt[full] +``` + +You can also install PyAEDT from Conda-Forge with this command: + +```sh + conda install -c conda-forge pyaedt +``` +PyAEDT remains compatible with IronPython and can be still used in the AEDT Framework. + +## About PyAnsys + +`PyAEDT` is part of the larger [PyAnsys](https://docs.pyansys.com "PyAnsys") effort to facilitate the use of Ansys technologies directly from Python. + +PyAEDT is intended to consolidate and extend all existing +functionalities around scripting for AEDT to allow reuse of existing code, +sharing of best practices, and increased collaboration. + +## About AEDT + +[AEDT](https://www.ansys.com/products/electronics) 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, AEDT 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. + +

+ +

+ +`PyAEDT` is licensed under the [MIT License](https://github.com/pyansys/PyAEDT/blob/main/LICENSE) + +`PyAEDT` includes functionality for interacting with the following `AEDT tools` and `Ansys products`: + + - HFSS and HFSS 3D Layout + - Icepak + - Maxwell 2D, Maxwell 3D, and RMXprt + - 2D Extractor and Q3D Extractor + - Mechanical + - Nexxim + - EDB + - Twin Builder + +## Documentation and issues + +In addition to installation and usage information, the `PyAEDT` documentation provides [API reference](https://aedt.docs.pyansys.com/version/stable), [Examples](https://aedt.docs.pyansys.com/version/stable/examples/index.html), and [Contribute](https://aedt.docs.pyansys.com/version/stable/Contributing.html) sections. + +In the upper right corner of the documentation's title bar, there is an option +for switching from viewing the documentation for the latest stable release +to viewing the documentation for the development version or previously +released versions. + +On the [PyAEDT Issues](https://github.com/pyansys/PyAEDT/issues) page, you can +create issues to submit questions, report bugs, and request new features. + +To reach the project support team, email `pyansys.core@ansys.com `_. + +## Dependencies + +To run `PyAEDT`, you must have a local licenced copy of AEDT. +`PyAEDT` supports AEDT versions 2022 R1 or newer. + +## Student version + +`PyAEDT` supports `AEDT Student version 2022 R1` and later. For more information, see [Student Version page](https://www.ansys.com/academic/students/ansys-electronics-desktop-student). + +## Why PyAEDT? + +A quick and easy approach for automating a simple operation in the AEDT UI is to record and reuse a script. However, disadvantages of this approach are: + + - Recorded code is dirty and difficult to read and understand. + - Recorded scripts are difficult to reuse and adapt. + - Complex coding is required by many global users of `AEDT`. + +The main advantages of `PyAEDT` are: + + - Automatic initialization of all `AEDT` objects, such as desktop objects like the editor, boundaries, and so on + - Error management + - Log management + - Variable management + - Compatibility with IronPython and CPython + - Simplification of complex API syntax using data objects while maintaining PEP8 compliance. + - Code reusability across different solvers + - Clear documentation on functions and API + - Unit tests of code to increase quality across different AEDT versions + +## Example workflow + + 1. Initialize the ``Desktop`` class with the version of `AEDT` to use. + 2. Initialize the application to use within `AEDT`. + +## Connect to AEDT from Python IDE + +``PyAEDT`` works both inside AEDT and as a standalone application. This Python library automatically detects whether it is running in an IronPython or CPython environment and initializes AEDT accordingly. +``PyAEDT`` also provides advanced error management. Usage examples follow. + +## Explicit AEDT declaration and error management + +``` python + # Launch AEDT 2022 R2 in non-graphical mode + + from pyaedt import Desktop, Circuit + with Desktop(specified_version="2022.2", + non_graphical=False, new_desktop_session=True, + close_on_exit=True, student_version=False): + circuit = Circuit() + ... + # Any error here will be caught by Desktop. + ... + + # Desktop is automatically released here. +``` + +## Implicit AEDT declaration and error management + +``` python + # Launch the latest installed version of AEDT in graphical mode + + from pyaedt import Circuit + with Circuit(specified_version="2022.2", + non_graphical=False) as circuit: + ... + # Any error here will be caught by Desktop. + ... + + # Desktop is automatically released here. +``` + +## Remote application call + +You can make a remote application call on a CPython server +or any Windows client machine. + +On a CPython Server: + +``` python + # Launch PyAEDT remote server on CPython + + from pyaedt.common_rpc import pyaedt_service_manager + pyaedt_service_manager() +``` + +On any Windows client machine: + +``` python + from pyaedt.common_rpc import create_session + cl1 = create_session("server_name") + cl1.aedt(port=50000, non_graphical=False) + hfss = Hfss(machine="server_name", port=50000) + # your code here +``` + +## Variables + +``` python + from pyaedt.HFSS import HFSS + with HFSS as hfss: + hfss["dim"] = "1mm" # design variable + hfss["$dim"] = "1mm" # project variable +``` + +## Modeler + +``` python + # Create a box, assign variables, and assign materials. + + from pyaedt.hfss import Hfss + with Hfss as hfss: + hfss.modeler.create_box([0, 0, 0], [10, "dim", 10], + "mybox", "aluminum") +``` + +## License + +`PyAEDT` is licensed under the `MIT` license. + +This module makes no commercial claim over Ansys whatsoever. `PyAEDT` extends the functionality of `AEDT` by adding an additional Python interface to `AEDT` without changing the core behavior or license of the original software. The use of the interactive control of `PyAEDT` requires a legally licensed local copy of `AEDT`. For more information about `AEDT`, visit the [AEDT page](https://www.ansys.com/products/electronics) on the `Ansys` website. + +

back to top

+ +## Indices and tables + +- [Index](https://aedt.docs.pyansys.com/version/stable/genindex.html) +- [Module Index](https://aedt.docs.pyansys.com/version/stable/py-modindex.html) +- [Search Page](https://aedt.docs.pyansys.com/version/stable/search.html) diff --git a/README.rst b/README.rst deleted file mode 100644 index 221b7a163ab..00000000000 --- a/README.rst +++ /dev/null @@ -1,282 +0,0 @@ -PyAEDT -====== - -|pyansys| |pypi| |PyPIact| |PythonVersion| |GH-CI| |codecov| |MIT| |black| |Anaconda| |pre-commit| - -.. |pyansys| image:: https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC - :target: https://docs.pyansys.com/ - :alt: PyAnsys - -.. |pypi| image:: https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white - :target: https://pypi.org/project/pyaedt/ - -.. |PyPIact| image:: https://pepy.tech/badge/pyaedt/month - :target: https://pypi.org/project/pyaedt/ - -.. |PythonVersion| image:: https://img.shields.io/badge/python-3.7+-blue.svg - :target: https://www.python.org/downloads/ - -.. |GH-CI| image:: https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg - :target: https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml - -.. |codecov| image:: https://codecov.io/gh/pyansys/pyaedt/branch/main/graph/badge.svg - :target: https://codecov.io/gh/pyansys/pyaedt - -.. |MIT| image:: https://img.shields.io/badge/License-MIT-yellow.svg - :target: https://opensource.org/licenses/MIT - -.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=flat - :target: https://github.com/psf/black - :alt: black - -.. |Anaconda| image:: https://anaconda.org/conda-forge/pyaedt/badges/version.svg - :target: https://anaconda.org/conda-forge/pyaedt - -.. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pyansys/pyaedt/main.svg - :target: https://results.pre-commit.ci/latest/github/pyansys/pyaedt/main - :alt: pre-commit.ci status - - -What is PyAEDT? ---------------- -PyAEDT is a Python library that interacts directly with the API for -Ansys Electronics Desktop (AEDT) to make scripting simpler. The architecture -for PyAEDT can be reused for all AEDT 3D products (HFSS, Icepak, Maxwell 3D, -and Q3D Extractor), 2D tools, and Ansys Mechanical. PyAEDT also provides -support for circuit tools like Nexxim and system simulation tools like -Twin Builder. Finally, PyAEDT provides scripting capabilities in Ansys layout -tools like HFSS 3D Layout and EDB. The PyAEDT class and method structures -simplify operation while reusing information as much as possible across -the API. - -Install on CPython from PyPI ----------------------------- -You can install PyAEDT on CPython 3.7 through 3.10 from PyPI with this command: - -.. code:: python - - pip install pyaedt - -To install PyAEDT with all extra packages (Matplotlib, NumPy, Pandas, and PyVista), -use this command: - -.. code:: python - - pip install pyaedt[full] - -You can also install PyAEDT from Conda-Forge with this command: - -.. code:: python - - conda install -c conda-forge pyaedt - -PyAEDT remains compatible with IronPython and can be still used in the AEDT Framework. - -About PyAnsys -------------- - -PyAEDT is part of the larger `PyAnsys `_ -effort to facilitate the use of Ansys technologies directly from Python. - -PyAEDT is intended to consolidate and extend all existing -functionalities around scripting for AEDT to allow reuse of existing code, -sharing of best practices, and increased collaboration. - - -About AEDT ----------- - -`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, AEDT 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:: https://images.ansys.com/is/image/ansys/ansys-electronics-technology-collage?wid=941&op_usm=0.9,1.0,20,0&fit=constrain,0 - :width: 800 - :alt: AEDT Applications - :target: https://www.ansys.com/products/electronics - - -PyAEDT is licensed under the `MIT License -`_. - -PyAEDT includes functionality for interacting with the following AEDT tools and Ansys products: - -- HFSS and HFSS 3D Layout -- Icepak -- Maxwell 2D, Maxwell 3D, and RMXprt -- 2D Extractor and Q3D Extractor -- Mechanical -- Nexxim -- EDB -- Twin Builder - - -Documentation and issues ------------------------- -Documentation for the latest stable release of PyAEDT is hosted at -`PyAEDT Documentation `_. - -In the upper right corner of the documentation's title bar, there is an option -for switching from viewing the documentation for the latest stable release -to viewing the documentation for the development version or previously -released versions. - -On the `PyAEDT Issues `_ page, you can -create issues to submit questions, report bugs, and request new features. - -To reach the project support team, email `pyansys.core@ansys.com `_. - -Dependencies ------------- -To run PyAEDT, you must have a local licensed copy of AEDT. -PyAEDT supports AEDT versions 2022 R1 and later. - -Student version ---------------- - -PyAEDT supports AEDT Student versions 2022 R1 and later. For more information, see -`Ansys Electronics Desktop Student - Free Software Download `_ on the Ansys website. - - -Why PyAEDT? ------------ -A quick and easy approach for automating a simple operation in the -AEDT UI is to record and reuse a script. However, here are some disadvantages of -this approach: - -- Recorded code is dirty and difficult to read and understand. -- Recorded scripts are difficult to reuse and adapt. -- Complex coding is required by many global users of AEDT. - -Here are the main advantages that PyAEDT provides: - -- Automatic initialization of all AEDT objects, such as desktop - objects like the editor, boundaries, and more -- Error management -- Log management -- Variable management -- Compatibility with IronPython and CPython -- Simplification of complex API syntax using data objects while - maintaining PEP8 compliance. -- Code reusability across different solvers -- Clear documentation on functions and API -- Unit tests of code to increase quality across different AEDT versions - - -Example workflow ------------------ -1. Initialize the ``Desktop`` class with the version of AEDT to use. -2. Initialize the application to use within AEDT. - - -Connect to AEDT from a Python IDE ---------------------------------- -PyAEDT works both inside AEDT and as a standalone app. -This Python library automatically detects whether it is running -in an IronPython or CPython environment and initializes AEDT accordingly. -PyAEDT also provides advanced error management. Usage examples follow. - -Explicit AEDT declaration and error management -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: python - - # Launch AEDT 2023 R1 in non-graphical mode - - from pyaedt import Desktop, Circuit - with 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. - ... - - # Desktop is automatically released here. - - -Implicit AEDT declaration and error management -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: python - - # Launch the latest installed version of AEDT in graphical mode - - from pyaedt import Circuit - with Circuit(specified_version="2023.1", - non_graphical=False) as circuit: - ... - # Any error here will be caught by Desktop. - ... - - # Desktop is automatically released here. - - -Remote application call -~~~~~~~~~~~~~~~~~~~~~~~ -You can make a remote application call on a CPython server -or any Windows client machine. - -On a CPython server: - -.. code:: python - - # Launch PyAEDT remote server on CPython - - from pyaedt.common_rpc import pyaedt_service_manager - pyaedt_service_manager() - - -On any Windows client machine: - -.. code:: python - - from pyaedt.common_rpc import create_session - cl1 = create_session("server_name") - cl1.aedt(port=50000, non_graphical=False) - hfss = Hfss(machine="server_name", port=50000) - # your code here - -Variables -~~~~~~~~~ - -.. code:: python - - from pyaedt.HFSS import HFSS - with HFSS as hfss: - hfss["dim"] = "1mm" # design variable - hfss["$dim"] = "1mm" # project variable - - -Modeler -~~~~~~~ - -.. code:: python - - # Create a box, assign variables, and assign materials. - - from pyaedt.hfss import Hfss - with Hfss as hfss: - hfss.modeler.create_box([0, 0, 0], [10, "dim", 10], - "mybox", "aluminum") - -License -------- -PyAEDT is licensed under the MIT license. - -This module makes no commercial claim over Ansys whatsoever. -PyAEDT extends the functionality of AEDT by adding -an additional Python interface to AEDT without changing the core -behavior or license of the original software. The use of the -interactive control of PyAEDT requires a legally licensed -local copy of AEDT. For more information about AEDT, -see the `Ansys Electronics `_ -page on the Ansys website. diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 00000000000..83c8e5b5844 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,215 @@ + + + + +# PyAEDT + +

+
English | 中文 +

+ +[![PyAnsys](https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC)](https://docs.pyansys.com/)[![pypi](https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white)](https://pypi.org/project/pyaedt/)[![PyPIact](https://pepy.tech/badge/pyaedt/month)](https://pypi.org/project/pyaedt/)[![PythonVersion](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/)[![GH-CI](https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml)[![codecov](https://codecov.io/gh/pyansys/pyaedt/branch/main/graph/badge.svg)](https://codecov.io/gh/pyansys/pyaedt)[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)[![black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat)](https://github.com/psf/black)[![Anaconda](https://anaconda.org/conda-forge/pyaedt/badges/version.svg)](https://anaconda.org/conda-forge/pyaedt)[![pre-commit](https://results.pre-commit.ci/badge/github/pyansys/pyaedt/main.svg)](https://results.pre-commit.ci/latest/github/pyansys/pyaedt/main) + +## PyAEDT 简介 + +`PyAEDT` 是一个直接与 `AEDT API` 交互的 `Python` 库。使最终用户的脚本编写更简易。它的框架可重复使用,适用于所有 AEDT 3D 产品(`HFSS`、`Icepak`、`Maxwell 3D` 和 `Q3D Extractor`)、`2D tools` 和 `Ansys Mechanical`。它支持像 `Nexxim` 这样的电路工具和 `Twin Builder` 这样的系统模拟工具。最后它为 `HFSS 3D Layout` 和 `EDB` 等 `Ansys` 布局工具提供脚本功能。它的类方法结构简化了最终用户的操作,同时尽可能在整个 API 中复用信息。 + +## 关于 PyAEDT 的安装 + +1. 您可以使用 `PyPI` 工具将 `PyAEDT` 安装在 `CPython 3.7-3.10` 版本上: +``` python + pip install pyaedt +``` + +2. 当您同时需要其他数据处理时可以选择安装 `PyAEDT` 和以下对应的库(`matplotlib`, `numpy`, `pandas`, `pyvista`): + +```sh + pip install pyaedt[all] + pip install matplotlib + pip install numpy + pip install pandas + pip install pyvista +``` + +3. 您也可以从 `Conda-Forge` 安装 `PyAEDT` : + +```sh + conda install -c conda-forge pyaedt +``` + +`PyAEDT` 与 `Ironpython` 兼容,仍然可以在 `AEDT` 框架中使用。 + +备注(2023年3月22日):`PyAEDT`的主要贡献者决定减少默认包的内存占用。如果你运行 pip install pyaedt,以下安装是必须的(在拥有PyAEDT基础功能的情况下): +``` +"cffi == 1.15.0;platform_system=='Linux'", +"pywin32 >= 303;platform_system=='Windows'", +"pythonnet == 3.0.1", +"rpyc==5.3.0", +"psutil", +"dotnetcore2 ==3.1.23;platform_system=='Linux'" +``` +4. 如果你需要其他库来做后期处理,可以使用以下方法来安装它们: +```sh +pip install pyaedt[full] +``` + +## 关于 PyAEDT + +`PyAEDT` 是 [PyAnsys](https://docs.pyansys.com "PyAnsys") 的一部分,后者更庞大。努力促进直接从 `Python` 使用 `Ansys` 技术。 + +`PyAEDT` 旨在整合和扩展 `Ansys Electronics Desktop (AEDT)` 脚本的所有现有功能,以允许重用现有代码、共享最佳实践并加强协作。 + + ## 关于 AEDT + +`Ansys Electronics Desktop (AEDT)` 是一个真正支持电子系统设计的平台。[AEDT](https://www.ansys.com/products/electronics) 使用 `电气CAD(ECAD)` 和 `机械CAD(MCAD)` 工作流程提供对 Ansys gold-standard electro-magnetics 仿真解决方案的访问,例如 `Ansys HFSS`、`Ansys Maxwell`、`Ansys Q3D Extractor`、`Ansys Siwave` 和 `Ansys Icepak`。此外,它还包括与完整的 `Ansys热求解器(Thermal)`、`流体求解器(Fluid)` 和 `机械求解器(Mechanical)` 产品组合的直接连接,用于全面的多物理场分析。这些解决方案之间的紧密集成提供了前所未有的设置易用性,并更快地解决了用于设计和优化的复杂仿真。 + +

+ +

+ +`PyAEDT` 遵循 [MIT](https://github.com/pyansys/PyAEDT/blob/main/LICENSE "MIT") 许可证,并包括与 `AEDT tools` 和 `Ansys products` 交互的功能: + +- HFSS and HFSS 3D Layout +- Icepak +- Maxwell 2D, Maxwell 3D, and RMXprt +- 2D Extractor and Q3D Extractor +- Mechanical +- Nexxim +- EDB +- Twin Builder + +## 文档与反馈 + +除了安装和使用信息外,`PyAEDT` 文档还提供了 [参考API](https://aedt.docs.pyansys.com/version/stable/API/index.html "API reference"),[示例](https://aedt.docs.pyansys.com/version/stable/examples/index.html "Examples"),和 [贡献](https://aedt.docs.pyansys.com/version/stable/Contributing.html "Contribute") 。 + + +在 [PyAEDT Issues](https://github.com/pyansys/PyAEDT/issues) 页面,您可以提交所遇到的问题、反馈 bug、为新功能贡献您的想法与思路。如联系项目支持团队,请点击后面的链接发送电子邮件 [pyansys.support@ansys.com](pyansys.support@ansys.com). + +## 适用范围 + +`PyAEDT` 支持 `AEDT 2022 R1` 或更高的版本。 + +`PyAEDT` 支持 `AEDT Student version 2022 R1` 或更高的版本。 + +关于学生版本的更多信息请查阅 [Student Version page](https://www.ansys.com/academic/students/ansys-electronics-desktop-student). + +## 为什么选择 PyAEDT + +在 AEDT UI 中使用记录和重用脚本自动执行是简化操作的一种快速又简便的办法。但是,此方法有以下缺陷: + +- 记录的代码比较混乱,难以阅读与理解。 +- 录制的脚本难以复用和二次编辑。 +- AEDT 多个全局用户会生成复杂庞大的代码。 + +`PyAEDT` 的主要优点是: + +- 自动初始化所有AEDT项目,例如desktop项目中的编辑器、边界等等 +- 清晰的错误管理 +- 详细的日志管理 +- 自由的变量管理 +- 兼容 IronPython 和 CPython +- 使用数据对象简化复杂的 API 语法,同时保持 PEP8 合规性。 +- 在不同求解器之间的可复用代码 +- 在函数与API上拥有清晰的文档 +- 代码单元测试,以提高不同 AEDT 版本的质量 + +## 工作流程示例 + + 1. 在所需的`AEDT`版本中初始化`desktop`类。 + 2. 并初始化`AEDT`中所要使用的应用程序。 + +## 使用 Python IDE 连接 AEDT + +`PyAEDT` 既可以在AEDT内部工作,也可以作为独立应用程序使用。此 Python 库会自动检测它是否正在运行在 IronPython 或 CPython 环境中并相应地初始化 AEDT。 +`PyAEDT` 还提供高级错误管理。使用示例如下: + +### 明确 AEDT 声明 [INFO] 并管理报错 [ERROR] + +``` python + # 以非图形模式启动 AEDT 2022 R2 + + from pyaedt import Desktop, Circuit + with Desktop(specified_version="2022.2", + non_graphical=False, new_desktop_session=True, + close_on_exit=True, student_version=False): + circuit = Circuit() + ... + # 此处的任何错误都将被 Desktop 捕获。 + ... + + # Desktop 将在此处自动发布。 +``` + +### Implicit AEDT declaration and error management + +``` python + # Launch the latest installed version of AEDT in graphical mode + + from pyaedt import Circuit + with Circuit(specified_version="2022.2", + non_graphical=False) as circuit: + ... + # Any error here will be caught by Desktop. + ... + + # Desktop is automatically released here. +``` + +## 远程应用程序调用 + +您可以在 CPython 服务器或任何 Windows 客户端计算机上进行远程应用程序调用。 + +CPython 服务器: + +``` python + # 在CPython上启动PyAEDT远程服务器 + + from pyaedt.common_rpc import pyaedt_service_manager + pyaedt_service_manager() +``` + +任意的 Windows 客户端: + +``` python + from pyaedt.common_rpc import create_session + cl1 = create_session("server_name") + cl1.aedt(port=50000, non_graphical=False) + hfss = Hfss(machine="server_name", port=50000) + # 在这里编辑您的代码 +``` + +## 变量 Variables + +``` python + from pyaedt.HFSS import HFSS + with HFSS as hfss: + hfss["dim"] = "1mm" # design variable + hfss["$dim"] = "1mm" # project variable +``` + +## 模型 Modeler + +``` python + # 创建BOX、分配变量和指定材料。 + + from pyaedt.hfss import Hfss + with Hfss as hfss: + hfss.modeler.create_box([0, 0, 0], [10, "dim", 10], + "mybox", "aluminum") +``` + +## 许可 + +要运行`PyAEDT`,您必须拥有`AEDT`的本地许可证。 +`PyAEDT`在`MIT`许可下获得许可。这个模块对`Ansys`没有任何商业要求。`PyAEDT`通过向`AEDT`添加额外的Python接口扩展了`AEDT`的功能,而不改变原始软件的核心行为或许可。使用`PyAEDT`的交互式控件需要合法授权的`AEDT`本地license。有关`AEDT`的更多信息,请访问`Ansys`网站上的[AEDT页面](https://www.ansys.com/products/electronics)。 + +

回到顶部

+ +## 索引与目录 + +- [Index](https://aedt.docs.pyansys.com/version/stable/genindex.html) +- [Module Index](https://aedt.docs.pyansys.com/version/stable/py-modindex.html) +- [Search Page](https://aedt.docs.pyansys.com/version/stable/search.html) diff --git a/_unittest/example_models/T08/Layoutcomponent_231.aedbcomp b/_unittest/example_models/T08/Layoutcomponent_231.aedbcomp new file mode 100644 index 00000000000..6817d49bdf9 Binary files /dev/null and b/_unittest/example_models/T08/Layoutcomponent_231.aedbcomp differ diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index a8ed30988c4..874cfb570ee 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -765,7 +765,8 @@ def test_068_create_polygon(self): [-0.001, -0.001], [0.001, -0.001, "ccw", 0.0, -0.0012], [0.001, 0.001], - [-0.001, 0.001], + [0.0015, 0.0015, 0.0001], + [-0.001, 0.0015], [-0.001, -0.001], ] void1 = self.edbapp.modeler.Shape("polygon", points=points) @@ -2397,3 +2398,13 @@ def test_134_hfss_extent_info(self): assert j.tofloat == hfss_extent_info._get_edb_value(config[i]).ToDouble() else: assert j == config[i] + + def test_134_create_port_on_pin(self): + source_path = os.path.join(local_path, "example_models", test_subfolder, "Galileo.aedb") + target_path = os.path.join(self.local_scratch.path, "test_0134.aedb") + self.local_scratch.copyfolder(source_path, target_path) + edbapp = Edb(target_path, edbversion=desktop_version) + pin = "AJ6" + ref_pins = [pin for pin in list(edbapp.components["U2A5"].pins.values()) if pin.net_name == "GND"] + term = edbapp.components.create_port_on_pins(refdes="U2A5", pins=pin, reference_pins=ref_pins) + assert term diff --git a/_unittest/test_07_Object3D.py b/_unittest/test_07_Object3D.py index b55a7c8fc07..61c6f94d62e 100644 --- a/_unittest/test_07_Object3D.py +++ b/_unittest/test_07_Object3D.py @@ -169,16 +169,24 @@ def test_03_FacePrimitive(self): assert isinstance(o_box.longest_edge()[0], EdgePrimitive) assert isinstance(o_box.shortest_edge()[0], EdgePrimitive) - def test_04_object_material_property_invalid(self): + def test_04a_object_material_property_invalid(self): o_box = self.create_copper_box("Invalid1") o_box.material_name = "Copper1234Invalid" assert o_box.material_name == "copper" - def test_04_object_material_property_valid(self): + def test_04b_object_material_property_valid(self): o_box = self.create_copper_box("Valid2") o_box.material_name = "aluminum" assert o_box.material_name == "aluminum" + def test_04c_material_name_setter(self): + self.aedtapp.materials.add_material("myMat") + self.aedtapp.materials.add_material("myMat2") + self.aedtapp["mat_sweep_test"] = '["myMat", "myMat2"]' + box = self.aedtapp.modeler["MyBox"] + box.material_name = "mat_sweep_test[0]" + assert self.aedtapp.modeler.get_objects_by_material(materialname="mat_sweep_test[0]")[0].name == "MyBox" + def test_05_object3d_properties_transparency(self): o = self.create_copper_box("TransparencyBox") diff --git a/_unittest/test_08_Primitives3D.py b/_unittest/test_08_Primitives3D.py index 50e7c2b1faf..253cb85640d 100644 --- a/_unittest/test_08_Primitives3D.py +++ b/_unittest/test_08_Primitives3D.py @@ -28,6 +28,7 @@ step = "input.stp" component3d = "new.a3dcomp" encrypted_cylinder = "encrypted_cylinder.a3dcomp" +layout_comp = "Layoutcomponent_231.aedbcomp" test_subfolder = "T08" if config["desktopVersion"] > "2022.2": assembly = "assembly_231" @@ -57,6 +58,7 @@ def setup_class(self): self.flatten = BasisTest.add_app(self, project_name=components_flatten, subfolder=test_subfolder) test_54b_project = os.path.join(local_path, "example_models", test_subfolder, polyline + ".aedt") self.test_54b_project = self.local_scratch.copyfile(test_54b_project) + self.layout_component = os.path.join(local_path, "example_models", test_subfolder, layout_comp) def teardown_class(self): BasisTest.my_teardown(self) @@ -1731,3 +1733,29 @@ def test_84_replace_3dcomponent(self): object_list=[box2.name], ) assert len(self.aedtapp.modeler.user_defined_components) == 2 + + @pytest.mark.skipif( + config["desktopVersion"] < "2023.1" or is_ironpython, reason="Method available in beta from 2023.1" + ) + def test_85_insert_layoutcomponent(self): + self.aedtapp.insert_design("LayoutComponent") + self.aedtapp.solution_type = "Modal" + assert not self.aedtapp.modeler.insert_layout_component( + self.layout_component, name=None, parameter_mapping=False + ) + self.aedtapp.solution_type = "Terminal" + comp = self.aedtapp.modeler.insert_layout_component(self.layout_component, name=None, parameter_mapping=False) + assert isinstance(comp, UserDefinedComponent) + assert len(self.aedtapp.modeler.modeler.user_defined_components[comp.name].parts) == 3 + comp2 = self.aedtapp.modeler.insert_layout_component( + self.layout_component, name="new_layout", parameter_mapping=True + ) + assert isinstance(comp2, UserDefinedComponent) + assert len(comp2.parameters) == 2 + assert comp2.show_layout + comp2.show_layout = False + assert not comp2.show_layout + comp2.show_layout = True + assert comp2.fast_transformation + comp2.fast_transformation = False + assert not comp2.fast_transformation diff --git a/_unittest/test_31_Q3D.py b/_unittest/test_31_Q3D.py index 2a73d4297f7..e1bd56abc67 100644 --- a/_unittest/test_31_Q3D.py +++ b/_unittest/test_31_Q3D.py @@ -53,6 +53,11 @@ def test_06a_create_setup(self): mysetup = self.aedtapp.create_setup() mysetup.props["SaveFields"] = True assert mysetup.update() + assert mysetup.dc_enabled + mysetup.dc_resistance_only = True + assert mysetup.dc_resistance_only + mysetup.dc_enabled = False + mysetup.dc_enabled = True sweep = self.aedtapp.create_discrete_sweep(mysetup.name, sweepname="mysweep", freqstart=1, units="GHz") assert sweep assert sweep.props["RangeStart"] == "1GHz" diff --git a/_unittest/test_40_3dlayout_edb.py b/_unittest/test_40_3dlayout_edb.py index e023c4ab3cd..bb1bdb6aff4 100644 --- a/_unittest/test_40_3dlayout_edb.py +++ b/_unittest/test_40_3dlayout_edb.py @@ -312,12 +312,18 @@ def test_17_ports_on_components_nets(self): if "GND" not in self.aedtapp.modeler.pins[i].net_name ] ports_before = len(self.aedtapp.port_list) - assert self.aedtapp.create_ports_on_component_by_nets( + ports = self.aedtapp.create_ports_on_component_by_nets( "J1", nets, ) + assert ports ports_after = len(self.aedtapp.port_list) assert ports_after - ports_before == len(nets) + ports[0].name = "port_test" + assert ports[0].name == "port_test" + assert ports[0].props["Port"] == "port_test" + ports[0].props["Port"] = "port_test2" + assert ports[0].name == "port_test2" def test_18_set_variable(self): self.aedtapp.variable_manager.set_variable("var_test", expression="123") diff --git a/doc/source/Contributing.rst b/doc/source/Getting_started/Contributing.rst similarity index 100% rename from doc/source/Contributing.rst rename to doc/source/Getting_started/Contributing.rst diff --git a/doc/source/Getting_started/Installation.rst b/doc/source/Getting_started/Installation.rst index 0b28fe6b80a..8dc4b2c37af 100644 --- a/doc/source/Getting_started/Installation.rst +++ b/doc/source/Getting_started/Installation.rst @@ -57,12 +57,30 @@ For example, on Windows with Python 3.7, install PyAEDT and all its dependencies pip install --no-cache-dir --no-index --find-links=file:////PyAEDT-v-wheelhouse-Windows-3.7 pyaedt +Install from a Python file +~~~~~~~~~~~~~~~~~~~~~~~~~~ +AEDT already includes CPython 3.7, which can be used to run PyAEDT. +It is also possible to use CPython 3.7 (3.10 from AEDT 2023R2) as a virtual environment to run PyAEDT. +In order to do that you can download the following file +:download:`PyAEDT Installer python file <../Resources/PyAEDTInstallerFromDesktop.py>` +Open an Electronics Desktop Session and click on Tools->Run Script and execute the file. + +After installation a new menu appears in AEDT Menu as in the image below. + +.. image:: ../Resources/toolkits.png + :width: 800 + :alt: PyAEDT toolkit installed after batch run + + +Starting from 2023R2, a Ribbon button is available in Automation Tab as in the example below. + +.. image:: ../Resources/toolkits_ribbon.png + :width: 800 + :alt: PyAEDT toolkit buttons available in AEDT 2023.2 after batch run + Install from a batch file ~~~~~~~~~~~~~~~~~~~~~~~~~ -AEDT already includes CPython 3.7, which can be used to run PyAEDT. -It is also possible to use CPython 3.7 as a virtual environment to run PyAEDT. - If you are running on Windows, you can download :download:`PyAEDT Environment with IDE bat file <../Resources/pyaedt_with_IDE.bat>` and run this batch file on your local machine. Using this approach diff --git a/doc/source/Getting_started/index.rst b/doc/source/Getting_started/index.rst index f4940382187..86f861e09cd 100644 --- a/doc/source/Getting_started/index.rst +++ b/doc/source/Getting_started/index.rst @@ -31,4 +31,6 @@ on the Ansys website. Installation ClientServer + versioning + Contributing diff --git a/doc/source/Getting_started/versioning.rst b/doc/source/Getting_started/versioning.rst new file mode 100644 index 00000000000..f4ce39a4e28 --- /dev/null +++ b/doc/source/Getting_started/versioning.rst @@ -0,0 +1,75 @@ +.. _versions_and_interfaces: + +======================= +Versions and interfaces +======================= + +The PyAEDT project attempts to maintain compatibility with legacy +versions of AEDT while allowing for support of faster and better +interfaces with the latest versions of AEDT. + +There are two interfaces PyAEDT can use to connect to AEDT. +You can see a table with the AEDT version and the supported interfaces +in `Table of supported versions `_ + + +gRPC interface +============== + +This is the default and preferred interface to connect to AEDT. +Ansys 2022 R2 and later support the latest gRPC interface, allowing +for remote management of MAPDL with rapid streaming of mesh, results, +and files from the MAPDL service. + + +Legacy interfaces +================= + +COM interface +-------------- + +AnsysEM supports the legacy COM interface, enabled with the settings option. + +This interface works only on Windows and uses .NET COM objects. + + +.. code:: python + + + from pyaedt import settings + + settings.use_grpc_api = False + + + +Compatibility between AEDT and interfaces +========================================= + +The following table shows the supported versions of Ansys EDT and the recommended interface for each one of them in PyAEDT. + + +**Table of supported versions** + +.. _table_versions: + ++---------------------------+------------------------+-----------------------------------------------+ +| Ansys Version | Recommended interface | Support | +| | +-----------------------+-----------------------+ +| | | gRPC | COM | ++===========================+========================+=======================+=======================+ +| AnsysEM 2023 R2 | gRPC | YES | YES* | ++---------------------------+------------------------+-----------------------+-----------------------+ +| AnsysEM 2023 R1 | gRPC | YES | YES* | ++---------------------------+------------------------+-----------------------+-----------------------+ +| AnsysEM 2022 R2 | gRPC | YES* | YES | ++---------------------------+------------------------+-----------------------+-----------------------+ +| AnsysEM 2022 R1 | gRPC | NO | YES | ++---------------------------+------------------------+-----------------------+-----------------------+ +| AnsysEM 2021 R2 | gRPC | NO | YES | ++---------------------------+------------------------+-----------------------+-----------------------+ + +Where: + +* YES means that the interface is supported and recommended. +* YES* means that the interface is supported, but not recommended. Their support might be dropped in the future. +* NO means that the interface is not supported. \ No newline at end of file diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py new file mode 100644 index 00000000000..c313aeedc6e --- /dev/null +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -0,0 +1,146 @@ +import argparse +import os +import sys +import platform + +is_iron_python = platform.python_implementation().lower() == "ironpython" +is_linux = os.name == "posix" +is_windows = not is_linux + + +def run_pyinstaller_from_c_python(oDesktop): + # This is called when run from IronPython + version = oDesktop.GetVersion()[2:6].replace(".", "") + python_version = "3.10" + if version <= "231": + python_version = "3.7" + edt_root = os.path.normpath(oDesktop.GetExeDir()) + if is_windows: + python_exe = os.path.normpath(os.path.join(edt_root, "commonfiles", "CPython", python_version.replace(".", "_"), + "winx64", "Release", "python", "python.exe")) + else: + python_exe = os.path.normpath(os.path.join(edt_root, "commonfiles", "CPython", python_version.replace(".", "_"), + "linx64", "Release", "python", "runpython")) + command = ['"{}"'.format(python_exe), '"{}"'.format(os.path.normpath(__file__)), "--version=" + version] + if is_student_version(oDesktop): + command.append("--student") + if is_linux: + command.extend(['--edt_root="{}"'.format(edt_root), '--python_version="{}"'.format(python_version)]) + + oDesktop.AddMessage("", "", 0, "Installing PyAEDT.") + if is_windows: + import subprocess + process = subprocess.Popen(" ".join(command)) + process.wait() + return_code = process.returncode + err_msg = "There was an error while installing PyAEDT." + else: + return_code = run_command(" ".join(command)) + err_msg = "There was an error while installing PyAEDT. Refer to the Terminal window where AEDT was launched " \ + "from." + + if version >= "232": + oDesktop.RefreshToolkitUI() + + if str(return_code) != "0": + oDesktop.AddMessage("", "", 2, err_msg) + return + + msg = "PyAEDT setup complete." + if is_linux: + msg += " Please ensure Ansys Electronics Desktop is launched in gRPC mode (i.e. launch ansysedt with -grpcsrv" \ + " argument) to take advantage of the new toolkits." + from System.Windows.Forms import MessageBox, MessageBoxButtons, MessageBoxIcon + oDesktop.AddMessage("", "", 0, msg) + MessageBox.Show(msg, 'Info', MessageBoxButtons.OK, MessageBoxIcon.Information) + + +def parse_arguments_for_pyaedt_installer(args=None): + parser = argparse.ArgumentParser(description="Install PyAEDT") + if is_linux: + parser.add_argument("--edt_root", help="AEDT's path (required for Linux)", required=True) + parser.add_argument("--python_version", help="Python version (required for Linux)", required=True) + parser.add_argument("--version", "-v", help="AEDT's 3 digit version", required=True) + parser.add_argument("--student", "--student_version", "-sv", help="Is Student version", action="store_true") + args = parser.parse_args(args) + if len(sys.argv[1:]) == 0 and args is None: + parser.print_help() + parser.error("No arguments given!") + return args + + +def install_pyaedt(): + # This is called when run from CPython + args = parse_arguments_for_pyaedt_installer() + if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "v" + args.version) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + pip_exe = os.path.join(venv_dir, "Scripts", "pip.exe") + else: + venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "v" + args.version) + python_exe = os.path.join(venv_dir, "bin", "python") + pip_exe = os.path.join(venv_dir, "bin", "pip") + os.environ["ANSYSEM_ROOT{}".format(args.version)] = args.edt_root + ld_library_path_dirs_to_add = [ + "{}/commonfiles/CPython/{}/linx64/Release/python/lib".format(args.edt_root, + args.python_version.replace(".", "_")), + "{}/common/mono/Linux64/lib64".format(args.edt_root), + "{}/Delcross".format(args.edt_root), + "{}".format(args.edt_root), + ] + os.environ["LD_LIBRARY_PATH"] = ":".join(ld_library_path_dirs_to_add) + ":" + os.getenv("LD_LIBRARY_PATH", "") + + if not os.path.exists(venv_dir): + run_command('"{}" -m venv "{}"'.format(sys.executable, venv_dir)) + + run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) + run_command('"{}" --default-timeout=1000 install wheel'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install pyaedt[full]'.format(pip_exe)) + # run_command('"{}" --default-timeout=1000 install git+https://github.com/pyansys/pyaedt.git@main'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install jupyterlab'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install ipython -U'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install ipyvtklink'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install pyside6==6.4.0'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install pyqtgraph'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install qdarkstyle'.format(pip_exe)) + else: + run_command('"{}" uninstall --yes pyaedt'.format(pip_exe)) + run_command('"{}" --default-timeout=1000 install pyaedt[full]'.format(pip_exe)) + + if is_windows: + pyaedt_setup_script = "{}/Lib/site-packages/pyaedt/misc/aedtlib_personalib_install.py".format(venv_dir) + else: + pyaedt_setup_script = "{}/lib/python{}/site-packages/pyaedt/misc/aedtlib_personalib_install.py".format( + venv_dir, args.python_version) + + if not os.path.isfile(pyaedt_setup_script): + sys.exit("[ERROR] PyAEDT was not setup properly since {} file does not exist.".format(pyaedt_setup_script)) + + command = '"{}" "{}" --version={}'.format(python_exe, pyaedt_setup_script, args.version) + if args.student: + command += " --student" + run_command(command) + sys.exit(0) + + +def is_student_version(oDesktop): + edt_root = os.path.normpath(oDesktop.GetExeDir()) + if is_windows and os.path.isdir(edt_root): + if any("ansysedtsv" in fn.lower() for fn in os.listdir(edt_root)): + return True + return False + + +def run_command(command): + if is_windows: + command = '"{}"'.format(command) + ret_code = os.system(command) + return ret_code + + +if __name__ == "__main__": + # When launched from Toolkit (i.e. IronPython) call this same file using CPython + if is_iron_python: + run_pyinstaller_from_c_python(oDesktop) + else: + install_pyaedt() diff --git a/doc/source/Resources/toolkits_ribbon.png b/doc/source/Resources/toolkits_ribbon.png new file mode 100644 index 00000000000..e6e6d500d54 Binary files /dev/null and b/doc/source/Resources/toolkits_ribbon.png differ diff --git a/doc/source/conf.py b/doc/source/conf.py index 7da57e7907c..734d28410ce 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -103,7 +103,7 @@ def setup(app): # Intersphinx mapping intersphinx_mapping = { - "python": ("https://docs.python.org/dev", None), + "python": ("https://docs.python.org/3", None), "scipy": ("https://docs.scipy.org/doc/scipy/reference", None), "numpy": ("https://numpy.org/devdocs", None), "matplotlib": ("https://matplotlib.org/stable", None), diff --git a/doc/source/index.rst b/doc/source/index.rst index e2f977a6e4f..19cddc56516 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,10 +1,287 @@ PyAEDT documentation |version| =============================== -.. - Simply reuse the root readme +|pyansys| |PyPI| |PyPIact| |PythonVersion| |GH-CI| |coverage| |MIT| |black| |Anaconda| |pre-commit| + +.. |pyansys| image:: https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC + :target: https://docs.pyansys.com/ + :alt: PyAnsys + +.. |PyPI| image:: https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white + :target: https://pypi.org/project/pyaedt/ + +.. |PyPIact| image:: https://pepy.tech/badge/pyaedt/month + :target: https://pypi.org/project/pyaedt/ + +.. |PythonVersion| image:: https://img.shields.io/badge/python-3.7+-blue.svg + :target: https://www.python.org/downloads/ + +.. |GH-CI| image:: https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg + :target: https://github.com/pyansys/pyaedt/actions/workflows/unit_tests.yml + +.. |coverage| image:: https://codecov.io/gh/pyansys/pyaedt/branch/main/graph/badge.svg + :target: https://codecov.io/gh/pyansys/pyaedt + +.. |MIT| image:: https://img.shields.io/badge/License-MIT-yellow.svg + :target: https://opensource.org/licenses/MIT + +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=flat + :target: https://github.com/psf/black + :alt: black + +.. |Anaconda| image:: https://anaconda.org/conda-forge/pyaedt/badges/version.svg + :target: https://anaconda.org/conda-forge/pyaedt + +.. |pre-commit| image:: https://results.pre-commit.ci/badge/github/pyansys/pyaedt/main.svg + :target: https://results.pre-commit.ci/latest/github/pyansys/pyaedt/main + :alt: pre-commit.ci status + + +What is PyAEDT? +--------------- +PyAEDT is a Python library that interacts directly with the API for +Ansys Electronics Desktop (AEDT) to make scripting simpler. The architecture +for PyAEDT can be reused for all AEDT 3D products (HFSS, Icepak, Maxwell 3D, +and Q3D Extractor), 2D tools, and Ansys Mechanical. PyAEDT also provides +support for Circuit tools like Nexxim and system simulation tools like +Twin Builder. Finally, PyAEDT provides scripting capabilities in Ansys layout +tools like HFSS 3D Layout and EDB. The PyAEDT class and method structures +simplify operation while reusing information as much as possible across +the API. + +Install on CPython from PyPI +---------------------------- +You can install PyAEDT on CPython 3.7 through 3.10 from PyPI with this command: + +.. code:: python + + pip install pyaedt + +To install PyAEDT with all extra packages (matplotlib, numpy, pandas, and pyvista), +use this command: + +.. code:: python + + pip install pyaedt[full] + +You can also install PyAEDT from Conda-Forge with this command: + +.. code:: python + + conda install -c conda-forge pyaedt + +PyAEDT remains compatible with IronPython and can be still used in the AEDT Framework. + + +PyAnsys +------- + +PyAEDT is part of the larger `PyAnsys `_ +effort to facilitate the use of Ansys technologies directly from Python. + +PyAEDT is intended to consolidate and extend all existing +functionalities around scripting for AEDT to allow reuse of existing code, +sharing of best practices, and increased collaboration. + + +About AEDT +---------- + +`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, AEDT 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:: https://images.ansys.com/is/image/ansys/ansys-electronics-technology-collage?wid=941&op_usm=0.9,1.0,20,0&fit=constrain,0 + :width: 800 + :alt: AEDT Applications + :target: https://www.ansys.com/products/electronics + + +PyAEDT is licensed under the `MIT License +`_. + +PyAEDT includes functionality for interacting with the following AEDT tools and Ansys products: + +- HFSS and HFSS 3D Layout +- Icepak +- Maxwell 2D, Maxwell 3D, and RMXprt +- 2D Extractor and Q3D Extractor +- Mechanical +- Nexxim +- EDB +- Twin Builder + + +Documentation and issues +------------------------ +Documentation for the latest stable release of PyAEDT is hosted at +`PyAEDT Documentation `_. + +In the upper right corner of the documentation's title bar, there is an option +for switching from viewing the documentation for the latest stable release +to viewing the documentation for the development version or previously +released versions. + +On the `PyAEDT Issues `_ page, you can +create issues to submit questions, report bugs, and request new features. + +To reach the project support team, email `pyansys.core@ansys.com `_. + +Dependencies +------------ +To run PyAEDT, you must have a local licensed copy of AEDT. +PyAEDT supports AEDT versions 2022 R1 and later. + +Student version +--------------- + +PyAEDT supports AEDT Student versions 2022 R1 and later. For more information, see +`Ansys Electronics Desktop Student - Free Software Download `_ on the Ansys website. + + +Why PyAEDT? +----------- +A quick and easy approach for automating a simple operation in the +AEDT UI is to record and reuse a script. However, here are some disadvantages of +this approach: + +- Recorded code is dirty and difficult to read and understand. +- Recorded scripts are difficult to reuse and adapt. +- Complex coding is required by many global users of AEDT. + +Here are the main advantages that PyAEDT provides: + +- Automatic initialization of all AEDT objects, such as desktop + objects like the editor, boundaries, and more +- Error management +- Log management +- Variable management +- Compatibility with IronPython and CPython +- Simplification of complex API syntax using data objects while + maintaining PEP8 compliance. +- Code reusability across different solvers +- Clear documentation on functions and API +- Unit tests of code to increase quality across different AEDT versions + + +Example workflow +----------------- +1. Initialize the ``Desktop`` class with the version of AEDT to use. +2. Initialize the application to use within AEDT. + + +Connect to AEDT from a Python IDE +--------------------------------- +PyAEDT works both inside AEDT and as a standalone app. +This Python library automatically detects whether it is running +in an IronPython or CPython environment and initializes AEDT accordingly. +PyAEDT also provides advanced error management. Usage examples follow. + +Explicit AEDT declaration and error management +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + # Launch AEDT 2023 R1 in non-graphical mode + + from pyaedt import Desktop, Circuit + with 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. + ... + + # Desktop is automatically released here. + + +Implicit AEDT declaration and error management +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + # Launch the latest installed version of AEDT in graphical mode + + from pyaedt import Circuit + with Circuit(specified_version="2023.1", + non_graphical=False) as circuit: + ... + # Any error here will be caught by Desktop. + ... + + # Desktop is automatically released here. + + +Remote application call +~~~~~~~~~~~~~~~~~~~~~~~ +You can make a remote application call on a CPython server +or any Windows client machine. + +On a CPython server: + +.. code:: python + + # Launch PyAEDT remote server on CPython + + from pyaedt.common_rpc import pyaedt_service_manager + pyaedt_service_manager() + + +On any Windows client machine: + +.. code:: python + + from pyaedt.common_rpc import create_session + cl1 = create_session("server_name") + cl1.aedt(port=50000, non_graphical=False) + hfss = Hfss(machine="server_name", port=50000) + # your code here + +Variables +~~~~~~~~~ + +.. code:: python + + from pyaedt.HFSS import HFSS + with HFSS as hfss: + hfss["dim"] = "1mm" # design variable + hfss["$dim"] = "1mm" # project variable + + +Modeler +~~~~~~~ + +.. code:: python + + # Create a box, assign variables, and assign materials. + + from pyaedt.hfss import Hfss + with Hfss as hfss: + hfss.modeler.create_box([0, 0, 0], [10, "dim", 10], + "mybox", "aluminum") + +License +------- +PyAEDT is licensed under the MIT license. + +This module makes no commercial claim over Ansys whatsoever. +PyAEDT extends the functionality of AEDT by adding +an additional Python interface to AEDT without changing the core +behavior or license of the original software. The use of the +interactive control of PyAEDT requires a legally licensed +local copy of AEDT. For more information about AEDT, +see the `Ansys Electronics `_ +page on the Ansys website. -.. include:: ../../README.rst .. toctree:: :hidden: @@ -14,7 +291,6 @@ PyAEDT documentation |version| API/index EDBAPI/index examples/index - Contributing Indices and tables diff --git a/doc/styles/Vocab/ANSYS/accept.txt b/doc/styles/Vocab/ANSYS/accept.txt index 8f803d0bf0e..86a9a8c5360 100644 --- a/doc/styles/Vocab/ANSYS/accept.txt +++ b/doc/styles/Vocab/ANSYS/accept.txt @@ -9,6 +9,7 @@ autosave busbar busbars Bz +circuit Circuit CPython DesignXploration @@ -71,3 +72,11 @@ vias _static pwl Conda +PyAnsys +codecov +mechanical +reusability +pypi +EDT +pyansys + diff --git a/examples/00-EDB/00_EDB_Create_VIA.py b/examples/00-EDB/00_EDB_Create_VIA.py index 59926c21d8f..7377e911ff9 100644 --- a/examples/00-EDB/00_EDB_Create_VIA.py +++ b/examples/00-EDB/00_EDB_Create_VIA.py @@ -39,10 +39,10 @@ ] 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_from_points(points, "TOP") +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_from_points(points, "TOP") +edb.modeler.create_polygon(points, "TOP") ####################################### # Create vias with parametric positions diff --git a/examples/07-EMIT/ComputeChannelSeparation.py.back b/examples/07-EMIT/ComputeChannelSeparation.py.back index d30bfba8268..de6815f3a80 100644 --- a/examples/07-EMIT/ComputeChannelSeparation.py.back +++ b/examples/07-EMIT/ComputeChannelSeparation.py.back @@ -53,7 +53,7 @@ import numpy as np non_graphical = os.getenv("PYAEDT_NON_GRAPHICAL", "False").lower() in ("true", "1", "t") NewThread = False -desktop_version = "2022.2" +desktop_version = "2023.2" ############################################################################### # Launch AEDT with EMIT @@ -218,11 +218,11 @@ def minimum_tx_channel_separation(rx_band, tx_band, emi_threshold): # keeping the EMI below the threshold. # Freqs are used to set the domain, so they need to be in Hz rx_frequencies = rev.get_active_frequencies( - rx_band[0], rx_band[1], modeRx, "Hz" + rx_band[0], rx_band[1], modeRx ) rx_channel_count = len(rx_frequencies) tx_frequencies = rev.get_active_frequencies( - tx_band[0], tx_band[1], modeTx, "Hz" + tx_band[0], tx_band[1], modeTx ) tx_channel_count = len(tx_frequencies) chpair = domain diff --git a/examples/07-EMIT/ComputeProtectionLevels.py.back b/examples/07-EMIT/ComputeProtectionLevels.py.back index c53ce1adfd8..f28db1a6819 100644 --- a/examples/07-EMIT/ComputeProtectionLevels.py.back +++ b/examples/07-EMIT/ComputeProtectionLevels.py.back @@ -138,7 +138,8 @@ for band in bands: rev = emitapp.results.analyze() modeRx = econsts.tx_rx_mode().rx modeTx = econsts.tx_rx_mode().tx -resultMode = econsts.result_type().powerAtRx +mode_power = econsts.result_type().powerAtRx +mode_emi = econsts.result_type().emi tx_interferer = econsts.interferer_type().transmitters ############################################################################### @@ -239,74 +240,63 @@ domain = emitapp.results.interaction_domain() # at the input to each receiver due to each of the transmitters. Computes # which, if any, protection levels are exceeded by these power levels. -emi_matrix=[] +power_matrix=[] all_colors=[] for tx_radio in tx_radios: - rx_emis = [] + rx_powers = [] rx_colors = [] for rx_radio in rx_radios: + # powerAtRx is the same for all Rx bands, so just + # use the first one + rx_band = rev.get_band_names(rx_radio, modeRx)[0] if tx_radio == rx_radio: # skip self-interaction - rx_emis.append('N/A') + rx_powers.append('N/A') rx_colors.append('green') continue - print("Power Thresholds for {tx} vs {rx}".format(tx=tx_radio,rx=rx_radio)) - for rx_band in rev.get_band_names(rx_radio, modeRx): - # if "L2 P(Y)" not in rx_band: - # # skip 'normal' Rx bands - # continue - # check for enabled Bands - cur_rx_radio = get_radio_node(rx_radio) - bands = cur_rx_radio.bands() - for band in bands: - if rx_band in band.node_name: - bandEnabled = band.enabled - break - if not bandEnabled: - continue - # get enabled tx band - cur_tx_radio = get_radio_node(tx_radio) - bands = cur_tx_radio.bands() - for band in bands: - if band.enabled: - tx_band = band.node_name - break - for tx_band_shortname in rev.get_band_names(tx_radio, modeTx): - if tx_band_shortname in tx_band: - break - - # Find the highest power level at the Rx input due - # to each Tx Radio - domain.set_receiver(rx_radio, rx_band) - domain.set_interferer(tx_radio,tx_band_shortname) + print("Power Thresholds for {tx} vs {rx}".format(tx=tx_radio,rx=rx_radio)) + max_power = -200 + for tx_band in rev.get_band_names(tx_radio, modeTx): + # Find the highest power level at the Rx input due to each Tx Radio. + # Can look at any Rx freq since susceptibility won't impact + # powerAtRx, but need to look at all tx channels since coupling + # can change over a transmitter's bandwidth + rx_freq = rev.get_active_frequencies(rx_radio, rx_band, modeRx) + domain.set_receiver(rx_radio, rx_band, rx_freq[0]) + domain.set_interferer(tx_radio, tx_band) interaction = rev.run(domain) - worst = interaction.get_worst_instance(resultMode) - - # If the worst case for the band-pair is below the EMI limit, then - # there are no interference issues and no offset is required. - if worst.has_valid_values(): - emi = worst.get_value(resultMode) - rx_emis.append(emi) - if (emi > damage_threshold): - rx_colors.append('red') - print("{} may damage {}".format(tx_radio, rx_radio)) - elif (emi > overload_threshold): - rx_colors.append('orange') - print("{} may overload {}".format(tx_radio, rx_radio)) - elif (emi > intermod_threshold): - rx_colors.append('yellow') - print("{} may cause intermodulation in {}".format(tx_radio, rx_radio)) - else: - rx_colors.append('green') - print("{} may cause desensitization in {}".format(tx_radio, rx_radio)) - else: - rx_emis.append(-200) + tx_freqs = rev.get_active_frequencies(tx_radio, tx_band, modeTx) + for tx_freq in tx_freqs: + domain.set_interferer(tx_radio, tx_band, tx_freq) + #interaction = rev.run(domain) + instance = interaction.get_instance(domain) + if instance.get_value(mode_power) > max_power: + max_power = instance.get_value(mode_power) + + # If the worst case for the band-pair is below the power thresholds, then + # there are no interference issues and no offset is required. + if max_power > -200: + rx_powers.append(max_power) + if (max_power > damage_threshold): rx_colors.append('red') + print("{} may damage {}".format(tx_radio, rx_radio)) + elif (max_power > overload_threshold): + rx_colors.append('orange') + print("{} may overload {}".format(tx_radio, rx_radio)) + elif (max_power > intermod_threshold): + rx_colors.append('yellow') + print("{} may cause intermodulation in {}".format(tx_radio, rx_radio)) + else: + rx_colors.append('green') + print("{} may cause desensitization in {}".format(tx_radio, rx_radio)) + else: + rx_powers.append(-200) + rx_colors.append('red') all_colors.append(rx_colors) - emi_matrix.append(rx_emis) + power_matrix.append(rx_powers) # Create a scenario matrix-like view for the protection levels -create_scenario_view(emi_matrix, all_colors, tx_radios, rx_radios) +create_scenario_view(power_matrix, all_colors, tx_radios, rx_radios) # Create a legend for the protection levels create_legend_table() diff --git a/pyaedt/__init__.py b/pyaedt/__init__.py index d36eb3b81f7..49a088b9be8 100644 --- a/pyaedt/__init__.py +++ b/pyaedt/__init__.py @@ -8,6 +8,7 @@ os.environ["ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE"] = "1" os.environ["ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE"] = "1" os.environ["ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE"] = "1" +os.environ["ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE"] = "1" pyaedt_path = os.path.dirname(__file__) @@ -27,6 +28,7 @@ from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import is_linux from pyaedt.generic.general_methods import is_windows +from pyaedt.generic.general_methods import online_help from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.generic.general_methods import settings diff --git a/pyaedt/application/Analysis.py b/pyaedt/application/Analysis.py index 35b21843a86..86d36bad904 100644 --- a/pyaedt/application/Analysis.py +++ b/pyaedt/application/Analysis.py @@ -1658,7 +1658,18 @@ def analyze_setup( if self.working_directory[0] != "\\" else os.path.join(self.working_directory, config_name + ".acf") ) - shutil.copy2(source_name, target_name) + try: + shutil.copy2(source_name, target_name) + + # If source and destination are same + except shutil.SameFileError: + self.logger.warning("Source and destination represents the same file.") + # If there is any permission issue + except PermissionError: + self.logger.error("Permission denied.") + # For other errors + except: + self.logger.error("Error occurred while copying file.") if num_cores: update_hpc_option(target_name, "NumCores", num_cores, False) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 1bb9ec75639..e301711a818 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -530,7 +530,7 @@ def __init__( if version_key < "2022.2": settings.use_grpc_api = False elif ( - version_key == "2022.2" + version_key.startswith("2022.2") and not self.port and not self.machine and settings.use_grpc_api is None diff --git a/pyaedt/edb_core/components.py b/pyaedt/edb_core/components.py index e988feb3088..e550710f99d 100644 --- a/pyaedt/edb_core/components.py +++ b/pyaedt/edb_core/components.py @@ -730,6 +730,86 @@ def create_source_on_component(self, sources=None): ) return True + @pyaedt_function_handler() + def create_port_on_pins(self, refdes, pins, reference_pins, impedance=50.0): + """Create circuit port between pins and reference ones. + + Parameters + ---------- + refdes : Component reference designator + str or EDBComponent object. + pins : pin name where the terminal has to be created. Single pin or several ones can be provided.If several + pins are provided a pin group will is created. Pin names can be the EDB name or the EDBPadstackInstance one. + For instance the pin called ``Pin1`` located on component ``U1``, ``U1-Pin1`` or ``Pin1`` can be provided and + will be handled. + str, [str], EDBPadstackInstance, [EDBPadstackInstance] + reference_pins : reference pin name used for terminal reference. Single pin or several ones can be provided. + If several pins are provided a pin group will is created. Pin names can be the EDB name or the + EDBPadstackInstance one. For instance the pin called ``Pin1`` located on component ``U1``, ``U1-Pin1`` + or ``Pin1`` can be provided and will be handled. + str, [str], EDBPadstackInstance, [EDBPadstackInstance] + impedance : Port impedance + str, float + + Returns + ------- + EDB terminal created, or False if failed to create. + + Example: + >>> from pyaedt import Edb + >>> edb = Edb(path_to_edb_file) + >>> pin = "AJ6" + >>> ref_pins = ["AM7", "AM4"] + Or to take all reference pins + >>> ref_pins = [pin for pin in list(edb.components["U2A5"].pins.values()) if pin.net_name == "GND"] + >>> edb.components.create_port_on_pins(refdes="U2A5", pins=pin, reference_pins=ref_pins) + >>> edb.save_edb() + >>> edb.close_edb() + """ + + if isinstance(pins, str) or isinstance(pins, EDBPadstackInstance): + pins = [pins] + if isinstance(reference_pins, str): + reference_pins = [reference_pins] + if isinstance(refdes, str) or isinstance(refdes, EDBComponent): + refdes = self.instances[refdes] + if len([pin for pin in pins if isinstance(pin, str)]) == len(pins): + cmp_pins = [] + for pin_name in pins: + cmp_pin = [pin for pin in list(refdes.pins.values()) if pin_name in pin.name] + if cmp_pin: + cmp_pins.append(cmp_pin[0]) + if not cmp_pins: + return + pins = cmp_pins + if not len([pin for pin in pins if isinstance(pin, EDBPadstackInstance)]) == len(pins): + self._logger.error("Pin list must contain only pins instances") + return + if len([pin for pin in reference_pins if isinstance(pin, str)]) == len(reference_pins): + ref_cmp_pins = [] + for ref_pin_name in reference_pins: + cmp_ref_pin = [pin for pin in list(refdes.pins.values()) if ref_pin_name in pin.name] + if cmp_ref_pin: + ref_cmp_pins.append(cmp_ref_pin[0]) + if not ref_cmp_pins: + return + reference_pins = ref_cmp_pins + if not len([pin for pin in reference_pins if isinstance(pin, EDBPadstackInstance)]) == len(reference_pins): + return + group_name = "group_{}_{}".format(pins[0].net_name, pins[0].name) + ref_group_name = "group_{}_{}_ref".format(pins[0].net_name, pins[0].name) + pin_group = self.create_pingroup_from_pins(pins, group_name) + ref_pin_group = self.create_pingroup_from_pins(reference_pins, ref_group_name) + term = self._create_pin_group_terminal(pingroup=pin_group, component=refdes.refdes) + term.SetIsCircuitPort(True) + ref_term = self._create_pin_group_terminal(pingroup=ref_pin_group, component=refdes.refdes) + ref_term.SetIsCircuitPort(True) + term.SetImpedance(self._edb.Utility.Value(impedance)) + term.SetReferenceTerminal(ref_term) + if term: + return term + return False + @pyaedt_function_handler() def create_port_on_component( self, @@ -800,8 +880,13 @@ def create_port_on_component( pin_layers = cmp_pins[0].GetPadstackDef().GetData().GetLayerNames() if port_type == SourceType.CoaxPort: pad_params = self._padstack.get_pad_parameters(pin=cmp_pins[0], layername=pin_layers[0], pad_type=0) - sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]]) - solder_ball_height = sball_diam + if not pad_params[0] == 7: + sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]]) + solder_ball_height = sball_diam / 2 + else: + bbox = pad_params[1] + sball_diam = min([abs(bbox[2] - bbox[0]), abs(bbox[3] - bbox[1])]) * 0.8 + solder_ball_height = sball_diam / 2 self.set_solder_ball(component, solder_ball_height, sball_diam) for pin in cmp_pins: self._padstack.create_coax_port(pin) @@ -1390,7 +1475,7 @@ def create_pingroup_from_pins(self, pins, group_name=None): Parameters ---------- pins : list - List of EDB core pins. + List of EDB pins. group_name : str, optional Name for the group. The default is ``None``, in which case a default name is assigned as follows: ``[component Name] [NetName]``. @@ -1410,8 +1495,16 @@ def create_pingroup_from_pins(self, pins, group_name=None): if len(pins) < 1: self._logger.error("No pins specified for pin group %s", group_name) return (False, None) + if len([pin for pin in pins if isinstance(pin, EDBPadstackInstance)]): + _pins = [pin._edb_padstackinstance for pin in pins] + if _pins: + pins = _pins if group_name is None: group_name = self._edb.Cell.Hierarchy.PinGroup.GetUniqueName(self._active_layout) + for pin in pins: + pin.SetIsLayoutPin(True) + forbiden_car = "-><" + group_name = group_name.translate({ord(i): "_" for i in forbiden_car}) pingroup = _retry_ntimes( 10, self._edb.Cell.Hierarchy.PinGroup.Create, diff --git a/pyaedt/edb_core/edb_data/hfss_extent_info.py b/pyaedt/edb_core/edb_data/hfss_extent_info.py index e32cc14adf1..7e78d52d4d2 100644 --- a/pyaedt/edb_core/edb_data/hfss_extent_info.py +++ b/pyaedt/edb_core/edb_data/hfss_extent_info.py @@ -84,6 +84,7 @@ def air_box_positive_vertical_extent(self): @air_box_positive_vertical_extent.setter def air_box_positive_vertical_extent(self, value): + value = float(value) info = self._edb_hfss_extent_info info.AirBoxPositiveVerticalExtent = convert_pytuple_to_nettuple( (value, self.air_box_positive_vertical_extent_enabled) @@ -108,6 +109,7 @@ def air_box_negative_vertical_extent(self): @air_box_negative_vertical_extent.setter def air_box_negative_vertical_extent(self, value): + value = float(value) info = self._edb_hfss_extent_info info.AirBoxNegativeVerticalExtent = convert_pytuple_to_nettuple( (value, self.air_box_negative_vertical_extent_enabled) diff --git a/pyaedt/edb_core/ipc2581/ecad/cad_data/step.py b/pyaedt/edb_core/ipc2581/ecad/cad_data/step.py index 4b08d2b1475..6029f004707 100644 --- a/pyaedt/edb_core/ipc2581/ecad/cad_data/step.py +++ b/pyaedt/edb_core/ipc2581/ecad/cad_data/step.py @@ -128,24 +128,26 @@ def add_component(self, component=None): # pragma no cover geometry_type, pad_parameters, pos_x, pos_y, rot = self._pedb.padstacks.get_pad_parameters( pin._edb_padstackinstance, component.placement_layer, 0 ) - position = pin._position if pin._position else pin.position - - pin_pos_x = self._ipc.from_meter_to_units(position[0], self.units) - pin_pos_y = self._ipc.from_meter_to_units(position[1], self.units) - primitive_ref = "" - if geometry_type == 1: - primitive_ref = "CIRC_{}".format(pad_parameters[0]) - elif geometry_type == 2: - primitive_ref = "RECT_{}_{}".format(pad_parameters[0], pad_parameters[0]) - elif geometry_type == 3: - primitive_ref = "RECT_{}_{}".format(pad_parameters[0], pad_parameters[1]) - elif geometry_type == 4: - primitive_ref = "OVAL_{}_{}_{}".format(pad_parameters[0], pad_parameters[1], pad_parameters[2]) - if primitive_ref: - package.add_pin( - number=pin_number, x=pin_pos_x, y=pin_pos_y, rotation=rot, primitive_ref=primitive_ref - ) - pin_number += 1 + if pad_parameters: + position = pin._position if pin._position else pin.position + pin_pos_x = self._ipc.from_meter_to_units(position[0], self.units) + pin_pos_y = self._ipc.from_meter_to_units(position[1], self.units) + primitive_ref = "" + if geometry_type == 1: + primitive_ref = "CIRC_{}".format(pad_parameters[0]) + elif geometry_type == 2: + primitive_ref = "RECT_{}_{}".format(pad_parameters[0], pad_parameters[0]) + elif geometry_type == 3: + primitive_ref = "RECT_{}_{}".format(pad_parameters[0], pad_parameters[1]) + elif geometry_type == 4: + primitive_ref = "OVAL_{}_{}_{}".format( + pad_parameters[0], pad_parameters[1], pad_parameters[2] + ) + if primitive_ref: + package.add_pin( + number=pin_number, x=pin_pos_x, y=pin_pos_y, rotation=rot, primitive_ref=primitive_ref + ) + pin_number += 1 self.packages[package.name] = package ipc_component = Component() ipc_component.type = component.type diff --git a/pyaedt/edb_core/layout.py b/pyaedt/edb_core/layout.py index 8d278604995..1fb2d930ee4 100644 --- a/pyaedt/edb_core/layout.py +++ b/pyaedt/edb_core/layout.py @@ -2,6 +2,7 @@ This module contains these classes: `EdbLayout` and `Shape`. """ import math +import warnings from pyaedt.edb_core.edb_data.primitives_data import EDBPrimitives from pyaedt.edb_core.edb_data.utilities import EDBStatistics @@ -508,7 +509,11 @@ def create_polygon(self, main_shape, layer_name, voids=[], net_name=""): Parameters ---------- main_shape : list of points or PolygonData or ``modeler.Shape`` - Shape or point lists of the main object. + Shape or point lists of the main object. Point list can be in the format of `[[x1,y1], [x2,y2],..,[xn,yn]]`. + Each point can be: + - [x, y] coordinate + - [x, y, height] for an arc with specific height (between previous point and actual point) + - [x, y, rotation, xc, yc] for an arc given a point, rotation and center. layer_name : str Name of the layer on which to create the polygon. voids : list, optional @@ -555,10 +560,17 @@ def create_polygon(self, main_shape, layer_name, voids=[], net_name=""): def create_polygon_from_points(self, point_list, layer_name, net_name=""): """Create a new polygon from a point list. + .. deprecated:: 0.6.73 + Use :func:`create_polygon` method instead. It now supports point lists as arguments. + Parameters ---------- point_list : list Point list in the format of `[[x1,y1], [x2,y2],..,[xn,yn]]`. + Each point can be: + - [x,y] coordinate + - [x,y, height] for an arc with specific height (between previous point and actual point) + - [x,y, rotation, xc,yc] for an arc given a point, rotation and center. layer_name : str Name of layer on which create the polygon. net_name : str, optional @@ -568,18 +580,10 @@ def create_polygon_from_points(self, point_list, layer_name, net_name=""): ------- :class:`pyaedt.edb_core.edb_data.primitives_data.EDBPrimitives` """ - net = self._pedb.nets.find_or_create_net(net_name) - plane = self.Shape("polygon", points=point_list) - _poly = self.shape_to_polygon_data(plane) - if _poly is None or _poly.IsNull() or _poly is False: - self._logger.error("Failed to create main shape polygon data") - return False - polygon = self._edb.Cell.Primitive.Polygon.Create(self._active_layout, layer_name, net, _poly) - if polygon.IsNull(): - self._logger.error("Null polygon created") - return False - else: - return EDBPrimitives(polygon, self._pedb) + warnings.warn( + "Use :func:`create_polygon` method instead. It now supports point lists as arguments.", DeprecationWarning + ) + return self.create_polygon(point_list, layer_name, net_name=net_name) @pyaedt_function_handler() def create_rectangle( @@ -870,6 +874,21 @@ def _createPolygonDataFromPolygon(self, shape): self._pedb.point_data(endPoint[0], endPoint[1]), ) arcs.append(arc) + elif len(endPoint) == 3: + is_parametric = ( + is_parametric + or startPoint[0].IsParametric() + or startPoint[1].IsParametric() + or endPoint[0].IsParametric() + or endPoint[1].IsParametric() + or endPoint[2].IsParametric() + ) + arc = self._edb.Geometry.ArcData( + self._pedb.point_data(startPoint[0], startPoint[1]), + self._pedb.point_data(endPoint[0], endPoint[1]), + endPoint[2].ToDouble(), + ) + arcs.append(arc) elif len(endPoint) == 5: is_parametric = ( is_parametric @@ -889,19 +908,10 @@ def _createPolygonDataFromPolygon(self, shape): self._logger.error("Invalid rotation direction %s is specified.", endPoint[2]) return None arc = self._edb.Geometry.ArcData( - self._edb.Geometry.PointData( - self._get_edb_value(startPoint[0].ToDouble()), - self._get_edb_value(startPoint[1].ToDouble()), - ), - self._edb.Geometry.PointData( - self._get_edb_value(endPoint[0].ToDouble()), - self._get_edb_value(endPoint[1].ToDouble()), - ), + self._pedb.point_data(startPoint[0], startPoint[1]), + self._pedb.point_data(endPoint[0], endPoint[1]), rotationDirection, - self._edb.Geometry.PointData( - self._get_edb_value(endPoint[3].ToDouble()), - self._get_edb_value(endPoint[4].ToDouble()), - ), + self._pedb.point_data(endPoint[3], endPoint[4]), ) arcs.append(arc) polygon = self._edb.Geometry.PolygonData.CreateFromArcs(convert_py_list_to_net_list(arcs), True) @@ -928,6 +938,20 @@ def _validatePoint(self, point, allowArcs=True): self._logger.error("Point Y value must be a number.") return False return True + elif len(point) == 3: + if not allowArcs: + self._logger.error("Arc found but arcs are not allowed in _validatePoint.") + return False + if not isinstance(point[0], (int, float, str)): + self._logger.error("Point X value must be a number.") + return False + if not isinstance(point[1], (int, float, str)): + self._logger.error("Point Y value must be a number.") + return False + if not isinstance(point[1], (int, float, str)): + self._logger.error("Invalid point height.") + return False + return True elif len(point) == 5: if not allowArcs: self._logger.error("Arc found but arcs are not allowed in _validatePoint.") diff --git a/pyaedt/edb_core/padstack.py b/pyaedt/edb_core/padstack.py index c98c34c9635..65dc9b7f88b 100644 --- a/pyaedt/edb_core/padstack.py +++ b/pyaedt/edb_core/padstack.py @@ -571,12 +571,36 @@ def get_pad_parameters(self, pin, layername, pad_type=0): padparams = self._edb.Definition.PadstackDefData(pin.GetPadstackDef().GetData()).GetPadParametersValue( layername, self.int_to_pad_type(pad_type) ) - geom_type = int(padparams[1]) - parameters = [i.ToString() for i in padparams[2]] - offset_x = padparams[3].ToDouble() - offset_y = padparams[4].ToDouble() - rot = padparams[5].ToDouble() - return geom_type, parameters, offset_x, offset_y, rot + if padparams[2]: + geometry_type = int(padparams[1]) + parameters = [i.ToString() for i in padparams[2]] + offset_x = padparams[3].ToDouble() + offset_y = padparams[4].ToDouble() + rotation = padparams[5].ToDouble() + return geometry_type, parameters, offset_x, offset_y, rotation + else: + if isinstance(pin, self._edb.Definition.PadstackDef): + padparams = self._edb.Definition.PadstackDefData(pin.GetData()).GetPolygonalPadParameters( + layername, self.int_to_pad_type(pad_type) + ) + else: + padparams = self._edb.Definition.PadstackDefData( + pin.GetPadstackDef().GetData() + ).GetPolygonalPadParameters(layername, self.int_to_pad_type(pad_type)) + + if padparams[0]: + parameters = [ + padparams[1].GetBBox().Item1.X.ToDouble(), + padparams[1].GetBBox().Item1.Y.ToDouble(), + padparams[1].GetBBox().Item2.X.ToDouble(), + padparams[1].GetBBox().Item2.Y.ToDouble(), + ] + offset_x = padparams[2] + offset_y = padparams[3] + rotation = padparams[4] + geometry_type = 7 + return geometry_type, parameters, offset_x, offset_y, rotation + return 0, [0], 0, 0, 0 @pyaedt_function_handler def set_all_antipad_value(self, value): diff --git a/pyaedt/emit_core/__init__.py b/pyaedt/emit_core/__init__.py index 7f7d53f8448..7cd035f69c4 100644 --- a/pyaedt/emit_core/__init__.py +++ b/pyaedt/emit_core/__init__.py @@ -1,7 +1,10 @@ +import imp from importlib import import_module import os import sys +from pyaedt import pyaedt_logger as logger + EMIT_API_PYTHON = None delcross_python_path = os.environ.get("ANSYS_DELCROSS_PYTHON_PATH") @@ -24,6 +27,9 @@ def _set_api(aedt_version): desktop_path = os.environ.get(aedt_version) if desktop_path and numeric_version > 231: path = os.path.join(desktop_path, "Delcross") - sys.path.append(path) + sys.path.insert(0, path) + module_path = imp.find_module("EmitApiPython")[1] + logger.info("Importing EmitApiPython from: {}".format(module_path)) global EMIT_API_PYTHON EMIT_API_PYTHON = import_module("EmitApiPython") + logger.info("Loaded {}".format(EMIT_API_PYTHON.EmitApi().get_version(True))) diff --git a/pyaedt/emit_core/results/results.py b/pyaedt/emit_core/results/results.py index 2fb7ec29bac..84cfff83ae7 100644 --- a/pyaedt/emit_core/results/results.py +++ b/pyaedt/emit_core/results/results.py @@ -38,27 +38,23 @@ def __init__(self, emit_obj): @pyaedt_function_handler() def _add_revision(self, name=None): - """Add a new revision. + """Add a new revision or get the current revision if it already exists. Parameters ---------- name : str, optional - Name for the new revision. If None, it will - be named the current design revision. + Name for the new revision, if created. The default is ``None``, in which + case the name of the current design revision is used. + + Raises + ------ + RuntimeError if the name given is not the name of an existing result set and a current result set already + exists. Returns ------- ``Revision`` object that was created. """ - if name == None: - # check for a Current Revision that just hasn't been - # loaded by pyaedt - if self.design.GetCurrentResult() == "": - self.design.AddResult() - rev_num = self.design.GetRevision() - name = "Revision {}".format(rev_num) - else: - name = self.design.GetCurrentResult() revision = Revision(self, self.emit_project, name) self.revisions.append(revision) return revision diff --git a/pyaedt/emit_core/results/revision.py b/pyaedt/emit_core/results/revision.py index 98bff647542..5c3d1c5dd54 100644 --- a/pyaedt/emit_core/results/revision.py +++ b/pyaedt/emit_core/results/revision.py @@ -1,4 +1,3 @@ -import os import warnings import pyaedt.emit_core.EmitConstants as emitConsts @@ -16,8 +15,12 @@ class Revision: emit_obj : ``Emit`` object that this revision is associated with. name : str, optional - Name of the revision to create. The default is ``None``, in which case a - default name is given. + Name of the revision to create. The default is ``None``, in which + case the name of the current design revision is used. + + Raises + ------ + RuntimeError if the name given is not the name of an existing result set and a current result set already exists. Examples -------- @@ -29,28 +32,16 @@ class Revision: >>> rev.run(domain) """ - def __init__(self, parent_results, emit_obj, name=""): - design = emit_obj.odesktop.GetActiveProject().GetActiveDesign() - subfolder = "" - proj_name = emit_obj.oproject.GetName() - for f in os.scandir(emit_obj.oproject.GetPath()): - if os.path.splitext(f.name)[0] == proj_name and os.path.splitext(f.name)[1].lower() == ".aedtresults": - subfolder = os.path.join(f.path, "EmitDesign1") - default_behaviour = not os.path.exists(os.path.join(subfolder, "{}.emit".format(name))) - if default_behaviour: - print("The most recently generated revision will be used because the revision specified does not exist.") - if name == "" or default_behaviour: - # if there are no results yet, add a new Result - result_files = os.listdir(subfolder) - if len(result_files) == 0: - name = design.AddResult() - full = subfolder + "/{}.emit".format(name) - else: - file = max([f for f in os.scandir(subfolder)], key=lambda x: x.stat().st_mtime) - full = file.path - name = file.name + def __init__(self, parent_results, emit_obj, name=None): + if not name: + name = emit_obj.odesign.GetCurrentResult() + if not name: + name = emit_obj.odesign.AddResult("") else: - full = subfolder + "/{}.emit".format(name) + if name not in emit_obj.odesign.GetResultList(): + name = emit_obj.odesign.AddResult(name) + full = emit_obj.odesign.GetResultDirectory(name) + self.name = name """Name of the revision.""" @@ -60,14 +51,15 @@ def __init__(self, parent_results, emit_obj, name=""): self.emit_project = emit_obj """Emit project.""" - result_props = design.GetResultProperties(name) - # Strip off the Revision # - self.revision_number = result_props[0][9:] + raw_props = emit_obj.odesign.GetResultProperties(name) + key = lambda s: s.split("=", 1)[0] + val = lambda s: s.split("=", 1)[1] + props = {key(s): val(s) for s in raw_props} + + self.revision_number = int(props["Revision"]) """Unique revision number from the Emit design""" - result_props = design.GetResultProperties(name) - # Strip off the 'Timestamp=' - self.timestamp = result_props[1][10:] + self.timestamp = props["Timestamp"] """Unique timestamp for the revision""" self.parent_results = parent_results @@ -126,6 +118,8 @@ def run(self, domain): """ self._load_revision() engine = self.emit_project._emit_api.get_engine() + if domain.interferer_names and engine.max_simultaneous_interferers != len(domain.interferer_names): + raise ValueError("The max_simultaneous_interferers must equal the number of interferers in the domain.") interaction = engine.run(domain) # save the revision self.emit_project._emit_api.save_project() @@ -325,11 +319,10 @@ def notes(self): >>> aedtapp.results.current_revision.notes 'Added a filter to the WiFi Radio.' """ - design = self.emit_project.odesktop.GetActiveProject().GetActiveDesign() + design = self.emit_project.odesign return design.GetResultNotes(self.name) @notes.setter def notes(self, notes): - design = self.emit_project.odesktop.GetActiveProject().GetActiveDesign() - design.SetResultNotes(self.name, notes) + self.emit_project.odesign.SetResultNotes(self.name, notes) self.emit_project._emit_api.save_project() diff --git a/pyaedt/generic/general_methods.py b/pyaedt/generic/general_methods.py index a6781844a0a..0e646753dbf 100644 --- a/pyaedt/generic/general_methods.py +++ b/pyaedt/generic/general_methods.py @@ -1574,6 +1574,126 @@ def _check_installed_version(install_path, long_version): return False +def install_with_pip(package_name, package_path=None, upgrade=False, uninstall=False): # pragma: no cover + """Install a new package using pip. + This method is useful for installing a package from the AEDT Console without launching the Python environment. + + Parameters + ---------- + package_name : str + Name of the package to install. + package_path : str, optional + Path for the GitHub package to download and install. For example, ``git+https://.....``. + upgrade : bool, optional + Whether to upgrade the package. The default is ``False``. + uninstall : bool, optional + Whether to install the package or uninstall the package. + """ + if is_linux and is_ironpython: + import subprocessdotnet as subprocess + else: + import subprocess + executable = '"{}"'.format(sys.executable) if is_windows else sys.executable + + commands = [] + if uninstall: + commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name]) + else: + if package_path and upgrade: + commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name]) + command = [executable, "-m", "pip", "install", package_path] + else: + command = [executable, "-m", "pip", "install", package_name] + if upgrade: + command.append("-U") + + commands.append(command) + for command in commands: + if is_linux: + p = subprocess.Popen(command) + else: + p = subprocess.Popen(" ".join(command)) + p.wait() + + +class Help: # pragma: no cover + def __init__(self): + self._base_path = "https://aedt.docs.pyansys.com/version/stable" + self.browser = "default" + + def _launch_ur(self, url): + import webbrowser + + if self.browser != "default": + webbrowser.get(self.browser).open_new_tab(url) + else: + webbrowser.open_new_tab(url) + + def search(self, keywords, app_name=None, search_in_examples_only=False): + """Search for one or more keywords. + + Parameters + ---------- + keywords : str or list + app_name : str, optional + Name of a PyAEDT app. For example, ``"Hfss"``, ``"Circuit"``, ``"Icepak"``, or any other available app. + search_in_examples_only : bool, optional + Whether to search for the one or more keywords only in the PyAEDT examples. + The default is ``False``. + """ + if isinstance(keywords, str): + keywords = [keywords] + if search_in_examples_only: + keywords.append("This example") + if app_name: + keywords.append(app_name) + url = self._base_path + "/search.html?q={}".format("+".join(keywords)) + self._launch_ur(url) + + def getting_started(self): + """Open the PyAEDT User guide page.""" + url = self._base_path + "/User_guide/index.html" + self._launch_ur(url) + + def examples(self): + """Open the PyAEDT Examples page.""" + url = self._base_path + "/examples/index.html" + self._launch_ur(url) + + def github(self): + """Open the PyAEDT GitHub page.""" + url = "https://github.com/pyansys/pyaedt" + self._launch_ur(url) + + def changelog(self, release=None): + """Open the PyAEDT GitHub Changelog for a given release. + + Parameters + ---------- + release : str, optional + Release to get the changelog for. For example, ``"0.6.70"``. + """ + if release is None: + from pyaedt import __version__ as release + url = "https://github.com/pyansys/pyaedt/releases/tag/v" + release + self._launch_ur(url) + + def issues(self): + """Open the PyAEDT GitHub Issues page.""" + url = "https://github.com/pyansys/pyaedt/issues" + self._launch_ur(url) + + def ansys_forum(self): + """Open the PyAEDT GitHub Issues page.""" + url = "https://discuss.ansys.com/discussions/tagged/pyaedt" + self._launch_ur(url) + + def developer_forum(self): + """Open the Discussions page on the Ansys Developer site.""" + url = "https://developer.ansys.com/" + self._launch_ur(url) + + class Settings(object): """Manages all PyAEDT environment variables and global settings.""" @@ -1625,6 +1745,7 @@ def __init__(self): "ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE": "1", "ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE": "1", "ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE": "1", + "ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE": "1", } if is_linux: self._aedt_environment_variables["ANS_NODEPCHECK"] = "1" @@ -2021,3 +2142,4 @@ def enable_debug_logger(self, val): settings = Settings() +online_help = Help() diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py index bb2abe9afd4..73e2ea8b883 100644 --- a/pyaedt/icepak.py +++ b/pyaedt/icepak.py @@ -1848,7 +1848,6 @@ def get_link_data(self, links_data, **kwargs): List containing the requested link data. """ - if "linkData" in kwargs: warnings.warn( "The ``linkData`` parameter was deprecated in 0.6.43. Use the ``links_data`` parameter instead.", @@ -2077,7 +2076,6 @@ def create_ipk_3dcomponent_pcb( >>> oModule.InsertNativeComponent """ - if "extenttype" in kwargs: warnings.warn( "The ``extenttype`` parameter was deprecated in 0.6.43. Use the ``extent_type`` parameter instead.", @@ -2184,16 +2182,16 @@ def create_pcb_from_3dlayout( Name of the design. resolution : int, optional Resolution of the mapping. The default is ``2``. - extent_type : + extent_type : str, optional Type of the extent. Options are ``"Polygon"`` and ``"Bounding Box"``. The default is ``"Bounding Box"``. outline_polygon : str, optional Name of the outline polygon if ``extent_type="Polygon"``. The default is ``""``. close_linked_project_after_import : bool, optional Whether to close the linked AEDT project after the import. The default is ``True``. - custom_x_resolution : + custom_x_resolution : int, optional The default is ``None``. - custom_y_resolution : + custom_y_resolution : int, optional The default is ``None``. power_in : float, optional Power in in Watt. @@ -2208,7 +2206,6 @@ def create_pcb_from_3dlayout( >>> oModule.InsertNativeComponent """ - if "extenttype" in kwargs: warnings.warn( "``extenttype`` was deprecated in 0.6.43. Use ``extent_type`` instead.", @@ -2270,7 +2267,6 @@ def copyGroupFrom(self, group_name, source_design, source_project_name=None, sou >>> oEditor.Copy >>> oeditor.Paste """ - if "groupName" in kwargs: warnings.warn( "The ``groupName`` parameter was deprecated in 0.6.43. Use the ``group_name`` parameter instead.", @@ -2360,7 +2356,6 @@ def globalMeshSettings( >>> oModule.EditGlobalMeshRegion """ - bounding_box = self.modeler.oeditor.GetModelBoundingBox() xsize = abs(float(bounding_box[0]) - float(bounding_box[3])) / (15 * meshtype * meshtype) ysize = abs(float(bounding_box[1]) - float(bounding_box[4])) / (15 * meshtype * meshtype) @@ -2688,9 +2683,9 @@ def apply_icepak_settings( If ``False``, full validation is performed. default_fluid : str, optional Type of fluid. The default is ``"Air"``. - default_solid : + default_solid : str, optional Type of solid. The default is ``"Al-Extruded"``. - default_surface : + default_surface : str, optional Type of surface. The default is ``"Steel-oxidised-surface"``. Returns @@ -2703,7 +2698,6 @@ def apply_icepak_settings( >>> oDesign.SetDesignSettings """ - ambient_temperature = self.modeler._arg_with_dim(ambienttemp, "cel") axes = ["X", "Y", "Z"] diff --git a/pyaedt/modeler/cad/Modeler.py b/pyaedt/modeler/cad/Modeler.py index 70131e41a76..c7b71879878 100644 --- a/pyaedt/modeler/cad/Modeler.py +++ b/pyaedt/modeler/cad/Modeler.py @@ -2639,7 +2639,6 @@ def mirror(self, objid, position, vector, duplicate=False, is_3d_comp=False, dup >>> oEditor.Mirror >>> oEditor.DuplicateMirror """ - selections = self.convert_to_selections(objid) Xpos, Ypos, Zpos = self._pos_with_arg(position) Xnorm, Ynorm, Znorm = self._pos_with_arg(vector) @@ -4982,7 +4981,6 @@ def move_face(self, faces, offset=1.0): >>> oEditor.MoveFaces """ - face_selection = self.convert_to_selections(faces, True) selection = {} for f in face_selection: @@ -5044,7 +5042,6 @@ def move_edge(self, edges, offset=1.0): >>> oEditor.MoveEdges """ - edge_selection = self.convert_to_selections(edges, True) selection = {} for f in edge_selection: diff --git a/pyaedt/modeler/cad/Primitives3D.py b/pyaedt/modeler/cad/Primitives3D.py index 6f0b151e6a3..de3ed7e9ed7 100644 --- a/pyaedt/modeler/cad/Primitives3D.py +++ b/pyaedt/modeler/cad/Primitives3D.py @@ -11,6 +11,7 @@ from math import tan import os +from pyaedt import Edb from pyaedt import Icepak from pyaedt.generic import LoadAEDTFile from pyaedt.generic.general_methods import _retry_ntimes @@ -1426,6 +1427,241 @@ def insert_3d_component( else: return udm_obj + @pyaedt_function_handler() + def insert_layout_component( + self, + comp_file, + coordinate_system="Global", + name=None, + parameter_mapping=False, + ): + """Insert a new layout component. + + Parameters + ---------- + comp_file : str + Path of the component file. Either ``".aedb"`` and ``".aedbcomp"`` are allowed. + coordinate_system : str, optional + Target coordinate system. The default is ``"Global"``. + name : str, optional + 3D component name. The default is ``None``. + parameter_mapping : bool, optional + Map the layout parameters in the target HFSS design. The default is ``False``. + + Returns + ------- + :class:`pyaedt.modeler.components_3d.UserDefinedComponent` + User defined component object. + + References + ---------- + + >>> oEditor.InsertNativeComponent + + Examples + -------- + >>> from pyaedt import Hfss + >>> app = Hfss() + >>> layout_component = "path/to/layout_component/component.aedbcomp" + >>> comp = app.modeler.insert_layout_component(layout_component) + + """ + if self._app.solution_type != "Terminal" and self._app.solution_type != "TransientAPhiFormulation": + self.logger.warning("Solution type must be terminal in HFSS or APhi in Maxwell") + return False + + component_name = os.path.splitext(os.path.basename(comp_file))[0] + aedt_component_name = component_name + if component_name not in self._app.o_component_manager.GetNames(): + compInfo = ["NAME:" + str(component_name), "Info:=", []] + + compInfo.extend( + [ + "CircuitEnv:=", + 0, + "Refbase:=", + "U", + "NumParts:=", + 1, + "ModSinceLib:=", + True, + "Terminal:=", + [], + "CompExtID:=", + 9, + "ModelEDBFilePath:=", + comp_file, + "EDBCompPassword:=", + "", + ] + ) + + aedt_component_name = self._app.o_component_manager.Add(compInfo) + + if not name or name in self.modeler.user_defined_component_names: + name = generate_unique_name("LC") + + # Open Layout component and get information + aedb_component_path = comp_file + if os.path.splitext(os.path.basename(comp_file))[1] == ".aedbcomp": + aedb_project_path = os.path.join(self._app.project_path, self._app.project_name + ".aedb") + aedb_component_path = os.path.join( + aedb_project_path, "LayoutComponents", aedt_component_name, aedt_component_name + ".aedb" + ) + aedb_component_path = aedb_component_path.replace("/", "\\") + + component_obj = Edb( + edbpath=aedb_component_path, + isreadonly=True, + edbversion=self._app._aedt_version, + student_version=self._app.student_version, + ) + + # Extract and map parameters + parameters = {} + for param in component_obj.design_variables: + parameters[param] = [param + "_" + name, component_obj.design_variables[param].value_string] + if parameter_mapping: + self._app[param + "_" + name] = component_obj.design_variables[param].value_string + + # Get coordinate systems + component_cs = list(component_obj.components.components.keys()) + component_obj.close_edb() + + vArg1 = ["NAME:InsertNativeComponentData"] + vArg1.append("TargetCS:=") + vArg1.append(coordinate_system) + vArg1.append("SubmodelDefinitionName:=") + vArg1.append("LC") + varg2 = ["NAME:ComponentPriorityLists"] + vArg1.append(varg2) + vArg1.append("NextUniqueID:=") + vArg1.append(0) + vArg1.append("MoveBackwards:=") + vArg1.append(False) + vArg1.append("DatasetType:=") + vArg1.append("ComponentDatasetType") + varg3 = ["NAME:DatasetDefinitions"] + vArg1.append(varg3) + varg4 = [ + "NAME:BasicComponentInfo", + "ComponentName:=", + "LC", + "Company:=", + "", + "Company URL:=", + "", + "Model Number:=", + "", + "Help URL:=", + "", + "Version:=", + "1.0", + "Notes:=", + "", + "IconTypeL:=", + "Layout Component", + ] + vArg1.append(varg4) + varg5 = [ + "NAME:GeometryDefinitionParameters", + ] + if parameters and parameter_mapping: + for param in parameters: + varg5.append("VariableProp:=") + varg5.append([parameters[param][0], "D", "", parameters[param][1]]) + + varg5.append(["NAME:VariableOrders"]) + vArg1.append(varg5) + + varg6 = ["NAME:DesignDefinitionParameters", ["NAME:VariableOrders"]] + vArg1.append(varg6) + + varg7 = ["NAME:MaterialDefinitionParameters", ["NAME:VariableOrders"]] + vArg1.append(varg7) + + vArg1.append("DefReferenceCSID:=") + vArg1.append(1) + vArg1.append("MapInstanceParameters:=") + vArg1.append("DesignVariable") + vArg1.append("UniqueDefinitionIdentifier:=") + vArg1.append("") + vArg1.append("OriginFilePath:=") + vArg1.append("") + vArg1.append("IsLocal:=") + vArg1.append(False) + vArg1.append("ChecksumString:=") + vArg1.append("") + vArg1.append("ChecksumHistory:=") + vArg1.append([]) + vArg1.append("VersionHistory:=") + vArg1.append([]) + + varg8 = ["NAME:VariableMap"] + + for param in parameters: + varg8.append(param + ":=") + if parameter_mapping: + varg8.append(parameters[param][0]) + else: + varg8.append(parameters[param][1]) + + varg9 = [ + "NAME:NativeComponentDefinitionProvider", + "Type:=", + "Layout Component", + "Unit:=", + "mm", + "Version:=", + 1.1, + "EDBDefinition:=", + aedt_component_name, + varg8, + "ReferenceCS:=", + "Global", + "CSToImport:=", + ] + + if component_cs: + varg10 = component_cs + varg10.append("Global") + else: + varg10 = ["Global"] + varg9.append(varg10) + vArg1.append(varg9) + + varg11 = ["NAME:InstanceParameters"] + varg11.append("GeometryParameters:=") + + if parameters and parameter_mapping: + varg12 = "" + for param in parameters: + varg12 += " {0}='{1}'".format(parameters[param][0], parameters[param][0]) + else: + varg12 = "" + varg11.append(varg12[1:]) + + varg11.append("MaterialParameters:=") + varg11.append("") + varg11.append("DesignParameters:=") + varg11.append("") + vArg1.append(varg11) + + try: + new_object_name = self.oeditor.InsertNativeComponent(vArg1) + udm_obj = False + if new_object_name: + obj_list = list(self.oeditor.Get3DComponentPartNames(new_object_name)) + for new_name in obj_list: + self._create_object(new_name) + + udm_obj = self._create_user_defined_component(new_object_name) + if name: + udm_obj.name = name + except Exception: # pragma: no cover + udm_obj = False + return udm_obj + @pyaedt_function_handler() def get_3d_component_object_list(self, componentname): """Retrieve all objects belonging to a 3D component. diff --git a/pyaedt/modeler/cad/components_3d.py b/pyaedt/modeler/cad/components_3d.py index fb583934572..1fb8186dbd3 100644 --- a/pyaedt/modeler/cad/components_3d.py +++ b/pyaedt/modeler/cad/components_3d.py @@ -102,6 +102,8 @@ def __init__(self, primitives, name=None, props=None, component_type=None): self._group_name = None self._is3dcomponent = None self._mesh_assembly = None + self._show_layout = None + self._fast_transformation = None if name: self._m_name = name else: @@ -123,7 +125,7 @@ def __init__(self, primitives, name=None, props=None, component_type=None): OrderedDict( { "TargetCS": self._target_coordinate_system, - "SubmodelDefinitionName": component, + "SubmodelDefinitionName": self.definition_name, "ComponentPriorityLists": OrderedDict({}), "NextUniqueID": 0, "MoveBackwards": False, @@ -131,7 +133,7 @@ def __init__(self, primitives, name=None, props=None, component_type=None): "DatasetDefinitions": OrderedDict({}), "BasicComponentInfo": OrderedDict( { - "ComponentName": component, + "ComponentName": self.definition_name, "Company": "", "Company URL": "", "Model Number": "", @@ -300,6 +302,66 @@ def mesh_assembly(self, ma): self._primitives.oeditor.GetChildObject(self.name).SetPropValue(key, ma) self._mesh_assembly = ma + @property + def show_layout(self): + """Show layout flag. + + Returns + ------- + bool + ``True`` if show layout is checked and if the component is a Layout Component, + ``None`` if other cases. + + """ + key = "Show Layout" + if self.is3dcomponent and key in self._primitives.oeditor.GetChildObject(self.name).GetPropNames(): + show_layout = self._primitives.oeditor.GetChildObject(self.name).GetPropValue(key) + self._show_layout = show_layout + return show_layout + else: + return None + + @show_layout.setter + def show_layout(self, show_layout): + key = "Show Layout" + if ( + self.is3dcomponent + and isinstance(show_layout, bool) + and key in self._primitives.oeditor.GetChildObject(self.name).GetPropNames() + ): + self._primitives.oeditor.GetChildObject(self.name).SetPropValue(key, show_layout) + self._show_layout = show_layout + + @property + def fast_transformation(self): + """Show layout flag. + + Returns + ------- + bool + ``True`` if fast transformation is checked and if the component is a Layout Component, + ``None`` if other cases. + + """ + key = "Fast Transformation" + if self.is3dcomponent and key in self._primitives.oeditor.GetChildObject(self.name).GetPropNames(): + fast_transformation = self._primitives.oeditor.GetChildObject(self.name).GetPropValue(key) + self._fast_transformation = fast_transformation + return fast_transformation + else: + return None + + @fast_transformation.setter + def fast_transformation(self, fast_transformation): + key = "Fast Transformation" + if ( + self.is3dcomponent + and isinstance(fast_transformation, bool) + and key in self._primitives.oeditor.GetChildObject(self.name).GetPropNames() + ): + self._primitives.oeditor.GetChildObject(self.name).SetPropValue(key, fast_transformation) + self._fast_transformation = fast_transformation + @property def name(self): """Name of the object. @@ -378,7 +440,11 @@ def parts(self): :class:`pyaedt.modeler.Object3d` """ - component_parts = list(self._primitives.oeditor.GetChildObject(self.name).GetChildNames()) + if self.is3dcomponent: + component_parts = list(self._primitives.oeditor.Get3DComponentPartNames(self.name)) + else: + component_parts = list(self._primitives.oeditor.GetChildObject(self.name).GetChildNames()) + parts_id = [ self._primitives._object_names_to_ids[part] for part in self._primitives._object_names_to_ids diff --git a/pyaedt/modeler/cad/object3d.py b/pyaedt/modeler/cad/object3d.py index 6b7bec09bf3..bf1bcc5508b 100644 --- a/pyaedt/modeler/cad/object3d.py +++ b/pyaedt/modeler/cad/object3d.py @@ -892,12 +892,17 @@ def material_name(self): @material_name.setter def material_name(self, mat): matobj = self._primitives._materials.checkifmaterialexists(mat) + mat_value = None if matobj: + mat_value = chr(34) + matobj.name + chr(34) + elif "[" in mat or "(" in mat: + mat_value = mat + if mat_value is not None: if not self.model: self.model = True - vMaterial = ["NAME:Material", "Value:=", chr(34) + matobj.name + chr(34)] + vMaterial = ["NAME:Material", "Value:=", mat_value] self._change_property(vMaterial) - self._material_name = matobj.name.lower() + self._material_name = mat_value.strip('"') self._solve_inside = None else: self.logger.warning("Material %s does not exist.", mat) diff --git a/pyaedt/modeler/cad/polylines.py b/pyaedt/modeler/cad/polylines.py index 581407618d3..8c35a1c437d 100644 --- a/pyaedt/modeler/cad/polylines.py +++ b/pyaedt/modeler/cad/polylines.py @@ -368,7 +368,8 @@ def _update_segments_and_points(self): It will be called only once after opening a new project, then the internal variables are maintained updated. It is a single update call for both properties because they are very similar, - and we can access to the history only once.""" + and we can access to the history only once. + """ def _convert_points(p_in, dest_unit): p_out = [] @@ -441,7 +442,6 @@ def _convert_points(p_in, dest_unit): @property def points(self): """Polyline Points.""" - if self._positions: return self._positions else: @@ -573,7 +573,7 @@ def _point_segment_string_array(self): @pyaedt_function_handler() def _evaluate_arc_angle_extra_points(self, segment, start_point): - """Evaluates the extra points for the ArcAngle segment type. + """Evaluate the extra points for the ArcAngle segment type. It also auto evaluates the arc_plane if it was not specified by the user. segment.extra_points[0] contains the arc mid point (on the arc). segment.extra_points[1] contains the arc end point. @@ -632,22 +632,22 @@ def _segment_array(self, segment_data, start_index=0, start_point=None): """Retrieve a property array for a polyline segment for use in the :class:`pyaedt.modeler.Primitives.Polyline` constructor. - Parameters - ---------- - segment_data : :class:`pyaedt.modeler.Primitives.PolylineSegment` or str - Pointer to the calling object that provides additional functionality - or a string with the segment type ``Line`` or ``Arc``. - start_index : int, string - Starting vertex index of the segment within a compound polyline. The - default is ``0``. - start_point : list, optional - Position of the first point for type ``AngularArc``. The default is - ``None``. Float values are considered in model units. - - Returns - ------ - list - List of properties defining a polyline segment. + Parameters + ---------- + segment_data : :class:`pyaedt.modeler.Primitives.PolylineSegment` or str + Pointer to the calling object that provides additional functionality + or a string with the segment type ``Line`` or ``Arc``. + start_index : int, string + Starting vertex index of the segment within a compound polyline. The + default is ``0``. + start_point : list, optional + Position of the first point for type ``AngularArc``. The default is + ``None``. Float values are considered in model units. + + Returns + ------- + list + List of properties defining a polyline segment. """ if isinstance(segment_data, str): @@ -1033,7 +1033,7 @@ def _get_segment_id_from_point_n(self, pn, at_start, allow_inner_points=False): Returns ------- int, bool - segment id when successful. ``False`` when failed. + Segment id when successful. ``False`` when failed. """ n_points = 0 for i, s in enumerate(self.segment_types): @@ -1068,7 +1068,7 @@ def insert_segment(self, position_list, segment=None): Definition of the segment to insert. For the types ``"Line"`` and ``"Arc"``, use their string values ``"Line"`` and ``"Arc"``. For the types ``"AngularArc"`` and ``"Spline"``, use the :class:`pyaedt.modeler.Primitives.PolylineSegment` - object to define the segment precisely. + object to define the segment precisely. The default is ``None``. Returns ------- @@ -1081,7 +1081,6 @@ def insert_segment(self, position_list, segment=None): >>> oEditor.InsertPolylineSegment """ - # Check for a valid number of points num_points = len(position_list) diff --git a/pyaedt/modeler/geometry_operators.py b/pyaedt/modeler/geometry_operators.py index 9b519f02e0c..7e6aca6e8ec 100644 --- a/pyaedt/modeler/geometry_operators.py +++ b/pyaedt/modeler/geometry_operators.py @@ -47,12 +47,13 @@ def parse_dim_arg(string, scale_to_unit=None, variable_manager=None): Parameters ---------- - string : str - String to convert. For example, ``"2mm"``. - scale_to_unit : str + string : str, optional + String to convert. For example, ``"2mm"``. The default is ``None``. + scale_to_unit : str, optional Units for the value to convert. For example, ``"mm"``. variable_manager : :class:`pyaedt.application.Variables.VariableManager`, optional Try to parse formula and returns numeric value. + The default is ``None``. Returns ------- @@ -73,7 +74,6 @@ def parse_dim_arg(string, scale_to_unit=None, variable_manager=None): >>> 2.0 """ - if type(string) is not str: try: return float(string) @@ -115,8 +115,8 @@ def cs_plane_to_axis_str(val): Parameters ---------- - val : - + val : int + ``PLANE`` enum vélo. Returns ------- @@ -161,7 +161,8 @@ def cs_axis_str(val): Parameters ---------- - val : + val : int + ``AXIS`` enum value. Returns @@ -184,8 +185,8 @@ def draft_type_str(val): Parameters ---------- - val : - + val : int + ``SWEEPDRAFT`` enum value. Returns ------- @@ -267,7 +268,6 @@ def v_cross(a, b): List List of ``[x, y, z]`` coordinates for the result vector. """ - c = [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]] return c @@ -431,8 +431,8 @@ def v_norm(a): Parameters ---------- - a : List - List of ``[x, y, z]`` coordinates for the vector. + a : List + List of ``[x, y, z]`` coordinates for the vector. Returns ------- @@ -529,7 +529,6 @@ def find_point_on_plane(pointlists, direction=0): List """ - if direction <= 2: point = 1e6 for p in pointlists: @@ -728,7 +727,7 @@ def is_projection_inside(a1, a2, b1, b2): @staticmethod @pyaedt_function_handler() def arrays_positions_sum(vertlist1, vertlist2): - """ADD DESCRIPTION. + """Return the sum of two vertices lists. Parameters ---------- @@ -792,7 +791,6 @@ def pointing_to_axis(x_pointing, y_pointing): tuple ``[Xx, Xy, Xz], [Yx, Yy, Yz], [Zx, Zy, Zz]`` of the three axes (normalized). """ - zpt = GeometryOperators.v_cross(x_pointing, y_pointing) ypt = GeometryOperators.v_cross(zpt, x_pointing) @@ -1364,16 +1362,16 @@ def get_numeric(s): @staticmethod @pyaedt_function_handler() def is_small(s): - """ - Return True if the number represented by s is zero (i.e very small). + """Return ``True`` if the number represented by s is zero (i.e very small). Parameters ---------- - s, numeric or str + s : numeric or str Variable value. Returns ------- + bool """ n = GeometryOperators.get_numeric(s) diff --git a/pyaedt/modules/Boundary.py b/pyaedt/modules/Boundary.py index c1c27f709fa..659e6b594ff 100644 --- a/pyaedt/modules/Boundary.py +++ b/pyaedt/modules/Boundary.py @@ -1603,8 +1603,12 @@ def name(self): @name.setter def name(self, value): - self._name = value + if "Port" in self.props: + self.auto_update = False + self.props["Port"] = value + self.auto_update = True self.update() + self._name = value @pyaedt_function_handler() def _get_args(self, props=None): @@ -1648,7 +1652,10 @@ def update(self): """ updated = False for el in list(self.props.keys()): - if el in self._app.oeditor.GetProperties("EM Design", "Excitations:{}".format(self.name)) and self.props[ + if el == "Port" and self._name != self.props[el]: + self._app.oeditor.SetPropertyValue("EM Design", "Excitations:" + self.name, el, self.props[el]) + self._name = self.props[el] + elif el in self._app.oeditor.GetProperties("EM Design", "Excitations:{}".format(self.name)) and self.props[ el ] != self._app.oeditor.GetPropertyValue("EM Design", "Excitations:" + self.name, el): self._app.oeditor.SetPropertyValue("EM Design", "Excitations:" + self.name, el, self.props[el]) diff --git a/pyaedt/modules/SolveSetup.py b/pyaedt/modules/SolveSetup.py index 66ada7e7de8..a488d37a96b 100644 --- a/pyaedt/modules/SolveSetup.py +++ b/pyaedt/modules/SolveSetup.py @@ -3077,6 +3077,24 @@ def dc_enabled(self, value): self._dc_enabled = value self.update() + @property + def dc_resistance_only(self): + """Get/Set the DC Resistance Only or Resistance/Inductance calculatio in active Q3D setup. + + Returns + ------- + bool + """ + try: + return self.props["DC"]["SolveResOnly"] + except KeyError: + return False + + @dc_resistance_only.setter + def dc_resistance_only(self, value): + if self.dc_enabled: + self.props["DC"]["SolveResOnly"] = value + @pyaedt_function_handler() def update(self, update_dictionary=None): """Update the setup based on either the class argument or a dictionary. diff --git a/pyproject.toml b/pyproject.toml index e6bc03f4766..201d7f0cb4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "flit_core.buildapi" name = "pyaedt" dynamic = ["version"] description = "Higher-Level Pythonic Ansys Electronics Desktop Framework" -readme = "README.rst" +readme = "README.md" requires-python = ">=3.7,<4" license = {file = "LICENSE"} authors = [{name = "ANSYS, Inc.", email = "pyansys.core@ansys.com"}] @@ -35,7 +35,8 @@ dependencies = [ [project.optional-dependencies] tests = [ - "ipython==8.12.0", + "ipython==8.13.0", + "imageio==2.28.0", "joblib==1.2.0", "matplotlib==3.5.3; python_version <= '3.7'", "matplotlib==3.7.1; python_version > '3.7'", @@ -48,7 +49,7 @@ tests = [ "pytest==7.3.1", "pytest-cov==4.0.0", "pytest-xdist==3.2.1", - "pyvista==0.38.5", + "pyvista==0.38.6", "scikit-learn==1.2.2", "SRTM.py", "utm", @@ -59,7 +60,7 @@ doc = [ "ansys-sphinx-theme==0.9.8", "imageio==2.28.0", "imageio-ffmpeg==0.4.8", - "ipython==8.12.0", + "ipython==8.13.0", "ipywidgets==8.0.6", "joblib==1.2.0", "jupyterlab==3.6.3", @@ -70,10 +71,10 @@ doc = [ "osmnx", "pypandoc==1.11", "pytest-sphinx==0.5.0", - "pyvista==0.38.5", + "pyvista==0.38.6", "recommonmark==0.7.1", "scikit-learn==1.2.2", - "Sphinx==6.2.1", + "Sphinx==7.0.0", "sphinx-autobuild==2021.3.14", "sphinx-autodoc-typehints==1.23.0", "sphinx-copybutton==0.5.2", @@ -86,6 +87,7 @@ doc = [ "openpyxl==3.1.2", ] full = [ + "imageio==2.28.0", "matplotlib==3.5.3; python_version <= '3.7'", "matplotlib==3.7.1; python_version > '3.7'", "numpy==1.21.6; python_version <= '3.7'", @@ -93,13 +95,14 @@ full = [ "pandas==1.3.5; python_version <= '3.7'", "pandas==2.0.1; python_version > '3.7'", "osmnx", - "pyvista==0.38.5", + "pyvista==0.38.6", "SRTM.py", "utm", "scikit-rf", "openpyxl==3.1.2", ] all = [ + "imageio==2.28.0", "matplotlib==3.5.3; python_version <= '3.7'", "matplotlib==3.7.1; python_version > '3.7'", "numpy==1.21.6; python_version <= '3.7'", @@ -107,7 +110,7 @@ all = [ "pandas==1.3.5; python_version <= '3.7'", "pandas==2.0.1; python_version > '3.7'", "osmnx", - "pyvista==0.38.5", + "pyvista==0.38.6", "SRTM.py", "utm", "scikit-rf",